[WIP] Invoice WorkFlow

This commit is contained in:
Dario Lodeiros
2019-01-10 15:21:10 +01:00
parent 8ba2051496
commit b2c8305b26
11 changed files with 666 additions and 727 deletions

View File

@@ -33,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
@@ -68,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)
@@ -148,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'),
@@ -160,10 +207,8 @@ class HotelFolio(models.Model):
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)]},
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')
@@ -212,12 +257,6 @@ class HotelFolio(models.Model):
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.multi
def compute_amount(self):
@@ -380,8 +419,9 @@ 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({
@@ -390,13 +430,18 @@ class HotelFolio(models.Model):
'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(
@@ -424,127 +469,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
@api.multi
def _prepare_invoice(self):
"""
Prepare the dict of values to create the new invoice for a sales order. This method may be
overridden to implement custom invoice generation (making sure to call super() to establish
a clean extension chain).
"""
self.ensure_one()
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.'))
import wdb; wdb.set_trace()
invoice_vals = {
'name': self.client_order_ref or '',
'origin': self.name,
'type': 'out_invoice',
'account_id': self.partner_invoice_id.property_account_receivable_id.id,
'partner_id': self.partner_invoice_id.id,
'partner_shipping_id': self.partner_id.id,
'journal_id': journal_id,
'currency_id': self.pricelist_id.currency_id.id,
'comment': self.note,
'payment_term_id': self.payment_term_id.id,
'fiscal_position_id': self.fiscal_position_id.id or self.partner_invoice_id.property_account_position_id.id,
'company_id': self.company_id.id,
'user_id': self.user_id and self.user_id.id,
'team_id': self.team_id.id
}
return invoice_vals
@api.multi
def action_invoice_create(self, grouped=False):
"""
Create the invoice associated to the Folio.
:param grouped: if True, invoices are grouped by Folio id. If False, invoices are grouped by
(partner_invoice_id, currency)
:returns: list of created invoices
"""
inv_obj = self.env['account.invoice']
precision = self.env['decimal.precision'].precision_get('Product Unit of Measure')
invoices = {}
references = {}
invoices_origin = {}
invoices_name = {}
for folio in self:
group_key = folio.id if grouped else (folio.partner_invoice_id.id, folio.currency_id.id)
for line in folio.room_lines.sorted(key=lambda l: l.qty_to_invoice < 0):
if float_is_zero(line.qty_to_invoice, precision_digits=precision):
continue
if group_key not in invoices:
inv_data = folio._prepare_invoice()
invoice = inv_obj.create(inv_data)
references[invoice] = folio
invoices[group_key] = invoice
invoices_origin[group_key] = [invoice.origin]
invoices_name[group_key] = [invoice.name]
elif group_key in invoices:
if folio.name not in invoices_origin[group_key]:
invoices_origin[group_key].append(folio.name)
if folio.client_order_ref and folio.client_order_ref not in invoices_name[group_key]:
invoices_name[group_key].append(folio.client_order_ref)
if line.qty_to_invoice > 0:
line.invoice_line_create(invoices[group_key].id, line.nights)
if references.get(invoices.get(group_key)):
if folio not in references[invoices[group_key]]:
references[invoices[group_key]] |= folio
for group_key in invoices:
invoices[group_key].write({'name': ', '.join(invoices_name[group_key]),
'origin': ', '.join(invoices_origin[group_key])})
if not invoices:
raise UserError(_('There is no invoiceable line.'))
for invoice in invoices.values():
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_untaxed < 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': references[invoice]},
subtype_id=self.env.ref('mail.mt_note').id)
return [inv.id for inv in invoices.values()]
'''
WORKFLOW STATE
'''

View File

@@ -77,6 +77,36 @@ class HotelReservation(models.Model):
return folio.room_lines[0].departure_hour
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 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.
"""
pass
#~ precision = self.env['decimal.precision'].precision_get('Product Unit of Measure')
#~ for line in self:
#~ if line.state not in ('confirm', 'done'):
#~ line.invoice_status = 'no'
#~ elif not float_is_zero(line.qty_to_invoice, precision_digits=precision):
#~ line.invoice_status = 'to invoice'
#~ elif line.state == 'sale' and line.product_id.invoice_policy == 'order' and\
#~ float_compare(line.qty_delivered, line.product_uom_qty, precision_digits=precision) == 1:
#~ line.invoice_status = 'upselling'
#~ elif float_compare(line.qty_invoiced, line.product_uom_qty, precision_digits=precision) >= 0:
#~ line.invoice_status = 'invoiced'
#~ else:
#~ line.invoice_status = 'no'
@api.model
@@ -170,7 +200,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,
@@ -237,13 +267,13 @@ class HotelReservation(models.Model):
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([
('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_ids = fields.Many2many('account.tax',
string='Taxes',
domain=['|', ('active', '=', False), ('active', '=', True)])
qty_to_invoice = fields.Float(
@@ -252,7 +282,7 @@ class HotelReservation(models.Model):
qty_invoiced = fields.Float(
compute='_get_invoice_qty', string='Invoiced', store=True, readonly=True,
digits=dp.get_precision('Product Unit of Measure'))
invoice_lines = fields.Many2many('account.invoice.line', 'sale_order_line_invoice_rel', 'order_line_id', 'invoice_line_id', string='Invoice Lines', copy=False)
invoice_line_ids = fields.Many2many('account.invoice.line', 'reservation_line_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',
@@ -765,7 +795,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.
@@ -775,7 +805,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'],
@@ -809,7 +839,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
@@ -1131,22 +1161,24 @@ class HotelReservation(models.Model):
"""
INVOICING PROCESS
"""
@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 in ['confirm', 'done']:
if line.room_type_id.product_id.invoice_policy == 'order':
line.qty_to_invoice = line.nights - line.qty_invoiced
else:
line.qty_to_invoice = line.qty_delivered - line.qty_invoiced
else:
line.qty_to_invoice = 0
pass
#~ for line in self:
#~ if line.folio_id.state in ['confirm', 'done']:
#~ if line.room_type_id.product_id.invoice_policy == 'order':
#~ line.qty_to_invoice = line.nights - line.qty_invoiced
#~ else:
#~ line.qty_to_invoice = line.qty_delivered - line.qty_invoiced
#~ else:
#~ line.qty_to_invoice = 0
@api.depends('invoice_lines.invoice_id.state', 'invoice_lines.quantity')
@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
@@ -1154,64 +1186,13 @@ class HotelReservation(models.Model):
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_lines:
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_uom)
elif invoice_line.invoice_id.type == 'out_refund':
qty_invoiced -= invoice_line.uom_id._compute_quantity(invoice_line.quantity, line.product_uom)
line.qty_invoiced = qty_invoiced
@api.multi
def _prepare_invoice_line(self, qty):
"""
Prepare the dict of values to create the new invoice line for a reservation.
:param qty: float quantity to invoice
"""
self.ensure_one()
res = {}
product = self.env['product.product'].browse(self.room_type_id.product_id.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 = self.folio_id.fiscal_position_id or self.folio_id.partner_id.property_account_position_id
if fpos:
account = fpos.map_account(account)
res = {
'name': self.name,
'sequence': self.sequence,
'origin': self.folio_id.name,
'account_id': account.id,
'price_unit': self.price_unit,
'quantity': qty,
'discount': self.discount,
'uom_id': self.product_uom.id,
'product_id': product.id or False,
'invoice_line_tax_ids': [(6, 0, self.tax_id.ids)],
'account_analytic_id': self.folio_id.analytic_account_id.id,
'analytic_tag_ids': [(6, 0, self.analytic_tag_ids.ids)],
}
return res
@api.multi
def invoice_line_create(self, invoice_id, qty):
""" Create an invoice line. The quantity to invoice can be positive (invoice) or negative (refund).
: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:
if not float_is_zero(qty, precision_digits=precision):
vals = line._prepare_invoice_line(qty=qty)
vals.update({'invoice_id': invoice_id, 'reservation_ids': [(6, 0, [line.id])]})
invoice_lines |= self.env['account.invoice.line'].create(vals)
return invoice_lines
pass
#~ 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_uom)
#~ elif invoice_line.invoice_id.type == 'out_refund':
#~ qty_invoiced -= invoice_line.uom_id._compute_quantity(invoice_line.quantity, line.product_uom)
#~ line.qty_invoiced = qty_invoiced

View File

@@ -44,7 +44,73 @@ class HotelService(models.Model):
(ids)], limit=1)
return False
name = fields.Char('Service description')
@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.
"""
pass
#~ for line in self:
#~ if line.folio_id.state in ['confirm', 'done']:
#~ if line.room_type_id.product_id.invoice_policy == 'order':
#~ line.qty_to_invoice = line.nights - line.qty_invoiced
#~ else:
#~ line.qty_to_invoice = line.qty_delivered - 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
"""
pass
#~ 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_uom)
#~ elif invoice_line.invoice_id.type == 'out_refund':
#~ qty_invoiced -= invoice_line.uom_id._compute_quantity(invoice_line.quantity, line.product_uom)
#~ 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.
"""
pass
#~ precision = self.env['decimal.precision'].precision_get('Product Unit of Measure')
#~ for line in self:
#~ if line.state not in ('sale', 'done'):
#~ line.invoice_status = 'no'
#~ elif not float_is_zero(line.qty_to_invoice, precision_digits=precision):
#~ line.invoice_status = 'to invoice'
#~ elif line.state == 'sale' and line.product_id.invoice_policy == 'order' and\
#~ float_compare(line.qty_delivered, line.product_uom_qty, precision_digits=precision) == 1:
#~ line.invoice_status = 'upselling'
#~ elif float_compare(line.qty_invoiced, line.product_uom_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')
ser_room_line = fields.Many2one('hotel.reservation', 'Room',
@@ -57,6 +123,12 @@ 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([
('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')
channel_type = fields.Selection([
('door', 'Door'),
('mail', 'Mail'),
@@ -67,6 +139,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,
@@ -130,7 +210,7 @@ class HotelService(models.Model):
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)
@@ -171,7 +251,7 @@ class HotelService(models.Model):
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 +275,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()

View File

@@ -33,7 +33,6 @@ class AccountInvoice(models.Model):
'domain': [('id', 'in', payment_ids)],
}
dif_customer_payment = fields.Boolean(compute='_compute_dif_customer_payment')
from_folio = fields.Boolean(compute='_compute_dif_customer_payment')
sale_ids = fields.Many2many(
'sale.order', 'sale_order_invoice_rel', 'invoice_id',
@@ -45,17 +44,11 @@ class AccountInvoice(models.Model):
@api.multi
def _compute_dif_customer_payment(self):
for inv in self:
return False
#~ sales = inv.mapped('invoice_line_ids.sale_line_ids.order_id')
#~ folios = self.env['hotel.folio'].search([('order_id.id','in',sales.ids)])
#~ 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
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)]
@api.multi
def action_invoice_open(self):

View File

@@ -11,3 +11,8 @@ class AccountInvoiceLine(models.Model):
'reservation_line_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)