<commit_before># -*- coding: utf-8 -*-
# This file is part of Shuup.
#
# Copyright (c) 2012-2017, Shoop Commerce Ltd. All rights reserved.
#
# This source code is licensed under the OSL-3.0 license found in the
# LICENSE file in the root directory of this source tree.
from __future__ import unicode_literals

import six
from django.contrib import messages

from shuup.core.models import Product, Supplier
from shuup.utils.numbers import parse_decimal_string


class BasketUpdateMethods(object):
    def __init__(self, request, basket):
        self.request = request
        self.basket = basket

    def get_prefix_to_method_map(self):
        """Override this method to link prefixes with their associated methods to call.

        Format of the dictionary is: { FIELD_NAME_PREFIX: METHOD }.

        METHOD is a function which accepts the keyword arguments given in `update_basket_contents`. It should perform
        the necessary changes to the basket_line and then return whether the value had changed or not.
        (See `update_quantity` or `delete_line` for examples.)
        """

        return {
            'q_': self.update_quantity,
            'delete_': self.delete_line,
        }

    def delete_line(self, line, **kwargs):
        if line:
            return self.basket.delete_line(line["line_id"])

    def _get_orderability_errors(self, product, supplier, delta):
        basket_quantities = self.basket.get_product_ids_and_quantities()
        product_basket_quantity = basket_quantities.get(product.id, 0)
        total_product_quantity = delta + product_basket_quantity
        shop_product = product.get_shop_instance(shop=self.request.shop)
        errors = list(shop_product.get_orderability_errors(
            supplier=supplier,
            customer=self.basket.customer,
            quantity=total_product_quantity
        ))
        if product.is_package_parent():
            for child_product, child_quantity in six.iteritems(product.get_package_child_to_quantity_map()):
                child_basket_quantity = basket_quantities.get(child_product.id, 0)
                total_child_quantity = (delta * child_quantity) + child_basket_quantity
                shop_product = child_product.get_shop_instance(shop=self.request.shop)
                child_errors = list(shop_product.get_orderability_errors(
                    supplier=supplier,
                    customer=self.basket.customer,
                    quantity=total_child_quantity
                ))
                errors.extend(child_errors)
        return errors

    def update_quantity(self, line, value, **kwargs):
        new_quantity = int(parse_decimal_string(value))  # TODO: The quantity could be a non-integral value
        if new_quantity is None:
            return False

        if not (line and line["quantity"] != new_quantity):
            return False

        changed = False

        # Ensure sub-lines also get changed accordingly
        linked_lines = [line] + list(self.basket.find_lines_by_parent_line_id(line["line_id"]))
        orderable_line_ids = [basket_line.line_id for basket_line in self.basket.get_lines()]

        for linked_line in linked_lines:
            if linked_line["line_id"] not in orderable_line_ids:
                # Customer can change quantity in non-orderable lines regardless
                linked_line["quantity"] = new_quantity
                changed = True<commit_msg>Basket: Fix issue with partial quantities<commit_after># -*- coding: utf-8 -*-
# This file is part of Shuup.
#
# Copyright (c) 2012-2017, Shoop Commerce Ltd. All rights reserved.
#
# This source code is licensed under the OSL-3.0 license found in the
# LICENSE file in the root directory of this source tree.
from __future__ import unicode_literals

import six
from django.contrib import messages

from shuup.core.models import Product, Supplier
from shuup.utils.numbers import parse_decimal_string


class BasketUpdateMethods(object):
    def __init__(self, request, basket):
        self.request = request
        self.basket = basket

    def get_prefix_to_method_map(self):
        """Override this method to link prefixes with their associated methods to call.

        Format of the dictionary is: { FIELD_NAME_PREFIX: METHOD }.

        METHOD is a function which accepts the keyword arguments given in `update_basket_contents`. It should perform
        the necessary changes to the basket_line and then return whether the value had changed or not.
        (See `update_quantity` or `delete_line` for examples.)
        """

        return {
            'q_': self.update_quantity,
            'delete_': self.delete_line,
        }

    def delete_line(self, line, **kwargs):
        if line:
            return self.basket.delete_line(line["line_id"])

    def _get_orderability_errors(self, product, supplier, delta):
        basket_quantities = self.basket.get_product_ids_and_quantities()
        product_basket_quantity = basket_quantities.get(product.id, 0)
        total_product_quantity = delta + product_basket_quantity
        shop_product = product.get_shop_instance(shop=self.request.shop)
        errors = list(shop_product.get_orderability_errors(
            supplier=supplier,
            customer=self.basket.customer,
            quantity=total_product_quantity
        ))
        if product.is_package_parent():
            for child_product, child_quantity in six.iteritems(product.get_package_child_to_quantity_map()):
                child_basket_quantity = basket_quantities.get(child_product.id, 0)
                total_child_quantity = (delta * child_quantity) + child_basket_quantity
                shop_product = child_product.get_shop_instance(shop=self.request.shop)
                child_errors = list(shop_product.get_orderability_errors(
                    supplier=supplier,
                    customer=self.basket.customer,
                    quantity=total_child_quantity
                ))
                errors.extend(child_errors)
        return errors

    def update_quantity(self, line, value, **kwargs):
        new_quantity = parse_decimal_string(value)
        if new_quantity is None:
            return False

        if not (line and line["quantity"] != new_quantity):
            return False

        changed = False

        # Ensure sub-lines also get changed accordingly
        linked_lines = [line] + list(self.basket.find_lines_by_parent_line_id(line["line_id"]))
        orderable_line_ids = [basket_line.line_id for basket_line in self.basket.get_lines()]

        for linked_line in linked_lines:
            if linked_line["line_id"] not in orderable_line_ids:
                # Customer can change quantity in non-orderable lines regardless
                linked_line["quantity"] = new_quantity
                changed = True
{'fraction_non_alphanumeric': 0.0726101595666813, 'fraction_numerical': 0.0038061777192211974, 'mean_word_length': 2.7069994574064027, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3852032', 'n_tokens_mistral': 1920, 'n_tokens_neox': 1814, 'n_words': 498}