Kaynağa Gözat

feat(s0): S0 制造 A 类页公司/工厂前台无感化(订单排程周期/人员技能/生产线/标准工序)

复用批次6 useS0MfgOrgScope helper,将 4 页公司/工厂从"用户前台选择字段"降级为"系统后台归属字段":

- 新增:applyDefaultOrgScopeForCreate 自动补默认 SysOrg 归属(S0示范公司/一工厂),用户无需选择公司/工厂。
- ProductionLine/StandardProcess:因 domain(工厂域编码) 后端 Required 非空、原由工厂派生,openCreate 用 getDefaultS0OrgScope 灌 form.domain=S0-F001、submit 兜底补 domain,并随公司/工厂一并隐藏工厂域编码控件。
- 编辑:preserveOrgScopeForEdit 保留接口返回的历史归属,不转雪花。
- 提交:normalizeOrgScopeForSubmit 确保 payload 稳定携带 companyRefId/factoryRefId(/domainCode/domain)。
- watcher 走 shouldClearFactoryOnCompanyChange 守卫,隐藏字段/程序化载入不清空 legacy 工厂。
- 移除公司/工厂用户必填 rules(ProductionLine 一并移除 companyHit/factoryHit 提交阻断,避免编辑 legacy 记录被误拦截)。
- 对话框隐藏公司/工厂(及 domain 页的工厂域编码)输入控件,form 值与 payload 不变。
- 仅前端 4 页,未改 helper、未改后端;不做 legacy↔SysOrg 域映射、不回填历史数据。

chore: bump version Web 2.4.239
YY968XX 15 saat önce
ebeveyn
işleme
2aba1d9c89

+ 1 - 1
Web/package.json

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

+ 12 - 5
Web/src/views/aidop/s0/manufacturing/OrderScheduleCycleList.vue

@@ -53,12 +53,13 @@
 		<el-dialog v-model="dialogVisible" :title="dialogTitle" width="620px" destroy-on-close @closed="resetForm">
 			<el-form ref="formRef" :model="form" :rules="rules" label-width="130px">
 				<el-row :gutter="16">
-					<el-col :span="12"><el-form-item label="公司" prop="companyRefId">
+					<!-- 公司/工厂已降级为系统后台归属字段:前台隐藏输入控件,form 值与 payload 仍由 openCreate 补默认 / openEdit 保值维护。 -->
+					<el-col :span="12" v-if="false"><el-form-item label="公司" prop="companyRefId">
 						<el-select v-model="form.companyRefId" filterable style="width: 100%">
 							<el-option v-for="item in companyOptions" :key="item.id" :label="item.name || item.code || `${item.id}`" :value="item.id" />
 						</el-select>
 					</el-form-item></el-col>
-					<el-col :span="12"><el-form-item label="工厂" prop="factoryRefId">
+					<el-col :span="12" v-if="false"><el-form-item label="工厂" prop="factoryRefId">
 						<el-select v-model="form.factoryRefId" filterable style="width: 100%" @change="syncDomainCode">
 							<el-option v-for="item in formFactoryOptions" :key="item.id" :label="item.name || item.code || `${item.id}`" :value="item.id" />
 						</el-select>
@@ -84,6 +85,7 @@ import { useRoute } from 'vue-router';
 import { ElMessage, ElMessageBox, type FormInstance, type FormRules } from 'element-plus';
 import AidopDemoShell from '../../components/AidopDemoShell.vue';
 import { loadOrgList, s0MfgOrderScheduleCyclesApi, type OrgOption, type S0OrderScheduleCycleRow, type S0OrderScheduleCycleUpsert } from '../api/s0ManufacturingApi';
+import { applyDefaultOrgScopeForCreate, normalizeOrgScopeForSubmit, preserveOrgScopeForEdit, shouldClearFactoryOnCompanyChange } from '../composables/useS0MfgOrgScope';
 
 const route = useRoute();
 const pageTitle = computed(() => (route.meta?.title as string) || '订单排程周期');
