Add support for editing catalog cost in receiving batch, per new theme
had to add several "under the hood" features to make this work, to embed a Vue component within grid `<td>` cells, etc.
This commit is contained in:
parent
ec71f532a1
commit
2e3823364c
|
@ -48,7 +48,7 @@ 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
|
||||||
|
|
||||||
from tailbone.util import raw_datetime
|
from tailbone.util import raw_datetime, get_form_data
|
||||||
from . import types
|
from . import types
|
||||||
from .widgets import ReadonlyWidget, PlainDateWidget, JQueryDateWidget, JQueryTimeWidget
|
from .widgets import ReadonlyWidget, PlainDateWidget, JQueryDateWidget, JQueryTimeWidget
|
||||||
from tailbone.exceptions import TailboneJSONFieldError
|
from tailbone.exceptions import TailboneJSONFieldError
|
||||||
|
@ -1071,17 +1071,15 @@ class Form(object):
|
||||||
if self.request.method != 'POST':
|
if self.request.method != 'POST':
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# use POST or JSON body, whichever is present
|
controls = get_form_data(self.request).items()
|
||||||
# TODO: per docs, some JS libraries may not set this flag?
|
|
||||||
# https://docs.pylonsproject.org/projects/pyramid/en/latest/api/request.html#pyramid.request.Request.is_xhr
|
|
||||||
if self.request.is_xhr and not self.request.POST:
|
|
||||||
controls = self.request.json_body.items()
|
|
||||||
|
|
||||||
# unfortunately the normal form logic (i.e. peppercorn) is
|
# unfortunately the normal form logic (i.e. peppercorn) is
|
||||||
# expecting all values to be strings, whereas the JSON body we
|
# expecting all values to be strings, whereas if our data
|
||||||
# just parsed, may have given us some Pythonic objects. so
|
# came from JSON body, may have given us some Pythonic
|
||||||
# here we must convert them *back* to strings...
|
# objects. so here we must convert them *back* to strings
|
||||||
# TODO: this seems like a hack, i must be missing something
|
# TODO: this seems like a hack, i must be missing something
|
||||||
|
# TODO: also this uses same "JSON" check as get_form_data()
|
||||||
|
if self.request.is_xhr and not self.request.POST:
|
||||||
controls = [[key, val] for key, val in controls]
|
controls = [[key, val] for key, val in controls]
|
||||||
for i in range(len(controls)):
|
for i in range(len(controls)):
|
||||||
key, value = controls[i]
|
key, value = controls[i]
|
||||||
|
@ -1094,9 +1092,6 @@ class Form(object):
|
||||||
elif not isinstance(value, six.string_types):
|
elif not isinstance(value, six.string_types):
|
||||||
controls[i][1] = six.text_type(value)
|
controls[i][1] = six.text_type(value)
|
||||||
|
|
||||||
else:
|
|
||||||
controls = self.request.POST.items()
|
|
||||||
|
|
||||||
dform = self.make_deform_form()
|
dform = self.make_deform_form()
|
||||||
try:
|
try:
|
||||||
self.validated = dform.validate(controls)
|
self.validated = dform.validate(controls)
|
||||||
|
|
|
@ -68,11 +68,49 @@ class FieldList(list):
|
||||||
class Grid(object):
|
class Grid(object):
|
||||||
"""
|
"""
|
||||||
Core grid class. In sore need of documentation.
|
Core grid class. In sore need of documentation.
|
||||||
|
|
||||||
|
.. attribute:: raw_renderers
|
||||||
|
|
||||||
|
Dict of "raw" field renderers. See also
|
||||||
|
:meth:`set_raw_renderer()`.
|
||||||
|
|
||||||
|
When present, these are rendered "as-is" into the grid
|
||||||
|
template, whereas the more typical scenario involves rendering
|
||||||
|
each field "into" a span element, like:
|
||||||
|
|
||||||
|
.. code-block:: html
|
||||||
|
|
||||||
|
<span v-html="RENDERED-FIELD"></span>
|
||||||
|
|
||||||
|
So instead of injecting into a span, any "raw" fields defined
|
||||||
|
via this dict, will be injected as-is, like:
|
||||||
|
|
||||||
|
.. code-block:: html
|
||||||
|
|
||||||
|
RENDERED-FIELD
|
||||||
|
|
||||||
|
Note that each raw renderer is called only once, and *without*
|
||||||
|
any arguments. Likely the only use case for this, is to inject
|
||||||
|
a Vue component into the field. A basic example::
|
||||||
|
|
||||||
|
from webhelpers2.html import HTML
|
||||||
|
|
||||||
|
def myrender():
|
||||||
|
return HTML.tag('my-component', **{'v-model': 'props.row.myfield'})
|
||||||
|
|
||||||
|
grid = Grid(
|
||||||
|
# ..normal constructor args here..
|
||||||
|
|
||||||
|
raw_renderers={
|
||||||
|
'myfield': myrender,
|
||||||
|
},
|
||||||
|
)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, key, data, columns=None, width='auto', request=None,
|
def __init__(self, key, data, columns=None, width='auto', request=None,
|
||||||
model_class=None, model_title=None, model_title_plural=None,
|
model_class=None, model_title=None, model_title_plural=None,
|
||||||
enums={}, labels={}, assume_local_times=False, renderers={}, invisible=[],
|
enums={}, labels={}, assume_local_times=False, renderers={}, invisible=[],
|
||||||
|
raw_renderers={},
|
||||||
extra_row_class=None, linked_columns=[], url='#',
|
extra_row_class=None, linked_columns=[], url='#',
|
||||||
joiners={}, filterable=False, filters={}, use_byte_string_filters=False,
|
joiners={}, filterable=False, filters={}, use_byte_string_filters=False,
|
||||||
searchable={},
|
searchable={},
|
||||||
|
@ -109,6 +147,7 @@ class Grid(object):
|
||||||
self.labels = labels or {}
|
self.labels = labels or {}
|
||||||
self.assume_local_times = assume_local_times
|
self.assume_local_times = assume_local_times
|
||||||
self.renderers = self.make_default_renderers(renderers or {})
|
self.renderers = self.make_default_renderers(renderers or {})
|
||||||
|
self.raw_renderers = raw_renderers or {}
|
||||||
self.invisible = invisible or []
|
self.invisible = invisible or []
|
||||||
self.extra_row_class = extra_row_class
|
self.extra_row_class = extra_row_class
|
||||||
self.linked_columns = linked_columns or []
|
self.linked_columns = linked_columns or []
|
||||||
|
@ -286,6 +325,21 @@ class Grid(object):
|
||||||
def set_renderer(self, key, renderer):
|
def set_renderer(self, key, renderer):
|
||||||
self.renderers[key] = renderer
|
self.renderers[key] = renderer
|
||||||
|
|
||||||
|
def set_raw_renderer(self, key, renderer):
|
||||||
|
"""
|
||||||
|
Set or remove the "raw" renderer for the given field.
|
||||||
|
|
||||||
|
See :attr:`raw_renderers` for more about these.
|
||||||
|
|
||||||
|
:param key: Field name.
|
||||||
|
|
||||||
|
:param renderer: Either a renderer callable, or ``None``.
|
||||||
|
"""
|
||||||
|
if renderer:
|
||||||
|
self.raw_renderers[key] = renderer
|
||||||
|
else:
|
||||||
|
self.raw_renderers.pop(key, None)
|
||||||
|
|
||||||
def set_type(self, key, type_):
|
def set_type(self, key, type_):
|
||||||
if type_ == 'boolean':
|
if type_ == 'boolean':
|
||||||
self.set_renderer(key, self.render_boolean)
|
self.set_renderer(key, self.render_boolean)
|
||||||
|
@ -1313,7 +1367,10 @@ class Grid(object):
|
||||||
# iterate over data rows
|
# iterate over data rows
|
||||||
for i in range(count):
|
for i in range(count):
|
||||||
rowobj = raw_data[i]
|
rowobj = raw_data[i]
|
||||||
row = {}
|
|
||||||
|
# nb. cache 0-based index on the row, in case client-side
|
||||||
|
# logic finds it useful
|
||||||
|
row = {'_index': i}
|
||||||
|
|
||||||
# sometimes we need to include some "raw" data columns in our
|
# sometimes we need to include some "raw" data columns in our
|
||||||
# result set, even though the column is not displayed as part of
|
# result set, even though the column is not displayed as part of
|
||||||
|
|
|
@ -221,8 +221,14 @@
|
||||||
% if grid.is_searchable(column['field']):
|
% if grid.is_searchable(column['field']):
|
||||||
searchable
|
searchable
|
||||||
% endif
|
% endif
|
||||||
|
cell-class="${column['field']}"
|
||||||
|
% if grid.has_click_handler(column['field']):
|
||||||
|
@click.native="${grid.click_handlers[column['field']]}"
|
||||||
|
% endif
|
||||||
:visible="${json.dumps(column['visible'])}">
|
:visible="${json.dumps(column['visible'])}">
|
||||||
% if grid.is_linked(column['field']):
|
% if column['field'] in grid.raw_renderers:
|
||||||
|
${grid.raw_renderers[column['field']]()}
|
||||||
|
% elif grid.is_linked(column['field']):
|
||||||
<a :href="props.row._action_url_view" v-html="props.row.${column['field']}"></a>
|
<a :href="props.row._action_url_view" v-html="props.row.${column['field']}"></a>
|
||||||
% else:
|
% else:
|
||||||
<span v-html="props.row.${column['field']}"></span>
|
<span v-html="props.row.${column['field']}"></span>
|
||||||
|
@ -349,6 +355,18 @@
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
|
||||||
|
addRowClass(index, className) {
|
||||||
|
|
||||||
|
// TODO: this may add duplicated name to class string
|
||||||
|
// (not a serious problem i think, but could be improved)
|
||||||
|
this.rowStatusMap[index] = (this.rowStatusMap[index] || '')
|
||||||
|
+ ' ' + className
|
||||||
|
|
||||||
|
// nb. for some reason b-table does not always "notice"
|
||||||
|
// when we update status; so we force it to refresh
|
||||||
|
this.$forceUpdate()
|
||||||
|
},
|
||||||
|
|
||||||
getRowClass(row, index) {
|
getRowClass(row, index) {
|
||||||
return this.rowStatusMap[index]
|
return this.rowStatusMap[index]
|
||||||
},
|
},
|
||||||
|
|
|
@ -115,12 +115,23 @@
|
||||||
</b-checkbox>
|
</b-checkbox>
|
||||||
</b-field>
|
</b-field>
|
||||||
|
|
||||||
<b-checkbox name="rattail.batch.purchase.receiving.should_autofix_invoice_case_vs_unit"
|
<b-field>
|
||||||
v-model="simpleSettings['rattail.batch.purchase.receiving.should_autofix_invoice_case_vs_unit']"
|
<b-checkbox name="rattail.batch.purchase.receiving.should_autofix_invoice_case_vs_unit"
|
||||||
native-value="true"
|
v-model="simpleSettings['rattail.batch.purchase.receiving.should_autofix_invoice_case_vs_unit']"
|
||||||
@input="settingsNeedSaved = true">
|
native-value="true"
|
||||||
Try to auto-correct "case vs. unit" mistakes from invoice parser
|
@input="settingsNeedSaved = true">
|
||||||
</b-checkbox>
|
Try to auto-correct "case vs. unit" mistakes from invoice parser
|
||||||
|
</b-checkbox>
|
||||||
|
</b-field>
|
||||||
|
|
||||||
|
<b-field>
|
||||||
|
<b-checkbox name="rattail.batch.purchase.receiving.allow_edit_catalog_unit_cost"
|
||||||
|
v-model="simpleSettings['rattail.batch.purchase.receiving.allow_edit_catalog_unit_cost']"
|
||||||
|
native-value="true"
|
||||||
|
@input="settingsNeedSaved = true">
|
||||||
|
Allow edit of Catalog Unit Cost
|
||||||
|
</b-checkbox>
|
||||||
|
</b-field>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
<%def name="extra_javascript()">
|
<%def name="extra_javascript()">
|
||||||
${parent.extra_javascript()}
|
${parent.extra_javascript()}
|
||||||
% if master.has_perm('edit_row'):
|
% if not use_buefy and master.has_perm('edit_row'):
|
||||||
${h.javascript_link(request.static_url('tailbone:static/js/numeric.js'))}
|
${h.javascript_link(request.static_url('tailbone:static/js/numeric.js'))}
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
|
|
||||||
|
@ -264,7 +264,21 @@
|
||||||
|
|
||||||
<%def name="extra_styles()">
|
<%def name="extra_styles()">
|
||||||
${parent.extra_styles()}
|
${parent.extra_styles()}
|
||||||
% if not batch.executed and master.has_perm('edit_row'):
|
% if use_buefy and allow_edit_catalog_unit_cost:
|
||||||
|
<style type="text/css">
|
||||||
|
|
||||||
|
td.catalog_unit_cost {
|
||||||
|
cursor: pointer;
|
||||||
|
background-color: #fcc;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr.catalog_cost_confirmed td.catalog_unit_cost {
|
||||||
|
/* cursor: pointer; */
|
||||||
|
background-color: #cfc;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
% elif not use_buefy and not batch.executed and master.has_perm('edit_row'):
|
||||||
<style type="text/css">
|
<style type="text/css">
|
||||||
.grid tr:not(.header) td.catalog_unit_cost,
|
.grid tr:not(.header) td.catalog_unit_cost,
|
||||||
.grid tr:not(.header) td.invoice_unit_cost {
|
.grid tr:not(.header) td.invoice_unit_cost {
|
||||||
|
@ -357,6 +371,26 @@
|
||||||
% endif
|
% endif
|
||||||
</%def>
|
</%def>
|
||||||
|
|
||||||
|
<%def name="render_this_page_template()">
|
||||||
|
${parent.render_this_page_template()}
|
||||||
|
|
||||||
|
% if allow_edit_catalog_unit_cost:
|
||||||
|
<script type="text/x-template" id="receiving-cost-editor-template">
|
||||||
|
<div>
|
||||||
|
<span v-show="!editing">
|
||||||
|
{{ value }}
|
||||||
|
</span>
|
||||||
|
<b-input v-model="inputValue"
|
||||||
|
ref="input"
|
||||||
|
v-show="editing"
|
||||||
|
@keydown.native="inputKeyDown"
|
||||||
|
@blur="inputBlur">
|
||||||
|
</b-input>
|
||||||
|
</div>
|
||||||
|
</script>
|
||||||
|
% endif
|
||||||
|
</%def>
|
||||||
|
|
||||||
<%def name="object_helpers()">
|
<%def name="object_helpers()">
|
||||||
${self.render_status_breakdown()}
|
${self.render_status_breakdown()}
|
||||||
${self.render_po_vs_invoice_helper()}
|
${self.render_po_vs_invoice_helper()}
|
||||||
|
@ -418,13 +452,128 @@
|
||||||
|
|
||||||
% endif
|
% endif
|
||||||
|
|
||||||
|
% if allow_edit_catalog_unit_cost:
|
||||||
|
|
||||||
|
let ReceivingCostEditor = {
|
||||||
|
template: '#receiving-cost-editor-template',
|
||||||
|
props: {
|
||||||
|
row: Object,
|
||||||
|
value: String,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
inputValue: this.value,
|
||||||
|
editing: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
|
||||||
|
startEdit() {
|
||||||
|
this.inputValue = this.value
|
||||||
|
this.editing = true
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$refs.input.focus()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
inputKeyDown(event) {
|
||||||
|
|
||||||
|
// when user presses Enter while editing cost value, submit
|
||||||
|
// value to server for immediate persistence
|
||||||
|
if (event.which == 13) {
|
||||||
|
this.submitEdit()
|
||||||
|
|
||||||
|
// when user presses Escape, cancel the edit
|
||||||
|
} else if (event.which == 27) {
|
||||||
|
this.cancelEdit()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
inputBlur(event) {
|
||||||
|
// always assume user meant to cancel
|
||||||
|
this.cancelEdit()
|
||||||
|
},
|
||||||
|
|
||||||
|
cancelEdit() {
|
||||||
|
// reset input to discard any user entry
|
||||||
|
this.inputValue = this.value
|
||||||
|
this.editing = false
|
||||||
|
this.$emit('cancel-edit')
|
||||||
|
},
|
||||||
|
|
||||||
|
submitEdit() {
|
||||||
|
let url = '${url('{}.update_row_cost'.format(route_prefix), uuid=batch.uuid)}'
|
||||||
|
|
||||||
|
// TODO: should get csrf token from parent component?
|
||||||
|
let csrftoken = ${json.dumps(request.session.get_csrf_token() or request.session.new_csrf_token())|n}
|
||||||
|
let headers = {'${csrf_header_name}': csrftoken}
|
||||||
|
|
||||||
|
let params = {
|
||||||
|
row_uuid: this.$props.row.uuid,
|
||||||
|
catalog_unit_cost: this.inputValue,
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$http.post(url, params, {headers: headers}).then(response => {
|
||||||
|
if (!response.data.error) {
|
||||||
|
|
||||||
|
// let parent know cost value has changed
|
||||||
|
// (this in turn will update data in *this*
|
||||||
|
// component, and display will refresh)
|
||||||
|
this.$emit('input', response.data.row.catalog_unit_cost,
|
||||||
|
this.$props.row._index)
|
||||||
|
|
||||||
|
// and hide the input box
|
||||||
|
this.editing = false
|
||||||
|
|
||||||
|
} else {
|
||||||
|
this.$buefy.toast.open({
|
||||||
|
message: "Submit failed: " + response.data.error,
|
||||||
|
type: 'is-warning',
|
||||||
|
duration: 4000, // 4 seconds
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}, response => {
|
||||||
|
this.$buefy.toast.open({
|
||||||
|
message: "Submit failed: (unknown error)",
|
||||||
|
type: 'is-warning',
|
||||||
|
duration: 4000, // 4 seconds
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
Vue.component('receiving-cost-editor', ReceivingCostEditor)
|
||||||
|
|
||||||
|
${rows_grid.component_studly}.methods.catalogUnitCostClicked = function(row) {
|
||||||
|
|
||||||
|
// start edit for clicked cell
|
||||||
|
this.$refs['catalogUnitCost_' + row.uuid].startEdit()
|
||||||
|
}
|
||||||
|
|
||||||
|
${rows_grid.component_studly}.methods.catalogCostConfirmed = function(amount, index) {
|
||||||
|
|
||||||
|
// update display to indicate cost was confirmed
|
||||||
|
this.addRowClass(index, 'catalog_cost_confirmed')
|
||||||
|
|
||||||
|
// start editing next row, unless there are no more
|
||||||
|
let nextRow = index + 1
|
||||||
|
if (this.data.length > nextRow) {
|
||||||
|
nextRow = this.data[nextRow]
|
||||||
|
this.$refs['catalogUnitCost_' + nextRow.uuid].startEdit()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
% endif
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
</%def>
|
</%def>
|
||||||
|
|
||||||
|
|
||||||
${parent.body()}
|
${parent.body()}
|
||||||
|
|
||||||
% if master.handler.allow_truck_dump_receiving() and master.has_perm('edit_row'):
|
% if not use_buefy and master.handler.allow_truck_dump_receiving() and master.has_perm('edit_row'):
|
||||||
${h.form(url('{}.transform_unit_row'.format(route_prefix), uuid=batch.uuid), name='transform-unit-form')}
|
${h.form(url('{}.transform_unit_row'.format(route_prefix), uuid=batch.uuid), name='transform-unit-form')}
|
||||||
${h.csrf_token(request)}
|
${h.csrf_token(request)}
|
||||||
${h.hidden('row_uuid')}
|
${h.hidden('row_uuid')}
|
||||||
|
|
|
@ -64,6 +64,21 @@ def csrf_token(request, name='_csrf'):
|
||||||
return HTML.tag("div", tags.hidden(name, value=token), style="display:none;")
|
return HTML.tag("div", tags.hidden(name, value=token), style="display:none;")
|
||||||
|
|
||||||
|
|
||||||
|
def get_form_data(request):
|
||||||
|
"""
|
||||||
|
Returns the effective form data for the given request. Mostly
|
||||||
|
this is a convenience, to return either POST or JSON depending on
|
||||||
|
the type of request.
|
||||||
|
"""
|
||||||
|
# nb. we prefer JSON only if no POST is present
|
||||||
|
# TODO: this seems to work for our use case at least, but perhaps
|
||||||
|
# there is a better way? see also
|
||||||
|
# https://docs.pylonsproject.org/projects/pyramid/en/latest/api/request.html#pyramid.request.Request.is_xhr
|
||||||
|
if request.is_xhr and not request.POST:
|
||||||
|
return request.json_body
|
||||||
|
return request.POST
|
||||||
|
|
||||||
|
|
||||||
def should_use_buefy(request):
|
def should_use_buefy(request):
|
||||||
"""
|
"""
|
||||||
Returns a flag indicating whether or not the current theme supports (and
|
Returns a flag indicating whether or not the current theme supports (and
|
||||||
|
|
|
@ -46,6 +46,7 @@ from pyramid import httpexceptions
|
||||||
from webhelpers2.html import tags, HTML
|
from webhelpers2.html import tags, HTML
|
||||||
|
|
||||||
from tailbone import forms, grids
|
from tailbone import forms, grids
|
||||||
|
from tailbone.util import get_form_data
|
||||||
from tailbone.views.purchasing import PurchasingBatchView
|
from tailbone.views.purchasing import PurchasingBatchView
|
||||||
|
|
||||||
|
|
||||||
|
@ -715,6 +716,11 @@ class ReceivingBatchView(PurchasingBatchView):
|
||||||
|
|
||||||
return breakdown
|
return breakdown
|
||||||
|
|
||||||
|
def allow_edit_catalog_unit_cost(self, batch):
|
||||||
|
return (not batch.executed
|
||||||
|
and self.has_perm('edit_row')
|
||||||
|
and self.batch_handler.allow_receiving_edit_catalog_unit_cost())
|
||||||
|
|
||||||
def template_kwargs_view(self, **kwargs):
|
def template_kwargs_view(self, **kwargs):
|
||||||
kwargs = super(ReceivingBatchView, self).template_kwargs_view(**kwargs)
|
kwargs = super(ReceivingBatchView, self).template_kwargs_view(**kwargs)
|
||||||
batch = kwargs['instance']
|
batch = kwargs['instance']
|
||||||
|
@ -739,6 +745,8 @@ class ReceivingBatchView(PurchasingBatchView):
|
||||||
data=breakdown,
|
data=breakdown,
|
||||||
columns=['title', 'count'])
|
columns=['title', 'count'])
|
||||||
|
|
||||||
|
kwargs['allow_edit_catalog_unit_cost'] = self.allow_edit_catalog_unit_cost(batch)
|
||||||
|
|
||||||
return kwargs
|
return kwargs
|
||||||
|
|
||||||
def get_context_credits(self, row):
|
def get_context_credits(self, row):
|
||||||
|
@ -933,6 +941,7 @@ 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)
|
||||||
|
use_buefy = self.get_use_buefy()
|
||||||
batch = self.get_instance()
|
batch = self.get_instance()
|
||||||
|
|
||||||
# vendor_code
|
# vendor_code
|
||||||
|
@ -943,6 +952,10 @@ class ReceivingBatchView(PurchasingBatchView):
|
||||||
if (self.handler.has_purchase_order(batch)
|
if (self.handler.has_purchase_order(batch)
|
||||||
or self.handler.has_invoice_file(batch)):
|
or self.handler.has_invoice_file(batch)):
|
||||||
g.remove('catalog_unit_cost')
|
g.remove('catalog_unit_cost')
|
||||||
|
elif use_buefy and self.allow_edit_catalog_unit_cost(batch):
|
||||||
|
g.set_raw_renderer('catalog_unit_cost', self.render_catalog_unit_cost)
|
||||||
|
g.set_click_handler('catalog_unit_cost',
|
||||||
|
'catalogUnitCostClicked(props.row)')
|
||||||
|
|
||||||
# po_unit_cost
|
# po_unit_cost
|
||||||
if self.handler.has_invoice_file(batch):
|
if self.handler.has_invoice_file(batch):
|
||||||
|
@ -1001,6 +1014,14 @@ class ReceivingBatchView(PurchasingBatchView):
|
||||||
else:
|
else:
|
||||||
g.set_enum('truck_dump_status', model.PurchaseBatchRow.STATUS)
|
g.set_enum('truck_dump_status', model.PurchaseBatchRow.STATUS)
|
||||||
|
|
||||||
|
def render_catalog_unit_cost(self):
|
||||||
|
return HTML.tag('receiving-cost-editor', **{
|
||||||
|
'v-model': 'props.row.catalog_unit_cost',
|
||||||
|
':ref': "'catalogUnitCost_' + props.row.uuid",
|
||||||
|
':row': 'props.row',
|
||||||
|
'@input': 'catalogCostConfirmed',
|
||||||
|
})
|
||||||
|
|
||||||
def row_grid_extra_class(self, row, i):
|
def row_grid_extra_class(self, row, i):
|
||||||
css_class = super(ReceivingBatchView, self).row_grid_extra_class(row, i)
|
css_class = super(ReceivingBatchView, self).row_grid_extra_class(row, i)
|
||||||
|
|
||||||
|
@ -1790,10 +1811,10 @@ class ReceivingBatchView(PurchasingBatchView):
|
||||||
|
|
||||||
def update_row_cost(self):
|
def update_row_cost(self):
|
||||||
"""
|
"""
|
||||||
AJAX view for updating the invoice (actual) unit cost for a row.
|
AJAX view for updating various cost fields in a data row.
|
||||||
"""
|
"""
|
||||||
batch = self.get_instance()
|
batch = self.get_instance()
|
||||||
data = dict(self.request.POST)
|
data = dict(get_form_data(self.request))
|
||||||
|
|
||||||
# validate row
|
# validate row
|
||||||
uuid = data.get('row_uuid')
|
uuid = data.get('row_uuid')
|
||||||
|
@ -1939,6 +1960,9 @@ class ReceivingBatchView(PurchasingBatchView):
|
||||||
{'section': 'rattail.batch',
|
{'section': 'rattail.batch',
|
||||||
'option': 'purchase.receiving.should_autofix_invoice_case_vs_unit',
|
'option': 'purchase.receiving.should_autofix_invoice_case_vs_unit',
|
||||||
'type': bool},
|
'type': bool},
|
||||||
|
{'section': 'rattail.batch',
|
||||||
|
'option': 'purchase.receiving.allow_edit_catalog_unit_cost',
|
||||||
|
'type': bool},
|
||||||
|
|
||||||
# mobile interface
|
# mobile interface
|
||||||
{'section': 'rattail.batch',
|
{'section': 'rattail.batch',
|
||||||
|
|
Loading…
Reference in a new issue