[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:
Odoo's Mergebot
2020-01-27 13:04:48 +01:00
committed by GitHub
5 changed files with 88 additions and 102 deletions
-7
View File
@@ -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:
+20 -37
View File
@@ -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;
}
}
}
+36 -37
View File
@@ -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>
+17 -9
View File
@@ -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
+15 -12
View File
@@ -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', '&lt;=', 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>