Changeset - 25483a21a057
[Not reviewed]
0 2 0
Lance Edgar (lance) - 3 years ago 2021-09-27 11:31:12
lance@edbob.org
Add "all" enum values for custorder item status, event

also attach "initiated" event(s) when creating new custorder
2 files changed with 72 insertions and 8 deletions:
0 comments (0 inline, 0 general)
rattail/batch/custorder.py
Show inline comments
 
# -*- coding: utf-8; -*-
 
################################################################################
 
#
 
#  Rattail -- Retail Software Framework
 
#  Copyright © 2010-2021 Lance Edgar
 
#
 
#  This file is part of Rattail.
 
#
 
#  Rattail is free software: you can redistribute it and/or modify it under the
 
#  terms of the GNU General Public License as published by the Free Software
 
#  Foundation, either version 3 of the License, or (at your option) any later
 
#  version.
 
#
 
#  Rattail is distributed in the hope that it will be useful, but WITHOUT ANY
 
#  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 
#  FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
 
#  details.
 
#
 
#  You should have received a copy of the GNU General Public License along with
 
#  Rattail.  If not, see <http://www.gnu.org/licenses/>.
 
#
 
################################################################################
 
"""
 
Handler for "customer order" batches
 
"""
 

	
 
from __future__ import unicode_literals, absolute_import, division
 

	
 
import re
 

	
 
import six
 
import sqlalchemy as sa
 
from sqlalchemy import orm
 

	
 
from rattail.db import model
 
from rattail.batch import BatchHandler
 

	
 

	
 
