Refactor "receive row" and "declare credit" tools per buefy theme

This commit is contained in:
Lance Edgar 2021-12-08 19:44:50 -06:00
parent ae76ceea04
commit 10e34b83ed
10 changed files with 313 additions and 89 deletions

View file

@ -118,11 +118,7 @@ class ReceivingBatchViews(APIBatchView):
return {'purchases': purchases} return {'purchases': purchases}
def normalize_eligible_purchase(self, purchase): def normalize_eligible_purchase(self, purchase):
return { return self.handler.normalize_eligible_purchase(purchase)
'key': purchase.uuid,
'department_uuid': purchase.department_uuid,
'display': self.render_eligible_purchase(purchase),
}
def render_eligible_purchase(self, purchase): def render_eligible_purchase(self, purchase):
return self.handler.render_eligible_purchase(purchase) return self.handler.render_eligible_purchase(purchase)

View file

@ -343,8 +343,9 @@ class Form(object):
model_instance=None, model_class=None, appstruct=UNSPECIFIED, nodes={}, enums={}, labels={}, model_instance=None, model_class=None, appstruct=UNSPECIFIED, nodes={}, enums={}, labels={},
assume_local_times=False, renderers=None, assume_local_times=False, renderers=None,
hidden={}, widgets={}, defaults={}, validators={}, required={}, helptext={}, focus_spec=None, hidden={}, widgets={}, defaults={}, validators={}, required={}, helptext={}, focus_spec=None,
action_url=None, cancel_url=None, use_buefy=None, component='tailbone-form'): action_url=None, cancel_url=None, use_buefy=None, component='tailbone-form',
vuejs_field_converters={},
):
self.fields = None self.fields = None
if fields is not None: if fields is not None:
self.set_fields(fields) self.set_fields(fields)
@ -380,6 +381,7 @@ class Form(object):
self.cancel_url = cancel_url self.cancel_url = cancel_url
self.use_buefy = use_buefy self.use_buefy = use_buefy
self.component = component self.component = component
self.vuejs_field_converters = vuejs_field_converters or {}
@property @property
def component_studly(self): def component_studly(self):
@ -702,6 +704,9 @@ class Form(object):
""" """
return self.helptext[key] return self.helptext[key]
def set_vuejs_field_converter(self, field, converter):
self.vuejs_field_converters[field] = converter
def render(self, template=None, **kwargs): def render(self, template=None, **kwargs):
if not template: if not template:
if self.readonly and not self.use_buefy: if self.readonly and not self.use_buefy:
@ -788,6 +793,11 @@ class Form(object):
model value for the given field. This JS will be written as part of model value for the given field. This JS will be written as part of
the overall response, to be interpreted on the client side. the overall response, to be interpreted on the client side.
""" """
if field.name in self.vuejs_field_converters:
convert = self.vuejs_field_converters[field.name]
value = convert(field.cstruct)
return json.dumps(value)
if isinstance(field.schema.typ, deform.FileData): if isinstance(field.schema.typ, deform.FileData):
# TODO: we used to always/only return 'null' here but hopefully # TODO: we used to always/only return 'null' here but hopefully
# this also works, to show existing filename when present # this also works, to show existing filename when present
@ -807,6 +817,16 @@ class Form(object):
except Exception as error: except Exception as error:
raise TailboneJSONFieldError(field.name, error) raise TailboneJSONFieldError(field.name, error)
def get_error_messages(self, field):
if field.error:
return field.error.messages()
error = self.make_deform_form().error
if error:
if isinstance(error, colander.Invalid):
if error.node.name == field.name:
return error.messages()
def messages_json(self, messages): def messages_json(self, messages):
dump = json.dumps(messages) dump = json.dumps(messages)
dump = dump.replace("'", ''') dump = dump.replace("'", ''')

View file

