[WIP] workflow invoices

This commit is contained in:
Dario Lodeiros
2019-01-16 10:45:03 +01:00
parent b2c8305b26
commit b8b110aed1
8 changed files with 172 additions and 64 deletions

View File

@@ -257,10 +257,32 @@ class HotelFolio(models.Model):
for record in self:
record.rooms_char = ', '.join(record.mapped('room_lines.room_id.name'))
# @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):

View File

@@ -1,7 +1,6 @@
# 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
@@ -14,6 +13,7 @@ from odoo.tools import (
DEFAULT_SERVER_DATETIME_FORMAT)
from odoo import models, fields, api, _
from odoo.addons import decimal_precision as dp
import logging
_logger = logging.getLogger(__name__)
@@ -168,6 +168,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([
@@ -333,11 +334,13 @@ 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': vals.get('folio_id'),
}))
}
res.update(self.env['hotel.service']._prepare_add_missing_fields(res))
board_services.append((0, False, res))
vals.update({'service_ids': board_services})
if self.compute_price_out_vals(vals):
days_diff = (
@@ -382,18 +385,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,
@@ -457,7 +457,7 @@ class HotelReservation(models.Model):
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()
@@ -625,7 +625,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)
@@ -633,6 +636,7 @@ 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):
@@ -670,18 +674,20 @@ 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})
for service in self.service_ids.filtered(lambda r: r.is_board_service == True):
@@ -1162,6 +1168,29 @@ class HotelReservation(models.Model):
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):
"""

View File

@@ -206,7 +206,7 @@ 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):

View File

@@ -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,7 +106,7 @@
</span>
<span class="o_stat_text">Refunds</span>
</div>
</button> -->
</button>
<button type="object" class="oe_stat_button" id="invoice_button"
icon="fa-pencil-square-o" name="open_invoices_folio"

View File

@@ -18,10 +18,10 @@
<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" />
<field name="tax_ids" invisible="1"/>
<field name="has_checkout_to_send" invisible="1" />
<button name="send_reservation_mail" type="object" string="Send Confirmation Email"
attrs="{'invisible': [('has_confirmed_reservations_to_send', '=', False)]}" class="oe_highlight"/>
@@ -99,6 +99,15 @@
<span class="o_stat_text">Books</span>
</div>
</button>
<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_value">
<field name="invoice_count"/>
</span>
<span class="o_stat_text">Invoices</span>
</div>
</button>
<button type="object" class="oe_stat_button"
id="open_master"
icon="fa-chain-broken"

View File

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

View File

@@ -27,20 +27,29 @@ class FolioAdvancePaymentInv(models.TransientModel):
@api.model
def _get_default_folio(self):
folios = self.env['hotel.folio'].browse(self._context.get('active_ids', []))
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):
folios = self._get_default_folio()
reservations = self.env['hotel.reservation']
for folio in folios:
reservations |= folio.room_lines
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):
return self.env['hotel.folio'].browse(self._context.get('active_id', [])).partner_invoice_id
folios = self._get_default_folio()
return folios[0].partner_invoice_id
@api.model
def _default_deposit_account_id(self):
@@ -72,9 +81,6 @@ class FolioAdvancePaymentInv(models.TransientModel):
'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="Product",
domain=[('type', '=', 'service')], default=_default_product_id)
@@ -215,7 +221,24 @@ class FolioAdvancePaymentInv(models.TransientModel):
'tax_id': [(6, 0, tax_ids)],
})
del context
self._create_invoice(folio, service_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 folios.open_invoices_folio()
return {'type': 'ir.actions.act_window_close'}
@@ -307,11 +330,12 @@ class FolioAdvancePaymentInv(models.TransientModel):
invoice_lines = {}
reservations = self.env['hotel.reservation']
services = self.env['hotel.service']
for folio in folios:
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.reservation_ids |= reservations
self.prepare_invoice_lines()
@api.model
@@ -369,6 +393,12 @@ class LineAdvancePaymentInv(models.TransientModel):
description = fields.Text('Description')
reservation_id = fields.Many2one('hotel.reservation')
service_id = fields.Many2one('hotel.service')
folio_id = fields.Many2one('hotel.folio', compute='_compute_folio_id')
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.multi
def invoice_line_create(self, invoice_id, qty):
@@ -380,7 +410,7 @@ class LineAdvancePaymentInv(models.TransientModel):
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
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
@@ -388,7 +418,7 @@ class LineAdvancePaymentInv(models.TransientModel):
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
fpos = line.folio_id.fiscal_position_id or line.folio_id.partner_id.property_account_position_id
if fpos:
account = fpos.map_account(account)
@@ -403,10 +433,13 @@ class LineAdvancePaymentInv(models.TransientModel):
'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,
'account_analytic_id': line.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])]})
if line.reservation_id:
vals.update({'invoice_id': invoice_id, 'reservation_ids': [(6, 0, [origin.id])]})
elif line.service_id:
vals.update({'invoice_id': invoice_id, 'service_ids': [(6, 0, [origin.id])]})
invoice_lines |= self.env['account.invoice.line'].create(vals)
return invoice_lines

View File

@@ -50,6 +50,7 @@
<tree string="Lines" editable="bottom">
<field name="product_id" invisible="1"/>
<field name="reservation_id" />
<field name="service_id" />
<field name="description" />
<field name="qty" />
<field name="discount" />