Gogs 3 недель назад
Родитель
Сommit
68d236b7a0
28 измененных файлов с 4269 добавлено и 1 удалено
  1. BIN
      data/storage/入库数据/2025采购入库单_1119.xlsx
  2. BIN
      data/storage/入库数据/产品资料.xlsx
  3. BIN
      data/storage/半成品组装/半成品匹配成品编码明细.xlsx
  4. BIN
      data/storage/半成品组装/组装明细 - 1107.xlsx
  5. BIN
      data/storage/销售数据/2025年订单数据.xlsx
  6. BIN
      data/supply/2026年每日QA登记表.xlsx
  7. BIN
      docs/__pycache__/create_monitoring_architecture_ppt.cpython-311.pyc
  8. 326 0
      docs/create-monitoring-architecture-ppt.ps1
  9. 311 0
      docs/create_monitoring_architecture_ppt.py
  10. 220 0
      docs/order-monitoring-architecture-current.svg
  11. 220 0
      docs/order-monitoring-architecture.svg
  12. 138 0
      docs/order-monitoring-module-design.md
  13. 215 0
      docs/supply-monitoring-architecture-current.svg
  14. 215 0
      docs/supply-monitoring-architecture.svg
  15. 304 0
      docs/supply-monitoring-module-design.md
  16. BIN
      docs/供应监测评分卡QA质量评分机制_两页汇报_修正版.pptx
  17. BIN
      docs/订单与供应监测模块架构汇报.pptx
  18. 23 0
      dtm-admin/src/main/java/com/dtm/web/controller/supply/SupplyMonitorController.java
  19. 46 0
      dtm-admin/src/main/java/com/dtm/web/controller/supply/SupplySupplierController.java
  20. 50 0
      dtm-system/src/main/java/com/dtm/supply/mapper/SupplyMonitorMapper.java
  21. 938 1
      dtm-system/src/main/java/com/dtm/supply/service/SupplyMonitorService.java
  22. 479 0
      dtm-system/src/main/resources/mapper/supply/SupplyMonitorMapper.xml
  23. BIN
      tmpcheck/supply_match.xlsx
  24. 210 0
      tools/create_qa_score_ppt.py
  25. 203 0
      tools/create_supply_monitoring_ppt.ps1
  26. 191 0
      tools/render_supply_flow.ps1
  27. 180 0
      tools/supply_flow_diagram.svg
  28. BIN
      供应监测评分卡版块优化需求整理.docx

BIN
data/storage/入库数据/2025采购入库单_1119.xlsx


BIN
data/storage/入库数据/产品资料.xlsx


BIN
data/storage/半成品组装/半成品匹配成品编码明细.xlsx


BIN
data/storage/半成品组装/组装明细 - 1107.xlsx


BIN
data/storage/销售数据/2025年订单数据.xlsx


BIN
data/supply/2026年每日QA登记表.xlsx


BIN
docs/__pycache__/create_monitoring_architecture_ppt.cpython-311.pyc


+ 326 - 0
docs/create-monitoring-architecture-ppt.ps1

@@ -0,0 +1,326 @@
+$ErrorActionPreference = "Stop"
+
+$outputPath = Join-Path $PSScriptRoot "订单与供应监测模块架构汇报.pptx"
+$buildDir = Join-Path $PSScriptRoot "pptx-build"
+
+if (Test-Path $outputPath) {
+    Remove-Item -LiteralPath $outputPath -Force
+}
+if (Test-Path $buildDir) {
+    Remove-Item -LiteralPath $buildDir -Recurse -Force
+}
+
+New-Item -ItemType Directory -Force -Path $buildDir | Out-Null
+New-Item -ItemType Directory -Force -Path (Join-Path $buildDir "_rels") | Out-Null
+New-Item -ItemType Directory -Force -Path (Join-Path $buildDir "docProps") | Out-Null
+New-Item -ItemType Directory -Force -Path (Join-Path $buildDir "ppt") | Out-Null
+New-Item -ItemType Directory -Force -Path (Join-Path $buildDir "ppt\_rels") | Out-Null
+New-Item -ItemType Directory -Force -Path (Join-Path $buildDir "ppt\slides") | Out-Null
+New-Item -ItemType Directory -Force -Path (Join-Path $buildDir "ppt\slides\_rels") | Out-Null
+New-Item -ItemType Directory -Force -Path (Join-Path $buildDir "ppt\slideMasters") | Out-Null
+New-Item -ItemType Directory -Force -Path (Join-Path $buildDir "ppt\slideMasters\_rels") | Out-Null
+New-Item -ItemType Directory -Force -Path (Join-Path $buildDir "ppt\slideLayouts") | Out-Null
+New-Item -ItemType Directory -Force -Path (Join-Path $buildDir "ppt\slideLayouts\_rels") | Out-Null
+New-Item -ItemType Directory -Force -Path (Join-Path $buildDir "ppt\theme") | Out-Null
+
+$emu = 9525
+$shapeId = 1
+
+function XmlEscape($value) {
+    return [System.Security.SecurityElement]::Escape([string]$value)
+}
+
+function Emu($px) {
+    return [int64]([double]$px * $emu)
+}
+
+function Rgb($hex) {
+    return $hex.TrimStart("#").ToUpperInvariant()
+}
+
+function Write-Utf8($path, $content) {
+    $utf8 = New-Object System.Text.UTF8Encoding($false)
+    [System.IO.File]::WriteAllText($path, $content, $utf8)
+}
+
+function TextShape($text, $x, $y, $w, $h, $fontSize, $color, $bold) {
+    $script:shapeId++
+    $b = if ($bold) { ' b="1"' } else { "" }
+    $paras = @()
+    foreach ($line in ([string]$text -split "`r?`n")) {
+        $paras += @"
+<a:p><a:r><a:rPr lang="zh-CN" sz="$([int]($fontSize * 100))"$b><a:solidFill><a:srgbClr val="$(Rgb $color)"/></a:solidFill><a:latin typeface="Microsoft YaHei"/><a:ea typeface="Microsoft YaHei"/></a:rPr><a:t>$(XmlEscape $line)</a:t></a:r><a:endParaRPr lang="zh-CN" sz="$([int]($fontSize * 100))"/></a:p>
+"@
+    }
+    return @"
+<p:sp>
+  <p:nvSpPr><p:cNvPr id="$shapeId" name="Text $shapeId"/><p:cNvSpPr txBox="1"/><p:nvPr/></p:nvSpPr>
+  <p:spPr><a:xfrm><a:off x="$(Emu $x)" y="$(Emu $y)"/><a:ext cx="$(Emu $w)" cy="$(Emu $h)"/></a:xfrm><a:prstGeom prst="rect"><a:avLst/></a:prstGeom><a:noFill/><a:ln><a:noFill/></a:ln></p:spPr>
+  <p:txBody><a:bodyPr wrap="square" rtlCol="0"><a:spAutoFit/></a:bodyPr><a:lstStyle/>$($paras -join "")</p:txBody>
+</p:sp>
+"@
+}
+
+function RectShape($x, $y, $w, $h, $fill, $line = "FFFFFF", $radius = $false) {
+    $script:shapeId++
+    $geom = if ($radius) { "roundRect" } else { "rect" }
+    return @"
+<p:sp>
+  <p:nvSpPr><p:cNvPr id="$shapeId" name="Box $shapeId"/><p:cNvSpPr/><p:nvPr/></p:nvSpPr>
+  <p:spPr><a:xfrm><a:off x="$(Emu $x)" y="$(Emu $y)"/><a:ext cx="$(Emu $w)" cy="$(Emu $h)"/></a:xfrm><a:prstGeom prst="$geom"><a:avLst/></a:prstGeom><a:solidFill><a:srgbClr val="$(Rgb $fill)"/></a:solidFill><a:ln w="9525"><a:solidFill><a:srgbClr val="$(Rgb $line)"/></a:solidFill></a:ln></p:spPr>
+</p:sp>
+"@
+}
+
+function Card($title, $body, $x, $y, $w, $h, $accent) {
+    return (RectShape $x $y $w $h "FFFFFF" "DDDDDD" $true) +
+        (RectShape $x $y 8 $h $accent $accent $false) +
+        (TextShape $title ($x + 22) ($y + 16) ($w - 35) 34 17 "17304A" $true) +
+        (TextShape $body ($x + 22) ($y + 58) ($w - 35) ($h - 68) 13 "555555" $false)
+}
+
+function Node($text, $x, $y, $w, $h, $fill, $line) {
+    return (RectShape $x $y $w $h $fill $line $true) +
+        (TextShape $text ($x + 10) ($y + 15) ($w - 20) ($h - 20) 14 "17304A" $true)
+}
+
+function Title($title, $subtitle) {
+    $xml = (TextShape $title 70 42 980 52 30 "17304A" $true)
+    if ($subtitle -ne "") {
+        $xml += TextShape $subtitle 72 92 980 30 14 "666666" $false
+    }
+    $xml += RectShape 70 128 1140 3 "2B74B1" "2B74B1" $false
+    return $xml
+}
+
+$slides = @()
+
+$slides += @{
+    Bg = "FBFAF7"
+    Body = (TextShape "订单监测与供应监测" 90 170 900 58 38 "17304A" $true) +
+        (TextShape "模块架构、功能逻辑与系统关联汇报" 92 238 880 38 20 "666666" $false) +
+        (TextShape "面向管理层的简版说明" 94 300 420 28 15 "2B74B1" $true) +
+        (Card "汇报重点" "1. 两个模块分别解决什么问题`n2. 数据如何进入系统并形成分析结果`n3. 与库存、产品、生命周期等模块如何关联" 760 170 390 250 "2B74B1")
+}
+
+$slides += @{
+    Bg = "FFFFFF"
+    Body = (Title "一页总览" "两个模块都采用:数据接入 -> 数据入库 -> 后端聚合 -> 前端看板展示") +
+        (Card "订单监测" "关注销售侧:订单金额、商品贡献、渠道/平台表现、支付效率、退款漏损、连带购买。`n`n当前数据库已有核心表,具备较完整的数据分析基础。" 80 170 500 320 "2B74B1") +
+        (Card "供应监测" "关注采购侧:供应商履约、采购订单、入库金额、账期、准交率、付款计划。`n`n当前数据库只有部分基础数据,需补齐采购订单、账期和交付匹配数据。" 700 170 500 320 "449B7A")
+}
+
+$slides += @{
+    Bg = "FFFFFF"
+    Body = (Title "整体模块架构" "前端页面保持稳定,后端逐步将文件依赖切换为数据库驱动") +
+        (Node "数据文件上传`nCSV / Excel" 70 220 180 78 "DAE7F4" "2B74B1") +
+        (TextShape "→" 268 234 50 50 28 "666666" $true) +
+        (Node "导入服务`n解析 / 清洗 / 校验" 330 220 210 78 "EAF5EC" "449B7A") +
+        (TextShape "→" 558 234 50 50 28 "666666" $true) +
+        (Node "业务数据库`n订单 / 供应 / 库存" 620 220 210 78 "F7F1E8" "E88B3A") +
+        (TextShape "→" 848 234 50 50 28 "666666" $true) +
+        (Node "分析服务`nMapper / 聚合逻辑" 910 220 210 78 "F5F5F5" "666666") +
+        (TextShape "↓" 995 305 50 50 26 "666666" $true) +
+        (Node "管理看板`n图表 / 报表 / 对比" 910 370 210 78 "FFFFFF" "2B74B1") +
+        (Card "架构原则" "接口尽量保持不变,减少前端改造;数据统一入库,避免依赖本地路径;分析逻辑集中在 Service 和 Mapper 层。" 120 500 1030 105 "2B74B1")
+}
+
+$slides += @{
+    Bg = "FFFFFF"
+    Body = (Title "订单监测:功能逻辑" "订单监测已经具备数据库化基础,重点是持续完善导入和统计口径") +
+        (Card "核心功能" "订单价值分析:GMV、成交金额、退款金额`n商品分析:Top 商品、连带购买`n渠道分析:平台、渠道、业务单元贡献`n支付分析:平均支付耗时、支付漏斗`n经营报表:按日/月/年聚合" 70 165 360 370 "2B74B1") +
+        (Card "实现逻辑" "1. 订单和店铺价值数据进入数据库`n2. Mapper 查询订单主表和店铺价值明细表`n3. Service 聚合指标并返回统一结构`n4. 前端看板按日期范围展示结果" 460 165 360 370 "449B7A") +
+        (Card "当前基础" "dtm_order_main:订单主表`ndtm_shop_value_main:店铺价值明细表`n`n当前数据库中两张核心表已有较多数据,可支撑订单监测主要分析。" 850 165 360 370 "E88B3A")
+}
+
+$slides += @{
+    Bg = "FFFFFF"
+    Body = (Title "供应监测:功能逻辑" "供应监测目标是从 Excel 文件读取升级为数据库驱动") +
+        (Card "核心功能" "供应商统计:订单金额、入库金额、完成率、准交率`n供应商对比:多个供应商横向比较`n付款计划:账期 + 验收/入库日期计算预计付款`n数据导入:账期、订单、入库、匹配结果" 70 165 360 370 "449B7A") +
+        (Card "实现逻辑" "1. 上传供应监测相关 Excel`n2. 解析供应商、订单、入库、账期字段`n3. 写入供应监测数据表`n4. Service 从数据库聚合供应商指标`n5. 前端继续使用现有查询接口" 460 165 360 370 "2B74B1") +
+        (Card "当前缺口" "数据库已有:供应商基础表、采购入库基础表。`n`n仍缺:采购订单、供应商账期、计划交货日期、实际验收日期、订单入库匹配结果。" 850 165 360 370 "E88B3A")
+}
+
+$slides += @{
+    Bg = "FFFFFF"
+    Body = (Title "模块关联关系" "两个监测模块不是孤立看板,而是与基础数据和分析模块共享数据资产") +
+        (Card "订单监测关联" "产品模块:通过 SKU 关联商品、SPU、产品信息。`n生命周期模块:复用订单销售数据做热销、趋势、生命周期分析。`n店铺模块:复用平台、渠道、业务单元维度。" 90 170 500 280 "2B74B1") +
+        (Card "供应监测关联" "库存模块:复用采购入库和库存流转数据。`n产品模块:通过 SKU 关联采购产品。`n供应商资料:复用供应商编号和名称,扩展账期和履约指标。" 690 170 500 280 "449B7A") +
+        (Card "通用关联" "系统权限和操作日志可统一管理导入权限、导入记录和异常追踪;导入批次设计可在订单和供应两个模块复用。" 190 495 900 95 "E88B3A")
+}
+
+$slides += @{
+    Bg = "FFFFFF"
+    Body = (Title "数据入库建设重点" "管理层需要关注的是数据完整性、稳定性和可追踪性") +
+        (Card "订单监测" "建设重点:完善导入批次、重复数据处理、异常行反馈。`n`n收益:订单看板稳定、历史数据可追溯、跨模块复用能力强。" 90 170 500 300 "2B74B1") +
+        (Card "供应监测" "建设重点:补齐采购订单、账期、计划交货日期、实际验收日期、匹配结果。`n`n收益:供应商履约分析和付款计划可以稳定落地。" 690 170 500 300 "449B7A")
+}
+
+$slides += @{
+    Bg = "FFFFFF"
+    Body = (Title "推进路径" "建议分阶段推进,先稳定数据,再扩展分析能力") +
+        (Node "阶段一`n确认数据源和字段口径" 80 220 210 85 "DAE7F4" "2B74B1") +
+        (TextShape "→" 308 240 50 50 28 "666666" $true) +
+        (Node "阶段二`n建设导入和入库表" 370 220 210 85 "EAF5EC" "449B7A") +
+        (TextShape "→" 598 240 50 50 28 "666666" $true) +
+        (Node "阶段三`n切换 Service 查询数据库" 660 220 230 85 "F7F1E8" "E88B3A") +
+        (TextShape "→" 908 240 50 50 28 "666666" $true) +
+        (Node "阶段四`n完善看板和管理报表" 970 220 210 85 "FFFFFF" "2B74B1") +
+        (Card "结论" "订单监测已有较好的数据库基础,重点是规范化导入和追踪。供应监测需要先补齐核心数据表,再将现有 Excel 分析迁移到数据库查询。" 120 430 1040 130 "2B74B1")
+}
+
+function SlideXml($slide) {
+    $bg = Rgb $slide.Bg
+    return @"
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<p:sld xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main">
+  <p:cSld>
+    <p:bg><p:bgPr><a:solidFill><a:srgbClr val="$bg"/></a:solidFill><a:effectLst/></p:bgPr></p:bg>
+    <p:spTree>
+      <p:nvGrpSpPr><p:cNvPr id="1" name=""/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr>
+      <p:grpSpPr><a:xfrm><a:off x="0" y="0"/><a:ext cx="0" cy="0"/><a:chOff x="0" y="0"/><a:chExt cx="0" cy="0"/></a:xfrm></p:grpSpPr>
+      $($slide.Body)
+    </p:spTree>
+  </p:cSld>
+  <p:clrMapOvr><a:masterClrMapping/></p:clrMapOvr>
+</p:sld>
+"@
+}
+
+$overrides = @(
+    '<Override PartName="/ppt/presentation.xml" ContentType="application/vnd.openxmlformats-officedocument.presentationml.presentation.main+xml"/>',
+    '<Override PartName="/ppt/slideMasters/slideMaster1.xml" ContentType="application/vnd.openxmlformats-officedocument.presentationml.slideMaster+xml"/>',
+    '<Override PartName="/ppt/slideLayouts/slideLayout1.xml" ContentType="application/vnd.openxmlformats-officedocument.presentationml.slideLayout+xml"/>',
+    '<Override PartName="/ppt/theme/theme1.xml" ContentType="application/vnd.openxmlformats-officedocument.theme+xml"/>',
+    '<Override PartName="/docProps/core.xml" ContentType="application/vnd.openxmlformats-package.core-properties+xml"/>',
+    '<Override PartName="/docProps/app.xml" ContentType="application/vnd.openxmlformats-officedocument.extended-properties+xml"/>'
+)
+
+for ($i = 1; $i -le $slides.Count; $i++) {
+    $overrides += "<Override PartName=""/ppt/slides/slide$i.xml"" ContentType=""application/vnd.openxmlformats-officedocument.presentationml.slide+xml""/>"
+    Write-Utf8 (Join-Path $buildDir "ppt\slides\slide$i.xml") (SlideXml $slides[$i - 1])
+    Write-Utf8 (Join-Path $buildDir "ppt\slides\_rels\slide$i.xml.rels") @"
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
+  <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout" Target="../slideLayouts/slideLayout1.xml"/>
+</Relationships>
+"@
+}
+
+Write-Utf8 (Join-Path $buildDir "[Content_Types].xml") @"
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
+  <Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
+  <Default Extension="xml" ContentType="application/xml"/>
+  $($overrides -join "`n  ")
+</Types>
+"@
+
+Write-Utf8 (Join-Path $buildDir "_rels\.rels") @"
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
+  <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="ppt/presentation.xml"/>
+  <Relationship Id="rId2" Type="http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties" Target="docProps/core.xml"/>
+  <Relationship Id="rId3" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties" Target="docProps/app.xml"/>
+</Relationships>
+"@
+
+$slideIds = @()
+$presentationRels = @(
+    '<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideMaster" Target="slideMasters/slideMaster1.xml"/>'
+)
+for ($i = 1; $i -le $slides.Count; $i++) {
+    $relId = $i + 1
+    $slideDbId = 255 + $i
+    $slideIds += "<p:sldId id=""$slideDbId"" r:id=""rId$relId""/>"
+    $presentationRels += "<Relationship Id=""rId$relId"" Type=""http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide"" Target=""slides/slide$i.xml""/>"
+}
+
+Write-Utf8 (Join-Path $buildDir "ppt\presentation.xml") @"
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<p:presentation xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main">
+  <p:sldMasterIdLst><p:sldMasterId id="2147483648" r:id="rId1"/></p:sldMasterIdLst>
+  <p:sldIdLst>$($slideIds -join "")</p:sldIdLst>
+  <p:sldSz cx="12192000" cy="6858000" type="wide"/>
+  <p:notesSz cx="6858000" cy="9144000"/>
+  <p:defaultTextStyle/>
+</p:presentation>
+"@
+
+Write-Utf8 (Join-Path $buildDir "ppt\_rels\presentation.xml.rels") @"
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
+  $($presentationRels -join "`n  ")
+</Relationships>
+"@
+
+Write-Utf8 (Join-Path $buildDir "ppt\slideMasters\slideMaster1.xml") @"
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<p:sldMaster xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main">
+  <p:cSld><p:spTree><p:nvGrpSpPr><p:cNvPr id="1" name=""/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr><p:grpSpPr><a:xfrm><a:off x="0" y="0"/><a:ext cx="0" cy="0"/><a:chOff x="0" y="0"/><a:chExt cx="0" cy="0"/></a:xfrm></p:grpSpPr></p:spTree></p:cSld>
+  <p:clrMap bg1="lt1" tx1="dk1" bg2="lt2" tx2="dk2" accent1="accent1" accent2="accent2" accent3="accent3" accent4="accent4" accent5="accent5" accent6="accent6" hlink="hlink" folHlink="folHlink"/>
+  <p:sldLayoutIdLst><p:sldLayoutId id="2147483649" r:id="rId1"/></p:sldLayoutIdLst>
+  <p:txStyles><p:titleStyle/><p:bodyStyle/><p:otherStyle/></p:txStyles>
+</p:sldMaster>
+"@
+
+Write-Utf8 (Join-Path $buildDir "ppt\slideMasters\_rels\slideMaster1.xml.rels") @"
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
+  <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout" Target="../slideLayouts/slideLayout1.xml"/>
+  <Relationship Id="rId2" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme" Target="../theme/theme1.xml"/>
+</Relationships>
+"@
+
+Write-Utf8 (Join-Path $buildDir "ppt\slideLayouts\slideLayout1.xml") @"
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<p:sldLayout xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main" type="blank" preserve="1">
+  <p:cSld name="Blank"><p:spTree><p:nvGrpSpPr><p:cNvPr id="1" name=""/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr><p:grpSpPr><a:xfrm><a:off x="0" y="0"/><a:ext cx="0" cy="0"/><a:chOff x="0" y="0"/><a:chExt cx="0" cy="0"/></a:xfrm></p:grpSpPr></p:spTree></p:cSld>
+  <p:clrMapOvr><a:masterClrMapping/></p:clrMapOvr>
+</p:sldLayout>
+"@
+
+Write-Utf8 (Join-Path $buildDir "ppt\slideLayouts\_rels\slideLayout1.xml.rels") @"
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
+  <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideMaster" Target="../slideMasters/slideMaster1.xml"/>
+</Relationships>
+"@
+
+Write-Utf8 (Join-Path $buildDir "ppt\theme\theme1.xml") @"
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<a:theme xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" name="DTM">
+  <a:themeElements>
+    <a:clrScheme name="DTM"><a:dk1><a:srgbClr val="17304A"/></a:dk1><a:lt1><a:srgbClr val="FFFFFF"/></a:lt1><a:dk2><a:srgbClr val="666666"/></a:dk2><a:lt2><a:srgbClr val="FBFAF7"/></a:lt2><a:accent1><a:srgbClr val="2B74B1"/></a:accent1><a:accent2><a:srgbClr val="449B7A"/></a:accent2><a:accent3><a:srgbClr val="E88B3A"/></a:accent3><a:accent4><a:srgbClr val="8A8A8A"/></a:accent4><a:accent5><a:srgbClr val="DAE7F4"/></a:accent5><a:accent6><a:srgbClr val="F7F1E8"/></a:accent6><a:hlink><a:srgbClr val="2B74B1"/></a:hlink><a:folHlink><a:srgbClr val="449B7A"/></a:folHlink></a:clrScheme>
+    <a:fontScheme name="DTM"><a:majorFont><a:latin typeface="Microsoft YaHei"/><a:ea typeface="Microsoft YaHei"/></a:majorFont><a:minorFont><a:latin typeface="Microsoft YaHei"/><a:ea typeface="Microsoft YaHei"/></a:minorFont></a:fontScheme>
+    <a:fmtScheme name="DTM"><a:fillStyleLst><a:solidFill><a:schemeClr val="phClr"/></a:solidFill></a:fillStyleLst><a:lnStyleLst><a:ln w="9525"><a:solidFill><a:schemeClr val="phClr"/></a:solidFill></a:ln></a:lnStyleLst><a:effectStyleLst><a:effectStyle><a:effectLst/></a:effectStyle></a:effectStyleLst><a:bgFillStyleLst><a:solidFill><a:schemeClr val="phClr"/></a:solidFill></a:bgFillStyleLst></a:fmtScheme>
+  </a:themeElements>
+</a:theme>
+"@
+
+$created = [DateTime]::UtcNow.ToString("s") + "Z"
+Write-Utf8 (Join-Path $buildDir "docProps\core.xml") @"
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<cp:coreProperties xmlns:cp="http://schemas.openxmlformats.org/package/2006/metadata/core-properties" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:dcterms="http://purl.org/dc/terms/" xmlns:dcmitype="http://purl.org/dc/dcmitype/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+  <dc:title>订单与供应监测模块架构汇报</dc:title>
+  <dc:creator>Codex</dc:creator>
+  <cp:lastModifiedBy>Codex</cp:lastModifiedBy>
+  <dcterms:created xsi:type="dcterms:W3CDTF">$created</dcterms:created>
+  <dcterms:modified xsi:type="dcterms:W3CDTF">$created</dcterms:modified>
+</cp:coreProperties>
+"@
+
+Write-Utf8 (Join-Path $buildDir "docProps\app.xml") @"
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<Properties xmlns="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties" xmlns:vt="http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes">
+  <Application>Codex OpenXML Generator</Application>
+  <PresentationFormat>Widescreen</PresentationFormat>
+  <Slides>$($slides.Count)</Slides>
+  <Company>DTM</Company>
+</Properties>
+"@
+
+Add-Type -AssemblyName System.IO.Compression.FileSystem
+[System.IO.Compression.ZipFile]::CreateFromDirectory($buildDir, $outputPath)
+Remove-Item -LiteralPath $buildDir -Recurse -Force
+Write-Output $outputPath

+ 311 - 0
docs/create_monitoring_architecture_ppt.py