@ -1,29 +1,58 @@
<!--! -*- mode: html; -*- --> <!--! -*- mode: html; -*- -->
<div tal:define="oid oid|field.oid; <div tal:define="oid oid|field.oid;
name name|field.name;
css_class css_class|field.widget.css_class; css_class css_class|field.widget.css_class;
style style|field.widget.style;" style style|field.widget.style;
use_buefy use_buefy|0;"
i18n:domain="deform" i18n:domain="deform"
tal:omit-tag=""> tal:omit-tag="">
${field.start_mapping()}
<div> <div tal:condition="not use_buefy" tal:omit-tag="">
<input type="text" name="cases" value="${cases}" ${field.start_mapping()}
tal:attributes="style style; <div>
class string: form-control ${css_class or ''}; <input type="text" name="cases" value="${cases}"
cases_attributes|field.widget.cases_attributes|{};" tal:attributes="style style;
placeholder="cases" class string: form-control ${css_class or ''};
autocomplete="off" cases_attributes|field.widget.cases_attributes|{};"
id="${oid}-cases"/> placeholder="cases"
Cases autocomplete="off"
id="${oid}-cases"/>
Cases
</div>
<div>
<input type="text" name="units" value="${units}"
tal:attributes="class string: form-control ${css_class or ''};
style style;
units_attributes|field.widget.units_attributes|{};"
placeholder="units"
autocomplete="off"
id="${oid}-units"/>
Units
</div>
${field.end_mapping()}
</div> </div>
<div>
<input type="text" name="units" value="${units}" <div tal:condition="use_buefy"
tal:attributes="class string: form-control ${css_class or ''}; tal:define="vmodel vmodel|'field_model_' + name;"
style style; tal:omit-tag="">
units_attributes|field.widget.units_attributes|{};"
placeholder="units" ${field.start_mapping()}
autocomplete="off"
id="${oid}-units"/> <b-field label="Cases">
Units <b-input name="cases" autocomplete="off"
tal:attributes="v-model string: ${vmodel + '.cases'};
cases_attributes|field.widget.cases_attributes|{};">
</b-input>
</b-field>
<b-field label="Units">
<b-input name="units" autocomplete="off"
tal:attributes="v-model string: ${vmodel + '.units'};
units_attributes|field.widget.units_attributes|{};">
</b-input>
</b-field>
${field.end_mapping()}
</div> </div>
${field.end_mapping()}
</div> </div>

View file

@ -1,4 +1,5 @@
## -*- coding: utf-8; -*- ## -*- coding: utf-8; -*-
<%namespace file="/forms/util.mako" import="render_buefy_field" />
<script type="text/x-template" id="${form.component}-template"> <script type="text/x-template" id="${form.component}-template">
@ -9,6 +10,9 @@
% endif % endif
<section> <section>
% if form_body is not Undefined and form_body:
${form_body|n}
% else:
% for field in form.fields: % for field in form.fields:
% if form.readonly or (field not in dform and field in form.readonly_fields): % if form.readonly or (field not in dform and field in form.readonly_fields):
<b-field horizontal <b-field horizontal
@ -17,29 +21,11 @@
</b-field> </b-field>
% elif field in dform: % elif field in dform:
<% field = dform[field] %> ${render_buefy_field(dform[field])}
% if form.field_visible(field.name):
<b-field horizontal
label="${form.get_label(field.name)}"
## TODO: is this class="file" really needed?
% if isinstance(field.schema.typ, deform.FileData):
class="file"
% endif
% if field.error:
type="is-danger"
:message='${form.messages_json(field.error.messages())|n}'
% endif
>
${field.serialize(use_buefy=True)|n}
</b-field>
% else:
## hidden field
${field.serialize()|n}
% endif
% endif % endif
% endfor % endfor
% endif
</section> </section>
% if buttons: % if buttons:
@ -48,6 +34,20 @@
% elif not form.readonly and (buttons is Undefined or (buttons is not None and buttons is not False)): % elif not form.readonly and (buttons is Undefined or (buttons is not None and buttons is not False)):
<br /> <br />
<div class="buttons"> <div class="buttons">
% if getattr(form, 'show_cancel', True):
% if form.auto_disable_cancel:
<once-button tag="a" href="${form.cancel_url or request.get_referrer()}"
text="Cancel">
</once-button>
% else:
<b-button tag="a" href="${form.cancel_url or request.get_referrer()}">
Cancel
</b-button>
% endif
% endif
% if getattr(form, 'show_reset', False):
<input type="reset" value="Reset" class="button" />
% endif
## TODO: deprecate / remove the latter option here ## TODO: deprecate / remove the latter option here
% if form.auto_disable_save or form.auto_disable: % if form.auto_disable_save or form.auto_disable:
<b-button type="is-primary" <b-button type="is-primary"
@ -61,20 +61,6 @@
${getattr(form, 'submit_label', getattr(form, 'save_label', "Submit"))} ${getattr(form, 'submit_label', getattr(form, 'save_label', "Submit"))}
</b-button> </b-button>
% endif % endif
% if getattr(form, 'show_reset', False):
<input type="reset" value="Reset" class="button" />
% endif
% if getattr(form, 'show_cancel', True):
% if form.auto_disable_cancel:
<once-button tag="a" href="${form.cancel_url or request.get_referrer()}"
text="Cancel">
</once-button>
% else:
<b-button tag="a" href="${form.cancel_url or request.get_referrer()}">
Cancel
</b-button>
% endif
% endif
</div> </div>
% endif % endif

