diff --git a/src/api/oa/erp/contractInfo/types.ts b/src/api/oa/erp/contractInfo/types.ts index 61e5674..3ca2f64 100644 --- a/src/api/oa/erp/contractInfo/types.ts +++ b/src/api/oa/erp/contractInfo/types.ts @@ -49,6 +49,31 @@ export interface ContractInfoVO { */ contractDate: string; + /** + * 合同属地标识(1中国大陆 2其他) + */ + contractTerritorialFlag?: string; + + /** + * 合同属地国家/地区(字典) + */ + contractTerritorialCountry?: string; + + /** + * 结算币种(CNY/USD/EUR) + */ + settlementCurrency?: string; + + /** + * 对人民币汇率 + */ + rmbExchangeRate?: number; + + /** + * 已生效客户订单标识(1是 0否) + */ + effectiveCustomerOrderFlag?: string; + /** * 合同总价 */ @@ -307,6 +332,31 @@ export interface ContractInfoForm extends BaseEntity { */ contractDate?: string; + /** + * 合同属地标识(1中国大陆 2其他) + */ + contractTerritorialFlag?: string; + + /** + * 合同属地国家/地区(字典) + */ + contractTerritorialCountry?: string; + + /** + * 结算币种(CNY/USD/EUR) + */ + settlementCurrency?: string; + + /** + * 对人民币汇率 + */ + rmbExchangeRate?: number; + + /** + * 已生效客户订单标识(1是 0否) + */ + effectiveCustomerOrderFlag?: string; + /** * 合同总价 */ @@ -563,6 +613,31 @@ export interface ContractInfoQuery extends PageQuery { */ contractDate?: string; + /** + * 合同属地标识(1中国大陆 2其他) + */ + contractTerritorialFlag?: string; + + /** + * 合同属地国家/地区(字典) + */ + contractTerritorialCountry?: string; + + /** + * 结算币种(CNY/USD/EUR) + */ + settlementCurrency?: string; + + /** + * 对人民币汇率 + */ + rmbExchangeRate?: number; + + /** + * 已生效客户订单标识(1是 0否) + */ + effectiveCustomerOrderFlag?: string; + /** * 合同总价 */ diff --git a/src/api/oa/erp/contractLedgerReport/index.ts b/src/api/oa/erp/contractLedgerReport/index.ts index a867bef..c3688e6 100644 --- a/src/api/oa/erp/contractLedgerReport/index.ts +++ b/src/api/oa/erp/contractLedgerReport/index.ts @@ -18,6 +18,9 @@ export interface ContractLedgerReportVO { externalContractCode: string; customerContractCode: string; contractDate: string; + contractTerritorialFlag: string; + contractTerritorialCountry: string; + settlementCurrency: string; orderContractCode: string; projectContractCode: string; customerName: string; diff --git a/src/views/oa/erp/contractInfo/edit.vue b/src/views/oa/erp/contractInfo/edit.vue index c24547e..d3e3f54 100644 --- a/src/views/oa/erp/contractInfo/edit.vue +++ b/src/views/oa/erp/contractInfo/edit.vue @@ -116,6 +116,46 @@ + + + + + {{ dict.label }} + + + + + + + + + + + + + + + - - + + + + + + + + 已生效客户订单 + + + (无需盖章或签字,即刻生效的客户订单) + + + @@ -400,7 +457,7 @@ - 编辑 + 编辑 删除 @@ -723,7 +780,10 @@ const { contract_status, material_flag, contract_template_flag, - is_framework_contract + is_framework_contract, + contract_territorial_flag, + country_region, + currency_type } = toRefs( proxy?.useDict( 'contract_category', @@ -734,7 +794,10 @@ const { 'contract_status', 'material_flag', 'contract_template_flag', - 'is_framework_contract' + 'is_framework_contract', + 'contract_territorial_flag', + 'country_region', + 'currency_type' ) ); @@ -858,6 +921,7 @@ const materialDialog = reactive({ visible: false, title: '' }); +const editingMaterialIndex = ref(null); // 合同物料表单数据 const initMaterialFormData: ContractMaterialForm = { @@ -1011,6 +1075,11 @@ const initFormData: ContractInfoFormEx = { businessDirection: undefined, contractDeptId: undefined, contractDate: undefined, + contractTerritorialFlag: '1', + contractTerritorialCountry: undefined, + settlementCurrency: 'CNY', + rmbExchangeRate: 1, + effectiveCustomerOrderFlag: '0', totalPrice: 0, oneCustomerId: undefined, oneRepresent: undefined, @@ -1058,6 +1127,33 @@ const data = reactive<{ form: ContractInfoFormEx; rules: any }>({ contractName: [{ required: true, message: '合同名称不能为空', trigger: 'blur' }], businessDirection: [{ required: true, message: '业务方向不能为空', trigger: 'blur' }], contractManagerId: [{ required: true, message: '合同负责人不能为空', trigger: 'blur' }], + contractTerritorialFlag: [{ required: true, message: '合同属地标识不能为空', trigger: 'change' }], + contractTerritorialCountry: [ + { + validator: (_rule: any, _value: any, callback: (err?: Error) => void) => { + if (form.value.contractTerritorialFlag === '2' && !form.value.contractTerritorialCountry) { + callback(new Error('合同属地标识为“其他”时,国家/地区不能为空')); + return; + } + callback(); + }, + trigger: 'change' + } + ], + settlementCurrency: [{ required: true, message: '结算币种不能为空', trigger: 'change' }], + rmbExchangeRate: [{ required: true, message: '对人民币汇率不能为空', trigger: 'blur' }], + ossId: [ + { + validator: (_rule: any, _value: any, callback: (err?: Error) => void) => { + if (form.value.effectiveCustomerOrderFlag === '1' && !form.value.ossId) { + callback(new Error('已生效客户订单必须上传附件')); + return; + } + callback(); + }, + trigger: 'change' + } + ], contractTemplateFlag: [{ required: true, message: '合同模板标识不能为空', trigger: 'blur' }], isFrameworkContract: [{ required: true, message: '请选择关联框架合同', trigger: 'change' }], frameworkValidPeriod: [ @@ -1130,6 +1226,30 @@ watch( } ); +watch( + () => form.value.contractTerritorialFlag, + (newVal) => { + // 合同属地标识为“其他”时,新增物料默认税率为0 + initMaterialFormData.taxRate = newVal === '2' ? 0 : 13; + if (newVal === '2') { + materialForm.value.taxRate = 0; + } else { + form.value.contractTerritorialCountry = undefined; + } + }, + { immediate: true } +); + +watch( + () => form.value.settlementCurrency, + (newVal) => { + if (newVal === 'CNY') { + form.value.rmbExchangeRate = 1 as any; + } + }, + { immediate: true } +); + // 生成合同编号 const generateContractCode = async () => { if (isCodeGenerated.value) return; // 如果已经生成过,直接返回 @@ -1292,14 +1412,16 @@ const submitOss = () => { // 新增物料(新增合同时也可添加,不依赖contractId) const handleAddMaterial = () => { resetMaterialForm(); + editingMaterialIndex.value = null; materialForm.value.contractId = form.value.contractId; materialDialog.visible = true; materialDialog.title = '新增合同物料'; }; // 编辑物料 -const handleEditMaterial = (row: ContractMaterialVO) => { +const handleEditMaterial = (row: ContractMaterialVO, rowIndex: number) => { resetMaterialForm(); + editingMaterialIndex.value = rowIndex; materialForm.value = { ...row }; materialDialog.visible = true; materialDialog.title = '编辑合同物料'; @@ -1333,11 +1455,14 @@ const handleDeleteMaterial = async (row: ContractMaterialVO) => { const resetMaterialForm = () => { materialForm.value = { ...initMaterialFormData }; materialFormRef.value?.resetFields(); + // 合同属地标识=2 物料税率默认0 + materialForm.value.taxRate = form.value.contractTerritorialFlag === '2' ? 0 : 13; }; // 取消物料编辑 const cancelMaterial = () => { resetMaterialForm(); + editingMaterialIndex.value = null; materialDialog.visible = false; }; @@ -1490,7 +1615,11 @@ const submitMaterialForm = () => { unitName: unitName }; - if (materialForm.value.contractMaterialId) { + const editIndex = editingMaterialIndex.value; + if (editIndex !== null && editIndex >= 0 && editIndex < (form.value as any).contractMaterialList.length) { + // 优先按行索引回写,兼容框架合同带出但无主键ID的物料 + (form.value as any).contractMaterialList[editIndex] = materialData; + } else if (materialForm.value.contractMaterialId) { // 编辑模式:更新现有物料 const index = (form.value as any).contractMaterialList.findIndex( (item: any) => item.contractMaterialId === materialForm.value.contractMaterialId @@ -1511,6 +1640,7 @@ const submitMaterialForm = () => { calculateTotalPrice(); proxy?.$modal.msgSuccess('操作成功'); + editingMaterialIndex.value = null; materialDialog.visible = false; } }); diff --git a/src/views/oa/erp/contractInfo/index.vue b/src/views/oa/erp/contractInfo/index.vue index d9af35f..ea7fe2e 100644 --- a/src/views/oa/erp/contractInfo/index.vue +++ b/src/views/oa/erp/contractInfo/index.vue @@ -195,6 +195,22 @@ {{ parseTime(scope.row.frameworkValidPeriod, '{y}-{m}-{d}') }} + + + + + + + + + + + + + + + + @@ -360,7 +376,7 @@ import { CodeRuleEnum } from '@/enums/OAEnum'; const { proxy } = getCurrentInstance() as ComponentInternalInstance; const route = useRoute(); const router = useRouter(); -const { contract_category, business_direction, active_flag, contract_flag, contract_type, contract_status, contract_template_flag } = toRefs( +const { contract_category, business_direction, active_flag, contract_flag, contract_type, contract_status, contract_template_flag, contract_territorial_flag, country_region, currency_type } = toRefs( proxy?.useDict( 'contract_category', 'business_direction', @@ -368,7 +384,10 @@ const { contract_category, business_direction, active_flag, contract_flag, contr 'contract_flag', 'contract_type', 'contract_status', - 'contract_template_flag' + 'contract_template_flag', + 'contract_territorial_flag', + 'country_region', + 'currency_type' ) ); @@ -429,7 +448,11 @@ const columns = ref([ { key: 40, label: `合同模板标识`, visible: true }, { key: 41, label: `软控合同额(元)`, visible: true }, { key: 42, label: `关联框架合同`, visible: true }, - { key: 43, label: `框架合同有效期`, visible: false } + { key: 43, label: `框架合同有效期`, visible: false }, + { key: 44, label: `合同属地标识`, visible: true }, + { key: 45, label: `合同属地国家/地区`, visible: true }, + { key: 46, label: `结算币种`, visible: true }, + { key: 47, label: `对人民币汇率`, visible: true } ]); const data = reactive<{ queryParams: ContractInfoQuery }>({ @@ -445,6 +468,10 @@ const data = reactive<{ queryParams: ContractInfoQuery }>({ businessDirection: undefined, contractDeptId: undefined, contractDate: undefined, + contractTerritorialFlag: undefined, + contractTerritorialCountry: undefined, + settlementCurrency: undefined, + rmbExchangeRate: undefined, totalPrice: undefined, oneCustomerId: undefined, oneCustomerName: undefined, diff --git a/src/views/oa/erp/contractInfo/orderActivate.vue b/src/views/oa/erp/contractInfo/orderActivate.vue index e013b84..1d2fc21 100644 --- a/src/views/oa/erp/contractInfo/orderActivate.vue +++ b/src/views/oa/erp/contractInfo/orderActivate.vue @@ -181,6 +181,20 @@ + + + + + @@ -873,6 +887,9 @@ type ProjectInfoFormEx = ProjectInfoForm & { contractName?: string; customerContractCode?: string; finalCustomerId?: string | number; + effectiveCustomerOrderFlag?: string; + contractTerritorialFlag?: string; + rmbExchangeRate?: number; planStageList?: ErpProjectPlanStageForm[]; projectContractsList?: any[]; }; @@ -902,6 +919,8 @@ const initFormData: ProjectInfoFormEx = { contractName: undefined, customerContractCode: undefined, finalCustomerId: undefined, + contractTerritorialFlag: undefined, + rmbExchangeRate: undefined, remark: undefined, ossId: undefined, activeFlag: '1', @@ -919,6 +938,18 @@ const data = reactive<{ form: ProjectInfoFormEx; rules: any }>({ projectCategory: [{ required: true, message: '订单类别不能为空', trigger: 'change' }], orderType: [{ required: true, message: '订单类型不能为空', trigger: 'change' }], customerContractCode: [{ required: true, message: '客户合同编号不能为空', trigger: 'blur' }], + rmbExchangeRate: [ + { + validator: (_rule: any, value: any, callback: (err?: Error) => void) => { + if (form.value.contractTerritorialFlag === '2' && (value === undefined || value === null || value === '')) { + callback(new Error('合同属地标识为“其他”时,对人民币汇率不能为空')); + return; + } + callback(); + }, + trigger: 'blur' + } + ], peopleId: [{ required: true, message: '抄送人员不能为空', trigger: 'change' }], ossId: [{ required: true, message: '请上传终版合同', trigger: 'change' }] } @@ -1073,6 +1104,17 @@ const getDefaultRepaymentDate = (paymentMethod?: Record): string => return formatDateToYmd(target); }; +/** 回填合同扩展字段:属地标识、对人民币汇率 */ +const fillContractExtraFields = (contractData: any) => { + form.value.effectiveCustomerOrderFlag = contractData?.effectiveCustomerOrderFlag; + form.value.contractTerritorialFlag = contractData?.contractTerritorialFlag; + form.value.rmbExchangeRate = contractData?.rmbExchangeRate; + // 勾选“已生效客户订单”时,自动将合同附件带入终版合同附件 + if (contractData?.effectiveCustomerOrderFlag === '1' && contractData?.ossId) { + form.value.ossId = contractData.ossId; + } +}; + /** 根据路由 contractId 加载合同信息并回填表单;有项目则加载主项目+阶段+关联项目,无则从合同带出 */ const loadContractInfo = async () => { isFromContentChange.value = false; @@ -1105,6 +1147,7 @@ const loadContractInfo = async () => { form.value.contractName = contractData.contractName; form.value.customerContractCode = contractData.customerContractCode; form.value.finalCustomerId = contractData.finalCustomerId; + fillContractExtraFields(contractData); form.value.amount = contractData.totalPrice; // 根据部门ID查找部门信息,获取部门负责人和分管副总 @@ -1187,6 +1230,12 @@ onMounted(async () => { if (isEditMode) { const res = await getProjectInfo(id); Object.assign(form.value, res.data); + if (form.value.contractId) { + const contractRes = await getContractInfo(form.value.contractId as string | number); + if (contractRes?.data) { + fillContractExtraFields(contractRes.data); + } + } if (form.value.peopleId && typeof form.value.peopleId === 'string') { form.value.peopleId = (form.value.peopleId as string).split(',').map((id) => String(id.trim())) as any; } diff --git a/src/views/oa/erp/contractLedgerReport/index.vue b/src/views/oa/erp/contractLedgerReport/index.vue index d3c0472..993a1c5 100644 --- a/src/views/oa/erp/contractLedgerReport/index.vue +++ b/src/views/oa/erp/contractLedgerReport/index.vue @@ -55,6 +55,21 @@ + + + + + + + + + + + + + + + @@ -139,7 +154,9 @@ import { listContractLedgerReport, ContractLedgerReportQuery, ContractLedgerRepo type ElFormInstance = InstanceType; const { proxy } = getCurrentInstance() as ComponentInternalInstance; -const { business_direction } = toRefs(proxy?.useDict('business_direction')); +const { business_direction, contract_territorial_flag, country_region, currency_type } = toRefs( + proxy?.useDict('business_direction', 'contract_territorial_flag', 'country_region', 'currency_type') +); const reportList = ref([]); const selectedRows = ref([]);