liu
昨天 70b7935fc261cd5e8eaaf1cfbc8078b04db576d2
fix(mes): 自动回填销售订单生产信息
已添加1个文件
已修改5个文件
122 ■■■■■ 文件已修改
src/api/mes/md/item/index.ts 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/md/item/index.vue 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/md/item/type/components/tree.vue 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/pro/workorder/data.ts 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/pro/workorder/modules/form.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/pro/workorder/sale-order-fill.ts 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/mes/md/item/index.ts
@@ -6,6 +6,7 @@
  /** MES ç‰©æ–™äº§å“ */
  export interface Item {
    id?: number; // ç‰©æ–™ç¼–号
    mdmItemId?: number; // MDM ç‰©æ–™ç¼–号
    code?: string; // ç‰©æ–™ç¼–码
    name?: string; // ç‰©æ–™åç§°
    specification?: string; // è§„格型号
@@ -45,6 +46,13 @@
  return requestClient.get<MesMdItemApi.Item>(`/mes/md/item/get?id=${id}`);
}
/** æ ¹æ® MDM ç‰©æ–™ç¼–号查询 MES ç‰©æ–™äº§å“ */
export function getItemByMdmItemId(mdmItemId: number) {
  return requestClient.get<MesMdItemApi.Item>(
    `/mes/md/item/get-by-mdm-item-id?mdmItemId=${mdmItemId}`,
  );
}
/** æ–°å¢žç‰©æ–™äº§å“ */
export function createItem(data: MesMdItemApi.Item) {
  return requestClient.post<number>('/mes/md/item/create', data);
src/views/mes/md/item/index.vue
@@ -76,7 +76,11 @@
  /** å¯¼å‡ºç‰©æ–™ */
  async function handleExport() {
    const data = await exportItem(await gridApi.formApi.getValues());
    const data = await exportItem({
      ...(await gridApi.formApi.getValues()),
      itemTypeId: selectedItemTypeId.value,
      excludeItemTypeCode: "EQUIPMENT",
    });
    downloadFileFromBlobPart({ fileName: "物料产品.xls", source: data });
  }
@@ -107,6 +111,7 @@
              pageSize: page.pageSize,
              ...formValues,
              itemTypeId: selectedItemTypeId.value,
              excludeItemTypeCode: "EQUIPMENT",
            });
          },
        },
@@ -132,7 +137,10 @@
        class="mr-4 h-full w-1/6"
        :body-style="{ height: '100%', overflow: 'hidden' }"
      >
        <MdItemTypeTree @node-click="handleTypeNodeClick" />
        <MdItemTypeTree
          :exclude-codes="['EQUIPMENT']"
          @node-click="handleTypeNodeClick"
        />
      </Card>
      <div class="w-5/6">
        <Grid table-title="物料产品列表">
