diff --git a/__init__.py b/__init__.py
index 3568075..7546a06 100644
--- a/__init__.py
+++ b/__init__.py
@@ -1,6 +1,7 @@
# The COPYRIGHT file at the top level of this repository contains the full
# copyright notices and license terms.
from trytond.pool import Pool
+from . import amendment
from . import sale
from . import move
@@ -11,3 +12,7 @@ def register():
sale.SaleLine,
move.Move,
module='sale_discount', type_='model')
+ Pool.register(
+ amendment.AmendmentLine,
+ module='sale_discount', type_='model',
+ depends=['sale_amendment'])
diff --git a/amendment.py b/amendment.py
new file mode 100644
index 0000000..8029f12
--- /dev/null
+++ b/amendment.py
@@ -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
diff --git a/amendment.xml b/amendment.xml
new file mode 100644
index 0000000..05eb82f
--- /dev/null
+++ b/amendment.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+ sale.amendment.line
+
+ sale_amendment_line_form
+
+
+
diff --git a/locale/ca.po b/locale/ca.po
index 656a6dd..c7c3b6e 100644
--- a/locale/ca.po
+++ b/locale/ca.po
@@ -10,10 +10,6 @@ msgctxt "field:sale.line,gross_unit_price:"
msgid "Gross Price"
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:"
msgid "Sale Discount"
msgstr "Descompte venda"
diff --git a/locale/es.po b/locale/es.po
index b3604c6..701c8b1 100644
--- a/locale/es.po
+++ b/locale/es.po
@@ -10,10 +10,6 @@ msgctxt "field:sale.line,gross_unit_price:"
msgid "Gross Price"
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:"
msgid "Sale Discount"
msgstr "Descuento venta"
diff --git a/sale.py b/sale.py
index 093fb93..038bc27 100644
--- a/sale.py
+++ b/sale.py
@@ -6,11 +6,10 @@ from trytond.model import fields
from trytond.pool import Pool, PoolMeta
from trytond.pyson import Eval
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.product import price_digits, round_price
-
-__all__ = ['Sale', 'SaleLine', 'discount_digits']
+from trytond.modules.account_invoice_discount.invoice import (gross_unit_price_digits,
+ discount_digits)
+from trytond.modules.product import round_price
STATES = {
'invisible': Eval('type') != 'line',
@@ -82,11 +81,8 @@ class Sale(metaclass=PoolMeta):
class SaleLine(metaclass=PoolMeta):
__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'])
- 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,
states=STATES, depends=['type', 'sale_state'])
@@ -94,7 +90,6 @@ class SaleLine(metaclass=PoolMeta):
def __setup__(cls):
super().__setup__()
cls.unit_price.states['readonly'] = True
- cls.unit_price.digits = (20, price_digits[1] + discount_digits[1])
@staticmethod
def default_discount():
@@ -106,16 +101,13 @@ class SaleLine(metaclass=PoolMeta):
and self.promotion
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'])
def update_prices(self):
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')
- if self.gross_unit_price is None:
- return
-
if sale_discount is None:
if self.sale and hasattr(self.sale, 'sale_discount'):
sale_discount = self.sale.sale_discount or Decimal(0)
@@ -136,21 +128,19 @@ class SaleLine(metaclass=PoolMeta):
discount = (self.discount + sale_discount
- self.discount * sale_discount)
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:
- 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:
- gross_unit_price_wo_round = unit_price / (1 - sale_discount)
+ gross_unit_price = unit_price / (1 - sale_discount)
unit_price = round_price(unit_price)
- gup_wo_r_digits = self.__class__.gross_unit_price_wo_round.digits[1]
- gross_unit_price_wo_round = gross_unit_price_wo_round.quantize(
- Decimal(str(10.0 ** -gup_wo_r_digits)))
- gross_unit_price = gross_unit_price_wo_round
+ 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 = round_price(gross_unit_price)
- self.gross_unit_price_wo_round = gross_unit_price_wo_round
+ self.gross_unit_price = gross_unit_price
if self.has_promotion:
self.draft_unit_price = unit_price
else:
@@ -165,6 +155,12 @@ class SaleLine(metaclass=PoolMeta):
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('sale', methods=['update_prices'])
def on_change_discount(self):
return self.update_prices()
diff --git a/tests/scenario_sale_amendment.rst b/tests/scenario_sale_amendment.rst
new file mode 100644
index 0000000..662545a
--- /dev/null
+++ b/tests/scenario_sale_amendment.rst
@@ -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')
diff --git a/tests/test_module.py b/tests/test_module.py
index af10871..775103a 100644
--- a/tests/test_module.py
+++ b/tests/test_module.py
@@ -9,6 +9,7 @@ from trytond.tests.test_tryton import ModuleTestCase
class SaleDiscountTestCase(CompanyTestMixin, ModuleTestCase):
'Test SaleDiscount module'
module = 'sale_discount'
+ extras = ['sale_amendment']
del ModuleTestCase
diff --git a/tryton.cfg b/tryton.cfg
index e978be5..fff1674 100644
--- a/tryton.cfg
+++ b/tryton.cfg
@@ -4,8 +4,10 @@ depends:
sale
account_invoice_discount
extras_depend:
+ sale_amendment
sale_promotion
sale_shipment_cost
purchase_shipment_cost
xml:
sale.xml
+ amendment.xml
diff --git a/view/sale_amendment_line_form.xml b/view/sale_amendment_line_form.xml
new file mode 100644
index 0000000..e60d7c9
--- /dev/null
+++ b/view/sale_amendment_line_form.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+