Ver Fonte

合同管理解除终止单:adaptContract 补 terminationId/terminationProcessInstanceId,台账状态按 changeStatusName/terminationStatusName 展示并补状态码兜底表(DRAFT/PENDING_APPROVAL/SIGNING/EFFECTIVE/REJECTED/CANCELLED),编辑解除草稿携带 terminationId 原地修改、详情按主键精确定位草稿并可看解除审批记录,解除附件改多值键(修正误传 offlineFileId 导致解除详情一直暂无附件);薪酬档案参保城市改省市级联按 6 位行政区划码写 cityCode(直辖市补 0000、省直辖县级市原样),社保方案下拉带 loading 与联动,工伤浮动系数按方案是否含工伤险种禁用并要求大于 0,模板版本说明改为已启用模板当前版本;薪酬档案列表与社保方案去掉内部口径(税务法规依据、fieldPolicy、固定基数试算合计与独立查看列),改为点编号/名称进详情并补创建时间列;员工档案跳转薪资档案成功后关闭详情抽屉;调岗生效日期禁止早于今天;工资条重算错误文案改由后端返回原样展示

lihaiting há 3 dias atrás
pai
commit
4bc9476743

+ 5 - 0
src/api/hr/index.js

@@ -1129,6 +1129,11 @@ export function adaptContract(item = {}) {
     renewalContractId: item.renewalContractId || "",
     /* 续签审批流程实例:续签审批中的行查看「审批记录」用(列表未返回时回落到新合同详情) */
     renewalProcessInstanceId: item.renewalProcessInstanceId || "",
+    /* 解除终止单主键(2026-10-19 契约确认):解除审批(hr_contract_termination_approval)
+       的 businessId;编辑解除草稿时也用它原地修改 */
+    terminationId: item.terminationId || "",
+    /* 解除审批流程实例:解除审批中的行查看「审批记录」用 */
+    terminationProcessInstanceId: item.terminationProcessInstanceId || "",
   };
 }
 

+ 1 - 0
src/api/salary/salarySlip.js

