Skip to content

Sales: audit payloads snapshot productId but not the product name, so history re-renders under today's name #747

Description

@mforce

Found by reading #722's mockup artboard after that slice merged. A gap in what #722 shipped, not in the renderer that will consume it.

What

The payloads #722 added (97c866f) carry productId and no name:

details: new
{
    salesOrderItemId = result.Value.Id,
    productId = product.Id,
    quantity = command.Quantity,
    unitPriceMinorUnits = unitPrice.MinorUnits,
    listUnitPriceMinorUnits,
    listPriceBasis = listPriceBasis.ToString(),
    currencyCode = order.TotalAmount.CurrencyCode,
    currencyMinorUnit = order.TotalAmount.CurrencyMinorUnit,
}

Why it matters

#722's mockup (docs/images/discount-mockups/AuditRow.png) draws the Details column as:

Medium Eggs $5.40 → $4.32 (list $5.40)
Large Eggs ×240 at list $0.45

That renders the product name. With only a Guid stored, the renderer (#745) has to join to Products — and it gets today's name. Rename a product and every historical audit row re-renders under a name the seller never saw.

This is the exact hazard #720 exists to prevent, one field over. #720's whole argument for snapshotting the list price was that computing it live against today's catalogue means "raise a product's price next month and every past sale retroactively becomes a discount". The same reasoning applies to the name: an audit row is a record of what happened, and resolving any part of it live re-interprets history.

The house idiom already does this

Sibling handlers snapshot the name rather than the id:

  • CreateProductHandler.cs:65 — details: new { product.Name, product.ProductType, EggGrade = grade.Name }
  • UpdateProductHandler.cs:78 — product.Name, product.DefaultUnit, ...
  • SetProductActiveHandler.cs:24 — details: new { product.Name }

#722's design diffed its block against CreateProductHandler field by field and matched its transaction shape, while missing that it snapshots a name.

Scope

  • Add productName to both sales-line payloads, snapshotted at write time. AddOrderItemHandler already holds product. UpdateOrderItemHandler holds only the order and its items — SalesOrderItem carries ProductId but no name, so that path needs either a product read or a name snapshot on the line itself. Decide which before implementing; a product read in that handler is a new dependency.
  • Keep productId — it is the stable join key; the name is the human-readable snapshot beside it.
  • One assertion per payload plus a mutation row each, matching how Sales: record list/old/new price in the order-line audit payload #722 pinned every other field.

Not in scope

The renderer (#745), which consumes this. Filing them separately because this one changes what is written and that one changes what is read; the renderer is not blocked on it — it can show the id until the name exists.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:apiAPI/endpoint layersliceThin vertical work item

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions