changeset 099fb36301d6 in modules/commission:default
details: 
https://hg.tryton.org/modules/commission?cmd=changeset&node=099fb36301d6
description:
        Include commission to stock margin report

        issue11327
        review365041003
diffstat:

 CHANGELOG                                    |    2 +
 __init__.py                                  |   18 ++-
 commission.py                                |   19 ++
 setup.py                                     |    8 +-
 stock.py                                     |   55 +++++++
 stock_reporting_margin.py                    |   32 ++++
 stock_reporting_margin.xml                   |   12 +
 tests/scenario_commission_stock.rst          |  212 +++++++++++++++++++++++++++
 tests/test_module.py                         |    2 +-
 tryton.cfg                                   |    3 +
 view/stock_reporting_margin_context_form.xml |    9 +
 11 files changed, 368 insertions(+), 4 deletions(-)

diffs (479 lines):

diff -r 1d340eeea38f -r 099fb36301d6 CHANGELOG
--- a/CHANGELOG Mon May 02 17:24:18 2022 +0200
+++ b/CHANGELOG Wed May 11 23:32:24 2022 +0200
@@ -1,3 +1,5 @@
+* Include commission to stock margin report
+
 Version 6.4.0 - 2022-05-02
 * Bug fixes (see mercurial logs for details)
 * Add support for Python 3.10
diff -r 1d340eeea38f -r 099fb36301d6 __init__.py
--- a/__init__.py       Mon May 02 17:24:18 2022 +0200
+++ b/__init__.py       Wed May 11 23:32:24 2022 +0200
@@ -3,9 +3,15 @@
 
 from trytond.pool import Pool
 
+try:
+    from trytond.modules.stock.stock_reporting_margin import \
+        Abstract as StockReportingMarginAbstract
+except ImportError:
+    StockReportingMarginAbstract = None
+
 from . import (
     account, commission, commission_reporting, invoice, ir, party, product,
-    sale)
+    sale, stock, stock_reporting_margin)
 
 
 def register():
@@ -35,8 +41,18 @@
         module='commission', type_='model',
         depends=['sale'])
     Pool.register(
+        stock.Move,
+        stock_reporting_margin.Context,
+        module='commission', type_='model',
+        depends=['stock'])
+    Pool.register(
         commission.CreateInvoice,
         invoice.CreditInvoice,
         party.Replace,
         party.Erase,
         module='commission', type_='wizard')
+    if StockReportingMarginAbstract:
+        Pool.register_mixin(
+            stock_reporting_margin.AbstractCommissionMixin,
+            StockReportingMarginAbstract,
+            module='commission')
diff -r 1d340eeea38f -r 099fb36301d6 commission.py
--- a/commission.py     Mon May 02 17:24:18 2022 +0200
+++ b/commission.py     Wed May 11 23:32:24 2022 +0200
@@ -434,6 +434,10 @@
         pool = Pool()
         Invoice = pool.get('account.invoice')
         InvoiceLine = pool.get('account.invoice.line')
+        try:
+            Move = pool.get('stock.move')
+        except KeyError:
+            Move = None
 
         def invoice_key(c):
             return c._group_to_invoice_key()
@@ -464,6 +468,11 @@
         Invoice.update_taxes(invoices)
         cls.save(to_save)
 
