Add "most of" support for truck dump receiving
still not complete, but conceptually it sort of is...
This commit is contained in:
parent
805a1afa3f
commit
cd7922f204
|
@ -42,6 +42,7 @@ import deform
|
||||||
from colanderalchemy import SQLAlchemySchemaNode
|
from colanderalchemy import SQLAlchemySchemaNode
|
||||||
from colanderalchemy.schema import _creation_order
|
from colanderalchemy.schema import _creation_order
|
||||||
from deform import widget as dfwidget
|
from deform import widget as dfwidget
|
||||||
|
from pyramid_deform import SessionFileUploadTempStore
|
||||||
from pyramid.renderers import render
|
from pyramid.renderers import render
|
||||||
from webhelpers2.html import tags, HTML
|
from webhelpers2.html import tags, HTML
|
||||||
|
|
||||||
|
@ -585,6 +586,11 @@ class Form(object):
|
||||||
self.set_widget(key, dfwidget.TextAreaWidget(cols=80, rows=8))
|
self.set_widget(key, dfwidget.TextAreaWidget(cols=80, rows=8))
|
||||||
elif type_ == 'text':
|
elif type_ == 'text':
|
||||||
self.set_widget(key, dfwidget.TextAreaWidget(cols=80, rows=8))
|
self.set_widget(key, dfwidget.TextAreaWidget(cols=80, rows=8))
|
||||||
|
elif type_ == 'file':
|
||||||
|
tmpstore = SessionFileUploadTempStore(self.request)
|
||||||
|
self.set_node(key, colander.SchemaNode(deform.FileData(),
|
||||||
|
widget=dfwidget.FileUploadWidget(tmpstore),
|
||||||
|
title=self.get_label(key)))
|
||||||
else:
|
else:
|
||||||
raise ValueError("unknown type for '{}' field: {}".format(key, type_))
|
raise ValueError("unknown type for '{}' field: {}".format(key, type_))
|
||||||
|
|
||||||
|
|
|
@ -61,7 +61,13 @@
|
||||||
|
|
||||||
<%def name="execute_button()">
|
<%def name="execute_button()">
|
||||||
% if not batch.executed and request.has_perm('{}.execute'.format(permission_prefix)):
|
% if not batch.executed and request.has_perm('{}.execute'.format(permission_prefix)):
|
||||||
<button type="button" id="execute-batch"${'' if execute_enabled else ' disabled="disabled"'}>${execute_title}</button>
|
% if execute_enabled:
|
||||||
|
<button type="button" id="execute-batch">${execute_title}</button>
|
||||||
|
% elif why_not_execute:
|
||||||
|
<button type="button" id="execute-batch" disabled="disabled" title="${why_not_execute}">${execute_title}</button>
|
||||||
|
% else:
|
||||||
|
<button type="button" id="execute-batch" disabled="disabled">${execute_title}</button>
|
||||||
|
% endif
|
||||||
% endif
|
% endif
|
||||||
</%def>
|
</%def>
|
||||||
|
|
||||||
|
|
67
tailbone/templates/receiving/create.mako
Normal file
67
tailbone/templates/receiving/create.mako
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
## -*- coding: utf-8; -*-
|
||||||
|
<%inherit file="/batch/create.mako" />
|
||||||
|
|
||||||
|
<%def name="extra_javascript()">
|
||||||
|
${parent.extra_javascript()}
|
||||||
|
${self.func_show_batch_type()}
|
||||||
|
<script type="text/javascript">
|
||||||
|
|
||||||
|
% if master.allow_truck_dump:
|
||||||
|
var batch_vendor_map = ${json.dumps(batch_vendor_map)|n};
|
||||||
|
% endif
|
||||||
|
|
||||||
|
$(function() {
|
||||||
|
|
||||||
|
$('.batch_type select').on('selectmenuchange', function(event, ui) {
|
||||||
|
show_batch_type(ui.item.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
$('.truck_dump_batch_uuid select').on('selectmenuchange', function(event, ui) {
|
||||||
|
var form = $(this).parents('form');
|
||||||
|
var uuid = ui.item.value ? batch_vendor_map[ui.item.value] : '';
|
||||||
|
form.find('input[name="vendor_uuid"]').val(uuid);
|
||||||
|
});
|
||||||
|
|
||||||
|
show_batch_type();
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</%def>
|
||||||
|
|
||||||
|
<%def name="func_show_batch_type()">
|
||||||
|
<script type="text/javascript">
|
||||||
|
|
||||||
|
function show_batch_type(batch_type) {
|
||||||
|
|
||||||
|
if (batch_type === undefined) {
|
||||||
|
batch_type = $('.field-wrapper.batch_type select').val();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (batch_type == 'from_scratch') {
|
||||||
|
$('.field-wrapper.truck_dump_batch_uuid').hide();
|
||||||
|
$('.field-wrapper.invoice_file').hide();
|
||||||
|
$('.field-wrapper.invoice_parser_key').hide();
|
||||||
|
$('.field-wrapper.vendor_uuid').show();
|
||||||
|
$('.field-wrapper.date_ordered').show();
|
||||||
|
$('.field-wrapper.date_received').show();
|
||||||
|
$('.field-wrapper.po_number').show();
|
||||||
|
$('.field-wrapper.invoice_date').show();
|
||||||
|
$('.field-wrapper.invoice_number').show();
|
||||||
|
|
||||||
|
} else if (batch_type == 'truck_dump') {
|
||||||
|
$('.field-wrapper.truck_dump_batch_uuid').show();
|
||||||
|
$('.field-wrapper.invoice_file').show();
|
||||||
|
$('.field-wrapper.invoice_parser_key').show();
|
||||||
|
$('.field-wrapper.vendor_uuid').hide();
|
||||||
|
$('.field-wrapper.date_ordered').hide();
|
||||||
|
$('.field-wrapper.date_received').hide();
|
||||||
|
$('.field-wrapper.po_number').hide();
|
||||||
|
$('.field-wrapper.invoice_date').hide();
|
||||||
|
$('.field-wrapper.invoice_number').hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</%def>
|
||||||
|
|
||||||
|
${parent.body()}
|
|
@ -42,11 +42,9 @@ from rattail.util import load_object, prettify
|
||||||
|
|
||||||
import colander
|
import colander
|
||||||
import deform
|
import deform
|
||||||
from deform import widget as dfwidget
|
|
||||||
from pyramid import httpexceptions
|
from pyramid import httpexceptions
|
||||||
from pyramid.renderers import render_to_response
|
from pyramid.renderers import render_to_response
|
||||||
from pyramid.response import FileResponse
|
from pyramid.response import FileResponse
|
||||||
from pyramid_deform import SessionFileUploadTempStore
|
|
||||||
from webhelpers2.html import HTML, tags
|
from webhelpers2.html import HTML, tags
|
||||||
|
|
||||||
from tailbone import forms, grids
|
from tailbone import forms, grids
|
||||||
|
@ -131,6 +129,9 @@ class BatchMasterView(MasterView):
|
||||||
return load_object(spec)(self.rattail_config)
|
return load_object(spec)(self.rattail_config)
|
||||||
return self.batch_handler_class(self.rattail_config)
|
return self.batch_handler_class(self.rattail_config)
|
||||||
|
|
||||||
|
def download_path(self, batch, filename):
|
||||||
|
return self.rattail_config.batch_filepath(batch.batch_key, batch.uuid, filename)
|
||||||
|
|
||||||
def template_kwargs_view(self, **kwargs):
|
def template_kwargs_view(self, **kwargs):
|
||||||
batch = kwargs['instance']
|
batch = kwargs['instance']
|
||||||
kwargs['batch'] = batch
|
kwargs['batch'] = batch
|
||||||
|
@ -140,6 +141,8 @@ class BatchMasterView(MasterView):
|
||||||
if kwargs['execute_enabled']:
|
if kwargs['execute_enabled']:
|
||||||
url = self.get_action_url('execute', batch)
|
url = self.get_action_url('execute', batch)
|
||||||
kwargs['execute_form'] = self.make_execute_form(batch, action_url=url)
|
kwargs['execute_form'] = self.make_execute_form(batch, action_url=url)
|
||||||
|
else:
|
||||||
|
kwargs['why_not_execute'] = self.handler.why_not_execute(batch)
|
||||||
return kwargs
|
return kwargs
|
||||||
|
|
||||||
def allow_worksheet(self, batch):
|
def allow_worksheet(self, batch):
|
||||||
|
@ -278,9 +281,6 @@ class BatchMasterView(MasterView):
|
||||||
return status_code_text
|
return status_code_text
|
||||||
return render_status
|
return render_status
|
||||||
|
|
||||||
def download_path(self, batch, filename):
|
|
||||||
return self.rattail_config.batch_filepath(batch.batch_key, batch.uuid, filename)
|
|
||||||
|
|
||||||
def render_user(self, batch, field):
|
def render_user(self, batch, field):
|
||||||
user = getattr(batch, field)
|
user = getattr(batch, field)
|
||||||
if not user:
|
if not user:
|
||||||
|
@ -312,6 +312,7 @@ class BatchMasterView(MasterView):
|
||||||
f.remove_field('complete')
|
f.remove_field('complete')
|
||||||
|
|
||||||
def save_create_form(self, form):
|
def save_create_form(self, form):
|
||||||
|
uploads = self.normalize_uploads(form, skip=['filename'])
|
||||||
self.before_create(form)
|
self.before_create(form)
|
||||||
|
|
||||||
session = self.Session()
|
session = self.Session()
|
||||||
|
@ -346,17 +347,15 @@ class BatchMasterView(MasterView):
|
||||||
batch = self.handler.make_batch(session, **kwargs)
|
batch = self.handler.make_batch(session, **kwargs)
|
||||||
|
|
||||||
self.Session.flush()
|
self.Session.flush()
|
||||||
|
self.process_uploads(batch, form, uploads)
|
||||||
# TODO: this needs work yet surely...
|
|
||||||
# if batch has input data file, let handler properly establish that
|
|
||||||
if 'filename' in form.schema:
|
|
||||||
if filedict:
|
|
||||||
self.handler.set_input_file(batch, filepath)
|
|
||||||
os.remove(filepath)
|
|
||||||
os.rmdir(tempdir)
|
|
||||||
|
|
||||||
return batch
|
return batch
|
||||||
|
|
||||||
|
def process_uploads(self, batch, form, uploads):
|
||||||
|
for key, upload in six.iteritems(uploads):
|
||||||
|
self.handler.set_input_file(batch, upload['temp_path'], attr=key)
|
||||||
|
os.remove(upload['temp_path'])
|
||||||
|
os.rmdir(upload['tempdir'])
|
||||||
|
|
||||||
def save_mobile_create_form(self, form):
|
def save_mobile_create_form(self, form):
|
||||||
self.before_create(form)
|
self.before_create(form)
|
||||||
session = self.Session()
|
session = self.Session()
|
||||||
|
@ -536,6 +535,39 @@ class BatchMasterView(MasterView):
|
||||||
url = self.request.route_url('{}.delete_rows'.format(self.get_route_prefix()), uuid=batch.uuid)
|
url = self.request.route_url('{}.delete_rows'.format(self.get_route_prefix()), uuid=batch.uuid)
|
||||||
return HTML.tag('p', c=[tags.link_to("Delete all rows matching current search", url)])
|
return HTML.tag('p', c=[tags.link_to("Delete all rows matching current search", url)])
|
||||||
|
|
||||||
|
def make_row_grid_kwargs(self, **kwargs):
|
||||||
|
"""
|
||||||
|
Whether or not rows may be edited or deleted will depend partially on
|
||||||
|
whether the parent batch has been executed.
|
||||||
|
"""
|
||||||
|
batch = self.get_instance()
|
||||||
|
|
||||||
|
# TODO: most of this logic is copied from MasterView, should refactor/merge somehow...
|
||||||
|
if 'main_actions' not in kwargs:
|
||||||
|
actions = []
|
||||||
|
|
||||||
|
# view action
|
||||||
|
if self.rows_viewable:
|
||||||
|
view = lambda r, i: self.get_row_action_url('view', r)
|
||||||
|
actions.append(grids.GridAction('view', icon='zoomin', url=view))
|
||||||
|
|
||||||
|
# edit and delete are NOT allowed after execution, or if batch is "complete"
|
||||||
|
if not batch.executed and not batch.complete:
|
||||||
|
|
||||||
|
# edit action
|
||||||
|
if self.rows_editable:
|
||||||
|
actions.append(grids.GridAction('edit', icon='pencil', url=self.row_edit_action_url))
|
||||||
|
|
||||||
|
# delete action
|
||||||
|
permission_prefix = self.get_permission_prefix()
|
||||||
|
if self.rows_deletable and self.request.has_perm('{}.delete_row'.format(permission_prefix)):
|
||||||
|
actions.append(grids.GridAction('delete', icon='trash', url=self.row_delete_action_url))
|
||||||
|
kwargs.setdefault('delete_speedbump', self.rows_deletable_speedbump)
|
||||||
|
|
||||||
|
kwargs['main_actions'] = actions
|
||||||
|
|
||||||
|
return super(BatchMasterView, self).make_row_grid_kwargs(**kwargs)
|
||||||
|
|
||||||
def make_row_grid_tools(self, batch):
|
def make_row_grid_tools(self, batch):
|
||||||
return (self.make_default_row_grid_tools(batch) or '') + (self.make_batch_row_grid_tools(batch) or '')
|
return (self.make_default_row_grid_tools(batch) or '') + (self.make_batch_row_grid_tools(batch) or '')
|
||||||
|
|
||||||
|
@ -555,10 +587,7 @@ class BatchMasterView(MasterView):
|
||||||
"""
|
"""
|
||||||
Delete all data (files etc.) for the batch.
|
Delete all data (files etc.) for the batch.
|
||||||
"""
|
"""
|
||||||
if hasattr(batch, 'delete_data'):
|
self.handler.delete(batch)
|
||||||
batch.delete_data(self.rattail_config)
|
|
||||||
if hasattr(batch, 'data_rows'):
|
|
||||||
del batch.data_rows[:]
|
|
||||||
super(BatchMasterView, self).delete_instance(batch)
|
super(BatchMasterView, self).delete_instance(batch)
|
||||||
|
|
||||||
def get_fallback_templates(self, template, mobile=False):
|
def get_fallback_templates(self, template, mobile=False):
|
||||||
|
@ -1153,6 +1182,7 @@ class FileBatchMasterView(BatchMasterView):
|
||||||
"""
|
"""
|
||||||
Base class for all file-based "batch master" views.
|
Base class for all file-based "batch master" views.
|
||||||
"""
|
"""
|
||||||
|
downloadable = True
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def upload_dir(self):
|
def upload_dir(self):
|
||||||
|
@ -1171,62 +1201,26 @@ class FileBatchMasterView(BatchMasterView):
|
||||||
|
|
||||||
def configure_form(self, f):
|
def configure_form(self, f):
|
||||||
super(FileBatchMasterView, self).configure_form(f)
|
super(FileBatchMasterView, self).configure_form(f)
|
||||||
|
batch = f.model_instance
|
||||||
|
|
||||||
# filename
|
# filename
|
||||||
f.set_renderer('filename', self.render_filename)
|
|
||||||
f.set_label('filename', "Data File")
|
|
||||||
if self.editing:
|
|
||||||
f.set_readonly('filename')
|
|
||||||
|
|
||||||
if self.creating:
|
if self.creating:
|
||||||
if 'filename' not in f.fields:
|
# TODO: what's up with this re-insertion again..?
|
||||||
f.fields.insert(0, 'filename')
|
# if 'filename' not in f.fields:
|
||||||
tmpstore = SessionFileUploadTempStore(self.request)
|
# f.fields.insert(0, 'filename')
|
||||||
f.set_node('filename', colander.SchemaNode(deform.FileData(), widget=dfwidget.FileUploadWidget(tmpstore)))
|
f.set_type('filename', 'file')
|
||||||
|
else:
|
||||||
|
f.set_readonly('filename')
|
||||||
|
f.set_renderer('filename', self.render_filename)
|
||||||
|
|
||||||
def render_filename(self, batch, field):
|
def render_filename(self, batch, field):
|
||||||
path = batch.filepath(self.rattail_config, filename=batch.filename)
|
filename = getattr(batch, field)
|
||||||
|
if not filename:
|
||||||
|
return ""
|
||||||
|
path = batch.filepath(self.rattail_config, filename=filename)
|
||||||
url = self.get_action_url('download', batch)
|
url = self.get_action_url('download', batch)
|
||||||
return self.render_file_field(path, url)
|
return self.render_file_field(path, url)
|
||||||
|
|
||||||
def download(self):
|
|
||||||
"""
|
|
||||||
View for downloading the data file associated with a batch.
|
|
||||||
"""
|
|
||||||
batch = self.get_instance()
|
|
||||||
if not batch:
|
|
||||||
raise httpexceptions.HTTPNotFound()
|
|
||||||
path = batch.filepath(self.rattail_config)
|
|
||||||
response = FileResponse(path, request=self.request)
|
|
||||||
response.headers[b'Content-Length'] = six.binary_type(os.path.getsize(path))
|
|
||||||
filename = os.path.basename(batch.filename).encode('ascii', 'replace')
|
|
||||||
response.headers[b'Content-Disposition'] = b'attachment; filename="{}"'.format(filename)
|
|
||||||
return response
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def defaults(cls, config):
|
|
||||||
cls._filebatch_defaults(config)
|
|
||||||
cls._batch_defaults(config)
|
|
||||||
cls._defaults(config)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _filebatch_defaults(cls, config):
|
|
||||||
route_prefix = cls.get_route_prefix()
|
|
||||||
url_prefix = cls.get_url_prefix()
|
|
||||||
permission_prefix = cls.get_permission_prefix()
|
|
||||||
model_title = cls.get_model_title()
|
|
||||||
model_title_plural = cls.get_model_title_plural()
|
|
||||||
|
|
||||||
# fix permission group title
|
|
||||||
config.add_tailbone_permission_group(permission_prefix, model_title_plural)
|
|
||||||
|
|
||||||
# download batch data file
|
|
||||||
config.add_route('{}.download'.format(route_prefix), '{}/{{uuid}}/download'.format(url_prefix))
|
|
||||||
config.add_view(cls, attr='download', route_name='{}.download'.format(route_prefix),
|
|
||||||
permission='{}.download'.format(permission_prefix))
|
|
||||||
config.add_tailbone_permission(permission_prefix, '{}.download'.format(permission_prefix),
|
|
||||||
"Download existing {} data file".format(model_title))
|
|
||||||
|
|
||||||
|
|
||||||
class MobileBatchStatusFilter(grids.filters.MobileFilter):
|
class MobileBatchStatusFilter(grids.filters.MobileFilter):
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,7 @@ Model Master View
|
||||||
from __future__ import unicode_literals, absolute_import
|
from __future__ import unicode_literals, absolute_import
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import tempfile
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
import six
|
import six
|
||||||
|
@ -633,14 +634,39 @@ class MasterView(View):
|
||||||
return self.render_to_response('create', {'form': form}, mobile=True)
|
return self.render_to_response('create', {'form': form}, mobile=True)
|
||||||
|
|
||||||
def save_create_form(self, form):
|
def save_create_form(self, form):
|
||||||
|
uploads = self.normalize_uploads(form)
|
||||||
self.before_create(form)
|
self.before_create(form)
|
||||||
with self.Session().no_autoflush:
|
with self.Session().no_autoflush:
|
||||||
obj = self.objectify(form, self.form_deserialized)
|
obj = self.objectify(form, self.form_deserialized)
|
||||||
self.before_create_flush(obj, form)
|
self.before_create_flush(obj, form)
|
||||||
self.Session.add(obj)
|
self.Session.add(obj)
|
||||||
self.Session.flush()
|
self.Session.flush()
|
||||||
|
self.process_uploads(obj, form, uploads)
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
|
def normalize_uploads(self, form, skip=None):
|
||||||
|
uploads = {}
|
||||||
|
for node in form.schema:
|
||||||
|
if isinstance(node.typ, deform.FileData):
|
||||||
|
if skip and node.name in skip:
|
||||||
|
continue
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
return uploads
|
||||||
|
|
||||||
|
def process_uploads(self, obj, form, uploads):
|
||||||
|
pass
|
||||||
|
|
||||||
def before_create_flush(self, obj, form):
|
def before_create_flush(self, obj, form):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -1230,6 +1256,8 @@ class MasterView(View):
|
||||||
"""
|
"""
|
||||||
obj = self.get_instance()
|
obj = self.get_instance()
|
||||||
filename = self.request.GET.get('filename', None)
|
filename = self.request.GET.get('filename', None)
|
||||||
|
if not filename:
|
||||||
|
raise self.notfound()
|
||||||
path = self.download_path(obj, filename)
|
path = self.download_path(obj, filename)
|
||||||
response = FileResponse(path, request=self.request)
|
response = FileResponse(path, request=self.request)
|
||||||
response.content_length = os.path.getsize(path)
|
response.content_length = os.path.getsize(path)
|
||||||
|
@ -2124,6 +2152,14 @@ class MasterView(View):
|
||||||
"""
|
"""
|
||||||
return getattr(cls, 'mobile_row_form_factory', forms.Form)
|
return getattr(cls, 'mobile_row_form_factory', forms.Form)
|
||||||
|
|
||||||
|
def render_downloadable_file(self, obj, field):
|
||||||
|
filename = getattr(obj, field)
|
||||||
|
if not filename:
|
||||||
|
return ""
|
||||||
|
path = self.download_path(obj, filename)
|
||||||
|
url = self.get_action_url('download', obj, _query={'filename': filename})
|
||||||
|
return self.render_file_field(path, url)
|
||||||
|
|
||||||
def render_file_field(self, path, url=None, filename=None):
|
def render_file_field(self, path, url=None, filename=None):
|
||||||
"""
|
"""
|
||||||
Convenience for rendering a file with optional download link
|
Convenience for rendering a file with optional download link
|
||||||
|
|
|
@ -26,6 +26,8 @@ Views for "true" purchase credits
|
||||||
|
|
||||||
from __future__ import unicode_literals, absolute_import
|
from __future__ import unicode_literals, absolute_import
|
||||||
|
|
||||||
|
import six
|
||||||
|
|
||||||
from rattail.db import model
|
from rattail.db import model
|
||||||
|
|
||||||
from webhelpers2.html import tags
|
from webhelpers2.html import tags
|
||||||
|
@ -70,12 +72,13 @@ class PurchaseCreditView(MasterView):
|
||||||
|
|
||||||
g.set_sort_defaults('date_received', 'desc')
|
g.set_sort_defaults('date_received', 'desc')
|
||||||
|
|
||||||
|
g.set_enum('status', self.enum.PURCHASE_CREDIT_STATUS)
|
||||||
g.filters['status'].set_value_renderer(grids.filters.EnumValueRenderer(self.enum.PURCHASE_CREDIT_STATUS))
|
g.filters['status'].set_value_renderer(grids.filters.EnumValueRenderer(self.enum.PURCHASE_CREDIT_STATUS))
|
||||||
g.filters['status'].default_active = True
|
g.filters['status'].default_active = True
|
||||||
g.filters['status'].default_verb = 'not_equal'
|
g.filters['status'].default_verb = 'not_equal'
|
||||||
g.filters['status'].default_value = self.enum.PURCHASE_CREDIT_STATUS_SATISFIED
|
# TODO: should not have to convert value to string!
|
||||||
|
g.filters['status'].default_value = six.text_type(self.enum.PURCHASE_CREDIT_STATUS_SATISFIED)
|
||||||
|
|
||||||
g.set_enum('status', self.enum.PURCHASE_CREDIT_STATUS)
|
|
||||||
# g.set_type('upc', 'gpc')
|
# g.set_type('upc', 'gpc')
|
||||||
g.set_type('cases_shorted', 'quantity')
|
g.set_type('cases_shorted', 'quantity')
|
||||||
g.set_type('units_shorted', 'quantity')
|
g.set_type('units_shorted', 'quantity')
|
||||||
|
|
|
@ -48,6 +48,7 @@ class PurchasingBatchView(BatchMasterView):
|
||||||
model_row_class = model.PurchaseBatchRow
|
model_row_class = model.PurchaseBatchRow
|
||||||
default_handler_spec = 'rattail.batch.purchase:PurchaseBatchHandler'
|
default_handler_spec = 'rattail.batch.purchase:PurchaseBatchHandler'
|
||||||
supports_new_product = False
|
supports_new_product = False
|
||||||
|
cloneable = True
|
||||||
|
|
||||||
grid_columns = [
|
grid_columns = [
|
||||||
'id',
|
'id',
|
||||||
|
@ -513,22 +514,33 @@ class PurchasingBatchView(BatchMasterView):
|
||||||
kwargs = super(PurchasingBatchView, self).get_batch_kwargs(batch, mobile=mobile)
|
kwargs = super(PurchasingBatchView, self).get_batch_kwargs(batch, mobile=mobile)
|
||||||
kwargs['mode'] = self.batch_mode
|
kwargs['mode'] = self.batch_mode
|
||||||
kwargs['truck_dump'] = batch.truck_dump
|
kwargs['truck_dump'] = batch.truck_dump
|
||||||
|
kwargs['invoice_parser_key'] = batch.invoice_parser_key
|
||||||
|
|
||||||
if batch.store:
|
if batch.store:
|
||||||
kwargs['store'] = batch.store
|
kwargs['store'] = batch.store
|
||||||
elif batch.store_uuid:
|
elif batch.store_uuid:
|
||||||
kwargs['store_uuid'] = batch.store_uuid
|
kwargs['store_uuid'] = batch.store_uuid
|
||||||
|
|
||||||
|
if batch.truck_dump_batch:
|
||||||
|
kwargs['truck_dump_batch'] = batch.truck_dump_batch
|
||||||
|
elif batch.truck_dump_batch_uuid:
|
||||||
|
kwargs['truck_dump_batch_uuid'] = batch.truck_dump_batch_uuid
|
||||||
|
|
||||||
if batch.vendor:
|
if batch.vendor:
|
||||||
kwargs['vendor'] = batch.vendor
|
kwargs['vendor'] = batch.vendor
|
||||||
elif batch.vendor_uuid:
|
elif batch.vendor_uuid:
|
||||||
kwargs['vendor_uuid'] = batch.vendor_uuid
|
kwargs['vendor_uuid'] = batch.vendor_uuid
|
||||||
|
|
||||||
if batch.department:
|
if batch.department:
|
||||||
kwargs['department'] = batch.department
|
kwargs['department'] = batch.department
|
||||||
elif batch.department_uuid:
|
elif batch.department_uuid:
|
||||||
kwargs['department_uuid'] = batch.department_uuid
|
kwargs['department_uuid'] = batch.department_uuid
|
||||||
|
|
||||||
if batch.buyer:
|
if batch.buyer:
|
||||||
kwargs['buyer'] = batch.buyer
|
kwargs['buyer'] = batch.buyer
|
||||||
elif batch.buyer_uuid:
|
elif batch.buyer_uuid:
|
||||||
kwargs['buyer_uuid'] = batch.buyer_uuid
|
kwargs['buyer_uuid'] = batch.buyer_uuid
|
||||||
|
|
||||||
kwargs['po_number'] = batch.po_number
|
kwargs['po_number'] = batch.po_number
|
||||||
kwargs['po_total'] = batch.po_total
|
kwargs['po_total'] = batch.po_total
|
||||||
|
|
||||||
|
@ -600,7 +612,9 @@ class PurchasingBatchView(BatchMasterView):
|
||||||
def row_grid_extra_class(self, row, i):
|
def row_grid_extra_class(self, row, i):
|
||||||
if row.status_code == row.STATUS_PRODUCT_NOT_FOUND:
|
if row.status_code == row.STATUS_PRODUCT_NOT_FOUND:
|
||||||
return 'warning'
|
return 'warning'
|
||||||
if row.status_code in (row.STATUS_INCOMPLETE, row.STATUS_ORDERED_RECEIVED_DIFFER):
|
if row.status_code in (row.STATUS_INCOMPLETE,
|
||||||
|
row.STATUS_ORDERED_RECEIVED_DIFFER,
|
||||||
|
row.STATUS_TRUCKDUMP_UNCLAIMED):
|
||||||
return 'notice'
|
return 'notice'
|
||||||
|
|
||||||
def configure_row_form(self, f):
|
def configure_row_form(self, f):
|
||||||
|
|
|
@ -28,17 +28,19 @@ from __future__ import unicode_literals, absolute_import
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
import six
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
|
|
||||||
from rattail import pod
|
from rattail import pod
|
||||||
from rattail.db import model, api
|
from rattail.db import model, api
|
||||||
from rattail.gpc import GPC
|
from rattail.gpc import GPC
|
||||||
from rattail.util import pretty_quantity, prettify
|
from rattail.util import pretty_quantity, prettify
|
||||||
|
from rattail.vendors.invoices import iter_invoice_parsers, require_invoice_parser
|
||||||
|
|
||||||
import colander
|
import colander
|
||||||
from deform import widget as dfwidget
|
from deform import widget as dfwidget
|
||||||
from pyramid import httpexceptions
|
from pyramid import httpexceptions
|
||||||
from webhelpers2.html import tags
|
from webhelpers2.html import tags, HTML
|
||||||
|
|
||||||
from tailbone import forms, grids
|
from tailbone import forms, grids
|
||||||
from tailbone.views.purchasing import PurchasingBatchView
|
from tailbone.views.purchasing import PurchasingBatchView
|
||||||
|
@ -96,9 +98,8 @@ class ReceivingBatchView(PurchasingBatchView):
|
||||||
model_title = "Receiving Batch"
|
model_title = "Receiving Batch"
|
||||||
model_title_plural = "Receiving Batches"
|
model_title_plural = "Receiving Batches"
|
||||||
index_title = "Receiving"
|
index_title = "Receiving"
|
||||||
creatable = False
|
downloadable = True
|
||||||
rows_editable = True
|
rows_editable = True
|
||||||
rows_deletable = False
|
|
||||||
mobile_creatable = True
|
mobile_creatable = True
|
||||||
mobile_rows_filterable = True
|
mobile_rows_filterable = True
|
||||||
mobile_rows_creatable = True
|
mobile_rows_creatable = True
|
||||||
|
@ -107,6 +108,11 @@ class ReceivingBatchView(PurchasingBatchView):
|
||||||
allow_from_scratch = True
|
allow_from_scratch = True
|
||||||
allow_truck_dump = False
|
allow_truck_dump = False
|
||||||
|
|
||||||
|
labels = {
|
||||||
|
'truck_dump_batch': "Truck Dump Parent",
|
||||||
|
'invoice_parser_key': "Invoice Parser",
|
||||||
|
}
|
||||||
|
|
||||||
grid_columns = [
|
grid_columns = [
|
||||||
'id',
|
'id',
|
||||||
'vendor',
|
'vendor',
|
||||||
|
@ -123,9 +129,14 @@ class ReceivingBatchView(PurchasingBatchView):
|
||||||
|
|
||||||
form_fields = [
|
form_fields = [
|
||||||
'id',
|
'id',
|
||||||
|
'batch_type',
|
||||||
'store',
|
'store',
|
||||||
'vendor',
|
'vendor',
|
||||||
'truck_dump',
|
'truck_dump',
|
||||||
|
'truck_dump_children',
|
||||||
|
'truck_dump_batch',
|
||||||
|
'invoice_file',
|
||||||
|
'invoice_parser_key',
|
||||||
'department',
|
'department',
|
||||||
'purchase',
|
'purchase',
|
||||||
'vendor_email',
|
'vendor_email',
|
||||||
|
@ -143,6 +154,7 @@ class ReceivingBatchView(PurchasingBatchView):
|
||||||
'created',
|
'created',
|
||||||
'created_by',
|
'created_by',
|
||||||
'status_code',
|
'status_code',
|
||||||
|
'rowcount',
|
||||||
'complete',
|
'complete',
|
||||||
'executed',
|
'executed',
|
||||||
'executed_by',
|
'executed_by',
|
||||||
|
@ -203,12 +215,166 @@ class ReceivingBatchView(PurchasingBatchView):
|
||||||
def batch_mode(self):
|
def batch_mode(self):
|
||||||
return self.enum.PURCHASE_BATCH_MODE_RECEIVING
|
return self.enum.PURCHASE_BATCH_MODE_RECEIVING
|
||||||
|
|
||||||
|
def row_editable(self, row):
|
||||||
|
batch = row.batch
|
||||||
|
if batch.truck_dump_batch:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def row_deletable(self, row):
|
||||||
|
batch = row.batch
|
||||||
|
if batch.truck_dump:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
def configure_form(self, f):
|
def configure_form(self, f):
|
||||||
super(ReceivingBatchView, self).configure_form(f)
|
super(ReceivingBatchView, self).configure_form(f)
|
||||||
|
batch = f.model_instance
|
||||||
|
|
||||||
# truck_dump
|
# batch_type
|
||||||
if self.editing:
|
if self.creating:
|
||||||
f.set_readonly('truck_dump')
|
batch_type_values = [
|
||||||
|
('from_scratch', "New from Scratch"),
|
||||||
|
]
|
||||||
|
if self.allow_truck_dump:
|
||||||
|
batch_type_values.append(('truck_dump', "Invoice for Truck Dump"))
|
||||||
|
f.set_widget('batch_type', forms.widgets.JQuerySelectWidget(values=batch_type_values))
|
||||||
|
else:
|
||||||
|
f.remove_field('batch_type')
|
||||||
|
|
||||||
|
# truck_dump*
|
||||||
|
if self.allow_truck_dump:
|
||||||
|
|
||||||
|
# truck_dump
|
||||||
|
if self.creating:
|
||||||
|
f.remove_field('truck_dump')
|
||||||
|
elif batch.truck_dump_batch:
|
||||||
|
f.remove_field('truck_dump')
|
||||||
|
else:
|
||||||
|
f.set_readonly('truck_dump')
|
||||||
|
|
||||||
|
# truck_dump_children
|
||||||
|
if self.viewing:
|
||||||
|
if batch.truck_dump:
|
||||||
|
f.set_renderer('truck_dump_children', self.render_truck_dump_children)
|
||||||
|
else:
|
||||||
|
f.remove_field('truck_dump_children')
|
||||||
|
else:
|
||||||
|
f.remove_field('truck_dump_children')
|
||||||
|
|
||||||
|
# truck_dump_batch
|
||||||
|
if self.creating:
|
||||||
|
f.replace('truck_dump_batch', 'truck_dump_batch_uuid')
|
||||||
|
batches = self.Session.query(model.PurchaseBatch)\
|
||||||
|
.filter(model.PurchaseBatch.mode == self.enum.PURCHASE_BATCH_MODE_RECEIVING)\
|
||||||
|
.filter(model.PurchaseBatch.truck_dump == True)\
|
||||||
|
.filter(model.PurchaseBatch.complete == True)\
|
||||||
|
.filter(model.PurchaseBatch.executed == None)\
|
||||||
|
.order_by(model.PurchaseBatch.id)
|
||||||
|
batch_values = [(b.uuid, six.text_type(b)) for b in batches]
|
||||||
|
batch_values.insert(0, ('', "(please choose)"))
|
||||||
|
f.set_widget('truck_dump_batch_uuid', forms.widgets.JQuerySelectWidget(values=batch_values))
|
||||||
|
f.set_label('truck_dump_batch_uuid', "Truck Dump Parent")
|
||||||
|
elif batch.truck_dump:
|
||||||
|
f.remove_field('truck_dump_batch')
|
||||||
|
elif batch.truck_dump_batch:
|
||||||
|
f.set_readonly('truck_dump_batch')
|
||||||
|
f.set_renderer('truck_dump_batch', self.render_truck_dump_batch)
|
||||||
|
else:
|
||||||
|
f.remove_field('truck_dump_batch')
|
||||||
|
|
||||||
|
else:
|
||||||
|
f.remove_fields('truck_dump',
|
||||||
|
'truck_dump_children',
|
||||||
|
'truck_dump_batch')
|
||||||
|
|
||||||
|
# invoice_file
|
||||||
|
if self.creating:
|
||||||
|
f.set_type('invoice_file', 'file')
|
||||||
|
else:
|
||||||
|
f.set_readonly('invoice_file')
|
||||||
|
f.set_renderer('invoice_file', self.render_downloadable_file)
|
||||||
|
|
||||||
|
# invoice_parser_key
|
||||||
|
if self.creating:
|
||||||
|
parsers = sorted(iter_invoice_parsers(), key=lambda p: p.display)
|
||||||
|
parser_values = [(p.key, p.display) for p in parsers]
|
||||||
|
parser_values.insert(0, ('', "(please choose)"))
|
||||||
|
f.set_widget('invoice_parser_key', forms.widgets.JQuerySelectWidget(values=parser_values))
|
||||||
|
else:
|
||||||
|
f.remove_field('invoice_parser_key')
|
||||||
|
|
||||||
|
# store
|
||||||
|
if self.creating:
|
||||||
|
store = self.rattail_config.get_store(self.Session())
|
||||||
|
f.set_widget('store_uuid', forms.widgets.ReadonlyWidget())
|
||||||
|
f.set_default('store_uuid', store.uuid)
|
||||||
|
f.set_hidden('store_uuid')
|
||||||
|
|
||||||
|
# purchase
|
||||||
|
if self.creating:
|
||||||
|
f.remove_field('purchase')
|
||||||
|
|
||||||
|
# department
|
||||||
|
if self.creating:
|
||||||
|
f.remove_field('department_uuid')
|
||||||
|
|
||||||
|
def template_kwargs_create(self, **kwargs):
|
||||||
|
kwargs = super(ReceivingBatchView, self).template_kwargs_create(**kwargs)
|
||||||
|
if self.allow_truck_dump:
|
||||||
|
vmap = {}
|
||||||
|
batches = self.Session.query(model.PurchaseBatch)\
|
||||||
|
.filter(model.PurchaseBatch.mode == self.enum.PURCHASE_BATCH_MODE_RECEIVING)\
|
||||||
|
.filter(model.PurchaseBatch.truck_dump == True)\
|
||||||
|
.filter(model.PurchaseBatch.complete == True)
|
||||||
|
for batch in batches:
|
||||||
|
vmap[batch.uuid] = batch.vendor_uuid
|
||||||
|
kwargs['batch_vendor_map'] = vmap
|
||||||
|
return kwargs
|
||||||
|
|
||||||
|
def get_batch_kwargs(self, batch, mobile=False):
|
||||||
|
kwargs = super(ReceivingBatchView, self).get_batch_kwargs(batch, mobile=mobile)
|
||||||
|
if not mobile:
|
||||||
|
batch_type = self.request.POST['batch_type']
|
||||||
|
if batch_type == 'from_scratch':
|
||||||
|
kwargs.pop('truck_dump_batch', None)
|
||||||
|
kwargs.pop('truck_dump_batch_uuid', None)
|
||||||
|
elif batch_type == 'truck_dump':
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
raise NotImplementedError
|
||||||
|
return kwargs
|
||||||
|
|
||||||
|
def delete_instance(self, batch):
|
||||||
|
"""
|
||||||
|
Delete all data (files etc.) for the batch.
|
||||||
|
"""
|
||||||
|
truck_dump = batch.truck_dump_batch
|
||||||
|
if batch.truck_dump:
|
||||||
|
for child in batch.truck_dump_children:
|
||||||
|
self.delete_instance(child)
|
||||||
|
super(ReceivingBatchView, self).delete_instance(batch)
|
||||||
|
if truck_dump:
|
||||||
|
self.handler.refresh(truck_dump)
|
||||||
|
|
||||||
|
def render_truck_dump_batch(self, batch, field):
|
||||||
|
truck_dump = batch.truck_dump_batch
|
||||||
|
if not truck_dump:
|
||||||
|
return ""
|
||||||
|
text = six.text_type(truck_dump)
|
||||||
|
url = self.request.route_url('receiving.view', uuid=truck_dump.uuid)
|
||||||
|
return tags.link_to(text, url)
|
||||||
|
|
||||||
|
def render_truck_dump_children(self, batch, field):
|
||||||
|
children = batch.truck_dump_children
|
||||||
|
if not children:
|
||||||
|
return ""
|
||||||
|
items = []
|
||||||
|
for child in children:
|
||||||
|
text = six.text_type(child)
|
||||||
|
url = self.request.route_url('receiving.view', uuid=child.uuid)
|
||||||
|
items.append(HTML.tag('li', c=[tags.link_to(text, url)]))
|
||||||
|
return HTML.tag('ul', c=items)
|
||||||
|
|
||||||
def render_mobile_listitem(self, batch, i):
|
def render_mobile_listitem(self, batch, i):
|
||||||
title = "({}) {} for ${:0,.2f} - {}, {}".format(
|
title = "({}) {} for ${:0,.2f} - {}, {}".format(
|
||||||
|
|
Loading…
Reference in a new issue