Merge branch 'master' of github.com:inventree/InvenTree into bom_items_purchase_price

This commit is contained in:
eeintech 2021-05-19 13:25:13 -04:00
commit 7a175f13a1
38 changed files with 10992 additions and 8671 deletions

View File

@ -44,3 +44,8 @@ class InvenTreeConfig(AppConfig):
schedule_type=Schedule.MINUTES,
minutes=15
)
InvenTree.tasks.schedule_task(
'InvenTree.tasks.update_exchange_rates',
schedule_type=Schedule.DAILY,
)

View File

@ -87,6 +87,11 @@ DEBUG = _is_true(get_setting(
CONFIG.get('debug', True)
))
DOCKER = _is_true(get_setting(
'INVENTREE_DOCKER',
False
))
# Configure logging settings
log_level = get_setting(
'INVENTREE_LOG_LEVEL',
@ -508,7 +513,8 @@ CURRENCIES = CONFIG.get(
],
)
# TODO - Allow live web-based backends in the future
BASE_CURRENCY = CONFIG.get('base_currency', 'USD')
EXCHANGE_BACKEND = 'InvenTree.exchange.InvenTreeManualExchangeBackend'
# Extract email settings from the config file

View File

@ -901,6 +901,15 @@ input[type="submit"] {
color: #e00;
}
.info-messages {
padding: 5px;
}
.info-messages .alert {
padding: 5px;
margin-bottom: 10px;
}
.part-allocation {
padding: 3px 10px;
border: 1px solid #ccc;

View File

@ -12,7 +12,7 @@ function attachClipboard(selector, containerselector, textElement) {
return document.getElementById(textElement).textContent;
}
} else {
text = function() {
text = function(trigger) {
var content = trigger.parentElement.parentElement.textContent;return content.trim();
}
}
@ -22,7 +22,6 @@ function attachClipboard(selector, containerselector, textElement) {
text: text,
container: containerselector
});
console.log(cis);
}
@ -81,7 +80,6 @@ function inventreeDocReady() {
attachClipboard('.clip-btn');
attachClipboard('.clip-btn', 'modal-about'); // modals
attachClipboard('.clip-btn-version', 'modal-about', 'about-copy-text'); // version-text
}
function isFileTransfer(transfer) {

View File

@ -161,6 +161,34 @@ def check_for_updates():
)
def update_exchange_rates():
"""
If an API key for fixer.io has been provided, attempt to update currency exchange rates
"""
try:
import common.models
from django.conf import settings
from djmoney.contrib.exchange.backends import FixerBackend
except AppRegistryNotReady:
# Apps not yet loaded!
return
fixer_api_key = common.models.InvenTreeSetting.get_setting('INVENTREE_FIXER_API_KEY', '').strip()
if not fixer_api_key:
# API key not provided
return
backend = FixerBackend(access_key=fixer_api_key)
currencies = ','.join(settings.CURRENCIES)
base = settings.BASE_CURRENCY
backend.update_rates(base_currency=base, symbols=currencies)
def send_email(subject, body, recipients, from_email=None):
"""
Send an email with the specified subject and body,

View File

@ -9,44 +9,54 @@
{% inventree_title %} | {% trans "Build Order" %} - {{ build }}
{% endblock %}
{% block below_thumbnail %}
<div class='info-messages'>
{% if build.sales_order %}
<div class='alert alert-block alert-info'>
{% object_link 'so-detail' build.sales_order.id build.sales_order as link %}
{% blocktrans %}This Build Order is allocated to Sales Order {{link}}{% endblocktrans %}
</div>
{% endif %}
{% if build.parent %}
<div class='alert alert-block alert-info'>
{% object_link 'build-detail' build.parent.id build.parent as link %}
{% blocktrans %}This Build Order is a child of Build Order {{link}}{% endblocktrans %}
</div>
{% endif %}
{% if build.active %}
{% if build.can_complete %}
<div class='alert alert-block alert-success'>
{% trans "Build Order is ready to mark as completed" %}
</div>
{% endif %}
{% if build.incomplete_count > 0 %}
<div class='alert alert-block alert-danger'>
{% trans "Build Order cannot be completed as outstanding outputs remain" %}
</div>
{% endif %}
{% if build.completed < build.quantity %}
<div class='alert alert-block alert-warning'>
{% trans "Required build quantity has not yet been completed" %}
</div>
{% endif %}
{% if not build.areUntrackedPartsFullyAllocated %}
<div class='alert alert-block alert-warning'>
{% trans "Stock has not been fully allocated to this Build Order" %}
</div>
{% endif %}
{% endif %}
</div>
{% endblock %}
{% block header_pre_content %}
{% if build.sales_order %}
<div class='alert alert-block alert-info'>
{% object_link 'so-detail' build.sales_order.id build.sales_order as link %}
{% blocktrans %}This Build Order is allocated to Sales Order {{link}}{% endblocktrans %}
</div>
{% endif %}
{% if build.parent %}
<div class='alert alert-block alert-info'>
{% object_link 'build-detail' build.parent.id build.parent as link %}
{% blocktrans %}This Build Order is a child of Build Order {{link}}{% endblocktrans %}
</div>
{% endif %}
{% endblock %}
{% block header_post_content %}
{% if build.active %}
{% if build.can_complete %}
<div class='alert alert-block alert-success'>
{% trans "Build Order is ready to mark as completed" %}
</div>
{% endif %}
{% if build.incomplete_count > 0 %}
<div class='alert alert-block alert-danger'>
{% trans "Build Order cannot be completed as outstanding outputs remain" %}
</div>
{% endif %}
{% if build.completed < build.quantity %}
<div class='alert alert-block alert-warning'>
{% trans "Required build quantity has not yet been completed" %}
</div>
{% endif %}
{% if not build.areUntrackedPartsFullyAllocated %}
<div class='alert alert-block alert-warning'>
{% trans "Stock has not been fully allocated to this Build Order" %}
</div>
{% endif %}
{% endif %}
{% endblock %}
{% block thumbnail %}

View File

@ -87,6 +87,12 @@ class InvenTreeSetting(models.Model):
'choices': djmoney.settings.CURRENCY_CHOICES,
},
'INVENTREE_FIXER_API_KEY': {
'name': _('fixer.io API key'),
'description': _('API key for fixer.io currency conversion service'),
'default': '',
},
'INVENTREE_DOWNLOAD_FROM_URL': {
'name': _('Download from URL'),
'description': _('Allow download of remote images and files from external URL'),

View File

@ -449,8 +449,8 @@ class FileManagementFormView(MultiStepFormView):
if guess:
n = list(self.column_selections.values()).count(self.column_selections[col])
if n > 1:
duplicates.append(col)
if n > 1 and self.column_selections[col] not in duplicates:
duplicates.append(self.column_selections[col])
# Store extra context data
self.extra_context_data = {

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -13,6 +13,10 @@ from .models import SalesOrder, SalesOrderLineItem
from .models import SalesOrderAllocation
class PurchaseOrderLineItemInlineAdmin(admin.StackedInline):
model = PurchaseOrderLineItem
class PurchaseOrderAdmin(ImportExportModelAdmin):
list_display = (
@ -29,6 +33,10 @@ class PurchaseOrderAdmin(ImportExportModelAdmin):
'description',
]
inlines = [
PurchaseOrderLineItemInlineAdmin
]
class SalesOrderAdmin(ImportExportModelAdmin):

View File

@ -826,6 +826,9 @@ class SalesOrderAllocation(models.Model):
else:
return ""
def get_po(self):
return self.item.purchase_order
def complete_allocation(self, user):
"""
Complete this allocation (called when the parent SalesOrder is marked as "shipped"):

