mirror of
https://github.com/inventree/InvenTree.git
synced 2025-04-28 19:46:46 +00:00
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:
parent
160d014e44
commit
a00d5ab4b5
@ -1750,6 +1750,14 @@ class InvenTreeSetting(BaseInvenTreeSetting):
|
|||||||
'default': False,
|
'default': False,
|
||||||
'validator': bool,
|
'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': {
|
'BUILDORDER_REFERENCE_PATTERN': {
|
||||||
'name': _('Build Order Reference Pattern'),
|
'name': _('Build Order Reference Pattern'),
|
||||||
'description': _(
|
'description': _(
|
||||||
|
@ -584,9 +584,14 @@ class InstallStockItemSerializer(serializers.Serializer):
|
|||||||
parent_item = self.context['item']
|
parent_item = self.context['item']
|
||||||
parent_part = parent_item.part
|
parent_part = parent_item.part
|
||||||
|
|
||||||
# Check if the selected part is in the Bill of Materials of the parent item
|
if common.models.InvenTreeSetting.get_setting(
|
||||||
if not parent_part.check_if_part_in_bom(stock_item.part):
|
'STOCK_ENFORCE_BOM_INSTALLATION', backup_value=True, cache=False
|
||||||
raise ValidationError(_('Selected part is not in the Bill of Materials'))
|
):
|
||||||
|
# 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
|
return stock_item
|
||||||
|
|
||||||
|
@ -183,7 +183,10 @@
|
|||||||
|
|
||||||
$('#stock-item-install').click(function() {
|
$('#stock-item-install').click(function() {
|
||||||
|
|
||||||
|
{% settings_value "STOCK_ENFORCE_BOM_INSTALLATION" as enforce_bom %}
|
||||||
|
|
||||||
installStockItem({{ item.pk }}, {{ item.part.pk }}, {
|
installStockItem({{ item.pk }}, {{ item.part.pk }}, {
|
||||||
|
enforce_bom: {% js_bool enforce_bom %},
|
||||||
onSuccess: function(response) {
|
onSuccess: function(response) {
|
||||||
$("#installed-table").bootstrapTable('refresh');
|
$("#installed-table").bootstrapTable('refresh');
|
||||||
}
|
}
|
||||||
|
@ -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_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_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_SHOW_INSTALLED_ITEMS" icon="fa-sitemap" %}
|
||||||
|
{% include "InvenTree/settings/setting.html" with key="STOCK_ENFORCE_BOM_INSTALLATION" icon="fa-check-circle" %}
|
||||||
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
@ -3204,7 +3204,7 @@ function installStockItem(stock_item_id, part_id, options={}) {
|
|||||||
auto_fill: true,
|
auto_fill: true,
|
||||||
filters: {
|
filters: {
|
||||||
trackable: true,
|
trackable: true,
|
||||||
in_bom_for: part_id,
|
in_bom_for: options.enforce_bom ? part_id : undefined,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
stock_item: {
|
stock_item: {
|
||||||
|
@ -92,6 +92,7 @@ export enum ApiEndpoints {
|
|||||||
stock_merge = 'stock/merge/',
|
stock_merge = 'stock/merge/',
|
||||||
stock_assign = 'stock/assign/',
|
stock_assign = 'stock/assign/',
|
||||||
stock_status = 'stock/status/',
|
stock_status = 'stock/status/',
|
||||||
|
stock_install = 'stock/:id/install',
|
||||||
|
|
||||||
// Order API endpoints
|
// Order API endpoints
|
||||||
purchase_order_list = 'order/po/',
|
purchase_order_list = 'order/po/',
|
||||||
|
@ -212,7 +212,8 @@ export default function SystemSettings() {
|
|||||||
'STOCK_ALLOW_EXPIRED_BUILD',
|
'STOCK_ALLOW_EXPIRED_BUILD',
|
||||||
'STOCK_OWNERSHIP_CONTROL',
|
'STOCK_OWNERSHIP_CONTROL',
|
||||||
'STOCK_LOCATION_DEFAULT_ICON',
|
'STOCK_LOCATION_DEFAULT_ICON',
|
||||||
'STOCK_SHOW_INSTALLED_ITEMS'
|
'STOCK_SHOW_INSTALLED_ITEMS',
|
||||||
|
'STOCK_ENFORCE_BOM_INSTALLATION'
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
@ -55,6 +55,7 @@ import { useInstance } from '../../hooks/UseInstance';
|
|||||||
import { apiUrl } from '../../states/ApiState';
|
import { apiUrl } from '../../states/ApiState';
|
||||||
import { useUserState } from '../../states/UserState';
|
import { useUserState } from '../../states/UserState';
|
||||||
import { AttachmentTable } from '../../tables/general/AttachmentTable';
|
import { AttachmentTable } from '../../tables/general/AttachmentTable';
|
||||||
|
import InstalledItemsTable from '../../tables/stock/InstalledItemsTable';
|
||||||
import { StockItemTable } from '../../tables/stock/StockItemTable';
|
import { StockItemTable } from '../../tables/stock/StockItemTable';
|
||||||
import StockItemTestResultTable from '../../tables/stock/StockItemTestResultTable';
|
import StockItemTestResultTable from '../../tables/stock/StockItemTestResultTable';
|
||||||
|
|
||||||
@ -164,6 +165,14 @@ export default function StockDetail() {
|
|||||||
type: 'link',
|
type: 'link',
|
||||||
name: 'belongs_to',
|
name: 'belongs_to',
|
||||||
label: t`Installed In`,
|
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,
|
model: ModelType.stockitem,
|
||||||
hidden: !stockitem.belongs_to
|
hidden: !stockitem.belongs_to
|
||||||
},
|
},
|
||||||
@ -259,7 +268,8 @@ export default function StockDetail() {
|
|||||||
name: 'installed_items',
|
name: 'installed_items',
|
||||||
label: t`Installed Items`,
|
label: t`Installed Items`,
|
||||||
icon: <IconBoxPadding />,
|
icon: <IconBoxPadding />,
|
||||||
hidden: !stockitem?.part_detail?.assembly
|
hidden: !stockitem?.part_detail?.assembly,
|
||||||
|
content: <InstalledItemsTable parentId={stockitem.pk} />
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'child_items',
|
name: 'child_items',
|
||||||
|
76
src/frontend/src/tables/stock/InstalledItemsTable.tsx
Normal file
76
src/frontend/src/tables/stock/InstalledItemsTable.tsx
Normal 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
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user