diff --git a/recurring_consignment/demo/account_account.xml b/recurring_consignment/demo/account_account.xml
index 1c9b91fd..db6ca9de 100644
--- a/recurring_consignment/demo/account_account.xml
+++ b/recurring_consignment/demo/account_account.xml
@@ -6,6 +6,9 @@ License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
-->
+
+
+
Demo Account Receivable
411
@@ -38,6 +41,27 @@ License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
+
+ Cash Suspense Account
+ 5xx
+ asset_current
+
+
+
+
+
+
+
Commission Account
706
@@ -52,4 +76,8 @@ License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
+
+
+
+
diff --git a/recurring_consignment/demo/account_journal.xml b/recurring_consignment/demo/account_journal.xml
index 108ea38d..944fbf9e 100644
--- a/recurring_consignment/demo/account_journal.xml
+++ b/recurring_consignment/demo/account_journal.xml
@@ -4,20 +4,32 @@ Copyright (C) 2015 - Today: GRAP (http://www.grap.coop)
@author: Sylvain LE GAL (https://twitter.com/legalsylvain)
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
-->
-
+
+
- Demo Sale Journal For Consignor
+ Demo Sale Journal
sale
- SJ-CONS
+ SALE
+
+ Demo Cash Journal
+ cash
+ CASH
+
+
+
+
+
+
+
Demo Misc Journal For Consignor
general
- MC-CONS
+ MISC
diff --git a/recurring_consignment/demo/product_pricelist.xml b/recurring_consignment/demo/product_pricelist.xml
index 8726e37e..e23f2a5a 100644
--- a/recurring_consignment/demo/product_pricelist.xml
+++ b/recurring_consignment/demo/product_pricelist.xml
@@ -8,7 +8,8 @@ License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
My Pricelist (-10%)
-
+
+
@@ -21,7 +22,8 @@ License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
My Pricelist (-50%)
-
+
+
diff --git a/recurring_consignment/models/account_move.py b/recurring_consignment/models/account_move.py
index e9882108..7f7941ab 100644
--- a/recurring_consignment/models/account_move.py
+++ b/recurring_consignment/models/account_move.py
@@ -155,7 +155,7 @@ def _get_commission_information_product_detail_grouped(self):
# Get related invoice lines
com_invoice_lines = self.mapped(
"invoice_line_ids.consignment_invoice_line_ids"
- ).filtered(lambda x: x.display_type == "product")
+ ).filtered(lambda x: x.display_type == "product" and x.product_id)
for com_invoice_line in com_invoice_lines:
key = (
diff --git a/recurring_consignment/models/res_partner.py b/recurring_consignment/models/res_partner.py
index b54de746..0087f1bc 100644
--- a/recurring_consignment/models/res_partner.py
+++ b/recurring_consignment/models/res_partner.py
@@ -3,7 +3,7 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from odoo import _, api, fields, models
-from odoo.exceptions import Warning as UserError
+from odoo.exceptions import UserError
class ResPartner(models.Model):
@@ -35,7 +35,7 @@ def _check_is_consignor_consignment_account_id(self):
if partner.is_consignor:
if not partner.consignment_account_id:
raise UserError(
- _("A Consignor must have a 'Consignment Account'" " defined.")
+ _("A Consignor must have a 'Consignment Account' defined.")
)
else:
if (
@@ -48,15 +48,3 @@ def _check_is_consignor_consignment_account_id(self):
" Commission' neither 'Consignment Account' defined."
)
)
-
- # TODO prevent check and uncheck. (only via wizard is possible)
- # # Overload Section
- # def write(self, vals):
- # if not vals.get("is_consignor", True) and any(self.mapped("is_consignor")):
- # raise UserError(
- # _(
- # "You can not unset consignor setting on partner.\n"
- # " Please create a new one if you want to do so."
- # )
- # )
- # return super().write(vals)
diff --git a/recurring_consignment/tests/test_make_commissions.py b/recurring_consignment/tests/test_make_commissions.py
index c992e62e..0a0d9418 100644
--- a/recurring_consignment/tests/test_make_commissions.py
+++ b/recurring_consignment/tests/test_make_commissions.py
@@ -9,7 +9,7 @@
@tagged("post_install", "-at_install")
-class TestCreateConsignors(TransactionCase):
+class TestMakeCommissions(TransactionCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
@@ -73,13 +73,13 @@ def test_30_commission_workflow(self):
self.assertEqual(
len(commission_invoice.invoice_line_ids),
1,
- "Two commission lines should be generated",
+ "One commission line should be generated",
)
self.assertEqual(
len(lines_20), 1, "One 20% commission line should be generated"
)
- # Check line #2 details (Tax Excl)
+ # Check line details (Tax Excl)
line_20 = lines_20[0]
self.assertEqual(line_20.quantity, 1, "Incorrect Commission Quantity.")
self.assertEqual(
diff --git a/recurring_consignment_pos/__manifest__.py b/recurring_consignment_pos/__manifest__.py
index d9c36ef1..674db3ee 100644
--- a/recurring_consignment_pos/__manifest__.py
+++ b/recurring_consignment_pos/__manifest__.py
@@ -11,12 +11,16 @@
"website": "https://github.com/grap/grap-odoo-business",
"license": "AGPL-3",
"depends": [
- "recurring_consignment",
+ # Odoo
"point_of_sale",
+ # GRAP
+ "recurring_consignment",
],
- "data": [
- "views/view_account_invoice.xml",
- "views/templates.xml",
+ "data": ["views/view_account_move.xml"],
+ "demo": [
+ "demo/pos_payment_method.xml",
+ "demo/pos_config.xml",
+ "demo/product_product.xml",
],
"installable": True,
"auto_install": True,
diff --git a/recurring_consignment_pos/demo/pos_config.xml b/recurring_consignment_pos/demo/pos_config.xml
new file mode 100644
index 00000000..591292c7
--- /dev/null
+++ b/recurring_consignment_pos/demo/pos_config.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+ Pos Config
+
+
+
+
+
+
+
+
+
diff --git a/recurring_consignment_pos/demo/pos_payment_method.xml b/recurring_consignment_pos/demo/pos_payment_method.xml
new file mode 100644
index 00000000..71453d9e
--- /dev/null
+++ b/recurring_consignment_pos/demo/pos_payment_method.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+ Cash Method
+
+
+
+
+
diff --git a/recurring_consignment_pos/demo/product_product.xml b/recurring_consignment_pos/demo/product_product.xml
new file mode 100644
index 00000000..5cc7d9ce
--- /dev/null
+++ b/recurring_consignment_pos/demo/product_product.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ My Consigned Product E (VAT 20% - Consignor 1)
+
+
+
+
+
+
+
+
diff --git a/recurring_consignment_pos/models/__init__.py b/recurring_consignment_pos/models/__init__.py
index e4b9bbb2..5570f965 100644
--- a/recurring_consignment_pos/models/__init__.py
+++ b/recurring_consignment_pos/models/__init__.py
@@ -1,3 +1,2 @@
-from . import account_invoice
-from . import pos_order
+from . import account_move
from . import product_template
diff --git a/recurring_consignment_pos/models/account_invoice.py b/recurring_consignment_pos/models/account_move.py
similarity index 79%
rename from recurring_consignment_pos/models/account_invoice.py
rename to recurring_consignment_pos/models/account_move.py
index df1164d5..62e9acdd 100644
--- a/recurring_consignment_pos/models/account_invoice.py
+++ b/recurring_consignment_pos/models/account_move.py
@@ -2,11 +2,11 @@
# @author: Sylvain LE GAL (https://twitter.com/legalsylvain)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
-from odoo import api, models
+from odoo import models
-class AccountInvoice(models.Model):
- _inherit = "account.invoice"
+class AccountMove(models.Model):
+ _inherit = "account.move"
# View Section
def button_commission_view_pos_order_lines(self):
@@ -17,13 +17,32 @@ def button_commission_view_pos_order_lines(self):
return action_data
# Private Section
+ def _get_commission_information_product_detail_grouped(self):
+ groups = super()._get_commission_information_product_detail_grouped()
+
+ # Get related pos order lines
+ com_order_lines = self._get_commission_related_pos_order_lines()
+ for com_order_line in com_order_lines:
+ key = (
+ com_order_line.product_id,
+ com_order_line.price_unit,
+ com_order_line.discount,
+ )
+ groups.setdefault(key, {"quantity": 0, "total_vat_excl": 0})
+ groups[key] = {
+ "quantity": groups[key]["quantity"] + com_order_line.qty,
+ "total_vat_excl": groups[key]["total_vat_excl"]
+ + com_order_line.price_subtotal,
+ }
+ return groups
+
def _get_commission_related_pos_order_lines(self):
PosOrder = self.env["pos.order"]
PosOrderLine = self.env["pos.order.line"]
ProductProduct = self.env["product.product"]
# Get Account Move
- moves = self.mapped("consignment_line_ids.move_id")
+ moves = self.mapped("invoice_line_ids.consignment_invoice_line_ids.move_id")
# Get Product ids
consignor_products = ProductProduct.with_context(active_test=False).search(
@@ -31,7 +50,7 @@ def _get_commission_related_pos_order_lines(self):
)
# Get related pos orders
- com_orders = PosOrder.search([("account_move", "in", moves.ids)])
+ com_orders = PosOrder.search([("session_move_id", "in", moves.ids)])
# We add pos.order.line sales, that are not invoiced
# because the lines are still include in the module
@@ -44,29 +63,3 @@ def _get_commission_related_pos_order_lines(self):
("product_id", "in", consignor_products.ids),
]
)
-
- def _get_commission_information_product_detail_grouped(self):
- groups = super().get_commission_information_product_detail_grouped()
-
- # Get related pos order lines
- com_order_lines = self._get_commission_related_pos_order_lines()
-
- for com_order_line in com_order_lines:
- key = (
- com_order_line.product_id.id,
- com_order_line.price_unit,
- com_order_line.discount,
- )
- groups.setdefault(
- key,
- {
- "quantity": 0,
- "total_vat_excl": 0,
- },
- )
- groups[key] = {
- "quantity": groups[key]["quantity"] + com_order_line.qty,
- "total_vat_excl": groups[key]["total_vat_excl"]
- + com_order_line.price_subtotal,
- }
- return groups
diff --git a/recurring_consignment_pos/models/pos_order.py b/recurring_consignment_pos/models/pos_order.py
deleted file mode 100644
index 8f648027..00000000
--- a/recurring_consignment_pos/models/pos_order.py
+++ /dev/null
@@ -1,15 +0,0 @@
-# Copyright (C) 2018 - Today: GRAP (http://www.grap.coop)
-# @author: Sylvain LE GAL (https://twitter.com/legalsylvain)
-# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
-
-from odoo import _, api, models
-from odoo.exceptions import UserError
-
-
-class PosOrder(models.Model):
- _inherit = "pos.order"
-
- @api.constrains("partner_id")
- def _check_partner_id_recurring_consignment(self):
- if self.partner_id.is_consignor:
- raise UserError(_("You can not make PoS Orders to consignors"))
diff --git a/recurring_consignment_pos/models/product_template.py b/recurring_consignment_pos/models/product_template.py
index 1f6ad04a..e39d7302 100644
--- a/recurring_consignment_pos/models/product_template.py
+++ b/recurring_consignment_pos/models/product_template.py
@@ -2,8 +2,8 @@
# @author: Sylvain LE GAL (https://twitter.com/legalsylvain)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
-from odoo import _, api, models
-from odoo.exceptions import Warning as UserError
+from odoo import _, models
+from odoo.exceptions import ValidationError
class ProductTemplate(models.Model):
@@ -11,23 +11,30 @@ class ProductTemplate(models.Model):
# Constrains Section
def _check_consignor_changes(self, vals):
- super()._check_consignor_changes(vals)
+ res = super()._check_consignor_changes(vals)
+ PosSession = self.env["pos.session"]
+ if PosSession.search([("state", "!=", "closed")]):
+ raise ValidationError(
+ _(
+ "You can not change the value of the field"
+ " 'Consignor' because a Pos Session is opened."
+ " Please make such changement when sessions"
+ " are closed."
+ )
+ )
+
PosOrderLine = self.env["pos.order.line"]
- if vals.get("consignor_partner_id", False):
- for template in self:
- product_ids = template.product_variant_ids.ids
- if template.consignor_partner_id.id != vals.get(
- "consignor_partner_id", False
- ):
- order_lines = PosOrderLine.search(
- [("product_id", "in", product_ids)]
+ for template in self:
+ order_lines = PosOrderLine.search(
+ [("product_id", "in", template.product_variant_ids.ids)]
+ )
+ if len(order_lines):
+ raise ValidationError(
+ _(
+ "You can not change the value of the field"
+ " 'Consignor' because the product is associated"
+ " to one or more PoS Order Lines. You should"
+ " disable the product and create a new one."
)
- if len(order_lines):
- raise UserError(
- _(
- "You can not change the value of the field"
- " 'Consignor' because the product is associated"
- " to one or more PoS Order Lines. You should"
- " disable the product and create a new one."
- )
- )
+ )
+ return res
diff --git a/recurring_consignment_pos/static/src/js/recurring_consignment.js b/recurring_consignment_pos/static/src/js/recurring_consignment.js
deleted file mode 100644
index 5fe3a035..00000000
--- a/recurring_consignment_pos/static/src/js/recurring_consignment.js
+++ /dev/null
@@ -1,24 +0,0 @@
-// Copyright (C) 2014 - Today: GRAP (http://www.grap.coop)
-// @author: Sylvain LE GAL (https://twitter.com/legalsylvain)
-// License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
-
-odoo.define('recurring_consignment_pos.recurring_consignment', function (require) {
- 'use strict';
-
- var models = require('point_of_sale.models');
-
- var PosModel_super = models.PosModel.prototype;
-
- models.PosModel = models.PosModel.extend({
- load_server_data: function () {
- var self = this;
- _.each(self.models, function (item) {
- if (item.model === 'res.partner') {
- item.domain.push(['is_consignor', '=', false]);
- }
- });
- return PosModel_super.load_server_data.apply(this, arguments);
- },
- });
-
-});
diff --git a/recurring_consignment_pos/tests/__init__.py b/recurring_consignment_pos/tests/__init__.py
new file mode 100644
index 00000000..53a55243
--- /dev/null
+++ b/recurring_consignment_pos/tests/__init__.py
@@ -0,0 +1,3 @@
+from . import test_abstract
+from . import test_product_settings
+from . import test_make_commissions
diff --git a/recurring_consignment_pos/tests/test_abstract.py b/recurring_consignment_pos/tests/test_abstract.py
new file mode 100644
index 00000000..f038501f
--- /dev/null
+++ b/recurring_consignment_pos/tests/test_abstract.py
@@ -0,0 +1,77 @@
+# Copyright (C) 2024 - Today: GRAP (http://www.grap.coop)
+# @author: Sylvain LE GAL (https://twitter.com/legalsylvain)
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
+
+from odoo import Command
+from odoo.tests.common import TransactionCase, tagged
+
+
+@tagged("post_install", "-at_install")
+class TestAbstract(TransactionCase):
+ @classmethod
+ def setUpClass(cls):
+ super().setUpClass()
+ cls.PosOrder = cls.env["pos.order"]
+ cls.env.user = cls.env.ref("base.user_admin")
+ cls.env.user.company_id = cls.env.ref("recurring_consignment.company")
+ cls.env.company = cls.env.ref("recurring_consignment.company")
+ cls.config = cls.env.ref("recurring_consignment_pos.pos_config")
+ cls.product_E = cls.env.ref(
+ "recurring_consignment_pos.consigned_product_consignor_1_vat_20_E"
+ )
+ cls.cash_pm1 = cls.env.ref("recurring_consignment_pos.payment_method_cash")
+ cls.sales_account = cls.env.ref(
+ "recurring_consignment.account_income_commission"
+ )
+
+ def _make_pos_order(self, product=False, close_session=False):
+ self.config.open_ui()
+ self.pos_session = self.config.current_session_id
+
+ line_vals = {
+ "id": 1,
+ "qty": 1,
+ "price_unit": 1,
+ "price_subtotal": 1,
+ "price_subtotal_incl": 1.20,
+ "discount": 0,
+ "product_id": product.id,
+ "tax_ids": [[6, False, product.taxes_id.ids]],
+ "full_product_name": product.name,
+ }
+ statement_vals = {
+ "name": "2024-06-26 23:28:47",
+ "payment_method_id": 1,
+ "amount": 1.20,
+ }
+
+ order_data = {
+ "id": "00003-001-0001",
+ "data": {
+ "sequence_number": 1,
+ "name": "Order 00099-099-0099",
+ "pos_session_id": self.pos_session.id,
+ "creation_date": "2024-06-26T23:28:47.947Z",
+ "access_token": "a89e1005-6f4c-4aa6-9b0f-9bc27a5379ff",
+ "user_id": self.env.user.id,
+ "pricelist_id": 1,
+ "fiscal_position_id": False,
+ "partner_id": False,
+ "amount_paid": 1.20,
+ "amount_total": 1.20,
+ "amount_tax": 0.20,
+ "amount_return": 0,
+ "lines": [Command.create(line_vals)],
+ "statement_ids": [Command.create(statement_vals)],
+ },
+ "to_invoice": False,
+ }
+
+ orders = self.PosOrder.create_from_ui([order_data])
+
+ self.assertEqual(len(orders), 1)
+
+ if close_session:
+ self.pos_session.close_session_from_ui()
+
+ return self.pos_session, orders
diff --git a/recurring_consignment_pos/tests/test_make_commissions.py b/recurring_consignment_pos/tests/test_make_commissions.py
new file mode 100644
index 00000000..1e334d5d
--- /dev/null
+++ b/recurring_consignment_pos/tests/test_make_commissions.py
@@ -0,0 +1,62 @@
+# Copyright (C) 2024 - Today: GRAP (http://www.grap.coop)
+# @author: Sylvain LE GAL (https://twitter.com/legalsylvain)
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
+
+from odoo.tests.common import tagged
+
+from odoo.addons.recurring_consignment.tests.test_make_commissions import (
+ TestMakeCommissions,
+)
+
+from .test_abstract import TestAbstract
+
+
+@tagged("post_install", "-at_install")
+class TestMakeCommissionPointOfSale(TestAbstract, TestMakeCommissions):
+ @classmethod
+ def setUpClass(cls):
+ super().setUpClass()
+
+ def test_50_commission_workflow_point_of_sale(self):
+ self._make_pos_order(product=self.product_E, close_session=True)
+ commission_invoices = self._make_commission(self.consignor_1)
+
+ self.assertEqual(
+ len(commission_invoices), 1, "It should generate one invoice commission"
+ )
+
+ commission_invoice = commission_invoices[0]
+
+ self.assertEqual(commission_invoice.state, "draft")
+
+ lines_20 = commission_invoice.invoice_line_ids.filtered(
+ lambda x: x.product_id.id == self.commission_product_vat_20.id
+ )
+ # check invoice lines generated
+ self.assertEqual(
+ len(commission_invoice.invoice_line_ids),
+ 1,
+ "One commission line should be generated",
+ )
+ self.assertEqual(
+ len(lines_20), 1, "One 20% commission line should be generated"
+ )
+
+ # Check line #2 details (Tax Excl)
+ line_20 = lines_20[0]
+ self.assertEqual(line_20.quantity, 1, "Incorrect Commission Quantity.")
+ self.assertEqual(
+ line_20.price_unit, 0.2, "Incorrect Commission Price Unit, awaiting 1 * 0.2"
+ )
+ self.assertEqual(
+ line_20.tax_ids.ids,
+ [self.vat_20_exclude.id],
+ "Incorrect Commission Tax.",
+ )
+
+ self.IrActionsReport._render_qweb_pdf(
+ "account.account_invoices", commission_invoices.ids
+ )
+
+ commission_invoice.action_post()
+ self.assertEqual(commission_invoice.payment_state, "paid")
diff --git a/recurring_consignment_pos/tests/test_product_settings.py b/recurring_consignment_pos/tests/test_product_settings.py
new file mode 100644
index 00000000..b6c1f09f
--- /dev/null
+++ b/recurring_consignment_pos/tests/test_product_settings.py
@@ -0,0 +1,64 @@
+# Copyright (C) 2015 - Today: GRAP (http://www.grap.coop)
+# @author: Sylvain LE GAL (https://twitter.com/legalsylvain)
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
+
+from odoo.exceptions import ValidationError
+from odoo.tests.common import tagged
+
+from odoo.addons.recurring_consignment.tests.test_product_settings import (
+ TestProductSettings,
+)
+
+from .test_abstract import TestAbstract
+
+
+@tagged("post_install", "-at_install")
+class TestProductSettingsPointOfSale(TestAbstract, TestProductSettings):
+ @classmethod
+ def setUpClass(cls):
+ super().setUpClass()
+ cls.product_D = cls.env.ref(
+ "recurring_consignment.consigned_product_consignor_1_vat_20_D"
+ )
+
+ def test_51_change_consignor_no_pos_order(self):
+ """Test if it's possible to change a consignor for a product
+ that has not been saled in a pos order"""
+ self.product_E.product_tmpl_id.write(
+ {
+ "consignor_partner_id": self.consignor_2.id,
+ "fiscal_classification_id": self.fiscal_classification_0_consignor_2.id,
+ }
+ )
+
+ def test_52_change_consignor_openened_session(self):
+ """Test if it's impossible to change a consignor for a product
+ if a session is opened"""
+ self._make_pos_order(product=self.product_D, close_session=False)
+ with self.assertRaises(ValidationError):
+ new_vals = {
+ "consignor_partner_id": self.consignor_2.id,
+ "fiscal_classification_id": self.fiscal_classification_0_consignor_2.id,
+ }
+ self.product_E.product_tmpl_id.write(new_vals)
+
+ def test_53_change_consignor_closed_session(self):
+ """Test if it's possible to change a consignor for a product
+ if a session is closed and the product has not been sold"""
+ self._make_pos_order(product=self.product_D, close_session=True)
+ new_vals = {
+ "consignor_partner_id": self.consignor_2.id,
+ "fiscal_classification_id": self.fiscal_classification_0_consignor_2.id,
+ }
+ self.product_E.product_tmpl_id.write(new_vals)
+
+ def test_54_change_consignor_with_pos_order(self):
+ """Test if it's impossible to change a consignor for a product
+ that has been saled in a pos order"""
+ self._make_pos_order(product=self.product_E, close_session=True)
+ with self.assertRaises(ValidationError):
+ new_vals = {
+ "consignor_partner_id": self.consignor_2.id,
+ "fiscal_classification_id": self.fiscal_classification_0_consignor_2.id,
+ }
+ self.product_E.product_tmpl_id.write(new_vals)
diff --git a/recurring_consignment_pos/views/templates.xml b/recurring_consignment_pos/views/templates.xml
deleted file mode 100644
index 4f22968b..00000000
--- a/recurring_consignment_pos/views/templates.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
diff --git a/recurring_consignment_pos/views/view_account_invoice.xml b/recurring_consignment_pos/views/view_account_move.xml
similarity index 57%
rename from recurring_consignment_pos/views/view_account_invoice.xml
rename to recurring_consignment_pos/views/view_account_move.xml
index 5432f5cc..ee399703 100644
--- a/recurring_consignment_pos/views/view_account_invoice.xml
+++ b/recurring_consignment_pos/views/view_account_move.xml
@@ -6,15 +6,13 @@ License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
-->
-
- account.invoice
-
+
+ account.move
+
-
-
-
+