|
@@ -20,8 +20,8 @@
|
|
|
</div>
|
|
</div>
|
|
|
<div class="cm-compare-item is-span-2">
|
|
<div class="cm-compare-item is-span-2">
|
|
|
<span class="cm-compare-label">合同附件:</span><span class="cm-compare-value">
|
|
<span class="cm-compare-label">合同附件:</span><span class="cm-compare-value">
|
|
|
- <fileMain v-if="detailRow.attachmentFileIds.length" :value="detailRow.attachmentFileIds" type="view"
|
|
|
|
|
- size="small" />
|
|
|
|
|
|
|
+ <fileMain v-if="detailRow.beforeAttachmentFileIds.length" :value="detailRow.beforeAttachmentFileIds"
|
|
|
|
|
+ type="view" size="small" />
|
|
|
<span v-else class="cm-detail-muted">暂无附件</span>
|
|
<span v-else class="cm-detail-muted">暂无附件</span>
|
|
|
</span>
|
|
</span>
|
|
|
</div>
|
|
</div>
|
|
@@ -37,8 +37,8 @@
|
|
|
</div>
|
|
</div>
|
|
|
<div class="cm-compare-item is-span-2">
|
|
<div class="cm-compare-item is-span-2">
|
|
|
<span class="cm-compare-label">合同附件:</span><span class="cm-compare-value">
|
|
<span class="cm-compare-label">合同附件:</span><span class="cm-compare-value">
|
|
|
- <fileMain v-if="detailRow.attachmentFileIds.length" :value="detailRow.attachmentFileIds" type="view"
|
|
|
|
|
- size="small" />
|
|
|
|
|
|
|
+ <fileMain v-if="detailRow.afterAttachmentFileIds.length" :value="detailRow.afterAttachmentFileIds"
|
|
|
|
|
+ type="view" size="small" />
|
|
|
<span v-else class="cm-detail-muted">暂无附件</span>
|
|
<span v-else class="cm-detail-muted">暂无附件</span>
|
|
|
</span>
|
|
</span>
|
|
|
</div>
|
|
</div>
|
|
@@ -80,16 +80,29 @@
|
|
|
</template>
|
|
</template>
|
|
|
|
|
|
|
|
<script>
|
|
<script>
|
|
|
-import { getContractById } from '@/api/hr';
|
|
|
|
|
-import { getProcessInstance } from '@/api/bpm/processInstance';
|
|
|
|
|
|
|
+import { getContractApprovalDetail } from '@/api/hr';
|
|
|
import { parseAttachmentIds } from '@/utils/attachment';
|
|
import { parseAttachmentIds } from '@/utils/attachment';
|
|
|
|
|
|
|
|
|
|
+/** 本组件只服务「劳动合同变更审批」,businessKey 固定 */
|
|
|
|
|
+const CHANGE_BUSINESS_KEY = 'hr_contract_change_approval';
|
|
|
|
|
+
|
|
|
/** 签署方式:ONLINE-审批待办附件盖章签字;OFFLINE-线下签署 */
|
|
/** 签署方式:ONLINE-审批待办附件盖章签字;OFFLINE-线下签署 */
|
|
|
const SIGNING_METHOD_LABELS = {
|
|
const SIGNING_METHOD_LABELS = {
|
|
|
ONLINE: '线上',
|
|
ONLINE: '线上',
|
|
|
OFFLINE: '线下'
|
|
OFFLINE: '线下'
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
|
|
+/** 变更单业务状态(DRAFT/PENDING_APPROVAL/SIGNING/EFFECTIVE/REJECTED/CANCELLED):
|
|
|
|
|
+ 后端 changeStatusName 缺失时按已知编码翻中文,未知编码原样回显 */
|
|
|
|
|
+const CHANGE_STATUS_LABELS = {
|
|
|
|
|
+ DRAFT: '草稿',
|
|
|
|
|
+ PENDING_APPROVAL: '审批中',
|
|
|
|
|
+ SIGNING: '签署中',
|
|
|
|
|
+ EFFECTIVE: '已生效',
|
|
|
|
|
+ REJECTED: '已驳回',
|
|
|
|
|
+ CANCELLED: '已取消'
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
/** 对比字段:快照字段名与合同顶层字段同名,缺失时逐层回退 */
|
|
/** 对比字段:快照字段名与合同顶层字段同名,缺失时逐层回退 */
|
|
|
const FIELD_DEFS = [
|
|
const FIELD_DEFS = [
|
|
|
{ key: 'employeeName', label: '签订员工' },
|
|
{ key: 'employeeName', label: '签订员工' },
|
|
@@ -118,9 +131,19 @@ function parseSnapshot(raw) {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+/** 快照日期可能是秒(10 位)/毫秒(13 位)时间戳或日期串 → 统一转本地时区 YYYY-MM-DD */
|
|
|
function formatDate(value) {
|
|
function formatDate(value) {
|
|
|
if (!value) return '';
|
|
if (!value) return '';
|
|
|
- return String(value).slice(0, 10);
|
|
|
|
|
|
|
+ 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())}`;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
function formatSalary(value) {
|
|
function formatSalary(value) {
|
|
@@ -140,12 +163,13 @@ function formatProbation(value) {
|
|
|
export default {
|
|
export default {
|
|
|
name: 'HrChangeContractDetail',
|
|
name: 'HrChangeContractDetail',
|
|
|
props: {
|
|
props: {
|
|
|
|
|
+ /** 流程业务主键:变更审批即变更单主键(不是合同主键) */
|
|
|
businessId: {
|
|
businessId: {
|
|
|
default: ''
|
|
default: ''
|
|
|
},
|
|
},
|
|
|
- /** 流程实例 id:用于从流程变量里补取合同 id */
|
|
|
|
|
- id: {
|
|
|
|
|
- default: ''
|
|
|
|
|
|
|
+ /** 业务标识,默认取本组件对应的流程 */
|
|
|
|
|
+ businessKey: {
|
|
|
|
|
+ default: CHANGE_BUSINESS_KEY
|
|
|
}
|
|
}
|
|
|
},
|
|
},
|
|
|
data() {
|
|
data() {
|
|
@@ -170,17 +194,22 @@ export default {
|
|
|
}
|
|
}
|
|
|
},
|
|
},
|
|
|
methods: {
|
|
methods: {
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 审批详情统一走 GET /hr/contracts/approval-detail:
|
|
|
|
|
+ * 服务端按 businessKey + businessId(变更单主键)解析出原合同,返回合同完整档案(含 changes 台账)。
|
|
|
|
|
+ * 前端不再用变更单 id 去猜合同 id,也不再从流程变量里补取。
|
|
|
|
|
+ */
|
|
|
async getInfo(businessId) {
|
|
async getInfo(businessId) {
|
|
|
if (!businessId) return;
|
|
if (!businessId) return;
|
|
|
this.loading = true;
|
|
this.loading = true;
|
|
|
try {
|
|
try {
|
|
|
- let contract = await this.fetchContract(businessId);
|
|
|
|
|
- // businessId 可能是变更单 id(不是合同 id):拿到的不对时从流程变量补取合同 id
|
|
|
|
|
- if (!contract || String(contract.id) !== String(businessId)) {
|
|
|
|
|
- const contractId = await this.resolveContractIdFromProcess();
|
|
|
|
|
- if (contractId) contract = await this.fetchContract(contractId);
|
|
|
|
|
|
|
+ const contract = await getContractApprovalDetail(
|
|
|
|
|
+ businessId,
|
|
|
|
|
+ this.businessKey
|
|
|
|
|
+ );
|
|
|
|
|
+ if (!contract) {
|
|
|
|
|
+ throw new Error('未获取到合同信息,请后端确认审批详情的返回');
|
|
|
}
|
|
}
|
|
|
- if (!contract) throw new Error('未获取到合同信息');
|
|
|
|
|
this.detailRow = this.adaptChange(contract, businessId);
|
|
this.detailRow = this.adaptChange(contract, businessId);
|
|
|
} catch (e) {
|
|
} catch (e) {
|
|
|
this.detailRow = null;
|
|
this.detailRow = null;
|
|
@@ -189,63 +218,19 @@ export default {
|
|
|
this.loading = false;
|
|
this.loading = false;
|
|
|
}
|
|
}
|
|
|
},
|
|
},
|
|
|
- async fetchContract(id) {
|
|
|
|
|
- try {
|
|
|
|
|
- return (await getContractById(id)) || null;
|
|
|
|
|
- } catch (e) {
|
|
|
|
|
- return null;
|
|
|
|
|
- }
|
|
|
|
|
- },
|
|
|
|
|
- /**
|
|
|
|
|
- * 从流程变量补取合同 id(项目内既有做法,见 api/hr/attendanceApply.js)。
|
|
|
|
|
- * 兼容常见变量命名,并打印变量内容便于确认后端实际下发的字段名。
|
|
|
|
|
- */
|
|
|
|
|
- async resolveContractIdFromProcess() {
|
|
|
|
|
- if (!this.id) return '';
|
|
|
|
|
- try {
|
|
|
|
|
- const instance = await getProcessInstance(this.id);
|
|
|
|
|
- const raw = instance && instance.formVariables;
|
|
|
|
|
- const vars = typeof raw === 'string' ? parseSnapshot(raw) : raw || {};
|
|
|
|
|
- // eslint-disable-next-line no-console
|
|
|
|
|
- console.log('[HrChangeContractDetail] 流程变量 formVariables:', vars);
|
|
|
|
|
- const candidates = [
|
|
|
|
|
- vars.contractId,
|
|
|
|
|
- vars.contract_id,
|
|
|
|
|
- vars.contractPrimaryKey,
|
|
|
|
|
- vars.businessContractId,
|
|
|
|
|
- vars.contract && vars.contract.id
|
|
|
|
|
- ];
|
|
|
|
|
- const hit = candidates.find((item) => item != null && item !== '');
|
|
|
|
|
- return hit != null ? String(hit) : '';
|
|
|
|
|
- } catch (e) {
|
|
|
|
|
- return '';
|
|
|
|
|
- }
|
|
|
|
|
- },
|
|
|
|
|
- /**
|
|
|
|
|
- * 合同详情接口返回该合同全部变更记录(changes),定位本条审批对应的变更:
|
|
|
|
|
- * ① 传入的 businessId(后端流程 businessKey 实际存的是变更单 id)
|
|
|
|
|
- * ② contract.changeId
|
|
|
|
|
- * ③ 审批中/签署中的那条
|
|
|
|
|
- * ④ 最新一条
|
|
|
|
|
- */
|
|
|
|
|
- adaptChange(contract = {}, changeIdHint = '') {
|
|
|
|
|
|
|
+ /** 在合同变更台账里按 businessId(=变更单主键)精确取本条变更,取不到直接报错,不猜 */
|
|
|
|
|
+ adaptChange(contract = {}, changeId = '') {
|
|
|
const list = Array.isArray(contract.changes) ? contract.changes : [];
|
|
const list = Array.isArray(contract.changes) ? contract.changes : [];
|
|
|
- const pendingStatuses = ['PENDING_APPROVAL', 'SIGNING'];
|
|
|
|
|
- const record =
|
|
|
|
|
- list.find((item) => String(item.id) === String(changeIdHint)) ||
|
|
|
|
|
- list.find((item) => String(item.id) === String(contract.changeId)) ||
|
|
|
|
|
- list.find((item) =>
|
|
|
|
|
- pendingStatuses.includes(
|
|
|
|
|
- String(item.changeStatus || '').toUpperCase()
|
|
|
|
|
- )
|
|
|
|
|
- ) ||
|
|
|
|
|
- list[0] ||
|
|
|
|
|
- {};
|
|
|
|
|
- const before = {
|
|
|
|
|
- ...this.contractSnapshot(contract),
|
|
|
|
|
- ...parseSnapshot(record.beforeSnapshot)
|
|
|
|
|
- };
|
|
|
|
|
- const after = { ...before, ...parseSnapshot(record.afterSnapshot) };
|
|
|
|
|
|
|
+ const record = list.find((item) => String(item.id) === String(changeId));
|
|
|
|
|
+ if (!record) {
|
|
|
|
|
+ throw new Error(
|
|
|
|
|
+ `合同变更台账中未找到变更单(id: ${changeId}),请后端确认 approval-detail 是否返回该变更单`
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
+ const beforeSnapshot = parseSnapshot(record.beforeSnapshot);
|
|
|
|
|
+ const afterSnapshot = parseSnapshot(record.afterSnapshot);
|
|
|
|
|
+ const before = { ...this.contractSnapshot(contract), ...beforeSnapshot };
|
|
|
|
|
+ const after = { ...before, ...afterSnapshot };
|
|
|
const normalize = (snap) => ({
|
|
const normalize = (snap) => ({
|
|
|
employeeName: snap.employeeName || '',
|
|
employeeName: snap.employeeName || '',
|
|
|
contractNo: snap.contractNo || '',
|
|
contractNo: snap.contractNo || '',
|
|
@@ -262,7 +247,7 @@ export default {
|
|
|
endDate: formatDate(snap.endDate),
|
|
endDate: formatDate(snap.endDate),
|
|
|
signingMethodText:
|
|
signingMethodText:
|
|
|
SIGNING_METHOD_LABELS[
|
|
SIGNING_METHOD_LABELS[
|
|
|
- String(snap.signingMethod || '').toUpperCase()
|
|
|
|
|
|
|
+ String(snap.signingMethod || '').toUpperCase()
|
|
|
] ||
|
|
] ||
|
|
|
snap.signingMethodName ||
|
|
snap.signingMethodName ||
|
|
|
snap.signingMethod ||
|
|
snap.signingMethod ||
|
|
@@ -282,15 +267,30 @@ export default {
|
|
|
contractNo: contract.contractNo || beforeView.contractNo,
|
|
contractNo: contract.contractNo || beforeView.contractNo,
|
|
|
beforeFields: buildFields(beforeView, null),
|
|
beforeFields: buildFields(beforeView, null),
|
|
|
afterFields: buildFields(afterView, beforeView),
|
|
afterFields: buildFields(afterView, beforeView),
|
|
|
- attachmentFileIds: parseAttachmentIds(contract),
|
|
|
|
|
|
|
+ /** 原合同附件:合同档案 + 变更前快照 */
|
|
|
|
|
+ beforeAttachmentFileIds: parseAttachmentIds({
|
|
|
|
|
+ ...contract,
|
|
|
|
|
+ ...beforeSnapshot
|
|
|
|
|
+ }),
|
|
|
|
|
+ /** 变更后附件:变更单顶层 attachmentIds 与变更后快照合并,快照优先 */
|
|
|
|
|
+ afterAttachmentFileIds: parseAttachmentIds({
|
|
|
|
|
+ ...record,
|
|
|
|
|
+ ...afterSnapshot
|
|
|
|
|
+ }),
|
|
|
changeNo: record.changeNo || '',
|
|
changeNo: record.changeNo || '',
|
|
|
changeTypeText: record.changeTypeName || record.changeType || '-',
|
|
changeTypeText: record.changeTypeName || record.changeType || '-',
|
|
|
- changeStatusText: record.changeStatusName || record.changeStatus || '-',
|
|
|
|
|
|
|
+ changeStatusText:
|
|
|
|
|
+ record.changeStatusName ||
|
|
|
|
|
+ CHANGE_STATUS_LABELS[
|
|
|
|
|
+ String(record.changeStatus || '').toUpperCase()
|
|
|
|
|
+ ] ||
|
|
|
|
|
+ record.changeStatus ||
|
|
|
|
|
+ '-',
|
|
|
effectiveDate: formatDate(record.effectiveDate),
|
|
effectiveDate: formatDate(record.effectiveDate),
|
|
|
changeReason: record.changeReason || ''
|
|
changeReason: record.changeReason || ''
|
|
|
};
|
|
};
|
|
|
},
|
|
},
|
|
|
- /** 合同顶层快照字段:作为变更前快照的兜底来源 */
|
|
|
|
|
|
|
+ /** 合同顶层快照字段:变更前快照里没有的字段,用合同当前值补齐对比基准 */
|
|
|
contractSnapshot(contract) {
|
|
contractSnapshot(contract) {
|
|
|
return {
|
|
return {
|
|
|
employeeName: contract.employeeName,
|
|
employeeName: contract.employeeName,
|