Changeset - cdda58fd694c
[Not reviewed]
2 7 13
Lance Edgar (lance) - 12 years ago 2012-08-29 13:28:07
lance@edbob.org
extensive commit (see note)

The following changes are included:

- Initial batch support added (with ``PrintLabels`` provider).

- GPC data type added.

- Changed internal name of file monitor.

- Added progress support for label printing.

- Label profiles moved from config to database model.

- Removed ``rattail.db.init_database()`` function.

- Moved some enum values from db extension to core (``rattail.enum`` module).

- Improved SIL support: moved ``rattail.sil`` to subpackage, added ``Writer
class etc.
22 files changed with 1399 insertions and 1399 deletions:
0 comments (0 inline, 0 general)
rattail/__init__.py
Show inline comments
 
@@ -31,3 +31,5 @@ __path__ = extend_path(__path__, __name__)
 

	
 

	
 
from rattail._version import __version__
 
from rattail.enum import *
 
from rattail.gpc import GPC
rattail/batches.py
Show inline comments
 
deleted file
rattail/batches/__init__.py
Show inline comments
 
new file 100644
 
#!/usr/bin/env python
 
# -*- coding: utf-8  -*-
 
################################################################################
 
#
 
#  Rattail -- Retail Software Framework
 
#  Copyright © 2010-2012 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 Affero 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 Affero General Public License for
 
#  more details.
 
#
 
#  You should have received a copy of the GNU Affero General Public License
 
#  along with Rattail.  If not, see <http://www.gnu.org/licenses/>.
 
#
 
################################################################################
 

	
 
"""
 
``rattail.batches`` -- Batch System
 
"""
 

	
 
from edbob.util import entry_point_map
 

	
 
from rattail.batches.exceptions import *
 
from rattail.batches.providers import *
 

	
 

	
 
registered_providers = {}
 

	
 

	
 
def get_provider(name):
 
    provider = registered_providers.get(name)
 
    if not provider:
 
        raise BatchProviderNotFound(name)
 
    return provider()
 

	
 

	
 
def iter_providers():
 
    return sorted(registered_providers.itervalues(),
 
                  key=lambda x: x.description)
 

	
 

	
 
def init(config):
 
    global registered_providers
 
    registered_providers = entry_point_map('rattail.batches.providers')
rattail/batches/exceptions.py
Show inline comments
 
new file 100644
 
#!/usr/bin/env python
 
# -*- coding: utf-8  -*-
 
################################################################################
 
#
 
#  Rattail -- Retail Software Framework
 
#  Copyright © 2010-2012 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 Affero 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 Affero General Public License for
 
#  more details.
 
#
 
#  You should have received a copy of the GNU Affero General Public License
 
#  along with Rattail.  If not, see <http://www.gnu.org/licenses/>.
 
#
 
################################################################################
 

	
 
"""
 
``rattail.batches.exceptions`` -- Batch Exceptions
 
"""
 

	
 

	
 
class BatchError(Exception):
 

	
 
    pass
 

	
 

	
 
class BatchProviderNotFound(BatchError):
 

	
 
    def __init__(self, name):
 
        self.name = name
 

	
 
    def __str__(self):
 
        return "Batch provider not found: %s" % self.name
 

	
 

	
 
class BatchDestinationNotSupported(BatchError):
 

	
 
    def __init__(self, batch):
 
        self.batch = batch
 

	
 
    def __str__(self):
 
        return "Destination '%s' not supported for batch: %s" % (
 
            self.batch.destination, self.batch.description)
rattail/batches/providers/__init__.py
Show inline comments
 
new file 100644
 
#!/usr/bin/env python
 
# -*- coding: utf-8  -*-
 
################################################################################
 
#
 
#  Rattail -- Retail Software Framework
 
#  Copyright © 2010-2012 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 Affero 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 Affero General Public License for
 
#  more details.
 
#
 
#  You should have received a copy of the GNU Affero General Public License
 
#  along with Rattail.  If not, see <http://www.gnu.org/licenses/>.
 
#
 
################################################################################
 

	
 
"""
 
``rattail.batches.providers`` -- Batch Providers
 
"""
 

	
 
import datetime
 

	
 
import edbob
 

	
 
import rattail
 
from rattail import sil
 

	
 

	
 
__all__ = ['BatchProvider']
 

	
 

	
 
class BatchProvider(edbob.Object):
 

	
 
    name = None
 
    description = None
 
    source = 'RATAIL'
 
    destination = None
 
    action_type = None
 
    purge_date_offset = 90
 

	
 
    def add_columns(self, batch):
 
        pass
 

	
 
    def add_rows_begin(self, batch):
 
        pass
 

	
 
    def add_rows(self, batch, query, progress=None):
 
        self.add_rows_begin(batch)
 
        prog = None
 
        if progress:
 
            prog = progress("Adding rows to batch \"%s\"" % batch.description,
 
                            query.count())
 
        cancel = False
 
        for i, instance in enumerate(query, 1):
 
            self.add_row(batch, instance)
 
            if prog and not prog.update(i):
 
                cancel = True
 
                break
 
        if prog:
 
            prog.destroy()
 
        return not cancel
 

	
 
    def execute(self, batch, progress=None):
 
        raise NotImplementedError
 

	
 
    def make_batch(self, session, data, progress=None):
 
        batch = rattail.Batch()
 
        batch.provider = self.name
 
        batch.source = self.source
 
        batch.id = sil.consume_batch_id(batch.source)
 
        batch.destination = self.destination
 
        batch.description = self.description
 
        batch.action_type = self.action_type
 
        self.set_purge_date(batch)
 
        session.add(batch)
 
        session.flush()
 

	
 
        self.add_columns(batch)
 
        batch.create_table()
 
        if not self.add_rows(batch, data, progress=progress):
 
            batch.drop_table()
 
            return None
 
        return batch
 

	
 
    def set_purge_date(self, batch):
 
        today = edbob.utc_time(naive=True).date()
 
        purge_offset = datetime.timedelta(days=self.purge_date_offset)
 
        batch.purge = today + purge_offset
 

	
 
    def set_params(self, session, **params):
 
        pass
rattail/batches/providers/labels.py
Show inline comments
 
new file 100644
 
#!/usr/bin/env python
 

	
 
"""
 
``dtail.batches.products.labels`` -- Print Labels Batch
 
"""
 

	
 
from sqlalchemy.orm import object_session
 

	
 
import rattail
 
from rattail.batches.providers import BatchProvider
 

	
 

	
 
class PrintLabels(BatchProvider):
 

	
 
    name = 'print_labels'
 
    description = "Print Labels"
 

	
 
    default_profile = None
 
    default_quantity = 1
 

	
 
    def add_columns(self, batch):
 
        batch.add_column('F01')
 
        batch.add_column('F155')
 
        batch.add_column('F02')
 
        batch.add_column('F22', display_name="Size")
 
        batch.add_column('F95', display_name="Label")
 
        batch.add_column('F94', display_name="Quantity")
 

	
 
    def add_rows_begin(self, batch):
 
        session = object_session(batch)
 
        if not self.default_profile:
 
            q = session.query(rattail.LabelProfile)
 
            q = q.order_by(rattail.LabelProfile.ordinal)
 
            self.default_profile = q.first()
 
            assert self.default_profile
 
        else:
 
            self.default_profile = session.merge(self.default_profile)
 

	
 
    def add_row(self, batch, product):
 
        row = batch.rowclass()
 
        row.F01 = product.upc
 
        if product.brand:
 
            row.F155 = product.brand.name
 
        row.F02 = product.description[:20]
 
        row.F22 = product.size
 
        row.F95 = self.default_profile.code
 
        row.F94 = self.default_quantity
 
        batch.add_row(row)
 

	
 
    def execute(self, batch, progress=None):
 
        prog = None
 
        if progress:
 
            prog = progress("Loading product data", batch.rowcount)
 

	
 
        session = object_session(batch)
 
        profiles = {}
 

	
 
        cancel = False
 
        for i, row in enumerate(batch.iter_rows(), 1):
 

	
 
            profile = profiles.get(row.F95)
 
            if not profile:
 
                q = session.query(rattail.LabelProfile)
 
                q = q.filter(rattail.LabelProfile.code == row.F95)
 
                profile = q.one()
 
                profile.labels = []
 
                profiles[row.F95] = profile
 

	
 
            q = session.query(rattail.Product)
 
            q = q.filter(rattail.Product.upc == row.F01)
 
            product = q.one()
 

	
 
            profile.labels.append((product, row.F94))
 

	
 
            if prog and not prog.update(i):
 
                cancel = True
 
                break
 

	
 
        if not cancel:
 
            for profile in profiles.itervalues():
 
                printer = profile.get_printer()
 
                prog2 = None
 
                if prog:
 
                    prog2 = prog.secondary_progress()
 
                if not printer.print_labels(profile.labels, progress=prog2):
 
                    cancel = True
 
                    break
 

	
 
        if prog:
 
            prog.destroy()
 
        return not cancel
 

	
 
    def set_params(self, session, **params):
 
        profile = params.get('profile')
 
        if profile:
 
            q = session.query(rattail.LabelProfile)
 
            q = q.filter(rattail.LabelProfile.code == profile)
 
            self.default_profile = q.one()
 

	
 
        quantity = params.get('quantity')
 
        if quantity and quantity.isdigit():
 
            self.default_quantity = int(quantity)
