mirror of
https://github.com/OCA/pms.git
synced 2025-01-29 00:17:45 +02:00
[WIP] Invoice WorkFlow
This commit is contained in:
@@ -228,7 +228,6 @@ class HotelFolio(models.Model):
|
|||||||
'Prepaid Warning Days',
|
'Prepaid Warning Days',
|
||||||
help='Margin in days to create a notice if a payment \
|
help='Margin in days to create a notice if a payment \
|
||||||
advance has not been recorded')
|
advance has not been recorded')
|
||||||
rooms_char = fields.Char('Rooms', compute='_computed_rooms_char')
|
|
||||||
segmentation_ids = fields.Many2many('res.partner.category',
|
segmentation_ids = fields.Many2many('res.partner.category',
|
||||||
string='Segmentation')
|
string='Segmentation')
|
||||||
client_order_ref = fields.Char(string='Customer Reference', copy=False)
|
client_order_ref = fields.Char(string='Customer Reference', copy=False)
|
||||||
@@ -253,10 +252,6 @@ class HotelFolio(models.Model):
|
|||||||
'amount_total': amount_untaxed + amount_tax,
|
'amount_total': amount_untaxed + amount_tax,
|
||||||
})
|
})
|
||||||
|
|
||||||
def _computed_rooms_char(self):
|
|
||||||
for record in self:
|
|
||||||
record.rooms_char = ', '.join(record.mapped('room_lines.room_id.name'))
|
|
||||||
|
|
||||||
@api.depends('amount_total', 'payment_ids', 'return_ids')
|
@api.depends('amount_total', 'payment_ids', 'return_ids')
|
||||||
@api.multi
|
@api.multi
|
||||||
def compute_amount(self):
|
def compute_amount(self):
|
||||||
|
|||||||
@@ -4,11 +4,21 @@
|
|||||||
from odoo import models, fields, api, _
|
from odoo import models, fields, api, _
|
||||||
from odoo.addons import decimal_precision as dp
|
from odoo.addons import decimal_precision as dp
|
||||||
from odoo.exceptions import ValidationError
|
from odoo.exceptions import ValidationError
|
||||||
|
from datetime import date
|
||||||
|
|
||||||
class HotelReservationLine(models.Model):
|
class HotelReservationLine(models.Model):
|
||||||
_name = "hotel.reservation.line"
|
_name = "hotel.reservation.line"
|
||||||
_order = "date"
|
_order = "date"
|
||||||
|
|
||||||
|
@api.multi
|
||||||
|
def name_get(self):
|
||||||
|
result = []
|
||||||
|
for res in self:
|
||||||
|
date = fields.Date.from_string(res.date)
|
||||||
|
name = u'%s/%s' % (date.day, date.month)
|
||||||
|
result.append((res.id, name))
|
||||||
|
return result
|
||||||
|
|
||||||
reservation_id = fields.Many2one('hotel.reservation', string='Reservation',
|
reservation_id = fields.Many2one('hotel.reservation', string='Reservation',
|
||||||
ondelete='cascade', required=True,
|
ondelete='cascade', required=True,
|
||||||
copy=False)
|
copy=False)
|
||||||
|
|||||||
@@ -201,6 +201,7 @@
|
|||||||
</sheet>
|
</sheet>
|
||||||
<div class="oe_chatter">
|
<div class="oe_chatter">
|
||||||
<field name="message_follower_ids" widget="mail_followers"/>
|
<field name="message_follower_ids" widget="mail_followers"/>
|
||||||
|
<field name="activity_ids" widget="mail_activity"/>
|
||||||
<field name="message_ids" widget="mail_thread"/>
|
<field name="message_ids" widget="mail_thread"/>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
@@ -219,7 +220,7 @@
|
|||||||
<field name="name"/>
|
<field name="name"/>
|
||||||
<field name="partner_id" select="1"/>
|
<field name="partner_id" select="1"/>
|
||||||
<field name="date_order" select="1"/>
|
<field name="date_order" select="1"/>
|
||||||
<field name="rooms_char" string="Rooms"/>
|
<field name="room_lines" widget="many2many_tags" />
|
||||||
<field name="amount_total" sum="Total amount"/>
|
<field name="amount_total" sum="Total amount"/>
|
||||||
</tree>
|
</tree>
|
||||||
</field>
|
</field>
|
||||||
|
|||||||
@@ -195,10 +195,13 @@
|
|||||||
<!-- TODO: How to filter to avoid show False (generic) pricelist board when exist a specific pricelist board¿? -->
|
<!-- TODO: How to filter to avoid show False (generic) pricelist board when exist a specific pricelist board¿? -->
|
||||||
<field name="board_service_room_id" domain="[
|
<field name="board_service_room_id" domain="[
|
||||||
('hotel_room_type_id', '=', room_type_id),
|
('hotel_room_type_id', '=', room_type_id),
|
||||||
('pricelist_id', 'in', [pricelist_id, False])]" />
|
('pricelist_id', 'in', [pricelist_id, False])]"
|
||||||
|
options="{'no_create': True,'no_open': True}" />
|
||||||
<field name="name"/>
|
<field name="name"/>
|
||||||
<field name="adults"/>
|
<field name="adults"/>
|
||||||
<field name="children"/>
|
<field name="children"/>
|
||||||
|
<field name="qty_invoiced" />
|
||||||
|
<field name="qty_to_invoice" />
|
||||||
<field name="room_type_id" on_change="1" options="{'no_create': True,'no_open': True}"
|
<field name="room_type_id" on_change="1" options="{'no_create': True,'no_open': True}"
|
||||||
attrs="{'readonly':[('state','not in',('draft'))]}"/>
|
attrs="{'readonly':[('state','not in',('draft'))]}"/>
|
||||||
<field name="channel_type" attrs="{'required':[('reservation_type','not in',('staff','out'))]}"/>
|
<field name="channel_type" attrs="{'required':[('reservation_type','not in',('staff','out'))]}"/>
|
||||||
@@ -259,7 +262,7 @@
|
|||||||
attrs = "{'required': [('per_day','=',True)]}" />
|
attrs = "{'required': [('per_day','=',True)]}" />
|
||||||
<field name="product_id"
|
<field name="product_id"
|
||||||
domain="[('sale_ok', '=', True)]"
|
domain="[('sale_ok', '=', True)]"
|
||||||
options="{'create': False, 'create_edit': False}" />
|
options="{'no_create': True,'no_open': True}" />
|
||||||
<field name="name"/>
|
<field name="name"/>
|
||||||
<field name="product_qty" attrs="{'readonly': [('per_day','=',True)]}" force_save="1"/>
|
<field name="product_qty" attrs="{'readonly': [('per_day','=',True)]}" force_save="1"/>
|
||||||
<button type="object" class="oe_stat_button"
|
<button type="object" class="oe_stat_button"
|
||||||
@@ -308,6 +311,7 @@
|
|||||||
</sheet>
|
</sheet>
|
||||||
<div class="oe_chatter">
|
<div class="oe_chatter">
|
||||||
<field name="message_follower_ids" widget="mail_followers"/>
|
<field name="message_follower_ids" widget="mail_followers"/>
|
||||||
|
<field name="activity_ids" widget="mail_activity"/>
|
||||||
<field name="message_ids" widget="mail_thread"/>
|
<field name="message_ids" widget="mail_thread"/>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ class FolioAdvancePaymentInv(models.TransientModel):
|
|||||||
|
|
||||||
advance_payment_method = fields.Selection([
|
advance_payment_method = fields.Selection([
|
||||||
('all', 'Invoiceable lines (deduct down payments)'),
|
('all', 'Invoiceable lines (deduct down payments)'),
|
||||||
|
('one', 'One line (Bill all in one line)'),
|
||||||
('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,
|
||||||
@@ -80,7 +81,6 @@ class FolioAdvancePaymentInv(models.TransientModel):
|
|||||||
line_ids = fields.One2many('line.advance.inv',
|
line_ids = fields.One2many('line.advance.inv',
|
||||||
'advance_inv_id',
|
'advance_inv_id',
|
||||||
string="Invoice Lines")
|
string="Invoice Lines")
|
||||||
view_detail = fields.Boolean('View Detail')
|
|
||||||
#Advance Payment
|
#Advance Payment
|
||||||
product_id = fields.Many2one('product.product', string="Product",
|
product_id = fields.Many2one('product.product', string="Product",
|
||||||
domain=[('type', '=', 'service')], default=_default_product_id)
|
domain=[('type', '=', 'service')], default=_default_product_id)
|
||||||
@@ -185,6 +185,9 @@ class FolioAdvancePaymentInv(models.TransientModel):
|
|||||||
invoice = inv_obj.create(inv_data)
|
invoice = inv_obj.create(inv_data)
|
||||||
for line in self.line_ids:
|
for line in self.line_ids:
|
||||||
line.invoice_line_create(invoice.id, line.qty)
|
line.invoice_line_create(invoice.id, line.qty)
|
||||||
|
elif self.advance_payment_method == 'all':
|
||||||
|
pass
|
||||||
|
#Group lines by tax_ids
|
||||||
else:
|
else:
|
||||||
# Create deposit product if necessary
|
# Create deposit product if necessary
|
||||||
if not self.product_id:
|
if not self.product_id:
|
||||||
@@ -281,51 +284,30 @@ class FolioAdvancePaymentInv(models.TransientModel):
|
|||||||
for service in services:
|
for service in services:
|
||||||
extra_price += service.price_unit * \
|
extra_price += service.price_unit * \
|
||||||
service.service_line_ids.filtered(
|
service.service_line_ids.filtered(
|
||||||
lambda x: x.date == day.date).day_qty
|
lambda x: x.date == day.date).day_qty
|
||||||
|
#group_key: if group by reservation, We no need group by room_type
|
||||||
group_key = (reservation.id, reservation.room_type_id.id, day.price + extra_price, day.discount)
|
group_key = (reservation.id, reservation.room_type_id.id, day.price + extra_price, day.discount)
|
||||||
date = fields.Date.from_string(day.date)
|
date = fields.Date.from_string(day.date)
|
||||||
if group_key in invoice_lines:
|
description = folio.name + ' ' + reservation.room_type_id.name + ' (' + \
|
||||||
invoice_lines[group_key][('qty')] += 1
|
reservation.board_service_room_id.hotel_board_service_id.name + ')' \
|
||||||
if date == fields.Date.from_string(
|
if board_service else folio.name + ' ' + reservation.room_type_id.name
|
||||||
invoice_lines[group_key][('date_to')]) + timedelta(days=1):
|
invoice_lines[group_key] = {
|
||||||
desc = invoice_lines[group_key][('description')]
|
'description' : description,
|
||||||
invoice_lines[group_key][('description')] = \
|
'reservation_id': reservation.id,
|
||||||
desc.replace(desc[desc.rfind(" - "):], ' - ' + \
|
'room_type_id': reservation.room_type_id,
|
||||||
(date + timedelta(days=1)).strftime(DEFAULT_SERVER_DATE_FORMAT) + ')')
|
'product_id': self.env['product.product'].browse(
|
||||||
else:
|
reservation.room_type_id.product_id.id),
|
||||||
invoice_lines[group_key][('description')] += \
|
'discount': day.discount,
|
||||||
' (' + date.strftime(DEFAULT_SERVER_DATE_FORMAT) + \
|
'price_unit': day.price + extra_price,
|
||||||
' - ' + (date + timedelta(days=1)).strftime(
|
'reservation_line_ids': []
|
||||||
DEFAULT_SERVER_DATE_FORMAT) + \
|
|
||||||
')'
|
|
||||||
invoice_lines[group_key][('date_to')] = day.date
|
|
||||||
else:
|
|
||||||
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,
|
|
||||||
'reservation_line_ids': []
|
|
||||||
}
|
}
|
||||||
invoice_lines[group_key][('reservation_line_ids')].append((4,day.id))
|
invoice_lines[group_key][('reservation_line_ids')].append((4,day.id))
|
||||||
for group_key in invoice_lines:
|
for group_key in invoice_lines:
|
||||||
vals.append((0, False, invoice_lines[group_key]))
|
vals.append((0, False, invoice_lines[group_key]))
|
||||||
self.line_ids = vals
|
self.line_ids = vals
|
||||||
|
self.line_ids.onchange_reservation_line_ids()
|
||||||
|
|
||||||
@api.onchange('view_detail', 'folio_ids')
|
@api.onchange('folio_ids')
|
||||||
def onchange_folio_ids(self):
|
def onchange_folio_ids(self):
|
||||||
vals = []
|
vals = []
|
||||||
folios = self.folio_ids
|
folios = self.folio_ids
|
||||||
@@ -388,11 +370,13 @@ class LineAdvancePaymentInv(models.TransientModel):
|
|||||||
qty = fields.Integer('Quantity')
|
qty = fields.Integer('Quantity')
|
||||||
price_unit = fields.Float('Price')
|
price_unit = fields.Float('Price')
|
||||||
advance_inv_id = fields.Many2one('folio.advance.payment.inv')
|
advance_inv_id = fields.Many2one('folio.advance.payment.inv')
|
||||||
|
price_room = fields.Float(compute='_compute_price_room')
|
||||||
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)
|
||||||
to_invoice = fields.Boolean('To Invoice')
|
to_invoice = fields.Boolean('To Invoice')
|
||||||
description = fields.Text('Description')
|
description = fields.Text('Description')
|
||||||
|
description_dates = fields.Text('Range')
|
||||||
reservation_id = fields.Many2one('hotel.reservation')
|
reservation_id = fields.Many2one('hotel.reservation')
|
||||||
service_id = fields.Many2one('hotel.service')
|
service_id = fields.Many2one('hotel.service')
|
||||||
folio_id = fields.Many2one('hotel.folio', compute='_compute_folio_id')
|
folio_id = fields.Many2one('hotel.folio', compute='_compute_folio_id')
|
||||||
@@ -400,10 +384,25 @@ class LineAdvancePaymentInv(models.TransientModel):
|
|||||||
'hotel.reservation.line',
|
'hotel.reservation.line',
|
||||||
string='Reservation Lines')
|
string='Reservation Lines')
|
||||||
|
|
||||||
|
def _compute_price_room(self):
|
||||||
|
for record in self:
|
||||||
|
record.price_room = self.reservation_line_ids[0].price
|
||||||
|
|
||||||
def _compute_folio_id(self):
|
def _compute_folio_id(self):
|
||||||
for record in self:
|
for record in self:
|
||||||
origin = record.reservation_id if record.reservation_id.id else record.service_id
|
origin = record.reservation_id if record.reservation_id.id else record.service_id
|
||||||
record.folio_id = origin.folio_id
|
record.folio_id = origin.folio_id
|
||||||
|
|
||||||
|
@api.onchange('reservation_line_ids')
|
||||||
|
def onchange_reservation_line_ids(self):
|
||||||
|
for record in self:
|
||||||
|
if record.reservation_id:
|
||||||
|
if not record.reservation_line_ids:
|
||||||
|
raise UserError(_('If you want drop the line, use the trash icon'))
|
||||||
|
record.qty = len(record.reservation_line_ids)
|
||||||
|
record.description_dates = record.reservation_line_ids[0].date + ' - ' + \
|
||||||
|
((fields.Date.from_string(record.reservation_line_ids[-1].date)) + \
|
||||||
|
timedelta(days=1)).strftime(DEFAULT_SERVER_DATE_FORMAT)
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
def invoice_line_create(self, invoice_id, qty):
|
def invoice_line_create(self, invoice_id, qty):
|
||||||
@@ -446,6 +445,7 @@ class LineAdvancePaymentInv(models.TransientModel):
|
|||||||
'reservation_ids': [(6, 0, [origin.id])],
|
'reservation_ids': [(6, 0, [origin.id])],
|
||||||
'reservation_line_ids': [(6, 0, line.reservation_line_ids.ids)]
|
'reservation_line_ids': [(6, 0, line.reservation_line_ids.ids)]
|
||||||
})
|
})
|
||||||
|
import wdb; wdb.set_trace()
|
||||||
elif line.service_id:
|
elif line.service_id:
|
||||||
vals.update({
|
vals.update({
|
||||||
'invoice_id': invoice_id,
|
'invoice_id': invoice_id,
|
||||||
|
|||||||
@@ -27,6 +27,9 @@
|
|||||||
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)]}"/>
|
||||||
</group>
|
</group>
|
||||||
|
<group>
|
||||||
|
<field name="group_folios" string="Add Folios"/>
|
||||||
|
</group>
|
||||||
<field name="folio_ids" attrs="{'invisible': [('group_folios', '=', False)]}">
|
<field name="folio_ids" attrs="{'invisible': [('group_folios', '=', False)]}">
|
||||||
<tree string="Folios" editable="bottom"
|
<tree string="Folios" editable="bottom"
|
||||||
decoration-danger="partner_invoice_id != parent.partner_invoice_id">
|
decoration-danger="partner_invoice_id != parent.partner_invoice_id">
|
||||||
@@ -41,18 +44,21 @@
|
|||||||
domain="[('folio_id', 'in', folio_ids)]"/>
|
domain="[('folio_id', 'in', folio_ids)]"/>
|
||||||
</group>
|
</group>
|
||||||
<group>
|
<group>
|
||||||
<field name="group_folios" />
|
<field name="line_ids">
|
||||||
<field name="view_detail" />
|
|
||||||
</group>
|
|
||||||
<group>
|
|
||||||
<field name="line_ids" attrs="{'invisible': [('view_detail', '=', False)]}"
|
|
||||||
nolabel="1">
|
|
||||||
<tree string="Lines" editable="bottom">
|
<tree string="Lines" editable="bottom">
|
||||||
<field name="product_id" invisible="1"/>
|
<field name="product_id" invisible="1" />
|
||||||
<field name="reservation_line_ids" />
|
<field name="price_room" invisible="1" />
|
||||||
<field name="reservation_id" />
|
<field name="reservation_id"
|
||||||
<field name="service_id" />
|
options="{'no_create': True,'no_open': True}"/>
|
||||||
|
<field name="service_id"
|
||||||
|
options="{'no_create': True,'no_open': True}"/>
|
||||||
<field name="description" />
|
<field name="description" />
|
||||||
|
<field name="description_dates" readonly="1" />
|
||||||
|
<field name="reservation_line_ids"
|
||||||
|
widget="many2many_tags" string="Nights"
|
||||||
|
domain="[('reservation_id','=',reservation_id),
|
||||||
|
('price','=', price_room)]"
|
||||||
|
options="{'no_create': True,'no_open': True}"/>
|
||||||
<field name="qty" />
|
<field name="qty" />
|
||||||
<field name="discount" />
|
<field name="discount" />
|
||||||
<field name="price_unit" />
|
<field name="price_unit" />
|
||||||
|
|||||||
Reference in New Issue
Block a user