Skip to content

[Feature] External build order #9312

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 77 commits into from
Jun 12, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
77 commits
Select commit Hold shift + click to select a range
ab1bafb
Add BuildOrder reference to PurchaseOrderLineItem
SchrodingersGat Mar 13, 2025
0074110
Add setting to enable / disable external build orders
SchrodingersGat Mar 13, 2025
7ef4be1
Fix for supplier part detail
SchrodingersGat Mar 13, 2025
c3823ed
Update forms
SchrodingersGat Mar 13, 2025
277d3ea
Filter build list by "external" status
SchrodingersGat Mar 13, 2025
6b80014
Merge branch 'master' into external-build-order
SchrodingersGat Mar 13, 2025
7ad2f27
Add "external" attribute to BuildOrder
SchrodingersGat Mar 13, 2025
4577435
Filter by external build when selecting against purchase order line item
SchrodingersGat Mar 14, 2025
7c837b3
Add frontend elements
SchrodingersGat Mar 14, 2025
f1cb848
Merge branch 'master' into external-build-order
SchrodingersGat Mar 16, 2025
3167eaf
Prevent creation of build outputs
SchrodingersGat Mar 16, 2025
68464c9
Tweak related model field
SchrodingersGat Mar 16, 2025
42257dc
Merge branch 'master' into external-build-order
SchrodingersGat Mar 17, 2025
6d0a618
Fix migrations
SchrodingersGat Mar 17, 2025
61d0d34
Merge remote-tracking branch 'origin/master' into external-build-order
SchrodingersGat Mar 17, 2025
b9ad236
Fix some existing typos
SchrodingersGat Mar 18, 2025
82bf62a
Add build info when receiving line items
SchrodingersGat Mar 18, 2025
efcab26
Logic fix
SchrodingersGat Mar 18, 2025
778e734
Merge remote-tracking branch 'origin/master' into external-build-order
SchrodingersGat Mar 19, 2025
b0db20e
Merge branch 'master' into external-build-order
SchrodingersGat Mar 25, 2025
7888d08
Merge branch 'master' into external-build-order
SchrodingersGat Mar 26, 2025
3002cfd
Bump API version
SchrodingersGat Mar 26, 2025
22f9d0f
Merge branch 'master' into external-build-order
SchrodingersGat Mar 26, 2025
2b5f5ba
Merge branch 'master' into external-build-order
SchrodingersGat Mar 30, 2025
71cd8da
Merge remote-tracking branch 'origin/master' into external-build-order
SchrodingersGat Mar 31, 2025
1228ef6
Merge branch 'master' into external-build-order
SchrodingersGat Mar 31, 2025
33d86f1
Merge branch 'master' into external-build-order
SchrodingersGat Mar 31, 2025
eedae30
Merge branch 'master' into external-build-order
SchrodingersGat Apr 6, 2025
3ac918b
Merge branch 'master' into external-build-order
SchrodingersGat Apr 10, 2025
5f10c94
Updated relationship
SchrodingersGat Apr 10, 2025
f35ae77
Add external orders tab for order
SchrodingersGat Apr 10, 2025
f2c8eec
Display table of external purchase orders against a build order
SchrodingersGat Apr 10, 2025
c5ae4cc
Fix permissions
SchrodingersGat Apr 10, 2025
f07d5a7
Tweak field definition
SchrodingersGat Apr 11, 2025
cd026d0
Add unit tests
SchrodingersGat Apr 11, 2025
cb1b08c
Merge branch 'master' into external-build-order
SchrodingersGat Apr 11, 2025
dd68653
Tweak api_version.py
SchrodingersGat Apr 11, 2025
94701b7
Merge remote-tracking branch 'origin/master' into external-build-order
SchrodingersGat Apr 13, 2025
836bf13
Playwright testing
SchrodingersGat Apr 14, 2025
a71cae8
Merge branch 'master' into external-build-order
SchrodingersGat Apr 15, 2025
0b78672
Merge branch 'master' into external-build-order
SchrodingersGat Apr 15, 2025
778a9de
Merge branch 'master' into external-build-order
SchrodingersGat Apr 15, 2025
e9cb7d3
Merge branch 'master' into external-build-order
SchrodingersGat Apr 15, 2025
1dff8bd
Merge branch 'master' into external-build-order
SchrodingersGat Apr 17, 2025
edc4821
Merge branch 'master' into external-build-order
SchrodingersGat Apr 18, 2025
fad6031
Merge branch 'master' into external-build-order
SchrodingersGat Apr 18, 2025
1bd03fe
Merge branch 'master' into external-build-order
SchrodingersGat Apr 20, 2025
d68680b
Merge branch 'master' into external-build-order
SchrodingersGat Apr 21, 2025
906f2a9
Merge branch 'master' into external-build-order
SchrodingersGat Apr 21, 2025
8421188
Fix discrepancy in 'building' filter
SchrodingersGat Apr 22, 2025
2f09a6c
Merge remote-tracking branch 'origin/master' into external-build-order
SchrodingersGat Apr 22, 2025
4f0aafb
Add basic documentation
SchrodingersGat Apr 22, 2025
c859763
Merge branch 'master' into external-build-order
SchrodingersGat Apr 22, 2025
53bc7f9
Merge branch 'master' into external-build-order
SchrodingersGat Apr 24, 2025
3bae51c
Merge branch 'master' into external-build-order
SchrodingersGat Apr 26, 2025
ca407b7
Merge branch 'master' into external-build-order
SchrodingersGat Jun 6, 2025
29fdd8c
Tweak docs macros
SchrodingersGat Jun 6, 2025
d431d4d
Merge branch 'master' into external-build-order
SchrodingersGat Jun 11, 2025
51fc2b8
Migration fix
SchrodingersGat Jun 11, 2025
3bdd439
Merge remote-tracking branch 'origin/master' into external-build-order
SchrodingersGat Jun 11, 2025
acbe88a
Adjust build page tabs
SchrodingersGat Jun 12, 2025
7c128d6
Merge remote-tracking branch 'origin/master' into external-build-order
SchrodingersGat Jun 12, 2025
3133676
Fix imports
SchrodingersGat Jun 12, 2025
53f6cd0
Fix broken import
SchrodingersGat Jun 12, 2025
8622f1c
Update playywright tests
SchrodingersGat Jun 12, 2025
ab99473
Merge remote-tracking branch 'origin/master' into external-build-order
SchrodingersGat Jun 12, 2025
6d2397d
Bump API version
SchrodingersGat Jun 12, 2025
e080371
Handle DB issues
SchrodingersGat Jun 12, 2025
ca140bf
Improve filter
SchrodingersGat Jun 12, 2025
c02e7b6
Cleaner code
SchrodingersGat Jun 12, 2025
0c89404
Fix column ordering bug
SchrodingersGat Jun 12, 2025
def3c24
Add filters to build output table
SchrodingersGat Jun 12, 2025
c75dadc
Documentation
SchrodingersGat Jun 12, 2025
7ae74ab
Tweak unit test
SchrodingersGat Jun 12, 2025
06867b6
Add "scheduled_for_production" field
SchrodingersGat Jun 12, 2025
c761b34
Add helper function to part model
SchrodingersGat Jun 12, 2025
d19b639
Cleanup
SchrodingersGat Jun 12, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 7 additions & 0 deletions docs/docs/manufacturing/build.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ The build calendar allows the user to navigate month-by-month and display the fi

