trytond-patches/issue7280.diff

367 lines
12 KiB
Diff

--- a/trytond/trytond/modules/account_invoice_stock/__init__.py
+++ b/trytond/trytond/modules/account_invoice_stock/__init__.py
@@ -8,6 +8,7 @@ from .stock import *
def register():
Pool.register(
+ Invoice,
InvoiceLineStockMove,
InvoiceLine,
StockMove,
--- a/trytond/trytond/modules/account_invoice_stock/account.py
+++ b/trytond/trytond/modules/account_invoice_stock/account.py
@@ -1,13 +1,27 @@
# 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 trytond.model import ModelSQL, fields
+from trytond.model import ModelSQL, ModelView, Workflow, fields
from trytond.pool import Pool, PoolMeta
from trytond.pyson import Eval
-
-__all__ = ['InvoiceLineStockMove', 'InvoiceLine']
+__all__ = ['InvoiceLineStockMove', 'InvoiceLine', 'Invoice']
+class Invoice(metaclass=PoolMeta):
+ __name__ = 'account.invoice'
+
+ @classmethod
+ @ModelView.button
+ @Workflow.transition('posted')
+ def post(cls, invoices):
+ pool = Pool()
+ Move = pool.get('stock.move')
+ super().post(invoices)
+ moves = sum((l.stock_moves for i in invoices for l in i.lines), ())
+ if moves:
+ Move.__queue__.update_unit_price(moves)
+
+
class InvoiceLineStockMove(ModelSQL):
'Invoice Line - Stock Move'
__name__ = 'account.invoice.line-stock.move'
Index: /trytond/trytond/modules/account_invoice_stock/doc/index.rst
===================================================================
--- a/trytond/trytond/modules/account_invoice_stock/doc/index.rst
+++ b/trytond/trytond/modules/account_invoice_stock/doc/index.rst
@@ -3,3 +3,5 @@
The account invoice stock module adds link between invoice lines and stock
moves.
+The unit price of the stock move is updated based on the average price of the
+posted invoice lines that are linked to it.
Index: /trytond/trytond/modules/account_invoice_stock/setup.py
===================================================================
--- a/trytond/trytond/modules/account_invoice_stock/setup.py
+++ b/trytond/trytond/modules/account_invoice_stock/setup.py
@@ -57,6 +57,7 @@
requires.append(get_require_version('trytond_%s' % dep))
requires.append(get_require_version('trytond'))
+tests_require = [get_require_version('proteus')]
dependency_links = []
if minor_version % 2:
dependency_links.append('https://trydevpi.tryton.org/')
@@ -136,4 +137,5 @@
""",
test_suite='tests',
test_loader='trytond.test_loader:Loader',
+ tests_require=tests_require,
)
Index: /trytond/trytond/modules/account_invoice_stock/stock.py
===================================================================
--- a/trytond/trytond/modules/account_invoice_stock/stock.py
+++ b/trytond/trytond/modules/account_invoice_stock/stock.py
@@ -1,8 +1,11 @@
# 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 trytond.model import fields
+from decimal import Decimal
+from trytond.model import ModelView, Workflow, fields
from trytond.pool import Pool, PoolMeta
from trytond.transaction import Transaction
+from trytond.pyson import Eval
+from trytond.modules.product import round_price
__all__ = ['StockMove', 'ShipmentOut']
@@ -10,8 +13,15 @@ __all__ = ['StockMove', 'ShipmentOut']
class StockMove(metaclass=PoolMeta):
__name__ = 'stock.move'
- invoice_lines = fields.Many2Many('account.invoice.line-stock.move',
- 'stock_move', 'invoice_line', 'Invoice Lines')
+ invoice_lines = fields.Many2Many(
+ 'account.invoice.line-stock.move', 'stock_move', 'invoice_line',
+ "Invoice Lines",
+ domain=[
+ ('product.default_uom_category',
+ '=', Eval('product_uom_category', -1)),
+ ('type', '=', 'line'),
+ ],
+ depends=['product_uom_category'])
@property
def invoiced_quantity(self):
@@ -34,6 +67,37 @@ class StockMove(metaclass=PoolMeta):
default.setdefault('invoice_lines', None)
return super(StockMove, cls).copy(moves, default=default)
+ @classmethod
+ @ModelView.button
+ @Workflow.transition('done')
+ def do(cls, moves):
+ super().do(moves)
+ cls.update_unit_price(moves)
+
+ @classmethod
+ def update_unit_price(cls, moves):
+ for move in moves:
+ if move.state == 'done':
+ unit_price = move._compute_unit_price()
+ if unit_price != move.unit_price:
+ move.unit_price = unit_price
+ cls.save(moves)
+
+ def _compute_unit_price(self):
+ pool = Pool()
+ UoM = pool.get('product.uom')
+ amount, quantity = 0, 0
+ for line in self.invoice_lines:
+ if line.invoice and line.invoice.state in {'posted', 'paid'}:
+ amount += line.amount
+ quantity += UoM.compute_qty(
+ line.unit, line.quantity, self.uom)
+ if not quantity:
+ unit_price = self.unit_price
+ else:
+ unit_price = round_price(amount / Decimal(str(quantity)))
+ return unit_price
+
class ShipmentOut(metaclass=PoolMeta):
__name__ = 'stock.shipment.out'
Index: /trytond/trytond/modules/account_invoice_stock/tests/scenario_account_invoice_stock.rst
===================================================================
new file mode 100644
--- /dev/null
+++ b/trytond/trytond/modules/account_invoice_stock/tests/scenario_account_invoice_stock.rst
@@ -0,0 +1,140 @@
+========================
+Invoice - Stock 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)
+
+Install account_invoice_stock::
+
+ >>> config = activate_modules('account_invoice_stock')
+
+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)
+
+Create a party::
+
+ >>> Party = Model.get('party.party')
+ >>> party = Party(name="Party")
+ >>> party.save()
+
+Create an account category::
+
+ >>> ProductCategory = Model.get('product.category')
+ >>> account_category = ProductCategory(name="Account Category")
+ >>> account_category.accounting = True
+ >>> account_category.account_expense = accounts['expense']
+ >>> account_category.account_revenue = accounts['revenue']
+ >>> account_category.save()
+
+Create a product::
+
+ >>> 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.list_price = Decimal('40')
+ >>> template.account_category = account_category
+ >>> template.save()
+ >>> product, = template.products
+
+Get stock locations::
+
+ >>> Location = Model.get('stock.location')
+ >>> output_loc, = Location.find([('code', '=', 'OUT')])
+ >>> customer_loc, = Location.find([('code', '=', 'CUS')])
+
+Create a shipment::
+
+ >>> Shipment = Model.get('stock.shipment.out')
+ >>> Move = Model.get('stock.move')
+ >>> shipment = Shipment()
+ >>> shipment.customer = party
+ >>> move = shipment.outgoing_moves.new()
+ >>> move.product = product
+ >>> move.quantity = 10
+ >>> move.from_location = output_loc
+ >>> move.to_location = customer_loc
+ >>> move.unit_price = Decimal('40.0000')
+ >>> shipment.click('wait')
+ >>> shipment.state
+ 'waiting'
+ >>> move, = shipment.outgoing_moves
+
+Create an invoice for half the quantity with higher price::
+
+ >>> Invoice = Model.get('account.invoice')
+ >>> invoice = Invoice(type='out')
+ >>> invoice.party = party
+ >>> line = invoice.lines.new()
+ >>> line.product = product
+ >>> line.quantity = 5
+ >>> line.unit_price = Decimal('50.0000')
+ >>> line.stock_moves.append(Move(move.id))
+ >>> invoice.click('post')
+ >>> invoice.state
+ 'posted'
+
+Check move unit price is not changed::
+
+ >>> move.reload()
+ >>> move.unit_price
+ Decimal('40.0000')
+
+Ship the products::
+
+ >>> shipment.click('assign_force')
+ >>> shipment.click('pack')
+ >>> shipment.click('done')
+ >>> shipment.state
+ 'done'
+
+Check move unit price has been updated::
+
+ >>> move.reload()
+ >>> move.unit_price
+ Decimal('50.0000')
+
+Create a second invoice for the remaining quantity cheaper::
+
+ >>> invoice = Invoice(type='out')
+ >>> invoice.party = party
+ >>> line = invoice.lines.new()
+ >>> line.product = product
+ >>> line.quantity = 5
+ >>> line.unit_price = Decimal('40.0000')
+ >>> line.stock_moves.append(Move(move.id))
+ >>> invoice.click('post')
+ >>> invoice.state
+ 'posted'
+
+Check move unit price has been updated again::
+
+ >>> move.reload()
+ >>> move.unit_price
+ Decimal('45.0000')
Index: /trytond/trytond/modules/account_invoice_stock/tests/test_account_invoice_stock.py
===================================================================
--- a/trytond/trytond/modules/account_invoice_stock/tests/test_account_invoice_stock.py
+++ b/trytond/trytond/modules/account_invoice_stock/tests/test_account_invoice_stock.py
@@ -1,8 +1,11 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
+import doctest
import unittest
import trytond.tests.test_tryton
from trytond.tests.test_tryton import ModuleTestCase
+from trytond.tests.test_tryton import doctest_teardown
+from trytond.tests.test_tryton import doctest_checker
class AccountInvoiceStockTestCase(ModuleTestCase):
@@ -14,4 +17,9 @@
suite = trytond.tests.test_tryton.suite()
suite.addTests(unittest.TestLoader().loadTestsFromTestCase(
AccountInvoiceStockTestCase))
+ suite.addTests(doctest.DocFileSuite(
+ 'scenario_account_invoice_stock.rst',
+ tearDown=doctest_teardown, encoding='utf-8',
+ checker=doctest_checker,
+ optionflags=doctest.REPORT_ONLY_FIRST_FAILURE))
return suite
Index: /trytond/trytond/modules/account_stock_landed_cost/stock.py
===================================================================
--- a/trytond/trytond/modules/account_stock_landed_cost/stock.py
+++ b/trytond/trytond/modules/account_stock_landed_cost/stock.py
@@ -9,3 +9,9 @@
__name__ = 'stock.move'
unit_landed_cost = fields.Numeric('Unit Landed Cost',
digits=price_digits, readonly=True)
+
+ def _compute_unit_price(self):
+ unit_price = super()._compute_unit_price()
+ if self.unit_landed_cost:
+ unit_price += self.unit_landed_cost
+ return unit_price
Index: /trytond/trytond/modules/account_stock_landed_cost/tryton.cfg
===================================================================
--- a/trytond/trytond/modules/account_stock_landed_cost/tryton.cfg
+++ b/trytond/trytond/modules/account_stock_landed_cost/tryton.cfg
@@ -7,6 +7,8 @@
product
res
stock
+extra_depends:
+ account_invoice_stock
xml:
account.xml
product.xml
--- a/trytond/trytond/modules/product/product.py
+++ b/trytond/trytond/modules/product/product.py
@@ -20,7 +20,7 @@ from trytond.modules.company.model import (
__all__ = ['Template', 'Product', 'price_digits', 'TemplateFunction',
'ProductListPrice', 'ProductCostPriceMethod', 'ProductCostPrice',
- 'TemplateCategory', 'TemplateCategoryAll']
+ 'TemplateCategory', 'TemplateCategoryAll', 'round_price']
logger = logging.getLogger(__name__)
STATES = {
@@ -39,6 +39,11 @@ COST_PRICE_METHODS = [
price_digits = (16, config.getint('product', 'price_decimal', default=4))
+def round_price(value, rounding=None):
+ "Round price using the price digits"
+ return value.quantize(
+ Decimal(1) / 10 ** price_digits[1], rounding=rounding)
+
class Template(
DeactivableMixin, ModelSQL, ModelView, CompanyMultiValueMixin):