[WIP] Invoice WorkFlow

This commit is contained in:
Dario Lodeiros
2019-01-03 19:03:57 +01:00
parent 12ca208b59
commit 8ba2051496
9 changed files with 357 additions and 47 deletions

View File

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

View File

@@ -11,6 +11,8 @@ from dateutil.relativedelta import relativedelta
from odoo.exceptions import except_orm, UserError, ValidationError from odoo.exceptions import except_orm, UserError, ValidationError
from odoo.tools import ( from odoo.tools import (
misc, misc,
float_is_zero,
float_compare,
DEFAULT_SERVER_DATETIME_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT,
DEFAULT_SERVER_DATE_FORMAT) DEFAULT_SERVER_DATE_FORMAT)
from odoo import models, fields, api, _ from odoo import models, fields, api, _
@@ -44,9 +46,14 @@ class HotelFolio(models.Model):
def _amount_all(self): def _amount_all(self):
pass pass
@api.model
def _get_default_team(self):
return self.env['crm.team']._get_default_team_id()
#Main Fields-------------------------------------------------------- #Main Fields--------------------------------------------------------
name = fields.Char('Folio Number', readonly=True, index=True, name = fields.Char('Folio Number', readonly=True, index=True,
default=lambda self: _('New')) default=lambda self: _('New'))
client_order_ref = fields.Char(string='Customer Reference', copy=False)
partner_id = fields.Many2one('res.partner', partner_id = fields.Many2one('res.partner',
track_visibility='onchange') track_visibility='onchange')
@@ -87,6 +94,7 @@ class HotelFolio(models.Model):
required=True, readonly=True, index=True, required=True, readonly=True, index=True,
states={'draft': [('readonly', False)], 'sent': [('readonly', False)]}, states={'draft': [('readonly', False)], 'sent': [('readonly', False)]},
copy=False, default=fields.Datetime.now) 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([ state = fields.Selection([
('draft', 'Quotation'), ('draft', 'Quotation'),
('sent', 'Quotation Sent'), ('sent', 'Quotation Sent'),
@@ -111,6 +119,7 @@ class HotelFolio(models.Model):
readonly=True) readonly=True)
return_ids = fields.One2many('payment.return', 'folio_id', return_ids = fields.One2many('payment.return', 'folio_id',
readonly=True) readonly=True)
payment_term_id = fields.Many2one('account.payment.term', string='Payment Terms', oldname='payment_term')
#Amount Fields------------------------------------------------------ #Amount Fields------------------------------------------------------
pending_amount = fields.Monetary(compute='compute_amount', pending_amount = fields.Monetary(compute='compute_amount',
@@ -150,12 +159,13 @@ class HotelFolio(models.Model):
string='Invoice Status', string='Invoice Status',
compute='_compute_invoice_status', compute='_compute_invoice_status',
store=True, readonly=True, default='no') store=True, readonly=True, default='no')
#~ partner_invoice_id = fields.Many2one('res.partner', partner_invoice_id = fields.Many2one('res.partner',
#~ string='Invoice Address', string='Invoice Address',
#~ readonly=True, required=True, readonly=True, required=True,
#~ states={'draft': [('readonly', False)], states={'draft': [('readonly', False)],
#~ 'sent': [('readonly', False)]}, 'sent': [('readonly', False)]},
#~ help="Invoice address for current sales order.") help="Invoice address for current sales order.")
fiscal_position_id = fields.Many2one('account.fiscal.position', oldname='fiscal_position', string='Fiscal Position')
#WorkFlow Mail Fields----------------------------------------------- #WorkFlow Mail Fields-----------------------------------------------
has_confirmed_reservations_to_send = fields.Boolean( has_confirmed_reservations_to_send = fields.Boolean(
@@ -179,6 +189,7 @@ class HotelFolio(models.Model):
client_order_ref = fields.Char(string='Customer Reference', copy=False) client_order_ref = fields.Char(string='Customer Reference', copy=False)
note = fields.Text('Terms and conditions') note = fields.Text('Terms and conditions')
sequence = fields.Integer(string='Sequence', default=10) 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') @api.depends('room_lines.price_total','service_ids.price_total')
def _amount_all(self): def _amount_all(self):
@@ -356,7 +367,7 @@ class HotelFolio(models.Model):
if any(f not in vals for f in lfields): if any(f not in vals for f in lfields):
partner = self.env['res.partner'].browse(vals.get('partner_id')) partner = self.env['res.partner'].browse(vals.get('partner_id'))
addr = partner.address_get(['delivery', 'invoice']) 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( vals['pricelist_id'] = vals.setdefault(
'pricelist_id', 'pricelist_id',
partner.property_product_pricelist and partner.property_product_pricelist.id) partner.property_product_pricelist and partner.property_product_pricelist.id)
@@ -373,11 +384,11 @@ class HotelFolio(models.Model):
- user_id - user_id
""" """
if not self.partner_id: if not self.partner_id:
#~ self.update({ self.update({
#~ 'partner_invoice_id': False, 'partner_invoice_id': False,
#~ 'payment_term_id': False, 'payment_term_id': False,
#~ 'fiscal_position_id': False, 'fiscal_position_id': False,
#~ }) })
return return
addr = self.partner_id.address_get(['invoice']) addr = self.partner_id.address_get(['invoice'])
pricelist = self.partner_id.property_product_pricelist and \ pricelist = self.partner_id.property_product_pricelist and \
@@ -438,6 +449,102 @@ class HotelFolio(models.Model):
def advance_invoice(self): def advance_invoice(self):
pass 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 WORKFLOW STATE
''' '''
@@ -483,7 +590,21 @@ class HotelFolio(models.Model):
@api.multi @api.multi
def action_confirm(self): def action_confirm(self):
_logger.info('action_confirm') for folio in self.filtered(lambda folio: folio.partner_id not in folio.message_partner_ids):
folio.message_subscribe([folio.partner_id.id])
self.write({
'state': 'confirm',
'confirmation_date': fields.Datetime.now()
})
#~ if self.env.context.get('send_email'):
#~ self.force_quotation_send()
# create an analytic account if at least an expense product
#~ if any([expense_policy != 'no' for expense_policy in self.order_line.mapped('product_id.expense_policy')]):
#~ if not self.analytic_account_id:
#~ self._create_analytic_account()
return True
""" """

View File

@@ -8,6 +8,8 @@ from lxml import etree
from odoo.exceptions import UserError, ValidationError from odoo.exceptions import UserError, ValidationError
from odoo.tools import ( from odoo.tools import (
misc, misc,
float_is_zero,
float_compare,
DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATE_FORMAT,
DEFAULT_SERVER_DATETIME_FORMAT) DEFAULT_SERVER_DATETIME_FORMAT)
from odoo import models, fields, api, _ from odoo import models, fields, api, _
@@ -75,6 +77,7 @@ class HotelReservation(models.Model):
return folio.room_lines[0].departure_hour return folio.room_lines[0].departure_hour
else: else:
return default_departure_hour return default_departure_hour
@api.model @api.model
def name_search(self, name='', args=None, operator='ilike', limit=100): def name_search(self, name='', args=None, operator='ilike', limit=100):
@@ -115,6 +118,7 @@ class HotelReservation(models.Model):
).days ).days
name = fields.Text('Reservation Description', required=True) name = fields.Text('Reservation Description', required=True)
sequence = fields.Integer(string='Sequence', default=10)
room_id = fields.Many2one('hotel.room', string='Room') room_id = fields.Many2one('hotel.room', string='Room')
@@ -230,8 +234,6 @@ 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) # 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_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 = 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', currency_id = fields.Many2one('res.currency',
related='pricelist_id.currency_id', related='pricelist_id.currency_id',
string='Currency', readonly=True, required=True) string='Currency', readonly=True, required=True)
@@ -244,12 +246,13 @@ class HotelReservation(models.Model):
tax_id = fields.Many2many('account.tax', tax_id = fields.Many2many('account.tax',
string='Taxes', string='Taxes',
domain=['|', ('active', '=', False), ('active', '=', True)]) domain=['|', ('active', '=', False), ('active', '=', True)])
# qty_to_invoice = fields.Float( qty_to_invoice = fields.Float(
# string='To Invoice', store=True, readonly=True, compute='_get_to_invoice_qty', string='To Invoice', store=True, readonly=True,
# digits=dp.get_precision('Product Unit of Measure')) digits=dp.get_precision('Product Unit of Measure'))
# qty_invoiced = fields.Float( qty_invoiced = fields.Float(
# compute='_get_invoice_qty', string='Invoiced', store=True, readonly=True, compute='_get_invoice_qty', string='Invoiced', store=True, readonly=True,
# digits=dp.get_precision('Product Unit of Measure')) 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)
# qty_delivered = fields.Float(string='Delivered', copy=False, digits=dp.get_precision('Product Unit of Measure'), default=0.0) # 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) # qty_delivered_updateable = fields.Boolean(compute='_compute_qty_delivered_updateable', string='Can Edit Delivered', readonly=True, default=True)
price_subtotal = fields.Monetary(string='Subtotal', price_subtotal = fields.Monetary(string='Subtotal',
@@ -275,7 +278,7 @@ class HotelReservation(models.Model):
# FIXME discount per night # FIXME discount per night
discount = fields.Float(string='Discount (%)', digits=dp.get_precision('Discount'), default=0.0) 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 @api.model
def create(self, vals): def create(self, vals):
@@ -1124,3 +1127,91 @@ class HotelReservation(models.Model):
@api.multi @api.multi
def send_cancel_mail(self): def send_cancel_mail(self):
return self.folio_id.send_cancel_mail() return self.folio_id.send_cancel_mail()
"""
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
@api.depends('invoice_lines.invoice_id.state', 'invoice_lines.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_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

View File

@@ -17,6 +17,8 @@ class HotelReservationLine(models.Model):
discount = fields.Float( discount = fields.Float(
string='Discount (%)', string='Discount (%)',
digits=dp.get_precision('Discount'), default=0.0) digits=dp.get_precision('Discount'), default=0.0)
invoiced = fields.Boolean('Invoiced')
@api.constrains('date') @api.constrains('date')
def constrains_duplicated_date(self): def constrains_duplicated_date(self):

View File

@@ -45,16 +45,17 @@ class AccountInvoice(models.Model):
@api.multi @api.multi
def _compute_dif_customer_payment(self): def _compute_dif_customer_payment(self):
for inv in self: for inv in self:
sales = inv.mapped('invoice_line_ids.sale_line_ids.order_id') return False
folios = self.env['hotel.folio'].search([('order_id.id','in',sales.ids)]) #~ sales = inv.mapped('invoice_line_ids.sale_line_ids.order_id')
if folios: #~ folios = self.env['hotel.folio'].search([('order_id.id','in',sales.ids)])
inv.from_folio = True #~ if folios:
inv.folio_ids = [(6, 0, folios.ids)] #~ inv.from_folio = True
payments_obj = self.env['account.payment'] #~ inv.folio_ids = [(6, 0, folios.ids)]
payments = payments_obj.search([('folio_id','in',folios.ids)]) #~ payments_obj = self.env['account.payment']
for pay in payments: #~ payments = payments_obj.search([('folio_id','in',folios.ids)])
if pay.partner_id != inv.partner_id: #~ for pay in payments:
inv.dif_customer_payment = True #~ if pay.partner_id != inv.partner_id:
#~ inv.dif_customer_payment = True
@api.multi @api.multi
def action_invoice_open(self): def action_invoice_open(self):

View File

@@ -0,0 +1,13 @@
# 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_line_invoice_rel',
'invoice_line_id', 'reservation_id',
string='Reservations', readonly=True, copy=False)

View File

@@ -20,9 +20,9 @@
attrs="{'invisible': [('has_cancelled_reservations_to_send', '=', False)]}" class="oe_highlight"/> --> attrs="{'invisible': [('has_cancelled_reservations_to_send', '=', False)]}" class="oe_highlight"/> -->
<!-- <button name="send_exit_mail" type="object" string="Send Exit Email" <!-- <button name="send_exit_mail" type="object" string="Send Exit Email"
attrs="{'invisible': [('has_checkout_to_send', '=', False)]}" class="oe_highlight"/> --> 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" 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" <!-- <button name="action_cancel_draft" states="cancel,sale" string="Set to Draft"
type="object" icon="fa-undo" class="oe_highlight" /> --> type="object" icon="fa-undo" class="oe_highlight" /> -->
<button name="action_cancel" string="Cancel Folio" states="sale" <button name="action_cancel" string="Cancel Folio" states="sale"
@@ -132,10 +132,12 @@
<field name="email" placeholder="email"/> <field name="email" placeholder="email"/>
<field name="mobile" placeholder="mobile"/> <field name="mobile" placeholder="mobile"/>
<field name="phone" /> <field name="phone" />
<field name="partner_invoice_id" />
<field name="cancelled_reason" attrs="{'invisible':[('state','not in',('cancel'))]}"/> <field name="cancelled_reason" attrs="{'invisible':[('state','not in',('cancel'))]}"/>
</group> </group>
<group> <group>
<field name="pricelist_id" domain="[('type','=','sale')]" /> <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="company_id" options="{'no_create': True}" groups="base.group_multi_company"/>
<field name="reservation_type" attrs="{'readonly':[('state','not in',('draft'))]}"/> <field name="reservation_type" attrs="{'readonly':[('state','not in',('draft'))]}"/>
<field name="channel_type" attrs="{'required':[('reservation_type','=','normal')]}"/> <field name="channel_type" attrs="{'required':[('reservation_type','=','normal')]}"/>

View File

@@ -16,14 +16,7 @@ class FolioAdvancePaymentInv(models.TransientModel):
@api.model @api.model
def _get_advance_payment_method(self): def _get_advance_payment_method(self):
if self._count() == 1: return 'all'
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'
@api.model @api.model
def _default_product_id(self): def _default_product_id(self):
@@ -40,15 +33,22 @@ class FolioAdvancePaymentInv(models.TransientModel):
return self._default_product_id().taxes_id return self._default_product_id().taxes_id
advance_payment_method = fields.Selection([ advance_payment_method = fields.Selection([
('delivered', 'Invoiceable lines'),
('all', 'Invoiceable lines (deduct down payments)'), ('all', 'Invoiceable lines (deduct down payments)'),
('percentage', 'Down payment (percentage)'), ('percentage', 'Down payment (percentage)'),
('fixed', 'Down payment (fixed amount)') ('fixed', 'Down payment (fixed amount)')
], string='What do you want to invoice?', default=_get_advance_payment_method, ], string='What do you want to invoice?', default=_get_advance_payment_method,
required=True) required=True)
count = fields.Integer(default=_count, string='# of Orders')
folio_ids = fields.Many2many("hotel.folio", string="Folios",
help="Folios grouped")
group_folios = fields.Boolean('Group Folios')
line_ids = fields.One2many('line.advance.inv',
'advance_inv_id',
string="Invoice Lines")
view_detail = fields.Boolean('View Detail')
#Advance Payment
product_id = fields.Many2one('product.product', string='Down Payment Product', product_id = fields.Many2one('product.product', string='Down Payment Product',
domain=[('type', '=', 'service')], default=_default_product_id) domain=[('type', '=', 'service')], default=_default_product_id)
count = fields.Integer(default=_count, string='# of Orders')
amount = fields.Float('Down Payment Amount', amount = fields.Float('Down Payment Amount',
digits=dp.get_precision('Account'), digits=dp.get_precision('Account'),
help="The amount to be invoiced in advance, taxes excluded.") help="The amount to be invoiced in advance, taxes excluded.")
@@ -139,12 +139,11 @@ class FolioAdvancePaymentInv(models.TransientModel):
@api.multi @api.multi
def create_invoices(self): def create_invoices(self):
folios = self.env['hotel.folio'].browse(self._context.get('active_ids', [])) folios = self.env['hotel.folio'].browse(self._context.get('active_ids', []))
sale_orders = self.env['sale.order'].browse(folios.mapped('order_id.id'))
if self.advance_payment_method == 'delivered': if self.advance_payment_method == 'delivered':
sale_orders.action_invoice_create() folios.action_invoice_create()
elif self.advance_payment_method == 'all': elif self.advance_payment_method == 'all':
sale_orders.action_invoice_create(final=True) folios.action_invoice_create()
else: else:
# Create deposit product if necessary # Create deposit product if necessary
if not self.product_id: if not self.product_id:
@@ -196,3 +195,68 @@ class FolioAdvancePaymentInv(models.TransientModel):
'property_account_income_id': self.deposit_account_id.id, 'property_account_income_id': self.deposit_account_id.id,
'taxes_id': [(6, 0, self.deposit_taxes_id.ids)], 'taxes_id': [(6, 0, self.deposit_taxes_id.ids)],
} }
@api.onchange('view_detail')
def prepare_reservation_invoice_lines(self):
vals = []
folios = self.env['hotel.folio'].browse(self._context.get('active_ids', []))
for folio in folios:
folio_name = folio.name
for reservation in folio.room_lines:
reservation_name = reservation.name
unit_price = False
discount = False
qty = 0
for day in reservation.reservation_line_ids.sorted('date'):
if day.price == unit_price and day.discount == discount:
date_to = day.date
qty += 1
else:
if unit_price:
vals.append((0, False, {
'date_from': date_from,
'date_to': date_to,
'room_type_id': reservation.room_type_id,
'product_id': self.env['product.product'].browse(
reservation.room_type_id.product_id.id
),
'qty': qty,
'discount': discount,
'unit_price': unit_price
}))
qty = 1
unit_price = day.price
date_from = day.date
date_to = day.date
vals.append((0, False, {
'date_from': date_from,
'date_to': date_to,
'room_type_id': reservation.room_type_id,
'product_id': self.env['product.product'].browse(
reservation.room_type_id.product_id.id
),
'qty': qty,
'discount': discount,
'unit_price': unit_price
}))
self.line_ids = vals
class LineAdvancePaymentInv(models.TransientModel):
_name = "line.advance.inv"
_description = "Lines Advance Invoice"
date_from = fields.Date('From')
date_to = fields.Date('To')
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')
unit_price = fields.Float('Price')
advance_inv_id = fields.Many2one('folio.advance.payment.inv')
discount = fields.Float(
string='Discount (%)',
digits=dp.get_precision('Discount'), default=0.0)
to_invoice = fields.Boolean('To Invoice')

View File

@@ -28,8 +28,23 @@
<field name="deposit_taxes_id" class="oe_inline" widget="many2many_tags" <field name="deposit_taxes_id" class="oe_inline" widget="many2many_tags"
domain="[('type_tax_use','=','sale')]" domain="[('type_tax_use','=','sale')]"
attrs="{'invisible': ['|', ('advance_payment_method', 'not in', ('fixed', 'percentage')), ('product_id', '!=', False)]}"/> attrs="{'invisible': ['|', ('advance_payment_method', 'not in', ('fixed', 'percentage')), ('product_id', '!=', False)]}"/>
<field name="view_detail" />
</group>
<group>
<field name="line_ids" attrs="{'invisible': [('view_detail', '=', False)]}">
<tree string="Lines" editable="bottom">
<field name="room_type_id" />
<field name="date_from" />
<field name="date_to" />
<field name="qty" />
<field name="discount" />
<field name="unit_price" />
</tree>
</field>
</group> </group>
<footer> <footer>
<button name="prepare_reservation_invoice_lines" string="Prepare Lines" type="object"
class="btn-primary"/>
<button name="create_invoices" string="Create and View Invoices" type="object" <button name="create_invoices" string="Create and View Invoices" type="object"
context="{'open_invoices': True}" class="btn-primary"/> context="{'open_invoices': True}" class="btn-primary"/>
<button name="create_invoices" string="Create Invoices" type="object" <button name="create_invoices" string="Create Invoices" type="object"