@@ -736,6 +736,7 @@ export async function recalculateBatch(batchId, reason = '页面重算') {
  * 薪资核算预检查结果(★ 2026-10-09 新补接口的响应结构)
  * 原始字段:executable / candidateEmployeeCount / templateId / templateVersionId /
  *          issueCounts(按风险级别计数)/ issues[{code,message,riskLevel,userId}]
+ * ★ 2026-10-19:问题文案由后端负责(后端已优化 message),前端原样展示,不做映射、不做兜底。
  */
 function adaptPrecheck(raw = {}) {
   const counts = raw.issueCounts || {};

+ 168 - 39
src/views/contractManagement/index.vue

@@ -220,7 +220,8 @@
           </div>
           <div class="cm-detail-status">
             <small>合同状态</small>
-            <span class="cm-status" :class="statusClass(detailRow.statusCode)">{{ detailRow.status }}</span>
+            <!-- 与列表「状态」列同一套 el-tag 配色(statusTagType),不再用自定义底色 -->
+            <el-tag size="mini" :type="statusTagType(detailRow.statusCode)">{{ detailRow.status }}</el-tag>
           </div>
         </div>
 
@@ -310,9 +311,9 @@
           <div v-for="change in detailChanges" :key="'change-' + change.id" class="cm-detail-flow">
             <div class="cm-detail-flow-head">
               <span class="cm-detail-flow-title">变更单 {{ change.changeNo || '(编号未返回)' }}</span>
-              <el-tag size="mini" :type="changeStatusTagType(change.changeStatus)">{{ change.changeStatus || '状态未返回'
+              <el-tag size="mini" :type="changeStatusTagType(change.changeStatus)">{{ change.changeStatusText ||
+                '状态未返回'
               }}</el-tag>
-              <span class="cm-detail-flow-meta">变更类型:{{ change.changeTypeText }}</span>
               <span class="cm-detail-flow-meta">生效日期:{{ change.effectiveDateText }}</span>
               <span class="cm-detail-flow-meta">签署方式:{{ change.signingMethodText }}</span>
               <span class="cm-detail-flow-meta">审批单据:{{ change.documentCode || '-' }}</span>
@@ -330,6 +331,14 @@
                     <span class="cm-compare-label">{{ field.label }}:</span>
                     <span class="cm-compare-value">{{ compareFieldText(change.before, field) }}</span>
                   </div>
+                  <div class="cm-compare-item is-span-2">
+                    <span class="cm-compare-label">合同附件:</span>
+                    <span class="cm-compare-value">
+                      <fileMain v-if="change.before && change.before.attachmentFileIds.length"
+                        :value="change.before.attachmentFileIds" type="view" size="small" />
+                      <span v-else class="cm-detail-muted">暂无附件</span>
+                    </span>
+                  </div>
                 </div>
               </div>
               <div class="cm-compare-card">
@@ -343,6 +352,14 @@
                       :class="['cm-compare-value', { 'is-highlight': compareFieldChanged(change.before, change.after, field) }]">{{
                         compareFieldText(change.after, field) }}</span>
                   </div>
+                  <div class="cm-compare-item is-span-2">
+                    <span class="cm-compare-label">合同附件:</span>
+                    <span class="cm-compare-value">
+                      <fileMain v-if="change.after && change.after.attachmentFileIds.length"
+                        :value="change.after.attachmentFileIds" type="view" size="small" />
+                      <span v-else class="cm-detail-muted">暂无附件</span>
+                    </span>
+                  </div>
                 </div>
               </div>
             </div>
@@ -415,7 +432,7 @@
             <div class="cm-detail-flow-head">
               <span class="cm-detail-flow-title">解除终止单 {{ item.terminationNo || '(编号未返回)' }}</span>
               <el-tag size="mini" :type="terminationStatusTagType(item.terminationStatus)">{{
-                item.terminationStatus || '状态未返回' }}</el-tag>
+                item.terminationStatusText || '状态未返回' }}</el-tag>
               <span class="cm-detail-flow-meta">业务类型:{{ item.typeText }}</span>
               <span class="cm-detail-flow-meta">生效日期:{{ item.effectiveDateText }}</span>
               <span class="cm-detail-flow-meta">最后工作日:{{ item.lastWorkingDateText }}</span>
@@ -559,6 +576,11 @@
                 <el-date-picker v-model="createForm.endDate" type="date" value-format="yyyy-MM-dd" placeholder="请选择终止日期"
                   style="width:100%;" />
               </el-form-item>
+              <!-- 变更原因:仅变更模式展示;随变更草稿提交(changeReason),不再前端写死 -->
+              <el-form-item v-if="isChangeMode" label="变更原因" prop="changeReason">
+                <el-input v-model="createForm.changeReason" type="textarea" :rows="2" maxlength="200"
+                  show-word-limit placeholder="请填写变更原因" />
+              </el-form-item>
               <el-form-item label="签署方式" prop="signMethod">
                 <div class="cm-create-sign-method">
                   <button type="button" class="cm-method-btn is-unavailable"
@@ -894,17 +916,45 @@ const TERMINATE_REASON_FALLBACK = [
   { value: 'OTHER', label: '其他' },
 ];
 
-/* 变更类型编码 → 文案:编码取自 swagger「劳动合同变更参数」changeType 说明
-   (POSITION、SALARY、LOCATION、SCHEDULE、DEPARTMENT、OTHER),只做字面翻译 */
-const CHANGE_TYPE_LABELS = {
-  POSITION: '岗位调整',
-  SALARY: '薪资调整',
-  LOCATION: '工作地点调整',
-  SCHEDULE: '工作时间调整',
-  DEPARTMENT: '部门调整',
-  OTHER: '其他',
+/* 变更单 / 解除终止单的业务状态码空间一致(swagger 2026-10-19):
+   DRAFT、PENDING_APPROVAL、SIGNING、EFFECTIVE、REJECTED、CANCELLED。
+   台账接口给了 changeStatusName / terminationStatusName 时用后端的,没给时用这张表翻中文,
+   未知编码原样展示(不编造文案)。 */
+const FLOW_DOC_STATUS_LABELS = {
+  DRAFT: '草稿',
+  PENDING_APPROVAL: '审批中',
+  SIGNING: '签署中',
+  EFFECTIVE: '已生效',
+  REJECTED: '已驳回',
+  CANCELLED: '已取消',
 };
 
+/** 台账状态展示文案:后端中文名优先,缺失时按已知编码翻译,未知编码原样回显 */
+function flowDocStatusText(item = {}, codeKey, nameKey) {
+  const code = String(item[codeKey] || '').toUpperCase();
+  return (
+    item[nameKey] ||
+    FLOW_DOC_STATUS_LABELS[code] ||
+    item[codeKey] ||
+    ''
+  );
+}
+
+/** 秒(10 位)/毫秒(13 位)时间戳 → 本地时区 YYYY-MM-DD;其余按日期字符串截取 */
+function detailDateText(value) {
+  if (!value) return '';
+  const str = String(value).trim();
+  if (/^\d{10}$/.test(str)) return tsToYMD(Number(str) * 1000);
+  if (/^\d{13}$/.test(str)) return tsToYMD(Number(str));
+  return str.slice(0, 10);
+}
+
+function tsToYMD(ms) {
+  const d = new Date(ms);
+  const pad = (n) => String(n).padStart(2, '0');
+  return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
+}
+
 /* 解除/终止业务类型:TERMINATE-解除、END-终止(swagger「劳动合同解除终止参数」) */
 const TERMINATION_TYPE_LABELS = { TERMINATE: '解除', END: '终止' };
 
@@ -1131,6 +1181,8 @@ function blankCreateForm() {
     probationEnd: '',
     /* 试用期月数:由员工信息带出(probationPeriod,数字月数),只读;提交字段为 probationMonths */
     probationMonths: null,
+    /* 变更原因:仅变更模式展示/提交(changeReason),不再前端写死 */
+    changeReason: '',
     salary: '',
     manager: '',
     // 签署方式默认线下签订
@@ -1207,6 +1259,10 @@ export default {
       createVisible: false,
       createMode: 'create',
       changeSnapshot: blankChangeSnapshot(),
+      /* 编辑中的变更草稿主键:保存时带上 = 原地修改(2026-10-19 后端支持);空 = 新建 */
+      editingChangeId: '',
+      /* 编辑中的续签草稿主键(= 续签生成的新合同主键):保存时带上 = 原地修改;空 = 新建 */
+      editingRenewContractId: '',
       createForm: blankCreateForm(),
       createRules: {
         employeeId: [{ required: true, message: '请选择员工', trigger: 'change' }],
@@ -1220,6 +1276,18 @@ export default {
         positionType: [{ required: true, message: '请选择岗位性质', trigger: 'change' }],
         startDate: [{ required: true, message: '请选择合同起始日期', trigger: 'change' }],
         endDate: [{ required: true, message: '请选择合同终止日期', trigger: 'change' }],
+        changeReason: [
+          {
+            /* required: true 让必填星号显示;校验逻辑用 validator(仅变更模式必填) */
+            required: true,
+            validator: (_rule, value, callback) => {
+              if (!this.isChangeMode) return callback();
+              if (String(value || '').trim()) return callback();
+              callback(new Error('请填写变更原因'));
+            },
+            trigger: 'blur',
+          },
+        ],
         signMethod: [{ required: true, message: '请选择签署方式', trigger: 'change' }],
         templateId: [
           {
@@ -1258,6 +1326,8 @@ export default {
       detailCompareFields: DETAIL_COMPARE_FIELDS,
       terminateVisible: false,
       terminateSubmitting: false,
+      /* 编辑中的解除草稿主键:保存时带上 = 原地修改(2026-10-19 后端支持);空 = 新建 */
+      terminateDraftId: '',
       /* 解除草稿回填加载中:草稿态打开解除弹窗时先查详情 terminations */
       terminationLoading: false,
       terminateForm: blankTerminateForm(),
@@ -1606,12 +1676,23 @@ export default {
       };
       return map[tone] || 'info';
     },
-    /** 合同变更状态 tag 颜色:已知取值 DRAFT=变更草稿(抓包 2026-10-17),其余按编码关键字判定,识别不了用默认色 */
+    /** 合同变更状态 tag 颜色:与列表续签/解除状态同一套枚举配色(DRAFT=info、审批中=warning、
+        SIGNING=主色、EFFECTIVE=success、REJECTED=danger、CANCELLED=info);未知编码按关键字判定 */
     changeStatusTagType(code) {
       const value = String(code || '').toUpperCase();
+      const map = {
+        DRAFT: 'info',
+        PENDING_APPROVAL: 'warning',
+        SIGNING: '',
+        EFFECTIVE: 'success',
+        REJECTED: 'danger',
+        CANCELLED: 'info',
+      };
+      if (value in map) return map[value];
       if (/APPROV|PENDING|SUBMIT|PROCESS/.test(value)) return 'warning';
       if (/EFFECT|COMPLETE|DONE|SUCCESS|PASS/.test(value)) return 'success';
-      if (/REJECT|CANCEL|VOID|FAIL/.test(value)) return 'info';
+      if (/REJECT|FAIL/.test(value)) return 'danger';
+      if (/CANCEL|VOID/.test(value)) return 'info';
       if (/DRAFT/.test(value)) return 'info';
       return '';
     },
@@ -2100,7 +2181,7 @@ export default {
           statuses.includes(String(item.changeStatus || '').toUpperCase()),
         );
       }
-      if (!picks.length) return;
+      if (!picks.length) return null;
       /* 接口未承诺排序:多份变更单取 id 最大的(int64 以补零字符串比较) */
       const latest = picks.reduce((acc, cur) =>
         String(cur.id ?? '').padStart(20, '0') > String(acc?.id ?? '').padStart(20, '0')
@@ -2118,12 +2199,15 @@ export default {
         }
       }
       const form = this.createForm;
+      /* 变更原因不回填、不给默认值:一律留空由用户重新填写(旧草稿存的是当时写死的「合同变更」,不带回显) */
+      form.changeReason = '';
       if (snapshot.contractType) form.contractType = snapshot.contractType;
       if (snapshot.salaryStandard != null && snapshot.salaryStandard !== '') {
         form.salary = String(snapshot.salaryStandard);
       }
-      if (snapshot.startDate) form.startDate = String(snapshot.startDate).slice(0, 10);
-      if (snapshot.endDate) form.endDate = String(snapshot.endDate).slice(0, 10);
+      /* 快照日期可能是秒/毫秒时间戳(后端 beforeSnapshot 实测如此),统一归一成 YYYY-MM-DD */
+      if (snapshot.startDate) form.startDate = detailDateText(snapshot.startDate);
+      if (snapshot.endDate) form.endDate = detailDateText(snapshot.endDate);
       if (snapshot.probationMonths != null && snapshot.probationMonths !== '') {
         form.probationMonths = Number(snapshot.probationMonths);
       }
@@ -2147,6 +2231,8 @@ export default {
         form.positionType = String(snapshot.positionType);
       }
       if (snapshot.positionTypeName) form.positionTypeName = snapshot.positionTypeName;
+      /* 返回草稿(或最近变更单)主键:调用方保存时带上它 = 原地修改这份草稿 */
+      return latest.id || null;
     },
     /**
      * 打开变更弹框后按已回填的 部门 / 序列 / 岗位 / 性质 逐级加载下拉选项,
@@ -2421,6 +2507,11 @@ export default {
     buildDetailChange(item = {}) {
       const before = this.snapshotToCompareModel(this.parseSnapshot(item.beforeSnapshot));
       const after = this.snapshotToCompareModel(this.parseSnapshot(item.afterSnapshot));
+      /* 「变更后签署方式」的权威值是变更单顶层的 signingMethod(提交时即为变更后的值);
+         历史草稿的 afterSnapshot 可能没写签署方式(早期提交未入快照),缺失时取顶层,不猜 */
+      if (after && !after.signingMethod && item.signingMethod) {
+        after.signingMethod = signMethodCode(item.signingMethod);
+      }
       let gap = '';
       if (!before && !after) {
         gap = '该变更单未返回变更前 / 变更后快照(beforeSnapshot / afterSnapshot),无法展示变更内容,需后端补充。';
@@ -2431,8 +2522,7 @@ export default {
         id: item.id,
         changeNo: item.changeNo || '',
         changeStatus: item.changeStatus || '',
-        changeTypeText:
-          CHANGE_TYPE_LABELS[String(item.changeType || '').toUpperCase()] || item.changeType || '-',
+        changeStatusText: flowDocStatusText(item, 'changeStatus', 'changeStatusName'),
         effectiveDateText: this.formatDetailDate(item.effectiveDate),
         signingMethodText: signMethodLabel(item.signingMethod) || '-',
         documentCode: item.documentCode || '',
@@ -2478,6 +2568,7 @@ export default {
         id: item.id,
         terminationNo: item.terminationNo || '',
         terminationStatus: item.terminationStatus || '',
+        terminationStatusText: flowDocStatusText(item, 'terminationStatus', 'terminationStatusName'),
         typeText:
           TERMINATION_TYPE_LABELS[String(item.terminationType || '').toUpperCase()] ||
           item.terminationType ||
@@ -2557,10 +2648,10 @@ export default {
         case 'signMethod':
           return signMethodLabel(raw) || '-';
         case 'date':
-          return raw ? String(raw).slice(0, 10) : '-';
+          return detailDateText(raw) || '-';
         case 'dateEnd':
           /* 终止日期为空 = 无固定期限 */
-          return raw ? String(raw).slice(0, 10) : '长期';
+          return detailDateText(raw) || '长期';
         default:
           return raw === '' || raw === null || raw === undefined ? '-' : String(raw);
       }
@@ -2584,14 +2675,13 @@ export default {
       }
       if (field.type === 'signMethod') return signMethodCode(value);
       if (field.type === 'date' || field.type === 'dateEnd') {
-        return value ? String(value).slice(0, 10) : '';
+        return detailDateText(value);
       }
       return String(value).trim();
     },
-    /** 快照里的日期可能带时间(2026-10-17T00:00:00)→ 只留日期;空值显示 — */
+    /** 生效日期 / 最后工作日等台账日期:兼容日期串与秒/毫秒时间戳;空值显示 — */
     formatDetailDate(value) {
-      if (!value) return '-';
-      return String(value).slice(0, 10);
+      return detailDateText(value) || '-';
     },
     /**
      * 导出劳动合同台账。
@@ -2793,6 +2883,8 @@ export default {
       this.createVisible = false;
       this.createMode = 'create';
       this.changeSnapshot = blankChangeSnapshot();
+      this.editingChangeId = '';
+      this.editingRenewContractId = '';
       this.createForm = blankCreateForm();
       this.clearCreateQuery();
       if (typeof done === 'function') done();
@@ -2891,8 +2983,13 @@ export default {
            随后用该主键 + hr_contract_change_approval 发起审批,终态通过后才把字段应用到合同上。
            所以这里必须把返回的变更单主键带出去,不能用合同 id 顶替。 */
         const changeId = await changeContract(form.id, compact({
+          /* ★ 2026-10-19 后端支持:携带变更草稿主键 = 原地修改该草稿(不传 = 新建);
+             编辑已有草稿时必须带上,否则会撞「已有草稿」校验 */
+          id: this.editingChangeId || undefined,
+          /* ★ 变更类型仍是接口枚举必填项,前端目前只有「其他」一种业务口径,固定 OTHER;
+             变更原因改为用户在表单里填写(changeReason),不再写死「合同变更」 */
           changeType: 'OTHER',
-          changeReason: '合同变更',
+          changeReason: String(form.changeReason).trim(),
           effectiveDate: form.startDate || todayStr(),
           signingMethod: payload.signingMethod,
           offlineFileId: form.signMethod === '线下' ? (form.fileIds?.[0] || form.fileId) : undefined,
@@ -2916,6 +3013,9 @@ export default {
             positionLevel: payload.positionLevel,
             positionType: payload.positionType,
             positionTypeName: payload.positionTypeName,
+            /* 签署方式与合同附件也是变更内容:不进快照,详情「变更后」就看不到(2026-10-19 用户反馈) */
+            signingMethod: payload.signingMethod,
+            attachmentIds: payload.attachmentIds,
           }),
         }));
         return { id: form.id, changeId, payload };
@@ -2930,6 +3030,8 @@ export default {
             ...payload,
             renewal: true,
             probationMonths: 0,
+            /* ★ 2026-10-19 后端支持:携带续签草稿(新合同)主键 = 原地修改该草稿 */
+            id: this.editingRenewContractId || undefined,
           }),
         });
         /* 拿不到新合同主键时**不能**退回原合同 id —— 那会把审批发到旧合同上 */
@@ -3312,7 +3414,9 @@ export default {
         this.openEdit(row);
         return;
       }
-      this.openRenew(row);
+      /* 续签草稿(renewalStatusCode === 'DRAFT'):草稿单据就是续签生成的新合同,
+         载入其值原地修改(renewalContractId,2026-10-19 契约确认) */
+      this.openRenew(row, { draftContractId: row.renewalContractId || '' });
     },
     /**
      * 提交续签审批:审批对象是**续签生成的新合同**(businessKey 与签订相同 hr_contract_approval)。
@@ -3541,12 +3645,14 @@ export default {
       this.createMode = 'change';
       this.createForm = blankCreateForm();
       this.changeSnapshot = blankChangeSnapshot();
+      this.editingChangeId = '';
       this.createVisible = true;
       this.loading = true;
       try {
         await this.fillCreateFormFromContract(row);
-        /* 变更草稿回显:该合同挂着一份 DRAFT 变更单时,把变更后的值回填表单再编辑 */
-        await this.restoreChangeSnapshot(row.id, ['DRAFT']);
+        /* 变更草稿回显:该合同挂着一份 DRAFT 变更单时,把变更后的值回填表单再编辑;
+           返回草稿主键,保存时带上 = 原地修改这份草稿(2026-10-19 后端支持) */
+        this.editingChangeId = (await this.restoreChangeSnapshot(row.id, ['DRAFT'])) || '';
         /* 变更模式的机构/部门/岗位级联下拉:按已回填的值逐级加载选项,避免回显成裸 id */
         await this.loadChangeCascadeOptions();
         this.$nextTick(() => this.$refs.createFormRef?.clearValidate());
@@ -3557,18 +3663,20 @@ export default {
         this.loading = false;
       }
     },
-    async openRenew(row) {
+    async openRenew(row, { draftContractId = '' } = {}) {
       if (!row?.id) {
         this.$message.warning('缺少合同主键,无法续签');
         return;
       }
-      if (!isEffective(row)) {
+      if (!isEffective(row) && !draftContractId) {
         this.$message.warning('仅审批通过并签署成功的合同可以续签');
         return;
       }
       this.createMode = 'renew';
       this.createForm = blankCreateForm();
       this.changeSnapshot = blankChangeSnapshot();
+      /* 续签草稿主键(= 续签生成的新合同主键):保存时带上 = 原地修改该草稿 */
+      this.editingRenewContractId = draftContractId || '';
       this.createVisible = true;
       this.loading = true;
       try {
@@ -3581,6 +3689,12 @@ export default {
             this.createForm.endDate = addMonths(this.createForm.startDate, months);
           }
         }
+        /* 续签草稿回填:草稿单据就是续签生成的新合同,载入它的值覆盖默认值
+           (changeSnapshot 仍保留原合同,差异高亮不受影响) */
+        if (draftContractId) {
+          await this.fillCreateFormFromContract({ id: draftContractId });
+          this.createForm.contractNo = '';
+        }
         this.$nextTick(() => this.$refs.createFormRef?.clearValidate());
       } catch (e) {
         this.$message.error(e.message || '合同详情加载失败');
@@ -3601,6 +3715,8 @@ export default {
         this.$message.warning('仅审批通过并签署成功的合同可以解除');
         return;
       }
+      /* 编辑中的解除草稿主键:保存时带上 = 原地修改该草稿(2026-10-19 后端支持) */
+      this.terminateDraftId = draftMode ? row.terminationId || '' : '';
       this.terminateForm = {
         ...blankTerminateForm(),
         id: row.id,
@@ -3612,16 +3728,18 @@ export default {
       this.terminateVisible = true;
       this.$nextTick(() => this.$refs.terminateFormRef?.clearValidate());
       if (!draftMode) return;
-      /* 草稿态:查详情台账回填解除草稿;查不到如实提示,不拿空白表单冒充 */
+      /* 草稿态:优先用列表行返回的解除单主键(terminationId,2026-10-19 契约确认),
+         在详情台账里精确定位草稿;没有主键再按 DRAFT 状态找 */
       this.terminationLoading = true;
       try {
-        const draft = await this.resolveTerminationDraft(row.id);
+        const draft = await this.resolveTerminationDraft(row.id, row.terminationId);
         if (!draft) {
           this.$message.warning(
             '该合同存在解除草稿,但详情接口未返回草稿明细(terminations),无法回填,需后端补充',
           );
           return;
         }
+        this.terminateDraftId = draft.id || row.terminationId || '';
         this.terminateForm = {
           ...this.terminateForm,
           terminationType: draft.terminationType || this.terminateForm.terminationType,
@@ -3639,14 +3757,20 @@ export default {
       }
     },
     /**
-     * 查该合同的解除草稿单据:详情接口 terminations 台账里
-     * terminationStatus === 'DRAFT' 的那一条(含解除终止主键 id)。
-     * 查不到返回 null —— 由调用方明确提示,不编造。
+     * 查该合同的解除草稿单据:详情接口 terminations 台账里找草稿。
+     * ★ 优先用 terminationId(列表行 / 2026-10-19 契约确认的字段)精确匹配;
+     *   没传主键时按 terminationStatus === 'DRAFT' 找。查不到返回 null —— 不编造。
      */
-    async resolveTerminationDraft(contractId) {
+    async resolveTerminationDraft(contractId, terminationId = '') {
       if (!contractId) return null;
       const detail = await getContractById(contractId);
       const terminations = Array.isArray(detail?.terminations) ? detail.terminations : [];
+      if (terminationId) {
+        const hit = terminations.find(
+          (item) => String(item?.id ?? '') === String(terminationId),
+        );
+        if (hit) return hit;
+      }
       return (
         terminations.find(
           (item) => String(item?.terminationStatus || '').toUpperCase() === 'DRAFT',
@@ -3671,6 +3795,8 @@ export default {
       this.terminateSubmitting = true;
       try {
         const result = await terminateContract(form.id, compact({
+          /* ★ 2026-10-19 后端支持:携带解除草稿主键 = 原地修改该草稿(不传 = 新建) */
+          id: this.terminateDraftId || undefined,
           terminationType: form.terminationType,
           reason: form.reason,
           reasonCode: form.reasonCode,
@@ -3679,7 +3805,10 @@ export default {
           signingMethod,
           templateId: signingMethod === 'ONLINE' ? form.templateId : undefined,
           templateVersionId: signingMethod === 'ONLINE' ? form.templateVersionId : undefined,
-          offlineFileId: signingMethod === 'OFFLINE' ? form.offlineFileIds?.[0] : undefined,
+          /* ★ 契约字段是 attachmentIds(英文逗号分隔字符串,可多个);
+             之前误传 offlineFileId 单值键,后端收不到 → 解除详情一直「暂无附件」(2026-10-19 用户反馈) */
+          attachmentIds:
+            signingMethod === 'OFFLINE' ? joinAttachmentIds(form.offlineFileIds) : undefined,
         }));
         /* 接口返回解除/终止草稿主键(long),与变更一样:审批对象就是这张草稿单据 */
         if (!result) {

+ 7 - 1
src/views/hrManagement/employeeArchive/components/EmployeeArchiveDetail.vue

@@ -500,7 +500,13 @@ export default {
         );
         return;
       }
-      this.$router.push(path).catch(() => {});
+      /* 跳转成功后关掉本详情抽屉:页面已切到员工薪资档案,抽屉留着只会挡住新页面 */
+      this.$router
+        .push(path)
+        .then(() => {
+          this.drawerVisible = false;
+        })
+        .catch(() => {});
     }
   }
 };

+ 8 - 1
src/views/hrManagement/positionTransfer/index.vue

@@ -292,7 +292,7 @@
               </el-form-item>
               <el-form-item label="生效日期" prop="effectiveDate">
                 <el-date-picker v-model="form.effectiveDate" type="date" value-format="yyyy-MM-dd" placeholder="请选择生效日期"
-                  style="width: 100%" />
+                  :picker-options="effectiveDatePickerOptions" style="width: 100%" />
               </el-form-item>
               <!-- 签署方式原在下方区块,与异动类型互换位置后占原异动类型所在单元格 -->
               <el-form-item label="签署方式" prop="signType">
@@ -593,6 +593,13 @@ export default {
     styleResponsive() {
       return this.$store.state.theme?.styleResponsive !== false;
     },
+    /** 生效日期不得早于今天(调岗不允许回溯到过去生效) */
+    effectiveDatePickerOptions() {
+      const todayStart = new Date().setHours(0, 0, 0, 0);
+      return {
+        disabledDate: (date) => date.getTime() < todayStart,
+      };
+    },
     columns() {
       return [
         { type: "index", label: "序号", width: 58, align: "center" },

+ 155 - 9
src/views/salary/employeeArchive/components/SalaryProfileDialog.vue

@@ -103,7 +103,7 @@
           </el-col>
         </el-row>
         <p class="field-hint">
-          该员工按所选模板版本核算;下拉列出该法人<b>已启用模板的当前版本</b>(后端一次返回),默认带出当前档案引用的版本。
+          该员工按所选模板版本核算;下拉列出该法人<b>已启用模板的当前版本</b>,默认带出当前档案引用的版本。
         </p>
       </div>
 
@@ -163,7 +163,8 @@
             <el-col :span="12">
               <el-form-item label="五险一金方案" prop="social.socialPlanId">
                 <el-select v-model="form.social.socialPlanId" filterable
-                  :loading="socialPlanLoading" placeholder="请选择" style="width: 100%">
+                  :loading="socialPlanLoading" placeholder="请选择" style="width: 100%"
+                  @change="onSocialPlanChange">
                   <el-option v-for="item in socialPlanOptions" :key="item.id"
                     :label="planOptionLabel(item)" :value="item.id" :disabled="item.__usable === false" />
                 </el-select>
@@ -172,8 +173,12 @@
             </el-col>
             <el-col :span="12">
               <el-form-item label="参保城市">
-                <el-input v-model.trim="form.social.cityCode" placeholder="参保城市编码,如 430100"
-                  maxlength="20" />
+                <!-- 选择省 / 市后按行政区划码写入 cityCode(接口要 6 位编码,如 430100) -->
+                <el-cascader v-model="cityPath" :options="cityAreaOptions" :props="{ expandTrigger: 'hover' }"
+                  filterable clearable placeholder="请选择省 / 市" style="width: 100%" />
+                <p v-if="cityCodeOutsideLibrary" class="field-hint">
+                  当前编码 {{ form.social.cityCode }} 不在标准区划库中,如需调整请重新选择
+                </p>
               </el-form-item>
             </el-col>
             <el-col :span="12">
@@ -186,9 +191,12 @@
               </el-form-item>
             </el-col>
             <el-col :span="12">
-              <el-form-item label="工伤浮动系数">
+              <el-form-item label="工伤浮动系数" prop="social.workInjuryFloatingCoefficient"
+                class="floating-coef-item" :rules="floatingCoefficientRules">
                 <el-input-number v-model="form.social.workInjuryFloatingCoefficient" :min="0"
-                  :controls="false" placeholder="工伤险种必填,须大于 0" class="full-width" />
+                  :controls="false" :disabled="!schemeHasWorkInjury"
+                  :placeholder="schemeHasWorkInjury ? '须大于 0' : '所选方案无工伤险种,无需填写'"
+                  class="full-width" />
               </el-form-item>
             </el-col>
             <el-col :span="12">
@@ -279,7 +287,7 @@
               {{ item.statusLabel }}
             </el-tag>
           </template>
-          <span v-else>暂无(未维护时个税按法定口径与公司默认口径核算)</span>
+          <span v-else>暂无(未维护时个税按法定规则与公司默认设置核算)</span>
         </p>
       </div>
 
@@ -405,6 +413,47 @@
     checkSchemeUsable,
     INDUSTRY_RISK_LEVEL_OPTIONS
   } from '@/api/salary/socialScheme';
+  import { provinceAndCityData } from '@/utils/china-area-data';
+
+  /*
+   * 参保城市选择:接口 `cityCode` 要 6 位行政区划码(如 430100)。
+   * china-area-data 的 provinceAndCityData 里地级市是 4 位(4301 长沙市)→ 补 '00';
+   * 直辖市的库里直接挂区 → 取省级码补 '0000'(北京市 = 110000)。
+   * 省直辖县级市(如济源 419001)本身就是 6 位,原样使用。
+   */
+  const MUNICIPALITY_PROVINCE_CODES = ['11', '12', '31', '50'];
+  function buildCityAreaCascader() {
+    const options = [];
+    const codeByNodeValue = new Map();
+    const pathByCode = new Map();
+    const codeByProvinceCityName = new Map();
+    (provinceAndCityData || []).forEach((province) => {
+      const provinceValue = String(province.value);
+      if (MUNICIPALITY_PROVINCE_CODES.includes(provinceValue)) {
+        const cityCode = `${provinceValue}0000`;
+        options.push({
+          value: provinceValue,
+          label: province.label,
+          children: [{ value: cityCode, label: province.label }]
+        });
+        codeByNodeValue.set(cityCode, cityCode);
+        pathByCode.set(cityCode, [provinceValue, cityCode]);
+        codeByProvinceCityName.set(`${province.label}/${province.label}`, cityCode);
+        return;
+      }
+      const children = (province.children || []).map((city) => {
+        const nodeValue = String(city.value);
+        const cityCode = nodeValue.length === 4 ? `${nodeValue}00` : nodeValue;
+        codeByNodeValue.set(nodeValue, cityCode);
+        pathByCode.set(cityCode, [provinceValue, nodeValue]);
+        codeByProvinceCityName.set(`${province.label}/${city.label}`, cityCode);
+        return { value: nodeValue, label: city.label };
+      });
+      options.push({ value: provinceValue, label: province.label, children });
+    });
+    return { options, codeByNodeValue, pathByCode, codeByProvinceCityName };
+  }
+  const CITY_AREA = buildCityAreaCascader();
 
   /** 员工参保关系(可选维护) */
   function emptySocialForm() {
@@ -466,6 +515,8 @@
         saving: false,
         advancedOpen: false,
         form: emptyForm(''),
+        /* 参保城市级联选项(省 → 市,值为行政区划节点码,见 CITY_AREA) */
+        cityAreaOptions: CITY_AREA.options,
         /* 用工性质:字典 position_nature(value = 字典 id),弹框打开后加载;不预置任何本地枚举 */
         employmentTypeOptions: [],
         changeSourceOptions: CHANGE_SOURCE_OPTIONS,
@@ -504,11 +555,66 @@
       };
     },
     computed: {
+      /** 参保城市级联值(省 / 市 节点路径)↔ 接口 cityCode(6 位行政区划码)互转 */
+      cityPath: {
+        get() {
+          const code = String(this.form.social.cityCode || '').trim();
+          return code ? CITY_AREA.pathByCode.get(code) || [] : [];
+        },
+        set(path) {
+          const last = Array.isArray(path) && path.length ? path[path.length - 1] : '';
+          this.form.social.cityCode = last ? CITY_AREA.codeByNodeValue.get(last) || '' : '';
+        }
+      },
+      /** 后端回填的 cityCode 不在标准区划库里(无法反查出省市):如实提示,原值仍保留提交 */
+      cityCodeOutsideLibrary() {
+        const code = String(this.form.social.cityCode || '').trim();
+        return Boolean(code) && !CITY_AREA.pathByCode.has(code);
+      },
+      /**
+       * 当前所选五险一金方案是否含工伤险种(决定「工伤浮动系数」是否必填)。
+       * 数据源:getSocialSchemeOptions 返回的方案行自带 items(险种配置),列表页试算也在用它。
+       */
+      schemeHasWorkInjury() {
+        const planId = this.form?.social?.socialPlanId;
+        if (planId == null || planId === '') return false;
+        const plan = this.socialPlanOptions.find((i) => String(i.id) === String(planId));
+        if (!plan) return false;
+        return (plan.items || []).some(
+          (item) => String(item.itemType || '').toUpperCase() === 'WORK_INJURY'
+        );
+      },
       /** 方案下拉异常时的提示(空串 = 无需提示) */
       socialPlanTip() {
         if (!this.maintainSocial) return '';
         return this.socialPlanTipOf();
       },
+      /**
+       * 工伤浮动系数校验规则(动态):
+       * 契约写明「工伤险种必填,须大于 0」——必填条件取所选方案的险种集合是否含
+       * WORK_INJURY(不是看有没有选风险等级)。required 同时控制星号显示:
+       * 必填时出星号,非必填(方案无工伤险种 / 未勾选维护)不显示。
+       */
+      floatingCoefficientRules() {
+        return [
+          {
+            required: this.schemeHasWorkInjury,
+            validator: (rule, value, callback) => {
+              if (!this.maintainSocial || !this.schemeHasWorkInjury) {
+                callback();
+                return;
+              }
+              const num = Number(value);
+              if (value === '' || value == null || Number.isNaN(num) || num <= 0) {
+                callback(new Error('所选五险一金方案含工伤险种,工伤浮动系数必填且须大于 0'));
+                return;
+              }
+              callback();
+            },
+            trigger: ['blur', 'change']
+          }
+        ];
+      },
       /** 勾选「一并维护税务档案」时,底部「关联税务档案」的待创建预览文案 */
       taxPendingText() {
         const tax = this.form.tax || {};
@@ -522,6 +628,16 @@
       }
     },
     watch: {
+      /*
+       * 用户改选方案后新方案不含工伤险种 → 清掉之前填的浮动系数,避免把无效值提交给后端。
+       * 只在「已有方案 → 改选另一个方案」时清理;首次回填(oldVal 为空)不动,避免误清历史值。
+       */
+      'form.social.socialPlanId'(val, oldVal) {
+        if (!oldVal || String(val) === String(oldVal)) return;
+        if (!this.schemeHasWorkInjury) {
+          this.form.social.workInjuryFloatingCoefficient = undefined;
+        }
+      },
       visible(val) {
         if (val) this.initForm();
       }
@@ -541,6 +657,20 @@
         const name = item.schemeName || item.name || item.schemeCode || `方案 ${item.id}`;
         return item.__usable === false ? `${name}(${item.__reason || '不可用'})` : name;
       },
+      /**
+       * 选方案时默认带出参保城市:方案返回 province / city 名称(无编码),
+       * 用「省名/市名」在区划库里反查 cityCode。仅在用户尚未填城市时带出,已有值不覆盖。
+       */
+      onSocialPlanChange(planId) {
+        const plan = this.socialPlanOptions.find((i) => String(i.id) === String(planId));
+        if (!plan) return;
+        if (this.form.social.cityCode) return;
+        const province = String(plan.province || '').trim();
+        const city = String(plan.city || '').trim();
+        if (!province || !city) return;
+        const code = CITY_AREA.codeByProvinceCityName.get(`${province}/${city}`);
+        if (code) this.form.social.cityCode = code;
+      },
       /**
        * 勾选 / 取消「本次一并维护参保关系」。
        *
@@ -574,6 +704,8 @@
             return;
           }
           this.form.social.socialPlanId = firstUsable.id;
+          /* 代码赋值不触发 select 的 change,这里补一次城市带出 */
+          this.onSocialPlanChange(firstUsable.id);
         }
         /* 默认沿用档案里的基数与生效日期,减少重复录入 */
         if (!this.form.social.effectiveDate) {
@@ -920,8 +1052,13 @@
               /* 基数复用档案里的社保 / 公积金基数,避免两处不一致 */
               socialBase: this.form.socialBase,
               fundBase: this.form.fundBase,
-              workInjuryRiskLevel: this.form.social.workInjuryRiskLevel,
-              workInjuryFloatingCoefficient: this.form.social.workInjuryFloatingCoefficient,
+              /* 工伤相关字段仅工伤险种使用:方案不含 WORK_INJURY 时不提交(compact 会去掉 undefined) */
+              workInjuryRiskLevel: this.schemeHasWorkInjury
+                ? this.form.social.workInjuryRiskLevel
+                : undefined,
+              workInjuryFloatingCoefficient: this.schemeHasWorkInjury
+                ? this.form.social.workInjuryFloatingCoefficient
+                : undefined,
               effectiveDate: this.form.social.effectiveDate || this.form.effectiveDate,
               expireDate: this.form.social.expireDate,
               sourceType: 'EMPLOYEE'
@@ -1031,4 +1168,13 @@
   .relation-tag {
     margin-right: 6px;
   }
+
+  /* 工伤浮动系数:校验错误提示改为占位展示(el-form-item__error 默认 absolute,
+     会被下一行「参保失效日期」盖住);必填星号由 rules.required 动态控制 */
+  .floating-coef-item {
+    ::v-deep .el-form-item__error {
+      position: static;
+      padding-top: 2px;
+    }
+  }
 </style>

+ 7 - 51
src/views/salary/employeeArchive/index.vue

@@ -192,47 +192,15 @@
         </el-timeline>
         <el-empty v-else-if="!drawerLoading" description="该员工暂无薪资档案,请点击上方「新增调薪版本」录入" :image-size="70" />
 
+        <!-- 抽屉底部只保留 HR 用户需要的操作指引;税务法规依据、字段策略(fieldPolicy)、
+             后端校验规则等内部口径不在页面展示 -->
         <div class="drawer-placeholder">
-          <div class="placeholder-title">税务档案 · 员工个人选择</div>
-          <p class="placeholder-sub">
-            这两项在税法上是「纳税人的选择权」,按人、按年生效,公司无权代选,因此
-            <b>不放在公司级的个税设置里</b>(那里只配默认口径):
-          </p>
+          <div class="placeholder-title">维护说明</div>
           <ul class="placeholder-list">
-            <li><b>全年一次性奖金计税方式</b>:单独计税 / 并入当年综合所得(财税〔2018〕164 号,执行至 2027-12-31)。</li>
-            <li><b>外籍个人津补贴免税</b>:免税 / 不免税;选择免税后不得同时享受专项附加扣除,年度内不得变更。</li>
+            <li>全年一次性奖金计税方式、外籍个人津补贴免税按人按年选择:在右上角「新增调薪版本」弹框勾选<b>「员工年度税务档案 · 本次一并维护」</b>后录入。</li>
+            <li>员工可单独指定五险一金方案(方案、参保城市、工伤风险等级与浮动系数、生效区间):在弹框勾选<b>「员工参保关系 · 本次一并维护」</b>后录入;核算时优先于薪资模板的默认方案。</li>
+            <li>金额显示:*** = 无查看权限;— = 未录入;0 = 实际为 0。</li>
           </ul>
-          <p>
-            在右上角「新增调薪版本」弹框里勾选<b>「员工年度税务档案 · 本次一并维护」</b>即可录入,
-            按「税务年度 + 税务身份 + 专项附加扣除」保存。
-          </p>
-          <!--
-            P2-12:原文案写「税务身份与扣除金额属敏感信息,不回显」—— 该口径已过期。
-            2026-10-09 后端确认:受控字段统一按 **fieldPolicy** 判定,受控结果 / 档案视图接口固定返回
-            VISIBLE,且不再使用额外二次认证 token。这里按新口径描述三态,避免用户以为「看不到=没录」。
-          -->
-          <p>
-            金额类字段按<b>字段策略(fieldPolicy)</b>展示:无查看权限显示 <b>***</b>,
-            可见但确实未录入显示 <b>—</b>,真为 0 显示 <b>0</b> —— 三者含义不同,请勿混看。
-            受控字段的查看受「菜单权限 + 组织范围 + 审计留痕」共同约束。
-          </p>
-        </div>
-        <div class="drawer-placeholder">
-          <div class="placeholder-title">参保方案覆盖(员工级)</div>
-          <p class="placeholder-sub">
-            口径:<b>薪资模板上的方案 = 默认方案</b>,员工级参保关系可覆盖。
-          </p>
-          <ul class="placeholder-list">
-            <li>同一法人下按参保地可有多个五险一金方案,模板只能挂一个 —— 多参保地企业靠员工级指定。</li>
-            <li>在「新增调薪版本」里勾选<b>「员工参保关系 · 本次一并维护」</b>,
-              选择该法人的五险一金方案(须已启用)、参保城市、工伤风险等级与浮动系数、生效区间。
-            </li>
-            <li>同一员工启用关系的有效期<b>不得重叠</b>;方案必须属于同一法人且已启用,否则后端会拒绝。</li>
-          </ul>
-          <p>
-            已存在的参保关系展示在「新增调薪版本」弹框的「员工参保关系」区;
-            核算时按「员工参保关系 → 模板默认方案」的顺序取值。
-          </p>
         </div>
       </div>
     </el-drawer>
@@ -296,7 +264,7 @@ export default {
         { columnKey: 'effectiveDate', label: '当前生效日期', width: 130, align: 'center', slot: 'effectiveDate' },
         { columnKey: 'versionNo', label: '版本', width: 80, align: 'center', slot: 'versionNo' },
         { columnKey: 'createUserName', label: '创建人', width: 80, align: 'center', slot: 'createUserName' },
-        { columnKey: 'createTime', label: '创建时间', width: 80, align: 'center', slot: 'createTime' },
+        { columnKey: 'createTime', label: '创建时间', width: 100, align: 'center', slot: 'createTime' },
         { columnKey: 'action', label: '操作', width: 180, align: 'left', fixed: 'right', slot: 'action' }
       ],
       pageSize: this.$store.state.tablePageSize || 20
@@ -685,18 +653,6 @@ export default {
     margin-bottom: 6px;
   }
 
-  p {
-    margin: 0;
-    font-size: 12.5px;
-    line-height: 1.7;
-    color: #909399;
-  }
-
-  .placeholder-sub {
-    margin-bottom: 6px;
-    color: #606266;
-  }
-
   .placeholder-list {
     margin: 0 0 8px;
     padding-left: 18px;

+ 15 - 66
src/views/salary/socialScheme/index.vue

@@ -73,24 +73,6 @@
           </div>
         </template>
 
-        <!-- 个人合计(按试算基数);有未配置险种时明确标出来,避免把缺数据当成 0 -->
-        <template v-slot:personalTotal="{ row }">
-          <span class="amount-personal">{{ trialOf(row).personal.toFixed(2) }}</span>
-          <el-tooltip v-if="trialOf(row).unconfiguredItemCount" effect="dark" placement="top"
-            :content="`有 ${trialOf(row).unconfiguredItemCount} 个险种的费率 / 定额未配置或历史比例不完整,试算结果只覆盖已配置的部分`">
-            <i class="el-icon-warning-outline trial-warn" />
-          </el-tooltip>
-        </template>
-
-        <!-- 企业合计 -->
-        <template v-slot:companyTotal="{ row }">
-          <span class="amount-company">{{ trialOf(row).company.toFixed(2) }}</span>
-          <el-tooltip v-if="trialOf(row).unconfiguredItemCount" effect="dark" placement="top"
-            :content="`有 ${trialOf(row).unconfiguredItemCount} 个险种的费率 / 定额未配置或历史比例不完整,试算结果只覆盖已配置的部分`">
-            <i class="el-icon-warning-outline trial-warn" />
-          </el-tooltip>
-        </template>
-
         <!-- 状态 -->
         <template v-slot:status="{ row }">
           <el-tag size="mini" :type="row.statusType || 'info'" effect="light">
@@ -103,7 +85,7 @@
           </el-tag>
         </template>
 
-        <!-- 点编号开详情,替代原操作列「查看」入口 -->
+        <!-- 点编号、名称看详情,替代原操作列「查看」入口 -->
         <template v-slot:schemeCode="{ row }">
           <el-link v-if="row.schemeCode" type="primary" :underline="false" @click="openEdit(row)">
             {{ row.schemeCode }}
@@ -111,6 +93,12 @@
           <span v-else>—</span>
         </template>
 
+        <template v-slot:schemeName="{ row }">
+          <el-link v-if="row.schemeName" type="primary" :underline="false" @click="openEdit(row)">
+            {{ row.schemeName }}
+          </el-link>
+          <span v-else>—</span>
+        </template>
         <!-- 操作 -->
         <template v-slot:action="{ row }">
           <!-- 仅未启用(需要编辑)时显示「编辑」;已启用的查看改为点「方案编号」 -->
@@ -153,17 +141,12 @@ import {
   getSocialSchemePageList,
   enableSocialScheme,
   disableSocialScheme,
-  calcSchemeMonthlyAmount,
   checkSchemeUsable,
   schemeStatusMeta,
-  isSchemeEnabled,
-  SOCIAL_TRIAL_BASE
+  isSchemeEnabled
 } from '@/api/salary/socialScheme';
 import SchemeEditDialog from './SchemeEditDialog.vue';
 
-/** 列表「个人合计 / 企业合计」的试算基数(元):统一取 API 层常量,避免多处硬编码 */
-const TRIAL_BASE = SOCIAL_TRIAL_BASE;
-
 export default {
   name: 'SocialScheme',
   components: { SchemeEditDialog },
@@ -180,7 +163,6 @@ export default {
       currentRow: null,
       /* true = 弹窗以「下一年度草稿」方式打开 */
       copyMode: false,
-      trialBase: TRIAL_BASE,
       /*
        * ★ C8 已答复(2026-10-10):后端 status 入参兼容 DRAFT/ENABLED/DISABLED、中文与 1/0,
        * 由后端归一 —— 这里下发归一后的英文枚举,筛选在**服务端分页前**完成。
@@ -203,9 +185,9 @@ export default {
         {
           prop: 'schemeName',
           label: '方案名称',
-          showOverflowTooltip: true,
           align: 'left',
-          minWidth: 220
+          minWidth: 220,
+          slot: 'schemeName'
         },
         {
           columnKey: 'area',
@@ -224,29 +206,17 @@ export default {
           columnKey: 'baseRange',
           label: '缴费基数区间(元)',
           align: 'left',
-          minWidth: 200,
+          minWidth: 240,
           slot: 'baseRange'
         },
-        /* Y18:恢复试算列 —— 核对比例填对没填对最直接的手段,列头注明试算基数 */
-        {
-          columnKey: 'personalTotal',
-          label: `个人合计(按 ${TRIAL_BASE.toLocaleString('zh-CN')} 基数试算)`,
-          align: 'right',
-          width: 210,
-          slot: 'personalTotal'
-        },
-        {
-          columnKey: 'companyTotal',
-          label: '企业合计(用工成本)',
-          align: 'right',
-          width: 180,
-          slot: 'companyTotal'
-        },
+        /* 试算合计不放在列表:固定 8000 基数与任何员工真实基数无关(实际按员工参保关系基数、
+           且受上下限截断),放这里只会被当成「这个方案每人要交这么多」。
+           费率核对在编辑弹框的「试算器」里做(基数可改、逐险种明细)。 */
         {
           columnKey: 'status',
           label: '状态',
           align: 'center',
-          width: 170,
+          width: 120,
           slot: 'status'
         },
         {
@@ -350,10 +320,6 @@ export default {
         String(row.fundBaseMax ?? '') !== String(row.socialBaseMax ?? '')
       );
     },
-    /* 按试算基数算出的个人 / 企业月缴额 */
-    trialOf(row) {
-      return calcSchemeMonthlyAmount(row, this.trialBase);
-    },
     canEnable(row) {
       return !isSchemeEnabled(row.status);
     },
@@ -519,23 +485,6 @@ export default {
     color: #909399;
   }
 
-  .amount-personal {
-    color: #303133;
-    font-variant-numeric: tabular-nums;
-  }
-
-  /* P2-22 ①:试算结果不完整(有险种未配置费率 / 定额)时的警示图标 */
-  .trial-warn {
-    margin-left: 4px;
-    color: #e6a23c;
-    cursor: help;
-  }
-
-  .amount-company {
-    color: #606266;
-    font-variant-numeric: tabular-nums;
-  }
-
   /* 状态列里的「未启用 / 已过期」提示 */
   .usable-tag {
     display: block;