@@ -129,13 +131,12 @@ const form = reactive<S0OrderScheduleCycleUpsert>({
 });
 
 const rules: FormRules = {
-	companyRefId: [{ required: true, message: '请选择公司', trigger: 'change' }],
-	factoryRefId: [{ required: true, message: '请选择工厂', trigger: 'change' }],
+	// 公司/工厂已降级为系统后台归属字段(新增自动补默认、编辑保留原值),不再作为用户前台必填项。
 	stdHours: [{ required: true, message: '请填写标准排程时长', trigger: 'blur' }],
 };
 
 watch(() => form.companyRefId, () => {
-	if (!formFactoryOptions.value.some((item) => item.id === form.factoryRefId)) form.factoryRefId = undefined;
+	if (shouldClearFactoryOnCompanyChange(formFactoryOptions.value, form.factoryRefId, false)) { form.factoryRefId = undefined; }
 });
 watch(() => query.companyRefId, () => {
 	if (!filteredFactoryOptions.value.some((item) => item.id === query.factoryRefId)) query.factoryRefId = undefined;
@@ -186,6 +187,8 @@ function resetForm() {
 
 function openCreate() {
 	resetForm();
+	// 新增:自动补默认 SysOrg 归属,用户无需选择公司/工厂。
+	applyDefaultOrgScopeForCreate(form, companyOptions.value, factoryOptions.value);
 	dialogTitle.value = '新增订单排程周期';
 	dialogVisible.value = true;
 }
@@ -199,11 +202,15 @@ function openEdit(row: S0OrderScheduleCycleRow) {
 		orderType: row.orderType ?? '', stdHours: row.stdHours, remarks: row.remarks ?? '', isActive: row.isActive,
 		createUser: row.createUser ?? '', updateUser: row.updateUser ?? '',
 	});
+	// 编辑:保留接口返回的原归属(legacy 不转雪花)。
+	preserveOrgScopeForEdit(form, row);
 	dialogVisible.value = true;
 }
 
 async function submitForm() {
 	await formRef.value?.validate();
+	// 提交前统一归属:缺则补默认、有则保留,不做 legacy↔雪花映射。
+	normalizeOrgScopeForSubmit(form, editingId.value ? 'edit' : 'create', companyOptions.value, factoryOptions.value);
 	saving.value = true;
 	try {
 		const payload: S0OrderScheduleCycleUpsert = { ...form };

+ 21 - 7
Web/src/views/aidop/s0/manufacturing/PersonnelSkillList.vue

@@ -53,7 +53,8 @@
 
 		<el-dialog v-model="dialogVisible" :title="dialogTitle" width="520px" destroy-on-close @closed="resetForm">
 			<el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
-				<el-form-item label="公司" prop="companyRefId">
+				<!-- 公司/工厂已降级为系统后台归属字段:前台隐藏输入控件,form 值与 payload 仍由 openCreate 补默认 / openEdit 保值维护。 -->
+				<el-form-item label="公司" prop="companyRefId" v-if="false">
 					<el-select v-model="form.companyRefId" filterable style="width: 100%">
 						<el-option
 							v-for="item in companyOptions"
@@ -63,7 +64,7 @@
 						/>
 					</el-select>
 				</el-form-item>
-				<el-form-item label="工厂" prop="factoryRefId">
+				<el-form-item label="工厂" prop="factoryRefId" v-if="false">
 					<el-select v-model="form.factoryRefId" filterable style="width: 100%">
 						<el-option
 							v-for="item in formFactories"
@@ -102,14 +103,20 @@ import { computed, onMounted, reactive, ref, watch } from 'vue';
 import { useRoute } from 'vue-router';
 import { ElMessage, ElMessageBox, type FormInstance, type FormRules } from 'element-plus';
 import AidopDemoShell from '../../components/AidopDemoShell.vue';
-import { useS0MfgOrgScope } from '../composables/useS0MfgOrgScope';
+import {
+	useS0MfgOrgScope,
+	applyDefaultOrgScopeForCreate,
+	normalizeOrgScopeForSubmit,
+	preserveOrgScopeForEdit,
+	shouldClearFactoryOnCompanyChange,
+} from '../composables/useS0MfgOrgScope';
 import { s0MfgPersonSkillsApi } from '../api/s0ManufacturingApi';
 
 const route = useRoute();
 const pageTitle = computed(() => (route.meta?.title as string) || '人员技能');
 type Row = Record<string, any>;
 
-const { companyOptions, loadOrgs, factoriesForCompany } = useS0MfgOrgScope();
+const { companyOptions, factoryOptions, loadOrgs, factoriesForCompany } = useS0MfgOrgScope();
 const queryFactories = computed(() => factoriesForCompany(query.companyRefId));
 const formFactories = computed(() => factoriesForCompany(form.companyRefId));
 
@@ -150,13 +157,14 @@ watch(
 watch(
 	() => form.companyRefId,
 	() => {
-		if (!formFactories.value.some((x) => x.id === form.factoryRefId)) form.factoryRefId = undefined;
+		if (shouldClearFactoryOnCompanyChange(formFactories.value, form.factoryRefId, false)) {
+			form.factoryRefId = undefined;
+		}
 	},
 );
 
 const rules: FormRules = {
-	companyRefId: [{ required: true, message: '请选择公司', trigger: 'change' }],
-	factoryRefId: [{ required: true, message: '请选择工厂', trigger: 'change' }],
+	// 公司/工厂已降级为系统后台归属字段(新增自动补默认、编辑保留原值),不再作为用户前台必填项。
 	code: [{ required: true, message: '请填写编码', trigger: 'blur' }],
 	name: [{ required: true, message: '请填写名称', trigger: 'blur' }],
 };
@@ -207,6 +215,8 @@ function resetForm() {
 
 function openCreate() {
 	resetForm();
+	// 新增:自动补默认 SysOrg 归属,用户无需选择公司/工厂。
+	applyDefaultOrgScopeForCreate(form, companyOptions.value, factoryOptions.value);
 	dialogTitle.value = '新增人员技能';
 	dialogVisible.value = true;
 }
@@ -224,11 +234,15 @@ function openEdit(row: Row) {
 		remark: row.remark || '',
 		isEnabled: row.isEnabled,
 	});
+	// 编辑:保留接口返回的原归属(legacy 不转雪花)。
+	preserveOrgScopeForEdit(form, row);
 	dialogVisible.value = true;
 }
 
 async function submitForm() {
 	await formRef.value?.validate();
+	// 提交前统一归属:缺则补默认、有则保留(含 legacy),不做 legacy↔雪花映射。
+	normalizeOrgScopeForSubmit(form, editingId.value ? 'edit' : 'create', companyOptions.value, factoryOptions.value);
 	saving.value = true;
 	try {
 		const payload = { ...form };

+ 24 - 11
Web/src/views/aidop/s0/manufacturing/ProductionLineList.vue

@@ -65,7 +65,8 @@
 		<el-dialog v-model="dialogVisible" :title="dialogTitle" width="720px" destroy-on-close @closed="resetForm">
 			<el-form ref="formRef" :model="form" :rules="rules" label-width="120px">
 				<el-row :gutter="12">
-					<el-col :span="12">
+					<!-- 公司/工厂/工厂域编码已降级为系统后台归属字段:前台隐藏输入控件,form 值与 payload 仍由 openCreate 补默认 / openEdit 保值 / submit 兜底维护。 -->
+					<el-col v-if="false" :span="12">
 						<el-form-item label="公司" prop="companyRefId">
 							<el-select v-model="form.companyRefId" filterable style="width: 100%" @change="onFormCompanyChange">
 								<el-option v-for="item in companyOptions" :key="item.id" :label="item.name || item.code || `${item.id}`" :value="item.id" />
@@ -78,7 +79,7 @@
 							</el-select>
 						</el-form-item>
 					</el-col>
-					<el-col :span="12">
+					<el-col v-if="false" :span="12">
 						<el-form-item label="工厂" prop="factoryRefId">
 							<el-select v-model="form.factoryRefId" filterable style="width: 100%" @change="syncDomainFromFactory">
 								<el-option v-for="item in formFactories" :key="item.id" :label="item.name || item.code || `${item.id}`" :value="item.id" />
@@ -91,7 +92,7 @@
 							</el-select>
 						</el-form-item>
 					</el-col>
-					<el-col :span="24">
+					<el-col v-if="false" :span="24">
 						<el-form-item label="工厂域编码" prop="domain">
 							<el-input v-model="form.domain" placeholder="通常与所选工厂编码一致" clearable />
 						</el-form-item>
@@ -180,6 +181,7 @@ import { ElMessage, ElMessageBox, type FormInstance, type FormRules } from 'elem
 import AidopDemoShell from '../../components/AidopDemoShell.vue';
 import { useS0MfgOrgScope } from '../composables/useS0MfgOrgScope';
 import { s0MfgProductionLinesApi, type S0LineMasterRow, type S0LineMasterUpsert } from '../api/s0ManufacturingApi';
+import { applyDefaultOrgScopeForCreate, getDefaultS0OrgScope, normalizeOrgScopeForSubmit, preserveOrgScopeForEdit, shouldClearFactoryOnCompanyChange } from '../composables/useS0MfgOrgScope';
 import { loadDictOptions, type OptionItem } from '../api/s0SalesApi';
 import DictDataMaintainPanel from '../sales/components/DictDataMaintainPanel.vue';
 
@@ -261,16 +263,17 @@ watch(
 	() => form.companyRefId,
 	() => {
 		if (isRehydrating.value) return;
-		if (!formFactories.value.some((x) => x.id === form.factoryRefId)) {
+		// 公司/工厂为隐藏的后台归属字段,此变化均为程序化载入(openCreate 补默认 / openEdit 回填),
+		// 非用户主动切换,故 userInitiated=false,永不清空已载入的工厂值(保护编辑 legacy 归属)。
+		if (shouldClearFactoryOnCompanyChange(formFactories.value, form.factoryRefId, false)) {
 			form.factoryRefId = undefined;
 			form.domain = '';
 		}
 	},
 );
 
+// 公司/工厂已降级为系统后台归属字段(新增自动补默认、编辑保留原值),不再作为用户前台必填项。
 const rules: FormRules = {
-	companyRefId: [{ required: true, message: '请选择公司', trigger: 'change' }],
-	factoryRefId: [{ required: true, message: '请选择工厂', trigger: 'change' }],
 	domain: [{ required: true, message: '请填写工厂域编码', trigger: 'blur' }],
 	line: [{ required: true, message: '请填写生产线编码', trigger: 'blur' }],
 };
@@ -343,6 +346,10 @@ function resetForm() {
 
 function openCreate() {
 	resetForm();
+	// 新增:自动补默认 SysOrg 归属 + 工厂域编码,用户无需选择公司/工厂。
+	applyDefaultOrgScopeForCreate(form, companyOptions.value, factoryOptions.value);
+	const __scope = getDefaultS0OrgScope(companyOptions.value, factoryOptions.value);
+	form.domain = __scope.domainCode ?? form.domain ?? '';
 	dialogTitle.value = '新增生产线';
 	dialogVisible.value = true;
 }
@@ -371,6 +378,8 @@ async function openEdit(row: S0LineMasterRow) {
 			midLocation: detail.midLocation || '',
 			isActive: (detail as any).isActive !== false,
 		});
+		// 编辑:保留接口返回的原归属(legacy 不转雪花)。
+		preserveOrgScopeForEdit(form, detail);
 		await nextTick();
 	} finally {
 		isRehydrating.value = false;
@@ -381,13 +390,17 @@ async function openEdit(row: S0LineMasterRow) {
 async function submitForm() {
 	await formRef.value?.validate();
 
-	const companyHit = companyOptions.value.some((o) => o.id === String(form.companyRefId));
-	const factoryHit = factoryOptions.value.some((o) => o.id === String(form.factoryRefId));
-	if (!companyHit || !factoryHit) {
-		ElMessage.warning('当前记录未关联有效公司/工厂,请先选择公司和工厂后再保存。');
-		return;
+	// 提交前统一归属:缺则补默认、有则保留(含 legacy),不做 legacy↔雪花映射。
+	normalizeOrgScopeForSubmit(form, editingId.value ? 'edit' : 'create', companyOptions.value, factoryOptions.value);
+	// 工厂域编码兜底:仍为空时取默认工厂 code,避免隐藏控件后 domain 缺失。
+	if (!form.domain) {
+		const __s = getDefaultS0OrgScope(companyOptions.value, factoryOptions.value);
+		form.domain = __s.domainCode ?? form.domain;
 	}
 
+	// 公司/工厂已降级为系统后台归属字段:新增由 normalize 补默认、编辑保留原记录归属(含 legacy 1000/8010,
+	// 后端 Range(1,∞) 接受),不再以"归属必须命中雪花下拉"为提交前置,避免编辑 legacy 记录被误拦截。
+
 	const payload: S0LineMasterUpsert = {
 		companyRefId: form.companyRefId,
 		factoryRefId: form.factoryRefId,

+ 24 - 7
Web/src/views/aidop/s0/manufacturing/StandardProcessList.vue

@@ -49,21 +49,22 @@
 		<el-dialog v-model="dialogVisible" :title="dialogTitle" width="560px" destroy-on-close @closed="resetForm">
 			<el-form ref="formRef" :model="form" :rules="rules" label-width="120px">
 				<el-row :gutter="12">
-					<el-col :span="12">
+					<!-- 公司/工厂/工厂域编码已降级为系统后台归属字段:前台隐藏输入控件,form 值与 payload 仍由 openCreate 补默认 / openEdit 保值 / submit 兜底维护。 -->
+					<el-col v-if="false" :span="12">
 						<el-form-item label="公司" prop="companyRefId">
 							<el-select v-model="form.companyRefId" filterable style="width: 100%" @change="onFormCompanyChange">
 								<el-option v-for="item in companyOptions" :key="item.id" :label="item.name || item.code || `${item.id}`" :value="item.id" />
 							</el-select>
 						</el-form-item>
 					</el-col>
-					<el-col :span="12">
+					<el-col v-if="false" :span="12">
 						<el-form-item label="工厂" prop="factoryRefId">
 							<el-select v-model="form.factoryRefId" filterable style="width: 100%" @change="syncDomainFromFactory">
 								<el-option v-for="item in formFactories" :key="item.id" :label="item.name || item.code || `${item.id}`" :value="item.id" />
 							</el-select>
 						</el-form-item>
 					</el-col>
-					<el-col :span="24">
+					<el-col v-if="false" :span="24">
 						<el-form-item label="工厂域编码" prop="domain">
 							<el-input v-model="form.domain" placeholder="通常与所选工厂编码一致" clearable />
 						</el-form-item>
@@ -94,11 +95,12 @@ import { ElMessage, ElMessageBox, type FormInstance, type FormRules } from 'elem
 import AidopDemoShell from '../../components/AidopDemoShell.vue';
 import { useS0MfgOrgScope } from '../composables/useS0MfgOrgScope';
 import { s0MfgStandardOperationsApi, type S0StdOpMasterRow } from '../api/s0ManufacturingApi';
+import { applyDefaultOrgScopeForCreate, getDefaultS0OrgScope, normalizeOrgScopeForSubmit, preserveOrgScopeForEdit, shouldClearFactoryOnCompanyChange } from '../composables/useS0MfgOrgScope';
 
 const route = useRoute();
 const pageTitle = computed(() => (route.meta?.title as string) || '标准工序列表');
 
-const { companyOptions, loadOrgs, factoriesForCompany } = useS0MfgOrgScope();
+const { companyOptions, factoryOptions, loadOrgs, factoriesForCompany } = useS0MfgOrgScope();
 const queryFactories = computed(() => factoriesForCompany(query.companyRefId));
 const formFactories = computed(() => factoriesForCompany(form.companyRefId));
 
@@ -140,16 +142,17 @@ watch(
 watch(
 	() => form.companyRefId,
 	() => {
-		if (!formFactories.value.some((x) => x.id === form.factoryRefId)) {
+		// 公司/工厂为隐藏的后台归属字段,此变化均为程序化载入(openCreate 补默认 / openEdit 回填),
+		// 非用户主动切换,故 userInitiated=false,永不清空已载入的工厂值(保护编辑 legacy 归属)。
+		if (shouldClearFactoryOnCompanyChange(formFactories.value, form.factoryRefId, false)) {
 			form.factoryRefId = undefined;
 			form.domain = '';
 		}
 	},
 );
 
+// 公司/工厂已降级为系统后台归属字段(新增自动补默认、编辑保留原值),不再作为用户前台必填项。
 const rules: FormRules = {
-	companyRefId: [{ required: true, message: '请选择公司', trigger: 'change' }],
-	factoryRefId: [{ required: true, message: '请选择工厂', trigger: 'change' }],
 	domain: [{ required: true, message: '请填写工厂域编码', trigger: 'blur' }],
 	stdOp: [{ required: true, message: '请填写标准工序名称', trigger: 'blur' }],
 	stdOpCode: [{ required: true, message: '请填写标准工序编码', trigger: 'blur' }],
@@ -214,6 +217,11 @@ function resetForm() {
 
 function openCreate() {
 	resetForm();
+	// 新增:自动补默认 SysOrg 归属(公司/工厂),用户无需选择公司/工厂。
+	applyDefaultOrgScopeForCreate(form, companyOptions.value, factoryOptions.value);
+	// 隐藏工厂后 @change 不再触发,工厂域编码从默认工厂 code 灌入(取不到默认组织时保留原值)。
+	const __scope = getDefaultS0OrgScope(companyOptions.value, factoryOptions.value);
+	form.domain = __scope.domainCode ?? form.domain ?? '';
 	dialogTitle.value = '新增标准工序';
 	dialogVisible.value = true;
 }
@@ -230,11 +238,20 @@ function openEdit(row: S0StdOpMasterRow) {
 		stdOp: row.stdOp || '',
 		milestoneOp: row.milestoneOp || '',
 	});
+	// 编辑:保留接口返回记录的原归属(legacy 不转雪花);form.domain 已从 row.domain 回填保持不动。
+	preserveOrgScopeForEdit(form, row);
 	dialogVisible.value = true;
 }
 
 async function submitForm() {
 	await formRef.value?.validate();
+	// 提交前统一归属:缺则补默认、有则保留(含 legacy),不做 legacy↔雪花映射。
+	normalizeOrgScopeForSubmit(form, editingId.value ? 'edit' : 'create', companyOptions.value, factoryOptions.value);
+	// 工厂域编码兜底:隐藏工厂后仍确保 domain 有值(取默认工厂 code)。
+	if (!form.domain) {
+		const __s = getDefaultS0OrgScope(companyOptions.value, factoryOptions.value);
+		form.domain = __s.domainCode ?? form.domain;
+	}
 	saving.value = true;
 	try {
 		const payload = {