View file

@ -0,0 +1,24 @@
## -*- coding: utf-8; -*-
<%def name="render_buefy_field(field, bfield_kwargs={})">
% if form.field_visible(field.name):
<% error_messages = form.get_error_messages(field) %>
<b-field horizontal
label="${form.get_label(field.name)}"
## TODO: is this class="file" really needed?
% if isinstance(field.schema.typ, deform.FileData):
class="file"
% endif
% if error_messages:
type="is-danger"
:message='${form.messages_json(error_messages)|n}'
% endif
${h.HTML.render_attrs(bfield_kwargs)}
>
${field.serialize(use_buefy=True)|n}
</b-field>
% else:
## hidden field
${field.serialize()|n}
% endif
</%def>

View file

@ -1,16 +1,19 @@
## -*- coding: utf-8; -*- ## -*- coding: utf-8; -*-
<%inherit file="/base.mako" /> <%inherit file="/form.mako" />
<%namespace file="/forms/util.mako" import="render_buefy_field" />
<%def name="title()">Declare Credit for Row #${row.sequence}</%def> <%def name="title()">Declare Credit for Row #${row.sequence}</%def>
<%def name="context_menu_items()"> <%def name="context_menu_items()">
% if master.rows_viewable and request.has_perm('{}.view'.format(permission_prefix)): ${parent.context_menu_items()}
% if master.rows_viewable and master.has_perm('view'):
<li>${h.link_to("View this {}".format(row_model_title), row_action_url('view', row))}</li> <li>${h.link_to("View this {}".format(row_model_title), row_action_url('view', row))}</li>
% endif % endif
</%def> </%def>
<%def name="extra_javascript()"> <%def name="extra_javascript()">
${parent.extra_javascript()} ${parent.extra_javascript()}
% if not use_buefy:
<script type="text/javascript"> <script type="text/javascript">
function toggleFields(creditType) { function toggleFields(creditType) {
@ -34,11 +37,49 @@
}); });
</script> </script>
% endif
</%def> </%def>
<div style="display: flex; justify-content: space-between;"> <%def name="render_buefy_form()">
<div class="form-wrapper"> <p class="block">
Please select the "state" of the product, and enter the
appropriate quantity.
</p>
<p class="block">
Note that this tool will
<span class="has-text-weight-bold">deduct</span> from the
"received" quantity, and
<span class="has-text-weight-bold">add</span> to the
corresponding credit quantity.
</p>
<p class="block">
Please see ${h.link_to("Receive Row", url('{}.receive_row'.format(route_prefix), uuid=batch.uuid, row_uuid=row.uuid))}
if you need to "receive" instead of "convert" the product.
</p>
${parent.render_buefy_form()}
</%def>
<%def name="buefy_form_body()">
${render_buefy_field(dform['credit_type'])}
${render_buefy_field(dform['quantity'])}
${render_buefy_field(dform['expiration_date'], bfield_kwargs={'v-show': "field_model_credit_type == 'expired'"})}
</%def>
<%def name="render_form()">
% if use_buefy:
${form.render_deform(buttons=capture(self.render_form_buttons), form_body=capture(self.buefy_form_body))|n}
% else:
<p style="padding: 1em;"> <p style="padding: 1em;">
Please select the "state" of the product, and enter the appropriate Please select the "state" of the product, and enter the appropriate
@ -55,11 +96,10 @@
if you need to "receive" instead of "convert" the product. if you need to "receive" instead of "convert" the product.
</p> </p>
${form.render()|n} ${parent.render_form()}
</div><!-- form-wrapper -->
<ul id="context-menu"> % endif
${self.context_menu_items()} </%def>
</ul>
</div>
${parent.body()}

View file

