|
|
@@ -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
|
*/
|