Changeset - 4c19bc4118ff
[Not reviewed]
0 7 1
Lance Edgar (lance) - 3 years ago 2021-12-22 16:30:38
lance@edbob.org
Add basic "pending product" support for new custorder batch
8 files changed with 411 insertions and 15 deletions:
0 comments (0 inline, 0 general)
rattail/app.py
Show inline comments
 
@@ -529,6 +529,26 @@ class AppHandler(object):
 

	
 
        return format_phone_number(number)
 

	
 
    def make_gpc(self, value, **kwargs):
 
        """
 
        Convenience method; shortcut to
 
        :meth:`rattail.products.ProductsHandler.make_upc()`.
 
        """
 
        products_handler = self.get_products_handler()
 
        return products_handler.make_gpc(value, **kwargs)
 

	
 
    def render_gpc(self, value, **kwargs):
 
        """
 
        Returns a human-friendly display string for the given "upc" value.
 

	
 
        :param value: Should be a :class:`~rattail.gpc.GPC` instance.
 
        """
 
        if value:
 
            return value.pretty()
 

	
 
    # TODO: decide which one of these to stick with
 
    render_upc = render_gpc
 

	
 
    def render_currency(self, value, scale=2, **kwargs):
 
        """
 
        Must return a human-friendly display string for the given currency
rattail/batch/custorder.py
Show inline comments
 
@@ -31,7 +31,6 @@ import decimal
 

	
 
import six
 
import sqlalchemy as sa
 
from sqlalchemy import orm
 

	
 
from rattail.db import model
 
from rattail.batch import BatchHandler
 
@@ -84,16 +83,26 @@ class CustomerOrderBatchHandler(BatchHandler):
 
        """
 
        return self.config.getbool('rattail.custorders',
 
                                   'new_orders.allow_contact_info_choice',
 
                                   default=True)
 
                                   default=False)
 

	
 
    def allow_contact_info_creation(self):
 
        """
 
        Returns a boolean indicating whether the user is allowed to
 
        enter *new* contact info for the customer.  This setting
 
        should only be honored if :meth:`allow_contact_info_choice()`
 
        also returns true.
 
        """
 
        return self.config.getbool('rattail.custorders',
 
                                   'new_orders.allow_contact_info_create',
 
                                   default=False)
 

	
 
    def should_restrict_contact_info(self):
 
    def allow_unknown_product(self):
 
        """
 
        Returns a boolean indicating whether contact info should be
 
        "restricted" - i.e. user can only choose from existing contact
 
        info and cannot override by e.g. entering a new phone number.
 
        Returns a boolean indicating whether "unknown" products are
 
        allowed on new orders.
 
        """
 
        return self.config.getbool('rattail.custorders',
 
                                   'new_orders.restrict_contact_info',
 
                                   'allow_unknown_product',
 
                                   default=False)
 

	
 
    def product_price_may_be_questionable(self):
 
