Parcourir la source

feat(aidop): add outsource order flow and align entity mappings

Implement S3 outsource purchase order frontend/backend pages and APIs, align multiple entities with aidopdev schema updates, and adjust order review request behavior; bump frontend to 2.4.109 and backend entry to 1.0.76.

Made-with: Cursor
Pengxy il y a 5 mois
Parent
commit
8df0eea174
26 fichiers modifiés avec 3431 ajouts et 100 suppressions
  1. 1 1
      Web/package.json
  2. 7 1
      Web/src/views/aidop/api/seOrderReview.ts
  3. 144 0
      Web/src/views/aidop/s3/api/outsourceOrder.ts
  4. 41 0
      Web/src/views/aidop/s3/api/purchaseOrder.ts
  5. 240 0
      Web/src/views/aidop/s3/supply/outsourceOrderDetailForm.vue
  6. 176 0
      Web/src/views/aidop/s3/supply/outsourceOrderDetailList.vue
  7. 125 0
      Web/src/views/aidop/s3/supply/outsourceOrderForm.vue
  8. 166 0
      Web/src/views/aidop/s3/supply/outsourceOrderList.vue
  9. 217 0
      Web/src/views/aidop/s3/supply/purchaseOrderList.vue
  10. 108 0
      Web/src/views/aidop/s3/supply/selectItemMaster.vue
  11. 3 3
      server/Admin.NET.Web.Entry/Admin.NET.Web.Entry.csproj
  12. 2 2
      server/Plugins/Admin.NET.Plugin.AiDOP/Controllers/S0/Supply/AdoS0SrmPurchasesController.cs
  13. 6 6
      server/Plugins/Admin.NET.Plugin.AiDOP/Dto/S0/Supply/AdoS0SupplyDtos.cs
  14. 44 2
      server/Plugins/Admin.NET.Plugin.AiDOP/Entity/S0/Manufacturing/AdoS0ProductStructure.cs
  15. 22 1
      server/Plugins/Admin.NET.Plugin.AiDOP/Entity/S0/Sales/IcSubstitute.cs
  16. 71 8
      server/Plugins/Admin.NET.Plugin.AiDOP/Entity/S0/Supply/AdoS0SrmPurchase.cs
  17. 11 1
      server/Plugins/Admin.NET.Plugin.AiDOP/Infrastructure/AidopMenuLinkSync.cs
  18. 31 1
      server/Plugins/Admin.NET.Plugin.AiDOP/SeedData/SysMenuSeedData.cs
  19. 76 0
      server/Plugins/Admin.NET.Plugin.AiDOP/Supply/Dto/OutsourceOrderDto.cs
  20. 13 0
      server/Plugins/Admin.NET.Plugin.AiDOP/Supply/Dto/PurchaseOrderDto.cs
  21. 515 0
      server/Plugins/Admin.NET.Plugin.AiDOP/Supply/Entity/PurOrdDetail.cs
  22. 356 0
      server/Plugins/Admin.NET.Plugin.AiDOP/Supply/Entity/PurOrdMaster.cs
  23. 714 0
      server/Plugins/Admin.NET.Plugin.AiDOP/Supply/OutsourceOrderService.cs
  24. 163 0
      server/Plugins/Admin.NET.Plugin.AiDOP/Supply/PurchaseOrderService.cs
  25. 171 66
      server/Plugins/Admin.NET.Plugin.AiDOP/Universal/Entity/ItemMaster.cs
  26. 8 8
      server/Plugins/Admin.NET.Plugin.AiDOP/WorkOrder/Entity/WorkOrdMaster.cs

+ 1 - 1
Web/package.json

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

+ 7 - 1
Web/src/views/aidop/api/seOrderReview.ts

@@ -110,10 +110,16 @@ export function saveSeOrder(body: SeOrderUpsert) {
 }
 }
 
 
 const EXTERNAL_BASE = 'http://123.60.180.165:8087/api/business/resource-examine';
 const EXTERNAL_BASE = 'http://123.60.180.165:8087/api/business/resource-examine';
+const rawHttp = axios.create();
 
 
 /** 订单评审 — 直接调用外部 POST 接口,返回非 "ok" 时抛错 */
 /** 订单评审 — 直接调用外部 POST 接口,返回非 "ok" 时抛错 */
 export async function reviewSeOrder(ids: string): Promise<void> {
 export async function reviewSeOrder(ids: string): Promise<void> {
-	const resp = await axios.post(`${EXTERNAL_BASE}/receiveresult?companyid=&type=0&ids=${ids}`);
+	const resp = await rawHttp.post(`${EXTERNAL_BASE}/receiveresult?companyid=&type=0&ids=${ids}`, null, {
+		headers: {
+			Authorization: '',
+			'X-Authorization': '',
+		},
+	});
 	const body = String(resp.data ?? '').trim();
 	const body = String(resp.data ?? '').trim();
 	if (body.toLowerCase() !== 'ok') throw new Error(`评审接口返回异常:${body}`);
 	if (body.toLowerCase() !== 'ok') throw new Error(`评审接口返回异常:${body}`);
 }
 }

+ 144 - 0
Web/src/views/aidop/s3/api/outsourceOrder.ts

@@ -0,0 +1,144 @@
+import service from '/@/utils/request';
+
+export interface Paged<T> {
+	total: number;
+	page: number;
+	pageSize: number;
+	list: T[];
+}
+
+export interface OptionRow {
+	value: string;
+	label: string;
+}
+
+export interface OutsourceOrderRow {
+	id: number;
+	purOrd?: string | null;
+	suppName?: string | null;
+	buyer?: string | null;
+	ordDate?: string | null;
+	dueDate?: string | null;
+	curr?: string | null;
+	status?: string | null;
+	supp?: string | null;
+	buyerCode?: string | null;
+	department?: string | null;
+	usage?: string | null;
+	remark?: string | null;
+}
+
+export interface OutsourceOrderSaveInput {
+	id?: number | null;
+	purOrd?: string | null;
+	ordDate?: string | null;
+	supp?: string | null;
+	reqBy?: string | null;
+	buyer?: string | null;
+	department?: string | null;
+	usage?: string | null;
+	remark?: string | null;
+}
+
+export interface OutsourceOrderDetailRow {
+	id: number;
+	purOrd?: string | null;
+	line?: number | null;
+	itemNum?: string | null;
+	descr?: string | null;
+	um?: string | null;
+	rev?: string | null;
+	drawing?: string | null;
+	qtyOrded?: number | null;
+	qtyReceived?: number | null;
+	qtyReleased?: number | null;
+	dueDate?: string | null;
+	location?: string | null;
+	workOrd?: string | null;
+	lotSerial?: string | null;
+	status?: string | null;
+	purOrdRecID?: number | null;
+}
+
+export interface OutsourceOrderBatchRow {
+	batch?: number | null;
+	itemNum?: string | null;
+	suppItem?: string | null;
+	um?: string | null;
+	location?: string | null;
+	qtyOrded?: number | null;
+	qtyBO?: number | null;
+	qtyReleased?: number | null;
+	qtyReceived?: number | null;
+	qtyReturned?: number | null;
+	lotSerial?: string | null;
+}
+
+export interface OutsourceOrderDetailSaveInput {
+	id?: number | null;
+	purOrdRecID: number;
+	purOrd: string;
+	line?: number | null;
+	itemNum?: string | null;
+	qtyOrded?: number | null;
+	dueDate?: string | null;
+	potype?: string | null;
+	batches: OutsourceOrderBatchRow[];
+}
+
+export interface ItemRow {
+	recID: number;
+	itemNum?: string | null;
+	descr?: string | null;
+	descr1?: string | null;
+	um?: string | null;
+	location?: string | null;
+	rev?: string | null;
+	drawing?: string | null;
+}
+
+export function fetchOutsourceOrderList(params: any) {
+	return service.get<Paged<OutsourceOrderRow>>('/api/Supply/outsource-order/list', { params }).then((r) => r.data);
+}
+export function fetchOutsourceOrder(id: number) {
+	return service.get<OutsourceOrderRow>(`/api/Supply/outsource-order/${id}`).then((r) => r.data);
+}
+export function saveOutsourceOrder(body: OutsourceOrderSaveInput) {
+	return service.post('/api/Supply/outsource-order/save', body).then((r) => r.data);
+}
+export function deleteOutsourceOrder(id: number, purOrd: string) {
+	return service.post(`/api/Supply/outsource-order/delete/${id}`, null, { params: { purOrd } }).then((r) => r.data);
+}
+
+export function fetchOutsourceOrderDetails(params: any) {
+	return service.get<Paged<OutsourceOrderDetailRow>>('/api/Supply/outsource-order/detail/list', { params }).then((r) => r.data);
+}
+export function fetchOutsourceOrderDetail(id: number) {
+	return service.get<{ detail: OutsourceOrderDetailRow; batches: OutsourceOrderBatchRow[] }>(`/api/Supply/outsource-order/detail/${id}`).then((r) => r.data);
+}
+export function saveOutsourceOrderDetail(body: OutsourceOrderDetailSaveInput) {
+	return service.post('/api/Supply/outsource-order/detail/save', body).then((r) => r.data);
+}
+export function deleteOutsourceOrderDetail(id: number) {
+	return service.post(`/api/Supply/outsource-order/detail/delete/${id}`).then((r) => r.data);
+}
+export function fetchOutsourceOrderDetailInit(purOrd: string) {
+	return service.get<{ usage?: string; line: number; purOrdRecID: number; purOrd?: string }>('/api/Supply/outsource-order/detail/init', { params: { purOrd } }).then((r) => r.data);
+}
+
+export function fetchOutsourceItems(params: any) {
+	return service.get<Paged<ItemRow>>('/api/Supply/outsource-order/items', { params }).then((r) => r.data);
+}
+
+export function fetchOutsourceBuyers() {
+	return service.get<{ list: OptionRow[] }>('/api/Supply/outsource-order/options/buyers').then((r) => r.data);
+}
+export function fetchOutsourceSuppliers() {
+	return service.get<{ list: OptionRow[] }>('/api/Supply/outsource-order/options/suppliers').then((r) => r.data);
+}
+export function fetchOutsourceDepartments() {
+	return service.get<{ list: OptionRow[] }>('/api/Supply/outsource-order/options/departments').then((r) => r.data);
+}
+export function fetchOutsourceLocations() {
+	return service.get<{ list: OptionRow[] }>('/api/Supply/outsource-order/options/locations').then((r) => r.data);
+}

+ 41 - 0
Web/src/views/aidop/s3/api/purchaseOrder.ts

@@ -0,0 +1,41 @@
+import service from '/@/utils/request';
+
+export interface Paged<T> {
+	total: number;
+	page: number;
+	pageSize: number;
+	list: T[];
+}
+
+export interface PurchaseOrderRow {
+	gysdm?: string | null;
+	gysmc?: string | null;
+	cgdd?: string | null;
+	ddhh?: string | null;
+	buyer?: string | null;
+	orddate?: string | null;
+	wlbm?: string | null;
+	wlms?: string | null;
+	ddlx?: string | null;
+	jhdsl?: number | null;
+	wjhsl?: number | null;
+	jhrq?: string | null;
+	th?: string | null;
+	sffb?: string | null;
+	sffbcx?: string | null;
+}
+
+export interface PurchaseOrderListParams {
+	page: number;
+	pageSize: number;
+	cgdd?: string;
+	wlbm?: string;
+	gysdm?: string;
+	sffbcx?: string;
+	sortField?: string;
+	sortOrder?: string;
+}
+
+export function fetchPurchaseOrderList(params: PurchaseOrderListParams) {
+	return service.get<Paged<PurchaseOrderRow>>('/api/Supply/purchase-order/list', { params }).then((r) => r.data);
+}

+ 240 - 0
Web/src/views/aidop/s3/supply/outsourceOrderDetailForm.vue

@@ -0,0 +1,240 @@
+<template>
+	<div v-loading="loading">
+		<el-form ref="formRef" :model="form" :rules="rules" label-width="110px">
+			<el-row :gutter="12">
+				<el-col :span="12"><el-form-item label="采购单号"><el-input v-model="form.purOrd" disabled /></el-form-item></el-col>
+				<el-col :span="12"><el-form-item label="行号"><el-input v-model="form.line" disabled /></el-form-item></el-col>
+				<el-col :span="12">
+					<el-form-item label="物料编号" prop="itemNum">
+						<div class="pick-wrap">
+							<el-input v-model="form.itemNum" :disabled="isView" />
+							<el-button :disabled="isView" @click="openPick('main')">选择</el-button>
+						</div>
+					</el-form-item>
+				</el-col>
+				<el-col :span="12"><el-form-item label="批号"><el-input v-model="form.lotSerial" disabled /></el-form-item></el-col>
+				<el-col :span="12"><el-form-item label="版本"><el-input v-model="form.rev" disabled /></el-form-item></el-col>
+				<el-col :span="12"><el-form-item label="图纸"><el-input v-model="form.drawing" disabled /></el-form-item></el-col>
+				<el-col :span="12"><el-form-item label="单位"><el-input v-model="form.um" disabled /></el-form-item></el-col>
+				<el-col :span="12"><el-form-item label="库位"><el-input v-model="form.location" disabled /></el-form-item></el-col>
+				<el-col :span="12"><el-form-item label="订单数量" prop="qtyOrded"><el-input-number v-model="form.qtyOrded" :disabled="isView" :min="0" :precision="5" :controls="false" style="width: 100%" /></el-form-item></el-col>
+				<el-col :span="12"><el-form-item label="收货数量"><el-input v-model="form.qtyReceived" disabled /></el-form-item></el-col>
+				<el-col :span="12"><el-form-item label="下达数量"><el-input v-model="form.qtyReleased" disabled /></el-form-item></el-col>
+				<el-col :span="12"><el-form-item label="交货日期"><el-date-picker v-model="form.dueDate" type="date" value-format="YYYY-MM-DD" :disabled="isView" style="width: 100%" /></el-form-item></el-col>
+			</el-row>
+		</el-form>
+
+		<div class="sub-header">
+			<span>批次明细</span>
+			<div v-if="!isView">
+				<el-button type="primary" link @click="addBatch">添加</el-button>
+			</div>
+		</div>
+		<el-table :data="form.batches" border>
+			<el-table-column prop="batch" label="批次" width="80">
+				<template #default="{ row }"><el-input-number v-model="row.batch" :min="1" :controls="false" :disabled="isView" /></template>
+			</el-table-column>
+			<el-table-column prop="itemNum" label="物料编号" width="140"><template #default="{ row }"><el-input v-model="row.itemNum" disabled /></template></el-table-column>
+			<el-table-column prop="suppItem" label="供应商物料" width="180">
+				<template #default="{ row, $index }">
+					<div class="pick-wrap">
+						<el-input v-model="row.suppItem" :disabled="isView" />
+						<el-button :disabled="isView" @click="openPick('batch', $index)">选</el-button>
+					</div>
+				</template>
+			</el-table-column>
+			<el-table-column prop="um" label="单位" width="90"><template #default="{ row }"><el-input v-model="row.um" :disabled="isView" /></template></el-table-column>
+			<el-table-column prop="location" label="库位" width="120">
+				<template #default="{ row }"><el-select v-model="row.location" filterable :disabled="isView"><el-option v-for="o in locationOptions" :key="o.value" :label="o.label" :value="o.value" /></el-select></template>
+			</el-table-column>
+			<el-table-column prop="qtyOrded" label="订单数量" width="120"><template #default="{ row }"><el-input-number v-model="row.qtyOrded" :precision="5" :min="0" :controls="false" :disabled="isView" /></template></el-table-column>
+			<el-table-column prop="qtyBO" label="欠交量" width="120"><template #default="{ row }"><el-input-number v-model="row.qtyBO" :precision="5" :min="0" :controls="false" :disabled="isView" /></template></el-table-column>
+			<el-table-column prop="qtyReleased" label="下达量" width="120"><template #default="{ row }"><el-input-number v-model="row.qtyReleased" :precision="5" :min="0" :controls="false" :disabled="isView" /></template></el-table-column>
+			<el-table-column prop="qtyReceived" label="收货量" width="120"><template #default="{ row }"><el-input-number v-model="row.qtyReceived" :precision="5" :min="0" :controls="false" :disabled="isView" /></template></el-table-column>
+			<el-table-column prop="qtyReturned" label="退货量" width="120"><template #default="{ row }"><el-input-number v-model="row.qtyReturned" :precision="5" :min="0" :controls="false" :disabled="isView" /></template></el-table-column>
+			<el-table-column prop="lotSerial" label="批号" width="120"><template #default="{ row }"><el-input v-model="row.lotSerial" :disabled="isView" /></template></el-table-column>
+			<el-table-column v-if="!isView" label="操作" fixed="right" width="90">
+				<template #default="{ $index }">
+					<el-button type="danger" link @click="removeBatch($index)">删除</el-button>
+				</template>
+			</el-table-column>
+		</el-table>
+
+		<div class="footer">
+			<el-button @click="$emit('cancel')">取消</el-button>
+			<el-button v-if="!isView" type="primary" :loading="saving" @click="onSave">保存</el-button>
+		</div>
+	</div>
+
+	<el-dialog v-model="pickVisible" title="选择物料" width="75%">
+		<select-item-master @picked="onPicked" />
+	</el-dialog>
+</template>
+
+<script setup lang="ts">
+import { onMounted, reactive, ref } from 'vue';
+import { type FormInstance, type FormRules } from 'element-plus';
+import { fetchOutsourceLocations, fetchOutsourceOrderDetail, fetchOutsourceOrderDetailInit, saveOutsourceOrderDetail, type ItemRow, type OptionRow, type OutsourceOrderBatchRow } from '../api/outsourceOrder';
+import SelectItemMaster from './selectItemMaster.vue';
+
+const props = defineProps<{ mode: 'add' | 'edit' | 'view'; id: number | null; purOrd: string }>();
+const emit = defineEmits<{ (e: 'saved'): void; (e: 'cancel'): void }>();
+
+const formRef = ref<FormInstance>();
+const loading = ref(false);
+const saving = ref(false);
+const locationOptions = ref<OptionRow[]>([]);
+const isView = ref(props.mode === 'view');
+
+const form = reactive({
+	id: null as number | null,
+	purOrd: props.purOrd || '',
+	purOrdRecID: 0,
+	line: 0,
+	itemNum: '',
+	lotSerial: '',
+	rev: '',
+	drawing: '',
+	um: '',
+	location: '',
+	qtyOrded: 0,
+	qtyReceived: 0,
+	qtyReleased: 0,
+	dueDate: '',
+	potype: 'PW',
+	batches: [] as OutsourceOrderBatchRow[],
+});
+const rules: FormRules = {
+	itemNum: [{ required: true, message: '请选择物料', trigger: 'blur' }],
+	qtyOrded: [{ required: true, message: '请输入订单数量', trigger: 'blur' }],
+};
+
+const pickVisible = ref(false);
+const pickMode = ref<'main' | 'batch'>('main');
+const pickRowIndex = ref(-1);
+
+function addBatch() {
+	form.batches.push({
+		batch: form.batches.length + 1,
+		itemNum: form.itemNum,
+		suppItem: '',
+		um: form.um,
+		location: form.location,
+		qtyOrded: 0,
+		qtyBO: 0,
+		qtyReleased: 0,
+		qtyReceived: 0,
+		qtyReturned: 0,
+		lotSerial: '',
+	});
+}
+function removeBatch(index: number) {
+	form.batches.splice(index, 1);
+}
+function openPick(mode: 'main' | 'batch', index = -1) {
+	pickMode.value = mode;
+	pickRowIndex.value = index;
+	pickVisible.value = true;
+}
+function onPicked(row: ItemRow) {
+	if (pickMode.value === 'main') {
+		form.itemNum = row.itemNum || '';
+		form.um = row.um || '';
+		form.location = row.location || '';
+		form.rev = row.rev || '';
+		form.drawing = row.drawing || '';
+	} else if (pickRowIndex.value >= 0) {
+		const tar = form.batches[pickRowIndex.value];
+		tar.itemNum = row.itemNum || '';
+		tar.suppItem = row.itemNum || '';
+		tar.um = row.um || '';
+		tar.location = row.location || '';
+	}
+	pickVisible.value = false;
+}
+
+async function loadOptions() {
+	const ret = await fetchOutsourceLocations();
+	locationOptions.value = ret.list || [];
+}
+
+async function loadData() {
+	loading.value = true;
+	try {
+		if (props.mode === 'add') {
+			const init = await fetchOutsourceOrderDetailInit(props.purOrd);
+			form.purOrd = init.purOrd || props.purOrd;
+			form.purOrdRecID = init.purOrdRecID;
+			form.line = init.line;
+			form.batches = [];
+			return;
+		}
+		if (!props.id) return;
+		const data = await fetchOutsourceOrderDetail(props.id);
+		const d = data.detail;
+		form.id = d.id;
+		form.purOrd = d.purOrd || '';
+		form.purOrdRecID = d.purOrdRecID || 0;
+		form.line = d.line || 0;
+		form.itemNum = d.itemNum || '';
+		form.lotSerial = d.lotSerial || '';
+		form.rev = d.rev || '';
+		form.drawing = d.drawing || '';
+		form.um = d.um || '';
+		form.location = d.location || '';
+		form.qtyOrded = Number(d.qtyOrded || 0);
+		form.qtyReceived = Number(d.qtyReceived || 0);
+		form.qtyReleased = Number(d.qtyReleased || 0);
+		form.dueDate = d.dueDate ? String(d.dueDate).slice(0, 10) : '';
+		form.batches = (data.batches || []).map((x) => ({ ...x }));
+	} finally {
+		loading.value = false;
+	}
+}
+
+async function onSave() {
+	await formRef.value?.validate();
+	saving.value = true;
+	try {
+		await saveOutsourceOrderDetail({
+			id: form.id,
+			purOrdRecID: form.purOrdRecID,
+			purOrd: form.purOrd,
+			line: form.line,
+			itemNum: form.itemNum,
+			qtyOrded: form.qtyOrded,
+			dueDate: form.dueDate,
+			potype: 'PW',
+			batches: form.batches,
+		});
+		emit('saved');
+	} finally {
+		saving.value = false;
+	}
+}
+
+onMounted(async () => {
+	await loadOptions();
+	await loadData();
+});
+</script>
+
+<style scoped>
+.sub-header {
+	display: flex;
+	justify-content: space-between;
+	align-items: center;
+	margin: 8px 0;
+	font-weight: 600;
+}
+.pick-wrap {
+	display: flex;
+	gap: 6px;
+}
+.footer {
+	margin-top: 12px;
+	display: flex;
+	justify-content: flex-end;
+	gap: 10px;
+}
+</style>

+ 176 - 0
Web/src/views/aidop/s3/supply/outsourceOrderDetailList.vue

