gross_unit_price use same digits that unit_price (#5)

#163307
This commit is contained in:
Raimon Esteve 2023-11-16 19:24:02 +01:00 committed by Continuous Integration
parent adff65c201
commit a9f76aae65
10 changed files with 294 additions and 31 deletions

View File

@ -1,6 +1,7 @@
# The COPYRIGHT file at the top level of this repository contains the full # The COPYRIGHT file at the top level of this repository contains the full
# copyright notices and license terms. # copyright notices and license terms.
from trytond.pool import Pool from trytond.pool import Pool
from . import amendment
from . import sale from . import sale
from . import move from . import move
@ -11,3 +12,7 @@ def register():
sale.SaleLine, sale.SaleLine,
move.Move, move.Move,
module='sale_discount', type_='model') module='sale_discount', type_='model')
Pool.register(
amendment.AmendmentLine,
module='sale_discount', type_='model',
depends=['sale_amendment'])

76
amendment.py Normal file
View File

@ -0,0 +1,76 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
from decimal import Decimal
from trytond.model import fields
from trytond.pool import PoolMeta
from trytond.pyson import Eval
from trytond.modules.account_invoice_discount.invoice import (gross_unit_price_digits,
discount_digits)
from trytond.modules.product import round_price
STATES={
'readonly': Eval('state') != 'draft',
'invisible': Eval('action') != 'line',
'required': Eval('action') == 'line',
}
class AmendmentLine(metaclass=PoolMeta):
__name__ = 'sale.amendment.line'
gross_unit_price = fields.Numeric('Gross Price', digits=gross_unit_price_digits,
states=STATES, depends=['state', 'action'])
discount = fields.Numeric('Discount', digits=discount_digits,
states=STATES, depends=['state', 'action'])
@classmethod
def __setup__(cls):
super().__setup__()
cls.unit_price.states['readonly'] = True
@fields.depends(methods=['update_prices'])
def on_change_gross_unit_price(self):
return self.update_prices()
@fields.depends('unit_price', methods=['update_prices'])
def on_change_unit_price(self):
# unit_price has readonly state but could set unit_price from source code
if self.unit_price is not None:
self.update_prices()
@fields.depends(methods=['update_prices'])
def on_change_discount(self):
return self.update_prices()
@fields.depends('line')
def on_change_line(self):
super().on_change_line()
if self.line:
self.gross_unit_price = self.line.gross_unit_price
self.discount = self.line.discount
else:
self.gross_unit_price = None
self.discount = None
def _apply_line(self, sale, sale_line):
super()._apply_line(sale, sale_line)
sale_line.gross_unit_price = self.gross_unit_price
sale_line.discount = self.discount
@fields.depends('gross_unit_price', 'unit_price', 'discount')
def update_prices(self):
# TODO not support amendment upgrade_prices and sale_discount from sale (header)
unit_price = None
gross_unit_price = self.gross_unit_price
if self.gross_unit_price is not None and self.discount is not None:
unit_price = self.gross_unit_price * (1 - self.discount)
unit_price = round_price(unit_price)
if self.discount != 1:
gross_unit_price = unit_price / (1 - self.discount)
gup_digits = self.__class__.gross_unit_price.digits[1]
gross_unit_price = gross_unit_price.quantize(
Decimal(str(10.0 ** -gup_digits)))
self.gross_unit_price = gross_unit_price
self.unit_price = unit_price

12
amendment.xml Normal file
View File

@ -0,0 +1,12 @@
<?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. -->
<tryton>
<data depends="sale_amendment">
<record model="ir.ui.view" id="sale_amendment_line_view_form">
<field name="model">sale.amendment.line</field>
<field name="inherit" ref="sale_amendment.sale_amendment_line_view_form"/>
<field name="name">sale_amendment_line_form</field>
</record>
</data>
</tryton>

View File

