银川
1.联调产品维护页面
2.添加采购申请页面、联调
| | |
| | | |
| | | src/packages/icons: |
| | | dependencies: |
| | | '@iconify/json': |
| | | specifier: ^2.2.483 |
| | | version: 2.2.489 |
| | | '@vben-core/icons': |
| | | specifier: workspace:* |
| | | version: link:../@core/base/icons |
| | |
| | | export interface PurchaseRequest { |
| | | id?: number; // ç¼å· |
| | | no?: string; // ç³è¯·ç¼å· |
| | | status?: number; // ç¶æï¼0æªæäº¤ 10审æ¹ä¸ 20å®¡æ ¸éè¿ 30å®¡æ ¸ä¸éè¿ 40已忶 |
| | | requestUserId?: number; // ç³è¯·äººç¼å· |
| | | requestUserName?: string; // ç³è¯·äººåç§° |
| | | requestDeptId?: number; // ç³è¯·é¨é¨ç¼å· |
| | | requestDeptName?: string; // ç³è¯·é¨é¨åç§° |
| | | requestTime: string; // ç³è¯·æ¶é´ |
| | | requestReason?: string; // ç³è¯·çç± |
| | | supplierId?: number; // ä¾åºåç¼å· |
| | | supplierName?: string; // ä¾åºååç§° |
| | | requestTime: string; // ç³è¯·æ¶é´ |
| | | requesterId?: number; // ç³è¯·äººç¼å· |
| | | requesterName?: string; // ç³è¯·äººåç§° |
| | | reason?: string; // ç³è¯·çç± |
| | | estimatedAmount?: number; // é¢ä¼°éé¢ |
| | | status?: number; // ç¶æ |
| | | discountPercent?: number; // 伿 ç |
| | | fileUrl?: string; // éä»¶å°å |
| | | remark?: string; // 夿³¨ |
| | | totalCount?: number; // æ»æ°é |
| | | totalPrice?: number; // æ»éé¢ |
| | | productNames?: string; // 产ååç§°å表 |
| | | orderId?: number; // çæçéè´è®¢åç¼å· |
| | | orderNo?: string; // çæçéè´è®¢åå· |
| | | items?: PurchaseRequestItem[]; // ç³è¯·æç» |
| | | createTime?: string; // å建æ¶é´ |
| | | } |
| | |
| | | id?: number; // ç¼å· |
| | | productId: number; // 产åç¼å· |
| | | productName?: string; // 产ååç§° |
| | | productUnitName?: string; // 产ååä½ |
| | | productUnitId?: number; // 产ååä½ç¼å· |
| | | productUnitName?: string; // 产ååä½åç§° |
| | | productBarCode?: string; // äº§åæ¡ç |
| | | requestCount: number; // ç³è¯·æ°é |
| | | estimatedPrice?: number; // é¢ä¼°åä»· |
| | | requiredDate?: string; // éæ±æ¥æ |
| | | productPrice?: number; // 产ååä»·ï¼åèä»·ï¼ |
| | | count: number; // æ°é |
| | | taxPercent?: number; // ç¨ç |
| | | demandTime?: string; // éæ±æ¥æ |
| | | remark?: string; // 夿³¨ |
| | | } |
| | | } |
| | |
| | | return requestClient.put('/erp/purchase-request/update', data); |
| | | } |
| | | |
| | | /** 审æ¹éè´ç³è¯· */ |
| | | export function approvePurchaseRequest(params: { |
| | | id: number; |
| | | status: number; |
| | | remark?: string; |
| | | }) { |
| | | return requestClient.put('/erp/purchase-request/approve', null, { params }); |
| | | /** æäº¤éè´ç³è¯·å®¡æ¹ */ |
| | | export function submitPurchaseRequest(id: number, processDefinitionKey: string) { |
| | | return requestClient.put('/erp/purchase-request/submit', null, { params: { id, processDefinitionKey } }); |
| | | } |
| | | |
| | | /** 转为éè´è®¢å */ |
| | | export function convertToPurchaseOrder(id: number) { |
| | | return requestClient.put('/erp/purchase-request/convert', null, { params: { id } }); |
| | | /** çæéè´è®¢å */ |
| | | export function generatePurchaseOrder(id: number, supplierId: number) { |
| | | return requestClient.post<number>('/erp/purchase-request/generate-order', null, { params: { id, supplierId } }); |
| | | } |
| | | |
| | | /** å é¤éè´ç³è¯· */ |
| | |
| | | export function exportPurchaseRequest(params: any) { |
| | | return requestClient.download('/erp/purchase-request/export-excel', { params }); |
| | | } |
| | | |
| | | /** è·åå®¡æ¹æµç¨å表 */ |
| | | export function getApproveProcessList(id: number) { |
| | | return requestClient.get(`/erp/purchase-request/approve-process-list`, { params: { id } }); |
| | | } |
| | |
| | | } |
| | | }, |
| | | "dependencies": { |
| | | "@iconify/json": "^2.2.483", |
| | | "@vben-core/icons": "workspace:*" |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | /** |
| | | * ç¦»çº¿å¾æ é¢å è½½é
ç½® |
| | | * ææ¶ç¦ç¨ï¼ä½¿ç¨å¨çº¿å è½½æ¹å¼ |
| | | */ |
| | | |
| | | import { addCollection } from '@iconify/vue'; |
| | | |
| | | /** |
| | | * åå§åç¦»çº¿å¾æ |
| | | * ç®å使ç¨å¨çº¿å è½½ï¼æ¤å½æ°ä¸ºç©º |
| | | */ |
| | | export function initOfflineIcons() { |
| | | // ææ¶ä¸é¢å è½½ï¼ä½¿ç¨ Iconify å¨çº¿ API |
| | | // 妿éè¦ç¦»çº¿å è½½ï¼éè¦å° JSON æä»¶å¤å¶å°é¡¹ç®ä¸ |
| | | } |
| | |
| | | companyName: import.meta.env.VITE_APP_TITLE, |
| | | companySiteLink: 'https://gitee.com/yudaocode/yudao-ui-admin-vben', |
| | | }, |
| | | tabbar: { |
| | | /** å·æ°æµè§å¨ååªä¿çå½å页é¢ï¼ä¸æä¹
åæææ ç¾é¡µ */ |
| | | persist: false, |
| | | }, |
| | | }); |
| | | |
| | | export const preferencesExtension = |
| | |
| | | import { |
| | | deleteItem, |
| | | deleteItemList, |
| | | exportItem, |
| | | // exportItem, |
| | | getItemPage, |
| | | updateItemStatus, |
| | | } from '#/api/mdm/item'; |
| | |
| | | } |
| | | |
| | | /** 导åºè¡¨æ ¼ */ |
| | | async function handleExport() { |
| | | const data = await exportItem(await gridApi.formApi.getValues()); |
| | | downloadFileFromBlobPart({ fileName: 'ç©æ.xls', source: data }); |
| | | } |
| | | // async function handleExport() { |
| | | // const data = await exportItem(await gridApi.formApi.getValues()); |
| | | // downloadFileFromBlobPart({ fileName: 'ç©æ.xls', source: data }); |
| | | // } |
| | | |
| | | /** åå»ºç©æ */ |
| | | function handleCreate() { |
| | |
| | | auth: ['mdm:item:create'], |
| | | onClick: handleCreate, |
| | | }, |
| | | { |
| | | label: $t('ui.actionTitle.export'), |
| | | type: 'primary', |
| | | icon: ACTION_ICON.DOWNLOAD, |
| | | auth: ['mdm:item:export'], |
| | | onClick: handleExport, |
| | | }, |
| | | // { |
| | | // label: $t('ui.actionTitle.export'), |
| | | // type: 'primary', |
| | | // icon: ACTION_ICON.DOWNLOAD, |
| | | // auth: ['mdm:item:export'], |
| | | // onClick: handleExport, |
| | | // }, |
| | | { |
| | | label: $t('ui.actionTitle.deleteBatch'), |
| | | type: 'primary', |
| | |
| | | |
| | | /** ç¶æå¸¸é */ |
| | | export const PURCHASE_REQUEST_STATUS = { |
| | | PENDING: 0, // å¾
å®¡æ¹ |
| | | APPROVED: 1, // å·²å®¡æ¹ |
| | | REJECTED: 2, // å·²æç» |
| | | CONVERTED: 3, // 已转订å |
| | | DRAFT: 0, // æªæäº¤ï¼èç¨¿ï¼ |
| | | PROCESSING: 10, // 审æ¹ä¸ |
| | | APPROVED: 20, // å®¡æ ¸éè¿ |
| | | REJECTED: 30, // å®¡æ ¸ä¸éè¿ |
| | | CANCELLED: 40, // 已忶 |
| | | }; |
| | | |
| | | /** 表åçé
置项 */ |
| | |
| | | rules: 'required', |
| | | }, |
| | | { |
| | | label: 'ä¾åºå', |
| | | label: 'ä¾åºåï¼å¯éï¼', |
| | | fieldName: 'supplierId', |
| | | component: 'ApiSelect', |
| | | componentProps: { |
| | |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'reason', |
| | | fieldName: 'discountPercent', |
| | | label: '伿 ç(%)', |
| | | component: 'InputNumber', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥ä¼æ ç', |
| | | min: 0, |
| | | max: 100, |
| | | precision: 2, |
| | | disabled: formType === 'detail', |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'requestReason', |
| | | label: 'ç³è¯·çç±', |
| | | component: 'Textarea', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥ç³è¯·çç±', |
| | | autoSize: { minRows: 1, maxRows: 1 }, |
| | | disabled: formType === 'detail', |
| | | }, |
| | | formItemClass: 'col-span-2', |
| | | }, |
| | | { |
| | | fieldName: 'fileUrl', |
| | | label: 'éä»¶', |
| | | component: 'FileUpload', |
| | | componentProps: { |
| | | placeholder: '请ä¸ä¼ éä»¶', |
| | | maxNumber: 5, |
| | | maxSize: 20, |
| | | accept: ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'jpg', 'png'], |
| | | disabled: formType === 'detail', |
| | | }, |
| | | formItemClass: 'col-span-2', |
| | |
| | | minWidth: 80, |
| | | }, |
| | | { |
| | | field: 'requestCount', |
| | | title: 'ç³è¯·æ°é', |
| | | field: 'count', |
| | | title: 'æ°é', |
| | | minWidth: 120, |
| | | fixed: 'right', |
| | | slots: { default: 'requestCount' }, |
| | | slots: { default: 'count' }, |
| | | }, |
| | | { |
| | | field: 'estimatedPrice', |
| | | title: 'é¢ä¼°åä»·', |
| | | field: 'productPrice', |
| | | title: 'åä»·ï¼åèä»·ï¼', |
| | | minWidth: 120, |
| | | fixed: 'right', |
| | | slots: { default: 'estimatedPrice' }, |
| | | slots: { default: 'productPrice' }, |
| | | }, |
| | | { |
| | | field: 'totalPrice', |
| | |
| | | formatter: 'formatAmount2', |
| | | }, |
| | | { |
| | | field: 'requiredDate', |
| | | field: 'taxPercent', |
| | | title: 'ç¨ç(%)', |
| | | minWidth: 100, |
| | | fixed: 'right', |
| | | slots: { default: 'taxPercent' }, |
| | | }, |
| | | { |
| | | field: 'demandTime', |
| | | title: 'éæ±æ¥æ', |
| | | minWidth: 140, |
| | | fixed: 'right', |
| | | slots: { default: 'requiredDate' }, |
| | | slots: { default: 'demandTime' }, |
| | | }, |
| | | { |
| | | field: 'remark', |
| | |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'requesterId', |
| | | fieldName: 'requestUserId', |
| | | label: 'ç³è¯·äºº', |
| | | component: 'ApiSelect', |
| | | componentProps: { |
| | |
| | | component: 'Select', |
| | | componentProps: { |
| | | options: [ |
| | | { label: 'å¾
审æ¹', value: 0 }, |
| | | { label: '已审æ¹', value: 1 }, |
| | | { label: 'å·²æç»', value: 2 }, |
| | | { label: '已转订å', value: 3 }, |
| | | { label: 'æªæäº¤', value: 0 }, |
| | | { label: '审æ¹ä¸', value: 10 }, |
| | | { label: 'å®¡æ ¸éè¿', value: 20 }, |
| | | { label: 'å®¡æ ¸ä¸éè¿', value: 30 }, |
| | | { label: '已忶', value: 40 }, |
| | | ], |
| | | placeholder: 'è¯·éæ©ç¶æ', |
| | | allowClear: true, |
| | |
| | | formatter: 'formatDate', |
| | | }, |
| | | { |
| | | field: 'requesterName', |
| | | field: 'requestUserName', |
| | | title: 'ç³è¯·äºº', |
| | | minWidth: 120, |
| | | }, |
| | | { |
| | | field: 'estimatedAmount', |
| | | field: 'requestDeptName', |
| | | title: 'ç³è¯·é¨é¨', |
| | | minWidth: 120, |
| | | }, |
| | | { |
| | | field: 'totalCount', |
| | | title: 'ç³è¯·æ°é', |
| | | minWidth: 120, |
| | | formatter: 'formatAmount3', |
| | | }, |
| | | { |
| | | field: 'totalPrice', |
| | | title: 'é¢ä¼°éé¢', |
| | | formatter: 'formatAmount2', |
| | | minWidth: 120, |
| | | }, |
| | | { |
| | | field: 'productNames', |
| | | title: 'ç³è¯·äº§å', |
| | | minWidth: 150, |
| | | showOverflow: 'tooltip', |
| | | }, |
| | | { |
| | | field: 'orderNo', |
| | | title: 'éè´è®¢å', |
| | | minWidth: 180, |
| | | }, |
| | | { |
| | | field: 'status', |
| | |
| | | slots: { default: 'status' }, |
| | | }, |
| | | { |
| | | field: 'reason', |
| | | field: 'requestReason', |
| | | title: 'ç³è¯·çç±', |
| | | minWidth: 150, |
| | | showOverflow: 'tooltip', |
| | |
| | | }, |
| | | { |
| | | title: 'æä½', |
| | | width: 200, |
| | | width: 240, |
| | | fixed: 'right', |
| | | slots: { default: 'actions' }, |
| | | }, |
| | |
| | | |
| | | import { ref } from 'vue'; |
| | | |
| | | import { Page, useVbenModal } from '../../../../packages/effects/common-ui/src'; |
| | | import { confirm, Page, useVbenModal } from '../../../../packages/effects/common-ui/src'; |
| | | import { downloadFileFromBlobPart, isEmpty } from '../../../../packages/utils/src'; |
| | | |
| | | import { message, Tag } from 'ant-design-vue'; |
| | | |
| | | import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; |
| | | import { |
| | | approvePurchaseRequest, |
| | | convertToPurchaseOrder, |
| | | deletePurchaseRequest, |
| | | exportPurchaseRequest, |
| | | getApproveProcessList, |
| | | getPurchaseRequestPage, |
| | | submitPurchaseRequest, |
| | | } from '#/api/erp/purchase/request'; |
| | | import { $t } from '#/locales'; |
| | | |
| | | import { useGridColumns, useGridFormSchema, PURCHASE_REQUEST_STATUS } from './data'; |
| | | import { useGridColumns, useGridFormSchema } from './data'; |
| | | import Form from './modules/form.vue'; |
| | | import OrderForm from './modules/order-form.vue'; |
| | | import ProcessSelectModal from './modules/process-select-modal.vue'; |
| | | |
| | | /** ERP éè´ç³è¯·å表 */ |
| | | defineOptions({ name: 'ErpPurchaseRequest' }); |
| | | |
| | | const [FormModal, formModalApi] = useVbenModal({ |
| | | connectedComponent: Form, |
| | | destroyOnClose: true, |
| | | }); |
| | | |
| | | const [OrderFormModal, orderFormModalApi] = useVbenModal({ |
| | | connectedComponent: OrderForm, |
| | | destroyOnClose: true, |
| | | }); |
| | | |
| | | const [ProcessModal, processModalApi] = useVbenModal({ |
| | | connectedComponent: ProcessSelectModal, |
| | | destroyOnClose: true, |
| | | }); |
| | | |
| | |
| | | } |
| | | } |
| | | |
| | | /** 审æ¹éè´ç³è¯· */ |
| | | async function handleApprove(row: ErpPurchaseRequestApi.PurchaseRequest, status: number) { |
| | | /** æäº¤å®¡æ¹ - å
è·åæµç¨å表 */ |
| | | async function handleSubmit(row: ErpPurchaseRequestApi.PurchaseRequest) { |
| | | const hideLoading = message.loading({ |
| | | content: `ç¡®å®${status === 1 ? '审æ¹éè¿' : 'å®¡æ¹æç»'}该ç³è¯·åï¼`, |
| | | content: 'è·åå®¡æ¹æµç¨...', |
| | | duration: 0, |
| | | }); |
| | | try { |
| | | await approvePurchaseRequest({ id: row.id!, status, remark: '' }); |
| | | message.success(`${status === 1 ? '审æ¹éè¿' : 'å®¡æ¹æç»'}æå`); |
| | | handleRefresh(); |
| | | } finally { |
| | | const processList = await getApproveProcessList(row.id!); |
| | | hideLoading(); |
| | | if (!processList || processList.length === 0) { |
| | | message.warning('ææ å¯ç¨å®¡æ¹æµç¨ï¼è¯·å
é
ç½® BPM æµç¨'); |
| | | return; |
| | | } |
| | | // æå¼æµç¨éæ©å¼¹çª |
| | | processModalApi.setData({ id: row.id, processList }).open(); |
| | | } catch { |
| | | hideLoading(); |
| | | } |
| | | } |
| | | |
| | | /** 转为éè´è®¢å */ |
| | | async function handleConvert(row: ErpPurchaseRequestApi.PurchaseRequest) { |
| | | const hideLoading = message.loading({ |
| | | content: 'æ£å¨è½¬ä¸ºéè´è®¢å...', |
| | | duration: 0, |
| | | }); |
| | | try { |
| | | await convertToPurchaseOrder(row.id!); |
| | | message.success('å·²æå转为éè´è®¢å'); |
| | | handleRefresh(); |
| | | } finally { |
| | | hideLoading(); |
| | | } |
| | | /** çæéè´è®¢å */ |
| | | function handleGenerateOrder(row: ErpPurchaseRequestApi.PurchaseRequest) { |
| | | orderFormModalApi.setData({ id: row.id, supplierId: row.supplierId }).open(); |
| | | } |
| | | |
| | | const checkedIds = ref<number[]>([]); |
| | |
| | | <template> |
| | | <Page auto-content-height> |
| | | <FormModal @success="handleRefresh" /> |
| | | <OrderFormModal @success="handleRefresh" /> |
| | | <ProcessModal @success="handleRefresh" /> |
| | | <Grid table-title="éè´ç³è¯·å表"> |
| | | <template #toolbar-tools> |
| | | <TableAction |
| | |
| | | /> |
| | | </template> |
| | | <template #status="{ row }"> |
| | | <Tag v-if="row.status === 0" color="warning">å¾
审æ¹</Tag> |
| | | <Tag v-else-if="row.status === 1" color="success">已审æ¹</Tag> |
| | | <Tag v-else-if="row.status === 2" color="error">å·²æç»</Tag> |
| | | <Tag v-else-if="row.status === 3" color="default">已转订å</Tag> |
| | | <Tag v-if="row.status === 0" color="default">æªæäº¤</Tag> |
| | | <Tag v-else-if="row.status === 10" color="processing">审æ¹ä¸</Tag> |
| | | <Tag v-else-if="row.status === 20" color="success">å®¡æ ¸éè¿</Tag> |
| | | <Tag v-else-if="row.status === 30" color="error">å®¡æ ¸ä¸éè¿</Tag> |
| | | <Tag v-else-if="row.status === 40" color="warning">已忶</Tag> |
| | | </template> |
| | | <template #actions="{ row }"> |
| | | <TableAction |
| | |
| | | onClick: handleEdit.bind(null, row), |
| | | }, |
| | | { |
| | | label: '审æ¹éè¿', |
| | | label: 'æäº¤', |
| | | type: 'link', |
| | | auth: ['erp:purchase-request:approve'], |
| | | ifShow: () => row.status === 0, |
| | | popConfirm: { |
| | | title: '确认审æ¹éè¿è¯¥ç³è¯·åï¼', |
| | | confirm: handleApprove.bind(null, row, 1), |
| | | }, |
| | | ifShow: () => row.status === 0 || row.status === 30, |
| | | onClick: handleSubmit.bind(null, row), |
| | | }, |
| | | { |
| | | label: 'å®¡æ¹æç»', |
| | | label: 'çæè®¢å', |
| | | type: 'link', |
| | | danger: true, |
| | | auth: ['erp:purchase-request:approve'], |
| | | ifShow: () => row.status === 0, |
| | | popConfirm: { |
| | | title: 'ç¡®è®¤å®¡æ¹æç»è¯¥ç³è¯·åï¼', |
| | | confirm: handleApprove.bind(null, row, 2), |
| | | }, |
| | | }, |
| | | { |
| | | label: '转订å', |
| | | type: 'link', |
| | | auth: ['erp:purchase-request:convert'], |
| | | ifShow: () => row.status === 1, |
| | | popConfirm: { |
| | | title: '确认转为éè´è®¢ååï¼', |
| | | confirm: handleConvert.bind(null, row), |
| | | }, |
| | | auth: ['erp:purchase-request:generate-order'], |
| | | ifShow: () => row.status === 20 && !row.orderId, |
| | | onClick: handleGenerateOrder.bind(null, row), |
| | | }, |
| | | { |
| | | label: $t('common.delete'), |
| | |
| | | </template> |
| | | </Grid> |
| | | </Page> |
| | | </template> |
| | | </template> |
| | |
| | | |
| | | import { useVbenModal } from '../../../../../packages/effects/common-ui/src'; |
| | | |
| | | import { message, Tag } from 'ant-design-vue'; |
| | | import { message } from 'ant-design-vue'; |
| | | |
| | | import { useVbenForm } from '#/adapter/form'; |
| | | import { |
| | |
| | | } from '#/api/erp/purchase/request'; |
| | | import { $t } from '#/locales'; |
| | | |
| | | import { useFormSchema, PURCHASE_REQUEST_STATUS } from '../data'; |
| | | import { useFormSchema } from '../data'; |
| | | import PurchaseRequestItemForm from './item-form.vue'; |
| | | |
| | | const emit = defineEmits(['success']); |
| | |
| | | const data = |
| | | (await formApi.getValues()) as ErpPurchaseRequestApi.PurchaseRequest; |
| | | data.items = formData.value?.items?.map((item) => ({ |
| | | ...item, |
| | | id: undefined, |
| | | productId: item.productId, |
| | | productUnitId: item.productUnitId, |
| | | productPrice: item.productPrice, |
| | | count: item.count, |
| | | taxPercent: item.taxPercent, |
| | | demandTime: item.demandTime, |
| | | remark: item.remark, |
| | | })); |
| | | try { |
| | | await (formType.value === 'create' |
| | |
| | | /** è·åè¡¨æ ¼åè®¡æ°æ® */ |
| | | const summaries = computed(() => { |
| | | return { |
| | | count: tableData.value.reduce((sum, item) => sum + (item.requestCount || 0), 0), |
| | | count: tableData.value.reduce((sum, item) => sum + (item.count || 0), 0), |
| | | totalPrice: tableData.value.reduce( |
| | | (sum, item) => { |
| | | const price = erpPriceMultiply(item.estimatedPrice || 0, item.requestCount || 0) ?? 0; |
| | | const price = erpPriceMultiply(item.productPrice || 0, item.count || 0) ?? 0; |
| | | return sum + price; |
| | | }, |
| | | 0, |
| | |
| | | const newRow = { |
| | | id: undefined, |
| | | productId: undefined, |
| | | productUnitId: undefined, |
| | | productUnitName: undefined, |
| | | productBarCode: undefined, |
| | | requestCount: 1, |
| | | estimatedPrice: undefined, |
| | | requiredDate: undefined, |
| | | count: 1, |
| | | productPrice: undefined, |
| | | taxPercent: undefined, |
| | | demandTime: undefined, |
| | | remark: undefined, |
| | | totalPrice: 0, |
| | | }; |
| | |
| | | } |
| | | row.productId = productId; |
| | | row.productBarCode = product.barCode; |
| | | row.productUnitId = product.unitMeasureId; |
| | | row.productUnitName = product.unitMeasureName; |
| | | row.productName = product.name; |
| | | row.estimatedPrice = product.purchasePrice || 0; |
| | | row.requestCount = row.requestCount || 1; |
| | | row.productPrice = product.purchasePrice || 0; |
| | | row.count = row.count || 1; |
| | | handleRowChange(row); |
| | | } |
| | | |
| | |
| | | tableData.value[index] = row; |
| | | } |
| | | // 计ç®é¢ä¼°éé¢ |
| | | if (row.estimatedPrice && row.requestCount) { |
| | | row.totalPrice = erpPriceMultiply(row.estimatedPrice, row.requestCount) ?? 0; |
| | | if (row.productPrice && row.count) { |
| | | row.totalPrice = erpPriceMultiply(row.productPrice, row.count) ?? 0; |
| | | } |
| | | emit('update:items', [...tableData.value]); |
| | | } |
| | | |
| | | /** åå§åè¡æ°æ® */ |
| | | function initRow(row: ErpPurchaseRequestApi.PurchaseRequestItem) { |
| | | if (row.estimatedPrice && row.requestCount) { |
| | | row.totalPrice = erpPriceMultiply(row.estimatedPrice, row.requestCount) ?? 0; |
| | | if (row.productPrice && row.count) { |
| | | row.totalPrice = erpPriceMultiply(row.productPrice, row.count) ?? 0; |
| | | } |
| | | } |
| | | |
| | |
| | | if (!item.productId) { |
| | | throw new Error(`第 ${i + 1} è¡ï¼äº§åä¸è½ä¸ºç©º`); |
| | | } |
| | | if (!item.requestCount || item.requestCount <= 0) { |
| | | throw new Error(`第 ${i + 1} è¡ï¼ç³è¯·æ°éä¸è½ä¸ºç©º`); |
| | | if (!item.count || item.count <= 0) { |
| | | throw new Error(`第 ${i + 1} è¡ï¼æ°éä¸è½ä¸ºç©º`); |
| | | } |
| | | } |
| | | } |
| | |
| | | /> |
| | | <span v-else>{{ row.productName || '-' }}</span> |
| | | </template> |
| | | <template #requestCount="{ row }"> |
| | | <template #count="{ row }"> |
| | | <InputNumber |
| | | v-if="!disabled" |
| | | v-model:value="row.requestCount" |
| | | v-model:value="row.count" |
| | | :min="0" |
| | | :precision="3" |
| | | @change="handleRowChange(row)" |
| | | /> |
| | | <span v-else>{{ erpCountInputFormatter(row.requestCount) || '-' }}</span> |
| | | <span v-else>{{ erpCountInputFormatter(row.count) || '-' }}</span> |
| | | </template> |
| | | <template #estimatedPrice="{ row }"> |
| | | <template #productPrice="{ row }"> |
| | | <InputNumber |
| | | v-if="!disabled" |
| | | v-model:value="row.estimatedPrice" |
| | | v-model:value="row.productPrice" |
| | | :min="0" |
| | | :precision="2" |
| | | @change="handleRowChange(row)" |
| | | /> |
| | | <span v-else>{{ erpPriceInputFormatter(row.estimatedPrice) || '-' }}</span> |
| | | <span v-else>{{ erpPriceInputFormatter(row.productPrice) || '-' }}</span> |
| | | </template> |
| | | <template #requiredDate="{ row }"> |
| | | <template #taxPercent="{ row }"> |
| | | <InputNumber |
| | | v-if="!disabled" |
| | | v-model:value="row.taxPercent" |
| | | :min="0" |
| | | :max="100" |
| | | :precision="2" |
| | | @change="handleRowChange(row)" |
| | | /> |
| | | <span v-else>{{ row.taxPercent ? `${row.taxPercent}%` : '-' }}</span> |
| | | </template> |
| | | <template #demandTime="{ row }"> |
| | | <DatePicker |
| | | v-if="!disabled" |
| | | v-model:value="row.requiredDate" |
| | | v-model:value="row.demandTime" |
| | | valueFormat="YYYY-MM-DD" |
| | | class="w-full" |
| | | @change="handleRowChange(row)" |
| | | /> |
| | | <span v-else>{{ row.requiredDate || '-' }}</span> |
| | | <span v-else>{{ row.demandTime || '-' }}</span> |
| | | </template> |
| | | <template #remark="{ row }"> |
| | | <Input v-if="!disabled" v-model:value="row.remark" class="w-full" @change="handleRowChange(row)" /> |
| | |
| | | <div class="flex justify-between text-sm text-muted-foreground"> |
| | | <span class="font-medium text-foreground">å计ï¼</span> |
| | | <div class="flex space-x-4"> |
| | | <span>ç³è¯·æ°éï¼{{ erpCountInputFormatter(summaries.count) }}</span> |
| | | <span>æ°éï¼{{ erpCountInputFormatter(summaries.count) }}</span> |
| | | <span>é¢ä¼°éé¢ï¼{{ erpPriceInputFormatter(summaries.totalPrice) }}</span> |
| | | </div> |
| | | </div> |
| | |
| | | /> |
| | | </template> |
| | | </Grid> |
| | | </template> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { ErpPurchaseRequestApi } from '#/api/erp/purchase/request'; |
| | | |
| | | import { ref, computed } from 'vue'; |
| | | |
| | | import { useVbenModal } from '../../../../../packages/effects/common-ui/src'; |
| | | |
| | | import { message, Select } from 'ant-design-vue'; |
| | | |
| | | import { generatePurchaseOrder } from '#/api/erp/purchase/request'; |
| | | import { getSupplierSimpleList } from '#/api/erp/purchase/supplier'; |
| | | |
| | | const emit = defineEmits(['success']); |
| | | |
| | | const requestId = ref<number>(); |
| | | const supplierId = ref<number>(); |
| | | const supplierOptions = ref<{ id: number; name: string }[]>([]); |
| | | |
| | | const supplierSelectOptions = computed(() => { |
| | | return supplierOptions.value.map((s) => ({ |
| | | label: s.name, |
| | | value: s.id, |
| | | })); |
| | | }); |
| | | |
| | | const [Modal, modalApi] = useVbenModal({ |
| | | async onConfirm() { |
| | | if (!supplierId.value) { |
| | | message.warning('è¯·éæ©ä¾åºå'); |
| | | return; |
| | | } |
| | | modalApi.lock(); |
| | | try { |
| | | const orderId = await generatePurchaseOrder(requestId.value!, supplierId.value!); |
| | | await modalApi.close(); |
| | | emit('success'); |
| | | message.success(`çæéè´è®¢åæåï¼è®¢åç¼å·ï¼${orderId}`); |
| | | } finally { |
| | | modalApi.unlock(); |
| | | } |
| | | }, |
| | | async onOpenChange(isOpen: boolean) { |
| | | if (!isOpen) { |
| | | requestId.value = undefined; |
| | | supplierId.value = undefined; |
| | | return; |
| | | } |
| | | const data = modalApi.getData<{ id: number; supplierId?: number }>(); |
| | | requestId.value = data.id; |
| | | supplierId.value = data.supplierId; |
| | | // è·åä¾åºåå表 |
| | | supplierOptions.value = await getSupplierSimpleList(); |
| | | }, |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <Modal title="çæéè´è®¢å" class="w-1/3"> |
| | | <div class="mb-4"> |
| | | <span class="mr-2">éæ©ä¾åºåï¼</span> |
| | | <Select |
| | | v-model:value="supplierId" |
| | | :options="supplierSelectOptions" |
| | | style="width: 200px" |
| | | placeholder="è¯·éæ©ä¾åºå" |
| | | show-search |
| | | :filter-option="(input: string, option: any) => option.label.toLowerCase().includes(input.toLowerCase())" |
| | | /> |
| | | </div> |
| | | </Modal> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { ErpPurchaseRequestApi } from '#/api/erp/purchase/request'; |
| | | |
| | | import { ref, computed } from 'vue'; |
| | | |
| | | import { useVbenModal } from '../../../../../packages/effects/common-ui/src'; |
| | | |
| | | import { message, Select } from 'ant-design-vue'; |
| | | |
| | | import { submitPurchaseRequest } from '#/api/erp/purchase/request'; |
| | | |
| | | interface ProcessDefinition { |
| | | id: string; |
| | | name: string; |
| | | version: number; |
| | | key: string; |
| | | } |
| | | |
| | | const emit = defineEmits(['success']); |
| | | |
| | | const requestId = ref<number>(); |
| | | const processList = ref<ProcessDefinition[]>([]); |
| | | const selectedProcessKey = ref<string>(''); |
| | | |
| | | const processOptions = computed(() => { |
| | | return processList.value.map((p) => ({ |
| | | label: `${p.name} (V${p.version})`, |
| | | value: p.key, |
| | | })); |
| | | }); |
| | | |
| | | const [Modal, modalApi] = useVbenModal({ |
| | | async onConfirm() { |
| | | if (!selectedProcessKey.value) { |
| | | message.warning('è¯·éæ©å®¡æ¹æµç¨'); |
| | | return; |
| | | } |
| | | modalApi.lock(); |
| | | try { |
| | | await submitPurchaseRequest(requestId.value!, selectedProcessKey.value); |
| | | await modalApi.close(); |
| | | emit('success'); |
| | | message.success('æäº¤æå'); |
| | | } finally { |
| | | modalApi.unlock(); |
| | | } |
| | | }, |
| | | async onOpenChange(isOpen: boolean) { |
| | | if (!isOpen) { |
| | | requestId.value = undefined; |
| | | selectedProcessKey.value = ''; |
| | | processList.value = []; |
| | | return; |
| | | } |
| | | const data = modalApi.getData<{ id: number; processList: ProcessDefinition[] }>(); |
| | | requestId.value = data.id; |
| | | processList.value = data.processList || []; |
| | | // é»è®¤éæ©ç¬¬ä¸ä¸ªæµç¨ |
| | | if (processList.value.length > 0) { |
| | | selectedProcessKey.value = processList.value[0].key; |
| | | } |
| | | }, |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <Modal title="éæ©å®¡æ¹æµç¨" class="w-1/3"> |
| | | <div class="mb-4"> |
| | | <span class="mr-2">å®¡æ¹æµç¨ï¼</span> |
| | | <Select |
| | | v-model:value="selectedProcessKey" |
| | | :options="processOptions" |
| | | style="width: 200px" |
| | | placeholder="è¯·éæ©å®¡æ¹æµç¨" |
| | | /> |
| | | </div> |
| | | </Modal> |
| | | </template> |
| | |
| | | rules: z.number().default(0), |
| | | }, |
| | | { |
| | | fieldName: 'sort', |
| | | label: 'æåº', |
| | | component: 'InputNumber', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥æåº', |
| | | min: 0, |
| | | class: '!w-full', |
| | | }, |
| | | rules: z.number().default(0), |
| | | }, |
| | | { |
| | | fieldName: 'qualification', |
| | | label: 'èµè´¨ä¿¡æ¯', |
| | | component: 'Textarea', |
| | |
| | | componentProps: { |
| | | class: 'w-full', |
| | | }, |
| | | labelWidth: 100, |
| | | labelWidth: 120, |
| | | }, |
| | | wrapperClass: 'grid-cols-2', |
| | | layout: 'horizontal', |
| | |
| | | component: 'IconPicker', |
| | | componentProps: { |
| | | placeholder: 'è¯·éæ©èå徿 ', |
| | | prefix: 'carbon', |
| | | prefix: 'ep', |
| | | }, |
| | | rules: 'required', |
| | | dependencies: { |