Merge pull request #87 from hootel/pr_wizard_invoice

Pr wizard invoice
This commit is contained in:
Darío Lodeiros
2019-01-27 16:15:10 +01:00
committed by GitHub
14 changed files with 1040 additions and 616 deletions

View File

@@ -31,3 +31,4 @@ from . import hotel_service_line
from . import hotel_board_service
from . import hotel_board_service_room_type_line
from . import hotel_board_service_line
from . import inherited_account_invoice_line

View File

@@ -11,6 +11,8 @@ from dateutil.relativedelta import relativedelta
from odoo.exceptions import except_orm, UserError, ValidationError
from odoo.tools import (
misc,
float_is_zero,
float_compare,
DEFAULT_SERVER_DATETIME_FORMAT,
DEFAULT_SERVER_DATE_FORMAT)
from odoo import models, fields, api, _
@@ -31,9 +33,57 @@ class HotelFolio(models.Model):
# @api.depends('product_id.invoice_policy', 'order_id.state')
def _compute_qty_delivered_updateable(self):
pass
# @api.depends('state', 'order_line.invoice_status')
@api.depends('state', 'room_lines.invoice_status', 'service_ids.invoice_status')
def _get_invoiced(self):
pass
"""
Compute the invoice status of a Folio. Possible statuses:
- no: if the Folio is not in status 'sale' or 'done', we consider that there is nothing to
invoice. This is also the default value if the conditions of no other status is met.
- to invoice: if any Folio line is 'to invoice', the whole Folio is 'to invoice'
- invoiced: if all Folio lines are invoiced, the Folio is invoiced.
- upselling: if all Folio lines are invoiced or upselling, the status is upselling.
The invoice_ids are obtained thanks to the invoice lines of the Folio lines, and we also search
for possible refunds created directly from existing invoices. This is necessary since such a
refund is not directly linked to the Folio.
"""
for folio in self:
invoice_ids = folio.room_lines.mapped('invoice_line_ids').mapped('invoice_id').filtered(lambda r: r.type in ['out_invoice', 'out_refund'])
invoice_ids |= folio.service_ids.mapped('invoice_line_ids').mapped('invoice_id').filtered(lambda r: r.type in ['out_invoice', 'out_refund'])
# Search for invoices which have been 'cancelled' (filter_refund = 'modify' in
# 'account.invoice.refund')
# use like as origin may contains multiple references (e.g. 'SO01, SO02')
refunds = invoice_ids.search([('origin', 'like', folio.name), ('company_id', '=', folio.company_id.id)]).filtered(lambda r: r.type in ['out_invoice', 'out_refund'])
invoice_ids |= refunds.filtered(lambda r: folio.id in r.folio_ids.ids)
# Search for refunds as well
refund_ids = self.env['account.invoice'].browse()
if invoice_ids:
for inv in invoice_ids:
refund_ids += refund_ids.search([('type', '=', 'out_refund'), ('origin', '=', inv.number), ('origin', '!=', False), ('journal_id', '=', inv.journal_id.id)])
# Ignore the status of the deposit product
deposit_product_id = self.env['sale.advance.payment.inv']._default_product_id()
#~ line_invoice_status = [line.invoice_status for line in order.order_line if line.product_id != deposit_product_id]
#~ TODO: REVIEW INVOICE_STATUS
#~ if folio.state not in ('confirm', 'done'):
#~ invoice_status = 'no'
#~ elif any(invoice_status == 'to invoice' for invoice_status in line_invoice_status):
#~ invoice_status = 'to invoice'
#~ elif all(invoice_status == 'invoiced' for invoice_status in line_invoice_status):
#~ invoice_status = 'invoiced'
#~ elif all(invoice_status in ['invoiced', 'upselling'] for invoice_status in line_invoice_status):
#~ invoice_status = 'upselling'
#~ else:
#~ invoice_status = 'no'
folio.update({
'invoice_count': len(set(invoice_ids.ids + refund_ids.ids)),
'invoice_ids': invoice_ids.ids + refund_ids.ids,
#~ 'invoice_status': invoice_status
})
# @api.depends('state', 'product_uom_qty', 'qty_delivered', 'qty_to_invoice', 'qty_invoiced')
def _compute_invoice_status(self):
pass
@@ -44,9 +94,14 @@ class HotelFolio(models.Model):
def _amount_all(self):
pass
@api.model
def _get_default_team(self):
return self.env['crm.team']._get_default_team_id()
#Main Fields--------------------------------------------------------
name = fields.Char('Folio Number', readonly=True, index=True,
default=lambda self: _('New'))
client_order_ref = fields.Char(string='Customer Reference', copy=False)
partner_id = fields.Many2one('res.partner',
track_visibility='onchange')
@@ -61,8 +116,8 @@ class HotelFolio(models.Model):
help="Hotel services detail provide to "
"customer and it will include in "
"main Invoice.")
company_id = fields.Many2one('res.company', 'Company')
company_id = fields.Many2one('res.company', 'Company', default=lambda self: self.env['res.company']._company_default_get('hotel.folio'))
analytic_account_id = fields.Many2one('account.analytic.account', 'Analytic Account', readonly=True, states={'draft': [('readonly', False)], 'sent': [('readonly', False)]}, help="The analytic account related to a folio.", copy=False)
currency_id = fields.Many2one('res.currency', related='pricelist_id.currency_id',
string='Currency', readonly=True, required=True)
@@ -87,6 +142,7 @@ class HotelFolio(models.Model):
required=True, readonly=True, index=True,
states={'draft': [('readonly', False)], 'sent': [('readonly', False)]},
copy=False, default=fields.Datetime.now)
confirmation_date = fields.Datetime(string='Confirmation Date', readonly=True, index=True, help="Date on which the folio is confirmed.", copy=False)
state = fields.Selection([
('draft', 'Quotation'),
('sent', 'Quotation Sent'),
@@ -111,6 +167,7 @@ class HotelFolio(models.Model):
readonly=True)
return_ids = fields.One2many('payment.return', 'folio_id',
readonly=True)
payment_term_id = fields.Many2one('account.payment.term', string='Payment Terms', oldname='payment_term')
#Amount Fields------------------------------------------------------
pending_amount = fields.Monetary(compute='compute_amount',
@@ -139,8 +196,7 @@ class HotelFolio(models.Model):
compute='_compute_checkin_partner_count')
#Invoice Fields-----------------------------------------------------
hotel_invoice_id = fields.Many2one('account.invoice', 'Invoice')
num_invoices = fields.Integer(compute='_compute_num_invoices')
invoice_count = fields.Integer(compute='_get_invoiced')
invoice_ids = fields.Many2many('account.invoice', string='Invoices',
compute='_get_invoiced', readonly=True, copy=False)
invoice_status = fields.Selection([('upselling', 'Upselling Opportunity'),
@@ -150,12 +206,11 @@ class HotelFolio(models.Model):
string='Invoice Status',
compute='_compute_invoice_status',
store=True, readonly=True, default='no')
#~ partner_invoice_id = fields.Many2one('res.partner',
#~ string='Invoice Address',
#~ readonly=True, required=True,
#~ states={'draft': [('readonly', False)],
#~ 'sent': [('readonly', False)]},
#~ help="Invoice address for current sales order.")
partner_invoice_id = fields.Many2one('res.partner',
string='Invoice Address', required=True,
states={'done': [('readonly', True)]},
help="Invoice address for current sales order.")
fiscal_position_id = fields.Many2one('account.fiscal.position', oldname='fiscal_position', string='Fiscal Position')
#WorkFlow Mail Fields-----------------------------------------------
has_confirmed_reservations_to_send = fields.Boolean(
@@ -173,12 +228,12 @@ class HotelFolio(models.Model):
'Prepaid Warning Days',
help='Margin in days to create a notice if a payment \
advance has not been recorded')
rooms_char = fields.Char('Rooms', compute='_computed_rooms_char')
segmentation_ids = fields.Many2many('res.partner.category',
string='Segmentation')
client_order_ref = fields.Char(string='Customer Reference', copy=False)
note = fields.Text('Terms and conditions')
sequence = fields.Integer(string='Sequence', default=10)
team_id = fields.Many2one('crm.team', 'Sales Channel', change_default=True, default=_get_default_team, oldname='section_id')
@api.depends('room_lines.price_total','service_ids.price_total')
def _amount_all(self):
@@ -197,20 +252,32 @@ class HotelFolio(models.Model):
'amount_total': amount_untaxed + amount_tax,
})
def _computed_rooms_char(self):
for record in self:
record.rooms_char = ', '.join(record.mapped('room_lines.room_id.name'))
@api.multi
def _compute_num_invoices(self):
pass
# for fol in self:
# fol.num_invoices = len(self.mapped('invoice_ids.id'))
# @api.depends('order_line.price_total', 'payment_ids', 'return_ids')
@api.depends('amount_total', 'payment_ids', 'return_ids')
@api.multi
def compute_amount(self):
_logger.info('compute_amount')
acc_pay_obj = self.env['account.payment']
for record in self:
if record.reservation_type in ('staff', 'out'):
vals = {
'pending_amount': 0,
'invoices_paid': 0,
'refund_amount': 0,
}
record.update(vals)
else:
total_inv_refund = 0
payments = acc_pay_obj.search([
('folio_id', '=', record.id)
])
total_paid = sum(pay.amount for pay in payments)
return_lines = self.env['payment.return.line'].search([('move_line_ids','in',payments.mapped('move_line_ids.id')),('return_id.state','=', 'done')])
total_inv_refund = sum(pay_return.amount for pay_return in return_lines)
vals = {
'pending_amount': record.amount_total - total_paid + total_inv_refund,
'invoices_paid': total_paid,
'refund_amount': total_inv_refund,
}
record.update(vals)
@api.multi
def action_pay(self):
@@ -356,7 +423,7 @@ class HotelFolio(models.Model):
if any(f not in vals for f in lfields):
partner = self.env['res.partner'].browse(vals.get('partner_id'))
addr = partner.address_get(['delivery', 'invoice'])
#~ vals['partner_invoice_id'] = vals.setdefault('partner_invoice_id', addr['invoice'])
vals['partner_invoice_id'] = vals.setdefault('partner_invoice_id', addr['invoice'])
vals['pricelist_id'] = vals.setdefault(
'pricelist_id',
partner.property_product_pricelist and partner.property_product_pricelist.id)
@@ -369,23 +436,29 @@ class HotelFolio(models.Model):
"""
Update the following fields when the partner is changed:
- Pricelist
- Payment terms
- Invoice address
- user_id
- Delivery address
"""
if not self.partner_id:
#~ self.update({
#~ 'partner_invoice_id': False,
#~ 'payment_term_id': False,
#~ 'fiscal_position_id': False,
#~ })
self.update({
'partner_invoice_id': False,
'payment_term_id': False,
'fiscal_position_id': False,
})
return
addr = self.partner_id.address_get(['invoice'])
pricelist = self.partner_id.property_product_pricelist and \
self.partner_id.property_product_pricelist.id or \
self.env['ir.default'].sudo().get('res.config.settings', 'default_pricelist_id')
values = {'user_id': self.partner_id.user_id.id or self.env.uid,
'pricelist_id': pricelist
}
values = {
'pricelist_id': pricelist,
'payment_term_id': self.partner_id.property_payment_term_id and self.partner_id.property_payment_term_id.id or False,
'partner_invoice_id': addr['invoice'],
'user_id': self.partner_id.user_id.id or self.env.uid
}
if self.env['ir.config_parameter'].sudo().get_param('sale.use_sale_note') and \
self.env.user.company_id.sale_note:
values['note'] = self.with_context(
@@ -413,31 +486,6 @@ class HotelFolio(models.Model):
else:
return 'normal'
@api.multi
def action_invoice_create(self, grouped=False, states=None):
'''
@param self: object pointer
'''
pass
# if states is None:
# states = ['confirmed', 'done']
# order_ids = [folio.order_id.id for folio in self]
# sale_obj = self.env['sale.order'].browse(order_ids)
# invoice_id = (sale_obj.action_invoice_create(grouped=False,
# states=['confirmed',
# 'done']))
# for line in self:
# values = {'invoiced': True,
# 'state': 'progress' if grouped else 'progress',
# 'hotel_invoice_id': invoice_id
# }
# line.write(values)
# return invoice_id
@api.multi
def advance_invoice(self):
pass
'''
WORKFLOW STATE
'''
@@ -483,7 +531,21 @@ class HotelFolio(models.Model):
@api.multi
def action_confirm(self):
_logger.info('action_confirm')
for folio in self.filtered(lambda folio: folio.partner_id not in folio.message_partner_ids):
folio.message_subscribe([folio.partner_id.id])
self.write({
'state': 'confirm',
'confirmation_date': fields.Datetime.now()
})
#~ if self.env.context.get('send_email'):
#~ self.force_quotation_send()
# create an analytic account if at least an expense product
#~ if any([expense_policy != 'no' for expense_policy in self.order_line.mapped('product_id.expense_policy')]):
#~ if not self.analytic_account_id:
#~ self._create_analytic_account()
return True
"""

View File

@@ -1,17 +1,19 @@
# Copyright 2017-2018 Alexandre Díaz
# Copyright 2017 Dario Lodeiros
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import logging
import time
from datetime import timedelta
from lxml import etree
from odoo.exceptions import UserError, ValidationError
from odoo.tools import (
misc,
float_is_zero,
float_compare,
DEFAULT_SERVER_DATE_FORMAT,
DEFAULT_SERVER_DATETIME_FORMAT)
from odoo import models, fields, api, _
from odoo.addons import decimal_precision as dp
import logging
_logger = logging.getLogger(__name__)
@@ -76,6 +78,28 @@ class HotelReservation(models.Model):
else:
return default_departure_hour
@api.depends('state', 'qty_to_invoice', 'qty_invoiced')
def _compute_invoice_status(self):
"""
Compute the invoice status of a Reservation. Possible statuses:
- no: if the Folio is not in status 'sale' or 'done', we consider that there is nothing to
invoice. This is also hte default value if the conditions of no other status is met.
- to invoice: we refer to the quantity to invoice of the line. Refer to method
`_get_to_invoice_qty()` for more information on how this quantity is calculated.
- invoiced: the quantity invoiced is larger or equal to the quantity ordered.
"""
precision = self.env['decimal.precision'].precision_get('Product Unit of Measure')
for line in self:
if line.state in ('draft'):
line.invoice_status = 'no'
elif not float_is_zero(line.qty_to_invoice, precision_digits=precision):
line.invoice_status = 'to invoice'
elif float_compare(line.qty_invoiced, len(line.reservation_line_ids), precision_digits=precision) >= 0:
line.invoice_status = 'invoiced'
else:
line.invoice_status = 'no'
@api.model
def name_search(self, name='', args=None, operator='ilike', limit=100):
if args is None:
@@ -115,6 +139,7 @@ class HotelReservation(models.Model):
).days
name = fields.Text('Reservation Description', required=True)
sequence = fields.Integer(string='Sequence', default=10)
room_id = fields.Many2one('hotel.room', string='Room')
@@ -134,6 +159,7 @@ class HotelReservation(models.Model):
track_visibility='onchange')
reservation_type = fields.Selection(related='folio_id.reservation_type',
default=lambda *a: 'normal')
invoice_count = fields.Integer(related='folio_id.invoice_count')
board_service_room_id = fields.Many2one('hotel.board.service.room.type',
string='Board Service')
cancelled_reason = fields.Selection([
@@ -166,7 +192,7 @@ class HotelReservation(models.Model):
partner_id = fields.Many2one(related='folio_id.partner_id')
closure_reason_id = fields.Many2one(related='folio_id.closure_reason_id')
company_id = fields.Many2one('res.company', 'Company')
company_id = fields.Many2one(related='folio_id.company_id', string='Company', store=True, readonly=True)
reservation_line_ids = fields.One2many('hotel.reservation.line',
'reservation_id',
readonly=True, required=True,
@@ -230,26 +256,24 @@ class HotelReservation(models.Model):
# order_line = fields.One2many('sale.order.line', 'order_id', string='Order Lines', states={'cancel': [('readonly', True)], 'done': [('readonly', True)]}, copy=True, auto_join=True)
# product_id = fields.Many2one('product.product', related='order_line.product_id', string='Product')
# product_uom = fields.Many2one('product.uom', string='Unit of Measure', required=True)
# product_uom_qty = fields.Float(string='Quantity', digits=dp.get_precision('Product Unit of Measure'), required=True, default=1.0)
currency_id = fields.Many2one('res.currency',
related='pricelist_id.currency_id',
string='Currency', readonly=True, required=True)
# invoice_status = fields.Selection([
# ('upselling', 'Upselling Opportunity'),
# ('invoiced', 'Fully Invoiced'),
# ('to invoice', 'To Invoice'),
# ('no', 'Nothing to Invoice')
# ], string='Invoice Status', compute='_compute_invoice_status', store=True, readonly=True, default='no')
tax_id = fields.Many2many('account.tax',
invoice_status = fields.Selection([
('invoiced', 'Fully Invoiced'),
('to invoice', 'To Invoice'),
('no', 'Nothing to Invoice')
], string='Invoice Status', compute='_compute_invoice_status', store=True, readonly=True, default='no')
tax_ids = fields.Many2many('account.tax',
string='Taxes',
domain=['|', ('active', '=', False), ('active', '=', True)])
# qty_to_invoice = fields.Float(
# string='To Invoice', store=True, readonly=True,
# digits=dp.get_precision('Product Unit of Measure'))
# qty_invoiced = fields.Float(
# compute='_get_invoice_qty', string='Invoiced', store=True, readonly=True,
# digits=dp.get_precision('Product Unit of Measure'))
qty_to_invoice = fields.Float(
compute='_get_to_invoice_qty', string='To Invoice', store=True, readonly=True,
digits=dp.get_precision('Product Unit of Measure'))
qty_invoiced = fields.Float(
compute='_get_invoice_qty', string='Invoiced', store=True, readonly=True,
digits=dp.get_precision('Product Unit of Measure'))
invoice_line_ids = fields.Many2many('account.invoice.line', 'reservation_invoice_rel', 'reservation_id', 'invoice_line_id', string='Invoice Lines', copy=False)
# qty_delivered = fields.Float(string='Delivered', copy=False, digits=dp.get_precision('Product Unit of Measure'), default=0.0)
# qty_delivered_updateable = fields.Boolean(compute='_compute_qty_delivered_updateable', string='Can Edit Delivered', readonly=True, default=True)
price_subtotal = fields.Monetary(string='Subtotal',
@@ -275,7 +299,7 @@ class HotelReservation(models.Model):
# FIXME discount per night
discount = fields.Float(string='Discount (%)', digits=dp.get_precision('Discount'), default=0.0)
# analytic_tag_ids = fields.Many2many('account.analytic.tag', string='Analytic Tags')
analytic_tag_ids = fields.Many2many('account.analytic.tag', string='Analytic Tags')
@api.model
def create(self, vals):
@@ -293,19 +317,12 @@ class HotelReservation(models.Model):
vals.update({'folio_id': folio.id,
'reservation_type': vals.get('reservation_type'),
'channel_type': vals.get('channel_type')})
if 'service_ids' in vals and vals['service_ids'][0][2]:
for service in vals['service_ids']:
service[2]['folio_id'] = folio.id
vals.update({
'last_updated_res': fields.Datetime.now(),
})
if 'board_service_room_id' in vals:
board_services = []
board = self.env['hotel.board.service.room.type'].browse(vals['board_service_room_id'])
for line in board.board_service_line_ids:
board_services.append((0, False, {
'product_id': line.product_id.id,
'is_board_service': True,
'folio_id': vals.get('folio_id'),
}))
vals.update({'service_ids': board_services})
if self.compute_price_out_vals(vals):
days_diff = (
fields.Date.from_string(vals['checkout']) - fields.Date.from_string(vals['checkin'])
@@ -349,18 +366,15 @@ class HotelReservation(models.Model):
board_services = []
board = self.env['hotel.board.service.room.type'].browse(vals['board_service_room_id'])
for line in board.board_service_line_ids:
board_services.append((0, False, {
res = {
'product_id': line.product_id.id,
'is_board_service': True,
'folio_id': record.folio_id.id or vals.get('folio_id')
}))
'folio_id': vals.get('folio_id'),
}
res.update(self.env['hotel.service']._prepare_add_missing_fields(res))
board_services.append((0, False, vals))
# NEED REVIEW: Why I need add manually the old IDs if board service is (0,0,(-)) ¿?¿?¿
record.update({'service_ids': [(6, 0, record.service_ids.ids)] + board_services})
update_services = record.service_ids.filtered(
lambda r: r.is_board_service == True
)
for service in update_services:
service.onchange_product_calc_qty()
if record.compute_price_out_vals(vals):
record.update(record.prepare_reservation_lines(
checkin,
@@ -419,12 +433,12 @@ class HotelReservation(models.Model):
""" Deduce missing required fields from the onchange """
res = {}
onchange_fields = ['room_id', 'reservation_type',
'currency_id', 'name', 'board_service_room_id']
'currency_id', 'name', 'board_service_room_id','service_ids']
if values.get('room_type_id'):
line = self.new(values)
if any(f not in values for f in onchange_fields):
line.onchange_room_id()
line.onchange_compute_reservation_description()
line.onchange_room_type_id()
line.onchange_board_service()
if 'pricelist_id' not in values:
line.onchange_partner_id()
@@ -592,7 +606,10 @@ class HotelReservation(models.Model):
update_old_prices=False))
@api.onchange('checkin', 'checkout', 'room_type_id')
def onchange_compute_reservation_description(self):
def onchange_room_type_id(self):
"""
When change de room_type_id, we calc the line description and tax_ids
"""
if self.room_type_id and self.checkin and self.checkout:
checkin_dt = fields.Date.from_string(self.checkin)
checkout_dt = fields.Date.from_string(self.checkout)
@@ -600,12 +617,13 @@ class HotelReservation(models.Model):
checkout_str = checkout_dt.strftime('%d/%m/%Y')
self.name = self.room_type_id.name + ': ' + checkin_str + ' - '\
+ checkout_str
self._compute_tax_ids()
@api.onchange('checkin', 'checkout')
def onchange_update_service_per_day(self):
services = self.service_ids.filtered(lambda r: r.per_day == True)
for service in services:
service.onchange_product_calc_qty()
service.onchange_product_id()
@api.multi
@api.onchange('checkin', 'checkout', 'room_id')
@@ -637,20 +655,23 @@ class HotelReservation(models.Model):
for line in self.board_service_room_id.board_service_line_ids:
product = line.product_id
if product.per_day:
vals = {
res = {
'product_id': product.id,
'is_board_service': True,
'folio_id': self.folio_id.id,
}
vals.update(self.env['hotel.service'].prepare_service_lines(
line = self.env['hotel.service'].new(res)
res.update(self.env['hotel.service']._prepare_add_missing_fields(res))
res.update(self.env['hotel.service'].prepare_service_lines(
dfrom=self.checkin,
days=self.nights,
per_person=product.per_person,
persons=self.adults,
old_line_days=False))
board_services.append((0, False, vals))
board_services.append((0, False, res))
other_services = self.service_ids.filtered(lambda r: r.is_board_service == False)
self.update({'service_ids': [(6, 0, other_services.ids)] + board_services})
self.update({'service_ids': board_services})
self.service_ids |= other_services
for service in self.service_ids.filtered(lambda r: r.is_board_service == True):
service._compute_tax_ids()
service.price_unit = service._compute_price_unit()
@@ -762,7 +783,7 @@ class HotelReservation(models.Model):
return True
return False
@api.depends('reservation_line_ids', 'reservation_line_ids.discount', 'tax_id')
@api.depends('reservation_line_ids', 'reservation_line_ids.discount', 'tax_ids')
def _compute_amount_reservation(self):
"""
Compute the amounts of the reservation.
@@ -772,7 +793,7 @@ class HotelReservation(models.Model):
if amount_room > 0:
product = record.room_type_id.product_id
price = amount_room * (1 - (record.discount or 0.0) * 0.01)
taxes = record.tax_id.compute_all(price, record.currency_id, 1, product=product)
taxes = record.tax_ids.compute_all(price, record.currency_id, 1, product=product)
record.update({
'price_tax': sum(t.get('amount', 0.0) for t in taxes.get('taxes', [])),
'price_total': taxes['total_included'],
@@ -806,7 +827,7 @@ class HotelReservation(models.Model):
pricelist=pricelist_id,
uom=product.uom_id.id)
line_price = self.env['account.tax']._fix_tax_included_price_company(
product.price, product.taxes_id, self.tax_id, self.company_id)
product.price, product.taxes_id, self.tax_ids, self.company_id)
if old_line:
cmds.append((1, old_line.id, {
'price': line_price
@@ -1012,7 +1033,7 @@ class HotelReservation(models.Model):
'ignore_avail_restrictions': True}).create(vals)
if not reservation_copy:
raise ValidationError(_("Unexpected error copying record. \
Can't split reservation!"))
Can't split reservation!"))
record.write({
'checkout': new_start_date_dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT),
'price_total': tprice[0],
@@ -1124,3 +1145,56 @@ class HotelReservation(models.Model):
@api.multi
def send_cancel_mail(self):
return self.folio_id.send_cancel_mail()
"""
INVOICING PROCESS
"""
@api.multi
def open_invoices_reservation(self):
invoices = self.folio_id.mapped('invoice_ids')
action = self.env.ref('account.action_invoice_tree1').read()[0]
if len(invoices) > 1:
action['domain'] = [('id', 'in', invoices.ids)]
elif len(invoices) == 1:
action['views'] = [(self.env.ref('account.invoice_form').id, 'form')]
action['res_id'] = invoices.ids[0]
else:
action = self.env.ref('hotel.action_view_folio_advance_payment_inv').read()[0]
action['context'] = {'default_reservation_id': self.id,
'default_folio_id': self.folio_id.id}
return action
@api.multi
def _compute_tax_ids(self):
for record in self:
# If company_id is set, always filter taxes by the company
folio = record.folio_id or self.env.context.get('default_folio_id')
product = self.env['product.product'].browse(record.room_type_id.product_id.id)
record.tax_ids = product.taxes_id.filtered(lambda r: not record.company_id or r.company_id == folio.company_id)
@api.depends('qty_invoiced', 'nights', 'folio_id.state')
def _get_to_invoice_qty(self):
"""
Compute the quantity to invoice. If the invoice policy is order, the quantity to invoice is
calculated from the ordered quantity. Otherwise, the quantity delivered is used.
"""
for line in self:
if line.folio_id.state not in ['draft']:
line.qty_to_invoice = len(line.reservation_line_ids) - line.qty_invoiced
else:
line.qty_to_invoice = 0
@api.depends('invoice_line_ids.invoice_id.state', 'invoice_line_ids.quantity')
def _get_invoice_qty(self):
"""
Compute the quantity invoiced. If case of a refund, the quantity invoiced is decreased. We
must check day per day and sum or decreased on 1 unit per invoice_line
"""
for line in self:
qty_invoiced = 0.0
for day in line.reservation_line_ids:
invoice_lines = day.invoice_line_ids.filtered(lambda r: r.invoice_id.state != 'cancel')
qty_invoiced += len(invoice_lines.filtered(lambda r: r.invoice_id.type == 'out_invoice')) - \
len(invoice_lines.filtered(lambda r: r.invoice_id.type == 'out_refund'))
line.qty_invoiced = qty_invoiced

View File

@@ -4,11 +4,21 @@
from odoo import models, fields, api, _
from odoo.addons import decimal_precision as dp
from odoo.exceptions import ValidationError
from datetime import date
class HotelReservationLine(models.Model):
_name = "hotel.reservation.line"
_order = "date"
@api.multi
def name_get(self):
result = []
for res in self:
date = fields.Date.from_string(res.date)
name = u'%s/%s' % (date.day, date.month)
result.append((res.id, name))
return result
reservation_id = fields.Many2one('hotel.reservation', string='Reservation',
ondelete='cascade', required=True,
copy=False)
@@ -17,6 +27,11 @@ class HotelReservationLine(models.Model):
discount = fields.Float(
string='Discount (%)',
digits=dp.get_precision('Discount'), default=0.0)
invoice_line_ids = fields.Many2many(
'account.invoice.line',
'reservation_line_invoice_rel',
'reservation_line_id', 'invoice_line_id',
string='Invoice Lines', readonly=True, copy=False)
@api.constrains('date')
def constrains_duplicated_date(self):

View File

@@ -2,7 +2,10 @@
# Copyright 2017 Dario Lodeiros
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import models, fields, api, _
from odoo.tools import DEFAULT_SERVER_DATE_FORMAT
from odoo.tools import (
float_is_zero,
float_compare,
DEFAULT_SERVER_DATE_FORMAT)
from datetime import timedelta
from odoo.exceptions import ValidationError
from odoo.addons import decimal_precision as dp
@@ -42,11 +45,78 @@ class HotelService(models.Model):
ids = [item[1] for item in self.env.context['room_lines']]
return self.env['hotel.reservation'].browse([
(ids)], limit=1)
elif self.env.context.get('default_ser_room_line'):
return self.env.context.get('default_ser_room_line')
return False
name = fields.Char('Service description')
@api.model
def _default_folio_id(self):
if 'folio_id' in self._context:
return self._context['folio_id']
return False
@api.depends('qty_invoiced', 'product_qty', 'folio_id.state')
def _get_to_invoice_qty(self):
"""
Compute the quantity to invoice. If the invoice policy is order, the quantity to invoice is
calculated from the ordered quantity. Otherwise, the quantity delivered is used.
"""
for line in self:
if line.folio_id.state not in ['draft']:
line.qty_to_invoice = line.product_qty - line.qty_invoiced
else:
line.qty_to_invoice = 0
@api.depends('invoice_line_ids.invoice_id.state', 'invoice_line_ids.quantity')
def _get_invoice_qty(self):
"""
Compute the quantity invoiced. If case of a refund, the quantity invoiced is decreased. Note
that this is the case only if the refund is generated from the SO and that is intentional: if
a refund made would automatically decrease the invoiced quantity, then there is a risk of reinvoicing
it automatically, which may not be wanted at all. That's why the refund has to be created from the SO
"""
for line in self:
qty_invoiced = 0.0
for invoice_line in line.invoice_line_ids:
if invoice_line.invoice_id.state != 'cancel':
if invoice_line.invoice_id.type == 'out_invoice':
qty_invoiced += invoice_line.uom_id._compute_quantity(invoice_line.quantity, line.product_id.uom_id)
elif invoice_line.invoice_id.type == 'out_refund':
qty_invoiced -= invoice_line.uom_id._compute_quantity(invoice_line.quantity, line.product_id.uom_id)
line.qty_invoiced = qty_invoiced
@api.depends('product_qty', 'qty_to_invoice', 'qty_invoiced')
def _compute_invoice_status(self):
"""
Compute the invoice status of a SO line. Possible statuses:
- no: if the SO is not in status 'sale' or 'done', we consider that there is nothing to
invoice. This is also hte default value if the conditions of no other status is met.
- to invoice: we refer to the quantity to invoice of the line. Refer to method
`_get_to_invoice_qty()` for more information on how this quantity is calculated.
- upselling: this is possible only for a product invoiced on ordered quantities for which
we delivered more than expected. The could arise if, for example, a project took more
time than expected but we decided not to invoice the extra cost to the client. This
occurs onyl in state 'sale', so that when a SO is set to done, the upselling opportunity
is removed from the list.
- invoiced: the quantity invoiced is larger or equal to the quantity ordered.
"""
precision = self.env['decimal.precision'].precision_get('Product Unit of Measure')
for line in self:
if line.folio_id.state in ('draft'):
line.invoice_status = 'no'
elif not float_is_zero(line.qty_to_invoice, precision_digits=precision):
line.invoice_status = 'to invoice'
elif float_compare(line.qty_invoiced, line.product_qty, precision_digits=precision) >= 0:
line.invoice_status = 'invoiced'
else:
line.invoice_status = 'no'
name = fields.Char('Service description', required=True)
sequence = fields.Integer(string='Sequence', default=10)
product_id = fields.Many2one('product.product', 'Service', required=True)
folio_id = fields.Many2one('hotel.folio', 'Folio', ondelete='cascade')
folio_id = fields.Many2one('hotel.folio', 'Folio',
ondelete='cascade',
default=_default_folio_id)
ser_room_line = fields.Many2one('hotel.reservation', 'Room',
default=_default_ser_room_line)
per_day = fields.Boolean(related='product_id.per_day')
@@ -57,6 +127,11 @@ class HotelService(models.Model):
# Non-stored related field to allow portal user to see the image of the product he has ordered
product_image = fields.Binary('Product Image', related="product_id.image", store=False)
company_id = fields.Many2one(related='folio_id.company_id', string='Company', store=True, readonly=True)
invoice_status = fields.Selection([
('invoiced', 'Fully Invoiced'),
('to invoice', 'To Invoice'),
('no', 'Nothing to Invoice')
], string='Invoice Status', compute='_compute_invoice_status', store=True, readonly=True, default='no')
channel_type = fields.Selection([
('door', 'Door'),
('mail', 'Mail'),
@@ -67,6 +142,14 @@ class HotelService(models.Model):
tax_ids = fields.Many2many('account.tax', string='Taxes', domain=['|', ('active', '=', False), ('active', '=', True)])
discount = fields.Float(string='Discount (%)', digits=dp.get_precision('Discount'), default=0.0)
currency_id = fields.Many2one(related='folio_id.currency_id', store=True, string='Currency', readonly=True)
invoice_line_ids = fields.Many2many('account.invoice.line', 'service_line_invoice_rel', 'service_id', 'invoice_line_id', string='Invoice Lines', copy=False)
analytic_tag_ids = fields.Many2many('account.analytic.tag', string='Analytic Tags')
qty_to_invoice = fields.Float(
compute='_get_to_invoice_qty', string='To Invoice', store=True, readonly=True,
digits=dp.get_precision('Product Unit of Measure'))
qty_invoiced = fields.Float(
compute='_get_invoice_qty', string='Invoiced', store=True, readonly=True,
digits=dp.get_precision('Product Unit of Measure'))
price_subtotal = fields.Monetary(string='Subtotal',
readonly=True,
store=True,
@@ -126,11 +209,11 @@ class HotelService(models.Model):
def _prepare_add_missing_fields(self, values):
""" Deduce missing required fields from the onchange """
res = {}
onchange_fields = ['price_unit','tax_ids']
onchange_fields = ['price_unit','tax_ids','name']
if values.get('product_id'):
line = self.new(values)
if any(f not in values for f in onchange_fields):
line.onchange_product_calc_qty()
line.onchange_product_id()
for field in onchange_fields:
if field not in values:
res[field] = line._fields[field].convert_to_write(line[field], line)
@@ -154,24 +237,28 @@ class HotelService(models.Model):
def _compute_tax_ids(self):
for record in self:
# If company_id is set, always filter taxes by the company
folio = record.folio_id or self.env.context.get('default_folio_id')
record.tax_ids = record.product_id.taxes_id.filtered(lambda r: not record.company_id or r.company_id == folio.company_id)
folio = record.folio_id or self.env['hotel.folio'].browse(self.env.context.get('default_folio_id'))
reservation = record.ser_room_line or self.env.context.get('ser_room_line')
origin = folio if folio else reservation
record.tax_ids = record.product_id.taxes_id.filtered(lambda r: not record.company_id or r.company_id == origin.company_id)
@api.multi
def _get_display_price(self, product):
folio = self.folio_id or self.env.context.get('default_folio_id')
if folio.pricelist_id.discount_policy == 'with_discount':
return product.with_context(pricelist=folio.pricelist_id.id).price
product_context = dict(self.env.context, partner_id=folio.partner_id.id, date=folio.date_order, uom=self.product_id.uom_id.id)
final_price, rule_id = folio.pricelist_id.with_context(product_context).get_product_price_rule(self.product_id, self.product_qty or 1.0, folio.partner_id)
base_price, currency_id = self.with_context(product_context)._get_real_price_currency(product, rule_id, self.product_qty, product_id.uom_id, folio.pricelist_id.id)
if currency_id != folio.pricelist_id.currency_id.id:
base_price = self.env['res.currency'].browse(currency_id).with_context(product_context).compute(base_price, folio.pricelist_id.currency_id)
reservation = self.ser_room_line or self.env.context.get('ser_room_line')
origin = folio if folio else reservation
if origin.pricelist_id.discount_policy == 'with_discount':
return product.with_context(pricelist=origin.pricelist_id.id).price
product_context = dict(self.env.context, partner_id=origin.partner_id.id, date=folio.date_order if folio else fields.Date.today(), uom=self.product_id.uom_id.id)
final_price, rule_id = origin.pricelist_id.with_context(product_context).get_product_price_rule(self.product_id, self.product_qty or 1.0, origin.partner_id)
base_price, currency_id = self.with_context(product_context)._get_real_price_currency(product, rule_id, self.product_qty, product_id.uom_id, origin.pricelist_id.id)
if currency_id != origin.pricelist_id.currency_id.id:
base_price = self.env['res.currency'].browse(currency_id).with_context(product_context).compute(base_price, origin.pricelist_id.currency_id)
# negative discounts (= surcharge) are included in the display price
return max(base_price, final_price)
@api.onchange('product_id')
def onchange_product_calc_qty(self):
def onchange_product_id(self):
"""
Compute the default quantity according to the
configuration of the selected product, in per_day product configuration,
@@ -195,6 +282,30 @@ class HotelService(models.Model):
for day in record.service_line_ids:
day.no_free_resources()
"""
Description and warnings
"""
product = self.product_id.with_context(
lang=self.folio_id.partner_id.lang,
partner=self.folio_id.partner_id.id
)
title = False
message = False
warning = {}
if product.sale_line_warn != 'no-message':
title = _("Warning for %s") % product.name
message = product.sale_line_warn_msg
warning['title'] = title
warning['message'] = message
result = {'warning': warning}
if product.sale_line_warn == 'block':
self.product_id = False
return result
name = product.name_get()[0][1]
if product.description_sale:
name += '\n' + product.description_sale
vals['name'] = name
"""
Compute tax and price unit
"""
self._compute_tax_ids()
@@ -206,9 +317,10 @@ class HotelService(models.Model):
self.ensure_one()
folio = self.folio_id or self.env.context.get('default_folio_id')
reservation = self.ser_room_line or self.env.context.get('ser_room_line')
if folio or reservation:
partner = folio.partner_id if folio else reservation.partner_id
pricelist = folio.pricelist_id if folio else reservation.pricelist_id
origin = folio if folio else reservation
if origin:
partner = origin.partner_id
pricelist = origin.pricelist_id
if reservation and self.is_board_service:
board_room_type = reservation.board_service_room_id
if board_room_type.price_type == 'fixed':
@@ -224,12 +336,12 @@ class HotelService(models.Model):
lang=partner.lang,
partner=partner.id,
quantity=self.product_qty,
date=folio.date_order or fields.Date.today(),
date=folio.date_order if folio else fields.Date.today(),
pricelist=pricelist.id,
uom=self.product_id.uom_id.id,
fiscal_position=False
)
return self.env['account.tax']._fix_tax_included_price_company(self._get_display_price(product), product.taxes_id, self.tax_ids, folio.company_id)
return self.env['account.tax']._fix_tax_included_price_company(self._get_display_price(product), product.taxes_id, self.tax_ids, origin.company_id)
@api.model
def prepare_service_lines(self, **kwargs):
@@ -263,7 +375,7 @@ class HotelService(models.Model):
Compute the amounts of the service line.
"""
for record in self:
folio = record.folio_id or self.env.context.get('default_folio_id')
folio = record.folio_id or self.env['hotel.folio'].browse(self.env.context.get('default_folio_id'))
reservation = record.ser_room_line or self.env.context.get('ser_room_line')
currency = folio.currency_id if folio else reservation.currency_id
product = record.product_id

View File

@@ -33,7 +33,6 @@ class AccountInvoice(models.Model):
'domain': [('id', 'in', payment_ids)],
}
dif_customer_payment = fields.Boolean(compute='_compute_dif_customer_payment')
from_folio = fields.Boolean(compute='_compute_dif_customer_payment')
sale_ids = fields.Many2many(
'sale.order', 'sale_order_invoice_rel', 'invoice_id',
@@ -45,16 +44,11 @@ class AccountInvoice(models.Model):
@api.multi
def _compute_dif_customer_payment(self):
for inv in self:
sales = inv.mapped('invoice_line_ids.sale_line_ids.order_id')
folios = self.env['hotel.folio'].search([('order_id.id','in',sales.ids)])
folios = inv.mapped('invoice_line_ids.reservation_ids.folio_id')
folios |= inv.mapped('invoice_line_ids.service_ids.folio_id')
if folios:
inv.from_folio = True
inv.folio_ids = [(6, 0, folios.ids)]
payments_obj = self.env['account.payment']
payments = payments_obj.search([('folio_id','in',folios.ids)])
for pay in payments:
if pay.partner_id != inv.partner_id:
inv.dif_customer_payment = True
@api.multi
def action_invoice_open(self):

View File

@@ -0,0 +1,24 @@
# Copyright 2017 Alexandre Díaz
# Copyright 2017 Dario Lodeiros
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
from odoo import api, fields, models, _
class AccountInvoiceLine(models.Model):
_inherit = 'account.invoice.line'
reservation_ids = fields.Many2many(
'hotel.reservation',
'reservation_invoice_rel',
'invoice_line_id', 'reservation_id',
string='Reservations', readonly=True, copy=False)
service_ids = fields.Many2many(
'hotel.service',
'service_line_invoice_rel',
'invoice_line_id', 'service_id',
string='Services', readonly=True, copy=False)
reservation_line_ids = fields.Many2many(
'hotel.reservation.line',
'reservation_line_invoice_rel',
'invoice_line_id', 'reservation_line_id',
string='Reservation Lines', readonly=True, copy=False)

View File

@@ -20,9 +20,9 @@
attrs="{'invisible': [('has_cancelled_reservations_to_send', '=', False)]}" class="oe_highlight"/> -->
<!-- <button name="send_exit_mail" type="object" string="Send Exit Email"
attrs="{'invisible': [('has_checkout_to_send', '=', False)]}" class="oe_highlight"/> -->
<!-- <button name="%(hotel.action_view_folio_advance_payment_inv)d"
<button name="%(hotel.action_view_folio_advance_payment_inv)d"
string="Create Invoice" type="action" class="btn-primary" states="sale"
attrs="{'invisible': [('invoice_status', '!=', 'to invoice')]}"/> -->
attrs="{'invisible': [('state', '!=', 'confirm')]}"/>
<!-- <button name="action_cancel_draft" states="cancel,sale" string="Set to Draft"
type="object" icon="fa-undo" class="oe_highlight" /> -->
<button name="action_cancel" string="Cancel Folio" states="sale"
@@ -66,7 +66,7 @@
</button>
<field name="currency_id" invisible="1"/>
<!-- <button type="object" class="oe_stat_button"
<button type="object" class="oe_stat_button"
id="invoices_smart_button"
icon="fa-thumbs-up"
name="action_payments"
@@ -78,9 +78,9 @@
</span>
<span class="o_stat_text">Paid out</span>
</div>
</button> -->
</button>
<!-- <button type="object" class="oe_stat_button"
<button type="object" class="oe_stat_button"
id="payment_smart_button"
icon="fa-money"
name="action_pay"
@@ -92,9 +92,9 @@
</span>
<span class="o_stat_text">Pending Payment</span>
</div>
</button> -->
</button>
<!-- <button type="object" class="oe_stat_button"
<button type="object" class="oe_stat_button"
id="refunds_smart_button"
icon="fa-undo"
name="action_return_payments"
@@ -106,19 +106,19 @@
</span>
<span class="o_stat_text">Refunds</span>
</div>
</button> -->
</button>
<!-- <button type="object" class="oe_stat_button" id="invoice_button"
<button type="object" class="oe_stat_button" id="invoice_button"
icon="fa-pencil-square-o" name="open_invoices_folio"
attrs="{'invisible': [('num_invoices', '=', 0)]}"
attrs="{'invisible': [('invoice_count', '=', 0)]}"
context="{'default_folio_id': active_id}">
<div class="o_form_field o_stat_info">
<span class="o_stat_value">
<field name="num_invoices"/>
<field name="invoice_count"/>
</span>
<span class="o_stat_text">Invoices</span>
</div>
</button> -->
</button>
</div>
<!-- <field name="image" widget="image" class="oe_avatar" options="{&quot;preview_image&quot;: &quot;image_medium&quot;, &quot;size&quot;: [90, 90]}"/> -->
@@ -132,10 +132,12 @@
<field name="email" placeholder="email"/>
<field name="mobile" placeholder="mobile"/>
<field name="phone" />
<field name="partner_invoice_id" />
<field name="cancelled_reason" attrs="{'invisible':[('state','not in',('cancel'))]}"/>
</group>
<group>
<field name="pricelist_id" domain="[('type','=','sale')]" />
<field name="company_id" />
<field name="company_id" options="{'no_create': True}" groups="base.group_multi_company"/>
<field name="reservation_type" attrs="{'readonly':[('state','not in',('draft'))]}"/>
<field name="channel_type" attrs="{'required':[('reservation_type','=','normal')]}"/>
@@ -150,315 +152,26 @@
</group>
</group>
<group invisible="1">
<!-- <field name="partner_shipping_id" invisible="1" domain="[('parent_id','=',partner_id)]" /> -->
<!-- <field name="warehouse_id" string="Branch" invisible="1"/> -->
<field name="invoice_ids" invisible="1"/>
<field name="invoice_status" invisible="1" />
<!-- <field name="hotel_invoice_id" states='progress,done,cancel'
readonly="1" invisible="1" /> -->
</group>
<notebook colspan="4" col="1">
<page string="Lines">
<field name="room_lines" colspan="4" string="Room Line"
nolabel="1" context="{'room_lines':room_lines,'folio_id': id}">
<tree string="Rooms"
colors="red:state == 'cancelled'"
default_order="id"
delete="false"
options="{'no_open': True}">
<button type="object" class="oe_stat_button"
id="splitted" icon="fa fa-1x fa-chain-broken"
name="open_master"
attrs="{'invisible':[('splitted','=', False)]}"
/>
<field name="folio_id" invisible="1"/>
<field name="state" />
<button type="action" class="oe_stat_button"
id="checkin_partner_smart_button" icon="fa fa-1x fa-user-plus"
name="%(launch_checkin_wizard_add)d"
context="{'partner_id': partner_id,'enter_date': checkin,
'exit_date': checkout,'reservation_id': id, 'hidden_checkin_partner': True, 'edit_checkin_partner': True }"
attrs="{'invisible':['|','|', ('state','not in',('confirm','booking')),
('checkin_partner_pending_count','=', 0),('parent_reservation','!=',False)]}"
/>
<field name="partner_id"/>
<field name="splitted" invisible="1" />
<field name="parent_reservation" invisible="1" />
<!-- <field name="room_type_id" string="Reserved Room Type"/> -->
<field name="room_type_id" string="Reserved Room Type"/>
<field name="nights" />
<field name="adults" string="Persons"/>
<field name="checkin" widget="date"/>
<field name="checkout" widget="date"/>
<field name="checkin_partner_ids" invisible ="1"/>
<field name="to_assign" invisible="1"/>
<field name="checkin_partner_pending_count" invisible="1"/>
<!-- <field name="qty_delivered" invisible="1"/> -->
<!-- attrs="{'readonly': [('qty_delivered_updateable', '=', False)]}"/> -->
<!-- <field name="qty_invoiced" invisible="1"/> -->
<!-- <field name="qty_to_invoice" invisible="1"/> -->
<!-- <field name="product_uom"
attrs="{'readonly': [('state', 'in', ('sale','done', 'cancel'))]}"
context="{'company_id': parent.company_id}"
groups="product.group_uom" options='{"no_open": True}'
invisible="1"/> -->
<!-- <field name="analytic_tag_ids" groups="analytic.group_analytic_accounting" widget="many2many_tags" invisible="1"/> -->
<!-- <field name="tax_id" widget="many2many_tags" domain="[('type_tax_use','=','sale'),('company_id','=',parent.company_id)]"
invisible="1"/> -->
<!-- <field name="qty_delivered_updateable" invisible="1"/> -->
<field name="state" invisible="1"/>
<!-- <field name="invoice_status" invisible="1"/> -->
<!-- <field name="customer_lead" invisible="1"/> -->
<!-- <field name="currency_id" invisible="1"/> -->
<!-- <field name="price_unit" invisible="1"/> -->
<!-- <field name="amount_room" string="Reservation Price" readonly="1"/>-->
<!-- <field name="amount_discount" string="Final Price"/> -->
<button type="object" class="oe_stat_button"
id="go_reservation" icon="fa fa-2x fa-bars"
name="open_reservation_form"/>
</tree>
<form string="Reservation" >
<sheet>
<header>
<field name="splitted" invisible="1" />
<field name="shared_folio" invisible="1"/>
<field name="folio_id" invisible="1" />
<field name="partner_id" invisible="1"/>
<field name="parent_reservation" invisible="True" />
<!-- <field name="has_confirmed_reservations_to_send" invisible="1" /> -->
<!-- <field name="has_cancelled_reservations_to_send" invisible="1" /> -->
<!-- <field name="has_checkout_to_send" invisible="1" /> -->
<!--<button name="send_reservation_mail" type="object" string="Send Reservation Email" states="confirm" class="oe_highlight"/>-->
<button name="confirm" string="Confirm" class="oe_highlight"
type="object"
attrs="{'invisible':[('state','not in',('draft','cancelled'))]}"
/>
<button name="action_cancel" string="Cancel Reservation"
class="oe_highlight" type="object"
attrs="{'invisible':['|',('folio_id', '=', False),('state','not in',('confirm','booking'))]}"
/>
<button name="action_reservation_checkout" string="Done"
states="booking" class="oe_highlight"
type="object"
/>
<button name="draft" string="Set to Draft"
states="cancelled" class="oe_highlight"
type="object"
/>
<button name="%(action_hotel_split_reservation)d" string="Split"
type="action" class="oe_highlight"
icon="fa-cut"
attrs="{'invisible':['|',('folio_id', '=', False),('state','not in',('draft','confirm','booking'))]}"
/>
<button name="unify" string="Unify"
type="object" class="oe_highlight"
icon="fa-compress"
attrs="{'invisible':[('splitted', '=', False)]}"
/>
<label for="preconfirm"
string="Autoconfirm"
attrs="{'invisible':[('folio_id', '!=', False)]}"
/>
<span name="preconfirm" attrs="{'invisible':[('folio_id', '!=', False)]}">
<field name="preconfirm" />
</span>
<!--button name="open_master" string="Open Master" type="object" class="oe_highlight" icon="fa-file" attrs="{'invisible':['|',['parent_reservation', '=', False]]}" /-->
<field name="state" widget="statusbar"/>
</header>
<span class="label label-danger" attrs="{'invisible': [('state', 'not in', ('cancelled'))]}">Cancelled Reservation!</span>
<span class="label label-warning" attrs="{'invisible': [('overbooking', '=', False)]}">OverBooking!</span>
<h1>
<field name="room_id" select="1"
nolabel="1" options="{'no_create': True,'no_open': True}" placeholder="Room"
style="margin-right: 30px;" required='1'/>
<field name="partner_id" default_focus="1"
placeholder="Lastname, Firstname"
attrs="{'readonly':[('folio_id','!=',False)]}"
required="1"/>
<span class="fa fa-user" style="margin-left:20px;"/>
</h1>
<h3>
<!-- <field name="room_id" select="1" domain="[('isroom','=',True)]"
nolabel="1" options="{'no_create': True,'no_open': True}" placeholder="Room"
style="margin-right: 30px;"/> -->
From <span class="fa fa-sign-in" style="margin: 5px;"/><field name="checkin" style="margin-right: 10px;"/> to
<span class="fa fa-sign-out" style="margin-right: 5px;"/><field name="checkout" />
</h3>
<group col="6">
<group string="General Info" name="contact_details" >
<field name="email" placeholder="email" widget="email" />
<field name="mobile" placeholder="mobile" widget="phone" />
<field name="phone" placeholder="phone" widget="phone" />
<field name="pricelist_id"/>
<field name="partner_internal_comment" string="Partner Note"/>
<field name="cancelled_reason" attrs="{'invisible':[('state','not in',('cancelled'))]}"/>
</group>
<group colspan="4" string="Reservation Details" name="reservation_details">
<field name="arrival_hour"/>
<field name="departure_hour"/>
<field name="nights" invisible="1"/>
<field name="board_service_room_id" domain="[
('hotel_room_type_id', '=', room_type_id),
('pricelist_id', 'in', [pricelist_id, False])]" />
<field name="name"/>
<field name="adults"/>
<field name="children"/>
<field name="room_type_id" on_change="1" options="{'no_create': True,'no_open': True}"
attrs="{'readonly':[('state','not in',('draft'))]}"/>
<field name="channel_type" attrs="{'required':[('reservation_type','not in',('staff','out'))]}"/>
</group>
<group class="oe_subtotal_footer" style="margin-right: 20px; !important" colspan="2" name="reservation_total" string="Amounts">
<!-- <field name="amount_room" widget="monetary" options="{'currency_field': 'currency_id'}"/> -->
<!-- <field name="discount" string="Room Discount" attrs="{'invisible': [('discount_type','=','fixed')]}" /> -->
<!-- <field name="discount_fixed" string="Room Discount" attrs="{'invisible': [('discount_type','=','percent')]}" />
<field name="discount_type" widget="radio" options="{'horizontal': true}" nolabel="1" colspan="2"/>
<div class="oe_subtotal_footer_separator oe_inline o_td_label">
<label for="amount_discount" />
<button name="%(action_hotel_massive_price_change_reservation_days)d" string="Massive Day Prices"
type="action" class="oe_edit_only oe_link" icon="fa-bolt"/>
</div>-->
<!-- <field name="amount_discount" nolabel="1" widget='monetary' class="oe_subtotal_footer_separator" options="{'currency_field': 'currency_id'}"/> -->
<!--<div class="oe_subtotal_footer_separator oe_inline o_td_label">
<label for="amount_reservation_services" />
</div>-->
<!-- <field name="amount_reservation_services" nolabel="1" widget='monetary' class="oe_subtotal_footer_separator" options="{'currency_field': 'currency_id'}"/> -->
<field name="price_total" invisible="1"/>
<!-- <field name="qty_delivered_updateable" invisible="1"/> -->
<field name="state" invisible="1"/>
<!-- <field name="invoice_status" invisible="1"/> -->
<!-- <field name="customer_lead" invisible="1"/> -->
<!-- <field name="currency_id" invisible="1"/> -->
<field name="price_subtotal" widget="monetary" invisible="1"/>
<!-- <field name="price_unit" invisible="1"/> -->
</group>
</group>
<group invisible="1">
<field name="company_id" options="{'no_create': True}" groups="base.group_multi_company"/>
<!-- <field name="check_rooms" invisible="1"/> -->
<field name="checkin_partner_pending_count" invisible="1"/>
</group>
<notebook>
<page name="days" string="Service and Days">
<group col="9">
<group colspan="6" string="Reservation Services" name="reservation_services" attrs="{'invisible': [('folio_id','=',False)]}">
<field name="service_ids"
context="{'default_ser_room_line': active_id, 'default_folio_id': folio_id}"
nolabel="1" style="padding-right:10px !important;">
<tree string="Services" editable="bottom">
<field name="folio_id" invisible="1"/>
<!-- <field name="layout_category_id" groups="sale.group_sale_layout"/> -->
<field name="name"/>
<field name="ser_room_line" invisible="1" />
<!-- <field name="product_uom_qty"
string="Ordered Qty"
context="{'partner_id':parent.partner_id, 'quantity':product_uom_qty, 'uom':product_uom, 'company_id': parent.company_id}"
/> -->
<!-- <field name="qty_delivered" invisible="1"/> -->
<!-- attrs="{'readonly': [('qty_delivered_updateable', '=', False)]}" -->
<!-- <field name="qty_invoiced" invisible="1"/> -->
<!-- <field name="qty_to_invoice" invisible="1"/> -->
<!-- <field name="product_uom"
attrs="{'readonly': [('state', 'in', ('sale','done', 'cancel'))]}"
context="{'company_id': parent.company_id}"
groups="product.group_uom" options='{"no_open": True}'/> -->
<!-- <field name="analytic_tag_ids" groups="analytic.group_analytic_accounting" widget="many2many_tags"/> -->
<!-- <field name="price_unit"
attrs="{'readonly': [('qty_invoiced', '&gt;', 0)]}"/> -->
<!-- <field name="tax_id" widget="many2many_tags" invisible="1"/> -->
<!-- <field name="discount" groups="sale.group_discount_per_so_line"/> -->
<!-- <field name="price_subtotal" widget="monetary" invisible="1"/> -->
<!-- <field name="price_total" widget="monetary"/> -->
<!-- <field name="qty_delivered_updateable" invisible="1"/> -->
<!-- <field name="state" invisible="1"/> -->
<!-- <field name="invoice_status" invisible="1"/> -->
<!-- <field name="customer_lead" invisible="1"/> -->
<!-- <field name="currency_id" invisible="1"/> -->
</tree>
</field>
</group>
<group colspan="3" string="Days" name="days">
<field name="reservation_line_ids" nolabel="1">
<tree create="false" delete="false" editable="bottom">
<field name="date" readonly="True" />
<field name="price" />
<field name="discount" />
</tree>
</field>
</group>
</group>
</page>
<page name="others" string="Others">
<group>
<group>
<field name="segmentation_ids" widget="many2many_tags" placeholder="Segmentation..."
options="{'no_create': True,'no_open': True}" />
<field name="overbooking" />
</group>
<group>
<field name="reservation_type" />
<field name="out_service_description"
attrs="{'invisible':[('reservation_type','not in',('out'))]}"/>
</group>
</group>
</page>
</notebook>
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids" widget="mail_followers"/>
<field name="message_ids" widget="mail_thread"/>
</div>
</form>
</field>
nolabel="1" context="{'from_folio':True,'room_lines':room_lines,'folio_id': id,'tree_view_ref':'hotel.hotel_reservation_view_bottom_tree', 'form_view_ref':'hotel.hotel_reservation_view_form'}"/>
<separator string="Service Lines" colspan="4"/>
<field name="service_ids"
context="{'default_folio_id': active_id}"
nolabel="1">
<tree string="Services" editable="bottom">
<field name="folio_id" invisible="1"/>
<!-- <field name="layout_category_id" groups="sale.group_sale_layout"/> -->
<field name="product_id"
domain="[('sale_ok', '=', True)]"
options="{'create': False, 'create_edit': False}" />
<field name="name"/>
<field name="ser_room_line" options="{'create': False, 'create_edit': False}"/>
<!-- <field name="product_uom_qty"
string="Ordered Qty"
context="{'partner_id':parent.partner_id, 'quantity':product_uom_qty, 'uom':product_uom, 'company_id': parent.company_id}"
/> -->
<!-- <field name="qty_delivered" invisible="1"/> -->
<!-- attrs="{'readonly': [('qty_delivered_updateable', '=', False)]}"/> -->
<!-- <field name="qty_invoiced" invisible="1"/> -->
<!-- <field name="qty_to_invoice" invisible="1"/> -->
<!-- <field name="product_uom"
attrs="{'readonly': [('state', 'in', ('sale','done', 'cancel'))]}"
context="{'company_id': parent.company_id}"
groups="product.group_uom" options='{"no_open": True}'/> -->
<!-- <field name="analytic_tag_ids" groups="analytic.group_analytic_accounting" widget="many2many_tags"/> -->
<!-- <field name="price_unit"
attrs="{'readonly': [('qty_invoiced', '&gt;', 0)]}"/> -->
<!-- <field name="tax_id" widget="many2many_tags" domain="[('type_tax_use','=','sale'),('company_id','=',parent.company_id)]"
attrs="{'readonly': [('qty_invoiced', '&gt;', 0)]}"/> -->
<!-- <field name="discount" groups="sale.group_discount_per_so_line"/> -->
<!-- <field name="price_subtotal" widget="monetary" invisible="1"/> -->
<!-- <field name="price_total" widget="monetary"/> -->
<!-- <field name="qty_delivered_updateable" invisible="1"/> -->
<!-- <field name="state" invisible="1"/> -->
<!-- <field name="invoice_status" invisible="1"/> -->
<!-- <field name="customer_lead" invisible="1"/> -->
<!-- <field name="currency_id" invisible="1"/> -->
<field name="channel_type" sttrs="{'invisible':[('channel_type', '!=', 'call')]"/>
</tree>
</field>
<group colspan="2" class="oe_subtotal_footer oe_right">
<field name="amount_untaxed" sum="Untaxed amount" widget='monetary' />
<field name="amount_tax" widget='monetary' />
<div class="oe_subtotal_footer_separator oe_inline">
<label for="amount_total" />
</div>
<field name="amount_total" nolabel="1" sum="Total amount"
widget='monetary' />
</group>
context="{'from_room':False,'folio_id': id,'tree_view_ref':'hotel.hotel_service_view_tree', 'form_view_ref':'hotel.hotel_service_view_form'}"
nolabel="1" />
<group colspan="2" class="oe_subtotal_footer oe_right">
<field name="amount_untaxed" sum="Untaxed amount" widget='monetary' />
<field name="amount_tax" widget='monetary' />
<div class="oe_subtotal_footer_separator oe_inline">
<label for="amount_total" />
</div>
<field name="amount_total" nolabel="1" sum="Total amount"
widget='monetary' />
</group>
<div class="oe_clear" />
<group>
<field name="note" />
@@ -488,6 +201,7 @@
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids" widget="mail_followers"/>
<field name="activity_ids" widget="mail_activity"/>
<field name="message_ids" widget="mail_thread"/>
</div>
</form>
@@ -506,7 +220,7 @@
<field name="name"/>
<field name="partner_id" select="1"/>
<field name="date_order" select="1"/>
<field name="rooms_char" string="Rooms"/>
<field name="room_lines" widget="many2many_tags" />
<field name="amount_total" sum="Total amount"/>
</tree>
</field>

View File

@@ -6,6 +6,7 @@
<field name="res_model">hotel.reservation</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form,graph,pivot</field>
<field name="context">{'from_room': True}</field>
</record>
<!--=== Hotel Reservation ==== -->
@@ -17,6 +18,7 @@
<form string="Reservation" >
<header>
<field name="splitted" invisible="True" />
<field name="tax_ids" invisible="1"/>
<field name="parent_reservation" invisible="True" />
<field name="has_confirmed_reservations_to_send" invisible="1" />
<field name="has_cancelled_reservations_to_send" invisible="1" />
@@ -97,16 +99,15 @@
<span class="o_stat_text">Books</span>
</div>
</button>
<!-- <button type="object" class="oe_stat_button"
id="payment_smart_button"
icon="fa-money"
name="action_recalcule_payment"
attrs="{'invisible': [('fix_folio_pending','=',False)]}"
help="Calcule the total Price">
<button type="object" class="oe_stat_button" id="invoice_button"
icon="fa-pencil-square-o" name="open_invoices_reservation">
<div class="o_form_field o_stat_info">
<span class="o_stat_text">Calcule Price</span>
<span class="o_stat_value">
<field name="invoice_count"/>
</span>
<span class="o_stat_text">Invoices</span>
</div>
</button> -->
</button>
<button type="object" class="oe_stat_button"
id="open_master"
icon="fa-chain-broken"
@@ -194,10 +195,13 @@
<!-- TODO: How to filter to avoid show False (generic) pricelist board when exist a specific pricelist board¿? -->
<field name="board_service_room_id" domain="[
('hotel_room_type_id', '=', room_type_id),
('pricelist_id', 'in', [pricelist_id, False])]" />
('pricelist_id', 'in', [pricelist_id, False])]"
options="{'no_create': True,'no_open': True}" />
<field name="name"/>
<field name="adults"/>
<field name="children"/>
<field name="qty_invoiced" />
<field name="qty_to_invoice" />
<field name="room_type_id" on_change="1" options="{'no_create': True,'no_open': True}"
attrs="{'readonly':[('state','not in',('draft'))]}"/>
<field name="channel_type" attrs="{'required':[('reservation_type','not in',('staff','out'))]}"/>
@@ -240,55 +244,56 @@
</group>
<notebook>
<page name="days" string="Service and Days">
<group string="Reservation Services" name="reservation_services">
<field name="service_ids"
context="{'default_ser_room_line': active_id, 'default_folio_id': folio_id}"
nolabel="1">
<tree string="Services" editable="bottom"
decoration-success="is_board_service == True">
<button type="object" class="oe_stat_button"
id="included_in_room" icon="fa fa-1x fa-bed"
name="open_service_lines"
attrs="{'invisible':[('is_board_service','=', False)]}" />
<!-- <field name="sequence" widget="handle"/> -->
<field name="per_day" invisible="1"/>
<field name="is_board_service" invisible="1" />
<field name="folio_id" invisible="1"/>
<field name="product_id"
domain="[('sale_ok', '=', True)]"
options="{'create': False, 'create_edit': False}" />
<!-- <field name="layout_category_id" groups="sale.group_sale_layout"/> -->
<field name="name"/>
<field name="product_qty" attrs="{'readonly': [('per_day','=',True)]}" force_save="1"/>
<button type="object" class="oe_stat_button"
id="go_service_lines" icon="fa fa-2x fa-bars"
name="open_service_lines"
attrs="{'invisible': [('per_day','=',False)]}"/>
<field name="days_qty" invisible="1"/>
<field name="price_unit" />
<field name="discount" />
<field name="tax_ids" widget="many2many_tags"/>
<field name="price_subtotal" />
<field name="price_tax" />
<field name="price_total" />
<field name="service_line_ids" invisible="1">
<tree string="Days" >
<field name="date" />
<field name="day_qty" />
</tree>
</field>
</tree>
</field>
</group>
<group string="Days" name="days">
<field name="reservation_line_ids" nolabel="1">
<tree create="false" delete="false" editable="bottom">
<field name="date" />
<field name="price" />
<field name="discount" />
</tree>
</field>
</group>
<group string="Reservation Services" name="reservation_services">
<field name="service_ids"
context="{'default_ser_room_line': active_id, 'default_folio_id': folio_id, 'form_view_ref':'hotel.hotel_service_view_form'}"
nolabel="1">
<!-- If charge this view with tree_view_ref, its !crash! (field not found) when open reservation form from folio form... ¿?-->
<tree string="Services" editable="bottom"
decoration-success="is_board_service == True">
<field name="is_board_service" invisible="1" />
<button type="object" class="oe_stat_button"
id="included_in_room" icon="fa fa-1x fa-bed"
name="open_service_lines"
attrs="{'invisible':[('is_board_service','=', False)]}" />
<field name="per_day" invisible="1"/>
<field name="folio_id" invisible="1"/>
<field name="ser_room_line" invisible="1"
attrs = "{'required': [('per_day','=',True)]}" />
<field name="product_id"
domain="[('sale_ok', '=', True)]"
options="{'no_create': True,'no_open': True}" />
<field name="name"/>
<field name="product_qty" attrs="{'readonly': [('per_day','=',True)]}" force_save="1"/>
<button type="object" class="oe_stat_button"
id="go_service_lines" icon="fa fa-2x fa-bars"
name="open_service_lines"
attrs="{'invisible': [('per_day','=',False)]}"/>
<field name="days_qty" invisible="1"/>
<field name="price_unit" />
<field name="discount" />
<field name="tax_ids" widget="many2many_tags"/>
<field name="price_subtotal" />
<field name="price_tax" />
<field name="price_total" />
<field name="service_line_ids" invisible="1">
<tree string="Days" >
<field name="date" />
<field name="day_qty" />
</tree>
</field>
</tree>
</field>
</group>
<group string="Days" name="days">
<field name="reservation_line_ids" nolabel="1">
<tree create="false" delete="false" editable="bottom">
<field name="date" />
<field name="price" />
<field name="discount" />
</tree>
</field>
</group>
</page>
<page name="others" string="Others">
<group>
@@ -306,6 +311,7 @@
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids" widget="mail_followers"/>
<field name="activity_ids" widget="mail_activity"/>
<field name="message_ids" widget="mail_thread"/>
</div>
</form>
@@ -331,6 +337,7 @@
<button icon="fa fa-2x fa-angellist"
attrs="{'invisible':['|',('folio_pending_amount','&gt;',0),('state' ,'!=', 'done')]}"
type="object"
id="open_folio"
name="open_folio" />
<field name="state" />
<button type="object" class="oe_stat_button"
@@ -345,6 +352,7 @@
'exit_date': checkout,'reservation_id': id, 'hidden_checkin_partner': True, 'edit_checkin_partner': True }"
attrs="{'invisible':['|','|', ('state','not in',('confirm','booking')),('checkin_partner_pending_count','=', 0),('parent_reservation','!=',False)]}"
/>
<field name="room_id" />
<button type="action" class="oe_stat_button"
icon="fa fa-2x fa-list-ul"
name="%(open_hotel_reservation_form_tree_all)d"
@@ -352,7 +360,6 @@
/>
<field name="partner_id"/>
<field name="parent_reservation" invisible="1" />
<!-- <field name="room_type_id" string="Reserved Room Type"/> -->
<field name="room_type_id" string="Reserved Unit Type" />
<field name="nights" />
<field name="adults" string="Persons"/>
@@ -363,8 +370,11 @@
<field name="last_updated_res" string="Updated on"/>
<field name="checkin_partner_ids" invisible ="1"/>
<field name="to_assign" invisible="1"/>
<!-- checkin_partner_smart_button attrs depends on checkin_partner_pending to be showed -->
<field name="checkin_partner_pending_count" invisible="1"/>
<field name="discount" />
<field name="tax_ids" invisible="1"/>
<field name="price_subtotal" invisible="1"/>
<field name="price_total" />
<field name="folio_pending_amount" string="Folio Pending Amount"/>
<button type="object" class="oe_stat_button"
icon="fa fa-3x fa-money"
@@ -376,6 +386,46 @@
</field>
</record>
<!-- Tree editable BOTTOM view of hotel reservation -->
<record model="ir.ui.view" id="hotel_reservation_view_bottom_tree">
<field name="name">hotel.reservation.tree</field>
<field name="model">hotel.reservation</field>
<field name="inherit_id" ref="hotel.hotel_reservation_view_tree" />
<field name="mode">primary</field>
<field name="arch" type="xml">
<tree position="attributes">
<attribute name="editable">bottom</attribute>
<attribute name="delete">false</attribute>
<attribute name="options">{'no_open': True}</attribute>
<attribute name="string">Rooms</attribute>
</tree>
<xpath expr="//field[@name='folio_id']" position="attributes">
<attribute name="invisible">1</attribute>
</xpath>
<xpath expr="//button[@name='open_folio'][1]" position="attributes">
<attribute name="invisible">True</attribute>
</xpath>
<xpath expr="//button[@name='open_folio'][2]" position="attributes">
<attribute name="invisible">True</attribute>
</xpath>
<xpath expr="//button[@name='open_folio'][2]" position="attributes">
<attribute name="invisible">True</attribute>
</xpath>
<xpath expr="//button[@name='%(open_hotel_reservation_form_tree_all)d']" position="attributes">
<attribute name="invisible">True</attribute>
</xpath>
<xpath expr="//field[@name='partner_id']" position="attributes">
<attribute name="invisible">True</attribute>
</xpath>
<xpath expr="//field[@name='folio_pending_amount']" position="attributes">
<attribute name="invisible">True</attribute>
</xpath>
<xpath expr="//field[@name='create_uid']" position="attributes">
<attribute name="invisible">True</attribute>
</xpath>
</field>
</record>
<!-- Search view of hotel reservation -->
<record model="ir.ui.view" id="hotel_reservation_view_search">
<field name="name">hotel.reservation.search</field>

