news 2026/9/14 2:09:10

Spree 6.1 B2B 订单文档体系:Documents 区域、装箱清单与付款指示打印设计全解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Spree 6.1 B2B 订单文档体系:Documents 区域、装箱清单与付款指示打印设计全解析

Spree 6.1 B2B 订单文档体系:Documents 区域、装箱清单与付款指示打印设计全解析

【免费下载链接】spreeOpen Source eCommerce Platform for B2B, Marketplace, and Enterprise. REST API, TypeScript SDK, and production-ready Next.js storefront. Self-host it. Own your stack. No vendor lock-in. Zero platform fees.项目地址: https://gitcode.com/GitHub_Trending/sp/spree

导读

B2B 订单在成交前后会积累大量纸质文档——采购方的采购订单(PO)、报价单、装箱清单、发货文件,这些在今天的 Spree 中只能散落在邮件线程里。本文基于docs/plans/6.1-b2b-order-documents.md设计计划,系统讲解 Spree 6.1 如何为订单引入一个Documents 文档区域(上传文件、私有存储、按文档粒度的客户可见性、买家订单视图展示),并扩展 Dashboard 已有的客户端打印路径,新增箱级装箱清单付款指示单两类可打印文档。读完本文,你将掌握Spree::OrderDocument的数据模型、私有附件与可见性设计、两类打印文档的数据来源(freight summary 与 payment terms),以及本计划与 PO 编号、批发运输、付款条款等上游计划的衔接方式,并理解为什么"发票"与 PDF 服务端渲染被刻意排除在 6.1 的 OSS 范围之外。

背景:B2B 订单的纸质文档困局

批发与 B2B 采购和零售完全不同:一单生意在真正发货前,双方往往已经交换了采购订单、报价、装箱说明、运输文件等大量资料。这些资料是订单的"事实档案"——买方财务部门几年后对账时依赖它们,海关与货运代理也需要它们。但当前 Spree 中订单模型只承载结构化数据,纸质文件的唯一去向是邮件线程,这带来两个问题:

  • 文档与订单脱钩:文件存在收发双方的邮箱里,无法按订单检索,无法控制谁能看到什么;
  • 打印输出能力有限:Dashboard 已有装箱单(packing slip)的客户端打印实现,但批发场景需要的箱级装箱清单(cartons/pallets/CBM/毛重)和付款指示单(terms + 银行转账信息 + 付款参考号)尚无输出载体。

该计划(docs/plans/6.1-b2b-order-documents.md)正是为了补齐这两块:一个订单级的文档存储区域,以及两类数据已就绪但尚无出口的生成式打印文档。它同时明确划定了边界——真正的发票(带编号的 proforma 与商业发票、PDF 生成、法定编号)不在 6.1 范围内,那属于 Enterprise 的 terms/invoicing 产品线。

核心设计决策(Key Decisions)

计划的开篇以"不得偏离"的强约束列出四条关键决策,它们是整个设计的骨架。

1.Spree::OrderDocument是"上传文件行",不是"生成流水线"

这是最重要的一条决策:文档区域是一个附件行模型,而不是服务器端文档生成管线。每行文档记录包含:

  • order_id:归属订单;
  • label:文档标签(如"装箱清单扫描件");
  • customer_visible:客户可见性,默认false(内部文书保持内部);
  • 私有存储附件:使用防伪造(spoofing-protected)校验,沿用 seller-submission / tax-certificate 先例;
  • uploaded_by:多态关联,记录上传者是员工还是客户——买家可以通过账户订单视图自行上传自己的文件。

计划中的po_document(来自 6.0-b2b-customer-po-numbers.md)保留其专属槽位——因为它有语义(强制必填检查)——但 Documents 区域会把它与通用文档行并列展示。

2. 生成式文档继续走客户端 HTML 打印

6.1 中两类生成文档(装箱清单、付款指示单)沿用已发布的 packing-slip 模式:刻意做成打印窗口而非 admin 路由,因为订单页面已经加载了所需全部数据。装箱清单从冻结的 freight summary 中取出 carton/pallet/CBM 区块;付款指示单渲染 terms 计划表 + 银行偏好 + 付款参考号。两者都不自称发票、不带发票编号、不产生存储工件

3. 核心不带 PDF 栈

计划明确:当未来需要服务端渲染时,它将随 Enterprise 发票产品一起到来;OSS 的打印与上传永远不会引入 PDF 依赖。这让 OSS 维护成本保持可控,同时把"法定编号 + PDF"划给商业产品线。

4. 客户可见性按文档粒度,默认私有

