Przeglądaj źródła

开票功能调整

qmj 1 miesiąc temu
rodzic
commit
e71d9480cd

+ 8 - 0
.claude/homunculus/observations.jsonl

@@ -3358,3 +3358,11 @@ plus.annotation.IdType;\\nimport com.baomidou.mybatisplus.annotation.TableField;
 {"timestamp":"2026-07-28T02:20:37Z","event":"parse_error","raw":"{\"session_id\":\"6fc6b65e-264d-4ee5-a2d3-a034ea377a4b\",\"transcript_path\":\"C:\\\\Users\\\\qmj\\\\.claude\\\\projects\\\\E--QtwCode-foodie-foodie-server\\\\6fc6b65e-264d-4ee5-a2d3-a034ea377a4b.jsonl\",\"cwd\":\"E:\\\\QtwCode\\\\foodie\\\\foodie_server\",\"prompt_id\":\"a1c7ae2a-2f30-4979-bfa7-a1621eacc6c1\",\"permission_mode\":\"bypassPermissions\",\"effort\":{\"level\":\"high\"},\"hook_event_name\":\"PostToolUse\",\"tool_name\":\"TaskUpdate\",\"tool_input\":{\"taskId\":\"9\",\"status\":\"completed\"},\"tool_response\":{\"success\":true,\"taskId\":\"9\",\"updatedFields\":[\"status\"],\"statusChange\":{\"from\":\"in_progress\",\"to\":\"completed\"}},\"tool_use_id\":\"call_5cde86bb2bd44eb0bcdaf0f6\",\"duration_ms\":31}\n"}
 {"timestamp":"2026-07-28T02:20:37Z","event":"parse_error","raw":"{\"session_id\":\"6fc6b65e-264d-4ee5-a2d3-a034ea377a4b\",\"transcript_path\":\"C:\\\\Users\\\\qmj\\\\.claude\\\\projects\\\\E--QtwCode-foodie-foodie-server\\\\6fc6b65e-264d-4ee5-a2d3-a034ea377a4b.jsonl\",\"cwd\":\"E:\\\\QtwCode\\\\foodie\\\\foodie_server\",\"prompt_id\":\"a1c7ae2a-2f30-4979-bfa7-a1621eacc6c1\",\"permission_mode\":\"bypassPermissions\",\"effort\":{\"level\":\"high\"},\"hook_event_name\":\"PostToolUse\",\"tool_name\":\"Grep\",\"tool_input\":{\"pattern\":\"issue_triggered|issueTriggered\",\"path\":\"E:\\\\QtwCode\\\\foodie\\\\foodie_server\\\\specs\\\\010-order-invoice\",\"output_mode\":\"files_with_matches\"},\"tool_response\":{\"mode\":\"files_with_matches\",\"filenames\":[\"specs\\\\010-order-invoice\\\\data-model.md\",\"specs\\\\010-order-invoice\\\\auto-invoice-design.md\",\"specs\\\\010-order-invoice\\\\tasks.md\",\"specs\\\\010-order-invoice\\\\plan.md\",\"specs\\\\010-order-invoice\\\\spec.md\"],\"numFiles\":5,\"totalFiles\":5},\"tool_use_id\":\"call_5a3fca9d99a642f495ad6987\",\"duration_ms\":177}\n"}
 {"timestamp":"2026-07-28T02:20:38Z","event":"parse_error","raw":"{\"session_id\":\"6fc6b65e-264d-4ee5-a2d3-a034ea377a4b\",\"transcript_path\":\"C:\\\\Users\\\\qmj\\\\.claude\\\\projects\\\\E--QtwCode-foodie-foodie-server\\\\6fc6b65e-264d-4ee5-a2d3-a034ea377a4b.jsonl\",\"cwd\":\"E:\\\\QtwCode\\\\foodie\\\\foodie_server\",\"prompt_id\":\"a1c7ae2a-2f30-4979-bfa7-a1621eacc6c1\",\"permission_mode\":\"bypassPermissions\",\"effort\":{\"level\":\"high\"},\"hook_event_name\":\"PostToolUse\",\"tool_name\":\"Grep\",\"tool_input\":{\"pattern\":\"issue_triggered|invoice_choice|DROP COLUMN\",\"path\":\"E:\\\\QtwCode\\\\foodie\\\\foodie_server\\\\updatesql\\\\sql.md\",\"output_mode\":\"content\",\"-n\":true},\"tool_response\":{\"mode\":\"content\",\"numFiles\":0,\"filenames\":[],\"content\":\"16:-- ALTER TABLE pos_order DROP COLUMN dining_status;\\n428:> `pos_order_invoice` \u9354\udca0 `invoice_choice`\u951b\u581c\u7b05\u9357\u66df\u5270\u9365\u6485\u7d1a+ `issue_triggered`\u951b\u581f\u69f8\u935a\ufe40\u51e1\u7459\ufe40\u5f42\u5bee\udc80\u7ec1\udca8 0/1\u951b\u5c7c\u7b0c invoice_status \u9419\udcac\u7ed4\u5b36\u7d30\u6d93\u5b2a\u5d1f\u93b9\u66e1\u5e4f\u93b0\u5fd3\u6d58=0\u9286\u4f78\u56ad\u6901\u612c\u7d11\u7ec1\u3126\u7966\u7ecb\u5b2b\u58bd\u741b\u5c83\u7e43=1\u951b\u6d9a\udcae\uff04\u608a\u7ed4\udcaf\u9286\u5c83\udcae\u3220\u5d1f\u9359\u6220\u30a8\u7ee0\uff04\u608a\u9286\u5d86\u5bdc `issue_triggered=1` \u7edb\u6da2\udc80\u591b\u7d1d\u93ba\u6397\u6ace\u93c8\udcaa\u7459\ufe40\u5f42\u9428\u52ec\u5270\u9365\u6350\udca1\u5c8b\u7d1a\u9286\u4fd3n434:  DROP COLUMN invoice_choice,\\n435:  DROP COLUMN carrier_type,\\n436:  DROP COLUMN carrier_num,\\n437"}
