Procházet zdrojové kódy

feat: 新增S4交货与发货管理并对齐工单实体

新增 S4 供应商交货管理/发货单前后端与菜单补链能力,同时按数据库结构调整工单相关实体字段映射并修正联表类型转换;同步前后端版本号到 Web 2.4.119 / server 1.0.86。

Co-authored-by: Cursor <cursoragent@cursor.com>
Pengxy před 2 týdny
rodič
revize
9eafc61fa9
19 změnil soubory, kde provedl 2190 přidání a 23 odebrání
  1. 1 1
      Web/package.json
  2. 127 0
      Web/src/views/aidop/s4/api/procurementExecution.ts
  3. 233 0
      Web/src/views/aidop/s4/delivery/supplierDeliveryManagementList.vue
  4. 199 0
      Web/src/views/aidop/s4/delivery/supplierShipmentForm.vue
  5. 207 0
      Web/src/views/aidop/s4/delivery/supplierShipmentList.vue
  6. 3 3
      server/Admin.NET.Web.Entry/Admin.NET.Web.Entry.csproj
  7. 98 0
      server/Plugins/Admin.NET.Plugin.AiDOP/Infrastructure/AidopMenuLinkSync.cs
  8. 94 0
      server/Plugins/Admin.NET.Plugin.AiDOP/ProcurementExecution/Dto/ProcurementExecutionDto.cs
  9. 90 0
      server/Plugins/Admin.NET.Plugin.AiDOP/ProcurementExecution/Entity/ScmJhjhJq.cs
  10. 93 0
      server/Plugins/Admin.NET.Plugin.AiDOP/ProcurementExecution/Entity/ScmShd.cs
  11. 96 0
      server/Plugins/Admin.NET.Plugin.AiDOP/ProcurementExecution/Entity/ScmShdzb.cs
  12. 338 0
      server/Plugins/Admin.NET.Plugin.AiDOP/ProcurementExecution/SupplierDeliveryManagementService.cs
  13. 531 0
      server/Plugins/Admin.NET.Plugin.AiDOP/ProcurementExecution/SupplierShipmentService.cs
  14. 1 1
      server/Plugins/Admin.NET.Plugin.AiDOP/Production/ExecutableDailyPlanService.cs
  15. 61 0
      server/Plugins/Admin.NET.Plugin.AiDOP/SeedData/SysMenuSeedData.cs
  16. 1 1
      server/Plugins/Admin.NET.Plugin.AiDOP/WorkOrder/Entity/MesMoentry.cs
  17. 4 4
      server/Plugins/Admin.NET.Plugin.AiDOP/WorkOrder/Entity/MesMorder.cs
  18. 6 6
      server/Plugins/Admin.NET.Plugin.AiDOP/WorkOrder/Entity/WorkOrdDetail.cs
  19. 7 7
      server/Plugins/Admin.NET.Plugin.AiDOP/WorkOrder/Entity/WorkOrdRouting.cs

+ 1 - 1
Web/package.json