@@ -0,0 +1,176 @@
+<template>
+	<div class="aidop-page">
+		<aidop-demo-shell title="委外采购明细">
+			<template #query>
+				<el-form :inline="true" :model="query" @submit.prevent>
+					<el-form-item label="采购单号"><el-input v-model="query.purOrd" clearable style="width: 160px" /></el-form-item>
+					<el-form-item label="物料编号"><el-input v-model="query.itemNum" clearable style="width: 160px" /></el-form-item>
+					<el-form-item label="库位">
+						<el-select v-model="query.location" clearable filterable style="width: 160px">
+							<el-option v-for="o in locationOptions" :key="o.value" :label="o.label" :value="o.value" />
+						</el-select>
+					</el-form-item>
+					<el-form-item label="工单编号"><el-input v-model="query.workOrd" clearable style="width: 160px" /></el-form-item>
+					<el-form-item label="生产批号"><el-input v-model="query.lotSerial" clearable style="width: 160px" /></el-form-item>
+					<el-form-item>
+						<el-button type="primary" @click="doSearch">查询</el-button>
+						<el-button @click="doReset">重置</el-button>
+					</el-form-item>
+				</el-form>
+			</template>
+			<template #actions>
+				<el-button type="primary" @click="openForm('add', null)">添加</el-button>
+			</template>
+			<template #default>
+				<el-table :data="rows" v-loading="loading" border stripe @sort-change="onSortChange">
+					<el-table-column prop="purOrd" label="采购单号" width="140" sortable="custom" />
+					<el-table-column prop="line" label="行号" width="80" sortable="custom" />
+					<el-table-column prop="itemNum" label="物料编号" min-width="140" fixed="left" sortable="custom" />
+					<el-table-column prop="descr" label="物料名称" min-width="160" sortable="custom" />
+					<el-table-column prop="um" label="单位" width="90" sortable="custom" />
+					<el-table-column prop="rev" label="版本" width="90" sortable="custom" />
+					<el-table-column prop="drawing" label="图纸号" width="120" sortable="custom" />
+					<el-table-column prop="qtyOrded" label="订单数量" width="120" sortable="custom" />
+					<el-table-column prop="qtyReceived" label="收货数量" width="120" sortable="custom" />
+					<el-table-column prop="qtyReleased" label="下达数量" width="120" sortable="custom" />
+					<el-table-column prop="dueDate" label="交期" width="120" sortable="custom">
+						<template #default="{ row }">{{ fmtDate(row.dueDate) }}</template>
+					</el-table-column>
+					<el-table-column prop="location" label="库位" width="100" sortable="custom" />
+					<el-table-column prop="workOrd" label="工单编号" width="120" sortable="custom" />
+					<el-table-column prop="lotSerial" label="生产批号" width="120" sortable="custom" />
+					<el-table-column prop="status" label="状态" width="90" sortable="custom" />
+					<el-table-column label="操作" width="180" fixed="right">
+						<template #default="{ row }">
+							<el-button type="primary" link @click="openForm('edit', row.id)">编辑</el-button>
+							<el-button type="primary" link @click="openForm('view', row.id)">查看</el-button>
+							<el-button type="danger" link @click="onDelete(row)">删除</el-button>
+						</template>
+					</el-table-column>
+				</el-table>
+				<div class="pager">
+					<el-pagination
+						v-model:current-page="query.page"
+						v-model:page-size="query.pageSize"
+						:total="total"
+						:page-sizes="[10, 20, 50]"
+						layout="total, sizes, prev, pager, next"
+						@current-change="loadList"
+						@size-change="loadList"
+					/>
+				</div>
+			</template>
+		</aidop-demo-shell>
+
+		<el-dialog v-model="formVisible" :title="formTitle" width="80%" destroy-on-close>
+			<outsource-order-detail-form :mode="formMode" :id="editingId" :pur-ord="purOrd" @saved="onSaved" @cancel="formVisible = false" />
+		</el-dialog>
+	</div>
+</template>
+
+<script setup lang="ts">
+import { computed, onMounted, reactive, ref } from 'vue';
+import { ElMessage, ElMessageBox } from 'element-plus';
+import AidopDemoShell from '../../components/AidopDemoShell.vue';
+import OutsourceOrderDetailForm from './outsourceOrderDetailForm.vue';
+import { deleteOutsourceOrderDetail, fetchOutsourceLocations, fetchOutsourceOrderDetails, type OptionRow, type OutsourceOrderDetailRow } from '../api/outsourceOrder';
+
+const query = reactive({
+	page: 1,
+	pageSize: 10,
+	purOrd: '',
+	itemNum: '',
+	location: '',
+	workOrd: '',
+	lotSerial: '',
+	sortField: '',
+	sortOrder: '',
+});
+
+const purOrd = ref('');
+const rows = ref<OutsourceOrderDetailRow[]>([]);
+const total = ref(0);
+const loading = ref(false);
+const locationOptions = ref<OptionRow[]>([]);
+
+const formVisible = ref(false);
+const formMode = ref<'add' | 'edit' | 'view'>('add');
+const editingId = ref<number | null>(null);
+const formTitle = computed(() => (formMode.value === 'add' ? '新增委外采购明细' : formMode.value === 'edit' ? '编辑委外采购明细' : '查看委外采购明细'));
+
+function fmtDate(v?: string | null) {
+	return v ? String(v).slice(0, 10) : '';
+}
+function initPurOrd() {
+	const params = new URLSearchParams(window.location.search);
+	purOrd.value = params.get('purOrd') || '';
+	query.purOrd = purOrd.value;
+}
+async function loadList() {
+	loading.value = true;
+	try {
+		const data = await fetchOutsourceOrderDetails({ ...query });
+		rows.value = data.list || [];
+		total.value = data.total || 0;
+	} finally {
+		loading.value = false;
+	}
+}
+function doSearch() {
+	query.page = 1;
+	loadList();
+}
+function doReset() {
+	query.itemNum = '';
+	query.location = '';
+	query.workOrd = '';
+	query.lotSerial = '';
+	query.page = 1;
+	query.sortField = '';
+	query.sortOrder = '';
+	loadList();
+}
+function onSortChange({ prop, order }: { prop: string; order: string | null }) {
+	query.sortField = prop || '';
+	query.sortOrder = order === 'ascending' ? 'asc' : order === 'descending' ? 'desc' : '';
+	loadList();
+}
+function openForm(mode: 'add' | 'edit' | 'view', id: number | null) {
+	formMode.value = mode;
+	editingId.value = id;
+	formVisible.value = true;
+}
+async function onDelete(row: OutsourceOrderDetailRow) {
+	if (row.status === 'C') {
+		ElMessage.warning('关闭状态不允许删除');
+		return;
+	}
+	await ElMessageBox.confirm('确认删除该明细?', '提示', { type: 'warning' });
+	await deleteOutsourceOrderDetail(row.id);
+	ElMessage.success('删除成功');
+	loadList();
+}
+function onSaved() {
+	formVisible.value = false;
+	ElMessage.success('保存成功');
+	loadList();
+}
+
+onMounted(async () => {
+	initPurOrd();
+	const ret = await fetchOutsourceLocations();
+	locationOptions.value = ret.list || [];
+	await loadList();
+});
+</script>
+
+<style scoped>
+.aidop-page {
+	padding: 8px;
+}
+.pager {
+	margin-top: 10px;
+	display: flex;
+	justify-content: flex-end;
+}
+</style>

+ 125 - 0
Web/src/views/aidop/s3/supply/outsourceOrderForm.vue

@@ -0,0 +1,125 @@
+<template>
+	<div v-loading="loading">
+		<el-form ref="formRef" :model="form" :rules="rules" label-width="110px">
+			<el-row :gutter="12">
+				<el-col :span="12"><el-form-item label="采购单号"><el-input v-model="form.purOrd" disabled /></el-form-item></el-col>
+				<el-col :span="12"><el-form-item label="订单日期" prop="ordDate"><el-date-picker v-model="form.ordDate" type="date" value-format="YYYY-MM-DD" style="width: 100%" /></el-form-item></el-col>
+				<el-col :span="12">
+					<el-form-item label="供应商" prop="supp">
+						<el-select v-model="form.supp" filterable style="width: 100%">
+							<el-option v-for="o in supplierOptions" :key="o.value" :label="o.label" :value="o.value" />
+						</el-select>
+					</el-form-item>
+				</el-col>
+				<el-col :span="12">
+					<el-form-item label="订单类型">
+						<el-select v-model="form.reqBy" style="width: 100%">
+							<el-option label="要货令" value="DO" />
+						</el-select>
+					</el-form-item>
+				</el-col>
+				<el-col :span="12"><el-form-item label="采购组"><el-input v-model="form.buyer" disabled /></el-form-item></el-col>
+				<el-col :span="12">
+					<el-form-item label="部门" prop="department">
+						<el-select v-model="form.department" filterable style="width: 100%">
+							<el-option v-for="o in departmentOptions" :key="o.value" :label="o.label" :value="o.value" />
+						</el-select>
+					</el-form-item>
+				</el-col>
+				<el-col :span="12">
+					<el-form-item label="供应类别">
+						<el-select v-model="form.usage" style="width: 100%">
+							<el-option label="委外加工" value="委外加工" />
+						</el-select>
+					</el-form-item>
+				</el-col>
+				<el-col :span="24"><el-form-item label="备注"><el-input v-model="form.remark" type="textarea" :rows="3" /></el-form-item></el-col>
+			</el-row>
+		</el-form>
+		<div class="footer">
+			<el-button @click="$emit('cancel')">取消</el-button>
+			<el-button type="primary" :loading="saving" @click="onSave">保存</el-button>
+		</div>
+	</div>
+</template>
+
+<script setup lang="ts">
+import { onMounted, reactive, ref } from 'vue';
+import { type FormInstance, type FormRules } from 'element-plus';
+import { fetchOutsourceDepartments, fetchOutsourceOrder, fetchOutsourceSuppliers, saveOutsourceOrder, type OptionRow } from '../api/outsourceOrder';
+
+const props = defineProps<{ id: number | null }>();
+const emit = defineEmits<{ (e: 'saved'): void; (e: 'cancel'): void }>();
+
+const formRef = ref<FormInstance>();
+const loading = ref(false);
+const saving = ref(false);
+const supplierOptions = ref<OptionRow[]>([]);
+const departmentOptions = ref<OptionRow[]>([]);
+
+const form = reactive({
+	id: null as number | null,
+	purOrd: '',
+	ordDate: '',
+	supp: '',
+	reqBy: 'DO',
+	buyer: '110',
+	department: '',
+	usage: '委外加工',
+	remark: '',
+});
+const rules: FormRules = {
+	ordDate: [{ required: true, message: '请选择订单日期', trigger: 'change' }],
+	supp: [{ required: true, message: '请选择供应商', trigger: 'change' }],
+	department: [{ required: true, message: '请选择部门', trigger: 'change' }],
+};
+
+async function loadOptions() {
+	const [sup, dep] = await Promise.all([fetchOutsourceSuppliers(), fetchOutsourceDepartments()]);
+	supplierOptions.value = sup.list || [];
+	departmentOptions.value = dep.list || [];
+}
+
+async function loadDetail() {
+	if (!props.id) return;
+	loading.value = true;
+	try {
+		const d = await fetchOutsourceOrder(props.id);
+		form.id = d.id;
+		form.purOrd = d.purOrd || '';
+		form.ordDate = d.ordDate ? String(d.ordDate).slice(0, 10) : '';
+		form.supp = d.supp || '';
+		form.reqBy = 'DO';
+		form.buyer = d.buyerCode || '110';
+		form.department = d.department || '';
+		form.usage = d.usage || '委外加工';
+		form.remark = d.remark || '';
+	} finally {
+		loading.value = false;
+	}
+}
+
+async function onSave() {
+	await formRef.value?.validate();
+	saving.value = true;
+	try {
+		await saveOutsourceOrder({ ...form });
+		emit('saved');
+	} finally {
+		saving.value = false;
+	}
+}
+
+onMounted(async () => {
+	await loadOptions();
+	await loadDetail();
+});
+</script>
+
+<style scoped>
+.footer {
+	display: flex;
+	justify-content: flex-end;
+	gap: 10px;
+}
+</style>

+ 166 - 0
Web/src/views/aidop/s3/supply/outsourceOrderList.vue

@@ -0,0 +1,166 @@
+<template>
+	<div class="aidop-page">
+		<aidop-demo-shell title="委外加工订单">
+			<template #query>
+				<el-form :inline="true" :model="query" @submit.prevent>
+					<el-form-item label="采购单号"><el-input v-model="query.purOrd" clearable style="width: 180px" /></el-form-item>
+					<el-form-item label="采购组">
+						<el-select v-model="query.buyer" clearable filterable style="width: 180px">
+							<el-option v-for="o in buyerOptions" :key="o.value" :label="o.label" :value="o.value" />
+						</el-select>
+					</el-form-item>
+					<el-form-item label="供应商">
+						<el-select v-model="query.supp" clearable filterable style="width: 200px">
+							<el-option v-for="o in supplierOptions" :key="o.value" :label="o.label" :value="o.value" />
+						</el-select>
+					</el-form-item>
+					<el-form-item>
+						<el-button type="primary" @click="doSearch">查询</el-button>
+						<el-button @click="doReset">重置</el-button>
+					</el-form-item>
+				</el-form>
+			</template>
+			<template #actions>
+				<el-button type="primary" @click="openForm(null)">添加</el-button>
+			</template>
+			<template #default>
+				<el-table :data="rows" v-loading="loading" border stripe @sort-change="onSortChange">
+					<el-table-column prop="purOrd" label="工单编号" min-width="160" fixed="left" sortable="custom" />
+					<el-table-column prop="suppName" label="供应商" min-width="200" sortable="custom" />
+					<el-table-column prop="buyer" label="采购组" width="160" sortable="custom" />
+					<el-table-column prop="ordDate" label="订单日期" width="120" sortable="custom"><template #default="{ row }">{{ fmtDate(row.ordDate) }}</template></el-table-column>
+					<el-table-column prop="dueDate" label="交期" width="120" sortable="custom"><template #default="{ row }">{{ fmtDate(row.dueDate) }}</template></el-table-column>
+					<el-table-column prop="curr" label="币别" width="90" sortable="custom" />
+					<el-table-column prop="status" label="状态" width="90" fixed="right" sortable="custom"><template #default="{ row }">{{ statusText(row.status) }}</template></el-table-column>
+					<el-table-column label="操作" width="240" fixed="right">
+						<template #default="{ row }">
+							<el-button type="primary" link @click="openForm(row.id)">编辑</el-button>
+							<el-button type="danger" link @click="onDelete(row)">删除</el-button>
+							<el-button type="primary" link :disabled="row.status !== 'R'" @click="goDetail(row)">委外采购明细</el-button>
+						</template>
+					</el-table-column>
+				</el-table>
+				<div class="pager">
+					<el-pagination
+						v-model:current-page="query.page"
+						v-model:page-size="query.pageSize"
+						:total="total"
+						:page-sizes="[10, 20, 50]"
+						layout="total, sizes, prev, pager, next"
+						@current-change="loadList"
+						@size-change="loadList"
+					/>
+				</div>
+			</template>
+		</aidop-demo-shell>
+
+		<el-dialog v-model="formVisible" :title="editingId ? '编辑委外加工订单' : '新增委外加工订单'" width="720px" destroy-on-close>
+			<outsource-order-form :id="editingId" @saved="onSaved" @cancel="formVisible = false" />
+		</el-dialog>
+	</div>
+</template>
+
+<script setup lang="ts">
+import { onMounted, reactive, ref } from 'vue';
+import { ElMessage, ElMessageBox } from 'element-plus';
+import AidopDemoShell from '../../components/AidopDemoShell.vue';
+import OutsourceOrderForm from './outsourceOrderForm.vue';
+import { deleteOutsourceOrder, fetchOutsourceBuyers, fetchOutsourceOrderList, fetchOutsourceSuppliers, type OptionRow, type OutsourceOrderRow } from '../api/outsourceOrder';
+
+const query = reactive({
+	page: 1,
+	pageSize: 10,
+	purOrd: '',
+	buyer: '',
+	supp: '',
+	sortField: '',
+	sortOrder: '',
+});
+const rows = ref<OutsourceOrderRow[]>([]);
+const total = ref(0);
+const loading = ref(false);
+
+const buyerOptions = ref<OptionRow[]>([]);
+const supplierOptions = ref<OptionRow[]>([]);
+
+const formVisible = ref(false);
+const editingId = ref<number | null>(null);
+
+function fmtDate(v?: string | null) {
+	return v ? String(v).slice(0, 10) : '';
+}
+function statusText(v?: string | null) {
+	if (v === 'R') return '新增';
+	if (v === 'C') return '关闭';
+	return v || '';
+}
+async function loadList() {
+	loading.value = true;
+	try {
+		const data = await fetchOutsourceOrderList({ ...query });
+		rows.value = data.list || [];
+		total.value = data.total || 0;
+	} finally {
+		loading.value = false;
+	}
+}
+function doSearch() {
+	query.page = 1;
+	loadList();
+}
+function doReset() {
+	query.purOrd = '';
+	query.buyer = '';
+	query.supp = '';
+	query.page = 1;
+	query.sortField = '';
+	query.sortOrder = '';
+	loadList();
+}
+function onSortChange({ prop, order }: { prop: string; order: string | null }) {
+	query.sortField = prop || '';
+	query.sortOrder = order === 'ascending' ? 'asc' : order === 'descending' ? 'desc' : '';
+	loadList();
+}
+function openForm(id: number | null) {
+	editingId.value = id;
+	formVisible.value = true;
+}
+function goDetail(row: OutsourceOrderRow) {
+	if (row.status !== 'R') {
+		ElMessage.warning('只有新增状态允许维护明细');
+		return;
+	}
+	const path = `/aidop/s3/supply/outsourceOrderDetailList?purOrd=${encodeURIComponent(row.purOrd || '')}`;
+	window.open(path, '_blank');
+}
+async function onDelete(row: OutsourceOrderRow) {
+	await ElMessageBox.confirm('确认删除该委外加工订单?', '提示', { type: 'warning' });
+	await deleteOutsourceOrder(row.id, row.purOrd || '');
+	ElMessage.success('删除成功');
+	loadList();
+}
+function onSaved() {
+	formVisible.value = false;
+	ElMessage.success('保存成功');
+	loadList();
+}
+
+onMounted(async () => {
+	const [buyers, suppliers] = await Promise.all([fetchOutsourceBuyers(), fetchOutsourceSuppliers()]);
+	buyerOptions.value = buyers.list || [];
+	supplierOptions.value = suppliers.list || [];
+	await loadList();
+});
+</script>
+
+<style scoped>
+.aidop-page {
+	padding: 8px;
+}
+.pager {
+	margin-top: 10px;
+	display: flex;
+	justify-content: flex-end;
+}
+</style>

+ 217 - 0
Web/src/views/aidop/s3/supply/purchaseOrderList.vue

