Эх сурвалжийг харах

夜市主PC自配送设置接口与回归修复

新增 /chanting/store/get|saveMyNightMarketSelfDelivery(token反查夜市主门店行,
前端不传mdId;反查不到拒绝 no.store.selfdelivery.market.missing 六语言)+3个测试;
补齐 th_TH 缺失的19个key并修正 no.oauth.verify.fail 多余占位符(i18n契约对齐zh_TW);
闪送DTO字段集测试补 orderVersion/payType(今晨合并遗留)。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
qmj 17 цаг өмнө
parent
commit
43c96d5572

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 14 - 0
.claude/homunculus/observations.jsonl


+ 44 - 0
ruoyi-admin/src/main/java/com/ruoyi/app/mendian/PosStoreController.java

@@ -536,6 +536,50 @@ public class PosStoreController extends BaseController {
         return success();
     }
 
+    /**
+     * (商家端 PC)读取当前登录夜市主自己的自配送配置(029)。
+     * 夜市主账号只有 userId、不持有门店 ID,后端按归属反查夜市主门店行;
+     * 非夜市主(普通商家等)反查不到门店行,统一按"未找到夜市门店"拒绝。
+     */
+    @Anonymous
+    @Auth(session = true)
+    @GetMapping("/getMyNightMarketSelfDelivery")
+    public AjaxResult getMyNightMarketSelfDelivery(@RequestHeader String token) {
+        Long userId = AuthContext.requireUserId();
+        PosStore configStore = selfDeliveryService.resolveNightMarketStoreByOwner(userId);
+        if (configStore == null) {
+            throw new ServiceException(MessageUtils.message("no.store.selfdelivery.market.missing"));
+        }
+        Map<String, Object> data = new LinkedHashMap<>();
+        data.put("enabled", Integer.valueOf(1).equals(configStore.getSelfDeliveryEnabled()));
+        data.put("mode", configStore.getSelfDeliveryMode());
+        data.put("hours", selfDeliveryService.listHours(Long.valueOf(configStore.getId())));
+        return success(data);
+    }
+
+    /**
+     * (商家端 PC)保存当前登录夜市主自己的自配送配置(029)。
+     * 配置主体由后端反查决定,前端不传也不信任 mdId;校验与覆盖式保存复用 saveConfig。
+     */
+    @Anonymous
+    @Auth(session = true)
+    @PostMapping("/saveMyNightMarketSelfDelivery")
+    public AjaxResult saveMyNightMarketSelfDelivery(@RequestHeader String token,
+                                                    @RequestBody SelfDeliveryConfigInput input) {
+        Long userId = AuthContext.requireUserId();
+        if (input == null) {
+            throw new ServiceException(MessageUtils.message("no.store.selfdelivery.hours.invalid"));
+        }
+        PosStore configStore = selfDeliveryService.resolveNightMarketStoreByOwner(userId);
+        if (configStore == null) {
+            throw new ServiceException(MessageUtils.message("no.store.selfdelivery.market.missing"));
+        }
+        // 主体由后端决定:覆盖前端传入的 mdId,防止越权写其他门店
+        input.setMdId(configStore.getId());
+        selfDeliveryService.saveConfig(input);
+        return success();
+    }
+
     //返回营业时间(按星期分组)
     private List<StoreOperatOutput> getStoreOperatOutput(List<OperatingHours> list, Long mdId){
         List<StoreOperatOutput> data= new ArrayList<>();

+ 12 - 0
ruoyi-admin/src/main/java/com/ruoyi/app/order/SelfDeliveryService.java

@@ -56,6 +56,18 @@ public class SelfDeliveryService {
                 .last("LIMIT 1"));
     }
 