class CustomerOrderBatchHandler(BatchHandler):
 
    """
 
    Handler for all "customer order" batches, regardless of "mode".  The
 
    handler must inspect the
 
    :attr:`~rattail.db.model.batch.custorder.CustomerOrderBatch.mode` attribute
 
    of each batch it deals with, in order to determine which logic to apply.
 

	
 
    .. attribute:: has_custom_product_autocomplete
 

	
 
       If true, this flag indicates that the handler provides custom
 
       autocomplete logic for use when selecting a product while
 
       creating a new order.
 
    """
 
    batch_model_class = model.CustomerOrderBatch
 
    has_custom_product_autocomplete = False
 
    nondigits_pattern = re.compile(r'\D')
 

	
 
    def init_batch(self, batch, progress=None, **kwargs):
 
        """
 
        Assign the "local" store to the batch, if applicable.
 
        """
 
        session = self.app.get_session(batch)
 
        batch.store = self.config.get_store(session)
 

	
 
    def new_order_requires_customer(self):
 
        """
 
        Returns a boolean indicating whether a *new* "customer order"
 
        in fact requires a proper customer account, or not.  Note that
 
        in all cases a new order requires a *person* to associate
 
        with, but technically the customer is optional, unless this
 
        returns true.
 
        """
 
        return self.config.getbool('rattail.custorders',
 
                                   'new_order_requires_customer',
 
                                   default=False)
 

	
 
    def assign_contact(self, batch, customer=None, person=None, **kwargs):
 
        """
 
        Assign the customer and/or person "contact" for the order.
 
        """
 
        clientele = self.app.get_clientele_handler()
 
        customer_required = self.new_order_requires_customer()
 

	
 
        # nb. person is always required
 
        if customer and not person:
 
            person = clientele.get_person(customer)
 
        if not person:
 
            raise ValueError("Must specify a person")
 

	
 
        # customer may or may not be optional
 
        if person and not customer:
 
            customer = clientele.get_customer(person)
 
        if customer_required and not customer:
 
            raise ValueError("Must specify a customer account")
 

	
 
        # assign contact
 
        batch.customer = customer
 
        batch.person = person
 

	
 
        # update phone/email per new contact
 
        batch.phone_number = None
 
        batch.email_address = None
 
        if customer_required:
 
            batch.phone_number = clientele.get_first_phone_number(customer)
 
            batch.email_address = clientele.get_first_email_address(customer)
 
        else:
 
            batch.phone_number = person.first_phone_number()
 
            batch.email_address = person.first_email_address()
 

	
 
        session = self.app.get_session(batch)
 
        session.flush()
 
        session.refresh(batch)
 

	
 
    def unassign_contact(self, batch, **kwargs):
 
        """
 
        Unassign the customer and/or person "contact" for the order.
 
        """
 
        batch.customer = None
 
        batch.person = None
 
        batch.phone_number = None
 
        batch.email_address = None
 

	
 
        session = self.app.get_session(batch)
 
        session.flush()
 
        session.refresh(batch)
 

	
 
    def get_case_size_for_product(self, product):
 
        if product.case_size:
 
            return product.case_size
 

	
 
        cost = product.cost
 
        if cost:
 
            return cost.case_size
 

	
 
    def get_phone_search_term(self, term):
 
        """
 
        Try to figure out if the given search term represents a whole
 
        or partial phone number, and if so return just the digits.
 
        """
 
        digits = self.nondigits_pattern.sub('', term)
 
        if digits and len(digits) >= 4:
 
            return digits
 

	
 
    def customer_autocomplete(self, session, term, **kwargs):
 
        """
 
        Override the Customer autocomplete, to search by phone number
 
        as well as name.
 
        """
 
        model = self.model
 

	
 
        # define the base query
 
        query = session.query(model.Customer)\
 
                       .options(orm.joinedload(model.Customer.phones))
 

	
 
        # does search term look like a phone number?
 
        phone_term = self.get_phone_search_term(term)
 
        if phone_term:
 

	
 
            # yep, so just search for the phone number
 
            query = query.join(model.CustomerPhoneNumber,
 
                               model.CustomerPhoneNumber.parent_uuid == model.Customer.uuid)
 
            query = query.filter(sa.func.regexp_replace(model.CustomerPhoneNumber.number,
 
                                                        r'\D', '', 'g')\
 
                                 .like('%{}%'.format(phone_term)))
 

	
 
        else: # term does not look like a phone number
 

	
 
            # so just search by name
 
            criteria = [model.Customer.name.ilike('%{}%'.format(word))
 
                        for word in term.split()]
 
            query = query.filter(sa.and_(*criteria))
 

	
 
        # oh, and sort by something useful
 
        query = query.order_by(model.Customer.name)
 

	
 
        # generate result list from query
 
        results = []
 
        for customer in query:
 
            phone = customer.first_phone()
 
            if phone:
 
                label = "{} {}".format(customer.name, phone.number)
 
            else:
 
                label = customer.name
 
            results.append({'value': customer.uuid,
 
                            'label': label,
 
                            'display': customer.name})
 

	
 
        return results
 

	
 
    def person_autocomplete(self, session, term, **kwargs):
 
        """
 
        Override the Person autocomplete, to search by phone number as
 
        well as name.
 
        """
 
        model = self.model
 

	
 
        # define the base query
 
        query = session.query(model.Person)\
 
                       .options(orm.joinedload(model.Person.phones))
 

	
 
        # does search term look like a phone number?
 
        phone_term = self.get_phone_search_term(term)
 
        if phone_term:
 

	
 
            # yep, so just search for the phone number
 
            query = query.join(model.PersonPhoneNumber,
 
                               model.PersonPhoneNumber.parent_uuid == model.Person.uuid)
 
            query = query.filter(sa.func.regexp_replace(model.PersonPhoneNumber.number,
 
                                                        r'\D', '', 'g')\
 
                                 .like('%{}%'.format(phone_term)))
 

	
 
        else: # term does not look like a phone number
 

	
 
            # so just search by name
 
            criteria = [model.Person.display_name.ilike('%{}%'.format(word))
 
                        for word in term.split()]
 
            query = query.filter(sa.and_(*criteria))
 

	
 
        # oh, and sort by something useful
 
        query = query.order_by(model.Person.display_name)
 

	
 
        # generate result list from query
 
        results = []
 
        for person in query:
 
            phone = person.first_phone()
 
            if phone:
 
                label = "{} {}".format(person.display_name, phone.number)
 
            else:
 
                label = person.display_name
 
            results.append({'value': person.uuid,
 
                            'label': label,
 
                            'display': person.display_name})
 

	
 
        return results
 

	
 
    def get_customer_info(self, batch, **kwargs):
 
        """
 
        Return a data dict containing misc. info pertaining to the
 
        customer/person for the order batch.
 
        """
 
        info = {
 
            'customer_uuid': None,
 
            'person_uuid': None,
 
            'phone_number': None,
 
            'email_address': None,
 
        }
 

	
 
        if batch.customer:
 
            info['customer_uuid'] = batch.customer.uuid
 
            phone = batch.customer.first_phone()
 
            if phone:
 
                info['phone_number'] = phone.number
 
            email = batch.customer.first_email()
 
            if email:
 
                info['email_address'] = email.address
 

	
 
        if batch.person:
 
            info['person_uuid'] = batch.person.uuid
 
            if not info['phone_number']:
 
                phone = batch.person.first_phone()
 
                if phone:
 
                    info['phone_number'] = phone.number
 
                email = batch.person.first_email()
 
                if email:
 
                    info['email_address'] = email.address
 

	
 
        return info
 

	
 
    def custom_product_autocomplete(self, session, term, **kwargs):
 
        """
 
        For the given term, this should return a (possibly empty) list
 
        of products which "match" the term.  Each element in the list
 
        should be a dict with "label" and "value" keys.
 
        """
 
        raise NotImplementedError("Please define the "
 
                                  "{}.custom_product_autocomplete() "
 
                                  "method.".format(__class__.__name__))
 

	
 
    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)
 
                if product:
 
                    row.product = product
 
            if not row.product:
 
                row.status_code = row.STATUS_PRODUCT_NOT_FOUND
 
                return
 

	
 
        product = row.product
 
        row.product_upc = product.upc
 
        row.product_brand = six.text_type(product.brand or "")
 
        row.product_description = product.description
 
        row.product_size = product.size
 
        row.product_weighed = product.weighed
 
        row.case_quantity = self.get_case_size_for_product(product)
 

	
 
        department = product.department
 
        row.department_number = department.number if department else None
 
        row.department_name = department.name if department else None
 

	
 
        cost = product.cost
 
        row.product_unit_cost = cost.unit_cost if cost else None
 

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

	
 
        # we need to know if total price is updated
 
        old_total = row.total_price
 

	
 
        # maybe update total price
 
        if row.unit_price is None:
 
            row.total_price = None
 
        elif not row.unit_price:
 
            row.total_price = 0
 
        else:
 
            row.total_price = row.unit_price * row.order_quantity
 
            if row.order_uom == self.enum.UNIT_OF_MEASURE_CASE:
 
                row.total_price *= (row.case_quantity or 1)
 

	
 
        # update total price for batch too, if it changed
 
        if row.total_price != old_total:
 
            batch = row.batch
 
            batch.total_price = ((batch.total_price or 0)
 
                                 + (row.total_price or 0)
 
                                 - (old_total or 0))
 

	
 
        row.status_code = row.STATUS_OK
 

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

	
 
        if not row.removed:
 
            row.removed = True
 

	
 
            if row.total_price:
 
                batch.total_price = (batch.total_price or 0) - row.total_price
 

	
 
        self.refresh_batch_status(batch)
 

	
 
    def execute(self, batch, user=None, progress=None, **kwargs):
 
        """
 
        Default behavior here will simply create a new (proper) Customer Order
 
        based on the batch contents.  Override as needed.
 
        """
 
        batch_fields = [
 
            'store',
 
            'id',
 
            'customer',
 
            'person',
 
            'phone_number',
 
            'email_address',
 
            'total_price',
 
        ]
 

	
 
        order = model.CustomerOrder()
 
        order.created_by = user
 
        order.status_code = self.enum.CUSTORDER_STATUS_ORDERED
 
        for field in batch_fields:
 
            setattr(order, field, getattr(batch, field))
 

	
 
        row_fields = [
 
            'product',
 
            'product_upc',
 
            'product_brand',
 
            'product_description',
 
            'product_size',
 
            'product_weighed',
 
            'department_number',
 
            'department_name',
 
            'case_quantity',
 
            'order_quantity',
 
            'order_uom',
 
            'product_unit_cost',
 
            'unit_price',
 
            'discount_percent',
 
            'total_price',
 
            'paid_amount',
 
            'payment_transaction_number',
 
        ]
 

	
 
        def convert(row, i):
 
            item = model.CustomerOrderItem()
 
            item.sequence = i
 
            item.status_code = self.enum.CUSTORDER_ITEM_STATUS_ORDERED
 
            item.status_code = self.enum.CUSTORDER_ITEM_STATUS_INITIATED
 
            for field in row_fields:
 
                setattr(item, field, getattr(row, field))
 
            order.items.append(item)
 

	
 
            # attach event
 
            item.events.append(model.CustomerOrderItemEvent(
 
                type_code=self.enum.CUSTORDER_ITEM_EVENT_INITIATED,
 
                user=user))
 

	
 
        self.progress_loop(convert, batch.active_rows(), progress,
 
                           message="Converting batch rows to order items")
 

	
 
        session = orm.object_session(batch)
 
        session.add(order)
 
        session.flush()
 

	
 
        return order
