feat(erp): 采购/销售明细按含税单价口径联算,采购申请与申请采购单合并入口
- 新增 erpTax 工具:含税总价 = 数量 × 含税单价,税额 = 含税总价 × 税率 ÷ (100 + 税率),不含税金额 = 含税总价 − 税额;采购订单、采购申请、销售订单明细统一使用
- 采购申请单页签合并「申请采购单」,原申请采购单/申请采购页保留旧路由直达
- 供应商合同支持合同附件上传,从供应商档案进入时按供应商筛选
- 采购订单/销售订单打印按含税口径展示
Co-Authored-By: Claude Code <noreply@anthropic.com>
| | |
| | | id?: number; // ç¼å· |
| | | no?: string; // ç³è¯·ç¼å· |
| | | status?: number; // ç¶æï¼0æªæäº¤ 10审æ¹ä¸ 20å®¡æ ¸éè¿ 30å®¡æ ¸ä¸éè¿ 40已忶 |
| | | inStatus?: number; // å
¥åºç¶æï¼0æªå
¥åº 1é¨åå
¥åº 2å
¨é¨å
¥åº |
| | | processInstanceId?: string; // 工使µç¼å· |
| | | requestUserId?: number; // ç³è¯·äººç¼å· |
| | | requestUserName?: string; // ç³è¯·äººåç§° |
| | |
| | | productNames?: string; // 产ååç§°å表 |
| | | orderId?: number; // çæçéè´è®¢åç¼å· |
| | | orderNo?: string; // çæçéè´è®¢åå· |
| | | processInstanceId?: number; // å®¡æ¹æµç¨å®ä¾ç¼å· |
| | | items?: PurchaseRequestItem[]; // ç³è¯·æç» |
| | | createTime?: string; // å建æ¶é´ |
| | | } |
| | |
| | | orderCount?: number; // å
³èéè´è®¢åæ°é |
| | | orderAmount?: number; // å
³èéè´è®¢åéé¢å计 |
| | | remark?: string; // 夿³¨ |
| | | blobIds?: number[]; // éä»¶ç¼å·åè¡¨ï¼æ°å¢/ä¿®æ¹æ¶æäº¤ï¼ |
| | | attachmentList?: { id: number; name?: string; url?: string }[]; // éä»¶å表ï¼è¯¦æ
è¿åï¼ |
| | | createTime?: string; // å建æ¶é´ |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import { erpPriceMultiply } from '@vben/utils'; |
| | | |
| | | /** ERP æç»è¡ä¸ä¸éé¢ãç¨é¢ç¸å
³çåæ®µ */ |
| | | export interface ErpTaxLine { |
| | | /** æ°é */ |
| | | count?: null | number; |
| | | /** 产ååä»·ï¼å«ç¨åä»·ï¼ */ |
| | | productPrice?: null | number; |
| | | /** ç¨çï¼ç¾åæ¯ï¼ä¾å¦ 13 */ |
| | | taxPercent?: null | number; |
| | | /** ç¨é¢ */ |
| | | taxPrice?: null | number; |
| | | /** ä¸å«ç¨éé¢ */ |
| | | totalProductPrice?: null | number; |
| | | /** å«ç¨æ»ä»· */ |
| | | totalPrice?: null | number; |
| | | } |
| | | |
| | | /** |
| | | * æã产åå价为å«ç¨åä»·ãçå£å¾éç®æç»è¡éé¢ |
| | | * |
| | | * å«ç¨æ»ä»· = æ°é à å«ç¨åä»· |
| | | * ç¨é¢ = å«ç¨æ»ä»· à ç¨ç ÷ï¼100 + ç¨çï¼ |
| | | * ä¸å«ç¨éé¢ = å«ç¨æ»ä»· â ç¨é¢ |
| | | */ |
| | | export function calcTaxIncludedLine<T extends ErpTaxLine>(row: T): T { |
| | | const totalPrice = |
| | | erpPriceMultiply(Number(row.productPrice ?? 0), Number(row.count ?? 0)) ?? |
| | | 0; |
| | | const taxPercent = Number(row.taxPercent ?? 0); |
| | | const taxPrice = |
| | | taxPercent > 0 |
| | | ? Number(((totalPrice * taxPercent) / (100 + taxPercent)).toFixed(2)) |
| | | : 0; |
| | | row.totalPrice = totalPrice; |
| | | row.taxPrice = taxPrice; |
| | | row.totalProductPrice = Number((totalPrice - taxPrice).toFixed(2)); |
| | | return row; |
| | | } |
| | |
| | | import type { Recordable } from '@vben/types'; |
| | | |
| | | export * from './erpTax'; |
| | | export * from './import-result'; |
| | | export * from './rangePickerProps'; |
| | | export * from './routerHelper'; |
| | |
| | | <script lang="ts" setup> |
| | | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; |
| | | import type { ErpPurchaseDemandApi } from '#/api/erp/purchase/demand'; |
| | | import { Page } from '@vben/common-ui'; |
| | | |
| | | import { ref } from 'vue'; |
| | | import { useRouter } from 'vue-router'; |
| | | import DemandPanel from './modules/demand-panel.vue'; |
| | | |
| | | import { confirm, Page, useVbenModal } from '@vben/common-ui'; |
| | | import { downloadFileFromBlobPart, isEmpty } from '@vben/utils'; |
| | | |
| | | import { message } from 'ant-design-vue'; |
| | | |
| | | import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; |
| | | import { |
| | | createPurchaseOrderByDemand, |
| | | deleteDemand, |
| | | exportDemand, |
| | | getDemandPage, |
| | | } from '#/api/erp/purchase/demand'; |
| | | import { $t } from '#/locales'; |
| | | |
| | | import { DEMAND_STATUS, useGridColumns, useGridFormSchema } from './data'; |
| | | import Form from './modules/form.vue'; |
| | | import Print from './modules/print.vue'; |
| | | |
| | | /** ç³è¯·éè´åï¼éæ±åï¼ï¼æå·¥/ç产计åéæ± â æ¹ééè´ä¸åçæéè´è®¢å */ |
| | | /** ç³è¯·éè´åï¼4.4ï¼ï¼å·²å¹¶å
¥ãéè´ç³è¯·åã页ç¾ï¼æ¤é¡µé¢ä¿çç¨äºæ§è·¯ç±ç´è¾¾ */ |
| | | defineOptions({ name: 'ErpPurchaseDemand' }); |
| | | |
| | | const router = useRouter(); |
| | | |
| | | const [FormModal, formModalApi] = useVbenModal({ |
| | | connectedComponent: Form, |
| | | destroyOnClose: true, |
| | | }); |
| | | |
| | | const checkedRows = ref<ErpPurchaseDemandApi.Demand[]>([]); |
| | | |
| | | /** æå°ç»ä»¶ï¼4.4⣠æå°éæ±åï¼ */ |
| | | const printRef = ref<InstanceType<typeof Print>>(); |
| | | |
| | | /** æå°éæ±å */ |
| | | function handlePrint(row: ErpPurchaseDemandApi.Demand) { |
| | | printRef.value?.print(row.id!); |
| | | } |
| | | |
| | | /** å·æ°è¡¨æ ¼ */ |
| | | function handleRefresh() { |
| | | gridApi.query(); |
| | | } |
| | | |
| | | /** 导åºè¡¨æ ¼ */ |
| | | async function handleExport() { |
| | | const data = await exportDemand(await gridApi.formApi.getValues()); |
| | | downloadFileFromBlobPart({ fileName: 'ç³è¯·éè´å.xlsx', source: data }); |
| | | } |
| | | |
| | | /** æ°å¢éæ±å */ |
| | | function handleCreate() { |
| | | formModalApi.setData({ formType: 'create' }).open(); |
| | | } |
| | | |
| | | /** ç¼è¾éæ±å */ |
| | | function handleEdit(row: ErpPurchaseDemandApi.Demand) { |
| | | formModalApi.setData({ formType: 'edit', id: row.id }).open(); |
| | | } |
| | | |
| | | /** æ¥ç详æ
*/ |
| | | function handleDetail(row: ErpPurchaseDemandApi.Demand) { |
| | | formModalApi.setData({ formType: 'detail', id: row.id }).open(); |
| | | } |
| | | |
| | | /** å é¤éæ±å */ |
| | | async function handleDelete(row: ErpPurchaseDemandApi.Demand) { |
| | | const hideLoading = message.loading({ |
| | | content: $t('ui.actionMessage.deleting'), |
| | | duration: 0, |
| | | }); |
| | | try { |
| | | await deleteDemand(row.id!); |
| | | message.success($t('ui.actionMessage.deleteSuccess')); |
| | | handleRefresh(); |
| | | } finally { |
| | | hideLoading(); |
| | | } |
| | | } |
| | | |
| | | /** éè´ä¸åï¼åæ¡ææ¹éï¼ï¼æééæ±åå¿
é¡»å±äºåä¸ä¾è´§å */ |
| | | async function handlePurchase(rows: ErpPurchaseDemandApi.Demand[]) { |
| | | const unpurchased = rows.filter( |
| | | (row) => row.status === DEMAND_STATUS.UNPURCHASED, |
| | | ); |
| | | if (unpurchased.length === 0) { |
| | | message.warning('æééæ±ååå·²éè´ï¼æ ééå¤ä¸å'); |
| | | return; |
| | | } |
| | | if (unpurchased.length !== rows.length) { |
| | | message.warning( |
| | | `æé ${rows.length} æ¡éæ±åä¸ï¼${rows.length - unpurchased.length} æ¡å·²éè´ï¼ä»
对å©ä½ç ${unpurchased.length} æ¡ä¸å`, |
| | | ); |
| | | } |
| | | const noSupplier = unpurchased.find((row) => !row.supplierId); |
| | | if (noSupplier) { |
| | | message.error( |
| | | `éæ±å[${noSupplier.no}]æªéæ©ä¾è´§åï¼è¯·å
ãç¼è¾ãè¡¥é½ä¾è´§åååéè´ä¸å`, |
| | | ); |
| | | return; |
| | | } |
| | | const supplierNames = new Set(unpurchased.map((row) => row.supplierName)); |
| | | if (supplierNames.size > 1) { |
| | | message.error( |
| | | `æééæ±ååå±å¤ä¸ªä¾è´§åï¼${[...supplierNames].join('ã')}ï¼ï¼ä¸æ¬¡åªè½å¯¹åä¸ä¾è´§åçéæ±ååå¹¶ä¸åï¼è¯·åæ¹æä½`, |
| | | ); |
| | | return; |
| | | } |
| | | await confirm( |
| | | `确认为ä¾è´§å[${[...supplierNames][0]}]ç ${unpurchased.length} æ¡éæ±åçæéè´è®¢ååï¼`, |
| | | ); |
| | | const hideLoading = message.loading({ |
| | | content: 'æ£å¨çæéè´è®¢å...', |
| | | duration: 0, |
| | | }); |
| | | try { |
| | | const result = await createPurchaseOrderByDemand( |
| | | unpurchased.map((row) => row.id!), |
| | | ); |
| | | message.success({ |
| | | content: `å·²çæéè´è®¢å[${result.orderNo}]ï¼å¯å¨ãæ¥çç¸å
³è®¢åãä¸è¿½æº¯`, |
| | | duration: 5, |
| | | }); |
| | | handleRefresh(); |
| | | } finally { |
| | | hideLoading(); |
| | | } |
| | | } |
| | | |
| | | /** æ¹ééè´ä¸åï¼åè¡¨æ ¼å¾éè¡ */ |
| | | function handleBatchPurchase() { |
| | | handlePurchase(checkedRows.value); |
| | | } |
| | | |
| | | /** æ¥çç¸å
³éè´è®¢å */ |
| | | function handleViewOrder(row: ErpPurchaseDemandApi.Demand) { |
| | | router.push({ |
| | | name: 'ErpPurchaseOrder', |
| | | query: { id: row.orderId }, |
| | | }); |
| | | } |
| | | |
| | | function handleRowCheckboxChange({ |
| | | records, |
| | | }: { |
| | | records: ErpPurchaseDemandApi.Demand[]; |
| | | }) { |
| | | checkedRows.value = records; |
| | | } |
| | | |
| | | const [Grid, gridApi] = useVbenVxeGrid({ |
| | | formOptions: { |
| | | schema: useGridFormSchema(), |
| | | }, |
| | | gridOptions: { |
| | | columns: useGridColumns(), |
| | | height: 'auto', |
| | | keepSource: true, |
| | | proxyConfig: { |
| | | ajax: { |
| | | query: async ({ page }, formValues) => |
| | | await getDemandPage({ |
| | | pageNo: page.currentPage, |
| | | pageSize: page.pageSize, |
| | | ...formValues, |
| | | }), |
| | | }, |
| | | }, |
| | | rowConfig: { keyField: 'id', isHover: true }, |
| | | toolbarConfig: { refresh: true, search: true, custom: true }, |
| | | } as VxeTableGridOptions<ErpPurchaseDemandApi.Demand>, |
| | | gridEvents: { |
| | | checkboxAll: handleRowCheckboxChange, |
| | | checkboxChange: handleRowCheckboxChange, |
| | | }, |
| | | }); |
| | | </script> |
| | | <template> |
| | | <Page auto-content-height> |
| | | <FormModal @success="handleRefresh" /> |
| | | <Print ref="printRef" /> |
| | | <Grid table-title="ç³è¯·éè´åå表"> |
| | | <template #toolbar-tools> |
| | | <TableAction |
| | | :actions="[ |
| | | { |
| | | label: $t('ui.actionTitle.create', ['ç³è¯·éè´å']), |
| | | type: 'primary', |
| | | icon: ACTION_ICON.ADD, |
| | | auth: ['erp:purchase-demand:create'], |
| | | onClick: handleCreate, |
| | | }, |
| | | { |
| | | label: 'æ¹ééè´ä¸å', |
| | | type: 'primary', |
| | | icon: ACTION_ICON.AUDIT, |
| | | disabled: isEmpty(checkedRows), |
| | | auth: ['erp:purchase-demand:purchase'], |
| | | onClick: handleBatchPurchase, |
| | | }, |
| | | { |
| | | label: $t('ui.actionTitle.export'), |
| | | type: 'primary', |
| | | icon: ACTION_ICON.DOWNLOAD, |
| | | auth: ['erp:purchase-demand:export'], |
| | | onClick: handleExport, |
| | | }, |
| | | ]" |
| | | /> |
| | | </template> |
| | | <template #actions="{ row }"> |
| | | <TableAction |
| | | :actions="[ |
| | | { |
| | | label: $t('common.detail'), |
| | | type: 'link', |
| | | icon: ACTION_ICON.VIEW, |
| | | onClick: handleDetail.bind(null, row), |
| | | }, |
| | | { |
| | | label: $t('common.edit'), |
| | | type: 'link', |
| | | icon: ACTION_ICON.EDIT, |
| | | auth: ['erp:purchase-demand:update'], |
| | | ifShow: () => row.status === DEMAND_STATUS.UNPURCHASED, |
| | | onClick: handleEdit.bind(null, row), |
| | | }, |
| | | { |
| | | label: 'éè´ä¸å', |
| | | type: 'link', |
| | | auth: ['erp:purchase-demand:purchase'], |
| | | ifShow: () => row.status === DEMAND_STATUS.UNPURCHASED, |
| | | onClick: handlePurchase.bind(null, [row]), |
| | | }, |
| | | { |
| | | label: 'æå°', |
| | | type: 'link', |
| | | icon: 'lucide:printer', |
| | | onClick: handlePrint.bind(null, row), |
| | | }, |
| | | { |
| | | label: 'æ¥çç¸å
³è®¢å', |
| | | type: 'link', |
| | | ifShow: () => !!row.orderId, |
| | | onClick: handleViewOrder.bind(null, row), |
| | | }, |
| | | { |
| | | label: $t('common.delete'), |
| | | type: 'link', |
| | | danger: true, |
| | | icon: ACTION_ICON.DELETE, |
| | | auth: ['erp:purchase-demand:delete'], |
| | | ifShow: () => row.status === DEMAND_STATUS.UNPURCHASED, |
| | | popConfirm: { |
| | | title: $t('ui.actionMessage.deleteConfirm', [row.no]), |
| | | confirm: handleDelete.bind(null, row), |
| | | }, |
| | | }, |
| | | ]" |
| | | /> |
| | | </template> |
| | | </Grid> |
| | | <DemandPanel /> |
| | | </Page> |
| | | </template> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; |
| | | import type { ErpPurchaseDemandApi } from '#/api/erp/purchase/demand'; |
| | | |
| | | import { ref } from 'vue'; |
| | | import { useRouter } from 'vue-router'; |
| | | |
| | | import { confirm, useVbenModal } from '@vben/common-ui'; |
| | | import { downloadFileFromBlobPart, isEmpty } from '@vben/utils'; |
| | | |
| | | import { message } from 'ant-design-vue'; |
| | | |
| | | import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; |
| | | import { |
| | | createPurchaseOrderByDemand, |
| | | deleteDemand, |
| | | exportDemand, |
| | | getDemandPage, |
| | | } from '#/api/erp/purchase/demand'; |
| | | import { $t } from '#/locales'; |
| | | |
| | | import { DEMAND_STATUS, useGridColumns, useGridFormSchema } from '../data'; |
| | | import Form from './form.vue'; |
| | | import Print from './print.vue'; |
| | | |
| | | /** ç³è¯·éè´åï¼éæ±åï¼ï¼æå·¥/ç产计åéæ± â æ¹ééè´ä¸åçæéè´è®¢å */ |
| | | defineOptions({ name: 'ErpPurchaseDemandPanel' }); |
| | | |
| | | const router = useRouter(); |
| | | |
| | | const [FormModal, formModalApi] = useVbenModal({ |
| | | connectedComponent: Form, |
| | | destroyOnClose: true, |
| | | }); |
| | | |
| | | const checkedRows = ref<ErpPurchaseDemandApi.Demand[]>([]); |
| | | |
| | | /** æå°ç»ä»¶ï¼4.4⣠æå°éæ±åï¼ */ |
| | | const printRef = ref<InstanceType<typeof Print>>(); |
| | | |
| | | /** æå°éæ±å */ |
| | | function handlePrint(row: ErpPurchaseDemandApi.Demand) { |
| | | printRef.value?.print(row.id!); |
| | | } |
| | | |
| | | /** å·æ°è¡¨æ ¼ */ |
| | | function handleRefresh() { |
| | | gridApi.query(); |
| | | } |
| | | |
| | | /** 导åºè¡¨æ ¼ */ |
| | | async function handleExport() { |
| | | const data = await exportDemand(await gridApi.formApi.getValues()); |
| | | downloadFileFromBlobPart({ fileName: 'ç³è¯·éè´å.xlsx', source: data }); |
| | | } |
| | | |
| | | /** æ°å¢éæ±å */ |
| | | function handleCreate() { |
| | | formModalApi.setData({ formType: 'create' }).open(); |
| | | } |
| | | |
| | | /** ç¼è¾éæ±å */ |
| | | function handleEdit(row: ErpPurchaseDemandApi.Demand) { |
| | | formModalApi.setData({ formType: 'edit', id: row.id }).open(); |
| | | } |
| | | |
| | | /** æ¥ç详æ
*/ |
| | | function handleDetail(row: ErpPurchaseDemandApi.Demand) { |
| | | formModalApi.setData({ formType: 'detail', id: row.id }).open(); |
| | | } |
| | | |
| | | /** å é¤éæ±å */ |
| | | async function handleDelete(row: ErpPurchaseDemandApi.Demand) { |
| | | const hideLoading = message.loading({ |
| | | content: $t('ui.actionMessage.deleting'), |
| | | duration: 0, |
| | | }); |
| | | try { |
| | | await deleteDemand(row.id!); |
| | | message.success($t('ui.actionMessage.deleteSuccess')); |
| | | handleRefresh(); |
| | | } finally { |
| | | hideLoading(); |
| | | } |
| | | } |
| | | |
| | | /** éè´ä¸åï¼åæ¡ææ¹éï¼ï¼æééæ±åå¿
é¡»å±äºåä¸ä¾è´§å */ |
| | | async function handlePurchase(rows: ErpPurchaseDemandApi.Demand[]) { |
| | | const unpurchased = rows.filter( |
| | | (row) => row.status === DEMAND_STATUS.UNPURCHASED, |
| | | ); |
| | | if (unpurchased.length === 0) { |
| | | message.warning('æééæ±ååå·²éè´ï¼æ ééå¤ä¸å'); |
| | | return; |
| | | } |
| | | if (unpurchased.length !== rows.length) { |
| | | message.warning( |
| | | `æé ${rows.length} æ¡éæ±åä¸ï¼${rows.length - unpurchased.length} æ¡å·²éè´ï¼ä»
对å©ä½ç ${unpurchased.length} æ¡ä¸å`, |
| | | ); |
| | | } |
| | | const noSupplier = unpurchased.find((row) => !row.supplierId); |
| | | if (noSupplier) { |
| | | message.error( |
| | | `éæ±å[${noSupplier.no}]æªéæ©ä¾è´§åï¼è¯·å
ãç¼è¾ãè¡¥é½ä¾è´§åååéè´ä¸å`, |
| | | ); |
| | | return; |
| | | } |
| | | const supplierNames = new Set(unpurchased.map((row) => row.supplierName)); |
| | | if (supplierNames.size > 1) { |
| | | message.error( |
| | | `æééæ±ååå±å¤ä¸ªä¾è´§åï¼${[...supplierNames].join('ã')}ï¼ï¼ä¸æ¬¡åªè½å¯¹åä¸ä¾è´§åçéæ±ååå¹¶ä¸åï¼è¯·åæ¹æä½`, |
| | | ); |
| | | return; |
| | | } |
| | | await confirm( |
| | | `确认为ä¾è´§å[${[...supplierNames][0]}]ç ${unpurchased.length} æ¡éæ±åçæéè´è®¢ååï¼`, |
| | | ); |
| | | const hideLoading = message.loading({ |
| | | content: 'æ£å¨çæéè´è®¢å...', |
| | | duration: 0, |
| | | }); |
| | | try { |
| | | const result = await createPurchaseOrderByDemand( |
| | | unpurchased.map((row) => row.id!), |
| | | ); |
| | | message.success({ |
| | | content: `å·²çæéè´è®¢å[${result.orderNo}]ï¼å¯å¨ãæ¥çç¸å
³è®¢åãä¸è¿½æº¯`, |
| | | duration: 5, |
| | | }); |
| | | handleRefresh(); |
| | | } finally { |
| | | hideLoading(); |
| | | } |
| | | } |
| | | |
| | | /** æ¹ééè´ä¸åï¼åè¡¨æ ¼å¾éè¡ */ |
| | | function handleBatchPurchase() { |
| | | handlePurchase(checkedRows.value); |
| | | } |
| | | |
| | | /** æ¥çç¸å
³éè´è®¢å */ |
| | | function handleViewOrder(row: ErpPurchaseDemandApi.Demand) { |
| | | router.push({ |
| | | name: 'ErpPurchaseOrder', |
| | | query: { id: row.orderId }, |
| | | }); |
| | | } |
| | | |
| | | function handleRowCheckboxChange({ |
| | | records, |
| | | }: { |
| | | records: ErpPurchaseDemandApi.Demand[]; |
| | | }) { |
| | | checkedRows.value = records; |
| | | } |
| | | |
| | | const [Grid, gridApi] = useVbenVxeGrid({ |
| | | formOptions: { |
| | | schema: useGridFormSchema(), |
| | | }, |
| | | gridOptions: { |
| | | columns: useGridColumns(), |
| | | height: 'auto', |
| | | keepSource: true, |
| | | proxyConfig: { |
| | | ajax: { |
| | | query: async ({ page }, formValues) => |
| | | await getDemandPage({ |
| | | pageNo: page.currentPage, |
| | | pageSize: page.pageSize, |
| | | ...formValues, |
| | | }), |
| | | }, |
| | | }, |
| | | rowConfig: { keyField: 'id', isHover: true }, |
| | | toolbarConfig: { refresh: true, search: true, custom: true }, |
| | | } as VxeTableGridOptions<ErpPurchaseDemandApi.Demand>, |
| | | gridEvents: { |
| | | checkboxAll: handleRowCheckboxChange, |
| | | checkboxChange: handleRowCheckboxChange, |
| | | }, |
| | | }); |
| | | </script> |
| | | <template> |
| | | <FormModal @success="handleRefresh" /> |
| | | <Print ref="printRef" /> |
| | | <Grid table-title="ç³è¯·éè´åå表"> |
| | | <template #toolbar-tools> |
| | | <TableAction |
| | | :actions="[ |
| | | { |
| | | label: $t('ui.actionTitle.create', ['ç³è¯·éè´å']), |
| | | type: 'primary', |
| | | icon: ACTION_ICON.ADD, |
| | | auth: ['erp:purchase-demand:create'], |
| | | onClick: handleCreate, |
| | | }, |
| | | { |
| | | label: 'æ¹ééè´ä¸å', |
| | | type: 'primary', |
| | | icon: ACTION_ICON.AUDIT, |
| | | disabled: isEmpty(checkedRows), |
| | | auth: ['erp:purchase-demand:purchase'], |
| | | onClick: handleBatchPurchase, |
| | | }, |
| | | { |
| | | label: $t('ui.actionTitle.export'), |
| | | type: 'primary', |
| | | icon: ACTION_ICON.DOWNLOAD, |
| | | auth: ['erp:purchase-demand:export'], |
| | | onClick: handleExport, |
| | | }, |
| | | ]" |
| | | /> |
| | | </template> |
| | | <template #actions="{ row }"> |
| | | <TableAction |
| | | :actions="[ |
| | | { |
| | | label: $t('common.detail'), |
| | | type: 'link', |
| | | icon: ACTION_ICON.VIEW, |
| | | onClick: handleDetail.bind(null, row), |
| | | }, |
| | | { |
| | | label: $t('common.edit'), |
| | | type: 'link', |
| | | icon: ACTION_ICON.EDIT, |
| | | auth: ['erp:purchase-demand:update'], |
| | | ifShow: () => row.status === DEMAND_STATUS.UNPURCHASED, |
| | | onClick: handleEdit.bind(null, row), |
| | | }, |
| | | { |
| | | label: 'éè´ä¸å', |
| | | type: 'link', |
| | | auth: ['erp:purchase-demand:purchase'], |
| | | ifShow: () => row.status === DEMAND_STATUS.UNPURCHASED, |
| | | onClick: handlePurchase.bind(null, [row]), |
| | | }, |
| | | { |
| | | label: 'æå°', |
| | | type: 'link', |
| | | icon: 'lucide:printer', |
| | | onClick: handlePrint.bind(null, row), |
| | | }, |
| | | { |
| | | label: 'æ¥çç¸å
³è®¢å', |
| | | type: 'link', |
| | | ifShow: () => !!row.orderId, |
| | | onClick: handleViewOrder.bind(null, row), |
| | | }, |
| | | { |
| | | label: $t('common.delete'), |
| | | type: 'link', |
| | | danger: true, |
| | | icon: ACTION_ICON.DELETE, |
| | | auth: ['erp:purchase-demand:delete'], |
| | | ifShow: () => row.status === DEMAND_STATUS.UNPURCHASED, |
| | | popConfirm: { |
| | | title: $t('ui.actionMessage.deleteConfirm', [row.no]), |
| | | confirm: handleDelete.bind(null, row), |
| | | }, |
| | | }, |
| | | ]" |
| | | /> |
| | | </template> |
| | | </Grid> |
| | | </template> |
| | |
| | | label: 'å
³èåå', |
| | | fieldName: 'contractId', |
| | | component: 'ApiSelect', |
| | | componentProps: (values) => ({ |
| | | placeholder: values.supplierId |
| | | ? 'è¯·éæ©è¯¥ä¾åºåçéè´åå' |
| | | : '请å
éæ©ä¾åºå', |
| | | allowClear: true, |
| | | showSearch: true, |
| | | disabled: !values.supplierId, |
| | | api: values.supplierId |
| | | ? () => getContractListBySupplier(values.supplierId) |
| | | : () => Promise.resolve([]), |
| | | labelField: 'name', |
| | | valueField: 'id', |
| | | }), |
| | | dependencies: { |
| | | triggerFields: ['supplierId'], |
| | | componentProps: (values) => ({ |
| | | placeholder: values.supplierId |
| | | ? 'è¯·éæ©è¯¥ä¾åºåçéè´åå' |
| | | : '请å
éæ©ä¾åºå', |
| | | allowClear: true, |
| | | showSearch: true, |
| | | disabled: !values.supplierId, |
| | | // åªåéè´ååï¼å忍¡æ¿ä¸è½å
³è订å |
| | | api: () => |
| | | values.supplierId |
| | | ? getContractListBySupplier(values.supplierId).then((list) => |
| | | list.filter((item) => item.type === 1), |
| | | ) |
| | | : Promise.resolve([]), |
| | | // ä¾åºåååæ¶å¼ºå¶éæ°æåé项 |
| | | params: { supplierId: values.supplierId }, |
| | | labelFn: (item: { code?: string; name?: string }) => |
| | | `${item.code || ''} ${item.name || ''}`.trim(), |
| | | labelField: 'name', |
| | | valueField: 'id', |
| | | }), |
| | | }, |
| | | help: 'ä»
å¯éæ©å½åä¾åºååä¸çååï¼ååä¸ä¾åºåä¸å¹é
æ¶ä¿åä¼è¢«æç»', |
| | | }, |
| | |
| | | }, |
| | | { |
| | | field: 'productPrice', |
| | | title: '产ååä»·', |
| | | title: 'å«ç¨åä»·', |
| | | minWidth: 120, |
| | | fixed: 'right', |
| | | slots: { default: 'productPrice' }, |
| | | }, |
| | | { |
| | | field: 'totalProductPrice', |
| | | title: 'éé¢', |
| | | title: 'ä¸å«ç¨éé¢', |
| | | minWidth: 120, |
| | | fixed: 'right', |
| | | formatter: 'formatAmount2', |
| | |
| | | }, |
| | | { |
| | | field: 'totalPrice', |
| | | title: 'ç¨é¢å计', |
| | | title: 'å«ç¨æ»ä»·', |
| | | minWidth: 120, |
| | | fixed: 'right', |
| | | formatter: 'formatAmount2', |
| | |
| | | import { Page, useVbenModal } from '@vben/common-ui'; |
| | | import { downloadFileFromBlobPart, isEmpty } from '@vben/utils'; |
| | | |
| | | import { message, Tag } from 'ant-design-vue'; |
| | | import { message, Tabs, Tag } from 'ant-design-vue'; |
| | | |
| | | import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; |
| | | import { |
| | |
| | | } from '#/api/erp/purchase/order'; |
| | | import { $t } from '#/locales'; |
| | | |
| | | import PlacePanel from '../place/modules/place-panel.vue'; |
| | | import { useGridColumns, useGridFormSchema } from './data'; |
| | | import Form from './modules/form.vue'; |
| | | import ImportForm from './modules/import-form.vue'; |
| | |
| | | defineOptions({ name: 'ErpPurchaseOrder' }); |
| | | |
| | | const route = useRoute(); |
| | | |
| | | /** |
| | | * 页ç¾ï¼4.2 éè´ä¸åä¸ 4.5 éè´è®¢åå
±ç¨ä¸å¼ 订åï¼ |
| | | * å并为ä¸ä¸ªèåï¼é¿å
两个å
¥å£éå¤ã |
| | | */ |
| | | const activeTab = ref('order'); |
| | | const TAB_ITEMS = [ |
| | | { key: 'order', label: 'éè´è®¢å' }, |
| | | { key: 'place', label: 'éè´ä¸å' }, |
| | | ]; |
| | | |
| | | const [FormModal, formModalApi] = useVbenModal({ |
| | | connectedComponent: Form, |
| | |
| | | toolbarConfig: { |
| | | refresh: true, |
| | | search: true, |
| | | custom: true, |
| | | }, |
| | | } as VxeTableGridOptions<ErpPurchaseOrderApi.PurchaseOrder>, |
| | | gridEvents: { |
| | |
| | | <FormModal @success="handleRefresh" /> |
| | | <ImportModal @success="handleRefresh" /> |
| | | <Print ref="printRef" /> |
| | | <Grid table-title="éè´è®¢åå表"> |
| | | <div class="mx-4 mb-2"> |
| | | <Tabs v-model:active-key="activeTab" :items="TAB_ITEMS" /> |
| | | </div> |
| | | <PlacePanel v-if="activeTab === 'place'" /> |
| | | <Grid v-show="activeTab === 'order'" table-title="éè´è®¢åå表"> |
| | | <template #toolbar-tools> |
| | | <TableAction |
| | | :actions="[ |
| | |
| | | import { Input, InputNumber } from 'ant-design-vue'; |
| | | |
| | | import { TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; |
| | | import { calcTaxIncludedLine } from '#/utils'; |
| | | import { MdmItemSelect } from '#/views/basicData/mdm/components'; |
| | | |
| | | import { useFormItemColumns } from '../data'; |
| | |
| | | handleRowChange(row); |
| | | } |
| | | |
| | | /** å¤çè¡æ°æ®åæ´ */ |
| | | /** å¤çè¡æ°æ®åæ´ï¼æ°éãå«ç¨åä»·ãç¨çåååç«å³éç®éé¢ */ |
| | | function handleRowChange(row: any) { |
| | | const index = tableData.value.findIndex((item) => item.seq === row.seq); |
| | | if (index === -1) { |
| | |
| | | } else { |
| | | tableData.value[index] = row; |
| | | } |
| | | calcTaxIncludedLine(row); |
| | | emit('update:items', [...tableData.value]); |
| | | } |
| | | |
| | | /** åå§åè¡æ°æ® */ |
| | | function initRow(row: ErpPurchaseOrderApi.PurchaseOrderItem) { |
| | | if (row.productPrice && row.count) { |
| | | row.totalProductPrice = erpPriceMultiply(row.productPrice, row.count) ?? 0; |
| | | row.taxPrice = |
| | | erpPriceMultiply(row.totalProductPrice, (row.taxPercent || 0) / 100) ?? 0; |
| | | row.totalPrice = row.totalProductPrice + row.taxPrice; |
| | | calcTaxIncludedLine(row); |
| | | } |
| | | } |
| | | |
| | |
| | | throw new Error(`第 ${i + 1} è¡ï¼äº§åæ°éä¸è½ä¸ºç©º`); |
| | | } |
| | | if (!item.productPrice || item.productPrice <= 0) { |
| | | throw new Error(`第 ${i + 1} è¡ï¼äº§ååä»·ä¸è½ä¸ºç©º`); |
| | | throw new Error(`第 ${i + 1} è¡ï¼å«ç¨åä»·ä¸è½ä¸ºç©º`); |
| | | } |
| | | } |
| | | } |
| | |
| | | <div class="flex space-x-4"> |
| | | <span>æ°éï¼{{ erpCountInputFormatter(summaries.count) }}</span> |
| | | <span> |
| | | éé¢ï¼{{ erpPriceInputFormatter(summaries.totalProductPrice) }} |
| | | ä¸å«ç¨éé¢ï¼{{ erpPriceInputFormatter(summaries.totalProductPrice) }} |
| | | </span> |
| | | <span>ç¨é¢ï¼{{ erpPriceInputFormatter(summaries.taxPrice) }}</span> |
| | | <span> |
| | | ç¨é¢å计ï¼{{ erpPriceInputFormatter(summaries.totalPrice) }} |
| | | å«ç¨æ»ä»·ï¼{{ erpPriceInputFormatter(summaries.totalPrice) }} |
| | | </span> |
| | | </div> |
| | | </div> |
| | |
| | | : erpPriceMultiply(price, count); |
| | | } |
| | | |
| | | /** æç»è¡ï¼éé¢ = åä»· à æ°éï¼ç¨é¢ = éé¢ Ã ç¨çï¼å«ç¨éé¢ = éé¢ + ç¨é¢ */ |
| | | /** æç»è¡ï¼å价为å«ç¨åä»·ï¼ï¼å«ç¨æ»ä»· = åä»· à æ°éï¼ç¨é¢ = å«ç¨æ»ä»· à ç¨ç ÷ï¼100 + ç¨çï¼ï¼ä¸å«ç¨éé¢ = å«ç¨æ»ä»· â ç¨é¢ */ |
| | | const printRows = computed(() => |
| | | (printData.value.items || []).map((item) => { |
| | | const totalProductPrice = |
| | | item.totalProductPrice ?? |
| | | multiplyPrice(item.productPrice, item.count) ?? |
| | | 0; |
| | | const totalPrice = |
| | | item.totalPrice ?? multiplyPrice(item.productPrice, item.count) ?? 0; |
| | | const taxPercent = item.taxPercent ?? 0; |
| | | const taxPrice = |
| | | item.taxPrice ?? |
| | | multiplyPrice(totalProductPrice, (item.taxPercent ?? 0) / 100) ?? |
| | | 0; |
| | | const totalPrice = item.totalPrice ?? totalProductPrice + taxPrice; |
| | | (taxPercent > 0 |
| | | ? Number(((totalPrice * taxPercent) / (100 + taxPercent)).toFixed(2)) |
| | | : 0); |
| | | const totalProductPrice = |
| | | item.totalProductPrice ?? Number((totalPrice - taxPrice).toFixed(2)); |
| | | return { ...item, totalProductPrice, taxPrice, totalPrice }; |
| | | }), |
| | | ); |
| | |
| | | æ°é |
| | | </th> |
| | | <th class="border border-solid border-[#dcdfe6] bg-[#f5f7fa] p-2 text-right font-bold"> |
| | | åä»·(å
) |
| | | å«ç¨åä»·(å
) |
| | | </th> |
| | | <th class="border border-solid border-[#dcdfe6] bg-[#f5f7fa] p-2 text-right font-bold"> |
| | | éé¢(å
) |
| | | ä¸å«ç¨éé¢(å
) |
| | | </th> |
| | | <th class="border border-solid border-[#dcdfe6] bg-[#f5f7fa] p-2 text-right font-bold"> |
| | | ç¨ç(%) |
| | |
| | | allowClear: true, |
| | | }, |
| | | }, |
| | | { |
| | | fieldName: 'contactName', |
| | | label: 'è系人', |
| | | component: 'Input', |
| | | componentProps: { |
| | | placeholder: '请è¾å
¥è系人', |
| | | allowClear: true, |
| | | }, |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | |
| | | <script lang="ts" setup> |
| | | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; |
| | | import type { SrmSupplierApi } from '#/api/srm/supplier'; |
| | | import { Page } from '@vben/common-ui'; |
| | | |
| | | import { ref } from 'vue'; |
| | | import PlacePanel from './modules/place-panel.vue'; |
| | | |
| | | import { Page, useVbenModal } from '@vben/common-ui'; |
| | | import { downloadFileFromBlobPart } from '@vben/utils'; |
| | | |
| | | import { message, Tabs } from 'ant-design-vue'; |
| | | |
| | | import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; |
| | | import { exportSupplierExcel, getSupplierPage } from '#/api/srm/supplier'; |
| | | import { $t } from '#/locales'; |
| | | import { |
| | | SUPPLIER_ONLINE_STATUS_CELLTAG, |
| | | SUPPLIER_PURCHASE_CATEGORY_CELLTAG, |
| | | SUPPLIER_STATUS_CELLTAG, |
| | | } from '#/views/srm/enums'; |
| | | |
| | | import Form from '../order/modules/form.vue'; |
| | | import { PLACE_CATEGORY_TABS, useGridColumns, useGridFormSchema } from './data'; |
| | | |
| | | /** éè´ä¸åï¼4.2ï¼ï¼æéè´åç±»åæ¢æ¥çä¾åºåï¼ç¹å»ãéè´ãç´æ¥è¿å
¥ä¸å */ |
| | | /** éè´ä¸åï¼4.2ï¼ï¼å·²å¹¶å
¥ãéè´è®¢åã页ç¾ï¼æ¤é¡µé¢ä¿çç¨äºæ§è·¯ç±ç´è¾¾ */ |
| | | defineOptions({ name: 'ErpPurchasePlace' }); |
| | | |
| | | /** å½åéè´åç±»æ ç¾é¡µï¼1 代çåå 2 åè¾
æ */ |
| | | const activeTab = ref('1'); |
| | | |
| | | const [FormModal, formModalApi] = useVbenModal({ |
| | | connectedComponent: Form, |
| | | destroyOnClose: true, |
| | | }); |
| | | |
| | | function handleRefresh() { |
| | | gridApi.query(); |
| | | } |
| | | |
| | | /** 导åºå½åéè´åç±»çä¾åºåæ¸
åï¼4.2â£ æ°æ®å¯¼åºï¼ */ |
| | | async function handleExport() { |
| | | const data = await exportSupplierExcel({ |
| | | ...(await gridApi.formApi.getValues()), |
| | | purchaseCategory: Number(activeTab.value), |
| | | }); |
| | | downloadFileFromBlobPart({ fileName: 'éè´ä¸åæ¸
å.xlsx', source: data }); |
| | | } |
| | | |
| | | /** å½åæ ç¾é¡µçä¾åºåæ¸
åï¼æå°ç¨ï¼ */ |
| | | const printRows = ref<SrmSupplierApi.SupplierVO[]>([]); |
| | | const printing = ref(false); |
| | | |
| | | /** æå°å½åéè´åç±»çä¾åºåæ¸
åï¼4.2⣠æå°æ¸
åï¼ */ |
| | | async function handlePrint() { |
| | | const rows = (gridApi.grid.getData() || |
| | | []) as SrmSupplierApi.SupplierVO[]; |
| | | if (rows.length === 0) { |
| | | message.warning('å½å没æå¯æå°çä¾åºåæ°æ®'); |
| | | return; |
| | | } |
| | | printRows.value = rows; |
| | | printing.value = true; |
| | | await new Promise<void>((resolve) => { |
| | | requestAnimationFrame(() => requestAnimationFrame(() => resolve())); |
| | | }); |
| | | const removePrintMode = () => { |
| | | document.body.classList.remove('erp-purchase-place-printing'); |
| | | printing.value = false; |
| | | }; |
| | | document.body.classList.add('erp-purchase-place-printing'); |
| | | window.addEventListener('afterprint', removePrintMode, { once: true }); |
| | | window.print(); |
| | | } |
| | | |
| | | /** åç±»ææ¡ */ |
| | | function categoryLabel(value?: number) { |
| | | return SUPPLIER_PURCHASE_CATEGORY_CELLTAG.labels[value ?? 0] || 'æªåç±»'; |
| | | } |
| | | |
| | | function onlineLabel(value?: number) { |
| | | return SUPPLIER_ONLINE_STATUS_CELLTAG.labels[value ?? 0] || '-'; |
| | | } |
| | | |
| | | function statusLabel(value?: number) { |
| | | return SUPPLIER_STATUS_CELLTAG.labels[value ?? 0] || '-'; |
| | | } |
| | | |
| | | /** ç¹å»ä¾åºåãéè´ãï¼æå¼éè´è®¢åæ°å»ºå¼¹çªå¹¶å¸¦å
¥ä¾åºå */ |
| | | function handlePlaceOrder(row: SrmSupplierApi.SupplierVO) { |
| | | formModalApi.setData({ formType: 'create', supplierId: row.id }).open(); |
| | | } |
| | | |
| | | function handleTabChange(key: number | string) { |
| | | activeTab.value = String(key); |
| | | gridApi.query(); |
| | | } |
| | | |
| | | const [Grid, gridApi] = useVbenVxeGrid({ |
| | | formOptions: { |
| | | schema: useGridFormSchema(), |
| | | }, |
| | | gridOptions: { |
| | | columns: useGridColumns(), |
| | | height: 'auto', |
| | | keepSource: true, |
| | | proxyConfig: { |
| | | ajax: { |
| | | query: async ({ page }, formValues) => |
| | | await getSupplierPage({ |
| | | pageNo: page.currentPage, |
| | | pageSize: page.pageSize, |
| | | purchaseCategory: Number(activeTab.value), |
| | | ...formValues, |
| | | }), |
| | | }, |
| | | }, |
| | | rowConfig: { keyField: 'id', isHover: true }, |
| | | toolbarConfig: { refresh: true, search: true, custom: true }, |
| | | } as VxeTableGridOptions<SrmSupplierApi.SupplierVO>, |
| | | }); |
| | | </script> |
| | | <template> |
| | | <Page auto-content-height> |
| | | <FormModal @success="handleRefresh" /> |
| | | <div class="mb-2"> |
| | | <Tabs |
| | | v-model:active-key="activeTab" |
| | | :items="PLACE_CATEGORY_TABS" |
| | | size="small" |
| | | @change="handleTabChange" |
| | | /> |
| | | </div> |
| | | <Grid table-title="éè´ä¸å"> |
| | | <template #toolbar-tools> |
| | | <TableAction |
| | | :actions="[ |
| | | { |
| | | label: $t('ui.actionTitle.export'), |
| | | type: 'primary', |
| | | icon: ACTION_ICON.DOWNLOAD, |
| | | auth: ['srm:supplier:export'], |
| | | onClick: handleExport, |
| | | }, |
| | | { |
| | | label: 'æå°æ¸
å', |
| | | type: 'primary', |
| | | icon: 'lucide:printer', |
| | | onClick: handlePrint, |
| | | }, |
| | | { |
| | | label: 'å·æ°', |
| | | type: 'primary', |
| | | icon: ACTION_ICON.REFRESH, |
| | | onClick: handleRefresh, |
| | | }, |
| | | ]" |
| | | /> |
| | | </template> |
| | | <template #actions="{ row }"> |
| | | <TableAction |
| | | :actions="[ |
| | | { |
| | | label: 'éè´', |
| | | type: 'link', |
| | | icon: ACTION_ICON.ADD, |
| | | auth: ['erp:purchase-order:create'], |
| | | onClick: handlePlaceOrder.bind(null, row), |
| | | }, |
| | | ]" |
| | | /> |
| | | </template> |
| | | </Grid> |
| | | |
| | | <!-- æå°æ¸
åï¼4.2â£ï¼ï¼ä»
æå°æ¶å±ç¤º --> |
| | | <Teleport to="body"> |
| | | <div |
| | | v-if="printing" |
| | | id="erpPurchasePlacePrint" |
| | | class="erp-purchase-place-print pointer-events-none fixed left-0 top-0 z-[-1] w-full bg-white text-[#303133] opacity-0" |
| | | > |
| | | <h2 class="m-0 mb-2 text-center text-[1.6em] font-bold leading-[1.2]"> |
| | | {{ |
| | | activeTab === '2' ? 'åè¾
æéè´æ¸
å' : '代çååéè´æ¸
å' |
| | | }} |
| | | </h2> |
| | | <table class="w-full border-collapse text-[13px] leading-[1.5]"> |
| | | <thead> |
| | | <tr> |
| | | <th class="border border-solid border-[#dcdfe6] bg-[#f5f7fa] p-2 text-center font-bold"> |
| | | åºå· |
| | | </th> |
| | | <th class="border border-solid border-[#dcdfe6] bg-[#f5f7fa] p-2 text-left font-bold"> |
| | | ä¾åºåç¼ç |
| | | </th> |
| | | <th class="border border-solid border-[#dcdfe6] bg-[#f5f7fa] p-2 text-left font-bold"> |
| | | ä¾åºååç§° |
| | | </th> |
| | | <th class="border border-solid border-[#dcdfe6] bg-[#f5f7fa] p-2 text-center font-bold"> |
| | | éè´åç±» |
| | | </th> |
| | | <th class="border border-solid border-[#dcdfe6] bg-[#f5f7fa] p-2 text-center font-bold"> |
| | | å¨çº¿ç¶æ |
| | | </th> |
| | | <th class="border border-solid border-[#dcdfe6] bg-[#f5f7fa] p-2 text-left font-bold"> |
| | | è系人 |
| | | </th> |
| | | <th class="border border-solid border-[#dcdfe6] bg-[#f5f7fa] p-2 text-left font-bold"> |
| | | èç³»çµè¯ |
| | | </th> |
| | | <th class="border border-solid border-[#dcdfe6] bg-[#f5f7fa] p-2 text-center font-bold"> |
| | | ç¶æ |
| | | </th> |
| | | </tr> |
| | | </thead> |
| | | <tbody> |
| | | <tr |
| | | v-for="(row, index) in printRows" |
| | | :key="row.id || index" |
| | | > |
| | | <td class="border border-solid border-[#dcdfe6] p-2 text-center"> |
| | | {{ index + 1 }} |
| | | </td> |
| | | <td class="border border-solid border-[#dcdfe6] p-2"> |
| | | {{ row.code || '-' }} |
| | | </td> |
| | | <td class="border border-solid border-[#dcdfe6] p-2"> |
| | | {{ row.name || '-' }} |
| | | </td> |
| | | <td class="border border-solid border-[#dcdfe6] p-2 text-center"> |
| | | {{ categoryLabel(row.purchaseCategory) }} |
| | | </td> |
| | | <td class="border border-solid border-[#dcdfe6] p-2 text-center"> |
| | | {{ onlineLabel(row.onlineStatus) }} |
| | | </td> |
| | | <td class="border border-solid border-[#dcdfe6] p-2"> |
| | | {{ row.contactName || '-' }} |
| | | </td> |
| | | <td class="border border-solid border-[#dcdfe6] p-2"> |
| | | {{ row.contactMobile || '-' }} |
| | | </td> |
| | | <td class="border border-solid border-[#dcdfe6] p-2 text-center"> |
| | | {{ statusLabel(row.status) }} |
| | | </td> |
| | | </tr> |
| | | </tbody> |
| | | </table> |
| | | </div> |
| | | </Teleport> |
| | | <PlacePanel /> |
| | | </Page> |
| | | </template> |
| | | |
| | | <style scoped> |
| | | @page { |
| | | margin: 8mm 10mm; |
| | | } |
| | | |
| | | @media print { |
| | | :global(body.erp-purchase-place-printing) { |
| | | padding: 0 !important; |
| | | margin: 0 !important; |
| | | -webkit-print-color-adjust: exact; |
| | | print-color-adjust: exact; |
| | | } |
| | | |
| | | :global(body.erp-purchase-place-printing *) { |
| | | visibility: hidden !important; |
| | | } |
| | | |
| | | :global(body.erp-purchase-place-printing .erp-purchase-place-print), |
| | | :global(body.erp-purchase-place-printing .erp-purchase-place-print *) { |
| | | visibility: visible !important; |
| | | } |
| | | |
| | | :global(body.erp-purchase-place-printing .erp-purchase-place-print) { |
| | | position: absolute; |
| | | top: 0; |
| | | left: 0; |
| | | z-index: auto; |
| | | box-sizing: border-box; |
| | | width: 100%; |
| | | padding: 0 !important; |
| | | margin: 0 !important; |
| | | pointer-events: auto; |
| | | opacity: 1; |
| | | } |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; |
| | | import type { SrmSupplierApi } from '#/api/srm/supplier'; |
| | | |
| | | import { ref } from 'vue'; |
| | | |
| | | import { useVbenModal } from '@vben/common-ui'; |
| | | import { downloadFileFromBlobPart } from '@vben/utils'; |
| | | |
| | | import { message, Tabs } from 'ant-design-vue'; |
| | | |
| | | import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; |
| | | import { exportSupplierExcel, getSupplierPage } from '#/api/srm/supplier'; |
| | | import { $t } from '#/locales'; |
| | | import { |
| | | SUPPLIER_ONLINE_STATUS_CELLTAG, |
| | | SUPPLIER_PURCHASE_CATEGORY_CELLTAG, |
| | | SUPPLIER_STATUS_CELLTAG, |
| | | } from '#/views/srm/enums'; |
| | | |
| | | import Form from './purchase-form.vue'; |
| | | import { PLACE_CATEGORY_TABS, useGridColumns, useGridFormSchema } from '../data'; |
| | | |
| | | /** éè´ä¸åï¼4.2ï¼ï¼æéè´åç±»åæ¢æ¥çä¾åºåï¼ç¹å»ãéè´ãç´æ¥è¿å
¥ä¸å */ |
| | | defineOptions({ name: 'ErpPurchasePlacePanel' }); |
| | | |
| | | /** å½åéè´åç±»æ ç¾é¡µï¼1 代çåå 2 åè¾
æ */ |
| | | const activeTab = ref('1'); |
| | | |
| | | const [FormModal, formModalApi] = useVbenModal({ |
| | | connectedComponent: Form, |
| | | destroyOnClose: true, |
| | | }); |
| | | |
| | | function handleRefresh() { |
| | | gridApi.query(); |
| | | } |
| | | |
| | | /** 导åºå½åéè´åç±»çä¾åºåæ¸
åï¼4.2â£ æ°æ®å¯¼åºï¼ */ |
| | | async function handleExport() { |
| | | const data = await exportSupplierExcel({ |
| | | ...(await gridApi.formApi.getValues()), |
| | | purchaseCategory: Number(activeTab.value), |
| | | }); |
| | | downloadFileFromBlobPart({ fileName: 'éè´ä¸åæ¸
å.xlsx', source: data }); |
| | | } |
| | | |
| | | /** å½åæ ç¾é¡µçä¾åºåæ¸
åï¼æå°ç¨ï¼ */ |
| | | const printRows = ref<SrmSupplierApi.SupplierVO[]>([]); |
| | | const printing = ref(false); |
| | | |
| | | /** æå°å½åéè´åç±»çä¾åºåæ¸
åï¼4.2⣠æå°æ¸
åï¼ */ |
| | | async function handlePrint() { |
| | | const rows = (gridApi.grid.getData() || []) as SrmSupplierApi.SupplierVO[]; |
| | | if (rows.length === 0) { |
| | | message.warning('å½å没æå¯æå°çä¾åºåæ°æ®'); |
| | | return; |
| | | } |
| | | printRows.value = rows; |
| | | printing.value = true; |
| | | await new Promise<void>((resolve) => { |
| | | requestAnimationFrame(() => requestAnimationFrame(() => resolve())); |
| | | }); |
| | | const removePrintMode = () => { |
| | | document.body.classList.remove('erp-purchase-place-printing'); |
| | | printing.value = false; |
| | | }; |
| | | document.body.classList.add('erp-purchase-place-printing'); |
| | | window.addEventListener('afterprint', removePrintMode, { once: true }); |
| | | window.print(); |
| | | } |
| | | |
| | | /** åç±»ææ¡ */ |
| | | function categoryLabel(value?: number) { |
| | | return SUPPLIER_PURCHASE_CATEGORY_CELLTAG.labels[value ?? 0] || 'æªåç±»'; |
| | | } |
| | | |
| | | function onlineLabel(value?: number) { |
| | | return SUPPLIER_ONLINE_STATUS_CELLTAG.labels[value ?? 0] || '-'; |
| | | } |
| | | |
| | | function statusLabel(value?: number) { |
| | | return SUPPLIER_STATUS_CELLTAG.labels[value ?? 0] || '-'; |
| | | } |
| | | |
| | | /** ç¹å»ä¾åºåãéè´ãï¼ç¡®è®¤éè´åç±»ãåçï¼å½å
¥æ°éãåä»·åçæè®¢å */ |
| | | function handlePlaceOrder(row: SrmSupplierApi.SupplierVO) { |
| | | formModalApi |
| | | .setData({ |
| | | supplier: row, |
| | | purchaseCategory: Number(activeTab.value), |
| | | }) |
| | | .open(); |
| | | } |
| | | |
| | | function handleTabChange(key: number | string) { |
| | | activeTab.value = String(key); |
| | | gridApi.query(); |
| | | } |
| | | |
| | | const [Grid, gridApi] = useVbenVxeGrid({ |
| | | formOptions: { |
| | | schema: useGridFormSchema(), |
| | | }, |
| | | gridOptions: { |
| | | columns: useGridColumns(), |
| | | height: 'auto', |
| | | keepSource: true, |
| | | proxyConfig: { |
| | | ajax: { |
| | | query: async ({ page }, formValues) => |
| | | await getSupplierPage({ |
| | | pageNo: page.currentPage, |
| | | pageSize: page.pageSize, |
| | | purchaseCategory: Number(activeTab.value), |
| | | ...formValues, |
| | | }), |
| | | }, |
| | | }, |
| | | rowConfig: { keyField: 'id', isHover: true }, |
| | | toolbarConfig: { refresh: true, search: true, custom: true }, |
| | | } as VxeTableGridOptions<SrmSupplierApi.SupplierVO>, |
| | | }); |
| | | </script> |
| | | <template> |
| | | <FormModal @success="handleRefresh" /> |
| | | <div class="mb-2"> |
| | | <Tabs |
| | | v-model:active-key="activeTab" |
| | | :items="PLACE_CATEGORY_TABS" |
| | | size="small" |
| | | @change="handleTabChange" |
| | | /> |
| | | </div> |
| | | <Grid table-title="éè´ä¸å"> |
| | | <template #toolbar-tools> |
| | | <TableAction |
| | | :actions="[ |
| | | { |
| | | label: $t('ui.actionTitle.export'), |
| | | type: 'primary', |
| | | icon: ACTION_ICON.DOWNLOAD, |
| | | auth: ['srm:supplier:export'], |
| | | onClick: handleExport, |
| | | }, |
| | | { |
| | | label: 'æå°æ¸
å', |
| | | type: 'primary', |
| | | icon: 'lucide:printer', |
| | | onClick: handlePrint, |
| | | }, |
| | | { |
| | | label: 'å·æ°', |
| | | type: 'primary', |
| | | icon: ACTION_ICON.REFRESH, |
| | | onClick: handleRefresh, |
| | | }, |
| | | ]" |
| | | /> |
| | | </template> |
| | | <template #actions="{ row }"> |
| | | <TableAction |
| | | :actions="[ |
| | | { |
| | | label: 'éè´', |
| | | type: 'link', |
| | | icon: ACTION_ICON.ADD, |
| | | auth: ['erp:purchase-order:create'], |
| | | onClick: handlePlaceOrder.bind(null, row), |
| | | }, |
| | | ]" |
| | | /> |
| | | </template> |
| | | </Grid> |
| | | |
| | | <!-- æå°æ¸
åï¼4.2â£ï¼ï¼ä»
æå°æ¶å±ç¤º --> |
| | | <Teleport to="body"> |
| | | <div |
| | | v-if="printing" |
| | | id="erpPurchasePlacePrint" |
| | | class="erp-purchase-place-print pointer-events-none fixed left-0 top-0 z-[-1] w-full bg-white text-[#303133] opacity-0" |
| | | > |
| | | <h2 class="m-0 mb-2 text-center text-[1.6em] font-bold leading-[1.2]"> |
| | | {{ activeTab === '2' ? 'åè¾
æéè´æ¸
å' : '代çååéè´æ¸
å' }} |
| | | </h2> |
| | | <table class="w-full border-collapse text-[13px] leading-[1.5]"> |
| | | <thead> |
| | | <tr> |
| | | <th class="border border-solid border-[#dcdfe6] bg-[#f5f7fa] p-2 text-center font-bold"> |
| | | åºå· |
| | | </th> |
| | | <th class="border border-solid border-[#dcdfe6] bg-[#f5f7fa] p-2 text-left font-bold"> |
| | | ä¾åºåç¼ç |
| | | </th> |
| | | <th class="border border-solid border-[#dcdfe6] bg-[#f5f7fa] p-2 text-left font-bold"> |
| | | ä¾åºååç§° |
| | | </th> |
| | | <th class="border border-solid border-[#dcdfe6] bg-[#f5f7fa] p-2 text-center font-bold"> |
| | | éè´åç±» |
| | | </th> |
| | | <th class="border border-solid border-[#dcdfe6] bg-[#f5f7fa] p-2 text-center font-bold"> |
| | | å¨çº¿ç¶æ |
| | | </th> |
| | | <th class="border border-solid border-[#dcdfe6] bg-[#f5f7fa] p-2 text-left font-bold"> |
| | | è系人 |
| | | </th> |
| | | <th class="border border-solid border-[#dcdfe6] bg-[#f5f7fa] p-2 text-left font-bold"> |
| | | èç³»çµè¯ |
| | | </th> |
| | | <th class="border border-solid border-[#dcdfe6] bg-[#f5f7fa] p-2 text-center font-bold"> |
| | | ç¶æ |
| | | </th> |
| | | </tr> |
| | | </thead> |
| | | <tbody> |
| | | <tr v-for="(row, index) in printRows" :key="row.id || index"> |
| | | <td class="border border-solid border-[#dcdfe6] p-2 text-center"> |
| | | {{ index + 1 }} |
| | | </td> |
| | | <td class="border border-solid border-[#dcdfe6] p-2"> |
| | | {{ row.code || '-' }} |
| | | </td> |
| | | <td class="border border-solid border-[#dcdfe6] p-2"> |
| | | {{ row.name || '-' }} |
| | | </td> |
| | | <td class="border border-solid border-[#dcdfe6] p-2 text-center"> |
| | | {{ categoryLabel(row.purchaseCategory) }} |
| | | </td> |
| | | <td class="border border-solid border-[#dcdfe6] p-2 text-center"> |
| | | {{ onlineLabel(row.onlineStatus) }} |
| | | </td> |
| | | <td class="border border-solid border-[#dcdfe6] p-2"> |
| | | {{ row.contactName || '-' }} |
| | | </td> |
| | | <td class="border border-solid border-[#dcdfe6] p-2"> |
| | | {{ row.contactMobile || '-' }} |
| | | </td> |
| | | <td class="border border-solid border-[#dcdfe6] p-2 text-center"> |
| | | {{ statusLabel(row.status) }} |
| | | </td> |
| | | </tr> |
| | | </tbody> |
| | | </table> |
| | | </div> |
| | | </Teleport> |
| | | </template> |
| | | |
| | | <style scoped> |
| | | @page { |
| | | margin: 8mm 10mm; |
| | | } |
| | | |
| | | @media print { |
| | | :global(body.erp-purchase-place-printing) { |
| | | padding: 0 !important; |
| | | margin: 0 !important; |
| | | -webkit-print-color-adjust: exact; |
| | | print-color-adjust: exact; |
| | | } |
| | | |
| | | :global(body.erp-purchase-place-printing *) { |
| | | visibility: hidden !important; |
| | | } |
| | | |
| | | :global(body.erp-purchase-place-printing .erp-purchase-place-print), |
| | | :global(body.erp-purchase-place-printing .erp-purchase-place-print *) { |
| | | visibility: visible !important; |
| | | } |
| | | |
| | | :global(body.erp-purchase-place-printing .erp-purchase-place-print) { |
| | | position: absolute; |
| | | top: 0; |
| | | left: 0; |
| | | z-index: auto; |
| | | box-sizing: border-box; |
| | | width: 100%; |
| | | padding: 0 !important; |
| | | margin: 0 !important; |
| | | pointer-events: auto; |
| | | opacity: 1; |
| | | } |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { ErpPurchaseOrderApi } from '#/api/erp/purchase/order'; |
| | | import type { MdmItemApi } from '#/api/mdm/item'; |
| | | import type { SrmSupplierApi } from '#/api/srm/supplier'; |
| | | |
| | | import { computed, nextTick, ref } from 'vue'; |
| | | |
| | | import { useVbenModal } from '@vben/common-ui'; |
| | | import { |
| | | erpCountInputFormatter, |
| | | erpPriceInputFormatter, |
| | | erpPriceMultiply, |
| | | } from '@vben/utils'; |
| | | |
| | | import { Descriptions, InputNumber, message, Select } from 'ant-design-vue'; |
| | | |
| | | import { TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; |
| | | import { createPurchaseOrder } from '#/api/erp/purchase/order'; |
| | | import { getContractListBySupplier } from '#/api/erp/purchase/supplier-contract'; |
| | | import { MdmItemSelect } from '#/views/basicData/mdm/components'; |
| | | import { SUPPLIER_PURCHASE_CATEGORY_CELLTAG } from '#/views/srm/enums'; |
| | | |
| | | defineOptions({ name: 'ErpPurchasePlaceForm' }); |
| | | |
| | | const emit = defineEmits(['success']); |
| | | |
| | | /** éè´ä¸åæç»è¡ */ |
| | | interface PlaceItem { |
| | | seq: number; |
| | | productId?: number; |
| | | productName?: string; |
| | | brandName?: string; |
| | | productUnitId?: number; |
| | | productUnitName?: string; |
| | | count?: number; |
| | | productPrice?: number; |
| | | totalProductPrice?: number; |
| | | } |
| | | |
| | | const supplier = ref<SrmSupplierApi.SupplierVO>(); |
| | | const purchaseCategory = ref<number>(); |
| | | const tableData = ref<PlaceItem[]>([]); |
| | | let nextSeq = 1; |
| | | |
| | | /** 该ä¾åºååä¸å¯å
³èçéè´ååï¼å忍¡æ¿ä¸åä¸ä¸åå
³èï¼ */ |
| | | const contractOptions = ref< |
| | | { label: string; value: number; status?: number }[] |
| | | >([]); |
| | | const contractId = ref<number>(); |
| | | |
| | | /** å è½½ä¾åºåçéè´ååï¼ä¾ãå
³èååã䏿鿩 */ |
| | | async function loadContracts(supplierId?: number) { |
| | | contractId.value = undefined; |
| | | contractOptions.value = []; |
| | | if (!supplierId) { |
| | | return; |
| | | } |
| | | const contracts = (await getContractListBySupplier(supplierId)) || []; |
| | | contractOptions.value = contracts |
| | | .filter((item) => item.type === 1) |
| | | .map((item) => ({ |
| | | label: `${item.code || ''} ${item.name || ''}`.trim(), |
| | | value: item.id!, |
| | | status: item.status, |
| | | })); |
| | | } |
| | | |
| | | const supplierName = computed(() => supplier.value?.name || '-'); |
| | | const categoryLabel = computed(() => { |
| | | const value = purchaseCategory.value ?? supplier.value?.purchaseCategory ?? 0; |
| | | return SUPPLIER_PURCHASE_CATEGORY_CELLTAG.labels[value] || 'æªåç±»'; |
| | | }); |
| | | |
| | | /** åè®¡ï¼æ°éãéé¢ */ |
| | | const summaries = computed(() => ({ |
| | | count: tableData.value.reduce((sum, item) => sum + (item.count || 0), 0), |
| | | totalProductPrice: tableData.value.reduce( |
| | | (sum, item) => sum + (item.totalProductPrice || 0), |
| | | 0, |
| | | ), |
| | | })); |
| | | |
| | | const [Grid, gridApi] = useVbenVxeGrid({ |
| | | gridOptions: { |
| | | columns: [ |
| | | { type: 'seq', title: 'åºå·', width: 55, fixed: 'left' }, |
| | | { |
| | | field: 'productId', |
| | | title: '产ååç§°', |
| | | minWidth: 220, |
| | | slots: { default: 'productId' }, |
| | | }, |
| | | { |
| | | field: 'brandName', |
| | | title: 'åç', |
| | | minWidth: 120, |
| | | }, |
| | | { |
| | | field: 'productUnitName', |
| | | title: 'åä½', |
| | | minWidth: 80, |
| | | }, |
| | | { |
| | | field: 'count', |
| | | title: 'æ°é', |
| | | minWidth: 110, |
| | | slots: { default: 'count' }, |
| | | }, |
| | | { |
| | | field: 'productPrice', |
| | | title: 'åä»·', |
| | | minWidth: 110, |
| | | slots: { default: 'productPrice' }, |
| | | }, |
| | | { |
| | | field: 'totalProductPrice', |
| | | title: 'éé¢', |
| | | minWidth: 110, |
| | | formatter: 'formatAmount2', |
| | | }, |
| | | { |
| | | title: 'æä½', |
| | | width: 60, |
| | | fixed: 'right', |
| | | slots: { default: 'actions' }, |
| | | }, |
| | | ], |
| | | data: [] as PlaceItem[], |
| | | minHeight: 240, |
| | | autoResize: true, |
| | | border: true, |
| | | rowConfig: { keyField: 'seq', isHover: true }, |
| | | pagerConfig: { enabled: false }, |
| | | toolbarConfig: { enabled: false }, |
| | | }, |
| | | }); |
| | | |
| | | /** éæ°å è½½è¡¨æ ¼æ°æ® */ |
| | | async function reloadTable() { |
| | | await nextTick(); |
| | | await gridApi.grid.reloadData(tableData.value); |
| | | } |
| | | |
| | | /** æ°å¢ä¸è¡ */ |
| | | async function handleAdd() { |
| | | tableData.value.push({ seq: nextSeq++ }); |
| | | await reloadTable(); |
| | | } |
| | | |
| | | /** å é¤ä¸è¡ */ |
| | | async function handleDelete(row: PlaceItem) { |
| | | const index = tableData.value.findIndex((item) => item.seq === row.seq); |
| | | if (index !== -1) { |
| | | tableData.value.splice(index, 1); |
| | | } |
| | | await reloadTable(); |
| | | } |
| | | |
| | | /** éæ©äº§åå带åºåçãåä½ãåèåä»· */ |
| | | async function handleProductChange( |
| | | item: MdmItemApi.Item | undefined, |
| | | row: PlaceItem, |
| | | ) { |
| | | if (!item?.id) { |
| | | return; |
| | | } |
| | | row.productId = item.id; |
| | | row.productName = item.name; |
| | | row.brandName = item.brandName; |
| | | row.productUnitId = item.unitMeasureId; |
| | | row.productUnitName = item.unitMeasureName; |
| | | row.productPrice = item.purchasePrice || 0; |
| | | row.count = row.count || 1; |
| | | handlePriceChange(row); |
| | | } |
| | | |
| | | /** æ°é / åä»·åååéç®è¡éé¢ */ |
| | | function handlePriceChange(row: PlaceItem) { |
| | | row.totalProductPrice = |
| | | erpPriceMultiply(row.productPrice || 0, row.count || 0) ?? 0; |
| | | void reloadTable(); |
| | | } |
| | | |
| | | /** æ ¡éªæç» */ |
| | | function validateItems() { |
| | | if (tableData.value.length === 0) { |
| | | throw new Error('请è³å°æ·»å ä¸è¡éè´äº§å'); |
| | | } |
| | | tableData.value.forEach((item, index) => { |
| | | if (!item.productId) { |
| | | throw new Error(`第 ${index + 1} è¡ï¼è¯·éæ©äº§å`); |
| | | } |
| | | if (!item.count || item.count <= 0) { |
| | | throw new Error(`第 ${index + 1} è¡ï¼æ°éå¿
é¡»å¤§äº 0`); |
| | | } |
| | | if (!item.productPrice || item.productPrice <= 0) { |
| | | throw new Error(`第 ${index + 1} è¡ï¼åä»·å¿
é¡»å¤§äº 0`); |
| | | } |
| | | }); |
| | | } |
| | | |
| | | /** æäº¤çæéè´è®¢å */ |
| | | async function submit() { |
| | | validateItems(); |
| | | const items: ErpPurchaseOrderApi.PurchaseOrderItem[] = |
| | | tableData.value.map((item) => ({ |
| | | productId: item.productId, |
| | | productUnitId: item.productUnitId, |
| | | productPrice: item.productPrice, |
| | | count: item.count, |
| | | taxPercent: 0, |
| | | })); |
| | | await createPurchaseOrder({ |
| | | supplierId: supplier.value!.id!, |
| | | contractId: contractId.value, |
| | | orderTime: formatNow(), |
| | | discountPercent: 0, |
| | | depositPrice: 0, |
| | | items, |
| | | }); |
| | | await modalApi.close(); |
| | | emit('success'); |
| | | message.success('éè´è®¢åå·²çæï¼å¹¶å·²æäº¤å®¡æ¹'); |
| | | } |
| | | |
| | | /** å½åæ¶é´ï¼yyyy-MM-dd HH:mm:ssï¼ */ |
| | | function formatNow() { |
| | | const now = new Date(); |
| | | const pad = (value: number) => String(value).padStart(2, '0'); |
| | | return `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())} ${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}`; |
| | | } |
| | | |
| | | /** å¼¹çª */ |
| | | const [Modal, modalApi] = useVbenModal({ |
| | | async onConfirm() { |
| | | modalApi.lock(); |
| | | try { |
| | | await submit(); |
| | | } catch (error: any) { |
| | | message.error(error.message || 'éè´ä¸å失败'); |
| | | } finally { |
| | | modalApi.unlock(); |
| | | } |
| | | }, |
| | | async onOpenChange(isOpen: boolean) { |
| | | if (!isOpen) { |
| | | tableData.value = []; |
| | | supplier.value = undefined; |
| | | contractId.value = undefined; |
| | | contractOptions.value = []; |
| | | return; |
| | | } |
| | | const data = modalApi.getData<{ |
| | | supplier: SrmSupplierApi.SupplierVO; |
| | | purchaseCategory?: number; |
| | | }>(); |
| | | supplier.value = data?.supplier; |
| | | purchaseCategory.value = |
| | | data?.purchaseCategory ?? data?.supplier?.purchaseCategory; |
| | | await loadContracts(supplier.value?.id); |
| | | nextSeq = 1; |
| | | tableData.value = [{ seq: nextSeq++ }]; |
| | | await reloadTable(); |
| | | }, |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <Modal |
| | | :title="`éè´ä¸å - ${supplierName}`" |
| | | class="w-4/5" |
| | | :show-confirm-button="true" |
| | | confirm-text="æäº¤çæè®¢å" |
| | | > |
| | | <Descriptions |
| | | class="mx-3 mb-2" |
| | | :column="2" |
| | | size="small" |
| | | bordered |
| | | > |
| | | <Descriptions.Item label="ä¾åºå">{{ supplierName }}</Descriptions.Item> |
| | | <Descriptions.Item label="éè´åç±»"> |
| | | {{ categoryLabel }} |
| | | </Descriptions.Item> |
| | | </Descriptions> |
| | | <div class="mx-3 mb-2 flex items-center gap-2"> |
| | | <span class="shrink-0 text-sm">å
³èååï¼</span> |
| | | <Select |
| | | v-model:value="contractId" |
| | | allow-clear |
| | | show-search |
| | | :options="contractOptions" |
| | | option-filter-prop="label" |
| | | class="w-96" |
| | | placeholder="å¯éï¼éæ©è¯¥ä¾åºåçéè´ååï¼ç¨äºååæ§è¡è¿åº¦ç»è®¡" |
| | | /> |
| | | <span |
| | | v-if="contractOptions.length === 0" |
| | | class="text-xs text-muted-foreground" |
| | | > |
| | | 该ä¾åºåææ éè´ååï¼å¯å
å¨ãåå管çãä¸å建 |
| | | </span> |
| | | </div> |
| | | <Grid class="mx-3 w-auto"> |
| | | <template #productId="{ row }"> |
| | | <MdmItemSelect |
| | | v-model:model-value="row.productId" |
| | | :label="row.productName" |
| | | placeholder="è¯·éæ©äº§å" |
| | | @change="(item) => handleProductChange(item, row)" |
| | | /> |
| | | </template> |
| | | <template #count="{ row }"> |
| | | <InputNumber |
| | | v-model:value="row.count" |
| | | :min="0" |
| | | :precision="3" |
| | | @change="handlePriceChange(row)" |
| | | /> |
| | | </template> |
| | | <template #productPrice="{ row }"> |
| | | <InputNumber |
| | | v-model:value="row.productPrice" |
| | | :min="0" |
| | | :precision="2" |
| | | @change="handlePriceChange(row)" |
| | | /> |
| | | </template> |
| | | <template #actions="{ row }"> |
| | | <TableAction |
| | | :actions="[ |
| | | { |
| | | label: 'å é¤', |
| | | type: 'link', |
| | | danger: true, |
| | | popConfirm: { |
| | | title: '确认å é¤è¯¥äº§ååï¼', |
| | | confirm: handleDelete.bind(null, row), |
| | | }, |
| | | }, |
| | | ]" |
| | | /> |
| | | </template> |
| | | <template #bottom> |
| | | <div class="mt-2 rounded border border-border bg-muted p-2"> |
| | | <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> |
| | | éé¢ï¼{{ erpPriceInputFormatter(summaries.totalProductPrice) }} |
| | | </span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <TableAction |
| | | class="mt-2 flex justify-center" |
| | | :actions="[ |
| | | { |
| | | label: 'æ·»å éè´äº§å', |
| | | type: 'default', |
| | | onClick: handleAdd, |
| | | }, |
| | | ]" |
| | | /> |
| | | </template> |
| | | </Grid> |
| | | </Modal> |
| | | </template> |
| | |
| | | }, |
| | | { |
| | | field: 'productPrice', |
| | | title: 'åä»·ï¼åèä»·ï¼', |
| | | title: 'å«ç¨åä»·ï¼åèä»·ï¼', |
| | | minWidth: 120, |
| | | fixed: 'right', |
| | | slots: { default: 'productPrice' }, |
| | | }, |
| | | { |
| | | field: 'totalPrice', |
| | | title: 'é¢ä¼°éé¢', |
| | | title: 'é¢ä¼°å«ç¨æ»ä»·', |
| | | minWidth: 120, |
| | | fixed: 'right', |
| | | formatter: 'formatAmount2', |
| | |
| | | }, |
| | | { |
| | | field: 'totalPrice', |
| | | title: 'é¢ä¼°éé¢', |
| | | title: 'é¢ä¼°å«ç¨éé¢', |
| | | formatter: 'formatAmount2', |
| | | minWidth: 120, |
| | | }, |
| | |
| | | }, |
| | | { |
| | | field: 'productPrice', |
| | | title: 'åä»·ï¼åèä»·ï¼', |
| | | title: 'å«ç¨åä»·ï¼åèä»·ï¼', |
| | | minWidth: 120, |
| | | formatter: ({ cellValue }) => erpPriceInputFormatter(cellValue) || '-', |
| | | }, |
| | | { |
| | | field: 'totalPrice', |
| | | title: 'é¢ä¼°éé¢', |
| | | title: 'é¢ä¼°å«ç¨æ»ä»·', |
| | | minWidth: 120, |
| | | formatter: ({ cellValue }) => erpPriceInputFormatter(cellValue) || '-', |
| | | }, |
| | |
| | | import { confirm, Page, useVbenModal } from '@vben/common-ui'; |
| | | import { downloadFileFromBlobPart, isEmpty } from '@vben/utils'; |
| | | |
| | | import { message, Tag } from 'ant-design-vue'; |
| | | import { message, Tabs, Tag } from 'ant-design-vue'; |
| | | |
| | | import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; |
| | | import { |
| | |
| | | } from '#/api/erp/purchase/request'; |
| | | import { $t } from '#/locales'; |
| | | |
| | | import DemandPanel from '../demand/modules/demand-panel.vue'; |
| | | import { useGridColumns, useGridFormSchema } from './data'; |
| | | import Form from './modules/form.vue'; |
| | | import ImportForm from './modules/import-form.vue'; |
| | |
| | | defineOptions({ name: 'ErpPurchaseRequest' }); |
| | | |
| | | const router = useRouter(); |
| | | |
| | | /** |
| | | * 页ç¾ï¼4.3 éè´ç³è¯·åä¸ 4.4 ç³è¯·éè´å齿¯ãæéè´éæ±ãï¼ |
| | | * å并为ä¸ä¸ªèåï¼é¿å
两个å
¥å£éå¤ã |
| | | */ |
| | | const activeTab = ref('request'); |
| | | const TAB_ITEMS = [ |
| | | { key: 'request', label: 'éè´ç³è¯·å' }, |
| | | { key: 'demand', label: 'ç³è¯·éè´å' }, |
| | | ]; |
| | | |
| | | const [FormModal, formModalApi] = useVbenModal({ |
| | | connectedComponent: Form, |
| | |
| | | toolbarConfig: { |
| | | refresh: true, |
| | | search: true, |
| | | custom: true, |
| | | }, |
| | | } as VxeTableGridOptions<ErpPurchaseRequestApi.PurchaseRequest>, |
| | | gridEvents: { |
| | |
| | | <ImportModal @success="handleRefresh" /> |
| | | <OrderFormModal @success="handleRefresh" /> |
| | | <ProcessModal @success="handleRefresh" /> |
| | | <Grid table-title="éè´ç³è¯·å表"> |
| | | <div class="mx-4 mb-2"> |
| | | <Tabs v-model:active-key="activeTab" :items="TAB_ITEMS" /> |
| | | </div> |
| | | <DemandPanel v-if="activeTab === 'demand'" /> |
| | | <Grid v-show="activeTab === 'request'" table-title="éè´ç³è¯·å表"> |
| | | <template #toolbar-tools> |
| | | <TableAction |
| | | :actions="[ |
| | |
| | | import { Input, InputNumber, DatePicker } from 'ant-design-vue'; |
| | | |
| | | import { TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; |
| | | import { calcTaxIncludedLine } from '#/utils'; |
| | | import { MdmItemSelect } from '#/views/basicData/mdm/components'; |
| | | |
| | | import { useFormItemColumns } from '../data'; |
| | |
| | | } else { |
| | | tableData.value[index] = row; |
| | | } |
| | | // 计ç®é¢ä¼°éé¢ |
| | | // 计ç®é¢ä¼°å«ç¨æ»ä»·ï¼å价为å«ç¨åä»·ï¼ |
| | | if (row.productPrice && row.count) { |
| | | row.totalPrice = erpPriceMultiply(row.productPrice, row.count) ?? 0; |
| | | calcTaxIncludedLine(row); |
| | | } |
| | | emit('update:items', [...tableData.value]); |
| | | } |
| | |
| | | /** åå§åè¡æ°æ® */ |
| | | function initRow(row: ErpPurchaseRequestApi.PurchaseRequestItem) { |
| | | if (row.productPrice && row.count) { |
| | | row.totalPrice = erpPriceMultiply(row.productPrice, row.count) ?? 0; |
| | | calcTaxIncludedLine(row); |
| | | } |
| | | } |
| | | |
| | |
| | | <span class="font-medium text-foreground">å计ï¼</span> |
| | | <div class="flex space-x-4"> |
| | | <span>æ°éï¼{{ erpCountInputFormatter(summaries.count) }}</span> |
| | | <span>é¢ä¼°éé¢ï¼{{ erpPriceInputFormatter(summaries.totalPrice) }}</span> |
| | | <span> |
| | | é¢ä¼°å«ç¨æ»ä»·ï¼{{ erpPriceInputFormatter(summaries.totalPrice) }} |
| | | </span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | |
| | | formItemClass: 'col-span-2', |
| | | componentProps: { placeholder: '请è¾å
¥å¤æ³¨', rows: 3 }, |
| | | }, |
| | | { |
| | | fieldName: 'blobIds', |
| | | label: 'ååéä»¶', |
| | | component: 'FileUpload', |
| | | help: 'æ¯æä¸ä¼ ååæ«æä»¶ãè¡¥å
åè®®ç', |
| | | componentProps: { |
| | | maxNumber: 10, |
| | | multiple: true, |
| | | valueKey: 'id', |
| | | }, |
| | | formItemClass: 'col-span-2', |
| | | }, |
| | | ]; |
| | | } |
| | | |
| | |
| | | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; |
| | | import type { ErpSupplierContractApi } from '#/api/erp/purchase/supplier-contract'; |
| | | |
| | | import { onMounted } from 'vue'; |
| | | import { useRoute } from 'vue-router'; |
| | | |
| | | import { Page, useVbenModal } from '@vben/common-ui'; |
| | | import { downloadFileFromBlobPart } from '@vben/utils'; |
| | | |
| | |
| | | |
| | | import { useGridColumns, useGridFormSchema } from './data'; |
| | | import Form from './modules/form.vue'; |
| | | |
| | | const route = useRoute(); |
| | | |
| | | const [FormModal, formModalApi] = useVbenModal({ |
| | | connectedComponent: Form, |
| | |
| | | toolbarConfig: { refresh: true, search: true, custom: true }, |
| | | } as VxeTableGridOptions<ErpSupplierContractApi.Contract>, |
| | | }); |
| | | |
| | | /** ä»ä¾åºåæ¡£æ¡ãååãè¿å
¥æ¶ï¼é»è®¤æè¯¥ä¾åºåçé */ |
| | | onMounted(async () => { |
| | | const supplierId = route.query.supplierId; |
| | | if (!supplierId) { |
| | | return; |
| | | } |
| | | await gridApi.formApi.setFieldValue('supplierId', Number(supplierId)); |
| | | const formValues = await gridApi.formApi.getValues(); |
| | | gridApi.formApi.setLatestSubmissionValues(formValues); |
| | | await gridApi.reload(formValues); |
| | | }); |
| | | </script> |
| | | <template> |
| | | <Page auto-content-height> |
| | |
| | | import { useVbenForm } from '#/adapter/form'; |
| | | import { |
| | | createContract, |
| | | getContract, |
| | | updateContract, |
| | | } from '#/api/erp/purchase/supplier-contract'; |
| | | import { $t } from '#/locales'; |
| | |
| | | isEdit.value = !!data?.id; |
| | | await formApi.resetForm(); |
| | | if (data?.id) { |
| | | await formApi.setValues(data); |
| | | // å表è¡ä¸å¸¦éä»¶ï¼ç¼è¾æ¶éæ°æå详æ
ï¼åå¡«ååéä»¶ |
| | | const detail = (await getContract(data.id)) || data; |
| | | await formApi.setValues(detail); |
| | | const blobIds = (detail.attachmentList || []).map((item) => ({ |
| | | uid: String(item.id), |
| | | name: item.name || '', |
| | | url: item.url || '', |
| | | status: 'done', |
| | | id: item.id, |
| | | })); |
| | | await formApi.setFieldValue('blobIds', blobIds); |
| | | } |
| | | }, |
| | | }); |
| | |
| | | }, |
| | | { |
| | | field: 'productPrice', |
| | | title: '产ååä»·', |
| | | title: 'å«ç¨åä»·', |
| | | minWidth: 120, |
| | | fixed: 'right', |
| | | slots: { default: 'productPrice' }, |
| | | }, |
| | | { |
| | | field: 'totalProductPrice', |
| | | title: 'éé¢', |
| | | title: 'ä¸å«ç¨éé¢', |
| | | minWidth: 120, |
| | | fixed: 'right', |
| | | formatter: 'formatAmount2', |
| | |
| | | }, |
| | | { |
| | | field: 'totalPrice', |
| | | title: 'ç¨é¢å计', |
| | | title: 'å«ç¨æ»ä»·', |
| | | minWidth: 120, |
| | | fixed: 'right', |
| | | formatter: 'formatAmount2', |
| | |
| | | import { Input, InputNumber, Select, Tag } from 'ant-design-vue'; |
| | | |
| | | import { TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; |
| | | import { calcTaxIncludedLine } from '#/utils'; |
| | | import { MdmItemSelect } from '#/views/basicData/mdm/components'; |
| | | |
| | | import { useFormItemColumns } from '../data'; |
| | |
| | | handleRowChange(row); |
| | | } |
| | | |
| | | /** å¤çè¡æ°æ®åæ´ */ |
| | | /** å¤çè¡æ°æ®åæ´ï¼æ°éãå«ç¨åä»·ãç¨çåååç«å³éç®éé¢ */ |
| | | function handleRowChange(row: any) { |
| | | const index = tableData.value.findIndex((item) => item.seq === row.seq); |
| | | if (index === -1) { |
| | |
| | | } else { |
| | | tableData.value[index] = row; |
| | | } |
| | | calcTaxIncludedLine(row); |
| | | emit('update:items', [...tableData.value]); |
| | | } |
| | | |
| | | /** åå§åè¡æ°æ® */ |
| | | function initRow(row: ErpSaleOrderApi.SaleOrderItem) { |
| | | if (row.productPrice && row.count) { |
| | | row.totalProductPrice = erpPriceMultiply(row.productPrice, row.count) ?? 0; |
| | | row.taxPrice = |
| | | erpPriceMultiply(row.totalProductPrice, (row.taxPercent || 0) / 100) ?? 0; |
| | | row.totalPrice = row.totalProductPrice + row.taxPrice; |
| | | calcTaxIncludedLine(row); |
| | | } |
| | | // é»è®¤éè¦ç产 |
| | | if (row.needProduction === undefined) { |
| | |
| | | throw new Error(`第 ${i + 1} è¡ï¼äº§åæ°éä¸è½ä¸ºç©º`); |
| | | } |
| | | if (!item.productPrice || item.productPrice <= 0) { |
| | | throw new Error(`第 ${i + 1} è¡ï¼äº§ååä»·ä¸è½ä¸ºç©º`); |
| | | throw new Error(`第 ${i + 1} è¡ï¼å«ç¨åä»·ä¸è½ä¸ºç©º`); |
| | | } |
| | | } |
| | | } |
| | |
| | | <div class="flex space-x-4"> |
| | | <span>æ°éï¼{{ erpCountInputFormatter(summaries.count) }}</span> |
| | | <span> |
| | | éé¢ï¼{{ erpPriceInputFormatter(summaries.totalProductPrice) }} |
| | | ä¸å«ç¨éé¢ï¼{{ erpPriceInputFormatter(summaries.totalProductPrice) }} |
| | | </span> |
| | | <span>ç¨é¢ï¼{{ erpPriceInputFormatter(summaries.taxPrice) }}</span> |
| | | <span> |
| | | ç¨é¢å计ï¼{{ erpPriceInputFormatter(summaries.totalPrice) }} |
| | | å«ç¨æ»ä»·ï¼{{ erpPriceInputFormatter(summaries.totalPrice) }} |
| | | </span> |
| | | </div> |
| | | </div> |
| | |
| | | } |
| | | |
| | | /** |
| | | * æç»è¡ï¼å端ä¸è¿åæç»è¡ç totalProductPrice/totalPriceï¼éæåä»·Ãæ°é计ç®ï¼ |
| | | * éé¢ = åä»· à æ°éï¼ç¨é¢ = éé¢ Ã ç¨çï¼å«ç¨éé¢ = éé¢ + ç¨é¢ï¼ |
| | | * æç»è¡ï¼å价为å«ç¨åä»·ï¼ï¼ |
| | | * å«ç¨æ»ä»· = åä»· à æ°éï¼ç¨é¢ = å«ç¨æ»ä»· à ç¨ç ÷ï¼100 + ç¨çï¼ï¼ä¸å«ç¨éé¢ = å«ç¨æ»ä»· â ç¨é¢ |
| | | */ |
| | | const printRows = computed(() => |
| | | (printData.value.items || []).map((item) => { |
| | | const totalProductPrice = |
| | | item.totalProductPrice ?? multiplyPrice(item.productPrice, item.count) ?? 0; |
| | | const totalPrice = |
| | | item.totalPrice ?? multiplyPrice(item.productPrice, item.count) ?? 0; |
| | | const taxPercent = item.taxPercent ?? 0; |
| | | const taxPrice = |
| | | item.taxPrice ?? multiplyPrice(totalProductPrice, (item.taxPercent ?? 0) / 100) ?? 0; |
| | | const totalPrice = item.totalPrice ?? totalProductPrice + taxPrice; |
| | | item.taxPrice ?? |
| | | (taxPercent > 0 |
| | | ? Number(((totalPrice * taxPercent) / (100 + taxPercent)).toFixed(2)) |
| | | : 0); |
| | | const totalProductPrice = |
| | | item.totalProductPrice ?? Number((totalPrice - taxPrice).toFixed(2)); |
| | | return { ...item, totalProductPrice, taxPrice, totalPrice }; |
| | | }), |
| | | ); |
| | |
| | | <th |
| | | class="border border-solid border-[#dcdfe6] bg-[#f5f7fa] p-2 text-right font-bold" |
| | | > |
| | | 产ååä»·(å
) |
| | | å«ç¨åä»·(å
) |
| | | </th> |
| | | <th |
| | | class="border border-solid border-[#dcdfe6] bg-[#f5f7fa] p-2 text-right font-bold" |
| | | > |
| | | éé¢(å
) |
| | | ä¸å«ç¨éé¢(å
) |
| | | </th> |
| | | <th |
| | | class="border border-solid border-[#dcdfe6] bg-[#f5f7fa] p-2 text-right font-bold" |