2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-08-07 12:22:11 +00:00

Merge branch 'master' of https://github.com/inventree/InvenTree into pui-maintine-v7

This commit is contained in:
Matthias Mair
2024-04-16 07:38:18 +02:00
12 changed files with 152 additions and 68 deletions

View File

@@ -9,15 +9,13 @@ on:
pull_request_target: pull_request_target:
types: [ "labeled", "closed" ] types: [ "labeled", "closed" ]
permissions:
contents: write
jobs: jobs:
backport: backport:
name: Backport PR name: Backport PR
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions: permissions:
contents: write contents: write
pull-requests: write
if: | if: |
github.event.pull_request.merged == true github.event.pull_request.merged == true
&& contains(github.event.pull_request.labels.*.name, 'backport') && contains(github.event.pull_request.labels.*.name, 'backport')
@@ -31,7 +29,6 @@ jobs:
with: with:
github_token: ${{ secrets.GITHUB_TOKEN }} github_token: ${{ secrets.GITHUB_TOKEN }}
auto_backport_label_prefix: backport-to- auto_backport_label_prefix: backport-to-
add_original_reviewers: true
- name: Info log - name: Info log
if: ${{ success() }} if: ${{ success() }}

View File

@@ -128,7 +128,7 @@ jobs:
uses: docker/setup-buildx-action@2b51285047da1547ffb1b2203d8be4c0af6b1f20 # pin@v3.2.0 uses: docker/setup-buildx-action@2b51285047da1547ffb1b2203d8be4c0af6b1f20 # pin@v3.2.0
- name: Set up cosign - name: Set up cosign
if: github.event_name != 'pull_request' if: github.event_name != 'pull_request'
uses: sigstore/cosign-installer@e1523de7571e31dbe865fd2e80c5c7c23ae71eb4 # pin@v3.4.0 uses: sigstore/cosign-installer@59acb6260d9c0ba8f4a2f9d9b48431a222b68e20 # pin@v3.5.0
- name: Check if Dockerhub login is required - name: Check if Dockerhub login is required
id: docker_login id: docker_login
run: | run: |

View File

@@ -213,7 +213,7 @@ jobs:
echo "Version: $version" echo "Version: $version"
mkdir export/${version} mkdir export/${version}
mv schema.yml export/${version}/api.yaml mv schema.yml export/${version}/api.yaml
- uses: stefanzweifel/git-auto-commit-action@8756aa072ef5b4a080af5dc8fef36c5d586e521d # v5.0.0 - uses: stefanzweifel/git-auto-commit-action@8621497c8c39c72f3e2a999a26b4ca1b5058a842 # v5.0.1
with: with:
commit_message: "Update API schema for ${version}" commit_message: "Update API schema for ${version}"

View File

@@ -67,6 +67,6 @@ jobs:
# Upload the results to GitHub's code scanning dashboard. # Upload the results to GitHub's code scanning dashboard.
- name: "Upload to code-scanning" - name: "Upload to code-scanning"
uses: github/codeql-action/upload-sarif@4355270be187e1b672a7a1c7c7bae5afdc1ab94a # v3.24.10 uses: github/codeql-action/upload-sarif@df5a14dc28094dc936e103b37d749c6628682b60 # v3.25.0
with: with:
sarif_file: results.sarif sarif_file: results.sarif

View File

@@ -1,7 +1,7 @@
# Web process: gunicorn # Web process: gunicorn
web: env/bin/gunicorn --chdir $APP_HOME/src/backend/InvenTree -c src/backend/InvenTree/gunicorn.conf.py InvenTree.wsgi -b 0.0.0.0:$PORT web: env/bin/gunicorn --chdir $APP_HOME/src/backend/InvenTree -c src/backend/InvenTree/gunicorn.conf.py InvenTree.wsgi -b 0.0.0.0:$PORT
# Worker process: qcluster # Worker process: qcluster
worker: env/bin/python src/backendInvenTree/manage.py qcluster worker: env/bin/python src/backend/InvenTree/manage.py qcluster
# Invoke commands # Invoke commands
invoke: echo "" | echo "" && . env/bin/activate && invoke invoke: echo "" | echo "" && . env/bin/activate && invoke
# CLI: Provided for backwards compatibility # CLI: Provided for backwards compatibility

View File

