Skip to content

[ADD] event_ticket_registration_limit, website_appointment_filters, and sale_extension modules #237

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions event_ticket_registration_limit/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import models
19 changes: 19 additions & 0 deletions event_ticket_registration_limit/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
'name': 'Event Ticket Registration Limit',
'description': 'adds a feature to restrict the maximum number of tickets per registration',
'category': 'Event/Event Ticket',
'depends': ['base', 'event', 'website_event'],

'version': '1.0',
'author': 'Kishan B. Gajera',

'installable': True,
'application': True,

'license': 'LGPL-3',

'data': [
'views/event_ticket_view.xml',
'views/modal_ticket_registration_web_view.xml',
]
}
1 change: 1 addition & 0 deletions event_ticket_registration_limit/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import event_ticket
6 changes: 6 additions & 0 deletions event_ticket_registration_limit/models/event_ticket.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from odoo import models, fields

class EventTicket(models.Model):
_inherit = "event.event.ticket"

max_tickets_per_registration = fields.Integer(string="Max Tickets per Registration", help="Define the maximum number of tickets that can be booked per registration.")
13 changes: 13 additions & 0 deletions event_ticket_registration_limit/views/event_ticket_view.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="event_event_ticket_view_tree_from_event_inherited" model="ir.ui.view">
<field name="name">event.event.ticket.view.list.inherit</field>
<field name="model">event.event.ticket</field>
<field name="inherit_id" ref="event.event_event_ticket_view_tree_from_event"/>
<field name="arch" type="xml">
<xpath expr="//list" position="inside">
<field name="max_tickets_per_registration" string="Max Tickets/Registration" />
</xpath>
</field>
</record>
</odoo>
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="modal_ticket_registration" name="modal_ticket_registration" inherit_id="website_event.modal_ticket_registration">
<xpath expr="//div//t[@t-set='seats_max']" position="before">
<t t-set="seats_max_registration" t-value="(ticket.max_tickets_per_registration + 1) if ticket.max_tickets_per_registration else 10"/>
</xpath>

<xpath expr="//div//t[@t-set='seats_max']" position="attributes">
<attribute name="t-value">min(seats_max_ticket, seats_max_event, seats_max_registration)</attribute>
</xpath>

<xpath expr="//div[hasclass('o_wevent_registration_single_select')]//t[@t-set='seats_max']" position="before">
<t t-set="seats_max_registration" t-value="(tickets.max_tickets_per_registration + 1) if tickets.max_tickets_per_registration else 10"/>
</xpath>

<xpath expr="//div[hasclass('o_wevent_registration_single_select')]//t[@t-set='seats_max']" position="attributes">
<attribute name="t-value">min(seats_max_ticket, seats_max_event, seats_max_registration)</attribute>
</xpath>
</template>
</odoo>
2 changes: 2 additions & 0 deletions sale_extension/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from . import models
from . import wizard
21 changes: 21 additions & 0 deletions sale_extension/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
'name': 'Sale Extension',
'version': '1.0',
'depends': ['base', 'sale', 'sale_management'],
'author': "Kishan B. Gajera",
'category': 'Sale/Sale',
'description': """
A sample sale extension
""",

'application': True,
'installable': True,

'data': [
'security/ir.model.access.csv',
'views/sale_order_views.xml',
'wizard/cost_distribution_wizard.xml',
],

'license':'LGPL-3',
}
1 change: 1 addition & 0 deletions sale_extension/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import sale_order_line
20 changes: 20 additions & 0 deletions sale_extension/models/sale_order_line.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from odoo import models, fields

class SaleOrderLine(models.Model):
_inherit = 'sale.order.line'

distributed_cost = fields.Float("Distributed Cost")

def action_distribute_cost(self):
return {
'type': 'ir.actions.act_window',
'name': 'Distribute Cost',
'res_model': 'cost.distribution.wizard',
'view_mode': 'form',
'target': 'new',
'context': {
'order_id': self.order_id.id,
'default_order_line_id': self.id,
'default_price_subtotal': self.price_subtotal,
}
}
2 changes: 2 additions & 0 deletions sale_extension/security/ir.model.access.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
sale_extension.access_cost_distribution_wizard,access_cost_distribution_wizard,sale_extension.model_cost_distribution_wizard,base.group_user,1,1,1,1
18 changes: 18 additions & 0 deletions sale_extension/views/sale_order_views.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>

