From 83a672975533fdd10efa3fce9801587f7d309643 Mon Sep 17 00:00:00 2001 From: John Luetke Date: Wed, 17 Jun 2026 00:59:37 -0700 Subject: [PATCH 1/4] Change order custom status via api (#11982) * Set custom_status_key via API Refactor `custom_status_key` to be writable via the API and validate that the proposed value is valid for the current order status * Refactor status_text serializer to consider custom status label * Update api_version.py * Additional unit testagainst N + 1 --------- Co-authored-by: Matthias Mair Co-authored-by: Oliver Walters --- .../InvenTree/InvenTree/api_version.py | 5 +- src/backend/InvenTree/order/models.py | 25 ++-- src/backend/InvenTree/order/serializers.py | 33 +++++ src/backend/InvenTree/order/test_api.py | 129 ++++++++++++++++++ 4 files changed, 181 insertions(+), 11 deletions(-) diff --git a/src/backend/InvenTree/InvenTree/api_version.py b/src/backend/InvenTree/InvenTree/api_version.py index 8c33402798..8637e9232d 100644 --- a/src/backend/InvenTree/InvenTree/api_version.py +++ b/src/backend/InvenTree/InvenTree/api_version.py @@ -1,11 +1,14 @@ """InvenTree API version information.""" # InvenTree API version -INVENTREE_API_VERSION = 507 +INVENTREE_API_VERSION = 508 """Increment this API version number whenever there is a significant change to the API that any clients need to know about.""" INVENTREE_API_TEXT = """ +v508 -> 2026-06-17 : https://github.com/inventree/InvenTree/pull/11982 + - An order's "status_custom_key" can be updated via PATCH API endpoint + v507 -> 2026-06-16 : https://github.com/inventree/InvenTree/pull/12180 - Adds "lookup_field" parameter to the DataImportSessionSerializer, which allows for more flexible lookup of related objects during data import operations diff --git a/src/backend/InvenTree/order/models.py b/src/backend/InvenTree/order/models.py index 428f9f1d29..17f9d8193f 100644 --- a/src/backend/InvenTree/order/models.py +++ b/src/backend/InvenTree/order/models.py @@ -590,6 +590,21 @@ class Order( """Return the Address associated with this order.""" return self.address or self.company.primary_address + @property + def status_text(self): + """Return the text representation of the current status. This will consider any custom status.""" + if self.get_custom_status() is not None: + from generic.states.custom import ( + get_logical_value as get_custom_state_logical_value, + ) + + custom_status = get_custom_state_logical_value( + self.get_custom_status(), model=self._meta.model_name + ) + return custom_status.label + else: + return self.status_class.label(self.get_status()) + @classmethod def get_status_class(cls): """Return the enumeration class which represents the 'status' field for this model.""" @@ -692,11 +707,6 @@ class PurchaseOrder(TotalPriceMixin, Order): help_text=_('Purchase order status'), ) - @property - def status_text(self): - """Return the text representation of the status field.""" - return PurchaseOrderStatus.text(self.status) - supplier = models.ForeignKey( Company, on_delete=models.SET_NULL, @@ -1443,11 +1453,6 @@ class SalesOrder(TotalPriceMixin, Order): help_text=_('Sales order status'), ) - @property - def status_text(self) -> str: - """Return the text representation of the status field.""" - return SalesOrderStatus.text(self.status) - customer_reference = models.CharField( max_length=64, blank=True, diff --git a/src/backend/InvenTree/order/serializers.py b/src/backend/InvenTree/order/serializers.py index 108a6d51c1..7ef36f5609 100644 --- a/src/backend/InvenTree/order/serializers.py +++ b/src/backend/InvenTree/order/serializers.py @@ -127,6 +127,14 @@ class AbstractOrderSerializer( # status field cannot be set directly status = serializers.IntegerField(read_only=True, label=_('Order Status')) + # can be set directly, but must be valid for the current order status + status_custom_key = serializers.IntegerField( + label=_('Custom Status Key'), + help_text=_('Update order status to a custom value for this logical value'), + allow_null=True, + default=None, + ) + # Reference string is *required* reference = serializers.CharField(required=True) @@ -199,6 +207,31 @@ class AbstractOrderSerializer( self.Meta.model.validate_reference_field(reference) return reference + def validate_status_custom_key(self, value): + """Validate the status_custom_key field. + + Ensure the custom status key is valid for the logical order status. + """ + if value is None: + return value + + from generic.states.custom import get_logical_value + + if not isinstance(value, int): + raise ValidationError(_('Custom status key must be an integer')) + + try: + custom_status = get_logical_value( + value, model=self.Meta.model._meta.model_name + ) + except: + raise ValidationError(_('Invalid custom status key')) + + if custom_status.logical_key is not self.instance.status: + raise ValidationError(_('Invalid custom status key for this order status')) + + return value + @staticmethod def annotate_queryset(queryset): """Add extra information to the queryset.""" diff --git a/src/backend/InvenTree/order/test_api.py b/src/backend/InvenTree/order/test_api.py index 4e4347a92e..10b944f40f 100644 --- a/src/backend/InvenTree/order/test_api.py +++ b/src/backend/InvenTree/order/test_api.py @@ -6,6 +6,7 @@ import json from datetime import date, datetime, timedelta from typing import Optional +from django.contrib.contenttypes.models import ContentType from django.core.exceptions import ValidationError from django.db import connection from django.test.utils import CaptureQueriesContext @@ -240,6 +241,77 @@ class PurchaseOrderTest(OrderTest): self.assertEqual(data['pk'], 1) self.assertEqual(data['description'], 'Ordering some screws') + def test_po_status_custom_key_options(self): + """Test that status_custom_key is exposed as writable in options.""" + self.assignRole('purchase_order.add') + + response = self.options(self.LIST_URL, expected_code=200) + post = response.data['actions']['POST'] + + self.assertIn('status_custom_key', post) + self.assertEqual(post['status_custom_key']['required'], False) + self.assertEqual(post['status_custom_key']['read_only'], False) + + def test_po_status_custom_key_patch_valid(self): + """Test patching a valid custom status key for the current PO status.""" + self.assignRole('purchase_order.change') + + po = models.PurchaseOrder.objects.get(pk=1) + self.assertEqual(po.status, PurchaseOrderStatus.PENDING.value) + + custom_status = InvenTreeCustomUserStateModel.objects.create( + key=901, + name='PO Pending Custom', + label='PO Pending Custom', + color='secondary', + logical_key=PurchaseOrderStatus.PENDING.value, + model=ContentType.objects.get_for_model(models.PurchaseOrder), + reference_status='PurchaseOrderStatus', + ) + + url = reverse('api-po-detail', kwargs={'pk': po.pk}) + response = self.patch( + url, {'status_custom_key': custom_status.key}, expected_code=200 + ) + + self.assertEqual(response.data['status'], PurchaseOrderStatus.PENDING.value) + self.assertEqual(response.data['status_custom_key'], custom_status.key) + + def test_po_status_custom_key_patch_invalid(self): + """Test patching an invalid custom status key for a PO.""" + self.assignRole('purchase_order.change') + + po = models.PurchaseOrder.objects.get(pk=1) + url = reverse('api-po-detail', kwargs={'pk': po.pk}) + + response = self.patch(url, {'status_custom_key': 999999}, expected_code=400) + + self.assertIn('status_custom_key', response.data) + + def test_po_status_custom_key_patch_wrong_logical_status(self): + """Test patching a custom key mapped to a different logical status.""" + self.assignRole('purchase_order.change') + + po = models.PurchaseOrder.objects.get(pk=1) + self.assertEqual(po.status, PurchaseOrderStatus.PENDING.value) + + custom_status = InvenTreeCustomUserStateModel.objects.create( + key=902, + name='PO Placed Custom', + label='PO Placed Custom', + color='secondary', + logical_key=PurchaseOrderStatus.PLACED.value, + model=ContentType.objects.get_for_model(models.PurchaseOrder), + reference_status='PurchaseOrderStatus', + ) + + url = reverse('api-po-detail', kwargs={'pk': po.pk}) + response = self.patch( + url, {'status_custom_key': custom_status.key}, expected_code=400 + ) + + self.assertIn('status_custom_key', response.data) + def test_po_reference(self): """Test that a reference with a too big / small reference is handled correctly.""" # get permissions @@ -1950,6 +2022,63 @@ class SalesOrderTest(OrderTest): reverse('api-so-detail', kwargs={'pk': 1}), ['customer_detail'] ) + def test_so_custom_status_query_count(self): + """Test that listing SalesOrders with custom statuses does not cause N+1 queries. + + Ensures that resolving the 'status_text' field for custom status values + is O(1) in database queries, not O(N) relative to the number of results. + """ + so_content_type = ContentType.objects.get_for_model(models.SalesOrder) + + logical_keys = [ + SalesOrderStatus.PENDING.value, + SalesOrderStatus.IN_PROGRESS.value, + SalesOrderStatus.SHIPPED.value, + SalesOrderStatus.ON_HOLD.value, + SalesOrderStatus.COMPLETE.value, + SalesOrderStatus.CANCELLED.value, + SalesOrderStatus.PENDING.value, + SalesOrderStatus.IN_PROGRESS.value, + SalesOrderStatus.SHIPPED.value, + SalesOrderStatus.ON_HOLD.value, + ] + + custom_statuses = [ + InvenTreeCustomUserStateModel.objects.create( + key=2000 + i, + name=f'SoCustomStatus{i}', + label=f'SO Custom Status Label {i}', + color='secondary', + logical_key=logical_keys[i], + model=so_content_type, + reference_status='SalesOrderStatus', + ) + for i in range(10) + ] + + customer = Company.objects.filter(is_customer=True).first() + models.SalesOrder.objects.bulk_create([ + models.SalesOrder( + customer=customer, + reference=f'SO-QTEST-{i}', + status=custom_statuses[i % 10].logical_key, + status_custom_key=custom_statuses[i % 10].key, + ) + for i in range(100) + ]) + + for limit in [1, 5, 10, 25, 50, 100]: + response = self.get( + self.LIST_URL, + data={'limit': limit}, + expected_code=200, + max_query_count=50, + ) + + for result in response.data['results']: + self.assertIn('status_text', result) + self.assertIsNotNone(result['status_text']) + class SalesOrderLineItemTest(OrderTest): """Tests for the SalesOrderLineItem API.""" From c262efb25f0ab31efd87a3618e058e99214af2ee Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Thu, 18 Jun 2026 00:07:37 +0200 Subject: [PATCH 2/4] fix(ci): disable running on dependabot and backport branches (#12189) --- .github/workflows/frontend.yaml | 2 +- .github/workflows/import_export.yaml | 2 +- .github/workflows/qc_checks.yaml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/frontend.yaml b/.github/workflows/frontend.yaml index 97ddf2a6fc..e894fc30d4 100644 --- a/.github/workflows/frontend.yaml +++ b/.github/workflows/frontend.yaml @@ -8,7 +8,7 @@ name: Frontend on: push: - branches-ignore: ["l10*"] + branches-ignore: ["l10*", "dependabot/*", "backport/*"] pull_request: branches-ignore: ["l10*"] diff --git a/.github/workflows/import_export.yaml b/.github/workflows/import_export.yaml index 1f0599f0b5..9b33ab8cab 100644 --- a/.github/workflows/import_export.yaml +++ b/.github/workflows/import_export.yaml @@ -7,7 +7,7 @@ name: Import / Export on: push: - branches-ignore: ["l10*"] + branches-ignore: ["l10*", "dependabot/*", "backport/*"] pull_request: branches-ignore: ["l10*"] diff --git a/.github/workflows/qc_checks.yaml b/.github/workflows/qc_checks.yaml index a4d2c3e1f2..2b3419ac64 100644 --- a/.github/workflows/qc_checks.yaml +++ b/.github/workflows/qc_checks.yaml @@ -4,7 +4,7 @@ name: QC on: push: - branches-ignore: ["l10*"] + branches-ignore: ["l10*", "dependabot/*", "backport/*"] pull_request: branches-ignore: ["l10*"] From f602714dc93a8f53612ecf1f03265f9f58a70d1d Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Thu, 18 Jun 2026 00:10:32 +0200 Subject: [PATCH 3/4] fix(backend): import session metadata (#12184) * add storage for historic import metadata for reporting and display purposes fixes breakage fromhttps://github.com/inventree/InvenTree/pull/12169 * add api bump * re-enable test * fix migration * ensure session is not overwritten * fix statusrender without custom key --- .../InvenTree/InvenTree/api_version.py | 5 +++- ...on_completed_row_count_history_and_more.py | 23 +++++++++++++++++ src/backend/InvenTree/importer/models.py | 14 +++++++++++ src/backend/InvenTree/importer/serializers.py | 4 +++ src/frontend/src/pages/build/BuildDetail.tsx | 2 +- .../pages/purchasing/PurchaseOrderDetail.tsx | 2 +- .../src/pages/sales/ReturnOrderDetail.tsx | 2 +- .../src/pages/sales/SalesOrderDetail.tsx | 2 +- .../src/pages/stock/TransferOrderDetail.tsx | 2 +- .../tables/settings/ImportSessionTable.tsx | 25 +++++++++++++------ src/frontend/tests/pui_importing.spec.ts | 2 +- 11 files changed, 69 insertions(+), 14 deletions(-) create mode 100644 src/backend/InvenTree/importer/migrations/0007_dataimportsession_completed_row_count_history_and_more.py diff --git a/src/backend/InvenTree/InvenTree/api_version.py b/src/backend/InvenTree/InvenTree/api_version.py index 8637e9232d..2b522c0987 100644 --- a/src/backend/InvenTree/InvenTree/api_version.py +++ b/src/backend/InvenTree/InvenTree/api_version.py @@ -1,11 +1,14 @@ """InvenTree API version information.""" # InvenTree API version -INVENTREE_API_VERSION = 508 +INVENTREE_API_VERSION = 509 """Increment this API version number whenever there is a significant change to the API that any clients need to know about.""" INVENTREE_API_TEXT = """ +v509 -> 2026-06-17 : https://github.com/inventree/InvenTree/pull/12184 + - Adds "completed_row_count_history" and "row_count_history" fields to the DataImportSession model, which store the historic count of completed rows and total rows for a data import session. + v508 -> 2026-06-17 : https://github.com/inventree/InvenTree/pull/11982 - An order's "status_custom_key" can be updated via PATCH API endpoint diff --git a/src/backend/InvenTree/importer/migrations/0007_dataimportsession_completed_row_count_history_and_more.py b/src/backend/InvenTree/importer/migrations/0007_dataimportsession_completed_row_count_history_and_more.py new file mode 100644 index 0000000000..c70be9e5b3 --- /dev/null +++ b/src/backend/InvenTree/importer/migrations/0007_dataimportsession_completed_row_count_history_and_more.py @@ -0,0 +1,23 @@ +# Generated by Django 5.2.15 on 2026-06-17 05:57 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('importer', '0006_dataimportcolumnmap_lookup_field'), + ] + + operations = [ + migrations.AddField( + model_name='dataimportsession', + name='completed_row_count_history', + field=models.PositiveIntegerField(blank=True, null=True, verbose_name='Completed Row Count History'), + ), + migrations.AddField( + model_name='dataimportsession', + name='row_count_history', + field=models.PositiveIntegerField(blank=True, null=True, verbose_name='Row Count History'), + ), + ] diff --git a/src/backend/InvenTree/importer/models.py b/src/backend/InvenTree/importer/models.py index 042e8df43b..00f361be02 100644 --- a/src/backend/InvenTree/importer/models.py +++ b/src/backend/InvenTree/importer/models.py @@ -385,7 +385,13 @@ class DataImportSession(models.Model): if self.status != DataImportStatusCode.COMPLETE.value: self.status = DataImportStatusCode.COMPLETE.value + + # persist historic count values for reporting purposes + self.completed_row_count_history = self.completed_row_count + self.row_count_history = self.row_count + self.save() + # Clear staging data now that all rows have been imported self.rows.all().delete() self.column_mappings.all().delete() @@ -402,6 +408,14 @@ class DataImportSession(models.Model): """Return the number of completed rows for this session.""" return self.rows.filter(complete=True).count() + # Historic values for reporting purposes + completed_row_count_history = models.PositiveIntegerField( + blank=True, null=True, verbose_name=_('Completed Row Count History') + ) + row_count_history = models.PositiveIntegerField( + blank=True, null=True, verbose_name=_('Row Count History') + ) + def available_fields(self): """Returns information on the available fields. diff --git a/src/backend/InvenTree/importer/serializers.py b/src/backend/InvenTree/importer/serializers.py index 2da60e0e32..46bc8562fc 100644 --- a/src/backend/InvenTree/importer/serializers.py +++ b/src/backend/InvenTree/importer/serializers.py @@ -62,6 +62,8 @@ class DataImportSessionSerializer(InvenTreeModelSerializer): 'field_filters', 'row_count', 'completed_row_count', + 'completed_row_count_history', + 'row_count_history', ] read_only_fields = ['pk', 'user', 'status', 'columns'] @@ -220,6 +222,8 @@ class DataImportAcceptRowSerializer(serializers.Serializer): row.validate(commit=True, request=request) if session := self.context.get('session', None): + # ensure current state is available + session.refresh_from_db() session.check_complete() return rows diff --git a/src/frontend/src/pages/build/BuildDetail.tsx b/src/frontend/src/pages/build/BuildDetail.tsx index f249332eb9..6b8d8e3459 100644 --- a/src/frontend/src/pages/build/BuildDetail.tsx +++ b/src/frontend/src/pages/build/BuildDetail.tsx @@ -783,7 +783,7 @@ export default function BuildDetail() { ? [] : [ , diff --git a/src/frontend/src/pages/purchasing/PurchaseOrderDetail.tsx b/src/frontend/src/pages/purchasing/PurchaseOrderDetail.tsx index 99099bad1a..43f5e84301 100644 --- a/src/frontend/src/pages/purchasing/PurchaseOrderDetail.tsx +++ b/src/frontend/src/pages/purchasing/PurchaseOrderDetail.tsx @@ -543,7 +543,7 @@ export default function PurchaseOrderDetail() { ? [] : [ diff --git a/src/frontend/src/pages/sales/ReturnOrderDetail.tsx b/src/frontend/src/pages/sales/ReturnOrderDetail.tsx index 67c1eba98f..b094826d7c 100644 --- a/src/frontend/src/pages/sales/ReturnOrderDetail.tsx +++ b/src/frontend/src/pages/sales/ReturnOrderDetail.tsx @@ -390,7 +390,7 @@ export default function ReturnOrderDetail() { ? [] : [ diff --git a/src/frontend/src/pages/sales/SalesOrderDetail.tsx b/src/frontend/src/pages/sales/SalesOrderDetail.tsx index ce5c4ac094..959ea726d1 100644 --- a/src/frontend/src/pages/sales/SalesOrderDetail.tsx +++ b/src/frontend/src/pages/sales/SalesOrderDetail.tsx @@ -605,7 +605,7 @@ export default function SalesOrderDetail() { ? [] : [ diff --git a/src/frontend/src/tables/settings/ImportSessionTable.tsx b/src/frontend/src/tables/settings/ImportSessionTable.tsx index 39df628147..6dfd8101a4 100644 --- a/src/frontend/src/tables/settings/ImportSessionTable.tsx +++ b/src/frontend/src/tables/settings/ImportSessionTable.tsx @@ -18,6 +18,7 @@ import { useCreateApiFormModal, useDeleteApiFormModal } from '../../hooks/UseForm'; +import useStatusCodes from '../../hooks/UseStatusCodes'; import { useImporterState } from '../../states/ImporterState'; import { DateColumn, StatusColumn } from '../ColumnRenderers'; import { StatusFilterOptions, UserFilter } from '../Filter'; @@ -26,6 +27,9 @@ import { InvenTreeTable } from '../InvenTreeTable'; export default function ImportSessionTable() { const table = useTable('importsession'); const openImporter = useImporterState((state) => state.openImporter); + const importSessionStatus = useStatusCodes({ + modelType: ModelType.importsession + }); const [selectedSession, setSelectedSession] = useState( undefined @@ -82,13 +86,20 @@ export default function ImportSessionTable() { sortable: false, accessor: 'row_count', title: t`Imported Rows`, - render: (record: any) => ( - - ) + render: (record: any) => + record.status == importSessionStatus.COMPLETE ? ( + + ) : ( + + ) } ]; }, []); diff --git a/src/frontend/tests/pui_importing.spec.ts b/src/frontend/tests/pui_importing.spec.ts index 026f7117c5..b7d5f64061 100644 --- a/src/frontend/tests/pui_importing.spec.ts +++ b/src/frontend/tests/pui_importing.spec.ts @@ -60,7 +60,7 @@ test('Importing - Admin Center', async ({ browser }) => { await page.getByRole('button', { name: 'Close' }).click(); // Confirmation of full import success - // await page.getByRole('cell', { name: '3 / 3' }).first().waitFor(); + await page.getByRole('cell', { name: '3 / 3' }).first().waitFor(); // Manually delete records await page.getByRole('checkbox', { name: 'Select all records' }).check(); From 9e125be07e94e0997ce03fd905673c44aad81c00 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Thu, 18 Jun 2026 01:40:51 +0200 Subject: [PATCH 4/4] fix(ci): improve release pipe stability (#12191) * update attestations step * build stable channel release seperatly --- .github/workflows/release.yaml | 50 +++++++++++++++++++++++++--------- 1 file changed, 37 insertions(+), 13 deletions(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 3dc2176956..1795c7ce0e 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -42,6 +42,8 @@ jobs: id-token: write contents: write attestations: write + artifact-metadata: write + steps: - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # pin@v6.0.3 with: @@ -73,7 +75,7 @@ jobs: zip -r ../frontend-build.zip * .vite - name: Attest Build Provenance id: attest - uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # pin@v1 + uses: actions/attest@59d89421af93a897026c735860bf21b6eb4f7b26 # pin@v4 with: subject-path: "${{ github.workspace }}/src/backend/InvenTree/web/static/frontend-build.zip" @@ -87,11 +89,15 @@ jobs: with: name: frontend-build path: src/backend/InvenTree/web/static/frontend-build.zip + - name: Rename Attestation Bundle + run: | + mv ${BUNDLE_PATH} src/backend/InvenTree/web/static/frontend-build.intoto.jsonl + env: + BUNDLE_PATH: ${{ steps.attest.outputs.bundle-path}} - name: Upload Attestation - run: gh release upload ${REF} ${BUNDLE_PATH}#frontend-build.intoto.jsonl + run: gh release upload ${REF} src/backend/InvenTree/web/static/frontend-build.intoto.jsonl#frontend-build.intoto.jsonl env: REF: ${{ github.ref_name }} - BUNDLE_PATH: ${{ steps.attest.outputs.bundle-path}} GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} docs: @@ -148,12 +154,12 @@ jobs: - ubuntu:22.04 - ubuntu:24.04 - debian:12 + steps: - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # pin@v6.0.3 with: fetch-depth: 0 persist-credentials: false - - name: Get frontend artifact uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # pin@v8.0.1 with: @@ -209,7 +215,7 @@ jobs: echo "calculate release channel" pip install --require-hashes -r contrib/dev_reqs/requirements.txt python3 .github/scripts/version_check.py - - name: Package + - name: Package - current release channel uses: pkgr/action/package@c5666febcd31750da6428042193fc5b2fb765435 # pin@main id: package with: @@ -235,14 +241,6 @@ jobs: repository: inventree/InvenTree channel: ${{ env.pkg_channel }} file: ${{ steps.package.outputs.package_path }} - - name: Publish to go.packager.io - stable release channel - uses: pkgr/action/publish@3bce081ae512c5020856e237d37b3f5479d4aa71 # pin@main - with: - target: ${{ matrix.target }} - token: ${{ secrets.PACKAGER_RELEASE_TOKEN }} - repository: inventree/InvenTree - channel: stable - file: ${{ steps.package.outputs.package_path }} - name: Publish to artifact run: gh release upload ${REF} ${PACKAGE_PATH}#${PACKAGE_NAME} env: @@ -250,3 +248,29 @@ jobs: PACKAGE_PATH: ${{ steps.package.outputs.package_path }} PACKAGE_NAME: ${{ matrix.target }}-${{ steps.setup.outputs.version }}.tar.gz GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Package - stable release channel + uses: pkgr/action/package@c5666febcd31750da6428042193fc5b2fb765435 # pin@main + id: package-stable + with: + target: ${{ matrix.target }} + version: ${{ steps.setup.outputs.version }} + debug: true + cache_prefix: ${{ github.ref_name }} + env: | + INVENTREE_DB_ENGINE=sqlite3 + INVENTREE_DB_NAME=database.sqlite3 + INVENTREE_PLUGINS_ENABLED=true + INVENTREE_MEDIA_ROOT=/opt/inventree/media + INVENTREE_STATIC_ROOT=/opt/inventree/static + INVENTREE_BACKUP_DIR=/opt/inventree/backup + INVENTREE_PLUGIN_FILE=/opt/inventree/plugins.txt + INVENTREE_CONFIG_FILE=/opt/inventree/config.yaml + APP_REPO=inventree/InvenTree + - name: Publish to go.packager.io - stable release channel + uses: pkgr/action/publish@3bce081ae512c5020856e237d37b3f5479d4aa71 # pin@main + with: + target: ${{ matrix.target }} + token: ${{ secrets.PACKAGER_RELEASE_TOKEN }} + repository: inventree/InvenTree + channel: stable + file: ${{ steps.package-stable.outputs.package_path }}