[UI] Reimplement calendar views (#8933)

* Add deps for @fullcalendar

* Basic calendar setup

* Add locale support

* Add custom <SegmentedIconControl> component

* optional 'control' for panel

* Scaffolding out build index page

* Fetch build orders based on calendar dates

* Display builds

* Click to navigate to build

* Working on custom header

* Implement button functionality

* Display current month name

* Implement datepicker dropdown

* Adjust layout

* Custom rendering

* Include part detail

* Implement hook to track calendar state

* Clearing search causes immediate update

* Offload core functionality into hook

- Adds search input

* Spread props correctly

* Cleanup

* Allow build orders to be edited directly from calendar view

* Cleanup

* Fix for InvenTreeCalendarProps

* Improve date range filtering for Build model

* Simplify BuildFilter

* Refactoring

* PurchaseOrder calendar implementation

* Allow edit of purchase orders

* Refactor and implement for all order types

- Add missing "issue_date" field for SalesOrder
- Refactor API for other order types
- Fix rendering issues

* Bump API version

* Add playwright tests for calendar (simple)

* Fix migrations

* Simplify notifications

* Refactor filter components

- Make them more generic

* Enable custom filters for calendars

* Custom filters per table

* Display status label

* Refactoring

* Update playwright tests

* Update build order docs

* Bug fix for order date editing

* Update purchase order docs

* Return Order docs

* Update date formatting
This commit is contained in:
Oliver
2025-03-16 18:01:51 +11:00
committed by GitHub
parent 191c0b1007
commit 3afafe594b
54 changed files with 1287 additions and 342 deletions
+7 -103
View File
@@ -1,6 +1,5 @@
"""Order model definitions."""
from datetime import datetime
from decimal import Decimal
from django.contrib.auth.models import User
@@ -389,6 +388,13 @@ class Order(
verbose_name=_('Created By'),
)
issue_date = models.DateField(
blank=True,
null=True,
verbose_name=_('Issue Date'),
help_text=_('Date order was issued'),
)
responsible = models.ForeignKey(
UserModels.Owner,
on_delete=models.SET_NULL,
@@ -482,50 +488,6 @@ class PurchaseOrder(TotalPriceMixin, Order):
"""Return the associated barcode model type code for this model."""
return 'PO'
@staticmethod
def filterByDate(queryset, min_date, max_date):
"""Filter by 'minimum and maximum date range'.
- Specified as min_date, max_date
- Both must be specified for filter to be applied
- Determine which "interesting" orders exist between these dates
To be "interesting":
- A "received" order where the received date lies within the date range
- A "pending" order where the target date lies within the date range
- TODO: An "overdue" order where the target date is in the past
"""
date_fmt = '%Y-%m-%d' # ISO format date string
# Ensure that both dates are valid
try:
min_date = datetime.strptime(str(min_date), date_fmt).date()
max_date = datetime.strptime(str(max_date), date_fmt).date()
except (ValueError, TypeError):
# Date processing error, return queryset unchanged
return queryset
# Construct a queryset for "received" orders within the range
received = (
Q(status=PurchaseOrderStatus.COMPLETE.value)
& Q(complete_date__gte=min_date)
& Q(complete_date__lte=max_date)
)
# Construct a queryset for "pending" orders within the range
pending = (
Q(status__in=PurchaseOrderStatusGroups.OPEN)
& ~Q(target_date=None)
& Q(target_date__gte=min_date)
& Q(target_date__lte=max_date)
)
# TODO - Construct a queryset for "overdue" orders within the range
queryset = queryset.filter(received | pending)
return queryset
def __str__(self):
"""Render a string representation of this PurchaseOrder."""
return f'{self.reference} - {self.supplier.name if self.supplier else _("deleted")}'
@@ -584,13 +546,6 @@ class PurchaseOrder(TotalPriceMixin, Order):
verbose_name=_('received by'),
)
issue_date = models.DateField(
blank=True,
null=True,
verbose_name=_('Issue Date'),
help_text=_('Date order was issued'),
)
complete_date = models.DateField(
blank=True,
null=True,
@@ -1054,50 +1009,6 @@ class SalesOrder(TotalPriceMixin, Order):
"""Return the associated barcode model type code for this model."""
return 'SO'
@staticmethod
def filterByDate(queryset, min_date, max_date):
"""Filter by "minimum and maximum date range".
- Specified as min_date, max_date
- Both must be specified for filter to be applied
- Determine which "interesting" orders exist between these dates
To be "interesting":
- A "completed" order where the completion date lies within the date range
- A "pending" order where the target date lies within the date range
- TODO: An "overdue" order where the target date is in the past
"""
date_fmt = '%Y-%m-%d' # ISO format date string
# Ensure that both dates are valid
try:
min_date = datetime.strptime(str(min_date), date_fmt).date()
max_date = datetime.strptime(str(max_date), date_fmt).date()
except (ValueError, TypeError):
# Date processing error, return queryset unchanged
return queryset
# Construct a queryset for "completed" orders within the range
completed = (
Q(status__in=SalesOrderStatusGroups.COMPLETE)
& Q(shipment_date__gte=min_date)
& Q(shipment_date__lte=max_date)
)
# Construct a queryset for "pending" orders within the range
pending = (
Q(status__in=SalesOrderStatusGroups.OPEN)
& ~Q(target_date=None)
& Q(target_date__gte=min_date)
& Q(target_date__lte=max_date)
)
# TODO: Construct a queryset for "overdue" orders within the range
queryset = queryset.filter(completed | pending)
return queryset
def __str__(self):
"""Render a string representation of this SalesOrder."""
return f'{self.reference} - {self.customer.name if self.customer else _("deleted")}'
@@ -2363,13 +2274,6 @@ class ReturnOrder(TotalPriceMixin, Order):
help_text=_('Customer order reference code'),
)
issue_date = models.DateField(
blank=True,
null=True,
verbose_name=_('Issue Date'),
help_text=_('Date order was issued'),
)
complete_date = models.DateField(
blank=True,
null=True,