Exemple #1
0
    def create_refund(self, refund_data, created_by=None, supplier=None):
        """
        Create a refund if passed a list of refund line data.

        Refund line data is simply a list of dictionaries where
        each dictionary contains data for a particular refund line.

        Additionally, if the parent line is of `enum` type
        `OrderLineType.PRODUCT` and the `restock_products` boolean
        flag is set to `True`, the products will be restocked with the
        exact amount set in the order supplier's `quantity` field.

        :param refund_data: List of dicts containing refund data.
        :type refund_data: [dict]
        :param created_by: Refund creator's user instance, used for
                           adjusting supplier stock.
        :type created_by: django.contrib.auth.User|None
        """
        tax_module = taxing.get_tax_module()
        refund_lines = tax_module.create_refund_lines(self, supplier,
                                                      created_by, refund_data)

        self.cache_prices()
        self.save()
        self.update_shipping_status()
        self.update_payment_status()
        refund_created.send(sender=type(self),
                            order=self,
                            refund_lines=refund_lines)
Exemple #2
0
    def create_refund(self, refund_data, created_by=None):
        """
        Create a refund if passed a list of refund line data.

        Refund line data is simply a list of dictionaries where
        each dictionary contains data for a particular refund line.

        If refund line data includes a parent line, the refund is
        associated with that line and cannot exceed the line amount.

        Additionally, if the parent line is of enum type
        `OrderLineType.PRODUCT` and the `restock_products` boolean
        flag is set to `True`, the products will be restocked with the
        order's supplier the exact amount of the value of the `quantity`
        field.

        :param refund_data: List of dicts containing refund data.
        :type refund_data: [dict]
        :param created_by: Refund creator's user instance, used for
                           adjusting supplier stock.
        :type created_by: django.contrib.auth.User|None
        """
        index = self.lines.all().aggregate(models.Max("ordering"))["ordering__max"]
        zero = Money(0, self.currency)
        refund_lines = []
        for refund in refund_data:
            index += 1
            amount = refund.get("amount", zero)
            quantity = refund.get("quantity", 0)
            parent_line = refund.get("line")
            restock_products = refund.get("restock_products")

            # TODO: Also raise this if the sum amount of refunds exceeds total,
            #       order amount, and do so before creating any order lines
            self.cache_prices()
            if amount > self.taxful_total_price.amount:
                raise RefundExceedsAmountException

            unit_price = parent_line.base_unit_price.amount if parent_line else zero
            total_price = unit_price * quantity + amount

            refund_line = OrderLine.objects.create(
                text=_("Refund for %s" % parent_line.text) if parent_line else _("Manual refund"),
                order=self,
                type=OrderLineType.REFUND,
                parent_line=parent_line,
                ordering=index,
                base_unit_price_value=-total_price,
                quantity=1
            )
            refund_lines.append(refund_line)

            if parent_line and parent_line.type == OrderLineType.PRODUCT:
                product = parent_line.product
            else:
                product = None

            if restock_products and quantity and product and (product.stock_behavior == StockBehavior.STOCKED):
                parent_line.supplier.adjust_stock(product.id, quantity, created_by=created_by)

        self.cache_prices()
        self.save()
        refund_created.send(sender=type(self), order=self, refund_lines=refund_lines)
