Преглед на файлове

feat(aidop): S3 供应/委外与 S4 供应商交货/发货单能力调整

涵盖供应商交货按计划回复与回复交期、srm_polist_ds 主键驱动、草稿 bqsl 与发货单号规则、表单联动与弹窗生成发货单、委外明细与交货计划前端等。
chore: bump Web 2.4.139 / server 1.0.106

Co-authored-by: Cursor <cursoragent@cursor.com>
Pengxy преди 2 дни
родител
ревизия
b32a204bf1
променени са 20 файла, в които са добавени 935 реда и са изтрити 364 реда
  1. 1 1
      Web/package.json
  2. 58 0
      Web/src/utils/aidopMenuDisplay.ts
  3. 34 0
      Web/src/views/aidop/s3/api/deliverySchedule.ts
  4. 48 4
      Web/src/views/aidop/s3/supply/deliveryScheduleList.vue
  5. 214 43
      Web/src/views/aidop/s3/supply/outsourceOrderDetailForm.vue
  6. 125 70
      Web/src/views/aidop/s3/supply/outsourceOrderDetailList.vue
  7. 45 31
      Web/src/views/aidop/s3/supply/outsourceOrderList.vue
  8. 47 38
      Web/src/views/aidop/s3/supply/processOutsourceOrderList.vue
  9. 4 1
      Web/src/views/aidop/s4/api/procurementExecution.ts
  10. 38 11
      Web/src/views/aidop/s4/delivery/supplierDeliveryManagementList.vue
  11. 135 17
      Web/src/views/aidop/s4/delivery/supplierShipmentForm.vue
  12. 3 3
      server/Admin.NET.Web.Entry/Admin.NET.Web.Entry.csproj
  13. 76 114
      server/Plugins/Admin.NET.Plugin.AiDOP/ProcurementExecution/SupplierDeliveryManagementService.cs
  14. 53 5
      server/Plugins/Admin.NET.Plugin.AiDOP/ProcurementExecution/SupplierShipmentService.cs
  15. 7 7
      server/Plugins/Admin.NET.Plugin.AiDOP/Supply/DeliveryScheduleService.cs
  16. 2 2
      server/Plugins/Admin.NET.Plugin.AiDOP/Supply/DemandScheduleService.cs
  17. 1 1
      server/Plugins/Admin.NET.Plugin.AiDOP/Supply/Entity/DeliveryExceptionMaster.cs
  18. 1 1
      server/Plugins/Admin.NET.Plugin.AiDOP/Supply/Entity/PolistDeliverySchedule.cs
  19. 39 11
      server/Plugins/Admin.NET.Plugin.AiDOP/Supply/OutsourceOrderService.cs
  20. 4 4
      server/Plugins/Admin.NET.Plugin.AiDOP/Supply/ProcessOutsourceOrderService.cs

+ 1 - 1
Web/package.json

@@ -1,7 +1,7 @@
 {
 {
 	"name": "admin.net",
 	"name": "admin.net",
 	"type": "module",
 	"type": "module",
-	"version": "2.4.138",
+	"version": "2.4.139",
 	"packageManager": "pnpm@10.32.1",
 	"packageManager": "pnpm@10.32.1",
 	"lastBuildTime": "2026.03.15",
 	"lastBuildTime": "2026.03.15",
 	"description": "Admin.NET 站在巨人肩膀上的 .NET 通用权限开发框架",
 	"description": "Admin.NET 站在巨人肩膀上的 .NET 通用权限开发框架",

+ 58 - 0
Web/src/utils/aidopMenuDisplay.ts

@@ -44,6 +44,26 @@ const PRODUCTION_AUX_ROUTES: Array<{ path: string; title: string; component: str
 	{ path: '/aidop/production/work-order-routings', title: '工单工序明细', component: '/aidop/production/workOrderRoutingDetailList', name: 'aidopProductionWorkOrderRoutings' },
 	{ path: '/aidop/production/work-order-routings', title: '工单工序明细', component: '/aidop/production/workOrderRoutingDetailList', name: 'aidopProductionWorkOrderRoutings' },
 ];
 ];
 
 
+/** S3 供应协同子页(系统内标签打开,侧栏隐藏) */
+const S3_SUPPLY_AUX_ROUTES: Array<{ path: string; title: string; component: string; name: string }> = [
+	{
+		path: '/aidop/s3/supply/outsource-order-details',
+		title: '委外采购明细',
+		component: '/aidop/s3/supply/outsourceOrderDetailList',
+		name: 'aidopS3OutsourceOrderDetails',
+	},
+];
+
+/** S4 采购执行:发货单表单(从供应商交货管理「生成发货单」跳转;库/角色未带隐藏菜单时避免标签页空白) */
+const S4_DELIVERY_AUX_ROUTES: Array<{ path: string; title: string; component: string; name: string }> = [
+	{
+		path: '/aidop/s4/delivery/supplier-shipment-form',
+		title: '发货单表单',
+		component: '/aidop/s4/delivery/supplierShipmentForm',
+		name: 'aidopS4SupplierShipmentForm',
+	},
+];
+
 const SMART_OPS_CHILDREN: Array<{ path: string; title: string; component: string; name: string }> = [
 const SMART_OPS_CHILDREN: Array<{ path: string; title: string; component: string; name: string }> = [
 	{ path: '/aidop/smart-ops/grid', title: '九宫格智慧运营看板', component: '/dashboard/home', name: 'aidopSmartOpsGrid' },
 	{ path: '/aidop/smart-ops/grid', title: '九宫格智慧运营看板', component: '/dashboard/home', name: 'aidopSmartOpsGrid' },
 	{ path: '/aidop/smart-ops/modeling', title: '运营指标建模', component: '/aidop/kanban/s0', name: 'aidopSmartOpsModeling' },
 	{ path: '/aidop/smart-ops/modeling', title: '运营指标建模', component: '/aidop/kanban/s0', name: 'aidopSmartOpsModeling' },
@@ -135,6 +155,8 @@ export function augmentAllowedPathsForAidopHomepage(menuTree: any[] | undefined,
 	if (!aidopRoot) return;
 	if (!aidopRoot) return;
 	for (const c of SMART_OPS_CHILDREN) allowed.add(c.path);
 	for (const c of SMART_OPS_CHILDREN) allowed.add(c.path);
 	for (const c of PRODUCTION_AUX_ROUTES) allowed.add(c.path);
 	for (const c of PRODUCTION_AUX_ROUTES) allowed.add(c.path);
+	for (const c of S3_SUPPLY_AUX_ROUTES) allowed.add(c.path);
+	for (const c of S4_DELIVERY_AUX_ROUTES) allowed.add(c.path);
 	allowed.add('/aidop/smart-ops');
 	allowed.add('/aidop/smart-ops');
 	allowed.add('/aidop/smart-diagnosis');
 	allowed.add('/aidop/smart-diagnosis');
 }
 }
@@ -196,6 +218,28 @@ function patchAidopCustomMenusIfMissing(routes: AMenu[] | undefined): void {
 		});
 		});
 		existing.add(child.path);
 		existing.add(child.path);
 	}
 	}
+
+	for (const child of S3_SUPPLY_AUX_ROUTES) {
+		if (existing.has(child.path)) continue;
+		upsertMenu(aidopRoot, {
+			path: child.path,
+			name: child.name,
+			component: child.component,
+			meta: { title: child.title, isHide: true, icon: 'ele-Document' },
+		});
+		existing.add(child.path);
+	}
+
+	for (const child of S4_DELIVERY_AUX_ROUTES) {
+		if (existing.has(child.path)) continue;
+		upsertMenu(aidopRoot, {
+			path: child.path,
+			name: child.name,
+			component: child.component,
+			meta: { title: child.title, isHide: true, icon: 'ele-Document' },
+		});
+		existing.add(child.path);
+	}
 }
 }
 
 
 /** 为 S8 异常监控目录补可见子路由(如「订单执行档案」),兼容库未刷新到最新菜单的过渡期。 */
 /** 为 S8 异常监控目录补可见子路由(如「订单执行档案」),兼容库未刷新到最新菜单的过渡期。 */
@@ -302,6 +346,19 @@ function patchS8ConfigRouteComponents(nodes: AMenu[] | undefined): void {
 	}
 	}
 }
 }
 
 
