From c4280c2a03dec87afda9478957dba328f82ae4b6 Mon Sep 17 00:00:00 2001 From: Oliver Date: Tue, 27 Oct 2026 15:14:15 +1100 Subject: [PATCH] [Refactor] Stock tracking audit (#13101) * Add "event_type" and "notes" support to AuditLog model * Register history entries for stock items * record stock edited events as normal updates * Remove legacy tracking entry generation code * Updated unit tests for all stock event types * remove legacy endpoints * remove legacy tables from UI * Updated docs * Data migration * Remove old models * Update docs * Updated playwright tests * Fix broken links * Update CHANGELOG.md * Cache CID value * Fix migration tests * Increase query threshold for unit test * Fix bug and add regression test * mark migration as one-way * flush events in the correct order * improve prefetch * Adjust row rendering --- CHANGELOG.md | 3 + .../images/stock/part_stock_history.png | Bin 0 -> 211458 bytes .../images/stock/part_tracking_history.png | Bin 111740 -> 0 bytes .../images/stock/stock_item_history.png | Bin 0 -> 107094 bytes .../stock/stock_item_tracking_history.png | Bin 59318 -> 0 bytes docs/docs/concepts/audit.md | 25 +- docs/docs/manufacturing/output.md | 2 +- docs/docs/plugins/mixins/validation.md | 4 +- docs/docs/sales/return_order.md | 2 +- docs/docs/stock/adjust.md | 2 +- docs/docs/stock/disassemble.md | 8 +- docs/docs/stock/index.md | 4 +- docs/docs/stock/tracking.md | 47 +- .../InvenTree/InvenTree/api_version.py | 11 +- src/backend/InvenTree/InvenTree/apps.py | 1 + .../management/commands/bulkloaddata.py | 8 +- src/backend/InvenTree/InvenTree/models.py | 3 + .../InvenTree/InvenTree/serializers.py | 2 +- src/backend/InvenTree/InvenTree/tasks.py | 2 +- .../InvenTree/InvenTree/test_commands.py | 29 +- src/backend/InvenTree/InvenTree/unit_test.py | 42 + src/backend/InvenTree/audit/api.py | 10 +- src/backend/InvenTree/audit/context.py | 164 ++++ src/backend/InvenTree/audit/helpers.py | 209 ++++- .../audit/migrations/0003_event_type.py | 31 + src/backend/InvenTree/audit/models.py | 58 +- src/backend/InvenTree/audit/registry.py | 42 + src/backend/InvenTree/audit/serializers.py | 49 + src/backend/InvenTree/audit/tests.py | 354 ++++++- src/backend/InvenTree/build/models.py | 242 +++-- src/backend/InvenTree/build/serializers.py | 6 +- src/backend/InvenTree/build/test_api.py | 87 +- src/backend/InvenTree/build/test_audit.py | 222 +++++ src/backend/InvenTree/build/test_build.py | 16 +- .../InvenTree/common/setting/system.py | 17 - src/backend/InvenTree/common/setting/user.py | 2 +- src/backend/InvenTree/order/models.py | 205 ++--- src/backend/InvenTree/order/serializers.py | 8 +- src/backend/InvenTree/order/test_api.py | 130 ++- src/backend/InvenTree/order/test_audit.py | 82 ++ .../InvenTree/order/test_sales_order.py | 74 +- src/backend/InvenTree/stock/__init__.py | 1 - src/backend/InvenTree/stock/admin.py | 21 +- src/backend/InvenTree/stock/api.py | 232 +---- ...129_migrate_stock_tracking_to_audit_log.py | 341 +++++++ .../0130_delete_stockitemtracking.py | 9 + src/backend/InvenTree/stock/models.py | 861 ++++++++---------- src/backend/InvenTree/stock/serializers.py | 178 +--- src/backend/InvenTree/stock/tasks.py | 36 - src/backend/InvenTree/stock/test_api.py | 373 +++----- src/backend/InvenTree/stock/test_audit.py | 569 ++++++++++++ .../InvenTree/stock/test_migrations.py | 361 ++++++++ src/backend/InvenTree/stock/tests.py | 265 +++--- src/backend/InvenTree/users/ruleset.py | 6 +- src/frontend/lib/enums/ApiEndpoints.tsx | 1 - src/frontend/lib/enums/ModelInformation.tsx | 2 +- .../src/components/panels/HistoryPanel.tsx | 9 +- src/frontend/src/forms/StockForms.tsx | 1 - .../pages/Index/Settings/SystemSettings.tsx | 7 - .../src/pages/part/PartStockHistoryDetail.tsx | 9 - src/frontend/src/pages/stock/StockDetail.tsx | 23 +- .../src/tables/general/AuditLogTable.tsx | 102 ++- .../src/tables/stock/StockTrackingTable.tsx | 317 ------- src/frontend/tests/pages/pui_stock.spec.ts | 92 +- 64 files changed, 3869 insertions(+), 2150 deletions(-) create mode 100644 docs/docs/assets/images/stock/part_stock_history.png delete mode 100644 docs/docs/assets/images/stock/part_tracking_history.png create mode 100644 docs/docs/assets/images/stock/stock_item_history.png delete mode 100644 docs/docs/assets/images/stock/stock_item_tracking_history.png create mode 100644 src/backend/InvenTree/audit/migrations/0003_event_type.py create mode 100644 src/backend/InvenTree/stock/migrations/0129_migrate_stock_tracking_to_audit_log.py create mode 100644 src/backend/InvenTree/stock/migrations/0130_delete_stockitemtracking.py delete mode 100644 src/backend/InvenTree/stock/tasks.py create mode 100644 src/backend/InvenTree/stock/test_audit.py delete mode 100644 src/frontend/src/tables/stock/StockTrackingTable.tsx diff --git a/CHANGELOG.md b/CHANGELOG.md index 8db8b4469e..7b6b2c9523 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Breaking Changes - [#13034](https://github.com/inventree/InvenTree/pull/13034) moves pricing models into a new dedicated "pricing" model, and provides new API endpoints for managing pricing separately from other models. +- [#13101](https://github.com/inventree/InvenTree/pull/13101) removes the StockTracking model and its associated API endpoints. Stock history tracking is now handled by the global audit log. ### Added @@ -19,6 +20,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Removed +- [#13101](https://github.com/inventree/InvenTree/pull/13101) removes the StockTracking model and its associated API endpoints. Stock history tracking is now handled by the global audit log. + ## 1.6.0 - 2026-10-18 ### Breaking Changes diff --git a/docs/docs/assets/images/stock/part_stock_history.png b/docs/docs/assets/images/stock/part_stock_history.png new file mode 100644 index 0000000000000000000000000000000000000000..74456046c4138bcd4f197db57c9a9e93b6137017 GIT binary patch literal 211458 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�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 z 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�+JP0coesD85LG6PNE8$F9? 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�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

uo=CY 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�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 literal 0 HcmV?d00001 diff --git a/docs/docs/assets/images/stock/part_tracking_history.png b/docs/docs/assets/images/stock/part_tracking_history.png deleted file mode 100644 index cf31097ffe8afa7f77f24145eeee8e65cefa220b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 111740 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 ANB{r; diff --git a/docs/docs/assets/images/stock/stock_item_history.png b/docs/docs/assets/images/stock/stock_item_history.png new file mode 100644 index 0000000000000000000000000000000000000000..3ec4336a547962a3fdb1d113260458ad6cb59fd5 GIT binary patch literal 107094 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 literal 0 HcmV?d00001 diff --git a/docs/docs/assets/images/stock/stock_item_tracking_history.png b/docs/docs/assets/images/stock/stock_item_tracking_history.png deleted file mode 100644 index 895fb61e0479c1dbbde503a98f427529fae4c66b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 59318 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

!rld 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 diff --git a/docs/docs/concepts/audit.md b/docs/docs/concepts/audit.md index 1a8f409237..a11b5d6231 100644 --- a/docs/docs/concepts/audit.md +++ b/docs/docs/concepts/audit.md @@ -36,7 +36,6 @@ To keep the audit log focused and performant, some changes are intentionally *no - **Derived fields** - Internal or calculated fields (such as tree structure fields, cached pricing values and metadata) are excluded. - **Data import** - Changes made using the [data import](./data_import.md) tool are not recorded. -- **Stock items** - Changes to stock items are tracked separately using [stock tracking](../stock/tracking.md). - **Unsupported models** - Only the models listed below are audited. ### Audited Models @@ -45,6 +44,21 @@ The following models are currently audited. Where a model has a *parent*, change {{ audited_models() }} +### Events + +Some models record named *events* in the audit log, in addition to field changes. An event describes *what happened* to an item (for example, an operation which affects multiple fields, or multiple items), and may include: + +- **Event type** - The type of event (displayed in place of the action in the audit log table) +- **Notes** - Optional notes provided by the user when the event was recorded +- **Deltas** - Additional information about the event, such as quantities or references to related objects. References to related objects are recorded as they were at the time of the event. + +An event may be recorded even if no fields of the item were changed. + +For example, stock operations (such as moving, counting or shipping stock) are recorded as [stock tracking](../stock/tracking.md) events against each affected stock item. + +!!! warning "Disabling the Audit Log" + Events are recorded in the audit log, and are subject to the same settings as all other audit entries. If the audit log is disabled, events are **not** recorded - this includes **all stock tracking history**. + ### Viewing Change History #### Detail Pages @@ -60,9 +74,10 @@ For example, the history panel for a purchase order shows changes made to the or The *History* panel is only displayed if **all** of the following conditions are met: - The audit log is enabled (via the `Enable Audit Log` system setting) -- The user is a *staff* user - The user has enabled the *Show Change History* user setting +The entries displayed in the *History* panel are filtered by the permissions of the user (see [Permissions](#permissions) below). + {{ image("concepts/audit_user_setting.png", "Show change history setting") }} #### Admin Center @@ -79,10 +94,12 @@ The audit log table can be filtered by action, user, date range and model type: Access to the audit log is restricted: -- The audit log (including the `/api/audit/` API endpoint) is only available to *staff* users. -- Audit entries are additionally filtered by the user's [role permissions](../settings/permissions.md). A user can only view audit entries for models which they have permission to view. +- Audit entries are filtered by the user's [role permissions](../settings/permissions.md). A user can only view audit entries for models which they have permission to view. - Some models (e.g. attachments and parameters) require the user to also have permission to view the *parent* model. +- The history of some models (e.g. global settings) is only available to *staff* users. - The remote IP address associated with each entry is only visible to staff users. +- References to related objects (recorded against [events](#events)) are hidden if the user does not have permission to view the related model. +- The *Audit Log* panel in the Admin Center is only available to staff users. ### Data Retention diff --git a/docs/docs/manufacturing/output.md b/docs/docs/manufacturing/output.md index 2f98c2ff16..886dd3747e 100644 --- a/docs/docs/manufacturing/output.md +++ b/docs/docs/manufacturing/output.md @@ -41,7 +41,7 @@ The following options are available when creating a new build output: ### Specifying Serial Numbers -Refer to the [serial number generation guide](../stock/tracking.md#generating-serial-numbers) for further information on serial number input. +Refer to the [serial number generation guide](../stock/traceability.md#generating-serial-numbers) for further information on serial number input. ### Creating Outputs Without Serial Numbers diff --git a/docs/docs/plugins/mixins/validation.md b/docs/docs/plugins/mixins/validation.md index bbdbc2207f..8dbf917928 100644 --- a/docs/docs/plugins/mixins/validation.md +++ b/docs/docs/plugins/mixins/validation.md @@ -150,7 +150,7 @@ Validation of the Part IPN (Internal Part Number) field is exposed to custom plu ### Batch Codes -[Batch codes](../../stock/tracking.md#batch-codes) can be generated and/or validated by custom plugins. +[Batch codes](../../stock/traceability.md#batch-codes) can be generated and/or validated by custom plugins. #### Validate Batch Code @@ -184,7 +184,7 @@ The `generate_batch_code` method can be implemented to generate a new batch code Requirements for serial numbers can vary greatly depending on the application. Rather than attempting to provide a "one size fits all" serial number implementation, InvenTree allows custom serial number schemes to be implemented via plugins. -The default InvenTree [serial numbering system](../../stock/tracking.md#serial-numbers) uses a simple algorithm to validate and increment serial numbers. More complex behaviors can be implemented using the `ValidationMixin` plugin class and the following custom methods: +The default InvenTree [serial numbering system](../../stock/traceability.md#serial-numbers) uses a simple algorithm to validate and increment serial numbers. More complex behaviors can be implemented using the `ValidationMixin` plugin class and the following custom methods: #### Serial Number Validation diff --git a/docs/docs/sales/return_order.md b/docs/docs/sales/return_order.md index 79db9c1420..8e0b1ba9f9 100644 --- a/docs/docs/sales/return_order.md +++ b/docs/docs/sales/return_order.md @@ -145,7 +145,7 @@ Receiving a line item performs the following actions on the underlying stock ite * Sets the stock item status (defaulting to *Quarantined*) * Removes the *Customer* reference from the stock item * Clears any outstanding sales order allocations against the stock item -* Adds a tracking entry recording the return +* Records a [stock tracking](../stock/tracking.md) event for the return !!! info "Partial Returns" If a customer returns less than the full quantity of a non-serialized stock item, InvenTree automatically splits the returned quantity into a new stock item, leaving the remainder of the original stock item untouched (e.g. still recorded as sold to the customer). diff --git a/docs/docs/stock/adjust.md b/docs/docs/stock/adjust.md index c536bf0670..2c75360798 100644 --- a/docs/docs/stock/adjust.md +++ b/docs/docs/stock/adjust.md @@ -12,7 +12,7 @@ Stock adjustments can be accessed in any stock items table using the "Stock Opti ### Move Stock -Multiple stock items can be moved to a new location in a single operation. Each item is moved to the selected location, and a stock tracking entry is added to the stock item history. +Multiple stock items can be moved to a new location in a single operation. Each item is moved to the selected location, and a [stock tracking](./tracking.md) event is recorded against each stock item. {{ image("stock/stock_move.png", "Stock Move") }} diff --git a/docs/docs/stock/disassemble.md b/docs/docs/stock/disassemble.md index dd03877c35..2c5aa07016 100644 --- a/docs/docs/stock/disassemble.md +++ b/docs/docs/stock/disassemble.md @@ -44,7 +44,7 @@ The original stock item is never deleted as a result of disassembly - its quanti If the stock item being disassembled has other stock items [installed](../manufacturing/allocate.md#allocating-tracked-stock) within it (for example, tracked components that were installed during a build order), these installed items **must** be accounted for during disassembly: - Each installed item is matched against a BOM line item, based on the component part (including any [substitute](../manufacturing/bom.md#substitute-bom-line-items) or [variant](../part/index.md#assembly) parts allowed for that line) -- Matched installed items are *uninstalled* directly, rather than being discarded and re-created - this preserves the original stock item, including its own tracking history, batch code, and purchase price +- Matched installed items are *uninstalled* directly, rather than being discarded and re-created - this preserves the original stock item, including its own stock tracking history, batch code, and purchase price - The quantity requested for the matching BOM line is reduced by the quantity already covered by the installed item(s). A new stock item is only created for any remaining quantity - if the installed items fully cover the required quantity, no new stock item is created for that line - Any installed item which does *not* match one of the selected BOM lines is still uninstalled (it cannot be left "installed" inside a smaller or non-existent parent), but its quantity is **not** subtracted from any line @@ -65,10 +65,10 @@ If the original stock item has a recorded purchase price, and no explicit *Unit Disassembling a stock item generates a full audit trail: -- A `Disassembled into components` entry is added to the tracking history of the original stock item -- Each newly created component stock item receives a `Created from disassembly` tracking entry, referencing the original stock item +- A `Disassembled into components` event is added to the [stock tracking history](./tracking.md) of the original stock item +- Each newly created component stock item records a `Created from disassembly` event, referencing the original stock item - The *batch code* and source *purchase order* of the original stock item are copied directly to each generated component -- If the original stock item was generated by a build order, that build order cannot be directly copied to the new component (since the component was not actually built by that order) - instead, it is recorded as a reference within the `Created from disassembly` tracking entry +- If the original stock item was generated by a build order, that build order cannot be directly copied to the new component (since the component was not actually built by that order) - instead, it is recorded as a reference within the `Created from disassembly` event ### Purchasing Bundled Items diff --git a/docs/docs/stock/index.md b/docs/docs/stock/index.md index 83ec1e5f78..12916fd9fd 100644 --- a/docs/docs/stock/index.md +++ b/docs/docs/stock/index.md @@ -44,9 +44,9 @@ Refer to the [traceability](./traceability.md) page for more information on how ## Stock Tracking -Every time a *Stock Item* is adjusted, a *Stock Tracking* entry is automatically created. This ensures a complete history of the *Stock Item* is maintained as long as the item is in the system. +Every time a *Stock Item* is adjusted, a stock tracking event is automatically recorded in the [audit log](../concepts/audit.md). This ensures a complete history of the *Stock Item* is maintained. -Each stock tracking historical item records the user who performed the action. [Read more about stock tracking here](./tracking.md). +Each stock tracking event records the user who performed the action. [Read more about stock tracking here](./tracking.md). ## Stock Locations diff --git a/docs/docs/stock/tracking.md b/docs/docs/stock/tracking.md index 12ac65c48f..d412f0bc1f 100644 --- a/docs/docs/stock/tracking.md +++ b/docs/docs/stock/tracking.md @@ -4,47 +4,56 @@ title: Stock Tracking ## Stock Tracking -Stock tracking entries record the history of stock item adjustments, including the user who performed the action, the date of the action, and the quantity change. This allows users to maintain a complete history of stock item movements and adjustments over time. +Stock tracking records the history of stock item adjustments, including the user who performed the action, the date of the action, and the changes made to the stock item (e.g. quantity or location). This allows users to maintain a complete history of stock item movements and adjustments over time. + +Stock tracking history is recorded in the [audit log](../concepts/audit.md) - each stock operation is recorded as a named [event](../concepts/audit.md#events) against each affected stock item. ### Tracking Events -Stock tracking entries are created automatically whenever a stock item is adjusted, either through manual adjustments or through automated processes such as order fulfillment or build completion. +Stock tracking events are recorded automatically whenever a stock item is adjusted, either through manual adjustments or through automated processes such as order fulfillment or build completion. -Some examples of events that may trigger stock tracking entries include: +Some examples of operations that record stock tracking events include: - Manual stock adjustments (e.g. correcting inventory counts) - Creation of new stock items (e.g. receiving new inventory) - Allocation of stock items to orders (e.g. shipping items against sales orders) - Consumption of stock items during build processes (e.g. using items to complete a build order) -### Tracking Entry Types +### Tracking Event Types -Each stock tracking entry records a specific *type*, indicating which event triggered it: +Each stock tracking event records a specific *type*, indicating which operation triggered it: {{ statuscodes("StockHistoryCode") }} +!!! info "Legacy Event Types" + The *Legacy stock tracking entry*, *Edited stock item* and *Assigned serial number* event types are only present in stock history recorded by older versions of InvenTree. Edits to a stock item (e.g. changing the status) are now recorded as regular updates in the audit log, and serial numbers are recorded as part of the creation of the stock item. + ## Viewing Stock Tracking History -There are multiple ways to view the stock tracking history for a particular stock item or part via the user interface. +Stock tracking events are recorded in the [audit log](../concepts/audit.md), along with any other changes made to a stock item. Each event records the user who performed the action, any notes provided, and additional information about the event (such as the quantity added or removed, or the order which the item was received against). -### Stock Item Tracking History +!!! warning "Audit Log Required" + Stock tracking history is recorded in the audit log. If the audit log is disabled (via the `Enable Audit Log` system setting), **no stock tracking history is recorded**. Refer to the [audit log documentation](../concepts/audit.md#settings) for more information. -The stock tracking history for a particular stock item can be viewed on the *Stock Item Detail* page, under the *Stock Tracking* tab: +### Stock Item History -{{ image("stock/stock_item_tracking_history.png", title="Stock tracking tab") }} +The stock tracking history for a particular stock item is displayed in the *History* panel of the *Stock Item Detail* page: -This view displays all tracking entries associated with the particular stock item. +{{ image("stock/stock_item_history.png", "Stock item history") }} -### Part Tracking History +Each entry displays the type of event, along with any changes made to the stock item, and any additional information recorded against the event (such as notes, quantities, or links to related orders and stock items). -Additionally, the stock tracking history for a particular part can be viewed on the *Part Detail* page, under the *Stock History* tab: +### Part History -{{ image("stock/part_tracking_history.png", title="Part stock tracking history") }} +The history of all stock items for a particular part is displayed in the *History* panel of the *Part Detail* page, along with any changes made to the part itself: -This view displays all tracking entries associated with any stock item linked to the particular part. +{{ image("stock/part_stock_history.png", "Part stock history") }} + +!!! info "Show Change History" + The *History* panel is only displayed if the *Show Change History* user setting is enabled. Refer to the [audit log documentation](../concepts/audit.md#viewing-change-history) for more information. !!! info "Deleted Stock Items" - Even if a stock item is deleted from the system, the associated stock tracking entries are retained for historical reference. They will be visible in the part tracking history, but not in the stock item tracking history (as the stock item itself has been deleted). + Even if a stock item is deleted from the system, the associated history is retained for historical reference. It is visible in the history of the part, but not in the history of the stock item (as the stock item itself has been deleted). ## Installed Stock Items @@ -73,9 +82,7 @@ Additionally, installed items are automatically uninstalled when the parent item ## Stock Tracking Settings -There are a number of configuration options available for controlling the behavior of stock tracking functionality in the [system settings view](../settings/global.md): +Stock tracking history is recorded in the audit log, and is subject to the [audit log settings](../concepts/audit.md#settings). For example, old stock tracking history can be automatically removed using the {{ globalsetting("AUDIT_LOG_DELETE_OLD_ENTRIES", short=True) }} setting. -| Name | Description | Default | Units | -| ---- | ----------- | ------- | ----- | -{{ globalsetting("STOCK_TRACKING_DELETE_OLD_ENTRIES") }} -{{ globalsetting("STOCK_TRACKING_DELETE_DAYS") }} +!!! info "Audit Log Retention" + The *Delete Old Stock Tracking Entries* and *Stock Tracking Deletion Interval* settings have been removed. The retention of stock tracking history is now controlled by the audit log settings. diff --git a/src/backend/InvenTree/InvenTree/api_version.py b/src/backend/InvenTree/InvenTree/api_version.py index be5ad33454..d2a6740b80 100644 --- a/src/backend/InvenTree/InvenTree/api_version.py +++ b/src/backend/InvenTree/InvenTree/api_version.py @@ -1,11 +1,20 @@ """InvenTree API version information.""" # InvenTree API version -INVENTREE_API_VERSION = 565 +INVENTREE_API_VERSION = 566 """Increment this API version number whenever there is a significant change to the API that any clients need to know about.""" INVENTREE_API_TEXT = """ +v566 -> 2026-10-25 : https://github.com/inventree/InvenTree/pull/13101 + - Audit log API (/api/audit/) is available to all authenticated users (entries are filtered by model permissions) + - Adds "event_type", "event_label", "notes" and "deltas" fields to the audit log API + - Adds "event_type" filter to the audit log API + - Stock item history is recorded in the audit log (StockItem events, with event_type values from StockHistoryCode) + - Breaking: Removes the stock tracking API endpoints (/api/stock/track/, /api/stock/track//, /api/stock/track/status/) - use /api/audit/ instead + - Breaking: Removes the "tracking_items" field from the StockItem API + - Breaking: Removes the "copy_history" option when duplicating a StockItem + v565 -> 2026-10-24 : https://github.com/inventree/InvenTree/pull/13069 - Adds read-only audit log API endpoints (/api/audit/) - OPTIONS metadata includes a "filters" entry, listing the available choices for choice-based list filters diff --git a/src/backend/InvenTree/InvenTree/apps.py b/src/backend/InvenTree/InvenTree/apps.py index 1a7687d0e4..10560122ce 100644 --- a/src/backend/InvenTree/InvenTree/apps.py +++ b/src/backend/InvenTree/InvenTree/apps.py @@ -117,6 +117,7 @@ class InvenTreeConfig(AppConfig): 'label.tasks.cleanup_old_label_outputs', 'report.tasks.cleanup_old_report_outputs', 'stock.tasks.delete_old_stock_items', + 'stock.tasks.delete_old_stock_tracking', ] try: diff --git a/src/backend/InvenTree/InvenTree/management/commands/bulkloaddata.py b/src/backend/InvenTree/InvenTree/management/commands/bulkloaddata.py index d5e8f43274..8bd4ded893 100644 --- a/src/backend/InvenTree/InvenTree/management/commands/bulkloaddata.py +++ b/src/backend/InvenTree/InvenTree/management/commands/bulkloaddata.py @@ -31,7 +31,7 @@ def _suspend_auto_now(model): use the fixture's own value. bulk_create() has no equivalent of that raw=True path - it always calls Field.pre_save(), which unconditionally stamps such fields with timezone.now(), discarding whatever value the - fixture provided (e.g. stock.StockItemTracking.date). Clearing the flags + fixture provided (e.g. stock.StockItem.creation_date). Clearing the flags for the duration of the bulk_create() call restores the raw=True behaviour for this model. """ @@ -65,7 +65,7 @@ class Command(LoadDataCommand): auto_now / auto_now_add fields are a partial exception: they are temporarily disabled around the bulk_create() call (see _suspend_auto_now()) so that fixture-provided values for fields such as - stock.StockItemTracking.date survive the import, matching what a + stock.StockItem.creation_date survive the import, matching what a normal, non-bulk fixture load does via raw=True. - Multi-table inheritance is not supported by bulk_create() and will fail loudly rather than being silently mishandled. Natural-key foreign key / @@ -74,7 +74,7 @@ class Command(LoadDataCommand): sped up by caching each resolved natural key for the life of the command (see _cached_natural_keys()), since 'export_records' uses --natural-foreign and a large fixture can have many rows referencing the - same handful of natural-keyed objects (e.g. stock.StockItemTracking.user + same handful of natural-keyed objects (e.g. audit.AuditLogEntry.actor -> auth.User) - without caching, each one costs a separate DB query. Based on the django forum thread: @@ -135,7 +135,7 @@ class Command(LoadDataCommand): Django's deserializer (deserialize_fk_value) issues a fresh DB query every time it resolves a natural-key FK reference, with no caching of its own. A fixture with many rows referencing the same handful of - natural-keyed objects (e.g. thousands of stock.StockItemTracking rows + natural-keyed objects (e.g. thousands of audit.AuditLogEntry rows all pointing at a few auth.User accounts) would otherwise cost one query per row instead of one query per distinct value. """ diff --git a/src/backend/InvenTree/InvenTree/models.py b/src/backend/InvenTree/InvenTree/models.py index 7f880c053b..017c9a089e 100644 --- a/src/backend/InvenTree/InvenTree/models.py +++ b/src/backend/InvenTree/InvenTree/models.py @@ -547,6 +547,8 @@ class InvenTreeAuditMixin: AUDIT_VIEW_RESTRICTION: Restrict reading audit history to 'staff' or 'superuser' users (or None) AUDIT_REQUIRE_PARENT_PERMISSION: Entries are only visible to users who can view the *parent* model (for models whose access is determined by the linked object, e.g. attachments) + AUDIT_EVENT_TYPES: Enumeration of named event types which can be recorded against this model + (e.g. StockHistoryCode) - see audit.helpers.audit_event """ AUDIT_INCLUDE_FIELDS: tuple[str, ...] = () @@ -556,6 +558,7 @@ class InvenTreeAuditMixin: AUDIT_PARENT_FIELD: Optional[str] = None AUDIT_VIEW_RESTRICTION: Optional[str] = None AUDIT_REQUIRE_PARENT_PERMISSION: bool = False + AUDIT_EVENT_TYPES: Optional[type] = None def get_audit_parent(self): """Return the "parent" object for audit log entries against this instance. diff --git a/src/backend/InvenTree/InvenTree/serializers.py b/src/backend/InvenTree/InvenTree/serializers.py index 4c3d98c4d5..20a94b36c2 100644 --- a/src/backend/InvenTree/InvenTree/serializers.py +++ b/src/backend/InvenTree/InvenTree/serializers.py @@ -1232,7 +1232,7 @@ def apply_duplicate_copy_options( at each call site rather than a new copy-pasted `if duplicate.get(...): instance.copy_..._ from(...)` block. Any duplicate flag whose target method doesn't follow the copy__from() naming convention (e.g. Part's copy_bom/copy_image/copy_tests, or - StockItem's copy_history/copy_tests) must still be handled separately by the caller. + StockItem's copy_tests) must still be handled separately by the caller. Arguments: instance: The newly created instance to copy data onto diff --git a/src/backend/InvenTree/InvenTree/tasks.py b/src/backend/InvenTree/InvenTree/tasks.py index c21b538d67..2110fe9475 100644 --- a/src/backend/InvenTree/InvenTree/tasks.py +++ b/src/backend/InvenTree/InvenTree/tasks.py @@ -346,7 +346,7 @@ def batch_offload_tasks(): Nesting is not supported: a nested batch_offload_tasks() call reuses the outer batch, and only the outermost call schedules a flush. - This mirrors plugin.base.event.events.batch_events() and stock.models.batch_tracking_entries() + This mirrors plugin.base.event.events.batch_events() and audit.helpers.write_batched() - see batch_events()'s docstring for the reasoning behind the on-commit flush and the context-local (rather than parameter-based) design. diff --git a/src/backend/InvenTree/InvenTree/test_commands.py b/src/backend/InvenTree/InvenTree/test_commands.py index 5414c6bde9..139921ce57 100644 --- a/src/backend/InvenTree/InvenTree/test_commands.py +++ b/src/backend/InvenTree/InvenTree/test_commands.py @@ -165,9 +165,8 @@ class CommandTestCase(TestCase): """bulk_create() must not overwrite fixture values for auto_now_add fields. Covers both an auto_now_add DateTimeField (BarcodeScanResult.timestamp, - StockItem.creation_date, StockItemTracking.date) and an auto_now_add - DateField (Part.creation_date) - StockItem/StockItemTracking are also - the models the bug was originally reported against. + StockItem.creation_date) and an auto_now_add DateField (Part.creation_date) + - StockItem is also the model the bug was originally reported against. """ import datetime @@ -176,8 +175,7 @@ class CommandTestCase(TestCase): from common.models import BarcodeScanResult from part.models import Part - from stock.models import StockItem, StockItemTracking - from stock.status_codes import StockHistoryCode + from stock.models import StockItem # JSON fixtures only round-trip datetimes to millisecond precision # (DjangoJSONEncoder truncates microseconds) - use a value already at @@ -207,27 +205,13 @@ class CommandTestCase(TestCase): item.refresh_from_db() self.assertEqual(item.creation_date, original_timestamp) - tracking = StockItemTracking.objects.create( - item=item, tracking_type=StockHistoryCode.CREATED - ) - tracking.date = original_timestamp - tracking.save() - tracking.refresh_from_db() - self.assertEqual(tracking.date, original_timestamp) - - pks = { - 'barcode': entry.pk, - 'part': part.pk, - 'item': item.pk, - 'tracking': tracking.pk, - } + pks = {'barcode': entry.pk, 'part': part.pk, 'item': item.pk} # Serialize parent-before-child, so bulkloaddata's per-model # bulk_create() calls happen in an order that satisfies FK constraints. - data = serializers.serialize('json', [entry, part, item, tracking]) + data = serializers.serialize('json', [entry, part, item]) # Use queryset deletes - Part.delete() refuses to delete an active part - StockItemTracking.objects.filter(pk=tracking.pk).delete() StockItem.objects.filter(pk=item.pk).delete() Part.objects.filter(pk=part.pk).delete() BarcodeScanResult.objects.filter(pk=entry.pk).delete() @@ -247,10 +231,7 @@ class CommandTestCase(TestCase): reloaded_item = StockItem.objects.get(pk=pks['item']) self.assertEqual(reloaded_item.creation_date, original_timestamp) - reloaded_tracking = StockItemTracking.objects.get(pk=pks['tracking']) - self.assertEqual(reloaded_tracking.date, original_timestamp) finally: - StockItemTracking.objects.filter(pk=pks['tracking']).delete() StockItem.objects.filter(pk=pks['item']).delete() Part.objects.filter(pk=pks['part']).delete() BarcodeScanResult.objects.filter(pk=pks['barcode']).delete() diff --git a/src/backend/InvenTree/InvenTree/unit_test.py b/src/backend/InvenTree/InvenTree/unit_test.py index 572f8930cc..6f5044bf7f 100644 --- a/src/backend/InvenTree/InvenTree/unit_test.py +++ b/src/backend/InvenTree/InvenTree/unit_test.py @@ -210,6 +210,48 @@ def findOffloadedEvent( ) +def stock_history(item, **filters): + """Return the audit log entries (stock history) for a StockItem, oldest first. + + Arguments: + item: The StockItem instance (or primary key) - the item may have since been deleted + **filters: Additional filters to apply to the queryset (e.g. event_type=...) + """ + from django.contrib.contenttypes.models import ContentType + + from audit.models import AuditLogEntry + from stock.models import StockItem + + pk = getattr(item, 'pk', item) + + return AuditLogEntry.objects.filter( + content_type=ContentType.objects.get_for_model(StockItem), + object_pk=str(pk), + **filters, + ).order_by('pk') + + +def stock_history_count(**filters) -> int: + """Return the total number of audit log entries recorded for all StockItems. + + Arguments: + **filters: Additional filters to apply (e.g. event_type=...) + """ + from django.contrib.contenttypes.models import ContentType + + from audit.models import AuditLogEntry + from stock.models import StockItem + + return AuditLogEntry.objects.filter( + content_type=ContentType.objects.get_for_model(StockItem), **filters + ).count() + + +def stock_events(item, **filters): + """Return the named stock tracking events recorded for a StockItem, oldest first.""" + return stock_history(item, event_type__isnull=False, **filters) + + class UserMixin: """Mixin to setup a user and login for tests. diff --git a/src/backend/InvenTree/audit/api.py b/src/backend/InvenTree/audit/api.py index c45aef7bdd..ca1a5a7c47 100644 --- a/src/backend/InvenTree/audit/api.py +++ b/src/backend/InvenTree/audit/api.py @@ -22,7 +22,7 @@ from audit.permissions import ( from common.filters import determine_content_type from InvenTree.filters import SEARCH_ORDER_FILTER from InvenTree.helpers_api import InvenTreeApiRouter -from InvenTree.permissions import IsAdminOrAdminScope +from InvenTree.permissions import IsAuthenticatedOrReadScope def audited_model_type_choices(user=None) -> list[tuple[str, str]]: @@ -83,7 +83,7 @@ class AuditLogFilter(rest_filters.FilterSet): """Metaclass options.""" model = AuditLogEntry - fields = ['action', 'actor', 'cid'] + fields = ['action', 'actor', 'cid', 'event_type'] model_type = ModelTypeChoiceFilter( choices=audited_model_type_choices, @@ -178,9 +178,9 @@ class AuditLogViewSet(viewsets.ReadOnlyModelViewSet): ) serializer_class = audit.serializers.AuditLogEntrySerializer - # The audit log is only available to staff users. - # Entries are *additionally* filtered by the user's permissions on each audited model. - permission_classes = [IsAdminOrAdminScope] + # Entries are filtered by the user's permissions on each audited model + # (some models are further restricted to staff users, see audit.registry.AUDIT_VIEW_RESTRICTIONS) + permission_classes = [IsAuthenticatedOrReadScope] filterset_class = AuditLogFilter filter_backends = SEARCH_ORDER_FILTER diff --git a/src/backend/InvenTree/audit/context.py b/src/backend/InvenTree/audit/context.py index 2426d082aa..c988c1bbf8 100644 --- a/src/backend/InvenTree/audit/context.py +++ b/src/backend/InvenTree/audit/context.py @@ -18,6 +18,9 @@ _audit_request = ContextVar('inventree_audit_request', default=None) _audit_cid = ContextVar('inventree_audit_cid', default=None) _audit_source = ContextVar('inventree_audit_source', default=None) _audit_batch = ContextVar('inventree_audit_batch', default=None) +_audit_batch_cid = ContextVar('inventree_audit_batch_cid', default=None) +_audit_events = ContextVar('inventree_audit_events', default=None) +_audit_flush_event = ContextVar('inventree_audit_flush_event', default=None) @contextlib.contextmanager @@ -26,6 +29,9 @@ def audit_batch(): Nested contexts share the outermost list. + The correlation ID is resolved once for the batch (see get_cid), + as resolving it for every entry is expensive for large batches. + Yields: The list of collected entries, which the caller is responsible for writing (or None for a nested context, as there is nothing to write). @@ -36,10 +42,12 @@ def audit_batch(): entries = [] token = _audit_batch.set(entries) + cid_token = _audit_batch_cid.set(resolve_cid()) try: yield entries finally: + _audit_batch_cid.reset(cid_token) _audit_batch.reset(token) @@ -48,6 +56,151 @@ def get_batch() -> Optional[list]: return _audit_batch.get() +class PendingEvent: + """A named event which is waiting to be attached to an audit entry (see audit.helpers.audit_event).""" + + def __init__(self, instance, event_type: int, notes=None, deltas=None, actor=None): + """Initialize the pending event.""" + self.instance = instance + self.event_type = event_type + self.notes = notes + self.deltas = deltas + self.actor = actor + self.consumed = False + + +def instance_key(instance) -> Optional[tuple]: + """Return a key which identifies the database row of an instance (or None if unsaved).""" + if instance.pk is None: + return None + + return (type(instance), instance.pk) + + +class EventRegistry: + """The pending events within an audit_event() context. + + Events are indexed by instance (identity), and by database row (model and primary key), + so that matching events can be found efficiently for large numbers of events. + """ + + def __init__(self): + """Initialize an empty registry.""" + self.counter = 0 + self.by_id: dict[int, list] = {} + self.by_key: dict[tuple, list] = {} + + def add(self, event: PendingEvent) -> None: + """Add an event to the registry.""" + self.counter += 1 + event.order = self.counter + + self.by_id.setdefault(id(event.instance), []).append(event) + + if key := instance_key(event.instance): + self.by_key.setdefault(key, []).append(event) + + def remove(self, event: PendingEvent) -> None: + """Remove an event from the registry.""" + for index, key in [ + (self.by_id, id(event.instance)), + (self.by_key, instance_key(event.instance)), + ]: + if key is not None and event in (events := index.get(key, [])): + events.remove(event) + + if not events: + del index[key] + + def find(self, instance) -> Optional[PendingEvent]: + """Return the first (unconsumed) pending event for the provided instance. + + Instances are matched by identity, or by model and primary key + (e.g. if the same database row is saved via a different instance). + """ + candidates = list(self.by_id.get(id(instance), [])) + + if key := instance_key(instance): + candidates += self.by_key.get(key, []) + + candidates = [event for event in candidates if not event.consumed] + + return min(candidates, key=lambda event: event.order, default=None) + + +@contextlib.contextmanager +def pending_events(events: list): + """Register the provided events as pending for the duration of the context.""" + registry = _audit_events.get() + token = None + + if registry is None: + registry = EventRegistry() + token = _audit_events.set(registry) + + for event in events: + registry.add(event) + + try: + yield + finally: + for event in events: + registry.remove(event) + + if token is not None: + _audit_events.reset(token) + + +def has_pending_event(instance) -> bool: + """Return True if there is a pending (unconsumed) event for the provided instance.""" + registry = _audit_events.get() + return registry is not None and registry.find(instance) is not None + + +@contextlib.contextmanager +def flushing_event(event: PendingEvent): + """Consume the provided event for any entry recorded for its instance within this context. + + Used when writing an entry for a specific event (see audit.helpers.write_event_entry), + which may not be the first pending event for the instance. + """ + token = _audit_flush_event.set(event) + + try: + yield + finally: + _audit_flush_event.reset(token) + + +def consume_event(instance) -> Optional[PendingEvent]: + """Return (and consume) the first pending event for the provided instance, if any.""" + event = _audit_flush_event.get() + + if ( + event is not None + and not event.consumed + and ( + event.instance is instance + or ( + (key := instance_key(instance)) is not None + and key == instance_key(event.instance) + ) + ) + ): + event.consumed = True + return event + + registry = _audit_events.get() + + if registry is None: + return None + + if event := registry.find(instance): + event.consumed = True + + return event + + @contextlib.contextmanager def audit_request(request): """Track the provided request (and a new correlation ID) for the duration of the context.""" @@ -131,6 +284,17 @@ def end_worker_task() -> None: def get_cid() -> Optional[str]: """Return the current correlation ID (used as AUDITLOG_CID_GETTER). + Within an audit_batch() context, the ID resolved when the batch was entered is returned. + """ + if cid := _audit_batch_cid.get(): + return cid + + return resolve_cid() + + +def resolve_cid() -> Optional[str]: + """Resolve the current correlation ID. + - Within a request: the request ID bound by django-structlog, so that audit entries can be matched against log entries (and the X-Request-ID header) for the request. Note: django-structlog uses the incoming 'X-Request-ID' header if provided diff --git a/src/backend/InvenTree/audit/helpers.py b/src/backend/InvenTree/audit/helpers.py index dfcdfe6b03..3c37539bbd 100644 --- a/src/backend/InvenTree/audit/helpers.py +++ b/src/backend/InvenTree/audit/helpers.py @@ -1,11 +1,15 @@ -"""Helper functions for recording audit entries for bulk database operations. +"""Helper functions for recording audit entries. -QuerySet.update() (and bulk_create / bulk_update) bypass model signals, -so changes made this way are not recorded by django-auditlog. -Code which modifies audited models in bulk should use these helpers instead. +- Named events (with notes and deltas) can be recorded against an instance (audit_event / log_event) +- QuerySet.update() (and bulk_create / bulk_update) bypass model signals, + so changes made this way are not recorded by django-auditlog. + Code which modifies audited models in bulk should use the bulk helpers instead. """ import contextlib +import datetime +from decimal import Decimal +from typing import Optional from django.conf import settings from django.core.exceptions import FieldDoesNotExist @@ -14,6 +18,7 @@ from django.db.models.signals import pre_save from auditlog.context import auditlog_disabled from auditlog.registry import auditlog +from auditlog.signals import pre_log def is_audited(model) -> bool: @@ -108,7 +113,13 @@ def audited_update(queryset, **values) -> int: with transaction.atomic(): if connection.features.has_select_for_update: - old_rows = list(queryset.select_for_update()) + # Only lock rows of the model itself (related rows may be joined via select_related) + if connection.features.has_select_for_update_of: + locked = queryset.select_for_update(of=('self',)) + else: + locked = queryset.select_for_update() + + old_rows = list(locked) else: old_rows = list(queryset) @@ -125,6 +136,9 @@ def audited_update(queryset, **values) -> int: with write_batched(): for old in old_rows: if new := new_rows.get(old.pk): + # Retain cached related objects (e.g. from select_related) + copy_related_cache(old, new) + _create_log_entry( action=AuditLogEntry.Action.UPDATE, instance=new, @@ -210,9 +224,6 @@ def audited_bulk_create(model, objs: list) -> list: Returns: list: The created objects, as fetched from the database (in insertion order) """ - from auditlog.receivers import _create_log_entry - - from audit.models import AuditLogEntry from InvenTree.helpers_db import bulk_create_and_fetch # Required by bulk_create_and_fetch() - checked for all models (not only audited ones), @@ -241,16 +252,176 @@ def audited_bulk_create(model, objs: list) -> list: obj._state.adding = False obj._state.db = instance._state.db - if is_audited(model): - with write_batched(): - for instance in created: - _create_log_entry( - action=AuditLogEntry.Action.CREATE, - instance=instance, - sender=model, - diff_old=None, - diff_new=instance, - use_json_for_changes=settings.AUDITLOG_STORE_JSON_CHANGES, - ) + record_created(model, created) return created + + +def record_created(model, instances) -> None: + """Record a CREATE audit entry for each of the provided (newly created) instances. + + For instances which have been created in bulk (bypassing model signals), + e.g. via InvenTree.helpers_db.bulk_create_and_fetch(). + The entries are written with a single bulk insert. + + Note: Any pending events for the instances (see audit_event) are attached to the entries. + """ + from auditlog.receivers import _create_log_entry + + from audit.models import AuditLogEntry + + if not is_audited(model): + return + + with transaction.atomic(), write_batched(): + for instance in instances: + _create_log_entry( + action=AuditLogEntry.Action.CREATE, + instance=instance, + sender=model, + diff_old=None, + diff_new=instance, + use_json_for_changes=settings.AUDITLOG_STORE_JSON_CHANGES, + ) + + +def audit_reference(obj) -> dict: + """Return a reference to a model instance, for recording in event deltas. + + The string representation is recorded at the time of the event, + so that the reference remains meaningful if the object is later changed or deleted. + """ + return { + 'model_type': f'{obj._meta.app_label}.{obj._meta.model_name}', + 'pk': obj.pk, + 'str': str(obj), + } + + +def serialize_deltas(value): + """Convert event deltas into a JSON serializable form. + + - Model instances are converted to references (see audit_reference) + - Decimal values are converted to float + - Dates are converted to ISO format strings + """ + from django.db import models + + if isinstance(value, models.Model): + return audit_reference(value) + + if isinstance(value, dict): + return {str(key): serialize_deltas(val) for key, val in value.items()} + + if isinstance(value, (list, tuple, set)): + return [serialize_deltas(val) for val in value] + + if isinstance(value, Decimal): + return float(value) + + if isinstance(value, (datetime.date, datetime.datetime)): + return value.isoformat() + + return value + + +def validate_event_type(model, event_type: int) -> int: + """Ensure that the event type is valid for the given model.""" + from audit.registry import event_type_choices + + if event_type is None: + raise ValueError('An event type must be provided') + + if int(event_type) not in event_type_choices(model): + raise ValueError( + f"Invalid audit event type '{event_type}' for model {model._meta.label}" + ) + + return int(event_type) + + +@contextlib.contextmanager +def audit_event( + instance, event_type: Optional[int], notes: str = '', deltas=None, actor=None +): + """Record a named event against an instance. + + The first audit entry recorded for the instance within this context + (e.g. when the instance is saved) carries the event type, notes and deltas. + + If no entry is recorded for the instance (e.g. no fields were changed, or the + instance was not saved), an entry is created for the event when the context exits. + + If no event type is provided, the notes / deltas / actor are attached to any entry + recorded for the instance within this context - but no entry is created otherwise. + + Contexts can be nested, to record events against multiple instances. + + Arguments: + instance: The (audited) model instance + event_type: The event type (must be defined in the AUDIT_EVENT_TYPES of the model), or None + notes: Optional notes associated with the event + deltas: Optional dict of additional information (e.g. references to related objects) + Deltas which are also recorded as field changes (of the same entry) are omitted. + actor: Optional user who performed the event (defaults to the user of the current request) + + Yields: + The pending event (consumed once it has been attached to an audit entry) + """ + from audit.context import PendingEvent, pending_events + from audit.models import NOTES_MAX_LENGTH + + if event_type is not None: + event_type = validate_event_type(type(instance), event_type) + + event = PendingEvent( + instance, + event_type, + notes=str(notes)[:NOTES_MAX_LENGTH] if notes else None, + deltas=serialize_deltas(deltas) if deltas else None, + actor=actor, + ) + + with pending_events([event]): + yield event + + # An entry is only created without any changes for a *named* event + if not event.consumed and event.event_type is not None: + write_event_entry(event) + + +def write_event_entry(event) -> None: + """Write an audit entry for an event which has not been attached to any other entry.""" + from audit.context import flushing_event + from audit.models import AuditLogEntry + + instance = event.instance + + # The instance was never saved (or has since been deleted) + if instance.pk is None or not is_audited(type(instance)): + return + + action = AuditLogEntry.Action.UPDATE + + # Apply the same checks as for any other entry (e.g. audit log disabled) + results = pre_log.send(type(instance), instance=instance, action=action) + + if any(result is False for _, result in results): + return + + # Attach this event to the entry - not the first pending event for the instance, + # which may belong to an (outer) context which has not yet exited + with flushing_event(event): + AuditLogEntry.objects.log_create( + instance, action=action, changes={}, force_log=True + ) + + +def log_event( + instance, event_type: int, notes: str = '', deltas=None, actor=None +) -> None: + """Record a named event against an instance, which does not involve saving the instance.""" + validate_event_type(type(instance), event_type) + + with audit_event(instance, event_type, notes=notes, deltas=deltas, actor=actor): + pass diff --git a/src/backend/InvenTree/audit/migrations/0003_event_type.py b/src/backend/InvenTree/audit/migrations/0003_event_type.py new file mode 100644 index 0000000000..ca07e50ebb --- /dev/null +++ b/src/backend/InvenTree/audit/migrations/0003_event_type.py @@ -0,0 +1,31 @@ +# Generated by Django 5.2.17 on 2026-10-25 02:32 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("audit", "0002_actor_username"), + ] + + operations = [ + migrations.AddField( + model_name="auditlogentry", + name="event_type", + field=models.PositiveIntegerField( + blank=True, null=True, verbose_name="Event Type" + ), + ), + migrations.AddField( + model_name="auditlogentry", + name="notes", + field=models.CharField( + blank=True, + help_text="Notes associated with this entry", + max_length=512, + null=True, + verbose_name="Notes", + ), + ), + ] diff --git a/src/backend/InvenTree/audit/models.py b/src/backend/InvenTree/audit/models.py index f17501873e..1e11d481b8 100644 --- a/src/backend/InvenTree/audit/models.py +++ b/src/backend/InvenTree/audit/models.py @@ -1,6 +1,7 @@ """Database models for the audit app.""" import os +from typing import Optional from django.contrib.contenttypes.models import ContentType from django.core.exceptions import FieldDoesNotExist @@ -14,6 +15,9 @@ import audit.context MASKED_VALUE = '***' +# Maximum length of the notes associated with an audit entry +NOTES_MAX_LENGTH = 512 + def get_audit_parent_reference(instance): """Return a (ContentType, pk) reference to the audit "parent" of an instance (or None).""" @@ -161,7 +165,13 @@ class AuditLogEntryManager(LogEntryManager): kwargs.setdefault('parent_content_type', parent_ct) kwargs.setdefault('parent_object_pk', parent_pk) - if 'actor' not in kwargs and (actor := audit.context.get_request_actor()): + # A pending named event for this instance (see audit.helpers.audit_event) + event = audit.context.consume_event(instance) + + # The actor is (in order of preference) provided explicitly, by the event, or by the request + if 'actor' not in kwargs and ( + actor := (event and event.actor) or audit.context.get_request_actor() + ): kwargs['actor'] = actor kwargs['actor_email'] = getattr(actor, 'email', None) @@ -179,12 +189,35 @@ class AuditLogEntryManager(LogEntryManager): kwargs['additional_data'] = {**(additional_data or {}), 'source': source} + # Attach the pending named event + if event: + kwargs['event_type'] = event.event_type + kwargs['notes'] = event.notes or None + + # Deltas which are already recorded as field changes are not duplicated + changes = kwargs.get('changes') + deltas = { + key: value + for key, value in (event.deltas or {}).items() + if not (isinstance(changes, dict) and key in changes) + } + + if deltas: + kwargs['additional_data'] = { + **(kwargs.get('additional_data') or {}), + 'deltas': deltas, + } + is_masked = getattr(instance, 'is_audit_masked', None) if callable(is_masked) and is_masked(): # Mask at write time - sensitive values must never reach the log table kwargs['changes'] = mask_changes(kwargs.get('changes')) kwargs['serialized_data'] = None + kwargs['notes'] = None + + if additional_data := kwargs.get('additional_data'): + additional_data.pop('deltas', None) return kwargs @@ -324,6 +357,29 @@ class AuditLogEntry(AbstractLogEntry): max_length=150, null=True, blank=True, verbose_name=_('Actor Username') ) + # Named event type (see InvenTreeAuditMixin.AUDIT_EVENT_TYPES) + event_type = models.PositiveIntegerField( + null=True, blank=True, verbose_name=_('Event Type') + ) + + notes = models.CharField( + max_length=NOTES_MAX_LENGTH, + null=True, + blank=True, + verbose_name=_('Notes'), + help_text=_('Notes associated with this entry'), + ) + + @property + def event_label(self) -> Optional[str]: + """Return the label for the event type of this entry (if any).""" + from audit.registry import event_type_label + + if self.event_type is None: + return None + + return event_type_label(self.content_type.model_class(), self.event_type) + @property def model_type(self) -> str: """Return the 'app_label.model' string for the audited model.""" diff --git a/src/backend/InvenTree/audit/registry.py b/src/backend/InvenTree/audit/registry.py index 49c1ee114f..92ffae4d5e 100644 --- a/src/backend/InvenTree/audit/registry.py +++ b/src/backend/InvenTree/audit/registry.py @@ -1,5 +1,8 @@ """Registration of audited models with django-auditlog.""" +import functools +from typing import Optional + from django.apps import apps import structlog @@ -103,3 +106,42 @@ def get_view_restriction(model): return restriction return AUDIT_VIEW_RESTRICTIONS.get(model._meta.label_lower) + + +def event_type_choices(model) -> dict[int, str]: + """Return a mapping of event type value -> label for the given model. + + Event types are defined by the AUDIT_EVENT_TYPES attribute of the model, + which may be a StatusCode enumeration or a Django Choices class. + """ + enum = getattr(model, 'AUDIT_EVENT_TYPES', None) + + if enum is None: + return {} + + return _enum_choices(enum) + + +@functools.cache +def _enum_choices(enum) -> dict[int, str]: + """Return a mapping of value -> label for an event type enumeration (cached). + + Note: Labels are returned as (lazy) translatable strings, + so that they are translated at the point of use. + """ + # StatusCode enumeration (e.g. StockHistoryCode) + if callable(getattr(enum, 'items', None)): + return dict(enum.items(custom=False)) + + # Django Choices class + return {int(key): label for key, label in enum.choices} + + +def event_type_label(model, event_type: int) -> Optional[str]: + """Return the label for an event type of the given model (or None if unknown).""" + if model is None or event_type is None: + return None + + label = event_type_choices(model).get(int(event_type)) + + return None if label is None else str(label) diff --git a/src/backend/InvenTree/audit/serializers.py b/src/backend/InvenTree/audit/serializers.py index 9ebbf1ea08..6e41f62c2f 100644 --- a/src/backend/InvenTree/audit/serializers.py +++ b/src/backend/InvenTree/audit/serializers.py @@ -81,6 +81,10 @@ class AuditLogEntrySerializer( 'actor', 'actor_detail', 'actor_username', + 'event_type', + 'event_label', + 'notes', + 'deltas', 'model_type', 'model_label', 'object_pk', @@ -115,6 +119,42 @@ class AuditLogEntrySerializer( read_only=True, allow_null=True, label=_('Parent Model Type') ) + event_label = serializers.CharField( + read_only=True, allow_null=True, label=_('Event') + ) + + deltas = serializers.SerializerMethodField(label=_('Deltas')) + + @extend_schema_field(serializers.JSONField(allow_null=True)) + def get_deltas(self, entry: AuditLogEntry) -> Optional[dict]: + """Return the deltas recorded against a named event. + + References to objects which the user cannot view are redacted. + """ + deltas = (entry.additional_data or {}).get('deltas') + + if not isinstance(deltas, dict): + return None + + return {key: self.redact_reference(value) for key, value in deltas.items()} + + def redact_reference(self, value): + """Redact a reference to a model instance, if the user cannot view the model.""" + from django.apps import apps + + if not (isinstance(value, dict) and 'model_type' in value and 'pk' in value): + return value + + try: + model = apps.get_model(value['model_type']) + except (LookupError, ValueError): + model = None + + if self.can_view_model(model): + return value + + return {**value, 'pk': None, 'str': None} + changes = serializers.SerializerMethodField(label=_('Changes')) changes_detail = OptionalField( @@ -297,6 +337,15 @@ class AuditLogEntrySerializer( for field in self.STAFF_ONLY_FIELDS: data.pop(field, None) + # Event deltas are provided (with redaction) via the 'deltas' field + if isinstance(data.get('additional_data'), dict): + additional_data = { + key: value + for key, value in data['additional_data'].items() + if key != 'deltas' + } + data['additional_data'] = additional_data or None + # Only expose the parent reference if the user may view the parent model if parent_ct := instance.parent_content_type: if not self.can_view_model(parent_ct.model_class()): diff --git a/src/backend/InvenTree/audit/tests.py b/src/backend/InvenTree/audit/tests.py index 631b3defa7..86c96e55f5 100644 --- a/src/backend/InvenTree/audit/tests.py +++ b/src/backend/InvenTree/audit/tests.py @@ -7,10 +7,12 @@ PurchaseOrder (parent) and PurchaseOrderLineItem (child) with the audit log. import contextlib from base64 import b64encode from datetime import timedelta +from decimal import Decimal from unittest import mock from django.contrib.auth import get_user_model from django.contrib.contenttypes.models import ContentType +from django.db import models, transaction from django.test import TestCase, override_settings from django.urls import reverse from django.utils import timezone @@ -18,6 +20,7 @@ from django.utils import timezone from auditlog.registry import auditlog import audit.context +from audit.helpers import write_batched from audit.models import MASKED_VALUE, AuditLogEntry, mask_changes from audit.registry import BASE_EXCLUDE_FIELDS, audited_models, register_model from audit.serializers import AuditLogEntrySerializer @@ -145,6 +148,7 @@ class AuditRegistryTest(TestCase): 'pricing.partinternalpricebreak', 'pricing.partpricing', 'pricing.partsellpricebreak', + 'stock.stockitem', ], ) @@ -269,6 +273,32 @@ class AuditModelTest(AuditTestMixin, TestCase): self.assertIsNone(audit.context.get_source()) self.assertIsNone(audit.context.get_cid()) + def test_batch_cid(self): + """The correlation ID is resolved once for an audit batch.""" + with audit.context.audit_source('task:test_task'): + cid = audit.context.get_cid() + + with ( + mock.patch.object( + audit.context, 'resolve_cid', wraps=audit.context.resolve_cid + ) as resolve, + transaction.atomic(), + write_batched(), + ): + for idx in range(3): + self.po.description = f'Batched {idx}' + self.po.save() + + self.assertEqual(resolve.call_count, 1) + + entries = self.entries_for(self.po, action=AuditLogEntry.Action.UPDATE) + + self.assertEqual(entries.count(), 3) + self.assertEqual({entry.cid for entry in entries}, {cid}) + + # Context is reset afterwards + self.assertIsNone(audit.context.get_cid()) + def test_disabled(self): """No entries are recorded when the audit log is disabled.""" set_global_setting('AUDIT_LOG_ENABLED', False) @@ -536,8 +566,8 @@ class AuditAPITest(AuditTestMixin, InvenTreeAPITestCase): len(line_entries({'model_type': 'purchaseorderlineitem'})), 1 ) - def test_staff_required(self): - """The audit log API is only available to staff users.""" + def test_non_staff(self): + """Non-staff users can view entries for models they have permission to view.""" self.patch_po('Edit') entry = self.entries_for(self.po).first() @@ -548,12 +578,24 @@ class AuditAPITest(AuditTestMixin, InvenTreeAPITestCase): self.assertEqual(data[0]['remote_addr'], '127.0.0.1') self.get(detail_url) - # Non-staff users are denied - even with view permission on the audited model self.user.is_staff = False self.user.save() - self.get(self.list_url(), expected_code=403) - self.get(detail_url, expected_code=403) + # Non-staff users can view the entries - but not the remote address + data = self.po_history() + self.assertGreater(len(data), 0) + self.assertNotIn('remote_addr', data[0]) + self.assertNotIn('remote_addr', self.get(detail_url).data) + + # ...but not without view permission on the audited model + self.clearRoles() + + self.assertEqual(self.po_history(), []) + self.get(detail_url, expected_code=404) + + # Anonymous users are denied + self.client.logout() + self.get(self.list_url(), expected_code=401) def test_view_restriction(self): """Models with a view restriction are hidden from users without that status.""" @@ -1259,3 +1301,305 @@ class AuditRetentionTest(InvenTreeAPITestCase): data = self.get(url).data self.assertIsNone(data['actor']) self.assertEqual(data['actor_username'], 'leaver') + + +class AuditTestEvents(models.IntegerChoices): + """Event types used for testing named audit events.""" + + MOVED = 10, 'Moved' + COUNTED = 20, 'Counted' + + +class AuditEventTest(InvenTreeAPITestCase): + """Tests for named audit events (see audit.helpers.audit_event).""" + + roles = ['purchase_order.view', 'purchase_order.change'] + + @classmethod + def setUpClass(cls): + """Define event types for the Company model.""" + cls.events_patch = mock.patch.object( + Company, 'AUDIT_EVENT_TYPES', AuditTestEvents + ) + cls.events_patch.start() + + super().setUpClass() + + @classmethod + def tearDownClass(cls): + """Remove event types from the Company model.""" + super().tearDownClass() + cls.events_patch.stop() + + @classmethod + def setUpTestData(cls): + """Create test data.""" + super().setUpTestData() + + cls.company = Company.objects.create(name='Event Company', is_supplier=True) + cls.part = Part.objects.create(name='Event Part', description='A part') + + def entries(self): + """Return all entries for the test company (oldest first).""" + return AuditLogEntry.objects.get_for_object(self.company).order_by('pk') + + def test_event_with_changes(self): + """The event is attached to the entry recorded when the instance is saved.""" + from audit.helpers import audit_event + + n = self.entries().count() + + with audit_event( + self.company, + AuditTestEvents.MOVED, + notes='Moved company', + deltas={'part': self.part, 'quantity': Decimal('1.5')}, + ): + self.company.description = 'Moved' + self.company.save() + + # A single entry, which records both the changes and the event + self.assertEqual(self.entries().count(), n + 1) + + entry = self.entries().last() + + self.assertEqual(entry.event_type, AuditTestEvents.MOVED) + self.assertEqual(entry.event_label, 'Moved') + self.assertEqual(entry.notes, 'Moved company') + self.assertEqual(entry.changes_dict['description'], ['', 'Moved']) + self.assertEqual( + entry.additional_data['deltas'], + { + 'part': { + 'model_type': 'part.part', + 'pk': self.part.pk, + 'str': str(self.part), + }, + 'quantity': 1.5, + }, + ) + + def test_event_without_changes(self): + """An event is recorded even if no fields are changed.""" + from audit.helpers import audit_event, log_event + + n = self.entries().count() + + # Saved, but not changed + with audit_event(self.company, AuditTestEvents.COUNTED, notes='Counted'): + self.company.save() + + # Not saved at all + log_event(self.company, AuditTestEvents.COUNTED, deltas={'quantity': 5}) + + entries = list(self.entries()[n:]) + + self.assertEqual(len(entries), 2) + + for entry in entries: + self.assertEqual(entry.action, AuditLogEntry.Action.UPDATE) + self.assertEqual(entry.event_type, AuditTestEvents.COUNTED) + self.assertEqual(entry.changes_dict, {}) + + self.assertEqual(entries[0].notes, 'Counted') + self.assertEqual(entries[1].additional_data['deltas'], {'quantity': 5}) + + def test_unnamed_event(self): + """Notes (without an event type) are attached to an entry - but do not create one.""" + from audit.helpers import audit_event + + n = self.entries().count() + + # No changes - no entry + with audit_event(self.company, None, notes='Nothing changed'): + self.company.save() + + self.assertEqual(self.entries().count(), n) + + with audit_event(self.company, None, notes='Changed'): + self.company.description = 'Unnamed' + self.company.save() + + [entry] = list(self.entries()[n:]) + + self.assertIsNone(entry.event_type) + self.assertIsNone(entry.event_label) + self.assertEqual(entry.notes, 'Changed') + + def test_no_changes_without_event(self): + """Saving an instance without changes (and without an event) is not recorded.""" + n = self.entries().count() + self.company.save() + self.assertEqual(self.entries().count(), n) + + def test_multiple_instances(self): + """Events can be recorded against multiple instances (one entry each).""" + from audit.helpers import audit_event + + other = Company.objects.create(name='Other Company') + + with ( + audit_event(self.company, AuditTestEvents.MOVED, deltas={'to': other}), + audit_event(other, AuditTestEvents.COUNTED, deltas={'from': self.company}), + ): + self.company.description = 'A' + self.company.save() + + entry = self.entries().last() + self.assertEqual(entry.event_type, AuditTestEvents.MOVED) + self.assertEqual(entry.additional_data['deltas']['to']['pk'], other.pk) + + # The other instance was not saved - so an entry is created for the event + other_entry = AuditLogEntry.objects.get_for_object(other).order_by('pk').last() + self.assertEqual(other_entry.event_type, AuditTestEvents.COUNTED) + self.assertEqual(other_entry.changes_dict, {}) + + def test_nested_events_same_instance(self): + """Nested events for the same (unsaved) instance each create an entry.""" + from contextlib import ExitStack + + from audit.helpers import audit_event + + n = self.entries().count() + + # Contexts exit in reverse order (newest event first) + with ExitStack() as stack: + for quantity in [1, 2]: + stack.enter_context( + audit_event( + self.company, + AuditTestEvents.COUNTED, + deltas={'quantity': quantity}, + ) + ) + + entries = list(self.entries()[n:]) + + self.assertEqual(len(entries), 2) + self.assertEqual( + sorted(entry.additional_data['deltas']['quantity'] for entry in entries), + [1, 2], + ) + + def test_new_instance(self): + """An event can be attached to the creation of a new instance.""" + from audit.helpers import audit_event + + company = Company(name='New Company') + + with audit_event(company, AuditTestEvents.MOVED, notes='Created'): + company.save() + + [entry] = AuditLogEntry.objects.get_for_object(company) + + self.assertEqual(entry.action, AuditLogEntry.Action.CREATE) + self.assertEqual(entry.event_type, AuditTestEvents.MOVED) + self.assertEqual(entry.notes, 'Created') + + def test_invalid_event_type(self): + """Event types must be defined by the model.""" + from audit.helpers import log_event + + with self.assertRaises(ValueError): + log_event(self.company, 999) + + with self.assertRaises(ValueError): + log_event(self.company, None) + + # Models without event types + with self.assertRaises(ValueError): + log_event(self.part, AuditTestEvents.MOVED) + + def test_error_not_recorded(self): + """No event entry is created if an error occurs within the context.""" + from audit.helpers import audit_event + + n = self.entries().count() + + with ( + self.assertRaises(RuntimeError), + audit_event(self.company, AuditTestEvents.MOVED), + ): + raise RuntimeError('Failed') + + self.assertEqual(self.entries().count(), n) + + def test_disabled(self): + """Events are not recorded when auditing is disabled.""" + from auditlog.context import disable_auditlog + + from audit.helpers import log_event + + n = self.entries().count() + + with disable_auditlog(): + log_event(self.company, AuditTestEvents.MOVED) + + set_global_setting('AUDIT_LOG_ENABLED', False) + self.addCleanup(set_global_setting, 'AUDIT_LOG_ENABLED', True) + + log_event(self.company, AuditTestEvents.MOVED) + + self.assertEqual(self.entries().count(), n) + + def test_batched(self): + """Event entries can be written in a batch.""" + from django.db import transaction + + from audit.helpers import log_event, write_batched + + n = self.entries().count() + + with transaction.atomic(), write_batched(): + log_event(self.company, AuditTestEvents.MOVED) + log_event(self.company, AuditTestEvents.COUNTED) + + # Not written until the batch is complete + self.assertEqual(self.entries().count(), n) + + self.assertEqual( + [entry.event_type for entry in self.entries()[n:]], + [AuditTestEvents.MOVED, AuditTestEvents.COUNTED], + ) + + def test_api(self): + """Event information is provided via the API (with redacted references).""" + from audit.helpers import log_event + + log_event( + self.company, + AuditTestEvents.MOVED, + notes='Via API', + deltas={'part': self.part, 'quantity': 3}, + ) + + entry = self.entries().last() + url = reverse('api-audit-log-list') + + # Filter by event type + data = self.get(url, {'event_type': AuditTestEvents.MOVED}).data + self.assertEqual([item['pk'] for item in data], [entry.pk]) + self.assertEqual( + self.get(url, {'event_type': AuditTestEvents.COUNTED}).data, [] + ) + + [item] = data + + self.assertEqual(item['event_type'], AuditTestEvents.MOVED) + self.assertEqual(item['event_label'], 'Moved') + self.assertEqual(item['notes'], 'Via API') + self.assertEqual(item['deltas']['quantity'], 3) + + # Deltas are only provided via the 'deltas' field + self.assertIsNone(item['additional_data']) + + # The user cannot view parts - the reference is redacted + self.assertEqual( + item['deltas']['part'], {'model_type': 'part.part', 'pk': None, 'str': None} + ) + + self.assignRole('part.view') + + item = self.get(url, {'event_type': AuditTestEvents.MOVED}).data[0] + self.assertEqual(item['deltas']['part']['pk'], self.part.pk) + self.assertEqual(item['deltas']['part']['str'], str(self.part)) diff --git a/src/backend/InvenTree/build/models.py b/src/backend/InvenTree/build/models.py index 02ee6b91f5..b23d9a35f0 100644 --- a/src/backend/InvenTree/build/models.py +++ b/src/backend/InvenTree/build/models.py @@ -2,6 +2,7 @@ import copy import decimal +from contextlib import ExitStack from typing import Optional, TypedDict from django.contrib.auth.models import User @@ -31,6 +32,13 @@ import part.models import report.mixins import stock.models import users.models +from audit.helpers import ( + audit_event, + audited_bulk_update, + copy_related_cache, + record_created, + write_batched, +) from build.events import BuildEvents from build.filters import annotate_allocated_quantity, annotate_required_quantity from build.status_codes import BuildStatus, BuildStatusGroups @@ -687,7 +695,7 @@ class Build( Notes: This unrolls what would otherwise be a per-BuildItem call, so that the underlying StockItem, - StockItemTracking, BuildLine and BuildItem writes can be batched into + audit log, BuildLine and BuildItem writes can be batched into a handful of bulk queries instead of several per build item. """ import part.tasks @@ -704,7 +712,15 @@ class Build( # Preselect related fields to avoid per-row database queries below build_items = list( build_items.select_related( - 'build_line', 'stock_item', 'stock_item__part', 'install_into' + 'build_line', + 'stock_item', + 'stock_item__part', + 'stock_item__location', + 'stock_item__purchase_order__supplier', + 'install_into', + 'install_into__part', + 'install_into__location', + 'install_into__purchase_order__supplier', ) ) @@ -843,8 +859,10 @@ class Build( # which the tracking entries and (for installed items) BuildItem records need below new_stock_item_data = [new_item for _, new_item, _ in split_items] - new_stock_items = bulk_create_and_fetch( - stock.models.StockItem, new_stock_item_data + # Evaluate the queryset immediately (into a list) - otherwise, indexing into it + # below would re-query the database once per split item + new_stock_items = list( + bulk_create_and_fetch(stock.models.StockItem, new_stock_item_data) ) # Back-populate the resolved primary keys onto the original 'new_item' objects @@ -853,68 +871,23 @@ class Build( # resolved pk too. On backends that can't return pks from bulk_create (MySQL), # 'new_stock_items' are freshly-fetched, distinct objects, so a plain list # replacement here would leave those other references with pk=None. - for i, (_stock_item, new_item, _quantity) in enumerate(split_items): + for i, (source_item, new_item, _quantity) in enumerate(split_items): new_item.pk = new_stock_items[i].pk + new_item._state.adding = False + + # Retain the cached related objects (avoid per-item lookups) + copy_related_cache(source_item, new_item) + copy_related_cache(source_item, new_stock_items[i]) - tracking_entries = [] split_events = [] install_events = [] # Split stock items for "split_items" - for source_item, new_item, quantity in split_items: - tracking_entries.append( - stock.models.StockItemTracking( - item_id=new_item.pk, - part_id=new_item.part_id, - tracking_type=StockHistoryCode.SPLIT_FROM_PARENT.value, - user=user, - notes=notes, - deltas={'stockitem': source_item.pk, 'quantity': float(quantity)}, - ) - ) - tracking_entries.append( - stock.models.StockItemTracking( - item_id=source_item.pk, - part_id=source_item.part_id, - tracking_type=StockHistoryCode.SPLIT_CHILD_ITEM.value, - user=user, - notes=notes, - deltas={ - 'removed': float(quantity), - 'quantity': float(source_item.quantity), - }, - ) - ) - + for source_item, new_item, _quantity in split_items: split_events.append(((), {'id': new_item.pk, 'parent': source_item.pk})) # Install stock items for "install_items" - for build_item, target_item, output, quantity in install_items: - tracking_entries.append( - stock.models.StockItemTracking( - item_id=target_item.pk, - part_id=target_item.part_id, - tracking_type=StockHistoryCode.INSTALLED_INTO_ASSEMBLY.value, - user=user, - notes=notes, - deltas={ - 'stockitem': output.pk, - 'quantity': float(quantity), - 'buildorder': self.pk, - }, - ) - ) - tracking_entries.append( - stock.models.StockItemTracking( - item_id=output.pk, - part_id=output.part_id, - tracking_type=StockHistoryCode.INSTALLED_CHILD_ITEM.value, - user=user, - notes=notes, - deltas={'stockitem': target_item.pk, 'quantity': float(quantity)}, - ) - ) - + for build_item, target_item, output, _quantity in install_items: install_events.append(( (), {'id': target_item.pk, 'assembly_id': output.pk}, @@ -923,27 +896,55 @@ class Build( # Ensure the build item points to the (possibly newly split) stock item build_item.stock_item = target_item - # Consume stock items for "consume_items" - for target_item, quantity in consume_items: - tracking_entries.append( - stock.models.StockItemTracking( - item_id=target_item.pk, - part_id=target_item.part_id, - tracking_type=StockHistoryCode.BUILD_CONSUMED.value, - user=user, - notes=notes, - deltas={'buildorder': self.pk, 'quantity': float(quantity)}, + # Record the operation in the audit log - a single entry for each stock item + with write_batched(), ExitStack() as events: + + def audit(item, code, deltas): + """Record a named event against the provided stock item.""" + events.enter_context( + audit_event(item, code, notes=notes, deltas=deltas, actor=user) ) + + for source_item, new_item, quantity in split_items: + audit( + source_item, + StockHistoryCode.SPLIT_CHILD_ITEM, + { + 'stockitem': new_item, + 'removed': quantity, + 'quantity': source_item.quantity, + }, + ) + + for _build_item, target_item, output, quantity in install_items: + audit( + target_item, + StockHistoryCode.INSTALLED_INTO_ASSEMBLY, + {'stockitem': output, 'quantity': quantity, 'buildorder': self}, + ) + audit( + output, + StockHistoryCode.INSTALLED_CHILD_ITEM, + {'stockitem': target_item, 'quantity': quantity}, + ) + + for target_item, quantity in consume_items: + audit( + target_item, + StockHistoryCode.BUILD_CONSUMED, + {'buildorder': self, 'quantity': quantity}, + ) + + # Newly split items are recorded as created (with the consume / install event) + record_created(stock.models.StockItem, new_stock_items) + + # Flush all StockItem field changes (quantity reductions, consumption, installs) + audited_bulk_update( + stock.models.StockItem, + list(seen_stock_items.values()), + ['quantity', 'consumed_by', 'location', 'belongs_to'], ) - # Flush all StockItem field changes (quantity reductions, consumption, installs) - stock.models.StockItem.objects.bulk_update( - seen_stock_items.values(), - ['quantity', 'consumed_by', 'location', 'belongs_to'], - ) - - stock.models.StockItemTracking.objects.bulk_create(tracking_entries) - # Update build lines for "seen_build_lines" BuildLine.objects.bulk_update(seen_build_lines.values(), ['consumed']) @@ -1181,12 +1182,22 @@ class Build( if serials: """Create multiple build outputs with a single quantity of 1.""" - # Create tracking entries for each item - tracking = [] allocations = [] + # Record a tracking event for each new output outputs = stock.models.StockItem._create_serial_numbers( serials, + tracking=lambda output: { + 'entry_type': StockHistoryCode.BUILD_OUTPUT_CREATED, + 'user': user, + 'deltas': { + 'quantity': 1, + 'buildorder': self, + 'batch': output.batch, + 'serial': output.serial, + 'location': location, + }, + }, part=self.part, build=self, batch=batch, @@ -1195,21 +1206,6 @@ class Build( ) for output in outputs: - # Generate a new historical tracking entry - if entry := output.add_tracking_entry( - StockHistoryCode.BUILD_OUTPUT_CREATED, - user, - deltas={ - 'quantity': 1, - 'buildorder': self.pk, - 'batch': output.batch, - 'serial': output.serial, - 'location': location.pk if location else None, - }, - commit=False, - ): - tracking.append(entry) - # Auto-allocate stock based on serial number if auto_allocate: if new_allocations := self.auto_allocate_tracked_output( @@ -1217,16 +1213,13 @@ class Build( ): allocations.extend(new_allocations) - # Bulk create tracking entries - stock.models.StockItemTracking.objects.bulk_create(tracking, batch_size=250) - # Generate stock allocations BuildItem.objects.bulk_create(allocations, batch_size=250) else: """Create a single build output of the given quantity.""" - output = stock.models.StockItem.objects.create( + output = stock.models.StockItem( quantity=quantity, location=location, part=self.part, @@ -1235,16 +1228,17 @@ class Build( is_building=True, ) - output.add_tracking_entry( + with output.tracking_event( StockHistoryCode.BUILD_OUTPUT_CREATED, user, deltas={ 'quantity': float(quantity), - 'buildorder': self.pk, + 'buildorder': self, 'batch': batch, - 'location': location.pk if location else None, + 'location': location, }, - ) + ): + output.save() # Ensure we return a QuerySet object here, too outputs = stock.models.StockItem.objects.filter(pk=output.pk) @@ -1384,32 +1378,31 @@ class Build( ) output.build = self - # Update build output item - output.is_building = False - output.status = StockStatus.REJECTED.value - output.location = location - output.save(add_note=False) - - allocated_items = output.items_to_install.all() - - # Complete or discard allocations - if not discard_allocations: - self.complete_allocations(build_items=allocated_items, user=user) - - # Delete allocations - allocated_items.all().delete() - - output.add_tracking_entry( + with output.tracking_event( StockHistoryCode.BUILD_OUTPUT_REJECTED, user, notes=notes, deltas={ 'quantity': float(quantity), - 'location': location.pk, + 'location': location, 'status': StockStatus.REJECTED.value, - 'buildorder': self.pk, + 'buildorder': self, }, - ) + ): + # Update build output item + output.is_building = False + output.status = StockStatus.REJECTED.value + output.location = location + output.save(add_note=False) + + allocated_items = output.items_to_install.all() + + # Complete or discard allocations + if not discard_allocations: + self.complete_allocations(build_items=allocated_items, user=user) + + # Delete allocations + allocated_items.all().delete() def can_complete_output( self, @@ -1527,20 +1520,19 @@ class Build( # Assign the stock status output.set_status(status) - output.save(add_note=False) - deltas = { 'status': status, - 'buildorder': self.pk, + 'buildorder': self, 'quantity': float(output.quantity), } if location: - deltas['location'] = location.pk + deltas['location'] = location - output.add_tracking_entry( + with output.tracking_event( StockHistoryCode.BUILD_OUTPUT_COMPLETED, user, notes=notes, deltas=deltas - ) + ): + output.save(add_note=False) trigger_event(BuildEvents.OUTPUT_COMPLETED, id=output.pk, build_id=self.pk) diff --git a/src/backend/InvenTree/build/serializers.py b/src/backend/InvenTree/build/serializers.py index 7f7708c09f..6d5804aae1 100644 --- a/src/backend/InvenTree/build/serializers.py +++ b/src/backend/InvenTree/build/serializers.py @@ -536,7 +536,7 @@ class BuildOutputScrapSerializer(serializers.Serializer): notes = serializers.CharField( label=_('Notes'), - max_length=stock_models.StockItemTracking.NOTES_MAX_LENGTH, + max_length=stock_models.StockItem.TRACKING_NOTES_MAX_LENGTH, help_text=_('Reason for scrapping build output(s)'), required=True, allow_blank=False, @@ -599,7 +599,7 @@ class BuildOutputCompleteSerializer(serializers.Serializer): notes = serializers.CharField( label=_('Notes'), - max_length=stock_models.StockItemTracking.NOTES_MAX_LENGTH, + max_length=stock_models.StockItem.TRACKING_NOTES_MAX_LENGTH, required=False, allow_blank=True, ) @@ -1786,7 +1786,7 @@ class BuildConsumeSerializer(serializers.Serializer): notes = serializers.CharField( label=_('Notes'), - max_length=stock_models.StockItemTracking.NOTES_MAX_LENGTH, + max_length=stock_models.StockItem.TRACKING_NOTES_MAX_LENGTH, help_text=_('Optional notes for the stock consumption'), required=False, allow_blank=True, diff --git a/src/backend/InvenTree/build/test_api.py b/src/backend/InvenTree/build/test_api.py index 9d954a4d1b..be97907e83 100644 --- a/src/backend/InvenTree/build/test_api.py +++ b/src/backend/InvenTree/build/test_api.py @@ -11,14 +11,20 @@ from django.urls import reverse from django_q.models import OrmQ from rest_framework import status +from audit.models import AuditLogEntry from build.models import Build, BuildItem, BuildLine from build.status_codes import BuildStatus from common.models import Note from common.settings import set_global_setting -from InvenTree.unit_test import InvenTreeAPITestCase +from InvenTree.unit_test import ( + InvenTreeAPITestCase, + stock_events, + stock_history, + stock_history_count, +) from part.models import BomItem, BomItemSubstitute, Part, PartTestTemplate from stock.events import StockEvents -from stock.models import StockItem, StockItemTracking, StockLocation, StockSortOrder +from stock.models import StockItem, StockLocation, StockSortOrder from stock.status_codes import StockHistoryCode, StockStatus @@ -2427,13 +2433,13 @@ class BuildConsumeTest(BuildAPITest): consume_build_stock(self.build.pk, items=items, expected=expected) - n_tracking = StockItemTracking.objects.count() + n_tracking = stock_history_count() n_stock = StockItem.objects.count() # Simulate redelivery of the exact same task payload consume_build_stock(self.build.pk, items=items, expected=expected) - self.assertEqual(StockItemTracking.objects.count(), n_tracking) + self.assertEqual(stock_history_count(), n_tracking) self.assertEqual(StockItem.objects.count(), n_stock) self.assertEqual(self.build.consumed_stock.count(), 3) @@ -2468,12 +2474,12 @@ class BuildConsumeTest(BuildAPITest): remaining = self.build.allocated_stock.get() self.assertEqual(remaining.quantity, 60) - n_tracking = StockItemTracking.objects.count() + n_tracking = stock_history_count() # Simulate redelivery of the exact same task payload consume_build_stock(self.build.pk, lines=lines, expected=expected) - self.assertEqual(StockItemTracking.objects.count(), n_tracking) + self.assertEqual(stock_history_count(), n_tracking) remaining.refresh_from_db() self.assertEqual(remaining.quantity, 60) @@ -2749,16 +2755,15 @@ class BuildConsumeTest(BuildAPITest): self.assertEqual(si.consumed_by, build) self.assertFalse( - StockItemTracking.objects.filter( - item=si, - tracking_type=StockHistoryCode.SPLIT_CHILD_ITEM.value, + stock_events( + si, event_type=StockHistoryCode.SPLIT_CHILD_ITEM ).exists() ) self.assertTrue( - StockItemTracking.objects.filter( - item=si, - tracking_type=StockHistoryCode.BUILD_CONSUMED.value, - deltas__buildorder=build.pk, + stock_events( + si, + event_type=StockHistoryCode.BUILD_CONSUMED, + additional_data__deltas__buildorder__pk=build.pk, ).exists() ) @@ -2786,18 +2791,15 @@ class BuildConsumeTest(BuildAPITest): self.assertEqual(consumed_item.quantity, required_quantity) self.assertNotEqual(consumed_item.pk, stock_item.pk) - # Split tracking entries: one on the new (split-off) item, one on the original + # The new (split-off) item records its creation (as a child of the original) + created = stock_history(consumed_item).first() + self.assertEqual(created.action, AuditLogEntry.Action.CREATE) + self.assertEqual(created.changes_dict['parent'], [None, stock_item.pk]) + + # The original item records the split self.assertTrue( - StockItemTracking.objects.filter( - item=consumed_item, - tracking_type=StockHistoryCode.SPLIT_FROM_PARENT.value, - deltas__stockitem=stock_item.pk, - ).exists() - ) - self.assertTrue( - StockItemTracking.objects.filter( - item=stock_item, - tracking_type=StockHistoryCode.SPLIT_CHILD_ITEM.value, + stock_events( + stock_item, event_type=StockHistoryCode.SPLIT_CHILD_ITEM ).exists() ) @@ -2813,10 +2815,10 @@ class BuildConsumeTest(BuildAPITest): # Consumption is recorded against the new (split-off) item self.assertTrue( - StockItemTracking.objects.filter( - item=consumed_item, - tracking_type=StockHistoryCode.BUILD_CONSUMED.value, - deltas__buildorder=build.pk, + stock_events( + consumed_item, + event_type=StockHistoryCode.BUILD_CONSUMED, + additional_data__deltas__buildorder__pk=build.pk, ).exists() ) else: @@ -2826,16 +2828,15 @@ class BuildConsumeTest(BuildAPITest): # No split occurred - consumption is recorded directly against this item self.assertFalse( - StockItemTracking.objects.filter( - item=stock_item, - tracking_type=StockHistoryCode.SPLIT_CHILD_ITEM.value, + stock_events( + stock_item, event_type=StockHistoryCode.SPLIT_CHILD_ITEM ).exists() ) self.assertTrue( - StockItemTracking.objects.filter( - item=stock_item, - tracking_type=StockHistoryCode.BUILD_CONSUMED.value, - deltas__buildorder=build.pk, + stock_events( + stock_item, + event_type=StockHistoryCode.BUILD_CONSUMED, + additional_data__deltas__buildorder__pk=build.pk, ).exists() ) @@ -2951,17 +2952,17 @@ class BuildConsumeTest(BuildAPITest): ) self.assertTrue( - StockItemTracking.objects.filter( - item=component_item, - tracking_type=StockHistoryCode.INSTALLED_INTO_ASSEMBLY.value, - deltas__stockitem=output_item.pk, + stock_events( + component_item, + event_type=StockHistoryCode.INSTALLED_INTO_ASSEMBLY, + additional_data__deltas__stockitem__pk=output_item.pk, ).exists() ) self.assertTrue( - StockItemTracking.objects.filter( - item=output_item, - tracking_type=StockHistoryCode.INSTALLED_CHILD_ITEM.value, - deltas__stockitem=component_item.pk, + stock_events( + output_item, + event_type=StockHistoryCode.INSTALLED_CHILD_ITEM, + additional_data__deltas__stockitem__pk=component_item.pk, ).exists() ) diff --git a/src/backend/InvenTree/build/test_audit.py b/src/backend/InvenTree/build/test_audit.py index d44ed1f269..d9d93df902 100644 --- a/src/backend/InvenTree/build/test_audit.py +++ b/src/backend/InvenTree/build/test_audit.py @@ -152,3 +152,225 @@ class BuildAuditAPITest(InvenTreeAPITestCase): self.assertEqual(len(history), 1) self.assertEqual(history[0]['model_label'], 'Build Order') + + +class BuildStockAuditTest(BuildTestBase): + """Stock operations performed by a build order record a single audit entry per stock item.""" + + def stock_entries(self, item, n=0): + """Return the audit entries for a stock item (after the first n entries).""" + from stock.models import StockItem + + return list( + AuditLogEntry.objects.filter( + content_type=StockItem.get_content_type(), object_pk=str(item.pk) + ).order_by('pk')[n:] + ) + + def test_create_output(self): + """Creating build outputs records a single CREATE entry for each output.""" + from part.models import Part + from stock.status_codes import StockHistoryCode + + # An assembly without any trackable components + assembly = Part.objects.create( + name='Simple Assembly', description='An assembly', assembly=True + ) + build = Build.objects.create(part=assembly, quantity=5, reference='BO-9876') + + outputs = build.create_build_output(5, user=self.user) + + for output in outputs: + [entry] = self.stock_entries(output) + + self.assertEqual(entry.action, ACTION.CREATE) + self.assertEqual(entry.event_type, StockHistoryCode.BUILD_OUTPUT_CREATED) + self.assertEqual(entry.actor, self.user) + self.assertEqual( + entry.additional_data['deltas']['buildorder']['pk'], build.pk + ) + + def test_create_serialized_outputs(self): + """Creating serialized build outputs records a single CREATE entry for each output.""" + from stock.status_codes import StockHistoryCode + + self.build.part.trackable = True + self.build.part.save() + + outputs = self.build.create_build_output( + 3, serials=['1001', '1002', '1003'], user=self.user + ) + + self.assertEqual(outputs.count(), 3) + + for output in outputs: + [entry] = self.stock_entries(output) + + self.assertEqual(entry.event_type, StockHistoryCode.BUILD_OUTPUT_CREATED) + self.assertEqual(entry.changes_dict['serial'][1], output.serial) + + def test_consume(self): + """Consuming allocated stock records a single entry for each affected item.""" + from build.models import BuildItem + from stock.models import StockItem + from stock.status_codes import StockHistoryCode + + BuildItem.objects.create( + build_line=self.line_1, stock_item=self.stock_1_1, quantity=3 + ) + BuildItem.objects.create( + build_line=self.line_1, stock_item=self.stock_1_2, quantity=10 + ) + + n_1 = len(self.stock_entries(self.stock_1_1)) + n_2 = len(self.stock_entries(self.stock_1_2)) + + self.build.complete_allocations( + BuildItem.objects.filter(build_line__build=self.build), user=self.user + ) + + # Fully consumed item + [entry] = self.stock_entries(self.stock_1_1, n_1) + + self.assertEqual(entry.event_type, StockHistoryCode.BUILD_CONSUMED) + self.assertEqual(entry.changes_dict['consumed_by'], [None, self.build.pk]) + self.assertEqual(entry.actor, self.user) + + # Partially consumed item (split) + [entry] = self.stock_entries(self.stock_1_2, n_2) + + self.assertEqual(entry.event_type, StockHistoryCode.SPLIT_CHILD_ITEM) + self.assertEqual(entry.changes_dict['quantity'], ['100.00000', '90.00000']) + + # The split item is created (and consumed) + split = StockItem.objects.get(parent=self.stock_1_2) + + [entry] = self.stock_entries(split) + + self.assertEqual(entry.action, ACTION.CREATE) + self.assertEqual(entry.event_type, StockHistoryCode.BUILD_CONSUMED) + self.assertEqual(entry.changes_dict['consumed_by'][1], self.build.pk) + + def test_complete_output(self): + """Completing a build output records a single entry.""" + from stock.status_codes import StockHistoryCode + + self.build.issue_build() + + n = len(self.stock_entries(self.output_1)) + + self.build.complete_build_output(self.output_1, self.user) + + [entry] = self.stock_entries(self.output_1, n) + + self.assertEqual(entry.event_type, StockHistoryCode.BUILD_OUTPUT_COMPLETED) + self.assertEqual(entry.changes_dict['is_building'], [True, False]) + + def test_scrap_output(self): + """Scrapping a build output records a single entry.""" + from stock.models import StockLocation + from stock.status_codes import StockHistoryCode, StockStatus + + self.build.issue_build() + + location = StockLocation.objects.create(name='Scrap') + + n = len(self.stock_entries(self.output_1)) + + self.build.scrap_build_output( + self.output_1, + self.output_1.quantity, + location, + user=self.user, + notes='Scrapped', + discard_allocations=True, + ) + + [entry] = self.stock_entries(self.output_1, n) + + self.assertEqual(entry.event_type, StockHistoryCode.BUILD_OUTPUT_REJECTED) + self.assertEqual(entry.notes, 'Scrapped') + self.assertEqual( + entry.changes_dict['status'], + [StockStatus.OK.value, StockStatus.REJECTED.value], + ) + + def test_consume_install(self): + """Consuming trackable stock (installed into an output) records an entry for both items.""" + from build.models import BuildItem + from stock.models import StockItem + from stock.status_codes import StockHistoryCode + + component = StockItem.objects.create( + part=self.sub_part_3, quantity=1, serial='SN-1' + ) + + BuildItem.objects.create( + build_line=self.line_3, + stock_item=component, + quantity=1, + install_into=self.output_1, + ) + + n_component = len(self.stock_entries(component)) + n_output = len(self.stock_entries(self.output_1)) + + self.build.complete_allocations( + BuildItem.objects.filter(stock_item=component), user=self.user + ) + + [entry] = self.stock_entries(component, n_component) + + self.assertEqual(entry.event_type, StockHistoryCode.INSTALLED_INTO_ASSEMBLY) + self.assertEqual(entry.changes_dict['belongs_to'], [None, self.output_1.pk]) + self.assertEqual( + entry.additional_data['deltas']['buildorder']['pk'], self.build.pk + ) + + # Nothing changed for the output itself - the event is recorded regardless + [entry] = self.stock_entries(self.output_1, n_output) + + self.assertEqual(entry.event_type, StockHistoryCode.INSTALLED_CHILD_ITEM) + self.assertEqual(entry.changes_dict, {}) + self.assertEqual( + entry.additional_data['deltas']['stockitem']['pk'], component.pk + ) + + def test_consume_install_multiple(self): + """Installing multiple components into one output records an entry for each.""" + from build.models import BuildItem + from stock.models import StockItem + from stock.status_codes import StockHistoryCode + + components = [ + StockItem.objects.create(part=self.sub_part_3, quantity=1, serial=serial) + for serial in ['SN-1', 'SN-2'] + ] + + for component in components: + BuildItem.objects.create( + build_line=self.line_3, + stock_item=component, + quantity=1, + install_into=self.output_1, + ) + + n_output = len(self.stock_entries(self.output_1)) + + self.build.complete_allocations( + BuildItem.objects.filter(stock_item__in=components), user=self.user + ) + + entries = self.stock_entries(self.output_1, n_output) + + self.assertEqual(len(entries), 2) + + for entry in entries: + self.assertEqual(entry.event_type, StockHistoryCode.INSTALLED_CHILD_ITEM) + + self.assertEqual( + sorted( + entry.additional_data['deltas']['stockitem']['pk'] for entry in entries + ), + sorted(component.pk for component in components), + ) diff --git a/src/backend/InvenTree/build/test_build.py b/src/backend/InvenTree/build/test_build.py index b1e84e1e58..5c9091f9f5 100644 --- a/src/backend/InvenTree/build/test_build.py +++ b/src/backend/InvenTree/build/test_build.py @@ -31,15 +31,11 @@ from InvenTree.unit_test import ( InvenTreeTestCase, findOffloadedEvent, findOffloadedTask, + stock_history_count, ) from order.models import PurchaseOrder, PurchaseOrderLineItem from part.models import BomItem, BomItemSubstitute, Part, PartTestTemplate -from stock.models import ( - StockItem, - StockItemTestResult, - StockItemTracking, - StockLocation, -) +from stock.models import StockItem, StockItemTestResult, StockLocation from stock.status_codes import StockStatus from users.models import Owner @@ -1540,7 +1536,7 @@ class BuildTaskTests(BuildTestBase): self.assertEqual(self.build.status, BuildStatus.COMPLETE) n_consumed = StockItem.objects.filter(consumed_by=self.build).count() - n_tracking = StockItemTracking.objects.count() + n_tracking = stock_history_count() # A redelivered task run must skip based on the (locked) database state with mock.patch('build.tasks.trigger_event') as trigger: @@ -1550,7 +1546,7 @@ class BuildTaskTests(BuildTestBase): self.assertEqual( StockItem.objects.filter(consumed_by=self.build).count(), n_consumed ) - self.assertEqual(StockItemTracking.objects.count(), n_tracking) + self.assertEqual(stock_history_count(), n_tracking) def test_allocate_stock_merges_quantities(self): """Repeated allocations against the same (line, stock item) accumulate. @@ -1615,7 +1611,7 @@ class BuildTaskTests(BuildTestBase): self.assertEqual(self.build.completed, 10) self.assertFalse(self.output_1.is_building) - n_tracking = StockItemTracking.objects.count() + n_tracking = stock_history_count() # Run the task again (simulating a duplicated / redelivered task) build.tasks.complete_build_outputs( @@ -1630,7 +1626,7 @@ class BuildTaskTests(BuildTestBase): # and no additional stock history has been generated self.build.refresh_from_db() self.assertEqual(self.build.completed, 10) - self.assertEqual(StockItemTracking.objects.count(), n_tracking) + self.assertEqual(stock_history_count(), n_tracking) def test_complete_output_twice_rejected(self): """Completing or scrapping an already-completed output must be rejected. diff --git a/src/backend/InvenTree/common/setting/system.py b/src/backend/InvenTree/common/setting/system.py index 30c39d68fe..a14aad8c34 100644 --- a/src/backend/InvenTree/common/setting/system.py +++ b/src/backend/InvenTree/common/setting/system.py @@ -1317,23 +1317,6 @@ SYSTEM_SETTINGS: dict[str, InvenTreeSettingsKeyType] = { 'units': _('days'), 'validator': [int, MinValueValidator(30)], }, - 'STOCK_TRACKING_DELETE_OLD_ENTRIES': { - 'name': _('Delete Old Stock Tracking Entries'), - 'description': _( - 'Delete stock tracking entries older than the specified number of days' - ), - 'default': False, - 'validator': bool, - }, - 'STOCK_TRACKING_DELETE_DAYS': { - 'name': _('Stock Tracking Deletion Interval'), - 'description': _( - 'Stock tracking entries will be deleted after specified number of days' - ), - 'default': 365, - 'units': _('days'), - 'validator': [int, MinValueValidator(30)], - }, 'AUDIT_LOG_ENABLED': { 'name': _('Enable Audit Log'), 'description': _('Record a history of changes made to supported models'), diff --git a/src/backend/InvenTree/common/setting/user.py b/src/backend/InvenTree/common/setting/user.py index 2cf5c2bff0..f50c4a6ed5 100644 --- a/src/backend/InvenTree/common/setting/user.py +++ b/src/backend/InvenTree/common/setting/user.py @@ -252,7 +252,7 @@ USER_SETTINGS: dict[str, InvenTreeSettingsKeyType] = { 'DISPLAY_HISTORY_TAB': { 'name': _('Show Change History'), 'description': _( - 'Display the change history (audit log) panel on detail pages (staff users only)' + 'Display the change history (audit log) panel on detail pages' ), 'default': False, 'validator': bool, diff --git a/src/backend/InvenTree/order/models.py b/src/backend/InvenTree/order/models.py index 056adf2daa..cdf0abc873 100644 --- a/src/backend/InvenTree/order/models.py +++ b/src/backend/InvenTree/order/models.py @@ -1,6 +1,7 @@ """Order model definitions.""" import copy +from contextlib import ExitStack from decimal import Decimal from typing import Any, Optional, TypedDict @@ -32,7 +33,13 @@ import order.validators import report.mixins import stock.models import users.models as UserModels -from audit.helpers import audited_bulk_update +from audit.helpers import ( + audit_event, + audited_bulk_update, + copy_related_cache, + record_created, + write_batched, +) from build.status_codes import BuildStatus from common.currency import currency_code_default from common.notifications import InvenTreeNotificationBodies @@ -1139,22 +1146,16 @@ class PurchaseOrder(AuditedOrderMixin, TotalPriceMixin, Order): # List of stock items which have been created stock_items: list[stock.models.StockItem] = [] - # Per-item 'note' text, index-aligned with stock_items - StockItem no longer - # has its own 'notes' field, so this is threaded through to each item's - # RECEIVED_AGAINST_PURCHASE_ORDER tracking entry instead (see below) - stock_item_notes: list[str] = [] - # List of stock items to bulk create bulk_create_items: list[stock.models.StockItem] = [] # Notes for bulk_create_items, appended in lockstep - bulk_create_and_fetch() # re-fetches fresh instances from the database, so any note has to be tracked - # positionally here rather than stashed on the (discarded) unsaved instance + # positionally here rather than stashed on the (discarded) unsaved instance. + # StockItem has no 'notes' field, so the note is recorded against each item's + # RECEIVED_AGAINST_PURCHASE_ORDER tracking event instead bulk_create_notes: list[str] = [] - # List of tracking entries to create - tracking_entries: list[stock.models.StockItemTracking] = [] - # List of line items to update line_items_to_update: list[PurchaseOrderLineItem] = [] @@ -1326,14 +1327,22 @@ class PurchaseOrder(AuditedOrderMixin, TotalPriceMixin, Order): # Now, create the new stock items if serialize: + # Resolve the (possibly custom) status *before* the items are created, + # so that each new item is created (and recorded) with the selected status + status_item = stock.models.StockItem() + status_item.set_status(status, custom_values=custom_stock_status_values) + new_items = stock.models.StockItem._create_serial_numbers( - serials=serials, **stock_data + serials=serials, + tracking=lambda item, note=note: self._receive_tracking_event( + item, user, note + ), + status=status_item.status, + status_custom_key=status_item.status_custom_key, + **stock_data, ) for new_item in new_items: - new_item.set_status( - status, custom_values=custom_stock_status_values - ) # run validation for serialized items plugin.validate_batch_code new_item.validate_batch_code() @@ -1341,7 +1350,6 @@ class PurchaseOrder(AuditedOrderMixin, TotalPriceMixin, Order): new_item.run_plugin_validation() stock_items.append(new_item) - stock_item_notes.append(note) else: new_item = stock.models.StockItem(**stock_data, serial='', parent=None) @@ -1368,29 +1376,23 @@ class PurchaseOrder(AuditedOrderMixin, TotalPriceMixin, Order): bulk_create_and_fetch(stock.models.StockItem, bulk_create_items) ) - stock_items.extend(new_items) - stock_item_notes.extend(bulk_create_notes) + # Retain the cached related objects (avoid per-item lookups) + for original, new_item in zip(bulk_create_items, new_items, strict=True): + copy_related_cache(original, new_item) - # Generate a new tracking entry for each stock item - for item, item_note in zip(stock_items, stock_item_notes, strict=True): - tracking_entries.append( - item.add_tracking_entry( - StockHistoryCode.RECEIVED_AGAINST_PURCHASE_ORDER, - user, - deltas={ - 'location': item.location.pk if item.location else None, - 'purchaseorder': self.pk, - 'quantity': float(item.quantity), - }, - notes=item_note, - commit=False, - ) + # Record a tracking event for each new item + item_notes = dict( + zip([item.pk for item in new_items], bulk_create_notes, strict=True) ) - # Bulk create new tracking entries for each item - stock.models.StockItemTracking.objects.bulk_create( - tracking_entries, batch_size=250 - ) + stock.models.StockItem._record_created_items( + new_items, + tracking=lambda item: self._receive_tracking_event( + item, user, item_notes[item.pk] + ), + ) + + stock_items.extend(new_items) # Update received quantity for each line item audited_bulk_update(PurchaseOrderLineItem, line_items_to_update, ['received']) @@ -1425,6 +1427,19 @@ class PurchaseOrder(AuditedOrderMixin, TotalPriceMixin, Order): pk__in=[item.pk for item in stock_items] ) + def _receive_tracking_event(self, item, user, notes: str) -> dict: + """Return the tracking event for a stock item received against this order.""" + return { + 'entry_type': StockHistoryCode.RECEIVED_AGAINST_PURCHASE_ORDER, + 'user': user, + 'notes': notes, + 'deltas': { + 'location': item.location, + 'purchaseorder': self, + 'quantity': float(item.quantity), + }, + } + @transaction.atomic def receive_line_item( self, line, location, quantity, user, status=StockStatus.OK.value, **kwargs @@ -2974,7 +2989,7 @@ class SalesOrderShipment( Notes: This unrolls what would otherwise be a per-allocation call, so that the underlying - StockItem, StockItemTracking, SalesOrderLineItem and SalesOrderAllocation writes can + StockItem, audit log, SalesOrderLineItem and SalesOrderAllocation writes can be batched into a handful of bulk queries instead of several per allocation. """ import part.tasks @@ -3057,6 +3072,10 @@ class SalesOrderShipment( for i, (source, placeholder, quantity) in enumerate(split_items): persisted = new_stock_items[i] + + # Retain the cached related objects (avoid per-item lookups) + copy_related_cache(source, persisted) + split_item_map[id(placeholder)] = persisted split_items[i] = (source, persisted, quantity) @@ -3068,69 +3087,53 @@ class SalesOrderShipment( for allocation in allocations_to_update: allocation.item = split_item_map[id(allocation.item)] - tracking_entries = [] - split_events = [] - customer_events = [] + split_events = [ + ((), {'id': new_item.pk, 'parent': source_item.pk}) + for source_item, new_item, _quantity in split_items + ] - # Split stock items for "split_items" - for source_item, new_item, quantity in split_items: - tracking_entries.append( - stock.models.StockItemTracking( - item_id=new_item.pk, - part_id=new_item.part_id, - tracking_type=StockHistoryCode.SPLIT_FROM_PARENT.value, - user=user, - deltas={'stockitem': source_item.pk, 'quantity': float(quantity)}, - ) - ) - tracking_entries.append( - stock.models.StockItemTracking( - item_id=source_item.pk, - part_id=source_item.part_id, - tracking_type=StockHistoryCode.SPLIT_CHILD_ITEM.value, - user=user, - deltas={ - 'removed': float(quantity), - 'quantity': float(source_item.quantity), + customer_events = [ + ((), {'id': target_item.pk, 'customer': customer.pk if customer else None}) + for target_item, _quantity in shipped_items + ] + + # Record the operation in the audit log - a single entry for each stock item + with write_batched(), ExitStack() as events: + + def audit(item, code, deltas): + """Record a named event against the provided stock item.""" + events.enter_context(audit_event(item, code, deltas=deltas, actor=user)) + + for source_item, new_item, quantity in split_items: + audit( + source_item, + StockHistoryCode.SPLIT_CHILD_ITEM, + { + 'stockitem': new_item, + 'removed': quantity, + 'quantity': source_item.quantity, }, ) - ) - split_events.append(((), {'id': new_item.pk, 'parent': source_item.pk})) - - # Ship stock items for "shipped_items" - for target_item, quantity in shipped_items: - deltas = {'quantity': float(quantity), 'salesorder': order.pk} - - if customer is not None: - deltas['customer'] = customer.pk - deltas['customer_name'] = customer.name - - tracking_entries.append( - stock.models.StockItemTracking( - item_id=target_item.pk, - part_id=target_item.part_id, - tracking_type=StockHistoryCode.SHIPPED_AGAINST_SALES_ORDER.value, - user=user, - deltas=deltas, + for target_item, quantity in shipped_items: + audit( + target_item, + StockHistoryCode.SHIPPED_AGAINST_SALES_ORDER, + {'quantity': quantity, 'salesorder': order, 'customer': customer}, ) + + # Newly split items are recorded as created (with the shipment event) + record_created(stock.models.StockItem, new_stock_items) + + # Flush all StockItem field changes (quantity reductions, and shipment details) + # Note: Newly split items were created with their final state (no update required), + # and any other shipped item is one of the (existing) items drawn from + audited_bulk_update( + stock.models.StockItem, + list(seen_stock_items.values()), + ['quantity', 'sales_order', 'customer', 'location'], ) - customer_events.append(( - (), - {'id': target_item.pk, 'customer': customer.pk if customer else None}, - )) - - # Flush all StockItem field changes (quantity reductions, and shipment details) - stock.models.StockItem.objects.bulk_update( - seen_stock_items.values(), ['quantity'] - ) - stock.models.StockItem.objects.bulk_update( - [item for item, _ in shipped_items], ['sales_order', 'customer', 'location'] - ) - - stock.models.StockItemTracking.objects.bulk_create(tracking_entries) - # Update sales order lines for "seen_lines" audited_bulk_update(SalesOrderLineItem, list(seen_lines.values()), ['shipped']) @@ -3658,31 +3661,29 @@ class ReturnOrder(AuditedOrderMixin, TotalPriceMixin, Order): deltas = { 'status': status, - 'returnorder': self.pk, - 'location': location.pk, + 'returnorder': self, + 'location': location, 'quantity': float(line.quantity), } if stock_item.customer: - deltas['customer'] = stock_item.customer.pk + deltas['customer'] = stock_item.customer # Update the StockItem stock_item.set_status(status) stock_item.location = location stock_item.customer = None stock_item.sales_order = None - stock_item.save(add_note=False) - stock_item.clearAllocations() - # Add a tracking entry to the StockItem - stock_item.add_tracking_entry( + with stock_item.tracking_event( StockHistoryCode.RETURNED_AGAINST_RETURN_ORDER, user, notes=kwargs.get('note', ''), deltas=deltas, - location=location, - returnorder=self, - ) + ): + stock_item.save(add_note=False) + + stock_item.clearAllocations() # Update the LineItem line.received_date = InvenTree.helpers.current_date() diff --git a/src/backend/InvenTree/order/serializers.py b/src/backend/InvenTree/order/serializers.py index ec77957283..028daead69 100644 --- a/src/backend/InvenTree/order/serializers.py +++ b/src/backend/InvenTree/order/serializers.py @@ -19,6 +19,7 @@ import order.models import part.filters as part_filters import stock.models import stock.serializers +from audit.helpers import write_batched from common.settings import get_global_setting from company.serializers import ( AddressBriefSerializer, @@ -53,7 +54,6 @@ from order.status_codes import ( ) from part.serializers import PartBriefSerializer from plugin.base.event.events import batch_events -from stock.models import batch_tracking_entries from stock.status_codes import StockStatus from users.serializers import OwnerSerializer, UserSerializer @@ -927,7 +927,7 @@ class PurchaseOrderLineItemReceiveSerializer(serializers.Serializer): ) note = serializers.CharField( - max_length=stock.models.StockItemTracking.NOTES_MAX_LENGTH, + max_length=stock.models.StockItem.TRACKING_NOTES_MAX_LENGTH, label=_('Note'), help_text=_('Additional note for incoming stock items'), required=False, @@ -1127,7 +1127,7 @@ class PurchaseOrderReceiveSerializer(serializers.Serializer): with ( transaction.atomic(), batch_events(), - batch_tracking_entries(), + write_batched(), batch_offload_tasks(), ): items = order.receive_line_items( @@ -2344,7 +2344,7 @@ class ReturnOrderReceiveSerializer(serializers.Serializer): ) note = serializers.CharField( - max_length=stock.models.StockItemTracking.NOTES_MAX_LENGTH, + max_length=stock.models.StockItem.TRACKING_NOTES_MAX_LENGTH, label=_('Note'), help_text=_('Additional note for incoming stock items'), required=False, diff --git a/src/backend/InvenTree/order/test_api.py b/src/backend/InvenTree/order/test_api.py index be7519e1bf..68921e74f5 100644 --- a/src/backend/InvenTree/order/test_api.py +++ b/src/backend/InvenTree/order/test_api.py @@ -5,6 +5,7 @@ import io import json import threading from datetime import date, datetime, timedelta +from decimal import Decimal from typing import Optional from unittest import mock @@ -24,7 +25,7 @@ from common.currency import currency_codes from common.models import InvenTreeCustomUserStateModel, InvenTreeSetting from common.settings import set_global_setting from company.models import Address, Company, Contact, SupplierPart, SupplierPriceBreak -from InvenTree.unit_test import InvenTreeAPITestCase +from InvenTree.unit_test import InvenTreeAPITestCase, stock_history from order import models from order.models import SalesOrderAllocation, SalesOrderLineItem, SalesOrderShipment from order.serializers import TransferOrderSerialAllocationSerializer @@ -38,7 +39,7 @@ from order.status_codes import ( TransferOrderStatusGroups, ) from part.models import Part -from stock.models import StockItem, StockItemTracking, StockLocation, StockSortOrder +from stock.models import StockItem, StockLocation, StockSortOrder from stock.status_codes import StockHistoryCode, StockStatus from users.models import Owner @@ -1956,7 +1957,7 @@ class PurchaseOrderReceiveTest(OrderTest): 'batch_code': stock_field('batch'), 'packaging': stock_field('packaging'), 'barcode': stock_field('barcode_data'), - 'note': StockItemTracking.NOTES_MAX_LENGTH, + 'note': StockItem.TRACKING_NOTES_MAX_LENGTH, } line_data = {'line_item': 1, 'quantity': 10} @@ -2166,13 +2167,15 @@ class PurchaseOrderReceiveTest(OrderTest): self.assertEqual(item['status'], StockStatus.QUARANTINED) stock_item = StockItem.objects.get(pk=item['pk']) - # Check that the item has tracking entries - self.assertEqual(stock_item.tracking_info.count(), 1) - entry = stock_item.tracking_info.first() - self.assertEqual(entry.deltas['quantity'], stock_item.quantity) - self.assertEqual(entry.user, self.user) + # Check that the item has a single stock history entry (its creation) + self.assertEqual(stock_history(stock_item).count(), 1) + entry = stock_history(stock_item).first() self.assertEqual( - entry.tracking_type, StockHistoryCode.RECEIVED_AGAINST_PURCHASE_ORDER + Decimal(entry.changes_dict['quantity'][1]), stock_item.quantity + ) + self.assertEqual(entry.actor, self.user) + self.assertEqual( + entry.event_type, StockHistoryCode.RECEIVED_AGAINST_PURCHASE_ORDER ) # Check that the order has been completed @@ -2207,10 +2210,10 @@ class PurchaseOrderReceiveTest(OrderTest): stock_item = StockItem.objects.get(pk=response[0]['pk']) - self.assertEqual(stock_item.tracking_info.count(), 1) - entry = stock_item.tracking_info.first() + self.assertEqual(stock_history(stock_item).count(), 1) + entry = stock_history(stock_item).first() self.assertEqual( - entry.tracking_type, StockHistoryCode.RECEIVED_AGAINST_PURCHASE_ORDER + entry.event_type, StockHistoryCode.RECEIVED_AGAINST_PURCHASE_ORDER ) self.assertEqual(entry.notes, 'Damaged box, 2 units short') @@ -2239,10 +2242,86 @@ class PurchaseOrderReceiveTest(OrderTest): for i in range(200, 203): item = StockItem.objects.get(serial_int=i) - self.assertEqual(item.tracking_info.count(), 1) - entry = item.tracking_info.first() + self.assertEqual(stock_history(item).count(), 1) + entry = stock_history(item).first() + self.assertEqual( + entry.event_type, StockHistoryCode.RECEIVED_AGAINST_PURCHASE_ORDER + ) self.assertEqual(entry.notes, 'Received via serialized batch') + def test_receive_serialized_status(self): + """Test that the selected status (including custom status values) is applied to received serialized items. + + Regression test: the status was previously only applied to the (already created) + serialized items in memory, and was never saved to the database. + """ + self.post( + self.url, + { + 'items': [ + { + 'line_item': 1, + 'quantity': 3, + 'serial_numbers': '300+', + 'status': StockStatus.DAMAGED.value, + } + ], + 'location': 1, + }, + expected_code=201, + ) + + for i in range(300, 303): + item = StockItem.objects.get(serial_int=i) + self.assertEqual(item.status, StockStatus.DAMAGED.value) + + # The status is recorded against the creation of the item + [entry] = stock_history(item) + self.assertEqual( + entry.changes_dict['status'], [None, StockStatus.DAMAGED.value] + ) + + # A custom status value is also applied (and recorded) + custom_status = InvenTreeCustomUserStateModel.objects.create( + key=1055, + name='Damaged (cosmetic)', + label='Damaged (cosmetic)', + color='warning', + logical_key=StockStatus.DAMAGED.value, + model=ContentType.objects.get_for_model(StockItem), + reference_status='StockStatus', + ) + + self.post( + self.url, + { + 'items': [ + { + 'line_item': 1, + 'quantity': 3, + 'serial_numbers': '400+', + 'status': custom_status.key, + } + ], + 'location': 1, + }, + expected_code=201, + ) + + for i in range(400, 403): + item = StockItem.objects.get(serial_int=i) + self.assertEqual(item.status, StockStatus.DAMAGED.value) + self.assertEqual(item.status_custom_key, custom_status.key) + self.assertEqual(item.get_custom_status(), custom_status.key) + + [entry] = stock_history(item) + self.assertEqual( + entry.changes_dict['status'], [None, StockStatus.DAMAGED.value] + ) + self.assertEqual( + entry.changes_dict['status_custom_key'], [None, custom_status.key] + ) + def test_bulk_receive_query_benchmark(self): """Benchmark: measure the number of DB queries required to receive 100 line items at once.""" InvenTreeSetting.set_setting('ENABLE_PLUGINS_EVENTS', True, change_user=None) @@ -4001,7 +4080,7 @@ class ReturnOrderTests(InvenTreeAPITestCase): rma = models.ReturnOrder.objects.first() url = reverse('api-return-order-receive', kwargs={'pk': rma.pk}) - max_length = StockItemTracking.NOTES_MAX_LENGTH + max_length = StockItem.TRACKING_NOTES_MAX_LENGTH response = self.post( url, @@ -4222,7 +4301,7 @@ class ReturnOrderTests(InvenTreeAPITestCase): # Get a reference to one of the stock items stock_item = rma.lines.first().item - n_tracking = stock_item.tracking_info.count() + n_tracking = stock_history(stock_item).count() # Receive items successfully data = receive( @@ -4234,16 +4313,19 @@ class ReturnOrderTests(InvenTreeAPITestCase): self.assertTrue(line.received) self.assertIsNotNone(line.received_date) - # A single tracking entry should have been added to the item - self.assertEqual(stock_item.tracking_info.count(), n_tracking + 1) + # A single audit entry should have been added to the item + self.assertEqual(stock_history(stock_item).count(), n_tracking + 1) - tracking_entry = stock_item.tracking_info.last() - deltas = tracking_entry.deltas + entry = stock_history(stock_item).last() + self.assertEqual( + entry.event_type, StockHistoryCode.RETURNED_AGAINST_RETURN_ORDER + ) - self.assertEqual(deltas['status'], StockStatus.QUARANTINED) - self.assertEqual(deltas['customer'], customer.pk) - self.assertEqual(deltas['location'], 1) - self.assertEqual(deltas['returnorder'], rma.pk) + # Field changes are recorded as such (and not duplicated in the deltas) + self.assertEqual(entry.changes_dict['status'][1], StockStatus.QUARANTINED) + self.assertEqual(entry.changes_dict['customer'], [customer.pk, None]) + self.assertEqual(entry.changes_dict['location'][1], 1) + self.assertEqual(entry.additional_data['deltas']['returnorder']['pk'], rma.pk) def test_receive_untracked(self): """Test that we can receive untracked items against a ReturnOrder. diff --git a/src/backend/InvenTree/order/test_audit.py b/src/backend/InvenTree/order/test_audit.py index 99691e7041..70ef61c832 100644 --- a/src/backend/InvenTree/order/test_audit.py +++ b/src/backend/InvenTree/order/test_audit.py @@ -264,6 +264,23 @@ class PurchaseOrderAuditTest(OrderAuditTestBase): self.assertEqual(new, line.received + 3) self.assertEqual(entry.parent_object_pk, str(po.pk)) + # Each received stock item records a single entry (creation, with the event) + from stock.models import StockItem + from stock.status_codes import StockHistoryCode + + items = StockItem.objects.filter(purchase_order=po) + self.assertEqual(items.count(), 2) + + for item in items: + [entry] = self.entries(StockItem, item.pk) + + self.assertEqual(entry.action, ACTION.CREATE) + self.assertEqual( + entry.event_type, StockHistoryCode.RECEIVED_AGAINST_PURCHASE_ORDER + ) + self.assertEqual(entry.actor, self.user) + self.assertEqual(entry.changes_dict['location'][1], location.pk) + class SalesOrderAuditTest(OrderAuditTestBase): """Audit tests for SalesOrder, line items, extra lines and shipments.""" @@ -412,6 +429,27 @@ class SalesOrderAuditTest(OrderAuditTestBase): self.assertIsNone(shipment_entry.changes_dict['shipment_date'][0]) + # Stock items record a single entry each + from stock.models import StockItem + from stock.status_codes import StockHistoryCode + + [source_entry] = self.entries( + StockItem, self.stock.pk, event_type__isnull=False + ).exclude(event_type=StockHistoryCode.CREATED) + + self.assertEqual(source_entry.event_type, StockHistoryCode.SPLIT_CHILD_ITEM) + self.assertEqual(source_entry.actor, self.user) + + shipped = StockItem.objects.get(parent=self.stock) + + [shipped_entry] = self.entries(StockItem, shipped.pk) + + self.assertEqual(shipped_entry.action, ACTION.CREATE) + self.assertEqual( + shipped_entry.event_type, StockHistoryCode.SHIPPED_AGAINST_SALES_ORDER + ) + self.assertEqual(shipped_entry.changes_dict['customer'][1], self.customer.pk) + class ReturnOrderAuditTest(OrderAuditTestBase): """Audit tests for ReturnOrder, line items and extra lines.""" @@ -484,6 +522,36 @@ class ReturnOrderAuditTest(OrderAuditTestBase): update.changes_dict['description'], ['Restocking fee', 'Inspection fee'] ) + def test_receive_stock(self): + """Receiving an item against a return order records a single entry.""" + from stock.models import StockItem + from stock.status_codes import StockHistoryCode, StockStatus + + location = StockLocation.objects.create(name='Returns') + + line = models.ReturnOrderLineItem.objects.create( + order=self.order, item=self.item + ) + + self.order.issue_order() + + n = self.entries(StockItem, self.item.pk).count() + + self.order.receive_line_item(line, location, self.user, note='Inspected') + + [entry] = list(self.entries(StockItem, self.item.pk).order_by('pk')[n:]) + + self.assertEqual( + entry.event_type, StockHistoryCode.RETURNED_AGAINST_RETURN_ORDER + ) + self.assertEqual(entry.actor, self.user) + self.assertEqual(entry.notes, 'Inspected') + self.assertEqual(entry.changes_dict['location'][1], location.pk) + self.assertEqual(entry.changes_dict['status'][1], StockStatus.QUARANTINED.value) + self.assertEqual( + entry.additional_data['deltas']['returnorder']['pk'], self.order.pk + ) + class TransferOrderAuditTest(OrderAuditTestBase): """Audit tests for TransferOrder and line items.""" @@ -566,3 +634,17 @@ class TransferOrderAuditTest(OrderAuditTestBase): ) self.assertNotIn('F(', str(entry.changes)) self.assertEqual(entry.parent_object_pk, str(self.order.pk)) + + # The transferred stock records a single STOCK_MOVE event (linked to the order) + from stock.models import StockItem + from stock.status_codes import StockHistoryCode + + moved = StockItem.objects.get(parent=self.stock) + + [created] = self.entries(StockItem, moved.pk) + + self.assertEqual(created.event_type, StockHistoryCode.SPLIT_FROM_PARENT) + self.assertEqual(created.changes_dict['location'][1], self.target.pk) + self.assertEqual( + created.additional_data['deltas']['transferorder']['pk'], self.order.pk + ) diff --git a/src/backend/InvenTree/order/test_sales_order.py b/src/backend/InvenTree/order/test_sales_order.py index 47ba514bc2..88d1e7ea48 100644 --- a/src/backend/InvenTree/order/test_sales_order.py +++ b/src/backend/InvenTree/order/test_sales_order.py @@ -24,6 +24,9 @@ from InvenTree.unit_test import ( InvenTreeAPITestCase, InvenTreeTestCase, addUserPermission, + stock_events, + stock_history, + stock_history_count, ) from order.models import ( SalesOrder, @@ -35,7 +38,7 @@ from order.models import ( from order.serializers import SalesOrderShipmentAllocationSerializer from part.models import Part from stock.events import StockEvents -from stock.models import StockItem, StockItemTracking, StockLocation +from stock.models import StockItem, StockLocation from stock.status_codes import StockHistoryCode from users.models import Owner @@ -506,7 +509,7 @@ class SalesOrderTest(InvenTreeAPITestCase): self.assertFalse(self.shipment.check_can_complete(raise_error=False)) n_items = StockItem.objects.count() - n_tracking = StockItemTracking.objects.count() + n_tracking = stock_history_count() # Simulate a duplicated task execution # (e.g. a second completion request submitted before the first task ran) @@ -519,7 +522,7 @@ class SalesOrderTest(InvenTreeAPITestCase): # No additional stock items or tracking entries have been created self.assertEqual(StockItem.objects.count(), n_items) - self.assertEqual(StockItemTracking.objects.count(), n_tracking) + self.assertEqual(stock_history_count(), n_tracking) def test_shipment_many_items(self): """Test completion of a shipment with many items. @@ -618,8 +621,8 @@ class SalesOrderTest(InvenTreeAPITestCase): url, expected_code=200, benchmark=True, - max_query_time=5.0, - max_query_count=120, + max_query_time=10.0, + max_query_count=150, ) self.assertEqual(response.status_code, 200) @@ -674,25 +677,18 @@ class SalesOrderTest(InvenTreeAPITestCase): self.assertEqual(len(low_stock_tasks), 1) self.assertEqual(low_stock_tasks[0].args(), (part.pk,)) - # Exactly one StockItemTracking entry should exist per split, per side + # Exactly one 'split' event should exist per split (on the original item) self.assertEqual( - StockItemTracking.objects.filter( - tracking_type=StockHistoryCode.SPLIT_FROM_PARENT.value - ).count(), - len(split_pks), - ) - self.assertEqual( - StockItemTracking.objects.filter( - tracking_type=StockHistoryCode.SPLIT_CHILD_ITEM.value - ).count(), + stock_history_count(event_type=StockHistoryCode.SPLIT_CHILD_ITEM), len(split_pks), ) - # Exactly one 'shipped' tracking entry should exist per allocated item + # Exactly one 'shipped' event should exist per allocated item + # (for split items, this is recorded against the creation of the new item) self.assertEqual( - StockItemTracking.objects.filter( - tracking_type=StockHistoryCode.SHIPPED_AGAINST_SALES_ORDER.value - ).count(), + stock_history_count( + event_type=StockHistoryCode.SHIPPED_AGAINST_SALES_ORDER + ), N_ITEMS, ) @@ -704,15 +700,15 @@ class SalesOrderTest(InvenTreeAPITestCase): self.assertEqual(item.customer, customer) self.assertFalse( - StockItemTracking.objects.filter( - item=item, tracking_type=StockHistoryCode.SPLIT_CHILD_ITEM.value + stock_events( + item, event_type=StockHistoryCode.SPLIT_CHILD_ITEM ).exists() ) self.assertTrue( - StockItemTracking.objects.filter( - item=item, - tracking_type=StockHistoryCode.SHIPPED_AGAINST_SALES_ORDER.value, - deltas__salesorder=so.pk, + stock_events( + item, + event_type=StockHistoryCode.SHIPPED_AGAINST_SALES_ORDER, + additional_data__deltas__salesorder__pk=so.pk, ).exists() ) @@ -735,24 +731,20 @@ class SalesOrderTest(InvenTreeAPITestCase): ) self.assertEqual(sample_new_item.quantity, 1) - self.assertTrue( - StockItemTracking.objects.filter( - item=sample_new_item, - tracking_type=StockHistoryCode.SPLIT_FROM_PARENT.value, - deltas__stockitem=sample_original.pk, - ).exists() + + # The new item records a single entry - its creation (as a child of the original), + # with the 'shipped' event + [created] = stock_history(sample_new_item) + self.assertEqual(created.changes_dict['parent'], [None, sample_original.pk]) + self.assertEqual( + created.event_type, StockHistoryCode.SHIPPED_AGAINST_SALES_ORDER ) + self.assertEqual(created.additional_data['deltas']['salesorder']['pk'], so.pk) + + # The original item records the split self.assertTrue( - StockItemTracking.objects.filter( - item=sample_original, - tracking_type=StockHistoryCode.SPLIT_CHILD_ITEM.value, - ).exists() - ) - self.assertTrue( - StockItemTracking.objects.filter( - item=sample_new_item, - tracking_type=StockHistoryCode.SHIPPED_AGAINST_SALES_ORDER.value, - deltas__salesorder=so.pk, + stock_events( + sample_original, event_type=StockHistoryCode.SPLIT_CHILD_ITEM ).exists() ) self.assertTrue( diff --git a/src/backend/InvenTree/stock/__init__.py b/src/backend/InvenTree/stock/__init__.py index 524da0600c..43a6dd2853 100644 --- a/src/backend/InvenTree/stock/__init__.py +++ b/src/backend/InvenTree/stock/__init__.py @@ -4,5 +4,4 @@ It includes models for: - StockLocation - StockItem -- StockItemTracking """ diff --git a/src/backend/InvenTree/stock/admin.py b/src/backend/InvenTree/stock/admin.py index 5326f5cc33..585c523184 100644 --- a/src/backend/InvenTree/stock/admin.py +++ b/src/backend/InvenTree/stock/admin.py @@ -3,15 +3,7 @@ from django.contrib import admin from django.db.models import Count -from common.admin_shared import ReadOnlyAdminMixin - -from .models import ( - StockItem, - StockItemTestResult, - StockItemTracking, - StockLocation, - StockLocationType, -) +from .models import StockItem, StockItemTestResult, StockLocation, StockLocationType class LocationInline(admin.TabularInline): @@ -78,17 +70,6 @@ class StockItemAdmin(admin.ModelAdmin): ] -@admin.register(StockItemTracking) -class StockTrackingAdmin(ReadOnlyAdminMixin, admin.ModelAdmin): - """Admin class for StockTracking - read-only to preserve audit trail integrity.""" - - list_display = ('item', 'date', 'label') - - search_fields = ['item__part__name', 'item__serial', 'notes'] - - autocomplete_fields = ['item'] - - @admin.register(StockItemTestResult) class StockItemTestResultAdmin(admin.ModelAdmin): """Admin class for StockItemTestResult.""" diff --git a/src/backend/InvenTree/stock/api.py b/src/backend/InvenTree/stock/api.py index 7625190774..0cb6072aba 100644 --- a/src/backend/InvenTree/stock/api.py +++ b/src/backend/InvenTree/stock/api.py @@ -26,9 +26,7 @@ import InvenTree.helpers import InvenTree.permissions import stock.serializers as StockSerializers from build.models import Build -from build.serializers import BuildSerializer from company.models import Company, ManufacturerPart, SupplierPart -from company.serializers import CompanySerializer from data_exporter.mixins import DataExportViewMixin from generic.states.api import StatusView from InvenTree.api import ( @@ -56,20 +54,11 @@ from InvenTree.mixins import ( SerializerContextMixin, ) from InvenTree.serializers import apply_duplicate_copy_options -from order.models import PurchaseOrder, ReturnOrder, SalesOrder, TransferOrder -from order.serializers import ( - PurchaseOrderSerializer, - ReturnOrderSerializer, - SalesOrderSerializer, - TransferOrderSerializer, -) from part.models import BomItem, Part, PartCategory -from part.serializers import PartBriefSerializer from stock.generators import generate_batch_code, generate_serial_number from stock.models import ( StockItem, StockItemTestResult, - StockItemTracking, StockLocation, StockLocationType, ) @@ -1287,46 +1276,43 @@ class StockList( original = duplicate['original'] - # copy_history/copy_tests don't follow the copy__from() naming - # convention (copyHistoryFrom/copyTestResultsFrom), so still need + # copy_tests doesn't follow the copy__from() naming + # convention (copyTestResultsFrom), so still needs # handling here - only copy_notes can go through the shared helper apply_duplicate_copy_options(item, duplicate, original, copy_notes=True) - if duplicate.get('copy_history', False): - item.copyHistoryFrom(original) - if duplicate.get('copy_tests', False): item.copyTestResultsFrom(original) with transaction.atomic(): if serials: + create_data = {**serializer.validated_data} + + if status_value: + # Resolve the (possibly custom) status before the items are created, + # so that each item is created with the correct status + status_item = StockItem() + + if status_item.set_status(status_value): + create_data['status'] = status_item.status + create_data['status_custom_key'] = status_item.status_custom_key + # Create multiple serialized StockItem objects + # (recording a tracking event for each new item) items = StockItem._create_serial_numbers( - serials, **serializer.validated_data + serials, + tracking=lambda item: { + 'entry_type': StockHistoryCode.CREATED, + 'user': user, + 'deltas': {'status': status_value}, + 'quantity': float(item.quantity), + }, + **create_data, ) - # Next, bulk-create stock tracking entries for the newly created items - tracking = [] - for item in items: - if status_value and not item.compare_status(status_value): - item.set_status(status_value) - item.save() - apply_duplicate_options(item) - if entry := item.add_tracking_entry( - StockHistoryCode.CREATED, - user, - deltas={'status': status_value}, - location=location, - quantity=float(item.quantity), - commit=False, - ): - tracking.append(entry) - - StockItemTracking.objects.bulk_create(tracking, batch_size=250) - # Annotate the stock items with part information queryset = StockSerializers.StockItemSerializer.annotate_queryset(items) @@ -1578,161 +1564,6 @@ class StockItemTestResultList( serializer.save(user=self.request.user) -class StockTrackingDetail(RetrieveAPI): - """Detail API endpoint for StockItemTracking model.""" - - queryset = StockItemTracking.objects.all() - serializer_class = StockSerializers.StockTrackingSerializer - - -class StockTrackingOutputOptions(OutputConfiguration): - """Output options for StockItemTracking endpoint.""" - - OPTIONS = [ - InvenTreeOutputOption(flag='item_detail'), - InvenTreeOutputOption(flag='user_detail'), - ] - - -class StockTrackingFilter(FilterSet): - """API filter options for the StockTrackingList endpoint.""" - - class Meta: - """Metaclass options.""" - - model = StockItemTracking - fields = ['item', 'user'] - - include_variants = rest_filters.BooleanFilter( - label=_('Include Part Variants'), method='filter_include_variants' - ) - - def filter_include_variants(self, queryset, name, value): - """Filter by whether or not to include part variants. - - Note: - - This filter does nothing by itself, and is only used to modify the behavior of the 'part' filter. - - Refer to the 'filter_part' method for more information on how this works. - """ - return queryset - - part = rest_filters.ModelChoiceFilter( - label=_('Part'), queryset=Part.objects.all(), method='filter_part' - ) - - def filter_part(self, queryset, name, part): - """Filter StockTracking entries by the linked part. - - Note: - - This filter behavior also takes into account the 'include_variants' filter, which determines whether or not to include part variants in the results. - """ - include_variants = str2bool(self.data.get('include_variants', False)) - - if include_variants: - return queryset.filter(part__in=part.get_descendants(include_self=True)) - else: - return queryset.filter(part=part) - - min_date = InvenTreeDateFilter( - label=_('Date after'), field_name='date', lookup_expr='gt' - ) - - max_date = InvenTreeDateFilter( - label=_('Date before'), field_name='date', lookup_expr='lt' - ) - - -class StockTrackingList( - SerializerContextMixin, DataExportViewMixin, OutputOptionsMixin, ListAPI -): - """API endpoint for list view of StockItemTracking objects. - - StockItemTracking objects are read-only - (they are created by internal model functionality) - - - GET: Return list of StockItemTracking objects - """ - - queryset = StockItemTracking.objects.all().prefetch_related('item', 'part') - serializer_class = StockSerializers.StockTrackingSerializer - filterset_class = StockTrackingFilter - output_options = StockTrackingOutputOptions - - def get_delta_model_map(self) -> dict: - """Return a mapping of delta models to their respective models and serializers. - - This is used to generate additional context information for the historical data, - with some attempt at caching so that we can reduce the number of database hits. - """ - return { - 'part': (Part, PartBriefSerializer), - 'location': (StockLocation, StockSerializers.LocationSerializer), - 'customer': (Company, CompanySerializer), - 'purchaseorder': (PurchaseOrder, PurchaseOrderSerializer), - 'salesorder': (SalesOrder, SalesOrderSerializer), - 'returnorder': (ReturnOrder, ReturnOrderSerializer), - 'transferorder': (TransferOrder, TransferOrderSerializer), - 'buildorder': (Build, BuildSerializer), - 'item': (StockItem, StockSerializers.StockItemSerializer), - 'stockitem': (StockItem, StockSerializers.StockItemSerializer), - } - - def list(self, request, *args, **kwargs): - """List all stock tracking entries.""" - queryset = self.filter_queryset(self.get_queryset()) - - page = self.paginate_queryset(queryset) - - if page is not None: - serializer = self.get_serializer(page, many=True) - else: - serializer = self.get_serializer(queryset, many=True) - - data = serializer.data - - delta_models = self.get_delta_model_map() - - # Construct a set of related models we need to lookup for later - related_model_lookups = {key: set() for key in delta_models} - - # Run a first pass through the data to determine which related models we need to lookup - for item in data: - deltas = item['deltas'] or {} - - for key in delta_models: - if key in deltas: - related_model_lookups[key].add(deltas[key]) - - for key in delta_models: - model, serializer = delta_models[key] - - # Fetch all related models in one go - related_models = model.objects.filter(pk__in=related_model_lookups[key]) - - # Construct a mapping of pk -> serialized data - related_data = {obj.pk: serializer(obj).data for obj in related_models} - - # Now, update the data with the serialized data - for item in data: - deltas = item['deltas'] or {} - - if key in deltas: - item['deltas'][f'{key}_detail'] = related_data.get(deltas[key]) - - if page is not None: - return self.get_paginated_response(data) - - return Response(data) - - filter_backends = SEARCH_ORDER_FILTER - - ordering = '-date' - - ordering_fields = ['date'] - - search_fields = ['notes'] - - stock_api_urls = [ path( 'location/', @@ -1796,25 +1627,6 @@ stock_api_urls = [ ), ]), ), - # StockItemTracking API endpoints - path( - 'track/', - include([ - path( - '/', - StockTrackingDetail.as_view(), - name='api-stock-tracking-detail', - ), - # Stock tracking status code information - path( - 'status/', - StatusView.as_view(), - {StatusView.MODEL_REF: StockHistoryCode}, - name='api-stock-tracking-status-codes', - ), - path('', StockTrackingList.as_view(), name='api-stock-tracking-list'), - ]), - ), # Detail views for a single stock item path( '/', diff --git a/src/backend/InvenTree/stock/migrations/0129_migrate_stock_tracking_to_audit_log.py b/src/backend/InvenTree/stock/migrations/0129_migrate_stock_tracking_to_audit_log.py new file mode 100644 index 0000000000..452098ae48 --- /dev/null +++ b/src/backend/InvenTree/stock/migrations/0129_migrate_stock_tracking_to_audit_log.py @@ -0,0 +1,341 @@ +"""Copy legacy stock tracking entries (StockItemTracking) into the audit log (AuditLogEntry). + +Each StockItemTracking row is converted to a single AuditLogEntry: + +- The entry is recorded against the stock item (even if the item has since been deleted) +- The parent reference is the part (so the entry is included in the history of the part) +- The tracking type is recorded as the event type (StockHistoryCode values are unchanged) + - 'EDITED' entries are recorded as regular updates (no event type) + - Entries which record the creation of a stock item are recorded as CREATE entries +- The user, date and notes are copied across +- Tracking deltas are converted to audit log changes / deltas: + - Status changes are recorded as field changes ('status' / 'status_custom_key') + - Location changes (with a known previous location) are recorded as field changes + - For CREATE entries, initial field values are recorded as field changes + - References to other objects (by pk) are recorded as {model_type, pk, str} references + - Any other deltas are copied as-is +- The pk of the original tracking entry is retained in additional_data['tracking_id'] + +The legacy StockItemTracking rows are not modified (or deleted) by this migration. +""" + +from decimal import Decimal, InvalidOperation + +from django.db import migrations + +# StockHistoryCode values (copied here, as migrations should not import application code) +CREATED = 1 +EDITED = 5 +SPLIT_FROM_PARENT = 40 +CREATED_FROM_DISASSEMBLY = 47 +BUILD_OUTPUT_CREATED = 50 +RECEIVED_AGAINST_PURCHASE_ORDER = 70 + +# Tracking codes which record the creation of a stock item +CREATE_CODES = { + CREATED, + SPLIT_FROM_PARENT, + CREATED_FROM_DISASSEMBLY, + BUILD_OUTPUT_CREATED, + RECEIVED_AGAINST_PURCHASE_ORDER, +} + +# AuditLogEntry action codes (as defined by django-auditlog) +ACTION_CREATE = 0 +ACTION_UPDATE = 1 + +# Delta keys which reference other objects (by pk) +REFERENCE_KEYS = { + 'stockitem': ('stock', 'stockitem'), + 'location': ('stock', 'stocklocation'), + 'old_location': ('stock', 'stocklocation'), + 'stockitemlocation': ('stock', 'stocklocation'), + 'part': ('part', 'part'), + 'customer': ('company', 'company'), + 'buildorder': ('build', 'build'), + 'build_order': ('build', 'build'), + 'purchaseorder': ('order', 'purchaseorder'), + 'salesorder': ('order', 'salesorder'), + 'returnorder': ('order', 'returnorder'), + 'transferorder': ('order', 'transferorder'), +} + +# Delta keys which are recorded as field changes (rather than deltas) +STATUS_KEYS = ('status', 'status_logical', 'old_status', 'old_status_logical') + +# For CREATE entries, these deltas record the initial value of the stock item field +CREATE_FIELD_KEYS = ('quantity', 'location', 'serial', 'batch') + +BATCH_SIZE = 1000 + + +def format_quantity(quantity) -> str: + """Format a stock quantity for display (e.g. '10', '2.5').""" + try: + value = Decimal(str(quantity)).normalize() + except (InvalidOperation, TypeError, ValueError): + return str(quantity) + + # Remove exponent notation (e.g. 1E+1) + return format(value, 'f') + + +def stock_item_str(item) -> str: + """Return a string representation of a stock item. + + Note: This approximates StockItem.__str__ - historical models do not provide model + methods, and the configurable part name format cannot be applied here. + """ + if item.serial: + return f'{item.part.name} #{item.serial}' + + return f'{format_quantity(item.quantity)} x {item.part.name}' + + +def reference_str(model_type: str, instance) -> str: + """Return a string representation for a referenced object.""" + if model_type == 'stock.stockitem': + return stock_item_str(instance) + + if model_type == 'stock.stocklocation': + return instance.pathstring or instance.name + + if model_type in ( + 'build.build', + 'order.purchaseorder', + 'order.salesorder', + 'order.returnorder', + 'order.transferorder', + ): + return instance.reference + + return instance.name + + +def to_pk(value): + """Convert a delta value to a primary key (or None).""" + if value is None or isinstance(value, bool): + return None + + try: + return int(value) + except (TypeError, ValueError): + return None + + +def convert_entry(entry, references: dict) -> dict: + """Convert a single StockItemTracking entry into AuditLogEntry field values.""" + code = entry.tracking_type + deltas = dict(entry.deltas or {}) if isinstance(entry.deltas, dict) else {} + + action = ACTION_CREATE if code in CREATE_CODES else ACTION_UPDATE + + changes = {} + + # Status changes are recorded as field changes + if 'status' in deltas or 'status_logical' in deltas: + new_status = deltas.get('status') + new_logical = deltas.get('status_logical', new_status) + old_status = deltas.get('old_status') + old_logical = deltas.get('old_status_logical', old_status) + + changes['status'] = [old_logical, new_logical] + + # A custom status is recorded separately to the logical status + new_custom = new_status if new_status != new_logical else None + old_custom = old_status if old_status != old_logical else None + + if new_custom is not None or old_custom is not None: + changes['status_custom_key'] = [old_custom, new_custom] + + for key in STATUS_KEYS: + deltas.pop(key, None) + + # A location change (with known previous location) is recorded as a field change + if 'old_location' in deltas: + old_location = to_pk(deltas.pop('old_location')) + new_location = to_pk(deltas.pop('location', None)) + + if old_location != new_location: + changes['location'] = [old_location, new_location] + + # The initial values of a new stock item are recorded as field changes + if action == ACTION_CREATE: + for key in CREATE_FIELD_KEYS: + if key in deltas: + value = deltas.pop(key) + + if key == 'location': + value = to_pk(value) + elif key == 'quantity': + value = format_quantity(value) + elif value is not None: + value = str(value) + + if value is not None: + changes[key] = [None, value] + + # The customer name is recorded as the string representation of the customer reference + customer_name = deltas.pop('customer_name', None) + + # Convert references (by pk) to {model_type, pk, str} + for key, (app_label, model_name) in REFERENCE_KEYS.items(): + if key not in deltas: + continue + + pk = to_pk(deltas[key]) + + if pk is None: + continue + + model_type = f'{app_label}.{model_name}' + instance = references.get(model_type, {}).get(pk) + + if instance is not None: + text = reference_str(model_type, instance) + elif key == 'customer' and customer_name: + text = customer_name + else: + # The referenced object no longer exists + text = None + + deltas[key] = {'model_type': model_type, 'pk': pk, 'str': text} + + additional_data = {'tracking_id': entry.pk} + + if deltas: + additional_data['deltas'] = deltas + + item = entry.item + + if item is not None: + object_repr = stock_item_str(item) + elif entry.part is not None: + object_repr = entry.part.name + else: + object_repr = '' + + user = entry.user + + return { + 'object_pk': str(entry.item_id) if entry.item_id is not None else '', + 'object_id': entry.item_id, + 'object_repr': object_repr, + 'action': action, + 'changes': changes, + 'changes_text': '', + 'timestamp': entry.date, + 'actor_id': entry.user_id, + 'actor_email': getattr(user, 'email', None) or None, + 'actor_username': getattr(user, 'username', None), + 'parent_object_pk': str(entry.part_id) if entry.part_id is not None else None, + 'event_type': None if code == EDITED else code, + 'notes': entry.notes[:512] if entry.notes else None, + 'additional_data': additional_data, + } + + +def collect_references(apps, entries) -> dict: + """Fetch all objects referenced (by pk) in the deltas of the provided entries.""" + required = {} + + for entry in entries: + if not isinstance(entry.deltas, dict): + continue + + for key, (app_label, model_name) in REFERENCE_KEYS.items(): + if (pk := to_pk(entry.deltas.get(key))) is not None: + required.setdefault(f'{app_label}.{model_name}', set()).add(pk) + + references = {} + + for model_type, pks in required.items(): + app_label, model_name = model_type.split('.') + model = apps.get_model(app_label, model_name) + + queryset = model.objects.all() + + if model_type == 'stock.stockitem': + queryset = queryset.select_related('part') + + references[model_type] = queryset.in_bulk(list(pks)) + + return references + + +def migrate_tracking_entries(apps, schema_editor): + """Copy all StockItemTracking entries into the audit log.""" + StockItemTracking = apps.get_model('stock', 'stockitemtracking') + AuditLogEntry = apps.get_model('audit', 'auditlogentry') + ContentType = apps.get_model('contenttypes', 'contenttype') + + n = StockItemTracking.objects.count() + + if n == 0: + return + + print(f'\nstock.migrations.0129: Migrating {n} stock tracking entries to the audit log') + + stock_ct, _ = ContentType.objects.get_or_create( + app_label='stock', model='stockitem' + ) + part_ct, _ = ContentType.objects.get_or_create(app_label='part', model='part') + + queryset = StockItemTracking.objects.select_related( + 'item', 'item__part', 'part', 'user' + ).order_by('pk') + + last_pk = 0 + migrated = 0 + + while True: + entries = list(queryset.filter(pk__gt=last_pk)[:BATCH_SIZE]) + + if not entries: + break + + references = collect_references(apps, entries) + + audit_entries = [] + + for entry in entries: + data = convert_entry(entry, references) + + audit_entries.append( + AuditLogEntry( + content_type_id=stock_ct.pk, + parent_content_type_id=part_ct.pk + if data['parent_object_pk'] is not None + else None, + **data, + ) + ) + + AuditLogEntry.objects.bulk_create(audit_entries, batch_size=BATCH_SIZE) + + migrated += len(audit_entries) + last_pk = entries[-1].pk + + print(f'- Migrated {migrated} / {n} entries') + + +class Migration(migrations.Migration): + """Copy legacy stock tracking entries into the audit log.""" + + dependencies = [ + ('stock', '0128_remove_stockitem_notes'), + ('audit', '0003_event_type'), + ('build', '0060_remove_build_notes'), + ('company', '0083_company_is_internal'), + ('order', '0124_purchaseorder_delivery_address'), + ('part', '0155_move_pricing_models'), + ('contenttypes', '0002_remove_content_type_name'), + ] + + operations = [ + # The migrated entries are retained on reversal, as the legacy table + # (dropped in the following migration) cannot be restored + migrations.RunPython( + migrate_tracking_entries, reverse_code=migrations.RunPython.noop + ) + ] diff --git a/src/backend/InvenTree/stock/migrations/0130_delete_stockitemtracking.py b/src/backend/InvenTree/stock/migrations/0130_delete_stockitemtracking.py new file mode 100644 index 0000000000..14d1027968 --- /dev/null +++ b/src/backend/InvenTree/stock/migrations/0130_delete_stockitemtracking.py @@ -0,0 +1,9 @@ +# Generated by Django 5.2.17 on 2026-10-25 20:53 + +from django.db import migrations + + +class Migration(migrations.Migration): + dependencies = [('stock', '0129_migrate_stock_tracking_to_audit_log')] + + operations = [migrations.DeleteModel(name='StockItemTracking')] diff --git a/src/backend/InvenTree/stock/models.py b/src/backend/InvenTree/stock/models.py index 09ad9fb381..5ba0db69d1 100644 --- a/src/backend/InvenTree/stock/models.py +++ b/src/backend/InvenTree/stock/models.py @@ -2,10 +2,10 @@ from __future__ import annotations -import contextvars import copy import os -from contextlib import contextmanager +from collections.abc import Callable +from contextlib import ExitStack, contextmanager, nullcontext from datetime import timedelta from decimal import Decimal, InvalidOperation @@ -36,6 +36,7 @@ import InvenTree.ready import InvenTree.tasks import order.models import report.mixins +from audit.models import NOTES_MAX_LENGTH as AUDIT_NOTES_MAX_LENGTH from common.icons import validate_icon from common.settings import get_global_setting from company import models as CompanyModels @@ -425,6 +426,7 @@ STOCK_SORT_DEFAULT = StockSortOrder.DATE_OLDEST class StockItem( + InvenTree.models.InvenTreeAuditMixin, InvenTree.models.InvenTreeAttachmentMixin, InvenTree.models.InvenTreeBarcodeMixin, InvenTree.models.InvenTreeNoteMixin, @@ -465,6 +467,27 @@ class StockItem( IMPORT_ID_FIELDS = ['serial'] STATUS_CLASS = StockStatus + # Maximum length of the notes recorded against a stock tracking event + TRACKING_NOTES_MAX_LENGTH = AUDIT_NOTES_MAX_LENGTH + + # Stock operations are recorded as named events in the audit log (see tracking_event) + AUDIT_EVENT_TYPES = StockHistoryCode + AUDIT_PARENT_FIELD = 'part' + + # Fields which are derived from other fields + AUDIT_EXCLUDE_FIELDS = ('barcode_hash', 'serial_int') + + # Tracking deltas which are not recorded in the audit log + # (status / location changes are recorded as field changes, and the customer name via the customer reference) + AUDIT_IGNORE_DELTAS = ( + 'status', + 'status_logical', + 'old_status', + 'old_status_logical', + 'old_location', + 'customer_name', + ) + class Meta: """Model meta options.""" @@ -498,6 +521,7 @@ class StockItem( Performs a number of checks: - Unique serial number requirement - Adds a transaction note when the item is first created. + - Records the user / notes (if provided) against the audit entry for an update. """ self.validate_unique() self.clean() @@ -513,60 +537,31 @@ class StockItem( notes = kwargs.pop('notes', '') + # Tracking event recorded against this save (if any) + event = None + if self.pk: - # StockItem has already been saved + # Changes to an existing item are recorded as a regular update, + # including the user and notes (if provided) - unless an event is already pending + from audit.context import has_pending_event + from audit.helpers import audit_event - # Check if "interesting" fields have been changed - # (we wish to record these as historical records) + if (user or notes) and not has_pending_event(self): + event = audit_event(self, None, notes=notes, actor=user) - try: - old = StockItem.objects.get(pk=self.pk) - old_custom_status = old.get_custom_status() - custom_status = self.get_custom_status() - - deltas = {} - - # Status changed? - if old.status != self.status: - # Custom status changed? - # Matches custom status tracking behavior of StockChangeStatusSerializer - if old_custom_status != custom_status: - deltas['status'] = custom_status - deltas['status_logical'] = self.status - else: - deltas['status'] = self.status - deltas['status_logical'] = self.status - - if old_custom_status: - deltas['old_status'] = old_custom_status - deltas['old_status_logical'] = old.status - else: - deltas['old_status'] = old.status - deltas['old_status_logical'] = old.status - - if add_note and len(deltas) > 0: - self.add_tracking_entry( - StockHistoryCode.EDITED, user, deltas=deltas, notes=notes - ) - - except (ValueError, StockItem.DoesNotExist): - pass - - super().save(*args, **kwargs) - - # If user information is provided, and no existing note exists, create one! - if add_note and self.tracking_info.count() == 0: - tracking_info = {'status': self.status} - - self.add_tracking_entry( + elif add_note: + # A new StockItem is being created + event = self.tracking_event( StockHistoryCode.CREATED, user, - deltas=tracking_info, + deltas={'status': self.status}, notes=notes, - location=self.location, quantity=float(self.quantity), ) + with event or nullcontext(): + super().save(*args, **kwargs) + def delete(self, ignore_serial_check: bool = False, **kwargs): """Custom delete method for StockItem model. @@ -586,8 +581,14 @@ class StockItem( # Re-link any child items to the parent of this item, # so the genealogy chain survives deletion of an intermediate item + from audit.helpers import audited_update + parent = StockItem.objects.filter(pk=self.parent_id).first() - StockItem.objects.filter(parent=self).update(parent=parent) + children = StockItem.objects.filter(parent=self).select_related( + 'part', 'location', 'purchase_order__supplier' + ) + + audited_update(children, parent=parent) super().delete(**kwargs) @@ -735,11 +736,15 @@ class StockItem( @classmethod @transaction.atomic - def _create_serial_numbers(cls, serials: list, **kwargs) -> QuerySet: + def _create_serial_numbers( + cls, serials: list, tracking: Callable | None = None, **kwargs + ) -> QuerySet: """Create multiple stock items with the provided serial numbers. Arguments: serials: List of serial numbers to create + tracking: Optional callable which returns the tracking event for each created item + (see StockItem._record_created_items) **kwargs: Additional keyword arguments to pass to the StockItem creation function Returns: @@ -755,7 +760,7 @@ class StockItem( serialized against each other (see _lock_serial_numbers()), so unlike other validation checks, this method's duplicate-serial protection is safe even when the caller's own pre-check raced against another - request. It does not generate any "stock tracking entries". + request. Stock tracking events are only recorded if 'tracking' is provided. Note: This is an 'internal' function and should not be used by external code / plugins. """ @@ -835,6 +840,17 @@ class StockItem( except IntegrityError as exc: raise ValidationError({'serial_numbers': str(exc)}) from exc + # Every new item shares the same related objects - avoid redundant per-item lookups + # (bulk_create_and_fetch() does not preserve the cached related objects) + from audit.helpers import copy_related_cache + + template = StockItem(**data) + + for item in items: + copy_related_cache(template, item) + + StockItem._record_created_items(items, tracking=tracking) + # Trigger a 'created' event for the new items # Note that instead of a single event for each item, # we trigger a single event for all items created @@ -844,6 +860,30 @@ class StockItem( # Return the newly created StockItem objects return items + @staticmethod + def _record_created_items(items, tracking: Callable | None = None) -> None: + """Record audit (and tracking) entries for stock items which have been created in bulk. + + Items created in bulk (e.g. via bulk_create_and_fetch) bypass model signals, + so the audit entries must be recorded explicitly. + + Arguments: + items: The newly created StockItem instances + tracking: Optional callable which returns the tracking_event() kwargs for each item + (or None, if no tracking event is to be recorded for that item) + """ + from audit.helpers import record_created + + items = list(items) + + with ExitStack() as stack: + if tracking is not None: + for item in items: + if event := tracking(item): + stack.enter_context(item.tracking_event(**event)) + + record_created(StockItem, items) + @staticmethod def convert_serial_to_int(serial: str) -> int | None: """Convert the provided serial number to an integer value. @@ -1371,14 +1411,14 @@ class StockItem( return self.part = variant - self.save() - self.add_tracking_entry( + with self.tracking_event( StockHistoryCode.CONVERTED_TO_VARIANT, user, - deltas={'part': variant.pk}, + deltas={'part': variant}, notes=_('Converted to part') + ': ' + variant.full_name, - ) + ): + self.save() def get_item_owner(self): """Return the closest "owner" for this StockItem. @@ -1509,26 +1549,25 @@ class StockItem( else: item = self.splitStock(quantity, None, user) - # Update StockItem fields with new information - item.sales_order = order - item.customer = customer - item.location = None - - item.save(add_note=False) - code = StockHistoryCode.SENT_TO_CUSTOMER deltas = {'quantity': float(quantity)} if customer is not None: - deltas['customer'] = customer.pk + deltas['customer'] = customer deltas['customer_name'] = customer.name # If an order is provided, we are shipping against a SalesOrder, not manually! if order: code = StockHistoryCode.SHIPPED_AGAINST_SALES_ORDER - deltas['salesorder'] = order.pk + deltas['salesorder'] = order - item.add_tracking_entry(code, user, deltas, notes=notes) + # Update StockItem fields with new information + item.sales_order = order + item.customer = customer + item.location = None + + with item.tracking_event(code, user, deltas, notes=notes): + item.save(add_note=False) trigger_event( StockEvents.ITEM_ASSIGNED_TO_CUSTOMER, @@ -1605,14 +1644,14 @@ class StockItem( } if location: - tracking_info['location'] = location.pk + tracking_info['location'] = location if item.customer: - tracking_info['customer'] = item.customer.id + tracking_info['customer'] = item.customer tracking_info['customer_name'] = item.customer.name if item.consumed_by: - tracking_info['build_order'] = item.consumed_by.id + tracking_info['build_order'] = item.consumed_by # Clear out allocation information for the stock item item.consumed_by = None @@ -1635,14 +1674,13 @@ class StockItem( ) tracking_info['old_status_logical'] = old_status_logical - item.save() + with item.tracking_event( + tracking_code, user, notes=notes, deltas=tracking_info + ): + item.save() item.clearAllocations() - item.add_tracking_entry( - tracking_code, user, notes=notes, deltas=tracking_info, location=location - ) - trigger_event(StockEvents.ITEM_RETURNED_TO_STOCK, id=item.id) # Attempt to merge returned item into parent item: @@ -1923,25 +1961,28 @@ class StockItem( stock_item.consumed_by = build stock_item.location = None - stock_item.save(add_note=False) - deltas = {'stockitem': self.pk, 'quantity': float(quantity)} + deltas = {'stockitem': self, 'quantity': float(quantity)} if build is not None: - deltas['buildorder'] = build.pk + deltas['buildorder'] = build - # Add a transaction note to the other item - stock_item.add_tracking_entry( - StockHistoryCode.INSTALLED_INTO_ASSEMBLY, user, notes=notes, deltas=deltas - ) - - # Add a transaction note to this item (the assembly) - self.add_tracking_entry( - StockHistoryCode.INSTALLED_CHILD_ITEM, - user, - notes=notes, - deltas={'stockitem': stock_item.pk, 'quantity': float(quantity)}, - ) + # Record the operation against both the installed item and this item (the assembly) + with ( + stock_item.tracking_event( + StockHistoryCode.INSTALLED_INTO_ASSEMBLY, + user, + notes=notes, + deltas=deltas, + ), + self.tracking_event( + StockHistoryCode.INSTALLED_CHILD_ITEM, + user, + notes=notes, + deltas={'stockitem': stock_item, 'quantity': float(quantity)}, + ), + ): + stock_item.save(add_note=False) trigger_event( StockEvents.ITEM_INSTALLED_INTO_ASSEMBLY, @@ -1967,33 +2008,29 @@ class StockItem( 'location': _('Cannot assign stock to structural location') }) - # Add a transaction note to the parent item - self.belongs_to.add_tracking_entry( - StockHistoryCode.REMOVED_CHILD_ITEM, - user, - deltas={'stockitem': self.pk, 'quantity': float(self.quantity)}, - notes=notes, - ) + tracking_info = {'stockitem': self.belongs_to, 'quantity': float(self.quantity)} - tracking_info = { - 'stockitem': self.belongs_to.pk, - 'quantity': float(self.quantity), - } + # Record the operation against both the parent item and this item + with ( + self.belongs_to.tracking_event( + StockHistoryCode.REMOVED_CHILD_ITEM, + user, + deltas={'stockitem': self, 'quantity': float(self.quantity)}, + notes=notes, + ), + self.tracking_event( + StockHistoryCode.REMOVED_FROM_ASSEMBLY, + user, + notes=notes, + deltas=tracking_info, + ), + ): + # Mark this stock item as *not* belonging to anyone + self.belongs_to = None + self.consumed_by = None + self.location = location - self.add_tracking_entry( - StockHistoryCode.REMOVED_FROM_ASSEMBLY, - user, - notes=notes, - deltas=tracking_info, - location=location, - ) - - # Mark this stock item as *not* belonging to anyone - self.belongs_to = None - self.consumed_by = None - self.location = location - - self.save() + self.save() def allocate_disassembly_pricing(self, quantity, lines: list) -> list: """Allocate the purchase price of this stock item across disassembly lines. @@ -2212,25 +2249,23 @@ class StockItem( if status := line.get('status'): new_item.set_status(status, custom_values=custom_status_values) - # Ensure the tree structure is observed - new_item.save(add_note=False) - - deltas = {'stockitem': self.pk, 'quantity': float(line_quantity)} + deltas = {'stockitem': self, 'quantity': float(line_quantity)} # Record traceability links against the disassembled assembly if self.build: - deltas['buildorder'] = self.build.pk + deltas['buildorder'] = self.build if self.purchase_order: - deltas['purchaseorder'] = self.purchase_order.pk + deltas['purchaseorder'] = self.purchase_order - new_item.add_tracking_entry( + # Ensure the tree structure is observed + with new_item.tracking_event( StockHistoryCode.CREATED_FROM_DISASSEMBLY, user, notes=notes, deltas=deltas, - location=destination, - ) + ): + new_item.save(add_note=False) items.append(new_item) @@ -2255,11 +2290,10 @@ class StockItem( self.quantity = self.quantity - quantity - self.save(add_note=False) - - self.add_tracking_entry( + with self.tracking_event( StockHistoryCode.DISASSEMBLED, user, notes=notes, deltas=deltas - ) + ): + self.save(add_note=False) trigger_event(StockEvents.ITEM_DISASSEMBLED, id=self.pk) @@ -2326,78 +2360,126 @@ class StockItem( return self.sales_order is None - @property - def tracking_info_count(self): - """How many tracking entries are available?""" - return self.tracking_info.count() + def _tracking_deltas(self, deltas: dict | None, kwargs: dict) -> dict: + """Return the deltas for a tracking event, including any information provided via kwargs. - @property - def has_tracking_info(self): - """Is tracking info available?""" - return self.tracking_info_count > 0 + Model references (see model_reference_fields) may be provided as kwargs, + and are retained as model instances (recorded as references in the audit log). + """ + deltas = dict(deltas or {}) + + for field in StockItem.model_reference_fields(): + if (instance := kwargs.pop(field, None)) is not None: + deltas.setdefault(field, instance) + + # Quantity specified? + if quantity := kwargs.get('quantity'): + deltas['quantity'] = float(quantity) + + return deltas + + def _audit_deltas(self, deltas: dict) -> dict: + """Return the deltas to record in the audit log for a tracking event.""" + return { + key: value + for key, value in deltas.items() + if key not in self.AUDIT_IGNORE_DELTAS + } + + @staticmethod + def _is_empty_tracking_event(entry_type: int, deltas: dict, notes: str) -> bool: + """Return True if the tracking event contains no information, and should not be recorded.""" + return ( + entry_type == StockHistoryCode.STOCK_UPDATE + and len(deltas) == 0 + and not notes + ) + + @contextmanager + def tracking_event( + self, + entry_type: int, + user: User | None = None, + deltas: dict | None = None, + notes: str = '', + **kwargs, + ): + """Record a stock tracking event against this StockItem. + + - Changes to this item which are saved within the context are recorded + in the audit log as part of this event (as a single entry) + - If this item is not saved (or not changed) within the context, + an entry for the event is recorded when the context exits + - If an event is already pending for this item (e.g. save() is called within a + stock operation), the outer event takes precedence in the audit log + + Arguments: + entry_type: Code describing the "type" of historical action (see StockHistoryCode) + user: The user performing this action + deltas: Additional information about the event (model references may be provided as instances) + notes: Notes associated with the event + **kwargs: Model references (see model_reference_fields) and quantity + """ + from audit.context import has_pending_event + from audit.helpers import audit_event + + deltas = self._tracking_deltas(deltas, kwargs) + + if self._is_empty_tracking_event(entry_type, deltas, notes): + yield + return + + if has_pending_event(self): + event = nullcontext() + else: + event = audit_event( + self, + entry_type, + notes=notes, + deltas=self._audit_deltas(deltas), + actor=user, + ) + + with event: + yield def add_tracking_entry( self, entry_type: int, - user: User, + user: User | None, deltas: dict | None = None, notes: str = '', - commit: bool = True, **kwargs, - ): - """Add a history tracking entry for this StockItem. + ) -> None: + """Record a stock tracking event for this StockItem (in the audit log). + + Note: This records the event immediately, and must not be called after saving + changes to this item as part of the same operation (which would record + two separate audit entries) - use tracking_event() around the save instead. Args: entry_type (int): Code describing the "type" of historical action (see StockHistoryCode) user (User): The user performing this action - deltas (dict, optional): A map of the changes made to the model. Defaults to None. - notes (str, optional): URL associated with this tracking entry. Defaults to ''. - commit (bool, optional): If True, save the entry to the database. Defaults to True. - - Returns: - StockItemTracking: The created tracking entry + deltas (dict, optional): Additional information about the event. Defaults to None. + notes (str, optional): Notes associated with the event. Defaults to ''. + **kwargs: Model references (see model_reference_fields) and quantity """ - if deltas is None: - deltas = {} + from audit.context import has_pending_event + from audit.helpers import log_event - # Prevent empty entry - if ( - entry_type == StockHistoryCode.STOCK_UPDATE - and len(deltas) == 0 - and not notes - ): - return None + deltas = self._tracking_deltas(deltas, kwargs) - # Extract any additional information from the kwargs - self._apply_model_reference_fields(kwargs, deltas) + if self._is_empty_tracking_event(entry_type, deltas, notes): + return - # Quantity specified? - quantity = kwargs.get('quantity') - - if quantity: - deltas['quantity'] = float(quantity) - - # If this item has already been deleted (e.g. depleted via delete_on_deplete), - # self.pk is None - Django refuses to save a FK pointing at an unsaved instance, - # even for a nullable/SET_NULL field, so pass None explicitly instead of self. - entry = StockItemTracking( - item=self if self.pk else None, - part=self.part, - tracking_type=entry_type.value, - user=user, - date=InvenTree.helpers.current_time(), - notes=notes, - deltas=deltas, - ) - - if commit: - if (batch := _tracking_batch.get()) is not None: - # A batch_tracking_entries() context is active - queue rather than save now - batch.add(entry) - else: - entry.save() - - return entry + if not has_pending_event(self): + log_event( + self, + entry_type, + notes=notes, + deltas=self._audit_deltas(deltas), + actor=user, + ) @transaction.atomic def serializeStock( @@ -2485,50 +2567,26 @@ class StockItem( data['parent'] = self # Generate a new serial number for each item - items = StockItem._create_serial_numbers(serials, **data) - - # Create a new tracking entry for each item - history_items = [] - - # Every new item shares this exact part - avoid a redundant per-item - # part_id -> Part lookup (bulk_create_and_fetch() does not preserve the - # cached FK) by seeding each item's cache with the instance we already have - part = self.part + items = StockItem._create_serial_numbers( + serials, + tracking=lambda item: { + 'entry_type': StockHistoryCode.SPLIT_FROM_PARENT, + 'user': user, + 'notes': notes, + 'quantity': 1, + }, + **data, + ) # Check (once) whether there are any test results to copy across, rather # than re-querying an identical (and usually empty) result set per item has_test_results = self.test_results.exists() - for item in items: - item.part = part - - # Construct tracking entries for the new StockItem - if entry := item.add_tracking_entry( - StockHistoryCode.SPLIT_FROM_PARENT, - user, - quantity=1, - notes=notes, - location=location, - commit=False, - ): - history_items.append(entry) - - if entry := item.add_tracking_entry( - StockHistoryCode.ASSIGNED_SERIAL, - user, - notes=notes, - deltas={'serial': item.serial}, - location=location, - commit=False, - ): - history_items.append(entry) - - # Copy any test results from this item to the new one - if has_test_results: + # Copy any test results from this item to the new items + if has_test_results: + for item in items: item.copyTestResultsFrom(self) - StockItemTracking.objects.bulk_create(history_items, batch_size=250) - # Remove the equivalent number of items self.take_stock( quantity, user, code=StockHistoryCode.STOCK_SERIALIZED, notes=notes @@ -2536,14 +2594,6 @@ class StockItem( return items - @transaction.atomic - def copyHistoryFrom(self, other): - """Copy stock history from another StockItem.""" - for item in other.tracking_info.all(): - item.item = self - item.pk = None - item.save() - @transaction.atomic def copyTestResultsFrom(self, other: StockItem, filters: dict | None = None): """Copy all test results from another StockItem.""" @@ -2764,6 +2814,12 @@ class StockItem( location = kwargs.get('location', self.location) notes = kwargs.get('notes') or '' + # Any references to other items (e.g. the merged item) must be recorded + # *before* the merged items are deleted below + from audit.helpers import serialize_deltas + + transfer_deltas = serialize_deltas(kwargs.pop('transfer_deltas', None) or {}) + parent_id = self.parent.pk if self.parent else None # Keep track of pricing data for the merged data @@ -2811,8 +2867,6 @@ class StockItem( other.delete() - transfer_deltas = kwargs.pop('transfer_deltas', None) - tracking_deltas = { 'quantity': float(self.quantity), 'added': float(merged_quantity), @@ -2823,14 +2877,6 @@ class StockItem( if transfer_deltas: tracking_deltas.update(transfer_deltas) - self.add_tracking_entry( - StockHistoryCode.MERGED_STOCK_ITEMS, - user, - quantity=self.quantity, - notes=notes, - deltas=tracking_deltas, - ) - # Update the location of the item self.location = location @@ -2856,7 +2902,14 @@ class StockItem( if quantity > 0: self.purchase_price = total_price / quantity - self.save() + with self.tracking_event( + StockHistoryCode.MERGED_STOCK_ITEMS, + user, + quantity=self.quantity, + notes=notes, + deltas=tracking_deltas, + ): + self.save() @transaction.atomic def splitStock(self, quantity, location=None, user=None, **kwargs): @@ -2936,7 +2989,7 @@ class StockItem( new_stock.parent = self # Create 'deltas' dict to record changes for tracking - deltas = {'stockitem': self.pk} + deltas = {'stockitem': self} new_stock._apply_model_reference_fields(kwargs, deltas) @@ -2946,25 +2999,27 @@ class StockItem( # Optional fields which can be supplied in a 'move' call new_stock._apply_optional_transfer_fields(kwargs, deltas) - new_stock.save(add_note=False) + if record_tracking: + # Record a stock tracking event for the newly created item + event = new_stock.tracking_event( + StockHistoryCode.SPLIT_FROM_PARENT, + user, + quantity=quantity, + notes=kwargs.get('notes', ''), + deltas=deltas, + ) + else: + event = nullcontext() + + with event: + new_stock.save(add_note=False) if split_transfer_deltas is not None: split_transfer_deltas.clear() split_transfer_deltas.update(deltas) if location: - split_transfer_deltas['location'] = location.pk - - if record_tracking: - # Add a stock tracking entry for the newly created item - new_stock.add_tracking_entry( - StockHistoryCode.SPLIT_FROM_PARENT, - user, - quantity=quantity, - notes=kwargs.get('notes', ''), - location=location, - deltas=deltas, - ) + split_transfer_deltas['location'] = location # Copy the test results of this part to the new one if kwargs.get('copy_test_results', True): @@ -3015,15 +3070,14 @@ class StockItem( ] def _apply_model_reference_fields(self, kwargs: dict, deltas: dict) -> None: - """Pop any model reference kwargs (see model_reference_fields) and record their pk in deltas.""" + """Pop any model reference kwargs (see model_reference_fields) and record them in deltas. + + Model instances are retained - these are recorded as references in the audit log. + """ for field in StockItem.model_reference_fields(): instance = deltas.pop(field, None) or kwargs.pop(field, None) - if instance is None: - continue - elif hasattr(instance, 'pk'): - deltas[field] = instance.pk - else: + if instance is not None: deltas[field] = instance def _resolve_status_kwarg(self, kwargs: dict): @@ -3061,11 +3115,8 @@ class StockItem( if field in kwargs: setattr(self, field, kwargs[field]) - # If the provided value is a model instance, store the primary key in the deltas dict - if hasattr(kwargs[field], 'pk'): - deltas[field] = kwargs[field].pk - else: - deltas[field] = kwargs[field] + # Model instances are retained (recorded as references in the audit log) + deltas[field] = kwargs[field] @transaction.atomic def move(self, location, notes, user, **kwargs): @@ -3138,7 +3189,7 @@ class StockItem( if same_location: tracking_code = StockHistoryCode.STOCK_UPDATE else: - tracking_info['location'] = location.pk + tracking_info['location'] = location status = self._resolve_status_kwarg(kwargs) self._apply_status_change(status, tracking_info) @@ -3147,9 +3198,10 @@ class StockItem( # Optional fields which can be supplied in a 'move' call self._apply_optional_transfer_fields(kwargs, tracking_info) - self.add_tracking_entry(tracking_code, user, notes=notes, deltas=tracking_info) - - self.save(add_note=False) + with self.tracking_event( + tracking_code, user, notes=notes, deltas=tracking_info + ): + self.save(add_note=False) # Trigger event for the plugin system trigger_event( @@ -3266,8 +3318,8 @@ class StockItem( if location and location != self.location: old_location = self.location self.location = location - tracking_info['location'] = location.pk - tracking_info['old_location'] = old_location.pk if old_location else None + tracking_info['location'] = location + tracking_info['old_location'] = old_location status = self._resolve_status_kwarg(kwargs) self._apply_status_change(status, tracking_info) @@ -3295,40 +3347,37 @@ class StockItem( self.stocktake_date = InvenTree.helpers.current_date() self.stocktake_user = user - raw_update_result = None if self.serialized else self.updateQuantity(count) - quantity_updated = self.serialized or raw_update_result + # Record the resulting quantity, whether or not this item survives the stocktake + # (i.e. even if the item is deleted, when counted to zero with delete_on_deplete set) + tracking_info['quantity'] = 1 if self.serialized else float(count) - # True only if updateQuantity() actually performed the save above - # (as opposed to: serialized item, no change, or item deleted) - update_persisted = raw_update_result is True - - # Record the resulting quantity, whether or not this item survived the stocktake - # (self.quantity is updated by updateQuantity() even if the item was deleted) - tracking_info['quantity'] = 1 if self.serialized else float(self.quantity) - - # Save if the quantity or any other field was changed, and updateQuantity() - # didn't already do so above. - # Note that updateQuantity() may have *deleted* the item (depleted to zero), - # in which case there is nothing left to save. - if self.pk and (quantity_updated or fields_updated): - if not update_persisted: - self.save(add_note=False) - - trigger_event( - StockEvents.ITEM_COUNTED, - id=self.id, - quantity=1 if self.serialized else float(self.quantity), - ) - - # Always record a tracking entry, even if the item was deleted as a result - # of this stocktake (e.g. counted to zero with delete_on_deplete set) - - # StockItemTracking.item uses SET_NULL so the history is retained regardless. - self.add_tracking_entry( + # Always record a tracking event (even if nothing was changed) + with self.tracking_event( StockHistoryCode.STOCK_COUNT, user, notes=kwargs.get('notes', ''), deltas=tracking_info, - ) + ): + raw_update_result = None if self.serialized else self.updateQuantity(count) + quantity_updated = self.serialized or raw_update_result + + # True only if updateQuantity() actually performed the save above + # (as opposed to: serialized item, no change, or item deleted) + update_persisted = raw_update_result is True + + # Save if the quantity or any other field was changed, and updateQuantity() + # didn't already do so above. + # Note that updateQuantity() may have *deleted* the item (depleted to zero), + # in which case there is nothing left to save. + if self.pk and (quantity_updated or fields_updated): + if not update_persisted: + self.save(add_note=False) + + trigger_event( + StockEvents.ITEM_COUNTED, + id=self.id, + quantity=1 if self.serialized else float(self.quantity), + ) @transaction.atomic def add_stock(self, quantity, user, **kwargs): @@ -3365,30 +3414,21 @@ class StockItem( self._apply_status_change(status, tracking_info) self._apply_model_reference_fields(kwargs, tracking_info) - # Determine up-front whether any optional fields will actually change, - # so we can skip the second save() below when updateQuantity() (which - # already writes the full row, including the status/reference field - # changes applied above) has already persisted everything that matters. - optional_fields_changed = any( - field in kwargs for field in StockItem.optional_transfer_fields() - ) + # Optional fields which can be supplied in an 'add' call + # Note: Applied *before* the quantity is updated, so that all changes + # are written in the single save() performed by updateQuantity() + self._apply_optional_transfer_fields(kwargs, tracking_info) - if self.updateQuantity(self.quantity + quantity): - tracking_info['added'] = float(quantity) - tracking_info['quantity'] = float(self.quantity) + tracking_info['added'] = float(quantity) + tracking_info['quantity'] = float(self.quantity + quantity) - # Optional fields which can be supplied in a 'stocktake' call - self._apply_optional_transfer_fields(kwargs, tracking_info) - - if optional_fields_changed: - self.save(add_note=False) - - self.add_tracking_entry( - StockHistoryCode.STOCK_ADD, - user, - notes=kwargs.get('notes', ''), - deltas=tracking_info, - ) + with self.tracking_event( + StockHistoryCode.STOCK_ADD, + user, + notes=kwargs.get('notes', ''), + deltas=tracking_info, + ): + self.updateQuantity(self.quantity + quantity) return True @@ -3439,35 +3479,26 @@ class StockItem( self._apply_status_change(status, deltas) self._apply_model_reference_fields(kwargs, deltas) - # Determine up-front whether any optional fields will actually change, - # so we can skip the second save() below when updateQuantity() (which - # already writes the full row, including the status/reference field - # changes applied above) has already persisted everything that matters. - optional_fields_changed = any( - field in kwargs for field in StockItem.optional_transfer_fields() - ) + # Optional fields which can be supplied in a 'remove' call + # Note: Applied *before* the quantity is updated, so that all changes + # are written in the single save() performed by updateQuantity() + self._apply_optional_transfer_fields(kwargs, deltas) - quantity_updated = self.updateQuantity(self.quantity - quantity) - - # Record the resulting quantity, whether or not this item survived the removal - # (self.quantity is updated by updateQuantity() even if the item was deleted) + # Record the resulting quantity, whether or not this item survives the removal deltas['removed'] = float(quantity) - deltas['quantity'] = float(self.quantity) + deltas['quantity'] = float(self.quantity - quantity) - if quantity_updated: - # Optional fields which can be supplied in a 'stocktake' call - self._apply_optional_transfer_fields(kwargs, deltas) - - if optional_fields_changed: - self.save(add_note=False) - - # Always record a tracking entry, even if the item was deleted as a result - # of this removal (e.g. depleted to zero with delete_on_deplete set) - - # StockItemTracking.item uses SET_NULL so the history is retained regardless. + # Always record a tracking event, even if the item is deleted as a result + # of this removal (e.g. depleted to zero with delete_on_deplete set) if record_tracking: - self.add_tracking_entry( + event = self.tracking_event( code, user, notes=kwargs.get('notes', ''), deltas=deltas ) + else: + event = nullcontext() + + with event: + self.updateQuantity(self.quantity - quantity) return True @@ -3663,152 +3694,6 @@ def after_save_stock_item(sender, instance: StockItem, created, **kwargs): instance.part.schedule_pricing_update(create=True) -# Context-local batch of pending StockItemTracking entries (see batch_tracking_entries()) -_tracking_batch: contextvars.ContextVar = contextvars.ContextVar( - 'tracking_batch', default=None -) - - -class TrackingEntryBatch: - """Collects StockItemTracking entries created within a batch_tracking_entries() scope.""" - - def __init__(self): - """Initialize an empty batch.""" - self.entries: list[StockItemTracking] = [] - - def add(self, entry: StockItemTracking) -> None: - """Record a single (unsaved) tracking entry against this batch.""" - self.entries.append(entry) - - def flush(self) -> None: - """Bulk-create all entries collected so far, in a single database write.""" - entries, self.entries = self.entries, [] - - if entries: - StockItemTracking.objects.bulk_create(entries, batch_size=250) - - -@contextmanager -def batch_tracking_entries(): - """Batch StockItemTracking entries created within this scope into a single bulk_create(). - - StockItem.add_tracking_entry() saves its entry immediately by default (commit=True). - While this context is active, any such call - made directly, or indirectly via a nested - function call - is queued instead of saved immediately. The queued entries are flushed via - a single bulk_create() when the current transaction commits (or immediately, if no - transaction is active). If the transaction is instead rolled back, the queued entries are - discarded, rather than being written for a change that never happened. - - Calls that already pass commit=False to add_tracking_entry() are unaffected either way - - that contract (the caller takes ownership of saving/collecting the entry itself, as done - for example in PurchaseOrder.receive_line_items()) is unchanged. - - Nesting is not supported: a nested batch_tracking_entries() call reuses the outer batch, - and only the outermost call schedules a flush. - - This mirrors plugin.base.event.events.batch_events() - see that docstring for the - reasoning behind the on-commit flush and the context-local (rather than parameter-based) - design, which allows existing single-item entrypoints (e.g. StockItem.stocktake()) to be - reused unmodified by both single-item and bulk callers. - - Yields: - The current TrackingEntryBatch instance - """ - if _tracking_batch.get() is not None: - # Already inside a batch - extend it, rather than creating a nested one - yield _tracking_batch.get() - return - - batch = TrackingEntryBatch() - token = _tracking_batch.set(batch) - - try: - yield batch - finally: - _tracking_batch.reset(token) - transaction.on_commit(batch.flush) - - -class StockItemTracking(InvenTree.models.InvenTreeModel): - """Stock tracking entry - used for tracking history of a particular StockItem. - - Attributes: - item: ForeignKey reference to a particular StockItem - part: ForeignKey reference to the Part associated with this StockItem - date: Date that this tracking info was created - tracking_type: The type of tracking information - notes: Associated notes (input by user) - user: The user associated with this tracking info - deltas: The changes associated with this history item - - Notes: - If the underlying stock item is deleted, the "item" field will be set to null, but the tracking information will be retained. - The tracking data will be removed if the associated part is deleted, as the tracking information is not relevant without the part context. - """ - - NOTES_MAX_LENGTH = 512 - - class Meta: - """Meta data for the StockItemTracking class.""" - - verbose_name = _('Stock Item Tracking') - - @staticmethod - def get_api_url(): - """Return API url.""" - return reverse('api-stock-tracking-list') - - def save(self, *args, **kwargs): - """Ensure that the 'part' link is always correct.""" - if self.item: - self.part = self.item.part - - super().save(*args, **kwargs) - - def get_absolute_url(self): - """Return url for instance.""" - return InvenTree.helpers.pui_url(f'/stock/item/{self.item.id}') - - def label(self): - """Return label.""" - if self.tracking_type in StockHistoryCode.keys(): # noqa: SIM118 - return StockHistoryCode.label(self.tracking_type) - - return getattr(self, 'title', '') - - tracking_type = models.IntegerField(default=StockHistoryCode.LEGACY) - - item = models.ForeignKey( - StockItem, - on_delete=models.SET_NULL, - null=True, - blank=False, - related_name='tracking_info', - ) - - part = models.ForeignKey( - 'part.part', - on_delete=models.CASCADE, - related_name='stock_tracking_info', - null=True, - blank=True, - ) - - date = models.DateTimeField(auto_now_add=True, editable=False) - - notes = models.CharField( - blank=True, - null=True, - max_length=NOTES_MAX_LENGTH, - verbose_name=_('Notes'), - help_text=_('Entry notes'), - ) - - user = models.ForeignKey(User, on_delete=models.SET_NULL, blank=True, null=True) - - deltas = models.JSONField(null=True, blank=True) - - def rename_stock_item_test_result_attachment(instance, filename): """Rename test result.""" return os.path.join( diff --git a/src/backend/InvenTree/stock/serializers.py b/src/backend/InvenTree/stock/serializers.py index 0eabb08dd6..a55a6db887 100644 --- a/src/backend/InvenTree/stock/serializers.py +++ b/src/backend/InvenTree/stock/serializers.py @@ -28,6 +28,7 @@ import part.models as part_models import part.serializers as part_serializers import stock.filters import stock.status_codes +from audit.helpers import audit_event, write_batched from common.settings import get_global_setting from generic.states.fields import InvenTreeCustomStatusSerializerMixin from importer.registry import register_importer @@ -44,14 +45,7 @@ from InvenTree.tasks import batch_offload_tasks from plugin.base.event.events import batch_events from users.serializers import UserSerializer -from .models import ( - StockItem, - StockItemTestResult, - StockItemTracking, - StockLocation, - StockLocationType, - batch_tracking_entries, -) +from .models import StockItem, StockItemTestResult, StockLocation, StockLocationType logger = structlog.get_logger('inventree') @@ -326,7 +320,7 @@ class StockItemSerializer( - Includes serialization for the item location """ - export_exclude_fields = ['tags', 'tracking_items'] + export_exclude_fields = ['tags'] SKIP_CREATE_FIELDS = ['duplicate'] @@ -400,7 +394,6 @@ class StockItemSerializer( 'supplier_part_detail', 'tags', 'tests', - 'tracking_items', ] read_only_fields = [ 'allocated', @@ -486,13 +479,7 @@ class StockItemSerializer( 'label': _('Copy Test Results'), 'help_text': _('Copy test results from the original stock item'), 'default': False, - }, - { - 'name': 'copy_history', - 'label': _('Copy History'), - 'help_text': _('Copy stock history from the original stock item'), - 'default': False, - }, + } ], ) @@ -559,7 +546,6 @@ class StockItemSerializer( ) # Annotate the queryset with the number of tracking items - queryset = queryset.annotate(tracking_items=SubqueryCount('tracking_info')) # Add flag to indicate if the StockItem has expired queryset = queryset.annotate( @@ -689,9 +675,6 @@ class StockItemSerializer( read_only=True, allow_null=True, label=_('Child Items') ) stale = serializers.BooleanField(read_only=True, allow_null=True, label=_('Stale')) - tracking_items = serializers.IntegerField( - read_only=True, allow_null=True, label=_('Tracking Items') - ) purchase_price = InvenTree.serializers.InvenTreeMoneySerializer( label=_('Purchase Price'), @@ -773,7 +756,7 @@ class SerializeStockItemSerializer(serializers.Serializer): notes = serializers.CharField( required=False, - max_length=StockItemTracking.NOTES_MAX_LENGTH, + max_length=StockItem.TRACKING_NOTES_MAX_LENGTH, allow_blank=True, label=_('Notes'), help_text=_('Optional note field'), @@ -841,7 +824,7 @@ class SerializeStockItemSerializer(serializers.Serializer): with ( transaction.atomic(), batch_events(), - batch_tracking_entries(), + write_batched(), batch_offload_tasks(), ): return ( @@ -877,7 +860,7 @@ class InstallStockItemSerializer(serializers.Serializer): ) note = serializers.CharField( - max_length=StockItemTracking.NOTES_MAX_LENGTH, + max_length=StockItem.TRACKING_NOTES_MAX_LENGTH, label=_('Note'), help_text=_('Add transaction note (optional)'), required=False, @@ -958,7 +941,7 @@ class UninstallStockItemSerializer(serializers.Serializer): ) note = serializers.CharField( - max_length=StockItemTracking.NOTES_MAX_LENGTH, + max_length=StockItem.TRACKING_NOTES_MAX_LENGTH, label=_('Notes'), help_text=_('Add transaction note (optional)'), required=False, @@ -1137,7 +1120,7 @@ class DisassembleStockItemSerializer(serializers.Serializer): notes = serializers.CharField( required=False, - max_length=StockItemTracking.NOTES_MAX_LENGTH, + max_length=StockItem.TRACKING_NOTES_MAX_LENGTH, allow_blank=True, label=_('Notes'), help_text=_('Optional note field'), @@ -1305,7 +1288,7 @@ class StockChangeStatusSerializer(serializers.Serializer): ) note = serializers.CharField( - max_length=StockItemTracking.NOTES_MAX_LENGTH, + max_length=StockItem.TRACKING_NOTES_MAX_LENGTH, label=_('Notes'), help_text=_('Add transaction note (optional)'), required=False, @@ -1325,55 +1308,25 @@ class StockChangeStatusSerializer(serializers.Serializer): note = data.get('note', '') - transaction_notes = [] - - now = InvenTree.helpers.current_time() - - # Instead of performing database updates for each item, - # perform bulk database updates (much more efficient) - # Pre-cache the custom status values (to reduce DB hits) custom_status_codes = StockItem.STATUS_CLASS.custom_values() - for item in items: - # Ignore items which are already in the desired status + # Audit log entries are written in a single query + with write_batched(): + for item in items: + # Ignore items which are already in the desired status - # Careful check for custom status codes also - if item.compare_status(status): - custom_status = item.get_custom_status() - if status == custom_status or custom_status is None: - continue + # Careful check for custom status codes also + if item.compare_status(status): + custom_status = item.get_custom_status() + if status == custom_status or custom_status is None: + continue - deltas = {'status': status} + item.set_status(status, custom_values=custom_status_codes) - # before save, track old status logical - deltas['old_status_logical'] = item.status - - if item.get_custom_status(): - deltas['old_status'] = item.get_custom_status() - else: - deltas['old_status'] = item.status - - item.set_status(status, custom_values=custom_status_codes) - item.save(add_note=False) - - # after save, can track new status_logical - deltas['status_logical'] = item.status - - # Create a new transaction note for each item - transaction_notes.append( - StockItemTracking( - item=item, - tracking_type=stock.status_codes.StockHistoryCode.EDITED.value, - date=now, - deltas=deltas, - user=user, - notes=note, - ) - ) - - # Create tracking entries - StockItemTracking.objects.bulk_create(transaction_notes, batch_size=250) + # The status change is recorded as a regular update (with the provided note) + with audit_event(item, None, notes=note, actor=user): + item.save(add_note=False) class StockLocationTypeSerializer(InvenTree.serializers.InvenTreeModelSerializer): @@ -1538,73 +1491,6 @@ class LocationSerializer( ) -@register_importer() -class StockTrackingSerializer( - InvenTree.serializers.FilterableSerializerMixin, - DataImportExportSerializerMixin, - InvenTree.serializers.InvenTreeModelSerializer, -): - """Serializer for StockItemTracking model.""" - - class Meta: - """Metaclass options.""" - - model = StockItemTracking - fields = [ - 'pk', - 'item', - 'item_detail', - 'part', - 'part_detail', - 'date', - 'deltas', - 'label', - 'notes', - 'tracking_type', - 'user', - 'user_detail', - ] - read_only_fields = ['date', 'part', 'user', 'label', 'tracking_type'] - - label = serializers.CharField(read_only=True) - - item_detail = OptionalField( - serializer_class=StockItemSerializer, - serializer_kwargs={ - 'source': 'item', - 'many': False, - 'read_only': True, - 'allow_null': True, - }, - prefetch_fields=['item', 'item__part'], - ) - - part_detail = OptionalField( - serializer_class=part_serializers.PartBriefSerializer, - serializer_kwargs={ - 'source': 'part', - 'many': False, - 'read_only': True, - 'allow_null': True, - }, - default_include=False, - prefetch_fields=['part'], - ) - - user_detail = OptionalField( - serializer_class=UserSerializer, - serializer_kwargs={ - 'source': 'user', - 'many': False, - 'read_only': True, - 'allow_null': True, - }, - prefetch_fields=['user'], - ) - - deltas = serializers.JSONField(read_only=True) - - class StockAssignmentItemSerializer(serializers.Serializer): """Serializer for a single StockItem with in StockAssignment request. @@ -1689,7 +1575,7 @@ class StockAssignmentSerializer(serializers.Serializer): notes = serializers.CharField( required=False, - max_length=StockItemTracking.NOTES_MAX_LENGTH, + max_length=StockItem.TRACKING_NOTES_MAX_LENGTH, allow_blank=True, label=_('Notes'), help_text=_('Stock assignment notes'), @@ -1779,7 +1665,7 @@ class StockMergeSerializer(serializers.Serializer): notes = serializers.CharField( required=False, - max_length=StockItemTracking.NOTES_MAX_LENGTH, + max_length=StockItem.TRACKING_NOTES_MAX_LENGTH, allow_blank=True, label=_('Notes'), help_text=_('Stock merging notes'), @@ -1989,7 +1875,7 @@ class StockAdjustmentSerializer(serializers.Serializer): notes = serializers.CharField( required=False, - max_length=StockItemTracking.NOTES_MAX_LENGTH, + max_length=StockItem.TRACKING_NOTES_MAX_LENGTH, allow_blank=True, label=_('Notes'), help_text=_('Stock transaction notes'), @@ -2073,7 +1959,7 @@ class StockCountSerializer(StockAdjustmentSerializer): with ( transaction.atomic(), batch_events(), - batch_tracking_entries(), + write_batched(), batch_offload_tasks(), ): for item in items: @@ -2106,7 +1992,7 @@ class StockAddSerializer(StockAdjustmentSerializer): with ( transaction.atomic(), batch_events(), - batch_tracking_entries(), + write_batched(), batch_offload_tasks(), ): for item in data['items']: @@ -2140,7 +2026,7 @@ class StockRemoveSerializer(StockAdjustmentSerializer): with ( transaction.atomic(), batch_events(), - batch_tracking_entries(), + write_batched(), batch_offload_tasks(), ): for item in data['items']: @@ -2202,7 +2088,7 @@ class StockTransferSerializer(StockAdjustmentSerializer): with ( transaction.atomic(), batch_events(), - batch_tracking_entries(), + write_batched(), batch_offload_tasks(), ): for item in items: @@ -2251,10 +2137,10 @@ class StockTransferSerializer(StockAdjustmentSerializer): merge_kwargs['transfer_deltas'] = transfer_deltas target.merge_stock_items([piece], **merge_kwargs) else: - transfer_deltas = {'stockitem': stock_item.pk} + transfer_deltas = {'stockitem': stock_item} if location: - transfer_deltas['location'] = location.pk + transfer_deltas['location'] = location for field_name in StockItem.optional_transfer_fields(): if field_name in kwargs: diff --git a/src/backend/InvenTree/stock/tasks.py b/src/backend/InvenTree/stock/tasks.py deleted file mode 100644 index d56fed9630..0000000000 --- a/src/backend/InvenTree/stock/tasks.py +++ /dev/null @@ -1,36 +0,0 @@ -"""Background tasks for the stock app.""" - -from datetime import datetime, timedelta - -import structlog -from opentelemetry import trace - -from common.settings import get_global_setting -from InvenTree.tasks import ScheduledTask, scheduled_task - -tracer = trace.get_tracer(__name__) -logger = structlog.get_logger('inventree') - - -@tracer.start_as_current_span('delete_old_stock_tracking') -@scheduled_task(ScheduledTask.DAILY) -def delete_old_stock_tracking(): - """Remove old stock tracking entries before a certain date.""" - from stock.models import StockItemTracking - - if not get_global_setting('STOCK_TRACKING_DELETE_OLD_ENTRIES', False): - return - - delete_n_days = int(get_global_setting('STOCK_TRACKING_DELETE_DAYS', 365)) - - threshold = datetime.now() - timedelta(days=delete_n_days) - - old_entries = StockItemTracking.objects.filter(date__lte=threshold) - - if old_entries.exists(): - logger.info( - 'Deleting old stock tracking entries', - count=old_entries.count(), - threshold=threshold, - ) - old_entries.delete() diff --git a/src/backend/InvenTree/stock/test_api.py b/src/backend/InvenTree/stock/test_api.py index a3ae6bfd7e..c38ebce1c8 100644 --- a/src/backend/InvenTree/stock/test_api.py +++ b/src/backend/InvenTree/stock/test_api.py @@ -1,8 +1,8 @@ """Unit testing for the Stock API.""" import os -import random from datetime import datetime, timedelta +from decimal import Decimal from django.contrib.contenttypes.models import ContentType from django.core.exceptions import ValidationError @@ -23,12 +23,13 @@ from InvenTree.unit_test import ( InvenTreeAPIPerformanceTestCase, InvenTreeAPITestCase, findOffloadedEvent, + stock_events, + stock_history, ) from part.models import Part, PartTestTemplate from stock.models import ( StockItem, StockItemTestResult, - StockItemTracking, StockLocation, StockLocationType, ) @@ -1621,9 +1622,10 @@ class StockItemTest(StockAPITestCase): # Check that the item was associated with the correct user item = StockItem.objects.get(pk=response.data[0]['pk']) - self.assertEqual(item.tracking_info_count, 1) - tracking = item.tracking_info.first() - self.assertEqual(tracking.user, self.user) + self.assertEqual(stock_events(item).count(), 1) + tracking = stock_events(item).first() + self.assertEqual(tracking.event_type, StockHistoryCode.CREATED) + self.assertEqual(tracking.actor, self.user) # What if we explicitly set the location to a different value? @@ -2110,7 +2112,7 @@ class StockItemTest(StockAPITestCase): def test_note_max_length(self): """Test that overly long transaction notes are rejected with a validation error.""" - max_length = StockItemTracking.NOTES_MAX_LENGTH + max_length = StockItem.TRACKING_NOTES_MAX_LENGTH note = 'x' * (max_length + 1) for url, data in [ @@ -2139,7 +2141,7 @@ class StockItemTest(StockAPITestCase): sub_part = part.models.Part.objects.get(pk=50) sub_item = StockItem.objects.create(part=sub_part, serial='xyz-123', quantity=1) - n_entries = sub_item.tracking_info.count() + n_entries = stock_events(sub_item).count() self.assertIsNone(sub_item.belongs_to) @@ -2170,7 +2172,11 @@ class StockItemTest(StockAPITestCase): self.assertEqual(sub_item.belongs_to, item) - self.assertEqual(n_entries + 1, sub_item.tracking_info.count()) + self.assertEqual(n_entries + 1, stock_events(sub_item).count()) + self.assertEqual( + stock_events(sub_item).last().event_type, + StockHistoryCode.INSTALLED_INTO_ASSEMBLY, + ) # Try to install again - this time, should fail because the StockItem is not available! response = self.post( @@ -2215,7 +2221,7 @@ class StockItemTest(StockAPITestCase): item.customer = customer item.save() - n_entries = item.tracking_info_count + n_entries = stock_events(item).count() url = reverse('api-stock-return') @@ -2249,8 +2255,12 @@ class StockItemTest(StockAPITestCase): item.refresh_from_db() - # A new stock tracking entry should have been created - self.assertEqual(n_entries + 1, item.tracking_info_count) + # A new stock tracking event should have been recorded + self.assertEqual(n_entries + 1, stock_events(item).count()) + + entry = stock_events(item).last() + self.assertEqual(entry.event_type, StockHistoryCode.RETURNED_TO_STOCK) + self.assertEqual(entry.notes, 'Returned from this customer for testing') # The item is now in stock self.assertIsNone(item.customer) @@ -2300,6 +2310,18 @@ class StockItemTest(StockAPITestCase): stock_item.refresh_from_db() self.assertEqual(stock_item.part, variant) + def status_change(self, item): + """Return the most recent audit entry for the provided stock item.""" + from audit.models import AuditLogEntry + + return ( + AuditLogEntry.objects + .get_for_object(item) + .filter(action=AuditLogEntry.Action.UPDATE) + .order_by('pk') + .last() + ) + def test_set_status(self): """Test API endpoint for setting StockItem status.""" url = reverse('api-stock-change-status') @@ -2317,11 +2339,12 @@ class StockItemTest(StockAPITestCase): for item in items: item.refresh_from_db() self.assertEqual(item.status, StockStatus.OK.value) - self.assertEqual(item.tracking_info.count(), 1) + self.assertIsNone(self.status_change(item)) data = { 'items': [item.pk for item in items], 'status': StockStatus.DAMAGED.value, + 'note': 'Damaged in transit', } self.post(url, data, expected_code=201) @@ -2330,21 +2353,23 @@ class StockItemTest(StockAPITestCase): for item in items: item.refresh_from_db() self.assertEqual(item.status, StockStatus.DAMAGED.value) - self.assertEqual(item.tracking_info.count(), 2) - tracking = item.tracking_info.last() - self.assertEqual(tracking.deltas['old_status'], StockStatus.OK.value) + + # The status change is recorded as a regular update + entry = self.status_change(item) + self.assertIsNone(entry.event_type) + self.assertEqual(entry.actor, self.user) + self.assertEqual(entry.notes, 'Damaged in transit') self.assertEqual( - tracking.deltas['old_status_logical'], StockStatus.OK.value - ) - self.assertEqual(tracking.deltas['status'], StockStatus.DAMAGED.value) - self.assertEqual( - tracking.deltas['status_logical'], StockStatus.DAMAGED.value + entry.changes_dict['status'], + [StockStatus.OK.value, StockStatus.DAMAGED.value], ) # Same test, but with one item unchanged items[0].set_status(StockStatus.ATTENTION.value) items[0].save() + unchanged = self.status_change(items[0]) + data['status'] = StockStatus.ATTENTION.value self.post(url, data, expected_code=201) @@ -2352,10 +2377,15 @@ class StockItemTest(StockAPITestCase): for item in items: item.refresh_from_db() self.assertEqual(item.status, StockStatus.ATTENTION.value) - self.assertEqual(item.tracking_info.count(), 3) - tracking = item.tracking_info.last() - self.assertEqual(tracking.tracking_type, StockHistoryCode.EDITED.value) + # No entry is recorded for the unchanged item + self.assertEqual(self.status_change(items[0]).pk, unchanged.pk) + + for item in items[1:]: + self.assertEqual( + self.status_change(item).changes_dict['status'], + [StockStatus.DAMAGED.value, StockStatus.ATTENTION.value], + ) def test_set_custom_status(self): """Test API endpoint for setting StockItem custom status.""" @@ -2374,7 +2404,6 @@ class StockItemTest(StockAPITestCase): for item in items: item.refresh_from_db() self.assertEqual(item.status, StockStatus.OK.value) - self.assertEqual(item.tracking_info.count(), 1) # Test tracking with custom status # *from* standard *to* custom @@ -2389,35 +2418,36 @@ class StockItemTest(StockAPITestCase): item.refresh_from_db() self.assertEqual(item.status, self.inspect_custom_status.logical_key) self.assertEqual(item.get_custom_status(), self.inspect_custom_status.key) - tracking = item.tracking_info.last() - self.assertEqual(tracking.deltas['old_status'], StockStatus.OK.value) + + changes = self.status_change(item).changes_dict + self.assertEqual( - tracking.deltas['old_status_logical'], StockStatus.OK.value + changes['status'], + [StockStatus.OK.value, self.inspect_custom_status.logical_key], ) - self.assertEqual(tracking.deltas['status'], self.inspect_custom_status.key) self.assertEqual( - tracking.deltas['status_logical'], - self.inspect_custom_status.logical_key, + changes['status_custom_key'], [None, self.inspect_custom_status.key] ) # reverse case # *from* custom *to* standard data['status'] = StockStatus.OK.value self.post(url, data, expected_code=201) + for item in items: item.refresh_from_db() self.assertEqual(item.status, StockStatus.OK.value) self.assertIsNone(item.get_custom_status()) - tracking = item.tracking_info.last() + + changes = self.status_change(item).changes_dict + self.assertEqual( - tracking.deltas['old_status'], self.inspect_custom_status.key + changes['status'], + [self.inspect_custom_status.logical_key, StockStatus.OK.value], ) self.assertEqual( - tracking.deltas['old_status_logical'], - self.inspect_custom_status.logical_key, + changes['status_custom_key'], [self.inspect_custom_status.key, None] ) - self.assertEqual(tracking.deltas['status'], StockStatus.OK.value) - self.assertEqual(tracking.deltas['status_logical'], StockStatus.OK.value) def test_bulk_batch_change(self): """Test that we can bulk-change batch code for a set of stock items.""" @@ -2618,18 +2648,18 @@ class StockItemDisassembleTest(StockAPITestCase): # Check stock tracking entries have been created self.assertTrue( - self.item.tracking_info.filter( - tracking_type=StockHistoryCode.DISASSEMBLED.value - ).exists() + stock_events(self.item, event_type=StockHistoryCode.DISASSEMBLED).exists() ) for item in [item_1, item_50]: - entry = item.tracking_info.filter( - tracking_type=StockHistoryCode.CREATED_FROM_DISASSEMBLY.value + entry = stock_events( + item, event_type=StockHistoryCode.CREATED_FROM_DISASSEMBLY ).first() self.assertIsNotNone(entry) - self.assertEqual(entry.deltas['stockitem'], self.item.pk) + self.assertEqual( + entry.additional_data['deltas']['stockitem']['pk'], self.item.pk + ) def test_full_disassembly(self): """Test that a fully disassembled item is retained at zero quantity.""" @@ -2675,13 +2705,13 @@ class StockItemDisassembleTest(StockAPITestCase): # an output of the build) - instead it is recorded in the stock history self.assertIsNone(component.build) - entry = component.tracking_info.filter( - tracking_type=StockHistoryCode.CREATED_FROM_DISASSEMBLY.value + entry = stock_events( + component, event_type=StockHistoryCode.CREATED_FROM_DISASSEMBLY ).first() self.assertIsNotNone(entry) - self.assertEqual(entry.deltas['buildorder'], bo.pk) - self.assertEqual(entry.deltas['purchaseorder'], po.pk) + self.assertEqual(entry.additional_data['deltas']['buildorder']['pk'], bo.pk) + self.assertEqual(entry.additional_data['deltas']['purchaseorder']['pk'], po.pk) def test_explicit_pricing(self): """Test that automatic cost allocation is skipped if explicit pricing is provided.""" @@ -2750,23 +2780,23 @@ class StockItemDisassembleTest(StockAPITestCase): # Uninstalled items are updated, not created afresh self.assertTrue( - sub_1.tracking_info.filter( - tracking_type=StockHistoryCode.REMOVED_FROM_ASSEMBLY.value + stock_events( + sub_1, event_type=StockHistoryCode.REMOVED_FROM_ASSEMBLY ).exists() ) self.assertFalse( - sub_1.tracking_info.filter( - tracking_type=StockHistoryCode.CREATED_FROM_DISASSEMBLY.value + stock_events( + sub_1, event_type=StockHistoryCode.CREATED_FROM_DISASSEMBLY ).exists() ) self.assertTrue( - self.item.tracking_info.filter( - tracking_type=StockHistoryCode.REMOVED_CHILD_ITEM.value + stock_events( + self.item, event_type=StockHistoryCode.REMOVED_CHILD_ITEM ).exists() ) self.assertTrue( - new_item.tracking_info.filter( - tracking_type=StockHistoryCode.CREATED_FROM_DISASSEMBLY.value + stock_events( + new_item, event_type=StockHistoryCode.CREATED_FROM_DISASSEMBLY ).exists() ) @@ -3017,7 +3047,7 @@ class StocktakeTest(StockAPITestCase): def test_notes_max_length(self): """Test that overly long notes are rejected with a validation error.""" - max_length = StockItemTracking.NOTES_MAX_LENGTH + max_length = StockItem.TRACKING_NOTES_MAX_LENGTH for endpoint in [ 'api-stock-count', @@ -3130,12 +3160,9 @@ class StocktakeTest(StockAPITestCase): item_a.refresh_from_db() self.assertEqual(item_a.location.pk, 1) - # Tracking entry records the location change - entry = StockItemTracking.objects.filter( - item=item_a, tracking_type=StockHistoryCode.STOCK_COUNT - ).latest('date') - self.assertEqual(entry.deltas.get('location'), 1) - self.assertEqual(entry.deltas.get('old_location'), 5) + # The stocktake event records the location change + entry = stock_events(item_a, event_type=StockHistoryCode.STOCK_COUNT).last() + self.assertEqual(entry.changes_dict['location'], [5, 1]) # --- location is updated for multiple items simultaneously --- response = self.post( @@ -3156,13 +3183,10 @@ class StocktakeTest(StockAPITestCase): self.assertEqual(item_a.location.pk, 2) self.assertEqual(item_b.location.pk, 2) - # Both items have a tracking entry with the new location + # Both items have a stocktake event with the new location for item, old_loc in [(item_a, 1), (item_b, 3)]: - entry = StockItemTracking.objects.filter( - item=item, tracking_type=StockHistoryCode.STOCK_COUNT - ).latest('date') - self.assertEqual(entry.deltas.get('location'), 2) - self.assertEqual(entry.deltas.get('old_location'), old_loc) + entry = stock_events(item, event_type=StockHistoryCode.STOCK_COUNT).last() + self.assertEqual(entry.changes_dict['location'], [old_loc, 2]) # --- location is unchanged when not provided --- response = self.post( @@ -3173,12 +3197,10 @@ class StocktakeTest(StockAPITestCase): # Location should still be 2 (unchanged from the previous count) self.assertEqual(item_a.location.pk, 2) - # Tracking entry has no location delta when location was not provided - entry = StockItemTracking.objects.filter( - item=item_a, tracking_type=StockHistoryCode.STOCK_COUNT - ).latest('date') - self.assertNotIn('location', entry.deltas) - self.assertNotIn('old_location', entry.deltas) + # No location change is recorded when location was not provided + entry = stock_events(item_a, event_type=StockHistoryCode.STOCK_COUNT).last() + self.assertNotIn('location', entry.changes_dict) + self.assertNotIn('location', (entry.additional_data or {}).get('deltas', {})) # --- structural location is rejected --- structural = StockLocation.objects.create(name='Structural', structural=True) @@ -3234,14 +3256,13 @@ class StocktakeTest(StockAPITestCase): self.assertIsNotNone(item.stocktake_date) # The stock history entry must agree with the saved state - entry = StockItemTracking.objects.filter( - item=item, tracking_type=StockHistoryCode.STOCK_COUNT - ).latest('date') - self.assertEqual(entry.deltas.get('quantity'), float(quantity)) - self.assertEqual(entry.deltas.get('location'), 1) - self.assertEqual(entry.deltas.get('old_location'), 5) - self.assertEqual(entry.deltas.get('status'), StockStatus.DAMAGED.value) - self.assertEqual(entry.deltas.get('old_status'), StockStatus.OK.value) + entry = stock_events(item, event_type=StockHistoryCode.STOCK_COUNT).last() + self.assertEqual(entry.additional_data['deltas']['quantity'], float(quantity)) + self.assertEqual(entry.changes_dict['location'], [5, 1]) + self.assertEqual( + entry.changes_dict['status'], + [StockStatus.OK.value, StockStatus.DAMAGED.value], + ) def test_bulk_count_query_benchmark(self): """Benchmark: measure the number of DB queries required to count 100 stock items at once.""" @@ -3475,9 +3496,6 @@ class StockTransferMergeTest(StockAPITestCase): def test_transfer_merge_does_not_copy_source_tracking(self): """Transfer merge keeps destination history and adds a single merge entry.""" - # Track total number of tracking entries created - N_TRACKING_ENTRIES = StockItemTracking.objects.count() - existing = StockItem.objects.create( part=self.part, location=self.dest, quantity=100 ) @@ -3490,7 +3508,7 @@ class StockTransferMergeTest(StockAPITestCase): ) incoming_pk = incoming.pk - tracking_count = existing.tracking_info.count() + tracking_count = stock_events(existing).count() self.post( self.url, @@ -3503,35 +3521,39 @@ class StockTransferMergeTest(StockAPITestCase): existing.refresh_from_db() + # The history of the incoming item is not copied to the existing item self.assertFalse( - existing.tracking_info.filter(notes='Source tracking entry').exists() + stock_history(existing, notes='Source tracking entry').exists() ) - self.assertEqual(existing.tracking_info.count(), tracking_count + 1) - merge_entry = existing.tracking_info.filter( - tracking_type=StockHistoryCode.MERGED_STOCK_ITEMS + self.assertEqual(stock_events(existing).count(), tracking_count + 1) + + merge_entry = stock_events( + existing, event_type=StockHistoryCode.MERGED_STOCK_ITEMS ).first() + self.assertIsNotNone(merge_entry) - self.assertEqual(merge_entry.deltas['added'], 50.0) - self.assertEqual(merge_entry.deltas['quantity'], 150.0) - self.assertEqual(merge_entry.deltas['stockitem'], incoming_pk) - self.assertEqual(merge_entry.deltas['location'], self.dest.pk) + self.assertEqual( + [Decimal(value) for value in merge_entry.changes_dict['quantity']], + [100, 150], + ) - self.assertEqual(StockItemTracking.objects.count(), N_TRACKING_ENTRIES + 4) + deltas = merge_entry.additional_data['deltas'] + self.assertEqual(deltas['added'], 50.0) + self.assertEqual(deltas['stockitem']['pk'], incoming_pk) + self.assertEqual(deltas['location']['pk'], self.dest.pk) - # Ensure tracking entries were bulk created in the correct order - entries = list(StockItemTracking.objects.order_by('-pk')[:4])[::-1] + # Ensure the expected events were recorded (in order) + self.assertEqual( + list(stock_events(existing).values_list('event_type', flat=True)), + [StockHistoryCode.CREATED, StockHistoryCode.MERGED_STOCK_ITEMS], + ) + self.assertEqual( + list(stock_events(incoming_pk).values_list('event_type', flat=True)), + [StockHistoryCode.CREATED, StockHistoryCode.STOCK_UPDATE], + ) - for idx, tt in enumerate([ - StockHistoryCode.CREATED, - StockHistoryCode.CREATED, - StockHistoryCode.STOCK_UPDATE, - StockHistoryCode.MERGED_STOCK_ITEMS, - ]): - self.assertEqual( - entries[idx].tracking_type, - tt, - f'Entry {idx} has unexpected tracking type {entries[idx].tracking_type}', - ) + # The incoming item was deleted (as part of the merge) + self.assertTrue(stock_history(incoming_pk, action=2).exists()) def test_transfer_merge_partial_reuses_split_transfer_deltas(self): """Partial merge reuses split transfer deltas on the merge tracking entry.""" @@ -3554,14 +3576,18 @@ class StockTransferMergeTest(StockAPITestCase): incoming.refresh_from_db() self.assertEqual(incoming.quantity, 70) - merge_entry = existing.tracking_info.filter( - tracking_type=StockHistoryCode.MERGED_STOCK_ITEMS + merge_entry = stock_events( + existing, event_type=StockHistoryCode.MERGED_STOCK_ITEMS ).first() - self.assertEqual(merge_entry.deltas['stockitem'], incoming.pk) - self.assertEqual(merge_entry.deltas['location'], self.dest.pk) + + deltas = merge_entry.additional_data['deltas'] + self.assertEqual(deltas['stockitem']['pk'], incoming.pk) + self.assertEqual(deltas['location']['pk'], self.dest.pk) + + # No split event is recorded against the source item self.assertFalse( - incoming.tracking_info.filter( - tracking_type=StockHistoryCode.SPLIT_CHILD_ITEM + stock_events( + incoming, event_type=StockHistoryCode.SPLIT_CHILD_ITEM ).exists() ) @@ -4089,127 +4115,6 @@ class StockTestResultTest(StockAPITestCase): ) -class StockTrackingTest(StockAPITestCase): - """Tests for the StockTracking API endpoints.""" - - fixtures = [ - *StockAPITestCase.fixtures, - 'build', - 'order', - 'return_order', - 'sales_order', - ] - - @classmethod - def setUpTestData(cls): - """Initialize some test data for the StockTracking tests.""" - super().setUpTestData() - - import build.models - import company.models - import order.models - import stock.models - import stock.status_codes - - entries = [] - - N_BO = build.models.Build.objects.count() - N_PO = order.models.PurchaseOrder.objects.count() - N_RO = order.models.ReturnOrder.objects.count() - N_SO = order.models.SalesOrder.objects.count() - - N_COMPANY = company.models.Company.objects.count() - N_LOCATION = stock.models.StockLocation.objects.count() - - # Generate a large quantity of tracking items - # Note that the pk values are not guaranteed to exist in the database - for item in StockItem.objects.all(): - for i in range(50): - entries.append( - StockItemTracking( - item=item, - notes='This is a test entry', - tracking_type=stock.status_codes.StockHistoryCode.LEGACY.value, - deltas={ - 'quantity': 50 - i, - 'buildorder': random.randint(0, N_BO + 1), - 'purchaseorder': random.randint(0, N_PO + 1), - 'returnorder': random.randint(0, N_RO + 1), - 'salesorder': random.randint(0, N_SO + 1), - 'customer': random.randint(0, N_COMPANY + 1), - 'location': random.randint(0, N_LOCATION + 1), - }, - ) - ) - - StockItemTracking.objects.bulk_create(entries) - - def get_url(self): - """Helper function to get stock tracking api url.""" - return reverse('api-stock-tracking-list') - - def test_count(self): - """Test list endpoint with limit = 1.""" - url = self.get_url() - - N = StockItemTracking.objects.count() - - # Test counting - response = self.get(url, {'limit': 1}) - self.assertEqual(response.data['count'], N) - - # Test with search and pagination - response = self.get(url, {'limit': 1, 'offset': 10, 'search': 'berries'}) - - self.assertEqual(response.data['count'], 0) - - def test_list(self): - """Test list endpoint.""" - url = self.get_url() - - N = StockItemTracking.objects.count() - self.assertGreater(N, 1000) - - response = self.client.get(url, max_query_count=25) - self.assertEqual(len(response.data), N) - - # Check expected delta values - keys = ['quantity', 'returnorder', 'buildorder', 'customer'] - - for item in response.data: - deltas = item['deltas'] - - for key in keys: - self.assertIn(key, deltas) - self.assertIsNotNone(deltas.get(key, None)) - - def test_output_options(self): - """Test output options.""" - self.run_output_test( - self.get_url(), - ['item_detail', 'user_detail'], - additional_params={'limit': 2}, - assert_fnc=lambda x: x.data['results'][0], - ) - - def test_status_codes_endpoint(self): - """The 'track/status/' endpoint must resolve to the status-codes view. - - Regression test: ensures the literal 'status/' path is not shadowed by the - 'track//' detail route it sits alongside in the same urlconf. - """ - response = self.get( - reverse('api-stock-tracking-status-codes'), expected_code=200 - ) - - self.assertIn('status_class', response.data) - self.assertIn('values', response.data) - self.assertIn('CREATED', response.data['values']) - self.assertEqual( - response.data['values']['CREATED']['key'], StockHistoryCode.CREATED.value - ) - - class StockAssignTest(StockAPITestCase): """Unit tests for the stock assignment API endpoint, where stock items are manually assigned to a customer.""" diff --git a/src/backend/InvenTree/stock/test_audit.py b/src/backend/InvenTree/stock/test_audit.py new file mode 100644 index 0000000000..d235cf225a --- /dev/null +++ b/src/backend/InvenTree/stock/test_audit.py @@ -0,0 +1,569 @@ +"""Audit log tests for the StockItem model (stock tracking events).""" + +from decimal import Decimal + +from django.contrib.contenttypes.models import ContentType +from django.urls import reverse + +from audit.models import AuditLogEntry +from InvenTree.unit_test import InvenTreeAPITestCase +from part.models import Part +from stock.models import StockItem, StockLocation +from stock.status_codes import StockHistoryCode, StockStatus + +ACTION = AuditLogEntry.Action + + +class StockAuditTestBase(InvenTreeAPITestCase): + """Base class for stock item audit tests.""" + + roles = ['stock.view', 'stock.add', 'stock.change', 'stock.delete', 'part.view'] + + @classmethod + def setUpTestData(cls): + """Create test data.""" + super().setUpTestData() + + cls.part = Part.objects.create( + name='Audit Widget', description='A widget', component=True + ) + cls.assembly = Part.objects.create( + name='Audit Assembly', description='An assembly', assembly=True + ) + cls.trackable = Part.objects.create( + name='Audit Trackable', description='A trackable part', trackable=True + ) + + cls.loc_a = StockLocation.objects.create(name='Location A') + cls.loc_b = StockLocation.objects.create(name='Location B') + + def setUp(self): + """Create a stock item for each test.""" + super().setUp() + + self.item = StockItem.objects.create( + part=self.part, quantity=100, location=self.loc_a + ) + + def entries(self, item): + """Return the audit entries for a stock item (oldest first).""" + return AuditLogEntry.objects.filter( + content_type=ContentType.objects.get_for_model(StockItem), + object_pk=str(item.pk), + ).order_by('pk') + + def new_entries(self, item, n): + """Return the audit entries for a stock item, recorded after the first n entries.""" + return list(self.entries(item)[n:]) + + +class StockAuditTest(StockAuditTestBase): + """Stock operations are recorded as named events in the audit log. + + Each operation records a single audit entry for each affected stock item. + """ + + def test_create(self): + """Creating a stock item records a single CREATE entry, with the CREATED event.""" + [entry] = self.entries(self.item) + + self.assertEqual(entry.action, ACTION.CREATE) + self.assertEqual(entry.event_type, StockHistoryCode.CREATED) + self.assertEqual(entry.event_label, 'Stock item created') + + # The entry is linked to the part + self.assertEqual(entry.parent_object_pk, str(self.part.pk)) + + # Derived fields are not recorded + self.assertNotIn('serial_int', entry.changes_dict) + self.assertNotIn('barcode_hash', entry.changes_dict) + + def test_create_via_api(self): + """Creating a stock item via the API records the user.""" + response = self.post( + reverse('api-stock-list'), + {'part': self.part.pk, 'quantity': 5, 'location': self.loc_a.pk}, + expected_code=201, + ) + + item = StockItem.objects.get(pk=response.data[0]['pk']) + + [entry] = self.entries(item) + + self.assertEqual(entry.event_type, StockHistoryCode.CREATED) + self.assertEqual(entry.actor, self.user) + + def test_create_serialized_via_api(self): + """Serialized items created via the API are recorded (with status).""" + response = self.post( + reverse('api-stock-list'), + { + 'part': self.trackable.pk, + 'quantity': 3, + 'serial_numbers': '1-3', + 'location': self.loc_a.pk, + 'status': StockStatus.DAMAGED.value, + }, + expected_code=201, + ) + + self.assertEqual(len(response.data), 3) + + for data in response.data: + item = StockItem.objects.get(pk=data['pk']) + self.assertEqual(item.status, StockStatus.DAMAGED.value) + + # A single entry, which records the creation (including the status) + [entry] = self.entries(item) + + self.assertEqual(entry.action, ACTION.CREATE) + self.assertEqual(entry.event_type, StockHistoryCode.CREATED) + self.assertEqual(entry.actor, self.user) + self.assertEqual(entry.changes_dict['status'][1], StockStatus.DAMAGED.value) + + def test_move(self): + """Moving the entire stock item records a single entry.""" + n = self.entries(self.item).count() + + self.item.move(self.loc_b, 'Moved', self.user) + + [entry] = self.new_entries(self.item, n) + + self.assertEqual(entry.action, ACTION.UPDATE) + self.assertEqual(entry.event_type, StockHistoryCode.STOCK_MOVE) + self.assertEqual(entry.notes, 'Moved') + self.assertEqual(entry.actor, self.user) + self.assertEqual(entry.changes_dict['location'], [self.loc_a.pk, self.loc_b.pk]) + + # The location is recorded as a field change - not duplicated in the deltas + self.assertEqual(entry.additional_data['deltas'], {'quantity': 100.0}) + + def test_partial_move(self): + """Moving part of a stock item records an entry for each item.""" + n = self.entries(self.item).count() + + self.item.move(self.loc_b, 'Split', self.user, quantity=30) + + new_item = StockItem.objects.get(parent=self.item) + + # The original item + [entry] = self.new_entries(self.item, n) + + self.assertEqual(entry.event_type, StockHistoryCode.SPLIT_CHILD_ITEM) + self.assertEqual(entry.changes_dict['quantity'], ['100.00000', '70.00000']) + self.assertEqual( + entry.additional_data['deltas']['stockitem'], + {'model_type': 'stock.stockitem', 'pk': new_item.pk, 'str': str(new_item)}, + ) + + # The new item + [new_entry] = self.entries(new_item) + + self.assertEqual(new_entry.action, ACTION.CREATE) + self.assertEqual(new_entry.event_type, StockHistoryCode.SPLIT_FROM_PARENT) + self.assertEqual(new_entry.changes_dict['location'][1], self.loc_b.pk) + + def test_add_remove_stock(self): + """Adding / removing stock records a single entry for each operation.""" + n = self.entries(self.item).count() + + self.item.add_stock(10, self.user, notes='Added', batch='B-123') + self.item.take_stock(5, self.user, notes='Removed') + + add, remove = self.new_entries(self.item, n) + + self.assertEqual(add.event_type, StockHistoryCode.STOCK_ADD) + self.assertEqual(add.changes_dict['quantity'], ['100.00000', '110.00000']) + self.assertEqual(add.changes_dict['batch'], ['', 'B-123']) + self.assertEqual(add.additional_data['deltas'], {'added': 10.0}) + + self.assertEqual(remove.event_type, StockHistoryCode.STOCK_REMOVE) + self.assertEqual(remove.notes, 'Removed') + self.assertEqual(remove.additional_data['deltas'], {'removed': 5.0}) + + def test_stocktake(self): + """A stocktake is always recorded - even if the quantity is unchanged.""" + n = self.entries(self.item).count() + + # Unchanged quantity + self.item.stocktake(100, self.user, notes='Counted') + + # Changed quantity + self.item.stocktake(90, self.user) + + unchanged, changed = self.new_entries(self.item, n) + + self.assertEqual(unchanged.event_type, StockHistoryCode.STOCK_COUNT) + self.assertEqual(unchanged.notes, 'Counted') + + # Nothing has changed - but the event (and counted quantity) is recorded + self.assertEqual(unchanged.changes_dict, {}) + self.assertEqual(unchanged.additional_data['deltas'], {'quantity': 100.0}) + + self.assertEqual(changed.event_type, StockHistoryCode.STOCK_COUNT) + self.assertEqual( + [Decimal(value) for value in changed.changes_dict['quantity']], + [Decimal(100), Decimal(90)], + ) + + def test_deplete(self): + """Depleting a stock item (which is then deleted) records the event against the deletion.""" + self.item.delete_on_deplete = True + self.item.save() + + pk = self.item.pk + n = self.entries(self.item).count() + + self.item.take_stock(100, self.user) + + self.assertFalse(StockItem.objects.filter(pk=pk).exists()) + + [entry] = list( + AuditLogEntry.objects.filter( + content_type=ContentType.objects.get_for_model(StockItem), + object_pk=str(pk), + ).order_by('pk')[n:] + ) + + self.assertEqual(entry.action, ACTION.DELETE) + self.assertEqual(entry.event_type, StockHistoryCode.STOCK_REMOVE) + + def test_status_change(self): + """Changing the status of stock items (via the API) records a single entry for each item.""" + n = self.entries(self.item).count() + + self.post( + reverse('api-stock-change-status'), + { + 'items': [self.item.pk], + 'status': StockStatus.DAMAGED.value, + 'note': 'Damaged', + }, + expected_code=201, + ) + + [entry] = self.new_entries(self.item, n) + + # A regular update (not a named event) - but the note is retained + self.assertEqual(entry.action, ACTION.UPDATE) + self.assertIsNone(entry.event_type) + self.assertEqual(entry.notes, 'Damaged') + self.assertEqual(entry.actor, self.user) + self.assertEqual( + entry.changes_dict['status'], + [StockStatus.OK.value, StockStatus.DAMAGED.value], + ) + + # Status information is recorded as field changes (not as deltas) + self.assertIsNone(entry.additional_data) + + def test_edit(self): + """Editing a stock item (e.g. changing the status) records a regular update entry.""" + n = self.entries(self.item).count() + + self.patch( + reverse('api-stock-detail', kwargs={'pk': self.item.pk}), + {'status': StockStatus.DAMAGED.value, 'batch': 'B-999'}, + ) + + [entry] = self.new_entries(self.item, n) + + self.assertEqual(entry.action, ACTION.UPDATE) + self.assertIsNone(entry.event_type) + self.assertEqual(entry.actor, self.user) + self.assertEqual( + entry.changes_dict['status'], + [StockStatus.OK.value, StockStatus.DAMAGED.value], + ) + self.assertEqual(entry.changes_dict['batch'], ['', 'B-999']) + + def test_install(self): + """Installing an item records an entry against both items.""" + assembly = StockItem.objects.create(part=self.assembly, quantity=1) + + n_item = self.entries(self.item).count() + n_assembly = self.entries(assembly).count() + + assembly.installStockItem(self.item, 100, self.user, 'Installed') + + [item_entry] = self.new_entries(self.item, n_item) + [assembly_entry] = self.new_entries(assembly, n_assembly) + + self.assertEqual( + item_entry.event_type, StockHistoryCode.INSTALLED_INTO_ASSEMBLY + ) + self.assertEqual(item_entry.changes_dict['belongs_to'], [None, assembly.pk]) + + # Nothing has changed for the assembly - the event is still recorded + self.assertEqual( + assembly_entry.event_type, StockHistoryCode.INSTALLED_CHILD_ITEM + ) + self.assertEqual(assembly_entry.changes_dict, {}) + self.assertEqual( + assembly_entry.additional_data['deltas']['stockitem']['pk'], self.item.pk + ) + + # Uninstall + self.item.refresh_from_db() + + n_item = self.entries(self.item).count() + n_assembly = self.entries(assembly).count() + + self.item.uninstall_into_location(self.loc_b, self.user, 'Removed') + + [item_entry] = self.new_entries(self.item, n_item) + [assembly_entry] = self.new_entries(assembly, n_assembly) + + self.assertEqual(item_entry.event_type, StockHistoryCode.REMOVED_FROM_ASSEMBLY) + self.assertEqual(assembly_entry.event_type, StockHistoryCode.REMOVED_CHILD_ITEM) + + def test_serialize(self): + """Serializing stock records an entry for each new item, and the original item.""" + item = StockItem.objects.create(part=self.trackable, quantity=5) + + n = self.entries(item).count() + + item.serializeStock(3, ['A1', 'A2', 'A3'], self.user, notes='Serialized') + + [entry] = self.new_entries(item, n) + self.assertEqual(entry.event_type, StockHistoryCode.STOCK_SERIALIZED) + + for child in StockItem.objects.filter(parent=item): + [child_entry] = self.entries(child) + + self.assertEqual(child_entry.action, ACTION.CREATE) + self.assertEqual(child_entry.event_type, StockHistoryCode.SPLIT_FROM_PARENT) + self.assertEqual(child_entry.actor, self.user) + self.assertEqual(child_entry.changes_dict['serial'][1], child.serial) + + def test_merge(self): + """Merging stock items records the event against the merged item.""" + other = StockItem.objects.create( + part=self.part, quantity=50, location=self.loc_a + ) + other_pk = other.pk + + n = self.entries(self.item).count() + + self.item.merge_stock_items([other], user=self.user, location=self.loc_a) + + [entry] = self.new_entries(self.item, n) + + self.assertEqual(entry.event_type, StockHistoryCode.MERGED_STOCK_ITEMS) + self.assertEqual(entry.changes_dict['quantity'], ['100.00000', '150.00000']) + + # The other item was deleted + self.assertTrue( + AuditLogEntry.objects.filter( + object_pk=str(other_pk), action=ACTION.DELETE + ).exists() + ) + + def test_part_history(self): + """Stock item entries are included in the history of the part.""" + self.item.move(self.loc_b, '', self.user) + + data = self.get( + reverse('api-audit-log-list'), + {'model_type': 'part.part', 'model_id': self.part.pk}, + ).data + + stock_entries = [e for e in data if e['model_type'] == 'stock.stockitem'] + + self.assertEqual(len(stock_entries), 2) + self.assertEqual(stock_entries[0]['event_label'], 'Location changed') + + def test_permissions(self): + """Stock history is visible to (non-staff) users with permission to view stock.""" + self.user.is_staff = False + self.user.save() + + url = reverse('api-audit-log-list') + params = {'model_type': 'stock.stockitem', 'model_id': self.item.pk} + + self.assertEqual(len(self.get(url, params).data), 1) + + self.clearRoles() + self.assignRole('part.view') + + self.assertEqual(self.get(url, params).data, []) + + +class StockEventCoverageTest(StockAuditTestBase): + """Regression tests for the remaining stock tracking event types.""" + + def latest(self, item): + """Return the most recent audit entry for a stock item.""" + return self.entries(item).last() + + def test_update_same_location(self): + """Moving stock into its current location records a STOCK_UPDATE event.""" + n = self.entries(self.item).count() + + self.item.move(self.loc_a, 'Same place', self.user, batch='B-2') + + [entry] = self.new_entries(self.item, n) + + self.assertEqual(entry.event_type, StockHistoryCode.STOCK_UPDATE) + self.assertEqual(entry.notes, 'Same place') + self.assertEqual(entry.changes_dict['batch'], ['', 'B-2']) + + def test_customer(self): + """Sending stock to a customer (and returning it) records the expected events.""" + from company.models import Company + + customer = Company.objects.create(name='Audit Customer', is_customer=True) + + n = self.entries(self.item).count() + + self.item.allocateToCustomer(customer, user=self.user, notes='Sent') + + [entry] = self.new_entries(self.item, n) + + self.assertEqual(entry.event_type, StockHistoryCode.SENT_TO_CUSTOMER) + self.assertEqual(entry.changes_dict['customer'], [None, customer.pk]) + self.assertEqual(entry.additional_data['deltas']['quantity'], 100.0) + + # Return the item from the customer + self.item.refresh_from_db() + n = self.entries(self.item).count() + + self.item.return_from_customer(self.loc_b, user=self.user, notes='Returned') + + [entry] = self.new_entries(self.item, n) + + self.assertEqual(entry.event_type, StockHistoryCode.RETURNED_FROM_CUSTOMER) + self.assertEqual(entry.notes, 'Returned') + + # The customer is recorded as a field change (not duplicated in the deltas) + self.assertEqual(entry.changes_dict['customer'], [customer.pk, None]) + self.assertNotIn('customer', entry.additional_data['deltas']) + + def test_ship_against_order(self): + """Allocating stock against a sales order records the SHIPPED_AGAINST_SALES_ORDER event.""" + from company.models import Company + from order.models import SalesOrder + + customer = Company.objects.create(name='Order Customer', is_customer=True) + order = SalesOrder.objects.create(customer=customer, reference='SO-7777') + + n = self.entries(self.item).count() + + self.item.allocateToCustomer(customer, quantity=40, order=order, user=self.user) + + # The original item is split - the remaining stock records the split + [entry] = self.new_entries(self.item, n) + self.assertEqual(entry.event_type, StockHistoryCode.SPLIT_CHILD_ITEM) + + shipped = StockItem.objects.get(parent=self.item) + + # The split item records its creation, and the shipment + [created, shipped_entry] = self.entries(shipped) + + self.assertEqual(created.event_type, StockHistoryCode.SPLIT_FROM_PARENT) + self.assertEqual( + shipped_entry.event_type, StockHistoryCode.SHIPPED_AGAINST_SALES_ORDER + ) + self.assertEqual( + shipped_entry.additional_data['deltas']['salesorder']['pk'], order.pk + ) + + def test_return_to_stock(self): + """Returning (consumed) stock to stock records the RETURNED_TO_STOCK event.""" + from company.models import Company + + customer = Company.objects.create(name='Return Customer', is_customer=True) + + self.item.customer = customer + self.item.location = None + self.item.save() + + n = self.entries(self.item).count() + + self.item.return_to_stock(self.loc_b, self.user, notes='Back in stock') + + [entry] = self.new_entries(self.item, n) + + self.assertEqual(entry.event_type, StockHistoryCode.RETURNED_TO_STOCK) + self.assertEqual(entry.notes, 'Back in stock') + self.assertEqual(entry.changes_dict['location'], [None, self.loc_b.pk]) + + def test_convert_to_variant(self): + """Converting a stock item to a variant records the CONVERTED_TO_VARIANT event.""" + template = Part.objects.create( + name='Template', description='A template', is_template=True + ) + variant = Part.objects.create( + name='Variant', description='A variant', variant_of=template + ) + + item = StockItem.objects.create(part=template, quantity=5) + n = self.entries(item).count() + + item.convert_to_variant(variant, self.user) + + [entry] = self.new_entries(item, n) + + self.assertEqual(entry.event_type, StockHistoryCode.CONVERTED_TO_VARIANT) + self.assertEqual(entry.changes_dict['part'], [template.pk, variant.pk]) + + def test_disassemble(self): + """Disassembling stock records DISASSEMBLED and CREATED_FROM_DISASSEMBLY events.""" + from part.models import BomItem + + bom_item = BomItem.objects.create( + part=self.assembly, sub_part=self.part, quantity=2 + ) + + assembly = StockItem.objects.create(part=self.assembly, quantity=5) + n = self.entries(assembly).count() + + components = assembly.disassemble( + 2, + [{'bom_item': bom_item, 'quantity': 4, 'location': self.loc_b}], + self.user, + notes='Broken down', + ) + + [entry] = self.new_entries(assembly, n) + + self.assertEqual(entry.event_type, StockHistoryCode.DISASSEMBLED) + self.assertEqual(entry.additional_data['deltas'], {'removed': 2.0}) + + [component] = components + [entry] = self.entries(component) + + self.assertEqual(entry.action, ACTION.CREATE) + self.assertEqual(entry.event_type, StockHistoryCode.CREATED_FROM_DISASSEMBLY) + self.assertEqual( + entry.additional_data['deltas']['stockitem']['pk'], assembly.pk + ) + + def test_transfer_merge(self): + """Transferring stock into an existing item (merge) records the event against the merged item.""" + target = StockItem.objects.create( + part=self.part, quantity=10, location=self.loc_b + ) + + n = self.entries(target).count() + + self.post( + reverse('api-stock-transfer'), + { + 'items': [{'pk': self.item.pk, 'quantity': 100, 'merge': True}], + 'location': self.loc_b.pk, + 'notes': 'Merged on transfer', + }, + expected_code=201, + ) + + target.refresh_from_db() + self.assertEqual(target.quantity, 110) + + [entry] = self.new_entries(target, n) + + self.assertEqual(entry.event_type, StockHistoryCode.MERGED_STOCK_ITEMS) + self.assertEqual(entry.notes, 'Merged on transfer') + self.assertEqual( + entry.additional_data['deltas']['stockitem']['pk'], self.item.pk + ) diff --git a/src/backend/InvenTree/stock/test_migrations.py b/src/backend/InvenTree/stock/test_migrations.py index f3284edb04..bbdd0014f6 100644 --- a/src/backend/InvenTree/stock/test_migrations.py +++ b/src/backend/InvenTree/stock/test_migrations.py @@ -272,3 +272,364 @@ class TestRemoveMpttFieldsMigration(MigratorTestCase): # The orphan item still has no parent orphan = StockItem.objects.get(pk=self.orphan_pk) self.assertIsNone(orphan.parent_id) + + +class TestStockTrackingAuditMigration(MigratorTestCase): + """Test data migration which copies StockItemTracking entries into the audit log. + + Ensures that every tracking entry is copied exactly once, with the event type, + user, date, notes, parent (part) reference and deltas transferred correctly. + """ + + migrate_from = ('stock', '0128_remove_stockitem_notes') + migrate_to = ('stock', '0129_migrate_stock_tracking_to_audit_log') + + def make_item(self, part, quantity, serial=None, location=None) -> int: + """Insert a StockItem via raw SQL (avoids a duplicate status_custom_key ORM bug).""" + from django.db import connection + + insert_sql = """ + INSERT INTO stock_stockitem + (part_id, quantity, status, delete_on_deplete, is_building, + link, serial_int, barcode_data, barcode_hash, serial, location_id) + VALUES (%s, %s, 10, false, false, '', 0, '', '', %s, %s) + """ + + params = [part.pk, quantity, serial, location.pk if location else None] + + with connection.cursor() as cursor: + if connection.features.can_return_rows_from_bulk_insert: + cursor.execute(insert_sql + 'RETURNING id', params) + return cursor.fetchone()[0] + + cursor.execute(insert_sql, params) + return cursor.lastrowid + + def prepare(self): + """Create legacy stock tracking entries to be migrated.""" + from datetime import datetime, timezone + + from django.conf import settings + + apps = self.old_state.apps + + User = apps.get_model('auth', 'user') + Part = apps.get_model('part', 'part') + Company = apps.get_model('company', 'company') + SalesOrder = apps.get_model('order', 'salesorder') + StockLocation = apps.get_model('stock', 'stocklocation') + StockItem = apps.get_model('stock', 'stockitem') + StockItemTracking = apps.get_model('stock', 'stockitemtracking') + + self.user = User.objects.create(username='tracker', email='tracker@example.com') + + self.part = Part.objects.create( + name='Tracked Widget', level=0, tree_id=0, lft=0, rght=0 + ) + + self.loc_1 = StockLocation.objects.create( + name='Shelf', pathstring='Store/Shelf', level=0, tree_id=1, lft=1, rght=2 + ) + self.loc_2 = StockLocation.objects.create( + name='Bin', pathstring='Store/Bin', level=0, tree_id=2, lft=1, rght=2 + ) + + self.customer = Company.objects.create(name='Acme Customer', is_customer=True) + self.sales_order = SalesOrder.objects.create( + reference='SO-1234', customer=self.customer, reference_int=1234 + ) + + item_pk = self.make_item(self.part, 100, location=self.loc_1) + other_pk = self.make_item(self.part, 1, serial='SN-42') + + item = StockItem.objects.get(pk=item_pk) + other = StockItem.objects.get(pk=other_pk) + + self.item_pk = item_pk + self.other_pk = other_pk + + def track(code, deltas=None, notes='', user=None, stock_item=item, day=1): + """Create a legacy tracking entry (with a known date).""" + entry = StockItemTracking.objects.create( + item=stock_item, + part=self.part, + tracking_type=code, + deltas=deltas, + notes=notes, + user=user, + ) + + # MySQL rejects timezone-aware values when USE_TZ is False + utc = timezone.utc if settings.USE_TZ else None + date = datetime(2025, 1, day, 12, 0, tzinfo=utc) + StockItemTracking.objects.filter(pk=entry.pk).update(date=date) + + return entry.pk + + self.entries = { + 'created': track( + 1, + {'status': 10, 'quantity': 100.0, 'location': self.loc_1.pk}, + notes='Initial stock', + user=self.user, + day=1, + ), + 'moved': track( + 20, + {'quantity': 100.0, 'location': self.loc_2.pk}, + user=self.user, + day=2, + ), + 'counted': track( + 10, + { + 'quantity': 90.0, + 'location': self.loc_1.pk, + 'old_location': self.loc_2.pk, + }, + user=self.user, + day=3, + ), + 'edited': track( + 5, + { + 'status': 55, + 'status_logical': 55, + 'old_status': 10, + 'old_status_logical': 10, + }, + user=self.user, + day=4, + ), + 'custom_status': track( + 5, + { + 'status': 1001, + 'status_logical': 50, + 'old_status': 55, + 'old_status_logical': 55, + }, + day=5, + ), + 'shipped': track( + 60, + { + 'quantity': 5.0, + 'salesorder': self.sales_order.pk, + 'customer': self.customer.pk, + 'customer_name': 'Acme Customer', + }, + user=self.user, + day=6, + ), + 'split': track(42, {'stockitem': other_pk, 'removed': 5.0}, day=7), + 'missing_reference': track( + 42, {'stockitem': 999999, 'removed': 1.0}, day=8 + ), + 'deleted_item': track(12, {'removed': 1.0}, stock_item=None, day=9), + 'legacy': track(0, None, notes='Legacy entry', stock_item=other, day=10), + } + + self.assertEqual(StockItemTracking.objects.count(), len(self.entries)) + + def migrated(self, name): + """Return the audit log entry migrated from the named tracking entry.""" + AuditLogEntry = self.new_state.apps.get_model('audit', 'auditlogentry') + + return AuditLogEntry.objects.get( + additional_data__tracking_id=self.entries[name] + ) + + def check_integrity(self): + """Every tracking entry is copied exactly once, with all common fields transferred.""" + apps = self.new_state.apps + + AuditLogEntry = apps.get_model('audit', 'auditlogentry') + StockItemTracking = apps.get_model('stock', 'stockitemtracking') + ContentType = apps.get_model('contenttypes', 'contenttype') + + stock_ct = ContentType.objects.get(app_label='stock', model='stockitem') + part_ct = ContentType.objects.get(app_label='part', model='part') + + # The legacy entries are retained + self.assertEqual(StockItemTracking.objects.count(), len(self.entries)) + + migrated = AuditLogEntry.objects.filter( + content_type=stock_ct, additional_data__has_key='tracking_id' + ) + + self.assertEqual(migrated.count(), len(self.entries)) + self.assertEqual( + sorted(entry.additional_data['tracking_id'] for entry in migrated), + sorted(self.entries.values()), + ) + + for tracking in StockItemTracking.objects.all(): + entry = migrated.get(additional_data__tracking_id=tracking.pk) + + self.assertEqual(entry.timestamp, tracking.date) + self.assertEqual(entry.actor_id, tracking.user_id) + self.assertEqual(entry.notes, tracking.notes or None) + + # The entry is recorded against the stock item + self.assertEqual(entry.content_type_id, stock_ct.pk) + self.assertEqual(entry.object_id, tracking.item_id) + self.assertEqual( + entry.object_pk, str(tracking.item_id) if tracking.item_id else '' + ) + + # The parent reference is the part + self.assertEqual(entry.parent_content_type_id, part_ct.pk) + self.assertEqual(entry.parent_object_pk, str(self.part.pk)) + + # The event type is transferred ('EDITED' entries are regular updates) + if tracking.tracking_type == 5: + self.assertIsNone(entry.event_type) + else: + self.assertEqual(entry.event_type, tracking.tracking_type) + + def check_created(self): + """A creation entry is recorded as a CREATE entry, with the initial values as field changes.""" + entry = self.migrated('created') + + self.assertEqual(entry.action, 0) + self.assertEqual(entry.event_type, 1) + self.assertEqual(entry.notes, 'Initial stock') + self.assertEqual(entry.actor_id, self.user.pk) + self.assertEqual(entry.actor_username, 'tracker') + self.assertEqual(entry.actor_email, 'tracker@example.com') + self.assertEqual(entry.object_repr, '100 x Tracked Widget') + + self.assertEqual( + entry.changes, + { + 'status': [None, 10], + 'quantity': [None, '100'], + 'location': [None, self.loc_1.pk], + }, + ) + + self.assertNotIn('deltas', entry.additional_data) + + def check_moved(self): + """A move records the destination location as a reference.""" + entry = self.migrated('moved') + + self.assertEqual(entry.action, 1) + self.assertEqual(entry.event_type, 20) + self.assertEqual(entry.changes, {}) + self.assertEqual( + entry.additional_data['deltas'], + { + 'quantity': 100.0, + 'location': { + 'model_type': 'stock.stocklocation', + 'pk': self.loc_2.pk, + 'str': 'Store/Bin', + }, + }, + ) + + def check_counted(self): + """A location change (with known previous location) is recorded as a field change.""" + entry = self.migrated('counted') + + self.assertEqual(entry.event_type, 10) + self.assertEqual(entry.changes, {'location': [self.loc_2.pk, self.loc_1.pk]}) + self.assertEqual(entry.additional_data['deltas'], {'quantity': 90.0}) + + def check_status_changes(self): + """Status changes are recorded as field changes (including custom status values).""" + entry = self.migrated('edited') + + self.assertEqual(entry.action, 1) + self.assertIsNone(entry.event_type) + self.assertEqual(entry.changes, {'status': [10, 55]}) + self.assertNotIn('deltas', entry.additional_data) + + entry = self.migrated('custom_status') + + self.assertEqual( + entry.changes, {'status': [55, 50], 'status_custom_key': [None, 1001]} + ) + + def check_references(self): + """References to other objects are converted to {model_type, pk, str}.""" + deltas = self.migrated('shipped').additional_data['deltas'] + + self.assertEqual( + deltas, + { + 'quantity': 5.0, + 'salesorder': { + 'model_type': 'order.salesorder', + 'pk': self.sales_order.pk, + 'str': 'SO-1234', + }, + 'customer': { + 'model_type': 'company.company', + 'pk': self.customer.pk, + 'str': 'Acme Customer', + }, + }, + ) + + deltas = self.migrated('split').additional_data['deltas'] + + self.assertEqual( + deltas['stockitem'], + { + 'model_type': 'stock.stockitem', + 'pk': self.other_pk, + 'str': 'Tracked Widget #SN-42', + }, + ) + self.assertEqual(deltas['removed'], 5.0) + + # A reference to an object which no longer exists + deltas = self.migrated('missing_reference').additional_data['deltas'] + + self.assertEqual( + deltas['stockitem'], + {'model_type': 'stock.stockitem', 'pk': 999999, 'str': None}, + ) + + def check_deleted_item(self): + """Entries for deleted stock items are retained against the part.""" + entry = self.migrated('deleted_item') + + self.assertEqual(entry.object_pk, '') + self.assertIsNone(entry.object_id) + self.assertEqual(entry.object_repr, 'Tracked Widget') + self.assertEqual(entry.parent_object_pk, str(self.part.pk)) + self.assertEqual(entry.event_type, 12) + + def check_legacy(self): + """Legacy entries (without user or deltas) are migrated.""" + entry = self.migrated('legacy') + + self.assertEqual(entry.event_type, 0) + self.assertIsNone(entry.actor_id) + self.assertIsNone(entry.actor_username) + self.assertEqual(entry.notes, 'Legacy entry') + self.assertEqual(entry.object_repr, 'Tracked Widget #SN-42') + self.assertEqual(entry.changes, {}) + self.assertEqual(entry.additional_data, {'tracking_id': self.entries['legacy']}) + + def test_migration(self): + """Run all checks against the migrated data. + + Note: A single test method is used, as the migrations are run for each test method. + """ + for check in [ + self.check_integrity, + self.check_created, + self.check_moved, + self.check_counted, + self.check_status_changes, + self.check_references, + self.check_deleted_item, + self.check_legacy, + ]: + with self.subTest(check=check.__name__): + check() diff --git a/src/backend/InvenTree/stock/tests.py b/src/backend/InvenTree/stock/tests.py index e312ae1f17..8c731a61a3 100644 --- a/src/backend/InvenTree/stock/tests.py +++ b/src/backend/InvenTree/stock/tests.py @@ -2,6 +2,7 @@ import datetime import threading +from decimal import Decimal from unittest import mock from django.contrib.auth import get_user_model @@ -15,24 +16,24 @@ from django_q.models import OrmQ from djmoney.money import Money from rest_framework.test import APIClient +from audit.helpers import write_batched +from audit.models import AuditLogEntry from build.models import Build from common.models import InvenTreeSetting from company.models import Company -from InvenTree.unit_test import AdminTestCase, InvenTreeTestCase +from InvenTree.unit_test import ( + AdminTestCase, + InvenTreeTestCase, + stock_events, + stock_history, +) from order.models import SalesOrder from part.models import Part, PartTestTemplate from plugin.base.event.events import batch_events from stock.events import StockEvents from stock.status_codes import StockHistoryCode, StockStatus -from .models import ( - StockItem, - StockItemTestResult, - StockItemTracking, - StockLocation, - StockLocationType, - batch_tracking_entries, -) +from .models import StockItem, StockItemTestResult, StockLocation, StockLocationType class StockTestBase(InvenTreeTestCase): @@ -295,21 +296,23 @@ class StockTest(StockTestBase): ) # Check that a tracking item was added - track = StockItemTracking.objects.filter(item=it).latest('id') + track = stock_events(it).last() - self.assertEqual(track.item, it) - self.assertEqual(track.tracking_type, StockHistoryCode.STOCK_MOVE) + self.assertEqual(track.event_type, StockHistoryCode.STOCK_MOVE) self.assertEqual(track.notes, 'Moved to the bathroom') def test_self_move(self): """Test moving stock to its current location.""" it = StockItem.objects.get(pk=1) - n = it.tracking_info.count() + n = stock_events(it).count() self.assertTrue(it.move(it.location, 'Moved to same place', None)) - # Ensure tracking info was not added - self.assertEqual(it.tracking_info.count(), n + 1) + # A STOCK_UPDATE event is recorded + self.assertEqual(stock_events(it).count(), n + 1) + self.assertEqual( + stock_events(it).last().event_type, StockHistoryCode.STOCK_UPDATE + ) def test_partial_move(self): """Test partial stock moving.""" @@ -361,12 +364,14 @@ class StockTest(StockTestBase): parent = StockItem.objects.get(id=1234) child = parent.splitStock(100, None, self.user) - parent_entry = parent.tracking_info.filter( - tracking_type=StockHistoryCode.SPLIT_CHILD_ITEM.value + parent_entry = stock_events( + parent, event_type=StockHistoryCode.SPLIT_CHILD_ITEM ).first() self.assertIsNotNone(parent_entry) - self.assertEqual(parent_entry.deltas.get('stockitem'), child.pk) + self.assertEqual( + parent_entry.additional_data['deltas']['stockitem']['pk'], child.pk + ) def test_delete_reparents_children(self): """Test that deleting an intermediate item re-links children to the grandparent.""" @@ -456,14 +461,16 @@ class StockTest(StockTestBase): self.assertEqual(item.quantity, 0) # History records the quantity which was *actually* removed - entry = item.tracking_info.latest('pk') - self.assertEqual(entry.deltas['removed'], 10.0) - self.assertEqual(entry.deltas['quantity'], 0.0) + entry = stock_events(item).last() + self.assertEqual(entry.additional_data['deltas']['removed'], 10.0) + self.assertEqual( + [Decimal(value) for value in entry.changes_dict['quantity']], [10, 0] + ) # Removing stock from an empty item fails, and adds no history - n = item.tracking_info.count() + n = stock_history(item).count() self.assertFalse(item.take_stock(5, self.user)) - self.assertEqual(item.tracking_info.count(), n) + self.assertEqual(stock_history(item).count(), n) # --- allocateToCustomer: allocate more than available --- customer = Company.objects.create(name='Clamp customer', is_customer=True) @@ -477,8 +484,8 @@ class StockTest(StockTestBase): self.assertEqual(allocated.customer, customer) # History records the quantity which was *actually* allocated - entry = allocated.tracking_info.latest('pk') - self.assertEqual(entry.deltas['quantity'], 10.0) + entry = stock_events(allocated).last() + self.assertEqual(entry.additional_data['deltas']['quantity'], 10.0) # A zero quantity is rejected outright item = StockItem.objects.create(part=part, quantity=10) @@ -503,15 +510,15 @@ class StockTest(StockTestBase): self.assertEqual(component.quantity, 10) # Both history entries record the quantity which was *actually* installed - entry = component.tracking_info.filter( - tracking_type=StockHistoryCode.INSTALLED_INTO_ASSEMBLY - ).latest('pk') - self.assertEqual(entry.deltas['quantity'], 10.0) + entry = stock_events( + component, event_type=StockHistoryCode.INSTALLED_INTO_ASSEMBLY + ).last() + self.assertEqual(entry.additional_data['deltas']['quantity'], 10.0) - entry = parent_item.tracking_info.filter( - tracking_type=StockHistoryCode.INSTALLED_CHILD_ITEM - ).latest('pk') - self.assertEqual(entry.deltas['quantity'], 10.0) + entry = stock_events( + parent_item, event_type=StockHistoryCode.INSTALLED_CHILD_ITEM + ).last() + self.assertEqual(entry.additional_data['deltas']['quantity'], 10.0) # A zero quantity is rejected outright other = StockItem.objects.create(part=part, quantity=5) @@ -527,8 +534,8 @@ class StockTest(StockTestBase): item.belongs_to = parent item.save() - n_entries = item.tracking_info.count() - n_parent_entries = parent.tracking_info.count() + n_entries = stock_history(item).count() + n_parent_entries = stock_history(parent).count() structural = StockLocation.objects.create( name='Structural location', structural=True @@ -541,8 +548,8 @@ class StockTest(StockTestBase): item.refresh_from_db() self.assertEqual(item.belongs_to, parent) self.assertNotEqual(item.location, structural) - self.assertEqual(item.tracking_info.count(), n_entries) - self.assertEqual(parent.tracking_info.count(), n_parent_entries) + self.assertEqual(stock_history(item).count(), n_entries) + self.assertEqual(stock_history(parent).count(), n_parent_entries) # Uninstalling into a non-structural location is still permitted item.uninstall_into_location(self.drawer2, self.user, 'Uninstalling') @@ -671,12 +678,12 @@ class StockTest(StockTestBase): self.assertEqual(it.quantity, 255) # Check that a tracking item was added - track = StockItemTracking.objects.filter(item=it).latest('id') + track = stock_events(it).last() - self.assertEqual(track.tracking_type, StockHistoryCode.STOCK_COUNT) + self.assertEqual(track.event_type, StockHistoryCode.STOCK_COUNT) self.assertIn('Counted items', track.notes) - n = it.tracking_info.count() + n = stock_history(it).count() self.assertFalse( it.stocktake( -1, @@ -686,8 +693,8 @@ class StockTest(StockTestBase): ) ) - # Ensure tracking info was not added - self.assertEqual(it.tracking_info.count(), n) + # Ensure no history was added + self.assertEqual(stock_history(it).count(), n) it.refresh_from_db() self.assertEqual(it.status, StockStatus.OK.value) @@ -780,9 +787,9 @@ class StockTest(StockTestBase): self.assertEqual(it.quantity, n + 45) # Check that a tracking item was added - track = StockItemTracking.objects.filter(item=it).latest('id') + track = stock_events(it).last() - self.assertEqual(track.tracking_type, StockHistoryCode.STOCK_ADD) + self.assertEqual(track.event_type, StockHistoryCode.STOCK_ADD) self.assertIn('Added some items', track.notes) self.assertFalse(it.add_stock(-10, None)) @@ -810,11 +817,9 @@ class StockTest(StockTestBase): self.assertFalse(ait.location) # Check that a tracking item was added - track = StockItemTracking.objects.filter(item=ait).latest('id') + track = stock_events(ait).last() - self.assertEqual( - track.tracking_type, StockHistoryCode.SHIPPED_AGAINST_SALES_ORDER - ) + self.assertEqual(track.event_type, StockHistoryCode.SHIPPED_AGAINST_SALES_ORDER) self.assertIn('Allocated some stock', track.notes) def test_return_from_customer(self): @@ -869,9 +874,9 @@ class StockTest(StockTestBase): self.assertFalse(ait.sales_order) # Check that a tracking item was added - track = StockItemTracking.objects.filter(item=ait).latest('id') + track = stock_events(ait).last() - self.assertEqual(track.tracking_type, StockHistoryCode.RETURNED_FROM_CUSTOMER) + self.assertEqual(track.event_type, StockHistoryCode.RETURNED_FROM_CUSTOMER) self.assertIn('Stock removed from customer', track.notes) # Establish total stock for the part after remove from customer to check that we still have the correct quantity in stock @@ -889,11 +894,11 @@ class StockTest(StockTestBase): self.assertEqual(it.quantity, n - 15) # Check that a tracking item was added - track = StockItemTracking.objects.filter(item=it).latest('id') + track = stock_events(it).last() - self.assertEqual(track.tracking_type, StockHistoryCode.STOCK_REMOVE) + self.assertEqual(track.event_type, StockHistoryCode.STOCK_REMOVE) self.assertIn('Removed some items', track.notes) - self.assertTrue(it.has_tracking_info) + self.assertTrue(stock_events(it).exists()) # Test that negative quantity does nothing self.assertFalse(it.take_stock(-10, None)) @@ -1112,12 +1117,14 @@ class StockTest(StockTestBase): self.assertEqual(s1.quantity, 60) self.assertIsNone(s1.purchase_price) - merge_entry = s1.tracking_info.filter( - tracking_type=StockHistoryCode.MERGED_STOCK_ITEMS + merge_entry = stock_events( + s1, event_type=StockHistoryCode.MERGED_STOCK_ITEMS ).first() self.assertIsNotNone(merge_entry) - self.assertEqual(merge_entry.deltas['added'], 50.0) - self.assertEqual(merge_entry.deltas['quantity'], 60.0) + self.assertEqual(merge_entry.additional_data['deltas']['added'], 50.0) + self.assertEqual( + [Decimal(value) for value in merge_entry.changes_dict['quantity']], [10, 60] + ) part.stock_items.all().delete() @@ -1265,107 +1272,70 @@ class StockTest(StockTestBase): self.assertEqual(item.purchase_price_currency, 'GBP') -class TrackingEntryBatchTests(StockTestBase): - """Unit tests for the batch_tracking_entries() context manager.""" +class TrackingEventBatchTests(StockTestBase): + """Unit tests for batching stock tracking events (audit entries) via write_batched().""" - def test_entries_queued_and_flushed_on_commit(self): - """Tracking entries created inside batch_tracking_entries() are bulk-created on commit.""" + def history(self, item): + """Return the audit entries for the provided stock item.""" + return AuditLogEntry.objects.get_for_object(item).order_by('pk') + + def test_events_written_on_exit(self): + """Tracking events recorded inside write_batched() are written when the context exits.""" item = StockItem.objects.get(pk=2) - n = StockItemTracking.objects.count() + n = self.history(item).count() - with self.captureOnCommitCallbacks(execute=True): - with transaction.atomic(), batch_tracking_entries(): - for idx in range(10): - item.add_tracking_entry( - StockHistoryCode.STOCK_COUNT, - self.user, - deltas={'quantity': idx}, - notes=f'Batch entry {idx}', - ) + with transaction.atomic(), write_batched(): + for idx in range(10): + item.add_tracking_entry( + StockHistoryCode.STOCK_COUNT, + self.user, + deltas={'quantity': idx}, + notes=f'Batch entry {idx}', + ) - # Nothing should be written yet - the batch only flushes on commit - self.assertEqual(StockItemTracking.objects.count(), n) + # Nothing should be written yet + self.assertEqual(self.history(item).count(), n) - entries = StockItemTracking.objects.filter(item=item).order_by('id')[:10] - self.assertEqual(StockItemTracking.objects.count(), n + 10) + entries = list(self.history(item)[n:]) + self.assertEqual(len(entries), 10) for idx, entry in enumerate(entries): - self.assertEqual(entry.tracking_type, StockHistoryCode.STOCK_COUNT) - self.assertEqual(entry.deltas.get('quantity'), idx) + self.assertEqual(entry.event_type, StockHistoryCode.STOCK_COUNT) + self.assertEqual(entry.additional_data['deltas']['quantity'], idx) self.assertEqual(entry.notes, f'Batch entry {idx}') - def test_entries_discarded_on_rollback(self): - """Tracking entries queued in a batch are discarded if the transaction rolls back.""" + def test_events_discarded_on_rollback(self): + """Tracking events are discarded if the transaction rolls back.""" item = StockItem.objects.get(pk=2) - n = StockItemTracking.objects.count() + n = self.history(item).count() - with self.captureOnCommitCallbacks(execute=True): - try: - with transaction.atomic(), batch_tracking_entries(): - item.add_tracking_entry( - StockHistoryCode.STOCK_COUNT, self.user, deltas={'quantity': 1} - ) - raise ValueError('boom') - except ValueError: - pass + try: + with transaction.atomic(), write_batched(): + item.add_tracking_entry( + StockHistoryCode.STOCK_COUNT, self.user, deltas={'quantity': 1} + ) + raise ValueError('boom') + except ValueError: + pass - self.assertEqual(StockItemTracking.objects.count(), n) + self.assertEqual(self.history(item).count(), n) - def test_entries_outside_batch_fire_immediately(self): - """Tracking entries created outside any batch are unaffected - saved immediately.""" + def test_events_outside_batch_written_immediately(self): + """Tracking events recorded outside any batch are written immediately.""" item = StockItem.objects.get(pk=2) - n = StockItemTracking.objects.count() + n = self.history(item).count() item.add_tracking_entry( StockHistoryCode.STOCK_COUNT, self.user, deltas={'quantity': 1} ) - # No transaction commit or captureOnCommitCallbacks needed - it was never queued - self.assertEqual(StockItemTracking.objects.count(), n + 1) + self.assertEqual(self.history(item).count(), n + 1) - def test_nested_batch_share_one_flush(self): - """A nested batch_tracking_entries() call reuses the outer batch, rather than flushing twice.""" - item = StockItem.objects.get(pk=2) - - n = StockItemTracking.objects.count() - - with self.captureOnCommitCallbacks(execute=True): - with transaction.atomic(), batch_tracking_entries(): - item.add_tracking_entry( - StockHistoryCode.STOCK_COUNT, self.user, deltas={'quantity': 1} - ) - with batch_tracking_entries(): - item.add_tracking_entry( - StockHistoryCode.STOCK_COUNT, self.user, deltas={'quantity': 2} - ) - self.assertEqual(StockItemTracking.objects.count(), n) - - self.assertEqual(StockItemTracking.objects.count(), n + 2) - - def test_commit_false_is_unaffected_by_batch(self): - """commit=False callers keep manual ownership of the entry, even inside a batch.""" - item = StockItem.objects.get(pk=2) - - n = StockItemTracking.objects.count() - - with transaction.atomic(), batch_tracking_entries(): - entry = item.add_tracking_entry( - StockHistoryCode.STOCK_COUNT, - self.user, - deltas={'quantity': 1}, - commit=False, - ) - - # The entry was returned uncommitted - the batch never saw it, so it was never written - self.assertIsNotNone(entry) - self.assertIsNone(entry.pk) - self.assertEqual(StockItemTracking.objects.count(), n) - - def test_stocktake_batch_tracking_entries(self): - """Integration test: batching stocktake() tracking entries via the StockCountSerializer path.""" + def test_stocktake_batch(self): + """Integration test: batching stocktake() events via the StockCountSerializer path.""" part = Part.objects.create( name='Batch tracking part', description='For batch tracking testing' ) @@ -1375,18 +1345,14 @@ class TrackingEntryBatchTests(StockTestBase): for idx in range(10) ] - n = StockItemTracking.objects.count() + with transaction.atomic(), write_batched(): + for idx, item in enumerate(items): + item.stocktake(100 + idx, self.user, notes='Batch stocktake') - with self.captureOnCommitCallbacks(execute=True): - with transaction.atomic(), batch_tracking_entries(): - for idx, item in enumerate(items): - item.stocktake(100 + idx, self.user, notes='Batch stocktake') - - entries = StockItemTracking.objects.filter( - item__in=items, tracking_type=StockHistoryCode.STOCK_COUNT - ).order_by('id') - self.assertEqual(entries.count(), 10) - self.assertEqual(StockItemTracking.objects.count(), n + 10) + for item in items: + entry = self.history(item).last() + self.assertEqual(entry.event_type, StockHistoryCode.STOCK_COUNT) + self.assertEqual(entry.notes, 'Batch stocktake') class StockBarcodeTest(StockTestBase): @@ -2368,7 +2334,7 @@ class StockItemTakeStockConcurrencyTest(TransactionTestCase): lock is acquired) in-memory copy, could both cap to the same amount - the first to acquire the lock removes it all, and the second, upon acquiring the lock, would find nothing left, but would still record a phantom - removal in StockItemTracking and report success. + removal in the stock history and report success. take_stock() now performs the cap *after* lock_quantity() has refreshed self.quantity, so the loser of the race is correctly capped to zero and @@ -2449,8 +2415,7 @@ class StockItemTakeStockConcurrencyTest(TransactionTestCase): # recorded 'removed' amount must not exceed the quantity that # actually existed total_removed = sum( - entry.deltas.get('removed', 0) - for entry in self.item.tracking_info.all() - if entry.deltas + (entry.additional_data or {}).get('deltas', {}).get('removed', 0) + for entry in stock_events(self.item) ) self.assertEqual(total_removed, 10) diff --git a/src/backend/InvenTree/users/ruleset.py b/src/backend/InvenTree/users/ruleset.py index f1a85b4e13..08615e7727 100644 --- a/src/backend/InvenTree/users/ruleset.py +++ b/src/backend/InvenTree/users/ruleset.py @@ -148,11 +148,7 @@ def get_ruleset_models() -> dict: 'pricing_partinternalpricebreak', ], RuleSetEnum.STOCK_LOCATION: ['stock_stocklocation', 'stock_stocklocationtype'], - RuleSetEnum.STOCK: [ - 'stock_stockitem', - 'stock_stockitemtracking', - 'stock_stockitemtestresult', - ], + RuleSetEnum.STOCK: ['stock_stockitem', 'stock_stockitemtestresult'], RuleSetEnum.PURCHASE_ORDER: [ 'company_company', 'company_contact', diff --git a/src/frontend/lib/enums/ApiEndpoints.tsx b/src/frontend/lib/enums/ApiEndpoints.tsx index 4e6ab23b7b..62c83dcb5b 100644 --- a/src/frontend/lib/enums/ApiEndpoints.tsx +++ b/src/frontend/lib/enums/ApiEndpoints.tsx @@ -145,7 +145,6 @@ export enum ApiEndpoints { // Stock item API endpoints stock_item_list = 'stock/', - stock_tracking_list = 'stock/track/', stock_test_result_list = 'stock/test/', stock_transfer = 'stock/transfer/', stock_remove = 'stock/remove/', diff --git a/src/frontend/lib/enums/ModelInformation.tsx b/src/frontend/lib/enums/ModelInformation.tsx index 93f670402c..7e2aebbd60 100644 --- a/src/frontend/lib/enums/ModelInformation.tsx +++ b/src/frontend/lib/enums/ModelInformation.tsx @@ -151,7 +151,7 @@ export const ModelInformationDict: ModelDict = { stockhistory: { label: () => t`Stock History`, label_multiple: () => t`Stock Histories`, - api_endpoint: ApiEndpoints.stock_tracking_list, + api_endpoint: ApiEndpoints.audit_log_list, icon: 'history' }, build: { diff --git a/src/frontend/src/components/panels/HistoryPanel.tsx b/src/frontend/src/components/panels/HistoryPanel.tsx index b01cb643eb..9853dfa379 100644 --- a/src/frontend/src/components/panels/HistoryPanel.tsx +++ b/src/frontend/src/components/panels/HistoryPanel.tsx @@ -9,25 +9,24 @@ import { useGlobalSettingsState, useUserSettingsState } from '../../states/SettingsStates'; -import { useUserState } from '../../states/UserState'; import { AuditLogTable } from '../../tables/general/AuditLogTable'; /** * Determine whether the history panel should be displayed on detail pages: - * - Only available to staff users + * - The audit log must be enabled * - Must be enabled via the 'DISPLAY_HISTORY_TAB' user setting + * + * Note: The displayed entries are filtered by the permissions of the user */ export function useShowHistoryPanel(): boolean { - const user = useUserState(); const userSettings = useUserSettingsState(); const globalSettings = useGlobalSettingsState(); return useMemo( () => - user.isStaff() && globalSettings.isSet('AUDIT_LOG_ENABLED') && userSettings.isSet('DISPLAY_HISTORY_TAB', false), - [user, userSettings, globalSettings] + [userSettings, globalSettings] ); } diff --git a/src/frontend/src/forms/StockForms.tsx b/src/frontend/src/forms/StockForms.tsx index a8f015ba4f..4470a3a224 100644 --- a/src/frontend/src/forms/StockForms.tsx +++ b/src/frontend/src/forms/StockForms.tsx @@ -342,7 +342,6 @@ export function useStockFields({ originalId: duplicateStockItem.pk, extraFields: { copy_notes: { value: true }, - copy_history: { value: false }, copy_tests: { value: false, hidden: !duplicateStockItem?.part_detail?.testable diff --git a/src/frontend/src/pages/Index/Settings/SystemSettings.tsx b/src/frontend/src/pages/Index/Settings/SystemSettings.tsx index 10a58cbe3e..3411020a4d 100644 --- a/src/frontend/src/pages/Index/Settings/SystemSettings.tsx +++ b/src/frontend/src/pages/Index/Settings/SystemSettings.tsx @@ -297,13 +297,6 @@ export default function SystemSettings() { 'STOCKTAKE_DELETE_DAYS' ]} /> - ) { return ( - - - {t`Stock Tracking`} - - - - - {t`Stocktake Entries`} diff --git a/src/frontend/src/pages/stock/StockDetail.tsx b/src/frontend/src/pages/stock/StockDetail.tsx index 4244a62226..33c4ec598d 100644 --- a/src/frontend/src/pages/stock/StockDetail.tsx +++ b/src/frontend/src/pages/stock/StockDetail.tsx @@ -5,7 +5,6 @@ import { IconBookmark, IconBoxPadding, IconChecklist, - IconHistory, IconInfoCircle, IconPackages, IconShoppingCart, @@ -41,6 +40,9 @@ import InstanceDetail from '../../components/nav/InstanceDetail'; import NavigationTree from '../../components/nav/NavigationTree'; import { PageDetail } from '../../components/nav/PageDetail'; import AttachmentPanel from '../../components/panels/AttachmentPanel'; +import HistoryPanel, { + useShowHistoryPanel +} from '../../components/panels/HistoryPanel'; import NotesPanel from '../../components/panels/NotesPanel'; import { PanelGroup } from '../../components/panels/PanelGroup'; import LocateItemButton from '../../components/plugins/LocateItemButton'; @@ -69,7 +71,6 @@ import SalesOrderAllocationTable from '../../tables/sales/SalesOrderAllocationTa import InstalledItemsTable from '../../tables/stock/InstalledItemsTable'; import { StockItemTable } from '../../tables/stock/StockItemTable'; import StockItemTestResultTable from '../../tables/stock/StockItemTestResultTable'; -import { StockTrackingTable } from '../../tables/stock/StockTrackingTable'; import TransferOrderAllocationTable from '../../tables/stock/TransferOrderAllocationTable'; import { StockDetailsPanel } from './StockDetailsPanel'; @@ -182,6 +183,8 @@ export default function StockDetail() { return stockitem?.part_detail?.assembly; }, [trackedBomItemQuery, stockitem]); + const showHistory = useShowHistoryPanel(); + const stockPanels: PanelType[] = useMemo(() => { return [ { @@ -197,16 +200,6 @@ export default function StockDetail() { /> ) }, - { - name: 'tracking', - label: t`Stock Tracking`, - icon: , - content: stockitem.pk ? ( - - ) : ( - - ) - }, { name: 'allocations', label: t`Allocations`, @@ -320,9 +313,15 @@ export default function StockDetail() { model_type: ModelType.stockitem, model_id: stockitem.pk, note_count: instanceInfo.note_count + }), + HistoryPanel({ + model_type: ModelType.stockitem, + model_id: stockitem.pk, + visible: showHistory }) ]; }, [ + showHistory, showSalesAllocations, showBuildAllocations, showInstalledItems, diff --git a/src/frontend/src/tables/general/AuditLogTable.tsx b/src/frontend/src/tables/general/AuditLogTable.tsx index 0088b259ab..081116b1f3 100644 --- a/src/frontend/src/tables/general/AuditLogTable.tsx +++ b/src/frontend/src/tables/general/AuditLogTable.tsx @@ -233,6 +233,86 @@ function getChanges(record: any): AuditChange[] { return changes; } +/** + * Return a human readable label for an event delta key (e.g. 'sales_order' -> 'Sales order') + */ +function deltaLabel(key: string): string { + // Labels for commonly used delta keys (e.g. stock tracking events) + const labels: Record = { + added: t`Added`, + build_order: t`Build Order`, + buildorder: t`Build Order`, + customer: t`Customer`, + location: t`Location`, + part: t`Part`, + purchaseorder: t`Purchase Order`, + quantity: t`Quantity`, + removed: t`Removed`, + returnorder: t`Return Order`, + salesorder: t`Sales Order`, + stockitem: t`Stock Item`, + transferorder: t`Transfer Order` + }; + + if (key in labels) { + return labels[key]; + } + + const label = key.replaceAll('_', ' '); + return label.charAt(0).toUpperCase() + label.slice(1); +} + +/** + * Render the deltas and notes recorded against a named event (if any) + */ +function AuditEventDetails({ + record, + navigate +}: Readonly<{ + record: any; + navigate: NavigateFunction; +}>): ReactNode { + const deltas: [string, any][] = Object.entries(record.deltas ?? {}); + + if (deltas.length == 0 && !record.notes) { + return null; + } + + return ( + + {deltas.map(([key, value]) => { + // A reference to another object: {model_type, pk, str} + const isReference = + value !== null && typeof value === 'object' && 'model_type' in value; + + return ( + + + {deltaLabel(key)}: + + {isReference && !value.str ? ( + {t`Hidden`} + ) : ( + + )} + + ); + })} + {record.notes && ( + + {record.notes} + + )} + + ); +} + /** * Render a summary of the changes for an audit entry */ @@ -243,17 +323,30 @@ function AuditChangesSummary({ record: any; navigate: NavigateFunction; }>): ReactNode { + const eventDetails = ( + + ); + if (record.action == AuditAction.DELETE) { + // A named event may be recorded against the deletion (e.g. stock depleted to zero) return ( - - {t`Object was deleted`} - + + {eventDetails} + + {t`Object was deleted`} + + ); } const changes = getChanges(record); if (changes.length == 0) { + // A named event may be recorded without any field changes + if (record.event_type !== null && record.event_type !== undefined) { + return eventDetails; + } + return ( {t`No changes recorded`} @@ -263,6 +356,7 @@ function AuditChangesSummary({ return ( + {eventDetails} {changes.slice(0, MAX_INLINE_CHANGES).map((change) => ( ( - {actionLabel(record.action)} + {record.event_label ?? actionLabel(record.action)} ) }, diff --git a/src/frontend/src/tables/stock/StockTrackingTable.tsx b/src/frontend/src/tables/stock/StockTrackingTable.tsx deleted file mode 100644 index 581c075124..0000000000 --- a/src/frontend/src/tables/stock/StockTrackingTable.tsx +++ /dev/null @@ -1,317 +0,0 @@ -import { t } from '@lingui/core/macro'; -import { Table, Text } from '@mantine/core'; -import { type ReactNode, useCallback, useMemo } from 'react'; -import { useNavigate } from 'react-router-dom'; - -import { ApiEndpoints } from '@lib/enums/ApiEndpoints'; -import { ModelType } from '@lib/enums/ModelType'; -import { apiUrl } from '@lib/functions/Api'; -import { formatDecimal } from '@lib/functions/Formatting'; -import useTable from '@lib/hooks/UseTable'; -import type { TableFilter } from '@lib/types/Filters'; -import type { TableColumn } from '@lib/types/Tables'; -import { RenderBuildOrder } from '../../components/render/Build'; -import { RenderCompany } from '../../components/render/Company'; -import { - RenderPurchaseOrder, - RenderReturnOrder, - RenderSalesOrder, - RenderTransferOrder -} from '../../components/render/Order'; -import { RenderPart } from '../../components/render/Part'; -import { StatusRenderer } from '../../components/render/StatusRenderer'; -import { - RenderStockItem, - RenderStockLocation -} from '../../components/render/Stock'; -import { RenderUser } from '../../components/render/User'; -import { - DateColumn, - DescriptionColumn, - IPNColumn, - PartColumn, - StockColumn -} from '../../components/tables/ColumnRenderers'; -import { - IncludeVariantsFilter, - MaxDateFilter, - MinDateFilter, - UserFilter -} from '../../components/tables/Filter'; -import { InvenTreeTable } from '../../components/tables/InvenTreeTable'; - -type StockTrackingEntry = { - label: string; - key: string; - details: ReactNode; -}; - -export function StockTrackingTable({ - itemId, - partId -}: Readonly<{ - itemId?: number; - partId?: number; -}>) { - const navigate = useNavigate(); - const table = useTable(partId ? 'part_stock_tracking' : 'stock_tracking'); - - // Render "details" for a stock tracking record - const renderDetails = useCallback( - (record: any) => { - const deltas: any = record?.deltas ?? {}; - - const entries: StockTrackingEntry[] = [ - { - label: t`Stock Item`, - key: 'stockitem', - details: - deltas.stockitem_detail && - RenderStockItem({ instance: deltas.stockitem_detail, link: true }) - }, - { - label: t`Stock Item`, - key: 'item', - details: - deltas.item_detail && - RenderStockItem({ - instance: deltas.item_detail, - link: true - }) - }, - { - label: t`Status`, - key: 'status', - details: - deltas.status && - StatusRenderer({ - status: deltas.status, - type: ModelType.stockitem, - fallbackStatus: deltas.status_logical - }) - }, - { - label: t`Old Status`, - key: 'old_status', - details: - deltas.old_status && - StatusRenderer({ - status: deltas.old_status, - type: ModelType.stockitem, - fallbackStatus: deltas.old_status_logical - }) - }, - { - label: t`Quantity`, - key: 'quantity', - details: formatDecimal(deltas.quantity) - }, - { - label: t`Added`, - key: 'added', - details: deltas.added - }, - { - label: t`Removed`, - key: 'removed', - details: deltas.removed - }, - { - label: t`Part`, - key: 'part', - details: - deltas.part_detail && - RenderPart({ - instance: deltas.part_detail, - link: true, - navigate: navigate - }) - }, - { - label: t`Location`, - key: 'location', - details: - deltas.location_detail && - RenderStockLocation({ - instance: deltas.location_detail, - link: true, - navigate: navigate - }) - }, - { - label: t`Build Order`, - key: 'buildorder', - details: - deltas.buildorder_detail && - RenderBuildOrder({ - instance: deltas.buildorder_detail, - link: true, - navigate: navigate - }) - }, - { - label: t`Purchase Order`, - key: 'purchaseorder', - details: - deltas.purchaseorder_detail && - RenderPurchaseOrder({ - instance: deltas.purchaseorder_detail, - link: true, - navigate: navigate - }) - }, - { - label: t`Sales Order`, - key: 'salesorder', - details: - deltas.salesorder_detail && - RenderSalesOrder({ - instance: deltas.salesorder_detail, - link: true, - navigate: navigate - }) - }, - { - label: t`Return Order`, - key: 'returnorder', - details: - deltas.returnorder_detail && - RenderReturnOrder({ - instance: deltas.returnorder_detail, - link: true, - navigate: navigate - }) - }, - { - label: t`Transfer Order`, - key: 'transferorder', - details: - deltas.transferorder_detail && - RenderTransferOrder({ - instance: deltas.transferorder_detail, - link: true, - navigate: navigate - }) - }, - { - label: t`Customer`, - key: 'customer', - details: - deltas.customer_detail && - RenderCompany({ - instance: deltas.customer_detail, - link: true, - navigate: navigate - }) - } - ]; - - return ( - - - {entries.map( - (entry) => - entry.details && ( - - - {entry.label} - - {entry.details} - - ) - )} - -
- ); - }, - [navigate] - ); - - const filters: TableFilter[] = useMemo(() => { - return [ - MinDateFilter(), - MaxDateFilter(), - IncludeVariantsFilter(), - UserFilter({ - name: 'user', - label: t`User`, - description: t`Filter by user` - }) - ]; - }, []); - - const tableColumns: TableColumn[] = useMemo(() => { - return [ - DateColumn({ - switchable: false - }), - PartColumn({ - title: t`Part`, - part: 'part_detail', - switchable: true, - hidden: !partId - }), - IPNColumn({ - defaultVisible: false, - hidden: !partId - }), - StockColumn({ - title: t`Stock Item`, - accessor: 'item_detail', - nullMessage: ( - {t`Stock item no longer exists`} - ), - sortable: false, - switchable: false, - hidden: !partId - }), - DescriptionColumn({ - accessor: 'label' - }), - { - accessor: 'details', - title: t`Details`, - switchable: false, - render: renderDetails - }, - { - accessor: 'notes', - title: t`Notes`, - sortable: false, - switchable: true - }, - { - accessor: 'user', - title: t`User`, - render: (record: any) => { - if (!record.user_detail) { - return {t`No user information`}; - } - - return RenderUser({ instance: record.user_detail }); - } - } - ]; - }, []); - - return ( - - ); -} diff --git a/src/frontend/tests/pages/pui_stock.spec.ts b/src/frontend/tests/pages/pui_stock.spec.ts index 701b8d5149..517ae4da9e 100644 --- a/src/frontend/tests/pages/pui_stock.spec.ts +++ b/src/frontend/tests/pages/pui_stock.spec.ts @@ -1,5 +1,5 @@ import { expect, test } from '../baseFixtures.js'; -import { stevenuser } from '../defaults.js'; +import { adminuser, stevenuser } from '../defaults.js'; import { clearTableFilters, clickButtonIfVisible, @@ -30,7 +30,6 @@ test('Stock - Basic Tests', async ({ browser }) => { await navigate(page, 'stock/item/1194/details'); await page.getByText('D.123 | Doohickey').waitFor(); await page.getByText('Batch Code: BX-123-2024-2-7').waitFor(); - await loadTab(page, 'Stock Tracking'); await loadTab(page, 'Test Results'); await page.getByText('395c6d5586e5fb656901d047be27e1f7').waitFor(); await loadTab(page, 'Installed Items'); @@ -47,12 +46,6 @@ test('Stock - Basic Tests', async ({ browser }) => { // Automatically navigate through to the newly created stock item await page.getByText('Quantity: 987').first().waitFor(); - await loadTab(page, 'Stock Tracking'); - await page - .getByRole('cell', { name: 'Stock item created' }) - .first() - .waitFor(); - await page.getByRole('cell', { name: 'allaccess Ally Access' }).waitFor(); }); test('Stock - Location Tree', async ({ browser }) => { @@ -508,24 +501,59 @@ test('Stock - Return Items', async ({ browser }) => { await page.getByText('This field is required.').first().waitFor(); }); -test('Stock - Tracking', async ({ browser }) => { - const page = await doCachedLogin(browser, { url: 'stock/item/176/details' }); +test('Stock - History (Stock Item)', async ({ browser }) => { + // Note: The 'admin' user has the "Show Change History" setting enabled + const page = await doCachedLogin(browser, { + user: adminuser, + url: 'stock/item/193/history' + }); + + await page.getByText('widget.purple | Purple Widget').first().waitFor(); + + // Stock tracking events are displayed in the "History" tab + await page.getByText('Installed into assembly').first().waitFor(); + await page.getByText('Assigned serial number').first().waitFor(); + await page.getByText('Stock item created').first().waitFor(); + + // Event deltas link to the related stock item + await page.getByText('Widget Board (assembled) #159').first().waitFor(); +}); + +test('Stock - History (Part)', async ({ browser }) => { + // Note: The 'admin' user has the "Show Change History" setting enabled + const page = await doCachedLogin(browser, { + user: adminuser, + url: 'part/76/history' + }); + + await page.getByText('widget.purple | Purple Widget').first().waitFor(); + + // The history of the part includes stock tracking events for its stock items + await page.getByText('Shipped against Sales Order').first().waitFor(); + await page.getByText('Customer D').first().waitFor(); + await page.getByText('SO0009').first().waitFor(); + await page.getByText('Installed into assembly').first().waitFor(); + await page.getByText('Purple Widget #35').first().waitFor(); + + // ...as well as changes to the part itself (e.g. parameters) + await page.getByText('Color: Purple').first().waitFor(); +}); + +test('Stock - History', async ({ browser }) => { + // Note: The 'admin' user has the "Show Change History" setting enabled + const page = await doCachedLogin(browser, { + user: adminuser, + url: 'stock/item/176/details' + }); await page.getByRole('link', { name: 'Widget Assembly # 2' }).waitFor(); - // Navigate to the "stock tracking" tab - await loadTab(page, 'Stock Tracking'); - - await page.getByText('Factory/Office Block/Room').first().waitFor(); - await page.getByRole('link', { name: 'Widget Assembly' }).waitFor(); - await page.getByRole('cell', { name: 'Installed into assembly' }).waitFor(); - - /* Add some more stock items and tracking information: + /* Add some more stock items and history information: * - Duplicate this stock item * - Give it a unique serial number - * - Ensure the tracking information is duplicated correctly + * - Ensure the creation of the new item is recorded * - Delete the new stock item - * - Ensure that the tracking information is retained against the base part + * - Ensure that the history is retained against the base part */ // Duplicate the stock item @@ -540,13 +568,10 @@ test('Stock - Tracking', async ({ browser }) => { .fill('9876'); await page.getByRole('button', { name: 'Submit' }).click(); - // Check stock tracking information is correct + // Check that the creation of the new item is recorded await page.getByText('Serial Number: 9876').first().waitFor(); - await loadTab(page, 'Stock Tracking'); - await page - .getByRole('cell', { name: 'Stock item created' }) - .first() - .waitFor(); + await loadTab(page, 'History', true); + await page.getByText('Stock item created').first().waitFor(); // Delete this stock item await page @@ -557,17 +582,10 @@ test('Stock - Tracking', async ({ browser }) => { .click(); await page.getByRole('button', { name: 'Delete' }).click(); - // Check stock tracking for base part - await loadTab(page, 'Stock History'); - await page.getByRole('button', { name: 'Stock Tracking' }).click(); - - await page.getByText('Stock item no longer exists').first().waitFor(); - await page - .getByRole('cell', { name: 'Thumbnail Blue Widget' }) - .first() - .waitFor(); - - await page.getByText('# 162').first().waitFor(); + // The history of the deleted item is retained against the base part + await loadTab(page, 'History', true); + await page.getByText('Object was deleted').first().waitFor(); + await page.getByText('Stock item created').first().waitFor(); }); test('Stock - Location', async ({ browser }) => {