@ -10,10 +10,6 @@ msgctxt "field:sale.line,gross_unit_price:"
msgid "Gross Price" msgid "Gross Price"
msgstr "Preu brut" msgstr "Preu brut"
msgctxt "field:sale.line,gross_unit_price_wo_round:"
msgid "Gross Price without rounding"
msgstr "Preu brut sense arrodoniment"
msgctxt "field:sale.sale,sale_discount:" msgctxt "field:sale.sale,sale_discount:"
msgid "Sale Discount" msgid "Sale Discount"
msgstr "Descompte venda" msgstr "Descompte venda"

View File

@ -10,10 +10,6 @@ msgctxt "field:sale.line,gross_unit_price:"
msgid "Gross Price" msgid "Gross Price"
msgstr "Precio bruto" msgstr "Precio bruto"
msgctxt "field:sale.line,gross_unit_price_wo_round:"
msgid "Gross Price without rounding"
msgstr "Precio bruto sin redondeo"
msgctxt "field:sale.sale,sale_discount:" msgctxt "field:sale.sale,sale_discount:"
msgid "Sale Discount" msgid "Sale Discount"
msgstr "Descuento venta" msgstr "Descuento venta"

42
sale.py
View File

@ -6,11 +6,10 @@ from trytond.model import fields
from trytond.pool import Pool, PoolMeta from trytond.pool import Pool, PoolMeta
from trytond.pyson import Eval from trytond.pyson import Eval
from trytond.transaction import Transaction from trytond.transaction import Transaction
from trytond.modules.account_invoice_discount.invoice import discount_digits
from trytond.modules.currency.fields import Monetary from trytond.modules.currency.fields import Monetary
from trytond.modules.product import price_digits, round_price from trytond.modules.account_invoice_discount.invoice import (gross_unit_price_digits,
discount_digits)
__all__ = ['Sale', 'SaleLine', 'discount_digits'] from trytond.modules.product import round_price
STATES = { STATES = {
'invisible': Eval('type') != 'line', 'invisible': Eval('type') != 'line',
@ -82,11 +81,8 @@ class Sale(metaclass=PoolMeta):
class SaleLine(metaclass=PoolMeta): class SaleLine(metaclass=PoolMeta):
__name__ = 'sale.line' __name__ = 'sale.line'
gross_unit_price = Monetary('Gross Price', digits=price_digits, gross_unit_price = Monetary('Gross Price', digits=gross_unit_price_digits,
currency='currency', states=STATES, depends=['type', 'sale_state']) currency='currency', states=STATES, depends=['type', 'sale_state'])
gross_unit_price_wo_round = Monetary('Gross Price without rounding',
digits=(16, price_digits[1] + discount_digits[1]), currency='currency',
readonly=True)
discount = fields.Numeric('Discount', digits=discount_digits, discount = fields.Numeric('Discount', digits=discount_digits,
states=STATES, depends=['type', 'sale_state']) states=STATES, depends=['type', 'sale_state'])
@ -94,7 +90,6 @@ class SaleLine(metaclass=PoolMeta):
def __setup__(cls): def __setup__(cls):
super().__setup__() super().__setup__()
cls.unit_price.states['readonly'] = True cls.unit_price.states['readonly'] = True
cls.unit_price.digits = (20, price_digits[1] + discount_digits[1])
@staticmethod @staticmethod
def default_discount(): def default_discount():
@ -106,16 +101,13 @@ class SaleLine(metaclass=PoolMeta):
and self.promotion and self.promotion
and self.draft_unit_price) and self.draft_unit_price)
@fields.depends('gross_unit_price', 'discount', @fields.depends('sale', 'gross_unit_price', 'unit_price', 'discount',
methods=['on_change_with_amount']) methods=['on_change_with_amount'])
def update_prices(self): def update_prices(self):
unit_price = None unit_price = None
gross_unit_price = gross_unit_price_wo_round = self.gross_unit_price gross_unit_price = self.gross_unit_price
sale_discount = Transaction().context.get('sale_discount') sale_discount = Transaction().context.get('sale_discount')
if self.gross_unit_price is None:
return
if sale_discount is None: if sale_discount is None:
if self.sale and hasattr(self.sale, 'sale_discount'): if self.sale and hasattr(self.sale, 'sale_discount'):
sale_discount = self.sale.sale_discount or Decimal(0) sale_discount = self.sale.sale_discount or Decimal(0)
@ -136,21 +128,19 @@ class SaleLine(metaclass=PoolMeta):
discount = (self.discount + sale_discount discount = (self.discount + sale_discount
- self.discount * sale_discount) - self.discount * sale_discount)
if discount != 1: if discount != 1:
gross_unit_price_wo_round = unit_price / (1 - discount) gross_unit_price = unit_price / (1 - discount)
elif self.discount and self.discount != 1: elif self.discount and self.discount != 1:
gross_unit_price_wo_round = unit_price / (1 - self.discount) gross_unit_price = unit_price / (1 - self.discount)
elif sale_discount and sale_discount != 1: elif sale_discount and sale_discount != 1:
gross_unit_price_wo_round = unit_price / (1 - sale_discount) gross_unit_price = unit_price / (1 - sale_discount)
unit_price = round_price(unit_price) unit_price = round_price(unit_price)
gup_wo_r_digits = self.__class__.gross_unit_price_wo_round.digits[1] gup_digits = self.__class__.gross_unit_price.digits[1]
gross_unit_price_wo_round = gross_unit_price_wo_round.quantize( gross_unit_price = gross_unit_price.quantize(
Decimal(str(10.0 ** -gup_wo_r_digits))) Decimal(str(10.0 ** -gup_digits)))
gross_unit_price = gross_unit_price_wo_round
self.gross_unit_price = round_price(gross_unit_price) self.gross_unit_price = gross_unit_price
self.gross_unit_price_wo_round = gross_unit_price_wo_round
if self.has_promotion: if self.has_promotion:
self.draft_unit_price = unit_price self.draft_unit_price = unit_price
else: else:
@ -165,6 +155,12 @@ class SaleLine(metaclass=PoolMeta):
def on_change_gross_unit_price(self): def on_change_gross_unit_price(self):
return self.update_prices() return self.update_prices()
@fields.depends('unit_price', methods=['update_prices'])
def on_change_unit_price(self):
# unit_price has readonly state but could set unit_price from source code
if self.unit_price is not None:
self.update_prices()
@fields.depends('sale', methods=['update_prices']) @fields.depends('sale', methods=['update_prices'])
def on_change_discount(self): def on_change_discount(self):
return self.update_prices() return self.update_prices()

