Repository navigation
Reports: discount totals per salesperson and per customer, in report and CSV export #725
Description
Activity
- addedsliceThin vertical work itemThin vertical work itemepic-719Discount visibility and control on sales (epic #719)Discount visibility and control on sales (epic #719)area:apiAPI/endpoint layerAPI/endpoint layerarea:frontendReact/Vite web clientReact/Vite web client
on Sep 8, 2026 Mockup
Amendment: the per-salesperson breakdown in this issue's scope cannot be built
SalesOrdershas 11 columns and none of them records a creating user. Verified at both the C# and SQL layers:Id, ReferenceNumber, CustomerId, Status, OrderDate, TotalMinorUnits, TotalCurrencyCode, TotalCurrencyMinorUnit, VoidReason, Version, AccountId.grep -rn "CreatedBy" src/returns only response DTOs andEntityProvenance, every one audit-derived, none a stored column.Three options, none free:
- Add a
CreatedByUserIdcolumn — its own migration and schema-docs cycle, and null for every existing order with no backfill except the audit trail. - Join
AuditEventsonAction = 'SalesOrder.Create'—AuditEventConfigurationdeclares two indexes,(AccountId, OccurredAtUtc)and(AccountId, EntityId), neither supporting a filter onAction. Worse, orders written before Show "created by / when" and "last changed by / when" inline on record pages #494 have no creation event and never get one, so the column is permanently blank for historical rows with no way to distinguish unknown from nobody. - Cut the per-salesperson half from this slice.
My recommendation is 3 now and 1 as a separate slice, because option 2 produces a report that is silently wrong about exactly the history the owner wants to audit.
The per-customer half is genuinely easy
SalesOrder.CustomerIdexists,ReportQueries.csis plain LINQ-to-EF (265 lines, no raw SQL), andExpenseCategoryTotal/GetExpensesAsyncis a working template for group-by-an-id-and-look-up-names.Second correction: "the CSV export" is two different things
The sales report has no CSV download.
ReportEndpoints.csmaps fourMapGets that allreturn Results.Ok(<record>). The repo's CSV machinery is a separate feature, the 20-dataset account backup, whosesales-orders/sales-order-itemsdatasets are hand-written column lists inExportQueries.cs. So this issue's "CSV export" is either two lines in that dataset, or new report-download machinery with no precedent to copy. Roughly a 10× difference, and the issue does not say which.Also worth pricing:
MaxRangeDays = 366plus the per-account report concurrency cap (#311/#545) makes a new grouped query a real cost, not a freeGroupBy.- Add a
- addedsize:LSeveral days; wide blast radius or unresolved scopeSeveral days; wide blast radius or unresolved scopeneeds-scopeScope contradicts the code; decide before schedulingScope contradicts the code; decide before scheduling
on Sep 8, 2026 Closing as won't-do — owner decision, 2026-09-13
Closed during the issue cleanup as part of winding up epic #719. Not deferred — decided against.
Why this is defensible now, when it would not have been on 2026-09-08
This issue's body argues it is "the slice that matters most", because the badges in #723/#724 are
lagging indicators that only speak when somebody opens a screen, and the discounts that opened #719
went unnoticed for months precisely because nobody did.That argument was correct when it was written and has since been overtaken by two slices that
moved the control from detection to prevention:- Sales: require a discount reason when confirming a below-list order #721 requires a discount reason at confirm. A below-list order cannot be recorded silently.
- Sales: per-farm discount ceiling, with Owner/Manager approval above it #727 refuses an over-ceiling confirm from a Sales user outright, per-farm, measured per
line, stored as basis points. Owner and Manager may exceed; a Sales user cannot.
So the failure mode this slice was the answer to — discounts happening quietly for months — is now
blocked at the point of entry rather than discovered in a weekly report. A report is still the better
instrument for patterns, but it is no longer the only thing standing between the farm and an
unnoticed discount.And half of it was unbuildable as filed
Per the amendment above, verified at both the C# and SQL layers:
SalesOrdershas 11 columns and
none of them records a creating user. The per-salesperson breakdown — the half that answers "who
is giving these away" — cannot be built without first adding an actor column to the order, writing
its migration, and backfilling or declaring the pre-existing rows unattributable. That is a separate
slice with its own decisions, not a reporting change.What is consequently NOT available, stated plainly
- No discount totals in the sales report or the CSV export.
- No per-customer or per-salesperson discount aggregation anywhere (Customers: discount history and running total on the customer record #726 closed alongside this).
- Discount analysis over a period remains a manual read of the Orders list, which carries the
Sales: discount badge with amount and percent in history and order detail #724 badge and total per order.
What would reopen this
- A farm asking "how much did we discount last month" and the Orders list not answering it.
- The order gaining a creating-user column for some other reason, which removes the unbuildable half.
- Any decision to loosen Sales: per-farm discount ceiling, with Owner/Manager approval above it #727's ceiling or Sales: require a discount reason when confirming a below-list order #721's reason requirement — those are what make this
closable, so weakening either brings the detection gap back.

Important
Amended 2026-09-08 after reading the source. Part of the scope below is not buildable as written. The body is left as filed for history; see this comment for what actually holds.
Part of #719. Blocked by #720 (needs the list-price snapshot).
What
Discount totals in the sales report and the CSV export, broken down per
salesperson and per customer over a date range.
Why this is the slice that matters most
The badges in #723 and #724 are lagging indicators — they only tell you something
if a person opens the screen. The discounts that opened #719 went unnoticed for
months precisely because nobody was opening those screens one order at a time.
A number in the weekly report is what would have surfaced it. This slice is the
detection mechanism; the badges are the explanation once you go looking.
Scope
SalesSummary(src/Cluckwork.Application/Features/Reports/IReportQueries.cs:43)with discount totals for the period.
ExportEndpoints.cs/CsvExport.cs).percent.
how much.
Rules
the existing
SalesSummarycontract — do not invent a second date conventionin the same record.
and counted separately as unknown, not folded in as zero. A silent zero
would make the first months after launch look clean.
rest of
SalesSummary.Acceptance
percent and reason.
Repo rules
find its guards by grepping the registry's readers, not by recall
(
grep -rn "exports.Datasets\|GetDataset" tests/).