内部文书保持内部;买家的订单视图只列出customer_visible: true的文档,并通过带过期时间的签名 URL(私有存储读取模式)访问。

Spree::OrderDocument模型设计

计划给出了模型的骨架代码:

class Spree::OrderDocument < Spree.base_class belongs_to :order, class_name: 'Spree::Order' belongs_to :uploaded_by, polymorphic: true, optional: true has_one_attached :file, service: Spree.private_storage_service_name validates :label, presence: true # customer_visible boolean, default false, null: false end

这个模型的设计可以在仓库现有的附件先例中找到完整的实现依据:

  • 私有存储服务Spree.private_storage_service_name是核心中处理机密文件的统一入口。Spree::TaxExemptionCertificate#document(tax_exemption_certificate.rb)与Spree::SellerRequirementSubmission#file(seller_requirement_submission.rb)都通过它挂载附件,注释明确说明原因:"Confidential, so the private service rather than the public one that serves product images"。OrderDocument#file沿用同一模式——买家的 PO 里带着价格、条款和内部成本代码,只能通过认证的下载动作访问。
  • 防伪造内容校验Spree::SellerRequirementSubmission中的做法是"让字节决定文件是什么,而不是上传者发送的 header"(Spree::AttachmentContentTypeValidator),Spree::Purchase::PurchaseOrder模块(purchase_order.rb)则给出了更具体的清单:application/pdfimage/jpegimage/pngimage/heicimage/webp、Word 文档等,并限制单文件 10 MB——计划中的OrderDocument附件应沿用同一套校验思路。
  • 有界上传TaxExemptionCertificate::MAX_DOCUMENT_SIZE = 10.megabytes的注释解释了原因:"the download action reads the whole blob into memory to serve it, so an unbounded upload would be an unbounded allocation per request"。PurchaseOrder还进一步实现了磁盘上实际尺寸校验(分块下载测量,防止上传者虚报byte_size绕过校验),这些约束都是OrderDocument的私有附件应当继承的安全基线。

API 表面:Admin 与 Store 两条链路

计划为文档区域规划了两条 API 链路,分工清晰:

Admin API:嵌套 CRUD + 直传

  • nested /admin/orders/:id/documentsCRUD;
  • 上传走signed blob 直传(direct upload),与服务端转存相比可避免大文件占满请求线程。

Store API:买家视角的文档索引与上传

  • 账户订单视图上的documents index只暴露customer_visible的文档
  • create:买家上传自己的文件,永远对自己可见,并为员工标记为"买家提供"(buyer-supplied)。

这一"可见性过滤 + 买家可写"的组合正是文档区域的差异化价值:员工上传的内部核对单不会泄漏给买家,而买家补传的 PO 修订版又能立即回到员工视野。

Dashboard 与 Storefront 的呈现

Dashboard:订单页 Documents 卡片

订单页面新增 Documents 卡片,提供四类操作:

  • 上传(upload)
  • 可见性开关(visibility toggle)——对应customer_visible字段
  • 下载(download)——走签名 URL 私有读取
  • 删除(带确认)(delete-with-confirm)

打印动作紧挨现有 packing slip 按钮排列,即订单页同时具备"装箱单 / 装箱清单 / 付款指示单"三类打印入口。

Storefront:完整的买家订单视图

Storefront 订单页新增文档列表,与 6.1 另外两个计划组合成买家的完整视图:

  • 订单阶段 + 预计就绪日期(6.1-order-stages.md);
  • 付款计划表(6.0-6.1-b2b-payment-terms.md);
  • 发货 / freight summary;
  • 文档列表。

两类生成式打印文档的数据来源

6.1 扩展的是 Dashboard 现有的客户端打印路径,因此理解先例至关重要。

先例:packing-slip 的客户端打印模式

仓库中已实现的原型是 packing-slip.ts。它的核心模式值得细读:

  1. FulfillmentOrder数据构建行;
  2. 生成完整 HTML(含内联 CSS)写入printWindow.document,然后window.open新窗口并调用print()
  3. 刻意不是 admin 路由——注释写明:"the admin shell (sidebar, top bar) would print with an in-app page, and everything the slip needs is already loaded on the order screen";
  4. 不含价格——"a packing slip says what is in the box, not what it cost";
  5. 所有用户文本经escapeHtml转义,防止订单数据注入打印 HTML。

6.1 的两类新打印将复用这一整套模式。

装箱清单(Carton-Level Packing List)

装箱清单在 packing slip 的基础上叠加物流区块,数据来自冻结的 freight summary:

  • 单位数(units)
  • 箱数(cartons)
  • 托盘数(pallets)
  • 毛重(gross weight)
  • 体积 CBM

外加 PO 编号与订单号。这里的"冻结"语义来自批发运输计划(6.0-b2b-wholesale-shipping.md):freight summary 在估价时由 freight provider 计算,并持久化到所选DeliveryRate#metadata(jsonb),订单面读取一律来自那里,绝不从活跃商品目录重新推导——与 duty snapshot 的 doctrine 一致。

仓库中 freight_summary.rb 的实现给出了这个数据结构的完整形态:build从库存包装内容沿Unit → Carton → Pallet → CBM/Weight链条汇总,total_unitstotal_cartonstotal_pallets(仅当所有箱级行都声明了cartons_per_pallet时非 nil)、total_volume(CBM)、total_weight(kg),以及complete?标志——当某行缺少箱数据时退回单位体积/重量并标记不完整,让数字保持可用同时如实告知"部分数据来自未测量的目录"。as_json的输出形状就是装箱清单打印区块直接消费的数据。

付款指示单(Payment-Instructions Sheet)

付款指示单渲染的是付款条款计划(6.0-6.1-b2b-payment-terms.md)中的数据:

  • 订单号 + PO 引用
  • 条款快照(terms snapshot):kindprepaid | deposit)、deposit_percentagebalance_due_label(如"Before shipping")
  • 现在应付金额(amount due now)
  • 银行转账信息(bank transfer details)+付款参考号(payment reference)

关键点:条款快照在订单成交时冻结(与议价价格同理,公司后续条款变更不影响已成交订单),打印读取的始终是订单快照而非重新解析;付款参考号是 6.1 存储的、可 ransack 搜索的reference列(RF/ISO 11649 结构化债权人参考风格,校验位基于付款的 prefixed id),保证银行对账单的一行能精确对应一笔付款。而派生编号R1001-P1明确不作为对账标识——兄弟记录被删除时派生编号会漂移,这正是付款条款计划记录在案的教训。计划还规定:当 PO 编号与付款参考号存在时,打印必须渲染它们(PO 计划中的文档约束同样适用)。

与上游计划的衔接:本计划的三大依赖

计划在头部声明了三个依赖,理解它们才能理解文档区域的数据从何而来:

依赖一:Customer PO Numbers(6.0-b2b-customer-po-numbers.md,已实现)

po_document订单的第一个附件,也是本计划模型的原型。它实现为Spree::Purchase::PurchaseOrderconcern(Cart 与 Order 共用),包含po_number规范化、私有po_document附件(防伪造 + 10 MB 上限)、以及通过resolved_company读取的po_number_required?。PO 编号在成交时随购买字段复制到订单,同一 blob 重新挂到订单(blob 共享,media-library 先例)。本计划文档区域列出它时,其"必填"语义保持专属槽位——通用文档行不承担必填检查。

依赖二:B2B Wholesale Shipping(6.0-b2b-wholesale-shipping.md,已实现)

freight summary 是装箱清单打印的数据源。批发运输计划还明确了一条与本计划相关的边界:货运单据(提单、托盘标牌、装箱清单)绝不放进Spree::ShippingLabel(那张表存放带成本和退款生命周期的承运商标签),而是进入Spree::OrderDocument或本计划的客户端打印。仓库中 freight 数据已落地于 freight.rb、delivery_rate.rb、delivery_rate_provider/freight.rb 等文件。

依赖三:B2B Payment Terms(6.0-6.1-b2b-payment-terms.md,草稿中)

条款快照与银行参考喂给付款指示单。该计划同时预告了PaymentMethod::BankTransfer(6.1)加入核心、每笔付款生成存储型reference、以及 storefront pay-balance 路由——付款指示单正是把这些信息落到可打印载体的出口。

迁移路径:三阶段落地

计划给出的落地顺序清晰,且明确"Nothing to migrate"——没有数据迁移需求:

  1. Schema + model + admin 嵌套 CRUD + dashboard 卡片:先打通存储与管理面;
  2. Store 表面(文档列表 + 买家上传):打通买家可见性链路;
  3. 两类打印(装箱清单区块、付款指示单):最后补上输出面。

由于 6.0 的 PO 文档(po_document)与 freight summary 已实现、payment terms 仍在草稿,付款指示单的实际数据依赖会随 payment-terms 计划的推进而就绪。

约束与边界(Constraints on Current Work)

