Explorar el Código

闪送订单增加支付方式字段

payType 4=现金(默认)/6=线下转账,与主订单系统值域一致;
下单时选定锁定,详情与骑手列表返回;sql.md 记录 DDL 待手动执行。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
qmj hace 1 día
padre
commit
f6c748550b

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 11 - 0
.claude/homunculus/observations.jsonl


+ 3 - 1
docs/flash-delivery-app-api.md

@@ -335,7 +335,7 @@ App 完整详情使用以下订单字段,响应的 `data` 直接返回这些
 
 用户列表摘要固定字段为:`id,orderNo,serviceType,deliveryType,status,packageType,quantity,weightRange,deliveryMode,scheduledPickupStartAt,scheduledPickupEndAt,pickupAddress,pickupDetailAddress,deliveryAddress,deliveryDetailAddress,estimatedDurationSeconds,baseDeliveryFee,urgentFee,tipAmount,amount,currency,deliveredAt,createTime`。用户列表不返回联系人、电话、精确坐标、照片、日志或内部计价审计字段。
 
-骑手列表摘要固定字段为:`id,orderNo,serviceType,deliveryType,status,packageType,quantity,weightRange,specification,deliveryMode,scheduledPickupStartAt,scheduledPickupEndAt,pinRequired,pickupAddress,pickupDetailAddress,deliveryAddress,deliveryDetailAddress,pickupDistanceMeters,distanceMeters,estimatedDurationSeconds,baseDeliveryFee,distanceFee,urgentFee,tipAmount,amount,currency,createTime`。`newTask` 分页响应额外返回 `nearbyTaskCount` 和 `highestOrderAmount`,不返回内部加急比例、最低加急费或骑手收入字段。
+骑手列表摘要固定字段为:`id,orderNo,serviceType,deliveryType,status,packageType,quantity,weightRange,specification,deliveryMode,scheduledPickupStartAt,scheduledPickupEndAt,pinRequired,pickupAddress,pickupDetailAddress,deliveryAddress,deliveryDetailAddress,pickupDistanceMeters,distanceMeters,estimatedDurationSeconds,baseDeliveryFee,distanceFee,urgentFee,tipAmount,amount,currency,payType,createTime`。`newTask` 分页响应额外返回 `nearbyTaskCount` 和 `highestOrderAmount`,不返回内部加急比例、最低加急费或骑手收入字段。
 
 ### 6.3 App 完整详情对象
 
@@ -367,6 +367,7 @@ App 完整详情使用以下订单字段,响应的 `data` 直接返回这些
   "tipAmount": 5,
   "amount": 92,
   "currency": "TWD",
+  "payType": "4",
   "userNote": null,
   "acceptedAt": "2026-09-01T10:20:00+08:00",
   "pickedUpAt": null,
@@ -624,6 +625,7 @@ POST /system/flashDelivery/orders
 | `quotedUrgentFee` | integer | 是 | 报价响应的加急费 |
 | `quotedAmount` | integer | 是 | 报价响应的总金额 |
 | `pinRequired` | boolean | 否 | 不传时默认为 `true` |
+| `payType` | string | 否 | 支付方式:`4`=现金(默认)、`6`=线下转账。闪送款项直达骑手,仅允许线下方式,下单后锁定 |
 | `senderImageUrls` | string[] | 否 | 寄件图片,最多 9 张 |
 | `userNote` | string | 否 | 最长 500 字符 |
 

+ 3 - 0
ruoyi-admin/src/main/java/com/ruoyi/app/flashdelivery/dto/FlashDeliveryCreateRequest.java

