From eee4916350cd22a3178830845b458c6c62564ec5 Mon Sep 17 00:00:00 2001 From: Oliver Date: Wed, 29 Jan 2025 22:45:39 +1100 Subject: [PATCH] Order start dates (#8966) * Add 'start_date' field to orders - PurchaseOrder - SalesOrder - ReturnOrder * Add serializer field * Add API filters * Add table columns * Add fields to forms * Table filters * Add validation check * Refactor BuildOrderTable * Update detail page * Bump API version * Allow sorting by start_date * Fix for purchase order field * Update detail pages * Update playwright tests * Updated playwright tests * Documentation updates * Updated playwright tests --- docs/docs/build/build.md | 15 ++++++---- docs/docs/order/purchase_order.md | 24 ++++++++++++--- docs/docs/order/return_order.md | 18 +++++++++++- docs/docs/order/sales_order.md | 29 ++++++++++++++----- .../InvenTree/InvenTree/api_version.py | 8 ++++- src/backend/InvenTree/order/api.py | 27 +++++++++++++++++ ...rt_date_returnorder_start_date_and_more.py | 28 ++++++++++++++++++ src/backend/InvenTree/order/models.py | 16 ++++++++++ src/backend/InvenTree/order/serializers.py | 3 +- src/frontend/src/forms/PurchaseOrderForms.tsx | 3 ++ src/frontend/src/forms/ReturnOrderForms.tsx | 14 +++++++-- src/frontend/src/forms/SalesOrderForms.tsx | 14 +++++++-- src/frontend/src/pages/build/BuildDetail.tsx | 4 +++ .../pages/purchasing/PurchaseOrderDetail.tsx | 8 +++++ .../src/pages/sales/ReturnOrderDetail.tsx | 8 +++++ .../src/pages/sales/SalesOrderDetail.tsx | 8 +++++ src/frontend/src/tables/ColumnRenderers.tsx | 8 +++++ src/frontend/src/tables/Filter.tsx | 18 ++++++++++++ .../src/tables/build/BuildOrderTable.tsx | 23 ++++----------- .../tables/purchasing/PurchaseOrderTable.tsx | 18 ++++++++++++ .../src/tables/sales/ReturnOrderTable.tsx | 18 ++++++++++++ .../src/tables/sales/SalesOrderTable.tsx | 18 ++++++++++++ .../tests/pages/pui_purchase_order.spec.ts | 18 ++++++++---- src/frontend/tests/pui_tables.spec.ts | 14 +++++++++ 24 files changed, 317 insertions(+), 45 deletions(-) create mode 100644 src/backend/InvenTree/order/migrations/0106_purchaseorder_start_date_returnorder_start_date_and_more.py diff --git a/docs/docs/build/build.md b/docs/docs/build/build.md index 7facbfbab5..d800f153a6 100644 --- a/docs/docs/build/build.md +++ b/docs/docs/build/build.md @@ -96,7 +96,7 @@ When a *Build Order* is created, we then have the ability to *allocate* stock it !!! info "Example - Stock Allocation" Let's say that to assembly a single "Widget", we require 2 "flanges". So, to complete a build of 10 "Widgets", 20 "flanges" will be required. We *allocate* 20 flanged against this build order. -Allocating stock to a build does not actually subtrack the stock from the database. Allocations signal an *intent* to take that stock for the purpose of this build. Stock allocations are actioned at the completion of a build. +Allocating stock to a build does not actually subtract the stock from the database. Allocations signal an *intent* to take that stock for the purpose of this build. Stock allocations are subtracted from stock at the completion of a build. !!! info "Part Allocation Information" Any part which has stock allocated to a build order will indicate this on the part information page. @@ -243,7 +243,7 @@ The form will validate the build order is ready to be completed, and will preven If you wish to complete the build despite the missing parts, toggle the `Accept Unallocated` option to true to override the warning and allow completion with unallocated parts. !!! info "Overallocated Stock Items" - If the warning message `Some stock items have been overallocated` is shown, you have more stock than required by the BOM for the part being built allocated to the build order. By default the `Not permissted` option is selected and you will need to return to the allocation screen and remove the extra items before the build can be completed. + If the warning message `Some stock items have been overallocated` is shown, you have more stock than required by the BOM for the part being built allocated to the build order. By default the `Not permitted` option is selected and you will need to return to the allocation screen and remove the extra items before the build can be completed. Alternatively, you can select `Accept as consumed by this build order` to continue with the allocation and remove the extra items from stock (e.g. if they were destroyed during build), or select `Deallocate before completing this build order` if you would like the extra items to be returned to stock for use in future builds. @@ -265,17 +265,22 @@ The `Cancel Build` form will be displayed, click on the confirmation switch then ## Build Scheduling +Build orders can be scheduled for a future date, to allow for planning of production schedules. + ### Start Date -Build orders can be optionally scheduled to *start* at a specified date. This may be useful for planning production schedules. +Build orders can be optionally scheduled to *start* at a specified date, by setting the *Start Date* field. This field can be left blank if the build is to start immediately. + +### Target Date + +Build orders can be optionally scheduled to be completed by a certain date, by setting the *Target Date* field. This field can be left blank if the build has no specific deadline. ### Overdue Builds -Build orders may (optionally) have a target completion date specified. If this date is reached but the build order remains incomplete, then the build is considered *overdue*. +If the *Target Date* is reached but the build order remains incomplete, then the build is considered *overdue*. This can be useful for tracking production delays, and can be used to generate reports on build order performance. - ## Build Order Settings The following [global settings](../settings/global.md) are available for adjusting the behavior of build orders: diff --git a/docs/docs/order/purchase_order.md b/docs/docs/order/purchase_order.md index 349a145936..d0b9233efc 100644 --- a/docs/docs/order/purchase_order.md +++ b/docs/docs/order/purchase_order.md @@ -107,19 +107,19 @@ Each item marked as "received" is automatically converted into a stock item. To see the list of stock items created from the purchase order, click on the Received Items tab. -### Complete Order +## Complete Order Once the quantity of all __received__ items is equal or above the quantity of all line items, the order will be automatically marked as __complete__. It is also possible to complete the order before all items were received (or if there were missing items). To do so, click on the button on the main purchase order detail panel and confirm the order was completed. -### Cancel Order +## Cancel Order In the event that the order won't be processed, user has the option of cancelling the order instead. To do so, simply click on the button on the main purchase order detail panel and confirm the purchase order has been cancelled. -### Duplicate Purchase Order +## Duplicate Purchase Order Duplicating a Purchase Order allows the user to quickly create a new *copy* of an existing order, using the same supplier and line item information. @@ -141,7 +141,23 @@ A new purchase order is then created based on the currently selected order: {% include "img.html" %} {% endwith %} -### Calendar view +## Order Scheduling + +Purchase orders can be scheduled for a future date, to allow for planning of future orders. + +### Start Date + +The *Start Date* of the purchase order is the date on which the order is scheduled to be issued to the supplier. + +### Target Date + +The *Target Date* of the purchase order is the date on which the order is expected to be completed / received from the supplier. + +### Overdue Orders + +If the *Target Date* of the purchase order is reached but the order has not been completed, the order will be marked as *overdue*. + +## Calendar view Using the button to the top right of the list of Purchase Orders, the view can be switched to a calendar view using the button . This view shows orders with a defined target date only. diff --git a/docs/docs/order/return_order.md b/docs/docs/order/return_order.md index c02653fd17..a188dd076f 100644 --- a/docs/docs/order/return_order.md +++ b/docs/docs/order/return_order.md @@ -115,7 +115,23 @@ While [line items](#line-items) must reference a particular stock item, extra li Custom [reports](../report/templates.md) can be generated against each Return Order. -### Calendar view +## Order Scheduling + +Return Orders can be scheduled to be completed on a specific date. This can be useful for planning and tracking the return of items. + +### Start Date + +The *Start Date* of the return order is the date on which the order is scheduled to be issued to the customer. + +### Target Date + +The *Target Date* of the return order is the date on which the order is scheduled to be completed. + +### Overdue Orders + +If the *Target Date* of a return order has passed, the order will be marked as *Overdue*. This can be useful for tracking orders which are behind schedule. + +## Calendar view Using the button to the top right of the list of Return Orders, the view can be switched to a calendar view using the button . This view shows orders with a defined target date only. diff --git a/docs/docs/order/sales_order.md b/docs/docs/order/sales_order.md index a8834a2d0d..fca9a46f7b 100644 --- a/docs/docs/order/sales_order.md +++ b/docs/docs/order/sales_order.md @@ -60,7 +60,7 @@ Fill out the rest of the form with the sales order information then click on Line Items tab then clicking on the Add Line Item button. @@ -71,7 +71,7 @@ Once the "Add Line Item" form opens, select a part in the list. Fill out the rest of the form then click on Submit -#### Shipments +## Shipments After all line items were added to the sales order, user needs to create one or more [shipments](#sales-order-shipments) in order to allocate stock for those parts. @@ -82,7 +82,7 @@ In order to create a new shipment: Repeat the two steps above to create more shipments. -#### Allocate Stock Items +### Allocate Stock Items After shipments were created, user can either: @@ -91,7 +91,7 @@ After shipments were created, user can either: During the allocation process, user is required to select the desired shipment that will contain the stock items. -#### Complete Shipment +### Complete Shipment To complete a shipment, click on the Pending Shipments tab then click on button shown in the shipment table. @@ -99,16 +99,31 @@ Fill out the "Complete Shipment" form then click on Completed Shipments tab. In the completed shipments table, click on the icon next to each shipment reference to see the items and quantities which were shipped. -### Complete Order +## Complete Order Once all items in the sales order have been shipped, click on Complete Order to mark the sales order as shipped. Confirm then click on Submit to complete the order. -### Cancel Order +## Cancel Order To cancel the order, click on the menu button next to the Complete Order button, then click on the " Cancel Order" menu option. Confirm then click on the Submit to cancel the order. +## Order Scheduling -### Calendar view +Sales orders can be scheduled for a future date, to allow for order scheduling. + +### Start Date + +The *Start Date* of the sales order is the date on which the order is scheduled to be issued, allowing work to begin on the order. + +### Target Date + +The *Target Date* of the sales order is the date on which the order is scheduled to be completed and shipped. + +### Overdue Orders + +If the *Target Date* of the sales order has passed, the order will be marked as *overdue*. + +## Calendar view Using the button to the top right of the list of Sales Orders, the view can be switched to a calendar view using the button . This view shows orders with a defined target date only. diff --git a/src/backend/InvenTree/InvenTree/api_version.py b/src/backend/InvenTree/InvenTree/api_version.py index 666567f6b0..e7d0e0e2f6 100644 --- a/src/backend/InvenTree/InvenTree/api_version.py +++ b/src/backend/InvenTree/InvenTree/api_version.py @@ -1,13 +1,19 @@ """InvenTree API version information.""" # InvenTree API version -INVENTREE_API_VERSION = 305 +INVENTREE_API_VERSION = 306 """Increment this API version number whenever there is a significant change to the API that any clients need to know about.""" INVENTREE_API_TEXT = """ +v3-6 0 2025-01-28 : https://github.com/inventree/InvenTree/pull/8966 + - Adds "start_date" to PurchasesOrder API + - Adds "start_date" to SalesOrder API + - Adds "start_date" to ReturnOrder API + - Updated API filters + v305 - 2025-01-26 : https://github.com/inventree/InvenTree/pull/8950 - Bug fixes for the SupplierPart API - Refactoring for data export via API diff --git a/src/backend/InvenTree/order/api.py b/src/backend/InvenTree/order/api.py index 906307c7d5..80b31ec6c4 100644 --- a/src/backend/InvenTree/order/api.py +++ b/src/backend/InvenTree/order/api.py @@ -181,6 +181,30 @@ class OrderFilter(rest_filters.FilterSet): label=_('Created After'), field_name='creation_date', lookup_expr='gt' ) + has_start_date = rest_filters.BooleanFilter( + label=_('Has Start Date'), method='filter_has_start_date' + ) + + def filter_has_start_date(self, queryset, name, value): + """Filter by whether or not the order has a start date.""" + return queryset.filter(start_date__isnull=not str2bool(value)) + + start_date_before = InvenTreeDateFilter( + label=_('Start Date Before'), field_name='start_date', lookup_expr='lt' + ) + + start_date_after = InvenTreeDateFilter( + label=_('Start Date After'), field_name='start_date', lookup_expr='gt' + ) + + has_target_date = rest_filters.BooleanFilter( + label=_('Has Target Date'), method='filter_has_target_date' + ) + + def filter_has_target_date(self, queryset, name, value): + """Filter by whether or not the order has a target date.""" + return queryset.filter(target_date__isnull=not str2bool(value)) + target_date_before = InvenTreeDateFilter( label=_('Target Date Before'), field_name='target_date', lookup_expr='lt' ) @@ -336,6 +360,7 @@ class PurchaseOrderList( 'created_by', 'reference', 'supplier__name', + 'start_date', 'target_date', 'complete_date', 'line_items', @@ -797,6 +822,7 @@ class SalesOrderList( 'customer__name', 'customer_reference', 'status', + 'start_date', 'target_date', 'line_items', 'shipment_date', @@ -1383,6 +1409,7 @@ class ReturnOrderList( 'customer_reference', 'line_items', 'status', + 'start_date', 'target_date', 'complete_date', 'project_code', diff --git a/src/backend/InvenTree/order/migrations/0106_purchaseorder_start_date_returnorder_start_date_and_more.py b/src/backend/InvenTree/order/migrations/0106_purchaseorder_start_date_returnorder_start_date_and_more.py new file mode 100644 index 0000000000..d238723629 --- /dev/null +++ b/src/backend/InvenTree/order/migrations/0106_purchaseorder_start_date_returnorder_start_date_and_more.py @@ -0,0 +1,28 @@ +# Generated by Django 4.2.18 on 2025-01-27 12:50 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('order', '0105_auto_20241128_0431'), + ] + + operations = [ + migrations.AddField( + model_name='purchaseorder', + name='start_date', + field=models.DateField(blank=True, help_text='Scheduled start date for this order', null=True, verbose_name='Start date'), + ), + migrations.AddField( + model_name='returnorder', + name='start_date', + field=models.DateField(blank=True, help_text='Scheduled start date for this order', null=True, verbose_name='Start date'), + ), + migrations.AddField( + model_name='salesorder', + name='start_date', + field=models.DateField(blank=True, help_text='Scheduled start date for this order', null=True, verbose_name='Start date'), + ), + ] diff --git a/src/backend/InvenTree/order/models.py b/src/backend/InvenTree/order/models.py index 536489659f..d46d48af55 100644 --- a/src/backend/InvenTree/order/models.py +++ b/src/backend/InvenTree/order/models.py @@ -203,6 +203,8 @@ class Order( creation_date: Automatic date of order creation created_by: User who created this order (automatically captured) issue_date: Date the order was issued + start_date: Date the order is scheduled to be started + target_date: Expected or desired completion date complete_date: Date the order was completed responsible: User (or group) responsible for managing the order """ @@ -244,6 +246,13 @@ class Order( 'contact': _('Contact does not match selected company') }) + # Target date should be *after* the start date + if self.start_date and self.target_date and self.start_date > self.target_date: + raise ValidationError({ + 'target_date': _('Target date must be after start date'), + 'start_date': _('Start date must be before target date'), + }) + def clean_line_item(self, line): """Clean a line item for this order. @@ -310,6 +319,13 @@ class Order( blank=True, verbose_name=_('Link'), help_text=_('Link to external page') ) + start_date = models.DateField( + null=True, + blank=True, + verbose_name=_('Start date'), + help_text=_('Scheduled start date for this order'), + ) + target_date = models.DateField( blank=True, null=True, diff --git a/src/backend/InvenTree/order/serializers.py b/src/backend/InvenTree/order/serializers.py index ba2446b697..e74f4655cd 100644 --- a/src/backend/InvenTree/order/serializers.py +++ b/src/backend/InvenTree/order/serializers.py @@ -186,8 +186,9 @@ class AbstractOrderSerializer(DataImportExportSerializerMixin, serializers.Seria """Construct a set of fields for this serializer.""" return [ 'pk', - 'creation_date', 'created_by', + 'creation_date', + 'start_date', 'target_date', 'description', 'line_items', diff --git a/src/frontend/src/forms/PurchaseOrderForms.tsx b/src/frontend/src/forms/PurchaseOrderForms.tsx index 9b8415a0d6..5998b9b2ce 100644 --- a/src/frontend/src/forms/PurchaseOrderForms.tsx +++ b/src/frontend/src/forms/PurchaseOrderForms.tsx @@ -170,6 +170,9 @@ export function usePurchaseOrderFields({ order_currency: { icon: }, + start_date: { + icon: + }, target_date: { icon: }, diff --git a/src/frontend/src/forms/ReturnOrderForms.tsx b/src/frontend/src/forms/ReturnOrderForms.tsx index 91fe3acd6c..f4e2776e54 100644 --- a/src/frontend/src/forms/ReturnOrderForms.tsx +++ b/src/frontend/src/forms/ReturnOrderForms.tsx @@ -1,6 +1,11 @@ import { t } from '@lingui/macro'; import { Flex, Table } from '@mantine/core'; -import { IconAddressBook, IconUser, IconUsers } from '@tabler/icons-react'; +import { + IconAddressBook, + IconCalendar, + IconUser, + IconUsers +} from '@tabler/icons-react'; import { useMemo } from 'react'; import RemoveRowButton from '../components/buttons/RemoveRowButton'; @@ -39,7 +44,12 @@ export function useReturnOrderFields({ customer_reference: {}, project_code: {}, order_currency: {}, - target_date: {}, + start_date: { + icon: + }, + target_date: { + icon: + }, link: {}, contact: { icon: , diff --git a/src/frontend/src/forms/SalesOrderForms.tsx b/src/frontend/src/forms/SalesOrderForms.tsx index c191357dda..c30f9ea0db 100644 --- a/src/frontend/src/forms/SalesOrderForms.tsx +++ b/src/frontend/src/forms/SalesOrderForms.tsx @@ -1,6 +1,11 @@ import { t } from '@lingui/macro'; import { Table } from '@mantine/core'; -import { IconAddressBook, IconUser, IconUsers } from '@tabler/icons-react'; +import { + IconAddressBook, + IconCalendar, + IconUser, + IconUsers +} from '@tabler/icons-react'; import { useEffect, useMemo, useState } from 'react'; import RemoveRowButton from '../components/buttons/RemoveRowButton'; @@ -40,7 +45,12 @@ export function useSalesOrderFields({ customer_reference: {}, project_code: {}, order_currency: {}, - target_date: {}, + start_date: { + icon: + }, + target_date: { + icon: + }, link: {}, contact: { icon: , diff --git a/src/frontend/src/pages/build/BuildDetail.tsx b/src/frontend/src/pages/build/BuildDetail.tsx index d70a219731..916be8f861 100644 --- a/src/frontend/src/pages/build/BuildDetail.tsx +++ b/src/frontend/src/pages/build/BuildDetail.tsx @@ -186,6 +186,7 @@ export default function BuildDetail() { name: 'creation_date', label: t`Created`, icon: 'calendar', + copy: true, hidden: !build.creation_date }, { @@ -193,6 +194,7 @@ export default function BuildDetail() { name: 'start_date', label: t`Start Date`, icon: 'calendar', + copy: true, hidden: !build.start_date }, { @@ -200,6 +202,7 @@ export default function BuildDetail() { name: 'target_date', label: t`Target Date`, icon: 'calendar', + copy: true, hidden: !build.target_date }, { @@ -207,6 +210,7 @@ export default function BuildDetail() { name: 'completion_date', label: t`Completed`, icon: 'calendar', + copy: true, hidden: !build.completion_date }, { diff --git a/src/frontend/src/pages/purchasing/PurchaseOrderDetail.tsx b/src/frontend/src/pages/purchasing/PurchaseOrderDetail.tsx index dec6fa689f..2edc54374d 100644 --- a/src/frontend/src/pages/purchasing/PurchaseOrderDetail.tsx +++ b/src/frontend/src/pages/purchasing/PurchaseOrderDetail.tsx @@ -243,6 +243,14 @@ export default function PurchaseOrderDetail() { copy: true, hidden: !order.issue_date }, + { + type: 'date', + name: 'start_date', + label: t`Start Date`, + icon: 'calendar', + copy: true, + hidden: !order.start_date + }, { type: 'date', name: 'target_date', diff --git a/src/frontend/src/pages/sales/ReturnOrderDetail.tsx b/src/frontend/src/pages/sales/ReturnOrderDetail.tsx index e63f484dd3..4925ea1e9e 100644 --- a/src/frontend/src/pages/sales/ReturnOrderDetail.tsx +++ b/src/frontend/src/pages/sales/ReturnOrderDetail.tsx @@ -214,6 +214,14 @@ export default function ReturnOrderDetail() { copy: true, hidden: !order.issue_date }, + { + type: 'date', + name: 'start_date', + label: t`Start Date`, + icon: 'calendar', + copy: true, + hidden: !order.start_date + }, { type: 'date', name: 'target_date', diff --git a/src/frontend/src/pages/sales/SalesOrderDetail.tsx b/src/frontend/src/pages/sales/SalesOrderDetail.tsx index 282dcf2d49..4abc8f1e06 100644 --- a/src/frontend/src/pages/sales/SalesOrderDetail.tsx +++ b/src/frontend/src/pages/sales/SalesOrderDetail.tsx @@ -225,6 +225,14 @@ export default function SalesOrderDetail() { copy: true, hidden: !order.issue_date }, + { + type: 'date', + name: 'start_date', + label: t`Start Date`, + icon: 'calendar', + hidden: !order.start_date, + copy: true + }, { type: 'date', name: 'target_date', diff --git a/src/frontend/src/tables/ColumnRenderers.tsx b/src/frontend/src/tables/ColumnRenderers.tsx index 393ba8df39..54722fe3b6 100644 --- a/src/frontend/src/tables/ColumnRenderers.tsx +++ b/src/frontend/src/tables/ColumnRenderers.tsx @@ -243,6 +243,14 @@ export function DateColumn(props: TableColumnProps): TableColumn { }; } +export function StartDateColumn(props: TableColumnProps): TableColumn { + return DateColumn({ + accessor: 'start_date', + title: t`Start Date`, + ...props + }); +} + export function TargetDateColumn(props: TableColumnProps): TableColumn { return DateColumn({ accessor: 'target_date', diff --git a/src/frontend/src/tables/Filter.tsx b/src/frontend/src/tables/Filter.tsx index fd8bce2856..00cdb4ca55 100644 --- a/src/frontend/src/tables/Filter.tsx +++ b/src/frontend/src/tables/Filter.tsx @@ -169,6 +169,24 @@ export function CreatedAfterFilter(): TableFilter { }; } +export function StartDateBeforeFilter(): TableFilter { + return { + name: 'start_date_before', + label: t`Start Date Before`, + description: t`Show items with a start date before this date`, + type: 'date' + }; +} + +export function StartDateAfterFilter(): TableFilter { + return { + name: 'start_date_after', + label: t`Start Date After`, + description: t`Show items with a start date after this date`, + type: 'date' + }; +} + export function TargetDateBeforeFilter(): TableFilter { return { name: 'target_date_before', diff --git a/src/frontend/src/tables/build/BuildOrderTable.tsx b/src/frontend/src/tables/build/BuildOrderTable.tsx index 9aae96df0a..b94ae28c71 100644 --- a/src/frontend/src/tables/build/BuildOrderTable.tsx +++ b/src/frontend/src/tables/build/BuildOrderTable.tsx @@ -26,6 +26,7 @@ import { ProjectCodeColumn, ReferenceColumn, ResponsibleColumn, + StartDateColumn, StatusColumn, TargetDateColumn } from '../ColumnRenderers'; @@ -43,6 +44,8 @@ import { OverdueFilter, ProjectCodeFilter, ResponsibleFilter, + StartDateAfterFilter, + StartDateBeforeFilter, type TableFilter, TargetDateAfterFilter, TargetDateBeforeFilter @@ -107,11 +110,7 @@ export function BuildOrderTable({ sortable: true }, CreationDateColumn({}), - DateColumn({ - accessor: 'start_date', - title: t`Start Date`, - sortable: true - }), + StartDateColumn({}), TargetDateColumn({}), DateColumn({ accessor: 'completion_date', @@ -156,18 +155,8 @@ export function BuildOrderTable({ CreatedAfterFilter(), TargetDateBeforeFilter(), TargetDateAfterFilter(), - { - name: 'start_date_before', - type: 'date', - label: t`Start Date Before`, - description: t`Show items with a start date before this date` - }, - { - name: 'start_date_after', - type: 'date', - label: t`Start Date After`, - description: t`Show items with a start date after this date` - }, + StartDateBeforeFilter(), + StartDateAfterFilter(), { name: 'has_target_date', type: 'boolean', diff --git a/src/frontend/src/tables/purchasing/PurchaseOrderTable.tsx b/src/frontend/src/tables/purchasing/PurchaseOrderTable.tsx index 36c343bad9..19c1432f7d 100644 --- a/src/frontend/src/tables/purchasing/PurchaseOrderTable.tsx +++ b/src/frontend/src/tables/purchasing/PurchaseOrderTable.tsx @@ -26,6 +26,7 @@ import { ProjectCodeColumn, ReferenceColumn, ResponsibleColumn, + StartDateColumn, StatusColumn, TargetDateColumn } from '../ColumnRenderers'; @@ -44,6 +45,8 @@ import { OverdueFilter, ProjectCodeFilter, ResponsibleFilter, + StartDateAfterFilter, + StartDateBeforeFilter, type TableFilter, TargetDateAfterFilter, TargetDateBeforeFilter @@ -79,6 +82,20 @@ export function PurchaseOrderTable({ CreatedAfterFilter(), TargetDateBeforeFilter(), TargetDateAfterFilter(), + StartDateBeforeFilter(), + StartDateAfterFilter(), + { + name: 'has_target_date', + type: 'boolean', + label: t`Has Target Date`, + description: t`Show orders with a target date` + }, + { + name: 'has_start_date', + type: 'boolean', + label: t`Has Start Date`, + description: t`Show orders with a start date` + }, CompletedBeforeFilter(), CompletedAfterFilter(), ProjectCodeFilter({ choices: projectCodeFilters.choices }), @@ -120,6 +137,7 @@ export function PurchaseOrderTable({ ProjectCodeColumn({}), CreationDateColumn({}), CreatedByColumn({}), + StartDateColumn({}), TargetDateColumn({}), CompletionDateColumn({ accessor: 'complete_date' diff --git a/src/frontend/src/tables/sales/ReturnOrderTable.tsx b/src/frontend/src/tables/sales/ReturnOrderTable.tsx index d1715a3658..76c0038d15 100644 --- a/src/frontend/src/tables/sales/ReturnOrderTable.tsx +++ b/src/frontend/src/tables/sales/ReturnOrderTable.tsx @@ -26,6 +26,7 @@ import { ProjectCodeColumn, ReferenceColumn, ResponsibleColumn, + StartDateColumn, StatusColumn, TargetDateColumn } from '../ColumnRenderers'; @@ -44,6 +45,8 @@ import { OverdueFilter, ProjectCodeFilter, ResponsibleFilter, + StartDateAfterFilter, + StartDateBeforeFilter, type TableFilter, TargetDateAfterFilter, TargetDateBeforeFilter @@ -76,6 +79,20 @@ export function ReturnOrderTable({ CreatedAfterFilter(), TargetDateBeforeFilter(), TargetDateAfterFilter(), + StartDateBeforeFilter(), + StartDateAfterFilter(), + { + name: 'has_target_date', + type: 'boolean', + label: t`Has Target Date`, + description: t`Show orders with a target date` + }, + { + name: 'has_start_date', + type: 'boolean', + label: t`Has Start Date`, + description: t`Show orders with a start date` + }, CompletedBeforeFilter(), CompletedAfterFilter(), HasProjectCodeFilter(), @@ -129,6 +146,7 @@ export function ReturnOrderTable({ ProjectCodeColumn({}), CreationDateColumn({}), CreatedByColumn({}), + StartDateColumn({}), TargetDateColumn({}), CompletionDateColumn({ accessor: 'complete_date' diff --git a/src/frontend/src/tables/sales/SalesOrderTable.tsx b/src/frontend/src/tables/sales/SalesOrderTable.tsx index 62adb55f86..f07f0cad1f 100644 --- a/src/frontend/src/tables/sales/SalesOrderTable.tsx +++ b/src/frontend/src/tables/sales/SalesOrderTable.tsx @@ -27,6 +27,7 @@ import { ReferenceColumn, ResponsibleColumn, ShipmentDateColumn, + StartDateColumn, StatusColumn, TargetDateColumn } from '../ColumnRenderers'; @@ -45,6 +46,8 @@ import { OverdueFilter, ProjectCodeFilter, ResponsibleFilter, + StartDateAfterFilter, + StartDateBeforeFilter, type TableFilter, TargetDateAfterFilter, TargetDateBeforeFilter @@ -77,6 +80,20 @@ export function SalesOrderTable({ CreatedAfterFilter(), TargetDateBeforeFilter(), TargetDateAfterFilter(), + StartDateBeforeFilter(), + StartDateAfterFilter(), + { + name: 'has_target_date', + type: 'boolean', + label: t`Has Target Date`, + description: t`Show orders with a target date` + }, + { + name: 'has_start_date', + type: 'boolean', + label: t`Has Start Date`, + description: t`Show orders with a start date` + }, CompletedBeforeFilter(), CompletedAfterFilter(), HasProjectCodeFilter(), @@ -166,6 +183,7 @@ export function SalesOrderTable({ ProjectCodeColumn({}), CreationDateColumn({}), CreatedByColumn({}), + StartDateColumn({}), TargetDateColumn({}), ShipmentDateColumn({}), ResponsibleColumn({}), diff --git a/src/frontend/tests/pages/pui_purchase_order.spec.ts b/src/frontend/tests/pages/pui_purchase_order.spec.ts index dbfd36da08..19ea8c10a3 100644 --- a/src/frontend/tests/pages/pui_purchase_order.spec.ts +++ b/src/frontend/tests/pages/pui_purchase_order.spec.ts @@ -3,11 +3,12 @@ import { baseUrl } from '../defaults.ts'; import { clearTableFilters, clickButtonIfVisible, - openFilterDrawer + openFilterDrawer, + setTableChoiceFilter } from '../helpers.ts'; import { doQuickLogin } from '../login.ts'; -test('Purchase Orders', async ({ page }) => { +test('Purchase Orders - List', async ({ page }) => { await doQuickLogin(page); await page.getByRole('tab', { name: 'Purchasing' }).click(); @@ -22,10 +23,17 @@ test('Purchase Orders', async ({ page }) => { await page.getByText('Pending').first().waitFor(); await page.getByText('On Hold').first().waitFor(); - // Click through to a particular purchase order - await page.getByRole('cell', { name: 'PO0013' }).click(); + // Filter by 'has start date' + await setTableChoiceFilter(page, 'Has Start Date', 'Yes'); + await page.getByRole('cell', { name: 'Scheduled purchase order' }).waitFor(); + // Click through to a particular purchase order + await page.getByRole('cell', { name: 'PO0015' }).click(); await page.getByRole('button', { name: 'Issue Order' }).waitFor(); + + // Expected values + await page.getByText('2025-06-12').waitFor(); // Start Date + await page.getByText('2025-07-17').waitFor(); // Target Date }); test('Purchase Orders - Barcodes', async ({ page }) => { @@ -70,7 +78,7 @@ test('Purchase Orders - Barcodes', async ({ page }) => { await page.getByRole('button', { name: 'Issue Order' }).waitFor(); // Ensure we can scan back to this page, with the associated barcode - await page.goto(`${baseUrl}/`); + await page.getByRole('tab', { name: 'Sales' }).click(); await page.waitForTimeout(250); await page.getByRole('button', { name: 'Open Barcode Scanner' }).click(); await page.getByPlaceholder('Enter barcode data').fill('1234567890'); diff --git a/src/frontend/tests/pui_tables.spec.ts b/src/frontend/tests/pui_tables.spec.ts index 601143fbb5..eef38b6dd4 100644 --- a/src/frontend/tests/pui_tables.spec.ts +++ b/src/frontend/tests/pui_tables.spec.ts @@ -33,6 +33,7 @@ test('Tables - Filters', async ({ page }) => { await setTableChoiceFilter(page, 'Responsible', 'readers'); await setTableChoiceFilter(page, 'Assigned to me', 'No'); await setTableChoiceFilter(page, 'Project Code', 'PRO-ZEN'); + await setTableChoiceFilter(page, 'Has Start Date', 'Yes'); await clearTableFilters(page); }); @@ -49,4 +50,17 @@ test('Tables - Columns', async ({ page }) => { // De-select some items await page.getByRole('menuitem', { name: 'Description' }).click(); await page.getByRole('menuitem', { name: 'Stocktake' }).click(); + await page.keyboard.press('Escape'); + + await page.goto(`${baseUrl}/sales/index/salesorders`); + + // Open column selector + await page.getByLabel('table-select-columns').click(); + + await page.getByRole('menuitem', { name: 'Start Date' }).click(); + await page.getByRole('menuitem', { name: 'Target Date' }).click(); + await page.getByRole('menuitem', { name: 'Reference', exact: true }).click(); + await page.getByRole('menuitem', { name: 'Project Code' }).click(); + + await page.waitForTimeout(1000); });