mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Merge remote-tracking branch 'inventree/master'
This commit is contained in:
commit
40037d826d
@ -7,7 +7,7 @@ python:
|
||||
|
||||
addons:
|
||||
apt-packages:
|
||||
-sqlite3
|
||||
- sqlite3
|
||||
|
||||
before_install:
|
||||
- sudo apt-get update
|
||||
|
@ -17,6 +17,8 @@ import logging
|
||||
import tempfile
|
||||
import yaml
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
|
||||
@ -101,6 +103,8 @@ INSTALLED_APPS = [
|
||||
'django_cleanup', # Automatically delete orphaned MEDIA files
|
||||
'qr_code', # Generate QR codes
|
||||
'mptt', # Modified Preorder Tree Traversal
|
||||
'markdownx', # Markdown editing
|
||||
'markdownify', # Markdown template rendering
|
||||
]
|
||||
|
||||
LOGGING = {
|
||||
@ -161,6 +165,37 @@ REST_FRAMEWORK = {
|
||||
|
||||
WSGI_APPLICATION = 'InvenTree.wsgi.application'
|
||||
|
||||
# Markdownx configuration
|
||||
# Ref: https://neutronx.github.io/django-markdownx/customization/
|
||||
MARKDOWNX_MEDIA_PATH = datetime.now().strftime('markdownx/%Y/%m/%d')
|
||||
|
||||
# Markdownify configuration
|
||||
# Ref: https://django-markdownify.readthedocs.io/en/latest/settings.html
|
||||
|
||||
MARKDOWNIFY_WHITELIST_TAGS = [
|
||||
'a',
|
||||
'abbr',
|
||||
'b',
|
||||
'blockquote',
|
||||
'em',
|
||||
'h1', 'h2', 'h3',
|
||||
'i',
|
||||
'img',
|
||||
'li',
|
||||
'ol',
|
||||
'p',
|
||||
'strong',
|
||||
'ul'
|
||||
]
|
||||
|
||||
MARKDOWNIFY_WHITELIST_ATTRS = [
|
||||
'href',
|
||||
'src',
|
||||
'alt',
|
||||
]
|
||||
|
||||
MARKDOWNIFY_BLEACH = True
|
||||
|
||||
DATABASES = {}
|
||||
|
||||
"""
|
||||
|
@ -5,6 +5,29 @@
|
||||
--basic-color: #333;
|
||||
}
|
||||
|
||||
.markdownx .row {
|
||||
margin: 5px;
|
||||
padding: 5px;
|
||||
border: 1px solid #cce;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.markdownx-editor {
|
||||
width: 100%;
|
||||
border: 1px solid #cce;
|
||||
border-radius: 3px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.panel-content {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.markdownx-preview {
|
||||
border: 1px solid #cce;
|
||||
border-radius: 3px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.qr-code {
|
||||
max-width: 400px;
|
||||
@ -305,6 +328,12 @@
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
input[type="submit"] {
|
||||
color: #333;
|
||||
background-color: #e6e6e6;
|
||||
border-color: #adadad;
|
||||
}
|
||||
|
||||
.modal textarea {
|
||||
width: 100%;
|
||||
}
|
||||
|
@ -100,6 +100,8 @@ urlpatterns = [
|
||||
|
||||
url(r'^api/', include(apipatterns)),
|
||||
url(r'^api-doc/', include_docs_urls(title='InvenTree API')),
|
||||
|
||||
url(r'^markdownx/', include('markdownx.urls')),
|
||||
]
|
||||
|
||||
# Static file access
|
||||
|
@ -4,7 +4,7 @@ Provides information on the current InvenTree version
|
||||
|
||||
import subprocess
|
||||
|
||||
INVENTREE_SW_VERSION = "0.0.7"
|
||||
INVENTREE_SW_VERSION = "0.0.8"
|
||||
|
||||
|
||||
def inventreeVersion():
|
||||
@ -15,6 +15,12 @@ def inventreeVersion():
|
||||
def inventreeCommitHash():
|
||||
""" Returns the git commit hash for the running codebase """
|
||||
|
||||
commit = str(subprocess.check_output('git rev-parse --short HEAD'.split()), 'utf-8').strip()
|
||||
return str(subprocess.check_output('git rev-parse --short HEAD'.split()), 'utf-8').strip()
|
||||
|
||||
return commit
|
||||
|
||||
def inventreeCommitDate():
|
||||
""" Returns the git commit date for the running codebase """
|
||||
|
||||
d = str(subprocess.check_output('git show -s --format=%ci'.split()), 'utf-8').strip()
|
||||
|
||||
return d.split(' ')[0]
|
||||
|
@ -26,7 +26,6 @@ class EditBuildForm(HelperForm):
|
||||
'take_from',
|
||||
'batch',
|
||||
'URL',
|
||||
'notes',
|
||||
]
|
||||
|
||||
|
||||
|
19
InvenTree/build/migrations/0008_auto_20200201_1247.py
Normal file
19
InvenTree/build/migrations/0008_auto_20200201_1247.py
Normal file
@ -0,0 +1,19 @@
|
||||
# Generated by Django 2.2.9 on 2020-02-01 12:47
|
||||
|
||||
from django.db import migrations
|
||||
import markdownx.models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('build', '0007_auto_20191118_2321'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='build',
|
||||
name='notes',
|
||||
field=markdownx.models.MarkdownxField(blank=True, help_text='Extra build notes'),
|
||||
),
|
||||
]
|
@ -16,6 +16,8 @@ from django.db import models, transaction
|
||||
from django.db.models import Sum
|
||||
from django.core.validators import MinValueValidator
|
||||
|
||||
from markdownx.models import MarkdownxField
|
||||
|
||||
from InvenTree.status_codes import BuildStatus
|
||||
from InvenTree.fields import InvenTreeURLField
|
||||
|
||||
@ -92,7 +94,7 @@ class Build(models.Model):
|
||||
|
||||
URL = InvenTreeURLField(blank=True, help_text=_('Link to external URL'))
|
||||
|
||||
notes = models.TextField(blank=True, help_text=_('Extra build notes'))
|
||||
notes = MarkdownxField(blank=True, help_text=_('Extra build notes'))
|
||||
|
||||
@transaction.atomic
|
||||
def cancelBuild(self, user):
|
||||
|
@ -1,74 +1,67 @@
|
||||
{% extends "build/build_base.html" %}
|
||||
{% load static %}
|
||||
|
||||
{% load i18n %}
|
||||
{% block details %}
|
||||
|
||||
{% include "build/tabs.html" with tab='details' %}
|
||||
|
||||
<h4>Build Details</h4>
|
||||
<h4>{% trans "Build Details" %}</h4>
|
||||
|
||||
<hr>
|
||||
|
||||
<table class='table table-striped'>
|
||||
<tr>
|
||||
<td>Title</td><td>{{ build.title }}</td>
|
||||
<td>{% trans "Title" %}</td><td>{{ build.title }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Part</td><td><a href="{% url 'part-build' build.part.id %}">{{ build.part.full_name }}</a></td>
|
||||
<td>{% trans "Part" %}</td><td><a href="{% url 'part-build' build.part.id %}">{{ build.part.full_name }}</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Quantity</td><td>{{ build.quantity }}</td>
|
||||
<td>{% trans "Quantity" %}</td><td>{{ build.quantity }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Stock Source</td>
|
||||
<td>{% trans "Stock Source" %}</td>
|
||||
<td>
|
||||
{% if build.take_from %}
|
||||
<a href="{% url 'stock-location-detail' build.take_from.id %}">{{ build.take_from }}</a>
|
||||
{% else %}
|
||||
Stock can be taken from any available location.
|
||||
{% trans "Stock can be taken from any available location." %}
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Status</td><td>{% include "build_status.html" with build=build %}</td>
|
||||
<td>{% trans "Status" %}</td><td>{% include "build_status.html" with build=build %}</td>
|
||||
</tr>
|
||||
{% if build.batch %}
|
||||
<tr>
|
||||
<td>Batch</td><td>{{ build.batch }}</td>
|
||||
<td>{% trans "Batch" %}</td><td>{{ build.batch }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if build.URL %}
|
||||
<tr>
|
||||
<td>URL</td><td><a href="{{ build.URL }}">{{ build.URL }}</a></td>
|
||||
<td>{% trans "URL" %}</td><td><a href="{{ build.URL }}">{{ build.URL }}</a></td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
<tr>
|
||||
<td>Created</td><td>{{ build.creation_date }}</td>
|
||||
<td>{% trans "Created" %}</td><td>{{ build.creation_date }}</td>
|
||||
</tr>
|
||||
{% if build.is_active %}
|
||||
<tr>
|
||||
<td>Enough Parts?</td>
|
||||
<td>{% trans "Enough Parts?" %}</td>
|
||||
<td>
|
||||
{% if build.can_build %}
|
||||
Yes
|
||||
{% trans "Yes" %}
|
||||
{% else %}
|
||||
No
|
||||
{% trans "No" %}
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if build.completion_date %}
|
||||
<tr>
|
||||
<td>Completed</td><td>{{ build.completion_date }}{% if build.completed_by %}<span class='badge'>{{ build.completed_by }}</span>{% endif %}</td>
|
||||
<td>{% trans "Completed" %}</td><td>{{ build.completion_date }}{% if build.completed_by %}<span class='badge'>{{ build.completed_by }}</span>{% endif %}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</table>
|
||||
|
||||
{% if build.notes %}
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading"><b>Notes</b></div>
|
||||
<div class="panel-body">{{ build.notes }}</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
|
56
InvenTree/build/templates/build/notes.html
Normal file
56
InvenTree/build/templates/build/notes.html
Normal file
@ -0,0 +1,56 @@
|
||||
{% extends "build/build_base.html" %}
|
||||
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
{% load markdownify %}
|
||||
|
||||
{% block details %}
|
||||
|
||||
{% include "build/tabs.html" with tab='notes' %}
|
||||
|
||||
|
||||
{% if editing %}
|
||||
<h4>{% trans "Build Notes" %}</h4>
|
||||
<hr>
|
||||
<form method='POST'>
|
||||
{% csrf_token %}
|
||||
|
||||
{{ form }}
|
||||
<hr>
|
||||
<input type="submit" value='{% trans "Save" %}'/>
|
||||
|
||||
</form>
|
||||
|
||||
{{ form.media }}
|
||||
|
||||
{% else %}
|
||||
|
||||
<div class='row'>
|
||||
<div class='col-sm-6'>
|
||||
<h4>{% trans "Build Notes" %}</h4>
|
||||
</div>
|
||||
<div class='col-sm-6'>
|
||||
<button title='{% trans "Edit notes" %}' class='btn btn-default btn-glyph float-right' id='edit-notes'><span class='glyphicon glyphicon-edit'></span></button>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
<div class='panel panel-default'>
|
||||
<div class='panel-content'>
|
||||
{{ build.notes | markdownify }}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block js_ready %}
|
||||
{{ block.super }}
|
||||
|
||||
{% if editing %}
|
||||
{% else %}
|
||||
$("#edit-notes").click(function() {
|
||||
location.href = "{% url 'build-notes' build.id %}?edit=1";
|
||||
});
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
@ -1,8 +1,13 @@
|
||||
{% load i18n %}
|
||||
|
||||
<ul class='nav nav-tabs'>
|
||||
<li{% if tab == 'details' %} class='active'{% endif %}>
|
||||
<a href="{% url 'build-detail' build.id %}">Details</a>
|
||||
<a href="{% url 'build-detail' build.id %}">{% trans "Details" %}</a>
|
||||
</li>
|
||||
<li{% if tab == 'notes' %} class='active'{% endif %}>
|
||||
<a href="{% url 'build-notes' build.id %}">{% trans "Notes" %}{% if build.notes %} <span class='glyphicon glyphicon-small glyphicon-info-sign'></span>{% endif %}</a>
|
||||
</li>
|
||||
<li{% if tab == 'allocate' %} class='active'{% endif %}>
|
||||
<a href="{% url 'build-allocate' build.id %}">Assign Parts</a>
|
||||
<a href="{% url 'build-allocate' build.id %}">{% trans "Assign Parts" %}</a>
|
||||
</li>
|
||||
</ul>
|
@ -25,6 +25,7 @@ build_detail_urls = [
|
||||
url(r'^auto-allocate/?', views.BuildAutoAllocate.as_view(), name='build-auto-allocate'),
|
||||
url(r'^unallocate/', views.BuildUnallocate.as_view(), name='build-unallocate'),
|
||||
|
||||
url(r'^notes/', views.BuildNotes.as_view(), name='build-notes'),
|
||||
url(r'^.*$', views.BuildDetail.as_view(), name='build-detail'),
|
||||
]
|
||||
|
||||
|
@ -7,8 +7,9 @@ from __future__ import unicode_literals
|
||||
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.views.generic import DetailView, ListView
|
||||
from django.views.generic import DetailView, ListView, UpdateView
|
||||
from django.forms import HiddenInput
|
||||
from django.urls import reverse
|
||||
|
||||
from part.models import Part
|
||||
from .models import Build, BuildItem
|
||||
@ -309,6 +310,28 @@ class BuildComplete(AjaxUpdateView):
|
||||
}
|
||||
|
||||
|
||||
class BuildNotes(UpdateView):
|
||||
""" View for editing the 'notes' field of a Build object.
|
||||
"""
|
||||
|
||||
context_object_name = 'build'
|
||||
template_name = 'build/notes.html'
|
||||
model = Build
|
||||
|
||||
fields = ['notes']
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse('build-notes', kwargs={'pk': self.get_object().id})
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
|
||||
ctx = super().get_context_data(**kwargs)
|
||||
|
||||
ctx['editing'] = str2bool(self.request.GET.get('edit', ''))
|
||||
|
||||
return ctx
|
||||
|
||||
|
||||
class BuildDetail(DetailView):
|
||||
""" Detail view of a single Build object. """
|
||||
model = Build
|
||||
|
@ -27,7 +27,6 @@ class EditCompanyForm(HelperForm):
|
||||
'contact',
|
||||
'is_customer',
|
||||
'is_supplier',
|
||||
'notes'
|
||||
]
|
||||
|
||||
|
||||
|
19
InvenTree/company/migrations/0010_auto_20200201_1231.py
Normal file
19
InvenTree/company/migrations/0010_auto_20200201_1231.py
Normal file
@ -0,0 +1,19 @@
|
||||
# Generated by Django 2.2.9 on 2020-02-01 12:31
|
||||
|
||||
from django.db import migrations
|
||||
import markdownx.models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('company', '0009_auto_20191118_2323'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='company',
|
||||
name='notes',
|
||||
field=markdownx.models.MarkdownxField(blank=True),
|
||||
),
|
||||
]
|
@ -10,6 +10,7 @@ import os
|
||||
import math
|
||||
from decimal import Decimal
|
||||
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.core.validators import MinValueValidator
|
||||
from django.db import models
|
||||
from django.db.models import Sum
|
||||
@ -18,6 +19,8 @@ from django.apps import apps
|
||||
from django.urls import reverse
|
||||
from django.conf import settings
|
||||
|
||||
from markdownx.models import MarkdownxField
|
||||
|
||||
from InvenTree.fields import InvenTreeURLField
|
||||
from InvenTree.status_codes import OrderStatus
|
||||
from common.models import Currency
|
||||
@ -68,32 +71,32 @@ class Company(models.Model):
|
||||
"""
|
||||
|
||||
name = models.CharField(max_length=100, blank=False, unique=True,
|
||||
help_text='Company name')
|
||||
help_text=_('Company name'))
|
||||
|
||||
description = models.CharField(max_length=500, help_text='Description of the company')
|
||||
description = models.CharField(max_length=500, help_text=_('Description of the company'))
|
||||
|
||||
website = models.URLField(blank=True, help_text='Company website URL')
|
||||
website = models.URLField(blank=True, help_text=_('Company website URL'))
|
||||
|
||||
address = models.CharField(max_length=200,
|
||||
blank=True, help_text='Company address')
|
||||
blank=True, help_text=_('Company address'))
|
||||
|
||||
phone = models.CharField(max_length=50,
|
||||
blank=True, help_text='Contact phone number')
|
||||
blank=True, help_text=_('Contact phone number'))
|
||||
|
||||
email = models.EmailField(blank=True, help_text='Contact email address')
|
||||
email = models.EmailField(blank=True, help_text=_('Contact email address'))
|
||||
|
||||
contact = models.CharField(max_length=100,
|
||||
blank=True, help_text='Point of contact')
|
||||
blank=True, help_text=_('Point of contact'))
|
||||
|
||||
URL = InvenTreeURLField(blank=True, help_text='Link to external company information')
|
||||
URL = InvenTreeURLField(blank=True, help_text=_('Link to external company information'))
|
||||
|
||||
image = models.ImageField(upload_to=rename_company_image, max_length=255, null=True, blank=True)
|
||||
|
||||
notes = models.TextField(blank=True)
|
||||
notes = MarkdownxField(blank=True)
|
||||
|
||||
is_customer = models.BooleanField(default=False, help_text='Do you sell items to this company?')
|
||||
is_customer = models.BooleanField(default=False, help_text=_('Do you sell items to this company?'))
|
||||
|
||||
is_supplier = models.BooleanField(default=True, help_text='Do you purchase items from this company?')
|
||||
is_supplier = models.BooleanField(default=True, help_text=_('Do you purchase items from this company?'))
|
||||
|
||||
def __str__(self):
|
||||
""" Get string representation of a Company """
|
||||
@ -223,32 +226,32 @@ class SupplierPart(models.Model):
|
||||
'purchaseable': True,
|
||||
'is_template': False,
|
||||
},
|
||||
help_text='Select part',
|
||||
help_text=_('Select part'),
|
||||
)
|
||||
|
||||
supplier = models.ForeignKey(Company, on_delete=models.CASCADE,
|
||||
related_name='parts',
|
||||
limit_choices_to={'is_supplier': True},
|
||||
help_text='Select supplier',
|
||||
help_text=_('Select supplier'),
|
||||
)
|
||||
|
||||
SKU = models.CharField(max_length=100, help_text='Supplier stock keeping unit')
|
||||
SKU = models.CharField(max_length=100, help_text=_('Supplier stock keeping unit'))
|
||||
|
||||
manufacturer = models.CharField(max_length=100, blank=True, help_text='Manufacturer')
|
||||
manufacturer = models.CharField(max_length=100, blank=True, help_text=_('Manufacturer'))
|
||||
|
||||
MPN = models.CharField(max_length=100, blank=True, help_text='Manufacturer part number')
|
||||
MPN = models.CharField(max_length=100, blank=True, help_text=_('Manufacturer part number'))
|
||||
|
||||
URL = InvenTreeURLField(blank=True, help_text='URL for external supplier part link')
|
||||
URL = InvenTreeURLField(blank=True, help_text=_('URL for external supplier part link'))
|
||||
|
||||
description = models.CharField(max_length=250, blank=True, help_text='Supplier part description')
|
||||
description = models.CharField(max_length=250, blank=True, help_text=_('Supplier part description'))
|
||||
|
||||
note = models.CharField(max_length=100, blank=True, help_text='Notes')
|
||||
note = models.CharField(max_length=100, blank=True, help_text=_('Notes'))
|
||||
|
||||
base_cost = models.DecimalField(max_digits=10, decimal_places=3, default=0, validators=[MinValueValidator(0)], help_text='Minimum charge (e.g. stocking fee)')
|
||||
base_cost = models.DecimalField(max_digits=10, decimal_places=3, default=0, validators=[MinValueValidator(0)], help_text=_('Minimum charge (e.g. stocking fee)'))
|
||||
|
||||
packaging = models.CharField(max_length=50, blank=True, help_text='Part packaging')
|
||||
packaging = models.CharField(max_length=50, blank=True, help_text=_('Part packaging'))
|
||||
|
||||
multiple = models.PositiveIntegerField(default=1, validators=[MinValueValidator(1)], help_text='Order multiple')
|
||||
multiple = models.PositiveIntegerField(default=1, validators=[MinValueValidator(1)], help_text=('Order multiple'))
|
||||
|
||||
# TODO - Reimplement lead-time as a charfield with special validation (pattern matching).
|
||||
# lead_time = models.DurationField(blank=True, null=True)
|
||||
|
@ -18,13 +18,6 @@
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
{% if company.notes %}
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading"><b>Notes</b></div>
|
||||
<div class="panel-body">{{ company.notes }}</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
{% block js_ready %}
|
||||
{{ block.super }}
|
||||
|
53
InvenTree/company/templates/company/notes.html
Normal file
53
InvenTree/company/templates/company/notes.html
Normal file
@ -0,0 +1,53 @@
|
||||
{% extends "company/company_base.html" %}
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
{% block details %}
|
||||
{% load markdownify %}
|
||||
|
||||
{% include 'company/tabs.html' with tab='notes' %}
|
||||
|
||||
{% if editing %}
|
||||
<h4>{% trans "Company Notes" %}</h4>
|
||||
<hr>
|
||||
<form method='POST'>
|
||||
{% csrf_token %}
|
||||
|
||||
{{ form }}
|
||||
<hr>
|
||||
<input type="submit" value='{% trans "Save" %}'/>
|
||||
|
||||
</form>
|
||||
|
||||
{{ form.media }}
|
||||
|
||||
{% else %}
|
||||
|
||||
<div class='row'>
|
||||
<div class='col-sm-6'>
|
||||
<h4>{% trans "Company Notes" %}</h4>
|
||||
</div>
|
||||
<div class='col-sm-6'>
|
||||
<button title='{% trans "Edit notes" %}' class='btn btn-default btn-glyph float-right' id='edit-notes'><span class='glyphicon glyphicon-edit'></span></button>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
<div class='panel panel-default'>
|
||||
<div class='panel-content'>
|
||||
{{ company.notes | markdownify }}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block js_ready %}
|
||||
{{ block.super }}
|
||||
|
||||
{% if editing %}
|
||||
{% else %}
|
||||
$("#edit-notes").click(function() {
|
||||
location.href = "{% url 'company-notes' company.id %}?edit=1";
|
||||
});
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
@ -1,23 +1,28 @@
|
||||
{% load i18n %}
|
||||
|
||||
<ul class='nav nav-tabs'>
|
||||
<li{% if tab == 'details' %} class='active'{% endif %}>
|
||||
<a href="{% url 'company-detail' company.id %}">Details</a>
|
||||
<a href="{% url 'company-detail' company.id %}">{% trans "Details" %}</a>
|
||||
</li>
|
||||
{% if company.is_supplier %}
|
||||
<li{% if tab == 'parts' %} class='active'{% endif %}>
|
||||
<a href="{% url 'company-detail-parts' company.id %}">Supplier Parts <span class='badge'>{{ company.part_count }}</span></a>
|
||||
<a href="{% url 'company-detail-parts' company.id %}">{% trans "Supplier Parts" %} <span class='badge'>{{ company.part_count }}</span></a>
|
||||
</li>
|
||||
<li{% if tab == 'stock' %} class='active'{% endif %}>
|
||||
<a href="{% url 'company-detail-stock' company.id %}">Stock <span class='badge'>{{ company.stock_count }}</a>
|
||||
<a href="{% url 'company-detail-stock' company.id %}">{% trans "Stock" %} <span class='badge'>{{ company.stock_count }}</a>
|
||||
</li>
|
||||
<li{% if tab == 'po' %} class='active'{% endif %}>
|
||||
<a href="{% url 'company-detail-purchase-orders' company.id %}">Purchase Orders <span class='badge'>{{ company.purchase_orders.count }}</span></a>
|
||||
<a href="{% url 'company-detail-purchase-orders' company.id %}">{% trans "Purchase Orders" %} <span class='badge'>{{ company.purchase_orders.count }}</span></a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if company.is_customer %}
|
||||
{% if 0 %}
|
||||
<li{% if tab == 'co' %} class='active'{% endif %}>
|
||||
<a href="#">Sales Orders</a>
|
||||
<a href="#">{% trans "Sales Orders" %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
<li{% if tab == 'notes' %} class='active'{% endif %}>
|
||||
<a href="{% url 'company-notes' company.id %}">{% trans "Notes" %}{% if company.notes %} <span class='glyphicon glyphicon-small glyphicon-info-sign'></span>{% endif %}</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
@ -18,6 +18,7 @@ company_detail_urls = [
|
||||
url(r'parts/?', views.CompanyDetail.as_view(template_name='company/detail_part.html'), name='company-detail-parts'),
|
||||
url(r'stock/?', views.CompanyDetail.as_view(template_name='company/detail_stock.html'), name='company-detail-stock'),
|
||||
url(r'purchase-orders/?', views.CompanyDetail.as_view(template_name='company/detail_purchase_orders.html'), name='company-detail-purchase-orders'),
|
||||
url(r'notes/?', views.CompanyNotes.as_view(), name='company-notes'),
|
||||
|
||||
url(r'thumbnail/?', views.CompanyImage.as_view(), name='company-image'),
|
||||
|
||||
|
@ -6,8 +6,9 @@ Django views for interacting with Company app
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.views.generic import DetailView, ListView
|
||||
from django.views.generic import DetailView, ListView, UpdateView
|
||||
|
||||
from django.urls import reverse
|
||||
from django.forms import HiddenInput
|
||||
|
||||
from InvenTree.views import AjaxCreateView, AjaxUpdateView, AjaxDeleteView
|
||||
@ -52,6 +53,28 @@ class CompanyIndex(ListView):
|
||||
return queryset
|
||||
|
||||
|
||||
class CompanyNotes(UpdateView):
|
||||
""" View for editing the 'notes' field of a Company object.
|
||||
"""
|
||||
|
||||
context_object_name = 'company'
|
||||
template_name = 'company/notes.html'
|
||||
model = Company
|
||||
|
||||
fields = ['notes']
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse('company-notes', kwargs={'pk': self.get_object().id})
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
|
||||
ctx = super().get_context_data(**kwargs)
|
||||
|
||||
ctx['editing'] = str2bool(self.request.GET.get('edit', ''))
|
||||
|
||||
return ctx
|
||||
|
||||
|
||||
class CompanyDetail(DetailView):
|
||||
""" Detail view for Company object """
|
||||
context_obect_name = 'company'
|
||||
|
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
19
InvenTree/order/migrations/0015_auto_20200201_2346.py
Normal file
19
InvenTree/order/migrations/0015_auto_20200201_2346.py
Normal file
@ -0,0 +1,19 @@
|
||||
# Generated by Django 2.2.9 on 2020-02-01 23:46
|
||||
|
||||
from django.db import migrations
|
||||
import markdownx.models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('order', '0014_auto_20191118_2328'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='purchaseorder',
|
||||
name='notes',
|
||||
field=markdownx.models.MarkdownxField(blank=True, help_text='Order notes'),
|
||||
),
|
||||
]
|
@ -12,6 +12,8 @@ from django.contrib.auth.models import User
|
||||
from django.urls import reverse
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from markdownx.models import MarkdownxField
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from stock.models import StockItem
|
||||
@ -81,7 +83,7 @@ class Order(models.Model):
|
||||
|
||||
complete_date = models.DateField(blank=True, null=True)
|
||||
|
||||
notes = models.TextField(blank=True, help_text=_('Order notes'))
|
||||
notes = MarkdownxField(blank=True, help_text=_('Order notes'))
|
||||
|
||||
def place_order(self):
|
||||
""" Marks the order as PLACED. Order must be currently PENDING. """
|
||||
|
132
InvenTree/order/templates/order/order_base.html
Normal file
132
InvenTree/order/templates/order/order_base.html
Normal file
@ -0,0 +1,132 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
{% load inventree_extras %}
|
||||
|
||||
{% block page_title %}
|
||||
InvenTree | {{ order }}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class='row'>
|
||||
<div class='col-sm-6'>
|
||||
<div class='media'>
|
||||
<div class='media-left'>
|
||||
<img class='part-thumb'
|
||||
{% if order.supplier.image %}
|
||||
src="{{ order.supplier.image.url }}"
|
||||
{% else %}
|
||||
src="{% static 'img/blank_image.png' %}"
|
||||
{% endif %}
|
||||
/>
|
||||
</div>
|
||||
<div class='media-body'>
|
||||
<h4>{{ order }}</h4>
|
||||
<p>{{ order.description }}</p>
|
||||
{% if order.URL %}
|
||||
<a href="{{ order.URL }}">{{ order.URL }}</a>
|
||||
{% endif %}
|
||||
<p>
|
||||
<div class='btn-row'>
|
||||
<div class='btn-group'>
|
||||
<button type='button' class='btn btn-default btn-glyph' id='edit-order' title='Edit order information'>
|
||||
<span class='glyphicon glyphicon-edit'></span>
|
||||
</button>
|
||||
<button type='button' class='btn btn-default btn-glyph' id='export-order' title='Export order to file'>
|
||||
<span class='glyphicon glyphicon-download-alt'></span>
|
||||
</button>
|
||||
{% if order.status == OrderStatus.PENDING and order.lines.count > 0 %}
|
||||
<button type='button' class='btn btn-default btn-glyph' id='place-order' title='Place order'>
|
||||
<span class='glyphicon glyphicon-send'></span>
|
||||
</button>
|
||||
{% elif order.status == OrderStatus.PLACED %}
|
||||
<button type='button' class='btn btn-default btn-glyph' id='receive-order' title='Receive items'>
|
||||
<span class='glyphicon glyphicon-check'></span>
|
||||
</button>
|
||||
<button type='button' class='btn btn-default btn-glyph' id='complete-order' title='Mark order as complete'>
|
||||
<span class='glyphicon glyphicon-ok'></span>
|
||||
</button>
|
||||
{% endif %}
|
||||
{% if order.status == OrderStatus.PENDING or order.status == OrderStatus.PLACED %}
|
||||
<button type='button' class='btn btn-default btn-glyph' id='cancel-order' title='Cancel order'>
|
||||
<span class='glyphicon glyphicon-remove'></span>
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class='col-sm-6'>
|
||||
<h4>{% trans "Purchase Order Details" %}</h4>
|
||||
<table class='table'>
|
||||
<tr>
|
||||
<td>{% trans "Supplier" %}</td>
|
||||
<td><a href="{% url 'company-detail' order.supplier.id %}">{{ order.supplier }}</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans "Status" %}</td>
|
||||
<td>{% include "order/order_status.html" %}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans "Created" %}</td>
|
||||
<td>{{ order.creation_date }}<span class='badge'>{{ order.created_by }}</span></td>
|
||||
</tr>
|
||||
{% if order.issue_date %}
|
||||
<tr>
|
||||
<td>{% trans "Issued" %}</td>
|
||||
<td>{{ order.issue_date }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if order.status == OrderStatus.COMPLETE %}
|
||||
<tr>
|
||||
<td>{% trans "Received" %}</td>
|
||||
<td>{{ order.complete_date }}<span class='badge'>{{ order.received_by }}</span></td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
<div class='container-fluid'>
|
||||
{% block details %}
|
||||
|
||||
<!-- Specific order details to go here -->
|
||||
|
||||
{% endblock %}
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block js_ready %}
|
||||
{{ block.super }}
|
||||
|
||||
{% if order.status == OrderStatus.PENDING and order.lines.count > 0 %}
|
||||
$("#place-order").click(function() {
|
||||
launchModalForm("{% url 'purchase-order-issue' order.id %}",
|
||||
{
|
||||
reload: true,
|
||||
});
|
||||
});
|
||||
{% endif %}
|
||||
|
||||
$("#edit-order").click(function() {
|
||||
launchModalForm("{% url 'purchase-order-edit' order.id %}",
|
||||
{
|
||||
reload: true,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
$("#cancel-order").click(function() {
|
||||
launchModalForm("{% url 'purchase-order-cancel' order.id %}", {
|
||||
reload: true,
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
{% endblock %}
|
57
InvenTree/order/templates/order/order_notes.html
Normal file
57
InvenTree/order/templates/order/order_notes.html
Normal file
@ -0,0 +1,57 @@
|
||||
{% extends "order/order_base.html" %}
|
||||
|
||||
{% load inventree_extras %}
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
{% load markdownify %}
|
||||
|
||||
{% block details %}
|
||||
|
||||
{% include 'order/tabs.html' with tab='notes' %}
|
||||
|
||||
{% if editing %}
|
||||
<h4>{% trans "Order Notes" %}</h4>
|
||||
<hr>
|
||||
|
||||
<form method='POST'>
|
||||
{% csrf_token %}
|
||||
|
||||
{{ form }}
|
||||
<hr>
|
||||
<input type='submit' value='{% trans "Save" %}'/>
|
||||
</form>
|
||||
|
||||
{{ form.media }}
|
||||
|
||||
{% else %}
|
||||
<div class='row'>
|
||||
<div class='col-sm-6'>
|
||||
<h4>{% trans "Order Notes" %}</h4>
|
||||
</div>
|
||||
<div class='col-sm-6'>
|
||||
<button title='{% trans "Edit notes" %}' class='btn btn-default btn-glyph float-right' id='edit-notes'><span class='glyphicon glyphicon-edit'></span></button>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
<div class='panel panel-default'>
|
||||
<div class='panel-content'>
|
||||
{{ order.notes | markdownify }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block js_ready %}
|
||||
|
||||
{{ block.super }}
|
||||
|
||||
{% if editing %}
|
||||
{% else %}
|
||||
$("#edit-notes").click(function() {
|
||||
location.href = "{% url 'purchase-order-notes' order.id %}?edit=1";
|
||||
});
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
@ -1,104 +1,22 @@
|
||||
{% extends "base.html" %}
|
||||
{% extends "order/order_base.html" %}
|
||||
|
||||
{% load inventree_extras %}
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
{% load inventree_extras %}
|
||||
|
||||
{% block page_title %}
|
||||
InvenTree | {{ order }}
|
||||
{% endblock %}
|
||||
{% block details %}
|
||||
|
||||
{% block content %}
|
||||
<div class='row'>
|
||||
<div class='col-sm-6'>
|
||||
<div class='media'>
|
||||
<div class='media-left'>
|
||||
<img class='part-thumb'
|
||||
{% if order.supplier.image %}
|
||||
src="{{ order.supplier.image.url }}"
|
||||
{% else %}
|
||||
src="{% static 'img/blank_image.png' %}"
|
||||
{% endif %}
|
||||
/>
|
||||
</div>
|
||||
<div class='media-body'>
|
||||
<h4>{{ order }}</h4>
|
||||
<p>{{ order.description }}</p>
|
||||
{% if order.URL %}
|
||||
<a href="{{ order.URL }}">{{ order.URL }}</a>
|
||||
{% endif %}
|
||||
<p>
|
||||
<div class='btn-row'>
|
||||
<div class='btn-group'>
|
||||
<button type='button' class='btn btn-default btn-glyph' id='edit-order' title='Edit order information'>
|
||||
<span class='glyphicon glyphicon-edit'></span>
|
||||
</button>
|
||||
<button type='button' class='btn btn-default btn-glyph' id='export-order' title='Export order to file'>
|
||||
<span class='glyphicon glyphicon-download-alt'></span>
|
||||
</button>
|
||||
{% if order.status == OrderStatus.PENDING and order.lines.count > 0 %}
|
||||
<button type='button' class='btn btn-default btn-glyph' id='place-order' title='Place order'>
|
||||
<span class='glyphicon glyphicon-send'></span>
|
||||
</button>
|
||||
{% elif order.status == OrderStatus.PLACED %}
|
||||
<button type='button' class='btn btn-default btn-glyph' id='receive-order' title='Receive items'>
|
||||
<span class='glyphicon glyphicon-check'></span>
|
||||
</button>
|
||||
<button type='button' class='btn btn-default btn-glyph' id='complete-order' title='Mark order as complete'>
|
||||
<span class='glyphicon glyphicon-ok'></span>
|
||||
</button>
|
||||
{% endif %}
|
||||
{% if order.status == OrderStatus.PENDING or order.status == OrderStatus.PLACED %}
|
||||
<button type='button' class='btn btn-default btn-glyph' id='cancel-order' title='Cancel order'>
|
||||
<span class='glyphicon glyphicon-remove'></span>
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class='col-sm-6'>
|
||||
<h4>{% trans "Purchase Order Details" %}</h4>
|
||||
<table class='table'>
|
||||
<tr>
|
||||
<td>{% trans "Supplier" %}</td>
|
||||
<td><a href="{% url 'company-detail' order.supplier.id %}">{{ order.supplier }}</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans "Status" %}</td>
|
||||
<td>{% include "order/order_status.html" %}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans "Created" %}</td>
|
||||
<td>{{ order.creation_date }}<span class='badge'>{{ order.created_by }}</span></td>
|
||||
</tr>
|
||||
{% if order.issue_date %}
|
||||
<tr>
|
||||
<td>{% trans "Issued" %}</td>
|
||||
<td>{{ order.issue_date }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if order.status == OrderStatus.COMPLETE %}
|
||||
<tr>
|
||||
<td>{% trans "Received" %}</td>
|
||||
<td>{{ order.complete_date }}<span class='badge'>{{ order.received_by }}</span></td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% include 'order/tabs.html' with tab='details' %}
|
||||
|
||||
<hr>
|
||||
|
||||
<div id='order-toolbar-buttons' class='btn-group' style='float: right;'>
|
||||
{% if order.status == OrderStatus.PENDING %}
|
||||
<button type='button' class='btn btn-default' id='new-po-line'>Add Line Item</button>
|
||||
<button type='button' class='btn btn-default' id='new-po-line'>{% trans "Add Line Item" %}</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<h4>Order Items</h4>
|
||||
<h4>{% trans "Order Items" %}</h4>
|
||||
|
||||
<table class='table table-striped table-condensed' id='po-lines-table' data-toolbar='#order-toolbar-buttons'>
|
||||
<thead>
|
||||
@ -162,40 +80,11 @@ InvenTree | {{ order }}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{% if order.notes %}
|
||||
<hr>
|
||||
<div class='panel panel-default'>
|
||||
<div class='panel-heading'><b>{% trans "Notes" %}</b></div>
|
||||
<div class='panel-body'>{{ order.notes }}</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block js_ready %}
|
||||
|
||||
{% if order.status == OrderStatus.PENDING and order.lines.count > 0 %}
|
||||
$("#place-order").click(function() {
|
||||
launchModalForm("{% url 'purchase-order-issue' order.id %}",
|
||||
{
|
||||
reload: true,
|
||||
});
|
||||
});
|
||||
{% endif %}
|
||||
|
||||
$("#edit-order").click(function() {
|
||||
launchModalForm("{% url 'purchase-order-edit' order.id %}",
|
||||
{
|
||||
reload: true,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
$("#cancel-order").click(function() {
|
||||
launchModalForm("{% url 'purchase-order-cancel' order.id %}", {
|
||||
reload: true,
|
||||
});
|
||||
});
|
||||
{{ block.super }}
|
||||
|
||||
$("#po-lines-table").on('click', ".line-receive", function() {
|
||||
|
||||
|
10
InvenTree/order/templates/order/tabs.html
Normal file
10
InvenTree/order/templates/order/tabs.html
Normal file
@ -0,0 +1,10 @@
|
||||
{% load i18n %}
|
||||
|
||||
<ul class='nav nav-tabs'>
|
||||
<li{% ifequal tab 'details' %} class='active'{% endifequal %}>
|
||||
<a href="{% url 'purchase-order-detail' order.id %}">{% trans "Items" %}</a>
|
||||
</li>
|
||||
<li{% ifequal tab 'notes' %} class='active'{% endifequal %}>
|
||||
<a href="{% url 'purchase-order-notes' order.id %}">{% trans "Notes" %}{% if order.notes %} <span class='glyphicon glyphicon-small glyphicon-info-sign'></span>{% endif %}</a>
|
||||
</li>
|
||||
</ul>
|
@ -19,6 +19,8 @@ purchase_order_detail_urls = [
|
||||
|
||||
url(r'^export/?', views.PurchaseOrderExport.as_view(), name='purchase-order-export'),
|
||||
|
||||
url(r'^notes/', views.PurchaseOrderNotes.as_view(), name='purchase-order-notes'),
|
||||
|
||||
url(r'^.*$', views.PurchaseOrderDetail.as_view(), name='purchase-order-detail'),
|
||||
]
|
||||
|
||||
|
@ -7,8 +7,9 @@ from __future__ import unicode_literals
|
||||
|
||||
from django.db import transaction
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.urls import reverse
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.views.generic import DetailView, ListView
|
||||
from django.views.generic import DetailView, ListView, UpdateView
|
||||
from django.forms import HiddenInput
|
||||
|
||||
import logging
|
||||
@ -69,6 +70,28 @@ class PurchaseOrderDetail(DetailView):
|
||||
return ctx
|
||||
|
||||
|
||||
class PurchaseOrderNotes(UpdateView):
|
||||
""" View for updating the 'notes' field of a PurchaseOrder """
|
||||
|
||||
context_object_name = 'order'
|
||||
template_name = 'order/order_notes.html'
|
||||
model = PurchaseOrder
|
||||
|
||||
fields = ['notes']
|
||||
|
||||
def get_success_url(self):
|
||||
|
||||
return reverse('purchase-order-notes', kwargs={'pk': self.get_object().id})
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
|
||||
ctx = super().get_context_data(**kwargs)
|
||||
|
||||
ctx['editing'] = str2bool(self.request.GET.get('edit', ''))
|
||||
|
||||
return ctx
|
||||
|
||||
|
||||
class PurchaseOrderCreate(AjaxCreateView):
|
||||
""" View for creating a new PurchaseOrder object using a modal form """
|
||||
|
||||
|
@ -104,7 +104,6 @@ class EditPartForm(HelperForm):
|
||||
'default_supplier',
|
||||
'units',
|
||||
'minimum_stock',
|
||||
'notes',
|
||||
'active',
|
||||
]
|
||||
|
||||
|
19
InvenTree/part/migrations/0026_auto_20200131_1022.py
Normal file
19
InvenTree/part/migrations/0026_auto_20200131_1022.py
Normal file
@ -0,0 +1,19 @@
|
||||
# Generated by Django 2.2.9 on 2020-01-31 10:22
|
||||
|
||||
from django.db import migrations
|
||||
import markdownx.models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('part', '0025_auto_20191118_2316'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='part',
|
||||
name='notes',
|
||||
field=markdownx.models.MarkdownxField(help_text='Part notes - supports Markdown formatting'),
|
||||
),
|
||||
]
|
@ -22,6 +22,8 @@ from django.contrib.auth.models import User
|
||||
from django.db.models.signals import pre_delete
|
||||
from django.dispatch import receiver
|
||||
|
||||
from markdownx.models import MarkdownxField
|
||||
|
||||
from mptt.models import TreeForeignKey
|
||||
|
||||
from datetime import datetime
|
||||
@ -422,7 +424,7 @@ class Part(models.Model):
|
||||
|
||||
virtual = models.BooleanField(default=False, help_text=_('Is this a virtual part, such as a software product or license?'))
|
||||
|
||||
notes = models.TextField(blank=True)
|
||||
notes = MarkdownxField(help_text=_('Part notes - supports Markdown formatting'))
|
||||
|
||||
bom_checksum = models.CharField(max_length=128, blank=True, help_text=_('Stored BOM checksum'))
|
||||
|
||||
|
10
InvenTree/part/templates/markdownx/widget.html
Normal file
10
InvenTree/part/templates/markdownx/widget.html
Normal file
@ -0,0 +1,10 @@
|
||||
{% load i18n %}
|
||||
|
||||
<div class="markdownx row">
|
||||
<div class="markdown col-md-6">
|
||||
{% include 'django/forms/widgets/textarea.html' %}
|
||||
</div>
|
||||
<div class="markdown col-md-6">
|
||||
<div class="markdownx-preview"></div>
|
||||
</div>
|
||||
</div>
|
@ -3,10 +3,8 @@
|
||||
|
||||
{% include "part/tabs.html" with tab="allocation" %}
|
||||
|
||||
<h3>Part Allocation</h3>
|
||||
<h4>Part Allocation</h4>
|
||||
|
||||
{% if part.allocated_build_count > 0 %}
|
||||
<h4>Allocated to Part Builds</h4>
|
||||
<table class='table table-striped table-condensed' id='build-table'>
|
||||
<tr>
|
||||
<th>Build</th>
|
||||
@ -23,7 +21,6 @@
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
|
@ -1,17 +1,17 @@
|
||||
{% extends "part/part_base.html" %}
|
||||
{% load static %}
|
||||
|
||||
{% load i18n %}
|
||||
{% block details %}
|
||||
|
||||
{% include 'part/tabs.html' with tab='attachments' %}
|
||||
|
||||
<h4>Part Attachments</h4>
|
||||
<h4>{% trans "Part Attachments" %}</h4>
|
||||
|
||||
<hr>
|
||||
|
||||
<div id='attachment-buttons'>
|
||||
<div class="btn-group">
|
||||
<button type='button' class='btn btn-success' id='new-attachment'>Add Attachment</button>
|
||||
<button type='button' class='btn btn-success' id='new-attachment'>{% trans "Add Attachment" %}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -19,8 +19,8 @@
|
||||
<table class='table table-striped table-condensed' data-toolbar='#attachment-buttons' id='attachment-table'>
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-field='file' data-searchable='true'>File</th>
|
||||
<th data-field='comment' data-searchable='true'>Comment</th>
|
||||
<th data-field='file' data-searchable='true'>{% trans "File" %}</th>
|
||||
<th data-field='comment' data-searchable='true'>{% trans "Comment" %}</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
@ -31,10 +31,10 @@
|
||||
<td>{{ attachment.comment }}</td>
|
||||
<td>
|
||||
<div class='btn-group' style='float: right;'>
|
||||
<button type='button' class='btn btn-default btn-glyph attachment-edit-button' url="{% url 'part-attachment-edit' attachment.id %}" data-toggle='tooltip' title='Edit attachment ({{ attachment.basename }})'>
|
||||
<button type='button' class='btn btn-default btn-glyph attachment-edit-button' url="{% url 'part-attachment-edit' attachment.id %}" data-toggle='tooltip' title='{% trans "Edit attachment" %}'>
|
||||
<span class='glyphicon glyphicon-edit'/>
|
||||
</button>
|
||||
<button type='button' class='btn btn-default btn-glyph attachment-delete-button' url="{% url 'part-attachment-delete' attachment.id %}" data-toggle='tooltip' title='Delete attachment ({{ attachment.basename }})'>
|
||||
<button type='button' class='btn btn-default btn-glyph attachment-delete-button' url="{% url 'part-attachment-delete' attachment.id %}" data-toggle='tooltip' title='{% trans "Delete attachment" %}'>
|
||||
<span class='glyphicon glyphicon-trash'/>
|
||||
</button>
|
||||
</div>
|
||||
|
@ -147,13 +147,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if part.notes %}
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading"><b>{% trans "Notes" %}</b></div>
|
||||
<div class="panel-body">{{ part.notes }}</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block js_load %}
|
||||
|
56
InvenTree/part/templates/part/notes.html
Normal file
56
InvenTree/part/templates/part/notes.html
Normal file
@ -0,0 +1,56 @@
|
||||
{% extends "part/part_base.html" %}
|
||||
{% load static %}
|
||||
{% load crispy_forms_tags %}
|
||||
{% load i18n %}
|
||||
{% load markdownify %}
|
||||
|
||||
{% block details %}
|
||||
|
||||
{% include 'part/tabs.html' with tab='notes' %}
|
||||
|
||||
|
||||
{% if editing %}
|
||||
<h4>{% trans "Part Notes" %}</h4>
|
||||
<hr>
|
||||
<form method='POST'>
|
||||
{% csrf_token %}
|
||||
|
||||
{{ form }}
|
||||
<hr>
|
||||
<input type="submit" value='{% trans "Save" %}'/>
|
||||
|
||||
</form>
|
||||
|
||||
{{ form.media }}
|
||||
|
||||
{% else %}
|
||||
<div class='row'>
|
||||
<div class='col-sm-6'>
|
||||
<h4>{% trans "Part Notes" %}</h4>
|
||||
</div>
|
||||
<div class='col-sm-6'>
|
||||
<button title='{% trans "Edit notes" %}' class='btn btn-default btn-glyph float-right' id='edit-notes'><span class='glyphicon glyphicon-edit'></span></button>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
<div class='panel panel-default'>
|
||||
<div class='panel-content'>
|
||||
{{ part.notes | markdownify }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block js_ready %}
|
||||
{{ block.super }}
|
||||
|
||||
{% if editing %}
|
||||
{% else %}
|
||||
$("#edit-notes").click(function() {
|
||||
location.href = "{% url 'part-notes' part.id %}?edit=1";
|
||||
});
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
@ -54,4 +54,7 @@
|
||||
<li{% ifequal tab 'attachments' %} class="active"{% endifequal %}>
|
||||
<a href="{% url 'part-attachments' part.id %}">{% trans "Attachments" %} {% if part.attachment_count > 0 %}<span class="badge">{{ part.attachment_count }}</span>{% endif %}</a>
|
||||
</li>
|
||||
<li{% ifequal tab 'notes' %} class="active"{% endifequal %}>
|
||||
<a href="{% url 'part-notes' part.id %}">{% trans "Notes" %}{% if part.notes %} <span class='glyphicon glyphicon-small glyphicon-info-sign'></span>{% endif %}</a>
|
||||
</li>
|
||||
</ul>
|
@ -48,11 +48,17 @@ def inventree_version(*args, **kwargs):
|
||||
|
||||
|
||||
@register.simple_tag()
|
||||
def inventree_commit(*args, **kwargs):
|
||||
def inventree_commit_hash(*args, **kwargs):
|
||||
""" Return InvenTree git commit hash string """
|
||||
return version.inventreeCommitHash()
|
||||
|
||||
|
||||
@register.simple_tag()
|
||||
def inventree_commit_date(*args, **kwargs):
|
||||
""" Return InvenTree git commit date string """
|
||||
return version.inventreeCommitDate()
|
||||
|
||||
|
||||
@register.simple_tag()
|
||||
def inventree_github_url(*args, **kwargs):
|
||||
""" Return URL for InvenTree github site """
|
||||
|
@ -22,9 +22,13 @@ class TemplateTagTest(TestCase):
|
||||
self.assertEqual(type(inventree_extras.inventree_version()), str)
|
||||
|
||||
def test_hash(self):
|
||||
hash = inventree_extras.inventree_commit()
|
||||
hash = inventree_extras.inventree_commit_hash()
|
||||
self.assertEqual(len(hash), 7)
|
||||
|
||||
def test_date(self):
|
||||
d = inventree_extras.inventree_commit_date()
|
||||
self.assertEqual(len(d.split('-')), 3)
|
||||
|
||||
def test_github(self):
|
||||
self.assertIn('github.com', inventree_extras.inventree_github_url())
|
||||
|
||||
|
@ -52,6 +52,7 @@ part_detail_urls = [
|
||||
url(r'^orders/?', views.PartDetail.as_view(template_name='part/orders.html'), name='part-orders'),
|
||||
url(r'^track/?', views.PartDetail.as_view(template_name='part/track.html'), name='part-track'),
|
||||
url(r'^attachments/?', views.PartDetail.as_view(template_name='part/attachments.html'), name='part-attachments'),
|
||||
url(r'^notes/?', views.PartNotes.as_view(), name='part-notes'),
|
||||
|
||||
url(r'^qr_code/?', views.PartQRCode.as_view(), name='part-qr'),
|
||||
|
||||
|
@ -11,7 +11,7 @@ from django.shortcuts import get_object_or_404
|
||||
from django.shortcuts import HttpResponseRedirect
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.urls import reverse, reverse_lazy
|
||||
from django.views.generic import DetailView, ListView, FormView
|
||||
from django.views.generic import DetailView, ListView, FormView, UpdateView
|
||||
from django.forms.models import model_to_dict
|
||||
from django.forms import HiddenInput, CheckboxInput
|
||||
|
||||
@ -519,6 +519,39 @@ class PartCreate(AjaxCreateView):
|
||||
return initials
|
||||
|
||||
|
||||
class PartNotes(UpdateView):
|
||||
""" View for editing the 'notes' field of a Part object.
|
||||
Presents a live markdown editor.
|
||||
"""
|
||||
|
||||
context_object_name = 'part'
|
||||
# form_class = part_forms.EditNotesForm
|
||||
template_name = 'part/notes.html'
|
||||
model = Part
|
||||
|
||||
fields = ['notes']
|
||||
|
||||
def get_success_url(self):
|
||||
""" Return the success URL for this form """
|
||||
|
||||
return reverse('part-notes', kwargs={'pk': self.get_object().id})
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
|
||||
part = self.get_object()
|
||||
|
||||
ctx = super().get_context_data(**kwargs)
|
||||
|
||||
ctx['editing'] = str2bool(self.request.GET.get('edit', ''))
|
||||
|
||||
ctx['starred'] = part.isStarredBy(self.request.user)
|
||||
ctx['disabled'] = not part.active
|
||||
|
||||
ctx['OrderStatus'] = OrderStatus
|
||||
|
||||
return ctx
|
||||
|
||||
|
||||
class PartDetail(DetailView):
|
||||
""" Detail view for Part object
|
||||
"""
|
||||
|
@ -162,7 +162,6 @@ class EditStockItemForm(HelperForm):
|
||||
'serial',
|
||||
'batch',
|
||||
'status',
|
||||
'notes',
|
||||
'URL',
|
||||
'delete_on_deplete',
|
||||
]
|
||||
|
19
InvenTree/stock/migrations/0018_auto_20200202_0103.py
Normal file
19
InvenTree/stock/migrations/0018_auto_20200202_0103.py
Normal file
@ -0,0 +1,19 @@
|
||||
# Generated by Django 2.2.9 on 2020-02-02 01:03
|
||||
|
||||
from django.db import migrations
|
||||
import markdownx.models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('stock', '0017_auto_20191118_2311'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='stockitem',
|
||||
name='notes',
|
||||
field=markdownx.models.MarkdownxField(blank=True, help_text='Stock Item Notes'),
|
||||
),
|
||||
]
|
@ -16,6 +16,8 @@ from django.contrib.auth.models import User
|
||||
from django.db.models.signals import pre_delete
|
||||
from django.dispatch import receiver
|
||||
|
||||
from markdownx.models import MarkdownxField
|
||||
|
||||
from mptt.models import TreeForeignKey
|
||||
|
||||
from decimal import Decimal, InvalidOperation
|
||||
@ -358,7 +360,7 @@ class StockItem(models.Model):
|
||||
choices=StockStatus.items(),
|
||||
validators=[MinValueValidator(0)])
|
||||
|
||||
notes = models.CharField(max_length=250, blank=True, help_text=_('Stock Item Notes'))
|
||||
notes = MarkdownxField(blank=True, help_text=_('Stock Item Notes'))
|
||||
|
||||
# If stock item is incoming, an (optional) ETA field
|
||||
# expected_arrival = models.DateField(null=True, blank=True)
|
||||
|
@ -1,160 +1,12 @@
|
||||
{% extends "stock/stock_app_base.html" %}
|
||||
{% extends "stock/item_base.html" %}
|
||||
|
||||
{% load static %}
|
||||
{% load inventree_extras %}
|
||||
{% load i18n %}
|
||||
{% block content %}
|
||||
|
||||
<div class='row'>
|
||||
<div class='col-sm-6'>
|
||||
<h3>{% trans "Stock Item Details" %}</h3>
|
||||
{% if item.serialized %}
|
||||
<p><i>{{ item.part.full_name}} # {{ item.serial }}</i></p>
|
||||
{% else %}
|
||||
<p><i>{{ item.quantity }} × {{ item.part.full_name }}</i></p>
|
||||
{% endif %}
|
||||
<p>
|
||||
<div class='btn-group'>
|
||||
{% include "qr_button.html" %}
|
||||
{% if item.in_stock %}
|
||||
{% if not item.serialized %}
|
||||
<button type='button' class='btn btn-default btn-glyph' id='stock-add' title='Add to stock'>
|
||||
<span class='glyphicon glyphicon-plus-sign' style='color: #1a1;'/>
|
||||
</button>
|
||||
<button type='button' class='btn btn-default btn-glyph' id='stock-remove' title='Take from stock'>
|
||||
<span class='glyphicon glyphicon-minus-sign' style='color: #a11;'/>
|
||||
</button>
|
||||
<button type='button' class='btn btn-default btn-glyph' id='stock-count' title='Count stock'>
|
||||
<span class='glyphicon glyphicon-ok-circle'/>
|
||||
</button>
|
||||
{% if item.part.trackable %}
|
||||
<button type='button' class='btn btn-default btn-glyph' id='stock-serialize' title='Serialize stock'>
|
||||
<span class='glyphicon glyphicon-th-list'/>
|
||||
</button>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
<button type='button' class='btn btn-default btn-glyph' id='stock-move' title='Transfer stock'>
|
||||
<span class='glyphicon glyphicon-transfer' style='color: #11a;'/>
|
||||
</button>
|
||||
<button type='button' class='btn btn-default btn-glyph' id='stock-duplicate' title='Duplicate stock item'>
|
||||
<span class='glyphicon glyphicon-duplicate'/>
|
||||
</button>
|
||||
{% endif %}
|
||||
<button type='button' class='btn btn-default btn-glyph' id='stock-edit' title='Edit stock item'>
|
||||
<span class='glyphicon glyphicon-edit'/>
|
||||
</button>
|
||||
<button type='button' class='btn btn-default btn-glyph' id='stock-delete' title='Edit stock item'>
|
||||
<span class='glyphicon glyphicon-trash'/>
|
||||
</button>
|
||||
</div>
|
||||
</p>
|
||||
{% if item.serialized %}
|
||||
<div class='alert alert-block alert-info'>
|
||||
{% trans "This stock item is serialized - it has a unique serial number and the quantity cannot be adjusted." %}
|
||||
</div>
|
||||
{% elif item.delete_on_deplete %}
|
||||
<div class='alert alert-block alert-warning'>
|
||||
{% trans "This stock item will be automatically deleted when all stock is depleted." %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% block details %}
|
||||
|
||||
<div class='row'>
|
||||
<div class='col-sm-6'>
|
||||
<table class="table table-striped">
|
||||
<tr>
|
||||
<td>Part</td>
|
||||
<td>
|
||||
{% include "hover_image.html" with image=item.part.image hover=True %}
|
||||
<a href="{% url 'part-stock' item.part.id %}">{{ item.part.full_name }}
|
||||
</td>
|
||||
</tr>
|
||||
{% if item.belongs_to %}
|
||||
<tr>
|
||||
<td>{% trans "Belongs To" %}</td>
|
||||
<td><a href="{% url 'stock-item-detail' item.belongs_to.id %}">{{ item.belongs_to }}</a></td>
|
||||
</tr>
|
||||
{% elif item.location %}
|
||||
<tr>
|
||||
<td>{% trans "Location" %}</td>
|
||||
<td><a href="{% url 'stock-location-detail' item.location.id %}">{{ item.location.name }}</a></td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if item.serialized %}
|
||||
<tr>
|
||||
<td>{% trans "Serial Number" %}</td>
|
||||
<td>{{ item.serial }}</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr>
|
||||
<td>{% trans "Quantity" %}</td>
|
||||
<td>{% decimal item.quantity %} {% if item.part.units %}{{ item.part.units }}{% endif %}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if item.batch %}
|
||||
<tr>
|
||||
<td>{% trans "Batch" %}</td>
|
||||
<td>{{ item.batch }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if item.build %}
|
||||
<tr>
|
||||
<td>{% trans "Build" %}</td>
|
||||
<td><a href="{% url 'build-detail' item.build.id %}">{{ item.build }}</a></td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if item.purchase_order %}
|
||||
<tr>
|
||||
<td>{% trans "Purchase Order" %}</td>
|
||||
<td><a href="{% url 'purchase-order-detail' item.purchase_order.id %}">{{ item.purchase_order }}</a></td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if item.customer %}
|
||||
<tr>
|
||||
<td>{% trans "Customer" %}</td>
|
||||
<td>{{ item.customer.name }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if item.URL %}
|
||||
<tr>
|
||||
<td>{% trans "URL" %}</td>
|
||||
<td><a href="{{ item.URL }}">{{ item.URL }}</a></td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if item.supplier_part %}
|
||||
<tr>
|
||||
<td>{% trans "Supplier" %}</td>
|
||||
<td><a href="{% url 'company-detail' item.supplier_part.supplier.id %}">{{ item.supplier_part.supplier.name }}</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans "Supplier Part" %}</td>
|
||||
<td><a href="{% url 'supplier-part-detail' item.supplier_part.id %}">{{ item.supplier_part.SKU }}</a></td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
<tr>
|
||||
<td>{% trans "Last Updated" %}</td>
|
||||
<td>{{ item.updated }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans "Last Stocktake" %}</td>
|
||||
{% if item.stocktake_date %}
|
||||
<td>{{ item.stocktake_date }} <span class='badge'>{{ item.stocktake_user }}</span></td>
|
||||
{% else %}
|
||||
<td>{% trans "No stocktake performed" %}</td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans "Status" %}</td>
|
||||
<td>{{ item.get_status_display }}</td>
|
||||
</tr>
|
||||
{% if item.notes %}
|
||||
<tr>
|
||||
<td>{% trans "Notes" %}</td>
|
||||
<td>{{ item.notes }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% include "stock/tabs.html" with tab="tracking" %}
|
||||
|
||||
<hr>
|
||||
<h4>{% trans "Stock Tracking Information" %}</h4>
|
||||
@ -167,6 +19,7 @@
|
||||
</table>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block js_ready %}
|
||||
{{ block.super }}
|
||||
|
||||
@ -179,84 +32,6 @@
|
||||
);
|
||||
});
|
||||
|
||||
$("#stock-serialize").click(function() {
|
||||
launchModalForm(
|
||||
"{% url 'stock-item-serialize' item.id %}",
|
||||
{
|
||||
reload: true,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
$("#stock-duplicate").click(function() {
|
||||
launchModalForm(
|
||||
"{% url 'stock-item-create' %}",
|
||||
{
|
||||
follow: true,
|
||||
data: {
|
||||
copy: {{ item.id }},
|
||||
},
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
$("#stock-edit").click(function () {
|
||||
launchModalForm(
|
||||
"{% url 'stock-item-edit' item.id %}",
|
||||
{
|
||||
reload: true,
|
||||
submit_text: "Save",
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
$("#show-qr-code").click(function() {
|
||||
launchModalForm("{% url 'stock-item-qr' item.id %}",
|
||||
{
|
||||
no_post: true,
|
||||
});
|
||||
});
|
||||
|
||||
{% if item.in_stock %}
|
||||
|
||||
function itemAdjust(action) {
|
||||
launchModalForm("/stock/adjust/",
|
||||
{
|
||||
data: {
|
||||
action: action,
|
||||
item: {{ item.id }},
|
||||
},
|
||||
reload: true,
|
||||
follow: true,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
$("#stock-move").click(function() {
|
||||
itemAdjust("move");
|
||||
});
|
||||
|
||||
$("#stock-count").click(function() {
|
||||
itemAdjust('count');
|
||||
});
|
||||
|
||||
$('#stock-remove').click(function() {
|
||||
itemAdjust('take');
|
||||
});
|
||||
|
||||
$('#stock-add').click(function() {
|
||||
itemAdjust('add');
|
||||
});
|
||||
|
||||
{% endif %}
|
||||
|
||||
$("#stock-delete").click(function () {
|
||||
launchModalForm(
|
||||
"{% url 'stock-item-delete' item.id %}",
|
||||
{
|
||||
redirect: "{% url 'part-stock' item.part.id %}"
|
||||
});
|
||||
});
|
||||
|
||||
loadStockTrackingTable($("#track-table"), {
|
||||
params: function(p) {
|
||||
|
247
InvenTree/stock/templates/stock/item_base.html
Normal file
247
InvenTree/stock/templates/stock/item_base.html
Normal file
@ -0,0 +1,247 @@
|
||||
{% extends "stock/stock_app_base.html" %}
|
||||
{% load static %}
|
||||
{% load inventree_extras %}
|
||||
{% load i18n %}
|
||||
{% block content %}
|
||||
|
||||
|
||||
<div class='row'>
|
||||
<div class='col-sm-6'>
|
||||
<h3>{% trans "Stock Item Details" %}</h3>
|
||||
{% if item.serialized %}
|
||||
<p><i>{{ item.part.full_name}} # {{ item.serial }}</i></p>
|
||||
{% else %}
|
||||
<p><i>{{ item.quantity }} × {{ item.part.full_name }}</i></p>
|
||||
{% endif %}
|
||||
<p>
|
||||
<div class='btn-group'>
|
||||
{% include "qr_button.html" %}
|
||||
{% if item.in_stock %}
|
||||
{% if not item.serialized %}
|
||||
<button type='button' class='btn btn-default btn-glyph' id='stock-add' title='Add to stock'>
|
||||
<span class='glyphicon glyphicon-plus-sign' style='color: #1a1;'/>
|
||||
</button>
|
||||
<button type='button' class='btn btn-default btn-glyph' id='stock-remove' title='Take from stock'>
|
||||
<span class='glyphicon glyphicon-minus-sign' style='color: #a11;'/>
|
||||
</button>
|
||||
<button type='button' class='btn btn-default btn-glyph' id='stock-count' title='Count stock'>
|
||||
<span class='glyphicon glyphicon-ok-circle'/>
|
||||
</button>
|
||||
{% if item.part.trackable %}
|
||||
<button type='button' class='btn btn-default btn-glyph' id='stock-serialize' title='Serialize stock'>
|
||||
<span class='glyphicon glyphicon-th-list'/>
|
||||
</button>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
<button type='button' class='btn btn-default btn-glyph' id='stock-move' title='Transfer stock'>
|
||||
<span class='glyphicon glyphicon-transfer' style='color: #11a;'/>
|
||||
</button>
|
||||
<button type='button' class='btn btn-default btn-glyph' id='stock-duplicate' title='Duplicate stock item'>
|
||||
<span class='glyphicon glyphicon-duplicate'/>
|
||||
</button>
|
||||
{% endif %}
|
||||
<button type='button' class='btn btn-default btn-glyph' id='stock-edit' title='Edit stock item'>
|
||||
<span class='glyphicon glyphicon-edit'/>
|
||||
</button>
|
||||
<button type='button' class='btn btn-default btn-glyph' id='stock-delete' title='Edit stock item'>
|
||||
<span class='glyphicon glyphicon-trash'/>
|
||||
</button>
|
||||
</div>
|
||||
</p>
|
||||
{% if item.serialized %}
|
||||
<div class='alert alert-block alert-info'>
|
||||
{% trans "This stock item is serialized - it has a unique serial number and the quantity cannot be adjusted." %}
|
||||
</div>
|
||||
{% elif item.delete_on_deplete %}
|
||||
<div class='alert alert-block alert-warning'>
|
||||
{% trans "This stock item will be automatically deleted when all stock is depleted." %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class='row'>
|
||||
<div class='col-sm-6'>
|
||||
<table class="table table-striped">
|
||||
<tr>
|
||||
<td>Part</td>
|
||||
<td>
|
||||
{% include "hover_image.html" with image=item.part.image hover=True %}
|
||||
<a href="{% url 'part-stock' item.part.id %}">{{ item.part.full_name }}
|
||||
</td>
|
||||
</tr>
|
||||
{% if item.belongs_to %}
|
||||
<tr>
|
||||
<td>{% trans "Belongs To" %}</td>
|
||||
<td><a href="{% url 'stock-item-detail' item.belongs_to.id %}">{{ item.belongs_to }}</a></td>
|
||||
</tr>
|
||||
{% elif item.location %}
|
||||
<tr>
|
||||
<td>{% trans "Location" %}</td>
|
||||
<td><a href="{% url 'stock-location-detail' item.location.id %}">{{ item.location.name }}</a></td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if item.serialized %}
|
||||
<tr>
|
||||
<td>{% trans "Serial Number" %}</td>
|
||||
<td>{{ item.serial }}</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr>
|
||||
<td>{% trans "Quantity" %}</td>
|
||||
<td>{% decimal item.quantity %} {% if item.part.units %}{{ item.part.units }}{% endif %}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if item.batch %}
|
||||
<tr>
|
||||
<td>{% trans "Batch" %}</td>
|
||||
<td>{{ item.batch }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if item.build %}
|
||||
<tr>
|
||||
<td>{% trans "Build" %}</td>
|
||||
<td><a href="{% url 'build-detail' item.build.id %}">{{ item.build }}</a></td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if item.purchase_order %}
|
||||
<tr>
|
||||
<td>{% trans "Purchase Order" %}</td>
|
||||
<td><a href="{% url 'purchase-order-detail' item.purchase_order.id %}">{{ item.purchase_order }}</a></td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if item.customer %}
|
||||
<tr>
|
||||
<td>{% trans "Customer" %}</td>
|
||||
<td>{{ item.customer.name }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if item.URL %}
|
||||
<tr>
|
||||
<td>{% trans "URL" %}</td>
|
||||
<td><a href="{{ item.URL }}">{{ item.URL }}</a></td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if item.supplier_part %}
|
||||
<tr>
|
||||
<td>{% trans "Supplier" %}</td>
|
||||
<td><a href="{% url 'company-detail' item.supplier_part.supplier.id %}">{{ item.supplier_part.supplier.name }}</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans "Supplier Part" %}</td>
|
||||
<td><a href="{% url 'supplier-part-detail' item.supplier_part.id %}">{{ item.supplier_part.SKU }}</a></td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
<tr>
|
||||
<td>{% trans "Last Updated" %}</td>
|
||||
<td>{{ item.updated }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans "Last Stocktake" %}</td>
|
||||
{% if item.stocktake_date %}
|
||||
<td>{{ item.stocktake_date }} <span class='badge'>{{ item.stocktake_user }}</span></td>
|
||||
{% else %}
|
||||
<td>{% trans "No stocktake performed" %}</td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans "Status" %}</td>
|
||||
<td>{{ item.get_status_display }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<hr>
|
||||
<div class='container-fluid'>
|
||||
{% block details %}
|
||||
<!-- Stock item details go here -->
|
||||
{% endblock %}
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block js_ready %}
|
||||
|
||||
{{ block.super }}
|
||||
|
||||
$("#stock-serialize").click(function() {
|
||||
launchModalForm(
|
||||
"{% url 'stock-item-serialize' item.id %}",
|
||||
{
|
||||
reload: true,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
$("#stock-duplicate").click(function() {
|
||||
launchModalForm(
|
||||
"{% url 'stock-item-create' %}",
|
||||
{
|
||||
follow: true,
|
||||
data: {
|
||||
copy: {{ item.id }},
|
||||
},
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
$("#stock-edit").click(function () {
|
||||
launchModalForm(
|
||||
"{% url 'stock-item-edit' item.id %}",
|
||||
{
|
||||
reload: true,
|
||||
submit_text: "Save",
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
$("#show-qr-code").click(function() {
|
||||
launchModalForm("{% url 'stock-item-qr' item.id %}",
|
||||
{
|
||||
no_post: true,
|
||||
});
|
||||
});
|
||||
|
||||
{% if item.in_stock %}
|
||||
|
||||
function itemAdjust(action) {
|
||||
launchModalForm("/stock/adjust/",
|
||||
{
|
||||
data: {
|
||||
action: action,
|
||||
item: {{ item.id }},
|
||||
},
|
||||
reload: true,
|
||||
follow: true,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
$("#stock-move").click(function() {
|
||||
itemAdjust("move");
|
||||
});
|
||||
|
||||
$("#stock-count").click(function() {
|
||||
itemAdjust('count');
|
||||
});
|
||||
|
||||
$('#stock-remove').click(function() {
|
||||
itemAdjust('take');
|
||||
});
|
||||
|
||||
$('#stock-add').click(function() {
|
||||
itemAdjust('add');
|
||||
});
|
||||
|
||||
{% endif %}
|
||||
|
||||
$("#stock-delete").click(function () {
|
||||
launchModalForm(
|
||||
"{% url 'stock-item-delete' item.id %}",
|
||||
{
|
||||
redirect: "{% url 'part-stock' item.part.id %}"
|
||||
});
|
||||
});
|
||||
|
||||
{% endblock %}
|
57
InvenTree/stock/templates/stock/item_notes.html
Normal file
57
InvenTree/stock/templates/stock/item_notes.html
Normal file
@ -0,0 +1,57 @@
|
||||
{% extends "stock/item_base.html" %}
|
||||
|
||||
{% load static %}
|
||||
{% load inventree_extras %}
|
||||
{% load i18n %}
|
||||
{% load markdownify %}
|
||||
|
||||
{% block details %}
|
||||
|
||||
{% include "stock/tabs.html" with tab="notes" %}
|
||||
|
||||
{% if editing %}
|
||||
<h4>{% trans "Stock Item Notes" %}</h4>
|
||||
<hr>
|
||||
|
||||
<form method='POST'>
|
||||
{% csrf_token %}
|
||||
|
||||
{{ form }}
|
||||
<hr>
|
||||
<input type='submit' value='{% trans "Save" %}'/>
|
||||
</form>
|
||||
|
||||
{{ form.media }}
|
||||
|
||||
{% else %}
|
||||
<div class='row'>
|
||||
<div class='col-sm-6'>
|
||||
<h4>{% trans "Stock Item Notes" %}</h4>
|
||||
</div>
|
||||
<div class='col-sm-6'>
|
||||
<button title='{% trans "Edit notes" %}' class='btn btn-default btn-glyph float-right' id='edit-notes'><span class='glyphicon glyphicon-edit'></span></button>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
<div class='panel panel-default'>
|
||||
<div class='panel-content'>
|
||||
{{ item.notes | markdownify }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block js_ready %}
|
||||
|
||||
{{ block.super }}
|
||||
|
||||
{% if editing %}
|
||||
{% else %}
|
||||
$("#edit-notes").click(function() {
|
||||
location.href = "{% url 'stock-item-notes' item.id %}?edit=1";
|
||||
});
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
16
InvenTree/stock/templates/stock/tabs.html
Normal file
16
InvenTree/stock/templates/stock/tabs.html
Normal file
@ -0,0 +1,16 @@
|
||||
{% load i18n %}
|
||||
|
||||
<ul class='nav nav-tabs'>
|
||||
<li{% ifequal tab 'tracking' %} class='active'{% endifequal %}>
|
||||
<a href="{% url 'stock-item-detail' item.id %}">{% trans "Tracking" %}</a>
|
||||
</li>
|
||||
{% if 0 %}
|
||||
<!-- These tabs are to be implemented in the future -->
|
||||
<li{% ifequal tab 'builds' %} class='active'{% endifequal %}>
|
||||
<a href='#'>{% trans "Builds" %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
<li{% ifequal tab 'notes' %} class='active'{% endifequal %}>
|
||||
<a href="{% url 'stock-item-notes' item.id %}">{% trans "Notes" %}{% if item.notes %} <span class='glyphicon glyphicon-small glyphicon-info-sign'></span>{% endif %}</a>
|
||||
</li>
|
||||
</ul>
|
@ -24,6 +24,8 @@ stock_item_detail_urls = [
|
||||
|
||||
url(r'^add_tracking/', views.StockItemTrackingCreate.as_view(), name='stock-tracking-create'),
|
||||
|
||||
url(r'^notes/', views.StockItemNotes.as_view(), name='stock-item-notes'),
|
||||
|
||||
url('^.*$', views.StockItemDetail.as_view(), name='stock-item-detail'),
|
||||
]
|
||||
|
||||
|
@ -7,7 +7,7 @@ from __future__ import unicode_literals
|
||||
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.views.generic.edit import FormMixin
|
||||
from django.views.generic import DetailView, ListView
|
||||
from django.views.generic import DetailView, ListView, UpdateView
|
||||
from django.forms.models import model_to_dict
|
||||
from django.forms import HiddenInput
|
||||
from django.urls import reverse
|
||||
@ -83,6 +83,27 @@ class StockItemDetail(DetailView):
|
||||
model = StockItem
|
||||
|
||||
|
||||
class StockItemNotes(UpdateView):
|
||||
""" View for editing the 'notes' field of a StockItem object """
|
||||
|
||||
context_object_name = 'item'
|
||||
template_name = 'stock/item_notes.html'
|
||||
model = StockItem
|
||||
|
||||
fields = ['notes']
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse('stock-item-notes', kwargs={'pk': self.get_object().id})
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
|
||||
ctx = super().get_context_data(**kwargs)
|
||||
|
||||
ctx['editing'] = str2bool(self.request.GET.get('edit', ''))
|
||||
|
||||
return ctx
|
||||
|
||||
|
||||
class StockLocationEdit(AjaxUpdateView):
|
||||
"""
|
||||
View for editing details of a StockLocation.
|
||||
|
@ -1,5 +1,6 @@
|
||||
{% load static %}
|
||||
{% load inventree_extras %}
|
||||
{% load i18n %}
|
||||
|
||||
<div class='modal fade modal-fixed-footer' tabindex='-1' role='dialog' id='modal-about'>
|
||||
<div class='modal-dialog'>
|
||||
@ -14,23 +15,26 @@
|
||||
<div>
|
||||
<!--
|
||||
-->
|
||||
<h4>InvenTree Version Information</h4>
|
||||
<h4>{% trans "InvenTree Version Information" %}</h4>
|
||||
<table class='table table-striped table-condensed'>
|
||||
<tr>
|
||||
<td>Version</td><td><a href="https://github.com/inventree/InvenTree/releases">{% inventree_version %}</a></td>
|
||||
<td>{% trans "Version" %}</td><td><a href="https://github.com/inventree/InvenTree/releases">{% inventree_version %}</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Commit Hash</td><td><a href="https://github.com/inventree/InvenTree/commit/{% inventree_commit %}">{% inventree_commit %}</a></td>
|
||||
<td>{% trans "Commit Hash" %}</td><td><a href="https://github.com/inventree/InvenTree/commit/{% inventree_commit_hash %}">{% inventree_commit_hash %}</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans "Commit Date" %}</td><td>{% inventree_commit_date %}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>InvenTree Documenation</td>
|
||||
<td>{% trans "InvenTree Documentation" %}</td>
|
||||
<td><a href="{% inventree_docs_url %}">{% inventree_docs_url %}</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>View Code on GitHub</td><td><a href="{% inventree_github_url %}">{% inventree_github_url %}</a></td>
|
||||
<td>{% trans "View Code on GitHub" %}</td><td><a href="{% inventree_github_url %}">{% inventree_github_url %}</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
|
@ -5,6 +5,8 @@ django-cors-headers==3.2.0 # CORS headers extension for DRF
|
||||
django_filter==2.2.0 # Extended filtering options
|
||||
django-mptt==0.10.0 # Modified Preorder Tree Traversal
|
||||
django-dbbackup==3.2.0 # Database backup / restore functionality
|
||||
django-markdownx==3.0.1 # Markdown form fields
|
||||
django-markdownify==0.8.0 # Markdown rendering
|
||||
coreapi==2.3.0 # API documentation
|
||||
pygments==2.2.0 # Syntax highlighting
|
||||
tablib==0.13.0 # Import / export data files
|
||||
|
Loading…
Reference in New Issue
Block a user