2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-04-28 11:36:44 +00:00

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
This commit is contained in:
Oliver 2025-01-29 22:45:39 +11:00 committed by GitHub
parent 0c56a3132b
commit eee4916350
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
24 changed files with 317 additions and 45 deletions

View File

@ -96,7 +96,7 @@ When a *Build Order* is created, we then have the ability to *allocate* stock it
!!! info "Example - Stock Allocation" !!! 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. 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" !!! info "Part Allocation Information"
Any part which has stock allocated to a build order will indicate this on the part information page. 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. 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" !!! 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. 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 Scheduling
Build orders can be scheduled for a future date, to allow for planning of production schedules.
### Start Date ### 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 ### 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. This can be useful for tracking production delays, and can be used to generate reports on build order performance.
## Build Order Settings ## Build Order Settings
The following [global settings](../settings/global.md) are available for adjusting the behavior of build orders: The following [global settings](../settings/global.md) are available for adjusting the behavior of build orders:

View File

@ -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 <span class="badge inventree nav side"><span class='fas fa-sign-in-alt'></span> Received Items</span> tab. To see the list of stock items created from the purchase order, click on the <span class="badge inventree nav side"><span class='fas fa-sign-in-alt'></span> Received Items</span> 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__. 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). 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 <span class='fas fa-check-circle'></span> button on the main purchase order detail panel and confirm the order was completed. To do so, click on the <span class='fas fa-check-circle'></span> 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. 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 <span class='fas fa-times-circle'></span> button on the main purchase order detail panel and confirm the purchase order has been cancelled. To do so, simply click on the <span class='fas fa-times-circle'></span> 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. 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" %} {% include "img.html" %}
{% endwith %} {% 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 <span class='fas fa-calendar-alt'></span>. This view shows orders with a defined target date only. 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 <span class='fas fa-calendar-alt'></span>. This view shows orders with a defined target date only.

View File

@ -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. 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 <span class='fas fa-calendar-alt'></span>. This view shows orders with a defined target date only. 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 <span class='fas fa-calendar-alt'></span>. This view shows orders with a defined target date only.

View File

@ -60,7 +60,7 @@ Fill out the rest of the form with the sales order information then click on <sp
Each Sales Order is uniquely identified by its *Reference* field. Read more about [reference fields](../settings/reference.md). Each Sales Order is uniquely identified by its *Reference* field. Read more about [reference fields](../settings/reference.md).
#### Add Line Items ### Add Line Items
On the sales order detail page, user can link parts to the sales order selecting the <span class="badge inventree nav side"><span class='fas fa-list-ol'></span> Line Items</span> tab then clicking on the <span class="badge inventree add"><span class='fas fa-plus-circle'></span> Add Line Item</span> button. On the sales order detail page, user can link parts to the sales order selecting the <span class="badge inventree nav side"><span class='fas fa-list-ol'></span> Line Items</span> tab then clicking on the <span class="badge inventree add"><span class='fas fa-plus-circle'></span> Add Line Item</span> 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 <span class="badge inventree confirm">Submit</span> Fill out the rest of the form then click on <span class="badge inventree confirm">Submit</span>
#### 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. 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. Repeat the two steps above to create more shipments.
#### Allocate Stock Items ### Allocate Stock Items
After shipments were created, user can either: 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. 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 <span class="badge inventree nav side"><span class='fas fa-truck-loading'></span> Pending Shipments</span> tab then click on <span class='fas fa-truck'></span> button shown in the shipment table. To complete a shipment, click on the <span class="badge inventree nav side"><span class='fas fa-truck-loading'></span> Pending Shipments</span> tab then click on <span class='fas fa-truck'></span> button shown in the shipment table.
@ -99,16 +99,31 @@ Fill out the "Complete Shipment" form then click on <span class="badge inventree
To view all the completed shipment, click on the <span class="badge inventree nav side"><span class='fas fa-truck'></span> Completed Shipments</span> tab. In the completed shipments table, click on the <span class='fas fa-plus'></span> icon next to each shipment reference to see the items and quantities which were shipped. To view all the completed shipment, click on the <span class="badge inventree nav side"><span class='fas fa-truck'></span> Completed Shipments</span> tab. In the completed shipments table, click on the <span class='fas fa-plus'></span> 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 <span class="badge inventree add"><span class='fas fa-check-circle'></span> Complete Order</span> to mark the sales order as shipped. Confirm then click on <span class="badge inventree confirm">Submit</span> to complete the order. Once all items in the sales order have been shipped, click on <span class="badge inventree add"><span class='fas fa-check-circle'></span> Complete Order</span> to mark the sales order as shipped. Confirm then click on <span class="badge inventree confirm">Submit</span> to complete the order.
### Cancel Order ## Cancel Order
To cancel the order, click on the <span class='fas fa-tools'></span> menu button next to the <span class="badge inventree add"><span class='fas fa-check-circle'></span> Complete Order</span> button, then click on the "<span class='fas fa-tools'></span> Cancel Order" menu option. Confirm then click on the <span class="badge inventree confirm">Submit</span> to cancel the order. To cancel the order, click on the <span class='fas fa-tools'></span> menu button next to the <span class="badge inventree add"><span class='fas fa-check-circle'></span> Complete Order</span> button, then click on the "<span class='fas fa-tools'></span> Cancel Order" menu option. Confirm then click on the <span class="badge inventree confirm">Submit</span> 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 <span class='fas fa-calendar-alt'></span>. This view shows orders with a defined target date only. 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 <span class='fas fa-calendar-alt'></span>. This view shows orders with a defined target date only.

View File

@ -1,13 +1,19 @@
"""InvenTree API version information.""" """InvenTree API version information."""
# InvenTree API version # 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.""" """Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
INVENTREE_API_TEXT = """ 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 v305 - 2025-01-26 : https://github.com/inventree/InvenTree/pull/8950
- Bug fixes for the SupplierPart API - Bug fixes for the SupplierPart API
- Refactoring for data export via API - Refactoring for data export via API

View File

@ -181,6 +181,30 @@ class OrderFilter(rest_filters.FilterSet):
label=_('Created After'), field_name='creation_date', lookup_expr='gt' 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( target_date_before = InvenTreeDateFilter(
label=_('Target Date Before'), field_name='target_date', lookup_expr='lt' label=_('Target Date Before'), field_name='target_date', lookup_expr='lt'
) )
@ -336,6 +360,7 @@ class PurchaseOrderList(
'created_by', 'created_by',
'reference', 'reference',
'supplier__name', 'supplier__name',
'start_date',
'target_date', 'target_date',
'complete_date', 'complete_date',
'line_items', 'line_items',
@ -797,6 +822,7 @@ class SalesOrderList(
'customer__name', 'customer__name',
'customer_reference', 'customer_reference',
'status', 'status',
'start_date',
'target_date', 'target_date',
'line_items', 'line_items',
'shipment_date', 'shipment_date',
@ -1383,6 +1409,7 @@ class ReturnOrderList(
'customer_reference', 'customer_reference',
'line_items', 'line_items',
'status', 'status',
'start_date',
'target_date', 'target_date',
'complete_date', 'complete_date',
'project_code', 'project_code',

View File

@ -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'),
),
]

View File

@ -203,6 +203,8 @@ class Order(
creation_date: Automatic date of order creation creation_date: Automatic date of order creation
created_by: User who created this order (automatically captured) created_by: User who created this order (automatically captured)
issue_date: Date the order was issued 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 complete_date: Date the order was completed
responsible: User (or group) responsible for managing the order responsible: User (or group) responsible for managing the order
""" """
@ -244,6 +246,13 @@ class Order(
'contact': _('Contact does not match selected company') '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): def clean_line_item(self, line):
"""Clean a line item for this order. """Clean a line item for this order.
@ -310,6 +319,13 @@ class Order(
blank=True, verbose_name=_('Link'), help_text=_('Link to external page') 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( target_date = models.DateField(
blank=True, blank=True,
null=True, null=True,

View File

@ -186,8 +186,9 @@ class AbstractOrderSerializer(DataImportExportSerializerMixin, serializers.Seria
"""Construct a set of fields for this serializer.""" """Construct a set of fields for this serializer."""
return [ return [
'pk', 'pk',
'creation_date',
'created_by', 'created_by',
'creation_date',
'start_date',
'target_date', 'target_date',
'description', 'description',
'line_items', 'line_items',

View File

@ -170,6 +170,9 @@ export function usePurchaseOrderFields({
order_currency: { order_currency: {
icon: <IconCoins /> icon: <IconCoins />
}, },
start_date: {
icon: <IconCalendar />
},
target_date: { target_date: {
icon: <IconCalendar /> icon: <IconCalendar />
}, },

View File

@ -1,6 +1,11 @@
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
import { Flex, Table } from '@mantine/core'; 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 { useMemo } from 'react';
import RemoveRowButton from '../components/buttons/RemoveRowButton'; import RemoveRowButton from '../components/buttons/RemoveRowButton';
@ -39,7 +44,12 @@ export function useReturnOrderFields({
customer_reference: {}, customer_reference: {},
project_code: {}, project_code: {},
order_currency: {}, order_currency: {},
target_date: {}, start_date: {
icon: <IconCalendar />
},
target_date: {
icon: <IconCalendar />
},
link: {}, link: {},
contact: { contact: {
icon: <IconUser />, icon: <IconUser />,

View File

@ -1,6 +1,11 @@
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
import { Table } from '@mantine/core'; 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 { useEffect, useMemo, useState } from 'react';
import RemoveRowButton from '../components/buttons/RemoveRowButton'; import RemoveRowButton from '../components/buttons/RemoveRowButton';
@ -40,7 +45,12 @@ export function useSalesOrderFields({
customer_reference: {}, customer_reference: {},
project_code: {}, project_code: {},
order_currency: {}, order_currency: {},
target_date: {}, start_date: {
icon: <IconCalendar />
},
target_date: {
icon: <IconCalendar />
},
link: {}, link: {},
contact: { contact: {
icon: <IconUser />, icon: <IconUser />,

View File

@ -186,6 +186,7 @@ export default function BuildDetail() {
name: 'creation_date', name: 'creation_date',
label: t`Created`, label: t`Created`,
icon: 'calendar', icon: 'calendar',
copy: true,
hidden: !build.creation_date hidden: !build.creation_date
}, },
{ {
@ -193,6 +194,7 @@ export default function BuildDetail() {
name: 'start_date', name: 'start_date',
label: t`Start Date`, label: t`Start Date`,
icon: 'calendar', icon: 'calendar',
copy: true,
hidden: !build.start_date hidden: !build.start_date
}, },
{ {
@ -200,6 +202,7 @@ export default function BuildDetail() {
name: 'target_date', name: 'target_date',
label: t`Target Date`, label: t`Target Date`,
icon: 'calendar', icon: 'calendar',
copy: true,
hidden: !build.target_date hidden: !build.target_date
}, },
{ {
@ -207,6 +210,7 @@ export default function BuildDetail() {
name: 'completion_date', name: 'completion_date',
label: t`Completed`, label: t`Completed`,
icon: 'calendar', icon: 'calendar',
copy: true,
hidden: !build.completion_date hidden: !build.completion_date
}, },
{ {

View File

@ -243,6 +243,14 @@ export default function PurchaseOrderDetail() {
copy: true, copy: true,
hidden: !order.issue_date hidden: !order.issue_date
}, },
{
type: 'date',
name: 'start_date',
label: t`Start Date`,
icon: 'calendar',
copy: true,
hidden: !order.start_date
},
{ {
type: 'date', type: 'date',
name: 'target_date', name: 'target_date',

View File

@ -214,6 +214,14 @@ export default function ReturnOrderDetail() {
copy: true, copy: true,
hidden: !order.issue_date hidden: !order.issue_date
}, },
{
type: 'date',
name: 'start_date',
label: t`Start Date`,
icon: 'calendar',
copy: true,
hidden: !order.start_date
},
{ {
type: 'date', type: 'date',
name: 'target_date', name: 'target_date',

View File

@ -225,6 +225,14 @@ export default function SalesOrderDetail() {
copy: true, copy: true,
hidden: !order.issue_date hidden: !order.issue_date
}, },
{
type: 'date',
name: 'start_date',
label: t`Start Date`,
icon: 'calendar',
hidden: !order.start_date,
copy: true
},
{ {
type: 'date', type: 'date',
name: 'target_date', name: 'target_date',

View File

@ -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 { export function TargetDateColumn(props: TableColumnProps): TableColumn {
return DateColumn({ return DateColumn({
accessor: 'target_date', accessor: 'target_date',

View File

@ -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 { export function TargetDateBeforeFilter(): TableFilter {
return { return {
name: 'target_date_before', name: 'target_date_before',

View File

@ -26,6 +26,7 @@ import {
ProjectCodeColumn, ProjectCodeColumn,
ReferenceColumn, ReferenceColumn,
ResponsibleColumn, ResponsibleColumn,
StartDateColumn,
StatusColumn, StatusColumn,
TargetDateColumn TargetDateColumn
} from '../ColumnRenderers'; } from '../ColumnRenderers';
@ -43,6 +44,8 @@ import {
OverdueFilter, OverdueFilter,
ProjectCodeFilter, ProjectCodeFilter,
ResponsibleFilter, ResponsibleFilter,
StartDateAfterFilter,
StartDateBeforeFilter,
type TableFilter, type TableFilter,
TargetDateAfterFilter, TargetDateAfterFilter,
TargetDateBeforeFilter TargetDateBeforeFilter
@ -107,11 +110,7 @@ export function BuildOrderTable({
sortable: true sortable: true
}, },
CreationDateColumn({}), CreationDateColumn({}),
DateColumn({ StartDateColumn({}),
accessor: 'start_date',
title: t`Start Date`,
sortable: true
}),
TargetDateColumn({}), TargetDateColumn({}),
DateColumn({ DateColumn({
accessor: 'completion_date', accessor: 'completion_date',
@ -156,18 +155,8 @@ export function BuildOrderTable({
CreatedAfterFilter(), CreatedAfterFilter(),
TargetDateBeforeFilter(), TargetDateBeforeFilter(),
TargetDateAfterFilter(), TargetDateAfterFilter(),
{ StartDateBeforeFilter(),
name: 'start_date_before', StartDateAfterFilter(),
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`
},
{ {
name: 'has_target_date', name: 'has_target_date',
type: 'boolean', type: 'boolean',

View File

@ -26,6 +26,7 @@ import {
ProjectCodeColumn, ProjectCodeColumn,
ReferenceColumn, ReferenceColumn,
ResponsibleColumn, ResponsibleColumn,
StartDateColumn,
StatusColumn, StatusColumn,
TargetDateColumn TargetDateColumn
} from '../ColumnRenderers'; } from '../ColumnRenderers';
@ -44,6 +45,8 @@ import {
OverdueFilter, OverdueFilter,
ProjectCodeFilter, ProjectCodeFilter,
ResponsibleFilter, ResponsibleFilter,
StartDateAfterFilter,
StartDateBeforeFilter,
type TableFilter, type TableFilter,
TargetDateAfterFilter, TargetDateAfterFilter,
TargetDateBeforeFilter TargetDateBeforeFilter
@ -79,6 +82,20 @@ export function PurchaseOrderTable({
CreatedAfterFilter(), CreatedAfterFilter(),
TargetDateBeforeFilter(), TargetDateBeforeFilter(),
TargetDateAfterFilter(), 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(), CompletedBeforeFilter(),
CompletedAfterFilter(), CompletedAfterFilter(),
ProjectCodeFilter({ choices: projectCodeFilters.choices }), ProjectCodeFilter({ choices: projectCodeFilters.choices }),
@ -120,6 +137,7 @@ export function PurchaseOrderTable({
ProjectCodeColumn({}), ProjectCodeColumn({}),
CreationDateColumn({}), CreationDateColumn({}),
CreatedByColumn({}), CreatedByColumn({}),
StartDateColumn({}),
TargetDateColumn({}), TargetDateColumn({}),
CompletionDateColumn({ CompletionDateColumn({
accessor: 'complete_date' accessor: 'complete_date'

View File

@ -26,6 +26,7 @@ import {
ProjectCodeColumn, ProjectCodeColumn,
ReferenceColumn, ReferenceColumn,
ResponsibleColumn, ResponsibleColumn,
StartDateColumn,
StatusColumn, StatusColumn,
TargetDateColumn TargetDateColumn
} from '../ColumnRenderers'; } from '../ColumnRenderers';
@ -44,6 +45,8 @@ import {
OverdueFilter, OverdueFilter,
ProjectCodeFilter, ProjectCodeFilter,
ResponsibleFilter, ResponsibleFilter,
StartDateAfterFilter,
StartDateBeforeFilter,
type TableFilter, type TableFilter,
TargetDateAfterFilter, TargetDateAfterFilter,
TargetDateBeforeFilter TargetDateBeforeFilter
@ -76,6 +79,20 @@ export function ReturnOrderTable({
CreatedAfterFilter(), CreatedAfterFilter(),
TargetDateBeforeFilter(), TargetDateBeforeFilter(),
TargetDateAfterFilter(), 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(), CompletedBeforeFilter(),
CompletedAfterFilter(), CompletedAfterFilter(),
HasProjectCodeFilter(), HasProjectCodeFilter(),
@ -129,6 +146,7 @@ export function ReturnOrderTable({
ProjectCodeColumn({}), ProjectCodeColumn({}),
CreationDateColumn({}), CreationDateColumn({}),
CreatedByColumn({}), CreatedByColumn({}),
StartDateColumn({}),
TargetDateColumn({}), TargetDateColumn({}),
CompletionDateColumn({ CompletionDateColumn({
accessor: 'complete_date' accessor: 'complete_date'

View File

@ -27,6 +27,7 @@ import {
ReferenceColumn, ReferenceColumn,
ResponsibleColumn, ResponsibleColumn,
ShipmentDateColumn, ShipmentDateColumn,
StartDateColumn,
StatusColumn, StatusColumn,
TargetDateColumn TargetDateColumn
} from '../ColumnRenderers'; } from '../ColumnRenderers';
@ -45,6 +46,8 @@ import {
OverdueFilter, OverdueFilter,
ProjectCodeFilter, ProjectCodeFilter,
ResponsibleFilter, ResponsibleFilter,
StartDateAfterFilter,
StartDateBeforeFilter,
type TableFilter, type TableFilter,
TargetDateAfterFilter, TargetDateAfterFilter,
TargetDateBeforeFilter TargetDateBeforeFilter
@ -77,6 +80,20 @@ export function SalesOrderTable({
CreatedAfterFilter(), CreatedAfterFilter(),
TargetDateBeforeFilter(), TargetDateBeforeFilter(),
TargetDateAfterFilter(), 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(), CompletedBeforeFilter(),
CompletedAfterFilter(), CompletedAfterFilter(),
HasProjectCodeFilter(), HasProjectCodeFilter(),
@ -166,6 +183,7 @@ export function SalesOrderTable({
ProjectCodeColumn({}), ProjectCodeColumn({}),
CreationDateColumn({}), CreationDateColumn({}),
CreatedByColumn({}), CreatedByColumn({}),
StartDateColumn({}),
TargetDateColumn({}), TargetDateColumn({}),
ShipmentDateColumn({}), ShipmentDateColumn({}),
ResponsibleColumn({}), ResponsibleColumn({}),

View File

@ -3,11 +3,12 @@ import { baseUrl } from '../defaults.ts';
import { import {
clearTableFilters, clearTableFilters,
clickButtonIfVisible, clickButtonIfVisible,
openFilterDrawer openFilterDrawer,
setTableChoiceFilter
} from '../helpers.ts'; } from '../helpers.ts';
import { doQuickLogin } from '../login.ts'; import { doQuickLogin } from '../login.ts';
test('Purchase Orders', async ({ page }) => { test('Purchase Orders - List', async ({ page }) => {
await doQuickLogin(page); await doQuickLogin(page);
await page.getByRole('tab', { name: 'Purchasing' }).click(); 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('Pending').first().waitFor();
await page.getByText('On Hold').first().waitFor(); await page.getByText('On Hold').first().waitFor();
// Click through to a particular purchase order // Filter by 'has start date'
await page.getByRole('cell', { name: 'PO0013' }).click(); 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(); 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 }) => { test('Purchase Orders - Barcodes', async ({ page }) => {
@ -70,7 +78,7 @@ test('Purchase Orders - Barcodes', async ({ page }) => {
await page.getByRole('button', { name: 'Issue Order' }).waitFor(); await page.getByRole('button', { name: 'Issue Order' }).waitFor();
// Ensure we can scan back to this page, with the associated barcode // 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.waitForTimeout(250);
await page.getByRole('button', { name: 'Open Barcode Scanner' }).click(); await page.getByRole('button', { name: 'Open Barcode Scanner' }).click();
await page.getByPlaceholder('Enter barcode data').fill('1234567890'); await page.getByPlaceholder('Enter barcode data').fill('1234567890');

View File

@ -33,6 +33,7 @@ test('Tables - Filters', async ({ page }) => {
await setTableChoiceFilter(page, 'Responsible', 'readers'); await setTableChoiceFilter(page, 'Responsible', 'readers');
await setTableChoiceFilter(page, 'Assigned to me', 'No'); await setTableChoiceFilter(page, 'Assigned to me', 'No');
await setTableChoiceFilter(page, 'Project Code', 'PRO-ZEN'); await setTableChoiceFilter(page, 'Project Code', 'PRO-ZEN');
await setTableChoiceFilter(page, 'Has Start Date', 'Yes');
await clearTableFilters(page); await clearTableFilters(page);
}); });
@ -49,4 +50,17 @@ test('Tables - Columns', async ({ page }) => {
// De-select some items // De-select some items
await page.getByRole('menuitem', { name: 'Description' }).click(); await page.getByRole('menuitem', { name: 'Description' }).click();
await page.getByRole('menuitem', { name: 'Stocktake' }).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);
}); });