## Build Order Details

Select an individual build order from the build order table to navigate to the Build Order detail page. The build order detail page provides a comprehensive overview of the build order, including all relevant information and actions.

### Build Order Reference

Each Build Order is uniquely identified by its *Reference* field. Read more about [reference fields](../settings/reference.md).
Expand Down Expand Up @@ -181,6 +183,10 @@ Build order notes (which support markdown formatting) are displayed in the *Note

{{ image("build/build_notes.png", title="Notes") }}

## External Build Orders

InvenTree supports the creation of *external build orders*, which are used to manage the manufacturing of parts by an external supplier. Read more about [external build orders](./external.md).

## Create Build Order

To create a build order for your part, you have two options:
Expand Down Expand Up @@ -266,6 +272,7 @@ The following [global settings](../settings/global.md) are available for adjusti
| Name | Description | Default | Units |
| ---- | ----------- | ------- | ----- |
{{ globalsetting("BUILDORDER_REFERENCE_PATTERN") }}
{{ globalsetting("BUILDORDER_EXTERNAL_BUILDS") }}
{{ globalsetting("BUILDORDER_REQUIRE_RESPONSIBLE") }}
{{ globalsetting("BUILDORDER_REQUIRE_ACTIVE_PART") }}
{{ globalsetting("BUILDORDER_REQUIRE_LOCKED_PART") }}
Expand Down
72 changes: 72 additions & 0 deletions docs/docs/manufacturing/external.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
---
title: External Manufacturing
---

## External Manufacturing

In some cases, it may be necessary to outsource the manufacturing of certain components or assemblies to an external supplier. InvenTree provides a simple interface for managing external manufacturing processes, allowing users to create and track orders with external suppliers.

