Changeset - 6166b6e425a1
[Not reviewed]
0 3 0
Lance Edgar (lance) - 21 months ago 2023-01-11 15:35:56
lance@edbob.org
Add support for per-item default discounts, for new custorder
3 files changed with 58 insertions and 3 deletions:
0 comments (0 inline, 0 general)
rattail/app.py
Show inline comments
 
# -*- coding: utf-8; -*-
 
################################################################################
 
#
 
#  Rattail -- Retail Software Framework
 
#  Copyright © 2010-2022 Lance Edgar
 
#  Copyright © 2010-2023 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.
 
@@ -20,24 +20,25 @@
 
#  Rattail.  If not, see <http://www.gnu.org/licenses/>.
 
#
 
################################################################################
 
"""
 
App Handler
 
"""
 

	
 
from __future__ import unicode_literals, absolute_import
 

	
 
import os
 
# import re
 
import datetime
 
import decimal
 
import os
 
import shutil
 
import socket
 
import tempfile
 
import warnings
 
import logging
 

	
 
import six
 
import humanize
 
from mako.template import Template
 

	
 
from rattail.util import (load_object, load_entry_points,
 
@@ -138,24 +139,44 @@ class AppHandler(object):
 
        Returns a configured time zone.
 

	
 
        Default logic invokes :func:`rattail.time.timezone()` to
 
        obtain the time zone object.
 

	
 
        :param key: Unique key designating which time zone should be
 
           returned.  Note that most apps have only one ("default"),
 
           but may have others defined.
 
        """
 
        from rattail.time import timezone
 
        return timezone(self.config, key)
 

	
 
    def json_friendly(self, value):
 
        """
 
        Coerce a Python value to one which is JSON-serializable.
 

	
 
        So, this does *not* return a JSON string, but rather a Python
 
        object which can then be safely converted via
 
        ``json.dumps()``.
 

	
 
        If the value is a container, it will be crawled recursively
 
        and all values it contains will be coerced.
 
        """
 
        if isinstance(value, dict):
 
            for key, val in six.iteritems(value):
 
                value[key] = self.json_friendly(val)
 

	
 
        elif isinstance(value, decimal.Decimal):
 
            value = float(value)
 

	
 
        return value
 

	
 
    def localtime(self, *args, **kwargs):
 
        """
 
        Produce or convert a timestamp in the default time zone.
 

	
 
        Default logic invokes :func:`rattail.time.localtime()` to
 
        obtain the timestamp.  All args and kwargs are passed directly
 
        to that function.
 

	
 
        :returns: A :class:`python:datetime.datetime` object.  Usually
 
           this will be timezone-aware but this will depend on the
 
           args and kwargs you specify.
 
        """
rattail/batch/custorder.py
Show inline comments
 
# -*- coding: utf-8; -*-
 
################################################################################
 
#
 
#  Rattail -- Retail Software Framework
 
#  Copyright © 2010-2022 Lance Edgar
 
#  Copyright © 2010-2023 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.
 
@@ -48,24 +48,28 @@ class CustomerOrderBatchHandler(BatchHandler):
 
    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__(self, config, **kwargs):
 
        super(CustomerOrderBatchHandler, self).__init__(config, **kwargs)
 
        self.custorder_handler = self.app.get_custorder_handler()
 

	
 
    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
 
@@ -125,24 +129,33 @@ class CustomerOrderBatchHandler(BatchHandler):
 
                                   'allow_unknown_product',
 
                                   default=False)
 

	
 
    def allow_item_discounts(self):
 
        """
 
        Returns boolean indicating whether per-item discounts are
 
        allowed.
 
        """
 
        return self.config.getbool('rattail.custorders',
 
                                   'allow_item_discounts',
 
                                   default=False)
 

	
 
    def allow_item_discounts_if_on_sale(self):
 
        """
 
        Returns boolean indicating whether per-item discounts are
 
        allowed when item is already on sale.
 
        """
 
        return self.config.getbool('rattail.custorders',
 
                                   'allow_item_discounts_if_on_sale',
 
                                   default=False)
 

	
 
    def allow_past_item_reorder(self):
 
        """
 
        Returns boolean indicating whether to expose past items for
 
        re-order.
 
        """
 
        return self.config.getbool('rattail.custorders',
 
                                   'allow_past_item_reorder',
 
                                   default=False)
 

	
 
    def product_price_may_be_questionable(self):
 
        """
 
        Returns a boolean indicating whether "any" product's price may
 
@@ -509,24 +522,25 @@ class CustomerOrderBatchHandler(BatchHandler):
 
            'brand_name': product.brand.name if product.brand else None,
 
            'description': product.description,
 
            'size': product.size,
 
            'full_description': product.full_description,
 
            'case_quantity': self.app.render_quantity(self.get_case_size_for_product(product)),
 
            'unit_price': float(product.regular_price.price) if product.regular_price and product.regular_price.price is not None else None,
 
            'unit_price_display': products.render_price(product.regular_price),
 
            'department_name': product.department.name if product.department else None,
 
            'vendor_name': vendor.name if vendor else None,
 
            'url': products.get_url(product),
 
            'image_url': products.get_image_url(product),
 
            'uom_choices': self.uom_choices_for_product(product),
 
            'default_item_discount': self.get_default_item_discount(product),
 
        }
 

	
 
        # TODO: this was somewhat copied from
 
        # tailbone.views.products.render_price() - should make it part
 
        # of the products handler instead?
 
        sale_price = None
 
        if not product.not_for_sale:
 
            sale_price = product.current_price
 
            if sale_price:
 
                if sale_price.price:
 
                    info['sale_price'] = float(sale_price.price)
 
                info['sale_price_display'] = products.render_price(sale_price)
 