View File

@@ -41,6 +41,48 @@
</field>
</record>
<!-- Tree view of hotel service -->
<record model="ir.ui.view" id="hotel_service_view_tree">
<field name="name">.hotel.service.view.tree</field>
<field name="model">hotel.service</field>
<field name="arch" type="xml">
<tree string="Services" editable="bottom"
decoration-success="is_board_service == True">
<field name="is_board_service" invisible="1" />
<button type="object" class="oe_stat_button"
id="included_in_room" icon="fa fa-1x fa-bed"
name="open_service_lines"
attrs="{'invisible':[('is_board_service','=', False)]}" />
<field name="per_day" invisible="1"/>
<field name="folio_id" invisible="1"/>
<field name="ser_room_line"
attrs = "{'required': [('per_day','=',True)]}" />
<field name="product_id"
domain="[('sale_ok', '=', True)]"
options="{'create': False, 'create_edit': False}"/>
<field name="name"/>
<field name="product_qty" attrs="{'readonly': [('per_day','=',True)]}" force_save="1"/>
<button type="object" class="oe_stat_button"
id="go_service_lines" icon="fa fa-2x fa-bars"
name="open_service_lines"
attrs="{'invisible': [('per_day','=',False)]}"/>
<field name="days_qty" invisible="1"/>
<field name="price_unit" />
<field name="discount" />
<field name="tax_ids" widget="many2many_tags"/>
<field name="price_subtotal" />
<field name="price_tax" />
<field name="price_total" />
<field name="service_line_ids" invisible="1">
<tree string="Days" >
<field name="date" />
<field name="day_qty" />
</tree>
</field>
</tree>
</field>
</record>
<!-- search view of hotel service -->
<record model="ir.ui.view" id="hotel_service_view_search">
<field name="name">hotel.service.search</field>
@@ -60,19 +102,6 @@
</field>
</record>
<!-- Tree view of hotel service -->
<record model="ir.ui.view" id="hotel_service_view_tree">
<field name="name">hotel.service.tree</field>
<field name="model">hotel.service</field>
<field name="arch" type="xml">
<tree string="Hotel Services">
<field name="name" />
<field name="product_id" />
<field name="product_qty" />
</tree>
</field>
</record>
<!-- Action for hotel service -->
<record model="ir.actions.act_window" id="action_hotel_services_form">
<field name="name">Hotel Services</field>