@ -1,16 +1,19 @@
## -*- coding: utf-8; -*- ## -*- coding: utf-8; -*-
<%inherit file="/base.mako" /> <%inherit file="/form.mako" />
<%namespace file="/forms/util.mako" import="render_buefy_field" />
<%def name="title()">Receive for Row #${row.sequence}</%def> <%def name="title()">Receive for Row #${row.sequence}</%def>
<%def name="context_menu_items()"> <%def name="context_menu_items()">
% if master.rows_viewable and request.has_perm('{}.view'.format(permission_prefix)): ${parent.context_menu_items()}
% if master.rows_viewable and master.has_perm('view'):
<li>${h.link_to("View this {}".format(row_model_title), row_action_url('view', row))}</li> <li>${h.link_to("View this {}".format(row_model_title), row_action_url('view', row))}</li>
% endif % endif
</%def> </%def>
<%def name="extra_javascript()"> <%def name="extra_javascript()">
${parent.extra_javascript()} ${parent.extra_javascript()}
% if not use_buefy:
<script type="text/javascript"> <script type="text/javascript">
function toggleFields(mode) { function toggleFields(mode) {
@ -34,11 +37,46 @@
}); });
</script> </script>
% endif
</%def> </%def>
<div style="display: flex; justify-content: space-between;"> <%def name="render_buefy_form()">
<div class="form-wrapper"> <p class="block">
Please select the "state" of the product, and enter the appropriate
quantity.
</p>
<p class="block">
Note that this tool will <span class="has-text-weight-bold">add</span>
the corresponding quantities for the row.
</p>
<p class="block">
Please see ${h.link_to("Declare Credit", url('{}.declare_credit'.format(route_prefix), uuid=batch.uuid, row_uuid=row.uuid))}
if you need to "convert" some already-received amount, into a credit.
</p>
${parent.render_buefy_form()}
</%def>
<%def name="buefy_form_body()">
${render_buefy_field(dform['mode'])}
${render_buefy_field(dform['quantity'])}
${render_buefy_field(dform['expiration_date'], bfield_kwargs={'v-show': "field_model_mode == 'expired'"})}
</%def>
<%def name="render_form()">
% if use_buefy:
${form.render_deform(buttons=capture(self.render_form_buttons), form_body=capture(self.buefy_form_body))|n}
% else:
<p style="padding: 1em;"> <p style="padding: 1em;">
Please select the "state" of the product, and enter the appropriate Please select the "state" of the product, and enter the appropriate
@ -55,11 +93,10 @@
if you need to "convert" some already-received amount, into a credit. if you need to "convert" some already-received amount, into a credit.
</p> </p>
${form.render()|n} ${parent.render_form()}
</div><!-- form-wrapper -->
<ul id="context-menu"> % endif
${self.context_menu_items()} </%def>
</ul>
</div>
${parent.body()}

View file

@ -8,8 +8,19 @@
<h3>Receiving Tools</h3> <h3>Receiving Tools</h3>
<div class="object-helper-content"> <div class="object-helper-content">
<div style="white-space: nowrap;"> <div style="white-space: nowrap;">
% if use_buefy:
<once-button type="is-primary"
tag="a" href="${url('{}.receive_row'.format(route_prefix), uuid=batch.uuid, row_uuid=row.uuid)}"
text="Receive Product">
</once-button>
<once-button type="is-primary"
tag="a" href="${url('{}.declare_credit'.format(route_prefix), uuid=batch.uuid, row_uuid=row.uuid)}"
text="Declare Credit">
</once-button>
% else:
${h.link_to("Receive Product", url('{}.receive_row'.format(route_prefix), uuid=batch.uuid, row_uuid=row.uuid), class_='button autodisable')} ${h.link_to("Receive Product", url('{}.receive_row'.format(route_prefix), uuid=batch.uuid, row_uuid=row.uuid), class_='button autodisable')}
${h.link_to("Declare Credit", url('{}.declare_credit'.format(route_prefix), uuid=batch.uuid, row_uuid=row.uuid), class_='button autodisable')} ${h.link_to("Declare Credit", url('{}.declare_credit'.format(route_prefix), uuid=batch.uuid, row_uuid=row.uuid), class_='button autodisable')}
% endif
</div> </div>
</div> </div>
</div> </div>

View file

@ -637,6 +637,11 @@ class PurchasingBatchView(BatchMasterView):
g.set_label('catalog_unit_cost', "Catalog Cost") g.set_label('catalog_unit_cost', "Catalog Cost")
g.filters['catalog_unit_cost'].label = "Catalog Unit Cost" g.filters['catalog_unit_cost'].label = "Catalog Unit Cost"
# po_unit_cost
g.set_renderer('po_unit_cost', self.render_row_grid_cost)
g.set_label('po_unit_cost', "PO Cost")
g.filters['po_unit_cost'].label = "PO Unit Cost"
# invoice_unit_cost # invoice_unit_cost
g.set_renderer('invoice_unit_cost', self.render_row_grid_cost) g.set_renderer('invoice_unit_cost', self.render_row_grid_cost)
g.set_label('invoice_unit_cost', "Invoice Cost") g.set_label('invoice_unit_cost', "Invoice Cost")

