19.0 vanilla

This commit is contained in:
Ernad Husremovic 2026-03-09 09:32:12 +01:00
parent 79f83631d5
commit 73afc09215
6267 changed files with 1534193 additions and 1130106 deletions

View file

@ -1,13 +1,11 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import account_move
from . import product
from . import event_booth_registration
from . import event_booth
from . import event_booth_category
from . import event_type_booth
from . import product_product
from . import product_template
from . import sale_order
from . import sale_order_line
from . import sale_order_template_line
from . import sale_order_template_option

View file

@ -17,10 +17,10 @@ class EventBooth(models.Model):
groups='sales_team.group_sale_salesman', copy=False)
sale_order_line_id = fields.Many2one(
'sale.order.line', string='Final Sale Order Line', ondelete='set null',
readonly=True, states={'available': [('readonly', False)]},
readonly=False, index='btree_not_null',
groups='sales_team.group_sale_salesman', copy=False)
sale_order_id = fields.Many2one(
related='sale_order_line_id.order_id', store='True', readonly=True,
related='sale_order_line_id.order_id', store='True', readonly=True, index='btree_not_null',
groups='sales_team.group_sale_salesman')
is_paid = fields.Boolean('Is Paid', copy=False)

View file

@ -3,7 +3,9 @@
import logging
from odoo import api, fields, models
from odoo import _, api, fields, models
from odoo.addons.product.models.product_template import PRICE_CONTEXT_KEYS
from odoo.exceptions import ValidationError
_logger = logging.getLogger(__name__)
@ -16,21 +18,35 @@ class EventBoothCategory(models.Model):
product_id = fields.Many2one(
'product.product', string='Product', required=True,
domain=[('detailed_type', '=', 'event_booth')], default=_default_product_id,
domain=[('service_tracking', '=', 'event_booth')], default=_default_product_id,
groups="event.group_event_registration_desk")
price = fields.Float(
string='Price', compute='_compute_price', digits='Product Price', readonly=False,
string='Price', compute='_compute_price', min_display_digits='Product Price', readonly=False,
store=True, groups="event.group_event_registration_desk")
price_incl = fields.Float(
string='Price incl', compute='_compute_price_incl', min_display_digits='Product Price', readonly=False,
groups="event.group_event_registration_desk")
currency_id = fields.Many2one(related='product_id.currency_id', groups="event.group_event_registration_desk")
price_reduce = fields.Float(
string='Price Reduce', compute='_compute_price_reduce',
compute_sudo=True, digits='Product Price', groups="event.group_event_registration_desk")
compute_sudo=True, min_display_digits='Product Price', groups="event.group_event_registration_desk")
price_reduce_taxinc = fields.Float(
string='Price Reduce Tax inc', compute='_compute_price_reduce_taxinc',
compute_sudo=True
)
image_1920 = fields.Image(compute='_compute_image_1920', readonly=False, store=True)
@api.constrains('product_id')
def _check_service_tracking(self):
for record in self:
if record.product_id and record.product_id.service_tracking != 'event_booth':
raise ValidationError(
_(
'The product, %(product_name)s , is used for Event Booth, it must have service_tracking set to "Event Booth".',
product_name=record.product_id.name
)
)
@api.depends('product_id')
def _compute_image_1920(self):
for category in self:
@ -44,29 +60,30 @@ class EventBoothCategory(models.Model):
if category.product_id and category.product_id.list_price:
category.price = category.product_id.list_price + category.product_id.price_extra
@api.depends_context('pricelist', 'quantity')
@api.depends('product_id', 'product_id.taxes_id', 'price')
def _compute_price_incl(self):
for category in self:
if category.product_id and category.price:
tax_ids = category.product_id.taxes_id
taxes = tax_ids.compute_all(category.price, category.currency_id, 1.0, product=category.product_id)
category.price_incl = taxes['total_included']
else:
category.price_incl = 0
@api.depends_context(*PRICE_CONTEXT_KEYS)
@api.depends('product_id', 'price')
def _compute_price_reduce(self):
for category in self:
product = category.product_id
pricelist = product.product_tmpl_id._get_contextual_pricelist()
lst_price = product.currency_id._convert(
product.lst_price,
pricelist.currency_id,
self.env.company,
fields.Datetime.now(),
round=False,
)
discount = (lst_price - product._get_contextual_price()) / lst_price if lst_price else 0.0
category.price_reduce = (1.0 - discount) * category.price
contextual_discount = category.product_id._get_contextual_discount()
category.price_reduce = (1.0 - contextual_discount) * category.price
@api.depends_context('pricelist', 'quantity')
@api.depends_context(*PRICE_CONTEXT_KEYS)
@api.depends('product_id', 'price_reduce')
def _compute_price_reduce_taxinc(self):
for category in self:
tax_ids = category.product_id.taxes_id
taxes = tax_ids.compute_all(category.price_reduce, category.currency_id, 1.0, product=category.product_id)
category.price_reduce_taxinc = taxes['total_included']
category.price_reduce_taxinc = taxes['total_included'] or 0
def _init_column(self, column_name):
""" Initialize product_id for existing columns when installing sale
@ -89,10 +106,11 @@ class EventBoothCategory(models.Model):
else:
product_id = self.env['product.product'].create({
'name': 'Generic Event Booth Product',
'categ_id': self.env.ref('event_sale.product_category_events').id,
'categ_id': self.env.ref('event_product.product_category_events').id,
'list_price': 100,
'standard_price': 0,
'detailed_type': 'event_booth',
'type': 'service',
'service_tracking': 'event_booth',
'invoice_policy': 'order',
}).id
self.env['ir.model.data'].create({

View file

@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from markupsafe import Markup
from odoo import api, fields, models, _
@ -11,17 +12,18 @@ class EventBoothRegistration(models.Model):
_name = 'event.booth.registration'
_description = 'Event Booth Registration'
sale_order_line_id = fields.Many2one('sale.order.line', string='Sale Order Line', required=True, ondelete='cascade')
event_booth_id = fields.Many2one('event.booth', string='Booth', required=True)
sale_order_line_id = fields.Many2one('sale.order.line', string='Sale Order Line', required=True, index=True, ondelete='cascade')
event_booth_id = fields.Many2one('event.booth', string='Booth', required=True, index=True)
partner_id = fields.Many2one(
'res.partner', related='sale_order_line_id.order_partner_id', store=True)
contact_name = fields.Char(string='Contact Name', compute='_compute_contact_name', readonly=False, store=True)
contact_email = fields.Char(string='Contact Email', compute='_compute_contact_email', readonly=False, store=True)
contact_phone = fields.Char(string='Contact Phone', compute='_compute_contact_phone', readonly=False, store=True)
contact_mobile = fields.Char(string='Contact Mobile', compute='_compute_contact_mobile', readonly=False, store=True)
_sql_constraints = [('unique_registration', 'unique(sale_order_line_id, event_booth_id)',
'There can be only one registration for a booth by sale order line')]
_unique_registration = models.Constraint(
'unique(sale_order_line_id, event_booth_id)',
'There can be only one registration for a booth by sale order line',
)
@api.depends('partner_id')
def _compute_contact_name(self):
@ -41,15 +43,9 @@ class EventBoothRegistration(models.Model):
if not registration.contact_phone:
registration.contact_phone = registration.partner_id.phone or False
@api.depends('partner_id')
def _compute_contact_mobile(self):
for registration in self:
if not registration.contact_mobile:
registration.contact_mobile = registration.partner_id.mobile or False
@api.model
def _get_fields_for_booth_confirmation(self):
return ['sale_order_line_id', 'partner_id', 'contact_name', 'contact_email', 'contact_phone', 'contact_mobile']
return ['sale_order_line_id', 'partner_id', 'contact_name', 'contact_email', 'contact_phone']
def action_confirm(self):
for registration in self:
@ -61,9 +57,9 @@ class EventBoothRegistration(models.Model):
self._cancel_pending_registrations()
def _cancel_pending_registrations(self):
body = '<p>%(message)s: <ul>%(booth_names)s</ul></p>' % {
body = Markup('<p>%(message)s: <ul>%(booth_names)s</ul></p>') % {
'message': _('Your order has been cancelled because the following booths have been reserved'),
'booth_names': ''.join('<li>%s</li>' % booth.display_name for booth in self.event_booth_id)
'booth_names': Markup().join(Markup('<li>%s</li>') % booth.display_name for booth in self.event_booth_id)
}
other_registrations = self.search([
('event_booth_id', 'in', self.event_booth_id.ids),

View file

@ -1,31 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models
class ProductTemplate(models.Model):
_inherit = 'product.template'
detailed_type = fields.Selection(selection_add=[
('event_booth', 'Event Booth'),
], ondelete={'event_booth': 'set service'})
@api.onchange('detailed_type')
def _onchange_type_event_booth(self):
if self.detailed_type == 'event_booth':
self.invoice_policy = 'order'
def _detailed_type_mapping(self):
type_mapping = super()._detailed_type_mapping()
type_mapping['event_booth'] = 'service'
return type_mapping
class Product(models.Model):
_inherit = 'product.product'
@api.onchange('detailed_type')
def _onchange_type_event_booth(self):
if self.detailed_type == 'event_booth':
self.invoice_policy = 'order'

View file

@ -0,0 +1,25 @@
from odoo import _, api, models
from odoo.exceptions import ValidationError
class ProductProduct(models.Model):
_inherit = 'product.product'
@api.onchange('service_tracking')
def _onchange_type_event_booth(self):
if self.service_tracking == 'event_booth':
self.invoice_policy = 'order'
@api.constrains('service_tracking')
def _check_service_tracking_for_event_booths(self):
if product_not_event_booth := self.filtered(lambda p: p.service_tracking != 'event_booth'):
booth_category = self.env['event.booth.category'].search([('product_id', 'in', product_not_event_booth.ids)], limit=1)
if booth_category:
raise ValidationError(
_(
"You cannot change the service_tracking of the product %(product_name)s because it is already assigned "
"to %(booth_category_name)s. The service_tracking must remain 'event_booth'.",
product_name=product_not_event_booth.name,
booth_category_name=booth_category.name,
)
)

View file

@ -0,0 +1,43 @@
from odoo import _, api, fields, models
from odoo.exceptions import ValidationError
class ProductTemplate(models.Model):
_inherit = 'product.template'
service_tracking = fields.Selection(selection_add=[
('event_booth', 'Event Booth'),
], ondelete={'event_booth': 'set default'})
def _prepare_service_tracking_tooltip(self):
if self.service_tracking == 'event_booth':
return _("Mark the selected Booth as Unavailable.")
return super()._prepare_service_tracking_tooltip()
@api.onchange('service_tracking')
def _onchange_type_event_booth(self):
if self.service_tracking == 'event_booth':
self.invoice_policy = 'order'
def _service_tracking_blacklist(self):
return super()._service_tracking_blacklist() + ['event_booth']
@api.constrains('service_tracking')
def _check_service_tracking_for_event_booths(self):
""" Prevent changing the service_tracking field if the product template or any of its variants
is linked to an Event Booth Category.
"""
if product_variants_not_event_booth := self.product_variant_ids.filtered(lambda p: p.service_tracking != 'event_booth'):
linked_booth_category = self.env['event.booth.category'].sudo().search([
('product_id', 'in', product_variants_not_event_booth.ids)
], limit=1)
if linked_booth_category:
raise ValidationError(
_(
'The "service_tracking" for the product template, %(product_template_name)s cannot be changed because '
'one of its variants is assigned to the Event Booth Category, %(event_booth_category_name)s. '
'The service_tracking must remain "Event Booth".',
product_template_name=linked_booth_category.product_id.name,
event_booth_category_name=linked_booth_category.name,
)
)

View file

@ -1,7 +1,8 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models
from odoo import api, fields, models, _
from odoo.exceptions import ValidationError
from odoo.fields import Domain
class SaleOrder(models.Model):
@ -12,20 +13,23 @@ class SaleOrder(models.Model):
@api.depends('event_booth_ids')
def _compute_event_booth_count(self):
if self.ids:
slot_data = self.env['event.booth']._read_group(
[('sale_order_id', 'in', self.ids)],
['sale_order_id'], ['sale_order_id']
)
slot_mapped = dict((data['sale_order_id'][0], data['sale_order_id_count']) for data in slot_data)
else:
slot_mapped = dict()
slot_data = self.env['event.booth']._read_group(
[('sale_order_id', 'in', self.ids)],
['sale_order_id'], ['__count'],
)
slot_mapped = {sale_order.id: count for sale_order, count in slot_data}
for so in self:
so.event_booth_count = slot_mapped.get(so.id, 0)
def action_confirm(self):
res = super(SaleOrder, self).action_confirm()
for so in self:
if not any(line.service_tracking == 'event_booth' for line in so.order_line):
continue
so_lines_missing_booth = so.order_line.filtered(lambda line: line.service_tracking == 'event_booth' and not line.event_booth_pending_ids)
if so_lines_missing_booth:
so_lines_descriptions = "".join(f"\n- {so_line_description.name}" for so_line_description in so_lines_missing_booth)
raise ValidationError(_("Please make sure all your event-booth related lines are configured before confirming this order:%s", so_lines_descriptions))
so.order_line._update_event_booths()
return res
@ -33,3 +37,6 @@ class SaleOrder(models.Model):
action = self.env['ir.actions.act_window']._for_xml_id('event_booth.event_booth_action')
action['domain'] = [('sale_order_id', 'in', self.ids)]
return action
def _get_product_catalog_domain(self):
return super()._get_product_catalog_domain() & Domain('service_tracking', '!=', 'event_booth')

View file

@ -3,6 +3,7 @@
from odoo import api, fields, models, _
from odoo.exceptions import ValidationError
from odoo.fields import Domain
class SaleOrderLine(models.Model):
@ -17,19 +18,6 @@ class SaleOrderLine(models.Model):
event_booth_registration_ids = fields.One2many(
'event.booth.registration', 'sale_order_line_id', string='Confirmed Registration')
event_booth_ids = fields.One2many('event.booth', 'sale_order_line_id', string='Confirmed Booths')
is_event_booth = fields.Boolean(compute='_compute_is_event_booth')
@api.depends('product_id.type')
def _compute_is_event_booth(self):
for record in self:
record.is_event_booth = record.product_id.detailed_type == 'event_booth'
@api.depends('event_booth_ids')
def _compute_name_short(self):
wbooth = self.filtered(lambda line: line.event_booth_pending_ids)
for record in wbooth:
record.name_short = record.event_booth_pending_ids.event_id.name
super(SaleOrderLine, self - wbooth)._compute_name_short()
@api.depends('event_booth_registration_ids')
def _compute_event_booth_pending_ids(self):
@ -55,6 +43,8 @@ class SaleOrderLine(models.Model):
} for booth in selected_booths - existing_booths])
def _search_event_booth_pending_ids(self, operator, value):
if operator in Domain.NEGATIVE_OPERATORS:
return NotImplemented
return [('event_booth_registration_ids.event_booth_id', operator, value)]
@api.constrains('event_booth_registration_ids')
@ -74,7 +64,7 @@ class SaleOrderLine(models.Model):
if self.event_booth_pending_ids and (not self.event_id or self.event_id != self.event_booth_pending_ids.event_id):
self.event_booth_pending_ids = None
@api.depends('event_booth_registration_ids.event_booth_id')
@api.depends('event_booth_pending_ids')
def _compute_name(self):
"""Override to add the compute dependency.
@ -83,7 +73,7 @@ class SaleOrderLine(models.Model):
super()._compute_name()
def _update_event_booths(self, set_paid=False):
for so_line in self.filtered('is_event_booth'):
for so_line in self.filtered(lambda sol: sol.product_id.service_tracking == 'event_booth'):
if so_line.event_booth_pending_ids and not so_line.event_booth_ids:
unavailable = so_line.event_booth_pending_ids.filtered(lambda booth: not booth.is_available)
if unavailable:
@ -100,17 +90,20 @@ class SaleOrderLine(models.Model):
return self.event_booth_pending_ids._get_booth_multiline_description()
return super()._get_sale_order_line_multiline_description_sale()
def _use_template_name(self):
""" We do not want configured description to get rewritten by template default"""
if self.event_booth_pending_ids:
return False
return super()._use_template_name()
def _get_display_price(self):
if self.event_booth_pending_ids and self.event_id:
company = self.event_id.company_id or self.env.company
currency = company.currency_id
pricelist = self.order_id.pricelist_id
if pricelist.discount_policy == "with_discount":
total_price = sum([booth.booth_category_id.with_context(pricelist=pricelist.id).price_reduce for booth in self.event_booth_pending_ids])
if not self.pricelist_item_id._show_discount():
event_booths = self.event_booth_pending_ids.with_context(**self._get_pricelist_price_context())
total_price = sum(booth.booth_category_id.price_reduce for booth in event_booths)
else:
total_price = sum([booth.price for booth in self.event_booth_pending_ids])
return currency._convert(
total_price, self.order_id.currency_id,
self.order_id.company_id or self.env.company.id,
self.order_id.date_order or fields.Date.today())
total_price = sum(booth.price for booth in self.event_booth_pending_ids)
return self._convert_to_sol_currency(total_price, company.currency_id)
return super()._get_display_price()

View file

@ -1,6 +0,0 @@
from odoo import fields, models
class SaleOrderTemplateLine(models.Model):
_inherit = "sale.order.template.line"
product_id = fields.Many2one(domain="[('sale_ok', '=', True), ('detailed_type', 'not in', ['event', 'event_booth']), ('company_id', 'in', [company_id, False])]")

View file

@ -1,6 +0,0 @@
from odoo import fields, models
class SaleOrderTemplateOption(models.Model):
_inherit = "sale.order.template.option"
product_id = fields.Many2one(domain="[('sale_ok', '=', True), ('detailed_type', 'not in', ['event', 'event_booth']), ('company_id', 'in', [company_id, False])]")