rattail/db/__init__.py
Show inline comments
 
@@ -27,7 +27,6 @@
 
"""
 

	
 
import edbob
 
# from edbob.db.extensions import activate_extension
 

	
 
import rattail
 

	
 
@@ -42,60 +41,3 @@ def init(config):
 

	
 
    from rattail.db.extension import enum
 
    edbob.graft(rattail, enum)
 

	
 

	
 
# def init_database(engine, session):
 
#     """
 
#     Initialize an ``edbob`` database for use with Rattail.
 
#     """
 

	
 
#     activate_extension('rattail', engine)
 

	
 
#     columns = [
 
#         ('F01',         'UPC',                  'GPC(14)'),
 
#         ('F02',         'Description',          'CHAR(20)'),
 
#         ('F03',         'Department Number',    'NUMBER(4,0)'),
 
#         ('F22',         'Size',                 'CHAR(30)'),
 
#         ('F155',        'Brand',                'CHAR(30)'),
 
#         ('F238',        'Department Name',      'CHAR(30)'),
 
#         ]
 

	
 
#     for name, disp, dtype in columns:
 
#         session.add(rattail.SilColumn(
 
#                 sil_name=name, display=disp, data_type=dtype))
 
#         session.flush()
 

	
 
#     dictionaries = [
 
#         ('DEPT_DCT', 'Department', [
 
#                 ('F03', True),
 
#                 'F238',
 
#                 ]),
 
#         ('ITEM_DCT', 'Product', [
 
#                 ('F01', True),
 
#                 'F02',
 
#                 'F03',
 
#                 'F22',
 
#                 'F155',
 
#                 ]),
 
#         # ('PRICE_DCT', 'Price', []),
 
#         # ('FCOST_DCT', 'Future Cost', []),
 
#         # ('FSPRICE_DCT', 'Future Sale Price', []),
 
#         # ('CLASS_GROUP', 'Scale Class / Group', []),
 
#         # ('NUTRITION', 'Scale Nutrition', []),
 
#         # ('SCALE_TEXT', 'Scale Text', []),
 
#         # ('VENDOR_DCT', 'Vendor', []),
 
#         ]
 

	
 
#     for name, desc, cols in dictionaries:
 
#         bd = rattail.BatchDictionary(name=name, description=desc)
 
#         for col in cols:
 
#             key = False
 
#             if not isinstance(col, basestring):
 
#                 col, key = col
 
#             q = session.query(rattail.SilColumn)
 
#             q = q.filter(rattail.SilColumn.sil_name == col)
 
#             col = q.one()
 
#             bd.columns.append(
 
#                 rattail.BatchDictionaryColumn(sil_column=col, key=key))
 
#         session.add(bd)
 
#         session.flush()
rattail/db/extension/enum.py
Show inline comments
 
@@ -27,28 +27,6 @@
 
"""
 

	
 

	
 
BATCH_ADD                       = 'ADD'
 
BATCH_ADD_REPLACE               = 'ADDRPL'
 
BATCH_CHANGE                    = 'CHANGE'
 
BATCH_LOAD                      = 'LOAD'
 
BATCH_REMOVE                    = 'REMOVE'
 

	
 
BATCH_ACTION_TYPE = {
 
    BATCH_ADD                   : "Add",
 
    BATCH_ADD_REPLACE           : "Add/Replace",
 
    BATCH_CHANGE                : "Change",
 
    BATCH_LOAD                  : "Load",
 
    BATCH_REMOVE                : "Remove",
 
    }
 

	
 

	
 
# BATCH_MAIN_ITEM                 = 'ITEM_DCT'
 

	
 
# BATCH_DICTIONARY = {
 
#     BATCH_MAIN_ITEM             : "Main Item",
 
#     }
 

	
 

	
 
EMPLOYEE_STATUS_CURRENT         = 1
 
EMPLOYEE_STATUS_FORMER          = 2
 

	
 
@@ -58,34 +36,6 @@ EMPLOYEE_STATUS = {
 
    }
 

	
 

	
 
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",
 
    }
 

	
 

	
 
UNIT_OF_MEASURE_EACH            = '01'
 
UNIT_OF_MEASURE_POUND           = '49'
 

	
 
UNIT_OF_MEASURE = {
 
    UNIT_OF_MEASURE_EACH        : "Each",
 
    UNIT_OF_MEASURE_POUND       : "Pound",
 
    }
 

	
 

	
 
# VENDOR_CATALOG_NOT_PARSED       = 1
 
# VENDOR_CATALOG_PARSED           = 2
 
# VENDOR_CATALOG_COGNIZED         = 3
rattail/db/extension/model.py
Show inline comments
 
@@ -26,12 +26,9 @@
 
``rattail.db.extension.model`` -- Schema Definition
 
"""
 

	
 
from __future__ import absolute_import
 

	
 
import re
 

	
 
from sqlalchemy import (Column, String, Integer, Date, DateTime,
 
                        Boolean, Text, ForeignKey, BigInteger, Numeric)
 
from sqlalchemy import Column, ForeignKey
 
from sqlalchemy import String, Integer, DateTime, Date, Boolean, Numeric, Text
 
from sqlalchemy import types
 
from sqlalchemy import and_
 
from sqlalchemy.orm import relationship, object_session
 
from sqlalchemy.ext.associationproxy import association_proxy
 
@@ -39,475 +36,175 @@ from sqlalchemy.ext.orderinglist import ordering_list
 

	
 
import edbob
 
from edbob.db.model import Base, uuid_column
 
from edbob.db.extensions.contact import (Person, EmailAddress, PhoneNumber,
 
                                         PersonPhoneNumber)
 
from edbob.db.extensions.contact import Person, EmailAddress, PhoneNumber
 
from edbob.exceptions import LoadSpecError
 
from edbob.sqlalchemy import getset_factory
 

	
 
from rattail import sil
 
from rattail import batches
 
from rattail.gpc import GPCType
 

	
 
# __all__ = ['SilColumn', 'BatchDictionaryColumn', 'BatchDictionary',
 
#            'BatchTerminalColumn', 'BatchTerminal', 'BatchColumn',
 
#            'Batch', 'Brand', 'Department', 'Subdepartment', 'Category',
 
#            'Product', 'Employee', 'Vendor', 'VendorContact', 'VendorPhoneNumber',
 
#            'ProductCost', 'ProductPrice', 'Customer', 'CustomerGroup', 
 
#            'CustomerGroupAssignment', 'CustomerPerson']
 

	
 
__all__ = ['Department', 'Subdepartment', 'Brand', 'Category', 'Vendor',
 
           'VendorContact', 'VendorPhoneNumber', 'Product', 'ProductCost',
 
           'ProductPrice', 'Customer', 'CustomerEmailAddress',
 
           'CustomerPhoneNumber', 'CustomerGroup', 'CustomerGroupAssignment',
 
           'CustomerPerson', 'Employee', 'EmployeeEmailAddress',
 
           'EmployeePhoneNumber']
 

	
 
# sil_type_pattern = re.compile(r'^(CHAR|NUMBER)\((\d+(?:\,\d+)?)\)$')
 

	
 

	
 
# class SilColumn(Base):
 
#     """
 
#     Represents a SIL-compatible column available to the batch system.
 
#     """
 
           'EmployeePhoneNumber', 'BatchColumn', 'Batch', 'LabelProfile']
 

	
 
#     __tablename__ = 'sil_columns'
 

	
 
#     uuid = uuid_column()
 
#     sil_name = Column(String(10))
 
#     display = Column(String(20))
 
#     data_type = Column(String(15))
 
    
 
#     def __repr__(self):
 
#         return "<SilColumn: %s>" % self.sil_name
 

	
 
#     def __str__(self):
 
#         return str(self.sil_name or '')
 

	
 
class BatchColumn(Base):
 
    """
 
    Represents a :class:`SilColumn` associated with a :class:`Batch`.
 
    """
 

	
 
# class BatchDictionaryColumn(Base):
 
#     """
 
#     Represents a column within a :class:`BatchDictionary`.
 
#     """
 
    __tablename__ = 'batch_columns'
 

	
 
#     __tablename__ = 'batch_dictionary_columns'
 
    uuid = uuid_column()
 
    batch_uuid = Column(String(32), ForeignKey('batches.uuid'))
 
    ordinal = Column(Integer, nullable=False)
 
    name = Column(String(20))
 
    display_name = Column(String(50))
 
    sil_name = Column(String(10))
 
    data_type = Column(String(15))
 
    description = Column(String(50))
 
    visible = Column(Boolean, default=True)
 

	
 
    def __init__(self, sil_name=None, **kwargs):
 
        if sil_name:
 
            kwargs['sil_name'] = sil_name
 
            sil_column = sil.get_column(sil_name)
 
            kwargs.setdefault('name', sil_name)
 
            kwargs.setdefault('data_type', sil_column.data_type)
 
            kwargs.setdefault('description', sil_column.description)
 
            kwargs.setdefault('display_name', sil_column.display_name)
 
        super(BatchColumn, self).__init__(**kwargs)
 

	
 
#     uuid = uuid_column()
 
#     dictionary_uuid = Column(String(32), ForeignKey('batch_dictionaries.uuid'))
 
#     sil_column_uuid = Column(String(32), ForeignKey('sil_columns.uuid'))
 
#     key = Column(Boolean)
 
    def __repr__(self):
 
        return "<BatchColumn: %s>" % self.name
 

	
 
#     sil_column = relationship(SilColumn)
 
    def __unicode__(self):
 
        return unicode(self.display_name or '')
 

	
 
#     def __repr__(self):
 
#         return "<BatchDictionaryColumn: %s>" % self.sil_column
 

	
 
#     def __str__(self):
 
#         return str(self.sil_column or '')
 
class BatchRow(edbob.Object):
 
    """
 
    Superclass of batch row objects.
 
    """
 

	
 
    def __unicode__(self):
 
        return u"Row %d" % self.ordinal
 

	
 
# class BatchDictionary(Base):
 
#     """
 
#     Represents a SIL-based dictionary supported by one or more
 
#     :class:`BatchTerminal` classes.
 
#     """
 

	
 
#     __tablename__ = 'batch_dictionaries'
 
class Batch(Base):
 
    """
 
    Represents a SIL-compliant batch of data.
 
    """
 

	
 
#     uuid = uuid_column()
 
#     name = Column(String(20))
 
#     description = Column(String(255))
 
    __tablename__ = 'batches'
 

	
 
#     columns = relationship(
 
#         BatchDictionaryColumn,
 
#         backref='dictionary')
 
    uuid = uuid_column()
 
    provider = Column(String(50))
 
    id = Column(String(8))
 
    source = Column(String(6))
 
    destination = Column(String(6))
 
    action_type = Column(String(6))
 
    description = Column(String(50))
 
    rowcount = Column(Integer, default=0)
 
    executed = Column(DateTime)
 
    purge = Column(Date)
 

	
 
    columns = relationship(BatchColumn, backref='batch',
 
                           collection_class=ordering_list('ordinal'),
 
                           order_by=BatchColumn.ordinal,
 
                           cascade='save-update, merge, delete, delete-orphan')
 

	
 
    _rowclasses = {}
 

	
 
#     def __repr__(self):
 
#         return "<BatchDictionary: %s>" % self.name
 
    def __repr__(self):
 
        return "<Batch: %s>" % self.description
 

	
 
#     def __str__(self):
 
#         return str(self.description or '')
 
    def __unicode__(self):
 
        return unicode(self.description or '')
 

	
 
    @property
 
    def rowclass(self):
 
        """
 
        Returns the mapped class for the underlying row (data) table.
 
        """
 

	
 
# class BatchTerminalColumn(Base):
 
#     """
 
#     Represents a column supported by a :class:`BatchTerminal`.
 
#     """
 
        if not self.uuid:
 
            object_session(self).flush()
 
            assert self.uuid
 

	
 
#     __tablename__ = 'batch_terminal_columns'
 
        if self.uuid not in self._rowclasses:
 

	
 
#     uuid = uuid_column()
 
#     terminal_uuid = Column(String(32), ForeignKey('batch_terminals.uuid'))
 
#     dictionary_uuid = Column(String(32), ForeignKey('batch_dictionaries.uuid'))
 
#     sil_column_uuid = Column(String(32), ForeignKey('sil_columns.uuid'))
 
#     ordinal = Column(Integer)
 
#     source = Column(Boolean)
 
#     target = Column(Boolean)
 
            kwargs = {
 
                '__tablename__': 'batch.%s' % self.uuid,
 
                'uuid': uuid_column(),
 
                'ordinal': Column(Integer, nullable=False),
 
                }
 

	
 
#     dictionary = relationship(BatchDictionary)
 
#     sil_column = relationship(SilColumn)
 
            for column in self.columns:
 
                data_type = sil.get_sqlalchemy_type(column.data_type)
 
                kwargs[column.name] = Column(data_type)
 
            rowclass = type('BatchRow_%s' % str(self.uuid), (Base, BatchRow), kwargs)
 

	
 
#     def __repr__(self):
 
#         return "<BatchTerminalColumn: %s, %s, %s>" % (
 
#             self.terminal, self.dictionary, self.sil_column)
 
            batch_uuid = self.uuid
 
            def batch(self):
 
                return object_session(self).query(Batch).get(batch_uuid)
 
            rowclass.batch = property(batch)
 

	
 
#     def __str__(self):
 
#         return str(self.sil_column or '')
 
            self._rowclasses[self.uuid] = rowclass
 

	
 
        return self._rowclasses[self.uuid]
 

	
 
# class BatchTerminal(Base):
 
#     """
 
#     Represents a terminal, or "junction" for batch data.
 
#     """
 
    def add_column(self, sil_name=None, **kwargs):
 
        column = BatchColumn(sil_name, **kwargs)
 
        self.columns.append(column)
 

	
 
#     __tablename__ = 'batch_terminals'
 
    def add_row(self, row, **kwargs):
 
        """
 
        Adds a row to the batch data table.
 
        """
 

	
 
#     uuid = uuid_column()
 
#     sil_id = Column(String(20), unique=True)
 
#     description = Column(String(50))
 
#     class_spec = Column(String(255))
 
#     functional = Column(Boolean, default=False)
 
#     source = Column(Boolean)
 
#     target = Column(Boolean)
 
#     source_kwargs = Column(Text)
 
#     target_kwargs = Column(Text)
 
        session = object_session(self)
 
        # FIXME: This probably needs to use a func.max() query.
 
        row.ordinal = self.rowcount + 1
 
        session.add(row)
 
        self.rowcount += 1
 
        session.flush()
 

	
 
#     columns = relationship(
 
#         BatchTerminalColumn,
 
#         backref='terminal')
 
    def create_table(self):
 
        """
 
        Creates the batch's data table within the database.
 
        """
 

	
 
#     _terminal = 'not_got_yet'
 
        self.rowclass.__table__.create()
 

	
 
#     def __repr__(self):
 
#         return "<BatchTerminal: %s>" % self.sil_id
 
    def drop_table(self):
 
        """
 
        Drops the batch's data table from the database.
 
        """
 

	
 
#     def __str__(self):
 
#         return str(self.description or '')
 
        self.rowclass.__table__.drop()
 

	
 
#     def source_columns(self, dictionary):
 
#         for col in self.columns:
 
#             if col.dictionary is dictionary:
 
#                 yield col
 
    def execute(self, progress=None):
 
        provider = self.get_provider()
 
        assert provider
 
        provider.execute(self, progress)
 
        self.executed = edbob.utc_time(naive=True)
 
        object_session(self).flush()
 

	
 
#     def get_terminal(self):
 
#         """
 
#         Returns the :class:`rattail.batches.BatchTerminal` instance which is
 
#         associated with the database record via its ``python_spec`` field.
 
#         """
 
    def get_provider(self):
 
        assert self.provider
 
        return batches.get_provider(self.provider)
 

	
 
#         if self._terminal == 'not_got_yet':
 
#             self._terminal = None
 
#             if self.class_spec:
 
#                 term = edbob.load_spec(self.class_spec)
 
#                 if term:
 
#                     self._terminal = term()
 
#         return self._terminal
 

	
 

	
 
# class BatchColumn(Base):
 
#     """
 
#     Represents a :class:`SilColumn` associated with a :class:`Batch`.
 
#     """
 

	
 
#     __tablename__ = 'batch_columns'
 

	
 
#     uuid = uuid_column()
 
#     batch_uuid = Column(String(32), ForeignKey('batches.uuid'))
 
#     ordinal = Column(Integer)
 
#     sil_column_uuid = Column(String(32), ForeignKey('sil_columns.uuid'))
 
#     source_uuid = Column(String(32), ForeignKey('batch_terminals.uuid'))
 
#     targeted = Column(Boolean)
 

	
 
#     sil_column = relationship(SilColumn)
 

	
 
#     source = relationship(
 
#         BatchTerminal,
 
#         primaryjoin=BatchTerminal.uuid == source_uuid,
 
#         order_by=[BatchTerminal.description],
 
#         )
 

	
 
#     def __repr__(self):
 
#         return "<BatchColumn: %s, %s>" % (self.batch, self.sil_column)
 

	
 

	
 
# def get_sil_column(name):
 
#     """
 
#     Returns a ``sqlalchemy.Column`` instance according to Rattail's notion of
 
#     what each SIL field ought to look like.
 
#     """
 

	
 
#     type_map = {
 

	
 
#         # The first list of columns is a subset of Level 1 SIL.
 

	
 
#         'F01':
 
#             Integer,    # upc
 
#         'F02':
 
#             String(60), # short (receipt) description
 
#         'F478':
 
#             Integer,    # scale text type
 

	
 
#         # The remaining columns are custom to Rattail.
 

	
 
#         'F4001':
 
#             String(60), # short description, line 2
 
#         }
 

	
 
#     return Column(name, type_map[name])
 

	
 

	
 
# def get_sil_type(data_type):
 
#     """
 
#     Returns a SQLAlchemy-based data type according to the SIL-compliant type
 
#     specifier found in ``data_type``.
 
#     """
 

	
 
#     if data_type == 'GPC(14)':
 
#         return BigInteger
 

	
 
#     m = sil_type_pattern.match(data_type)
 
#     if m:
 
#         data_type, precision = m.groups()
 
#         if precision.isdigit():
 
#             precision = int(precision)
 
#             scale = 0
 
#         else:
 
#             precision, scale = precision.split(',')
 
#             precision = int(precision)
 
#             scale = int(scale)
 
#         if data_type == 'CHAR':
 
#             assert not scale, "FIXME"
 
#             return String(precision)
 
#         if data_type == 'NUMBER':
 
#             return Numeric(precision, scale)
 

	
 
#     assert False, "FIXME"
 
    
 

	
 
# class Batch(Base):
 
#     """
 
#     Represents a batch of data, presumably in need of processing.
 
#     """
 

	
 
#     __tablename__ = 'batches'
 

	
 
#     _rowclass = None
 
#     _row_classes = {}
 

	
 
#     uuid = uuid_column()
 
#     source_uuid = Column(String(32), ForeignKey('batch_terminals.uuid'))
 
#     source_description = Column(String(50))
 
#     source_batch_id = Column(String(8))
 
#     batch_id = Column(String(8))
 
#     dictionary_uuid = Column(String(32), ForeignKey('batch_dictionaries.uuid'))
 
#     name = Column(String(30))
 
#     target_uuid = Column(String(32), ForeignKey('batch_terminals.uuid'))
 
#     action_type = Column(String(6))
 
#     elements = Column(String(255))
 
#     description = Column(String(50))
 
#     rowcount = Column(Integer, default=0)
 
#     effective = Column(DateTime)
 
#     deleted = Column(Boolean, default=False)
 
#     sil_type = Column(String(2))
 
#     sil_source_id = Column(String(20))
 
#     sil_target_id = Column(String(20))
 
#     sil_audit_file = Column(String(12))
 
#     sil_response_file = Column(String(12))
 
#     sil_origin_time = Column(DateTime)
 
#     sil_purge_date = Column(Date)
 
#     sil_user1 = Column(String(30))
 
#     sil_user2 = Column(String(30))
 
#     sil_user3 = Column(String(30))
 
#     sil_warning_level = Column(Integer)
 
#     sil_max_errors = Column(Integer)
 
#     sil_level = Column(String(7))
 
#     sil_software_revision = Column(String(4))
 
#     sil_primary_key = Column(String(50))
 
#     sil_sys_command = Column(String(512))
 
#     sil_dict_revision = Column(String(8))
 
    
 
#     source = relationship(
 
#         BatchTerminal,
 
#         primaryjoin=BatchTerminal.uuid == source_uuid,
 
#         order_by=[BatchTerminal.description],
 
#         )
 

	
 
#     dictionary = relationship(
 
#         BatchDictionary,
 
#         order_by=[BatchDictionary.name],
 
#         )
 

	
 
#     target = relationship(
 
#         BatchTerminal,
 
#         primaryjoin=BatchTerminal.uuid == target_uuid,
 
#         order_by=[BatchTerminal.description],
 
#         )
 

	
 
#     columns = relationship(
 
#         BatchColumn,
 
#         backref='batch',
 
#         )
 

	
 
#     # _table = None
 
#     # # _source_junction = 'not set'
 
#     # # _target_junction = 'not set'
 

	
 
#     # invalid_name_chars = re.compile(r'[^A-Za-z0-9]')
 

	
 
#     def __repr__(self):
 
#         return "<Batch: %s>" % (self.name or '(no name)')
 

	
 
#     def __str__(self):
 
#         return str(self.name or '')
 

	
 
#     # @property
 
#     # def source_junction(self):
 
#     #     """
 
#     #     Returns the :class:`rattail.BatchJunction` instance associated with
 
#     #     this batch's :attr:`Batch.source` attribute.
 
#     #     """
 

	
 
#     #     if self._source_junction == 'not set':
 
#     #         from rattail.sil import get_available_junctions
 
#     #         self._source_junction = None
 
#     #         junctions = get_available_junctions()
 
#     #         if self.source in junctions:
 
#     #             self._source_junction = junctions[self.source]
 
#     #     return self._source_junction
 

	
 
#     # @property
 
#     # def table(self):
 
#     #     """
 
#     #     Returns the ``sqlalchemy.Table`` instance for the underlying batch
 
#     #     data.
 
#     #     """
 

	
 
#     #     # from sqlalchemy import MetaData, Table, Column, String
 
#     #     from sqlalchemy import Table, Column, String
 
#     #     from rattail import metadata
 
#     #     from rattail.sqlalchemy import get_sil_column
 

	
 
#     #     if self._table is None:
 
#     #         # assert self.uuid
 
#     #         assert self.name
 
#     #         name = 'batch.%s.%s' % (self.source, self.batch_id)
 
#     #         if name in metadata.tables:
 
#     #             self._table = metadata.tables[name]
 
#     #         else:
 
#     #             # session = object_session(self)
 
#     #             # metadata = MetaData(session.bind)
 
#     #             columns = [Column('uuid', String(32), primary_key=True, default=get_uuid)]
 
#     #             # columns.extend([get_sil_column(x) for x in self.elements.split(',')])
 
#     #             columns.extend([get_sil_column(x) for x in self.elements.split(',')])
 
#     #             self._table = Table(name, metadata, *columns)
 
#     #     return self._table
 

	
 
#     # @property
 
#     # def rowclass(self):
 
#     #     """
 
#     #     Returns a unique subclass of :class:`rattail.BatchRow`, specific to the
 
#     #     batch.
 
#     #     """
 

	
 
#     #     if self._rowclass is None:
 
#     #         name = self.invalid_name_chars.sub('_', self.name)
 
#     #         self._rowclass = type('BatchRow_%s' % str(name), (BatchRow,), {})
 
#     #         mapper(self._rowclass, self.table)
 
#     #         # session = object_session(self)
 
#     #         # engine = session.bind
 
#     #         # session.configure(binds={self._rowclass:engine})
 
#     #     return self._rowclass
 

	
 
#     @property
 
#     def rowclass(self):
 
#         """
 
#         Returns the SQLAlchemy-mapped class for the underlying data table.
 
#         """
 

	
 
#         assert self.uuid
 
#         if self.uuid not in self._row_classes:
 
#             kwargs = {
 
#                 '__tablename__':        'batch.%s' % self.name,
 
#                 'uuid':                 uuid_column(),
 
#                 }
 
#             for col in self.columns:
 
#                 kwargs[col.sil_column.sil_name] = Column(get_sil_type(col.sil_column.data_type))
 
#             self._row_classes[self.uuid] = type('BatchRow', (Base,), kwargs)
 
#         return self._row_classes[self.uuid]
 

	
 
#     def create_table(self):
 
#         """
 
#         Creates the batch's data table within the database.
 
#         """
 

	
 
#         self.rowclass.__table__.create()
 

	
 
#     # @property
 
#     # def target_junction(self):
 
#     #     """
 
#     #     Returns the :class:`rattail.BatchJunction` instance associated with
 
#     #     this batch's :attr:`Batch.target` attribute.
 
#     #     """
 

	
 
#     #     if self._target_junction == 'not set':
 
#     #         from rattail.sil import get_available_junctions
 
#     #         self._target_junction = None
 
#     #         junctions = get_available_junctions()
 
#     #         if self.target in junctions:
 
#     #             self._target_junction = junctions[self.target]
 
#     #     return self._target_junction
 

	
 
#     # def append(self, **row):
 
#     #     """
 
#     #     Appends a row of data to the batch.  Note that this is done
 
#     #     immediately, and not within the context of any transaction.
 
#     #     """
 

	
 
#     #     # self.connection.execute(self.table.insert().values(**row))
 
#     #     # self.rowcount += 1
 
#     #     session = object_session(self)
 
#     #     session.add(self.rowclass(**row))
 
#     #     self.rowcount += 1
 
#     #     session.flush()
 

	
 
#     def add_rows(self, source, dictionary, **kwargs):
 
#         session = object_session(self)
 
#         source = source.get_terminal()
 
#         for row in source.provide_rows(session, self.rowclass,
 
#                                        dictionary, **kwargs):
 
#             session.add(row)
 
#             session.flush()
 

	
 
#     def execute(self):
 
#         """
 
#         Invokes the batch execution logic.  This will instantiate the
 
#         :class:`rattail.batches.BatchTerminal` instance identified by the
 
#         batch's :attr:`target` attribute and ask it to process the batch
 
#         according to its action type.
 

	
 
#         .. note::
 
#            No check is performed to verify the current time is appropriate as
 
#            far as the batch's effective date is concerned.  It is assumed that
 
#            other logic has already taken care of that and that yes, in fact it
 
#            *is* time for the batch to be executed.
 
#         """
 

	
 
#         target = self.target.get_terminal()
 
#         target.execute_batch(self)
 

	
 
#     def provide_rows(self):
 
#         """
 
#         Generator which yields :class:`BatchRow` instances belonging to the
 
#         batch.
 
#         """
 

	
 
#         session = object_session(self)
 
#         for row in session.query(self.rowclass):
 
#             yield row
 

	
 

	
 
# class BatchRow(edbob.Object):
 
#     """
 
#     Superclass of batch row objects.
 
#     """
 

	
 
#     def __repr__(self):
 
#         return "<BatchRow: %s>" % self.key_value
 
    def iter_rows(self):
 
        session = object_session(self)
 
        q = session.query(self.rowclass)
 
        q = q.order_by(self.rowclass.ordinal)
 
        return q
 

	
 

	
 
class Brand(Base):
 
