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>