[IMP]pms: multiple autoinvoicing improvements

This commit is contained in:
Darío Lodeiros
2022-07-25 17:13:51 +02:00
parent 3deda9f413
commit c0cba08936
14 changed files with 244 additions and 603 deletions

View File

@@ -1,9 +1,11 @@
# Copyright 2020 Dario Lodeiros
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from datetime import timedelta
from math import ceil
import babel.dates
from dateutil import relativedelta
from odoo import _, api, fields, models
from odoo.osv import expression
@@ -44,6 +46,15 @@ class FolioSaleLine(models.Model):
comodel_name="pms.service",
ondelete="cascade",
)
pms_property_id = fields.Many2one(
string="Property",
help="Property with access to the element;",
readonly=True,
store=True,
comodel_name="pms.property",
related="folio_id.pms_property_id",
check_pms_properties=True,
)
is_board_service = fields.Boolean(
string="Board Service",
help="Indicates if the service included in "
@@ -51,7 +62,6 @@ class FolioSaleLine(models.Model):
store=True,
related="service_id.is_board_service",
)
name = fields.Text(
string="Description",
help="Description of folio sale line",
@@ -259,13 +269,6 @@ class FolioSaleLine(models.Model):
index=True,
related="folio_id.company_id",
)
folio_partner_id = fields.Many2one(
string="Customer",
help="Related customer with Folio Sale Line",
readonly=False,
store=True,
related="folio_id.partner_id",
)
origin_agency_id = fields.Many2one(
string="Origin Agency",
help="The agency where the folio sale line originates",
@@ -330,14 +333,38 @@ class FolioSaleLine(models.Model):
store=True,
compute="_compute_date_order",
)
default_invoice_to = fields.Many2one(
string="Invoice to",
help="""Indicates the contact to which this line will be
billed by default, if it is not established,
a guest or the generic contact will be used instead""",
comodel_name="res.partner",
ondelete="restrict",
)
autoinvoice_date = fields.Date(
string="Autoinvoice Date",
compute="_compute_autoinvoice_date",
store=True,
)
@api.depends(
"folio_id.agency_id",
"reservation_line_ids",
"reservation_id.agency_id",
"service_line_ids",
)
def _compute_origin_agency_id(self):
"""
Set the origin agency if the origin lines channel
match with the agency's channel
"""
for rec in self:
rec.origin_agency_id = rec.folio_id.agency_id
# TODO: ServiceLines agency
if rec.folio_id.agency_id and list(
set(rec.reservation_line_ids.mapped("sale_channel_id.id"))
) == rec.folio_id.agency_id.mapped("sale_channel_id.id"):
rec.origin_agency_id = rec.folio_id.agency_id
else:
rec.origin_agency_id = False
@api.depends("qty_to_invoice")
def _compute_service_order(self):
@@ -370,6 +397,54 @@ class FolioSaleLine(models.Model):
else:
record.date_order = 0
@api.depends(
"default_invoice_to",
"invoice_status",
"folio_id.last_checkout",
"reservation_id.checkout",
"service_id.reservation_id.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.default_invoice_to
if self.reservation_id:
last_checkout = self.reservation_id.checkout
elif self.service_id and self.service_id.reservation_id:
last_checkout = self.service_id.reservation_id.checkout
else:
last_checkout = self.folio_id.last_checkout
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 last_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 last_checkout.day <= month_day:
self.autoinvoice_date = last_checkout.replace(day=month_day)
else:
self.autoinvoice_date = (
last_checkout + relativedelta.relativedelta(months=1)
).replace(day=month_day)
@api.depends("date_order")
def _compute_reservation_order(self):
for record in self:

View File

@@ -4,11 +4,8 @@
import datetime
import logging
from datetime import timedelta
from itertools import groupby
from dateutil import relativedelta
from odoo import _, api, fields, models
from odoo.exceptions import AccessError, UserError, ValidationError
from odoo.tools import float_compare, float_is_zero
@@ -625,13 +622,11 @@ class PmsFolio(models.Model):
folio_lines_to_invoice = folio.sale_line_ids.filtered(
lambda l: l.id in list(lines_to_invoice.keys())
)
folio_partner_invoice_id = partner_invoice_id
if not folio_partner_invoice_id:
folio_partner_invoice_id = folio._get_default_partner_invoice_id()
folio._set_default_partner_invoice_id(
folio_lines_to_invoice, partner_invoice_id
)
groups_invoice_lines = folio._get_groups_invoice_lines(
lines_to_invoice=folio_lines_to_invoice,
partner_invoice_id=folio_partner_invoice_id,
)
for group in groups_invoice_lines:
folio = folio.with_company(folio.company_id)
@@ -698,60 +693,49 @@ class PmsFolio(models.Model):
invoice_vals_list.append(invoice_vals)
return invoice_vals_list
def _get_groups_invoice_lines(self, lines_to_invoice, partner_invoice_id):
def _get_groups_invoice_lines(self, lines_to_invoice):
self.ensure_one()
target_lines = lines_to_invoice
if self._context.get("lines_auto_add") and partner_invoice_id:
folio_partner_invoice = self.env["res.partner"].browse(partner_invoice_id)
if folio_partner_invoice.default_invoice_lines == "overnights":
target_lines = target_lines.filtered(
lambda r: r.is_board_service
or (r.reservation_line_ids and r.reservation_id.overnight_room)
)
elif folio_partner_invoice.default_invoice_lines == "reservations":
target_lines = target_lines.filtered(
lambda r: r.is_board_service or r.reservation_line_ids
)
elif folio_partner_invoice.default_invoice_lines == "services":
target_lines = target_lines.filtered(
lambda r: not r.is_board_service or r.service_line_ids
)
groups_invoice_lines = [
{
"partner_id": partner_invoice_id,
"lines": target_lines,
}
]
if (
self.autoinvoice_date
and self.autoinvoice_date <= fields.Date.today()
and len(target_lines) < len(lines_to_invoice)
):
other_partner_to_invoice = self.partner_invoice_ids.filtered(
lambda p: p.id != partner_invoice_id
)
if not other_partner_to_invoice:
other_partner_to_invoice = self.env.ref("pms.various_pms_partner")
groups_invoice_lines = []
partners = lines_to_invoice.mapped("default_invoice_to")
for partner in partners:
groups_invoice_lines.append(
{
"partner_id": other_partner_to_invoice.id,
"lines": lines_to_invoice - target_lines,
"partner_id": partner.id,
"lines": lines_to_invoice.filtered(
lambda l: l.default_invoice_to == partner
),
}
)
return groups_invoice_lines
def _get_default_partner_invoice_id(self):
def _set_default_partner_invoice_id(
self, lines_to_invoice, folio_partner_invoice_id=False
):
# By priotiy:
# 1º- Partner set in parameter,
# 2º- Partner in default_invoice_to in line
# 3º- Partner in folio,
# 4º- Partner in checkins,
# 5º- Generic various partner
self.ensure_one()
folio_partner_invoice_id = False
if self.partner_id and self.partner_id.vat:
folio_partner_invoice_id = self.partner_id.id
if not folio_partner_invoice_id:
folio_partner_invoice_id = (
self.partner_invoice_ids[0].id if self.partner_invoice_ids else False
for line in lines_to_invoice:
if not folio_partner_invoice_id and line.default_invoice_to:
folio_partner_invoice_id = line.default_invoice_to
if (
not folio_partner_invoice_id
and self.partner_id
and self.partner_id._check_enought_invoice_data()
and not self.partner_id.is_agency
):
folio_partner_invoice_id = self.partner_id.id
checkin_invoice_partner = self.checkin_partner_ids.filtered(
lambda c: c.partner_id and c.partner_id._check_enought_invoice_data()
)
if not folio_partner_invoice_id:
folio_partner_invoice_id = self.env.ref("pms.various_pms_partner").id
return folio_partner_invoice_id
if not folio_partner_invoice_id and checkin_invoice_partner:
folio_partner_invoice_id = checkin_invoice_partner[0].partner_id
if not folio_partner_invoice_id:
folio_partner_invoice_id = self.env.ref("pms.various_pms_partner").id
line.default_invoice_to = folio_partner_invoice_id
def _get_tax_amount_by_group(self):
self.ensure_one()
@@ -789,42 +773,6 @@ class PmsFolio(models.Model):
else:
return False
@api.depends("partner_id", "invoice_status", "last_checkout", "partner_invoice_ids")
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.last_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.last_checkout.day <= month_day:
self.autoinvoice_date = self.last_checkout.replace(day=month_day)
else:
self.autoinvoice_date = (
self.last_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:
@@ -848,6 +796,7 @@ class PmsFolio(models.Model):
"reservation_ids",
"service_ids",
"service_ids.reservation_id",
"service_ids.default_invoice_to",
"service_ids.service_line_ids.price_day_total",
"service_ids.service_line_ids.discount",
"service_ids.service_line_ids.cancel_discount",
@@ -857,6 +806,7 @@ class PmsFolio(models.Model):
"reservation_ids.reservation_line_ids.price",
"reservation_ids.reservation_line_ids.discount",
"reservation_ids.reservation_line_ids.cancel_discount",
"reservation_ids.reservation_line_ids.default_invoice_to",
"reservation_ids.tax_ids",
)
def _compute_sale_line_ids(self):
@@ -1054,7 +1004,7 @@ class PmsFolio(models.Model):
"reservation_ids",
"reservation_ids.sale_channel_ids",
"service_ids",
"service_ids.sale_channel_ids",
"service_ids.sale_channel_origin_id",
)
def _compute_sale_channel_ids(self):
for record in self:
@@ -1063,8 +1013,6 @@ class PmsFolio(models.Model):
for sale in record.reservation_ids.mapped("sale_channel_ids.id"):
sale_channel_ids.append(sale)
if record.service_ids:
# si es un board service que mire sale_channel_ids
# y si es un servicio a secas entonces que coja el origen
for sale in record.service_ids.mapped("sale_channel_origin_id.id"):
sale_channel_ids.append(sale)
sale_channel_ids = list(set(sale_channel_ids))
@@ -1910,9 +1858,17 @@ class PmsFolio(models.Model):
self = self.with_context(lines_auto_add=True)
lines_to_invoice = dict()
for line in self.sale_line_ids:
lines_to_invoice[line.id] = (
0 if line.display_type else line.qty_to_invoice
)
if not self._context.get("autoinvoice"):
lines_to_invoice[line.id] = (
0 if line.display_type else line.qty_to_invoice
)
elif (
line.autoinvoice_date
and line.autoinvoice_date <= fields.Date.today()
):
lines_to_invoice[line.id] = (
0 if line.display_type else line.qty_to_invoice
)
invoice_vals_list = self.get_invoice_vals_list(
final=final,
lines_to_invoice=lines_to_invoice,
@@ -2375,8 +2331,8 @@ class PmsFolio(models.Model):
("reservation_id", "=", reservation.id),
("cancel_discount", "<", 100),
],
["price", "discount", "cancel_discount"],
["price", "discount", "cancel_discount"],
["price", "discount", "cancel_discount", "default_invoice_to"],
["price", "discount", "cancel_discount", "default_invoice_to"],
lazy=False,
)
current_sale_line_ids = reservation.sale_line_ids.filtered(
@@ -2391,13 +2347,17 @@ class PmsFolio(models.Model):
final_discount = self.concat_discounts(
item["discount"], item["cancel_discount"]
)
partner_invoice = lines_to.mapped("default_invoice_to")
if current_sale_line_ids and index <= (len(current_sale_line_ids) - 1):
current = {
"price_unit": item["price"],
"discount": final_discount,
"reservation_line_ids": [(6, 0, lines_to.ids)],
"sequence": sequence,
"default_invoice_to": partner_invoice[0].id
if partner_invoice
else current_sale_line_ids[index].default_invoice_to,
}
sale_reservation_vals.append(
(1, current_sale_line_ids[index].id, current)
@@ -2412,6 +2372,9 @@ class PmsFolio(models.Model):
"tax_ids": [(6, 0, reservation.tax_ids.ids)],
"reservation_line_ids": [(6, 0, lines_to.ids)],
"sequence": sequence,
"default_invoice_to": partner_invoice[0].id
if partner_invoice
else False,
}
sale_reservation_vals.append((0, 0, new))
folio_sale_lines_to_remove = []
@@ -2434,8 +2397,8 @@ class PmsFolio(models.Model):
("service_id", "=", service.id),
("cancel_discount", "<", 100),
],
["price_unit", "discount", "cancel_discount"],
["price_unit", "discount", "cancel_discount"],
["price_unit", "discount", "cancel_discount", "default_invoice_to"],
["price_unit", "discount", "cancel_discount", "default_invoice_to"],
lazy=False,
)
current_sale_service_ids = reservation.sale_line_ids.filtered(
@@ -2449,7 +2412,7 @@ class PmsFolio(models.Model):
final_discount = self.concat_discounts(
item["discount"], item["cancel_discount"]
)
partner_invoice = lines_to.mapped("default_invoice_to")
if current_sale_service_ids and index <= (
len(current_sale_service_ids) - 1
):
@@ -2458,6 +2421,9 @@ class PmsFolio(models.Model):
"discount": final_discount,
"service_line_ids": [(6, 0, lines_to.ids)],
"sequence": sequence,
"default_invoice_to": partner_invoice[0].id
if partner_invoice
else current_sale_service_ids[index].default_invoice_to,
}
sale_service_vals.append(
(1, current_sale_service_ids[index].id, current)
@@ -2473,6 +2439,9 @@ class PmsFolio(models.Model):
"product_id": service.product_id.id,
"tax_ids": [(6, 0, service.tax_ids.ids)],
"sequence": sequence,
"default_invoice_to": partner_invoice[0].id
if partner_invoice
else False,
}
sale_service_vals.append((0, 0, new))
sequence = sequence + 1

