@@ -44,11 +44,15 @@ this repository contains the full copyright notices and license terms. -->
|
|||||||
<label name="state"/>
|
<label name="state"/>
|
||||||
<field name="state"/>
|
<field name="state"/>
|
||||||
</group>
|
</group>
|
||||||
<group col="2" colspan="2" id="amount" yfill="1">
|
<group col="-1" colspan="2" id="amount" yfill="1">
|
||||||
<label name="untaxed_amount" xalign="1.0" xexpand="1"/>
|
<label name="untaxed_amount" xalign="1.0" xexpand="1"/>
|
||||||
<field name="untaxed_amount" xalign="1.0" xexpand="0"/>
|
<field name="untaxed_amount" xalign="1.0" xexpand="0"/>
|
||||||
|
<newline/>
|
||||||
|
|
||||||
<label name="tax_amount" xalign="1.0" xexpand="1"/>
|
<label name="tax_amount" xalign="1.0" xexpand="1"/>
|
||||||
<field name="tax_amount" xalign="1.0" xexpand="0"/>
|
<field name="tax_amount" xalign="1.0" xexpand="0"/>
|
||||||
|
<newline/>
|
||||||
|
|
||||||
<label name="total_amount" xalign="1.0" xexpand="1"/>
|
<label name="total_amount" xalign="1.0" xexpand="1"/>
|
||||||
<field name="total_amount" xalign="1.0" xexpand="0"/>
|
<field name="total_amount" xalign="1.0" xexpand="0"/>
|
||||||
</group>
|
</group>
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
* Add original amounts before promotion
|
||||||
|
|
||||||
Version 7.4.0 - 2024-11-04
|
Version 7.4.0 - 2024-11-04
|
||||||
--------------------------
|
--------------------------
|
||||||
|
|||||||
+113
-21
@@ -22,6 +22,44 @@ from .exceptions import FormulaError
|
|||||||
class Sale(metaclass=PoolMeta):
|
class Sale(metaclass=PoolMeta):
|
||||||
__name__ = 'sale.sale'
|
__name__ = 'sale.sale'
|
||||||
|
|
||||||
|
original_untaxed_amount = fields.Function(Monetary(
|
||||||
|
"Original Untaxed", digits='currency', currency='currency',
|
||||||
|
states={
|
||||||
|
'invisible': (
|
||||||
|
~Eval('state').in_(['draft', 'quotation'])
|
||||||
|
| (Eval('original_untaxed_amount')
|
||||||
|
== Eval('untaxed_amount'))),
|
||||||
|
}),
|
||||||
|
'get_original_amount')
|
||||||
|
original_tax_amount = fields.Function(Monetary(
|
||||||
|
"Original Tax", digits='currency', currency='currency',
|
||||||
|
states={
|
||||||
|
'invisible': (
|
||||||
|
~Eval('state').in_(['draft', 'quotation'])
|
||||||
|
| (Eval('original_tax_amount') == Eval('tax_amount'))),
|
||||||
|
}),
|
||||||
|
'get_original_amount')
|
||||||
|
original_total_amount = fields.Function(Monetary(
|
||||||
|
"Original Total", digits='currency', currency='currency',
|
||||||
|
states={
|
||||||
|
'invisible': (
|
||||||
|
~Eval('state').in_(['draft', 'quotation'])
|
||||||
|
| (Eval('original_total_amount') == Eval('total_amount'))),
|
||||||
|
}),
|
||||||
|
'get_original_amount')
|
||||||
|
|
||||||
|
def get_original_amount(self, names):
|
||||||
|
amounts = dict.fromkeys(names)
|
||||||
|
if self.state in {'draft', 'quotation'}:
|
||||||
|
amounts['original_untaxed_amount'] = sum(
|
||||||
|
(line.original_amount for line in self.line_lines), Decimal(0))
|
||||||
|
if {'original_tax_amount', 'original_total_amount'} & set(names):
|
||||||
|
with Transaction().set_context(_original_amount=True):
|
||||||
|
amounts['original_tax_amount'] = self.get_tax_amount()
|
||||||
|
amounts['original_total_amount'] = sum(
|
||||||
|
filter(None, amounts.values()))
|
||||||
|
return amounts
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ModelView.button
|
@ModelView.button
|
||||||
@Workflow.transition('draft')
|
@Workflow.transition('draft')
|
||||||
@@ -29,19 +67,7 @@ class Sale(metaclass=PoolMeta):
|
|||||||
super(Sale, cls).draft(sales)
|
super(Sale, cls).draft(sales)
|
||||||
# Reset to draft unit price
|
# Reset to draft unit price
|
||||||
for sale in sales:
|
for sale in sales:
|
||||||
changed = False
|
sale.unapply_promotion()
|
||||||
for line in sale.lines:
|
|
||||||
if line.type != 'line':
|
|
||||||
continue
|
|
||||||
if line.draft_unit_price is not None:
|
|
||||||
line.unit_price = line.draft_unit_price
|
|
||||||
line.draft_unit_price = None
|
|
||||||
changed = True
|
|
||||||
if line.promotion:
|
|
||||||
line.promotion = None
|
|
||||||
changed = True
|
|
||||||
if changed:
|
|
||||||
sale.lines = sale.lines # Trigger changes
|
|
||||||
cls.save(sales)
|
cls.save(sales)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -51,19 +77,34 @@ class Sale(metaclass=PoolMeta):
|
|||||||
super(Sale, cls).quote(sales)
|
super(Sale, cls).quote(sales)
|
||||||
# Store draft unit price before changing it
|
# Store draft unit price before changing it
|
||||||
for sale in sales:
|
for sale in sales:
|
||||||
for line in sale.lines:
|
|
||||||
if line.type != 'line':
|
|
||||||
continue
|
|
||||||
if line.draft_unit_price is None:
|
|
||||||
line.draft_unit_price = line.unit_price
|
|
||||||
sale.apply_promotion()
|
sale.apply_promotion()
|
||||||
cls.save(sales)
|
cls.save(sales)
|
||||||
|
|
||||||
|
def unapply_promotion(self):
|
||||||
|
"Unapply promotion"
|
||||||
|
changed = False
|
||||||
|
for line in self.lines:
|
||||||
|
if line.type != 'line':
|
||||||
|
continue
|
||||||
|
if line.original_unit_price is not None:
|
||||||
|
line.unit_price = line.original_unit_price
|
||||||
|
line.original_unit_price = None
|
||||||
|
changed = True
|
||||||
|
if line.promotion:
|
||||||
|
line.promotion = None
|
||||||
|
changed = True
|
||||||
|
if changed:
|
||||||
|
self.lines = self.lines # Trigger changes
|
||||||
|
|
||||||
def apply_promotion(self):
|
def apply_promotion(self):
|
||||||
'Apply promotion'
|
"Apply promotion"
|
||||||
pool = Pool()
|
pool = Pool()
|
||||||
Promotion = pool.get('sale.promotion')
|
Promotion = pool.get('sale.promotion')
|
||||||
|
|
||||||
|
for line in self.lines:
|
||||||
|
if line.type == 'line' and line.original_unit_price is None:
|
||||||
|
line.original_unit_price = line.unit_price
|
||||||
|
|
||||||
promotions = Promotion.get_promotions(self)
|
promotions = Promotion.get_promotions(self)
|
||||||
for promotion in promotions:
|
for promotion in promotions:
|
||||||
promotion.apply(self)
|
promotion.apply(self)
|
||||||
@@ -72,17 +113,68 @@ class Sale(metaclass=PoolMeta):
|
|||||||
class Line(metaclass=PoolMeta):
|
class Line(metaclass=PoolMeta):
|
||||||
__name__ = 'sale.line'
|
__name__ = 'sale.line'
|
||||||
|
|
||||||
draft_unit_price = fields.Numeric('Draft Unit Price',
|
original_unit_price = Monetary(
|
||||||
digits=price_digits, readonly=True,
|
"Original Unit Price", digits=price_digits, currency='currency',
|
||||||
|
readonly=True,
|
||||||
states={
|
states={
|
||||||
'required': Bool(Eval('promotion', None)),
|
'required': Bool(Eval('promotion', None)),
|
||||||
|
'invisible': ~Eval('promotion'),
|
||||||
})
|
})
|
||||||
|
original_amount = fields.Function(Monetary(
|
||||||
|
"Original Amount", digits='currency', currency='currency',
|
||||||
|
states={
|
||||||
|
'invisible': ~Eval('promotion'),
|
||||||
|
}),
|
||||||
|
'get_original_amount')
|
||||||
promotion = fields.Many2One('sale.promotion', "Promotion",
|
promotion = fields.Many2One('sale.promotion', "Promotion",
|
||||||
ondelete='RESTRICT',
|
ondelete='RESTRICT',
|
||||||
domain=[
|
domain=[
|
||||||
('company', '=', Eval('company', -1)),
|
('company', '=', Eval('company', -1)),
|
||||||
])
|
])
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def __register__(cls, module):
|
||||||
|
table_h = cls.__table_handler__(module)
|
||||||
|
# Migration from 7.4: rename draft_unit_price
|
||||||
|
table_h.column_rename('draft_unit_price', 'original_unit_price')
|
||||||
|
super().__register__(module)
|
||||||
|
|
||||||
|
def get_original_amount(self, name):
|
||||||
|
currency = self.sale.currency
|
||||||
|
|
||||||
|
def _amount(line):
|
||||||
|
return currency.round(
|
||||||
|
Decimal(str(line.quantity))
|
||||||
|
* (line.original_unit_price or line.unit_price))
|
||||||
|
|
||||||
|
if self.type == 'line':
|
||||||
|
return _amount(self)
|
||||||
|
elif self.type == 'subtotal':
|
||||||
|
amount = Decimal(0)
|
||||||
|
for line2 in self.sale.lines:
|
||||||
|
if line2.type == 'line':
|
||||||
|
amount += _amount(line2)
|
||||||
|
elif line2.type == 'subtotal':
|
||||||
|
if self == line2:
|
||||||
|
break
|
||||||
|
amount = Decimal(0)
|
||||||
|
return amount
|
||||||
|
|
||||||
|
@property
|
||||||
|
def taxable_lines(self):
|
||||||
|
lines = super().taxable_lines
|
||||||
|
if (getattr(self, 'type', None) == 'line'
|
||||||
|
and Transaction().context.get('_original_amount')):
|
||||||
|
lines = [(
|
||||||
|
getattr(self, 'taxes', None) or [],
|
||||||
|
getattr(self, 'original_unit_price', None)
|
||||||
|
or getattr(self, 'unit_price', None)
|
||||||
|
or Decimal(0),
|
||||||
|
getattr(self, 'quantity', None) or 0,
|
||||||
|
None,
|
||||||
|
)]
|
||||||
|
return lines
|
||||||
|
|
||||||
|
|
||||||
class Promotion(
|
class Promotion(
|
||||||
DeactivableMixin, ModelSQL, ModelView, MatchMixin):
|
DeactivableMixin, ModelSQL, ModelView, MatchMixin):
|
||||||
|
|||||||
@@ -3,6 +3,18 @@
|
|||||||
this repository contains the full copyright notices and license terms. -->
|
this repository contains the full copyright notices and license terms. -->
|
||||||
<tryton>
|
<tryton>
|
||||||
<data>
|
<data>
|
||||||
|
<record model="ir.ui.view" id="sale_sale_view_form">
|
||||||
|
<field name="model">sale.sale</field>
|
||||||
|
<field name="inherit" ref="sale.sale_view_form"/>
|
||||||
|
<field name="name">sale_sale_form</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record model="ir.ui.view" id="sale_line_view_form">
|
||||||
|
<field name="model">sale.line</field>
|
||||||
|
<field name="inherit" ref="sale.sale_line_view_form"/>
|
||||||
|
<field name="name">sale_line_form</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
<record model="ir.ui.view" id="promotion_view_form">
|
<record model="ir.ui.view" id="promotion_view_form">
|
||||||
<field name="model">sale.promotion</field>
|
<field name="model">sale.promotion</field>
|
||||||
<field name="type">form</field>
|
<field name="type">form</field>
|
||||||
|
|||||||
@@ -8,7 +8,8 @@ Imports::
|
|||||||
>>> from decimal import Decimal
|
>>> from decimal import Decimal
|
||||||
|
|
||||||
>>> from proteus import Model
|
>>> from proteus import Model
|
||||||
>>> from trytond.modules.account.tests.tools import create_chart, get_accounts
|
>>> from trytond.modules.account.tests.tools import (
|
||||||
|
... create_chart, create_tax, get_accounts)
|
||||||
>>> from trytond.modules.account_invoice.tests.tools import create_payment_term
|
>>> from trytond.modules.account_invoice.tests.tools import create_payment_term
|
||||||
>>> from trytond.modules.company.tests.tools import create_company
|
>>> from trytond.modules.company.tests.tools import create_company
|
||||||
>>> from trytond.tests.tools import activate_modules
|
>>> from trytond.tests.tools import activate_modules
|
||||||
@@ -22,6 +23,10 @@ Get accounts::
|
|||||||
>>> accounts = get_accounts()
|
>>> accounts = get_accounts()
|
||||||
>>> revenue = accounts['revenue']
|
>>> revenue = accounts['revenue']
|
||||||
|
|
||||||
|
Create a tax::
|
||||||
|
|
||||||
|
>>> tax = create_tax(Decimal('0.1'))
|
||||||
|
|
||||||
Create customer::
|
Create customer::
|
||||||
|
|
||||||
>>> Party = Model.get('party.party')
|
>>> Party = Model.get('party.party')
|
||||||
@@ -34,6 +39,7 @@ Create account category::
|
|||||||
>>> account_category = ProductCategory(name="Account Category")
|
>>> account_category = ProductCategory(name="Account Category")
|
||||||
>>> account_category.accounting = True
|
>>> account_category.accounting = True
|
||||||
>>> account_category.account_revenue = revenue
|
>>> account_category.account_revenue = revenue
|
||||||
|
>>> account_category.customer_taxes.append(tax)
|
||||||
>>> account_category.save()
|
>>> account_category.save()
|
||||||
|
|
||||||
Create products::
|
Create products::
|
||||||
@@ -73,6 +79,8 @@ Create products::
|
|||||||
>>> product3.save()
|
>>> product3.save()
|
||||||
|
|
||||||
>>> template4, = template.duplicate(default={'categories': [category2.id]})
|
>>> template4, = template.duplicate(default={'categories': [category2.id]})
|
||||||
|
>>> template4.account_category = account_category
|
||||||
|
>>> template4.save()
|
||||||
>>> product4 = Product(template4.products[0].id)
|
>>> product4 = Product(template4.products[0].id)
|
||||||
|
|
||||||
Create payment term::
|
Create payment term::
|
||||||
@@ -112,9 +120,23 @@ Sale enough products for promotion::
|
|||||||
>>> sale.save()
|
>>> sale.save()
|
||||||
>>> sale.untaxed_amount
|
>>> sale.untaxed_amount
|
||||||
Decimal('500.00')
|
Decimal('500.00')
|
||||||
|
>>> sale.tax_amount
|
||||||
|
Decimal('50.00')
|
||||||
|
>>> sale.total_amount
|
||||||
|
Decimal('550.00')
|
||||||
>>> sale.click('quote')
|
>>> sale.click('quote')
|
||||||
>>> sale.untaxed_amount
|
>>> sale.untaxed_amount
|
||||||
Decimal('470.00')
|
Decimal('470.00')
|
||||||
|
>>> sale.tax_amount
|
||||||
|
Decimal('47.00')
|
||||||
|
>>> sale.total_amount
|
||||||
|
Decimal('517.00')
|
||||||
|
>>> sale.original_untaxed_amount
|
||||||
|
Decimal('500.00')
|
||||||
|
>>> sale.original_tax_amount
|
||||||
|
Decimal('50.00')
|
||||||
|
>>> sale.original_total_amount
|
||||||
|
Decimal('550.00')
|
||||||
|
|
||||||
Go back to draft reset the original price::
|
Go back to draft reset the original price::
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
|
||||||
|
this repository contains the full copyright notices and license terms. -->
|
||||||
|
<data>
|
||||||
|
<xpath expr="//field[@name='quantity']" position="after">
|
||||||
|
<label name="original_unit_price"/>
|
||||||
|
<field name="original_unit_price"/>
|
||||||
|
<label name="original_amount"/>
|
||||||
|
<field name="original_amount"/>
|
||||||
|
</xpath>
|
||||||
|
</data>
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
|
||||||
|
this repository contains the full copyright notices and license terms. -->
|
||||||
|
<data>
|
||||||
|
<xpath expr="//field[@name='untaxed_amount']" position="before">
|
||||||
|
<field name="original_untaxed_amount" xalign="1.0" xexpand="0"/>
|
||||||
|
</xpath>
|
||||||
|
<xpath expr="//field[@name='tax_amount']" position="before">
|
||||||
|
<field name="original_tax_amount" xalign="1.0" xexpand="0"/>
|
||||||
|
</xpath>
|
||||||
|
<xpath expr="//field[@name='total_amount']" position="before">
|
||||||
|
<field name="original_total_amount" xalign="1.0" xexpand="0"/>
|
||||||
|
</xpath>
|
||||||
|
</data>
|
||||||
Reference in New Issue
Block a user