import { buildDmsPrintBarcode, type DmsPrintBarcodeBuilder } from './dmsPrintBarcode';
|
import type { DmsPrintAttempt, DmsPrintBatch } from './dmsPrintAttempt';
|
import { v4 as uuidv4 } from 'uuid';
|
import { defHttp } from '#/api/request';
|
import { onlineUtils } from '#/utils/jnpf';
|
import { printManagedDmsDocument } from './dmsHiprint';
|
import { attemptBatches } from './dmsPrintAttempt';
|
|
const MODEL_ID = '37a318b30d7c4daf9670d141b33ed1b7';
|
const endpoint = '/api/dms/biz/wenjian/dayin/chongda/';
|
export interface DmsReprintAttempt extends DmsPrintAttempt {
|
printRecordId: string;
|
reason: string;
|
bizSign: string;
|
createdAt: number | string;
|
submittedAt?: number | string;
|
documentId?: string;
|
}
|
export interface DmsReprintLockStatus {
|
locked: boolean;
|
requestId?: string;
|
printRecordId: string;
|
userId?: string;
|
userName?: string;
|
reason?: string;
|
createdAt?: number | string;
|
phase?: 'READY' | 'UNKNOWN' | 'PARTIAL' | 'REGISTRATION_PENDING';
|
owner?: boolean;
|
canCancel?: boolean;
|
canResume?: boolean;
|
submittedBatches?: number;
|
totalBatches?: number;
|
}
|
export const REPRINT_LOCK_EVENT = 'DMS_REPRINT_LOCK_STATUS';
|
export const REPRINT_COMPLETED_EVENT = 'DMS_REPRINT_COMPLETED';
|
export interface DmsReprintLockDialog {
|
buildCustomBarcode?: DmsPrintBarcodeBuilder;
|
watermarkBusinessTypes?: string[];
|
status: DmsReprintLockStatus;
|
onConfirm?: () => void;
|
}
|
interface NativeSubmitContext {
|
formData: Record<string, any>;
|
params?: Record<string, any>;
|
setFormData: (field: string, value: any) => void;
|
formContext: {
|
closeReprintForm?: () => Promise<void>;
|
refreshReprint?: () => void;
|
isEdit: boolean;
|
submit: (resolve: (value: any) => void, reject: (error: unknown) => void) => void;
|
};
|
}
|
async function post(action: string, data: unknown): Promise<DmsReprintAttempt> {
|
const res = await defHttp.post<{ code: number; data: DmsReprintAttempt; msg?: string }>({ url: endpoint + action, data }, { errorMessageMode: 'none' });
|
if (res.code !== 200) throw new Error(res.msg || '保存重打进度失败');
|
return res.data;
|
}
|
function update(attempt: DmsPrintAttempt, action: string, data: Record<string, unknown> = {}) {
|
return post(action, { requestId: attempt.id, printRecordId: (attempt as DmsReprintAttempt).printRecordId, ...data });
|
}
|
function receiptKey(attempt: DmsReprintAttempt, batch: DmsPrintBatch) {
|
return `dms-reprint-receipt:${encodeURIComponent(attempt.tenantId)}:${encodeURIComponent(attempt.userId)}:${attempt.id}:${batch.id}`;
|
}
|
/** 局部实例锁防止同一表单的连点和迟到回调并发保存,服务端唯一关联兜底。 */
|
const submissions = new Map<string, Promise<any>>();
|
|
export async function getDmsReprintLockStatus(recordId: string): Promise<DmsReprintLockStatus> {
|
const res = await defHttp.get<{ code: number; data: DmsReprintLockStatus; msg?: string }>({
|
url: `${endpoint}${encodeURIComponent(recordId)}/lock-status`,
|
});
|
if (res.code !== 200) throw new Error(res.msg || '无法读取重打状态');
|
return res.data;
|
}
|
|
export async function openDmsReprint(recordId: string, onConfirm?: () => void, buildCustomBarcode?: DmsPrintBarcodeBuilder, watermarkBusinessTypes?: string[]) {
|
if (!recordId) throw new Error('打印记录ID不能为空');
|
const status = await getDmsReprintLockStatus(recordId);
|
if (status.locked) {
|
onlineUtils.getEmitter().emit(REPRINT_LOCK_EVENT, { status, onConfirm, buildCustomBarcode, ...(watermarkBusinessTypes ? { watermarkBusinessTypes } : {}) });
|
return;
|
}
|
openNativeReprint(recordId, undefined, onConfirm, buildCustomBarcode, watermarkBusinessTypes);
|
}
|
|
async function getActive(recordId: string) {
|
const res = await defHttp.get<{ code: number; data?: DmsReprintAttempt; msg?: string }>({
|
url: `${endpoint}${encodeURIComponent(recordId)}/active`,
|
});
|
if (res.code !== 200) throw new Error(res.msg || '无法读取重打状态');
|
return res.data;
|
}
|
|
export async function resumeDmsReprint(recordId: string, requestId: string, onConfirm?: () => void, buildCustomBarcode?: DmsPrintBarcodeBuilder, watermarkBusinessTypes?: string[]) {
|
let existing = await getActive(recordId);
|
if (!existing || existing.id !== requestId) throw new Error('原任务状态已变化,请刷新状态后操作');
|
if (attemptBatches(existing).some((batch) => batch.state === 'SENDING')) throw new Error('发送结果尚未确认,请先刷新状态并补交已有回执');
|
existing = await update(existing, 'zhuangtai');
|
if (existing.documentId && existing.state !== 'OPEN') {
|
await sendDmsReprint(existing, undefined, buildCustomBarcode, watermarkBusinessTypes);
|
onConfirm?.();
|
} else if (existing.documentId) await openReprintPreview(existing, onConfirm, undefined, buildCustomBarcode, watermarkBusinessTypes);
|
else openNativeReprint(recordId, existing, onConfirm, buildCustomBarcode, watermarkBusinessTypes);
|
}
|
|
export async function cancelDmsReprint(recordId: string, requestId: string) {
|
await post('quxiao-fasong', { printRecordId: recordId, requestId });
|
}
|
|
/** 只补交当前浏览器已经收到的成功回执;不能把未知状态推定为成功或失败。 */
|
export async function refreshDmsReprintLock(status: DmsReprintLockStatus) {
|
if (status.owner) {
|
let active = await getActive(status.printRecordId);
|
if (active && active.id === status.requestId) {
|
for (const batch of attemptBatches(active)) {
|
const key = receiptKey(active, batch);
|
let token: string | null = null;
|
try {
|
token = localStorage.getItem(key);
|
} catch {
|
/* 未保存回执时保持未知。 */
|
}
|
if (!token) continue;
|
active = await update(active, 'queren-pici', { batchId: batch.id, token });
|
try {
|
localStorage.removeItem(key);
|
} catch {
|
/* 服务端回执幂等。 */
|
}
|
}
|
}
|
}
|
return getDmsReprintLockStatus(status.printRecordId);
|
}
|
|
function openNativeReprint(recordId: string, existing?: DmsReprintAttempt, onConfirm?: () => void, buildCustomBarcode?: DmsPrintBarcodeBuilder, watermarkBusinessTypes?: string[]) {
|
const requestId = existing?.id || uuidv4();
|
onlineUtils.openFormModal({
|
modelId: MODEL_ID,
|
mode: 'form',
|
submitMode: 'form',
|
params: {
|
dmsReprint: true,
|
buildCustomBarcode,
|
watermarkBusinessTypes,
|
recovering: !!existing,
|
requestId,
|
printRecordId: recordId,
|
bupingheng_mingxi: [{ dayin_jilu_id: recordId, cuoshi: 'cuoshi3', yuanyin: existing?.reason || '' }],
|
chongda_fasong_id: requestId,
|
},
|
fieldMapping: { bupingheng_mingxi: 'bupingheng_mingxi', chongda_fasong_id: 'chongda_fasong_id' },
|
onConfirm,
|
});
|
}
|
|
/** 由签名后的原生customSubmit调用;原生formContext.submit仍负责主子表CRUD。 */
|
export function submitDmsReprint(context: NativeSubmitContext): Promise<any> {
|
const id = String(context.params?.requestId || context.formData.chongda_fasong_id || '');
|
if (!id) return Promise.reject(new Error('请从打印记录重打入口操作'));
|
const current = submissions.get(id);
|
if (current) return current;
|
const promise = execute(context, id).finally(() => submissions.delete(id));
|
submissions.set(id, promise);
|
return promise;
|
}
|
async function execute(context: NativeSubmitContext, id: string) {
|
const rows = context.formData.bupingheng_mingxi;
|
if (!Array.isArray(rows) || rows.length !== 1 || rows[0]?.cuoshi !== 'cuoshi3' || context.formContext.isEdit)
|
throw new Error('重打必须由单条原打印记录发起,已保存记录不得再次执行');
|
let attempt = await post('zhunbei-fasong', {
|
requestId: id,
|
printRecordId: rows[0].dayin_jilu_id,
|
reason: rows[0].yuanyin,
|
bizSign: context.formData.biz_sign,
|
});
|
// 重试保留第一次发送的签名和原因,不允许新输入改变已经发生的重打。
|
function freezeForm() {
|
context.setFormData('chongda_fasong_id', attempt.id);
|
context.setFormData('biz_sign', attempt.bizSign);
|
context.setFormData('dengji_ren_id', attempt.userId);
|
context.setFormData('dengji_shijian', new Date(attempt.createdAt).getTime());
|
context.setFormData('bupingheng_mingxi', [
|
{ ...rows[0], dayin_jilu_id: attempt.printRecordId, cuoshi: 'cuoshi3', yuanyin: attempt.reason, xin_dayin_renwu_id: '' },
|
]);
|
}
|
freezeForm();
|
attempt = await update(attempt, 'zhuangtai');
|
const saveRegistration = async () => {
|
const result: any = await new Promise((resolve, reject) => context.formContext.submit(resolve, reject));
|
if (result?.code !== 200) throw new Error(result?.msg || '不平衡登记保存失败');
|
};
|
// 恢复已成功发送的任务只补原生登记,不再打开预览或派发。
|
if (attempt.state === 'CONFIRMED' || attempt.state === 'CLOSED') {
|
await completeRegistration(attempt, saveRegistration);
|
const completed = await update(attempt, 'zhuangtai');
|
return { code: 200, msg: '重打不平衡登记已完成', data: { id: completed.documentId } };
|
}
|
await context.formContext.closeReprintForm?.();
|
let msg = '重打信息已暂存,请在预览中手动打印,发送成功后自动登记不平衡';
|
try {
|
await openReprintPreview(attempt, context.formContext.refreshReprint, saveRegistration, context.params?.buildCustomBarcode, context.params?.watermarkBusinessTypes);
|
} catch (error: any) {
|
msg = `重打信息已暂存,预览打开失败:${error?.message || '网络异常'}。请从原打印记录点击重打继续,尚未新增不平衡登记`;
|
}
|
return { code: 200, msg, dmsReprintPrepared: true };
|
}
|
|
// 发送成功与原生保存分属两个系统:以任务状态和唯一关联恢复,失败时只补登记。
|
const registrations = new Map<string, Promise<void>>();
|
function completeRegistration(original: DmsReprintAttempt, saveRegistration?: () => Promise<void>): Promise<void> {
|
const current = registrations.get(original.id);
|
if (current) return current;
|
const promise = (async () => {
|
let attempt = await update(original, 'zhuangtai');
|
if (!['CONFIRMED', 'CLOSED'].includes(attempt.state)) throw new Error('尚未收到全部打印发送成功回传,不能登记不平衡');
|
if (attempt.state !== 'CLOSED') {
|
try {
|
if (!attempt.documentId) {
|
if (!saveRegistration) throw new Error('请从原打印记录继续任务,补存不平衡登记');
|
try {
|
await saveRegistration();
|
} catch (error) {
|
attempt = await update(attempt, 'zhuangtai');
|
if (!attempt.documentId) throw error;
|
}
|
attempt = await update(attempt, 'zhuangtai');
|
if (!attempt.documentId) throw new Error('尚未确认原生登记保存结果,请恢复原任务核对');
|
}
|
attempt = await update(attempt, 'jieshu-fasong');
|
} catch (error: any) {
|
throw new Error(`打印已发送成功,但不平衡登记未完成:${error?.message || '网络异常'}。请继续原任务补登记,系统不会重复打印`);
|
}
|
}
|
onlineUtils.getEmitter().emit(REPRINT_COMPLETED_EVENT, { printRecordId: attempt.printRecordId, requestId: attempt.id });
|
})().finally(() => registrations.delete(original.id));
|
registrations.set(original.id, promise);
|
return promise;
|
}
|
|
async function openReprintPreview(attempt: DmsReprintAttempt, onPrintSuccess?: () => void, saveRegistration?: () => Promise<void>, buildCustomBarcode?: DmsPrintBarcodeBuilder, watermarkBusinessTypes?: string[]) {
|
const numbers = JSON.parse(attempt.numbersJson || '[]');
|
const number = numbers[0];
|
if (!number?.serialNumber) throw new Error('原流水缺失,不能打开重打预览');
|
const opened = await onlineUtils.previewDmsFile({
|
wenjianZhongxinId: attempt.fileVersionId,
|
previewFormat: 'pdf',
|
watermarkType: 'print',
|
customBarcode: await buildDmsPrintBarcode(number, buildCustomBarcode),
|
...(watermarkBusinessTypes ? { watermarkBusinessTypes } : {}),
|
showPrintButton: true,
|
printOptions: { copies: 1 },
|
closeOnPrintSuccess: true,
|
onPrintSuccess,
|
reprint: { requestId: attempt.id, printRecordId: attempt.printRecordId, send: () => sendDmsReprint(attempt, saveRegistration, buildCustomBarcode, watermarkBusinessTypes) },
|
});
|
if (!opened) throw new Error('打印预览未能打开,请稍后重试');
|
}
|
|
const sending = new Map<string, Promise<void>>();
|
/** 预览手动发送或恢复已确认任务;先核对原状态,已发送只补登记,不重新取号或派发。 */
|
export function sendDmsReprint(original: DmsReprintAttempt, saveRegistration?: () => Promise<void>, buildCustomBarcode?: DmsPrintBarcodeBuilder, watermarkBusinessTypes?: string[]): Promise<void> {
|
const current = sending.get(original.id);
|
if (current) return current;
|
const promise = send(original, saveRegistration, buildCustomBarcode, watermarkBusinessTypes).finally(() => sending.delete(original.id));
|
sending.set(original.id, promise);
|
return promise;
|
}
|
async function send(original: DmsReprintAttempt, saveRegistration?: () => Promise<void>, buildCustomBarcode?: DmsPrintBarcodeBuilder, watermarkBusinessTypes?: string[]) {
|
let attempt = await update(original, 'zhuangtai');
|
if (String(attempt.state) === 'CANCELLED') throw new Error('本次重打已取消,请关闭预览');
|
if (attempt.state === 'CONFIRMED' || attempt.state === 'CLOSED') {
|
await completeRegistration(attempt, saveRegistration);
|
return;
|
}
|
if (!attempt.documentId && !saveRegistration) throw new Error('请从原打印记录继续重打,恢复原生登记上下文后再打印');
|
const finish = () => completeRegistration(attempt, saveRegistration);
|
async function submitted(original: DmsPrintAttempt, batch: DmsPrintBatch, token: string) {
|
const current = original as DmsReprintAttempt;
|
const key = receiptKey(current, batch);
|
try {
|
localStorage.setItem(key, token);
|
} catch {
|
/* 成功事实优先持久化到服务端。 */
|
}
|
attempt = await update(current, 'queren-pici', { batchId: batch.id, token });
|
try {
|
localStorage.removeItem(key);
|
} catch {
|
/* 重复回传服务端幂等。 */
|
}
|
if (attempt.state !== 'OPEN') await finish();
|
return attempt;
|
}
|
async function recover(original: DmsPrintAttempt) {
|
attempt = original as DmsReprintAttempt;
|
for (const batch of attemptBatches(original)) {
|
let token: string | null = null;
|
try {
|
token = localStorage.getItem(receiptKey(attempt, batch));
|
} catch {
|
/* 无证据保持未知。 */
|
}
|
if (token) await submitted(attempt, batch, token);
|
}
|
return attempt;
|
}
|
await printManagedDmsDocument('原流水重打', { fileVersionId: attempt.fileVersionId, copies: 1, buildCustomBarcode, watermarkBusinessTypes }, attempt, {
|
update,
|
recover,
|
submitted,
|
allowHistoricalSerial: true,
|
});
|
await finish();
|
}
|