@@ -460,12 +469,15 @@ class CustomerOrderBatchHandler(BatchHandler):
 
        info = {
 
            'uuid': product.uuid,
 
            'upc': six.text_type(product.upc),
 
            'item_id': product.item_id,
 
            'scancode': product.scancode,
 
            'upc_pretty': product.upc.pretty(),
 
            'brand_name': product.brand.name if product.brand else None,
 
            'description': product.description,
 
            'size': product.size,
 
            'full_description': product.full_description,
 
            'case_quantity': self.app.render_quantity(self.get_case_size_for_product(product)),
 
            'unit_price': float(product.regular_price.price) if product.regular_price and product.regular_price.price is not None else None,
 
            'unit_price_display': products.render_price(product.regular_price),
 
            'department_name': product.department.name if product.department else None,
 
            'vendor_name': vendor.name if vendor else None,
 
@@ -510,6 +522,28 @@ class CustomerOrderBatchHandler(BatchHandler):
 

	
 
        return info
 

	
 
    def uom_choices_for_row(self, row):
 
        """
 
        Return a list of UOM choices for the given batch row.
 
        """
 
        if row.product:
 
            return self.uom_choices_for_product(row.product)
 

	
 
        choices = []
 

	
 
        # Each
 
        unit_name = self.enum.UNIT_OF_MEASURE[self.enum.UNIT_OF_MEASURE_EACH]
 
        choices.append({'key': self.enum.UNIT_OF_MEASURE_EACH,
 
                        'value': unit_name})
 

	
 
        # Case
 
        case_text = self.enum.UNIT_OF_MEASURE[self.enum.UNIT_OF_MEASURE_CASE]
 
        if case_text:
 
            choices.append({'key': self.enum.UNIT_OF_MEASURE_CASE,
 
                            'value': case_text})
 

	
 
        return choices
 

	
 
    def uom_choices_for_product(self, product):
 
        """
 
        Return a list of UOM choices for the given product.
 
@@ -575,20 +609,111 @@ class CustomerOrderBatchHandler(BatchHandler):
 
        self.add_row(batch, row)
 
        return row
 

	
 
    def add_pending_product(self, batch, pending_info,
 
                            order_quantity, order_uom,
 
                            **kwargs):
 
        """
 
        Add a new row to the batch, for the given "pending" product
 
        and order quantity.
 

	
 
        :param batch: Reference to the current order batch.
 

	
 
        :param pending_info: Dict of information about a new pending product.
 

	
 
        :param order_quantity: Quantity of the item to be added to the order.
 

	
 
        :param order_uom: Unit of measure for the order quantity.
 
        """
 
        session = self.app.get_session(batch)
 

	
 
        # make a new pending product
 
        pending_product = self.make_pending_product(**pending_info)
 
        session.add(pending_product)
 
        session.flush()
 

	
 
        # TODO: seems like make_pending_product() should do this?
 
        if pending_product.department and not pending_product.department_name:
 
            pending_product.department_name = pending_product.department.name
 

	
 
        # make a new row, w/ pending product
 
        row = self.make_row()
 
        row.pending_product = pending_product
 
        row.order_quantity = order_quantity
 
        row.order_uom = order_uom
 
        self.add_row(batch, row)
 
        return row
 

	
 
    def make_pending_product(self, **kwargs):
 
        products_handler = self.app.get_products_handler()
 
        if 'status_code' not in kwargs:
 
            kwargs['status_code'] = self.enum.PENDING_PRODUCT_STATUS_PENDING
 
        return products_handler.make_pending_product(**kwargs)
 

	
 
    def update_pending_product(self, row, data):
 
        """
 
        Update the pending product data for the given batch row.
 
        """
 
        # create pending product if needed
 
        pending = row.pending_product
 
        if not pending:
 
            pending = self.make_pending_product(**data)
 

	
 
        simple_fields = [
 
            'scancode',
 
            'item_id',
 
            'item_type',
 
            'department_name',
 
            'department_uuid',
 
            'brand_name',
 
            'brand_uuid',
 
            'description',
 
            'size',
 
            'vendor_name',
 
            'vendor_uuid',
 
            'vendor_item_code',
 
            'special_order',
 
            'notes',
 
        ]
 

	
 
        decimal_fields = [
 
            'unit_cost',
 
            'case_size',
 
            'case_cost',
 
            'regular_price_amount',
 
        ]
 

	
 
        # update pending product info
 
        if 'upc' in data:
 
            pending.upc = self.app.make_gpc(data['upc']) if data['upc'] else None
 
        for field in simple_fields:
 
            if field in data:
 
                setattr(pending, field, data[field])
 
        for field in decimal_fields:
 
            if field in data:
 
                value = data[field]
 
                value = decimal.Decimal(value) if value is not None else None
 
                setattr(pending, field, value)
 

	
 
        # refresh the row per new pending product info
 
        self.refresh_row(row)
 

	
 
    def refresh_row(self, row):
 
        if not row.product:
 
            if row.item_entry:
 
                session = orm.object_session(row)
 
                # TODO: should do more than just query for uuid here
 
                product = session.query(model.Product).get(row.item_entry)
 
                session = self.app.get_session(row)
 
                product = self.locate_product_for_entry(session, row.item_entry)
 
                if product:
 
                    row.product = product
 
            if not row.product:
 
            if not row.product and not row.pending_product:
 
                row.status_code = row.STATUS_PRODUCT_NOT_FOUND
 
                return
 

	
 
        product = row.product
 
        if product:
 

	
 
            row.product_upc = product.upc
 
            row.product_item_id = product.item_id
 
            row.product_scancode = product.scancode
 
            row.product_brand = six.text_type(product.brand or "")
 
            row.product_description = product.description
 
            row.product_size = product.size
 
@@ -603,7 +728,16 @@ class CustomerOrderBatchHandler(BatchHandler):
 
            row.product_unit_cost = cost.unit_cost if cost else None
 

	
 
            regprice = product.regular_price
 
        row.unit_price = regprice.price if regprice else None
 
            row.unit_regular_price = regprice.price if regprice else None
 

	
 
            curprice = product.current_price
 
            row.unit_sale_price = curprice.price if curprice else None
 
            row.sale_ends = curprice.ends if curprice else None
 

	
 
            row.unit_price = row.unit_sale_price or row.unit_regular_price
 

	
 
        else:
 
            self.refresh_row_from_pending_product(row)
 

	
 
        # we need to know if total price is updated
 
        old_total = row.total_price
 
@@ -625,8 +759,48 @@ class CustomerOrderBatchHandler(BatchHandler):
 
                                 + (row.total_price or 0)
 
                                 - (old_total or 0))
 

	
 
        if not row.product and row.pending_product:
 
            row.status_code = row.STATUS_PENDING_PRODUCT
 
        else:
 
            row.status_code = row.STATUS_OK
 

	
 
    def refresh_row_from_pending_product(self, row):
 
        """
 
        Refresh basic row attributes from its pending product.
 
        """
 
        pending = row.pending_product
 
        if not pending:
 
            return
 

	
 
        row.product_upc = pending.upc
 
        row.product_item_id = pending.item_id
 
        row.product_scancode = pending.scancode
 
        row.product_brand = six.text_type(pending.brand or pending.brand_name or '')
 
        row.product_description = pending.description
 
        row.product_size = pending.size
 
        # TODO: is this even important?  pending does not have it
 
        # row.product_weighed = pending.weighed
 
        row.case_quantity = pending.case_size
 

	
 
        if pending.department:
 
            row.department = pending.department
 
            row.department_number = pending.department.number
 
            row.department_name = pending.department.name
 
        elif pending.department_name:
 
            row.department = None
 
            row.department_number = None
 
            row.department_name = pending.department_name
 
        else:
 
            row.department = None
 
            row.department_number = None
 
            row.department_name = None
 

	
 
        row.product_unit_cost = pending.unit_cost
 
        row.unit_regular_price = pending.regular_price_amount
 
        row.unit_price = row.unit_regular_price
 
        row.unit_sale_price = None
 
        row.sale_ends = None
 

	
 
    def remove_row(self, row):
 
        batch = row.batch
 

	
 
@@ -638,6 +812,27 @@ class CustomerOrderBatchHandler(BatchHandler):
 

	
 
        self.refresh_batch_status(batch)
 

	
 
    def delete_extra_data(self, batch, progress=None, **kwargs):
 

	
 
        # do the normal stuff (e.g. delete input files)
 
        super(CustomerOrderBatchHandler, self).delete_extra_data(
 
            batch, progress=progress, **kwargs)
 

	
 
        session = self.app.get_session(batch)
 

	
 
        # delete pending customer if present
 
        pending = batch.pending_customer
 
        if pending:
 
            batch.pending_customer = None
 
            session.delete(pending)
 

	
 
        # delete any pending products if present
 
        for row in batch.data_rows:
 
            pending = row.pending_product
 
            if pending:
 
                row.pending_product = None
 
                session.delete(pending)
 

	
 
    def execute(self, batch, user=None, progress=None, **kwargs):
 
        """
 
        Default behavior here will create and return a new rattail
 
@@ -713,6 +908,7 @@ class CustomerOrderBatchHandler(BatchHandler):
 

	
 
        row_fields = [
 
            'product',
 
            'pending_product',
 
            'product_upc',
 
            'product_brand',
 
            'product_description',
 
@@ -747,7 +943,7 @@ class CustomerOrderBatchHandler(BatchHandler):
 
        self.progress_loop(convert, batch.active_rows(), progress,
 
                           message="Converting batch rows to order items")
 

	
 
        session = orm.object_session(batch)
 
        session = self.app.get_session(batch)
 
        session.add(order)
 
        session.flush()
 

	
rattail/db/alembic/versions/08cc2ef12c18_add_more_custorder_stuff.py
Show inline comments
 
new file 100644
 
# -*- coding: utf-8; -*-
 
"""add more custorder stuff
 

	
 
Revision ID: 08cc2ef12c18
 
Revises: 3f3523703313
 
Create Date: 2021-12-21 17:00:11.841243
 

	
 
"""
 

	
 
from __future__ import unicode_literals
 

	
 
# revision identifiers, used by Alembic.
 
revision = '08cc2ef12c18'
 
down_revision = '3f3523703313'
 
branch_labels = None
 
depends_on = None
 

	
 
from alembic import op
 
import sqlalchemy as sa
 
import rattail.db.types
 

	
 

	
 

	
 
def upgrade():
 

	
 
    # pending_product
 
    op.add_column('pending_product', sa.Column('vendor_name', sa.String(length=50), nullable=True))
 
    op.add_column('pending_product', sa.Column('vendor_uuid', sa.String(length=32), nullable=True))
 
    op.add_column('pending_product', sa.Column('vendor_item_code', sa.String(length=20), nullable=True))
 
    op.add_column('pending_product', sa.Column('unit_cost', sa.Numeric(precision=10, scale=5), nullable=True))
 
    op.create_foreign_key('pending_product_fk_vendor', 'pending_product', 'vendor', ['vendor_uuid'], ['uuid'])
 

	
 
    # batch_custorder_row
 
    op.add_column('batch_custorder_row', sa.Column('pending_product_uuid', sa.String(length=32), nullable=True))
 
    op.create_foreign_key('batch_custorder_row_fk_pending_product', 'batch_custorder_row', 'pending_product', ['pending_product_uuid'], ['uuid'])
 
    op.add_column('batch_custorder_row', sa.Column('unit_regular_price', sa.Numeric(precision=8, scale=3), nullable=True))
 
    op.add_column('batch_custorder_row', sa.Column('unit_sale_price', sa.Numeric(precision=8, scale=3), nullable=True))
 
    op.add_column('batch_custorder_row', sa.Column('sale_ends', sa.DateTime(), nullable=True))
 
    op.add_column('batch_custorder_row', sa.Column('product_scancode', sa.String(length=14), nullable=True))
 
    op.add_column('batch_custorder_row', sa.Column('product_item_id', sa.String(length=50), nullable=True))
 

	
 
    # custorder_item
 
    op.add_column('custorder_item', sa.Column('pending_product_uuid', sa.String(length=32), nullable=True))
 
    op.create_foreign_key('custorder_item_fk_pending_product', 'custorder_item', 'pending_product', ['pending_product_uuid'], ['uuid'])
 
    op.add_column('custorder_item', sa.Column('unit_regular_price', sa.Numeric(precision=8, scale=3), nullable=True))
 
    op.add_column('custorder_item', sa.Column('unit_sale_price', sa.Numeric(precision=8, scale=3), nullable=True))
 
    op.add_column('custorder_item', sa.Column('sale_ends', sa.DateTime(), nullable=True))
 
    op.add_column('custorder_item', sa.Column('product_scancode', sa.String(length=14), nullable=True))
 
    op.add_column('custorder_item', sa.Column('product_item_id', sa.String(length=50), nullable=True))
 

	
 

	
 
def downgrade():
 

	
 
    # custorder_item
 
    # op.drop_column('custorder_item', 'product_item_id')
 
    # op.drop_column('custorder_item', 'product_scancode')
 
    op.drop_column('custorder_item', 'sale_ends')
 
    op.drop_column('custorder_item', 'unit_sale_price')
 
    op.drop_column('custorder_item', 'unit_regular_price')
 
    op.drop_constraint('custorder_item_fk_pending_product', 'custorder_item', type_='foreignkey')
 
    op.drop_column('custorder_item', 'pending_product_uuid')
 

	
 
    # batch_custorder_row
 
    # op.drop_column('batch_custorder_row', 'product_item_id')
 
    # op.drop_column('batch_custorder_row', 'product_scancode')
 
    op.drop_column('batch_custorder_row', 'sale_ends')
 
    op.drop_column('batch_custorder_row', 'unit_sale_price')
 
    op.drop_column('batch_custorder_row', 'unit_regular_price')
 
    op.drop_constraint('batch_custorder_row_fk_pending_product', 'batch_custorder_row', type_='foreignkey')
 
    op.drop_column('batch_custorder_row', 'pending_product_uuid')
 

	
 
    # pending_product
 
    op.drop_constraint('pending_product_fk_vendor', 'pending_product', type_='foreignkey')
 
    op.drop_column('pending_product', 'unit_cost')
 
    op.drop_column('pending_product', 'vendor_item_code')
 
    op.drop_column('pending_product', 'vendor_uuid')
 
    op.drop_column('pending_product', 'vendor_name')
rattail/db/model/batch/custorder.py
Show inline comments
 
@@ -94,11 +94,13 @@ class CustomerOrderBatchRow(BatchRowMixin, CustomerOrderItemBase, Base):
 
    STATUS_OK                           = 1
 
    STATUS_PRODUCT_NOT_FOUND            = 2
 
    # STATUS_PRICE_NEEDS_CONFIRMATION     = 3
 
    STATUS_PENDING_PRODUCT              = 4
 

	
 
    STATUS = {
 
        STATUS_OK                       : "ok",
 
        STATUS_PRODUCT_NOT_FOUND        : "product not found",
 
        # STATUS_PRICE_NEEDS_CONFIRMATION : "price needs to be confirmed",
 
        STATUS_PENDING_PRODUCT          : "has pending product",
 
    }
 

	
 
    item_entry = sa.Column(sa.String(length=32), nullable=True, doc="""
rattail/db/model/custorders.py
Show inline comments
 
@@ -35,7 +35,8 @@ from sqlalchemy.ext.orderinglist import ordering_list
 
from sqlalchemy.ext.declarative import declared_attr
 

	
 
from rattail.db.model import Base, uuid_column
 
from rattail.db.model import Store, Customer, PendingCustomer, Person, Product, User, Note
 
from rattail.db.model import (Store, Customer, PendingCustomer, Person,
 
                              Product, PendingProduct, User, Note)
 
from rattail.db.types import GPCType
 

	
 

	
 
@@ -184,6 +185,8 @@ class CustomerOrderItemBase(object):
 
        return (
 
            sa.ForeignKeyConstraint(['product_uuid'], ['product.uuid'],
 
                                    name='{}_fk_product'.format(table_name)),
 
            sa.ForeignKeyConstraint(['pending_product_uuid'], ['pending_product.uuid'],
 
                                    name='{}_fk_pending_product'.format(table_name)),
 
        )
 

	
 
    product_uuid = sa.Column(sa.String(length=32), nullable=True)
 
@@ -196,10 +199,29 @@ class CustomerOrderItemBase(object):
 
            Reference to the master product record for the line item.
 
            """)
 

	
 
    pending_product_uuid = sa.Column(sa.String(length=32), nullable=True)
 

	
 
    @declared_attr
 
    def pending_product(cls):
 
        return orm.relationship(
 
            PendingProduct,
 
            doc="""
 
            Reference to the *pending* product record for the order
 
            item, if applicable.
 
            """)
 

	
 
    product_upc = sa.Column(GPCType(), nullable=True, doc="""
 
    UPC for the product associated with the row.
 
    """)
 

	
 
    product_scancode = sa.Column(sa.String(length=14), nullable=True, doc="""
 
    Scancode for the product, if applicable.
 
    """)
 

	
 
    product_item_id = sa.Column(sa.String(length=50), nullable=True, doc="""
 
    Item ID for the product, if applicable.
 
    """)
 

	
 
    product_brand = sa.Column(sa.String(length=100), nullable=True, doc="""
 
    Brand name for the product being ordered.  This should be a cache of the
 
    relevant :attr:`Brand.name`.
 
@@ -273,6 +295,19 @@ class CustomerOrderItemBase(object):
 
    the relevant :attr:`ProductPrice.price`.
 
    """)
 

	
 
    unit_regular_price = sa.Column(sa.Numeric(precision=8, scale=3), nullable=True, doc="""
 
    Regular price for the item unit.  Note that if a sale price is in
 
    effect, then this may differ from :attr:`unit_price`.
 
    """)
 

	
 
    unit_sale_price = sa.Column(sa.Numeric(precision=8, scale=3), nullable=True, doc="""
 
    Sale price for the item unit, if applicable.
 
    """)
 

	
 
    sale_ends = sa.Column(sa.DateTime(), nullable=True, doc="""
 
    End date/time for the sale in effect, if any.
 
    """)
 

	
 
    discount_percent = sa.Column(sa.Numeric(precision=5, scale=3), nullable=False, default=0, doc="""
 
    Discount percentage which will be applied to the product's price as part of
 
    calculating the :attr:`total_price` for the item.
rattail/db/model/products.py
Show inline comments
 
@@ -990,6 +990,7 @@ class PendingProduct(Base):
 
        sa.ForeignKeyConstraint(['user_uuid'], ['user.uuid'], name='pending_product_fk_user'),
 
        sa.ForeignKeyConstraint(['department_uuid'], ['department.uuid'], name='pending_product_fk_department'),
 
        sa.ForeignKeyConstraint(['brand_uuid'], ['brand.uuid'], name='pending_product_fk_brand'),
 
        sa.ForeignKeyConstraint(['vendor_uuid'], ['vendor.uuid'], name='pending_product_fk_vendor'),
 
    )
 

	
 
    uuid = uuid_column()
 
@@ -1023,7 +1024,15 @@ class PendingProduct(Base):
 

	
 
    description = sa.Column(sa.String(length=255), nullable=True)
 
    size = sa.Column(sa.String(length=30), nullable=True)
 

	
 
    vendor_name = sa.Column(sa.String(length=50), nullable=True)
 
    vendor_uuid = sa.Column(sa.String(length=32), nullable=True)
 
    vendor = orm.relationship(Vendor)
 

	
 
    vendor_item_code = sa.Column(sa.String(length=20), nullable=True)
 
    unit_cost = sa.Column(sa.Numeric(precision=10, scale=5), nullable=True)
 
    case_size = sa.Column(sa.Numeric(precision=9, scale=4), nullable=True)
 

	
 
    regular_price_amount = sa.Column(sa.Numeric(precision=8, scale=3), nullable=True)
 
    special_order = sa.Column(sa.Boolean(), nullable=True)
 
    notes = sa.Column(sa.Text(), nullable=True)
rattail/products.py
Show inline comments
 
@@ -46,6 +46,52 @@ class ProductsHandler(GenericHandler):
 
    particular product can be deleted, etc.
 
    """
 

	
 
    def make_gpc(self, value, **kwargs):
 
        """
 
        Try to convert the given value to a :class:`~rattail.gpc.GPC`
 
        instance, and return the result.
 
        """
 
        return GPC(value)
 

	
 
    def make_full_description(self, product=None,
 
                              brand_name=None, description=None, size=None,
 
                              **kwargs):
 
        """
 
        Return a "full" description for the given product, or
 
        attributes thereof.
 

	
 
        :param product: Optional, but can be a reference to either a
 
           :class:`~rattail.db.model.products.Product` or
 
           :class:`~rattail.db.model.products.PendingProduct`
 
           instance.
 

	
 
        :param brand_name: Optional; brand name as string.  If not
 
           provided then will be obtained from ``product`` param.
 

	
 
        :param description: Optional; description as string.  If not
 
           provided then will be obtained from ``product`` param.
 

	
 
        :param size: Optional; size as string.  If not provided then
 
           will be obtained from ``product`` param.
 
        """
 
        from rattail.db.util import make_full_description
 

	
 
        model = self.model
 

	
 
        if brand_name is None and product:
 
            if product.brand:
 
                brand_name = product.brand.name
 
            elif isinstance(product, model.PendingProduct):
 
                brand_name = product.brand_name
 

	
 
        if description is None and product:
 
            description = product.description
 

	
 
        if size is None and product:
 
            size = product.size
 

	
 
        return make_full_description(brand_name, description, size)
 

	
 
    def find_products_by_key(self, session, value, **kwargs):
 
        """
 
        Locate any products where the "key" matches the given value.
 
@@ -252,6 +298,17 @@ class ProductsHandler(GenericHandler):
 
                self.app.render_currency(price.pack_price),
 
                price.pack_multiple)
 

	
 
    def make_pending_product(self, **kwargs):
 
        """
 
        Create and return a new
 
        :class:`~rattail.db.model.products.PendingProduct` instance,
 
        per the given kwargs.
 
        """
 
        model = self.model
 
        kwargs.setdefault('status_code', self.enum.PENDING_PRODUCT_STATUS_PENDING)
 
        pending = model.PendingProduct(**kwargs)
 
        return pending
 

	
 
    def get_uom_sil_codes(self, session, uppercase=False, **kwargs):
 
        """
 
        This should return a dict, keys of which are UOM abbreviation strings,
rattail/templates/mail/new_email_requested.html.mako
Show inline comments
 
@@ -13,7 +13,7 @@
 
        Contact:  ${contact_id} ${contact}
 
      </li>
 
      <li>
 
        New Email Address:&nbsp; ${email address}
 
        New Email Address:&nbsp; ${email_address}
 
      </li>
 
    </ul>
 

	
0 comments (0 inline, 0 general)