mirror of
https://github.com/OCA/pms.git
synced 2025-01-29 00:17:45 +02:00
14.0 pms account flow (#36)
* [WIP] Basic tests definition * [DEL] Default diff invoicing * [WIP] Reservation refact invoice fields * [FIX] test price without taxes * [FIX] Security csv merge * [WIP]pms: Wizard adv inv views * [ADD] Wizard Filter Invoice Days * [WIP] Payment WorkFlow
This commit is contained in:
@@ -3,3 +3,6 @@ from . import wizard_massive_changes
|
||||
from . import wizard_advanced_filters
|
||||
from . import wizard_folio
|
||||
from . import wizard_folio_availability
|
||||
from . import folio_make_invoice_advance
|
||||
from . import wizard_invoice_filter_days
|
||||
from . import wizard_payment_folio
|
||||
|
||||
288
pms/wizards/folio_make_invoice_advance.py
Normal file
288
pms/wizards/folio_make_invoice_advance.py
Normal file
@@ -0,0 +1,288 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import time
|
||||
|
||||
from odoo import _, api, fields, models
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
|
||||
class FolioAdvancePaymentInv(models.TransientModel):
|
||||
_name = "folio.advance.payment.inv"
|
||||
_description = "Folio Advance Payment Invoice"
|
||||
|
||||
@api.model
|
||||
def _count(self):
|
||||
return len(self._context.get("active_ids", []))
|
||||
|
||||
@api.model
|
||||
def _default_product_id(self):
|
||||
product_id = (
|
||||
self.env["ir.config_parameter"]
|
||||
.sudo()
|
||||
.get_param("sale.default_deposit_product_id")
|
||||
)
|
||||
return self.env["product.product"].browse(int(product_id)).exists()
|
||||
|
||||
@api.model
|
||||
def _default_deposit_account_id(self):
|
||||
return self._default_product_id()._get_product_accounts()["income"]
|
||||
|
||||
@api.model
|
||||
def _default_deposit_taxes_id(self):
|
||||
return self._default_product_id().taxes_id
|
||||
|
||||
@api.model
|
||||
def _default_has_down_payment(self):
|
||||
if self._context.get("active_model") == "pms.folio" and self._context.get(
|
||||
"active_id", False
|
||||
):
|
||||
folio = self.env["pms.folio"].browse(self._context.get("active_id"))
|
||||
return folio.sale_line_ids.filtered(lambda line: line.is_downpayment)
|
||||
|
||||
return False
|
||||
|
||||
@api.model
|
||||
def _default_currency_id(self):
|
||||
if self._context.get("active_model") == "pms.folio" and self._context.get(
|
||||
"active_id", False
|
||||
):
|
||||
sale_order = self.env["pms.folio"].browse(self._context.get("active_id"))
|
||||
return sale_order.currency_id
|
||||
|
||||
advance_payment_method = fields.Selection(
|
||||
[
|
||||
("delivered", "Regular invoice"),
|
||||
("percentage", "Down payment (percentage)"),
|
||||
("fixed", "Down payment (fixed amount)"),
|
||||
],
|
||||
string="Create Invoice",
|
||||
default="delivered",
|
||||
required=True,
|
||||
help="A standard invoice is issued with all the order \
|
||||
lines ready for invoicing, \
|
||||
according to their invoicing policy \
|
||||
(based on ordered or delivered quantity).",
|
||||
)
|
||||
bill_services = fields.Boolean("Bill Services", default=True)
|
||||
bill_rooms = fields.Boolean("Bill Rooms", default=True)
|
||||
deduct_down_payments = fields.Boolean("Deduct down payments", default=True)
|
||||
has_down_payments = fields.Boolean(
|
||||
"Has down payments", default=_default_has_down_payment, readonly=True
|
||||
)
|
||||
product_id = fields.Many2one(
|
||||
"product.product",
|
||||
string="Down Payment Product",
|
||||
domain=[("type", "=", "service")],
|
||||
default=_default_product_id,
|
||||
)
|
||||
count = fields.Integer(default=_count, string="Order Count")
|
||||
amount = fields.Float(
|
||||
"Down Payment Amount",
|
||||
digits="Account",
|
||||
help="The percentage of amount to be invoiced in advance, taxes excluded.",
|
||||
)
|
||||
currency_id = fields.Many2one(
|
||||
"res.currency", string="Currency", default=_default_currency_id
|
||||
)
|
||||
fixed_amount = fields.Monetary(
|
||||
"Down Payment Amount (Fixed)",
|
||||
help="The fixed amount to be invoiced in advance, taxes excluded.",
|
||||
)
|
||||
deposit_account_id = fields.Many2one(
|
||||
"account.account",
|
||||
string="Income Account",
|
||||
domain=[("deprecated", "=", False)],
|
||||
help="Account used for deposits",
|
||||
default=_default_deposit_account_id,
|
||||
)
|
||||
deposit_taxes_id = fields.Many2many(
|
||||
"account.tax",
|
||||
string="Customer Taxes",
|
||||
help="Taxes used for deposits",
|
||||
default=_default_deposit_taxes_id,
|
||||
)
|
||||
|
||||
@api.onchange("advance_payment_method")
|
||||
def onchange_advance_payment_method(self):
|
||||
if self.advance_payment_method == "percentage":
|
||||
amount = self.default_get(["amount"]).get("amount")
|
||||
return {"value": {"amount": amount}}
|
||||
return {}
|
||||
|
||||
def _prepare_invoice_values(self, order, name, amount, line):
|
||||
invoice_vals = {
|
||||
"ref": order.client_order_ref,
|
||||
"move_type": "out_invoice",
|
||||
"invoice_origin": order.name,
|
||||
"invoice_user_id": order.user_id.id,
|
||||
"narration": order.note,
|
||||
"partner_id": order.partner_invoice_id.id,
|
||||
"currency_id": order.pricelist_id.currency_id.id,
|
||||
"payment_reference": order.reference,
|
||||
"invoice_payment_term_id": order.payment_term_id.id,
|
||||
"partner_bank_id": order.company_id.partner_id.bank_ids[:1].id,
|
||||
# 'campaign_id': order.campaign_id.id,
|
||||
# 'medium_id': order.medium_id.id,
|
||||
# 'source_id': order.source_id.id,
|
||||
"invoice_line_ids": [
|
||||
(
|
||||
0,
|
||||
0,
|
||||
{
|
||||
"name": name,
|
||||
"price_unit": amount,
|
||||
"quantity": 1.0,
|
||||
"product_id": self.product_id.id,
|
||||
"product_uom_id": line.product_uom.id,
|
||||
"tax_ids": [(6, 0, line.tax_ids.ids)],
|
||||
"folio_line_ids": [(6, 0, [line.id])],
|
||||
"analytic_tag_ids": [(6, 0, line.analytic_tag_ids.ids)],
|
||||
"analytic_account_id": order.analytic_account_id.id or False,
|
||||
},
|
||||
)
|
||||
],
|
||||
}
|
||||
|
||||
return invoice_vals
|
||||
|
||||
def _get_advance_details(self, order):
|
||||
context = {"lang": order.partner_id.lang}
|
||||
if self.advance_payment_method == "percentage":
|
||||
amount = order.amount_untaxed * self.amount / 100
|
||||
name = _("Down payment of %s%%") % (self.amount)
|
||||
else:
|
||||
amount = self.fixed_amount
|
||||
name = _("Down Payment")
|
||||
del context
|
||||
|
||||
return amount, name
|
||||
|
||||
def _create_invoice(self, order, line, amount):
|
||||
if (self.advance_payment_method == "percentage" and self.amount <= 0.00) or (
|
||||
self.advance_payment_method == "fixed" and self.fixed_amount <= 0.00
|
||||
):
|
||||
raise UserError(_("The value of the down payment amount must be positive."))
|
||||
|
||||
amount, name = self._get_advance_details(order)
|
||||
|
||||
invoice_vals = self._prepare_invoice_values(order, name, amount, line)
|
||||
|
||||
if order.fiscal_position_id:
|
||||
invoice_vals["fiscal_position_id"] = order.fiscal_position_id.id
|
||||
invoice = (
|
||||
self.env["account.move"].sudo().create(invoice_vals).with_user(self.env.uid)
|
||||
)
|
||||
invoice.message_post_with_view(
|
||||
"mail.message_origin_link",
|
||||
values={"self": invoice, "origin": order},
|
||||
subtype_id=self.env.ref("mail.mt_note").id,
|
||||
)
|
||||
return invoice
|
||||
|
||||
def _prepare_line(self, order, analytic_tag_ids, tax_ids, amount):
|
||||
context = {"lang": order.partner_id.lang}
|
||||
so_values = {
|
||||
"name": _("Down Payment: %s") % (time.strftime("%m %Y"),),
|
||||
"price_unit": amount,
|
||||
"product_uom_qty": 0.0,
|
||||
"folio_id": order.id,
|
||||
"discount": 0.0,
|
||||
"product_uom": self.product_id.uom_id.id,
|
||||
"product_id": self.product_id.id,
|
||||
"analytic_tag_ids": analytic_tag_ids,
|
||||
"tax_ids": [(6, 0, tax_ids)],
|
||||
"is_downpayment": True,
|
||||
"sequence": order.sale_line_ids
|
||||
and order.sale_line_ids[-1].sequence + 1
|
||||
or 10,
|
||||
}
|
||||
del context
|
||||
return so_values
|
||||
|
||||
def create_invoices(self):
|
||||
folios = self.env["pms.folio"].browse(self._context.get("active_ids", []))
|
||||
|
||||
if self.advance_payment_method == "delivered":
|
||||
lines_to_invoice = self._get_lines_to_invoice(
|
||||
folios=folios,
|
||||
bill_services=self.bill_services,
|
||||
bill_rooms=self.bill_rooms,
|
||||
)
|
||||
folios._create_invoices(
|
||||
final=self.deduct_down_payments,
|
||||
lines_to_invoice=lines_to_invoice,
|
||||
)
|
||||
else:
|
||||
# Create deposit product if necessary
|
||||
if not self.product_id:
|
||||
vals = self._prepare_deposit_product()
|
||||
self.product_id = self.env["product.product"].create(vals)
|
||||
self.env["ir.config_parameter"].sudo().set_param(
|
||||
"sale.default_deposit_product_id", self.product_id.id
|
||||
)
|
||||
|
||||
sale_line_obj = self.env["folio.sale.line"]
|
||||
for order in folios:
|
||||
amount, name = self._get_advance_details(order)
|
||||
|
||||
if self.product_id.invoice_policy != "order":
|
||||
raise UserError(
|
||||
_(
|
||||
"""The product used to invoice a down payment should
|
||||
have an invoice policy set to "Ordered quantities".
|
||||
Please update your deposit product to be able
|
||||
to create a deposit invoice."""
|
||||
)
|
||||
)
|
||||
if self.product_id.type != "service":
|
||||
raise UserError(
|
||||
_(
|
||||
"""The product used to invoice a down payment should
|
||||
be of type 'Service'.
|
||||
Please use another product or update this product."""
|
||||
)
|
||||
)
|
||||
taxes = self.product_id.taxes_id.filtered(
|
||||
lambda r: not order.company_id or r.company_id == order.company_id
|
||||
)
|
||||
tax_ids = order.fiscal_position_id.map_tax(taxes, self.product_id).ids
|
||||
analytic_tag_ids = []
|
||||
for line in order.sale_line_ids:
|
||||
analytic_tag_ids = [
|
||||
(4, analytic_tag.id, None)
|
||||
for analytic_tag in line.analytic_tag_ids
|
||||
]
|
||||
|
||||
line_values = self._prepare_line(
|
||||
order, analytic_tag_ids, tax_ids, amount
|
||||
)
|
||||
line = sale_line_obj.sudo().create(line_values)
|
||||
self._create_invoice(order, line, amount)
|
||||
if self._context.get("open_invoices", False):
|
||||
return folios.action_view_invoice()
|
||||
return {"type": "ir.actions.act_window_close"}
|
||||
|
||||
def _prepare_deposit_product(self):
|
||||
return {
|
||||
"name": "Down payment",
|
||||
"type": "service",
|
||||
"invoice_policy": "order",
|
||||
"property_account_income_id": self.deposit_account_id.id,
|
||||
"taxes_id": [(6, 0, self.deposit_taxes_id.ids)],
|
||||
"company_id": False,
|
||||
}
|
||||
|
||||
@api.model
|
||||
def _get_lines_to_invoice(self, folios, bill_services=True, bill_rooms=True):
|
||||
lines_to_invoice = folios.sale_line_ids
|
||||
if not self.bill_services:
|
||||
lines_to_invoice = lines_to_invoice - lines_to_invoice.filtered(
|
||||
lambda l: l.service_id and not l.service_id.is_board_service
|
||||
)
|
||||
if not self.bill_rooms:
|
||||
lines_to_invoice = lines_to_invoice.filtered(
|
||||
lambda l: l.reservation_id and l.reservation_line_ids
|
||||
)
|
||||
if not lines_to_invoice:
|
||||
raise UserError(_("Nothing to invoice"))
|
||||
return lines_to_invoice
|
||||
127
pms/wizards/folio_make_invoice_advance_views.xml
Normal file
127
pms/wizards/folio_make_invoice_advance_views.xml
Normal file
@@ -0,0 +1,127 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<odoo>
|
||||
<record id="view_folio_advance_payment_inv" model="ir.ui.view">
|
||||
<field name="name">Invoice Orders</field>
|
||||
<field name="model">folio.advance.payment.inv</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Invoice Folio Order">
|
||||
<p class="oe_grey">
|
||||
Invoices will be created in draft so that you can review
|
||||
them before validation.
|
||||
</p>
|
||||
<group>
|
||||
<group>
|
||||
<field
|
||||
name="count"
|
||||
attrs="{'invisible': [('count','=', 1)]}"
|
||||
readonly="True"
|
||||
/>
|
||||
<field
|
||||
name="advance_payment_method"
|
||||
class="oe_inline"
|
||||
widget="radio"
|
||||
attrs="{'invisible': [('count','>',1)]}"
|
||||
/>
|
||||
<field name="has_down_payments" invisible="1" />
|
||||
<label
|
||||
for="deduct_down_payments"
|
||||
string=""
|
||||
attrs="{'invisible': ['|', ('has_down_payments', '=', False), ('advance_payment_method', '!=', 'delivered')]}"
|
||||
/>
|
||||
<div
|
||||
attrs="{'invisible': ['|', ('has_down_payments', '=', False), ('advance_payment_method', '!=', 'delivered')]}"
|
||||
id="down_payment_details"
|
||||
>
|
||||
<field name="deduct_down_payments" nolabel="1" />
|
||||
<label for="deduct_down_payments" />
|
||||
</div>
|
||||
<field
|
||||
name="product_id"
|
||||
context="{'default_invoice_policy': 'order'}"
|
||||
class="oe_inline"
|
||||
invisible="1"
|
||||
/>
|
||||
<label
|
||||
for="amount"
|
||||
attrs="{'invisible': [('advance_payment_method', 'not in', ('fixed','percentage'))]}"
|
||||
/>
|
||||
<div
|
||||
attrs="{'invisible': [('advance_payment_method', 'not in', ('fixed','percentage'))]}"
|
||||
id="payment_method_details"
|
||||
>
|
||||
<field name="currency_id" invisible="1" />
|
||||
<field
|
||||
name="fixed_amount"
|
||||
attrs="{'required': [('advance_payment_method', '=', 'fixed')], 'invisible': [('advance_payment_method', '!=','fixed')]}"
|
||||
class="oe_inline"
|
||||
/>
|
||||
<field
|
||||
name="amount"
|
||||
attrs="{'required': [('advance_payment_method', '=', 'percentage')], 'invisible': [('advance_payment_method', '!=', 'percentage')]}"
|
||||
class="oe_inline"
|
||||
/>
|
||||
<span
|
||||
attrs="{'invisible': [('advance_payment_method', '!=', 'percentage')]}"
|
||||
class="oe_inline"
|
||||
>%</span>
|
||||
</div>
|
||||
<field
|
||||
name="deposit_account_id"
|
||||
options="{'no_create': True}"
|
||||
class="oe_inline"
|
||||
attrs="{'invisible': ['|', ('advance_payment_method', 'not in', ('fixed', 'percentage')), ('product_id', '!=', False)]}"
|
||||
groups="account.group_account_manager"
|
||||
/>
|
||||
<field
|
||||
name="deposit_taxes_id"
|
||||
class="oe_inline"
|
||||
widget="many2many_tags"
|
||||
domain="[('type_tax_use','=','sale')]"
|
||||
attrs="{'invisible': ['|', ('advance_payment_method', 'not in', ('fixed', 'percentage')), ('product_id', '!=', False)]}"
|
||||
/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="bill_services" widget="boolean_toggle" />
|
||||
<field name="bill_rooms" widget="boolean_toggle" />
|
||||
</group>
|
||||
</group>
|
||||
<footer>
|
||||
<button
|
||||
name="create_invoices"
|
||||
id="create_invoice_open"
|
||||
string="Create and View Invoice"
|
||||
type="object"
|
||||
context="{'open_invoices': True}"
|
||||
class="btn-primary"
|
||||
/>
|
||||
<button
|
||||
name="create_invoices"
|
||||
id="create_invoice"
|
||||
string="Create Invoice"
|
||||
type="object"
|
||||
/>
|
||||
<button
|
||||
string="Cancel"
|
||||
class="btn-secondary"
|
||||
special="cancel"
|
||||
/>
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record
|
||||
id="action_view_folio_advance_payment_inv"
|
||||
model="ir.actions.act_window"
|
||||
>
|
||||
<field name="name">Create invoices</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="res_model">folio.advance.payment.inv</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="target">new</field>
|
||||
<!-- TODO: check if we need this -->
|
||||
<field name="binding_model_id" ref="pms.model_pms_folio" />
|
||||
<field name="binding_view_types">list</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
114
pms/wizards/wizard_invoice_filter_days.py
Normal file
114
pms/wizards/wizard_invoice_filter_days.py
Normal file
@@ -0,0 +1,114 @@
|
||||
from odoo import _, api, fields, models
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
|
||||
class InvoiceFilterDays(models.TransientModel):
|
||||
|
||||
_name = "pms.invoice.filter.days"
|
||||
_description = "Filter Days"
|
||||
|
||||
@api.model
|
||||
def default_reservation_lines(self):
|
||||
return (
|
||||
self.env["account.move.line"]
|
||||
.browse(self.env.context.get("active_ids"))
|
||||
.reservation_line_ids
|
||||
)
|
||||
|
||||
@api.model
|
||||
def default_move_lines(self):
|
||||
return self.env["account.move.line"].browse(self.env.context.get("active_ids"))
|
||||
|
||||
@api.model
|
||||
def default_from_date(self):
|
||||
return min(
|
||||
self.env["account.move.line"]
|
||||
.browse(self.env.context.get("active_ids"))
|
||||
.reservation_line_ids.mapped("date")
|
||||
)
|
||||
|
||||
@api.model
|
||||
def default_to_date(self):
|
||||
return max(
|
||||
self.env["account.move.line"]
|
||||
.browse(self.env.context.get("active_ids"))
|
||||
.reservation_line_ids.mapped("date")
|
||||
)
|
||||
|
||||
move_line_ids = fields.Many2many("account.move.line", default=default_move_lines)
|
||||
move_ids = fields.Many2many("account.move", compute="_compute_move_ids")
|
||||
reservation_line_ids = fields.Many2many(
|
||||
"pms.reservation.line", default=default_reservation_lines
|
||||
)
|
||||
from_date = fields.Date("Date From", default=default_from_date)
|
||||
to_date = fields.Date("Date to", default=default_to_date)
|
||||
date_ids = fields.One2many(
|
||||
comodel_name="pms.invoice.filter.days.items",
|
||||
inverse_name="filter_wizard_id",
|
||||
compute="_compute_date_ids",
|
||||
store=True,
|
||||
readonly=False,
|
||||
)
|
||||
|
||||
def do_filter(self):
|
||||
self.ensure_one()
|
||||
invoice_lines = self.move_line_ids
|
||||
for line in invoice_lines:
|
||||
reservation_lines = line.reservation_line_ids.filtered(
|
||||
lambda d: d.date in self.date_ids.filtered("included").mapped("date")
|
||||
)
|
||||
if not reservation_lines:
|
||||
raise UserError(_("You can not remove all lines for invoice"))
|
||||
else:
|
||||
# Write on invoice for syncr business/account
|
||||
line.move_id.write(
|
||||
{
|
||||
"invoice_line_ids": [
|
||||
(
|
||||
1,
|
||||
line.id,
|
||||
{
|
||||
"reservation_line_ids": [
|
||||
(6, False, reservation_lines.ids)
|
||||
],
|
||||
"quantity": len(reservation_lines),
|
||||
},
|
||||
)
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
@api.depends("from_date", "to_date", "reservation_line_ids")
|
||||
def _compute_date_ids(self):
|
||||
self.ensure_one()
|
||||
date_list = [(5, 0, 0)]
|
||||
dates = self.reservation_line_ids.filtered(
|
||||
lambda d: d.date >= self.from_date and d.date <= self.to_date
|
||||
).mapped("date")
|
||||
for date in dates:
|
||||
date_list.append(
|
||||
(
|
||||
0,
|
||||
False,
|
||||
{
|
||||
"date": date,
|
||||
},
|
||||
)
|
||||
)
|
||||
self.date_ids = date_list
|
||||
|
||||
@api.depends("move_line_ids")
|
||||
def _compute_move_ids(self):
|
||||
self.ensure_one()
|
||||
self.move_ids = [(6, 0, self.move_line_ids.mapped("move_id.id"))]
|
||||
|
||||
|
||||
class InvoiceFilterDaysItems(models.TransientModel):
|
||||
|
||||
_name = "pms.invoice.filter.days.items"
|
||||
_description = "Item Days"
|
||||
_rec_name = "date"
|
||||
|
||||
date = fields.Date("Date")
|
||||
included = fields.Boolean("Included", default=True)
|
||||
filter_wizard_id = fields.Many2one(comodel_name="pms.invoice.filter.days")
|
||||
86
pms/wizards/wizard_invoice_filter_days.xml
Normal file
86
pms/wizards/wizard_invoice_filter_days.xml
Normal file
@@ -0,0 +1,86 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<odoo>
|
||||
<data>
|
||||
<record id="pms_invoice_filter_days_form" model="ir.ui.view">
|
||||
<field name="name">pms.invoice.filter.days.form</field>
|
||||
<field name="model">pms.invoice.filter.days</field>
|
||||
<field name="arch" type="xml">
|
||||
<form>
|
||||
<div class="o_row">
|
||||
<field
|
||||
name="from_date"
|
||||
widget="daterange"
|
||||
nolabel="1"
|
||||
class="oe_inline"
|
||||
options="{'related_end_date': 'to_date'}"
|
||||
/>
|
||||
<i
|
||||
class="fa fa-long-arrow-right mx-2"
|
||||
aria-label="Arrow icon"
|
||||
title="Arrow"
|
||||
/>
|
||||
<field
|
||||
name="to_date"
|
||||
widget="daterange"
|
||||
nolabel="1"
|
||||
class="oe_inline"
|
||||
options="{'related_start_date': 'from_date'}"
|
||||
/>
|
||||
</div>
|
||||
<group>
|
||||
<field name="move_ids" invisible="0" />
|
||||
<field
|
||||
name="move_line_ids"
|
||||
string="Invoice Lines"
|
||||
widget="many2many_tags"
|
||||
options="{'no_create':True, 'no_open':True}"
|
||||
domain="[
|
||||
('move_id', 'in', move_ids),
|
||||
('reservation_line_ids', '!=', 0),
|
||||
('exclude_from_invoice_tab', '=', False),
|
||||
('display_type', '=', False)
|
||||
]"
|
||||
/>
|
||||
<field
|
||||
name="date_ids"
|
||||
default_focus="1"
|
||||
string="Dates to invoice"
|
||||
>
|
||||
<tree
|
||||
editable="bottom"
|
||||
create="false"
|
||||
delete="false"
|
||||
decoration-muted="not included"
|
||||
decoration-primary="included"
|
||||
>
|
||||
<field name="date" readonly="1" force_save="1" />
|
||||
<field name="included" />
|
||||
</tree>
|
||||
</field>
|
||||
<field name="reservation_line_ids" invisible="1" />
|
||||
</group>
|
||||
<footer>
|
||||
<button
|
||||
string="Apply"
|
||||
name="do_filter"
|
||||
type="object"
|
||||
class="oe_highlight"
|
||||
/>
|
||||
<button
|
||||
string="Cancel"
|
||||
class="btn btn-secondary"
|
||||
special="cancel"
|
||||
/>
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="pms_invoice_filter_days_action" model="ir.actions.act_window">
|
||||
<field name="name">Filter Days</field>
|
||||
<field name="res_model">pms.invoice.filter.days</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="target">new</field>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
124
pms/wizards/wizard_payment_folio.py
Normal file
124
pms/wizards/wizard_payment_folio.py
Normal file
@@ -0,0 +1,124 @@
|
||||
import logging
|
||||
|
||||
from odoo import _, api, fields, models
|
||||
from odoo.exceptions import ValidationError
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class WizardPaymentFolio(models.TransientModel):
|
||||
|
||||
_name = "wizard.payment.folio"
|
||||
_description = "Payments"
|
||||
|
||||
@api.model
|
||||
def default_folio_id(self):
|
||||
return self.env["pms.folio"].browse(self._context.get("active_id", [])).id
|
||||
|
||||
@api.model
|
||||
def _default_amount(self):
|
||||
folio = self.env["pms.folio"].browse(self._context.get("active_id", []))
|
||||
return folio.pending_amount
|
||||
|
||||
@api.model
|
||||
def _default_partner(self):
|
||||
folio = self.env["pms.folio"].browse(self._context.get("active_id", []))
|
||||
return folio.partner_id.id
|
||||
|
||||
folio_id = fields.Many2one(
|
||||
"pms.folio",
|
||||
string="Folio",
|
||||
required=True,
|
||||
default=default_folio_id,
|
||||
)
|
||||
reservation_ids = fields.Many2many(
|
||||
"pms.reservation",
|
||||
string="Reservations",
|
||||
)
|
||||
service_ids = fields.Many2many(
|
||||
"pms.service",
|
||||
string="Services",
|
||||
)
|
||||
payment_method_id = fields.Many2one(
|
||||
"account.journal",
|
||||
string="Payment Method",
|
||||
required=True,
|
||||
domain="[('id', 'in', allowed_method_ids)]",
|
||||
)
|
||||
allowed_method_ids = fields.Many2many(
|
||||
"account.journal",
|
||||
"allowed_payment_journal_rel",
|
||||
"payment_id",
|
||||
"journal_id",
|
||||
compute="_compute_allowed_method_ids",
|
||||
store="True",
|
||||
)
|
||||
amount = fields.Float("Amount", digits=("Product Price"), default=_default_amount)
|
||||
date = fields.Date("Date", default=fields.Date.context_today, required=True)
|
||||
partner_id = fields.Many2one("res.partner", default=_default_partner)
|
||||
|
||||
@api.depends("folio_id")
|
||||
def _compute_allowed_method_ids(self):
|
||||
self.ensure_one()
|
||||
journal_ids = False
|
||||
if self.folio_id:
|
||||
journal_ids = self.folio_id.pms_property_id._get_payment_methods().ids
|
||||
self.allowed_method_ids = journal_ids
|
||||
|
||||
def button_payment(self):
|
||||
BankStatementLine = self.env["account.bank.statement.line"]
|
||||
line = self._get_statement_line_vals(
|
||||
journal=self.payment_method_id,
|
||||
receivable_account=self.payment_method_id.suspense_account_id,
|
||||
user=self.env.user,
|
||||
amount=self.amount,
|
||||
folios=self.folio_id,
|
||||
partner=self.partner_id,
|
||||
date=self.date,
|
||||
)
|
||||
BankStatementLine.sudo().create(line)
|
||||
|
||||
def _get_statement_line_vals(
|
||||
self,
|
||||
journal,
|
||||
receivable_account,
|
||||
user,
|
||||
amount,
|
||||
folios,
|
||||
reservations=False,
|
||||
services=False,
|
||||
partner=False,
|
||||
date=False,
|
||||
):
|
||||
property_folio_id = folios.mapped("pms_property_id.id")
|
||||
if len(property_folio_id) != 1:
|
||||
raise ValidationError(_("Only can payment by property"))
|
||||
statement = (
|
||||
self.env["account.bank.statement"]
|
||||
.sudo()
|
||||
.search(
|
||||
[
|
||||
("journal_id", "=", journal.id),
|
||||
("property_id", "=", property_folio_id[0]),
|
||||
("state", "=", "open"),
|
||||
]
|
||||
)
|
||||
)
|
||||
reservation_ids = reservations.ids if reservations else []
|
||||
service_ids = services.ids if services else []
|
||||
# TODO: If not open statement, create new, with cash control option
|
||||
if statement:
|
||||
return {
|
||||
"date": date,
|
||||
"amount": amount,
|
||||
"partner_id": partner.id if partner else False,
|
||||
"statement_folio_ids": [(6, 0, folios.ids)],
|
||||
"reservation_ids": [(6, 0, reservation_ids)],
|
||||
"service_ids": [(6, 0, service_ids)],
|
||||
"payment_ref": folios.mapped("name"),
|
||||
"statement_id": statement.id,
|
||||
"journal_id": statement.journal_id.id,
|
||||
"counterpart_account_id": receivable_account.id,
|
||||
}
|
||||
else:
|
||||
return False
|
||||
43
pms/wizards/wizard_payment_folio.xml
Normal file
43
pms/wizards/wizard_payment_folio.xml
Normal file
@@ -0,0 +1,43 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<odoo>
|
||||
<record id="wizard_payment_folio_view_form" model="ir.ui.view">
|
||||
<field name="name">wizard.payment.folio.view.form</field>
|
||||
<field name="model">wizard.payment.folio</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Payment">
|
||||
<group>
|
||||
<group>
|
||||
<field name="allowed_method_ids" invisible="1" />
|
||||
<field name="payment_method_id" widget="radio" />
|
||||
<field name="amount" />
|
||||
</group>
|
||||
<group>
|
||||
<field name="date" />
|
||||
<field name="partner_id" />
|
||||
<field name="folio_id" />
|
||||
</group>
|
||||
</group>
|
||||
<footer>
|
||||
<button
|
||||
type="object"
|
||||
class="btn-primary"
|
||||
id="payment"
|
||||
name="button_payment"
|
||||
string="Pay"
|
||||
/>
|
||||
<button string="Cancel" class="oe_link" special="cancel" />
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_payment_folio" model="ir.actions.act_window">
|
||||
<field name="name">Payment Folio</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="res_model">wizard.payment.folio</field>
|
||||
<field name="view_id" ref="wizard_payment_folio_view_form" />
|
||||
<field name="view_mode">form</field>
|
||||
<field name="target">new</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
Reference in New Issue
Block a user