[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

@@ -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):
@@ -20,9 +22,25 @@ class FolioAdvancePaymentInv(models.TransientModel):
@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):
folios = self.env['hotel.folio'].browse(self._context.get('active_ids', []))
return folios
@api.model
def _get_default_reservation(self):
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):
return self.env['hotel.folio'].browse(self._context.get('active_id', [])).partner_invoice_id
@api.model
def _default_deposit_account_id(self):
@@ -40,14 +58,25 @@ class FolioAdvancePaymentInv(models.TransientModel):
required=True)
count = fields.Integer(default=_count, string='# of Orders')
folio_ids = fields.Many2many("hotel.folio", string="Folios",
help="Folios grouped")
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")
view_detail = fields.Boolean('View Detail')
line_ids = fields.One2many('line.advance.inv',
'advance_inv_id',
string="Lines")
#Advance Payment
product_id = fields.Many2one('product.product', string='Down Payment Product',
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'),
@@ -107,7 +136,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 +147,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,
@@ -138,26 +166,31 @@ class FolioAdvancePaymentInv(models.TransientModel):
@api.multi
def create_invoices(self):
folios = self.env['hotel.folio'].browse(self._context.get('active_ids', []))
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':
folios.action_invoice_create()
elif self.advance_payment_method == 'all':
folios.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)
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':
@@ -165,26 +198,26 @@ 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)
self._create_invoice(folio, service_line, amount)
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,67 +229,184 @@ class FolioAdvancePaymentInv(models.TransientModel):
'taxes_id': [(6, 0, self.deposit_taxes_id.ids)],
}
@api.onchange('view_detail')
def prepare_reservation_invoice_lines(self):
@api.onchange('reservation_ids')
def prepare_invoice_lines(self):
vals = []
folios = self.env['hotel.folio'].browse(self._context.get('active_ids', []))
folios = self.folio_ids
invoice_lines = {}
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 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'):
if day.price == unit_price and day.discount == discount:
date_to = day.date
qty += 1
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 = (reservation.id, reservation.room_type_id.id, day.price + extra_price, day.discount)
date = fields.Date.from_string(day.date)
if group_key in invoice_lines:
invoice_lines[group_key][('qty')] += 1
if date == fields.Date.from_string(
invoice_lines[group_key][('date_to')]) + timedelta(days=1):
desc = invoice_lines[group_key][('description')]
invoice_lines[group_key][('description')] = \
desc.replace(desc[desc.rfind(" - "):], ' - ' + \
(date + timedelta(days=1)).strftime(DEFAULT_SERVER_DATE_FORMAT) + ')')
else:
invoice_lines[group_key][('description')] += \
' (' + date.strftime(DEFAULT_SERVER_DATE_FORMAT) + \
' - ' + (date + timedelta(days=1)).strftime(
DEFAULT_SERVER_DATE_FORMAT) + \
')'
invoice_lines[group_key][('date_to')] = day.date
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
}))
room_type_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
description = room_type_description + \
': (' + date.strftime(DEFAULT_SERVER_DATE_FORMAT) + \
' - ' + (date + timedelta(days=1)).strftime(DEFAULT_SERVER_DATE_FORMAT) + \
')'
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
),
'qty': 1,
'discount': day.discount,
'price_unit': day.price + extra_price,
'date_to': day.date
}
for group_key in invoice_lines:
vals.append((0, False, invoice_lines[group_key]))
self.line_ids = vals
@api.onchange('view_detail', 'folio_ids')
def onchange_folio_ids(self):
vals = []
folios = self.folio_ids
invoice_lines = {}
reservations = self.env['hotel.reservation']
services = self.env['hotel.service']
for folio in folios:
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"
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')
price_unit = 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')
description = fields.Text('Description')
reservation_id = fields.Many2one('hotel.reservation')
service_id = fields.Many2one('hotel.service')
@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 = self.reservation_id if self.reservation_id.id else self.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 = origin.folio_id.fiscal_position_id or origin.folio_id.partner_id.property_account_position_id
if fpos:
account = fpos.map_account(account)
vals = {
'name': line.description,
'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': origin.folio_id.analytic_account_id.id,
'analytic_tag_ids': [(6, 0, origin.analytic_tag_ids.ids)],
}
vals.update({'invoice_id': invoice_id, 'reservation_ids': [(6, 0, [origin.id])]})
invoice_lines |= self.env['account.invoice.line'].create(vals)
return invoice_lines