@@ -0,0 +1,311 @@
+# -*- coding: utf-8 -*-
+from __future__ import annotations
+
+import html
+import os
+import shutil
+import zipfile
+from datetime import datetime, timezone
+from pathlib import Path
+
+
+BASE = Path(__file__).resolve().parent
+OUT = BASE / "订单与供应监测模块架构汇报.pptx"
+BUILD = BASE / "pptx-build-py"
+EMU = 9525
+
+
+def emu(px: float) -> int:
+    return int(px * EMU)
+
+
+shape_id = 1
+
+
+def next_id() -> int:
+    global shape_id
+    shape_id += 1
+    return shape_id
+
+
+def rgb(value: str) -> str:
+    return value.strip("#").upper()
+
+
+def write(path: Path, content: str) -> None:
+    path.parent.mkdir(parents=True, exist_ok=True)
+    path.write_text(content, encoding="utf-8")
+
+
+def text_shape(text: str, x: int, y: int, w: int, h: int, size: int, color: str, bold: bool = False) -> str:
+    sid = next_id()
+    bold_attr = ' b="1"' if bold else ""
+    paragraphs = []
+    for line in str(text).splitlines() or [""]:
+        paragraphs.append(
+            f'<a:p><a:r><a:rPr lang="zh-CN" sz="{size * 100}"{bold_attr}>'
+            f'<a:solidFill><a:srgbClr val="{rgb(color)}"/></a:solidFill>'
+            f'<a:latin typeface="Microsoft YaHei"/><a:ea typeface="Microsoft YaHei"/></a:rPr>'
+            f'<a:t>{html.escape(line)}</a:t></a:r>'
+            f'<a:endParaRPr lang="zh-CN" sz="{size * 100}"/></a:p>'
+        )
+    return f"""
+<p:sp>
+  <p:nvSpPr><p:cNvPr id="{sid}" name="Text {sid}"/><p:cNvSpPr txBox="1"/><p:nvPr/></p:nvSpPr>
+  <p:spPr><a:xfrm><a:off x="{emu(x)}" y="{emu(y)}"/><a:ext cx="{emu(w)}" cy="{emu(h)}"/></a:xfrm><a:prstGeom prst="rect"><a:avLst/></a:prstGeom><a:noFill/><a:ln><a:noFill/></a:ln></p:spPr>
+  <p:txBody><a:bodyPr wrap="square" rtlCol="0"><a:spAutoFit/></a:bodyPr><a:lstStyle/>{''.join(paragraphs)}</p:txBody>
+</p:sp>
+"""
+
+
+def rect_shape(x: int, y: int, w: int, h: int, fill: str, line: str = "FFFFFF", rounded: bool = False) -> str:
+    sid = next_id()
+    geom = "roundRect" if rounded else "rect"
+    return f"""
+<p:sp>
+  <p:nvSpPr><p:cNvPr id="{sid}" name="Box {sid}"/><p:cNvSpPr/><p:nvPr/></p:nvSpPr>
+  <p:spPr><a:xfrm><a:off x="{emu(x)}" y="{emu(y)}"/><a:ext cx="{emu(w)}" cy="{emu(h)}"/></a:xfrm><a:prstGeom prst="{geom}"><a:avLst/></a:prstGeom><a:solidFill><a:srgbClr val="{rgb(fill)}"/></a:solidFill><a:ln w="9525"><a:solidFill><a:srgbClr val="{rgb(line)}"/></a:solidFill></a:ln></p:spPr>
+</p:sp>
+"""
+
+
+def card(title: str, body: str, x: int, y: int, w: int, h: int, accent: str) -> str:
+    return (
+        rect_shape(x, y, w, h, "FFFFFF", "DDDDDD", True)
+        + rect_shape(x, y, 8, h, accent, accent, False)
+        + text_shape(title, x + 22, y + 16, w - 35, 34, 17, "17304A", True)
+        + text_shape(body, x + 22, y + 58, w - 35, h - 68, 13, "555555", False)
+    )
+
+
+def node(text: str, x: int, y: int, w: int, h: int, fill: str, line: str) -> str:
+    return rect_shape(x, y, w, h, fill, line, True) + text_shape(text, x + 10, y + 15, w - 20, h - 20, 14, "17304A", True)
+
+
+def title(text: str, subtitle: str = "") -> str:
+    body = text_shape(text, 70, 42, 980, 52, 30, "17304A", True)
+    if subtitle:
+        body += text_shape(subtitle, 72, 92, 980, 30, 14, "666666")
+    body += rect_shape(70, 128, 1140, 3, "2B74B1", "2B74B1")
+    return body
+
+
+slides = [
+    {
+        "bg": "FBFAF7",
+        "body": text_shape("订单监测与供应监测", 90, 170, 900, 58, 38, "17304A", True)
+        + text_shape("模块架构、功能逻辑与系统关联汇报", 92, 238, 880, 38, 20, "666666")
+        + text_shape("面向管理层的简版说明", 94, 300, 420, 28, 15, "2B74B1", True)
+        + card("汇报重点", "1. 两个模块分别解决什么问题\n2. 数据如何进入系统并形成分析结果\n3. 与库存、产品、生命周期等模块如何关联", 760, 170, 390, 250, "2B74B1"),
+    },
+    {
+        "bg": "FFFFFF",
+        "body": title("一页总览", "两个模块都采用:数据接入 -> 数据入库 -> 后端聚合 -> 前端看板展示")
+        + card("订单监测", "关注销售侧:订单金额、商品贡献、渠道/平台表现、支付效率、退款漏损、连带购买。\n\n当前数据库已有核心表,具备较完整的数据分析基础。", 80, 170, 500, 320, "2B74B1")
+        + card("供应监测", "关注采购侧:供应商履约、采购订单、入库金额、账期、准交率、付款计划。\n\n当前数据库只有部分基础数据,需补齐采购订单、账期和交付匹配数据。", 700, 170, 500, 320, "449B7A"),
+    },
+    {
+        "bg": "FFFFFF",
+        "body": title("整体模块架构", "前端页面保持稳定,后端逐步将文件依赖切换为数据库驱动")
+        + node("数据文件上传\nCSV / Excel", 70, 220, 180, 78, "DAE7F4", "2B74B1")
+        + text_shape("→", 268, 234, 50, 50, 28, "666666", True)
+        + node("导入服务\n解析 / 清洗 / 校验", 330, 220, 210, 78, "EAF5EC", "449B7A")
+        + text_shape("→", 558, 234, 50, 50, 28, "666666", True)
+        + node("业务数据库\n订单 / 供应 / 库存", 620, 220, 210, 78, "F7F1E8", "E88B3A")
+        + text_shape("→", 848, 234, 50, 50, 28, "666666", True)
+        + node("分析服务\nMapper / 聚合逻辑", 910, 220, 210, 78, "F5F5F5", "666666")
+        + text_shape("↓", 995, 305, 50, 50, 26, "666666", True)
+        + node("管理看板\n图表 / 报表 / 对比", 910, 370, 210, 78, "FFFFFF", "2B74B1")
+        + card("架构原则", "接口尽量保持不变,减少前端改造;数据统一入库,避免依赖本地路径;分析逻辑集中在 Service 和 Mapper 层。", 120, 500, 1030, 105, "2B74B1"),
+    },
+    {
+        "bg": "FFFFFF",
+        "body": title("订单监测:功能逻辑", "订单监测已经具备数据库化基础,重点是持续完善导入和统计口径")
+        + card("核心功能", "订单价值分析:GMV、成交金额、退款金额\n商品分析:Top 商品、连带购买\n渠道分析:平台、渠道、业务单元贡献\n支付分析:平均支付耗时、支付漏斗\n经营报表:按日/月/年聚合", 70, 165, 360, 370, "2B74B1")
+        + card("实现逻辑", "1. 订单和店铺价值数据进入数据库\n2. Mapper 查询订单主表和店铺价值明细表\n3. Service 聚合指标并返回统一结构\n4. 前端看板按日期范围展示结果", 460, 165, 360, 370, "449B7A")
+        + card("当前基础", "dtm_order_main:订单主表\ndtm_shop_value_main:店铺价值明细表\n\n当前数据库中两张核心表已有较多数据,可支撑订单监测主要分析。", 850, 165, 360, 370, "E88B3A"),
+    },
+    {
+        "bg": "FFFFFF",
+        "body": title("供应监测:功能逻辑", "供应监测目标是从 Excel 文件读取升级为数据库驱动")
+        + card("核心功能", "供应商统计:订单金额、入库金额、完成率、准交率\n供应商对比:多个供应商横向比较\n付款计划:账期 + 验收/入库日期计算预计付款\n数据导入:账期、订单、入库、匹配结果", 70, 165, 360, 370, "449B7A")
+        + card("实现逻辑", "1. 上传供应监测相关 Excel\n2. 解析供应商、订单、入库、账期字段\n3. 写入供应监测数据表\n4. Service 从数据库聚合供应商指标\n5. 前端继续使用现有查询接口", 460, 165, 360, 370, "2B74B1")
+        + card("当前缺口", "数据库已有:供应商基础表、采购入库基础表。\n\n仍缺:采购订单、供应商账期、计划交货日期、实际验收日期、订单入库匹配结果。", 850, 165, 360, 370, "E88B3A"),
+    },
+    {
+        "bg": "FFFFFF",
+        "body": title("模块关联关系", "两个监测模块不是孤立看板,而是与基础数据和分析模块共享数据资产")
+        + card("订单监测关联", "产品模块:通过 SKU 关联商品、SPU、产品信息。\n生命周期模块:复用订单销售数据做热销、趋势、生命周期分析。\n店铺模块:复用平台、渠道、业务单元维度。", 90, 170, 500, 280, "2B74B1")
+        + card("供应监测关联", "库存模块:复用采购入库和库存流转数据。\n产品模块:通过 SKU 关联采购产品。\n供应商资料:复用供应商编号和名称,扩展账期和履约指标。", 690, 170, 500, 280, "449B7A")
+        + card("通用关联", "系统权限和操作日志可统一管理导入权限、导入记录和异常追踪;导入批次设计可在订单和供应两个模块复用。", 190, 495, 900, 95, "E88B3A"),
+    },
+    {
+        "bg": "FFFFFF",
+        "body": title("数据入库建设重点", "管理层需要关注的是数据完整性、稳定性和可追踪性")
+        + card("订单监测", "建设重点:完善导入批次、重复数据处理、异常行反馈。\n\n收益:订单看板稳定、历史数据可追溯、跨模块复用能力强。", 90, 170, 500, 300, "2B74B1")
+        + card("供应监测", "建设重点:补齐采购订单、账期、计划交货日期、实际验收日期、匹配结果。\n\n收益:供应商履约分析和付款计划可以稳定落地。", 690, 170, 500, 300, "449B7A"),
+    },
+    {
+        "bg": "FFFFFF",
+        "body": title("推进路径", "建议分阶段推进,先稳定数据,再扩展分析能力")
+        + node("阶段一\n确认数据源和字段口径", 80, 220, 210, 85, "DAE7F4", "2B74B1")
+        + text_shape("→", 308, 240, 50, 50, 28, "666666", True)
+        + node("阶段二\n建设导入和入库表", 370, 220, 210, 85, "EAF5EC", "449B7A")
+        + text_shape("→", 598, 240, 50, 50, 28, "666666", True)
+        + node("阶段三\n切换 Service 查询数据库", 660, 220, 230, 85, "F7F1E8", "E88B3A")
+        + text_shape("→", 908, 240, 50, 50, 28, "666666", True)
+        + node("阶段四\n完善看板和管理报表", 970, 220, 210, 85, "FFFFFF", "2B74B1")
+        + card("结论", "订单监测已有较好的数据库基础,重点是规范化导入和追踪。供应监测需要先补齐核心数据表,再将现有 Excel 分析迁移到数据库查询。", 120, 430, 1040, 130, "2B74B1"),
+    },
+]
+
+
+def slide_xml(slide: dict[str, str]) -> str:
+    return f'''<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<p:sld xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main">
+  <p:cSld>
+    <p:bg><p:bgPr><a:solidFill><a:srgbClr val="{rgb(slide["bg"])}"/></a:solidFill><a:effectLst/></p:bgPr></p:bg>
+    <p:spTree>
+      <p:nvGrpSpPr><p:cNvPr id="1" name=""/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr>
+      <p:grpSpPr><a:xfrm><a:off x="0" y="0"/><a:ext cx="0" cy="0"/><a:chOff x="0" y="0"/><a:chExt cx="0" cy="0"/></a:xfrm></p:grpSpPr>
+      {slide["body"]}
+    </p:spTree>
+  </p:cSld>
+  <p:clrMapOvr><a:masterClrMapping/></p:clrMapOvr>
+</p:sld>
+'''
+
+
+def build() -> None:
+    if OUT.exists():
+        OUT.unlink()
+    if BUILD.exists():
+        shutil.rmtree(BUILD)
+
+    for folder in [
+        "_rels",
+        "docProps",
+        "ppt/_rels",
+        "ppt/slides/_rels",
+        "ppt/slideMasters/_rels",
+        "ppt/slideLayouts/_rels",
+        "ppt/theme",
+    ]:
+        (BUILD / folder).mkdir(parents=True, exist_ok=True)
+
+    overrides = [
+        '<Override PartName="/ppt/presentation.xml" ContentType="application/vnd.openxmlformats-officedocument.presentationml.presentation.main+xml"/>',
+        '<Override PartName="/ppt/slideMasters/slideMaster1.xml" ContentType="application/vnd.openxmlformats-officedocument.presentationml.slideMaster+xml"/>',
+        '<Override PartName="/ppt/slideLayouts/slideLayout1.xml" ContentType="application/vnd.openxmlformats-officedocument.presentationml.slideLayout+xml"/>',
+        '<Override PartName="/ppt/theme/theme1.xml" ContentType="application/vnd.openxmlformats-officedocument.theme+xml"/>',
+        '<Override PartName="/docProps/core.xml" ContentType="application/vnd.openxmlformats-package.core-properties+xml"/>',
+        '<Override PartName="/docProps/app.xml" ContentType="application/vnd.openxmlformats-officedocument.extended-properties+xml"/>',
+    ]
+
+    for index, slide in enumerate(slides, start=1):
+        overrides.append(f'<Override PartName="/ppt/slides/slide{index}.xml" ContentType="application/vnd.openxmlformats-officedocument.presentationml.slide+xml"/>')
+        write(BUILD / f"ppt/slides/slide{index}.xml", slide_xml(slide))
+        write(
+            BUILD / f"ppt/slides/_rels/slide{index}.xml.rels",
+            '''<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
+  <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout" Target="../slideLayouts/slideLayout1.xml"/>
+</Relationships>
+''',
+        )
+
+    write(
+        BUILD / "[Content_Types].xml",
+        f'''<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
+  <Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
+  <Default Extension="xml" ContentType="application/xml"/>
+  {' '.join(overrides)}
+</Types>
+''',
+    )
+
+    write(
+        BUILD / "_rels/.rels",
+        '''<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
+  <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="ppt/presentation.xml"/>
+  <Relationship Id="rId2" Type="http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties" Target="docProps/core.xml"/>
+  <Relationship Id="rId3" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties" Target="docProps/app.xml"/>
+</Relationships>
+''',
+    )
+
+    slide_ids = []
+    rels = ['<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideMaster" Target="slideMasters/slideMaster1.xml"/>']
+    for index in range(1, len(slides) + 1):
+        rid = index + 1
+        slide_ids.append(f'<p:sldId id="{255 + index}" r:id="rId{rid}"/>')
+        rels.append(f'<Relationship Id="rId{rid}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide" Target="slides/slide{index}.xml"/>')
+
+    write(
+        BUILD / "ppt/presentation.xml",
+        f'''<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<p:presentation xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main">
+  <p:sldMasterIdLst><p:sldMasterId id="2147483648" r:id="rId1"/></p:sldMasterIdLst>
+  <p:sldIdLst>{''.join(slide_ids)}</p:sldIdLst>
+  <p:sldSz cx="12192000" cy="6858000" type="wide"/>
+  <p:notesSz cx="6858000" cy="9144000"/>
+  <p:defaultTextStyle/>
+</p:presentation>
+''',
+    )
+    write(BUILD / "ppt/_rels/presentation.xml.rels", f'''<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
+  {' '.join(rels)}
+</Relationships>
+''')
+
+    write(BUILD / "ppt/slideMasters/slideMaster1.xml", '''<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<p:sldMaster xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main">
+  <p:cSld><p:spTree><p:nvGrpSpPr><p:cNvPr id="1" name=""/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr><p:grpSpPr><a:xfrm><a:off x="0" y="0"/><a:ext cx="0" cy="0"/><a:chOff x="0" y="0"/><a:chExt cx="0" cy="0"/></a:xfrm></p:grpSpPr></p:spTree></p:cSld>
+  <p:clrMap bg1="lt1" tx1="dk1" bg2="lt2" tx2="dk2" accent1="accent1" accent2="accent2" accent3="accent3" accent4="accent4" accent5="accent5" accent6="accent6" hlink="hlink" folHlink="folHlink"/>
+  <p:sldLayoutIdLst><p:sldLayoutId id="2147483649" r:id="rId1"/></p:sldLayoutIdLst>
+  <p:txStyles><p:titleStyle/><p:bodyStyle/><p:otherStyle/></p:txStyles>
+</p:sldMaster>
+''')
+    write(BUILD / "ppt/slideMasters/_rels/slideMaster1.xml.rels", '''<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
+  <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout" Target="../slideLayouts/slideLayout1.xml"/>
+  <Relationship Id="rId2" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme" Target="../theme/theme1.xml"/>
+</Relationships>
+''')
+    write(BUILD / "ppt/slideLayouts/slideLayout1.xml", '''<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<p:sldLayout xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main" type="blank" preserve="1">
+  <p:cSld name="Blank"><p:spTree><p:nvGrpSpPr><p:cNvPr id="1" name=""/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr><p:grpSpPr><a:xfrm><a:off x="0" y="0"/><a:ext cx="0" cy="0"/><a:chOff x="0" y="0"/><a:chExt cx="0" cy="0"/></a:xfrm></p:grpSpPr></p:spTree></p:cSld>
+  <p:clrMapOvr><a:masterClrMapping/></p:clrMapOvr>
+</p:sldLayout>
+''')
+    write(BUILD / "ppt/slideLayouts/_rels/slideLayout1.xml.rels", '''<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
+  <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideMaster" Target="../slideMasters/slideMaster1.xml"/>
+</Relationships>
+''')
+    write(BUILD / "ppt/theme/theme1.xml", '''<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<a:theme xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" name="DTM"><a:themeElements><a:clrScheme name="DTM"><a:dk1><a:srgbClr val="17304A"/></a:dk1><a:lt1><a:srgbClr val="FFFFFF"/></a:lt1><a:dk2><a:srgbClr val="666666"/></a:dk2><a:lt2><a:srgbClr val="FBFAF7"/></a:lt2><a:accent1><a:srgbClr val="2B74B1"/></a:accent1><a:accent2><a:srgbClr val="449B7A"/></a:accent2><a:accent3><a:srgbClr val="E88B3A"/></a:accent3><a:accent4><a:srgbClr val="8A8A8A"/></a:accent4><a:accent5><a:srgbClr val="DAE7F4"/></a:accent5><a:accent6><a:srgbClr val="F7F1E8"/></a:accent6><a:hlink><a:srgbClr val="2B74B1"/></a:hlink><a:folHlink><a:srgbClr val="449B7A"/></a:folHlink></a:clrScheme><a:fontScheme name="DTM"><a:majorFont><a:latin typeface="Microsoft YaHei"/><a:ea typeface="Microsoft YaHei"/></a:majorFont><a:minorFont><a:latin typeface="Microsoft YaHei"/><a:ea typeface="Microsoft YaHei"/></a:minorFont></a:fontScheme><a:fmtScheme name="DTM"><a:fillStyleLst><a:solidFill><a:schemeClr val="phClr"/></a:solidFill></a:fillStyleLst><a:lnStyleLst><a:ln w="9525"><a:solidFill><a:schemeClr val="phClr"/></a:solidFill></a:ln></a:lnStyleLst><a:effectStyleLst><a:effectStyle><a:effectLst/></a:effectStyle></a:effectStyleLst><a:bgFillStyleLst><a:solidFill><a:schemeClr val="phClr"/></a:solidFill></a:bgFillStyleLst></a:fmtScheme></a:themeElements></a:theme>
+''')
+
+    stamp = datetime.now(timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")
+    write(BUILD / "docProps/core.xml", f'''<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<cp:coreProperties xmlns:cp="http://schemas.openxmlformats.org/package/2006/metadata/core-properties" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:dcterms="http://purl.org/dc/terms/" xmlns:dcmitype="http://purl.org/dc/dcmitype/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><dc:title>订单与供应监测模块架构汇报</dc:title><dc:creator>Codex</dc:creator><cp:lastModifiedBy>Codex</cp:lastModifiedBy><dcterms:created xsi:type="dcterms:W3CDTF">{stamp}</dcterms:created><dcterms:modified xsi:type="dcterms:W3CDTF">{stamp}</dcterms:modified></cp:coreProperties>
+''')
+    write(BUILD / "docProps/app.xml", f'''<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<Properties xmlns="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties" xmlns:vt="http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes"><Application>Codex OpenXML Generator</Application><PresentationFormat>Widescreen</PresentationFormat><Slides>{len(slides)}</Slides><Company>DTM</Company></Properties>
+''')
+
+    with zipfile.ZipFile(OUT, "w", zipfile.ZIP_DEFLATED) as zf:
+        for root, _, files in os.walk(BUILD):
+            for file in files:
+                path = Path(root) / file
+                zf.write(path, path.relative_to(BUILD).as_posix())
+    shutil.rmtree(BUILD)
+
+
+if __name__ == "__main__":
+    build()
+    print(OUT)

+ 220 - 0
docs/order-monitoring-architecture-current.svg

@@ -0,0 +1,220 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="2000" height="1050" viewBox="0 0 2000 1050">
+  <defs>
+    <marker id="arrow" markerWidth="10" markerHeight="10" refX="8" refY="3" orient="auto" markerUnits="strokeWidth">
+      <path d="M0,0 L0,6 L8,3 z" fill="#666"/>
+    </marker>
+    <style>
+      text { font-family:"Microsoft YaHei", Arial, sans-serif; fill:#1f2937; }
+      .title { font-size:28px; font-weight:700; }
+      .subtitle { font-size:18px; }
+      .panel-title { font-size:17px; font-weight:700; }
+      .panel-subtitle { font-size:14px; }
+      .box-text { font-size:14px; }
+      .small { font-size:12px; }
+      .stage1 { fill:#0ea5e9; font-size:12px; font-weight:700; }
+      .stage2 { fill:#d97706; font-size:12px; font-weight:700; }
+      .stage3 { fill:#9333ea; font-size:12px; font-weight:700; }
+      .top { fill:#dbeafe; stroke:#bfdbfe; stroke-width:1.5; }
+      .head { fill:#d9ead7; stroke:#c4dfc2; stroke-width:1.2; }
+      .feature { fill:#c7e9fb; stroke:#b7def2; stroke-width:1; }
+      .output { fill:#f8caca; stroke:#f3b5b5; stroke-width:1; }
+      .rule { fill:#fff2c8; stroke:#f5dda3; stroke-width:1; }
+      .panel-blue { fill:none; stroke:#58c7f7; stroke-width:1.5; stroke-dasharray:4 5; }
+      .panel-red { fill:none; stroke:#f4a6a6; stroke-width:1.4; stroke-dasharray:4 5; }
+      .panel-yellow { fill:none; stroke:#f2d16b; stroke-width:1.4; stroke-dasharray:4 5; }
+      .line { stroke:#666; stroke-width:1.6; fill:none; marker-end:url(#arrow); }
+      .line-soft { stroke:#999; stroke-width:1.2; fill:none; marker-end:url(#arrow); }
+    </style>
+  </defs>
+
+  <rect width="2000" height="1050" fill="#fff"/>
+
+  <rect x="760" y="28" width="480" height="72" rx="6" class="top"/>
+  <text x="1000" y="58" text-anchor="middle" class="title">订单监测模块</text>
+  <text x="1000" y="84" text-anchor="middle" class="subtitle">店铺运营报表、订单价值分析、渠道店铺分析、利润模拟与组织效率看板</text>
+
+  <path d="M1000 100 L1000 145 L180 145 L180 188" class="line"/>
+  <path d="M1000 145 L565 145 L565 188" class="line"/>
+  <path d="M1000 145 L960 145 L960 188" class="line"/>
+  <path d="M1000 145 L1355 145 L1355 188" class="line"/>
+  <path d="M1000 145 L1740 145 L1740 188" class="line"/>
+
+  <!-- 店铺运营报表 -->
+  <rect x="24" y="190" width="360" height="300" class="panel-blue"/>
+  <rect x="118" y="202" width="172" height="48" rx="5" class="head"/>
+  <text x="204" y="224" text-anchor="middle" class="panel-title">店铺运营报表</text>
+  <text x="204" y="243" text-anchor="middle" class="panel-subtitle">按模板生成经营报表</text>
+  <text x="354" y="214" text-anchor="middle" class="stage1">第一阶段</text>
+  <g text-anchor="middle" class="box-text">
+    <rect x="48" y="268" width="145" height="40" rx="4" class="feature"/><text x="120" y="293">报表模板</text>
+    <rect x="215" y="268" width="145" height="40" rx="4" class="feature"/><text x="288" y="293">每日销售数据</text>
+    <rect x="48" y="322" width="145" height="40" rx="4" class="feature"/><text x="120" y="347">平台销售额</text>
+    <rect x="215" y="322" width="145" height="40" rx="4" class="feature"/><text x="288" y="347">部门销售额</text>
+    <rect x="48" y="376" width="145" height="40" rx="4" class="feature"/><text x="120" y="401">产品利润核算</text>
+    <rect x="215" y="376" width="145" height="40" rx="4" class="feature"/><text x="288" y="401">毛利报表</text>
+    <rect x="132" y="430" width="145" height="40" rx="4" class="feature"/><text x="204" y="455">月度/季度汇总</text>
+  </g>
+
+  <!-- 订单价值分析 -->
+  <rect x="425" y="190" width="360" height="300" class="panel-blue"/>
+  <rect x="520" y="202" width="172" height="48" rx="5" class="head"/>
+  <text x="606" y="224" text-anchor="middle" class="panel-title">订单价值分析</text>
+  <text x="606" y="243" text-anchor="middle" class="panel-subtitle">销售额、商品贡献与支付效率</text>
+  <text x="755" y="214" text-anchor="middle" class="stage1">第一阶段</text>
+  <g text-anchor="middle" class="box-text">
+    <rect x="450" y="268" width="145" height="40" rx="4" class="feature"/><text x="523" y="293">GMV 汇总</text>
+    <rect x="617" y="268" width="145" height="40" rx="4" class="feature"/><text x="690" y="293">Top 商品</text>
+    <rect x="450" y="322" width="145" height="40" rx="4" class="feature"/><text x="523" y="347">销售占比</text>
+    <rect x="617" y="322" width="145" height="40" rx="4" class="feature"/><text x="690" y="347">高价值订单</text>
+    <rect x="450" y="376" width="145" height="40" rx="4" class="feature"/><text x="523" y="401">支付时长</text>
+    <rect x="617" y="376" width="145" height="40" rx="4" class="feature"/><text x="690" y="401">退款流失</text>
+    <rect x="525" y="430" width="165" height="40" rx="4" class="feature"/><text x="607" y="455">支付决策漏斗</text>
+  </g>
+
+  <!-- 渠道店铺分析 -->
+  <rect x="825" y="190" width="360" height="300" class="panel-blue"/>
+  <rect x="920" y="202" width="172" height="48" rx="5" class="head"/>
+  <text x="1006" y="224" text-anchor="middle" class="panel-title">渠道店铺分析</text>
+  <text x="1006" y="243" text-anchor="middle" class="panel-subtitle">平台、渠道、部门经营表现</text>
+  <text x="1155" y="214" text-anchor="middle" class="stage1">第一阶段</text>
+  <g text-anchor="middle" class="box-text">
+    <rect x="850" y="268" width="145" height="40" rx="4" class="feature"/><text x="923" y="293">渠道贡献</text>
+    <rect x="1017" y="268" width="145" height="40" rx="4" class="feature"/><text x="1090" y="293">平台贡献</text>
+    <rect x="850" y="322" width="145" height="40" rx="4" class="feature"/><text x="923" y="347">ROI 指标</text>
+    <rect x="1017" y="322" width="145" height="40" rx="4" class="feature"/><text x="1090" y="347">部门效率</text>
+    <rect x="850" y="376" width="145" height="40" rx="4" class="feature"/><text x="923" y="401">交叉销售商品</text>
+    <rect x="1017" y="376" width="145" height="40" rx="4" class="feature"/><text x="1090" y="401">渠道丰富度</text>
+    <rect x="925" y="430" width="165" height="40" rx="4" class="feature"/><text x="1007" y="455">经营报表查询</text>
+  </g>
+
+  <!-- 订单利润模拟 -->
+  <rect x="1225" y="190" width="360" height="300" class="panel-blue"/>
+  <rect x="1320" y="202" width="172" height="48" rx="5" class="head"/>
+  <text x="1406" y="224" text-anchor="middle" class="panel-title">订单利润模拟</text>
+  <text x="1406" y="243" text-anchor="middle" class="panel-subtitle">输入费用并实时测算毛利</text>
+  <text x="1555" y="214" text-anchor="middle" class="stage2">第二阶段</text>
+  <g text-anchor="middle" class="box-text">
+    <rect x="1250" y="268" width="145" height="40" rx="4" class="feature"/><text x="1323" y="293">SKU 输入</text>
+    <rect x="1417" y="268" width="145" height="40" rx="4" class="feature"/><text x="1490" y="293">数量输入</text>
+    <rect x="1250" y="322" width="145" height="40" rx="4" class="feature"/><text x="1323" y="347">供货价</text>
+    <rect x="1417" y="322" width="145" height="40" rx="4" class="feature"/><text x="1490" y="347">系统费用率</text>
+    <rect x="1250" y="376" width="145" height="40" rx="4" class="feature"/><text x="1323" y="401">毛利额测算</text>
+    <rect x="1417" y="376" width="145" height="40" rx="4" class="feature"/><text x="1490" y="401">毛利率测算</text>
+    <rect x="1325" y="430" width="165" height="40" rx="4" class="feature"/><text x="1407" y="455">判断指标</text>
+  </g>
+
+  <!-- 组织效率看板 -->
+  <rect x="1625" y="190" width="345" height="300" class="panel-blue"/>
+  <rect x="1712" y="202" width="172" height="48" rx="5" class="head"/>
+  <text x="1798" y="224" text-anchor="middle" class="panel-title">组织效率看板</text>
+  <text x="1798" y="243" text-anchor="middle" class="panel-subtitle">部门、团队、人员贡献分析</text>
+  <text x="1940" y="214" text-anchor="middle" class="stage3">第三阶段</text>
+  <g text-anchor="middle" class="box-text">
+    <rect x="1650" y="280" width="145" height="42" rx="4" class="feature"/><text x="1723" y="305">岗位预算</text>
+    <rect x="1810" y="280" width="145" height="42" rx="4" class="feature"/><text x="1883" y="305">人员销售额</text>
+    <rect x="1650" y="350" width="145" height="42" rx="4" class="feature"/><text x="1723" y="375">完成率</text>
+    <rect x="1810" y="350" width="145" height="42" rx="4" class="feature"/><text x="1883" y="375">贡献占比</text>
+    <rect x="1730" y="420" width="145" height="42" rx="4" class="feature"/><text x="1803" y="445">月度效率报告</text>
+  </g>
+
+  <path d="M205 490 L205 625" class="line"/>
+  <path d="M605 490 L605 625" class="line"/>
+  <path d="M1005 490 L1005 530 L645 530 L645 625" class="line"/>
+  <path d="M1405 490 L1405 625" class="line"/>
+  <path d="M1800 490 L1800 530 L855 530 L855 625" class="line"/>
+
+  <!-- 业务输出 -->
+  <rect x="110" y="565" width="890" height="160" class="panel-red"/>
+  <text x="555" y="595" text-anchor="middle" class="title" font-size="22">业务输出</text>
+  <g text-anchor="middle" class="box-text">
+    <rect x="135" y="625" width="180" height="65" rx="5" class="output"/>
+    <text x="225" y="650">店铺运营报表</text>
+    <text x="225" y="670" class="small">月度、季度、年度汇总</text>
+    <text x="285" y="642" class="stage1">一阶段</text>
+
+    <rect x="345" y="625" width="180" height="65" rx="5" class="output"/>
+    <text x="435" y="650">订单价值看板</text>
+    <text x="435" y="670" class="small">销售额、商品、漏斗</text>
+    <text x="495" y="642" class="stage1">一阶段</text>
+
+    <rect x="555" y="625" width="180" height="65" rx="5" class="output"/>
+    <text x="645" y="650">渠道店铺评估</text>
+    <text x="645" y="670" class="small">平台、渠道、部门效率</text>
+    <text x="705" y="642" class="stage1">一阶段</text>
+
+    <rect x="765" y="625" width="180" height="65" rx="5" class="output"/>
+    <text x="855" y="650">利润与效率报告</text>
+    <text x="855" y="670" class="small">毛利测算、组织效率</text>
+    <text x="915" y="642" class="stage2">二阶段</text>
+    <text x="915" y="684" class="stage3">三阶段</text>
+  </g>
+
+  <!-- 核心业务逻辑 -->
+  <rect x="1030" y="540" width="930" height="405" class="panel-yellow"/>
+  <text x="1495" y="575" text-anchor="middle" class="title" font-size="22">核心业务逻辑</text>
+  <g text-anchor="middle" class="box-text">
+    <rect x="1060" y="660" width="155" height="70" rx="5" class="rule"/>
+    <text x="1138" y="682">数据来源</text>
+    <text x="1138" y="702" class="small">订单、店铺销售</text>
+    <text x="1138" y="718" class="small">产品基础数据</text>
+    <text x="1200" y="652" class="stage1">一阶段</text>
+
+    <rect x="1280" y="600" width="170" height="75" rx="5" class="rule"/>
+    <text x="1365" y="623">模板报表口径</text>
+    <text x="1365" y="643" class="small">按统一模板取数</text>
+    <text x="1365" y="659" class="small">生成运营报表</text>
+
+    <rect x="1280" y="760" width="170" height="75" rx="5" class="rule"/>
+    <text x="1365" y="783">订单价值口径</text>
+    <text x="1365" y="803" class="small">实付金额优先</text>
+    <text x="1365" y="819" class="small">按日期和商品汇总</text>
+
+    <rect x="1530" y="600" width="170" height="75" rx="5" class="rule"/>
+    <text x="1615" y="623">利润核算规则</text>
+    <text x="1615" y="643" class="small">销售额减供货价</text>
+    <text x="1615" y="659" class="small">扣除平台和运营费用</text>
+    <text x="1680" y="592" class="stage2">二阶段</text>
+
+    <rect x="1530" y="760" width="170" height="75" rx="5" class="rule"/>
+    <text x="1615" y="783">判断指标</text>
+    <text x="1615" y="803" class="small">平台费、运营费</text>
+    <text x="1615" y="819" class="small">退货比例、毛利率</text>
+    <text x="1680" y="752" class="stage2">二阶段</text>
+
+    <rect x="1760" y="600" width="160" height="75" rx="5" class="rule"/>
+    <text x="1840" y="623">组织效率规则</text>
+    <text x="1840" y="643" class="small">预算对比实际销售</text>
+    <text x="1840" y="659" class="small">计算完成率和贡献</text>
+    <text x="1900" y="592" class="stage3">三阶段</text>
+
+    <rect x="1760" y="760" width="160" height="75" rx="5" class="rule"/>
+    <text x="1840" y="783">输出规则</text>
+    <text x="1840" y="803" class="small">毛利报表、效率报告</text>
+    <text x="1840" y="819" class="small">按月更新</text>
+    <text x="1900" y="752" class="stage3">三阶段</text>
+
+    <rect x="1280" y="870" width="170" height="52" rx="5" class="rule"/>
+    <text x="1365" y="892">产品利润报表</text>
+    <text x="1365" y="910" class="small">品名、毛利额、毛利率</text>
+    <text x="1430" y="862" class="stage1">一阶段</text>
+
+    <rect x="1530" y="870" width="170" height="52" rx="5" class="rule"/>
+    <text x="1615" y="892">订单利润模拟器</text>
+    <text x="1615" y="910" class="small">输入框实时计算</text>
+    <text x="1680" y="862" class="stage2">二阶段</text>
+
+    <rect x="1760" y="870" width="160" height="52" rx="5" class="rule"/>
+    <text x="1840" y="892">组织效率看板</text>
+    <text x="1840" y="910" class="small">部门/个人绩效报告</text>
+    <text x="1900" y="862" class="stage3">三阶段</text>
+  </g>
+
+  <path d="M1215 695 L1280 638" class="line"/>
+  <path d="M1215 695 L1280 798" class="line"/>
+  <path d="M1450 638 L1530 638" class="line"/>
+  <path d="M1450 798 L1530 798" class="line"/>
+  <path d="M1700 638 L1760 638" class="line"/>
+  <path d="M1700 798 L1760 798" class="line"/>
+  <path d="M1450 896 L1530 896" class="line-soft"/>
+  <path d="M1700 896 L1760 896" class="line-soft"/>
+</svg>

+ 220 - 0
docs/order-monitoring-architecture.svg

@@ -0,0 +1,220 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="2000" height="1050" viewBox="0 0 2000 1050">
+  <defs>
+    <marker id="arrow" markerWidth="10" markerHeight="10" refX="8" refY="3" orient="auto" markerUnits="strokeWidth">
+      <path d="M0,0 L0,6 L8,3 z" fill="#666"/>
+    </marker>
+    <style>
+      text { font-family:"Microsoft YaHei", Arial, sans-serif; fill:#1f2937; }
+      .title { font-size:28px; font-weight:700; }
+      .subtitle { font-size:18px; }
+      .panel-title { font-size:17px; font-weight:700; }
+      .panel-subtitle { font-size:14px; }
+      .box-text { font-size:14px; }
+      .small { font-size:12px; }
+      .stage1 { fill:#0ea5e9; font-size:12px; font-weight:700; }
+      .stage2 { fill:#d97706; font-size:12px; font-weight:700; }
+      .stage3 { fill:#9333ea; font-size:12px; font-weight:700; }
+      .top { fill:#dbeafe; stroke:#bfdbfe; stroke-width:1.5; }
+      .head { fill:#d9ead7; stroke:#c4dfc2; stroke-width:1.2; }
+      .feature { fill:#c7e9fb; stroke:#b7def2; stroke-width:1; }
+      .output { fill:#f8caca; stroke:#f3b5b5; stroke-width:1; }
+      .rule { fill:#fff2c8; stroke:#f5dda3; stroke-width:1; }
+      .panel-blue { fill:none; stroke:#58c7f7; stroke-width:1.5; stroke-dasharray:4 5; }
+      .panel-red { fill:none; stroke:#f4a6a6; stroke-width:1.4; stroke-dasharray:4 5; }
+      .panel-yellow { fill:none; stroke:#f2d16b; stroke-width:1.4; stroke-dasharray:4 5; }
+      .line { stroke:#666; stroke-width:1.6; fill:none; marker-end:url(#arrow); }
+      .line-soft { stroke:#999; stroke-width:1.2; fill:none; marker-end:url(#arrow); }
+    </style>
+  </defs>
+
+  <rect width="2000" height="1050" fill="#fff"/>
+
+  <rect x="760" y="28" width="480" height="72" rx="6" class="top"/>
+  <text x="1000" y="58" text-anchor="middle" class="title">订单监测模块</text>
+  <text x="1000" y="84" text-anchor="middle" class="subtitle">店铺运营报表、订单价值分析、渠道店铺分析、利润模拟与组织效率看板</text>
+
+  <path d="M1000 100 L1000 145 L180 145 L180 188" class="line"/>
+  <path d="M1000 145 L565 145 L565 188" class="line"/>
+  <path d="M1000 145 L960 145 L960 188" class="line"/>
+  <path d="M1000 145 L1355 145 L1355 188" class="line"/>
+  <path d="M1000 145 L1740 145 L1740 188" class="line"/>
+
+  <!-- 店铺运营报表 -->
+  <rect x="24" y="190" width="360" height="300" class="panel-blue"/>
+  <rect x="118" y="202" width="172" height="48" rx="5" class="head"/>
+  <text x="204" y="224" text-anchor="middle" class="panel-title">店铺运营报表</text>
+  <text x="204" y="243" text-anchor="middle" class="panel-subtitle">按模板生成经营报表</text>
+  <text x="354" y="214" text-anchor="middle" class="stage1">第一阶段</text>
+  <g text-anchor="middle" class="box-text">
+    <rect x="48" y="268" width="145" height="40" rx="4" class="feature"/><text x="120" y="293">报表模板</text>
+    <rect x="215" y="268" width="145" height="40" rx="4" class="feature"/><text x="288" y="293">每日销售数据</text>
+    <rect x="48" y="322" width="145" height="40" rx="4" class="feature"/><text x="120" y="347">平台销售额</text>
+    <rect x="215" y="322" width="145" height="40" rx="4" class="feature"/><text x="288" y="347">部门销售额</text>
+    <rect x="48" y="376" width="145" height="40" rx="4" class="feature"/><text x="120" y="401">产品利润核算</text>
+    <rect x="215" y="376" width="145" height="40" rx="4" class="feature"/><text x="288" y="401">毛利报表</text>
+    <rect x="132" y="430" width="145" height="40" rx="4" class="feature"/><text x="204" y="455">月度/季度汇总</text>
+  </g>
+
+  <!-- 订单价值分析 -->
+  <rect x="425" y="190" width="360" height="300" class="panel-blue"/>
+  <rect x="520" y="202" width="172" height="48" rx="5" class="head"/>
+  <text x="606" y="224" text-anchor="middle" class="panel-title">订单价值分析</text>
+  <text x="606" y="243" text-anchor="middle" class="panel-subtitle">销售额、商品贡献与支付效率</text>
+  <text x="755" y="214" text-anchor="middle" class="stage1">第一阶段</text>
+  <g text-anchor="middle" class="box-text">
+    <rect x="450" y="268" width="145" height="40" rx="4" class="feature"/><text x="523" y="293">GMV 汇总</text>
+    <rect x="617" y="268" width="145" height="40" rx="4" class="feature"/><text x="690" y="293">Top 商品</text>
+    <rect x="450" y="322" width="145" height="40" rx="4" class="feature"/><text x="523" y="347">销售占比</text>
+    <rect x="617" y="322" width="145" height="40" rx="4" class="feature"/><text x="690" y="347">高价值订单</text>
+    <rect x="450" y="376" width="145" height="40" rx="4" class="feature"/><text x="523" y="401">支付时长</text>
+    <rect x="617" y="376" width="145" height="40" rx="4" class="feature"/><text x="690" y="401">退款流失</text>
+    <rect x="525" y="430" width="165" height="40" rx="4" class="feature"/><text x="607" y="455">支付决策漏斗</text>
+  </g>
+
+  <!-- 渠道店铺分析 -->
+  <rect x="825" y="190" width="360" height="300" class="panel-blue"/>
+  <rect x="920" y="202" width="172" height="48" rx="5" class="head"/>
+  <text x="1006" y="224" text-anchor="middle" class="panel-title">渠道店铺分析</text>
+  <text x="1006" y="243" text-anchor="middle" class="panel-subtitle">平台、渠道、部门经营表现</text>
+  <text x="1155" y="214" text-anchor="middle" class="stage1">第一阶段</text>
+  <g text-anchor="middle" class="box-text">
+    <rect x="850" y="268" width="145" height="40" rx="4" class="feature"/><text x="923" y="293">渠道贡献</text>
+    <rect x="1017" y="268" width="145" height="40" rx="4" class="feature"/><text x="1090" y="293">平台贡献</text>
+    <rect x="850" y="322" width="145" height="40" rx="4" class="feature"/><text x="923" y="347">ROI 指标</text>
+    <rect x="1017" y="322" width="145" height="40" rx="4" class="feature"/><text x="1090" y="347">部门效率</text>
+    <rect x="850" y="376" width="145" height="40" rx="4" class="feature"/><text x="923" y="401">交叉销售商品</text>
+    <rect x="1017" y="376" width="145" height="40" rx="4" class="feature"/><text x="1090" y="401">渠道丰富度</text>
+    <rect x="925" y="430" width="165" height="40" rx="4" class="feature"/><text x="1007" y="455">经营报表查询</text>
+  </g>
+
+  <!-- 订单利润模拟 -->
+  <rect x="1225" y="190" width="360" height="300" class="panel-blue"/>
+  <rect x="1320" y="202" width="172" height="48" rx="5" class="head"/>
+  <text x="1406" y="224" text-anchor="middle" class="panel-title">订单利润模拟</text>
+  <text x="1406" y="243" text-anchor="middle" class="panel-subtitle">输入费用并实时测算毛利</text>
+  <text x="1555" y="214" text-anchor="middle" class="stage2">第二阶段</text>
+  <g text-anchor="middle" class="box-text">
+    <rect x="1250" y="268" width="145" height="40" rx="4" class="feature"/><text x="1323" y="293">SKU 输入</text>
+    <rect x="1417" y="268" width="145" height="40" rx="4" class="feature"/><text x="1490" y="293">数量输入</text>
+    <rect x="1250" y="322" width="145" height="40" rx="4" class="feature"/><text x="1323" y="347">供货价</text>
+    <rect x="1417" y="322" width="145" height="40" rx="4" class="feature"/><text x="1490" y="347">系统费用率</text>
+    <rect x="1250" y="376" width="145" height="40" rx="4" class="feature"/><text x="1323" y="401">毛利额测算</text>
+    <rect x="1417" y="376" width="145" height="40" rx="4" class="feature"/><text x="1490" y="401">毛利率测算</text>
+    <rect x="1325" y="430" width="165" height="40" rx="4" class="feature"/><text x="1407" y="455">判断指标</text>
+  </g>
+
+  <!-- 组织效率看板 -->
+  <rect x="1625" y="190" width="345" height="300" class="panel-blue"/>
+  <rect x="1712" y="202" width="172" height="48" rx="5" class="head"/>
+  <text x="1798" y="224" text-anchor="middle" class="panel-title">组织效率看板</text>
+  <text x="1798" y="243" text-anchor="middle" class="panel-subtitle">部门、团队、人员贡献分析</text>
+  <text x="1940" y="214" text-anchor="middle" class="stage3">第三阶段</text>
+  <g text-anchor="middle" class="box-text">
+    <rect x="1650" y="280" width="145" height="42" rx="4" class="feature"/><text x="1723" y="305">岗位预算</text>
+    <rect x="1810" y="280" width="145" height="42" rx="4" class="feature"/><text x="1883" y="305">人员销售额</text>
+    <rect x="1650" y="350" width="145" height="42" rx="4" class="feature"/><text x="1723" y="375">完成率</text>
+    <rect x="1810" y="350" width="145" height="42" rx="4" class="feature"/><text x="1883" y="375">贡献占比</text>
+    <rect x="1730" y="420" width="145" height="42" rx="4" class="feature"/><text x="1803" y="445">月度效率报告</text>
+  </g>
+
+  <path d="M205 490 L205 625" class="line"/>
+  <path d="M605 490 L605 625" class="line"/>
+  <path d="M1005 490 L1005 530 L645 530 L645 625" class="line"/>
+  <path d="M1405 490 L1405 625" class="line"/>
+  <path d="M1800 490 L1800 530 L855 530 L855 625" class="line"/>
+
+  <!-- 业务输出 -->
+  <rect x="110" y="565" width="890" height="160" class="panel-red"/>
+  <text x="555" y="595" text-anchor="middle" class="title" font-size="22">业务输出</text>
+  <g text-anchor="middle" class="box-text">
+    <rect x="135" y="625" width="180" height="65" rx="5" class="output"/>
+    <text x="225" y="650">店铺运营报表</text>
+    <text x="225" y="670" class="small">月度、季度、年度汇总</text>
+    <text x="285" y="642" class="stage1">一阶段</text>
+
+    <rect x="345" y="625" width="180" height="65" rx="5" class="output"/>
+    <text x="435" y="650">订单价值看板</text>
+    <text x="435" y="670" class="small">销售额、商品、漏斗</text>
+    <text x="495" y="642" class="stage1">一阶段</text>
+
+    <rect x="555" y="625" width="180" height="65" rx="5" class="output"/>
+    <text x="645" y="650">渠道店铺评估</text>
+    <text x="645" y="670" class="small">平台、渠道、部门效率</text>
+    <text x="705" y="642" class="stage1">一阶段</text>
+
+    <rect x="765" y="625" width="180" height="65" rx="5" class="output"/>
+    <text x="855" y="650">利润与效率报告</text>
+    <text x="855" y="670" class="small">毛利测算、组织效率</text>
+    <text x="915" y="642" class="stage2">二阶段</text>
+    <text x="915" y="684" class="stage3">三阶段</text>
+  </g>
+
+  <!-- 核心业务逻辑 -->
+  <rect x="1030" y="540" width="930" height="405" class="panel-yellow"/>
+  <text x="1495" y="575" text-anchor="middle" class="title" font-size="22">核心业务逻辑</text>
+  <g text-anchor="middle" class="box-text">
+    <rect x="1060" y="660" width="155" height="70" rx="5" class="rule"/>
+    <text x="1138" y="682">数据来源</text>
+    <text x="1138" y="702" class="small">订单、店铺销售</text>
+    <text x="1138" y="718" class="small">产品基础数据</text>
+    <text x="1200" y="652" class="stage1">一阶段</text>
+
+    <rect x="1280" y="600" width="170" height="75" rx="5" class="rule"/>
+    <text x="1365" y="623">模板报表口径</text>
+    <text x="1365" y="643" class="small">按统一模板取数</text>
+    <text x="1365" y="659" class="small">生成运营报表</text>
+
+    <rect x="1280" y="760" width="170" height="75" rx="5" class="rule"/>
+    <text x="1365" y="783">订单价值口径</text>
+    <text x="1365" y="803" class="small">实付金额优先</text>
+    <text x="1365" y="819" class="small">按日期和商品汇总</text>
+
+    <rect x="1530" y="600" width="170" height="75" rx="5" class="rule"/>
+    <text x="1615" y="623">利润核算规则</text>
+    <text x="1615" y="643" class="small">销售额减供货价</text>
+    <text x="1615" y="659" class="small">扣除平台和运营费用</text>
+    <text x="1680" y="592" class="stage2">二阶段</text>
+
+    <rect x="1530" y="760" width="170" height="75" rx="5" class="rule"/>
+    <text x="1615" y="783">判断指标</text>
+    <text x="1615" y="803" class="small">平台费、运营费</text>
+    <text x="1615" y="819" class="small">退货比例、毛利率</text>
+    <text x="1680" y="752" class="stage2">二阶段</text>
+
+    <rect x="1760" y="600" width="160" height="75" rx="5" class="rule"/>
+    <text x="1840" y="623">组织效率规则</text>
+    <text x="1840" y="643" class="small">预算对比实际销售</text>
+    <text x="1840" y="659" class="small">计算完成率和贡献</text>
+    <text x="1900" y="592" class="stage3">三阶段</text>
+
+    <rect x="1760" y="760" width="160" height="75" rx="5" class="rule"/>
+    <text x="1840" y="783">输出规则</text>
+    <text x="1840" y="803" class="small">毛利报表、效率报告</text>
+    <text x="1840" y="819" class="small">按月更新</text>
+    <text x="1900" y="752" class="stage3">三阶段</text>
+
+    <rect x="1280" y="870" width="170" height="52" rx="5" class="rule"/>
+    <text x="1365" y="892">产品利润报表</text>
+    <text x="1365" y="910" class="small">品名、毛利额、毛利率</text>
+    <text x="1430" y="862" class="stage1">一阶段</text>
+
+    <rect x="1530" y="870" width="170" height="52" rx="5" class="rule"/>
+    <text x="1615" y="892">订单利润模拟器</text>
+    <text x="1615" y="910" class="small">输入框实时计算</text>
+    <text x="1680" y="862" class="stage2">二阶段</text>
+
+    <rect x="1760" y="870" width="160" height="52" rx="5" class="rule"/>
+    <text x="1840" y="892">组织效率看板</text>
+    <text x="1840" y="910" class="small">部门/个人绩效报告</text>
+    <text x="1900" y="862" class="stage3">三阶段</text>
+  </g>
+
+  <path d="M1215 695 L1280 638" class="line"/>
+  <path d="M1215 695 L1280 798" class="line"/>
+  <path d="M1450 638 L1530 638" class="line"/>
+  <path d="M1450 798 L1530 798" class="line"/>
+  <path d="M1700 638 L1760 638" class="line"/>
+  <path d="M1700 798 L1760 798" class="line"/>
+  <path d="M1450 896 L1530 896" class="line-soft"/>
+  <path d="M1700 896 L1760 896" class="line-soft"/>
+</svg>

+ 138 - 0
docs/order-monitoring-module-design.md

@@ -0,0 +1,138 @@
+# 订单监测模块设计文档
+
+## 1. 文档范围
+
+本文档说明当前系统中订单监测模块的功能设计、实现逻辑、数据来源以及与其他模块的关联关系。
+
+文档内容基于当前代码和数据库已有能力整理,覆盖订单价值分析、店铺价值分析、渠道贡献、关联购买、支付转化、运营报表等已接入的功能。
+
+## 2. 模块定位
+
+订单监测模块用于对订单和店铺销售数据进行统计分析,辅助业务人员观察订单规模、销售贡献、支付效率、退款流失、渠道表现和商品组合关系。
+
+模块当前主要面向两类数据:
+
+- 订单明细数据:来自订单主表,用于 GMV、Top 商品、退款流失、支付耗时、支付漏斗、关联购买等分析。
+- 店铺销售数据:来自店铺价值明细表,用于平台、渠道、事业部、商品销售贡献和运营报表分析。
+
+## 3. 功能设计
+
+### 3.1 数据上传
+
+订单监测保留数据上传入口,用于将外部订单或销售文件导入数据库。
+
+当前已实现的上传能力包括:
+
+- 订单数据上传:导入订单主表数据。
+- 店铺销售数据上传:导入店铺价值明细数据。
+- 上传后由后端解析文件并写入对应业务表,前端分析页面再从数据库读取统计结果。
+
+### 3.2 订单价值分析
+
+订单价值分析围绕订单主表进行统计,支持按日期范围筛选。
+
+当前功能包括:
+
+- GMV 统计:统计指定日期范围内的实付金额合计。
+- Top5 商品销售额:按商品编码汇总销售额并取前五名。
+- Top5 销售占比:计算 Top5 商品销售额占整体销售额的比例。
+- R 大客户或高价值订单分析:根据订单金额分布识别高价值订单区间。
+- 平均支付时长:计算下单时间到支付时间的平均耗时。
+- 退款流失率:结合退款金额与成功支付金额计算订单流失情况。
+- 支付决策漏斗:按支付耗时区间统计用户支付转化过程。
+
+### 3.3 关联购买分析
+
+关联购买分析用于识别同一支付单或同一购买行为下经常一起出现的商品组合。
+
+当前逻辑以支付单号作为购买行为标识,按商品编码组合统计共同出现次数,输出高频商品组合列表。该结果可用于搭配销售、组合推荐或商品运营分析。
+
+### 3.4 店铺价值分析
+
+店铺价值分析基于店铺销售明细数据,面向平台、渠道、事业部和商品维度进行统计。
+
+当前功能包括:
+
+- 平台订单数和销售额统计。
+- 渠道销量、销售额贡献统计。
+- 事业部销量和销售额贡献统计。
+- Top 商品销售贡献分析。
+- 跨平台销售商品识别。
+- 事业部效率分析。
+- 渠道商品多样性分析。
+- 渠道 ROI 数值查询。
+
+### 3.5 运营报表
+
+运营报表用于按日期、月份或年份聚合店铺销售数据,输出平台、渠道、订单数、销量、销售额、客单价等指标。
+
+当前支持:
+
+- 报表列表查询。
+- 按周期聚合。
+- 报表导出。
+
+## 4. 数据与业务逻辑
+
+### 4.1 数据来源
+
+当前订单监测主要使用以下数据库表:
+
+- 订单主表:保存订单编号、商品编码、商品名称、价格、数量、应付金额、实付金额、退款金额、下单时间、支付时间、支付单号等信息。
+- 店铺价值明细表:保存平台、渠道、事业部、商品编码、销量、销售额、统计日期等信息。
+- 产品基础表:在部分分析中作为商品编码、商品名称等基础信息补充来源。
+
+### 4.2 日期筛选
+
+订单分析使用订单创建时间作为日期筛选口径。
+
+店铺价值分析使用统计日期作为日期筛选口径;当统计日期为空时,系统会通过年、月、日字段组合出统计日期。
+
+### 4.3 金额口径
+
+订单价值类分析优先使用实付金额。部分支付金额分布和流失分析中,当实付金额为空或小于等于 0 时,会使用应付金额作为补充口径。
+
+店铺价值类分析使用店铺销售明细表中的销售金额字段进行汇总。
+
+### 4.4 支付效率口径
+
+平均支付时长以订单创建时间和支付时间的时间差计算。
+
+支付决策漏斗将订单划分为:
+
+- 5 分钟内支付。
+- 5 到 30 分钟内支付。
+- 30 分钟后支付。
+- 未支付。
+
+### 4.5 退款流失口径
+
+退款流失分析从订单主表中读取退款金额,并与成功支付金额进行对比,用于衡量订单成交后的金额流失情况。
+
+### 4.6 关联购买口径
+
+关联购买使用支付单号聚合同一次购买行为中的商品。系统会对同一支付单下不同商品编码进行组合,统计共同出现次数,并按次数降序输出。
+
+## 5. 与其他模块的关联关系
+
+### 5.1 数据上传模块
+
+订单监测依赖数据上传模块完成原始订单和店铺销售文件入库。只有数据入库后,分析页面才能从数据库中读取统计结果。
+
+### 5.2 产品基础数据
+
+订单监测以商品编码作为核心分析对象,部分场景需要结合产品基础信息补充商品名称或分类信息。
+
+### 5.3 生命周期模块
+
+生命周期模块同样使用订单和商品销售数据。订单监测偏重订单规模、支付转化、渠道贡献和销售表现;生命周期模块偏重商品经营阶段识别和阶段性运营判断。
+
+### 5.4 供应监测模块
+
+订单监测输出的是销售端和订单端表现,供应监测输出的是供应商成本、交付和账期表现。两者可通过商品编码关联,用于判断某个商品在销售表现和供应能力上的综合情况。
+
+## 6. 当前实现说明
+
+当前模块已经接入 Java 后端和数据库,主要接口由订单分析控制器、店铺数据控制器和运营报表控制器提供。
+
+前端页面中已有多个订单分析页面文件,实际展示入口受系统菜单和路由配置控制。本文档只描述当前代码和接口中已经存在的功能,不包含尚未实现的规划能力。

+ 215 - 0
docs/supply-monitoring-architecture-current.svg

@@ -0,0 +1,215 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="2000" height="1050" viewBox="0 0 2000 1050">
+  <defs>
+    <marker id="arrow" markerWidth="10" markerHeight="10" refX="8" refY="3" orient="auto" markerUnits="strokeWidth">
+      <path d="M0,0 L0,6 L8,3 z" fill="#666"/>
+    </marker>
+    <style>
+      text { font-family:"Microsoft YaHei", Arial, sans-serif; fill:#1f2937; }
+      .title { font-size:28px; font-weight:700; }
+      .subtitle { font-size:18px; }
+      .panel-title { font-size:17px; font-weight:700; }
+      .panel-subtitle { font-size:14px; }
+      .box-text { font-size:14px; }
+      .small { font-size:12px; }
+      .stage1 { fill:#0ea5e9; font-size:12px; font-weight:700; }
+      .stage3 { fill:#9333ea; font-size:12px; font-weight:700; }
+      .top { fill:#dbeafe; stroke:#bfdbfe; stroke-width:1.5; }
+      .head { fill:#d9ead7; stroke:#c4dfc2; stroke-width:1.2; }
+      .feature { fill:#c7e9fb; stroke:#b7def2; stroke-width:1; }
+      .output { fill:#f8caca; stroke:#f3b5b5; stroke-width:1; }
+      .rule { fill:#fff2c8; stroke:#f5dda3; stroke-width:1; }
+      .panel-blue { fill:none; stroke:#58c7f7; stroke-width:1.5; stroke-dasharray:4 5; }
+      .panel-red { fill:none; stroke:#f4a6a6; stroke-width:1.4; stroke-dasharray:4 5; }
+      .panel-yellow { fill:none; stroke:#f2d16b; stroke-width:1.4; stroke-dasharray:4 5; }
+      .line { stroke:#666; stroke-width:1.6; fill:none; marker-end:url(#arrow); }
+      .line-soft { stroke:#999; stroke-width:1.2; fill:none; marker-end:url(#arrow); }
+    </style>
+  </defs>
+
+  <rect width="2000" height="1050" fill="#fff"/>
+
+  <rect x="760" y="28" width="480" height="72" rx="6" class="top"/>
+  <text x="1000" y="58" text-anchor="middle" class="title">供应监测模块</text>
+  <text x="1000" y="84" text-anchor="middle" class="subtitle">供应商综合评分、成本分析、交付分析、查询对比与付款计划</text>
+
+  <path d="M1000 100 L1000 145 L180 145 L180 188" class="line"/>
+  <path d="M1000 145 L565 145 L565 188" class="line"/>
+  <path d="M1000 145 L960 145 L960 188" class="line"/>
+  <path d="M1000 145 L1355 145 L1355 188" class="line"/>
+  <path d="M1000 145 L1740 145 L1740 188" class="line"/>
+
+  <!-- 综合评分 -->
+  <rect x="24" y="190" width="360" height="300" class="panel-blue"/>
+  <rect x="118" y="202" width="172" height="48" rx="5" class="head"/>
+  <text x="204" y="224" text-anchor="middle" class="panel-title">综合评分</text>
+  <text x="204" y="243" text-anchor="middle" class="panel-subtitle">供应商能力总览</text>
+  <text x="354" y="214" text-anchor="middle" class="stage1">第一阶段</text>
+  <g text-anchor="middle" class="box-text">
+    <rect x="48" y="268" width="145" height="40" rx="4" class="feature"/><text x="120" y="293">供应商总数</text>
+    <rect x="215" y="268" width="145" height="40" rx="4" class="feature"/><text x="288" y="293">平均综合得分</text>
+    <rect x="48" y="322" width="145" height="40" rx="4" class="feature"/><text x="120" y="347">预警供应商</text>
+    <rect x="215" y="322" width="145" height="40" rx="4" class="feature"/><text x="288" y="347">优秀供应商</text>
+    <rect x="48" y="376" width="145" height="40" rx="4" class="feature"/><text x="120" y="401">单品评分查询</text>
+    <rect x="215" y="376" width="145" height="40" rx="4" class="feature"/><text x="288" y="401">综合排名</text>
+    <rect x="132" y="430" width="145" height="40" rx="4" class="feature"/><text x="204" y="455">概览统计</text>
+  </g>
+
+  <!-- 成本维度 -->
+  <rect x="425" y="190" width="360" height="300" class="panel-blue"/>
+  <rect x="520" y="202" width="172" height="48" rx="5" class="head"/>
+  <text x="606" y="224" text-anchor="middle" class="panel-title">成本维度</text>
+  <text x="606" y="243" text-anchor="middle" class="panel-subtitle">供应商报价竞争力</text>
+  <text x="755" y="214" text-anchor="middle" class="stage1">第一阶段</text>
+  <g text-anchor="middle" class="box-text">
+    <rect x="450" y="268" width="145" height="40" rx="4" class="feature"/><text x="523" y="293">产品编码查询</text>
+    <rect x="617" y="268" width="145" height="40" rx="4" class="feature"/><text x="690" y="293">参考价</text>
+    <rect x="450" y="322" width="145" height="40" rx="4" class="feature"/><text x="523" y="347">成本排名</text>
+    <rect x="617" y="322" width="145" height="40" rx="4" class="feature"/><text x="690" y="347">成本分数</text>
+    <rect x="450" y="376" width="145" height="40" rx="4" class="feature"/><text x="523" y="401">成本详情</text>
+    <rect x="617" y="376" width="145" height="40" rx="4" class="feature"/><text x="690" y="401">结果导出</text>
+    <rect x="525" y="430" width="165" height="40" rx="4" class="feature"/><text x="607" y="455">成本详情查询</text>
+  </g>
+
+  <!-- 交付维度 -->
+  <rect x="825" y="190" width="360" height="300" class="panel-blue"/>
+  <rect x="920" y="202" width="172" height="48" rx="5" class="head"/>
+  <text x="1006" y="224" text-anchor="middle" class="panel-title">交付维度</text>
+  <text x="1006" y="243" text-anchor="middle" class="panel-subtitle">计划交期与实际入库对比</text>
+  <text x="1155" y="214" text-anchor="middle" class="stage1">第一阶段</text>
+  <g text-anchor="middle" class="box-text">
+    <rect x="850" y="268" width="145" height="40" rx="4" class="feature"/><text x="923" y="293">准时率</text>
+    <rect x="1017" y="268" width="145" height="40" rx="4" class="feature"/><text x="1090" y="293">交付分数</text>
+    <rect x="850" y="322" width="145" height="40" rx="4" class="feature"/><text x="923" y="347">计划数量</text>
+    <rect x="1017" y="322" width="145" height="40" rx="4" class="feature"/><text x="1090" y="347">实际入库数量</text>
+    <rect x="850" y="376" width="145" height="40" rx="4" class="feature"/><text x="923" y="401">交付详情</text>
+    <rect x="1017" y="376" width="145" height="40" rx="4" class="feature"/><text x="1090" y="401">结果导出</text>
+    <rect x="925" y="430" width="165" height="40" rx="4" class="feature"/><text x="1007" y="455">交付详情查询</text>
+  </g>
+
+  <!-- 供应商查询对比 -->
+  <rect x="1225" y="190" width="360" height="300" class="panel-blue"/>
+  <rect x="1320" y="202" width="172" height="48" rx="5" class="head"/>
+  <text x="1406" y="224" text-anchor="middle" class="panel-title">供应商查询对比</text>
+  <text x="1406" y="243" text-anchor="middle" class="panel-subtitle">供应商表现横向比较</text>
+  <text x="1555" y="214" text-anchor="middle" class="stage1">第一阶段</text>
+  <g text-anchor="middle" class="box-text">
+    <rect x="1250" y="268" width="145" height="40" rx="4" class="feature"/><text x="1323" y="293">供应商列表</text>
+    <rect x="1417" y="268" width="145" height="40" rx="4" class="feature"/><text x="1490" y="293">供应商筛选</text>
+    <rect x="1250" y="322" width="145" height="40" rx="4" class="feature"/><text x="1323" y="347">多供应商对比</text>
+    <rect x="1417" y="322" width="145" height="40" rx="4" class="feature"/><text x="1490" y="347">日期范围</text>
+    <rect x="1250" y="376" width="145" height="40" rx="4" class="feature"/><text x="1323" y="401">完成率/准时率</text>
+    <rect x="1417" y="376" width="145" height="40" rx="4" class="feature"/><text x="1490" y="401">订单与入库金额</text>
+    <rect x="1250" y="430" width="145" height="40" rx="4" class="feature"/><text x="1323" y="455">供应商查询</text>
+    <rect x="1417" y="430" width="145" height="40" rx="4" class="feature"/><text x="1490" y="455">对比分析</text>
+  </g>
+
+  <!-- 付款计划与现金流 -->
+  <rect x="1625" y="190" width="345" height="300" class="panel-blue"/>
+  <rect x="1712" y="202" width="172" height="48" rx="5" class="head"/>
+  <text x="1798" y="224" text-anchor="middle" class="panel-title">付款计划</text>
+  <text x="1798" y="243" text-anchor="middle" class="panel-subtitle">按账期生成付款计划</text>
+  <text x="1940" y="214" text-anchor="middle" class="stage1">第一阶段</text>
+  <g text-anchor="middle" class="box-text">
+    <rect x="1650" y="280" width="145" height="42" rx="4" class="feature"/><text x="1723" y="305">预计付款日</text>
+    <rect x="1810" y="280" width="145" height="42" rx="4" class="feature"/><text x="1883" y="305">应付金额</text>
+    <rect x="1650" y="350" width="145" height="42" rx="4" class="feature"/><text x="1723" y="375">入库数量</text>
+    <rect x="1810" y="350" width="145" height="42" rx="4" class="feature"/><text x="1883" y="375">付款状态</text>
+    <rect x="1730" y="420" width="145" height="42" rx="4" class="feature"/><text x="1803" y="445">付款计划查询</text>
+  </g>
+
+  <path d="M205 490 L205 625" class="line"/>
+  <path d="M605 490 L605 625" class="line"/>
+  <path d="M1005 490 L1005 530 L645 530 L645 625" class="line"/>
+  <path d="M1405 490 L1405 520 L1138 520 L1138 660" class="line"/>
+  <path d="M1800 490 L1800 530 L1365 530 L1365 870" class="line"/>
+
+  <!-- 业务输出 -->
+  <rect x="110" y="565" width="890" height="160" class="panel-red"/>
+  <text x="555" y="595" text-anchor="middle" class="title" font-size="22">业务输出</text>
+  <g text-anchor="middle" class="box-text">
+    <rect x="135" y="625" width="180" height="65" rx="5" class="output"/>
+    <text x="225" y="650">供应能力总览</text>
+    <text x="225" y="670" class="small">总数、均分、预警、优秀</text>
+    <text x="285" y="642" class="stage1">一阶段</text>
+
+    <rect x="345" y="625" width="180" height="65" rx="5" class="output"/>
+    <text x="435" y="650">单品供应商排名</text>
+    <text x="435" y="670" class="small">成本、交付、账期综合</text>
+    <text x="495" y="642" class="stage1">一阶段</text>
+
+    <rect x="555" y="625" width="180" height="65" rx="5" class="output"/>
+    <text x="645" y="650">供应商横向对比</text>
+    <text x="645" y="670" class="small">订单、入库、完成率</text>
+    <text x="705" y="642" class="stage1">一阶段</text>
+
+    <rect x="765" y="625" width="180" height="65" rx="5" class="output"/>
+    <text x="855" y="650">资金与预警输出</text>
+    <text x="855" y="670" class="small">付款、现金流、风险提醒</text>
+    <text x="915" y="642" class="stage3">三阶段</text>
+  </g>
+
+  <!-- 核心业务逻辑 -->
+  <rect x="1030" y="540" width="930" height="405" class="panel-yellow"/>
+  <text x="1495" y="575" text-anchor="middle" class="title" font-size="22">核心业务逻辑</text>
+  <g text-anchor="middle" class="box-text">
+    <rect x="1060" y="660" width="155" height="70" rx="5" class="rule"/>
+    <text x="1138" y="682">数据来源</text>
+    <text x="1138" y="702" class="small">采购订单、入库、账期</text>
+    <text x="1138" y="718" class="small">订单入库匹配数据</text>
+    <text x="1200" y="652" class="stage1">一阶段</text>
+
+    <rect x="1280" y="600" width="170" height="75" rx="5" class="rule"/>
+    <text x="1365" y="623">成本口径</text>
+    <text x="1365" y="643" class="small">同产品统计参考价</text>
+    <text x="1365" y="659" class="small">按供应商价格排名</text>
+
+    <rect x="1280" y="760" width="170" height="75" rx="5" class="rule"/>
+    <text x="1365" y="783">交付口径</text>
+    <text x="1365" y="803" class="small">计划交期对比验收日</text>
+    <text x="1365" y="819" class="small">计算准时与满足率</text>
+
+    <rect x="1530" y="600" width="170" height="75" rx="5" class="rule"/>
+    <text x="1615" y="623">成本规则</text>
+    <text x="1615" y="643" class="small">第1名100分</text>
+    <text x="1615" y="659" class="small">第2/3/4名90/80/60分</text>
+
+    <rect x="1530" y="760" width="170" height="75" rx="5" class="rule"/>
+    <text x="1615" y="783">交付规则</text>
+    <text x="1615" y="803" class="small">准时率占70%</text>
+    <text x="1615" y="819" class="small">数量满足率占30%</text>
+
+    <rect x="1760" y="600" width="160" height="75" rx="5" class="rule"/>
+    <text x="1840" y="623">账期规则</text>
+    <text x="1840" y="643" class="small">账期越长分越高</text>
+    <text x="1840" y="659" class="small">按天数分档评分</text>
+
+    <rect x="1760" y="760" width="160" height="75" rx="5" class="rule"/>
+    <text x="1840" y="783">综合评分</text>
+    <text x="1840" y="803" class="small">成本40% + 交付40%</text>
+    <text x="1840" y="819" class="small">+ 账期20%</text>
+
+    <rect x="1280" y="870" width="170" height="52" rx="5" class="rule"/>
+    <text x="1365" y="892">付款计划生成</text>
+    <text x="1365" y="910" class="small">验收日期 + 账期天数</text>
+    <text x="1430" y="862" class="stage1">一阶段</text>
+
+    <rect x="1530" y="870" width="170" height="52" rx="5" class="rule"/>
+    <text x="1615" y="892">现金流测算</text>
+    <text x="1615" y="910" class="small">按月聚合预计应付</text>
+    <text x="1680" y="862" class="stage3">三阶段</text>
+
+    <rect x="1760" y="870" width="160" height="52" rx="5" class="rule"/>
+    <text x="1840" y="892">风险预警</text>
+    <text x="1840" y="910" class="small">低分、延期、资金缺口</text>
+    <text x="1900" y="862" class="stage3">三阶段</text>
+  </g>
+
+  <path d="M1215 695 L1280 638" class="line"/>
+  <path d="M1215 695 L1280 798" class="line"/>
+  <path d="M1450 638 L1530 638" class="line"/>
+  <path d="M1450 798 L1530 798" class="line"/>
+  <path d="M1700 638 L1760 638" class="line"/>
+  <path d="M1700 798 L1760 798" class="line"/>
+  <path d="M1450 896 L1530 896" class="line-soft"/>
+  <path d="M1700 896 L1760 896" class="line-soft"/>
+</svg>

+ 215 - 0
docs/supply-monitoring-architecture.svg

@@ -0,0 +1,215 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="2000" height="1050" viewBox="0 0 2000 1050">
+  <defs>
+    <marker id="arrow" markerWidth="10" markerHeight="10" refX="8" refY="3" orient="auto" markerUnits="strokeWidth">
+      <path d="M0,0 L0,6 L8,3 z" fill="#666"/>
+    </marker>
+    <style>
+      text { font-family:"Microsoft YaHei", Arial, sans-serif; fill:#1f2937; }
+      .title { font-size:28px; font-weight:700; }
+      .subtitle { font-size:18px; }
+      .panel-title { font-size:17px; font-weight:700; }
+      .panel-subtitle { font-size:14px; }
+      .box-text { font-size:14px; }
+      .small { font-size:12px; }
+      .stage1 { fill:#0ea5e9; font-size:12px; font-weight:700; }
+      .stage3 { fill:#9333ea; font-size:12px; font-weight:700; }
+      .top { fill:#dbeafe; stroke:#bfdbfe; stroke-width:1.5; }
+      .head { fill:#d9ead7; stroke:#c4dfc2; stroke-width:1.2; }
+      .feature { fill:#c7e9fb; stroke:#b7def2; stroke-width:1; }
+      .output { fill:#f8caca; stroke:#f3b5b5; stroke-width:1; }
+      .rule { fill:#fff2c8; stroke:#f5dda3; stroke-width:1; }
+      .panel-blue { fill:none; stroke:#58c7f7; stroke-width:1.5; stroke-dasharray:4 5; }
+      .panel-red { fill:none; stroke:#f4a6a6; stroke-width:1.4; stroke-dasharray:4 5; }
+      .panel-yellow { fill:none; stroke:#f2d16b; stroke-width:1.4; stroke-dasharray:4 5; }
+      .line { stroke:#666; stroke-width:1.6; fill:none; marker-end:url(#arrow); }
+      .line-soft { stroke:#999; stroke-width:1.2; fill:none; marker-end:url(#arrow); }
+    </style>
+  </defs>
+
+  <rect width="2000" height="1050" fill="#fff"/>
+
+  <rect x="760" y="28" width="480" height="72" rx="6" class="top"/>
+  <text x="1000" y="58" text-anchor="middle" class="title">供应监测模块</text>
+  <text x="1000" y="84" text-anchor="middle" class="subtitle">供应商综合评分、成本分析、交付分析、查询对比与付款计划</text>
+
+  <path d="M1000 100 L1000 145 L180 145 L180 188" class="line"/>
+  <path d="M1000 145 L565 145 L565 188" class="line"/>
+  <path d="M1000 145 L960 145 L960 188" class="line"/>
+  <path d="M1000 145 L1355 145 L1355 188" class="line"/>
+  <path d="M1000 145 L1740 145 L1740 188" class="line"/>
+
+  <!-- 综合评分 -->
+  <rect x="24" y="190" width="360" height="300" class="panel-blue"/>
+  <rect x="118" y="202" width="172" height="48" rx="5" class="head"/>
+  <text x="204" y="224" text-anchor="middle" class="panel-title">综合评分</text>
+  <text x="204" y="243" text-anchor="middle" class="panel-subtitle">供应商能力总览</text>
+  <text x="354" y="214" text-anchor="middle" class="stage1">第一阶段</text>
+  <g text-anchor="middle" class="box-text">
+    <rect x="48" y="268" width="145" height="40" rx="4" class="feature"/><text x="120" y="293">供应商总数</text>
+    <rect x="215" y="268" width="145" height="40" rx="4" class="feature"/><text x="288" y="293">平均综合得分</text>
+    <rect x="48" y="322" width="145" height="40" rx="4" class="feature"/><text x="120" y="347">预警供应商</text>
+    <rect x="215" y="322" width="145" height="40" rx="4" class="feature"/><text x="288" y="347">优秀供应商</text>
+    <rect x="48" y="376" width="145" height="40" rx="4" class="feature"/><text x="120" y="401">单品评分查询</text>
+    <rect x="215" y="376" width="145" height="40" rx="4" class="feature"/><text x="288" y="401">综合排名</text>
+    <rect x="132" y="430" width="145" height="40" rx="4" class="feature"/><text x="204" y="455">概览统计</text>
+  </g>
+
+  <!-- 成本维度 -->
+  <rect x="425" y="190" width="360" height="300" class="panel-blue"/>
+  <rect x="520" y="202" width="172" height="48" rx="5" class="head"/>
+  <text x="606" y="224" text-anchor="middle" class="panel-title">成本维度</text>
+  <text x="606" y="243" text-anchor="middle" class="panel-subtitle">供应商报价竞争力</text>
+  <text x="755" y="214" text-anchor="middle" class="stage1">第一阶段</text>
+  <g text-anchor="middle" class="box-text">
+    <rect x="450" y="268" width="145" height="40" rx="4" class="feature"/><text x="523" y="293">产品编码查询</text>
+    <rect x="617" y="268" width="145" height="40" rx="4" class="feature"/><text x="690" y="293">参考价</text>
+    <rect x="450" y="322" width="145" height="40" rx="4" class="feature"/><text x="523" y="347">成本排名</text>
+    <rect x="617" y="322" width="145" height="40" rx="4" class="feature"/><text x="690" y="347">成本分数</text>
+    <rect x="450" y="376" width="145" height="40" rx="4" class="feature"/><text x="523" y="401">成本详情</text>
+    <rect x="617" y="376" width="145" height="40" rx="4" class="feature"/><text x="690" y="401">结果导出</text>
+    <rect x="525" y="430" width="165" height="40" rx="4" class="feature"/><text x="607" y="455">成本详情查询</text>
+  </g>
+
+  <!-- 交付维度 -->
+  <rect x="825" y="190" width="360" height="300" class="panel-blue"/>
+  <rect x="920" y="202" width="172" height="48" rx="5" class="head"/>
+  <text x="1006" y="224" text-anchor="middle" class="panel-title">交付维度</text>
+  <text x="1006" y="243" text-anchor="middle" class="panel-subtitle">计划交期与实际入库对比</text>
+  <text x="1155" y="214" text-anchor="middle" class="stage1">第一阶段</text>
+  <g text-anchor="middle" class="box-text">
+    <rect x="850" y="268" width="145" height="40" rx="4" class="feature"/><text x="923" y="293">准时率</text>
+    <rect x="1017" y="268" width="145" height="40" rx="4" class="feature"/><text x="1090" y="293">交付分数</text>
+    <rect x="850" y="322" width="145" height="40" rx="4" class="feature"/><text x="923" y="347">计划数量</text>
+    <rect x="1017" y="322" width="145" height="40" rx="4" class="feature"/><text x="1090" y="347">实际入库数量</text>
+    <rect x="850" y="376" width="145" height="40" rx="4" class="feature"/><text x="923" y="401">交付详情</text>
+    <rect x="1017" y="376" width="145" height="40" rx="4" class="feature"/><text x="1090" y="401">结果导出</text>
+    <rect x="925" y="430" width="165" height="40" rx="4" class="feature"/><text x="1007" y="455">交付详情查询</text>
+  </g>
+
+  <!-- 供应商查询对比 -->
+  <rect x="1225" y="190" width="360" height="300" class="panel-blue"/>
+  <rect x="1320" y="202" width="172" height="48" rx="5" class="head"/>
+  <text x="1406" y="224" text-anchor="middle" class="panel-title">供应商查询对比</text>
+  <text x="1406" y="243" text-anchor="middle" class="panel-subtitle">供应商表现横向比较</text>
+  <text x="1555" y="214" text-anchor="middle" class="stage1">第一阶段</text>
+  <g text-anchor="middle" class="box-text">
+    <rect x="1250" y="268" width="145" height="40" rx="4" class="feature"/><text x="1323" y="293">供应商列表</text>
+    <rect x="1417" y="268" width="145" height="40" rx="4" class="feature"/><text x="1490" y="293">供应商筛选</text>
+    <rect x="1250" y="322" width="145" height="40" rx="4" class="feature"/><text x="1323" y="347">多供应商对比</text>
+    <rect x="1417" y="322" width="145" height="40" rx="4" class="feature"/><text x="1490" y="347">日期范围</text>
+    <rect x="1250" y="376" width="145" height="40" rx="4" class="feature"/><text x="1323" y="401">完成率/准时率</text>
+    <rect x="1417" y="376" width="145" height="40" rx="4" class="feature"/><text x="1490" y="401">订单与入库金额</text>
+    <rect x="1250" y="430" width="145" height="40" rx="4" class="feature"/><text x="1323" y="455">供应商查询</text>
+    <rect x="1417" y="430" width="145" height="40" rx="4" class="feature"/><text x="1490" y="455">对比分析</text>
+  </g>
+
+  <!-- 付款计划与现金流 -->
+  <rect x="1625" y="190" width="345" height="300" class="panel-blue"/>
+  <rect x="1712" y="202" width="172" height="48" rx="5" class="head"/>
+  <text x="1798" y="224" text-anchor="middle" class="panel-title">付款计划</text>
+  <text x="1798" y="243" text-anchor="middle" class="panel-subtitle">按账期生成付款计划</text>
+  <text x="1940" y="214" text-anchor="middle" class="stage1">第一阶段</text>
+  <g text-anchor="middle" class="box-text">
+    <rect x="1650" y="280" width="145" height="42" rx="4" class="feature"/><text x="1723" y="305">预计付款日</text>
+    <rect x="1810" y="280" width="145" height="42" rx="4" class="feature"/><text x="1883" y="305">应付金额</text>
+    <rect x="1650" y="350" width="145" height="42" rx="4" class="feature"/><text x="1723" y="375">入库数量</text>
+    <rect x="1810" y="350" width="145" height="42" rx="4" class="feature"/><text x="1883" y="375">付款状态</text>
+    <rect x="1730" y="420" width="145" height="42" rx="4" class="feature"/><text x="1803" y="445">付款计划查询</text>
+  </g>
+
+  <path d="M205 490 L205 625" class="line"/>
+  <path d="M605 490 L605 625" class="line"/>
+  <path d="M1005 490 L1005 530 L645 530 L645 625" class="line"/>
+  <path d="M1405 490 L1405 520 L1138 520 L1138 660" class="line"/>
+  <path d="M1800 490 L1800 530 L1365 530 L1365 870" class="line"/>
+
+  <!-- 业务输出 -->
+  <rect x="110" y="565" width="890" height="160" class="panel-red"/>
+  <text x="555" y="595" text-anchor="middle" class="title" font-size="22">业务输出</text>
+  <g text-anchor="middle" class="box-text">
+    <rect x="135" y="625" width="180" height="65" rx="5" class="output"/>
+    <text x="225" y="650">供应能力总览</text>
+    <text x="225" y="670" class="small">总数、均分、预警、优秀</text>
+    <text x="285" y="642" class="stage1">一阶段</text>
+
+    <rect x="345" y="625" width="180" height="65" rx="5" class="output"/>
+    <text x="435" y="650">单品供应商排名</text>
+    <text x="435" y="670" class="small">成本、交付、账期综合</text>
+    <text x="495" y="642" class="stage1">一阶段</text>
+
+    <rect x="555" y="625" width="180" height="65" rx="5" class="output"/>
+    <text x="645" y="650">供应商横向对比</text>
+    <text x="645" y="670" class="small">订单、入库、完成率</text>
+    <text x="705" y="642" class="stage1">一阶段</text>
+
+    <rect x="765" y="625" width="180" height="65" rx="5" class="output"/>
+    <text x="855" y="650">资金与预警输出</text>
+    <text x="855" y="670" class="small">付款、现金流、风险提醒</text>
+    <text x="915" y="642" class="stage3">三阶段</text>
+  </g>
+
+  <!-- 核心业务逻辑 -->
+  <rect x="1030" y="540" width="930" height="405" class="panel-yellow"/>
+  <text x="1495" y="575" text-anchor="middle" class="title" font-size="22">核心业务逻辑</text>
+  <g text-anchor="middle" class="box-text">
+    <rect x="1060" y="660" width="155" height="70" rx="5" class="rule"/>
+    <text x="1138" y="682">数据来源</text>
+    <text x="1138" y="702" class="small">采购订单、入库、账期</text>
+    <text x="1138" y="718" class="small">订单入库匹配数据</text>
+    <text x="1200" y="652" class="stage1">一阶段</text>
+
+    <rect x="1280" y="600" width="170" height="75" rx="5" class="rule"/>
+    <text x="1365" y="623">成本口径</text>
+    <text x="1365" y="643" class="small">同产品统计参考价</text>
+    <text x="1365" y="659" class="small">按供应商价格排名</text>
+
+    <rect x="1280" y="760" width="170" height="75" rx="5" class="rule"/>
+    <text x="1365" y="783">交付口径</text>
+    <text x="1365" y="803" class="small">计划交期对比验收日</text>
+    <text x="1365" y="819" class="small">计算准时与满足率</text>
+
+    <rect x="1530" y="600" width="170" height="75" rx="5" class="rule"/>
+    <text x="1615" y="623">成本规则</text>
+    <text x="1615" y="643" class="small">第1名100分</text>
+    <text x="1615" y="659" class="small">第2/3/4名90/80/60分</text>
+
+    <rect x="1530" y="760" width="170" height="75" rx="5" class="rule"/>
+    <text x="1615" y="783">交付规则</text>
+    <text x="1615" y="803" class="small">准时率占70%</text>
+    <text x="1615" y="819" class="small">数量满足率占30%</text>
+
+    <rect x="1760" y="600" width="160" height="75" rx="5" class="rule"/>
+    <text x="1840" y="623">账期规则</text>
+    <text x="1840" y="643" class="small">账期越长分越高</text>
+    <text x="1840" y="659" class="small">按天数分档评分</text>
+
+    <rect x="1760" y="760" width="160" height="75" rx="5" class="rule"/>
+    <text x="1840" y="783">综合评分</text>
+    <text x="1840" y="803" class="small">成本40% + 交付40%</text>
+    <text x="1840" y="819" class="small">+ 账期20%</text>
+
+    <rect x="1280" y="870" width="170" height="52" rx="5" class="rule"/>
+    <text x="1365" y="892">付款计划生成</text>
+    <text x="1365" y="910" class="small">验收日期 + 账期天数</text>
+    <text x="1430" y="862" class="stage1">一阶段</text>
+
+    <rect x="1530" y="870" width="170" height="52" rx="5" class="rule"/>
+    <text x="1615" y="892">现金流测算</text>
+    <text x="1615" y="910" class="small">按月聚合预计应付</text>
+    <text x="1680" y="862" class="stage3">三阶段</text>
+
+    <rect x="1760" y="870" width="160" height="52" rx="5" class="rule"/>
+    <text x="1840" y="892">风险预警</text>
+    <text x="1840" y="910" class="small">低分、延期、资金缺口</text>
+    <text x="1900" y="862" class="stage3">三阶段</text>
+  </g>
+
+  <path d="M1215 695 L1280 638" class="line"/>
+  <path d="M1215 695 L1280 798" class="line"/>
+  <path d="M1450 638 L1530 638" class="line"/>
+  <path d="M1450 798 L1530 798" class="line"/>
+  <path d="M1700 638 L1760 638" class="line"/>
+  <path d="M1700 798 L1760 798" class="line"/>
+  <path d="M1450 896 L1530 896" class="line-soft"/>
+  <path d="M1700 896 L1760 896" class="line-soft"/>
+</svg>

+ 304 - 0
docs/supply-monitoring-module-design.md

@@ -0,0 +1,304 @@
+# 供应监测模块设计文档
+
+## 1. 文档范围
+
+本文档说明当前系统中供应监测模块的功能设计、实现逻辑、数据来源、接口进度以及与其他模块的关联关系。
+
+文档内容基于当前 Java 后端、MySQL 数据库和本地 QA 质检数据的实现状态整理,覆盖供应商综合评分、成本分析、交付分析、质量合格率、供应商查询对比、付款计划和入库对账等功能。
+
+## 2. 模块定位
+
+供应监测模块用于分析供应商在成本、交付、账期和质量四个维度上的表现,帮助业务人员选择更合适的供应商,并对供应过程中的延期、成本差异、质量异常和付款安排进行监控。
+
+当前模块的核心目标包括:
+
+- 查看供应商总体数量、平均得分、预警供应商和优秀供应商数量。
+- 针对单个产品查询可选供应商的综合评分和排名。
+- 对比多个供应商的采购、入库、交付、账期和质量表现。
+- 根据入库日期和账期生成付款计划。
+- 按月份汇总采购入库金额、数量和付款计划。
+- 引入 QA 抽检数据,按供应商计算合格率并纳入最新评分卡。
+
+## 3. 当前项目进度
+
+当前供应监测主链路已经接入 Java 后端和 MySQL 数据库,后端优先读取数据库数据;当数据库没有供应监测基础数据时,服务层保留从本地 Excel 加载的兜底能力。
+
+已完成或已接入的能力包括:
+
+- 供应商列表查询:按供应商名称、开始日期、结束日期筛选。
+- 多供应商对比:最多支持 5 个供应商横向对比。
+- 付款计划:按实际验收日期和账期生成预计付款日。
+- 付款月度汇总:基于付款计划按月份汇总金额、数量和状态。
+- 入库对账:按月份、供应商汇总采购入库数量、金额、单据行数和均价。
+- 单个产品供应商评估:返回 Top 供应商、参考价、成本排名、成本分、交付分、账期分和综合得分。
+- 产品评分明细:拆分返回成本详情、交付详情和账期详情。
+- 供应商总览:返回供应商总数、平均得分、预警供应商数和优秀供应商数。
+- QA 质量数据接入:读取 `2026年每日QA登记表.xlsx`,汇总供应商抽检数量、不合格数量和合格率。
+
+当前后端接口包括:
+
+| 接口 | 方法 | 说明 |
+| --- | --- | --- |
+| `/api/supply/monitor/suppliers` | GET | 供应商列表与评分卡数据 |
+| `/api/supply/monitor/compare` | GET | 多供应商对比 |
+| `/api/supply/monitor/payment-plan` | GET | 付款计划 |
+| `/api/supply/monitor/payment-summary` | GET | 付款月度汇总 |
+| `/api/supply/monitor/receipt-reconciliation` | GET | 入库对账 |
+| `/api/supply/monitor/evaluate` | POST | 按产品编码评估供应商 |
+| `/api/supplier/evaluate` | POST | 兼容旧前端的产品评估接口 |
+| `/api/supplier/overview` | GET | 供应商总览统计 |
+| `/api/supplier/product/{productCode}/details` | GET | 产品评分明细 |
+
+## 4. 功能设计
+
+### 4.1 最新供应商评分卡
+
+最新评分卡用于供应商列表和多供应商对比,已经加入质量合格率维度。综合得分由四个维度构成:
+
+| 维度 | 权重 | 分数来源 |
+| --- | ---: | --- |
+| 成本维度 | 30% | 同 SKU 下按参考价从低到高排名后换算成本分 |
+| 交付维度 | 30% | 准时率 70 分 + 订单完成率 30 分 |
+| 账期维度 | 15% | 按供应商账期天数换算账期分 |
+| 质量维度 | 25% | QA 抽检合格率换算质量分 |
+
+综合得分计算公式:
+
+```text
+综合得分 = 成本分 * 30% + 交付分 * 30% + 账期分 * 15% + 质量分 * 25%
+```
+
+评分等级:
+
+- A:综合得分大于等于 85 分。
+- B:综合得分大于等于 70 分且小于 85 分。
+- C:综合得分大于等于 60 分且小于 70 分。
+- 预警:综合得分小于 60 分。
+
+预警标签规则:
+
+- 综合得分小于 60 分:综合分预警。
+- 有 QA 抽检数据且合格率低于 95%:质量预警。
+- 准时交付率低于 60%:交付预警。
+- 订单完成率低于 80%:完成率预警。
+- 以上条件均未命中:正常。
+
+### 4.2 成本维度分析
+
+成本分析用于比较供应商针对同一产品的实际采购成本。
+
+系统以产品编码或 SKU 为查询条件,从采购入库数据中按供应商汇总入库数量和入库金额,计算参考价:
+
+```text
+参考价 = 入库金额合计 / 入库数量合计
+```
+
+系统按参考价从低到高排名,并换算为成本分数:
+
+- 第 1 名:100 分。
+- 第 2 名:90 分。
+- 第 3 名:80 分。
+- 第 4 名:60 分。
+- 其余:0 分。
+
+在供应商总列表中,成本分会按供应商参与的多个 SKU 成本排名取平均值。
+
+### 4.3 交付维度分析
+
+交付分析用于评价供应商是否按计划交货,以及采购订单是否完成。
+
+供应商列表中的交付分数由两部分组成:
+
+- 准时率:占 70 分。按订单入库匹配表中实际验收日期是否小于等于计划交货日期计算。
+- 订单完成率:占 30 分。按采购订单完成数量与订单数量的比例计算,最高按 100% 计。
+
+计算公式:
+
+```text
+交付分 = 准时率 * 70 + 订单完成率 * 30
+```
+
+产品维度交付详情使用订单入库匹配数据,展示匹配行数、准时行数、延迟行数、准时率、数量满足率、平均偏差天数、最大延迟天数、交付状态和风险等级。
+
+### 4.4 账期维度分析
+
+账期分析用于评价供应商给出的付款周期。
+
+系统从供应商账期表读取账期天数,并按以下规则换算为账期分数:
+
+- 大于等于 90 天:100 分。
+- 大于等于 60 天:90 分。
+- 大于等于 45 天:80 分。
+- 大于等于 30 天:60 分。
+- 小于 30 天或缺少账期:40 分。
+
+账期详情会展示供应商代码、供应商名称、结算期限、账期天数、账期分数、账期等级和维护状态。
+
+### 4.5 质量维度和合格率
+
+质量维度来自 QA 抽检数据,当前后端读取 `2026年每日QA登记表.xlsx`。文件路径优先级如下:
+
+1. 环境变量 `SUPPLY_QA_DATA_PATH`。
+2. 环境变量 `DTM_SUPPLY_QA_DATA_PATH`。
+3. Spring 配置项 `supply.qa.path`。
+4. `supply.data.path` 指向目录下的 `2026年每日QA登记表.xlsx`。
+5. 项目运行目录下的 `data/supply/2026年每日QA登记表.xlsx`。
+
+系统读取 QA 文件中的验货日期、供应商、来货数量、抽检数量、不合格数量和大批次品数量,并按供应商汇总。
+
+合格率和质量分计算规则:
+
+```text
+不合格数量 = 不合格数量 + 大批次品数量
+合格率 = (抽检数量 - 不合格数量) / 抽检数量
+质量分 = 合格率 * 100
+```
+
+当供应商没有 QA 抽检数据时,供应商列表评分卡暂按质量分 80 分处理;同时 `qaPassRate` 返回为空,便于前端区分“无数据”和“合格率为 0”。
+
+供应商列表返回的质量字段包括:
+
+- `qualityScore`:质量分。
+- `qaPassRate`:QA 合格率,保留 4 位小数。
+- `qaSampleQty`:抽检数量。
+- `qaDefectQty`:不合格数量。
+- `qaIncomingQty`:来货数量。
+- `qaLines`:QA 记录行数。
+
+### 4.6 供应商查询对比
+
+供应商查询对比用于查看供应商在采购、入库、交付、账期和质量方面的明细表现。
+
+当前支持:
+
+- 供应商列表查询。
+- 按供应商名称筛选。
+- 按日期范围筛选。
+- 多供应商对比。
+- 查看采购数量、完成数量、未完成数量、入库数量、入库金额、准时交付率、订单完成率、账期天数、质量分和 QA 合格率。
+
+### 4.7 付款计划
+
+付款计划根据采购入库记录和供应商账期生成。
+
+当前逻辑为:
+
+```text
+预计付款日 = 实际验收日期 + 供应商账期天数
+```
+
+系统按供应商和预计付款日汇总入库金额、入库数量和入库单据行数,并根据预计付款日判断付款状态:
+
+- 已到期:预计付款日早于当前日期。
+- 7 天内到期:预计付款日在当前日期到未来 7 天内。
+- 未到期:预计付款日晚于未来 7 天。
+
+### 4.8 入库对账
+
+入库对账按采购入库业务日期统计,当前返回月度维度数据:
+
+- 月份。
+- 供应商代码。
+- 供应商名称。
+- 入库数量。
+- 入库金额。
+- 入库单据行数。
+- 入库单据数。
+- 平均入库单价。
+
+该功能用于辅助采购侧和财务侧核对供应商入库规模、金额变化和付款基础数据。
+
+## 5. 数据与业务逻辑
+
+### 5.1 数据来源
+
+当前供应监测使用以下数据源:
+
+- `dtm_supply_purchase_order`:采购订单表,保存业务日期、计划交货日期、供应商、订单数量、完成数量、未完成数量和金额。
+- `dtm_supply_purchase_receipt`:采购入库表,保存业务日期、实际验收日期、供应商、产品编码、实际入库数量和金额。
+- `dtm_supply_supplier_term`:供应商账期表,保存供应商名称、供应商代码、结算期限和账期天数。
+- `dtm_supply_order_receipt_match`:订单入库匹配表,保存产品、供应商、计划交货日期、实际验收日期、计划数量和实际入库数量。
+- `2026年每日QA登记表.xlsx`:QA 质量数据,保存验货日期、供应商、来货数量、抽检数量、不合格数量和大批次品数量。
+
+### 5.2 产品维度评分流程
+
+单个产品评分的核心流程如下:
+
+1. 根据产品编码查询采购入库数据,计算不同供应商的参考价和成本排名。
+2. 根据产品编码查询订单入库匹配数据,计算准时率、数量满足率和交付分数。
+3. 根据供应商名称读取账期天数,计算账期分数。
+4. 按成本 40%、交付 40%、账期 20% 计算产品维度综合得分。
+5. 按综合得分降序输出 Top 5 供应商排名。
+
+说明:产品维度 Top 供应商接口当前仍使用成本、交付、账期三维评分,尚未把 QA 合格率纳入单个产品评分。最新四维评分卡已用于供应商列表和多供应商对比。
+
+### 5.3 供应商总览统计流程
+
+供应商总览会汇总采购入库、订单入库匹配和供应商账期中的供应商,形成供应商基础集合。
+
+系统分别计算各供应商的成本分、交付分和账期分,再计算综合得分,并输出:
+
+- 供应商总数。
+- 平均综合得分。
+- 综合得分低于 60 分的预警供应商数量。
+- 综合得分大于等于 85 分的优秀供应商数量。
+
+说明:`/api/supplier/overview` 当前 SQL 仍按成本 40%、交付 40%、账期 20% 计算总览统计;如需总览卡片与最新四维评分卡完全一致,需要后续把 QA 合格率也纳入总览 SQL 或改由服务层统一聚合。
+
+### 5.4 日期筛选
+
+供应商列表、供应商对比、付款计划、付款汇总和入库对账支持日期范围筛选。
+
+不同数据侧使用的日期口径如下:
+
+- 采购订单统计:主要使用采购订单业务日期。
+- 采购入库统计:主要使用采购入库业务日期或实际验收日期。
+- 交付统计:主要使用订单入库匹配中的实际验收日期。
+- 付款计划:使用采购入库实际验收日期计算预计付款日。
+- QA 质量统计:使用 QA 文件中的验货日期。
+
+### 5.5 数据兜底逻辑
+
+后端服务优先读取 MySQL 数据库。当采购订单、采购入库、供应商账期和订单入库匹配表均无数据时,服务层会尝试从本地供应监测 Excel 数据中加载,以兼容早期开发和演示阶段的数据来源。
+
+## 6. 与其他模块的关联关系
+
+### 6.1 数据上传模块
+
+供应监测依赖采购订单、采购入库、供应商账期、订单入库匹配和 QA 质检数据。当前核心采购侧数据已写入数据库,后端接口直接从数据库读取;QA 质量数据仍从本地 Excel 文件读取。
+
+后续可将 QA 文件导入流程纳入统一数据上传模块,落库后再由供应监测统一从数据库聚合,减少本地文件路径依赖。
+
+### 6.2 产品基础数据
+
+供应监测以产品编码作为成本和交付分析的核心查询条件。产品编码可与订单监测、生命周期模块中的商品编码进行关联,用于分析同一商品的销售表现和供应能力。
+
+### 6.3 订单监测模块
+
+订单监测反映销售端表现,供应监测反映采购和供应端表现。两者通过产品编码关联后,可以判断高销售商品是否存在供应成本过高、交付延迟、质量合格率偏低或账期不足的问题。
+
+### 6.4 库存管理模块
+
+供应监测中的采购入库数量、交付完成情况和质量合格率可为库存管理提供补充信息。库存模块关注库存现状,供应监测关注供应商履约、质量表现和后续付款安排。
+
+### 6.5 财务付款模块
+
+供应监测中的付款计划按入库验收日期和账期生成,可作为后续应付计划、资金安排和供应商对账的基础数据。当前模块只生成预计付款计划和状态,不处理实际付款核销。
+
+## 7. 当前实现边界和后续事项
+
+当前实现边界:
+
+- 最新四维评分卡已用于供应商列表和多供应商对比。
+- 单个产品 Top 供应商评分仍使用成本、交付、账期三维评分。
+- 供应商总览 SQL 仍使用旧三维权重,暂未纳入 QA 合格率。
+- QA 质量数据当前来自本地 Excel 文件,尚未落入 MySQL 表。
+- 权重配置页面在前端代码中仍保留原有接口入口,不属于当前 Java 与数据库主链路。
+
+建议后续事项:
+
+- 新增 QA 质检数据表和导入接口,将 `2026年每日QA登记表.xlsx` 落库。
+- 统一供应商列表、供应商总览、产品评估的评分口径,避免页面间得分口径不一致。
+- 将评分权重抽成配置项,支持成本、交付、账期、质量权重维护。
+- 给 QA 合格率增加产品维度映射后,再纳入单个产品供应商评分。
+- 增加评分计算单元测试,覆盖无 QA 数据、低合格率、缺少账期、无交付匹配等边界场景。

BIN
docs/供应监测评分卡QA质量评分机制_两页汇报_修正版.pptx


BIN
docs/订单与供应监测模块架构汇报.pptx


+ 23 - 0
dtm-admin/src/main/java/com/dtm/web/controller/supply/SupplyMonitorController.java

@@ -4,6 +4,8 @@ import com.dtm.common.core.domain.AjaxResult;
 import com.dtm.supply.service.SupplyMonitorService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.web.bind.annotation.RestController;
@@ -11,6 +13,7 @@ import org.springframework.web.bind.annotation.RestController;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
 import java.util.stream.Collectors;
 
 @RestController
@@ -40,6 +43,26 @@ public class SupplyMonitorController {
         return AjaxResult.success(supplyMonitorService.paymentPlan(supplierName, startDate, endDate));
     }
 
+    @GetMapping("/payment-summary")
+    public AjaxResult paymentSummary(@RequestParam(required = false) String supplierName,
+                                     @RequestParam(required = false) String startDate,
+                                     @RequestParam(required = false) String endDate) {
+        return AjaxResult.success(supplyMonitorService.paymentSummary(supplierName, startDate, endDate));
+    }
+
+    @GetMapping("/receipt-reconciliation")
+    public AjaxResult receiptReconciliation(@RequestParam(required = false) String supplierName,
+                                            @RequestParam(required = false) String startDate,
+                                            @RequestParam(required = false) String endDate) {
+        return AjaxResult.success(supplyMonitorService.receiptReconciliation(supplierName, startDate, endDate));
+    }
+
+    @PostMapping("/evaluate")
+    public AjaxResult evaluate(@RequestBody Map<String, Object> body) {
+        String productCode = body == null ? "" : String.valueOf(body.getOrDefault("product_code", ""));
+        return AjaxResult.success(supplyMonitorService.evaluateProduct(productCode));
+    }
+
     private List<String> splitSupplierNames(String supplierNames) {
         if (supplierNames == null || supplierNames.trim().isEmpty()) {
             return Collections.emptyList();

+ 46 - 0
dtm-admin/src/main/java/com/dtm/web/controller/supply/SupplySupplierController.java

@@ -0,0 +1,46 @@
+package com.dtm.web.controller.supply;
+
+import com.dtm.supply.service.SupplyMonitorService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.Map;
+
+@RestController
+@RequestMapping("/api/supplier")
+public class SupplySupplierController {
+    @Autowired
+    private SupplyMonitorService supplyMonitorService;
+
+    @PostMapping("/evaluate")
+    public Object evaluate(@RequestBody Map<String, Object> body) {
+        String productCode = body == null ? "" : String.valueOf(body.getOrDefault("product_code", ""));
+        return supplyMonitorService.evaluateProduct(productCode);
+    }
+
+    @GetMapping("/overview")
+    public Object overview() {
+        return supplyMonitorService.overviewStats();
+    }
+
+    @GetMapping("/product/{productCode}/details")
+    public Object productDetails(@PathVariable String productCode) {
+        return supplyMonitorService.productDetails(productCode);
+    }
+
+    @GetMapping("/config/weights")
+    public Object weights() {
+        return supplyMonitorService.getScoreWeights();
+    }
+
+    @PutMapping("/config/weights")
+    public Object updateWeights(@RequestBody Map<String, Object> body) {
+        return supplyMonitorService.updateScoreWeights(body);
+    }
+}

+ 50 - 0
dtm-system/src/main/java/com/dtm/supply/mapper/SupplyMonitorMapper.java

@@ -0,0 +1,50 @@
+package com.dtm.supply.mapper;
+
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+import java.util.Map;
+
+public interface SupplyMonitorMapper {
+    Long countPurchaseOrders();
+
+    Long countPurchaseReceipts();
+
+    Long countSupplierTerms();
+
+    Long countOrderReceiptMatches();
+
+    Long countQaInspections();
+
+    List<Map<String, Object>> selectPurchaseOrders();
+
+    List<Map<String, Object>> selectPurchaseReceipts();
+
+    List<Map<String, Object>> selectSupplierTerms();
+
+    List<Map<String, Object>> selectOrderReceiptMatches();
+
+    List<Map<String, Object>> selectQaInspections();
+
+    List<Map<String, Object>> selectSupplierMonitorSummary(@Param("supplierName") String supplierName,
+                                                           @Param("startDate") String startDate,
+                                                           @Param("endDate") String endDate);
+
+    List<Map<String, Object>> selectSupplierSkuCosts(@Param("supplierName") String supplierName,
+                                                     @Param("startDate") String startDate,
+                                                     @Param("endDate") String endDate);
+
+    List<Map<String, Object>> selectPaymentPlanRows(@Param("supplierName") String supplierName,
+                                                    @Param("startDate") String startDate,
+                                                    @Param("endDate") String endDate);
+
+    List<Map<String, Object>> selectReceiptReconciliationRows(@Param("supplierName") String supplierName,
+                                                              @Param("startDate") String startDate,
+                                                              @Param("endDate") String endDate);
+
+    List<Map<String, Object>> selectProductCost(@Param("sku") String sku);
+
+    List<Map<String, Object>> selectProductDelivery(@Param("sku") String sku);
+
+    Map<String, Object> selectOverviewStats();
+}

Разница между файлами не показана из-за своего большого размера
+ 938 - 1
dtm-system/src/main/java/com/dtm/supply/service/SupplyMonitorService.java


+ 479 - 0
dtm-system/src/main/resources/mapper/supply/SupplyMonitorMapper.xml

@@ -0,0 +1,479 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.dtm.supply.mapper.SupplyMonitorMapper">
+
+    <select id="countPurchaseOrders" resultType="java.lang.Long">
+        SELECT COUNT(1) FROM dtm_supply_purchase_order
+    </select>
+
+    <select id="countPurchaseReceipts" resultType="java.lang.Long">
+        SELECT COUNT(1) FROM dtm_supply_purchase_receipt
+    </select>
+
+    <select id="countSupplierTerms" resultType="java.lang.Long">
+        SELECT COUNT(1) FROM dtm_supply_supplier_term
+    </select>
+
+    <select id="countOrderReceiptMatches" resultType="java.lang.Long">
+        SELECT COUNT(1) FROM dtm_supply_order_receipt_match
+    </select>
+
+    <select id="countQaInspections" resultType="java.lang.Long">
+        SELECT COUNT(1) FROM dtm_supply_qa_inspection
+    </select>
+
+    <select id="selectPurchaseOrders" resultType="java.util.HashMap">
+        SELECT
+            business_date AS businessDate,
+            delivery_date AS deliveryDate,
+            document_id AS documentId,
+            supplier_code AS supplierCode,
+            supplier_name AS supplierName,
+            IFNULL(order_qty, 0) AS orderQty,
+            IFNULL(completed_qty, 0) AS completedQty,
+            IFNULL(uncompleted_qty, 0) AS uncompletedQty,
+            IFNULL(amount, 0) AS amount
+        FROM dtm_supply_purchase_order
+        WHERE supplier_name IS NOT NULL
+          AND supplier_name != ''
+    </select>
+
+    <select id="selectPurchaseReceipts" resultType="java.util.HashMap">
+        SELECT
+            business_date AS businessDate,
+            acceptance_date AS acceptanceDate,
+            receipt_id AS receiptId,
+            supplier_code AS supplierCode,
+            supplier_name AS supplierName,
+            sku AS sku,
+            IFNULL(quantity, 0) AS quantity,
+            IFNULL(amount, 0) AS amount
+        FROM dtm_supply_purchase_receipt
+        WHERE supplier_name IS NOT NULL
+          AND supplier_name != ''
+    </select>
+
+    <select id="selectSupplierTerms" resultType="java.util.HashMap">
+        SELECT
+            supplier_code AS supplierCode,
+            supplier_name AS supplierName,
+            payment_terms AS paymentTerms,
+            term_days AS termDays
+        FROM dtm_supply_supplier_term
+        WHERE supplier_name IS NOT NULL
+          AND supplier_name != ''
+    </select>
+
+    <select id="selectOrderReceiptMatches" resultType="java.util.HashMap">
+        SELECT
+            acceptance_date AS acceptanceDate,
+            plan_delivery_date AS planDeliveryDate,
+            supplier_code AS supplierCode,
+            supplier_name AS supplierName,
+            sku AS sku,
+            product_name AS productName,
+            IFNULL(receipt_qty, 0) AS receiptQty,
+            IFNULL(plan_qty, 0) AS planQty
+        FROM dtm_supply_order_receipt_match
+        WHERE supplier_name IS NOT NULL
+          AND supplier_name != ''
+    </select>
+
+    <select id="selectQaInspections" resultType="java.util.HashMap">
+        SELECT
+            inspect_date AS inspectDate,
+            supplier_name AS supplierName,
+            IFNULL(incoming_qty, 0) AS incomingQty,
+            IFNULL(sample_qty, 0) AS sampleQty,
+            IFNULL(defect_qty, 0) AS defectQty
+        FROM dtm_supply_qa_inspection
+        WHERE supplier_name IS NOT NULL
+          AND supplier_name != ''
+          AND inspect_date IS NOT NULL
+    </select>
+
+    <select id="selectSupplierMonitorSummary" resultType="java.util.HashMap">
+        SELECT
+            base.supplierCode AS supplierCode,
+            base.supplierName AS supplierName,
+            IFNULL(o.orderCount, 0) AS orderCount,
+            IFNULL(o.orderLines, 0) AS orderLines,
+            IFNULL(o.orderQty, 0) AS orderQty,
+            IFNULL(o.completedQty, 0) AS completedQty,
+            IFNULL(o.uncompletedQty, 0) AS uncompletedQty,
+            IFNULL(o.orderAmount, 0) AS orderAmount,
+            IFNULL(r.receiptQty, 0) AS receiptQty,
+            IFNULL(r.receiptAmount, 0) AS receiptAmount,
+            IFNULL(r.receiptLines, 0) AS receiptLines,
+            IFNULL(m.matchedLines, 0) AS matchedLines,
+            IFNULL(m.onTimeLines, 0) AS onTimeLines,
+            IFNULL(m.matchedPlanQty, 0) AS matchedPlanQty,
+            IFNULL(m.matchedReceiptQty, 0) AS matchedReceiptQty,
+            IFNULL(t.term_days, 0) AS termDays
+        FROM (
+            SELECT supplier_code AS supplierCode, supplier_name AS supplierName
+            FROM dtm_supply_purchase_order
+            WHERE supplier_name IS NOT NULL AND supplier_name != ''
+            <if test="supplierName != null and supplierName != ''">
+                AND supplier_name LIKE CONCAT('%', #{supplierName}, '%')
+            </if>
+            <if test="startDate != null and startDate != ''">
+                AND business_date &gt;= #{startDate}
+            </if>
+            <if test="endDate != null and endDate != ''">
+                AND business_date &lt;= #{endDate}
+            </if>
+            UNION
+            SELECT supplier_code AS supplierCode, supplier_name AS supplierName
+            FROM dtm_supply_purchase_receipt
+            WHERE supplier_name IS NOT NULL AND supplier_name != ''
+            <if test="supplierName != null and supplierName != ''">
+                AND supplier_name LIKE CONCAT('%', #{supplierName}, '%')
+            </if>
+            <if test="startDate != null and startDate != ''">
+                AND business_date &gt;= #{startDate}
+            </if>
+            <if test="endDate != null and endDate != ''">
+                AND business_date &lt;= #{endDate}
+            </if>
+            UNION
+            SELECT supplier_code AS supplierCode, supplier_name AS supplierName
+            FROM dtm_supply_order_receipt_match
+            WHERE supplier_name IS NOT NULL AND supplier_name != ''
+            <if test="supplierName != null and supplierName != ''">
+                AND supplier_name LIKE CONCAT('%', #{supplierName}, '%')
+            </if>
+            <if test="startDate != null and startDate != ''">
+                AND acceptance_date &gt;= #{startDate}
+            </if>
+            <if test="endDate != null and endDate != ''">
+                AND acceptance_date &lt;= #{endDate}
+            </if>
+        ) base
+        LEFT JOIN (
+            SELECT
+                supplier_name AS supplierName,
+                COUNT(DISTINCT document_id) AS orderCount,
+                COUNT(1) AS orderLines,
+                SUM(IFNULL(order_qty, 0)) AS orderQty,
+                SUM(IFNULL(completed_qty, 0)) AS completedQty,
+                SUM(IFNULL(uncompleted_qty, 0)) AS uncompletedQty,
+                SUM(IFNULL(amount, 0)) AS orderAmount
+            FROM dtm_supply_purchase_order
+            WHERE supplier_name IS NOT NULL AND supplier_name != ''
+            <if test="supplierName != null and supplierName != ''">
+                AND supplier_name LIKE CONCAT('%', #{supplierName}, '%')
+            </if>
+            <if test="startDate != null and startDate != ''">
+                AND business_date &gt;= #{startDate}
+            </if>
+            <if test="endDate != null and endDate != ''">
+                AND business_date &lt;= #{endDate}
+            </if>
+            GROUP BY supplier_name
+        ) o ON o.supplierName = base.supplierName
+        LEFT JOIN (
+            SELECT
+                supplier_name AS supplierName,
+                SUM(IFNULL(quantity, 0)) AS receiptQty,
+                SUM(IFNULL(amount, 0)) AS receiptAmount,
+                COUNT(1) AS receiptLines
+            FROM dtm_supply_purchase_receipt
+            WHERE supplier_name IS NOT NULL AND supplier_name != ''
+            <if test="supplierName != null and supplierName != ''">
+                AND supplier_name LIKE CONCAT('%', #{supplierName}, '%')
+            </if>
+            <if test="startDate != null and startDate != ''">
+                AND business_date &gt;= #{startDate}
+            </if>
+            <if test="endDate != null and endDate != ''">
+                AND business_date &lt;= #{endDate}
+            </if>
+            GROUP BY supplier_name
+        ) r ON r.supplierName = base.supplierName
+        LEFT JOIN (
+            SELECT
+                supplier_name AS supplierName,
+                COUNT(1) AS matchedLines,
+                SUM(CASE
+                    WHEN acceptance_date IS NOT NULL
+                     AND plan_delivery_date IS NOT NULL
+                     AND acceptance_date &lt;= plan_delivery_date THEN 1
+                    ELSE 0
+                END) AS onTimeLines,
+                SUM(IFNULL(plan_qty, 0)) AS matchedPlanQty,
+                SUM(IFNULL(receipt_qty, 0)) AS matchedReceiptQty
+            FROM dtm_supply_order_receipt_match
+            WHERE supplier_name IS NOT NULL AND supplier_name != ''
+            <if test="supplierName != null and supplierName != ''">
+                AND supplier_name LIKE CONCAT('%', #{supplierName}, '%')
+            </if>
+            <if test="startDate != null and startDate != ''">
+                AND acceptance_date &gt;= #{startDate}
+            </if>
+            <if test="endDate != null and endDate != ''">
+                AND acceptance_date &lt;= #{endDate}
+            </if>
+            GROUP BY supplier_name
+        ) m ON m.supplierName = base.supplierName
+        LEFT JOIN dtm_supply_supplier_term t ON t.supplier_name = base.supplierName
+        ORDER BY IFNULL(r.receiptAmount, 0) DESC
+    </select>
+
+    <select id="selectSupplierSkuCosts" resultType="java.util.HashMap">
+        SELECT
+            sku AS sku,
+            supplier_name AS supplierName,
+            SUM(IFNULL(quantity, 0)) AS receiptQty,
+            SUM(IFNULL(amount, 0)) AS receiptAmount
+        FROM dtm_supply_purchase_receipt
+        WHERE supplier_name IS NOT NULL
+          AND supplier_name != ''
+          AND sku IS NOT NULL
+          AND sku != ''
+        <if test="supplierName != null and supplierName != ''">
+            AND supplier_name LIKE CONCAT('%', #{supplierName}, '%')
+        </if>
+        <if test="startDate != null and startDate != ''">
+            AND business_date &gt;= #{startDate}
+        </if>
+        <if test="endDate != null and endDate != ''">
+            AND business_date &lt;= #{endDate}
+        </if>
+        GROUP BY sku, supplier_name
+        HAVING SUM(IFNULL(quantity, 0)) &gt; 0
+    </select>
+
+    <select id="selectPaymentPlanRows" resultType="java.util.HashMap">
+        SELECT
+            plan.supplierCode,
+            plan.supplierName,
+            plan.termDays,
+            DATE_FORMAT(plan.dueDate, '%Y-%m-%d') AS dueDate,
+            plan.estimatedPayAmount,
+            plan.receiptQty,
+            plan.receiptLines
+        FROM (
+            SELECT
+                r.supplier_code AS supplierCode,
+                r.supplier_name AS supplierName,
+                IFNULL(t.termDays, 0) AS termDays,
+                DATE_ADD(r.acceptance_date, INTERVAL IFNULL(t.termDays, 0) DAY) AS dueDate,
+                SUM(IFNULL(r.amount, 0)) AS estimatedPayAmount,
+                SUM(IFNULL(r.quantity, 0)) AS receiptQty,
+                COUNT(1) AS receiptLines
+            FROM dtm_supply_purchase_receipt r
+            LEFT JOIN (
+                SELECT supplier_name, MAX(IFNULL(term_days, 0)) AS termDays
+                FROM dtm_supply_supplier_term
+                WHERE supplier_name IS NOT NULL
+                  AND supplier_name != ''
+                GROUP BY supplier_name
+            ) t ON t.supplier_name = r.supplier_name
+            WHERE r.supplier_name IS NOT NULL
+              AND r.supplier_name != ''
+              AND r.acceptance_date IS NOT NULL
+            <if test="supplierName != null and supplierName != ''">
+                AND r.supplier_name LIKE CONCAT('%', #{supplierName}, '%')
+            </if>
+            <if test="startDate != null and startDate != ''">
+                AND r.acceptance_date &gt;= #{startDate}
+            </if>
+            <if test="endDate != null and endDate != ''">
+                AND r.acceptance_date &lt;= #{endDate}
+            </if>
+            GROUP BY r.supplier_code, r.supplier_name, IFNULL(t.termDays, 0), DATE_ADD(r.acceptance_date, INTERVAL IFNULL(t.termDays, 0) DAY)
+        ) plan
+        ORDER BY
+            CASE
+                WHEN plan.dueDate &lt; CURRENT_DATE THEN 0
+                WHEN plan.dueDate &lt;= DATE_ADD(CURRENT_DATE, INTERVAL 7 DAY) THEN 1
+                ELSE 2
+            END,
+            plan.dueDate DESC,
+            plan.estimatedPayAmount DESC
+        LIMIT 500
+    </select>
+
+    <select id="selectReceiptReconciliationRows" resultType="java.util.HashMap">
+        SELECT
+            DATE_FORMAT(business_date, '%Y-%m') AS month,
+            supplier_code AS supplierCode,
+            supplier_name AS supplierName,
+            SUM(IFNULL(quantity, 0)) AS receiptQty,
+            SUM(IFNULL(amount, 0)) AS receiptAmount,
+            COUNT(1) AS receiptLines,
+            COUNT(DISTINCT receipt_id) AS receiptCount,
+            ROUND(SUM(IFNULL(amount, 0)) / NULLIF(SUM(IFNULL(quantity, 0)), 0), 2) AS avgReceiptPrice
+        FROM dtm_supply_purchase_receipt
+        WHERE supplier_name IS NOT NULL
+          AND supplier_name != ''
+          AND business_date IS NOT NULL
+        <if test="supplierName != null and supplierName != ''">
+            AND supplier_name LIKE CONCAT('%', #{supplierName}, '%')
+        </if>
+        <if test="startDate != null and startDate != ''">
+            AND business_date &gt;= #{startDate}
+        </if>
+        <if test="endDate != null and endDate != ''">
+            AND business_date &lt;= #{endDate}
+        </if>
+        GROUP BY DATE_FORMAT(business_date, '%Y-%m'), supplier_code, supplier_name
+        ORDER BY DATE_FORMAT(business_date, '%Y-%m') DESC, supplier_name
+    </select>
+
+    <select id="selectProductCost" resultType="java.util.HashMap">
+        SELECT
+            supplier_code AS supplierCode,
+            supplier_name AS supplierName,
+            ROUND(SUM(IFNULL(amount, 0)) / NULLIF(SUM(IFNULL(quantity, 0)), 0), 6) AS referencePrice,
+            SUM(IFNULL(quantity, 0)) AS receiptQty,
+            SUM(IFNULL(amount, 0)) AS receiptAmount,
+            COUNT(1) AS receiptLines,
+            MIN(acceptance_date) AS firstAcceptanceDate,
+            MAX(acceptance_date) AS latestAcceptanceDate
+        FROM dtm_supply_purchase_receipt
+        WHERE sku = #{sku}
+          AND supplier_name IS NOT NULL
+          AND supplier_name != ''
+        GROUP BY supplier_code, supplier_name
+        HAVING SUM(IFNULL(quantity, 0)) > 0
+        ORDER BY referencePrice ASC
+    </select>
+
+    <select id="selectProductDelivery" resultType="java.util.HashMap">
+        SELECT
+            m.supplier_code AS supplierCode,
+            m.supplier_name AS supplierName,
+            MAX(m.product_name) AS productName,
+            COUNT(1) AS matchedLines,
+            SUM(CASE
+                WHEN m.acceptance_date IS NOT NULL
+                 AND m.plan_delivery_date IS NOT NULL
+                 AND m.acceptance_date &lt;= m.plan_delivery_date THEN 1
+                ELSE 0
+            END) AS onTimeLines,
+            SUM(CASE
+                WHEN m.acceptance_date IS NOT NULL
+                 AND m.plan_delivery_date IS NOT NULL
+                 AND m.acceptance_date &gt; m.plan_delivery_date THEN 1
+                ELSE 0
+            END) AS delayedLines,
+            SUM(IFNULL(m.receipt_qty, 0)) AS receiptQtyForDelivery,
+            SUM(IFNULL(m.plan_qty, 0)) AS planQty,
+            AVG(CASE
+                WHEN m.acceptance_date IS NOT NULL
+                 AND m.plan_delivery_date IS NOT NULL
+                THEN DATEDIFF(m.acceptance_date, m.plan_delivery_date)
+                ELSE 0
+            END) AS avgDeviationDays,
+            MAX(CASE
+                WHEN m.acceptance_date IS NOT NULL
+                 AND m.plan_delivery_date IS NOT NULL
+                 AND m.acceptance_date &gt; m.plan_delivery_date
+                THEN DATEDIFF(m.acceptance_date, m.plan_delivery_date)
+                ELSE 0
+            END) AS maxDelayDays,
+            IFNULL(MAX(t.term_days), 0) AS termDays
+        FROM dtm_supply_order_receipt_match m
+        LEFT JOIN dtm_supply_supplier_term t
+               ON t.supplier_name = m.supplier_name
+        WHERE m.sku = #{sku}
+          AND m.supplier_name IS NOT NULL
+          AND m.supplier_name != ''
+        GROUP BY m.supplier_code, m.supplier_name
+    </select>
+
+    <select id="selectOverviewStats" resultType="java.util.HashMap">
+        WITH supplier_base AS (
+            SELECT supplier_name FROM dtm_supply_purchase_receipt
+            WHERE supplier_name IS NOT NULL AND supplier_name != ''
+            UNION
+            SELECT supplier_name FROM dtm_supply_order_receipt_match
+            WHERE supplier_name IS NOT NULL AND supplier_name != ''
+            UNION
+            SELECT supplier_name FROM dtm_supply_supplier_term
+            WHERE supplier_name IS NOT NULL AND supplier_name != ''
+        ),
+        cost_rank AS (
+            SELECT
+                supplier_name,
+                sku,
+                RANK() OVER (
+                    PARTITION BY sku
+                    ORDER BY SUM(IFNULL(amount, 0)) / NULLIF(SUM(IFNULL(quantity, 0)), 0)
+                ) AS cost_rank
+            FROM dtm_supply_purchase_receipt
+            WHERE sku IS NOT NULL
+              AND sku != ''
+              AND supplier_name IS NOT NULL
+              AND supplier_name != ''
+            GROUP BY supplier_name, sku
+            HAVING SUM(IFNULL(quantity, 0)) > 0
+        ),
+        cost_score AS (
+            SELECT
+                supplier_name,
+                AVG(CASE
+                    WHEN cost_rank = 1 THEN 100
+                    WHEN cost_rank = 2 THEN 90
+                    WHEN cost_rank = 3 THEN 80
+                    WHEN cost_rank = 4 THEN 60
+                    ELSE 0
+                END) AS cost_score
+            FROM cost_rank
+            GROUP BY supplier_name
+        ),
+        delivery_score AS (
+            SELECT
+                supplier_name,
+                (
+                    SUM(CASE
+                        WHEN acceptance_date IS NOT NULL
+                         AND plan_delivery_date IS NOT NULL
+                         AND acceptance_date &lt;= plan_delivery_date THEN 1
+                        ELSE 0
+                    END) / NULLIF(COUNT(1), 0) * 70
+                    +
+                    LEAST(1, SUM(IFNULL(receipt_qty, 0)) / NULLIF(SUM(IFNULL(plan_qty, 0)), 0)) * 30
+                ) AS delivery_score
+            FROM dtm_supply_order_receipt_match
+            WHERE supplier_name IS NOT NULL
+              AND supplier_name != ''
+            GROUP BY supplier_name
+        ),
+        term_score AS (
+            SELECT
+                supplier_name,
+                CASE
+                    WHEN IFNULL(term_days, 0) &gt;= 90 THEN 100
+                    WHEN IFNULL(term_days, 0) &gt;= 60 THEN 90
+                    WHEN IFNULL(term_days, 0) &gt;= 45 THEN 80
+                    WHEN IFNULL(term_days, 0) &gt;= 30 THEN 60
+                    ELSE 40
+                END AS term_score
+            FROM dtm_supply_supplier_term
+            WHERE supplier_name IS NOT NULL
+              AND supplier_name != ''
+        ),
+        supplier_score AS (
+            SELECT
+                b.supplier_name,
+                IFNULL(c.cost_score, 0) * 0.4
+                + IFNULL(d.delivery_score, 0) * 0.4
+                + IFNULL(t.term_score, 40) * 0.2 AS total_score
+            FROM supplier_base b
+            LEFT JOIN cost_score c ON c.supplier_name = b.supplier_name
+            LEFT JOIN delivery_score d ON d.supplier_name = b.supplier_name
+            LEFT JOIN term_score t ON t.supplier_name = b.supplier_name
+        )
+        SELECT
+            COUNT(1) AS supplierTotal,
+            ROUND(IFNULL(AVG(total_score), 0), 1) AS averageScore,
+            SUM(CASE WHEN total_score &lt; 60 THEN 1 ELSE 0 END) AS warningSupplierCount,
+            SUM(CASE WHEN total_score &gt;= 85 THEN 1 ELSE 0 END) AS excellentSupplierCount
+        FROM supplier_score
+    </select>
+</mapper>

BIN
tmpcheck/supply_match.xlsx


+ 210 - 0
tools/create_qa_score_ppt.py

@@ -0,0 +1,210 @@
+from pathlib import Path
+from zipfile import ZipFile, ZIP_DEFLATED
+import html
+
+
+OUT = Path("docs/供应监测评分卡QA质量评分机制_两页汇报_修正版.pptx")
+OUT.parent.mkdir(parents=True, exist_ok=True)
+
+
+def esc(value):
+    return html.escape(str(value), quote=True)
+
+
+shape_id = 10
+
+
+def next_id():
+    global shape_id
+    shape_id += 1
+    return shape_id
+
+
+def text_box(x, y, cx, cy, text, size=1800, bold=False, color="1F2937"):
+    sid = next_id()
+    runs = []
+    for line in text.split("\n"):
+        b = ' b="1"' if bold else ""
+        runs.append(
+            f'<a:p><a:r><a:rPr lang="zh-CN" sz="{size}"{b}>'
+            f'<a:solidFill><a:srgbClr val="{color}"/></a:solidFill>'
+            f'<a:latin typeface="Microsoft YaHei"/><a:ea typeface="Microsoft YaHei"/></a:rPr>'
+            f"<a:t>{esc(line)}</a:t></a:r></a:p>"
+        )
+    return (
+        f'<p:sp><p:nvSpPr><p:cNvPr id="{sid}" name="TextBox {sid}"/>'
+        f'<p:cNvSpPr txBox="1"/><p:nvPr/></p:nvSpPr><p:spPr>'
+        f'<a:xfrm><a:off x="{x}" y="{y}"/><a:ext cx="{cx}" cy="{cy}"/></a:xfrm>'
+        f'<a:prstGeom prst="rect"><a:avLst/></a:prstGeom><a:noFill/>'
+        f'<a:ln><a:noFill/></a:ln></p:spPr><p:txBody><a:bodyPr wrap="square"/>'
+        f"<a:lstStyle/>{''.join(runs)}</p:txBody></p:sp>"
+    )
+
+
+def rect(x, y, cx, cy, fill="FFFFFF", line="D9E2F1", rounded=True):
+    sid = next_id()
+    prst = "roundRect" if rounded else "rect"
+    return (
+        f'<p:sp><p:nvSpPr><p:cNvPr id="{sid}" name="Shape {sid}"/>'
+        f"<p:cNvSpPr/><p:nvPr/></p:nvSpPr><p:spPr>"
+        f'<a:xfrm><a:off x="{x}" y="{y}"/><a:ext cx="{cx}" cy="{cy}"/></a:xfrm>'
+        f'<a:prstGeom prst="{prst}"><a:avLst/></a:prstGeom>'
+        f'<a:solidFill><a:srgbClr val="{fill}"/></a:solidFill>'
+        f'<a:ln w="12700"><a:solidFill><a:srgbClr val="{line}"/></a:solidFill></a:ln>'
+        f"</p:spPr><p:txBody><a:bodyPr/><a:lstStyle/><a:p/></p:txBody></p:sp>"
+    )
+
+
+def card(x, y, cx, cy, title, body, accent="2563EB"):
+    return (
+        rect(x, y, cx, cy)
+        + text_box(x + 180000, y + 130000, cx - 360000, 230000, title, 1500, True, accent)
+        + text_box(x + 180000, y + 420000, cx - 360000, cy - 520000, body, 1120, False, "475569")
+    )
+
+
+def slide(title, subtitle, shapes):
+    return f'''<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<p:sld xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
+       xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
+       xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main">
+  <p:cSld>
+    <p:bg><p:bgPr><a:solidFill><a:srgbClr val="F4F7FB"/></a:solidFill><a:effectLst/></p:bgPr></p:bg>
+    <p:spTree>
+      <p:nvGrpSpPr><p:cNvPr id="1" name=""/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr>
+      <p:grpSpPr><a:xfrm><a:off x="0" y="0"/><a:ext cx="0" cy="0"/><a:chOff x="0" y="0"/><a:chExt cx="0" cy="0"/></a:xfrm></p:grpSpPr>
+      {rect(0, 0, 12192000, 760000, "1F4E79", "1F4E79", False)}
+      {text_box(520000, 170000, 9500000, 340000, title, 2300, True, "FFFFFF")}
+      {text_box(520000, 545000, 9800000, 200000, subtitle, 1050, False, "DBEAFE")}
+      {shapes}
+    </p:spTree>
+  </p:cSld>
+  <p:clrMapOvr><a:masterClrMapping/></p:clrMapOvr>
+</p:sld>'''
+
+
+slide1_shapes = ""
+slide1_shapes += card(
+    520000,
+    1060000,
+    3500000,
+    1700000,
+    "数据可以接入",
+    "QA 表包含供应商、商品编码、验货日期、来货数量、抽检个数、不合格个数、大批次品和问题描述,可支撑供应商质量维度统计。",
+)
+slide1_shapes += card(
+    4300000,
+    1060000,
+    3500000,
+    1700000,
+    "核心计算口径",
+    "抽检总数 = SUM(抽检/个数)\n不合格总数 = SUM(不合格/个 + 大批次品)\nQA合格率 = 合格数 / 抽检总数",
+    "059669",
+)
+slide1_shapes += card(
+    8080000,
+    1060000,
+    3500000,
+    1700000,
+    "供应商匹配",
+    "QA 表常用简称,如“顾舒家华”。系统供应商可能是公司全称。采用包含关系匹配:全称包含简称即可归属。",
+    "7C3AED",
+)
+slide1_shapes += rect(520000, 3200000, 11150000, 2500000)
+slide1_shapes += text_box(820000, 3430000, 10300000, 260000, "落地数据流", 1600, True, "1F4E79")
+slide1_shapes += text_box(
+    850000,
+    3920000,
+    10200000,
+    850000,
+    "QA Excel 多月份 Sheet → 读取所有月份 → 按供应商汇总抽检/不合格 → 宽松匹配供应商评分卡 → 输出 QA合格率、质量分、不合格数",
+    1400,
+    False,
+    "334155",
+)
+slide1_shapes += text_box(
+    850000,
+    5030000,
+    10200000,
+    450000,
+    "无 QA 数据的供应商:QA合格率显示“暂无”,质量分按 80 分中性值处理,避免误伤。",
+    1250,
+    False,
+    "64748B",
+)
+
+slide2_shapes = ""
+slide2_shapes += rect(520000, 1040000, 11150000, 1200000)
+slide2_shapes += text_box(850000, 1260000, 10200000, 280000, "综合评分新权重", 1750, True, "1F4E79")
+slide2_shapes += text_box(
+    850000,
+    1680000,
+    10200000,
+    360000,
+    "综合分 = 成本分 × 30% + 交付分 × 30% + 账期分 × 15% + 质量分 × 25%",
+    1550,
+    True,
+    "2563EB",
+)
+slide2_shapes += card(520000, 2550000, 2550000, 1900000, "页面新增字段", "质量分\nQA合格率\n不合格数\n可扩展:QA抽检数、QA来货数")
+slide2_shapes += card(3370000, 2550000, 2550000, 1900000, "质量预警规则", "QA抽检数 > 0\n且 QA合格率 < 95%\n则标记:质量预警", "DC2626")
+slide2_shapes += card(6220000, 2550000, 2550000, 1900000, "业务价值", "识别低价但质量不稳的供应商\n避免评分只被成本和交付主导\n支持整改、淘汰、谈判", "059669")
+slide2_shapes += card(9070000, 2550000, 2550000, 1900000, "后续建议", "将 QA Excel 入库\n建立质量检验明细表\n扩展质量趋势和问题类型分析", "7C3AED")
+slide2_shapes += rect(520000, 5050000, 11150000, 850000, "EFF6FF", "BFDBFE")
+slide2_shapes += text_box(
+    820000,
+    5290000,
+    10500000,
+    300000,
+    "结论:QA 数据可以纳入供应监测评分卡,推荐以 QA 合格率形成质量分,并作为 25% 权重参与综合评分。",
+    1350,
+    True,
+    "1E3A8A",
+)
+
+slide1 = slide("供应监测评分卡新增 QA 质量维度", "数据来源与计算逻辑", slide1_shapes)
+slide2 = slide("评分机制与页面落地", "新增质量分、QA合格率与质量预警", slide2_shapes)
+
+content_types = '''<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
+  <Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
+  <Default Extension="xml" ContentType="application/xml"/>
+  <Override PartName="/ppt/presentation.xml" ContentType="application/vnd.openxmlformats-officedocument.presentationml.presentation.main+xml"/>
+  <Override PartName="/ppt/slides/slide1.xml" ContentType="application/vnd.openxmlformats-officedocument.presentationml.slide+xml"/>
+  <Override PartName="/ppt/slides/slide2.xml" ContentType="application/vnd.openxmlformats-officedocument.presentationml.slide+xml"/>
+</Types>'''
+
+rels = '''<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
+  <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="ppt/presentation.xml"/>
+</Relationships>'''
+
+pres = '''<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<p:presentation xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
+                xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
+                xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main">
+  <p:sldIdLst><p:sldId id="256" r:id="rId1"/><p:sldId id="257" r:id="rId2"/></p:sldIdLst>
+  <p:sldSz cx="12192000" cy="6858000" type="screen4x3"/>
+  <p:notesSz cx="6858000" cy="9144000"/>
+</p:presentation>'''
+
+pres_rels = '''<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
+  <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide" Target="slides/slide1.xml"/>
+  <Relationship Id="rId2" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide" Target="slides/slide2.xml"/>
+</Relationships>'''
+
+empty_rels = '''<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"/>'''
+
+with ZipFile(OUT, "w", ZIP_DEFLATED) as z:
+    z.writestr("[Content_Types].xml", content_types.encode("utf-8"))
+    z.writestr("_rels/.rels", rels.encode("utf-8"))
+    z.writestr("ppt/presentation.xml", pres.encode("utf-8"))
+    z.writestr("ppt/_rels/presentation.xml.rels", pres_rels.encode("utf-8"))
+    z.writestr("ppt/slides/slide1.xml", slide1.encode("utf-8"))
+    z.writestr("ppt/slides/slide2.xml", slide2.encode("utf-8"))
+    z.writestr("ppt/slides/_rels/slide1.xml.rels", empty_rels.encode("utf-8"))
+    z.writestr("ppt/slides/_rels/slide2.xml.rels", empty_rels.encode("utf-8"))
+
+print(OUT.resolve())

+ 203 - 0
tools/create_supply_monitoring_ppt.ps1

@@ -0,0 +1,203 @@
+$ErrorActionPreference = "Stop"
+
+$repo = "C:\Users\akiby\Desktop\houduan\dtm"
+$desktop = "C:\Users\akiby\Desktop"
+$pptPath = Join-Path $desktop "供应监测一期功能讲解.pptx"
+$archPath = Join-Path $repo "docs\supply-monitoring-architecture-current.svg"
+
+function Add-Title($slide, [string]$title, [string]$subtitle = "") {
+    $shape = $slide.Shapes.AddTextbox(1, 45, 28, 1180, 42)
+    $shape.TextFrame.TextRange.Text = $title
+    $shape.TextFrame.TextRange.Font.Name = "Microsoft YaHei"
+    $shape.TextFrame.TextRange.Font.Size = 26
+    $shape.TextFrame.TextRange.Font.Bold = $true
+    $shape.TextFrame.TextRange.Font.Color.RGB = 0x1F2937
+
+    if ($subtitle -ne "") {
+        $sub = $slide.Shapes.AddTextbox(1, 47, 70, 1180, 26)
+        $sub.TextFrame.TextRange.Text = $subtitle
+        $sub.TextFrame.TextRange.Font.Name = "Microsoft YaHei"
+        $sub.TextFrame.TextRange.Font.Size = 12
+        $sub.TextFrame.TextRange.Font.Color.RGB = 0x667085
+    }
+}
+
+function Add-BulletBox($slide, [double]$x, [double]$y, [double]$w, [double]$h, [string]$heading, [string[]]$items, [int]$accentRgb = 0x6366F1) {
+    $box = $slide.Shapes.AddShape(5, $x, $y, $w, $h)
+    $box.Fill.ForeColor.RGB = 0xF8FAFC
+    $box.Line.ForeColor.RGB = 0xE5E7EB
+    $box.Line.Weight = 1
+
+    $bar = $slide.Shapes.AddShape(1, $x, $y, 6, $h)
+    $bar.Fill.ForeColor.RGB = $accentRgb
+    $bar.Line.Visible = $false
+
+    $head = $slide.Shapes.AddTextbox(1, $x + 18, $y + 14, $w - 32, 24)
+    $head.TextFrame.TextRange.Text = $heading
+    $head.TextFrame.TextRange.Font.Name = "Microsoft YaHei"
+    $head.TextFrame.TextRange.Font.Size = 16
+    $head.TextFrame.TextRange.Font.Bold = $true
+    $head.TextFrame.TextRange.Font.Color.RGB = 0x111827
+
+    $body = $slide.Shapes.AddTextbox(1, $x + 20, $y + 48, $w - 36, $h - 58)
+    $body.TextFrame.TextRange.Text = ($items | ForEach-Object { "• " + $_ }) -join "`r"
+    $body.TextFrame.TextRange.Font.Name = "Microsoft YaHei"
+    $body.TextFrame.TextRange.Font.Size = 12
+    $body.TextFrame.TextRange.Font.Color.RGB = 0x374151
+}
+
+function Add-Card($slide, [double]$x, [double]$y, [double]$w, [double]$h, [string]$title, [string]$value, [string]$note, [int]$rgb = 0x6366F1) {
+    $card = $slide.Shapes.AddShape(5, $x, $y, $w, $h)
+    $card.Fill.ForeColor.RGB = 0xFFFFFF
+    $card.Line.ForeColor.RGB = 0xE5E7EB
+
+    $t = $slide.Shapes.AddTextbox(1, $x + 18, $y + 12, $w - 36, 20)
+    $t.TextFrame.TextRange.Text = $title
+    $t.TextFrame.TextRange.Font.Name = "Microsoft YaHei"
+    $t.TextFrame.TextRange.Font.Size = 12
+    $t.TextFrame.TextRange.Font.Color.RGB = 0x667085
+
+    $v = $slide.Shapes.AddTextbox(1, $x + 18, $y + 34, $w - 36, 34)
+    $v.TextFrame.TextRange.Text = $value
+    $v.TextFrame.TextRange.Font.Name = "Microsoft YaHei"
+    $v.TextFrame.TextRange.Font.Size = 24
+    $v.TextFrame.TextRange.Font.Bold = $true
+    $v.TextFrame.TextRange.Font.Color.RGB = $rgb
+
+    $n = $slide.Shapes.AddTextbox(1, $x + 18, $y + 72, $w - 36, 20)
+    $n.TextFrame.TextRange.Text = $note
+    $n.TextFrame.TextRange.Font.Name = "Microsoft YaHei"
+    $n.TextFrame.TextRange.Font.Size = 10
+    $n.TextFrame.TextRange.Font.Color.RGB = 0x667085
+}
+
+$ppt = New-Object -ComObject PowerPoint.Application
+$ppt.Visible = [Microsoft.Office.Core.MsoTriState]::msoTrue
+$presentation = $ppt.Presentations.Add()
+$presentation.PageSetup.SlideWidth = 1280
+$presentation.PageSetup.SlideHeight = 720
+
+# Slide 1
+$slide = $presentation.Slides.Add(1, 12)
+$bg = $slide.Shapes.AddShape(1, 0, 0, 1280, 720)
+$bg.Fill.ForeColor.RGB = 0xF5F7FB
+$bg.Line.Visible = $false
+Add-Title $slide "供应监测一期功能讲解" "面向甲方说明:本阶段新增供应商查询、对比、付款计划与评分分析能力"
+Add-BulletBox $slide 70 150 350 220 "本次新增的一期能力" @(
+    "供应商列表查询:按名称、日期范围筛选",
+    "供应商横向对比:订单、入库、完成率、交付及时率",
+    "付款计划:按验收日期和账期生成预计付款",
+    "评分基础:成本、交付、账期三维度支撑"
+) 0x6366F1
+Add-BulletBox $slide 465 150 350 220 "甲方可获得的价值" @(
+    "快速识别重点供应商履约情况",
+    "降低人工汇总供应商账期和付款数据的成本",
+    "用统一口径支撑采购、财务和管理层沟通",
+    "为后续现金流预测和风险预警打基础"
+) 0x10B981
+Add-BulletBox $slide 860 150 350 220 "数据基础" @(
+    "采购订单数据",
+    "采购入库数据",
+    "供应商账期数据",
+    "订单入库匹配数据"
+) 0xF59E0B
+Add-Card $slide 130 430 220 100 "阶段重点" "第一阶段" "查询、对比、付款计划"
+Add-Card $slide 390 430 220 100 "核心对象" "供应商" "按供应商沉淀采购履约表现" 0x10B981
+Add-Card $slide 650 430 220 100 "关键输出" "报表化" "列表、对比、付款计划" 0xF59E0B
+Add-Card $slide 910 430 220 100 "后续承接" "三阶段" "现金流与风险预警" 0x9333EA
+
+# Slide 2
+$slide = $presentation.Slides.Add(2, 12)
+Add-Title $slide "供应监测模块架构" "蓝色标注为一期已建设能力,紫色标注为后续扩展方向"
+if (Test-Path $archPath) {
+    try {
+        $pic = $slide.Shapes.AddPicture($archPath, $false, $true, 45, 105, 1190, 585)
+    } catch {
+        $box = $slide.Shapes.AddShape(1, 80, 150, 1120, 470)
+        $box.Fill.ForeColor.RGB = 0xF8FAFC
+        $box.Line.ForeColor.RGB = 0xCBD5E1
+        $txt = $slide.Shapes.AddTextbox(1, 120, 320, 1040, 80)
+        $txt.TextFrame.TextRange.Text = "架构图文件:$archPath"
+        $txt.TextFrame.TextRange.Font.Name = "Microsoft YaHei"
+        $txt.TextFrame.TextRange.Font.Size = 18
+    }
+}
+
+# Slide 3
+$slide = $presentation.Slides.Add(3, 12)
+Add-Title $slide "一期功能:供应商查询与筛选" "从人工翻表转为系统化查询,降低供应商信息检索成本"
+Add-BulletBox $slide 70 140 360 260 "查询条件" @(
+    "供应商名称:支持模糊查询",
+    "日期范围:支持按业务期间筛选",
+    "列表切换:供应商列表、供应商对比、付款计划"
+) 0x6366F1
+Add-BulletBox $slide 470 140 360 260 "列表指标" @(
+    "供应商代码、账期、订单量",
+    "订单数量、入库数量、入库金额",
+    "交付及时率、完成率"
+) 0x10B981
+Add-BulletBox $slide 870 140 340 260 "业务说明" @(
+    "采购侧查看供应商履约表现",
+    "财务侧查看账期和预计付款基础",
+    "管理层查看供应商表现是否稳定"
+) 0xF59E0B
+Add-Card $slide 170 465 250 95 "使用场景" "供应商巡检" "按日期筛查异常供应商"
+Add-Card $slide 515 465 250 95 "使用对象" "采购/财务" "统一查看供应与付款基础"
+Add-Card $slide 860 465 250 95 "输出形式" "列表报表" "可作为管理汇报基础"
+
+# Slide 4
+$slide = $presentation.Slides.Add(4, 12)
+Add-Title $slide "一期功能:供应商对比与付款计划" "把供应履约和账期付款放到同一业务链路中"
+Add-BulletBox $slide 70 130 520 260 "供应商对比" @(
+    "支持选择多个供应商进行横向对比",
+    "对比采购订单、入库金额、完成率、准时率",
+    "用于识别供应商交付稳定性和履约差异"
+) 0x6366F1
+Add-BulletBox $slide 690 130 520 260 "付款计划" @(
+    "预计付款日 = 实际验收日期 + 供应商账期",
+    "按供应商和预计付款日汇总金额、数量、单据行",
+    "辅助财务提前查看未来付款压力"
+) 0x10B981
+Add-BulletBox $slide 190 450 900 120 "一期边界" @(
+    "当前付款计划按实际验收日期和账期推算;后续如接入发票日期、真实付款日期,可替换为更精确的真实付款计划。",
+    "当前重点是补齐供应商查询、对比和计划付款的基础数据闭环。"
+) 0xF59E0B
+
+# Slide 5
+$slide = $presentation.Slides.Add(5, 12)
+Add-Title $slide "系统页面示例" "此页用于放置甲方演示截图:供应商查询、对比与付款计划页面"
+$frame = $slide.Shapes.AddShape(1, 60, 115, 1160, 520)
+$frame.Fill.ForeColor.RGB = 0xF8FAFC
+$frame.Line.ForeColor.RGB = 0xCBD5E1
+$frame.Line.Weight = 1.2
+$hint = $slide.Shapes.AddTextbox(1, 220, 320, 840, 80)
+$hint.TextFrame.TextRange.Text = "页面截图占位:请把刚才浏览器截图另存为图片后发路径,我可以替换为原图。"
+$hint.TextFrame.TextRange.Font.Name = "Microsoft YaHei"
+$hint.TextFrame.TextRange.Font.Size = 20
+$hint.TextFrame.TextRange.Font.Color.RGB = 0x667085
+$hint.TextFrame.TextRange.ParagraphFormat.Alignment = 2
+
+# Slide 6
+$slide = $presentation.Slides.Add(6, 12)
+Add-Title $slide "给甲方的讲解口径" "建议按业务痛点、一期功能、数据口径、后续扩展四步讲"
+Add-BulletBox $slide 80 130 520 360 "讲解重点" @(
+    "以前:供应商订单、入库、账期和付款信息分散,人工汇总成本高。",
+    "现在:一期先打通供应商查询、横向对比和付款计划,形成统一查询入口。",
+    "价值:采购能看履约,财务能看付款计划,管理层能看供应商表现。",
+    "后续:在一期数据闭环上扩展现金流预测和风险预警。"
+) 0x6366F1
+Add-BulletBox $slide 680 130 520 360 "当前可演示内容" @(
+    "供应商列表:查看供应商订单量、入库金额、完成率。",
+    "供应商对比:选择多个供应商看履约差异。",
+    "付款计划:按账期推算预计付款日和金额。",
+    "综合评分:结合成本、交付和账期输出供应商能力排名。"
+) 0x10B981
+
+$presentation.SaveAs($pptPath)
+$presentation.Close()
+$ppt.Quit()
+
+[System.Runtime.InteropServices.Marshal]::ReleaseComObject($presentation) | Out-Null
+[System.Runtime.InteropServices.Marshal]::ReleaseComObject($ppt) | Out-Null
+
+Write-Output $pptPath

+ 191 - 0
tools/render_supply_flow.ps1

@@ -0,0 +1,191 @@
+Add-Type -AssemblyName System.Drawing
+
+$desktop = [Environment]::GetFolderPath("Desktop")
+$output = Join-Path $desktop "供应监测决策流程图.png"
+
+$width = 3600
+$height = 1850
+$bmp = New-Object System.Drawing.Bitmap($width, $height)
+$g = [System.Drawing.Graphics]::FromImage($bmp)
+$g.SmoothingMode = [System.Drawing.Drawing2D.SmoothingMode]::AntiAlias
+$g.TextRenderingHint = [System.Drawing.Text.TextRenderingHint]::ClearTypeGridFit
+$g.Clear([System.Drawing.Color]::White)
+
+$fontFamily = "Microsoft YaHei"
+$font = New-Object System.Drawing.Font($fontFamily, 28, [System.Drawing.FontStyle]::Regular)
+$boldFont = New-Object System.Drawing.Font($fontFamily, 30, [System.Drawing.FontStyle]::Bold)
+$titleFont = New-Object System.Drawing.Font($fontFamily, 44, [System.Drawing.FontStyle]::Bold)
+$labelFont = New-Object System.Drawing.Font($fontFamily, 24, [System.Drawing.FontStyle]::Bold)
+
+$black = [System.Drawing.Color]::FromArgb(35, 35, 35)
+$lineColor = [System.Drawing.Color]::FromArgb(72, 92, 120)
+$blueFill = [System.Drawing.Color]::FromArgb(235, 244, 255)
+$greenFill = [System.Drawing.Color]::FromArgb(235, 250, 241)
+$yellowFill = [System.Drawing.Color]::FromArgb(255, 249, 225)
+$redFill = [System.Drawing.Color]::FromArgb(255, 238, 238)
+$grayFill = [System.Drawing.Color]::FromArgb(247, 249, 252)
+$strokeBlue = [System.Drawing.Color]::FromArgb(69, 125, 197)
+$strokeGreen = [System.Drawing.Color]::FromArgb(46, 140, 88)
+$strokeYellow = [System.Drawing.Color]::FromArgb(196, 142, 35)
+$strokeRed = [System.Drawing.Color]::FromArgb(196, 76, 76)
+$strokeGray = [System.Drawing.Color]::FromArgb(130, 145, 165)
+
+function New-Pen($color, $width) {
+    $pen = New-Object System.Drawing.Pen($color, $width)
+    $pen.StartCap = [System.Drawing.Drawing2D.LineCap]::Round
+    $pen.EndCap = [System.Drawing.Drawing2D.LineCap]::Round
+    return $pen
+}
+
+$linePen = New-Pen $lineColor 4
+$arrowPen = New-Pen $lineColor 4
+$arrowPen.CustomEndCap = New-Object System.Drawing.Drawing2D.AdjustableArrowCap(8, 8)
+
+$textBrush = New-Object System.Drawing.SolidBrush($black)
+$labelBrush = New-Object System.Drawing.SolidBrush([System.Drawing.Color]::FromArgb(55, 75, 100))
+
+$sf = New-Object System.Drawing.StringFormat
+$sf.Alignment = [System.Drawing.StringAlignment]::Center
+$sf.LineAlignment = [System.Drawing.StringAlignment]::Center
+$sf.FormatFlags = [System.Drawing.StringFormatFlags]::LineLimit
+
+function Draw-RoundRect($graphics, $rect, $radius, $fill, $stroke) {
+    $path = New-Object System.Drawing.Drawing2D.GraphicsPath
+    $d = $radius * 2
+    $path.AddArc($rect.X, $rect.Y, $d, $d, 180, 90)
+    $path.AddArc($rect.Right - $d, $rect.Y, $d, $d, 270, 90)
+    $path.AddArc($rect.Right - $d, $rect.Bottom - $d, $d, $d, 0, 90)
+    $path.AddArc($rect.X, $rect.Bottom - $d, $d, $d, 90, 90)
+    $path.CloseFigure()
+    $graphics.FillPath((New-Object System.Drawing.SolidBrush($fill)), $path)
+    $graphics.DrawPath((New-Object System.Drawing.Pen($stroke, 4)), $path)
+}
+
+function Draw-Node($id, $x, $y, $w, $h, $text, $fill, $stroke) {
+    $rect = New-Object System.Drawing.RectangleF($x, $y, $w, $h)
+    Draw-RoundRect $g $rect 22 $fill $stroke
+    $inner = New-Object System.Drawing.RectangleF($x + 18, $y + 10, $w - 36, $h - 20)
+    $g.DrawString($text, $font, $textBrush, $inner, $sf)
+    $script:nodes[$id] = $rect
+}
+
+function Draw-Diamond($id, $x, $y, $w, $h, $text, $fill, $stroke) {
+    $path = New-Object System.Drawing.Drawing2D.GraphicsPath
+    $path.AddPolygon(@(
+        (New-Object System.Drawing.PointF(($x + $w / 2), $y)),
+        (New-Object System.Drawing.PointF(($x + $w), ($y + $h / 2))),
+        (New-Object System.Drawing.PointF(($x + $w / 2), ($y + $h))),
+        (New-Object System.Drawing.PointF($x, ($y + $h / 2)))
+    ))
+    $g.FillPath((New-Object System.Drawing.SolidBrush($fill)), $path)
+    $g.DrawPath((New-Object System.Drawing.Pen($stroke, 4)), $path)
+    $inner = New-Object System.Drawing.RectangleF($x + 40, $y + 18, $w - 80, $h - 36)
+    $g.DrawString($text, $boldFont, $textBrush, $inner, $sf)
+    $script:nodes[$id] = New-Object System.Drawing.RectangleF($x, $y, $w, $h)
+}
+
+function Center($rect) {
+    return New-Object System.Drawing.PointF(($rect.X + $rect.Width / 2), ($rect.Y + $rect.Height / 2))
+}
+
+function EdgePoint($rect, $side) {
+    switch ($side) {
+        "L" { return New-Object System.Drawing.PointF($rect.X, ($rect.Y + $rect.Height / 2)) }
+        "R" { return New-Object System.Drawing.PointF(($rect.X + $rect.Width), ($rect.Y + $rect.Height / 2)) }
+        "T" { return New-Object System.Drawing.PointF(($rect.X + $rect.Width / 2), $rect.Y) }
+        "B" { return New-Object System.Drawing.PointF(($rect.X + $rect.Width / 2), ($rect.Y + $rect.Height)) }
+    }
+}
+
+function Draw-Arrow($from, $fromSide, $to, $toSide, $label = "") {
+    $a = EdgePoint $nodes[$from] $fromSide
+    $b = EdgePoint $nodes[$to] $toSide
+    $g.DrawLine($arrowPen, $a, $b)
+    if ($label -ne "") {
+        $mx = ($a.X + $b.X) / 2
+        $my = ($a.Y + $b.Y) / 2
+        $labelRect = New-Object System.Drawing.RectangleF($mx - 42, $my - 32, 84, 44)
+        $g.FillRectangle([System.Drawing.Brushes]::White, $labelRect)
+        $g.DrawString($label, $labelFont, $labelBrush, $labelRect, $sf)
+    }
+}
+
+function Draw-PolylineArrow($points, $label = "", $labelX = 0, $labelY = 0) {
+    for ($i = 0; $i -lt $points.Count - 2; $i++) {
+        $g.DrawLine($linePen, $points[$i], $points[$i + 1])
+    }
+    $g.DrawLine($arrowPen, $points[$points.Count - 2], $points[$points.Count - 1])
+    if ($label -ne "") {
+        $labelRect = New-Object System.Drawing.RectangleF($labelX - 42, $labelY - 32, 84, 44)
+        $g.FillRectangle([System.Drawing.Brushes]::White, $labelRect)
+        $g.DrawString($label, $labelFont, $labelBrush, $labelRect, $sf)
+    }
+}
+
+$nodes = @{}
+
+$titleRect = New-Object System.Drawing.RectangleF(0, 35, $width, 70)
+$g.DrawString("供应监测补货决策流程", $titleFont, $textBrush, $titleRect, $sf)
+
+Draw-Node "A" 80 735 500 180 "更新数据`n预测需求 / 现有库存 / 在途库存`n半成品库存 / 已下单未要求来货" $blueFill $strokeBlue
+Draw-Node "B" 700 735 430 180 "核算现有可用量`n现有库存 + 半成品可组装量" $blueFill $strokeBlue
+Draw-Diamond "C" 1240 680 380 290 "现有可用量`n是否覆盖需求?" $yellowFill $strokeYellow
+
+Draw-Node "D" 1780 330 360 140 "现有库存可覆盖" $greenFill $strokeGreen
+Draw-Diamond "E" 2300 285 360 230 "是否需要`n组装半成品?" $yellowFill $strokeYellow
+Draw-Node "F" 2830 190 340 130 "无需来货 / 下单" $greenFill $strokeGreen
+Draw-Node "G" 2830 420 340 150 "安排半成品组装`n转为成品库存" $greenFill $strokeGreen
+
+Draw-Node "H" 1780 830 360 150 "现有可用量不足`n计算缺口" $redFill $strokeRed
+Draw-Diamond "I" 2300 795 360 230 "在途库存`n是否覆盖缺口?" $yellowFill $strokeYellow
+Draw-Node "J" 2830 710 340 150 "跟进在途到货`n更新到货计划" $greenFill $strokeGreen
+Draw-Node "K" 2830 960 340 130 "在途后仍有缺口" $redFill $strokeRed
+
+Draw-Diamond "L" 2300 1210 360 250 "已下单未要求来货`n是否覆盖缺口?" $yellowFill $strokeYellow
+Draw-Node "M" 2830 1180 340 160 "安排来货`n通知供应商发货 / 到货" $greenFill $strokeGreen
+Draw-Node "N" 1780 1275 360 160 "已下单量仍不足`n计算新增下单量" $redFill $strokeRed
+Draw-Diamond "O" 1240 1225 380 230 "是否急需?" $yellowFill $strokeYellow
+Draw-Node "P" 700 1125 430 150 "新增加急下单`n优先排产" $redFill $strokeRed
+Draw-Node "Q" 700 1390 430 130 "新增常规下单" $grayFill $strokeGray
+
+Draw-Node "S" 2830 610 340 130 "更新组装计划`n回写库存跟踪" $blueFill $strokeBlue
+Draw-Node "T" 2830 890 340 130 "更新在途到货计划`n回写库存跟踪" $blueFill $strokeBlue
+Draw-Node "U" 2830 1385 340 130 "更新来货计划`n回写库存跟踪" $blueFill $strokeBlue
+Draw-Node "V" 700 1610 430 130 "更新下单计划`n回写库存跟踪" $blueFill $strokeBlue
+Draw-Node "R" 1780 1610 420 130 "持续跟踪销售与库存" $greenFill $strokeGreen
+
+Draw-Arrow "A" "R" "B" "L"
+Draw-Arrow "B" "R" "C" "L"
+Draw-Arrow "C" "R" "D" "L" "是"
+Draw-Arrow "C" "R" "H" "L" "否"
+Draw-Arrow "D" "R" "E" "L"
+Draw-Arrow "E" "R" "F" "L" "否"
+Draw-Arrow "E" "R" "G" "L" "是"
+Draw-Arrow "H" "R" "I" "L"
+Draw-Arrow "I" "R" "J" "L" "是"
+Draw-Arrow "I" "R" "K" "L" "否"
+
+Draw-PolylineArrow @((EdgePoint $nodes["K"] "B"), (New-Object System.Drawing.PointF(3000, 1130)), (New-Object System.Drawing.PointF(2480, 1130)), (EdgePoint $nodes["L"] "T"))
+Draw-Arrow "L" "R" "M" "L" "是"
+Draw-PolylineArrow @((EdgePoint $nodes["L"] "L"), (New-Object System.Drawing.PointF(2160, 1335)), (EdgePoint $nodes["N"] "R")) "否" 2200 1335
+Draw-Arrow "N" "L" "O" "R"
+Draw-Arrow "O" "L" "P" "R" "是"
+Draw-PolylineArrow @((EdgePoint $nodes["O"] "B"), (New-Object System.Drawing.PointF(1430, 1455)), (EdgePoint $nodes["Q"] "R")) "否" 1300 1455
+
+Draw-PolylineArrow @((EdgePoint $nodes["G"] "B"), (New-Object System.Drawing.PointF(3000, 595)), (EdgePoint $nodes["S"] "T"))
+Draw-PolylineArrow @((EdgePoint $nodes["J"] "B"), (New-Object System.Drawing.PointF(3000, 875)), (EdgePoint $nodes["T"] "T"))
+Draw-PolylineArrow @((EdgePoint $nodes["M"] "B"), (New-Object System.Drawing.PointF(3000, 1365)), (EdgePoint $nodes["U"] "T"))
+Draw-Arrow "P" "B" "V" "T"
+Draw-Arrow "Q" "B" "V" "T"
+
+Draw-PolylineArrow @((EdgePoint $nodes["F"] "B"), (New-Object System.Drawing.PointF(3000, 1645)), (EdgePoint $nodes["R"] "R"))
+Draw-PolylineArrow @((EdgePoint $nodes["S"] "B"), (New-Object System.Drawing.PointF(3000, 1665)), (EdgePoint $nodes["R"] "R"))
+Draw-PolylineArrow @((EdgePoint $nodes["T"] "B"), (New-Object System.Drawing.PointF(3000, 1680)), (EdgePoint $nodes["R"] "R"))
+Draw-PolylineArrow @((EdgePoint $nodes["U"] "B"), (New-Object System.Drawing.PointF(3000, 1700)), (EdgePoint $nodes["R"] "R"))
+Draw-Arrow "V" "R" "R" "L"
+
+$bmp.Save($output, [System.Drawing.Imaging.ImageFormat]::Png)
+$g.Dispose()
+$bmp.Dispose()
+
+Write-Output $output

+ 180 - 0
tools/supply_flow_diagram.svg

@@ -0,0 +1,180 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="3600" height="1850" viewBox="0 0 3600 1850">
+  <defs>
+    <marker id="arrow" viewBox="0 0 12 12" refX="10" refY="6" markerWidth="10" markerHeight="10" orient="auto-start-reverse">
+      <path d="M 1 1 L 11 6 L 1 11 z" fill="#485c78"/>
+    </marker>
+    <filter id="shadow" x="-10%" y="-20%" width="130%" height="150%">
+      <feDropShadow dx="0" dy="5" stdDeviation="5" flood-color="#152033" flood-opacity="0.12"/>
+    </filter>
+    <style>
+      .bg { fill: #ffffff; }
+      .title { font-family: "Microsoft YaHei", "PingFang SC", sans-serif; font-size: 44px; font-weight: 700; fill: #232323; }
+      .node-text { font-family: "Microsoft YaHei", "PingFang SC", sans-serif; font-size: 28px; fill: #232323; text-anchor: middle; dominant-baseline: middle; }
+      .decision-text { font-family: "Microsoft YaHei", "PingFang SC", sans-serif; font-size: 30px; font-weight: 700; fill: #232323; text-anchor: middle; dominant-baseline: middle; }
+      .edge-label { font-family: "Microsoft YaHei", "PingFang SC", sans-serif; font-size: 24px; font-weight: 700; fill: #374b64; text-anchor: middle; dominant-baseline: middle; }
+      .edge { fill: none; stroke: #485c78; stroke-width: 4; stroke-linecap: round; stroke-linejoin: round; marker-end: url(#arrow); }
+      .node { rx: 22; ry: 22; filter: url(#shadow); stroke-width: 4; }
+      .blue { fill: #ebf4ff; stroke: #457dc5; }
+      .green { fill: #ebfaf1; stroke: #2e8c58; }
+      .yellow { fill: #fff9e1; stroke: #c48e23; }
+      .red { fill: #ffeeee; stroke: #c44c4c; }
+      .gray { fill: #f7f9fc; stroke: #8291a5; }
+      .label-bg { fill: #ffffff; stroke: #d8e0ea; stroke-width: 2; rx: 16; ry: 16; }
+    </style>
+  </defs>
+
+  <rect class="bg" width="3600" height="1850"/>
+  <text class="title" x="1800" y="80">&#x4F9B;&#x5E94;&#x76D1;&#x6D4B;&#x8865;&#x8D27;&#x51B3;&#x7B56;&#x6D41;&#x7A0B;</text>
+
+  <rect class="node blue" x="80" y="735" width="500" height="180"/>
+  <text class="node-text" x="330" y="797">
+    <tspan x="330" dy="0">&#x66F4;&#x65B0;&#x6570;&#x636E;</tspan>
+    <tspan x="330" dy="42">&#x9884;&#x6D4B;&#x9700;&#x6C42; / &#x73B0;&#x6709;&#x5E93;&#x5B58; / &#x5728;&#x9014;&#x5E93;&#x5B58;</tspan>
+    <tspan x="330" dy="42">&#x534A;&#x6210;&#x54C1;&#x5E93;&#x5B58; / &#x5DF2;&#x4E0B;&#x5355;&#x672A;&#x8981;&#x6C42;&#x6765;&#x8D27;</tspan>
+  </text>
+
+  <rect class="node blue" x="700" y="735" width="430" height="180"/>
+  <text class="node-text" x="915" y="805">
+    <tspan x="915" dy="0">&#x6838;&#x7B97;&#x73B0;&#x6709;&#x53EF;&#x7528;&#x91CF;</tspan>
+    <tspan x="915" dy="46">&#x73B0;&#x6709;&#x5E93;&#x5B58; + &#x534A;&#x6210;&#x54C1;&#x53EF;&#x7EC4;&#x88C5;&#x91CF;</tspan>
+  </text>
+
+  <polygon class="yellow" points="1430,680 1620,825 1430,970 1240,825" filter="url(#shadow)" stroke-width="4"/>
+  <text class="decision-text" x="1430" y="806">
+    <tspan x="1430" dy="0">&#x73B0;&#x6709;&#x53EF;&#x7528;&#x91CF;</tspan>
+    <tspan x="1430" dy="48">&#x662F;&#x5426;&#x8986;&#x76D6;&#x9700;&#x6C42;?</tspan>
+  </text>
+
+  <rect class="node green" x="1780" y="330" width="360" height="140"/>
+  <text class="node-text" x="1960" y="400">&#x73B0;&#x6709;&#x5E93;&#x5B58;&#x53EF;&#x8986;&#x76D6;</text>
+
+  <polygon class="yellow" points="2480,285 2660,400 2480,515 2300,400" filter="url(#shadow)" stroke-width="4"/>
+  <text class="decision-text" x="2480" y="382">
+    <tspan x="2480" dy="0">&#x662F;&#x5426;&#x9700;&#x8981;</tspan>
+    <tspan x="2480" dy="48">&#x7EC4;&#x88C5;&#x534A;&#x6210;&#x54C1;?</tspan>
+  </text>
+
+  <rect class="node green" x="2830" y="190" width="340" height="130"/>
+  <text class="node-text" x="3000" y="255">&#x65E0;&#x9700;&#x6765;&#x8D27; / &#x4E0B;&#x5355;</text>
+
+  <rect class="node green" x="2830" y="420" width="340" height="150"/>
+  <text class="node-text" x="3000" y="474">
+    <tspan x="3000" dy="0">&#x5B89;&#x6392;&#x534A;&#x6210;&#x54C1;&#x7EC4;&#x88C5;</tspan>
+    <tspan x="3000" dy="44">&#x8F6C;&#x4E3A;&#x6210;&#x54C1;&#x5E93;&#x5B58;</tspan>
+  </text>
+
+  <rect class="node red" x="1780" y="830" width="360" height="150"/>
+  <text class="node-text" x="1960" y="884">
+    <tspan x="1960" dy="0">&#x73B0;&#x6709;&#x53EF;&#x7528;&#x91CF;&#x4E0D;&#x8DB3;</tspan>
+    <tspan x="1960" dy="44">&#x8BA1;&#x7B97;&#x7F3A;&#x53E3;</tspan>
+  </text>
+
+  <polygon class="yellow" points="2480,795 2660,910 2480,1025 2300,910" filter="url(#shadow)" stroke-width="4"/>
+  <text class="decision-text" x="2480" y="892">
+    <tspan x="2480" dy="0">&#x5728;&#x9014;&#x5E93;&#x5B58;</tspan>
+    <tspan x="2480" dy="48">&#x662F;&#x5426;&#x8986;&#x76D6;&#x7F3A;&#x53E3;?</tspan>
+  </text>
+
+  <rect class="node green" x="2830" y="710" width="340" height="150"/>
+  <text class="node-text" x="3000" y="764">
+    <tspan x="3000" dy="0">&#x8DDF;&#x8FDB;&#x5728;&#x9014;&#x5230;&#x8D27;</tspan>
+    <tspan x="3000" dy="44">&#x66F4;&#x65B0;&#x5230;&#x8D27;&#x8BA1;&#x5212;</tspan>
+  </text>
+
+  <rect class="node red" x="2830" y="960" width="340" height="130"/>
+  <text class="node-text" x="3000" y="1025">&#x5728;&#x9014;&#x540E;&#x4ECD;&#x6709;&#x7F3A;&#x53E3;</text>
+
+  <polygon class="yellow" points="2480,1210 2660,1335 2480,1460 2300,1335" filter="url(#shadow)" stroke-width="4"/>
+  <text class="decision-text" x="2480" y="1310">
+    <tspan x="2480" dy="0">&#x5DF2;&#x4E0B;&#x5355;&#x672A;&#x8981;&#x6C42;&#x6765;&#x8D27;</tspan>
+    <tspan x="2480" dy="48">&#x662F;&#x5426;&#x8986;&#x76D6;&#x7F3A;&#x53E3;?</tspan>
+  </text>
+
+  <rect class="node green" x="2830" y="1180" width="340" height="160"/>
+  <text class="node-text" x="3000" y="1238">
+    <tspan x="3000" dy="0">&#x5B89;&#x6392;&#x6765;&#x8D27;</tspan>
+    <tspan x="3000" dy="44">&#x901A;&#x77E5;&#x4F9B;&#x5E94;&#x5546;&#x53D1;&#x8D27; / &#x5230;&#x8D27;</tspan>
+  </text>
+
+  <rect class="node red" x="1780" y="1275" width="360" height="160"/>
+  <text class="node-text" x="1960" y="1333">
+    <tspan x="1960" dy="0">&#x5DF2;&#x4E0B;&#x5355;&#x91CF;&#x4ECD;&#x4E0D;&#x8DB3;</tspan>
+    <tspan x="1960" dy="44">&#x8BA1;&#x7B97;&#x65B0;&#x589E;&#x4E0B;&#x5355;&#x91CF;</tspan>
+  </text>
+
+  <polygon class="yellow" points="1430,1225 1620,1340 1430,1455 1240,1340" filter="url(#shadow)" stroke-width="4"/>
+  <text class="decision-text" x="1430" y="1340">&#x662F;&#x5426;&#x6025;&#x9700;?</text>
+
+  <rect class="node red" x="700" y="1125" width="430" height="150"/>
+  <text class="node-text" x="915" y="1179">
+    <tspan x="915" dy="0">&#x65B0;&#x589E;&#x52A0;&#x6025;&#x4E0B;&#x5355;</tspan>
+    <tspan x="915" dy="44">&#x4F18;&#x5148;&#x6392;&#x4EA7;</tspan>
+  </text>
+
+  <rect class="node gray" x="700" y="1390" width="430" height="130"/>
+  <text class="node-text" x="915" y="1455">&#x65B0;&#x589E;&#x5E38;&#x89C4;&#x4E0B;&#x5355;</text>
+
+  <rect class="node blue" x="2830" y="610" width="340" height="130"/>
+  <text class="node-text" x="3000" y="655">
+    <tspan x="3000" dy="0">&#x66F4;&#x65B0;&#x7EC4;&#x88C5;&#x8BA1;&#x5212;</tspan>
+    <tspan x="3000" dy="42">&#x56DE;&#x5199;&#x5E93;&#x5B58;&#x8DDF;&#x8E2A;</tspan>
+  </text>
+
+  <rect class="node blue" x="2830" y="890" width="340" height="130"/>
+  <text class="node-text" x="3000" y="935">
+    <tspan x="3000" dy="0">&#x66F4;&#x65B0;&#x5728;&#x9014;&#x5230;&#x8D27;&#x8BA1;&#x5212;</tspan>
+    <tspan x="3000" dy="42">&#x56DE;&#x5199;&#x5E93;&#x5B58;&#x8DDF;&#x8E2A;</tspan>
+  </text>
+
+  <rect class="node blue" x="2830" y="1385" width="340" height="130"/>
+  <text class="node-text" x="3000" y="1430">
+    <tspan x="3000" dy="0">&#x66F4;&#x65B0;&#x6765;&#x8D27;&#x8BA1;&#x5212;</tspan>
+    <tspan x="3000" dy="42">&#x56DE;&#x5199;&#x5E93;&#x5B58;&#x8DDF;&#x8E2A;</tspan>
+  </text>
+
+  <rect class="node blue" x="700" y="1610" width="430" height="130"/>
+  <text class="node-text" x="915" y="1655">
+    <tspan x="915" dy="0">&#x66F4;&#x65B0;&#x4E0B;&#x5355;&#x8BA1;&#x5212;</tspan>
+    <tspan x="915" dy="42">&#x56DE;&#x5199;&#x5E93;&#x5B58;&#x8DDF;&#x8E2A;</tspan>
+  </text>
+
+  <rect class="node green" x="1780" y="1610" width="420" height="130"/>
+  <text class="node-text" x="1990" y="1675">&#x6301;&#x7EED;&#x8DDF;&#x8E2A;&#x9500;&#x552E;&#x4E0E;&#x5E93;&#x5B58;</text>
+
+  <path class="edge" d="M580,825 L700,825"/>
+  <path class="edge" d="M1130,825 L1240,825"/>
+  <path class="edge" d="M1620,825 L1780,400"/>
+  <rect class="label-bg" x="1668" y="580" width="70" height="42"/><text class="edge-label" x="1703" y="602">&#x662F;</text>
+  <path class="edge" d="M1620,825 L1780,905"/>
+  <rect class="label-bg" x="1668" y="860" width="70" height="42"/><text class="edge-label" x="1703" y="882">&#x5426;</text>
+  <path class="edge" d="M2140,400 L2300,400"/>
+  <path class="edge" d="M2660,400 L2830,255"/>
+  <rect class="label-bg" x="2730" y="303" width="70" height="42"/><text class="edge-label" x="2765" y="325">&#x5426;</text>
+  <path class="edge" d="M2660,400 L2830,495"/>
+  <rect class="label-bg" x="2730" y="428" width="70" height="42"/><text class="edge-label" x="2765" y="450">&#x662F;</text>
+  <path class="edge" d="M2140,905 L2300,910"/>
+  <path class="edge" d="M2660,910 L2830,785"/>
+  <rect class="label-bg" x="2730" y="820" width="70" height="42"/><text class="edge-label" x="2765" y="842">&#x662F;</text>
+  <path class="edge" d="M2660,910 L2830,1025"/>
+  <rect class="label-bg" x="2730" y="942" width="70" height="42"/><text class="edge-label" x="2765" y="964">&#x5426;</text>
+  <path class="edge" d="M3000,1090 L3000,1130 L2480,1130 L2480,1210"/>
+  <path class="edge" d="M2660,1335 L2830,1260"/>
+  <rect class="label-bg" x="2730" y="1270" width="70" height="42"/><text class="edge-label" x="2765" y="1292">&#x662F;</text>
+  <path class="edge" d="M2300,1335 L2140,1355"/>
+  <rect class="label-bg" x="2165" y="1314" width="70" height="42"/><text class="edge-label" x="2200" y="1336">&#x5426;</text>
+  <path class="edge" d="M1780,1355 L1620,1340"/>
+  <path class="edge" d="M1240,1340 L1130,1200"/>
+  <rect class="label-bg" x="1160" y="1245" width="70" height="42"/><text class="edge-label" x="1195" y="1267">&#x662F;</text>
+  <path class="edge" d="M1430,1455 L1430,1490 L1130,1455"/>
+  <rect class="label-bg" x="1265" y="1435" width="70" height="42"/><text class="edge-label" x="1300" y="1457">&#x5426;</text>
+  <path class="edge" d="M3000,570 L3000,610"/>
+  <path class="edge" d="M3000,860 L3000,890"/>
+  <path class="edge" d="M3000,1340 L3000,1385"/>
+  <path class="edge" d="M915,1275 L915,1610"/>
+  <path class="edge" d="M915,1520 L915,1610"/>
+  <path class="edge" d="M3000,320 L3000,1645 L2200,1675"/>
+  <path class="edge" d="M3000,740 L3000,1665 L2200,1675"/>
+  <path class="edge" d="M3000,1020 L3000,1680 L2200,1675"/>
+  <path class="edge" d="M3000,1515 L3000,1700 L2200,1675"/>
+  <path class="edge" d="M1130,1675 L1780,1675"/>
+</svg>

BIN
供应监测评分卡版块优化需求整理.docx


Некоторые файлы не были показаны из-за большого количества измененных файлов