GuideMarch 13, 2026

By-Products & Co-Products in Odoo 19:
Capture Every Output from Your Production Line

INTRODUCTION

Your Production Line Produces More Than One Thing. Your ERP Should Know That.

A sawmill cuts lumber. The main product is planks. But the same process also produces sawdust, bark chips, and wood shavings. A dairy processes raw milk into cheese (the main product), but also yields whey, cream, and buttermilk. A petroleum refinery cracks crude oil into gasoline, diesel, kerosene, and asphalt — none of them are "waste," and all of them have market value.

If your ERP only tracks the primary finished good, you're flying blind on the secondary outputs. That sawdust has value — it sells to particleboard manufacturers, garden centers, and biomass energy plants. The whey goes to protein supplement companies. Untracked by-products are uncosted inventory sitting on your floor, invisible to accounting, and missing from your margin calculations.

Odoo 19's Manufacturing module (MRP) has first-class support for by-products and co-products — secondary outputs that are automatically created when a manufacturing order completes. This guide walks you through configuring them on Bills of Materials, allocating costs correctly, routing co-products to different warehouses, valuing by-products for accounting, managing waste streams, and building reports that give you full visibility into every gram of output from your production line.

01

Configuring By-Products on a Bill of Materials in Odoo 19

A by-product in Odoo is any product that is produced as a side effect of manufacturing the main product. It is defined on the BoM (Bill of Materials) and is automatically moved into stock when the manufacturing order is marked as done. Odoo 19 requires you to enable the feature first.

Step 1: Enable the By-Products Feature

Navigate to Manufacturing → Configuration → Settings. Under the Operations section, enable By-Products. Click Save. This adds a "By-Products" tab on every BoM form.

Step 2: Add By-Products to Your BoM

Open a Bill of Materials (Manufacturing → Bills of Materials). Click the By-Products tab. Add a line for each secondary output. For each by-product, you specify the product, quantity, and unit of measure. You can also assign a specific operation if you use work centers — this tells Odoo at which stage of the routing the by-product is produced.

XML — Adding by-products to a BoM via data file
<!-- By-product: Sawdust from Plank manufacturing -->
<record id="bom_plank_oak" model="mrp.bom">
    <field name="product_tmpl_id" ref="product_oak_plank"/>
    <field name="product_qty">1</field>
    <field name="product_uom_id" ref="uom.product_uom_unit"/>
</record>

<!-- BoM components (inputs) -->
<record id="bom_line_oak_log" model="mrp.bom.line">
    <field name="bom_id" ref="bom_plank_oak"/>
    <field name="product_id" ref="product_oak_log"/>
    <field name="product_qty">1</field>
    <field name="product_uom_id" ref="uom.product_uom_unit"/>
</record>

<!-- By-product lines (outputs) -->
<record id="byproduct_sawdust" model="mrp.bom.byproduct">
    <field name="bom_id" ref="bom_plank_oak"/>
    <field name="product_id" ref="product_sawdust"/>
    <field name="product_qty">5</field>
    <field name="product_uom_id" ref="uom.product_uom_kgm"/>
    <field name="cost_share">3.0</field>
</record>

<record id="byproduct_bark_chips" model="mrp.bom.byproduct">
    <field name="bom_id" ref="bom_plank_oak"/>
    <field name="product_id" ref="product_bark_chips"/>
    <field name="product_qty">2</field>
    <field name="product_uom_id" ref="uom.product_uom_kgm"/>
    <field name="cost_share">2.0</field>
</record>

When the manufacturing order for 1 Oak Plank completes, Odoo automatically creates stock moves for 5 kg of Sawdust and 2 kg of Bark Chips into your finished goods location. No manual stock adjustments, no forgotten inventory.

Step 3: Programmatic By-Product Creation

If you need to add by-products dynamically — for example, a custom module that calculates by-product quantities based on input quality — here is the Python approach:

Python — Creating by-product lines programmatically
from odoo import models, fields, api


class MrpBom(models.Model):
    _inherit = 'mrp.bom'

    def action_add_sawmill_byproducts(self):
        """Add standard sawmill by-products to a BoM.

        Called from a button on the BoM form view or
        via automated action during BoM creation.
        """
        ByProduct = self.env['mrp.bom.byproduct']
        sawdust = self.env.ref('my_module.product_sawdust')
        bark = self.env.ref('my_module.product_bark_chips')
        kg = self.env.ref('uom.product_uom_kgm')

        for bom in self:
            # Skip if by-products already configured
            if bom.byproduct_ids:
                continue

            ByProduct.create([
                {
                    'bom_id': bom.id,
                    'product_id': sawdust.id,
                    'product_qty': 5.0,
                    'product_uom_id': kg.id,
                    'cost_share': 3.0,  # 3% of total cost
                },
                {
                    'bom_id': bom.id,
                    'product_id': bark.id,
                    'product_qty': 2.0,
                    'product_uom_id': kg.id,
                    'cost_share': 2.0,  # 2% of total cost
                },
            ])
By-Product vs. Co-Product: What Is the Difference?

In Odoo 19, the term "by-product" covers both concepts technically. The distinction is economic: a by-product is a secondary output with minor value relative to the main product (sawdust from lumber). A co-product is a secondary output with significant, sometimes equal, value (diesel from crude oil refining). In Odoo, you model both using the same mrp.bom.byproduct model — the difference is in the cost_share percentage you assign.

02

Cost Allocation for By-Products and Co-Products in Odoo 19 Manufacturing

When a manufacturing order consumes $100 of raw materials and labor, that $100 needs to be distributed across all outputs — not just the main product. Without proper cost allocation, the main product carries 100% of the cost while by-products enter inventory at $0. This distorts your margins on both sides: the main product looks less profitable than it is, and the by-products show infinite margin when sold.

The cost_share Field

Each by-product line on the BoM has a cost_share field expressed as a percentage. The main product's cost share is calculated as 100% minus the sum of all by-product cost shares. For example:

OutputTypecost_shareCost (if total = $100)
Oak PlankMain Product95% (implicit)$95.00
SawdustBy-Product3%$3.00
Bark ChipsBy-Product2%$2.00

This cost split happens at the accounting level. When the manufacturing order is completed, Odoo creates journal entries that debit the finished goods accounts for each product according to its cost share, and credits the WIP (Work in Progress) account for the total consumed amount.

Co-Product Scenario: Equal-Value Outputs

For a petroleum refinery or a dairy processor, the outputs are closer in value. Here is how you would configure a cheese-making BoM where whey protein is a valuable co-product:

Python — Co-product cost allocation for dairy processing
# BoM: Raw Milk → Cheddar Cheese + Whey Protein + Buttermilk
bom = env['mrp.bom'].create({
    'product_tmpl_id': cheddar_cheese.product_tmpl_id.id,
    'product_qty': 10,  # 10 kg of cheese
    'product_uom_id': kg.id,
})

# Components (inputs)
env['mrp.bom.line'].create({
    'bom_id': bom.id,
    'product_id': raw_milk.id,
    'product_qty': 100,  # 100 liters of raw milk
    'product_uom_id': liter.id,
})

# Co-products (valuable secondary outputs)
env['mrp.bom.byproduct'].create([
    {
        'bom_id': bom.id,
        'product_id': whey_protein.id,
        'product_qty': 6,   # 6 kg whey protein
        'product_uom_id': kg.id,
        'cost_share': 25.0,  # 25% — whey is valuable
    },
    {
        'bom_id': bom.id,
        'product_id': buttermilk.id,
        'product_qty': 80,   # 80 liters buttermilk
        'product_uom_id': liter.id,
        'cost_share': 10.0,  # 10% — lower value
    },
])
# Main product (Cheddar) gets 100% - 25% - 10% = 65%
How to Determine the Right cost_share Percentages

Use the net realizable value (NRV) method: calculate the market price of each output, subtract any post-split processing costs, and allocate the joint cost proportionally to each output's NRV. For example, if cheese sells for $8/kg (NRV $80), whey sells for $5/kg (NRV $30), and buttermilk sells for $0.50/L (NRV $40), the total NRV is $150. Cheese gets 53%, whey gets 20%, buttermilk gets 27%. Adjust quarterly as market prices shift.

03

Routing Co-Products to Different Locations and Warehouses in Odoo 19

By default, all by-products move to the same finished goods location as the main product. But in practice, by-products often need to go somewhere else: sawdust goes to a bulk storage silo, whey goes to a cold storage room, or waste material routes directly to a scrap location. Odoo 19 lets you override the destination per by-product line.

