Factorizer/Cluster ERP WikiCluster ERP Türkçe
Home / Business modules

Module

Costing module (costing)

Module page for costing, which rolls a BOM version up into material, labour, nesting, scrap, and overhead costs, a suggested price, and an approval flow.

The costing module in src/cluster_erp/modules/costing/ costs a BOM version. A costing analysis reads the BOM explosion, applies labour and overhead rates, adds an optional nesting cost, and stores a cost breakdown, a total, a suggested price for a target margin, and a low-margin flag. An analysis then goes through submission, approval, or rejection, and an approved analysis linked to a quotation reports its price back. Labour rates and overhead rates are the module's own reference records.

At a glance

Item Value Source
Package src/cluster_erp/modules/costing/ folder
Module class Module src/cluster_erp/modules/costing/module.py:15
name / version costing / 0.1.0 src/cluster_erp/modules/costing/module.py:16-22
API prefix /api/v1/costing (ModuleBase.api_prefix) src/cluster_erp/core/module_base.py:37-38
Sub-modules 3: costing_analyses, labor_rates, overhead_rates folder listing
Tables 5: costing_costing_analyses, costing_cost_breakdown_items, costing_rate_snapshots, costing_labor_rates, costing_overhead_rates src/cluster_erp/modules/costing/costing_analyses/models.py:24, src/cluster_erp/modules/costing/costing_analyses/models.py:193, src/cluster_erp/modules/costing/costing_analyses/models.py:294, src/cluster_erp/modules/costing/labor_rates/models.py:15, src/cluster_erp/modules/costing/overhead_rates/models.py:13
Entities in metadata 3: costing_analysis, labor_rate, overhead_rate src/cluster_erp/modules/costing/module.py:303-307
Routes 30 route decorators in the package grep for @router.
Web app presence Catalogue app costing src/cluster_erp/modules/app_store/catalog.json (entry costing)
Desktop presence Generic widgets only; no folder in src/cluster_erp/desktop/modules/ folder

What the module does

  • Records a costing analysis for one BOM and BOM version, optionally linked to a quotation and a nesting job, with a target margin and a currency (src/cluster_erp/modules/costing/costing_analyses/models.py:23-189).
  • Calculates the cost breakdown in tiers and stores one breakdown row per cost line (src/cluster_erp/modules/costing/costing_analyses/services.py:256-708).
  • Stores a snapshot of every rate used, so a later rate change does not change the explanation of an old result (src/cluster_erp/modules/costing/costing_analyses/services.py:627-662).
  • Lets a user set a final price, submit, approve, reject, return to draft, and supersede (src/cluster_erp/modules/costing/costing_analyses/services.py:710-896).
  • Compares two to five analyses and clones an analysis as a what-if scenario with cost multipliers (src/cluster_erp/modules/costing/costing_analyses/services.py:957-1159).
  • Keeps labour rates per work centre and operation type, and overhead rates with a basis and an apply order (src/cluster_erp/modules/costing/labor_rates/models.py:14-68, src/cluster_erp/modules/costing/overhead_rates/models.py:12-54).

Sub-modules

Sub-module Purpose Main entities Router Source
costing_analyses Analyses, breakdown, snapshots, lifecycle CostingAnalysis, CostBreakdownItem, CostingRateSnapshot /api/v1/costing/analyses src/cluster_erp/modules/costing/costing_analyses/routes.py:31
labor_rates Labour rates LaborRate /api/v1/costing/labor-rates src/cluster_erp/modules/costing/labor_rates/routes.py:22
overhead_rates Overhead rates OverheadRate /api/v1/costing/overhead-rates src/cluster_erp/modules/costing/overhead_rates/routes.py:22

Data model

