Merge pull request #1397 from SchrodingersGat/order-report

Order report
This commit is contained in:
Oliver 2021-03-12 14:44:10 +11:00 committed by GitHub
commit ed028aed62
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 863 additions and 37 deletions

View File

@ -35,7 +35,10 @@ src="{% static 'img/blank_image.png' %}"
<hr>
<p>{{ order.description }}</p>
<div class='btn-row'>
<div class='btn-group action-buttons'>
<div class='btn-group action-buttons' role='group'>
<button type='button' class='btn btn-default' id='print-order-report' title='{% trans "Print" %}'>
<span class='fas fa-print'></span>
</button>
{% if roles.purchase_order.change %}
<button type='button' class='btn btn-default' id='edit-order' title='{% trans "Edit order information" %}'>
<span class='fas fa-edit icon-green'></span>
@ -156,6 +159,10 @@ $("#place-order").click(function() {
});
{% endif %}
$('#print-order-report').click(function() {
printPurchaseOrderReports([{{ order.pk }}]);
});
$("#edit-order").click(function() {
launchModalForm("{% url 'po-edit' order.id %}",
{

View File

@ -15,18 +15,24 @@ InvenTree | {% trans "Purchase Orders" %}
<div id='table-buttons'>
<div class='button-toolbar container-fluid' style='float: right;'>
{% if roles.purchase_order.add %}
<button class='btn btn-primary' type='button' id='po-create' title='{% trans "Create new purchase order" %}'>
<span class='fas fa-plus-circle'></span> {% trans "New Purchase Order" %}</button>
{% endif %}
<button class='btn btn-default' type='button' id='view-calendar' title='{% trans "Display calendar view" %}'>
<span class='fas fa-calendar-alt'></span>
</button>
<button class='btn btn-default' type='button' id='view-list' title='{% trans "Display list view" %}'>
<span class='fas fa-th-list'></span>
</button>
<div class='filter-list' id='filter-list-purchaseorder'>
<!-- An empty div in which the filter list will be constructed -->
<div class='btn-group'>
{% if roles.purchase_order.add %}
<button class='btn btn-primary' type='button' id='po-create' title='{% trans "Create new purchase order" %}'>
<span class='fas fa-plus-circle'></span> {% trans "New Purchase Order" %}
</button>
{% endif %}
<button id='order-print' class='btn btn-default' title='{% trans "Print Order Reports" %}'>
<span class='fas fa-print'></span>
</button>
<button class='btn btn-default' type='button' id='view-calendar' title='{% trans "Display calendar view" %}'>
<span class='fas fa-calendar-alt'></span>
</button>
<button class='btn btn-default' type='button' id='view-list' title='{% trans "Display list view" %}'>
<span class='fas fa-th-list'></span>
</button>
<div class='filter-list' id='filter-list-purchaseorder'>
<!-- An empty div in which the filter list will be constructed -->
</div>
</div>
</div>
</div>
@ -154,6 +160,18 @@ $("#view-list").click(function() {
$("#view-calendar").show();
});
$("#order-print").click(function() {
var rows = $("#purchase-order-table").bootstrapTable('getSelections');
var orders = [];
rows.forEach(function(row) {
orders.push(row.pk);
});
printPurchaseOrderReports(orders);
})
$("#po-create").click(function() {
launchModalForm("{% url 'po-create' %}",
{

View File

@ -45,6 +45,9 @@ src="{% static 'img/blank_image.png' %}"
<p>{{ order.description }}</p>
<div class='btn-row'>
<div class='btn-group action-buttons'>
<button type='button' class='btn btn-default' id='print-order-report' title='{% trans "Print" %}'>
<span class='fas fa-print'></span>
</button>
{% if roles.sales_order.change %}
<button type='button' class='btn btn-default' id='edit-order' title='Edit order information'>
<span class='fas fa-edit icon-green'></span>
@ -165,4 +168,8 @@ $("#ship-order").click(function() {
});
});
$('#print-order-report').click(function() {
printSalesOrderReports([{{ order.pk }}]);
});
{% endblock %}

View File

@ -15,18 +15,24 @@ InvenTree | {% trans "Sales Orders" %}
<div id='table-buttons'>
<div class='button-toolbar container-fluid' style='float: right;'>
{% if roles.sales_order.add %}
<button class='btn btn-primary' type='button' id='so-create' title='{% trans "Create new sales order" %}'>
<span class='fas fa-plus-circle'></span> {% trans "New Sales Order" %}</button>
{% endif %}
<button class='btn btn-default' type='button' id='view-calendar' title='{% trans "Display calendar view" %}'>
<span class='fas fa-calendar-alt'></span>
</button>
<button class='btn btn-default' type='button' id='view-list' title='{% trans "Display list view" %}'>
<span class='fas fa-th-list'></span>
</button>
<div class='filter-list' id='filter-list-salesorder'>
<!-- An empty div in which the filter list will be constructed -->
<div class='btn-group'>
{% if roles.sales_order.add %}
<button class='btn btn-primary' type='button' id='so-create' title='{% trans "Create new sales order" %}'>
<span class='fas fa-plus-circle'></span> {% trans "New Sales Order" %}
</button>
{% endif %}
<button id='order-print' class='btn btn-default' title='{% trans "Print Order Reports" %}'>
<span class='fas fa-print'></span>
</button>
<button class='btn btn-default' type='button' id='view-calendar' title='{% trans "Display calendar view" %}'>
<span class='fas fa-calendar-alt'></span>
</button>
<button class='btn btn-default' type='button' id='view-list' title='{% trans "Display list view" %}'>
<span class='fas fa-th-list'></span>
</button>
<div class='filter-list' id='filter-list-salesorder'>
<!-- An empty div in which the filter list will be constructed -->
</div>
</div>
</div>
</div>
@ -156,10 +162,30 @@ loadSalesOrderTable("#sales-order-table", {
url: "{% url 'api-so-list' %}",
});
$("#order-print").click(function() {
var rows = $("#sales-order-table").bootstrapTable('getSelections');
var orders = [];
rows.forEach(function(row) {
orders.push(row.pk);
});
printSalesOrderReports(orders);
})
$("#so-create").click(function() {
launchModalForm("{% url 'so-create' %}",
{
follow: true,
secondary: [
{
field: 'customer',
label: '{% trans "New Customer" %}',
title: '{% trans "Create new Customer" %}',
url: '{% url "customer-create" %}',
}
]
}
);
});

View File

@ -7,6 +7,8 @@ from .models import ReportSnippet, ReportAsset
from .models import TestReport
from .models import BuildReport
from .models import BillOfMaterialsReport
from .models import PurchaseOrderReport
from .models import SalesOrderReport
class ReportTemplateAdmin(admin.ModelAdmin):
@ -30,3 +32,5 @@ admin.site.register(ReportAsset, ReportAssetAdmin)
admin.site.register(TestReport, ReportTemplateAdmin)
admin.site.register(BuildReport, ReportTemplateAdmin)
admin.site.register(BillOfMaterialsReport, ReportTemplateAdmin)
admin.site.register(PurchaseOrderReport, ReportTemplateAdmin)
admin.site.register(SalesOrderReport, ReportTemplateAdmin)

View File

@ -18,14 +18,19 @@ from stock.models import StockItem
import build.models
import part.models
import order.models
from .models import TestReport
from .models import BuildReport
from .models import BillOfMaterialsReport
from .models import PurchaseOrderReport
from .models import SalesOrderReport
from .serializers import TestReportSerializer
from .serializers import BuildReportSerializer
from .serializers import BOMReportSerializer
from .serializers import POReportSerializer
from .serializers import SOReportSerializer
class ReportListView(generics.ListAPIView):
@ -113,6 +118,40 @@ class BuildReportMixin:
return build.models.Build.objects.filter(pk__in=valid_ids)
class OrderReportMixin:
"""
Mixin for extracting order items from query params
requires the OrderModel class attribute to be set!
"""
def get_orders(self):
"""
Return a list of order objects
"""
orders = []
params = self.request.query_params
for key in ['order', 'order[]', 'orders', 'orders[]']:
if key in params:
orders = params.getlist(key, [])
break
valid_ids = []
for o in orders:
try:
valid_ids.append(int(o))
except (ValueError):
pass
valid_orders = self.OrderModel.objects.filter(pk__in=valid_ids)
return valid_orders
class PartReportMixin:
"""
Mixin for extracting part items from query params
@ -481,14 +520,203 @@ class BuildReportPrint(generics.RetrieveAPIView, BuildReportMixin, ReportPrintMi
return self.print(request, builds)
class POReportList(ReportListView, OrderReportMixin):
OrderModel = order.models.PurchaseOrder
queryset = PurchaseOrderReport.objects.all()
serializer_class = POReportSerializer
def filter_queryset(self, queryset):
queryset = super().filter_queryset(queryset)
orders = self.get_orders()
if len(orders) > 0:
"""
We wish to filter by purchase orders
We need to compare the 'filters' string of each report,
and see if it matches against each of the specified orders.
TODO: In the future, perhaps there is a way to make this more efficient.
"""
valid_report_ids = set()
for report in queryset.all():
matches = True
# Filter string defined for the report object
try:
filters = InvenTree.helpers.validateFilterString(report.filters)
except:
continue
for o in orders:
order_query = order.models.PurchaseOrder.objects.filter(pk=o.pk)
try:
if not order_query.filter(**filters).exists():
matches = False
break
except FieldError:
matches = False
break
if matches:
valid_report_ids.add(report.pk)
else:
continue
# Reduce queryset to only valid matches
queryset = queryset.filter(pk__in=[pk for pk in valid_report_ids])
return queryset
class POReportDetail(generics.RetrieveUpdateDestroyAPIView):
"""
API endpoint for a single PurchaseOrderReport object
"""
queryset = PurchaseOrderReport.objects.all()
serializer_class = POReportSerializer
class POReportPrint(generics.RetrieveAPIView, OrderReportMixin, ReportPrintMixin):
"""
API endpoint for printing a PurchaseOrderReport object
"""
OrderModel = order.models.PurchaseOrder
queryset = PurchaseOrderReport.objects.all()
serializer_class = POReportSerializer
def get(self, request, *args, **kwargs):
orders = self.get_orders()
return self.print(request, orders)
class SOReportList(ReportListView, OrderReportMixin):
OrderModel = order.models.SalesOrder
queryset = SalesOrderReport.objects.all()
serializer_class = SOReportSerializer
def filter_queryset(self, queryset):
queryset = super().filter_queryset(queryset)
orders = self.get_orders()
if len(orders) > 0:
"""
We wish to filter by purchase orders
We need to compare the 'filters' string of each report,
and see if it matches against each of the specified orders.
TODO: In the future, perhaps there is a way to make this more efficient.
"""
valid_report_ids = set()
for report in queryset.all():
matches = True
# Filter string defined for the report object
try:
filters = InvenTree.helpers.validateFilterString(report.filters)
except:
continue
for o in orders:
order_query = order.models.SalesOrder.objects.filter(pk=o.pk)
try:
if not order_query.filter(**filters).exists():
matches = False
break
except FieldError:
matches = False
break
if matches:
valid_report_ids.add(report.pk)
else:
continue
# Reduce queryset to only valid matches
queryset = queryset.filter(pk__in=[pk for pk in valid_report_ids])
return queryset
class SOReportDetail(generics.RetrieveUpdateDestroyAPIView):
"""
API endpoint for a single SalesOrderReport object
"""
queryset = SalesOrderReport.objects.all()
serializer_class = SOReportSerializer
class SOReportPrint(generics.RetrieveAPIView, OrderReportMixin, ReportPrintMixin):
"""
API endpoint for printing a PurchaseOrderReport object
"""
OrderModel = order.models.SalesOrder
queryset = SalesOrderReport.objects.all()
serializer_class = SOReportSerializer
def get(self, request, *args, **kwargs):
orders = self.get_orders()
return self.print(request, orders)
report_api_urls = [
# Purchase order reports
url(r'po/', include([
# Detail views
url(r'^(?P<pk>\d+)/', include([
url(r'print/', POReportPrint.as_view(), name='api-po-report-print'),
url(r'^$', POReportDetail.as_view(), name='api-po-report-detail'),
])),
# List view
url(r'^$', POReportList.as_view(), name='api-po-report-list'),
])),
# Sales order reports
url(r'so/', include([
# Detail views
url(r'^(?P<pk>\d+)/', include([
url(r'print/', SOReportPrint.as_view(), name='api-so-report-print'),
url(r'^$', SOReportDetail.as_view(), name='api-so-report-detail'),
])),
url(r'^$', SOReportList.as_view(), name='api-so-report-list'),
])),
# Build reports
url(r'build/', include([
# Detail views
url(r'^(?P<pk>\d+)/', include([
url(r'print/?', BuildReportPrint.as_view(), name='api-build-report-print'),
url(r'^.*$', BuildReportDetail.as_view(), name='api-build-report-detail'),
url(r'^.$', BuildReportDetail.as_view(), name='api-build-report-detail'),
])),
# List view

View File

@ -0,0 +1,45 @@
# Generated by Django 3.0.7 on 2021-03-10 05:46
import django.core.validators
from django.db import migrations, models
import report.models
class Migration(migrations.Migration):
dependencies = [
('report', '0013_testreport_include_installed'),
]
operations = [
migrations.CreateModel(
name='PurchaseOrderReport',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(help_text='Template name', max_length=100, verbose_name='Name')),
('template', models.FileField(help_text='Report template file', upload_to=report.models.rename_template, validators=[django.core.validators.FileExtensionValidator(allowed_extensions=['html', 'htm'])], verbose_name='Template')),
('description', models.CharField(help_text='Report template description', max_length=250, verbose_name='Description')),
('revision', models.PositiveIntegerField(default=1, editable=False, help_text='Report revision number (auto-increments)', verbose_name='Revision')),
('enabled', models.BooleanField(default=True, help_text='Report template is enabled', verbose_name='Enabled')),
('filters', models.CharField(blank=True, help_text='Purchase order query filters', max_length=250, validators=[report.models.validate_purchase_order_filters], verbose_name='Filters')),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='SalesOrderReport',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(help_text='Template name', max_length=100, verbose_name='Name')),
('template', models.FileField(help_text='Report template file', upload_to=report.models.rename_template, validators=[django.core.validators.FileExtensionValidator(allowed_extensions=['html', 'htm'])], verbose_name='Template')),
('description', models.CharField(help_text='Report template description', max_length=250, verbose_name='Description')),
('revision', models.PositiveIntegerField(default=1, editable=False, help_text='Report revision number (auto-increments)', verbose_name='Revision')),
('enabled', models.BooleanField(default=True, help_text='Report template is enabled', verbose_name='Enabled')),
('filters', models.CharField(blank=True, help_text='Sales order query filters', max_length=250, validators=[report.models.validate_sales_order_filters], verbose_name='Filters')),
],
options={
'abstract': False,
},
),
]

View File

@ -24,6 +24,7 @@ import build.models
import common.models
import part.models
import stock.models
import order.models
from InvenTree.helpers import validateFilterString
@ -94,6 +95,22 @@ def validate_build_report_filters(filters):
return validateFilterString(filters, model=build.models.Build)
def validate_purchase_order_filters(filters):
"""
Validate filter string against PurchaseOrder model
"""
return validateFilterString(filters, model=order.models.PurchaseOrder)
def validate_sales_order_filters(filters):
"""
Validate filter string against SalesOrder model
"""
return validateFilterString(filters, model=order.models.SalesOrder)
class WeasyprintReportMixin(WeasyTemplateResponseMixin):
"""
Class for rendering a HTML template to a PDF.
@ -383,6 +400,74 @@ class BillOfMaterialsReport(ReportTemplateBase):
}
class PurchaseOrderReport(ReportTemplateBase):
"""
Render a report against a PurchaseOrder object
"""
@classmethod
def getSubdir(cls):
return 'purchaseorder'
filters = models.CharField(
blank=True,
max_length=250,
verbose_name=_('Filters'),
help_text=_('Purchase order query filters'),
validators=[
validate_purchase_order_filters,
]
)
def get_context_data(self, request):
order = self.object_to_print
return {
'description': order.description,
'lines': order.lines,
'order': order,
'reference': order.reference,
'supplier': order.supplier,
'prefix': common.models.InvenTreeSetting.get_setting('PURCHASEORDER_REFERENCE_PREFIX'),
'title': str(order),
}
class SalesOrderReport(ReportTemplateBase):
"""
Render a report against a SalesOrder object
"""
@classmethod
def getSubdir(cls):
return 'salesorder'
filters = models.CharField(
blank=True,
max_length=250,
verbose_name=_('Filters'),
help_text=_('Sales order query filters'),
validators=[
validate_sales_order_filters
]
)
def get_context_data(self, request):
order = self.object_to_print
return {
'customer': order.customer,
'description': order.description,
'lines': order.lines,
'order': order,
'prefix': common.models.InvenTreeSetting.get_setting('SALESORDER_REFERENCE_PREFIX'),
'reference': order.reference,
'title': str(order),
}
def rename_snippet(instance, filename):
filename = os.path.basename(filename)

View File

@ -7,6 +7,7 @@ from InvenTree.serializers import InvenTreeAttachmentSerializerField
from .models import TestReport
from .models import BuildReport
from .models import BillOfMaterialsReport
from .models import PurchaseOrderReport, SalesOrderReport
class TestReportSerializer(InvenTreeModelSerializer):
@ -55,3 +56,35 @@ class BOMReportSerializer(InvenTreeModelSerializer):
'filters',
'enabled',
]
class POReportSerializer(InvenTreeModelSerializer):
template = InvenTreeAttachmentSerializerField(required=True)
class Meta:
model = PurchaseOrderReport
fields = [
'pk',
'name',
'description',
'template',
'filters',
'enabled',
]
class SOReportSerializer(InvenTreeModelSerializer):
template = InvenTreeAttachmentSerializerField(required=True)
class Meta:
model = SalesOrderReport
fields = [
'pk',
'name',
'description',
'template',
'filters',
'enabled',
]

View File

@ -77,12 +77,9 @@ margin-top: 4cm;
content: "v{{report_revision}} - {{ date.isoformat }}";
{% endblock %}
{% block bottom_center %}
content: "www.currawong.aero";
{% endblock %}
{% block header_content %}
<img class='logo' src="{% asset 'logo_black_with_black_bird.png' %}" alt="hello" width="150">
<!-- TODO - Make the company logo asset generic -->
<img class='logo' src="{% asset 'company_logo.png' %}" alt="hello" width="150">
<div class='header-right'>
<h3>

View File

@ -0,0 +1,116 @@
{% extends "report/inventree_report_base.html" %}
{% load i18n %}
{% load report %}
{% load barcode %}
{% load inventree_extras %}
{% load markdownify %}
{% block page_margin %}
margin: 2cm;
margin-top: 4cm;
{% endblock %}
{% block bottom_left %}
content: "v{{report_revision}} - {{ date.isoformat }}";
{% endblock %}
{% block bottom_center %}
content: "InvenTree v{% inventree_version %}";
{% endblock %}
{% block style %}
.header-right {
text-align: right;
float: right;
}
.logo {
height: 20mm;
vertical-align: middle;
}
.thumb-container {
width: 32px;
display: inline;
}
.part-thumb {
max-width: 32px;
max-height: 32px;
display: inline;
}
.part-text {
display: inline;
}
table {
border: 1px solid #eee;
border-radius: 3px;
border-collapse: collapse;
width: 100%;
font-size: 80%;
}
table td {
border: 1px solid #eee;
}
table td.shrink {
white-space: nowrap
}
table td.expand {
width: 99%
}
{% endblock %}
{% block header_content %}
<img class='logo' src='{% company_image supplier %}' alt="{{ supplier }}" width='150'>
<div class='header-right'>
<h3>{% trans "Purchase Order" %} {{ prefix }}{{ reference }}</h3>
{{ supplier.name }}
</div>
{% endblock %}
{% block page_content %}
<h3>{% trans "Line Items" %}</h3>
<table class='table table-striped table-condensed'>
<thead>
<tr>
<th>{% trans "Part" %}</th>
<th>{% trans "Quantity" %}</th>
<th>{% trans "Reference" %}</th>
<th>{% trans "Note" %}</th>
</tr>
</thead>
<tbody>
{% for line in lines.all %}
<tr>
<td>
<div class='thumb-container'>
<img src='{% part_image line.part.part %}' class='part-thumb'>
</div>
<div class='part-text'>
{{ line.part.part.full_name }}
</div>
</td>
<td>{% decimal line.quantity %}</td>
<td>{{ line.reference }}</td>
<td>{{ line.notes }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endblock %}

View File

@ -0,0 +1,116 @@
{% extends "report/inventree_report_base.html" %}
{% load i18n %}
{% load report %}
{% load barcode %}
{% load inventree_extras %}
{% load markdownify %}
{% block page_margin %}
margin: 2cm;
margin-top: 4cm;
{% endblock %}
{% block bottom_left %}
content: "v{{report_revision}} - {{ date.isoformat }}";
{% endblock %}
{% block bottom_center %}
content: "InvenTree v{% inventree_version %}";
{% endblock %}
{% block style %}
.header-right {
text-align: right;
float: right;
}
.logo {
height: 20mm;
vertical-align: middle;
}
.thumb-container {
width: 32px;
display: inline;
}
.part-thumb {
max-width: 32px;
max-height: 32px;
display: inline;
}
.part-text {
display: inline;
}
table {
border: 1px solid #eee;
border-radius: 3px;
border-collapse: collapse;
width: 100%;
font-size: 80%;
}
table td {
border: 1px solid #eee;
}
table td.shrink {
white-space: nowrap
}
table td.expand {
width: 99%
}
{% endblock %}
{% block header_content %}
<img class='logo' src='{% company_image customer %}' alt="{{ customer }}" width='150'>
<div class='header-right'>
<h3>{% trans "Sales Order" %} {{ prefix }}{{ reference }}</h3>
{{ customer.name }}
</div>
{% endblock %}
{% block page_content %}
<h3>{% trans "Line Items" %}</h3>
<table class='table table-striped table-condensed'>
<thead>
<tr>
<th>{% trans "Part" %}</th>
<th>{% trans "Quantity" %}</th>
<th>{% trans "Reference" %}</th>
<th>{% trans "Note" %}</th>
</tr>
</thead>
<tbody>
{% for line in lines.all %}
<tr>
<td>
<div class='thumb-container'>
<img src='{% part_image line.part %}' class='part-thumb'>
</div>
<div class='part-text'>
{{ line.part.full_name }}
</div>
</td>
<td>{% decimal line.quantity %}</td>
<td>{{ line.reference }}</td>
<td>{{ line.notes }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endblock %}

View File

@ -8,6 +8,7 @@ from django import template
from django.conf import settings
from django.utils.safestring import mark_safe
from company.models import Company
from part.models import Part
from stock.models import StockItem
@ -72,6 +73,39 @@ def part_image(part):
return f"file://{path}"
@register.simple_tag()
def company_image(company):
"""
Return a fully-qualified path for a company image
"""
# If in debug mode, return the URL to the image, not a local file
debug_mode = InvenTreeSetting.get_setting('REPORT_DEBUG_MODE')
if type(company) is Company:
img = company.image.name
else:
img = ''
if debug_mode:
if img:
return os.path.join(settings.MEDIA_URL, img)
else:
return os.path.join(settings.STATIC_URL, 'img', 'blank_image.png')
else:
path = os.path.join(settings.MEDIA_ROOT, img)
path = os.path.abspath(path)
if not os.path.exists(path) or not os.path.isfile(path):
# Image does not exist
# Return the 'blank' image
path = os.path.join(settings.STATIC_ROOT, 'img', 'blank_image.png')
path = os.path.abspath(path)
return f"file://{path}"
@register.simple_tag()
def internal_link(link, text):
"""

View File

@ -138,9 +138,9 @@ function loadPurchaseOrderTable(table, options) {
formatNoMatches: function() { return '{% trans "No purchase orders found" %}'; },
columns: [
{
field: 'pk',
title: 'ID',
visible: false,
title: '',
visible: true,
checkbox: true,
switchable: false,
},
{
@ -234,9 +234,9 @@ function loadSalesOrderTable(table, options) {
formatNoMatches: function() { return '{% trans "No sales orders found" %}'; },
columns: [
{
field: 'pk',
title: 'ID',
visible: false,
title: '',
checkbox: true,
visible: true,
switchable: false,
},
{

View File

@ -247,3 +247,111 @@ function printBomReports(parts, options={}) {
}
)
}
function printPurchaseOrderReports(orders, options={}) {
/**
* Print PO reports for the provided purchase order(s)
*/
if (orders.length == 0) {
showAlertDialog(
'{% trans "Select Purchase Orders" %}',
'{% trans "Purchase Order(s) must be selected before printing report" %}',
);
return;
}
// Request avaiable report templates
inventreeGet(
'{% url "api-po-report-list" %}',
{
enabled: true,
orders: orders,
},
{
success: function(response) {
if (response.length == 0) {
showAlertDialog(
'{% trans "No Reports Found" %}',
'{% trans "No report templates found which match selected orders" %}',
);
return;
}
// Select report template
selectReport(
response,
orders,
{
success: function(pk) {
var href = `/api/report/po/${pk}/print/?`;
orders.forEach(function(order) {
href += `order=${order}&`;
});
window.location.href = href;
}
}
)
}
}
)
}
function printSalesOrderReports(orders, options={}) {
/**
* Print SO reports for the provided purchase order(s)
*/
if (orders.length == 0) {
showAlertDialog(
'{% trans "Select Sales Orders" %}',
'{% trans "Sales Order(s) must be selected before printing report" %}',
);
return;
}
// Request avaiable report templates
inventreeGet(
'{% url "api-so-report-list" %}',
{
enabled: true,
orders: orders,
},
{
success: function(response) {
if (response.length == 0) {
showAlertDialog(
'{% trans "No Reports Found" %}',
'{% trans "No report templates found which match selected orders" %}',
);
return;
}
// Select report template
selectReport(
response,
orders,
{
success: function(pk) {
var href = `/api/report/so/${pk}/print/?`;
orders.forEach(function(order) {
href += `order=${order}&`;
});
window.location.href = href;
}
}
)
}
}
)
}

View File

@ -124,6 +124,8 @@ class RuleSet(models.Model):
'report_reportasset',
'report_reportsnippet',
'report_billofmaterialsreport',
'report_purchaseorderreport',
'report_salesorderreport',
'users_owner',
# Third-party tables