Setting a Custom Destination on By-Product Lines

On the BoM form, each by-product line has an optional Operation field. When the manufacturing order uses routing (work centers), by-products are produced at the operation's output location. For more control, you can override the stock move destination in a custom module:

Python — Override by-product destination location
from odoo import models, fields, api


class MrpBomByproduct(models.Model):
    _inherit = 'mrp.bom.byproduct'

    destination_location_id = fields.Many2one(
        'stock.location',
        string='Destination Location',
        help='Override the default finished goods location '
             'for this by-product. Leave empty to use the '
             'manufacturing order default.',
    )


class MrpProduction(models.Model):
    _inherit = 'mrp.production'

    def _get_moves_finished_values(self):
        """Override to apply custom destinations to by-product moves."""
        moves = super()._get_moves_finished_values()
        for move_vals in moves:
            # Find matching by-product line
            byproduct_id = move_vals.get('byproduct_id')
            if byproduct_id:
                bp_line = self.env['mrp.bom.byproduct'].browse(
                    byproduct_id
                )
                if bp_line.destination_location_id:
                    move_vals['location_dest_id'] = (
                        bp_line.destination_location_id.id
                    )
        return moves
XML — Form view to expose the destination field
<record id="view_mrp_bom_byproduct_form_inherit" model="ir.ui.view">
    <field name="name">mrp.bom.byproduct.form.inherit</field>
    <field name="model">mrp.bom.byproduct</field>
    <field name="inherit_id" ref="mrp.mrp_bom_byproduct_form_view"/>
    <field name="arch" type="xml">
        <xpath expr="//field[@name='operation_id']" position="after">
            <field name="destination_location_id"
                   options="{{'no_create': True}}"
                   domain="[('usage', '=', 'internal')]"/>
        </xpath>
    </field>
</record>

With this customization, your production planners can specify that sawdust routes to WH/Stock/Bulk Silo, bark chips route to WH/Stock/Outdoor Yard, and the main product routes to WH/Stock/Finished Goods — all from a single manufacturing order.

Use Operations for Standard Routing Instead of Custom Code

If your by-product is always produced at a specific work center (e.g., sawdust is always produced at the "Cutting" operation), assign the operation on the by-product line instead of writing custom code. The by-product will move to that operation's output location. The custom destination_location_id approach above is only needed when you want a destination that is different from any work center's output location.

04

Inventory Valuation of By-Products: FIFO, AVCO, and Standard Cost in Odoo 19

By-products enter inventory with a cost determined by the cost_share percentage. But how that cost is subsequently tracked depends on the product's costing method. Odoo 19 supports three costing methods, and each interacts differently with by-product costs.

Costing MethodBy-Product Entry CostSubsequent ValuationBest For
Standard CostFixed price from product form, variance posted to adjustment accountAlways at standard priceStable by-products with predictable value (sawdust, scrap metal)
AVCOcost_share % of MO cost, averaged with existing stockRunning weighted averageBy-products received from multiple MOs at varying costs
FIFOcost_share % of MO cost, tracked per layerSold at oldest layer costPerishable by-products (whey, buttermilk) where batch cost matters

Journal Entries on MO Completion

When a manufacturing order with by-products is marked as done, Odoo generates the following accounting entries (assuming automated valuation):

Accounting — Journal entries for MO with by-products
# MO: 1 Oak Log ($100) → 1 Oak Plank + 5kg Sawdust + 2kg Bark
# cost_share: Plank=95%, Sawdust=3%, Bark=2%

# 1. Consume raw materials (Oak Log)
Debit:  WIP Account (Work in Progress)     $100.00
Credit: Raw Materials Inventory              $100.00

# 2. Receive finished goods (main product)
Debit:  Finished Goods — Oak Plank           $95.00
Credit: WIP Account                           $95.00

# 3. Receive by-product (Sawdust)
Debit:  Finished Goods — Sawdust              $3.00
Credit: WIP Account                            $3.00

# 4. Receive by-product (Bark Chips)
Debit:  Finished Goods — Bark Chips           $2.00
Credit: WIP Account                            $2.00

# Result: WIP = $0 (fully allocated)

The key insight: the WIP account zeroes out completely. Every dollar of input cost is allocated across all outputs. If your cost_share percentages don't sum to 100%, the main product absorbs the remainder — the WIP account still zeroes out, but your main product's unit cost will be higher than expected.

