Преглед изворни кода

增加与ai大模型对话

wwh пре 1 недеља
родитељ
комит
27cf138e33

+ 47 - 2
huimv-employment/fe-api/src/main/java/com/huimv/employment/controller/mp/ContractController.java

@@ -6,27 +6,33 @@ import com.huimv.employment.common.web.R;
6 6
 import com.huimv.employment.security.LoginUser;
7 7
 import com.huimv.employment.security.LoginUserHolder;
8 8
 import com.huimv.employment.service.contract.ContractService;
9
+import com.huimv.employment.service.contract.dto.ContractItemResponse;
9 10
 import com.huimv.employment.service.contract.dto.ContractSummaryResponse;
10 11
 import io.swagger.v3.oas.annotations.Operation;
11 12
 import io.swagger.v3.oas.annotations.Parameter;
12 13
 import io.swagger.v3.oas.annotations.enums.ParameterIn;
13 14
 import io.swagger.v3.oas.annotations.security.SecurityRequirement;
14 15
 import io.swagger.v3.oas.annotations.tags.Tag;
16
+import org.springframework.web.bind.annotation.GetMapping;
15 17
 import org.springframework.web.bind.annotation.PathVariable;
16 18
 import org.springframework.web.bind.annotation.PostMapping;
17 19
 import org.springframework.web.bind.annotation.RequestMapping;
20
+import org.springframework.web.bind.annotation.RequestParam;
18 21
 import org.springframework.web.bind.annotation.RestController;
19 22
 
23
+import java.util.List;
24
+
20 25
 /**
21
- * 电子合同(企业端):重新生成待签合同。
26
+ * 电子合同:临时工列表/签署确认;企业端重新生成待签合同。
22 27
  */
23 28
 @RestController
24 29
 @RequestMapping("/api/v1/mp/contracts")
25
-@Tag(name = "电子合同", description = "审核通过后自动生成待签合同;失败可重试")
30
+@Tag(name = "电子合同", description = "临时工查看与签署;企业重生成待签合同")
26 31
 @SecurityRequirement(name = "Authorization")
27 32
 public class ContractController {
28 33
 
29 34
     private static final String USER_TYPE_ENTERPRISE = "enterprise";
35
+    private static final String USER_TYPE_WORKER = "worker";
30 36
 
31 37
     private final ContractService contractService;
32 38
 
@@ -34,6 +40,37 @@ public class ContractController {
34 40
         this.contractService = contractService;
35 41
     }
36 42
 
43
+    @GetMapping
44
+    @Operation(summary = "我的电子合同列表",
45
+            description = "临时工 JWT;按 worker_id 查询本人合同。可选 sign_status=pending|signing|signed 过滤。")
46
+    public R<List<ContractItemResponse>> listMine(
47
+            @RequestParam(value = "sign_status", required = false) String signStatus) {
48
+        LoginUser loginUser = requireWorker();
49
+        return R.ok(contractService.listMyContracts(loginUser.getUserId(), signStatus));
50
+    }
51
+
52
+    @GetMapping("/{id}")
53
+    @Operation(summary = "合同详情",
54
+            description = "临时工查看本人合同详情",
55
+            parameters = {
56
+                    @Parameter(name = "id", in = ParameterIn.PATH, required = true, description = "fe_contract.id")
57
+            })
58
+    public R<ContractItemResponse> detail(@PathVariable("id") Long id) {
59
+        LoginUser loginUser = requireWorker();
60
+        return R.ok(contractService.getMyContract(loginUser.getUserId(), id));
61
+    }
62
+
63
+    @PostMapping("/{id}/confirm-sign")
64
+    @Operation(summary = "确认签署完成",
65
+            description = "临时工确认签署(Mock 闭环)。sign_status→signed,登记 contract_signing→completed。已签署幂等返回。",
66
+            parameters = {
67
+                    @Parameter(name = "id", in = ParameterIn.PATH, required = true, description = "fe_contract.id")
68
+            })
69
+    public R<ContractItemResponse> confirmSign(@PathVariable("id") Long id) {
70
+        LoginUser loginUser = requireWorker();
71
+        return R.ok(contractService.confirmSign(loginUser.getUserId(), id));
72
+    }
73
+
37 74
     @PostMapping("/by-registration/{registrationId}/regenerate")
38 75
     @Operation(summary = "重新生成待签合同",
39 76
             description = "电子签失败或签署链接缺失时调用。同一 registration 幂等;已签署不可重生成。",
@@ -53,4 +90,12 @@ public class ContractController {
53 90
         }
54 91
         return loginUser;
55 92
     }
93
+
94
+    private static LoginUser requireWorker() {
95
+        LoginUser loginUser = LoginUserHolder.require();
96
+        if (!USER_TYPE_WORKER.equals(loginUser.getUserType())) {
97
+            throw new BizException(ErrorCode.WORKER_ONLY);
98
+        }
99
+        return loginUser;
100
+    }
56 101
 }

+ 186 - 0
huimv-employment/fe-service/src/main/java/com/huimv/employment/service/contract/ContractService.java

@@ -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(),

+ 186 - 0
huimv-employment/fe-service/src/main/java/com/huimv/employment/service/contract/dto/ContractItemResponse.java