View File

@@ -5,20 +5,15 @@
<field name="model">account.invoice</field>
<field name="inherit_id" ref="account.invoice_form" />
<field name="arch" type="xml">
<xpath expr="//header" position="after">
<div class="alert alert-danger" role="alert" style="margin-bottom:0px;"
attrs="{'invisible': [('dif_customer_payment','=',False)]}">
You have payments on the related folio associated with other customers than the current one on the invoice.
Make sure to <bold><button class="alert-link" type="object" name="action_folio_payments" string="modify the payment"/></bold> from the folio if necessary before paying this invoice
</div>
<field name="dif_customer_payment" invisible="1" />
<field name="from_folio" invisible="1" />
</xpath>
<xpath expr="//field[@name='date_invoice']" position="after">
<field name="folio_ids" widget="many2many_tags"/>
<field name="from_folio" invisible="1" />
</xpath>
<xpath expr="//field[@name='product_id']" position="after">
<field name="reservation_line_ids" widget="many2many_tags"/>
</xpath>
<xpath expr="//button[@name='%(account.action_account_invoice_payment)d']" position="attributes">
<attribute name="attrs">{'invisible': ['|',('from_folio','=',True)]}</attribute>
<attribute name="attrs">{'invisible': ['|',('from_folio','=',True)]}</attribute>
</xpath>
</field>
</record>

