feat: 完成好水川装箱、工单成本、留样与发货统计页面
| | |
| | | maxStock?: number; // æé«åºåé |
| | | highValue?: boolean; // æ¯å¦é«å¼ç©æ |
| | | batchFlag?: boolean; // æ¯å¦å¯ç¨æ¹æ¬¡ç®¡ç |
| | | boxQuantity?: number; // æ¯ç®±æ°é |
| | | remark?: string; // 夿³¨ |
| | | createTime?: Date; // å建æ¶é´ |
| | | } |
| | |
| | | code?: string; // åç±»ç¼ç |
| | | name?: string; // åç±»åç§° |
| | | itemOrProduct?: string; // ç©æ/äº§åæ è¯ |
| | | finishedFlag?: boolean; // æ¯å¦æååç±» |
| | | sort?: number; // æ¾ç¤ºæåº |
| | | status?: number; // ç¶æ |
| | | remark?: string; // 夿³¨ |
| | |
| | | import type { PageParam, PageResult } from '@vben/request'; |
| | | import type { SrmSupplierApi } from '#/api/srm/supplier'; |
| | | |
| | | import { requestClient } from '#/api/request'; |
| | | |
| | | export namespace MesMdVendorApi { |
| | | /** MES ä¾åºå */ |
| | | /** MES ä¾åºåï¼æ°æ®æ¥æºï¼SRM ä¾åºåæ¡£æ¡ï¼ */ |
| | | export interface Vendor { |
| | | id?: number; // ä¾åºåç¼å· |
| | | code?: string; // ä¾åºåç¼ç |
| | |
| | | remark?: string; // 夿³¨ |
| | | createTime?: Date; // å建æ¶é´ |
| | | } |
| | | |
| | | /** ä¾åºå导å
¥ç»æ */ |
| | | export interface VendorImportRespVO { |
| | | createCodes?: string[]; // æ°å¢æåçä¾åºåç¼ç |
| | | updateCodes?: string[]; // æ´æ°æåçä¾åºåç¼ç |
| | | failureCodes?: Record<string, string>; // 导å
¥å¤±è´¥çä¾åºåç¼ç ååå |
| | | } |
| | | } |
| | | |
| | | /** æ¥è¯¢ä¾åºåå页 */ |
| | | export function getVendorPage(params: PageParam) { |
| | | return requestClient.get<PageResult<MesMdVendorApi.Vendor>>( |
| | | '/mes/md-vendor/page', |
| | | { params }, |
| | | /** SRM ä¾åºåæ¡£æ¡è¿ååæ®µï¼å«å端类åå°æªå£°æçç®ç§°ãå®ç½ï¼ */ |
| | | interface SrmSupplierRow extends SrmSupplierApi.SupplierVO { |
| | | shortName?: string; |
| | | website?: string; |
| | | } |
| | | |
| | | /** SRM ä¾åºåå页æ¥è¯¢åæ° */ |
| | | interface VendorPageParams extends PageParam { |
| | | code?: string; |
| | | name?: string; |
| | | status?: number; |
| | | } |
| | | |
| | | function toVendor(supplier: SrmSupplierRow): MesMdVendorApi.Vendor { |
| | | return { |
| | | id: supplier.id, |
| | | code: supplier.code, |
| | | name: supplier.name, |
| | | nickname: supplier.shortName, |
| | | address: supplier.address, |
| | | website: supplier.website, |
| | | email: supplier.contactEmail, |
| | | telephone: supplier.contactMobile, |
| | | contact1Name: supplier.contactName, |
| | | contact1Telephone: supplier.contactMobile, |
| | | contact1Email: supplier.contactEmail, |
| | | creditCode: supplier.taxNo, |
| | | status: supplier.status, |
| | | remark: supplier.remark, |
| | | createTime: supplier.createTime ? new Date(supplier.createTime) : undefined, |
| | | }; |
| | | } |
| | | |
| | | /** æ¥è¯¢ä¾åºåå页ï¼è¯»å SRM ä¾åºåæ¡£æ¡ï¼ */ |
| | | export async function getVendorPage(params: VendorPageParams) { |
| | | const page = await requestClient.get<PageResult<SrmSupplierRow>>( |
| | | '/srm/supplier/page', |
| | | { |
| | | params: { |
| | | pageNo: params.pageNo, |
| | | pageSize: params.pageSize, |
| | | code: params.code, |
| | | name: params.name, |
| | | status: params.status, |
| | | }, |
| | | }, |
| | | ); |
| | | return { |
| | | list: page.list.map((supplier) => toVendor(supplier)), |
| | | total: page.total, |
| | | } as PageResult<MesMdVendorApi.Vendor>; |
| | | } |
| | | |
| | | /** æ¥è¯¢ä¾åºå详æ
*/ |
| | | export function getVendor(id: number) { |
| | | return requestClient.get<MesMdVendorApi.Vendor>( |
| | | `/mes/md-vendor/get?id=${id}`, |
| | | /** æ¥è¯¢ä¾åºå详æ
ï¼è¯»å SRM ä¾åºåæ¡£æ¡ï¼ */ |
| | | export async function getVendor(id: number) { |
| | | const supplier = await requestClient.get<SrmSupplierRow>( |
| | | '/srm/supplier/get', |
| | | { params: { id } }, |
| | | ); |
| | | } |
| | | |
| | | /** æ°å¢ä¾åºå */ |
| | | export function createVendor(data: MesMdVendorApi.Vendor) { |
| | | return requestClient.post('/mes/md-vendor/create', data); |
| | | } |
| | | |
| | | /** ä¿®æ¹ä¾åºå */ |
| | | export function updateVendor(data: MesMdVendorApi.Vendor) { |
| | | return requestClient.put('/mes/md-vendor/update', data); |
| | | } |
| | | |
| | | /** å é¤ä¾åºå */ |
| | | export function deleteVendor(id: number) { |
| | | return requestClient.delete(`/mes/md-vendor/delete?id=${id}`); |
| | | } |
| | | |
| | | /** 导åºä¾åºå */ |
| | | export function exportVendor(params: any) { |
| | | return requestClient.download('/mes/md-vendor/export-excel', { params }); |
| | | } |
| | | |
| | | /** ä¸è½½ä¾åºå导å
¥æ¨¡æ¿ */ |
| | | export function importVendorTemplate() { |
| | | return requestClient.download('/mes/md-vendor/get-import-template'); |
| | | } |
| | | |
| | | /** 导å
¥ä¾åºå */ |
| | | export function importVendor(file: File, updateSupport: boolean) { |
| | | return requestClient.upload<MesMdVendorApi.VendorImportRespVO>( |
| | | `/mes/md-vendor/import?updateSupport=${updateSupport}`, |
| | | { file }, |
| | | ); |
| | | return toVendor(supplier); |
| | | } |
| | |
| | | productId?: number; |
| | | clientId?: number; |
| | | status?: number; |
| | | workOrderId?: number; |
| | | requestDate?: number[]; |
| | | } |
| | | |
| | |
| | | quantityProduced?: number; // å·²ç产æ°é |
| | | quantityChanged?: number; // è°æ´æ°é |
| | | quantityScheduled?: number; // å·²æäº§æ°é |
| | | packedQuantity?: number; // å·²è£
ç®±æ°é |
| | | packedBoxCount?: number; // å·²è£
ç®±ç®±æ° |
| | | packingStatus?: number; // è£
ç®±ç¶æï¼0=æªè£
ç®± 1=é¨åå·²è£
ç®± 2=å·²è£
ç®±å®æ |
| | | clientId?: number; // 客æ·ç¼å· |
| | | clientName?: string; // 客æ·åç§° |
| | | vendorId?: number; // ä¾åºåç¼å· |
| | |
| | | clientId?: number; |
| | | status?: number; |
| | | type?: number; |
| | | packingStatus?: number; |
| | | requestDate?: number[]; |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import type { PageParam, PageResult } from '@vben/request'; |
| | | |
| | | import { requestClient } from '#/api/request'; |
| | | |
| | | export namespace MesQcRetentionSampleApi { |
| | | /** MES çæ ·è®°å½ */ |
| | | export interface RetentionSample { |
| | | id?: number; |
| | | code?: string; |
| | | workOrderId?: number; |
| | | workOrderCode?: string; |
| | | saleOrderId?: number; |
| | | saleOrderNo?: string; |
| | | productId?: number; |
| | | productCode?: string; |
| | | productName?: string; |
| | | productSpecification?: string; |
| | | batchCode?: string; |
| | | sampleQuantity?: number; |
| | | sampleDate?: string; |
| | | expiryDate?: string; |
| | | keeperUserId?: number; |
| | | keeperNickname?: string; |
| | | remark?: string; |
| | | createTime?: string; |
| | | } |
| | | } |
| | | |
| | | /** æ¥è¯¢çæ ·è®°å½å页 */ |
| | | export function getRetentionSamplePage(params: PageParam) { |
| | | return requestClient.get<PageResult<MesQcRetentionSampleApi.RetentionSample>>( |
| | | '/mes/qc/retention-sample/page', |
| | | { params }, |
| | | ); |
| | | } |
| | | |
| | | /** æ¥è¯¢çæ ·è®°å½è¯¦æ
*/ |
| | | export function getRetentionSample(id: number) { |
| | | return requestClient.get<MesQcRetentionSampleApi.RetentionSample>( |
| | | `/mes/qc/retention-sample/get?id=${id}`, |
| | | ); |
| | | } |
| | | |
| | | /** æ°å¢çæ ·è®°å½ */ |
| | | export function createRetentionSample( |
| | | data: MesQcRetentionSampleApi.RetentionSample, |
| | | ) { |
| | | return requestClient.post('/mes/qc/retention-sample/create', data); |
| | | } |
| | | |
| | | /** ä¿®æ¹çæ ·è®°å½ */ |
| | | export function updateRetentionSample( |
| | | data: MesQcRetentionSampleApi.RetentionSample, |
| | | ) { |
| | | return requestClient.put('/mes/qc/retention-sample/update', data); |
| | | } |
| | | |
| | | /** å é¤çæ ·è®°å½ */ |
| | | export function deleteRetentionSample(id: number) { |
| | | return requestClient.delete(`/mes/qc/retention-sample/delete?id=${id}`); |
| | | } |
| | |
| | | specification?: string; // è§æ ¼åå· |
| | | unitMeasureName?: string; // 计éåä½åç§° |
| | | quantity?: number; // è£
ç®±æ°é |
| | | boxCount?: number; // ç®±æ° |
| | | tailBoxQuantity?: number; // 尾箱æ°éï¼æåä¸ç®±å®é
è£
çæ°éï¼ |
| | | tailBoxFull?: boolean; // 尾箱æ¯å¦æ»¡è£
|
| | | workOrderId?: number; // ç产订åç¼å· |
| | | workOrderCode?: string; // ç产订åç¼å· |
| | | batchCode?: string; // æ¹æ¬¡å· |
| | |
| | | specification?: string; // è§æ ¼åå· |
| | | unitMeasureName?: string; // åä½ |
| | | quantity?: number; // åºåºæ°é |
| | | boxCount?: number; // ç®±æ° |
| | | pickedQuantity?: number; // å·²æ£è´§æ°é |
| | | batchId?: number; // æ¹æ¬¡ç¼å· |
| | | batchCode?: string; // æ¹æ¬¡å· |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import type { PageParam, PageResult } from '@vben/request'; |
| | | |
| | | import { requestClient } from '#/api/request'; |
| | | |
| | | export namespace MesWmProductSalesStatisticsApi { |
| | | /** MES åè´§ç»è®¡ */ |
| | | export interface Statistics { |
| | | id?: string; // å端è¡ä¸»é®ï¼å®¢æ·+éå®è®¢åå·+产åï¼ |
| | | clientId?: number; // 客æ·ç¼å· |
| | | clientName?: string; // 客æ·åç§° |
| | | salesOrderCode?: string; // éå®è®¢åç¼å· |
| | | itemId?: number; // 产åç¼å· |
| | | itemCode?: string; // 产åç¼ç |
| | | itemName?: string; // 产ååç§° |
| | | specification?: string; // è§æ ¼åå· |
| | | unitMeasureName?: string; // åä½ |
| | | totalQuantity?: number; // åºåºæ»æ°é |
| | | totalBoxCount?: number; // æ»ç®±æ° |
| | | shipmentCount?: number; // åè´§æ¬¡æ° |
| | | lastShipmentDate?: Date; // æååè´§æ¶é´ |
| | | } |
| | | |
| | | /** åè´§ç»è®¡å页æ¥è¯¢åæ° */ |
| | | export interface PageParams extends PageParam { |
| | | clientId?: number; |
| | | salesOrderCode?: string; |
| | | itemId?: number; |
| | | shipmentDate?: string[]; |
| | | } |
| | | } |
| | | |
| | | /** æ¥è¯¢åè´§ç»è®¡å页 */ |
| | | export function getProductSalesStatisticsPage( |
| | | params: MesWmProductSalesStatisticsApi.PageParams, |
| | | ) { |
| | | return requestClient.get< |
| | | PageResult<MesWmProductSalesStatisticsApi.Statistics> |
| | | >('/mes/wm/product-sales/statistics-page', { params }); |
| | | } |
| | |
| | | QC_IQC_VENDOR_BATCH_CODE: 'QC_IQC_VENDOR_BATCH_CODE', |
| | | QC_IPQC_CODE: 'QC_IPQC_CODE', |
| | | QC_OQC_CODE: 'QC_OQC_CODE', |
| | | QC_RETENTION_SAMPLE_CODE: 'QC_RETENTION_SAMPLE_CODE', |
| | | QC_RQC_CODE: 'QC_RQC_CODE', |
| | | QC_TEMPLATE_CODE: 'QC_TEMPLATE_CODE', |
| | | TM_TOOL_TYPE_CODE: 'TM_TOOL_TYPE_CODE', |
| | |
| | | MES_PRO_WORK_ORDER_STATUS: 'mes_pro_work_order_status', // MES ç产工åç¶æ |
| | | ORDER_PRODUCTION_STATUS: 'order_production_status', // ç产订åç¶æ |
| | | MES_PRO_WORK_ORDER_TYPE: 'mes_pro_work_order_type', // MES å·¥åç±»å |
| | | MES_PRO_WORK_ORDER_PACKING_STATUS: 'mes_pro_work_order_packing_status', // MES å·¥åè£
ç®±ç¶æ |
| | | MES_PRO_WORK_ORDER_SOURCE_TYPE: 'mes_pro_work_order_source_type', // MES 工忥æºç±»å |
| | | MES_PRO_TASK_STATUS: 'mes_pro_task_status', // MES ç产任å¡ç¶æ |
| | | MES_PRO_FEEDBACK_STATUS: 'mes_pro_feedback_status', // MES ç产æ¥å·¥ç¶æ |
| | |
| | | rules: z.number().default(CommonStatusEnum.DISABLE), |
| | | }, |
| | | { |
| | | fieldName: 'boxQuantity', |
| | | label: 'æ¯ç®±æ°é', |
| | | component: 'InputNumber', |
| | | componentProps: { |
| | | class: '!w-full', |
| | | min: 0.01, |
| | | placeholder: '请è¾å
¥æ¯ç®±æ°é', |
| | | precision: 2, |
| | | }, |
| | | help: 'ä»
æåéè¦å¡«åï¼è£
ç®±ä¸åè´§æ¶ç¨äºèªå¨è®¡ç®ç®±æ°ã', |
| | | }, |
| | | { |
| | | fieldName: 'highValue', |
| | | label: 'é«å¼ç©æ', |
| | | component: 'Switch', |
| | |
| | | align: 'center', |
| | | }, |
| | | { |
| | | field: 'boxQuantity', |
| | | title: 'æ¯ç®±æ°é', |
| | | width: 100, |
| | | align: 'center', |
| | | }, |
| | | { |
| | | field: 'itemTypeName', |
| | | title: 'ç©æåç±»', |
| | | minWidth: 140, |
| | |
| | | rules: z.string().default(MesItemOrProductEnum.ITEM.value), |
| | | }, |
| | | { |
| | | fieldName: 'finishedFlag', |
| | | label: 'æ¯å¦æååç±»', |
| | | component: 'Switch', |
| | | componentProps: { |
| | | checkedChildren: 'æ¯', |
| | | unCheckedChildren: 'å¦', |
| | | }, |
| | | rules: z.boolean().default(false), |
| | | help: '设为æååç±»åï¼è¯¥åç±»ä¸çç©ææå
许è£
ç®±ã', |
| | | }, |
| | | { |
| | | fieldName: 'sort', |
| | | label: 'æ¾ç¤ºæåº', |
| | | component: 'InputNumber', |
| | |
| | | }, |
| | | }, |
| | | { |
| | | field: 'finishedFlag', |
| | | title: 'æååç±»', |
| | | width: 110, |
| | | align: 'center', |
| | | cellRender: { |
| | | name: 'CellDict', |
| | | props: { type: DICT_TYPE.INFRA_BOOLEAN_STRING }, |
| | | }, |
| | | }, |
| | | { |
| | | field: 'sort', |
| | | title: 'æåº', |
| | | width: 100, |
| | |
| | | placeholder: '请è¾å
¥ä¾åºååç§°', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'nickname', |
| | | label: 'ä¾åºåç®ç§°', |
| | | component: 'Input', |
| | | componentProps: { |
| | | allowClear: true, |
| | | placeholder: '请è¾å
¥ä¾åºåç®ç§°', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'englishName', |
| | | label: 'è±æåç§°', |
| | | component: 'Input', |
| | | componentProps: { |
| | | allowClear: true, |
| | | placeholder: '请è¾å
¥è±æåç§°', |
| | | }, |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | |
| | | width: 120, |
| | | }, |
| | | { |
| | | field: 'level', |
| | | title: 'ä¾åºåç级', |
| | | field: 'contact1Name', |
| | | title: 'è系人', |
| | | width: 120, |
| | | cellRender: { |
| | | name: 'CellDict', |
| | | props: { type: DICT_TYPE.MES_VENDOR_LEVEL }, |
| | | }, |
| | | }, |
| | | { |
| | | field: 'score', |
| | | title: 'ä¾åºåè¯å', |
| | | width: 110, |
| | | }, |
| | | { |
| | | field: 'telephone', |
| | |
| | | ganttInited.value = true; |
| | | } |
| | | |
| | | /** è½¬ä¸ºåæ³æ¥æï¼æ æ³è§£ææ¶è¿å null */ |
| | | function toValidDate(value: any): Date | null { |
| | | if (value === null || value === undefined || value === '') { |
| | | return null; |
| | | } |
| | | const date = value instanceof Date ? value : new Date(value); |
| | | return Number.isNaN(date.getTime()) ? null : date; |
| | | } |
| | | |
| | | /** è¡¥å
¨çç¹å¾æ¶é¿ï¼å°æ¶ï¼ï¼ç¼ºå¤±æéæ³æ¶æèµ·æ¢æ¶é´è®¡ç® */ |
| | | function resolveDuration(item: any): number { |
| | | const duration = Number(item.duration); |
| | | if (Number.isFinite(duration) && duration > 0) { |
| | | return duration; |
| | | } |
| | | const hours = |
| | | (item.end_date.getTime() - item.start_date.getTime()) / 3_600_000; |
| | | return Math.max(hours, 1); |
| | | } |
| | | |
| | | /** å è½½æ°æ®å°çç¹å¾ */ |
| | | function loadData(tasks: any[]) { |
| | | if (!ganttInited.value) { |
| | |
| | | [BarcodeBizTypeEnum.WORKORDER]: 'project', |
| | | [BarcodeBizTypeEnum.TASK]: 'task', |
| | | }; |
| | | gantt.parse({ |
| | | data: tasks.map((item: any) => ({ |
| | | ...item, |
| | | type: typeMap[item.type] || item.type, |
| | | start_date: item.startDate ? new Date(item.startDate) : undefined, |
| | | end_date: item.endDate ? new Date(item.endDate) : undefined, |
| | | })), |
| | | links: [], |
| | | |
| | | // ä»»å¡è¡ï¼èµ·æ¢æ¶é´ä¸å®æ´çä¸è¿çç¹å¾ï¼é¿å
ç¬¬ä¸æ¹åºè§£æç©ºæ¥ææ¶æ¥é |
| | | const taskRows = tasks |
| | | .filter((item: any) => item.type !== BarcodeBizTypeEnum.WORKORDER) |
| | | .map((item: any) => { |
| | | const startDate = toValidDate(item.startDate); |
| | | const endDate = toValidDate(item.endDate); |
| | | if (!startDate || !endDate) { |
| | | return null; |
| | | } |
| | | return { |
| | | ...item, |
| | | type: typeMap[item.type] || item.type, |
| | | start_date: startDate, |
| | | end_date: endDate, |
| | | }; |
| | | }) |
| | | .filter(Boolean) as any[]; |
| | | |
| | | // å·¥åè¡ï¼æ²¡æèªèº«æ¶é´æ¶ç¨åä»»å¡çææ©å¼å§/ææç»æè¡¥å
¨ï¼æ²¡æä»»ä½æäº§ä»»å¡çå·¥å䏿¾ç¤º |
| | | const tasksByParent = new Map<string, any[]>(); |
| | | taskRows.forEach((row: any) => { |
| | | if (!row.parent) { |
| | | return; |
| | | } |
| | | const children = tasksByParent.get(row.parent) || []; |
| | | children.push(row); |
| | | tasksByParent.set(row.parent, children); |
| | | }); |
| | | const projectRows = tasks |
| | | .filter((item: any) => item.type === BarcodeBizTypeEnum.WORKORDER) |
| | | .map((item: any) => { |
| | | const children = tasksByParent.get(item.id) || []; |
| | | let startDate = toValidDate(item.startDate); |
| | | let endDate = toValidDate(item.endDate); |
| | | if ((!startDate || !endDate) && children.length > 0) { |
| | | startDate = new Date( |
| | | Math.min(...children.map((child) => child.start_date.getTime())), |
| | | ); |
| | | endDate = new Date( |
| | | Math.max(...children.map((child) => child.end_date.getTime())), |
| | | ); |
| | | } |
| | | if (!startDate || !endDate) { |
| | | return null; |
| | | } |
| | | return { |
| | | ...item, |
| | | type: 'project', |
| | | start_date: startDate, |
| | | end_date: endDate, |
| | | }; |
| | | }) |
| | | .filter(Boolean) as any[]; |
| | | |
| | | const projectIds = new Set(projectRows.map((row: any) => row.id)); |
| | | taskRows.forEach((row: any) => { |
| | | if (row.parent && !projectIds.has(row.parent)) { |
| | | delete row.parent; |
| | | } |
| | | }); |
| | | |
| | | const data = [...projectRows, ...taskRows].map((row: any) => ({ |
| | | ...row, |
| | | duration: resolveDuration(row), |
| | | })); |
| | | if (data.length === 0) { |
| | | return; |
| | | } |
| | | try { |
| | | gantt.parse({ data, links: [] }); |
| | | } catch (error) { |
| | | // ç¬¬ä¸æ¹åºæ¸²æå¼å¸¸åªå½±åçç¹å¾èªèº«ï¼ä¸è½è®©æ´ä¸ªé¡µé¢è·¯ç±åæ¢å¤±è´¥ |
| | | console.error('çç¹å¾æ°æ®è§£æå¤±è´¥ï¼å·²è·³è¿æ¬æ¬¡æ¸²æ', error); |
| | | try { |
| | | gantt.clearAll(); |
| | | } catch { |
| | | // å¿½ç¥æ¸
ç失败 |
| | | } |
| | | } |
| | | } |
| | | |
| | | watch( |
| | | () => props.tasks, |
| | | (val) => { |
| | | if (val?.length && ganttInited.value) { |
| | | loadData(val); |
| | | if (ganttInited.value) { |
| | | loadData(val || []); |
| | | } |
| | | }, |
| | | { deep: true }, |
| | |
| | | }); |
| | | |
| | | onBeforeUnmount(() => { |
| | | if (ganttInited.value) { |
| | | if (!ganttInited.value) { |
| | | return; |
| | | } |
| | | try { |
| | | gantt.clearAll(); |
| | | } catch { |
| | | // ç¬¬ä¸æ¹çç¹å¾æ¸
çå¼å¸¸ä¸åºå½±åè·¯ç±åæ¢ |
| | | } finally { |
| | | ganttInited.value = false; |
| | | } |
| | | }); |
| | | |
| | |
| | | const currentRouteId = ref(0); // å½åå·¥èºè·¯çº¿ç¼å· |
| | | |
| | | const isReadonly = computed(() => formType.value === 'detail'); // 详æ
æåªè¯» |
| | | const canFinish = computed( |
| | | () => (workOrder.value?.quantityProduced ?? 0) > 0, |
| | | ); // æäº§åºæå
è®¸å®æ |
| | | const getTitle = computed(() => |
| | | formType.value === 'detail' ? '订å详æ
' : 'ç产æäº§', |
| | | ); |
| | |
| | | <div class="flex flex-auto items-center gap-2"> |
| | | <template v-if="formType === 'schedule'"> |
| | | <Popconfirm |
| | | v-if="routeProcessList.length > 0" |
| | | v-if="routeProcessList.length > 0 && canFinish" |
| | | title="确认è¦å®æè¯¥è®¢ååï¼å®æå订å䏿æä»»å¡å°æ è®°ä¸ºå·²å®æã" |
| | | @confirm="handleFinish" |
| | | > |
| | | <Button type="primary">宿</Button> |
| | | </Popconfirm> |
| | | <span |
| | | v-else-if="routeProcessList.length > 0" |
| | | class="text-gray-400 text-sm" |
| | | > |
| | | å½å订åå·²ç产æ°é为 0ï¼è¯·å
宿æ¥å·¥ååå®æè®¢å |
| | | </span> |
| | | <span v-else class="text-gray-400 text-sm"> |
| | | å½åè®¢åæªé
置工åºï¼è¯·å
å¨å·¥åä¸ç»´æ¤å·¥åºä¿¡æ¯åå宿 |
| | | </span> |
| | |
| | | |
| | | const props = withDefaults( |
| | | defineProps<{ |
| | | excludeStatuses?: number[]; |
| | | status?: number; |
| | | type?: number; |
| | | checkMethod?: (row: MesProWorkOrderApi.WorkOrder) => boolean; |
| | | }>(), |
| | | { |
| | | excludeStatuses: undefined, |
| | | status: undefined, |
| | | type: undefined, |
| | | checkMethod: undefined, |
| | |
| | | ...formValues, |
| | | ...(props.status === undefined ? {} : { status: props.status }), |
| | | ...(props.type === undefined ? {} : { type: props.type }), |
| | | ...(props.excludeStatuses === undefined |
| | | ? {} |
| | | : { excludeStatuses: props.excludeStatuses }), |
| | | }); |
| | | }, |
| | | }, |
| | |
| | | defineProps<{ |
| | | allowClear?: boolean; |
| | | disabled?: boolean; |
| | | excludeStatuses?: number[]; |
| | | modelValue?: number; |
| | | placeholder?: string; |
| | | status?: number; |
| | |
| | | { |
| | | allowClear: true, |
| | | disabled: false, |
| | | excludeStatuses: undefined, |
| | | modelValue: undefined, |
| | | placeholder: 'è¯·éæ©è®¢å', |
| | | status: undefined, |
| | |
| | | ref="dialogRef" |
| | | :status="status" |
| | | :type="type" |
| | | :exclude-statuses="excludeStatuses" |
| | | :check-method="checkMethod" |
| | | @selected="handleSelected" |
| | | /> |
| | |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'packingStatus', |
| | | label: 'è£
ç®±ç¶æ', |
| | | component: 'Select', |
| | | componentProps: { |
| | | allowClear: true, |
| | | options: getDictOptions( |
| | | DICT_TYPE.MES_PRO_WORK_ORDER_PACKING_STATUS, |
| | | 'number', |
| | | ), |
| | | placeholder: 'è¯·éæ©è£
ç®±ç¶æ', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'requestDate', |
| | | label: 'éæ±æ¥æ', |
| | | component: 'RangePicker', |
| | |
| | | width: 100, |
| | | }, |
| | | { |
| | | field: 'packedQuantity', |
| | | title: 'å·²è£
ç®±æ°é', |
| | | width: 110, |
| | | }, |
| | | { |
| | | field: 'packedBoxCount', |
| | | title: 'å·²è£
箱箱æ°', |
| | | width: 110, |
| | | }, |
| | | { |
| | | field: 'packingStatus', |
| | | title: 'è£
ç®±ç¶æ', |
| | | width: 110, |
| | | cellRender: { |
| | | name: 'CellDict', |
| | | props: { type: DICT_TYPE.MES_PRO_WORK_ORDER_PACKING_STATUS }, |
| | | }, |
| | | }, |
| | | { |
| | | field: 'clientName', |
| | | title: '客æ·åç§°', |
| | | width: 120, |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import type { PageParam } from '@vben/request'; |
| | | |
| | | import type { VbenFormApi, VbenFormSchema } from '#/adapter/form'; |
| | | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; |
| | | import type { MesProWorkOrderApi } from '#/api/mes/pro/workorder'; |
| | | import type { MesQcRetentionSampleApi } from '#/api/mes/qc/retention-sample'; |
| | | |
| | | import { markRaw } from 'vue'; |
| | | |
| | | import dayjs from 'dayjs'; |
| | | |
| | | import { message } from 'ant-design-vue'; |
| | | |
| | | import { getSaleOrder, getSaleOrderPage } from '#/api/erp/sale/order'; |
| | | import { getMpsPage } from '#/api/mes/pro/mps'; |
| | | import { getSimpleUserList } from '#/api/system/user'; |
| | | import { getDateRangePickerDefaultProps } from '#/utils'; |
| | | import { ErpSaleOrderSelect } from '#/views/erp/sale/order/components'; |
| | | import { MdItemSelect } from '#/views/mes/md/item/components'; |
| | | import { ProWorkOrderSelect } from '#/views/mes/pro/workorder/components'; |
| | | |
| | | /** çæ ·ä¿åæ¶é¿ï¼æï¼ï¼ä¸å端é»è®¤å¼ä¿æä¸è´ */ |
| | | const RETENTION_MONTHS = 6; |
| | | |
| | | /** æ ¡éªéå®è®¢åæ¯å¦çå®åå¨ï¼è¿ååå¨ç订åç¼å· */ |
| | | async function resolveExistingSaleOrderId(id: number) { |
| | | const saleOrder = await getSaleOrder(id); |
| | | return saleOrder?.id; |
| | | } |
| | | |
| | | /** 鿩工åå带åºäº§åãæ¹æ¬¡ä¸éå®è®¢å */ |
| | | async function handleWorkOrderChange( |
| | | item: MesProWorkOrderApi.WorkOrder | undefined, |
| | | formApi?: VbenFormApi, |
| | | ) { |
| | | if (!formApi) { |
| | | return; |
| | | } |
| | | await formApi.setFieldValue('productName', item?.productName ?? undefined); |
| | | await formApi.setFieldValue('batchCode', item?.batchCode ?? undefined); |
| | | let saleOrderId: number | undefined; |
| | | // 1. 客æ·è®¢åæ¥æºå·¥åï¼ææ¥æºåå·èªå¨å¹é
éå®è®¢å |
| | | if (item?.orderSourceType === 1 && item.orderSourceCode) { |
| | | const params: PageParam & { no?: string } = { |
| | | no: item.orderSourceCode, |
| | | pageNo: 1, |
| | | pageSize: 1, |
| | | }; |
| | | const page = await getSaleOrderPage(params); |
| | | saleOrderId = page.list?.[0]?.id; |
| | | } |
| | | // 2. MPS ä¸åå·¥åï¼ä»¥ MPS è®°å½çéå®è®¢å为åï¼åå¹¶ä¸åå¯è½å¯¹åºå¤å¼ ï¼ |
| | | if (item?.id) { |
| | | try { |
| | | const page = await getMpsPage({ |
| | | pageNo: 1, |
| | | pageSize: 20, |
| | | workOrderId: item.id, |
| | | }); |
| | | const mpsSaleOrders = new Map<number, string>(); |
| | | for (const mps of page.list ?? []) { |
| | | if (mps.saleOrderId) { |
| | | mpsSaleOrders.set( |
| | | mps.saleOrderId, |
| | | mps.saleOrderNo || `ID=${mps.saleOrderId}`, |
| | | ); |
| | | } |
| | | } |
| | | if (mpsSaleOrders.size === 1) { |
| | | const firstMpsSaleOrder = [...mpsSaleOrders.entries()][0]; |
| | | if (firstMpsSaleOrder) { |
| | | const [mpsSaleOrderId, mpsSaleOrderNo] = firstMpsSaleOrder; |
| | | const existingId = await resolveExistingSaleOrderId(mpsSaleOrderId); |
| | | if (existingId) { |
| | | saleOrderId = existingId; |
| | | } else { |
| | | saleOrderId = undefined; |
| | | message.warning( |
| | | `该工åç± MPS ä¸åï¼ä½ MPS è®°å½çéå®è®¢åã${mpsSaleOrderNo}ãï¼ID=${mpsSaleOrderId}ï¼å¨ç³»ç»ä¸ä¸åå¨ï¼è¯·å
ä¿®æ£ MPS æ°æ®ååç»è®°çæ ·`, |
| | | ); |
| | | } |
| | | } |
| | | } else if (mpsSaleOrders.size > 1) { |
| | | saleOrderId = undefined; |
| | | message.warning( |
| | | `该工åç± ${mpsSaleOrders.size} å¼ éå®è®¢ååå¹¶ä¸åï¼è¯·éæ©æ¬æ¬¡çæ ·å¯¹åºçéå®è®¢å`, |
| | | ); |
| | | } |
| | | } catch { |
| | | // MPS æ¥è¯¢å¤±è´¥ï¼å¦æ æ¥è¯¢æéï¼æ¶ä¸é»æå·¥åéæ©ï¼ä¿åæ¶ç±å端å
åºæ ¡éª |
| | | } |
| | | } |
| | | await formApi.setFieldValue('saleOrderId', saleOrderId); |
| | | } |
| | | |
| | | /** çæ ·æ¥æåæ´æ¶åæ¥å°ææ¥æ */ |
| | | function syncExpiryDate(value: string, formApi?: VbenFormApi) { |
| | | if (!formApi) { |
| | | return; |
| | | } |
| | | void formApi.setFieldValue( |
| | | 'expiryDate', |
| | | value ? dayjs(value).add(RETENTION_MONTHS, 'month').format('YYYY-MM-DD') : undefined, |
| | | ); |
| | | } |
| | | |
| | | /** æ°å¢/ä¿®æ¹ç表å */ |
| | | export function useFormSchema( |
| | | formType: 'create' | 'update', |
| | | formApi?: VbenFormApi, |
| | | ): VbenFormSchema[] { |
| | | return [ |
| | | { |
| | | fieldName: 'id', |
| | | component: 'Input', |
| | | dependencies: { |
| | | triggerFields: [''], |
| | | show: () => false, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'workOrderId', |
| | | label: 'ç产工å', |
| | | component: markRaw(ProWorkOrderSelect), |
| | | componentProps: { |
| | | disabled: formType === 'update', |
| | | excludeStatuses: [3], |
| | | placeholder: 'è¯·éæ©ç产工å', |
| | | onChange: (item: MesProWorkOrderApi.WorkOrder | undefined) => |
| | | handleWorkOrderChange(item, formApi), |
| | | }, |
| | | rules: 'selectRequired', |
| | | }, |
| | | { |
| | | fieldName: 'productName', |
| | | label: '产ååç§°', |
| | | component: 'Input', |
| | | componentProps: { |
| | | disabled: true, |
| | | placeholder: '鿩工ååèªå¨å¸¦åº', |
| | | }, |
| | | dependencies: { |
| | | triggerFields: ['workOrderId'], |
| | | show: (values) => !!values.workOrderId, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'batchCode', |
| | | label: 'çäº§æ¹æ¬¡å·', |
| | | component: 'Input', |
| | | componentProps: { |
| | | disabled: true, |
| | | placeholder: '鿩工ååèªå¨å¸¦åº', |
| | | }, |
| | | dependencies: { |
| | | triggerFields: ['workOrderId'], |
| | | show: (values) => !!values.workOrderId, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'saleOrderId', |
| | | label: 'éå®è®¢å', |
| | | component: markRaw(ErpSaleOrderSelect), |
| | | componentProps: { |
| | | placeholder: 'åºåå¤è´§å·¥åå¯ä¸éï¼å®¢æ·è®¢åå·¥åå°èªå¨å¹é
', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'sampleQuantity', |
| | | label: 'çæ ·æ°é', |
| | | component: 'InputNumber', |
| | | componentProps: { |
| | | class: '!w-full', |
| | | min: 1, |
| | | placeholder: '请è¾å
¥çæ ·æ°é', |
| | | precision: 0, |
| | | }, |
| | | rules: 'required', |
| | | }, |
| | | { |
| | | fieldName: 'sampleDate', |
| | | label: 'çæ ·æ¥æ', |
| | | component: 'DatePicker', |
| | | componentProps: { |
| | | class: '!w-full', |
| | | format: 'YYYY-MM-DD', |
| | | placeholder: 'è¯·éæ©çæ ·æ¥æ', |
| | | valueFormat: 'YYYY-MM-DD', |
| | | onChange: (value: string) => syncExpiryDate(value, formApi), |
| | | }, |
| | | rules: 'required', |
| | | }, |
| | | { |
| | | fieldName: 'expiryDate', |
| | | label: 'çæ ·å°ææ¥æ', |
| | | component: 'DatePicker', |
| | | componentProps: { |
| | | class: '!w-full', |
| | | disabled: true, |
| | | format: 'YYYY-MM-DD', |
| | | valueFormat: 'YYYY-MM-DD', |
| | | }, |
| | | help: `æçæ ·æ¥æ + ${RETENTION_MONTHS} 个æèªå¨è®¡ç®`, |
| | | }, |
| | | { |
| | | fieldName: 'keeperUserId', |
| | | label: 'çæ ·äºº', |
| | | component: 'ApiSelect', |
| | | componentProps: { |
| | | allowClear: true, |
| | | api: getSimpleUserList, |
| | | labelField: 'nickname', |
| | | placeholder: 'ä¸éåé»è®¤ä¸ºå½åç»å½ç¨æ·', |
| | | showSearch: true, |
| | | optionFilterProp: 'label', |
| | | valueField: 'id', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'remark', |
| | | label: '夿³¨', |
| | | component: 'Textarea', |
| | | formItemClass: 'col-span-2', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥å¤æ³¨', |
| | | rows: 3, |
| | | }, |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | /** å表çæç´¢è¡¨å */ |
| | | export function useGridFormSchema(): VbenFormSchema[] { |
| | | return [ |
| | | { |
| | | fieldName: 'code', |
| | | label: 'çæ ·ç¼å·', |
| | | component: 'Input', |
| | | componentProps: { |
| | | allowClear: true, |
| | | placeholder: '请è¾å
¥çæ ·ç¼å·', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'workOrderCode', |
| | | label: 'ç产工åç¼å·', |
| | | component: 'Input', |
| | | componentProps: { |
| | | allowClear: true, |
| | | placeholder: '请è¾å
¥ç产工åç¼å·', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'saleOrderNo', |
| | | label: 'éå®è®¢åå·', |
| | | component: 'Input', |
| | | componentProps: { |
| | | allowClear: true, |
| | | placeholder: '请è¾å
¥éå®è®¢åå·', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'productId', |
| | | label: '产å', |
| | | component: markRaw(MdItemSelect), |
| | | componentProps: { |
| | | placeholder: 'è¯·éæ©äº§å', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'batchCode', |
| | | label: 'çäº§æ¹æ¬¡å·', |
| | | component: 'Input', |
| | | componentProps: { |
| | | allowClear: true, |
| | | placeholder: '请è¾å
¥çäº§æ¹æ¬¡å·', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'sampleDate', |
| | | label: 'çæ ·æ¥æ', |
| | | component: 'RangePicker', |
| | | componentProps: { |
| | | ...getDateRangePickerDefaultProps(), |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'expiryDate', |
| | | label: 'å°ææ¥æ', |
| | | component: 'RangePicker', |
| | | componentProps: { |
| | | ...getDateRangePickerDefaultProps(), |
| | | }, |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | /** å表çåæ®µ */ |
| | | export function useGridColumns(): VxeTableGridOptions<MesQcRetentionSampleApi.RetentionSample>['columns'] { |
| | | return [ |
| | | { |
| | | field: 'code', |
| | | title: 'çæ ·ç¼å·', |
| | | width: 160, |
| | | }, |
| | | { |
| | | field: 'workOrderCode', |
| | | title: 'ç产工åç¼å·', |
| | | width: 150, |
| | | }, |
| | | { |
| | | field: 'saleOrderNo', |
| | | title: 'éå®è®¢åå·', |
| | | width: 150, |
| | | }, |
| | | { |
| | | field: 'productCode', |
| | | title: '产åç¼ç ', |
| | | width: 130, |
| | | }, |
| | | { |
| | | field: 'productName', |
| | | title: '产ååç§°', |
| | | minWidth: 160, |
| | | }, |
| | | { |
| | | field: 'batchCode', |
| | | title: 'çäº§æ¹æ¬¡å·', |
| | | width: 140, |
| | | }, |
| | | { |
| | | field: 'sampleQuantity', |
| | | title: 'çæ ·æ°é', |
| | | width: 100, |
| | | }, |
| | | { |
| | | field: 'sampleDate', |
| | | title: 'çæ ·æ¥æ', |
| | | width: 120, |
| | | formatter: 'formatDate', |
| | | }, |
| | | { |
| | | field: 'expiryDate', |
| | | title: 'å°ææ¥æ', |
| | | width: 120, |
| | | formatter: 'formatDate', |
| | | }, |
| | | { |
| | | title: 'çæ ·ç¶æ', |
| | | width: 100, |
| | | slots: { default: 'expiryStatus' }, |
| | | }, |
| | | { |
| | | field: 'keeperNickname', |
| | | title: 'çæ ·äºº', |
| | | width: 100, |
| | | }, |
| | | { |
| | | field: 'createTime', |
| | | title: 'å建æ¶é´', |
| | | width: 180, |
| | | formatter: 'formatDate', |
| | | }, |
| | | { |
| | | title: 'æä½', |
| | | width: 160, |
| | | fixed: 'right', |
| | | slots: { default: 'actions' }, |
| | | }, |
| | | ]; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; |
| | | import type { MesQcRetentionSampleApi } from '#/api/mes/qc/retention-sample'; |
| | | |
| | | import { Page, useVbenModal } from '@vben/common-ui'; |
| | | |
| | | import { message, Tag } from 'ant-design-vue'; |
| | | import dayjs from 'dayjs'; |
| | | |
| | | import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; |
| | | import { |
| | | deleteRetentionSample, |
| | | getRetentionSamplePage, |
| | | } from '#/api/mes/qc/retention-sample'; |
| | | import { $t } from '#/locales'; |
| | | |
| | | import { useGridColumns, useGridFormSchema } from './data'; |
| | | import Form from './modules/form.vue'; |
| | | |
| | | defineOptions({ name: 'RetentionSampleIndex' }); |
| | | |
| | | const [FormModal, formModalApi] = useVbenModal({ |
| | | connectedComponent: Form, |
| | | destroyOnClose: true, |
| | | }); |
| | | |
| | | /** å·æ°è¡¨æ ¼ */ |
| | | function handleRefresh() { |
| | | gridApi.query(); |
| | | } |
| | | |
| | | /** å建 */ |
| | | function handleCreate() { |
| | | formModalApi.setData(null).open(); |
| | | } |
| | | |
| | | /** ç¼è¾ */ |
| | | function handleEdit(row: MesQcRetentionSampleApi.RetentionSample) { |
| | | formModalApi.setData(row).open(); |
| | | } |
| | | |
| | | /** å é¤ */ |
| | | async function handleDelete(row: MesQcRetentionSampleApi.RetentionSample) { |
| | | const hideLoading = message.loading({ |
| | | content: $t('ui.actionMessage.deleting', [row.code]), |
| | | duration: 0, |
| | | }); |
| | | try { |
| | | await deleteRetentionSample(row.id!); |
| | | message.success($t('ui.actionMessage.deleteSuccess', [row.code])); |
| | | handleRefresh(); |
| | | } finally { |
| | | hideLoading(); |
| | | } |
| | | } |
| | | |
| | | /** æ¯å¦å·²å°æ */ |
| | | function isExpired(row: MesQcRetentionSampleApi.RetentionSample) { |
| | | return row.expiryDate ? dayjs(row.expiryDate).isBefore(dayjs(), 'day') : false; |
| | | } |
| | | |
| | | const [Grid, gridApi] = useVbenVxeGrid({ |
| | | formOptions: { |
| | | schema: useGridFormSchema(), |
| | | }, |
| | | gridOptions: { |
| | | columns: useGridColumns(), |
| | | height: 'auto', |
| | | keepSource: true, |
| | | proxyConfig: { |
| | | ajax: { |
| | | query: async ({ page }, formValues) => { |
| | | return await getRetentionSamplePage({ |
| | | pageNo: page.currentPage, |
| | | pageSize: page.pageSize, |
| | | ...formValues, |
| | | }); |
| | | }, |
| | | }, |
| | | }, |
| | | rowConfig: { |
| | | keyField: 'id', |
| | | isHover: true, |
| | | }, |
| | | toolbarConfig: { |
| | | refresh: true, |
| | | search: true, |
| | | }, |
| | | } as VxeTableGridOptions<MesQcRetentionSampleApi.RetentionSample>, |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <Page auto-content-height> |
| | | <FormModal @success="handleRefresh" /> |
| | | <Grid table-title="çæ ·ç®¡ç"> |
| | | <template #toolbar-tools> |
| | | <TableAction |
| | | :actions="[ |
| | | { |
| | | label: $t('ui.actionTitle.create', ['çæ ·è®°å½']), |
| | | type: 'primary', |
| | | icon: ACTION_ICON.ADD, |
| | | auth: ['mes:qc-retention-sample:create'], |
| | | onClick: handleCreate, |
| | | }, |
| | | ]" |
| | | /> |
| | | </template> |
| | | <template #expiryStatus="{ row }"> |
| | | <Tag v-if="isExpired(row)" color="red">已尿</Tag> |
| | | <Tag v-else color="green">çæ ·ä¸</Tag> |
| | | </template> |
| | | <template #actions="{ row }"> |
| | | <TableAction |
| | | :actions="[ |
| | | { |
| | | label: $t('common.edit'), |
| | | type: 'link', |
| | | icon: ACTION_ICON.EDIT, |
| | | auth: ['mes:qc-retention-sample:update'], |
| | | onClick: handleEdit.bind(null, row), |
| | | }, |
| | | { |
| | | label: $t('common.delete'), |
| | | type: 'link', |
| | | danger: true, |
| | | icon: ACTION_ICON.DELETE, |
| | | auth: ['mes:qc-retention-sample:delete'], |
| | | popConfirm: { |
| | | title: $t('ui.actionMessage.deleteConfirm', [row.code]), |
| | | confirm: handleDelete.bind(null, row), |
| | | }, |
| | | }, |
| | | ]" |
| | | /> |
| | | </template> |
| | | </Grid> |
| | | </Page> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { MesQcRetentionSampleApi } from '#/api/mes/qc/retention-sample'; |
| | | |
| | | import { computed, ref } from 'vue'; |
| | | |
| | | import { useVbenModal } from '@vben/common-ui'; |
| | | |
| | | import { message } from 'ant-design-vue'; |
| | | import dayjs from 'dayjs'; |
| | | |
| | | import { useVbenForm } from '#/adapter/form'; |
| | | import { |
| | | createRetentionSample, |
| | | getRetentionSample, |
| | | updateRetentionSample, |
| | | } from '#/api/mes/qc/retention-sample'; |
| | | import { $t } from '#/locales'; |
| | | |
| | | import { useFormSchema } from '../data'; |
| | | |
| | | defineOptions({ name: 'RetentionSampleForm' }); |
| | | |
| | | const emit = defineEmits(['success']); |
| | | const formData = ref<MesQcRetentionSampleApi.RetentionSample>(); |
| | | const getTitle = computed(() => { |
| | | return formData.value?.id |
| | | ? $t('ui.actionTitle.edit', ['çæ ·è®°å½']) |
| | | : $t('ui.actionTitle.create', ['çæ ·è®°å½']); |
| | | }); |
| | | |
| | | const [Form, formApi] = useVbenForm({ |
| | | commonConfig: { |
| | | componentProps: { |
| | | class: 'w-full', |
| | | }, |
| | | formItemClass: 'col-span-2', |
| | | labelWidth: 110, |
| | | }, |
| | | layout: 'horizontal', |
| | | schema: [], |
| | | showDefaultActions: false, |
| | | }); |
| | | |
| | | const [Modal, modalApi] = useVbenModal({ |
| | | async onConfirm() { |
| | | const { valid } = await formApi.validate(); |
| | | if (!valid) { |
| | | return; |
| | | } |
| | | modalApi.lock(); |
| | | const data = |
| | | (await formApi.getValues()) as MesQcRetentionSampleApi.RetentionSample; |
| | | try { |
| | | await (formData.value?.id |
| | | ? updateRetentionSample(data) |
| | | : createRetentionSample(data)); |
| | | await modalApi.close(); |
| | | emit('success'); |
| | | message.success($t('ui.actionMessage.operationSuccess')); |
| | | } finally { |
| | | modalApi.unlock(); |
| | | } |
| | | }, |
| | | async onOpenChange(isOpen: boolean) { |
| | | if (!isOpen) { |
| | | formData.value = undefined; |
| | | return; |
| | | } |
| | | formData.value = undefined; |
| | | const data = modalApi.getData<MesQcRetentionSampleApi.RetentionSample>(); |
| | | const isEdit = !!data?.id; |
| | | formApi.setState({ schema: useFormSchema(isEdit ? 'update' : 'create', formApi) }); |
| | | await formApi.resetForm(); |
| | | if (!isEdit) { |
| | | await formApi.setValues({ |
| | | sampleDate: dayjs().format('YYYY-MM-DD'), |
| | | expiryDate: dayjs().add(6, 'month').format('YYYY-MM-DD'), |
| | | sampleQuantity: 2, |
| | | }); |
| | | return; |
| | | } |
| | | modalApi.lock(); |
| | | try { |
| | | formData.value = await getRetentionSample(data!.id!); |
| | | await formApi.setValues(formData.value); |
| | | } finally { |
| | | modalApi.unlock(); |
| | | } |
| | | }, |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <Modal :title="getTitle" class="w-3/5"> |
| | | <Form class="mx-4" /> |
| | | </Modal> |
| | | </template> |
| | |
| | | import { Button } from 'ant-design-vue'; |
| | | |
| | | import { generateAutoCode } from '#/api/mes/md/autocode/record'; |
| | | import { getItem } from '#/api/mes/md/item'; |
| | | import { getSimpleUserList } from '#/api/system/user'; |
| | | import { ErpSaleOrderSelect } from '#/views/erp/sale/order/components'; |
| | | import { MdClientSelect } from '#/views/mes/md/client/components'; |
| | |
| | | ]; |
| | | } |
| | | |
| | | /** ç»è£
尾箱é¢è§ææ¡ï¼æ´é¤æ¶æ¾ç¤ºæ¯ç®±æ°éå¹¶æ 记满è£
*/ |
| | | function buildTailPreview(quantity?: number, boxQuantity?: number): string { |
| | | if (!quantity || quantity <= 0 || !boxQuantity || boxQuantity <= 0) { |
| | | return 'è¾å
¥è£
ç®±æ°éåèªå¨è®¡ç®'; |
| | | } |
| | | const boxCount = Math.ceil(quantity / boxQuantity - 1e-9); |
| | | const tail = Number((quantity - (boxCount - 1) * boxQuantity).toFixed(2)); |
| | | const full = Math.abs(tail - boxQuantity) < 1e-6; |
| | | return `é¢è®¡è£
${boxCount} ç®±ï¼å°¾ç®± ${full ? boxQuantity : tail} ä»¶${ |
| | | full ? 'ï¼æ»¡è£
ï¼' : '' |
| | | }`; |
| | | } |
| | | |
| | | /** å·æ°è£
ç®±é¢è§ï¼æè¡¨åä¸çè£
ç®±æ°é䏿¯ç®±æ°é宿¶è®¡ç®ï¼ */ |
| | | export async function refreshTailPreview(formApi?: VbenFormApi) { |
| | | if (!formApi) { |
| | | return; |
| | | } |
| | | const values = (await formApi.getValues()) as { |
| | | boxQuantity?: number; |
| | | quantity?: number; |
| | | }; |
| | | await formApi.setFieldValue( |
| | | 'tailPreview', |
| | | buildTailPreview(values.quantity, values.boxQuantity), |
| | | ); |
| | | } |
| | | |
| | | /** è£
ç®±æç»ç表å */ |
| | | export function useLineFormSchema(): VbenFormSchema[] { |
| | | export function useLineFormSchema(formApi?: VbenFormApi): VbenFormSchema[] { |
| | | return [ |
| | | { |
| | | fieldName: 'workOrderId', |
| | |
| | | component: markRaw(ProWorkOrderSelect), |
| | | componentProps: { |
| | | placeholder: 'è¯·éæ©ç产订å', |
| | | // éæ©ç产订ååï¼èªå¨å¸¦åºè¯¥è®¢åç产åç©æ |
| | | onChange: async (order?: { productId?: number }) => { |
| | | await formApi?.setFieldValue('itemId', order?.productId); |
| | | // 带åºäº§åç©æçæ¯ç®±æ°éï¼ç¨äºè®¡ç®å°¾ç®± |
| | | const item = order?.productId |
| | | ? await getItem(order.productId).catch(() => undefined) |
| | | : undefined; |
| | | await formApi?.setFieldValue('boxQuantity', item?.boxQuantity); |
| | | await refreshTailPreview(formApi); |
| | | }, |
| | | }, |
| | | rules: 'selectRequired', |
| | | }, |
| | |
| | | label: '产åç©æ', |
| | | component: markRaw(MdItemSelect), |
| | | componentProps: { |
| | | placeholder: 'è¯·éæ©äº§åç©æ', |
| | | disabled: true, |
| | | placeholder: 'éæ©ç产订ååèªå¨å¸¦åº', |
| | | }, |
| | | rules: 'selectRequired', |
| | | help: 'èªå¨åç产订åç产åï¼æ éæå¨éæ©ã', |
| | | }, |
| | | { |
| | | fieldName: 'boxQuantity', |
| | | component: 'Input', |
| | | dependencies: { |
| | | triggerFields: [''], |
| | | show: () => false, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'quantity', |
| | |
| | | min: 0.01, |
| | | placeholder: '请è¾å
¥è£
ç®±æ°é', |
| | | precision: 2, |
| | | onChange: () => { |
| | | refreshTailPreview(formApi); |
| | | }, |
| | | }, |
| | | rules: 'required', |
| | | }, |
| | | { |
| | | fieldName: 'tailPreview', |
| | | label: 'è£
ç®±é¢è§', |
| | | component: 'Input', |
| | | formItemClass: 'col-span-2', |
| | | componentProps: { |
| | | disabled: true, |
| | | placeholder: 'è¾å
¥è£
ç®±æ°éåèªå¨è®¡ç®', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'expireDate', |
| | |
| | | width: 100, |
| | | }, |
| | | { |
| | | field: 'boxCount', |
| | | title: 'ç®±æ°', |
| | | width: 80, |
| | | }, |
| | | { |
| | | field: 'tailBoxQuantity', |
| | | title: '尾箱æ°é', |
| | | width: 110, |
| | | slots: { default: 'tailBoxQuantity' }, |
| | | }, |
| | | { |
| | | field: 'workOrderCode', |
| | | title: 'ç产订åç¼å·', |
| | | minWidth: 140, |
| | |
| | | type: 'link', |
| | | icon: ACTION_ICON.EDIT, |
| | | auth: ['mes:wm-package:update'], |
| | | ifShow: row.status === MesWmPackageStatusEnum.PREPARE, |
| | | disabled: row.status !== MesWmPackageStatusEnum.PREPARE, |
| | | onClick: handleEdit.bind(null, row), |
| | | }, |
| | | { |
| | |
| | | danger: true, |
| | | icon: ACTION_ICON.DELETE, |
| | | auth: ['mes:wm-package:delete'], |
| | | ifShow: row.status === MesWmPackageStatusEnum.PREPARE, |
| | | disabled: row.status !== MesWmPackageStatusEnum.PREPARE, |
| | | popConfirm: { |
| | | title: $t('ui.actionMessage.deleteConfirm', [row.code]), |
| | | confirm: handleDelete.bind(null, row), |
| | |
| | | import { message } from 'ant-design-vue'; |
| | | |
| | | import { useVbenForm } from '#/adapter/form'; |
| | | import { getItem } from '#/api/mes/md/item'; |
| | | import { |
| | | createPackageLine, |
| | | getPackageLine, |
| | |
| | | } from '#/api/mes/wm/packages/line'; |
| | | import { $t } from '#/locales'; |
| | | |
| | | import { useLineFormSchema } from '../data'; |
| | | import { refreshTailPreview, useLineFormSchema } from '../data'; |
| | | |
| | | const emit = defineEmits(['success']); |
| | | const formData = ref<MesWmPackageLineApi.PackageLine>(); |
| | |
| | | labelWidth: 100, |
| | | }, |
| | | layout: 'horizontal', |
| | | schema: useLineFormSchema(), |
| | | schema: [], |
| | | showDefaultActions: false, |
| | | wrapperClass: 'grid-cols-2', |
| | | }); |
| | |
| | | } |
| | | modalApi.lock(); |
| | | // æäº¤è¡¨å |
| | | const data = (await formApi.getValues()) as MesWmPackageLineApi.PackageLine; |
| | | const data = (await formApi.getValues()) as MesWmPackageLineApi.PackageLine & { |
| | | boxQuantity?: number; |
| | | tailPreview?: string; |
| | | }; |
| | | data.packageId = packageId.value; |
| | | // ä»
ç¨äºé¢è§çåæ®µä¸æäº¤ |
| | | delete data.boxQuantity; |
| | | delete data.tailPreview; |
| | | try { |
| | | await (formData.value?.id |
| | | ? updatePackageLine({ ...data, id: formData.value.id }) |
| | |
| | | // å è½½æ°æ® |
| | | const data = modalApi.getData<{ id?: number; packageId: number }>(); |
| | | packageId.value = data.packageId; |
| | | formApi.setState({ schema: useLineFormSchema(formApi) }); |
| | | if (!data.id) { |
| | | return; |
| | | } |
| | |
| | | formData.value = await getPackageLine(data.id); |
| | | // è®¾ç½®å° values |
| | | await formApi.setValues(formData.value); |
| | | // å¸¦åºæ¯ç®±æ°éå¹¶å·æ°å°¾ç®±é¢è§ |
| | | if (formData.value.itemId) { |
| | | const item = await getItem(formData.value.itemId).catch( |
| | | () => undefined, |
| | | ); |
| | | await formApi.setFieldValue('boxQuantity', item?.boxQuantity); |
| | | } |
| | | await refreshTailPreview(formApi); |
| | | } finally { |
| | | modalApi.unlock(); |
| | | } |
| | |
| | | ]" |
| | | /> |
| | | </template> |
| | | <template #tailBoxQuantity="{ row }"> |
| | | <span v-if="row.tailBoxQuantity !== undefined && row.tailBoxQuantity !== null"> |
| | | {{ row.tailBoxQuantity }}{{ row.tailBoxFull ? 'ï¼æ»¡è£
ï¼' : '' }} |
| | | </span> |
| | | <span v-else>â</span> |
| | | </template> |
| | | <template #actions="{ row }"> |
| | | <TableAction |
| | | :actions="[ |
| | |
| | | label: $t('common.edit'), |
| | | type: 'link', |
| | | icon: ACTION_ICON.EDIT, |
| | | ifShow: editable, |
| | | disabled: !editable, |
| | | onClick: handleEdit.bind(null, row), |
| | | }, |
| | | { |
| | |
| | | type: 'link', |
| | | danger: true, |
| | | icon: ACTION_ICON.DELETE, |
| | | ifShow: editable, |
| | | disabled: !editable, |
| | | popConfirm: { |
| | | title: $t('ui.actionMessage.deleteConfirm', [row.itemName]), |
| | | confirm: handleDelete.bind(null, row), |
| | |
| | | type: 'link', |
| | | danger: true, |
| | | icon: ACTION_ICON.DELETE, |
| | | ifShow: editable, |
| | | disabled: !editable, |
| | | popConfirm: { |
| | | title: '确认å°è¯¥è£
ç®±åä»åç®±å表ä¸ç§»é¤ï¼', |
| | | confirm: handleRemoveChild.bind(null, row), |
| | |
| | | width: 100, |
| | | }, |
| | | { |
| | | field: 'boxCount', |
| | | title: 'ç®±æ°', |
| | | width: 80, |
| | | }, |
| | | { |
| | | field: 'batchCode', |
| | | title: 'æ¹æ¬¡å·', |
| | | minWidth: 120, |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import type { VbenFormSchema } from '#/adapter/form'; |
| | | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; |
| | | import type { MesWmProductSalesStatisticsApi } from '#/api/mes/wm/productsales/statistics'; |
| | | |
| | | import { markRaw } from 'vue'; |
| | | |
| | | import { getDateRangePickerDefaultProps } from '#/utils'; |
| | | import { MdClientSelect } from '#/views/mes/md/client/components'; |
| | | import { MdItemSelect } from '#/views/mes/md/item/components'; |
| | | |
| | | /** å表çæç´¢è¡¨å */ |
| | | export function useGridFormSchema(): VbenFormSchema[] { |
| | | return [ |
| | | { |
| | | fieldName: 'clientId', |
| | | label: '客æ·', |
| | | component: markRaw(MdClientSelect), |
| | | componentProps: { |
| | | placeholder: 'è¯·éæ©å®¢æ·', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'salesOrderCode', |
| | | label: 'éå®è®¢åç¼å·', |
| | | component: 'Input', |
| | | componentProps: { |
| | | allowClear: true, |
| | | placeholder: '请è¾å
¥éå®è®¢åç¼å·', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'itemId', |
| | | label: '产å', |
| | | component: markRaw(MdItemSelect), |
| | | componentProps: { |
| | | placeholder: 'è¯·éæ©äº§å', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'shipmentDate', |
| | | label: 'åè´§æ¥æ', |
| | | component: 'RangePicker', |
| | | componentProps: { |
| | | ...getDateRangePickerDefaultProps(), |
| | | allowClear: true, |
| | | }, |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | | /** å表çåæ®µ */ |
| | | export function useGridColumns(): VxeTableGridOptions<MesWmProductSalesStatisticsApi.Statistics>['columns'] { |
| | | return [ |
| | | { |
| | | field: 'clientName', |
| | | title: '客æ·åç§°', |
| | | minWidth: 140, |
| | | }, |
| | | { |
| | | field: 'salesOrderCode', |
| | | title: 'éå®è®¢åç¼å·', |
| | | minWidth: 160, |
| | | }, |
| | | { |
| | | field: 'itemCode', |
| | | title: '产åç¼ç ', |
| | | minWidth: 130, |
| | | }, |
| | | { |
| | | field: 'itemName', |
| | | title: '产ååç§°', |
| | | minWidth: 160, |
| | | }, |
| | | { |
| | | field: 'specification', |
| | | title: 'è§æ ¼åå·', |
| | | minWidth: 130, |
| | | }, |
| | | { |
| | | field: 'unitMeasureName', |
| | | title: 'åä½', |
| | | width: 90, |
| | | align: 'center', |
| | | }, |
| | | { |
| | | field: 'totalQuantity', |
| | | title: 'åºåºæ»æ°é', |
| | | width: 120, |
| | | align: 'right', |
| | | }, |
| | | { |
| | | field: 'totalBoxCount', |
| | | title: 'æ»ç®±æ°', |
| | | width: 100, |
| | | align: 'right', |
| | | }, |
| | | { |
| | | field: 'shipmentCount', |
| | | title: 'å货次æ°', |
| | | width: 100, |
| | | align: 'center', |
| | | }, |
| | | { |
| | | field: 'lastShipmentDate', |
| | | title: 'æååè´§æ¶é´', |
| | | width: 180, |
| | | formatter: 'formatDate', |
| | | }, |
| | | ]; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; |
| | | import type { MesWmProductSalesStatisticsApi } from '#/api/mes/wm/productsales/statistics'; |
| | | |
| | | import { Page } from '@vben/common-ui'; |
| | | |
| | | import { useVbenVxeGrid } from '#/adapter/vxe-table'; |
| | | import { getProductSalesStatisticsPage } from '#/api/mes/wm/productsales/statistics'; |
| | | |
| | | import { useGridColumns, useGridFormSchema } from './data'; |
| | | |
| | | const [Grid] = useVbenVxeGrid({ |
| | | formOptions: { |
| | | schema: useGridFormSchema(), |
| | | }, |
| | | gridOptions: { |
| | | columns: useGridColumns(), |
| | | height: 'auto', |
| | | keepSource: true, |
| | | proxyConfig: { |
| | | ajax: { |
| | | query: async ({ page }, formValues) => { |
| | | const result = await getProductSalesStatisticsPage({ |
| | | pageNo: page.currentPage, |
| | | pageSize: page.pageSize, |
| | | ...formValues, |
| | | }); |
| | | result.list = result.list.map(row => ({ |
| | | ...row, |
| | | id: `${row.clientId ?? ''}_${row.salesOrderCode ?? ''}_${row.itemId ?? ''}`, |
| | | })); |
| | | return result; |
| | | }, |
| | | }, |
| | | }, |
| | | rowConfig: { |
| | | keyField: 'id', |
| | | isHover: true, |
| | | }, |
| | | toolbarConfig: { |
| | | refresh: true, |
| | | search: true, |
| | | }, |
| | | } as VxeTableGridOptions<MesWmProductSalesStatisticsApi.Statistics>, |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <Page auto-content-height> |
| | | <Grid table-title="åè´§ç»è®¡" /> |
| | | </Page> |
| | | </template> |