@@ -1,7 +1,7 @@
 {
 {
 	"name": "admin.net",
 	"name": "admin.net",
 	"type": "module",
 	"type": "module",
-	"version": "2.4.118",
+	"version": "2.4.119",
 	"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 通用权限开发框架",

+ 127 - 0
Web/src/views/aidop/s4/api/procurementExecution.ts

@@ -0,0 +1,127 @@
+import service from '/@/utils/request';
+
+export interface Paged<T> {
+	total: number;
+	page: number;
+	pageSize: number;
+	list: T[];
+}
+
+export interface SupplierDeliveryRow {
+	id?: string;
+	sffb?: string;
+	wlbm?: string;
+	wlms?: string;
+	buyer?: string;
+	gysdm?: string;
+	gysmc?: string;
+	cgdd?: string;
+	ddhh?: number;
+	jhdsl?: number;
+	dfhsl?: number;
+	dsnum?: string;
+	requestdate?: string;
+	schedqty?: number;
+	jqhfnew?: string;
+	ztsl?: number;
+	zsl1?: number;
+	rksl?: number;
+	bhgsl?: number;
+	thsl?: number;
+	bz?: string;
+}
+
+export interface SupplierShipmentRow {
+	mid: number;
+	id: number;
+	shddh?: string;
+	poBill?: string;
+	usage?: string;
+	jhshrq?: string;
+	shMaterialCode?: string;
+	shMaterialName?: string;
+	shDeliveryQuantity?: number;
+	sfpc?: number;
+	pcrksl?: number;
+	shPurchaseName?: string;
+	shpc?: string;
+	scph?: string;
+	wldh?: string;
+	dycs?: number;
+	shzt?: string;
+	th?: string;
+	state?: number;
+}
+
+export interface SupplierShipmentDetailRow {
+	id?: number | null;
+	hh?: number | null;
+	poBill?: string;
+	poBillLine?: string;
+	orderType?: string;
+	shMaterialCode?: string;
+	shMaterialName?: string;
+	th?: string;
+	shDeliveryQuantity?: number | null;
+	bzsl?: number | null;
+	bqsl?: number | null;
+	shMaterialDw?: string;
+	scrq?: string;
+	scph?: string;
+	remarks?: string;
+	djsl?: number | null;
+	jybb?: string;
+	jhdbh?: string;
+}
+
+export interface SupplierShipmentFormData {
+	id?: number | null;
+	shddh?: string;
+	jhshrq?: string;
+	wlsc?: string;
+	yjdhrq?: string;
+	shPurchaseName?: string;
+	shPurchaseNum?: string;
+	wldh?: string;
+	sfpc?: number;
+	chbg?: string;
+	pcsm?: string;
+	details: SupplierShipmentDetailRow[];
+}
+
+export function fetchSupplierDeliveryList(params: any) {
+	return service.get<Paged<SupplierDeliveryRow>>('/api/ProcurementExecution/supplier-delivery/list', { params }).then((r) => r.data);
+}
+
+export function publishSupplierDelivery(ids: string) {
+	return service.post('/api/ProcurementExecution/supplier-delivery/publish', { ids }).then((r) => r.data);
+}
+
+export function closeDeliveryOrder(dsNum: string) {
+	return service.post('/api/ProcurementExecution/supplier-delivery/close', { dsNum }).then((r) => r.data);
+}
+
+export function fetchSupplierShipmentList(params: any) {
+	return service.get<Paged<SupplierShipmentRow>>('/api/ProcurementExecution/supplier-shipment/list', { params }).then((r) => r.data);
+}
+
+export function fetchSupplierShipmentDetail(id: number) {
+	return service.get<SupplierShipmentFormData>(`/api/ProcurementExecution/supplier-shipment/${id}`).then((r) => r.data);
+}
+
+export function fetchSupplierShipmentDraft(ids: string) {
+	return service.get<SupplierShipmentFormData>('/api/ProcurementExecution/supplier-shipment/create-draft', { params: { ids } }).then((r) => r.data);
+}
+
+export function saveSupplierShipment(body: SupplierShipmentFormData) {
+	return service.post('/api/ProcurementExecution/supplier-shipment/save', body).then((r) => r.data);
+}
+
+export function deleteSupplierShipment(id: number) {
+	return service.post('/api/ProcurementExecution/supplier-shipment/delete', { id }).then((r) => r.data);
+}
+
+export function shipmentPlaceholderAction(action: 'generate-label' | 'print-shipping-note' | 'print-label', id: number) {
+	return service.post(`/api/ProcurementExecution/supplier-shipment/${action}`, { id }).then((r) => r.data);
+}
+

+ 233 - 0
Web/src/views/aidop/s4/delivery/supplierDeliveryManagementList.vue

@@ -0,0 +1,233 @@
+<template>
+	<AidopDemoShell :title="pageTitle" subtitle="供应商交货管理">
+		<el-form :inline="true" :model="query" class="mb12" @submit.prevent>
+			<el-form-item label="采购单号"><el-input v-model="query.cgdd" clearable style="width: 150px" /></el-form-item>
+			<el-form-item label="交货单号"><el-input v-model="query.dsNum" clearable style="width: 150px" /></el-form-item>
+			<el-form-item label="物料编码"><el-input v-model="query.wlbm" clearable style="width: 150px" /></el-form-item>
+			<el-form-item label="供应商"><el-input v-model="query.gys" clearable style="width: 160px" /></el-form-item>
+			<el-form-item>
+				<el-button type="primary" @click="doSearch">查询</el-button>
+				<el-button @click="resetQuery">重置</el-button>
+			</el-form-item>
+		</el-form>
+
+		<div class="toolbar">
+			<el-button type="primary" @click="onCreateShipment">生成发货单</el-button>
+			<el-button type="success" @click="onPublish">发布</el-button>
+			<el-button @click="onExport">导出</el-button>
+			<el-popover placement="bottom" width="220" trigger="click">
+				<template #reference><el-button text>列设置</el-button></template>
+				<el-checkbox v-for="item in toggleItems" :key="item.key" :model-value="col[item.key]" @change="(v) => setColumnVisible(item.key, Boolean(v))">
+					{{ item.label }}
+				</el-checkbox>
+			</el-popover>
+		</div>
+
+		<el-table :data="rows" v-loading="loading" border stripe @selection-change="onSelectionChange" @sort-change="onSortChange">
+			<el-table-column type="selection" width="44" fixed="left" />
+			<el-table-column v-if="col.sffb" prop="sffb" label="发布日期" width="120" fixed="left" sortable="custom">
+				<template #default="{ row }">{{ fmtDate(row.sffb) }}</template>
+			</el-table-column>
+			<el-table-column v-if="col.wlbm" prop="wlbm" label="物料编码" width="120" sortable="custom" />
+			<el-table-column v-if="col.wlms" prop="wlms" label="物料描述" min-width="150" show-overflow-tooltip sortable="custom" />
+			<el-table-column v-if="col.buyer" prop="buyer" label="采购组" width="100" sortable="custom" />
+			<el-table-column v-if="col.gysdm" prop="gysdm" label="供应商代码" width="120" sortable="custom" />
+			<el-table-column v-if="col.gysmc" prop="gysmc" label="供应商名称" min-width="140" show-overflow-tooltip sortable="custom" />
+			<el-table-column v-if="col.cgdd" prop="cgdd" label="采购单号" width="140" sortable="custom" />
+			<el-table-column v-if="col.ddhh" prop="ddhh" label="采购单行号" width="100" sortable="custom" />
+			<el-table-column v-if="col.jhdsl" prop="jhdsl" label="采购订单数量" width="120" align="right" sortable="custom" />
+			<el-table-column v-if="col.dfhsl" prop="dfhsl" label="待交数量" width="100" align="right" sortable="custom" />
+			<el-table-column v-if="col.dsnum" prop="dsnum" label="交货单号" width="130" sortable="custom" />
+			<el-table-column v-if="col.requestdate" prop="requestdate" label="到货日期" width="120" sortable="custom">
+				<template #default="{ row }">{{ fmtDate(row.requestdate) }}</template>
+			</el-table-column>
+			<el-table-column v-if="col.schedqty" prop="schedqty" label="交货数量" width="100" align="right" sortable="custom" />
+			<el-table-column v-if="col.jqhfnew" prop="jqhfnew" label="交期回复" width="120" sortable="custom">
+				<template #default="{ row }">{{ fmtDate(row.jqhfnew) }}</template>
+			</el-table-column>
+			<el-table-column v-if="col.ztsl" prop="ztsl" label="在途量" width="90" align="right" sortable="custom" />
+			<el-table-column v-if="col.zsl1" prop="zsl1" label="在检量" width="90" align="right" sortable="custom" />
+			<el-table-column v-if="col.rksl" prop="rksl" label="入库量" width="90" align="right" sortable="custom" />
+			<el-table-column v-if="col.bhgsl" prop="bhgsl" label="不合格量" width="100" align="right" sortable="custom" />
+			<el-table-column v-if="col.thsl" prop="thsl" label="退货量" width="90" align="right" sortable="custom" />
+			<el-table-column v-if="col.bz" prop="bz" label="备注" min-width="140" show-overflow-tooltip sortable="custom" />
+			<el-table-column label="操作" width="110" fixed="right">
+				<template #default="{ row }">
+					<el-button link type="danger" @click="onCloseDelivery(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>
+	</AidopDemoShell>
+</template>
+
+<script setup lang="ts" name="aidopS4SupplierDeliveryManagementList">
+import { computed, onMounted, reactive, ref } from 'vue';
+import { useRoute, useRouter } from 'vue-router';
+import { ElMessage, ElMessageBox } from 'element-plus';
+import AidopDemoShell from '/@/views/aidop/components/AidopDemoShell.vue';
+import { closeDeliveryOrder, fetchSupplierDeliveryList, publishSupplierDelivery, type SupplierDeliveryRow } from '../api/procurementExecution';
+
+const route = useRoute();
+const router = useRouter();
+const pageTitle = computed(() => (route.meta?.title as string) || '供应商交货管理');
+
+const query = reactive({
+	cgdd: '',
+	dsNum: '',
+	wlbm: '',
+	gys: '',
+	page: 1,
+	pageSize: 10,
+	sortField: '',
+	sortOrder: '',
+});
+
+const loading = ref(false);
+const rows = ref<SupplierDeliveryRow[]>([]);
+const total = ref(0);
+const selectedRows = ref<SupplierDeliveryRow[]>([]);
+
+const col = reactive({
+	sffb: true, wlbm: true, wlms: true, buyer: true, gysdm: true, gysmc: true, cgdd: true, ddhh: true,
+	jhdsl: true, dfhsl: true, dsnum: true, requestdate: true, schedqty: true, jqhfnew: true, ztsl: true,
+	zsl1: true, rksl: true, bhgsl: true, thsl: true, bz: true,
+});
+
+type ColumnKey = keyof typeof col;
+const toggleItems: Array<{ key: ColumnKey; label: string }> = [
+	{ key: 'sffb', label: '发布日期' }, { key: 'wlbm', label: '物料编码' }, { key: 'wlms', label: '物料描述' }, { key: 'buyer', label: '采购组' },
+	{ key: 'gysdm', label: '供应商代码' }, { key: 'gysmc', label: '供应商名称' }, { key: 'cgdd', label: '采购单号' }, { key: 'ddhh', label: '采购单行号' },
+	{ key: 'jhdsl', label: '采购订单数量' }, { key: 'dfhsl', label: '待交数量' }, { key: 'dsnum', label: '交货单号' }, { key: 'requestdate', label: '到货日期' },
+	{ key: 'schedqty', label: '交货数量' }, { key: 'jqhfnew', label: '交期回复' }, { key: 'ztsl', label: '在途量' }, { key: 'zsl1', label: '在检量' },
+	{ key: 'rksl', label: '入库量' }, { key: 'bhgsl', label: '不合格量' }, { key: 'thsl', label: '退货量' }, { key: 'bz', label: '备注' },
+];
+
+function fmtDate(v?: string | null) {
+	return v ? String(v).slice(0, 10) : '';
+}
+function onSelectionChange(val: SupplierDeliveryRow[]) {
+	selectedRows.value = val;
+}
+function setColumnVisible(key: ColumnKey, visible: boolean) {
+	col[key] = visible;
+}
+function onSortChange({ prop, order }: { prop: string; order: string | null }) {
+	query.sortField = prop || '';
+	query.sortOrder = order === 'ascending' ? 'asc' : order === 'descending' ? 'desc' : '';
+	loadList();
+}
+
+async function loadList() {
+	loading.value = true;
+	try {
+		const data = await fetchSupplierDeliveryList({ ...query });
+		rows.value = data.list || [];
+		total.value = data.total || 0;
+	} finally {
+		loading.value = false;
+	}
+}
+
+function doSearch() {
+	query.page = 1;
+	loadList();
+}
+function resetQuery() {
+	query.cgdd = '';
+	query.dsNum = '';
+	query.wlbm = '';
+	query.gys = '';
+	query.page = 1;
+	query.sortField = '';
+	query.sortOrder = '';
+	loadList();
+}
+
+function getSelectedIdsAndSupplier() {
+	if (!selectedRows.value.length) {
+		ElMessage.warning('请先勾选数据');
+		return null;
+	}
+	const supplier = selectedRows.value[0].gysdm || '';
+	if (!supplier) {
+		ElMessage.warning('所选数据缺少供应商代码');
+		return null;
+	}
+	const notSame = selectedRows.value.some((x) => (x.gysdm || '') !== supplier);
+	if (notSame) {
+		ElMessage.warning('请选择相同供应商的数据');
+		return null;
+	}
+	const ids = selectedRows.value.map((x) => x.id).filter(Boolean).join(',');
+	if (!ids) {
+		ElMessage.warning('所选数据缺少ID');
+		return null;
+	}
+	return { ids, supplier };
+}
+
+function onCreateShipment() {
+	const payload = getSelectedIdsAndSupplier();
+	if (!payload) return;
+	router.push({
+		path: '/aidop/s4/delivery/supplier-shipment-form',
+		query: { mode: 'create', ids: payload.ids },
+	});
+}
+
+async function onPublish() {
+	const payload = getSelectedIdsAndSupplier();
+	if (!payload) return;
+	await publishSupplierDelivery(payload.ids);
+	ElMessage.success('发布成功');
+	await loadList();
+}
+
+async function onCloseDelivery(row: SupplierDeliveryRow) {
+	if (!row.dsnum) {
+		ElMessage.warning('当前行无交货单号');
+		return;
+	}
+	await ElMessageBox.confirm(`确认关闭交货单 ${row.dsnum} 吗?`, '交货单关闭', { type: 'warning' });
+	await closeDeliveryOrder(row.dsnum);
+	ElMessage.success('关闭成功');
+	await loadList();
+}
+
+function onExport() {
+	const headers = ['发布日期', '物料编码', '物料描述', '采购组', '供应商代码', '供应商名称', '采购单号', '采购单行号', '采购订单数量', '待交数量', '交货单号', '到货日期', '交货数量', '交期回复', '在途量', '在检量', '入库量', '不合格量', '退货量', '备注'];
+	const lines = rows.value.map((r) => [
+		fmtDate(r.sffb), r.wlbm ?? '', r.wlms ?? '', r.buyer ?? '', r.gysdm ?? '', r.gysmc ?? '', r.cgdd ?? '', r.ddhh ?? '', r.jhdsl ?? '', r.dfhsl ?? '',
+		r.dsnum ?? '', fmtDate(r.requestdate), r.schedqty ?? '', fmtDate(r.jqhfnew), r.ztsl ?? '', r.zsl1 ?? '', r.rksl ?? '', r.bhgsl ?? '', r.thsl ?? '', r.bz ?? '',
+	]);
+	const csv = [headers, ...lines].map((row) => row.map((v) => `"${String(v ?? '').replace(/"/g, '""')}"`).join(',')).join('\n');
+	const blob = new Blob([`\uFEFF${csv}`], { type: 'text/csv;charset=utf-8;' });
+	const a = document.createElement('a');
+	a.href = URL.createObjectURL(blob);
+	a.download = `供应商交货管理_${new Date().toISOString().slice(0, 10)}.csv`;
+	a.click();
+	URL.revokeObjectURL(a.href);
+}
+
+onMounted(loadList);
+</script>
+
+<style scoped lang="scss">
+@import '/@/views/aidop/styles/aidop-demo.scss';
+.mb12 { margin-bottom: 12px; }
+.toolbar { display: flex; align-items: center; gap: 8px; margin-bottom: 12px; }
+.pager { margin-top: 12px; display: flex; justify-content: flex-end; }
+</style>
+

+ 199 - 0
Web/src/views/aidop/s4/delivery/supplierShipmentForm.vue

@@ -0,0 +1,199 @@
+<template>
+	<AidopDemoShell :title="title">
+		<div v-loading="loading">
+			<el-form :model="form" label-width="120px">
+				<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-date-picker v-model="form.jhshrq" type="date" value-format="YYYY-MM-DD" style="width: 100%" :disabled="isView" />
+						</el-form-item>
+					</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-form-item label="预计到货日期">
+							<el-date-picker v-model="form.yjdhrq" type="date" value-format="YYYY-MM-DD" style="width: 100%" :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>
+					<el-col :span="12"><el-form-item label="供应商编号"><el-input v-model="form.shPurchaseNum" :disabled="isView" /></el-form-item></el-col>
+					<el-col :span="12"><el-form-item label="物流单号"><el-input v-model="form.wldh" :disabled="isView" /></el-form-item></el-col>
+					<el-col :span="12">
+						<el-form-item label="偏差申请">
+							<el-radio-group v-model="form.sfpc" :disabled="isView">
+								<el-radio :value="0">否</el-radio>
+								<el-radio :value="1">是</el-radio>
+							</el-radio-group>
+						</el-form-item>
+					</el-col>
+					<el-col :span="12">
+						<el-form-item label="偏差申请附件">
+							<el-upload :auto-upload="false" :show-file-list="false" :disabled="isView" :on-change="(file) => (form.chbg = file.name)">
+								<el-button :disabled="isView">上传</el-button>
+							</el-upload>
+							<span class="upload-name">{{ form.chbg }}</span>
+						</el-form-item>
+					</el-col>
+					<el-col :span="24"><el-form-item label="偏差说明"><el-input v-model="form.pcsm" type="textarea" :rows="2" :disabled="isView" /></el-form-item></el-col>
+				</el-row>
+			</el-form>
+
+			<el-divider content-position="left">发货明细</el-divider>
+			<div class="sub-toolbar" v-if="!isView">
+				<el-button type="primary" @click="addDetail">添加</el-button>
+			</div>
+			<el-table :data="form.details" border stripe>
+				<el-table-column label="行号" width="80"><template #default="{ row, $index }">{{ row.hh || $index + 1 }}</template></el-table-column>
+				<el-table-column label="订单号" width="130"><template #default="{ row }"><el-input v-model="row.poBill" :disabled="isView" /></template></el-table-column>
+				<el-table-column label="采购订单行" width="100"><template #default="{ row }"><el-input v-model="row.poBillLine" :disabled="isView" /></template></el-table-column>
+				<el-table-column label="订单类型" width="100"><template #default="{ row }"><el-input v-model="row.orderType" :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="图号" 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="90"><template #default="{ row }"><el-input v-model="row.shMaterialDw" :disabled="isView" /></template></el-table-column>
+				<el-table-column label="生产日期" width="120">
+					<template #default="{ row }">
+						<el-date-picker v-model="row.scrq" type="date" value-format="YYYY-MM-DD" :disabled="isView" style="width: 100%" />
+					</template>
+				</el-table-column>
+				<el-table-column label="生产批号" width="120"><template #default="{ row }"><el-input v-model="row.scph" :disabled="isView" /></template></el-table-column>
+				<el-table-column label="备注" min-width="140"><template #default="{ row }"><el-input v-model="row.remarks" :disabled="isView" /></template></el-table-column>
+				<el-table-column label="待交数量" width="100"><template #default="{ row }"><el-input-number v-model="row.djsl" :controls="false" :disabled="isView" style="width: 100%" /></template></el-table-column>
+				<el-table-column label="检验报告" width="130">
+					<template #default="{ row }">
+						<el-upload :auto-upload="false" :show-file-list="false" :disabled="isView" :on-change="(file) => (row.jybb = file.name)">
+							<el-button :disabled="isView">上传</el-button>
+						</el-upload>
+						<div class="upload-name">{{ row.jybb }}</div>
+					</template>
+				</el-table-column>
+				<el-table-column label="交货单号" width="120"><template #default="{ row }"><el-input v-model="row.jhdbh" :disabled="isView" /></template></el-table-column>
+				<el-table-column v-if="!isView" label="操作" width="110" fixed="right">
+					<template #default="{ $index }">
+						<el-button link type="danger" @click="removeDetail($index)">删除</el-button>
+					</template>
+				</el-table-column>
+			</el-table>
+
+			<div class="footer">
+				<el-button @click="onCancel">取消</el-button>
+				<el-button v-if="!isView" type="primary" :loading="saving" @click="onSave">保存</el-button>
+			</div>
+		</div>
+	</AidopDemoShell>
+</template>
+
+<script setup lang="ts" name="aidopS4SupplierShipmentForm">
+import { computed, onMounted, reactive, ref } from 'vue';
+import { useRoute, useRouter } from 'vue-router';
+import { ElMessage } from 'element-plus';
+import AidopDemoShell from '/@/views/aidop/components/AidopDemoShell.vue';
+import {
+	fetchSupplierShipmentDetail,
+	fetchSupplierShipmentDraft,
+	saveSupplierShipment,
+	type SupplierShipmentDetailRow,
+	type SupplierShipmentFormData,
+} from '../api/procurementExecution';
+
+const route = useRoute();
+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 isView = computed(() => mode.value === 'view');
+const title = computed(() => (mode.value === 'create' ? '发货单新增' : mode.value === 'edit' ? '发货单编辑' : '发货单查看'));
+
+const loading = ref(false);
+const saving = ref(false);
+const form = reactive<SupplierShipmentFormData>({
+	id: null,
+	shddh: '',
+	jhshrq: '',
+	wlsc: '',
+	yjdhrq: '',
+	shPurchaseName: '',
+	shPurchaseNum: '',
+	wldh: '',
+	sfpc: 0,
+	chbg: '',
+	pcsm: '',
+	details: [],
+});
+
+function setForm(data: SupplierShipmentFormData) {
+	form.id = data.id || null;
+	form.shddh = data.shddh || '';
+	form.jhshrq = data.jhshrq || '';
+	form.wlsc = data.wlsc || '';
+	form.yjdhrq = data.yjdhrq || '';
+	form.shPurchaseName = data.shPurchaseName || '';
+	form.shPurchaseNum = data.shPurchaseNum || '';
+	form.wldh = data.wldh || '';
+	form.sfpc = data.sfpc ?? 0;
+	form.chbg = data.chbg || '';
+	form.pcsm = data.pcsm || '';
+	form.details = (data.details || []).map((d, i) => ({ ...d, hh: d.hh || i + 1 }));
+}
+
+function addDetail() {
+	form.details.push({
+		id: null, hh: form.details.length + 1, poBill: '', poBillLine: '', orderType: '', shMaterialCode: '',
+		shMaterialName: '', th: '', shDeliveryQuantity: 0, bzsl: 0, bqsl: 0, shMaterialDw: '', scrq: '', scph: '', remarks: '',
+		djsl: 0, jybb: '', jhdbh: '',
+	});
+}
+function removeDetail(index: number) {
+	form.details.splice(index, 1);
+}
+
+async function loadData() {
+	loading.value = true;
+	try {
+		if (mode.value === 'create' && ids.value) {
+			const draft = await fetchSupplierShipmentDraft(ids.value);
+			setForm(draft);
+			return;
+		}
+		if ((mode.value === 'edit' || mode.value === 'view') && id.value > 0) {
+			const detail = await fetchSupplierShipmentDetail(id.value);
+			setForm(detail);
+			return;
+		}
+	} finally {
+		loading.value = false;
+	}
+}
+
+async function onSave() {
+	saving.value = true;
+	try {
+		await saveSupplierShipment({
+			...form,
+			id: form.id || undefined,
+			details: form.details.map((d: SupplierShipmentDetailRow, i) => ({ ...d, hh: d.hh || i + 1 })),
+		});
+		ElMessage.success('保存成功');
+		router.push('/aidop/s4/delivery/supplier-shipment');
+	} finally {
+		saving.value = false;
+	}
+}
+
+function onCancel() {
+	router.push('/aidop/s4/delivery/supplier-shipment');
+}
+
+onMounted(loadData);
+</script>
+
+<style scoped lang="scss">
+@import '/@/views/aidop/styles/aidop-demo.scss';
+.sub-toolbar { margin-bottom: 8px; }
+.footer { display: flex; justify-content: flex-end; gap: 10px; margin-top: 12px; }
+.upload-name { margin-left: 8px; color: #606266; font-size: 12px; }
+</style>
+

+ 207 - 0
Web/src/views/aidop/s4/delivery/supplierShipmentList.vue

@@ -0,0 +1,207 @@
+<template>
+	<AidopDemoShell :title="pageTitle" subtitle="供应商发货单">
+		<el-form :inline="true" :model="query" class="mb12" @submit.prevent>
+			<el-form-item label="发货日期">
+				<el-date-picker v-model="query.jhshrqFrom" type="date" value-format="YYYY-MM-DD" style="width: 150px" />
+			</el-form-item>
+			<el-form-item label="供应商名称"><el-input v-model="query.gysmc" clearable style="width: 150px" /></el-form-item>
+			<el-form-item label="发货单编号"><el-input v-model="query.shddh" clearable style="width: 150px" /></el-form-item>
+			<el-form-item label="物流单号"><el-input v-model="query.wldh" clearable style="width: 150px" /></el-form-item>
+			<el-form-item label="订单编号"><el-input v-model="query.poBill" clearable style="width: 150px" /></el-form-item>
+			<el-form-item label="物料编码"><el-input v-model="query.shMaterialCode" clearable style="width: 150px" /></el-form-item>
+			<el-form-item label="送货状态">
+				<el-select v-model="query.shzt" clearable style="width: 130px">
+					<el-option label="待收" value="待收" />
+					<el-option label="收货中" value="收货中" />
+					<el-option label="完成" value="完成" />
+				</el-select>
+			</el-form-item>
+			<el-form-item label="客户批次号"><el-input v-model="query.shpc" clearable style="width: 150px" /></el-form-item>
+			<el-form-item>
+				<el-button type="primary" @click="doSearch">查询</el-button>
+				<el-button @click="resetQuery">重置</el-button>
+			</el-form-item>
+		</el-form>
+
+		<div class="toolbar">
+			<el-button type="primary" @click="openForm('create')">添加</el-button>
+			<el-button @click="onExport">导出</el-button>
+			<el-popover placement="bottom" width="220" trigger="click">
+				<template #reference><el-button text>列设置</el-button></template>
+				<el-checkbox v-for="item in toggleItems" :key="item.key" :model-value="col[item.key]" @change="(v) => setColumnVisible(item.key, Boolean(v))">
+					{{ item.label }}
+				</el-checkbox>
+			</el-popover>
+		</div>
+
+		<el-table :data="rows" v-loading="loading" border stripe @sort-change="onSortChange" :row-class-name="rowClassName">
+			<el-table-column v-if="col.shddh" prop="shddh" label="发货单编号" width="140" fixed="left" sortable="custom" />
+			<el-table-column v-if="col.shzt" prop="shzt" label="送货状态" width="100" fixed="left" sortable="custom" />
+			<el-table-column v-if="col.poBill" prop="poBill" label="订单编号" width="130" sortable="custom" />
+			<el-table-column v-if="col.usage" prop="usage" label="物料类型" width="100" sortable="custom" />
+			<el-table-column v-if="col.jhshrq" prop="jhshrq" label="发货日期" width="120" sortable="custom" />
+			<el-table-column v-if="col.shMaterialCode" prop="shMaterialCode" label="物料编码" width="120" sortable="custom" />
+			<el-table-column v-if="col.shMaterialName" prop="shMaterialName" label="物料名称" min-width="150" show-overflow-tooltip sortable="custom" />
+			<el-table-column v-if="col.shDeliveryQuantity" prop="shDeliveryQuantity" label="发货数量" width="100" align="right" sortable="custom" />
+			<el-table-column v-if="col.sfpc" label="是否偏差" width="90" sortable="custom">
+				<template #default="{ row }">{{ row.sfpc === 1 ? '是' : '否' }}</template>
+			</el-table-column>
+			<el-table-column v-if="col.pcrksl" prop="pcrksl" label="合格入库" width="100" align="right" sortable="custom" />
+			<el-table-column v-if="col.shPurchaseName" prop="shPurchaseName" label="供应商名称" min-width="140" show-overflow-tooltip sortable="custom" />
+			<el-table-column v-if="col.shpc" prop="shpc" label="客户批次号" width="120" sortable="custom" />
+			<el-table-column v-if="col.scph" prop="scph" label="供应商批号" width="120" sortable="custom" />
+			<el-table-column v-if="col.wldh" prop="wldh" label="物流单号" width="120" sortable="custom" />
+			<el-table-column v-if="col.dycs" prop="dycs" label="已打次数" width="100" sortable="custom" />
+			<el-table-column v-if="col.th" prop="th" label="图号" width="110" sortable="custom" />
+			<el-table-column label="操作" width="360" fixed="right">
+				<template #default="{ row }">
+					<el-button link type="primary" @click="onPlaceholder('generate-label', row)">生成标签</el-button>
+					<el-button link type="primary" @click="onPlaceholder('print-shipping-note', row)">打印送货单</el-button>
+					<el-button link type="primary" @click="onPlaceholder('print-label', row)">打印标签</el-button>
+					<el-button link type="primary" @click="openForm('edit', row.mid)">质检报告</el-button>
+					<el-button link type="primary" @click="openForm('edit', row.mid)">编辑</el-button>
+					<el-button link type="danger" @click="onDelete(row)">删除</el-button>
+					<el-button link @click="openForm('view', row.mid)">查看</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>
+	</AidopDemoShell>
+</template>
+
+<script setup lang="ts" name="aidopS4SupplierShipmentList">
+import { computed, onMounted, reactive, ref } from 'vue';
+import { useRoute, useRouter } from 'vue-router';
+import { ElMessage, ElMessageBox } from 'element-plus';
+import AidopDemoShell from '/@/views/aidop/components/AidopDemoShell.vue';
+import { deleteSupplierShipment, fetchSupplierShipmentList, shipmentPlaceholderAction, type SupplierShipmentRow } from '../api/procurementExecution';
+
+const route = useRoute();
+const router = useRouter();
+const pageTitle = computed(() => (route.meta?.title as string) || '供应商发货单');
+
+const query = reactive({
+	jhshrqFrom: '',
+	gysmc: '',
+	shddh: '',
+	wldh: '',
+	poBill: '',
+	shMaterialCode: '',
+	shzt: '',
+	shpc: '',
+	page: 1,
+	pageSize: 10,
+	sortField: '',
+	sortOrder: '',
+});
+
+const loading = ref(false);
+const rows = ref<SupplierShipmentRow[]>([]);
+const total = ref(0);
+
+const col = reactive({
+	shddh: true, poBill: true, usage: true, jhshrq: true, shMaterialCode: true, shMaterialName: true,
+	shDeliveryQuantity: true, sfpc: true, pcrksl: true, shPurchaseName: true, shpc: true, scph: true, wldh: true, dycs: true, shzt: true, th: true,
+});
+type ColumnKey = keyof typeof col;
+const toggleItems: Array<{ key: ColumnKey; label: string }> = [
+	{ key: 'shddh', label: '发货单编号' }, { key: 'poBill', label: '订单编号' }, { key: 'usage', label: '物料类型' }, { key: 'jhshrq', label: '发货日期' },
+	{ key: 'shMaterialCode', label: '物料编码' }, { key: 'shMaterialName', label: '物料名称' }, { key: 'shDeliveryQuantity', label: '发货数量' }, { key: 'sfpc', label: '是否偏差' },
+	{ key: 'pcrksl', label: '合格入库' }, { key: 'shPurchaseName', label: '供应商名称' }, { key: 'shpc', label: '客户批次号' }, { key: 'scph', label: '供应商批号' },
+	{ key: 'wldh', label: '物流单号' }, { key: 'dycs', label: '已打次数' }, { key: 'shzt', label: '送货状态' }, { key: 'th', label: '图号' },
+];
+
+function setColumnVisible(key: ColumnKey, visible: boolean) {
+	col[key] = visible;
+}
+function onSortChange({ prop, order }: { prop: string; order: string | null }) {
+	query.sortField = prop || '';
+	query.sortOrder = order === 'ascending' ? 'asc' : order === 'descending' ? 'desc' : '';
+	loadList();
+}
+function rowClassName({ row }: { row: SupplierShipmentRow }) {
+	return row.state === 0 ? 'row-deleted' : '';
+}
+
+async function loadList() {
+	loading.value = true;
+	try {
+		const data = await fetchSupplierShipmentList({ ...query });
+		rows.value = data.list || [];
+		total.value = data.total || 0;
+	} finally {
+		loading.value = false;
+	}
+}
+
+function doSearch() {
+	query.page = 1;
+	loadList();
+}
+function resetQuery() {
+	Object.assign(query, {
+		jhshrqFrom: '', gysmc: '', shddh: '', wldh: '', poBill: '', shMaterialCode: '', shzt: '', shpc: '', page: 1, sortField: '', sortOrder: '',
+	});
+	loadList();
+}
+
+function openForm(mode: 'create' | 'edit' | 'view', id?: number) {
+	router.push({
+		path: '/aidop/s4/delivery/supplier-shipment-form',
+		query: { mode, id: id ? String(id) : undefined },
+	});
+}
+
+async function onDelete(row: SupplierShipmentRow) {
+	await ElMessageBox.confirm(`确认删除发货单 ${row.shddh || ''} 吗?`, '删除确认', {
+		confirmButtonText: '删除',
+		cancelButtonText: '取消',
+		type: 'warning',
+	});
+	await deleteSupplierShipment(row.mid);
+	ElMessage.success('删除成功');
+	await loadList();
+}
+
+async function onPlaceholder(action: 'generate-label' | 'print-shipping-note' | 'print-label', row: SupplierShipmentRow) {
+	const ret = await shipmentPlaceholderAction(action, row.mid);
+	ElMessage.info(ret?.message || '功能预留');
+}
+
+function onExport() {
+	const headers = ['发货单编号', '订单编号', '物料类型', '发货日期', '物料编码', '物料名称', '发货数量', '是否偏差', '合格入库', '供应商名称', '客户批次号', '供应商批号', '物流单号', '已打次数', '送货状态', '图号'];
+	const lines = rows.value.map((r) => [
+		r.shddh ?? '', r.poBill ?? '', r.usage ?? '', r.jhshrq ?? '', r.shMaterialCode ?? '', r.shMaterialName ?? '', r.shDeliveryQuantity ?? '',
+		r.sfpc === 1 ? '是' : '否', r.pcrksl ?? '', r.shPurchaseName ?? '', r.shpc ?? '', r.scph ?? '', r.wldh ?? '', r.dycs ?? '', r.shzt ?? '', r.th ?? '',
+	]);
+	const csv = [headers, ...lines].map((row) => row.map((v) => `"${String(v ?? '').replace(/"/g, '""')}"`).join(',')).join('\n');
+	const blob = new Blob([`\uFEFF${csv}`], { type: 'text/csv;charset=utf-8;' });
+	const a = document.createElement('a');
+	a.href = URL.createObjectURL(blob);
+	a.download = `供应商发货单_${new Date().toISOString().slice(0, 10)}.csv`;
+	a.click();
+	URL.revokeObjectURL(a.href);
+}
+
+onMounted(loadList);
+</script>
+
+<style scoped lang="scss">
+@import '/@/views/aidop/styles/aidop-demo.scss';
+.mb12 { margin-bottom: 12px; }
+.toolbar { display: flex; align-items: center; gap: 8px; margin-bottom: 12px; }
+.pager { margin-top: 12px; display: flex; justify-content: flex-end; }
+:deep(.row-deleted .el-table__cell) { background: #ffe7e7 !important; }
+</style>
+

+ 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.85</AssemblyVersion>
-    <FileVersion>1.0.85</FileVersion>
-    <Version>1.0.85</Version>
+    <AssemblyVersion>1.0.86</AssemblyVersion>
+    <FileVersion>1.0.86</FileVersion>
+    <Version>1.0.86</Version>
   </PropertyGroup>
   </PropertyGroup>
 
 
   <ItemGroup>
   <ItemGroup>

+ 98 - 0
server/Plugins/Admin.NET.Plugin.AiDOP/Infrastructure/AidopMenuLinkSync.cs

@@ -30,6 +30,7 @@ public static class AidopMenuLinkSync
         NormalizeS2OperationPlanLeafMenus(db);
         NormalizeS2OperationPlanLeafMenus(db);
         NormalizeS2WorkOrderProgressKanbanMenu(db);
         NormalizeS2WorkOrderProgressKanbanMenu(db);
         NormalizeS3SupplyMenus(db);
         NormalizeS3SupplyMenus(db);
+        NormalizeS4DeliveryMenus(db);
         EnsureLinkagePlanMenu(db);
         EnsureLinkagePlanMenu(db);
         RemoveDeprecatedS8DashboardChildMenu(db);
         RemoveDeprecatedS8DashboardChildMenu(db);
         NormalizeS8MenuParents(db);
         NormalizeS8MenuParents(db);
@@ -628,6 +629,103 @@ public static class AidopMenuLinkSync
             "/aidop/s3/supply/workOrderMaterialReadinessKanban", "ele-List", 20, "S3 工单物料齐套上线看板");
             "/aidop/s3/supply/workOrderMaterialReadinessKanban", "ele-List", 20, "S3 工单物料齐套上线看板");
     }
     }
 
 
