- % if data and cost.product_uuid in data['items']:
- ${'{} / {}'.format(int(data['items'][cost.product_uuid].cases_ordered or 0), int(data['items'][cost.product_uuid].units_ordered or 0))}
-## ${int(data['items'][cost.product_uuid].cases_ordered or 0) or ''}
+ % if data:
+ <% item = data['items'].get(cost.product_uuid) %>
+ % if item:
+ % if data['purchase'].date_ordered and (item.cases_ordered is not None or item.units_ordered is not None):
+ ${'{} / {}'.format(int(item.cases_ordered or 0), int(item.units_ordered or 0))}
+ % elif item.cases_received is not None or item.units_received is not None:
+ ${'{} / {}'.format(int(item.cases_received or 0), int(item.units_received or 0))}
+ % endif
+ % endif
% endif
% endfor
diff --git a/tailbone/templates/purchases/batches/view.mako b/tailbone/templates/purchases/batches/view.mako
index 1be34ce1..3115a3b1 100644
--- a/tailbone/templates/purchases/batches/view.mako
+++ b/tailbone/templates/purchases/batches/view.mako
@@ -23,7 +23,7 @@
%def>
<%def name="leading_buttons()">
- % if not batch.complete and not batch.executed and request.has_perm('purchases.batch.order_form'):
+ % if batch.mode == enum.PURCHASE_BATCH_MODE_NEW and not batch.complete and not batch.executed and request.has_perm('purchases.batch.order_form'):
% endif
%def>
diff --git a/tailbone/views/batch.py b/tailbone/views/batch.py
index 3b7faa1c..01f113ac 100644
--- a/tailbone/views/batch.py
+++ b/tailbone/views/batch.py
@@ -237,19 +237,18 @@ class BatchMasterView(MasterView):
def save_create_form(self, form):
self.before_create(form)
- # current user is batch creator
- creator = self.request.user or self.late_login_user()
-
- # transfer form data to batch instance
- form.fieldset.sync()
- batch = form.fieldset.model
- batch.created_by = creator
-
- # destroy initial batch and re-make using handler
- kwargs = self.get_batch_kwargs(batch)
- Session.expunge(batch)
- # TODO: is no_autoflush necessary?
with Session.no_autoflush:
+
+ # transfer form data to batch instance
+ form.fieldset.sync()
+ batch = form.fieldset.model
+
+ # current user is batch creator
+ batch.created_by = self.request.user or self.late_login_user()
+
+ # destroy initial batch and re-make using handler
+ kwargs = self.get_batch_kwargs(batch)
+ Session.expunge(batch)
batch = self.handler.make_batch(Session(), **kwargs)
Session.flush()
@@ -279,6 +278,7 @@ class BatchMasterView(MasterView):
kwargs['filename'] = batch.filename
return kwargs
+ # TODO: deprecate / remove this (is it used at all now?)
def init_batch(self, batch):
"""
Initialize a new batch. Derived classes can override this to
@@ -667,7 +667,7 @@ class BatchMasterView(MasterView):
fs.status_text.set(readonly=True)
fs.removed.set(readonly=True)
try:
- fs.product.set(readonly=True)
+ fs.product.set(readonly=True, renderer=forms.renderers.ProductFieldRenderer)
except AttributeError:
pass
diff --git a/tailbone/views/purchases/batch.py b/tailbone/views/purchases/batch.py
index 5132012b..51fe9847 100644
--- a/tailbone/views/purchases/batch.py
+++ b/tailbone/views/purchases/batch.py
@@ -36,6 +36,7 @@ from rattail.core import Object
from rattail.util import OrderedDict
import formalchemy as fa
+from pyramid import httpexceptions
from tailbone import forms
from tailbone.db import Session
@@ -56,6 +57,9 @@ class PurchaseBatchView(BatchMasterView):
rows_editable = True
edit_with_rows = False
+ def get_instance_title(self, batch):
+ return '{} ({})'.format(batch.id_str, self.enum.PURCHASE_BATCH_MODE[batch.mode])
+
def _preconfigure_grid(self, g):
super(PurchaseBatchView, self)._preconfigure_grid(g)
@@ -74,6 +78,7 @@ class PurchaseBatchView(BatchMasterView):
g.filters['complete'].default_verb = 'is_true'
g.date_ordered.set(label="Ordered")
+ g.date_received.set(label="Received")
g.mode.set(renderer=forms.renderers.EnumFieldRenderer(self.enum.PURCHASE_BATCH_MODE))
def configure_grid(self, g):
@@ -93,11 +98,14 @@ class PurchaseBatchView(BatchMasterView):
def _preconfigure_fieldset(self, fs):
super(PurchaseBatchView, self)._preconfigure_fieldset(fs)
fs.mode.set(renderer=forms.renderers.EnumFieldRenderer(self.enum.PURCHASE_BATCH_MODE))
- fs.purchase.set(renderer=forms.renderers.PurchaseFieldRenderer)
- fs.vendor.set(renderer=forms.renderers.VendorFieldRenderer)
+ fs.purchase.set(renderer=forms.renderers.PurchaseFieldRenderer, options=[])
+ fs.vendor.set(renderer=forms.renderers.VendorFieldRenderer,
+ attrs={'selected': 'vendor_selected',
+ 'cleared': 'vendor_cleared'})
fs.buyer.set(renderer=forms.renderers.EmployeeFieldRenderer)
fs.po_number.set(label="PO Number")
- fs.po_total.set(label="PO Total", readonly=True)
+ fs.po_total.set(label="PO Total", readonly=True, renderer=forms.renderers.CurrencyFieldRenderer)
+ fs.invoice_total.set(readonly=True, renderer=forms.renderers.CurrencyFieldRenderer)
fs.append(fa.Field('vendor_email', readonly=True,
value=lambda b: b.vendor.email.address if b.vendor.email else None))
@@ -123,17 +131,19 @@ class PurchaseBatchView(BatchMasterView):
include=[
fs.id,
fs.mode,
- fs.purchase,
fs.store,
fs.vendor,
+ fs.purchase,
fs.vendor_email,
fs.vendor_fax,
fs.vendor_contact,
fs.vendor_phone,
fs.buyer,
fs.date_ordered,
+ fs.date_received,
fs.po_number,
fs.po_total,
+ fs.invoice_total,
fs.created,
fs.created_by,
fs.complete,
@@ -142,8 +152,8 @@ class PurchaseBatchView(BatchMasterView):
])
if self.creating:
- del fs.purchase
del fs.po_total
+ del fs.invoice_total
del fs.complete
del fs.vendor_email
del fs.vendor_fax
@@ -163,12 +173,14 @@ class PurchaseBatchView(BatchMasterView):
if buyer:
fs.model.buyer = buyer
- # default order date is today
- fs.model.date_ordered = localtime(self.rattail_config).date()
+ # TODO: something tells me this isn't quite safe..
+ # all dates have today as default
+ today = localtime(self.rattail_config).date()
+ fs.model.date_ordered = today
+ fs.model.date_received = today
# TODO: temp hack until we support more modes
modes = dict(self.enum.PURCHASE_BATCH_MODE)
- del modes[self.enum.PURCHASE_BATCH_MODE_RECEIVING]
del modes[self.enum.PURCHASE_BATCH_MODE_COSTING]
fs.mode.set(renderer=forms.renderers.EnumFieldRenderer(modes))
@@ -176,6 +188,30 @@ class PurchaseBatchView(BatchMasterView):
fs.mode.set(readonly=True)
fs.store.set(readonly=True)
fs.vendor.set(readonly=True)
+ fs.purchase.set(readonly=True)
+
+ def eligible_purchases(self):
+ uuid = self.request.GET.get('vendor_uuid')
+ vendor = Session.query(model.Vendor).get(uuid) if uuid else None
+ if not vendor:
+ return {'error': "Must specify a vendor."}
+
+ mode = self.request.GET.get('mode')
+ mode = int(mode) if mode and mode.isdigit() else None
+ if not mode or mode not in self.enum.PURCHASE_BATCH_MODE:
+ return {'error': "Unknown mode: {}".format(mode)}
+
+ purchases = Session.query(model.Purchase)\
+ .filter(model.Purchase.vendor == vendor)
+ if mode == enum.PURCHASE_BATCH_MODE_RECEIVING:
+ purchases = purchases.filter(model.Purchase.status == self.enum.PURCHASE_STATUS_ORDERED)\
+ .order_by(model.Purchase.date_ordered, model.Purchase.created)
+
+ return {'purchases': [{'uuid': p.uuid, 'display': self.render_eligible_purchase(p)}
+ for p in purchases]}
+
+ def render_eligible_purchase(self, purchase):
+ return '{} for ${:0,.2f} ({})'.format(purchase.date_ordered, purchase.po_total, purchase.buyer)
def get_batch_kwargs(self, batch):
kwargs = super(PurchaseBatchView, self).get_batch_kwargs(batch)
@@ -192,8 +228,20 @@ class PurchaseBatchView(BatchMasterView):
kwargs['buyer'] = batch.buyer
elif batch.buyer_uuid:
kwargs['buyer_uuid'] = batch.buyer_uuid
- kwargs['date_ordered'] = batch.date_ordered
kwargs['po_number'] = batch.po_number
+
+ if batch.mode == self.enum.PURCHASE_BATCH_MODE_NEW:
+ kwargs['date_ordered'] = batch.date_ordered
+
+ elif batch.mode == self.enum.PURCHASE_BATCH_MODE_RECEIVING:
+ kwargs['date_received'] = batch.date_received
+ if batch.purchase_uuid:
+ purchase = Session.query(model.Purchase).get(batch.purchase_uuid)
+ assert purchase
+ kwargs['purchase'] = purchase
+ kwargs['date_ordered'] = purchase.date_ordered
+ kwargs['po_total'] = purchase.po_total
+
return kwargs
def template_kwargs_view(self, **kwargs):
@@ -214,11 +262,16 @@ class PurchaseBatchView(BatchMasterView):
g.upc.set(label="UPC")
g.brand_name.set(label="Brand")
- g.cases_ordered.set(label="Cases")
- g.units_ordered.set(label="Units")
- g.po_total.set(label="Total")
+ g.cases_ordered.set(label="Cases Ord.")
+ g.units_ordered.set(label="Units Ord.")
+ g.cases_received.set(label="Cases Rec.")
+ g.units_received.set(label="Units Rec.")
+ g.po_total.set(label="Total", renderer=forms.renderers.CurrencyFieldRenderer)
+ g.invoice_total.set(label="Total", renderer=forms.renderers.CurrencyFieldRenderer)
def configure_row_grid(self, g):
+ batch = self.get_instance()
+
g.configure(
include=[
g.sequence,
@@ -228,32 +281,38 @@ class PurchaseBatchView(BatchMasterView):
g.size,
g.cases_ordered,
g.units_ordered,
+ g.cases_received,
+ g.units_received,
g.po_total,
+ g.invoice_total,
g.status_code,
],
readonly=True)
+ if batch.mode == self.enum.PURCHASE_BATCH_MODE_NEW:
+ del g.cases_received
+ del g.units_received
+ del g.invoice_total
+ elif batch.mode == self.enum.PURCHASE_BATCH_MODE_RECEIVING:
+ del g.po_total
+
def make_row_grid_tools(self, batch):
return self.make_default_row_grid_tools(batch)
-# def row_grid_row_attrs(self, row, i):
-# attrs = {}
-# if row.status_code in (row.STATUS_NOT_IN_PURCHASE,
-# row.STATUS_NOT_IN_INVOICE,
-# row.STATUS_DIFFERS_FROM_PURCHASE):
-# attrs['class_'] = 'notice'
-# if row.status_code in (row.STATUS_NOT_IN_DB,
-# row.STATUS_COST_NOT_IN_DB,
-# row.STATUS_NO_CASE_QUANTITY):
-# attrs['class_'] = 'warning'
-# return attrs
+ def row_grid_row_attrs(self, row, i):
+ attrs = {}
+ if row.status_code in (row.STATUS_INCOMPLETE,
+ row.STATUS_ORDERED_RECEIVED_DIFFER):
+ attrs['class_'] = 'notice'
+ return attrs
def _preconfigure_row_fieldset(self, fs):
super(PurchaseBatchView, self)._preconfigure_row_fieldset(fs)
fs.upc.set(label="UPC")
fs.brand_name.set(label="Brand")
fs.po_unit_cost.set(label="PO Unit Cost")
- fs.po_total.set(label="PO Total")
+ fs.po_total.set(label="PO Total", renderer=forms.renderers.CurrencyFieldRenderer)
+ fs.invoice_total.set(renderer=forms.renderers.CurrencyFieldRenderer)
fs.append(fa.Field('item_lookup', label="Item Lookup Code", required=True,
validate=self.item_lookup))
@@ -277,35 +336,67 @@ class PurchaseBatchView(BatchMasterView):
raise fa.ValidationError("Product not found")
def configure_row_fieldset(self, fs):
+ try:
+ batch = self.get_instance()
+ except httpexceptions.HTTPNotFound:
+ batch = self.get_row_instance().batch
+
+ fs.configure(
+ include=[
+ fs.item_lookup,
+ fs.upc,
+ fs.product,
+ fs.cases_ordered,
+ fs.units_ordered,
+ fs.cases_received,
+ fs.units_received,
+ fs.po_total,
+ fs.invoice_total,
+ ])
if self.creating:
- fs.configure(
- include=[
- fs.item_lookup,
- fs.cases_ordered,
- fs.units_ordered,
- ])
+ del fs.upc
+ del fs.product
+ del fs.po_total
+ del fs.invoice_total
+ if batch.mode == self.enum.PURCHASE_BATCH_MODE_NEW:
+ del fs.cases_received
+ del fs.units_received
+ elif batch.mode == self.enum.PURCHASE_BATCH_MODE_RECEIVING:
+ del fs.cases_ordered
+ del fs.units_ordered
elif self.editing:
- fs.configure(
- include=[
- fs.upc.readonly(),
- fs.product.readonly(),
- fs.cases_ordered,
- fs.units_ordered,
- ])
+ del fs.item_lookup
+ fs.upc.set(readonly=True)
+ fs.product.set(readonly=True)
+ del fs.po_total
+ del fs.invoice_total
+
+ elif self.viewing:
+ del fs.item_lookup
def before_create_row(self, form):
row = form.fieldset.model
batch = self.get_instance()
- row.sequence = max([0] + [r.sequence for r in batch.data_rows]) + 1
- row.batch = batch
+ batch.add_row(row)
# TODO: this seems heavy-handed but works..
row.product_uuid = self.item_lookup(form.fieldset.item_lookup.value)
def after_create_row(self, row):
self.handler.refresh_row(row)
+ def after_edit_row(self, row):
+ batch = row.batch
+
+ # first undo any totals previously in effect for the row
+ if batch.mode == self.enum.PURCHASE_BATCH_MODE_NEW and row.po_total:
+ batch.po_total -= row.po_total
+ elif batch.mode == self.enum.PURCHASE_BATCH_MODE_RECEIVING and row.invoice_total:
+ batch.invoice_total -= row.invoice_total
+
+ self.handler.refresh_row(row)
+
def redirect_after_create_row(self, row):
self.request.session.flash("Added item: {} {}".format(row.upc.pretty(), row.product))
return self.redirect(self.request.current_route_url())
@@ -478,6 +569,12 @@ class PurchaseBatchView(BatchMasterView):
model_key = cls.get_model_key()
model_title = cls.get_model_title()
+ # eligible purchases (AJAX)
+ config.add_route('{}.eligible_purchases'.format(route_prefix), '{}/eligible-purchases'.format(url_prefix))
+ config.add_view(cls, attr='eligible_purchases', route_name='{}.eligible_purchases'.format(route_prefix),
+ renderer='json', permission='{}.view'.format(permission_prefix))
+
+ # defaults
cls._batch_defaults(config)
cls._defaults(config)
diff --git a/tailbone/views/purchases/core.py b/tailbone/views/purchases/core.py
index 4716f8a9..6acaff77 100644
--- a/tailbone/views/purchases/core.py
+++ b/tailbone/views/purchases/core.py
@@ -45,8 +45,9 @@ class BatchesFieldRenderer(fa.FieldRenderer):
return ''
def render(batch):
- return tags.link_to('{} ({})'.format(batch.id_str, enum.PURCHASE_BATCH_MODE[batch.mode]),
- self.request.route_url('purchases.batch.view', uuid=batch.uuid))
+ display = '{} ({}){}'.format(batch.id_str, enum.PURCHASE_BATCH_MODE[batch.mode],
+ '' if batch.executed else ' (pending)')
+ return tags.link_to(display, self.request.route_url('purchases.batch.view', uuid=batch.uuid))
enum = self.request.rattail_config.get_enum()
items = [HTML.tag('li', c=render(batch)) for batch in batches]
@@ -65,6 +66,17 @@ class PurchaseView(MasterView):
model_row_class = model.PurchaseItem
row_model_title = 'Purchase Item'
+ def get_instance_title(self, purchase):
+ if purchase.status >= self.enum.PURCHASE_STATUS_RECEIVED:
+ if purchase.date_received:
+ return "{} (received {})".format(purchase.vendor, purchase.date_received.strftime('%Y-%m-%d'))
+ return "{} (received)".format(purchase.vendor)
+ elif purchase.status >= self.enum.PURCHASE_STATUS_ORDERED:
+ if purchase.date_ordered:
+ return "{} (ordered {})".format(purchase.vendor, purchase.date_ordered.strftime('%Y-%m-%d'))
+ return "{} (ordered)".format(purchase.vendor)
+ return unicode(purchase)
+
def _preconfigure_grid(self, g):
g.joiners['store'] = lambda q: q.join(model.Store)
g.filters['store'] = g.make_filter('store', model.Store.name)
@@ -106,7 +118,8 @@ class PurchaseView(MasterView):
fs.status.set(renderer=forms.renderers.EnumFieldRenderer(enum.PURCHASE_STATUS),
readonly=True)
fs.po_number.set(label="PO Number")
- fs.po_total.set(label="PO Total")
+ fs.po_total.set(label="PO Total", renderer=forms.renderers.CurrencyFieldRenderer)
+ fs.invoice_total.set(renderer=forms.renderers.CurrencyFieldRenderer)
fs.batches.set(renderer=BatchesFieldRenderer)
def configure_fieldset(self, fs):
@@ -114,15 +127,24 @@ class PurchaseView(MasterView):
include=[
fs.store,
fs.vendor,
+ fs.status,
fs.buyer,
fs.date_ordered,
+ fs.date_received,
fs.po_number,
fs.po_total,
- fs.status,
+ fs.invoice_number,
+ fs.invoice_total,
fs.created,
fs.created_by,
fs.batches,
])
+ if self.viewing:
+ purchase = fs.model
+ if purchase.status == self.enum.PURCHASE_STATUS_ORDERED:
+ del fs.date_received
+ del fs.invoice_number
+ del fs.invoice_total
def get_parent(self, item):
return item.purchase
@@ -136,11 +158,15 @@ class PurchaseView(MasterView):
g.sequence.set(label="Seq")
g.upc.set(label="UPC")
g.brand_name.set(label="Brand")
- g.cases_ordered.set(label="Cases")
- g.units_ordered.set(label="Units")
- g.po_total.set(label="PO Total")
+ g.cases_ordered.set(label="Cases Ord.")
+ g.units_ordered.set(label="Units Ord.")
+ g.cases_received.set(label="Cases Rec.")
+ g.units_received.set(label="Units Rec.")
+ g.po_total.set(label="Total", renderer=forms.renderers.CurrencyFieldRenderer)
+ g.invoice_total.set(label="Total", renderer=forms.renderers.CurrencyFieldRenderer)
def configure_row_grid(self, g):
+ purchase = self.get_instance()
g.configure(
include=[
g.sequence,
@@ -150,15 +176,26 @@ class PurchaseView(MasterView):
g.size,
g.cases_ordered,
g.units_ordered,
+ g.cases_received,
+ g.units_received,
g.po_total,
+ g.invoice_total,
],
readonly=True)
+ if purchase.status == enum.PURCHASE_STATUS_ORDERED:
+ del g.cases_received
+ del g.units_received
+ del g.invoice_total
+ elif purchase.status == enum.PURCHASE_STATUS_RECEIVED:
+ del g.po_total
def _preconfigure_row_fieldset(self, fs):
fs.vendor_code.set(label="Vendor Item Code")
fs.upc.set(label="UPC")
- fs.po_unit_cost.set(label="PO Unit Cost")
- fs.po_total.set(label="PO Total")
+ fs.po_unit_cost.set(label="PO Unit Cost", renderer=forms.renderers.CurrencyFieldRenderer)
+ fs.po_total.set(label="PO Total", renderer=forms.renderers.CurrencyFieldRenderer)
+ fs.invoice_unit_cost.set(renderer=forms.renderers.CurrencyFieldRenderer)
+ fs.invoice_total.set(renderer=forms.renderers.CurrencyFieldRenderer)
fs.append(fa.Field('department', value=lambda i: '{} {}'.format(i.department_number, i.department_name)))
def configure_row_fieldset(self, fs):
@@ -173,8 +210,12 @@ class PurchaseView(MasterView):
fs.case_quantity,
fs.cases_ordered,
fs.units_ordered,
+ fs.cases_received,
+ fs.units_received,
fs.po_unit_cost,
fs.po_total,
+ fs.invoice_unit_cost,
+ fs.invoice_total,
])