@@ -0,0 +1,217 @@
+<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 placeholder="模糊查询" style="width: 160px" />
+			</el-form-item>
+			<el-form-item label="物料编码">
+				<el-input v-model="query.wlbm" clearable placeholder="模糊查询" style="width: 150px" />
+			</el-form-item>
+			<el-form-item label="供应商编码">
+				<el-input v-model="query.gysdm" clearable placeholder="模糊查询" style="width: 150px" />
+			</el-form-item>
+			<el-form-item label="是否发布">
+				<el-select v-model="query.sffbcx" clearable style="width: 140px">
+					<el-option label="已发布" value="V" />
+					<el-option label="未发布" value="X" />
+				</el-select>
+			</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 @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 style="width: 100%" @sort-change="onSortChange">
+			<el-table-column v-if="col.cgdd" prop="cgdd" label="采购单号" width="160" fixed="left" sortable="custom" />
+			<el-table-column v-if="col.sffb" prop="sffb" label="发布" width="110" fixed="left" 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="150" show-overflow-tooltip sortable="custom" />
+			<el-table-column v-if="col.ddhh" prop="ddhh" label="采购单行号" width="110" sortable="custom" />
+			<el-table-column v-if="col.buyer" prop="buyer" label="采购组" width="100" sortable="custom" />
+			<el-table-column v-if="col.orddate" label="凭证日期" width="120" sortable="custom">
+				<template #default="{ row }">{{ fmtDate(row.orddate) }}</template>
+			</el-table-column>
+			<el-table-column v-if="col.wlbm" prop="wlbm" label="物料编码" width="140" sortable="custom" />
+			<el-table-column v-if="col.wlms" prop="wlms" label="物料描述" min-width="180" show-overflow-tooltip sortable="custom" />
+			<el-table-column v-if="col.ddlx" prop="ddlx" label="订单类型" width="110" sortable="custom" />
+			<el-table-column v-if="col.jhdsl" prop="jhdsl" label="订单数量" width="110" align="right" sortable="custom" />
+			<el-table-column v-if="col.wjhsl" prop="wjhsl" label="未收货数量" width="120" align="right" sortable="custom" />
+			<el-table-column v-if="col.jhrq" label="交货日期" width="120" sortable="custom">
+				<template #default="{ row }">{{ fmtDate(row.jhrq) }}</template>
+			</el-table-column>
+			<el-table-column v-if="col.th" prop="th" label="图号" min-width="120" show-overflow-tooltip sortable="custom" />
+		</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="aidopS3SupplyPurchaseOrderList">
+import { computed, onMounted, reactive, ref } from 'vue';
+import { useRoute } from 'vue-router';
+import AidopDemoShell from '/@/views/aidop/components/AidopDemoShell.vue';
+import { fetchPurchaseOrderList, type PurchaseOrderRow } from '../api/purchaseOrder';
+
+const route = useRoute();
+const pageTitle = computed(() => (route.meta?.title as string) || '物料采购订单');
+
+const query = reactive({
+	cgdd: '',
+	wlbm: '',
+	gysdm: '',
+	sffbcx: '',
+	page: 1,
+	pageSize: 10,
+	sortField: '',
+	sortOrder: '',
+});
+
+const loading = ref(false);
+const rows = ref<PurchaseOrderRow[]>([]);
+const total = ref(0);
+
+const col = reactive({
+	gysdm: true,
+	gysmc: true,
+	cgdd: true,
+	ddhh: true,
+	buyer: true,
+	orddate: true,
+	wlbm: true,
+	wlms: true,
+	ddlx: true,
+	jhdsl: true,
+	wjhsl: true,
+	jhrq: true,
+	th: true,
+	sffb: true,
+});
+
+type ColumnKey = keyof typeof col;
+const toggleItems: Array<{ key: ColumnKey; label: string }> = [
+	{ key: 'gysdm', label: '供应商代码' },
+	{ key: 'gysmc', label: '供应商名称' },
+	{ key: 'cgdd', label: '采购单号' },
+	{ key: 'ddhh', label: '采购单行号' },
+	{ key: 'buyer', label: '采购组' },
+	{ key: 'orddate', label: '凭证日期' },
+	{ key: 'wlbm', label: '物料编码' },
+	{ key: 'wlms', label: '物料描述' },
+	{ key: 'ddlx', label: '订单类型' },
+	{ key: 'jhdsl', label: '订单数量' },
+	{ key: 'wjhsl', label: '未收货数量' },
+	{ key: 'jhrq', label: '交货日期' },
+	{ key: 'th', label: '图号' },
+	{ key: 'sffb', label: '发布' },
+];
+
+function fmtDate(v?: string | null) {
+	return v ? String(v).slice(0, 10) : '';
+}
+
+async function loadList() {
+	loading.value = true;
+	try {
+		const data = await fetchPurchaseOrderList({
+			page: query.page,
+			pageSize: query.pageSize,
+			cgdd: query.cgdd || undefined,
+			wlbm: query.wlbm || undefined,
+			gysdm: query.gysdm || undefined,
+			sffbcx: query.sffbcx || undefined,
+			sortField: query.sortField || undefined,
+			sortOrder: query.sortOrder || undefined,
+		});
+		rows.value = data.list || [];
+		total.value = data.total || 0;
+	} finally {
+		loading.value = false;
+	}
+}
+
+function doSearch() {
+	query.page = 1;
+	loadList();
+}
+function resetQuery() {
+	query.cgdd = '';
+	query.wlbm = '';
+	query.gysdm = '';
+	query.sffbcx = '';
+	query.page = 1;
+	query.sortField = '';
+	query.sortOrder = '';
+	loadList();
+}
+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 onExport() {
+	const headers = ['供应商代码', '供应商名称', '采购单号', '采购单行号', '采购组', '凭证日期', '物料编码', '物料描述', '订单类型', '订单数量', '未收货数量', '交货日期', '图号', '发布'];
+	const lines = rows.value.map((r) => [
+		r.gysdm ?? '', r.gysmc ?? '', r.cgdd ?? '', r.ddhh ?? '', r.buyer ?? '', fmtDate(r.orddate), r.wlbm ?? '', r.wlms ?? '',
+		r.ddlx ?? '', r.jhdsl ?? '', r.wjhsl ?? '', fmtDate(r.jhrq), r.th ?? '', r.sffb ?? '',
+	]);
+	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>

+ 108 - 0
Web/src/views/aidop/s3/supply/selectItemMaster.vue

@@ -0,0 +1,108 @@
+<template>
+	<div>
+		<el-form :inline="true" :model="query" class="mb12" @submit.prevent>
+			<el-form-item label="物料编号">
+				<el-input v-model="query.itemNum" clearable style="width: 160px" />
+			</el-form-item>
+			<el-form-item label="物料名称">
+				<el-input v-model="query.descr" clearable style="width: 160px" />
+			</el-form-item>
+			<el-form-item>
+				<el-button type="primary" @click="doSearch">查询</el-button>
+				<el-button @click="reset">重置</el-button>
+			</el-form-item>
+		</el-form>
+
+		<el-table :data="rows" v-loading="loading" border stripe height="420" @sort-change="onSortChange" @row-dblclick="onDblClick">
+			<el-table-column prop="itemNum" label="物料编号" width="140" sortable="custom" />
+			<el-table-column prop="descr" label="物料名称" min-width="160" show-overflow-tooltip sortable="custom" />
+			<el-table-column prop="descr1" label="规格型号" width="140" sortable="custom" />
+			<el-table-column prop="um" label="单位" width="90" sortable="custom" />
+			<el-table-column prop="location" label="库位" width="110" sortable="custom" />
+			<el-table-column prop="rev" label="版本" width="100" sortable="custom" />
+			<el-table-column prop="drawing" label="图纸号" width="140" sortable="custom" />
+		</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>
+	</div>
+</template>
+
+<script setup lang="ts">
+import { onMounted, reactive, ref } from 'vue';
+import { fetchOutsourceItems, type ItemRow } from '../api/outsourceOrder';
+
+const emit = defineEmits<{ (e: 'picked', row: ItemRow): void }>();
+
+const query = reactive({
+	itemNum: '',
+	descr: '',
+	page: 1,
+	pageSize: 10,
+	sortField: '',
+	sortOrder: '',
+});
+const loading = ref(false);
+const rows = ref<ItemRow[]>([]);
+const total = ref(0);
+
+async function loadList() {
+	loading.value = true;
+	try {
+		const data = await fetchOutsourceItems({
+			page: query.page,
+			pageSize: query.pageSize,
+			itemNum: query.itemNum || undefined,
+			descr: query.descr || undefined,
+			sortField: query.sortField || undefined,
+			sortOrder: query.sortOrder || undefined,
+		});
+		rows.value = data.list || [];
+		total.value = data.total || 0;
+	} finally {
+		loading.value = false;
+	}
+}
+function doSearch() {
+	query.page = 1;
+	loadList();
+}
+function reset() {
+	query.itemNum = '';
+	query.descr = '';
+	query.page = 1;
+	query.sortField = '';
+	query.sortOrder = '';
+	loadList();
+}
+function onSortChange({ prop, order }: { prop: string; order: string | null }) {
+	query.sortField = prop || '';
+	query.sortOrder = order === 'ascending' ? 'asc' : order === 'descending' ? 'desc' : '';
+	loadList();
+}
+function onDblClick(row: ItemRow) {
+	emit('picked', row);
+}
+
+onMounted(loadList);
+</script>
+
+<style scoped>
+.mb12 {
+	margin-bottom: 12px;
+}
+.pager {
+	margin-top: 10px;
+	display: flex;
+	justify-content: flex-end;
+}
+</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.81</AssemblyVersion>
-    <FileVersion>1.0.81</FileVersion>
-    <Version>1.0.81</Version>
+    <AssemblyVersion>1.0.82</AssemblyVersion>
+    <FileVersion>1.0.82</FileVersion>
+    <Version>1.0.82</Version>
   </PropertyGroup>
   </PropertyGroup>
 
 
   <ItemGroup>
   <ItemGroup>

+ 2 - 2
server/Plugins/Admin.NET.Plugin.AiDOP/Controllers/S0/Supply/AdoS0SrmPurchasesController.cs

@@ -38,10 +38,10 @@ public class AdoS0SrmPurchasesController : ControllerBase
             .WhereIF(q.FactoryRefId.HasValue, x => x.FactoryRefId == q.FactoryRefId.Value)
             .WhereIF(q.FactoryRefId.HasValue, x => x.FactoryRefId == q.FactoryRefId.Value)
             .WhereIF(!string.IsNullOrWhiteSpace(q.DomainCode), x => x.DomainCode == q.DomainCode)
             .WhereIF(!string.IsNullOrWhiteSpace(q.DomainCode), x => x.DomainCode == q.DomainCode)
             .WhereIF(!string.IsNullOrWhiteSpace(q.SupplierType), x => x.SupplierType != null && x.SupplierType.Contains(q.SupplierType!))
             .WhereIF(!string.IsNullOrWhiteSpace(q.SupplierType), x => x.SupplierType != null && x.SupplierType.Contains(q.SupplierType!))
-            .WhereIF(q.IsActive.HasValue, x => x.IsActive == q.IsActive.Value)
+            .WhereIF(!string.IsNullOrWhiteSpace(q.IsActive), x => x.IsActive != null && x.IsActive.Contains(q.IsActive!))
             .WhereIF(!string.IsNullOrWhiteSpace(q.SupplierName), x => x.SupplierName != null && x.SupplierName.Contains(q.SupplierName!))
             .WhereIF(!string.IsNullOrWhiteSpace(q.SupplierName), x => x.SupplierName != null && x.SupplierName.Contains(q.SupplierName!))
             .WhereIF(!string.IsNullOrWhiteSpace(q.SupplierNumber), x => x.SupplierNumber != null && x.SupplierNumber.Contains(q.SupplierNumber!))
             .WhereIF(!string.IsNullOrWhiteSpace(q.SupplierNumber), x => x.SupplierNumber != null && x.SupplierNumber.Contains(q.SupplierNumber!))
-            .WhereIF(!string.IsNullOrWhiteSpace(q.CurrencyType), x => x.CurrencyType != null && x.CurrencyType.Contains(q.CurrencyType!));
+            .WhereIF(q.CurrencyType.HasValue, x => x.CurrencyType == q.CurrencyType.Value);
 
 
         if (!string.IsNullOrWhiteSpace(q.Keyword))
         if (!string.IsNullOrWhiteSpace(q.Keyword))
         {
         {

+ 6 - 6
server/Plugins/Admin.NET.Plugin.AiDOP/Dto/S0/Supply/AdoS0SupplyDtos.cs

@@ -127,10 +127,10 @@ public class AdoS0SrmPurchaseQueryDto
     public string? Keyword { get; set; }
     public string? Keyword { get; set; }
 
 
     public string? SupplierType { get; set; }
     public string? SupplierType { get; set; }
-    public bool? IsActive { get; set; }
+    public string? IsActive { get; set; }
     public string? SupplierName { get; set; }
     public string? SupplierName { get; set; }
     public string? SupplierNumber { get; set; }
     public string? SupplierNumber { get; set; }
-    public string? CurrencyType { get; set; }
+    public long? CurrencyType { get; set; }
 
 
     public int Page { get; set; } = 1;
     public int Page { get; set; } = 1;
     public int PageSize { get; set; } = 20;
     public int PageSize { get; set; } = 20;
@@ -151,7 +151,7 @@ public class AdoS0SrmPurchaseUpsertDto
 
 
     public string? IcitemName { get; set; }
     public string? IcitemName { get; set; }
     public string? SupplierType { get; set; }
     public string? SupplierType { get; set; }
-    public bool IsActive { get; set; } = true;
+    public string? IsActive { get; set; }
 
 
     [Range(1, long.MaxValue, ErrorMessage = "供应商不能为空")]
     [Range(1, long.MaxValue, ErrorMessage = "供应商不能为空")]
     public long SupplierId { get; set; }
     public long SupplierId { get; set; }
@@ -160,7 +160,7 @@ public class AdoS0SrmPurchaseUpsertDto
     public string? SupplierNumber { get; set; }
     public string? SupplierNumber { get; set; }
 
 
     public decimal? OrderPrice { get; set; }
     public decimal? OrderPrice { get; set; }
-    public string? CurrencyType { get; set; }
+    public long? CurrencyType { get; set; }
     public decimal? Taxrate { get; set; }
     public decimal? Taxrate { get; set; }
     public decimal? Tariff { get; set; }
     public decimal? Tariff { get; set; }
     public decimal? Freight { get; set; }
     public decimal? Freight { get; set; }
@@ -168,12 +168,12 @@ public class AdoS0SrmPurchaseUpsertDto
     public DateTime? EffectiveDate { get; set; }
     public DateTime? EffectiveDate { get; set; }
     public DateTime? ExpiringDate { get; set; }
     public DateTime? ExpiringDate { get; set; }
     public decimal? QuotaRate { get; set; }
     public decimal? QuotaRate { get; set; }
-    public int? LeadTime { get; set; }
+    public decimal? LeadTime { get; set; }
     public decimal? QtyMin { get; set; }
     public decimal? QtyMin { get; set; }
     public decimal? PackagingQty { get; set; }
     public decimal? PackagingQty { get; set; }
     public string? OrderRectorName { get; set; }
     public string? OrderRectorName { get; set; }
     public string? OrderRectorNum { get; set; }
     public string? OrderRectorNum { get; set; }
-    public bool IsRequireGoods { get; set; }
+    public int IsRequireGoods { get; set; }
 
 
     public string? CreateUser { get; set; }
     public string? CreateUser { get; set; }
     public string? UpdateUser { get; set; }
     public string? UpdateUser { get; set; }

+ 44 - 2
server/Plugins/Admin.NET.Plugin.AiDOP/Entity/S0/Manufacturing/AdoS0ProductStructure.cs

@@ -73,8 +73,20 @@ public class AdoS0ProductStructureMaster : ITenantIdFilter
 [SugarIndex("ix_ProductStructureOp_factory_pc", nameof(CompanyRefId), OrderByType.Asc, nameof(FactoryRefId), OrderByType.Asc, nameof(ParentMaterialId), OrderByType.Asc, nameof(ComponentMaterialId), OrderByType.Asc)]
 [SugarIndex("ix_ProductStructureOp_factory_pc", nameof(CompanyRefId), OrderByType.Asc, nameof(FactoryRefId), OrderByType.Asc, nameof(ParentMaterialId), OrderByType.Asc, nameof(ComponentMaterialId), OrderByType.Asc)]
 public class AdoS0ProductStructureOp : ITenantIdFilter
 public class AdoS0ProductStructureOp : ITenantIdFilter
 {
 {
-    [SugarColumn(ColumnName = "RecID", ColumnDescription = "主键", IsPrimaryKey = true, IsIdentity = true, ColumnDataType = "bigint")]
-    public long Id { get; set; }
+    [SugarColumn(ColumnName = "RecID", ColumnDescription = "主键", IsPrimaryKey = true, IsIdentity = true)]
+    public int Id { get; set; }
+
+    [SugarColumn(ColumnName = "Domain", Length = 16, IsNullable = true)]
+    public string? Domain { get; set; }
+
+    [SugarColumn(ColumnName = "ParentItem", Length = 48, IsNullable = true)]
+    public string? ParentItem { get; set; }
+
+    [SugarColumn(ColumnName = "ComponentItem", Length = 48, IsNullable = true)]
+    public string? ComponentItem { get; set; }
+
+    [SugarColumn(ColumnName = "ProductItem", Length = 48, IsNullable = true)]
+    public string? ProductItem { get; set; }
 
 
     [SugarColumn(ColumnDescription = "公司", ColumnDataType = "bigint")]
     [SugarColumn(ColumnDescription = "公司", ColumnDataType = "bigint")]
     public long CompanyRefId { get; set; }
     public long CompanyRefId { get; set; }
@@ -94,6 +106,36 @@ public class AdoS0ProductStructureOp : ITenantIdFilter
     [SugarColumn(ColumnName = "Op", ColumnDescription = "工序号")]
     [SugarColumn(ColumnName = "Op", ColumnDescription = "工序号")]
     public int Op { get; set; }
     public int Op { get; set; }
 
 
+    [SugarColumn(ColumnName = "Refs", Length = 24, IsNullable = true)]
+    public string? Refs { get; set; }
+
+    [SugarColumn(ColumnName = "Ufld1", Length = 120, IsNullable = true)]
+    public string? Ufld1 { get; set; }
+
+    [SugarColumn(ColumnName = "Ufld2", Length = 120, IsNullable = true)]
+    public string? Ufld2 { get; set; }
+
+    [SugarColumn(ColumnName = "IsActive", ColumnDataType = "bit(1)", IsNullable = true)]
+    public bool? IsActive { get; set; }
+
+    [SugarColumn(ColumnName = "IsConfirm", ColumnDataType = "bit(1)", IsNullable = true)]
+    public bool? IsConfirm { get; set; }
+
+    [SugarColumn(ColumnName = "CreateUser", Length = 24, IsNullable = true)]
+    public string? CreateUser { get; set; }
+
+    [SugarColumn(ColumnName = "UpdateUser", Length = 24, IsNullable = true)]
+    public string? UpdateUser { get; set; }
+
+    [SugarColumn(ColumnName = "CreateTime", IsNullable = true)]
+    public DateTime? CreateTime { get; set; }
+
+    [SugarColumn(ColumnName = "UpdateTime", IsNullable = true)]
+    public DateTime? UpdateTime { get; set; }
+
+    [SugarColumn(ColumnName = "Id", ColumnDataType = "bigint")]
+    public long BizId { get; set; }
+
     [SugarColumn(ColumnDescription = "排序")]
     [SugarColumn(ColumnDescription = "排序")]
     public int SortNo { get; set; }
     public int SortNo { get; set; }
 
 

+ 22 - 1
server/Plugins/Admin.NET.Plugin.AiDOP/Entity/S0/Sales/IcSubstitute.cs

@@ -7,27 +7,48 @@ namespace Admin.NET.Plugin.AiDOP.Entity.S0.Sales;
 [SugarTable("ic_substitute", "替代方案(源平台表映射)")]
 [SugarTable("ic_substitute", "替代方案(源平台表映射)")]
 public class IcSubstitute : ITenantIdFilter
 public class IcSubstitute : ITenantIdFilter
 {
 {
-    [SugarColumn(ColumnName = "Id", ColumnDescription = "主键", IsPrimaryKey = true, ColumnDataType = "bigint")]
+    [SugarColumn(ColumnName = "id", ColumnDescription = "主键", IsPrimaryKey = true, ColumnDataType = "bigint")]
     public long Id { get; set; }
     public long Id { get; set; }
 
 
     [SugarColumn(ColumnName = "substitute_code", ColumnDescription = "替代方案编码", IsNullable = true, Length = 100)]
     [SugarColumn(ColumnName = "substitute_code", ColumnDescription = "替代方案编码", IsNullable = true, Length = 100)]
     public string? SubstituteCode { get; set; }
     public string? SubstituteCode { get; set; }
 
 
+    [SugarColumn(ColumnName = "substitute_type", ColumnDescription = "替代类型", IsNullable = true)]
+    public int? SubstituteType { get; set; }
+
     [SugarColumn(ColumnName = "substitute_strategy", ColumnDescription = "替代策略", IsNullable = true, Length = 100)]
     [SugarColumn(ColumnName = "substitute_strategy", ColumnDescription = "替代策略", IsNullable = true, Length = 100)]
     public string? SubstituteStrategy { get; set; }
     public string? SubstituteStrategy { get; set; }
 
 
     [SugarColumn(ColumnName = "substitute_mode", ColumnDescription = "替代方式", IsNullable = true, Length = 100)]
     [SugarColumn(ColumnName = "substitute_mode", ColumnDescription = "替代方式", IsNullable = true, Length = 100)]
     public string? SubstituteMode { get; set; }
     public string? SubstituteMode { get; set; }
 
 
+    [SugarColumn(ColumnName = "create_by", ColumnDescription = "创建人ID", IsNullable = true, ColumnDataType = "bigint")]
+    public long? CreateBy { get; set; }
+
+    [SugarColumn(ColumnName = "create_by_name", ColumnDescription = "创建人姓名", IsNullable = true, Length = 50)]
+    public string? CreateByName { get; set; }
+
     [SugarColumn(ColumnName = "create_time", ColumnDescription = "创建时间", IsNullable = true)]
     [SugarColumn(ColumnName = "create_time", ColumnDescription = "创建时间", IsNullable = true)]
     public DateTime? CreateTime { get; set; }
     public DateTime? CreateTime { get; set; }
 
 
+    [SugarColumn(ColumnName = "update_by", ColumnDescription = "更新人ID", IsNullable = true, ColumnDataType = "bigint")]
+    public long? UpdateBy { get; set; }
+
+    [SugarColumn(ColumnName = "update_by_name", ColumnDescription = "更新人姓名", IsNullable = true, Length = 50)]
+    public string? UpdateByName { get; set; }
+
+    [SugarColumn(ColumnName = "update_time", ColumnDescription = "更新时间", IsNullable = true)]
+    public DateTime? UpdateTime { get; set; }
+
     [SugarColumn(ColumnName = "company_id", ColumnDescription = "公司ID", IsNullable = true, ColumnDataType = "bigint")]
     [SugarColumn(ColumnName = "company_id", ColumnDescription = "公司ID", IsNullable = true, ColumnDataType = "bigint")]
     public long? CompanyId { get; set; }
     public long? CompanyId { get; set; }
 
 
     [SugarColumn(ColumnName = "factory_id", ColumnDescription = "工厂ID", IsNullable = true, ColumnDataType = "bigint")]
     [SugarColumn(ColumnName = "factory_id", ColumnDescription = "工厂ID", IsNullable = true, ColumnDataType = "bigint")]
     public long? FactoryId { get; set; }
     public long? FactoryId { get; set; }
 
 
+    [SugarColumn(ColumnName = "org_id", ColumnDescription = "组织ID", IsNullable = true, ColumnDataType = "bigint")]
+    public long? OrgId { get; set; }
+
     [SugarColumn(ColumnName = "IsDeleted", ColumnDescription = "逻辑删除标记", IsNullable = true, ColumnDataType = "int")]
     [SugarColumn(ColumnName = "IsDeleted", ColumnDescription = "逻辑删除标记", IsNullable = true, ColumnDataType = "int")]
     public int? IsDeleted { get; set; }
     public int? IsDeleted { get; set; }
 
 

+ 71 - 8
server/Plugins/Admin.NET.Plugin.AiDOP/Entity/S0/Supply/AdoS0SrmPurchase.cs

@@ -27,8 +27,8 @@ public class AdoS0SrmPurchase : ITenantIdFilter
     [SugarColumn(ColumnName = "supplier_type", ColumnDescription = "供应类别", Length = 50, IsNullable = true)]
     [SugarColumn(ColumnName = "supplier_type", ColumnDescription = "供应类别", Length = 50, IsNullable = true)]
     public string? SupplierType { get; set; }
     public string? SupplierType { get; set; }
 
 
-    [SugarColumn(ColumnName = "is_active", ColumnDescription = "是否启用", ColumnDataType = "boolean")]
-    public bool IsActive { get; set; } = true;
+    [SugarColumn(ColumnName = "is_active", ColumnDescription = "是否启用", Length = 64, IsNullable = true)]
+    public string? IsActive { get; set; }
 
 
     [SugarColumn(ColumnName = "supplier_id", ColumnDescription = "供应商 ID(FK 供应商主数据)", ColumnDataType = "bigint")]
     [SugarColumn(ColumnName = "supplier_id", ColumnDescription = "供应商 ID(FK 供应商主数据)", ColumnDataType = "bigint")]
     public long SupplierId { get; set; }
     public long SupplierId { get; set; }
@@ -42,8 +42,8 @@ public class AdoS0SrmPurchase : ITenantIdFilter
     [SugarColumn(ColumnName = "order_price", ColumnDescription = "价格", ColumnDataType = "decimal(18,4)", IsNullable = true)]
     [SugarColumn(ColumnName = "order_price", ColumnDescription = "价格", ColumnDataType = "decimal(18,4)", IsNullable = true)]
     public decimal? OrderPrice { get; set; }
     public decimal? OrderPrice { get; set; }
 
 
-    [SugarColumn(ColumnName = "currency_type", ColumnDescription = "币种", Length = 20, IsNullable = true)]
-    public string? CurrencyType { get; set; }
+    [SugarColumn(ColumnName = "currency_type", ColumnDescription = "币种", ColumnDataType = "bigint", IsNullable = true)]
+    public long? CurrencyType { get; set; }
 
 
     [SugarColumn(ColumnName = "taxrate", ColumnDescription = "增值税率(%)", ColumnDataType = "decimal(18,4)", IsNullable = true)]
     [SugarColumn(ColumnName = "taxrate", ColumnDescription = "增值税率(%)", ColumnDataType = "decimal(18,4)", IsNullable = true)]
     public decimal? Taxrate { get; set; }
     public decimal? Taxrate { get; set; }
@@ -66,8 +66,8 @@ public class AdoS0SrmPurchase : ITenantIdFilter
     [SugarColumn(ColumnName = "quota_rate", ColumnDescription = "配额比例(%)", ColumnDataType = "decimal(18,4)", IsNullable = true)]
     [SugarColumn(ColumnName = "quota_rate", ColumnDescription = "配额比例(%)", ColumnDataType = "decimal(18,4)", IsNullable = true)]
     public decimal? QuotaRate { get; set; }
     public decimal? QuotaRate { get; set; }
 
 
-    [SugarColumn(ColumnName = "lead_time", ColumnDescription = "采购前置期(天)", IsNullable = true)]
-    public int? LeadTime { get; set; }
+    [SugarColumn(ColumnName = "lead_time", ColumnDescription = "采购前置期(天)", ColumnDataType = "decimal(18,5)", IsNullable = true)]
+    public decimal? LeadTime { get; set; }
 
 
     [SugarColumn(ColumnName = "qty_min", ColumnDescription = "最小订货数量", ColumnDataType = "decimal(18,4)", IsNullable = true)]
     [SugarColumn(ColumnName = "qty_min", ColumnDescription = "最小订货数量", ColumnDataType = "decimal(18,4)", IsNullable = true)]
     public decimal? QtyMin { get; set; }
     public decimal? QtyMin { get; set; }
@@ -81,8 +81,8 @@ public class AdoS0SrmPurchase : ITenantIdFilter
     [SugarColumn(ColumnName = "order_rector_num", ColumnDescription = "下单负责人编号", Length = 100, IsNullable = true)]
     [SugarColumn(ColumnName = "order_rector_num", ColumnDescription = "下单负责人编号", Length = 100, IsNullable = true)]
     public string? OrderRectorNum { get; set; }
     public string? OrderRectorNum { get; set; }
 
 
-    [SugarColumn(ColumnName = "IsRequireGoods", ColumnDescription = "采购类型(需到货)", ColumnDataType = "boolean")]
-    public bool IsRequireGoods { get; set; }
+    [SugarColumn(ColumnName = "IsRequireGoods", ColumnDescription = "采购类型(需到货)", ColumnDataType = "int")]
+    public int IsRequireGoods { get; set; }
 
 
     [SugarColumn(ColumnName = "create_user", ColumnDescription = "创建人", Length = 100, IsNullable = true)]
     [SugarColumn(ColumnName = "create_user", ColumnDescription = "创建人", Length = 100, IsNullable = true)]
     public string? CreateUser { get; set; }
     public string? CreateUser { get; set; }
@@ -96,6 +96,69 @@ public class AdoS0SrmPurchase : ITenantIdFilter
     [SugarColumn(ColumnName = "update_time", ColumnDescription = "更新时间", IsNullable = true)]
     [SugarColumn(ColumnName = "update_time", ColumnDescription = "更新时间", IsNullable = true)]
     public DateTime? UpdateTime { get; set; }
     public DateTime? UpdateTime { get; set; }
 
 
+    [SugarColumn(ColumnName = "number", ColumnDescription = "物料编码", Length = 255, IsNullable = true)]
+    public string? Number { get; set; }
+
+    [SugarColumn(ColumnName = "sourcelist_number", ColumnDescription = "货源清单编号", Length = 255, IsNullable = true)]
+    public string? SourcelistNumber { get; set; }
+
+    [SugarColumn(ColumnName = "purchgroup", ColumnDescription = "采购组", Length = 255, IsNullable = true)]
+    public string? Purchgroup { get; set; }
+
+    [SugarColumn(ColumnName = "purcher", ColumnDescription = "采购员", Length = 255, IsNullable = true)]
+    public string? Purcher { get; set; }
+
+    [SugarColumn(ColumnName = "purchase_unit", ColumnDescription = "采购单位", Length = 255, IsNullable = true)]
+    public string? PurchaseUnit { get; set; }
+
+    [SugarColumn(ColumnName = "netpurchase_price", ColumnDescription = "净采购价", ColumnDataType = "decimal(18,6)", IsNullable = true)]
+    public decimal? NetpurchasePrice { get; set; }
+
+    [SugarColumn(ColumnName = "factory_code", ColumnDescription = "工厂编码", Length = 255, IsNullable = true)]
+    public string? FactoryCode { get; set; }
+
+    [SugarColumn(ColumnName = "order_dept", ColumnDescription = "下单部门", Length = 255, IsNullable = true)]
+    public string? OrderDept { get; set; }
+
+    [SugarColumn(ColumnName = "sale_price", ColumnDescription = "销售价格", ColumnDataType = "decimal(18,6)", IsNullable = true)]
+    public decimal? SalePrice { get; set; }
+
+    [SugarColumn(ColumnName = "batch_append_qty", ColumnDescription = "批量追加数量", ColumnDataType = "decimal(18,6)", IsNullable = true)]
+    public decimal? BatchAppendQty { get; set; }
+
+    [SugarColumn(ColumnName = "is_week", ColumnDescription = "是否按周", ColumnDataType = "tinyint(1)", IsNullable = true)]
+    public bool? IsWeek { get; set; }
+
+    [SugarColumn(ColumnName = "is_days", ColumnDescription = "天数设置", Length = 255, IsNullable = true)]
+    public string? IsDays { get; set; }
+
+    [SugarColumn(ColumnName = "quota_priority", ColumnDescription = "配额优先级", IsNullable = false)]
+    public int QuotaPriority { get; set; }
+
+    [SugarColumn(ColumnName = "create_by", ColumnDescription = "创建人ID", ColumnDataType = "bigint", IsNullable = true)]
+    public long? CreateBy { get; set; }
+
+    [SugarColumn(ColumnName = "create_by_name", ColumnDescription = "创建人姓名", Length = 50, IsNullable = true)]
+    public string? CreateByName { get; set; }
+
+    [SugarColumn(ColumnName = "update_by", ColumnDescription = "更新人ID", ColumnDataType = "bigint", IsNullable = true)]
+    public long? UpdateBy { get; set; }
+
+    [SugarColumn(ColumnName = "update_by_name", ColumnDescription = "更新人姓名", Length = 50, IsNullable = true)]
+    public string? UpdateByName { get; set; }
+
+    [SugarColumn(ColumnName = "company_id", ColumnDescription = "公司ID", ColumnDataType = "bigint", IsNullable = true)]
+    public long? CompanyId { get; set; }
+
+    [SugarColumn(ColumnName = "factory_id", ColumnDescription = "工厂ID", ColumnDataType = "bigint", IsNullable = true)]
+    public long? FactoryId { get; set; }
+
+    [SugarColumn(ColumnName = "org_id", ColumnDescription = "组织ID", ColumnDataType = "bigint", IsNullable = true)]
+    public long? OrgId { get; set; }
+
+    [SugarColumn(ColumnName = "IsDeleted", ColumnDescription = "是否删除", ColumnDataType = "tinyint(1)", IsNullable = false)]
+    public bool IsDeleted { get; set; }
+
     /// <summary>列表:物料编码(来自物料主数据,不落库)</summary>
     /// <summary>列表:物料编码(来自物料主数据,不落库)</summary>
     [SugarColumn(IsIgnore = true)]
     [SugarColumn(IsIgnore = true)]
     public string? MaterialCode { get; set; }
     public string? MaterialCode { get; set; }

+ 11 - 1
server/Plugins/Admin.NET.Plugin.AiDOP/Infrastructure/AidopMenuLinkSync.cs

@@ -472,7 +472,7 @@ public static class AidopMenuLinkSync
     /// </summary>
     /// </summary>
     private static void NormalizeS3SupplyMenus(ISqlSugarClient db)
     private static void NormalizeS3SupplyMenus(ISqlSugarClient db)
     {
     {
-        const long s3RootId = 1321000003000L;
+        const long s3RootId = 1321000004000L;
         const long parentId = 1322000000009L;
         const long parentId = 1322000000009L;
         const long procurementDirId = 1322000000010L;
         const long procurementDirId = 1322000000010L;
 
 
@@ -597,6 +597,16 @@ public static class AidopMenuLinkSync
             "/aidop/s3/procurement/purchase-request", "aidopS3PurchaseRequest",
             "/aidop/s3/procurement/purchase-request", "aidopS3PurchaseRequest",
             "/aidop/s3/supply/purchaseRequestList", "ele-Document", 10, "S3 物料采购申请(srm_pr_main)");
             "/aidop/s3/supply/purchaseRequestList", "ele-Document", 10, "S3 物料采购申请(srm_pr_main)");
 
 
+        EnsureS3LeafMenu(
+            db, ct, 1329003100012L, procurementDirId, "物料采购订单",
+            "/aidop/s3/procurement/purchase-order", "aidopS3PurchaseOrder",
+            "/aidop/s3/supply/purchaseOrderList", "ele-Tickets", 20, "S3 物料采购订单(vscm_jhjh)");
+
+        EnsureS3LeafMenu(
+            db, ct, 1329003100013L, procurementDirId, "委外加工订单",
+            "/aidop/s3/procurement/outsource-order", "aidopS3OutsourceOrder",
+            "/aidop/s3/supply/outsourceOrderList", "ele-List", 30, "S3 委外加工订单(PurOrdMaster/PW)");
+
         EnsureS3LeafMenu(
         EnsureS3LeafMenu(
             db, ct, 1329003100010L, s3RootId, "供应协同看板",
             db, ct, 1329003100010L, s3RootId, "供应协同看板",
             "/aidop/s3/supply-kanban", "aidopS3SupplyKanban",
             "/aidop/s3/supply-kanban", "aidopS3SupplyKanban",

+ 31 - 1
server/Plugins/Admin.NET.Plugin.AiDOP/SeedData/SysMenuSeedData.cs

@@ -1199,7 +1199,7 @@ public class SysMenuSeedData : ISqlSugarEntitySeedData<SysMenu>
     {
     {
         const long s3MaterialPlanDirId = 1322000000009L;
         const long s3MaterialPlanDirId = 1322000000009L;
         const long s3ProcurementDirId = 1322000000010L;
         const long s3ProcurementDirId = 1322000000010L;
-        const long s3RootDirId = 1321000003000L;
+        const long s3RootDirId = 1321000004000L;
 
 
         yield return new SysMenu
         yield return new SysMenu
         {
         {
@@ -1261,6 +1261,36 @@ public class SysMenuSeedData : ISqlSugarEntitySeedData<SysMenu>
             Remark = "S3 物料采购申请(srm_pr_main)"
             Remark = "S3 物料采购申请(srm_pr_main)"
         };
         };
 
 
+        yield return new SysMenu
+        {
+            Id = 1329003100012L,
+            Pid = s3ProcurementDirId,
+            Title = "物料采购订单",
+            Path = "/aidop/s3/procurement/purchase-order",
+            Name = "aidopS3PurchaseOrder",
+            Component = "/aidop/s3/supply/purchaseOrderList",
+            Icon = "ele-Tickets",
+            Type = MenuTypeEnum.Menu,
+            CreateTime = ct,
+            OrderNo = 20,
+            Remark = "S3 物料采购订单(vscm_jhjh)"
+        };
+
+        yield return new SysMenu
+        {
+            Id = 1329003100013L,
+            Pid = s3ProcurementDirId,
+            Title = "委外加工订单",
+            Path = "/aidop/s3/procurement/outsource-order",
+            Name = "aidopS3OutsourceOrder",
+            Component = "/aidop/s3/supply/outsourceOrderList",
+            Icon = "ele-List",
+            Type = MenuTypeEnum.Menu,
+            CreateTime = ct,
+            OrderNo = 30,
+            Remark = "S3 委外加工订单(PurOrdMaster/PW)"
+        };
+
         yield return new SysMenu
         yield return new SysMenu
         {
         {
             Id = 1329003100010L,
             Id = 1329003100010L,

+ 76 - 0
server/Plugins/Admin.NET.Plugin.AiDOP/Supply/Dto/OutsourceOrderDto.cs

@@ -0,0 +1,76 @@
+namespace Admin.NET.Plugin.AiDOP.Supply;
+
+public class OutsourceOrderListInput
+{
+    public int Page { get; set; } = 1;
+    public int PageSize { get; set; } = 10;
+    public string? PurOrd { get; set; }
+    public string? Buyer { get; set; }
+    public string? Supp { get; set; }
+    public string? SortField { get; set; }
+    public string? SortOrder { get; set; }
+}
+
+public class OutsourceOrderSaveInput
+{
+    public int? Id { get; set; }
+    public string? PurOrd { get; set; }
+    public string? OrdDate { get; set; }
+    public string? Supp { get; set; }
+    public string? ReqBy { get; set; }
+    public string? Buyer { get; set; }
+    public string? Department { get; set; }
+    public string? Usage { get; set; }
+    public string? Remark { get; set; }
+}
+
+public class OutsourceOrderDetailListInput
+{
+    public int Page { get; set; } = 1;
+    public int PageSize { get; set; } = 10;
+    public string? PurOrd { get; set; }
+    public string? ItemNum { get; set; }
+    public string? Location { get; set; }
+    public string? WorkOrd { get; set; }
+    public string? LotSerial { get; set; }
+    public string? SortField { get; set; }
+    public string? SortOrder { get; set; }
+}
+
+public class OutsourceOrderDetailSaveInput
+{
+    public int? Id { get; set; }
+    public int PurOrdRecID { get; set; }
+    public string PurOrd { get; set; } = string.Empty;
+    public int? Line { get; set; }
+    public string? ItemNum { get; set; }
+    public decimal? QtyOrded { get; set; }
+    public string? DueDate { get; set; }
+    public string? Potype { get; set; }
+    public List<OutsourceOrderDetailBatchInput> Batches { get; set; } = new();
+}
+
+public class OutsourceOrderDetailBatchInput
+{
+    public int? Batch { get; set; }
+    public string? ItemNum { get; set; }
+    public string? SuppItem { get; set; }
+    public string? UM { get; set; }
+    public string? Location { get; set; }
+    public decimal? QtyOrded { get; set; }
+    public decimal? QtyBO { get; set; }
+    public decimal? QtyReleased { get; set; }
+    public decimal? QtyReceived { get; set; }
+    public decimal? QtyReturned { get; set; }
+    public string? LotSerial { get; set; }
+}
+
+public class ItemSelectListInput
+{
+    public int Page { get; set; } = 1;
+    public int PageSize { get; set; } = 10;
+    public string? ItemNum { get; set; }
+    public string? Descr { get; set; }
+    public string? SortField { get; set; }
+    public string? SortOrder { get; set; }
+}

+ 13 - 0
server/Plugins/Admin.NET.Plugin.AiDOP/Supply/Dto/PurchaseOrderDto.cs

@@ -0,0 +1,13 @@
+namespace Admin.NET.Plugin.AiDOP.Supply;
+
+public class PurchaseOrderListInput
+{
+    public int Page { get; set; } = 1;
+    public int PageSize { get; set; } = 10;
+    public string? Cgdd { get; set; }
+    public string? Wlbm { get; set; }
+    public string? Gysdm { get; set; }
+    public string? Sffbcx { get; set; }
+    public string? SortField { get; set; }
+    public string? SortOrder { get; set; }
+}

+ 515 - 0
server/Plugins/Admin.NET.Plugin.AiDOP/Supply/Entity/PurOrdDetail.cs

@@ -0,0 +1,515 @@
+namespace Admin.NET.Plugin.AiDOP.Supply;
+
+/// <summary>
+/// 采购订单明细(PurOrdDetail)
+/// </summary>
+[SugarTable("PurOrdDetail", "采购订单明细")]
+public class PurOrdDetail
+{
+    [SugarColumn(ColumnName = "RecID", IsPrimaryKey = true, IsIdentity = true)]
+    public int RecID { get; set; }
+
+    [SugarColumn(ColumnName = "PurAcct", Length = 8, IsNullable = true)]
+    public string? PurAcct { get; set; }
+
+    [SugarColumn(ColumnName = "ApprovedBy", Length = 8, IsNullable = true)]
+    public string? ApprovedBy { get; set; }
+
+    [SugarColumn(ColumnName = "ApprovalCode", Length = 8, IsNullable = true)]
+    public string? ApprovalCode { get; set; }
+
+    [SugarColumn(ColumnName = "Assay", ColumnDataType = "decimal(18,5)", IsNullable = true)]
+    public decimal? Assay { get; set; }
+
+    [SugarColumn(ColumnName = "Blanket", Length = 8, IsNullable = true)]
+    public string? Blanket { get; set; }
+
+    [SugarColumn(ColumnName = "BlanketLine")]
+    public int BlanketLine { get; set; }
+
+    [SugarColumn(ColumnName = "QtyBO", ColumnDataType = "decimal(12,5)")]
+    public decimal QtyBO { get; set; }
+
+    [SugarColumn(ColumnName = "CostCtr", Length = 4, IsNullable = true)]
+    public string? CostCtr { get; set; }
+
+    [SugarColumn(ColumnName = "CommentIndex")]
+    public int CommentIndex { get; set; }
+
+    [SugarColumn(ColumnName = "Consignment")]
+    public short Consignment { get; set; }
+
+    [SugarColumn(ColumnName = "Contract", Length = 24, IsNullable = true)]
+    public string? Contract { get; set; }
+
+    [SugarColumn(ColumnName = "ContractID", Length = 8, IsNullable = true)]
+    public string? ContractID { get; set; }
+
+    [SugarColumn(ColumnName = "RctCost", ColumnDataType = "decimal(14,5)")]
+    public decimal RctCost { get; set; }
+
+    [SugarColumn(ColumnName = "CreditTermsInt", ColumnDataType = "decimal(9,5)")]
+    public decimal CreditTermsInt { get; set; }
+
+    [SugarColumn(ColumnName = "UpdateCurrentCost", ColumnDataType = "bit(1)")]
+    public bool UpdateCurrentCost { get; set; }
+
+    [SugarColumn(ColumnName = "CumStart1", IsNullable = true)]
+    public DateTime? CumStart1 { get; set; }
+
+    [SugarColumn(ColumnName = "CumStart2", IsNullable = true)]
+    public DateTime? CumStart2 { get; set; }
+
+    [SugarColumn(ColumnName = "CumStart3", IsNullable = true)]
+    public DateTime? CumStart3 { get; set; }
+
+    [SugarColumn(ColumnName = "CumStart4", IsNullable = true)]
+    public DateTime? CumStart4 { get; set; }
+
+    [SugarColumn(ColumnName = "CumReceived1", ColumnDataType = "decimal(15,5)")]
+    public decimal CumReceived1 { get; set; }
+
+    [SugarColumn(ColumnName = "CumReceived2", ColumnDataType = "decimal(15,5)")]
+    public decimal CumReceived2 { get; set; }
+
+    [SugarColumn(ColumnName = "CumReceived3", ColumnDataType = "decimal(15,5)")]
+    public decimal CumReceived3 { get; set; }
+
+    [SugarColumn(ColumnName = "CumReceived4", ColumnDataType = "decimal(15,5)")]
+    public decimal CumReceived4 { get; set; }
+
+    [SugarColumn(ColumnName = "ActiveRlseID1", Length = 100, IsNullable = true)]
+    public string? ActiveRlseID1 { get; set; }
+
+    [SugarColumn(ColumnName = "ActiveRlseID2", Length = 100, IsNullable = true)]
+    public string? ActiveRlseID2 { get; set; }
+
+    [SugarColumn(ColumnName = "ActiveRlseID3", Length = 100, IsNullable = true)]
+    public string? ActiveRlseID3 { get; set; }
+
+    [SugarColumn(ColumnName = "Disc", ColumnDataType = "decimal(7,5)")]
+    public decimal Disc { get; set; }
+
+    [SugarColumn(ColumnName = "Dock", Length = 8, IsNullable = true)]
+    public string? Dock { get; set; }
+
+    [SugarColumn(ColumnName = "Domain", Length = 24, IsNullable = true)]
+    public string? Domain { get; set; }
+
+    [SugarColumn(ColumnName = "DRPRef", Length = 8, IsNullable = true)]
+    public string? DRPRef { get; set; }
+
+    [SugarColumn(ColumnName = "DueDate", IsNullable = true)]
+    public DateTime? DueDate { get; set; }
+
+    [SugarColumn(ColumnName = "EndEff1", IsNullable = true)]
+    public DateTime? EndEff1 { get; set; }
+
+    [SugarColumn(ColumnName = "EndEff2", IsNullable = true)]
+    public DateTime? EndEff2 { get; set; }
+
+    [SugarColumn(ColumnName = "EndEff3", IsNullable = true)]
+    public DateTime? EndEff3 { get; set; }
+
+    [SugarColumn(ColumnName = "EndEff4", IsNullable = true)]
+    public DateTime? EndEff4 { get; set; }
+
+    [SugarColumn(ColumnName = "ERSOption")]
+    public int ERSOption { get; set; }
+
+    [SugarColumn(ColumnName = "ExpireDate", IsNullable = true)]
+    public DateTime? ExpireDate { get; set; }
+
+    [SugarColumn(ColumnName = "FabAuthDays")]
+    public int FabAuthDays { get; set; }
+
+    [SugarColumn(ColumnName = "FabAuthWeeks")]
+    public int FabAuthWeeks { get; set; }
+
+    [SugarColumn(ColumnName = "FirmDays")]
+    public int FirmDays { get; set; }
+
+    [SugarColumn(ColumnName = "FirmWeeks")]
+    public int FirmWeeks { get; set; }
+
+    [SugarColumn(ColumnName = "FixedPrice", ColumnDataType = "bit(1)")]
+    public bool FixedPrice { get; set; }
+
+    [SugarColumn(ColumnName = "FrtList", Length = 8, IsNullable = true)]
+    public string? FrtList { get; set; }
+
+    [SugarColumn(ColumnName = "FSMType", Length = 8, IsNullable = true)]
+    public string? FSMType { get; set; }
+
+    [SugarColumn(ColumnName = "Grade", Length = 2, IsNullable = true)]
+    public string? Grade { get; set; }
+
+    [SugarColumn(ColumnName = "InspectReq", ColumnDataType = "bit(1)")]
+    public bool InspectReq { get; set; }
+
+    [SugarColumn(ColumnName = "Line")]
+    public short Line { get; set; }
+
+    [SugarColumn(ColumnName = "Location", Length = 8, IsNullable = true)]
+    public string? Location { get; set; }
+
+    [SugarColumn(ColumnName = "NextLotSerial", Length = 24, IsNullable = true)]
+    public string? NextLotSerial { get; set; }
+
+    [SugarColumn(ColumnName = "SingleLot", ColumnDataType = "bit(1)")]
+    public bool SingleLot { get; set; }
+
+    [SugarColumn(ColumnName = "MaxAgingDays")]
+    public int MaxAgingDays { get; set; }
+
+    [SugarColumn(ColumnName = "SupplyPer", ColumnDataType = "decimal(8,5)")]
+    public decimal SupplyPer { get; set; }
+
+    [SugarColumn(ColumnName = "MultiDueDates")]
+    public short MultiDueDates { get; set; }
+
+    [SugarColumn(ColumnName = "PurOrd", Length = 24)]
+    public string PurOrd { get; set; } = string.Empty;
+
+    [SugarColumn(ColumnName = "NeedDate", IsNullable = true)]
+    public DateTime? NeedDate { get; set; }
+
+    [SugarColumn(ColumnName = "Op")]
+    public int Op { get; set; }
+
+    [SugarColumn(ColumnName = "StdPackQty", ColumnDataType = "decimal(10,0)", IsNullable = true)]
+    public decimal? StdPackQty { get; set; }
+
+    [SugarColumn(ColumnName = "ItemNum", Length = 60, IsNullable = true)]
+    public string? ItemNum { get; set; }
+
+    [SugarColumn(ColumnName = "Dimension1", Length = 8)]
+    public string Dimension1 { get; set; } = string.Empty;
+
+    [SugarColumn(ColumnName = "Dimension2", Length = 8)]
+    public string Dimension2 { get; set; } = string.Empty;
+
+    [SugarColumn(ColumnName = "PastDueLogic", Length = 1, IsNullable = true)]
+    public string? PastDueLogic { get; set; }
+
+    [SugarColumn(ColumnName = "PayUM", Length = 2, IsNullable = true)]
+    public string? PayUM { get; set; }
+
+    [SugarColumn(ColumnName = "PerformDate", IsNullable = true)]
+    public DateTime? PerformDate { get; set; }
+
+    [SugarColumn(ColumnName = "ProjectLine")]
+    public int ProjectLine { get; set; }
+
+    [SugarColumn(ColumnName = "PkgCode", Length = 60, IsNullable = true)]
+    public string? PkgCode { get; set; }
+
+    [SugarColumn(ColumnName = "PlanSchedDays")]
+    public int PlanSchedDays { get; set; }
+
+    [SugarColumn(ColumnName = "PlanSchedMonths")]
+    public int PlanSchedMonths { get; set; }
+
+    [SugarColumn(ColumnName = "PlanSchedWeeks")]
+    public int PlanSchedWeeks { get; set; }
+
+    [SugarColumn(ColumnName = "PODatabase", Length = 8, IsNullable = true)]
+    public string? PODatabase { get; set; }
+
+    [SugarColumn(ColumnName = "POSite", Length = 8, IsNullable = true)]
+    public string? POSite { get; set; }
+
+    [SugarColumn(ColumnName = "PricingDate", IsNullable = true)]
+    public DateTime? PricingDate { get; set; }
+
+    [SugarColumn(ColumnName = "Project", Length = 24, IsNullable = true)]
+    public string? Project { get; set; }
+
+    [SugarColumn(ColumnName = "PriceList", Length = 20, IsNullable = true)]
+    public string? PriceList { get; set; }
+
+    [SugarColumn(ColumnName = "ERSPriceListOption")]
+    public int ERSPriceListOption { get; set; }
+
+    [SugarColumn(ColumnName = "PST", ColumnDataType = "bit(1)")]
+    public bool PST { get; set; }
+
+    [SugarColumn(ColumnName = "PackingSlipQty", ColumnDataType = "decimal(12,5)")]
+    public decimal PackingSlipQty { get; set; }
+
+    [SugarColumn(ColumnName = "PayUMConv", ColumnDataType = "decimal(10,5)")]
+    public decimal PayUMConv { get; set; }
+
+    [SugarColumn(ColumnName = "PurCost", ColumnDataType = "decimal(15,6)")]
+    public decimal PurCost { get; set; }
+
+    [SugarColumn(ColumnName = "RctQty", ColumnDataType = "decimal(15,5)")]
+    public decimal RctQty { get; set; }
+
+    [SugarColumn(ColumnName = "QtyOrded", ColumnDataType = "decimal(15,5)")]
+    public decimal QtyOrded { get; set; }
+
+    [SugarColumn(ColumnName = "QtyReceived", ColumnDataType = "decimal(15,5)")]
+    public decimal QtyReceived { get; set; }
+
+    [SugarColumn(ColumnName = "QtyReturned", ColumnDataType = "decimal(15,5)")]
+    public decimal QtyReturned { get; set; }
+
+    [SugarColumn(ColumnName = "RawAuthDays")]
+    public int RawAuthDays { get; set; }
+
+    [SugarColumn(ColumnName = "RawAuthWeeks")]
+    public int RawAuthWeeks { get; set; }
+
+    [SugarColumn(ColumnName = "RctStatus", Length = 60, IsNullable = true)]
+    public string? RctStatus { get; set; }
+
+    [SugarColumn(ColumnName = "Active", ColumnDataType = "bit(1)")]
+    public bool Active { get; set; }
+
+    [SugarColumn(ColumnName = "Reason", Length = 8, IsNullable = true)]
+    public string? Reason { get; set; }
+
+    [SugarColumn(ColumnName = "Rel")]
+    public int Rel { get; set; }
+
+    [SugarColumn(ColumnName = "QtyReleased", ColumnDataType = "decimal(12,5)")]
+    public decimal QtyReleased { get; set; }
+
+    [SugarColumn(ColumnName = "RequestedBy", Length = 8, IsNullable = true)]
+    public string? RequestedBy { get; set; }
+
+    [SugarColumn(ColumnName = "ReqLine")]
+    public int ReqLine { get; set; }
+
+    [SugarColumn(ColumnName = "Req", Length = 20, IsNullable = true)]
+    public string? Req { get; set; }
+
+    [SugarColumn(ColumnName = "Revision", Length = 4, IsNullable = true)]
+    public string? Revision { get; set; }
+
+    [SugarColumn(ColumnName = "NextReleaseID")]
+    public int NextReleaseID { get; set; }
+
+    [SugarColumn(ColumnName = "RMAType", Length = 1, IsNullable = true)]
+    public string? RMAType { get; set; }
+
+    [SugarColumn(ColumnName = "RMAUM", Length = 2, IsNullable = true)]
+    public string? RMAUM { get; set; }
+
+    [SugarColumn(ColumnName = "RctUMConversion", ColumnDataType = "decimal(10,5)")]
+    public decimal RctUMConversion { get; set; }
+
+    [SugarColumn(ColumnName = "Scheduled", ColumnDataType = "bit(1)")]
+    public bool Scheduled { get; set; }
+
+    [SugarColumn(ColumnName = "ScheduledChanged", ColumnDataType = "bit(1)")]
+    public bool ScheduledChanged { get; set; }
+
+    [SugarColumn(ColumnName = "ScheduleData", Length = 8, IsNullable = true)]
+    public string? ScheduleData { get; set; }
+
+    [SugarColumn(ColumnName = "SchedMRPReq", ColumnDataType = "bit(1)")]
+    public bool SchedMRPReq { get; set; }
+
+    [SugarColumn(ColumnName = "ShipDlvyPattern", Length = 2, IsNullable = true)]
+    public string? ShipDlvyPattern { get; set; }
+
+    [SugarColumn(ColumnName = "ShipDlvyTime", Length = 2, IsNullable = true)]
+    public string? ShipDlvyTime { get; set; }
+
+    [SugarColumn(ColumnName = "WIPLotSerial", Length = 24, IsNullable = true)]
+    public string? WIPLotSerial { get; set; }
+
+    [SugarColumn(ColumnName = "SafetyDays", ColumnDataType = "decimal(8,5)")]
+    public decimal SafetyDays { get; set; }
+
+    [SugarColumn(ColumnName = "SafetyHours", ColumnDataType = "decimal(8,5)")]
+    public decimal SafetyHours { get; set; }
+
+    [SugarColumn(ColumnName = "ShipSchedWeeks")]
+    public int ShipSchedWeeks { get; set; }
+
+    [SugarColumn(ColumnName = "Site", Length = 24, IsNullable = true)]
+    public string? Site { get; set; }
+
+    [SugarColumn(ColumnName = "PrimarySOLine")]
+    public int PrimarySOLine { get; set; }
+
+    [SugarColumn(ColumnName = "SalesJob", Length = 200, IsNullable = true)]
+    public string? SalesJob { get; set; }
+
+    [SugarColumn(ColumnName = "SecondarySOStatus", Length = 1, IsNullable = true)]
+    public string? SecondarySOStatus { get; set; }
+
+    [SugarColumn(ColumnName = "StartEff1", IsNullable = true)]
+    public DateTime? StartEff1 { get; set; }
+
+    [SugarColumn(ColumnName = "StartEff2", IsNullable = true)]
+    public DateTime? StartEff2 { get; set; }
+
+    [SugarColumn(ColumnName = "StartEff3", IsNullable = true)]
+    public DateTime? StartEff3 { get; set; }
+
+    [SugarColumn(ColumnName = "StartEff4", IsNullable = true)]
+    public DateTime? StartEff4 { get; set; }
+
+    [SugarColumn(ColumnName = "Status", Length = 1, IsNullable = true)]
+    public string? Status { get; set; }
+
+    [SugarColumn(ColumnName = "StdCost", ColumnDataType = "decimal(14,5)")]
+    public decimal StdCost { get; set; }
+
+    [SugarColumn(ColumnName = "PurSubAcct", Length = 8, IsNullable = true)]
+    public string? PurSubAcct { get; set; }
+
+    [SugarColumn(ColumnName = "SubcontractShip", IsNullable = true)]
+    public DateTime? SubcontractShip { get; set; }
+
+    [SugarColumn(ColumnName = "SubcontractType", Length = 8, IsNullable = true)]
+    public string? SubcontractType { get; set; }
+
+    [SugarColumn(ColumnName = "Taxable", ColumnDataType = "bit(1)")]
+    public bool Taxable { get; set; }
+
+    [SugarColumn(ColumnName = "TaxClass", Length = 20, IsNullable = true)]
+    public string? TaxClass { get; set; }
+
+    [SugarColumn(ColumnName = "TaxEnvironment", Length = 16, IsNullable = true)]
+    public string? TaxEnvironment { get; set; }
+
+    [SugarColumn(ColumnName = "TaxIn", ColumnDataType = "bit(1)")]
+    public bool TaxIn { get; set; }
+
+    [SugarColumn(ColumnName = "MaxTaxableAmt", ColumnDataType = "decimal(14,5)")]
+    public decimal MaxTaxableAmt { get; set; }
+
+    [SugarColumn(ColumnName = "TaxUsage", Length = 8, IsNullable = true)]
+    public string? TaxUsage { get; set; }
+
+    [SugarColumn(ColumnName = "TransportDays", ColumnDataType = "decimal(18,5)", IsNullable = true)]
+    public decimal? TransportDays { get; set; }
+
+    [SugarColumn(ColumnName = "TransportHours", ColumnDataType = "decimal(8,5)")]
+    public decimal TransportHours { get; set; }
+
+    [SugarColumn(ColumnName = "Typed", Length = 1, IsNullable = true)]
+    public string? Typed { get; set; }
+
+    [SugarColumn(ColumnName = "UM", Length = 8, IsNullable = true)]
+    public string? UM { get; set; }
+
+    [SugarColumn(ColumnName = "UMConversion", ColumnDataType = "decimal(12,7)")]
+    public decimal UMConversion { get; set; }
+
+    [SugarColumn(ColumnName = "Ufld1", Length = 24, IsNullable = true)]
+    public string? Ufld1 { get; set; }
+
+    [SugarColumn(ColumnName = "Ufld2", Length = 24, IsNullable = true)]
+    public string? Ufld2 { get; set; }
+
+    [SugarColumn(ColumnName = "VAT", ColumnDataType = "bit(1)")]
+    public bool VAT { get; set; }
+
+    [SugarColumn(ColumnName = "SuppItem", Length = 24, IsNullable = true)]
+    public string? SuppItem { get; set; }
+
+    [SugarColumn(ColumnName = "LotSerial", Length = 120, IsNullable = true)]
+    public string? LotSerial { get; set; }
+
+    [SugarColumn(ColumnName = "WorkOrdID", Length = 30, IsNullable = true)]
+    public string? WorkOrdID { get; set; }
+
+    [SugarColumn(ColumnName = "BusinessID")]
+    public long BusinessID { get; set; }
+
+    [SugarColumn(ColumnName = "CreateUser", Length = 24, IsNullable = true)]
+    public string? CreateUser { get; set; }
+
+    [SugarColumn(ColumnName = "UpdateUser", Length = 24, IsNullable = true)]
+    public string? UpdateUser { get; set; }
+
+    [SugarColumn(ColumnName = "CreateTime", IsNullable = true)]
+    public DateTime? CreateTime { get; set; }
+
+    [SugarColumn(ColumnName = "UpdateTime", IsNullable = true)]
+    public DateTime? UpdateTime { get; set; }
+
+    [SugarColumn(ColumnName = "EffTime", IsNullable = true)]
+    public DateTime? EffTime { get; set; }
+
+    [SugarColumn(ColumnName = "IsActive", ColumnDataType = "bit(1)")]
+    public bool IsActive { get; set; }
+
+    [SugarColumn(ColumnName = "IsConfirm", ColumnDataType = "bit(1)")]
+    public bool IsConfirm { get; set; }
+
+    [SugarColumn(ColumnName = "Potype", Length = 8)]
+    public string Potype { get; set; } = string.Empty;
+
+    [SugarColumn(ColumnName = "IsChanged", ColumnDataType = "bit(1)")]
+    public bool IsChanged { get; set; }
+
+    [SugarColumn(ColumnName = "Remark", ColumnDataType = "text", IsNullable = true)]
+    public string? Remark { get; set; }
+
+    [SugarColumn(ColumnName = "Usage", Length = 30, IsNullable = true)]
+    public string? Usage { get; set; }
+
+    [SugarColumn(ColumnName = "TaxRate", ColumnDataType = "decimal(8,5)")]
+    public decimal TaxRate { get; set; }
+
+    [SugarColumn(ColumnName = "Comments1", Length = 50, IsNullable = true)]
+    public string? Comments1 { get; set; }
+
+    [SugarColumn(ColumnName = "ListPurCost", ColumnDataType = "decimal(18,5)", IsNullable = true)]
+    public decimal? ListPurCost { get; set; }
+
+    [SugarColumn(ColumnName = "IsRounding", ColumnDataType = "bit(1)")]
+    public bool IsRounding { get; set; }
+
+    [SugarColumn(ColumnName = "ReceiptQty", ColumnDataType = "decimal(15,5)")]
+    public decimal ReceiptQty { get; set; }
+
+    [SugarColumn(ColumnName = "BarCodeQty", ColumnDataType = "decimal(18,5)")]
+    public decimal BarCodeQty { get; set; }
+
+    [SugarColumn(ColumnName = "IsClosed", ColumnDataType = "bit(1)")]
+    public bool IsClosed { get; set; }
+
+    [SugarColumn(ColumnName = "Rev", Length = 8, IsNullable = true)]
+    public string? Rev { get; set; }
+
+    [SugarColumn(ColumnName = "Remarks", Length = 100, IsNullable = true)]
+    public string? Remarks { get; set; }
+
+    [SugarColumn(ColumnName = "Department", Length = 8, IsNullable = true)]
+    public string? Department { get; set; }
+
+    [SugarColumn(ColumnName = "WorkOrd", Length = 30, IsNullable = true)]
+    public string? WorkOrd { get; set; }
+
+    [SugarColumn(ColumnName = "QtyReturnedRefund", ColumnDataType = "decimal(15,5)")]
+    public decimal QtyReturnedRefund { get; set; }
+
+    [SugarColumn(ColumnName = "CumQtyBO", ColumnDataType = "decimal(18,5)")]
+    public decimal CumQtyBO { get; set; }
+
+    [SugarColumn(ColumnName = "PurOrdRecID")]
+    public int PurOrdRecID { get; set; }
+
+    [SugarColumn(ColumnName = "Drawing", Length = 24, IsNullable = true)]
+    public string? Drawing { get; set; }
+
+    [SugarColumn(ColumnName = "ERPfld1", Length = 200, IsNullable = true)]
+    public string? ERPfld1 { get; set; }
+
+    [SugarColumn(ColumnName = "ERPfld2", Length = 200, IsNullable = true)]
+    public string? ERPfld2 { get; set; }
+
+    [SugarColumn(ColumnName = "IsPush", ColumnDataType = "bit(1)", IsNullable = true)]
+    public bool? IsPush { get; set; }
+
+    [SugarColumn(ColumnName = "Descr", ColumnDataType = "text", IsNullable = true)]
+    public string? Descr { get; set; }
+}

+ 356 - 0
server/Plugins/Admin.NET.Plugin.AiDOP/Supply/Entity/PurOrdMaster.cs

@@ -0,0 +1,356 @@
+namespace Admin.NET.Plugin.AiDOP.Supply;
+
+/// <summary>
+/// 采购订单主表(PurOrdMaster)
+/// </summary>
+[SugarTable("PurOrdMaster", "采购订单主表")]
+public class PurOrdMaster
+{
+    [SugarColumn(ColumnName = "RecID", IsPrimaryKey = true, IsIdentity = true)]
+    public int RecID { get; set; }
+
+    [SugarColumn(ColumnName = "OwnerApplication", Length = 12, IsNullable = true)]
+    public string? OwnerApplication { get; set; }
+
+    [SugarColumn(ColumnName = "APAcct", Length = 16, IsNullable = true)]
+    public string? APAcct { get; set; }
+
+    [SugarColumn(ColumnName = "CostCtr", Length = 4, IsNullable = true)]
+    public string? CostCtr { get; set; }
+
+    [SugarColumn(ColumnName = "APSubAcct", Length = 8, IsNullable = true)]
+    public string? APSubAcct { get; set; }
+
+    [SugarColumn(ColumnName = "Bank", Length = 2, IsNullable = true)]
+    public string? Bank { get; set; }
+
+    [SugarColumn(ColumnName = "BillTo", Length = 10, IsNullable = true)]
+    public string? BillTo { get; set; }
+
+    [SugarColumn(ColumnName = "BlanketOrd", Length = 24, IsNullable = true)]
+    public string? BlanketOrd { get; set; }
+
+    [SugarColumn(ColumnName = "Buyer", Length = 30, IsNullable = true)]
+    public string? Buyer { get; set; }
+
+    [SugarColumn(ColumnName = "CloseDate", IsNullable = true)]
+    public DateTime? CloseDate { get; set; }
+
+    [SugarColumn(ColumnName = "CommentIndex")]
+    public int CommentIndex { get; set; }
+
+    [SugarColumn(ColumnName = "Confirming", ColumnDataType = "bit(1)")]
+    public bool Confirming { get; set; }
+
+    [SugarColumn(ColumnName = "Consignment")]
+    public short Consignment { get; set; }
+
+    [SugarColumn(ColumnName = "Contact", Length = 24, IsNullable = true)]
+    public string? Contact { get; set; }
+
+    [SugarColumn(ColumnName = "Contract", Length = 24, IsNullable = true)]
+    public string? Contract { get; set; }
+
+    [SugarColumn(ColumnName = "CreditTermsInt", ColumnDataType = "decimal(9,5)")]
+    public decimal CreditTermsInt { get; set; }
+
+    [SugarColumn(ColumnName = "CreditTerms", Length = 8, IsNullable = true)]
+    public string? CreditTerms { get; set; }
+
+    [SugarColumn(ColumnName = "Curr", Length = 3, IsNullable = true)]
+    public string? Curr { get; set; }
+
+    [SugarColumn(ColumnName = "CycleCode", Length = 2, IsNullable = true)]
+    public string? CycleCode { get; set; }
+
+    [SugarColumn(ColumnName = "DeliverTo", Length = 4, IsNullable = true)]
+    public string? DeliverTo { get; set; }
+
+    [SugarColumn(ColumnName = "Disc", ColumnDataType = "decimal(7,5)")]
+    public decimal Disc { get; set; }
+
+    [SugarColumn(ColumnName = "Domain", Length = 24, IsNullable = true)]
+    public string? Domain { get; set; }
+
+    [SugarColumn(ColumnName = "DueDate", IsNullable = true)]
+    public DateTime? DueDate { get; set; }
+
+    [SugarColumn(ColumnName = "DutyType", Length = 1, IsNullable = true)]
+    public string? DutyType { get; set; }
+
+    [SugarColumn(ColumnName = "BlanketStart", IsNullable = true)]
+    public DateTime? BlanketStart { get; set; }
+
+    [SugarColumn(ColumnName = "BlanketEnd", IsNullable = true)]
+    public DateTime? BlanketEnd { get; set; }
+
+    [SugarColumn(ColumnName = "ExchRate", ColumnDataType = "decimal(10,5)")]
+    public decimal ExchRate { get; set; }
+
+    [SugarColumn(ColumnName = "ERSOption", Length = 2, IsNullable = true)]
+    public string? ERSOption { get; set; }
+
+    [SugarColumn(ColumnName = "EstVal", ColumnDataType = "decimal(13,5)")]
+    public decimal EstVal { get; set; }
+
+    [SugarColumn(ColumnName = "Sequence")]
+    public int Sequence { get; set; }
+
+    [SugarColumn(ColumnName = "ExchRate1", ColumnDataType = "decimal(15,5)")]
+    public decimal ExchRate1 { get; set; }
+
+    [SugarColumn(ColumnName = "ExchRate2", ColumnDataType = "decimal(15,5)")]
+    public decimal ExchRate2 { get; set; }
+
+    [SugarColumn(ColumnName = "RateType", Length = 8, IsNullable = true)]
+    public string? RateType { get; set; }
+
+    [SugarColumn(ColumnName = "FixedPrice", ColumnDataType = "bit(1)")]
+    public bool FixedPrice { get; set; }
+
+    [SugarColumn(ColumnName = "FixedRate", ColumnDataType = "bit(1)")]
+    public bool FixedRate { get; set; }
+
+    [SugarColumn(ColumnName = "FOB", Length = 20, IsNullable = true)]
+    public string? FOB { get; set; }
+
+    [SugarColumn(ColumnName = "Frt", ColumnDataType = "decimal(6,5)")]
+    public decimal Frt { get; set; }
+
+    [SugarColumn(ColumnName = "FSMType", Length = 8, IsNullable = true)]
+    public string? FSMType { get; set; }
+
+    [SugarColumn(ColumnName = "FSTID", Length = 24, IsNullable = true)]
+    public string? FSTID { get; set; }
+
+    [SugarColumn(ColumnName = "InvoicesVia", Length = 8, IsNullable = true)]
+    public string? InvoicesVia { get; set; }
+
+    [SugarColumn(ColumnName = "EMTPO")]
+    public short EMTPO { get; set; }
+
+    [SugarColumn(ColumnName = "Language", Length = 2, IsNullable = true)]
+    public string? Language { get; set; }
+
+    [SugarColumn(ColumnName = "MaxAgingDays")]
+    public int MaxAgingDays { get; set; }
+
+    [SugarColumn(ColumnName = "PurOrd", Length = 48, IsNullable = true)]
+    public string? PurOrd { get; set; }
+
+    [SugarColumn(ColumnName = "OrdDate", IsNullable = true)]
+    public DateTime? OrdDate { get; set; }
+
+    [SugarColumn(ColumnName = "PartialOK", ColumnDataType = "bit(1)")]
+    public bool PartialOK { get; set; }
+
+    [SugarColumn(ColumnName = "AmtPrepaid", ColumnDataType = "decimal(15,5)")]
+    public decimal AmtPrepaid { get; set; }
+
+    [SugarColumn(ColumnName = "LastPriceDt", IsNullable = true)]
+    public DateTime? LastPriceDt { get; set; }
+
+    [SugarColumn(ColumnName = "PricingDate", IsNullable = true)]
+    public DateTime? PricingDate { get; set; }
+
+    [SugarColumn(ColumnName = "PrintPO", ColumnDataType = "bit(1)")]
+    public bool PrintPO { get; set; }
+
+    [SugarColumn(ColumnName = "Project", Length = 24, IsNullable = true)]
+    public string? Project { get; set; }
+
+    [SugarColumn(ColumnName = "DiscTbl", Length = 8, IsNullable = true)]
+    public string? DiscTbl { get; set; }
+
+    [SugarColumn(ColumnName = "PriceTbl", Length = 20, IsNullable = true)]
+    public string? PriceTbl { get; set; }
+
+    [SugarColumn(ColumnName = "ERSPriceListOption")]
+    public int ERSPriceListOption { get; set; }
+
+    [SugarColumn(ColumnName = "PST", ColumnDataType = "bit(1)")]
+    public bool PST { get; set; }
+
+    [SugarColumn(ColumnName = "PSTID", Length = 24, IsNullable = true)]
+    public string? PSTID { get; set; }
+
+    [SugarColumn(ColumnName = "Recurr", ColumnDataType = "bit(1)")]
+    public bool Recurr { get; set; }
+
+    [SugarColumn(ColumnName = "Release", ColumnDataType = "bit(1)")]
+    public bool ReleaseFlag { get; set; }
+
+    [SugarColumn(ColumnName = "Rel")]
+    public int Rel { get; set; }
+
+    [SugarColumn(ColumnName = "ReqBy", Length = 8, IsNullable = true)]
+    public string? ReqBy { get; set; }
+
+    [SugarColumn(ColumnName = "Revision", ColumnDataType = "decimal(8,5)")]
+    public decimal Revision { get; set; }
+
+    [SugarColumn(ColumnName = "Remark", Length = 200, IsNullable = true)]
+    public string? Remark { get; set; }
+
+    [SugarColumn(ColumnName = "Scheduled", ColumnDataType = "bit(1)")]
+    public bool Scheduled { get; set; }
+
+    [SugarColumn(ColumnName = "SchedulesVia", Length = 1, IsNullable = true)]
+    public string? SchedulesVia { get; set; }
+
+    [SugarColumn(ColumnName = "ServiceCharge", ColumnDataType = "decimal(6,5)")]
+    public decimal ServiceCharge { get; set; }
+
+    [SugarColumn(ColumnName = "ShipTo", Length = 20, IsNullable = true)]
+    public string? ShipTo { get; set; }
+
+    [SugarColumn(ColumnName = "ShipVia", Length = 20, IsNullable = true)]
+    public string? ShipVia { get; set; }
+
+    [SugarColumn(ColumnName = "Site", Length = 24, IsNullable = true)]
+    public string? Site { get; set; }
+
+    [SugarColumn(ColumnName = "SecondarySOCrHold")]
+    public short SecondarySOCrHold { get; set; }
+
+    [SugarColumn(ColumnName = "PrimarySO", Length = 8, IsNullable = true)]
+    public string? PrimarySO { get; set; }
+
+    [SugarColumn(ColumnName = "SpecialCharge", ColumnDataType = "decimal(6,5)")]
+    public decimal SpecialCharge { get; set; }
+
+    [SugarColumn(ColumnName = "Status", Length = 2, IsNullable = true)]
+    public string? Status { get; set; }
+
+    [SugarColumn(ColumnName = "Taxable", ColumnDataType = "bit(1)")]
+    public bool Taxable { get; set; }
+
+    [SugarColumn(ColumnName = "TaxClass", Length = 20, IsNullable = true)]
+    public string? TaxClass { get; set; }
+
+    [SugarColumn(ColumnName = "TaxDate", IsNullable = true)]
+    public DateTime? TaxDate { get; set; }
+
+    [SugarColumn(ColumnName = "TaxEnvironment", Length = 16, IsNullable = true)]
+    public string? TaxEnvironment { get; set; }
+
+    [SugarColumn(ColumnName = "Tax1", ColumnDataType = "decimal(7,5)")]
+    public decimal Tax1 { get; set; }
+
+    [SugarColumn(ColumnName = "Tax2", ColumnDataType = "decimal(7,5)")]
+    public decimal Tax2 { get; set; }
+
+    [SugarColumn(ColumnName = "Tax3", ColumnDataType = "decimal(7,5)")]
+    public decimal Tax3 { get; set; }
+
+    [SugarColumn(ColumnName = "TaxUsage", Length = 8, IsNullable = true)]
+    public string? TaxUsage { get; set; }
+
+    [SugarColumn(ColumnName = "TermsofTrade", Length = 20, IsNullable = true)]
+    public string? TermsofTrade { get; set; }
+
+    [SugarColumn(ColumnName = "TransportDays", ColumnDataType = "decimal(8,5)")]
+    public decimal TransportDays { get; set; }
+
+    [SugarColumn(ColumnName = "Typed", Length = 18, IsNullable = true)]
+    public string? Typed { get; set; }
+
+    [SugarColumn(ColumnName = "Ufld1", Length = 24, IsNullable = true)]
+    public string? Ufld1 { get; set; }
+
+    [SugarColumn(ColumnName = "Ufld2", Length = 60, IsNullable = true)]
+    public string? Ufld2 { get; set; }
+
+    [SugarColumn(ColumnName = "EnteredBy", Length = 8, IsNullable = true)]
+    public string? EnteredBy { get; set; }
+
+    [SugarColumn(ColumnName = "Supp", Length = 20, IsNullable = true)]
+    public string? Supp { get; set; }
+
+    [SugarColumn(ColumnName = "Transm", Length = 1, IsNullable = true)]
+    public string? Transm { get; set; }
+
+    [SugarColumn(ColumnName = "BusinessID")]
+    public long BusinessID { get; set; }
+
+    [SugarColumn(ColumnName = "CreateUser", Length = 24, IsNullable = true)]
+    public string? CreateUser { get; set; }
+
+    [SugarColumn(ColumnName = "UpdateUser", Length = 24, IsNullable = true)]
+    public string? UpdateUser { get; set; }
+
+    [SugarColumn(ColumnName = "CreateTime", IsNullable = true)]
+    public DateTime? CreateTime { get; set; }
+
+    [SugarColumn(ColumnName = "UpdateTime", IsNullable = true)]
+    public DateTime? UpdateTime { get; set; }
+
+    [SugarColumn(ColumnName = "EffTime", IsNullable = true)]
+    public DateTime? EffTime { get; set; }
+
+    [SugarColumn(ColumnName = "IsActive", ColumnDataType = "bit(1)")]
+    public bool IsActive { get; set; }
+
+    [SugarColumn(ColumnName = "IsConfirm", ColumnDataType = "bit(1)")]
+    public bool IsConfirm { get; set; }
+
+    [SugarColumn(ColumnName = "Potype", Length = 8)]
+    public string Potype { get; set; } = string.Empty;
+
+    [SugarColumn(ColumnName = "Department", Length = 8, IsNullable = true)]
+    public string? Department { get; set; }
+
+    [SugarColumn(ColumnName = "IsChanged", ColumnDataType = "bit(1)")]
+    public bool IsChanged { get; set; }
+
+    [SugarColumn(ColumnName = "TaxIn", ColumnDataType = "bit(1)")]
+    public bool TaxIn { get; set; }
+
+    [SugarColumn(ColumnName = "Amt", ColumnDataType = "decimal(15,5)")]
+    public decimal Amt { get; set; }
+
+    [SugarColumn(ColumnName = "Usage", Length = 30, IsNullable = true)]
+    public string? Usage { get; set; }
+
+    [SugarColumn(ColumnName = "CustAddress", Length = 200, IsNullable = true)]
+    public string? CustAddress { get; set; }
+
+    [SugarColumn(ColumnName = "CustContact", Length = 50, IsNullable = true)]
+    public string? CustContact { get; set; }
+
+    [SugarColumn(ColumnName = "CustPhone", Length = 30, IsNullable = true)]
+    public string? CustPhone { get; set; }
+
+    [SugarColumn(ColumnName = "SalesOrd", Length = 30, IsNullable = true)]
+    public string? SalesOrd { get; set; }
+
+    [SugarColumn(ColumnName = "CustPO", Length = 24, IsNullable = true)]
+    public string? CustPO { get; set; }
+
+    [SugarColumn(ColumnName = "IsPriceChanged", ColumnDataType = "bit(1)")]
+    public bool IsPriceChanged { get; set; }
+
+    [SugarColumn(ColumnName = "CustName", Length = 120, IsNullable = true)]
+    public string? CustName { get; set; }
+
+    [SugarColumn(ColumnName = "Rev")]
+    public int Rev { get; set; }
+
+    [SugarColumn(ColumnName = "RoleNo", Length = 20, IsNullable = true)]
+    public string? RoleNo { get; set; }
+
+    [SugarColumn(ColumnName = "InvoiceType", Length = 20, IsNullable = true)]
+    public string? InvoiceType { get; set; }
+
+    [SugarColumn(ColumnName = "PricingMethod", Length = 20, IsNullable = true)]
+    public string? PricingMethod { get; set; }
+
+    [SugarColumn(ColumnName = "ERPfld1", Length = 200, IsNullable = true)]
+    public string? ERPfld1 { get; set; }
+
+    [SugarColumn(ColumnName = "ERPWorkOrd", Length = 255, IsNullable = true)]
+    public string? ERPWorkOrd { get; set; }
+
+    [SugarColumn(ColumnName = "WorkOrd", Length = 255, IsNullable = true)]
+    public string? WorkOrd { get; set; }
+}

+ 714 - 0
server/Plugins/Admin.NET.Plugin.AiDOP/Supply/OutsourceOrderService.cs

@@ -0,0 +1,714 @@
+namespace Admin.NET.Plugin.AiDOP.Supply;
+
+/// <summary>
+/// 委外加工订单服务
+/// </summary>
+[ApiDescriptionSettings(Order = 310, Description = "委外加工订单")]
+[Route("api/Supply")]
+[AllowAnonymous]
+[NonUnify]
+public class OutsourceOrderService : IDynamicApiController, ITransient
+{
+    private readonly ISqlSugarClient _db;
+    private readonly UserManager _userManager;
+
+    public OutsourceOrderService(ISqlSugarClient db, UserManager userManager)
+    {
+        _db = db;
+        _userManager = userManager;
+    }
+
+    [DisplayName("委外加工订单列表")]
+    [HttpGet("outsource-order/list")]
+    public async Task<object> GetOrderList([FromQuery] OutsourceOrderListInput input)
+    {
+        var page = input.Page <= 0 ? 1 : input.Page;
+        var pageSize = input.PageSize <= 0 ? 10 : input.PageSize;
+        var offset = (page - 1) * pageSize;
+
+        var where = new List<string>
+        {
+            "p.Potype='PW'",
+            "p.IsActive=1",
+            "IFNULL(p.`Usage`,'')='委外加工'"
+        };
+        var pars = new List<SugarParameter>();
+        if (!string.IsNullOrWhiteSpace(input.PurOrd))
+        {
+            where.Add("p.PurOrd LIKE @PurOrd");
+            pars.Add(new SugarParameter("@PurOrd", $"%{input.PurOrd.Trim()}%"));
+        }
+        if (!string.IsNullOrWhiteSpace(input.Buyer))
+        {
+            where.Add("p.Buyer=@Buyer");
+            pars.Add(new SugarParameter("@Buyer", input.Buyer.Trim()));
+        }
+        if (!string.IsNullOrWhiteSpace(input.Supp))
+        {
+            where.Add("p.Supp=@Supp");
+            pars.Add(new SugarParameter("@Supp", input.Supp.Trim()));
+        }
+
+        var fromSql = $"""
+            FROM PurOrdMaster p
+            LEFT JOIN SuppMaster s ON p.Domain=s.Domain AND p.Supp=s.Supp
+            LEFT JOIN DepartmentMaster d ON p.Domain=d.Domain AND p.Department=d.Department
+            LEFT JOIN EmployeeMaster e ON p.Domain=e.Domain AND p.Buyer=e.Employee
+            WHERE {string.Join(" AND ", where)}
+            """;
+
+        var total = await _db.Ado.GetIntAsync($"SELECT COUNT(1) {fromSql}", pars);
+        var list = await _db.Ado.SqlQueryAsync<OutsourceOrderListRow>(
+            $"""
+            SELECT
+                p.RecID AS Id,
+                p.PurOrd AS PurOrd,
+                CONCAT(TRIM(IFNULL(p.Supp,'')),' ',TRIM(IFNULL(s.SortName,''))) AS SuppName,
+                p.Potype AS Potype,
+                p.ReqBy AS ReqBy,
+                CONCAT(TRIM(IFNULL(p.Buyer,'')),' ',TRIM(IFNULL(e.Name,''))) AS Buyer,
+                CONCAT(TRIM(IFNULL(p.Department,'')),' ',TRIM(IFNULL(d.Descr,''))) AS DepartmentDescr,
+                p.OrdDate AS OrdDate,
+                p.DueDate AS DueDate,
+                p.Curr AS Curr,
+                CASE
+                    WHEN IFNULL(LENGTH(p.Status),0)=0 OR p.Buyer IS NULL THEN 'R'
+                    ELSE p.Status
+                END AS Status,
+                p.Supp AS Supp,
+                p.Buyer AS BuyerCode,
+                p.Department AS Department,
+                p.`Usage` AS `Usage`,
+                p.Remark AS Remark
+            {fromSql}
+            ORDER BY {BuildOrderBy(input.SortField, input.SortOrder)}
+            LIMIT {pageSize} OFFSET {offset}
+            """,
+            pars);
+
+        return new { total, page, pageSize, list };
+    }
+
+    [DisplayName("委外加工订单详情")]
+    [HttpGet("outsource-order/{id:int}")]
+    public async Task<object> GetOrderDetail(int id)
+    {
+        var row = (await _db.Ado.SqlQueryAsync<OutsourceOrderListRow>(
+            """
+            SELECT
+                p.RecID AS Id,
+                p.PurOrd AS PurOrd,
+                CONCAT(TRIM(IFNULL(p.Supp,'')),' ',TRIM(IFNULL(s.SortName,''))) AS SuppName,
+                p.Potype AS Potype,
+                p.ReqBy AS ReqBy,
+                CONCAT(TRIM(IFNULL(p.Buyer,'')),' ',TRIM(IFNULL(e.Name,''))) AS Buyer,
+                CONCAT(TRIM(IFNULL(p.Department,'')),' ',TRIM(IFNULL(d.Descr,''))) AS DepartmentDescr,
+                p.OrdDate AS OrdDate,
+                p.DueDate AS DueDate,
+                p.Curr AS Curr,
+                CASE WHEN IFNULL(LENGTH(p.Status),0)=0 OR p.Buyer IS NULL THEN 'R' ELSE p.Status END AS Status,
+                p.Supp AS Supp,
+                p.Buyer AS BuyerCode,
+                p.Department AS Department,
+                p.`Usage` AS `Usage`,
+                p.Remark AS Remark
+            FROM PurOrdMaster p
+            LEFT JOIN SuppMaster s ON p.Domain=s.Domain AND p.Supp=s.Supp
+            LEFT JOIN DepartmentMaster d ON p.Domain=d.Domain AND p.Department=d.Department
+            LEFT JOIN EmployeeMaster e ON p.Domain=e.Domain AND p.Buyer=e.Employee
+            WHERE p.RecID=@Id
+            LIMIT 1
+            """,
+            new SugarParameter("@Id", id))).FirstOrDefault();
+        return row ?? throw Oops.Oh("记录不存在");
+    }
+
+    [DisplayName("保存委外加工订单")]
+    [HttpPost("outsource-order/save")]
+    public async Task<object> SaveOrder([FromBody] OutsourceOrderSaveInput input)
+    {
+        var now = DateTime.Now;
+        var ordDate = ParseDate(input.OrdDate) ?? now.Date;
+        var buyer = string.IsNullOrWhiteSpace(input.Buyer) ? "110" : input.Buyer.Trim();
+        var usage = string.IsNullOrWhiteSpace(input.Usage) ? "委外加工" : input.Usage.Trim();
+        var reqBy = string.IsNullOrWhiteSpace(input.ReqBy) ? "DO" : input.ReqBy.Trim();
+
+        if (string.IsNullOrWhiteSpace(input.Supp)) throw Oops.Oh("请选择供应商");
+        if (string.IsNullOrWhiteSpace(input.Department)) throw Oops.Oh("请选择部门");
+
+        if (input.Id is null or <= 0)
+        {
+            var purOrd = string.IsNullOrWhiteSpace(input.PurOrd) ? $"PW{DateTime.Now:yyyyMMddHHmmssfff}" : input.PurOrd.Trim();
+            await _db.Ado.ExecuteCommandAsync(
+                """
+                INSERT INTO PurOrdMaster
+                (PurOrd,OrdDate,Supp,ReqBy,Buyer,Department,`Usage`,Remark,Potype,Status,IsActive,CreateUser,CreateTime,UpdateUser,UpdateTime,Domain,tenant_id)
+                VALUES
+                (@PurOrd,@OrdDate,@Supp,@ReqBy,@Buyer,@Department,@Usage,@Remark,'PW','R',1,@CreateUser,@CreateTime,@UpdateUser,@UpdateTime,@Domain,@TenantId)
+                """,
+                new SugarParameter("@PurOrd", purOrd),
+                new SugarParameter("@OrdDate", ordDate),
+                new SugarParameter("@Supp", input.Supp.Trim()),
+                new SugarParameter("@ReqBy", reqBy),
+                new SugarParameter("@Buyer", buyer),
+                new SugarParameter("@Department", input.Department.Trim()),
+                new SugarParameter("@Usage", usage),
+                new SugarParameter("@Remark", input.Remark?.Trim()),
+                new SugarParameter("@CreateUser", _userManager.Account),
+                new SugarParameter("@CreateTime", now),
+                new SugarParameter("@UpdateUser", _userManager.Account),
+                new SugarParameter("@UpdateTime", now),
+                new SugarParameter("@Domain", "100"),
+                new SugarParameter("@TenantId", _userManager.TenantId <= 0 ? null : _userManager.TenantId)
+            );
+            var id = await _db.Ado.GetIntAsync("SELECT IFNULL(MAX(RecID),0) FROM PurOrdMaster WHERE PurOrd=@PurOrd", new SugarParameter("@PurOrd", purOrd));
+            return new { id, purOrd, message = "新增成功" };
+        }
+
+        var exists = await _db.Ado.GetIntAsync("SELECT COUNT(1) FROM PurOrdMaster WHERE RecID=@Id", new SugarParameter("@Id", input.Id.Value));
+        if (exists <= 0) throw Oops.Oh("记录不存在");
+        await _db.Ado.ExecuteCommandAsync(
+            """
+            UPDATE PurOrdMaster
+            SET OrdDate=@OrdDate,Supp=@Supp,ReqBy=@ReqBy,Buyer=@Buyer,Department=@Department,`Usage`=@Usage,Remark=@Remark,UpdateUser=@UpdateUser,UpdateTime=@UpdateTime
+            WHERE RecID=@Id
+            """,
+            new SugarParameter("@Id", input.Id.Value),
+            new SugarParameter("@OrdDate", ordDate),
+            new SugarParameter("@Supp", input.Supp.Trim()),
+            new SugarParameter("@ReqBy", reqBy),
+            new SugarParameter("@Buyer", buyer),
+            new SugarParameter("@Department", input.Department.Trim()),
+            new SugarParameter("@Usage", usage),
+            new SugarParameter("@Remark", input.Remark?.Trim()),
+            new SugarParameter("@UpdateUser", _userManager.Account),
+            new SugarParameter("@UpdateTime", now)
+        );
+        return new { id = input.Id, message = "编辑成功" };
+    }
+
+    [DisplayName("删除委外加工订单")]
+    [HttpPost("outsource-order/delete/{id:int}")]
+    public async Task<object> DeleteOrder(int id, [FromQuery] string purOrd)
+    {
+        if (string.IsNullOrWhiteSpace(purOrd)) throw Oops.Oh("缺少采购订单号");
+        var hasReceipt = await _db.Ado.GetIntAsync(
+            """
+            SELECT COUNT(1)
+            FROM PurOrdRctDetail
+            WHERE OrdNbr=@PurOrd AND IFNULL(QtyReceived,0)>0
+            """,
+            new SugarParameter("@PurOrd", purOrd.Trim()));
+        if (hasReceipt > 0) throw Oops.Oh("存在收货数量,不允许删除");
+
+        var hasShip = await _db.Ado.GetIntAsync(
+            "SELECT COUNT(1) FROM scm_shdzb WHERE po_bill=@PurOrd",
+            new SugarParameter("@PurOrd", purOrd.Trim()));
+        if (hasShip > 0) throw Oops.Oh("存在发货单,不允许删除");
+
+        try
+        {
+            _db.Ado.BeginTran();
+            await _db.Ado.ExecuteCommandAsync("DELETE FROM PurOrdDetailBatch WHERE PurOrd=@PurOrd", new SugarParameter("@PurOrd", purOrd.Trim()));
+            await _db.Ado.ExecuteCommandAsync("DELETE FROM PurOrdDetail WHERE PurOrd=@PurOrd", new SugarParameter("@PurOrd", purOrd.Trim()));
+            await _db.Ado.ExecuteCommandAsync("DELETE FROM PurOrdMaster WHERE RecID=@Id AND PurOrd=@PurOrd", new SugarParameter("@Id", id), new SugarParameter("@PurOrd", purOrd.Trim()));
+            _db.Ado.CommitTran();
+        }
+        catch
+        {
+            _db.Ado.RollbackTran();
+            throw;
+        }
+
+        return new { message = "删除成功" };
+    }
+
+    [DisplayName("委外采购明细列表")]
+    [HttpGet("outsource-order/detail/list")]
+    public async Task<object> GetDetailList([FromQuery] OutsourceOrderDetailListInput input)
+    {
+        var page = input.Page <= 0 ? 1 : input.Page;
+        var pageSize = input.PageSize <= 0 ? 10 : input.PageSize;
+        var offset = (page - 1) * pageSize;
+
+        var where = new List<string> { "d.Potype='PW'" };
+        var pars = new List<SugarParameter>();
+        if (!string.IsNullOrWhiteSpace(input.PurOrd))
+        {
+            where.Add("d.PurOrd LIKE @PurOrd");
+            pars.Add(new SugarParameter("@PurOrd", $"%{input.PurOrd.Trim()}%"));
+        }
+        if (!string.IsNullOrWhiteSpace(input.ItemNum))
+        {
+            where.Add("d.ItemNum LIKE @ItemNum");
+            pars.Add(new SugarParameter("@ItemNum", $"%{input.ItemNum.Trim()}%"));
+        }
+        if (!string.IsNullOrWhiteSpace(input.Location))
+        {
+            where.Add("d.Location=@Location");
+            pars.Add(new SugarParameter("@Location", input.Location.Trim()));
+        }
+        if (!string.IsNullOrWhiteSpace(input.WorkOrd))
+        {
+            where.Add("d.WorkOrd LIKE @WorkOrd");
+            pars.Add(new SugarParameter("@WorkOrd", $"%{input.WorkOrd.Trim()}%"));
+        }
+        if (!string.IsNullOrWhiteSpace(input.LotSerial))
+        {
+            where.Add("d.LotSerial LIKE @LotSerial");
+            pars.Add(new SugarParameter("@LotSerial", $"%{input.LotSerial.Trim()}%"));
+        }
+
+        var fromSql = $"""
+            FROM PurOrdDetail d
+            LEFT JOIN ItemMaster i ON d.ItemNum=i.ItemNum
+            WHERE {string.Join(" AND ", where)}
+            """;
+        var total = await _db.Ado.GetIntAsync($"SELECT COUNT(1) {fromSql}", pars);
+        var list = await _db.Ado.SqlQueryAsync<OutsourceOrderDetailRow>(
+            $"""
+            SELECT
+                d.PurOrd AS PurOrd,d.ItemNum AS ItemNum,d.QtyOrded AS QtyOrded,d.QtyReceived AS QtyReceived,d.QtyReleased AS QtyReleased,
+                d.DueDate AS DueDate,d.Line AS Line,d.Location AS Location,d.UM AS UM,d.RecID AS Id,d.Rev AS Rev,d.Drawing AS Drawing,
+                d.WorkOrd AS WorkOrd,d.LotSerial AS LotSerial,d.Status AS Status,IFNULL(i.Descr,'') AS Descr,d.PurOrdRecID AS PurOrdRecID
+            {fromSql}
+            ORDER BY {BuildDetailOrderBy(input.SortField, input.SortOrder)}
+            LIMIT {pageSize} OFFSET {offset}
+            """,
+            pars);
+        return new { total, page, pageSize, list };
+    }
+
+    [DisplayName("委外采购明细详情")]
+    [HttpGet("outsource-order/detail/{id:int}")]
+    public async Task<object> GetDetail(int id)
+    {
+        var detail = (await _db.Ado.SqlQueryAsync<OutsourceOrderDetailRow>(
+            """
+            SELECT
+                d.PurOrd AS PurOrd,d.ItemNum AS ItemNum,d.QtyOrded AS QtyOrded,d.QtyReceived AS QtyReceived,d.QtyReleased AS QtyReleased,
+                d.DueDate AS DueDate,d.Line AS Line,d.Location AS Location,d.UM AS UM,d.RecID AS Id,d.Rev AS Rev,d.Drawing AS Drawing,
+                d.WorkOrd AS WorkOrd,d.LotSerial AS LotSerial,d.Status AS Status,IFNULL(i.Descr,'') AS Descr,d.PurOrdRecID AS PurOrdRecID
+            FROM PurOrdDetail d
+            LEFT JOIN ItemMaster i ON d.ItemNum=i.ItemNum
+            WHERE d.RecID=@Id
+            LIMIT 1
+            """,
+            new SugarParameter("@Id", id))).FirstOrDefault();
+        if (detail == null) throw Oops.Oh("记录不存在");
+
+        var batches = await _db.Ado.SqlQueryAsync<OutsourceOrderBatchRow>(
+            """
+            SELECT
+                b.Batch AS Batch,b.ItemNum AS ItemNum,b.SuppItem AS SuppItem,b.UM AS UM,b.Location AS Location,b.QtyOrded AS QtyOrded,
+                b.QtyBO AS QtyBO,b.QtyReleased AS QtyReleased,b.QtyReceived AS QtyReceived,b.QtyReturned AS QtyReturned,b.LotSerial AS LotSerial
+            FROM PurOrdDetailBatch b
+            WHERE b.PurOrdDetailRecID=@RecId
+            ORDER BY b.Batch
+            """,
+            new SugarParameter("@RecId", detail.Id));
+
+        return new { detail, batches };
+    }
+
+    [DisplayName("保存委外采购明细")]
+    [HttpPost("outsource-order/detail/save")]
+    public async Task<object> SaveDetail([FromBody] OutsourceOrderDetailSaveInput input)
+    {
+        if (string.IsNullOrWhiteSpace(input.PurOrd)) throw Oops.Oh("采购订单号不能为空");
+        if (input.PurOrdRecID <= 0) throw Oops.Oh("采购订单主键值不能为空");
+        if (string.IsNullOrWhiteSpace(input.ItemNum)) throw Oops.Oh("请选择物料");
+        if (!input.QtyOrded.HasValue || input.QtyOrded.Value <= 0) throw Oops.Oh("订单数量必须大于0");
+
+        var item = (await _db.Ado.SqlQueryAsync<ItemLookupRow>(
+            """
+            SELECT ItemNum,Descr,UM,Location,Rev,Drawing
+            FROM ItemMaster
+            WHERE ItemNum=@ItemNum
+            LIMIT 1
+            """,
+            new SugarParameter("@ItemNum", input.ItemNum.Trim()))).FirstOrDefault();
+        if (item == null) throw Oops.Oh("物料不存在");
+
+        try
+        {
+            _db.Ado.BeginTran();
+            int detailId;
+            int line;
+            if (input.Id is null or <= 0)
+            {
+                line = input.Line ?? await _db.Ado.GetIntAsync("SELECT IFNULL(MAX(Line),0)+1 FROM PurOrdDetail WHERE PurOrd=@PurOrd", new SugarParameter("@PurOrd", input.PurOrd.Trim()));
+                await _db.Ado.ExecuteCommandAsync(
+                    """
+                    INSERT INTO PurOrdDetail
+                    (PurOrd,Line,ItemNum,Descr,UM,Rev,Drawing,Location,QtyOrded,QtyReceived,QtyReleased,DueDate,LotSerial,Potype,PurOrdRecID,Status,CreateUser,CreateTime,UpdateUser,UpdateTime,tenant_id)
+                    VALUES
+                    (@PurOrd,@Line,@ItemNum,@Descr,@UM,@Rev,@Drawing,@Location,@QtyOrded,0,0,@DueDate,@LotSerial,'PW',@PurOrdRecID,'R',@CreateUser,@CreateTime,@UpdateUser,@UpdateTime,@TenantId)
+                    """,
+                    new SugarParameter("@PurOrd", input.PurOrd.Trim()),
+                    new SugarParameter("@Line", line),
+                    new SugarParameter("@ItemNum", item.ItemNum),
+                    new SugarParameter("@Descr", item.Descr),
+                    new SugarParameter("@UM", item.UM),
+                    new SugarParameter("@Rev", item.Rev),
+                    new SugarParameter("@Drawing", item.Drawing),
+                    new SugarParameter("@Location", item.Location),
+                    new SugarParameter("@QtyOrded", input.QtyOrded.Value),
+                    new SugarParameter("@DueDate", ParseDate(input.DueDate)),
+                    new SugarParameter("@LotSerial", string.Empty),
+                    new SugarParameter("@PurOrdRecID", input.PurOrdRecID),
+                    new SugarParameter("@CreateUser", _userManager.Account),
+                    new SugarParameter("@CreateTime", DateTime.Now),
+                    new SugarParameter("@UpdateUser", _userManager.Account),
+                    new SugarParameter("@UpdateTime", DateTime.Now),
+                    new SugarParameter("@TenantId", _userManager.TenantId <= 0 ? null : _userManager.TenantId)
+                );
+                detailId = await _db.Ado.GetIntAsync("SELECT IFNULL(MAX(RecID),0) FROM PurOrdDetail WHERE PurOrd=@PurOrd AND Line=@Line", new SugarParameter("@PurOrd", input.PurOrd.Trim()), new SugarParameter("@Line", line));
+            }
+            else
+            {
+                detailId = input.Id.Value;
+                line = input.Line ?? await _db.Ado.GetIntAsync("SELECT IFNULL(Line,0) FROM PurOrdDetail WHERE RecID=@Id", new SugarParameter("@Id", detailId));
+                await _db.Ado.ExecuteCommandAsync(
+                    """
+                    UPDATE PurOrdDetail
+                    SET ItemNum=@ItemNum,Descr=@Descr,UM=@UM,Rev=@Rev,Drawing=@Drawing,Location=@Location,QtyOrded=@QtyOrded,DueDate=@DueDate,UpdateUser=@UpdateUser,UpdateTime=@UpdateTime
+                    WHERE RecID=@Id
+                    """,
+                    new SugarParameter("@Id", detailId),
+                    new SugarParameter("@ItemNum", item.ItemNum),
+                    new SugarParameter("@Descr", item.Descr),
+                    new SugarParameter("@UM", item.UM),
+                    new SugarParameter("@Rev", item.Rev),
+                    new SugarParameter("@Drawing", item.Drawing),
+                    new SugarParameter("@Location", item.Location),
+                    new SugarParameter("@QtyOrded", input.QtyOrded.Value),
+                    new SugarParameter("@DueDate", ParseDate(input.DueDate)),
+                    new SugarParameter("@UpdateUser", _userManager.Account),
+                    new SugarParameter("@UpdateTime", DateTime.Now)
+                );
+            }
+
+            await _db.Ado.ExecuteCommandAsync("DELETE FROM PurOrdDetailBatch WHERE PurOrdDetailRecID=@RecID", new SugarParameter("@RecID", detailId));
+
+            var batchNo = 1;
+            foreach (var b in input.Batches)
+            {
+                if (string.IsNullOrWhiteSpace(b.SuppItem) && string.IsNullOrWhiteSpace(b.ItemNum))
+                    continue;
+                var batchItem = (await _db.Ado.SqlQueryAsync<ItemLookupRow>(
+                    """
+                    SELECT ItemNum,Descr,UM,Location,Rev,Drawing
+                    FROM ItemMaster
+                    WHERE ItemNum=@ItemNum
+                    LIMIT 1
+                    """,
+                    new SugarParameter("@ItemNum", (b.SuppItem ?? b.ItemNum)!.Trim()))).FirstOrDefault();
+                await _db.Ado.ExecuteCommandAsync(
+                    """
+                    INSERT INTO PurOrdDetailBatch
+                    (PurOrd,Potype,Line,Batch,ItemNum,SuppItem,UM,Location,QtyOrded,QtyBO,QtyReleased,QtyReceived,QtyReturned,LotSerial,PurOrdDetailRecID,CreateUser,CreateTime,UpdateUser,UpdateTime,tenant_id)
+                    VALUES
+                    (@PurOrd,'PW',@Line,@Batch,@ItemNum,@SuppItem,@UM,@Location,@QtyOrded,@QtyBO,@QtyReleased,@QtyReceived,@QtyReturned,@LotSerial,@PurOrdDetailRecID,@CreateUser,@CreateTime,@UpdateUser,@UpdateTime,@TenantId)
+                    """,
+                    new SugarParameter("@PurOrd", input.PurOrd.Trim()),
+                    new SugarParameter("@Line", line),
+                    new SugarParameter("@Batch", b.Batch ?? batchNo),
+                    new SugarParameter("@ItemNum", batchItem?.ItemNum ?? b.ItemNum?.Trim()),
+                    new SugarParameter("@SuppItem", b.SuppItem?.Trim()),
+                    new SugarParameter("@UM", b.UM ?? batchItem?.UM),
+                    new SugarParameter("@Location", b.Location ?? batchItem?.Location),
+                    new SugarParameter("@QtyOrded", b.QtyOrded ?? 0),
+                    new SugarParameter("@QtyBO", b.QtyBO ?? 0),
+                    new SugarParameter("@QtyReleased", b.QtyReleased ?? 0),
+                    new SugarParameter("@QtyReceived", b.QtyReceived ?? 0),
+                    new SugarParameter("@QtyReturned", b.QtyReturned ?? 0),
+                    new SugarParameter("@LotSerial", b.LotSerial?.Trim()),
+                    new SugarParameter("@PurOrdDetailRecID", detailId),
+                    new SugarParameter("@CreateUser", _userManager.Account),
+                    new SugarParameter("@CreateTime", DateTime.Now),
+                    new SugarParameter("@UpdateUser", _userManager.Account),
+                    new SugarParameter("@UpdateTime", DateTime.Now),
+                    new SugarParameter("@TenantId", _userManager.TenantId <= 0 ? null : _userManager.TenantId)
+                );
+                batchNo++;
+            }
+
+            _db.Ado.CommitTran();
+            return new { id = detailId, message = "保存成功" };
+        }
+        catch
+        {
+            _db.Ado.RollbackTran();
+            throw;
+        }
+    }
+
+    [DisplayName("删除委外采购明细")]
+    [HttpPost("outsource-order/detail/delete/{id:int}")]
+    public async Task<object> DeleteDetail(int id)
+    {
+        var status = await _db.Ado.GetStringAsync("SELECT Status FROM PurOrdDetail WHERE RecID=@Id LIMIT 1", new SugarParameter("@Id", id));
+        if (string.Equals(status, "C", StringComparison.OrdinalIgnoreCase))
+            throw Oops.Oh("关闭状态不允许删除");
+
+        try
+        {
+            _db.Ado.BeginTran();
+            await _db.Ado.ExecuteCommandAsync("DELETE FROM PurOrdDetailBatch WHERE PurOrdDetailRecID=@Id", new SugarParameter("@Id", id));
+            await _db.Ado.ExecuteCommandAsync("DELETE FROM PurOrdDetail WHERE RecID=@Id", new SugarParameter("@Id", id));
+            _db.Ado.CommitTran();
+        }
+        catch
+        {
+            _db.Ado.RollbackTran();
+            throw;
+        }
+        return new { message = "删除成功" };
+    }
+
+    [DisplayName("委外订单新增明细初始化")]
+    [HttpGet("outsource-order/detail/init")]
+    public async Task<object> GetDetailInit([FromQuery] string purOrd)
+    {
+        if (string.IsNullOrWhiteSpace(purOrd)) throw Oops.Oh("采购订单号不能为空");
+        var row = (await _db.Ado.SqlQueryAsync<DetailInitRow>(
+            """
+            SELECT p.`Usage` AS `Usage`, IFNULL(MAX(d.Line),0)+1 AS Line, p.RecID AS PurOrdRecID, p.PurOrd AS PurOrd
+            FROM PurOrdMaster p
+            LEFT JOIN PurOrdDetail d ON p.PurOrd=d.PurOrd
+            WHERE p.PurOrd=@PurOrd
+            GROUP BY p.`Usage`, p.RecID, p.PurOrd
+            LIMIT 1
+            """,
+            new SugarParameter("@PurOrd", purOrd.Trim()))).FirstOrDefault();
+        return row ?? throw Oops.Oh("采购订单不存在");
+    }
+
+    [DisplayName("选择物料列表")]
+    [HttpGet("outsource-order/items")]
+    public async Task<object> GetItemList([FromQuery] ItemSelectListInput input)
+    {
+        var page = input.Page <= 0 ? 1 : input.Page;
+        var pageSize = input.PageSize <= 0 ? 10 : input.PageSize;
+        var offset = (page - 1) * pageSize;
+        var where = new List<string> { "1=1" };
+        var pars = new List<SugarParameter>();
+
+        if (!string.IsNullOrWhiteSpace(input.ItemNum))
+        {
+            where.Add("ItemNum LIKE @ItemNum");
+            pars.Add(new SugarParameter("@ItemNum", $"%{input.ItemNum.Trim()}%"));
+        }
+        if (!string.IsNullOrWhiteSpace(input.Descr))
+        {
+            where.Add("Descr LIKE @Descr");
+            pars.Add(new SugarParameter("@Descr", $"%{input.Descr.Trim()}%"));
+        }
+
+        var fromSql = $"FROM ItemMaster WHERE {string.Join(" AND ", where)}";
+        var total = await _db.Ado.GetIntAsync($"SELECT COUNT(1) {fromSql}", pars);
+        var list = await _db.Ado.SqlQueryAsync<ItemLookupRow>(
+            $"""
+            SELECT RecID, ItemNum, Descr, Descr1, UM, Location, Rev, Drawing
+            {fromSql}
+            ORDER BY {BuildItemOrderBy(input.SortField, input.SortOrder)}
+            LIMIT {pageSize} OFFSET {offset}
+            """,
+            pars);
+        return new { total, page, pageSize, list };
+    }
+
+    [DisplayName("采购组下拉")]
+    [HttpGet("outsource-order/options/buyers")]
+    public async Task<object> GetBuyerOptions()
+    {
+        var list = await _db.Ado.SqlQueryAsync<OptionRow>(
+            """
+            SELECT Employee AS Value, CONCAT(TRIM(Employee),' ',TRIM(IFNULL(Name,''))) AS Label
+            FROM EmployeeMaster
+            ORDER BY Employee
+            """);
+        return new { list };
+    }
+
+    [DisplayName("供应商下拉")]
+    [HttpGet("outsource-order/options/suppliers")]
+    public async Task<object> GetSupplierOptions()
+    {
+        var list = await _db.Ado.SqlQueryAsync<OptionRow>(
+            """
+            SELECT Supp AS Value, CONCAT(TRIM(Supp),' ',TRIM(IFNULL(SortName,''))) AS Label
+            FROM SuppMaster
+            ORDER BY Supp
+            """);
+        return new { list };
+    }
+
+    [DisplayName("部门下拉")]
+    [HttpGet("outsource-order/options/departments")]
+    public async Task<object> GetDepartmentOptions()
+    {
+        var list = await _db.Ado.SqlQueryAsync<OptionRow>(
+            """
+            SELECT Department AS Value, CONCAT(TRIM(Department),' ',TRIM(IFNULL(Descr,''))) AS Label
+            FROM DepartmentMaster
+            ORDER BY Department
+            """);
+        return new { list };
+    }
+
+    [DisplayName("库位下拉")]
+    [HttpGet("outsource-order/options/locations")]
+    public async Task<object> GetLocationOptions()
+    {
+        var list = await _db.Ado.SqlQueryAsync<OptionRow>(
+            """
+            SELECT location AS Value, CONCAT(TRIM(location),' ',TRIM(IFNULL(descr,''))) AS Label
+            FROM LocationMaster
+            WHERE IFNULL(typed,'')<>'Supp'
+            ORDER BY location
+            """);
+        return new { list };
+    }
+
+    private static DateTime? ParseDate(string? v) => DateTime.TryParse(v, out var d) ? d : null;
+
+    private static string BuildOrderBy(string? sortField, string? sortOrder)
+    {
+        var dir = string.Equals(sortOrder, "asc", StringComparison.OrdinalIgnoreCase) ? "ASC" : "DESC";
+        return sortField?.ToLowerInvariant() switch
+        {
+            "purord" => $"p.PurOrd {dir}",
+            "suppname" => $"p.Supp {dir}",
+            "buyer" => $"p.Buyer {dir}",
+            "orddate" => $"p.OrdDate {dir}",
+            "duedate" => $"p.DueDate {dir}",
+            "curr" => $"p.Curr {dir}",
+            "status" => $"p.Status {dir}",
+            _ => "p.RecID DESC"
+        };
+    }
+
+    private static string BuildDetailOrderBy(string? sortField, string? sortOrder)
+    {
+        var dir = string.Equals(sortOrder, "asc", StringComparison.OrdinalIgnoreCase) ? "ASC" : "DESC";
+        return sortField?.ToLowerInvariant() switch
+        {
+            "purord" => $"d.PurOrd {dir}",
+            "line" => $"d.Line {dir}",
+            "itemnum" => $"d.ItemNum {dir}",
+            "descr" => $"i.Descr {dir}",
+            "um" => $"d.UM {dir}",
+            "rev" => $"d.Rev {dir}",
+            "drawing" => $"d.Drawing {dir}",
+            "qtyorded" => $"d.QtyOrded {dir}",
+            "qtyreceived" => $"d.QtyReceived {dir}",
+            "qtyreleased" => $"d.QtyReleased {dir}",
+            "duedate" => $"d.DueDate {dir}",
+            "location" => $"d.Location {dir}",
+            "workord" => $"d.WorkOrd {dir}",
+            "lotserial" => $"d.LotSerial {dir}",
+            "status" => $"d.Status {dir}",
+            _ => "d.RecID DESC"
+        };
+    }
+
+    private static string BuildItemOrderBy(string? sortField, string? sortOrder)
+    {
+        var dir = string.Equals(sortOrder, "asc", StringComparison.OrdinalIgnoreCase) ? "ASC" : "DESC";
+        return sortField?.ToLowerInvariant() switch
+        {
+            "itemnum" => $"ItemNum {dir}",
+            "descr" => $"Descr {dir}",
+            "descr1" => $"Descr1 {dir}",
+            "um" => $"UM {dir}",
+            "location" => $"Location {dir}",
+            "rev" => $"Rev {dir}",
+            "drawing" => $"Drawing {dir}",
+            _ => "RecID DESC"
+        };
+    }
+
+    private sealed class OutsourceOrderListRow
+    {
+        public int Id { get; set; }
+        public string? PurOrd { get; set; }
+        public string? SuppName { get; set; }
+        public string? Potype { get; set; }
+        public string? ReqBy { get; set; }
+        public string? Buyer { get; set; }
+        public string? DepartmentDescr { get; set; }
+        public DateTime? OrdDate { get; set; }
+        public DateTime? DueDate { get; set; }
+        public string? Curr { get; set; }
+        public string? Status { get; set; }
+        public string? Supp { get; set; }
+        public string? BuyerCode { get; set; }
+        public string? Department { get; set; }
+        public string? Usage { get; set; }
+        public string? Remark { get; set; }
+    }
+
+    private sealed class OutsourceOrderDetailRow
+    {
+        public int Id { get; set; }
+        public string? PurOrd { get; set; }
+        public int? Line { get; set; }
+        public string? ItemNum { get; set; }
+        public string? Descr { get; set; }
+        public string? UM { get; set; }
+        public string? Rev { get; set; }
+        public string? Drawing { get; set; }
+        public decimal? QtyOrded { get; set; }
+        public decimal? QtyReceived { get; set; }
+        public decimal? QtyReleased { get; set; }
+        public DateTime? DueDate { get; set; }
+        public string? Location { get; set; }
+        public string? WorkOrd { get; set; }
+        public string? LotSerial { get; set; }
+        public string? Status { get; set; }
+        public int? PurOrdRecID { get; set; }
+    }
+
+    private sealed class OutsourceOrderBatchRow
+    {
+        public int? Batch { get; set; }
+        public string? ItemNum { get; set; }
+        public string? SuppItem { get; set; }
+        public string? UM { get; set; }
+        public string? Location { get; set; }
+        public decimal? QtyOrded { get; set; }
+        public decimal? QtyBO { get; set; }
+        public decimal? QtyReleased { get; set; }
+        public decimal? QtyReceived { get; set; }
+        public decimal? QtyReturned { get; set; }
+        public string? LotSerial { get; set; }
+    }
+
+    private sealed class ItemLookupRow
+    {
+        public long RecID { get; set; }
+        public string? ItemNum { get; set; }
+        public string? Descr { get; set; }
+        public string? Descr1 { get; set; }
+        public string? UM { get; set; }
+        public string? Location { get; set; }
+        public string? Rev { get; set; }
+        public string? Drawing { get; set; }
+    }
+
+    private sealed class OptionRow
+    {
+        public string? Value { get; set; }
+        public string? Label { get; set; }
+    }
+
+    private sealed class DetailInitRow
+    {
+        public string? Usage { get; set; }
+        public int Line { get; set; }
+        public int PurOrdRecID { get; set; }
+        public string? PurOrd { get; set; }
+    }
+}

+ 163 - 0
server/Plugins/Admin.NET.Plugin.AiDOP/Supply/PurchaseOrderService.cs

@@ -0,0 +1,163 @@
+namespace Admin.NET.Plugin.AiDOP.Supply;
+
+/// <summary>
+/// 物料采购订单服务
+/// </summary>
+[ApiDescriptionSettings(Order = 309, Description = "物料采购订单")]
+[Route("api/Supply")]
+[AllowAnonymous]
+[NonUnify]
+public class PurchaseOrderService : IDynamicApiController, ITransient
+{
+    private readonly ISqlSugarClient _db;
+
+    public PurchaseOrderService(ISqlSugarClient db)
+    {
+        _db = db;
+    }
+
+    [DisplayName("物料采购订单列表")]
+    [HttpGet("purchase-order/list")]
+    public async Task<object> GetList([FromQuery] PurchaseOrderListInput input)
+    {
+        var page = input.Page <= 0 ? 1 : input.Page;
+        var pageSize = input.PageSize <= 0 ? 10 : input.PageSize;
+        var offset = (page - 1) * pageSize;
+
+        var where = new List<string> { "1=1" };
+        var pars = new List<SugarParameter>();
+
+        if (!string.IsNullOrWhiteSpace(input.Cgdd))
+        {
+            where.Add("a.cgdd LIKE @Cgdd");
+            pars.Add(new SugarParameter("@Cgdd", $"%{input.Cgdd.Trim()}%"));
+        }
+        if (!string.IsNullOrWhiteSpace(input.Wlbm))
+        {
+            where.Add("a.wlbm LIKE @Wlbm");
+            pars.Add(new SugarParameter("@Wlbm", $"%{input.Wlbm.Trim()}%"));
+        }
+        if (!string.IsNullOrWhiteSpace(input.Gysdm))
+        {
+            where.Add("a.gysdm LIKE @Gysdm");
+            pars.Add(new SugarParameter("@Gysdm", $"%{input.Gysdm.Trim()}%"));
+        }
+        if (!string.IsNullOrWhiteSpace(input.Sffbcx))
+        {
+            where.Add("(CASE WHEN b2.b2id IS NULL THEN 'X' ELSE 'V' END)=@Sffbcx");
+            pars.Add(new SugarParameter("@Sffbcx", input.Sffbcx.Trim().ToUpperInvariant()));
+        }
+
+        var fromSql = $"""
+            FROM (
+                SELECT
+                    a.id, a.wlbm, a.wlms, a.wlgg, a.cgdd, a.jhdsl, a.wjhsl, a.jhd, a.yjjhrq, a.gysdm, a.gysmc,
+                    DATE_FORMAT(DATE(a.yjjhrq), '%Y-%m-%d') AS jhrq,
+                    CASE
+                        WHEN IFNULL(b.jqhf,'')='' AND IFNULL(b.qhdj,'')='' THEN 'wait'
+                        WHEN IFNULL(b.jqhf,'')='' AND IFNULL(b.qhdj,'')<>'' THEN 'refuse'
+                        WHEN b.jqhf IS NOT NULL AND DATEDIFF(DATE(a.yjjhrq), DATE(b.jqhf))>=0 THEN 'OK'
+                        ELSE 'NO'
+                    END AS sfyq,
+                    b.jqhf, a.ddhh, a.th, a.bbh, a.StdCost, a.PurCost, a.PurCost * a.jhdsl AS je,
+                    a.createtime, a.buyer, a.type AS ddlx, a.dw, a.payumconv, a.orddate
+                FROM vscm_jhjh a
+                LEFT JOIN (
+                    SELECT glid, MAX(jqhf) AS jqhf, GROUP_CONCAT(qhdj SEPARATOR '') AS qhdj
+                    FROM scm_jhjh_jq
+                    WHERE flag = 0
+                    GROUP BY glid
+                ) b ON a.id = b.glid
+            ) a
+            LEFT JOIN (
+                SELECT OrdNbr, OrdLine, SUM(ReceiptQty + yssl) AS zshl, SUM(yssl) AS rksl, SUM(QtyReturn) AS bhgsl, SUM(QtyReturned) AS thsl
+                FROM vscm_cgshrk
+                GROUP BY OrdNbr, OrdLine
+            ) b ON a.cgdd = b.OrdNbr AND a.ddhh = b.OrdLine
+            LEFT JOIN (
+                SELECT po_bill, po_billline, SUM(sh_delivery_quantity) AS zfhl
+                FROM scm_shdzb a
+                INNER JOIN scm_shd b ON a.glid = b.id
+                WHERE b.state = 0
+                GROUP BY po_bill, po_billline
+            ) c ON a.cgdd = c.po_bill AND a.ddhh = c.po_billline
+            LEFT JOIN (
+                SELECT cgdd, ddhh, MIN(id) AS b2id, MIN(hfsj) AS hfsj
+                FROM scm_jhjh_jq
+                WHERE flag = 2
+                GROUP BY cgdd, ddhh
+            ) b2 ON a.cgdd = b2.cgdd AND a.ddhh = b2.ddhh
+            WHERE {string.Join(" AND ", where)}
+            """;
+
+        var orderBy = BuildOrderBy(input.SortField, input.SortOrder);
+        var total = await _db.Ado.GetIntAsync($"SELECT COUNT(1) {fromSql}", pars);
+        var list = await _db.Ado.SqlQueryAsync<PurchaseOrderListRow>(
+            $"""
+            SELECT
+                a.gysdm AS Gysdm,
+                a.gysmc AS Gysmc,
+                a.cgdd AS Cgdd,
+                a.ddhh AS Ddhh,
+                a.buyer AS Buyer,
+                a.orddate AS Orddate,
+                a.wlbm AS Wlbm,
+                a.wlms AS Wlms,
+                a.ddlx AS Ddlx,
+                a.jhdsl AS Jhdsl,
+                a.wjhsl AS Wjhsl,
+                a.jhrq AS Jhrq,
+                a.th AS Th,
+                CASE WHEN b2.b2id IS NULL THEN 'X' ELSE DATE_FORMAT(b2.hfsj, '%Y.%m.%d') END AS Sffb,
+                CASE WHEN b2.b2id IS NULL THEN 'X' ELSE 'V' END AS Sffbcx
+            {fromSql}
+            ORDER BY {orderBy}
+            LIMIT {pageSize} OFFSET {offset}
+            """,
+            pars);
+
+        return new { total, page, pageSize, list };
+    }
+
+    private static string BuildOrderBy(string? sortField, string? sortOrder)
+    {
+        var dir = string.Equals(sortOrder, "asc", StringComparison.OrdinalIgnoreCase) ? "ASC" : "DESC";
+        return sortField?.ToLowerInvariant() switch
+        {
+            "gysdm" => $"a.gysdm {dir}",
+            "gysmc" => $"a.gysmc {dir}",
+            "cgdd" => $"a.cgdd {dir}",
+            "ddhh" => $"a.ddhh {dir}",
+            "buyer" => $"a.buyer {dir}",
+            "orddate" => $"a.orddate {dir}",
+            "wlbm" => $"a.wlbm {dir}",
+            "wlms" => $"a.wlms {dir}",
+            "ddlx" => $"a.ddlx {dir}",
+            "jhdsl" => $"a.jhdsl {dir}",
+            "wjhsl" => $"a.wjhsl {dir}",
+            "jhrq" => $"a.jhrq {dir}",
+            "th" => $"a.th {dir}",
+            "sffb" => $"b2.hfsj {dir}",
+            _ => "a.id DESC"
+        };
+    }
+
+    private sealed class PurchaseOrderListRow
+    {
+        public string? Gysdm { get; set; }
+        public string? Gysmc { get; set; }
+        public string? Cgdd { get; set; }
+        public string? Ddhh { get; set; }
+        public string? Buyer { get; set; }
+        public DateTime? Orddate { get; set; }
+        public string? Wlbm { get; set; }
+        public string? Wlms { get; set; }
+        public string? Ddlx { get; set; }
+        public decimal? Jhdsl { get; set; }
+        public decimal? Wjhsl { get; set; }
+        public string? Jhrq { get; set; }
+        public string? Th { get; set; }
+        public string? Sffb { get; set; }
+        public string? Sffbcx { get; set; }
+    }
+}

+ 171 - 66
server/Plugins/Admin.NET.Plugin.AiDOP/Universal/Entity/ItemMaster.cs

@@ -7,91 +7,196 @@ namespace Admin.NET.Plugin.AiDOP.Universal;
 [SugarTable("ItemMaster", "物料主数据")]
 [SugarTable("ItemMaster", "物料主数据")]
 public class ItemMaster
 public class ItemMaster
 {
 {
-    /// <summary>自增列</summary>
-    [SugarColumn(ColumnName = "RecID", IsPrimaryKey = true, IsIdentity = true)]
-    public int RecID { get; set; }
+	[SugarColumn(ColumnName = "RecID", IsPrimaryKey = true, IsIdentity = true)]
+	public int RecID { get; set; }
 
 
-    /// <summary>域名</summary>
-    [SugarColumn(ColumnName = "Domain", Length = 8, IsNullable = true)]
-    public string? Domain { get; set; }
+	[SugarColumn(ColumnName = "Domain", Length = 50, IsNullable = true)]
+	public string? Domain { get; set; }
 
 
-    /// <summary>零件号</summary>
-    [SugarColumn(ColumnName = "ItemNum", Length = 30, IsNullable = true)]
-    public string? ItemNum { get; set; }
+	[SugarColumn(ColumnName = "ItemNum", Length = 100)]
+	public string ItemNum { get; set; } = string.Empty;
 
 
-    /// <summary>描述</summary>
-    [SugarColumn(ColumnName = "Descr", Length = 60, IsNullable = true)]
-    public string? Descr { get; set; }
+	[SugarColumn(ColumnName = "Descr", Length = 200)]
+	public string Descr { get; set; } = string.Empty;
 
 
-    /// <summary>描述1</summary>
-    [SugarColumn(ColumnName = "Descr1", Length = 60, IsNullable = true)]
-    public string? Descr1 { get; set; }
+	[SugarColumn(ColumnName = "Descr1", Length = 200, IsNullable = true)]
+	public string? Descr1 { get; set; }
 
 
-    /// <summary>单位</summary>
-    [SugarColumn(ColumnName = "UM", Length = 4, IsNullable = true)]
-    public string? UM { get; set; }
+	[SugarColumn(ColumnName = "UM", Length = 50, IsNullable = true)]
+	public string? UM { get; set; }
 
 
-    /// <summary>库位</summary>
-    [SugarColumn(ColumnName = "Location", Length = 10, IsNullable = true)]
-    public string? Location { get; set; }
+	[SugarColumn(ColumnName = "Location", Length = 100, IsNullable = true)]
+	public string? Location { get; set; }
 
 
-    /// <summary>版本</summary>
-    [SugarColumn(ColumnName = "Rev", Length = 4, IsNullable = true)]
-    public string? Rev { get; set; }
+	[SugarColumn(ColumnName = "Rev", Length = 50, IsNullable = true)]
+	public string? Rev { get; set; }
 
 
-    /// <summary>图纸</summary>
-    [SugarColumn(ColumnName = "Drawing", Length = 30, IsNullable = true)]
-    public string? Drawing { get; set; }
+	[SugarColumn(ColumnName = "Drawing", Length = 100, IsNullable = true)]
+	public string? Drawing { get; set; }
 
 
-    /// <summary>状态</summary>
-    [SugarColumn(ColumnName = "Status", Length = 1, IsNullable = true)]
-    public string? Status { get; set; }
+	[SugarColumn(ColumnName = "Status", Length = 8, IsNullable = true)]
+	public string? Status { get; set; }
 
 
-    /// <summary>零件类型</summary>
-    [SugarColumn(ColumnName = "ItemType", Length = 2, IsNullable = true)]
-    public string? ItemType { get; set; }
+	[SugarColumn(ColumnName = "ItemType", Length = 100, IsNullable = true)]
+	public string? ItemType { get; set; }
 
 
-    /// <summary>P/L 件(P=采购/L=制造)</summary>
-    [SugarColumn(ColumnName = "PurMfg", Length = 1, IsNullable = true)]
-    public string? PurMfg { get; set; }
+	[SugarColumn(ColumnName = "PurMfg", Length = 100, IsNullable = true)]
+	public string? PurMfg { get; set; }
 
 
-    /// <summary>地点</summary>
-    [SugarColumn(ColumnName = "Site", Length = 8, IsNullable = true)]
-    public string? Site { get; set; }
+	[SugarColumn(ColumnName = "Site", Length = 12, IsNullable = true)]
+	public string? Site { get; set; }
 
 
-    /// <summary>购买者</summary>
-    [SugarColumn(ColumnName = "Buyer", Length = 8, IsNullable = true)]
-    public string? Buyer { get; set; }
+	[SugarColumn(ColumnName = "Buyer", Length = 8, IsNullable = true)]
+	public string? Buyer { get; set; }
 
 
-    /// <summary>计划员</summary>
-    [SugarColumn(ColumnName = "Planner", Length = 8, IsNullable = true)]
-    public string? Planner { get; set; }
+	[SugarColumn(ColumnName = "Planner", Length = 50, IsNullable = true)]
+	public string? Planner { get; set; }
 
 
-    /// <summary>生产线</summary>
-    [SugarColumn(ColumnName = "ProdLine", Length = 10, IsNullable = true)]
-    public string? ProdLine { get; set; }
+	[SugarColumn(ColumnName = "ProdLine", Length = 20, IsNullable = true)]
+	public string? ProdLine { get; set; }
 
 
-    /// <summary>采购计量单位</summary>
-    [SugarColumn(ColumnName = "PurUM", Length = 4, IsNullable = true)]
-    public string? PurUM { get; set; }
+	[SugarColumn(ColumnName = "PurUM", Length = 4, IsNullable = true)]
+	public string? PurUM { get; set; }
 
 
-    /// <summary>变更日期</summary>
-    [SugarColumn(ColumnName = "ModDate", IsNullable = true)]
-    public DateTime? ModDate { get; set; }
+	[SugarColumn(ColumnName = "PkgCode", Length = 50, IsNullable = true)]
+	public string? PkgCode { get; set; }
 
 
-    /// <summary>创建时间</summary>
-    [SugarColumn(ColumnName = "CreateTime", IsNullable = true)]
-    public DateTime? CreateTime { get; set; }
+	[SugarColumn(ColumnName = "DefaultShelf", Length = 100, IsNullable = true)]
+	public string? DefaultShelf { get; set; }
 
 
-    /// <summary>更新时间</summary>
-    [SugarColumn(ColumnName = "UpdateTime", IsNullable = true)]
-    public DateTime? UpdateTime { get; set; }
+	[SugarColumn(ColumnName = "KeyItem", ColumnDataType = "tinyint(1)")]
+	public bool KeyItem { get; set; }
 
 
-    /// <summary>有效</summary>
-    [SugarColumn(ColumnName = "IsActive", IsNullable = true)]
-    public int? IsActive { get; set; }
+	[SugarColumn(ColumnName = "NetWeight", ColumnDataType = "decimal(18,5)", IsNullable = true)]
+	public decimal? NetWeight { get; set; }
 
 
-    /// <summary>备注</summary>
-    [SugarColumn(ColumnName = "Remark", Length = 255, IsNullable = true)]
-    public string? Remark { get; set; }
+	[SugarColumn(ColumnName = "NetWeightUM", Length = 50, IsNullable = true)]
+	public string? NetWeightUM { get; set; }
+
+	[SugarColumn(ColumnName = "Inspect", ColumnDataType = "tinyint(1)")]
+	public bool Inspect { get; set; }
+
+	[SugarColumn(ColumnName = "IssuePolicy", ColumnDataType = "tinyint(1)")]
+	public bool IssuePolicy { get; set; }
+
+	[SugarColumn(ColumnName = "PurLT", IsNullable = true)]
+	public int? PurLT { get; set; }
+
+	[SugarColumn(ColumnName = "InsLT", IsNullable = true)]
+	public int? InsLT { get; set; }
+
+	[SugarColumn(ColumnName = "MfgLT", IsNullable = true)]
+	public int? MfgLT { get; set; }
+
+	[SugarColumn(ColumnName = "Length", ColumnDataType = "decimal(18,5)", IsNullable = true)]
+	public decimal? Length { get; set; }
+
+	[SugarColumn(ColumnName = "Size", ColumnDataType = "decimal(18,5)", IsNullable = true)]
+	public decimal? Size { get; set; }
+
+	[SugarColumn(ColumnName = "SizeUM", Length = 50, IsNullable = true)]
+	public string? SizeUM { get; set; }
+
+	[SugarColumn(ColumnName = "LocationType", Length = 50, IsNullable = true)]
+	public string? LocationType { get; set; }
+
+	[SugarColumn(ColumnName = "CommodityCode", Length = 100, IsNullable = true)]
+	public string? CommodityCode { get; set; }
+
+	[SugarColumn(ColumnName = "MaxOrd", ColumnDataType = "decimal(18,5)", IsNullable = true)]
+	public decimal? MaxOrd { get; set; }
+
+	[SugarColumn(ColumnName = "MinOrd", ColumnDataType = "decimal(18,5)", IsNullable = true)]
+	public decimal? MinOrd { get; set; }
+
+	[SugarColumn(ColumnName = "OrdMult", ColumnDataType = "decimal(18,5)", IsNullable = true)]
+	public decimal? OrdMult { get; set; }
+
+	[SugarColumn(ColumnName = "MinOrdSales", ColumnDataType = "decimal(18,5)", IsNullable = true)]
+	public decimal? MinOrdSales { get; set; }
+
+	[SugarColumn(ColumnName = "AutoLotNums", ColumnDataType = "tinyint(1)")]
+	public bool AutoLotNums { get; set; }
+
+	[SugarColumn(ColumnName = "Install", ColumnDataType = "tinyint(1)")]
+	public bool Install { get; set; }
+
+	[SugarColumn(ColumnName = "SafetyStk", ColumnDataType = "decimal(18,5)", IsNullable = true)]
+	public decimal? SafetyStk { get; set; }
+
+	[SugarColumn(ColumnName = "DaysBetweenPM", IsNullable = true)]
+	public int? DaysBetweenPM { get; set; }
+
+	[SugarColumn(ColumnName = "ExpireAlarmDays", IsNullable = true)]
+	public int? ExpireAlarmDays { get; set; }
+
+	[SugarColumn(ColumnName = "StockTurnOver", ColumnDataType = "decimal(18,5)", IsNullable = true)]
+	public decimal? StockTurnOver { get; set; }
+
+	[SugarColumn(ColumnName = "LotSerialControl", ColumnDataType = "tinyint(1)")]
+	public bool LotSerialControl { get; set; }
+
+	[SugarColumn(ColumnName = "AllocateSingleLot", ColumnDataType = "tinyint(1)")]
+	public bool AllocateSingleLot { get; set; }
+
+	[SugarColumn(ColumnName = "TraceDetail", ColumnDataType = "bit(1)", IsNullable = true)]
+	public bool? TraceDetail { get; set; }
+
+	[SugarColumn(ColumnName = "IsMainMas", ColumnDataType = "bit(1)", IsNullable = true)]
+	public bool? IsMainMas { get; set; }
+
+	[SugarColumn(ColumnName = "EMTType", Length = 50, IsNullable = true)]
+	public string? EmtType { get; set; }
+
+	[SugarColumn(ColumnName = "OwnerApplication", Length = 100, IsNullable = true)]
+	public string? OwnerApplication { get; set; }
+
+	[SugarColumn(ColumnName = "ModDate", IsNullable = true)]
+	public DateTime? ModDate { get; set; }
+
+	[SugarColumn(ColumnName = "CreateTime", IsNullable = true)]
+	public DateTime? CreateTime { get; set; }
+
+	[SugarColumn(ColumnName = "UpdateTime", IsNullable = true)]
+	public DateTime? UpdateTime { get; set; }
+
+	[SugarColumn(ColumnName = "CreateUser", Length = 24, IsNullable = true)]
+	public string? CreateUser { get; set; }
+
+	[SugarColumn(ColumnName = "UpdateUser", Length = 24, IsNullable = true)]
+	public string? UpdateUser { get; set; }
+
+	[SugarColumn(ColumnName = "IsActive", ColumnDataType = "bit(1)", IsNullable = true)]
+	public bool? IsActive { get; set; }
+
+	[SugarColumn(ColumnName = "IsConfirm", ColumnDataType = "bit(1)", IsNullable = true)]
+	public bool? IsConfirm { get; set; }
+
+	[SugarColumn(ColumnName = "Remark", Length = 200, IsNullable = true)]
+	public string? Remark { get; set; }
+
+	// 扩展列:与 ItemMaster 中下划线字段保持映射,避免后续查询缺列
+	[SugarColumn(ColumnName = "rec_id")]
+	public long RecIdEx { get; set; }
+
+	[SugarColumn(ColumnName = "company_ref_id")]
+	public long CompanyRefId { get; set; }
+
+	[SugarColumn(ColumnName = "factory_ref_id")]
+	public long FactoryRefId { get; set; }
+
+	[SugarColumn(ColumnName = "domain_code", Length = 50, IsNullable = true)]
+	public string? DomainCode { get; set; }
+
+	[SugarColumn(ColumnName = "item_num", Length = 100)]
+	public string ItemNumEx { get; set; } = string.Empty;
+
+	[SugarColumn(ColumnName = "is_active", ColumnDataType = "tinyint(1)")]
+	public bool IsActiveEx { get; set; }
+
+	[SugarColumn(ColumnName = "is_confirm", ColumnDataType = "tinyint(1)")]
+	public bool IsConfirmEx { get; set; }
+
+	[SugarColumn(ColumnName = "tenant_id", IsNullable = true)]
+	public long? TenantId { get; set; }
 }
 }

+ 8 - 8
server/Plugins/Admin.NET.Plugin.AiDOP/WorkOrder/Entity/WorkOrdMaster.cs

@@ -67,8 +67,8 @@ public class WorkOrdMaster
     [SugarColumn(ColumnName = "Remark", Length = 512, IsNullable = true)]
     [SugarColumn(ColumnName = "Remark", Length = 512, IsNullable = true)]
     public string? Remark { get; set; }
     public string? Remark { get; set; }
 
 
-    [SugarColumn(ColumnName = "Priority", IsNullable = true)]
-    public int? Priority { get; set; }
+    [SugarColumn(ColumnName = "Priority", ColumnDataType = "decimal(18,8)")]
+    public decimal Priority { get; set; }
 
 
     [SugarColumn(ColumnName = "Urgent", IsNullable = true)]
     [SugarColumn(ColumnName = "Urgent", IsNullable = true)]
     public int? Urgent { get; set; }
     public int? Urgent { get; set; }
@@ -76,8 +76,8 @@ public class WorkOrdMaster
     [SugarColumn(ColumnName = "CustNo", Length = 64, IsNullable = true)]
     [SugarColumn(ColumnName = "CustNo", Length = 64, IsNullable = true)]
     public string? CustNo { get; set; }
     public string? CustNo { get; set; }
 
 
-    [SugarColumn(ColumnName = "Class", Length = 64, IsNullable = true)]
-    public string? Class { get; set; }
+    [SugarColumn(ColumnName = "Class", ColumnDataType = "decimal(18,8)", IsNullable = true)]
+    public decimal? Class { get; set; }
 
 
     [SugarColumn(ColumnName = "ItemName", Length = 256, IsNullable = true)]
     [SugarColumn(ColumnName = "ItemName", Length = 256, IsNullable = true)]
     public string? ItemName { get; set; }
     public string? ItemName { get; set; }
@@ -142,11 +142,11 @@ public class WorkOrdMaster
     [SugarColumn(ColumnName = "UpdateTime", IsNullable = true)]
     [SugarColumn(ColumnName = "UpdateTime", IsNullable = true)]
     public DateTime? UpdateTime { get; set; }
     public DateTime? UpdateTime { get; set; }
 
 
-    [SugarColumn(ColumnName = "IsActive", IsNullable = true)]
-    public int? IsActive { get; set; }
+    [SugarColumn(ColumnName = "IsActive", ColumnDataType = "tinyint(1)")]
+    public bool IsActive { get; set; }
 
 
-    [SugarColumn(ColumnName = "IsConfirm", IsNullable = true)]
-    public int? IsConfirm { get; set; }
+    [SugarColumn(ColumnName = "IsConfirm", ColumnDataType = "tinyint(1)")]
+    public bool IsConfirm { get; set; }
 
 
     /// <summary>租户ID(多租户隔离)</summary>
     /// <summary>租户ID(多租户隔离)</summary>
     [SugarColumn(ColumnName = "tenant_id", IsNullable = true)]
     [SugarColumn(ColumnName = "tenant_id", IsNullable = true)]