mirror of
https://github.com/inventree/InvenTree.git
synced 2026-07-22 06:33:03 +00:00
Add "discount" support to order line items
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
# Generated by Django 5.2.15 on 2026-07-14 02:18
|
||||
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("order", "0120_purchaseorder_tags_returnorder_tags_salesorder_tags_and_more"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="purchaseorderextraline",
|
||||
name="discount",
|
||||
field=models.DecimalField(
|
||||
decimal_places=2,
|
||||
default=0,
|
||||
help_text="Discount percentage applied to this line item (0-100)",
|
||||
max_digits=5,
|
||||
validators=[
|
||||
django.core.validators.MinValueValidator(0),
|
||||
django.core.validators.MaxValueValidator(100),
|
||||
],
|
||||
verbose_name="Discount",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="purchaseorderlineitem",
|
||||
name="discount",
|
||||
field=models.DecimalField(
|
||||
decimal_places=2,
|
||||
default=0,
|
||||
help_text="Discount percentage applied to this line item (0-100)",
|
||||
max_digits=5,
|
||||
validators=[
|
||||
django.core.validators.MinValueValidator(0),
|
||||
django.core.validators.MaxValueValidator(100),
|
||||
],
|
||||
verbose_name="Discount",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="returnorderextraline",
|
||||
name="discount",
|
||||
field=models.DecimalField(
|
||||
decimal_places=2,
|
||||
default=0,
|
||||
help_text="Discount percentage applied to this line item (0-100)",
|
||||
max_digits=5,
|
||||
validators=[
|
||||
django.core.validators.MinValueValidator(0),
|
||||
django.core.validators.MaxValueValidator(100),
|
||||
],
|
||||
verbose_name="Discount",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="returnorderlineitem",
|
||||
name="discount",
|
||||
field=models.DecimalField(
|
||||
decimal_places=2,
|
||||
default=0,
|
||||
help_text="Discount percentage applied to this line item (0-100)",
|
||||
max_digits=5,
|
||||
validators=[
|
||||
django.core.validators.MinValueValidator(0),
|
||||
django.core.validators.MaxValueValidator(100),
|
||||
],
|
||||
verbose_name="Discount",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="salesorderextraline",
|
||||
name="discount",
|
||||
field=models.DecimalField(
|
||||
decimal_places=2,
|
||||
default=0,
|
||||
help_text="Discount percentage applied to this line item (0-100)",
|
||||
max_digits=5,
|
||||
validators=[
|
||||
django.core.validators.MinValueValidator(0),
|
||||
django.core.validators.MaxValueValidator(100),
|
||||
],
|
||||
verbose_name="Discount",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="salesorderlineitem",
|
||||
name="discount",
|
||||
field=models.DecimalField(
|
||||
decimal_places=2,
|
||||
default=0,
|
||||
help_text="Discount percentage applied to this line item (0-100)",
|
||||
max_digits=5,
|
||||
validators=[
|
||||
django.core.validators.MinValueValidator(0),
|
||||
django.core.validators.MaxValueValidator(100),
|
||||
],
|
||||
verbose_name="Discount",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="transferorderlineitem",
|
||||
name="discount",
|
||||
field=models.DecimalField(
|
||||
decimal_places=2,
|
||||
default=0,
|
||||
help_text="Discount percentage applied to this line item (0-100)",
|
||||
max_digits=5,
|
||||
validators=[
|
||||
django.core.validators.MinValueValidator(0),
|
||||
django.core.validators.MaxValueValidator(100),
|
||||
],
|
||||
verbose_name="Discount",
|
||||
),
|
||||
),
|
||||
]
|
||||
@@ -5,7 +5,7 @@ from typing import Any, Optional, TypedDict
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.core.validators import MinValueValidator
|
||||
from django.core.validators import MaxValueValidator, MinValueValidator
|
||||
from django.db import models, transaction
|
||||
from django.db.models import F, Q, QuerySet, Sum
|
||||
from django.db.models.functions import Coalesce
|
||||
@@ -157,7 +157,11 @@ class TotalPriceMixin(models.Model):
|
||||
continue
|
||||
|
||||
try:
|
||||
total += line.quantity * convert_money(line.price, target_currency)
|
||||
total += (
|
||||
line.quantity
|
||||
* convert_money(line.price, target_currency)
|
||||
* (1 - line.discount / 100)
|
||||
)
|
||||
except MissingRate:
|
||||
log_error('order.calculate_total_price')
|
||||
logger.exception("Missing exchange rate for '%s'", target_currency)
|
||||
@@ -171,7 +175,11 @@ class TotalPriceMixin(models.Model):
|
||||
continue
|
||||
|
||||
try:
|
||||
total += line.quantity * convert_money(line.price, target_currency)
|
||||
total += (
|
||||
line.quantity
|
||||
* convert_money(line.price, target_currency)
|
||||
* (1 - line.discount / 100)
|
||||
)
|
||||
except MissingRate:
|
||||
# Record the error, try to press on
|
||||
|
||||
@@ -2109,11 +2117,20 @@ class OrderLineItem(InvenTree.models.InvenTreeMetadataModel):
|
||||
validators=[MinValueValidator(0)],
|
||||
)
|
||||
|
||||
discount = models.DecimalField(
|
||||
verbose_name=_('Discount'),
|
||||
help_text=_('Discount percentage applied to this line item (0-100)'),
|
||||
default=0,
|
||||
max_digits=5,
|
||||
decimal_places=2,
|
||||
validators=[MinValueValidator(0), MaxValueValidator(100)],
|
||||
)
|
||||
|
||||
@property
|
||||
def total_line_price(self):
|
||||
"""Return the total price for this line item."""
|
||||
"""Return the total price for this line item, after any discount is applied."""
|
||||
if self.price:
|
||||
return self.quantity * self.price
|
||||
return self.quantity * self.price * (1 - self.discount / 100)
|
||||
|
||||
line = models.CharField(
|
||||
max_length=20,
|
||||
|
||||
@@ -305,6 +305,8 @@ class AbstractLineItemSerializer(FilterableSerializerMixin, serializers.Serializ
|
||||
required=False, allow_null=True, label=_('Target Date')
|
||||
)
|
||||
|
||||
discount = InvenTreeDecimalField(required=False)
|
||||
|
||||
project_code_label = common.filters.enable_project_label_filter()
|
||||
|
||||
project_code_detail = common.filters.enable_project_code_filter()
|
||||
@@ -322,6 +324,7 @@ class AbstractExtraLineSerializer(
|
||||
'pk',
|
||||
'line',
|
||||
'description',
|
||||
'discount',
|
||||
'link',
|
||||
'notes',
|
||||
'order',
|
||||
@@ -340,6 +343,8 @@ class AbstractExtraLineSerializer(
|
||||
|
||||
quantity = serializers.FloatField()
|
||||
|
||||
discount = InvenTreeDecimalField(required=False)
|
||||
|
||||
price = InvenTreeMoneySerializer(allow_null=True)
|
||||
|
||||
price_currency = InvenTreeCurrencySerializer()
|
||||
@@ -355,6 +360,7 @@ class AbstractExtraLineMeta:
|
||||
fields = [
|
||||
'pk',
|
||||
'description',
|
||||
'discount',
|
||||
'quantity',
|
||||
'reference',
|
||||
'notes',
|
||||
@@ -558,6 +564,7 @@ class PurchaseOrderLineItemSerializer(
|
||||
fields = AbstractLineItemSerializer.line_fields([
|
||||
'part',
|
||||
'build_order',
|
||||
'discount',
|
||||
'overdue',
|
||||
'received',
|
||||
'purchase_price',
|
||||
@@ -604,7 +611,10 @@ class PurchaseOrderLineItemSerializer(
|
||||
|
||||
queryset = queryset.annotate(
|
||||
total_price=ExpressionWrapper(
|
||||
F('purchase_price') * F('quantity'), output_field=models.DecimalField()
|
||||
F('purchase_price')
|
||||
* F('quantity')
|
||||
* (1 - F('discount') / Decimal(100)),
|
||||
output_field=models.DecimalField(),
|
||||
)
|
||||
)
|
||||
|
||||
@@ -1208,6 +1218,7 @@ class SalesOrderLineItemSerializer(
|
||||
fields = AbstractLineItemSerializer.line_fields([
|
||||
'allocated',
|
||||
'customer_detail',
|
||||
'discount',
|
||||
'overdue',
|
||||
'part',
|
||||
'part_detail',
|
||||
@@ -2305,6 +2316,7 @@ class ReturnOrderLineItemSerializer(
|
||||
'item',
|
||||
'received_date',
|
||||
'outcome',
|
||||
'discount',
|
||||
'price',
|
||||
'price_currency',
|
||||
# Filterable detail fields
|
||||
|
||||
Reference in New Issue
Block a user