You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1248 lines
48 KiB
Vue

<template>
<div class="p-2">
<!-- 审批按钮组件 -->
<el-card shadow="never" style="margin-top: 0">
<approvalButton
@submitForm="submitForm"
@approvalVerifyOpen="approvalVerifyOpen"
@handleApprovalRecord="handleApprovalRecord"
:buttonLoading="buttonLoading"
:id="form.shippingBillId"
:status="form.flowStatus"
:pageType="routeParams.type"
:mode="false"
/>
</el-card>
<!-- 全部到货确认区块仅到货确认节点允许申请人录入 -->
<el-card v-if="isArrivalConfirmApprover" shadow="never" style="margin-top: 10px; border: 1px solid #e6a23c">
<template #header>
<div style="text-align: left; font-weight: bold; font-size: 16px; color: #e6a23c">
<el-icon style="margin-right: 6px"><Warning /></el-icon>
</div>
</template>
<el-form :model="arrivalConfirmForm" label-width="120px" ref="arrivalConfirmFormRef">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="全部到货标识" prop="isAllReceiving" :rules="[{ required: true, message: '请选择全部到货标识', trigger: 'change' }]">
<el-radio-group v-model="arrivalConfirmForm.isAllReceiving">
<el-radio-button v-for="dict in is_all_receiving" :key="dict.value" :label="dict.value">
{{ dict.label }}
</el-radio-button>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item
label="收货单附件"
prop="arrivalReceiptOssId"
:rules="[{ required: arrivalConfirmForm.isAllReceiving === '0', message: '全部到货必须上传收货单', trigger: 'change' }]"
>
<fileUpload v-model="arrivalConfirmForm.arrivalReceiptOssId" :limit="5" />
</el-form-item>
</el-col>
</el-row>
</el-form>
</el-card>
<!-- 基本信息区域 -->
<el-card shadow="never" style="margin-top: 0">
<template #header>
<div style="text-align: left; font-weight: bold; font-size: 18px">基本信息</div>
</template>
<el-form ref="shippingBillFormRef" :model="form" :loading="buttonLoading" :disabled="mainFormDisabled" :rules="rules" label-width="120px">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="发货单号" prop="shippingCode">
<el-input v-model="form.shippingCode" placeholder="由系统自动生成" disabled />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="发货类型" prop="shippingType">
<el-select v-model="form.shippingType" placeholder="请选择发货类型" style="width: 100%">
<!-- <el-option label="普通发货" value="1" />
<el-option label="备件发货" value="2" />
<el-option label="物流发货" value="3" /> -->
<el-option v-for="dict in shipping_type" :key="dict.value" :label="dict.label" :value="dict.value" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="发货方式" prop="shippingMode">
<el-select v-model="form.shippingMode" placeholder="请选择发货方式" style="width: 100%">
<el-option v-for="dict in shipping_mode" :key="dict.value" :label="dict.label" :value="dict.value" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="绑定类型" prop="bindType">
<el-radio-group v-model="form.bindType">
<el-radio-button label="1">按项目</el-radio-button>
<el-radio-button label="2">按合同</el-radio-button>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="12" v-if="form.bindType === '1'">
<el-form-item label="项目" prop="projectId">
<el-input v-model="selectedProjectName" placeholder="请选择项目" readonly>
<template #suffix>
<el-icon style="cursor: pointer" @click="openProjectSelect">
<Search />
</el-icon>
</template>
</el-input>
</el-form-item>
</el-col>
<el-col :span="12" v-if="form.bindType === '1'">
<el-form-item label="项目编号" prop="projectCode">
<el-input v-model="form.projectCode" disabled placeholder="自动带入" />
</el-form-item>
</el-col>
<el-col :span="12" v-if="form.bindType === '2'">
<el-form-item label="合同" prop="contractId">
<el-input v-model="selectedContractName" placeholder="请选择合同" readonly>
<template #suffix>
<el-icon style="cursor: pointer" @click="openContractSelect">
<Search />
</el-icon>
</template>
</el-input>
</el-form-item>
</el-col>
<el-col :span="12" v-if="form.bindType === '2'">
<el-form-item label="合同编号" prop="contractCode">
<el-input v-model="form.contractCode" disabled placeholder="自动带入" />
</el-form-item>
</el-col>
<el-col :span="12" v-if="form.bindType === '2' && selectedSapOrderCode">
<el-form-item label="SAP订单号">
<el-input v-model="selectedSapOrderCode" disabled placeholder="自动带入" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="客户" prop="customerId">
<el-select v-model="form.customerId" placeholder="请选择客户" filterable style="width: 100%" @change="handleCustomerChange">
<el-option v-for="item in customerList" :key="item.customerId" :label="item.customerName" :value="item.customerId" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="客户联系人" prop="customerContactId">
<el-select
v-model="form.customerContactId"
placeholder="请先选择客户"
filterable
style="width: 100%"
@change="handleCustomerContactChange"
:disabled="!form.customerId"
>
<el-option v-for="item in customerContactList" :key="item.contactId" :label="item.contactName" :value="item.contactId" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="收货联系人" prop="receiverName">
<el-input v-model="form.receiverName" placeholder="请输入收货联系人" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="收货联系电话" prop="receiverPhone">
<el-input v-model="form.receiverPhone" placeholder="请输入收货联系电话" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="收货地址" prop="shippingAddress">
<el-input v-model="form.shippingAddress" placeholder="请输入收货地址" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="发货日期" prop="shippingTime">
<el-date-picker
v-model="form.shippingTime"
type="datetime"
value-format="YYYY-MM-DD HH:mm:ss"
placeholder="请选择发货日期"
style="width: 100%"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="计划到货时间" prop="planArrivalTime">
<el-date-picker
v-model="form.planArrivalTime"
type="datetime"
value-format="YYYY-MM-DD HH:mm:ss"
placeholder="请选择计划到货时间"
style="width: 100%"
/>
</el-form-item>
</el-col>
<el-col :span="12" v-if="form.shippingMode === '2'">
<el-form-item label="供应商" prop="supplierId">
<el-select v-model="form.supplierId" placeholder="请选择供应商" filterable style="width: 100%" @change="handleSupplierChange">
<el-option v-for="item in supplierList" :key="item.supplierId" :label="item.supplierName" :value="item.supplierId" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12" v-if="form.shippingMode === '1'">
<el-form-item label="发货仓库" prop="warehouseId">
<el-select v-model="form.warehouseId" placeholder="请选择仓库" filterable style="width: 100%" @change="handleWarehouseChange">
<el-option v-for="item in warehouseList" :key="item.warehouseId" :label="item.warehouseName" :value="item.warehouseId" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="发货说明" prop="directions">
<el-input v-model="form.directions" type="textarea" placeholder="请输入发货说明" :rows="2" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<el-form v-if="isArrivalConfirmApprover" ref="copyManagerFormRef" :model="form" label-width="120px">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="抄送人员" prop="tManagerId" :rules="copyManagerRules">
<el-select
v-model="form.tManagerId"
placeholder="请选择抄送人员"
:disabled="copyManagerDisabled"
clearable
filterable
multiple
style="width: 100%"
>
<el-option v-for="user in userList" :key="user.userId" :label="user.nickName" :value="user.userId" />
</el-select>
</el-form-item>
</el-col>
</el-row>
</el-form>
</el-card>
<!-- 物流信息区域 -->
<!-- <el-card shadow="never" style="margin-top: 0">
<template #header>
<div style="text-align: left; font-weight: bold; font-size: 18px">物流信息</div>
</template>
<el-form :model="form" :disabled="routeParams.type === 'view' || routeParams.type === 'approval'" label-width="120px">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="物流公司" prop="logisticsCompany">
<el-input v-model="form.logisticsCompany" placeholder="请输入物流公司" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="运单号" prop="trackingNo">
<el-input v-model="form.trackingNo" placeholder="请输入运单号" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="物流联系电话" prop="logisticsPhone">
<el-input v-model="form.logisticsPhone" placeholder="请输入物流联系电话" />
</el-form-item>
</el-col>
</el-row>
</el-form>
</el-card>-->
<!-- 发货明细区域 -->
<el-card shadow="never" style="margin-top: 0">
<template #header>
<el-row :gutter="10" type="flex" align="middle">
<el-col :span="1.5">
<span style="font-weight: bold; font-size: 18px">发货明细</span>
</el-col>
<el-col :span="3">
<!-- 物料来源切换 -->
<el-radio-group v-model="materialSourceType" size="small" @change="handleMaterialSourceChange" :disabled="detailFormDisabled">
<!-- <el-radio-button value="1">ERP物料</el-radio-button>-->
<el-radio-button value="2">WMS物料</el-radio-button>
</el-radio-group>
</el-col>
<el-col :span="1.5">
<el-button type="primary" icon="Plus" @click="handleAddDetail" v-if="canEditDetailFields"> </el-button>
</el-col>
</el-row>
</template>
<el-table :data="detailsList" v-loading="buttonLoading" border>
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column label="物料编码" align="center" prop="materialCode" min-width="120" />
<el-table-column label="物料名称" align="center" prop="materialName" min-width="150" />
<el-table-column label="规格型号" align="center" prop="materielSpecification" min-width="120" />
<el-table-column label="批次号" align="center" prop="batchNumber" width="120" />
<el-table-column label="发货数量" align="center" prop="shippingStockAmount" width="120">
<template #default="scope">
<el-input-number
v-model="scope.row.shippingStockAmount"
:min="0"
:precision="2"
size="small"
style="width: 100%"
@change="calculateTotalPrice(scope.row)"
:disabled="detailFormDisabled"
/>
</template>
</el-table-column>
<el-table-column label="单位" align="center" prop="unitName" width="80" />
<el-table-column label="单价" align="center" prop="unitPrice" width="100">
<template #default="scope">
<el-input-number
v-model="scope.row.unitPrice"
:min="0"
:precision="2"
:controls="false"
size="small"
style="width: 100%"
:disabled="detailFormDisabled"
@change="calculateTotalPrice(scope.row)"
/>
</template>
</el-table-column>
<el-table-column label="总价" align="center" prop="totalPrice" width="100">
<template #default="scope">
{{ scope.row.totalPrice ? Number(scope.row.totalPrice).toFixed(2) : '0.00' }}
</template>
</el-table-column>
<el-table-column label="备注" align="center" prop="remark" min-width="120">
<template #default="scope">
<el-input v-model="scope.row.remark" placeholder="备注" size="small" :disabled="detailFormDisabled" />
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="80" fixed="right" v-if="canEditDetailFields">
<template #default="scope">
<el-button link type="danger" icon="Delete" @click="handleDeleteDetail(scope.$index)"></el-button>
</template>
</el-table-column>
</el-table>
</el-card>
<!-- ERP物料选择对话框 -->
<SaleMaterialSelect ref="saleMaterialSelectRef" :multiple="true" @confirm-call-back="erpMaterialSelectCallBack" />
<!-- 项目选择对话框 -->
<ProjectSelect ref="projectSelectRef" :multiple="false" @confirm-call-back="projectInfoSelectCallBack" />
<!-- 合同选择对话框 -->
<el-dialog title="选择合同" v-model="contractDialog.visible" width="900px" append-to-body>
<el-form :model="contractQueryParams" :inline="true" label-width="100px">
<el-form-item label="合同编号">
<el-input
v-model="contractQueryParams.contractCode"
placeholder="请输入合同编号"
clearable
style="width: 200px"
@keyup.enter="getContractList"
/>
</el-form-item>
<el-form-item label="合同名称">
<el-input
v-model="contractQueryParams.contractName"
placeholder="请输入合同名称"
clearable
style="width: 200px"
@keyup.enter="getContractList"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="getContractList"></el-button>
<el-button icon="Refresh" @click="resetContractQuery"></el-button>
</el-form-item>
</el-form>
<el-table v-loading="contractLoading" :data="contractList" border @row-click="handleContractRowClick" max-height="400">
<el-table-column label="合同编号" align="center" prop="contractCode" min-width="120" />
<el-table-column label="合同名称" align="center" prop="contractName" min-width="180" show-overflow-tooltip />
<el-table-column label="合同总价" align="center" prop="totalPrice" min-width="120" />
</el-table>
<pagination
v-show="contractTotal > 0"
:total="contractTotal"
v-model:page="contractQueryParams.pageNum"
v-model:limit="contractQueryParams.pageSize"
@pagination="getContractList"
/>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitContractSelect"> </el-button>
<el-button @click="contractDialog.visible = false"> </el-button>
</div>
</template>
</el-dialog>
<!-- WMS物料选择对话框 -->
<el-dialog title="选择WMS物料" v-model="wmsMaterialDialog.visible" width="900px" append-to-body>
<el-form :model="wmsMaterialQueryParams" :inline="true" label-width="100px">
<el-form-item label="物料编码">
<el-input
v-model="wmsMaterialQueryParams.productCode"
placeholder="请输入物料编码"
clearable
style="width: 180px"
@keyup.enter="getWmsMaterialList"
/>
</el-form-item>
<el-form-item label="物料名称">
<el-input
v-model="wmsMaterialQueryParams.productName"
placeholder="请输入物料名称"
clearable
style="width: 180px"
@keyup.enter="getWmsMaterialList"
/>
</el-form-item>
<el-form-item label="仓库">
<el-select v-model="wmsMaterialQueryParams.warehouseId" placeholder="请选择仓库" clearable style="width: 180px">
<el-option v-for="item in warehouseList" :key="item.warehouseId" :label="item.warehouseName" :value="item.warehouseId" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="getWmsMaterialList"></el-button>
</el-form-item>
</el-form>
<el-table
ref="wmsMaterialTableRef"
:data="wmsMaterialList"
v-loading="wmsMaterialLoading"
border
@selection-change="handleWmsMaterialSelectionChange"
>
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="物料编码" align="center" prop="productCode" min-width="120" />
<el-table-column label="物料名称" align="center" prop="productName" min-width="150" />
<el-table-column label="规格型号" align="center" prop="productSpe" min-width="120" />
<el-table-column label="仓库" align="center" prop="warehouseName" width="100" />
<el-table-column label="批次号" align="center" prop="batchNumber" width="120" />
<el-table-column label="库存数量" align="center" prop="inventoryAmount" width="100" />
<el-table-column label="单价" align="center" prop="unitPrice" width="80" />
</el-table>
<pagination
v-show="wmsMaterialTotal > 0"
:total="wmsMaterialTotal"
v-model:page="wmsMaterialQueryParams.pageNum"
v-model:limit="wmsMaterialQueryParams.pageSize"
@pagination="getWmsMaterialList"
/>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="confirmWmsMaterialSelect"> </el-button>
<el-button @click="wmsMaterialDialog.visible = false"> </el-button>
</div>
</template>
</el-dialog>
<!-- 提交审批组件 -->
<submitVerify ref="submitVerifyRef" :task-variables="taskVariables" @submit-callback="submitCallback" />
<!-- 审批记录 -->
<approvalRecord ref="approvalRecordRef" />
</div>
</template>
<script setup name="WmsShippingBillEdit" lang="ts">
import { addWmsShippingBill, getWmsShippingBill, shippingBillSubmitAndFlowStart, updateWmsShippingBill } from '@/api/wms/wmsShippingBill';
import { WmsShippingBillForm } from '@/api/wms/wmsShippingBill/types';
import { useUserStore } from '@/store/modules/user';
import { getTask } from '@/api/workflow/task';
import type { FlowTaskVO } from '@/api/workflow/task/types';
import { WmsShippingDetailsForm } from '@/api/wms/wmsShippingDetails/types';
import { listInventoryDetails } from '@/api/wms/inventoryDetails';
import { InventoryDetailsQuery, InventoryDetailsVO } from '@/api/wms/inventoryDetails/types';
import { getWmsWarehouseInfoList } from '@/api/wms/warehouseInfo';
import { getCrmCustomerInfoList } from '@/api/oa/crm/customerInfo';
import { getCrmCustomerContactList } from '@/api/oa/crm/customerContact';
import type { CustomerContactVO } from '@/api/oa/crm/customerContact/types';
import { getCrmSupplierInfoList } from '@/api/oa/crm/crmSupplierInfo';
import { listUser } from '@/api/system/user';
import type { UserQuery } from '@/api/system/user/types';
import SaleMaterialSelect from '@/components/SaleMaterialSelect/index.vue';
import SubmitVerify from '@/components/Process/submitVerify.vue';
import ApprovalRecord from '@/components/Process/approvalRecord.vue';
import ApprovalButton from '@/components/Process/approvalButton.vue';
import ProjectSelect from '@/components/ProjectSelect/index.vue';
import type { ProjectInfoVO } from '@/api/oa/erp/projectInfo/types';
import { getProjectInfo } from '@/api/oa/erp/projectInfo';
import { getContractInfo, listContractInfo } from '@/api/oa/erp/contractInfo';
import type { ContractInfoQuery, ContractInfoVO } from '@/api/oa/erp/contractInfo/types';
import { FlowCodeEnum } from '@/enums/OAEnum';
import { Search, Warning } from '@element-plus/icons-vue';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const route = useRoute();
const router = useRouter();
const userStore = useUserStore();
// 路由参数
const routeParams = ref<Record<string, any>>({});
// 字典
const { shipping_mode, shipping_status, shipping_bill_status, material_source_type, shipping_type, is_all_receiving } = toRefs<any>(
proxy?.useDict('shipping_mode', 'shipping_status', 'shipping_bill_status', 'material_source_type', 'shipping_type', 'is_all_receiving')
);
const buttonLoading = ref(false);
const shippingBillFormRef = ref<ElFormInstance>();
const arrivalConfirmFormRef = ref<ElFormInstance>();
const copyManagerFormRef = ref<ElFormInstance>();
type ArrivalConfirmFormModel = {
shippingBillId: string | number;
isAllReceiving: string;
arrivalReceiptOssId: string;
};
// 到货确认表单数据
const arrivalConfirmForm = ref<ArrivalConfirmFormModel>({
shippingBillId: '',
isAllReceiving: '0',
arrivalReceiptOssId: ''
});
watch(
() => arrivalConfirmForm.value.isAllReceiving,
() => {
arrivalConfirmFormRef.value?.clearValidate(['arrivalReceiptOssId']);
}
);
const currentTask = ref<FlowTaskVO | null>(null);
const isArrivalConfirmTask = computed(() => currentTask.value?.nodeCode === 'arrival-confirm');
const isCurrentTaskBusinessMatched = computed(() => {
const taskBusinessId = String(currentTask.value?.businessId || '').trim();
const shippingBillId = String(form.value.shippingBillId || '').trim();
if (!taskBusinessId) {
// 为什么这样做:部分任务接口场景可能不回 businessId前端不能因为缺少辅助字段把到货确认入口误隐藏
return true;
}
return taskBusinessId === shippingBillId;
});
// 是否显示到货确认区块:审批模式 + 到货确认节点 + 当前用户是申请人
const isArrivalConfirmApprover = computed(() => {
return (
routeParams.value.type === 'approval' &&
String(form.value.needArrivalConfirm || '') === '1' &&
isArrivalConfirmTask.value &&
isCurrentTaskBusinessMatched.value &&
String(userStore.userId || '') === String(form.value.createBy || '')
);
});
const isViewMode = computed(() => routeParams.value.type === 'view');
const isDraftFlowStatus = computed(() => {
if (!form.value.shippingBillId) {
return true;
}
return String(form.value.flowStatus || '') === 'draft';
});
const canEditBusinessFields = computed(() => {
// 基础信息与明细仅允许草稿态维护,审批中任何节点都不能通过编辑页修改这些业务字段
return !isViewMode.value && routeParams.value.type !== 'approval' && isDraftFlowStatus.value;
});
const mainFormDisabled = computed(() => !canEditBusinessFields.value);
const canEditDetailFields = computed(() => canEditBusinessFields.value);
const detailFormDisabled = computed(() => !canEditDetailFields.value);
const validateCopyManager = (rule: any, value: unknown, callback: (error?: Error) => void) => {
if (!isArrivalConfirmApprover.value) {
callback();
return;
}
const selectedIds = toWorkflowUserIdArray(value);
if (!selectedIds || selectedIds.length === 0) {
callback(new Error('请选择抄送人员'));
return;
}
callback();
};
const copyManagerRules = [{ validator: validateCopyManager, trigger: 'change' }];
const canEditCopyManager = computed(() => {
if (isViewMode.value) {
return false;
}
// 抄送人员仅在到货确认节点由申请人选择
return isArrivalConfirmApprover.value;
});
const copyManagerDisabled = computed(() => !canEditCopyManager.value);
// 审批相关组件引用
const submitVerifyRef = ref<InstanceType<typeof SubmitVerify>>();
const approvalRecordRef = ref<InstanceType<typeof ApprovalRecord>>();
// 任务变量
const taskVariables = ref<Record<string, any>>({});
// 物料来源类型1-ERP物料2-WMS物料默认使用WMS物料
const materialSourceType = ref('2');
// 下拉数据源
const userList = ref<any[]>([]);
const customerList = ref<any[]>([]);
const supplierList = ref<any[]>([]);
const warehouseList = ref<any[]>([]);
const customerContactList = ref<CustomerContactVO[]>([]); // 客户联系人列表
// 项目选择
const selectedProjectName = ref<string>('');
const projectSelectRef = ref<InstanceType<typeof ProjectSelect>>();
// 合同选择
const selectedContractName = ref<string>('');
const selectedSapOrderCode = ref<string>(''); // SAP订单号来自合同
const contractDialog = reactive({ visible: false });
const contractLoading = ref(false);
const contractList = ref<ContractInfoVO[]>([]);
const contractTotal = ref(0);
const contractQueryParams = ref<ContractInfoQuery>({
pageNum: 1,
pageSize: 10,
contractStatus: '3'
});
const selectedContract = ref<ContractInfoVO | null>(null);
// 发货明细列表
const detailsList = ref<WmsShippingDetailsForm[]>([]);
// ERP物料选择组件引用
const saleMaterialSelectRef = ref();
// WMS物料选择对话框
const wmsMaterialDialog = reactive({ visible: false });
const wmsMaterialLoading = ref(false);
const wmsMaterialList = ref<InventoryDetailsVO[]>([]);
const wmsMaterialTotal = ref(0);
const selectedWmsMaterials = ref<InventoryDetailsVO[]>([]);
const wmsMaterialQueryParams = ref<InventoryDetailsQuery>({
pageNum: 1,
pageSize: 10,
productCode: undefined,
productName: undefined,
warehouseId: undefined
});
// 表单初始数据
const initFormData: WmsShippingBillForm = {
shippingBillId: undefined,
shippingCode: undefined,
shippingType: undefined,
shippingMode: '1', // 默认公司仓库发货
bindType: '1',
projectId: undefined,
projectCode: undefined,
projectName: undefined,
customerId: undefined,
customerContactId: undefined,
customerName: undefined,
shippingAddress: undefined,
receiverName: undefined,
receiverPhone: undefined,
supplierId: undefined,
supplier: undefined,
contactUser: undefined,
contactNumber: undefined,
logisticsCompany: undefined,
trackingNo: undefined,
logisticsPhone: undefined,
directions: undefined,
planArrivalTime: undefined,
shippingTime: undefined,
warehouseId: undefined,
warehouseName: undefined,
outStockBillStatus: '1', // 默认暂存
flowStatus: 'draft',
shippingStatus: '1', // 默认待发货
remark: undefined,
detailsList: []
};
const validateProjectOrContract = (rule: any, value: any, callback: any) => {
if (form.value.bindType === '1' && !form.value.projectId) {
callback(new Error('项目不能为空'));
return;
}
callback();
};
const validateContractWhenBind = (rule: any, value: any, callback: any) => {
if (form.value.bindType === '2' && !form.value.contractId) {
callback(new Error('合同不能为空'));
return;
}
callback();
};
const data = reactive<{ form: WmsShippingBillForm; rules: any }>({
form: { ...initFormData },
rules: {
shippingMode: [{ required: true, message: '发货方式不能为空', trigger: 'change' }],
bindType: [{ required: true, message: '绑定类型不能为空', trigger: 'change' }],
projectId: [{ validator: validateProjectOrContract, trigger: 'change' }],
contractId: [{ validator: validateContractWhenBind, trigger: 'change' }],
customerId: [{ required: true, message: '客户不能为空', trigger: 'change' }]
}
});
const { form, rules } = toRefs(data);
const normalizeWorkflowUserIds = (value: unknown): string | undefined => {
if (Array.isArray(value)) {
const ids = value.map((item) => String(item ?? '').trim()).filter((item) => item.length > 0);
return ids.length > 0 ? ids.join(',') : undefined;
}
if (value === undefined || value === null) {
return undefined;
}
const text = String(value).trim();
return text.length > 0 ? text : undefined;
};
const toWorkflowUserIdArray = (value: unknown): (string | number)[] | undefined => {
const text = normalizeWorkflowUserIds(value);
if (!text) {
return undefined;
}
return text
.split(',')
.map((id) => id.trim())
.filter((id) => id.length > 0);
};
const buildFlowCopyVariables = async (submitData: WmsShippingBillForm) => {
let tManagerId = normalizeWorkflowUserIds((submitData as any).tManagerId);
const bindType = submitData.bindType;
// 先使用页面已有口径(合同负责人/项目抄送人),避免每次都发起远程查询
if (!tManagerId && bindType === '2') {
tManagerId = normalizeWorkflowUserIds((submitData as any).contractManagerId);
}
if (!tManagerId && bindType === '1') {
tManagerId = normalizeWorkflowUserIds((submitData as any).peopleId);
}
// 绑定合同时优先取合同负责人作为流程抄送人,保证流程变量完整
// 统一归一为 tManagerId流程节点只关心当前业务口径下的实际抄送人
if (bindType === '2' && !tManagerId && submitData.contractId) {
try {
const contractRes = await getContractInfo(submitData.contractId);
tManagerId = normalizeWorkflowUserIds((contractRes.data as any)?.contractManagerId);
} catch (error) {
console.error('加载合同负责人失败:', error);
}
}
// 绑定项目时兜底取项目负责人,防止流程变量缺失
if (bindType === '1' && !tManagerId && submitData.projectId) {
try {
const projectRes = await getProjectInfo(submitData.projectId);
tManagerId = normalizeWorkflowUserIds((projectRes.data as any)?.peopleId);
} catch (error) {
console.error('加载抄送人员失败:', error);
}
}
return { tManagerId };
};
const prepareTaskVariables = async () => {
if (!isArrivalConfirmApprover.value) {
taskVariables.value = {};
return true;
}
const valid = await copyManagerFormRef.value?.validate().catch(() => false);
if (!valid) {
return false;
}
const tManagerId = normalizeWorkflowUserIds((form.value as any).tManagerId);
if (!tManagerId) {
proxy?.$modal.msgError('请选择抄送人员');
return false;
}
// Why抄送人员不落库只在到货确认节点审批时作为流程变量传递
taskVariables.value = { tManagerId };
return true;
};
const syncCopyManagerSelection = async () => {
const current = normalizeWorkflowUserIds((form.value as any).tManagerId);
if (current) {
return;
}
const { tManagerId } = await buildFlowCopyVariables(form.value as WmsShippingBillForm);
if (tManagerId) {
// 编辑历史单据时回显抄送人员,保持与 orderActivate 一样“可见可改”
(form.value as any).tManagerId = toWorkflowUserIdArray(tManagerId);
}
};
const getContractList = async () => {
try {
contractLoading.value = true;
const res = await listContractInfo(contractQueryParams.value);
contractList.value = res.rows || [];
contractTotal.value = (res.total as number) || contractList.value.length;
} catch (error) {
console.error('查询合同列表失败:', error);
} finally {
contractLoading.value = false;
}
};
const resetContractQuery = () => {
contractQueryParams.value.contractCode = undefined;
contractQueryParams.value.contractName = undefined;
contractQueryParams.value.pageNum = 1;
getContractList();
};
const handleContractRowClick = (row: ContractInfoVO) => {
selectedContract.value = row;
};
const submitContractSelect = () => {
if (selectedContract.value) {
const contract = selectedContract.value;
form.value.contractId = contract.contractId as any;
form.value.contractCode = contract.contractCode || '';
form.value.contractName = contract.contractName || '';
const contractManagerId = normalizeWorkflowUserIds((contract as any).contractManagerId);
(form.value as any).contractManagerId = contractManagerId;
(form.value as any).tManagerId = toWorkflowUserIdArray(contractManagerId);
selectedContractName.value = contract.contractName || '';
// SAP订单号来自合同的 orderContractCode
selectedSapOrderCode.value = (contract as any).orderContractCode || '';
}
contractDialog.visible = false;
};
/** 打开项目选择弹窗 */
const openProjectSelect = () => {
if (!canEditBusinessFields.value) return;
projectSelectRef.value?.open();
};
/** 打开合同选择弹窗 */
const openContractSelect = () => {
if (!canEditBusinessFields.value) return;
contractDialog.visible = true;
contractQueryParams.value.pageNum = 1;
getContractList();
};
/** 项目选择回调 */
const projectInfoSelectCallBack = (data: ProjectInfoVO[]) => {
if (data && data.length > 0) {
const project = data[0];
form.value.projectId = project.projectId;
form.value.projectCode = project.projectCode || '';
form.value.projectName = project.projectName || '';
const peopleId = normalizeWorkflowUserIds((project as any).peopleId);
(form.value as any).peopleId = peopleId;
(form.value as any).tManagerId = toWorkflowUserIdArray(peopleId);
selectedProjectName.value = project.projectName || '';
// 如果项目有关联客户,自动带入
if (project.customerId) {
form.value.customerId = project.customerId;
handleCustomerChange(project.customerId);
}
}
};
/** 客户选择变化 - 加载对应的客户联系人列表 */
const handleCustomerChange = async (customerId: any) => {
const customer = customerList.value.find((c: any) => c.customerId === customerId);
if (customer) {
form.value.customerName = customer.customerName;
// 默认使用CRM客户的详细地址作为收货地址用户仍可在界面上手动修改
form.value.shippingAddress = customer.detailedAddress;
}
// 清空已选联系人和相关信息
form.value.customerContactId = undefined;
form.value.receiverName = undefined;
form.value.receiverPhone = undefined;
customerContactList.value = [];
// 加载客户联系人列表
if (customerId) {
try {
const res = await getCrmCustomerContactList({ customerId });
customerContactList.value = res.data || [];
// 默认选择首要联系人firstFlag='1'或1若无则选择第一个
let defaultContact = customerContactList.value.find((c: CustomerContactVO) => c.firstFlag === '1' || c.firstFlag === (1 as any));
if (!defaultContact && customerContactList.value.length > 0) {
defaultContact = customerContactList.value[0];
}
if (defaultContact) {
form.value.customerContactId = defaultContact.contactId;
// 带出收货联系人信息
form.value.receiverName = defaultContact.contactName;
form.value.receiverPhone = defaultContact.phoneNumber;
}
} catch (error) {
console.error('加载客户联系人列表失败:', error);
}
}
};
/** 客户联系人选择变化 - 自动带出姓名和电话 */
const handleCustomerContactChange = (contactId: any) => {
const contact = customerContactList.value.find((c: CustomerContactVO) => c.contactId === contactId);
if (contact) {
form.value.receiverName = contact.contactName;
form.value.receiverPhone = contact.phoneNumber;
}
};
/** 供应商选择变化 */
const handleSupplierChange = (supplierId: any) => {
const supplier = supplierList.value.find((s) => s.supplierId === supplierId);
if (supplier) {
form.value.supplier = supplier.supplierName;
form.value.contactUser = supplier.contactPerson;
form.value.contactNumber = supplier.contactPhone;
}
};
/** 仓库选择变化 */
const handleWarehouseChange = (warehouseId: any) => {
const warehouse = warehouseList.value.find((w) => w.warehouseId === warehouseId);
if (warehouse) {
form.value.warehouseName = warehouse.warehouseName;
}
};
/** 物料来源切换 */
const handleMaterialSourceChange = () => {
// 切换物料来源时,可以选择是否清空已选物料
// 这里暂不清空,允许混合选择
};
/** 新增物料 */
const handleAddDetail = () => {
if (!canEditDetailFields.value) {
return;
}
if (materialSourceType.value === '1') {
// ERP物料选择
saleMaterialSelectRef.value?.openDialog();
} else {
// WMS物料选择
wmsMaterialDialog.visible = true;
getWmsMaterialList();
}
};
/** ERP物料选择回调 */
const erpMaterialSelectCallBack = (materials: any[]) => {
if (materials && materials.length > 0) {
materials.forEach((material) => {
// 检查是否已存在
const exists = detailsList.value.some((d) => d.materialSourceType === '1' && d.erpMaterialId === material.materialId);
if (!exists) {
detailsList.value.push({
shippingDetailsId: undefined,
shippingBillId: form.value.shippingBillId,
materialSourceType: '1', // ERP物料
erpMaterialId: material.materialId,
wmsMaterialId: undefined,
materialCode: material.materialCode,
materialName: material.materialName,
materielSpecification: material.specificationModel,
batchNumber: undefined,
unitPrice: material.unitPrice || 0,
shippingStockAmount: 1,
unitId: material.unitId,
unitName: material.unitName,
totalPrice: material.unitPrice || 0,
remark: undefined
});
}
});
}
};
/** 获取WMS物料列表 */
const getWmsMaterialList = async () => {
wmsMaterialLoading.value = true;
try {
const res = await listInventoryDetails(wmsMaterialQueryParams.value);
wmsMaterialList.value = res.rows;
wmsMaterialTotal.value = res.total;
} finally {
wmsMaterialLoading.value = false;
}
};
/** WMS物料选择变化 */
const handleWmsMaterialSelectionChange = (selection: InventoryDetailsVO[]) => {
selectedWmsMaterials.value = selection;
};
/** 确认WMS物料选择 */
const confirmWmsMaterialSelect = () => {
if (selectedWmsMaterials.value.length > 0) {
selectedWmsMaterials.value.forEach((material) => {
// 检查是否已存在
const exists = detailsList.value.some((d) => d.materialSourceType === '2' && d.wmsMaterialId === material.inventoryDetailsId);
if (!exists) {
detailsList.value.push({
shippingDetailsId: undefined,
shippingBillId: form.value.shippingBillId,
materialSourceType: '2', // WMS物料
erpMaterialId: undefined,
wmsMaterialId: material.inventoryDetailsId,
warehouseId: material.warehouseId,
materielId: material.materielId,
materialCode: material.productCode,
materialName: material.productName,
materielSpecification: material.productSpe,
batchNumber: material.batchNumber,
unitPrice: material.unitPrice || 0,
shippingStockAmount: 1,
unitId: material.unitId,
unitName: material.unitName,
totalPrice: material.unitPrice || 0,
remark: undefined
});
}
});
}
wmsMaterialDialog.visible = false;
};
/** 删除明细 */
const handleDeleteDetail = (index: number) => {
if (!canEditDetailFields.value) {
return;
}
detailsList.value.splice(index, 1);
};
/** 计算总价 */
const calculateTotalPrice = (row: WmsShippingDetailsForm) => {
row.totalPrice = (row.unitPrice || 0) * (row.shippingStockAmount || 0);
};
/** 提交表单 */
const submitForm = async (status: string, mode: boolean) => {
try {
await shippingBillFormRef.value?.validate();
buttonLoading.value = true;
// 将明细列表设置到表单
form.value.detailsList = detailsList.value;
// 准备提交数据
const submitData = { ...form.value };
if (status !== 'draft') {
// 提交流程:设置流程编码与变量,驱动审批流
// 提交审批 - 后端发起流程模式
submitData.flowCode = FlowCodeEnum.SHIPPING_BILL_CODE;
// 流程变量
submitData.variables = {
shippingBillId: submitData.shippingBillId,
shippingCode: submitData.shippingCode,
projectName: submitData.projectName,
customerName: submitData.customerName
};
// 流程实例业务扩展字段
submitData.bizExt = {
businessTitle: '发货单审批',
businessCode: submitData.shippingCode
};
submitData.outStockBillStatus = '2'; // 审批中
submitData.flowStatus = 'waiting';
// 调用提交审批接口
const res = await shippingBillSubmitAndFlowStart(submitData);
form.value = res.data;
proxy?.$modal.msgSuccess('提交成功');
} else {
// 暂存仅做草稿保存,不触发流程
// 暂存
submitData.outStockBillStatus = '1';
submitData.flowStatus = 'draft';
if (submitData.shippingBillId) {
await updateWmsShippingBill(submitData);
} else {
await addWmsShippingBill(submitData);
}
proxy?.$modal.msgSuccess('暂存成功');
}
proxy?.$tab.closePage();
router.go(-1);
} catch (error) {
console.error('提交失败:', error);
} finally {
buttonLoading.value = false;
}
};
/** 加载下拉数据 */
const loadSelectOptions = async () => {
try {
const userQuery: UserQuery = { pageNum: 1, pageSize: 9999 };
const [userRes, customerRes, supplierRes, warehouseRes] = await Promise.all([
listUser(userQuery),
getCrmCustomerInfoList(null),
getCrmSupplierInfoList(null),
getWmsWarehouseInfoList(null)
]);
userList.value = userRes.rows || [];
customerList.value = customerRes.data || [];
supplierList.value = supplierRes.data || [];
warehouseList.value = warehouseRes.data || [];
} catch (error) {
console.error('加载下拉数据失败:', error);
}
};
/** 加载表单数据 */
const loadFormData = async (id: string | number) => {
try {
const res = await getWmsShippingBill(id);
Object.assign(form.value, res.data);
await syncCopyManagerSelection();
arrivalConfirmForm.value.shippingBillId = res.data.shippingBillId;
arrivalConfirmForm.value.isAllReceiving = res.data.isAllReceiving || '0';
arrivalConfirmForm.value.arrivalReceiptOssId = res.data.arrivalReceiptOssId || '';
selectedProjectName.value = form.value.projectName || '';
selectedContractName.value = form.value.contractName || '';
// SAP订单号来自后端联查合同表
selectedSapOrderCode.value = (res.data as any).orderContractCode || '';
// 加载明细列表
if (res.data.itemsVo && res.data.itemsVo.length > 0) {
detailsList.value = res.data.itemsVo.map((item: any) => ({
shippingDetailsId: item.shippingDetailsId,
shippingBillId: item.shippingBillId,
materialSourceType: item.materialSourceType,
erpMaterialId: item.erpMaterialId,
wmsMaterialId: item.wmsMaterialId,
warehouseId: item.warehouseId,
materielId: item.materielId,
materialCode: item.materialCode,
materialName: item.materialName,
materielSpecification: item.materielSpecification,
batchNumber: item.batchNumber,
unitPrice: item.unitPrice,
shippingStockAmount: item.shippingStockAmount,
unitId: item.unitId,
unitName: item.unitName,
totalPrice: item.totalPrice,
remark: item.remark
}));
}
// 编辑模式下如果有客户ID加载客户联系人列表
if (form.value.customerId) {
try {
const contactRes = await getCrmCustomerContactList({ customerId: form.value.customerId });
customerContactList.value = contactRes.data || [];
} catch (error) {
console.error('加载客户联系人列表失败:', error);
}
}
} catch (error) {
console.error('加载表单数据失败:', error);
}
};
const loadCurrentTask = async () => {
if (routeParams.value.type !== 'approval' || !routeParams.value.taskId) {
currentTask.value = null;
return true;
}
try {
const res = await getTask(String(routeParams.value.taskId));
currentTask.value = res.data;
return !!currentTask.value;
} catch (error) {
currentTask.value = null;
console.error('加载当前审批任务失败:', error);
return false;
}
};
// 审批记录
const handleApprovalRecord = () => {
approvalRecordRef.value?.init(form.value.shippingBillId);
};
// 提交回调
const submitCallback = async () => {
if (isArrivalConfirmApprover.value) {
const valid = await arrivalConfirmFormRef.value?.validate().catch(() => false);
if (!valid) return;
const saveData: WmsShippingBillForm = {
shippingBillId: form.value.shippingBillId,
projectId: form.value.projectId,
contractId: form.value.contractId,
isAllReceiving: arrivalConfirmForm.value.isAllReceiving,
arrivalReceiptOssId: arrivalConfirmForm.value.arrivalReceiptOssId,
// 到货确认存储时同步维护发货状态,避免审批提交后状态与到货标识不一致
shippingStatus: arrivalConfirmForm.value.isAllReceiving === '0' ? '3' : '2'
};
await updateWmsShippingBill(saveData);
form.value.isAllReceiving = saveData.isAllReceiving;
form.value.arrivalReceiptOssId = saveData.arrivalReceiptOssId;
form.value.shippingStatus = saveData.shippingStatus;
}
await proxy?.$tab.closePage(route);
router.go(-1);
};
// 审批(到货确认节点先校验,再打开审批弹窗)
const approvalVerifyOpen = async () => {
if (routeParams.value.type === 'approval' && routeParams.value.taskId) {
const taskLoaded = await loadCurrentTask();
if (!taskLoaded || !currentTask.value) {
proxy?.$modal.msgError('当前任务信息加载失败,请刷新后重试');
return;
}
}
if (isArrivalConfirmApprover.value) {
// 到货确认节点:先校验到货确认表单,业务字段在 submitCallback 中统一存储
const valid = await arrivalConfirmFormRef.value?.validate().catch(() => false);
if (!valid) return;
}
const prepared = await prepareTaskVariables();
if (!prepared) return;
await submitVerifyRef.value?.openDialog(routeParams.value.taskId);
};
onMounted(async () => {
nextTick(async () => {
routeParams.value = route.query;
// 页面初始化先展示加载态,防止首屏空白
proxy?.$modal.loading('正在加载数据,请稍后...');
await loadSelectOptions();
const id = routeParams.value.id as string | number;
if (id && (routeParams.value.type === 'update' || routeParams.value.type === 'view' || routeParams.value.type === 'approval')) {
// 编辑/查看/审批场景加载后端数据,避免覆盖草稿
await loadFormData(id);
}
await loadCurrentTask();
proxy?.$modal.closeLoading();
});
});
</script>
<style scoped>
.card-title {
font-weight: bold;
font-size: 18px;
}
</style>