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 533202454..885065589 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:
@@ -1500,6 +1575,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] = (
@@ -1622,29 +1698,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,
@@ -1664,6 +1742,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