Overhaul the /ordering batch API somewhat; update docs

mostly a savepoint; the /ordering API still needs some work for sure
This commit is contained in:
Lance Edgar 2020-02-23 21:07:50 -06:00
parent 877e6088e2
commit 6c5cc95e51
6 changed files with 176 additions and 47 deletions

View file

@ -0,0 +1,15 @@
``tailbone.api.batch.core``
===========================
.. automodule:: tailbone.api.batch.core
.. autoclass:: APIBatchMixin
.. autoclass:: APIBatchView
.. autoclass:: APIBatchRowView
.. autoattribute:: editable
.. autoattribute:: supports_quick_entry

View file

@ -0,0 +1,41 @@
``tailbone.api.batch.ordering``
===============================
.. automodule:: tailbone.api.batch.ordering
.. autoclass:: OrderingBatchViews
.. autoattribute:: collection_url_prefix
.. autoattribute:: object_url_prefix
.. autoattribute:: model_class
.. autoattribute:: route_prefix
.. autoattribute:: permission_prefix
.. autoattribute:: default_handler_spec
.. automethod:: base_query
.. automethod:: create_object
.. autoclass:: OrderingBatchRowViews
.. autoattribute:: collection_url_prefix
.. autoattribute:: object_url_prefix
.. autoattribute:: model_class
.. autoattribute:: route_prefix
.. autoattribute:: permission_prefix
.. autoattribute:: default_handler_spec
.. autoattribute:: supports_quick_entry
.. automethod:: update_object

View file

@ -41,6 +41,7 @@ extensions = [
] ]
intersphinx_mapping = { intersphinx_mapping = {
'rattail': ('https://rattailproject.org/docs/rattail/', None),
'webhelpers2': ('https://webhelpers2.readthedocs.io/en/latest/', None), 'webhelpers2': ('https://webhelpers2.readthedocs.io/en/latest/', None),
} }

View file

@ -42,6 +42,8 @@ Package API:
.. toctree:: .. toctree::
:maxdepth: 1 :maxdepth: 1
api/api/batch/core
api/api/batch/ordering
api/forms api/forms
api/grids api/grids
api/progress api/progress

View file

@ -219,6 +219,7 @@ class APIBatchRowView(APIBatchMixin, APIMasterView):
""" """
Base class for all API views which are meant to handle "batch rows" data. Base class for all API views which are meant to handle "batch rows" data.
""" """
editable = False
supports_quick_entry = False supports_quick_entry = False
def __init__(self, request, **kwargs): def __init__(self, request, **kwargs):
@ -280,6 +281,8 @@ class APIBatchRowView(APIBatchMixin, APIMasterView):
resource.add_view(cls.collection_get, permission='{}.view'.format(permission_prefix)) resource.add_view(cls.collection_get, permission='{}.view'.format(permission_prefix))
resource.add_view(cls.get, permission='{}.view'.format(permission_prefix)) resource.add_view(cls.get, permission='{}.view'.format(permission_prefix))
if cls.editable:
resource.add_view(cls.post, permission='{}.edit'.format(permission_prefix))
rows_resource = resource.add_resource(cls, collection_path=collection_url_prefix, rows_resource = resource.add_resource(cls, collection_path=collection_url_prefix,
path='{}/{{uuid}}'.format(object_url_prefix)) path='{}/{{uuid}}'.format(object_url_prefix))
config.add_cornice_resource(rows_resource) config.add_cornice_resource(rows_resource)

View file

@ -2,7 +2,7 @@
################################################################################ ################################################################################
# #
# Rattail -- Retail Software Framework # Rattail -- Retail Software Framework
# Copyright © 2010-2019 Lance Edgar # Copyright © 2010-2020 Lance Edgar
# #
# This file is part of Rattail. # This file is part of Rattail.
# #
@ -22,6 +22,9 @@
################################################################################ ################################################################################
""" """
Tailbone Web API - Ordering Batches Tailbone Web API - Ordering Batches
These views expose the basic CRUD interface to "ordering" batches, for the web
API.
""" """
from __future__ import unicode_literals, absolute_import from __future__ import unicode_literals, absolute_import
@ -29,73 +32,137 @@ from __future__ import unicode_literals, absolute_import
import six import six
from rattail.db import model from rattail.db import model
from rattail.time import localtime from rattail.util import pretty_quantity
from cornice.resource import resource, view from tailbone.api.batch import APIBatchView, APIBatchRowView
from tailbone.api import APIMasterView
@resource(collection_path='/ordering-batches', path='/ordering-batch/{uuid}') class OrderingBatchViews(APIBatchView):
class OrderingBatchView(APIMasterView):
model_class = model.PurchaseBatch model_class = model.PurchaseBatch
default_handler_spec = 'rattail.batch.purchase:PurchaseBatchHandler'
route_prefix = 'orderingbatchviews'
permission_prefix = 'ordering'
collection_url_prefix = '/ordering-batches'
object_url_prefix = '/ordering-batch'
def base_query(self): def base_query(self):
return self.Session.query(model.PurchaseBatch)\ """
.filter(model.PurchaseBatch.mode == self.enum.PURCHASE_BATCH_MODE_ORDERING) Modifies the default logic as follows:
def pretty_datetime(self, dt): Adds a condition to the query, to ensure only purchase batches with
if not dt: "ordering" mode are returned.
return "" """
return dt.strftime('%Y-%m-%d @ %I:%M %p') query = super(OrderingBatchViews, self).base_query()
query = query.filter(model.PurchaseBatch.mode == self.enum.PURCHASE_BATCH_MODE_ORDERING)
return query
def normalize(self, batch): def normalize(self, batch):
data = super(OrderingBatchViews, self).normalize(batch)
created = batch.created data['vendor_uuid'] = batch.vendor.uuid
created = localtime(self.rattail_config, created, from_utc=True) data['vendor_display'] = six.text_type(batch.vendor)
created = self.pretty_datetime(created)
executed = batch.executed data['department_uuid'] = batch.department_uuid
if executed: data['department_display'] = six.text_type(batch.department) if batch.department else None
executed = localtime(self.rattail_config, executed, from_utc=True)
executed = self.pretty_datetime(executed)
return { data['po_total_calculated_display'] = "${:0.2f}".format(batch.po_total_calculated) if batch.po_total_calculated is not None else None
'uuid': batch.uuid,
'_str': six.text_type(batch), return data
'id': batch.id,
'id_str': batch.id_str, def create_object(self, data):
'description': batch.description, """
'vendor_uuid': batch.vendor.uuid, Modifies the default logic as follows:
'vendor_name': batch.vendor.name,
'po_total_calculated': batch.po_total_calculated, Sets the mode to "ordering" for the new batch.
'po_total_calculated_display': "${:0.2f}".format(batch.po_total_calculated) if batch.po_total_calculated is not None else None, """
'date_ordered': six.text_type(batch.date_ordered or ''), data = dict(data)
'created': created, data['mode'] = self.enum.PURCHASE_BATCH_MODE_ORDERING
'created_by_uuid': batch.created_by.uuid, batch = super(OrderingBatchViews, self).create_object(data)
'created_by_display': six.text_type(batch.created_by), return batch
'executed': executed,
'executed_by_uuid': batch.executed_by_uuid,
'executed_by_display': six.text_type(batch.executed_by or ''),
}
@view(permission='ordering.list')
def collection_get(self): def collection_get(self):
return self._collection_get() return self._collection_get()
# @view(permission='ordering.create') def collection_post(self):
# def collection_post(self): return self._collection_post()
# return self._collection_post()
@view(permission='ordering.view')
def get(self): def get(self):
return self._get() return self._get()
# @view(permission='ordering.edit')
# def post(self): class OrderingBatchRowViews(APIBatchRowView):
# return self._post()
model_class = model.PurchaseBatchRow
default_handler_spec = 'rattail.batch.purchase:PurchaseBatchHandler'
route_prefix = 'ordering.rows'
permission_prefix = 'ordering'
collection_url_prefix = '/ordering-batch-rows'
object_url_prefix = '/ordering-batch-row'
supports_quick_entry = True
def normalize(self, row):
batch = row.batch
data = super(OrderingBatchRowViews, self).normalize(row)
data['item_id'] = row.item_id
# data['upc'] = six.text_type(row.upc)
# data['upc_pretty'] = row.upc.pretty() if row.upc else None
# data['brand_name'] = row.brand_name
data['description'] = row.description
# data['size'] = row.size
# data['full_description'] = row.product.full_description if row.product else row.description
# # only provide image url if so configured
# if self.rattail_config.getbool('rattail.batch', 'purchase.mobile_images', default=True):
# data['image_url'] = pod.get_image_url(self.rattail_config, row.upc) if row.upc else None
# # unit_uom can vary by product
# data['unit_uom'] = 'LB' if row.product and row.product.weighed else 'EA'
# data['case_quantity'] = row.case_quantity
# data['order_quantities_known'] = batch.order_quantities_known
data['cases_ordered'] = row.cases_ordered
data['units_ordered'] = row.units_ordered
data['units_ordered_display'] = pretty_quantity(row.units_ordered or 0, empty_zero=False)
# 'po_unit_cost': row.po_unit_cost,
data['po_unit_cost_display'] = "${:0.2f}".format(row.po_unit_cost) if row.po_unit_cost is not None else None
# 'po_total_calculated': row.po_total_calculated,
data['po_total_calculated_display'] = "${:0.2f}".format(row.po_total_calculated) if row.po_total_calculated is not None else None
# 'status_code': row.status_code,
# 'status_display': row.STATUS.get(row.status_code, six.text_type(row.status_code)),
return data
def collection_get(self):
return self._collection_get()
def get(self):
return self._get()
def post(self):
return self._post()
def update_object(self, row, data):
"""
Overrides the default logic as follows:
So far, we only allow updating the ``cases_ordered`` and/or
``units_ordered`` quantities; therefore ``data`` should have one or
both of those keys.
This data is then passed to the
:meth:`~rattail:rattail.batch.purchase.PurchaseBatchHandler.update_row_quantity()`
method of the batch handler.
Note that the "normal" logic for this method is not invoked at all.
"""
self.handler.update_row_quantity(row, **data)
return row
def includeme(config): def includeme(config):
config.scan(__name__) OrderingBatchViews.defaults(config)
OrderingBatchRowViews.defaults(config)