Add initial support for vendor invoice batch feature, etc.
Also included: * Add "edit batch" template, refactor "view batch" template. * Tweak form templates to allow specifying form ID and buttons HTML. * Make deleting batch rows only work when editing a batch.
This commit is contained in:
parent
aee69f5a2c
commit
2e8db05717
78
tailbone/templates/batch/crud.mako
Normal file
78
tailbone/templates/batch/crud.mako
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
## -*- coding: utf-8 -*-
|
||||||
|
<%inherit file="/crud.mako" />
|
||||||
|
|
||||||
|
<%def name="title()">${"View" if form.readonly else "Edit"} ${batch_display}</%def>
|
||||||
|
|
||||||
|
<%def name="head_tags()">
|
||||||
|
<script type="text/javascript">
|
||||||
|
$(function() {
|
||||||
|
$('#rows-wrapper').load('${url('{0}.rows'.format(route_prefix), uuid=batch.uuid)}', function() {
|
||||||
|
// TODO: It'd be nice if we didn't have to do this here.
|
||||||
|
$(this).find('button').button();
|
||||||
|
$(this).find('input[type=submit]').button();
|
||||||
|
});
|
||||||
|
$('#save-refresh').click(function() {
|
||||||
|
$('#batch-form').append($('<input type="hidden" name="refresh" value="true" />'));
|
||||||
|
$('#batch-form').submit();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<style type="text/css">
|
||||||
|
#rows-wrapper {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
.grid tr.notice.odd {
|
||||||
|
background-color: #fe8;
|
||||||
|
}
|
||||||
|
.grid tr.notice.even {
|
||||||
|
background-color: #fd6;
|
||||||
|
}
|
||||||
|
.grid tr.notice.hovering {
|
||||||
|
background-color: #ec7;
|
||||||
|
}
|
||||||
|
.grid tr.warning.odd {
|
||||||
|
background-color: #ebb;
|
||||||
|
}
|
||||||
|
.grid tr.warning.even {
|
||||||
|
background-color: #fcc;
|
||||||
|
}
|
||||||
|
.grid tr.warning.hovering {
|
||||||
|
background-color: #daa;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</%def>
|
||||||
|
|
||||||
|
<div class="form-wrapper">
|
||||||
|
|
||||||
|
<ul class="context-menu">
|
||||||
|
<li>${h.link_to("Back to {0}".format(batch_display_plural), url(route_prefix))}</li>
|
||||||
|
% if not batch.executed:
|
||||||
|
% if form.updating:
|
||||||
|
<li>${h.link_to("View this {0}".format(batch_display), url('{0}.view'.format(route_prefix), uuid=batch.uuid))}</li>
|
||||||
|
% endif
|
||||||
|
% if form.readonly and request.has_perm('{0}.edit'.format(permission_prefix)):
|
||||||
|
<li>${h.link_to("Edit this {0}".format(batch_display), url('{0}.edit'.format(route_prefix), uuid=batch.uuid))}</li>
|
||||||
|
% endif
|
||||||
|
% endif
|
||||||
|
% if request.has_perm('{0}.delete'.format(permission_prefix)):
|
||||||
|
<li>${h.link_to("Delete this {0}".format(batch_display), url('{0}.delete'.format(route_prefix), uuid=batch.uuid))}</li>
|
||||||
|
% endif
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
${form.render(form_id='batch-form', buttons=capture(buttons))|n}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<%def name="buttons()">
|
||||||
|
<div class="buttons">
|
||||||
|
% if not form.readonly and batch.refreshable:
|
||||||
|
${h.submit('save-refresh', "Save & Refresh Data")}
|
||||||
|
% endif
|
||||||
|
% if not batch.executed and request.has_perm('{0}.execute'.format(permission_prefix)):
|
||||||
|
## ${h.link_to(execute_title, url('{0}.execute'.format(route_prefix), uuid=batch.uuid))}
|
||||||
|
<button type="button" onclick="location.href = '${url('{0}.execute'.format(route_prefix), uuid=batch.uuid)}';">${execute_title}</button>
|
||||||
|
% endif
|
||||||
|
</div>
|
||||||
|
</%def>
|
||||||
|
|
||||||
|
<div id="rows-wrapper"></div>
|
3
tailbone/templates/batch/edit.mako
Normal file
3
tailbone/templates/batch/edit.mako
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
## -*- coding: utf-8 -*-
|
||||||
|
<%inherit file="/batch/crud.mako" />
|
||||||
|
${parent.body()}
|
|
@ -9,7 +9,10 @@
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="tools">
|
<td class="tools">
|
||||||
<p>${h.link_to("Delete all rows matching current search", url('{0}.rows.bulk_delete'.format(route_prefix), uuid=batch.uuid))}</p>
|
## TODO: Fix this check for edit mode.
|
||||||
|
% if not batch.executed and request.referrer.endswith('/edit'):
|
||||||
|
<p>${h.link_to("Delete all rows matching current search", url('{0}.rows.bulk_delete'.format(route_prefix), uuid=batch.uuid))}</p>
|
||||||
|
% endif
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
|
@ -1,65 +1,3 @@
|
||||||
## -*- coding: utf-8 -*-
|
## -*- coding: utf-8 -*-
|
||||||
<%inherit file="/crud.mako" />
|
<%inherit file="/batch/crud.mako" />
|
||||||
|
${parent.body()}
|
||||||
<%def name="title()">View ${batch_display}</%def>
|
|
||||||
|
|
||||||
<%def name="head_tags()">
|
|
||||||
<script type="text/javascript">
|
|
||||||
$(function() {
|
|
||||||
$('#rows-wrapper').load('${url('{0}.rows'.format(route_prefix), uuid=batch.uuid)}', function() {
|
|
||||||
// TODO: It'd be nice if we didn't have to do this here.
|
|
||||||
$(this).find('button').button();
|
|
||||||
$(this).find('input[type=submit]').button();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
<style type="text/css">
|
|
||||||
#rows-wrapper {
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
.grid tr.notice.odd {
|
|
||||||
background-color: #fe8;
|
|
||||||
}
|
|
||||||
.grid tr.notice.even {
|
|
||||||
background-color: #fd6;
|
|
||||||
}
|
|
||||||
.grid tr.notice.hovering {
|
|
||||||
background-color: #ec7;
|
|
||||||
}
|
|
||||||
.grid tr.warning.odd {
|
|
||||||
background-color: #ebb;
|
|
||||||
}
|
|
||||||
.grid tr.warning.even {
|
|
||||||
background-color: #fcc;
|
|
||||||
}
|
|
||||||
.grid tr.warning.hovering {
|
|
||||||
background-color: #daa;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</%def>
|
|
||||||
|
|
||||||
<div class="form-wrapper">
|
|
||||||
|
|
||||||
<ul class="context-menu">
|
|
||||||
<li>${h.link_to("Back to {0}".format(batch_display_plural), url(route_prefix))}</li>
|
|
||||||
% if not batch.executed:
|
|
||||||
% if request.has_perm('{0}.edit'.format(permission_prefix)):
|
|
||||||
## <li>${h.link_to("Edit this {0}".format(batch_display), url('{0}.edit'.format(route_prefix), uuid=batch.uuid))}</li>
|
|
||||||
% if batch.refreshable:
|
|
||||||
<li>${h.link_to("Refresh Data for this {0}".format(batch_display), url('{0}.refresh'.format(route_prefix), uuid=batch.uuid))}</li>
|
|
||||||
% endif
|
|
||||||
% endif
|
|
||||||
% if request.has_perm('{0}.execute'.format(permission_prefix)):
|
|
||||||
<li>${h.link_to("Execute this {0}".format(batch_display), url('{0}.execute'.format(route_prefix), uuid=batch.uuid))}</li>
|
|
||||||
% endif
|
|
||||||
% endif
|
|
||||||
% if request.has_perm('{0}.delete'.format(permission_prefix)):
|
|
||||||
<li>${h.link_to("Delete this {0}".format(batch_display), url('{0}.delete'.format(route_prefix), uuid=batch.uuid))}</li>
|
|
||||||
% endif
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
${form.render()|n}
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="rows-wrapper"></div>
|
|
||||||
|
|
|
@ -1,16 +1,20 @@
|
||||||
## -*- coding: utf-8 -*-
|
## -*- coding: utf-8 -*-
|
||||||
<div class="form">
|
<div class="form">
|
||||||
${h.form(form.action_url, enctype='multipart/form-data')}
|
${h.form(form.action_url, id=form_id or None, method='post', enctype='multipart/form-data')}
|
||||||
|
|
||||||
${form.fieldset.render()|n}
|
${form.fieldset.render()|n}
|
||||||
|
|
||||||
<div class="buttons">
|
% if buttons:
|
||||||
${h.submit('create', form.create_label if form.creating else form.update_label)}
|
${buttons|n}
|
||||||
% if form.creating and form.allow_successive_creates:
|
% else:
|
||||||
${h.submit('create_and_continue', form.successive_create_label)}
|
<div class="buttons">
|
||||||
% endif
|
${h.submit('create', form.create_label if form.creating else form.update_label)}
|
||||||
<a href="${form.cancel_url}">Cancel</a>
|
% if form.creating and form.allow_successive_creates:
|
||||||
</div>
|
${h.submit('create_and_continue', form.successive_create_label)}
|
||||||
|
% endif
|
||||||
|
<a href="${form.cancel_url}">Cancel</a>
|
||||||
|
</div>
|
||||||
|
% endif
|
||||||
|
|
||||||
${h.end_form()}
|
${h.end_form()}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
## -*- coding: utf-8 -*-
|
## -*- coding: utf-8 -*-
|
||||||
<div class="form">
|
<div class="form">
|
||||||
${form.fieldset.render()|n}
|
${form.fieldset.render()|n}
|
||||||
|
% if buttons:
|
||||||
|
${buttons|n}
|
||||||
|
% endif
|
||||||
</div>
|
</div>
|
||||||
|
|
3
tailbone/templates/vendors/invoices/create.mako
vendored
Normal file
3
tailbone/templates/vendors/invoices/create.mako
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
## -*- coding: utf-8 -*-
|
||||||
|
<%inherit file="/batch/create.mako" />
|
||||||
|
${parent.body()}
|
3
tailbone/templates/vendors/invoices/edit.mako
vendored
Normal file
3
tailbone/templates/vendors/invoices/edit.mako
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
## -*- coding: utf-8 -*-
|
||||||
|
<%inherit file="/batch/edit.mako" />
|
||||||
|
${parent.body()}
|
3
tailbone/templates/vendors/invoices/index.mako
vendored
Normal file
3
tailbone/templates/vendors/invoices/index.mako
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
## -*- coding: utf-8 -*-
|
||||||
|
<%inherit file="/batch/index.mako" />
|
||||||
|
${parent.body()}
|
3
tailbone/templates/vendors/invoices/view.mako
vendored
Normal file
3
tailbone/templates/vendors/invoices/view.mako
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
## -*- coding: utf-8 -*-
|
||||||
|
<%inherit file="/batch/view.mako" />
|
||||||
|
${parent.body()}
|
|
@ -37,7 +37,7 @@ import formalchemy
|
||||||
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.httpexceptions import HTTPFound, HTTPNotFound
|
from pyramid.httpexceptions import HTTPFound, HTTPNotFound
|
||||||
from webhelpers.html.tags import link_to
|
from webhelpers.html.tags import link_to, HTML
|
||||||
|
|
||||||
from rattail.db import model
|
from rattail.db import model
|
||||||
from rattail.db import Session as RatSession
|
from rattail.db import Session as RatSession
|
||||||
|
@ -234,9 +234,9 @@ class BatchGrid(BaseGrid):
|
||||||
if self.request.has_perm('{0}.view'.format(self.permission_prefix)):
|
if self.request.has_perm('{0}.view'.format(self.permission_prefix)):
|
||||||
g.viewable = True
|
g.viewable = True
|
||||||
g.view_route_name = '{0}.view'.format(self.route_prefix)
|
g.view_route_name = '{0}.view'.format(self.route_prefix)
|
||||||
# if self.request.has_perm('{0}.edit'.format(self.permission_prefix)):
|
if self.request.has_perm('{0}.edit'.format(self.permission_prefix)):
|
||||||
# g.editable = True
|
g.editable = True
|
||||||
# g.edit_route_name = '{0}.edit'.format(self.route_prefix)
|
g.edit_route_name = '{0}.edit'.format(self.route_prefix)
|
||||||
if self.request.has_perm('{0}.delete'.format(self.permission_prefix)):
|
if self.request.has_perm('{0}.delete'.format(self.permission_prefix)):
|
||||||
g.deletable = True
|
g.deletable = True
|
||||||
g.delete_route_name = '{0}.delete'.format(self.route_prefix)
|
g.delete_route_name = '{0}.delete'.format(self.route_prefix)
|
||||||
|
@ -321,6 +321,12 @@ class BaseCrud(CrudView):
|
||||||
else:
|
else:
|
||||||
super(BaseCrud, self).flash_create(model)
|
super(BaseCrud, self).flash_create(model)
|
||||||
|
|
||||||
|
def flash_update(self, model):
|
||||||
|
if 'update' in self.flash:
|
||||||
|
self.request.session.flash(self.flash['update'])
|
||||||
|
else:
|
||||||
|
super(BaseCrud, self).flash_update(model)
|
||||||
|
|
||||||
def flash_delete(self, model):
|
def flash_delete(self, model):
|
||||||
if 'delete' in self.flash:
|
if 'delete' in self.flash:
|
||||||
self.request.session.flash(self.flash['delete'])
|
self.request.session.flash(self.flash['delete'])
|
||||||
|
@ -414,6 +420,33 @@ class BatchCrud(BaseCrud):
|
||||||
fs.executed_by,
|
fs.executed_by,
|
||||||
])
|
])
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
"""
|
||||||
|
Don't allow editing a batch which has already been executed.
|
||||||
|
"""
|
||||||
|
batch = self.get_model_from_request()
|
||||||
|
if not batch:
|
||||||
|
return HTTPNotFound()
|
||||||
|
if batch.executed:
|
||||||
|
return HTTPFound(location=self.view_url(batch.uuid))
|
||||||
|
return self.crud(batch)
|
||||||
|
|
||||||
|
def post_create_url(self, form):
|
||||||
|
"""
|
||||||
|
Redirect to view batch after creating a batch.
|
||||||
|
"""
|
||||||
|
batch = form.fieldset.model
|
||||||
|
return self.view_url(batch.uuid)
|
||||||
|
|
||||||
|
def post_update_url(self, form):
|
||||||
|
"""
|
||||||
|
Redirect back to edit batch page after editing a batch, unless the
|
||||||
|
refresh flag is set, in which case do that.
|
||||||
|
"""
|
||||||
|
if self.request.params.get('refresh') == 'true':
|
||||||
|
return self.refresh_url()
|
||||||
|
return self.request.current_route_url()
|
||||||
|
|
||||||
def template_kwargs(self, form):
|
def template_kwargs(self, form):
|
||||||
"""
|
"""
|
||||||
Add some things to the template context: current batch model, batch
|
Add some things to the template context: current batch model, batch
|
||||||
|
@ -425,6 +458,7 @@ class BatchCrud(BaseCrud):
|
||||||
'batch': batch,
|
'batch': batch,
|
||||||
'batch_display': self.batch_display,
|
'batch_display': self.batch_display,
|
||||||
'batch_display_plural': self.batch_display_plural,
|
'batch_display_plural': self.batch_display_plural,
|
||||||
|
'execute_title': self.handler.get_execute_title(batch),
|
||||||
'route_prefix': self.route_prefix,
|
'route_prefix': self.route_prefix,
|
||||||
'permission_prefix': self.permission_prefix,
|
'permission_prefix': self.permission_prefix,
|
||||||
}
|
}
|
||||||
|
@ -511,6 +545,14 @@ class BatchCrud(BaseCrud):
|
||||||
uuid = self.request.matchdict['uuid']
|
uuid = self.request.matchdict['uuid']
|
||||||
return self.request.route_url('{0}.view'.format(self.route_prefix), uuid=uuid)
|
return self.request.route_url('{0}.view'.format(self.route_prefix), uuid=uuid)
|
||||||
|
|
||||||
|
def refresh_url(self, uuid=None):
|
||||||
|
"""
|
||||||
|
Returns the URL for refreshing a batch; defaults to current batch.
|
||||||
|
"""
|
||||||
|
if uuid is None:
|
||||||
|
uuid = self.request.matchdict['uuid']
|
||||||
|
return self.request.route_url('{0}.refresh'.format(self.route_prefix), uuid=uuid)
|
||||||
|
|
||||||
def execute(self):
|
def execute(self):
|
||||||
batch = self.current_batch()
|
batch = self.current_batch()
|
||||||
if self.handler.execute(batch):
|
if self.handler.execute(batch):
|
||||||
|
@ -561,15 +603,15 @@ class FileBatchCrud(BatchCrud):
|
||||||
override this, but :meth:`configure_fieldset()` instead.
|
override this, but :meth:`configure_fieldset()` instead.
|
||||||
"""
|
"""
|
||||||
fs = self.make_fieldset(model)
|
fs = self.make_fieldset(model)
|
||||||
fs.created.set(label="Uploaded", renderer=DateTimeFieldRenderer(self.request.rattail_config))
|
fs.created.set(label="Uploaded", renderer=DateTimeFieldRenderer(self.request.rattail_config), readonly=True)
|
||||||
fs.created_by.set(label="Uploaded by", renderer=UserFieldRenderer)
|
fs.created_by.set(label="Uploaded by", renderer=UserFieldRenderer, readonly=True)
|
||||||
fs.cognized.set(renderer=DateTimeFieldRenderer(self.request.rattail_config))
|
fs.cognized.set(renderer=DateTimeFieldRenderer(self.request.rattail_config))
|
||||||
fs.cognized_by.set(label="Cognized by", renderer=UserFieldRenderer)
|
fs.cognized_by.set(label="Cognized by", renderer=UserFieldRenderer)
|
||||||
fs.executed.set(renderer=DateTimeFieldRenderer(self.request.rattail_config))
|
fs.executed.set(renderer=DateTimeFieldRenderer(self.request.rattail_config))
|
||||||
fs.executed_by.set(label="Executed by", renderer=UserFieldRenderer)
|
fs.executed_by.set(label="Executed by", renderer=UserFieldRenderer)
|
||||||
fs.append(formalchemy.Field('data_file'))
|
fs.append(formalchemy.Field('data_file'))
|
||||||
fs.data_file.set(renderer=formalchemy.fields.FileFieldRenderer)
|
fs.data_file.set(renderer=formalchemy.fields.FileFieldRenderer)
|
||||||
fs.filename.set(renderer=DownloadLinkRenderer(self.route_prefix))
|
fs.filename.set(renderer=DownloadLinkRenderer(self.route_prefix), readonly=True)
|
||||||
self.configure_fieldset(fs)
|
self.configure_fieldset(fs)
|
||||||
if self.creating:
|
if self.creating:
|
||||||
del fs.created
|
del fs.created
|
||||||
|
@ -660,13 +702,6 @@ class FileBatchCrud(BatchCrud):
|
||||||
return HTTPFound(location=self.request.route_url(
|
return HTTPFound(location=self.request.route_url(
|
||||||
'{0}.create'.format(self.route_prefix)))
|
'{0}.create'.format(self.route_prefix)))
|
||||||
|
|
||||||
def post_save_url(self, form):
|
|
||||||
"""
|
|
||||||
Redirect to "view batch" after creating or updating a batch.
|
|
||||||
"""
|
|
||||||
batch = form.fieldset.model
|
|
||||||
return self.view_url(batch.uuid)
|
|
||||||
|
|
||||||
def pre_delete(self, batch):
|
def pre_delete(self, batch):
|
||||||
"""
|
"""
|
||||||
Delete all data (files etc.) for the batch.
|
Delete all data (files etc.) for the batch.
|
||||||
|
@ -690,6 +725,23 @@ class FileBatchCrud(BatchCrud):
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
class StatusRenderer(EnumFieldRenderer):
|
||||||
|
"""
|
||||||
|
Custom renderer for ``status_code`` fields. Adds ``status_text`` value as
|
||||||
|
title attribute if it exists.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def render_readonly(self, **kwargs):
|
||||||
|
value = self.raw_value
|
||||||
|
if value is None:
|
||||||
|
return ''
|
||||||
|
status_code_text = self.enumeration.get(value, unicode(value))
|
||||||
|
row = self.field.parent.model
|
||||||
|
if row.status_text:
|
||||||
|
return HTML.tag('span', title=row.status_text, c=status_code_text)
|
||||||
|
return status_code_text
|
||||||
|
|
||||||
|
|
||||||
class BatchRowGrid(BaseGrid):
|
class BatchRowGrid(BaseGrid):
|
||||||
"""
|
"""
|
||||||
Base grid view for batch rows, which can be filtered and sorted. Also it
|
Base grid view for batch rows, which can be filtered and sorted. Also it
|
||||||
|
@ -778,14 +830,16 @@ class BatchRowGrid(BaseGrid):
|
||||||
g = self.make_grid()
|
g = self.make_grid()
|
||||||
g.extra_row_class = self.tr_class
|
g.extra_row_class = self.tr_class
|
||||||
g.sequence.set(label="Seq.")
|
g.sequence.set(label="Seq.")
|
||||||
g.status_code.set(label="Status", renderer=EnumFieldRenderer(self.row_class.STATUS))
|
g.status_code.set(label="Status", renderer=StatusRenderer(self.row_class.STATUS))
|
||||||
self._configure_grid(g)
|
self._configure_grid(g)
|
||||||
self.configure_grid(g)
|
self.configure_grid(g)
|
||||||
|
|
||||||
batch = self.current_batch()
|
batch = self.current_batch()
|
||||||
# g.viewable = True
|
# g.viewable = True
|
||||||
# g.view_route_name = '{0}.rows.view'.format(self.route_prefix)
|
# g.view_route_name = '{0}.rows.view'.format(self.route_prefix)
|
||||||
if not batch.executed and self.request.has_perm('{0}.edit'.format(self.permission_prefix)):
|
# TODO: Fix this check for edit mode.
|
||||||
|
edit_mode = self.request.referrer.endswith('/edit')
|
||||||
|
if edit_mode and not batch.executed and self.request.has_perm('{0}.edit'.format(self.permission_prefix)):
|
||||||
# g.editable = True
|
# g.editable = True
|
||||||
# g.edit_route_name = '{0}.rows.edit'.format(self.route_prefix)
|
# g.edit_route_name = '{0}.rows.edit'.format(self.route_prefix)
|
||||||
g.deletable = True
|
g.deletable = True
|
||||||
|
|
|
@ -158,7 +158,11 @@ class CrudView(View):
|
||||||
and self.request.params.get('create_and_continue')):
|
and self.request.params.get('create_and_continue')):
|
||||||
return HTTPFound(location=self.request.current_route_url())
|
return HTTPFound(location=self.request.current_route_url())
|
||||||
|
|
||||||
return HTTPFound(location=self.post_save_url(form))
|
if form.creating:
|
||||||
|
url = self.post_create_url(form)
|
||||||
|
else:
|
||||||
|
url = self.post_update_url(form)
|
||||||
|
return HTTPFound(location=url)
|
||||||
|
|
||||||
self.validation_failed(form)
|
self.validation_failed(form)
|
||||||
|
|
||||||
|
@ -199,6 +203,12 @@ class CrudView(View):
|
||||||
def post_save_url(self, form):
|
def post_save_url(self, form):
|
||||||
return self.home_url
|
return self.home_url
|
||||||
|
|
||||||
|
def post_create_url(self, form):
|
||||||
|
return self.post_save_url(form)
|
||||||
|
|
||||||
|
def post_update_url(self, form):
|
||||||
|
return self.post_save_url(form)
|
||||||
|
|
||||||
def validation_failed(self, form):
|
def validation_failed(self, form):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
1
tailbone/views/vendors/__init__.py
vendored
1
tailbone/views/vendors/__init__.py
vendored
|
@ -33,3 +33,4 @@ from .core import (VendorsGrid, VendorCrud, VendorVersionView,
|
||||||
def includeme(config):
|
def includeme(config):
|
||||||
config.include('tailbone.views.vendors.core')
|
config.include('tailbone.views.vendors.core')
|
||||||
config.include('tailbone.views.vendors.catalogs')
|
config.include('tailbone.views.vendors.catalogs')
|
||||||
|
config.include('tailbone.views.vendors.invoices')
|
||||||
|
|
2
tailbone/views/vendors/catalogs.py
vendored
2
tailbone/views/vendors/catalogs.py
vendored
|
@ -26,8 +26,6 @@ Views for maintaining vendor catalogs
|
||||||
|
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from pyramid.httpexceptions import HTTPFound
|
|
||||||
|
|
||||||
from rattail.db import model
|
from rattail.db import model
|
||||||
from rattail.db.api import get_setting, get_vendor
|
from rattail.db.api import get_setting, get_vendor
|
||||||
from rattail.db.batch.vendorcatalog import VendorCatalog, VendorCatalogRow
|
from rattail.db.batch.vendorcatalog import VendorCatalog, VendorCatalogRow
|
||||||
|
|
191
tailbone/views/vendors/invoices.py
vendored
Normal file
191
tailbone/views/vendors/invoices.py
vendored
Normal file
|
@ -0,0 +1,191 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
################################################################################
|
||||||
|
#
|
||||||
|
# Rattail -- Retail Software Framework
|
||||||
|
# Copyright © 2010-2015 Lance Edgar
|
||||||
|
#
|
||||||
|
# This file is part of Rattail.
|
||||||
|
#
|
||||||
|
# Rattail is free software: you can redistribute it and/or modify it under the
|
||||||
|
# terms of the GNU Affero General Public License as published by the Free
|
||||||
|
# Software Foundation, either version 3 of the License, or (at your option)
|
||||||
|
# any later version.
|
||||||
|
#
|
||||||
|
# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
|
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
|
# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
|
||||||
|
# more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
|
# along with Rattail. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
################################################################################
|
||||||
|
"""
|
||||||
|
Views for maintaining vendor invoices
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from rattail.db import model
|
||||||
|
from rattail.db.api import get_setting, get_vendor
|
||||||
|
from rattail.db.batch.vendorinvoice import VendorInvoice, VendorInvoiceRow
|
||||||
|
from rattail.db.batch.vendorinvoice.handler import VendorInvoiceHandler
|
||||||
|
from rattail.vendors.invoices import iter_invoice_parsers, require_invoice_parser
|
||||||
|
from rattail.util import load_object
|
||||||
|
|
||||||
|
import formalchemy
|
||||||
|
|
||||||
|
from tailbone.db import Session
|
||||||
|
from tailbone.views.batch import FileBatchGrid, FileBatchCrud, BatchRowGrid, BatchRowCrud, defaults
|
||||||
|
|
||||||
|
|
||||||
|
class VendorInvoiceGrid(FileBatchGrid):
|
||||||
|
"""
|
||||||
|
Grid view for vendor invoices.
|
||||||
|
"""
|
||||||
|
batch_class = VendorInvoice
|
||||||
|
batch_display = "Vendor Invoice"
|
||||||
|
route_prefix = 'vendors.invoices'
|
||||||
|
|
||||||
|
def join_map_extras(self):
|
||||||
|
return {'vendor': lambda q: q.join(model.Vendor)}
|
||||||
|
|
||||||
|
def filter_map_extras(self):
|
||||||
|
return {'vendor': self.filter_ilike(model.Vendor.name)}
|
||||||
|
|
||||||
|
def filter_config_extras(self):
|
||||||
|
return {'filter_type_vendor': 'lk',
|
||||||
|
'include_filter_vendor': True}
|
||||||
|
|
||||||
|
def sort_map_extras(self):
|
||||||
|
return {'vendor': self.sorter(model.Vendor.name)}
|
||||||
|
|
||||||
|
def configure_grid(self, g):
|
||||||
|
g.configure(
|
||||||
|
include=[
|
||||||
|
g.created,
|
||||||
|
g.created_by,
|
||||||
|
g.vendor,
|
||||||
|
g.filename,
|
||||||
|
g.executed,
|
||||||
|
],
|
||||||
|
readonly=True)
|
||||||
|
|
||||||
|
|
||||||
|
class VendorInvoiceCrud(FileBatchCrud):
|
||||||
|
"""
|
||||||
|
CRUD view for vendor invoices.
|
||||||
|
"""
|
||||||
|
batch_class = VendorInvoice
|
||||||
|
batch_handler_class = VendorInvoiceHandler
|
||||||
|
route_prefix = 'vendors.invoices'
|
||||||
|
|
||||||
|
batch_display = "Vendor Invoice"
|
||||||
|
flash = {'create': "New vendor invoice has been uploaded.",
|
||||||
|
'update': "Vendor invoice has been updated.",
|
||||||
|
'delete': "Vendor invoice has been deleted."}
|
||||||
|
|
||||||
|
def get_handler(self):
|
||||||
|
"""
|
||||||
|
Returns a `BatchHandler` instance for the view.
|
||||||
|
|
||||||
|
Derived classes may override this, but if you only need to replace the
|
||||||
|
handler (i.e. and not the view logic) then you can instead subclass
|
||||||
|
:class:`rattail.db.batch.vendorinvoice.handler.VendorInvoiceHandler`
|
||||||
|
and create a setting named "rattail.batch.vendorinvoice.handler" in the
|
||||||
|
database, the value of which should be a spec string pointed at your
|
||||||
|
custom handler.
|
||||||
|
"""
|
||||||
|
handler = get_setting(Session, 'rattail.batch.vendorinvoice.handler')
|
||||||
|
if not handler:
|
||||||
|
handler = self.request.rattail_config.get('rattail.batch', 'vendorinvoice.handler')
|
||||||
|
if handler:
|
||||||
|
handler = load_object(handler)(self.request.rattail_config)
|
||||||
|
if not handler:
|
||||||
|
handler = super(VendorInvoiceCrud, self).get_handler()
|
||||||
|
return handler
|
||||||
|
|
||||||
|
def configure_fieldset(self, fs):
|
||||||
|
parsers = sorted(iter_invoice_parsers(), key=lambda p: p.display)
|
||||||
|
parser_options = [(p.display, p.key) for p in parsers]
|
||||||
|
parser_options.insert(0, ("(please choose)", ''))
|
||||||
|
fs.parser_key.set(renderer=formalchemy.fields.SelectFieldRenderer,
|
||||||
|
options=parser_options)
|
||||||
|
fs.configure(
|
||||||
|
include=[
|
||||||
|
fs.vendor.readonly(),
|
||||||
|
fs.data_file.label("Invoice File"),
|
||||||
|
fs.parser_key.label("File Type"),
|
||||||
|
fs.filename,
|
||||||
|
fs.purchase_order_number.label(self.handler.po_number_title),
|
||||||
|
fs.invoice_date.readonly(),
|
||||||
|
fs.created,
|
||||||
|
fs.created_by,
|
||||||
|
fs.executed,
|
||||||
|
fs.executed_by,
|
||||||
|
])
|
||||||
|
if self.creating:
|
||||||
|
del fs.vendor
|
||||||
|
del fs.invoice_date
|
||||||
|
else:
|
||||||
|
del fs.parser_key
|
||||||
|
|
||||||
|
def init_batch(self, batch):
|
||||||
|
parser = require_invoice_parser(batch.parser_key)
|
||||||
|
vendor = get_vendor(Session, parser.vendor_key)
|
||||||
|
if not vendor:
|
||||||
|
self.request.session.flash("No vendor setting found in database for key: {0}".format(parser.vendor_key))
|
||||||
|
return False
|
||||||
|
batch.vendor = vendor
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
class VendorInvoiceRowGrid(BatchRowGrid):
|
||||||
|
"""
|
||||||
|
Grid view for vendor invoice rows.
|
||||||
|
"""
|
||||||
|
row_class = VendorInvoiceRow
|
||||||
|
route_prefix = 'vendors.invoices'
|
||||||
|
|
||||||
|
def filter_map_extras(self):
|
||||||
|
return {'ilike': ['upc', 'brand_name', 'description', 'size', 'vendor_code']}
|
||||||
|
|
||||||
|
def filter_config_extras(self):
|
||||||
|
return {'filter_label_upc': "UPC",
|
||||||
|
'filter_label_brand_name': "Brand"}
|
||||||
|
|
||||||
|
def configure_grid(self, g):
|
||||||
|
g.configure(
|
||||||
|
include=[
|
||||||
|
g.sequence,
|
||||||
|
g.upc.label("UPC"),
|
||||||
|
g.brand_name.label("Brand"),
|
||||||
|
g.description,
|
||||||
|
g.size,
|
||||||
|
g.vendor_code,
|
||||||
|
g.shipped_cases.label("Cases"),
|
||||||
|
g.shipped_units.label("Units"),
|
||||||
|
g.unit_cost,
|
||||||
|
g.status_code,
|
||||||
|
],
|
||||||
|
readonly=True)
|
||||||
|
|
||||||
|
def tr_class(self, row, i):
|
||||||
|
if row.status_code in ((row.STATUS_NOT_IN_PURCHASE,
|
||||||
|
row.STATUS_NOT_IN_INVOICE,
|
||||||
|
row.STATUS_DIFFERS_FROM_PURCHASE)):
|
||||||
|
return 'notice'
|
||||||
|
if row.status_code == row.STATUS_NOT_IN_DB:
|
||||||
|
return 'warning'
|
||||||
|
|
||||||
|
|
||||||
|
class VendorInvoiceRowCrud(BatchRowCrud):
|
||||||
|
row_class = VendorInvoiceRow
|
||||||
|
route_prefix = 'vendors.invoices'
|
||||||
|
|
||||||
|
|
||||||
|
def includeme(config):
|
||||||
|
"""
|
||||||
|
Add configuration for the vendor invoice views.
|
||||||
|
"""
|
||||||
|
defaults(config, VendorInvoiceGrid, VendorInvoiceCrud, VendorInvoiceRowGrid, VendorInvoiceRowCrud, '/vendors/invoices/')
|
Loading…
Reference in a new issue