计划对后续实现设定了三条硬约束,值得任何计划读者铭记:

  1. 订单面向的文件必须走OrderDocument或专属语义槽位(如po_document)——绝不允许在其他模型上挂松散附件。这是架构纪律:附件必须可检索、可控制可见性,而不是散落在任意模型上。
  2. OSS 中禁用发票词汇——任何模型、编号或文档都不得命名为 invoice;付款文档只能叫"payment instructions"(付款指示)。这既是合规谨慎,也是产品边界(法定编号是 Enterprise terms/invoicing 的领域)。
  3. 打印必须渲染po_number与付款参考号(存在时)——PO 计划确立的文档约束在此延续,保证会计人员无论手拿哪个参考号都能对上同一笔交易。

刻意不做的事与开放问题

计划的Open Questions 为空,但有一项刻意的取舍值得强调:不把生成的打印自动附加为存储文档。理由是——"a print is a view of live data; a stored artifact is an invoicing feature":打印是对实时数据的视图(条款或物流数据变了,打印跟着变),而存储工件属于发票产品线。这一区分贯穿全计划:6.1 的文档区域只处理"上传的真实文件",打印只是"实时数据的输出窗口",两者不混为一谈。

总结

6.1-b2b-order-documents.md为 Spree 的 B2B 能力补齐了最后一块订单拼图:通过Spree::OrderDocument让纸质文档获得订单归属、私有存储与细粒度可见性;通过两类客户端打印(箱级装箱清单、付款指示单)让 freight summary 与 payment terms 这些已就绪的数据获得出口。整个设计遵循四条一贯原则:附件归口单一模型、打印不产生存储工件、OSS 不引入 PDF 与发票词汇、数据读取以冻结快照为准。它同时为 Enterprise 的 terms/invoicing 产品留下清晰的挂载点——未来若需服务端 PDF 渲染与法定编号,将在商业产品线中到来,OSS 的上传与打印路径无需为此改动。

延伸阅读

  • 上游依赖:docs/plans/6.0-b2b-customer-po-numbers.md、docs/plans/6.0-b2b-wholesale-shipping.md、docs/plans/6.0-6.1-b2b-payment-terms.md
  • 相邻计划:docs/plans/6.1-order-stages.md(买家订单视图的阶段与预计日期)、docs/plans/6.0-document-numbers.md(编号机制,核心从不承诺法定编号)
  • 打印先例:packages/dashboard/src/lib/packing-slip.ts(客户端 HTML 打印的完整实现)
  • 附件先例:spree/core/app/models/spree/tax_exemption_certificate.rb(私有附件 + 10 MB 上限)、spree/core/app/models/spree/seller_requirement_submission.rb(防伪造内容类型校验)、spree/core/app/models/concerns/spree/purchase/purchase_order.rb(PO 文档的完整实现)
  • 物流数据来源:spree/core/app/models/spree/freight_summary.rb(装箱清单打印区块消费的数据结构)

【免费下载链接】spreeOpen Source eCommerce Platform for B2B, Marketplace, and Enterprise. REST API, TypeScript SDK, and production-ready Next.js storefront. Self-host it. Own your stack. No vendor lock-in. Zero platform fees.项目地址: https://gitcode.com/GitHub_Trending/sp/spree

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/14 2:09:02

OpenClaw 配 TaoToken:Docker 沙箱里安全调用模型 API

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/14 2:08:26

POD流场重构全流程:从快照法、SVD到Gappy POD修复缺损数据

简介&#xff1a;POD&#xff08;本征正交分解&#xff09;在流场分析中应用广泛&#xff0c;这套Matlab实现代码包面向计算流体力学研究者、研究生及工程技术人员&#xff0c;解决复杂流场数据降维与特征提取问题。资源共3个文件&#xff0c;包含1个.m脚本和2张PNG结果图&…

作者头像 李华
网站建设 2026/9/14 2:08:01

YOLOv8+DeepSORT车辆跟踪计数系统:从原理到部署调优

简介&#xff1a;这是一份基于YOLOv8与DeepSORT的智能车辆跟踪与计数系统完整源码&#xff0c;面向毕业设计、课程设计及期末大作业等场景&#xff0c;适合具备一定Python与深度学习基础的学生参考与二次开发。项目通过YOLOv8完成车辆目标检测&#xff0c;再借助DeepSORT实现跨…

作者头像 李华
网站建设 2026/9/14 2:07:55

Git新手入门:从安装到提交全流程详解

说实话&#xff0c;我见过太多新手倒在了 Git 的第一道坎上。明明官方文档写得清清楚楚&#xff0c;网上的教程也一抓一大把&#xff0c;可真到自己动手的时候&#xff0c;不是装完不知道下一步干嘛&#xff0c;就是git commit完之后发现提交错了&#xff0c;更常见的是git pus…

作者头像 李华