View File

@ -235,6 +235,7 @@ class SalesOrderAllocationSerializer(InvenTreeModelSerializer):
location_path = serializers.CharField(source='get_location_path')
location_id = serializers.IntegerField(source='get_location')
serial = serializers.CharField(source='get_serial')
po = serializers.CharField(source='get_po')
quantity = serializers.FloatField()
class Meta:
@ -247,6 +248,7 @@ class SalesOrderAllocationSerializer(InvenTreeModelSerializer):
'quantity',
'location_id',
'location_path',
'po',
'item',
]

View File

@ -55,7 +55,7 @@
<td>
{{ col }}
{% for duplicate in duplicates %}
{% if duplicate == col.name %}
{% if duplicate == col.value %}
<div class='alert alert-danger alert-block text-center' role='alert' style='padding:2px; margin-top:6px; margin-bottom:2px'>
<b>{% trans "Duplicate selection" %}</b>
</div>

View File

@ -9,13 +9,15 @@
{% inventree_title %} | {% trans "Sales Order" %}
{% endblock %}
{% block pre_content %}
{% if order.status == SalesOrderStatus.PENDING and not order.is_fully_allocated %}
<div class='alert alert-block alert-danger'>
{% trans "This SalesOrder has not been fully allocated" %}
{% block below_thumbnail %}
<div class='info-messages'>
{% if order.status == SalesOrderStatus.PENDING and not order.is_fully_allocated %}
<div class='alert alert-block alert-danger'>
{% trans "This Sales Order has not been fully allocated" %}
</div>
{% endif %}
</div>
{% endif %}
{% endblock %}
{% endblock %}
{% block thumbnail %}
<img class='part-thumb'

