diff --git a/easy_my_coop_api/services/__init__.py b/easy_my_coop_api/services/__init__.py index d657c40..546abd6 100644 --- a/easy_my_coop_api/services/__init__.py +++ b/easy_my_coop_api/services/__init__.py @@ -1,2 +1,3 @@ from . import ping_service from . import subscription_request_service +from . import account_invoice_service diff --git a/easy_my_coop_api/services/account_invoice_service.py b/easy_my_coop_api/services/account_invoice_service.py index e69de29..1eda236 100644 --- a/easy_my_coop_api/services/account_invoice_service.py +++ b/easy_my_coop_api/services/account_invoice_service.py @@ -0,0 +1,81 @@ +# Copyright 2019 Coop IT Easy SCRL fs +# Robin Keunen +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). +# pylint: disable=consider-merging-classes-inherited + +import logging + +from werkzeug.exceptions import NotFound + +from odoo import _ +from odoo.fields import Date + +from odoo.addons.base_rest.http import wrapJsonException +from odoo.addons.component.core import Component + +from . import schemas + +_logger = logging.getLogger(__name__) + + +class AccountInvoiceService(Component): + _inherit = "base.rest.service" + _name = "account.invoice.services" + _usage = "invoice" + _collection = "emc.services" + _description = """ + Account Invoice Services + """ + + def get(self, _id): + sr = self.env["account.invoice"].search( + [("_api_external_id", "=", _id)] + ) + if sr: + return self._to_dict(sr) + else: + raise wrapJsonException( + NotFound(_("No invoice found for id %s") % _id) + ) + + def _to_dict(self, invoice): + invoice.ensure_one() + + if invoice.subscription_request: + sr_external_id = invoice.subscription_request.get_api_external_id() + else: + sr_external_id = None + + # todo return dictionaries for Many2one fields + data = { + "id": invoice.get_api_external_id(), + "name": invoice.name, + "state": invoice.state, + "type": invoice.type, + "date": Date.to_string(invoice.date), + "date_due": Date.to_string(invoice.date_due), + "date_invoice": Date.to_string(invoice.date_invoice), + "partner": invoice.partner_id.get_api_external_id(), + "journal": invoice.journal_id.get_api_external_id(), + "account": invoice.account_id.get_api_external_id(), + "subscription_request": sr_external_id, + "invoice_lines": [ + self._line_to_dict(line) for line in invoice.invoice_line_ids + ], + } + return data + + def _line_to_dict(self, line): + return { + "name": line.name, + "account": line.account_id.get_api_external_id(), + "product": line.product_id.product_tmpl_id.get_api_external_id(), + "quantity": line.quantity, + "price_unit": line.price_unit, + } + + def _validator_get(self): + return schemas.S_INVOICE_GET + + def _validator_return_get(self): + return schemas.S_INVOICE_RETURN_GET diff --git a/easy_my_coop_api/services/schemas.py b/easy_my_coop_api/services/schemas.py index 2b374f4..a1886fc 100644 --- a/easy_my_coop_api/services/schemas.py +++ b/easy_my_coop_api/services/schemas.py @@ -9,7 +9,7 @@ from odoo.fields import Date def date_validator(field, value, error): try: Date.from_string(value) - except ValueError as e: + except ValueError: return error( field, _("{} does not match format '%Y-%m-%d'".format(value)) ) @@ -101,3 +101,36 @@ S_SUBSCRIPTION_REQUEST_UPDATE = { } S_SUBSCRIPTION_REQUEST_VALIDATE = {"_id": {"type": "integer"}} + +S_INVOICE_GET = {"_id": {"type": "integer"}} + +S_INVOICE_LINE_RETURN_GET = { + "type": "list", + "schema": { + "type": "dict", + "schema": { + "name": {"type": "string", "required": True}, + "account": {"type": "integer", "required": True}, + "product": {"type": "integer", "required": True}, + "quantity": {"type": "float", "required": True}, + "price_unit": {"type": "float", "required": True}, + }, + }, + "required": True, + "empty": True, +} + +S_INVOICE_RETURN_GET = { + "id": {"type": "integer", "required": True}, + "name": {"type": "string", "required": True, "empty": False}, + "state": {"type": "string", "required": True, "empty": False}, + "type": {"type": "string", "required": True, "empty": False}, + "date": {"type": "string", "required": True, "empty": False}, + "date_due": {"type": "string", "required": True, "empty": False}, + "date_invoice": {"type": "string", "required": True, "empty": False}, + "partner": {"type": "integer", "required": True}, + "journal": {"type": "integer", "required": True}, + "account": {"type": "integer", "required": True}, + "subscription_request": {"type": "integer", "nullable": True}, + "invoice_lines": S_INVOICE_LINE_RETURN_GET, +} diff --git a/easy_my_coop_api/tests/__init__.py b/easy_my_coop_api/tests/__init__.py index a7be43d..b3a4cd1 100644 --- a/easy_my_coop_api/tests/__init__.py +++ b/easy_my_coop_api/tests/__init__.py @@ -2,3 +2,4 @@ from . import test_ping from . import test_registry from . import test_external_id_mixin from . import test_subscription_requests +from . import test_account_invoice diff --git a/easy_my_coop_api/tests/common.py b/easy_my_coop_api/tests/common.py index d959400..763b66b 100644 --- a/easy_my_coop_api/tests/common.py +++ b/easy_my_coop_api/tests/common.py @@ -24,6 +24,163 @@ class BaseEMCRestCase(BaseRestCase): cls.api_key_test = cls.env.ref( "easy_my_coop_api.auth_api_key_manager_emc_demo" ) + cls._chart_template_create() + cls._add_chart_of_accounts() + cls._journals_setup() + + def setUp(self): + super().setUp() + self.session = requests.Session() + + @classmethod + def _chart_template_create(cls): + transfer_account_id = cls.env["account.account.template"].create( + { + "code": "000", + "name": "Liquidity Transfers", + "reconcile": True, + "user_type_id": cls.env.ref( + "account.data_account_type_current_assets" + ).id, + } + ) + cls.chart = cls.env["account.chart.template"].create( + { + "name": "Test COA", + "code_digits": 4, + "bank_account_code_prefix": 1014, + "cash_account_code_prefix": 1014, + "currency_id": cls.env.ref("base.USD").id, + "transfer_account_code_prefix": "000", + } + ) + transfer_account_id.update({"chart_template_id": cls.chart.id}) + cls.env["ir.model.data"].create( + { + "res_id": transfer_account_id.id, + "model": transfer_account_id._name, + "name": "Liquidity Transfers", + } + ) + act = cls.env["account.account.template"].create( + { + "code": "001", + "name": "Expenses", + "user_type_id": cls.env.ref( + "account.data_account_type_expenses" + ).id, + "chart_template_id": cls.chart.id, + "reconcile": True, + } + ) + cls.env["ir.model.data"].create( + {"res_id": act.id, "model": act._name, "name": "expenses"} + ) + act = cls.env["account.account.template"].create( + { + "code": "002", + "name": "Product Sales", + "user_type_id": cls.env.ref( + "account.data_account_type_revenue" + ).id, + "chart_template_id": cls.chart.id, + "reconcile": True, + } + ) + cls.env["ir.model.data"].create( + {"res_id": act.id, "model": act._name, "name": "sales"} + ) + act = cls.env["account.account.template"].create( + { + "code": "003", + "name": "Account Receivable", + "user_type_id": cls.env.ref( + "account.data_account_type_receivable" + ).id, + "chart_template_id": cls.chart.id, + "reconcile": True, + } + ) + cls.env["ir.model.data"].create( + {"res_id": act.id, "model": act._name, "name": "receivable"} + ) + act = cls.env["account.account.template"].create( + { + "code": "004", + "name": "Account Payable", + "user_type_id": cls.env.ref( + "account.data_account_type_payable" + ).id, + "chart_template_id": cls.chart.id, + "reconcile": True, + } + ) + cls.env["ir.model.data"].create( + {"res_id": act.id, "model": act._name, "name": "payable"} + ) + + @classmethod + def _add_chart_of_accounts(cls): + cls.company = cls.env.user.company_id + cls.chart.try_loading_for_current_company() + cls.revenue = cls.env["account.account"].search( + [ + ( + "user_type_id", + "=", + cls.env.ref("account.data_account_type_revenue").id, + ) + ], + limit=1, + ) + cls.expense = cls.env["account.account"].search( + [ + ( + "user_type_id", + "=", + cls.env.ref("account.data_account_type_expenses").id, + ) + ], + limit=1, + ) + cls.receivable = cls.env["account.account"].search( + [ + ( + "user_type_id", + "=", + cls.env.ref("account.data_account_type_receivable").id, + ) + ], + limit=1, + ) + cls.payable = cls.env["account.account"].search( + [ + ( + "user_type_id", + "=", + cls.env.ref("account.data_account_type_payable").id, + ) + ], + limit=1, + ) + cls.equity_account = cls.env.ref("easy_my_coop.account_equity_demo") + cls.cooperator_account = cls.env.ref( + "easy_my_coop.account_cooperator_demo" + ) + return True + + @classmethod + def _journals_setup(cls): + cls.subscription_journal = cls.env.ref( + "easy_my_coop.subscription_journal" + ) + cls.subscription_journal.write( + { + "default_debit_account_id": cls.equity_account.id, + "default_credit_account_id": cls.equity_account.id, + } + ) + return True def _add_api_key(self, headers): key_dict = {"API-KEY": self.api_key_test.key} @@ -33,10 +190,6 @@ class BaseEMCRestCase(BaseRestCase): headers = key_dict return headers - def setUp(self): - super().setUp() - self.session = requests.Session() - def http_get(self, url, headers=None): headers = self._add_api_key(headers) if url.startswith("/"): diff --git a/easy_my_coop_api/tests/test_account_invoice.py b/easy_my_coop_api/tests/test_account_invoice.py new file mode 100644 index 0000000..3fdf07a --- /dev/null +++ b/easy_my_coop_api/tests/test_account_invoice.py @@ -0,0 +1,101 @@ +# Copyright 2020 Coop IT Easy SCRL fs +# Robin Keunen +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +from odoo.fields import Date + +from odoo.addons.base_rest.controllers.main import _PseudoCollection +from odoo.addons.component.core import WorkContext + +from .common import BaseEMCRestCase + + +class TestAccountInvoiceController(BaseEMCRestCase): + @classmethod + def setUpClass(cls, *args, **kwargs): + super().setUpClass(*args, **kwargs) + + def setUp(self): + res = super().setUp() + collection = _PseudoCollection("emc.services", self.env) + emc_services_env = WorkContext( + model_name="rest.service.registration", collection=collection + ) + self.ai_service = emc_services_env.component(usage="invoice") + + self.share_type_A = self.browse_ref( + "easy_my_coop.product_template_share_type_1_demo" + ) + self._capital_release_create() + + today = Date.to_string(Date.today()) + self.demo_invoice_dict = { + "id": 1, + "name": "Capital Release Example", + "partner": 1, + "account": 1, + "journal": 1, + "subscription_request": None, + "state": "open", + "date": today, + "date_invoice": today, + "date_due": today, + "type": "out_invoice", + "invoice_lines": [ + { + "account": 2, + "name": "Share Type A", + "price_unit": 100.0, + "product": 1, + "quantity": 2.0, + } + ], + } + return res + + def _capital_release_create(self): + self.coop_candidate = self.env["res.partner"].create( + { + "name": "Catherine des Champs", + "company_id": self.company.id, + "property_account_receivable_id": self.receivable.id, + "property_account_payable_id": self.payable.id, + } + ) + + capital_release_line = [ + ( + 0, + False, + { + "name": "Share Type A", + "account_id": self.equity_account.id, + "quantity": 2.0, + "price_unit": 100.0, + "product_id": self.share_type_A.product_variant_id.id, + }, + ) + ] + + self.capital_release = self.env["account.invoice"].create( + { + "name": "Capital Release Example", + "partner_id": self.coop_candidate.id, + "type": "out_invoice", + "invoice_line_ids": capital_release_line, + "account_id": self.cooperator_account.id, + "journal_id": self.subscription_journal.id, + } + ) + self.capital_release.action_invoice_open() + + def test_service_get(self): + external_id = self.capital_release.get_api_external_id() + result = self.ai_service.get(external_id) + self.assertEquals(self.demo_invoice_dict, result) + + def test_route_get(self): + external_id = self.capital_release.get_api_external_id() + route = "/api/invoice/%s" % external_id + content = self.http_get_content(route) + self.assertEquals(self.demo_invoice_dict, content)