rattail/enum.py
Show inline comments
 
# -*- coding: utf-8; -*-
 
################################################################################
 
#
 
#  Rattail -- Retail Software Framework
 
#  Copyright © 2010-2021 Lance Edgar
 
#
 
#  This file is part of Rattail.
 
#
 
#  Rattail is free software: you can redistribute it and/or modify it under the
 
#  terms of the GNU General Public License as published by the Free Software
 
#  Foundation, either version 3 of the License, or (at your option) any later
 
#  version.
 
#
 
#  Rattail is distributed in the hope that it will be useful, but WITHOUT ANY
 
#  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 
#  FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
 
#  details.
 
#
 
#  You should have received a copy of the GNU General Public License along with
 
#  Rattail.  If not, see <http://www.gnu.org/licenses/>.
 
#
 
################################################################################
 
"""
 
**Enumerations**
 

	
 
The following enumerations are provided:
 

	
 
.. attribute:: BATCH_ACTION
 

	
 
   Action types for use with batches.  These are taken from the SIL
 
   specification.
 

	
 
.. attribute:: EMAIL_PREFERENCE
 

	
 
   Various options indicating a person's preferences on whether to receive
 
   email, and if so, in what format.
 

	
 
.. attribute:: EMPLOYEE_STATUS
 

	
 
   Status types for employees (e.g. current, former).
 

	
 
.. attribute:: PHONE_TYPE
 

	
 
   Various "types" of phone contact information (e.g. home, work).
 

	
 
.. attribute:: PRICE_TYPE
 

	
 
   Various types of prices which may exist for a product.  These are taken from
 
   the SIL specification.
 

	
 
.. attribute:: UNIT_OF_MEASURE
 

	
 
   Units of measure for use with products (e.g. each, pound).  These are taken
 
   from the SIL specification.
 
"""
 

	
 
from __future__ import unicode_literals, absolute_import
 

	
 
from rattail.util import OrderedDict
 

	
 

	
 
BATCH_ACTION_ADD                = 'ADD'
 
BATCH_ACTION_ADD_REPLACE        = 'ADDRPL'
 
BATCH_ACTION_CHANGE             = 'CHANGE'
 
BATCH_ACTION_LOAD               = 'LOAD'
 
BATCH_ACTION_REMOVE             = 'REMOVE'
 

	
 
BATCH_ACTION = {
 
    BATCH_ACTION_ADD            : "Add",
 
    BATCH_ACTION_ADD_REPLACE    : "Add/Replace",
 
    BATCH_ACTION_CHANGE         : "Change",
 
    BATCH_ACTION_LOAD           : "Load",
 
    BATCH_ACTION_REMOVE         : "Remove",
 
    }
 

	
 

	
 
CUSTORDER_BATCH_MODE_CREATING            = 10
 
CUSTORDER_BATCH_MODE_GATHERING           = 20
 

	
 
CUSTORDER_BATCH_MODE = {
 
    CUSTORDER_BATCH_MODE_CREATING        : "creating",
 
    CUSTORDER_BATCH_MODE_GATHERING       : "gathering",
 
}
 

	
 

	
 
CUSTORDER_STATUS_ORDERED                = 10
 
# CUSTORDER_STATUS_PAID                   = 20
 

	
 
CUSTORDER_STATUS = {
 
    CUSTORDER_STATUS_ORDERED            : "ordered",
 
    # CUSTORDER_STATUS_PAID               : "paid",
 
}
 

	
 

	
 
CUSTORDER_ITEM_STATUS_ORDERED           = 10
 
# CUSTORDER_ITEM_STATUS_PAID              = 20
 

	
 
CUSTORDER_ITEM_STATUS = {
 
    CUSTORDER_ITEM_STATUS_ORDERED       : "ordered",
 
    # CUSTORDER_ITEM_STATUS_PAID          : "paid",
 
}
 
CUSTORDER_ITEM_STATUS_INITIATED         = 10
 
# TODO: deprecate / remove this one
 
CUSTORDER_ITEM_STATUS_ORDERED           = CUSTORDER_ITEM_STATUS_INITIATED
 
CUSTORDER_ITEM_STATUS_PAID              = 20
 
CUSTORDER_ITEM_STATUS_PLACED            = 30
 
CUSTORDER_ITEM_STATUS_RECEIVED          = 40
 
CUSTORDER_ITEM_STATUS_CONTACTED         = 50
 
CUSTORDER_ITEM_STATUS_CONTACT_FAILED    = 60
 
CUSTORDER_ITEM_STATUS_DELIVERED         = 70
 
CUSTORDER_ITEM_STATUS_CANCELED          = 900
 
CUSTORDER_ITEM_STATUS_REFUND_PENDING    = 910
 
CUSTORDER_ITEM_STATUS_REFUNDED          = 920
 
CUSTORDER_ITEM_STATUS_RESTOCKED         = 930
 
CUSTORDER_ITEM_STATUS_EXPIRED           = 940
 
CUSTORDER_ITEM_STATUS_INACTIVE          = 950
 

	
 
CUSTORDER_ITEM_STATUS = OrderedDict([
 
    (CUSTORDER_ITEM_STATUS_INITIATED,           "customer order initiated"),
 
    (CUSTORDER_ITEM_STATUS_PAID,                "payment received"),
 
    (CUSTORDER_ITEM_STATUS_PLACED,              "order placed with vendor"),
 
    (CUSTORDER_ITEM_STATUS_RECEIVED,            "received from vendor"),
 
    (CUSTORDER_ITEM_STATUS_CONTACTED,           "customer contacted"),
 
    (CUSTORDER_ITEM_STATUS_CONTACT_FAILED,      "unable to contact customer"),
 
    (CUSTORDER_ITEM_STATUS_DELIVERED,           "delivered to customer"),
 
    (CUSTORDER_ITEM_STATUS_CANCELED,            "canceled"),
 
    (CUSTORDER_ITEM_STATUS_REFUND_PENDING,      "refund pending"),
 
    (CUSTORDER_ITEM_STATUS_REFUNDED,            "refunded"),
 
    (CUSTORDER_ITEM_STATUS_RESTOCKED,           "restocked"),
 
    (CUSTORDER_ITEM_STATUS_EXPIRED,             "expired"),
 
    (CUSTORDER_ITEM_STATUS_INACTIVE,            "inactive"),
 
])
 

	
 

	
 
CUSTORDER_ITEM_EVENT_INITIATED          = 10
 
CUSTORDER_ITEM_EVENT_PAID               = 20
 
CUSTORDER_ITEM_EVENT_PLACED             = 30
 
CUSTORDER_ITEM_EVENT_RECEIVED           = 40
 
CUSTORDER_ITEM_EVENT_CONTACTED          = 50
 
CUSTORDER_ITEM_EVENT_CONTACT_FAILED     = 60
 
CUSTORDER_ITEM_EVENT_DELIVERED          = 70
 
CUSTORDER_ITEM_EVENT_STATUS_CHANGE      = 500 # nb. this is not in STATUS enum
 
CUSTORDER_ITEM_EVENT_CANCELED           = 900
 
CUSTORDER_ITEM_EVENT_REFUND_PENDING     = 910
 
CUSTORDER_ITEM_EVENT_REFUNDED           = 920
 
CUSTORDER_ITEM_EVENT_RESTOCKED          = 930
 
CUSTORDER_ITEM_EVENT_EXPIRED            = 940
 
CUSTORDER_ITEM_EVENT_INACTIVE           = 950
 

	
 
CUSTORDER_ITEM_EVENT = OrderedDict([
 
    (CUSTORDER_ITEM_EVENT_INITIATED,            "customer order initiated"),
 
    (CUSTORDER_ITEM_EVENT_PAID,                 "payment received"),
 
    (CUSTORDER_ITEM_EVENT_PLACED,               "order placed with vendor"),
 
    (CUSTORDER_ITEM_EVENT_RECEIVED,             "received from vendor"),
 
    (CUSTORDER_ITEM_EVENT_CONTACTED,            "customer contacted"),
 
    (CUSTORDER_ITEM_EVENT_CONTACT_FAILED,       "unable to contact customer"),
 
    (CUSTORDER_ITEM_EVENT_DELIVERED,            "delivered to customer"),
 
    (CUSTORDER_ITEM_EVENT_STATUS_CHANGE,        "manual status change"),
 
    (CUSTORDER_ITEM_EVENT_CANCELED,             "canceled"),
 
    (CUSTORDER_ITEM_EVENT_REFUND_PENDING,       "refund pending"),
 
    (CUSTORDER_ITEM_EVENT_REFUNDED,             "refunded"),
 
    (CUSTORDER_ITEM_EVENT_RESTOCKED,            "restocked"),
 
    (CUSTORDER_ITEM_EVENT_EXPIRED,              "expired"),
 
    (CUSTORDER_ITEM_EVENT_INACTIVE,             "inactive"),
 
])
 

	
 

	
 
EMAIL_ATTEMPT_CREATED           = 0
 
EMAIL_ATTEMPT_SUCCESS           = 1
 
EMAIL_ATTEMPT_FAILURE           = 2
 
# EMAIL_ATTEMPT_BOUNCED           = 3
 

	
 
EMAIL_ATTEMPT = {
 
    EMAIL_ATTEMPT_CREATED       : "created",
 
    EMAIL_ATTEMPT_SUCCESS       : "success",
 
    EMAIL_ATTEMPT_FAILURE       : "failure",
 
    # EMAIL_ATTEMPT_BOUNCED       : "bounced",
 
}
 

	
 

	
 