View File

@@ -607,7 +607,7 @@ class PmsProperty(models.Model):
"""
folios = self.env["pms.folio"].search(
[
("autoinvoice_date", "=", fields.date.today()),
("sale_line_ids.autoinvoice_date", "=", fields.date.today()),
("invoice_status", "=", "to_invoice"),
]
)

View File

@@ -1635,12 +1635,10 @@ class PmsReservation(models.Model):
else:
record.lang = self.env["res.lang"].get_installed()
@api.depends(
"reservation_line_ids",
"reservation_line_ids.sale_channel_id",
"service_ids",
"service_ids.sale_channel_ids",
"service_ids.sale_channel_origin_id",
)
def _compute_sale_channel_ids(self):
@@ -1650,7 +1648,7 @@ class PmsReservation(models.Model):
for sale in record.reservation_line_ids.mapped("sale_channel_id.id"):
sale_channel_ids.append(sale)
if record.service_ids:
for sale in record.service_ids.mapped("sale_channel_ids.id"):
for sale in record.service_ids.mapped("sale_channel_origin_id.id"):
sale_channel_ids.append(sale)
sale_channel_ids = list(set(sale_channel_ids))
record.sale_channel_ids = [(6, 0, sale_channel_ids)]

View File

@@ -45,8 +45,7 @@ class PmsReservationLine(models.Model):
)
pms_property_id = fields.Many2one(
string="Property",
help="Property with access to the element;"
" if not set, all properties can access",
help="Property with access to the element",
readonly=True,
store=True,
comodel_name="pms.property",
@@ -119,6 +118,17 @@ class PmsReservationLine(models.Model):
comodel_name="pms.sale.channel",
check_pms_properties=True,
)
default_invoice_to = fields.Many2one(
string="Invoice to",
help="""Indicates the contact to which this line will be
billed by default, if it is not established,
a guest or the generic contact will be used instead""",
readonly=False,
store=True,
compute="_compute_default_invoice_to",
comodel_name="res.partner",
ondelete="restrict",
)
_sql_constraints = [
(
@@ -502,6 +512,19 @@ class PmsReservationLine(models.Model):
)
return records
@api.depends("sale_channel_id", "reservation_id.agency_id")
def _compute_default_invoice_to(self):
for record in self:
agency = record.reservation_id.agency_id
if (
agency
and agency.invoice_to_agency == "always"
and agency.sale_channel_id == record.sale_channel_id
):
record.default_invoice_to = agency
elif not record.default_invoice_to:
record.default_invoice_to = False
# Constraints and onchanges
@api.constrains("date")
def constrains_duplicated_date(self):

View File

@@ -146,14 +146,6 @@ class PmsService(models.Model):
("no", "Nothing to Invoice"),
],
)
sale_channel_ids = fields.Many2many(
string="Sale Channels",
help="Sale Channels through which service lines were managed",
store=True,
compute="_compute_sale_channel_ids",
comodel_name="pms.sale.channel",
check_pms_properties=True,
)
sale_channel_origin_id = fields.Many2one(
string="Sale Channel Origin",
help="Sale Channel through which service was created, the original",
@@ -199,6 +191,17 @@ class PmsService(models.Model):
""",
default=False,
)
default_invoice_to = fields.Many2one(
string="Invoice to",
help="""Indicates the contact to which this line will be
billed by default, if it is not established,
a guest or the generic contact will be used instead""",
readonly=False,
store=True,
compute="_compute_default_invoice_to",
comodel_name="res.partner",
ondelete="restrict",
)
# Compute and Search methods
@api.depends("product_id")
@@ -427,12 +430,18 @@ class PmsService(models.Model):
line.discount = record.discount
line.cancel_discount = 0
@api.depends("service_line_ids", "service_line_ids.sale_channel_id")
def _compute_sale_channel_ids(self):
@api.depends("sale_channel_origin_id", "folio_id.agency_id")
def _compute_default_invoice_to(self):
for record in self:
record.sale_channel_ids = [
(6, 0, record.mapped("service_line_ids.sale_channel_id.id"))
]
agency = record.folio_id.agency_id
if (
agency
and agency.invoice_to_agency == "always"
and agency.sale_channel_id == record.sale_channel_origin_id
):
record.default_invoice_to = agency
elif not record.default_invoice_to:
record.default_invoice_to = False
def name_get(self):
result = []
@@ -566,7 +575,6 @@ class PmsService(models.Model):
lines_to_update_channel = self.env["pms.service.line"]
if "sale_channel_origin_id" in vals:
folios_to_update_channel = self.get_folios_to_update_channel(vals)
lines_to_update_channel = self.get_service_lines_to_update_channel(vals)
res = super(PmsService, self).write(vals)
if folios_to_update_channel:
folios_to_update_channel.sale_channel_origin_id = vals[
@@ -590,13 +598,3 @@ class PmsService(models.Model):
):
folios_to_update_channel += folio
return folios_to_update_channel
def get_service_lines_to_update_channel(self, vals):
lines_to_update_channel = self.env["pms.service.line"]
for record in self:
for service_line in record.service_line_ids:
if service_line.sale_channel_id == self.sale_channel_origin_id and (
vals["sale_channel_origin_id"] != service_line.sale_channel_id.id
):
lines_to_update_channel += service_line
return lines_to_update_channel

