mirror of
https://github.com/OCA/pms.git
synced 2025-01-29 00:17:45 +02:00
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
"""
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
24
hotel/models/inherited_account_invoice_line.py
Normal file
24
hotel/models/inherited_account_invoice_line.py
Normal 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)
|
||||
|
||||
@@ -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="{"preview_image": "image_medium", "size": [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', '>', 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', '>', 0)]}"/> -->
|
||||
<!-- <field name="tax_id" widget="many2many_tags" domain="[('type_tax_use','=','sale'),('company_id','=',parent.company_id)]"
|
||||
attrs="{'readonly': [('qty_invoiced', '>', 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>
|
||||
|
||||
@@ -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','>',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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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','>',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','>',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"
|
||||
|
||||
Reference in New Issue
Block a user