EMAIL_PREFERENCE_NONE           = 0
 
EMAIL_PREFERENCE_TEXT           = 1
 
EMAIL_PREFERENCE_HTML           = 2
 
EMAIL_PREFERENCE_MOBILE         = 3
 

	
 
EMAIL_PREFERENCE = {
 
    EMAIL_PREFERENCE_NONE       : "No Emails",
 
    EMAIL_PREFERENCE_TEXT       : "Text",
 
    EMAIL_PREFERENCE_HTML       : "HTML",
 
    EMAIL_PREFERENCE_MOBILE     : "Mobile",
 
    }
 

	
 

	
 
HANDHELD_DEVICE_TYPE_MOTOROLA           = 'motorola'
 
HANDHELD_DEVICE_TYPE_PALMOS             = 'palmos'
 

	
 
HANDHELD_DEVICE_TYPE = {
 
    HANDHELD_DEVICE_TYPE_MOTOROLA       : "Motorola",
 
    HANDHELD_DEVICE_TYPE_PALMOS         : "PalmOS",
 
}
 

	
 

	
 
IMPORTER_BATCH_ROW_STATUS_NOCHANGE      = 0
 
IMPORTER_BATCH_ROW_STATUS_CREATE        = 1
 
IMPORTER_BATCH_ROW_STATUS_UPDATE        = 2
 
IMPORTER_BATCH_ROW_STATUS_DELETE        = 3
 

	
 
IMPORTER_BATCH_ROW_STATUS = {
 
    IMPORTER_BATCH_ROW_STATUS_NOCHANGE  : "no change",
 
    IMPORTER_BATCH_ROW_STATUS_CREATE    : "create",
 
    IMPORTER_BATCH_ROW_STATUS_UPDATE    : "update",
 
    IMPORTER_BATCH_ROW_STATUS_DELETE    : "delete",
 
}
 

	
 

	
 
INVENTORY_MODE_REPLACE          = 1
 
INVENTORY_MODE_REPLACE_ADJUST   = 2
 
INVENTORY_MODE_ADJUST           = 3
 
INVENTORY_MODE_ZERO_ALL         = 4
 
INVENTORY_MODE_VARIANCE         = 5
 

	
 
INVENTORY_MODE = {
 
    INVENTORY_MODE_REPLACE              : "Replace only",
 
    INVENTORY_MODE_REPLACE_ADJUST       : "Replace then adjust",
 
    INVENTORY_MODE_ADJUST               : "Adjust only",
 
    INVENTORY_MODE_ZERO_ALL             : "Zero all",
 
    INVENTORY_MODE_VARIANCE             : "Variance Correction",
 
}
 

	
 

	
 
MESSAGE_STATUS_INBOX            = 1
 
MESSAGE_STATUS_ARCHIVE          = 2
 

	
 
MESSAGE_STATUS = {
 
    MESSAGE_STATUS_INBOX        : "Inbox",
 
    MESSAGE_STATUS_ARCHIVE      : "Archive",
 
}
 

	
 

	
 
PHONE_TYPE_HOME                 = 'home'
 
PHONE_TYPE_MOBILE               = 'mobile'
 
PHONE_TYPE_OTHER                = 'other'
 

	
 
PHONE_TYPE = {
 
    PHONE_TYPE_HOME             : "Home",
 
    PHONE_TYPE_MOBILE           : "Mobile",
 
    PHONE_TYPE_OTHER            : "Other",
 
    }
 

	
 

	
 
PRICE_TYPE_REGULAR              = 0
 
PRICE_TYPE_TPR                  = 1
 
PRICE_TYPE_SALE                 = 2
 
PRICE_TYPE_MANAGER_SPECIAL      = 3
 
PRICE_TYPE_ALTERNATE            = 4
 
PRICE_TYPE_FREQUENT_SHOPPER     = 5
 
PRICE_TYPE_MFR_SUGGESTED        = 901
 

	
 
PRICE_TYPE = {
 
    PRICE_TYPE_REGULAR          : "Regular Price",
 
    PRICE_TYPE_TPR              : "TPR",
 
    PRICE_TYPE_SALE             : "Sale",
 
    PRICE_TYPE_MANAGER_SPECIAL  : "Manager Special",
 
    PRICE_TYPE_ALTERNATE        : "Alternate Price",
 
    PRICE_TYPE_FREQUENT_SHOPPER : "Frequent Shopper",
 
    PRICE_TYPE_MFR_SUGGESTED    : "Manufacturer's Suggested",
 
    }
 

	
 

	
 
PURCHASE_BATCH_MODE_ORDERING            = 10
 
PURCHASE_BATCH_MODE_RECEIVING           = 20
 
PURCHASE_BATCH_MODE_COSTING             = 30
 

	
 
PURCHASE_BATCH_MODE = {
 
    PURCHASE_BATCH_MODE_ORDERING        : "ordering",
 
    PURCHASE_BATCH_MODE_RECEIVING       : "receiving",
 
    PURCHASE_BATCH_MODE_COSTING         : "invoicing",
 
}
 

	
 

	
 
PURCHASE_CREDIT_STATUS_NEW              = 10
 
PURCHASE_CREDIT_STATUS_SUBMITTED        = 20
 
PURCHASE_CREDIT_STATUS_SATISFIED        = 30
 
PURCHASE_CREDIT_STATUS_NONCREDITABLE    = 40
 

	
 
PURCHASE_CREDIT_STATUS = {
 
    PURCHASE_CREDIT_STATUS_NEW          : "new",
 
    PURCHASE_CREDIT_STATUS_SUBMITTED    : "submitted",
 
    PURCHASE_CREDIT_STATUS_SATISFIED    : "satisfied",
 
    PURCHASE_CREDIT_STATUS_NONCREDITABLE: "non-creditable",
 
}
 

	
 

	
 