Validate Your cost_share Sum

Add a constraint to catch BoMs where by-product cost shares exceed 100%. While Odoo won't crash, a by-product cost_share of 60% on top of another at 50% means the main product gets a negative cost share of -10%, creating a negative inventory valuation entry. This is technically valid accounting (the main product is "subsidized" by the by-products), but it confuses auditors and usually signals a data entry error.

Python — Constraint to validate cost_share totals
from odoo import models, api
from odoo.exceptions import ValidationError


class MrpBom(models.Model):
    _inherit = 'mrp.bom'

    @api.constrains('byproduct_ids')
    def _check_byproduct_cost_share(self):
        for bom in self:
            total_share = sum(
                bom.byproduct_ids.mapped('cost_share')
            )
            if total_share > 100:
                raise ValidationError(
                    f'By-product cost shares total '
                    f'{total_share}%%, which exceeds 100%%. '
                    f'The main product would receive a '
                    f'negative cost allocation. Please '
                    f'adjust the cost_share values.'
                )
            if total_share > 80:
                # Warning-level: log but allow
                import logging
                _logger = logging.getLogger(__name__)
                _logger.warning(
                    'BoM %s: by-product cost shares total '
                    '%s%%, leaving only %s%% for the main '
                    'product.', bom.display_name,
                    total_share, 100 - total_share,
                )
05

Managing Production Waste Alongside By-Products in Odoo 19

Not every secondary output has value. Some outputs are genuine waste — material that must be disposed of, often at a cost. Odoo distinguishes between by-products (which enter stock) and scrap (which exits stock). The trick is knowing when to use each.

Output TypeOdoo ModelEnters Inventory?Has Cost Share?Example
By-Productmrp.bom.byproductYesYes (0-100%)Sawdust, whey, scrap metal
Scrapstock.scrapNo (moves to scrap location)No (cost absorbed by main product)Defective pieces, contaminated material
Zero-Value By-Productmrp.bom.byproductYes (for tracking)Yes (set to 0%)Emissions data, water usage tracking

Automated Scrap from Manufacturing Orders

Odoo 19 allows operators to scrap directly from the manufacturing order. But if your process always produces a predictable amount of waste (e.g., 2% material loss during cutting), you can automate scrap creation:

Python — Auto-create scrap for predictable waste
from odoo import models, api


class MrpProduction(models.Model):
    _inherit = 'mrp.production'

    def button_mark_done(self):
        """Override to auto-scrap predictable waste."""
        res = super().button_mark_done()
        for production in self:
            production._create_automatic_scrap()
        return res

    def _create_automatic_scrap(self):
        """Create scrap orders for predictable waste.

        Reads a 'waste_percentage' field from the BoM
        and scraps that percentage of each component.
        """
        Scrap = self.env['stock.scrap']
        scrap_location = self.env.ref(
            'stock.stock_location_scrapped'
        )
        for move in self.move_raw_ids.filtered(
            lambda m: m.state == 'done'
        ):
            waste_pct = (
                self.bom_id.waste_percentage or 0.0
            )
            if waste_pct <= 0:
                continue
            scrap_qty = move.quantity * (waste_pct / 100)
            if scrap_qty > 0:
                Scrap.create({
                    'product_id': move.product_id.id,
                    'scrap_qty': scrap_qty,
                    'product_uom_id': move.product_uom.id,
                    'production_id': self.id,
                    'location_id': (
                        self.location_src_id.id
                    ),
                    'scrap_location_id': (
                        scrap_location.id
                    ),
                }).action_validate()
By-Product with cost_share=0 vs. Scrap: When to Use Which

Use a zero-cost by-product when you need to track the quantity in inventory (for environmental reporting, regulatory compliance, or future sale potential) but don't want to allocate production cost to it. Use scrap when the material is truly disposed of and leaves your inventory system. The accounting difference: a zero-cost by-product sits in stock at $0; scrap posts a loss to your scrap expense account.

06

Reporting on By-Product Yield, Cost Recovery, and Production Efficiency

Once by-products are flowing through your system, you need visibility into three metrics: yield (are we producing the expected quantity of each by-product?), cost recovery (how much revenue are by-products generating?), and efficiency (what percentage of input is becoming useful output vs. waste?).