<record id="view_order_form_inherit" model="ir.ui.view">
<field name="name">sale.order.view.form.inherit</field>
<field name="model">sale.order</field>
<field name="inherit_id" ref="sale.view_order_form"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='product_template_id']" position="after">
<button icon="fa-share-alt" string=" " type="object" name="action_distribute_cost"></button>
</xpath>
<xpath expr="//field[@name='price_total'][@invisible='is_downpayment']" position="after">
<field name="distributed_cost"></field>
</xpath>
</field>
</record>

</odoo>
1 change: 1 addition & 0 deletions sale_extension/wizard/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import cost_distribution_wizard
52 changes: 52 additions & 0 deletions sale_extension/wizard/cost_distribution_wizard.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
from odoo import models, fields, api
from odoo.exceptions import UserError

class CostDistributionWizard(models.TransientModel):
_name = "cost.distribution.wizard"
_description = "A Wizard to distribute cost over other sales order lines."

order_line_id = fields.Many2one("sale.order.line", string="Order Line")
order_line_ids = fields.Many2many("sale.order.line", string="Order Lines")
order_line_cost = fields.Float(string="Cost to Distribute")
order_id = fields.Many2one("sale.order")
price_subtotal = fields.Float("Price Subtotal")

@api.model
def default_get(self, fields_list):
res = super(CostDistributionWizard, self).default_get(fields_list)
order_id = self.env.context.get("order_id")
default_order_line_id = self.env.context.get("default_order_line_id")
default_price_subtotal = self.env.context.get("default_price_subtotal", 0)

if order_id:
order = self.env["sale.order"].browse(order_id)
order_line_ids = order.order_line.ids
if default_order_line_id in order_line_ids:
order_line_ids.remove(default_order_line_id)

res.update({
"order_id": order_id,
"order_line_ids": [(6, 0, order_line_ids)],
"order_line_cost": default_price_subtotal,
})

distributed_cost = round(default_price_subtotal / len(order_line_ids), 2) if order_line_ids else 0
for line in self.env["sale.order.line"].browse(order_line_ids):
line.write({"distributed_cost": distributed_cost})

return res

def distribute_cost(self):
total_cost_distributed = sum(line.distributed_cost for line in self.order_line_ids)

if total_cost_distributed > self.price_subtotal:
raise UserError("Distributed price is greater than the distributable price.")

for line in self.order_line_ids:
line.price_subtotal += line.distributed_cost

original_order_line = self.env["sale.order.line"].browse(self.env.context.get("default_order_line_id"))
original_order_line.price_subtotal -= total_cost_distributed

if total_cost_distributed == original_order_line.price_subtotal:
pass
27 changes: 27 additions & 0 deletions sale_extension/wizard/cost_distribution_wizard.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="cost_distribution_wizard" model="ir.ui.view">
<field name="name">cost.distribution.wizard.form</field>
<field name="model">cost.distribution.wizard</field>
<field name="arch" type="xml">
<form>
<sheet>
<group style="text-align: right;">
<field name="order_line_cost" readonly="1"/>
</group>
<field name="order_line_ids" invisible="price_subtotal == 0">
<list editable="bottom">
<field name="product_id" readonly="1" />
<field name="price_subtotal" string="Current Cost" readonly="1" />
<field name="distributed_cost" />
</list>
</field>
<footer>
<button string="Distribute" type="object" name="distribute_cost" class="btn-primary"/>
<button string="Cancel" class="btn-secondary" special="cancel"/>
</footer>
</sheet>
</form>
</field>
</record>
</odoo>
1 change: 1 addition & 0 deletions website_appointment_filters/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import controllers
19 changes: 19 additions & 0 deletions website_appointment_filters/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
'name': 'Website Appointment Filters',
'version': '1.0',
'depends': ['base', 'website_appointment', 'appointment_account_payment'],
'author': 'Kishan B. Gajera',
'category': 'Appointment',
'description': """
A sample module to add filters in Appointment Website View
""",

'application': True,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think no need for this