@@ -726,16 +423,6 @@ class ProductCost(Base):
 
    unit_cost = Column(Numeric(9,5))
 
    effective = Column(DateTime)
 

	
 
    # case_pack = Column(Integer)
 
    # case_qty = Column(Integer)
 
    # pack_qty = Column(Integer)
 
    # # suggested_retail = Column(Numeric(6,2))
 
    # case_cost = Column(Numeric(9,5))
 
    # unit_cost = Column(Numeric(9,5))
 
    # # cost_effective = Column(DateTime)
 
    # # cost_expires = Column(DateTime)
 
    # # cost_recorded = Column(DateTime, default=datetime.datetime.now)
 

	
 
    vendor = relationship(Vendor)
 

	
 
    def __repr__(self):
 
@@ -772,7 +459,7 @@ class Product(Base):
 
    __tablename__ = 'products'
 

	
 
    uuid = uuid_column()
 
    upc = Column(BigInteger, index=True)
 
    upc = Column(GPCType, index=True)
 
    department_uuid = Column(String(32), ForeignKey('departments.uuid'))
 
    subdepartment_uuid = Column(String(32), ForeignKey('subdepartments.uuid'))
 
    category_uuid = Column(String(32), ForeignKey('categories.uuid'))
 
@@ -794,16 +481,6 @@ class Product(Base):
 
                   use_alter=True,
 
                   name='products_current_price_uuid_fkey'))
 

	
 
    # regular_price = Column(Numeric(10,4))
 
    # # package_price = Column(Numeric(10,4))
 
    # # package_price_quantity = Column(Integer)
 
    # sale_price = Column(Numeric(10,4))
 
    # sale_price_quantity = Column(Integer)
 

	
 
    # case_quantity = Column(Integer)
 
    # pack_quantity = Column(Integer)
 
    # pack_price = Column(Numeric(8,3))
 

	
 
    department = relationship(Department)
 
    subdepartment = relationship(Subdepartment)
 
    category = relationship(Category)
 
@@ -1102,3 +779,65 @@ Customer.groups = association_proxy(
 
    '_groups', 'group',
 
    getset_factory=getset_factory,
 
    creator=lambda x: CustomerGroupAssignment(group=x))
 

	
 

	
 
class LabelProfile(Base):
 
    """
 
    Represents a "profile" (collection of settings) for product label printing.
 
    """
 

	
 
    __tablename__ = 'label_profiles'
 

	
 
    uuid = uuid_column()
 
    ordinal = Column(Integer)
 
    code = Column(String(3))
 
    description = Column(String(50))
 
    printer_spec = Column(String(255))
 
    formatter_spec = Column(String(255))
 
    format = Column(Text)
 

	
 
    _printer = None
 
    _formatter = None
 

	
 
    def __repr__(self):
 
        return "<LabelProfile: %s>" % self.code
 

	
 
    def __unicode__(self):
 
        return unicode(self.description or '')
 

	
 
    def get_formatter(self):
 
        if not self._formatter and self.formatter_spec:
 
            try:
 
                formatter = edbob.load_spec(self.formatter_spec)
 
            except LoadSpecError:
 
                pass
 
            else:
 
                self._formatter = formatter()
 
        return self._formatter        
 

	
 
    def get_printer(self):
 
        if not self._printer and self.printer_spec:
 
            try:
 
                printer = edbob.load_spec(self.printer_spec)
 
            except LoadSpecError:
 
                pass
 
            else:
 
                self._printer = printer()
 
                for name in printer.required_settings:
 
                    setattr(printer, name, self.get_printer_setting(name))
 
                self._printer.formatter = self.get_formatter()
 
        return self._printer
 

	
 
    def get_printer_setting(self, name):
 
        session = object_session(self)
 
        if not self.uuid:
 
            session.flush()
 
        name = 'labels.%s.printer.%s' % (self.uuid, name)
 
        return edbob.get_setting(name, session)
 

	
 
    def save_printer_setting(self, name, value):
 
        session = object_session(self)
 
        if not self.uuid:
 
            session.flush()
 
        name = 'labels.%s.printer.%s' % (self.uuid, name)
 
        edbob.save_setting(name, value, session)
rattail/enum.py
Show inline comments
 
new file 100644
 
#!/usr/bin/env python
 
# -*- coding: utf-8  -*-
 
################################################################################
 
#
 
#  Rattail -- Retail Software Framework
 
#  Copyright © 2010-2012 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 Affero 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 Affero General Public License for
 
#  more details.
 
#
 
#  You should have received a copy of the GNU Affero General Public License
 
#  along with Rattail.  If not, see <http://www.gnu.org/licenses/>.
 
#
 
################################################################################
 

	
 
"""
 
``rattail.enum`` -- Enumerations
 
"""
 

	
 

	
 
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",
 
    }
 

	
 

	
 
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",
 
    }
 

	
 

	
 
UNIT_OF_MEASURE_EACH            = '01'
 
UNIT_OF_MEASURE_POUND           = '49'
 

	
 
UNIT_OF_MEASURE = {
 
    UNIT_OF_MEASURE_EACH        : "Each",
 
    UNIT_OF_MEASURE_POUND       : "Pound",
 
    }
rattail/exceptions.py
Show inline comments
 
new file 100644
 
#!/usr/bin/env python
 
# -*- coding: utf-8  -*-
 
################################################################################
 
#
 
#  Rattail -- Retail Software Framework
 
#  Copyright © 2010-2012 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 Affero 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 Affero General Public License for
 
#  more details.
 
#
 
#  You should have received a copy of the GNU Affero General Public License
 
#  along with Rattail.  If not, see <http://www.gnu.org/licenses/>.
 
#
 
################################################################################
 

	
 
"""
 
``rattail.exceptions`` -- Exceptions
 
"""
 

	
 

	
 
class LabelPrintingError(Exception):
 

	
 
    pass
rattail/filemon.py
Show inline comments
 
@@ -31,7 +31,7 @@ from edbob.filemon.win32 import FileMonitorService
 

	
 
class RattailFileMonitor(FileMonitorService):
 

	
 
    _svc_name_ = "Rattail File Monitor"
 
    _svc_name_ = "RattailFileMonitor"
 
    _svc_display_name_ = "Rattail : File Monitoring Service"
 

	
 
    appname = 'rattail'
rattail/gpc.py
Show inline comments
 
new file 100644
 
#!/usr/bin/env python
 
# -*- coding: utf-8  -*-
 
################################################################################
 
#
 
#  Rattail -- Retail Software Framework
 
#  Copyright © 2010-2012 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 Affero 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 Affero General Public License for
 
#  more details.
 
#
 
#  You should have received a copy of the GNU Affero General Public License
 
#  along with Rattail.  If not, see <http://www.gnu.org/licenses/>.
 
#
 
################################################################################
 

	
 
"""
 
``rattail.gpc`` -- Global Product Code
 
"""
 

	
 
from sqlalchemy import types
 

	
 
from rattail import barcodes
 

	
 

	
 
class GPC(object):
 
    """
 
    Class to abstract the details of Global Product Code data.  Examples of
 
    this would be UPC or EAN barcodes.
 

	
 
    The initial motivation for this class was to provide better SIL support.
 
    To that end, the instances are assumed to always be comprised of only
 
    numeric digits, and must include a check digit.  If you do not know the
 
    check digit, provide a ``calc_check_digit`` value to the constructor.
 
    """
 

	
 
    def __init__(self, value, calc_check_digit=False):
 
        """
 
        Constructor.  ``value`` must be either an integer or a long value, or a
 
        string containing only digits.
 

	
 
        If ``calc_check_digit`` is ``False``, then ``value`` is assumed to
 
        include the check digit.  If the value does not include a check digit
 
        and needs one to be calculated, then ``calc_check_digit`` should be a
 
        keyword signifying the algorithm to be used.
 

	
 
        Currently the only check digit algorithm keyword supported is
 
        ``'upc'``.  As that is likely to always be the default, a
 
        ``calc_check_digit`` value of ``True`` will be perceived as equivalent
 
        to ``'upc'``.
 
        """
 

	
 
        value = str(value)
 
        if calc_check_digit is True or calc_check_digit == 'upc':
 
            value += str(barcodes.upc_check_digit(value))
 
        self.value = int(value)
 

	
 
    def __cmp__(self, other):
 
        if int(self) < int(other):
 
            return -1
 
        if int(self) > int(other):
 
            return 1
 
        if int(self) == int(other):
 
            return 0
 
        assert False
 

	
 
    def __hash__(self):
 
        return hash(self.value)
 

	
 
    def __int__(self):
 
        return int(self.value)
 

	
 
    def __long__(self):
 
        return long(self.value)
 

	
 
    def __repr__(self):
 
        return "GPC('%014d')" % self.value
 

	
 
    def __str__(self):
 
        return str(unicode(self))
 

	
 
    def __unicode__(self):
 
        return u'%014d' % self.value
 

	
 

	
 
class GPCType(types.TypeDecorator):
 
    """
 
    SQLAlchemy type engine for GPC data.
 
    """
 

	
 
    impl = types.BigInteger
 

	
 
    def process_bind_param(self, value, dialect):
 
        if value is None:
 
            return None
 
        return int(value)
 

	
 
    def process_result_value(self, value, dialect):
 
        if value is None:
 
            return None
 
        return GPC(value)
rattail/labels.py
Show inline comments
 
@@ -29,18 +29,13 @@
 
import os
 
import os.path
 
import socket
 
import shutil
 
from cStringIO import StringIO
 

	
 
try:
 
    from collections import OrderedDict
 
except ImportError:
 
    from ordereddict import OrderedDict
 

	
 
import edbob
 
from edbob.util import requires_impl
 

	
 

	
 