View File

@@ -126,12 +126,6 @@ class PmsServiceLine(models.Model):
readonly=True,
store=True,
)
sale_channel_id = fields.Many2one(
string="Sale Channel",
help="Sale Channel through which service line was created",
comodel_name="pms.sale.channel",
check_pms_properties=True,
)
auto_qty = fields.Boolean(
string="Qty automated setted",
help="Show if the day qty was calculated automatically",
@@ -139,6 +133,16 @@ class PmsServiceLine(models.Model):
readonly=False,
store=True,
)
default_invoice_to = fields.Many2one(
string="Invoice to",
help="""Indicates the contact to which this line will be
billed by default, if it is not established,
a guest or the generic contact will be used instead""",
comodel_name="res.partner",
store=True,
related="service_id.default_invoice_to",
ondelete="restrict",
)
@api.depends("day_qty", "discount", "price_unit", "tax_ids")
def _compute_day_amount_service(self):
@@ -255,15 +259,6 @@ class PmsServiceLine(models.Model):
% (record.service_id.product_id.name, record.date)
)
@api.model
def create(self, vals):
if vals.get("service_id") and not vals.get("sale_channel_id"):
service = self.env["pms.service"].browse(vals["service_id"])
if service.sale_channel_origin_id:
vals["sale_channel_id"] = service.sale_channel_origin_id.id
record = super(PmsServiceLine, self).create(vals)
return record
# Business methods
def _cancel_discount(self):
for record in self:

View File

@@ -192,22 +192,6 @@ class ResPartner(models.Model):
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",
)
vat_document_type = fields.Selection(
string="Document Type",
help="""The vat document type of the partner,