Disable BOM requirement (#6719)

* Add new setting STOCK_ENFORCE_BOM_INSTALLATION

- Defaults to True (legacy)

* Add logic to bypass BOM check

* Update CUI to reflect new logic

* Render InstalledItemsTable in PUI
This commit is contained in:
Oliver 2024-03-15 17:53:58 +11:00 committed by GitHub
parent 160d014e44
commit a00d5ab4b5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 111 additions and 6 deletions

View File

@ -1750,6 +1750,14 @@ class InvenTreeSetting(BaseInvenTreeSetting):
'default': False,
'validator': bool,
},
'STOCK_ENFORCE_BOM_INSTALLATION': {
'name': _('Check BOM when installing items'),
'description': _(
'Installed stock items must exist in the BOM for the parent part'
),
'default': True,
'validator': bool,
},
'BUILDORDER_REFERENCE_PATTERN': {
'name': _('Build Order Reference Pattern'),
'description': _(

View File

@ -584,9 +584,14 @@ class InstallStockItemSerializer(serializers.Serializer):
parent_item = self.context['item']
parent_part = parent_item.part
# Check if the selected part is in the Bill of Materials of the parent item
if not parent_part.check_if_part_in_bom(stock_item.part):
raise ValidationError(_('Selected part is not in the Bill of Materials'))
if common.models.InvenTreeSetting.get_setting(
'STOCK_ENFORCE_BOM_INSTALLATION', backup_value=True, cache=False
):
# Check if the selected part is in the Bill of Materials of the parent item
if not parent_part.check_if_part_in_bom(stock_item.part):
raise ValidationError(
_('Selected part is not in the Bill of Materials')
)
return stock_item

View File

@ -183,7 +183,10 @@
$('#stock-item-install').click(function() {
{% settings_value "STOCK_ENFORCE_BOM_INSTALLATION" as enforce_bom %}
installStockItem({{ item.pk }}, {{ item.part.pk }}, {
enforce_bom: {% js_bool enforce_bom %},
onSuccess: function(response) {
$("#installed-table").bootstrapTable('refresh');
}

View File

@ -22,6 +22,7 @@
{% include "InvenTree/settings/setting.html" with key="STOCK_OWNERSHIP_CONTROL" icon="fa-users" %}
{% include "InvenTree/settings/setting.html" with key="STOCK_LOCATION_DEFAULT_ICON" icon="fa-icons" %}
{% include "InvenTree/settings/setting.html" with key="STOCK_SHOW_INSTALLED_ITEMS" icon="fa-sitemap" %}
{% include "InvenTree/settings/setting.html" with key="STOCK_ENFORCE_BOM_INSTALLATION" icon="fa-check-circle" %}
</tbody>
</table>

View File

@ -3204,7 +3204,7 @@ function installStockItem(stock_item_id, part_id, options={}) {
auto_fill: true,
filters: {
trackable: true,
in_bom_for: part_id,
in_bom_for: options.enforce_bom ? part_id : undefined,
}
},
stock_item: {

View File

@ -92,6 +92,7 @@ export enum ApiEndpoints {
stock_merge = 'stock/merge/',
stock_assign = 'stock/assign/',
stock_status = 'stock/status/',
stock_install = 'stock/:id/install',
// Order API endpoints
purchase_order_list = 'order/po/',

View File

@ -212,7 +212,8 @@ export default function SystemSettings() {
'STOCK_ALLOW_EXPIRED_BUILD',
'STOCK_OWNERSHIP_CONTROL',
'STOCK_LOCATION_DEFAULT_ICON',
'STOCK_SHOW_INSTALLED_ITEMS'
'STOCK_SHOW_INSTALLED_ITEMS',
'STOCK_ENFORCE_BOM_INSTALLATION'
]}
/>
)

View File

@ -55,6 +55,7 @@ import { useInstance } from '../../hooks/UseInstance';
import { apiUrl } from '../../states/ApiState';
import { useUserState } from '../../states/UserState';
import { AttachmentTable } from '../../tables/general/AttachmentTable';
import InstalledItemsTable from '../../tables/stock/InstalledItemsTable';
import { StockItemTable } from '../../tables/stock/StockItemTable';
import StockItemTestResultTable from '../../tables/stock/StockItemTestResultTable';
@ -164,6 +165,14 @@ export default function StockDetail() {
type: 'link',
name: 'belongs_to',
label: t`Installed In`,
model_formatter: (model: any) => {
let text = model?.part_detail?.full_name ?? model?.name;
if (model.serial && model.quantity == 1) {
text += `# ${model.serial}`;
}
return text;
},
model: ModelType.stockitem,
hidden: !stockitem.belongs_to
},
@ -259,7 +268,8 @@ export default function StockDetail() {
name: 'installed_items',
label: t`Installed Items`,
icon: <IconBoxPadding />,
hidden: !stockitem?.part_detail?.assembly
hidden: !stockitem?.part_detail?.assembly,
content: <InstalledItemsTable parentId={stockitem.pk} />
},
{
name: 'child_items',

View File

@ -0,0 +1,76 @@
import { useMemo } from 'react';
import { useNavigate } from 'react-router-dom';
import { ApiEndpoints } from '../../enums/ApiEndpoints';
import { ModelType } from '../../enums/ModelType';
import { getDetailUrl } from '../../functions/urls';
import { useTable } from '../../hooks/UseTable';
import { apiUrl } from '../../states/ApiState';
import { useUserState } from '../../states/UserState';
import { TableColumn } from '../Column';
import { PartColumn, StatusColumn } from '../ColumnRenderers';
import { InvenTreeTable } from '../InvenTreeTable';
export default function InstalledItemsTable({
parentId
}: {
parentId?: number | string;
}) {
const table = useTable('stock_item_install');
const user = useUserState();
const navigate = useNavigate();
const tableColumns: TableColumn[] = useMemo(() => {
return [
{
accessor: 'part',
switchable: false,
render: (record: any) => PartColumn(record?.part_detail)
},
{
accessor: 'quantity',
switchable: false,
render: (record: any) => {
let text = record.quantity;
if (record.serial && record.quantity == 1) {
text = `# ${record.serial}`;
}
return text;
}
},
{
accessor: 'batch',
switchable: false
},
StatusColumn(ModelType.stockitem)
];
}, []);
const tableActions = useMemo(() => {
return [];
}, [user]);
return (
<>
<InvenTreeTable
url={apiUrl(ApiEndpoints.stock_item_list)}
tableState={table}
columns={tableColumns}
props={{
tableActions: tableActions,
onRowClick: (record: any) => {
if (record.pk) {
navigate(getDetailUrl(ModelType.stockitem, record.pk));
}
},
params: {
belongs_to: parentId,
part_detail: true
}
}}
/>
</>
);
}