1.1.42 优化合同、开票、订单台账、发货单显示问题

dev
yinq 1 month ago
parent 0416b2a7ee
commit a8a6ba5620

@ -1240,6 +1240,7 @@ watch(
form.value.frameworkContractId = undefined;
form.value.frameworkContractCode = undefined;
form.value.frameworkContractName = undefined;
} else {
form.value.frameworkValidPeriod = undefined;
}
}

@ -159,28 +159,34 @@
</el-form-item>
</el-col>
</el-row>
<el-col :span="12">
<el-form-item
label="发票附件"
prop="ossId"
v-if="routeParams.type === 'update' || routeParams.type === 'view' || routeParams.type === 'approval'"
>
<FileUpload
v-model="ossIdString"
:limit="5"
:fileSize="20"
:fileType="['png', 'jpg', 'pdf', 'ofd', 'xml']"
:isShowTip="true"
:disabled="!hasInvoiceAttachPer"
/>
</el-form-item>
</el-col>
</el-form>
<!-- 发票附件单独表单避免主表单在审批/待办时整体 disabled 导致无法上传与权限角色 invoice 审批场景配合 -->
<el-form
v-if="routeParams.type === 'update' || routeParams.type === 'view' || routeParams.type === 'approval'"
:model="form"
label-width="130px"
status-icon
:disabled="invoiceAttachReadonly"
>
<el-row :gutter="24">
<el-col :span="12">
<el-form-item label="发票附件" prop="ossId">
<FileUpload
v-model="ossIdString"
:limit="5"
:fileSize="20"
:fileType="['png', 'jpg', 'pdf', 'ofd', 'xml']"
:isShowTip="true"
:disabled="invoiceAttachReadonly"
/>
</el-form-item>
</el-col>
</el-row>
</el-form>
</el-card>
<!-- 开票明细 -->
<el-card shadow="never" class="mb-[15px]" header="开票明细">
<el-card shadow="never" class="mb-[15px]">
<template #header>
<div class="flex justify-between items-center">
<span class="font-medium">开票明细</span>
@ -212,17 +218,22 @@
<el-table-column label="序号" type="index" width="60" align="center" />
<el-table-column label="开票内容" prop="billingItems" min-width="160">
<template #default="{ row, $index }">
<el-input v-model="row.billingItems" placeholder="请输入开票内容" @change="handleItemChange($index)" />
<el-input
v-model="row.billingItems"
placeholder="请输入开票内容"
:disabled="isFormDisabled"
@change="handleItemChange($index)"
/>
</template>
</el-table-column>
<el-table-column label="规格型号" prop="specificationModel" min-width="140">
<template #default="{ row }">
<el-input v-model="row.specificationModel" placeholder="请输入规格型号" />
<el-input v-model="row.specificationModel" placeholder="请输入规格型号" :disabled="isFormDisabled" />
</template>
</el-table-column>
<el-table-column label="单位" prop="unitName" width="120">
<template #default="{ row }">
<el-input v-model="row.unitName" placeholder="请输入单位" />
<el-input v-model="row.unitName" placeholder="请输入单位" :disabled="isFormDisabled" />
</template>
</el-table-column>
<el-table-column label="数量" prop="quantity" width="145">
@ -232,6 +243,7 @@
:min="0"
:precision="2"
controls-position="right"
:disabled="isFormDisabled"
@change="calculateItemAmount($index)"
style="width: 120px"
/>
@ -247,6 +259,7 @@
clearable
placeholder="税率"
style="width: 80px"
:disabled="isFormDisabled"
@change="calculateItemAmount($index)"
>
<el-option v-for="rate in taxRateOptions" :key="rate" :label="`${rate}`" :value="rate" />
@ -266,6 +279,7 @@
:min="0"
:precision="2"
controls-position="right"
:disabled="isFormDisabled"
@change="calculateItemAmountByTotal($index)"
style="width: 120px"
/>
@ -278,6 +292,7 @@
:min="0"
:precision="2"
controls-position="right"
:disabled="isFormDisabled"
@change="calculateItemAmountByNoTax($index)"
style="width: 120px"
/>
@ -301,7 +316,7 @@
<!-- 项目选择弹窗 -->
<!-- 项目选择弹窗组件 -->
<ProjectSelectDialog v-model:visible="projectSelectDialogVisible" projectCategory="1" @project-selected="handleProjectSelected" />
<ProjectSelectDialog v-model:visible="projectSelectDialogVisible" @project-selected="handleProjectSelected" />
<!-- 合同选择弹窗组件 -->
<ContractSelectDialog v-model:visible="contractSelectDialogVisible" @contract-selected="handleContractSelected" />
@ -356,7 +371,7 @@ import { ContractPaymentMethodVO } from '@/api/oa/erp/contractPaymentMethod/type
import { UserVO } from '@/api/system/user/types';
import ContractSelectDialog from '@/views/oa/components/ContractSelectDialog.vue';
import FileUpload from '@/components/FileUpload/index.vue';
import { checkPermi } from '@/utils/permission';
import { checkPermi, checkRole } from '@/utils/permission';
import { getContractInfo } from '@/api/oa/erp/contractInfo';
import { listProjectInfoByContractId } from '@/api/oa/erp/projectInfo';
@ -397,6 +412,25 @@ const CONTRACT_FLAG = {
};
const hasInvoiceAttachPer = checkPermi(['oa/erp:finInvoiceInfo:invoiceAttach']);
/** 仅允许下载:查看页;或修改页但流程审批中(与主表单锁定一致)。其余按菜单权限 / invoice 审批角色 */
const invoiceAttachReadonly = computed(() => {
if (routeParams.value.type === 'view') {
return true;
}
if (routeParams.value.type === 'update') {
return true;
}
const canInvoiceRoleAttach =
routeParams.value.type === 'approval' &&
form.value.flowStatus === FLOW_STATUS.WAITING &&
checkRole(['invoice']);
if (hasInvoiceAttachPer || canInvoiceRoleAttach) {
return false;
}
return true;
});
const isFormDisabled = computed(() => {
return routeParams.value.type === 'view' || routeParams.value.type === 'approval' || form.value.flowStatus === FLOW_STATUS.WAITING;
});
@ -488,7 +522,7 @@ watch(
// 使 ?? null undefined 'empty'
// ossId ()
if (isInitialized.value && form.value.invoiceId) {
if (isInitialized.value && form.value.invoiceId && !invoiceAttachReadonly.value) {
const normalizedOld = oldVal ?? 'empty';
const normalizedNew = newVal ?? 'empty';
if (normalizedOld !== normalizedNew) {
@ -529,8 +563,19 @@ const handleUserChange = async (newUserId) => {
};
const getContractPaymentMethods = async (contractId) => {
const res = await getContractPaymentMethodList(contractId);
contractPaymentMethodVoList.value = res.data;
if (!contractId) {
contractPaymentMethodVoList.value = [];
return;
}
const [pmRes, contractRes] = await Promise.all([
getContractPaymentMethodList(contractId),
getContractInfo(contractId)
]);
contractPaymentMethodVoList.value = pmRes.data ?? [];
const c = contractRes?.data;
if (c?.contractCode) {
form.value.contractCode = c.contractCode;
}
};
const paymentDescription = computed(() => {
@ -559,7 +604,8 @@ const filterDisabledDept = (deptList: DeptTreeVO[]) => {
// - issueAmount issuancePercentage
const totalInvoiceAmount = computed(() => {
const total = Number(form.value.erpFinInvoiceDetailList.reduce((sum, item) => sum + (item.totalPrice || 0), 0).toFixed(2));
const detailList = form.value.erpFinInvoiceDetailList || [];
const total = Number(detailList.reduce((sum, item) => sum + (item.totalPrice || 0), 0).toFixed(2));
//
form.value.issueAmount = total;
@ -648,8 +694,27 @@ onMounted(async () => {
if (id && (routeParams.value.type === 'update' || routeParams.value.type === 'view' || routeParams.value.type === 'approval')) {
const res = await getFinInvoiceInfo(id);
Object.assign(form.value, res.data);
form.value.erpFinInvoiceDetailList = res.data.erpFinInvoiceDetailVoList || [];
form.value.erpFinInvoiceDetailList.forEach((_, index) => calculateItemAmountByTotal(index));
const invoiceVo = res.data;
const detailFromApi =
invoiceVo.erpFinInvoiceDetailVoList ?? (invoiceVo as FinInvoiceInfoForm).erpFinInvoiceDetailList;
form.value.erpFinInvoiceDetailList = normalizeInvoiceDetailListFromApi(
Array.isArray(detailFromApi) ? detailFromApi : []
);
// /
form.value.erpFinInvoiceDetailList.forEach((item) => {
if (
item.totalPrice != null &&
item.totalPriceNoTax == null &&
item.taxRate != null
) {
calculateTaxAmounts(item);
}
});
if (form.value.contractId) {
await getContractPaymentMethods(form.value.contractId);
} else {
contractPaymentMethodVoList.value = [];
}
} else {
const res = await getBaseInfo();
Object.assign(form.value, res.data);
@ -786,6 +851,25 @@ const handleSelectionChange = (selection: FinInvoiceDetailVO[]) => {
selectedItems.value = selection;
};
/** 接口 BigDecimal 等可能为数字或字符串,统一为表单可用的 number便于 el-select / el-input-number 正确回显 */
const toFormNumber = (val: unknown): number | undefined => {
if (val === undefined || val === null || val === '') return undefined;
const n = Number(val);
return Number.isFinite(n) ? n : undefined;
};
const normalizeInvoiceDetailListFromApi = (list: FinInvoiceDetailVO[]): FinInvoiceDetailVO[] => {
return list.map((row) => ({
...row,
quantity: toFormNumber(row.quantity),
unitPrice: toFormNumber(row.unitPrice),
totalPrice: toFormNumber(row.totalPrice),
totalPriceNoTax: toFormNumber(row.totalPriceNoTax),
taxRate: toFormNumber(row.taxRate),
taxPrice: toFormNumber(row.taxPrice)
})) as FinInvoiceDetailVO[];
};
const formatAmount = (value?: number) => {
return value !== undefined && value !== null ? Number(value).toFixed(2) : '0.00';
};
@ -808,8 +892,10 @@ const calculateTaxAmounts = (item: FinInvoiceDetailVO) => {
// ()
const calculateItemAmount = (index: number) => {
const item = form.value.erpFinInvoiceDetailList[index];
if (item.quantity && item.totalPrice !== undefined && item.totalPrice !== null) {
item.unitPrice = roundAmount(item.totalPrice / item.quantity);
const qty = toFormNumber(item.quantity);
const total = toFormNumber(item.totalPrice);
if (qty !== undefined && qty > 0 && total !== undefined) {
item.unitPrice = roundAmount(total / qty);
} else {
item.unitPrice = undefined;
}
@ -831,8 +917,9 @@ const calculateItemAmountByNoTax = (index: number) => {
}
const taxRate = Number(item.taxRate || 0);
item.totalPrice = roundAmount(noTaxAmount * (1 + taxRate / 100)) || 0;
if (item.quantity && item.quantity > 0) {
item.unitPrice = roundAmount((item.totalPrice || 0) / item.quantity);
const qty = toFormNumber(item.quantity);
if (qty !== undefined && qty > 0) {
item.unitPrice = roundAmount((item.totalPrice || 0) / qty);
} else {
item.unitPrice = undefined;
}
@ -903,8 +990,20 @@ const submitCallback = async () => {
router.go(-1);
};
//
// invoice
const approvalVerifyOpen = async () => {
const needInvoiceAttach =
routeParams.value.type === 'approval' &&
form.value.flowStatus === FLOW_STATUS.WAITING &&
checkRole(['invoice']);
if (needInvoiceAttach) {
const raw = form.value.ossId;
const hasFile = raw !== undefined && raw !== null && String(raw).trim() !== '';
if (!hasFile) {
ElMessage.warning('请先上传发票附件后再审批');
return;
}
}
await submitVerifyRef.value.openDialog(routeParams.value.taskId);
};

@ -484,6 +484,7 @@ import { ContractOrderPurchaseMaterialVO } from '@/api/oa/erp/contractOrder/type
import { getContractInfo } from '@/api/oa/erp/contractInfo';
import { ContractInfoVO } from '@/api/oa/erp/contractInfo/types';
import { ProjectInfoVO } from '@/api/oa/erp/projectInfo/types';
import { listProjectInfoByContractId } from '@/api/oa/erp/projectInfo';
import { listProjectPurchase } from '@/api/oa/erp/projectPurchase';
import { ProjectPurchaseVO } from '@/api/oa/erp/projectPurchase/types';
import { listFinInvoiceInfo } from '@/api/oa/erp/finInvoiceInfo';
@ -499,6 +500,9 @@ import { useUserStore } from '@/store/modules/user';
/** 回款台账可见角色标识 */
const PAYMENT_LEDGER_ROLE_KEY = 'CVP';
/** 项目类别9 = 合同订单(非实施项目),采购数据挂在实施项目的 project_id 上 */
const PROJECT_CATEGORY_CONTRACT_ORDER = '9';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const userStore = useUserStore();
const route = useRoute();
@ -769,24 +773,58 @@ const loadOrderAndContract = async () => {
}
};
/**
* 采购/采购物料清单按 erp_project_purchase.project_id实施项目查询
* 台账路由参数为合同订单行时需解析到关联的实施项目 ID不能直接用路由上的合同订单 project_id
*/
const resolvePurchaseBizProjectId = async (): Promise<string | number | undefined> => {
const o = orderInfo.value;
if (!o?.projectId) return undefined;
if (o.projectCategory !== PROJECT_CATEGORY_CONTRACT_ORDER) {
return o.projectId;
}
if (o.parentProjectId != null && String(o.parentProjectId) !== '') {
return o.parentProjectId;
}
const cid = o.contractId;
if (!cid) return undefined;
try {
const res = await listProjectInfoByContractId(cid);
const list = res.data ?? [];
const impl = list.find((p) => p.projectCategory !== PROJECT_CATEGORY_CONTRACT_ORDER);
if (impl?.projectId != null && String(impl.projectId) !== '') {
return impl.projectId;
}
} catch {
//
}
return undefined;
};
/** 加载采购信息 */
const loadPurchaseList = async () => {
const contractId = orderInfo.value?.contractId ?? contractInfo.value?.contractId;
if (!contractId && !projectId.value) {
if (!orderInfo.value) {
purchaseList.value = [];
purchaseTotal.value = 0;
purchaseMaterialList.value = [];
return;
}
// ID
purchaseQuery.relationId = contractId ? contractId : undefined;
const bizProjectId = await resolvePurchaseBizProjectId();
if (bizProjectId == null || bizProjectId === '') {
purchaseList.value = [];
purchaseTotal.value = 0;
purchaseMaterialList.value = [];
return;
}
purchaseQuery.projectId = bizProjectId;
purchaseQuery.relationId = undefined;
loadingPurchase.value = true;
try {
const [purchaseRes, materialRes] = await Promise.all([
listProjectPurchase({
...purchaseQuery
}),
getContractOrderPurchaseMaterialList(projectId.value as string | number)
getContractOrderPurchaseMaterialList(bizProjectId)
]);
purchaseList.value = purchaseRes.rows || [];
purchaseTotal.value = purchaseRes.total || 0;

@ -117,29 +117,24 @@
<dict-tag :options="shipping_bill_status" :value="scope.row.outStockBillStatus" />
</template>
</el-table-column>
<el-table-column label="发货状态" align="center" prop="shippingStatus" width="100" v-if="columns[8].visible">
<template #default="scope">
<dict-tag :options="shipping_status" :value="scope.row.shippingStatus" />
</template>
</el-table-column>
<el-table-column label="需到货确认" align="center" prop="needArrivalConfirm" width="100" v-if="columns[9].visible">
<el-table-column label="需到货确认" align="center" prop="needArrivalConfirm" width="100" v-if="columns[8].visible">
<template #default="scope">
<dict-tag :options="need_arrival_confirm" :value="scope.row.needArrivalConfirm" />
</template>
</el-table-column>
<el-table-column label="到货标识" align="center" prop="isAllReceiving" width="100" v-if="columns[10].visible">
<el-table-column label="到货标识" align="center" prop="isAllReceiving" width="100" v-if="columns[9].visible">
<template #default="scope">
<dict-tag :options="is_all_receiving" :value="scope.row.needArrivalConfirm === '1' ? scope.row.isAllReceiving : undefined" />
<span v-if="scope.row.needArrivalConfirm === '0'">-</span>
</template>
</el-table-column>
<el-table-column label="到货确认时间" align="center" prop="arrivalConfirmTime" width="160" v-if="columns[11].visible">
<el-table-column label="到货确认时间" align="center" prop="arrivalConfirmTime" width="160" v-if="columns[10].visible">
<template #default="scope">
<span>{{ proxy?.parseTime(scope.row.arrivalConfirmTime, '{y}-{m}-{d} {h}:{i}:{s}') || '-' }}</span>
</template>
</el-table-column>
<el-table-column label="到货确认人" align="center" prop="arrivalConfirmByName" width="110" show-overflow-tooltip v-if="columns[12].visible" />
<el-table-column label="收货单附件" align="center" min-width="160" v-if="columns[13].visible">
<el-table-column label="到货确认人" align="center" prop="arrivalConfirmByName" width="110" show-overflow-tooltip v-if="columns[11].visible" />
<el-table-column label="收货单附件" align="center" min-width="160" v-if="columns[12].visible">
<template #default="scope">
<template v-if="getArrivalReceiptOssIds(scope.row).length > 0">
<el-button
@ -156,17 +151,17 @@
<span v-else style="color: #999">暂无附件</span>
</template>
</el-table-column>
<el-table-column label="计划到货时间" align="center" prop="planArrivalTime" width="110" v-if="columns[14].visible">
<el-table-column label="计划到货时间" align="center" prop="planArrivalTime" width="110" v-if="columns[13].visible">
<template #default="scope">
<span>{{ proxy?.parseTime(scope.row.planArrivalTime, '{y}-{m}-{d}') }}</span>
</template>
</el-table-column>
<el-table-column label="实际发货时间" align="center" prop="shippingTime" width="110" v-if="columns[15].visible">
<el-table-column label="实际发货时间" align="center" prop="shippingTime" width="110" v-if="columns[14].visible">
<template #default="scope">
<span>{{ proxy?.parseTime(scope.row.shippingTime, '{y}-{m}-{d}') }}</span>
</template>
</el-table-column>
<el-table-column label="创建时间" align="center" prop="createTime" width="110" v-if="columns[16].visible">
<el-table-column label="创建时间" align="center" prop="createTime" width="110" v-if="columns[15].visible">
<template #default="scope">
<span>{{ proxy?.parseTime(scope.row.createTime, '{y}-{m}-{d}') }}</span>
</template>
@ -364,22 +359,21 @@ const dialog = reactive<DialogOption>({
//
const columns = ref<FieldOption[]>([
{ key: 0, label: '发货单号', visible: true },
{ key: 1, label: 'SAP订单号', visible: true },
{ key: 1, label: 'SAP订单号', visible: false },
{ key: 2, label: '发货类型', visible: true },
{ key: 3, label: '发货方式', visible: true },
{ key: 4, label: '项目名称', visible: false },
{ key: 5, label: '客户名称', visible: true },
{ key: 6, label: '供应商', visible: true },
{ key: 7, label: '发货单状态', visible: true },
{ key: 8, label: '发货状态', visible: true },
{ key: 9, label: '需到货确认', visible: true },
{ key: 10, label: '到货标识', visible: true },
{ key: 11, label: '到货确认时间', visible: true },
{ key: 12, label: '到货确认人', visible: false },
{ key: 13, label: '收货单附件', visible: true },
{ key: 14, label: '计划到货时间', visible: true },
{ key: 15, label: '实际发货时间', visible: true },
{ key: 16, label: '创建时间', visible: false }
{ key: 8, label: '需到货确认', visible: true },
{ key: 9, label: '到货标识', visible: true },
{ key: 10, label: '到货确认时间', visible: true },
{ key: 11, label: '到货确认人', visible: false },
{ key: 12, label: '收货单附件', visible: true },
{ key: 13, label: '计划到货时间', visible: true },
{ key: 14, label: '实际发货时间', visible: true },
{ key: 15, label: '创建时间', visible: false }
]);
const initFormData: WmsShippingBillForm = {

Loading…
Cancel
Save