_profiles = OrderedDict()
 
from rattail.exceptions import LabelPrintingError
 

	
 

	
 
class LabelPrinter(edbob.Object):
 
@@ -55,6 +50,7 @@ class LabelPrinter(edbob.Object):
 

	
 
    profile_name = None
 
    formatter = None
 
    required_settings = None
 

	
 
    @requires_impl()
 
    def print_labels(self, labels, *args, **kwargs):
 
@@ -73,6 +69,9 @@ class CommandFilePrinter(LabelPrinter):
 
    from there.
 
    """
 

	
 
    required_settings = {'output_dir': "Output Folder"}
 
    output_dir = None
 

	
 
    def batch_header_commands(self):
 
        """
 
        This method, if implemented, must return a sequence of string commands
 
@@ -91,7 +90,7 @@ class CommandFilePrinter(LabelPrinter):
 

	
 
        return None
 

	
 
    def print_labels(self, labels, output_dir=None):
 
    def print_labels(self, labels, output_dir=None, progress=None):
 
        """
 
        "Prints" ``labels`` by generating a command file in the output folder.
 
        The full path of the output file to which commands are written will be
 
@@ -103,28 +102,36 @@ class CommandFilePrinter(LabelPrinter):
 
        current (working) directory will be assumed.
 
        """
 

	
 
        if not output_dir and self.profile_name:
 
            output_dir = edbob.config.get('rattail.labels', '%s.output_dir' % self.profile_name)
 
        if not output_dir:
 
            output_dir = os.getcwd()
 
            output_dir = self.output_dir
 
        if not output_dir:
 
            raise LabelPrintingError("Printer does not have an output folder defined")
 

	
 
        fn = '%s_%s.labels' % (socket.gethostname(),
 
                               edbob.local_time().strftime('%Y-%m-%d_%H-%M-%S'))
 
        labels_path = os.path.join(output_dir, fn)
 
        labels_path = edbob.temp_path(prefix='rattail.', suffix='.labels')
 
        labels_file = open(labels_path, 'w')
 

	
 
        header = self.batch_header_commands()
 
        if header:
 
            labels_file.write('%s\n' % '\n'.join(header))
 

	
 
        labels_file.write(self.formatter.format_labels(labels))
 
        commands = self.formatter.format_labels(labels, progress=progress)
 
        if commands is None:
 
            labels_file.close()
 
            os.remove(labels_path)
 
            return None
 

	
 
        labels_file.write(commands)
 

	
 
        footer = self.batch_footer_commands()
 
        if footer:
 
            labels_file.write('%s\n' % '\n'.join(footer))
 

	
 
        labels_file.close()
 
        return labels_path
 
        fn = '%s_%s.labels' % (socket.gethostname(),
 
                               edbob.local_time().strftime('%Y-%m-%d_%H-%M-%S'))
 
        final_path = os.path.join(output_dir, fn)
 
        shutil.move(labels_path, final_path)
 
        return final_path
 

	
 

	
 
class LabelFormatter(edbob.Object):
 
@@ -149,11 +156,16 @@ class CommandFormatter(LabelFormatter):
 
    (textual) commands.
 
    """
 

	
 
    def format_labels(self, labels):
 
    def format_labels(self, labels, progress=None):
 
        prog = None
 
        if progress:
 
            prog = progress("Formatting labels", len(labels))
 

	
 
        fmt = StringIO()
 

	
 
        for product, quantity in labels:
 
            for i in range(quantity):
 
        cancel = False
 
        for i, (product, quantity) in enumerate(labels, 1):
 
            for j in range(quantity):
 
                header = self.label_header_commands()
 
                if header:
 
                    fmt.write('%s\n' % '\n'.join(header))
 
@@ -161,6 +173,16 @@ class CommandFormatter(LabelFormatter):
 
                footer = self.label_footer_commands()
 
                if footer:
 
                    fmt.write('%s\n' % '\n'.join(footer))
 
            if prog and not prog.update(i):
 
                cancel = True
 
                break
 

	
 
        if prog:
 
            prog.destroy()
 

	
 
        if cancel:
 
            fmt.close()
 
            return None
 

	
 
        val = fmt.getvalue()
 
        fmt.close()
 
@@ -238,68 +260,3 @@ class TwoUpCommandFormatter(CommandFormatter):
 
        val = fmt.getvalue()
 
        fmt.close()
 
        return val
 
    
 

	
 
class LabelProfile(edbob.Object):
 
    """
 
    Represents a label printing profile.  This abstraction is used to define
 
    not only the physical (or otherwise?) device to which label should be sent,
 
    but the label formatting specifics as well.
 
    """
 

	
 
    name = None
 
    display_name = None
 
    printer_factory = None
 
    formatter_factory = None
 
    format = None
 

	
 
    def get_formatter(self):
 
        if self.formatter_factory:
 
            return self.formatter_factory(format=self.format)
 
        return None
 

	
 
    def get_printer(self):
 
        if self.printer_factory:
 
            return self.printer_factory(
 
                profile_name=self.name,
 
                formatter=self.get_formatter())
 
        return None
 

	
 

	
 
def init(config):
 
    """
 
    Initializes the label printing system.
 

	
 
    This reads label profiles from config and caches the corresponding
 
    :class:`LabelProfile` instances in memory.
 
    """
 

	
 
    profiles = config.require('rattail.labels', 'profiles')
 
    profiles = profiles.split(',')
 
    for key in profiles:
 
        key = key.strip()
 
        profile = LabelProfile(name=key)
 
        profile.display_name = config.require('rattail.labels', '%s.display' % key)
 
        profile.printer_factory = edbob.load_spec(
 
            config.require('rattail.labels', '%s.printer' % key))
 
        profile.formatter_factory = edbob.load_spec(
 
            config.require('rattail.labels', '%s.formatter' % key))
 
        profile.format = config.get('rattail.labels', '%s.format' % key)
 
        _profiles[key] = profile
 

	
 

	
 
def get_profile(name):
 
    """
 
    Returns the :class:`LabelProfile` instance corresponding to ``name``.
 
    """
 

	
 
    return _profiles.get(name)
 

	
 

	
 
def iter_profiles():
 
    """
 
    Returns an iterator over the collection of :class:`LabelProfile` instances
 
    which were read and created from config.
 
    """
 

	
 
    return _profiles.itervalues()
rattail/sil.py
Show inline comments
 
deleted file
rattail/sil/__init__.py
Show inline comments
 
new file 100644
 
#!/usr/bin/env python
 
# -*- coding: utf-8  -*-
 
################################################################################
 
#
 
#  Rattail -- Retail Software Framework
 
#  Copyright © 2010-2012 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 Affero 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 Affero General Public License for
 
#  more details.
 
#
 
#  You should have received a copy of the GNU Affero General Public License
 
#  along with Rattail.  If not, see <http://www.gnu.org/licenses/>.
 
#
 
################################################################################
 

	
 
"""
 
``rattail.sil`` -- Standard Interchange Language
 

	
 
Please see the `Standard Interchange Language Specifications
 
<http://productcatalog.gs1us.org/Store/tabid/86/CategoryID/21/List/1/catpageindex/2/Level/a/ProductID/46/Default.aspx>`_
 
for more information.
 
"""
 

	
 
from rattail.sil.columns import *
 
from rattail.sil.batches import *
 
from rattail.sil.sqlalchemy import *
 
from rattail.sil.writer import *
rattail/sil/batches.py
Show inline comments
 
new file 100644
 
#!/usr/bin/env python
 
# -*- coding: utf-8  -*-
 
################################################################################
 
#
 
#  Rattail -- Retail Software Framework
 
#  Copyright © 2010-2012 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 Affero 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 Affero General Public License for
 
#  more details.
 
#
 
#  You should have received a copy of the GNU Affero General Public License
 
#  along with Rattail.  If not, see <http://www.gnu.org/licenses/>.
 
#
 
################################################################################
 

	
 
"""
 
``rattail.sil.batches`` -- Batch Stuff
 
"""
 

	
 
import edbob
 

	
 

	
 
__all__ = ['consume_batch_id']
 

	
 

	
 
def consume_batch_id(source='RATAIL'):
 
    """
 
    Returns the next available batch identifier for ``source``, incrementing
 
    the number to preserve uniqueness.
 
    """
 

	
 
    option = 'next_batch_id.%s' % source
 

	
 
    config = edbob.AppConfigParser('rattail')
 
    config_path = config.get_user_file('rattail.conf', create=True)
 
    config.read(config_path)
 

	
 
    batch_id = config.get('rattail.sil', option, default='')
 
    if not batch_id.isdigit():
 
        batch_id = '1'
 
    batch_id = int(batch_id)
 

	
 
    config.set('rattail.sil', option, str(batch_id + 1))
 
    config_file = open(config_path, 'w')
 
    config.write(config_file)
 
    config_file.close()
 
    return '%08u' % batch_id
rattail/sil/columns.py
Show inline comments
 
new file 100644
 
#!/usr/bin/env python
 
# -*- coding: utf-8  -*-
 
################################################################################
 
#
 
#  Rattail -- Retail Software Framework
 
#  Copyright © 2010-2012 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 Affero 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 Affero General Public License for
 
#  more details.
 
#
 
#  You should have received a copy of the GNU Affero General Public License
 
#  along with Rattail.  If not, see <http://www.gnu.org/licenses/>.
 
#
 
################################################################################
 

	
 
"""
 