Exemple #3
0
    def create_refund(self, refund_data, created_by=None):
        """
        Create a refund if passed a list of refund line data.

        Refund line data is simply a list of dictionaries where
        each dictionary contains data for a particular refund line.

        Additionally, if the parent line is of enum type
        `OrderLineType.PRODUCT` and the `restock_products` boolean
        flag is set to `True`, the products will be restocked with the
        order's supplier the exact amount of the value of the `quantity`
        field.

        :param refund_data: List of dicts containing refund data.
        :type refund_data: [dict]
        :param created_by: Refund creator's user instance, used for
                           adjusting supplier stock.
        :type created_by: django.contrib.auth.User|None
        """
        index = self.lines.all().aggregate(models.Max("ordering"))["ordering__max"]
        zero = Money(0, self.currency)
        refund_lines = []
        total_refund_amount = zero
        order_total = self.taxful_total_price.amount
        product_summary = self.get_product_summary()
        for refund in refund_data:
            index += 1
            amount = refund.get("amount", zero)
            quantity = refund.get("quantity", 0)
            parent_line = refund.get("line")
            restock_products = refund.get("restock_products")
            refund_line = None

            assert parent_line
            assert quantity

            # ensure the amount to refund and the order line amount have the same signs
            if ((amount > zero and parent_line.taxful_price.amount < zero) or
               (amount < zero and parent_line.taxful_price.amount > zero)):
                raise InvalidRefundAmountException

            if abs(amount) > abs(parent_line.max_refundable_amount):
                raise RefundExceedsAmountException

            # If restocking products, calculate quantity of products to restock
            product = parent_line.product
            if (restock_products and quantity and product and (product.stock_behavior == StockBehavior.STOCKED)):
                from shuup.core.suppliers.enums import StockAdjustmentType
                # restock from the unshipped quantity first
                unshipped_quantity_to_restock = min(quantity, product_summary[product.pk]["unshipped"])
                shipped_quantity_to_restock = min(
                    quantity - unshipped_quantity_to_restock,
                    product_summary[product.pk]["ordered"] - product_summary[product.pk]["refunded"])

                if unshipped_quantity_to_restock > 0:
                    product_summary[product.pk]["unshipped"] -= unshipped_quantity_to_restock
                    parent_line.supplier.adjust_stock(
                        product.id,
                        unshipped_quantity_to_restock,
                        created_by=created_by,
                        type=StockAdjustmentType.RESTOCK_LOGICAL)
                if shipped_quantity_to_restock > 0:
                    parent_line.supplier.adjust_stock(
                        product.id,
                        shipped_quantity_to_restock,
                        created_by=created_by,
                        type=StockAdjustmentType.RESTOCK)
                product_summary[product.pk]["refunded"] += quantity

            base_amount = amount if self.prices_include_tax else amount / (1 + parent_line.tax_rate)
            refund_line = OrderLine.objects.create(
                text=_("Refund for %s" % parent_line.text),
                order=self,
                type=OrderLineType.REFUND,
                parent_line=parent_line,
                ordering=index,
                base_unit_price_value=-(base_amount / quantity),
                quantity=quantity,
            )
            for line_tax in parent_line.taxes.all():
                tax_base_amount = amount / (1 + line_tax.tax.rate)
                tax_amount = tax_base_amount * line_tax.tax.rate
                refund_line.taxes.create(
                    tax=line_tax.tax,
                    name=_("Refund for %s" % line_tax.name),
                    amount_value=-tax_amount,
                    base_amount_value=-tax_base_amount,
                    ordering=line_tax.ordering
                )

            # we round refund lines here since cache_prices does the same
            total_refund_amount += _round_price(refund_line.taxful_price.amount)
            refund_lines.append(refund_line)
        if abs(total_refund_amount) > order_total:
            raise RefundExceedsAmountException
        self.cache_prices()
        self.save()
        self.update_shipping_status()
        refund_created.send(sender=type(self), order=self, refund_lines=refund_lines)
Exemple #4
0
    def create_refund(self, refund_data, created_by=None):
        """
        Create a refund if passed a list of refund line data.

        Refund line data is simply a list of dictionaries where
        each dictionary contains data for a particular refund line.

        Additionally, if the parent line is of enum type
        `OrderLineType.PRODUCT` and the `restock_products` boolean
        flag is set to `True`, the products will be restocked with the
        order's supplier the exact amount of the value of the `quantity`
        field.

        :param refund_data: List of dicts containing refund data.
        :type refund_data: [dict]
        :param created_by: Refund creator's user instance, used for
                           adjusting supplier stock.
        :type created_by: django.contrib.auth.User|None
        """
        index = self.lines.all().aggregate(
            models.Max("ordering"))["ordering__max"]
        zero = Money(0, self.currency)
        refund_lines = []
        total_refund_amount = zero
        order_total = self.taxful_total_price.amount
        product_summary = self.get_product_summary()
        for refund in refund_data:
            index += 1
            amount = refund.get("amount", zero)
            quantity = refund.get("quantity", 0)
            parent_line = refund.get("line")
            restock_products = refund.get("restock_products")
            refund_line = None

            assert parent_line
            assert quantity

            # ensure the amount to refund and the order line amount have the same signs
            if ((amount > zero and parent_line.taxful_price.amount < zero) or
                (amount < zero and parent_line.taxful_price.amount > zero)):
                raise InvalidRefundAmountException

            if abs(amount) > abs(parent_line.max_refundable_amount):
                raise RefundExceedsAmountException

            # If restocking products, calculate quantity of products to restock
            product = parent_line.product
            if (restock_products and quantity and product
                    and (product.stock_behavior == StockBehavior.STOCKED)):
                from shuup.core.suppliers.enums import StockAdjustmentType
                # restock from the unshipped quantity first
                unshipped_quantity_to_restock = min(
                    quantity, product_summary[product.pk]["unshipped"])
                shipped_quantity_to_restock = min(
                    quantity - unshipped_quantity_to_restock,
                    product_summary[product.pk]["ordered"] -
                    product_summary[product.pk]["refunded"])

                if unshipped_quantity_to_restock > 0:
                    product_summary[product.pk][
                        "unshipped"] -= unshipped_quantity_to_restock
                    parent_line.supplier.adjust_stock(
                        product.id,
                        unshipped_quantity_to_restock,
                        created_by=created_by,
                        type=StockAdjustmentType.RESTOCK_LOGICAL)
                if shipped_quantity_to_restock > 0:
                    parent_line.supplier.adjust_stock(
                        product.id,
                        shipped_quantity_to_restock,
                        created_by=created_by,
                        type=StockAdjustmentType.RESTOCK)
                product_summary[product.pk]["refunded"] += quantity

            base_amount = amount if self.prices_include_tax else amount / (
                1 + parent_line.tax_rate)
            refund_line = OrderLine.objects.create(
                text=_("Refund for %s" % parent_line.text),
                order=self,
                type=OrderLineType.REFUND,
                parent_line=parent_line,
                ordering=index,
                base_unit_price_value=-(base_amount / quantity),
                quantity=quantity,
            )
            for line_tax in parent_line.taxes.all():
                tax_base_amount = amount / (1 + line_tax.tax.rate)
                tax_amount = tax_base_amount * line_tax.tax.rate
                refund_line.taxes.create(tax=line_tax.tax,
                                         name=_("Refund for %s" %
                                                line_tax.name),
                                         amount_value=-tax_amount,
                                         base_amount_value=-tax_base_amount,
                                         ordering=line_tax.ordering)

            total_refund_amount += refund_line.taxful_price.amount
            refund_lines.append(refund_line)
        if abs(total_refund_amount) > order_total:
            raise RefundExceedsAmountException
        self.cache_prices()
        self.save()
        self.update_shipping_status()
        refund_created.send(sender=type(self),
                            order=self,
                            refund_lines=refund_lines)
