feat(mes): 实现生产报工批次属性管理和SRM投标优化功能
- 新增仓库选择组件,实现仓库列表缓存和自动填充仓库编码
- 添加商机价格支持,动态切换产品编辑表格价格列显示
- 优化SRM投标详情页面,实现供应商和投标记录联动筛选
- 实现生产报工批次属性管理,新增批次输入弹窗和验证机制
- 完善报工审批流程,集成批次需求检查和属性收集
- 移除物料分类选择组件中的禁用标记逻辑
- 添加采购申请审批详情查看验证,防止未提交申请被访问
- 新增仓库简单列表API接口和数据模型定义
| | |
| | | /** åæºäº§åä¿¡æ¯ */ |
| | | 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; |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | 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', |
| | | ); |
| | | } |
| | |
| | | } |
| | | |
| | | /** 审æ¹ç产æ¥å·¥ï¼è¿åæ¯å¦å·²å®¡æ¹å®æï¼ */ |
| | | 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 } }, |
| | | ); |
| | | } |
| | | |
| | | /** å¿«éæ¥å·¥è¯·æ± */ |
| | |
| | | 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[] { |
| | |
| | | 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; |
| | | } |
| | | |
| | | /** æ°å¢/ä¿®æ¹ç表å */ |
| | |
| | | }, |
| | | }, |
| | | { |
| | | component: 'Input', |
| | | component: 'ApiSelect', |
| | | fieldName: 'warehouseId', |
| | | label: 'é»è®¤ä»åº', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥é»è®¤ä»åºID', |
| | | 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, |
| | | }, |
| | | }, |
| | | { |
| | |
| | | title: 'ç©æç¼ç ', |
| | | }, |
| | | { |
| | | field: 'itemBarCode', |
| | | field: 'barCode', |
| | | title: 'ç©ææ¡ç ', |
| | | }, |
| | | { |
| | |
| | | } |
| | | |
| | | /** 产åç¼è¾è¡¨æ ¼çåå®ä¹ */ |
| | | 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 }, |
| | | { |
| | |
| | | minWidth: 100, |
| | | formatter: 'formatAmount2', |
| | | }, |
| | | { |
| | | field: 'contractPrice', |
| | | title: 'ååä»·ï¼å
ï¼', |
| | | minWidth: 100, |
| | | slots: { default: 'contractPrice' }, |
| | | }, |
| | | priceColumn, |
| | | { |
| | | field: 'count', |
| | | title: 'æ°é', |
| | |
| | | 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'; |
| | | |
| | |
| | | }>(); |
| | | |
| | | const emit = defineEmits(['update:products']); |
| | | |
| | | /** æ¯å¦ä¸ºåæºç±»å */ |
| | | const isBusiness = computed(() => props.bizType === BizTypeEnum.CRM_BUSINESS); |
| | | |
| | | /** å½åä¸å¡ç±»å对åºçä»·æ ¼åæ®µå */ |
| | | const priceField = computed(() => isBusiness.value ? 'businessPrice' : 'contractPrice'); |
| | | |
| | | /** è¡¨æ ¼å
鍿°æ® */ |
| | | const tableData = ref<any[]>([]); |
| | |
| | | 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); |
| | |
| | | trigger: 'click', |
| | | mode: 'cell', |
| | | }, |
| | | columns: useProductEditTableColumns(), |
| | | columns: useProductEditTableColumns(isBusiness.value), |
| | | data: tableData.value, |
| | | border: true, |
| | | showOverflow: true, |
| | |
| | | @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" |
| | |
| | | |
| | | /** æ¥ç审æ¹è¯¦æ
*/ |
| | | function handleViewProcess(row: ErpPurchaseRequestApi.PurchaseRequest) { |
| | | if (!row.processInstanceId) { |
| | | message.warning('该ç³è¯·å°æªæäº¤å®¡æ¹'); |
| | | return; |
| | | } |
| | | router.push({ name: 'BpmProcessInstanceDetail', query: { id: row.processInstanceId } }); |
| | | } |
| | | |
| | |
| | | }, |
| | | }); |
| | | |
| | | /** éå½å°æåèç¹ç忝èç¹æ 记为 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 = |
| | |
| | | /** æ¥è¯¢ç©æåç±»æ */ |
| | | async function getItemTypeTree() { |
| | | allList.value = await getItemTypeSimpleList(); |
| | | itemTypeTree.value = markParentsDisabled(handleTree(allList.value)); |
| | | itemTypeTree.value = handleTree(allList.value) as ItemTypeNode[]; |
| | | syncSelectedItem(props.modelValue); |
| | | } |
| | | |
| | |
| | | |
| | | 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(); |
| | |
| | | |
| | | const [FormModal, formModalApi] = useVbenModal({ |
| | | connectedComponent: Form, |
| | | destroyOnClose: true, |
| | | }); |
| | | |
| | | const [QuickFeedbackModal, quickFeedbackModalApi] = useVbenModal({ |
| | | connectedComponent: QuickFeedbackForm, |
| | | destroyOnClose: true, |
| | | }); |
| | | |
| | | const [BatchFeedbackModal, batchFeedbackModalApi] = useVbenModal({ |
| | | connectedComponent: BatchFeedbackForm, |
| | | destroyOnClose: true, |
| | | }); |
| | | |
| | |
| | | } |
| | | } |
| | | |
| | | /** å¿«éæ¥å·¥ */ |
| | | function handleQuickCreate() { |
| | | quickFeedbackModalApi.setData({}).open(); |
| | | } |
| | | |
| | | /** æ¹éæ¥å·¥ */ |
| | | function handleBatchCreate() { |
| | | batchFeedbackModalApi.setData({}).open(); |
| | | } |
| | | |
| | | const [Grid, gridApi] = useVbenVxeGrid({ |
| | | formOptions: { schema: useGridFormSchema() }, |
| | | gridOptions: { |
| | |
| | | <template> |
| | | <Page auto-content-height> |
| | | <FormModal @success="handleRefresh" /> |
| | | <QuickFeedbackModal @success="handleRefresh" /> |
| | | <BatchFeedbackModal @success="handleRefresh" /> |
| | | <Grid table-title="ç产æ¥å·¥å表"> |
| | | <template #toolbar-tools> |
| | | <TableAction |
| | |
| | | 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'), |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <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: '请è¾å
¥å®¢æ·ID' }, |
| | | 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> |
| | |
| | | |
| | | 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'; |
| | |
| | | import { |
| | | approveFeedback, |
| | | createFeedback, |
| | | getBatchRequirements, |
| | | getFeedback, |
| | | rejectFeedback, |
| | | submitFeedback, |
| | |
| | | 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'; |
| | | |
| | |
| | | } |
| | | } |
| | | |
| | | /** 审æ¹éè¿ */ |
| | | 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( |
| | |
| | | } 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); |
| | | } |
| | | |
| | | /** 审æ¹ä¸éè¿ */ |
| | |
| | | ); |
| | | await formApi.setValues({ |
| | | code, |
| | | feedbackTime: Date.now(), |
| | | feedbackTime: dayjs().format('YYYY-MM-DD HH:mm:ss'), |
| | | feedbackUserId: userStore.userInfo?.id, |
| | | }); |
| | | // è®°å½åå§å¿«ç
§ï¼å«æ°é对é½ç»æï¼ï¼submit æ¶ç¨äºè·³è¿æ åæ´çä¿åè¯·æ± |
| | |
| | | </Popconfirm> |
| | | </div> |
| | | </template> |
| | | <BatchInputDialog ref="batchDialogRef" @confirm="handleBatchConfirm" /> |
| | | </Modal> |
| | | </template> |
| | |
| | | import { |
| | | approveFeedback, |
| | | deleteFeedback, |
| | | getBatchRequirements, |
| | | getFeedbackPage, |
| | | rejectFeedback, |
| | | submitFeedback, |
| | |
| | | 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' }); |
| | | |
| | |
| | | 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) { |
| | |
| | | |
| | | /** 审æ¹éè¿ï¼ä»
审æ¹ä¸ï¼ */ |
| | | 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); |
| | | } |
| | | } |
| | | |
| | | /** 驳åï¼ä»
审æ¹ä¸ï¼ */ |
| | |
| | | <template> |
| | | <div class="feedback-history-tab"> |
| | | <FormModal @success="refreshGrid" /> |
| | | <BatchInputDialog ref="batchDialogRef" @confirm="handleBatchConfirm" /> |
| | | |
| | | <div v-if="!taskId" class="feedback-history-tab__empty"> |
| | | 请ä»å·¦ä¾§å·¥ååè¡¨éæ©ä¸æ¡ä»»å¡æ¥çæ¥å·¥è®°å½ |
| | |
| | | |
| | | import { computed, ref, watch } from 'vue'; |
| | | |
| | | import dayjs from 'dayjs'; |
| | | |
| | | import { |
| | | MesAutoCodeRuleCode, |
| | | MesProFeedbackStatusEnum, |
| | |
| | | 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, |
| | |
| | | /** 审æ¹éè¿ */ |
| | | async function handleApprove() { |
| | | if (!feedbackId.value) return; |
| | | const finished = await approveFeedback(feedbackId.value); |
| | | const finished = await approveFeedback({ id: feedbackId.value }); |
| | | message.success( |
| | | finished ? 'æ¥è®¢å已审æ¹å®æ' : 'æ¥å·¥æåï¼è¯·çå¾
è´¨éæ£éªå®æï¼', |
| | | ); |
| | |
| | | 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; } |
| | | } |
| | |
| | | <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> |