+/** 强制 S4 发货单表单 component,避免库中 path 正确但 component 为空或旧路径时动态路由解析失败(空白页) */
+function patchS4DeliveryAuxRouteComponents(nodes: AMenu[] | undefined): void {
+	if (!nodes?.length) return;
+	const byPath = Object.fromEntries(S4_DELIVERY_AUX_ROUTES.map((c) => [c.path, c.component])) as Record<string, string>;
+	for (const item of nodes) {
+		const raw = item.path as string | undefined;
+		const p = raw ? (raw.startsWith('/') ? raw : `/${raw}`) : '';
+		const comp = p ? byPath[p] : undefined;
+		if (comp) item.component = comp;
+		if (item.children?.length) patchS4DeliveryAuxRouteComponents(item.children as AMenu[]);
+	}
+}
+
 export function patchAidopMenuTitles(routes: any[] | undefined): void {
 export function patchAidopMenuTitles(routes: any[] | undefined): void {
 	if (!routes?.length) return;
 	if (!routes?.length) return;
 	patchAidopCustomMenusIfMissing(routes as AMenu[]);
 	patchAidopCustomMenusIfMissing(routes as AMenu[]);
@@ -310,6 +367,7 @@ export function patchAidopMenuTitles(routes: any[] | undefined): void {
 	patchS8ConfigHiddenRoutesIfMissing(routes as AMenu[]);
 	patchS8ConfigHiddenRoutesIfMissing(routes as AMenu[]);
 	patchSmartOpsRouteComponents(routes as AMenu[]);
 	patchSmartOpsRouteComponents(routes as AMenu[]);
 	patchS8ConfigRouteComponents(routes as AMenu[]);
 	patchS8ConfigRouteComponents(routes as AMenu[]);
+	patchS4DeliveryAuxRouteComponents(routes as AMenu[]);
 	for (const item of routes) {
 	for (const item of routes) {
 		const name = item.name as string | undefined;
 		const name = item.name as string | undefined;
 		if (name && AIDOP_DIRECTORY_TITLES[name]) {
 		if (name && AIDOP_DIRECTORY_TITLES[name]) {

+ 34 - 0
Web/src/views/aidop/s3/api/deliverySchedule.ts

@@ -1,4 +1,5 @@
 import service from '/@/utils/request';
 import service from '/@/utils/request';
+import { EXTERNAL_RESOURCE_BASE } from '/@/views/aidop/api/workOrderScheduling';
 
 
 export interface Paged<T> {
 export interface Paged<T> {
 	total: number;
 	total: number;
@@ -64,10 +65,43 @@ export function cancelDeliverySchedule(id: number) {
 	return service.post(`/api/Supply/delivery-schedule/cancel/${id}`).then((r) => r.data);
 	return service.post(`/api/Supply/delivery-schedule/cancel/${id}`).then((r) => r.data);
 }
 }
 
 
+/** 本系统内生成(不经外部 replenishment);列表「生成交货单」已改为前端直连外部链式接口。 */
 export function generateDeliverySchedule() {
 export function generateDeliverySchedule() {
 	return service.post<DeliveryScheduleGenerateResult>('/api/Supply/delivery-schedule/generate').then((r) => r.data);
 	return service.post<DeliveryScheduleGenerateResult>('/api/Supply/delivery-schedule/generate').then((r) => r.data);
 }
 }
 
 
+async function postExternalJson(url: string, body: Record<string, unknown>): Promise<string> {
+	const r = await fetch(url, {
+		method: 'POST',
+		headers: { 'Content-Type': 'application/json;charset=UTF-8' },
+		body: JSON.stringify(body),
+	});
+	const text = String((await r.text()) ?? '').trim();
+	if (!r.ok) {
+		throw new Error(`${r.status}(${r.statusText})${text ? `:${text}` : ''}`);
+	}
+	return text;
+}
+
+/**
+ * 外部 replenishment:生成交货单(与旧版 $.ajax 一致:query + JSON body 均带 domain、user)。
+ * 使用 fetch,避免全局 axios 向外部地址附带 Authorization。
+ */
+export function createDeliveryScheduleExternal(domain: string, user: string) {
+	const qs = new URLSearchParams({ domain, user }).toString();
+	const url = `${EXTERNAL_RESOURCE_BASE}/api/business/replenishment/CreateDeliverySchedule?${qs}`;
+	return postExternalJson(url, { domain, user });
+}
+
+/**
+ * 外部 resource-examine:自动合并 PR / 转 DO 等(第二步;响应体为 `ok` 表示成功,忽略大小写)。
+ */
+export function autoTransferDoOrPoExternal(domain: string) {
+	const qs = new URLSearchParams({ domain }).toString();
+	const url = `${EXTERNAL_RESOURCE_BASE}/api/business/resource-examine/AutoTransferDoOrPo?${qs}`;
+	return postExternalJson(url, { domain });
+}
+
 export function batchGenerateDeliverySchedule(poNumber: string) {
 export function batchGenerateDeliverySchedule(poNumber: string) {
 	return service.post<DeliveryScheduleGenerateResult>('/api/Supply/delivery-schedule/batch-generate', { poNumber }).then((r) => r.data);
 	return service.post<DeliveryScheduleGenerateResult>('/api/Supply/delivery-schedule/batch-generate', { poNumber }).then((r) => r.data);
 }
 }

+ 48 - 4
Web/src/views/aidop/s3/supply/deliveryScheduleList.vue

@@ -94,17 +94,31 @@ import { ElMessage, ElMessageBox } from 'element-plus';
 import AidopDemoShell from '/@/views/aidop/components/AidopDemoShell.vue';
 import AidopDemoShell from '/@/views/aidop/components/AidopDemoShell.vue';
 import DeliveryScheduleBatchForm from './deliveryScheduleBatchForm.vue';
 import DeliveryScheduleBatchForm from './deliveryScheduleBatchForm.vue';
 import {
 import {
+	autoTransferDoOrPoExternal,
 	cancelDeliverySchedule,
 	cancelDeliverySchedule,
+	createDeliveryScheduleExternal,
 	fetchDeliveryScheduleList,
 	fetchDeliveryScheduleList,
-	generateDeliverySchedule,
 	publishDeliverySchedule,
 	publishDeliverySchedule,
 	type DeliveryScheduleRow,
 	type DeliveryScheduleRow,
 	unpublishDeliverySchedule,
 	unpublishDeliverySchedule,
 } from '../api/deliverySchedule';
 } from '../api/deliverySchedule';
+import { useUserInfo } from '/@/stores/userInfo';
 
 
 const route = useRoute();
 const route = useRoute();
+const userInfoStore = useUserInfo();
 const pageTitle = computed(() => (route.meta?.title as string) || '物料交货计划');
 const pageTitle = computed(() => (route.meta?.title as string) || '物料交货计划');
 
 
+/** 外部接口 `domain`:与工单池「生成物料需求」等一致,传当前用户租户 ID(旧系统 UserFactoryNum / 租户 id)。 */
+async function resolveExternalDomain(): Promise<string | null> {
+	if (!userInfoStore.userInfos?.tenantId && !userInfoStore.userInfos?.currentTenantId) {
+		await userInfoStore.setUserInfos();
+	}
+	const u = userInfoStore.userInfos;
+	const tid = u?.currentTenantId ?? u?.tenantId;
+	if (tid === undefined || tid === null || String(tid).trim() === '') return null;
+	return String(tid);
+}
+
 const query = reactive({
 const query = reactive({
 	poNumber: '',
 	poNumber: '',
 	dsNum: '',
 	dsNum: '',
@@ -227,11 +241,41 @@ function resetQuery() {
 }
 }
 
 
 async function onGenerate() {
 async function onGenerate() {
+	const domain = await resolveExternalDomain();
+	if (!domain) {
+		ElMessage.warning('当前用户无租户信息,无法生成交货单');
+		return;
+	}
+	const user = String(userInfoStore.userInfos?.account ?? '').trim();
+	if (!user) {
+		ElMessage.warning('当前用户缺少登录账号,无法生成交货单');
+		return;
+	}
+
 	generating.value = true;
 	generating.value = true;
 	try {
 	try {
-		const ret = await generateDeliverySchedule();
-		const msg = ret?.message || '生成交货单成功';
-		ret?.createdCount > 0 ? ElMessage.success(msg) : ElMessage.warning(msg);
+		const result = await createDeliveryScheduleExternal(domain, user);
+		const arr = result.split('|');
+		if (arr[0]?.trim() !== 'OK') {
+			ElMessage.error(arr[1] ? `生成交货单失败:${arr[1]}` : '生成交货单失败');
+			await loadList();
+			return;
+		}
+		try {
+			const result1 = await autoTransferDoOrPoExternal(domain);
+			if (String(result1).trim().toLowerCase() === 'ok') {
+				ElMessage.success('生成交货单成功,转DO/生成推送SAP事务成功');
+			} else {
+				ElMessage.warning(`生成交货单成功,转DO/生成推送SAP事务失败:${result1}`);
+			}
+		} catch (e: any) {
+			ElMessage.warning(
+				`生成交货单成功,转DO/生成推送SAP事务失败:${e?.message ?? ''}`.trim() || '转DO/生成推送SAP事务失败'
+			);
+		}
+		await loadList();
+	} catch (e: any) {
+		ElMessage.error(e?.message ? `生成交货单失败:${e.message}` : '生成交货单失败');
 		await loadList();
 		await loadList();
 	} finally {
 	} finally {
 		generating.value = false;
 		generating.value = false;

+ 214 - 43
Web/src/views/aidop/s3/supply/outsourceOrderDetailForm.vue

@@ -1,60 +1,138 @@
 <template>
 <template>
 	<div v-loading="loading">
 	<div v-loading="loading">
-		<el-form ref="formRef" :model="form" :rules="rules" label-width="110px">
+		<el-form ref="formRef" :model="form" :rules="rules" label-width="120px">
 			<el-row :gutter="12">
 			<el-row :gutter="12">
-				<el-col :span="12"><el-form-item label="采购单号"><el-input v-model="form.purOrd" disabled /></el-form-item></el-col>
-				<el-col :span="12"><el-form-item label="行号"><el-input v-model="form.line" disabled /></el-form-item></el-col>
+				<el-col :span="12">
+					<el-form-item label="采购订单号"><el-input v-model="form.purOrd" disabled /></el-form-item>
+				</el-col>
+				<el-col :span="12">
+					<el-form-item label="项次"><el-input v-model="form.line" disabled /></el-form-item>
+				</el-col>
 				<el-col :span="12">
 				<el-col :span="12">
 					<el-form-item label="物料编号" prop="itemNum">
 					<el-form-item label="物料编号" prop="itemNum">
 						<div class="pick-wrap">
 						<div class="pick-wrap">
-							<el-input v-model="form.itemNum" :disabled="isView" />
-							<el-button :disabled="isView" @click="openPick('main')">选择</el-button>
+							<el-input v-model="form.itemNum" disabled />
+							<el-button v-if="!isView" @click="openPick('main')">选择</el-button>
 						</div>
 						</div>
 					</el-form-item>
 					</el-form-item>
 				</el-col>
 				</el-col>
-				<el-col :span="12"><el-form-item label="批号"><el-input v-model="form.lotSerial" disabled /></el-form-item></el-col>
-				<el-col :span="12"><el-form-item label="版本"><el-input v-model="form.rev" disabled /></el-form-item></el-col>
-				<el-col :span="12"><el-form-item label="图纸"><el-input v-model="form.drawing" disabled /></el-form-item></el-col>
-				<el-col :span="12"><el-form-item label="单位"><el-input v-model="form.um" disabled /></el-form-item></el-col>
-				<el-col :span="12"><el-form-item label="库位"><el-input v-model="form.location" disabled /></el-form-item></el-col>
-				<el-col :span="12"><el-form-item label="订单数量" prop="qtyOrded"><el-input-number v-model="form.qtyOrded" :disabled="isView" :min="0" :precision="5" :controls="false" style="width: 100%" /></el-form-item></el-col>
-				<el-col :span="12"><el-form-item label="收货数量"><el-input v-model="form.qtyReceived" disabled /></el-form-item></el-col>
-				<el-col :span="12"><el-form-item label="下达数量"><el-input v-model="form.qtyReleased" disabled /></el-form-item></el-col>
-				<el-col :span="12"><el-form-item label="交货日期"><el-date-picker v-model="form.dueDate" type="date" value-format="YYYY-MM-DD" :disabled="isView" style="width: 100%" /></el-form-item></el-col>
+				<el-col :span="12">
+					<el-form-item label="生产批次"><el-input v-model="form.lotSerial" disabled /></el-form-item>
+				</el-col>
+				<el-col :span="12">
+					<el-form-item label="版本"><el-input v-model="form.rev" disabled /></el-form-item>
+				</el-col>
+				<el-col :span="12">
+					<el-form-item label="图纸"><el-input v-model="form.drawing" disabled /></el-form-item>
+				</el-col>
+				<el-col :span="12">
+					<el-form-item label="单位"><el-input v-model="form.um" disabled /></el-form-item>
+				</el-col>
+				<el-col :span="12">
+					<el-form-item label="库位"><el-input v-model="form.location" disabled /></el-form-item>
+				</el-col>
+				<el-col :span="12">
+					<el-form-item label="订单数量" prop="qtyOrded">
+						<el-input-number
+							v-model="form.qtyOrded"
+							:disabled="isView"
+							:min="0"
+							:precision="5"
+							:controls="false"
+							style="width: 100%"
+							@change="syncAllBatchQty"
+						/>
+					</el-form-item>
+				</el-col>
+				<el-col :span="12">
+					<el-form-item label="收货数量"><el-input v-model="form.qtyReceived" disabled /></el-form-item>
+				</el-col>
+				<el-col :span="12">
+					<el-form-item label="暂收数量"><el-input v-model="form.qtyReleased" disabled /></el-form-item>
+				</el-col>
+				<el-col :span="12">
+					<el-form-item label="交货日期">
+						<el-date-picker v-model="form.dueDate" type="date" value-format="YYYY-MM-DD" :disabled="isView" style="width: 100%" />
+					</el-form-item>
+				</el-col>
 			</el-row>
 			</el-row>
 		</el-form>
 		</el-form>
 
 
 		<div class="sub-header">
 		<div class="sub-header">
-			<span>批次明细</span>
+			<span>组件明细</span>
 			<div v-if="!isView">
 			<div v-if="!isView">
 				<el-button type="primary" link @click="addBatch">添加</el-button>
 				<el-button type="primary" link @click="addBatch">添加</el-button>
 			</div>
 			</div>
 		</div>
 		</div>
 		<el-table :data="form.batches" border>
 		<el-table :data="form.batches" border>
-			<el-table-column prop="batch" label="批次" width="80">
-				<template #default="{ row }"><el-input-number v-model="row.batch" :min="1" :controls="false" :disabled="isView" /></template>
+			<el-table-column prop="batch" label="组件行号" width="150">
+				<template #default="{ row, $index }">
+					<div class="pick-wrap">
+						<el-input v-model="row.batch" disabled />
+						<el-button v-if="!isView && editingBatchIndex === $index" @click="openPick('batch', $index)">选择</el-button>
+					</div>
+				</template>
+			</el-table-column>
+			<el-table-column prop="itemNum" label="物料编号" width="140">
+				<template #default="{ row }"><el-input v-model="row.itemNum" disabled /></template>
 			</el-table-column>
 			</el-table-column>
-			<el-table-column prop="itemNum" label="物料编号" width="140"><template #default="{ row }"><el-input v-model="row.itemNum" disabled /></template></el-table-column>
-			<el-table-column prop="suppItem" label="供应商物料" width="180">
+			<el-table-column prop="suppItem" label="组件物料编号" width="180">
 				<template #default="{ row, $index }">
 				<template #default="{ row, $index }">
 					<div class="pick-wrap">
 					<div class="pick-wrap">
-						<el-input v-model="row.suppItem" :disabled="isView" />
-						<el-button :disabled="isView" @click="openPick('batch', $index)">选</el-button>
+						<el-input v-model="row.suppItem" :disabled="isView || editingBatchIndex !== $index" />
+						<el-button v-if="!isView && editingBatchIndex === $index" @click="openPick('suppItem', $index)">选择</el-button>
 					</div>
 					</div>
 				</template>
 				</template>
 			</el-table-column>
 			</el-table-column>
-			<el-table-column prop="um" label="单位" width="90"><template #default="{ row }"><el-input v-model="row.um" :disabled="isView" /></template></el-table-column>
-			<el-table-column prop="location" label="库位" width="120">
-				<template #default="{ row }"><el-select v-model="row.location" filterable :disabled="isView"><el-option v-for="o in locationOptions" :key="o.value" :label="o.label" :value="o.value" /></el-select></template>
+			<el-table-column prop="um" label="单位" width="90">
+				<template #default="{ row, $index }"><el-input v-model="row.um" :disabled="isView || editingBatchIndex !== $index" /></template>
+			</el-table-column>
+			<el-table-column prop="location" label="收货库位" width="160">
+				<template #default="{ row, $index }">
+					<el-select v-model="row.location" filterable :disabled="isView || editingBatchIndex !== $index" style="width: 100%">
+						<el-option v-for="o in locationOptions" :key="o.value" :label="o.label" :value="o.value" />
+					</el-select>
+				</template>
+			</el-table-column>
+			<el-table-column prop="qtyOrded" label="订单数量" width="120">
+				<template #default="{ row }">
+					<el-input-number v-model="row.qtyOrded" :precision="5" :min="0" :controls="false" disabled style="width: 100%" />
+				</template>
 			</el-table-column>
 			</el-table-column>
-			<el-table-column prop="qtyOrded" label="订单数量" width="120"><template #default="{ row }"><el-input-number v-model="row.qtyOrded" :precision="5" :min="0" :controls="false" :disabled="isView" /></template></el-table-column>
-			<el-table-column prop="qtyBO" label="欠交量" width="120"><template #default="{ row }"><el-input-number v-model="row.qtyBO" :precision="5" :min="0" :controls="false" :disabled="isView" /></template></el-table-column>
-			<el-table-column prop="qtyReleased" label="下达量" width="120"><template #default="{ row }"><el-input-number v-model="row.qtyReleased" :precision="5" :min="0" :controls="false" :disabled="isView" /></template></el-table-column>
-			<el-table-column prop="qtyReceived" label="收货量" width="120"><template #default="{ row }"><el-input-number v-model="row.qtyReceived" :precision="5" :min="0" :controls="false" :disabled="isView" /></template></el-table-column>
-			<el-table-column prop="qtyReturned" label="退货量" width="120"><template #default="{ row }"><el-input-number v-model="row.qtyReturned" :precision="5" :min="0" :controls="false" :disabled="isView" /></template></el-table-column>
-			<el-table-column prop="lotSerial" label="批号" width="120"><template #default="{ row }"><el-input v-model="row.lotSerial" :disabled="isView" /></template></el-table-column>
-			<el-table-column v-if="!isView" label="操作" fixed="right" width="90">
+			<el-table-column prop="qtyBO" label="BOM单位用量" width="130">
+				<template #default="{ row, $index }">
+					<el-input-number
+						v-model="row.qtyBO"
+						:precision="5"
+						:min="0"
+						:controls="false"
+						:disabled="isView || editingBatchIndex !== $index"
+						style="width: 100%"
+						@change="() => syncBatchRowQty($index)"
+					/>
+				</template>
+			</el-table-column>
+			<el-table-column prop="qtyReleased" label="组件需求数量" width="130">
+				<template #default="{ row }">
+					<el-input-number v-model="row.qtyReleased" :precision="5" :min="0" :controls="false" disabled style="width: 100%" />
+				</template>
+			</el-table-column>
+			<el-table-column prop="qtyReceived" label="收货数量" width="120">
+				<template #default="{ row, $index }">
+					<el-input-number v-model="row.qtyReceived" :precision="5" :min="0" :controls="false" :disabled="isView || editingBatchIndex !== $index" style="width: 100%" />
+				</template>
+			</el-table-column>
+			<el-table-column prop="qtyReturned" label="退回数量" width="120">
+				<template #default="{ row, $index }">
+					<el-input-number v-model="row.qtyReturned" :precision="5" :min="0" :controls="false" :disabled="isView || editingBatchIndex !== $index" style="width: 100%" />
+				</template>
+			</el-table-column>
+			<el-table-column prop="lotSerial" label="生产批号" width="120">
+				<template #default="{ row, $index }"><el-input v-model="row.lotSerial" :disabled="isView || editingBatchIndex !== $index" /></template>
+			</el-table-column>
+			<el-table-column v-if="!isView" label="操作" fixed="right" width="120">
 				<template #default="{ $index }">
 				<template #default="{ $index }">
+					<el-button type="primary" link @click="editBatch($index)">编辑</el-button>
 					<el-button type="danger" link @click="removeBatch($index)">删除</el-button>
 					<el-button type="danger" link @click="removeBatch($index)">删除</el-button>
 				</template>
 				</template>
 			</el-table-column>
 			</el-table-column>
@@ -66,15 +144,23 @@
 		</div>
 		</div>
 	</div>
 	</div>
 
 
-	<el-dialog v-model="pickVisible" title="选择物料" width="75%">
+	<el-dialog v-model="pickVisible" title="选择物料" width="75%" append-to-body destroy-on-close>
 		<select-item-master @picked="onPicked" />
 		<select-item-master @picked="onPicked" />
 	</el-dialog>
 	</el-dialog>
 </template>
 </template>
 
 
 <script setup lang="ts">
 <script setup lang="ts">
-import { onMounted, reactive, ref } from 'vue';
+import { computed, onMounted, reactive, ref, watch } from 'vue';
 import { type FormInstance, type FormRules } from 'element-plus';
 import { type FormInstance, type FormRules } from 'element-plus';
-import { fetchOutsourceLocations, fetchOutsourceOrderDetail, fetchOutsourceOrderDetailInit, saveOutsourceOrderDetail, type ItemRow, type OptionRow, type OutsourceOrderBatchRow } from '../api/outsourceOrder';
+import {
+	fetchOutsourceLocations,
+	fetchOutsourceOrderDetail,
+	fetchOutsourceOrderDetailInit,
+	saveOutsourceOrderDetail,
+	type ItemRow,
+	type OptionRow,
+	type OutsourceOrderBatchRow,
+} from '../api/outsourceOrder';
 import SelectItemMaster from './selectItemMaster.vue';
 import SelectItemMaster from './selectItemMaster.vue';
 
 
 const props = defineProps<{ mode: 'add' | 'edit' | 'view'; id: number | null; purOrd: string }>();
 const props = defineProps<{ mode: 'add' | 'edit' | 'view'; id: number | null; purOrd: string }>();
@@ -84,7 +170,8 @@ const formRef = ref<FormInstance>();
 const loading = ref(false);
 const loading = ref(false);
 const saving = ref(false);
 const saving = ref(false);
 const locationOptions = ref<OptionRow[]>([]);
 const locationOptions = ref<OptionRow[]>([]);
-const isView = ref(props.mode === 'view');
+const isView = computed(() => props.mode === 'view');
+const editingBatchIndex = ref(-1);
 
 
 const form = reactive({
 const form = reactive({
 	id: null as number | null,
 	id: null as number | null,
@@ -104,38 +191,88 @@ const form = reactive({
 	potype: 'PW',
 	potype: 'PW',
 	batches: [] as OutsourceOrderBatchRow[],
 	batches: [] as OutsourceOrderBatchRow[],
 });
 });
+
 const rules: FormRules = {
 const rules: FormRules = {
 	itemNum: [{ required: true, message: '请选择物料', trigger: 'blur' }],
 	itemNum: [{ required: true, message: '请选择物料', trigger: 'blur' }],
 	qtyOrded: [{ required: true, message: '请输入订单数量', trigger: 'blur' }],
 	qtyOrded: [{ required: true, message: '请输入订单数量', trigger: 'blur' }],
 };
 };
 
 
 const pickVisible = ref(false);
 const pickVisible = ref(false);
-const pickMode = ref<'main' | 'batch'>('main');
+const pickMode = ref<'main' | 'batch' | 'suppItem'>('main');
 const pickRowIndex = ref(-1);
 const pickRowIndex = ref(-1);
+const qtySyncReady = ref(false);
+
+function roundQty(v: number) {
+	return Math.round(v * 100000) / 100000;
+}
+
+function calcBatchReleased(qtyBO?: number | null, qtyOrded?: number | null) {
+	return roundQty(Number(qtyBO || 0) * Number(qtyOrded || 0));
+}
+
+function syncBatchRowQty(index: number) {
+	const row = form.batches[index];
+	if (!row) return;
+	row.qtyOrded = Number(form.qtyOrded || 0);
+	row.qtyReleased = calcBatchReleased(row.qtyBO, row.qtyOrded);
+}
+
+function syncAllBatchQty() {
+	const mainQty = Number(form.qtyOrded || 0);
+	for (let i = 0; i < form.batches.length; i++) {
+		const row = form.batches[i];
+		row.qtyOrded = mainQty;
+		row.qtyReleased = calcBatchReleased(row.qtyBO, mainQty);
+	}
+}
+
+function nextBatchNo() {
+	const nums = form.batches.map((x) => Number(x.batch || 0)).filter((n) => n > 0);
+	return nums.length ? Math.max(...nums) + 1 : 1;
+}
 
 
 function addBatch() {
 function addBatch() {
+	const index = form.batches.length;
 	form.batches.push({
 	form.batches.push({
-		batch: form.batches.length + 1,
+		batch: nextBatchNo(),
 		itemNum: form.itemNum,
 		itemNum: form.itemNum,
 		suppItem: '',
 		suppItem: '',
 		um: form.um,
 		um: form.um,
 		location: form.location,
 		location: form.location,
-		qtyOrded: 0,
+		qtyOrded: Number(form.qtyOrded || 0),
 		qtyBO: 0,
 		qtyBO: 0,
 		qtyReleased: 0,
 		qtyReleased: 0,
 		qtyReceived: 0,
 		qtyReceived: 0,
 		qtyReturned: 0,
 		qtyReturned: 0,
 		lotSerial: '',
 		lotSerial: '',
 	});
 	});
+	syncBatchRowQty(index);
+	editingBatchIndex.value = index;
+}
+
+function editBatch(index: number) {
+	editingBatchIndex.value = index;
 }
 }
+
 function removeBatch(index: number) {
 function removeBatch(index: number) {
 	form.batches.splice(index, 1);
 	form.batches.splice(index, 1);
+	if (editingBatchIndex.value === index) editingBatchIndex.value = -1;
+	else if (editingBatchIndex.value > index) editingBatchIndex.value -= 1;
 }
 }
-function openPick(mode: 'main' | 'batch', index = -1) {
+
+function openPick(mode: 'main' | 'batch' | 'suppItem', index = -1) {
 	pickMode.value = mode;
 	pickMode.value = mode;
 	pickRowIndex.value = index;
 	pickRowIndex.value = index;
 	pickVisible.value = true;
 	pickVisible.value = true;
 }
 }
+
+function applyItemToBatchRow(row: OutsourceOrderBatchRow, item: ItemRow, fillSuppItem = true) {
+	row.itemNum = item.itemNum || '';
+	if (fillSuppItem) row.suppItem = item.itemNum || '';
+	row.um = item.um || '';
+	row.location = item.location || '';
+}
+
 function onPicked(row: ItemRow) {
 function onPicked(row: ItemRow) {
 	if (pickMode.value === 'main') {
 	if (pickMode.value === 'main') {
 		form.itemNum = row.itemNum || '';
 		form.itemNum = row.itemNum || '';
@@ -145,10 +282,14 @@ function onPicked(row: ItemRow) {
 		form.drawing = row.drawing || '';
 		form.drawing = row.drawing || '';
 	} else if (pickRowIndex.value >= 0) {
 	} else if (pickRowIndex.value >= 0) {
 		const tar = form.batches[pickRowIndex.value];
 		const tar = form.batches[pickRowIndex.value];
-		tar.itemNum = row.itemNum || '';
-		tar.suppItem = row.itemNum || '';
-		tar.um = row.um || '';
-		tar.location = row.location || '';
+		if (pickMode.value === 'batch') {
+			applyItemToBatchRow(tar, row, true);
+		} else {
+			tar.suppItem = row.itemNum || '';
+			if (!tar.itemNum) tar.itemNum = row.itemNum || '';
+			if (!tar.um) tar.um = row.um || '';
+			if (!tar.location) tar.location = row.location || '';
+		}
 	}
 	}
 	pickVisible.value = false;
 	pickVisible.value = false;
 }
 }
@@ -160,12 +301,25 @@ async function loadOptions() {
 
 
 async function loadData() {
 async function loadData() {
 	loading.value = true;
 	loading.value = true;
+	editingBatchIndex.value = -1;
+	qtySyncReady.value = false;
 	try {
 	try {
 		if (props.mode === 'add') {
 		if (props.mode === 'add') {
 			const init = await fetchOutsourceOrderDetailInit(props.purOrd);
 			const init = await fetchOutsourceOrderDetailInit(props.purOrd);
+			form.id = null;
 			form.purOrd = init.purOrd || props.purOrd;
 			form.purOrd = init.purOrd || props.purOrd;
 			form.purOrdRecID = init.purOrdRecID;
 			form.purOrdRecID = init.purOrdRecID;
 			form.line = init.line;
 			form.line = init.line;
+			form.itemNum = '';
+			form.lotSerial = '';
+			form.rev = '';
+			form.drawing = '';
+			form.um = '';
+			form.location = '';
+			form.qtyOrded = 0;
+			form.qtyReceived = 0;
+			form.qtyReleased = 0;
+			form.dueDate = '';
 			form.batches = [];
 			form.batches = [];
 			return;
 			return;
 		}
 		}
@@ -189,6 +343,7 @@ async function loadData() {
 		form.batches = (data.batches || []).map((x) => ({ ...x }));
 		form.batches = (data.batches || []).map((x) => ({ ...x }));
 	} finally {
 	} finally {
 		loading.value = false;
 		loading.value = false;
+		qtySyncReady.value = true;
 	}
 	}
 }
 }
 
 
@@ -213,6 +368,21 @@ async function onSave() {
 	}
 	}
 }
 }
 
 
+watch(
+	() => form.qtyOrded,
+	() => {
+		if (!qtySyncReady.value || isView.value) return;
+		syncAllBatchQty();
+	},
+);
+
+watch(
+	() => [props.mode, props.id, props.purOrd] as const,
+	() => {
+		loadData();
+	},
+);
+
 onMounted(async () => {
 onMounted(async () => {
 	await loadOptions();
 	await loadOptions();
 	await loadData();
 	await loadData();
@@ -230,6 +400,7 @@ onMounted(async () => {
 .pick-wrap {
 .pick-wrap {
 	display: flex;
 	display: flex;
 	gap: 6px;
 	gap: 6px;
+	align-items: center;
 }
 }
 .footer {
 .footer {
 	margin-top: 12px;
 	margin-top: 12px;

+ 125 - 70
Web/src/views/aidop/s3/supply/outsourceOrderDetailList.vue

@@ -1,80 +1,94 @@
 <template>
 <template>
 	<div class="aidop-page">
 	<div class="aidop-page">
-		<aidop-demo-shell title="委外采购明细">
-			<template #query>
-				<el-form :inline="true" :model="query" @submit.prevent>
-					<el-form-item label="采购单号"><el-input v-model="query.purOrd" clearable style="width: 160px" /></el-form-item>
-					<el-form-item label="物料编号"><el-input v-model="query.itemNum" clearable style="width: 160px" /></el-form-item>
-					<el-form-item label="库位">
-						<el-select v-model="query.location" clearable filterable style="width: 160px">
-							<el-option v-for="o in locationOptions" :key="o.value" :label="o.label" :value="o.value" />
-						</el-select>
-					</el-form-item>
-					<el-form-item label="工单编号"><el-input v-model="query.workOrd" clearable style="width: 160px" /></el-form-item>
-					<el-form-item label="生产批号"><el-input v-model="query.lotSerial" clearable style="width: 160px" /></el-form-item>
-					<el-form-item>
-						<el-button type="primary" @click="doSearch">查询</el-button>
-						<el-button @click="doReset">重置</el-button>
-					</el-form-item>
-				</el-form>
-			</template>
-			<template #actions>
-				<el-button type="primary" @click="openForm('add', null)">添加</el-button>
-			</template>
-			<template #default>
-				<el-table :data="rows" v-loading="loading" border stripe @sort-change="onSortChange">
-					<el-table-column prop="purOrd" label="采购单号" width="140" sortable="custom" />
-					<el-table-column prop="line" label="行号" width="80" sortable="custom" />
-					<el-table-column prop="itemNum" label="物料编号" min-width="140" fixed="left" sortable="custom" />
-					<el-table-column prop="descr" label="物料名称" min-width="160" sortable="custom" />
-					<el-table-column prop="um" label="单位" width="90" sortable="custom" />
-					<el-table-column prop="rev" label="版本" width="90" sortable="custom" />
-					<el-table-column prop="drawing" label="图纸号" width="120" sortable="custom" />
-					<el-table-column prop="qtyOrded" label="订单数量" width="120" sortable="custom" />
-					<el-table-column prop="qtyReceived" label="收货数量" width="120" sortable="custom" />
-					<el-table-column prop="qtyReleased" label="下达数量" width="120" sortable="custom" />
-					<el-table-column prop="dueDate" label="交期" width="120" sortable="custom">
-						<template #default="{ row }">{{ fmtDate(row.dueDate) }}</template>
-					</el-table-column>
-					<el-table-column prop="location" label="库位" width="100" sortable="custom" />
-					<el-table-column prop="workOrd" label="工单编号" width="120" sortable="custom" />
-					<el-table-column prop="lotSerial" label="生产批号" width="120" sortable="custom" />
-					<el-table-column prop="status" label="状态" width="90" sortable="custom" />
-					<el-table-column label="操作" width="180" fixed="right">
-						<template #default="{ row }">
-							<el-button type="primary" link @click="openForm('edit', row.id)">编辑</el-button>
-							<el-button type="primary" link @click="openForm('view', row.id)">查看</el-button>
-							<el-button type="danger" link @click="onDelete(row)">删除</el-button>
-						</template>
-					</el-table-column>
-				</el-table>
-				<div class="pager">
-					<el-pagination
-						v-model:current-page="query.page"
-						v-model:page-size="query.pageSize"
-						:total="total"
-						:page-sizes="[10, 20, 50]"
-						layout="total, sizes, prev, pager, next"
-						@current-change="loadList"
-						@size-change="loadList"
-					/>
-				</div>
-			</template>
+		<aidop-demo-shell :title="pageTitle" :subtitle="pageSubtitle">
+			<el-form :inline="true" :model="query" class="mb12" @submit.prevent>
+				<el-form-item label="采购单号">
+					<el-input v-model="query.purOrd" :disabled="purOrdLocked" clearable style="width: 160px" />
+				</el-form-item>
+				<el-form-item label="物料编号"><el-input v-model="query.itemNum" clearable style="width: 160px" /></el-form-item>
+				<el-form-item label="库位">
+					<el-select v-model="query.location" clearable filterable style="width: 180px">
+						<el-option v-for="o in locationOptions" :key="o.value" :label="o.label" :value="o.value" />
+					</el-select>
+				</el-form-item>
+				<el-form-item label="工单编号"><el-input v-model="query.workOrd" clearable style="width: 160px" /></el-form-item>
+				<el-form-item label="生产批号"><el-input v-model="query.lotSerial" clearable style="width: 160px" /></el-form-item>
+				<el-form-item>
+					<el-button type="primary" @click="doSearch">查询</el-button>
+					<el-button @click="doReset">重置</el-button>
+				</el-form-item>
+			</el-form>
+
+			<div class="toolbar">
+				<el-button type="primary" plain :disabled="!purOrd" @click="openForm('add', null)">添加</el-button>
+			</div>
+
+			<el-table :data="rows" v-loading="loading" border stripe style="width: 100%" @sort-change="onSortChange">
+				<el-table-column prop="purOrd" label="采购单号" width="140" sortable="custom" />
+				<el-table-column prop="line" label="项次" width="80" sortable="custom" />
+				<el-table-column prop="itemNum" label="物料编号" min-width="140" fixed="left" sortable="custom" />
+				<el-table-column prop="descr" label="物料名称" min-width="160" show-overflow-tooltip sortable="custom" />
+				<el-table-column prop="um" label="单位" width="90" sortable="custom" />
+				<el-table-column prop="rev" label="版本" width="90" sortable="custom" />
+				<el-table-column prop="drawing" label="图纸号" width="120" sortable="custom" />
+				<el-table-column prop="qtyOrded" label="订单数量" width="110" align="right" sortable="custom" />
+				<el-table-column prop="qtyReceived" label="收货数量" width="110" align="right" sortable="custom" />
+				<el-table-column prop="qtyReleased" label="暂收数量" width="110" align="right" sortable="custom" />
+				<el-table-column prop="dueDate" label="交付日期" width="120" sortable="custom">
+					<template #default="{ row }">{{ fmtDate(row.dueDate) }}</template>
+				</el-table-column>
+				<el-table-column prop="location" label="库位" width="100" sortable="custom" />
+				<el-table-column prop="workOrd" label="工单编号" width="120" sortable="custom" />
+				<el-table-column prop="lotSerial" label="生产批号" width="120" sortable="custom" />
+				<el-table-column prop="status" label="状态" width="100" sortable="custom">
+					<template #default="{ row }">{{ statusText(row.status) }}</template>
+				</el-table-column>
+				<el-table-column label="操作" width="200" fixed="right">
+					<template #default="{ row }">
+						<el-button type="primary" link @click="openForm('edit', row.id)">编辑</el-button>
+						<el-button v-if="row.status !== 'C'" type="danger" link @click="onDelete(row)">删除</el-button>
+						<el-button type="primary" link @click="openForm('view', row.id)">查看</el-button>
+					</template>
+				</el-table-column>
+			</el-table>
+			<div class="pager">
+				<el-pagination
+					v-model:current-page="query.page"
+					v-model:page-size="query.pageSize"
+					:total="total"
+					:page-sizes="[10, 20, 50]"
+					layout="total, sizes, prev, pager, next"
+					@current-change="loadList"
+					@size-change="loadList"
+				/>
+			</div>
 		</aidop-demo-shell>
 		</aidop-demo-shell>
 
 
-		<el-dialog v-model="formVisible" :title="formTitle" width="80%" destroy-on-close>
-			<outsource-order-detail-form :mode="formMode" :id="editingId" :pur-ord="purOrd" @saved="onSaved" @cancel="formVisible = false" />
+		<el-dialog v-model="formVisible" :title="formTitle" width="88%" destroy-on-close append-to-body>
+			<outsource-order-detail-form
+				v-if="formVisible"
+				:mode="formMode"
+				:id="editingId"
+				:pur-ord="purOrd"
+				@saved="onSaved"
+				@cancel="formVisible = false"
+			/>
 		</el-dialog>
 		</el-dialog>
 	</div>
 	</div>
 </template>
 </template>
 
 
 <script setup lang="ts">
 <script setup lang="ts">
-import { computed, onMounted, reactive, ref } from 'vue';
+import { computed, onMounted, reactive, ref, watch } from 'vue';
+import { useRoute } from 'vue-router';
 import { ElMessage, ElMessageBox } from 'element-plus';
 import { ElMessage, ElMessageBox } from 'element-plus';
 import AidopDemoShell from '../../components/AidopDemoShell.vue';
 import AidopDemoShell from '../../components/AidopDemoShell.vue';
 import OutsourceOrderDetailForm from './outsourceOrderDetailForm.vue';
 import OutsourceOrderDetailForm from './outsourceOrderDetailForm.vue';
 import { deleteOutsourceOrderDetail, fetchOutsourceLocations, fetchOutsourceOrderDetails, type OptionRow, type OutsourceOrderDetailRow } from '../api/outsourceOrder';
 import { deleteOutsourceOrderDetail, fetchOutsourceLocations, fetchOutsourceOrderDetails, type OptionRow, type OutsourceOrderDetailRow } from '../api/outsourceOrder';
 
 
+const route = useRoute();
+const pageTitle = computed(() => (route.meta?.title as string) || '委外采购明细');
+const pageSubtitle = computed(() => (purOrd.value ? `采购单号:${purOrd.value}` : ''));
+
 const query = reactive({
 const query = reactive({
 	page: 1,
 	page: 1,
 	pageSize: 10,
 	pageSize: 10,
@@ -88,6 +102,7 @@ const query = reactive({
 });
 });
 
 
 const purOrd = ref('');
 const purOrd = ref('');
+const purOrdLocked = computed(() => Boolean((route.query.purOrd as string) || ''));
 const rows = ref<OutsourceOrderDetailRow[]>([]);
 const rows = ref<OutsourceOrderDetailRow[]>([]);
 const total = ref(0);
 const total = ref(0);
 const loading = ref(false);
 const loading = ref(false);
@@ -101,11 +116,20 @@ const formTitle = computed(() => (formMode.value === 'add' ? '新增委外采购
 function fmtDate(v?: string | null) {
 function fmtDate(v?: string | null) {
 	return v ? String(v).slice(0, 10) : '';
 	return v ? String(v).slice(0, 10) : '';
 }
 }
-function initPurOrd() {
-	const params = new URLSearchParams(window.location.search);
-	purOrd.value = params.get('purOrd') || '';
-	query.purOrd = purOrd.value;
+
+function statusText(v?: string | null) {
+	if (v === 'A') return '审核中';
+	if (v === 'B') return '同意';
+	if (v === 'C') return '关闭';
+	return '新增';
+}
+
+function syncPurOrdFromRoute() {
+	const nextPurOrd = (route.query.purOrd as string) || '';
+	purOrd.value = nextPurOrd;
+	query.purOrd = nextPurOrd;
 }
 }
+
 async function loadList() {
 async function loadList() {
 	loading.value = true;
 	loading.value = true;
 	try {
 	try {
@@ -116,11 +140,14 @@ async function loadList() {
 		loading.value = false;
 		loading.value = false;
 	}
 	}
 }
 }
+
 function doSearch() {
 function doSearch() {
 	query.page = 1;
 	query.page = 1;
 	loadList();
 	loadList();
 }
 }
+
 function doReset() {
 function doReset() {
+	if (!purOrdLocked.value) query.purOrd = '';
 	query.itemNum = '';
 	query.itemNum = '';
 	query.location = '';
 	query.location = '';
 	query.workOrd = '';
 	query.workOrd = '';
@@ -130,16 +157,23 @@ function doReset() {
 	query.sortOrder = '';
 	query.sortOrder = '';
 	loadList();
 	loadList();
 }
 }
+
 function onSortChange({ prop, order }: { prop: string; order: string | null }) {
 function onSortChange({ prop, order }: { prop: string; order: string | null }) {
 	query.sortField = prop || '';
 	query.sortField = prop || '';
 	query.sortOrder = order === 'ascending' ? 'asc' : order === 'descending' ? 'desc' : '';
 	query.sortOrder = order === 'ascending' ? 'asc' : order === 'descending' ? 'desc' : '';
 	loadList();
 	loadList();
 }
 }
+
 function openForm(mode: 'add' | 'edit' | 'view', id: number | null) {
 function openForm(mode: 'add' | 'edit' | 'view', id: number | null) {
+	if (mode === 'add' && !purOrd.value) {
+		ElMessage.warning('缺少采购单号,无法新增明细');
+		return;
+	}
 	formMode.value = mode;
 	formMode.value = mode;
 	editingId.value = id;
 	editingId.value = id;
 	formVisible.value = true;
 	formVisible.value = true;
 }
 }
+
 async function onDelete(row: OutsourceOrderDetailRow) {
 async function onDelete(row: OutsourceOrderDetailRow) {
 	if (row.status === 'C') {
 	if (row.status === 'C') {
 		ElMessage.warning('关闭状态不允许删除');
 		ElMessage.warning('关闭状态不允许删除');
@@ -150,26 +184,47 @@ async function onDelete(row: OutsourceOrderDetailRow) {
 	ElMessage.success('删除成功');
 	ElMessage.success('删除成功');
 	loadList();
 	loadList();
 }
 }
+
 function onSaved() {
 function onSaved() {
 	formVisible.value = false;
 	formVisible.value = false;
 	ElMessage.success('保存成功');
 	ElMessage.success('保存成功');
 	loadList();
 	loadList();
 }
 }
 
 
+watch(
+	() => route.query.purOrd,
+	() => {
+		syncPurOrdFromRoute();
+		query.page = 1;
+		loadList();
+	},
+);
+
 onMounted(async () => {
 onMounted(async () => {
-	initPurOrd();
+	syncPurOrdFromRoute();
 	const ret = await fetchOutsourceLocations();
 	const ret = await fetchOutsourceLocations();
 	locationOptions.value = ret.list || [];
 	locationOptions.value = ret.list || [];
 	await loadList();
 	await loadList();
 });
 });
 </script>
 </script>
 
 
-<style scoped>
+<style scoped lang="scss">
+@import '/@/views/aidop/styles/aidop-demo.scss';
+
 .aidop-page {
 .aidop-page {
 	padding: 8px;
 	padding: 8px;
 }
 }
+.mb12 {
+	margin-bottom: 12px;
+}
+.toolbar {
+	display: flex;
+	align-items: center;
+	gap: 8px;
+	margin-bottom: 12px;
+}
 .pager {
 .pager {
-	margin-top: 10px;
+	margin-top: 12px;
 	display: flex;
 	display: flex;
 	justify-content: flex-end;
 	justify-content: flex-end;
 }
 }

+ 45 - 31
Web/src/views/aidop/s3/supply/outsourceOrderList.vue

@@ -1,31 +1,30 @@
 <template>
 <template>
 	<div class="aidop-page">
 	<div class="aidop-page">
 		<aidop-demo-shell title="委外加工订单">
 		<aidop-demo-shell title="委外加工订单">
-			<template #query>
-				<el-form :inline="true" :model="query" @submit.prevent>
-					<el-form-item label="采购单号"><el-input v-model="query.purOrd" clearable style="width: 180px" /></el-form-item>
-					<el-form-item label="采购组">
-						<el-select v-model="query.buyer" clearable filterable style="width: 180px">
-							<el-option v-for="o in buyerOptions" :key="o.value" :label="o.label" :value="o.value" />
-						</el-select>
-					</el-form-item>
-					<el-form-item label="供应商">
-						<el-select v-model="query.supp" clearable filterable style="width: 200px">
-							<el-option v-for="o in supplierOptions" :key="o.value" :label="o.label" :value="o.value" />
-						</el-select>
-					</el-form-item>
-					<el-form-item>
-						<el-button type="primary" @click="doSearch">查询</el-button>
-						<el-button @click="doReset">重置</el-button>
-					</el-form-item>
-				</el-form>
-			</template>
-			<template #actions>
-				<el-button type="primary" @click="openForm(null)">添加</el-button>
-			</template>
-			<template #default>
-				<el-table :data="rows" v-loading="loading" border stripe @sort-change="onSortChange">
-					<el-table-column prop="purOrd" label="工单编号" min-width="160" fixed="left" sortable="custom" />
+			<el-form :inline="true" :model="query" class="mb12" @submit.prevent>
+				<el-form-item label="采购单号"><el-input v-model="query.purOrd" clearable style="width: 180px" /></el-form-item>
+				<el-form-item label="采购组">
+					<el-select v-model="query.buyer" clearable filterable style="width: 180px">
+						<el-option v-for="o in buyerOptions" :key="o.value" :label="o.label" :value="o.value" />
+					</el-select>
+				</el-form-item>
+				<el-form-item label="供应商">
+					<el-select v-model="query.supp" clearable filterable style="width: 200px">
+						<el-option v-for="o in supplierOptions" :key="o.value" :label="o.label" :value="o.value" />
+					</el-select>
+				</el-form-item>
+				<el-form-item>
+					<el-button type="primary" @click="doSearch">查询</el-button>
+					<el-button @click="doReset">重置</el-button>
+				</el-form-item>
+			</el-form>
+
+			<div class="toolbar">
+				<el-button type="primary" plain @click="openForm(null)">添加</el-button>
+			</div>
+
+			<el-table :data="rows" v-loading="loading" border stripe @sort-change="onSortChange">
+				<el-table-column prop="purOrd" label="工单编号" min-width="160" fixed="left" sortable="custom" />
 					<el-table-column prop="suppName" label="供应商" min-width="200" sortable="custom" />
 					<el-table-column prop="suppName" label="供应商" min-width="200" sortable="custom" />
 					<el-table-column prop="buyer" label="采购组" width="160" sortable="custom" />
 					<el-table-column prop="buyer" label="采购组" width="160" sortable="custom" />
 					<el-table-column prop="ordDate" label="订单日期" width="120" sortable="custom"><template #default="{ row }">{{ fmtDate(row.ordDate) }}</template></el-table-column>
 					<el-table-column prop="ordDate" label="订单日期" width="120" sortable="custom"><template #default="{ row }">{{ fmtDate(row.ordDate) }}</template></el-table-column>
@@ -50,8 +49,7 @@
 						@current-change="loadList"
 						@current-change="loadList"
 						@size-change="loadList"
 						@size-change="loadList"
 					/>
 					/>
-				</div>
-			</template>
+			</div>
 		</aidop-demo-shell>
 		</aidop-demo-shell>
 
 
 		<el-dialog v-model="formVisible" :title="editingId ? '编辑委外加工订单' : '新增委外加工订单'" width="720px" destroy-on-close>
 		<el-dialog v-model="formVisible" :title="editingId ? '编辑委外加工订单' : '新增委外加工订单'" width="720px" destroy-on-close>
@@ -62,11 +60,14 @@
 
 
 <script setup lang="ts">
 <script setup lang="ts">
 import { onMounted, reactive, ref } from 'vue';
 import { onMounted, reactive, ref } from 'vue';
+import { useRouter } from 'vue-router';
 import { ElMessage, ElMessageBox } from 'element-plus';
 import { ElMessage, ElMessageBox } from 'element-plus';
 import AidopDemoShell from '../../components/AidopDemoShell.vue';
 import AidopDemoShell from '../../components/AidopDemoShell.vue';
 import OutsourceOrderForm from './outsourceOrderForm.vue';
 import OutsourceOrderForm from './outsourceOrderForm.vue';
 import { deleteOutsourceOrder, fetchOutsourceBuyers, fetchOutsourceOrderList, fetchOutsourceSuppliers, type OptionRow, type OutsourceOrderRow } from '../api/outsourceOrder';
 import { deleteOutsourceOrder, fetchOutsourceBuyers, fetchOutsourceOrderList, fetchOutsourceSuppliers, type OptionRow, type OutsourceOrderRow } from '../api/outsourceOrder';
 
 
+const router = useRouter();
+
 const query = reactive({
 const query = reactive({
 	page: 1,
 	page: 1,
 	pageSize: 10,
 	pageSize: 10,
@@ -131,8 +132,10 @@ function goDetail(row: OutsourceOrderRow) {
 		ElMessage.warning('只有新增状态允许维护明细');
 		ElMessage.warning('只有新增状态允许维护明细');
 		return;
 		return;
 	}
 	}
-	const path = `/aidop/s3/supply/outsourceOrderDetailList?purOrd=${encodeURIComponent(row.purOrd || '')}`;
-	window.open(path, '_blank');
+	router.push({
+		path: '/aidop/s3/supply/outsource-order-details',
+		query: { purOrd: row.purOrd || '' },
+	});
 }
 }
 async function onDelete(row: OutsourceOrderRow) {
 async function onDelete(row: OutsourceOrderRow) {
 	await ElMessageBox.confirm('确认删除该委外加工订单?', '提示', { type: 'warning' });
 	await ElMessageBox.confirm('确认删除该委外加工订单?', '提示', { type: 'warning' });
@@ -154,12 +157,23 @@ onMounted(async () => {
 });
 });
 </script>
 </script>
 
 
-<style scoped>
+<style scoped lang="scss">
+@import '/@/views/aidop/styles/aidop-demo.scss';
+
 .aidop-page {
 .aidop-page {
 	padding: 8px;
 	padding: 8px;
 }
 }
+.mb12 {
+	margin-bottom: 12px;
+}
+.toolbar {
+	display: flex;
+	align-items: center;
+	gap: 8px;
+	margin-bottom: 12px;
+}
 .pager {
 .pager {
-	margin-top: 10px;
+	margin-top: 12px;
 	display: flex;
 	display: flex;
 	justify-content: flex-end;
 	justify-content: flex-end;
 }
 }

+ 47 - 38
Web/src/views/aidop/s3/supply/processOutsourceOrderList.vue

@@ -1,40 +1,39 @@
 <template>
 <template>
 	<div class="aidop-page">
 	<div class="aidop-page">
 		<aidop-demo-shell title="工序外协订单">
 		<aidop-demo-shell title="工序外协订单">
-			<template #query>
-				<el-form :inline="true" :model="query" @submit.prevent>
-					<el-form-item label="采购单号"><el-input v-model="query.purOrd" clearable style="width: 170px" /></el-form-item>
-					<el-form-item label="工单"><el-input v-model="query.workOrd" clearable style="width: 170px" /></el-form-item>
-					<el-form-item label="采购组">
-						<el-select v-model="query.buyer" clearable filterable style="width: 170px">
-							<el-option v-for="o in buyerOptions" :key="o.value" :label="o.label" :value="o.value" />
-						</el-select>
-					</el-form-item>
-					<el-form-item label="供应商">
-						<el-select v-model="query.supp" clearable filterable style="width: 190px">
-							<el-option v-for="o in supplierOptions" :key="o.value" :label="o.label" :value="o.value" />
-						</el-select>
-					</el-form-item>
-					<el-form-item label="状态">
-						<el-select v-model="query.status" clearable style="width: 140px">
-							<el-option label="新增" value="R" />
-							<el-option label="审核中" value="A" />
-							<el-option label="同意" value="B" />
-							<el-option label="关闭" value="C" />
-						</el-select>
-					</el-form-item>
-					<el-form-item>
-						<el-button type="primary" @click="doSearch">查询</el-button>
-						<el-button @click="doReset">重置</el-button>
-					</el-form-item>
-				</el-form>
-			</template>
-			<template #actions>
-				<el-button type="primary" @click="openForm('create', null)">添加</el-button>
-			</template>
-			<template #default>
-				<el-table :data="rows" v-loading="loading" border stripe @sort-change="onSortChange">
-					<el-table-column prop="workOrd" label="工单" min-width="150" fixed="left" sortable="custom" />
+			<el-form :inline="true" :model="query" class="mb12" @submit.prevent>
+				<el-form-item label="采购单号"><el-input v-model="query.purOrd" clearable style="width: 170px" /></el-form-item>
+				<el-form-item label="工单"><el-input v-model="query.workOrd" clearable style="width: 170px" /></el-form-item>
+				<el-form-item label="采购组">
+					<el-select v-model="query.buyer" clearable filterable style="width: 170px">
+						<el-option v-for="o in buyerOptions" :key="o.value" :label="o.label" :value="o.value" />
+					</el-select>
+				</el-form-item>
+				<el-form-item label="供应商">
+					<el-select v-model="query.supp" clearable filterable style="width: 190px">
+						<el-option v-for="o in supplierOptions" :key="o.value" :label="o.label" :value="o.value" />
+					</el-select>
+				</el-form-item>
+				<el-form-item label="状态">
+					<el-select v-model="query.status" clearable style="width: 140px">
+						<el-option label="新增" value="R" />
+						<el-option label="审核中" value="A" />
+						<el-option label="同意" value="B" />
+						<el-option label="关闭" value="C" />
+					</el-select>
+				</el-form-item>
+				<el-form-item>
+					<el-button type="primary" @click="doSearch">查询</el-button>
+					<el-button @click="doReset">重置</el-button>
+				</el-form-item>
+			</el-form>
+
+			<div class="toolbar">
+				<el-button type="primary" plain @click="openForm('create', null)">添加</el-button>
+			</div>
+
+			<el-table :data="rows" v-loading="loading" border stripe @sort-change="onSortChange">
+				<el-table-column prop="workOrd" label="工单" min-width="150" fixed="left" sortable="custom" />
 					<el-table-column prop="status" label="状态" width="100" fixed="left" sortable="custom">
 					<el-table-column prop="status" label="状态" width="100" fixed="left" sortable="custom">
 						<template #default="{ row }">{{ statusText(row.status) }}</template>
 						<template #default="{ row }">{{ statusText(row.status) }}</template>
 					</el-table-column>
 					</el-table-column>
@@ -73,8 +72,7 @@
 						@current-change="loadList"
 						@current-change="loadList"
 						@size-change="loadList"
 						@size-change="loadList"
 					/>
 					/>
-				</div>
-			</template>
+			</div>
 		</aidop-demo-shell>
 		</aidop-demo-shell>
 
 
 		<el-dialog v-model="formVisible" :title="formTitle" width="88%" destroy-on-close append-to-body>
 		<el-dialog v-model="formVisible" :title="formTitle" width="88%" destroy-on-close append-to-body>
@@ -191,12 +189,23 @@ onMounted(async () => {
 });
 });
 </script>
 </script>
 
 
-<style scoped>
+<style scoped lang="scss">
+@import '/@/views/aidop/styles/aidop-demo.scss';
+
 .aidop-page {
 .aidop-page {
 	padding: 8px;
 	padding: 8px;
 }
 }
+.mb12 {
+	margin-bottom: 12px;
+}
+.toolbar {
+	display: flex;
+	align-items: center;
+	gap: 8px;
+	margin-bottom: 12px;
+}
 .pager {
 .pager {
-	margin-top: 10px;
+	margin-top: 12px;
 	display: flex;
 	display: flex;
 	justify-content: flex-end;
 	justify-content: flex-end;
 }
 }

+ 4 - 1
Web/src/views/aidop/s4/api/procurementExecution.ts

@@ -122,7 +122,10 @@ export function fetchSupplierShipmentDraft(ids: string) {
 }
 }
 
 
 export function saveSupplierShipment(body: SupplierShipmentFormData) {
 export function saveSupplierShipment(body: SupplierShipmentFormData) {
-	return service.post('/api/ProcurementExecution/supplier-shipment/save', body).then((r) => r.data);
+	return service.post('/api/ProcurementExecution/supplier-shipment/save', body).then((r) => {
+		const d = r.data as { result?: { id?: number; message?: string; shddh?: string }; shddh?: string };
+		return d?.result ?? d;
+	});
 }
 }
 
 
 export function deleteSupplierShipment(id: number) {
 export function deleteSupplierShipment(id: number) {

+ 38 - 11
Web/src/views/aidop/s4/delivery/supplierDeliveryManagementList.vue

@@ -73,6 +73,25 @@
 		</div>
 		</div>
 	</AidopDemoShell>
 	</AidopDemoShell>
 
 
+	<el-dialog
+		v-model="shipmentDialog.visible"
+		title="添加发货单"
+		width="90%"
+		top="4vh"
+		destroy-on-close
+		append-to-body
+		class="supplier-shipment-embed-dialog"
+		@closed="shipmentDialog.ids = ''"
+	>
+		<SupplierShipmentForm
+			v-if="shipmentDialog.visible && shipmentDialog.ids"
+			embedded
+			:draft-ids="shipmentDialog.ids"
+			@close="shipmentDialog.visible = false"
+			@saved="loadList"
+		/>
+	</el-dialog>
+
 	<el-dialog v-model="replyDialog.visible" title="回复交期" width="420px" destroy-on-close>
 	<el-dialog v-model="replyDialog.visible" title="回复交期" width="420px" destroy-on-close>
 		<el-form label-width="90px">
 		<el-form label-width="90px">
 			<el-form-item label="交期日期">
 			<el-form-item label="交期日期">
@@ -88,13 +107,13 @@
 
 
 <script setup lang="ts" name="aidopS4SupplierDeliveryManagementList">
 <script setup lang="ts" name="aidopS4SupplierDeliveryManagementList">
 import { computed, onMounted, reactive, ref } from 'vue';
 import { computed, onMounted, reactive, ref } from 'vue';
-import { useRoute, useRouter } from 'vue-router';
+import { useRoute } from 'vue-router';
 import { ElMessage, ElMessageBox } from 'element-plus';
 import { ElMessage, ElMessageBox } from 'element-plus';
 import AidopDemoShell from '/@/views/aidop/components/AidopDemoShell.vue';
 import AidopDemoShell from '/@/views/aidop/components/AidopDemoShell.vue';
+import SupplierShipmentForm from './supplierShipmentForm.vue';
 import { closeDeliveryOrder, fetchSupplierDeliveryList, publishSupplierDelivery, replySupplierDeliveryByPlanDate, replySupplierDeliveryDueDate, type SupplierDeliveryRow } from '../api/procurementExecution';
 import { closeDeliveryOrder, fetchSupplierDeliveryList, publishSupplierDelivery, replySupplierDeliveryByPlanDate, replySupplierDeliveryDueDate, type SupplierDeliveryRow } from '../api/procurementExecution';
 
 
 const route = useRoute();
 const route = useRoute();
-const router = useRouter();
 const pageTitle = computed(() => (route.meta?.title as string) || '供应商交货管理');
 const pageTitle = computed(() => (route.meta?.title as string) || '供应商交货管理');
 
 
 const query = reactive({
 const query = reactive({
@@ -113,6 +132,7 @@ const rows = ref<SupplierDeliveryRow[]>([]);
 const total = ref(0);
 const total = ref(0);
 const selectedRows = ref<SupplierDeliveryRow[]>([]);
 const selectedRows = ref<SupplierDeliveryRow[]>([]);
 const replyDialog = reactive({ visible: false, jqhf: '' });
 const replyDialog = reactive({ visible: false, jqhf: '' });
+const shipmentDialog = reactive({ visible: false, ids: '' });
 
 
 const col = reactive({
 const col = reactive({
 	sffb: true, wlbm: true, wlms: true, buyer: true, gysdm: true, gysmc: true, cgdd: true, ddhh: true,
 	sffb: true, wlbm: true, wlms: true, buyer: true, gysdm: true, gysmc: true, cgdd: true, ddhh: true,
@@ -185,7 +205,7 @@ function getSelectedIdsAndSupplier() {
 		ElMessage.warning('请选择相同供应商的数据');
 		ElMessage.warning('请选择相同供应商的数据');
 		return null;
 		return null;
 	}
 	}
-	const ids = selectedRows.value.map((x) => x.id).filter(Boolean).join(',');
+	const ids = selectedRows.value.map((x) => (x.id != null && x.id !== '' ? String(x.id) : '')).filter(Boolean).join(',');
 	if (!ids) {
 	if (!ids) {
 		ElMessage.warning('所选数据缺少ID');
 		ElMessage.warning('所选数据缺少ID');
 		return null;
 		return null;
@@ -196,10 +216,8 @@ function getSelectedIdsAndSupplier() {
 function onCreateShipment() {
 function onCreateShipment() {
 	const payload = getSelectedIdsAndSupplier();
 	const payload = getSelectedIdsAndSupplier();
 	if (!payload) return;
 	if (!payload) return;
-	router.push({
-		path: '/aidop/s4/delivery/supplier-shipment-form',
-		query: { mode: 'create', ids: payload.ids },
-	});
+	shipmentDialog.ids = payload.ids;
+	shipmentDialog.visible = true;
 }
 }
 
 
 async function onPublish() {
 async function onPublish() {
@@ -232,10 +250,14 @@ async function confirmReplyDueDate() {
 		ElMessage.warning('请选择交期日期');
 		ElMessage.warning('请选择交期日期');
 		return;
 		return;
 	}
 	}
-	await replySupplierDeliveryDueDate(payload.ids, replyDialog.jqhf);
-	replyDialog.visible = false;
-	ElMessage.success('回复交期成功');
-	await loadList();
+	try {
+		await replySupplierDeliveryDueDate(payload.ids, replyDialog.jqhf);
+		replyDialog.visible = false;
+		ElMessage.success('回复交期成功');
+		await loadList();
+	} catch {
+		// 错误由 request 拦截器提示;失败时保留弹窗便于重试
+	}
 }
 }
 
 
 async function onCloseDelivery(row: SupplierDeliveryRow) {
 async function onCloseDelivery(row: SupplierDeliveryRow) {
@@ -272,5 +294,10 @@ onMounted(loadList);
 .mb12 { margin-bottom: 12px; }
 .mb12 { margin-bottom: 12px; }
 .toolbar { display: flex; align-items: center; gap: 8px; margin-bottom: 12px; }
 .toolbar { display: flex; align-items: center; gap: 8px; margin-bottom: 12px; }
 .pager { margin-top: 12px; display: flex; justify-content: flex-end; }
 .pager { margin-top: 12px; display: flex; justify-content: flex-end; }
+:deep(.supplier-shipment-embed-dialog .el-dialog__body) {
+	max-height: calc(100vh - 140px);
+	overflow-y: auto;
+	padding-top: 8px;
+}
 </style>
 </style>
 
 

+ 135 - 17
Web/src/views/aidop/s4/delivery/supplierShipmentForm.vue

@@ -1,5 +1,5 @@
 <template>
 <template>
-	<AidopDemoShell :title="title">
+	<AidopDemoShell :title="title" :show-bar="!embedded">
 		<div v-loading="loading">
 		<div v-loading="loading">
 			<div class="top-toolbar" v-if="isView">
 			<div class="top-toolbar" v-if="isView">
 				<el-button type="primary" @click="onPrintShippingNote">打印送货单</el-button>
 				<el-button type="primary" @click="onPrintShippingNote">打印送货单</el-button>
@@ -8,16 +8,31 @@
 
 
 			<el-form :model="form" label-width="120px">
 			<el-form :model="form" label-width="120px">
 				<el-row :gutter="12">
 				<el-row :gutter="12">
-					<el-col :span="12"><el-form-item label="送货单号"><el-input v-model="form.shddh" :disabled="isView" /></el-form-item></el-col>
+					<el-col :span="12">
+						<el-form-item label="送货单号">
+							<el-input v-model="form.shddh" readonly :placeholder="shddhPlaceholder" />
+						</el-form-item>
+					</el-col>
 					<el-col :span="12">
 					<el-col :span="12">
 						<el-form-item label="计划发货日期">
 						<el-form-item label="计划发货日期">
-							<el-date-picker v-model="form.jhshrq" type="date" value-format="YYYY-MM-DD" style="width: 100%" :disabled="isView" />
+							<el-date-picker
+								v-model="form.jhshrq"
+								type="date"
+								value-format="YYYY-MM-DD"
+								style="width: 100%"
+								:disabled="isView"
+								@change="recalcYjdhrq"
+							/>
+						</el-form-item>
+					</el-col>
+					<el-col :span="12">
+						<el-form-item label="物流时长(天)">
+							<el-input v-model="form.wlsc" :disabled="isView" placeholder="整数天" @change="recalcYjdhrq" @input="recalcYjdhrq" />
 						</el-form-item>
 						</el-form-item>
 					</el-col>
 					</el-col>
-					<el-col :span="12"><el-form-item label="物流时长"><el-input v-model="form.wlsc" :disabled="isView" /></el-form-item></el-col>
 					<el-col :span="12">
 					<el-col :span="12">
 						<el-form-item label="预计到货日期">
 						<el-form-item label="预计到货日期">
-							<el-date-picker v-model="form.yjdhrq" type="date" value-format="YYYY-MM-DD" style="width: 100%" :disabled="isView" />
+							<el-date-picker v-model="form.yjdhrq" type="date" value-format="YYYY-MM-DD" style="width: 100%" :disabled="true" />
 						</el-form-item>
 						</el-form-item>
 					</el-col>
 					</el-col>
 					<el-col :span="12"><el-form-item label="供应商名称"><el-input v-model="form.shPurchaseName" :disabled="isView" /></el-form-item></el-col>
 					<el-col :span="12"><el-form-item label="供应商名称"><el-input v-model="form.shPurchaseName" :disabled="isView" /></el-form-item></el-col>
@@ -55,9 +70,35 @@
 				<el-table-column label="物料编号" width="130"><template #default="{ row }"><el-input v-model="row.shMaterialCode" :disabled="isView" /></template></el-table-column>
 				<el-table-column label="物料编号" width="130"><template #default="{ row }"><el-input v-model="row.shMaterialCode" :disabled="isView" /></template></el-table-column>
 				<el-table-column label="物料名称" min-width="160"><template #default="{ row }"><el-input v-model="row.shMaterialName" :disabled="isView" /></template></el-table-column>
 				<el-table-column label="物料名称" min-width="160"><template #default="{ row }"><el-input v-model="row.shMaterialName" :disabled="isView" /></template></el-table-column>
 				<el-table-column label="图号" width="120"><template #default="{ row }"><el-input v-model="row.th" :disabled="isView" /></template></el-table-column>
 				<el-table-column label="图号" width="120"><template #default="{ row }"><el-input v-model="row.th" :disabled="isView" /></template></el-table-column>
-				<el-table-column label="送货数量" width="100"><template #default="{ row }"><el-input-number v-model="row.shDeliveryQuantity" :controls="false" :min="0" :disabled="isView" style="width: 100%" /></template></el-table-column>
-				<el-table-column label="每箱数量" width="100"><template #default="{ row }"><el-input-number v-model="row.bzsl" :controls="false" :min="0" :disabled="isView" style="width: 100%" /></template></el-table-column>
-				<el-table-column label="标签数量" width="100"><template #default="{ row }"><el-input-number v-model="row.bqsl" :controls="false" :min="0" :disabled="isView" style="width: 100%" /></template></el-table-column>
+				<el-table-column label="送货数量" width="100">
+					<template #default="{ row }">
+						<el-input-number
+							v-model="row.shDeliveryQuantity"
+							:controls="false"
+							:min="0"
+							:disabled="isView"
+							style="width: 100%"
+							@change="() => recalcDetailBqsl(row)"
+						/>
+					</template>
+				</el-table-column>
+				<el-table-column label="每箱数量" width="100">
+					<template #default="{ row }">
+						<el-input-number
+							v-model="row.bzsl"
+							:controls="false"
+							:min="0"
+							:disabled="isView"
+							style="width: 100%"
+							@change="() => recalcDetailBqsl(row)"
+						/>
+					</template>
+				</el-table-column>
+				<el-table-column label="标签数量" width="110">
+					<template #default="{ row }">
+						<el-input-number v-model="row.bqsl" :controls="false" :min="0" :disabled="true" style="width: 100%" />
+					</template>
+				</el-table-column>
 				<el-table-column label="单位" width="90"><template #default="{ row }"><el-input v-model="row.shMaterialDw" :disabled="isView" /></template></el-table-column>
 				<el-table-column label="单位" width="90"><template #default="{ row }"><el-input v-model="row.shMaterialDw" :disabled="isView" /></template></el-table-column>
 				<el-table-column label="生产日期" width="120">
 				<el-table-column label="生产日期" width="120">
 					<template #default="{ row }">
 					<template #default="{ row }">
@@ -104,14 +145,42 @@ import {
 	type SupplierShipmentFormData,
 	type SupplierShipmentFormData,
 } from '../api/procurementExecution';
 } from '../api/procurementExecution';
 
 
+const props = withDefaults(
+	defineProps<{
+		/** 为 true 时在弹窗内使用,数据来自 draftIds,关闭/保存通过 emit 通知父级 */
+		embedded?: boolean;
+		/** embedded 且创建模式:勾选的交货计划行 id,逗号分隔 */
+		draftIds?: string;
+	}>(),
+	{ embedded: false, draftIds: '' }
+);
+
+const emit = defineEmits<{
+	close: [];
+	saved: [];
+}>();
+
 const route = useRoute();
 const route = useRoute();
 const router = useRouter();
 const router = useRouter();
-const mode = computed(() => String(route.query.mode || 'create') as 'create' | 'edit' | 'view');
-const id = computed(() => Number(route.query.id || 0));
-const ids = computed(() => String(route.query.ids || ''));
-const autoPrint = computed(() => String(route.query.autoPrint || '') === '1');
+const mode = computed(() => {
+	if (props.embedded) return 'create' as const;
+	return String(route.query.mode || 'create') as 'create' | 'edit' | 'view';
+});
+const id = computed(() => {
+	if (props.embedded) return 0;
+	return Number(route.query.id || 0);
+});
+const ids = computed(() => {
+	if (props.embedded) return String(props.draftIds || '');
+	return String(route.query.ids || '');
+});
+const autoPrint = computed(() => {
+	if (props.embedded) return false;
+	return String(route.query.autoPrint || '') === '1';
+});
 const isView = computed(() => mode.value === 'view');
 const isView = computed(() => mode.value === 'view');
 const title = computed(() => (mode.value === 'create' ? '发货单新增' : mode.value === 'edit' ? '发货单编辑' : '发货单查看'));
 const title = computed(() => (mode.value === 'create' ? '发货单新增' : mode.value === 'edit' ? '发货单编辑' : '发货单查看'));
+const shddhPlaceholder = computed(() => (isView.value ? '' : '保存后自动生成'));
 
 
 const loading = ref(false);
 const loading = ref(false);
 const saving = ref(false);
 const saving = ref(false);
@@ -145,12 +214,50 @@ function setForm(data: SupplierShipmentFormData) {
 	form.details = (data.details || []).map((d, i) => ({ ...d, hh: d.hh || i + 1 }));
 	form.details = (data.details || []).map((d, i) => ({ ...d, hh: d.hh || i + 1 }));
 }
 }
 
 
+/** 标签数量 = ceil(送货数量 / 每箱数量),每箱数量≤0 时为 0 */
+function recalcDetailBqsl(row: SupplierShipmentDetailRow) {
+	const qty = Number(row.shDeliveryQuantity) || 0;
+	const per = Number(row.bzsl) || 0;
+	if (per <= 0) {
+		row.bqsl = 0;
+		return;
+	}
+	row.bqsl = Math.ceil(qty / per);
+}
+
+/** 预计到货日期 = 计划发货日期 + 物流时长(天,非负,四舍五入为整数天) */
+function recalcYjdhrq() {
+	const ship = form.jhshrq?.trim();
+	if (!ship) {
+		form.yjdhrq = '';
+		return;
+	}
+	const daysRaw = String(form.wlsc ?? '').trim();
+	const days = Math.max(0, Math.round(Number.parseFloat(daysRaw) || 0));
+	const d = new Date(`${ship}T12:00:00`);
+	if (Number.isNaN(d.getTime())) {
+		form.yjdhrq = '';
+		return;
+	}
+	d.setDate(d.getDate() + days);
+	form.yjdhrq = d.toISOString().slice(0, 10);
+}
+
+async function afterFormLoaded() {
+	await nextTick();
+	if (!isView.value) {
+		recalcYjdhrq();
+		for (const row of form.details) recalcDetailBqsl(row);
+	}
+}
+
 function addDetail() {
 function addDetail() {
 	form.details.push({
 	form.details.push({
 		id: null, hh: form.details.length + 1, poBill: '', poBillLine: '', orderType: '', shMaterialCode: '',
 		id: null, hh: form.details.length + 1, poBill: '', poBillLine: '', orderType: '', shMaterialCode: '',
 		shMaterialName: '', th: '', shDeliveryQuantity: 0, bzsl: 0, bqsl: 0, shMaterialDw: '', scrq: '', scph: '', remarks: '',
 		shMaterialName: '', th: '', shDeliveryQuantity: 0, bzsl: 0, bqsl: 0, shMaterialDw: '', scrq: '', scph: '', remarks: '',
 		djsl: 0, jybb: '', jhdbh: '',
 		djsl: 0, jybb: '', jhdbh: '',
 	});
 	});
+	recalcDetailBqsl(form.details[form.details.length - 1]!);
 }
 }
 function removeDetail(index: number) {
 function removeDetail(index: number) {
 	form.details.splice(index, 1);
 	form.details.splice(index, 1);
@@ -162,11 +269,13 @@ async function loadData() {
 		if (mode.value === 'create' && ids.value) {
 		if (mode.value === 'create' && ids.value) {
 			const draft = await fetchSupplierShipmentDraft(ids.value);
 			const draft = await fetchSupplierShipmentDraft(ids.value);
 			setForm(draft);
 			setForm(draft);
+			await afterFormLoaded();
 			return;
 			return;
 		}
 		}
 		if ((mode.value === 'edit' || mode.value === 'view') && id.value > 0) {
 		if ((mode.value === 'edit' || mode.value === 'view') && id.value > 0) {
 			const detail = await fetchSupplierShipmentDetail(id.value);
 			const detail = await fetchSupplierShipmentDetail(id.value);
 			setForm(detail);
 			setForm(detail);
+			await afterFormLoaded();
 			if (mode.value === 'view' && autoPrint.value) {
 			if (mode.value === 'view' && autoPrint.value) {
 				// 等表单渲染完成后再触发浏览器打印
 				// 等表单渲染完成后再触发浏览器打印
 				await nextTick();
 				await nextTick();
@@ -186,20 +295,29 @@ function onPrintShippingNote() {
 async function onSave() {
 async function onSave() {
 	saving.value = true;
 	saving.value = true;
 	try {
 	try {
-		await saveSupplierShipment({
+		recalcYjdhrq();
+		for (const row of form.details) recalcDetailBqsl(row);
+		const data = (await saveSupplierShipment({
 			...form,
 			...form,
 			id: form.id || undefined,
 			id: form.id || undefined,
 			details: form.details.map((d: SupplierShipmentDetailRow, i) => ({ ...d, hh: d.hh || i + 1 })),
 			details: form.details.map((d: SupplierShipmentDetailRow, i) => ({ ...d, hh: d.hh || i + 1 })),
-		});
-		ElMessage.success('保存成功');
-		router.push('/aidop/s4/delivery/supplier-shipment');
+		})) as { shddh?: string };
+		if (data?.shddh) form.shddh = String(data.shddh);
+		ElMessage.success(data?.shddh ? `保存成功,发货单号:${data.shddh}` : '保存成功');
+		if (props.embedded) {
+			emit('saved');
+			emit('close');
+		} else {
+			router.push('/aidop/s4/delivery/supplier-shipment');
+		}
 	} finally {
 	} finally {
 		saving.value = false;
 		saving.value = false;
 	}
 	}
 }
 }
 
 
 function onCancel() {
 function onCancel() {
-	router.push('/aidop/s4/delivery/supplier-shipment');
+	if (props.embedded) emit('close');
+	else router.push('/aidop/s4/delivery/supplier-shipment');
 }
 }
 
 
 onMounted(loadData);
 onMounted(loadData);

+ 3 - 3
server/Admin.NET.Web.Entry/Admin.NET.Web.Entry.csproj

@@ -11,9 +11,9 @@
     <GenerateSatelliteAssembliesForCore>true</GenerateSatelliteAssembliesForCore>
     <GenerateSatelliteAssembliesForCore>true</GenerateSatelliteAssembliesForCore>
     <Copyright>Admin.NET</Copyright>
     <Copyright>Admin.NET</Copyright>
     <Description>Admin.NET 通用权限开发平台</Description>
     <Description>Admin.NET 通用权限开发平台</Description>
-    <AssemblyVersion>1.0.105</AssemblyVersion>
-    <FileVersion>1.0.105</FileVersion>
-    <Version>1.0.105</Version>
+    <AssemblyVersion>1.0.106</AssemblyVersion>
+    <FileVersion>1.0.106</FileVersion>
+    <Version>1.0.106</Version>
   </PropertyGroup>
   </PropertyGroup>
 
 
   <ItemGroup>
   <ItemGroup>

+ 76 - 114
server/Plugins/Admin.NET.Plugin.AiDOP/ProcurementExecution/SupplierDeliveryManagementService.cs

@@ -68,6 +68,7 @@ public class SupplierDeliveryManagementService : IDynamicApiController, ITransie
         var sql = """
         var sql = """
             INSERT INTO `scm_jhjh_jq`
             INSERT INTO `scm_jhjh_jq`
             (
             (
+              `id`,
               `glid1`,
               `glid1`,
               `wlbm`, `wlms`, `wlgg`, `cgdd`,
               `wlbm`, `wlms`, `wlgg`, `cgdd`,
               `jhdsl`, `wjhsl`, `jhd`, `yjjhrq`, `jqhf`,
               `jhdsl`, `wjhsl`, `jhd`, `yjjhrq`, `jqhf`,
@@ -77,6 +78,7 @@ public class SupplierDeliveryManagementService : IDynamicApiController, ITransie
               `qhdj`, `ddhh`, `dw`, `bzsl`, `ly`
               `qhdj`, `ddhh`, `dw`, `bzsl`, `ly`
             )
             )
             SELECT
             SELECT
+              UUID() AS id,
               ds.id AS glid1,
               ds.id AS glid1,
               ds.itemnum AS wlbm,
               ds.itemnum AS wlbm,
               im.Descr AS wlms,
               im.Descr AS wlms,
@@ -134,13 +136,11 @@ public class SupplierDeliveryManagementService : IDynamicApiController, ITransie
         var userId = _userManager.UserId.ToString();
         var userId = _userManager.UserId.ToString();
         var userName = _userManager.Account ?? "system";
         var userName = _userManager.Account ?? "system";
 
 
-        // 说明:
-        // - 需求来自 SQLServer 写法:STRING_SPLIT(@ids, ',')
-        // - MySQL 使用 FIND_IN_SET 判断逗号分隔集合
+        // 列表勾选 id 为 srm_polist_ds.id,须以交货计划表驱动(与发布一致),不能从 vscm_jhjh 驱动否则匹配不到行。
         var sql = """
         var sql = """
             INSERT INTO `scm_jhjh_jq`
             INSERT INTO `scm_jhjh_jq`
             (
             (
-              `glid`, `wlbm`, `wlms`, `wlgg`, `cgdd`, `jhdsl`, `wjhsl`,
+              `id`, `glid`, `wlbm`, `wlms`, `wlgg`, `cgdd`, `jhdsl`, `wjhsl`,
               `jhd`, `yjjhrq`, `jqhf`, `type`, `flag`,
               `jhd`, `yjjhrq`, `jqhf`, `type`, `flag`,
               `scrq`, `scrid`, `scrxm`,
               `scrq`, `scrid`, `scrxm`,
               `gysdm`, `gysmc`,
               `gysdm`, `gysmc`,
@@ -148,75 +148,53 @@ public class SupplierDeliveryManagementService : IDynamicApiController, ITransie
               `qhdj`, `ddhh`, `dw`, `bzsl`, `ly`
               `qhdj`, `ddhh`, `dw`, `bzsl`, `ly`
             )
             )
             SELECT
             SELECT
-              x.id AS glid,
-              x.wlbm,
-              x.wlms,
-              x.wlgg,
-              x.cgdd,
-              x.jhdsl,
-              x.wjhsl,
-              x.jhd,
-              x.yjjhrq,
-              x.jqhf,
-              x.type,
+              UUID() AS id,
+              ds.id AS glid,
+              ds.itemnum AS wlbm,
+              im.Descr AS wlms,
+              im.Descr1 AS wlgg,
+              ds.ponumber AS cgdd,
+              CAST(ds.schedqty AS CHAR(50)) AS jhdsl,
+              CAST(IFNULL(a.wjhsl, 0) AS CHAR(50)) AS wjhsl,
+              ds.dsnum AS jhd,
+              CAST(IFNULL(a.yjjhrq, ds.requestDate) AS CHAR(50)) AS yjjhrq,
+              DATE_FORMAT(DATE(IFNULL(a.yjjhrq, ds.requestDate)), '%Y-%m-%d') AS jqhf,
+              a.`type` AS `type`,
               0 AS flag,
               0 AS flag,
-              x.scrq,
-              x.scrid,
-              x.scrxm,
-              x.gysdm,
-              x.gysmc,
+              a.scrq AS scrq,
+              a.scrid AS scrid,
+              a.scrxm AS scrxm,
+              IFNULL(a.gysdm, ds.suppliercode) AS gysdm,
+              IFNULL(a.gysmc, ds.supplier) AS gysmc,
               @userid AS hfrid,
               @userid AS hfrid,
               @username AS hfrxm,
               @username AS hfrxm,
               DATE_FORMAT(NOW(), '%Y-%m-%d %H:%i:%s') AS hfsj,
               DATE_FORMAT(NOW(), '%Y-%m-%d %H:%i:%s') AS hfsj,
-              x.qhdj,
-              x.ddhh,
-              x.dw,
-              x.bzsl,
-              x.ly
-            FROM (
-              SELECT
-                CAST(IFNULL(ds.id, a.id) AS CHAR(50)) AS id,
-                a.wlbm,
-                a.wlms,
-                a.wlgg,
-                a.cgdd,
-                a.jhdsl,
-                a.wjhsl,
-                a.jhd,
-                a.yjjhrq,
-                DATE_FORMAT(DATE(a.yjjhrq), '%Y-%m-%d') AS jqhf,
-                a.type,
-                a.scrq,
-                a.scrid,
-                a.scrxm,
-                a.gysdm,
-                a.gysmc,
-                a.qhdj,
-                a.ddhh,
-                a.dw,
-                a.bzsl,
-                a.ly
-              FROM vscm_jhjh a
-              LEFT JOIN (
-                SELECT * FROM srm_polist_ds
-                WHERE status = 'P' AND isactive = 1 AND restQTY > 0
-              ) ds
-                ON a.cgdd = ds.ponumber AND a.ddhh = ds.poline
-            ) x
-            WHERE FIND_IN_SET(x.id, REPLACE(IFNULL(@ids, ''), ' ', '')) > 0
-              AND NOT EXISTS (
-                SELECT 1 FROM scm_jhjh_jq t
-                WHERE t.flag = 0 AND t.glid = x.id
-              );
+              a.qhdj AS qhdj,
+              ds.poline AS ddhh,
+              a.dw AS dw,
+              a.bzsl AS bzsl,
+              a.ly AS ly
+            FROM srm_polist_ds ds
+            LEFT JOIN ItemMaster im
+              ON ds.itemnum = im.ItemNum
+            LEFT JOIN vscm_jhjh a
+              ON a.cgdd = ds.ponumber
+             AND a.ddhh = ds.poline
+            WHERE FIND_IN_SET(CAST(ds.id AS CHAR(50)), REPLACE(IFNULL(@ids, ''), ' ', '')) > 0
+              AND ds.isactive = 1
+              AND ds.status = 'P';
             """;
             """;
 
 
-        await _db.Ado.ExecuteCommandAsync(sql, new List<SugarParameter>
+        var affected = await _db.Ado.ExecuteCommandAsync(sql, new List<SugarParameter>
         {
         {
             new("@ids", input.Ids),
             new("@ids", input.Ids),
             new("@userid", userId),
             new("@userid", userId),
             new("@username", userName),
             new("@username", userName),
         });
         });
 
 
+        if (affected <= 0)
+            throw Oops.Oh("未插入任何回复记录。请确认勾选的是本列表中的交货计划行,且交货单 status=P、isactive=1。");
+
         return new { message = "按计划日期回复成功" };
         return new { message = "按计划日期回复成功" };
     }
     }
 
 
@@ -232,11 +210,11 @@ public class SupplierDeliveryManagementService : IDynamicApiController, ITransie
         var userId = _userManager.UserId.ToString();
         var userId = _userManager.UserId.ToString();
         var userName = _userManager.Account ?? "system";
         var userName = _userManager.Account ?? "system";
 
 
-        // 需求 SQLServer 写法:STRING_SPLIT(@ids, ',')
-        // MySQL 使用 FIND_IN_SET 判断逗号分隔集合
+        // 列表勾选 id 为 srm_polist_ds.id,须以交货计划表驱动(与发布一致)。
         var sql = """
         var sql = """
             INSERT INTO `scm_jhjh_jq`
             INSERT INTO `scm_jhjh_jq`
             (
             (
+              `id`,
               `glid`, `wlbm`, `wlms`, `wlgg`, `cgdd`,
               `glid`, `wlbm`, `wlms`, `wlgg`, `cgdd`,
               `jhdsl`, `wjhsl`, `jhd`, `yjjhrq`, `jqhf`,
               `jhdsl`, `wjhsl`, `jhd`, `yjjhrq`, `jqhf`,
               `type`, `flag`,
               `type`, `flag`,
@@ -246,63 +224,44 @@ public class SupplierDeliveryManagementService : IDynamicApiController, ITransie
               `qhdj`, `ddhh`, `dw`, `bzsl`, `ly`
               `qhdj`, `ddhh`, `dw`, `bzsl`, `ly`
             )
             )
             SELECT
             SELECT
-              x.id AS glid,
-              x.wlbm,
-              x.wlms,
-              x.wlgg,
-              x.cgdd,
-              x.jhdsl,
-              x.wjhsl,
-              x.jhd,
-              x.yjjhrq,
+              UUID() AS id,
+              ds.id AS glid,
+              ds.itemnum AS wlbm,
+              im.Descr AS wlms,
+              im.Descr1 AS wlgg,
+              ds.ponumber AS cgdd,
+              CAST(ds.schedqty AS CHAR(50)) AS jhdsl,
+              CAST(IFNULL(a.wjhsl, 0) AS CHAR(50)) AS wjhsl,
+              ds.dsnum AS jhd,
+              CAST(IFNULL(a.yjjhrq, ds.requestDate) AS CHAR(50)) AS yjjhrq,
               @jqhf AS jqhf,
               @jqhf AS jqhf,
-              x.type,
+              a.`type` AS `type`,
               0 AS flag,
               0 AS flag,
-              x.scrq,
-              x.scrid,
-              x.scrxm,
-              x.gysdm,
-              x.gysmc,
+              a.scrq AS scrq,
+              a.scrid AS scrid,
+              a.scrxm AS scrxm,
+              IFNULL(a.gysdm, ds.suppliercode) AS gysdm,
+              IFNULL(a.gysmc, ds.supplier) AS gysmc,
               @userid AS hfrid,
               @userid AS hfrid,
               @username AS hfrxm,
               @username AS hfrxm,
               DATE_FORMAT(NOW(), '%Y-%m-%d %H:%i:%s') AS hfsj,
               DATE_FORMAT(NOW(), '%Y-%m-%d %H:%i:%s') AS hfsj,
-              x.qhdj,
-              x.ddhh,
-              x.dw,
-              x.bzsl,
-              x.ly
-            FROM (
-              SELECT
-                CAST(IFNULL(ds.id, a.id) AS CHAR(50)) AS id,
-                a.wlbm,
-                a.wlms,
-                a.wlgg,
-                a.cgdd,
-                a.jhdsl,
-                a.wjhsl,
-                a.jhd,
-                a.yjjhrq,
-                a.type,
-                a.scrq,
-                a.scrid,
-                a.scrxm,
-                a.gysdm,
-                a.gysmc,
-                a.qhdj,
-                a.ddhh,
-                a.dw,
-                a.bzsl,
-                a.ly
-              FROM vscm_jhjh a
-              LEFT JOIN (
-                SELECT * FROM srm_polist_ds
-                WHERE status='P' AND isactive=1 AND restQTY>0
-              ) ds ON a.cgdd = ds.ponumber AND a.ddhh = ds.poline
-            ) x
-            WHERE FIND_IN_SET(x.id, REPLACE(IFNULL(@ids, ''), ' ', '')) > 0;
+              a.qhdj AS qhdj,
+              ds.poline AS ddhh,
+              a.dw AS dw,
+              a.bzsl AS bzsl,
+              a.ly AS ly
+            FROM srm_polist_ds ds
+            LEFT JOIN ItemMaster im
+              ON ds.itemnum = im.ItemNum
+            LEFT JOIN vscm_jhjh a
+              ON a.cgdd = ds.ponumber
+             AND a.ddhh = ds.poline
+            WHERE FIND_IN_SET(CAST(ds.id AS CHAR(50)), REPLACE(IFNULL(@ids, ''), ' ', '')) > 0
+              AND ds.isactive = 1
+              AND ds.status = 'P';
             """;
             """;
 
 
-        await _db.Ado.ExecuteCommandAsync(sql, new List<SugarParameter>
+        var affected = await _db.Ado.ExecuteCommandAsync(sql, new List<SugarParameter>
         {
         {
             new("@ids", input.Ids),
             new("@ids", input.Ids),
             new("@jqhf", input.Jqhf.Trim()),
             new("@jqhf", input.Jqhf.Trim()),
@@ -310,6 +269,9 @@ public class SupplierDeliveryManagementService : IDynamicApiController, ITransie
             new("@username", userName),
             new("@username", userName),
         });
         });
 
 
+        if (affected <= 0)
+            throw Oops.Oh("未插入任何回复记录。请确认勾选的是本列表中的交货计划行,且交货单 status=P、isactive=1。");
+
         return new { message = "回复交期成功" };
         return new { message = "回复交期成功" };
     }
     }
 
 
@@ -414,7 +376,7 @@ public class SupplierDeliveryManagementService : IDynamicApiController, ITransie
                 ds.SentQty,
                 ds.SentQty,
                 IFNULL(im.Drawing, p.Drawing) AS th,
                 IFNULL(im.Drawing, p.Drawing) AS th,
                 IFNULL(im.Rev, p.Rev) AS bbh,
                 IFNULL(im.Rev, p.Rev) AS bbh,
-                IFNULL(ds.id, p.RecID) AS id,
+                CAST(ds.id AS CHAR) AS id,
                 '' AS jhd,
                 '' AS jhd,
                 ds.suppliercode AS gysdm,
                 ds.suppliercode AS gysdm,
                 CASE WHEN IFNULL(ds.id, '') = '' THEN p.Remarks ELSE ds.Remarks END AS bz,
                 CASE WHEN IFNULL(ds.id, '') = '' THEN p.Remarks ELSE ds.Remarks END AS bz,

+ 53 - 5
server/Plugins/Admin.NET.Plugin.AiDOP/ProcurementExecution/SupplierShipmentService.cs

@@ -154,6 +154,10 @@ public class SupplierShipmentService : IDynamicApiController, ITransient
                 IFNULL(im.Drawing, p.Drawing) AS th,
                 IFNULL(im.Drawing, p.Drawing) AS th,
                 IFNULL(ds.SchedQty, p.QtyOrded - p.RctQty - p.ReceiptQty + p.QtyReturned) AS shDeliveryQuantity,
                 IFNULL(ds.SchedQty, p.QtyOrded - p.RctQty - p.ReceiptQty + p.QtyReturned) AS shDeliveryQuantity,
                 p.StdPackQty AS bzsl,
                 p.StdPackQty AS bzsl,
+                CASE
+                    WHEN IFNULL(p.StdPackQty, 0) > 0 THEN CEILING(IFNULL(ds.SchedQty, p.QtyOrded - p.RctQty - p.ReceiptQty + p.QtyReturned) / p.StdPackQty)
+                    ELSE 0
+                END AS bqsl,
                 p.UM AS shMaterialDw,
                 p.UM AS shMaterialDw,
                 (p.QtyOrded - p.RctQty - p.ReceiptQty + p.QtyReturned) AS djsl,
                 (p.QtyOrded - p.RctQty - p.ReceiptQty + p.QtyReturned) AS djsl,
                 ds.DSNum AS jhdbh,
                 ds.DSNum AS jhdbh,
@@ -192,7 +196,7 @@ public class SupplierShipmentService : IDynamicApiController, ITransient
                 th = r.Th,
                 th = r.Th,
                 shDeliveryQuantity = r.ShDeliveryQuantity,
                 shDeliveryQuantity = r.ShDeliveryQuantity,
                 bzsl = r.Bzsl,
                 bzsl = r.Bzsl,
-                bqsl = 0,
+                bqsl = r.Bqsl ?? 0,
                 shMaterialDw = r.ShMaterialDw,
                 shMaterialDw = r.ShMaterialDw,
                 scrq = string.Empty,
                 scrq = string.Empty,
                 scph = string.Empty,
                 scph = string.Empty,
@@ -216,10 +220,11 @@ public class SupplierShipmentService : IDynamicApiController, ITransient
         if (input.Id is null or 0)
         if (input.Id is null or 0)
         {
         {
             var newId = YitIdHelper.NextId();
             var newId = YitIdHelper.NextId();
+            var shddh = await AllocateShddhAsync(input.ShPurchaseNum ?? string.Empty);
             var entity = new ScmShd
             var entity = new ScmShd
             {
             {
                 Id = newId,
                 Id = newId,
-                Shddh = string.IsNullOrWhiteSpace(input.Shddh) ? $"SH{DateTime.Now:yyyyMMddHHmmss}" : input.Shddh!.Trim(),
+                Shddh = shddh,
                 Jhshrq = shipDate,
                 Jhshrq = shipDate,
                 Wlsc = input.Wlsc,
                 Wlsc = input.Wlsc,
                 Yjdhrq = input.Yjdhrq,
                 Yjdhrq = input.Yjdhrq,
@@ -237,11 +242,10 @@ public class SupplierShipmentService : IDynamicApiController, ITransient
             };
             };
             await _masterRep.InsertAsync(entity);
             await _masterRep.InsertAsync(entity);
             await SaveDetailsAsync(newId, input.Details);
             await SaveDetailsAsync(newId, input.Details);
-            return new { id = newId, message = "新增成功" };
+            return new { id = newId, message = "新增成功", shddh = entity.Shddh };
         }
         }
 
 
         var master = await _masterRep.GetFirstAsync(x => x.Id == input.Id.Value) ?? throw Oops.Oh("发货单不存在");
         var master = await _masterRep.GetFirstAsync(x => x.Id == input.Id.Value) ?? throw Oops.Oh("发货单不存在");
-        master.Shddh = input.Shddh;
         master.Jhshrq = shipDate;
         master.Jhshrq = shipDate;
         master.Wlsc = input.Wlsc;
         master.Wlsc = input.Wlsc;
         master.Yjdhrq = input.Yjdhrq;
         master.Yjdhrq = input.Yjdhrq;
@@ -253,7 +257,7 @@ public class SupplierShipmentService : IDynamicApiController, ITransient
         master.Pcsm = input.Pcsm;
         master.Pcsm = input.Pcsm;
         await _masterRep.UpdateAsync(master);
         await _masterRep.UpdateAsync(master);
         await SaveDetailsAsync(input.Id.Value, input.Details);
         await SaveDetailsAsync(input.Id.Value, input.Details);
-        return new { id = input.Id, message = "编辑成功" };
+        return new { id = input.Id, message = "编辑成功", shddh = master.Shddh };
     }
     }
 
 
     [DisplayName("删除发货单")]
     [DisplayName("删除发货单")]
@@ -688,6 +692,49 @@ public class SupplierShipmentService : IDynamicApiController, ITransient
     private Task ReleaseMySqlLockAsync(string key)
     private Task ReleaseMySqlLockAsync(string key)
         => _db.Ado.ExecuteCommandAsync("SELECT RELEASE_LOCK(@k)", new List<SugarParameter> { new("@k", key) });
         => _db.Ado.ExecuteCommandAsync("SELECT RELEASE_LOCK(@k)", new List<SugarParameter> { new("@k", key) });
 
 
+    /// <summary>
+    /// 发货单号:供应商编码-yyyyMMdd-四位流水(同日同供应商递增,保存时分配)。
+    /// </summary>
+    private async Task<string> AllocateShddhAsync(string supplierCode)
+    {
+        var code = (supplierCode ?? string.Empty).Trim().Replace("-", string.Empty);
+        if (string.IsNullOrEmpty(code))
+            throw Oops.Oh("请先填写供应商编号,保存时将用于生成发货单号");
+
+        var date = DateTime.Now.ToString("yyyyMMdd");
+        var prefix = $"{code}-{date}-";
+        if (prefix.Length > 240)
+            throw Oops.Oh("供应商编码过长,无法生成发货单号");
+
+        var lockKey = $"scm_shddh_{code}_{date}";
+        if (lockKey.Length > 64)
+            lockKey = lockKey[..64];
+
+        await AcquireMySqlLockAsync(lockKey, 15);
+        try
+        {
+            var maxSeq = await _db.Ado.GetIntAsync(
+                """
+                SELECT IFNULL(MAX(CAST(RIGHT(shddh, 4) AS UNSIGNED)), 0)
+                FROM scm_shd
+                WHERE shddh LIKE CONCAT(@pfx, '%')
+                  AND CHAR_LENGTH(shddh) = CHAR_LENGTH(@pfx) + 4
+                  AND RIGHT(shddh, 4) REGEXP '^[0-9]{4}$'
+                """,
+                new List<SugarParameter> { new("@pfx", prefix) });
+
+            var next = maxSeq + 1;
+            if (next > 9999)
+                throw Oops.Oh("当日该供应商发货单流水号已超过 9999");
+
+            return $"{prefix}{next:D4}";
+        }
+        finally
+        {
+            await ReleaseMySqlLockAsync(lockKey);
+        }
+    }
+
     private async Task<int> GetNextDailySerialAsync(string prefix, string domain)
     private async Task<int> GetNextDailySerialAsync(string prefix, string domain)
     {
     {
         // 从已存在的批次号中取当天最大流水:yyMMdd + 3位
         // 从已存在的批次号中取当天最大流水:yyMMdd + 3位
@@ -1095,6 +1142,7 @@ public class SupplierShipmentService : IDynamicApiController, ITransient
         public string? Th { get; set; }
         public string? Th { get; set; }
         public decimal? ShDeliveryQuantity { get; set; }
         public decimal? ShDeliveryQuantity { get; set; }
         public decimal? Bzsl { get; set; }
         public decimal? Bzsl { get; set; }
+        public decimal? Bqsl { get; set; }
         public string? ShMaterialDw { get; set; }
         public string? ShMaterialDw { get; set; }
         public decimal? Djsl { get; set; }
         public decimal? Djsl { get; set; }
         public string? Jhdbh { get; set; }
         public string? Jhdbh { get; set; }

+ 7 - 7
server/Plugins/Admin.NET.Plugin.AiDOP/Supply/DeliveryScheduleService.cs

@@ -36,10 +36,10 @@ public class DeliveryScheduleService : IDynamicApiController, ITransient
         var pageSize = input.PageSize <= 0 ? 10 : input.PageSize;
         var pageSize = input.PageSize <= 0 ? 10 : input.PageSize;
         var offset = (page - 1) * pageSize;
         var offset = (page - 1) * pageSize;
 
 
-        if (await ShouldUseStdAsync())
-        {
-            return await GetStdListAsync(input, page, pageSize, offset);
-        }
+        //if (await ShouldUseStdAsync())
+        //{
+            //return await GetStdListAsync(input, page, pageSize, offset);
+        //}
 
 
         var pars = new List<SugarParameter>();
         var pars = new List<SugarParameter>();
         var where = new List<string> { "ss.isactive = 1" };
         var where = new List<string> { "ss.isactive = 1" };
@@ -77,7 +77,7 @@ public class DeliveryScheduleService : IDynamicApiController, ITransient
         var orderBy = BuildListOrderBy(input.SortField, input.SortOrder);
         var orderBy = BuildListOrderBy(input.SortField, input.SortOrder);
         var fromSql = $"""
         var fromSql = $"""
             FROM srm_polist_ds ss
             FROM srm_polist_ds ss
-            LEFT JOIN ItemMaster im ON ss.domain = im.Domain AND ss.itemnum = im.ItemNum
+            LEFT JOIN ItemMaster im ON ss.itemnum = im.ItemNum
             WHERE {string.Join(" AND ", where)}
             WHERE {string.Join(" AND ", where)}
             """;
             """;
 
 
@@ -311,7 +311,7 @@ public class DeliveryScheduleService : IDynamicApiController, ITransient
             FROM PurOrdMaster m
             FROM PurOrdMaster m
             INNER JOIN PurOrdDetail d ON m.RecID = d.PurOrdRecID
             INNER JOIN PurOrdDetail d ON m.RecID = d.PurOrdRecID
             LEFT JOIN srm_polist_ds ds ON d.PurOrd = ds.ponumber AND d.Line = ds.poline AND ds.isactive = 1
             LEFT JOIN srm_polist_ds ds ON d.PurOrd = ds.ponumber AND d.Line = ds.poline AND ds.isactive = 1
-            LEFT JOIN ConsigneeAddressMaster s ON m.Domain = s.Domain AND m.Supp = s.Address AND s.Typed = 'Supp'
+            LEFT JOIN ConsigneeAddressMaster s ON m.Supp = s.Address AND s.Typed = 'Supp'
             WHERE {string.Join(" AND ", where)}
             WHERE {string.Join(" AND ", where)}
             """;
             """;
         var distinctSql = $"SELECT DISTINCT m.PurOrd, d.Line, d.ItemNum {fromSql}";
         var distinctSql = $"SELECT DISTINCT m.PurOrd, d.Line, d.ItemNum {fromSql}";
@@ -386,7 +386,7 @@ public class DeliveryScheduleService : IDynamicApiController, ITransient
                 m.tenant_id AS TenantId
                 m.tenant_id AS TenantId
             FROM PurOrdMaster m
             FROM PurOrdMaster m
             INNER JOIN PurOrdDetail d ON m.RecID = d.PurOrdRecID
             INNER JOIN PurOrdDetail d ON m.RecID = d.PurOrdRecID
-            LEFT JOIN ConsigneeAddressMaster s ON m.Domain = s.Domain AND m.Supp = s.Address AND s.Typed = 'Supp'
+            LEFT JOIN ConsigneeAddressMaster s ON m.Supp = s.Address AND s.Typed = 'Supp'
             WHERE {string.Join(" AND ", where)}
             WHERE {string.Join(" AND ", where)}
             ORDER BY m.PurOrd, d.Line
             ORDER BY m.PurOrd, d.Line
             """,
             """,

+ 2 - 2
server/Plugins/Admin.NET.Plugin.AiDOP/Supply/DemandScheduleService.cs

@@ -63,7 +63,7 @@ public class DemandScheduleService : IDynamicApiController, ITransient
 
 
         var fromSql = $"""
         var fromSql = $"""
             FROM ic_demandschedule a
             FROM ic_demandschedule a
-            INNER JOIN ItemMaster b ON a.itemnum = b.ItemNum AND CAST(a.factory_id AS CHAR(64)) = CAST(b.Domain AS CHAR(64))
+            INNER JOIN ItemMaster b ON a.itemnum = b.ItemNum 
             WHERE {string.Join(" AND ", where)}
             WHERE {string.Join(" AND ", where)}
             """;
             """;
 
 
@@ -120,8 +120,8 @@ public class DemandScheduleService : IDynamicApiController, ITransient
             var entity = input.Adapt<DemandSchedule>();
             var entity = input.Adapt<DemandSchedule>();
             entity.Id = YitIdHelper.NextId();
             entity.Id = YitIdHelper.NextId();
             entity.ItemNum = input.ItemNum?.Trim();
             entity.ItemNum = input.ItemNum?.Trim();
+            entity.IsHistoryVersion = null;
             entity.Status ??= string.Empty;
             entity.Status ??= string.Empty;
-            entity.IsHistoryVersion ??= "N";
             entity.IsDeleted ??= 0;
             entity.IsDeleted ??= 0;
             entity.TenantId ??= _userManager.TenantId;
             entity.TenantId ??= _userManager.TenantId;
             entity.CompanyId ??= 1000;
             entity.CompanyId ??= 1000;

+ 1 - 1
server/Plugins/Admin.NET.Plugin.AiDOP/Supply/Entity/DeliveryExceptionMaster.cs

@@ -9,7 +9,7 @@ public class DeliveryExceptionMaster
     [SugarColumn(ColumnName = "RecID", IsIdentity = false)]
     [SugarColumn(ColumnName = "RecID", IsIdentity = false)]
     public long RecID { get; set; }
     public long RecID { get; set; }
 
 
-    [SugarColumn(ColumnName = "Domain", Length = 8, IsNullable = true)]
+    [SugarColumn(ColumnName = "Domain", Length = 80, IsNullable = true)]
     public string? Domain { get; set; }
     public string? Domain { get; set; }
 
 
     [SugarColumn(ColumnName = "Icdsid", IsNullable = true)]
     [SugarColumn(ColumnName = "Icdsid", IsNullable = true)]

+ 1 - 1
server/Plugins/Admin.NET.Plugin.AiDOP/Supply/Entity/PolistDeliverySchedule.cs

@@ -9,7 +9,7 @@ public class PolistDeliverySchedule
     [SugarColumn(ColumnName = "Id", IsPrimaryKey = true, IsIdentity = false)]
     [SugarColumn(ColumnName = "Id", IsPrimaryKey = true, IsIdentity = false)]
     public long Id { get; set; }
     public long Id { get; set; }
 
 
-    [SugarColumn(ColumnName = "domain", Length = 8)]
+    [SugarColumn(ColumnName = "domain", Length = 80)]
     public string Domain { get; set; } = string.Empty;
     public string Domain { get; set; } = string.Empty;
 
 
     [SugarColumn(ColumnName = "icdsid")]
     [SugarColumn(ColumnName = "icdsid")]

+ 39 - 11
server/Plugins/Admin.NET.Plugin.AiDOP/Supply/OutsourceOrderService.cs

@@ -51,9 +51,9 @@ public class OutsourceOrderService : IDynamicApiController, ITransient
 
 
         var fromSql = $"""
         var fromSql = $"""
             FROM PurOrdMaster p
             FROM PurOrdMaster p
-            LEFT JOIN SuppMaster s ON p.Domain=s.Domain AND p.Supp=s.Supp
-            LEFT JOIN DepartmentMaster d ON p.Domain=d.Domain AND p.Department=d.Department
-            LEFT JOIN EmployeeMaster e ON p.Domain=e.Domain AND p.Buyer=e.Employee
+            LEFT JOIN SuppMaster s ON p.Supp=s.Supp
+            LEFT JOIN DepartmentMaster d ON p.Department=d.Department
+            LEFT JOIN EmployeeMaster e ON p.Buyer=e.Employee
             WHERE {string.Join(" AND ", where)}
             WHERE {string.Join(" AND ", where)}
             """;
             """;
 
 
@@ -113,9 +113,9 @@ public class OutsourceOrderService : IDynamicApiController, ITransient
                 p.`Usage` AS `Usage`,
                 p.`Usage` AS `Usage`,
                 p.Remark AS Remark
                 p.Remark AS Remark
             FROM PurOrdMaster p
             FROM PurOrdMaster p
-            LEFT JOIN SuppMaster s ON p.Domain=s.Domain AND p.Supp=s.Supp
-            LEFT JOIN DepartmentMaster d ON p.Domain=d.Domain AND p.Department=d.Department
-            LEFT JOIN EmployeeMaster e ON p.Domain=e.Domain AND p.Buyer=e.Employee
+            LEFT JOIN SuppMaster s ON p.Supp=s.Supp
+            LEFT JOIN DepartmentMaster d ON p.Department=d.Department
+            LEFT JOIN EmployeeMaster e ON p.Buyer=e.Employee
             WHERE p.RecID=@Id
             WHERE p.RecID=@Id
             LIMIT 1
             LIMIT 1
             """,
             """,
@@ -341,9 +341,29 @@ public class OutsourceOrderService : IDynamicApiController, ITransient
                 await _db.Ado.ExecuteCommandAsync(
                 await _db.Ado.ExecuteCommandAsync(
                     """
                     """
                     INSERT INTO PurOrdDetail
                     INSERT INTO PurOrdDetail
-                    (PurOrd,Line,ItemNum,Descr,UM,Rev,Drawing,Location,QtyOrded,QtyReceived,QtyReleased,DueDate,LotSerial,Potype,PurOrdRecID,Status,CreateUser,CreateTime,UpdateUser,UpdateTime,tenant_id)
+                    (
+                        QtyBO, RctCost, CreditTermsInt, UpdateCurrentCost, CumReceived1, CumReceived2,
+                        CumReceived3, CumReceived4, Disc, FixedPrice, InspectReq, SingleLot, SupplyPer,
+                        PurOrd, PST, PackingSlipQty, PayUMConv, PurCost, RctQty, QtyOrded, QtyReceived,
+                        QtyReturned, Active, QtyReleased, RctUMConversion, Scheduled, ScheduledChanged,
+                        SchedMRPReq, SafetyDays, SafetyHours, StdCost, Taxable, TaxIn, MaxTaxableAmt,
+                        TransportHours, UMConversion, VAT, IsActive, IsConfirm, Potype, IsChanged,
+                        TaxRate, IsRounding, ReceiptQty, BarCodeQty, IsClosed, QtyReturnedRefund, CumQtyBO,
+                        Line, ItemNum, Descr, UM, Rev, Drawing, Location, DueDate, LotSerial, PurOrdRecID, Status,
+                        CreateUser, CreateTime, UpdateUser, UpdateTime, tenant_id
+                    )
                     VALUES
                     VALUES
-                    (@PurOrd,@Line,@ItemNum,@Descr,@UM,@Rev,@Drawing,@Location,@QtyOrded,0,0,@DueDate,@LotSerial,'PW',@PurOrdRecID,'R',@CreateUser,@CreateTime,@UpdateUser,@UpdateTime,@TenantId)
+                    (
+                        0, 0, 0, 0, 0, 0,
+                        0, 0, 0, 0, 0, 0, 0,
+                        @PurOrd, 0, 0, 1, 0, 0, @QtyOrded, 0,
+                        0, 1, 0, 1, 0, 0,
+                        0, 0, 0, 0, 1, 1, 0,
+                        0, 1, 0, 1, 1, 'PW', 0,
+                        0, 0, 0, 0, 0, 0, 0,
+                        @Line, @ItemNum, @Descr, @UM, @Rev, @Drawing, @Location, @DueDate, @LotSerial, @PurOrdRecID, 'R',
+                        @CreateUser, @CreateTime, @UpdateUser, @UpdateTime, @TenantId
+                    )
                     """,
                     """,
                     new SugarParameter("@PurOrd", input.PurOrd.Trim()),
                     new SugarParameter("@PurOrd", input.PurOrd.Trim()),
                     new SugarParameter("@Line", line),
                     new SugarParameter("@Line", line),
@@ -407,9 +427,17 @@ public class OutsourceOrderService : IDynamicApiController, ITransient
                 await _db.Ado.ExecuteCommandAsync(
                 await _db.Ado.ExecuteCommandAsync(
                     """
                     """
                     INSERT INTO PurOrdDetailBatch
                     INSERT INTO PurOrdDetailBatch
-                    (PurOrd,Potype,Line,Batch,ItemNum,SuppItem,UM,Location,QtyOrded,QtyBO,QtyReleased,QtyReceived,QtyReturned,LotSerial,PurOrdDetailRecID,CreateUser,CreateTime,UpdateUser,UpdateTime,tenant_id)
+                    (
+                        Domain, PurOrd, Potype, Line, Batch, ItemNum, SuppItem, UM, Location,
+                        QtyOrded, QtyBO, QtyReleased, QtyReceived, QtyReturned, LotSerial, PurOrdDetailRecID,
+                        CreateUser, CreateTime, UpdateUser, UpdateTime, tenant_id
+                    )
                     VALUES
                     VALUES
-                    (@PurOrd,'PW',@Line,@Batch,@ItemNum,@SuppItem,@UM,@Location,@QtyOrded,@QtyBO,@QtyReleased,@QtyReceived,@QtyReturned,@LotSerial,@PurOrdDetailRecID,@CreateUser,@CreateTime,@UpdateUser,@UpdateTime,@TenantId)
+                    (
+                        '', @PurOrd, 'PW', @Line, @Batch, @ItemNum, @SuppItem, @UM, @Location,
+                        @QtyOrded, @QtyBO, @QtyReleased, @QtyReceived, @QtyReturned, @LotSerial, @PurOrdDetailRecID,
+                        @CreateUser, @CreateTime, @UpdateUser, @UpdateTime, @TenantId
+                    )
                     """,
                     """,
                     new SugarParameter("@PurOrd", input.PurOrd.Trim()),
                     new SugarParameter("@PurOrd", input.PurOrd.Trim()),
                     new SugarParameter("@Line", line),
                     new SugarParameter("@Line", line),
@@ -419,7 +447,7 @@ public class OutsourceOrderService : IDynamicApiController, ITransient
                     new SugarParameter("@UM", b.UM ?? batchItem?.UM),
                     new SugarParameter("@UM", b.UM ?? batchItem?.UM),
                     new SugarParameter("@Location", b.Location ?? batchItem?.Location),
                     new SugarParameter("@Location", b.Location ?? batchItem?.Location),
                     new SugarParameter("@QtyOrded", b.QtyOrded ?? 0),
                     new SugarParameter("@QtyOrded", b.QtyOrded ?? 0),
-                    new SugarParameter("@QtyBO", b.QtyBO ?? 0),
+                    new SugarParameter("@QtyBO", b.QtyBO.GetValueOrDefault(0)),
                     new SugarParameter("@QtyReleased", b.QtyReleased ?? 0),
                     new SugarParameter("@QtyReleased", b.QtyReleased ?? 0),
                     new SugarParameter("@QtyReceived", b.QtyReceived ?? 0),
                     new SugarParameter("@QtyReceived", b.QtyReceived ?? 0),
                     new SugarParameter("@QtyReturned", b.QtyReturned ?? 0),
                     new SugarParameter("@QtyReturned", b.QtyReturned ?? 0),

+ 4 - 4
server/Plugins/Admin.NET.Plugin.AiDOP/Supply/ProcessOutsourceOrderService.cs

@@ -79,9 +79,9 @@ public class ProcessOutsourceOrderService : IDynamicApiController, ITransient
                 p.Curr AS Curr,
                 p.Curr AS Curr,
                 p.Remark AS Remark
                 p.Remark AS Remark
             FROM PurOrdMaster p
             FROM PurOrdMaster p
-            LEFT JOIN SuppMaster s ON p.Domain=s.Domain AND p.Supp=s.Supp
-            LEFT JOIN DepartmentMaster d ON p.Domain=d.Domain AND p.Department=d.Department
-            LEFT JOIN EmployeeMaster e ON p.Domain=e.Domain AND p.Buyer=e.Employee
+            LEFT JOIN SuppMaster s ON p.Supp=s.Supp
+            LEFT JOIN DepartmentMaster d ON p.Department=d.Department
+            LEFT JOIN EmployeeMaster e ON p.Buyer=e.Employee
             LEFT JOIN PurOrdDetail f ON p.PurOrd=f.PurOrd
             LEFT JOIN PurOrdDetail f ON p.PurOrd=f.PurOrd
             LEFT JOIN RoutingOpDetail r ON f.Op=r.Op AND f.ItemNum=r.RoutingCode
             LEFT JOIN RoutingOpDetail r ON f.Op=r.Op AND f.ItemNum=r.RoutingCode
             LEFT JOIN ItemMaster i ON f.ItemNum=i.ItemNum
             LEFT JOIN ItemMaster i ON f.ItemNum=i.ItemNum
@@ -761,7 +761,7 @@ public class ProcessOutsourceOrderService : IDynamicApiController, ITransient
             FROM RoutingOpDetail
             FROM RoutingOpDetail
             WHERE UDeci5!=0
             WHERE UDeci5!=0
               AND RoutingCode IN (SELECT ItemNum FROM PurOrdDetail WHERE PurOrdRecID=@PurOrdRecID)
               AND RoutingCode IN (SELECT ItemNum FROM PurOrdDetail WHERE PurOrdRecID=@PurOrdRecID)
-            ORDER BY Op
+            ORDER BY Value
             """,
             """,
             new SugarParameter("@PurOrdRecID", purOrdRecId));
             new SugarParameter("@PurOrdRecID", purOrdRecId));
         return new { list };
         return new { list };