Add basic support for receiving from multiple invoice files

This commit is contained in:
Lance Edgar 2023-01-10 16:46:21 -06:00
parent 2b7ebedb22
commit dfa4178204
10 changed files with 295 additions and 40 deletions

View file

@ -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

View file

@ -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):

View file

@ -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},