| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <script lang="ts" setup> |
| | | import type { TableColumnsType } from 'ant-design-vue'; |
| | | |
| | | import type { PendingProductionItem } from '#/api/erp/sale/order'; |
| | | |
| | | import { computed, ref } from 'vue'; |
| | | |
| | | import { useVbenModal } from '@vben/common-ui'; |
| | | |
| | | import { Alert, InputNumber, message, Spin, Table, Tag } from 'ant-design-vue'; |
| | | |
| | | import { |
| | | generateMpsFromSaleOrder, |
| | | getPendingProductionList, |
| | | } from '#/api/erp/sale/order'; |
| | | |
| | | const emit = defineEmits(['success']); |
| | | |
| | | interface RowItem extends PendingProductionItem { |
| | | /** 计åç产æ°éï¼å¯ç¼è¾ï¼ */ |
| | | planQuantity?: number; |
| | | /** æ¯å¦å¾é */ |
| | | selected?: boolean; |
| | | } |
| | | |
| | | const loading = ref(false); |
| | | const submitting = ref(false); |
| | | const list = ref<RowItem[]>([]); |
| | | |
| | | const columns: TableColumnsType = [ |
| | | { title: 'éå®è®¢åå·', dataIndex: 'saleOrderNo', width: 150, fixed: 'left' }, |
| | | { title: '客æ·', dataIndex: 'customerName', width: 130, ellipsis: true }, |
| | | { title: '产åç¼ç ', dataIndex: 'productCode', width: 120 }, |
| | | { title: '产ååç§°', dataIndex: 'productName', width: 140, ellipsis: true }, |
| | | { title: 'è§æ ¼', dataIndex: 'productSpecification', width: 100 }, |
| | | { title: 'è®¢è´æ°é', dataIndex: 'orderCount', width: 100, align: 'right' }, |
| | | { title: 'è®¢åæ¥æ', dataIndex: 'orderTime', width: 160 }, |
| | | { |
| | | title: 'å¯ç¨åºå', |
| | | dataIndex: 'stockQuantity', |
| | | width: 100, |
| | | align: 'right', |
| | | }, |
| | | { |
| | | title: '建议ç产', |
| | | dataIndex: 'suggestQuantity', |
| | | width: 110, |
| | | align: 'right', |
| | | }, |
| | | { title: 'è®¡åæ°é', dataIndex: 'planQuantity', width: 130, key: 'plan' }, |
| | | ]; |
| | | |
| | | /** å·²å¾éçè¡ */ |
| | | const selectedRows = computed(() => list.value.filter((r) => r.selected)); |
| | | |
| | | /** åè®¡è®¡åæ°é */ |
| | | const totalPlanQuantity = computed(() => |
| | | selectedRows.value.reduce((sum, r) => sum + (r.planQuantity ?? 0), 0), |
| | | ); |
| | | |
| | | /** åºåå
è¶³ï¼æ éç产ï¼çè¡æ° */ |
| | | const enoughStockCount = computed( |
| | | () => list.value.filter((r) => (r.suggestQuantity ?? 0) <= 0).length, |
| | | ); |
| | | |
| | | function handleSelectAll(checked: boolean) { |
| | | list.value.forEach((row) => { |
| | | // åºåå¤çé»è®¤ä¸å¾éï¼ä½å
许æå·¥å¾éï¼å®¢æ·å¯è½è¦æ±å¤è´§ï¼ |
| | | row.selected = checked; |
| | | }); |
| | | } |
| | | |
| | | async function loadList() { |
| | | loading.value = true; |
| | | try { |
| | | const data = await getPendingProductionList(); |
| | | list.value = (data ?? []).map((item) => { |
| | | const suggest = item.suggestQuantity ?? 0; |
| | | return { |
| | | ...item, |
| | | // é»è®¤å¾ééè¦ç产çï¼å»ºè®®ç产 > 0ï¼ |
| | | selected: suggest > 0, |
| | | planQuantity: suggest > 0 ? suggest : undefined, |
| | | }; |
| | | }); |
| | | } finally { |
| | | loading.value = false; |
| | | } |
| | | } |
| | | |
| | | const [Modal, modalApi] = useVbenModal({ |
| | | async onConfirm() { |
| | | const items = selectedRows.value |
| | | .filter((r) => (r.planQuantity ?? 0) > 0) |
| | | .map((r) => ({ |
| | | saleOrderItemId: r.saleOrderItemId!, |
| | | quantity: r.planQuantity!, |
| | | })); |
| | | if (items.length === 0) { |
| | | message.warning('请è³å°å¾é䏿¡å¹¶å¡«åå¤§äº 0 çè®¡åæ°é'); |
| | | return; |
| | | } |
| | | submitting.value = true; |
| | | try { |
| | | const count = await generateMpsFromSaleOrder(items); |
| | | emit('success'); |
| | | message.success(`å·²çæ ${count} æ¡ç产计å`); |
| | | await modalApi.close(); |
| | | } finally { |
| | | submitting.value = false; |
| | | } |
| | | }, |
| | | async onOpenChange(isOpen: boolean) { |
| | | if (!isOpen) { |
| | | list.value = []; |
| | | return; |
| | | } |
| | | await loadList(); |
| | | }, |
| | | }); |
| | | </script> |
| | | |
| | | <template> |
| | | <Modal title="ä»éå®è®¢åçæç产计å" class="w-4/5"> |
| | | <Spin :spinning="loading || submitting"> |
| | | <div class="mx-4 flex flex-col gap-3"> |
| | | <Alert |
| | | type="info" |
| | | show-icon |
| | | message="æãå¯ç¨åºåã夿æ¯å¦éè¦ç产ï¼åºåå
è¶³çå¯ä¸å®æï¼å»ºè®®ç产æ°é = è®¢è´æ°é â å¯ç¨åºåãå¾éåå¯ç´æ¥ä¿®æ¹è®¡åæ°éã" |
| | | /> |
| | | |
| | | <div class="flex items-center gap-4 text-gray-600"> |
| | | <span>å
± <b>{{ list.length }}</b> æ¡å¾
宿æç»</span> |
| | | <span v-if="enoughStockCount > 0" class="text-green-600"> |
| | | å
¶ä¸ {{ enoughStockCount }} æ¡åºåå
è¶³ |
| | | </span> |
| | | <span> |
| | | å·²é <b>{{ selectedRows.length }}</b> æ¡ï¼åè®¡è®¡åæ°é |
| | | <b>{{ totalPlanQuantity }}</b> |
| | | </span> |
| | | </div> |
| | | |
| | | <Table |
| | | :data-source="list" |
| | | :columns="columns" |
| | | :pagination="false" |
| | | :row-selection="{ |
| | | selectedRowKeys: selectedRows.map((r) => r.saleOrderItemId), |
| | | onSelect: (record: RowItem, checked: boolean) => { |
| | | record.selected = checked; |
| | | if (checked && !record.planQuantity) { |
| | | record.planQuantity = |
| | | (record.suggestQuantity ?? 0) > 0 |
| | | ? record.suggestQuantity |
| | | : undefined; |
| | | } |
| | | }, |
| | | onSelectAll: (checked: boolean) => handleSelectAll(checked), |
| | | }" |
| | | :scroll="{ y: 420, x: 1400 }" |
| | | row-key="saleOrderItemId" |
| | | size="small" |
| | | > |
| | | <template #bodyCell="{ column, record }"> |
| | | <template v-if="column.dataIndex === 'stockQuantity'"> |
| | | <span |
| | | :class=" |
| | | (record.stockQuantity ?? 0) > 0 |
| | | ? 'font-semibold text-green-600' |
| | | : 'text-red-500' |
| | | " |
| | | > |
| | | {{ record.stockQuantity ?? 0 }} |
| | | </span> |
| | | </template> |
| | | <template v-else-if="column.dataIndex === 'suggestQuantity'"> |
| | | <Tag |
| | | v-if="(record.suggestQuantity ?? 0) > 0" |
| | | color="warning" |
| | | > |
| | | {{ record.suggestQuantity }} |
| | | </Tag> |
| | | <Tag v-else color="success">åºåå¤</Tag> |
| | | </template> |
| | | <template v-else-if="column.key === 'plan'"> |
| | | <InputNumber |
| | | v-model:value="record.planQuantity" |
| | | :min="0" |
| | | :precision="3" |
| | | class="w-full" |
| | | placeholder="å¡«åè®¡åæ°é" |
| | | @change=" |
| | | (val: any) => { |
| | | record.selected = (val ?? 0) > 0; |
| | | } |
| | | " |
| | | /> |
| | | </template> |
| | | </template> |
| | | </Table> |
| | | </div> |
| | | </Spin> |
| | | </Modal> |
| | | </template> |