+{"timestamp":"2026-07-28T03:10:00Z","event":"parse_error","raw":"{\"session_id\":\"6fc6b65e-264d-4ee5-a2d3-a034ea377a4b\",\"transcript_path\":\"C:\\\\Users\\\\qmj\\\\.claude\\\\projects\\\\E--QtwCode-foodie-foodie-server\\\\6fc6b65e-264d-4ee5-a2d3-a034ea377a4b.jsonl\",\"cwd\":\"E:\\\\QtwCode\\\\foodie\\\\foodie_server\",\"prompt_id\":\"5cc8075a-36ac-4833-97e3-e5ac5e2821d3\",\"permission_mode\":\"bypassPermissions\",\"effort\":{\"level\":\"high\"},\"hook_event_name\":\"PreToolUse\",\"tool_name\":\"Read\",\"tool_input\":{\"file_path\":\"E:\\\\QtwCode\\\\foodie\\\\foodie_server\\\\ruoyi-admin\\\\src\\\\main\\\\java\\\\com\\\\ruoyi\\\\app\\\\order\\\\OrderInvoiceService.java\",\"offset\":535,\"limit\":210},\"tool_use_id\":\"call_689fa3c4c98f4dd79ff6051d\"}\n"}
+{"timestamp":"2026-07-28T03:10:02Z","event":"parse_error","raw":"{\"session_id\":\"6fc6b65e-264d-4ee5-a2d3-a034ea377a4b\",\"transcript_path\":\"C:\\\\Users\\\\qmj\\\\.claude\\\\projects\\\\E--QtwCode-foodie-foodie-server\\\\6fc6b65e-264d-4ee5-a2d3-a034ea377a4b.jsonl\",\"cwd\":\"E:\\\\QtwCode\\\\foodie\\\\foodie_server\",\"prompt_id\":\"5cc8075a-36ac-4833-97e3-e5ac5e2821d3\",\"permission_mode\":\"bypassPermissions\",\"effort\":{\"level\":\"high\"},\"hook_event_name\":\"PostToolUse\",\"tool_name\":\"Read\",\"tool_input\":{\"file_path\":\"E:\\\\QtwCode\\\\foodie\\\\foodie_server\\\\ruoyi-admin\\\\src\\\\main\\\\java\\\\com\\\\ruoyi\\\\app\\\\order\\\\OrderInvoiceService.java\",\"offset\":535,\"limit\":210},\"tool_response\":{\"type\":\"text\",\"file\":{\"filePath\":\"E:\\\\QtwCode\\\\foodie\\\\foodie_server\\\\ruoyi-admin\\\\src\\\\main\\\\java\\\\com\\\\ruoyi\\\\app\\\\order\\\\OrderInvoiceService.java\",\"content\":\"        }\\n        PosStoreEzpay ez = posStoreEzpayMapper.selectOne(\\n                new LambdaQueryWrapper<PosStoreEzpay>().eq(PosStoreEzpay::getStoreId, storeId));\\n        if (ez == null || !Integer.valueOf(2).equals(ez.getEzpayStatus())\\n                |"}
+{"timestamp":"2026-07-28T03:12:34Z","event":"parse_error","raw":"{\"session_id\":\"6fc6b65e-264d-4ee5-a2d3-a034ea377a4b\",\"transcript_path\":\"C:\\\\Users\\\\qmj\\\\.claude\\\\projects\\\\E--QtwCode-foodie-foodie-server\\\\6fc6b65e-264d-4ee5-a2d3-a034ea377a4b.jsonl\",\"cwd\":\"E:\\\\QtwCode\\\\foodie\\\\foodie_server\",\"prompt_id\":\"5cc8075a-36ac-4833-97e3-e5ac5e2821d3\",\"permission_mode\":\"bypassPermissions\",\"effort\":{\"level\":\"high\"},\"hook_event_name\":\"PreToolUse\",\"tool_name\":\"Write\",\"tool_input\":{\"file_path\":\"E:\\\\QtwCode\\\\foodie\\\\foodie_server\\\\_patch_email.py\",\"content\":\"# -*- coding: utf-8 -*-\\ndef patch_pair(path, pairs):\\n    with open(path, \\\"r\\\", encoding=\\\"utf-8\\\") as f:\\n        c = f.read()\\n    for old, new in pairs:\\n        n = c.count(old)\\n        assert n == 1, f\\\"{path}: {n} for {old[:70]!r}\\\"\\n        c = c.replace(old, new)\\n    with open(path, \\\"w\\\", encoding=\\\"utf-8\\\", newline=\\\"\\\\r\\\\n\\\") as f:\\n        f.write(c)\\n    print(f\\\"OK {path} ({len(pairs)})\\\")\\n\\n# 1) OrderCreateInput: \u9354\udca0 buyerEmail \u934f\u30e5\u5f2c\\npatch_pair(\\\"ruoyi-admin/src/main/java/com/ruoyi/app/order/d"}
+{"timestamp":"2026-07-28T03:12:35Z","event":"parse_error","raw":"{\"session_id\":\"6fc6b65e-264d-4ee5-a2d3-a034ea377a4b\",\"transcript_path\":\"C:\\\\Users\\\\qmj\\\\.claude\\\\projects\\\\E--QtwCode-foodie-foodie-server\\\\6fc6b65e-264d-4ee5-a2d3-a034ea377a4b.jsonl\",\"cwd\":\"E:\\\\QtwCode\\\\foodie\\\\foodie_server\",\"prompt_id\":\"5cc8075a-36ac-4833-97e3-e5ac5e2821d3\",\"permission_mode\":\"bypassPermissions\",\"effort\":{\"level\":\"high\"},\"hook_event_name\":\"PostToolUse\",\"tool_name\":\"Write\",\"tool_input\":{\"file_path\":\"E:\\\\QtwCode\\\\foodie\\\\foodie_server\\\\_patch_email.py\",\"content\":\"# -*- coding: utf-8 -*-\\ndef patch_pair(path, pairs):\\n    with open(path, \\\"r\\\", encoding=\\\"utf-8\\\") as f:\\n        c = f.read()\\n    for old, new in pairs:\\n        n = c.count(old)\\n        assert n == 1, f\\\"{path}: {n} for {old[:70]!r}\\\"\\n        c = c.replace(old, new)\\n    with open(path, \\\"w\\\", encoding=\\\"utf-8\\\", newline=\\\"\\\\r\\\\n\\\") as f:\\n        f.write(c)\\n    print(f\\\"OK {path} ({len(pairs)})\\\")\\n\\n# 1) OrderCreateInput: \u9354\udca0 buyerEmail \u934f\u30e5\u5f2c\\npatch_pair(\\\"ruoyi-admin/src/main/java/com/ruoyi/app/order/"}
+{"timestamp":"2026-07-28T03:13:07Z","event":"parse_error","raw":"{\"session_id\":\"6fc6b65e-264d-4ee5-a2d3-a034ea377a4b\",\"transcript_path\":\"C:\\\\Users\\\\qmj\\\\.claude\\\\projects\\\\E--QtwCode-foodie-foodie-server\\\\6fc6b65e-264d-4ee5-a2d3-a034ea377a4b.jsonl\",\"cwd\":\"E:\\\\QtwCode\\\\foodie\\\\foodie_server\",\"prompt_id\":\"5cc8075a-36ac-4833-97e3-e5ac5e2821d3\",\"permission_mode\":\"bypassPermissions\",\"effort\":{\"level\":\"high\"},\"hook_event_name\":\"PreToolUse\",\"tool_name\":\"Bash\",\"tool_input\":{\"command\":\"cd \\\"E:/QtwCode/foodie/foodie_server\\\" && python _patch_email.py && rm _patch_email.py && JAVA_HOME='C:\\\\Users\\\\qmj\\\\.jdks\\\\graalvm-jdk-21.0.7' mvn -pl ruoyi-admin -am compile -DskipTests -q 2>&1 | tail -30 && echo \\\"BUILD_DONE\\\"\",\"timeout\":420000,\"description\":\"\u93b5\u0446\udca1\udc8c COMPANY \u95ad\udcae\u7ee0\u8fab\u02c9\u6d93\u4f75\udc80\u4f79\u7afb\u941e\u55d7\u82df JDK21 \u7f02\u682c\u7627\"},\"tool_use_id\":\"call_4b2bc3659a904a2fbcb7cfed\"}\n"}
+{"timestamp":"2026-07-28T03:13:31Z","event":"parse_error","raw":"{\"session_id\":\"6fc6b65e-264d-4ee5-a2d3-a034ea377a4b\",\"transcript_path\":\"C:\\\\Users\\\\qmj\\\\.claude\\\\projects\\\\E--QtwCode-foodie-foodie-server\\\\6fc6b65e-264d-4ee5-a2d3-a034ea377a4b.jsonl\",\"cwd\":\"E:\\\\QtwCode\\\\foodie\\\\foodie_server\",\"prompt_id\":\"5cc8075a-36ac-4833-97e3-e5ac5e2821d3\",\"permission_mode\":\"bypassPermissions\",\"effort\":{\"level\":\"high\"},\"hook_event_name\":\"PostToolUse\",\"tool_name\":\"Bash\",\"tool_input\":{\"command\":\"cd \\\"E:/QtwCode/foodie/foodie_server\\\" && python _patch_email.py && rm _patch_email.py && JAVA_HOME='C:\\\\Users\\\\qmj\\\\.jdks\\\\graalvm-jdk-21.0.7' mvn -pl ruoyi-admin -am compile -DskipTests -q 2>&1 | tail -30 && echo \\\"BUILD_DONE\\\"\",\"timeout\":420000,\"description\":\"\u93b5\u0446\udca1\udc8c COMPANY \u95ad\udcae\u7ee0\u8fab\u02c9\u6d93\u4f75\udc80\u4f79\u7afb\u941e\u55d7\u82df JDK21 \u7f02\u682c\u7627\"},\"tool_response\":{\"stdout\":\"OK ruoyi-admin/src/main/java/com/ruoyi/app/order/dto/OrderCreateInput.java (1)\\r\\nOK ruoyi-admin/src/main/java/com/ruoyi/app/order/UserOrderController.java (2)\\r\\nOK ruoyi-admin/src/main/java/com/ruoyi/app/order/OrderInvoiceService.java (3)\\r\\n=== COMPA"}
+{"timestamp":"2026-07-28T03:20:34Z","event":"parse_error","raw":"{\"session_id\":\"6fc6b65e-264d-4ee5-a2d3-a034ea377a4b\",\"transcript_path\":\"C:\\\\Users\\\\qmj\\\\.claude\\\\projects\\\\E--QtwCode-foodie-foodie-server\\\\6fc6b65e-264d-4ee5-a2d3-a034ea377a4b.jsonl\",\"cwd\":\"E:\\\\QtwCode\\\\foodie\\\\foodie_server\",\"prompt_id\":\"98c69897-8f9d-48db-924e-ba3a98ca1835\",\"permission_mode\":\"bypassPermissions\",\"effort\":{\"level\":\"high\"},\"hook_event_name\":\"PreToolUse\",\"tool_name\":\"Bash\",\"tool_input\":{\"command\":\"cd \\\"E:/QtwCode/foodie/foodie_server\\\" && python << 'PYEOF'\\n# -*- coding: utf-8 -*-\\npath = \\\"specs/010-order-invoice/auto-invoice-design.md\\\"\\nwith open(path, \\\"rb\\\") as f:\\n    raw = f.read()\\nnl = \\\"\\\\r\\\\n\\\" if b\\\"\\\\r\\\\n\\\" in raw else \\\"\\\\n\\\"\\nc = raw.decode(\\\"utf-8\\\").replace(\\\"\\\\r\\\\n\\\", \\\"\\\\n\\\")\\nanchor = \\\"| \u934f\udcac\u9359\u7a3f\u5f42\u7ec1\udca8 | B2B | BuyerUBN + BuyerName\\\"\\nidx = c.find(anchor)\\nassert idx >= 0, \\\"anchor not found\\\"\\nls = c.rfind(\\\"\\\\n\\\", 0, idx) + 1\\nle = c.find(\\\"\\\\n\\\", idx)\\nnew_line = \\\"| \u934f\udcac\u9359\u7a3f\u5f42\u7ec1\udca8 | B2B | BuyerUBN + BuyerName(\u934f\udcac\u9359\u7a3f\u6095) + BuyerEmail | Y |\\\"\\nc = c[:ls] + new_line "}
+{"timestamp":"2026-07-28T03:20:36Z","event":"parse_error","raw":"{\"session_id\":\"6fc6b65e-264d-4ee5-a2d3-a034ea377a4b\",\"transcript_path\":\"C:\\\\Users\\\\qmj\\\\.claude\\\\projects\\\\E--QtwCode-foodie-foodie-server\\\\6fc6b65e-264d-4ee5-a2d3-a034ea377a4b.jsonl\",\"cwd\":\"E:\\\\QtwCode\\\\foodie\\\\foodie_server\",\"prompt_id\":\"98c69897-8f9d-48db-924e-ba3a98ca1835\",\"permission_mode\":\"bypassPermissions\",\"effort\":{\"level\":\"high\"},\"hook_event_name\":\"PostToolUse\",\"tool_name\":\"Bash\",\"tool_input\":{\"command\":\"cd \\\"E:/QtwCode/foodie/foodie_server\\\" && python << 'PYEOF'\\n# -*- coding: utf-8 -*-\\npath = \\\"specs/010-order-invoice/auto-invoice-design.md\\\"\\nwith open(path, \\\"rb\\\") as f:\\n    raw = f.read()\\nnl = \\\"\\\\r\\\\n\\\" if b\\\"\\\\r\\\\n\\\" in raw else \\\"\\\\n\\\"\\nc = raw.decode(\\\"utf-8\\\").replace(\\\"\\\\r\\\\n\\\", \\\"\\\\n\\\")\\nanchor = \\\"| \u934f\udcac\u9359\u7a3f\u5f42\u7ec1\udca8 | B2B | BuyerUBN + BuyerName\\\"\\nidx = c.find(anchor)\\nassert idx >= 0, \\\"anchor not found\\\"\\nls = c.rfind(\\\"\\\\n\\\", 0, idx) + 1\\nle = c.find(\\\"\\\\n\\\", idx)\\nnew_line = \\\"| \u934f\udcac\u9359\u7a3f\u5f42\u7ec1\udca8 | B2B | BuyerUBN + BuyerName(\u934f\udcac\u9359\u7a3f\u6095) + BuyerEmail | Y |\\\"\\nc = c[:ls] + new_line"}