@@ -1,8 +0,0 @@
{
"repoOwner": "Oliver Walters",
"repoName": "InvenTree",
"targetBranchChoices": [],
"branchLabelMapping": {
"^backport-to-(.+)$": "$1"
}
}

View File

@@ -1,5 +1,11 @@
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
import { SegmentedControl, SimpleGrid, Stack } from '@mantine/core'; import {
Group,
SegmentedControl,
SimpleGrid,
Stack,
Text
} from '@mantine/core';
import { ReactNode, useMemo, useState } from 'react'; import { ReactNode, useMemo, useState } from 'react';
import { import {
Bar, Bar,
@@ -17,6 +23,7 @@ import {
import { CHART_COLORS } from '../../../components/charts/colors'; import { CHART_COLORS } from '../../../components/charts/colors';
import { formatDecimal, formatPriceRange } from '../../../defaults/formatters'; import { formatDecimal, formatPriceRange } from '../../../defaults/formatters';
import { ApiEndpoints } from '../../../enums/ApiEndpoints'; import { ApiEndpoints } from '../../../enums/ApiEndpoints';
import { ModelType } from '../../../enums/ModelType';
import { useTable } from '../../../hooks/UseTable'; import { useTable } from '../../../hooks/UseTable';
import { apiUrl } from '../../../states/ApiState'; import { apiUrl } from '../../../states/ApiState';
import { TableColumn } from '../../../tables/Column'; import { TableColumn } from '../../../tables/Column';
@@ -110,7 +117,17 @@ export default function BomPricingPanel({
title: t`Quantity`, title: t`Quantity`,
sortable: true, sortable: true,
switchable: false, switchable: false,
render: (record: any) => formatDecimal(record.quantity) render: (record: any) => {
let quantity = formatDecimal(record.quantity);
let units = record.sub_part_detail?.units;
return (
<Group spacing="apart" grow>
<Text>{quantity}</Text>
{units && <Text size="xs">[{units}]</Text>}
</Group>
);
}
}, },
{ {
accessor: 'unit_price', accessor: 'unit_price',
@@ -178,7 +195,9 @@ export default function BomPricingPanel({
sub_part_detail: true, sub_part_detail: true,
has_pricing: true has_pricing: true
}, },
enableSelection: false enableSelection: false,
modelType: ModelType.part,
modelField: 'sub_part'
}} }}
/> />
{bomPricingData.length > 0 ? ( {bomPricingData.length > 0 ? (

View File

@@ -14,6 +14,7 @@ import {
import { CHART_COLORS } from '../../../components/charts/colors'; import { CHART_COLORS } from '../../../components/charts/colors';
import { formatCurrency } from '../../../defaults/formatters'; import { formatCurrency } from '../../../defaults/formatters';
import { ApiEndpoints } from '../../../enums/ApiEndpoints'; import { ApiEndpoints } from '../../../enums/ApiEndpoints';
import { ModelType } from '../../../enums/ModelType';
import { useTable } from '../../../hooks/UseTable'; import { useTable } from '../../../hooks/UseTable';
import { apiUrl } from '../../../states/ApiState'; import { apiUrl } from '../../../states/ApiState';
import { TableColumn } from '../../../tables/Column'; import { TableColumn } from '../../../tables/Column';
@@ -37,7 +38,7 @@ export default function VariantPricingPanel({
title: t`Variant Part`, title: t`Variant Part`,
sortable: true, sortable: true,
switchable: false, switchable: false,
render: (record: any) => PartColumn(record) render: (record: any) => PartColumn(record, true)
}, },
{ {
accessor: 'pricing_min', accessor: 'pricing_min',
@@ -90,7 +91,8 @@ export default function VariantPricingPanel({
ancestor: part?.pk, ancestor: part?.pk,
has_pricing: true has_pricing: true
}, },
enablePagination: false enablePagination: true,
modelType: ModelType.part
}} }}
/> />
{variantPricingData.length > 0 ? ( {variantPricingData.length > 0 ? (

View File

@@ -16,8 +16,13 @@ import { TableColumn } from './Column';
import { ProjectCodeHoverCard } from './TableHoverCard'; import { ProjectCodeHoverCard } from './TableHoverCard';
// Render a Part instance within a table // Render a Part instance within a table
export function PartColumn(part: any) { export function PartColumn(part: any, full_name?: boolean) {
return <Thumbnail src={part?.thumbnail ?? part.image} text={part.name} />; return (
<Thumbnail
src={part?.thumbnail ?? part.image}
text={full_name ? part.full_name : part.name}
/>
);
} }
export function BooleanColumn({ export function BooleanColumn({

View File

@@ -1,21 +1,26 @@
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
import { Text } from '@mantine/core'; import { Group, Text } from '@mantine/core';
import { import {
IconArrowRight, IconArrowRight,
IconCircleCheck, IconCircleCheck,
IconSwitch3 IconSwitch3
} from '@tabler/icons-react'; } from '@tabler/icons-react';
import { ReactNode, useCallback, useMemo } from 'react'; import { ReactNode, useCallback, useMemo, useState } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { AddItemButton } from '../../components/buttons/AddItemButton';
import { YesNoButton } from '../../components/buttons/YesNoButton'; import { YesNoButton } from '../../components/buttons/YesNoButton';
import { Thumbnail } from '../../components/images/Thumbnail'; import { Thumbnail } from '../../components/images/Thumbnail';
import { formatPriceRange } from '../../defaults/formatters'; import { formatDecimal, formatPriceRange } from '../../defaults/formatters';
import { ApiEndpoints } from '../../enums/ApiEndpoints'; import { ApiEndpoints } from '../../enums/ApiEndpoints';
import { ModelType } from '../../enums/ModelType'; import { ModelType } from '../../enums/ModelType';
import { UserRoles } from '../../enums/Roles'; import { UserRoles } from '../../enums/Roles';
import { bomItemFields } from '../../forms/BomForms'; import { bomItemFields } from '../../forms/BomForms';
import { openDeleteApiForm, openEditApiForm } from '../../functions/forms'; import {
useCreateApiFormModal,
useDeleteApiFormModal,
useEditApiFormModal
} from '../../hooks/UseForm';
import { useTable } from '../../hooks/UseTable'; import { useTable } from '../../hooks/UseTable';
import { apiUrl } from '../../states/ApiState'; import { apiUrl } from '../../states/ApiState';
import { useUserState } from '../../states/UserState'; import { useUserState } from '../../states/UserState';
@@ -98,9 +103,19 @@ export function BomTable({
{ {
accessor: 'quantity', accessor: 'quantity',
switchable: false, switchable: false,
sortable: true sortable: true,
// TODO: Custom quantity renderer render: (record: any) => {
// TODO: see bom.js for existing implementation let quantity = formatDecimal(record.quantity);
let units = record.sub_part_detail?.units;
return (
<Group position="apart" grow>
<Text>{quantity}</Text>
{record.overage && <Text size="xs">+{record.overage}</Text>}
{units && <Text size="xs">{units}</Text>}
</Group>
);
}
}, },
{ {
accessor: 'substitutes', accessor: 'substitutes',
@@ -131,12 +146,22 @@ export function BomTable({
}), }),
{ {
accessor: 'price_range', accessor: 'price_range',
title: t`Price Range`, title: t`Unit Price`,
ordering: 'pricing_max',
sortable: false, sortable: true,
switchable: true,
render: (record: any) => render: (record: any) =>
formatPriceRange(record.pricing_min, record.pricing_max) formatPriceRange(record.pricing_min, record.pricing_max)
}, },
{
accessor: 'total_price',
title: t`Total Price`,
ordering: 'pricing_max_total',
sortable: true,
switchable: true,
render: (record: any) =>
formatPriceRange(record.pricing_min_total, record.pricing_max_total)
},
{ {
accessor: 'available_stock', accessor: 'available_stock',
sortable: true, sortable: true,
@@ -277,6 +302,36 @@ export function BomTable({
]; ];
}, [partId, params]); }, [partId, params]);
const [selectedBomItem, setSelectedBomItem] = useState<number>(0);
const newBomItem = useCreateApiFormModal({
url: ApiEndpoints.bom_list,
title: t`Create BOM Item`,
fields: bomItemFields(),
initialData: {
part: partId
},
successMessage: t`BOM item created`,
onFormSuccess: table.refreshTable
});
const editBomItem = useEditApiFormModal({
url: ApiEndpoints.bom_list,
pk: selectedBomItem,
title: t`Edit BOM Item`,
fields: bomItemFields(),
successMessage: t`BOM item updated`,
onFormSuccess: table.refreshTable
});
const deleteBomItem = useDeleteApiFormModal({
url: ApiEndpoints.bom_list,
pk: selectedBomItem,
title: t`Delete BOM Item`,
successMessage: t`BOM item deleted`,
onFormSuccess: table.refreshTable
});
const rowActions = useCallback( const rowActions = useCallback(
(record: any) => { (record: any) => {
// If this BOM item is defined for a *different* parent, then it cannot be edited // If this BOM item is defined for a *different* parent, then it cannot be edited
@@ -313,14 +368,8 @@ export function BomTable({
RowEditAction({ RowEditAction({
hidden: !user.hasChangeRole(UserRoles.part), hidden: !user.hasChangeRole(UserRoles.part),
onClick: () => { onClick: () => {
openEditApiForm({ setSelectedBomItem(record.pk);
url: ApiEndpoints.bom_list, editBomItem.open();
pk: record.pk,
title: t`Edit Bom Item`,
fields: bomItemFields(),
successMessage: t`Bom item updated`,
onFormSuccess: table.refreshTable
});
} }
}) })
); );
@@ -330,14 +379,8 @@ export function BomTable({
RowDeleteAction({ RowDeleteAction({
hidden: !user.hasDeleteRole(UserRoles.part), hidden: !user.hasDeleteRole(UserRoles.part),
onClick: () => { onClick: () => {
openDeleteApiForm({ setSelectedBomItem(record.pk);
url: ApiEndpoints.bom_list, deleteBomItem.open();
pk: record.pk,
title: t`Delete Bom Item`,
successMessage: t`Bom item deleted`,
onFormSuccess: table.refreshTable,
preFormWarning: t`Are you sure you want to remove this BOM item?`
});
} }
}) })
); );
@@ -347,22 +390,38 @@ export function BomTable({
[partId, user] [partId, user]
); );
const tableActions = useMemo(() => {
return [
<AddItemButton
hidden={!user.hasAddRole(UserRoles.part)}
tooltip={t`Add BOM Item`}
onClick={() => newBomItem.open()}
/>
];
}, [user]);
return ( return (
<InvenTreeTable <>
url={apiUrl(ApiEndpoints.bom_list)} {newBomItem.modal}
tableState={table} {editBomItem.modal}
columns={tableColumns} {deleteBomItem.modal}
props={{ <InvenTreeTable
params: { url={apiUrl(ApiEndpoints.bom_list)}
...params, tableState={table}
part: partId, columns={tableColumns}
part_detail: true, props={{
sub_part_detail: true params: {
}, ...params,
tableFilters: tableFilters, part: partId,
modelType: ModelType.part, part_detail: true,
rowActions: rowActions sub_part_detail: true
}} },
/> tableActions: tableActions,
tableFilters: tableFilters,
modelType: ModelType.part,
rowActions: rowActions
}}
/>
</>
); );
} }

View File

@@ -1,7 +1,9 @@
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
import { Group, Text } from '@mantine/core';
import { useMemo } from 'react'; import { useMemo } from 'react';
import { PartHoverCard } from '../../components/images/Thumbnail'; import { PartHoverCard } from '../../components/images/Thumbnail';
import { formatDecimal } from '../../defaults/formatters';
import { ApiEndpoints } from '../../enums/ApiEndpoints'; import { ApiEndpoints } from '../../enums/ApiEndpoints';
import { ModelType } from '../../enums/ModelType'; import { ModelType } from '../../enums/ModelType';
import { useTable } from '../../hooks/UseTable'; import { useTable } from '../../hooks/UseTable';
@@ -39,8 +41,15 @@ export function UsedInTable({
{ {
accessor: 'quantity', accessor: 'quantity',
render: (record: any) => { render: (record: any) => {
// TODO: render units if appropriate let quantity = formatDecimal(record.quantity);
return record.quantity; let units = record.sub_part_detail?.units;
return (
<Group position="apart" grow>
<Text>{quantity}</Text>
{units && <Text size="xs">{units}</Text>}
</Group>
);
} }
}, },
ReferenceColumn() ReferenceColumn()

View File

@@ -158,7 +158,8 @@ function partTableColumns(): TableColumn[] {
{ {
accessor: 'price_range', accessor: 'price_range',
title: t`Price Range`, title: t`Price Range`,
sortable: false, sortable: true,
ordering: 'pricing_max',
render: (record: any) => render: (record: any) =>
formatPriceRange(record.pricing_min, record.pricing_max) formatPriceRange(record.pricing_min, record.pricing_max)
}, },