ZuloOne
English
Sign in
Menu

ABC/XYZ

Classification of items and customers in Sales. It is not a localisation and not its own screen: office sets a scheme, recalculates, and reads a journal. Replenishment is a second matrix on top of the class that is already there. It does not create a purchase order by itself.

What it is#

After revenue, stock and inventory value already move, the office needs a configurable class, not three letters in code. A profile is the scheme; bands are the class ladder; an information register is the dated snapshot.

Two seed profiles:

  • items by revenue over 12 months, Pareto 80/95, XYZ by coefficient of variation;
  • customers on the same scheme.

Further measures, letters and extra classes are rows of data, not a release.

Subject in the journal is the Guid of an item or a customer, whichever the profile names. Two profiles do not mix objects. A non-empty item group on an item profile narrows the set: shares are computed inside the group. Empty group — whole catalogue. On a customer profile the group is ignored.

Measure Item Customer
Revenue sum of Amount by Item same by Customer
StockQty Stock.Qty balance recalc error
InventoryValue Value balance recalc error

ABC uses cumulative share: sort by descending score, assign the class from the share before including the row (the first large item that already holds 80% of revenue is A). Zero scores stay out of Pareto.

XYZ cuts the window into BucketCount buckets; Cv = σ/μ (μ = 0 → last XYZ band). A stock snapshot does not compute XYZ — set XyzMethod = None on the profile.

Overlapping or gapped bands make recalc fail; the journal is unchanged.

How to set up#

Sales → Settings

  • ABC profiles — who to count (item or customer), which measure, how many months, which method. Empty Item group counts the whole catalogue.
  • ABC class bands — boundaries (A/B/C, X/Y/Z, or your own codes) for the chosen profile.

Sales → Settings → ABC replenishment cells — one row per class pair of one item profile.

  1. Profile — the same one as in the classification journal.
  2. ABC and XYZ — the same letters as the bands. Empty XYZ matches a profile without XYZ.
  3. Mode:
    • Not set — the cell does nothing;
    • Keep stock — coverage in days;
    • To order — do not keep stock; quantity to order will be 0.

On the desktop: Configure → Add widget → ABC replenishment. It lists items whose quantity to order is greater than zero, largest first, at most twelve.

The night job RecalcAbcClassifications (0 2 * * * UTC) skips IsDisabled profiles. The Recalculate button refuses a disabled profile.

How to use#

Recalculate the class#

  1. Open a profile (seed: “Items by revenue” or “Customers by revenue”).
  2. Check the bands: ABC usually 0–80 / 80–95 / 95+; XYZ by CV 0–0.5 / 0.5–1 / 1+.
  3. Command Recalculate on the profile card.
  4. Open Sales → Totals → ABC classification and filter by profile.

Recalc does not run on shipment. The button counts one profile; the nightly job counts every enabled one.

In the journal, ABC manual / XYZ manual keep that class on the next recalc (score and share still refresh). Use this for “this item is always A”.

Agent phone#

After sync, the class is visible without the journal: item list, customer list, item card, customer card, order picker. One enabled profile writes A/X. Two profiles prefix the name so the letters do not mix. The phone never computes Pareto — if the office has not recalculated, there is no caption.

Replenish#

  1. Command Calculate replenishment on the item profile.
  2. Sales → Totals → ABC replenishment proposal — demand, stock, qty to order.
  3. Command Create purchase orders on the profile. Drafts are built only for rows with qty to order > 0 that already have a received purchase. Supplier, receiving cell, unit and price copy from the latest such line. Repeat while that draft is not yet Ordered does not add a second document. The order shows ABC profile.

Example. Window 12 months, as-of 1 September 2026 (365 days). The item sold 365 base units, stock is 10, cell A/X “keep” 14 days. Average demand 1 a day, target 14, qty to order 4. The same cell “to order” yields 0.

The widget does not create an order. Calculate replenishment does not move stock. Customer return does not reduce demand: only issued and shipped invoices count.

How it is built#

This is the Sales business layer, not Core and not a country pack. There is no if (abc) in Core. Letters A/B/C live in the seed Sales/DataPackages/abc-profiles.json, not in C#.

Name Kind
AbcProfile scheme dictionary. ItemGroup empty — whole catalogue
AbcClassBand class band (unique Profile+Axis+ClassCode on save)
AbcClassification information register, dimensions Profile + Subject
IAbcClassifier RecalcAsync / RecalcWithScoresAsync / RecalcAllEnabledAsync
RecalcAbc dictionary command on the profile
RecalcAbcClassifications job 0 2 * * *, same RecalcAsync for live profiles
AbcPolicy / AbcSuggestion / AbcPolicyCell replenishment

The service is named AbcClassifier, not AbcClassification: otherwise the service class would collide with the POCO IR_AbcClassification. Same pattern for AbcPolicy vs IR_AbcSuggestion.

RecalcAsync reads revenue via movement query + analytic expand, stock and inventory value via balances, writes SetAsync("AbcClassification", asOf, …). Manual flags keep the class. A narrowed item group deletes out-of-group rows for that as-of so the journal does not keep the old catalogue.

Public DTOs cannot sit on the service contract — I<Name> is assembled before entities. Tests feed IReadOnlyDictionary<Guid, decimal>. Tests: AbcClassificationTest, AbcPolicyTest.

Phone projection CachedAbc is assembled in MobileSyncService on the SalesAgent pull from SliceLast("AbcClassification"). No extra method on the classifier.

Demand is SalesRealization lines in Issued and Shipped, BaseQuantity (or Quantity if that is 0). Negative Stock movements are not demand: a cell transfer would inflate it. AbcStockMode zero = unspecified and writes nothing.

Do not#

  • Put ABC in Localization.* or write the class onto Item.
  • Price or move Stock from this service — it only reads registers and writes AbcSuggestion.
  • Fill ExpectedReceipt on the draft: an empty date does not promise ATP.
  • Invent a second classifier in the job: the button and the cron call the same RecalcAsync.

What is not here yet#

There is no separate chart. List and card are ordinary platform forms. Replenishment does not subtract a sales return. The widget does not place an order.

Was this page helpful?