Add basic support for receiving from multiple invoice files
This commit is contained in:
parent
2b7ebedb22
commit
dfa4178204
10 changed files with 295 additions and 40 deletions
|
@ -2,7 +2,7 @@
|
|||
################################################################################
|
||||
#
|
||||
# Rattail -- Retail Software Framework
|
||||
# Copyright © 2010-2022 Lance Edgar
|
||||
# Copyright © 2010-2023 Lance Edgar
|
||||
#
|
||||
# This file is part of Rattail.
|
||||
#
|
||||
|
@ -362,6 +362,7 @@ class BatchMasterView(MasterView):
|
|||
f.remove('params')
|
||||
else:
|
||||
f.set_readonly('params')
|
||||
f.set_renderer('params', self.render_params)
|
||||
|
||||
# created
|
||||
f.set_readonly('created')
|
||||
|
@ -419,6 +420,16 @@ class BatchMasterView(MasterView):
|
|||
f.remove_fields('executed',
|
||||
'executed_by')
|
||||
|
||||
def render_params(self, batch, field):
|
||||
params = self.get_visible_params(batch)
|
||||
if not params:
|
||||
return
|
||||
|
||||
return params
|
||||
|
||||
def get_visible_params(self, batch):
|
||||
return dict(batch.params or {})
|
||||
|
||||
def render_complete(self, batch, field):
|
||||
permission_prefix = self.get_permission_prefix()
|
||||
use_buefy = self.get_use_buefy()
|
||||
|
@ -515,11 +526,21 @@ class BatchMasterView(MasterView):
|
|||
return batch
|
||||
|
||||
def process_uploads(self, batch, form, uploads):
|
||||
for key, upload in six.iteritems(uploads):
|
||||
|
||||
def process(upload, key):
|
||||
self.handler.set_input_file(batch, upload['temp_path'], attr=key)
|
||||
os.remove(upload['temp_path'])
|
||||
os.rmdir(upload['tempdir'])
|
||||
|
||||
for key, upload in six.iteritems(uploads):
|
||||
if isinstance(upload, dict):
|
||||
process(upload, key)
|
||||
else:
|
||||
uploads = upload
|
||||
for upload in uploads:
|
||||
if isinstance(upload, dict):
|
||||
process(upload, key)
|
||||
|
||||
def get_batch_kwargs(self, batch, **kwargs):
|
||||
"""
|
||||
Return a kwargs dict for use with ``self.handler.make_batch()``, using
|
||||
|
|
|
@ -53,6 +53,7 @@ from rattail.gpc import GPC
|
|||
|
||||
import colander
|
||||
import deform
|
||||
from deform import widget as dfwidget
|
||||
from pyramid import httpexceptions
|
||||
from pyramid.renderers import get_renderer, render_to_response, render
|
||||
from pyramid.response import FileResponse
|
||||
|
@ -691,26 +692,40 @@ class MasterView(View):
|
|||
|
||||
def normalize_uploads(self, form, skip=None):
|
||||
uploads = {}
|
||||
|
||||
def normalize(filedict):
|
||||
tempdir = tempfile.mkdtemp()
|
||||
filepath = os.path.join(tempdir, filedict['filename'])
|
||||
tmpinfo = form.deform_form[node.name].widget.tmpstore.get(filedict['uid'])
|
||||
tmpdata = tmpinfo['fp'].read()
|
||||
with open(filepath, 'wb') as f:
|
||||
f.write(tmpdata)
|
||||
return {'tempdir': tempdir,
|
||||
'temp_path': filepath}
|
||||
|
||||
for node in form.schema:
|
||||
if isinstance(node.typ, deform.FileData):
|
||||
if skip and node.name in skip:
|
||||
continue
|
||||
# TODO: does form ever *not* have 'validated' attr here?
|
||||
if hasattr(form, 'validated'):
|
||||
filedict = form.validated.get(node.name)
|
||||
if skip and node.name in skip:
|
||||
continue
|
||||
|
||||
value = form.validated.get(node.name)
|
||||
if not value:
|
||||
continue
|
||||
|
||||
if isinstance(value, dfwidget.filedict):
|
||||
uploads[node.name] = normalize(value)
|
||||
|
||||
elif not isinstance(value, dict):
|
||||
|
||||
try:
|
||||
values = iter(value)
|
||||
except TypeError:
|
||||
pass
|
||||
else:
|
||||
filedict = self.form_deserialized.get(node.name)
|
||||
if filedict:
|
||||
tempdir = tempfile.mkdtemp()
|
||||
filepath = os.path.join(tempdir, filedict['filename'])
|
||||
tmpinfo = form.deform_form[node.name].widget.tmpstore.get(filedict['uid'])
|
||||
tmpdata = tmpinfo['fp'].read()
|
||||
with open(filepath, 'wb') as f:
|
||||
f.write(tmpdata)
|
||||
uploads[node.name] = {
|
||||
'tempdir': tempdir,
|
||||
'temp_path': filepath,
|
||||
}
|
||||
for value in values:
|
||||
if isinstance(value, dfwidget.filedict):
|
||||
uploads.setdefault(node.name, []).append(
|
||||
normalize(value))
|
||||
|
||||
return uploads
|
||||
|
||||
def process_uploads(self, obj, form, uploads):
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
################################################################################
|
||||
#
|
||||
# Rattail -- Retail Software Framework
|
||||
# Copyright © 2010-2022 Lance Edgar
|
||||
# Copyright © 2010-2023 Lance Edgar
|
||||
#
|
||||
# This file is part of Rattail.
|
||||
#
|
||||
|
@ -26,6 +26,7 @@ Views for 'receiving' (purchasing) batches
|
|||
|
||||
from __future__ import unicode_literals, absolute_import
|
||||
|
||||
import os
|
||||
import re
|
||||
import decimal
|
||||
import logging
|
||||
|
@ -551,6 +552,15 @@ class ReceivingBatchView(PurchasingBatchView):
|
|||
if not self.editing:
|
||||
f.remove_field('order_quantities_known')
|
||||
|
||||
# multiple invoice files (if applicable)
|
||||
if (not self.creating
|
||||
and batch.get_param('receiving_workflow') == 'from_multi_invoice'):
|
||||
|
||||
if 'invoice_files' not in f:
|
||||
f.insert_before('invoice_file', 'invoice_files')
|
||||
f.set_renderer('invoice_files', self.render_invoice_files)
|
||||
f.set_readonly('invoice_files', True)
|
||||
|
||||
# invoice totals
|
||||
f.set_label('invoice_total', "Invoice Total (Orig.)")
|
||||
f.set_label('invoice_total_calculated', "Invoice Total (Calc.)")
|
||||
|
@ -584,6 +594,17 @@ class ReceivingBatchView(PurchasingBatchView):
|
|||
'invoice_date',
|
||||
'invoice_number')
|
||||
|
||||
elif workflow == 'from_multi_invoice':
|
||||
if 'invoice_files' not in f:
|
||||
f.insert_before('invoice_file', 'invoice_files')
|
||||
f.set_type('invoice_files', 'multi_file')
|
||||
f.set_required('invoice_parser_key')
|
||||
f.remove('truck_dump_batch_uuid',
|
||||
'po_number',
|
||||
'invoice_file',
|
||||
'invoice_date',
|
||||
'invoice_number')
|
||||
|
||||
elif workflow == 'from_po':
|
||||
f.remove('truck_dump_batch_uuid',
|
||||
'date_ordered',
|
||||
|
@ -620,12 +641,31 @@ class ReceivingBatchView(PurchasingBatchView):
|
|||
'invoice_date',
|
||||
'invoice_number')
|
||||
|
||||
def render_invoice_files(self, batch, field):
|
||||
datadir = self.batch_handler.datadir(batch)
|
||||
items = []
|
||||
for filename in batch.get_param('invoice_files', []):
|
||||
path = os.path.join(datadir, filename)
|
||||
url = self.get_action_url('download', batch,
|
||||
_query={'filename': filename})
|
||||
link = self.render_file_field(path, url)
|
||||
items.append(HTML.tag('li', c=[link]))
|
||||
return HTML.tag('ul', c=items)
|
||||
|
||||
def render_receiving_workflow(self, batch, field):
|
||||
key = self.request.matchdict['workflow_key']
|
||||
info = self.handler.receiving_workflow_info(key)
|
||||
if info:
|
||||
return info['display']
|
||||
|
||||
def get_visible_params(self, batch):
|
||||
params = super(ReceivingBatchView, self).get_visible_params(batch)
|
||||
|
||||
# remove this since we show it separately
|
||||
params.pop('invoice_files', None)
|
||||
|
||||
return params
|
||||
|
||||
def template_kwargs_create(self, **kwargs):
|
||||
kwargs = super(ReceivingBatchView, self).template_kwargs_create(**kwargs)
|
||||
if self.handler.allow_truck_dump_receiving():
|
||||
|
@ -655,6 +695,8 @@ class ReceivingBatchView(PurchasingBatchView):
|
|||
kwargs.pop('truck_dump_batch_uuid', None)
|
||||
elif batch_type == 'from_invoice':
|
||||
pass
|
||||
elif batch_type == 'from_multi_invoice':
|
||||
pass
|
||||
elif batch_type == 'from_po':
|
||||
# TODO: how to best handle this field? this doesn't seem flexible
|
||||
kwargs['purchase_key'] = batch.purchase_uuid
|
||||
|
@ -1952,6 +1994,9 @@ class ReceivingBatchView(PurchasingBatchView):
|
|||
{'section': 'rattail.batch',
|
||||
'option': 'purchase.allow_receiving_from_invoice',
|
||||
'type': bool},
|
||||
{'section': 'rattail.batch',
|
||||
'option': 'purchase.allow_receiving_from_multi_invoice',
|
||||
'type': bool},
|
||||
{'section': 'rattail.batch',
|
||||
'option': 'purchase.allow_receiving_from_purchase_order',
|
||||
'type': bool},
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue