From d8f1da7e1ba4b748280211dc906de7184ee5b913 Mon Sep 17 00:00:00 2001 From: Dario Lodeiros Date: Mon, 10 Dec 2018 09:14:06 +0100 Subject: [PATCH 1/5] [ADD] Service Lines Prices and Compute Flow --- hotel/__manifest__.py | 1 + hotel/models/hotel_folio.py | 1 - hotel/models/hotel_reservation.py | 10 +- hotel/models/hotel_service.py | 121 +++++++++++++++++++----- hotel/views/hotel_folio_views.xml | 14 +-- hotel/views/hotel_reservation_views.xml | 53 ++++------- hotel/views/hotel_service_views.xml | 60 +++++------- 7 files changed, 158 insertions(+), 102 deletions(-) diff --git a/hotel/__manifest__.py b/hotel/__manifest__.py index 56728c424..98c2551c5 100644 --- a/hotel/__manifest__.py +++ b/hotel/__manifest__.py @@ -46,6 +46,7 @@ 'views/hotel_room_type_restriction_item_views.xml', 'views/hotel_reservation_views.xml', 'views/hotel_room_closure_reason_views.xml', + 'views/hotel_service_views.xml', 'views/hotel_board_service_views.xml', 'views/hotel_checkin_partner_views.xml', 'views/hotel_room_type_availability_views.xml', diff --git a/hotel/models/hotel_folio.py b/hotel/models/hotel_folio.py index 7dba397b6..fc327e837 100644 --- a/hotel/models/hotel_folio.py +++ b/hotel/models/hotel_folio.py @@ -90,7 +90,6 @@ class HotelFolio(models.Model): pricelist_id = fields.Many2one('product.pricelist', string='Pricelist', required=True, - readonly=True, states={'draft': [('readonly', False)], 'sent': [('readonly', False)]}, help="Pricelist for current folio.") diff --git a/hotel/models/hotel_reservation.py b/hotel/models/hotel_reservation.py index 8aae087c3..c6d845075 100644 --- a/hotel/models/hotel_reservation.py +++ b/hotel/models/hotel_reservation.py @@ -173,8 +173,7 @@ class HotelReservation(models.Model): service_ids = fields.One2many('hotel.service', 'ser_room_line') pricelist_id = fields.Many2one('product.pricelist', - related='folio_id.pricelist_id', - readonly="1") + related='folio_id.pricelist_id') #TODO: Warning Mens to update pricelist checkin_partner_ids = fields.One2many('hotel.checkin.partner', 'reservation_id') # TODO: As checkin_partner_count is a computed field, it can't not be used in a domain filer # Non-stored field hotel.reservation.checkin_partner_count cannot be searched @@ -290,6 +289,7 @@ class HotelReservation(models.Model): board_services.append((0, False, { 'product_id': product.id, 'is_board_service': True, + 'folio_id': vals.get('folio_id'), })) vals.update({'service_ids': board_services}) if self.compute_price_out_vals(vals): @@ -327,6 +327,7 @@ class HotelReservation(models.Model): board_services.append((0, False, { 'product_id': product.id, 'is_board_service': True, + 'folio_id': record.folio_id.id or vals.get('folio_id') })) # 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}) @@ -610,6 +611,7 @@ class HotelReservation(models.Model): vals = { 'product_id': product.id, 'is_board_service': True, + 'folio_id': self.folio_id.id, } vals.update(self.env['hotel.service'].prepare_service_lines( dfrom=self.checkin, @@ -619,8 +621,10 @@ class HotelReservation(models.Model): old_line_days=False)) board_services.append((0, False, vals)) 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): + service._compute_tax_ids() + service.price_unit = service._compute_price_unit() """ STATE WORKFLOW ----------------------------------------------------- diff --git a/hotel/models/hotel_service.py b/hotel/models/hotel_service.py index c19aaddbc..459dcded3 100644 --- a/hotel/models/hotel_service.py +++ b/hotel/models/hotel_service.py @@ -5,6 +5,10 @@ from odoo import models, fields, api, _ from odoo.tools import DEFAULT_SERVER_DATE_FORMAT from datetime import timedelta from odoo.exceptions import ValidationError +from odoo.addons import decimal_precision as dp +import logging +_logger = logging.getLogger(__name__) + class HotelService(models.Model): _name = 'hotel.service' @@ -50,31 +54,34 @@ class HotelService(models.Model): product_qty = fields.Integer('Quantity') days_qty = fields.Integer(compute="_compute_days_qty", store=True) is_board_service = fields.Boolean() - pricelist_id = fields.Many2one(related='folio_id.pricelist_id') + # 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) channel_type = fields.Selection([ ('door', 'Door'), ('mail', 'Mail'), ('phone', 'Phone'), ('call', 'Call Center'), ('web', 'Web')], 'Sales Channel') - currency_id = fields.Many2one('res.currency', - related='pricelist_id.currency_id', - string='Currency', readonly=True, required=True) + price_unit = fields.Float('Unit Price', required=True, digits=dp.get_precision('Product Price'), default=0.0) + 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) price_subtotal = fields.Monetary(string='Subtotal', readonly=True, store=True, - compute='_compute_amount_reservation') + compute='_compute_amount_service') price_total = fields.Monetary(string='Total', readonly=True, store=True, - compute='_compute_amount_reservation') + compute='_compute_amount_service') price_tax = fields.Float(string='Taxes', readonly=True, store=True, - compute='_compute_amount_reservation') + compute='_compute_amount_service') @api.model def create(self, vals): + vals.update(self._prepare_add_missing_fields(vals)) if self.compute_lines_out_vals(vals): reservation = self.env['hotel.reservation'].browse(vals['ser_room_line']) product = self.env['product.product'].browse(vals['product_id']) @@ -114,6 +121,20 @@ class HotelService(models.Model): res = super(HotelService, self).write(vals) return res + @api.model + def _prepare_add_missing_fields(self, values): + """ Deduce missing required fields from the onchange """ + res = {} + onchange_fields = ['price_unit','tax_ids'] + 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() + for field in onchange_fields: + if field not in values: + res[field] = line._fields[field].convert_to_write(line[field], line) + return res + @api.multi def compute_lines_out_vals(self, vals): """ @@ -128,6 +149,26 @@ class HotelService(models.Model): return True return False + @api.multi + def _compute_tax_ids(self): + for record in self: + # If company_id is set, always filter taxes by the company + folio = self.folio_id or self.env.context.get('default_folio_id') + record.tax_id = record.product_id.taxes_id.filtered(lambda r: not record.company_id or r.company_id == folio.company_id) + + @api.multi + def _get_display_price(self, product): + folio = self.folio_id or self.env.context.get('default_folio_id') + if folio.pricelist_id.discount_policy == 'with_discount': + return product.with_context(pricelist=folio.pricelist_id.id).price + product_context = dict(self.env.context, partner_id=folio.partner_id.id, date=folio.date_order, uom=self.product_id.uom_id.id) + final_price, rule_id = folio.pricelist_id.with_context(product_context).get_product_price_rule(self.product_id, self.product_qty or 1.0, folio.partner_id) + base_price, currency_id = self.with_context(product_context)._get_real_price_currency(product, rule_id, self.product_qty, product_id.uom_id, folio.pricelist_id.id) + if currency_id != folio.pricelist_id.currency_id.id: + base_price = self.env['res.currency'].browse(currency_id).with_context(product_context).compute(base_price, folio.pricelist_id.currency_id) + # negative discounts (= surcharge) are included in the display price + return max(base_price, final_price) + @api.onchange('product_id') def onchange_product_calc_qty(self): """ @@ -135,11 +176,15 @@ class HotelService(models.Model): configuration of the selected product, in per_day product configuration, the qty is autocalculated and readonly based on service_lines qty """ + if not self.product_id: + return + vals = {} + vals['product_qty'] = 1.0 for record in self: if record.per_day and record.ser_room_line: product = record.product_id reservation = record.ser_room_line - record.update(self.prepare_service_lines( + vals.update(self.prepare_service_lines( dfrom=reservation.checkin, days=reservation.nights, per_person=product.per_person, @@ -148,6 +193,30 @@ class HotelService(models.Model): if record.product_id.daily_limit > 0: for day in record.service_line_ids: day.no_free_resources() + """ + Compute tax and price unit + """ + self._compute_tax_ids() + vals['price_unit'] = self._compute_price_unit() + record.update(vals) + + @api.multi + def _compute_price_unit(self): + """ + Compute tax and price unit + """ + folio = self.folio_id or self.env.context.get('default_folio_id') + product = self.product_id.with_context( + lang=folio.partner_id.lang, + partner=folio.partner_id.id, + quantity=self.product_qty, + date=folio.date_order, + pricelist=folio.pricelist_id.id, + uom=self.product_id.uom_id.id, + fiscal_position=False + ) + return self.env['account.tax']._fix_tax_included_price_company(self._get_display_price(product), product.taxes_id, self.tax_ids, folio.company_id) + @api.model def prepare_service_lines(self, **kwargs): @@ -155,16 +224,15 @@ class HotelService(models.Model): Prepare line and respect the old manual changes on lines """ cmds = [(5, 0, 0)] - old_lines_days = kwargs.get('old_lines_days') + old_line_days = kwargs.get('old_line_days') total_qty = 0 day_qty = 1 if kwargs.get('per_person'): #WARNING: Change adults in reservation NOT update qty service!! day_qty = kwargs.get('persons') - old_line_days = self.env['hotel.service.line'].browse(kwargs.get('old_line_days')) for i in range(0, kwargs.get('days')): idate = (fields.Date.from_string(kwargs.get('dfrom')) + timedelta(days=i)).strftime( DEFAULT_SERVER_DATE_FORMAT) - if not old_lines_days or idate not in old_lines_days.mapped('date'): + if not old_line_days or idate not in old_line_days.mapped('date'): cmds.append((0, False, { 'date': idate, 'day_qty': day_qty @@ -176,15 +244,16 @@ class HotelService(models.Model): total_qty = total_qty + old_line.day_qty return {'service_line_ids': cmds, 'product_qty': total_qty} - @api.depends('qty_product', 'tax_id') + @api.depends('product_qty', 'discount', 'price_unit', 'tax_ids') def _compute_amount_service(self): """ Compute the amounts of the service line. """ for record in self: + folio = record.folio_id or self.env.context.get('default_folio_id') product = record.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) + price = record.price_unit * (1 - (record.discount or 0.0) * 0.01) + taxes = record.tax_ids.compute_all(price, folio.currency_id, record.product_qty, product=product) record.update({ 'price_tax': sum(t.get('amount', 0.0) for t in taxes.get('taxes', [])), 'price_total': taxes['total_included'], @@ -203,12 +272,20 @@ class HotelService(models.Model): else: vals = {'days_qty': 0} record.update(vals) + + @api.multi + def open_service_lines(self): + action = self.env.ref('hotel.action_hotel_services_form').read()[0] + action['views'] = [(self.env.ref('hotel.hotel_service_view_form').id, 'form')] + action['res_id'] = self.id + action['target'] = 'new' + return action - @api.constrains('qty_product') - def constrains_qty_per_day(self): - for record in self: - if record.per_day: - service_lines = self.env['hotel.service_line'] - total_day_qty = sum(service_lines.with_context({'service_id': record.id}).mapped('day_qty')) - if record.qty_product != total_day_qty: - raise ValidationError (_('The quantity per line and per day does not correspond')) + #~ @api.constrains('product_qty') + #~ def constrains_qty_per_day(self): + #~ for record in self: + #~ if record.per_day: + #~ service_lines = self.env['hotel.service_line'] + #~ total_day_qty = sum(service_lines.with_context({'service_id': record.id}).mapped('day_qty')) + #~ if record.product_qty != total_day_qty: + #~ raise ValidationError (_('The quantity per line and per day does not correspond')) diff --git a/hotel/views/hotel_folio_views.xml b/hotel/views/hotel_folio_views.xml index bec9ab38f..43abd5777 100644 --- a/hotel/views/hotel_folio_views.xml +++ b/hotel/views/hotel_folio_views.xml @@ -287,19 +287,24 @@ - + + + - + + + + + attrs="{'readonly':[('state','not in',('draft'))]}"/> @@ -331,8 +336,6 @@ - - @@ -417,7 +420,6 @@ domain="[('sale_ok', '=', True)]" options="{'create': False, 'create_edit': False}" /> - @@ -231,29 +231,36 @@ - - - - + - - + context="{'default_ser_room_line': active_id, 'default_folio_id': folio_id}" + nolabel="1"> + - - + + - - + +