+    /**
+     * 夜市主登录态反查自己的夜市主门店行(isNightMarket=1 且 user_id=登录 userId)。
+     * 夜市主账号(info_user)不持有门店 ID,PC「我的自配送」接口只能按归属反查;
+     * 查不到说明该账号没有夜市主门店行,调用方按"未找到夜市门店"提示。
+     */
+    public PosStore resolveNightMarketStoreByOwner(Long userId) {
+        return posStoreService.getOne(new LambdaQueryWrapper<PosStore>()
+                .eq(PosStore::getUserId, userId)
+                .eq(PosStore::getIsNightMarket, 1)
+                .last("LIMIT 1"));
+    }
+
     /**
      * 判定基准时刻(029):预约单(delryTime 非空)按预约送达时间段、取其开始时刻,
      * 即时单按当前时间。delryTime 格式为"yyyy-MM-dd HH:mm-HH:mm"(预约时间段,见 DateUtil.isNearOrPastStartTime);

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

@@ -341,6 +341,7 @@ address.data.invalid=Contact, address, or coordinates are invalid
 flash.delivery.rider.type.not.enabled=当前账号未开通闪送配送
 no.store.stall.selfdelivery.readonly=Phương thức giao hàng của quầy do chủ chợ đêm thiết lập thống nhất
 no.store.selfdelivery.nightmarket.only=Chỉ chủ chợ đêm mới được cấu hình tự giao hàng
+no.store.selfdelivery.market.missing=Không tìm thấy cửa hàng chợ đêm, vui lòng hoàn thành tạo chợ đêm trong quản lý cửa hàng trước
 no.store.selfdelivery.hours.invalid=Cấu hình khung giờ tự giao không hợp lệ
 no.order.selfdelivery.required=Đơn hàng này không phải là đơn hàng do cửa hàng tự giao
 no.order.selfdelivery.rider.denied=Đơn hàng này do cửa hàng tự giao, không cần tài xế

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

@@ -344,6 +344,7 @@ address.data.invalid=Contact, address, or coordinates are invalid
 flash.delivery.rider.type.not.enabled=This account is not enabled for flash delivery orders
 no.store.stall.selfdelivery.readonly=Stall delivery mode is set by the night market owner
 no.store.selfdelivery.nightmarket.only=Self-delivery can only be configured by the night market owner
+no.store.selfdelivery.market.missing=Night market store not found; please complete night market setup in store management first
 no.store.selfdelivery.hours.invalid=Invalid self-delivery time slot configuration
 no.order.selfdelivery.required=This order is not a merchant self-delivery order
 no.order.selfdelivery.rider.denied=This order is delivered by the merchant; no rider is needed

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

@@ -186,7 +186,7 @@ no.oauth.tempkey.expired=ข้อมูลรับรองการเข้
 no.oauth.token.invalid=ข้อมูลรับรองการเข้าสู่ระบบ {0} ไม่ถูกต้อง
 no.oauth.token.expired=ข้อมูลรับรองการเข้าสู่ระบบ {0} หมดอายุแล้ว
 no.oauth.audience.mismatch=การตรวจสอบข้อมูลรับรอง {0} ไม่ผ่าน
-no.oauth.verify.fail=การตรวจสอบการเข้าสู่ระบบ {0} ไม่สำเร็จ: {1}
+no.oauth.verify.fail=การตรวจสอบการเข้าสู่ระบบ {0} ไม่สำเร็จ
 
 # ตรวจสอบใบกำกับภาษีของคำสั่งซื้อ (010)
 no.invoice.choice.invalid=ประเภทใบกำกับภาษีไม่ถูกต้อง: {0}
@@ -341,3 +341,23 @@ flash.delivery.order.version.invalid=กรุณาระบุเวอร์
 flash.delivery.tip.additional.invalid=ทิปเพิ่มเติมต้องเป็นจำนวนเต็มบวกและไม่เกินวงเงินที่รองรับ
 flash.delivery.pay.type.invalid=รองรับเฉพาะการชำระเงินสดหรือโอนแบบออฟไลน์
 no.store.selfdelivery.nightmarket.only=ตั้งค่าการส่งด้วยตนเองได้เฉพาะเจ้าของตลาดกลางคืนเท่านั้น
+no.store.selfdelivery.market.missing=ไม่พบร้านค้าตลาดกลางคืน โปรดสร้างตลาดกลางคืนในการจัดการร้านค้าก่อน
+menu.copy.store.required=กรุณาระบุร้านค้าต้นทางและร้านค้าปลายทาง
+menu.copy.store.same=ร้านค้าต้นทางและร้านค้าปลายทางต้องไม่ใช่ร้านเดียวกัน
+menu.copy.strategy.invalid=วิธีการคัดลอกสินค้าไม่ถูกต้อง
+no.billing.selfdelivery.freight=ค่าจัดส่งด้วยตนเองของร้านค้า
+no.message.push.merchant.accepted.content=ร้านค้ารับออเดอร์แล้ว กำลังเตรียมอาหารของคุณ
+no.message.push.merchant.delivery.complete=ร้านค้ายืนยันจัดส่งออเดอร์ของคุณเรียบร้อยแล้ว
+no.message.push.merchant.delivery.start=ร้านค้าเริ่มจัดส่งออเดอร์ของคุณแล้ว
+no.oauth.client.config.invalid=การตั้งค่าการเข้าสู่ระบบ {0} ไม่ถูกต้อง
+no.oauth.line.config.invalid=การตั้งค่าการเข้าสู่ระบบ LINE ไม่ถูกต้อง
+no.oauth.phone.blank=กรุณาระบุหมายเลขโทรศัพท์
+no.oauth.state.invalid=ข้อมูลการเข้าสู่ระบบหมดอายุ กรุณาเข้าสู่ระบบอีกครั้ง
+no.order.not.found=ไม่พบออเดอร์
+no.order.selfdelivery.required=ออเดอร์นี้ไม่ใช่ออเดอร์จัดส่งด้วยตนเองของร้านค้า
+no.order.selfdelivery.rider.denied=ออเดอร์นี้เป็นการจัดส่งด้วยตนเองของร้านค้า ไม่ต้องใช้ไรเดอร์
+no.order.selfdelivery.status.invalid=สถานะออเดอร์ไม่อนุญาตให้ทำรายการนี้
+no.order.selfdelivery.unpaid=ออเดอร์ยังไม่ได้ชำระเงิน กรุณายืนยันการรับเงินก่อนยืนยันการจัดส่ง
+no.store.selfdelivery.hours.invalid=การตั้งค่าช่วงเวลาจัดส่งด้วยตนเองไม่ถูกต้อง
+no.store.stall.selfdelivery.readonly=วิธีการจัดส่งของแผงลอยถูกกำหนดโดยเจ้าของตลาดกลางคืน
+no.user.phone.blank=กรุณาระบุหมายเลขโทรศัพท์

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

@@ -344,6 +344,7 @@ address.data.invalid=Thông tin liên hệ, địa chỉ hoặc tọa độ khô
 flash.delivery.rider.type.not.enabled=Tài khoản này chưa mở giao hàng nhanh
 no.store.stall.selfdelivery.readonly=Phương thức giao hàng của quầy do chủ chợ đêm thiết lập thống nhất
 no.store.selfdelivery.nightmarket.only=Chỉ chủ chợ đêm mới được cấu hình tự giao hàng
+no.store.selfdelivery.market.missing=Không tìm thấy cửa hàng chợ đêm, vui lòng hoàn thành tạo chợ đêm trong quản lý cửa hàng trước
 no.store.selfdelivery.hours.invalid=Cấu hình khung giờ tự giao không hợp lệ
 no.order.selfdelivery.required=Đơn hàng này không phải là đơn hàng do cửa hàng tự giao
 no.order.selfdelivery.rider.denied=Đơn hàng này do cửa hàng tự giao, không cần tài xế

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

@@ -345,6 +345,7 @@ address.data.invalid=联系人、地址或经纬度信息无效
 flash.delivery.rider.type.not.enabled=当前账号未开通闪送配送
 no.store.stall.selfdelivery.readonly=摊位配送方式由夜市主统一设置
 no.store.selfdelivery.nightmarket.only=自配送仅夜市主可设置
+no.store.selfdelivery.market.missing=未找到夜市门店,请先在门店管理完成夜市创建
 no.store.selfdelivery.hours.invalid=自配送时段配置无效
 no.order.selfdelivery.required=该订单不是商家自配送订单
 no.order.selfdelivery.rider.denied=该订单为商家自配送订单,无需骑手配送

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

@@ -345,6 +345,7 @@ address.data.invalid=聯絡人、地址或經緯度資訊無效
 flash.delivery.rider.type.not.enabled=當前賬號未開通闁送配送
 no.store.stall.selfdelivery.readonly=攤位配送方式由夜市主統一設定
 no.store.selfdelivery.nightmarket.only=自配送僅夜市主可設定
+no.store.selfdelivery.market.missing=未找到夜市門店,請先在門店管理完成夜市創建
 no.store.selfdelivery.hours.invalid=自配送時段配置無效
 no.order.selfdelivery.required=該訂單不是商家自配送訂單
 no.order.selfdelivery.rider.denied=該訂單為商家自配送訂單,無需騎手配送

+ 3 - 2
ruoyi-admin/src/test/java/com/ruoyi/app/flashdelivery/dto/FlashDeliveryRiderOrderListViewTest.java

@@ -15,7 +15,8 @@ class FlashDeliveryRiderOrderListViewTest {
                 "quantity", "weightRange", "baseDeliveryFee", "urgentFee", "tipAmount",
                 "deliveryMode", "scheduledPickupStartAt", "scheduledPickupEndAt", "pickupAddress",
                 "pickupDetailAddress", "deliveryAddress", "deliveryDetailAddress",
-                "estimatedDurationSeconds", "amount", "currency", "deliveredAt", "createTime");
+                "estimatedDurationSeconds", "amount", "currency", "deliveredAt", "createTime",
+                "orderVersion");
         org.junit.jupiter.api.Assertions.assertEquals(expected, fields);
     }
 
@@ -29,7 +30,7 @@ class FlashDeliveryRiderOrderListViewTest {
                 "pinRequired", "pickupAddress", "pickupDetailAddress", "deliveryAddress",
                 "deliveryDetailAddress", "pickupDistanceMeters", "distanceMeters",
                 "estimatedDurationSeconds", "baseDeliveryFee", "distanceFee", "urgentFee",
-                "tipAmount", "amount", "currency", "createTime");
+                "tipAmount", "amount", "currency", "payType", "createTime");
         org.junit.jupiter.api.Assertions.assertEquals(expected, fields);
     }
 

+ 41 - 0
ruoyi-admin/src/test/java/com/ruoyi/app/mendian/PosStoreControllerSelfDeliveryTest.java

@@ -146,6 +146,47 @@ class PosStoreControllerSelfDeliveryTest {
         assertTrue(((java.util.List<?>) data.get("hours")).isEmpty());
     }
 
+    @Test
+    void myNightMarketSelfDeliveryResolvesStoreByOwner() {
+        // 夜市主登录态只有 userId,接口按归属反查门店行后返回配置
+        login(77L, "3");
+        PosStore market = store(1, 0, 1, 77L);
+        market.setSelfDeliveryEnabled(1);
+        market.setSelfDeliveryMode(1);
+        when(selfDelivery.resolveNightMarketStoreByOwner(77L)).thenReturn(market);
+        when(selfDelivery.listHours(1L)).thenReturn(new ArrayList<>());
+
+        var result = controller.getMyNightMarketSelfDelivery("t");
+        Map<String, Object> data = (Map<String, Object>) result.get("data");
+        assertEquals(Boolean.TRUE, data.get("enabled"));
+        assertEquals(1, data.get("mode"));
+    }
+
+    @Test
+    void myNightMarketSelfDeliveryRejectsUserWithoutNightMarketStore() {
+        // 普通商家反查不到夜市主门店行,读写一律拒绝
+        login(101L, "1");
+        when(selfDelivery.resolveNightMarketStoreByOwner(101L)).thenReturn(null);
+        assertThrows(ServiceException.class, () -> controller.getMyNightMarketSelfDelivery("t"));
+        assertThrows(ServiceException.class,
+                () -> controller.saveMyNightMarketSelfDelivery("t", config(1)));
+        verify(selfDelivery, never()).saveConfig(any());
+    }
+
+    @Test
+    void saveMyNightMarketSelfDeliveryOverridesClientMdId() {
+        // 配置主体由后端反查决定,前端传入的 mdId 被覆盖,防止越权写其他门店
+        login(77L, "3");
+        when(selfDelivery.resolveNightMarketStoreByOwner(77L)).thenReturn(store(1, 0, 1, 77L));
+
+        controller.saveMyNightMarketSelfDelivery("t", config(999));
+
+        org.mockito.ArgumentCaptor<SelfDeliveryConfigInput> captor =
+                org.mockito.ArgumentCaptor.forClass(SelfDeliveryConfigInput.class);
+        verify(selfDelivery).saveConfig(captor.capture());
+        assertEquals(Integer.valueOf(1), captor.getValue().getMdId());
+    }
+
     private SelfDeliveryConfigInput config(Integer mdId) {
         SelfDeliveryConfigInput input = new SelfDeliveryConfigInput();
         input.setMdId(mdId);

+ 6 - 0
specs/029-merchant-self-delivery/spec.md

@@ -16,6 +16,12 @@
 - **PC 端(foodie-store)功能先隐藏**:T034-T038 挂起不做(PC 从未开发自配送界面,无需代码改动);商家 App(外部)同步只对夜市主展示设置入口,普通商家不展示。
 - 已知缺口备查:`messages_th_TH.properties` 缺 029 的 `no.store.*`/`no.order.*` 系列 key(本变更的新 key 已补,其余历史 key 待统一补译)。
 
+### 2026-09-10 追加:夜市主 PC 设置入口(T034 夜市子集恢复)
+
+- 后端新增「我的」读写接口:`GET/POST /chanting/store/{get|save}MyNightMarketSelfDelivery`——夜市主登录态只有 userId,接口内部按 user_id+is_night_market=1 反查门店行,前端不传也不信任 mdId(save 覆盖客户端 mdId 防越权);反查不到门店行(普通商家/未建夜市门店)统一拒绝 `no.store.selfdelivery.market.missing`(六语言)。原 mdId 版接口保留。
+- PC(foodie-store):「摊位管理」菜单下新增「自配送设置」(仅 userType==3 可见),新页面 `SelfDeliverySettings.vue`(开关+全天/自定义+星期多时段,复用整体覆盖保存);i18n 四语言新增 `Aside.selfDeliverySettings` 与 `selfDelivery` 命名空间。
+- 顺带发现既有缺口:foodie-store `en.js`/`vi.js` 缺整个 `Aside` 命名空间(本次为新增菜单 key 补了只含该 key 的对象,其余历史 key 未回填);后端 `messages_th_TH.properties` 缺 029 历史 no.* keys。
+
 ## Clarifications
 
 ### Session 2026-09-08

+ 3 - 1
specs/029-merchant-self-delivery/tasks.md

@@ -118,7 +118,7 @@ description: "Task list for 029-merchant-self-delivery"
 **Independent Test**: quickstart 场景 7。
 
 - [x] T033 [P] [US6] 定位前端结构:阅读 foodie-store 门店信息页(营业时段编辑所在组件)与订单列表/详情组件、`src/lang/` 四语言文件对应对象层级,输出改动点清单(无代码改动)
-- [ ] T034 [US6] 【2026-09-10 决策:PC 端功能先隐藏,T034-T038 挂起不做】门店信息页加自配送区块:开关 + 模式(全天/自定义)+ 星期×多时段编辑(复用营业时段编辑交互),调 get/saveMdSelfDeliveryHours;摊位主只读展示"由夜市主统一配送"(foodie-store 对应门店设置组件,Python 脚本编辑保 CRLF)- [ ] T035 [P] [US6] i18n:foodie-store `src/lang/zh.js`、`tw.js`、`en.js`、`vi.js` 四文件同步加 selfDelivery.* keys(有意义英文驼峰、按对象层级插入、四文件 key 一致)- [ ] T036 [US6] 订单列表/详情:selfDelivery=1 显示"自配送"标签($t),不显示等待骑手类状态- [ ] T037 [US6] 【可裁剪:依赖 clarify Q2 端覆盖决策,未决策前跳过】PC 配送操作按钮:开始配送/确认送达调 `/system/orderShOprate/selfDeliveryStart|Complete`
+- [ ] T034 [US6] 【2026-09-10 决策:PC 普通商家功能隐藏不做;夜市主子集已由 T042 落地】门店信息页加自配送区块:开关 + 模式(全天/自定义)+ 星期×多时段编辑(复用营业时段编辑交互),调 get/saveMdSelfDeliveryHours;摊位主只读展示"由夜市主统一配送"(foodie-store 对应门店设置组件,Python 脚本编辑保 CRLF)- [ ] T035 [P] [US6] i18n:foodie-store `src/lang/zh.js`、`tw.js`、`en.js`、`vi.js` 四文件同步加 selfDelivery.* keys(有意义英文驼峰、按对象层级插入、四文件 key 一致)- [ ] T036 [US6] 订单列表/详情:selfDelivery=1 显示"自配送"标签($t),不显示等待骑手类状态- [ ] T037 [US6] 【可裁剪:依赖 clarify Q2 端覆盖决策,未决策前跳过】PC 配送操作按钮:开始配送/确认送达调 `/system/orderShOprate/selfDeliveryStart|Complete`
 - [ ] T038 [US6] 冒烟:quickstart 场景 7(配置保存回显、标识、四语言、摊位主无编辑入口)
 ---
 
@@ -184,3 +184,5 @@ description: "Task list for 029-merchant-self-delivery"
 - 不触碰废弃代码清单(research.md「涉及的废弃代码」)
 
 - [x] T041 【2026-09-10 变更】普通门店保存自配送配置改为一律拒绝:仅夜市主门店行且夜市主本人可保存;新增提示 `no.store.selfdelivery.nightmarket.only`(六语言,th_TH 顺带补本 key);测试 `PosStoreControllerSelfDeliveryTest.normalStoreCannotSaveSelfDeliveryConfig`(原 normalStoreSaveGoesThroughStoreAccess 断言翻转)
+
+- [x] T042 【2026-09-10 追加】夜市主 PC 自配送设置入口:后端 my 读写接口(token 反查门店行+六语言拒绝文案+3 个控制器测试);foodie-store 新页面/路由/菜单/API/四语言 i18n

Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно