-
Notifications
You must be signed in to change notification settings - Fork 2.2k
[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
Changes from all commits
97549be
69e1a02
4706d7e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
from . import models |
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', | ||
] | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
from . import event_ticket |
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.") |
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> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
from . import models | ||
from . import wizard |
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', | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
from . import sale_order_line |
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, | ||
} | ||
} |
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 |
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> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
from . import cost_distribution_wizard |
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 |
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> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
from . import controllers |
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, | ||
'installable': True, | ||
|
||
'data': [ | ||
'views/appointment_templates_appointment_filters.xml' | ||
], | ||
|
||
'license':'LGPL-3', | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
from . import main |
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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. |
||
|
||
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' | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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" /> | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Can't we use mode directly instead of assigning it in to a variable |
||||||
<div class="dropdown d-none d-lg-block"> | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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> |
There was a problem hiding this comment.
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