Skip to content

Commit

Permalink
Merge PR #781 into 13.0
Browse files Browse the repository at this point in the history
Signed-off-by simahawk
  • Loading branch information
shopinvader-git-bot committed Oct 7, 2020
2 parents c92ee69 + f443148 commit b1ff2b2
Show file tree
Hide file tree
Showing 6 changed files with 238 additions and 45 deletions.
38 changes: 38 additions & 0 deletions shopinvader/models/shopinvader_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from contextlib import contextmanager

from odoo import _, api, fields, models, tools
from odoo.addons.base_sparse_field.models.fields import Serialized
from odoo.addons.server_environment import serv_config
from odoo.http import request

Expand Down Expand Up @@ -193,6 +194,34 @@ class ShopinvaderBackend(models.Model):
string="Available partner industries",
default=lambda self: self._default_partner_industry_ids(),
)
# Invoice settings
invoice_settings = Serialized(
# Default values on the sparse fields work only for create
# and does not provide defaults for existing records.
default={
"invoice_linked_to_sale_only": True,
"invoice_access_open": False,
}
)
invoice_linked_to_sale_only = fields.Boolean(
default=True,
string="Only sale invoices",
help="Only serve invoices that are linked to a sale order.",
sparse="invoice_settings",
)
invoice_access_open = fields.Boolean(
default=False,
string="Open invoices",
help="Give customer access to open invoices as well as the paid ones.",
sparse="invoice_settings",
)
invoice_report_id = fields.Many2one(
comodel_name="ir.actions.report",
domain=lambda self: self._get_invoice_report_id_domain(),
string="Specific report",
help="Select a specific report for invoice download, if none are selected "
"default shopinvader implementation is used.",
)