Entity Table Key fields Notes Source
CostingAnalysis costing_costing_analyses analysis_no, bom_id, bom_version_id, quote_id, nesting_job_id, status, is_scenario, parent_analysis_id, superseded_by_id, seven cost totals, target_margin_pct, actual_margin_pct, markup_pct, suggested_price, final_price, currency, low_margin_flag, calculation_version, approval stamps money Numeric(19, 4), percentages Numeric(5, 2) src/cluster_erp/modules/costing/costing_analyses/models.py:23-189
CostBreakdownItem costing_cost_breakdown_items analysis_id, cost_type, bom_item_id, part_id, work_center_id, operation_type, overhead_category, description, quantity, rate, scrap_factor, base_cost, scrap_cost, total_cost, currency, rate_snapshot_id one row per cost line src/cluster_erp/modules/costing/costing_analyses/models.py:192-290
CostingRateSnapshot costing_rate_snapshots analysis_id, rate_type, source_rate_id, snapshot_data JSONB copy of a rate src/cluster_erp/modules/costing/costing_analyses/models.py:293-307
LaborRate costing_labor_rates work_center_id, operation_type, rate_type, rate_per_hour, rate_per_unit, pricing_unit_id, currency, valid_from, valid_to, is_active one open rate per work centre and operation type src/cluster_erp/modules/costing/labor_rates/models.py:14-68
OverheadRate costing_overhead_rates name, rate_type, basis, rate_value, currency, valid_from, valid_to, is_active, apply_order one open rate per rate_type src/cluster_erp/modules/costing/overhead_rates/models.py:12-54

"Open" means valid_to is empty and the row is live; partial unique indexes enforce it (src/cluster_erp/modules/costing/labor_rates/models.py:16-24, src/cluster_erp/modules/costing/overhead_rates/models.py:14-21). Money is Numeric and Decimal; see money and Decimal.

Screens and metadata

get_metadata declares a sidebar group Costing Management (Turkish Maliyet Yönetimi) with three lists: Costing Analyses, Labor Rates, and Overhead Rates (src/cluster_erp/modules/costing/module.py:253-308). The analysis list shows number, status, total cost, final price, actual margin, currency, low-margin flag, and calculation time, with a status filter (src/cluster_erp/modules/costing/costing_analyses/metadata.py:125-200).

Status and actions

Statuses and allowed transitions are fixed in the service (src/cluster_erp/modules/costing/costing_analyses/services.py:71-80): draft to calculated; calculated to calculated again or to pending_approval; pending_approval to approved or rejected; approved to superseded; rejected back to draft. approved and superseded analyses cannot be changed.

Action Label Condition Endpoint Permission Source
calculate Calculate status is draft POST /analyses/{id}/calculate costing_analysis.calculate src/cluster_erp/modules/costing/costing_analyses/metadata.py:41-51, src/cluster_erp/modules/costing/costing_analyses/routes.py:129-141
recalculate Recalculate status is calculated POST /analyses/{id}/recalculate costing_analysis.calculate src/cluster_erp/modules/costing/costing_analyses/metadata.py:52-65, src/cluster_erp/modules/costing/costing_analyses/routes.py:144-156
submit_approval Submit for Approval status is calculated POST /analyses/{id}/submit-for-approval costing_analysis.submit src/cluster_erp/modules/costing/costing_analyses/metadata.py:66-79, src/cluster_erp/modules/costing/costing_analyses/routes.py:175-187
approve Approve status is pending_approval POST /analyses/{id}/approve costing_analysis.approve src/cluster_erp/modules/costing/costing_analyses/metadata.py:80-93, src/cluster_erp/modules/costing/costing_analyses/routes.py:190-203
reject Reject status is pending_approval POST /analyses/{id}/reject costing_analysis.reject src/cluster_erp/modules/costing/costing_analyses/metadata.py:94-107, src/cluster_erp/modules/costing/costing_analyses/routes.py:206-219
return_to_draft Return to Draft status is rejected POST /analyses/{id}/return-to-draft costing_analysis.update src/cluster_erp/modules/costing/costing_analyses/metadata.py:108-121, src/cluster_erp/modules/costing/costing_analyses/routes.py:222-234

Two lifecycle routes have no ActionMeta: POST /analyses/{id}/set-final-price and POST /analyses/{id}/supersede (src/cluster_erp/modules/costing/costing_analyses/routes.py:159-172, src/cluster_erp/modules/costing/costing_analyses/routes.py:237-250).

Business rules

The costing roll-up

The diagram shows the order of the tiers in calculate.

flowchart TD
 E["GetBomExplosion (bom)"] --> M["material tier: effective_qty x material_cost_per_unit, plus scrap"]
 E --> L["labour tier: minutes x hourly rate, or qty x unit rate, plus scrap"]
 N["GetNestingJobById (nesting)"] --> NT["nesting tier: net_material_cost"]
 M --> S["subtotal"]
 L --> S
 NT --> S
 S --> O["overhead tier, in apply_order"]
 O --> T["total_cost = material + labour + overhead + scrap + nesting"]
 T --> P["suggested_price = total / (1 - target margin)"]
 P --> F["low_margin_flag if margin below 15"]