src/views/mes/md/item/type/components/tree.vue
@@ -12,11 +12,13 @@
defineOptions({ name: 'MdItemTypeTree' });
withDefaults(
const props = withDefaults(
  defineProps<{
    excludeCodes?: string[];
    filterPlaceholder?: string;
  }>(),
  {
    excludeCodes: () => [],
    filterPlaceholder: '搜索分类',
  },
);
@@ -32,13 +34,36 @@
const itemTypeList = ref<MesMdItemTypeApi.ItemType[]>([]); // ç‰©æ–™åˆ†ç±»åˆ—表
const itemTypeTree = ref<any[]>([]); // ç‰©æ–™åˆ†ç±»æ ‘
/** æŒ‰åˆ†ç±»ç¼–码排除分类及其全部子分类 */
function filterExcludedTypes(data: MesMdItemTypeApi.ItemType[]) {
  const excludedIds = new Set<number>();
  let changed = true;
  while (changed) {
    changed = false;
    for (const item of data) {
      if (
        item.id !== undefined &&
        (props.excludeCodes.includes(item.code ?? '') ||
          (item.parentId !== undefined && excludedIds.has(item.parentId))) &&
        !excludedIds.has(item.id)
      ) {
        excludedIds.add(item.id);
        changed = true;
      }
    }
  }
  return data.filter(
    (item) => item.id === undefined || !excludedIds.has(item.id),
  );
}
/** åŠ è½½åˆ†ç±»æ ‘ */
async function loadTree() {
  loading.value = true;
  try {
    const data = await getItemTypeSimpleList();
    itemTypeList.value = data;
    itemTypeTree.value = handleTree(data, 'id', 'parentId');
    itemTypeList.value = filterExcludedTypes(data);
    itemTypeTree.value = handleTree(itemTypeList.value, 'id', 'parentId');
  } finally {
    loading.value = false;
  }
src/views/mes/pro/workorder/data.ts
@@ -15,9 +15,11 @@
} from '@vben/constants';
import { getDictOptions } from '@vben/hooks';
import { Button } from 'ant-design-vue';
import { Button, message } from 'ant-design-vue';
import { generateAutoCode } from '#/api/mes/md/autocode/record';
import { getItemByMdmItemId } from '#/api/mes/md/item';
import { getSaleOrderItemListByOrderId } from '#/api/erp/sale/order';
import CrmCustomerSelect from '#/components/crm-customer-select.vue';
import { getRangePickerDefaultProps } from '#/utils';
import {
@@ -25,6 +27,8 @@
} from '#/views/mes/md/item/components';
import { SrmSupplierSelect } from '#/views/srm/supplier/components';
import { ErpSaleOrderSelect } from '#/views/erp/sale/order/components';
import { summarizeProductionProduct } from './sale-order-fill';
/** è¡¨å•类型 */
export type FormType = 'confirm' | 'create' | 'detail' | 'finish' | 'update';
@@ -208,15 +212,40 @@
      componentProps: {
        disabled: headerReadonly,
        placeholder: '请选择营销订单(ERP销售订单)',
        // é€‰ä¸­è¥é”€è®¢å•后自动带入客户
        // é€‰ä¸­è¥é”€è®¢å•后自动带入客户、来源单号、产品和数量
        onChange: async (item?: ErpSaleOrderApi.SaleOrder) => {
          if (!item) {
            return;
          }
          const values = (await formApi?.getValues()) ?? {};
          if (values.clientId == null && item.customerId) {
            await formApi?.setFieldValue('clientId', item.customerId);
          await formApi?.setValues({
            clientId: item.customerId,
            orderSourceCode: item.no,
            productId: undefined,
            quantity: undefined,
          });
          if (!item.id) {
            return;
          }
          const orderItems = await getSaleOrderItemListByOrderId(item.id);
          const products = summarizeProductionProduct(orderItems ?? []);
          if (products.length === 0) {
            message.warning('该销售订单没有需要生产的产品明细');
            return;
          }
          if (products.length > 1) {
            message.warning('该销售订单包含多个生产产品,请选择具体产品并填写数量');
            return;
          }
          const [product] = products;
          const mesItem = await getItemByMdmItemId(product!.mdmProductId);
          if (!mesItem?.id) {
            message.warning('销售订单产品尚未同步到生产物料,请先同步产品主数据');
            return;
          }
          await formApi?.setValues({
            productId: mesItem.id,
            quantity: product!.quantity,
          });
        },
      },
      dependencies: {
src/views/mes/pro/workorder/modules/form.vue
@@ -90,7 +90,7 @@
      // äº§å“å˜æ›´æ—¶ï¼Œè§¦å‘工序加载
      if (isCreateMode.value && fieldsChanged.includes('productId')) {
        const newProductId = values.productId;
        if (newProductId && newProductId !== currentProductId.value) {
        if (newProductId !== currentProductId.value) {
          currentProductId.value = newProductId;
        }
      }
src/views/mes/pro/workorder/sale-order-fill.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,30 @@
import type { ErpSaleOrderApi } from '#/api/erp/sale/order';
export interface SaleOrderProductSummary {
  mdmProductId: number;
  quantity: number;
}
/**
 * æ±‡æ€»é”€å”®è®¢å•中需要生产的产品。
 * åŒä¸€äº§å“å¯å­˜åœ¨å¤šè¡Œï¼Œç”Ÿäº§å·¥å•只需要一条产品及合计数量。
 */
export function summarizeProductionProduct(
  items: ErpSaleOrderApi.SaleOrderItem[],
): SaleOrderProductSummary[] {
  const productionItems = items.filter(
    (item) => item.needProduction === 1 && item.productId != null,
  );
  const quantityByProduct = new Map<number, number>();
  for (const item of productionItems) {
    const productId = item.productId!;
    quantityByProduct.set(
      productId,
      (quantityByProduct.get(productId) ?? 0) + Number(item.count ?? 0),
    );
  }
  return [...quantityByProduct.entries()].map(([mdmProductId, quantity]) => ({
    mdmProductId,
    quantity,
  }));
}