# A product's discount in two parts: the seller's and Kapitano's

ADR 0008 (2026-11-04) made the seller pay for every discount on their products. From 2026-11-10 Kapitano
may take part too: a product's discount is made of **two parts that add up**, the seller's and Kapitano's,
each with its own dates.

    seller price 100, markup 25% → selling 125, purchase 100
    the seller's part 10% of their price  → 10 off, the seller gets 90
    Kapitano's part 8% of the customer's 125 → 10 off, out of our margin
    the customer pays 105 (16% off), our margin 25 - 10 = 15

The three cases are the same model: seller only (Kapitano's part 0), Kapitano only (the seller's part 0),
both.

Decided with the user on 2026-11-10 (grilling rounds, every recommendation accepted):

- **Two parts that add up, not one discount split by a share.** The seller sets only their own part; they
  never set or change Kapitano's. Staff set both (product form, category-wise discount, offers section).
- **Units.** The seller's part is a percentage of the seller's own price or an amount in their currency
  (RAW), wherever it is typed, staff forms included (before, a percentage staff typed was of the customer's
  price). Kapitano's part is a percentage of the customer's price or an amount in the product's currency,
  shown in SYP on the form. The customer sees the total as one percentage.
- **Each part has its own dates**, so a weekend Kapitano campaign can run on a month-long seller discount;
  while both run the customer gets the sum, otherwise the one running. The seller moves and stops only
  their own part.
- **One part per side per product.** A new part of a side replaces the old one.
- **Our margin may go below zero** because of Kapitano's part: staff are warned (never refused) and the
  line's commission is booked negative, so the campaign's cost shows. Any other line below cost still
  books 0.
- **The seller never pays below zero**: their cut on a line is at most their purchase price and at most
  what the customer was let off; anything beyond is recorded as Kapitano's.
- **Refused**: a total that reaches the customer's price at any moment both parts run; a part on a product
  with a fake discount; a seller part on a product with no seller (our own and partner products have only
  Kapitano's part).
- **The offers section** gives each offer both parts (a new offer is all on the seller); the offer's parts
  are the product's parts, read only for the seller, removed with the offer.
- **What the seller sees of Kapitano's part**: its percentage of the customer's price, its dates and,
  while both run, the customer's whole percentage - never money (it would tell our price and commission).
- **The seller is notified** only when staff set or change the seller's part (value or dates).
- **Staff see** what each side bore per order line and in Seller Profitability ("Discounts borne by
  Kapitano").
- **Carry-over**: every discount running or upcoming at the deploy became the seller's part (on our own
  and partner products, Kapitano's) with the same dates; no customer price moved. Lines placed before keep
  what they froze; coupons, fake discounts and wholesale tiers stay outside.
- **Old seller-app versions** keep working: the existing `discount` fields read and write the seller's
  part; `kapitano_discount` is a new key.

## How it works

The parts live in `product_discount_parts`; the product's own discount columns keep holding the effect -
the total of the parts running now, with the dates of that stretch - so no price helper, filter or cache
changed. `DiscountParts::sync()` writes them after every write of a part, after every product save (a
price change keeps the seller's percentage of their own price) and, every minute, for the products where a
part started or ended (`discount-parts:roll`): a price change can land up to a minute late.

## Considered Options

- **One discount with a share split** ("20%, the seller pays 60%"): rejected, the seller would be setting
  a discount whose split we decide afterwards.
- **One shared window for both parts**: no scheduled job, but the seller could not keep their own dates
  under a Kapitano campaign. Rejected by the user once the roll was proposed.
- **Two windows read directly by every price helper**: rewrites the many copies of the price logic and the
  price filter SQL. Rejected as too risky.

## Consequences

- A product's discount columns must never be written by hand once it has parts: `DiscountParts` is the
  only writer (the forms, the category-wise discount, the seller's prices page and the offers section go
  through it).
- `discount-parts:roll` must run every minute (the scheduler).
- `order_details.kapitano_discount` (BASE) records Kapitano's part of each line; `seller_discount` the
  seller's, as before.