In prose, step by step (src/cluster_erp/modules/costing/costing_analyses/services.py:256-708):

  1. The analysis must be allowed to move to calculated (src/cluster_erp/modules/costing/costing_analyses/services.py:261-262).
  2. The module asks the bom module for the explosion of the BOM version and fails if there is none or it has no items (src/cluster_erp/modules/costing/costing_analyses/services.py:264-290).
  3. It loads the active labour and overhead rates and refuses any rate whose currency differs from the analysis currency, with COSTING_CURRENCY_MISMATCH (src/cluster_erp/modules/costing/costing_analyses/services.py:292-313).
  4. It removes the previous breakdown rows and snapshots (src/cluster_erp/modules/costing/costing_analyses/services.py:315-329).
  5. Material tier: for each item with a material cost, base cost is effective quantity times material cost per unit, and scrap cost is base cost times the scrap factor (src/cluster_erp/modules/costing/costing_analyses/services.py:346-404).
  6. Labour tier: for each item with labour minutes, the rate is looked up by work centre and operation type, then work centre only, then operation type only, then the general operation type, then a rate with neither. A unit-price rate multiplies the quantity; an hourly rate multiplies the hours. Without a rate, the item's own labour cost per unit is used. Labour scrap is the labour cost times the scrap factor (src/cluster_erp/modules/costing/costing_analyses/services.py:406-504).
  7. Nesting tier: when the analysis names a nesting job, its net_material_cost becomes one breakdown line (src/cluster_erp/modules/costing/costing_analyses/services.py:506-534).
  8. Overhead tier: each active overhead rate, in apply_order, takes a percentage of labour, material, the total so far, or the running subtotal, or a fixed amount per unit of quantity; each result is added to the running subtotal (src/cluster_erp/modules/costing/costing_analyses/services.py:536-587).
  9. The total is material plus labour plus overhead plus scrap plus nesting (src/cluster_erp/modules/costing/costing_analyses/services.py:589-600).
  10. With a target margin, the suggested price is the total divided by one minus the margin, and the markup is derived from it (src/cluster_erp/modules/costing/costing_analyses/services.py:602-619).
  11. The low-margin flag is set when the margin is below 15 percent (src/cluster_erp/modules/costing/costing_analyses/services.py:60, src/cluster_erp/modules/costing/costing_analyses/services.py:621-625).
  12. Rate snapshots are stored, the status becomes calculated, calculation_version goes up by one, and CostingCalculated is emitted, plus LowMarginAlert when flagged (src/cluster_erp/modules/costing/costing_analyses/services.py:627-706).

Each money result is rounded with ROUND_HALF_EVEN to four places (src/cluster_erp/modules/costing/costing_analyses/services.py:359-372, src/cluster_erp/modules/costing/costing_analyses/services.py:606-609).

Other rules

  • The final price can be set only on a calculated analysis; the actual margin becomes (price minus cost) divided by price, and the markup (price minus cost) divided by cost (src/cluster_erp/modules/costing/costing_analyses/services.py:710-739).
  • Submission needs a final or a suggested price; without a final price, the suggested price is copied into it (src/cluster_erp/modules/costing/costing_analyses/services.py:742-759).
  • Approval of an analysis with a quotation and a final price also emits QuoteCostingCompleted (src/cluster_erp/modules/costing/costing_analyses/services.py:768-808).
  • Supersede needs a new analysis of the same BOM (src/cluster_erp/modules/costing/costing_analyses/services.py:857-880).
  • Only a draft analysis can be deleted one by one (src/cluster_erp/modules/costing/costing_analyses/services.py:233-244).
  • A scenario can be cloned from a calculated or approved analysis; material and labour lines are scaled by the given multipliers (src/cluster_erp/modules/costing/costing_analyses/services.py:1031-1104).
  • A labour or overhead rate's valid_to may not be before its valid_from (src/cluster_erp/modules/costing/labor_rates/services.py:159-163, src/cluster_erp/modules/costing/overhead_rates/services.py:107-111). A labour rate type is hourly or unit_price (src/cluster_erp/modules/costing/labor_rates/schemas.py:9).
  • analysis_no comes from the yearly sequence costing_analysis (src/cluster_erp/modules/costing/costing_analyses/services.py:183).

Events and queries

The module consumes 7 contracts and produces 10, counted as on the parties module page.

