/**
|
* 项目管理字段映射
|
*
|
* 后端 projectManagement 的字段名与业务含义差异很大,映射关系取自管理端
|
* views/projectManagement/Management/components/formDia.vue 与 projectDetail.vue:
|
*
|
* info 层 no=单据编号、title=项目名称、clientName=客户名称、
|
* establishTime=立项日期、source=项目来源、managerName=立项人、
|
* orderAmount=项目金额、reviewStatus=审核状态、
|
* status=单据状态、stage=计划状态、planStartTime/planEndTime=计划起止
|
* contractInfo 层 name=联系人、sex='男'/'女'、lineaFissa=企业微信、wx=微信、
|
* origineEtnica=地址、rappresentanteLegale=备注
|
* shippingAddress 层 consignee=收货人、contract=联系电话、address=地址
|
* teamList 项 userId/userName、userRoleId/userRoleName、joinTime/departTime、
|
* contact=联系方式
|
*
|
* 双向转换全部收敛在这里,页面里不出现裸字段名。
|
* 详情返回:detail.info / detail.shippingAddress / detail.contractInfo /
|
* detail.salesLedgerProductList / detail.info.teamList / detail.info.attachmentList
|
* 提交结构:{ info, shippingAddress, contractInfo, salesLedgerProductList }
|
*/
|
|
export const GENDER_OPTIONS = [
|
{ name: "男", value: "1" },
|
{ name: "女", value: "2" },
|
];
|
|
let uidSeed = 1;
|
const nextUid = () => `p_${uidSeed++}`;
|
|
const toStr = value =>
|
value === undefined || value === null ? "" : String(value);
|
|
// 后端存的是中文性别,表单选的是 '1'/'2'
|
const genderToValue = sex => (sex === "男" ? "1" : sex === "女" ? "2" : "");
|
const genderFromValue = value =>
|
value === "1" ? "男" : value === "2" ? "女" : "";
|
|
// 字典值后端可能返回数字,选择器按字符串比对
|
const dictValue = value =>
|
value === undefined || value === null || value === "" ? "" : String(value);
|
|
const idOrNull = value =>
|
value === undefined || value === null || value === "" ? null : value;
|
|
// 收货地址、产品子表都可能为空对象或缺失,统一取数组
|
const asArray = value => (Array.isArray(value) ? value : []);
|
|
/** 列表行:列表接口字段 → 展示字段 */
|
export function toListRow(record = {}) {
|
return {
|
id: record.id,
|
billNo: record.no ?? record.billNo ?? "",
|
projectName: record.title ?? record.projectName ?? "",
|
billStatus: dictValue(record.billStatus ?? record.status),
|
auditStatus: dictValue(record.reviewStatus ?? record.auditStatus),
|
projectStage: dictValue(record.stage ?? record.projectStage),
|
customerName: record.clientName ?? record.customerName ?? "",
|
setupDate: record.establishTime ?? record.setupDate ?? "",
|
projectSource: record.source ?? record.projectSource ?? "",
|
projectClassification:
|
record.departmentName ?? record.projectClassification ?? "",
|
projectAmount: record.orderAmount,
|
salesContractCount: record.salesContractCount,
|
raw: record,
|
};
|
}
|
|
/** 详情接口返回 → 详情页各分区 */
|
export function toDetail(detail = {}) {
|
const info = detail?.info || {};
|
const shippingAddress = detail?.shippingAddress || {};
|
const contractInfo = detail?.contractInfo || {};
|
|
const rawPhase =
|
detail?.phaseList ||
|
detail?.projectPhaseList ||
|
detail?.projectStageList ||
|
info?.phaseList ||
|
info?.projectPhaseList;
|
|
return {
|
info,
|
shippingAddress: {
|
consignee: shippingAddress.consignee ?? "",
|
contract: shippingAddress.contract ?? "",
|
address: shippingAddress.address ?? "",
|
},
|
contractInfo: {
|
name: contractInfo.name ?? "",
|
sex: contractInfo.sex ?? "",
|
birthday: contractInfo.birthday ?? "",
|
department: contractInfo.department ?? "",
|
job: contractInfo.job ?? "",
|
phoneNumber: contractInfo.phoneNumber ?? "",
|
email: contractInfo.email ?? "",
|
qq: contractInfo.qq ?? "",
|
wx: contractInfo.wx ?? "",
|
// 后端这两个字段名与业务含义完全无关,UI 上按业务含义展示
|
workWechat: contractInfo.lineaFissa ?? "",
|
address: contractInfo.origineEtnica ?? "",
|
remark: contractInfo.rappresentanteLegale ?? "",
|
},
|
productList: asArray(detail?.salesLedgerProductList),
|
teamList: asArray(info.teamList),
|
attachments: asArray(info.attachmentList).map(a => ({
|
id: a.id ?? a.fileId,
|
name: a.fileName ?? a.name ?? "",
|
url: a.url ?? a.fileUrl ?? a.path ?? "",
|
})),
|
// 表单里没有可编辑的阶段子表(后端 /save 不吃阶段),这里只给详情页只读展示用
|
phaseList: asArray(rawPhase),
|
};
|
}
|
|
/** 详情接口返回 → 表单(含子表) */
|
export function toForm(detail = {}) {
|
const d = toDetail(detail);
|
const info = d.info;
|
const contractInfo = detail?.contractInfo || {};
|
const shippingAddress = detail?.shippingAddress || {};
|
|
const form = {
|
id: info.id,
|
billNo: info.no ?? "",
|
projectName: info.title ?? "",
|
clientId: info.clientId ?? "",
|
customerName: info.clientName ?? "",
|
parentProjectId: info.projectManagementInfoParentId ?? "",
|
parentProjectName: info.projectManagementInfoParentName ?? "",
|
projectManagementPlanId: info.projectManagementPlanId ?? "",
|
projectSource: info.source ?? "",
|
managerId: info.managerId ?? "",
|
creatorName: info.managerName ?? "",
|
salesmanId: info.salesmanId ?? "",
|
salesmanName: info.salesmanName ?? "",
|
departmentId: info.departmentId ?? "",
|
departmentName: info.departmentName ?? "",
|
setupDate: info.establishTime ?? "",
|
planStartDate: info.planStartTime ?? "",
|
planEndDate: info.planEndTime ?? "",
|
actualStartDate: info.actualStartTime ?? "",
|
actualEndDate: info.actualEndTime ?? "",
|
orderDate: info.orderDate ?? "",
|
projectAmount: info.orderAmount ?? "",
|
billStatus: dictValue(info.status),
|
auditStatus: dictValue(info.reviewStatus),
|
projectStage: dictValue(info.stage ?? info.projectStage),
|
remark: info.remark ?? "",
|
contactName: contractInfo.name ?? "",
|
contactGender: genderToValue(contractInfo.sex),
|
contactBirthday: contractInfo.birthday ?? "",
|
contactDept: contractInfo.department ?? "",
|
contactJob: contractInfo.job ?? "",
|
contactMobile: contractInfo.phoneNumber ?? "",
|
contactEmail: contractInfo.email ?? "",
|
contactQq: contractInfo.qq ?? "",
|
contactWechat: contractInfo.wx ?? "",
|
contactWorkWechat: contractInfo.lineaFissa ?? "",
|
contactAddress: contractInfo.origineEtnica ?? "",
|
contactRemark: contractInfo.rappresentanteLegale ?? "",
|
};
|
|
// 收货地址后端只存一条,这里用单行编辑,提交时取第一行
|
const addressList = shippingAddress?.address
|
? [
|
{
|
receiver: shippingAddress.consignee ?? "",
|
phone: shippingAddress.contract ?? "",
|
address: shippingAddress.address ?? "",
|
},
|
]
|
: [];
|
|
const teamList = d.teamList.map(t => ({
|
memberId: t.userId ?? "",
|
memberName: t.userName ?? "",
|
roleId: t.userRoleId ?? "",
|
roleName: t.userRoleName ?? "",
|
enterDate: t.joinTime ?? "",
|
leaveDate: t.departTime ?? "",
|
phone: t.contact ?? "",
|
remark: t.remark ?? "",
|
}));
|
|
const productList = d.productList.map(p => ({
|
uid: nextUid(),
|
productCategoryId: p.productCategoryId ?? "",
|
productCategory: p.productCategory ?? "",
|
productModelId: p.productModelId ?? "",
|
specificationModel: p.specificationModel ?? "",
|
unit: p.unit ?? "",
|
quantity: p.quantity ?? "",
|
taxRate: p.taxRate ?? "",
|
taxInclusiveUnitPrice: p.taxInclusiveUnitPrice ?? "",
|
taxExclusiveTotalPrice: p.taxExclusiveTotalPrice ?? "",
|
invoiceType: p.invoiceType ?? "",
|
}));
|
|
return { form, teamList, addressList, productList, attachments: d.attachments };
|
}
|
|
/** 表单 → 提交结构 */
|
export function toPayload({
|
form,
|
teamList,
|
addressList,
|
productList,
|
attachmentIds,
|
}) {
|
const shippingRow = asArray(addressList)[0] || {};
|
|
const info = {
|
id: form.id ?? null,
|
no: form.billNo,
|
title: form.projectName,
|
clientId: idOrNull(form.clientId),
|
clientName: form.customerName,
|
projectManagementInfoParentId: idOrNull(form.parentProjectId),
|
projectManagementPlanId: idOrNull(form.projectManagementPlanId),
|
establishTime: form.setupDate,
|
source: form.projectSource,
|
managerId: idOrNull(form.managerId),
|
managerName: form.creatorName,
|
salesmanId: idOrNull(form.salesmanId),
|
salesmanName: form.salesmanName || "",
|
departmentId: idOrNull(form.departmentId),
|
departmentName: form.departmentName || "",
|
planStartTime: form.planStartDate,
|
planEndTime: form.planEndDate,
|
actualStartTime: form.actualStartDate,
|
actualEndTime: form.actualEndDate,
|
orderDate: form.orderDate,
|
orderAmount: Number(form.projectAmount) || 0,
|
status: form.billStatus === "" ? null : Number(form.billStatus),
|
reviewStatus: form.auditStatus === "" ? null : Number(form.auditStatus),
|
stage: form.projectStage === "" ? null : Number(form.projectStage),
|
remark: form.remark,
|
attachmentIds: asArray(attachmentIds),
|
// userName / userRoleName 是下拉 label,后端不反查,必须前端带上
|
teamList: asArray(teamList).map(t => ({
|
userId: t.memberId,
|
userName: t.memberName,
|
userRoleId: t.roleId,
|
userRoleName: t.roleName,
|
joinTime: t.enterDate,
|
departTime: t.leaveDate,
|
contact: t.phone,
|
remark: t.remark,
|
})),
|
};
|
|
const shippingAddress = {
|
id: undefined,
|
consignee: shippingRow.receiver,
|
contract: shippingRow.phone,
|
address: shippingRow.address,
|
};
|
|
const contractInfo = {
|
id: undefined,
|
name: form.contactName,
|
sex: genderFromValue(form.contactGender),
|
birthday: form.contactBirthday,
|
department: form.contactDept,
|
job: form.contactJob,
|
phoneNumber: form.contactMobile,
|
email: form.contactEmail,
|
qq: form.contactQq,
|
wx: form.contactWechat,
|
lineaFissa: form.contactWorkWechat,
|
origineEtnica: form.contactAddress,
|
rappresentanteLegale: form.contactRemark,
|
};
|
|
return {
|
info,
|
shippingAddress,
|
contractInfo,
|
salesLedgerProductList: asArray(productList).map(p => ({
|
productCategoryId: p.productCategoryId,
|
productCategory: p.productCategory,
|
productModelId: p.productModelId,
|
specificationModel: p.specificationModel,
|
unit: p.unit,
|
quantity: Number(p.quantity) || 0,
|
taxRate: p.taxRate,
|
taxInclusiveUnitPrice: Number(p.taxInclusiveUnitPrice) || 0,
|
taxInclusiveTotalPrice: productInclusiveTotal(p),
|
taxExclusiveTotalPrice: Number(p.taxExclusiveTotalPrice) || 0,
|
invoiceType: p.invoiceType,
|
})),
|
};
|
}
|
|
/** 含税总价 = 含税单价 × 数量 */
|
export function productInclusiveTotal(row) {
|
const total =
|
Number(row?.taxInclusiveUnitPrice || 0) * Number(row?.quantity || 0);
|
return Number(total.toFixed(2));
|
}
|
|
/** 详情页展示用的空产品行 */
|
export function createEmptyProduct() {
|
return {
|
uid: nextUid(),
|
productCategoryId: "",
|
productCategory: "",
|
productModelId: "",
|
specificationModel: "",
|
unit: "",
|
quantity: "",
|
taxRate: "",
|
taxInclusiveUnitPrice: "",
|
taxExclusiveTotalPrice: "",
|
invoiceType: "",
|
};
|
}
|