View File

@@ -4,47 +4,60 @@
<field name="name">Invoice Orders</field>
<field name="model">folio.advance.payment.inv</field>
<field name="arch" type="xml">
<form string="Invoice Sales Order">
<p class="oe_grey">
Invoices will be created in draft so that you can review
them before validation.
</p>
<form string="Invoice Folio">
<group>
<field name="count" invisible="[('count','=',1)]" readonly="True"/>
<field name="advance_payment_method" class="oe_inline" widget="radio"
attrs="{'invisible': [('count','&gt;',1)]}"/>
<field name="product_id"
context="{'search_default_services': 1, 'default_type': 'service', 'default_invoice_policy': 'order'}" class="oe_inline"
attrs="{'invisible': 1}"/>
<label for="amount" attrs="{'invisible': [('advance_payment_method', 'not in', ('fixed','percentage'))]}"/>
<div attrs="{'invisible': [('advance_payment_method', 'not in', ('fixed','percentage'))]}">
<field name="amount"
attrs="{'required': [('advance_payment_method', 'in', ('fixed','percentage'))]}" class="oe_inline" widget="monetary"/>
<label string="%%"
attrs="{'invisible': [('advance_payment_method', '!=', 'percentage')]}" class="oe_inline"/>
</div>
<field name="deposit_account_id" class="oe_inline"
attrs="{'invisible': ['|', ('advance_payment_method', 'not in', ('fixed', 'percentage')), ('product_id', '!=', False)]}" groups="account.group_account_manager"/>
<field name="deposit_taxes_id" class="oe_inline" widget="many2many_tags"
domain="[('type_tax_use','=','sale')]"
attrs="{'invisible': ['|', ('advance_payment_method', 'not in', ('fixed', 'percentage')), ('product_id', '!=', False)]}"/>
<group>
<field name="partner_invoice_id" />
<field name="count" invisible="[('count','=',1)]" readonly="True"/>
<field name="advance_payment_method" class="oe_inline" widget="radio"
attrs="{'invisible': [('count','&gt;',1)]}"/>
<field name="product_id" string="Down Payment Product"
context="{'search_default_services': 1, 'default_type': 'service', 'default_invoice_policy': 'order'}" class="oe_inline"
attrs="{'invisible': 1}"/>
<label for="amount" attrs="{'invisible': [('advance_payment_method', 'not in', ('fixed','percentage'))]}"/>
<div attrs="{'invisible': [('advance_payment_method', 'not in', ('fixed','percentage'))]}">
<field name="amount"
attrs="{'required': [('advance_payment_method', 'in', ('fixed','percentage'))]}" class="oe_inline" widget="monetary"/>
<label string="%%"
attrs="{'invisible': [('advance_payment_method', '!=', 'percentage')]}" class="oe_inline"/>
</div>
<field name="deposit_account_id" class="oe_inline"
attrs="{'invisible': ['|', ('advance_payment_method', 'not in', ('fixed', 'percentage')), ('product_id', '!=', False)]}" groups="account.group_account_manager"/>
<field name="deposit_taxes_id" class="oe_inline" widget="many2many_tags"
domain="[('type_tax_use','=','sale')]"
attrs="{'invisible': ['|', ('advance_payment_method', 'not in', ('fixed', 'percentage')), ('product_id', '!=', False)]}"/>
</group>
<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="group_folios" />
<field name="view_detail" />
</group>
<group>
<field name="line_ids" attrs="{'invisible': [('view_detail', '=', False)]}">
<field name="line_ids" attrs="{'invisible': [('view_detail', '=', False)]}"
nolabel="1">
<tree string="Lines" editable="bottom">
<field name="room_type_id" />
<field name="date_from" />
<field name="date_to" />
<field name="product_id" invisible="1"/>
<field name="reservation_id" />
<field name="description" />
<field name="qty" />
<field name="discount" />
<field name="unit_price" />
<field name="price_unit" />
</tree>
</field>
</group>
<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"
context="{'open_invoices': True}" class="btn-primary"/>
<button name="create_invoices" string="Create Invoices" type="object"