View File

@@ -16,42 +16,52 @@
<field name="model">account.payment</field>
<field name="arch" type="xml">
<form string="Register Payment" version="7">
<header>
<button name="post" class="oe_highlight" states="draft" string="Confirm" type="object"/>
<button name="action_draft" class="oe_highlight" states="cancelled" string="Set To Draft" type="object"/>
<field name="state" widget="statusbar" statusbar_visible="draft,posted,reconciled,cancelled"/>
</header>
<sheet>
<div class="oe_button_box" name="button_box">
<button class="oe_stat_button" name="button_journal_entries" string="Journal Items" type="object" groups="account.group_account_manager" attrs="{'invisible':[('move_line_ids','=',[])]}" icon="fa-bars"/>
<button class="oe_stat_button" name="button_journal_entries"
string="Journal Items" type="object"
groups="account.group_account_user"
attrs="{'invisible':[('move_line_ids','=',[])]}" icon="fa-bars"/>
<field name="move_line_ids" invisible="1"/>
<button class="oe_stat_button" name="button_invoices" string="Invoices" type="object" attrs="{'invisible':[('has_invoices','=',False)]}" icon="fa-bars"/>
<button class="oe_stat_button" name="button_invoices"
string="Invoices" type="object"
attrs="{'invisible':[('has_invoices','=',False)]}" icon="fa-bars"/>
<button class="oe_stat_button" name="open_payment_matching_screen"
string="Payment Matching" type="object"
attrs="{'invisible':[('move_reconciled','=',True)]}" icon="fa-university"/>
<field name="has_invoices" invisible="1"/>
<field name="move_reconciled" invisible="1"/>
</div>
<field name="id" invisible="1"/>
<div class="oe_title" attrs="{'invisible': [('state', '=', 'draft')]}">
<h1><field name="name"/></h1>
</div>
<group>
<field name="payment_type" invisible="1"/>
<field name="partner_type" invisible="1"/>
<field name="state" invisible="1"/>
<group>
<field name="journal_id" widget="selection"/>
<field name="partner_id"/>
<field name="hide_payment_method" invisible="1"/>
<field name="payment_method_id" widget="radio" attrs="{'invisible': [('hide_payment_method', '=', True)]}"/>
<field name="payment_method_code" invisible="1"/>
<field name="payment_type" widget="radio" attrs="{'readonly': [('state', '!=', 'draft')]}"/>
<field name="partner_type" widget="selection" attrs="{'required': [('state', '=', 'draft'), ('payment_type', 'in', ('inbound', 'outbound'))], 'invisible': [('payment_type', 'not in', ('inbound', 'outbound'))], 'readonly': [('state', '!=', 'draft')]}"/>
<field name="partner_id" attrs="{'required': [('state', '=', 'draft'), ('payment_type', 'in', ('inbound', 'outbound'))], 'invisible': [('payment_type', 'not in', ('inbound', 'outbound'))], 'readonly': [('state', '!=', 'draft')]}" context="{'default_is_company': True, 'default_supplier': payment_type == 'outbound', 'default_customer': payment_type == 'inbound'}"/>
<label for="amount"/>
<div name="amount_div" class="o_row">
<field name="amount"/>
<field name="amount" attrs="{'readonly': [('state', '!=', 'draft')]}"/>
<field name="currency_id" options="{'no_create': True, 'no_open': True}" groups="base.group_multi_currency" attrs="{'readonly': [('state', '!=', 'draft')]}"/>
</div>
<field name="journal_id" widget="selection" attrs="{'readonly': [('state', '!=', 'draft')]}"/>
<field name="destination_journal_id" widget="selection" attrs="{'required': [('payment_type', '=', 'transfer')], 'invisible': [('payment_type', '!=', 'transfer')], 'readonly': [('state', '!=', 'draft')]}"/>
<field name="hide_payment_method" invisible="1"/>
<field name="payment_method_id" string=" " widget="radio" attrs="{'invisible': [('hide_payment_method', '=', True)], 'readonly': [('state', '!=', 'draft')]}"/>
<field name="payment_method_code" invisible="1"/>
</group>
<group>
<field name="payment_date"/>
<field name="communication"/>
<field name="payment_date" attrs="{'readonly': [('state', '!=', 'draft')]}"/>
<field name="communication" attrs="{'invisible': [('state', '!=', 'draft'), ('communication', '=', False)], 'readonly': [('state', '!=', 'draft')]}"/>
<field name="folio_id"/>
</group>
<group attrs="{'invisible': [('payment_difference', '=', 0.0)]}">
<label for="payment_difference"/>
<div>
<field name="payment_difference"/>
<field name="payment_difference_handling" widget="radio" nolabel="1"/>
</div>
<field name="writeoff_account_id" string="Post Difference In"
attrs="{'invisible': [('payment_difference_handling','=','open')], 'required': [('payment_difference_handling', '=', 'reconcile')]}"/>
</group>
</group>
</sheet>
<footer>
@@ -65,6 +75,10 @@
attrs="{'invisible': [('state','=','draft')]}"/>
<button string="Cancel" class="btn-default" special="cancel"/>
</footer>
<div class="oe_chatter">
<field name="message_follower_ids" widget="mail_followers" groups="base.group_user"/>
<field name="message_ids" widget="mail_thread"/>
</div>
</form>
</field>
</record>

