({
- lowerLimit: item?.lowerLimit ?? '',
- upperLimit: item?.upperLimit ?? '',
- unitPrice:
- item?.unitPrice === undefined || item?.unitPrice === ''
- ? row.unitPrice ?? ''
- : item.unitPrice,
- minimumBillingWeight:
- item?.minimumBillingWeight === undefined || item?.minimumBillingWeight === ''
- ? this.usesRangeMinimum(row)
- ? row.minimumBillingWeight ?? ''
- : ''
- : item.minimumBillingWeight,
- }))
- .filter(
- item =>
- item.lowerLimit !== '' ||
- item.upperLimit !== '' ||
- item.unitPrice !== '' ||
- item.minimumBillingWeight !== ''
- )
- : [];
+ const rawRanges = Array.isArray(row.limitRanges) ? row.limitRanges : [];
+ const ranges = rawRanges
+ .map((item, index) => ({
+ lowerLimit: item?.lowerLimit ?? '',
+ upperLimit: item?.upperLimit ?? '',
+ unitPrice:
+ item?.unitPrice === undefined || item?.unitPrice === ''
+ ? row.unitPrice ?? ''
+ : item.unitPrice,
+ minimumBillingWeight: this.resolveRangeMinimum(row, item, index),
+ }))
+ .filter(
+ item =>
+ item.lowerLimit !== '' ||
+ item.upperLimit !== '' ||
+ item.unitPrice !== '' ||
+ item.minimumBillingWeight !== ''
+ );
if (
!ranges.length &&
(row.lowerLimit !== '' ||
@@ -598,9 +580,23 @@ export default {
minimumBillingWeight: this.usesRangeMinimum(row) ? row.minimumBillingWeight ?? '' : '',
});
return ranges.length
- ? ranges
+ ? ranges.map((item, index) => ({
+ ...item,
+ minimumBillingWeight: this.usesRangeMinimum(row)
+ ? index === 0
+ ? item.minimumBillingWeight
+ : ''
+ : '',
+ }))
: [{ lowerLimit: '', upperLimit: '', unitPrice: '', minimumBillingWeight: '' }];
},
+ resolveRangeMinimum(row, item, index) {
+ if (!this.usesRangeMinimum(row) || index !== 0) return '';
+ if (item?.minimumBillingWeight !== undefined && item?.minimumBillingWeight !== '') {
+ return item.minimumBillingWeight;
+ }
+ return row.minimumBillingWeight ?? '';
+ },
syncLegacyLimit(row) {
const first = row.limitRanges?.[0] || {};
row.lowerLimit = first.lowerLimit ?? '';
@@ -803,11 +799,16 @@ export default {
if (index === 0) row.unitPrice = target.value;
},
rangeMinimumInput(row, index, value) {
+ if (index !== 0) return;
row.limitRanges = this.getRanges(row);
const target = { value };
this.decimalInput(target, 'value', value);
- row.limitRanges[index].minimumBillingWeight = target.value;
- if (index === 0) row.minimumBillingWeight = target.value;
+ row.limitRanges[0].minimumBillingWeight = target.value;
+ row.minimumBillingWeight = target.value;
+ row.limitRanges = row.limitRanges.map((item, rangeIndex) => ({
+ ...item,
+ minimumBillingWeight: rangeIndex === 0 ? target.value : '',
+ }));
},
addRule() {
this.draft.rules.push(defaultRule());
@@ -929,15 +930,22 @@ export default {
plan.transportModeLabel = transportMode?.label || plan.transportMode;
plan.rules = plan.rules.map(rule => {
const ranges = this.canEditLimit(rule) ? this.getRanges(rule) : [];
+ const normalizedRanges = ranges.map((item, rangeIndex) => ({
+ ...item,
+ minimumBillingWeight:
+ this.usesRangeMinimum(rule) && rangeIndex === 0 ? item.minimumBillingWeight || '' : '',
+ }));
return {
...rule,
- unitPrice: this.usesRangeUnitPrice(rule) ? ranges[0]?.unitPrice || '' : rule.unitPrice,
- limitRanges: ranges,
- lowerLimit: ranges[0]?.lowerLimit || '',
- upperLimit: ranges[0]?.upperLimit || '',
+ unitPrice: this.usesRangeUnitPrice(rule)
+ ? normalizedRanges[0]?.unitPrice || ''
+ : rule.unitPrice,
+ limitRanges: normalizedRanges,
+ lowerLimit: normalizedRanges[0]?.lowerLimit || '',
+ upperLimit: normalizedRanges[0]?.upperLimit || '',
minimumBillingWeight: this.canEditMinimum(rule)
? this.usesRangeMinimum(rule)
- ? ranges[0]?.minimumBillingWeight || ''
+ ? normalizedRanges[0]?.minimumBillingWeight || ''
: rule.minimumBillingWeight
: '',
};
@@ -950,9 +958,6 @@ export default {
this.matchIndex = index;
this.matchForm = { ...defaultRule().matchCondition, ...(row.matchCondition || {}) };
this.matchVisible = true;
- this.ensureTransportModes().then(() => {
- this.matchForm.transportMode = this.normalizeTransportMode(this.matchForm.transportMode);
- });
this.ensureRegions();
this.ensureCargo().then(() => {
const path = this.resolveCargoPath(this.matchForm);
@@ -977,21 +982,6 @@ export default {
});
return this.transportModeRequest;
},
- normalizeTransportMode(value) {
- const transportMode = String(value || '').trim();
- if (!transportMode) return '';
- const option = this.transportModeOptions.find(item => {
- const optionValue = String(item.value || '').trim();
- const optionLabel = String(item.label || '').trim();
- return (
- optionValue === transportMode ||
- optionLabel === transportMode ||
- optionLabel.includes(transportMode) ||
- transportMode.includes(optionLabel)
- );
- });
- return option?.value || transportMode;
- },
ensureRegions() {
if (this.regionOptions.length) return Promise.resolve(this.regionOptions);
if (this.regionRequest) return this.regionRequest;
@@ -1214,6 +1204,11 @@ export default {
cursor: pointer;
}
+.billing-plan-editor__column-title {
+ display: inline-flex;
+ align-items: center;
+}
+
.limit-list {
display: flex;
flex-direction: column;
@@ -1229,4 +1224,8 @@ export default {
.limit-row .el-input {
flex: 1;
}
+
+.limit-placeholder {
+ height: 32px;
+}
diff --git a/src/views/business/components/business-crud-page.vue b/src/views/business/components/business-crud-page.vue
index 419eb99..0e4afc7 100644
--- a/src/views/business/components/business-crud-page.vue
+++ b/src/views/business/components/business-crud-page.vue
@@ -1927,13 +1927,13 @@
@@ -1986,6 +1986,59 @@
+
+
+
+
+
+ handleCustomPeriodStartDayChange($index, value)"
+ >
+
+
+
+
+
+
+ handleCustomPeriodEndDayChange($index, value)"
+ >
+
+
+
+
+
+
+ 添加
+ 删除
+
+
+
+ 添加
+
+
+
@@ -5412,13 +5465,19 @@ const defaultBillingPlan = () => ({
});
const defaultSettlementRule = () => ({
- autoGenerate: 1,
+ autoGenerate: '',
billStartDate: '',
- settlementType: '月结',
- billCycleType: '固定截单日',
- billCutoffDay: 25,
+ settlementType: '',
+ billCycleType: '',
+ billCutoffDay: '',
cycleDays: '',
+ customPeriods: [],
});
+const normalizeCustomPeriods = (periods = []) =>
+ (periods || []).map(item => ({
+ startDay: Number(item?.startDay) > 0 ? Number(item.startDay) : '',
+ endDay: Number(item?.endDay) > 0 ? Number(item.endDay) : '',
+ }));
const defaultReconciliation = () => ({
skipReconciliation: 1,
@@ -5837,7 +5896,7 @@ export default {
按数量: ['固定单价', '区间单价', '阶梯单价', '区间阶梯一口价'],
},
settlementTypeOptions: ['月结', '日结', '周结', '半月结', '固定天数周期结算'],
- billCycleTypeOptions: ['固定截单日', '自然月'],
+ billCycleTypeOptions: ['固定截单日', '自然月', '自定义多周期'],
reconciliationModeOptions: ['明细逐行核对', '按账单汇总核对', '跳过自动核对'],
packageOptions,
quantityUnitOptions: ['吨', '千克', '立方米', '件', '车', '箱', '托盘'],
@@ -6448,6 +6507,11 @@ export default {
this.showSettlementBillCycleType && this.settlementRuleForm.billCycleType === '固定截单日'
);
},
+ showSettlementCustomPeriods() {
+ return (
+ this.showSettlementBillCycleType && this.settlementRuleForm.billCycleType === '自定义多周期'
+ );
+ },
showSettlementCycleDays() {
return this.settlementRuleForm.settlementType === '固定天数周期结算';
},
@@ -7025,7 +7089,7 @@ export default {
if (!row) return '-';
if (prop === 'businessStatus') return this.displayStatus(row, prop);
if (prop === 'feeGenerationMode') {
- return row[prop] === 'manual' ? '手动生成' : '系统生成';
+ return row[prop] === 'manual' ? '账单导入生成' : '系统生成';
}
const column = this.findColumn(this.option.column, prop);
if (column?.formatter) return column.formatter(row, {}, row[prop]);
@@ -7566,23 +7630,32 @@ export default {
isFixedBillCutoffSettlement(rule = this.settlementRuleForm) {
return this.isMonthlySettlement(rule) && rule.billCycleType === '固定截单日';
},
+ isCustomMultiPeriodSettlement(rule = this.settlementRuleForm) {
+ return this.isMonthlySettlement(rule) && rule.billCycleType === '自定义多周期';
+ },
isFixedCycleSettlement(rule = this.settlementRuleForm) {
return rule.settlementType === '固定天数周期结算';
},
handleSettlementTypeChange(value) {
this.clearSettlementRuleError('settlementType');
if (value === '月结') {
- if (!this.settlementRuleForm.billCycleType) {
- this.settlementRuleForm.billCycleType = '固定截单日';
- }
if (this.settlementRuleForm.billCycleType === '固定截单日') {
- this.settlementRuleForm.billCutoffDay = this.settlementRuleForm.billCutoffDay || 25;
+ this.settlementRuleForm.customPeriods = [];
+ } else if (this.settlementRuleForm.billCycleType === '自定义多周期') {
+ this.settlementRuleForm.billCutoffDay = '';
+ this.settlementRuleForm.customPeriods = normalizeCustomPeriods(
+ this.settlementRuleForm.customPeriods || []
+ );
+ } else {
+ this.settlementRuleForm.billCutoffDay = '';
+ this.settlementRuleForm.customPeriods = [];
}
this.settlementRuleForm.cycleDays = '';
return;
}
this.settlementRuleForm.billCycleType = '';
this.settlementRuleForm.billCutoffDay = '';
+ this.settlementRuleForm.customPeriods = [];
if (value !== '固定天数周期结算') {
this.settlementRuleForm.cycleDays = '';
}
@@ -7590,10 +7663,17 @@ export default {
handleSettlementBillCycleTypeChange(value) {
this.clearSettlementRuleError('billCycleType');
if (value === '固定截单日') {
- this.settlementRuleForm.billCutoffDay = this.settlementRuleForm.billCutoffDay || 25;
+ this.settlementRuleForm.customPeriods = [];
return;
}
this.settlementRuleForm.billCutoffDay = '';
+ if (value === '自定义多周期') {
+ this.settlementRuleForm.customPeriods = normalizeCustomPeriods(
+ this.settlementRuleForm.customPeriods || []
+ );
+ return;
+ }
+ this.settlementRuleForm.customPeriods = [];
},
normalizeSettlementRule(rule = this.settlementRuleForm) {
const nextRule = {
@@ -7603,14 +7683,94 @@ export default {
if (!this.isMonthlySettlement(nextRule)) {
nextRule.billCycleType = '';
nextRule.billCutoffDay = '';
+ nextRule.customPeriods = [];
} else if (!this.isFixedBillCutoffSettlement(nextRule)) {
nextRule.billCutoffDay = '';
}
+ if (this.isCustomMultiPeriodSettlement(nextRule)) {
+ nextRule.customPeriods = normalizeCustomPeriods(
+ Array.isArray(nextRule.customPeriods) ? nextRule.customPeriods : []
+ );
+ } else {
+ nextRule.customPeriods = [];
+ }
if (!this.isFixedCycleSettlement(nextRule)) {
nextRule.cycleDays = '';
}
return nextRule;
},
+ customPeriodEndDayOptions(row) {
+ const startDay = Number(row?.startDay);
+ if (!Number.isFinite(startDay) || startDay < 1) return this.billCutoffDayOptions;
+ return Array.from({ length: 31 - startDay + 1 }, (_, index) => {
+ const value = startDay + index;
+ return { label: `${value}日`, value };
+ });
+ },
+ handleCustomPeriodStartDayChange(index, value) {
+ const periods = normalizeCustomPeriods(this.settlementRuleForm.customPeriods || []);
+ const row = periods[index];
+ if (!row) return;
+ row.startDay = value || '';
+ const startDay = Number(row.startDay);
+ const endDay = Number(row.endDay);
+ if (
+ Number.isFinite(startDay) &&
+ Number.isFinite(endDay) &&
+ (endDay < startDay || endDay > 31)
+ ) {
+ row.endDay = '';
+ }
+ this.settlementRuleForm.customPeriods = periods;
+ },
+ handleCustomPeriodEndDayChange(index, value) {
+ const periods = normalizeCustomPeriods(this.settlementRuleForm.customPeriods || []);
+ const row = periods[index];
+ if (!row) return;
+ row.endDay = value || '';
+ this.settlementRuleForm.customPeriods = periods;
+ },
+ addCustomPeriodRow() {
+ const periods = normalizeCustomPeriods(this.settlementRuleForm.customPeriods || []);
+ periods.push({ startDay: '', endDay: '' });
+ this.settlementRuleForm.customPeriods = periods;
+ },
+ removeCustomPeriodRow(index) {
+ if (index <= 0) return;
+ const periods = [...(this.settlementRuleForm.customPeriods || [])];
+ periods.splice(index, 1);
+ this.settlementRuleForm.customPeriods = normalizeCustomPeriods(periods);
+ },
+ validateCustomPeriods(periods = [], label = '') {
+ const rows = normalizeCustomPeriods(periods);
+ const prefix = label ? `${label}:` : '';
+ if (!rows.length) {
+ this.$message.warning(`${prefix}请至少配置一段自定义周期`);
+ return false;
+ }
+ for (let index = 0; index < rows.length; index += 1) {
+ const row = rows[index];
+ const startDay = Number(row.startDay);
+ const endDay = Number(row.endDay);
+ if (!Number.isFinite(startDay) || startDay < 1 || startDay > 31) {
+ this.$message.warning(`${prefix}请选择第${index + 1}行运单区间开始日`);
+ return false;
+ }
+ if (!Number.isFinite(endDay) || endDay < 1 || endDay > 31) {
+ this.$message.warning(`${prefix}请选择第${index + 1}行运单区间结束日`);
+ return false;
+ }
+ if (endDay < startDay) {
+ this.$message.warning(`${prefix}第${index + 1}行结束日不能早于开始日`);
+ return false;
+ }
+ if (index > 0 && startDay !== Number(rows[index - 1].endDay) + 1) {
+ this.$message.warning(`${prefix}自定义多周期区间必须连续,不允许重叠或存在日期缺口`);
+ return false;
+ }
+ }
+ return true;
+ },
clearSettlementRuleError(field) {
if (!this.settlementRuleErrors[field]) return;
this.settlementRuleErrors = {
@@ -7632,8 +7792,8 @@ export default {
return false;
}
if (this.isMonthlySettlement(rule) && this.isBillingFieldEmpty(rule.billCycleType)) {
- this.settlementRuleErrors = { billCycleType: '请选择账单周期类型' };
- this.$message.warning(`${label}${label ? ':' : ''}请选择账单周期类型`);
+ this.settlementRuleErrors = { billCycleType: '请选择结算周期' };
+ this.$message.warning(`${label}${label ? ':' : ''}请选择结算周期`);
return false;
}
if (this.isFixedBillCutoffSettlement(rule)) {
@@ -7649,6 +7809,9 @@ export default {
return false;
}
}
+ if (this.isCustomMultiPeriodSettlement(rule)) {
+ return this.validateCustomPeriods(rule.customPeriods, label);
+ }
if (this.isFixedCycleSettlement(rule)) {
const cycleDays = Number(rule.cycleDays);
if (!Number.isInteger(cycleDays) || cycleDays < 1 || cycleDays > 365) {
@@ -11239,8 +11402,10 @@ export default {
if (!this.canEditBillingLimit(row)) {
continue;
}
- const key = row.billingElement || '';
- groups[key] = groups[key] || [];
+ const feeItem = String(row.feeItem || '').trim();
+ const billingElement = String(row.billingElement || '').trim();
+ const key = JSON.stringify([feeItem, billingElement]);
+ groups[key] = groups[key] || { feeItem, billingElement, ranges: [] };
const ranges = this.getBillingLimitRanges(row);
for (const range of ranges) {
const lower = Number(range.lowerLimit);
@@ -11258,14 +11423,14 @@ export default {
this.$message.warning('计费要素下限不能大于上限');
return false;
}
- groups[key].push({ lower, upper });
+ groups[key].ranges.push({ lower, upper });
}
}
return this.validateBillingLimitRangeGroups(groups);
},
validateBillingLimitRangeGroups(groups) {
const precision = 0.000001;
- for (const [billingElement, ranges] of Object.entries(groups)) {
+ for (const { feeItem, billingElement, ranges } of Object.values(groups)) {
if (ranges.length <= 1) continue;
const sortedRanges = [...ranges].sort((prev, next) => {
if (prev.lower !== next.lower) return prev.lower - next.lower;
@@ -11275,11 +11440,13 @@ export default {
const prevRange = sortedRanges[index - 1];
const currentRange = sortedRanges[index];
if (currentRange.lower < prevRange.upper - precision) {
- this.$message.warning(`${billingElement}的计费要素区间不能重叠`);
+ this.$message.warning(`费用项“${feeItem}”${billingElement}的计费要素区间不能重叠`);
return false;
}
if (currentRange.lower > prevRange.upper + precision) {
- this.$message.warning(`${billingElement}的计费要素区间必须连续,不能存在间隙`);
+ this.$message.warning(
+ `费用项“${feeItem}”${billingElement}的计费要素区间必须连续,不能存在间隙`
+ );
return false;
}
}
diff --git a/src/views/business/components/contract-attachment-section.vue b/src/views/business/components/contract-attachment-section.vue
new file mode 100644
index 0000000..fdf4225
--- /dev/null
+++ b/src/views/business/components/contract-attachment-section.vue
@@ -0,0 +1,493 @@
+
+
+
+
+
+
+
diff --git a/src/views/business/components/waybill-manage-page.vue b/src/views/business/components/waybill-manage-page.vue
index 5a6d7ab..2dc76b5 100644
--- a/src/views/business/components/waybill-manage-page.vue
+++ b/src/views/business/components/waybill-manage-page.vue
@@ -4287,7 +4287,7 @@ export default {
if (!row) return '-';
if (prop === 'businessStatus') return this.displayStatus(row, prop);
if (prop === 'feeGenerationMode') {
- return row[prop] === 'manual' ? '手动生成' : '系统生成';
+ return row[prop] === 'manual' ? '账单导入生成' : '系统生成';
}
const column = this.findColumn(this.option.column, prop);
if (column?.formatter) return column.formatter(row, {}, row[prop]);
diff --git a/src/views/business/contract-manage-change.vue b/src/views/business/contract-manage-change.vue
index c654141..71071ff 100644
--- a/src/views/business/contract-manage-change.vue
+++ b/src/views/business/contract-manage-change.vue
@@ -25,6 +25,10 @@
positiveIntegerInput('copyCount', value)" />
positiveIntegerInput('paymentDays', value)" />
+
+
+
+
@@ -42,7 +46,7 @@
- 系统生成手动生成
+ 系统生成账单导入生成
{{ row.defaultPlan ? '是' : '否' }}编辑删除
@@ -65,10 +69,17 @@
-
+
+
+
+ handleCustomPeriodStartDayChange($index, value)">
+ handleCustomPeriodEndDayChange($index, value)">
+ 添加删除
+ 添加
+
@@ -144,15 +155,27 @@ const normalizeOptionalPositiveInteger = value => {
const number = Number(value);
return Number.isInteger(number) && number > 0 ? number : null;
};
+const normalizeOptionalAmount = value => {
+ if (value === undefined || value === null || value === '') return null;
+ const number = Number(value);
+ return Number.isFinite(number) && number >= 0 ? Number(number.toFixed(2)) : null;
+};
+
+
+const normalizeCustomPeriods = (periods = []) =>
+ (periods || []).map(item => ({
+ startDay: Number(item?.startDay) > 0 ? Number(item.startDay) : '',
+ endDay: Number(item?.endDay) > 0 ? Number(item.endDay) : '',
+ }));
export default {
components: { BillingPlanEditor, ElImageViewer, OpenFileViewer },
- data() { return { form: {}, period: [], plans: [], attachments: [], contractFiles: [], contractFileRows: [], selectedContractFiles: [], paymentRatioRows: [], settlementConfigTab: 'pre', changeMaterials: [], imagePreviewVisible: false, imagePreviewUrls: [], imagePreviewIndex: 0, documentPreviewVisible: false, previewFile: {}, viewerPlugins, viewerToolbar: { zoom: true, rotate: true, download: true, fullscreen: true }, feeGenerationMode: 'system', settlementRule: { autoGenerate: 1, settlementType: '月结', billCycleType: '固定截单日', billCutoffDay: 25, cycleDays: '' }, preSettlementConfig: {}, formalSettlementConfig: {}, settlementTypeOptions: ['月结','日结','周结','半月结','固定天数周期结算'], billCycleTypeOptions: ['固定截单日','自然月'], billCutoffDayOptions: Array.from({ length: 31 }, (_, index) => ({ label: `${index + 1}日`, value: index + 1 })), cycleDayOptions: [7,15,30,60].map(value => ({ label: `${value}天`, value })), planDialogVisible: false, planEditor: { planName: '', defaultPlan: false, remark: '', rules: [] }, planEditorIndex: -1, billingElements: ['按重量','按体积','按车辆','按里程','按吨·公里','固定金额(整单一口价)','按数量'], attachmentFileTypes: ['pdf','bmp','jpeg','png','jpg','doc','docx','ppt','pptx','xlsx','xls','eml','msg','zip'], rules: { contractName: [{ required: true, message: '请输入合同名称', trigger: 'blur' }], changeReason: [{ required: true, message: '请输入变更原因', trigger: 'blur' }] } }; },
- computed: { showSettlementBillCycleType() { return this.settlementRule.settlementType === '月结'; }, showSettlementBillCutoffDay() { return this.showSettlementBillCycleType && this.settlementRule.billCycleType === '固定截单日'; }, showSettlementCycleDays() { return this.settlementRule.settlementType === '固定天数周期结算'; } },
+ data() { return { form: {}, period: [], plans: [], attachments: [], contractFiles: [], contractFileRows: [], selectedContractFiles: [], paymentRatioRows: [], settlementConfigTab: 'pre', changeMaterials: [], imagePreviewVisible: false, imagePreviewUrls: [], imagePreviewIndex: 0, documentPreviewVisible: false, previewFile: {}, viewerPlugins, viewerToolbar: { zoom: true, rotate: true, download: true, fullscreen: true }, feeGenerationMode: 'system', settlementRule: { autoGenerate: '', billStartDate: '', settlementType: '', billCycleType: '', billCutoffDay: '', cycleDays: '', customPeriods: [] }, preSettlementConfig: {}, formalSettlementConfig: {}, settlementTypeOptions: ['月结','日结','周结','半月结','固定天数周期结算'], billCycleTypeOptions: ['固定截单日','自然月','自定义多周期'], billCutoffDayOptions: Array.from({ length: 31 }, (_, index) => ({ label: `${index + 1}日`, value: index + 1 })), cycleDayOptions: [7,15,30,60].map(value => ({ label: `${value}天`, value })), planDialogVisible: false, planEditor: { planName: '', defaultPlan: false, remark: '', rules: [] }, planEditorIndex: -1, billingElements: ['按重量','按体积','按车辆','按里程','按吨·公里','固定金额(整单一口价)','按数量'], attachmentFileTypes: ['pdf','bmp','jpeg','png','jpg','doc','docx','ppt','pptx','xlsx','xls','eml','msg','zip'], rules: { contractName: [{ required: true, message: '请输入合同名称', trigger: 'blur' }], changeReason: [{ required: true, message: '请输入变更原因', trigger: 'blur' }] } }; },
+ computed: { showSettlementBillCycleType() { return this.settlementRule.settlementType === '月结'; }, showSettlementBillCutoffDay() { return this.showSettlementBillCycleType && this.settlementRule.billCycleType === '固定截单日'; }, showSettlementCustomPeriods() { return this.showSettlementBillCycleType && this.settlementRule.billCycleType === '自定义多周期'; }, showSettlementCycleDays() { return this.settlementRule.settlementType === '固定天数周期结算'; } },
mounted() { this.load(); },
watch: { settlementConfigTab(tab, oldTab) { if (tab === oldTab) return; if (oldTab === 'pre') this.preSettlementConfig = { ...this.settlementRule }; else this.formalSettlementConfig = { ...this.settlementRule }; this.settlementRule = { ...(tab === 'pre' ? this.preSettlementConfig : this.formalSettlementConfig) }; } },
methods: {
- async load() { const id = this.$route.query.id; if (!id) return; const res = await api.getDetail(id); const data = res.data?.data || res.data || {}; this.form = { ...data, changeContent: '', changeReason: '', changeAttachmentsJson: '', copyCount: normalizeOptionalPositiveInteger(data.copyCount), paymentDays: normalizeOptionalPositiveInteger(data.paymentDays), changeType: '合同信息变更' }; this.changeMaterials = []; this.period = data.startDate && data.endDate ? [data.startDate, data.endDate] : []; this.plans = this.parse(data.billingPlanJson); this.attachments = this.mergeChangeAttachments(this.parse(data.attachmentsJson), this.parse(data.changeAttachmentsJson)); this.contractFileRows = this.parse(data.contractFileJson); const rules = this.parseObject(data.settlementRuleJson); const pre = this.parseObject(data.preSettlementConfigJson); const formal = this.parseObject(data.formalSettlementConfigJson); const legacy = Object.keys(rules).some(key => !['preSettlementConfig', 'formalSettlementConfig'].includes(key)) ? rules : {}; this.preSettlementConfig = rules.preSettlementConfig || (Object.keys(pre).length ? pre : legacy); this.formalSettlementConfig = rules.formalSettlementConfig || (Object.keys(formal).length ? formal : legacy); this.settlementRule = { ...this.preSettlementConfig }; this.feeGenerationMode = data.feeGenerationMode || (Number(data.billingEnabled) === 0 ? 'manual' : 'system'); this.paymentRatioRows = this.parse(data.paymentRatioJson); },
+ async load() { const id = this.$route.query.id; if (!id) return; const res = await api.getDetail(id); const data = res.data?.data || res.data || {}; this.form = { ...data, changeContent: '', changeReason: '', changeAttachmentsJson: '', copyCount: normalizeOptionalPositiveInteger(data.copyCount), paymentDays: normalizeOptionalPositiveInteger(data.paymentDays), contractAmount: normalizeOptionalAmount(data.contractAmount), templateFlag: data.templateFlag ?? 0, electronicSealFlag: data.electronicSealFlag ?? 0, changeType: '合同信息变更' }; this.changeMaterials = []; this.period = data.startDate && data.endDate ? [data.startDate, data.endDate] : []; this.plans = this.parse(data.billingPlanJson); this.attachments = this.mergeChangeAttachments(this.parse(data.attachmentsJson), this.parse(data.changeAttachmentsJson)); this.contractFileRows = this.parse(data.contractFileJson); const rules = this.parseObject(data.settlementRuleJson); const pre = this.parseObject(data.preSettlementConfigJson); const formal = this.parseObject(data.formalSettlementConfigJson); const legacy = Object.keys(rules).some(key => !['preSettlementConfig', 'formalSettlementConfig'].includes(key)) ? rules : {}; const normalizeRule = rule => { const next = { autoGenerate: '', billStartDate: '', settlementType: '', billCycleType: '', billCutoffDay: '', cycleDays: '', customPeriods: [], ...(rule || {}) }; if (next.settlementType === '月结' && next.billCycleType === '自定义多周期') next.customPeriods = normalizeCustomPeriods(Array.isArray(next.customPeriods) ? next.customPeriods : []); else next.customPeriods = []; return next; }; this.preSettlementConfig = normalizeRule(rules.preSettlementConfig || (Object.keys(pre).length ? pre : legacy)); this.formalSettlementConfig = normalizeRule(rules.formalSettlementConfig || (Object.keys(formal).length ? formal : legacy)); this.settlementRule = { ...this.preSettlementConfig }; this.feeGenerationMode = data.feeGenerationMode || (Number(data.billingEnabled) === 0 ? 'manual' : 'system'); this.paymentRatioRows = this.parse(data.paymentRatioJson); },
parse(value) { try { const result = JSON.parse(value || '[]'); return Array.isArray(result) ? result : []; } catch { return []; } },
// 审核通过后上一次的变更材料归集到「其它附件」,按文件地址/文件名去重,避免重复展示
mergeChangeAttachments(attachments = [], changeAttachments = []) {
@@ -163,14 +186,14 @@ export default {
.map(item => ({ ...item, size: /^\d+$/.test(String(item.size ?? '')) ? this.formatFileSize(item.size) : item.size }));
return [...attachments, ...merged];
},
- parseObject(value) { try { return { autoGenerate: 1, settlementType: '月结', billCycleType: '固定截单日', billCutoffDay: 25, cycleDays: 15, ...(JSON.parse(value || '{}') || {}) }; } catch { return { autoGenerate: 1, settlementType: '月结', billCycleType: '固定截单日', billCutoffDay: 25, cycleDays: 15 }; } },
+ parseObject(value) { try { return { autoGenerate: '', billStartDate: '', settlementType: '', billCycleType: '', billCutoffDay: '', cycleDays: '', customPeriods: [], ...(JSON.parse(value || '{}') || {}) }; } catch { return { autoGenerate: '', billStartDate: '', settlementType: '', billCycleType: '', billCutoffDay: '', cycleDays: '', customPeriods: [] }; } },
positiveIntegerInput(prop, value) { this.form[prop] = String(value ?? '').replace(/\D/g, '').replace(/^0+/, ''); },
addPlan() { this.planEditorIndex = -1; this.planEditor = { planName: `计费方案${this.plans.length + 1}`, defaultPlan: !this.plans.length, remark: '', rules: [{}] }; this.planDialogVisible = true; },
editPlan(row, index) { this.planEditorIndex = index; this.planEditor = JSON.parse(JSON.stringify(row)); this.planDialogVisible = true; },
toggleDefaultPlan(value) { if (value) this.plans.forEach(item => { item.defaultPlan = false; }); },
savePlan(value, index) { if (index < 0) this.plans.push(value); else this.plans.splice(index, 1, value); if (value.defaultPlan) this.plans.forEach((item, current) => { if (current !== (index < 0 ? this.plans.length - 1 : index)) item.defaultPlan = false; }); },
- handleSettlementTypeChange(value) { if (value !== '月结') { this.settlementRule.billCycleType = ''; this.settlementRule.billCutoffDay = ''; } else if (!this.settlementRule.billCycleType) this.settlementRule.billCycleType = '固定截单日'; if (value !== '固定天数周期结算') this.settlementRule.cycleDays = ''; },
- handleCycleTypeChange(value) { if (value === '固定截单日' && !this.settlementRule.billCutoffDay) this.settlementRule.billCutoffDay = 25; if (value !== '固定截单日') this.settlementRule.billCutoffDay = ''; },
+ handleSettlementTypeChange(value) { if (value !== '月结') { this.settlementRule.billCycleType = ''; this.settlementRule.billCutoffDay = ''; this.settlementRule.customPeriods = []; } else if (this.settlementRule.billCycleType === '自定义多周期') this.settlementRule.customPeriods = normalizeCustomPeriods(this.settlementRule.customPeriods || []); else this.settlementRule.customPeriods = []; if (value !== '固定天数周期结算') this.settlementRule.cycleDays = ''; },
+ handleCycleTypeChange(value) { if (value !== '固定截单日') this.settlementRule.billCutoffDay = ''; if (value === '自定义多周期') this.settlementRule.customPeriods = normalizeCustomPeriods(this.settlementRule.customPeriods || []); else this.settlementRule.customPeriods = []; },
currentUploadUserName() {
const userInfo = this.$store.getters.userInfo || {};
return userInfo.realName || userInfo.userName || '';
@@ -209,7 +232,14 @@ export default {
handleContractFileBatchDownload() { (this.selectedContractFiles.length ? this.selectedContractFiles : this.contractFileRows).forEach(row => { if (row.url) window.open(row.url, '_blank'); }); },
formatFileSize(value) { const size = Number(value || 0); return size > 1024 * 1024 ? `${(size / 1024 / 1024).toFixed(2)}MB` : `${Math.max(1, Math.ceil(size / 1024))}KB`; },
addPaymentRatioRow() { this.paymentRatioRows.push({ paymentTerm: `第${this.paymentRatioRows.length + 1}笔`, ratioLimit: '', remark: '' }); },
- async submit() { await this.$refs.formRef.validate(); const total = this.paymentRatioRows.reduce((sum, row) => sum + Number(row.ratioLimit || 0), 0); if (this.paymentRatioRows.length && Math.abs(total - 100) > 0.0001) { this.$message.warning('付款比例上限合计必须等于100%'); return; } const settlementRule = { preSettlementConfig: this.preSettlementConfig, formalSettlementConfig: this.formalSettlementConfig }; await api.submitChange({ ...this.form, settlementCurrency: String(this.form.settlementCurrency || '').trim() || 'RMB', copyCount: normalizeOptionalPositiveInteger(this.form.copyCount), paymentDays: normalizeOptionalPositiveInteger(this.form.paymentDays), startDate: this.period[0], endDate: this.period[1], feeGenerationMode: this.feeGenerationMode, billingEnabled: this.feeGenerationMode === 'system' ? 1 : 0, billingPlanJson: JSON.stringify(this.plans), settlementRuleJson: JSON.stringify(settlementRule), preSettlementConfigJson: JSON.stringify(this.preSettlementConfig), formalSettlementConfigJson: JSON.stringify(this.formalSettlementConfig), paymentRatioJson: JSON.stringify(this.paymentRatioRows), contractFileJson: JSON.stringify(this.contractFileRows), attachmentsJson: JSON.stringify(this.attachments), changeContent: this.form.changeContent, changeReason: this.form.changeReason, changeAttachmentsJson: JSON.stringify(this.changeMaterials) }); this.$message.success('变更已提交'); this.$router.back(); },
+ customPeriodEndDayOptions(row) { const startDay = Number(row?.startDay); if (!Number.isFinite(startDay) || startDay < 1) return this.billCutoffDayOptions; return Array.from({ length: 31 - startDay + 1 }, (_, index) => ({ label: `${startDay + index}日`, value: startDay + index })); },
+ handleCustomPeriodStartDayChange(index, value) { const periods = normalizeCustomPeriods(this.settlementRule.customPeriods || []); const row = periods[index]; if (!row) return; row.startDay = value || ''; const startDay = Number(row.startDay); const endDay = Number(row.endDay); if (Number.isFinite(startDay) && Number.isFinite(endDay) && (endDay < startDay || endDay > 31)) row.endDay = ''; this.settlementRule.customPeriods = periods; },
+ handleCustomPeriodEndDayChange(index, value) { const periods = normalizeCustomPeriods(this.settlementRule.customPeriods || []); const row = periods[index]; if (!row) return; row.endDay = value || ''; this.settlementRule.customPeriods = periods; },
+ addCustomPeriodRow() { const periods = normalizeCustomPeriods(this.settlementRule.customPeriods || []); periods.push({ startDay: '', endDay: '' }); this.settlementRule.customPeriods = periods; },
+ removeCustomPeriodRow(index) { if (index <= 0) return; const periods = [...(this.settlementRule.customPeriods || [])]; periods.splice(index, 1); this.settlementRule.customPeriods = normalizeCustomPeriods(periods); },
+ validateCustomPeriods(periods = [], label) { const rows = normalizeCustomPeriods(periods); if (!rows.length) { this.$message.warning(`${label}:请至少配置一段自定义周期`); return false; } for (let index = 0; index < rows.length; index += 1) { const row = rows[index]; const startDay = Number(row.startDay); const endDay = Number(row.endDay); if (!Number.isFinite(startDay) || startDay < 1 || startDay > 31) { this.$message.warning(`${label}:请选择第${index + 1}行运单区间开始日`); return false; } if (!Number.isFinite(endDay) || endDay < 1 || endDay > 31) { this.$message.warning(`${label}:请选择第${index + 1}行运单区间结束日`); return false; } if (endDay < startDay) { this.$message.warning(`${label}:第${index + 1}行结束日不能早于开始日`); return false; } if (index > 0 && startDay !== Number(rows[index - 1].endDay) + 1) { this.$message.warning(`${label}:自定义多周期区间必须连续,不允许重叠或存在日期缺口`); return false; } } return true; },
+ validateSettlementRule(rule, label) { if (Number(rule.autoGenerate) !== 1) return true; if (!rule.billStartDate || !rule.settlementType) { this.$message.warning(`${label}:请完整填写账单起始日期和结算类型`); return false; } if (rule.settlementType === '月结' && !rule.billCycleType) { this.$message.warning(`${label}:请选择结算周期`); return false; } if (rule.settlementType === '月结' && rule.billCycleType === '固定截单日' && !rule.billCutoffDay) { this.$message.warning(`${label}:请选择账单截单日`); return false; } if (rule.settlementType === '月结' && rule.billCycleType === '自定义多周期') return this.validateCustomPeriods(rule.customPeriods, label); if (rule.settlementType === '固定天数周期结算' && !rule.cycleDays) { this.$message.warning(`${label}:请选择周期天数`); return false; } return true; },
+ async submit() { await this.$refs.formRef.validate(); const total = this.paymentRatioRows.reduce((sum, row) => sum + Number(row.ratioLimit || 0), 0); if (this.paymentRatioRows.length && Math.abs(total - 100) > 0.0001) { this.$message.warning('付款比例上限合计必须等于100%'); return; } if (this.settlementConfigTab === 'pre') this.preSettlementConfig = { ...this.settlementRule }; else this.formalSettlementConfig = { ...this.settlementRule }; if (!this.validateSettlementRule(this.preSettlementConfig, '预结算配置') || !this.validateSettlementRule(this.formalSettlementConfig, '正式结算配置')) return; const settlementRule = { preSettlementConfig: this.preSettlementConfig, formalSettlementConfig: this.formalSettlementConfig }; await api.submitChange({ ...this.form, settlementCurrency: String(this.form.settlementCurrency || '').trim() || 'RMB', copyCount: normalizeOptionalPositiveInteger(this.form.copyCount), paymentDays: normalizeOptionalPositiveInteger(this.form.paymentDays), contractAmount: normalizeOptionalAmount(this.form.contractAmount), startDate: this.period[0], endDate: this.period[1], feeGenerationMode: this.feeGenerationMode, billingEnabled: this.feeGenerationMode === 'system' ? 1 : 0, billingPlanJson: JSON.stringify(this.plans), settlementRuleJson: JSON.stringify(settlementRule), preSettlementConfigJson: JSON.stringify(this.preSettlementConfig), formalSettlementConfigJson: JSON.stringify(this.formalSettlementConfig), paymentRatioJson: JSON.stringify(this.paymentRatioRows), contractFileJson: JSON.stringify(this.contractFileRows), attachmentsJson: JSON.stringify(this.attachments), changeContent: this.form.changeContent, changeReason: this.form.changeReason, changeAttachmentsJson: JSON.stringify(this.changeMaterials) }); this.$message.success('变更已提交'); this.$router.back(); },
},
};
diff --git a/src/views/business/contract-manage.vue b/src/views/business/contract-manage.vue
index c8fc9fd..6d0f968 100644
--- a/src/views/business/contract-manage.vue
+++ b/src/views/business/contract-manage.vue
@@ -64,6 +64,12 @@
+
+
+ {{ row.archiveStatus || '未归档' }}
+
+
+
- 费用生成模式
系统生成
- 手动生成
+
+
+
+ 账单导入生成
添加
@@ -392,10 +434,10 @@
/>
-
+
+
+
+
+
+
+ handleCustomPeriodStartDayChange($index, value)"
+ >
+
+
+
+
+
+
+ handleCustomPeriodEndDayChange($index, value)"
+ >
+
+
+
+
+
+
+ 添加
+ 删除
+
+
+
+ 添加
+
+
+
-