Quellcode durchsuchen

增加与ai大模型对话

wwh vor 1 Woche
Ursprung
Commit
3784ceb04c

+ 23 - 2
huimv-employment/fe-api/src/main/java/com/huimv/employment/controller/mp/EmploymentOrderController.java

@@ -6,6 +6,8 @@ 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.order.EmploymentOrderService;
9
+import com.huimv.employment.service.order.dto.OrderCompleteRequest;
10
+import com.huimv.employment.service.order.dto.OrderCompleteResponse;
9 11
 import com.huimv.employment.service.order.dto.OrderStartWorkRequest;
10 12
 import com.huimv.employment.service.order.dto.OrderStartWorkResponse;
11 13
 import com.huimv.employment.service.settlement.SettlementService;
@@ -28,12 +30,13 @@ import org.springframework.web.bind.annotation.RestController;
28 30
  * 企业端用工订单 API。
29 31
  * <p>
30 32
  * 节点推进:确认开工(→ work_started)、发起结算(→ settlement_pending)、
31
- * 确认并支付(→ settlement_paid)。均需企业 JWT;订单须归属当前登录企业。
33
+ * 确认并支付(→ settlement_paid)、完税开票办结(→ completed)。
34
+ * 均需企业 JWT;订单须归属当前登录企业。
32 35
  * </p>
33 36
  */
34 37
 @RestController
35 38
 @RequestMapping("/api/v1/mp/orders")
36
-@Tag(name = "用工订单", description = "企业确认开工、发起结算、确认支付等")
39
+@Tag(name = "用工订单", description = "企业确认开工、结算支付、完税开票办结等")
37 40
 @SecurityRequirement(name = "Authorization")