+    /// <summary>
+    /// S4「交货管理」目录与子菜单纠偏/补齐。
+    /// </summary>
+    private static void NormalizeS4DeliveryMenus(ISqlSugarClient db)
+    {
+        const long s4RootId = 1321000005000L;
+        const long deliveryDirId = 1322000000012L;
+        if (!db.Queryable<SysMenu>().Any(m => m.Id == deliveryDirId))
+            return;
+
+        var dir = db.Queryable<SysMenu>().First(m => m.Id == deliveryDirId);
+        if (dir != null)
+        {
+            var needFix = dir.Pid != s4RootId
+                          || dir.Title != "交货管理"
+                          || dir.Path != "/aidop/s4/delivery"
+                          || dir.Name != "aidopS4Delivery"
+                          || dir.Component != "Layout"
+                          || dir.Type != MenuTypeEnum.Dir
+                          || dir.Icon != "ele-Folder"
+                          || dir.Redirect != "/aidop/s4/delivery/supplier-delivery-management";
+            if (needFix)
+            {
+                dir.Pid = s4RootId;
+                dir.Title = "交货管理";
+                dir.Path = "/aidop/s4/delivery";
+                dir.Name = "aidopS4Delivery";
+                dir.Component = "Layout";
+                dir.Type = MenuTypeEnum.Dir;
+                dir.Icon = "ele-Folder";
+                dir.Redirect = "/aidop/s4/delivery/supplier-delivery-management";
+                db.Updateable(dir)
+                    .UpdateColumns(m => new { m.Pid, m.Title, m.Path, m.Name, m.Component, m.Type, m.Icon, m.Redirect })
+                    .ExecuteCommand();
+            }
+        }
+
+        var ct = DateTime.Parse("2022-02-10 00:00:00");
+        EnsureS3LeafMenu(
+            db, ct, 1329004100001L, deliveryDirId, "供应商交货管理",
+            "/aidop/s4/delivery/supplier-delivery-management", "aidopS4SupplierDeliveryManagement",
+            "/aidop/s4/delivery/supplierDeliveryManagementList", "ele-Document", 10, "S4 供应商交货管理");
+        EnsureS3LeafMenu(
+            db, ct, 1329004100002L, deliveryDirId, "供应商发货单",
+            "/aidop/s4/delivery/supplier-shipment", "aidopS4SupplierShipment",
+            "/aidop/s4/delivery/supplierShipmentList", "ele-Tickets", 20, "S4 供应商发货单");
+
+        var formMenu = db.Queryable<SysMenu>().First(m => m.Id == 1329004100003L);
+        if (formMenu == null)
+        {
+            db.Insertable(new SysMenu
+            {
+                Id = 1329004100003L,
+                Pid = deliveryDirId,
+                Title = "发货单表单",
+                Path = "/aidop/s4/delivery/supplier-shipment-form",
+                Name = "aidopS4SupplierShipmentForm",
+                Component = "/aidop/s4/delivery/supplierShipmentForm",
+                Icon = "ele-Document",
+                Type = MenuTypeEnum.Menu,
+                IsHide = true,
+                CreateTime = ct,
+                OrderNo = 90,
+                Status = StatusEnum.Enable,
+                Remark = "S4 发货单新增/编辑/查看表单"
+            }).ExecuteCommand();
+            return;
+        }
+
+        var formNeedFix = formMenu.Pid != deliveryDirId
+                          || formMenu.Path != "/aidop/s4/delivery/supplier-shipment-form"
+                          || formMenu.Name != "aidopS4SupplierShipmentForm"
+                          || formMenu.Component != "/aidop/s4/delivery/supplierShipmentForm"
+                          || formMenu.Title != "发货单表单"
+                          || formMenu.Icon != "ele-Document"
+                          || formMenu.Type != MenuTypeEnum.Menu
+                          || formMenu.OrderNo != 90
+                          || formMenu.IsHide != true
+                          || formMenu.Remark != "S4 发货单新增/编辑/查看表单";
+        if (!formNeedFix)
+            return;
+
+        formMenu.Pid = deliveryDirId;
+        formMenu.Path = "/aidop/s4/delivery/supplier-shipment-form";
+        formMenu.Name = "aidopS4SupplierShipmentForm";
+        formMenu.Component = "/aidop/s4/delivery/supplierShipmentForm";
+        formMenu.Title = "发货单表单";
+        formMenu.Icon = "ele-Document";
+        formMenu.Type = MenuTypeEnum.Menu;
+        formMenu.OrderNo = 90;
+        formMenu.IsHide = true;
+        formMenu.Remark = "S4 发货单新增/编辑/查看表单";
+        db.Updateable(formMenu)
+            .UpdateColumns(m => new { m.Pid, m.Path, m.Name, m.Component, m.Title, m.Icon, m.Type, m.OrderNo, m.IsHide, m.Remark })
+            .ExecuteCommand();
+    }
+
     private static void EnsureS3LeafMenu(
     private static void EnsureS3LeafMenu(
         ISqlSugarClient db,
         ISqlSugarClient db,
         DateTime ct,
         DateTime ct,

+ 94 - 0
server/Plugins/Admin.NET.Plugin.AiDOP/ProcurementExecution/Dto/ProcurementExecutionDto.cs

@@ -0,0 +1,94 @@
+using System.ComponentModel.DataAnnotations;
+
+namespace Admin.NET.Plugin.AiDOP.ProcurementExecution.Dto;
+
+public class SupplierDeliveryListInput
+{
+    public int Page { get; set; } = 1;
+    public int PageSize { get; set; } = 10;
+    public string? Cgdd { get; set; }
+    public string? DsNum { get; set; }
+    public string? Wlbm { get; set; }
+    public string? Gys { get; set; }
+    public string? SortField { get; set; }
+    public string? SortOrder { get; set; }
+}
+
+public class SupplierDeliveryBatchInput
+{
+    [Required(ErrorMessage = "ids不能为空")]
+    public string Ids { get; set; } = string.Empty;
+}
+
+public class DeliveryCloseInput
+{
+    [Required(ErrorMessage = "交货单号不能为空")]
+    public string DsNum { get; set; } = string.Empty;
+}
+
+public class SupplierShipmentListInput
+{
+    public int Page { get; set; } = 1;
+    public int PageSize { get; set; } = 10;
+    public string? JhshrqFrom { get; set; }
+    public string? Gysmc { get; set; }
+    public string? Shddh { get; set; }
+    public string? Wldh { get; set; }
+    public string? PoBill { get; set; }
+    public string? ShMaterialCode { get; set; }
+    public string? Shzt { get; set; }
+    public string? Shpc { get; set; }
+    public string? SortField { get; set; }
+    public string? SortOrder { get; set; }
+}
+
+public class SupplierShipmentSaveInput
+{
+    public long? Id { get; set; }
+    public string? Shddh { get; set; }
+    public string? Jhshrq { get; set; }
+    public string? Wlsc { get; set; }
+    public string? Yjdhrq { get; set; }
+    public string? ShPurchaseName { get; set; }
+    public string? ShPurchaseNum { get; set; }
+    public string? Wldh { get; set; }
+    public int? Sfpc { get; set; }
+    public string? Chbg { get; set; }
+    public string? Pcsm { get; set; }
+    public List<SupplierShipmentDetailInput> Details { get; set; } = new();
+}
+
+public class SupplierShipmentDetailInput
+{
+    public long? Id { get; set; }
+    public int? Hh { get; set; }
+    public string? PoBill { get; set; }
+    public string? PoBillLine { get; set; }
+    public string? OrderType { get; set; }
+    public string? ShMaterialCode { get; set; }
+    public string? ShMaterialName { get; set; }
+    public string? Th { get; set; }
+    public decimal? ShDeliveryQuantity { get; set; }
+    public decimal? Bzsl { get; set; }
+    public decimal? Bqsl { get; set; }
+    public string? ShMaterialDw { get; set; }
+    public string? Scrq { get; set; }
+    public string? Scph { get; set; }
+    public string? Remarks { get; set; }
+    public decimal? Djsl { get; set; }
+    public string? Jybb { get; set; }
+    public string? Jhdbh { get; set; }
+}
+
+public class SupplierShipmentDeleteInput
+{
+    [Required]
+    public long Id { get; set; }
+}
+
+public class SupplierShipmentOperationInput
+{
+    [Required]
+    public long Id { get; set; }
+}
+

+ 90 - 0
server/Plugins/Admin.NET.Plugin.AiDOP/ProcurementExecution/Entity/ScmJhjhJq.cs

@@ -0,0 +1,90 @@
+namespace Admin.NET.Plugin.AiDOP.ProcurementExecution.Entity;
+
+/// <summary>
+/// 交期发布/回复表(scm_jhjh_jq)
+/// </summary>
+[SugarTable("scm_jhjh_jq")]
+public class ScmJhjhJq
+{
+    [SugarColumn(ColumnName = "id", IsPrimaryKey = true, Length = 36)]
+    public string Id { get; set; } = string.Empty;
+
+    [SugarColumn(ColumnName = "glid", IsNullable = true, Length = 50)]
+    public string? Glid { get; set; }
+
+    [SugarColumn(ColumnName = "wlbm", IsNullable = true, Length = 500)]
+    public string? Wlbm { get; set; }
+
+    [SugarColumn(ColumnName = "wlms", IsNullable = true, Length = 500)]
+    public string? Wlms { get; set; }
+
+    [SugarColumn(ColumnName = "wlgg", IsNullable = true, Length = 500)]
+    public string? Wlgg { get; set; }
+
+    [SugarColumn(ColumnName = "cgdd", IsNullable = true, Length = 500)]
+    public string? Cgdd { get; set; }
+
+    [SugarColumn(ColumnName = "jhdsl", IsNullable = true, Length = 500)]
+    public string? Jhdsl { get; set; }
+
+    [SugarColumn(ColumnName = "wjhsl", IsNullable = true, Length = 500)]
+    public string? Wjhsl { get; set; }
+
+    [SugarColumn(ColumnName = "jhd", IsNullable = true, Length = 500)]
+    public string? Jhd { get; set; }
+
+    [SugarColumn(ColumnName = "yjjhrq", IsNullable = true, Length = 500)]
+    public string? Yjjhrq { get; set; }
+
+    [SugarColumn(ColumnName = "jqhf", IsNullable = true, Length = 500)]
+    public string? Jqhf { get; set; }
+
+    [SugarColumn(ColumnName = "type", IsNullable = true, Length = 50)]
+    public string? Type { get; set; }
+
+    [SugarColumn(ColumnName = "flag", IsNullable = true)]
+    public int? Flag { get; set; }
+
+    [SugarColumn(ColumnName = "scrq", IsNullable = true, Length = 500)]
+    public string? Scrq { get; set; }
+
+    [SugarColumn(ColumnName = "scrid", IsNullable = true, Length = 500)]
+    public string? Scrid { get; set; }
+
+    [SugarColumn(ColumnName = "scrxm", IsNullable = true, Length = 500)]
+    public string? Scrxm { get; set; }
+
+    [SugarColumn(ColumnName = "gysdm", IsNullable = true, Length = 500)]
+    public string? Gysdm { get; set; }
+
+    [SugarColumn(ColumnName = "gysmc", IsNullable = true, Length = 500)]
+    public string? Gysmc { get; set; }
+
+    [SugarColumn(ColumnName = "hfrid", IsNullable = true, Length = 500)]
+    public string? Hfrid { get; set; }
+
+    [SugarColumn(ColumnName = "hfrxm", IsNullable = true, Length = 500)]
+    public string? Hfrxm { get; set; }
+
+    [SugarColumn(ColumnName = "hfsj", IsNullable = true, Length = 500)]
+    public string? Hfsj { get; set; }
+
+    [SugarColumn(ColumnName = "qhdj", IsNullable = true, Length = 500)]
+    public string? Qhdj { get; set; }
+
+    [SugarColumn(ColumnName = "ddhh", IsNullable = true)]
+    public int? Ddhh { get; set; }
+
+    [SugarColumn(ColumnName = "dw", IsNullable = true, Length = 50)]
+    public string? Dw { get; set; }
+
+    [SugarColumn(ColumnName = "bzsl", IsNullable = true, Length = 50)]
+    public string? Bzsl { get; set; }
+
+    [SugarColumn(ColumnName = "ly", IsNullable = true, Length = 50)]
+    public string? Ly { get; set; }
+
+    [SugarColumn(ColumnName = "glid1", IsNullable = true, Length = 50)]
+    public string? Glid1 { get; set; }
+}
+

+ 93 - 0
server/Plugins/Admin.NET.Plugin.AiDOP/ProcurementExecution/Entity/ScmShd.cs

@@ -0,0 +1,93 @@
+namespace Admin.NET.Plugin.AiDOP.ProcurementExecution.Entity;
+
+/// <summary>
+/// 供应商发货单主表(scm_shd)
+/// </summary>
+[SugarTable("scm_shd")]
+public class ScmShd
+{
+    [SugarColumn(ColumnName = "id", IsPrimaryKey = true)]
+    public long Id { get; set; }
+
+    [SugarColumn(ColumnName = "sh_purchase_id", IsNullable = true)]
+    public long? ShPurchaseId { get; set; }
+
+    [SugarColumn(ColumnName = "sh_purchase_name", IsNullable = true, Length = 255)]
+    public string? ShPurchaseName { get; set; }
+
+    [SugarColumn(ColumnName = "sh_purchase_num", IsNullable = true, Length = 255)]
+    public string? ShPurchaseNum { get; set; }
+
+    [SugarColumn(ColumnName = "sh_purchase_address", IsNullable = true, Length = 255)]
+    public string? ShPurchaseAddress { get; set; }
+
+    [SugarColumn(ColumnName = "sh_purchase_lxr", IsNullable = true, Length = 255)]
+    public string? ShPurchaseLxr { get; set; }
+
+    [SugarColumn(ColumnName = "sh_purchase_phone", IsNullable = true, Length = 255)]
+    public string? ShPurchasePhone { get; set; }
+
+    [SugarColumn(ColumnName = "client", IsNullable = true)]
+    public long? Client { get; set; }
+
+    [SugarColumn(ColumnName = "delivery_Address", IsNullable = true, Length = 255)]
+    public string? DeliveryAddress { get; set; }
+
+    [SugarColumn(ColumnName = "expected_consignee", IsNullable = true, Length = 255)]
+    public string? ExpectedConsignee { get; set; }
+
+    [SugarColumn(ColumnName = "consignee_phone", IsNullable = true, Length = 255)]
+    public string? ConsigneePhone { get; set; }
+
+    [SugarColumn(ColumnName = "estimated_delivery_date", IsNullable = true)]
+    public DateTime? EstimatedDeliveryDate { get; set; }
+
+    [SugarColumn(ColumnName = "po_billno", IsNullable = true, Length = 255)]
+    public string? PoBillNo { get; set; }
+
+    [SugarColumn(ColumnName = "shddh", IsNullable = true, Length = 255)]
+    public string? Shddh { get; set; }
+
+    [SugarColumn(ColumnName = "jhshrq", IsNullable = true, Length = 50)]
+    public string? Jhshrq { get; set; }
+
+    [SugarColumn(ColumnName = "tjrid", IsNullable = true, Length = 50)]
+    public string? Tjrid { get; set; }
+
+    [SugarColumn(ColumnName = "tjrxm", IsNullable = true, Length = 50)]
+    public string? Tjrxm { get; set; }
+
+    [SugarColumn(ColumnName = "tjrq", IsNullable = true, Length = 50)]
+    public string? Tjrq { get; set; }
+
+    [SugarColumn(ColumnName = "scbq", IsNullable = true)]
+    public int? Scbq { get; set; }
+
+    [SugarColumn(ColumnName = "chbg", IsNullable = true)]
+    public string? Chbg { get; set; }
+
+    [SugarColumn(ColumnName = "sfpc", IsNullable = true)]
+    public int? Sfpc { get; set; }
+
+    [SugarColumn(ColumnName = "pcsm", IsNullable = true)]
+    public string? Pcsm { get; set; }
+
+    [SugarColumn(ColumnName = "wlsc", IsNullable = true, Length = 500)]
+    public string? Wlsc { get; set; }
+
+    [SugarColumn(ColumnName = "yjdhrq", IsNullable = true, Length = 500)]
+    public string? Yjdhrq { get; set; }
+
+    [SugarColumn(ColumnName = "state", IsNullable = true)]
+    public int? State { get; set; }
+
+    [SugarColumn(ColumnName = "shzt", IsNullable = true, Length = 50)]
+    public string? Shzt { get; set; }
+
+    [SugarColumn(ColumnName = "wldh", IsNullable = true, Length = 50)]
+    public string? Wldh { get; set; }
+
+    [SugarColumn(ColumnName = "dycs", IsNullable = true)]
+    public int? Dycs { get; set; }
+}
+

+ 96 - 0
server/Plugins/Admin.NET.Plugin.AiDOP/ProcurementExecution/Entity/ScmShdzb.cs

@@ -0,0 +1,96 @@
+namespace Admin.NET.Plugin.AiDOP.ProcurementExecution.Entity;
+
+/// <summary>
+/// 供应商发货单子表(scm_shdzb)
+/// </summary>
+[SugarTable("scm_shdzb")]
+public class ScmShdzb
+{
+    [SugarColumn(ColumnName = "id", IsPrimaryKey = true)]
+    public long Id { get; set; }
+
+    [SugarColumn(ColumnName = "glid", IsNullable = true, Length = 255)]
+    public string? Glid { get; set; }
+
+    [SugarColumn(ColumnName = "sh_material_code", IsNullable = true, Length = 255)]
+    public string? ShMaterialCode { get; set; }
+
+    [SugarColumn(ColumnName = "sh_material_name", IsNullable = true, Length = 255)]
+    public string? ShMaterialName { get; set; }
+
+    [SugarColumn(ColumnName = "sh_material_ggxh", IsNullable = true, Length = 255)]
+    public string? ShMaterialGgxh { get; set; }
+
+    [SugarColumn(ColumnName = "sh_delivery_quantity", IsNullable = true)]
+    public decimal? ShDeliveryQuantity { get; set; }
+
+    [SugarColumn(ColumnName = "sh_material_dw", IsNullable = true, Length = 255)]
+    public string? ShMaterialDw { get; set; }
+
+    [SugarColumn(ColumnName = "remarks", IsNullable = true, Length = 255)]
+    public string? Remarks { get; set; }
+
+    [SugarColumn(ColumnName = "bzsl", IsNullable = true)]
+    public decimal? Bzsl { get; set; }
+
+    [SugarColumn(ColumnName = "bqsl", IsNullable = true)]
+    public decimal? Bqsl { get; set; }
+
+    [SugarColumn(ColumnName = "order_type", IsNullable = true, Length = 255)]
+    public string? OrderType { get; set; }
+
+    [SugarColumn(ColumnName = "po_bill", IsNullable = true, Length = 255)]
+    public string? PoBill { get; set; }
+
+    [SugarColumn(ColumnName = "po_billline", IsNullable = true, Length = 50)]
+    public string? PoBillLine { get; set; }
+
+    [SugarColumn(ColumnName = "hh", IsNullable = true)]
+    public int? Hh { get; set; }
+
+    [SugarColumn(ColumnName = "scrq", IsNullable = true, Length = 255)]
+    public string? Scrq { get; set; }
+
+    [SugarColumn(ColumnName = "scph", IsNullable = true, Length = 255)]
+    public string? Scph { get; set; }
+
+    [SugarColumn(ColumnName = "th", IsNullable = true, Length = 255)]
+    public string? Th { get; set; }
+
+    [SugarColumn(ColumnName = "bbh", IsNullable = true, Length = 255)]
+    public string? Bbh { get; set; }
+
+    [SugarColumn(ColumnName = "djsl", IsNullable = true)]
+    public decimal? Djsl { get; set; }
+
+    [SugarColumn(ColumnName = "ccrq", IsNullable = true, Length = 255)]
+    public string? Ccrq { get; set; }
+
+    [SugarColumn(ColumnName = "cgyt", IsNullable = true, Length = 255)]
+    public string? Cgyt { get; set; }
+
+    [SugarColumn(ColumnName = "jybb", IsNullable = true)]
+    public string? Jybb { get; set; }
+
+    [SugarColumn(ColumnName = "jhdbh", IsNullable = true, Length = 50)]
+    public string? Jhdbh { get; set; }
+
+    [SugarColumn(ColumnName = "jhdhh", IsNullable = true, Length = 50)]
+    public string? Jhdhh { get; set; }
+
+    [SugarColumn(ColumnName = "shpc", IsNullable = true, Length = 50)]
+    public string? Shpc { get; set; }
+
+    [SugarColumn(ColumnName = "shzt", IsNullable = true, Length = 255)]
+    public string? Shzt { get; set; }
+
+    [SugarColumn(ColumnName = "rksl", IsNullable = true)]
+    public decimal? Rksl { get; set; }
+
+    [SugarColumn(ColumnName = "thsl", IsNullable = true)]
+    public decimal? Thsl { get; set; }
+
+    [SugarColumn(ColumnName = "po_billno", IsNullable = true, Length = 255)]
+    public string? PoBillNo { get; set; }
+}
+

+ 338 - 0
server/Plugins/Admin.NET.Plugin.AiDOP/ProcurementExecution/SupplierDeliveryManagementService.cs

@@ -0,0 +1,338 @@
+using Admin.NET.Plugin.AiDOP.ProcurementExecution.Dto;
+
+namespace Admin.NET.Plugin.AiDOP.ProcurementExecution;
+
+/// <summary>
+/// S4 供应商交货管理
+/// </summary>
+[ApiDescriptionSettings(Order = 320, Description = "S4供应商交货管理")]
+[Route("api/ProcurementExecution")]
+[AllowAnonymous]
+[NonUnify]
+public class SupplierDeliveryManagementService : IDynamicApiController, ITransient
+{
+    private readonly ISqlSugarClient _db;
+    private readonly UserManager _userManager;
+
+    public SupplierDeliveryManagementService(ISqlSugarClient db, UserManager userManager)
+    {
+        _db = db;
+        _userManager = userManager;
+    }
+
+    [DisplayName("供应商交货管理列表")]
+    [HttpGet("supplier-delivery/list")]
+    public async Task<object> GetList([FromQuery] SupplierDeliveryListInput input)
+    {
+        var pars = new List<SugarParameter>();
+        var conditions = new List<string>();
+        if (!string.IsNullOrWhiteSpace(input.Cgdd))
+        {
+            conditions.Add("v.cgdd LIKE @cgdd");
+            pars.Add(new SugarParameter("@cgdd", $"%{input.Cgdd.Trim()}%"));
+        }
+        if (!string.IsNullOrWhiteSpace(input.DsNum))
+        {
+            conditions.Add("v.dsnum LIKE @dsnum");
+            pars.Add(new SugarParameter("@dsnum", $"%{input.DsNum.Trim()}%"));
+        }
+        if (!string.IsNullOrWhiteSpace(input.Wlbm))
+        {
+            conditions.Add("v.wlbm LIKE @wlbm");
+            pars.Add(new SugarParameter("@wlbm", $"%{input.Wlbm.Trim()}%"));
+        }
+        if (!string.IsNullOrWhiteSpace(input.Gys))
+        {
+            conditions.Add("(v.gysdm LIKE @gys OR v.gysmc LIKE @gys)");
+            pars.Add(new SugarParameter("@gys", $"%{input.Gys.Trim()}%"));
+        }
+
+        var where = conditions.Count > 0 ? $" WHERE {string.Join(" AND ", conditions)} " : string.Empty;
+        var orderBy = BuildOrderBy(input.SortField, input.SortOrder);
+        var offset = (input.Page - 1) * input.PageSize;
+        var wrapped = $"{BuildBaseSql()} {where}";
+
+        var total = await _db.Ado.GetIntAsync($"SELECT COUNT(*) FROM ({wrapped}) t", pars);
+        var list = await _db.Ado.SqlQueryAsync<SupplierDeliveryListRow>(
+            $"SELECT * FROM ({wrapped}) t {orderBy} LIMIT {input.PageSize} OFFSET {offset}", pars);
+
+        return new { total, page = input.Page, pageSize = input.PageSize, list };
+    }
+
+    [DisplayName("供应商交货管理发布")]
+    [HttpPost("supplier-delivery/publish")]
+    public async Task<object> Publish([FromBody] SupplierDeliveryBatchInput input)
+    {
+        var userId = _userManager.UserId.ToString();
+        var userName = _userManager.Account ?? "system";
+        var sql = """
+            INSERT INTO `scm_jhjh_jq`
+            (
+              `id`,`glid`, `wlbm`, `wlms`, `wlgg`, `cgdd`,
+              `jhdsl`, `wjhsl`, `jhd`, `yjjhrq`, `jqhf`,
+              `type`, `flag`, `scrq`, `scrid`, `scrxm`,
+              `gysdm`, `gysmc`, `hfrid`, `hfrxm`, `hfsj`,
+              `qhdj`, `ddhh`, `dw`, `bzsl`, `ly`,`glid1`
+            )
+            SELECT
+              UUID(),
+              CAST(d.RecID AS CHAR(50)) AS glid,
+              d.ItemNum AS wlbm,
+              i.Descr AS wlms,
+              i.Descr1 AS wlgg,
+              p.PurOrd AS cgdd,
+              CAST(d.QtyOrded AS CHAR(20)) AS jhdsl,
+              (d.QtyOrded - d.RctQty - d.ReceiptQty + d.QtyReturned) AS wjhsl,
+              '' AS jhd,
+              DATE_FORMAT(DATE(d.DueDate), '%Y-%m-%d') AS yjjhrq,
+              '' AS jqhf,
+              p.Potype AS `type`,
+              2 AS flag,
+              '' AS scrq,
+              '' AS scrid,
+              '' AS scrxm,
+              p.Supp AS gysdm,
+              s.SortName AS gysmc,
+              @hfrid AS hfrid,
+              @hfrxm AS hfrxm,
+              DATE_FORMAT(NOW(), '%Y-%m-%d') AS hfsj,
+              '' AS qhdj,
+              d.Line AS ddhh,
+              d.UM AS dw,
+              d.StdPackQty AS bzsl,
+              '2' AS ly,
+              CAST(IFNULL(ds.id, d.RecID) AS CHAR(50)) AS glid1
+            FROM PurOrdMaster p
+            LEFT JOIN PurOrdDetail d
+              ON p.Domain = d.Domain
+             AND p.Potype = d.Potype
+             AND p.PurOrd = d.PurOrd
+            LEFT JOIN SuppMaster s
+              ON p.Domain = s.Domain
+             AND p.Supp = s.Supp
+            LEFT JOIN ItemMaster i
+              ON d.Domain = i.Domain
+             AND d.ItemNum = i.ItemNum
+            LEFT JOIN srm_polist_ds ds
+              ON p.PurOrd = ds.ponumber
+             AND p.Line = ds.poline
+            WHERE p.IsActive = 1
+              AND IFNULL(p.Status, '') <> 'C'
+              AND IFNULL(d.Status, '') <> 'C'
+              AND FIND_IN_SET(CAST(IFNULL(ds.id, d.RecID) AS CHAR(50)), REPLACE(IFNULL(@ids, ''), ' ', '')) > 0
+              AND NOT EXISTS (
+                    SELECT 1 FROM scm_jhjh_jq x
+                    WHERE x.flag = 2
+                      AND x.glid1 = CAST(IFNULL(ds.id, d.RecID) AS CHAR(50))
+              );
+            """;
+        await _db.Ado.ExecuteCommandAsync(sql, new List<SugarParameter>
+        {
+            new("@ids", input.Ids),
+            new("@hfrid", userId),
+            new("@hfrxm", userName)
+        });
+        return new { message = "发布成功" };
+    }
+
+    [DisplayName("交货单关闭")]
+    [HttpPost("supplier-delivery/close")]
+    public async Task<object> CloseDelivery([FromBody] DeliveryCloseInput input)
+    {
+        await _db.Ado.ExecuteCommandAsync(
+            "UPDATE srm_polist_ds SET status='C' WHERE DSNum=@jhdbh",
+            new List<SugarParameter> { new("@jhdbh", input.DsNum) });
+        return new { message = "交货单关闭成功" };
+    }
+
+    private static string BuildOrderBy(string? sortField, string? sortOrder)
+    {
+        var map = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
+        {
+            ["sffb"] = "t.sffb",
+            ["wlbm"] = "t.wlbm",
+            ["wlms"] = "t.wlms",
+            ["buyer"] = "t.buyer",
+            ["gysdm"] = "t.gysdm",
+            ["gysmc"] = "t.gysmc",
+            ["cgdd"] = "t.cgdd",
+            ["ddhh"] = "t.ddhh",
+            ["jhdsl"] = "t.jhdsl",
+            ["dfhsl"] = "t.dfhsl",
+            ["dsnum"] = "t.dsnum",
+            ["requestdate"] = "t.requestdate",
+            ["schedqty"] = "t.schedqty",
+            ["jqhfnew"] = "t.jqhfnew",
+            ["ztsl"] = "t.ztsl",
+            ["zsl1"] = "t.zsl1",
+            ["rksl"] = "t.rksl",
+            ["bhgsl"] = "t.bhgsl",
+            ["thsl"] = "t.thsl",
+            ["bz"] = "t.bz"
+        };
+        var field = map.TryGetValue(sortField ?? string.Empty, out var sqlField) ? sqlField : "t.requestdate";
+        var order = string.Equals(sortOrder, "asc", StringComparison.OrdinalIgnoreCase) ? "ASC" : "DESC";
+        return $" ORDER BY {field} {order} ";
+    }
+
+    private static string BuildBaseSql() => """
+        SELECT
+            v.*,
+            CASE
+                WHEN v.jhdsl - IFNULL(v.zfhl, 0) + IFNULL(v.thsl, 0) > v.wjhsl THEN v.wjhsl
+                ELSE v.jhdsl - IFNULL(v.zfhl, 0) + IFNULL(v.thsl, 0)
+            END AS dfhsl,
+            CASE
+                WHEN IFNULL(v.zsl18, 0) - IFNULL(v.thsl, 0) <= IFNULL(v.zshl, 0) THEN 0
+                ELSE IFNULL(v.zsl18, 0) - IFNULL(v.thsl, 0) - IFNULL(v.zshl, 0)
+            END AS ztsl,
+            IFNULL(v.zsl, 0) AS zsl1,
+            v.thsl AS bhgsl,
+            CONCAT(
+                CASE
+                    WHEN v.sfyqnew = 'wait' OR v.sfyqnew = 'NO' THEN 'jqhfnew:yellow'
+                    WHEN v.sfyqnew = 'refuse' THEN 'jqhfnew:red'
+                    WHEN v.sfyqnew = 'OK' THEN 'jqhfnew:#99FF00'
+                    ELSE ''
+                END,
+                v.sffbgrdnew
+            ) AS background,
+            CASE
+                WHEN v.jhdsl - IFNULL(v.zfhl, 0) + IFNULL(v.thsl, 0) <= 0 THEN '完成'
+                ELSE '待交'
+            END AS yjzt
+        FROM
+        (
+            SELECT
+                p.PurOrd,
+                p.Line,
+                p.ItemNum,
+                s.fhsl - IFNULL(dor.thsl, 0) AS zfhl,
+                p.ReceiptQty + p.RctQty AS zshl,
+                i.zjsl AS zsl,
+                CASE WHEN p.PurOrd LIKE 'DO%' THEN p.RctQty ELSE p.RctQty - IFNULL(i.zjsl, 0) END AS rksl,
+                CASE
+                    WHEN p.PurOrd LIKE 'DO%' THEN IF(p.QtyReturned > 0, p.QtyReturned, dor.thsl)
+                    ELSE IF(p.QtyReturned > 0, p.QtyReturned, p.QtyReturned + dor.thsl)
+                END AS thsl,
+                s.fhsl AS zsl18,
+                sh.jhdyj,
+                jy.jhdzj,
+                IFNULL(ds.SchedQty, 0) - IFNULL(sh.jhdyj, 0) AS jhddj,
+                p.QtyOrded - p.RctQty - p.ReceiptQty + p.QtyReturned AS wjhsl,
+                p.QtyOrded AS jhdsl,
+                ds.SchedQty,
+                ds.DSNum,
+                CASE WHEN b2.id IS NULL THEN 'X' ELSE b2.hfsj END AS sffb,
+                p.ItemNum AS wlbm,
+                im.Descr AS wlms,
+                ds.supplier AS gysmc,
+                p.PurOrd AS cgdd,
+                p.Line AS ddhh,
+                pm.Buyer AS buyer,
+                IFNULL(ds.requestDate, p.DueDate) AS requestdate,
+                IFNULL(b.jqhf, '') AS jqhfnew,
+                IFNULL(b.fpjh, '') AS fpjhnew,
+                ds.SentQty,
+                IFNULL(im.Drawing, p.Drawing) AS th,
+                IFNULL(im.Rev, p.Rev) AS bbh,
+                IFNULL(ds.id, p.RecID) AS id,
+                '' AS jhd,
+                ds.suppliercode AS gysdm,
+                CASE WHEN IFNULL(ds.id, '') = '' THEN p.Remarks ELSE ds.Remarks END AS bz,
+                '' AS shd,
+                ds.id AS jhdid,
+                p.DueDate AS jhrq,
+                p.RecID AS polid,
+                im.Descr1 AS wlgg,
+                CASE WHEN b2.id IS NULL THEN '' ELSE ';sffb:#99FF00' END AS sffbgrdnew,
+                CASE
+                    WHEN IFNULL(b.jqhf, '') = '' THEN 'wait'
+                    WHEN TIMESTAMPDIFF(DAY, IFNULL(ds.requestDate, p.DueDate), b.jqhf) <= 2 THEN 'OK'
+                    WHEN TIMESTAMPDIFF(DAY, IFNULL(ds.requestDate, p.DueDate), b.jqhf) > 2 THEN 'NO'
+                    WHEN b.jqhf = '' AND IFNULL(b.qhdj, '') <> '' THEN 'refuse'
+                    ELSE 'wait'
+                END AS sfyqnew
+            FROM PurOrdDetail p
+            LEFT JOIN PurOrdMaster pm ON p.PurOrd = pm.PurOrd
+            LEFT JOIN (
+                SELECT SUM(sh_delivery_quantity) AS fhsl, po_bill, po_billline
+                FROM scm_shdzb
+                GROUP BY po_bill, po_billline
+            ) s ON s.po_bill = p.PurOrd AND s.po_billline = p.Line
+            LEFT JOIN (
+                SELECT SUM(Qty) AS zjsl, PurOrd, PurLine
+                FROM MissedPrint
+                WHERE Status = 'I'
+                GROUP BY PurOrd, PurLine
+            ) i ON i.PurOrd = p.PurOrd AND i.PurLine = p.Line
+            LEFT JOIN (
+                SELECT SUM(QtyReturn) AS thsl, OrdNbr, OrdLine
+                FROM PurOrdRctDetail
+                WHERE rcttype IN ('pt', 'temp')
+                GROUP BY OrdNbr, OrdLine
+            ) dor ON dor.OrdNbr = p.PurOrd AND dor.OrdLine = p.Line
+            LEFT JOIN srm_polist_ds ds ON p.PurOrd = ds.ponumber AND p.Line = ds.poline
+            LEFT JOIN (
+                SELECT SUM(sh_delivery_quantity) AS jhdyj, jhdbh
+                FROM scm_shdzb
+                GROUP BY jhdbh
+            ) sh ON ds.dsnum = sh.jhdbh
+            LEFT JOIN (
+                SELECT SUM(IFNULL(Qty, 0)) AS jhdzj, PurOrdDetBatchNbr
+                FROM MissedPrint
+                WHERE Status = 'I'
+                GROUP BY PurOrdDetBatchNbr
+            ) jy ON ds.dsnum = jy.PurOrdDetBatchNbr
+            LEFT JOIN (
+                SELECT glid1, MIN(id) AS id, MIN(DATE_FORMAT(hfsj, '%Y.%m.%d')) AS hfsj
+                FROM scm_jhjh_jq
+                WHERE flag = 2
+                GROUP BY glid1
+            ) b2 ON ds.id = b2.glid1
+            LEFT JOIN ItemMaster im ON p.ItemNum = im.ItemNum
+            LEFT JOIN (
+                SELECT
+                    glid,
+                    GROUP_CONCAT(CONCAT(CAST(jhdsl AS CHAR(10)), '(', DATE_FORMAT(jqhf, '%Y-%m-%d'), ')') SEPARATOR '<br>') AS fpjh,
+                    GROUP_CONCAT(qhdj SEPARATOR '') AS qhdj,
+                    MAX(jqhf) AS jqhf
+                FROM scm_jhjh_jq
+                WHERE flag = 0
+                GROUP BY glid
+            ) b ON ds.id = b.glid
+            WHERE p.Status <> 'C'
+              AND ds.schedqty >= 0
+              AND ds.isactive = 1
+              AND ds.status = 'P'
+        ) v
+        """;
+
+    private sealed class SupplierDeliveryListRow
+    {
+        public string? Sffb { get; set; }
+        public string? Wlbm { get; set; }
+        public string? Wlms { get; set; }
+        public string? Buyer { get; set; }
+        public string? Gysdm { get; set; }
+        public string? Gysmc { get; set; }
+        public string? Cgdd { get; set; }
+        public int? Ddhh { get; set; }
+        public decimal? Jhdsl { get; set; }
+        public decimal? Dfhsl { get; set; }
+        public string? Dsnum { get; set; }
+        public DateTime? Requestdate { get; set; }
+        public decimal? Schedqty { get; set; }
+        public string? Jqhfnew { get; set; }
+        public decimal? Ztsl { get; set; }
+        public decimal? Zsl1 { get; set; }
+        public decimal? Rksl { get; set; }
+        public decimal? Bhgsl { get; set; }
+        public decimal? Thsl { get; set; }
+        public string? Bz { get; set; }
+        public string? Id { get; set; }
+        public decimal? Wjhsl { get; set; }
+        public decimal? Zfhl { get; set; }
+    }
+}
+

+ 531 - 0
server/Plugins/Admin.NET.Plugin.AiDOP/ProcurementExecution/SupplierShipmentService.cs

@@ -0,0 +1,531 @@
+using Admin.NET.Plugin.AiDOP.ProcurementExecution.Dto;
+using Admin.NET.Plugin.AiDOP.ProcurementExecution.Entity;
+using Yitter.IdGenerator;
+
+namespace Admin.NET.Plugin.AiDOP.ProcurementExecution;
+
+/// <summary>
+/// S4 供应商发货单(列表 + 表单)
+/// </summary>
+[ApiDescriptionSettings(Order = 321, Description = "S4供应商发货单")]
+[Route("api/ProcurementExecution")]
+[AllowAnonymous]
+[NonUnify]
+public class SupplierShipmentService : IDynamicApiController, ITransient
+{
+    private readonly ISqlSugarClient _db;
+    private readonly SqlSugarRepository<ScmShd> _masterRep;
+    private readonly SqlSugarRepository<ScmShdzb> _detailRep;
+    private readonly UserManager _userManager;
+
+    public SupplierShipmentService(
+        ISqlSugarClient db,
+        SqlSugarRepository<ScmShd> masterRep,
+        SqlSugarRepository<ScmShdzb> detailRep,
+        UserManager userManager)
+    {
+        _db = db;
+        _masterRep = masterRep;
+        _detailRep = detailRep;
+        _userManager = userManager;
+    }
+
+    [DisplayName("供应商发货单列表")]
+    [HttpGet("supplier-shipment/list")]
+    public async Task<object> GetList([FromQuery] SupplierShipmentListInput input)
+    {
+        var pars = new List<SugarParameter>();
+        var conditions = new List<string> { "IFNULL(m.state, 1) <> 0" };
+        if (!string.IsNullOrWhiteSpace(input.JhshrqFrom))
+        {
+            conditions.Add("m.jhshrq >= @jhshrqFrom");
+            pars.Add(new SugarParameter("@jhshrqFrom", input.JhshrqFrom.Trim()));
+        }
+        if (!string.IsNullOrWhiteSpace(input.Gysmc))
+        {
+            conditions.Add("m.sh_purchase_name LIKE @gysmc");
+            pars.Add(new SugarParameter("@gysmc", $"%{input.Gysmc.Trim()}%"));
+        }
+        if (!string.IsNullOrWhiteSpace(input.Shddh))
+        {
+            conditions.Add("m.shddh LIKE @shddh");
+            pars.Add(new SugarParameter("@shddh", $"%{input.Shddh.Trim()}%"));
+        }
+        if (!string.IsNullOrWhiteSpace(input.Wldh))
+        {
+            conditions.Add("m.wldh LIKE @wldh");
+            pars.Add(new SugarParameter("@wldh", $"%{input.Wldh.Trim()}%"));
+        }
+        if (!string.IsNullOrWhiteSpace(input.PoBill))
+        {
+            conditions.Add("m.po_bill LIKE @poBill");
+            pars.Add(new SugarParameter("@poBill", $"%{input.PoBill.Trim()}%"));
+        }
+        if (!string.IsNullOrWhiteSpace(input.ShMaterialCode))
+        {
+            conditions.Add("m.sh_material_code LIKE @shMaterialCode");
+            pars.Add(new SugarParameter("@shMaterialCode", $"%{input.ShMaterialCode.Trim()}%"));
+        }
+        if (!string.IsNullOrWhiteSpace(input.Shzt))
+        {
+            conditions.Add("m.shzt = @shzt");
+            pars.Add(new SugarParameter("@shzt", input.Shzt.Trim()));
+        }
+        if (!string.IsNullOrWhiteSpace(input.Shpc))
+        {
+            conditions.Add("m.shpc LIKE @shpc");
+            pars.Add(new SugarParameter("@shpc", $"%{input.Shpc.Trim()}%"));
+        }
+
+        var where = $" WHERE {string.Join(" AND ", conditions)} ";
+        var orderBy = BuildListOrderBy(input.SortField, input.SortOrder);
+        var offset = (input.Page - 1) * input.PageSize;
+        var wrapped = $"{BuildListSql()} {where}";
+
+        var total = await _db.Ado.GetIntAsync($"SELECT COUNT(*) FROM ({wrapped}) t", pars);
+        var list = await _db.Ado.SqlQueryAsync<SupplierShipmentListRow>(
+            $"SELECT * FROM ({wrapped}) t {orderBy} LIMIT {input.PageSize} OFFSET {offset}", pars);
+
+        return new { total, page = input.Page, pageSize = input.PageSize, list };
+    }
+
+    [DisplayName("获取发货单详情")]
+    [HttpGet("supplier-shipment/{id:long}")]
+    public async Task<object> GetDetail(long id)
+    {
+        var master = await _masterRep.GetFirstAsync(x => x.Id == id) ?? throw Oops.Oh("发货单不存在");
+        var details = await _detailRep.AsQueryable().Where(x => x.Glid == id.ToString()).OrderBy(x => x.Hh).ToListAsync();
+        return new
+        {
+            id = master.Id,
+            shddh = master.Shddh,
+            jhshrq = master.Jhshrq,
+            wlsc = master.Wlsc,
+            yjdhrq = master.Yjdhrq,
+            shPurchaseName = master.ShPurchaseName,
+            shPurchaseNum = master.ShPurchaseNum,
+            wldh = master.Wldh,
+            sfpc = master.Sfpc ?? 0,
+            chbg = master.Chbg,
+            pcsm = master.Pcsm,
+            state = master.State ?? 1,
+            details = details.Select(d => new
+            {
+                id = d.Id,
+                hh = d.Hh,
+                poBill = d.PoBill,
+                poBillLine = d.PoBillLine,
+                orderType = d.OrderType,
+                shMaterialCode = d.ShMaterialCode,
+                shMaterialName = d.ShMaterialName,
+                th = d.Th,
+                shDeliveryQuantity = d.ShDeliveryQuantity,
+                bzsl = d.Bzsl,
+                bqsl = d.Bqsl,
+                shMaterialDw = d.ShMaterialDw,
+                scrq = d.Scrq,
+                scph = d.Scph,
+                remarks = d.Remarks,
+                djsl = d.Djsl,
+                jybb = d.Jybb,
+                jhdbh = d.Jhdbh
+            })
+        };
+    }
+
+    [DisplayName("发货单新增草稿")]
+    [HttpGet("supplier-shipment/create-draft")]
+    public async Task<object> GetCreateDraft([FromQuery] string ids)
+    {
+        var pars = new List<SugarParameter> { new("@ids", ids) };
+        var rows = await _db.Ado.SqlQueryAsync<SupplierShipmentDraftRow>(
+            """
+            SELECT
+                CAST(IFNULL(ds.id, p.RecID) AS CHAR(50)) AS sourceId,
+                ds.suppliercode AS gysdm,
+                ds.supplier AS gysmc,
+                p.PurOrd AS poBill,
+                p.Line AS poBillLine,
+                p.Potype AS orderType,
+                p.ItemNum AS shMaterialCode,
+                im.Descr AS shMaterialName,
+                IFNULL(im.Drawing, p.Drawing) AS th,
+                IFNULL(ds.SchedQty, p.QtyOrded - p.RctQty - p.ReceiptQty + p.QtyReturned) AS shDeliveryQuantity,
+                p.StdPackQty AS bzsl,
+                p.UM AS shMaterialDw,
+                (p.QtyOrded - p.RctQty - p.ReceiptQty + p.QtyReturned) AS djsl,
+                ds.DSNum AS jhdbh,
+                im.Descr1 AS shMaterialGgxh
+            FROM PurOrdDetail p
+            LEFT JOIN srm_polist_ds ds ON p.PurOrd = ds.ponumber AND p.Line = ds.poline
+            LEFT JOIN ItemMaster im ON p.ItemNum = im.ItemNum
+            WHERE FIND_IN_SET(CAST(IFNULL(ds.id, p.RecID) AS CHAR(50)), REPLACE(IFNULL(@ids, ''), ' ', '')) > 0
+            ORDER BY p.PurOrd, p.Line
+            """, pars);
+
+        if (rows.Count == 0)
+            throw Oops.Oh("未找到可生成的交货明细");
+
+        return new
+        {
+            shddh = string.Empty,
+            jhshrq = DateTime.Now.ToString("yyyy-MM-dd"),
+            wlsc = string.Empty,
+            yjdhrq = string.Empty,
+            shPurchaseName = rows[0].Gysmc,
+            shPurchaseNum = rows[0].Gysdm,
+            wldh = string.Empty,
+            sfpc = 0,
+            chbg = string.Empty,
+            pcsm = string.Empty,
+            details = rows.Select((r, i) => new
+            {
+                id = (long?)null,
+                hh = i + 1,
+                poBill = r.PoBill,
+                poBillLine = r.PoBillLine?.ToString(),
+                orderType = r.OrderType,
+                shMaterialCode = r.ShMaterialCode,
+                shMaterialName = r.ShMaterialName,
+                th = r.Th,
+                shDeliveryQuantity = r.ShDeliveryQuantity,
+                bzsl = r.Bzsl,
+                bqsl = 0,
+                shMaterialDw = r.ShMaterialDw,
+                scrq = string.Empty,
+                scph = string.Empty,
+                remarks = string.Empty,
+                djsl = r.Djsl,
+                jybb = string.Empty,
+                jhdbh = r.Jhdbh
+            })
+        };
+    }
+
+    [DisplayName("保存发货单")]
+    [ApiDescriptionSettings(Name = "SaveSupplierShipment"), HttpPost("supplier-shipment/save")]
+    public async Task<object> Save([FromBody] SupplierShipmentSaveInput input)
+    {
+        var now = DateTime.Now;
+        var userId = _userManager.UserId.ToString();
+        var userName = _userManager.Account ?? "system";
+        var shipDate = string.IsNullOrWhiteSpace(input.Jhshrq) ? now.ToString("yyyy-MM-dd") : input.Jhshrq!.Trim();
+
+        if (input.Id is null or 0)
+        {
+            var newId = YitIdHelper.NextId();
+            var entity = new ScmShd
+            {
+                Id = newId,
+                Shddh = string.IsNullOrWhiteSpace(input.Shddh) ? $"SH{DateTime.Now:yyyyMMddHHmmss}" : input.Shddh!.Trim(),
+                Jhshrq = shipDate,
+                Wlsc = input.Wlsc,
+                Yjdhrq = input.Yjdhrq,
+                ShPurchaseName = input.ShPurchaseName,
+                ShPurchaseNum = input.ShPurchaseNum,
+                Wldh = input.Wldh,
+                Sfpc = input.Sfpc ?? 0,
+                Chbg = input.Chbg,
+                Pcsm = input.Pcsm,
+                State = 1,
+                Shzt = "待收",
+                Tjrid = userId,
+                Tjrxm = userName,
+                Tjrq = now.ToString("yyyy-MM-dd")
+            };
+            await _masterRep.InsertAsync(entity);
+            await SaveDetailsAsync(newId, input.Details);
+            return new { id = newId, message = "新增成功" };
+        }
+
+        var master = await _masterRep.GetFirstAsync(x => x.Id == input.Id.Value) ?? throw Oops.Oh("发货单不存在");
+        master.Shddh = input.Shddh;
+        master.Jhshrq = shipDate;
+        master.Wlsc = input.Wlsc;
+        master.Yjdhrq = input.Yjdhrq;
+        master.ShPurchaseName = input.ShPurchaseName;
+        master.ShPurchaseNum = input.ShPurchaseNum;
+        master.Wldh = input.Wldh;
+        master.Sfpc = input.Sfpc ?? 0;
+        master.Chbg = input.Chbg;
+        master.Pcsm = input.Pcsm;
+        await _masterRep.UpdateAsync(master);
+        await SaveDetailsAsync(input.Id.Value, input.Details);
+        return new { id = input.Id, message = "编辑成功" };
+    }
+
+    [DisplayName("删除发货单")]
+    [ApiDescriptionSettings(Name = "DeleteSupplierShipment"), HttpPost("supplier-shipment/delete")]
+    public async Task<object> Delete([FromBody] SupplierShipmentDeleteInput input)
+    {
+        var master = await _masterRep.GetFirstAsync(x => x.Id == input.Id) ?? throw Oops.Oh("发货单不存在");
+        master.State = 0;
+        await _masterRep.UpdateAsync(master);
+        return new { message = "删除成功" };
+    }
+
+    [DisplayName("生成标签(预留)")]
+    [HttpPost("supplier-shipment/generate-label")]
+    public Task<object> GenerateLabel([FromBody] SupplierShipmentOperationInput input)
+        => Task.FromResult<object>(new { message = $"发货单{input.Id}:功能预留,暂未启用" });
+
+    [DisplayName("打印送货单(预留)")]
+    [HttpPost("supplier-shipment/print-shipping-note")]
+    public Task<object> PrintShippingNote([FromBody] SupplierShipmentOperationInput input)
+        => Task.FromResult<object>(new { message = $"发货单{input.Id}:功能预留,暂未启用" });
+
+    [DisplayName("打印标签(预留)")]
+    [HttpPost("supplier-shipment/print-label")]
+    public Task<object> PrintLabel([FromBody] SupplierShipmentOperationInput input)
+        => Task.FromResult<object>(new { message = $"发货单{input.Id}:功能预留,暂未启用" });
+
+    private async Task SaveDetailsAsync(long masterId, List<SupplierShipmentDetailInput> inputDetails)
+    {
+        var dbDetails = await _detailRep.AsQueryable().Where(x => x.Glid == masterId.ToString()).ToListAsync();
+        var dbById = dbDetails.ToDictionary(x => x.Id);
+        var inputIds = new HashSet<long>(inputDetails.Where(d => d.Id is > 0).Select(d => d.Id!.Value));
+
+        for (var i = 0; i < inputDetails.Count; i++)
+        {
+            var d = inputDetails[i];
+            if (d.Id is > 0 && dbById.TryGetValue(d.Id.Value, out var existing))
+            {
+                existing.Hh = d.Hh ?? (i + 1);
+                existing.PoBill = d.PoBill;
+                existing.PoBillLine = d.PoBillLine;
+                existing.OrderType = d.OrderType;
+                existing.ShMaterialCode = d.ShMaterialCode;
+                existing.ShMaterialName = d.ShMaterialName;
+                existing.Th = d.Th;
+                existing.ShDeliveryQuantity = d.ShDeliveryQuantity;
+                existing.Bzsl = d.Bzsl;
+                existing.Bqsl = d.Bqsl;
+                existing.ShMaterialDw = d.ShMaterialDw;
+                existing.Scrq = d.Scrq;
+                existing.Scph = d.Scph;
+                existing.Remarks = d.Remarks;
+                existing.Djsl = d.Djsl;
+                existing.Jybb = d.Jybb;
+                existing.Jhdbh = d.Jhdbh;
+                await _detailRep.UpdateAsync(existing);
+            }
+            else
+            {
+                var detail = new ScmShdzb
+                {
+                    Id = YitIdHelper.NextId(),
+                    Glid = masterId.ToString(),
+                    Hh = d.Hh ?? (i + 1),
+                    PoBill = d.PoBill,
+                    PoBillLine = d.PoBillLine,
+                    OrderType = d.OrderType,
+                    ShMaterialCode = d.ShMaterialCode,
+                    ShMaterialName = d.ShMaterialName,
+                    Th = d.Th,
+                    ShDeliveryQuantity = d.ShDeliveryQuantity,
+                    Bzsl = d.Bzsl,
+                    Bqsl = d.Bqsl,
+                    ShMaterialDw = d.ShMaterialDw,
+                    Scrq = d.Scrq,
+                    Scph = d.Scph,
+                    Remarks = d.Remarks,
+                    Djsl = d.Djsl,
+                    Jybb = d.Jybb,
+                    Jhdbh = d.Jhdbh
+                };
+                await _detailRep.InsertAsync(detail);
+            }
+        }
+
+        foreach (var old in dbDetails.Where(x => !inputIds.Contains(x.Id)))
+        {
+            await _detailRep.DeleteAsync(x => x.Id == old.Id);
+        }
+    }
+
+    private static string BuildListOrderBy(string? sortField, string? sortOrder)
+    {
+        var map = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
+        {
+            ["shddh"] = "t.shddh",
+            ["poBill"] = "t.po_bill",
+            ["jhshrq"] = "t.jhshrq",
+            ["shMaterialCode"] = "t.sh_material_code",
+            ["shMaterialName"] = "t.sh_material_name",
+            ["shDeliveryQuantity"] = "t.sh_delivery_quantity",
+            ["sfpc"] = "t.sfpc",
+            ["pcrksl"] = "t.pcrksl",
+            ["shPurchaseName"] = "t.sh_purchase_name",
+            ["shpc"] = "t.shpc",
+            ["scph"] = "t.scph",
+            ["wldh"] = "t.wldh",
+            ["dycs"] = "t.dycs",
+            ["shzt"] = "t.shzt"
+        };
+        var field = map.TryGetValue(sortField ?? string.Empty, out var sqlField) ? sqlField : "t.mid";
+        var order = string.Equals(sortOrder, "asc", StringComparison.OrdinalIgnoreCase) ? "ASC" : "DESC";
+        return $" ORDER BY {field} {order} ";
+    }
+
+    private static string BuildListSql() => """
+        SELECT
+            m.mid,
+            m.id,
+            m.sh_purchase_id,
+            m.sh_purchase_name,
+            m.sh_purchase_num,
+            m.sh_purchase_address,
+            m.sh_purchase_lxr,
+            m.sh_purchase_phone,
+            m.client,
+            m.delivery_Address,
+            m.expected_consignee,
+            m.consignee_phone,
+            m.estimated_delivery_date,
+            m.po_billno,
+            m.shddh,
+            m.jhshrq,
+            m.tjrid,
+            m.tjrxm,
+            m.tjrq,
+            m.scbq,
+            m.chbg,
+            m.sfpc,
+            m.pcsm,
+            m.wlsc,
+            m.yjdhrq,
+            m.state,
+            m.shzt,
+            m.wldh,
+            m.dycs,
+            m.gys,
+            m.sh_material_code,
+            m.sh_material_name,
+            m.sh_material_ggxh,
+            m.hh,
+            m.po_bill,
+            m.shpc,
+            m.scph,
+            m.sh_delivery_quantity,
+            m.th,
+            n.rksl,
+            n.bhgsl,
+            n.thsl,
+            n.zshl,
+            n.Delivery,
+            n.pc,
+            l.pcrksl,
+            po.potype,
+            po.Usage
+        FROM
+        (
+            SELECT
+                a.id mid,
+                b.id,
+                a.sh_purchase_id,
+                a.sh_purchase_name,
+                a.sh_purchase_num,
+                a.sh_purchase_address,
+                a.sh_purchase_lxr,
+                a.sh_purchase_phone,
+                a.client,
+                a.delivery_Address,
+                a.expected_consignee,
+                a.consignee_phone,
+                a.estimated_delivery_date,
+                a.po_billno,
+                a.shddh,
+                a.jhshrq,
+                a.tjrid,
+                a.tjrxm,
+                a.tjrq,
+                a.scbq,
+                a.chbg,
+                a.sfpc,
+                a.pcsm,
+                a.wlsc,
+                a.yjdhrq,
+                a.state,
+                IFNULL(a.shzt, '待收') shzt,
+                a.wldh,
+                a.dycs,
+                CONCAT(IFNULL(a.sh_purchase_num, ''), IFNULL(a.sh_purchase_name, '')) gys,
+                b.sh_material_code,
+                b.sh_material_name,
+                b.sh_material_ggxh,
+                b.hh,
+                b.po_bill,
+                c.shpc,
+                b.scph,
+                b.sh_delivery_quantity,
+                b.th
+            FROM scm_shd a
+            LEFT JOIN scm_shdzb b ON a.id = b.glid
+            LEFT JOIN (SELECT DISTINCT glid, shpc FROM scm_shbq) c ON b.id = c.glid
+        ) m
+        LEFT JOIN
+        (
+            SELECT
+                Delivery,
+                LotSerial pc,
+                SUM(ReceiptQty) zshl,
+                AVG(yssl) rksl,
+                SUM(QtyReturn) bhgsl,
+                SUM(QtyReturned) thsl
+            FROM vscm_cgshrk
+            GROUP BY Delivery, LotSerial
+        ) n ON m.shddh = n.Delivery AND m.shpc = n.pc
+        LEFT JOIN
+        (
+            SELECT LotSerial, SUM(QtyChange) pcrksl
+            FROM InvTransHist
+            WHERE QtyChange > 0 AND Reason LIKE '%收货'
+            GROUP BY LotSerial
+        ) l ON m.shpc = l.LotSerial
+        LEFT JOIN PurOrdMaster po ON m.po_bill = po.purord
+        """;
+
+    private sealed class SupplierShipmentListRow
+    {
+        public long Mid { get; set; }
+        public long Id { get; set; }
+        public string? ShPurchaseName { get; set; }
+        public string? ShPurchaseNum { get; set; }
+        public string? Shddh { get; set; }
+        public string? Jhshrq { get; set; }
+        public int? Sfpc { get; set; }
+        public string? Wldh { get; set; }
+        public int? Dycs { get; set; }
+        public string? Shzt { get; set; }
+        public string? ShMaterialCode { get; set; }
+        public string? ShMaterialName { get; set; }
+        public decimal? ShDeliveryQuantity { get; set; }
+        public decimal? Pcrksl { get; set; }
+        public string? PoBill { get; set; }
+        public string? Usage { get; set; }
+        public string? Shpc { get; set; }
+        public string? Scph { get; set; }
+        public string? Th { get; set; }
+        public int? State { get; set; }
+    }
+
+    private sealed class SupplierShipmentDraftRow
+    {
+        public string? SourceId { get; set; }
+        public string? Gysdm { get; set; }
+        public string? Gysmc { get; set; }
+        public string? PoBill { get; set; }
+        public int? PoBillLine { get; set; }
+        public string? OrderType { get; set; }
+        public string? ShMaterialCode { get; set; }
+        public string? ShMaterialName { get; set; }
+        public string? Th { get; set; }
+        public decimal? ShDeliveryQuantity { get; set; }
+        public decimal? Bzsl { get; set; }
+        public string? ShMaterialDw { get; set; }
+        public decimal? Djsl { get; set; }
+        public string? Jhdbh { get; set; }
+        public string? ShMaterialGgxh { get; set; }
+    }
+}
+

+ 1 - 1
server/Plugins/Admin.NET.Plugin.AiDOP/Production/ExecutableDailyPlanService.cs

@@ -232,7 +232,7 @@ public class ExecutableDailyPlanService : IDynamicApiController, ITransient
         LEFT JOIN ItemMaster i ON p.ItemNum = i.ItemNum AND p.`Domain` = i.`Domain`
         LEFT JOIN ItemMaster i ON p.ItemNum = i.ItemNum AND p.`Domain` = i.`Domain`
         LEFT JOIN WorkOrdMaster w ON p.WorkOrds = w.WorkOrd AND p.`Domain` = w.`Domain`
         LEFT JOIN WorkOrdMaster w ON p.WorkOrds = w.WorkOrd AND p.`Domain` = w.`Domain`
         LEFT JOIN WorkOrdRouting r ON p.Op = r.OP AND p.ItemNum = r.ItemNum AND p.WorkOrds = r.WorkOrd
         LEFT JOIN WorkOrdRouting r ON p.Op = r.OP AND p.ItemNum = r.ItemNum AND p.WorkOrds = r.WorkOrd
-        LEFT JOIN mes_morder m ON w.WorkOrd = m.morder_no AND w.`Domain` = m.factory_id
+        LEFT JOIN mes_morder m ON w.WorkOrd = m.morder_no AND CAST(w.`Domain` AS CHAR(64)) = CAST(m.factory_id AS CHAR(64))
         LEFT JOIN ProdLineDetail pd ON p.`Domain` = pd.`Domain` AND p.ItemNum = pd.Part AND p.Line = pd.Line AND p.Op = pd.Op
         LEFT JOIN ProdLineDetail pd ON p.`Domain` = pd.`Domain` AND p.ItemNum = pd.Part AND p.Line = pd.Line AND p.Op = pd.Op
         LEFT JOIN ScheduleResultOpMaster sm ON sm.`Domain` = p.`Domain` AND p.UDate2 = sm.WorkActivateTime AND p.ItemNum = sm.ItemNum AND sm.WorkOrd = p.WorkOrds AND sm.Op = p.Op
         LEFT JOIN ScheduleResultOpMaster sm ON sm.`Domain` = p.`Domain` AND p.UDate2 = sm.WorkActivateTime AND p.ItemNum = sm.ItemNum AND sm.WorkOrd = p.WorkOrds AND sm.Op = p.Op
         LEFT JOIN WorkCtrMaster wm ON wm.WorkCtr = sm.WorkCtr AND wm.`Domain` = sm.`Domain`
         LEFT JOIN WorkCtrMaster wm ON wm.WorkCtr = sm.WorkCtr AND wm.`Domain` = sm.`Domain`

+ 61 - 0
server/Plugins/Admin.NET.Plugin.AiDOP/SeedData/SysMenuSeedData.cs

@@ -100,6 +100,8 @@ public class SysMenuSeedData : ISqlSugarEntitySeedData<SysMenu>
             list.Add(m);
             list.Add(m);
         foreach (var m in BuildS3SupplyMenus(ct))
         foreach (var m in BuildS3SupplyMenus(ct))
             list.Add(m);
             list.Add(m);
+        foreach (var m in BuildS4DeliveryMenus(ct))
+            list.Add(m);
         foreach (var m in BuildS1SalesKanbanMenus(ct))
         foreach (var m in BuildS1SalesKanbanMenus(ct))
             list.Add(m);
             list.Add(m);
         foreach (var m in BuildS8CollaborationMenus(ct))
         foreach (var m in BuildS8CollaborationMenus(ct))
@@ -135,6 +137,9 @@ public class SysMenuSeedData : ISqlSugarEntitySeedData<SysMenu>
             s3Procurement.Redirect = "/aidop/s3/procurement/purchase-request";
             s3Procurement.Redirect = "/aidop/s3/procurement/purchase-request";
             s3Procurement.Remark = "S3 采购管理";
             s3Procurement.Remark = "S3 采购管理";
         }
         }
