From 99c00652cf3895009eacdb05025e02967a670367 Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期三, 05 八月 2026 11:36:00 +0800
Subject: [PATCH] feat(mes): 实现生产报工批次属性管理和SRM投标优化功能
---
src/views/crm/product/components/edit-table.vue | 29 ++
src/views/mes/md/item/type/components/select.vue | 18 -
src/views/mes/pro/feedback/modules/form.vue | 46 ++++
src/views/mes/pro/feedback/modules/batch-input-dialog.vue | 177 +++++++++++++++++++
src/views/crm/product/components/data.ts | 26 ++
src/views/srm/tender/detail.vue | 42 ++++
src/views/mes/workbench/components/tabs/FeedbackHistoryTab.vue | 40 +++
src/views/mes/pro/feedback/index.vue | 37 ----
src/views/erp/purchase/request/index.vue | 4
src/views/mes/workbench/components/tabs/FeedbackTab.vue | 6
src/api/crm/business/index.ts | 13
src/views/basicData/mdm/data.ts | 45 ++++
src/api/mes/pro/feedback/index.ts | 33 +++
src/api/mdm/warehouse/index.ts | 19 ++
14 files changed, 438 insertions(+), 97 deletions(-)
diff --git a/src/api/crm/business/index.ts b/src/api/crm/business/index.ts
index f30b5d3..8f6b241 100644
--- a/src/api/crm/business/index.ts
+++ b/src/api/crm/business/index.ts
@@ -38,11 +38,14 @@
/** 鍟嗘満浜у搧淇℃伅 */
export interface BusinessProduct {
id: number;
- productId: number;
- productName: string;
- productNo: string;
- productUnit: number;
- productPrice: number;
+ itemId: number;
+ itemName: string;
+ itemCode: string;
+ barCode: string;
+ itemSpecification: string;
+ itemUnitId: number;
+ itemUnitName: string;
+ itemPrice: number;
businessPrice: number;
count: number;
totalPrice: number;
diff --git a/src/api/mdm/warehouse/index.ts b/src/api/mdm/warehouse/index.ts
new file mode 100644
index 0000000..1ff141f
--- /dev/null
+++ b/src/api/mdm/warehouse/index.ts
@@ -0,0 +1,19 @@
+import { requestClient } from '#/api/request';
+
+export namespace MdmWarehouseApi {
+ export interface Warehouse {
+ id?: number;
+ code?: string;
+ name?: string;
+ address?: string;
+ type?: number;
+ status?: number;
+ }
+}
+
+/** 鏌ヨ鍏ㄩ儴鍚敤鐨勪粨搴撳垪琛� */
+export function getWarehouseSimpleList() {
+ return requestClient.get<MdmWarehouseApi.Warehouse[]>(
+ '/mdm/warehouse/list-all-simple',
+ );
+}
diff --git a/src/api/mes/pro/feedback/index.ts b/src/api/mes/pro/feedback/index.ts
index 78eecc3..7666071 100644
--- a/src/api/mes/pro/feedback/index.ts
+++ b/src/api/mes/pro/feedback/index.ts
@@ -110,8 +110,37 @@
}
/** 瀹℃壒鐢熶骇鎶ュ伐锛堣繑鍥炴槸鍚﹀凡瀹℃壒瀹屾垚锛� */
-export function approveFeedback(id: number) {
- return requestClient.put<boolean>(`/mes/pro/feedback/approve?id=${id}`);
+export function approveFeedback(data: { id: number; [key: string]: unknown }) {
+ return requestClient.put<boolean>(`/mes/pro/feedback/approve`, data);
+}
+
+/** 鎵规闇�姹傛鏌ュ搷搴� */
+export interface BatchCheckResp {
+ needBatchInput?: boolean;
+ isBatchManaged?: boolean;
+ produceDateFlag?: boolean;
+ expireDateFlag?: boolean;
+ receiptDateFlag?: boolean;
+ vendorFlag?: boolean;
+ clientFlag?: boolean;
+ salesOrderCodeFlag?: boolean;
+ purchaseOrderCodeFlag?: boolean;
+ workOrderFlag?: boolean;
+ taskFlag?: boolean;
+ workstationFlag?: boolean;
+ toolFlag?: boolean;
+ moldFlag?: boolean;
+ lotNumberFlag?: boolean;
+ qualityStatusFlag?: boolean;
+ autoFilledFields?: string[];
+}
+
+/** 妫�鏌ユ姤宸ュ鎵瑰墠鏄惁闇�瑕佸~鍐欐壒娆″睘鎬� */
+export function getBatchRequirements(feedbackId: number) {
+ return requestClient.get<BatchCheckResp>(
+ `/mes/pro/feedback/batch-requirements`,
+ { params: { id: feedbackId } },
+ );
}
/** 蹇�熸姤宸ヨ姹� */
diff --git a/src/views/basicData/mdm/data.ts b/src/views/basicData/mdm/data.ts
index 953b9f2..a9d6ca9 100644
--- a/src/views/basicData/mdm/data.ts
+++ b/src/views/basicData/mdm/data.ts
@@ -11,12 +11,17 @@
import { MesAutoCodeRuleCode } from '@vben/constants';
import { getItemTypeSimpleList } from '#/api/mes/md/item/type';
import { getUnitPage } from '#/api/mdm/unit';
+import { getWarehouseSimpleList } from '#/api/mes/wm/warehouse';
import { Button } from 'ant-design-vue';
// 缂撳瓨鍗曚綅鍒楄〃
let unitListCache: any[] | null = null;
let unitListPromise: Promise<any[]> | null = null;
+
+// 缂撳瓨浠撳簱鍒楄〃
+let warehouseListCache: any[] | null = null;
+let warehouseListPromise: Promise<any[]> | null = null;
/** 灏嗘墎骞冲垎绫诲垪琛ㄨ浆涓� TreeSelect 鎵�闇�鐨勬爲褰㈢粨鏋� */
export function buildCategoryTree(list: any[]): any[] {
@@ -53,9 +58,27 @@
return unitListPromise;
}
+/** 鑾峰彇浠撳簱鍒楄〃锛堝甫缂撳瓨锛� */
+async function getWarehouseListCached() {
+ if (warehouseListCache) {
+ return warehouseListCache;
+ }
+ if (warehouseListPromise) {
+ return warehouseListPromise;
+ }
+ warehouseListPromise = getWarehouseSimpleList().then((res) => {
+ warehouseListCache = res || [];
+ return warehouseListCache;
+ }).finally(() => {
+ warehouseListPromise = null;
+ });
+ return warehouseListPromise;
+}
+
/** 娓呴櫎鍗曚綅鍒楄〃缂撳瓨 */
export function clearUnitListCache() {
unitListCache = null;
+ warehouseListCache = null;
}
/** 鏂板/淇敼鐨勮〃鍗� */
@@ -188,11 +211,29 @@
},
},
{
- component: 'Input',
+ component: 'ApiSelect',
fieldName: 'warehouseId',
label: '榛樿浠撳簱',
componentProps: {
- placeholder: '璇疯緭鍏ラ粯璁や粨搴揑D',
+ placeholder: '璇烽�夋嫨榛樿浠撳簱',
+ allowClear: true,
+ api: getWarehouseListCached,
+ labelField: 'name',
+ valueField: 'id',
+ onChange: async (value: number | undefined) => {
+ const code = value
+ ? ((await getWarehouseListCached()).find((w: any) => w.id === value)?.code || '')
+ : '';
+ formApi?.setFieldValue('warehouseCode', code);
+ },
+ },
+ },
+ {
+ component: 'Input',
+ fieldName: 'warehouseCode',
+ dependencies: {
+ triggerFields: [''],
+ show: () => false,
},
},
{
diff --git a/src/views/crm/product/components/data.ts b/src/views/crm/product/components/data.ts
index 75d25ba..462508a 100644
--- a/src/views/crm/product/components/data.ts
+++ b/src/views/crm/product/components/data.ts
@@ -14,7 +14,7 @@
title: '鐗╂枡缂栫爜',
},
{
- field: 'itemBarCode',
+ field: 'barCode',
title: '鐗╂枡鏉$爜',
},
{
@@ -52,7 +52,22 @@
}
/** 浜у搧缂栬緫琛ㄦ牸鐨勫垪瀹氫箟 */
-export function useProductEditTableColumns(): VxeTableGridOptions['columns'] {
+export function useProductEditTableColumns(
+ showBusinessPrice: boolean = false,
+): VxeTableGridOptions['columns'] {
+ const priceColumn = showBusinessPrice
+ ? {
+ field: 'businessPrice',
+ title: '鍟嗘満浠锋牸锛堝厓锛�',
+ minWidth: 100,
+ slots: { default: 'businessPrice' },
+ }
+ : {
+ field: 'contractPrice',
+ title: '鍚堝悓浠凤紙鍏冿級',
+ minWidth: 100,
+ slots: { default: 'contractPrice' },
+ };
return [
{ type: 'seq', title: '搴忓彿', minWidth: 50 },
{
@@ -87,12 +102,7 @@
minWidth: 100,
formatter: 'formatAmount2',
},
- {
- field: 'contractPrice',
- title: '鍚堝悓浠凤紙鍏冿級',
- minWidth: 100,
- slots: { default: 'contractPrice' },
- },
+ priceColumn,
{
field: 'count',
title: '鏁伴噺',
diff --git a/src/views/crm/product/components/edit-table.vue b/src/views/crm/product/components/edit-table.vue
index 113080c..f7cb97a 100644
--- a/src/views/crm/product/components/edit-table.vue
+++ b/src/views/crm/product/components/edit-table.vue
@@ -3,7 +3,7 @@
import type { CrmContractApi } from '#/api/crm/contract';
import type { MdmItemApi } from '#/api/mdm/item';
-import { nextTick, ref, watch } from 'vue';
+import { computed, nextTick, ref, watch } from 'vue';
import { erpPriceMultiply } from '@vben/utils';
@@ -24,6 +24,12 @@
}>();
const emit = defineEmits(['update:products']);
+
+/** 鏄惁涓哄晢鏈虹被鍨� */
+const isBusiness = computed(() => props.bizType === BizTypeEnum.CRM_BUSINESS);
+
+/** 褰撳墠涓氬姟绫诲瀷瀵瑰簲鐨勪环鏍煎瓧娈靛悕 */
+const priceField = computed(() => isBusiness.value ? 'businessPrice' : 'contractPrice');
/** 琛ㄦ牸鍐呴儴鏁版嵁 */
const tableData = ref<any[]>([]);
@@ -46,28 +52,29 @@
row.itemId = product.id;
row.itemName = product.name;
row.itemCode = product.code;
- row.itemBarCode = product.barCode;
+ row.barCode = product.barCode;
+ row.itemUnitId = product.unitMeasureId;
row.itemUnitName = product.unitMeasureName;
row.itemUnitName2 = product.unitMeasureName2;
row.itemUnitName3 = product.unitMeasureName3;
row.itemSpecification = product.specification;
row.itemPrice = product.salesPrice;
- row.contractPrice = product.salesPrice;
+ row[priceField.value] = product.salesPrice;
row.count = row.count || 1;
- row.totalPrice = erpPriceMultiply(row.contractPrice, row.count) ?? 0;
+ row.totalPrice = erpPriceMultiply(row[priceField.value], row.count) ?? 0;
handleUpdateValue(row);
}
/** 閲戦鍙樺姩鏃堕噸鏂拌绠楀悎璁� */
function handlePriceChange(row: any) {
- row.totalPrice = erpPriceMultiply(row.contractPrice, row.count) ?? 0;
+ row.totalPrice = erpPriceMultiply(row[priceField.value], row.count) ?? 0;
handleUpdateValue(row);
}
/** 灏嗘渶鏂版暟鎹啓鍥炲苟閫氱煡鐖剁粍浠� */
function handleUpdateValue(row: any) {
const index = tableData.value.findIndex((item) => item.id === row.id);
- row.totalPrice = erpPriceMultiply(row.contractPrice, row.count) ?? 0;
+ row.totalPrice = erpPriceMultiply(row[priceField.value], row.count) ?? 0;
if (index === -1) {
row.id = tableData.value.length + 1;
tableData.value.push(row);
@@ -83,7 +90,7 @@
trigger: 'click',
mode: 'cell',
},
- columns: useProductEditTableColumns(),
+ columns: useProductEditTableColumns(isBusiness.value),
data: tableData.value,
border: true,
showOverflow: true,
@@ -137,6 +144,14 @@
@change="handlePriceChange(row)"
/>
</template>
+ <template #businessPrice="{ row }">
+ <InputNumber
+ v-model:value="row.businessPrice"
+ :min="0.001"
+ :precision="2"
+ @change="handlePriceChange(row)"
+ />
+ </template>
<template #count="{ row }">
<InputNumber
v-model:value="row.count"
diff --git a/src/views/erp/purchase/request/index.vue b/src/views/erp/purchase/request/index.vue
index 27336e9..cd97ba8 100644
--- a/src/views/erp/purchase/request/index.vue
+++ b/src/views/erp/purchase/request/index.vue
@@ -122,6 +122,10 @@
/** 鏌ョ湅瀹℃壒璇︽儏 */
function handleViewProcess(row: ErpPurchaseRequestApi.PurchaseRequest) {
+ if (!row.processInstanceId) {
+ message.warning('璇ョ敵璇峰皻鏈彁浜ゅ鎵�');
+ return;
+ }
router.push({ name: 'BpmProcessInstanceDetail', query: { id: row.processInstanceId } });
}
diff --git a/src/views/mes/md/item/type/components/select.vue b/src/views/mes/md/item/type/components/select.vue
index 11cac62..0f5c0d2 100644
--- a/src/views/mes/md/item/type/components/select.vue
+++ b/src/views/mes/md/item/type/components/select.vue
@@ -48,22 +48,6 @@
},
});
-/** 閫掑綊灏嗘湁瀛愯妭鐐圭殑鍒嗘敮鑺傜偣鏍囪涓� disabled */
-function markParentsDisabled(
- nodes: MesMdItemTypeApi.ItemType[],
-): ItemTypeNode[] {
- return nodes.map((node) => {
- const children = node.children?.length
- ? markParentsDisabled(node.children)
- : undefined;
- return {
- ...node,
- children,
- disabled: Boolean(children?.length),
- };
- });
-}
-
/** 鏍规嵁褰撳墠鍊煎悓姝� tooltip 灞曠ず鐨勫垎绫昏鎯� */
function syncSelectedItem(value: number | undefined) {
selectedItem.value =
@@ -81,7 +65,7 @@
/** 鏌ヨ鐗╂枡鍒嗙被鏍� */
async function getItemTypeTree() {
allList.value = await getItemTypeSimpleList();
- itemTypeTree.value = markParentsDisabled(handleTree(allList.value));
+ itemTypeTree.value = handleTree(allList.value) as ItemTypeNode[];
syncSelectedItem(props.modelValue);
}
diff --git a/src/views/mes/pro/feedback/index.vue b/src/views/mes/pro/feedback/index.vue
index b60997e..adb6ae1 100644
--- a/src/views/mes/pro/feedback/index.vue
+++ b/src/views/mes/pro/feedback/index.vue
@@ -22,8 +22,6 @@
import { useGridColumns, useGridFormSchema } from './data';
import Form from './modules/form.vue';
-import QuickFeedbackForm from '../workorder-center/modules/quick-feedback.vue';
-import BatchFeedbackForm from './modules/batch-feedback.vue';
import ArchiveDetail from '#/views/mes/pd/archive/modules/detail.vue';
const userStore = useUserStore();
@@ -31,16 +29,6 @@
const [FormModal, formModalApi] = useVbenModal({
connectedComponent: Form,
- destroyOnClose: true,
-});
-
-const [QuickFeedbackModal, quickFeedbackModalApi] = useVbenModal({
- connectedComponent: QuickFeedbackForm,
- destroyOnClose: true,
-});
-
-const [BatchFeedbackModal, batchFeedbackModalApi] = useVbenModal({
- connectedComponent: BatchFeedbackForm,
destroyOnClose: true,
});
@@ -108,16 +96,6 @@
}
}
-/** 蹇�熸姤宸� */
-function handleQuickCreate() {
- quickFeedbackModalApi.setData({}).open();
-}
-
-/** 鎵归噺鎶ュ伐 */
-function handleBatchCreate() {
- batchFeedbackModalApi.setData({}).open();
-}
-
const [Grid, gridApi] = useVbenVxeGrid({
formOptions: { schema: useGridFormSchema() },
gridOptions: {
@@ -144,8 +122,6 @@
<template>
<Page auto-content-height>
<FormModal @success="handleRefresh" />
- <QuickFeedbackModal @success="handleRefresh" />
- <BatchFeedbackModal @success="handleRefresh" />
<Grid table-title="鐢熶骇鎶ュ伐鍒楄〃">
<template #toolbar-tools>
<TableAction
@@ -156,19 +132,6 @@
icon: ACTION_ICON.ADD,
auth: ['mes:pro-feedback:create'],
onClick: handleCreate,
- },
- {
- label: '蹇�熸姤宸�',
- type: 'primary',
- icon: ACTION_ICON.ADD,
- auth: ['mes:pro-feedback:create'],
- onClick: handleQuickCreate,
- },
- {
- label: '鎵归噺鎶ュ伐',
- type: 'primary',
- auth: ['mes:pro-feedback:create'],
- onClick: handleBatchCreate,
},
{
label: $t('ui.actionTitle.export'),
diff --git a/src/views/mes/pro/feedback/modules/batch-input-dialog.vue b/src/views/mes/pro/feedback/modules/batch-input-dialog.vue
new file mode 100644
index 0000000..922e79b
--- /dev/null
+++ b/src/views/mes/pro/feedback/modules/batch-input-dialog.vue
@@ -0,0 +1,177 @@
+<script lang="ts" setup>
+import type { BatchCheckResp } from '#/api/mes/pro/feedback';
+
+import { computed, ref } from 'vue';
+
+import { DICT_TYPE } from '@vben/constants';
+import { getDictOptions } from '@vben/hooks';
+import { useVbenModal } from '@vben/common-ui';
+
+import { useVbenForm } from '#/adapter/form';
+
+const emit = defineEmits<{
+ confirm: [data: Record<string, unknown>];
+}>();
+
+const checkResult = ref<BatchCheckResp>({});
+const batchData = ref<Record<string, unknown>>({});
+
+const skipFieldSet = computed<Set<string>>(() => {
+ const autoFields = checkResult.value.autoFilledFields ?? [];
+ return new Set([...autoFields, 'workOrderId', 'taskId', 'workstationId', 'produceDate']);
+});
+
+const needTool = computed(() => checkResult.value.toolFlag && !skipFieldSet.value.has('toolId'));
+const needMold = computed(() => checkResult.value.moldFlag && !skipFieldSet.value.has('moldId'));
+const needQualityStatus = computed(() => checkResult.value.qualityStatusFlag && !skipFieldSet.value.has('qualityStatus'));
+const needExpireDate = computed(() => checkResult.value.expireDateFlag && !skipFieldSet.value.has('expireDate'));
+const needReceiptDate = computed(() => checkResult.value.receiptDateFlag && !skipFieldSet.value.has('receiptDate'));
+const needLotNumber = computed(() => checkResult.value.lotNumberFlag && !skipFieldSet.value.has('lotNumber'));
+const needVendor = computed(() => checkResult.value.vendorFlag && !skipFieldSet.value.has('vendorId'));
+const needClient = computed(() => checkResult.value.clientFlag && !skipFieldSet.value.has('clientId'));
+const needSalesOrderCode = computed(() => checkResult.value.salesOrderCodeFlag && !skipFieldSet.value.has('salesOrderCode'));
+const needPurchaseOrderCode = computed(() => checkResult.value.purchaseOrderCodeFlag && !skipFieldSet.value.has('purchaseOrderCode'));
+
+const [Form, formApi] = useVbenForm({
+ commonConfig: {
+ componentProps: { class: 'w-full' },
+ formItemClass: 'col-span-1',
+ labelWidth: 110,
+ },
+ layout: 'horizontal',
+ schema: [],
+ showDefaultActions: false,
+ wrapperClass: 'grid-cols-2',
+});
+
+const [Modal, modalApi] = useVbenModal({
+ onConfirm: async () => {
+ const { valid } = await formApi.validate();
+ if (!valid) return;
+ const values = await formApi.getValues();
+ emit('confirm', values as Record<string, unknown>);
+ await modalApi.close();
+ },
+ async onOpenChange(isOpen: boolean) {
+ if (!isOpen) return;
+ const data = modalApi.getData<{ checkResult: BatchCheckResp }>();
+ checkResult.value = data.checkResult ?? {};
+ batchData.value = {};
+
+ // 鍔ㄦ�佹瀯寤鸿〃鍗� schema
+ const schemas: any[] = [];
+ if (needExpireDate.value) {
+ schemas.push({
+ fieldName: 'expireDate',
+ label: '鏈夋晥鏈�',
+ component: 'DatePicker',
+ componentProps: { class: 'w-full', format: 'YYYY-MM-DD', valueFormat: 'YYYY-MM-DD HH:mm:ss', showTime: true, placeholder: '璇烽�夋嫨鏈夋晥鏈�' },
+ rules: 'required',
+ });
+ }
+ if (needReceiptDate.value) {
+ schemas.push({
+ fieldName: 'receiptDate',
+ label: '鍏ュ簱鏃ユ湡',
+ component: 'DatePicker',
+ componentProps: { class: 'w-full', format: 'YYYY-MM-DD', valueFormat: 'YYYY-MM-DD HH:mm:ss', showTime: true, placeholder: '璇烽�夋嫨鍏ュ簱鏃ユ湡' },
+ rules: 'required',
+ });
+ }
+ if (needLotNumber.value) {
+ schemas.push({
+ fieldName: 'lotNumber',
+ label: '鐢熶骇鎵瑰彿',
+ component: 'Input',
+ componentProps: { placeholder: '璇疯緭鍏ョ敓浜ф壒鍙�' },
+ rules: 'required',
+ });
+ }
+ if (needTool.value) {
+ schemas.push({
+ fieldName: 'toolId',
+ label: '宸ュ叿',
+ component: 'Input',
+ });
+ }
+ if (needMold.value) {
+ schemas.push({
+ fieldName: 'moldId',
+ label: '妯″叿',
+ component: 'Input',
+ });
+ }
+ if (needQualityStatus.value) {
+ schemas.push({
+ fieldName: 'qualityStatus',
+ label: '璐ㄩ噺鐘舵��',
+ component: 'Select',
+ componentProps: {
+ options: getDictOptions(DICT_TYPE.MES_WM_QUALITY_STATUS, 'number'),
+ placeholder: '璇烽�夋嫨璐ㄩ噺鐘舵��',
+ },
+ rules: 'required',
+ });
+ }
+ if (needVendor.value) {
+ schemas.push({
+ fieldName: 'vendorId',
+ label: '渚涘簲鍟�',
+ component: 'Input',
+ componentProps: { placeholder: '璇疯緭鍏ヤ緵搴斿晢ID' },
+ rules: 'required',
+ });
+ }
+ if (needClient.value) {
+ schemas.push({
+ fieldName: 'clientId',
+ label: '瀹㈡埛',
+ component: 'Input',
+ componentProps: { placeholder: '璇疯緭鍏ュ鎴稩D' },
+ rules: 'required',
+ });
+ }
+ if (needSalesOrderCode.value) {
+ schemas.push({
+ fieldName: 'salesOrderCode',
+ label: '閿�鍞鍗曠紪鍙�',
+ component: 'Input',
+ componentProps: { placeholder: '璇疯緭鍏ラ攢鍞鍗曠紪鍙�' },
+ rules: 'required',
+ });
+ }
+ if (needPurchaseOrderCode.value) {
+ schemas.push({
+ fieldName: 'purchaseOrderCode',
+ label: '閲囪喘璁㈠崟缂栧彿',
+ component: 'Input',
+ componentProps: { placeholder: '璇疯緭鍏ラ噰璐鍗曠紪鍙�' },
+ rules: 'required',
+ });
+ }
+
+ formApi.setState({ schema: schemas });
+ // 榛樿鍊�
+ const defaults: Record<string, unknown> = {};
+ if (needExpireDate.value) defaults.expireDate = undefined;
+ if (needReceiptDate.value) defaults.receiptDate = undefined;
+ await formApi.setValues(defaults);
+ },
+});
+
+defineExpose({
+ open(data: { checkResult: BatchCheckResp }) {
+ modalApi.setData(data);
+ modalApi.open();
+ },
+});
+</script>
+
+<template>
+ <Modal title="濉啓鎵规灞炴��" class="w-2/5">
+ <div class="mb-3 text-sm text-gray-500">
+ 璇ョ墿鏂欓渶瑕佹壒娆$鐞嗭紝璇峰~鍐欎互涓嬪繀濉壒娆″睘鎬у悗缁х画瀹℃壒銆�
+ </div>
+ <Form class="mx-4" />
+ </Modal>
+</template>
diff --git a/src/views/mes/pro/feedback/modules/form.vue b/src/views/mes/pro/feedback/modules/form.vue
index 00ae80a..d0e4edf 100644
--- a/src/views/mes/pro/feedback/modules/form.vue
+++ b/src/views/mes/pro/feedback/modules/form.vue
@@ -5,6 +5,8 @@
import { computed, ref } from 'vue';
+import dayjs from 'dayjs';
+
import { useVbenModal } from '@vben/common-ui';
import { MesAutoCodeRuleCode, MesProFeedbackStatusEnum } from '@vben/constants';
import { useUserStore } from '@vben/stores';
@@ -16,6 +18,7 @@
import {
approveFeedback,
createFeedback,
+ getBatchRequirements,
getFeedback,
rejectFeedback,
submitFeedback,
@@ -25,6 +28,7 @@
import { $t } from '#/locales';
import { useFormSchema } from '../data';
+import BatchInputDialog from './batch-input-dialog.vue';
import ItemConsumeList from './item-consume-list.vue';
import ProductProduceList from './product-produce-list.vue';
@@ -161,14 +165,17 @@
}
}
-/** 瀹℃壒閫氳繃 */
-async function handleApprove() {
- if (!formData.value?.id) {
- return;
- }
+const batchDialogRef = ref<InstanceType<typeof BatchInputDialog>>();
+
+/** 鎵ц瀹℃壒锛堝惈鎵规灞炴�э級 */
+async function doApprove(batchFields?: Record<string, unknown>) {
+ if (!formData.value?.id) return;
modalApi.lock();
try {
- const finished = await approveFeedback(formData.value.id);
+ const finished = await approveFeedback({
+ id: formData.value.id,
+ ...batchFields,
+ });
await modalApi.close();
emit('success');
message.success(
@@ -177,6 +184,30 @@
} finally {
modalApi.unlock();
}
+}
+
+/** 瀹℃壒閫氳繃 */
+async function handleApprove() {
+ if (!formData.value?.id) {
+ return;
+ }
+ try {
+ const checkResult = await getBatchRequirements(formData.value.id);
+ if (checkResult?.needBatchInput) {
+ // 闇�瑕佸~鍐欐壒娆″睘鎬э紝寮圭獥鏀堕泦
+ batchDialogRef.value?.open({ checkResult });
+ } else {
+ await doApprove();
+ }
+ } catch {
+ // 妫�鏌ュけ璐ユ椂鐩存帴瀹℃壒锛堝吋瀹规棫閫昏緫锛�
+ await doApprove();
+ }
+}
+
+/** 鎵规寮圭獥纭鍥炶皟 */
+function handleBatchConfirm(batchData: Record<string, unknown>) {
+ doApprove(batchData);
}
/** 瀹℃壒涓嶉�氳繃 */
@@ -248,7 +279,7 @@
);
await formApi.setValues({
code,
- feedbackTime: Date.now(),
+ feedbackTime: dayjs().format('YYYY-MM-DD HH:mm:ss'),
feedbackUserId: userStore.userInfo?.id,
});
// 璁板綍鍒濆蹇収锛堝惈鏁伴噺瀵归綈缁撴灉锛夛紝submit 鏃剁敤浜庤烦杩囨棤鍙樻洿鐨勪繚瀛樿姹�
@@ -314,5 +345,6 @@
</Popconfirm>
</div>
</template>
+ <BatchInputDialog ref="batchDialogRef" @confirm="handleBatchConfirm" />
</Modal>
</template>
diff --git a/src/views/mes/workbench/components/tabs/FeedbackHistoryTab.vue b/src/views/mes/workbench/components/tabs/FeedbackHistoryTab.vue
index 4048770..8620c70 100644
--- a/src/views/mes/workbench/components/tabs/FeedbackHistoryTab.vue
+++ b/src/views/mes/workbench/components/tabs/FeedbackHistoryTab.vue
@@ -16,6 +16,7 @@
import {
approveFeedback,
deleteFeedback,
+ getBatchRequirements,
getFeedbackPage,
rejectFeedback,
submitFeedback,
@@ -23,6 +24,7 @@
import { $t } from '#/locales';
import FeedbackForm from '../../../pro/feedback/modules/form.vue';
+import BatchInputDialog from '../../../pro/feedback/modules/batch-input-dialog.vue';
defineOptions({ name: 'WorkbenchFeedbackHistoryTab' });
@@ -38,6 +40,19 @@
connectedComponent: FeedbackForm,
destroyOnClose: true,
});
+
+const batchDialogRef = ref<InstanceType<typeof BatchInputDialog>>();
+const approvingRow = ref<MesProFeedbackApi.Feedback>();
+
+/** 鎵ц瀹℃壒锛堝惈鎵规灞炴�э級 */
+async function doApprove(row: MesProFeedbackApi.Feedback, batchFields?: Record<string, unknown>) {
+ const finished = await approveFeedback({ id: row.id!, ...batchFields });
+ message.success(
+ finished ? '鎶ュ伐鍗曞凡瀹℃壒瀹屾垚' : '鎶ュ伐鎴愬姛锛岃绛夊緟璐ㄩ噺妫�楠屽畬鎴愶紒',
+ );
+ refreshGrid();
+ emit('reported');
+}
/** 鏌ョ湅璇︽儏 */
function handleDetail(row: MesProFeedbackApi.Feedback) {
@@ -67,12 +82,24 @@
/** 瀹℃壒閫氳繃锛堜粎瀹℃壒涓級 */
async function handleApprove(row: MesProFeedbackApi.Feedback) {
- const finished = await approveFeedback(row.id!);
- message.success(
- finished ? '鎶ュ伐鍗曞凡瀹℃壒瀹屾垚' : '鎶ュ伐鎴愬姛锛岃绛夊緟璐ㄩ噺妫�楠屽畬鎴愶紒',
- );
- refreshGrid();
- emit('reported');
+ try {
+ const checkResult = await getBatchRequirements(row.id!);
+ if (checkResult?.needBatchInput) {
+ approvingRow.value = row;
+ batchDialogRef.value?.open({ checkResult });
+ } else {
+ await doApprove(row);
+ }
+ } catch {
+ await doApprove(row);
+ }
+}
+
+/** 鎵规寮圭獥纭鍥炶皟 */
+function handleBatchConfirm(batchData: Record<string, unknown>) {
+ if (approvingRow.value) {
+ doApprove(approvingRow.value, batchData);
+ }
}
/** 椹冲洖锛堜粎瀹℃壒涓級 */
@@ -202,6 +229,7 @@
<template>
<div class="feedback-history-tab">
<FormModal @success="refreshGrid" />
+ <BatchInputDialog ref="batchDialogRef" @confirm="handleBatchConfirm" />
<div v-if="!taskId" class="feedback-history-tab__empty">
璇蜂粠宸︿晶宸ュ崟鍒楄〃閫夋嫨涓�鏉′换鍔℃煡鐪嬫姤宸ヨ褰�
diff --git a/src/views/mes/workbench/components/tabs/FeedbackTab.vue b/src/views/mes/workbench/components/tabs/FeedbackTab.vue
index e7e449a..8439b83 100644
--- a/src/views/mes/workbench/components/tabs/FeedbackTab.vue
+++ b/src/views/mes/workbench/components/tabs/FeedbackTab.vue
@@ -4,6 +4,8 @@
import { computed, ref, watch } from 'vue';
+import dayjs from 'dayjs';
+
import {
MesAutoCodeRuleCode,
MesProFeedbackStatusEnum,
@@ -116,7 +118,7 @@
await formApi.setValues({
code,
type: 1,
- feedbackTime: Date.now(),
+ feedbackTime: dayjs().format('YYYY-MM-DD HH:mm:ss'),
feedbackUserId: userStore.userInfo?.id,
taskId: task.id,
workOrderId: task.workOrderId,
@@ -242,7 +244,7 @@
/** 瀹℃壒閫氳繃 */
async function handleApprove() {
if (!feedbackId.value) return;
- const finished = await approveFeedback(feedbackId.value);
+ const finished = await approveFeedback({ id: feedbackId.value });
message.success(
finished ? '鎶ヨ鍗曞凡瀹℃壒瀹屾垚' : '鎶ュ伐鎴愬姛锛岃绛夊緟璐ㄩ噺妫�楠屽畬鎴愶紒',
);
diff --git a/src/views/srm/tender/detail.vue b/src/views/srm/tender/detail.vue
index 839df72..4dfbeda 100644
--- a/src/views/srm/tender/detail.vue
+++ b/src/views/srm/tender/detail.vue
@@ -253,6 +253,40 @@
const awardFormVisible = ref(false);
const awardForm = ref<SrmTenderApi.BidAwardVO>({ tenderProjectId: projectId, supplierId: undefined!, bidId: undefined!, awardNo: '' });
+/** 宸叉姇鏍囦笖鏈挙鏍囩殑渚涘簲鍟嗗垪琛紙浠庢姇鏍囪褰曚腑鎻愬彇锛� */
+const bidSuppliers = computed(() => {
+ const activeBids = bidList.value.filter((b) => b.bidStatus !== BID_STATUS.WITHDRAWN);
+ const seen = new Map<number, { id: number; name: string }>();
+ activeBids.forEach((b) => {
+ if (b.supplierId && !seen.has(b.supplierId)) {
+ seen.set(b.supplierId, { id: b.supplierId, name: b.supplierName || '' });
+ }
+ });
+ return [...seen.values()];
+});
+
+/** 鍙�夋姇鏍囪褰曪細鎺掗櫎鎾ゆ爣锛屼笖濡傛灉閫変簡渚涘簲鍟嗗垯鍙樉绀鸿渚涘簲鍟嗙殑鎶曟爣 */
+const activeBids = computed(() => {
+ return bidList.value.filter((b) => {
+ if (b.bidStatus === BID_STATUS.WITHDRAWN) return false;
+ if (awardForm.value.supplierId && b.supplierId !== awardForm.value.supplierId) return false;
+ return true;
+ });
+});
+
+/** 閫夋嫨渚涘簲鍟嗘椂娓呯┖宸查�夌殑鎶曟爣 */
+function onAwardSupplierChange() {
+ awardForm.value.bidId = undefined!;
+}
+
+/** 閫夋嫨鎶曟爣鏃惰嚜鍔ㄥ甫鍏ヨ鎶曟爣鐨勬姤浠烽噾棰� */
+function onAwardBidChange(bidId: number) {
+ const bid = bidList.value.find((b) => b.id === bidId);
+ if (bid?.bidTotalAmount != null) {
+ awardForm.value.awardAmount = bid.bidTotalAmount;
+ }
+}
+
async function loadAward() {
try { award.value = await getAwardByProject(projectId); } catch { award.value = undefined; }
}
@@ -859,15 +893,15 @@
<a-row :gutter="12">
<a-col :span="12">
<a-form-item label="渚涘簲鍟�" required>
- <a-select v-model:value="awardForm.supplierId" show-search placeholder="閫夋嫨渚涘簲鍟�" :filter-option="filterOption">
- <a-select-option v-for="s in supplierList" :key="s.id" :value="s.id" :label="s.name">{{ s.name }}</a-select-option>
+ <a-select v-model:value="awardForm.supplierId" show-search placeholder="閫夋嫨渚涘簲鍟�" :filter-option="filterOption" @change="onAwardSupplierChange">
+ <a-select-option v-for="s in bidSuppliers" :key="s.id" :value="s.id" :label="s.name">{{ s.name }}</a-select-option>
</a-select>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="鎶曟爣" required>
- <a-select v-model:value="awardForm.bidId" show-search placeholder="閫夋嫨鎶曟爣" :filter-option="filterOption">
- <a-select-option v-for="b in bidList" :key="b.id" :value="b.id" :label="`${b.bidNo} - ${b.supplierName}`">{{ b.bidNo }} - {{ b.supplierName }}</a-select-option>
+ <a-select v-model:value="awardForm.bidId" show-search placeholder="閫夋嫨鎶曟爣" :filter-option="filterOption" :disabled="!awardForm.supplierId" @change="onAwardBidChange">
+ <a-select-option v-for="b in activeBids" :key="b.id" :value="b.id" :label="`${b.bidNo} - ${b.supplierName}`">{{ b.bidNo }} - {{ b.supplierName }}</a-select-option>
</a-select>
</a-form-item>
</a-col>
--
Gitblit v1.9.3