_sql_constraints = [
(
Expand All @@ -218,6 +247,15 @@ def _default_partner_title_ids(self):
def _default_partner_industry_ids(self):
return self.env["res.partner.industry"].search([])

def _get_invoice_report_id_domain(self):
return [
(
"binding_model_id",
"=",
self.env.ref("account.model_account_move").id,
)
]

def _to_compute_nbr_content(self):
"""
Get a dict to compute the number of content.
Expand Down
2 changes: 1 addition & 1 deletion shopinvader/services/abstract_download.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ def _get_report(self, report_name, report_type, params=None):
:param report_name: str
:param report_type: str
:param params: dict
:return: ir.actions.report.xml recordset
:return: ir.actions.report recordset
"""
domain = [
("report_type", "=", report_type),
Expand Down
71 changes: 51 additions & 20 deletions shopinvader/services/invoice.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,16 +44,24 @@ def to_openapi(self):
# Private implementation

def _get_allowed_invoice_states(self):
"""
Get every invoice states allowed to return on the service.
"""Get invoice states.
:return: list of str
"""
return ["posted"]

def _get_base_search_domain(self):
def _get_allowed_payment_states(self):
"""Get invoice payment states.
:return: list of str
"""
This method must provide a domain used to retrieve the requested
invoice.
states = ["paid"]
if self.shopinvader_backend.invoice_access_open:
states += ["not_paid", "in_payment"]
return states

def _get_base_search_domain(self):
"""Domain used to retrieve requested invoices.
This domain MUST TAKE CARE of restricting the access to the invoices
visible for the current customer
Expand All @@ -62,30 +70,53 @@ def _get_base_search_domain(self):
# The partner must be set and not be the anonymous one
if not self._is_logged_in():
return expression.FALSE_DOMAIN
invoices = self._get_available_invoices()
domain_invoice_ids = [("id", "in", invoices.ids)]
return expression.normalize_domain(
expression.AND([domain_invoice_ids, self._get_domain_state()])
)

def _get_domain_state(self):
domain_state = [("state", "in", self._get_allowed_invoice_states())]
domain_payment_state = [
("invoice_payment_state", "in", self._get_allowed_payment_states())
]
return expression.AND([domain_state, domain_payment_state])

def _get_available_invoices(self):
"""Retrieve invoices for current customer."""
# here we only allow access to invoices linked to a sale order of the
# current customer
so_domain = [
if self.shopinvader_backend.invoice_linked_to_sale_only:
so_domain = self._get_sale_order_domain()
# invoice_ids on sale.order is a computed field...
# to avoid to duplicate the logic, we search for the sale orders
# and check if the invoice_id is into the list of sale.invoice_ids
sales = self.env["sale.order"].search(so_domain)
invoices = sales.mapped("invoice_ids")
else:
invoices = self.env["account.move"].search(
[
("partner_id", "=", self.partner.id),
("type", "in", ["out_invoice", "out_refund"]),
]
)
return invoices

def _get_sale_order_domain(self):
return [
("partner_id", "=", self.partner.id),
("shopinvader_backend_id", "=", self.shopinvader_backend.id),
("typology", "=", "sale"),
]
# invoice_ids on sale.order is a computed field...
# to avoid to duplicate the logic, we search for the sale orders
# and check if the invoice_id is into the list of sale.invoice_ids
sales = self.env["sale.order"].search(so_domain)
invoice_ids = sales.mapped("invoice_ids").ids
states = self._get_allowed_invoice_states()
return expression.normalize_domain(
[("id", "in", invoice_ids), ("state", "in", states)]
)

def _get_report_action(self, target, params=None):
"""
Get the action/dict to generate the report
"""Get the action/dict to generate the report.
:param target: recordset
:return: dict/action
"""
return self.env.ref("account.account_invoices").report_action(
target, config=False
)
report = self.shopinvader_backend.invoice_report_id
if not report:
report = self.env.ref("account.account_invoices")
return report.report_action(target, config=False)
17 changes: 9 additions & 8 deletions shopinvader/services/sale.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,16 +97,17 @@ def _launch_notification(self, target, notif_type):

def _convert_one_sale(self, sale):
res = super(SaleService, self)._convert_one_sale(sale)
res["invoices"] = self._convert_invoices(sale.sudo())
res["invoices"] = self._convert_invoices(self._get_invoices(sale))
return res

def _convert_invoices(self, sale):
res = []
for invoice in sale.invoice_ids.filtered(
lambda i: i.invoice_payment_state == "paid"
):
res.append(self._convert_one_invoice(invoice))
return res
def _get_invoices(self, sale):
invoices = sale.sudo().invoice_ids
invoice_service = self.component(usage="invoice")
domain_state = invoice_service._get_domain_state()
return invoices.filtered_domain(domain_state)

def _convert_invoices(self, invoices):
return [self._convert_one_invoice(invoice) for invoice in invoices]

def _convert_one_invoice(self, invoice):
return {
Expand Down
146 changes: 130 additions & 16 deletions shopinvader/tests/test_invoice.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,28 +6,34 @@


class TestInvoice(CommonCase, CommonTestDownload):
def setUp(self, *args, **kwargs):
super(TestInvoice, self).setUp(*args, **kwargs)
self.register_payments_obj = self.env["account.payment.register"]
self.journal_obj = self.env["account.journal"]
self.sale = self.env.ref("shopinvader.sale_order_2")
self.partner = self.env.ref("shopinvader.partner_1")
self.payment_method_manual_in = self.env.ref(
@classmethod
def setUpClass(cls):
super(TestInvoice, cls).setUpClass()
cls.register_payments_obj = cls.env["account.payment.register"]
cls.journal_obj = cls.env["account.journal"]
cls.sale = cls.env.ref("shopinvader.sale_order_2")
cls.partner = cls.env.ref("shopinvader.partner_1")
cls.payment_method_manual_in = cls.env.ref(
"account.account_payment_method_manual_in"
)
self.bank_journal_euro = self.journal_obj.create(
cls.bank_journal_euro = cls.journal_obj.create(
{"name": "Bank", "type": "bank", "code": "BNK627"}
)
with self.work_on_services(partner=self.partner) as work:
self.sale_service = work.component(usage="sales")
self.invoice_service = work.component(usage="invoice")
self.invoice = self._confirm_and_invoice_sale(self.sale)
cls.invoice_obj = cls.env["account.move"]
cls.invoice = cls._confirm_and_invoice_sale(cls, cls.sale)
cls.non_sale_invoice = cls.invoice.copy()
# set the layout on the company to be sure that the print action
# will not display the document layout configurator
self.env.company.external_report_layout_id = self.env.ref(
cls.env.company.external_report_layout_id = cls.env.ref(
"web.external_layout_standard"
).id

def setUp(self, *args, **kwargs):
super(TestInvoice, self).setUp(*args, **kwargs)
with self.work_on_services(partner=self.partner) as work:
self.sale_service = work.component(usage="sales")
self.invoice_service = work.component(usage="invoice")

def _make_payment(self, invoice):
"""
Make the invoice payment
Expand All @@ -52,12 +58,44 @@ def _confirm_and_invoice_sale(self, sale):
line.write({"qty_delivered": line.product_uom_qty})
return sale._create_invoices()

def _create_invoice(self, partner, **kw):
product = self.env.ref("product.product_product_4")
account = self.env["account.account"].search(
[
(
"user_type_id",
"=",
self.env.ref("account.data_account_type_receivable").id,
)
],
limit=1,
)
values = {
"partner_id": self.partner.id,
"type": "out_invoice",
"line_ids": [
(
0,
0,
{
"account_id": account.id,
"product_id": product.product_variant_ids[:1].id,
"name": "Product 1",
"quantity": 4.0,
"price_unit": 123.00,
},
)
],
}
values.update(kw)
return self.env["account.move"].create(values)

def test_01(self):
"""
Data
* A confirmed sale order with an invoice not yet paid
Case:
* Try to download the image
* Try to download the PDF
Expected result:
* MissingError should be raised
"""
Expand All @@ -68,7 +106,7 @@ def test_02(self):
Data
* A confirmed sale order with a paid invoice
Case:
* Try to download the image
* Try to download the PDF
Expected result:
* An http response with the file to download
"""
Expand All @@ -81,7 +119,7 @@ def test_03(self):
* A confirmed sale order with a paid invoice but not for the
current customer
Case:
* Try to download the image
* Try to download the PDF
Expected result:
* MissingError should be raised
"""
Expand All @@ -91,3 +129,79 @@ def test_03(self):
invoice = self._confirm_and_invoice_sale(sale)
self._make_payment(invoice)
self._test_download_not_owner(self.invoice_service, self.invoice)

def test_domain_01(self):
# By default include only invoices related to sales
self.assertTrue(self.backend.invoice_linked_to_sale_only)
# and only paid invoice are accessible
self.assertFalse(self.backend.invoice_access_open)
# Invoices are open, none of them is included
self.invoice.post()
self.non_sale_invoice.post()
domain = self.invoice_service._get_base_search_domain()
self.assertNotIn(
self.non_sale_invoice, self.invoice_obj.search(domain)
)
self.assertNotIn(self.invoice, self.invoice_obj.search(domain))
# pay both invoices
self._make_payment(self.invoice)
self._make_payment(self.non_sale_invoice)
domain = self.invoice_service._get_base_search_domain()
# Extra invoice still not found
self.assertNotIn(
self.non_sale_invoice, self.invoice_obj.search(domain)
)
self.assertIn(self.invoice, self.invoice_obj.search(domain))

def test_domain_02(self):
# Include extra invoices
self.backend.invoice_linked_to_sale_only = False
# and only paid invoice are accessible
self.assertFalse(self.backend.invoice_access_open)
# Invoices are open, none of them is included
self.invoice.post()
self.non_sale_invoice.post()
domain = self.invoice_service._get_base_search_domain()
self.assertNotIn(
self.non_sale_invoice, self.invoice_obj.search(domain)
)
self.assertNotIn(self.invoice, self.invoice_obj.search(domain))
# pay both invoices
self._make_payment(self.invoice)
self._make_payment(self.non_sale_invoice)
domain = self.invoice_service._get_base_search_domain()
# Extra invoice available now as well
self.assertIn(self.non_sale_invoice, self.invoice_obj.search(domain))
self.assertIn(self.invoice, self.invoice_obj.search(domain))

def test_domain_03(self):
# Include extra invoices
self.backend.invoice_linked_to_sale_only = False
# and open invoices enabled as well
self.backend.invoice_access_open = True
self.invoice.post()
self.non_sale_invoice.post()
domain = self.invoice_service._get_base_search_domain()
self.assertIn(self.non_sale_invoice, self.invoice_obj.search(domain))
self.assertIn(self.invoice, self.invoice_obj.search(domain))
# pay both invoices
self._make_payment(self.invoice)
self._make_payment(self.non_sale_invoice)
domain = self.invoice_service._get_base_search_domain()
# Still both available
self.assertIn(self.non_sale_invoice, self.invoice_obj.search(domain))
self.assertIn(self.invoice, self.invoice_obj.search(domain))

def test_report_get(self):
default_report = self.env.ref("account.account_invoices")
self.assertEqual(
self.invoice_service._get_report_action(self.invoice),
default_report.report_action(self.invoice, config=False),
)
# set a custom report
custom = default_report.copy({"name": "My custom report"})
self.backend.invoice_report_id = custom
self.assertEqual(
self.invoice_service._get_report_action(self.invoice)["name"],
"My custom report",
)
Loading

0 comments on commit b1ff2b2

Please sign in to comment.