|
|
@@ -0,0 +1,94 @@
|
|
|
+/**
|
|
|
+ * S0 制造/仓储域 —— 业务关系字段的统一 lookup 取数。
|
|
|
+ *
|
|
|
+ * 设计约束(与 Quality 域 qualityLookups 同口径):
|
|
|
+ * 1. 只复用既有列表/options 接口,不新增后端端点;这些接口本身已走
|
|
|
+ * AdoS0TenantScope 租户作用域,前端不传 tenantId、不参与 scope 判定。
|
|
|
+ * 2. 每个 lookup 全页面只取一次(模块级 Promise 缓存),下拉与列表解析共用同一份,
|
|
|
+ * **不产生 per-row 请求**。
|
|
|
+ * 3. 选项 value 一律是**业务编码**(ItemNum / Line / PersonSkill.Code),
|
|
|
+ * 不是内部自增主键或雪花 id —— 保存回后端的仍是原业务编码,落库语义不变。
|
|
|
+ */
|
|
|
+import { s0CustomersApi, s0MaterialsApi } from '../api/s0SalesApi';
|
|
|
+import { s0EmployeesApi, s0LabelTypesApi, s0LocationsApi } from '../api/s0WarehouseApi';
|
|
|
+import { s0MfgPersonSkillsApi, s0MfgProductionLinesApi, s0MfgWorkCentersApi } from '../api/s0ManufacturingApi';
|
|
|
+
|
|
|
+export interface S0LookupOption {
|
|
|
+ value: string;
|
|
|
+ label: string;
|
|
|
+}
|
|
|
+
|
|
|
+export type S0LookupKey = 'item' | 'line' | 'personSkill' | 'workCenter' | 'location' | 'employee' | 'customer' | 'barcodeType';
|
|
|
+
|
|
|
+/** 下拉一次性取数上限:S0 主数据为配置级数据量,单页取全量即可,避免分页拼装。 */
|
|
|
+const PAGE_SIZE = 500;
|
|
|
+
|
|
|
+function norm(value: unknown, name: unknown): S0LookupOption | null {
|
|
|
+ const v = value == null ? '' : String(value).trim();
|
|
|
+ if (!v) return null;
|
|
|
+ const n = name == null ? '' : String(name).trim();
|
|
|
+ return { value: v, label: n ? `${v} / ${n}` : v };
|
|
|
+}
|
|
|
+
|
|
|
+function dedupe(list: Array<S0LookupOption | null>): S0LookupOption[] {
|
|
|
+ const seen = new Set<string>();
|
|
|
+ const out: S0LookupOption[] = [];
|
|
|
+ for (const o of list) {
|
|
|
+ if (!o || seen.has(o.value)) continue;
|
|
|
+ seen.add(o.value);
|
|
|
+ out.push(o);
|
|
|
+ }
|
|
|
+ return out;
|
|
|
+}
|
|
|
+
|
|
|
+const loaders: Record<S0LookupKey, () => Promise<S0LookupOption[]>> = {
|
|
|
+ item: async () => {
|
|
|
+ const d = await s0MaterialsApi.list({ page: 1, pageSize: PAGE_SIZE });
|
|
|
+ return dedupe((d.list ?? []).map((x: any) => norm(x.itemNum, x.descr)));
|
|
|
+ },
|
|
|
+ line: async () => {
|
|
|
+ const d = await s0MfgProductionLinesApi.list({ page: 1, pageSize: PAGE_SIZE });
|
|
|
+ return dedupe((d.list ?? []).map((x: any) => norm(x.line, x.describe)));
|
|
|
+ },
|
|
|
+ personSkill: async () => {
|
|
|
+ const d = await s0MfgPersonSkillsApi.options();
|
|
|
+ return dedupe((d ?? []).map((x: any) => norm(x.code ?? x.value, x.name)));
|
|
|
+ },
|
|
|
+ workCenter: async () => {
|
|
|
+ const d = await s0MfgWorkCentersApi.list({ page: 1, pageSize: PAGE_SIZE });
|
|
|
+ return dedupe((d.list ?? []).map((x: any) => norm(x.workCtr ?? x.code, x.descr ?? x.name)));
|
|
|
+ },
|
|
|
+ location: async () => {
|
|
|
+ const d = await s0LocationsApi.list({ page: 1, pageSize: PAGE_SIZE });
|
|
|
+ return dedupe((d.list ?? []).map((x: any) => norm(x.location, x.descr)));
|
|
|
+ },
|
|
|
+ employee: async () => {
|
|
|
+ const d = await s0EmployeesApi.list({ page: 1, pageSize: PAGE_SIZE });
|
|
|
+ return dedupe((d.list ?? []).map((x: any) => norm(x.employee, x.name)));
|
|
|
+ },
|
|
|
+ customer: async () => {
|
|
|
+ const d = await s0CustomersApi.list({ page: 1, pageSize: PAGE_SIZE });
|
|
|
+ return dedupe((d.list ?? []).map((x: any) => norm(x.cust, x.sortName ?? x.custFullName)));
|
|
|
+ },
|
|
|
+ barcodeType: async () => {
|
|
|
+ const d = await s0LabelTypesApi.list({ page: 1, pageSize: PAGE_SIZE });
|
|
|
+ return dedupe((d.list ?? []).map((x: any) => norm(x.barType, x.class)));
|
|
|
+ },
|
|
|
+};
|
|
|
+
|
|
|
+const cache = new Map<S0LookupKey, Promise<S0LookupOption[]>>();
|
|
|
+
|
|
|
+/** 取 lookup 选项;同一 key 在页面生命周期内只请求一次。 */
|
|
|
+export function loadS0Lookup(key: S0LookupKey): Promise<S0LookupOption[]> {
|
|
|
+ let hit = cache.get(key);
|
|
|
+ if (!hit) {
|
|
|
+ hit = loaders[key]().catch(() => [] as S0LookupOption[]);
|
|
|
+ cache.set(key, hit);
|
|
|
+ }
|
|
|
+ return hit;
|
|
|
+}
|
|
|
+
|
|
|
+/** 新增/编辑成功后调用,避免下拉停留在旧数据上。 */
|
|
|
+export function invalidateS0Lookups(): void {
|
|
|
+ cache.clear();
|
|
|
+}
|