+ 5 - 1
ruoyi-admin/src/main/java/com/ruoyi/app/order/OrderInvoiceService.java

@@ -286,6 +286,7 @@ public class OrderInvoiceService {
                 dto.setCategory("B2B");
                 dto.setBuyerUbn(intent.getBuyerUbn());
                 dto.setBuyerName(intent.getBuyerName());
+                dto.setBuyerEmail(intent.getBuyerEmail());
                 break;
             default:
                 break;
@@ -549,7 +550,7 @@ public class OrderInvoiceService {
      * @param storeId 一个可开票门店(调用方负责选 canInvoice 的门店,用于取 ezPay 凭证)
      */
     public void validateCheckoutInvoiceIntent(String invoiceChoice, String carrierNum, String loveCode,
-                                              String buyerUbn, Long storeId) {
+                                              String buyerUbn, String buyerEmail, Long storeId) {
         if (StrUtil.isBlank(invoiceChoice) || "PAPER".equals(invoiceChoice)) {
             return;
         }
@@ -591,6 +592,9 @@ public class OrderInvoiceService {
                 if (StrUtil.isBlank(buyerUbn) || !buyerUbn.matches("^\\d{8}$")) {
                     throw new ServiceException("统一编号格式不正确(须为 8 位数字)");
                 }
+                if (StrUtil.isBlank(buyerEmail) || !buyerEmail.matches("^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$")) {
+                    throw new ServiceException("公司发票须填写有效的买方邮箱(用于接收发票)");
+                }
                 break;
             default:
                 throw new ServiceException("发票类型不合法:" + invoiceChoice);

+ 2 - 1
ruoyi-admin/src/main/java/com/ruoyi/app/order/UserOrderController.java

@@ -161,7 +161,7 @@ public class UserOrderController extends BaseController {
                 throw new ServiceException("所选门店暂不支持电子发票");
             }
             orderInvoiceService.validateCheckoutInvoiceIntent(input.getInvoiceChoice(), input.getCarrierNum(),
-                    input.getLoveCode(), input.getBuyerUbn(), invoiceStoreId);
+                    input.getLoveCode(), input.getBuyerUbn(), input.getBuyerEmail(), invoiceStoreId);
         }
         createOrderParent(input,Long.valueOf(id),shdz);
         createOrderChild(input,Long.valueOf(id),Long.valueOf(id),shdz);
@@ -306,6 +306,7 @@ public class UserOrderController extends BaseController {
                 intent.setCarrierNum(input.getCarrierNum());
                 intent.setBuyerUbn(input.getBuyerUbn());
                 intent.setBuyerName(input.getBuyerName());
+                intent.setBuyerEmail(input.getBuyerEmail());
                 intent.setLoveCode(input.getLoveCode());
                 if ("PHONE_BARCODE".equals(choice)) {
                     intent.setCarrierType("0");

+ 2 - 0
ruoyi-admin/src/main/java/com/ruoyi/app/order/dto/OrderCreateInput.java

@@ -58,6 +58,8 @@ public class OrderCreateInput {
     private String buyerUbn;
     /** 公司名称(COMPANY 时填,作为发票买方名) */
     private String buyerName;
+    /** 买方邮箱(COMPANY 公司发票必填,ezPay 发开立通知给买方) */
+    private String buyerEmail;
     /** 捐赠码 3-7位(LOVE_CODE 时填) */
     private String loveCode;
     /** 是否将该载具/统编存为默认抬头(true 写 info_invoice 并标记默认) */

+ 1 - 1
specs/010-order-invoice/auto-invoice-design.md

@@ -72,7 +72,7 @@ ezPay 映射:
 | 个人→手機條碜 | B2C | CarrierType=0 + CarrierNum | N |
 | 个人→自然人凭证 | B2C | CarrierType=1 + CarrierNum | N |
 | 个人→捐赠 | B2C | LoveCode | N |
-| 公司发票 | B2B | BuyerUBN + BuyerName(公司名) | Y |
+| 公司发票 | B2B | BuyerUBN + BuyerName(公司名) + BuyerEmail | Y |
 
 > 010 现有 `ApplyInvoiceDto.DONATION` 独立类型可废弃:捐赠统一为 `B2C + loveCode`(代码 `buildIssueData` 已有此分支,见 010 T041)。