'installable': True,

'data': [
'views/appointment_templates_appointment_filters.xml'
],

'license':'LGPL-3',
}
1 change: 1 addition & 0 deletions website_appointment_filters/controllers/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import main
73 changes: 73 additions & 0 deletions website_appointment_filters/controllers/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
from odoo import http
from odoo.http import request
from odoo.addons.website_appointment.controllers.appointment import WebsiteAppointment

class WebsiteAppointmentFiltersController(WebsiteAppointment):
@http.route(['/appointment'], type='http', auth='public', website=True)
def appointment_type_index(self, page=1, **filters):
filtered_appointments_by_mode = set()
filtered_appointments_by_type = set()
filtered_appointments_by_schedule = set()

if 'mode' in filters:
if filters['mode'] == 'online':
for appointment in request.env['appointment.type'].search([('location_id', '=', None)]):
filtered_appointments_by_mode.add(appointment.id)
elif filters['mode'] == 'offline':
for appointment in request.env['appointment.type'].search([('location_id', '!=', None)]):
filtered_appointments_by_mode.add(appointment.id)
elif filters['mode'] == 'all':
for appointment in request.env['appointment.type'].search([]):
filtered_appointments_by_mode.add(appointment.id)
else:
for appointment in request.env['appointment.type'].search([]):
filtered_appointments_by_mode.add(appointment.id)

if 'type' in filters:
if filters['type'] == 'paid':
for appointment in request.env['appointment.type'].search([('has_payment_step', '=', 'true')]):
filtered_appointments_by_type.add(appointment.id)
elif filters['type'] == 'free':
for appointment in request.env['appointment.type'].search([('has_payment_step', '!=', 'null')]):
filtered_appointments_by_type.add(appointment.id)
elif filters['type'] == 'all':
for appointment in request.env['appointment.type'].search([]):
filtered_appointments_by_type.add(appointment.id)
else:
for appointment in request.env['appointment.type'].search([]):
filtered_appointments_by_type.add(appointment.id)

if 'schedule' in filters:
if filters['schedule'] == 'resources':
for appointment in request.env['appointment.type'].search([('schedule_based_on', '=', 'resources')]):
filtered_appointments_by_schedule.add(appointment.id)
elif filters['schedule'] == 'users':
for appointment in request.env['appointment.type'].search([('schedule_based_on', '=', 'users')]):
filtered_appointments_by_schedule.add(appointment.id)
elif filters['schedule'] == 'all':
for appointment in request.env['appointment.type'].search([]):
filtered_appointments_by_schedule.add(appointment.id)
else:
for appointment in request.env['appointment.type'].search([]):
filtered_appointments_by_schedule.add(appointment.id)
Comment on lines +12 to +52

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can improve this logic because with current implementation search is called for all the filters one by one.
Instead of that we can prepare domain from getting the values from filter and we make the final search by using that domain.


filtered_appointments_by_mode = set(map(lambda id: str(id), filtered_appointments_by_mode))
filtered_appointments_by_type = set(map(lambda id: str(id), filtered_appointments_by_type))
filtered_appointments_by_schedule = set(map(lambda id: str(id), filtered_appointments_by_schedule))

filters['filter_appointment_type_ids'] = f"[{','.join(filtered_appointments_by_mode & filtered_appointments_by_type & filtered_appointments_by_schedule)}]"

if 'mode' not in filters.keys():
filters['mode'] = 'all'
if 'type' not in filters.keys():
filters['type'] = 'all'
if 'schedule' not in filters.keys():
filters['schedule'] = 'all'

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unnecessary blank line


return super().appointment_type_index(**filters)

def _prepare_appointments_cards_data(self, page, appointment_types, **kwargs):
res = super()._prepare_appointments_cards_data(page, appointment_types, **kwargs)
res.update(kwargs)
return res
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>

<template id="website_calendar_index_topbar_mode_filter" name="Filter by Mode" inherit_id="website_appointment.website_calendar_index_topbar">
<xpath expr="//t[@t-call='website.website_search_box_input']" position="before">
<t t-set="selected_mode" t-value="mode" />

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<t t-set="selected_mode" t-value="mode" />
<t t-set="selected_mode" t-value="mode" />