+        if Move and hasattr(Move, 'update_unit_price'):
+            moves = list(set.union(*(c.stock_moves for c in commissions)))
+            if moves:
+                Move.__queue__.update_unit_price(moves)
+
     def _group_to_invoice_key(self):
         direction = {
             'in': 'out',
@@ -535,6 +544,16 @@
         invoice_line.unit_price = amount
         return invoice_line
 
+    @property
+    def stock_moves(self):
+        pool = Pool()
+        InvoiceLine = pool.get('account.invoice.line')
+        stock_moves = set()
+        if (isinstance(self.origin, InvoiceLine)
+                and hasattr(InvoiceLine, 'stock_moves')):
+            stock_moves.update(self.origin.stock_moves)
+        return stock_moves
+
 
 class CreateInvoice(Wizard):
     'Create Commission Invoice'
diff -r 1d340eeea38f -r 099fb36301d6 setup.py
--- a/setup.py  Mon May 02 17:24:18 2022 +0200
+++ b/setup.py  Wed May 11 23:32:24 2022 +0200
@@ -64,8 +64,12 @@
         requires.append(get_require_version('trytond_%s' % dep))
 requires.append(get_require_version('trytond'))
 
-tests_require = [get_require_version('proteus'),
-    get_require_version('trytond_sale')]
+tests_require = [
+    get_require_version('proteus'),
+    get_require_version('trytond_account_invoice_stock'),
+    get_require_version('trytond_sale'),
+    get_require_version('trytond_stock'),
+    ]
 dependency_links = []
 if minor_version % 2:
     dependency_links.append(
diff -r 1d340eeea38f -r 099fb36301d6 stock.py
--- /dev/null   Thu Jan 01 00:00:00 1970 +0000
+++ b/stock.py  Wed May 11 23:32:24 2022 +0200
@@ -0,0 +1,55 @@
+# 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.modules.product import price_digits, round_price
+from trytond.pool import Pool, PoolMeta
+from trytond.transaction import Transaction
+
+
+class Move(metaclass=PoolMeta):
+    __name__ = 'stock.move'
+
+    commission_price = fields.Numeric(
+        "Commission Price", digits=price_digits, readonly=True)
+
+    @classmethod
+    def __setup__(cls):
+        super().__setup__()
+        cls._allow_modify_closed_period.add('commission_price')
+
+    @classmethod
+    def update_unit_price(cls, moves):
+        for move in moves:
+            if move.state == 'done':
+                commission_price = move._compute_commission_price()
+                if commission_price != move.commission_price:
+                    move.commission_price = commission_price
+        super().update_unit_price(moves)
+
+    def _compute_commission_price(self):
+        pool = Pool()
+        UoM = pool.get('product.uom')
+        Currency = pool.get('currency.currency')
+        total, quantity = 0, 0
+        for line in self.invoice_lines:
+            if line.invoice and line.invoice.state in {'posted', 'paid'}:
+                for commission in line.commissions:
+                    with Transaction().set_context(date=self.effective_date):
+                        amount = Currency.compute(
+                            commission.currency, commission.amount,
+                            self.currency)
+                        if line.invoice.type == commission.type_:
+                            total -= amount
+                        else:
+                            total += amount
+                if line.invoice.type == 'out' or not line.correction:
+                    quantity += UoM.compute_qty(
+                        line.unit, line.quantity, self.uom)
+        if quantity:
+            unit_price = round_price(total / Decimal(str(quantity)))
+        else:
+            unit_price = round_price(total)
+        return unit_price
diff -r 1d340eeea38f -r 099fb36301d6 stock_reporting_margin.py
--- /dev/null   Thu Jan 01 00:00:00 1970 +0000
+++ b/stock_reporting_margin.py Wed May 11 23:32:24 2022 +0200
@@ -0,0 +1,32 @@
+# 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 sql.aggregate import Sum
+from sql.conditionals import Coalesce
+
+from trytond.model import fields
+from trytond.pool import Pool, PoolMeta
+from trytond.transaction import Transaction
+
+
+class AbstractCommissionMixin:
+    __slots__ = ()
+
+    @classmethod
+    def _column_cost(cls, tables, withs, sign):
+        pool = Pool()
+        Move = pool.get('stock.move')
+        move = tables['move']
+        cost = super()._column_cost(tables, withs, sign)
+        if (Transaction().context.get('include_commission')
+                and 'commission_price' in Move._fields):
+            cost += Sum(
+                sign * cls.cost.sql_cast(move.internal_quantity)
+                * Coalesce(move.commission_price, 0))
+        return cost
+
+
+class Context(metaclass=PoolMeta):
+    __name__ = 'stock.reporting.margin.context'
+
+    include_commission = fields.Boolean("Include Commission")
diff -r 1d340eeea38f -r 099fb36301d6 stock_reporting_margin.xml
--- /dev/null   Thu Jan 01 00:00:00 1970 +0000
+++ b/stock_reporting_margin.xml        Wed May 11 23:32:24 2022 +0200
@@ -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="stock">
+        <record model="ir.ui.view" 
id="stock_reporting_margin_context_view_form">
+            <field name="model">stock.reporting.margin.context</field>
+            <field name="inherit" 
ref="stock.reporting_margin_context_view_form"/>
+            <field name="name">stock_reporting_margin_context_form</field>
+        </record>
+    </data>
+</tryton>
diff -r 1d340eeea38f -r 099fb36301d6 tests/scenario_commission_stock.rst
--- /dev/null   Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/scenario_commission_stock.rst       Wed May 11 23:32:24 2022 +0200
@@ -0,0 +1,212 @@
+=========================
+Commission Stock Scenario
+=========================
+
+Imports::
+
+    >>> from decimal import Decimal
+    >>> import datetime as dt
+
+    >>> 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)
+
+    >>> today = dt.date.today()
+    >>> yesterday = today - dt.timedelta(days=1)
+
+Activate modules::
+
+    >>> config = activate_modules(
+    ...     ['commission', 'sale', 'stock', 'account_invoice_stock'])
+
+    >>> Agent = Model.get('commission.agent')
+    >>> Commission = Model.get('commission')
+    >>> MarginProduct = Model.get('stock.reporting.margin.product')
+    >>> Party = Model.get('party.party')
+    >>> Plan = Model.get('commission.plan')
+    >>> ProductCategory = Model.get('product.category')
+    >>> Sale = Model.get('sale.sale')
+    >>> Template = Model.get('product.template')
+    >>> Uom = Model.get('product.uom')
+
+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 customer::
+
+    >>> customer = Party(name='Customer')
+    >>> customer.save()
+
+Create account 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 commission product::
+
+    >>> unit, = Uom.find([('name', '=', 'Unit')])
+    >>> template = Template()
+    >>> template.name = 'Commission'
+    >>> template.default_uom = unit
+    >>> template.type = 'service'
+    >>> template.list_price = Decimal(0)
+    >>> template.account_category = account_category
+    >>> template.save()
+    >>> commission_product, = template.products
+
+Create commission plans::
+
+    >>> plan1 = Plan(name='Plan 1')
+    >>> plan1.commission_product = commission_product
+    >>> plan1.commission_method = 'posting'
+    >>> line = plan1.lines.new()
+    >>> line.formula = 'amount * 0.1'
+    >>> plan1.save()
+
+    >>> plan2 = Plan(name='Plan 2')
+    >>> plan2.commission_product = commission_product
+    >>> plan2.commission_method = 'posting'
+    >>> line = plan2.lines.new()
+    >>> line.formula = 'amount * 0.05'
+    >>> plan2.save()
+
+Create agent::
+
+    >>> agent_party = Party(name='Agent')
+    >>> agent_party.save()
+    >>> agent = Agent(party=agent_party)
+    >>> agent.type_ = 'agent'
+    >>> agent.plan = plan1
+    >>> agent.currency = company.currency
+    >>> agent.save()
+
+Create principal::
+
+    >>> principal_party = Party(name='Principal')
+    >>> principal_party.save()
+    >>> principal = Agent(party=principal_party)
+    >>> principal.type_ = 'principal'
+    >>> principal.plan = plan2
+    >>> principal.currency = company.currency
+    >>> principal.save()
+
+Create product sold::
+
+    >>> template = Template()
+    >>> template.name = 'Product'
+    >>> template.default_uom = unit
+    >>> template.type = 'goods'
+    >>> template.salable = True
+    >>> template.list_price = Decimal('100.0000')
+    >>> template.account_category = account_category
+    >>> template.principals.append(principal)
+    >>> template.save()
+    >>> product, = template.products
+    >>> product.cost_price = Decimal('50.0000')
+    >>> product.save()
+
+Create a sale::
+
+    >>> sale = Sale()
+    >>> sale.party = customer
+    >>> sale.agent = agent
+    >>> line = sale.lines.new()
+    >>> line.product = product
+    >>> line.quantity = 5
+    >>> sale.click('quote')
+    >>> sale.click('confirm')
+    >>> sale.state
+    'processing'
+
+Ship in two steps::
+
+    >>> shipment, = sale.shipments
+    >>> move, = shipment.inventory_moves
+    >>> move.quantity = 3
+    >>> shipment.click('assign_force')
+    >>> shipment.click('pick')
+    >>> shipment.click('pack')
+    >>> shipment.click('done')
+    >>> shipment.state
+    'done'
+
+    >>> sale.reload()
+    >>> _, shipment = sale.shipments
+    >>> shipment.click('assign_force')
+    >>> shipment.click('pick')
+    >>> shipment.click('pack')
+    >>> shipment.click('done')
+    >>> shipment.state
+    'done'
+
+Post the invoice::
+
+    >>> invoice, = sale.invoices
+    >>> invoice.click('post')
+    >>> invoice.state
+    'posted'
+
+Check stock move::
+
+    >>> shipment, _ = sale.shipments
+    >>> move, = shipment.outgoing_moves
+    >>> move.commission_price
+    Decimal('-5.0000')
+
+Check reporting margin::
+
+    >>> context = {
+    ...     'from_date': yesterday,
+    ...     'to_date': today,
+    ...     'period': 'day',
+    ...     }
+    >>> with config.set_context(context=context):
+    ...     report, = MarginProduct.find([])
+    >>> report.cost == Decimal('250.0000')
+    True
+    >>> report.revenue == Decimal('500.0000')
+    True
+
+    >>> context['include_commission'] = True
+    >>> with config.set_context(context=context):
+    ...     report, = MarginProduct.find([])
+    >>> report.cost == Decimal('225.0000')
+    True
+    >>> report.revenue == Decimal('500.0000')
+    True
+
+Update commission amount::
+
+    >>> commission, = Commission.find([('agent.type_', '=', 'agent')])
+    >>> commission.amount = Decimal('60.0000')
+
+Create commission invoice::
+
+    >>> commission.click('invoice')
+
+Check stock move::
+
+    >>> move.reload()
+    >>> move.commission_price
+    Decimal('-7.0000')
diff -r 1d340eeea38f -r 099fb36301d6 tests/test_module.py
--- a/tests/test_module.py      Mon May 02 17:24:18 2022 +0200
+++ b/tests/test_module.py      Wed May 11 23:32:24 2022 +0200
@@ -49,7 +49,7 @@
         PartyCompanyCheckEraseMixin, CompanyTestMixin, ModuleTestCase):
     'Test Commission module'
     module = 'commission'
-    extras = ['sale']
+    extras = ['sale', 'stock', 'account_invoice_stock']
 
     @with_transaction()
     def test_plan_category(self):
diff -r 1d340eeea38f -r 099fb36301d6 tryton.cfg
--- a/tryton.cfg        Mon May 02 17:24:18 2022 +0200
+++ b/tryton.cfg        Wed May 11 23:32:24 2022 +0200
@@ -9,7 +9,9 @@
     product
     res
 extras_depend:
+    account_invoice_stock
     sale
+    stock
 xml:
     commission.xml
     commission_reporting.xml
@@ -19,3 +21,4 @@
     product.xml
     message.xml
     party.xml
+    stock_reporting_margin.xml
diff -r 1d340eeea38f -r 099fb36301d6 
view/stock_reporting_margin_context_form.xml
--- /dev/null   Thu Jan 01 00:00:00 1970 +0000
+++ b/view/stock_reporting_margin_context_form.xml      Wed May 11 23:32:24 
2022 +0200
@@ -0,0 +1,9 @@
+<?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='include_lost']" position="after">
+        <label name="include_commission"/>
+        <field name="include_commission" xexpand="0" width="25"/>
+    </xpath>
+</data>

Reply via email to