From 0f5c577d2c1565bc8ac3816e45e5ca27f652a1f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dar=C3=ADo=20Lodeiros?= Date: Sun, 13 Nov 2022 21:28:58 +0100 Subject: [PATCH] [ADD]pms_api_rest: autoinvoice downpayments policy --- pms/models/account_journal.py | 5 + pms/models/account_payment.py | 131 +++++++++++++++++++--- pms/models/folio_sale_line.py | 9 +- pms/models/pms_folio.py | 13 +-- pms/models/pms_property.py | 15 +++ pms/models/res_company.py | 16 +++ pms/tests/test_pms_folio_invoice.py | 36 ------ pms/views/account_journal_views.xml | 9 +- pms/views/res_company_views.xml | 1 + pms/wizards/folio_make_invoice_advance.py | 18 ++- 10 files changed, 175 insertions(+), 78 deletions(-) diff --git a/pms/models/account_journal.py b/pms/models/account_journal.py index deea22ab4..82b51e21a 100644 --- a/pms/models/account_journal.py +++ b/pms/models/account_journal.py @@ -20,6 +20,11 @@ class AccountJournal(models.Model): string="For manual payments", help="Use to pay for reservations", ) + avoid_autoinvoice_downpayment = fields.Boolean( + string="Avoid autoinvoice downpayment", + help="Avoid autoinvoice downpayment", + default=False, + ) is_simplified_invoice = fields.Boolean( string="Simplified invoice", help="Use to simplified invoice", diff --git a/pms/models/account_payment.py b/pms/models/account_payment.py index 416ea3f37..872c95e09 100644 --- a/pms/models/account_payment.py +++ b/pms/models/account_payment.py @@ -1,6 +1,8 @@ # Copyright 2017 Dario Lodeiros # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from odoo import api, fields, models + + +from odoo import _, api, fields, models class AccountPayment(models.Model): @@ -143,21 +145,118 @@ class AccountPayment(models.Model): # if msg: # self.folio_id.message_post(subject=_("Payment Deleted"), body=msg) - # def post(self): - # rec = super(AccountPayment, self).post() - # if rec and not self._context.get("ignore_notification_post", False): - # for pay in self: - # if pay.folio_id: - # msg = _( - # "Payment of %s %s registered from %s \ - # using %s payment method" - # ) % ( - # pay.amount, - # pay.currency_id.symbol, - # pay.communication, - # pay.journal_id.name, - # ) - # pay.folio_id.message_post(subject=_("Payment"), body=msg) + def action_post(self): + res = super(AccountPayment, self).action_post() + for payment in self: + if ( + self.folio_ids + and self.company_id.pms_invoice_downpayment_policy != "no" + and not self.journal_id.avoid_autoinvoice_downpayment + ): + checkout_ref = max(self.folio_ids.mapped("last_checkout")) + if ( + self.company_id.pms_invoice_downpayment_policy == "all" + and payment.date < checkout_ref + ) or ( + self.company_id.pms_invoice_downpayment_policy + == "checkout_past_month" + and checkout_ref.month > payment.date.month + and checkout_ref.year >= payment.date.year + ): + partner_id = ( + payment.partner_id.id + or self.env.ref("pms.various_pms_partner").id + ) + if payment.payment_type == "inbound": + invoice_wizard = self.env["folio.advance.payment.inv"].create( + { + "partner_invoice_id": partner_id, + "advance_payment_method": "fixed", + "fixed_amount": payment.amount, + } + ) + move = invoice_wizard.with_context( + active_ids=self.folio_ids.ids, + return_invoices=True, + ).create_invoices() + move.action_post() + move_lines = move.line_ids.filtered( + lambda line: line.account_id.user_type_id.type + in ("receivable", "payable") + ) + payment_lines = payment.move_id.line_ids.filtered( + lambda line: line.account_id == move_lines.account_id + ) + if not move_lines.reconciled: + (payment_lines + move_lines).reconcile() + else: + # We try to revert the advance payment invoice + advance_invoices = self.env["account.move"].search( + [ + ("folio_ids", "in", self.folio_ids.ids), + ("state", "=", "posted"), + ("payment_state", "=", "paid"), + ("move_type", "=", "out_invoice"), + ] + ) + advance_invoices = advance_invoices.filtered( + lambda inv: any( + [ + line.folio_line_ids.is_downpayment + for line in inv.invoice_line_ids + ] + ) + ) + if advance_invoices: + match_inv = advance_invoices.filtered( + lambda inv: round(inv.amount_total, 2) + == round(payment.amount, 2) + ) + move_reversal = ( + self.env["account.move.reversal"] + .with_context( + active_model="account.move", + active_ids=match_inv.ids + if match_inv + else advance_invoices.ids, + ) + .create( + { + "date": fields.Date.today(), + "reason": _("Deposit return"), + "refund_method": "refund", + } + ) + ) + move_reversal.reverse_moves() + move = move_reversal.new_move_ids + invoice_line = move.invoice_line_ids.filtered( + lambda l: not l.display_type + ) + move.write( + { + "invoice_line_ids": [ + ( + 1, + invoice_line.id, + { + "price_unit": payment.amount, + }, + ) + ], + } + ) + move_lines = move.line_ids.filtered( + lambda line: line.account_id.user_type_id.type + in ("receivable", "payable") + ) + payment_lines = payment.move_id.line_ids.filtered( + lambda line: line.account_id == move_lines.account_id + ) + move.sudo().action_post() + if not move_lines.reconciled: + (payment_lines + move_lines).reconcile() + return res # def modify_payment(self): # self.ensure_one() diff --git a/pms/models/folio_sale_line.py b/pms/models/folio_sale_line.py index 9e3869b6c..20a8e53b8 100644 --- a/pms/models/folio_sale_line.py +++ b/pms/models/folio_sale_line.py @@ -719,7 +719,7 @@ class FolioSaleLine(models.Model): Otherwise, the quantity delivered is used. """ for line in self: - if line.folio_id.state not in ["draft"] and line.price_total > 0.0: + if line.folio_id.state not in ["draft"]: line.qty_to_invoice = line.product_uom_qty - line.qty_invoiced else: line.qty_to_invoice = 0 @@ -996,13 +996,6 @@ class FolioSaleLine(models.Model): should be added to the returned invoice line """ self.ensure_one() - if (qty > self.qty_to_invoice or qty <= 0) and not self.display_type: - raise ValueError( - _( - "The qty (%s) is wrong." % qty - + " The quantity pending to invoice is %s" % self.qty_to_invoice - ) - ) res = { "display_type": self.display_type, "sequence": self.sequence, diff --git a/pms/models/pms_folio.py b/pms/models/pms_folio.py index 7a2166319..cf3894a62 100644 --- a/pms/models/pms_folio.py +++ b/pms/models/pms_folio.py @@ -2032,7 +2032,7 @@ class PmsFolio(models.Model): (making sure to call super() to establish a clean extension chain). """ self.ensure_one() - journal = self._get_folio_default_journal(partner_invoice_id) + journal = self.pms_property_id._get_folio_default_journal(partner_invoice_id) if not journal: journal = ( self.env["account.move"] @@ -2074,17 +2074,6 @@ class PmsFolio(models.Model): } return invoice_vals - def _get_folio_default_journal(self, partner_invoice_id): - self.ensure_one() - pms_property = self.pms_property_id - partner = self.env["res.partner"].browse(partner_invoice_id) - if not partner or ( - not partner._check_enought_invoice_data() - and self._context.get("autoinvoice") - ): - return pms_property.journal_simplified_invoice_id - return pms_property.journal_normal_invoice_id - def do_payment( self, journal, diff --git a/pms/models/pms_property.py b/pms/models/pms_property.py index 024377011..61d94fa43 100644 --- a/pms/models/pms_property.py +++ b/pms/models/pms_property.py @@ -701,6 +701,21 @@ class PmsProperty(models.Model): if not pms_property.journal_simplified_invoice_id.is_simplified_invoice: pms_property.journal_simplified_invoice_id.is_simplified_invoice = True + @api.model + def _get_folio_default_journal(self, partner_invoice_id): + self.ensure_one() + partner = self.env["res.partner"].browse(partner_invoice_id) + if ( + not partner + or partner.id == self.env.ref("pms.various_pms_partner").id + or ( + not partner._check_enought_invoice_data() + and self._context.get("autoinvoice") + ) + ): + return self.journal_simplified_invoice_id + return self.journal_normal_invoice_id + def _get_adr(self, start_date, end_date, domain=False): """ Calculate monthly ADR for a property diff --git a/pms/models/res_company.py b/pms/models/res_company.py index f44d9effa..d047ff05e 100644 --- a/pms/models/res_company.py +++ b/pms/models/res_company.py @@ -27,3 +27,19 @@ class ResCompany(models.Model): - VAT, name, street, city, country""", default=False, ) + + pms_invoice_downpayment_policy = fields.Selection( + selection=[ + ("no", "Manual"), + ("all", "All"), + ("checkout_past_month", "Checkout past month"), + ], + string="Downpayment policy invoce", + help=""" + - Manual: Downpayment invoice will be created manually + - All: Downpayment invoice will be created automatically + - Current Month: Downpayment invoice will be created automatically + only for reservations with checkout date past of current month + """, + default="no", + ) diff --git a/pms/tests/test_pms_folio_invoice.py b/pms/tests/test_pms_folio_invoice.py index bed69c713..a46a8fcb4 100644 --- a/pms/tests/test_pms_folio_invoice.py +++ b/pms/tests/test_pms_folio_invoice.py @@ -253,42 +253,6 @@ class TestPmsFolioInvoice(TestPms): "The status after an invoicing is not correct", ) - def test_invoice_partial_folio_wrong_qtys(self): - """ - Check that an invoice of a folio with wrong amounts cannot be created. - ------------ - A reservation is created. Then the create_invoices method of the folio - is launched with the lines to be invoiced with wrong amounts and through - subtest it is verified that the invoices cannot be created. - """ - # ARRANGE - r1 = self.env["pms.reservation"].create( - { - "pms_property_id": self.property.id, - "checkin": datetime.datetime.now(), - "checkout": datetime.datetime.now() + datetime.timedelta(days=2), - "adults": 2, - "room_type_id": self.room_type_double.id, - "partner_id": self.env.ref("base.res_partner_12").id, - "sale_channel_origin_id": self.sale_channel_direct1.id, - } - ) - tcs = [-1, 0, 3] - - for tc in tcs: - with self.subTest(k=tc): - with self.assertRaises(ValueError): - # ARRANGE - dict_lines = dict() - dict_lines[ - r1.folio_id.sale_line_ids.filtered( - lambda l: not l.display_type - )[0].id - ] = tc - r1.folio_id._create_invoices(lines_to_invoice=dict_lines) - # test does not work without invalidating cache - self.env["account.move"].invalidate_cache() - def test_amount_invoice_folio(self): """ Test create and invoice from the Folio, and check amount of the reservation. diff --git a/pms/views/account_journal_views.xml b/pms/views/account_journal_views.xml index d63420eff..5bc5fa053 100644 --- a/pms/views/account_journal_views.xml +++ b/pms/views/account_journal_views.xml @@ -6,7 +6,14 @@ - + + diff --git a/pms/views/res_company_views.xml b/pms/views/res_company_views.xml index c69bc150e..5d5d688ad 100644 --- a/pms/views/res_company_views.xml +++ b/pms/views/res_company_views.xml @@ -16,6 +16,7 @@ + diff --git a/pms/wizards/folio_make_invoice_advance.py b/pms/wizards/folio_make_invoice_advance.py index 23ecb4b24..b04f8be1f 100644 --- a/pms/wizards/folio_make_invoice_advance.py +++ b/pms/wizards/folio_make_invoice_advance.py @@ -152,15 +152,21 @@ class FolioAdvancePaymentInv(models.TransientModel): return {} def _prepare_invoice_values(self, order, name, amount, line): + partner_id = ( + self.partner_invoice_id.id + if self.partner_invoice_id + else order.partner_invoice_id.id + ) invoice_vals = { "ref": order.name, "move_type": "out_invoice", + "journal_id": order.pms_property_id._get_folio_default_journal( + partner_id + ).id, "invoice_origin": order.name, "invoice_user_id": order.user_id.id, "narration": order.note, - "partner_id": self.partner_invoice_id - if self.partner_invoice_id - else order.partner_invoice_id.id, + "partner_id": partner_id, "currency_id": order.pricelist_id.currency_id.id, "folio_ids": [(6, 0, order.ids)], "payment_reference": order.reference, @@ -199,7 +205,6 @@ class FolioAdvancePaymentInv(models.TransientModel): amount, name = self._get_advance_details(order) invoice_vals = self._prepare_invoice_values(order, name, amount, line) - if order.fiscal_position_id: invoice_vals["fiscal_position_id"] = order.fiscal_position_id.id invoice = ( @@ -258,6 +263,7 @@ class FolioAdvancePaymentInv(models.TransientModel): ) sale_line_obj = self.env["folio.sale.line"] + invoices = self.env["account.move"] for order in folios: amount, name = self._get_advance_details(order) @@ -293,9 +299,11 @@ class FolioAdvancePaymentInv(models.TransientModel): order, analytic_tag_ids, tax_ids, amount ) line = sale_line_obj.sudo().create(line_values) - self._create_invoice(order, line, amount) + invoices += self._create_invoice(order, line, amount) if self._context.get("open_invoices", False): return folios.action_view_invoice() + if self._context.get("return_invoices", False): + return invoices return {"type": "ir.actions.act_window_close"} def _prepare_deposit_product(self):