云
5 天以前 1ea7a89ba34756154470cab6c41abe4e058d174e
feat: 完善仓储废料和合同导出界面

仓库设置增加类型字段及筛选,库存列表展示物料技术参数。新增废料入库、出售和按订单统计页面,并将合同下载入口明确标注为 Word 导出。

废料财务审批流程仍需发布,合同模板尚未完成 Word 版式检查。
已添加1个文件
已修改5个文件
115 ■■■■■ 文件已修改
src/api/mes/wm/warehouse/index.ts 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/crm/contract/detail/index.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/crm/contract/index.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/materialstock/data.ts 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/scrap/index.vue 84 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/wls/warehouse/data.ts 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/mes/wm/warehouse/index.ts
@@ -8,6 +8,7 @@
    id?: number; // ä»“库编号
    code?: string; // ä»“库编码
    name?: string; // ä»“库名称
    warehouseType?: string; // ä»“库类型
    address?: string; // åœ°å€
    area?: number; // é¢ç§¯
    chargeUserId?: number; // è´Ÿè´£äºº
src/views/crm/contract/detail/index.vue
@@ -120,7 +120,7 @@
            onClick: handleEdit,
          },
          {
            label: '下载合同',
            label: '导出 Word',
            type: 'primary',
            icon: 'lucide:download',
            auth: ['crm:contract:query'],
src/views/crm/contract/index.vue
@@ -333,7 +333,7 @@
        <TableAction
          :actions="[
            {
              label: '下载合同',
              label: '导出 Word',
              type: 'link',
              icon: 'lucide:download',
              auth: ['crm:contract:query'],
src/views/wls/materialstock/data.ts
@@ -104,6 +104,11 @@
      minWidth: 120,
    },
    {
      field: 'technicalParameters',
      title: '技术参数',
      minWidth: 160,
    },
    {
      field: 'quantity',
      title: '在库数量',
      width: 100,
src/views/wls/scrap/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,84 @@
<script lang="ts" setup>
import { onMounted, reactive, ref } from 'vue';
import { Page } from '@vben/common-ui';
import { Button, Card, DatePicker, Form, FormItem, Input, InputNumber, Modal, Space, Table, Tabs, TabPane, message } from 'ant-design-vue';
import dayjs from 'dayjs';
import { requestClient } from '#/api/request';
import { MdItemSelect } from '#/views/mes/md/item/components';
import { ProWorkOrderSelect } from '#/views/mes/pro/workorder/components';
import { WmMaterialStockSelect } from '#/views/wls/materialstock/components';
import { WmWarehouseAreaSelect, WmWarehouseLocationSelect, WmWarehouseSelect } from '#/views/wls/warehouse/components';
defineOptions({ name: 'WlsScrap' });
const receiptOpen = ref(false); const saleOpen = ref(false); const detailsOpen = ref(false); const saleLines = ref<any[]>([]); const busy = ref(false);
const receiptRows = ref<any[]>([]); const saleRows = ref<any[]>([]); const summaryRows = ref<any[]>([]);
const month = ref(dayjs());
const receiptPage = reactive({ current: 1, pageSize: 20, total: 0 });
const salePage = reactive({ current: 1, pageSize: 20, total: 0 });
const receipt = reactive<any>({}); const sale = reactive<any>({});
const receiptColumns = [{title:'入库单号',dataIndex:'code'}, {title:'生产订单号',dataIndex:'workOrderCode'}, {title:'物料编号',dataIndex:'itemId'}, {title:'数量',dataIndex:'quantity'}, {title:'库位编号',dataIndex:'areaId'}, {title:'入库时间',dataIndex:'receiptTime'}];
const saleColumns = [{title:'出售单号',dataIndex:'code'}, {title:'购买方',dataIndex:'buyer'}, {title:'状态',dataIndex:'status'}, {title:'创建时间',dataIndex:'createTime'}, {title:'操作',key:'actions'}];
const summaryColumns = [{title:'生产订单号',dataIndex:'workOrderCode'}, {title:'物料编号',dataIndex:'itemId'}, {title:'废料数量',dataIndex:'quantity'}];
const statusText: Record<number,string> = {0:'草稿',1:'审批中',2:'已出库',3:'已驳回',4:'已取消'};
async function loadReceipts() { const r: any = await requestClient.get('/mes/wm/scrap-receipt/page', {params:{pageNo:receiptPage.current,pageSize:receiptPage.pageSize}}); receiptRows.value = r.list; receiptPage.total = r.total; }
async function loadSales() { const r: any = await requestClient.get('/mes/wm/scrap-sale/page', {params:{pageNo:salePage.current,pageSize:salePage.pageSize}}); saleRows.value = r.list; salePage.total = r.total; }
async function loadSummary() { if (month.value) summaryRows.value = await requestClient.get('/mes/wm/scrap-receipt/monthly-summary', {params:{month:month.value.format('YYYY-MM')}}); }
function newRequestId() { return globalThis.crypto?.randomUUID?.() ?? 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replaceAll(/[xy]/g, c => { const n = Math.trunc(Math.random() * 16); return (c === 'x' ? n : (n & 3) | 8).toString(16); }); }
async function showDetails(row:any) { saleLines.value = await requestClient.get('/mes/wm/scrap-sale/lines', {params:{id:row.id}}); detailsOpen.value = true; }
function openReceipt() { Object.keys(receipt).forEach(k=>delete receipt[k]); Object.assign(receipt,{requestId:newRequestId(),quantity:1}); receiptOpen.value=true; }
function openSale() { Object.keys(sale).forEach(k=>delete sale[k]); Object.assign(sale,{requestId:newRequestId(),quantity:1,unitPrice:0}); saleOpen.value=true; }
async function saveReceipt() {
 if (!receipt.workOrderId || !receipt.itemId || !receipt.warehouseId || !receipt.locationId || !receipt.areaId || !(receipt.quantity > 0)) { message.warning('请填写生产订单、物料、库位和数量'); return; }
 busy.value=true; try { await requestClient.post('/mes/wm/scrap-receipt/create',receipt); receiptOpen.value=false; message.success('入库成功'); await loadReceipts(); } finally {busy.value=false;}
}
async function saveSale() {
 if (!sale.buyer?.trim() || !sale.materialStockId || !(sale.quantity > 0) || sale.unitPrice == null || sale.unitPrice < 0) { message.warning('请填写购买方、库存、数量和单价'); return; }
 busy.value=true; try { await requestClient.post('/mes/wm/scrap-sale/create',{requestId:sale.requestId,buyer:sale.buyer,remark:sale.remark,lines:[{materialStockId:sale.materialStockId,quantity:sale.quantity,unitPrice:sale.unitPrice}]}); saleOpen.value=false; message.success('出售单已创建'); await loadSales(); } finally {busy.value=false;}
}
async function submitSale(row:any) { await requestClient.put('/mes/wm/scrap-sale/submit',null,{params:{id:row.id}}); message.success('已提交财务审批'); await loadSales(); }
onMounted(async()=>{ await Promise.all([loadReceipts(),loadSales(),loadSummary()]); });
</script>
<template>
 <Page auto-content-height>
  <Card title="废料管理">
   <Tabs>
    <TabPane key="receipts" tab="废料入库">
     <Space style="margin-bottom: 16px"><Button type="primary" @click="openReceipt">登记入库</Button><Button @click="loadReceipts">刷新</Button></Space>
     <Table :data-source="receiptRows" :columns="receiptColumns" row-key="id" :pagination="receiptPage" :scroll="{x:900}" @change="(p:any)=>{receiptPage.current=p.current;receiptPage.pageSize=p.pageSize;loadReceipts()}" />
    </TabPane>
    <TabPane key="sales" tab="废料出售">
     <Space style="margin-bottom: 16px"><Button type="primary" @click="openSale">新建出售单</Button><Button @click="loadSales">刷新</Button></Space>
     <Table :data-source="saleRows" :columns="saleColumns" row-key="id" :pagination="salePage" :scroll="{x:800}" @change="(p:any)=>{salePage.current=p.current;salePage.pageSize=p.pageSize;loadSales()}">
      <template #bodyCell="{column,record}"><template v-if="column.dataIndex === 'status'">{{ statusText[record.status] || record.status }}</template><template v-else-if="column.key === 'actions'"><Button type="link" @click="showDetails(record)">明细</Button><Button v-if="record.status === 0" type="link" @click="submitSale(record)">提交财务审批</Button></template></template>
     </Table>
    </TabPane>
    <TabPane key="summary" tab="按订单统计">
     <Space style="margin-bottom: 16px"><DatePicker v-model:value="month" picker="month" :allow-clear="false" /><Button @click="loadSummary">查询</Button></Space>
     <Table :data-source="summaryRows" :columns="summaryColumns" :pagination="false" :row-key="(r:any)=>`${r.workOrderId}-${r.itemId}`" />
    </TabPane>
   </Tabs>
  </Card>
  <Modal v-model:open="receiptOpen" title="登记废料入库" :confirm-loading="busy" @ok="saveReceipt">
   <Form layout="vertical">
    <FormItem label="生产订单" required><ProWorkOrderSelect v-model="receipt.workOrderId" /></FormItem>
    <FormItem label="废料物料" required><MdItemSelect v-model="receipt.itemId" /></FormItem>
    <FormItem label="仓库" required><WmWarehouseSelect v-model="receipt.warehouseId" @change="receipt.locationId=undefined;receipt.areaId=undefined" /></FormItem>
    <FormItem label="库区" required><WmWarehouseLocationSelect v-model="receipt.locationId" :warehouse-id="receipt.warehouseId" @change="receipt.areaId=undefined" /></FormItem>
    <FormItem label="废料库位" required><WmWarehouseAreaSelect v-model="receipt.areaId" :location-id="receipt.locationId" /></FormItem>
    <FormItem label="数量" required><InputNumber v-model:value="receipt.quantity" :min="0.0001" :precision="4" style="width:100%" /></FormItem>
    <FormItem label="批次号"><Input v-model:value="receipt.batchCode" :maxlength="100" /></FormItem>
    <FormItem label="备注"><Input.TextArea v-model:value="receipt.remark" :maxlength="500" /></FormItem>
   </Form>
  </Modal>
  <Modal v-model:open="detailsOpen" title="出售明细" :footer="null"><Table :data-source="saleLines" row-key="id" :pagination="false" :columns="[{title:'库存编号',dataIndex:'materialStockId'},{title:'数量',dataIndex:'quantity'},{title:'单价',dataIndex:'unitPrice'},{title:'出库流水编号',dataIndex:'transactionId'}]" /></Modal>
  <Modal v-model:open="saleOpen" title="新建废料出售单" :confirm-loading="busy" @ok="saveSale">
   <Form layout="vertical">
    <FormItem label="购买方" required><Input v-model:value="sale.buyer" :maxlength="200" /></FormItem>
    <FormItem label="废料库存" required><WmMaterialStockSelect v-model="sale.materialStockId" /></FormItem>
    <FormItem label="数量" required><InputNumber v-model:value="sale.quantity" :min="0.0001" :precision="4" style="width:100%" /></FormItem>
    <FormItem label="单价" required><InputNumber v-model:value="sale.unitPrice" :min="0" :precision="4" style="width:100%" /></FormItem>
    <FormItem label="备注"><Input.TextArea v-model:value="sale.remark" :maxlength="500" /></FormItem>
   </Form>
  </Modal>
 </Page>
</template>
src/views/wls/warehouse/data.ts
@@ -70,6 +70,16 @@
      rules: 'required',
    },
    {
      fieldName: 'warehouseType',
      label: '仓库类型',
      component: 'Input',
      componentProps: {
        allowClear: true,
        maxlength: 50,
        placeholder: '例如原料仓、半成品仓、成品仓或废料仓',
      },
    },
    {
      fieldName: 'chargeUserId',
      label: '负责人',
      component: 'ApiSelect',
@@ -146,6 +156,12 @@
      },
    },
    {
      fieldName: 'warehouseType',
      label: '仓库类型',
      component: 'Input',
      componentProps: { allowClear: true, placeholder: '请输入仓库类型' },
    },
    {
      fieldName: 'frozen',
      label: '是否冻结',
      component: 'Select',
@@ -176,6 +192,11 @@
      minWidth: 160,
    },
    {
      field: 'warehouseType',
      title: '仓库类型',
      minWidth: 120,
    },
    {
      field: 'address',
      title: '仓库地址',
      minWidth: 180,