PURCHASE_STATUS_NEW             = 1 # TODO: is this needed?
 
PURCHASE_STATUS_ORDERED         = 10
 
PURCHASE_STATUS_RECEIVED        = 20
 
PURCHASE_STATUS_COSTED          = 30
 
PURCHASE_STATUS_PAID            = 40
 

	
 
PURCHASE_STATUS = {
 
    PURCHASE_STATUS_NEW         : "new/pending",
 
    PURCHASE_STATUS_ORDERED     : "ordered",
 
    PURCHASE_STATUS_RECEIVED    : "received",
 
    PURCHASE_STATUS_COSTED      : "invoiced",
 
    PURCHASE_STATUS_PAID        : "paid",
 
}
 

	
 

	
 
TEMPMON_APPLIANCE_TYPE_COOLER           = 1
 
TEMPMON_APPLIANCE_TYPE_FREEZER          = 2
 

	
 
TEMPMON_APPLIANCE_TYPE = {
 
    TEMPMON_APPLIANCE_TYPE_COOLER       : "cooler",
 
    TEMPMON_APPLIANCE_TYPE_FREEZER      : "freezer",
 
}
 

	
 

	
 
TEMPMON_DISK_TYPE_SDCARD                = 1
 
TEMPMON_DISK_TYPE_USB                   = 2
 

	
 
TEMPMON_DISK_TYPE = {
 
    TEMPMON_DISK_TYPE_SDCARD            : "SD card",
 
    TEMPMON_DISK_TYPE_USB               : "USB",
 
}
 

	
 

	
 
TEMPMON_PROBE_STATUS_GOOD_TEMP          = 1
 
TEMPMON_PROBE_STATUS_LOW_TEMP           = 2
 
TEMPMON_PROBE_STATUS_HIGH_TEMP          = 3
 
TEMPMON_PROBE_STATUS_CRITICAL_HIGH_TEMP = 4
 
# TODO: deprecate / remove this one
 
TEMPMON_PROBE_STATUS_CRITICAL_TEMP      = TEMPMON_PROBE_STATUS_CRITICAL_HIGH_TEMP
 
TEMPMON_PROBE_STATUS_ERROR              = 5
 
TEMPMON_PROBE_STATUS_CRITICAL_LOW_TEMP  = 6
 

	
 
TEMPMON_PROBE_STATUS = {
 
    TEMPMON_PROBE_STATUS_CRITICAL_HIGH_TEMP     : "critical high temp",
 
    # TODO: deprecate / remove this one
 
    TEMPMON_PROBE_STATUS_CRITICAL_TEMP          : "critical high temp",
 
    TEMPMON_PROBE_STATUS_HIGH_TEMP              : "high temp",
 
    TEMPMON_PROBE_STATUS_GOOD_TEMP              : "good temp",
 
    TEMPMON_PROBE_STATUS_LOW_TEMP               : "low temp",
 
    TEMPMON_PROBE_STATUS_CRITICAL_LOW_TEMP      : "critical low temp",
 
    TEMPMON_PROBE_STATUS_ERROR                  : "error",
 
}
 

	
 

	
 
# These values are taken from the SIL standard.
 
UNIT_OF_MEASURE_NONE                    = '00'
 
UNIT_OF_MEASURE_EACH                    = '01'
 
UNIT_OF_MEASURE_10_COUNT                = '02'
 
UNIT_OF_MEASURE_DOZEN                   = '03'
 
UNIT_OF_MEASURE_50_COUNT                = '04'
 
UNIT_OF_MEASURE_100_COUNT               = '05'
 
UNIT_OF_MEASURE_100_COUNT_1_PLY         = '06'
 
UNIT_OF_MEASURE_100_COUNT_2_PLY         = '07'
 
UNIT_OF_MEASURE_100_COUNT_3_PLY         = '08'
 
UNIT_OF_MEASURE_100_COUNT_4_PLY         = '09'
 
UNIT_OF_MEASURE_INCH                    = '21'
 
UNIT_OF_MEASURE_FOOT                    = '22'
 
UNIT_OF_MEASURE_50_FEET                 = '23'
 
UNIT_OF_MEASURE_100_FEET                = '24'
 
UNIT_OF_MEASURE_100_YARDS               = '25'
 
UNIT_OF_MEASURE_SQUARE_INCH             = '31'
 
UNIT_OF_MEASURE_SQUARE_FEET             = '32'
 
UNIT_OF_MEASURE_50_SQUARE_YARD          = '34'
 
UNIT_OF_MEASURE_LIQUID_OUNCE            = '41'
 
UNIT_OF_MEASURE_PINT                    = '42'
 
UNIT_OF_MEASURE_QUART                   = '43'
 
UNIT_OF_MEASURE_HALF_GALLON             = '44'
 
UNIT_OF_MEASURE_GALLON                  = '45'
 
UNIT_OF_MEASURE_DRY_OUNCE               = '48'
 
UNIT_OF_MEASURE_POUND                   = '49'
 
UNIT_OF_MEASURE_MILLIMETER              = '61'
 
UNIT_OF_MEASURE_CENTIMETER              = '62'
 
UNIT_OF_MEASURE_DECIMETER               = '63'
 
UNIT_OF_MEASURE_METER                   = '64'
 
UNIT_OF_MEASURE_DEKAMETER               = '65'
 
UNIT_OF_MEASURE_HECTOMETER              = '66'
 
UNIT_OF_MEASURE_SQ_CENTIMETER           = '71'
 
UNIT_OF_MEASURE_SQUARE_METER            = '72'
 
UNIT_OF_MEASURE_CENTILITER              = '81'
 