Exemple #5
0
    def create_refund(self, refund_data, created_by=None):
        """
        Create a refund if passed a list of refund line data.

        Refund line data is simply a list of dictionaries where
        each dictionary contains data for a particular refund line.

        Additionally, if the parent line is of enum type
        `OrderLineType.PRODUCT` and the `restock_products` boolean
        flag is set to `True`, the products will be restocked with the
        order's supplier the exact amount of the value of the `quantity`
        field.

        If data for a refund line includes both an amount and a
        quantity to refund, creates a separate refund line for each
        refund type.

        :param refund_data: List of dicts containing refund data.
        :type refund_data: [dict]
        :param created_by: Refund creator's user instance, used for
                           adjusting supplier stock.
        :type created_by: django.contrib.auth.User|None
        """
        index = self.lines.all().aggregate(models.Max("ordering"))["ordering__max"]
        zero = Money(0, self.currency)
        refund_lines = []
        for refund in refund_data:
            index += 1
            amount = refund.get("amount", zero)
            quantity = refund.get("quantity", 0)
            parent_line = refund.get("line")
            restock_products = refund.get("restock_products")
            refund_line = None
            percent_refunded = 0

            assert parent_line
            # TODO: Also raise this if the sum amount of refunds exceeds total,
            #       order amount, and do so before creating any order lines
            self.cache_prices()
            if amount > self.taxful_total_price.amount:
                raise RefundExceedsAmountException

            # If restocking products, calculate quantity of products to restock
            product = parent_line.product
            if (restock_products and quantity and product and (product.stock_behavior == StockBehavior.STOCKED)):
                from shuup.core.suppliers.enums import StockAdjustmentType

                shipped_quantity = parent_line.shipped_quantity
                refunded_quantity = parent_line.refunded_quantity
                restockable_quantity = shipped_quantity - refunded_quantity
                quantity_to_restock = min(quantity, restockable_quantity)

                if quantity_to_restock > 0:
                    parent_line.supplier.adjust_stock(
                        product.id, quantity_to_restock, created_by=created_by, type=StockAdjustmentType.RESTOCK)

            # If a quantity provided, add a separate refund line
            if quantity:
                unit_price = parent_line.discounted_unit_price.amount
                refund_line = OrderLine.objects.create(
                    text=_("Refund for %s" % parent_line.text),
                    order=self,
                    type=OrderLineType.QUANTITY_REFUND,
                    parent_line=parent_line,
                    ordering=index,
                    base_unit_price_value=-unit_price,
                    quantity=quantity,
                )
                percent_refunded = quantity / parent_line.quantity
                refund_lines.append(refund_line)

            # If amount is provided, add a separate refund line
            if amount:
                refund_line = OrderLine.objects.create(
                    text=_("Refund for %s" % parent_line.text),
                    order=self,
                    type=OrderLineType.AMOUNT_REFUND,
                    parent_line=parent_line,
                    ordering=index,
                    base_unit_price_value=-amount,
                    quantity=1
                )
                percent_refunded = amount / parent_line.price
                refund_lines.append(refund_line)

            if refund_line:
                for line_tax in parent_line.taxes.all():
                    refund_line.taxes.create(
                        tax=line_tax.tax,
                        name=_("Refund for %s" % line_tax.name),
                        amount_value=-line_tax.amount_value * percent_refunded,
                        base_amount_value=-line_tax.base_amount_value * percent_refunded,
                        ordering=line_tax.ordering
                    )

        self.cache_prices()
        self.save()
        refund_created.send(sender=type(self), order=self, refund_lines=refund_lines)