刘光辉
4 小时以前 cf11e63b659afcc9792ab3e395f1b45d844431a8
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
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();
}