[MERGE] event(_sale): improve event kanban view and sales information
PURPOSE
Improve event kanban view and improve sales information in kanban and form
views. Purpose is to give a better UI to users using event on a daily basis.
SPECIFICATIONS
Improve sale computation on event. Set the total sales price of an event to
be the converted sum of order lines, tax excluded. Currency is now a related
on the company to avoid de-synchronizing events and companies.
Move total sales information from kanban to form view :
* remove sales information from event kanban view;
* add it as a stat button on form view;
* clicking on this button should redirect to the orders linked to that
event, in no create mode to avoid creating de-synchronized data;
Improve kanban card display of events. Have more useful information on the
kanban record and removed redundant information :
* Display the location of the event
* Remove the duration
* Date should only be in the green block
* Use the same links as before
Task 2170831
PR #43173
Signed-off-by: Thibault Delavallee (tde) <tde@openerp.com>
This commit is contained in:
@@ -9,7 +9,6 @@ from odoo.addons.base.models.res_partner import _tz_get
|
||||
from odoo.tools import format_datetime
|
||||
from odoo.exceptions import ValidationError
|
||||
from odoo.tools.translate import html_translate
|
||||
from odoo.tools.misc import _format_time_ago
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -128,7 +127,6 @@ class EventEvent(models.Model):
|
||||
legend_blocked = fields.Char(related='stage_id.legend_blocked', string='Kanban Blocked Explanation', readonly=True)
|
||||
legend_done = fields.Char(related='stage_id.legend_done', string='Kanban Valid Explanation', readonly=True)
|
||||
legend_normal = fields.Char(related='stage_id.legend_normal', string='Kanban Ongoing Explanation', readonly=True)
|
||||
duration_str = fields.Char(compute='_compute_duration_str')
|
||||
# Seats and computation
|
||||
seats_max = fields.Integer(
|
||||
string='Maximum Attendees Number',
|
||||
@@ -265,11 +263,6 @@ class EventEvent(models.Model):
|
||||
end_tz = fields.Datetime.context_timestamp(event, event.date_end)
|
||||
event.is_one_day = (begin_tz.date() == end_tz.date())
|
||||
|
||||
@api.depends('date_begin', 'date_end')
|
||||
def _compute_duration_str(self):
|
||||
for event in self:
|
||||
event.duration_str = _format_time_ago(self.env, event.date_end - event.date_begin, add_direction=False)
|
||||
|
||||
@api.onchange('is_online')
|
||||
def _onchange_is_online(self):
|
||||
if self.is_online:
|
||||
|
||||
@@ -1,44 +1,27 @@
|
||||
.o_kanban_view.o_event_kanban_view {
|
||||
.o_kanban_record {
|
||||
position: relative;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
.o_kanban_record_title {
|
||||
font-size: 1.2em;
|
||||
}
|
||||
.o_event_left {
|
||||
flex: 1 1 25%;
|
||||
color: white;
|
||||
background-color: $o-brand-primary;
|
||||
padding: 8px;
|
||||
.o_day {
|
||||
font-size: 26px;
|
||||
}
|
||||
}
|
||||
.o_event_right {
|
||||
padding: 8px;
|
||||
margin-left: 8px;
|
||||
width: 70%;
|
||||
.o_kanban_content {
|
||||
min-height: 130px;
|
||||
|
||||
.o_event_stat {
|
||||
padding: 0;
|
||||
height: 35px;
|
||||
width: 60%;
|
||||
}
|
||||
.o_event_fontweight_500 {
|
||||
font-weight: 500;
|
||||
}
|
||||
.o_event_float_right {
|
||||
right: 8px;
|
||||
top: 40px;
|
||||
|
||||
.o_event_fontsize_09 {
|
||||
font-size: .9rem;
|
||||
}
|
||||
.o_event_bottom_right {
|
||||
width: 60px;
|
||||
right: 8px;
|
||||
bottom: 8px;
|
||||
img {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.o_event_fontsize_11 {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.o_event_fontsize_20 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.o_event_fit_20 {
|
||||
height: 20px;
|
||||
object-fit: cover;
|
||||
width: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -328,6 +328,7 @@
|
||||
<field name="arch" type="xml">
|
||||
<kanban class="o_event_kanban_view" default_group_by="stage_id" quick_create_view="event.event_event_view_form_quick_create">
|
||||
<field name="user_id"/>
|
||||
<field name="name"/>
|
||||
<field name="stage_id" options='{"group_by_tooltip": {"description": "Description"}}'/>
|
||||
<field name="address_id"/>
|
||||
<field name="date_begin"/>
|
||||
@@ -340,51 +341,49 @@
|
||||
<field name="legend_blocked"/>
|
||||
<field name="legend_normal"/>
|
||||
<field name="legend_done"/>
|
||||
<field name="duration_str"/>
|
||||
<field name="activity_ids"/>
|
||||
<field name="activity_state"/>
|
||||
<templates>
|
||||
<t t-name="kanban-box">
|
||||
<div t-attf-class="{{!selection_mode ? 'oe_kanban_color_' + kanban_getcolor(record.color.raw_value) : ''}} oe_kanban_card oe_kanban_global_click">
|
||||
<div class="o_event_left text-center">
|
||||
<div class="o_day"><t t-esc="record.date_begin.raw_value.getDate()"/></div>
|
||||
<div>
|
||||
<t t-esc="moment(record.date_begin.raw_value).format('MMM')"/>
|
||||
<t t-esc="record.date_begin.raw_value.getFullYear()"/>
|
||||
</div>
|
||||
<div><t t-esc="moment(record.date_begin.raw_value).format('LT')"/></div>
|
||||
</div>
|
||||
<div class="o_event_right d-flex flex-column justify-content-between">
|
||||
<div>
|
||||
<h1 class="o_kanban_record_title"><field name="name"/></h1>
|
||||
<p t-esc="record.address_id.value"/>
|
||||
</div>
|
||||
<div class="o_event_duration_str">Duration <field name="duration_str"/></div>
|
||||
<div class="o_event_stat text-left d-flex justify-content-between">
|
||||
<div class="text-left inline-block mr-2">
|
||||
<button
|
||||
type="action"
|
||||
class="btn btn-link p-0"
|
||||
name="%(event_registration_action_tree)d"
|
||||
t-esc="record.seats_expected.raw_value"/>
|
||||
<p>Expected</p>
|
||||
<div t-attf-class="p-0 {{!selection_mode ? 'oe_kanban_color_' + kanban_getcolor(record.color.raw_value) : ''}} oe_kanban_card oe_kanban_global_click">
|
||||
<div class="o_kanban_content p-0 m-0 position-relative row d-flex">
|
||||
<div class="col-3 bg-primary p-2 text-center d-flex flex-column justify-content-center">
|
||||
<div t-esc="record.date_begin.raw_value.getDate()" class="o_event_fontsize_20"/>
|
||||
<div>
|
||||
<t t-esc="moment(record.date_begin.raw_value).format('MMM')"/>
|
||||
<t t-esc="record.date_begin.raw_value.getFullYear()"/>
|
||||
</div>
|
||||
<div class="text-left inline-block">
|
||||
<button
|
||||
type="action"
|
||||
class="btn btn-link my-0 p-0"
|
||||
name="%(event_registration_action_tree)d"
|
||||
context="{'search_default_confirmed': True}"
|
||||
t-esc="record.seats_reserved.raw_value"/>
|
||||
<p>Confirmed</p>
|
||||
<div><t t-esc="moment(record.date_begin.raw_value).format('LT')"/></div>
|
||||
<div t-if="moment(record.date_begin.raw_value).dayOfYear() !== moment(record.date_end.raw_value).dayOfYear()">
|
||||
<i class="fa fa-arrow-right o_event_fontsize_09" title="End date"/>
|
||||
<t t-esc="moment(record.date_end.raw_value).format('D MMM')"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-9 py-2 px-3 d-flex flex-column justify-content-between pt-3">
|
||||
<div>
|
||||
<h4 class="o_event_fontweight_500 mb-0 o_text_overflow" t-att-title="record.name.value"><field name="name"/></h4>
|
||||
<div><i class="fa fa-map-marker" title="Location"/> <span class="o_text_overflow o_event_kanban_location" t-esc="record.address_id.value"/></div>
|
||||
</div>
|
||||
<h5 class="o_event_fontsize_11 p-0">
|
||||
<a name="%(act_event_registration_from_event)d" type="action">
|
||||
<t t-esc="record.seats_expected.raw_value"/> Expected attendees
|
||||
</a>
|
||||
<t t-set="total_seats" t-value="record.seats_reserved.raw_value + record.seats_used.raw_value"/>
|
||||
<t t-if="total_seats > 0 "><br/>
|
||||
<a name="%(event_event_action_pivot)d" type="action">
|
||||
<t t-esc="total_seats"/> Confirmed attendees
|
||||
</a>
|
||||
</t>
|
||||
</h5>
|
||||
<div class="text-right">
|
||||
<div class="float-left"><field name="activity_ids" widget="kanban_activity"/></div>
|
||||
<field name="kanban_state" widget="state_selection"/>
|
||||
<t t-set="unassigned"><t t-esc="_t('Unassigned')"/></t>
|
||||
<img class="oe_kanban_avatar o_event_fit_20 ml-2" t-att-src="kanban_image('res.users', 'image_128', record.user_id.raw_value)"
|
||||
t-att-title="record.user_id.value || unassigned" t-att-alt="record.user_id.value"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="o_event_bottom_right d-flex justify-content-between align-items-center position-absolute">
|
||||
<field name="activity_ids" widget="kanban_activity"/>
|
||||
<field name="kanban_state" widget="state_selection"/>
|
||||
<img t-att-src="kanban_image('res.users', 'image_128', record.user_id.raw_value)" t-att-title="record.user_id.value" t-att-alt="record.user_id.value"/>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
|
||||
@@ -37,16 +37,13 @@ class Event(models.Model):
|
||||
event_ticket_ids = fields.One2many(
|
||||
'event.event.ticket', 'event_id', string='Event Ticket',
|
||||
copy=True)
|
||||
|
||||
sale_order_lines_ids = fields.One2many(
|
||||
'sale.order.line', 'event_id',
|
||||
string='All sale order lines pointing to this event')
|
||||
|
||||
sale_total_price = fields.Monetary(compute='_compute_sale_total_price')
|
||||
sale_price_subtotal = fields.Monetary(string='Sales (Tax Excluded)', compute='_compute_sale_price_subtotal')
|
||||
currency_id = fields.Many2one(
|
||||
'res.currency', string='Currency',
|
||||
default=lambda self: self.env.company.currency_id.id, readonly=True)
|
||||
|
||||
related='company_id.currency_id', readonly=True)
|
||||
start_sale_date = fields.Date('Start sale date', compute='_compute_start_sale_date')
|
||||
|
||||
@api.onchange('event_type_id')
|
||||
@@ -67,12 +64,14 @@ class Event(models.Model):
|
||||
start_dates = [ticket.start_sale_date for ticket in event.event_ticket_ids if ticket.start_sale_date]
|
||||
event.start_sale_date = min(start_dates) if start_dates else False
|
||||
|
||||
@api.depends('sale_order_lines_ids')
|
||||
def _compute_sale_total_price(self):
|
||||
@api.depends('company_id.currency_id',
|
||||
'sale_order_lines_ids.price_subtotal', 'sale_order_lines_ids.currency_id',
|
||||
'sale_order_lines_ids.company_id', 'sale_order_lines_ids.order_id.date_order')
|
||||
def _compute_sale_price_subtotal(self):
|
||||
for event in self:
|
||||
event.sale_total_price = sum([
|
||||
event.sale_price_subtotal = sum([
|
||||
event.currency_id._convert(
|
||||
sale_order_line_id.price_reduce_taxexcl,
|
||||
sale_order_line_id.price_subtotal,
|
||||
sale_order_line_id.currency_id,
|
||||
sale_order_line_id.company_id,
|
||||
sale_order_line_id.order_id.date_order)
|
||||
@@ -84,3 +83,12 @@ class Event(models.Model):
|
||||
non_open_events = self.filtered(lambda event: not any(event.event_ticket_ids.mapped('sale_available')))
|
||||
non_open_events.event_registrations_open = False
|
||||
super(Event, self - non_open_events)._compute_event_registrations_open()
|
||||
|
||||
def action_view_linked_orders(self):
|
||||
""" Redirects to the orders linked to the current events """
|
||||
sale_order_action = self.env.ref('sale.action_orders').read()[0]
|
||||
sale_order_action.update({
|
||||
'domain': [('state', '!=', 'cancel'), ('order_line.event_id', 'in', self.ids)],
|
||||
'context': {'create': 0},
|
||||
})
|
||||
return sale_order_action
|
||||
|
||||
@@ -88,6 +88,21 @@
|
||||
<field name="model">event.event</field>
|
||||
<field name="inherit_id" ref="event.view_event_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//div[hasclass('oe_button_box')]" position="inside">
|
||||
<field name="currency_id" invisible="1"/>
|
||||
<button name="action_view_linked_orders"
|
||||
type="object" class="oe_stat_button" icon="fa-dollar"
|
||||
help="Total sales for this event"
|
||||
attrs="{'invisible': ['|', ('sale_price_subtotal', '=', 0), ('sale_price_subtotal', '=', False)]}">
|
||||
<div class="o_field_widget o_stat_info">
|
||||
<span class="o_stat_value">
|
||||
<field string="Sales" name="sale_price_subtotal"
|
||||
widget="monetary" options="{'currency_field': 'currency_id'}"/>
|
||||
</span>
|
||||
<span class="o_stat_text">Sales</span>
|
||||
</div>
|
||||
</button>
|
||||
</xpath>
|
||||
<page name="event_communication" position="before">
|
||||
<page string="Tickets" name="tickets">
|
||||
<field name="event_ticket_ids" context="{'default_name': name}" mode="tree,kanban">
|
||||
@@ -145,18 +160,6 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="event_view_kanban" model="ir.ui.view">
|
||||
<field name="name">event.kanban.inherit</field>
|
||||
<field name="model">event.event</field>
|
||||
<field name="inherit_id" ref="event.view_event_kanban"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//div[hasclass('o_event_duration_str')]" position="after">
|
||||
<field name="currency_id" invisible="1"/>
|
||||
<div attrs="{'invisible': [('sale_total_price', '<=', 0)]}">Sales <field name="sale_total_price" widget="monetary"/></div>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="event_ticket_form_view" model="ir.ui.view">
|
||||
<field name="name">event.event.ticket.form</field>
|
||||
<field name="model">event.event.ticket</field>
|
||||
|
||||
Reference in New Issue
Block a user