Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
119 changes: 119 additions & 0 deletions src/backend/InvenTree/part/migrations/0148_partrequirements.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
# Generated by Django 5.2.13 on 2026-04-11 06:56

import django.db.models.deletion
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("part", "0147_remove_part_default_supplier"),
]

operations = [
migrations.CreateModel(
name="PartRequirements",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"updated",
models.DateTimeField(
auto_now=True,
help_text="Timestamp of last update",
null=True,
verbose_name="Updated",
),
),
(
"in_stock",
models.DecimalField(
decimal_places=6,
default=0,
help_text="Total quantity of items in stock for this part",
max_digits=19,
verbose_name="In Stock",
),
),
(
"on_order",
models.DecimalField(
decimal_places=6,
default=0,
help_text="Total quantity of items on order for this part",
max_digits=19,
verbose_name="On Order",
),
),
(
"building",
models.DecimalField(
decimal_places=6,
default=0,
help_text="Total quantity of items currently being built for this part",
max_digits=19,
verbose_name="Building",
),
),
(
"build_order_requirements",
models.DecimalField(
decimal_places=6,
default=0,
help_text="Total quantity of this part required for all open build orders",
max_digits=19,
verbose_name="Build Order Requirements",
),
),
(
"sales_order_requirements",
models.DecimalField(
decimal_places=6,
default=0,
help_text="Total quantity of this part required for all open sales orders",
max_digits=19,
verbose_name="Sales Order Requirements",
),
),
(
"build_order_allocations",
models.DecimalField(
decimal_places=6,
default=0,
help_text="Total quantity of this part allocated to open build orders",
max_digits=19,
verbose_name="Build Order Allocations",
),
),
(
"sales_order_allocations",
models.DecimalField(
decimal_places=6,
default=0,
help_text="Total quantity of this part allocated to open sales orders",
max_digits=19,
verbose_name="Sales Order Allocations",
),
),
(
"part",
models.OneToOneField(
on_delete=django.db.models.deletion.CASCADE,
related_name="requirements_data",
to="part.part",
verbose_name="part",
),
),
],
options={
"abstract": False,
},
),
]
88 changes: 88 additions & 0 deletions src/backend/InvenTree/part/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -2703,6 +2703,94 @@ def after_save_part(sender, instance: Part, created, **kwargs):
)


class PartRequirements(common.models.MetaMixin):
"""Model for caching the various "requirements" associated with a Part.

This information is cached because it is expensive to calculate "on the fly",
and does not change very often (compared to the number of times it is accessed).

The following attributes are cached against each part:

- in_stock: The total quantity of items in stock for this part
- on_order: The total quantity of items on order for this part
- building: The total quantity of items currently being built for this part
- build_order_requirements: The total quantity of items required for all open build orders
- sales_order_requirements: The total quantity of items required for all open sales orders
- build_order_allocations: The total quantity of items allocated to open build orders
- sales_order_allocations: The total quantity of items allocated to open sales orders

Note that each of these fields includes the quantity for any active variants of this part, if they exist.
"""

part = models.OneToOneField(
Part,
on_delete=models.CASCADE,
related_name='requirements_data',
verbose_name=_('part'),
)

in_stock = models.DecimalField(
max_digits=19,
decimal_places=6,
default=0,
verbose_name=_('In Stock'),
help_text=_('Total quantity of items in stock for this part'),
)

on_order = models.DecimalField(
max_digits=19,
decimal_places=6,
default=0,
verbose_name=_('On Order'),
help_text=_('Total quantity of items on order for this part'),
)

building = models.DecimalField(
max_digits=19,
decimal_places=6,
default=0,
verbose_name=_('Building'),
help_text=_('Total quantity of items currently being built for this part'),
)

build_order_requirements = models.DecimalField(
max_digits=19,
decimal_places=6,
default=0,
verbose_name=_('Build Order Requirements'),
help_text=_('Total quantity of this part required for all open build orders'),
)

sales_order_requirements = models.DecimalField(
max_digits=19,
decimal_places=6,
default=0,
verbose_name=_('Sales Order Requirements'),
help_text=_('Total quantity of this part required for all open sales orders'),
)

build_order_allocations = models.DecimalField(
max_digits=19,
decimal_places=6,
default=0,
verbose_name=_('Build Order Allocations'),
help_text=_('Total quantity of this part allocated to open build orders'),
)

sales_order_allocations = models.DecimalField(
max_digits=19,
decimal_places=6,
default=0,
verbose_name=_('Sales Order Allocations'),
help_text=_('Total quantity of this part allocated to open sales orders'),
)

@property
def is_valid(self):
"""Return True if the cached requirements data is valid."""
return self.updated is not None


class PartPricing(common.models.MetaMixin):
"""Model for caching min/max pricing information for a particular Part.

Expand Down
Loading