38 41
 public class EmploymentOrderController {
39 42
 
@@ -126,6 +129,24 @@ public class EmploymentOrderController {
126 129
         return R.ok(settlementService.confirmAndPay(LoginUserHolder.require().getUserId(), orderId, request));
127 130
     }
128 131
 
132
+    @PostMapping("/{orderId}/complete")
133
+    @Operation(summary = "完税开票办结",
134
+            description = "支付完成后企业确认完税与开票,订单 settlement_paid→completed。"
135
+                    + "默认 tax_cleared/invoice_issued 为 true;显式 false 拒绝办结。"
136
+                    + "可传 invoice_no、tax_voucher_no 留痕。已办结幂等返回。",
137
+            parameters = {
138
+                    @Parameter(name = "orderId", in = ParameterIn.PATH, required = true,
139
+                            description = "fe_employment_order.id")
140
+            })
141
+    public R<OrderCompleteResponse> complete(@PathVariable("orderId") Long orderId,
142
+                                             @RequestBody(required = false) OrderCompleteRequest request) {
143
+        requireEnterprise();
144
+        if (request == null) {
145
+            request = new OrderCompleteRequest();
146
+        }
147
+        return R.ok(settlementService.completeOrder(LoginUserHolder.require().getUserId(), orderId, request));
148
+    }
149
+
129 150
     /** 本 Controller 接口仅企业用户可访问。 */
130 151
     private static LoginUser requireEnterprise() {
131 152
         LoginUser loginUser = LoginUserHolder.require();

+ 86 - 0
huimv-employment/fe-service/src/main/java/com/huimv/employment/service/order/dto/OrderCompleteRequest.java

@@ -0,0 +1,86 @@
1
+package com.huimv.employment.service.order.dto;
2
+
3
+import com.fasterxml.jackson.annotation.JsonAlias;
4
+import com.fasterxml.jackson.annotation.JsonProperty;
5
+import io.swagger.v3.oas.annotations.media.Schema;
6
+
7
+/**
8
+ * 企业完税/开票办结请求体。
9
+ * <p>订单 {@code settlement_paid → completed}。MVP 默认完税、开票均视为已完成。</p>
10
+ */
11
+@Schema(description = "企业完税开票办结请求")
12
+public class OrderCompleteRequest {
13
+
14
+    @JsonProperty("tax_cleared")
15
+    @JsonAlias({"taxCleared"})
16
+    @Schema(description = "是否已完税;默认 true(MVP Mock)")
17
+    private Boolean taxCleared;
18
+
19
+    @JsonProperty("invoice_issued")
20
+    @JsonAlias({"invoiceIssued"})
21
+    @Schema(description = "是否已开票;默认 true(MVP Mock)")
22
+    private Boolean invoiceIssued;
23
+
24
+    @JsonProperty("invoice_no")
25
+    @JsonAlias({"invoiceNo"})
26
+    @Schema(description = "发票号码(可选留痕)", example = "INV20260728001")
27
+    private String invoiceNo;
28
+
29
+    @JsonProperty("tax_voucher_no")
30
+    @JsonAlias({"taxVoucherNo"})
31
+    @Schema(description = "完税凭证号(可选留痕)", example = "TAX20260728001")
32
+    private String taxVoucherNo;
33
+
34
+    @Schema(description = "备注")
35
+    private String remark;
36
+
37
+    public Boolean getTaxCleared() {
38
+        return taxCleared;
39
+    }
40
+
41
+    public void setTaxCleared(Boolean taxCleared) {
42
+        this.taxCleared = taxCleared;
43
+    }
44
+
45
+    /** 未传时默认 true */
46
+    public boolean isTaxClearedOrDefault() {
47
+        return taxCleared == null || Boolean.TRUE.equals(taxCleared);
48
+    }
49
+
50
+    public Boolean getInvoiceIssued() {
51
+        return invoiceIssued;
52
+    }
53
+
54
+    public void setInvoiceIssued(Boolean invoiceIssued) {
55
+        this.invoiceIssued = invoiceIssued;
56
+    }
57
+
58
+    /** 未传时默认 true */
59
+    public boolean isInvoiceIssuedOrDefault() {
60
+        return invoiceIssued == null || Boolean.TRUE.equals(invoiceIssued);
61
+    }
62
+
63
+    public String getInvoiceNo() {
64
+        return invoiceNo;
65
+    }
66
+
67
+    public void setInvoiceNo(String invoiceNo) {
68
+        this.invoiceNo = invoiceNo;
69
+    }
70
+
71
+    public String getTaxVoucherNo() {
72
+        return taxVoucherNo;
73
+    }
74
+
75
+    public void setTaxVoucherNo(String taxVoucherNo) {
76
+        this.taxVoucherNo = taxVoucherNo;
77
+    }
78
+
79
+    public String getRemark() {
80
+        return remark;
81
+    }
82
+
83
+    public void setRemark(String remark) {
84
+        this.remark = remark;
85
+    }
86
+}

+ 156 - 0
huimv-employment/fe-service/src/main/java/com/huimv/employment/service/order/dto/OrderCompleteResponse.java

@@ -0,0 +1,156 @@
1
+package com.huimv.employment.service.order.dto;
2
+
3
+import com.fasterxml.jackson.annotation.JsonProperty;
4
+import io.swagger.v3.oas.annotations.media.Schema;
5
+
6
+import java.math.BigDecimal;
7
+import java.time.LocalDateTime;
8
+
9
+/**
10
+ * 订单办结(完税/开票)响应。
11
+ */
12
+@Schema(description = "订单办结响应")
13
+public class OrderCompleteResponse {
14
+
15
+    @JsonProperty("order_id")
16
+    private Long orderId;
17
+
18
+    @JsonProperty("order_no")
19
+    private String orderNo;
20
+
21
+    @JsonProperty("current_step_code")
22
+    private String currentStepCode;
23
+
24
+    @JsonProperty("order_status")
25
+    private String orderStatus;
26
+
27
+    @JsonProperty("settlement_id")
28
+    private Long settlementId;
29
+
30
+    @JsonProperty("settlement_no")
31
+    private String settlementNo;
32
+
33
+    @JsonProperty("invoice_amount")
34
+    private BigDecimal invoiceAmount;
35
+
36
+    @JsonProperty("tax_cleared")
37
+    private Boolean taxCleared;
38
+
39
+    @JsonProperty("invoice_issued")
40
+    private Boolean invoiceIssued;
41
+
42
+    @JsonProperty("invoice_no")
43
+    private String invoiceNo;
44
+
45
+    @JsonProperty("tax_voucher_no")
46
+    private String taxVoucherNo;
47
+
48
+    private String message;
49
+
50
+    @JsonProperty("completed_at")
51
+    private LocalDateTime completedAt;
52
+
53
+    public Long getOrderId() {
54
+        return orderId;
55
+    }
56
+
57
+    public void setOrderId(Long orderId) {
58
+        this.orderId = orderId;
59
+    }
60
+
61
+    public String getOrderNo() {
62
+        return orderNo;
63
+    }
64
+
65
+    public void setOrderNo(String orderNo) {
66
+        this.orderNo = orderNo;
67
+    }
68
+
69
+    public String getCurrentStepCode() {
70
+        return currentStepCode;
71
+    }
72
+
73
+    public void setCurrentStepCode(String currentStepCode) {
74
+        this.currentStepCode = currentStepCode;
75
+    }
76
+
77
+    public String getOrderStatus() {
78
+        return orderStatus;
79
+    }
80
+
81
+    public void setOrderStatus(String orderStatus) {
82
+        this.orderStatus = orderStatus;
83
+    }
84
+
85
+    public Long getSettlementId() {
86
+        return settlementId;
87
+    }
88
+
89
+    public void setSettlementId(Long settlementId) {
90
+        this.settlementId = settlementId;
91
+    }
92
+
93
+    public String getSettlementNo() {
94
+        return settlementNo;
95
+    }
96
+
97
+    public void setSettlementNo(String settlementNo) {
98
+        this.settlementNo = settlementNo;
99
+    }
100
+
101
+    public BigDecimal getInvoiceAmount() {
102
+        return invoiceAmount;
103
+    }
104
+
105
+    public void setInvoiceAmount(BigDecimal invoiceAmount) {
106
+        this.invoiceAmount = invoiceAmount;
107
+    }
108
+
109
+    public Boolean getTaxCleared() {
110
+        return taxCleared;
111
+    }
112
+
113
+    public void setTaxCleared(Boolean taxCleared) {
114
+        this.taxCleared = taxCleared;
115
+    }
116
+
117
+    public Boolean getInvoiceIssued() {
118
+        return invoiceIssued;
119
+    }
120
+
121
+    public void setInvoiceIssued(Boolean invoiceIssued) {
122
+        this.invoiceIssued = invoiceIssued;
123
+    }
124
+
125
+    public String getInvoiceNo() {
126
+        return invoiceNo;
127
+    }
128
+
129
+    public void setInvoiceNo(String invoiceNo) {
130
+        this.invoiceNo = invoiceNo;
131
+    }
132
+
133
+    public String getTaxVoucherNo() {
134
+        return taxVoucherNo;
135
+    }
136
+
137
+    public void setTaxVoucherNo(String taxVoucherNo) {
138
+        this.taxVoucherNo = taxVoucherNo;
139
+    }
140
+
141
+    public String getMessage() {
142
+        return message;
143
+    }
144
+
145
+    public void setMessage(String message) {
146
+        this.message = message;
147
+    }
148
+
149
+    public LocalDateTime getCompletedAt() {
150
+        return completedAt;
151
+    }
152
+
153
+    public void setCompletedAt(LocalDateTime completedAt) {
154
+        this.completedAt = completedAt;
155
+    }
156
+}

+ 106 - 2
huimv-employment/fe-service/src/main/java/com/huimv/employment/service/settlement/SettlementService.java

@@ -23,6 +23,8 @@ import com.huimv.employment.dao.mapper.FeSettlementItemMapper;
23 23
 import com.huimv.employment.dao.mapper.FeSettlementOrderMapper;
24 24
 import com.huimv.employment.dao.mapper.FeWorkerRegistrationMapper;
25 25
 import com.huimv.employment.service.enterprise.EnterpriseService;
26
+import com.huimv.employment.service.order.dto.OrderCompleteRequest;
27
+import com.huimv.employment.service.order.dto.OrderCompleteResponse;
26 28
 import com.huimv.employment.service.settlement.dto.OrderStartSettlementRequest;
27 29
 import com.huimv.employment.service.settlement.dto.SettlementConfirmPayRequest;
28 30
 import com.huimv.employment.service.settlement.dto.SettlementFeeResponse;
@@ -48,9 +50,9 @@ import java.util.concurrent.ThreadLocalRandom;
48 50
  * 结算服务(方案 A:企业主动发起)。
49 51
  * <p>
50 52
  * 流程:{@code work_started} → 发起结算 → {@code settlement_pending} →
51
- * 确认并支付(Mock)→ {@code settlement_paid}。
53
+ * 确认并支付(Mock)→ {@code settlement_paid} → 完税/开票办结 → {@code completed}
52 54
  * </p>
53
- * <p>MVP 无考勤、无真实支付通道:金额按日薪/方案快照估算,支付即时成功。</p>
55
+ * <p>MVP 无考勤、无真实支付/税票通道:金额按日薪/方案快照估算,支付与办结均为 Mock。</p>
54 56
  */
55 57
 @Service
56 58
 public class SettlementService {
@@ -63,6 +65,8 @@ public class SettlementService {
63 65
     private static final String STEP_SETTLEMENT_PENDING = "settlement_pending";
64 66
     /** 订单步骤:已支付 */
65 67
     private static final String STEP_SETTLEMENT_PAID = "settlement_paid";
68
+    /** 订单步骤:已完成(完税/开票办结后) */
69
+    private static final String STEP_COMPLETED = "completed";
66 70
     /** 登记状态:已签完合同,可纳入结算 */
67 71
     private static final String REG_COMPLETED = "completed";
68 72
     /** 结算单状态:待企业确认 */
@@ -83,6 +87,8 @@ public class SettlementService {
83 87
     private static final String ORDER_STATUS_SETTLING = "settling";
84 88
     /** 订单业务状态:已支付 */
85 89
     private static final String ORDER_STATUS_PAID = "paid";
90
+    /** 订单业务状态:已完成 */
91
+    private static final String ORDER_STATUS_COMPLETED = "completed";
86 92
     private static final String CHANNEL_WECHAT = "wechat";
87 93
     private static final String CHANNEL_ALIPAY = "alipay";
88 94
     private static final String CHANNEL_BANK = "bank";
@@ -357,6 +363,104 @@ public class SettlementService {
357 363
         return toSummary(order, settlement, false, "结算已确认并支付成功");
358 364
     }
359 365
 
366
+    /**
367
+     * 企业完税/开票办结:订单 {@code settlement_paid → completed}。
368
+     * <ul>
369
+     *   <li>前置:订单已支付,且存在已支付结算单</li>
370
+     *   <li>须完税且已开票(MVP 默认均为 true;显式传 false 会拒绝办结)</li>
371
+     *   <li>结算单合规标签追加完税/开票留痕;发票号、完税凭证号可选写入备注</li>
372
+     *   <li>幂等:已 completed 直接返回</li>
373
+     * </ul>
374
+     */
375
+    @Transactional(rollbackFor = Exception.class)
376
+    public OrderCompleteResponse completeOrder(Long userId, Long orderId, OrderCompleteRequest request) {
377
+        FeEmploymentOrder order = requireOwnedOrder(userId, orderId);
378
+        LocalDateTime now = LocalDateTime.now();
379
+
380
+        if (STEP_COMPLETED.equals(order.getCurrentStepCode())) {
381
+            return toCompleteResponse(order, findActiveByOrderId(orderId), request, now, "订单已办结");
382
+        }
383
+
384
+        String step = order.getCurrentStepCode();
385
+        if (!STEP_SETTLEMENT_PAID.equals(step)) {
386
+            throw new BizException(ErrorCode.BAD_REQUEST,
387
+                    "仅已支付后可办结(完税/开票),当前步骤:" + (step == null ? "未知" : step));
388
+        }
389
+
390
+        FeSettlementOrder settlement = findActiveByOrderId(orderId);
391
+        if (settlement == null || !STATUS_PAID.equals(settlement.getSettlementStatus())) {
392
+            throw new BizException(ErrorCode.BAD_REQUEST, "结算单尚未支付完成,无法办结");
393
+        }
394
+
395
+        OrderCompleteRequest req = request != null ? request : new OrderCompleteRequest();
396
+        if (!req.isTaxClearedOrDefault()) {
397
+            throw new BizException(ErrorCode.BAD_REQUEST, "须确认已完税(tax_cleared=true)后才能办结");
398
+        }
399
+        if (!req.isInvoiceIssuedOrDefault()) {
400
+            throw new BizException(ErrorCode.BAD_REQUEST, "须确认已开票(invoice_issued=true)后才能办结");
401
+        }
402
+
403
+        // 结算单合规留痕(MVP 无独立税票表)
404
+        StringBuilder remark = new StringBuilder("MVP 办结:完税已确认、发票已开具");
405
+        if (StringUtils.hasText(req.getInvoiceNo())) {
406
+            remark.append(";发票号=").append(req.getInvoiceNo().trim());
407
+        }
408
+        if (StringUtils.hasText(req.getTaxVoucherNo())) {
409
+            remark.append(";完税凭证=").append(req.getTaxVoucherNo().trim());
410
+        }
411
+        if (StringUtils.hasText(req.getRemark())) {
412
+            remark.append(";").append(req.getRemark().trim());
413
+        }
414
+
415
+        FeSettlementOrder settlementUpdate = new FeSettlementOrder();
416
+        settlementUpdate.setId(settlement.getId());
417
+        settlementUpdate.setComplianceTags(
418
+                "[\"合同已签署\",\"结算已确认\",\"支付已完成\",\"完税已申报\",\"发票已开具\"]");
419
+        settlementUpdate.setComplianceRemark(remark.toString());
420
+        settlementUpdate.setUpdateTime(now);
421
+        feSettlementOrderMapper.updateById(settlementUpdate);
422
+        settlement.setComplianceTags(settlementUpdate.getComplianceTags());
423
+        settlement.setComplianceRemark(settlementUpdate.getComplianceRemark());
424
+
425
+        FeEmploymentOrder orderUpdate = new FeEmploymentOrder();
426
+        orderUpdate.setId(order.getId());
427
+        orderUpdate.setCurrentStepCode(STEP_COMPLETED);
428
+        orderUpdate.setOrderStatus(ORDER_STATUS_COMPLETED);
429
+        orderUpdate.setUpdateTime(now);
430
+        feEmploymentOrderMapper.updateById(orderUpdate);
431
+        order.setCurrentStepCode(STEP_COMPLETED);
432
+        order.setOrderStatus(ORDER_STATUS_COMPLETED);
433
+
434
+        log.info("订单办结完成 orderId={} settlementId={} invoiceNo={} taxVoucherNo={}",
435
+                orderId, settlement.getId(), req.getInvoiceNo(), req.getTaxVoucherNo());
436
+        return toCompleteResponse(order, settlement, req, now, "完税开票已确认,订单已办结");
437
+    }
438
+
439
+    private OrderCompleteResponse toCompleteResponse(FeEmploymentOrder order,
440
+                                                     FeSettlementOrder settlement,
441
+                                                     OrderCompleteRequest request,
442
+                                                     LocalDateTime completedAt,
443
+                                                     String message) {
444
+        OrderCompleteRequest req = request != null ? request : new OrderCompleteRequest();
445
+        OrderCompleteResponse response = new OrderCompleteResponse();
446
+        response.setOrderId(order.getId());
447
+        response.setOrderNo(order.getOrderNo());
448
+        response.setCurrentStepCode(order.getCurrentStepCode());
449
+        response.setOrderStatus(order.getOrderStatus());
450
+        if (settlement != null) {
451
+            response.setSettlementId(settlement.getId());
452
+            response.setSettlementNo(settlement.getSettlementNo());
453
+            response.setInvoiceAmount(settlement.getInvoiceAmount());
454
+        }
455
+        response.setTaxCleared(req.isTaxClearedOrDefault());
456
+        response.setInvoiceIssued(req.isInvoiceIssuedOrDefault());
457
+        response.setInvoiceNo(StringUtils.hasText(req.getInvoiceNo()) ? req.getInvoiceNo().trim() : null);
458
+        response.setTaxVoucherNo(StringUtils.hasText(req.getTaxVoucherNo()) ? req.getTaxVoucherNo().trim() : null);
459
+        response.setMessage(message);
460
+        response.setCompletedAt(completedAt);
461
+        return response;
462
+    }
463
+
360 464
     /**
361 465
      * 幂等路径:结算单已存在但订单步骤未跟上时,校正到 settlement_pending。
362 466
      */