UNIT_OF_MEASURE_GRAMS                   = '86'
 
UNIT_OF_MEASURE_KILOGRAMS               = '87'
 
UNIT_OF_MEASURE_BOX                     = '100'
 
UNIT_OF_MEASURE_CARTON                  = '101'
 
UNIT_OF_MEASURE_CASE                    = '102'
 
UNIT_OF_MEASURE_PACKAGE                 = '103'
 
UNIT_OF_MEASURE_PACK                    = '104'
 
UNIT_OF_MEASURE_ROLL                    = '105'
 
UNIT_OF_MEASURE_GROSS                   = '106'
 
UNIT_OF_MEASURE_LOAD                    = '107'
 
UNIT_OF_MEASURE_COUNT                   = '108'
 
UNIT_OF_MEASURE_YARD                    = '109'
 
UNIT_OF_MEASURE_BUSHEL                  = '110'
 
UNIT_OF_MEASURE_CENTIGRAM               = '111'
 
UNIT_OF_MEASURE_DECILITER               = '112'
 
UNIT_OF_MEASURE_DECIGRAM                = '113'
 
UNIT_OF_MEASURE_MILLILITER              = '114'
 
UNIT_OF_MEASURE_PECK                    = '115'
 
UNIT_OF_MEASURE_PAIR                    = '116'
 
UNIT_OF_MEASURE_DRY_QUART               = '117'
 
UNIT_OF_MEASURE_SHEET                   = '118'
 
UNIT_OF_MEASURE_STICKS                  = '120'
 
UNIT_OF_MEASURE_THOUSAND                = '121'
 
UNIT_OF_MEASURE_PALLET_UNIT_LOAD        = '122'
 
UNIT_OF_MEASURE_PERCENT                 = '123'
 
UNIT_OF_MEASURE_TRUCKLOAD               = '124'
 
UNIT_OF_MEASURE_DOLLARS                 = '125'
 

	
 
# These values are *not* from the SIL standard.
 
UNIT_OF_MEASURE_BUNCH                   = '300'
 
UNIT_OF_MEASURE_LITER                   = '301'
 
UNIT_OF_MEASURE_BAG                     = '302'
 
UNIT_OF_MEASURE_CAPSULES                = '303'
 
UNIT_OF_MEASURE_CUBIC_CENTIMETERS       = '304'
 
UNIT_OF_MEASURE_SOFTGELS                = '305'
 
UNIT_OF_MEASURE_TABLETS                 = '306'
 
UNIT_OF_MEASURE_VEG_CAPSULES            = '307'
 
UNIT_OF_MEASURE_WAFERS                  = '308'
 
UNIT_OF_MEASURE_PIECE                   = '309'
 
UNIT_OF_MEASURE_KIT                     = '310'
 
UNIT_OF_MEASURE_CHEWABLE                = '311'
 
UNIT_OF_MEASURE_LOZENGE                 = '312'
 
UNIT_OF_MEASURE_CUBIC_INCH              = '313'
 
UNIT_OF_MEASURE_VEG_SOFTGEL             = '314'
 
UNIT_OF_MEASURE_CUBIC_FOOT              = '315'
 
UNIT_OF_MEASURE_PACKET                  = '316'
 
UNIT_OF_MEASURE_DOSE                    = '317'
 

	
 
