diff --git a/CHANGELOG.md b/CHANGELOG.md index 2981106a7622..a7970539d254 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - [#11383](https://github.com/inventree/InvenTree/pull/11383) adds "exists_for_model_id", "exists_for_related_model", and "exists_for_related_model_id" filters to the ParameterTemplate API endpoint. These filters allow users to check for the existence of parameters associated with specific models or related models, improving the flexibility and usability of the API. - [#10887](https://github.com/inventree/InvenTree/pull/10887) adds the ability to auto-allocate tracked items against specific build outputs. Currently, this will only allocate items where the serial number of the tracked item matches the serial number of the build output, but in future this may be extended to allow for more flexible allocation rules. - [#11372](https://github.com/inventree/InvenTree/pull/11372) adds backup metadata setter and restore metadata validator functions to ensure common footguns are harder to trigger when using the backup and restore functionality. +- [#11374](https://github.com/inventree/InvenTree/pull/11374) adds `updated_at` field on purchase, sales and return orders. ### Changed diff --git a/src/backend/InvenTree/InvenTree/api_version.py b/src/backend/InvenTree/InvenTree/api_version.py index 4d4b72abd294..a41015e398b9 100644 --- a/src/backend/InvenTree/InvenTree/api_version.py +++ b/src/backend/InvenTree/InvenTree/api_version.py @@ -1,11 +1,16 @@ """InvenTree API version information.""" # InvenTree API version -INVENTREE_API_VERSION = 459 +INVENTREE_API_VERSION = 460 """Increment this API version number whenever there is a significant change to the API that any clients need to know about.""" INVENTREE_API_TEXT = """ +v460 -> 2026-02-25 : https://github.com/inventree/InvenTree/pull/11374 + - Adds "updated_at" field to PurchaseOrder, SalesOrder and ReturnOrder API endpoints + - Adds "updated_before" and "updated_after" date filters to all three order list endpoints + - Adds "updated_at" ordering option to all three order list endpoints + v459 -> 2026-02-23 : https://github.com/inventree/InvenTree/pull/11411 - Changed PurchaseOrderLine "auto_pricing" default value from true to false diff --git a/src/backend/InvenTree/order/api.py b/src/backend/InvenTree/order/api.py index 096e8d1dfbcf..a7bdee756884 100644 --- a/src/backend/InvenTree/order/api.py +++ b/src/backend/InvenTree/order/api.py @@ -228,6 +228,14 @@ def filter_has_target_date(self, queryset, name, value): label=_('Target Date After'), field_name='target_date', lookup_expr='gt' ) + updated_before = InvenTreeDateFilter( + label=_('Updated Before'), field_name='updated_at', lookup_expr='lt' + ) + + updated_after = InvenTreeDateFilter( + label=_('Updated After'), field_name='updated_at', lookup_expr='gt' + ) + min_date = InvenTreeDateFilter(label=_('Min Date'), method='filter_min_date') def filter_min_date(self, queryset, name, value): @@ -420,6 +428,7 @@ class PurchaseOrderList( 'responsible', 'total_price', 'project_code', + 'updated_at', ] ordering = '-reference' @@ -882,6 +891,7 @@ class SalesOrderList( 'shipment_date', 'total_price', 'project_code', + 'updated_at', ] search_fields = [ @@ -1549,6 +1559,7 @@ class ReturnOrderList( 'target_date', 'complete_date', 'project_code', + 'updated_at', ] search_fields = [ diff --git a/src/backend/InvenTree/order/migrations/0115_purchaseorder_updated_at_returnorder_updated_at_and_more.py b/src/backend/InvenTree/order/migrations/0115_purchaseorder_updated_at_returnorder_updated_at_and_more.py new file mode 100644 index 000000000000..837e2a35e4e3 --- /dev/null +++ b/src/backend/InvenTree/order/migrations/0115_purchaseorder_updated_at_returnorder_updated_at_and_more.py @@ -0,0 +1,43 @@ +# Generated by Django 5.2.11 on 2026-02-19 22:31 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("order", "0114_purchaseorderextraline_project_code_and_more"), + ] + + operations = [ + migrations.AddField( + model_name="purchaseorder", + name="updated_at", + field=models.DateTimeField( + blank=True, + null=True, + help_text="Timestamp of last update", + verbose_name="Updated At", + ), + ), + migrations.AddField( + model_name="returnorder", + name="updated_at", + field=models.DateTimeField( + blank=True, + null=True, + help_text="Timestamp of last update", + verbose_name="Updated At", + ), + ), + migrations.AddField( + model_name="salesorder", + name="updated_at", + field=models.DateTimeField( + blank=True, + null=True, + help_text="Timestamp of last update", + verbose_name="Updated At", + ), + ), + ] diff --git a/src/backend/InvenTree/order/models.py b/src/backend/InvenTree/order/models.py index f34c0cb6dda0..fdba3e9323bb 100644 --- a/src/backend/InvenTree/order/models.py +++ b/src/backend/InvenTree/order/models.py @@ -9,7 +9,7 @@ from django.db import models, transaction from django.db.models import F, Q, QuerySet, Sum from django.db.models.functions import Coalesce -from django.db.models.signals import post_save +from django.db.models.signals import post_delete, post_save from django.dispatch.dispatcher import receiver from django.urls import reverse from django.utils.translation import gettext_lazy as _ @@ -331,6 +331,8 @@ def save(self, *args, **kwargs): if not self.creation_date: self.creation_date = InvenTree.helpers.current_date() + self.updated_at = InvenTree.helpers.current_time() + super().save(*args, **kwargs) def check_locked(self, db: bool = False) -> bool: @@ -498,6 +500,13 @@ def is_overdue(self): help_text=_('Date order was issued'), ) + updated_at = models.DateTimeField( + null=True, + blank=True, + verbose_name=_('Updated At'), + help_text=_('Timestamp of last update'), + ) + responsible = models.ForeignKey( UserModels.Owner, on_delete=models.SET_NULL, @@ -3072,3 +3081,43 @@ def get_api_url(): verbose_name=_('Order'), help_text=_('Return Order'), ) + + +def _touch_order_updated_at(instance): + """Bump updated_at on the parent order without triggering a full save.""" + if not InvenTree.ready.canAppAccessDatabase(allow_test=True): + return + instance.order.__class__.objects.filter(pk=instance.order_id).update( + updated_at=InvenTree.helpers.current_time() + ) + + +@receiver(post_save, sender=PurchaseOrderLineItem, dispatch_uid='po_lineitem_post_save') +@receiver( + post_delete, sender=PurchaseOrderLineItem, dispatch_uid='po_lineitem_post_delete' +) +@receiver( + post_save, sender=PurchaseOrderExtraLine, dispatch_uid='po_extraline_post_save' +) +@receiver( + post_delete, sender=PurchaseOrderExtraLine, dispatch_uid='po_extraline_post_delete' +) +@receiver(post_save, sender=SalesOrderLineItem, dispatch_uid='so_lineitem_post_save') +@receiver( + post_delete, sender=SalesOrderLineItem, dispatch_uid='so_lineitem_post_delete' +) +@receiver(post_save, sender=SalesOrderExtraLine, dispatch_uid='so_extraline_post_save') +@receiver( + post_delete, sender=SalesOrderExtraLine, dispatch_uid='so_extraline_post_delete' +) +@receiver(post_save, sender=ReturnOrderLineItem, dispatch_uid='ro_lineitem_post_save') +@receiver( + post_delete, sender=ReturnOrderLineItem, dispatch_uid='ro_lineitem_post_delete' +) +@receiver(post_save, sender=ReturnOrderExtraLine, dispatch_uid='ro_extraline_post_save') +@receiver( + post_delete, sender=ReturnOrderExtraLine, dispatch_uid='ro_extraline_post_delete' +) +def update_order_on_lineitem_change(sender, instance, **kwargs): + """Update parent order updated_at when any line item is saved or deleted.""" + _touch_order_updated_at(instance) diff --git a/src/backend/InvenTree/order/serializers.py b/src/backend/InvenTree/order/serializers.py index 5d0fd37d5bce..761332327159 100644 --- a/src/backend/InvenTree/order/serializers.py +++ b/src/backend/InvenTree/order/serializers.py @@ -373,8 +373,14 @@ class Meta: 'total_price', 'order_currency', 'destination', + 'updated_at', ]) - read_only_fields = ['issue_date', 'complete_date', 'creation_date'] + read_only_fields = [ + 'issue_date', + 'complete_date', + 'creation_date', + 'updated_at', + ] extra_kwargs = { 'supplier': {'required': True}, 'order_currency': {'required': False}, @@ -1026,8 +1032,9 @@ class Meta: 'shipments_count', 'completed_shipments_count', 'allocated_lines', + 'updated_at', ]) - read_only_fields = ['status', 'creation_date', 'shipment_date'] + read_only_fields = ['status', 'creation_date', 'shipment_date', 'updated_at'] extra_kwargs = {'order_currency': {'required': False}} def skip_create_fields(self): @@ -1918,8 +1925,9 @@ class Meta: 'customer_reference', 'order_currency', 'total_price', + 'updated_at', ]) - read_only_fields = ['creation_date'] + read_only_fields = ['creation_date', 'updated_at'] def skip_create_fields(self): """Skip these fields when instantiating a new object.""" diff --git a/src/backend/InvenTree/order/tests.py b/src/backend/InvenTree/order/tests.py index 1712f68759fd..e4e04c82eeb8 100644 --- a/src/backend/InvenTree/order/tests.py +++ b/src/backend/InvenTree/order/tests.py @@ -25,7 +25,17 @@ from stock.models import StockItem, StockLocation from users.models import Owner -from .models import PurchaseOrder, PurchaseOrderExtraLine, PurchaseOrderLineItem +from .models import ( + PurchaseOrder, + PurchaseOrderExtraLine, + PurchaseOrderLineItem, + ReturnOrder, + ReturnOrderExtraLine, + ReturnOrderLineItem, + SalesOrder, + SalesOrderExtraLine, + SalesOrderLineItem, +) class OrderTest(ExchangeRateMixin, PluginRegistryMixin, TestCase): @@ -369,7 +379,8 @@ def test_receive_pack_size(self): order=po, part=sp_1, quantity=3, - purchase_price=Money(1000, 'USD'), # "Unit price" should be $100USD + # "Unit price" should be $100USD + purchase_price=Money(1000, 'USD'), ) # 13 x 0.1 = 1.3 @@ -569,3 +580,151 @@ def test_metadata(self): p.set_metadata(k, k) self.assertEqual(len(p.metadata.keys()), 4) + + +class OrderUpdatedAtTest(TestCase): + """Tests to verify that the updated_at field is correctly maintained on all order types.""" + + def setUp(self): + """Set up objects for all three order types.""" + self.supplier = Company.objects.filter(is_supplier=True).first() + self.customer = Company.objects.filter(is_customer=True).first() + + self.po = PurchaseOrder.objects.create( + reference='PO-TEST-001', supplier=self.supplier + ) + self.so = SalesOrder.objects.create( + reference='SO-TEST-001', customer=self.customer + ) + self.ro = ReturnOrder.objects.create( + reference='RO-TEST-001', customer=self.customer + ) + + self.part = Part.objects.create(name='Test Part', description='Test Part') + self.stock_item = StockItem.objects.create(part=self.part, quantity=10) + + def _refresh(self, instance): + """Return a fresh copy of the instance from the database.""" + return instance.__class__.objects.get(pk=instance.pk) + + def test_updated_at_set_on_save(self): + """updated_at should be populated after the order is saved.""" + for instance in [self.po, self.so, self.ro]: + self.assertIsNotNone(self._refresh(instance).updated_at) + + def test_updated_at_changes_on_save(self): + """updated_at should advance when the order is saved again.""" + for instance in [self.po, self.so, self.ro]: + original = self._refresh(instance).updated_at + + instance.description = 'Updated description' + instance.save() + + refreshed = self._refresh(instance) + self.assertGreaterEqual(refreshed.updated_at, original) + + def test_updated_at_on_extra_line_add(self): + """updated_at should advance on the parent order when an extra line is added.""" + for instance, ExtraLine in [ + (self.po, PurchaseOrderExtraLine), + (self.so, SalesOrderExtraLine), + (self.ro, ReturnOrderExtraLine), + ]: + before = self._refresh(instance).updated_at + + ExtraLine.objects.create(order=instance, quantity=1) + + after = self._refresh(instance).updated_at + self.assertGreaterEqual(after, before) + + def test_updated_at_on_extra_line_update(self): + """updated_at should advance on the parent order when an extra line is updated.""" + for instance, ExtraLine in [ + (self.po, PurchaseOrderExtraLine), + (self.so, SalesOrderExtraLine), + (self.ro, ReturnOrderExtraLine), + ]: + line = ExtraLine.objects.create(order=instance, quantity=1) + + before = self._refresh(instance).updated_at + + line.quantity = 5 + line.save() + + after = self._refresh(instance).updated_at + self.assertGreaterEqual(after, before) + + def test_updated_at_on_extra_line_delete(self): + """updated_at should advance on the parent order when an extra line is deleted.""" + for instance, ExtraLine in [ + (self.po, PurchaseOrderExtraLine), + (self.so, SalesOrderExtraLine), + (self.ro, ReturnOrderExtraLine), + ]: + line = ExtraLine.objects.create(order=instance, quantity=1) + + before = self._refresh(instance).updated_at + + line.delete() + + after = self._refresh(instance).updated_at + self.assertGreaterEqual(after, before) + + def test_updated_at_on_line_item_add(self): + """updated_at should advance on the parent order when a regular line item is added.""" + before_po = self._refresh(self.po).updated_at + PurchaseOrderLineItem.objects.create(order=self.po, part=None, quantity=1) + self.assertGreaterEqual(self._refresh(self.po).updated_at, before_po) + + before_so = self._refresh(self.so).updated_at + SalesOrderLineItem.objects.create(order=self.so, part=None, quantity=1) + self.assertGreaterEqual(self._refresh(self.so).updated_at, before_so) + + before_ro = self._refresh(self.ro).updated_at + ReturnOrderLineItem.objects.create( + order=self.ro, item=self.stock_item, quantity=1 + ) + self.assertGreaterEqual(self._refresh(self.ro).updated_at, before_ro) + + def test_updated_at_on_line_item_update(self): + """updated_at should advance on the parent order when a regular line item is updated.""" + po_line = PurchaseOrderLineItem.objects.create( + order=self.po, part=None, quantity=1 + ) + so_line = SalesOrderLineItem.objects.create( + order=self.so, part=None, quantity=1 + ) + ro_line = ReturnOrderLineItem.objects.create( + order=self.ro, item=self.stock_item, quantity=1 + ) + + for instance, line in [ + (self.po, po_line), + (self.so, so_line), + (self.ro, ro_line), + ]: + before = self._refresh(instance).updated_at + line.quantity = 5 + line.save() + self.assertGreaterEqual(self._refresh(instance).updated_at, before) + + def test_updated_at_on_line_item_delete(self): + """updated_at should advance on the parent order when a regular line item is deleted.""" + po_line = PurchaseOrderLineItem.objects.create( + order=self.po, part=None, quantity=1 + ) + so_line = SalesOrderLineItem.objects.create( + order=self.so, part=None, quantity=1 + ) + ro_line = ReturnOrderLineItem.objects.create( + order=self.ro, item=self.stock_item, quantity=1 + ) + + for instance, line in [ + (self.po, po_line), + (self.so, so_line), + (self.ro, ro_line), + ]: + before = self._refresh(instance).updated_at + line.delete() + self.assertGreaterEqual(self._refresh(instance).updated_at, before) diff --git a/src/frontend/src/components/details/Details.tsx b/src/frontend/src/components/details/Details.tsx index 655af719c6f3..2e61e7377ab0 100644 --- a/src/frontend/src/components/details/Details.tsx +++ b/src/frontend/src/components/details/Details.tsx @@ -54,10 +54,16 @@ export type DetailsField = { type BadgeType = 'owner' | 'user' | 'group'; type ValueFormatterReturn = string | number | null | React.ReactNode; -type StringDetailField = { - type: 'string' | 'text' | 'date'; - unit?: boolean; -}; +type StringDetailField = + | { + type: 'string' | 'text'; + unit?: boolean; + } + | { + type: 'date'; + unit?: boolean; + showTime?: boolean; + }; type NumberDetailField = { type: 'number'; @@ -260,7 +266,13 @@ function NameBadge({ } function DateValue(props: Readonly) { - return {formatDate(props.field_value?.toString())}; + return ( + + {formatDate(props.field_value?.toString(), { + showTime: props.field_data?.showTime + })} + + ); } // Return a formatted "number" value, with optional unit diff --git a/src/frontend/src/pages/purchasing/PurchaseOrderDetail.tsx b/src/frontend/src/pages/purchasing/PurchaseOrderDetail.tsx index d14d298d8fbb..f8295065c93b 100644 --- a/src/frontend/src/pages/purchasing/PurchaseOrderDetail.tsx +++ b/src/frontend/src/pages/purchasing/PurchaseOrderDetail.tsx @@ -304,6 +304,15 @@ export default function PurchaseOrderDetail() { label: t`Completion Date`, copy: true, hidden: !order.complete_date + }, + { + type: 'date', + name: 'updated_at', + label: t`Last Updated`, + icon: 'calendar', + copy: true, + showTime: true, + hidden: !order.updated_at } ]; diff --git a/src/frontend/src/pages/sales/ReturnOrderDetail.tsx b/src/frontend/src/pages/sales/ReturnOrderDetail.tsx index fd64e7689bd0..7fccf4169096 100644 --- a/src/frontend/src/pages/sales/ReturnOrderDetail.tsx +++ b/src/frontend/src/pages/sales/ReturnOrderDetail.tsx @@ -282,6 +282,15 @@ export default function ReturnOrderDetail() { label: t`Completion Date`, copy: true, hidden: !order.complete_date + }, + { + type: 'date', + name: 'updated_at', + label: t`Last Updated`, + icon: 'calendar', + copy: true, + showTime: true, + hidden: !order.updated_at } ]; diff --git a/src/frontend/src/pages/sales/SalesOrderDetail.tsx b/src/frontend/src/pages/sales/SalesOrderDetail.tsx index acd7976457ff..9fc63179290c 100644 --- a/src/frontend/src/pages/sales/SalesOrderDetail.tsx +++ b/src/frontend/src/pages/sales/SalesOrderDetail.tsx @@ -273,6 +273,15 @@ export default function SalesOrderDetail() { label: t`Completion Date`, hidden: !order.shipment_date, copy: true + }, + { + type: 'date', + name: 'updated_at', + label: t`Last Updated`, + icon: 'calendar', + copy: true, + showTime: true, + hidden: !order.updated_at } ]; diff --git a/src/frontend/src/tables/ColumnRenderers.tsx b/src/frontend/src/tables/ColumnRenderers.tsx index 6247094b67a7..c31b1684abfb 100644 --- a/src/frontend/src/tables/ColumnRenderers.tsx +++ b/src/frontend/src/tables/ColumnRenderers.tsx @@ -725,6 +725,16 @@ export function ShipmentDateColumn(props: TableColumnProps): TableColumn { }); } +export function UpdatedAtColumn(props: TableColumnProps): TableColumn { + return DateColumn({ + accessor: 'updated_at', + title: t`Updated`, + defaultVisible: false, + extra: { showTime: true }, + ...props + }); +} + export function CurrencyColumn({ accessor, title, diff --git a/src/frontend/src/tables/Filter.tsx b/src/frontend/src/tables/Filter.tsx index f093cb94f273..b020f0ed81ac 100644 --- a/src/frontend/src/tables/Filter.tsx +++ b/src/frontend/src/tables/Filter.tsx @@ -286,6 +286,24 @@ export function CompletedAfterFilter(): TableFilter { }; } +export function UpdatedAfterFilter(): TableFilter { + return { + name: 'updated_after', + label: t`Updated After`, + description: t`Show orders updated after this date`, + type: 'date' + }; +} + +export function UpdatedBeforeFilter(): TableFilter { + return { + name: 'updated_before', + label: t`Updated Before`, + description: t`Show orders updated before this date`, + type: 'date' + }; +} + export function HasProjectCodeFilter(): TableFilter { const globalSettings = useGlobalSettingsState.getState(); const enabled = globalSettings.isSet('PROJECT_CODES_ENABLED', true); diff --git a/src/frontend/src/tables/purchasing/PurchaseOrderTable.tsx b/src/frontend/src/tables/purchasing/PurchaseOrderTable.tsx index 18977c91038e..e450ede8e77c 100644 --- a/src/frontend/src/tables/purchasing/PurchaseOrderTable.tsx +++ b/src/frontend/src/tables/purchasing/PurchaseOrderTable.tsx @@ -25,7 +25,8 @@ import { ResponsibleColumn, StartDateColumn, StatusColumn, - TargetDateColumn + TargetDateColumn, + UpdatedAtColumn } from '../ColumnRenderers'; import { AssignedToMeFilter, @@ -45,7 +46,9 @@ import { StartDateAfterFilter, StartDateBeforeFilter, TargetDateAfterFilter, - TargetDateBeforeFilter + TargetDateBeforeFilter, + UpdatedAfterFilter, + UpdatedBeforeFilter } from '../Filter'; import { InvenTreeTable } from '../InvenTreeTable'; @@ -99,6 +102,8 @@ export function PurchaseOrderTable({ }, CompletedBeforeFilter(), CompletedAfterFilter(), + UpdatedBeforeFilter(), + UpdatedAfterFilter(), ProjectCodeFilter(), HasProjectCodeFilter(), ResponsibleFilter(), @@ -142,6 +147,9 @@ export function PurchaseOrderTable({ CompletionDateColumn({ accessor: 'complete_date' }), + UpdatedAtColumn({ + defaultVisible: false + }), { accessor: 'total_price', title: t`Total Price`, diff --git a/src/frontend/src/tables/sales/ReturnOrderTable.tsx b/src/frontend/src/tables/sales/ReturnOrderTable.tsx index 498e0d216373..22ad44d9cea2 100644 --- a/src/frontend/src/tables/sales/ReturnOrderTable.tsx +++ b/src/frontend/src/tables/sales/ReturnOrderTable.tsx @@ -25,7 +25,8 @@ import { ResponsibleColumn, StartDateColumn, StatusColumn, - TargetDateColumn + TargetDateColumn, + UpdatedAtColumn } from '../ColumnRenderers'; import { AssignedToMeFilter, @@ -46,7 +47,9 @@ import { StartDateAfterFilter, StartDateBeforeFilter, TargetDateAfterFilter, - TargetDateBeforeFilter + TargetDateBeforeFilter, + UpdatedAfterFilter, + UpdatedBeforeFilter } from '../Filter'; import { InvenTreeTable } from '../InvenTreeTable'; @@ -99,6 +102,8 @@ export function ReturnOrderTable({ }, CompletedBeforeFilter(), CompletedAfterFilter(), + UpdatedBeforeFilter(), + UpdatedAfterFilter(), HasProjectCodeFilter(), ProjectCodeFilter(), ResponsibleFilter(), @@ -146,6 +151,9 @@ export function ReturnOrderTable({ CompletionDateColumn({ accessor: 'complete_date' }), + UpdatedAtColumn({ + defaultVisible: false + }), ResponsibleColumn({}), { accessor: 'total_price', diff --git a/src/frontend/src/tables/sales/SalesOrderTable.tsx b/src/frontend/src/tables/sales/SalesOrderTable.tsx index 6276a1a7d44f..3d97ed09b2c0 100644 --- a/src/frontend/src/tables/sales/SalesOrderTable.tsx +++ b/src/frontend/src/tables/sales/SalesOrderTable.tsx @@ -27,7 +27,8 @@ import { ShipmentDateColumn, StartDateColumn, StatusColumn, - TargetDateColumn + TargetDateColumn, + UpdatedAtColumn } from '../ColumnRenderers'; import { AssignedToMeFilter, @@ -48,7 +49,9 @@ import { StartDateAfterFilter, StartDateBeforeFilter, TargetDateAfterFilter, - TargetDateBeforeFilter + TargetDateBeforeFilter, + UpdatedAfterFilter, + UpdatedBeforeFilter } from '../Filter'; import { InvenTreeTable } from '../InvenTreeTable'; @@ -97,6 +100,8 @@ export function SalesOrderTable({ }, CompletedBeforeFilter(), CompletedAfterFilter(), + UpdatedBeforeFilter(), + UpdatedAfterFilter(), HasProjectCodeFilter(), ProjectCodeFilter(), ResponsibleFilter(), @@ -182,6 +187,9 @@ export function SalesOrderTable({ }), TargetDateColumn({}), ShipmentDateColumn({}), + UpdatedAtColumn({ + defaultVisible: false + }), ResponsibleColumn({}), { accessor: 'total_price',