Custom SQL View for By-Product Yield Analysis

Odoo's standard manufacturing analysis report tracks main product output. To get by-product visibility, create a custom report model:

Python — By-product yield analysis report
from odoo import models, fields, tools


class ByproductYieldReport(models.Model):
    _name = 'report.byproduct.yield'
    _description = 'By-Product Yield Analysis'
    _auto = False
    _order = 'date desc'

    date = fields.Date(readonly=True)
    production_id = fields.Many2one(
        'mrp.production', readonly=True,
    )
    product_id = fields.Many2one(
        'product.product', string='By-Product',
        readonly=True,
    )
    main_product_id = fields.Many2one(
        'product.product', string='Main Product',
        readonly=True,
    )
    expected_qty = fields.Float(readonly=True)
    actual_qty = fields.Float(readonly=True)
    yield_pct = fields.Float(
        string='Yield %', readonly=True,
    )
    cost_share = fields.Float(readonly=True)
    allocated_cost = fields.Float(readonly=True)

    def init(self):
        tools.drop_view_if_exists(
            self.env.cr, self._table
        )
        self.env.cr.execute(f"""
            CREATE OR REPLACE VIEW {self._table} AS (
                SELECT
                    sm.id AS id,
                    mp.date_start::date AS date,
                    mp.id AS production_id,
                    sm.product_id AS product_id,
                    mp.product_id AS main_product_id,
                    (bp.product_qty * mp.product_qty
                     / bb.product_qty)
                        AS expected_qty,
                    sm.quantity AS actual_qty,
                    CASE
                        WHEN bp.product_qty > 0
                        THEN (sm.quantity * 100.0)
                             / (bp.product_qty
                                * mp.product_qty
                                / bb.product_qty)
                        ELSE 0
                    END AS yield_pct,
                    bp.cost_share AS cost_share,
                    sm.quantity * sm.price_unit
                        AS allocated_cost
                FROM stock_move sm
                JOIN mrp_production mp
                    ON sm.production_id = mp.id
                JOIN mrp_bom bb
                    ON mp.bom_id = bb.id
                JOIN mrp_bom_byproduct bp
                    ON bp.bom_id = bb.id
                    AND bp.product_id = sm.product_id
                WHERE sm.state = 'done'
                  AND sm.production_id IS NOT NULL
                  AND sm.byproduct_id IS NOT NULL
            )
        """)

This report gives you a row for every by-product produced across all manufacturing orders. You can pivot it by date, by-product, or main product to spot yield trends. A yield percentage consistently below 90% signals a process issue — the BoM expected 5 kg of sawdust but you're only getting 4.2 kg, meaning material is being lost somewhere in the process.

XML — Menu and action for the yield report
<record id="action_byproduct_yield_report" model="ir.actions.act_window">
    <field name="name">By-Product Yield Analysis</field>
    <field name="res_model">report.byproduct.yield</field>
    <field name="view_mode">pivot,graph,list</field>
    <field name="context">{{
        'search_default_group_by_product': 1,
        'search_default_this_month': 1,
    }}</field>
</record>

<menuitem id="menu_byproduct_yield"
    name="By-Product Yield"
    parent="mrp.menu_mrp_reporting"
    action="action_byproduct_yield_report"
    sequence="30"/>
Track Cost Recovery Rate

Add a second metric to your dashboard: cost recovery rate = (by-product sales revenue / by-product allocated cost) x 100. If you allocated $3 of production cost to sawdust and sold it for $4.50, your cost recovery is 150% — the by-product is more than paying for itself. If recovery drops below 100%, you're losing money on the by-product and should either reduce its cost_share (pushing more cost to the main product) or find a higher-paying buyer.

07

4 By-Product Mistakes That Silently Corrupt Your Manufacturing Costs

1

Leaving cost_share at 0% on Valuable By-Products

The default cost_share on a new by-product line is 0%. If you add sawdust as a by-product but forget to set its cost share, the sawdust enters inventory at $0.00. When you sell it, your books show 100% gross margin on sawdust (revenue minus zero cost). Meanwhile, the main product absorbs 100% of the manufacturing cost, making it look less profitable than it actually is. Both margins are wrong.

Our Fix

Add a server action or onchange that warns when a by-product is saved with cost_share = 0 and the product has a non-zero sale price. Use the constraint we showed in Step 04 to enforce a minimum cost share for products that have a sales price configured.