Direction Contract class Handler or emitter Source
emits CostingCreated create src/cluster_erp/modules/costing/costing_analyses/services.py:200-209
emits CostingCalculated, LowMarginAlert calculate src/cluster_erp/modules/costing/costing_analyses/services.py:660-692
emits CostingApproved, QuoteCostingCompleted, CostingRejected, CostingSuperseded lifecycle methods src/cluster_erp/modules/costing/costing_analyses/services.py:786-808, src/cluster_erp/modules/costing/costing_analyses/services.py:828-836, src/cluster_erp/modules/costing/costing_analyses/services.py:886-894
emits CostingRateUpdated rate updates src/cluster_erp/modules/costing/labor_rates/services.py:218, src/cluster_erp/modules/costing/overhead_rates/services.py:157
queries GetBomExplosion, GetNestingJobById calculate src/cluster_erp/modules/costing/costing_analyses/services.py:262-267, src/cluster_erp/modules/costing/costing_analyses/services.py:508-513
answers query GetCostingById, GetCostingByBomId handlers src/cluster_erp/modules/costing/module.py:66-70, src/cluster_erp/modules/costing/module.py:89-139
answers query GetActiveLaborRate, GetActiveOverheadRates, GetCostBreakdown handlers src/cluster_erp/modules/costing/module.py:71-82, src/cluster_erp/modules/costing/module.py:141-223
subscribes BomActivated, NestingCostCalculated _on_bom_activated, _on_nesting_cost_calculated src/cluster_erp/modules/costing/module.py:84-85, src/cluster_erp/modules/costing/module.py:227-231

The two subscribed handlers have an empty body at this commit (src/cluster_erp/modules/costing/module.py:227-231). The full list is on the contracts reference.

Permissions

Permission key Used by Source
costing_analysis.read, .create, .update, .delete, .bulk_delete analysis routes, breakdown, compare, snapshots, scenario src/cluster_erp/modules/costing/costing_analyses/routes.py:43-295
costing_analysis.calculate, .submit, .approve, .reject, .supersede lifecycle routes src/cluster_erp/modules/costing/costing_analyses/routes.py:135-244
labor_rate.read, .create, .update, .delete, .bulk_delete labour rate routes src/cluster_erp/modules/costing/labor_rates/routes.py:34-113
overhead_rate.read, .create, .update, .delete, .bulk_delete overhead rate routes src/cluster_erp/modules/costing/overhead_rates/routes.py:32-109

Privacy, retention, and history

The module stores user ids of submitters and approvers and no personal fields of its own; it has no privacy_handlers.py. Analyses and rates are soft-deleted. Changes are captured by change history.

Analytics facts

This module has no analytics_facts.py.

Settings and reference data

Labour and overhead rates are the module's reference data; there is no settings section and no seed (src/cluster_erp/modules/costing/module.py:28-30). The low-margin threshold is the module constant _LOW_MARGIN_THRESHOLD (src/cluster_erp/modules/costing/costing_analyses/services.py:60).

Tests

Found by a grep for modules.costing in tests/ and by file name in web-client/.

Kind File Covers
e2e web-client/_e2e_costing_app.mjs the costing app's three metadata lists and its store card
e2e web-client/_e2e_quote_line_bom.mjs, web-client/_e2e_order_shipment_chain.mjs chains that call costing routes

No pytest file imports the module.

Discrepancies

Terms introduced

Term Code identifier Definition Turkish term Source
costing analysis CostingAnalysis A costed BOM version with totals, price, margin, and a lifecycle. Maliyet analizi src/cluster_erp/modules/costing/costing_analyses/models.py:23
cost breakdown item CostBreakdownItem One material, labour, nesting, or overhead line of an analysis. Maliyet kırılım kalemi src/cluster_erp/modules/costing/costing_analyses/models.py:192
rate snapshot CostingRateSnapshot A frozen copy of a rate used by a calculation. Oran anlık görüntüsü src/cluster_erp/modules/costing/costing_analyses/models.py:293
labour rate LaborRate An hourly or unit price for a work centre and operation type. İşçilik oranı src/cluster_erp/modules/costing/labor_rates/models.py:14
overhead rate OverheadRate A percentage or per-unit overhead with a basis and an apply order. Genel gider oranı src/cluster_erp/modules/costing/overhead_rates/models.py:12
scenario is_scenario A cloned analysis with cost multipliers for what-if comparison. Senaryo src/cluster_erp/modules/costing/costing_analyses/models.py:61

Verified against commit 92f8051 on 2026-11-13.

Verified against commit 92f8051 · 2026-11-13