Can't we use mode directly instead of assigning it in to a variable
Same for other occurences

<div class="dropdown d-none d-lg-block">

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why d-none d-lg-block?

<a href="#" role="button" class="btn dropdown-toggle btn-light" data-bs-toggle="dropdown" title="Filter by Mode">
Mode <span t-if="selected_mode != 'all'" t-out="1" class="badge bg-primary ms-1"/>
</a>
<div class="dropdown-menu">
<span t-att-data-post="'/appointment?%s' % keep_query('*', mode='all')" t-attf-class="post_link cursor-pointer dropdown-item d-flex align-items-center justify-content-between #{'active' if selected_mode == 'all' else ''}">
All
</span>
<span t-att-data-post="'/appointment?%s' % keep_query('*', mode='online')" t-attf-class="post_link cursor-pointer dropdown-item d-flex align-items-center justify-content-between #{'active' if selected_mode == 'online' else ''}">
Online
</span>
<span t-att-data-post="'/appointment?%s' % keep_query('*', mode='offline')" t-attf-class="post_link cursor-pointer dropdown-item d-flex align-items-center justify-content-between #{'active' if selected_mode == 'offline' else ''}">
Offline
</span>
</div>
</div>
</xpath>
</template>

<template id="website_calendar_index_topbar_type_filter" name="Filter by Type" inherit_id="website_appointment.website_calendar_index_topbar">
<xpath expr="//t[@t-call='website.website_search_box_input']" position="before">
<t t-set="selected_type" t-value="type" />
<div class="dropdown d-none d-lg-block">
<a href="#" role="button" class="btn dropdown-toggle btn-light" data-bs-toggle="dropdown" title="Filter by Type">
Type <span t-if="selected_type != 'all'" t-out="1" class="badge bg-primary ms-1"/>
</a>
<div class="dropdown-menu">
<span t-att-data-post="'/appointment?%s' % keep_query('*', type='all')" t-attf-class="post_link cursor-pointer dropdown-item d-flex align-items-center justify-content-between #{'active' if selected_type == 'all' else ''}">
All
</span>
<span t-att-data-post="'/appointment?%s' % keep_query('*', type='paid')" t-attf-class="post_link cursor-pointer dropdown-item d-flex align-items-center justify-content-between #{'active' if selected_type == 'paid' else ''}">
Paid
</span>
<span t-att-data-post="'/appointment?%s' % keep_query('*', type='free')" t-attf-class="post_link cursor-pointer dropdown-item d-flex align-items-center justify-content-between #{'active' if selected_type == 'free' else ''}">
Free
</span>
</div>
</div>
</xpath>
</template>
Comment on lines +26 to +46

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why separate templates for filters i think we can add all the filters in same tempalate.


<template id="website_calendar_index_topbar_schedule_filter" name="Filter by Date" inherit_id="website_appointment.website_calendar_index_topbar">
<xpath expr="//t[@t-call='website.website_search_box_input']" position="before">
<t t-set="selected_schedule" t-value="schedule" />
<div class="dropdown d-none d-lg-block">
<a href="#" role="button" class="btn dropdown-toggle btn-light" data-bs-toggle="dropdown" title="Filter by Date">
Schedule <span t-if="selected_schedule != 'all'" t-out="1" class="badge bg-primary ms-1"/>
</a>
<div class="dropdown-menu">
<span t-att-data-post="'/appointment?%s' % keep_query('*', schedule='all')" t-attf-class="post_link cursor-pointer dropdown-item d-flex align-items-center justify-content-between #{'active' if selected_schedule == 'all' else ''}">
All
</span>
<span t-att-data-post="'/appointment?%s' % keep_query('*', schedule='resources')" t-attf-class="post_link cursor-pointer dropdown-item d-flex align-items-center justify-content-between #{'active' if selected_schedule == 'resources' else ''}">
Resources
</span>
<span t-att-data-post="'/appointment?%s' % keep_query('*', schedule='users')" t-attf-class="post_link cursor-pointer dropdown-item d-flex align-items-center justify-content-between #{'active' if selected_schedule == 'users' else ''}">
Users
</span>
</div>
</div>
</xpath>
</template>

</odoo>