View File

@ -0,0 +1,163 @@
=======================
Sale Amendment Scenario
=======================
Imports::
>>> from decimal import Decimal
>>> from proteus import Model, Wizard
>>> from trytond.tests.tools import activate_modules
>>> from trytond.modules.company.tests.tools import create_company, \
... get_company
>>> from trytond.modules.account.tests.tools import create_fiscalyear, \
... create_chart, get_accounts
>>> from trytond.modules.account_invoice.tests.tools import \
... set_fiscalyear_invoice_sequences
Activate modules::
>>> config = activate_modules(['sale_discount', 'sale_amendment'])
Create company::
>>> _ = create_company()
>>> company = get_company()
Create fiscal year::
>>> fiscalyear = set_fiscalyear_invoice_sequences(
... create_fiscalyear(company))
>>> fiscalyear.click('create_period')
Create chart of accounts::
>>> _ = create_chart(company)
>>> accounts = get_accounts(company)
>>> revenue = accounts['revenue']
>>> expense = accounts['expense']
Create parties::
>>> Party = Model.get('party.party')
>>> customer1 = Party(name="Customer 1")
>>> customer1.save()
>>> customer2 = Party(name="Customer 2")
>>> customer2.save()
Create account categories::
>>> ProductCategory = Model.get('product.category')
>>> account_category = ProductCategory(name="Account Category")
>>> account_category.accounting = True
>>> account_category.account_expense = expense
>>> account_category.account_revenue = revenue
>>> account_category.save()
Create products::
>>> ProductUom = Model.get('product.uom')
>>> unit, = ProductUom.find([('name', '=', 'Unit')])
>>> ProductTemplate = Model.get('product.template')
>>> template = ProductTemplate()
>>> template.name = 'product'
>>> template.default_uom = unit
>>> template.type = 'goods'
>>> template.salable = True
>>> template.list_price = Decimal('10')
>>> template.account_category = account_category
>>> _ = template.products.new()
>>> template.save()
>>> product1, product2 = template.products
Sale first product::
>>> Sale = Model.get('sale.sale')
>>> sale = Sale()
>>> sale.party = customer1
>>> sale_line = sale.lines.new()
>>> sale_line.product = product1
>>> sale_line.quantity = 5.0
>>> sale_line = sale.lines.new()
>>> sale_line.quantity = 1
>>> sale_line.product = product1
>>> sale_line.gross_unit_price = Decimal('10.0000')
>>> sale_line.discount = Decimal('0.10')
>>> sale_line.unit_price == Decimal('9.0000')
True
>>> sale_line = sale.lines.new()
>>> sale_line.product = product1
>>> sale_line.quantity = 3.0
>>> sale_line.amount == Decimal('30.00')
True
>>> sale.click('quote')
>>> sale.click('confirm')
>>> sale.state
'processing'
>>> sale.revision
0
>>> sale.total_amount
Decimal('89.00')
>>> len(sale.shipments), len(sale.invoices)
(1, 1)
Add an amendment::
>>> amendment = sale.amendments.new()
>>> line = amendment.lines.new()
>>> line.action = 'line'
>>> line.line = sale.lines[0]
>>> line.product == product1
True
>>> line.product = product2
>>> line.quantity
5.0
>>> line.quantity = 4.0
>>> line.gross_unit_price
Decimal('10.0000')
>>> line.discount
Decimal('0')
>>> line.unit_price
Decimal('10.0000')
>>> line = amendment.lines.new()
>>> line.action = 'line'
>>> line.line = sale.lines[1]
>>> line.gross_unit_price
Decimal('10.0000')
>>> line.discount
Decimal('0.10')
>>> line.unit_price
Decimal('9.0000')
>>> line.discount = Decimal('0.20')
>>> line.unit_price
Decimal('8.0000')
>>> line.gross_unit_price = Decimal('20.0000')
>>> line.unit_price
Decimal('16.0000')
>>> amendment.save()
Validate amendment::
>>> amendment.click('validate_amendment')
>>> sale.reload()
>>> sale.revision
1
>>> line = sale.lines[0]
>>> line.product == product2
True
>>> line.quantity
4.0
>>> line.gross_unit_price
Decimal('10.0000')
>>> line.unit_price
Decimal('10.0000')
>>> line = sale.lines[1]
>>> line.gross_unit_price
Decimal('20.0000')
>>> line.unit_price
Decimal('16.0000')
>>> line.discount
Decimal('0.20')
>>> sale.total_amount
Decimal('86.00')

View File

@ -9,6 +9,7 @@ from trytond.tests.test_tryton import ModuleTestCase
class SaleDiscountTestCase(CompanyTestMixin, ModuleTestCase): class SaleDiscountTestCase(CompanyTestMixin, ModuleTestCase):
'Test SaleDiscount module' 'Test SaleDiscount module'
module = 'sale_discount' module = 'sale_discount'
extras = ['sale_amendment']
del ModuleTestCase del ModuleTestCase

View File

@ -4,8 +4,10 @@ depends:
sale sale
account_invoice_discount account_invoice_discount
extras_depend: extras_depend:
sale_amendment
sale_promotion sale_promotion
sale_shipment_cost sale_shipment_cost
purchase_shipment_cost purchase_shipment_cost
xml: xml:
sale.xml sale.xml
amendment.xml

View File

@ -0,0 +1,16 @@
<?xml version="1.0"?>
<!-- This file is part of the sale_discount module for Tryton.
The COPYRIGHT file at the top level of this repository contains the full
copyright notices and license terms. -->
<data>
<xpath expr="/form/label[@name='unit_price']"
position="before">
<label name="gross_unit_price"/>
<field name="gross_unit_price"/>
<group id="discount" colspan="2" col="3">
<label name="discount"/>
<field name="discount" factor="100" xexpand="0" xfill="0"/>
<label name="discount" string="%" xalign="0.0" xexpand="1" xfill="1"/>
</group>
</xpath>
</data>