调整预结算、正式结算
This commit is contained in:
@@ -30,5 +30,10 @@ export const getProjectContracts = projectId =>
|
|||||||
request({
|
request({
|
||||||
url: '/blade-transport/contract-manage/list',
|
url: '/blade-transport/contract-manage/list',
|
||||||
method: 'get',
|
method: 'get',
|
||||||
params: { current: 1, size: 9999, projectId },
|
params: {
|
||||||
|
current: 1,
|
||||||
|
size: 9999,
|
||||||
|
projectId,
|
||||||
|
contractCategory: '客户合同',
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -11,8 +11,10 @@ export const getCandidates = (current, size, params) =>
|
|||||||
method: 'get',
|
method: 'get',
|
||||||
params: { current, size, ...params },
|
params: { current, size, ...params },
|
||||||
});
|
});
|
||||||
export const getContractOptions = keyword =>
|
export const getContractOptions = (keyword, projectId) =>
|
||||||
request({ url: `${baseUrl}/contract-options`, method: 'get', params: { keyword } });
|
request({ url: `${baseUrl}/contract-options`, method: 'get', params: { keyword, projectId } });
|
||||||
|
export const getNextNo = () => request({ url: `${baseUrl}/next-no`, method: 'get' });
|
||||||
|
export const getFeeOptions = () => request({ url: `${baseUrl}/fee-options`, method: 'get' });
|
||||||
export const getCandidateDetails = (current, size, params) =>
|
export const getCandidateDetails = (current, size, params) =>
|
||||||
request({
|
request({
|
||||||
url: `${baseUrl}/candidate-details`,
|
url: `${baseUrl}/candidate-details`,
|
||||||
|
|||||||
@@ -34,6 +34,24 @@ export const option = {
|
|||||||
addDisplay: false,
|
addDisplay: false,
|
||||||
editDisplay: false,
|
editDisplay: false,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: '发货省ID',
|
||||||
|
prop: 'departureProvinceId',
|
||||||
|
hide: true,
|
||||||
|
display: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '发货市ID',
|
||||||
|
prop: 'departureCityId',
|
||||||
|
hide: true,
|
||||||
|
display: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '发货区ID',
|
||||||
|
prop: 'departureDistrictId',
|
||||||
|
hide: true,
|
||||||
|
display: false,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: '发货地址',
|
label: '发货地址',
|
||||||
prop: 'departureAddress',
|
prop: 'departureAddress',
|
||||||
@@ -82,6 +100,24 @@ export const option = {
|
|||||||
addDisplay: false,
|
addDisplay: false,
|
||||||
editDisplay: false,
|
editDisplay: false,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: '收货省ID',
|
||||||
|
prop: 'arrivalProvinceId',
|
||||||
|
hide: true,
|
||||||
|
display: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '收货市ID',
|
||||||
|
prop: 'arrivalCityId',
|
||||||
|
hide: true,
|
||||||
|
display: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '收货区ID',
|
||||||
|
prop: 'arrivalDistrictId',
|
||||||
|
hide: true,
|
||||||
|
display: false,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: '收货地址',
|
label: '收货地址',
|
||||||
prop: 'arrivalAddress',
|
prop: 'arrivalAddress',
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
export const formalSettlementFormFields = [
|
export const formalSettlementFormFields = [
|
||||||
{ label: '结算单号', prop: 'formalSettlementNo', readonly: true },
|
{ label: '结算单号', prop: 'formalSettlementNo', readonly: true },
|
||||||
{ label: '项目名称', prop: 'projectName', readonly: true },
|
{ label: '项目名称', prop: 'projectId', type: 'project', required: true },
|
||||||
{ label: '所属组织', prop: 'deptName', readonly: true },
|
{ label: '所属组织', prop: 'deptName', readonly: true },
|
||||||
{ label: '合同编号', prop: 'contractNo', readonly: true },
|
{ label: '合同编号', prop: 'contractNo', readonly: true },
|
||||||
{ label: '合同名称', prop: 'contractId', type: 'contract', required: true },
|
{ label: '合同名称', prop: 'contractId', type: 'contract', required: true },
|
||||||
@@ -18,6 +18,8 @@ export const formalSettlementFormFields = [
|
|||||||
export const createFormalSettlementForm = () => ({
|
export const createFormalSettlementForm = () => ({
|
||||||
id: null,
|
id: null,
|
||||||
formalSettlementNo: '',
|
formalSettlementNo: '',
|
||||||
|
projectId: null,
|
||||||
|
projectName: '',
|
||||||
contractId: null,
|
contractId: null,
|
||||||
contractName: '',
|
contractName: '',
|
||||||
settlementType: '',
|
settlementType: '',
|
||||||
|
|||||||
@@ -52,6 +52,15 @@ export const detailColumns = [
|
|||||||
{ label: '备注', prop: 'remark', minWidth: 180 },
|
{ label: '备注', prop: 'remark', minWidth: 180 },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export const summaryColumns = [
|
||||||
|
{ label: '费用类型', prop: 'feeType', minWidth: 150 },
|
||||||
|
{ label: '费用项', prop: 'feeItem', minWidth: 160 },
|
||||||
|
{ label: '原金额', prop: 'originalAmount', minWidth: 130 },
|
||||||
|
{ label: '调整金额', prop: 'adjustAmount', minWidth: 140 },
|
||||||
|
{ label: '结算金额', prop: 'settlementAmount', minWidth: 140 },
|
||||||
|
{ label: '备注', prop: 'remark', minWidth: 200 },
|
||||||
|
];
|
||||||
|
|
||||||
export const candidateColumns = [
|
export const candidateColumns = [
|
||||||
{ label: '预结算单号', prop: 'preSettlementNo', minWidth: 170 },
|
{ label: '预结算单号', prop: 'preSettlementNo', minWidth: 170 },
|
||||||
{ label: '项目名称', prop: 'projectName', minWidth: 140 },
|
{ label: '项目名称', prop: 'projectName', minWidth: 140 },
|
||||||
@@ -73,6 +82,8 @@ export const candidateDetailColumns = [
|
|||||||
{ label: '合同编号', prop: 'contractNo', minWidth: 150 },
|
{ label: '合同编号', prop: 'contractNo', minWidth: 150 },
|
||||||
{ label: '合同名称', prop: 'contractName', minWidth: 170 },
|
{ label: '合同名称', prop: 'contractName', minWidth: 170 },
|
||||||
{ label: '来源', prop: 'sourceType', minWidth: 110 },
|
{ label: '来源', prop: 'sourceType', minWidth: 110 },
|
||||||
|
{ label: '预结算单号', prop: 'preSettlementNo', minWidth: 160 },
|
||||||
|
{ label: '正式结算单号', prop: 'formalSettlementNo', minWidth: 160 },
|
||||||
{ label: '运单号', prop: 'waybillNo', minWidth: 150 },
|
{ label: '运单号', prop: 'waybillNo', minWidth: 150 },
|
||||||
{ label: '车号', prop: 'vehicleNo', minWidth: 130 },
|
{ label: '车号', prop: 'vehicleNo', minWidth: 130 },
|
||||||
{ label: '运输类型', prop: 'transportType', minWidth: 120 },
|
{ label: '运输类型', prop: 'transportType', minWidth: 120 },
|
||||||
|
|||||||
@@ -303,6 +303,7 @@
|
|||||||
<el-button
|
<el-button
|
||||||
type="primary"
|
type="primary"
|
||||||
:disabled="!routeMapSelected.longitude"
|
:disabled="!routeMapSelected.longitude"
|
||||||
|
:loading="routeMapConfirmLoading"
|
||||||
@click="confirmRouteMapPick"
|
@click="confirmRouteMapPick"
|
||||||
>
|
>
|
||||||
确定
|
确定
|
||||||
@@ -315,6 +316,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import * as api from '@/api/business/common-route';
|
import * as api from '@/api/business/common-route';
|
||||||
import { getList as getCommonAddressList } from '@/api/base/common-address';
|
import { getList as getCommonAddressList } from '@/api/base/common-address';
|
||||||
|
import { getDetail as getRegionDetail } from '@/api/base/region';
|
||||||
import { exportBlob } from '@/api/common';
|
import { exportBlob } from '@/api/common';
|
||||||
import { getDeptTree } from '@/api/system/dept';
|
import { getDeptTree } from '@/api/system/dept';
|
||||||
import { addressTypeOptions } from '@/option/base/common-address';
|
import { addressTypeOptions } from '@/option/base/common-address';
|
||||||
@@ -356,6 +358,7 @@ export default {
|
|||||||
routeMapVisible: false,
|
routeMapVisible: false,
|
||||||
routeMapTarget: '',
|
routeMapTarget: '',
|
||||||
routeMapLoading: false,
|
routeMapLoading: false,
|
||||||
|
routeMapConfirmLoading: false,
|
||||||
routeMapKeyword: '',
|
routeMapKeyword: '',
|
||||||
routeMapStatus: '可搜索地址或点击地图选点',
|
routeMapStatus: '可搜索地址或点击地图选点',
|
||||||
routeMapSelected: {},
|
routeMapSelected: {},
|
||||||
@@ -496,14 +499,22 @@ export default {
|
|||||||
this.addressLoading = false;
|
this.addressLoading = false;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
selectAddressRow(row) {
|
async selectAddressRow(row) {
|
||||||
if (!row) return;
|
if (!row) return;
|
||||||
this.applyAddress(this.addressTarget, row);
|
const applied = await this.applyAddress(this.addressTarget, row);
|
||||||
|
if (!applied) return;
|
||||||
this.addressDialogVisible = false;
|
this.addressDialogVisible = false;
|
||||||
},
|
},
|
||||||
applyAddress(target, address) {
|
async applyAddress(target, address) {
|
||||||
if (!target || !address) return;
|
if (!target || !address) return;
|
||||||
const prefix = target === 'departure' ? 'departure' : 'arrival';
|
const prefix = target === 'departure' ? 'departure' : 'arrival';
|
||||||
|
const regionIds = await this.resolveRegionIds(address.regionCode);
|
||||||
|
if (!this.hasCompleteRegionIds(regionIds)) {
|
||||||
|
this.$message.warning('该常用地址缺少完整的省、市、区ID,请先维护行政区划');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
this.applyRegionIds(prefix, regionIds);
|
||||||
|
this.form[`${prefix}AddressId`] = address.id || '';
|
||||||
this.form[`${prefix}Name`] = address.addressName || '';
|
this.form[`${prefix}Name`] = address.addressName || '';
|
||||||
this.form[`${prefix}Address`] = address.detailAddress || '';
|
this.form[`${prefix}Address`] = address.detailAddress || '';
|
||||||
this.form[`${prefix}AddressCode`] = address.addressCode || '';
|
this.form[`${prefix}AddressCode`] = address.addressCode || '';
|
||||||
@@ -515,6 +526,7 @@ export default {
|
|||||||
this.form[`${prefix}Latitude`] = address.latitude || '';
|
this.form[`${prefix}Latitude`] = address.latitude || '';
|
||||||
this.form[`${prefix}Contact`] = address.contactName || this.form[`${prefix}Contact`] || '';
|
this.form[`${prefix}Contact`] = address.contactName || this.form[`${prefix}Contact`] || '';
|
||||||
this.form[`${prefix}Phone`] = address.contactPhone || this.form[`${prefix}Phone`] || '';
|
this.form[`${prefix}Phone`] = address.contactPhone || this.form[`${prefix}Phone`] || '';
|
||||||
|
return true;
|
||||||
},
|
},
|
||||||
openRouteMapDialog(target) {
|
openRouteMapDialog(target) {
|
||||||
if (this.dialogReadonly) return;
|
if (this.dialogReadonly) return;
|
||||||
@@ -526,6 +538,10 @@ export default {
|
|||||||
lat: this.form[`${prefix}Latitude`],
|
lat: this.form[`${prefix}Latitude`],
|
||||||
address: this.form[`${prefix}Address`],
|
address: this.form[`${prefix}Address`],
|
||||||
regionName: this.form[`${prefix}Name`],
|
regionName: this.form[`${prefix}Name`],
|
||||||
|
regionCode: this.form[`${prefix}DistrictId`],
|
||||||
|
provinceId: this.form[`${prefix}ProvinceId`],
|
||||||
|
cityId: this.form[`${prefix}CityId`],
|
||||||
|
districtId: this.form[`${prefix}DistrictId`],
|
||||||
});
|
});
|
||||||
this.routeMapStatus = this.routeMapSelected.longitude
|
this.routeMapStatus = this.routeMapSelected.longitude
|
||||||
? '已加载当前选点,可重新选点'
|
? '已加载当前选点,可重新选点'
|
||||||
@@ -638,6 +654,7 @@ export default {
|
|||||||
...this.routeMapSelected,
|
...this.routeMapSelected,
|
||||||
detailAddress: address.detailAddress || this.routeMapSelected.detailAddress,
|
detailAddress: address.detailAddress || this.routeMapSelected.detailAddress,
|
||||||
regionName: address.regionName || this.routeMapSelected.regionName,
|
regionName: address.regionName || this.routeMapSelected.regionName,
|
||||||
|
regionCode: address.regionCode || '',
|
||||||
};
|
};
|
||||||
this.routeMapKeyword = this.routeMapSelected.detailAddress || this.routeMapKeyword;
|
this.routeMapKeyword = this.routeMapSelected.detailAddress || this.routeMapKeyword;
|
||||||
this.routeMapStatus = this.routeMapSelected.detailAddress || '已选点,可确认回填';
|
this.routeMapStatus = this.routeMapSelected.detailAddress || '已选点,可确认回填';
|
||||||
@@ -656,12 +673,23 @@ export default {
|
|||||||
this.routeAmapMarker.setMap(this.routeAmap);
|
this.routeAmapMarker.setMap(this.routeAmap);
|
||||||
this.routeAmap.setCenter(point);
|
this.routeAmap.setCenter(point);
|
||||||
},
|
},
|
||||||
confirmRouteMapPick() {
|
async confirmRouteMapPick() {
|
||||||
if (!this.routeMapSelected.longitude) {
|
if (!this.routeMapSelected.longitude) {
|
||||||
this.$message.warning('请先搜索或点击地图完成选点');
|
this.$message.warning('请先搜索或点击地图完成选点');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const prefix = this.routeMapTarget;
|
const prefix = this.routeMapTarget;
|
||||||
|
this.routeMapConfirmLoading = true;
|
||||||
|
try {
|
||||||
|
const regionIds = await this.resolveRegionIds(this.routeMapSelected.regionCode);
|
||||||
|
if (!this.hasCompleteRegionIds(regionIds)) {
|
||||||
|
this.$message.warning('当前选点未解析到完整的省、市、区ID,请重新选择地址');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.applyRegionIds(prefix, regionIds);
|
||||||
|
this.form[`${prefix}AddressId`] = '';
|
||||||
|
this.form[`${prefix}AddressCode`] = '';
|
||||||
|
this.form[`${prefix}SiteCode`] = '';
|
||||||
this.form[`${prefix}Address`] =
|
this.form[`${prefix}Address`] =
|
||||||
this.routeMapSelected.detailAddress || this.form[`${prefix}Address`];
|
this.routeMapSelected.detailAddress || this.form[`${prefix}Address`];
|
||||||
this.form[`${prefix}Name`] = this.routeMapSelected.regionName || this.form[`${prefix}Name`];
|
this.form[`${prefix}Name`] = this.routeMapSelected.regionName || this.form[`${prefix}Name`];
|
||||||
@@ -669,15 +697,66 @@ export default {
|
|||||||
this.form[`${prefix}Latitude`] = this.routeMapSelected.latitude;
|
this.form[`${prefix}Latitude`] = this.routeMapSelected.latitude;
|
||||||
this.routeMapVisible = false;
|
this.routeMapVisible = false;
|
||||||
this.$refs.crud?.validateField?.(`${prefix}Address`);
|
this.$refs.crud?.validateField?.(`${prefix}Address`);
|
||||||
|
} finally {
|
||||||
|
this.routeMapConfirmLoading = false;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
buildRouteMapSelection({ lng, lat, address, regionName }) {
|
buildRouteMapSelection({
|
||||||
|
lng,
|
||||||
|
lat,
|
||||||
|
address,
|
||||||
|
regionName,
|
||||||
|
regionCode,
|
||||||
|
provinceId,
|
||||||
|
cityId,
|
||||||
|
districtId,
|
||||||
|
}) {
|
||||||
return {
|
return {
|
||||||
longitude: this.formatCoordinate(lng),
|
longitude: this.formatCoordinate(lng),
|
||||||
latitude: this.formatCoordinate(lat),
|
latitude: this.formatCoordinate(lat),
|
||||||
detailAddress: address || '',
|
detailAddress: address || '',
|
||||||
regionName: regionName || '',
|
regionName: regionName || '',
|
||||||
|
regionCode: regionCode || '',
|
||||||
|
provinceId: provinceId || '',
|
||||||
|
cityId: cityId || '',
|
||||||
|
districtId: districtId || '',
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
applyRegionIds(prefix, regionIds = {}) {
|
||||||
|
this.form[`${prefix}ProvinceId`] = regionIds.provinceId || '';
|
||||||
|
this.form[`${prefix}CityId`] = regionIds.cityId || '';
|
||||||
|
this.form[`${prefix}DistrictId`] = regionIds.districtId || '';
|
||||||
|
},
|
||||||
|
hasCompleteRegionIds(regionIds = {}) {
|
||||||
|
return Boolean(regionIds.provinceId && regionIds.cityId && regionIds.districtId);
|
||||||
|
},
|
||||||
|
fallbackRegionIds(regionCode) {
|
||||||
|
const code = String(regionCode || '').trim();
|
||||||
|
if (!/^\d{6}$/.test(code)) {
|
||||||
|
return { provinceId: '', cityId: '', districtId: '' };
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
provinceId: `${code.slice(0, 2)}0000`,
|
||||||
|
cityId: `${code.slice(0, 4)}00`,
|
||||||
|
districtId: code,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
async resolveRegionIds(regionCode) {
|
||||||
|
const fallback = this.fallbackRegionIds(regionCode);
|
||||||
|
if (!regionCode) return fallback;
|
||||||
|
try {
|
||||||
|
const res = await getRegionDetail(regionCode);
|
||||||
|
const region = res.data.data || {};
|
||||||
|
return {
|
||||||
|
provinceId: String(region.provinceCode || fallback.provinceId || ''),
|
||||||
|
cityId: String(region.cityCode || fallback.cityId || ''),
|
||||||
|
districtId: String(region.districtCode || region.code || fallback.districtId || ''),
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
window.console.log(error);
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
},
|
||||||
formatCoordinate(value) {
|
formatCoordinate(value) {
|
||||||
if (value === undefined || value === null || value === '') return '';
|
if (value === undefined || value === null || value === '') return '';
|
||||||
const numberValue = Number(value);
|
const numberValue = Number(value);
|
||||||
@@ -754,6 +833,7 @@ export default {
|
|||||||
return {
|
return {
|
||||||
detailAddress: regeocode.formattedAddress || '',
|
detailAddress: regeocode.formattedAddress || '',
|
||||||
regionName: [component.province, city, component.district].filter(Boolean).join(''),
|
regionName: [component.province, city, component.district].filter(Boolean).join(''),
|
||||||
|
regionCode: component.adcode || '',
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
normalizeRow(row) {
|
normalizeRow(row) {
|
||||||
|
|||||||
@@ -5158,6 +5158,7 @@ const standaloneBusinessFormPaths = [
|
|||||||
...Object.keys(standaloneBusinessFormRoutes),
|
...Object.keys(standaloneBusinessFormRoutes),
|
||||||
...Object.values(standaloneBusinessFormRoutes),
|
...Object.values(standaloneBusinessFormRoutes),
|
||||||
];
|
];
|
||||||
|
const loadingCreateWaybillsStorageKey = 'loading-manage-create-waybills';
|
||||||
|
|
||||||
const attachmentViewerPlugins = [
|
const attachmentViewerPlugins = [
|
||||||
imagePlugin(),
|
imagePlugin(),
|
||||||
@@ -12779,27 +12780,40 @@ export default {
|
|||||||
this.$message.warning('请选择至少一条数据');
|
this.$message.warning('请选择至少一条数据');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const nonRoadWaybills = this.selectionList.filter(row => {
|
const nonRoadWaybills = this.selectionList.filter(row => !this.waybillIsRoadTransport(row));
|
||||||
const transportType = String(row.transportType || '').trim();
|
|
||||||
return transportType !== '公路运输' && transportType.toLowerCase() !== 'road';
|
|
||||||
});
|
|
||||||
if (nonRoadWaybills.length) {
|
if (nonRoadWaybills.length) {
|
||||||
this.$message.warning('公路配置仅支持运输方式为“公路运输”的运单');
|
this.$message.warning('公路配载仅支持运输方式为“公路运输”的运单');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (typeof this.api.roadLoading !== 'function') {
|
const unavailableWaybills = this.selectionList.filter(
|
||||||
this.$message.warning('公路配载功能暂未配置');
|
row =>
|
||||||
|
this.statusValue(row) !== 'pending' ||
|
||||||
|
row.loadingId ||
|
||||||
|
row.loadingManageId ||
|
||||||
|
row.loadingNo
|
||||||
|
);
|
||||||
|
if (unavailableWaybills.length) {
|
||||||
|
this.$message.warning('公路配载仅支持进行中且未配载的运单');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.$confirm('确定对选中的运单进行公路配载?', '提示', {
|
try {
|
||||||
confirmButtonText: '确定',
|
sessionStorage.setItem(
|
||||||
cancelButtonText: '取消',
|
loadingCreateWaybillsStorageKey,
|
||||||
type: 'warning',
|
JSON.stringify({
|
||||||
|
source: 'waybill-manage',
|
||||||
|
rows: this.selectionList,
|
||||||
})
|
})
|
||||||
.then(() => this.api.roadLoading(this.ids))
|
);
|
||||||
.then(() => {
|
} catch (error) {
|
||||||
this.$message.success('公路配载成功');
|
this.$message.error('暂存待配载运单失败,请重试');
|
||||||
this.onLoad(this.page, this.query);
|
return;
|
||||||
|
}
|
||||||
|
this.$router.push({
|
||||||
|
path: '/business/loading-manage/form',
|
||||||
|
query: {
|
||||||
|
mode: 'add',
|
||||||
|
name: '新增配载管理',
|
||||||
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -635,8 +635,11 @@ export default {
|
|||||||
editPending(item) {
|
editPending(item) {
|
||||||
const route = this.routes.find(routeItem => routeItem.segmentNo === item.segmentNo);
|
const route = this.routes.find(routeItem => routeItem.segmentNo === item.segmentNo);
|
||||||
if (!route) return;
|
if (!route) return;
|
||||||
const sourceIndex = (this.master.goods || []).findIndex(goods => goods.cargoName === item.cargoName && goods.cargoType === item.cargoType);
|
const itemSourceIndex = Number(item.sourceIndex);
|
||||||
let goods = route.goods.find(goodsItem => goodsItem.cargoName === item.cargoName && goodsItem.cargoType === item.cargoType);
|
const sourceIndex = Number.isInteger(itemSourceIndex) && itemSourceIndex >= 0
|
||||||
|
? itemSourceIndex
|
||||||
|
: (this.master.goods || []).findIndex(goods => goods.cargoName === item.cargoName && goods.cargoType === item.cargoType);
|
||||||
|
let goods = route.goods.find(goodsItem => String(goodsItem.sourceIndex) === String(sourceIndex));
|
||||||
if (!goods && sourceIndex >= 0) {
|
if (!goods && sourceIndex >= 0) {
|
||||||
goods = this.createGoodsRow(this.master.goods[sourceIndex], sourceIndex);
|
goods = this.createGoodsRow(this.master.goods[sourceIndex], sourceIndex);
|
||||||
route.goods.push(goods);
|
route.goods.push(goods);
|
||||||
@@ -646,8 +649,9 @@ export default {
|
|||||||
this.syncFreightItems(route);
|
this.syncFreightItems(route);
|
||||||
const freightItem = this.freightItemsForGoods(route, goods);
|
const freightItem = this.freightItemsForGoods(route, goods);
|
||||||
if (freightItem) { freightItem.unitPrice = item.unitPrice || ''; freightItem.priceUnit = item.priceUnit || freightItem.priceUnit; }
|
if (freightItem) { freightItem.unitPrice = item.unitPrice || ''; freightItem.priceUnit = item.priceUnit || freightItem.priceUnit; }
|
||||||
this.editingId = item.id;
|
|
||||||
this.removePending(item.id);
|
this.removePending(item.id);
|
||||||
|
this.editingId = item.id;
|
||||||
|
route.selected = true;
|
||||||
this.$nextTick(() => document.querySelector(`[data-segment="${item.segmentNo}"]`)?.scrollIntoView({ behavior: 'smooth', block: 'start' }));
|
this.$nextTick(() => document.querySelector(`[data-segment="${item.segmentNo}"]`)?.scrollIntoView({ behavior: 'smooth', block: 'start' }));
|
||||||
},
|
},
|
||||||
freightItemsForGoods(route, goods) {
|
freightItemsForGoods(route, goods) {
|
||||||
|
|||||||
@@ -907,7 +907,8 @@ export default {
|
|||||||
async loadContracts(projectId, selectFirst = true) {
|
async loadContracts(projectId, selectFirst = true) {
|
||||||
const response = await api.getProjectContracts(projectId);
|
const response = await api.getProjectContracts(projectId);
|
||||||
this.contracts = this.responseRecords(response).filter(
|
this.contracts = this.responseRecords(response).filter(
|
||||||
item => String(item.projectId) === String(projectId)
|
item =>
|
||||||
|
String(item.projectId) === String(projectId) && item.contractCategory === '客户合同'
|
||||||
);
|
);
|
||||||
if (selectFirst) {
|
if (selectFirst) {
|
||||||
const first = this.contracts[0];
|
const first = this.contracts[0];
|
||||||
|
|||||||
@@ -1002,6 +1002,7 @@ const defaultTransportType = '';
|
|||||||
const defaultCandidateTransportType = '';
|
const defaultCandidateTransportType = '';
|
||||||
const AMAP_KEY = '653b7cf105ad7fb8ec9b2f5198ade315';
|
const AMAP_KEY = '653b7cf105ad7fb8ec9b2f5198ade315';
|
||||||
const AMAP_SECURITY_CODE = '5aab65c632e48ebae0d0e28f5b28e21a';
|
const AMAP_SECURITY_CODE = '5aab65c632e48ebae0d0e28f5b28e21a';
|
||||||
|
const loadingCreateWaybillsStorageKey = 'loading-manage-create-waybills';
|
||||||
let detailAmapLoader;
|
let detailAmapLoader;
|
||||||
|
|
||||||
const createSearchForm = () => ({
|
const createSearchForm = () => ({
|
||||||
@@ -1111,6 +1112,18 @@ const parseJsonArray = value => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const parseIdJsonArray = value => {
|
||||||
|
if (Array.isArray(value)) return value.map(id => String(id));
|
||||||
|
if (typeof value !== 'string' || !value.trim()) return [];
|
||||||
|
try {
|
||||||
|
const normalizedValue = value.replace(/(^\s*\[\s*|,\s*)(-?\d{16,})(\s*(?=,|\]))/g, '$1"$2"$3');
|
||||||
|
const result = JSON.parse(normalizedValue);
|
||||||
|
return Array.isArray(result) ? result.map(id => String(id)) : [];
|
||||||
|
} catch (error) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const uniqueText = (rows, prop) =>
|
const uniqueText = (rows, prop) =>
|
||||||
[
|
[
|
||||||
...new Set(
|
...new Set(
|
||||||
@@ -1361,7 +1374,7 @@ export default {
|
|||||||
if (linkedRows[index]?.id || linkedRows[index]?.waybillId) {
|
if (linkedRows[index]?.id || linkedRows[index]?.waybillId) {
|
||||||
return linkedRows[index].id || linkedRows[index].waybillId;
|
return linkedRows[index].id || linkedRows[index].waybillId;
|
||||||
}
|
}
|
||||||
const ids = parseJsonArray(row.waybillIdsJson);
|
const ids = parseIdJsonArray(row.waybillIdsJson);
|
||||||
return ids[index] || '';
|
return ids[index] || '';
|
||||||
},
|
},
|
||||||
async openWaybillDetail(row = {}) {
|
async openWaybillDetail(row = {}) {
|
||||||
@@ -1545,11 +1558,25 @@ export default {
|
|||||||
};
|
};
|
||||||
actionMap[type]?.();
|
actionMap[type]?.();
|
||||||
},
|
},
|
||||||
initStandaloneFormPage() {
|
async initStandaloneFormPage() {
|
||||||
if (!this.isStandaloneFormPage) return;
|
if (!this.isStandaloneFormPage) return;
|
||||||
this.openLoadingDialog(this.$route.query.mode === 'edit' ? 'edit' : 'add', {
|
const mode = this.$route.query.mode === 'edit' ? 'edit' : 'add';
|
||||||
|
await this.openLoadingDialog(mode, {
|
||||||
id: this.$route.query.id,
|
id: this.$route.query.id,
|
||||||
});
|
});
|
||||||
|
if (mode === 'add') await this.consumeLoadingCreateWaybills();
|
||||||
|
},
|
||||||
|
async consumeLoadingCreateWaybills() {
|
||||||
|
const raw = sessionStorage.getItem(loadingCreateWaybillsStorageKey);
|
||||||
|
if (!raw) return;
|
||||||
|
sessionStorage.removeItem(loadingCreateWaybillsStorageKey);
|
||||||
|
try {
|
||||||
|
const payload = JSON.parse(raw);
|
||||||
|
if (payload?.source !== 'waybill-manage' || !Array.isArray(payload.rows)) return;
|
||||||
|
await this.addWaybillRows(payload.rows);
|
||||||
|
} catch (error) {
|
||||||
|
ElMessage.warning('待配载运单数据读取失败,请重新选择');
|
||||||
|
}
|
||||||
},
|
},
|
||||||
async openLoadingDialog(mode, row) {
|
async openLoadingDialog(mode, row) {
|
||||||
if (['add', 'edit'].includes(mode) && !this.isStandaloneFormPage) {
|
if (['add', 'edit'].includes(mode) && !this.isStandaloneFormPage) {
|
||||||
@@ -1863,7 +1890,7 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
async restoreWaybillRows(idsJson, loadingSubNos) {
|
async restoreWaybillRows(idsJson, loadingSubNos) {
|
||||||
const ids = parseJsonArray(idsJson).filter(Boolean);
|
const ids = parseIdJsonArray(idsJson).filter(Boolean);
|
||||||
if (ids.length) {
|
if (ids.length) {
|
||||||
const rows = await Promise.all(
|
const rows = await Promise.all(
|
||||||
ids.map(id =>
|
ids.map(id =>
|
||||||
@@ -1927,11 +1954,13 @@ export default {
|
|||||||
candidateSelectionChange(rows) {
|
candidateSelectionChange(rows) {
|
||||||
this.candidateSelection = rows || [];
|
this.candidateSelection = rows || [];
|
||||||
},
|
},
|
||||||
async addSelectedWaybills() {
|
async addWaybillRows(rows = []) {
|
||||||
|
const waybillRows = Array.isArray(rows) ? rows.filter(Boolean) : [];
|
||||||
|
if (!waybillRows.length) return;
|
||||||
this.candidateAdding = true;
|
this.candidateAdding = true;
|
||||||
try {
|
try {
|
||||||
const selectedRows = await Promise.all(
|
const selectedRows = await Promise.all(
|
||||||
this.candidateSelection.map(async row => {
|
waybillRows.map(async row => {
|
||||||
if (!row.id) return row;
|
if (!row.id) return row;
|
||||||
try {
|
try {
|
||||||
const res = await getWaybillDetail(row.id);
|
const res = await getWaybillDetail(row.id);
|
||||||
@@ -1961,6 +1990,9 @@ export default {
|
|||||||
this.candidateAdding = false;
|
this.candidateAdding = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
async addSelectedWaybills() {
|
||||||
|
await this.addWaybillRows(this.candidateSelection);
|
||||||
|
},
|
||||||
removeSelectedWaybill(index) {
|
removeSelectedWaybill(index) {
|
||||||
this.selectedWaybillRows.splice(index, 1);
|
this.selectedWaybillRows.splice(index, 1);
|
||||||
this.rebuildSummaryFromWaybills();
|
this.rebuildSummaryFromWaybills();
|
||||||
@@ -1972,7 +2004,12 @@ export default {
|
|||||||
.map(row => row.waybillNo)
|
.map(row => row.waybillNo)
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
.join(',');
|
.join(',');
|
||||||
this.dialogForm.waybillIdsJson = JSON.stringify(rows.map(row => row.id).filter(Boolean));
|
this.dialogForm.waybillIdsJson = JSON.stringify(
|
||||||
|
rows
|
||||||
|
.map(row => row.id)
|
||||||
|
.filter(Boolean)
|
||||||
|
.map(id => String(id))
|
||||||
|
);
|
||||||
this.dialogForm.projectName = uniqueText(rows, 'projectName');
|
this.dialogForm.projectName = uniqueText(rows, 'projectName');
|
||||||
this.dialogForm.customerName = uniqueText(rows, 'customerName');
|
this.dialogForm.customerName = uniqueText(rows, 'customerName');
|
||||||
this.dialogForm.originalNo = uniqueText(rows, 'originalNo');
|
this.dialogForm.originalNo = uniqueText(rows, 'originalNo');
|
||||||
|
|||||||
@@ -23,12 +23,29 @@
|
|||||||
value-format="YYYY-MM-DD"
|
value-format="YYYY-MM-DD"
|
||||||
placeholder="请选择"
|
placeholder="请选择"
|
||||||
/>
|
/>
|
||||||
|
<el-select
|
||||||
|
v-else-if="field.type === 'project' && editable"
|
||||||
|
v-model="form.projectId"
|
||||||
|
filterable
|
||||||
|
clearable
|
||||||
|
placeholder="请选择项目"
|
||||||
|
:disabled="details.length > 0 || sources.length > 0"
|
||||||
|
@change="handleProjectChange"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in projects"
|
||||||
|
:key="item.id"
|
||||||
|
:label="item.name"
|
||||||
|
:value="item.id"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
<el-select
|
<el-select
|
||||||
v-else-if="field.type === 'contract' && editable"
|
v-else-if="field.type === 'contract' && editable"
|
||||||
v-model="form.contractId"
|
v-model="form.contractId"
|
||||||
filterable
|
filterable
|
||||||
remote
|
remote
|
||||||
:remote-method="loadContracts"
|
:remote-method="loadContracts"
|
||||||
|
:disabled="!form.projectId || details.length > 0 || sources.length > 0"
|
||||||
placeholder="请选择合同"
|
placeholder="请选择合同"
|
||||||
@change="handleContractChange"
|
@change="handleContractChange"
|
||||||
>
|
>
|
||||||
@@ -50,6 +67,9 @@
|
|||||||
<span v-else-if="field.type === 'contract'">{{
|
<span v-else-if="field.type === 'contract'">{{
|
||||||
displayValue(form.contractName)
|
displayValue(form.contractName)
|
||||||
}}</span>
|
}}</span>
|
||||||
|
<span v-else-if="field.type === 'project'">{{
|
||||||
|
displayValue(form.projectName)
|
||||||
|
}}</span>
|
||||||
<span v-else>{{ displayValue(form[field.prop]) }}</span>
|
<span v-else>{{ displayValue(form[field.prop]) }}</span>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
@@ -69,40 +89,121 @@
|
|||||||
</el-form>
|
</el-form>
|
||||||
</section-card>
|
</section-card>
|
||||||
|
|
||||||
<section-card title="来源预结算单">
|
<section-card title="结算合计">
|
||||||
<template #extra>
|
<template #extra>
|
||||||
<div class="formal-editor__actions">
|
<el-button v-if="editable" type="primary" plain @click="addSummaryFee">
|
||||||
<el-button v-if="editable" type="primary" plain @click="openCandidateDialog"
|
添加费用
|
||||||
>选择预结算单</el-button
|
</el-button>
|
||||||
>
|
|
||||||
<el-button v-if="editable" type="primary" plain @click="openDetailDialog"
|
|
||||||
>选择结算明细</el-button
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
<el-table :data="sources" border>
|
<el-table :data="summaryFees" border show-summary :summary-method="getSummarySums">
|
||||||
<el-table-column type="index" label="序号" width="64" align="center" />
|
<el-table-column type="index" label="序号" width="64" align="center" />
|
||||||
<el-table-column
|
<el-table-column
|
||||||
v-for="column in sourceTableColumns"
|
v-for="column in summaryTableColumns"
|
||||||
:key="column.prop"
|
:key="column.prop"
|
||||||
v-bind="column"
|
:label="column.label"
|
||||||
|
:prop="column.prop"
|
||||||
|
:min-width="column.minWidth"
|
||||||
align="center"
|
align="center"
|
||||||
>
|
>
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<span v-if="column.money">{{ formatMoney(row[column.prop]) }}</span>
|
<el-select
|
||||||
|
v-if="editable && column.prop === 'feeType' && row.manualFlag === 1"
|
||||||
|
v-model="row.feeType"
|
||||||
|
filterable
|
||||||
|
placeholder="请选择"
|
||||||
|
@change="handleManualFeeTypeChange(row)"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in feeOptions"
|
||||||
|
:key="item.feeType"
|
||||||
|
:label="item.feeTypeName || item.feeType"
|
||||||
|
:value="item.feeType"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
<el-select
|
||||||
|
v-else-if="editable && column.prop === 'feeItem' && row.manualFlag === 1"
|
||||||
|
v-model="row.feeItem"
|
||||||
|
filterable
|
||||||
|
placeholder="请选择"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="name in manualFeeItems(row.feeType)"
|
||||||
|
:key="name"
|
||||||
|
:label="name"
|
||||||
|
:value="name"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
<el-input-number
|
||||||
|
v-else-if="editable && column.prop === 'adjustAmount'"
|
||||||
|
v-model="row.adjustAmount"
|
||||||
|
:precision="2"
|
||||||
|
:step="0.01"
|
||||||
|
:controls="false"
|
||||||
|
@change="recalculateSummaryRow(row)"
|
||||||
|
/>
|
||||||
|
<el-input
|
||||||
|
v-else-if="editable && column.prop === 'remark'"
|
||||||
|
v-model="row.remark"
|
||||||
|
maxlength="50"
|
||||||
|
show-word-limit
|
||||||
|
/>
|
||||||
|
<span v-else-if="isSummaryMoney(column.prop)">
|
||||||
|
{{ formatMoney(row[column.prop]) }}
|
||||||
|
</span>
|
||||||
|
<span v-else-if="column.prop === 'feeType'">{{ feeCategoryName(row.feeType) }}</span>
|
||||||
<span v-else>{{ displayValue(row[column.prop]) }}</span>
|
<span v-else>{{ displayValue(row[column.prop]) }}</span>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column v-if="editable" label="操作" width="90" align="center">
|
<el-table-column v-if="editable" label="操作" width="100" align="center">
|
||||||
<template #default="{ $index }"
|
<template #default="{ row, $index }">
|
||||||
><el-link type="danger" @click="removeSource($index)">删除</el-link></template
|
<el-link v-if="row.manualFlag === 1" type="danger" @click="removeSummaryFee($index)">
|
||||||
>
|
删除
|
||||||
|
</el-link>
|
||||||
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
</section-card>
|
</section-card>
|
||||||
|
|
||||||
<section-card v-if="details.length" title="结算明细">
|
<section-card title="结算明细">
|
||||||
<el-table :data="details" border>
|
<template #extra>
|
||||||
|
<div class="formal-editor__actions">
|
||||||
|
<el-button type="primary" text @click="detailCollapsed = !detailCollapsed">
|
||||||
|
{{ detailCollapsed ? '展开明细' : '收起明细' }}
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<el-form
|
||||||
|
:model="detailQuery"
|
||||||
|
inline
|
||||||
|
label-position="right"
|
||||||
|
label-width="160px"
|
||||||
|
class="formal-editor__detail-filter"
|
||||||
|
@submit.prevent
|
||||||
|
>
|
||||||
|
<el-form-item label="单据号">
|
||||||
|
<el-input v-model="detailQuery.documentNo" clearable @keyup.enter="handleDetailQuery" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="运单号">
|
||||||
|
<el-input v-model="detailQuery.waybillNo" clearable @keyup.enter="handleDetailQuery" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="批次号">
|
||||||
|
<el-input v-model="detailQuery.batchNo" clearable @keyup.enter="handleDetailQuery" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="货物名称">
|
||||||
|
<el-input v-model="detailQuery.cargoName" clearable @keyup.enter="handleDetailQuery" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item class="formal-editor__detail-filter-actions">
|
||||||
|
<el-button type="primary" @click="handleDetailQuery">查询</el-button>
|
||||||
|
<el-button @click="resetDetailQuery">重置</el-button>
|
||||||
|
<el-button type="primary" :disabled="!details.length" @click="exportDetails">
|
||||||
|
导出
|
||||||
|
</el-button>
|
||||||
|
<el-button v-if="editable" type="primary" @click="openDetailDialog">
|
||||||
|
选择结算明细
|
||||||
|
</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<el-table v-show="!detailCollapsed" :data="filteredDetails" border>
|
||||||
<el-table-column type="index" label="序号" width="64" fixed="left" align="center" />
|
<el-table-column type="index" label="序号" width="64" fixed="left" align="center" />
|
||||||
<el-table-column
|
<el-table-column
|
||||||
v-for="column in detailTableColumns"
|
v-for="column in detailTableColumns"
|
||||||
@@ -117,15 +218,12 @@
|
|||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column v-if="editable" label="操作" width="150" fixed="right" align="center">
|
<el-table-column v-if="editable" label="操作" width="150" fixed="right" align="center">
|
||||||
<template #default="{ row, $index }">
|
<template #default="{ row }">
|
||||||
<div class="formal-editor__links">
|
<div class="formal-editor__links">
|
||||||
<el-link v-if="row.formalSettlementId" type="primary" @click="openAdjustDialog(row)"
|
<el-link v-if="row.formalSettlementId" type="primary" @click="openAdjustDialog(row)"
|
||||||
>调整</el-link
|
>调整</el-link
|
||||||
>
|
>
|
||||||
<el-link
|
<el-link v-if="!row.sourcePreSettlementId" type="danger" @click="removeDetail(row)"
|
||||||
v-if="!row.sourcePreSettlementId"
|
|
||||||
type="danger"
|
|
||||||
@click="removeDetail($index)"
|
|
||||||
>删除</el-link
|
>删除</el-link
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
@@ -248,11 +346,12 @@
|
|||||||
<el-form-item label="批次号"
|
<el-form-item label="批次号"
|
||||||
><el-input v-model="detailCandidate.query.batchNo" clearable
|
><el-input v-model="detailCandidate.query.batchNo" clearable
|
||||||
/></el-form-item>
|
/></el-form-item>
|
||||||
<el-form-item label="费用日期"
|
<el-form-item label="创建时间"
|
||||||
><el-date-picker
|
><el-date-picker
|
||||||
v-model="detailCandidate.query.feeDateRange"
|
v-model="detailCandidate.query.createTimeRange"
|
||||||
type="daterange"
|
type="daterange"
|
||||||
value-format="YYYY-MM-DD"
|
value-format="YYYY-MM-DD"
|
||||||
|
format="YYYY-MM-DD"
|
||||||
start-placeholder="开始日期"
|
start-placeholder="开始日期"
|
||||||
end-placeholder="结束日期"
|
end-placeholder="结束日期"
|
||||||
/></el-form-item>
|
/></el-form-item>
|
||||||
@@ -377,8 +476,11 @@ import {
|
|||||||
getContractOptions,
|
getContractOptions,
|
||||||
getDetail,
|
getDetail,
|
||||||
getDetailFees,
|
getDetailFees,
|
||||||
|
getFeeOptions,
|
||||||
|
getNextNo,
|
||||||
save,
|
save,
|
||||||
} from '@/api/settlement/formalSettlement';
|
} from '@/api/settlement/formalSettlement';
|
||||||
|
import { getDetail as getPreSettlementDetail } from '@/api/settlement/preSettlement';
|
||||||
import {
|
import {
|
||||||
createFormalSettlementForm,
|
createFormalSettlementForm,
|
||||||
formalSettlementFormFields,
|
formalSettlementFormFields,
|
||||||
@@ -389,7 +491,9 @@ import {
|
|||||||
detailColumns,
|
detailColumns,
|
||||||
paymentColumns,
|
paymentColumns,
|
||||||
sourceColumns,
|
sourceColumns,
|
||||||
|
summaryColumns,
|
||||||
} from '@/option/settlement/formalSettlementTable';
|
} from '@/option/settlement/formalSettlementTable';
|
||||||
|
import * as XLSX from 'xlsx';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'FormalSettlementEditor',
|
name: 'FormalSettlementEditor',
|
||||||
@@ -411,6 +515,7 @@ export default {
|
|||||||
form: createFormalSettlementForm(),
|
form: createFormalSettlementForm(),
|
||||||
sources: [],
|
sources: [],
|
||||||
details: [],
|
details: [],
|
||||||
|
summaryFees: [],
|
||||||
payments: [],
|
payments: [],
|
||||||
attachments: [],
|
attachments: [],
|
||||||
attachmentFileTypes: [
|
attachmentFileTypes: [
|
||||||
@@ -431,13 +536,18 @@ export default {
|
|||||||
'rar',
|
'rar',
|
||||||
],
|
],
|
||||||
contracts: [],
|
contracts: [],
|
||||||
|
allContracts: [],
|
||||||
|
projects: [],
|
||||||
|
feeOptions: [],
|
||||||
fields: formalSettlementFormFields,
|
fields: formalSettlementFormFields,
|
||||||
sourceTableColumns: sourceColumns,
|
sourceTableColumns: sourceColumns,
|
||||||
|
summaryTableColumns: summaryColumns,
|
||||||
detailTableColumns: detailColumns,
|
detailTableColumns: detailColumns,
|
||||||
paymentTableColumns: paymentColumns,
|
paymentTableColumns: paymentColumns,
|
||||||
candidateTableColumns: candidateColumns,
|
candidateTableColumns: candidateColumns,
|
||||||
candidateDetailTableColumns: candidateDetailColumns,
|
candidateDetailTableColumns: candidateDetailColumns,
|
||||||
rules: {
|
rules: {
|
||||||
|
projectId: [{ required: true, message: '请选择项目', trigger: 'change' }],
|
||||||
contractId: [{ required: true, message: '请选择合同', trigger: 'change' }],
|
contractId: [{ required: true, message: '请选择合同', trigger: 'change' }],
|
||||||
exchangeRateDate: [{ required: true, message: '请选择汇率日期', trigger: 'change' }],
|
exchangeRateDate: [{ required: true, message: '请选择汇率日期', trigger: 'change' }],
|
||||||
exchangeRate: [{ required: true, message: '请输入结算汇率', trigger: 'blur' }],
|
exchangeRate: [{ required: true, message: '请输入结算汇率', trigger: 'blur' }],
|
||||||
@@ -453,7 +563,7 @@ export default {
|
|||||||
detailCandidate: {
|
detailCandidate: {
|
||||||
visible: false,
|
visible: false,
|
||||||
loading: false,
|
loading: false,
|
||||||
query: {},
|
query: { batchNo: '', createTimeRange: [] },
|
||||||
rows: [],
|
rows: [],
|
||||||
selected: [],
|
selected: [],
|
||||||
page: { current: 1, size: 10, total: 0 },
|
page: { current: 1, size: 10, total: 0 },
|
||||||
@@ -466,6 +576,19 @@ export default {
|
|||||||
reason: '',
|
reason: '',
|
||||||
rows: [],
|
rows: [],
|
||||||
},
|
},
|
||||||
|
detailCollapsed: false,
|
||||||
|
detailQuery: {
|
||||||
|
documentNo: '',
|
||||||
|
waybillNo: '',
|
||||||
|
batchNo: '',
|
||||||
|
cargoName: '',
|
||||||
|
},
|
||||||
|
appliedDetailQuery: {
|
||||||
|
documentNo: '',
|
||||||
|
waybillNo: '',
|
||||||
|
batchNo: '',
|
||||||
|
cargoName: '',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -499,6 +622,19 @@ export default {
|
|||||||
title() {
|
title() {
|
||||||
return this.readonly ? '查看正式结算单' : this.recordId ? '编辑正式结算单' : '新增正式结算单';
|
return this.readonly ? '查看正式结算单' : this.recordId ? '编辑正式结算单' : '新增正式结算单';
|
||||||
},
|
},
|
||||||
|
summaryTotal() {
|
||||||
|
return this.summaryFees.reduce((total, row) => total + Number(row.settlementAmount || 0), 0);
|
||||||
|
},
|
||||||
|
filteredDetails() {
|
||||||
|
return this.details.filter(row =>
|
||||||
|
Object.entries(this.appliedDetailQuery).every(([field, keyword]) => {
|
||||||
|
if (!keyword?.trim()) return true;
|
||||||
|
return String(row[field] || '')
|
||||||
|
.toLocaleLowerCase()
|
||||||
|
.includes(keyword.trim().toLocaleLowerCase());
|
||||||
|
})
|
||||||
|
);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
modelValue: {
|
modelValue: {
|
||||||
@@ -507,18 +643,34 @@ export default {
|
|||||||
if (value) this.initialize();
|
if (value) this.initialize();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
summaryTotal(value) {
|
||||||
|
this.form.settlementAmount = Number(value || 0).toFixed(2);
|
||||||
|
this.form.localSettlementAmount = (
|
||||||
|
Number(value || 0) * Number(this.form.exchangeRate || 1)
|
||||||
|
).toFixed(2);
|
||||||
|
},
|
||||||
|
'form.exchangeRate'(value) {
|
||||||
|
this.form.localSettlementAmount = (
|
||||||
|
Number(this.summaryTotal || 0) * Number(value || 1)
|
||||||
|
).toFixed(2);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async initialize() {
|
async initialize() {
|
||||||
this.form = createFormalSettlementForm();
|
this.form = createFormalSettlementForm();
|
||||||
this.sources = [];
|
this.sources = [];
|
||||||
this.details = [];
|
this.details = [];
|
||||||
|
this.summaryFees = [];
|
||||||
this.payments = [];
|
this.payments = [];
|
||||||
this.attachments = [];
|
this.attachments = [];
|
||||||
await this.loadContracts();
|
this.detailCollapsed = false;
|
||||||
|
this.resetDetailQuery(false);
|
||||||
|
await Promise.all([this.loadAllContracts(), this.loadFeeOptions()]);
|
||||||
if (!this.recordId) {
|
if (!this.recordId) {
|
||||||
this.form.exchangeRateDate = this.$dayjs().format('YYYY-MM-DD');
|
this.form.exchangeRateDate = this.$dayjs().format('YYYY-MM-DD');
|
||||||
if (this.initialData) this.applyInitialData();
|
const response = await getNextNo();
|
||||||
|
this.form.formalSettlementNo = this.unwrapData(response) || '';
|
||||||
|
if (this.initialData) await this.applyInitialData();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
@@ -535,13 +687,24 @@ export default {
|
|||||||
};
|
};
|
||||||
this.sources = data.sources || [];
|
this.sources = data.sources || [];
|
||||||
this.details = data.details || [];
|
this.details = data.details || [];
|
||||||
|
this.summaryFees = data.summaryFees || [];
|
||||||
this.payments = data.payments || [];
|
this.payments = data.payments || [];
|
||||||
this.attachments = this.parseAttachments(data.attachmentsJson);
|
this.attachments = this.parseAttachments(data.attachmentsJson);
|
||||||
|
this.contracts = this.allContracts.filter(
|
||||||
|
item => String(item.projectId) === String(this.form.projectId)
|
||||||
|
);
|
||||||
} finally {
|
} finally {
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
applyInitialData() {
|
async applyInitialData() {
|
||||||
|
const sourcePreSettlements = Array.isArray(this.initialData?.sourcePreSettlements)
|
||||||
|
? this.initialData.sourcePreSettlements
|
||||||
|
: [];
|
||||||
|
if (sourcePreSettlements.length) {
|
||||||
|
await this.applyInitialSources(sourcePreSettlements);
|
||||||
|
return;
|
||||||
|
}
|
||||||
const rows = Array.isArray(this.initialData?.rows) ? this.initialData.rows : [];
|
const rows = Array.isArray(this.initialData?.rows) ? this.initialData.rows : [];
|
||||||
if (!rows.length) return;
|
if (!rows.length) return;
|
||||||
const first = rows[0];
|
const first = rows[0];
|
||||||
@@ -583,16 +746,113 @@ export default {
|
|||||||
row.settlementAmountTax ?? row.totalAmount ?? row.afterAmount ?? row.settlementAmount,
|
row.settlementAmountTax ?? row.totalAmount ?? row.afterAmount ?? row.settlementAmount,
|
||||||
}));
|
}));
|
||||||
this.form.sourceDetailIds = this.details.map(item => item.sourceDetailId);
|
this.form.sourceDetailIds = this.details.map(item => item.sourceDetailId);
|
||||||
this.form.settlementAmount = this.details.reduce(
|
this.buildSummaryFeesFromDetails();
|
||||||
(total, item) => total + Number(item.settlementAmountTax || 0),
|
},
|
||||||
0
|
async applyInitialSources(rows) {
|
||||||
|
const first = rows[0];
|
||||||
|
const contractId = first.contractId || null;
|
||||||
|
const settlementType = first.settlementType || 'payable';
|
||||||
|
if (!this.contracts.some(item => String(item.id) === String(contractId))) {
|
||||||
|
this.contracts.push({
|
||||||
|
id: contractId,
|
||||||
|
contractNo: first.contractNo,
|
||||||
|
contractName: first.contractName,
|
||||||
|
projectId: first.projectId,
|
||||||
|
projectName: first.projectName,
|
||||||
|
deptId: first.deptId,
|
||||||
|
deptName: first.deptName,
|
||||||
|
payerName: first.payerName,
|
||||||
|
payeeName: first.payeeName,
|
||||||
|
settlementType,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.handleContractChange(contractId);
|
||||||
|
this.sources = rows.map(row => ({
|
||||||
|
...row,
|
||||||
|
preSettlementId: row.preSettlementId || row.id,
|
||||||
|
}));
|
||||||
|
this.form.sourcePreSettlementIds = this.sources.map(item => item.preSettlementId);
|
||||||
|
Object.assign(this.form, first, {
|
||||||
|
id: null,
|
||||||
|
formalSettlementNo: this.form.formalSettlementNo,
|
||||||
|
contractId,
|
||||||
|
settlementType,
|
||||||
|
settlementTypeName: settlementType === 'receivable' ? '应收' : '应付',
|
||||||
|
sourcePreSettlementIds: this.form.sourcePreSettlementIds,
|
||||||
|
});
|
||||||
|
const responses = await Promise.all(
|
||||||
|
this.sources.map(item => getPreSettlementDetail(item.preSettlementId))
|
||||||
);
|
);
|
||||||
this.form.localSettlementAmount =
|
const settlements = responses.map(response => this.unwrapData(response) || {});
|
||||||
this.form.settlementAmount * Number(this.form.exchangeRate || 1);
|
this.details = settlements.flatMap(settlement =>
|
||||||
|
(settlement.details || []).map(detail => ({
|
||||||
|
...detail,
|
||||||
|
sourcePreSettlementId: settlement.id,
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
const summaryMap = new Map();
|
||||||
|
settlements
|
||||||
|
.flatMap(settlement => settlement.summaryFees || [])
|
||||||
|
.forEach(row => {
|
||||||
|
const key = `${row.feeType}\u0000${row.feeItem}`;
|
||||||
|
const current = summaryMap.get(key) || {
|
||||||
|
feeType: row.feeType,
|
||||||
|
feeItem: row.feeItem,
|
||||||
|
originalAmount: 0,
|
||||||
|
adjustAmount: 0,
|
||||||
|
settlementAmount: 0,
|
||||||
|
remark: '',
|
||||||
|
manualFlag: 0,
|
||||||
|
};
|
||||||
|
current.originalAmount += Number(row.settlementAmount || 0);
|
||||||
|
current.settlementAmount = current.originalAmount;
|
||||||
|
summaryMap.set(key, current);
|
||||||
|
});
|
||||||
|
this.summaryFees = [...summaryMap.values()];
|
||||||
|
if (!this.summaryFees.length) this.buildSummaryFeesFromDetails();
|
||||||
|
},
|
||||||
|
async loadAllContracts() {
|
||||||
|
const response = await getContractOptions('');
|
||||||
|
this.allContracts = this.unwrapData(response) || [];
|
||||||
|
const projectMap = new Map();
|
||||||
|
this.allContracts.forEach(item => {
|
||||||
|
if (item.projectId)
|
||||||
|
projectMap.set(String(item.projectId), { id: item.projectId, name: item.projectName });
|
||||||
|
});
|
||||||
|
this.projects = [...projectMap.values()];
|
||||||
|
this.contracts = [];
|
||||||
},
|
},
|
||||||
async loadContracts(keyword = '') {
|
async loadContracts(keyword = '') {
|
||||||
const response = await getContractOptions(keyword);
|
const projectId = this.form.projectId;
|
||||||
|
if (!projectId) {
|
||||||
|
this.contracts = [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const response = await getContractOptions(keyword, projectId);
|
||||||
|
if (String(this.form.projectId) === String(projectId)) {
|
||||||
this.contracts = this.unwrapData(response) || [];
|
this.contracts = this.unwrapData(response) || [];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async loadFeeOptions() {
|
||||||
|
const response = await getFeeOptions();
|
||||||
|
this.feeOptions = this.unwrapData(response) || [];
|
||||||
|
},
|
||||||
|
handleProjectChange(id) {
|
||||||
|
const project = this.projects.find(item => String(item.id) === String(id));
|
||||||
|
this.form.projectName = project?.name || '';
|
||||||
|
this.form.contractId = null;
|
||||||
|
this.form.contractNo = '';
|
||||||
|
this.form.contractName = '';
|
||||||
|
this.form.deptId = null;
|
||||||
|
this.form.deptName = '';
|
||||||
|
this.form.payerName = '';
|
||||||
|
this.form.payeeName = '';
|
||||||
|
this.sources = [];
|
||||||
|
this.details = [];
|
||||||
|
this.summaryFees = [];
|
||||||
|
this.form.sourcePreSettlementIds = [];
|
||||||
|
this.form.sourceDetailIds = [];
|
||||||
|
this.loadContracts();
|
||||||
},
|
},
|
||||||
handleContractChange(id) {
|
handleContractChange(id) {
|
||||||
const contract = this.contracts.find(item => String(item.id) === String(id));
|
const contract = this.contracts.find(item => String(item.id) === String(id));
|
||||||
@@ -601,10 +861,17 @@ export default {
|
|||||||
Object.assign(this.form, contract, {
|
Object.assign(this.form, contract, {
|
||||||
id: formalSettlementId,
|
id: formalSettlementId,
|
||||||
contractId: contract.id,
|
contractId: contract.id,
|
||||||
|
payerName:
|
||||||
|
contract.payerName ||
|
||||||
|
(contract.settlementType === 'receivable' ? contract.partyB : contract.partyA),
|
||||||
|
payeeName:
|
||||||
|
contract.payeeName ||
|
||||||
|
(contract.settlementType === 'receivable' ? contract.partyA : contract.partyB),
|
||||||
settlementTypeName: contract.settlementType === 'receivable' ? '应收' : '应付',
|
settlementTypeName: contract.settlementType === 'receivable' ? '应收' : '应付',
|
||||||
});
|
});
|
||||||
this.sources = [];
|
this.sources = [];
|
||||||
this.details = [];
|
this.details = [];
|
||||||
|
this.summaryFees = [];
|
||||||
this.form.sourcePreSettlementIds = [];
|
this.form.sourcePreSettlementIds = [];
|
||||||
this.form.sourceDetailIds = [];
|
this.form.sourceDetailIds = [];
|
||||||
},
|
},
|
||||||
@@ -668,18 +935,19 @@ export default {
|
|||||||
if (!this.form.contractId) return this.$message.warning('请先选择合同');
|
if (!this.form.contractId) return this.$message.warning('请先选择合同');
|
||||||
this.detailCandidate.visible = true;
|
this.detailCandidate.visible = true;
|
||||||
this.detailCandidate.page.current = 1;
|
this.detailCandidate.page.current = 1;
|
||||||
|
this.detailCandidate.selected = [];
|
||||||
this.loadDetailCandidates();
|
this.loadDetailCandidates();
|
||||||
},
|
},
|
||||||
async loadDetailCandidates() {
|
async loadDetailCandidates() {
|
||||||
this.detailCandidate.loading = true;
|
this.detailCandidate.loading = true;
|
||||||
try {
|
try {
|
||||||
const range = this.detailCandidate.query.feeDateRange || [];
|
const range = this.detailCandidate.query.createTimeRange || [];
|
||||||
const params = {
|
const params = {
|
||||||
contractId: this.form.contractId,
|
contractId: this.form.contractId,
|
||||||
settlementType: this.form.settlementType,
|
settlementType: this.form.settlementType,
|
||||||
batchNo: this.detailCandidate.query.batchNo,
|
batchNo: this.detailCandidate.query.batchNo,
|
||||||
feeStartDate: range[0],
|
createStartDate: range[0],
|
||||||
feeEndDate: range[1],
|
createEndDate: range[1],
|
||||||
};
|
};
|
||||||
const response = await getCandidateDetails(
|
const response = await getCandidateDetails(
|
||||||
this.detailCandidate.page.current,
|
this.detailCandidate.page.current,
|
||||||
@@ -694,11 +962,14 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
resetDetailCandidates() {
|
resetDetailCandidates() {
|
||||||
this.detailCandidate.query = {};
|
this.detailCandidate.query = { batchNo: '', createTimeRange: [] };
|
||||||
this.detailCandidate.page.current = 1;
|
this.detailCandidate.page.current = 1;
|
||||||
this.loadDetailCandidates();
|
this.loadDetailCandidates();
|
||||||
},
|
},
|
||||||
confirmDetailCandidates() {
|
confirmDetailCandidates() {
|
||||||
|
if (!this.detailCandidate.selected.length) {
|
||||||
|
return this.$message.warning('请选择结算明细');
|
||||||
|
}
|
||||||
const existing = new Map(
|
const existing = new Map(
|
||||||
this.details
|
this.details
|
||||||
.filter(item => !item.formalSettlementId)
|
.filter(item => !item.formalSettlementId)
|
||||||
@@ -716,16 +987,144 @@ export default {
|
|||||||
...existing.values(),
|
...existing.values(),
|
||||||
];
|
];
|
||||||
this.form.sourceDetailIds = [...existing.values()].map(item => item.sourceDetailId);
|
this.form.sourceDetailIds = [...existing.values()].map(item => item.sourceDetailId);
|
||||||
this.form.settlementAmount =
|
this.buildSummaryFeesFromDetails();
|
||||||
this.sources.reduce((sum, item) => sum + Number(item.settlementAmount || 0), 0) +
|
|
||||||
[...existing.values()].reduce((sum, item) => sum + Number(item.totalAmount || 0), 0);
|
|
||||||
this.detailCandidate.visible = false;
|
this.detailCandidate.visible = false;
|
||||||
},
|
},
|
||||||
removeDetail(index) {
|
removeDetail(row) {
|
||||||
|
const index = this.details.indexOf(row);
|
||||||
|
if (index < 0) return;
|
||||||
this.details.splice(index, 1);
|
this.details.splice(index, 1);
|
||||||
this.form.sourceDetailIds = this.details
|
this.form.sourceDetailIds = this.details
|
||||||
.filter(item => !item.sourcePreSettlementId)
|
.filter(item => !item.sourcePreSettlementId)
|
||||||
.map(item => item.sourceDetailId);
|
.map(item => item.sourceDetailId);
|
||||||
|
this.buildSummaryFeesFromDetails();
|
||||||
|
},
|
||||||
|
addSummaryFee() {
|
||||||
|
this.summaryFees.push({
|
||||||
|
feeType: '',
|
||||||
|
feeItem: '',
|
||||||
|
originalAmount: 0,
|
||||||
|
adjustAmount: 0,
|
||||||
|
settlementAmount: 0,
|
||||||
|
remark: '',
|
||||||
|
manualFlag: 1,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
removeSummaryFee(index) {
|
||||||
|
this.summaryFees.splice(index, 1);
|
||||||
|
},
|
||||||
|
handleManualFeeTypeChange(row) {
|
||||||
|
if (!this.manualFeeItems(row.feeType).includes(row.feeItem)) row.feeItem = '';
|
||||||
|
},
|
||||||
|
manualFeeItems(feeType) {
|
||||||
|
return this.feeOptions.find(item => item.feeType === feeType)?.feeItems || [];
|
||||||
|
},
|
||||||
|
feeCategoryName(value) {
|
||||||
|
return (
|
||||||
|
this.feeOptions.find(item => String(item.feeType) === String(value))?.feeTypeName || value
|
||||||
|
);
|
||||||
|
},
|
||||||
|
isSummaryMoney(prop) {
|
||||||
|
return ['originalAmount', 'adjustAmount', 'settlementAmount'].includes(prop);
|
||||||
|
},
|
||||||
|
recalculateSummaryRow(row) {
|
||||||
|
row.settlementAmount = Number(row.originalAmount || 0) + Number(row.adjustAmount || 0);
|
||||||
|
},
|
||||||
|
getSummarySums({ columns, data }) {
|
||||||
|
const sumProps = ['originalAmount', 'adjustAmount', 'settlementAmount'];
|
||||||
|
return columns.map((column, index) => {
|
||||||
|
if (index === 0) return '合计';
|
||||||
|
if (!sumProps.includes(column.property)) return '';
|
||||||
|
const total = data.reduce(
|
||||||
|
(sum, row) => sum + Number(row[column.property] || 0),
|
||||||
|
0
|
||||||
|
);
|
||||||
|
return this.formatMoney(total);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
buildSummaryFeesFromDetails() {
|
||||||
|
const generatedMap = new Map();
|
||||||
|
const existingGenerated = new Map(
|
||||||
|
this.summaryFees
|
||||||
|
.filter(row => row.manualFlag !== 1)
|
||||||
|
.map(row => [`${row.feeType}\u0000${row.feeItem}`, row])
|
||||||
|
);
|
||||||
|
const append = (feeItem, value, fallbackFeeType = '') => {
|
||||||
|
const amount = Number(value || 0);
|
||||||
|
if (!feeItem || !Number.isFinite(amount) || Math.abs(amount) < 0.005) return;
|
||||||
|
const option = this.feeOptions.find(item =>
|
||||||
|
(item.feeItems || []).some(name => String(name) === String(feeItem))
|
||||||
|
);
|
||||||
|
const feeType =
|
||||||
|
option?.feeType ||
|
||||||
|
fallbackFeeType ||
|
||||||
|
(this.isFreightFeeItem(feeItem) ? '物流配送' : '其他费用');
|
||||||
|
const key = `${feeType}\u0000${feeItem}`;
|
||||||
|
const old = existingGenerated.get(key);
|
||||||
|
const current = generatedMap.get(key) || {
|
||||||
|
id: old?.id,
|
||||||
|
feeType,
|
||||||
|
feeItem,
|
||||||
|
originalAmount: 0,
|
||||||
|
adjustAmount: Number(old?.adjustAmount || 0),
|
||||||
|
settlementAmount: 0,
|
||||||
|
remark: old?.remark || '',
|
||||||
|
manualFlag: 0,
|
||||||
|
};
|
||||||
|
current.originalAmount = Number((current.originalAmount + amount).toFixed(2));
|
||||||
|
current.settlementAmount = Number(
|
||||||
|
(current.originalAmount + Number(current.adjustAmount || 0)).toFixed(2)
|
||||||
|
);
|
||||||
|
generatedMap.set(key, current);
|
||||||
|
};
|
||||||
|
this.details.forEach(row => {
|
||||||
|
const feeItems = this.parseFeeItems(row.feeItemsJson || row.feeItems);
|
||||||
|
const entries = Object.entries(feeItems);
|
||||||
|
entries.forEach(([feeItem, amount]) => append(feeItem, amount, row.feeType));
|
||||||
|
let knownAmount = entries.reduce((total, [, amount]) => total + Number(amount || 0), 0);
|
||||||
|
if (!entries.some(([feeItem]) => this.isFreightFeeItem(feeItem))) {
|
||||||
|
append('运输费', row.freightAmount, '物流配送');
|
||||||
|
knownAmount += Number(row.freightAmount || 0);
|
||||||
|
}
|
||||||
|
const totalAmount = Number(
|
||||||
|
row.settlementAmountTax ?? row.totalAmount ?? row.afterAmount ?? row.settlementAmount ?? 0
|
||||||
|
);
|
||||||
|
const residualAmount = Number((totalAmount - knownAmount).toFixed(2));
|
||||||
|
if (Math.abs(residualAmount) >= 0.005) append('其他费用', residualAmount, '其他费用');
|
||||||
|
});
|
||||||
|
const manualRows = this.summaryFees.filter(row => row.manualFlag === 1);
|
||||||
|
this.summaryFees = [...generatedMap.values(), ...manualRows];
|
||||||
|
},
|
||||||
|
isFreightFeeItem(name) {
|
||||||
|
return name && (name.includes('运费') || name.includes('运输费'));
|
||||||
|
},
|
||||||
|
handleDetailQuery() {
|
||||||
|
this.appliedDetailQuery = { ...this.detailQuery };
|
||||||
|
},
|
||||||
|
resetDetailQuery(apply = true) {
|
||||||
|
this.detailQuery = { documentNo: '', waybillNo: '', batchNo: '', cargoName: '' };
|
||||||
|
if (apply) this.handleDetailQuery();
|
||||||
|
else this.appliedDetailQuery = { ...this.detailQuery };
|
||||||
|
},
|
||||||
|
exportDetails() {
|
||||||
|
if (!this.details.length) return this.$message.warning('暂无可导出的结算明细');
|
||||||
|
const rows = this.details.map((row, index) => {
|
||||||
|
const result = { 序号: index + 1 };
|
||||||
|
this.detailTableColumns.forEach(column => {
|
||||||
|
result[column.label] = column.money
|
||||||
|
? Number(row[column.prop] || 0).toFixed(2)
|
||||||
|
: row[column.prop] ?? '';
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
const workbook = XLSX.utils.book_new();
|
||||||
|
XLSX.utils.book_append_sheet(workbook, XLSX.utils.json_to_sheet(rows), '结算明细');
|
||||||
|
XLSX.writeFile(
|
||||||
|
workbook,
|
||||||
|
`${this.form.formalSettlementNo || '正式结算单'}结算明细${this.$dayjs().format(
|
||||||
|
'YYYY-MM-DD HH-mm-ss'
|
||||||
|
)}.xlsx`
|
||||||
|
);
|
||||||
},
|
},
|
||||||
async openAdjustDialog(row) {
|
async openAdjustDialog(row) {
|
||||||
this.adjust = {
|
this.adjust = {
|
||||||
@@ -766,7 +1165,15 @@ export default {
|
|||||||
async handleSave() {
|
async handleSave() {
|
||||||
await this.$refs.formRef.validate();
|
await this.$refs.formRef.validate();
|
||||||
if (!this.form.sourcePreSettlementIds.length && !this.form.sourceDetailIds.length) {
|
if (!this.form.sourcePreSettlementIds.length && !this.form.sourceDetailIds.length) {
|
||||||
return this.$message.warning('请选择预结算单或结算明细');
|
return this.$message.warning('请选择结算明细');
|
||||||
|
}
|
||||||
|
const invalidManualFee = this.summaryFees.find(
|
||||||
|
row =>
|
||||||
|
row.manualFlag === 1 &&
|
||||||
|
(!String(row.feeType || '').trim() || !String(row.feeItem || '').trim())
|
||||||
|
);
|
||||||
|
if (invalidManualFee) {
|
||||||
|
return this.$message.warning('请完善手工费用的费用类型和费用项');
|
||||||
}
|
}
|
||||||
this.saving = true;
|
this.saving = true;
|
||||||
try {
|
try {
|
||||||
@@ -778,6 +1185,22 @@ export default {
|
|||||||
sourceDetailIds: this.form.sourceDetailIds,
|
sourceDetailIds: this.form.sourceDetailIds,
|
||||||
exchangeRateDate: this.form.exchangeRateDate,
|
exchangeRateDate: this.form.exchangeRateDate,
|
||||||
exchangeRate: this.form.exchangeRate,
|
exchangeRate: this.form.exchangeRate,
|
||||||
|
summaryFees: this.summaryFees
|
||||||
|
.filter(
|
||||||
|
row =>
|
||||||
|
row.manualFlag === 1 ||
|
||||||
|
row.id ||
|
||||||
|
Number(row.adjustAmount || 0) !== 0 ||
|
||||||
|
String(row.remark || '').trim()
|
||||||
|
)
|
||||||
|
.map(row => ({
|
||||||
|
id: row.id || undefined,
|
||||||
|
feeType: row.feeType,
|
||||||
|
feeItem: row.feeItem,
|
||||||
|
adjustAmount: Number(row.adjustAmount || 0),
|
||||||
|
remark: row.remark,
|
||||||
|
manualFlag: row.manualFlag || 0,
|
||||||
|
})),
|
||||||
attachmentsJson: JSON.stringify(this.attachments || []),
|
attachmentsJson: JSON.stringify(this.attachments || []),
|
||||||
remark: this.form.remark,
|
remark: this.form.remark,
|
||||||
});
|
});
|
||||||
@@ -824,8 +1247,22 @@ export default {
|
|||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.formal-editor__actions {
|
.formal-editor__actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
.formal-editor__detail-filter {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||||
|
gap: 8px 16px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
padding: 12px 12px 4px;
|
||||||
|
background: #fff;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
||||||
|
}
|
||||||
|
.formal-editor__detail-filter-actions {
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
justify-self: start;
|
||||||
|
}
|
||||||
.formal-editor__page-actions {
|
.formal-editor__page-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
@@ -865,4 +1302,10 @@ export default {
|
|||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1200px) {
|
||||||
|
.formal-editor__detail-filter {
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -62,7 +62,7 @@
|
|||||||
v-if="hasPermission('pre_settlement_formal')"
|
v-if="hasPermission('pre_settlement_formal')"
|
||||||
type="primary"
|
type="primary"
|
||||||
plain
|
plain
|
||||||
disabled
|
:disabled="!selection.length"
|
||||||
@click="handleFormalSettlement"
|
@click="handleFormalSettlement"
|
||||||
>
|
>
|
||||||
尾款结算
|
尾款结算
|
||||||
@@ -315,7 +315,6 @@ import {
|
|||||||
applyAdvance,
|
applyAdvance,
|
||||||
approve,
|
approve,
|
||||||
exportList,
|
exportList,
|
||||||
formalSettlement,
|
|
||||||
getDetail,
|
getDetail,
|
||||||
getList,
|
getList,
|
||||||
getPrintTemplates,
|
getPrintTemplates,
|
||||||
@@ -325,6 +324,7 @@ import {
|
|||||||
} from '@/api/settlement/preSettlement';
|
} from '@/api/settlement/preSettlement';
|
||||||
import { preSettlementSearchFields } from '@/option/settlement/preSettlementSearch';
|
import { preSettlementSearchFields } from '@/option/settlement/preSettlementSearch';
|
||||||
import { preSettlementTableColumns } from '@/option/settlement/preSettlementTable';
|
import { preSettlementTableColumns } from '@/option/settlement/preSettlementTable';
|
||||||
|
import { createSettlementTransfer } from '@/utils/settlement-transfer';
|
||||||
import { downloadFile } from '@/utils/util';
|
import { downloadFile } from '@/utils/util';
|
||||||
import PreSettlementEditor from './components/pre-settlement-editor.vue';
|
import PreSettlementEditor from './components/pre-settlement-editor.vue';
|
||||||
|
|
||||||
@@ -535,23 +535,37 @@ export default {
|
|||||||
this.advanceDialog.submitting = false;
|
this.advanceDialog.submitting = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async handleFormalSettlement() {
|
handleFormalSettlement() {
|
||||||
const row = this.selectedOne('尾款结算');
|
if (!this.selection.length) {
|
||||||
if (!row) return;
|
this.$message.warning('请选择需要转正式结算的预结算单');
|
||||||
if (
|
|
||||||
row.approvalStatus !== 'approved' ||
|
|
||||||
row.settlementType !== 'payable' ||
|
|
||||||
row.formalSettlementNo
|
|
||||||
) {
|
|
||||||
this.$message.warning('仅审批通过、未转正式结算的应付预结算单可发起尾款结算');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await this.$confirm('转正式结算后无法发起预付,确认继续?', '提示', {
|
if (this.selection.some(row => row.approvalStatus !== 'approved')) {
|
||||||
type: 'warning',
|
this.$message.warning('仅审核通过的预结算单可转正式结算');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const hasCompleteProjectIds = this.selection.every(
|
||||||
|
row => row.projectId !== undefined && row.projectId !== null && row.projectId !== ''
|
||||||
|
);
|
||||||
|
const projectKeys = new Set(
|
||||||
|
this.selection.map(row =>
|
||||||
|
hasCompleteProjectIds ? String(row.projectId) : String(row.projectName || '')
|
||||||
|
)
|
||||||
|
);
|
||||||
|
if (projectKeys.size > 1) {
|
||||||
|
this.$message.warning('尾款结算只能选择同一项目下的预结算单');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const transferToken = createSettlementTransfer({
|
||||||
|
sourcePreSettlements: this.selection.map(row => ({
|
||||||
|
...row,
|
||||||
|
preSettlementId: row.preSettlementId || row.id,
|
||||||
|
})),
|
||||||
|
});
|
||||||
|
this.$router.push({
|
||||||
|
path: '/settlement/formal-settlement/form',
|
||||||
|
query: { mode: 'add', name: '新增正式结算', transferToken },
|
||||||
});
|
});
|
||||||
const { data } = await formalSettlement(row.id);
|
|
||||||
this.$message.success(`正式结算单已生成:${data?.data || ''}`);
|
|
||||||
this.loadTable();
|
|
||||||
},
|
},
|
||||||
async openPrintDialog() {
|
async openPrintDialog() {
|
||||||
const row = this.selectedOne('打印结算单');
|
const row = this.selectedOne('打印结算单');
|
||||||
|
|||||||
+13
-13
@@ -50,26 +50,26 @@ export default ({
|
|||||||
__VUE_I18N_LEGACY_API__: true,
|
__VUE_I18N_LEGACY_API__: true,
|
||||||
__INTLIFY_PROD_DEVTOOLS__: false,
|
__INTLIFY_PROD_DEVTOOLS__: false,
|
||||||
},
|
},
|
||||||
// server: {
|
|
||||||
// port: 2888,
|
|
||||||
// proxy: {
|
|
||||||
// '/api': {
|
|
||||||
// target: 'http://localhost',
|
|
||||||
// //target: 'https://saber3.bladex.cn/api',
|
|
||||||
// changeOrigin: true,
|
|
||||||
// rewrite: path => path.replace(/^\/api/, ''),
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
server: {
|
server: {
|
||||||
port: 2889,
|
port: 2888,
|
||||||
proxy: {
|
proxy: {
|
||||||
'/api': {
|
'/api': {
|
||||||
target: 'http://172.16.203.228:8000',
|
target: 'http://localhost',
|
||||||
|
//target: 'https://saber3.bladex.cn/api',
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
|
rewrite: path => path.replace(/^\/api/, ''),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
// server: {
|
||||||
|
// port: 2889,
|
||||||
|
// proxy: {
|
||||||
|
// '/api': {
|
||||||
|
// target: 'http://172.16.203.228:8000',
|
||||||
|
// changeOrigin: true,
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// },
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
'~': resolve(__dirname, './'),
|
'~': resolve(__dirname, './'),
|
||||||
|
|||||||
Reference in New Issue
Block a user