[WIP]pms: advance autoinvoicinf configuration

This commit is contained in:
Darío Lodeiros
2022-02-05 11:24:40 +01:00
parent 3f298b3069
commit 1e09c0a02f
7 changed files with 287 additions and 71 deletions

View File

@@ -101,5 +101,21 @@
<field name="nextcall" eval="DateTime.now()" />
<field name="code">model.send_cancelation_mail()</field>
</record>
<record model="ir.cron" id="autoinvoicing_folios">
<field name="name">Auto Invoicing Folios</field>
<field name="interval_number">5</field>
<field name="user_id" ref="base.user_root" />
<field name="active" eval="False" />
<field name="interval_type">days</field>
<field name="numbercall">-1</field>
<field name="doall" eval="False" />
<field name="state">code</field>
<field name="model_id" ref="model_pms_property" />
<field
name="nextcall"
eval="(DateTime.now() + timedelta(days=1)).strftime('%Y-%m-%d 04:30:00')"
/>
<field name="code">model.autoinvoicing()</field>
</record>
</data>
</odoo>

View File

@@ -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
)

View File

@@ -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,

View File

@@ -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

View File

@@ -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):

View File

@@ -88,6 +88,10 @@
<page string="Invoicing" name="property_invoicing">
<group>
<field name="default_invoicing_policy" />
<field
name="margin_days_autoinvoice"
attrs="{'invisible': [('default_invoicing_policy', '=', 'manual')]}"
/>
<field
name="journal_simplified_invoice_id"
attrs="{'required': [('default_invoicing_policy', '!=', 'manual')]}"

View File

@@ -93,6 +93,29 @@
</xpath>
</field>
</record>
<record id="view_partner_property_form" model="ir.ui.view">
<field name="name">view.partner.property.form</field>
<field name="model">res.partner</field>
<field name="inherit_id" ref="account.view_partner_property_form" />
<field name="arch" type="xml">
<xpath expr='//group[@name="accounting_entries"]' position='after'>
<group string="PMS Invoice policy" name="pms_invoice_policy">
<field name="invoicing_policy" />
<field
name="invoicing_month_day"
attrs="{'invisible': [('invoicing_policy', '!=', 'month_day')]}"
/>
<field
name="margin_days_autoinvoice"
attrs="{'invisible': [('invoicing_policy', '!=', 'checkout')]}"
/>
<field name="default_invoice_lines" />
</group>
</xpath>
</field>
</record>
<record id="view_partner_data_form" model="ir.ui.view">
<field name="name">res.partner.form.data</field>
<field name="model">res.partner</field>