|
|
@@ -7,13 +7,17 @@ import com.huimv.employment.dao.entity.FeContract;
|
|
7
|
7
|
import com.huimv.employment.dao.entity.FeEmploymentOrder;
|
|
8
|
8
|
import com.huimv.employment.dao.entity.FeEnterprise;
|
|
9
|
9
|
import com.huimv.employment.dao.entity.FeServicePlan;
|
|
|
10
|
+import com.huimv.employment.dao.entity.FeWorker;
|
|
10
|
11
|
import com.huimv.employment.dao.entity.FeWorkerRegistration;
|
|
11
|
12
|
import com.huimv.employment.dao.mapper.FeContractMapper;
|
|
12
|
13
|
import com.huimv.employment.dao.mapper.FeEmploymentOrderMapper;
|
|
|
14
|
+import com.huimv.employment.dao.mapper.FeEnterpriseMapper;
|
|
13
|
15
|
import com.huimv.employment.dao.mapper.FeServicePlanMapper;
|
|
14
|
16
|
import com.huimv.employment.dao.mapper.FeWorkerRegistrationMapper;
|
|
|
17
|
+import com.huimv.employment.service.contract.dto.ContractItemResponse;
|
|
15
|
18
|
import com.huimv.employment.service.contract.dto.ContractSummaryResponse;
|
|
16
|
19
|
import com.huimv.employment.service.enterprise.EnterpriseService;
|
|
|
20
|
+import com.huimv.employment.service.worker.WorkerService;
|
|
17
|
21
|
import org.slf4j.Logger;
|
|
18
|
22
|
import org.slf4j.LoggerFactory;
|
|
19
|
23
|
import org.springframework.dao.DuplicateKeyException;
|
|
|
@@ -23,6 +27,13 @@ import org.springframework.util.StringUtils;
|
|
23
|
27
|
|
|
24
|
28
|
import java.time.LocalDateTime;
|
|
25
|
29
|
import java.time.format.DateTimeFormatter;
|
|
|
30
|
+import java.util.ArrayList;
|
|
|
31
|
+import java.util.Collections;
|
|
|
32
|
+import java.util.HashMap;
|
|
|
33
|
+import java.util.HashSet;
|
|
|
34
|
+import java.util.List;
|
|
|
35
|
+import java.util.Map;
|
|
|
36
|
+import java.util.Set;
|
|
26
|
37
|
import java.util.concurrent.ThreadLocalRandom;
|
|
27
|
38
|
|
|
28
|
39
|
/**
|
|
|
@@ -47,20 +58,26 @@ public class ContractService {
|
|
47
|
58
|
private final FeEmploymentOrderMapper feEmploymentOrderMapper;
|
|
48
|
59
|
private final FeWorkerRegistrationMapper feWorkerRegistrationMapper;
|
|
49
|
60
|
private final FeServicePlanMapper feServicePlanMapper;
|
|
|
61
|
+ private final FeEnterpriseMapper feEnterpriseMapper;
|
|
50
|
62
|
private final EnterpriseService enterpriseService;
|
|
|
63
|
+ private final WorkerService workerService;
|
|
51
|
64
|
private final ESignClient eSignClient;
|
|
52
|
65
|
|
|
53
|
66
|
public ContractService(FeContractMapper feContractMapper,
|
|
54
|
67
|
FeEmploymentOrderMapper feEmploymentOrderMapper,
|
|
55
|
68
|
FeWorkerRegistrationMapper feWorkerRegistrationMapper,
|
|
56
|
69
|
FeServicePlanMapper feServicePlanMapper,
|
|
|
70
|
+ FeEnterpriseMapper feEnterpriseMapper,
|
|
57
|
71
|
EnterpriseService enterpriseService,
|
|
|
72
|
+ WorkerService workerService,
|
|
58
|
73
|
ESignClient eSignClient) {
|
|
59
|
74
|
this.feContractMapper = feContractMapper;
|
|
60
|
75
|
this.feEmploymentOrderMapper = feEmploymentOrderMapper;
|
|
61
|
76
|
this.feWorkerRegistrationMapper = feWorkerRegistrationMapper;
|
|
62
|
77
|
this.feServicePlanMapper = feServicePlanMapper;
|
|
|
78
|
+ this.feEnterpriseMapper = feEnterpriseMapper;
|
|
63
|
79
|
this.enterpriseService = enterpriseService;
|
|
|
80
|
+ this.workerService = workerService;
|
|
64
|
81
|
this.eSignClient = eSignClient;
|
|
65
|
82
|
}
|
|
66
|
83
|
|
|
|
@@ -189,6 +206,175 @@ public class ContractService {
|
|
189
|
206
|
.last("LIMIT 1"));
|
|
190
|
207
|
}
|
|
191
|
208
|
|
|
|
209
|
+ /**
|
|
|
210
|
+ * 临时工查看自己的合同列表;可选按 {@code sign_status} 过滤。
|
|
|
211
|
+ */
|
|
|
212
|
+ public List<ContractItemResponse> listMyContracts(Long userId, String signStatus) {
|
|
|
213
|
+ FeWorker worker = requireWorkerProfile(userId);
|
|
|
214
|
+ LambdaQueryWrapper<FeContract> qw = new LambdaQueryWrapper<FeContract>()
|
|
|
215
|
+ .eq(FeContract::getWorkerId, worker.getId())
|
|
|
216
|
+ .ne(FeContract::getSignStatus, SIGN_VOIDED)
|
|
|
217
|
+ .orderByDesc(FeContract::getId);
|
|
|
218
|
+ if (StringUtils.hasText(signStatus)) {
|
|
|
219
|
+ qw.eq(FeContract::getSignStatus, signStatus.trim());
|
|
|
220
|
+ }
|
|
|
221
|
+ List<FeContract> contracts = feContractMapper.selectList(qw);
|
|
|
222
|
+ if (contracts == null || contracts.isEmpty()) {
|
|
|
223
|
+ return Collections.emptyList();
|
|
|
224
|
+ }
|
|
|
225
|
+ return toItemList(contracts);
|
|
|
226
|
+ }
|
|
|
227
|
+
|
|
|
228
|
+ /**
|
|
|
229
|
+ * 临时工查看自己的合同详情。
|
|
|
230
|
+ */
|
|
|
231
|
+ public ContractItemResponse getMyContract(Long userId, Long contractId) {
|
|
|
232
|
+ FeWorker worker = requireWorkerProfile(userId);
|
|
|
233
|
+ FeContract contract = requireOwnedContract(worker.getId(), contractId);
|
|
|
234
|
+ List<ContractItemResponse> items = toItemList(Collections.singletonList(contract));
|
|
|
235
|
+ return items.isEmpty() ? null : items.get(0);
|
|
|
236
|
+ }
|
|
|
237
|
+
|
|
|
238
|
+ /**
|
|
|
239
|
+ * 临时工确认签署完成(Mock 电子签闭环;真实电子签可改为回调驱动)。
|
|
|
240
|
+ * <p>将合同置为 {@code signed},登记 {@code contract_signing → completed}。</p>
|
|
|
241
|
+ */
|
|
|
242
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
243
|
+ public ContractItemResponse confirmSign(Long userId, Long contractId) {
|
|
|
244
|
+ FeWorker worker = requireWorkerProfile(userId);
|
|
|
245
|
+ FeContract contract = requireOwnedContract(worker.getId(), contractId);
|
|
|
246
|
+
|
|
|
247
|
+ if (SIGN_SIGNED.equals(contract.getSignStatus())) {
|
|
|
248
|
+ return getMyContract(userId, contractId);
|
|
|
249
|
+ }
|
|
|
250
|
+ if (SIGN_VOIDED.equals(contract.getSignStatus())) {
|
|
|
251
|
+ throw new BizException(ErrorCode.BAD_REQUEST, "合同已作废,无法签署");
|
|
|
252
|
+ }
|
|
|
253
|
+ if (!SIGN_PENDING.equals(contract.getSignStatus()) && !SIGN_SIGNING.equals(contract.getSignStatus())) {
|
|
|
254
|
+ throw new BizException(ErrorCode.BAD_REQUEST, "当前签署状态不可确认:" + contract.getSignStatus());
|
|
|
255
|
+ }
|
|
|
256
|
+
|
|
|
257
|
+ LocalDateTime now = LocalDateTime.now();
|
|
|
258
|
+ FeContract update = new FeContract();
|
|
|
259
|
+ update.setId(contract.getId());
|
|
|
260
|
+ update.setSignStatus(SIGN_SIGNED);
|
|
|
261
|
+ update.setSignedAt(now);
|
|
|
262
|
+ update.setUpdateTime(now);
|
|
|
263
|
+ feContractMapper.updateById(update);
|
|
|
264
|
+
|
|
|
265
|
+ markRegistrationCompleted(contract.getRegistrationId(), now);
|
|
|
266
|
+ log.info("合同签署完成 contractId={} workerId={} registrationId={}",
|
|
|
267
|
+ contract.getId(), worker.getId(), contract.getRegistrationId());
|
|
|
268
|
+ return getMyContract(userId, contractId);
|
|
|
269
|
+ }
|
|
|
270
|
+
|
|
|
271
|
+ private void markRegistrationCompleted(Long registrationId, LocalDateTime now) {
|
|
|
272
|
+ if (registrationId == null) {
|
|
|
273
|
+ return;
|
|
|
274
|
+ }
|
|
|
275
|
+ FeWorkerRegistration reg = feWorkerRegistrationMapper.selectById(registrationId);
|
|
|
276
|
+ if (reg == null) {
|
|
|
277
|
+ return;
|
|
|
278
|
+ }
|
|
|
279
|
+ if (!REG_CONTRACT_SIGNING.equals(reg.getRegStatus())) {
|
|
|
280
|
+ return;
|
|
|
281
|
+ }
|
|
|
282
|
+ FeWorkerRegistration update = new FeWorkerRegistration();
|
|
|
283
|
+ update.setId(reg.getId());
|
|
|
284
|
+ update.setRegStatus(REG_COMPLETED);
|
|
|
285
|
+ update.setUpdateTime(now);
|
|
|
286
|
+ feWorkerRegistrationMapper.updateById(update);
|
|
|
287
|
+ }
|
|
|
288
|
+
|
|
|
289
|
+ private FeWorker requireWorkerProfile(Long userId) {
|
|
|
290
|
+ workerService.requireWorkerUser(userId);
|
|
|
291
|
+ FeWorker worker = workerService.findWorkerByUserId(userId);
|
|
|
292
|
+ if (worker == null) {
|
|
|
293
|
+ throw new BizException(ErrorCode.BAD_REQUEST, "请先完成临时工实名登记");
|
|
|
294
|
+ }
|
|
|
295
|
+ return worker;
|
|
|
296
|
+ }
|
|
|
297
|
+
|
|
|
298
|
+ private FeContract requireOwnedContract(Long workerId, Long contractId) {
|
|
|
299
|
+ if (contractId == null) {
|
|
|
300
|
+ throw new BizException(ErrorCode.BAD_REQUEST, "合同 ID 无效");
|
|
|
301
|
+ }
|
|
|
302
|
+ FeContract contract = feContractMapper.selectById(contractId);
|
|
|
303
|
+ if (contract == null || SIGN_VOIDED.equals(contract.getSignStatus())) {
|
|
|
304
|
+ throw new BizException(ErrorCode.NOT_FOUND, "合同不存在");
|
|
|
305
|
+ }
|
|
|
306
|
+ if (contract.getWorkerId() == null || !contract.getWorkerId().equals(workerId)) {
|
|
|
307
|
+ throw new BizException(ErrorCode.FORBIDDEN, "无权查看该合同");
|
|
|
308
|
+ }
|
|
|
309
|
+ return contract;
|
|
|
310
|
+ }
|
|
|
311
|
+
|
|
|
312
|
+ private List<ContractItemResponse> toItemList(List<FeContract> contracts) {
|
|
|
313
|
+ Set<Long> orderIds = new HashSet<>();
|
|
|
314
|
+ Set<Long> enterpriseIds = new HashSet<>();
|
|
|
315
|
+ for (FeContract c : contracts) {
|
|
|
316
|
+ if (c.getOrderId() != null) {
|
|
|
317
|
+ orderIds.add(c.getOrderId());
|
|
|
318
|
+ }
|
|
|
319
|
+ if (c.getEnterpriseId() != null) {
|
|
|
320
|
+ enterpriseIds.add(c.getEnterpriseId());
|
|
|
321
|
+ }
|
|
|
322
|
+ }
|
|
|
323
|
+
|
|
|
324
|
+ Map<Long, FeEmploymentOrder> orderMap = new HashMap<>();
|
|
|
325
|
+ if (!orderIds.isEmpty()) {
|
|
|
326
|
+ List<FeEmploymentOrder> orders = feEmploymentOrderMapper.selectBatchIds(orderIds);
|
|
|
327
|
+ if (orders != null) {
|
|
|
328
|
+ for (FeEmploymentOrder o : orders) {
|
|
|
329
|
+ orderMap.put(o.getId(), o);
|
|
|
330
|
+ }
|
|
|
331
|
+ }
|
|
|
332
|
+ }
|
|
|
333
|
+
|
|
|
334
|
+ Map<Long, FeEnterprise> enterpriseMap = new HashMap<>();
|
|
|
335
|
+ if (!enterpriseIds.isEmpty()) {
|
|
|
336
|
+ List<FeEnterprise> enterprises = feEnterpriseMapper.selectBatchIds(enterpriseIds);
|
|
|
337
|
+ if (enterprises != null) {
|
|
|
338
|
+ for (FeEnterprise e : enterprises) {
|
|
|
339
|
+ enterpriseMap.put(e.getId(), e);
|
|
|
340
|
+ }
|
|
|
341
|
+ }
|
|
|
342
|
+ }
|
|
|
343
|
+
|
|
|
344
|
+ List<ContractItemResponse> result = new ArrayList<>(contracts.size());
|
|
|
345
|
+ for (FeContract c : contracts) {
|
|
|
346
|
+ result.add(toItem(c, orderMap.get(c.getOrderId()), enterpriseMap.get(c.getEnterpriseId())));
|
|
|
347
|
+ }
|
|
|
348
|
+ return result;
|
|
|
349
|
+ }
|
|
|
350
|
+
|
|
|
351
|
+ private static ContractItemResponse toItem(FeContract contract,
|
|
|
352
|
+ FeEmploymentOrder order,
|
|
|
353
|
+ FeEnterprise enterprise) {
|
|
|
354
|
+ ContractItemResponse item = new ContractItemResponse();
|
|
|
355
|
+ item.setContractId(contract.getId());
|
|
|
356
|
+ item.setContractNo(contract.getContractNo());
|
|
|
357
|
+ item.setRegistrationId(contract.getRegistrationId());
|
|
|
358
|
+ item.setOrderId(contract.getOrderId());
|
|
|
359
|
+ if (order != null) {
|
|
|
360
|
+ item.setOrderTitle(order.getTitle());
|
|
|
361
|
+ }
|
|
|
362
|
+ item.setEnterpriseId(contract.getEnterpriseId());
|
|
|
363
|
+ if (enterprise != null) {
|
|
|
364
|
+ item.setEnterpriseName(enterprise.getName());
|
|
|
365
|
+ }
|
|
|
366
|
+ item.setContractType(contract.getContractType());
|
|
|
367
|
+ item.setContractTitle(contract.getContractTitle());
|
|
|
368
|
+ item.setSignStatus(contract.getSignStatus());
|
|
|
369
|
+ item.setSignProvider(contract.getSignProvider());
|
|
|
370
|
+ item.setSignUrl(contract.getSignUrl());
|
|
|
371
|
+ item.setExternalContractId(contract.getExternalContractId());
|
|
|
372
|
+ item.setFileUrl(contract.getFileUrl());
|
|
|
373
|
+ item.setSignedAt(contract.getSignedAt());
|
|
|
374
|
+ item.setCreateTime(contract.getCreateTime());
|
|
|
375
|
+ return item;
|
|
|
376
|
+ }
|
|
|
377
|
+
|
|
192
|
378
|
private void tryFillESign(FeContract contract, FeWorkerRegistration reg) {
|
|
193
|
379
|
ESignClient.ESignCreateResult result = eSignClient.createPendingContract(
|
|
194
|
380
|
contract.getContractNo(),
|