+        var s4Delivery = list.FirstOrDefault(x => x.Id == 1322000000012L);
+        if (s4Delivery != null)
+            s4Delivery.Redirect = "/aidop/s4/delivery/supplier-delivery-management";
 
 
         // S8:复用自动生成的首项菜单位,直接作为「异常监控看板」页,避免再出现中间层「异常管理」目录
         // S8:复用自动生成的首项菜单位,直接作为「异常监控看板」页,避免再出现中间层「异常管理」目录
         var s8Dashboard = list.FirstOrDefault(x => x.Id == 1322000000027L);
         var s8Dashboard = list.FirstOrDefault(x => x.Id == 1322000000027L);
@@ -241,6 +246,7 @@ public class SysMenuSeedData : ISqlSugarEntitySeedData<SysMenu>
         { ("S2", 2), "/aidop/s2/operation-plan" },
         { ("S2", 2), "/aidop/s2/operation-plan" },
         { ("S2", 3), "/aidop/s2/collaboration-kanban" },
         { ("S2", 3), "/aidop/s2/collaboration-kanban" },
         { ("S3", 1), "/aidop/s3/material-plan" },
         { ("S3", 1), "/aidop/s3/material-plan" },
+        { ("S4", 2), "/aidop/s4/delivery" },
     };
     };
 
 
     /// <summary>S1 等模块下叶子菜单 route name 覆盖。</summary>
     /// <summary>S1 等模块下叶子菜单 route name 覆盖。</summary>