View File

@@ -1,9 +1,11 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import time
from odoo.tools import DEFAULT_SERVER_DATE_FORMAT
from odoo import api, fields, models, _
import odoo.addons.decimal_precision as dp
from odoo.exceptions import UserError
from datetime import timedelta
class FolioAdvancePaymentInv(models.TransientModel):
@@ -16,20 +18,38 @@ class FolioAdvancePaymentInv(models.TransientModel):
@api.model
def _get_advance_payment_method(self):
if self._count() == 1:
sale_obj = self.env['sale.order']
folio_obj = self.env['hotel.folio']
order = sale_obj.browse(folio_obj.mapped('order_id.id'))
if all([line.product_id.invoice_policy == 'order' for line in order.order_line]) \
or order.invoice_count:
return 'all'
return 'delivered'
return 'all'
@api.model
def _default_product_id(self):
product_id = self.env['ir.default'].sudo().get('sale.config.settings',
'deposit_product_id_setting')
return self.env['product.product'].browse(product_id)
product_id = self.env['ir.config_parameter'].sudo().get_param('sale.default_deposit_product_id')
return self.env['product.product'].browse(int(product_id))
@api.model
def _get_default_folio(self):
if self._context.get('default_reservation_id'):
folio_ids = self._context.get('default_folio_id', [])
else:
folio_ids = self._context.get('active_ids', [])
folios = self.env['hotel.folio'].browse(folio_ids)
return folios
@api.model
def _get_default_reservation(self):
if self._context.get('default_reservation_id'):
reservations = self.env['hotel.reservation'].browse(self._context.get('active_ids', []))
else:
folios = self._get_default_folio()
reservations = self.env['hotel.reservation']
for folio in folios:
reservations |= folio.room_lines
return reservations
@api.model
def _get_default_partner_invoice(self):
folios = self._get_default_folio()
return folios[0].partner_invoice_id
@api.model
def _default_deposit_account_id(self):
@@ -40,15 +60,30 @@ class FolioAdvancePaymentInv(models.TransientModel):
return self._default_product_id().taxes_id
advance_payment_method = fields.Selection([
('delivered', 'Invoiceable lines'),
('all', 'Invoiceable lines (deduct down payments)'),
('one', 'One line (Bill all in one line)'),
('percentage', 'Down payment (percentage)'),
('fixed', 'Down payment (fixed amount)')
], string='What do you want to invoice?', default=_get_advance_payment_method,
required=True)
product_id = fields.Many2one('product.product', string='Down Payment Product',
domain=[('type', '=', 'service')], default=_default_product_id)
count = fields.Integer(default=_count, string='# of Orders')
folio_ids = fields.Many2many("hotel.folio", string="Folios",
help="Folios grouped",
default=_get_default_folio)
reservation_ids = fields.Many2many("hotel.reservation", string="Rooms",
help="Folios grouped",
default=_get_default_reservation)
group_folios = fields.Boolean('Group Folios')
partner_invoice_id = fields.Many2one('res.partner',
string='Invoice Address', required=True,
default=_get_default_partner_invoice,
help="Invoice address for current Invoice.")
line_ids = fields.One2many('line.advance.inv',
'advance_inv_id',
string="Invoice Lines")
#Advance Payment
product_id = fields.Many2one('product.product', string="Product",
domain=[('type', '=', 'service')], default=_default_product_id)
amount = fields.Float('Down Payment Amount',
digits=dp.get_precision('Account'),
help="The amount to be invoiced in advance, taxes excluded.")
@@ -107,7 +142,6 @@ class FolioAdvancePaymentInv(models.TransientModel):
'reference': False,
'account_id': order.partner_id.property_account_receivable_id.id,
'partner_id': order.partner_invoice_id.id,
'partner_shipping_id': order.partner_shipping_id.id,
'invoice_line_ids': [(0, 0, {
'name': name,
'origin': order.name,
@@ -119,7 +153,7 @@ class FolioAdvancePaymentInv(models.TransientModel):
'product_id': self.product_id.id,
'sale_line_ids': [(6, 0, [so_line.id])],
'invoice_line_tax_ids': [(6, 0, tax_ids)],
'account_analytic_id': order.project_id.id or False,
'account_analytic_id': order.analytic_account_id.id or False,
})],
'currency_id': order.pricelist_id.currency_id.id,
'payment_term_id': order.payment_term_id.id,
@@ -136,29 +170,58 @@ class FolioAdvancePaymentInv(models.TransientModel):
subtype_id=self.env.ref('mail.mt_note').id)
return invoice
@api.model
def _validate_invoices(self, invoice):
invoice.action_invoice_open()
payment_ids = self.folio_ids.mapped('payment_ids.id')
domain = [('account_id', '=', invoice.account_id.id),
('payment_id', 'in', payment_ids), ('reconciled', '=', False),
'|', ('amount_residual', '!=', 0.0),
('amount_residual_currency', '!=', 0.0)]
if invoice.type in ('out_invoice', 'in_refund'):
domain.extend([('credit', '>', 0), ('debit', '=', 0)])
type_payment = _('Outstanding credits')
else:
domain.extend([('credit', '=', 0), ('debit', '>', 0)])
type_payment = _('Outstanding debits')
info = {'title': '', 'outstanding': True, 'content': [], 'invoice_id': invoice.id}
lines = self.env['account.move.line'].search(domain)
currency_id = invoice.currency_id
for line in lines:
invoice.assign_outstanding_credit(line.id)
@api.multi
def create_invoices(self):
folios = self.env['hotel.folio'].browse(self._context.get('active_ids', []))
sale_orders = self.env['sale.order'].browse(folios.mapped('order_id.id'))
inv_obj = self.env['account.invoice']
precision = self.env['decimal.precision'].precision_get('Product Unit of Measure')
folios = self.folio_ids
if self.advance_payment_method == 'delivered':
sale_orders.action_invoice_create()
for folio in folios:
if folio.partner_invoice_id != self.partner_invoice_id:
raise UserError(_('The billing directions must match'))
if self.advance_payment_method == 'all':
inv_data = self._prepare_invoice()
invoice = inv_obj.create(inv_data)
for line in self.line_ids:
line.invoice_line_create(invoice.id, line.qty)
self._validate_invoices(invoice)
elif self.advance_payment_method == 'all':
sale_orders.action_invoice_create(final=True)
pass
#Group lines by tax_ids
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.default'].sudo().set(
'sale.config.settings',
'deposit_product_id_setting',
self.product_id.id)
self.env['ir.config_parameter'].sudo().set_param(
'sale.default_deposit_product_id', self.product_id.id)
sale_line_obj = self.env['sale.order.line']
for order in sale_orders:
service_obj = self.env['hotel.service']
for folio in folios:
if self.advance_payment_method == 'percentage':
amount = order.amount_untaxed * self.amount / 100
amount = folio.amount_untaxed * folio.amount_total / 100
else:
amount = self.amount
if self.product_id.invoice_policy != 'order':
@@ -166,26 +229,43 @@ class FolioAdvancePaymentInv(models.TransientModel):
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)
if order.fiscal_position_id and taxes:
tax_ids = order.fiscal_position_id.map_tax(taxes).ids
lambda r: not folio.company_id or r.company_id == folio.company_id)
if folio.fiscal_position_id and taxes:
tax_ids = folio.fiscal_position_id.map_tax(taxes).ids
else:
tax_ids = taxes.ids
context = {'lang': order.partner_id.lang}
so_line = sale_line_obj.create({
context = {'lang': folio.partner_id.lang}
service_line = service_obj.create({
'name': _('Advance: %s') % (time.strftime('%m %Y'),),
'price_unit': amount,
'product_uom_qty': 0.0,
'order_id': order.id,
'folio_id': folio.id,
'discount': 0.0,
'product_uom': self.product_id.uom_id.id,
'product_id': self.product_id.id,
'tax_id': [(6, 0, tax_ids)],
})
del context
self._create_invoice(order, so_line, amount)
invoice = self._create_invoice(folio, service_line, amount)
invoice.compute_taxes()
if not invoice.invoice_line_ids:
raise UserError(_('There is no invoiceable line.'))
# If invoice is negative, do a refund invoice instead
if invoice.amount_total < 0:
invoice.type = 'out_refund'
for line in invoice.invoice_line_ids:
line.quantity = -line.quantity
# Use additional field helper function (for account extensions)
for line in invoice.invoice_line_ids:
line._set_additional_fields(invoice)
# Necessary to force computation of taxes. In account_invoice, they are triggered
# by onchanges, which are not triggered when doing a create.
invoice.compute_taxes()
invoice.message_post_with_view('mail.message_origin_link',
values={'self': invoice, 'origin': folios},
subtype_id=self.env.ref('mail.mt_note').id)
if self._context.get('open_invoices', False):
return sale_orders.action_view_invoice()
return folios.open_invoices_folio()
return {'type': 'ir.actions.act_window_close'}
def _prepare_deposit_product(self):
@@ -196,3 +276,226 @@ class FolioAdvancePaymentInv(models.TransientModel):
'property_account_income_id': self.deposit_account_id.id,
'taxes_id': [(6, 0, self.deposit_taxes_id.ids)],
}
@api.onchange('reservation_ids')
def prepare_invoice_lines(self):
vals = []
folios = self.folio_ids
invoice_lines = {}
for folio in folios:
for service in folio.service_ids.filtered(
lambda x: x.is_board_service == False and \
(x.ser_room_line.id in self.reservation_ids.ids or \
x.ser_room_line.id == False)):
invoice_lines[service.id] = {
'description' : service.name,
'product_id': service.product_id.id,
'qty': service.product_qty,
'discount': service.discount,
'price_unit': service.price_unit,
'service_id': service.id,
}
for reservation in folio.room_lines.filtered(
lambda x: x.id in self.reservation_ids.ids):
board_service = reservation.board_service_room_id
for day in reservation.reservation_line_ids.sorted('date'):
extra_price = 0
if board_service:
services = reservation.service_ids.filtered(
lambda x: x.is_board_service == True)
for service in services:
extra_price += service.price_unit * \
service.service_line_ids.filtered(
lambda x: x.date == day.date).day_qty
#group_key: if group by reservation, We no need group by room_type
group_key = (reservation.id, reservation.room_type_id.id, day.price + extra_price, day.discount)
date = fields.Date.from_string(day.date)
description = folio.name + ' ' + reservation.room_type_id.name + ' (' + \
reservation.board_service_room_id.hotel_board_service_id.name + ')' \
if board_service else folio.name + ' ' + reservation.room_type_id.name
if group_key not in invoice_lines:
invoice_lines[group_key] = {
'description' : description,
'reservation_id': reservation.id,
'room_type_id': reservation.room_type_id,
'product_id': self.env['product.product'].browse(
reservation.room_type_id.product_id.id),
'discount': day.discount,
'price_unit': day.price + extra_price,
'reservation_line_ids': [(4, day.id)]
}
else:
invoice_lines[group_key][('reservation_line_ids')].append((4,day.id))
for group_key in invoice_lines:
vals.append((0, False, invoice_lines[group_key]))
self.line_ids = vals
self.line_ids.onchange_reservation_line_ids()
@api.onchange('folio_ids')
def onchange_folio_ids(self):
vals = []
folios = self.folio_ids
invoice_lines = {}
reservations = self.env['hotel.reservation']
services = self.env['hotel.service']
old_folio_ids = self.reservation_ids.mapped('folio_id.id')
for folio in folios.filtered(lambda r: r.id not in old_folio_ids):
folio_reservations = folio.room_lines
if folio_reservations:
reservations |= folio_reservations
self.reservation_ids |= reservations
self.prepare_invoice_lines()
@api.model
def _prepare_invoice(self):
"""
Prepare the dict of values to create the new invoice for a folio. This method may be
overridden to implement custom invoice generation (making sure to call super() to establish
a clean extension chain).
"""
journal_id = self.env['account.invoice'].default_get(['journal_id'])['journal_id']
if not journal_id:
raise UserError(_('Please define an accounting sales journal for this company.'))
origin = ' '.join(self.folio_ids.mapped('name'))
pricelist = self.folio_ids[0].pricelist_id
currency = self.folio_ids[0].currency_id
payment_term = self.folio_ids[0].payment_term_id
fiscal_position = self.folio_ids[0].fiscal_position_id
company = self.folio_ids[0].company_id
user = self.folio_ids[0].user_id
team = self.folio_ids[0].team_id
for folio in self.folio_ids:
if folio.pricelist_id != pricelist:
raise UserError(_('All Folios must hace the same pricelist'))
invoice_vals = {
'name': self.folio_ids[0].client_order_ref or '',
'origin': origin,
'type': 'out_invoice',
'account_id': self.partner_invoice_id.property_account_receivable_id.id,
'partner_id': self.partner_invoice_id.id,
'journal_id': journal_id,
'currency_id': pricelist.id,
'payment_term_id': payment_term.id,
'fiscal_position_id': fiscal_position.id or self.partner_invoice_id.property_account_position_id.id,
'company_id': company.id,
'user_id': user and user.id,
'team_id': team.id
}
return invoice_vals
class LineAdvancePaymentInv(models.TransientModel):
_name = "line.advance.inv"
_description = "Lines Advance Invoice"
room_type_id = fields.Many2one('hotel.room.type')
product_id = fields.Many2one('product.product', string='Down Payment Product',
domain=[('type', '=', 'service')])
qty = fields.Integer('Quantity')
price_unit = fields.Float('Price Unit')
price_total = fields.Float('Price Total', compute='_compute_price_total')
price_tax = fields.Float('Price Tax', compute='_compute_price_total')
price_subtotal = fields.Float('Price Subtotal',
compute='_compute_price_total',
store=True)
advance_inv_id = fields.Many2one('folio.advance.payment.inv')
price_room = fields.Float(compute='_compute_price_room')
discount = fields.Float(
string='Discount (%)',
digits=dp.get_precision('Discount'), default=0.0)
to_invoice = fields.Boolean('To Invoice')
description = fields.Text('Description')
description_dates = fields.Text('Range')
reservation_id = fields.Many2one('hotel.reservation')
service_id = fields.Many2one('hotel.service')
folio_id = fields.Many2one('hotel.folio', compute='_compute_folio_id')
reservation_line_ids = fields.Many2many(
'hotel.reservation.line',
string='Reservation Lines')
@api.depends('qty', 'price_unit', 'discount')
def _compute_price_total(self):
for record in self:
origin = record.reservation_id if record.reservation_id.id else record.service_id
amount_line = record.price_unit * record.qty
if amount_line > 0:
product = record.product_id
price = amount_line * (1 - (record.discount or 0.0) * 0.01)
taxes = origin.tax_ids.compute_all(price, origin.currency_id, 1, product=product)
record.update({
'price_tax': sum(t.get('amount', 0.0) for t in taxes.get('taxes', [])),
'price_total': taxes['total_included'],
'price_subtotal': taxes['total_excluded'],
})
def _compute_price_room(self):
for record in self:
if record.reservation_id:
record.price_room = record.reservation_line_ids[0].price
def _compute_folio_id(self):
for record in self:
origin = record.reservation_id if record.reservation_id.id else record.service_id
record.folio_id = origin.folio_id
@api.onchange('reservation_line_ids')
def onchange_reservation_line_ids(self):
for record in self:
if record.reservation_id:
if not record.reservation_line_ids:
raise UserError(_('If you want drop the line, use the trash icon'))
record.qty = len(record.reservation_line_ids)
record.description_dates = record.reservation_line_ids[0].date + ' - ' + \
((fields.Date.from_string(record.reservation_line_ids[-1].date)) + \
timedelta(days=1)).strftime(DEFAULT_SERVER_DATE_FORMAT)
@api.multi
def invoice_line_create(self, invoice_id, qty):
""" Create an invoice line.
:param invoice_id: integer
:param qty: float quantity to invoice
:returns recordset of account.invoice.line created
"""
invoice_lines = self.env['account.invoice.line']
precision = self.env['decimal.precision'].precision_get('Product Unit of Measure')
for line in self:
origin = line.reservation_id if line.reservation_id.id else line.service_id
res = {}
product = line.product_id
account = product.property_account_income_id or product.categ_id.property_account_income_categ_id
if not account:
raise UserError(_('Please define income account for this product: "%s" (id:%d) - or for its category: "%s".') %
(product.name, product.id, product.categ_id.name))
fpos = line.folio_id.fiscal_position_id or line.folio_id.partner_id.property_account_position_id
if fpos:
account = fpos.map_account(account)
vals = {
'sequence': origin.sequence,
'origin': origin.name,
'account_id': account.id,
'price_unit': line.price_unit,
'quantity': line.qty,
'discount': line.discount,
'uom_id': product.uom_id.id,
'product_id': product.id or False,
'invoice_line_tax_ids': [(6, 0, origin.tax_ids.ids)],
'account_analytic_id': line.folio_id.analytic_account_id.id,
'analytic_tag_ids': [(6, 0, origin.analytic_tag_ids.ids)]
}
if line.reservation_id:
vals.update({
'name': line.description + ' (' + line.description_dates + ')',
'invoice_id': invoice_id,
'reservation_ids': [(6, 0, [origin.id])],
'reservation_line_ids': [(6, 0, line.reservation_line_ids.ids)]
})
elif line.service_id:
vals.update({
'name': line.description,
'invoice_id': invoice_id,
'service_ids': [(6, 0, [origin.id])]
})
invoice_lines |= self.env['account.invoice.line'].create(vals)
return invoice_lines

View File

@@ -4,30 +4,67 @@
<field name="name">Invoice Orders</field>
<field name="model">folio.advance.payment.inv</field>
<field name="arch" type="xml">
<form string="Invoice Sales Order">
<p class="oe_grey">
Invoices will be created in draft so that you can review
them before validation.
</p>
<form string="Invoice Folio">
<group>
<field name="count" invisible="[('count','=',1)]" readonly="True"/>
<field name="advance_payment_method" class="oe_inline" widget="radio"
attrs="{'invisible': [('count','&gt;',1)]}"/>
<field name="product_id"
context="{'search_default_services': 1, 'default_type': 'service', 'default_invoice_policy': 'order'}" class="oe_inline"
attrs="{'invisible': 1}"/>
<label for="amount" attrs="{'invisible': [('advance_payment_method', 'not in', ('fixed','percentage'))]}"/>
<div attrs="{'invisible': [('advance_payment_method', 'not in', ('fixed','percentage'))]}">
<field name="amount"
attrs="{'required': [('advance_payment_method', 'in', ('fixed','percentage'))]}" class="oe_inline" widget="monetary"/>
<label string="%%"
attrs="{'invisible': [('advance_payment_method', '!=', 'percentage')]}" class="oe_inline"/>
</div>
<field name="deposit_account_id" 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>
<field name="partner_invoice_id" />
<field name="count" invisible="[('count','=',1)]" readonly="True"/>
<field name="advance_payment_method" class="oe_inline" widget="radio"
attrs="{'invisible': [('count','&gt;',1)]}"/>
<field name="product_id" string="Down Payment Product"
context="{'search_default_services': 1, 'default_type': 'service', 'default_invoice_policy': 'order'}" class="oe_inline"
attrs="{'invisible': 1}"/>
<label for="amount" attrs="{'invisible': [('advance_payment_method', 'not in', ('fixed','percentage'))]}"/>
<div attrs="{'invisible': [('advance_payment_method', 'not in', ('fixed','percentage'))]}">
<field name="amount"
attrs="{'required': [('advance_payment_method', 'in', ('fixed','percentage'))]}" class="oe_inline" widget="monetary"/>
<label string="%%"
attrs="{'invisible': [('advance_payment_method', '!=', 'percentage')]}" class="oe_inline"/>
</div>
<field name="deposit_account_id" 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="group_folios" string="Add Folios"/>
</group>
<field name="folio_ids" attrs="{'invisible': [('group_folios', '=', False)]}">
<tree string="Folios" editable="bottom"
decoration-danger="partner_invoice_id != parent.partner_invoice_id">
<field name="partner_invoice_id" />
<field name="name" />
<field name="state" />
<field name="pending_amount" />
<field name="amount_total" />
</tree>
</field>
<field name="reservation_ids" widget="many2many_tags"
domain="[('folio_id', 'in', folio_ids)]"/>
</group>
<group>
<field name="line_ids">
<tree string="Lines" editable="bottom">
<field name="product_id" invisible="1" />
<field name="price_room" invisible="1" />
<field name="reservation_id"
options="{'no_create': True,'no_open': True}"/>
<field name="service_id"
options="{'no_create': True,'no_open': True}"/>
<field name="description" />
<field name="description_dates" readonly="1" force_save="1"/>
<field name="reservation_line_ids"
widget="many2many_tags" string="Nights"
domain="[('reservation_id','=',reservation_id),
('price','=', price_room)]"
options="{'no_create': True,'no_open': True}"/>
<field name="qty" />
<field name="price_unit" />
<field name="discount" />
<field name="price_total" />
</tree>
</field>
</group>
<footer>
<button name="create_invoices" string="Create and View Invoices" type="object"