wwh 5 часов назад
Родитель
Сommit
ed3eef5784

+ 1 - 1
huimv-employment/fe-api/src/main/java/com/huimv/employment/controller/mp/WorkerController.java

@@ -63,7 +63,7 @@ public class WorkerController {
63 63
     @Operation(summary = "第三方可用工人查询(Mock)",
64 64
             description = "请求体为条件数组,示例:[{\"jobType\":\"搬运工\",\"workLocation\":\"上海\"}]。"
65 65
                     + "jobType 与 Mock 中 confirmed_work_type 精确匹配;workLocation 可选(包含匹配)。"
66
-                    + "按工种分组返回 groups[].workers,字段同 workflow-progress 的 workers。"
66
+                    + "按工种分组返回 groups[].workers,含 mobile / id_card_no 明文(便于添加第三方工人)。"
67 67
                     + "MVP Mock:classpath mock/available-workers.json。需企业 JWT。")
68 68
     public R<ThirdPartyWorkerGroupResponse> listAvailable(
69 69
             @RequestBody @NotEmpty(message = "查询条件不能为空")

+ 1 - 1
huimv-employment/fe-api/src/main/resources/application-dev.yml

@@ -7,7 +7,7 @@ spring:
7 7
   datasource:
8 8
     type: com.alibaba.druid.pool.DruidDataSource
9 9
     driver-class-name: com.mysql.cj.jdbc.Driver
10
-    url: jdbc:mysql://122.112.224.199:3306/fe_employment_db_new?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true
10
+    url: jdbc:mysql://122.112.224.199:3306/fe_employment_db?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true
11 11
     username: root
12 12
     password: "XyoGR4b1ODP+h3hjFieKZrO1VoHhpryX+rRbM4qbQChDnn7whnEJdAPjfBPqKBgsbbhfZR136+nG7y89I+H+UQ=="
13 13
     druid:

+ 94 - 57
huimv-employment/fe-service/src/main/java/com/huimv/employment/service/settlement/SettlementService.java

@@ -4,7 +4,6 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
4 4
 import com.huimv.employment.common.exception.BizException;
5 5
 import com.huimv.employment.common.exception.ErrorCode;
6 6
 import com.huimv.employment.dao.entity.FeDraftCostSnapshot;
7
-import com.huimv.employment.dao.entity.FeDraftWorkItem;
8 7
 import com.huimv.employment.dao.entity.FeEmploymentDraft;
9 8
 import com.huimv.employment.dao.entity.FeEmploymentOrder;
10 9
 import com.huimv.employment.dao.entity.FeEnterprise;
@@ -24,7 +23,6 @@ import com.huimv.employment.dao.mapper.FeSettlementFeeMapper;
24 23
 import com.huimv.employment.dao.mapper.FeSettlementItemMapper;
25 24
 import com.huimv.employment.dao.mapper.FeSettlementOrderMapper;
26 25
 import com.huimv.employment.dao.mapper.FeWorkerRegistrationMapper;
27
-import com.huimv.employment.service.draft.DraftWorkItemService;
28 26
 import com.huimv.employment.service.enterprise.EnterpriseService;
29 27
 import com.huimv.employment.service.notification.NotificationService;
30 28
 import com.huimv.employment.service.order.OrderWorkItemService;
@@ -48,7 +46,9 @@ import java.time.LocalDateTime;
48 46
 import java.time.format.DateTimeFormatter;
49 47
 import java.util.ArrayList;
50 48
 import java.util.Arrays;
49
+import java.util.HashMap;
51 50
 import java.util.List;
51
+import java.util.Map;
52 52
 import java.util.concurrent.ThreadLocalRandom;
53 53
 
54 54
 /**
@@ -57,7 +57,7 @@ import java.util.concurrent.ThreadLocalRandom;
57 57
  * 流程:{@code task_publish} → 发起结算 → {@code settlement_pending} →
58 58
  * 确认并支付(Mock)→ {@code settlement_paid} → 完税/开票办结 → {@code completed}。
59 59
  * </p>
60
- * <p>MVP 无考勤、无真实支付/税票通道:金额按日薪/方案快照估算,支付与办结均为 Mock。</p>
60
+ * <p>MVP 无考勤、无真实支付/税票通道:明细按订单工种 unit_amount×work_days 计算,支付与办结均为 Mock。</p>
61 61
  */
62 62
 @Service
63 63
 public class SettlementService {
@@ -112,7 +112,6 @@ public class SettlementService {
112 112
     private final FeServicePlanMapper feServicePlanMapper;
113 113
     private final EnterpriseService enterpriseService;
114 114
     private final NotificationService notificationService;
115
-    private final DraftWorkItemService draftWorkItemService;
116 115
     private final OrderWorkItemService orderWorkItemService;
117 116
     private final OrderInvoiceService orderInvoiceService;
118 117
 
@@ -127,7 +126,6 @@ public class SettlementService {
127 126
                              FeServicePlanMapper feServicePlanMapper,
128 127
                              EnterpriseService enterpriseService,
129 128
                              NotificationService notificationService,
130
-                             DraftWorkItemService draftWorkItemService,
131 129
                              OrderWorkItemService orderWorkItemService,
132 130
                              OrderInvoiceService orderInvoiceService) {
133 131
         this.feSettlementOrderMapper = feSettlementOrderMapper;
@@ -141,7 +139,6 @@ public class SettlementService {
141 139
         this.feServicePlanMapper = feServicePlanMapper;
142 140
         this.enterpriseService = enterpriseService;
143 141
         this.notificationService = notificationService;
144
-        this.draftWorkItemService = draftWorkItemService;
145 142
         this.orderWorkItemService = orderWorkItemService;
146 143
         this.orderInvoiceService = orderInvoiceService;
147 144
     }
@@ -205,7 +202,11 @@ public class SettlementService {
205 202
             throw new BizException(ErrorCode.BAD_REQUEST, "无可结算工人(须登记已完成签署)");
206 203
         }
207 204
 
208
-        AmountBreakdown amounts = calculateAmounts(order, workers.size());
205
+        List<FeOrderWorkItem> orderItems = orderWorkItemService.listByOrderId(order.getId());
206
+        if (orderItems == null || orderItems.isEmpty()) {
207
+            throw new BizException(ErrorCode.BAD_REQUEST, "订单无用工明细,无法按工种结算");
208
+        }
209
+        AmountBreakdown amounts = calculateAmountsByJobType(order, workers, orderItems);
209 210
         LocalDateTime now = LocalDateTime.now();
210 211
         // 多批次时取首个可结算工人的批次作为主批次引用
211 212
         Long batchId = workers.get(0).getBatchId();
@@ -225,28 +226,24 @@ public class SettlementService {
225 226
         settlement.setTotalOutflow(amounts.totalOutflow);
226 227
         settlement.setSettlementStatus(STATUS_PENDING);
227 228
         settlement.setComplianceTags("[\"合同已签署\",\"待企业确认结算\"]");
228
-        settlement.setComplianceRemark("MVP 按约定天数与日薪/方案快照生成,待企业确认后支付");
229
+        settlement.setComplianceRemark("按订单工种 unit_amount×work_days 生成明细,待企业确认后支付");
229 230
         settlement.setCreateTime(now);
230 231
         settlement.setUpdateTime(now);
231 232
         settlement.setDelFlag(0);
232 233
         feSettlementOrderMapper.insert(settlement);
233 234
 
234
-        // 2) 按人明细(姓名脱敏;MVP 个税/其他扣款为 0)
235
-        BigDecimal workDays = orderWorkItemService.primaryWorkDays(orderWorkItemService.listByOrderId(order.getId()));
236
-        if (workDays == null) {
237
-            workDays = BigDecimal.ONE;
238
-        }
239
-        for (FeWorkerRegistration reg : workers) {
235
+        // 2) 按人明细:匹配登记工种 → 订单用工明细计算应发
236
+        for (WorkerLine line : amounts.lines) {
240 237
             FeSettlementItem item = new FeSettlementItem();
241 238
             item.setSettlementId(settlement.getId());
242
-            item.setRegistrationId(reg.getId());
243
-            item.setWorkerId(reg.getWorkerId());
244
-            item.setWorkerNameMask(maskName(reg.getRealName()));
245
-            item.setWorkDays(workDays);
246
-            item.setGrossAmount(amounts.perWorkerGross);
239
+            item.setRegistrationId(line.registration.getId());
240
+            item.setWorkerId(line.registration.getWorkerId());
241
+            item.setWorkerNameMask(maskName(line.registration.getRealName()));
242
+            item.setWorkDays(line.workDays);
243
+            item.setGrossAmount(line.gross);
247 244
             item.setTaxAmount(BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP));
248 245
             item.setOtherDeduction(BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP));
249
-            item.setNetAmount(amounts.perWorkerNet);
246
+            item.setNetAmount(line.net);
250 247
             item.setItemStatus(STATUS_PENDING);
251 248
             item.setCreateTime(now);
252 249
             item.setUpdateTime(now);
@@ -561,45 +558,55 @@ public class SettlementService {
561 558
     }
562 559
 
563 560
     /**
564
-     * MVP 金额估算(无考勤)
561
+     * 按工人确认工种匹配订单用工明细计算结算金额
565 562
      * <ol>
566
-     *   <li>人均应发:优先方案成本快照人均到手,否则草稿日薪 × 工作天数</li>
567
-     *   <li>服务费:方案 {@code serviceFeeRate × 应发总额}</li>
568
-     *   <li>企业总支出:按快照/订单总支出 × 实际人数/计划人数折算;差额记 other_fee</li>
563
+     *   <li>每人应发 = 对应工种 {@code unit_amount × work_days}</li>
564
+     *   <li>应发总额 = 各人应发合计</li>
565
+     *   <li>服务费 = 方案费率 × 应发总额(若有)</li>
566
+     *   <li>企业总支出:优先按工种明细 total_amount 折算;否则 应发+服务费</li>
569 567
      * </ol>
570 568
      */
571
-    private AmountBreakdown calculateAmounts(FeEmploymentOrder order, int workerCount) {
572
-        List<FeOrderWorkItem> orderItems = orderWorkItemService.listByOrderId(order.getId());
573
-        int plannedCount = orderWorkItemService.sumWorkerCount(orderItems);
574
-        BigDecimal workDays = orderWorkItemService.primaryWorkDays(orderItems);
575
-        if (workDays == null || workDays.compareTo(BigDecimal.ZERO) <= 0) {
576
-            workDays = BigDecimal.ONE;
577
-        }
578
-        BigDecimal dailyWage = BigDecimal.ZERO;
579
-        FeOrderWorkItem firstOrderItem = orderWorkItemService.firstItem(orderItems);
580
-        if (firstOrderItem != null && firstOrderItem.getUnitAmount() != null) {
581
-            dailyWage = firstOrderItem.getUnitAmount();
582
-        } else if (order.getDraftId() != null) {
583
-            List<FeDraftWorkItem> items = draftWorkItemService.listByDraftId(order.getDraftId());
584
-            if (!items.isEmpty() && items.get(0).getUnitAmount() != null) {
585
-                dailyWage = items.get(0).getUnitAmount();
569
+    private AmountBreakdown calculateAmountsByJobType(FeEmploymentOrder order,
570
+                                                      List<FeWorkerRegistration> workers,
571
+                                                      List<FeOrderWorkItem> orderItems) {
572
+        Map<String, FeOrderWorkItem> jobTypeMap = new HashMap<>();
573
+        for (FeOrderWorkItem wi : orderItems) {
574
+            if (wi != null && StringUtils.hasText(wi.getJobType())) {
575
+                jobTypeMap.putIfAbsent(wi.getJobType().trim(), wi);
586 576
             }
587 577
         }
578
+        FeOrderWorkItem fallback = orderWorkItemService.firstItem(orderItems);
588 579
 
589
-        // 默认:日薪 × 天数
590
-        BigDecimal perGross = dailyWage.multiply(workDays).setScale(2, RoundingMode.HALF_UP);
591
-        BigDecimal serviceFeeRate = null;
592
-        FeDraftCostSnapshot snapshot = findSelectedSnapshot(order);
593
-        // 有快照则覆盖为方案测算的人均到手
594
-        if (snapshot != null && snapshot.getWorkerTakeHomePerWorker() != null
595
-                && snapshot.getWorkerTakeHomePerWorker().compareTo(BigDecimal.ZERO) > 0) {
596
-            perGross = snapshot.getWorkerTakeHomePerWorker().setScale(2, RoundingMode.HALF_UP);
580
+        List<WorkerLine> lines = new ArrayList<>();
581
+        BigDecimal gross = BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
582
+        for (FeWorkerRegistration reg : workers) {
583
+            FeOrderWorkItem wi = resolveWorkItemForRegistration(reg, jobTypeMap, fallback);
584
+            BigDecimal workDays = wi.getWorkDays() != null && wi.getWorkDays().compareTo(BigDecimal.ZERO) > 0
585
+                    ? wi.getWorkDays() : BigDecimal.ONE;
586
+            BigDecimal unitAmount = wi.getUnitAmount() != null ? wi.getUnitAmount() : BigDecimal.ZERO;
587
+            BigDecimal perGross = unitAmount.multiply(workDays).setScale(2, RoundingMode.HALF_UP);
588
+            // 若明细已有 total_amount 且配置了人数,优先用「组内人均」作为该工种单价口径兜底为 0 时的回退
589
+            if (perGross.compareTo(BigDecimal.ZERO) <= 0
590
+                    && wi.getTotalAmount() != null
591
+                    && wi.getWorkerCount() != null
592
+                    && wi.getWorkerCount() > 0) {
593
+                perGross = wi.getTotalAmount()
594
+                        .divide(BigDecimal.valueOf(wi.getWorkerCount()), 2, RoundingMode.HALF_UP);
595
+            }
596
+
597
+            WorkerLine line = new WorkerLine();
598
+            line.registration = reg;
599
+            line.jobType = wi.getJobType();
600
+            line.workDays = workDays;
601
+            line.unitAmount = unitAmount;
602
+            line.gross = perGross;
603
+            line.net = perGross; // MVP:实发=应发
604
+            lines.add(line);
605
+            gross = gross.add(perGross);
597 606
         }
598 607
 
599
-        BigDecimal gross = perGross.multiply(BigDecimal.valueOf(workerCount)).setScale(2, RoundingMode.HALF_UP);
608
+        BigDecimal serviceFeeRate = null;
600 609
         BigDecimal serviceFee = BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
601
-        BigDecimal otherFee = BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
602
-
603 610
         if (order.getSelectedPlanId() != null) {
604 611
             FeServicePlan plan = feServicePlanMapper.selectById(order.getSelectedPlanId());
605 612
             if (plan != null && plan.getServiceFeeRate() != null
@@ -609,8 +616,11 @@ public class SettlementService {
609 616
             }
610 617
         }
611 618
 
612
-        // 总支出:按实际结算人数相对计划人数比例折算(缺编开工场景)
619
+        int workerCount = workers.size();
620
+        int plannedCount = orderWorkItemService.sumWorkerCount(orderItems);
621
+        BigDecimal otherFee = BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
613 622
         BigDecimal totalOutflow;
623
+        FeDraftCostSnapshot snapshot = findSelectedSnapshot(order);
614 624
         BigDecimal plannedTotal = orderWorkItemService.sumTotalAmount(orderItems);
615 625
         if (snapshot != null && snapshot.getEmployerTotalOutflow() != null && plannedCount > 0) {
616 626
             totalOutflow = snapshot.getEmployerTotalOutflow()
@@ -631,8 +641,7 @@ public class SettlementService {
631 641
         }
632 642
 
633 643
         AmountBreakdown breakdown = new AmountBreakdown();
634
-        breakdown.perWorkerGross = perGross;
635
-        breakdown.perWorkerNet = perGross; // MVP:实发=应发,暂不计个税
644
+        breakdown.lines = lines;
636 645
         breakdown.gross = gross;
637 646
         breakdown.deduction = BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
638 647
         breakdown.net = gross;
@@ -644,6 +653,26 @@ public class SettlementService {
644 653
         return breakdown;
645 654
     }
646 655
 
656
+    /** 按登记 confirmed_work_type 精确匹配订单工种;无工种时回退首条明细。 */
657
+    private static FeOrderWorkItem resolveWorkItemForRegistration(FeWorkerRegistration reg,
658
+                                                                  Map<String, FeOrderWorkItem> jobTypeMap,
659
+                                                                  FeOrderWorkItem fallback) {
660
+        if (reg != null && StringUtils.hasText(reg.getConfirmedWorkType())) {
661
+            FeOrderWorkItem matched = jobTypeMap.get(reg.getConfirmedWorkType().trim());
662
+            if (matched != null) {
663
+                return matched;
664
+            }
665
+            throw new BizException(ErrorCode.BAD_REQUEST,
666
+                    "工人「" + (reg.getRealName() != null ? reg.getRealName() : reg.getId())
667
+                            + "」确认工种「" + reg.getConfirmedWorkType().trim()
668
+                            + "」不在订单用工明细中,无法结算");
669
+        }
670
+        if (fallback == null) {
671
+            throw new BizException(ErrorCode.BAD_REQUEST, "订单无用工明细,无法结算");
672
+        }
673
+        return fallback;
674
+    }
675
+
647 676
     /** 取订单所选方案对应的草稿成本快照。 */
648 677
     private FeDraftCostSnapshot findSelectedSnapshot(FeEmploymentOrder order) {
649 678
         if (order.getDraftId() == null || order.getSelectedPlanId() == null) {
@@ -905,12 +934,20 @@ public class SettlementService {
905 934
         return curIdx >= 0 && targetIdx >= 0 && curIdx > targetIdx;
906 935
     }
907 936
 
937
+    /** 单人按工种计算的结算行。 */
938
+    private static final class WorkerLine {
939
+        private FeWorkerRegistration registration;
940
+        private String jobType;
941
+        private BigDecimal workDays;
942
+        private BigDecimal unitAmount;
943
+        private BigDecimal gross;
944
+        private BigDecimal net;
945
+    }
946
+
908 947
     /** 结算金额中间结果。 */
909 948
     private static final class AmountBreakdown {
910
-        /** 人均应发 */
911
-        private BigDecimal perWorkerGross;
912
-        /** 人均实发 */
913
-        private BigDecimal perWorkerNet;
949
+        /** 按人明细行 */
950
+        private List<WorkerLine> lines;
914 951
         /** 应发总额 */
915 952
         private BigDecimal gross;
916 953
         /** 扣款合计 */

+ 19 - 6
huimv-employment/fe-service/src/main/java/com/huimv/employment/service/worker/ThirdPartyWorkerService.java

@@ -6,7 +6,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
6 6
 import com.huimv.employment.common.exception.BizException;
7 7
 import com.huimv.employment.common.exception.ErrorCode;
8 8
 import com.huimv.employment.service.enterprise.EnterpriseService;
9
-import com.huimv.employment.service.registration.dto.RegistrationBatchWorkerItemResponse;
9
+import com.huimv.employment.service.worker.dto.ThirdPartyAvailableWorkerItem;
10 10
 import com.huimv.employment.service.worker.dto.ThirdPartyWorkerGroupResponse;
11 11
 import com.huimv.employment.service.worker.dto.ThirdPartyWorkerQueryItem;
12 12
 import org.slf4j.Logger;
@@ -66,7 +66,7 @@ public class ThirdPartyWorkerService {
66 66
         }
67 67
 
68 68
         // 保持查询工种顺序;同一工人只出现一次
69
-        Map<String, List<RegistrationBatchWorkerItemResponse>> grouped = new LinkedHashMap<>();
69
+        Map<String, List<ThirdPartyAvailableWorkerItem>> grouped = new LinkedHashMap<>();
70 70
         for (ThirdPartyWorkerQueryItem q : normalized) {
71 71
             grouped.putIfAbsent(q.getJobType(), new ArrayList<>());
72 72
         }
@@ -95,7 +95,7 @@ public class ThirdPartyWorkerService {
95 95
 
96 96
         int total = 0;
97 97
         List<ThirdPartyWorkerGroupResponse.JobTypeGroup> groups = new ArrayList<>();
98
-        for (Map.Entry<String, List<RegistrationBatchWorkerItemResponse>> e : grouped.entrySet()) {
98
+        for (Map.Entry<String, List<ThirdPartyAvailableWorkerItem>> e : grouped.entrySet()) {
99 99
             ThirdPartyWorkerGroupResponse.JobTypeGroup g = new ThirdPartyWorkerGroupResponse.JobTypeGroup();
100 100
             g.setJobType(e.getKey());
101 101
             g.setWorkers(e.getValue());
@@ -147,14 +147,16 @@ public class ThirdPartyWorkerService {
147 147
                 .contains(location.toLowerCase(Locale.ROOT));
148 148
     }
149 149
 
150
-    private static RegistrationBatchWorkerItemResponse toWorkerItem(MockWorkerRow row) {
151
-        RegistrationBatchWorkerItemResponse item = new RegistrationBatchWorkerItemResponse();
150
+    private static ThirdPartyAvailableWorkerItem toWorkerItem(MockWorkerRow row) {
151
+        ThirdPartyAvailableWorkerItem item = new ThirdPartyAvailableWorkerItem();
152 152
         item.setRegistrationId(row.registrationId);
153 153
         item.setWorkerId(row.workerId);
154 154
         item.setRealName(row.realName);
155 155
         item.setConfirmedWorkType(row.confirmedWorkType);
156 156
         item.setThirdPartySource(row.thirdPartySource != null ? row.thirdPartySource : 1);
157
-        item.setMobileMask(row.mobileMask);
157
+        item.setMobile(row.mobile);
158
+        item.setIdCardNo(row.idCardNo);
159
+        item.setMobileMask(StringUtils.hasText(row.mobileMask) ? row.mobileMask : maskMobile(row.mobile));
158 160
         item.setRegStatus(row.regStatus);
159 161
         item.setStatusTag(row.statusTag);
160 162
         item.setStatusText(row.statusText);
@@ -163,6 +165,13 @@ public class ThirdPartyWorkerService {
163 165
         return item;
164 166
     }
165 167
 
168
+    private static String maskMobile(String mobile) {
169
+        if (!StringUtils.hasText(mobile) || mobile.length() < 7) {
170
+            return mobile;
171
+        }
172
+        return mobile.substring(0, 3) + "****" + mobile.substring(mobile.length() - 4);
173
+    }
174
+
166 175
     @JsonIgnoreProperties(ignoreUnknown = true)
167 176
     private static class MockRoot {
168 177
         public List<MockWorkerRow> workers;
@@ -180,6 +189,10 @@ public class ThirdPartyWorkerService {
180 189
         public String confirmedWorkType;
181 190
         @JsonProperty("third_party_source")
182 191
         public Integer thirdPartySource;
192
+        @JsonProperty("mobile")
193
+        public String mobile;
194
+        @JsonProperty("id_card_no")
195
+        public String idCardNo;
183 196
         @JsonProperty("mobile_mask")
184 197
         public String mobileMask;
185 198
         @JsonProperty("reg_status")

+ 169 - 0
huimv-employment/fe-service/src/main/java/com/huimv/employment/service/worker/dto/ThirdPartyAvailableWorkerItem.java

@@ -0,0 +1,169 @@
1
+package com.huimv.employment.service.worker.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
+/**
9
+ * 第三方可用工人条目(含手机号/身份证明文,便于前端直接调用添加接口)。
10
+ */
11
+@Schema(description = "第三方可用工人条目")
12
+public class ThirdPartyAvailableWorkerItem {
13
+
14
+    @JsonProperty("registration_id")
15
+    @Schema(description = "第三方侧登记/候选人 ID(Mock)")
16
+    private Long registrationId;
17
+
18
+    @JsonProperty("worker_id")
19
+    @Schema(description = "工人 ID(第三方 Mock)")
20
+    private Long workerId;
21
+
22
+    @JsonProperty("real_name")
23
+    @Schema(description = "姓名", example = "王强")
24
+    private String realName;
25
+
26
+    @JsonProperty("confirmed_work_type")
27
+    @Schema(description = "工种", example = "搬运工")
28
+    private String confirmedWorkType;
29
+
30
+    @JsonProperty("third_party_source")
31
+    @Schema(description = "是否第三方人力来源:1=是", example = "1")
32
+    private Integer thirdPartySource;
33
+
34
+    @JsonProperty("mobile")
35
+    @Schema(description = "手机号明文", example = "13812341001")
36
+    private String mobile;
37
+
38
+    @JsonProperty("id_card_no")
39
+    @Schema(description = "身份证号明文", example = "110101199001011234")
40
+    private String idCardNo;
41
+
42
+    @JsonProperty("mobile_mask")
43
+    @Schema(description = "脱敏手机号(兼容展示)", example = "138****1001")
44
+    private String mobileMask;
45
+
46
+    @JsonProperty("reg_status")
47
+    @Schema(description = "登记状态")
48
+    private String regStatus;
49
+
50
+    @JsonProperty("status_tag")
51
+    @Schema(description = "前端状态样式标签")
52
+    private String statusTag;
53
+
54
+    @JsonProperty("status_text")
55
+    @Schema(description = "状态中文")
56
+    private String statusText;
57
+
58
+    @JsonProperty("fail_reason")
59
+    @Schema(description = "失败原因")
60
+    private String failReason;
61
+
62
+    @JsonProperty("submitted_at")
63
+    @Schema(description = "提交时间")
64
+    private LocalDateTime submittedAt;
65
+
66
+    public Long getRegistrationId() {
67
+        return registrationId;
68
+    }
69
+
70
+    public void setRegistrationId(Long registrationId) {
71
+        this.registrationId = registrationId;
72
+    }
73
+
74
+    public Long getWorkerId() {
75
+        return workerId;
76
+    }
77
+
78
+    public void setWorkerId(Long workerId) {
79
+        this.workerId = workerId;
80
+    }
81
+
82
+    public String getRealName() {
83
+        return realName;
84
+    }
85
+
86
+    public void setRealName(String realName) {
87
+        this.realName = realName;
88
+    }
89
+
90
+    public String getConfirmedWorkType() {
91
+        return confirmedWorkType;
92
+    }
93
+
94
+    public void setConfirmedWorkType(String confirmedWorkType) {
95
+        this.confirmedWorkType = confirmedWorkType;
96
+    }
97
+
98
+    public Integer getThirdPartySource() {
99
+        return thirdPartySource;
100
+    }
101
+
102
+    public void setThirdPartySource(Integer thirdPartySource) {
103
+        this.thirdPartySource = thirdPartySource;
104
+    }
105
+
106
+    public String getMobile() {
107
+        return mobile;
108
+    }
109
+
110
+    public void setMobile(String mobile) {
111
+        this.mobile = mobile;
112
+    }
113
+
114
+    public String getIdCardNo() {
115
+        return idCardNo;
116
+    }
117
+
118
+    public void setIdCardNo(String idCardNo) {
119
+        this.idCardNo = idCardNo;
120
+    }
121
+
122
+    public String getMobileMask() {
123
+        return mobileMask;
124
+    }
125
+
126
+    public void setMobileMask(String mobileMask) {
127
+        this.mobileMask = mobileMask;
128
+    }
129
+
130
+    public String getRegStatus() {
131
+        return regStatus;
132
+    }
133
+
134
+    public void setRegStatus(String regStatus) {
135
+        this.regStatus = regStatus;
136
+    }
137
+
138
+    public String getStatusTag() {
139
+        return statusTag;
140
+    }
141
+
142
+    public void setStatusTag(String statusTag) {
143
+        this.statusTag = statusTag;
144
+    }
145
+
146
+    public String getStatusText() {
147
+        return statusText;
148
+    }
149
+
150
+    public void setStatusText(String statusText) {
151
+        this.statusText = statusText;
152
+    }
153
+
154
+    public String getFailReason() {
155
+        return failReason;
156
+    }
157
+
158
+    public void setFailReason(String failReason) {
159
+        this.failReason = failReason;
160
+    }
161
+
162
+    public LocalDateTime getSubmittedAt() {
163
+        return submittedAt;
164
+    }
165
+
166
+    public void setSubmittedAt(LocalDateTime submittedAt) {
167
+        this.submittedAt = submittedAt;
168
+    }
169
+}

+ 4 - 5
huimv-employment/fe-service/src/main/java/com/huimv/employment/service/worker/dto/ThirdPartyWorkerGroupResponse.java

@@ -1,7 +1,6 @@
1 1
 package com.huimv.employment.service.worker.dto;
2 2
 
3 3
 import com.fasterxml.jackson.annotation.JsonProperty;
4
-import com.huimv.employment.service.registration.dto.RegistrationBatchWorkerItemResponse;
5 4
 import io.swagger.v3.oas.annotations.media.Schema;
6 5
 
7 6
 import java.util.ArrayList;
@@ -79,8 +78,8 @@ public class ThirdPartyWorkerGroupResponse {
79 78
         @Schema(description = "该工种可用人数")
80 79
         private Integer workerCount;
81 80
 
82
-        @Schema(description = "可用人员列表(字段同 workflow-progress 的 workers 子对象)")
83
-        private List<RegistrationBatchWorkerItemResponse> workers = new ArrayList<>();
81
+        @Schema(description = "可用人员列表(含 mobile / id_card_no 明文)")
82
+        private List<ThirdPartyAvailableWorkerItem> workers = new ArrayList<>();
84 83
 
85 84
         public String getJobType() {
86 85
             return jobType;
@@ -98,11 +97,11 @@ public class ThirdPartyWorkerGroupResponse {
98 97
             this.workerCount = workerCount;
99 98
         }
100 99
 
101
-        public List<RegistrationBatchWorkerItemResponse> getWorkers() {
100
+        public List<ThirdPartyAvailableWorkerItem> getWorkers() {
102 101
             return workers;
103 102
         }
104 103
 
105
-        public void setWorkers(List<RegistrationBatchWorkerItemResponse> workers) {
104
+        public void setWorkers(List<ThirdPartyAvailableWorkerItem> workers) {
106 105
             this.workers = workers != null ? workers : new ArrayList<>();
107 106
         }
108 107
     }

+ 16 - 0
huimv-employment/fe-service/src/main/resources/mock/available-workers.json

@@ -6,6 +6,8 @@
6 6
       "real_name": "王强",
7 7
       "confirmed_work_type": "搬运工",
8 8
       "third_party_source": 1,
9
+      "mobile": "13812341001",
10
+      "id_card_no": "110101199001011001",
9 11
       "mobile_mask": "138****1001",
10 12
       "reg_status": "completed",
11 13
       "status_tag": "done",
@@ -20,6 +22,8 @@
20 22
       "real_name": "李明",
21 23
       "confirmed_work_type": "搬运工",
22 24
       "third_party_source": 1,
25
+      "mobile": "13912342002",
26
+      "id_card_no": "110101199002021002",
23 27
       "mobile_mask": "139****2002",
24 28
       "reg_status": "completed",
25 29
       "status_tag": "done",
@@ -34,6 +38,8 @@
34 38
       "real_name": "赵磊",
35 39
       "confirmed_work_type": "搬运工",
36 40
       "third_party_source": 1,
41
+      "mobile": "13712343003",
42
+      "id_card_no": "310101199003031003",
37 43
       "mobile_mask": "137****3003",
38 44
       "reg_status": "completed",
39 45
       "status_tag": "done",
@@ -48,6 +54,8 @@
48 54
       "real_name": "陈厨",
49 55
       "confirmed_work_type": "厨师",
50 56
       "third_party_source": 1,
57
+      "mobile": "13612344004",
58
+      "id_card_no": "330101199004041004",
51 59
       "mobile_mask": "136****4004",
52 60
       "reg_status": "completed",
53 61
       "status_tag": "done",
@@ -62,6 +70,8 @@
62 70
       "real_name": "周敏",
63 71
       "confirmed_work_type": "厨师",
64 72
       "third_party_source": 1,
73
+      "mobile": "13512345005",
74
+      "id_card_no": "330101199005051005",
65 75
       "mobile_mask": "135****5005",
66 76
       "reg_status": "completed",
67 77
       "status_tag": "done",
@@ -76,6 +86,8 @@
76 86
       "real_name": "孙洁",
77 87
       "confirmed_work_type": "保洁",
78 88
       "third_party_source": 1,
89
+      "mobile": "13412346006",
90
+      "id_card_no": "330101199006061006",
79 91
       "mobile_mask": "134****6006",
80 92
       "reg_status": "completed",
81 93
       "status_tag": "done",
@@ -90,6 +102,8 @@
90 102
       "real_name": "钱峰",
91 103
       "confirmed_work_type": "保安",
92 104
       "third_party_source": 1,
105
+      "mobile": "13312347007",
106
+      "id_card_no": "310101199007071007",
93 107
       "mobile_mask": "133****7007",
94 108
       "reg_status": "completed",
95 109
       "status_tag": "done",
@@ -104,6 +118,8 @@
104 118
       "real_name": "吴涛",
105 119
       "confirmed_work_type": "仓库分拣",
106 120
       "third_party_source": 1,
121
+      "mobile": "13212348008",
122
+      "id_card_no": "310101199008081008",
107 123
       "mobile_mask": "132****8008",
108 124
       "reg_status": "completed",
109 125
       "status_tag": "done",