mirror of
https://github.com/inventree/InvenTree.git
synced 2025-12-17 12:25:04 -06:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
236ca36138 | ||
|
|
3a1d180c34 | ||
|
|
c621c4746a | ||
|
|
aca8ced4d4 | ||
|
|
44f26d8e80 | ||
|
|
93b51db089 | ||
|
|
5dd256db3c | ||
|
|
641adb6ae8 | ||
|
|
eb7aba4dd8 | ||
|
|
af8f1c6bab | ||
|
|
eaca66ab64 |
@@ -32,7 +32,6 @@ def canAppAccessDatabase(allow_test: bool = False, allow_plugins: bool = False):
|
||||
'prerender',
|
||||
'rebuild_models',
|
||||
'rebuild_thumbnails',
|
||||
'collectstatic',
|
||||
'makemessages',
|
||||
'compilemessages',
|
||||
'backup',
|
||||
@@ -51,6 +50,7 @@ def canAppAccessDatabase(allow_test: bool = False, allow_plugins: bool = False):
|
||||
excluded_commands.extend([
|
||||
'makemigrations',
|
||||
'migrate',
|
||||
'collectstatic',
|
||||
])
|
||||
|
||||
for cmd in excluded_commands:
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
}
|
||||
|
||||
main {
|
||||
overflow-x: clip;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.login-screen {
|
||||
|
||||
@@ -13,7 +13,7 @@ import common.models
|
||||
from InvenTree.api_version import INVENTREE_API_VERSION
|
||||
|
||||
# InvenTree software version
|
||||
INVENTREE_SW_VERSION = "0.9.0"
|
||||
INVENTREE_SW_VERSION = "0.9.2"
|
||||
|
||||
|
||||
def inventreeInstanceName():
|
||||
|
||||
@@ -1,2 +1,99 @@
|
||||
{% extends "order/order_wizard/po_upload.html" %}
|
||||
{% include "patterns/wizard/match_fields.html" %}
|
||||
{% load inventree_extras %}
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
|
||||
{% block form_alert %}
|
||||
{% if missing_columns and missing_columns|length > 0 %}
|
||||
<div class='alert alert-danger alert-block' style='margin-top:12px;' role='alert'>
|
||||
{% trans "Missing selections for the following required columns" %}:
|
||||
<br>
|
||||
<ul>
|
||||
{% for col in missing_columns %}
|
||||
<li>{{ col }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if duplicates and duplicates|length > 0 %}
|
||||
<div class='alert alert-danger alert-block' role='alert'>
|
||||
{% trans "Duplicate selections found, see below. Fix them then retry submitting." %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock form_alert %}
|
||||
|
||||
{% block form_buttons_top %}
|
||||
{% if wizard.steps.prev %}
|
||||
<button name='wizard_goto_step' type='submit' value='{{ wizard.steps.prev }}' class='save btn btn-outline-secondary'>{% trans "Previous Step" %}</button>
|
||||
{% endif %}
|
||||
<button type='submit' class='save btn btn-outline-secondary'>{% trans "Submit Selections" %}</button>
|
||||
{% endblock form_buttons_top %}
|
||||
|
||||
{% block form_content %}
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "File Fields" %}</th>
|
||||
<th></th>
|
||||
{% for col in form %}
|
||||
<th>
|
||||
<div>
|
||||
<input type='hidden' name='col_name_{{ forloop.counter0 }}' value='{{ col.name }}'/>
|
||||
{{ col.name }}
|
||||
<button class='btn btn-outline-secondary btn-remove' onClick='removeColFromBomWizard()' id='del_col_{{ forloop.counter0 }}' style='display: inline; float: right;' title='{% trans "Remove column" %}'>
|
||||
<span col_id='{{ forloop.counter0 }}' class='fas fa-trash-alt icon-red'></span>
|
||||
</button>
|
||||
</div>
|
||||
</th>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{% trans "Match Fields" %}</td>
|
||||
<td></td>
|
||||
{% for col in form %}
|
||||
<td>
|
||||
{{ col }}
|
||||
{% for duplicate in duplicates %}
|
||||
{% if duplicate == col.value %}
|
||||
<div class='alert alert-danger alert-block text-center' role='alert' style='padding:2px; margin-top:6px; margin-bottom:2px'>
|
||||
<strong>{% trans "Duplicate selection" %}</strong>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</td>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% for row in rows %}
|
||||
{% with forloop.counter as row_index %}
|
||||
<tr>
|
||||
<td style='width: 32px;'>
|
||||
<button class='btn btn-outline-secondary btn-remove' onClick='removeRowFromBomWizard()' id='del_row_{{ row_index }}' style='display: inline; float: left;' title='{% trans "Remove row" %}'>
|
||||
<span row_id='{{ row_index }}' class='fas fa-trash-alt icon-red'></span>
|
||||
</button>
|
||||
</td>
|
||||
<td style='text-align: left;'>{{ row_index }}</td>
|
||||
{% for item in row.data %}
|
||||
<td>
|
||||
<input type='hidden' name='row_{{ row_index }}_col_{{ forloop.counter0 }}' value='{{ item }}'/>
|
||||
{{ item }}
|
||||
</td>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% endwith %}
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
{% endblock form_content %}
|
||||
|
||||
{% block form_buttons_bottom %}
|
||||
{% endblock form_buttons_bottom %}
|
||||
|
||||
{% block js_ready %}
|
||||
{{ block.super }}
|
||||
|
||||
$('.fieldselect').select2({
|
||||
width: '100%',
|
||||
matcher: partialMatcher,
|
||||
});
|
||||
|
||||
{% endblock %}
|
||||
|
||||
@@ -12,12 +12,53 @@
|
||||
{% block page_content %}
|
||||
{% trans "Upload File for Purchase Order" as header_text %}
|
||||
{% trans "Order is already processed. Files cannot be uploaded." as error_text %}
|
||||
{% with "panel-upload-file" as panel_id %}
|
||||
{% if order.status == PurchaseOrderStatus.PENDING and roles.purchase_order.change %}
|
||||
{% include "patterns/wizard/upload.html" with header_text=header_text upload_go_ahead=True error_text=error_text panel_id=panel_id %}
|
||||
{% else %}
|
||||
{% include "patterns/wizard/upload.html" with header_text=header_text upload_go_ahead=False error_text=error_text panel_id=panel_id %}
|
||||
{% endif %}
|
||||
{% with panel_id="panel-upload-file" %}
|
||||
|
||||
<div class='panel' id='{{ panel_id }}'>
|
||||
<div class='panel-heading'>
|
||||
<h4>
|
||||
{{ header_text }}
|
||||
{{ wizard.form.media }}
|
||||
</h4>
|
||||
</div>
|
||||
<div class='panel-content'>
|
||||
{% if order.status == PurchaseOrderStatus.PENDING and roles.purchase_order.change %}
|
||||
|
||||
<p>{% blocktrans with step=wizard.steps.step1 count=wizard.steps.count %}Step {{step}} of {{count}}{% endblocktrans %}
|
||||
{% if description %}- {{ description }}{% endif %}</p>
|
||||
|
||||
{% block form_alert %}
|
||||
{% endblock form_alert %}
|
||||
|
||||
<form action='' method='post' class='js-modal-form' enctype='multipart/form-data'>
|
||||
{% csrf_token %}
|
||||
{% load crispy_forms_tags %}
|
||||
|
||||
{% block form_buttons_top %}
|
||||
{% endblock form_buttons_top %}
|
||||
|
||||
<table class='table table-striped' style='margin-top: 12px; margin-bottom: 0px'>
|
||||
{{ wizard.management_form }}
|
||||
{% block form_content %}
|
||||
{% crispy wizard.form %}
|
||||
{% endblock form_content %}
|
||||
</table>
|
||||
|
||||
{% block form_buttons_bottom %}
|
||||
{% if wizard.steps.prev %}
|
||||
<button name='wizard_goto_step' type='submit' value='{{ wizard.steps.prev }}' class='save btn btn-outline-secondary'>{% trans "Previous Step" %}</button>
|
||||
{% endif %}
|
||||
<button type='submit' class='save btn btn-outline-secondary'>{% trans "Upload File" %}</button>
|
||||
</form>
|
||||
{% endblock form_buttons_bottom %}
|
||||
|
||||
{% else %}
|
||||
<div class='alert alert-danger alert-block' role='alert'>
|
||||
{{ error_text }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endwith %}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
@@ -148,7 +148,7 @@ class PartCategory(MetadataMixin, InvenTreeTree):
|
||||
|
||||
- Ensure that the structural parameter cannot get set if products already assigned to the category
|
||||
"""
|
||||
if self.pk and self.structural and self.item_count > 0:
|
||||
if self.pk and self.structural and self.partcount(False, False) > 0:
|
||||
raise ValidationError(
|
||||
_("You cannot make this part category structural because some parts "
|
||||
"are already assigned to it!"))
|
||||
@@ -2248,13 +2248,19 @@ class Part(InvenTreeBarcodeMixin, MetadataMixin, MPTTModel):
|
||||
@receiver(post_save, sender=Part, dispatch_uid='part_post_save_log')
|
||||
def after_save_part(sender, instance: Part, created, **kwargs):
|
||||
"""Function to be executed after a Part is saved."""
|
||||
from pickle import PicklingError
|
||||
|
||||
from part import tasks as part_tasks
|
||||
|
||||
if not created and not InvenTree.ready.isImportingData():
|
||||
if instance and not created and not InvenTree.ready.isImportingData():
|
||||
# Check part stock only if we are *updating* the part (not creating it)
|
||||
|
||||
# Run this check in the background
|
||||
InvenTree.tasks.offload_task(part_tasks.notify_low_stock_if_required, instance)
|
||||
try:
|
||||
InvenTree.tasks.offload_task(part_tasks.notify_low_stock_if_required, instance)
|
||||
except PicklingError:
|
||||
# Can sometimes occur if the referenced Part has issues
|
||||
pass
|
||||
|
||||
|
||||
class PartPricing(models.Model):
|
||||
|
||||
@@ -123,4 +123,5 @@ def check_missing_pricing(limit=250):
|
||||
|
||||
for p in results:
|
||||
pricing = p.pricing
|
||||
pricing.save()
|
||||
pricing.schedule_for_update()
|
||||
|
||||
@@ -22,6 +22,9 @@ class ReportConfig(AppConfig):
|
||||
if canAppAccessDatabase(allow_test=True):
|
||||
self.create_default_test_reports()
|
||||
self.create_default_build_reports()
|
||||
self.create_default_bill_of_materials_reports()
|
||||
self.create_default_purchase_order_reports()
|
||||
self.create_default_sales_order_reports()
|
||||
|
||||
def create_default_reports(self, model, reports):
|
||||
"""Copy defualt report files across to the media directory."""
|
||||
@@ -96,6 +99,25 @@ class ReportConfig(AppConfig):
|
||||
|
||||
self.create_default_reports(TestReport, reports)
|
||||
|
||||
def create_default_bill_of_materials_reports(self):
|
||||
"""Create database entries for the default Bill of Material templates (if they do not already exist)"""
|
||||
try:
|
||||
from .models import BillOfMaterialsReport
|
||||
except Exception: # pragma: no cover
|
||||
# Database is not ready yet
|
||||
return
|
||||
|
||||
# List of Build reports to copy across
|
||||
reports = [
|
||||
{
|
||||
'file': 'inventree_bill_of_materials_report.html',
|
||||
'name': 'Bill of Materials',
|
||||
'description': 'Bill of Materials report',
|
||||
}
|
||||
]
|
||||
|
||||
self.create_default_reports(BillOfMaterialsReport, reports)
|
||||
|
||||
def create_default_build_reports(self):
|
||||
"""Create database entries for the default BuildReport templates (if they do not already exist)"""
|
||||
try:
|
||||
@@ -114,3 +136,41 @@ class ReportConfig(AppConfig):
|
||||
]
|
||||
|
||||
self.create_default_reports(BuildReport, reports)
|
||||
|
||||
def create_default_purchase_order_reports(self):
|
||||
"""Create database entries for the default SalesOrderReport templates (if they do not already exist)"""
|
||||
try:
|
||||
from .models import PurchaseOrderReport
|
||||
except Exception: # pragma: no cover
|
||||
# Database is not ready yet
|
||||
return
|
||||
|
||||
# List of Build reports to copy across
|
||||
reports = [
|
||||
{
|
||||
'file': 'inventree_po_report.html',
|
||||
'name': 'InvenTree Purchase Order',
|
||||
'description': 'Purchase Order example report',
|
||||
}
|
||||
]
|
||||
|
||||
self.create_default_reports(PurchaseOrderReport, reports)
|
||||
|
||||
def create_default_sales_order_reports(self):
|
||||
"""Create database entries for the default Sales Order report templates (if they do not already exist)"""
|
||||
try:
|
||||
from .models import SalesOrderReport
|
||||
except Exception: # pragma: no cover
|
||||
# Database is not ready yet
|
||||
return
|
||||
|
||||
# List of Build reports to copy across
|
||||
reports = [
|
||||
{
|
||||
'file': 'inventree_so_report.html',
|
||||
'name': 'InvenTree Sales Order',
|
||||
'description': 'Sales Order example report',
|
||||
}
|
||||
]
|
||||
|
||||
self.create_default_reports(SalesOrderReport, reports)
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
{% extends "report/inventree_report_base.html" %}
|
||||
|
||||
{% load i18n %}
|
||||
{% load report %}
|
||||
{% load barcode %}
|
||||
{% load inventree_extras %}
|
||||
|
||||
{% block page_margin %}
|
||||
margin: 2cm;
|
||||
margin-top: 4cm;
|
||||
{% endblock %}
|
||||
|
||||
{% block bottom_left %}
|
||||
content: "v{{report_revision}} - {{ date.isoformat }}";
|
||||
{% endblock %}
|
||||
|
||||
{% block bottom_center %}
|
||||
content: "{% inventree_version shortstring=True %}";
|
||||
{% endblock %}
|
||||
|
||||
{% block style %}
|
||||
|
||||
.header-right {
|
||||
text-align: right;
|
||||
float: right;
|
||||
}
|
||||
|
||||
.logo {
|
||||
height: 20mm;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.thumb-container {
|
||||
width: 32px;
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.part-thumb {
|
||||
max-width: 32px;
|
||||
max-height: 32px;
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.part-text {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.part-logo {
|
||||
max-width: 60px;
|
||||
max-height: 60px;
|
||||
display: inline;
|
||||
}
|
||||
|
||||
table {
|
||||
border: 1px solid #eee;
|
||||
border-radius: 3px;
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
table td {
|
||||
border: 1px solid #eee;
|
||||
}
|
||||
|
||||
table td.shrink {
|
||||
white-space: nowrap
|
||||
}
|
||||
|
||||
table td.expand {
|
||||
width: 99%
|
||||
}
|
||||
|
||||
.invisible-table {
|
||||
border: 0px solid transparent;
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
.invisible-table td {
|
||||
border: 0px solid transparent;
|
||||
}
|
||||
|
||||
.main-part-text {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.main-part-description {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block header_content %}
|
||||
|
||||
<img class='logo' src='{% logo_image %}' alt="Inventree logo" width='150'>
|
||||
|
||||
<div class='header-right'>
|
||||
<h3>{% trans "Bill of Materials" %}</h3>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block page_content %}
|
||||
|
||||
<table class="invisible-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th colspan="2"><h3>{% trans "Part" %}</h3></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<div class='main-part-text'>
|
||||
{{ part.full_name }}
|
||||
</div>
|
||||
<br/>
|
||||
<div class='main-part-description'>
|
||||
{{ part.description }}
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class='part-logo'>
|
||||
<img src='{% part_image part %}' class='part-logo'>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h3>{% trans "Materials needed" %}</h3>
|
||||
<table class='table table-striped table-condensed'>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "Part" %}</th>
|
||||
<th>{% trans "Quantity" %}</th>
|
||||
<th>{% trans "Reference" %}</th>
|
||||
<th>{% trans "Note" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for line in bom_items.all %}
|
||||
<tr>
|
||||
<td>
|
||||
<div class='thumb-container'>
|
||||
<img src='{% part_image line.part %}' class='part-thumb'>
|
||||
</div>
|
||||
<div class='part-text'>
|
||||
{{ line.part.full_name }}
|
||||
</div>
|
||||
</td>
|
||||
<td>{% decimal line.quantity %}</td>
|
||||
<td>{{ line.reference }}</td>
|
||||
<td>{{ line.notes }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{% endblock %}
|
||||
@@ -152,7 +152,7 @@ class StockLocation(InvenTreeBarcodeMixin, MetadataMixin, InvenTreeTree):
|
||||
|
||||
- Ensure stock location can't be made structural if stock items already located to them
|
||||
"""
|
||||
if self.pk and self.structural and self.item_count > 0:
|
||||
if self.pk and self.structural and self.stock_item_count(False) > 0:
|
||||
raise ValidationError(
|
||||
_("You cannot make this stock location structural because some stock items "
|
||||
"are already located into it!"))
|
||||
|
||||
@@ -1333,7 +1333,8 @@ function loadParametricPartTable(table, options={}) {
|
||||
uniqueId: 'pk',
|
||||
onLoadSuccess: function(response) {
|
||||
|
||||
var data = response.results;
|
||||
// Data may be returned paginated, in which case we preference response.results
|
||||
var data = response.results || response;
|
||||
|
||||
for (var idx = 0; idx < data.length; idx++) {
|
||||
var row = data[idx];
|
||||
@@ -1346,8 +1347,14 @@ function loadParametricPartTable(table, options={}) {
|
||||
data[idx] = row;
|
||||
}
|
||||
|
||||
if (response.results) {
|
||||
response.results = data;
|
||||
} else {
|
||||
response = data;
|
||||
}
|
||||
|
||||
// Update the table
|
||||
$(table).bootstrapTable('load', data);
|
||||
$(table).bootstrapTable('load', response);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
Django>=3.2.14,<4 # Django package
|
||||
coreapi # API documentation for djangorestframework
|
||||
cryptography==3.4.8 # Core cryptographic functionality
|
||||
django-allauth # SSO for external providers via OpenID
|
||||
django-allauth==0.52.0 # SSO for external providers via OpenID
|
||||
django-allauth-2fa # MFA / 2FA
|
||||
django-cleanup # Automated deletion of old / unused uploaded files
|
||||
django-cors-headers # CORS headers extension for DRF
|
||||
|
||||
@@ -68,7 +68,7 @@ django==3.2.16
|
||||
# django-weasyprint
|
||||
# django-xforwardedfor-middleware
|
||||
# djangorestframework
|
||||
django-allauth==0.51.0
|
||||
django-allauth==0.52.0
|
||||
# via
|
||||
# -r requirements.in
|
||||
# django-allauth-2fa
|
||||
|
||||
Reference in New Issue
Block a user