feat: add piece_count and piece_size fields to BomItem for cut-to-length parts (#12422)

* feat: add piece_count and piece_size fields to BomItem for cut-to-length parts

Manufacturing BOMs frequently require multiple pieces of a specific size
cut from continuous stock (cables, tubing, structural profiles). Currently
the only way to express "10 pieces of 250mm cable" is to enter the total
length (2.5m) as quantity, which loses the piece-count information that
purchasing and production need.

This adds two optional fields to BomItem:
- piece_count: number of discrete pieces required (default: 1)
- piece_size: size/length of each piece (e.g. "250 mm")

When piece_size is specified, the total quantity is auto-calculated as
piece_count × piece_size, maintaining full backward compatibility (existing
items effectively have piece_count=1 and empty piece_size).

Changes:
- Backend: new model fields, migration, updated recalculate_quantity()
  logic, hash_fields for BOM validation
- API: serializer exposes piece_count and piece_size
- Frontend: BOM form includes the new fields, BOM table shows them as
  optional columns

Addresses #10274

* refactor: simplify to single piece_count field per reviewer feedback

Remove the piece_size field entirely. The existing quantity field already
represents the per-piece size/length, so piece_count multiplied by
quantity gives the total material requirement.

Example: quantity=200mm, piece_count=10 → total 2m of wire in 10 pieces.

Changes:
- Remove piece_size model field, serializer field, and frontend column/form
- Update migration to only add piece_count
- Update get_required_quantity() to multiply by piece_count
- Restore original recalculate_quantity() without piece_size logic

* test/docs: add unit tests and documentation for piece_count field

* style: replace ambiguous × with x to fix RUF002 lint error

* Address review feedback: api_version bump, changelog, style fix

- Bump INVENTREE_API_VERSION to 531 with entry for piece_count field
- Add CHANGELOG.md entry under Unreleased > Added
- Fix RUF001: replace ambiguous × with x in serializers.py help_text

* fix: align piece_count migration help_text with model (RUF001)

The 0153 AddField recorded help_text with a Unicode multiplication sign
(×), while the model field uses plain 'x' after the RUF001 fix. This
mismatch made makemigrations --check flag an unstaged
0154_alter_bomitem_piece_count migration, failing the DB test CI jobs.

Update the original migration's help_text (and docstring) to plain 'x'
so the field definition matches the model, keeping a single clean
migration instead of add-then-alter.

* fix: use set_quantity() in piece_count tests

BomItem.quantity is a derived field, recalculated from raw_amount on
every save() via recalculate_quantity(). Setting item.quantity directly
was overwritten back to the fixture value on save, so the tests computed
against quantity=3 and failed. Use set_quantity() (which sets raw_amount)
to match how quantity is meant to be updated.

* ci: re-trigger CI to confirm Firefox E2E failures are transient

---------

Co-authored-by: Aman Jain <jainamn@amazon.com>
This commit is contained in:
amanjain57-gif
2026-08-16 09:43:42 +10:00
committed by GitHub
co-authored by Aman Jain
parent e4b23b4665
commit ee4ad7fd10
10 changed files with 293 additions and 3 deletions
+41
View File
@@ -22,6 +22,7 @@ A BOM for a particular assembly is comprised of a number (zero or more) of BOM "
| Quantity | The quantity of *Part* required for the assembly - this value is automatically calculated from the "raw amount" field, taking into account the units of measure associated with the underlying part. |
| Attrition | Estimated attrition losses for a production run. Expressed as a percentage of the base quantity (e.g. 2%) |
| Setup Quantity | An additional quantity of the part which is required to account for fixed setup losses during the production process. This is added to the base quantity of the BOM line item |
| Piece Count | The number of individual pieces required per assembly (for cut-to-length items). Defaults to 1. Total material = quantity x piece_count. |
| Rounding Multiple | A value which indicates that the required quantity should be rounded up to the nearest multiple of this value. |
| Consumable | A boolean field which indicates whether this BOM Line Item is *consumable* |
| Inherited | A boolean field which indicates whether this BOM Line Item will be "inherited" by BOMs for parts which are a variant (or sub-variant) of the part for which this BOM is defined. |
@@ -40,6 +41,45 @@ If the underlying part does not have a defined unit of measure, the `raw_amount`
The `raw_amount` field also allows for fractional representation of the required quantity. For example, if the required quantity is 0.5 kg, the user can specify this as `500 g`, `0.5 kg`, `1/2 kg`, etc. The `quantity` field will be automatically calculated as 0.5 kg, regardless of the specific representation used in the `raw_amount` field.
### Piece Count (Cut-to-Length Parts)
The `piece_count` field supports scenarios where a material is cut or divided into multiple identical pieces for each assembly. This is common for cables, wires, tubing, extrusions, and similar length-based materials.
When `piece_count` is greater than 1, the `quantity` field represents the size or length of each individual piece, and `piece_count` indicates how many such pieces are needed per assembly. The total material required is calculated as:
```
Total material = quantity x piece_count x build_quantity
```
#### Example: Wire Harness Assembly
Consider an assembly that requires 10 pieces of wire, each cut to 200mm length:
| Field | Value | Description |
| --- | --- | --- |
| Quantity | 200 (mm) | Length of each individual wire piece |
| Piece Count | 10 | Number of wire pieces per assembly |
| Build Quantity | 5 | Number of assemblies to build |
| **Total Required** | **10,000 mm (10m)** | 200 x 10 x 5 = 10,000 mm |
#### Example: Tubing for Hydraulic System
An assembly requires 4 pieces of tubing, each 500mm long, with 5% attrition:
| Field | Value | Description |
| --- | --- | --- |
| Quantity | 500 (mm) | Length of each tube section |
| Piece Count | 4 | Number of tube pieces per assembly |
| Build Quantity | 3 | Number of assemblies to build |
| Attrition | 5% | Account for cutting waste |
| **Total Required** | **6,300 mm (6.3m)** | (500 x 4 x 3) x 1.05 = 6,300 mm |
!!! note "Default Behavior"
When `piece_count` is left at its default value of 1, the BOM line item behaves exactly as it did before this feature was introduced. Existing BOMs are unaffected.
!!! tip "When to Use Piece Count"
Use `piece_count` when you are cutting or dividing a material into multiple identical pieces. If each piece has a different length, create separate BOM line items instead.
### Consumable BOM Line Items
If a BOM line item is marked as *consumable*, this means that while the part and quantity information is tracked in the BOM, this line item does not get allocated to a [Build Order](./build.md). This may be useful for certain items that the user does not wish to track through the build process, as they may be low value, in abundant stock, or otherwise complicated to track.
@@ -165,6 +205,7 @@ The following BOM item fields are used when calculating the BOM checksum:
- *Attrition* - The attrition percentage of the BOM line item.
- *Setup Quantity* - The setup quantity of the BOM line item.
- *Rounding Multiple* - The rounding multiple of the BOM line item.
- *Piece Count* - The number of pieces required per assembly (for cut-to-length items).
- *Consumable* - Whether the BOM line item is consumable.
- *Inherited* - Whether the BOM line item is inherited.
- *Optional* - Whether the BOM line item is optional.