@@ -0,0 +1,186 @@
1
+package com.huimv.employment.service.contract.dto;
2
+
3
+import com.fasterxml.jackson.annotation.JsonProperty;
4
+import io.swagger.v3.oas.annotations.media.Schema;
5
+
6
+import java.time.LocalDateTime;
7
+
8
+@Schema(description = "电子合同列表项(临时工)")
9
+public class ContractItemResponse {
10
+
11
+    @JsonProperty("contract_id")
12
+    private Long contractId;
13
+
14
+    @JsonProperty("contract_no")
15
+    private String contractNo;
16
+
17
+    @JsonProperty("registration_id")
18
+    private Long registrationId;
19
+
20
+    @JsonProperty("order_id")
21
+    private Long orderId;
22
+
23
+    @JsonProperty("order_title")
24
+    private String orderTitle;
25
+
26
+    @JsonProperty("enterprise_id")
27
+    private Long enterpriseId;
28
+
29
+    @JsonProperty("enterprise_name")
30
+    private String enterpriseName;
31
+
32
+    @JsonProperty("contract_type")
33
+    private String contractType;
34
+
35
+    @JsonProperty("contract_title")
36
+    private String contractTitle;
37
+
38
+    @JsonProperty("sign_status")
39
+    private String signStatus;
40
+
41
+    @JsonProperty("sign_provider")
42
+    private String signProvider;
43
+
44
+    @JsonProperty("sign_url")
45
+    private String signUrl;
46
+
47
+    @JsonProperty("external_contract_id")
48
+    private String externalContractId;
49
+
50
+    @JsonProperty("file_url")
51
+    private String fileUrl;
52
+
53
+    @JsonProperty("signed_at")
54
+    private LocalDateTime signedAt;
55
+
56
+    @JsonProperty("create_time")
57
+    private LocalDateTime createTime;
58
+
59
+    public Long getContractId() {
60
+        return contractId;
61
+    }
62
+
63
+    public void setContractId(Long contractId) {
64
+        this.contractId = contractId;
65
+    }
66
+
67
+    public String getContractNo() {
68
+        return contractNo;
69
+    }
70
+
71
+    public void setContractNo(String contractNo) {
72
+        this.contractNo = contractNo;
73
+    }
74
+
75
+    public Long getRegistrationId() {
76
+        return registrationId;
77
+    }
78
+
79
+    public void setRegistrationId(Long registrationId) {
80
+        this.registrationId = registrationId;
81
+    }
82
+
83
+    public Long getOrderId() {
84
+        return orderId;
85
+    }
86
+
87
+    public void setOrderId(Long orderId) {
88
+        this.orderId = orderId;
89
+    }
90
+
91
+    public String getOrderTitle() {
92
+        return orderTitle;
93
+    }
94
+
95
+    public void setOrderTitle(String orderTitle) {
96
+        this.orderTitle = orderTitle;
97
+    }
98
+
99
+    public Long getEnterpriseId() {
100
+        return enterpriseId;
101
+    }
102
+
103
+    public void setEnterpriseId(Long enterpriseId) {
104
+        this.enterpriseId = enterpriseId;
105
+    }
106
+
107
+    public String getEnterpriseName() {
108
+        return enterpriseName;
109
+    }
110
+
111
+    public void setEnterpriseName(String enterpriseName) {
112
+        this.enterpriseName = enterpriseName;
113
+    }
114
+
115
+    public String getContractType() {
116
+        return contractType;
117
+    }
118
+
119
+    public void setContractType(String contractType) {
120
+        this.contractType = contractType;
121
+    }
122
+
123
+    public String getContractTitle() {
124
+        return contractTitle;
125
+    }
126
+
127
+    public void setContractTitle(String contractTitle) {
128
+        this.contractTitle = contractTitle;
129
+    }
130
+
131
+    public String getSignStatus() {
132
+        return signStatus;
133
+    }
134
+
135
+    public void setSignStatus(String signStatus) {
136
+        this.signStatus = signStatus;
137
+    }
138
+
139
+    public String getSignProvider() {
140
+        return signProvider;
141
+    }
142
+
143
+    public void setSignProvider(String signProvider) {
144
+        this.signProvider = signProvider;
145
+    }
146
+
147
+    public String getSignUrl() {
148
+        return signUrl;
149
+    }
150
+
151
+    public void setSignUrl(String signUrl) {
152
+        this.signUrl = signUrl;
153
+    }
154
+
155
+    public String getExternalContractId() {
156
+        return externalContractId;
157
+    }
158
+
159
+    public void setExternalContractId(String externalContractId) {
160
+        this.externalContractId = externalContractId;
161
+    }
162
+
163
+    public String getFileUrl() {
164
+        return fileUrl;
165
+    }
166
+
167
+    public void setFileUrl(String fileUrl) {
168
+        this.fileUrl = fileUrl;
169
+    }
170
+
171
+    public LocalDateTime getSignedAt() {
172
+        return signedAt;
173
+    }
174
+
175
+    public void setSignedAt(LocalDateTime signedAt) {
176
+        this.signedAt = signedAt;
177
+    }
178
+
179
+    public LocalDateTime getCreateTime() {
180
+        return createTime;
181
+    }
182
+
183
+    public void setCreateTime(LocalDateTime createTime) {
184
+        this.createTime = createTime;
185
+    }
186
+}