|
|
@@ -3,39 +3,61 @@ package com.huimv.employment.service.order;
|
|
3
|
3
|
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
|
+import com.huimv.employment.dao.entity.FeContract;
|
|
6
|
7
|
import com.huimv.employment.dao.entity.FeEmploymentDraft;
|
|
7
|
8
|
import com.huimv.employment.dao.entity.FeEmploymentOrder;
|
|
8
|
9
|
import com.huimv.employment.dao.entity.FeEnterprise;
|
|
|
10
|
+import com.huimv.employment.dao.entity.FeWorkerRegistration;
|
|
9
|
11
|
import com.huimv.employment.dao.mapper.FeEmploymentDraftMapper;
|
|
10
|
12
|
import com.huimv.employment.dao.mapper.FeEmploymentOrderMapper;
|
|
|
13
|
+import com.huimv.employment.dao.mapper.FeWorkerRegistrationMapper;
|
|
|
14
|
+import com.huimv.employment.service.contract.ContractService;
|
|
11
|
15
|
import com.huimv.employment.service.conversation.ConversationService;
|
|
12
|
16
|
import com.huimv.employment.service.enterprise.EnterpriseService;
|
|
13
|
17
|
import com.huimv.employment.service.order.dto.EmploymentOrderItemResponse;
|
|
|
18
|
+import com.huimv.employment.service.order.dto.OrderStartWorkRequest;
|
|
|
19
|
+import com.huimv.employment.service.order.dto.OrderStartWorkResponse;
|
|
14
|
20
|
import org.springframework.stereotype.Service;
|
|
|
21
|
+import org.springframework.transaction.annotation.Transactional;
|
|
|
22
|
+import org.springframework.util.StringUtils;
|
|
15
|
23
|
|
|
|
24
|
+import java.time.LocalDateTime;
|
|
16
|
25
|
import java.util.ArrayList;
|
|
17
|
26
|
import java.util.Collections;
|
|
18
|
27
|
import java.util.List;
|
|
19
|
28
|
|
|
20
|
29
|
/**
|
|
21
|
|
- * 企业端用工订单查询。
|
|
|
30
|
+ * 企业端用工订单查询与开工确认。
|
|
22
|
31
|
*/
|
|
23
|
32
|
@Service
|
|
24
|
33
|
public class EmploymentOrderService {
|
|
25
|
34
|
|
|
|
35
|
+ private static final String STEP_CONTRACT_SIGNING = "contract_signing";
|
|
|
36
|
+ private static final String STEP_WORK_STARTED = "work_started";
|
|
|
37
|
+ private static final String REG_CONTRACT_SIGNING = "contract_signing";
|
|
|
38
|
+ private static final String REG_COMPLETED = "completed";
|
|
|
39
|
+ private static final String REG_UNDER_REVIEW = "under_review";
|
|
|
40
|
+ private static final String SIGN_SIGNED = "signed";
|
|
|
41
|
+
|
|
26
|
42
|
private final FeEmploymentOrderMapper feEmploymentOrderMapper;
|
|
27
|
43
|
private final FeEmploymentDraftMapper feEmploymentDraftMapper;
|
|
|
44
|
+ private final FeWorkerRegistrationMapper feWorkerRegistrationMapper;
|
|
28
|
45
|
private final ConversationService conversationService;
|
|
29
|
46
|
private final EnterpriseService enterpriseService;
|
|
|
47
|
+ private final ContractService contractService;
|
|
30
|
48
|
|
|
31
|
49
|
public EmploymentOrderService(FeEmploymentOrderMapper feEmploymentOrderMapper,
|
|
32
|
50
|
FeEmploymentDraftMapper feEmploymentDraftMapper,
|
|
|
51
|
+ FeWorkerRegistrationMapper feWorkerRegistrationMapper,
|
|
33
|
52
|
ConversationService conversationService,
|
|
34
|
|
- EnterpriseService enterpriseService) {
|
|
|
53
|
+ EnterpriseService enterpriseService,
|
|
|
54
|
+ ContractService contractService) {
|
|
35
|
55
|
this.feEmploymentOrderMapper = feEmploymentOrderMapper;
|
|
36
|
56
|
this.feEmploymentDraftMapper = feEmploymentDraftMapper;
|
|
|
57
|
+ this.feWorkerRegistrationMapper = feWorkerRegistrationMapper;
|
|
37
|
58
|
this.conversationService = conversationService;
|
|
38
|
59
|
this.enterpriseService = enterpriseService;
|
|
|
60
|
+ this.contractService = contractService;
|
|
39
|
61
|
}
|
|
40
|
62
|
|
|
41
|
63
|
/**
|
|
|
@@ -85,6 +107,174 @@ public class EmploymentOrderService {
|
|
85
|
107
|
return result;
|
|
86
|
108
|
}
|
|
87
|
109
|
|
|
|
110
|
+ /**
|
|
|
111
|
+ * 查询开工就绪状态(人数提示、是否齐签、是否可开工)。
|
|
|
112
|
+ */
|
|
|
113
|
+ public OrderStartWorkResponse getStartWorkStatus(Long userId, Long orderId) {
|
|
|
114
|
+ FeEmploymentOrder order = requireOwnedOrder(userId, orderId);
|
|
|
115
|
+ return buildStartWorkStatus(order, false);
|
|
|
116
|
+ }
|
|
|
117
|
+
|
|
|
118
|
+ /**
|
|
|
119
|
+ * 企业确认开工:要求已过审工人全部签完;缺编须 {@code allow_understaffed=true}。
|
|
|
120
|
+ * <p>订单 {@code contract_signing → work_started}。</p>
|
|
|
121
|
+ */
|
|
|
122
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
123
|
+ public OrderStartWorkResponse startWork(Long userId, Long orderId, OrderStartWorkRequest request) {
|
|
|
124
|
+ FeEmploymentOrder order = requireOwnedOrder(userId, orderId);
|
|
|
125
|
+ OrderStartWorkResponse status = buildStartWorkStatus(order, true);
|
|
|
126
|
+ if (STEP_WORK_STARTED.equals(order.getCurrentStepCode())
|
|
|
127
|
+ || isStepAfter(order.getCurrentStepCode(), STEP_WORK_STARTED)) {
|
|
|
128
|
+ status.setMessage("订单已进入开工阶段");
|
|
|
129
|
+ return status;
|
|
|
130
|
+ }
|
|
|
131
|
+ if (!STEP_CONTRACT_SIGNING.equals(order.getCurrentStepCode())) {
|
|
|
132
|
+ throw new BizException(ErrorCode.BAD_REQUEST,
|
|
|
133
|
+ "当前步骤不可开工:" + (order.getCurrentStepCode() == null ? "未知" : order.getCurrentStepCode()));
|
|
|
134
|
+ }
|
|
|
135
|
+ if (status.getApprovedCount() == null || status.getApprovedCount() <= 0) {
|
|
|
136
|
+ throw new BizException(ErrorCode.BAD_REQUEST, "尚无已过审工人,无法开工");
|
|
|
137
|
+ }
|
|
|
138
|
+ if (!Boolean.TRUE.equals(status.getAllApprovedSigned())) {
|
|
|
139
|
+ throw new BizException(ErrorCode.BAD_REQUEST,
|
|
|
140
|
+ "尚有已过审工人未完成签署(已签 " + status.getSignedCount()
|
|
|
141
|
+ + "/" + status.getApprovedCount() + ")");
|
|
|
142
|
+ }
|
|
|
143
|
+ boolean allowUnderstaffed = request != null && request.isAllowUnderstaffed();
|
|
|
144
|
+ if (Boolean.TRUE.equals(status.getUnderstaffed()) && !allowUnderstaffed) {
|
|
|
145
|
+ throw new BizException(ErrorCode.BAD_REQUEST,
|
|
|
146
|
+ "已签署 " + status.getSignedCount() + " 人,计划 "
|
|
|
147
|
+ + status.getPlannedCount() + " 人,未招满。"
|
|
|
148
|
+ + "确认缺编开工请传 allow_understaffed=true");
|
|
|
149
|
+ }
|
|
|
150
|
+
|
|
|
151
|
+ LocalDateTime now = LocalDateTime.now();
|
|
|
152
|
+ FeEmploymentOrder update = new FeEmploymentOrder();
|
|
|
153
|
+ update.setId(order.getId());
|
|
|
154
|
+ update.setCurrentStepCode(STEP_WORK_STARTED);
|
|
|
155
|
+ update.setUpdateTime(now);
|
|
|
156
|
+ feEmploymentOrderMapper.updateById(update);
|
|
|
157
|
+ order.setCurrentStepCode(STEP_WORK_STARTED);
|
|
|
158
|
+
|
|
|
159
|
+ OrderStartWorkResponse result = buildStartWorkStatus(order, true);
|
|
|
160
|
+ result.setMessage(Boolean.TRUE.equals(status.getUnderstaffed())
|
|
|
161
|
+ ? "已确认缺编开工"
|
|
|
162
|
+ : "已确认开工");
|
|
|
163
|
+ return result;
|
|
|
164
|
+ }
|
|
|
165
|
+
|
|
|
166
|
+ private FeEmploymentOrder requireOwnedOrder(Long userId, Long orderId) {
|
|
|
167
|
+ enterpriseService.requireEnterpriseUser(userId);
|
|
|
168
|
+ FeEnterprise enterprise = enterpriseService.findEnterpriseByUserId(userId);
|
|
|
169
|
+ if (enterprise == null) {
|
|
|
170
|
+ throw new BizException(ErrorCode.BAD_REQUEST, "请先完成企业登记");
|
|
|
171
|
+ }
|
|
|
172
|
+ if (orderId == null) {
|
|
|
173
|
+ throw new BizException(ErrorCode.BAD_REQUEST, "订单 ID 无效");
|
|
|
174
|
+ }
|
|
|
175
|
+ FeEmploymentOrder order = feEmploymentOrderMapper.selectById(orderId);
|
|
|
176
|
+ if (order == null) {
|
|
|
177
|
+ throw new BizException(ErrorCode.NOT_FOUND, "用工订单不存在");
|
|
|
178
|
+ }
|
|
|
179
|
+ if (order.getEnterpriseId() == null || !order.getEnterpriseId().equals(enterprise.getId())) {
|
|
|
180
|
+ throw new BizException(ErrorCode.FORBIDDEN, "无权操作该订单");
|
|
|
181
|
+ }
|
|
|
182
|
+ return order;
|
|
|
183
|
+ }
|
|
|
184
|
+
|
|
|
185
|
+ private OrderStartWorkResponse buildStartWorkStatus(FeEmploymentOrder order, boolean forStartAction) {
|
|
|
186
|
+ Long orderId = order.getId();
|
|
|
187
|
+ int planned = order.getWorkerCount() != null && order.getWorkerCount() > 0
|
|
|
188
|
+ ? order.getWorkerCount() : 0;
|
|
|
189
|
+
|
|
|
190
|
+ List<FeWorkerRegistration> regs = feWorkerRegistrationMapper.selectList(
|
|
|
191
|
+ new LambdaQueryWrapper<FeWorkerRegistration>()
|
|
|
192
|
+ .eq(FeWorkerRegistration::getOrderId, orderId));
|
|
|
193
|
+ if (regs == null) {
|
|
|
194
|
+ regs = Collections.emptyList();
|
|
|
195
|
+ }
|
|
|
196
|
+
|
|
|
197
|
+ int approved = 0;
|
|
|
198
|
+ int signed = 0;
|
|
|
199
|
+ int pendingSign = 0;
|
|
|
200
|
+ int underReview = 0;
|
|
|
201
|
+ for (FeWorkerRegistration reg : regs) {
|
|
|
202
|
+ String st = reg.getRegStatus();
|
|
|
203
|
+ if (REG_UNDER_REVIEW.equals(st)) {
|
|
|
204
|
+ underReview++;
|
|
|
205
|
+ continue;
|
|
|
206
|
+ }
|
|
|
207
|
+ if (!REG_CONTRACT_SIGNING.equals(st) && !REG_COMPLETED.equals(st)) {
|
|
|
208
|
+ continue;
|
|
|
209
|
+ }
|
|
|
210
|
+ approved++;
|
|
|
211
|
+ FeContract contract = contractService.findActiveByRegistrationId(reg.getId());
|
|
|
212
|
+ if (contract != null && SIGN_SIGNED.equals(contract.getSignStatus())) {
|
|
|
213
|
+ signed++;
|
|
|
214
|
+ } else {
|
|
|
215
|
+ pendingSign++;
|
|
|
216
|
+ }
|
|
|
217
|
+ }
|
|
|
218
|
+
|
|
|
219
|
+ boolean allApprovedSigned = approved > 0 && pendingSign == 0;
|
|
|
220
|
+ boolean understaffed = planned > 0 && signed < planned;
|
|
|
221
|
+ boolean atSigning = STEP_CONTRACT_SIGNING.equals(order.getCurrentStepCode());
|
|
|
222
|
+ boolean canStart = atSigning && allApprovedSigned;
|
|
|
223
|
+
|
|
|
224
|
+ OrderStartWorkResponse response = new OrderStartWorkResponse();
|
|
|
225
|
+ response.setOrderId(orderId);
|
|
|
226
|
+ response.setOrderNo(order.getOrderNo());
|
|
|
227
|
+ response.setCurrentStepCode(order.getCurrentStepCode());
|
|
|
228
|
+ response.setPlannedCount(planned);
|
|
|
229
|
+ response.setApprovedCount(approved);
|
|
|
230
|
+ response.setSignedCount(signed);
|
|
|
231
|
+ response.setPendingSignCount(pendingSign);
|
|
|
232
|
+ response.setUnderReviewCount(underReview);
|
|
|
233
|
+ response.setAllApprovedSigned(allApprovedSigned);
|
|
|
234
|
+ response.setUnderstaffed(understaffed);
|
|
|
235
|
+ response.setCanStartWork(canStart);
|
|
|
236
|
+
|
|
|
237
|
+ if (STEP_WORK_STARTED.equals(order.getCurrentStepCode())
|
|
|
238
|
+ || isStepAfter(order.getCurrentStepCode(), STEP_WORK_STARTED)) {
|
|
|
239
|
+ response.setMessage("订单已进入开工阶段");
|
|
|
240
|
+ } else if (!atSigning) {
|
|
|
241
|
+ response.setMessage("订单尚未进入签约阶段");
|
|
|
242
|
+ } else if (approved == 0) {
|
|
|
243
|
+ response.setMessage("尚无已过审工人");
|
|
|
244
|
+ } else if (!allApprovedSigned) {
|
|
|
245
|
+ response.setMessage("已签 " + signed + "/" + approved + ",待签 " + pendingSign + " 人");
|
|
|
246
|
+ } else if (understaffed) {
|
|
|
247
|
+ response.setMessage("已齐签,但未招满(已签 " + signed + "/" + planned
|
|
|
248
|
+ + ")。缺编开工须传 allow_understaffed=true"
|
|
|
249
|
+ + (underReview > 0 ? ";另有 " + underReview + " 人待审" : ""));
|
|
|
250
|
+ } else {
|
|
|
251
|
+ response.setMessage(forStartAction ? "可确认开工" : "已齐签且人数达标,可确认开工"
|
|
|
252
|
+ + (underReview > 0 ? "(另有 " + underReview + " 人待审,不阻挡开工)" : ""));
|
|
|
253
|
+ }
|
|
|
254
|
+ return response;
|
|
|
255
|
+ }
|
|
|
256
|
+
|
|
|
257
|
+ private static boolean isStepAfter(String current, String target) {
|
|
|
258
|
+ if (!StringUtils.hasText(current) || !StringUtils.hasText(target)) {
|
|
|
259
|
+ return false;
|
|
|
260
|
+ }
|
|
|
261
|
+ String[] steps = {
|
|
|
262
|
+ "draft_created", "plan_confirmed", "registration", "enterprise_audit",
|
|
|
263
|
+ "contract_signing", "work_started", "settlement_pending", "settlement_paid", "completed"
|
|
|
264
|
+ };
|
|
|
265
|
+ int curIdx = -1;
|
|
|
266
|
+ int targetIdx = -1;
|
|
|
267
|
+ for (int i = 0; i < steps.length; i++) {
|
|
|
268
|
+ if (steps[i].equals(current)) {
|
|
|
269
|
+ curIdx = i;
|
|
|
270
|
+ }
|
|
|
271
|
+ if (steps[i].equals(target)) {
|
|
|
272
|
+ targetIdx = i;
|
|
|
273
|
+ }
|
|
|
274
|
+ }
|
|
|
275
|
+ return curIdx >= 0 && targetIdx >= 0 && curIdx > targetIdx;
|
|
|
276
|
+ }
|
|
|
277
|
+
|
|
88
|
278
|
private static EmploymentOrderItemResponse toItem(FeEmploymentOrder order) {
|
|
89
|
279
|
EmploymentOrderItemResponse item = new EmploymentOrderItemResponse();
|
|
90
|
280
|
item.setDraftId(order.getDraftId());
|