Changeset - 331828e9ced1
[Not reviewed]
0 3 0
Lance Edgar (lance) - 3 years ago 2021-11-06 20:31:02
lance@edbob.org
Add support for finding past items, for new custorder

also misc. tweaks for new custorder feature
3 files changed with 46 insertions and 3 deletions:
0 comments (0 inline, 0 general)
rattail/app.py
Show inline comments
 
@@ -98,48 +98,52 @@ class AppHandler(object):
 

	
 
           Note that each key is still a simple string though, and that
 
           must be "unique" in the sense that only one autocompleter
 
           can be configured for each key.
 

	
 
        :returns: An :class:`~rattail.autocomplete.base.Autocompleter`
 
           instance if found, otherwise ``None``.
 
        """
 
        spec = self.config.get('rattail', 'autocomplete.{}'.format(key))
 
        if not spec:
 
            spec = self.default_autocompleters.get(key)
 
        if spec:
 
            return load_object(spec)(self.config)
 

	
 
        raise NotImplementedError("cannot locate autocompleter for key: {}".format(key))
 

	
 
    def get_auth_handler(self, **kwargs):
 
        if not hasattr(self, 'auth_handler'):
 
            spec = self.config.get('rattail', 'auth.handler',
 
                                   default='rattail.auth:AuthHandler')
 
            factory = load_object(spec)
 
            self.auth_handler = factory(self.config, **kwargs)
 
        return self.auth_handler
 

	
 
    def get_batch_handler(self, key, **kwargs):
 
        from rattail.batch import get_batch_handler
 
        return get_batch_handler(self.config, key, **kwargs)
 

	
 
    def get_board_handler(self, **kwargs):
 
        if not hasattr(self, 'board_handler'):
 
            from rattail.board import get_board_handler
 
            self.board_handler = get_board_handler(self.config, **kwargs)
 
        return self.board_handler
 

	
 
    def get_clientele_handler(self, **kwargs):
 
        if not hasattr(self, 'clientele_handler'):
 
            from rattail.clientele import get_clientele_handler
 
            self.clientele_handler = get_clientele_handler(self.config, **kwargs)
 
        return self.clientele_handler
 

	
 
    def get_employment_handler(self, **kwargs):
 
        if not hasattr(self, 'employment_handler'):
 
            from rattail.employment import get_employment_handler
 
            self.employment_handler = get_employment_handler(self.config, **kwargs)
 
        return self.employment_handler
 

	
 
    def get_feature_handler(self, **kwargs):
 
        if not hasattr(self, 'feature_handler'):
 
            from rattail.features import FeatureHandler
 
            self.feature_handler = FeatureHandler(self.config, **kwargs)
 
        return self.feature_handler
 

	
rattail/batch/custorder.py
Show inline comments
 
@@ -14,48 +14,49 @@
 
#  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 decimal
 

	
 
import six
 
import sqlalchemy as sa
 
from sqlalchemy import orm
 

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

	
 

	
 
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)
 
@@ -389,84 +390,114 @@ class CustomerOrderBatchHandler(BatchHandler):
 
                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 get_past_orders(self, batch, **kwargs):
 
        """
 
        Retrieve a list of past orders for the batch contact.
 
        """
 
        session = self.app.get_session(batch)
 
        model = self.model
 
        orders = session.query(model.CustomerOrder)
 

	
 
        contact = self.get_contact(batch)
 
        if isinstance(contact, model.Customer):
 
            orders = orders.filter(model.CustomerOrder.customer == contact)
 
        else:
 
            orders = orders.filter(model.CustomerOrder.person == contact)
 

	
 
        orders = orders.order_by(model.CustomerOrder.created.desc())
 
        return orders.all()
 

	
 
    def get_past_products(self, batch, **kwargs):
 
        """
 
        Should return a (possibly empty) list of products which have
 
        been ordered in the past by the customer who is associated
 
        with the given batch.
 
        """
 
        # TODO: should crawl the rattail order history here
 
        return []
 
        session = self.app.get_session(batch)
 
        model = self.model
 
        products = OrderedDict()
 

	
 
        # track down all order items for batch contact
 
        orders = self.get_past_orders(batch)
 
        for order in orders:
 
            for item in order.items:
 
                product = item.product
 
                if product:
 
                    # we only want the first match for each product
 
                    products.setdefault(product.uuid, product)
 

	
 
        return list(products.values())
 

	
 
    def get_product_info(self, batch, product, **kwargs):
 
        """
 
        Return a data dict containing misc. info pertaining to the
 
        given product, for the order batch.
 
        """
 
        products = self.app.get_products_handler()
 
        vendor = product.cost.vendor if product.cost else None
 
        info = {
 
            'uuid': product.uuid,
 
            'upc': six.text_type(product.upc),
 
            'upc_pretty': product.upc.pretty(),
 
            'brand_name': product.brand.name if product.brand else None,
 
            'description': product.description,
 
            'size': product.size,
 
            'full_description': product.full_description,
 
            'case_quantity': self.app.render_quantity(self.get_case_size_for_product(product)),
 
            'unit_price_display': products.render_price(product.regular_price),
 
            'department_name': product.department.name if product.department else None,
 
            'vendor_name': vendor.name if vendor else None,
 
            'url': products.get_url(product),
 
            'image_url': products.get_image_url(product),
 
            'uom_choices': self.uom_choices_for_product(product),
 
        }
 

	
 
        case_price = None
 
        if product.regular_price and product.regular_price is not None:
 
            case_size = self.get_case_size_for_product(product)
 
            case_price = case_size * product.regular_price.price
 
            case_price = (case_size or 1) * product.regular_price.price
 
            case_price = case_price.quantize(decimal.Decimal('0.01'))
 
        info['case_price'] = six.text_type(case_price) if case_price is not None else None
 
        info['case_price_display'] = self.app.render_currency(case_price)
 

	
 
        key = self.config.product_key()
 
        if key == 'upc':
 
            info['key'] = info['upc_pretty']
 
        else:
 
            info['key'] = getattr(product, key, info['upc_pretty'])
 

	
 
        return info
 

	
 
    def uom_choices_for_product(self, product):
 
        """
 
        Return a list of UOM choices for the given product.
 
        """
 
        choices = []
 

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

	
rattail/products.py
Show inline comments
 
@@ -159,48 +159,56 @@ class ProductsHandler(GenericHandler):
 
        if not only or 'scancode' in only:
 
            product = get_product_by_scancode(session, value)
 
            if product:
 
                products.append(('scancode', product))
 

	
 
        # maybe look for 'altcode' match
 
        if not only or 'altcode' in only:
 
            product = get_product_by_code(session, value)
 
            if product:
 
                products.append(('altcode', product))
 

	
 
        # maybe look for 'sku' match
 
        if not only or 'sku' in only:
 
            product = get_product_by_vendor_code(session, value,
 
                                                 vendor=vendor)
 
            if product:
 
                products.append(('sku', product))
 

	
 
        # maybe strip keys out of the result
 
        if not include_keys:
 
            products = [tup[1] for tup in products]
 

	
 
        return products
 

	
 
    def get_url(self, product, **kwargs):
 
        """
 
        Return the Tailbone "view" URL for the given product.
 
        """
 
        base_url = self.config.base_url()
 
        if base_url:
 
            return '{}/products/{}'.format(base_url, product.uuid)
 

	
 
    def get_image_url(self, product=None, upc=None, **kwargs):
 
        """
 
        Return the preferred image URL for the given UPC or product.
 
        """
 
        base_url = self.config.base_url()
 

	
 
        # we prefer the "image on file" if available
 
        if base_url and product and product.image:
 
            return '{}/products/{}/image'.format(base_url, product.uuid)
 

	
 
        # and if this product is a pack item, then we prefer the unit
 
        # item image as fallback, if available
 
        if base_url and product and product.is_pack_item():
 
            unit = product.unit
 
            if unit and unit.image:
 
                return '{}/products/{}/image'.format(base_url, unit.uuid)
 

	
 
        # fallback to the POD image, if available and so configured
 
        if self.config.getbool('tailbone', 'products.show_pod_image',
 
                               default=False):
 
            if product and not upc:
 
                upc = product.upc
 
            if upc:
 
                return self.get_pod_image_url(upc)
0 comments (0 inline, 0 general)