In the context of InvenTree, an *external build order* is a request to an external supplier to manufacture a specific assembly or component. This order is linked to a specific BOM and build order, ensuring that the correct components are produced.

In conjunction with the external [Build Order](./build.md), a [Purchase Order](../purchasing/purchase_order.md) is required to manage the procurement of the manufactured items. The purchase order is used to track the order with the external supplier, including details such as pricing, delivery dates, and payment terms.

When items are received from the external supplier (against the Purchase Order), they are automatically allocated to the external build order.

### Stock Allocation

Stock item can be allocated against an external build order as per the normal build order process. The external manufacturer may be expected to provide all of the components required to complete the build order, or they may only be responsible for a subset of the components. In either case, it is important to ensure that the correct stock items are allocated to the external build order. The allocation process is flexible, allowing users to specify which components should be included in the order.

In the case of partial allocation, the user simply selects the stock items that should be included in the order.

### Fulfillment

The fulfillment process for external build orders is slightly different from the standard build order process. Instead of manually creating build outputs for the build order, these outputs are generated when the linked Purchase Order items are received.
The Build Order and Purchase Order processes are closely linked, allowing users to easily manage the entire manufacturing process from start to finish. This includes tracking the progress of the external manufacturing process, managing stock allocations, and ensuring that the correct components are delivered on time.

### Extra Requirements

To successfully manage external build orders, the "assembly" (the part which is being manufactured) must be a *purchaseable* part, and it must have a linked Supplier Part which is associated with the external supplier.

This ensures that external build orders are only created for parts that can be purchased from the supplier, and that the supplier is provided with the correct order codes and descriptions.

The external suppiler must also be marked as a "manufacturer" in the InvenTree system.

## Enable External Manufacturing

By default, external manufacturing is disabled in InvenTree. To enable this feature, enable the {{ globalsetting("BUILDORDER_EXTERNAL_BUILDS", short=True) }} setting.

## External Build Order Process

The process for managing external build orders in InvenTree is as follows:

### Create Build Order

Create a new build order, specifying the assembly part and the quantity to be manufactured. When creating the new build order, select the "External" option to indicate that this is an external build order. The Build detail page will indicate that this is an external build order:

{{ image("build/external_build_detail.png", "External build order detail") }}

Additionally, the *Incomplete Outputs* panel will indicate that the build order is linked to an external purchase order, and that the outputs will be generated when the purchase order items are received.

{{ image("build/external_build_fulfilment.png", "External build order fulfillment") }}

### Create Purchase Order

Once the build order has been created, a purchase order provides the link between the build order and the incoming goods (which will fulfil the build order).

