mirror of
https://github.com/OCA/pms.git
synced 2025-01-29 00:17:45 +02:00
822 lines
34 KiB
Python
822 lines
34 KiB
Python
from datetime import datetime, timedelta
|
|
|
|
from odoo import _, fields
|
|
from odoo.exceptions import MissingError, ValidationError
|
|
from odoo.osv import expression
|
|
from odoo.tools import get_lang
|
|
|
|
from odoo.addons.base_rest import restapi
|
|
from odoo.addons.base_rest_datamodel.restapi import Datamodel
|
|
from odoo.addons.component.core import Component
|
|
|
|
|
|
class PmsFolioService(Component):
|
|
_inherit = "base.rest.service"
|
|
_name = "pms.folio.service"
|
|
_usage = "folios"
|
|
_collection = "pms.services"
|
|
|
|
@restapi.method(
|
|
[
|
|
(
|
|
[
|
|
"/<int:folio_id>",
|
|
],
|
|
"GET",
|
|
)
|
|
],
|
|
output_param=Datamodel("pms.folio.info", is_list=False),
|
|
auth="jwt_api_pms",
|
|
)
|
|
def get_folio(self, folio_id):
|
|
folio = self.env["pms.folio"].search(
|
|
[
|
|
("id", "=", folio_id),
|
|
]
|
|
)
|
|
if folio:
|
|
PmsFolioInfo = self.env.datamodels["pms.folio.info"]
|
|
return PmsFolioInfo(
|
|
id=folio.id,
|
|
name=folio.name,
|
|
partnerName=folio.partner_name if folio.partner_name else None,
|
|
partnerPhone=folio.mobile if folio.mobile else None,
|
|
partnerEmail=folio.email if folio.email else None,
|
|
state=dict(folio.fields_get(["state"])["state"]["selection"])[
|
|
folio.state
|
|
],
|
|
amountTotal=round(folio.amount_total, 2),
|
|
reservationType=folio.reservation_type,
|
|
pendingAmount=folio.pending_amount,
|
|
lastCheckout=str(folio.last_checkout),
|
|
internalComment=folio.internal_comment
|
|
if folio.internal_comment
|
|
else None,
|
|
invoiceStatus=folio.invoice_status,
|
|
pricelistId=folio.pricelist_id if folio.pricelist_id else None,
|
|
saleChannelId=folio.sale_channel_origin_id
|
|
if folio.sale_channel_origin_id
|
|
else None,
|
|
agencyId=folio.agency_id if folio.agency_id else None,
|
|
externalReference=folio.external_reference
|
|
if folio.external_reference
|
|
else None,
|
|
)
|
|
else:
|
|
raise MissingError(_("Folio not found"))
|
|
|
|
@restapi.method(
|
|
[
|
|
(
|
|
[
|
|
"/",
|
|
],
|
|
"GET",
|
|
)
|
|
],
|
|
input_param=Datamodel("pms.folio.search.param"),
|
|
output_param=Datamodel("pms.folio.short.info", is_list=True),
|
|
auth="jwt_api_pms",
|
|
)
|
|
def get_folios(self, folio_search_param):
|
|
domain_fields = list()
|
|
|
|
domain_fields.append(("pms_property_id", "=", folio_search_param.pmsPropertyId))
|
|
|
|
if folio_search_param.dateTo and folio_search_param.dateFrom:
|
|
date_from = fields.Date.from_string(folio_search_param.dateFrom)
|
|
date_to = fields.Date.from_string(folio_search_param.dateTo)
|
|
dates = [
|
|
date_from + timedelta(days=x)
|
|
for x in range(0, (date_to - date_from).days + 1)
|
|
]
|
|
reservation_lines = list(
|
|
set(
|
|
self.env["pms.reservation.line"]
|
|
.search([("date", "in", dates)])
|
|
.mapped("reservation_id")
|
|
.mapped("folio_id")
|
|
.ids
|
|
)
|
|
)
|
|
domain_fields.append(("folio_id", "in", reservation_lines))
|
|
|
|
domain_filter = list()
|
|
if folio_search_param.filter:
|
|
target = folio_search_param.filter
|
|
if "@" in target:
|
|
domain_filter.append(("email", "ilike", target))
|
|
else:
|
|
subdomains = [
|
|
[("name", "ilike", target)],
|
|
[("partner_name", "ilike", "%".join(target.split(" ")))],
|
|
[("mobile", "ilike", target)],
|
|
[("external_reference", "ilike", target)],
|
|
]
|
|
domain_filter.append(expression.OR(subdomains))
|
|
domain = []
|
|
if domain_filter:
|
|
domain = expression.AND([domain_fields, domain_filter[0]])
|
|
else:
|
|
domain = domain_fields
|
|
result_folios = []
|
|
|
|
reservations_result = (
|
|
self.env["pms.reservation"].search(domain).mapped("folio_id").ids
|
|
)
|
|
|
|
PmsFolioShortInfo = self.env.datamodels["pms.folio.short.info"]
|
|
for folio in self.env["pms.folio"].search(
|
|
[("id", "in", reservations_result)], order="write_date desc"
|
|
):
|
|
reservations = []
|
|
for reservation in folio.reservation_ids:
|
|
reservations.append(
|
|
{
|
|
"id": reservation.id,
|
|
"checkin": datetime.combine(
|
|
reservation.checkin, datetime.min.time()
|
|
).isoformat(),
|
|
"checkout": datetime.combine(
|
|
reservation.checkout, datetime.min.time()
|
|
).isoformat(),
|
|
"preferredRoomId": reservation.preferred_room_id.id
|
|
if reservation.preferred_room_id
|
|
else None,
|
|
"roomTypeId": reservation.room_type_id.id
|
|
if reservation.room_type_id
|
|
else None,
|
|
"adults": reservation.adults,
|
|
"pricelistId": reservation.pricelist_id.id
|
|
if reservation.pricelist_id
|
|
else None,
|
|
"saleChannelId": reservation.sale_channel_origin_id.id
|
|
if reservation.sale_channel_origin_id
|
|
else None,
|
|
"agencyId": reservation.agency_id.id
|
|
if reservation.agency_id
|
|
else None,
|
|
"splitted": reservation.splitted,
|
|
}
|
|
)
|
|
result_folios.append(
|
|
PmsFolioShortInfo(
|
|
id=folio.id,
|
|
partnerName=folio.partner_name if folio.partner_name else None,
|
|
partnerPhone=folio.mobile if folio.mobile else None,
|
|
partnerEmail=folio.email if folio.email else None,
|
|
amountTotal=round(folio.amount_total, 2),
|
|
reservations=[] if not reservations else reservations,
|
|
paymentStateCode=folio.payment_state,
|
|
paymentStateDescription=dict(
|
|
folio.fields_get(["payment_state"])["payment_state"][
|
|
"selection"
|
|
]
|
|
)[folio.payment_state],
|
|
)
|
|
)
|
|
return result_folios
|
|
|
|
@restapi.method(
|
|
[
|
|
(
|
|
[
|
|
"/<int:folio_id>/transactions",
|
|
],
|
|
"GET",
|
|
)
|
|
],
|
|
input_param=Datamodel("pms.search.param"),
|
|
output_param=Datamodel("pms.transaction.info", is_list=True),
|
|
auth="jwt_api_pms",
|
|
)
|
|
def get_folio_transactions(self, folio_id, pms_search_param):
|
|
domain = list()
|
|
domain.append(("id", "=", folio_id))
|
|
if pms_search_param.pmsPropertyId:
|
|
domain.append(("pms_property_id", "=", pms_search_param.pmsPropertyId))
|
|
folio = self.env["pms.folio"].search(domain)
|
|
transactions = []
|
|
PmsTransactiontInfo = self.env.datamodels["pms.transaction.info"]
|
|
if not folio:
|
|
pass
|
|
else:
|
|
# if folio.payment_state == "not_paid":
|
|
# pass
|
|
# else:
|
|
if folio.payment_ids:
|
|
for payment in folio.payment_ids.filtered(
|
|
lambda p: p.state == "posted"
|
|
):
|
|
payment._compute_pms_api_transaction_type()
|
|
transactions.append(
|
|
PmsTransactiontInfo(
|
|
id=payment.id,
|
|
amount=round(payment.amount, 2),
|
|
journalId=payment.journal_id.id,
|
|
date=datetime.combine(
|
|
payment.date, datetime.min.time()
|
|
).isoformat(),
|
|
transactionType=payment.pms_api_transaction_type,
|
|
partnerId=payment.partner_id.id
|
|
if payment.partner_id
|
|
else None,
|
|
partnerName=payment.partner_id.name
|
|
if payment.partner_id
|
|
else None,
|
|
reference=payment.ref if payment.ref else None,
|
|
isReconcilied=(payment.reconciled_statements_count > 0),
|
|
downPaymentInvoiceId=payment.reconciled_invoice_ids.filtered(
|
|
lambda inv: inv._is_downpayment()
|
|
),
|
|
)
|
|
)
|
|
return transactions
|
|
|
|
@restapi.method(
|
|
[
|
|
(
|
|
[
|
|
"/<int:folio_id>/charge",
|
|
],
|
|
"POST",
|
|
)
|
|
],
|
|
input_param=Datamodel("pms.transaction.info", is_list=False),
|
|
auth="jwt_api_pms",
|
|
)
|
|
def create_folio_charge(self, folio_id, pms_account_payment_info):
|
|
folio = self.env["pms.folio"].browse(folio_id)
|
|
partner_id = self.env["res.partner"].browse(pms_account_payment_info.partnerId)
|
|
journal = self.env["account.journal"].browse(pms_account_payment_info.journalId)
|
|
reservations = (
|
|
self.env["pms.reservation"].browse(pms_account_payment_info.reservationIds)
|
|
if pms_account_payment_info.reservationIds
|
|
else False
|
|
)
|
|
if journal.type == "cash":
|
|
# REVIEW: Temporaly, if not cash session open, create a new one automatically
|
|
# Review this in pms_folio_service (/charge & /refund)
|
|
# and in pms_transaction_service (POST)
|
|
last_session = self._get_last_cash_session(journal_id=journal.id)
|
|
if last_session.state != "open":
|
|
self._action_open_cash_session(
|
|
pms_property_id=folio.pms_property_id.id,
|
|
amount=last_session.balance_end_real,
|
|
journal_id=journal.id,
|
|
force=False,
|
|
)
|
|
self.env["pms.folio"].do_payment(
|
|
journal,
|
|
journal.suspense_account_id,
|
|
self.env.user,
|
|
pms_account_payment_info.amount,
|
|
folio,
|
|
reservations=reservations,
|
|
services=False,
|
|
partner=partner_id,
|
|
date=datetime.strptime(pms_account_payment_info.date, "%m/%d/%Y"),
|
|
)
|
|
|
|
@restapi.method(
|
|
[
|
|
(
|
|
[
|
|
"/<int:folio_id>/refund",
|
|
],
|
|
"POST",
|
|
)
|
|
],
|
|
input_param=Datamodel("pms.transaction.info", is_list=False),
|
|
auth="jwt_api_pms",
|
|
)
|
|
def create_folio_refund(self, folio_id, pms_account_payment_info):
|
|
folio = self.env["pms.folio"].browse(folio_id)
|
|
partner_id = self.env["res.partner"].browse(pms_account_payment_info.partnerId)
|
|
journal = self.env["account.journal"].browse(pms_account_payment_info.journalId)
|
|
if journal.type == "cash":
|
|
# REVIEW: Temporaly, if not cash session open, create a new one automatically
|
|
# Review this in pms_folio_service (/charge & /refund)
|
|
# and in pms_transaction_service (POST)
|
|
last_session = self._get_last_cash_session(journal_id=journal.id)
|
|
if last_session.state != "open":
|
|
self._action_open_cash_session(
|
|
pms_property_id=folio.pms_property_id.id,
|
|
amount=last_session.balance_end_real,
|
|
journal_id=journal.id,
|
|
force=False,
|
|
)
|
|
self.env["pms.folio"].do_refund(
|
|
journal,
|
|
journal.suspense_account_id,
|
|
self.env.user,
|
|
pms_account_payment_info.amount,
|
|
folio,
|
|
reservations=False,
|
|
services=False,
|
|
partner=partner_id,
|
|
date=datetime.strptime(pms_account_payment_info.date, "%m/%d/%Y"),
|
|
ref=pms_account_payment_info.reference,
|
|
)
|
|
|
|
@restapi.method(
|
|
[
|
|
(
|
|
[
|
|
"/<int:folio_id>/reservations",
|
|
],
|
|
"GET",
|
|
)
|
|
],
|
|
output_param=Datamodel("pms.reservation.short.info", is_list=True),
|
|
auth="jwt_api_pms",
|
|
)
|
|
def get_folio_reservations(self, folio_id):
|
|
folio = self.env["pms.folio"].browse(folio_id)
|
|
reservations = []
|
|
PmsReservationShortInfo = self.env.datamodels["pms.reservation.short.info"]
|
|
if not folio:
|
|
pass
|
|
else:
|
|
if folio.reservation_ids:
|
|
for reservation in folio.reservation_ids:
|
|
reservations.append(
|
|
PmsReservationShortInfo(
|
|
id=reservation.id,
|
|
boardServiceId=reservation.board_service_room_id.id
|
|
if reservation.board_service_room_id
|
|
else None,
|
|
checkin=datetime.combine(
|
|
reservation.checkin, datetime.min.time()
|
|
).isoformat(),
|
|
checkout=datetime.combine(
|
|
reservation.checkout, datetime.min.time()
|
|
).isoformat(),
|
|
roomTypeId=reservation.room_type_id.id
|
|
if reservation.room_type_id
|
|
else None,
|
|
preferredRoomId=reservation.preferred_room_id.id
|
|
if reservation.preferred_room_id
|
|
else None,
|
|
adults=reservation.adults,
|
|
stateCode=reservation.state,
|
|
stateDescription=dict(
|
|
reservation.fields_get(["state"])["state"]["selection"]
|
|
)[reservation.state],
|
|
children=reservation.children
|
|
if reservation.children
|
|
else None,
|
|
readyForCheckin=reservation.ready_for_checkin,
|
|
allowedCheckout=reservation.allowed_checkout,
|
|
splitted=reservation.splitted,
|
|
priceTotal=round(reservation.price_room_services_set, 2),
|
|
servicesCount=sum(
|
|
reservation.service_ids.filtered(
|
|
lambda x: not x.is_board_service
|
|
).mapped("product_qty")
|
|
),
|
|
)
|
|
)
|
|
|
|
return reservations
|
|
|
|
@restapi.method(
|
|
[
|
|
(
|
|
[
|
|
"/",
|
|
],
|
|
"POST",
|
|
)
|
|
],
|
|
input_param=Datamodel("pms.folio.info", is_list=False),
|
|
auth="jwt_api_pms",
|
|
)
|
|
def create_folio(self, pms_folio_info):
|
|
if pms_folio_info.reservationType == "out":
|
|
vals = {
|
|
"pms_property_id": pms_folio_info.pmsPropertyId,
|
|
"reservation_type": pms_folio_info.reservationType,
|
|
"closure_reason_id": pms_folio_info.closureReasonId,
|
|
}
|
|
else:
|
|
vals = {
|
|
"pms_property_id": pms_folio_info.pmsPropertyId,
|
|
"sale_channel_origin_id": pms_folio_info.saleChannelId,
|
|
"agency_id": pms_folio_info.agencyId
|
|
if pms_folio_info.agencyId
|
|
else False,
|
|
"reservation_type": pms_folio_info.reservationType,
|
|
"internal_comment": pms_folio_info.internalComment,
|
|
}
|
|
if pms_folio_info.partnerId:
|
|
vals.update(
|
|
{
|
|
"partner_id": pms_folio_info.partnerId,
|
|
}
|
|
)
|
|
else:
|
|
if pms_folio_info.partnerName:
|
|
vals.update(
|
|
{
|
|
"partner_name": pms_folio_info.partnerName,
|
|
}
|
|
)
|
|
if pms_folio_info.partnerPhone:
|
|
vals.update(
|
|
{
|
|
"mobile": pms_folio_info.partnerPhone,
|
|
}
|
|
)
|
|
if pms_folio_info.partnerEmail:
|
|
vals.update(
|
|
{
|
|
"email": pms_folio_info.partnerEmail,
|
|
}
|
|
)
|
|
folio = self.env["pms.folio"].create(vals)
|
|
for reservation in pms_folio_info.reservations:
|
|
vals = {
|
|
"folio_id": folio.id,
|
|
"room_type_id": reservation.roomTypeId,
|
|
"checkin": reservation.checkin,
|
|
"checkout": reservation.checkout,
|
|
"pms_property_id": pms_folio_info.pmsPropertyId,
|
|
"pricelist_id": pms_folio_info.pricelistId,
|
|
"external_reference": pms_folio_info.externalReference,
|
|
"board_service_room_id": reservation.boardServiceId,
|
|
"preferred_room_id": reservation.preferredRoomId,
|
|
"adults": reservation.adults,
|
|
"reservation_type": pms_folio_info.reservationType,
|
|
"children": reservation.children,
|
|
"preconfirm": pms_folio_info.preconfirm,
|
|
}
|
|
reservation_record = self.env["pms.reservation"].create(vals)
|
|
if reservation.services:
|
|
for service in reservation.services:
|
|
vals = {
|
|
"product_id": service.productId,
|
|
"reservation_id": reservation_record.id,
|
|
"is_board_service": False,
|
|
"service_line_ids": [
|
|
(
|
|
0,
|
|
False,
|
|
{
|
|
"date": line.date,
|
|
"price_unit": line.priceUnit,
|
|
"discount": line.discount or 0,
|
|
"day_qty": line.quantity,
|
|
},
|
|
)
|
|
for line in service.serviceLines
|
|
],
|
|
}
|
|
self.env["pms.service"].create(vals)
|
|
# REVIEW: analyze how to integrate the sending of mails from the API
|
|
# with the configuration of the automatic mails pms
|
|
# &
|
|
# the sending of mail should be a specific call once the folio has been created?
|
|
if folio and folio.email and pms_folio_info.sendConfirmationMail:
|
|
template = folio.pms_property_id.property_confirmed_template
|
|
if not template:
|
|
raise ValidationError(
|
|
_("There is no confirmation template for this property")
|
|
)
|
|
email_values = {
|
|
"email_to": folio.email,
|
|
"email_from": folio.pms_property_id.email
|
|
if folio.pms_property_id.email
|
|
else False,
|
|
"auto_delete": False,
|
|
}
|
|
template.send_mail(folio.id, force_send=True, email_values=email_values)
|
|
return folio.id
|
|
|
|
@restapi.method(
|
|
[
|
|
(
|
|
[
|
|
"/p/<int:folio_id>",
|
|
],
|
|
"PATCH",
|
|
)
|
|
],
|
|
input_param=Datamodel("pms.folio.info", is_list=False),
|
|
auth="jwt_api_pms",
|
|
)
|
|
def update_folio(self, folio_id, pms_folio_info):
|
|
folio = self.env["pms.folio"].browse(folio_id)
|
|
folio_vals = {}
|
|
if not folio:
|
|
raise MissingError(_("Folio not found"))
|
|
if pms_folio_info.cancelReservations:
|
|
folio.action_cancel()
|
|
if pms_folio_info.confirmReservations:
|
|
for reservation in folio.reservation_ids:
|
|
reservation.confirm()
|
|
if pms_folio_info.internalComment is not None:
|
|
folio_vals.update({"internal_comment": pms_folio_info.internalComment})
|
|
if pms_folio_info.reservations:
|
|
for reservation in pms_folio_info.reservations:
|
|
vals = {
|
|
"folio_id": folio.id,
|
|
"room_type_id": reservation.roomTypeId,
|
|
"checkin": reservation.checkin,
|
|
"checkout": reservation.checkout,
|
|
"pms_property_id": pms_folio_info.pmsPropertyId,
|
|
"pricelist_id": pms_folio_info.pricelistId,
|
|
"external_reference": pms_folio_info.externalReference,
|
|
"board_service_room_id": reservation.boardServiceId,
|
|
"preferred_room_id": reservation.preferredRoomId,
|
|
"adults": reservation.adults,
|
|
"reservation_type": pms_folio_info.reservationType,
|
|
"children": reservation.children,
|
|
}
|
|
reservation_record = self.env["pms.reservation"].create(vals)
|
|
if reservation.services:
|
|
for service in reservation.services:
|
|
vals = {
|
|
"product_id": service.productId,
|
|
"reservation_id": reservation_record.id,
|
|
"is_board_service": False,
|
|
"service_line_ids": [
|
|
(
|
|
0,
|
|
False,
|
|
{
|
|
"date": line.date,
|
|
"price_unit": line.priceUnit,
|
|
"discount": line.discount or 0,
|
|
"day_qty": line.quantity,
|
|
},
|
|
)
|
|
for line in service.serviceLines
|
|
],
|
|
}
|
|
self.env["pms.service"].create(vals)
|
|
if folio_vals:
|
|
folio.write(folio_vals)
|
|
|
|
@restapi.method(
|
|
[
|
|
(
|
|
[
|
|
"/<int:folio_id>/sale-lines",
|
|
],
|
|
"GET",
|
|
)
|
|
],
|
|
output_param=Datamodel("pms.folio.sale.line.info", is_list=True),
|
|
auth="jwt_api_pms",
|
|
)
|
|
def get_folio_sale_lines(self, folio_id):
|
|
folio = self.env["pms.folio"].browse(folio_id)
|
|
sale_lines = []
|
|
if not folio:
|
|
pass
|
|
else:
|
|
PmsFolioSaleLineInfo = self.env.datamodels["pms.folio.sale.line.info"]
|
|
if folio.sale_line_ids:
|
|
for sale_line in folio.sale_line_ids:
|
|
sale_lines.append(
|
|
PmsFolioSaleLineInfo(
|
|
id=sale_line.id if sale_line.id else None,
|
|
name=sale_line.name if sale_line.name else None,
|
|
priceUnit=sale_line.price_unit
|
|
if sale_line.price_unit
|
|
else None,
|
|
qtyToInvoice=self._get_section_qty_to_invoice(sale_line)
|
|
if sale_line.display_type == "line_section"
|
|
else sale_line.qty_to_invoice,
|
|
qtyInvoiced=sale_line.qty_invoiced
|
|
if sale_line.qty_invoiced
|
|
else None,
|
|
priceTotal=sale_line.price_total
|
|
if sale_line.price_total
|
|
else None,
|
|
discount=sale_line.discount if sale_line.discount else None,
|
|
productQty=sale_line.product_uom_qty
|
|
if sale_line.product_uom_qty
|
|
else None,
|
|
reservationId=sale_line.reservation_id
|
|
if sale_line.reservation_id
|
|
else None,
|
|
serviceId=sale_line.service_id
|
|
if sale_line.service_id
|
|
else None,
|
|
displayType=sale_line.display_type
|
|
if sale_line.display_type
|
|
else None,
|
|
defaultInvoiceTo=sale_line.default_invoice_to
|
|
if sale_line.default_invoice_to
|
|
else None,
|
|
isDownPayment=sale_line.is_downpayment,
|
|
)
|
|
)
|
|
|
|
return sale_lines
|
|
|
|
@restapi.method(
|
|
[
|
|
(
|
|
[
|
|
"/<int:folio_id>/invoices",
|
|
],
|
|
"GET",
|
|
)
|
|
],
|
|
output_param=Datamodel("pms.invoice.info", is_list=True),
|
|
auth="jwt_api_pms",
|
|
)
|
|
def get_folio_invoices(self, folio_id):
|
|
folio = self.env["pms.folio"].browse(folio_id)
|
|
invoices = []
|
|
if not folio:
|
|
pass
|
|
else:
|
|
PmsFolioInvoiceInfo = self.env.datamodels["pms.invoice.info"]
|
|
PmsInvoiceLineInfo = self.env.datamodels["pms.invoice.line.info"]
|
|
if folio.move_ids:
|
|
for move in folio.move_ids:
|
|
move_lines = []
|
|
for move_line in move.invoice_line_ids:
|
|
move_lines.append(
|
|
PmsInvoiceLineInfo(
|
|
id=move_line.id,
|
|
name=move_line.name if move_line.name else None,
|
|
quantity=move_line.quantity
|
|
if move_line.quantity
|
|
else None,
|
|
priceUnit=move_line.price_unit
|
|
if move_line.price_unit
|
|
else None,
|
|
total=move_line.price_total
|
|
if move_line.price_total
|
|
else None,
|
|
discount=move_line.discount
|
|
if move_line.discount
|
|
else None,
|
|
displayType=move_line.display_type
|
|
if move_line.display_type
|
|
else None,
|
|
saleLineId=move_line.folio_line_ids
|
|
if move_line.folio_line_ids
|
|
else None,
|
|
isDownPayment=move_line.move_id._is_downpayment(),
|
|
)
|
|
)
|
|
move_url = (
|
|
move.get_proforma_portal_url()
|
|
if move.state == "draft"
|
|
else move.get_portal_url()
|
|
)
|
|
portal_url = (
|
|
self.env["ir.config_parameter"].sudo().get_param("web.base.url")
|
|
+ move_url
|
|
)
|
|
invoices.append(
|
|
PmsFolioInvoiceInfo(
|
|
id=move.id if move.id else None,
|
|
name=move.name if move.name else None,
|
|
amount=round(move.amount_total, 2)
|
|
if move.amount_total
|
|
else None,
|
|
date=move.invoice_date.strftime("%d/%m/%Y")
|
|
if move.invoice_date
|
|
else None,
|
|
state=move.state if move.state else None,
|
|
paymentState=move.payment_state
|
|
if move.payment_state
|
|
else None,
|
|
partnerName=move.partner_id.name
|
|
if move.partner_id.name
|
|
else None,
|
|
partnerId=move.partner_id.id
|
|
if move.partner_id.id
|
|
else None,
|
|
moveLines=move_lines if move_lines else None,
|
|
portalUrl=portal_url,
|
|
moveType=move.move_type,
|
|
isReversed=move.payment_state == "reversed",
|
|
isDownPaymentInvoice=move._is_downpayment(),
|
|
isSimplifiedInvoice=move.journal_id.is_simplified_invoice,
|
|
)
|
|
)
|
|
return invoices
|
|
|
|
@restapi.method(
|
|
[
|
|
(
|
|
[
|
|
"/<int:folio_id>/invoices",
|
|
],
|
|
"POST",
|
|
)
|
|
],
|
|
input_param=Datamodel("pms.invoice.info", is_list=False),
|
|
auth="jwt_api_pms",
|
|
)
|
|
def create_folio_invoices(self, folio_id, invoice_info):
|
|
# TODO: Missing payload data:
|
|
# - date format is in invoice_info but dont save
|
|
# - invoice comment is in invoice_info but dont save
|
|
|
|
lines_to_invoice_dict = dict()
|
|
for item in invoice_info.saleLines:
|
|
if item.qtyToInvoice:
|
|
lines_to_invoice_dict[item.id] = item.qtyToInvoice
|
|
|
|
sale_lines_to_invoice = self.env["folio.sale.line"].browse(
|
|
lines_to_invoice_dict.keys()
|
|
)
|
|
folios_to_invoice = sale_lines_to_invoice.folio_id
|
|
invoices = folios_to_invoice._create_invoices(
|
|
lines_to_invoice=lines_to_invoice_dict,
|
|
partner_invoice_id=invoice_info.partnerId,
|
|
final=True, # To force take into account down payments
|
|
)
|
|
# TODO: Proposed improvement with strong refactoring:
|
|
# modify the folio _create_invoices() method so that it allows specifying any
|
|
# lines field before creation (right now it only allows quantity),
|
|
# avoiding having to review the lines to modify them afterwards
|
|
for item in invoice_info.saleLines:
|
|
if item.id in invoices.invoice_line_ids.mapped("folio_line_ids.id"):
|
|
invoice_line = invoices.invoice_line_ids.filtered(
|
|
lambda r: item.id in r.folio_line_ids.ids
|
|
and not any([r.folio_line_ids.is_downpayment])
|
|
# To avoid modifying down payments description
|
|
)
|
|
if invoice_line:
|
|
invoice_line.write({"name": item.name})
|
|
if invoice_info.narration:
|
|
invoices.write({"narration": invoice_info.narration})
|
|
|
|
return invoices.ids
|
|
|
|
# TODO: Used for the temporary function of auto-open cash session
|
|
# (View: charge/refund endpoints)
|
|
def _get_last_cash_session(self, journal_id, pms_property_id=False):
|
|
domain = [("journal_id", "=", journal_id)]
|
|
if pms_property_id:
|
|
domain.append(("pms_property_id", "=", pms_property_id))
|
|
return (
|
|
self.env["account.bank.statement"]
|
|
.sudo()
|
|
.search(
|
|
domain,
|
|
order="date desc, id desc",
|
|
limit=1,
|
|
)
|
|
)
|
|
|
|
# TODO: Used for the temporary function of auto-open cash session
|
|
# (View: charge/refund endpoints))
|
|
def _action_open_cash_session(self, pms_property_id, amount, journal_id, force):
|
|
statement = self._get_last_cash_session(
|
|
journal_id=journal_id,
|
|
pms_property_id=pms_property_id,
|
|
)
|
|
if round(statement.balance_end_real, 2) == round(amount, 2) or force:
|
|
self.env["account.bank.statement"].sudo().create(
|
|
{
|
|
"name": datetime.today().strftime(get_lang(self.env).date_format)
|
|
+ " ("
|
|
+ self.env.user.login
|
|
+ ")",
|
|
"date": datetime.today(),
|
|
"balance_start": amount,
|
|
"journal_id": journal_id,
|
|
"pms_property_id": pms_property_id,
|
|
}
|
|
)
|
|
diff = round(amount - statement.balance_end_real, 2)
|
|
return {"result": True, "diff": diff}
|
|
else:
|
|
diff = round(amount - statement.balance_end_real, 2)
|
|
return {"result": False, "diff": diff}
|
|
|
|
def _get_section_qty_to_invoice(self, sale_line):
|
|
folio = sale_line.folio_id
|
|
if sale_line.display_type == "line_section":
|
|
# Get if the section has a lines to invoice
|
|
seq = sale_line.sequence
|
|
next_line_section = folio.sale_line_ids.filtered(
|
|
lambda l: l.sequence > seq and l.display_type == "line_section"
|
|
)
|
|
if next_line_section:
|
|
return sum(
|
|
folio.sale_line_ids.filtered(
|
|
lambda l: l.sequence > seq
|
|
and l.sequence < next_line_section[0].sequence
|
|
and l.display_type != "line_section"
|
|
).mapped("qty_to_invoice")
|
|
)
|
|
else:
|
|
return sum(
|
|
folio.sale_line_ids.filtered(
|
|
lambda l: l.sequence > seq and l.display_type != "line_section"
|
|
).mapped("qty_to_invoice")
|
|
)
|
|
return False
|