View file

@ -144,6 +144,7 @@ class ReceivingBatchView(PurchasingBatchView):
'cases_received', 'cases_received',
'units_received', 'units_received',
'catalog_unit_cost', 'catalog_unit_cost',
'po_unit_cost',
'invoice_unit_cost', 'invoice_unit_cost',
'invoice_total_calculated', 'invoice_total_calculated',
'credits', 'credits',
@ -152,6 +153,7 @@ class ReceivingBatchView(PurchasingBatchView):
] ]
row_form_fields = [ row_form_fields = [
'sequence',
'item_entry', 'item_entry',
'upc', 'upc',
'item_id', 'item_id',
@ -487,6 +489,14 @@ class ReceivingBatchView(PurchasingBatchView):
if self.creating: if self.creating:
f.remove('invoice_total_calculated') f.remove('invoice_total_calculated')
# hide all invoice fields if batch does not have invoice file
if not self.creating and not self.handler.has_invoice_file(batch):
f.remove('invoice_file',
'invoice_date',
'invoice_number',
'invoice_total',
'invoice_total_calculated')
# receiving_complete # receiving_complete
if self.creating: if self.creating:
f.remove('receiving_complete') f.remove('receiving_complete')
@ -506,8 +516,12 @@ class ReceivingBatchView(PurchasingBatchView):
elif workflow == 'from_po': elif workflow == 'from_po':
f.remove('truck_dump_batch_uuid', f.remove('truck_dump_batch_uuid',
'date_ordered',
'po_number',
'invoice_file', 'invoice_file',
'invoice_parser_key') 'invoice_parser_key',
'invoice_date',
'invoice_number')
elif workflow == 'from_po_with_invoice': elif workflow == 'from_po_with_invoice':
f.remove('truck_dump_batch_uuid') f.remove('truck_dump_batch_uuid')
@ -736,11 +750,25 @@ class ReceivingBatchView(PurchasingBatchView):
def configure_row_grid(self, g): def configure_row_grid(self, g):
super(ReceivingBatchView, self).configure_row_grid(g) super(ReceivingBatchView, self).configure_row_grid(g)
batch = self.get_instance()
# vendor_code # vendor_code
g.filters['vendor_code'].default_active = True g.filters['vendor_code'].default_active = True
g.filters['vendor_code'].default_verb = 'contains' g.filters['vendor_code'].default_verb = 'contains'
# catalog_unit_cost
if (self.handler.has_purchase_order(batch)
or self.handler.has_invoice_file(batch)):
g.remove('catalog_unit_cost')
# po_unit_cost
if self.handler.has_invoice_file(batch):
g.remove('po_unit_cost')
# invoice_unit_cost
if not self.handler.has_invoice_file(batch):
g.remove('invoice_unit_cost')
# credits # credits
# note that sorting by credits involves a subquery with group by clause. # note that sorting by credits involves a subquery with group by clause.
# seems likely there may be a better way? but this seems to work fine # seems likely there may be a better way? but this seems to work fine
@ -753,7 +781,6 @@ class ReceivingBatchView(PurchasingBatchView):
# hide 'ordered' columns for truck dump parent, if its "children first" # hide 'ordered' columns for truck dump parent, if its "children first"
# flag is set, since that batch type is only concerned with receiving # flag is set, since that batch type is only concerned with receiving
batch = self.get_instance()
if batch.is_truck_dump_parent() and not batch.truck_dump_children_first: if batch.is_truck_dump_parent() and not batch.truck_dump_children_first:
g.remove('cases_ordered', g.remove('cases_ordered',
'units_ordered') 'units_ordered')
@ -788,6 +815,11 @@ class ReceivingBatchView(PurchasingBatchView):
return css_class return css_class
def get_row_instance_title(self, row):
if row.upc:
return row.upc.pretty()
return super(ReceivingBatchView, self).get_row_instance_title(row)
def transform_unit_url(self, row, i): def transform_unit_url(self, row, i):
# grid action is shown only when we return a URL here # grid action is shown only when we return a URL here
if self.row_editable(row): if self.row_editable(row):
@ -795,6 +827,18 @@ class ReceivingBatchView(PurchasingBatchView):
if row.product and row.product.is_pack_item(): if row.product and row.product.is_pack_item():
return self.get_row_action_url('transform_unit', row) return self.get_row_action_url('transform_unit', row)
def vuejs_convert_quantity(self, cstruct):
result = dict(cstruct)
if result['cases'] is colander.null:
result['cases'] = None
elif isinstance(result['cases'], decimal.Decimal):
result['cases'] = float(result['cases'])
if result['units'] is colander.null:
result['units'] = None
elif isinstance(result['units'], decimal.Decimal):
result['units'] = float(result['units'])
return result
def receive_row(self, **kwargs): def receive_row(self, **kwargs):
""" """
Primary desktop view for row-level receiving. Primary desktop view for row-level receiving.
@ -830,14 +874,24 @@ class ReceivingBatchView(PurchasingBatchView):
schema = ReceiveRowForm().bind(session=self.Session()) schema = ReceiveRowForm().bind(session=self.Session())
form = forms.Form(schema=schema, request=self.request, use_buefy=use_buefy) form = forms.Form(schema=schema, request=self.request, use_buefy=use_buefy)
form.cancel_url = self.get_row_action_url('view', row) form.cancel_url = self.get_row_action_url('view', row)
# mode
mode_values = [(mode, mode) for mode in possible_modes] mode_values = [(mode, mode) for mode in possible_modes]
if use_buefy: if use_buefy:
form.set_widget('mode', dfwidget.SelectWidget(values=mode_values)) mode_widget = dfwidget.SelectWidget(values=mode_values)
else: else:
form.set_widget('mode', forms.widgets.JQuerySelectWidget(values=mode_values)) mode_widget = forms.widgets.JQuerySelectWidget(values=mode_values)
form.set_widget('mode', mode_widget)
# quantity
form.set_widget('quantity', forms.widgets.CasesUnitsWidget(amount_required=True, form.set_widget('quantity', forms.widgets.CasesUnitsWidget(amount_required=True,
one_amount_only=True)) one_amount_only=True))
form.set_vuejs_field_converter('quantity', self.vuejs_convert_quantity)
# expiration_date
form.set_type('expiration_date', 'date_jquery') form.set_type('expiration_date', 'date_jquery')
# TODO: what is this one about again?
form.remove_field('quick_receive') form.remove_field('quick_receive')
if form.validate(newstyle=True): if form.validate(newstyle=True):
@ -946,6 +1000,7 @@ class ReceivingBatchView(PurchasingBatchView):
View for declaring a credit, i.e. converting some "received" or similar View for declaring a credit, i.e. converting some "received" or similar
quantity, to a credit of some sort. quantity, to a credit of some sort.
""" """
use_buefy = self.get_use_buefy()
row = self.get_row_instance() row = self.get_row_instance()
batch = row.batch batch = row.batch
possible_credit_types = [ possible_credit_types = [
@ -965,11 +1020,23 @@ class ReceivingBatchView(PurchasingBatchView):
} }
schema = DeclareCreditForm() schema = DeclareCreditForm()
form = forms.Form(schema=schema, request=self.request) form = forms.Form(schema=schema, request=self.request,
form.set_widget('credit_type', forms.widgets.JQuerySelectWidget( use_buefy=use_buefy)
values=[(m, m) for m in possible_credit_types]))
# credit_type
values = [(m, m) for m in possible_credit_types]
if use_buefy:
widget = dfwidget.SelectWidget(values=values)
else:
widget = forms.widgets.JQuerySelectWidget(values=values)
form.set_widget('credit_type', widget)
# quantity
form.set_widget('quantity', forms.widgets.CasesUnitsWidget( form.set_widget('quantity', forms.widgets.CasesUnitsWidget(
amount_required=True, one_amount_only=True)) amount_required=True, one_amount_only=True))
form.set_vuejs_field_converter('quantity', self.vuejs_convert_quantity)
# expiration_date
form.set_type('expiration_date', 'date_jquery') form.set_type('expiration_date', 'date_jquery')
if form.validate(newstyle=True): if form.validate(newstyle=True):
@ -1554,6 +1621,15 @@ class ReceiveRowForm(colander.MappingSchema):
quick_receive = colander.SchemaNode(colander.Boolean()) quick_receive = colander.SchemaNode(colander.Boolean())
def deserialize(self, *args):
result = super(ReceiveRowForm, self).deserialize(*args)
if result['mode'] == 'expired' and not result['expiration_date']:
msg = "Expiration date is required for items with 'expired' mode."
self.raise_invalid(msg, node=self.get('expiration_date'))
return result
class DeclareCreditForm(colander.MappingSchema): class DeclareCreditForm(colander.MappingSchema):