@@ -252,6 +258,7 @@ public class SysMenuSeedData : ISqlSugarEntitySeedData<SysMenu>
         { ("S2", 2), "aidopS2OperationPlan" },
         { ("S2", 2), "aidopS2OperationPlan" },
         { ("S2", 3), "aidopS2CollaborationKanban" },
         { ("S2", 3), "aidopS2CollaborationKanban" },
         { ("S3", 1), "aidopS3MaterialPlan" },
         { ("S3", 1), "aidopS3MaterialPlan" },
+        { ("S4", 2), "aidopS4Delivery" },
     };
     };
 
 
     /// <summary>
     /// <summary>
@@ -267,8 +274,62 @@ public class SysMenuSeedData : ISqlSugarEntitySeedData<SysMenu>
         { ("S2", 2) }, // 作业计划 → 目录(可执行日计划、产线日历等)
         { ("S2", 2) }, // 作业计划 → 目录(可执行日计划、产线日历等)
         { ("S2", 3) }, // 制造协同看板 → 目录(工单执行进度看板)
         { ("S2", 3) }, // 制造协同看板 → 目录(工单执行进度看板)
         { ("S3", 1) }, // 物料计划 → 目录(物料需求计划)
         { ("S3", 1) }, // 物料计划 → 目录(物料需求计划)