Create a purchase order against the external supplier which will be responsible for manufacturing the assembly. This assumes that there is already a [supplier part](../purchasing/supplier.md#supplier-parts) which links the assembled part to the external supplier.

### Add Items to Purchase Order

Once the purchase order has been created, add the assembly part to the purchase order. When adding the line item to the purchase order, ensure that the "Build Order" field is set to the external build order that was created earlier. This links the purchase order to the build order, allowing InvenTree to automatically allocate the received items to the build order when they are received.

{{ image("build/external_build_select_build.png", "Link items to build order") }}

### Receive Items

Follow the normal process for receiving items against the purchase order. When the items are received from the external supplier, they will be marked as "build outputs" against the external build order.

{{ image("build/external_build_receive_items.png", "Receive items against purchase order") }}

The received items will now be registered as "incomplete outputs" against the external build order:

{{ image("build/external_build_incomplete.png", "Incomplete build outputs") }}
35 changes: 24 additions & 11 deletions docs/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -318,8 +318,14 @@ def observe_setting(key: str, group: str):
json.dump(data, f, indent=4)

@env.macro
def rendersetting(key: str, setting: dict):
"""Render a provided setting object into a table row."""
def rendersetting(key: str, setting: dict, short: bool = False):
"""Render a provided setting object into a table row.

Arguments:
key: The name of the setting to extract information for.
setting: The setting object to render.
short: If True, return a short version of the setting (default: False)
"""
name = setting['name']
description = setting['description']
default = setting.get('default')
Expand All @@ -328,37 +334,44 @@ def rendersetting(key: str, setting: dict):
default = f'`{default}`' if default else ''
units = f'`{units}`' if units else ''

return (
f'| <div title="{key}">{name}</div> | {description} | {default} | {units} |'
)
if short:
return f'<span title="{key}"><strong>{name}</strong></span>'

return f'| <div title="{key}"><strong>{name}</strong></div> | {description} | {default} | {units} |'

@env.macro
def globalsetting(key: str):
def globalsetting(key: str, short: bool = False):
"""Extract information on a particular global setting.

Arguments:
key: The name of the global setting to extract information for.
short: If True, return a short version of the setting (default: False)
"""
global GLOBAL_SETTINGS
setting = GLOBAL_SETTINGS[key]

observe_setting(key, 'global')
# Settings are only 'observed' if they are displayed in full
if not short:
observe_setting(key, 'global')

return rendersetting(key, setting)
return rendersetting(key, setting, short=short)

@env.macro
def usersetting(key: str):
def usersetting(key: str, short: bool = False):
"""Extract information on a particular user setting.

Arguments:
key: The name of the user setting to extract information for.
short: If True, return a short version of the setting (default: False)
"""
global USER_SETTINGS
setting = USER_SETTINGS[key]

observe_setting(key, 'user')
# Settings are only 'observed' if they are displayed in full
if not short:
observe_setting(key, 'user')

return rendersetting(key, setting)
return rendersetting(key, setting, short=short)

@env.macro
def tags_and_filters():
Expand Down
1 change: 1 addition & 0 deletions docs/mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ nav:
- Build Orders: manufacturing/build.md
- Build Outputs: manufacturing/output.md
- Allocating Stock: manufacturing/allocate.md
- External Manufacturing: manufacturing/external.md
- Example Build Order: manufacturing/example.md
- Purchasing:
- Purchasing: purchasing/index.md
Expand Down
5 changes: 4 additions & 1 deletion src/backend/InvenTree/InvenTree/api_version.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
"""InvenTree API version information."""

# InvenTree API version
INVENTREE_API_VERSION = 347
INVENTREE_API_VERSION = 348

"""Increment this API version number whenever there is a significant change to the API that any clients need to know about."""


INVENTREE_API_TEXT = """
v348 -> 2025-04-22 : https://github.com/inventree/InvenTree/pull/9312
- Adds "external" flag for BuildOrder
- Adds link between PurchaseOrderLineItem and BuildOrder

v347 -> 2025-06-12 : https://github.com/inventree/InvenTree/pull/9764
- Adds "copy_tests" field to the DuplicatePart API endpoint
Expand Down
3 changes: 2 additions & 1 deletion src/backend/InvenTree/build/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ class Meta:
"""Metaclass options."""

model = Build
fields = ['sales_order']
fields = ['sales_order', 'external']

status = rest_filters.NumberFilter(label=_('Order Status'), method='filter_status')

Expand Down Expand Up @@ -355,6 +355,7 @@ class BuildList(DataExportViewMixin, BuildMixin, ListCreateAPI):
'project_code',
'priority',
'level',
'external',
]

ordering_field_aliases = {
Expand Down
22 changes: 22 additions & 0 deletions src/backend/InvenTree/build/migrations/0057_build_external.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Generated by Django 4.2.20 on 2025-03-13 22:34

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("build", "0056_alter_build_link"),
]

operations = [
migrations.AddField(
model_name="build",
name="external",
field=models.BooleanField(
default=False,
help_text="This build order is fulfilled externally",
verbose_name="External Build",
),
),
]
14 changes: 14 additions & 0 deletions src/backend/InvenTree/build/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ class Build(
sales_order: References to a SalesOrder object for which this Build is required (e.g. the output of this build will be used to fulfil a sales order)
take_from: Location to take stock from to make this build (if blank, can take from anywhere)
status: Build status code
external: Set to indicate that this build order is fulfilled externally
batch: Batch code transferred to build parts (optional)
creation_date: Date the build was created (auto)
target_date: Date the build will be overdue
Expand Down Expand Up @@ -191,6 +192,13 @@ def clean(self):
"""Validate the BuildOrder model."""
super().clean()

if self.external and not self.part.purchaseable:
raise ValidationError({
'external': _(
'Build orders can only be externally fulfilled for purchaseable parts'
)
})

if get_global_setting('BUILDORDER_REQUIRE_RESPONSIBLE'):
if not self.responsible:
raise ValidationError({
Expand Down Expand Up @@ -286,6 +294,12 @@ def get_absolute_url(self):
),
)

external = models.BooleanField(
default=False,
verbose_name=_('External Build'),
help_text=_('This build order is fulfilled externally'),
)

destination = models.ForeignKey(
'stock.StockLocation',
verbose_name=_('Destination Location'),
Expand Down
1 change: 1 addition & 0 deletions src/backend/InvenTree/build/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ class Meta:
'completed',
'completion_date',
'destination',
'external',
'parent',
'part',
'part_name',
Expand Down
Loading
Loading