Merge remote-tracking branch 'inventree/master'

This commit is contained in:
Oliver Walters 2021-01-14 14:25:28 +11:00
commit 758ca0d17a
16 changed files with 213 additions and 39 deletions

View File

@ -131,8 +131,7 @@ $.fn.inventreeTable = function(options) {
// Callback when a column is changed // Callback when a column is changed
options.onColumnSwitch = function(field, checked) { options.onColumnSwitch = function(field, checked) {
console.log(`${field} -> ${checked}`);
var columns = table.bootstrapTable('getVisibleColumns'); var columns = table.bootstrapTable('getVisibleColumns');
var text = visibleColumnString(columns); var text = visibleColumnString(columns);

View File

@ -182,6 +182,10 @@ class PartAttachmentList(generics.ListCreateAPIView, AttachmentMixin):
queryset = PartAttachment.objects.all() queryset = PartAttachment.objects.all()
serializer_class = part_serializers.PartAttachmentSerializer serializer_class = part_serializers.PartAttachmentSerializer
filter_backends = [
DjangoFilterBackend,
]
filter_fields = [ filter_fields = [
'part', 'part',
] ]

View File

@ -9,7 +9,7 @@
{% if category %} {% if category %}
<h3> <h3>
{{ category.name }} {{ category.name }}
{% if user.is_staff and roles.part.change %} {% if user.is_staff and roles.part_category.change %}
<a href="{% url 'admin:part_partcategory_change' category.pk %}"><span title="{% trans 'Admin view' %}" class='fas fa-user-shield'></span></a> <a href="{% url 'admin:part_partcategory_change' category.pk %}"><span title="{% trans 'Admin view' %}" class='fas fa-user-shield'></span></a>
{% endif %} {% endif %}
</h3> </h3>
@ -20,18 +20,18 @@
{% endif %} {% endif %}
<p> <p>
<div class='btn-group action-buttons'> <div class='btn-group action-buttons'>
{% if roles.part.add %} {% if roles.part_category.add %}
<button class='btn btn-default' id='cat-create' title='{% trans "Create new part category" %}'> <button class='btn btn-default' id='cat-create' title='{% trans "Create new part category" %}'>
<span class='fas fa-plus-circle icon-green'/> <span class='fas fa-plus-circle icon-green'/>
</button> </button>
{% endif %} {% endif %}
{% if category %} {% if category %}
{% if roles.part.change %} {% if roles.part_category.change %}
<button class='btn btn-default' id='cat-edit' title='{% trans "Edit part category" %}'> <button class='btn btn-default' id='cat-edit' title='{% trans "Edit part category" %}'>
<span class='fas fa-edit icon-blue'/> <span class='fas fa-edit icon-blue'/>
</button> </button>
{% endif %} {% endif %}
{% if roles.part.delete %} {% if roles.part_category.delete %}
<button class='btn btn-default' id='cat-delete' title='{% trans "Delete part category" %}'> <button class='btn btn-default' id='cat-delete' title='{% trans "Delete part category" %}'>
<span class='fas fa-trash-alt icon-red'/> <span class='fas fa-trash-alt icon-red'/>
</button> </button>

View File

@ -214,9 +214,9 @@
<tr> <tr>
<td> <td>
{% if part.active %} {% if part.active %}
<span class='fas fa-check-square'></span> <span class='fas fa-check-circle icon-green'></span>
{% else %} {% else %}
<span class='fas fa-times-square'></span> <span class='fas fa-times-circle icon-red'></span>
{% endif %} {% endif %}
</td> </td>
<td><b>{% trans "Active" %}</b></td> <td><b>{% trans "Active" %}</b></td>

View File

@ -37,7 +37,7 @@
{% if roles.part.change %} {% if roles.part.change %}
<button title='{% trans "Edit" %}' class='btn btn-default btn-glyph param-edit' url="{% url 'part-param-edit' param.id %}" type='button'><span class='fas fa-edit'/></button> <button title='{% trans "Edit" %}' class='btn btn-default btn-glyph param-edit' url="{% url 'part-param-edit' param.id %}" type='button'><span class='fas fa-edit'/></button>
{% endif %} {% endif %}
{% if roles.part.delete %} {% if roles.part.change %}
<button title='{% trans "Delete" %}' class='btn btn-default btn-glyph param-delete' url="{% url 'part-param-delete' param.id %}" type='button'><span class='fas fa-trash-alt icon-red'/></button> <button title='{% trans "Delete" %}' class='btn btn-default btn-glyph param-delete' url="{% url 'part-param-delete' param.id %}" type='button'><span class='fas fa-trash-alt icon-red'/></button>
{% endif %} {% endif %}
</div> </div>

View File

@ -231,7 +231,7 @@ class PartAttachmentDelete(AjaxDeleteView):
ajax_template_name = "attachment_delete.html" ajax_template_name = "attachment_delete.html"
context_object_name = "attachment" context_object_name = "attachment"
role_required = 'part.delete' role_required = 'part.change'
def get_data(self): def get_data(self):
return { return {
@ -1320,7 +1320,7 @@ class BomUpload(InvenTreeRoleMixin, FormView):
# Otherwise, check to see if there is a matching IPN # Otherwise, check to see if there is a matching IPN
try: try:
if row['part_ipn']: if row['part_ipn']:
part_matches = [part for part in self.allowed_parts if row['part_ipn'].lower() == part.IPN.lower()] part_matches = [part for part in self.allowed_parts if part.IPN and row['part_ipn'].lower() == str(part.IPN.lower())]
# Check for single match # Check for single match
if len(part_matches) == 1: if len(part_matches) == 1:
@ -2073,7 +2073,7 @@ class PartParameterEdit(AjaxUpdateView):
class PartParameterDelete(AjaxDeleteView): class PartParameterDelete(AjaxDeleteView):
""" View for deleting a PartParameter """ """ View for deleting a PartParameter """
role_required = 'part.delete' role_required = 'part.change'
model = PartParameter model = PartParameter
ajax_template_name = 'part/param_delete.html' ajax_template_name = 'part/param_delete.html'
@ -2088,7 +2088,7 @@ class CategoryDetail(InvenTreeRoleMixin, DetailView):
queryset = PartCategory.objects.all().prefetch_related('children') queryset = PartCategory.objects.all().prefetch_related('children')
template_name = 'part/category_partlist.html' template_name = 'part/category_partlist.html'
role_required = 'part.view' role_required = ['part_category.view', 'part.view']
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
@ -2138,7 +2138,7 @@ class CategoryEdit(AjaxUpdateView):
ajax_template_name = 'modal_form.html' ajax_template_name = 'modal_form.html'
ajax_form_title = _('Edit Part Category') ajax_form_title = _('Edit Part Category')
role_required = 'part.change' role_required = 'part_category.change'
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(CategoryEdit, self).get_context_data(**kwargs).copy() context = super(CategoryEdit, self).get_context_data(**kwargs).copy()
@ -2177,7 +2177,7 @@ class CategoryDelete(AjaxDeleteView):
context_object_name = 'category' context_object_name = 'category'
success_url = '/part/' success_url = '/part/'
role_required = 'part.delete' role_required = 'part_category.delete'
def get_data(self): def get_data(self):
return { return {
@ -2193,7 +2193,7 @@ class CategoryCreate(AjaxCreateView):
ajax_template_name = 'modal_form.html' ajax_template_name = 'modal_form.html'
form_class = part_forms.EditCategoryForm form_class = part_forms.EditCategoryForm
role_required = 'part.add' role_required = 'part_category.add'
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
""" Add extra context data to template. """ Add extra context data to template.
@ -2233,7 +2233,7 @@ class CategoryCreate(AjaxCreateView):
class CategoryParameterTemplateCreate(AjaxCreateView): class CategoryParameterTemplateCreate(AjaxCreateView):
""" View for creating a new PartCategoryParameterTemplate """ """ View for creating a new PartCategoryParameterTemplate """
role_required = 'part.add' role_required = 'part_category.change'
model = PartCategoryParameterTemplate model = PartCategoryParameterTemplate
form_class = part_forms.EditCategoryParameterTemplateForm form_class = part_forms.EditCategoryParameterTemplateForm
@ -2336,7 +2336,7 @@ class CategoryParameterTemplateCreate(AjaxCreateView):
class CategoryParameterTemplateEdit(AjaxUpdateView): class CategoryParameterTemplateEdit(AjaxUpdateView):
""" View for editing a PartCategoryParameterTemplate """ """ View for editing a PartCategoryParameterTemplate """
role_required = 'part.change' role_required = 'part_category.change'
model = PartCategoryParameterTemplate model = PartCategoryParameterTemplate
form_class = part_forms.EditCategoryParameterTemplateForm form_class = part_forms.EditCategoryParameterTemplateForm
@ -2395,7 +2395,7 @@ class CategoryParameterTemplateEdit(AjaxUpdateView):
class CategoryParameterTemplateDelete(AjaxDeleteView): class CategoryParameterTemplateDelete(AjaxDeleteView):
""" View for deleting an existing PartCategoryParameterTemplate """ """ View for deleting an existing PartCategoryParameterTemplate """
role_required = 'part.delete' role_required = 'part_category.change'
model = PartCategoryParameterTemplate model = PartCategoryParameterTemplate
ajax_form_title = _("Delete Category Parameter Template") ajax_form_title = _("Delete Category Parameter Template")
@ -2554,7 +2554,7 @@ class BomItemDelete(AjaxDeleteView):
context_object_name = 'item' context_object_name = 'item'
ajax_form_title = _('Confim BOM item deletion') ajax_form_title = _('Confim BOM item deletion')
role_required = 'part.delete' role_required = 'part.change'
class PartSalePriceBreakCreate(AjaxCreateView): class PartSalePriceBreakCreate(AjaxCreateView):

View File

@ -208,6 +208,7 @@ class StockItemSerializer(InvenTreeModelSerializer):
'stale', 'stale',
'status', 'status',
'status_text', 'status_text',
'stocktake_date',
'supplier_part', 'supplier_part',
'supplier_part_detail', 'supplier_part_detail',
'tracking_items', 'tracking_items',

View File

@ -8,7 +8,7 @@
{% if location %} {% if location %}
<h3> <h3>
{{ location.name }} {{ location.name }}
{% if user.is_staff and roles.stock.change %} {% if user.is_staff and roles.stock_location.change %}
<a href="{% url 'admin:stock_stocklocation_change' location.pk %}"><span title="{% trans 'Admin view' %}" class='fas fa-user-shield'></span></a> <a href="{% url 'admin:stock_stocklocation_change' location.pk %}"><span title="{% trans 'Admin view' %}" class='fas fa-user-shield'></span></a>
{% endif %} {% endif %}
</h3> </h3>
@ -18,7 +18,7 @@
<p>{% trans "All stock items" %}</p> <p>{% trans "All stock items" %}</p>
{% endif %} {% endif %}
<div class='btn-group action-buttons' role='group'> <div class='btn-group action-buttons' role='group'>
{% if roles.stock.add %} {% if roles.stock_location.add %}
<button class='btn btn-default' id='location-create' title='{% trans "Create new stock location" %}'> <button class='btn btn-default' id='location-create' title='{% trans "Create new stock location" %}'>
<span class='fas fa-plus-circle icon-green'/> <span class='fas fa-plus-circle icon-green'/>
</button> </button>
@ -41,11 +41,13 @@
{% trans "Count stock" %}</a></li> {% trans "Count stock" %}</a></li>
</ul> </ul>
</div> </div>
{% endif %}
{% if roles.stock_location.change %}
<div class='btn-group'> <div class='btn-group'>
<button id='location-actions' title='{% trans "Location actions" %}' class='btn btn-default dropdown-toggle' type='button' data-toggle="dropdown"><span class='fas fa-sitemap'></span> <span class='caret'></span></button> <button id='location-actions' title='{% trans "Location actions" %}' class='btn btn-default dropdown-toggle' type='button' data-toggle="dropdown"><span class='fas fa-sitemap'></span> <span class='caret'></span></button>
<ul class='dropdown-menu' role='menu'> <ul class='dropdown-menu' role='menu'>
<li><a href='#' id='location-edit'><span class='fas fa-edit icon-green'></span> {% trans "Edit location" %}</a></li> <li><a href='#' id='location-edit'><span class='fas fa-edit icon-green'></span> {% trans "Edit location" %}</a></li>
{% if roles.stock.delete %} {% if roles.stock_location.delete %}
<li><a href='#' id='location-delete'><span class='fas fa-trash-alt icon-red'></span> {% trans "Delete location" %}</a></li> <li><a href='#' id='location-delete'><span class='fas fa-trash-alt icon-red'></span> {% trans "Delete location" %}</a></li>
{% endif %} {% endif %}
</ul> </ul>

View File

@ -1,6 +1,6 @@
{% extends "collapse.html" %} {% extends "collapse.html" %}
{% if roles.stock.view %} {% if roles.stock_location.view or roles.stock.view %}
{% block collapse_title %} {% block collapse_title %}
Sub-Locations<span class='badge'>{{ children|length }}</span> Sub-Locations<span class='badge'>{{ children|length }}</span>
{% endblock %} {% endblock %}

View File

@ -73,7 +73,7 @@ class StockLocationDetail(InvenTreeRoleMixin, DetailView):
template_name = 'stock/location.html' template_name = 'stock/location.html'
queryset = StockLocation.objects.all() queryset = StockLocation.objects.all()
model = StockLocation model = StockLocation
role_required = 'stock.view' role_required = ['stock_location.view', 'stock.view']
class StockItemDetail(InvenTreeRoleMixin, DetailView): class StockItemDetail(InvenTreeRoleMixin, DetailView):
@ -121,7 +121,7 @@ class StockLocationEdit(AjaxUpdateView):
context_object_name = 'location' context_object_name = 'location'
ajax_template_name = 'modal_form.html' ajax_template_name = 'modal_form.html'
ajax_form_title = _('Edit Stock Location') ajax_form_title = _('Edit Stock Location')
role_required = 'stock.change' role_required = 'stock_location.change'
def get_form(self): def get_form(self):
""" Customize form data for StockLocation editing. """ Customize form data for StockLocation editing.
@ -146,7 +146,7 @@ class StockLocationQRCode(QRCodeView):
""" View for displaying a QR code for a StockLocation object """ """ View for displaying a QR code for a StockLocation object """
ajax_form_title = _("Stock Location QR code") ajax_form_title = _("Stock Location QR code")
role_required = 'stock.view' role_required = ['stock_location.view', 'stock.view']
def get_qr_data(self): def get_qr_data(self):
""" Generate QR code data for the StockLocation """ """ Generate QR code data for the StockLocation """
@ -1361,7 +1361,7 @@ class StockLocationCreate(AjaxCreateView):
context_object_name = 'location' context_object_name = 'location'
ajax_template_name = 'modal_form.html' ajax_template_name = 'modal_form.html'
ajax_form_title = _('Create new Stock Location') ajax_form_title = _('Create new Stock Location')
role_required = 'stock.add' role_required = 'stock_location.add'
def get_initial(self): def get_initial(self):
initials = super(StockLocationCreate, self).get_initial().copy() initials = super(StockLocationCreate, self).get_initial().copy()
@ -1721,7 +1721,7 @@ class StockItemCreate(AjaxCreateView):
item = form.save(commit=False) item = form.save(commit=False)
item.user = self.request.user item.user = self.request.user
item.save() item.save(user=self.request.user)
return item return item
@ -1732,7 +1732,7 @@ class StockItemCreate(AjaxCreateView):
item = form.save(commit=False) item = form.save(commit=False)
item.user = self.request.user item.user = self.request.user
item.save() item.save(user=self.request.user)
return item return item
@ -1748,7 +1748,7 @@ class StockLocationDelete(AjaxDeleteView):
ajax_template_name = 'stock/location_delete.html' ajax_template_name = 'stock/location_delete.html'
context_object_name = 'location' context_object_name = 'location'
ajax_form_title = _('Delete Stock Location') ajax_form_title = _('Delete Stock Location')
role_required = 'stock.delete' role_required = 'stock_location.delete'
class StockItemDelete(AjaxDeleteView): class StockItemDelete(AjaxDeleteView):

View File

@ -255,6 +255,38 @@ function loadBomTable(table, options) {
}); });
*/ */
} }
cols.push(
{
'field': 'can_build',
'title': '{% trans "Can Build" %}',
formatter: function(value, row, index, field) {
var can_build = 0;
if (row.quantity > 0) {
can_build = row.sub_part_detail.stock / row.quantity;
}
return +can_build.toFixed(2);
},
sorter: function(valA, valB, rowA, rowB) {
// Function to sort the "can build" quantity
var cb_a = 0;
var cb_b = 0;
if (rowA.quantity > 0) {
cb_a = rowA.sub_part_detail.stock / rowA.quantity;
}
if (rowB.quantity > 0) {
cb_b = rowB.sub_part_detail.stock / rowB.quantity;
}
return (cb_a > cb_b) ? 1 : -1;
},
sortable: true,
}
)
// Part notes // Part notes
cols.push( cols.push(

View File

@ -6,8 +6,18 @@
* Requires api.js to be loaded first * Requires api.js to be loaded first
*/ */
/* Functions for interacting with stock management forms
*/ function stockStatusCodes() {
return [
{% for code in StockStatus.list %}
{
key: {{ code.key }},
text: "{{ code.value }}",
},
{% endfor %}
];
}
function removeStockRow(e) { function removeStockRow(e) {
// Remove a selected row from a stock modal form // Remove a selected row from a stock modal form
@ -590,6 +600,11 @@ function loadStockTable(table, options) {
return locationDetail(row); return locationDetail(row);
} }
}, },
{
field: 'stocktake_date',
title: '{% trans "Stocktake" %}',
sortable: true,
},
{% settings_value "STOCK_ENABLE_EXPIRY" as expiry %} {% settings_value "STOCK_ENABLE_EXPIRY" as expiry %}
{% if expiry %} {% if expiry %}
{ {
@ -677,6 +692,93 @@ function loadStockTable(table, options) {
}); });
}); });
$("#multi-item-set-status").click(function() {
// Select and set the STATUS field for selected stock items
var selections = $("#stock-table").bootstrapTable('getSelections');
// Select stock status
var modal = '#modal-form';
var status_list = makeOptionsList(
stockStatusCodes(),
function(item) {
return item.text;
},
function (item) {
return item.key;
}
);
// Add an empty option at the start of the list
status_list.unshift('<option value="">---------</option>');
// Construct form
var html = `
<form method='post' action='' class='js-modal-form' enctype='multipart/form-data'>
<div class='form-group'>
<label class='control-label requiredField' for='id_status'>
{% trans "Stock Status" %}
</label>
<div class='controls'>
<select id='id_status' class='select form-control' name='label'>
${status_list}
</select>
</div>
</div>
</form>`;
openModal({
modal: modal,
});
modalEnable(modal, true);
modalSetTitle(modal, '{% trans "Set Stock Status" %}');
modalSetContent(modal, html);
attachSelect(modal);
modalSubmit(modal, function() {
var label = $(modal).find('#id_status');
var status_code = label.val();
closeModal(modal);
if (!status_code) {
showAlertDialog(
'{% trans "Select Status Code" %}',
'{% trans "Status code must be selected" %}'
);
return;
}
var requests = [];
selections.forEach(function(item) {
var url = `/api/stock/${item.pk}/`;
requests.push(
inventreePut(
url,
{
status: status_code,
},
{
method: 'PATCH',
success: function() {
}
}
)
);
});
$.when.apply($, requests).then(function() {
$("#stock-table").bootstrapTable('refresh');
});
})
});
$("#multi-item-delete").click(function() { $("#multi-item-delete").click(function() {
var selections = $("#stock-table").bootstrapTable("getSelections"); var selections = $("#stock-table").bootstrapTable("getSelections");

View File

@ -23,6 +23,7 @@
<li><a href="#" id='multi-item-stocktake' title='{% trans "Stocktake selected stock items" %}'><span class='fas fa-check-circle'></span> {% trans "Count stock" %}</a></li> <li><a href="#" id='multi-item-stocktake' title='{% trans "Stocktake selected stock items" %}'><span class='fas fa-check-circle'></span> {% trans "Count stock" %}</a></li>
<li><a href='#' id='multi-item-move' title='{% trans "Move selected stock items" %}'><span class='fas fa-exchange-alt'></span> {% trans "Move stock" %}</a></li> <li><a href='#' id='multi-item-move' title='{% trans "Move selected stock items" %}'><span class='fas fa-exchange-alt'></span> {% trans "Move stock" %}</a></li>
<li><a href='#' id='multi-item-order' title='{% trans "Order selected items" %}'><span class='fas fa-shopping-cart'></span> {% trans "Order stock" %}</a></li> <li><a href='#' id='multi-item-order' title='{% trans "Order selected items" %}'><span class='fas fa-shopping-cart'></span> {% trans "Order stock" %}</a></li>
<li><a href='#' id='multi-item-set-status' title='{% trans "Change status" %}'><span class='fas fa-exclamation-circle'></span> {% trans "Change stock status" %}</a></li>
{% endif %} {% endif %}
{% if roles.stock.delete %} {% if roles.stock.delete %}
<li><a href='#' id='multi-item-delete' title='{% trans "Delete selected items" %}'><span class='fas fa-trash-alt'></span> {% trans "Delete Stock" %}</a></li> <li><a href='#' id='multi-item-delete' title='{% trans "Delete selected items" %}'><span class='fas fa-trash-alt'></span> {% trans "Delete Stock" %}</a></li>

View File

@ -30,6 +30,8 @@ class RuleSetInline(admin.TabularInline):
max_num = len(RuleSet.RULESET_CHOICES) max_num = len(RuleSet.RULESET_CHOICES)
min_num = 1 min_num = 1
extra = 0 extra = 0
# TODO: find better way to order inlines
ordering = ['name']
class InvenTreeGroupAdminForm(forms.ModelForm): class InvenTreeGroupAdminForm(forms.ModelForm):
@ -87,7 +89,8 @@ class RoleGroupAdmin(admin.ModelAdmin):
RuleSetInline, RuleSetInline,
] ]
list_display = ('name', 'admin', 'part', 'stock', 'build', 'purchase_order', 'sales_order') list_display = ('name', 'admin', 'part_category', 'part', 'stock_location',
'stock_item', 'build', 'purchase_order', 'sales_order')
def get_rule_set(self, obj, rule_set_type): def get_rule_set(self, obj, rule_set_type):
''' Return list of permissions for the given ruleset ''' ''' Return list of permissions for the given ruleset '''
@ -130,10 +133,16 @@ class RoleGroupAdmin(admin.ModelAdmin):
def admin(self, obj): def admin(self, obj):
return self.get_rule_set(obj, 'admin') return self.get_rule_set(obj, 'admin')
def part_category(self, obj):
return self.get_rule_set(obj, 'part_category')
def part(self, obj): def part(self, obj):
return self.get_rule_set(obj, 'part') return self.get_rule_set(obj, 'part')
def stock(self, obj): def stock_location(self, obj):
return self.get_rule_set(obj, 'stock_location')
def stock_item(self, obj):
return self.get_rule_set(obj, 'stock') return self.get_rule_set(obj, 'stock')
def build(self, obj): def build(self, obj):

View File

@ -0,0 +1,18 @@
# Generated by Django 3.0.7 on 2021-01-13 19:09
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('users', '0003_auto_20201005_2227'),
]
operations = [
migrations.AlterField(
model_name='ruleset',
name='name',
field=models.CharField(choices=[('admin', 'Admin'), ('part_category', 'Part Categories'), ('part', 'Parts'), ('stock_location', 'Stock Locations'), ('stock', 'Stock Items'), ('build', 'Build Orders'), ('purchase_order', 'Purchase Orders'), ('sales_order', 'Sales Orders')], help_text='Permission set', max_length=50),
),
]

View File

@ -25,8 +25,10 @@ class RuleSet(models.Model):
RULESET_CHOICES = [ RULESET_CHOICES = [
('admin', _('Admin')), ('admin', _('Admin')),
('part_category', _('Part Categories')),
('part', _('Parts')), ('part', _('Parts')),
('stock', _('Stock')), ('stock_location', _('Stock Locations')),
('stock', _('Stock Items')),
('build', _('Build Orders')), ('build', _('Build Orders')),
('purchase_order', _('Purchase Orders')), ('purchase_order', _('Purchase Orders')),
('sales_order', _('Sales Orders')), ('sales_order', _('Sales Orders')),
@ -48,21 +50,25 @@ class RuleSet(models.Model):
'authtoken_token', 'authtoken_token',
'users_ruleset', 'users_ruleset',
], ],
'part_category': [
'part_partcategory',
'part_partcategoryparametertemplate',
],
'part': [ 'part': [
'part_part', 'part_part',
'part_bomitem', 'part_bomitem',
'part_partcategory',
'part_partattachment', 'part_partattachment',
'part_partsellpricebreak', 'part_partsellpricebreak',
'part_parttesttemplate', 'part_parttesttemplate',
'part_partparametertemplate', 'part_partparametertemplate',
'part_partparameter', 'part_partparameter',
'part_partrelated', 'part_partrelated',
'part_partcategoryparametertemplate', ],
'stock_location': [
'stock_stocklocation',
], ],
'stock': [ 'stock': [
'stock_stockitem', 'stock_stockitem',
'stock_stocklocation',
'stock_stockitemattachment', 'stock_stockitemattachment',
'stock_stockitemtracking', 'stock_stockitemtracking',
'stock_stockitemtestresult', 'stock_stockitemtestresult',