+        { ("S4", 2) }, // 交货管理 → 目录(供应商交货管理、供应商发货单)
     };
     };
 
 
+    private static IEnumerable<SysMenu> BuildS4DeliveryMenus(DateTime ct)
+    {
+        const long deliveryDirId = 1322000000012L;
+        const long baseId = 1329004100000L;
+
+        yield return new SysMenu
+        {
+            Id = baseId + 1,
+            Pid = deliveryDirId,
+            Title = "供应商交货管理",
+            Path = "/aidop/s4/delivery/supplier-delivery-management",
+            Name = "aidopS4SupplierDeliveryManagement",
+            Component = "/aidop/s4/delivery/supplierDeliveryManagementList",
+            Icon = "ele-Document",
+            Type = MenuTypeEnum.Menu,
+            CreateTime = ct,
+            OrderNo = 10,
+            Remark = "S4 供应商交货管理"
+        };
+
+        yield return new SysMenu
+        {
+            Id = baseId + 2,
+            Pid = deliveryDirId,
+            Title = "供应商发货单",
+            Path = "/aidop/s4/delivery/supplier-shipment",
+            Name = "aidopS4SupplierShipment",
+            Component = "/aidop/s4/delivery/supplierShipmentList",
+            Icon = "ele-Tickets",
+            Type = MenuTypeEnum.Menu,
+            CreateTime = ct,
+            OrderNo = 20,
+            Remark = "S4 供应商发货单"
+        };
+
+        // 隐藏路由:用于「生成发货单 / 编辑 / 查看」跳转
+        yield return new SysMenu
+        {
+            Id = baseId + 3,
+            Pid = deliveryDirId,
+            Title = "发货单表单",
+            Path = "/aidop/s4/delivery/supplier-shipment-form",
+            Name = "aidopS4SupplierShipmentForm",
+            Component = "/aidop/s4/delivery/supplierShipmentForm",
+            Icon = "ele-Document",
+            Type = MenuTypeEnum.Menu,
+            CreateTime = ct,
+            OrderNo = 90,
+            IsHide = true,
+            Remark = "S4 发货单新增/编辑/查看表单"
+        };
+    }
+
     private static string BuildRemark(string code, (string Title, string Desc, string Complexity, string Days, string Note) leaf)
     private static string BuildRemark(string code, (string Title, string Desc, string Complexity, string Days, string Note) leaf)
     {
     {
         var notePart = string.IsNullOrWhiteSpace(leaf.Note) ? "" : $" | {leaf.Note}";
         var notePart = string.IsNullOrWhiteSpace(leaf.Note) ? "" : $" | {leaf.Note}";

+ 1 - 1
server/Plugins/Admin.NET.Plugin.AiDOP/WorkOrder/Entity/MesMoentry.cs

@@ -59,7 +59,7 @@ public class MesMoentry
     public int? IsDeleted { get; set; }
     public int? IsDeleted { get; set; }
 
 
     [SugarColumn(ColumnName = "factory_id", Length = 64, IsNullable = true)]
     [SugarColumn(ColumnName = "factory_id", Length = 64, IsNullable = true)]
-    public string? FactoryId { get; set; }
+    public long? FactoryId { get; set; }
 
 
     [SugarColumn(ColumnName = "org_id", IsNullable = true)]
     [SugarColumn(ColumnName = "org_id", IsNullable = true)]
     public long? OrgId { get; set; }
     public long? OrgId { get; set; }

+ 4 - 4
server/Plugins/Admin.NET.Plugin.AiDOP/WorkOrder/Entity/MesMorder.cs

@@ -91,8 +91,8 @@ public class MesMorder
     [SugarColumn(ColumnName = "unit", Length = 32, IsNullable = true)]
     [SugarColumn(ColumnName = "unit", Length = 32, IsNullable = true)]
     public string? Unit { get; set; }
     public string? Unit { get; set; }
 
 
-    [SugarColumn(ColumnName = "morder_progress", IsNullable = true)]
-    public decimal? MorderProgress { get; set; }
+    [SugarColumn(ColumnName = "morder_progress", Length = 1000, IsNullable = true)]
+    public string? MorderProgress { get; set; }
 
 
     [SugarColumn(ColumnName = "morder_production_number", IsNullable = true)]
     [SugarColumn(ColumnName = "morder_production_number", IsNullable = true)]
     public decimal? MorderProductionNumber { get; set; }
     public decimal? MorderProductionNumber { get; set; }
@@ -139,8 +139,8 @@ public class MesMorder
     [SugarColumn(ColumnName = "IsDeleted", IsNullable = true)]
     [SugarColumn(ColumnName = "IsDeleted", IsNullable = true)]
     public int? IsDeleted { get; set; }
     public int? IsDeleted { get; set; }
 
 
-    [SugarColumn(ColumnName = "factory_id", Length = 64, IsNullable = true)]
-    public string? FactoryId { get; set; }
+    [SugarColumn(ColumnName = "factory_id", IsNullable = true)]
+    public long? FactoryId { get; set; }
 
 
     [SugarColumn(ColumnName = "org_id", IsNullable = true)]
     [SugarColumn(ColumnName = "org_id", IsNullable = true)]
     public long? OrgId { get; set; }
     public long? OrgId { get; set; }

+ 6 - 6
server/Plugins/Admin.NET.Plugin.AiDOP/WorkOrder/Entity/WorkOrdDetail.cs

@@ -8,11 +8,11 @@ public class WorkOrdDetail
 {
 {
     /// <summary>自增列</summary>
     /// <summary>自增列</summary>
     [SugarColumn(ColumnName = "RecID", IsPrimaryKey = true, IsIdentity = true)]
     [SugarColumn(ColumnName = "RecID", IsPrimaryKey = true, IsIdentity = true)]
-    public long RecID { get; set; }
+    public int RecID { get; set; }
 
 
     /// <summary>工单流水号</summary>
     /// <summary>工单流水号</summary>
     [SugarColumn(ColumnName = "WorkOrdMasterRecID", IsNullable = true)]
     [SugarColumn(ColumnName = "WorkOrdMasterRecID", IsNullable = true)]
-    public long? WorkOrdMasterRecID { get; set; }
+    public long WorkOrdMasterRecID { get; set; }
 
 
     [SugarColumn(ColumnName = "Domain", Length = 8, IsNullable = true)]
     [SugarColumn(ColumnName = "Domain", Length = 8, IsNullable = true)]
     public string? Domain { get; set; }
     public string? Domain { get; set; }
@@ -27,7 +27,7 @@ public class WorkOrdDetail
     public string? ItemNum { get; set; }
     public string? ItemNum { get; set; }
 
 
     [SugarColumn(ColumnName = "Op", Length = 6, IsNullable = true)]
     [SugarColumn(ColumnName = "Op", Length = 6, IsNullable = true)]
-    public string? Op { get; set; }
+    public int? Op { get; set; }
 
 
     [SugarColumn(ColumnName = "Location", Length = 10, IsNullable = true)]
     [SugarColumn(ColumnName = "Location", Length = 10, IsNullable = true)]
     public string? Location { get; set; }
     public string? Location { get; set; }
@@ -42,7 +42,7 @@ public class WorkOrdDetail
     public string? Typed { get; set; }
     public string? Typed { get; set; }
 
 
     [SugarColumn(ColumnName = "QtyRequired", IsNullable = true)]
     [SugarColumn(ColumnName = "QtyRequired", IsNullable = true)]
-    public decimal? QtyRequired { get; set; }
+    public decimal QtyRequired { get; set; }
 
 
     [SugarColumn(ColumnName = "QtyIssued", IsNullable = true)]
     [SugarColumn(ColumnName = "QtyIssued", IsNullable = true)]
     public decimal? QtyIssued { get; set; }
     public decimal? QtyIssued { get; set; }
@@ -51,7 +51,7 @@ public class WorkOrdDetail
     public decimal? QtyPicked { get; set; }
     public decimal? QtyPicked { get; set; }
 
 
     [SugarColumn(ColumnName = "QtyPosted", IsNullable = true)]
     [SugarColumn(ColumnName = "QtyPosted", IsNullable = true)]
-    public decimal? QtyPosted { get; set; }
+    public decimal QtyPosted { get; set; }
 
 
     [SugarColumn(ColumnName = "QtyReturned", IsNullable = true)]
     [SugarColumn(ColumnName = "QtyReturned", IsNullable = true)]
     public decimal? QtyReturned { get; set; }
     public decimal? QtyReturned { get; set; }
@@ -105,7 +105,7 @@ public class WorkOrdDetail
     public DateTime? UpdateTime { get; set; }
     public DateTime? UpdateTime { get; set; }
 
 
     [SugarColumn(ColumnName = "IsActive", IsNullable = true)]
     [SugarColumn(ColumnName = "IsActive", IsNullable = true)]
-    public int? IsActive { get; set; }
+    public bool IsActive { get; set; }
 
 
     [SugarColumn(ColumnName = "IsConfirm", IsNullable = true)]
     [SugarColumn(ColumnName = "IsConfirm", IsNullable = true)]
     public int? IsConfirm { get; set; }
     public int? IsConfirm { get; set; }

+ 7 - 7
server/Plugins/Admin.NET.Plugin.AiDOP/WorkOrder/Entity/WorkOrdRouting.cs

@@ -8,11 +8,11 @@ public class WorkOrdRouting
 {
 {
     /// <summary>自增列</summary>
     /// <summary>自增列</summary>
     [SugarColumn(ColumnName = "RecID", IsPrimaryKey = true, IsIdentity = true)]
     [SugarColumn(ColumnName = "RecID", IsPrimaryKey = true, IsIdentity = true)]
-    public long RecID { get; set; }
+    public int RecID { get; set; }
 
 
     /// <summary>工单流水号</summary>
     /// <summary>工单流水号</summary>
     [SugarColumn(ColumnName = "WorkOrdMasterRecID", IsNullable = true)]
     [SugarColumn(ColumnName = "WorkOrdMasterRecID", IsNullable = true)]
-    public long? WorkOrdMasterRecID { get; set; }
+    public long WorkOrdMasterRecID { get; set; }
 
 
     [SugarColumn(ColumnName = "Domain", Length = 8, IsNullable = true)]
     [SugarColumn(ColumnName = "Domain", Length = 8, IsNullable = true)]
     public string? Domain { get; set; }
     public string? Domain { get; set; }
@@ -21,7 +21,7 @@ public class WorkOrdRouting
     public string? WorkOrd { get; set; }
     public string? WorkOrd { get; set; }
 
 
     [SugarColumn(ColumnName = "OP", Length = 6, IsNullable = true)]
     [SugarColumn(ColumnName = "OP", Length = 6, IsNullable = true)]
-    public string? OP { get; set; }
+    public int OP { get; set; }
 
 
     [SugarColumn(ColumnName = "ItemNum", Length = 30, IsNullable = true)]
     [SugarColumn(ColumnName = "ItemNum", Length = 30, IsNullable = true)]
     public string? ItemNum { get; set; }
     public string? ItemNum { get; set; }
@@ -72,7 +72,7 @@ public class WorkOrdRouting
     public decimal? ActSetupTime { get; set; }
     public decimal? ActSetupTime { get; set; }
 
 
     [SugarColumn(ColumnName = "RunCrew", IsNullable = true)]
     [SugarColumn(ColumnName = "RunCrew", IsNullable = true)]
-    public int? RunCrew { get; set; }
+    public decimal RunCrew { get; set; }
 
 
     [SugarColumn(ColumnName = "SetupCrew", IsNullable = true)]
     [SugarColumn(ColumnName = "SetupCrew", IsNullable = true)]
     public int? SetupCrew { get; set; }
     public int? SetupCrew { get; set; }
@@ -111,13 +111,13 @@ public class WorkOrdRouting
     public string? NextOp1 { get; set; }
     public string? NextOp1 { get; set; }
 
 
     [SugarColumn(ColumnName = "ParentOp", Length = 6, IsNullable = true)]
     [SugarColumn(ColumnName = "ParentOp", Length = 6, IsNullable = true)]
-    public string? ParentOp { get; set; }
+    public int ParentOp { get; set; }
 
 
     [SugarColumn(ColumnName = "IsCheckOp", IsNullable = true)]
     [SugarColumn(ColumnName = "IsCheckOp", IsNullable = true)]
     public int? IsCheckOp { get; set; }
     public int? IsCheckOp { get; set; }
 
 
     [SugarColumn(ColumnName = "ProcessOut", IsNullable = true)]
     [SugarColumn(ColumnName = "ProcessOut", IsNullable = true)]
-    public int? ProcessOut { get; set; }
+    public int ProcessOut { get; set; }
 
 
     [SugarColumn(ColumnName = "Ufld1", Length = 64, IsNullable = true)]
     [SugarColumn(ColumnName = "Ufld1", Length = 64, IsNullable = true)]
     public string? Ufld1 { get; set; }
     public string? Ufld1 { get; set; }
@@ -144,7 +144,7 @@ public class WorkOrdRouting
     public DateTime? UpdateTime { get; set; }
     public DateTime? UpdateTime { get; set; }
 
 
     [SugarColumn(ColumnName = "IsActive", IsNullable = true)]
     [SugarColumn(ColumnName = "IsActive", IsNullable = true)]
-    public int? IsActive { get; set; }
+    public bool IsActive { get; set; }
 
 
     [SugarColumn(ColumnName = "IsConfirm", IsNullable = true)]
     [SugarColumn(ColumnName = "IsConfirm", IsNullable = true)]
     public int? IsConfirm { get; set; }
     public int? IsConfirm { get; set; }