AML/docs/justsolutionsWebV2/plan-plus-api实现报告2026070623...

305 lines
24 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<!DOCTYPE html>
<html lang="zh-HK">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>justsolutionsWebV2 · plans-plus 真實 API 對接實現報告</title>
<style>
:root {
--bg: #f6f8fa;
--card: #ffffff;
--text: #24292f;
--muted: #57606a;
--border: #d0d7de;
--accent: #0969da;
--accent-soft: #ddf4ff;
--code-bg: #f0f3f6;
--th-bg: #f0f3f6;
--row-alt: #fafbfc;
--warn-bg: #fff8c5;
--warn-border: #d4a72c;
--note-bg: #ddf4ff;
--note-border: #54aeff;
--gap-bg: #ffebe9;
--gap-border: #ff8182;
--ok-bg: #dafbe1;
--ok-border: #4ac26b;
--get: #1a7f37;
--post: #9a6700;
--new: #8250df;
}
* { box-sizing: border-box; }
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "PingFang HK", "Hiragino Sans GB", "Microsoft YaHei", Helvetica, Arial, sans-serif;
background: var(--bg);
color: var(--text);
line-height: 1.65;
font-size: 15px;
}
.wrap { max-width: 1100px; margin: 0 auto; padding: 32px 24px 80px; }
header.page {
background: var(--card); border: 1px solid var(--border);
border-radius: 12px; padding: 28px 32px; margin-bottom: 24px;
}
header.page h1 { margin: 0 0 10px; font-size: 26px; }
header.page p { margin: 4px 0; color: var(--muted); }
header.page .meta { font-size: 13px; }
.toc {
background: var(--card); border: 1px solid var(--border);
border-radius: 12px; padding: 18px 28px; margin-bottom: 28px;
}
.toc h2 { font-size: 13px; margin: 0 0 10px; color: var(--muted); text-transform: uppercase; letter-spacing: .5px; }
.toc ol { margin: 0; padding-left: 20px; columns: 2; column-gap: 36px; }
.toc li { margin: 4px 0; break-inside: avoid; }
.toc a { color: var(--accent); text-decoration: none; }
.toc a:hover { text-decoration: underline; }
section { margin-bottom: 36px; }
h2.sec {
font-size: 21px; border-bottom: 2px solid var(--border);
padding-bottom: 8px; margin: 0 0 18px; scroll-margin-top: 16px;
}
h3 { font-size: 17px; margin: 24px 0 10px; scroll-margin-top: 16px; }
h4 { font-size: 15px; margin: 16px 0 6px; color: var(--muted); }
p { margin: 10px 0; }
table {
border-collapse: collapse; width: 100%; margin: 14px 0;
font-size: 14px; background: var(--card);
border: 1px solid var(--border); border-radius: 8px; overflow: hidden;
}
th, td { border: 1px solid var(--border); padding: 8px 11px; text-align: left; vertical-align: top; }
th { background: var(--th-bg); font-weight: 600; }
tr:nth-child(even) td { background: var(--row-alt); }
code {
background: var(--code-bg); padding: 1.5px 6px; border-radius: 5px;
font-family: "SF Mono", "JetBrains Mono", "Fira Code", Consolas, monospace; font-size: 12.5px;
}
pre {
background: #0d1117; color: #e6edf3; padding: 16px 18px;
border-radius: 8px; overflow-x: auto; font-size: 12.5px; line-height: 1.55;
}
pre code { background: none; padding: 0; color: inherit; font-size: 12.5px; }
.method { font-weight: 700; font-size: 11.5px; padding: 1px 7px; border-radius: 5px; color: #fff; display: inline-block; }
.method.get { background: var(--get); }
.method.post { background: var(--post); }
.method.new { background: var(--new); }
.tag { font-size: 11px; padding: 1px 7px; border-radius: 20px; border: 1px solid var(--border); color: var(--muted); white-space: nowrap; }
.callout { border-radius: 8px; padding: 12px 16px; margin: 14px 0; border: 1px solid; }
.callout p { margin: 4px 0; }
.callout.warn { background: var(--warn-bg); border-color: var(--warn-border); }
.callout.note { background: var(--note-bg); border-color: var(--note-border); }
.callout.gap { background: var(--gap-bg); border-color: var(--gap-border); }
.callout.ok { background: var(--ok-bg); border-color: var(--ok-border); }
.callout.newreq { background:#fbefff; border-color:#d8b9ff; }
.callout .lbl { font-weight: 700; }
.pill { display:inline-block; font-size:11px; font-weight:700; padding:1px 8px; border-radius:20px; }
.pill.done { background: var(--ok-bg); color:#1a7f37; border:1px solid var(--ok-border);}
.pill.partial { background: var(--warn-bg); color:#7a5c00; border:1px solid var(--warn-border);}
.pill.todo { background: var(--gap-bg); color:#cf222e; border:1px solid var(--gap-border);}
.pill.newreq { background:#fbefff; color:#8250df; border:1px solid #d8b9ff;}
.endpoint-head {
display:flex; align-items:center; gap:10px; flex-wrap:wrap;
background: var(--code-bg); border:1px solid var(--border); border-radius:8px;
padding:10px 14px; margin: 6px 0 12px; font-family:"SF Mono",Consolas,monospace; font-size:13.5px;
}
.small { font-size: 13px; color: var(--muted); }
ul.tight { margin: 8px 0; padding-left: 22px; }
ul.tight li { margin: 4px 0; }
.flow { font-family:"SF Mono",Consolas,monospace; font-size:13px; background:var(--code-bg); padding:10px 14px; border-radius:8px; border:1px solid var(--border); overflow-x:auto; white-space:nowrap;}
hr.soft { border:none; border-top:1px dashed var(--border); margin: 26px 0; }
.updated { font-size:12px; color:#8250df; font-weight:700; }
</style>
</head>
<body>
<div class="wrap">
<header class="page">
<h1>justsolutionsWebV2 · plans-plus 真實 API 對接<span style="color:#1a7f37">實現報告</span></h1>
<p><a href="plan-plus-api分析.html" style="color:var(--accent)"><code>plan-plus-api分析.html</code></a> 的方案,把 <code>public/plans-plus.html</code> 單頁訂閱流程從 mock 切換到 AML 後端(<code>iCON.Abp.AMLPortal</code>)真實 API並在本地 <code>docker-compose-local-dev</code> 環境跑通、逐流程實測(<b>支付除外</b>)。本文記錄實際落地內容與驗證結果。</p>
<p class="meta">範圍:<code>AML_Backend</code>(後端 3 項改動 + 配置)· <code>justsolutionsWebV2/server</code>BFF 真實路由)· <code>docker-compose-local-dev</code>(本地棧 + 數據恢復)</p>
<p class="meta updated">狀態2026-07-06 實測P1 只讀目錄 / P2 新購 / P3 續費·加購 / P4 聯絡我們 —— <b>全部端到端打通並真實入庫</b>;支付與單次查詢真實檢測編排本輪除外(保持 mock</p>
</header>
<div class="toc">
<h2>目錄</h2>
<ol>
<li><a href="#s1">1. 落地結論速覽</a></li>
<li><a href="#s2">2. 數據恢復(.bak → AbpAML</a></li>
<li><a href="#s3">3. 後端改動AML_Backend</a></li>
<li><a href="#s4">4. BFF 對接justsolutionsWebV2</a></li>
<li><a href="#s5">5. 本地運行與驗證方式</a></li>
<li><a href="#s6">6. 端到端測試結果</a></li>
<li><a href="#s7">7. 本輪除外與後續待辦</a></li>
<li><a href="#s8">8. 變更文件清單</a></li>
</ol>
</div>
<!-- 1 -->
<section id="s1">
<h2 class="sec">1. 落地結論速覽</h2>
<p>plans-plus 依賴的 <b>3 條只讀目錄</b>editions / plans/catalog / countries<b>2 條交互查詢</b>agents / tenants/lookup<b>下單提交</b>subscribe含 new/renew/topup<b>聯絡我們</b>subscribe-offline → CreateFeedback均已對接真實後端並實測通過。單次查詢與支付本輪除外仍走 mock 兜底。</p>
<table>
<thead><tr><th>前端端點</th><th>對應後端</th><th>狀態</th><th>實測結果</th></tr></thead>
<tbody>
<tr><td><code>GET /editions</code></td><td><code>Order/portal/GetEditionList</code></td><td><span class="pill done">完成</span></td><td>9 行業(過濾 Standard/DC、Others 置末);<code>jQSeparatedEditions</code> 對齊真實 CPA GUID</td></tr>
<tr><td><code>GET /plans/catalog</code></td><td><code>plan/portal/GetPlanList</code></td><td><span class="pill done">完成</span></td><td>HKG·HKD 目錄standard/cpa/addons 全帶 <code>planDetailId</code>KYC 月租 + jQuota 配套</td></tr>
<tr><td><code>GET /countries</code></td><td><code>Order/getCategoryByTypes</code></td><td><span class="pill done">完成</span></td><td>249 國;已改<b>免 token</b>(原用 OAuth本地無憑證會失敗</td></tr>
<tr><td><code>GET /agents/:code</code></td><td><code>SearchUserByCodeAndType</code> + <code>GetPlanList(agentUserId)</code></td><td><span class="pill done">完成</span></td><td>返回 <code>{code,name,tiers,email,phone,agentorId}</code>tiers 經二次 GetPlanList 推導,空=不過濾</td></tr>
<tr><td><code>GET /tenants/lookup</code></td><td><span class="pill newreq">新增</span> <code>Order/portal/queryRenewableTenantByEmail</code></td><td><span class="pill done">完成</span></td><td>後端新端點按郵箱聚合 <code>currentSubscription</code>+<code>referrer</code>cpa1 唯一命中</td></tr>
<tr><td><code>POST /subscribe</code> <span class="tag">new</span></td><td><code>Order/portal/CreateOrder</code></td><td><span class="pill done">完成</span></td><td>空 BR / 個人主體 / 加值項 / agent 均建單入庫BR/CI 已放開)</td></tr>
<tr><td><code>POST /subscribe</code> <span class="tag">renew·topup</span></td><td><code>Order/portal/TenantRenewal</code></td><td><span class="pill done">完成</span></td><td>cpa1 續費 / jQuota 加購均建單;修復內部 token URL 後通過</td></tr>
<tr><td><code>POST /subscribe-offline</code><br><span class="small">(聯絡我們/推薦人)</span></td><td><code>customer/CreateFeedback</code></td><td><span class="pill done">完成</span></td><td>訂單摘要序列化為 message有推薦人則寫 <code>AssignedAgentUserId</code> 路由收件人</td></tr>
<tr><td><code>/single-query</code> · <code>/payments/*</code></td><td>ConsumerPortal待後端· 支付方(未定)</td><td><span class="tag">本輪除外</span></td><td>保持 mock 兜底;文檔明確除外</td></tr>
</tbody>
</table>
</section>
<!-- 2 -->
<section id="s2">
<h2 class="sec">2. 數據恢復(.bak → AbpAML</h2>
<p><code>docs/AML_Backend/docker-compose-local-dev/AML-local-dev.bak</code>201MBSQL Server 備份,內部庫名 <code>AML-local-dev</code>)恢復真實開發數據,取代原「補種子 SQL」路線——恢復後目錄 / 租戶 / 代理計劃齊備,直接可對接。</p>
<div class="flow">docker stop aml-httpapi-host → ALTER DATABASE AbpAML SET SINGLE_USER WITH ROLLBACK IMMEDIATE → RESTORE DATABASE AbpAML FROM DISK WITH MOVE(AML-local-dev→AbpAML.mdf, AML-local-dev_log→AbpAML_log.ldf), REPLACE → docker start aml-httpapi-host</div>
<p class="small">恢復為 <b>AbpAML</b>host 連接的庫MOVE 邏輯文件到 <code>/var/opt/mssql/data/AbpAML.mdf</code>/<code>_log.ldf</code>;恢復前停 host 釋放連接、置 SINGLE_USER 取得獨佔。</p>
<table>
<thead><tr><th></th><th>恢復後</th><th>用途</th></tr></thead>
<tbody>
<tr><td><code>AMLPortal_Plans</code> / <code>PlanDetails</code></td><td>123 / 123</td><td>方案目錄(<b>按國家分兩套</b>HKG-HKD 與 JPN-USD<code>systemCode</code>/<code>countryCode</code> 區分)</td></tr>
<tr><td><code>SaasEditions</code></td><td>12含真實 CPA <code>3A1A296B-…</code></td><td>所屬行業</td></tr>
<tr><td><code>SaasTenants</code> / <code>AMLPortal_TenantPropertys</code></td><td>42 / 98</td><td>續費 / 加購測試數據</td></tr>
<tr><td><code>AMLPortal_AgentUserPlans</code></td><td>24</td><td>agent tiers 過濾</td></tr>
<tr><td><code>AMLPortal_Orders</code> / <code>AbpUsers</code></td><td>142 / 131</td><td>訂單反查 / 推薦人</td></tr>
</tbody>
</table>
<div class="callout note"><p><span class="lbl">catalog 國家過濾:</span><code>services/catalog.js</code> 默認過濾 <code>HKG</code>香港市場·HKD與舊 mock 幣種一致);<code>/plans-jp</code> 的 PAYG 定價另走 <code>JPN</code></p></div>
</section>
<!-- 3 -->
<section id="s3">
<h2 class="sec">3. 後端改動AML_Backend</h2>
<p>共 3 項代碼改動 + 2 項配置,全部重建 <code>httpapi-host</code> 鏡像後實測通過。</p>
<h3>3.1 BR/CI 非必填 <span class="pill done">完成</span></h3>
<p>注釋 <code>OrderService.cs</code> <code>CreateOrder</code> 的兩處 BR/CI 必填校驗(前置校驗處 + <code>Organization BR/CI required</code> 處),保留唯一性校驗 <code>ExistsByOrganizationBRCI</code>(對 BR、CI 皆空返回 <code>false</code>,空值安全)。個人主體 / 空 BR 可下單。</p>
<h3>3.2 新增 <code>queryRenewableTenantByEmail</code> 端點 <span class="pill newreq">新增</span></h3>
<div class="endpoint-head"><span class="method post">POST</span> <span>/api/amlPortal/Order/portal/queryRenewableTenantByEmail</span> <span class="tag">AbpAutoAuth("Portal")</span></div>
<p>解決舊 <code>queryRenewableTenant</code> 只能按<b>租戶名</b> DB 搜、無法按郵箱過濾的維度錯位。新端點<b>維度反轉</b></p>
<ul class="tight">
<li><code>UserName=='admin' &amp;&amp; Email==email</code> 定位租戶管理員(禁多租戶過濾,全庫掃描可接受);</li>
<li>取該租戶 <code>IsActive</code><code>TenantProperty</code>(含 <code>Order→OrderDetails→Plan</code>),按 <code>CreationTime</code> 取最新;</li>
<li>反查訂單明細聚合 <code>currentSubscription</code>:基礎方案(B)→planId/名稱/月數/價AdlU→<code>addons.users</code>(PCS 之和)jQ/j→<code>jquotaPackageId</code><code>EnableKYC</code>→kyc並解析 <code>EditionName</code>(由 EditionId<code>referrer</code>(由 AgentorId 解析用戶)。</li>
</ul>
<p class="small">新增 DTO<code>QueryRenewableTenantByEmailParam</code><code>RenewableTenantByEmailDto</code>(含 <code>CurrentSubscriptionDto</code> / <code>RenewableReferrerDto</code>)。</p>
<h3>3.3 <code>CreateFeedback</code><code>AssignedAgentUserId</code>(聯絡推薦人路由)<span class="pill done">完成</span></h3>
<p><code>Feedback</code> 實體 + <code>CreateFeedbackDto</code> 加可空 <code>AssignedAgentUserId</code>AutoMapper 同名自動映射);<code>CustomerService.CreateFeedback</code> 於有值時用 <code>GetUsersByIDs</code> 解析推薦人郵箱,作 <code>SendEmailOnPortalFeedback</code> 的收件人(無郵箱回退平台銷售),給客戶本人的確認郵件不變。</p>
<div class="callout warn"><p><span class="lbl">EF 遷移(部署待辦):</span>本地以 <code>ALTER TABLE AMLPortal_Feedbacks ADD AssignedAgentUserId uniqueidentifier NULL</code> 加列(實體屬性映射到該列即可運行)。<b>正式部署需在構建環境跑 <code>dotnet ef migrations add</code></b> 生成遷移(就一句 <code>AddColumn</code>)——原因:運行時 <code>Database.Migrate()</code> 會讀取遷移的 <code>TargetModel</code>(完整模型快照),手寫遷移不可行。</p></div>
<h3>3.4 對齊 jQSeparatedEditions CPA GUID <span class="pill done">完成</span></h3>
<p><code>appsettings.local.json</code><code>Portal.jQSeparatedEditions.EditionIds</code> 由庫中不存在的 <code>3A1A2969-…</code> 改為真實 CPA edition <code>3A1A296B-DAB4-4B94-B267-4424683B8916</code>,否則前端選 CPA 不觸發 <code>isCpa</code> 方案集切換。</p>
<h3>3.5 修正內部 token URL本地<span class="pill done">完成</span></h3>
<div class="callout gap"><p><span class="lbl">實測中發現的坑:</span><code>AppConfig:General:ApiLocalhostUrl</code> 出廠 <code>https://localhost:44331/</code>,但本地 host 只監聽 HTTP → <code>TenantRenewal</code> / 建租戶等內部 <code>AbpTokenService.connect/token</code> 調用 <b>SSL 握手失敗500</b><code>docker-compose.local.yml</code> host env 已加 <code>AppConfig__General__ApiLocalhostUrl: "http://localhost:44331/"</code>。(<code>CreateOrder</code> 走異步隊列建租戶未同步觸發,故只有 renew/topup 報錯——由此定位。)</p></div>
</section>
<!-- 4 -->
<section id="s4">
<h2 class="sec">4. BFF 對接justsolutionsWebV2</h2>
<p>採 BFF 範式:瀏覽器只調本站 <code>/api/*</code>,服務端匿名代理後端並裁剪響應。前端 <code>plans-plus.js</code> <b>零改動</b></p>
<div class="callout note"><p><span class="lbl">免 token</span>門戶端點均 <code>[AbpAutoAuth("Portal")]</code><code>SearchUserByCodeAndType</code><code>[AllowAnonymous]</code>)——後端 <code>AbpAutoAuthMiddleware</code> 服務端注入門戶訪客 token 並覆蓋 Authorization故 BFF 匿名 POST/GET 即可,<b>無需配 AUTH_*</b>。統一助手 <code>server/services/portal.js</code></p></div>
<table>
<thead><tr><th>文件</th><th>職責</th></tr></thead>
<tbody>
<tr><td><code>services/portal.js</code> <span class="pill newreq"></span></td><td>匿名 POST/GET 助手axios + 自簽證書放行 + <code>{code,msg,data}</code> 拆包)</td></tr>
<tr><td><code>services/catalog.js</code> <span class="pill newreq"></span></td><td>GetPlanList → 按國家(默認 HKG)過濾 → filterPlan 拆 standard/cpa/addons → 產出前端契約 + <code>planDetailId</code> 索引5 分鐘緩存</td></tr>
<tr><td><code>routes/editions.js</code> <span class="pill newreq"></span></td><td>GetEditionList → 多語言映射 + 過濾 Standard/DC + Others 置末 + <code>jQSeparatedEditions</code> 鍵名/值轉小寫</td></tr>
<tr><td><code>routes/plans.js</code> <span class="pill newreq"></span></td><td><code>GET /plans/catalog[?countryCode]</code> → catalog 服務</td></tr>
<tr><td><code>routes/agents.js</code> <span class="pill newreq"></span></td><td><code>GET /agents/:code</code> → SearchUser + GetPlanList(agentUserId) 推導 tiers</td></tr>
<tr><td><code>routes/tenants.js</code> <span class="pill newreq"></span></td><td><code>GET /tenants/lookup?email=</code> → queryRenewableTenantByEmail字段直通 + 命中態 + 日期格式化</td></tr>
<tr><td><code>routes/subscribe.js</code> <span class="pill newreq"></span></td><td><code>POST /subscribe</code>(new→CreateOrder / renew·topup→TenantRenewal組裝 PlanList 回填 planDetailId、KYC PCS=月數) + <code>POST /subscribe-offline</code>(序列化摘要 + agentCode 解析→AssignedAgentUserId → CreateFeedback)</td></tr>
<tr><td><code>routes/countries.js</code> <span class="pill partial"></span></td><td>由 OAuth 改<b>免 token</b>(本地無憑證),輸出不變</td></tr>
<tr><td><code>index.js</code> <span class="pill partial"></span></td><td>真實路由挂在 <b>mock 之前</b>真實路由處理訂閱流程mock 只兜底 single-query/options、PAYG-GetPlanList、payments、promos</td></tr>
<tr><td><code>package.json</code> <span class="pill partial"></span></td><td>補回 <code>start:local</code> 腳本</td></tr>
</tbody>
</table>
<h4>PlanList 組裝subscribe 核心)</h4>
<pre><code>PlanList = []
if type != 'topup': push({PlanId: plan.planId, PlanDetailId: plan.planDetailId, PCS:1})
if addons.users > 0: push({PlanId: refs.user.planId, PlanDetailId: refs.user.planDetailId, PCS: users})
if addons.kyc: push({PlanId: refs.kyc.planId, PlanDetailId: refs.kyc.planDetailId, PCS: kycRentalMonths}) // KYC 月租
if addons.jquotaPackageId: push({PlanId: jqId, PlanDetailId: planDetailByPlanId[jqId], PCS:1})</code></pre>
</section>
<!-- 5 -->
<section id="s5">
<h2 class="sec">5. 本地運行與驗證方式</h2>
<h4>① 後端棧docker-compose-local-dev</h4>
<pre><code>cd AML_Backend/docker-compose-local-dev
docker compose -f docker-compose.local.yml build httpapi-host # 編譯後端改動
docker compose -f docker-compose.local.yml up -d --no-deps httpapi-host # 只重建 host不重跑 migrator
# 後端 Swagger: http://localhost:44331/swagger DB: localhost,11433 (sa / Aml@Local2026)</code></pre>
<h4>② BFFjustsolutionsWebV2</h4>
<pre><code>cd justsolutionsWebV2
npm run start:local
# = APP_ENV=dev API_BASE_URL=http://localhost:44331 PLANS_PLUS_MOCK=true PORT=8090
# 頁面: http://localhost:8090/plans-plus (日本版 /plans-jp</code></pre>
<p class="small"><code>PLANS_PLUS_MOCK=true</code> 讓 single-query/payments 走 mock 兜底;真實訂閱路由挂在 mock 之前優先處理。</p>
</section>
<!-- 6 -->
<section id="s6">
<h2 class="sec">6. 端到端測試結果</h2>
<p>以下均為 <b>APP_ENV=dev、指向本地 docker 後端</b> 的真實調用(非 mock並核對數據庫寫入。</p>
<table>
<thead><tr><th>流程</th><th>驗證</th></tr></thead>
<tbody>
<tr><td>只讀目錄</td><td>editions=9 行業CPA 用對齊 GUID· catalog standard/cpa/addons 全帶 planDetailId · countries=249 · agents/C99099 返回 tiers/email/agentorId</td></tr>
<tr><td>租戶查詢</td><td><code>tenants/lookup?email=307736951@qq.com</code> → cpa1 唯一命中,聚合 editionId=CPA / referrer / currentSubscription(kyc=true, expiry 2027)</td></tr>
<tr><td>新購(多變體)</td><td>4 筆真實訂單入庫:基礎 / <b>個人主體+空 BR</b> / <b>全加值項</b>(Standard+jQuota+增加使用人+KYC) / agentGoodsName 顯示 PlanList 組裝正確</td></tr>
<tr><td>續費 / 加購</td><td>cpa1 續費(CPA 24 Months) + jQuota 200 加購 均建單入庫(修 token URL 後)</td></tr>
<tr><td>聯絡我們/推薦人</td><td><code>subscribe-offline</code> → Feedback 寫入,<code>AssignedAgentUserId=3A09C9F3…</code>(推薦人)、摘要序列化 245 字</td></tr>
<tr><td>前端渲染(無頭 Chrome</td><td>行業下拉填充真實 editions、jQuota 真實 HKD 價渲染、無 JS 錯誤</td></tr>
</tbody>
</table>
<div class="callout ok"><p><span class="lbl">結論:</span>P1P4 全部端到端打通,真實建單入庫,前端真實渲染。</p></div>
</section>
<!-- 7 -->
<section id="s7">
<h2 class="sec">7. 本輪除外與後續待辦</h2>
<ul class="tight">
<li><span class="pill todo">除外</span> <b>在線支付</b>:文檔明確本輪除外;前端 submit 已臨時去支付鏈、直接顯示「已提交成功」。</li>
<li><span class="pill todo">除外</span> <b>單次查詢真實檢測編排</b>:支付完成後 AML 後端內部 <code>CreateConsumerLink</code><code>CreateConsumerOrder</code>(待後端 + 需 2C 租戶/檢測引擎),本輪保持 mock。</li>
<li><span class="pill partial">部署待辦</span> <b><code>AssignedAgentUserId</code> 正式 EF 遷移</b>:本地已 ALTER 加列;部署環境需 <code>dotnet ef migrations add</code> 生成遷移trivial AddColumn</li>
<li><span class="pill partial">數據</span> <b>邊界情況</b>:測試數據中 <code>test@test.com</code> 對應 3 個租戶(郵箱非全局唯一,生產唯一);部分租戶 active TenantProperty 無關聯 Order → <code>currentSubscription.planId</code> 為空(前端 <code>renewalPlanObj</code> 兜底)。</li>
<li><span class="pill partial">展示</span> <b>後端無字段</b><code>bestValue</code> / <code>note*</code> / <code>nameJP</code> / edition 多語言由 BFF 靜態映射補足(日文暫回退英文)。</li>
</ul>
</section>
<!-- 8 -->
<section id="s8">
<h2 class="sec">8. 變更文件清單</h2>
<h4>AML_Backend</h4>
<ul class="tight">
<li><code>modules/iCON.Abp.AMLPortal/…/OrderService.cs</code> — BR/CI 放開 + <code>QueryRenewableTenantByEmail</code></li>
<li><code>…/Application.Contracts/OrderAppLayer/{IOrderService,QueryRenewableTenantByEmailParam,RenewableTenantByEmailDto}.cs</code></li>
<li><code>…/HttpApi/Controllers/OrderController.cs</code> — 新端點路由</li>
<li><code>…/Domain/DbEntity/Feedback.cs</code> · <code>…/Application.Contracts/CustomerAppLayer/CreateFeedbackDto.cs</code> · <code>…/Application/CustomerService.cs</code><code>AssignedAgentUserId</code></li>
<li><code>docker-compose-local-dev/appsettings.local.json</code> — jQSeparatedEditions GUID</li>
<li><code>docker-compose-local-dev/docker-compose.local.yml</code><code>ApiLocalhostUrl</code> http override</li>
</ul>
<h4>justsolutionsWebV2</h4>
<ul class="tight">
<li>新增 <code>server/services/{portal,catalog}.js</code><code>server/routes/{editions,plans,agents,tenants,subscribe}.js</code></li>
<li>修改 <code>server/routes/countries.js</code>(免 token<code>server/index.js</code>(挂載)、<code>package.json</code><code>start:local</code></li>
</ul>
<hr class="soft">
<p class="small">本報告基於 2026-07-06 本地 docker 環境實測。配套方案文檔見 <a href="plan-plus-api分析.html" style="color:var(--accent)">plan-plus-api分析.html</a></p>
</section>
</div>
</body>
</html>