From 1e09c0a02f3c63a9972badc18c88a87941b37f76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dar=C3=ADo=20Lodeiros?= Date: Sat, 5 Feb 2022 11:24:40 +0100 Subject: [PATCH] [WIP]pms: advance autoinvoicinf configuration --- pms/data/cron_jobs.xml | 16 ++ pms/models/account_bank_statement.py | 2 +- pms/models/pms_folio.py | 229 +++++++++++++++++++-------- pms/models/pms_property.py | 28 +++- pms/models/res_partner.py | 56 ++++++- pms/views/pms_property_views.xml | 4 + pms/views/res_partner_views.xml | 23 +++ 7 files changed, 287 insertions(+), 71 deletions(-) diff --git a/pms/data/cron_jobs.xml b/pms/data/cron_jobs.xml index 87ace086f..305fbe6d6 100644 --- a/pms/data/cron_jobs.xml +++ b/pms/data/cron_jobs.xml @@ -101,5 +101,21 @@ model.send_cancelation_mail() + + Auto Invoicing Folios + 5 + + + days + -1 + + code + + + model.autoinvoicing() + diff --git a/pms/models/account_bank_statement.py b/pms/models/account_bank_statement.py index 5e5ff1aae..96a61bf59 100644 --- a/pms/models/account_bank_statement.py +++ b/pms/models/account_bank_statement.py @@ -34,7 +34,7 @@ class AccountBankStatement(models.Model): ) super(AccountBankStatement, self).button_post() for line in lines_of_moves_to_post: - payment_move_line = line._get_payment_move_lines_to_reconcile(line) + payment_move_line = line._get_payment_move_lines_to_reconcile() statement_move_line = line.move_id.line_ids.filtered( lambda line: line.account_id.reconcile ) diff --git a/pms/models/pms_folio.py b/pms/models/pms_folio.py index f47b7099a..ab607b344 100644 --- a/pms/models/pms_folio.py +++ b/pms/models/pms_folio.py @@ -3,6 +3,8 @@ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). import datetime +from dateutil import relativedelta +from datetime import timedelta import logging from itertools import groupby @@ -505,6 +507,11 @@ class PmsFolio(models.Model): store=True, compute="_compute_last_checkout", ) + autoinvoice_date = fields.Date( + string="Autoinvoice Date", + compute="_compute_autoinvoice_date", + store=True, + ) def name_get(self): result = [] @@ -539,68 +546,114 @@ class PmsFolio(models.Model): ) invoice_vals_list = [] invoice_item_sequence = 0 - for order in self: - order = order.with_company(order.company_id) - current_section_vals = None - down_payments = order.env["folio.sale.line"] - - # Invoice values. - invoice_vals = order._prepare_invoice(partner_invoice_id=partner_invoice_id) - - # Invoice line values (keep only necessary sections). - invoice_lines_vals = [] - for line in order.sale_line_ids.filtered( + for folio in self: + folio_lines_to_invoice = folio.sale_line_ids.filtered( lambda l: l.id in list(lines_to_invoice.keys()) - ): - if line.display_type == "line_section": - current_section_vals = line._prepare_invoice_line( - sequence=invoice_item_sequence + 1 - ) - continue - if line.display_type != "line_note" and float_is_zero( - line.qty_to_invoice, precision_digits=precision - ): - continue - if ( - line.qty_to_invoice > 0 - or (line.qty_to_invoice < 0 and final) - or line.display_type == "line_note" - ): - if line.is_downpayment: - down_payments += line - continue - if current_section_vals: - invoice_item_sequence += 1 - invoice_lines_vals.append(current_section_vals) - current_section_vals = None - invoice_item_sequence += 1 - prepared_line = line._prepare_invoice_line( - sequence=invoice_item_sequence, qty=lines_to_invoice[line.id] - ) - invoice_lines_vals.append(prepared_line) + ) - # If down payments are present in SO, group them under common section - if down_payments: - invoice_item_sequence += 1 - down_payments_section = order._prepare_down_payment_section_line( - sequence=invoice_item_sequence + folio_partner_invoice_id = partner_invoice_id + if not folio_partner_invoice_id: + if folio.partner_id and folio.partner_id.document_number_to_invoice: + folio_partner_invoice_id = folio.partner_id.id + else: + folio_partner_invoice_id = ( + self.partner_invoice_ids[0].id if self.partner_invoice_ids else False + ) + + target_lines = folio_lines_to_invoice + if self._context.get("lines_auto_add") and folio_partner_invoice_id: + if folio_partner_invoice_id.default_invoice_lines == 'overnights': + target_lines = target_lines.filtered( + lambda r: r.is_board_service or r.reservation_id.overnight_room + ) + elif folio_partner_invoice_id.default_invoice_lines == 'reservations': + target_lines = target_lines.filtered( + lambda r: r.is_board_service or r.reservation_id + ) + elif folio_partner_invoice_id.default_invoice_lines == 'services': + target_lines = target_lines.filtered( + lambda r: not r.is_board_service or r.service_id + ) + groups_invoice_lines = [ + { + "partner_id": folio_partner_invoice_id, + "lines": target_lines, + } + ] + if ( + folio.autoinvoice_date + and folio.autoinvoice_date <= fields.Date.today() + and len(target_lines) < len(folio_lines_to_invoice) + ): + second_partner_to_invoice = folio.partner_invoice_ids.filtered( + lambda p: p.id != folio_partner_invoice_id ) - invoice_lines_vals.append(down_payments_section) - for down_payment in down_payments: + groups_invoice_lines.append( + { + "partner_id": second_partner_to_invoice and second_partner_to_invoice.id, + "lines": folio_lines_to_invoice - target_lines + } + ) + for group in groups_invoice_lines: + folio = folio.with_company(folio.company_id) + down_payments = folio.env["folio.sale.line"] + + # Invoice values. + invoice_vals = folio._prepare_invoice(partner_invoice_id=group["partner_id"]) + + # Invoice line values (keep only necessary sections). + current_section_vals = None + invoice_lines_vals = [] + for line in group["lines"]: + if line.display_type == "line_section": + current_section_vals = line._prepare_invoice_line( + sequence=invoice_item_sequence + 1 + ) + continue + if line.display_type != "line_note" and float_is_zero( + line.qty_to_invoice, precision_digits=precision + ): + continue + if ( + line.qty_to_invoice > 0 + or (line.qty_to_invoice < 0 and final) + or line.display_type == "line_note" + ): + if line.is_downpayment: + down_payments += line + continue + if current_section_vals: + invoice_item_sequence += 1 + invoice_lines_vals.append(current_section_vals) + current_section_vals = None + invoice_item_sequence += 1 + prepared_line = line._prepare_invoice_line( + sequence=invoice_item_sequence, qty=lines_to_invoice[line.id] + ) + invoice_lines_vals.append(prepared_line) + + # If down payments are present in SO, group them under common section + if down_payments: invoice_item_sequence += 1 - invoice_down_payment_vals = down_payment._prepare_invoice_line( + down_payments_section = folio._prepare_down_payment_section_line( sequence=invoice_item_sequence ) - invoice_lines_vals.append(invoice_down_payment_vals) + invoice_lines_vals.append(down_payments_section) + for down_payment in down_payments: + invoice_item_sequence += 1 + invoice_down_payment_vals = down_payment._prepare_invoice_line( + sequence=invoice_item_sequence + ) + invoice_lines_vals.append(invoice_down_payment_vals) - if not any( - new_line["display_type"] is False for new_line in invoice_lines_vals - ): - raise self._nothing_to_invoice_error() + if not any( + new_line["display_type"] is False for new_line in invoice_lines_vals + ): + raise self._nothing_to_invoice_error() - invoice_vals["invoice_line_ids"] = [ - (0, 0, invoice_line_id) for invoice_line_id in invoice_lines_vals - ] + invoice_vals["invoice_line_ids"] = [ + (0, 0, invoice_line_id) for invoice_line_id in invoice_lines_vals + ] invoice_vals_list.append(invoice_vals) return invoice_vals_list @@ -627,6 +680,28 @@ class PmsFolio(models.Model): ] return res + @api.depends("partner_id", "invoice_status", "last_checkout") + def _compute_autoinvoice_date(self): + self.autoinvoice_date = False + for record in self.filtered(lambda r: r.invoice_status == "to_invoice"): + record.autoinvoice_date = record._get_to_invoice_date() + + def _get_to_invoice_date(self): + self.ensure_one() + partner = self.partner_id + invoicing_policy = self.pms_property_id.default_invoicing_policy if not partner or partner.invoicing_policy == "property" else partner.invoicing_policy + if invoicing_policy == "manual": + return False + if invoicing_policy == "checkout": + margin_days = self.pms_property_id.margin_days_autoinvoice if not partner or partner.invoicing_policy == "property" else partner.margin_days_autoinvoice + return self.checkout + timedelta(days=margin_days) + if invoicing_policy == "month_day": + month_day = self.pms_property_id.invoicing_month_day if not partner or partner.invoicing_policy == "property" else partner.invoicing_month_day + if self.checkout.day <= month_day: + self.autoinvoice_date = self.checkout.replace(day=month_day) + else: + self.autoinvoice_date = (self.checkout + relativedelta.relativedelta(months=1)).replace(day=month_day) + @api.depends("reservation_ids", "reservation_ids.state") def _compute_number_of_rooms(self): for folio in self: @@ -1508,6 +1583,7 @@ class PmsFolio(models.Model): return self.env["account.move"] # 1) Create invoices. if not lines_to_invoice: + self = self.with_context(lines_auto_add=True) lines_to_invoice = dict() for line in self.sale_line_ids: lines_to_invoice[line.id] = ( @@ -1630,29 +1706,31 @@ class PmsFolio(models.Model): (making sure to call super() to establish a clean extension chain). """ self.ensure_one() - journal = ( - self.env["account.move"] - .with_context( - default_move_type="out_invoice", - default_company_id=self.company_id.id, - default_pms_property_id=self.pms_property_id.id, + if not partner_invoice_id: + partner_invoice_id = ( + self.partner_invoice_ids[0].id if self.partner_invoice_ids else False + ) + + journal = self._get_folio_default_journal(partner_invoice_id) + if not journal: + journal = ( + self.env["account.move"] + .with_context( + default_move_type="out_invoice", + default_company_id=self.company_id.id, + default_pms_property_id=self.pms_property_id.id, + ) + ._get_default_journal() ) - ._get_default_journal() - ) if not journal: raise UserError( _("Please define an accounting sales journal for the company %s (%s).") % (self.company_id.name, self.company_id.id) ) - if not partner_invoice_id: - partner_invoice_id = ( - self.partner_invoice_ids[0].id if self.partner_invoice_ids else False - ) - invoice_vals = { "ref": self.client_order_ref or "", - "move_type": "out_invoice", + "move_type": self._get_default_move_type(partner_invoice_id), "narration": self.note, "currency_id": self.pricelist_id.currency_id.id, # 'campaign_id': self.campaign_id.id, @@ -1672,6 +1750,21 @@ 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 partner.document_number_to_invoice: + return pms_property.journal_normal_invoice_id + return pms_property.journal_simplified_invoice_id + + def _get_default_move_type(self, partner_invoice_id): + self.ensure_one() + partner = self.env["res.partner"].browse(partner_invoice_id) + if partner.document_number_to_invoice: + return "out_invoice" + return "entry" + def do_payment( self, journal, diff --git a/pms/models/pms_property.py b/pms/models/pms_property.py index 7b53f54f2..099d8d05e 100644 --- a/pms/models/pms_property.py +++ b/pms/models/pms_property.py @@ -147,10 +147,21 @@ class PmsProperty(models.Model): selection=[ ("manual", "Manual"), ("checkout", "Checkout"), + ("month_day", "Month Day Invoice"), ], default="manual", ) + margin_days_autoinvoice = fields.Integer( + string="Margin Days", + help="Days from Checkout to generate the invoice", + ) + + invoicing_month_day = fields.Integer( + string="Invoicing Month Day", + help="The day of the month to invoice", + ) + journal_simplified_invoice_id = fields.Many2one( string="Simplified Invoice Journal", comodel_name="account.journal", @@ -580,5 +591,20 @@ class PmsProperty(models.Model): "closed": True, } ) - + return True + + @api.model + def autoinvoicing(self): + """ + This method is used to autoinvoicing the folios + """ + folios = self.env["pms.folio"].search([ + ("autoinvoice_date", "=" , fields.date.today()), + ]) + if folios: + invoices = folios.with_context(autoinvoice=True)._create_invoices( + grouped=True, + ) + if invoices: + invoices.action_post() return True diff --git a/pms/models/res_partner.py b/pms/models/res_partner.py index 22ede5464..bbaafd415 100644 --- a/pms/models/res_partner.py +++ b/pms/models/res_partner.py @@ -42,7 +42,7 @@ class ResPartner(models.Model): ) pms_property_ids = fields.Many2many( string="Properties", - help="Properties with access to the element;" + help="Properties with access to the element" " if not set, all properties can access", required=False, comodel_name="pms.property", @@ -133,6 +133,60 @@ class ResPartner(models.Model): string="Possible Customer In Checkin Partner", comodel_name="pms.checkin.partner", ) + invoicing_policy = fields.Selection( + string="Invoicing Policy", + help="The invoicing policy of the partner, set Property to user the policy configured in the Property", + selection=[ + ("property", "Property Policy Invoice"), + ("manual", "Manual"), + ("checkout", "From Checkout"), + ("month_day", "Month Day Invoice"), + ], + default="property", + ) + invoicing_month_day = fields.Integer( + string="Invoicing Month Day", + help="The day of the month to invoice", + ) + margin_days_autoinvoice = fields.Integer( + string="Days from Checkout", + help="Days from Checkout to generate the invoice", + ) + default_invoice_lines = fields.Selection( + string="Invoice...", + help="""Use to preconfigure the sale lines to autoinvoice + for this partner. All (invoice reservations and services), + Only overnights to invoice only the reservations + with overnight and board services(exclude parkings, salon, etc...), + All reservations to include all reservations, + and Services only include services not boards""", + selection=[ + ("all", "All"), + ("overnights", "Only Overnights"), + ("reservations", "All reservations"), + ("services", "Services"), + ], + default="all", + ) + document_number_to_invoice = fields.Char( + string="Document Number to invoices", + help="""Technical field to compute the partner reference to invoice, + it can be the VAT, if its set, or the document number, if its set, + else it will be False""", + compute="_compute_document_number_to_invoice", + readonly=False, + store=True, + ) + + @api.depends("vat", "id_numbers", "id_numbers.name") + def _compute_document_number_to_invoice(self): + for partner in self: + if partner.vat: + partner.document_number_to_invoice = partner.vat + elif partner.id_numbers: + partner.document_number_to_invoice = partner.id_numbers[0].name + else: + partner.document_number_to_invoice = False @api.depends("pms_checkin_partner_ids", "pms_checkin_partner_ids.gender") def _compute_gender(self): diff --git a/pms/views/pms_property_views.xml b/pms/views/pms_property_views.xml index 20a4e255b..094ae4fa5 100644 --- a/pms/views/pms_property_views.xml +++ b/pms/views/pms_property_views.xml @@ -88,6 +88,10 @@ + + + + view.partner.property.form + res.partner + + + + + + + + + + + + + res.partner.form.data res.partner