diff --git a/src/views/contract/launch/components/ContractPartyPlaceholderModal.vue b/src/views/contract/launch/components/ContractPartyPlaceholderModal.vue index ec03783..a292f7f 100644 --- a/src/views/contract/launch/components/ContractPartyPlaceholderModal.vue +++ b/src/views/contract/launch/components/ContractPartyPlaceholderModal.vue @@ -28,921 +28,1066 @@
-
-
-
-
合同主信息
-
- -
-
我方
- -
-
- 我方{{ - detailState.partyState.partyA.companyName || "/" - }} -
-
- 电话{{ - detailState.partyState.partyA.contactPhone || "/" - }} -
-
- 公司地址{{ - detailState.partyState.partyA.registeredAddress || "/" - }} -
-
- 银行/账号{{ - detailState.partyState.partyA.bankAccount || "/" - }} -
- - - +
+
+
+
合同主信息
- - - 备选开票抬头选择 - -
-
-
相对方
- - 添加 - -
- -
-
-
{{ party.displayName }}
+
+
我方主体
- 删除 + 添加
-
- - - - +
+ 当前登录主体必须包含在我方主体中
-
-
- {{ party.displayName }}{{ - party.companyName || "/" - }} -
-
- 电话{{ party.contactPhone || "/" }} -
-
- 公司地址{{ party.registeredAddress || "/" }} -
-
- 银行/账号{{ party.bankAccount || "/" }} -
- - - -
-
-
- 暂无相对方,请点击右上角“添加” -
- -
- - - - - - - - - -
- -
-
合同起止时间
- - 固定期限 - 无固定期限 -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - - - - - - -
-
-
- -
-
-
-
- - 基本信息 -
-
- -
-
- 合同名称{{ detailState.contractName || "未填写" }} -
-
- 我方{{ - detailState.partyState.partyA.companyName || "未填写" - }} -
-
- 合同编号{{ detailState.contractCode || "未填写" }} -
-
- 相对方{{ counterpartySummaryText }} -
-
- -
-
合同正文
-
-
- {{ contractBodyDisplayText }} -
-
- {{ contractBodyHint }} -
-
- - - 下载 - - -
- -
-
签订依据附件
- -
签订依据文件,对应法务 contractGistFile
-
-
- +
+
+ {{ party.displayName }} +
- 下载 {{ item.name || item.fileName }} + 删除 - -
-
- -
-
授权委托书附件
- -
当前签订方式需要上传授权委托书附件
-
-
- - - 下载 {{ item.name || item.fileName }} - - -
-
- -
-
其他附件
- -
其他附件(可上传多个文件)
-
-
- - - 下载 {{ item.name || item.fileName }} - - -
-
- -
-
已签署文件
-
-
- {{ detailState.signedFileName || "已签署文件" }}
-
- 当前合同的签署完成版本 -
-
- - - 下载 - - -
- - - - - - - - - - - - - - - - - -
- - 合同属性 -
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
- 是否需修订条款 - - - - -
-
- 是否涉及港澳台 - - - - -
-
- 是否公开招标 - - - - -
-
- 签约主体是否涉外 - - - - -
-
- 是否列入当年预算 - - - - -
-
- 是否共享 - - - - -
-
- 是否重大合同 - - - - -
-
- -
-
额外金额
-
- -
-
- -
-
- -
-
- - 履行计划 -
-
+ + +
+
+
+ {{ party.displayName }}{{ party.companyName || "/" }} +
+
+ 主体编码{{ party.subjectCode || "/" }} +
+
+ 电话{{ party.contactPhone || "/" }} +
+
+ 公司地址{{ party.registeredAddress || "/" }} +
+
+ 银行/账号{{ party.bankAccount || "/" }} +
+ + + +
- 新增履行计划 + 备选开票抬头选择
- - - - - - - - - - - +
相对方
+ - -
- - - - - - - - - - - - - +
+ + +
+ 当前为单方协议,无需维护相对方 +
+ +
+ + + + + + + + + +
+ +
+
合同起止时间
+ + 固定期限 + 无固定期限 + +
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + +
-
-
+ +
+
+
+
+ + 基本信息 +
+
+ +
+
+ 合同名称{{ detailState.contractName || "未填写" }} +
+
+ 我方{{ ourPartySummaryText }} +
+
+ 合同编号{{ detailState.contractCode || "未填写" }} +
+
+ 相对方{{ counterpartySummaryText }} +
+
+ +
+
合同正文
+
+
+ {{ contractBodyDisplayText }} +
+
+ {{ contractBodyHint }} +
+
+ + + 下载 + + +
+ +
+
签订依据附件
+ +
+ 签订依据文件,对应法务 contractGistFile +
+
+
+ + + 下载 {{ item.name || item.fileName }} + + +
+
+ +
+
授权委托书附件
+ +
+ 当前签订方式需要上传授权委托书附件 +
+
+
+ + + 下载 {{ item.name || item.fileName }} + + +
+
+ +
+
其他附件
+ +
+ 其他附件(可上传多个文件) +
+
+
+ + + 下载 {{ item.name || item.fileName }} + + +
+
+ +
+
已签署文件
+
+
+ {{ detailState.signedFileName || "已签署文件" }} +
+
+ 当前合同的签署完成版本 +
+
+ + + 下载 + + +
+ + + + + + + + + + + + + + + + + +
+ + 合同属性 +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ 是否需修订条款 + + + + +
+
+ 是否涉及港澳台 + + + + +
+
+ 是否公开招标 + + + + +
+
+ 签约主体是否涉外 + + + + +
+
+ 是否列入当年预算 + + + + +
+
+ 是否共享 + + + + +
+
+ 是否重大合同 + + + + +
+
+ 是否单方协议 + + + + +
+
+ +
+
额外金额
+
+ +
+
+ +
+
+ +
+
+ + 履行计划 +
+
+ + + 新增履行计划 + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
@@ -1044,7 +1189,6 @@ const contractCompanyOptions = ref([ // ========== 合同相关字典 ========== - const mandateTypeOptions = [ { label: "海铁联运委托", value: "海铁联运委托" }, { label: "一站式物流委托", value: "一站式物流委托" }, @@ -1069,8 +1213,8 @@ const paymentMethodOptions = [ ]; const SELF_COMPANY_NAME = "浙港物流平台有限公司"; -const MAX_COUNTERPARTY_COUNT = 5; -const COUNTERPARTY_ROLE_CODES = ["B", "C", "D", "E", "F"]; +const PARTY_ROLE_OUR = "OUR"; +const PARTY_ROLE_OPPOSITE = "OPPOSITE"; // ========== 工具函数 ========== @@ -1082,7 +1226,9 @@ function formatDateTimeValue(value, endOfDay = false) { function normalizeFileItem(item = {}) { return { - uid: item.uid || `file_${Date.now()}_${Math.random().toString(16).slice(2, 8)}`, + uid: + item.uid || + `file_${Date.now()}_${Math.random().toString(16).slice(2, 8)}`, name: item.name || item.fileName || "", size: item.size ?? item.fileSize ?? 0, status: item.status || "done", @@ -1094,8 +1240,10 @@ function normalizeFileItem(item = {}) { function normalizeBooleanValue(value) { if (value === true || value === false) return value; - if (value === 1 || value === "1" || value === "Y" || value === "true") return true; - if (value === 0 || value === "0" || value === "N" || value === "false") return false; + if (value === 1 || value === "1" || value === "Y" || value === "true") + return true; + if (value === 0 || value === "0" || value === "N" || value === "false") + return false; return false; } @@ -1136,7 +1284,8 @@ function getFileNameFromDetail(source, category) { function splitFilesByCategory(source = {}) { const sourceFiles = source.fileList || []; - const bodyFile = sourceFiles.find((item) => item.fileCategory === "body") || {}; + const bodyFile = + sourceFiles.find((item) => item.fileCategory === "body") || {}; return { contractBodyFileName: @@ -1203,7 +1352,8 @@ function restoreExtraAmounts(source = {}, current = {}) { extraAmountFieldOptions.forEach((item) => { const amount = source[item.amountField]; if (selectedCodes.includes(item.code) || amount || amount === 0) { - result[`${item.key}Enabled`] = selectedCodes.includes(item.code) || amount > 0; + result[`${item.key}Enabled`] = + selectedCodes.includes(item.code) || amount > 0; if (amount !== undefined && amount !== null) { result[item.key] = amount; } @@ -1213,120 +1363,86 @@ function restoreExtraAmounts(source = {}, current = {}) { return result; } -/** 深拷贝合同方状态 */ -function copyPartyState(input) { - const source = input || {}; - const listState = buildPartyStateFromList(source.partyList); - const sourcePartyA = source.partyA || source.parties?.A || {}; - const sourcePartyB = source.partyB || source.parties?.B || {}; - const sourcePartyC = source.partyC || source.parties?.C || {}; - const sourceCounterpartyList = Array.isArray(source.counterpartyList) - ? source.counterpartyList - : [ - String(sourcePartyB.companyName || "").trim() ? { ...sourcePartyB, roleKey: "B" } : null, - String(sourcePartyC.companyName || "").trim() ? { ...sourcePartyC, roleKey: "C" } : null, - ].filter(Boolean); - const state = { - isThreePartyContract: Boolean(source.isThreePartyContract), - partyA: { - roleKey: "A", - roleName: "我方", - displayName: "我方", - ...sourcePartyA, - companyName: sourcePartyA.companyName || SELF_COMPANY_NAME, - }, - partyB: { - roleKey: "B", - roleName: "相对方", - displayName: "相对方1", - companyName: "", - ...sourcePartyB, - }, - partyC: { - roleKey: "C", - roleName: "相对方", - displayName: "相对方2", - companyName: "", - ...sourcePartyC, - }, - counterpartyList: sourceCounterpartyList.map((item, index) => ({ - ...createEmptyCounterparty(item.roleKey || COUNTERPARTY_ROLE_CODES[index]), - ...item, - roleKey: item.roleKey || COUNTERPARTY_ROLE_CODES[index], - displayName: `相对方${index + 1}`, - })), +function createLocalId(prefix = "party") { + return `${prefix}_${Date.now()}_${Math.random().toString(16).slice(2, 8)}`; +} + +function createEmptyParty(roleCode, index = 0) { + const isOur = roleCode === PARTY_ROLE_OUR; + return { + localId: createLocalId(isOur ? "our" : "opposite"), + roleCode, + roleName: isOur ? "我方" : "相对方", + displayName: isOur ? `我方主体${index + 1}` : `相对方${index + 1}`, + subjectCode: "", + partnerId: null, + customerSysid: null, + oppositeId: "", + companyName: "", + companyType: "", + oppositeCharacter: "", + oppCharacter: "", + companyNature: "", + organizationNature: "", + enterpriseNatureLevel2: "", + contactName: "", + contactPhone: "", + contactEmail: "", + contactAddress: "", + registeredAddress: "", + legalRepresentative: "", + creditCode: "", + bankName: "", + bankAccount: "", + naturalPersonIdType: "", + naturalPersonIdNumber: "", + isGroupInternal: false, + isHongKongMacaoTaiwan: false, + invoiceTitleOptions: [], }; - if (listState) { - state.partyA = { ...state.partyA, ...listState.partyA }; - state.counterpartyList = listState.counterpartyList.map((item) => ({ ...item })); - state.partyB = { ...state.partyB, ...(state.counterpartyList[0] || {}) }; - state.partyC = { ...state.partyC, ...(state.counterpartyList[1] || {}) }; - state.isThreePartyContract = listState.isThreePartyContract; - } else { - state.partyB = { ...state.partyB, ...(state.counterpartyList[0] || {}) }; - state.partyC = { ...state.partyC, ...(state.counterpartyList[1] || {}) }; - } - return state; } function mapLaunchPartyToFormParty(item = {}) { - const roleIndex = COUNTERPARTY_ROLE_CODES.indexOf(item.roleCode || "B"); + const roleCode = + String(item.roleCode || PARTY_ROLE_OPPOSITE).toUpperCase() === + PARTY_ROLE_OUR + ? PARTY_ROLE_OUR + : PARTY_ROLE_OPPOSITE; return { - roleKey: item.roleCode || "B", - roleName: item.roleName || "", - displayName: - item.roleCode === "A" ? "我方" : `相对方${roleIndex >= 0 ? roleIndex + 1 : 1}`, - partnerId: item.partnerId || null, - customerSysid: item.partnerId || null, - oppositeId: item.oppositeId || "", - companyName: item.companyName || "", - companyType: item.companyType || "", - oppositeCharacter: item.oppositeCharacter || "", - oppCharacter: item.oppositeCharacter || "", - companyNature: getCompanyNatureByOppositeCharacter(item.oppositeCharacter), - contactName: item.contactName || "", - contactPhone: item.contactPhone || "", - contactEmail: item.contactEmail || "", - contactAddress: item.contactAddress || "", - registeredAddress: item.registeredAddress || "", - legalRepresentative: item.legalRepresentative || "", - creditCode: item.creditCode || "", - bankName: item.bankName || "", - bankAccount: item.bankAccount || "", - naturalPersonIdType: item.naturalPersonIdType || "", - naturalPersonIdNumber: item.naturalPersonIdNumber || "", - isGroupInternal: item.isGroupInternal === "1" || item.isGroupInternal === true, - isHongKongMacaoTaiwan: - item.isHongKongMacaoTaiwan === "1" || item.isHongKongMacaoTaiwan === true, + ...createEmptyParty(roleCode), + ...item, + localId: + item.localId || + createLocalId(roleCode === PARTY_ROLE_OUR ? "our" : "opposite"), + roleCode, + roleName: roleCode === PARTY_ROLE_OUR ? "我方" : "相对方", + subjectCode: item.subjectCode || "", + companyNature: getCompanyNatureByOppositeCharacter( + item.oppositeCharacter || item.oppCharacter, + ), + oppCharacter: item.oppositeCharacter || item.oppCharacter || "", + oppositeCharacter: item.oppositeCharacter || item.oppCharacter || "", + displayName: "", + customerSysid: item.customerSysid || item.partnerId || null, }; } function buildPartyStateFromList(list = []) { if (!Array.isArray(list) || !list.length) return null; const result = { - isThreePartyContract: false, - partyA: {}, - partyB: {}, - partyC: {}, + ourPartyList: [], counterpartyList: [], }; list.forEach((item) => { if (!item?.roleCode) return; - if (item.roleCode === "A") { - result.partyA = mapLaunchPartyToFormParty(item); + const mapped = mapLaunchPartyToFormParty(item); + if (mapped.roleCode === PARTY_ROLE_OUR) { + result.ourPartyList.push(mapped); } else { - result.counterpartyList.push(mapLaunchPartyToFormParty(item)); + result.counterpartyList.push(mapped); } }); - result.counterpartyList = result.counterpartyList.map((item, index) => ({ - ...item, - roleKey: item.roleKey || COUNTERPARTY_ROLE_CODES[index], - displayName: `相对方${index + 1}`, - })); - result.partyB = result.counterpartyList[0] || {}; - result.partyC = result.counterpartyList[1] || {}; - result.isThreePartyContract = result.counterpartyList.length > 1; - return result; + return normalizePartyState(result); } function getCompanyNatureByOppositeCharacter(value) { @@ -1339,17 +1455,13 @@ function getCompanyNatureByOppositeCharacter(value) { return map[String(value || "")] || ""; } -function createEmptyCounterparty(roleKey) { - return { - roleKey, - roleName: "相对方", - displayName: `相对方${COUNTERPARTY_ROLE_CODES.indexOf(roleKey) + 1}`, - companyName: "", - }; -} - function isPartyFilled(party = {}) { - const ignoreKeys = new Set(["roleKey", "roleName", "displayName"]); + const ignoreKeys = new Set([ + "localId", + "roleCode", + "roleName", + "displayName", + ]); return Object.entries(party).some(([key, value]) => { if (ignoreKeys.has(key)) return false; if (Array.isArray(value)) return value.length > 0; @@ -1358,38 +1470,89 @@ function isPartyFilled(party = {}) { }); } -function buildCounterpartyRoleKeys(partyState) { - const state = partyState || {}; - const sourceList = Array.isArray(state.counterpartyList) - ? state.counterpartyList - : COUNTERPARTY_ROLE_CODES.map((roleKey) => state[`party${roleKey}`]).filter(Boolean); - return sourceList +function normalizePartyArray(list = [], roleCode) { + return (Array.isArray(list) ? list : []) .filter((party) => isPartyFilled(party)) - .map((party, index) => party.roleKey || COUNTERPARTY_ROLE_CODES[index]) - .slice(0, MAX_COUNTERPARTY_COUNT); + .map((party, index) => ({ + ...createEmptyParty(roleCode, index), + ...party, + localId: + party.localId || + createLocalId(roleCode === PARTY_ROLE_OUR ? "our" : "opposite"), + roleCode, + roleName: roleCode === PARTY_ROLE_OUR ? "我方" : "相对方", + displayName: + roleCode === PARTY_ROLE_OUR + ? `我方主体${index + 1}` + : `相对方${index + 1}`, + })); } function normalizePartyState(partyState) { - const state = copyPartyState(partyState); - state.partyA.companyName = state.partyA.companyName || SELF_COMPANY_NAME; - state.counterpartyList = (Array.isArray(state.counterpartyList) ? state.counterpartyList : []) - .filter((party) => isPartyFilled(party)) - .slice(0, MAX_COUNTERPARTY_COUNT) - .map((party, index) => ({ - ...createEmptyCounterparty(party.roleKey || COUNTERPARTY_ROLE_CODES[index]), - ...party, - roleKey: party.roleKey || COUNTERPARTY_ROLE_CODES[index], - displayName: `相对方${index + 1}`, - })); - state.partyB = state.counterpartyList[0] || createEmptyCounterparty("B"); - state.partyC = state.counterpartyList[1] || createEmptyCounterparty("C"); - state.isThreePartyContract = state.counterpartyList.length > 1; + const state = partyState || {}; + const ourPartyList = normalizePartyArray( + Array.isArray(state.ourPartyList) + ? state.ourPartyList + : state.partyA + ? [state.partyA] + : [], + PARTY_ROLE_OUR, + ); + const normalizedOurList = ourPartyList.length + ? ourPartyList + : [ + { + ...createEmptyParty(PARTY_ROLE_OUR, 0), + companyName: SELF_COMPANY_NAME, + }, + ]; + const counterpartyList = normalizePartyArray( + state.counterpartyList, + PARTY_ROLE_OPPOSITE, + ); + return { + partyA: { ...normalizedOurList[0] }, + ourPartyList: normalizedOurList, + counterpartyList, + isThreePartyContract: counterpartyList.length > 1, + }; +} + +function applySubjectCodesToPartyState(partyState, signingSubjectCode) { + const state = normalizePartyState(partyState); + const codes = String(signingSubjectCode || "") + .split(",") + .map((item) => item.trim()) + .filter(Boolean); + state.ourPartyList = state.ourPartyList.map((party, index) => ({ + ...party, + subjectCode: + party.subjectCode || (codes.length === 1 ? codes[0] : codes[index] || ""), + })); + state.partyA = state.ourPartyList[0] || createEmptyParty(PARTY_ROLE_OUR, 0); return state; } -function getPartyByRoleKey(partyState, roleKey) { - if (roleKey === "A") return partyState.partyA; - return (partyState.counterpartyList || []).find((item) => item.roleKey === roleKey); +function copyPartyState(input) { + const source = input || {}; + const listState = buildPartyStateFromList(source.partyList); + if (listState) { + return applySubjectCodesToPartyState( + listState, + source.signingSubjectCode ?? "", + ); + } + return normalizePartyState({ + ourPartyList: source.ourPartyList || (source.partyA ? [source.partyA] : []), + counterpartyList: source.counterpartyList || [], + isThreePartyContract: source.isThreePartyContract, + }); +} + +function getPartyByLocalId(list = [], localId) { + return (Array.isArray(list) ? list : []).find( + (item) => item.localId === localId, + ); } /** 创建一条空的履行计划 */ @@ -1457,11 +1620,15 @@ function createContractFormData() { templateFieldValues: {}, partyState: { isThreePartyContract: false, - partyA: { companyName: SELF_COMPANY_NAME }, - partyB: { companyName: "" }, - partyC: { companyName: "" }, + ourPartyList: [ + { + ...createEmptyParty(PARTY_ROLE_OUR, 0), + companyName: SELF_COMPANY_NAME, + }, + ], counterpartyList: [], }, + isSingleAgreement: "0", isInvolveHongKongMacaoTaiwan: false, isPublicTender: false, isForeignRelatedSubject: false, @@ -1501,7 +1668,10 @@ function copyContractFormData(data = {}) { if (key === "extraAmounts") { form.extraAmounts = restoreExtraAmounts(source, form.extraAmounts); } else if (key === "templateFieldValues") { - if (source.templateFieldValues && typeof source.templateFieldValues === "object") { + if ( + source.templateFieldValues && + typeof source.templateFieldValues === "object" + ) { form.templateFieldValues = { ...source.templateFieldValues }; } else if (source.templateFieldValuesJson) { try { @@ -1518,7 +1688,7 @@ function copyContractFormData(data = {}) { ...item, })); } else if (key === "partyState") { - form.partyState = normalizePartyState(source.partyState || source); + form.partyState = copyPartyState(source.partyState || source); } else if (key === "otherAttachmentList") { form.otherAttachmentList = fileState.otherAttachmentList; } else if (key === "contractGistFileList") { @@ -1540,13 +1710,23 @@ function copyContractFormData(data = {}) { ? source.contractBodyArrayBuffer.slice(0) : null : null; - } else if (key === "signDate" || key === "sealDate" || key === "invoiceDate" || key === "receiveDate" || key === "activeDate") { + } else if ( + key === "signDate" || + key === "sealDate" || + key === "invoiceDate" || + key === "receiveDate" || + key === "activeDate" + ) { form[key] = source[key] ? String(source[key]).slice(0, 10) : form[key]; } else if (key === "assistDept") { - form.assistDept = source.assistDept ?? source.assistDepartmentName ?? form.assistDept; + form.assistDept = + source.assistDept ?? source.assistDepartmentName ?? form.assistDept; } else if (key === "signingSubjectCode") { form.signingSubjectCode = source.signingSubjectCode ?? source.orgId ?? form.signingSubjectCode; + } else if (key === "isSingleAgreement") { + form.isSingleAgreement = + source.isSingleAgreement ?? form.isSingleAgreement; } else if ( [ "isInvolveHongKongMacaoTaiwan", @@ -1583,7 +1763,7 @@ const otherAttachmentList = ref([]); const gistAttachmentList = ref([]); const authAttachmentUploadList = ref([]); const bodyConfigModalRef = ref(null); -const counterpartyRoleKeys = ref([]); +const currentSelfParty = ref(null); const companyProfileMap = { 扬州宏富特种材料有限公司: { @@ -1626,10 +1806,16 @@ const companyProfileMap = { const isViewMode = computed(() => modalMode.value === "view"); -const counterpartyParties = computed(() => - (detailState.value.partyState.counterpartyList || []).filter((party) => - counterpartyRoleKeys.value.includes(party.roleKey), - ), +const isSingleAgreementChecked = computed( + () => String(detailState.value.isSingleAgreement || "0") === "1", +); + +const ourParties = computed( + () => detailState.value.partyState.ourPartyList || [], +); + +const counterpartyParties = computed( + () => detailState.value.partyState.counterpartyList || [], ); const counterpartySummaryText = computed(() => @@ -1640,11 +1826,36 @@ const counterpartySummaryText = computed(() => : "未填写", ); -const contractBodyDisplayText = computed(() => - detailState.value.contractBodyFileName || - (creationMode.value === "manual" - ? "点击上传合同正文 Word 文件" - : templateName.value || "点击配置合同正文"), +const ourPartySummaryText = computed(() => + ourParties.value.length + ? ourParties.value + .map((party) => party.companyName || party.displayName) + .join(";") + : "未填写", +); + +const allPartyDetails = computed(() => [ + ...ourParties.value, + ...counterpartyParties.value, +]); + +const signingSubjectCodeText = computed(() => + ourParties.value + .map((party) => String(party.subjectCode || "").trim()) + .filter(Boolean) + .join(","), +); + +const currentSelfSubjectCode = computed(() => + String(currentSelfParty.value?.subjectCode || "").trim(), +); + +const contractBodyDisplayText = computed( + () => + detailState.value.contractBodyFileName || + (creationMode.value === "manual" + ? "点击上传合同正文 Word 文件" + : templateName.value || "点击配置合同正文"), ); const contractBodyHint = computed(() => @@ -1676,36 +1887,46 @@ function syncUploadLists() { status: item.status || "done", }), ); - authAttachmentUploadList.value = (detailState.value.authAttachmentList || []).map( - (item, index) => ({ - uid: item.uid || `auth_${index + 1}`, - name: item.name || item.fileName, - size: item.size ?? item.fileSize, - status: item.status || "done", - }), - ); + authAttachmentUploadList.value = ( + detailState.value.authAttachmentList || [] + ).map((item, index) => ({ + uid: item.uid || `auth_${index + 1}`, + name: item.name || item.fileName, + size: item.size ?? item.fileSize, + status: item.status || "done", + })); } function syncCounterpartyState() { - detailState.value.partyState = normalizePartyState(detailState.value.partyState); - counterpartyRoleKeys.value = buildCounterpartyRoleKeys(detailState.value.partyState); - detailState.value.partyState.counterpartyList = - detailState.value.partyState.counterpartyList || []; - detailState.value.partyState.isThreePartyContract = counterpartyRoleKeys.value.length > 1; + detailState.value.partyState = normalizePartyState( + detailState.value.partyState, + ); refreshPartyDisplayMeta(); } function refreshPartyDisplayMeta() { - detailState.value.partyState.partyA.roleKey = "A"; - detailState.value.partyState.partyA.roleName = "我方"; - detailState.value.partyState.partyA.displayName = "我方"; - - (detailState.value.partyState.counterpartyList || []).forEach((party, index) => { - const roleKey = party.roleKey || COUNTERPARTY_ROLE_CODES[index]; - party.roleKey = roleKey; - party.roleName = "相对方"; - party.displayName = `相对方${index + 1}`; - }); + detailState.value.partyState.ourPartyList = ( + detailState.value.partyState.ourPartyList || [] + ).map((party, index) => ({ + ...party, + roleCode: PARTY_ROLE_OUR, + roleName: "我方", + displayName: `我方主体${index + 1}`, + })); + detailState.value.partyState.counterpartyList = ( + detailState.value.partyState.counterpartyList || [] + ).map((party, index) => ({ + ...party, + roleCode: PARTY_ROLE_OPPOSITE, + roleName: "相对方", + displayName: `相对方${index + 1}`, + })); + detailState.value.partyState.partyA = + detailState.value.partyState.ourPartyList[0] || + createEmptyParty(PARTY_ROLE_OUR, 0); + detailState.value.partyState.isThreePartyContract = + detailState.value.partyState.counterpartyList.length > 1; + detailState.value.signingSubjectCode = signingSubjectCodeText.value; } function resetCurrentState() { @@ -1718,19 +1939,11 @@ function resetCurrentState() { } function showSearchTip(title) { - if (title === "我方详情") { - message.info("我方信息当前按登录组织只读带出,后续接入真实组织主数据后自动完善。"); - return; - } loadPartnerOptions(); - message.info(`${title}数据已从相对方管理加载,可直接在下拉框搜索选择。`); + message.info(`${title}数据已从客户/主体库加载,可直接在下拉框搜索选择。`); } function showInvoiceTitles(party, roleName) { - if (roleName === "我方") { - message.info("我方开票抬头当前不在发起页维护,后续建议从组织主数据自动带出。"); - return; - } const titles = party.invoiceTitleOptions || []; message.info( titles.length @@ -1740,9 +1953,16 @@ function showInvoiceTitles(party, roleName) { } function filterPartnerOption(input, option) { - const keyword = String(input || "").trim().toLowerCase(); + const keyword = String(input || "") + .trim() + .toLowerCase(); if (!keyword) return true; - return [option?.label, option?.value, option?.raw?.oppositeCode, option?.raw?.oppositeId] + return [ + option?.label, + option?.value, + option?.raw?.oppositeCode, + option?.raw?.oppositeId, + ] .filter(Boolean) .some((item) => String(item).toLowerCase().includes(keyword)); } @@ -1781,9 +2001,13 @@ function getEnterpriseNatureLevel2Label(value) { function mapPartnerToParty(raw = {}, roleKey, companyName) { const firstContact = raw.contacts?.[0] || {}; + const roleCode = + roleKey === PARTY_ROLE_OUR ? PARTY_ROLE_OUR : PARTY_ROLE_OPPOSITE; return { + ...createEmptyParty(roleCode), partnerId: raw.customerSysid || null, customerSysid: raw.customerSysid || null, + subjectCode: raw.orgId == null ? "" : String(raw.orgId), oppositeId: raw.oppositeId || "", companyName: raw.customerNameC || companyName || "", companyType: getRelativeTypeLabel(raw.relativeType), @@ -1791,7 +2015,9 @@ function mapPartnerToParty(raw = {}, roleKey, companyName) { oppCharacter: raw.oppositeCharacter || "", companyNature: getCompanyNatureByOppositeCharacter(raw.oppositeCharacter), organizationNature: getOrganizationNatureLabel(raw.natureOfEnterpriseOrg), - enterpriseNatureLevel2: getEnterpriseNatureLevel2Label(raw.natureOfEnterpriseOrgLev), + enterpriseNatureLevel2: getEnterpriseNatureLevel2Label( + raw.natureOfEnterpriseOrgLev, + ), creditCode: raw.customerTaxno || "", tinNo: raw.tinCode || "", countryCode: raw.country || "", @@ -1809,24 +2035,59 @@ function mapPartnerToParty(raw = {}, roleKey, companyName) { isGroupInternal: raw.groupFlag === "Y" || raw.isJtn === "1", isHongKongMacaoTaiwan: raw.gatFlag === "Y" || raw.isSgat === "1", invoiceTitleOptions: raw.customerNameC ? [raw.customerNameC] : [], - roleKey, - roleName: roleKey === "A" ? "我方" : "相对方", - displayName: roleKey === "A" ? "我方" : roleKey === "C" ? "相对方2" : "相对方1", + roleCode, + roleName: roleCode === PARTY_ROLE_OUR ? "我方" : "相对方", }; } -function handleCompanySelect(roleKey, companyName, option = {}) { - const party = - roleKey === "A" - ? detailState.value.partyState.partyA - : getPartyByRoleKey(detailState.value.partyState, roleKey); +function getPartyOptions(roleCode) { + const baseOptions = contractCompanyOptions.value || []; + if (roleCode !== PARTY_ROLE_OUR) { + return baseOptions; + } + const selfParty = currentSelfParty.value; + if (!selfParty?.companyName) { + return baseOptions; + } + const exists = baseOptions.some( + (item) => item.value === selfParty.companyName, + ); + if (exists) { + return baseOptions; + } + return [ + { + label: `${selfParty.companyName}(当前主体)`, + value: selfParty.companyName, + raw: { + customerNameC: selfParty.companyName, + orgId: selfParty.subjectCode, + officephone: selfParty.contactPhone, + emailAddress: selfParty.contactEmail, + customerAddress: selfParty.contactAddress, + customerRegaddr: selfParty.registeredAddress, + customerBank: selfParty.bankName, + customerBankaccount: selfParty.bankAccount, + legalPersonName: selfParty.legalRepresentative, + customerTaxno: selfParty.creditCode, + }, + }, + ...baseOptions, + ]; +} + +function handleCompanySelect(party, companyName, option = {}) { if (!party) return; const rawPartner = option?.raw || - contractCompanyOptions.value.find((item) => item.value === companyName)?.raw; + contractCompanyOptions.value.find((item) => item.value === companyName) + ?.raw; if (rawPartner) { - Object.assign(party, mapPartnerToParty(rawPartner, roleKey, companyName)); + Object.assign( + party, + mapPartnerToParty(rawPartner, party.roleCode, companyName), + ); refreshPartyDisplayMeta(); return; } @@ -1838,19 +2099,32 @@ function handleCompanySelect(roleKey, companyName, option = {}) { } Object.assign(party, { - companyName, - contactPhone: party.contactPhone || profile.contactPhone, - registeredAddress: party.registeredAddress || profile.registeredAddress, - bankAccount: party.bankAccount || profile.bankAccount, - bankName: party.bankName || profile.bankName, - contactName: party.contactName || profile.contactName, - legalRepresentative: - party.legalRepresentative || profile.legalRepresentative, - creditCode: party.creditCode || profile.creditCode, - oppCharacter: - party.oppCharacter || - (String(party.companyNature || "").includes("个人") ? "2" : "1"), - }); + companyName, + subjectCode: party.subjectCode || "", + contactPhone: party.contactPhone || profile.contactPhone, + registeredAddress: party.registeredAddress || profile.registeredAddress, + bankAccount: party.bankAccount || profile.bankAccount, + bankName: party.bankName || profile.bankName, + contactName: party.contactName || profile.contactName, + legalRepresentative: + party.legalRepresentative || profile.legalRepresentative, + creditCode: party.creditCode || profile.creditCode, + oppCharacter: + party.oppCharacter || + (String(party.companyNature || "").includes("个人") ? "2" : "1"), + }); +} + +function handleAddOurParty() { + detailState.value.partyState.ourPartyList = + detailState.value.partyState.ourPartyList || []; + detailState.value.partyState.ourPartyList.push( + createEmptyParty( + PARTY_ROLE_OUR, + detailState.value.partyState.ourPartyList.length, + ), + ); + refreshPartyDisplayMeta(); } function handleContractTypeChange(value) { @@ -1862,39 +2136,33 @@ function handleContractTypeCodeChange(value) { } function handleAddCounterparty() { - if (counterpartyRoleKeys.value.length >= MAX_COUNTERPARTY_COUNT) { - message.warning(`最多支持添加 ${MAX_COUNTERPARTY_COUNT} 个相对方`); - return; - } - - const nextRoleKey = COUNTERPARTY_ROLE_CODES.find( - (roleKey) => !counterpartyRoleKeys.value.includes(roleKey), - ); - if (!nextRoleKey) return; - detailState.value.partyState.counterpartyList = detailState.value.partyState.counterpartyList || []; detailState.value.partyState.counterpartyList.push( - createEmptyCounterparty(nextRoleKey), + createEmptyParty( + PARTY_ROLE_OPPOSITE, + detailState.value.partyState.counterpartyList.length, + ), ); - counterpartyRoleKeys.value = [...counterpartyRoleKeys.value, nextRoleKey]; - detailState.value.partyState.isThreePartyContract = counterpartyRoleKeys.value.length > 1; refreshPartyDisplayMeta(); } -function handleRemoveCounterparty(roleKey) { - detailState.value.partyState.counterpartyList = - (detailState.value.partyState.counterpartyList || []) - .filter((item) => item.roleKey !== roleKey) - .map((item, index) => ({ - ...item, - roleKey: COUNTERPARTY_ROLE_CODES[index], - displayName: `相对方${index + 1}`, - })); - counterpartyRoleKeys.value = detailState.value.partyState.counterpartyList.map( - (item) => item.roleKey, - ); - detailState.value.partyState.isThreePartyContract = counterpartyRoleKeys.value.length > 1; +function handleRemoveOurParty(localId) { + detailState.value.partyState.ourPartyList = ( + detailState.value.partyState.ourPartyList || [] + ).filter((item) => item.localId !== localId); + if (!detailState.value.partyState.ourPartyList.length) { + detailState.value.partyState.ourPartyList = [ + createEmptyParty(PARTY_ROLE_OUR, 0), + ]; + } + refreshPartyDisplayMeta(); +} + +function handleRemoveCounterparty(localId) { + detailState.value.partyState.counterpartyList = ( + detailState.value.partyState.counterpartyList || [] + ).filter((item) => item.localId !== localId); refreshPartyDisplayMeta(); } @@ -2017,7 +2285,9 @@ function handleBodyConfigSave(payload = {}) { if (modalMode.value === "edit" && detailState.value.id) { const errorMessage = validateState(); if (errorMessage) { - message.success("合同正文配置已保存到当前页面,请继续完善主信息后再保存合同草稿。"); + message.success( + "合同正文配置已保存到当前页面,请继续完善主信息后再保存合同草稿。", + ); return; } @@ -2037,7 +2307,9 @@ function handleBodyConfigSave(payload = {}) { return; } - message.success("合同正文配置已保存到当前页面,请继续点击底部“保存”提交合同草稿。"); + message.success( + "合同正文配置已保存到当前页面,请继续点击底部“保存”提交合同草稿。", + ); } function addPerformancePlan() { @@ -2050,10 +2322,24 @@ function removePerformancePlan(id) { } function validateState() { - if (!detailState.value.partyState.partyA.companyName?.trim()) - return "请确认我方信息"; - if (!counterpartyRoleKeys.value.length) return "请至少添加一个相对方"; if ( + !ourParties.value.length || + ourParties.value.some((party) => !party.companyName?.trim()) + ) + return "请确认我方信息"; + if ( + currentSelfSubjectCode.value && + !ourParties.value.some( + (party) => + String(party.subjectCode || "").trim() === currentSelfSubjectCode.value, + ) + ) { + return "当前登录主体必须包含在我方主体中"; + } + if (!isSingleAgreementChecked.value && !counterpartyParties.value.length) + return "请至少添加一个相对方"; + if ( + !isSingleAgreementChecked.value && counterpartyParties.value.some((party) => !party.companyName?.trim()) ) return "请选择相对方"; @@ -2106,7 +2392,11 @@ function validateState() { if (!detailState.value.sealType) return "请选择签订方式"; if (!detailState.value.textSource) return "请选择文本来源"; // needAmendments 必填(文档要求) - if (!detailState.value.needAmendments || detailState.value.needAmendments === "") return "请选择是否需修订条款"; + if ( + !detailState.value.needAmendments || + detailState.value.needAmendments === "" + ) + return "请选择是否需修订条款"; if (!detailState.value.paymentDirection) return "请选择收支方向"; if (!detailState.value.contractTypeCode) return "请选择合同类型编码"; if (!detailState.value.executorAccount?.trim()) return "请填写合同执行人"; @@ -2125,11 +2415,17 @@ function validateState() { if (!plan.planMatter?.trim()) return "请填写履行计划的履行事项"; if (!plan.planWhere?.trim()) return "请填写履行计划的履约条件"; if (!plan.planTime) return "请选择履行计划的节点日期"; - if (plan.planDay === null || plan.planDay === undefined || plan.planDay === "") + if ( + plan.planDay === null || + plan.planDay === undefined || + plan.planDay === "" + ) return "请填写履行计划的提醒提前天数"; if ( !["0", "3"].includes(detailState.value.valuationMode) && - (plan.planMoney === null || plan.planMoney === undefined || plan.planMoney === "") + (plan.planMoney === null || + plan.planMoney === undefined || + plan.planMoney === "") ) return "请填写履行计划的款项金额"; } @@ -2168,32 +2464,11 @@ function handleSave() { function buildLaunchRecord() { const partyList = []; - const partyA = detailState.value.partyState.partyA || {}; - partyList.push({ - roleCode: "A", - roleName: "甲方", - companyName: partyA.companyName, - companyType: partyA.companyType, - oppositeCharacter: - partyA.oppCharacter || - (String(partyA.companyNature || "").includes("个人") ? "2" : "1"), - contactName: partyA.contactName, - contactPhone: partyA.contactPhone, - contactEmail: partyA.contactEmail, - contactAddress: partyA.contactAddress, - registeredAddress: partyA.registeredAddress, - legalRepresentative: partyA.legalRepresentative, - creditCode: partyA.creditCode, - bankName: partyA.bankName, - bankAccount: partyA.bankAccount, - naturalPersonIdType: partyA.naturalPersonIdType, - naturalPersonIdNumber: partyA.naturalPersonIdNumber, - }); - - counterpartyParties.value.forEach((party, index) => { + ourParties.value.forEach((party) => { partyList.push({ - roleCode: party.roleKey || COUNTERPARTY_ROLE_CODES[index], - roleName: party.displayName || `相对方${index + 1}`, + roleCode: PARTY_ROLE_OUR, + roleName: "我方", + subjectCode: party.subjectCode || "", partnerId: party.partnerId || party.customerSysid || null, oppositeId: party.oppositeId, companyName: party.companyName, @@ -2217,6 +2492,35 @@ function buildLaunchRecord() { }); }); + if (!isSingleAgreementChecked.value) { + counterpartyParties.value.forEach((party) => { + partyList.push({ + roleCode: PARTY_ROLE_OPPOSITE, + roleName: "相对方", + partnerId: party.partnerId || party.customerSysid || null, + oppositeId: party.oppositeId, + companyName: party.companyName, + companyType: party.companyType, + oppositeCharacter: + party.oppCharacter || + (String(party.companyNature || "").includes("个人") ? "2" : "1"), + contactName: party.contactName, + contactPhone: party.contactPhone, + contactEmail: party.contactEmail, + contactAddress: party.contactAddress, + registeredAddress: party.registeredAddress, + legalRepresentative: party.legalRepresentative, + creditCode: party.creditCode, + bankName: party.bankName, + bankAccount: party.bankAccount, + naturalPersonIdType: party.naturalPersonIdType, + naturalPersonIdNumber: party.naturalPersonIdNumber, + isGroupInternal: party.isGroupInternal ? "1" : "0", + isHongKongMacaoTaiwan: party.isHongKongMacaoTaiwan ? "1" : "0", + }); + }); + } + const fileList = []; if (detailState.value.contractBodyFileName) { fileList.push({ @@ -2255,14 +2559,20 @@ function buildLaunchRecord() { }); }); - const extraAmountPayload = buildExtraAmountPayload(detailState.value.extraAmounts); + const extraAmountPayload = buildExtraAmountPayload( + detailState.value.extraAmounts, + ); return { id: detailState.value.id || null, contractCode: detailState.value.contractCode, contractName: detailState.value.contractName, - contractType: getOptionLabel(contractTypeOptions, detailState.value.contractType), - contractTypeCode: detailState.value.contractTypeCode || detailState.value.contractType, + contractType: getOptionLabel( + contractTypeOptions, + detailState.value.contractType, + ), + contractTypeCode: + detailState.value.contractTypeCode || detailState.value.contractType, contractSource: detailState.value.contractSource || "ECP", creationMode: creationMode.value, templateId: templateKey.value || null, @@ -2296,6 +2606,7 @@ function buildLaunchRecord() { dateType: detailState.value.dateType || "0", isAddit: detailState.value.mainContractCode ? 1 : 2, isElectron: detailState.value.isElectron || "1", + isSingleAgreement: detailState.value.isSingleAgreement || "0", paymentDirection: detailState.value.paymentDirection || "0", isMajorContract: detailState.value.isMajorContract ? 1 : 0, isOpenBidding: detailState.value.isPublicTender ? 1 : 0, @@ -2303,9 +2614,12 @@ function buildLaunchRecord() { isShare: detailState.value.isShared ? "1" : "0", contractNature: detailState.value.isForeignRelatedSubject ? "1" : "0", isSgat: detailState.value.isInvolveHongKongMacaoTaiwan ? "1" : "0", - biddingNo: detailState.value.isPublicTender ? detailState.value.biddingNo || "" : "", + biddingNo: detailState.value.isPublicTender + ? detailState.value.biddingNo || "" + : "", contractGistRemark: detailState.value.contractGistRemark || "", - bodySummary: detailState.value.bodySummary || detailState.value.primaryContent || "", + bodySummary: + detailState.value.bodySummary || detailState.value.primaryContent || "", contractTextFileName: detailState.value.contractBodyFileName || "", contractTextFilePath: detailState.value.contractBodyFilePath || "", contractTextFileType: detailState.value.contractBodyFileType || "docx", @@ -2320,54 +2634,91 @@ function buildLaunchRecord() { detailState.value.templateFieldValues || {}, ), remindDays: 3, - signingSubjectCode: detailState.value.signingSubjectCode || "", + signingSubjectCode: + signingSubjectCodeText.value || + detailState.value.signingSubjectCode || + "", extraAmounts: { ...detailState.value.extraAmounts }, ...extraAmountPayload, partyList, fileList, - performancePlans: (detailState.value.performancePlans || []).map((item) => ({ - planMatter: item.planMatter, - planWhere: item.planWhere, - planTime: formatDateTimeValue(item.planTime), - planDay: item.planDay, - planMoney: item.planMoney, - planSubjectCode: item.planSubjectCode, - planSubjectName: item.planSubjectName, - executorUnitId: item.executorUnitId || null, - planDetail: item.planDetail, - planStatus: "0", - })), + performancePlans: (detailState.value.performancePlans || []).map( + (item) => ({ + planMatter: item.planMatter, + planWhere: item.planWhere, + planTime: formatDateTimeValue(item.planTime), + planDay: item.planDay, + planMoney: item.planMoney, + planSubjectCode: item.planSubjectCode, + planSubjectName: item.planSubjectName, + executorUnitId: item.executorUnitId || null, + planDetail: item.planDetail, + planStatus: "0", + }), + ), }; } function applySelfProfile(profile = {}) { - detailState.value.partyState.partyA = { - ...detailState.value.partyState.partyA, - companyName: profile.orgName || detailState.value.partyState.partyA.companyName, - contactPhone: profile.contactPhone || detailState.value.partyState.partyA.contactPhone, - contactEmail: profile.contactEmail || detailState.value.partyState.partyA.contactEmail, - contactAddress: profile.contactAddress || detailState.value.partyState.partyA.contactAddress, - registeredAddress: - profile.registeredAddress || detailState.value.partyState.partyA.registeredAddress, - bankName: profile.bankName || detailState.value.partyState.partyA.bankName, - bankAccount: profile.bankAccount || detailState.value.partyState.partyA.bankAccount, - legalRepresentative: - profile.legalRepresentative || detailState.value.partyState.partyA.legalRepresentative, - creditCode: profile.creditCode || detailState.value.partyState.partyA.creditCode, - invoiceTitleOptions: - profile.invoiceTitleOptions || detailState.value.partyState.partyA.invoiceTitleOptions || [], + const firstOurParty = + detailState.value.partyState.ourPartyList?.[0] || + createEmptyParty(PARTY_ROLE_OUR, 0); + currentSelfParty.value = { + ...createEmptyParty(PARTY_ROLE_OUR, 0), + companyName: profile.orgName || SELF_COMPANY_NAME, + subjectCode: profile.id || "", + contactPhone: profile.contactPhone || "", + contactEmail: profile.contactEmail || "", + contactAddress: profile.contactAddress || "", + registeredAddress: profile.registeredAddress || "", + bankName: profile.bankName || "", + bankAccount: profile.bankAccount || "", + legalRepresentative: profile.legalRepresentative || "", + creditCode: profile.creditCode || "", + invoiceTitleOptions: profile.invoiceTitleOptions || [], }; + const shouldHydrateFirstParty = + !firstOurParty.companyName || + firstOurParty.companyName === SELF_COMPANY_NAME || + firstOurParty.companyName === profile.orgName; + if (!shouldHydrateFirstParty) { + return; + } + detailState.value.partyState.ourPartyList = [ + { + ...firstOurParty, + companyName: profile.orgName || firstOurParty.companyName, + subjectCode: profile.id || firstOurParty.subjectCode, + contactPhone: profile.contactPhone || firstOurParty.contactPhone, + contactEmail: profile.contactEmail || firstOurParty.contactEmail, + contactAddress: profile.contactAddress || firstOurParty.contactAddress, + registeredAddress: + profile.registeredAddress || firstOurParty.registeredAddress, + bankName: profile.bankName || firstOurParty.bankName, + bankAccount: profile.bankAccount || firstOurParty.bankAccount, + legalRepresentative: + profile.legalRepresentative || firstOurParty.legalRepresentative, + creditCode: profile.creditCode || firstOurParty.creditCode, + invoiceTitleOptions: + profile.invoiceTitleOptions || firstOurParty.invoiceTitleOptions || [], + }, + ...(detailState.value.partyState.ourPartyList || []).slice(1), + ]; + refreshPartyDisplayMeta(); } async function showModal(context = {}, mode = "create") { modalMode.value = mode || "create"; - const source = context.detailState || + const source = + context.detailState || (Object.keys(context || {}).length ? context : createContractFormData()); creationMode.value = context.creationMode || source.creationMode || - (source.contractBodyArrayBuffer || source.contractBodyFilePath ? "manual" : "template"); + (source.contractBodyArrayBuffer || source.contractBodyFilePath + ? "manual" + : "template"); templateKey.value = context.templateKey || source.templateId || ""; templateName.value = context.templateName || source.templateName || ""; detailState.value = copyContractFormData(source); @@ -2386,7 +2737,9 @@ function loadPartnerOptions() { value: item.customerNameC, raw: item, })); - contractCompanyOptions.value = options.length ? options : contractCompanyOptions.value; + contractCompanyOptions.value = options.length + ? options + : contractCompanyOptions.value; }); } diff --git a/src/views/contract/launch/components/ContractTemplateFillDemoModal.vue b/src/views/contract/launch/components/ContractTemplateFillDemoModal.vue index 6cd0027..10ba189 100644 --- a/src/views/contract/launch/components/ContractTemplateFillDemoModal.vue +++ b/src/views/contract/launch/components/ContractTemplateFillDemoModal.vue @@ -130,11 +130,7 @@ 导出 Word - + 保存 @@ -176,7 +172,6 @@ - @@ -219,81 +214,84 @@ import { // ========== 创建方式选项 ========== const contractCreateModeOptions = [ - { label: '模板创建', value: 'template' }, - { label: '手动创建', value: 'manual' }, -] + { label: "模板创建", value: "template" }, + { label: "手动创建", value: "manual" }, +]; // ========== 可绑定业务字段列表(用于映射下拉和展示) ========== const templateFieldList = [ - { value: 'contractName', label: '合同名称' }, - { value: 'contractCode', label: '合同编码' }, - { value: 'contractType', label: '合同类型' }, - { value: 'contractAmountDisplay', label: '合同金额' }, - { value: 'effectiveStart', label: '有效期起' }, - { value: 'effectiveEnd', label: '有效期止' }, - { value: 'effectivePeriod', label: '有效期区间' }, - { value: 'paymentMethod', label: '付款方式' }, - { value: 'primaryContent', label: '合同标的说明' }, - { value: 'amountExplain', label: '金额说明' }, - { value: 'partyAName', label: '甲方名称' }, - { value: 'partyBName', label: '乙方名称' }, - { value: 'partyCName', label: '丙方名称' }, - { value: 'partyDName', label: '丁方名称' }, - { value: 'partyEName', label: '戊方名称' }, - { value: 'partyFName', label: '己方名称' }, - { value: 'counterpartySummary', label: '合同方摘要' }, - { value: 'attachmentSummary', label: '附件情况' }, - { value: 'templateLabel', label: '模板名称' }, - { value: 'signDate', label: '签署日期' }, - { value: 'businessRemark', label: '业务备注' }, - { value: 'contractSubject', label: '合同标的说明' }, -] + { value: "contractName", label: "合同名称" }, + { value: "contractCode", label: "合同编码" }, + { value: "contractType", label: "合同类型" }, + { value: "contractAmountDisplay", label: "合同金额" }, + { value: "effectiveStart", label: "有效期起" }, + { value: "effectiveEnd", label: "有效期止" }, + { value: "effectivePeriod", label: "有效期区间" }, + { value: "paymentMethod", label: "付款方式" }, + { value: "primaryContent", label: "合同标的说明" }, + { value: "amountExplain", label: "金额说明" }, + { value: "partyAName", label: "甲方名称" }, + { value: "partyBName", label: "乙方名称" }, + { value: "partyCName", label: "丙方名称" }, + { value: "partyDName", label: "丁方名称" }, + { value: "partyEName", label: "戊方名称" }, + { value: "partyFName", label: "己方名称" }, + { value: "counterpartySummary", label: "合同方摘要" }, + { value: "attachmentSummary", label: "附件情况" }, + { value: "templateLabel", label: "模板名称" }, + { value: "signDate", label: "签署日期" }, + { value: "businessRemark", label: "业务备注" }, + { value: "contractSubject", label: "合同标的说明" }, +]; // ========== 格式化合同金额(如 368,000.00 元) ========== function formatContractAmount(value) { - if (value === undefined || value === null || value === '') return '' - return `${new Intl.NumberFormat('zh-CN', { + if (value === undefined || value === null || value === "") return ""; + return `${new Intl.NumberFormat("zh-CN", { minimumFractionDigits: 2, maximumFractionDigits: 2, - }).format(Number(value))} 元` + }).format(Number(value))} 元`; } function getContractPeriodTypeLabel(value) { - if (value === '1') return '无固定期限' - return '固定期限' + if (value === "1") return "无固定期限"; + return "固定期限"; } function normalizeFileItem(item = {}) { return { - uid: item.uid || `file_${Date.now()}_${Math.random().toString(16).slice(2, 8)}`, - name: item.name || item.fileName || '', + uid: + item.uid || + `file_${Date.now()}_${Math.random().toString(16).slice(2, 8)}`, + name: item.name || item.fileName || "", size: item.size ?? item.fileSize ?? 0, - status: item.status || 'done', - filePath: item.filePath || '', - fileType: item.fileType || '', - fileCategory: item.fileCategory || '', - } + status: item.status || "done", + filePath: item.filePath || "", + fileType: item.fileType || "", + fileCategory: item.fileCategory || "", + }; } function splitFilesByCategory(source = {}) { - const sourceFiles = source.fileList || [] - const bodyFile = sourceFiles.find((item) => item.fileCategory === 'body') || {} + const sourceFiles = source.fileList || []; + const bodyFile = + sourceFiles.find((item) => item.fileCategory === "body") || {}; return { contractBodyFileName: source.contractBodyFileName || source.contractTextFileName || bodyFile.fileName || - '', + "", contractBodyFilePath: source.contractBodyFilePath || source.contractTextFilePath || bodyFile.filePath || - '', + "", contractBodyFileType: source.contractBodyFileType || source.contractTextFileType || bodyFile.fileType || - '', + "", contractBodyFileSize: source.contractBodyFileSize ?? source.contractTextFileSize ?? @@ -302,182 +300,225 @@ function splitFilesByCategory(source = {}) { otherAttachmentList: source.otherAttachmentList?.length ? source.otherAttachmentList.map(normalizeFileItem) : sourceFiles - .filter((item) => item.fileCategory === 'other') + .filter((item) => item.fileCategory === "other") .map(normalizeFileItem), contractGistFileList: source.contractGistFileList?.length ? source.contractGistFileList.map(normalizeFileItem) : sourceFiles - .filter((item) => item.fileCategory === 'gist') + .filter((item) => item.fileCategory === "gist") .map(normalizeFileItem), - } + }; } function formatFileSummary(fileList = []) { return fileList.length - ? fileList.map((item) => item.name || item.fileName).filter(Boolean).join(';') - : '暂无附件' + ? fileList + .map((item) => item.name || item.fileName) + .filter(Boolean) + .join(";") + : "暂无附件"; } function buildExtraAmountSummary(extraAmounts = {}) { const parts = extraAmountFieldOptions .filter((item) => extraAmounts[`${item.key}Enabled`]) - .map((item) => `${item.label}${formatContractAmount(extraAmounts[item.key]) || '0.00 元'}`) - return parts.length ? parts.join(';') : '无' + .map( + (item) => + `${item.label}${formatContractAmount(extraAmounts[item.key]) || "0.00 元"}`, + ); + return parts.length ? parts.join(";") : "无"; } function restoreExtraAmounts(source = {}, current = {}) { - const result = { ...current, ...(source.extraAmounts || {}) } - const selectedCodes = String(source.ewAmountType || '') - .split(',') + const result = { ...current, ...(source.extraAmounts || {}) }; + const selectedCodes = String(source.ewAmountType || "") + .split(",") .map((item) => item.trim()) - .filter(Boolean) + .filter(Boolean); extraAmountFieldOptions.forEach((item) => { - const amount = source[item.amountField] + const amount = source[item.amountField]; if (selectedCodes.includes(item.code) || amount || amount === 0) { - result[`${item.key}Enabled`] = selectedCodes.includes(item.code) || amount > 0 + result[`${item.key}Enabled`] = + selectedCodes.includes(item.code) || amount > 0; if (amount !== undefined && amount !== null) { - result[item.key] = amount + result[item.key] = amount; } } - }) + }); - return result + return result; } function buildPerformancePlanSummary(list = []) { - if (!list.length) return '暂无履行计划' + if (!list.length) return "暂无履行计划"; return list .map((item, index) => { - const money = item.planMoney || item.planMoney === 0 ? `,金额${formatContractAmount(item.planMoney)}` : '' - return `${index + 1}. ${item.planMatter || '未填写事项'},${item.planWhere || '未填写条件'},${item.planTime || '未填写日期'}${money}` + const money = + item.planMoney || item.planMoney === 0 + ? `,金额${formatContractAmount(item.planMoney)}` + : ""; + return `${index + 1}. ${item.planMatter || "未填写事项"},${item.planWhere || "未填写条件"},${item.planTime || "未填写日期"}${money}`; }) - .join('\n') + .join("\n"); } // ========== 合同方相关函数 ========== -const COUNTERPARTY_ROLE_CODES = ['B', 'C', 'D', 'E', 'F'] +const PARTY_ROLE_OUR = "OUR"; +const PARTY_ROLE_OPPOSITE = "OPPOSITE"; +const PARTY_NAME_KEYS = [ + "partyAName", + "partyBName", + "partyCName", + "partyDName", + "partyEName", + "partyFName", +]; -/** 深拷贝合同方状态 */ -function copyPartyState(input) { - const source = input || {} - const listState = buildPartyStateFromList(source.partyList) - const sourceCounterpartyList = Array.isArray(source.counterpartyList) - ? source.counterpartyList - : [ - source.partyB?.companyName ? { ...source.partyB, roleKey: 'B' } : null, - source.partyC?.companyName ? { ...source.partyC, roleKey: 'C' } : null, - ].filter(Boolean) +function createParty(roleCode = PARTY_ROLE_OPPOSITE, index = 0) { return { - partyA: { - roleKey: 'A', - roleName: '我方', - displayName: '我方', - companyName: '', - ...(source.partyA || source.parties?.A || {}), - ...(listState?.partyA || {}), - }, - counterpartyList: listState?.counterpartyList?.length - ? listState.counterpartyList.map((item) => ({ ...item })) - : sourceCounterpartyList.map((item, index) => ({ - roleKey: item.roleKey || COUNTERPARTY_ROLE_CODES[index], - roleName: item.roleName || '相对方', - displayName: `相对方${index + 1}`, - companyName: '', - ...item, - })), - isThreePartyContract: Boolean( - (listState?.counterpartyList || sourceCounterpartyList || []).length > 1, - ), - } + roleCode, + roleName: roleCode === PARTY_ROLE_OUR ? "我方" : "相对方", + displayName: + roleCode === PARTY_ROLE_OUR + ? `我方主体${index + 1}` + : `相对方${index + 1}`, + subjectCode: "", + companyName: "", + }; } function mapLaunchPartyToFormParty(item = {}) { - const roleIndex = COUNTERPARTY_ROLE_CODES.indexOf(item.roleCode || 'B') + const roleCode = + String(item.roleCode || PARTY_ROLE_OPPOSITE).toUpperCase() === + PARTY_ROLE_OUR + ? PARTY_ROLE_OUR + : PARTY_ROLE_OPPOSITE; return { - roleKey: item.roleCode || 'B', - roleName: item.roleCode === 'A' ? '我方' : item.roleName || '相对方', - displayName: - item.roleCode === 'A' - ? '我方' - : `相对方${roleIndex >= 0 ? roleIndex + 1 : 1}`, + ...createParty(roleCode), + roleCode, + roleName: roleCode === PARTY_ROLE_OUR ? "我方" : item.roleName || "相对方", + displayName: "", partnerId: item.partnerId || null, - oppositeId: item.oppositeId || '', - companyName: item.companyName || '', - companyType: item.companyType || '', - oppCharacter: item.oppositeCharacter || '', - contactName: item.contactName || '', - contactPhone: item.contactPhone || '', - contactEmail: item.contactEmail || '', - contactAddress: item.contactAddress || '', - registeredAddress: item.registeredAddress || '', - legalRepresentative: item.legalRepresentative || '', - creditCode: item.creditCode || '', - bankName: item.bankName || '', - bankAccount: item.bankAccount || '', - naturalPersonIdType: item.naturalPersonIdType || '', - naturalPersonIdNumber: item.naturalPersonIdNumber || '', - isGroupInternal: item.isGroupInternal === '1' || item.isGroupInternal === true, - isHongKongMacaoTaiwan: item.isHongKongMacaoTaiwan === '1' || item.isHongKongMacaoTaiwan === true, - } + oppositeId: item.oppositeId || "", + companyName: item.companyName || "", + companyType: item.companyType || "", + oppCharacter: item.oppositeCharacter || "", + contactName: item.contactName || "", + contactPhone: item.contactPhone || "", + contactEmail: item.contactEmail || "", + contactAddress: item.contactAddress || "", + registeredAddress: item.registeredAddress || "", + legalRepresentative: item.legalRepresentative || "", + creditCode: item.creditCode || "", + bankName: item.bankName || "", + bankAccount: item.bankAccount || "", + naturalPersonIdType: item.naturalPersonIdType || "", + naturalPersonIdNumber: item.naturalPersonIdNumber || "", + isGroupInternal: + item.isGroupInternal === "1" || item.isGroupInternal === true, + isHongKongMacaoTaiwan: + item.isHongKongMacaoTaiwan === "1" || item.isHongKongMacaoTaiwan === true, + }; +} + +/** 深拷贝合同方状态 */ +function copyPartyState(input) { + const source = input || {}; + const listState = buildPartyStateFromList(source.partyList); + if (listState) return listState; + const ourPartyList = Array.isArray(source.ourPartyList) + ? source.ourPartyList + : []; + const counterpartyList = Array.isArray(source.counterpartyList) + ? source.counterpartyList + : []; + return { + ourPartyList: ourPartyList.map((item, index) => ({ + ...createParty(PARTY_ROLE_OUR, index), + ...item, + roleCode: PARTY_ROLE_OUR, + roleName: "我方", + displayName: `我方主体${index + 1}`, + })), + counterpartyList: counterpartyList.map((item, index) => ({ + ...createParty(PARTY_ROLE_OPPOSITE, index), + ...item, + roleCode: PARTY_ROLE_OPPOSITE, + roleName: "相对方", + displayName: `相对方${index + 1}`, + })), + isThreePartyContract: counterpartyList.length > 1, + }; } function buildPartyStateFromList(list = []) { - if (!Array.isArray(list) || !list.length) return null - const state = { partyA: {}, counterpartyList: [], isThreePartyContract: false } + if (!Array.isArray(list) || !list.length) return null; + const state = { + ourPartyList: [], + counterpartyList: [], + isThreePartyContract: false, + }; list.forEach((item) => { - if (item.roleCode === 'A') { - state.partyA = mapLaunchPartyToFormParty(item) + const party = mapLaunchPartyToFormParty(item); + if (party.roleCode === PARTY_ROLE_OUR) { + state.ourPartyList.push(party); } else { - state.counterpartyList.push(mapLaunchPartyToFormParty(item)) + state.counterpartyList.push(party); } - }) + }); + state.ourPartyList = state.ourPartyList.map((item, index) => ({ + ...item, + displayName: `我方主体${index + 1}`, + })); state.counterpartyList = state.counterpartyList.map((item, index) => ({ ...item, - roleKey: item.roleKey || COUNTERPARTY_ROLE_CODES[index], displayName: `相对方${index + 1}`, - })) - state.isThreePartyContract = state.counterpartyList.length > 1 - return state + })); + state.isThreePartyContract = state.counterpartyList.length > 1; + return state; } /** 获取有效的合同方列表 */ function getPartyList(partyState) { - const state = copyPartyState(partyState) - return [state.partyA, ...(state.counterpartyList || [])].filter( - (item) => item && String(item.companyName || '').trim() !== '', - ) + const state = copyPartyState(partyState); + return [ + ...(state.ourPartyList || []), + ...(state.counterpartyList || []), + ].filter((item) => item && String(item.companyName || "").trim() !== ""); } /** 获取合同方摘要(如 "甲方:xxx;乙方:xxx") */ function getPartySummary(partyState) { return getPartyList(partyState) - .map((item) => `${item.roleName || '未知'}:${item.companyName || '未填写'}`) - .join(';') + .map( + (item) => `${item.roleName || "未知"}:${item.companyName || "未填写"}`, + ) + .join(";"); } /** 构建推送法务系统的相对方信息列表(排除甲方) */ function buildContractOpposites(partyState) { return getPartyList(partyState) - .filter((item) => item.roleKey !== 'A') + .filter((item) => item.roleCode === PARTY_ROLE_OPPOSITE) .map((item) => ({ roleName: item.roleName, oppositeName: item.companyName, - oppCharacter: item.oppCharacter || 'QY', - credirCode: item.oppCharacter === 'QY' ? item.creditCode : '', - idCard: item.oppCharacter === 'ZZR' ? item.naturalPersonIdNumber : '', + oppCharacter: item.oppCharacter || "QY", + credirCode: item.oppCharacter === "QY" ? item.creditCode : "", + idCard: item.oppCharacter === "ZZR" ? item.naturalPersonIdNumber : "", companyType: item.companyType, companyNature: item.companyNature, contactName: item.contactName, contactPhone: item.contactPhone, contactEmail: item.contactEmail, - })) + })); } /** 构建合同方同步列表(用于推送法务) */ function buildPartySyncList(partyState) { return getPartyList(partyState).map((item) => ({ - roleKey: item.roleKey, + roleCode: item.roleCode, roleName: item.roleName, companyName: item.companyName, oppCharacter: item.oppCharacter, @@ -497,7 +538,7 @@ function buildPartySyncList(partyState) { contactEmail: item.contactEmail, bankName: item.bankName, bankAccount: item.bankAccount, - })) + })); } // ========== 履行计划 ========== @@ -506,15 +547,15 @@ function buildPartySyncList(partyState) { function createPerformancePlan() { return { id: `plan_${Date.now()}_${Math.random().toString(16).slice(2, 8)}`, - planMatter: '', - planWhere: '', - planTime: '', + planMatter: "", + planWhere: "", + planTime: "", planDay: 0, - planMoney: '', - planSubjectCode: '', - planSubjectName: '', - planDetail: '', - } + planMoney: "", + planSubjectCode: "", + planSubjectName: "", + planDetail: "", + }; } // ========== 合同发起表单数据模型 ========== @@ -524,71 +565,76 @@ function createContractFormData() { return { id: null, // 合同基本信息 - contractName: '', // 合同名称 - contractCode: '', // 合同编码 - contractType: '', // 合同类型 - contractTypeCode: '', // 合同类型编码 - contractAmount: null, // 合同金额(元) - effectiveStart: '', // 有效期起 YYYY-MM-DD - effectiveEnd: '', // 有效期止 YYYY-MM-DD - contractPeriodType: '0', // 合同期限类型: 0-固定期限 1-无固定期限 - periodExplain: '', // 期限说明(无固定期限时填写) - paymentMethod: '', // 付款方式 - paymentDirection: '0', // 收支方向 - isElectron: '1', // 签署方式: 0-纸质 1-电子 - textSource: 'standardText', // 文本来源 - contractSource: 'ECP', // 合同来源 - dateType: '0', // 固定期间日期类型:0-年 1-月 2-日 - valuationMode: '', // 计价方式: 0-无金额 1-固定总价 2-预估总价 3-无法预估总价 4-固定单价 - currencyName: '', // 币种: RMB/USD/HKD/EUR/IDR - sealType: '', // 签订方式: GZ/HTZYZ/... - mandateType: '', // 委托类型 - cargoType: '', // 货种 - primaryContent: '', // 合同主要内容 - amountExplain: '', // 金额说明 - contractGistRemark: '', // 签订依据说明 - bodySummary: '', // 正文摘要 - assistDept: '', // 协助部门 - executorAccount: '', // 合同执行人手机号 - selfCode: '', // 二级单位合同编码 - mainContractCode: '', // 主合同编码 - signingSubjectCode: '', // 签约主体编码 - invoiceDate: '', // 开票时间 - receiveDate: '', // 收款时间 + contractName: "", // 合同名称 + contractCode: "", // 合同编码 + contractType: "", // 合同类型 + contractTypeCode: "", // 合同类型编码 + contractAmount: null, // 合同金额(元) + effectiveStart: "", // 有效期起 YYYY-MM-DD + effectiveEnd: "", // 有效期止 YYYY-MM-DD + contractPeriodType: "0", // 合同期限类型: 0-固定期限 1-无固定期限 + periodExplain: "", // 期限说明(无固定期限时填写) + paymentMethod: "", // 付款方式 + paymentDirection: "0", // 收支方向 + isElectron: "1", // 签署方式: 0-纸质 1-电子 + textSource: "standardText", // 文本来源 + contractSource: "ECP", // 合同来源 + dateType: "0", // 固定期间日期类型:0-年 1-月 2-日 + valuationMode: "", // 计价方式: 0-无金额 1-固定总价 2-预估总价 3-无法预估总价 4-固定单价 + currencyName: "", // 币种: RMB/USD/HKD/EUR/IDR + sealType: "", // 签订方式: GZ/HTZYZ/... + mandateType: "", // 委托类型 + cargoType: "", // 货种 + primaryContent: "", // 合同主要内容 + amountExplain: "", // 金额说明 + contractGistRemark: "", // 签订依据说明 + bodySummary: "", // 正文摘要 + assistDept: "", // 协助部门 + executorAccount: "", // 合同执行人手机号 + selfCode: "", // 二级单位合同编码 + mainContractCode: "", // 主合同编码 + signingSubjectCode: "", // 签约主体编码 + invoiceDate: "", // 开票时间 + receiveDate: "", // 收款时间 // 文件 - contractBodyFileName: '', // 合同正文文件名 - contractBodyFilePath: '', - contractBodyFileType: '', + contractBodyFileName: "", // 合同正文文件名 + contractBodyFilePath: "", + contractBodyFileType: "", contractBodyFileSize: null, contractBodyArrayBuffer: null, // 合同正文文件二进制 - otherAttachmentList: [], // 其他附件列表 - contractGistFileList: [], // 签订依据附件列表 - templateFieldValues: {}, // 模板字段值,统一按 {字段名: 值} 保存 + otherAttachmentList: [], // 其他附件列表 + contractGistFileList: [], // 签订依据附件列表 + templateFieldValues: {}, // 模板字段值,统一按 {字段名: 值} 保存 // 合同方 partyState: { isThreePartyContract: false, - partyA: { companyName: '', roleKey: 'A', roleName: '我方', displayName: '我方' }, + ourPartyList: [createParty(PARTY_ROLE_OUR, 0)], counterpartyList: [], }, + isSingleAgreement: "0", // 合同属性 - isInvolveHongKongMacaoTaiwan: false, // 是否涉及港澳台 - isPublicTender: false, // 是否公开招标 - isForeignRelatedSubject: false, // 签约主体是否涉外 - isIncludedInCurrentYearBudget: false, // 是否列入当年预算 - isShared: false, // 是否共享 - isMajorContract: false, // 是否重大合同 + isInvolveHongKongMacaoTaiwan: false, // 是否涉及港澳台 + isPublicTender: false, // 是否公开招标 + isForeignRelatedSubject: false, // 签约主体是否涉外 + isIncludedInCurrentYearBudget: false, // 是否列入当年预算 + isShared: false, // 是否共享 + isMajorContract: false, // 是否重大合同 // 额外金额 extraAmounts: { - depositEnabled: false, deposit: 0, - pledgeEnabled: false, pledge: 0, - advanceEnabled: false, advance: 0, - guaranteeEnabled: false, guarantee: 0, + depositEnabled: false, + deposit: 0, + pledgeEnabled: false, + pledge: 0, + advanceEnabled: false, + advance: 0, + guaranteeEnabled: false, + guarantee: 0, }, - ewAmountType: '', + ewAmountType: "", djAmount: 0, yjAmount: 0, yfkAmount: 0, @@ -598,153 +644,194 @@ function createContractFormData() { performancePlans: [], // 签署日期 - signDate: dayjs().format('YYYY-MM-DD'), - } + signDate: dayjs().format("YYYY-MM-DD"), + }; } /** 深拷贝合同表单数据(用 source 覆盖默认值) */ function copyContractFormData(data = {}) { - const form = createContractFormData() - const source = data || {} - const fileState = splitFilesByCategory(source) + const form = createContractFormData(); + const source = data || {}; + const fileState = splitFilesByCategory(source); - form.contractName = source.contractName ?? form.contractName - form.contractCode = source.contractCode ?? form.contractCode - form.contractType = source.contractType ?? form.contractType - form.contractTypeCode = source.contractTypeCode ?? source.contractType ?? form.contractTypeCode - form.contractAmount = source.contractAmount ?? form.contractAmount - form.effectiveStart = source.effectiveStart ? String(source.effectiveStart).slice(0, 10) : form.effectiveStart - form.effectiveEnd = source.effectiveEnd ? String(source.effectiveEnd).slice(0, 10) : form.effectiveEnd - form.contractPeriodType = source.contractPeriodType ?? form.contractPeriodType - form.periodExplain = source.periodExplain ?? form.periodExplain - form.paymentMethod = source.paymentMethod ?? form.paymentMethod - form.paymentDirection = source.paymentDirection ?? form.paymentDirection - form.isElectron = source.isElectron ?? form.isElectron - form.textSource = source.textSource ?? form.textSource - form.contractSource = source.contractSource ?? form.contractSource - form.dateType = source.dateType ?? form.dateType - form.valuationMode = source.valuationMode ?? form.valuationMode - form.currencyName = source.currencyName ?? form.currencyName - form.sealType = source.sealType ?? form.sealType - form.mandateType = source.mandateType ?? form.mandateType - form.cargoType = source.cargoType ?? form.cargoType - form.primaryContent = source.primaryContent ?? form.primaryContent - form.amountExplain = source.amountExplain ?? form.amountExplain - form.contractGistRemark = source.contractGistRemark ?? form.contractGistRemark - form.bodySummary = source.bodySummary ?? form.bodySummary - form.assistDept = source.assistDept ?? source.assistDepartmentName ?? form.assistDept - form.executorAccount = source.executorAccount ?? form.executorAccount - form.selfCode = source.selfCode ?? form.selfCode - form.mainContractCode = source.mainContractCode ?? form.mainContractCode - form.signingSubjectCode = source.signingSubjectCode ?? source.orgId ?? form.signingSubjectCode - form.invoiceDate = source.invoiceDate ? String(source.invoiceDate).slice(0, 10) : form.invoiceDate - form.receiveDate = source.receiveDate ? String(source.receiveDate).slice(0, 10) : form.receiveDate - form.contractBodyFileName = fileState.contractBodyFileName - form.contractBodyFilePath = fileState.contractBodyFilePath - form.contractBodyFileType = fileState.contractBodyFileType - form.contractBodyFileSize = fileState.contractBodyFileSize - form.templateFieldValues = source.templateFieldValues && typeof source.templateFieldValues === 'object' - ? { ...source.templateFieldValues } - : source.templateFieldValuesJson - ? (() => { - try { - return JSON.parse(source.templateFieldValuesJson) - } catch (error) { - return {} - } - })() - : {} + form.contractName = source.contractName ?? form.contractName; + form.contractCode = source.contractCode ?? form.contractCode; + form.contractType = source.contractType ?? form.contractType; + form.contractTypeCode = + source.contractTypeCode ?? source.contractType ?? form.contractTypeCode; + form.contractAmount = source.contractAmount ?? form.contractAmount; + form.effectiveStart = source.effectiveStart + ? String(source.effectiveStart).slice(0, 10) + : form.effectiveStart; + form.effectiveEnd = source.effectiveEnd + ? String(source.effectiveEnd).slice(0, 10) + : form.effectiveEnd; + form.contractPeriodType = + source.contractPeriodType ?? form.contractPeriodType; + form.periodExplain = source.periodExplain ?? form.periodExplain; + form.paymentMethod = source.paymentMethod ?? form.paymentMethod; + form.paymentDirection = source.paymentDirection ?? form.paymentDirection; + form.isElectron = source.isElectron ?? form.isElectron; + form.textSource = source.textSource ?? form.textSource; + form.contractSource = source.contractSource ?? form.contractSource; + form.dateType = source.dateType ?? form.dateType; + form.valuationMode = source.valuationMode ?? form.valuationMode; + form.currencyName = source.currencyName ?? form.currencyName; + form.sealType = source.sealType ?? form.sealType; + form.mandateType = source.mandateType ?? form.mandateType; + form.cargoType = source.cargoType ?? form.cargoType; + form.primaryContent = source.primaryContent ?? form.primaryContent; + form.amountExplain = source.amountExplain ?? form.amountExplain; + form.contractGistRemark = + source.contractGistRemark ?? form.contractGistRemark; + form.bodySummary = source.bodySummary ?? form.bodySummary; + form.assistDept = + source.assistDept ?? source.assistDepartmentName ?? form.assistDept; + form.executorAccount = source.executorAccount ?? form.executorAccount; + form.selfCode = source.selfCode ?? form.selfCode; + form.mainContractCode = source.mainContractCode ?? form.mainContractCode; + form.signingSubjectCode = + source.signingSubjectCode ?? source.orgId ?? form.signingSubjectCode; + form.isSingleAgreement = source.isSingleAgreement ?? form.isSingleAgreement; + form.invoiceDate = source.invoiceDate + ? String(source.invoiceDate).slice(0, 10) + : form.invoiceDate; + form.receiveDate = source.receiveDate + ? String(source.receiveDate).slice(0, 10) + : form.receiveDate; + form.contractBodyFileName = fileState.contractBodyFileName; + form.contractBodyFilePath = fileState.contractBodyFilePath; + form.contractBodyFileType = fileState.contractBodyFileType; + form.contractBodyFileSize = fileState.contractBodyFileSize; + form.templateFieldValues = + source.templateFieldValues && typeof source.templateFieldValues === "object" + ? { ...source.templateFieldValues } + : source.templateFieldValuesJson + ? (() => { + try { + return JSON.parse(source.templateFieldValuesJson); + } catch (error) { + return {}; + } + })() + : {}; form.contractBodyArrayBuffer = source.contractBodyArrayBuffer - ? (source.contractBodyArrayBuffer instanceof ArrayBuffer ? source.contractBodyArrayBuffer.slice(0) : null) - : null - form.otherAttachmentList = fileState.otherAttachmentList - form.contractGistFileList = fileState.contractGistFileList - form.partyState = copyPartyState(source.partyState || source) - form.isInvolveHongKongMacaoTaiwan = source.isInvolveHongKongMacaoTaiwan ?? form.isInvolveHongKongMacaoTaiwan - form.isPublicTender = source.isPublicTender ?? form.isPublicTender - form.isForeignRelatedSubject = source.isForeignRelatedSubject ?? form.isForeignRelatedSubject - form.isIncludedInCurrentYearBudget = source.isIncludedInCurrentYearBudget ?? form.isIncludedInCurrentYearBudget - form.isShared = source.isShared ?? form.isShared - form.isMajorContract = source.isMajorContract ?? form.isMajorContract - form.signDate = source.signDate ? String(source.signDate).slice(0, 10) : form.signDate + ? source.contractBodyArrayBuffer instanceof ArrayBuffer + ? source.contractBodyArrayBuffer.slice(0) + : null + : null; + form.otherAttachmentList = fileState.otherAttachmentList; + form.contractGistFileList = fileState.contractGistFileList; + form.partyState = copyPartyState(source.partyState || source); + form.isInvolveHongKongMacaoTaiwan = + source.isInvolveHongKongMacaoTaiwan ?? form.isInvolveHongKongMacaoTaiwan; + form.isPublicTender = source.isPublicTender ?? form.isPublicTender; + form.isForeignRelatedSubject = + source.isForeignRelatedSubject ?? form.isForeignRelatedSubject; + form.isIncludedInCurrentYearBudget = + source.isIncludedInCurrentYearBudget ?? form.isIncludedInCurrentYearBudget; + form.isShared = source.isShared ?? form.isShared; + form.isMajorContract = source.isMajorContract ?? form.isMajorContract; + form.signDate = source.signDate + ? String(source.signDate).slice(0, 10) + : form.signDate; form.extraAmounts = { - depositEnabled: false, deposit: 0, - pledgeEnabled: false, pledge: 0, - advanceEnabled: false, advance: 0, - guaranteeEnabled: false, guarantee: 0, - } - form.extraAmounts = restoreExtraAmounts(source, form.extraAmounts) - form.ewAmountType = source.ewAmountType ?? form.ewAmountType - form.djAmount = source.djAmount ?? form.djAmount - form.yjAmount = source.yjAmount ?? form.yjAmount - form.yfkAmount = source.yfkAmount ?? form.yfkAmount - form.bzjAmount = source.bzjAmount ?? form.bzjAmount + depositEnabled: false, + deposit: 0, + pledgeEnabled: false, + pledge: 0, + advanceEnabled: false, + advance: 0, + guaranteeEnabled: false, + guarantee: 0, + }; + form.extraAmounts = restoreExtraAmounts(source, form.extraAmounts); + form.ewAmountType = source.ewAmountType ?? form.ewAmountType; + form.djAmount = source.djAmount ?? form.djAmount; + form.yjAmount = source.yjAmount ?? form.yjAmount; + form.yfkAmount = source.yfkAmount ?? form.yfkAmount; + form.bzjAmount = source.bzjAmount ?? form.bzjAmount; form.performancePlans = (source.performancePlans || []).map((item) => ({ ...createPerformancePlan(), ...item, - planTime: item.planTime ? String(item.planTime).slice(0, 10) : '', - })) + planTime: item.planTime ? String(item.planTime).slice(0, 10) : "", + })); - return form + return form; } // ========== 模板变量预览数据构建 ========== /** 将合同表单数据转换为模板变量字典(用于 docxtemplater 渲染) */ -function buildTemplatePreviewData(formData, templateLabel = '', creationMode = 'template') { - const form = copyContractFormData(formData) - const partyA = getPartyList(form).find((item) => item.roleCode === 'A') || {} - const partyB = getPartyList(form).find((item) => item.roleCode === 'B') || {} - const partyC = getPartyList(form).find((item) => item.roleCode === 'C') || {} - const partyD = getPartyList(form).find((item) => item.roleCode === 'D') || {} - const partyE = getPartyList(form).find((item) => item.roleCode === 'E') || {} - const partyF = getPartyList(form).find((item) => item.roleCode === 'F') || {} - const templateValues = form.templateFieldValues || {} +function buildTemplatePreviewData( + formData, + templateLabel = "", + creationMode = "template", +) { + const form = copyContractFormData(formData); + const parties = getPartyList(form); + const templateValues = form.templateFieldValues || {}; - function pick(fieldKey, fallback = '') { - if (templateValues[fieldKey] !== undefined && templateValues[fieldKey] !== null && templateValues[fieldKey] !== '') { - return templateValues[fieldKey] + function pick(fieldKey, fallback = "") { + if ( + templateValues[fieldKey] !== undefined && + templateValues[fieldKey] !== null && + templateValues[fieldKey] !== "" + ) { + return templateValues[fieldKey]; } - return fallback + return fallback; } return { - contractName: pick('contractName', form.contractName || ''), - contractCode: pick('contractCode', form.contractCode || ''), - contractType: pick('contractType', form.contractType || ''), - contractAmountDisplay: pick('contractAmountDisplay', formatContractAmount(form.contractAmount)), - effectiveStart: pick('effectiveStart', form.effectiveStart || ''), - effectiveEnd: pick('effectiveEnd', form.effectiveEnd || ''), - effectivePeriod: pick('effectivePeriod', [form.effectiveStart, form.effectiveEnd].filter(Boolean).join(' 至 ')), - paymentMethod: pick('paymentMethod', form.paymentMethod || ''), - primaryContent: pick('primaryContent', form.primaryContent || ''), - amountExplain: pick('amountExplain', form.amountExplain || ''), - partyAName: pick('partyAName', partyA.companyName || ''), - partyBName: pick('partyBName', partyB.companyName || ''), - partyCName: pick('partyCName', partyC.companyName || ''), - partyDName: pick('partyDName', partyD.companyName || ''), - partyEName: pick('partyEName', partyE.companyName || ''), - partyFName: pick('partyFName', partyF.companyName || ''), - counterpartySummary: pick('counterpartySummary', getPartySummary(form)), - attachmentSummary: pick('attachmentSummary', formatFileSummary(form.otherAttachmentList)), - templateLabel: pick('templateLabel', templateLabel || (creationMode === 'manual' ? '手动创建' : '')), - signDate: pick('signDate', form.signDate || dayjs().format('YYYY-MM-DD')), - businessRemark: pick('businessRemark', ''), - contractSubject: pick('contractSubject', ''), + contractName: pick("contractName", form.contractName || ""), + contractCode: pick("contractCode", form.contractCode || ""), + contractType: pick("contractType", form.contractType || ""), + contractAmountDisplay: pick( + "contractAmountDisplay", + formatContractAmount(form.contractAmount), + ), + effectiveStart: pick("effectiveStart", form.effectiveStart || ""), + effectiveEnd: pick("effectiveEnd", form.effectiveEnd || ""), + effectivePeriod: pick( + "effectivePeriod", + [form.effectiveStart, form.effectiveEnd].filter(Boolean).join(" 至 "), + ), + paymentMethod: pick("paymentMethod", form.paymentMethod || ""), + primaryContent: pick("primaryContent", form.primaryContent || ""), + amountExplain: pick("amountExplain", form.amountExplain || ""), + partyAName: pick("partyAName", parties[0]?.companyName || ""), + partyBName: pick("partyBName", parties[1]?.companyName || ""), + partyCName: pick("partyCName", parties[2]?.companyName || ""), + partyDName: pick("partyDName", parties[3]?.companyName || ""), + partyEName: pick("partyEName", parties[4]?.companyName || ""), + partyFName: pick("partyFName", parties[5]?.companyName || ""), + counterpartySummary: pick("counterpartySummary", getPartySummary(form)), + attachmentSummary: pick( + "attachmentSummary", + formatFileSummary(form.otherAttachmentList), + ), + templateLabel: pick( + "templateLabel", + templateLabel || (creationMode === "manual" ? "手动创建" : ""), + ), + signDate: pick("signDate", form.signDate || dayjs().format("YYYY-MM-DD")), + businessRemark: pick("businessRemark", ""), + contractSubject: pick("contractSubject", ""), templateFieldValuesJson: JSON.stringify(form.templateFieldValues || {}), - } + }; } // ========== 提交报文构建 ========== /** 构建推送法务系统的合同报文 */ function buildContractSubmitPayload(formData, extra = {}) { - const form = copyContractFormData(formData) + const form = copyContractFormData(formData); return { - creationMode: extra.creationMode || 'template', - templateKey: extra.templateKey || '', - templateName: extra.templateName || '', + creationMode: extra.creationMode || "template", + templateKey: extra.templateKey || "", + templateName: extra.templateName || "", basicInfo: { contractName: form.contractName, contractCode: form.contractCode, @@ -774,7 +861,11 @@ function buildContractSubmitPayload(formData, extra = {}) { executorAccount: form.executorAccount, selfCode: form.selfCode, mainContractCode: form.mainContractCode, - signingSubjectCode: form.signingSubjectCode, + signingSubjectCode: + (form.partyState.ourPartyList || []) + .map((item) => String(item.subjectCode || "").trim()) + .filter(Boolean) + .join(",") || form.signingSubjectCode, invoiceDate: form.invoiceDate, receiveDate: form.receiveDate, }, @@ -816,7 +907,7 @@ function buildContractSubmitPayload(formData, extra = {}) { contractBodyFileName: form.contractBodyFileName, performancePlans: form.performancePlans, }, - } + }; } const emit = defineEmits(["save"]); @@ -881,52 +972,65 @@ const launchFieldList = computed(() => { /** 模板字段中与合同主信息同名的直接映射字段 */ const CONTRACT_DIRECT_FIELDS = [ - 'contractName', 'contractCode', 'contractType', - 'effectiveStart', 'effectiveEnd', - 'paymentMethod', 'primaryContent', 'amountExplain', - 'businessRemark', 'contractSubject', -] + "contractName", + "contractCode", + "contractType", + "effectiveStart", + "effectiveEnd", + "paymentMethod", + "primaryContent", + "amountExplain", + "businessRemark", + "contractSubject", +]; function getLaunchFieldValue(fieldKey) { // 1. 优先返回用户在"模板信息录入"中自定义的值 - const templateVal = contractDetailState.value.templateFieldValues?.[fieldKey] - if (templateVal !== undefined && templateVal !== null && templateVal !== '') { - return templateVal + const templateVal = contractDetailState.value.templateFieldValues?.[fieldKey]; + if (templateVal !== undefined && templateVal !== null && templateVal !== "") { + return templateVal; } // 2. 若字段与合同主信息直接同名,自动带入主信息的值 if (CONTRACT_DIRECT_FIELDS.includes(fieldKey)) { - return contractDetailState.value[fieldKey] ?? '' + return contractDetailState.value[fieldKey] ?? ""; } // 3. 特殊计算字段 - if (fieldKey === 'contractAmountDisplay') { - return formatContractAmount(contractDetailState.value.contractAmount) + if (fieldKey === "contractAmountDisplay") { + return formatContractAmount(contractDetailState.value.contractAmount); } - if (fieldKey === 'effectivePeriod') { - return [contractDetailState.value.effectiveStart, contractDetailState.value.effectiveEnd].filter(Boolean).join(' 至 ') + if (fieldKey === "effectivePeriod") { + return [ + contractDetailState.value.effectiveStart, + contractDetailState.value.effectiveEnd, + ] + .filter(Boolean) + .join(" 至 "); } - if (fieldKey === 'templateLabel') { - return activeTemplate.value?.templateName || '' + if (fieldKey === "templateLabel") { + return activeTemplate.value?.templateName || ""; } - if (fieldKey === 'signDate') { - return contractDetailState.value.signDate || dayjs().format('YYYY-MM-DD') + if (fieldKey === "signDate") { + return contractDetailState.value.signDate || dayjs().format("YYYY-MM-DD"); } - if (fieldKey === 'counterpartySummary') { - return getPartySummary(contractDetailState.value.partyState) + if (fieldKey === "counterpartySummary") { + return getPartySummary(contractDetailState.value.partyState); } - if (fieldKey === 'attachmentSummary') { - return formatFileSummary(contractDetailState.value.otherAttachmentList) + if (fieldKey === "attachmentSummary") { + return formatFileSummary(contractDetailState.value.otherAttachmentList); } // 4. 合同方名称字段 if (/^party[A-F]Name$/.test(fieldKey)) { - const roleCode = fieldKey.replace('party', '').replace('Name', '') - const party = getPartyList(contractDetailState.value.partyState).find((item) => item.roleCode === roleCode) - return party?.companyName ?? '' + const fieldIndex = PARTY_NAME_KEYS.indexOf(fieldKey); + return ( + getPartyList(contractDetailState.value.partyState)[fieldIndex] + ?.companyName ?? "" + ); } - return '' + return ""; } function setLaunchFieldValue(fieldKey, value) { @@ -1243,9 +1347,8 @@ async function handleManualContractBodyUpload(file) { file.name.split(".").pop()?.toLowerCase() || "docx"; contractDetailState.value.contractBodyFileSize = file.size; contractDetailState.value.textSource = "draftBySelf"; - contractDetailState.value.contractBodyArrayBuffer = await readFileAsArrayBuffer( - file, - ); + contractDetailState.value.contractBodyArrayBuffer = + await readFileAsArrayBuffer(file); syncManualBodyFileList(); previewBlob.value = null; previewError.value = ""; @@ -1369,7 +1472,10 @@ async function showModal(initialValues = {}, mode = "create") { : "template"); templateKey.value = isCreate ? publishedTemplates.value[0]?.id || "" - : initialValues.templateKey || initialValues.templateId || initialValues.detailState?.templateId || ""; + : initialValues.templateKey || + initialValues.templateId || + initialValues.detailState?.templateId || + ""; contractDetailState.value = isCreate ? createContractFormData() : initialValues.detailState diff --git a/src/views/contract/launch/index.vue b/src/views/contract/launch/index.vue index 6f7dcb8..62b094a 100644 --- a/src/views/contract/launch/index.vue +++ b/src/views/contract/launch/index.vue @@ -108,10 +108,11 @@ 复制 变更 编辑 - 提交法务 + {{ isSingleAgreementRecord(row) ? "提交审批" : "提交法务" }} 审批记录 回调data - 测试通过 + {{ isSingleAgreementRecord(row) ? "审批通过" : "测试通过" }} + {{ isSingleAgreementRecord(row) ? "审批驳回" : "测试驳回" }} 在线签订 下载签署文件 上传已签署版本 @@ -347,12 +348,19 @@ function canArchive(row) { return row.status === "pending_archive"; } +function isSingleAgreementRecord(row) { + return String(row?.isSingleAgreement || "0") === "1"; +} + function canViewApprove(row) { - return Boolean(row.lawContractId) && ["reviewing", "rejected", "pending_sign", "signing", "pending_collect", "pending_archive", "completed"].includes(row.status); + return !isSingleAgreementRecord(row) + && Boolean(row.lawContractId) + && ["reviewing", "rejected", "pending_sign", "signing", "pending_collect", "pending_archive", "completed"].includes(row.status); } function canViewLawCallback(row) { - return ["reviewing", "rejected", "pending_sign", "signing", "pending_collect", "pending_archive", "completed"].includes(row.status); + return !isSingleAgreementRecord(row) + && ["reviewing", "rejected", "pending_sign", "signing", "pending_collect", "pending_archive", "completed"].includes(row.status); } function canDownloadSigned(row) { @@ -499,10 +507,13 @@ function handleChange(row) { } function handleSubmit(row) { + const singleAgreement = isSingleAgreementRecord(row); Modal.confirm({ - title: "确认提交法务审批", + title: singleAgreement ? "确认提交内部审批" : "确认提交法务审批", content: h("div", [ - h("p", `确定将合同【${row.contractName}】提交法务系统审批吗?`), + h("p", singleAgreement + ? `确定将单方协议【${row.contractName}】提交内部审批吗?` + : `确定将合同【${row.contractName}】提交法务系统审批吗?`), h("p", { style: { color: "#999", fontSize: "12px" } }, "提交后合同进入「审批中」状态,审批完成前不可编辑。"), ]), okText: "确认提交", @@ -510,10 +521,10 @@ function handleSubmit(row) { onOk: () => submitLaunchRecord(row.id).then((res) => { if (!isSuccessResponse(res)) { - message.error(getResponseMessage(res, "提交法务失败")); + message.error(getResponseMessage(res, singleAgreement ? "提交内部审批失败" : "提交法务失败")); return; } - message.success("已提交法务,等待审批"); + message.success(singleAgreement ? "已提交内部审批,等待审批" : "已提交法务,等待审批"); getList(); }), icon: null, @@ -605,9 +616,12 @@ function handleViewLawCallback(row) { } function handleMockApprove(row) { + const singleAgreement = isSingleAgreementRecord(row); Modal.confirm({ - title: "测试操作:模拟审批通过", - content: `模拟法务审批通过合同【${row.contractName}】,状态将变为「待签署」。`, + title: singleAgreement ? "测试操作:模拟内部审批通过" : "测试操作:模拟审批通过", + content: singleAgreement + ? `模拟单方协议【${row.contractName}】内部审批通过,状态将变为「待签署」。` + : `模拟法务审批通过合同【${row.contractName}】,状态将变为「待签署」。`, okText: "确认模拟", cancelText: "取消", onOk: () => @@ -624,10 +638,13 @@ function handleMockApprove(row) { } function handleMockReject(row) { + const singleAgreement = isSingleAgreementRecord(row); Modal.confirm({ - title: "测试操作:模拟审批驳回", + title: singleAgreement ? "测试操作:模拟内部审批驳回" : "测试操作:模拟审批驳回", content: h("div", [ - h("p", `模拟法务审批驳回合同【${row.contractName}】,状态将回到「已驳回」。`), + h("p", singleAgreement + ? `模拟单方协议【${row.contractName}】内部审批驳回,状态将回到「已驳回」。` + : `模拟法务审批驳回合同【${row.contractName}】,状态将回到「已驳回」。`), h("p", { style: { color: "#999", fontSize: "12px" } }, "驳回后可重新编辑再提交。"), ]), okText: "确认驳回",