``rattail.sil.columns`` -- SIL Columns
 
"""
 

	
 
import edbob
 

	
 
from rattail.sil.exceptions import SILColumnNotFound
 

	
 

	
 
__all__ = ['get_column']
 

	
 

	
 
supported_columns = {}
 

	
 

	
 
class SILColumn(edbob.Object):
 
    """
 
    Represents a column for use with SIL.
 
    """
 

	
 
    def __init__(self, name, data_type, description, display_name=None, **kwargs):
 
        edbob.Object.__init__(self, **kwargs)
 
        self.name = name
 
        self.data_type = data_type
 
        self.description = description
 
        self.display_name = display_name or description
 

	
 
    def __repr__(self):
 
        return "<SILColumn: %s>" % self.name
 

	
 
    def __unicode__(self):
 
        return unicode(self.name)
 

	
 

	
 
def update_supported_columns(columns):
 
    """
 
    Updates the global column collection with those natively supported by
 
    Rattail.
 
    """
 

	
 
    SC = SILColumn
 

	
 
    standard = [ # These columns are part of the SIL standard.
 

	
 
        # ITEM_DCT
 
        SC('F01',       'GPC(14)',      "Primary Item U.P.C. Number (Key)",     "UPC"),
 
        SC('F02',       'CHAR(20)',     "Descriptor",                           "Description"),
 
        SC('F04',       'NUMBER(4,0)',  "Sub-Department Number"),
 
        SC('F22',       'CHAR(30)',     "Size Description"),
 
        SC('F90',       'FLAG(1)',      "Authorized DSD Item"),
 
        SC('F94',       'NUMBER(2,0)',  "Shelf Tag Quantity"),
 
        SC('F95',       'CHAR(3)',      "Shelf Tag Type"),
 
        SC('F155',      'CHAR(30)',     "Brand"),
 

	
 
        # PRICE_DCT
 
        SC('F30',       'NUMBER(8,3)',  "Retail Sell Price"),
 
        SC('F31',       'NUMBER(3,0)',  "Price Multiple (Quantity/For)"),
 
        SC('F35',       'DATE(7)',      "Price Start Date"),
 
        SC('F36',       'TIME(4)',      "Price Start Time"),
 
        SC('F126',      'NUMBER(2,0)',  "Pricing Level"),
 
        SC('F129',      'DATE(7)',      "Price End Date"),
 
        SC('F130',      'TIME(4)',      "Price End Time"),
 
        SC('F135',      'NUMBER(3,0)',  "Sale Price Multiple (Quantity/For)"),
 
        SC('F136',      'NUMBER(8,3)',  "Sale Price"),
 
        SC('F137',      'DATE(7)',      "Sale Price Start Date"),
 
        SC('F138',      'TIME(4)',      "Sale Price End Date"),
 
        SC('F139',      'NUMBER(8,3)',  "Sale Package Price"),
 
        SC('F140',      'NUMBER(8,3)',  "Package Price"),
 
        SC('F142',      'NUMBER(3,0)',  "Package Price Multiple"),
 
        SC('F143',      'NUMBER(3,0)',  "Sale Package Price Multiple"),
 
        SC('F144',      'TIME(4)',      "Sale Price Start Time"),
 
        SC('F145',      'TIME(4)',      "Sale Price End Time"),
 
        SC('F181',      'NUMBER(8,3)',  "TPR (Temporary Price Reduction)"),
 
        SC('F182',      'NUMBER(3,0)',  "TPR Multiple (Quantity/For)"),
 
        SC('F183',      'DATE(7)',      "TPR Start Date"),
 
        SC('F184',      'DATE(7)',      "TPR End Date"),
 
        SC('F387',      'NUMBER(3)',    "Price Type Code"),
 

	
 
        # FCOST_DCT
 
        SC('F19',       'NUMBER(4,0)',  "Case Pack Size"),
 
        SC('F20',       'NUMBER(4,0)',  "Receiving Pack Size"),
 
        SC('F38',       'NUMBER(9,5)',  "Case Receiving Base Cost"),
 
        SC('F212',      'TIME(4)',      "Cost Change Time"),
 
        SC('F227',      'DATE(7)',      "Cost Change Date"),
 

	
 
        # DEPT_DCT
 
        SC('F03',       'NUMBER(4,0)',  "Department Number"),
 
        SC('F238',      'CHAR(30)',     "Department Description"),
 

	
 
        # VENDOR_DCT
 
        SC('F27',       'CHAR(9)',      "Vendor Number"),
 
        SC('F334',      'CHAR(20)',     "Vendor Name"),
 
        SC('F335',      'CHAR(20)',     "Vendor Contact Name"),
 
        SC('F341',      'NUMBER(10,0)', "Vendor Phone Number - Voice"),
 
        SC('F342',      'NUMBER(10,0)', "Vendor Phone Number - Fax"),
 
        ]
 

	
 
    custom = [ # These columns are Rattail-specific.
 

	
 
        SC('R38',       'NUMBER(9,5)',  "Unit Receiving Base Cost"),
 
        SC('R101',      'NUMBER(9,5)',  "Difference Amount",                    "Difference"),
 
        ]
 

	
 
    for column in standard + custom:
 
        columns[column.name] = column
 

	
 

	
 
def get_column(name):
 
    """
 
    Returns the :class:`SILColumn` instance named ``name``.
 
    """
 

	
 
    column = supported_columns.get(name)
 
    if not column:
 
        raise SILColumnNotFound(name)
 
    return column
 

	
 

	
 
def init(config):
 
    """
 
    Initializes the collection of supported SIL columns.
 
    """
 

	
 
    global supported_columns
 

	
 
    update_supported_columns(supported_columns)
rattail/sil/exceptions.py
Show inline comments
 
new file 100644
 
#!/usr/bin/env python
 
# -*- coding: utf-8  -*-
 
################################################################################
 
#
 
#  Rattail -- Retail Software Framework
 
#  Copyright © 2010-2012 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 Affero 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 Affero General Public License for
 
#  more details.
 
#
 
#  You should have received a copy of the GNU Affero General Public License
 
#  along with Rattail.  If not, see <http://www.gnu.org/licenses/>.
 
#
 
################################################################################
 

	
 
"""
 
``rattail.sil.exceptions`` -- SIL Exceptions
 
"""
 

	
 

	
 
class SILError(Exception):
 

	
 
    pass
 

	
 

	
 
class SILColumnNotFound(SILError):
 

	
 
    def __init__(self, name):
 
        self.name = name
 

	
 
    def __str__(self):
 
        return "SIL column not found: %s" % self.name
rattail/sil/sqlalchemy.py
Show inline comments
 
new file 100644
 
#!/usr/bin/env python
 
# -*- coding: utf-8  -*-
 
################################################################################
 
#
 
#  Rattail -- Retail Software Framework
 
#  Copyright © 2010-2012 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 Affero 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 Affero General Public License for
 
#  more details.
 
#
 
#  You should have received a copy of the GNU Affero General Public License
 
#  along with Rattail.  If not, see <http://www.gnu.org/licenses/>.
 
#
 
################################################################################
 

	
 
"""
 
``rattail.sil.sqlalchemy`` -- SQLAlchemy Utilities
 
"""
 

	
 
from __future__ import absolute_import
 

	
 
import re
 

	
 
from sqlalchemy import types
 

	
 
from rattail.gpc import GPCType
 

	
 

	
 
__all__ = ['get_sqlalchemy_type']
 

	
 

	
 
sil_type_pattern = re.compile(r'^(CHAR|NUMBER)\((\d+(?:\,\d+)?)\)$')
 

	
 

	
 
def get_sqlalchemy_type(sil_type):
 
    """
 
    Returns a SQLAlchemy data type according to a SIL data type.
 
    """
 

	
 
    if sil_type == 'GPC(14)':
 
        return GPCType
 

	
 
    if sil_type == 'FLAG(1)':
 
        return types.Boolean
 

	
 
    m = sil_type_pattern.match(sil_type)
 
    if m:
 
        data_type, precision = m.groups()
 
        if precision.isdigit():
 
            precision = int(precision)
 
            scale = 0
 
        else:
 
            precision, scale = precision.split(',')
 
            precision = int(precision)
 
            scale = int(scale)
 
        if data_type == 'CHAR':
 
            assert not scale, "FIXME"
 
            return types.String(precision)
 
        if data_type == 'NUMBER':
 
            return types.Numeric(precision, scale)
 

	
 
    assert False, "FIXME"
rattail/sil/writer.py
Show inline comments
 
new file 100644
 
#!/usr/bin/env python
 
# -*- coding: utf-8  -*-
 
################################################################################
 
#
 
#  Rattail -- Retail Software Framework
 
#  Copyright © 2010-2012 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 Affero 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 Affero General Public License for
 
#  more details.
 
#
 
#  You should have received a copy of the GNU Affero General Public License
 
#  along with Rattail.  If not, see <http://www.gnu.org/licenses/>.
 
#
 
################################################################################
 

	
 
"""
 
``rattail.sil.writer`` -- SIL Writer
 