@@ -36,6 +36,9 @@ public class FlashDeliveryCreateRequest extends FlashDeliveryQuoteRequest {
     /** 是否启用四位交付 PIN,选填;不传时默认为 true。 */
     private Boolean pinRequired;
 
+    /** 支付方式,选填;4=现金(默认)、6=线下转账。闪送款项直达骑手,仅允许线下方式。 */
+    private String payType;
+
     /** 寄件图片地址,选填,最多 9 张;每项最长 1000 个字符,允许相对路径。 */
     private List<String> senderImageUrls;
 

+ 2 - 0
ruoyi-admin/src/main/java/com/ruoyi/app/flashdelivery/dto/FlashDeliveryOrderView.java

@@ -31,6 +31,8 @@ public class FlashDeliveryOrderView {
     private Long tipAmount;
     private Long amount;
     private String currency;
+    /** 支付方式:4=现金、6=线下转账;款项直达骑手,用户与骑手详情均返回。 */
+    private String payType;
     private String userNote;
     private Date acceptedAt;
     private Date pickedUpAt;

+ 2 - 0
ruoyi-admin/src/main/java/com/ruoyi/app/flashdelivery/dto/FlashDeliveryRiderOrderListView.java

@@ -33,5 +33,7 @@ public class FlashDeliveryRiderOrderListView {
     private Long tipAmount;
     private Long amount;
     private String currency;
+    /** 支付方式:4=现金、6=线下转账;骑手抢单前需知道如何收钱。 */
+    private String payType;
     private Date createTime;
 }

+ 15 - 0
ruoyi-admin/src/main/java/com/ruoyi/app/flashdelivery/service/FlashDeliveryApplicationService.java

@@ -51,6 +51,9 @@ public class FlashDeliveryApplicationService {
     private static final Set<String> WEIGHT_RANGES = Set.of("UP_TO_5_KG", "OVER_5_TO_10_KG",
             "OVER_10_TO_15_KG", "OVER_15_TO_20_KG");
     private static final Set<String> DELIVERY_MODES = Set.of("NOW", "SCHEDULED");
+    /** 支付方式取值与主订单系统同域:4=现金、6=线下转账(027 线下转账支付)。 */
+    private static final String PAY_TYPE_CASH = "4";
+    private static final String PAY_TYPE_TRANSFER = "6";
     private static final DateTimeFormatter PRICING_TIME_FORMAT = DateTimeFormatter.ofPattern("HH:mm");
     private static final ZoneId TAIPEI = ZoneId.of("Asia/Taipei");
     private static final int MAX_DISTANCE_METERS = 40_000;
@@ -152,6 +155,7 @@ public class FlashDeliveryApplicationService {
         String deliveryMode = normalizeDeliveryMode(request.getDeliveryMode());
         validateSchedule(deliveryMode, request.getScheduledPickupStartAt(), request.getScheduledPickupEndAt(), now);
         boolean pinRequired = request.getPinRequired() == null || request.getPinRequired();
+        String payType = normalizePayType(request.getPayType());
         List<String> senderImages = validateProofUrls(request.getSenderImageUrls(), false);
         FlashDeliveryOrder order = new FlashDeliveryOrder();
         order.setOrderNo("FD" + UUID.randomUUID().toString().replace("-", "").substring(0, 24).toUpperCase(Locale.ROOT));
@@ -177,6 +181,7 @@ public class FlashDeliveryApplicationService {
         order.setEstimatedDurationSeconds(quote.route().durationSeconds());
         order.setAmount(quote.breakdown().getAmount());
         order.setCurrency("TWD");
+        order.setPayType(payType);
         order.setPricingId(quote.pricing().getId());
         order.setPricingVersion(quote.pricing().getConfigVersion());
         order.setPricingStartTime(quote.pricing().getStartTime());
@@ -801,6 +806,7 @@ public class FlashDeliveryApplicationService {
         view.setPickupDetailAddress(order.getPickupAddressDetail());
         view.setDeliveryAddress(order.getDeliveryAddress());
         view.setDeliveryDetailAddress(order.getDeliveryAddressDetail());
+        view.setPayType(order.getPayType());
         view.setPickupDistanceMeters(distanceMeters(longitude, latitude,
                 order.getPickupLongitude(), order.getPickupLatitude()));
         view.setDistanceMeters(order.getDistanceMeters());
@@ -878,6 +884,7 @@ public class FlashDeliveryApplicationService {
         view.setTipAmount(order.getTipAmount());
         view.setAmount(order.getAmount());
         view.setCurrency(order.getCurrency());
+        view.setPayType(order.getPayType());
         view.setUserNote(includeSensitiveFields ? order.getUserNote() : null);
         view.setAcceptedAt(order.getAcceptedAt());
         view.setPickedUpAt(order.getPickedUpAt());
@@ -1039,6 +1046,14 @@ public class FlashDeliveryApplicationService {
         }
     }
 
+    /** 支付方式与主订单系统同域(4=现金、6=线下转账);闪送款项直达骑手,仅允许线下方式,缺省现金,下单后锁定。 */
+    private String normalizePayType(String payType) {
+        if (!hasText(payType)) return PAY_TYPE_CASH;
+        String trimmed = payType.trim();
+        if (PAY_TYPE_CASH.equals(trimmed) || PAY_TYPE_TRANSFER.equals(trimmed)) return trimmed;
+        throw fail("flash.delivery.pay.type.invalid");
+    }
+
     private String pricingTargetTime(Date date) {
         return PRICING_TIME_FORMAT.format(Instant.ofEpochMilli(date.getTime())
                 .atZone(TAIPEI).toLocalTime());

+ 1 - 0
ruoyi-admin/src/main/resources/i18n/messages.properties

@@ -335,3 +335,4 @@ flash.delivery.rider.exclusive.conflict=The rider has an active exclusive delive
 flash.delivery.edit.not.allowed=仅待接单且未分配骑手的订单可以修改
 flash.delivery.order.version.invalid=请提供有效的订单版本
 flash.delivery.tip.additional.invalid=追加小费必须为正整数且不能超出金额范围
+flash.delivery.pay.type.invalid=Chiỉ hỗ trợ thanh toán bằng tiền mặt hoặc chuyển khoản ngoại tuyến

+ 1 - 0
ruoyi-admin/src/main/resources/i18n/messages_en_US.properties

@@ -338,3 +338,4 @@ flash.delivery.rider.exclusive.conflict=The rider has an active exclusive delive
 flash.delivery.edit.not.allowed=Only orders awaiting acceptance with no assigned rider can be edited
 flash.delivery.order.version.invalid=Please provide a valid order version
 flash.delivery.tip.additional.invalid=Additional tip must be a positive integer within the supported amount range
+flash.delivery.pay.type.invalid=Payment method must be cash or offline transfer

+ 1 - 0
ruoyi-admin/src/main/resources/i18n/messages_th_TH.properties

@@ -339,3 +339,4 @@ flash.delivery.rider.exclusive.conflict=ไรเดอร์มีงานจ
 flash.delivery.edit.not.allowed=แก้ไขได้เฉพาะคำสั่งซื้อที่รอรับงานและยังไม่มีไรเดอร์
 flash.delivery.order.version.invalid=กรุณาระบุเวอร์ชันคำสั่งซื้อที่ถูกต้อง
 flash.delivery.tip.additional.invalid=ทิปเพิ่มเติมต้องเป็นจำนวนเต็มบวกและไม่เกินวงเงินที่รองรับ
+flash.delivery.pay.type.invalid=รองรับเฉพาะการชำระเงินสดหรือโอนแบบออฟไลน์

+ 1 - 0
ruoyi-admin/src/main/resources/i18n/messages_vi.properties

@@ -338,3 +338,4 @@ flash.delivery.rider.exclusive.conflict=Tài xế đang có nhiệm vụ giao h
 flash.delivery.edit.not.allowed=Chỉ có thể sửa đơn đang chờ nhận và chưa có tài xế
 flash.delivery.order.version.invalid=Vui lòng cung cấp phiên bản đơn hàng hợp lệ
 flash.delivery.tip.additional.invalid=Tiền boa thêm phải là số nguyên dương trong phạm vi số tiền được hỗ trợ
+flash.delivery.pay.type.invalid=Chiỉ hỗ trợ thanh toán bằng tiền mặt hoặc chuyển khoản ngoại tuyến

+ 1 - 0
ruoyi-admin/src/main/resources/i18n/messages_zh_CN.properties

@@ -339,3 +339,4 @@ flash.delivery.rider.exclusive.conflict=骑手存在进行中的独占配送任
 flash.delivery.edit.not.allowed=仅待接单且未分配骑手的订单可以修改
 flash.delivery.order.version.invalid=请提供有效的订单版本
 flash.delivery.tip.additional.invalid=追加小费必须为正整数且不能超出金额范围
+flash.delivery.pay.type.invalid=支付方式仅支持现金或线下转账

+ 1 - 0
ruoyi-admin/src/main/resources/i18n/messages_zh_TW.properties

@@ -339,3 +339,4 @@ flash.delivery.rider.exclusive.conflict=騎手存在進行中的獨佔配送任
 flash.delivery.edit.not.allowed=僅待接單且未分配騎手的訂單可以修改
 flash.delivery.order.version.invalid=請提供有效的訂單版本
 flash.delivery.tip.additional.invalid=追加小費必須為正整數且不能超出金額範圍
+flash.delivery.pay.type.invalid=支付方式僅支援現金或線下轉帳

+ 40 - 0
ruoyi-admin/src/test/java/com/ruoyi/app/flashdelivery/service/FlashDeliveryApplicationServiceTest.java

@@ -61,6 +61,7 @@ import static org.mockito.ArgumentMatchers.isNull;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.mockStatic;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 import static org.mockito.Mockito.doAnswer;
@@ -822,6 +823,45 @@ class FlashDeliveryApplicationServiceTest {
         verify(fixture.orderMapper, never()).insert(any(FlashDeliveryOrder.class));
     }
 
+    @Test
+    void createDefaultsPayTypeToCashAndStoresTransferSelection() {
+        Fixture fixture = new Fixture();
+        when(fixture.pricingMapper.selectAtTime(anyString())).thenReturn(List.of(pricing()));
+        when(fixture.routeService.calculate(any(), any())).thenReturn(new RouteDistance(3200, 600, "ROUTE"));
+        doAnswer(invocation -> {
+            FlashDeliveryOrder order = invocation.getArgument(0);
+            order.setId(99L);
+            return 1;
+        }).when(fixture.orderMapper).insert(any(FlashDeliveryOrder.class));
+        when(fixture.imageMapper.selectList(any(Wrapper.class))).thenReturn(List.of());
+        when(fixture.logMapper.selectList(any(Wrapper.class))).thenReturn(List.of());
+
+        var defaultResult = fixture.service.create(7L, createRequest());
+        FlashDeliveryCreateRequest transfer = createRequest();
+        transfer.setPayType("6");
+        var transferResult = fixture.service.create(7L, transfer);
+
+        ArgumentCaptor<FlashDeliveryOrder> saved = ArgumentCaptor.forClass(FlashDeliveryOrder.class);
+        verify(fixture.orderMapper, times(2)).insert(saved.capture());
+        assertEquals("4", saved.getAllValues().get(0).getPayType());
+        assertEquals("6", saved.getAllValues().get(1).getPayType());
+        assertEquals("4", defaultResult.getPayType());
+        assertEquals("6", transferResult.getPayType());
+    }
+
+    @Test
+    void createRejectsUnsupportedPayType() {
+        Fixture fixture = new Fixture();
+        when(fixture.pricingMapper.selectAtTime(anyString())).thenReturn(List.of(pricing()));
+        when(fixture.routeService.calculate(any(), any())).thenReturn(new RouteDistance(3200, 600, "ROUTE"));
+        FlashDeliveryCreateRequest request = createRequest();
+        request.setPayType("2");
+
+        assertThrows(ServiceException.class, () -> fixture.service.create(7L, request));
+
+        verify(fixture.orderMapper, never()).insert(any(FlashDeliveryOrder.class));
+    }
+
     @Test
     void quoteValidatesDeliveryLevelAndCompleteItemInformation() {
         Fixture fixture = new Fixture();

+ 2 - 0
ruoyi-system/src/main/java/com/ruoyi/system/domain/flash/FlashDeliveryOrder.java

@@ -68,6 +68,8 @@ public class FlashDeliveryOrder {
     /** 下单时的金额和计价配置快照,后续后台调价不会修改这些字段。 */
     private Long amount;
     private String currency;
+    /** 支付方式与主订单系统同域:4=现金、6=线下转账;闪送款项直达骑手,仅允许线下方式,下单时锁定。 */
+    private String payType;
     private Long pricingId;
     private Integer pricingVersion;
     private String pricingStartTime;

+ 1 - 0
ruoyi-system/src/main/resources/mapper/flash/FlashDeliveryOrderMapper.xml

@@ -43,6 +43,7 @@
         <result property="estimatedDurationSeconds" column="estimated_duration_seconds"/>
         <result property="amount" column="amount"/>
         <result property="currency" column="currency"/>
+        <result property="payType" column="pay_type"/>
         <result property="pricingId" column="pricing_id"/>
         <result property="pricingVersion" column="pricing_version"/>
         <result property="pricingStartTime" column="pricing_start_time"/>

+ 9 - 0
updatesql/sql.md

@@ -1521,3 +1521,12 @@ ALTER TABLE flash_delivery_order
   CHANGE COLUMN package_size weight_range VARCHAR(24) NOT NULL COMMENT '物品重量范围',
   DROP COLUMN total_weight_kg;
 ```
+
+## 2026-09-10 闪送订单支付方式(024-flash-delivery)
+
+> 用途:闪送款项直达骑手,订单补记支付方式(4=现金、6=线下转账,与主订单系统值域一致);脚本只记录,由开发者手动执行。
+
+```sql
+ALTER TABLE flash_delivery_order
+  ADD COLUMN pay_type VARCHAR(4) NOT NULL DEFAULT '4' COMMENT '支付方式:4=现金,6=线下转账(下单时锁定)' AFTER currency;
+```

Algunos archivos no se mostraron porque demasiados archivos cambiaron en este cambio