View File

@ -87,6 +87,9 @@ function showAllocationSubTable(index, row, element) {
return renderLink(row.location_path, `/stock/location/${row.location_id}/`);
},
},
{
field: 'po'
},
{
field: 'buttons',
title: '{% trans "Actions" %}',
@ -159,9 +162,12 @@ function showFulfilledSubTable(index, row, element) {
text = `{% trans "Quantity" %}: ${row.quantity}`;
}
return renderLink(text, `/stock/item/${row.pk}/`);
return renderLink(text, `/stock/item/${row.pk}/`);
},
}
},
{
field: 'po'
},
],
});
}
@ -271,6 +277,25 @@ $("#so-lines-table").inventreeTable({
field: 'notes',
title: '{% trans "Notes" %}',
},
{
field: 'po',
title: '{% trans "PO" %}',
formatter: function(value, row, index, field) {
var po_name = "";
if (row.allocated) {
row.allocations.forEach(function(allocation) {
if (allocation.po != po_name) {
if (po_name) {
po_name = "-";
} else {
po_name = allocation.po
}
}
})
}
return `<div>` + po_name + `</div>`;
}
},
{% if order.status == SalesOrderStatus.PENDING %}
{
field: 'buttons',

View File

@ -90,7 +90,7 @@ class SalesOrderDetail(InvenTreeRoleMixin, DetailView):
""" Detail view for a SalesOrder object """
context_object_name = 'order'
queryset = SalesOrder.objects.all().prefetch_related('lines')
queryset = SalesOrder.objects.all().prefetch_related('lines__allocations__item__purchase_order')
template_name = 'order/sales_order_detail.html'

View File

@ -12,13 +12,6 @@
<div class='panel panel-default panel-inventree'>
{% if part.variant_of %}
<div class='alert alert-info alert-block'>
{% object_link 'part-variants' part.variant_of.id part.variant_of.full_name as link %}
{% blocktrans %}This part is a variant of {{link}}{% endblocktrans %}
</div>
{% endif %}
<div class="row">
<div class="col-sm-6">
{% include "part/part_thumb.html" %}
@ -107,6 +100,15 @@
</table>
</div>
</div>
<div class='info-messages'>
{% if part.variant_of %}
<div class='alert alert-info alert-block'>
{% object_link 'part-variants' part.variant_of.id part.variant_of.full_name as link %}
{% blocktrans %}This part is a variant of {{link}}{% endblocktrans %}
</div>
{% endif %}
</div>
</div>
<div class="col-sm-6">
<table class="table table-striped">

View File

@ -1,8 +1,14 @@
# -*- coding: utf-8 -*-
""" This module provides template tags for extra functionality
over and above the built-in Django tags.
"""
import os
from django.utils.translation import ugettext_lazy as _
from django.conf import settings as djangosettings
from django import template
from django.urls import reverse
from django.utils.safestring import mark_safe
@ -68,6 +74,33 @@ def part_allocation_count(build, part, *args, **kwargs):
return InvenTree.helpers.decimal2string(build.getAllocatedQuantity(part))
@register.simple_tag()
def inventree_in_debug_mode(*args, **kwargs):
""" Return True if the server is running in DEBUG mode """
return djangosettings.DEBUG
@register.simple_tag()
def inventree_docker_mode(*args, **kwargs):
""" Return True if the server is running as a Docker image """
return djangosettings.DOCKER
@register.simple_tag()
def inventree_db_engine(*args, **kwargs):
""" Return the InvenTree database backend e.g. 'postgresql' """
db = djangosettings.DATABASES['default']
engine = db.get('ENGINE', _('Unknown database'))
engine = engine.replace('django.db.backends.', '')
return engine
@register.simple_tag()
def inventree_instance_name(*args, **kwargs):
""" Return the InstanceName associated with the current database """

View File

@ -1018,6 +1018,9 @@ class StockTrackingList(generics.ListAPIView):
for item in data:
deltas = item['deltas']
if not deltas:
deltas = {}
# Add location detail
if 'location' in deltas:
try:

View File

@ -1370,6 +1370,12 @@ class StockItem(MPTTModel):
if self.location:
s += ' @ {loc}'.format(loc=self.location.name)
if self.purchase_order:
s += " ({pre}{po})".format(
pre=helpers.getSetting("PURCHASEORDER_REFERENCE_PREFIX"),
po=self.purchase_order,
)
return s
@transaction.atomic

View File

@ -14,68 +14,6 @@
{% block pre_content %}
{% include 'stock/loc_link.html' with location=item.location %}
{% setting_object 'STOCK_OWNERSHIP_CONTROL' as owner_control %}
{% if owner_control.value == "True" %}
{% authorized_owners item.owner as owners %}
{% if not user in owners and not user.is_superuser %}
<div class='alert alert-block alert-info'>
{% trans "You are not in the list of owners of this item. This stock item cannot be edited." %}<br>
</div>
{% endif %}
{% endif %}
{% if item.is_building %}
<div class='alert alert-block alert-info'>
{% trans "This stock item is in production and cannot be edited." %}<br>
{% trans "Edit the stock item from the build view." %}<br>
{% if item.build %}
<a href="{% url 'build-detail' item.build.id %}">
<b>{{ item.build }}</b>
</a>
{% endif %}
</div>
{% endif %}
{% if item.hasRequiredTests and not item.passedAllRequiredTests %}
<div class='alert alert-block alert-danger'>
{% trans "This stock item has not passed all required tests" %}
</div>
{% endif %}
{% for allocation in item.sales_order_allocations.all %}
<div class='alert alert-block alert-info'>
{% object_link 'so-detail' allocation.line.order.id allocation.line.order as link %}
{% decimal allocation.quantity as qty %}
{% blocktrans %}This stock item is allocated to Sales Order {{ link }} (Quantity: {{ qty }}){% endblocktrans %}
</div>
{% endfor %}
{% for allocation in item.allocations.all %}
<div class='alert alert-block alert-info'>
{% object_link 'build-detail' allocation.build.id allocation.build %}
{% decimal allocation.quantity as qty %}
{% blocktrans %}This stock item is allocated to Build {{ link }} (Quantity: {{ qty }}){% endblocktrans %}
</div>
{% endfor %}
{% if item.serialized %}
<div class='alert alert-block alert-warning'>
{% trans "This stock item is serialized - it has a unique serial number and the quantity cannot be adjusted." %}
</div>
{% elif item.child_count > 0 %}
<div class='alert alert-block alert-warning'>
{% trans "This stock item cannot be deleted as it has child items" %}
</div>
{% elif item.delete_on_deplete and item.can_delete %}
<div class='alert alert-block alert-warning'>
{% trans "This stock item will be automatically deleted when all stock is depleted." %}
</div>
{% endif %}
{% endblock %}
{% block thumbnail %}
@ -221,6 +159,73 @@
{% endblock %}
{% block below_thumbnail %}
<div class='info-messages'>
{% setting_object 'STOCK_OWNERSHIP_CONTROL' as owner_control %}
{% if owner_control.value == "True" %}
{% authorized_owners item.owner as owners %}
{% if not user in owners and not user.is_superuser %}
<div class='alert alert-block alert-info'>
{% trans "You are not in the list of owners of this item. This stock item cannot be edited." %}<br>
</div>
{% endif %}
{% endif %}
{% if item.is_building %}
<div class='alert alert-block alert-info'>
{% trans "This stock item is in production and cannot be edited." %}<br>
{% trans "Edit the stock item from the build view." %}<br>
{% if item.build %}
<a href="{% url 'build-detail' item.build.id %}">
<b>{{ item.build }}</b>
</a>
{% endif %}
</div>
{% endif %}
{% if item.hasRequiredTests and not item.passedAllRequiredTests %}
<div class='alert alert-block alert-danger'>
{% trans "This stock item has not passed all required tests" %}
</div>
{% endif %}
{% for allocation in item.sales_order_allocations.all %}
<div class='alert alert-block alert-info'>
{% object_link 'so-detail' allocation.line.order.id allocation.line.order as link %}
{% decimal allocation.quantity as qty %}
{% blocktrans %}This stock item is allocated to Sales Order {{ link }} (Quantity: {{ qty }}){% endblocktrans %}
</div>
{% endfor %}
{% for allocation in item.allocations.all %}
<div class='alert alert-block alert-info'>
{% object_link 'build-detail' allocation.build.id allocation.build %}
{% decimal allocation.quantity as qty %}
{% blocktrans %}This stock item is allocated to Build {{ link }} (Quantity: {{ qty }}){% endblocktrans %}
</div>
{% endfor %}
{% if item.serialized %}
<div class='alert alert-block alert-warning'>
{% trans "This stock item is serialized - it has a unique serial number and the quantity cannot be adjusted." %}
</div>
{% elif item.child_count > 0 %}
<div class='alert alert-block alert-warning'>
{% trans "This stock item cannot be deleted as it has child items" %}
</div>
{% elif item.delete_on_deplete and item.can_delete %}
<div class='alert alert-block alert-warning'>
{% trans "This stock item will be automatically deleted when all stock is depleted." %}
</div>
{% endif %}
</div>
{% endblock %}
{% block page_details %}
<h4>{% trans "Stock Item Details" %}</h4>
<table class="table table-striped">

View File

@ -20,6 +20,7 @@
{% include "InvenTree/settings/setting.html" with key="INVENTREE_BASE_URL" icon="fa-globe" %}
{% include "InvenTree/settings/setting.html" with key="INVENTREE_COMPANY_NAME" icon="fa-building" %}
{% include "InvenTree/settings/setting.html" with key="INVENTREE_DEFAULT_CURRENCY" icon="fa-dollar-sign" %}
{% include "InvenTree/settings/setting.html" with key="INVENTREE_FIXER_API_KEY" icon="fa-key" %}
{% include "InvenTree/settings/setting.html" with key="INVENTREE_DOWNLOAD_FROM_URL" icon="fa-cloud-download-alt" %}
</tbody>
</table>

View File

@ -89,6 +89,15 @@ function setFieldOptions(fieldName, optionList, options={}) {
}
function clearFieldOptions(fieldName) {
/**
* Clear (emtpy) the options list for a particular field
*/
setFieldOptions(fieldName, []);
}
function reloadFieldOptions(fieldName, options) {
/* Reload the options for a given field,
* using an AJAX request.

View File

@ -1017,6 +1017,11 @@ function loadStockTrackingTable(table, options) {
formatter: function(details, row, index, field) {
var html = `<table class='table table-condensed' id='tracking-table-${row.pk}'>`;
if (!details) {
html += '</table>';
return html;
}
// Location information
if (details.location) {
@ -1218,6 +1223,17 @@ function createNewStockItem(options) {
field: 'part',
action: function(value) {
if (!value) {
// No part chosen
clearFieldOptions('supplier_part');
enableField('serial_numbers', false);
enableField('purchase_price_0', false);
enableField('purchase_price_1', false);
return;
}
// Reload options for supplier part
reloadFieldOptions(
'supplier_part',
@ -1243,6 +1259,9 @@ function createNewStockItem(options) {
enableField('serial_numbers', response.trackable);
clearField('serial_numbers');
enableField('purchase_price_0', response.purchaseable);
enableField('purchase_price_1', response.purchaseable);
// Populate the expiry date
if (response.default_expiry <= 0) {
// No expiry date

View File

@ -13,6 +13,27 @@
<td>{% trans "Instance Name" %}</td>
<td>{% inventree_instance_name %}</td>
</tr>
<tr>
<td><span class='fas fa-database'></span></td>
<td>{% trans "Database" %}</td>
<td>{% inventree_db_engine %}</td>
</tr>
{% inventree_in_debug_mode as debug_mode %}
{% if debug_mode %}
<tr>
<td><span class='fas fa-laptop-code'></span></td>
<td>{% trans "Debug Mode" %}</td>
<td>{% trans "Server is running in debug mode" %}</td>
</tr>
{% endif %}
{% inventree_docker_mode as docker_mode %}
{% if docker_mode %}
<tr>
<td><span class='fab fa-docker'></span></td>
<td>{% trans "Docker Mode" %}</td>
<td>{% trans "Server is deployed using docker" %}</td>
</tr>
{% endif %}
{% if user.is_staff %}
<tr>
<td><span class='fas fa-server'></span></td>

View File

@ -27,6 +27,8 @@
<!-- Data next to image goes here -->
{% endblock %}
</div>
{% block below_thumbnail %}
{% endblock %}
</div>
<div class='col-sm-6'>
{% block page_details %}

View File

@ -2,4 +2,7 @@
InvenTree-Version: {% inventree_version %}
Django Version: {% django_version %}
{% inventree_commit_hash as hash %}{% if hash %}Commit Hash: {{ hash }}{% endif %}
{% inventree_commit_date as commit_date %}{% if commit_date %}Commit Date: {{ commit_date }}{% endif %}
{% inventree_commit_date as commit_date %}{% if commit_date %}Commit Date: {{ commit_date }}{% endif %}
Database: {% inventree_db_engine %}
Debug-Mode: {% inventree_in_debug_mode %}
Deployed using Docker: {% inventree_docker_mode %}

View File

@ -1,3 +1,6 @@
"commit_message": "Fix: New translations %original_file_name% from Crowdin"
"append_commit_message": false
files:
- source: /InvenTree/locale/en/LC_MESSAGES/django.po
translation: /InvenTree/locale/%two_letters_code%/LC_MESSAGES/%original_file_name%

View File

@ -14,6 +14,7 @@ ENV INVENTREE_REPO="${repository}"
ENV INVENTREE_BRANCH="${branch}"
ENV INVENTREE_LOG_LEVEL="INFO"
ENV INVENTREE_DOCKER="true"
# InvenTree paths
ENV INVENTREE_SRC_DIR="${INVENTREE_HOME}/src"