"""
 

	
 
import datetime
 
from decimal import Decimal
 

	
 
import edbob
 

	
 
import rattail
 

	
 

	
 
__all__ = ['Writer']
 

	
 

	
 
class Writer(edbob.Object):
 

	
 
    def __init__(self, path, **kwargs):
 
        edbob.Object.__init__(self, **kwargs)
 
        self.sil_path = path
 
        self.fileobj = self.get_fileobj()
 

	
 
    def get_fileobj(self):
 
        return open(self.sil_path, 'w')
 

	
 
    def close(self):
 
        self.fileobj.close()
 

	
 
    def val(self, value):
 
        """
 
        Returns a string version of ``value``, suitable for inclusion within a
 
        data row of a SIL batch.  The conversion is done as follows:
 

	
 
        If ``value`` is ``None``, an empty string is returned.
 

	
 
        If it is an ``int`` or ``decimal.Decimal`` instance, it is converted
 
        directly to a string (i.e. not quoted).
 

	
 
        If it is a ``datetime.date`` instance, it will be formatted as
 
        ``'%Y%j'``.
 

	
 
        If it is a ``datetime.time`` instance, it will be formatted as
 
        ``'%H%M'``.
 

	
 
        Otherwise, it is converted to a string if necessary, and quoted with
 
        apostrophes escaped.
 
        """
 

	
 
        if value is None:
 
            return ''
 
        if isinstance(value, rattail.GPC):
 
            return str(value)
 
        if isinstance(value, int):
 
            return str(value)
 
        if isinstance(value, Decimal):
 
            return str(value)
 
        if isinstance(value, datetime.date):
 
            return value.strftime('%Y%j')
 
        if isinstance(value, datetime.time):
 
            return value.strftime('%H%M')
 
        if not isinstance(value, basestring):
 
            value = str(value)
 
        return "'%s'" % value.replace("'", "''")
 

	
 
    def write(self, string):
 
        self.fileobj.write(string)
 

	
 
    def write_batch_header_raw(self, **kwargs):
 
        """
 
        Writes a SIL batch header string.  All keyword arguments correspond to
 
        the SIL specification for the Batch Header Dictionary.
 

	
 
        **Batch Header Dictionary:**
 

	
 
        ====  ====    ====  ===========
 
        Name  Type    Size  Description
 
        ====  ====    ====  ===========
 
        H01   CHAR       2  Batch Type
 
        H02   CHAR       8  Batch Identifier
 
        H03   CHAR       6  Source Identifier
 
        H04   CHAR       6  Destination Identifier
 
        H05   CHAR      12  Audit File Name
 
        H06   CHAR      12  Response File Name
 
        H07   DATE       7  Origin Date
 
        H08   TIME       4  Origin Time
 
        H09   DATE       7  Execution (Apply) Date
 
        H10   DATE       4  Execution (Apply) Time
 
        H11   DATE       7  Purge Date
 
        H12   CHAR       6  Action Type
 
        H13   CHAR      50  Batch Description
 
        H14   CHAR      30  User Defined
 
        H15   CHAR      30  User Defined
 
        H16   CHAR      30  User Defined
 
        H17   NUMBER     1  Warning Level
 
        H18   NUMBER     5  Maximum Error Count
 
        H19   CHAR       7  SIL Level/Revision
 
        H20   CHAR       4  Software Revision
 
        H21   CHAR      50  Primary Key
 
        H22   CHAR     512  System Specific Command
 
        H23   CHAR       8  Dictionary Revision
 

	
 
        Consult the SIL Specification for more information.
 
        """
 

	
 
        kw = kwargs
 

	
 
        # Don't quote H09 if special "immediate" value.
 
        H09 = kw.get('H09')
 
        if H09 != '0000000':
 
            H09 = self.val(H09)
 

	
 
        # Don't quote H10 if special "immediate" value.
 
        H10 = kw.get('H10')
 
        if H10 != '0000':
 
            H10 = self.val(H10)
 

	
 
        row = [
 
            self.val(kw.get('H01')),
 
            self.val(kw.get('H02')),
 
            self.val(kw.get('H03')),
 
            self.val(kw.get('H04')),
 
            self.val(kw.get('H05')),
 
            self.val(kw.get('H06')),
 
            self.val(kw.get('H07')),
 
            self.val(kw.get('H08')),
 
            H09,
 
            H10,
 
            self.val(kw.get('H11')),
 
            self.val(kw.get('H12')),
 
            self.val(kw.get('H13')),
 
            self.val(kw.get('H14')),
 
            self.val(kw.get('H15')),
 
            self.val(kw.get('H16')),
 
            self.val(kw.get('H17')),
 
            self.val(kw.get('H18')),
 
            self.val(kw.get('H19')),
 
            self.val(kw.get('H20')),
 
            self.val(kw.get('H21')),
 
            self.val(kw.get('H22')),
 
            self.val(kw.get('H23')),
 
            ]
 

	
 
        self.fileobj.write('INSERT INTO HEADER_DCT VALUES\n')
 
        self.write_row(row, quote=False, last=True)
 
        self.fileobj.write('\n')
 

	
 
    def write_batch_header(self, **kwargs):
 
        """
 
        Convenience method to take some of the gruntwork out of writing batch
 
        headers.
 

	
 
        If you do not override ``H03`` (Source Identifier), then Rattail will
 
        provide a default value for it, as well as ``H20`` (Software Revision)
 
        - that is, unless you've supplied it yourself.
 

	
 
        If you do not provide values for ``H07`` or ``H08``, the current date
 
        and time will be assumed.
 

	
 
        If you do not provide values for ``H09`` or ``H10``, it is assumed that
 
        you wish the batch to be immediately executable.  Default values will
 
        be provided accordingly.
 

	
 
        If you do not provide a value for ``H11`` (Purge Date), a default of 90
 
        days from the current date will be assumed.
 
        """
 

	
 
        kw = kwargs
 

	
 
        # Provide default for H03 (Source Identifier) if none specified.
 
        if 'H03' not in kw:
 
            kw['H03'] = 'RATAIL'
 

	
 
            # Provide default for H20 (Software Revision) if none specified.
 
            if 'H20' not in kw:
 
                kw['H20'] = rattail.__version__[:4]
 

	
 
        # Provide default (current local time) values H07 and H08 (Origin Date /
 
        # Time) if none was specified.
 
        now = edbob.local_time()
 
        if 'H07' not in kw:
 
            kw['H07'] = now.date()
 
        if 'H08' not in kw:
 
            kw['H08'] = now.time()
 

	
 
        # Use special "immediate" values for H09 and H10 (Execution (Apply) Date /
 
        # Time) if none was specified.
 
        if 'H09' not in kw:
 
            kw['H09'] = '0000000'
 
        if 'H10' not in kw:
 
            kw['H10'] = '0000'
 

	
 
        # Provide default value for H11 (Purge Date) if none was specified.
 
        if 'H11' not in kw:
 
            kw['H11'] = (now + datetime.timedelta(days=90)).date()
 

	
 
        self.write_batch_header_raw(**kw)
 

	
 
    def write_create_header(self, **kwargs):
 
        """
 
        Convenience method to take some of the gruntwork out of writing batch
 
        headers.
 

	
 
        The following default values are provided by this method:
 

	
 
        * ``H01`` = ``'HC'``
 
        * ``H12`` = ``'LOAD'``
 

	
 
        This method also calls :meth:`write_batch_header()`; see its
 
        documentation for the other default values provided.
 
        """
 

	
 
        kw = kwargs
 
        kw.setdefault('H01', 'HC')
 
        kw.setdefault('H12', 'LOAD')
 
        self.write_batch_header(**kw)
 

	
 
    def write_maintenance_header(self, **kwargs):
 
        """
 
        Convenience method to take some of the gruntwork out of writing batch
 
        headers.
 

	
 
        The following default values are provided by this method:
 

	
 
        * ``H01`` = ``'HM'``
 

	
 
        This method also calls :meth:`write_batch_header()`; see its
 
        documentation for the other default values provided.
 
        """
 

	
 
        kw = kwargs
 
        kw.setdefault('H01', 'HM')
 
        self.write_batch_header(**kw)
 

	
 
    def write_row(self, row, quote=True, last=False):
 
        """
 
        Writes a SIL row string.
 

	
 
        ``row`` should be a sequence of values.
 

	
 
        If ``quote`` is ``True``, each value in ``row`` will be ran through the
 
        :func:`val()` function before being written.  If it is ``False``, the
 
        values are written as-is.
 

	
 
        If ``last`` is ``True``, then ``';'`` will be used as the statement
 
        terminator; otherwise ``','`` is used.
 
        """
 

	
 
        terminator = ';' if last else ','
 
        if quote:
 
            row = [self.val(x) for x in row]
 
        self.fileobj.write('(' + ','.join(row) + ')' + terminator + '\n')
 

	
 
    def write_rows(self, rows):
 
        """
 
        Writes a set of SIL row strings.
 

	
 
        ``rows`` should be a sequence of sequences, each of which should be
 
        suitable for use with :meth:`write_row()`.
 

	
 
        (This funcion primarily exists to handle the mundane task of setting
 
        the ``last`` flag when calling :meth:`write_row()`.)
 
        """
 

	
 
        last = len(rows) - 1
 
        for i, row in enumerate(rows):
 
            self.write_row(row, last=i == last)
setup.py
Show inline comments
 
@@ -114,5 +114,8 @@ rattail = rattail.db.extension:RattailExtension
 
[rattail.commands]
 
filemon = rattail.commands:FileMonitorCommand
 

	
 
[rattail.batches.providers]
 
print_labels = rattail.batches.providers.labels:PrintLabels
 

	
 
""",
 
    )
0 comments (0 inline, 0 general)