UNIT_OF_MEASURE = {
 

	
 
    # standard
 
    UNIT_OF_MEASURE_NONE                : "None",
 
    UNIT_OF_MEASURE_EACH                : "Each",
 
    UNIT_OF_MEASURE_10_COUNT            : "10 Count",
 
    UNIT_OF_MEASURE_DOZEN               : "Dozen",
 
    UNIT_OF_MEASURE_50_COUNT            : "50-Count",
 
    UNIT_OF_MEASURE_100_COUNT           : "100-Count",
 
    UNIT_OF_MEASURE_100_COUNT_1_PLY     : "100-Count (1 Ply)",
 
    UNIT_OF_MEASURE_100_COUNT_2_PLY     : "100-Count (2 Ply)",
 
    UNIT_OF_MEASURE_100_COUNT_3_PLY     : "100-Count (3 Ply)",
 
    UNIT_OF_MEASURE_100_COUNT_4_PLY     : "100-Count (4 Ply)",
 
    UNIT_OF_MEASURE_INCH                : "Inch",
 
    UNIT_OF_MEASURE_FOOT                : "Foot",
 
    UNIT_OF_MEASURE_50_FEET             : "50 Feet",
 
    UNIT_OF_MEASURE_100_FEET            : "100 Feet",
 
    UNIT_OF_MEASURE_100_YARDS           : "100 Yards",
 
    UNIT_OF_MEASURE_SQUARE_INCH         : "Square Inch",
 
    UNIT_OF_MEASURE_SQUARE_FEET         : "Square feet",
 
    UNIT_OF_MEASURE_50_SQUARE_YARD      : "50 Square yard",
 
    UNIT_OF_MEASURE_LIQUID_OUNCE        : "Liquid ounce",
 
    UNIT_OF_MEASURE_PINT                : "Pint",
 
    UNIT_OF_MEASURE_QUART               : "Quart",
 
    UNIT_OF_MEASURE_HALF_GALLON         : "Half gallon",
 
    UNIT_OF_MEASURE_GALLON              : "Gallon",
 
    UNIT_OF_MEASURE_DRY_OUNCE           : "Dry ounce",
 
    UNIT_OF_MEASURE_POUND               : "Pound",
 
    UNIT_OF_MEASURE_MILLIMETER          : "Millimeter",
 
    UNIT_OF_MEASURE_CENTIMETER          : "Centimeter",
 
    UNIT_OF_MEASURE_DECIMETER           : "Decimeter",
 
    UNIT_OF_MEASURE_METER               : "Meter",
 
    UNIT_OF_MEASURE_DEKAMETER           : "Dekameter",
 
    UNIT_OF_MEASURE_HECTOMETER          : "Hectometer",
 
    UNIT_OF_MEASURE_SQ_CENTIMETER       : "Sq. Centimeter",
 
    UNIT_OF_MEASURE_SQUARE_METER        : "Square Meter",
 
    UNIT_OF_MEASURE_CENTILITER          : "Centiliter",
 
    UNIT_OF_MEASURE_GRAMS               : "Grams",
 
    UNIT_OF_MEASURE_KILOGRAMS           : "Kilograms",
 
    UNIT_OF_MEASURE_BOX                 : "Box",
 
    UNIT_OF_MEASURE_CARTON              : "Carton",
 
    UNIT_OF_MEASURE_CASE                : "Case",
 
    UNIT_OF_MEASURE_PACKAGE             : "Package",
 
    UNIT_OF_MEASURE_PACK                : "Pack",
 
    UNIT_OF_MEASURE_ROLL                : "Roll",
 
    UNIT_OF_MEASURE_GROSS               : "Gross",
 
    UNIT_OF_MEASURE_LOAD                : "Load",
 
    UNIT_OF_MEASURE_COUNT               : "Count",
 
    UNIT_OF_MEASURE_YARD                : "Yard",
 
    UNIT_OF_MEASURE_BUSHEL              : "Bushel",
 
    UNIT_OF_MEASURE_CENTIGRAM           : "Centigram",
 
    UNIT_OF_MEASURE_DECILITER           : "Deciliter",
 
    UNIT_OF_MEASURE_DECIGRAM            : "Decigram",
 
    UNIT_OF_MEASURE_MILLILITER          : "Milliliter",
 
    UNIT_OF_MEASURE_PECK                : "Peck",
 
    UNIT_OF_MEASURE_PAIR                : "Pair",
 
    UNIT_OF_MEASURE_DRY_QUART           : "Quart (Dry)",
 
    UNIT_OF_MEASURE_SHEET               : "Sheet",
 
    UNIT_OF_MEASURE_STICKS              : "Sticks",
 
    UNIT_OF_MEASURE_THOUSAND            : "Thousand",
 
    UNIT_OF_MEASURE_PALLET_UNIT_LOAD    : "Pallet/Unit Load",
 
    UNIT_OF_MEASURE_PERCENT             : "Percent",
 
    UNIT_OF_MEASURE_TRUCKLOAD           : "Truckload",
 
    UNIT_OF_MEASURE_DOLLARS             : "Dollars",
 

	
 
    # non-standard
 
    UNIT_OF_MEASURE_BUNCH               : "Bunch",
 
    UNIT_OF_MEASURE_LITER               : "Liter",
 
    UNIT_OF_MEASURE_BAG                 : "Bag",
 
    UNIT_OF_MEASURE_CAPSULES            : "Capsules",
 
    UNIT_OF_MEASURE_CUBIC_CENTIMETERS   : "Cubic Centimeters",
 
    UNIT_OF_MEASURE_SOFTGELS            : "Soft Gels",
 
    UNIT_OF_MEASURE_TABLETS             : "Tablets",
 
    UNIT_OF_MEASURE_VEG_CAPSULES        : "Vegetarian Capsules",
 
    UNIT_OF_MEASURE_WAFERS              : "Wafers",
 
    UNIT_OF_MEASURE_PIECE               : "Piece",
 
    UNIT_OF_MEASURE_KIT                 : "Kit",
 
    UNIT_OF_MEASURE_CHEWABLE            : "Chewable",
 
    UNIT_OF_MEASURE_LOZENGE             : "Lozenge",
 
    UNIT_OF_MEASURE_CUBIC_INCH          : "Cubic Inches",
 
    UNIT_OF_MEASURE_VEG_SOFTGEL         : "Vegitarian Softgels",
 
    UNIT_OF_MEASURE_CUBIC_FOOT          : "Cubic Feet",
 
    UNIT_OF_MEASURE_PACKET              : "Packets",
 
    UNIT_OF_MEASURE_DOSE                : "Doses",
 
    }
 

	
 

	
 
UPGRADE_STATUS_PENDING          = 1
 
UPGRADE_STATUS_EXECUTING        = 2
 
UPGRADE_STATUS_SUCCEEDED        = 3
 
UPGRADE_STATUS_FAILED           = 4
 

	
 
UPGRADE_STATUS = {
 
    UPGRADE_STATUS_PENDING      : "pending execution",
 
    UPGRADE_STATUS_EXECUTING    : "currently executing",
 
    UPGRADE_STATUS_SUCCEEDED    : "execution succeeded",
 
    UPGRADE_STATUS_FAILED       : "execution failed",
 
}
 

	
 

	
 
USER_EVENT_LOGIN                = 1
 
USER_EVENT_LOGOUT               = 2
 
USER_EVENT_BECOME_ROOT          = 3
 
USER_EVENT_STOP_ROOT            = 4
 

	
 
USER_EVENT = {
 
    USER_EVENT_LOGIN            : "login",
 
    USER_EVENT_LOGOUT           : "logout",
 
    USER_EVENT_BECOME_ROOT      : "become root",
 
    USER_EVENT_STOP_ROOT        : "stop being root",
 
}
 

	
 

	
 
EMPLOYEE_STATUS_CURRENT         = 1
 
EMPLOYEE_STATUS_FORMER          = 2
 

	
 
EMPLOYEE_STATUS = {
 
    EMPLOYEE_STATUS_CURRENT     : "current",
 
    EMPLOYEE_STATUS_FORMER      : "former",
0 comments (0 inline, 0 general)