@@ -596,24 +610,32 @@ class CustomerOrderBatchHandler(BatchHandler):
 

	
 
    def why_not_add_product(self, product, batch):
 
        """
 
        This method can inspect the given product, and batch, to
 
        determine if the product may be added to the batch as a new
 
        row.  Useful to e.g. prevent one customer from ordering too
 
        many things, etc.
 

	
 
        :returns: If there is a reason not to add the product, should
 
           return that reason as a string; otherwise ``None``.
 
        """
 

	
 
    def get_default_item_discount(self, product=None, **kwargs):
 
        """
 
        Returns default item discount available.  If product is given,
 
        the default may be specific to its department etc.
 
        """
 
        return self.custorder_handler.get_default_item_discount(
 
            product=product, **kwargs)
 

	
 
    def add_product(self, batch, product, order_quantity, order_uom,
 
                    **kwargs):
 
        """
 
        Add a new row to the batch, for the given product and order
 
        quantity.
 
        """
 
        row = self.make_row()
 
        row.item_entry = product.uuid
 
        row.product = product
 
        row.order_quantity = order_quantity
 
        row.order_uom = order_uom
 

	
rattail/custorders.py
Show inline comments
 
# -*- coding: utf-8; -*-
 
################################################################################
 
#
 
#  Rattail -- Retail Software Framework
 
#  Copyright © 2010-2021 Lance Edgar
 
#  Copyright © 2010-2023 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.
 
@@ -19,32 +19,44 @@
 
#  You should have received a copy of the GNU General Public License along with
 
#  Rattail.  If not, see <http://www.gnu.org/licenses/>.
 
#
 
################################################################################
 
"""
 
Customer Orders Handler
 

	
 
Please note this is different from the Customer Order Batch Handler.
 
"""
 

	
 
from __future__ import unicode_literals, absolute_import
 

	
 
import decimal
 

	
 
from rattail.app import GenericHandler
 

	
 

	
 
class CustomerOrderHandler(GenericHandler):
 
    """
 
    Base class and default implementation for customer order handlers.
 
    """
 

	
 
    def get_default_item_discount(self, product=None, **kwargs):
 
        """
 
        Returns default item discount available.  If product is given,
 
        the default may be specific to its department etc.
 
        """
 
        discount = self.config.get('rattail.custorders',
 
                                   'default_item_discount')
 
        if discount:
 
            return decimal.Decimal(discount)
 

	
 
    def resolve_person(self, pending, person, user, **kwargs):
 
        """
 
        Resolve a pending person for all customer orders.
 
        """
 
        for order in list(pending.custorder_records):
 
            order.person = person
 
            order.pending_customer = None
 
            for item in order.items:
 
                item.add_event(self.enum.CUSTORDER_ITEM_EVENT_CUSTOMER_RESOLVED,
 
                               user)
 

	
 
    def resolve_product(self, pending, product, user, **kwargs):
0 comments (0 inline, 0 general)