2

By-Product UoM Mismatch with the Product's Default UoM

If the sawdust product is configured with a default UoM of "Units" but the BoM by-product line specifies "kg," Odoo will attempt a UoM conversion. If the product's UoM category doesn't match (Unit vs. Weight), the manufacturing order will fail on completion with a cryptic UoM conversion error. This only surfaces at MO completion time, not when saving the BoM.

Our Fix

Always ensure the by-product's UoM on the BoM line belongs to the same UoM category as the product's default UoM. Add a BoM validation check that runs on save: compare byproduct.product_uom_id.category_id with byproduct.product_id.uom_id.category_id.

3

Forgetting By-Products When Using "Produce" Wizard Partial Quantities

When a manufacturing order is partially produced (e.g., you planned to make 10 planks but only completed 7), Odoo scales the by-product quantities proportionally. However, real-world by-product ratios are not always linear. A batch of 7 planks might produce more sawdust per plank than a full batch of 10, because setup waste is constant regardless of batch size. If operators don't adjust the by-product quantities in the completion wizard, your inventory counts will drift.

Our Fix

Train operators to verify by-product quantities in the "Produce" wizard before confirming. For processes with non-linear by-product ratios, consider using the Manufacturing Tablet View with required quality checks that force the operator to weigh or count by-products before the system accepts the completion.

4

Not Tracking By-Products with Lot/Serial Numbers for Traceability

If your main product uses lot tracking but your by-products don't, you lose the ability to trace a batch of by-product back to its source manufacturing order. For food, pharma, and chemical industries, this breaks regulatory traceability requirements. A contamination recall on the main product should also flag all by-products from the same batch.

Our Fix

Enable lot tracking on by-product products when the main product uses lot tracking. Configure the BoM to auto-generate lot numbers for by-products that inherit the main product's lot prefix. This creates a traceable chain: MO-2026-0547 produced LOT-PLANK-0547 (main) and LOT-SAWDUST-0547 (by-product).

BUSINESS ROI

What Proper By-Product Tracking Saves Your Manufacturing Operation

By-product tracking isn't overhead — it's revenue recovery. Here's what changes when every output from your production line is captured in the system:

5-15%Recovered Revenue

By-products that were previously discarded or given away are now tracked, valued, and sold. Sawmills, dairies, and metal fabricators typically find 5-15% of additional revenue hiding in their waste streams.

AccurateProduct Costing

With cost_share allocation, your main product's unit cost reflects reality. Pricing decisions, margin analysis, and make-vs-buy calculations are based on correct numbers, not inflated costs.

100%Audit Traceability

Every gram of output is accounted for in the system. ISO 14001 environmental audits, food safety HACCP reviews, and financial audits all require this level of material traceability.

The hidden ROI is process improvement visibility. When you track by-product yield over time, you can spot trends: a declining sawdust yield might indicate blade dulling on the saw, an increasing whey ratio might signal changes in milk quality from a supplier. By-product data becomes a leading indicator for production issues — before the main product quality starts to suffer.

SEO NOTES

Optimization Metadata

Meta Desc

Configure by-products and co-products in Odoo 19 MRP. Cost allocation with cost_share, co-product routing, inventory valuation, waste management, and yield reporting.

H2 Keywords

1. "Configuring By-Products on a Bill of Materials in Odoo 19"
2. "Cost Allocation for By-Products and Co-Products in Odoo 19 Manufacturing"
3. "Inventory Valuation of By-Products: FIFO, AVCO, and Standard Cost in Odoo 19"
4. "4 By-Product Mistakes That Silently Corrupt Your Manufacturing Costs"

Stop Throwing Away Revenue with Every Manufacturing Order

Every untracked by-product is inventory you own but can't see, revenue you could earn but don't collect, and cost you allocate incorrectly. Whether it's sawdust, whey, scrap metal, or chemical residuals — if your production line creates it, your ERP should track it, value it, and route it.

If you're running Odoo 19 Manufacturing and want to capture the full value of your production line, we can help. We configure by-product flows, set up cost allocation models, build custom yield reports, and integrate waste management workflows. The setup typically takes 2-3 days and pays for itself within the first quarter through recovered by-product revenue and accurate product costing.

Book a Free Manufacturing Assessment