[MOV] event_barcode: merge into event
This PR moves event_barcode module from enterprise to community. It is now integrated directly with the base 'event' module. Using it is optional and activated directly from settings, instead of having to install a new module. This PR also allows organizers to use QR codes without needing a barcode. Some improvements are made in registration flow, notably improving barcode display and usage in checkout process and outgoing emails. Task-3411827 (Move event barcode to community) Task-3470321 (Improve registration flow) closes odoo/odoo#135153 Related: odoo/enterprise#45598 Related: odoo/upgrade#5183 Signed-off-by: Thibault Delavallee (tde) <tde@openerp.com>
This commit is contained in:
@@ -17,7 +17,7 @@ Key Features
|
||||
* Manage your Events and Registrations
|
||||
* Use emails to automatically confirm and send acknowledgments for any event registration
|
||||
""",
|
||||
'depends': ['base_setup', 'mail', 'phone_validation', 'portal', 'utm'],
|
||||
'depends': ['barcodes', 'base_setup', 'mail', 'phone_validation', 'portal', 'utm'],
|
||||
'data': [
|
||||
'security/event_security.xml',
|
||||
'security/ir.model.access.csv',
|
||||
@@ -30,6 +30,7 @@ Key Features
|
||||
'views/event_stage_views.xml',
|
||||
'report/event_event_templates.xml',
|
||||
'report/event_event_reports.xml',
|
||||
'report/event_registration_report.xml',
|
||||
'data/ir_cron_data.xml',
|
||||
'data/mail_template_data.xml',
|
||||
'data/event_data.xml',
|
||||
@@ -48,6 +49,7 @@ Key Features
|
||||
'installable': True,
|
||||
'assets': {
|
||||
'web.assets_backend': [
|
||||
'event/static/src/client_action/**/*',
|
||||
'event/static/src/scss/event.scss',
|
||||
'event/static/src/icon_selection_field/icon_selection_field.js',
|
||||
'event/static/src/icon_selection_field/icon_selection_field.xml',
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
import json
|
||||
from werkzeug.exceptions import NotFound
|
||||
|
||||
from odoo import _
|
||||
from odoo import http, _
|
||||
from odoo.http import Controller, request, route, content_disposition
|
||||
from odoo.tools import consteq
|
||||
|
||||
@@ -75,3 +75,23 @@ class EventController(Controller):
|
||||
('Content-Disposition', content_disposition(f'{report_name}.pdf')),
|
||||
]
|
||||
return request.make_response(pdf, headers=pdfhttpheaders)
|
||||
|
||||
@http.route(['/event/init_barcode_interface'], type='json', auth="user")
|
||||
def init_barcode_interface(self, event_id):
|
||||
event = request.env['event.event'].browse(event_id).exists() if event_id else False
|
||||
if event:
|
||||
return {
|
||||
'name': event.name,
|
||||
'country': event.address_id.country_id.name,
|
||||
'city': event.address_id.city,
|
||||
'company_name': event.company_id.name,
|
||||
'company_id': event.company_id.id
|
||||
}
|
||||
else:
|
||||
return {
|
||||
'name': _('Registration Desk'),
|
||||
'country': False,
|
||||
'city': False,
|
||||
'company_name': request.env.company.name,
|
||||
'company_id': request.env.company.id
|
||||
}
|
||||
|
||||
@@ -261,10 +261,17 @@
|
||||
<t t-out="object.name or 'Guest'"/>
|
||||
</span>
|
||||
</td><td valign="middle" align="right">
|
||||
<a t-attf-href="/event/{{ object.event_id.id }}/my_tickets?registration_ids={{ object.ids }}&tickets_hash={{ object.event_id._get_tickets_access_hash(object.ids) }}&responsive_html=1"
|
||||
target="_blank" style="padding: 8px 12px; font-size: 12px; color: #FFFFFF; text-decoration: none !important; font-weight: 400; background-color: #875A7B; border: 0px solid #875A7B; border-radius:3px">
|
||||
View Ticket
|
||||
</a>
|
||||
<div style="margin-bottom: 5px;">
|
||||
<a t-attf-href="/event/{{ object.event_id.id }}/my_tickets?registration_ids={{ object.ids }}&tickets_hash={{ object.event_id._get_tickets_access_hash(object.ids) }}&responsive_html=1"
|
||||
target="_blank" style="padding: 8px 12px; font-size: 12px; color: #FFFFFF; text-decoration: none !important; font-weight: 400; background-color: #875A7B; border: 0px solid #875A7B; border-radius:3px">
|
||||
View Ticket
|
||||
</a>
|
||||
</div>
|
||||
<t t-if="object.barcode">
|
||||
<div style="margin-bottom: 5px;">
|
||||
<img t-attf-src="/report/barcode/QR/{{object.barcode}}?&width=100&height=100&quiet=0" alt="QR Code"/>
|
||||
</div>
|
||||
</t>
|
||||
<t t-if="not object.company_id.uses_default_logo">
|
||||
<img t-att-src="'/logo.png?company=%s' % object.company_id.id" style="padding: 0px; margin: 0px; height: auto; width: 80px;" t-att-alt="'%s' % object.company_id.name"/>
|
||||
</t>
|
||||
|
||||
@@ -124,6 +124,7 @@ class EventEvent(models.Model):
|
||||
user_id = fields.Many2one(
|
||||
'res.users', string='Responsible', tracking=True,
|
||||
default=lambda self: self.env.user)
|
||||
use_barcode = fields.Boolean(compute='_compute_use_barcode')
|
||||
company_id = fields.Many2one(
|
||||
'res.company', string='Company', change_default=True,
|
||||
default=lambda self: self.env.company,
|
||||
@@ -236,6 +237,11 @@ class EventEvent(models.Model):
|
||||
compute='_compute_ticket_instructions', store=True, readonly=False,
|
||||
help="This information will be printed on your tickets.")
|
||||
|
||||
def _compute_use_barcode(self):
|
||||
use_barcode = self.env['ir.config_parameter'].sudo().get_param('event.use_event_barcode') == 'True'
|
||||
for record in self:
|
||||
record.use_barcode = use_barcode
|
||||
|
||||
@api.depends('stage_id', 'kanban_state')
|
||||
def _compute_kanban_state_label(self):
|
||||
for event in self:
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
import logging
|
||||
import os
|
||||
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
from odoo import _, api, fields, models, SUPERUSER_ID
|
||||
from odoo.tools import format_date, email_normalize, email_normalize_all
|
||||
from odoo.exceptions import AccessError, ValidationError
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class EventRegistration(models.Model):
|
||||
@@ -14,12 +17,27 @@ class EventRegistration(models.Model):
|
||||
_inherit = ['mail.thread', 'mail.activity.mixin']
|
||||
_order = 'id desc'
|
||||
|
||||
@api.model
|
||||
def _get_random_barcode(self):
|
||||
"""Generate a string representation of a pseudo-random 8-byte number for barcode
|
||||
generation.
|
||||
|
||||
A decimal serialisation is longer than a hexadecimal one *but* it
|
||||
generates a more compact barcode (Code128C rather than Code128A).
|
||||
|
||||
Generate 8 bytes (64 bits) barcodes as 16 bytes barcodes are not
|
||||
compatible with all scanners.
|
||||
"""
|
||||
return str(int.from_bytes(os.urandom(8), 'little'))
|
||||
|
||||
# event
|
||||
event_id = fields.Many2one(
|
||||
'event.event', string='Event', required=True)
|
||||
event_ticket_id = fields.Many2one(
|
||||
'event.event.ticket', string='Event Ticket', ondelete='restrict')
|
||||
active = fields.Boolean(default=True)
|
||||
barcode = fields.Char(string='Barcode', default=lambda self: self._get_random_barcode(), readonly=True, copy=False)
|
||||
|
||||
# utm informations
|
||||
utm_campaign_id = fields.Many2one('utm.campaign', 'Campaign', index=True, ondelete='set null')
|
||||
utm_source_id = fields.Many2one('utm.source', 'Source', index=True, ondelete='set null')
|
||||
@@ -48,7 +66,9 @@ class EventRegistration(models.Model):
|
||||
('draft', 'Unconfirmed'), ('cancel', 'Cancelled'),
|
||||
('open', 'Confirmed'), ('done', 'Attended')],
|
||||
string='Status', default='draft', readonly=True, copy=False, tracking=6)
|
||||
|
||||
_sql_constraints = [
|
||||
('barcode_event_uniq', 'unique(barcode)', "Barcode should be unique")
|
||||
]
|
||||
@api.depends('partner_id')
|
||||
def _compute_name(self):
|
||||
for registration in self:
|
||||
@@ -116,6 +136,27 @@ class EventRegistration(models.Model):
|
||||
country = self.partner_id.country_id or self.event_id.country_id or self.env.company.country_id
|
||||
self.phone = self._phone_format(fname='phone', country=country) or self.phone
|
||||
|
||||
@api.model
|
||||
def register_attendee(self, barcode, event_id):
|
||||
attendee = self.search([('barcode', '=', barcode)], limit=1)
|
||||
if not attendee:
|
||||
return {'error': 'invalid_ticket'}
|
||||
res = attendee._get_registration_summary()
|
||||
if attendee.state == 'cancel':
|
||||
status = 'canceled_registration'
|
||||
elif not attendee.event_id.is_ongoing:
|
||||
status = 'not_ongoing_event'
|
||||
elif attendee.state != 'done':
|
||||
if event_id and attendee.event_id.id != event_id:
|
||||
status = 'need_manual_confirmation'
|
||||
else:
|
||||
attendee.action_set_done()
|
||||
status = 'confirmed_registration'
|
||||
else:
|
||||
status = 'already_registered'
|
||||
res.update({'status': status, 'event_id': event_id})
|
||||
return res
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# CRUD
|
||||
# ------------------------------------------------------------
|
||||
|
||||
@@ -24,7 +24,8 @@ class ResConfigSettings(models.TransientModel):
|
||||
module_website_event_track_live = fields.Boolean("Live Mode")
|
||||
module_website_event_track_quiz = fields.Boolean("Quiz on Tracks")
|
||||
module_website_event_exhibitor = fields.Boolean("Advanced Sponsors")
|
||||
module_event_barcode = fields.Boolean("Barcode")
|
||||
use_event_barcode = fields.Boolean(string="Use Event Barcode", help="Enable or Disable Event Barcode functionality.", config_parameter='event.use_event_barcode')
|
||||
barcode_nomenclature_id = fields.Many2one('barcode.nomenclature', related='company_id.nomenclature_id', readonly=False)
|
||||
module_website_event_sale = fields.Boolean("Online Ticketing")
|
||||
module_event_booth = fields.Boolean("Booth Management")
|
||||
use_google_maps_static_api = fields.Boolean("Google Maps static API", default=_default_use_google_maps_static_api)
|
||||
|
||||
@@ -92,5 +92,15 @@
|
||||
<field name="report_name">event.event_registration_report_template_responsive_html_ticket</field>
|
||||
<field name="report_file">event.event_registration_report_template_responsive_html_ticket</field>
|
||||
</record>
|
||||
|
||||
|
||||
<record id="action_report_event_event_attendee_list" model="ir.actions.report">
|
||||
<field name="name">Attendee List</field>
|
||||
<field name="model">event.event</field>
|
||||
<field name="report_type">qweb-pdf</field>
|
||||
<field name="report_name">event.event_event_attendee_list</field>
|
||||
<field name="report_file">event.event_event_attendee_list</field>
|
||||
<field name="binding_model_id" ref="event.model_event_event"/>
|
||||
<field name="binding_type">report</field>
|
||||
<field name="print_report_name">'Attendee List - %s' % (object.name)</field>
|
||||
</record>
|
||||
</odoo>
|
||||
|
||||
@@ -15,9 +15,19 @@
|
||||
<div class="page col-6 position-relative p-1">
|
||||
<div class="oe_structure"/>
|
||||
<div class="o_event_foldable_badge_back text-center">
|
||||
<div class="o_event_foldable_badge_barcode_container_top"></div>
|
||||
<div class="o_event_foldable_badge_barcode_id_top fs-4 mt-2">
|
||||
ID: <span t-out="attendee.id if attendee else '0123456789'"/>
|
||||
<t t-if="event.use_barcode">
|
||||
<t t-if="attendee">
|
||||
<span t-field="attendee.barcode" class="barcode ms-2" t-options="{'widget': 'barcode', 'width': 200, 'height': 84, 'quiet': 0, 'humanreadable': 1}"/>
|
||||
</t>
|
||||
<t t-elif="not attendee">
|
||||
<span t-out="12345678901234567890" class="barcode ms-2" t-options="{'widget': 'barcode', 'width': 200, 'height': 84, 'quiet': 0, 'humanreadable': 1}"/>
|
||||
</t>
|
||||
</t>
|
||||
<div class="o_event_foldable_badge_barcode_container_top">
|
||||
<img t-attf-src="/report/barcode/QR/{{attendee.barcode if attendee else '12345678901234567890'}}?&width=174&height=174&quiet=0" alt="QR Code"/>
|
||||
</div>
|
||||
<div class="fs-4 mt-2">
|
||||
<span t-out="attendee.barcode if attendee else '12345678901234567890'"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -27,9 +37,20 @@
|
||||
<div class="o_event_foldable_badge_bottom_quarter page col-6 p-1">
|
||||
<div class="oe_structure"/>
|
||||
<div class="o_event_foldable_badge_back text-center">
|
||||
<div class="o_event_foldable_badge_barcode_container_bottom"></div>
|
||||
<div class="o_event_foldable_badge_barcode_id_bottom fs-4 mt-2">
|
||||
ID: <span t-out="attendee.id if attendee else '0123456789'"/>
|
||||
<t t-if="event.use_barcode">
|
||||
<t t-if="attendee">
|
||||
<span t-field="attendee.barcode" class="barcode ms-2" t-options="{'widget': 'barcode', 'width': 200, 'height': 84, 'quiet': 0, 'humanreadable': 1}"/>
|
||||
</t>
|
||||
<t t-elif="not attendee">
|
||||
<span t-out="12345678901234567890" class="barcode ms-2" t-options="{'widget': 'barcode', 'width': 200, 'height': 84, 'quiet': 0, 'humanreadable': 1}"/>
|
||||
</t>
|
||||
</t>
|
||||
<div class="o_event_foldable_badge_barcode_container_bottom">
|
||||
<img t-attf-src="/report/barcode/QR/{{attendee.barcode if attendee else '12345678901234567890'}}?&width=174&height=174&quiet=0" alt="QR Code"/>
|
||||
</div>
|
||||
|
||||
<div class="fs-4 mt-2">
|
||||
<span t-out="attendee.barcode if attendee else '12345678901234567890'"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -136,7 +157,18 @@
|
||||
<div t-if="not responsive_html" class="o_event_full_page_ticket_sponsors_container"></div>
|
||||
</div>
|
||||
<div class="o_event_full_page_ticket_barcode">
|
||||
<div class="o_event_full_page_ticket_barcode_container px-2"></div>
|
||||
|
||||
<div class="o_event_full_page_ticket_barcode_container px-2">
|
||||
<t t-if="not attendee or (attendee and attendee.barcode)">
|
||||
<div class="pb-3">
|
||||
<img t-attf-src="/report/barcode/QR/{{attendee.barcode if attendee and attendee.barcode else '12345678901234567890'}}?&width=116&height=116&quiet=0" alt="QR Code"/>
|
||||
</div>
|
||||
|
||||
<t t-if="event.use_barcode">
|
||||
<img class="o_event_barcode" t-attf-src="/report/barcode/?barcode_type=Code128&value={{attendee.barcode if attendee and attendee.barcode else '12345678901234567890'}}&width=168&height=84&humanreadable=1&quiet=0" alt="Barcode"/>
|
||||
</t>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -230,7 +262,18 @@
|
||||
</div>
|
||||
<div class="position-absolute bottom-0 w-100 text-center">
|
||||
<img t-if="event.organizer_id.image_256" class="o_event_badge_logo text-center mb-2" t-att-src="image_data_uri(event.organizer_id.image_256)"/>
|
||||
<span t-if="event.badge_format != 'A4_french_fold'" class="o_event_badge_barcode_container mb-2"></span>
|
||||
<span t-if="event.badge_format != 'A4_french_fold'" class="o_event_badge_barcode_container mb-2">
|
||||
<img t-att-class="'mb-2' + (' ms-5' if event.organizer_id.image_256 else '')" t-attf-src="/report/barcode/QR/{{attendee.barcode if attendee else '12345678901234567890'}}?&width=116&height=116&quiet=0" alt="QR Code"/>
|
||||
<t t-if="event.use_barcode">
|
||||
<t t-if="attendee">
|
||||
<span t-field="attendee.barcode" class="barcode ms-2" t-options="{'widget': 'barcode', 'width': 200, 'height': 84, 'quiet': 0, 'humanreadable': 1}"/>
|
||||
</t>
|
||||
<t t-elif="not attendee">
|
||||
<span t-out="12345678901234567890" class="barcode ms-2" t-options="{'widget': 'barcode', 'width': 200, 'height': 84, 'quiet': 0, 'humanreadable': 1}"/>
|
||||
</t>
|
||||
</t>
|
||||
</span>
|
||||
|
||||
<t t-set="first_ticket" t-value="event.event_ticket_ids[0] if event.event_ticket_ids else None"/>
|
||||
<t t-set="ticket" t-value="attendee.event_ticket_id if attendee else first_ticket"/>
|
||||
<div t-if="ticket" class="text-center w-100" t-attf-style="background-color: {{ticket.color or '#875A7B'}};">
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data>
|
||||
<record id="action_report_event_registration_attendee_list" model="ir.actions.report">
|
||||
<field name="name">Attendee List</field>
|
||||
<field name="model">event.registration</field>
|
||||
<field name="report_type">qweb-pdf</field>
|
||||
<field name="report_name">event.event_registration_attendee_list</field>
|
||||
<field name="report_file">event.event_registration_attendee_list</field>
|
||||
<field name="binding_model_id" ref="event.model_event_registration"/>
|
||||
<field name="binding_type">report</field>
|
||||
<field name="print_report_name">'Attendee List'</field>
|
||||
</record>
|
||||
</data>
|
||||
|
||||
<template id="attendee_list">
|
||||
<h1>Attendee list</h1>
|
||||
<div class="row">
|
||||
<div class="col-7">
|
||||
<div t-out="event.name"/>
|
||||
</div>
|
||||
<div class="col-5">
|
||||
<span t-out="event.date_begin"/>
|
||||
<i class="fa fa-arrow-right"/>
|
||||
<span t-out="event.date_end"/>
|
||||
</div>
|
||||
</div>
|
||||
<table class="table mt-3" style="page-break-after:always;">
|
||||
<thead>
|
||||
<tr class="text-start">
|
||||
<th>Name</th>
|
||||
<th>Company</th>
|
||||
<th>Ticket type</th>
|
||||
<th>Phone number</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr t-foreach="attendees" t-as="attendee">
|
||||
<td><t t-out="attendee.name"/></td>
|
||||
<td><t t-out="attendee.company_name"/></td>
|
||||
<td><t t-out="attendee.event_ticket_id.name"/></td>
|
||||
<td><t t-out="attendee.phone"/></td>
|
||||
<td class="text-center">
|
||||
<t t-if="attendee.barcode">
|
||||
<img t-attf-src="/report/barcode/QR/{{ attendee.barcode }}?&width=87&height=87&quiet=0" alt="QR Code"/>
|
||||
</t>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</template>
|
||||
|
||||
<template id="event_registration_attendee_list">
|
||||
<t t-call="web.html_container">
|
||||
<t t-call="web.internal_layout">
|
||||
<t t-foreach="docs.grouped('event_id').items()" t-as="group">
|
||||
<t t-call="event.attendee_list">
|
||||
<t t-set="event" t-value="group[0].with_context(tz=group[0].date_tz)"/>
|
||||
<t t-set="attendees" t-value="group[1]"/>
|
||||
</t>
|
||||
</t>
|
||||
</t>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
<template id="event_event_attendee_list">
|
||||
<t t-call="web.html_container">
|
||||
<t t-call="web.internal_layout">
|
||||
<t t-foreach="docs" t-as="event">
|
||||
<t t-call="event.attendee_list">
|
||||
<t t-set="event" t-value="event.with_context(tz=event.date_tz)"/>
|
||||
<t t-set="attendees" t-value="event.registration_ids"/>
|
||||
</t>
|
||||
</t>
|
||||
</t>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
</odoo>
|
||||
@@ -0,0 +1,111 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { _t } from "@web/core/l10n/translation";
|
||||
import { BarcodeScanner } from "@barcodes/components/barcode_scanner";
|
||||
import { Component, onWillStart } from "@odoo/owl";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { useBus, useService } from "@web/core/utils/hooks";
|
||||
import { EventRegistrationSummaryDialog } from "./event_registration_summary_dialog";
|
||||
|
||||
export class EventScanView extends Component {
|
||||
setup() {
|
||||
this.actionService = useService("action");
|
||||
this.dialog = useService("dialog");
|
||||
this.notification = useService("notification");
|
||||
this.orm = useService("orm");
|
||||
this.rpc = useService("rpc");
|
||||
|
||||
const { default_event_id, active_model, active_id } = this.props.action.context;
|
||||
this.eventId = default_event_id || (active_model === "event.event" && active_id);
|
||||
this.isMultiEvent = !this.eventId;
|
||||
|
||||
const barcode = useService("barcode");
|
||||
useBus(barcode.bus, "barcode_scanned", (ev) => this.onBarcodeScanned(ev.detail.barcode));
|
||||
|
||||
onWillStart(this.onWillStart);
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
* Fetch barcode init information. Notably eventId triggers mono- or multi-
|
||||
* event mode (Registration Desk in multi event allow to manage attendees
|
||||
* from several events and tickets without reloading / changing event in UX.
|
||||
*/
|
||||
async onWillStart() {
|
||||
this.data = await this.rpc("/event/init_barcode_interface", {
|
||||
event_id: this.eventId,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* When scanning a barcode, call Registration.register_attendee() to get
|
||||
* formatted registration information, notably its status or event-related
|
||||
* information. Open a confirmation / choice Dialog to confirm attendee.
|
||||
*/
|
||||
async onBarcodeScanned(barcode) {
|
||||
const result = await this.orm.call("event.registration", "register_attendee", [], {
|
||||
barcode: barcode,
|
||||
event_id: this.eventId,
|
||||
});
|
||||
|
||||
if (result.error && result.error === "invalid_ticket") {
|
||||
this.notification.add(_t("Invalid ticket"), {
|
||||
title: _t("Warning"),
|
||||
type: "danger",
|
||||
});
|
||||
} else {
|
||||
this.registrationId = result.id;
|
||||
this.dialog.add(EventRegistrationSummaryDialog, { registration: result });
|
||||
}
|
||||
}
|
||||
|
||||
onClickSelectAttendee() {
|
||||
if (this.isMultiEvent) {
|
||||
this.actionService.doAction("event.event_registration_action");
|
||||
} else {
|
||||
this.actionService.doAction("event.event_registration_action_kanban", {
|
||||
additionalContext: {
|
||||
active_id: this.eventId,
|
||||
search_default_unconfirmed: true,
|
||||
search_default_confirmed: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onClickBackToEvents() {
|
||||
if (this.isMultiEvent) {
|
||||
// define action from scratch instead of using existing 'action_event_view' to avoid
|
||||
// messing with menu bar
|
||||
this.actionService.doAction({
|
||||
type: "ir.actions.act_window",
|
||||
name: _t("Events"),
|
||||
res_model: "event.event",
|
||||
views: [
|
||||
[false, "kanban"],
|
||||
[false, "calendar"],
|
||||
[false, "list"],
|
||||
[false, "gantt"],
|
||||
[false, "form"],
|
||||
[false, "pivot"],
|
||||
[false, "graph"],
|
||||
[false, "map"],
|
||||
],
|
||||
target: "main",
|
||||
});
|
||||
} else {
|
||||
this.actionService.doAction({
|
||||
type: "ir.actions.act_window",
|
||||
res_model: "event.event",
|
||||
res_id: this.eventId,
|
||||
views: [[false, "form"]],
|
||||
target: "main",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
EventScanView.template = "event.EventScanView";
|
||||
EventScanView.components = { BarcodeScanner };
|
||||
|
||||
registry.category("actions").add("event.event_barcode_scan_view", EventScanView);
|
||||
@@ -0,0 +1,43 @@
|
||||
.o_event_barcode_bg {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
@include media-breakpoint-up(md) {
|
||||
@include o-position-absolute(0, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
.o_event_barcode_main {
|
||||
font-family: 'Lato', sans-serif;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
padding: 2em;
|
||||
img .o_event_barcode_image {
|
||||
width: 115px;
|
||||
height: 60px;
|
||||
}
|
||||
.o_event_barcode_company_image {
|
||||
overflow: hidden;
|
||||
margin: 1rem 0 2rem;
|
||||
max-width: 200px;
|
||||
max-height: 100px;
|
||||
}
|
||||
@include media-breakpoint-up(md) {
|
||||
flex: 0 0 auto;
|
||||
width: 550px;
|
||||
border-radius: 10px;
|
||||
-webkit-border-radius: 10px;
|
||||
-moz-border-radius: 10px;
|
||||
box-shadow: 2px 2px 10px rgba(0, 0, 0, 0.6);
|
||||
font-size: 1.2em;
|
||||
padding: 3em;
|
||||
}
|
||||
}
|
||||
.o_notification_manager {
|
||||
.o_event_success {
|
||||
color: white;
|
||||
background-color: #5BC236;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-name="event.EventScanView">
|
||||
<div class="o_event_barcode_bg o_home_menu_background">
|
||||
<div class="o_event_barcode_main bg-view">
|
||||
<a href="#" class="o_event_previous_menu float-start"><i class="oi oi-chevron-left" t-on-click.prevent="() => this.onClickBackToEvents()"></i></a>
|
||||
<div class="text-center">
|
||||
<h1 t-out="data.name"/>
|
||||
<p>
|
||||
<t t-if="data.city and data.country">
|
||||
<t t-out="data.city"/> - <t t-out="data.country"/>
|
||||
</t>
|
||||
<t t-if="data.city and !data.country" t-out="data.city"/>
|
||||
<t t-if="data.country and !data.city" t-out="data.country"/>
|
||||
</p>
|
||||
<h2><small>Welcome to</small> <t t-out="data.company_name"/></h2>
|
||||
<img t-if="data.company_id" t-attf-src="/web/image/res.company/{{data.company_id}}/logo_web" alt="Company Logo" class="o_event_barcode_company_image"/>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-5 mt16">
|
||||
<BarcodeScanner onBarcodeScanned="(ev) => this.onBarcodeScanned(ev)"/>
|
||||
<h5 class="mt8 mb0 text-muted">Scan a badge</h5>
|
||||
</div>
|
||||
<div class="col-sm-2 mt32">
|
||||
<h4 class="mt0 mb8"><i>or</i></h4>
|
||||
</div>
|
||||
<div class="col-sm-5 mt16">
|
||||
<button class="o_event_select_attendee btn btn-primary mb16" t-on-click="() => this.onClickSelectAttendee()">
|
||||
<div class="mb16 mt16">Select Attendee</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
@@ -0,0 +1,53 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { _t } from "@web/core/l10n/translation";
|
||||
import { Component } from "@odoo/owl";
|
||||
import { Dialog } from "@web/core/dialog/dialog";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
|
||||
export class EventRegistrationSummaryDialog extends Component {
|
||||
setup() {
|
||||
this.actionService = useService("action");
|
||||
this.notification = useService("notification");
|
||||
this.orm = useService("orm");
|
||||
}
|
||||
|
||||
get registration() {
|
||||
return this.props.registration;
|
||||
}
|
||||
|
||||
get needManualConfirmation() {
|
||||
return this.registration.status === "need_manual_confirmation";
|
||||
}
|
||||
|
||||
async onRegistrationConfirm() {
|
||||
await this.orm.call("event.registration", "action_set_done", [this.registration.id]);
|
||||
this.notification.add(_t("Registration confirmed"));
|
||||
this.props.close();
|
||||
}
|
||||
|
||||
onRegistrationPrintPdf() {
|
||||
this.actionService.doAction({
|
||||
type: "ir.actions.report",
|
||||
report_type: "qweb-pdf",
|
||||
report_name: `event.event_registration_report_template_badge/${this.registration.id}`,
|
||||
});
|
||||
}
|
||||
|
||||
async onRegistrationView() {
|
||||
await this.actionService.doAction({
|
||||
type: "ir.actions.act_window",
|
||||
res_model: "event.registration",
|
||||
res_id: this.registration.id,
|
||||
views: [[false, "form"]],
|
||||
target: "current",
|
||||
});
|
||||
this.props.close();
|
||||
}
|
||||
}
|
||||
EventRegistrationSummaryDialog.template = "event.EventRegistrationSummaryDialog";
|
||||
EventRegistrationSummaryDialog.components = { Dialog };
|
||||
EventRegistrationSummaryDialog.props = {
|
||||
close: Function,
|
||||
registration: Object,
|
||||
};
|
||||
@@ -0,0 +1,64 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-name="event.EventRegistrationSummaryDialog" >
|
||||
<Dialog size="'md'" title="'Registration Summary'">
|
||||
<div class="row">
|
||||
<div class="col-lg-8 offset-lg-2">
|
||||
<div class="text-center mb-3" t-if="registration.partner_id">
|
||||
<img t-attf-src="/web/image/res.partner/{{registration.partner_id}}/avatar_128" alt="Registration" class="o_image_64_cover"/>
|
||||
</div>
|
||||
<div t-if="registration.status === 'confirmed_registration'" class="alert alert-success text-center" role="alert">
|
||||
<t t-out="registration.name"/> is successfully registered
|
||||
</div>
|
||||
<div t-else="" class="alert alert-warning text-center" role="alert">
|
||||
<t t-if="registration.status === 'need_manual_confirmation'">
|
||||
<span>This ticket is for another event<br/>
|
||||
Confirm attendance for <t t-out="registration.name"/>?</span>
|
||||
</t>
|
||||
<t t-elif="registration.status === 'not_ongoing_event'">
|
||||
<span>This ticket is not for an ongoing event</span>
|
||||
</t>
|
||||
<t t-elif="registration.status === 'canceled_registration'">
|
||||
<span>Canceled registration</span>
|
||||
</t>
|
||||
<t t-elif="registration.status == 'already_registered'">
|
||||
<t t-out="registration.name"/><span> is already registered</span>
|
||||
</t>
|
||||
</div>
|
||||
<div t-if="registration.has_to_pay" class="alert alert-danger text-center" role="alert">
|
||||
The registration must be paid
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="registration_information" class="row">
|
||||
<div class="col-lg-12">
|
||||
<table class="table table-striped">
|
||||
<tr><td>Event</td><td><t t-out="registration.event_display_name"/></td></tr>
|
||||
<tr t-if="!registration.event_id and registration.company_name"><td>Company</td><td><t t-out="registration.company_name"/></td></tr>
|
||||
<tr><td>Name</td><td><t t-out="registration.name"/></td></tr>
|
||||
<tr t-if="registration.ticket_name"><td>Ticket</td><td><t t-out="registration.ticket_name"/></td></tr>
|
||||
<tr t-if="registration.payment_status_value"><td>Payment</td><td><t t-out="registration.payment_status_value"/></td></tr>
|
||||
<tr t-if="registration.registration_answers">
|
||||
<td>Answers</td>
|
||||
<td>
|
||||
<span t-foreach="registration.registration_answers" t-as="registration_answer" t-key="registration_answer_index"
|
||||
t-attf-class="o_tag o_tag_badge_text o_tag_color_#{registration_answer_index % 10} badge rounded-pill p-1 me-1 mb-1"
|
||||
t-out="registration_answer"/>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<t t-set-slot="footer">
|
||||
<button t-if="needManualConfirmation" class="btn btn-primary" t-on-click="() => this.onRegistrationConfirm()">Confirm</button>
|
||||
<button class="btn" t-att-class="needManualConfirmation ? 'btn-secondary' : 'btn-primary'" t-on-click="() => this.props.close()">Close</button>
|
||||
<button class="btn btn-secondary" t-on-click="() => this.onRegistrationPrintPdf()">Print</button>
|
||||
<button class="btn btn-secondary" t-on-click="() => this.onRegistrationView()">View</button>
|
||||
</t>
|
||||
</Dialog>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
@@ -0,0 +1,62 @@
|
||||
.o_event_foldable_badge_container {
|
||||
.o_event_foldable_badge_top {
|
||||
height: 148mm;
|
||||
|
||||
&.o_event_foldable_badge_ticket {
|
||||
border-left: 1px dashed black;
|
||||
}
|
||||
}
|
||||
|
||||
.o_event_foldable_badge_font_small {
|
||||
font-size: .8rem;
|
||||
}
|
||||
|
||||
.o_event_foldable_badge_bottom.o_event_foldable_badge_left {
|
||||
border-top: 1px dashed black;
|
||||
height: 148mm;
|
||||
|
||||
p {
|
||||
margin: 0px; // to match editor style (.o_field_html p)
|
||||
}
|
||||
}
|
||||
|
||||
.o_event_foldable_badge_bottom.o_event_foldable_badge_right {
|
||||
border-left: 1px dashed black;
|
||||
border-top:1px dashed black;
|
||||
height: 148mm;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.o_event_foldable_badge_ticket {
|
||||
.o_event_foldable_badge_ticket_wrapper {
|
||||
background-color: white;
|
||||
border: solid 1px #939393;
|
||||
margin: 0px 8px;
|
||||
padding: 10px 3px;
|
||||
font-size: 1.2rem;
|
||||
box-shadow: -3px 3px 9px 0px rgba(0,0,0,0.51);
|
||||
|
||||
.o_event_foldable_badge_ticket_wrapper_top {
|
||||
min-height: 80mm;
|
||||
}
|
||||
|
||||
.o_event_foldable_badge_font_faded {
|
||||
color: #939393;
|
||||
}
|
||||
|
||||
.o_event_foldable_badge_barcode {
|
||||
min-height: 35mm;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.o_event_foldable_badge_step {
|
||||
position: absolute;
|
||||
padding: 3px 9px;
|
||||
top: 5mm;
|
||||
left: 0mm;
|
||||
border-radius: 50%;
|
||||
background-color: black;
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
@@ -44,3 +44,14 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.o_event_full_page_ticket_container .o_event_full_page_ticket_barcode {
|
||||
border-left: dotted 2px #939393;
|
||||
.o_event_barcode {
|
||||
// Barcode image has fixed size. These values are hard-coded to
|
||||
// have it aligned correctly in the pdf report.
|
||||
margin: 28px 0 0 -28px;
|
||||
-webkit-transform: rotate(90deg);
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
<?xml version="1.0"?>
|
||||
<odoo><data>
|
||||
<record id="event_barcode_action_main_view" model="ir.actions.client">
|
||||
<field name="name">Barcode Interface</field>
|
||||
<field name="tag">event.event_barcode_scan_view</field>
|
||||
<field name="target">fullscreen</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="view_event_form">
|
||||
<field name="name">event.event.form</field>
|
||||
@@ -7,6 +12,11 @@
|
||||
<field name="arch" type="xml">
|
||||
<form string="Events" class="o_event_form_view">
|
||||
<header>
|
||||
<button name="%(event_barcode_action_main_view)d"
|
||||
type="action"
|
||||
context="{'default_event_id': active_id}">
|
||||
Registration Desk
|
||||
</button>
|
||||
<field name="stage_id" widget="statusbar" options="{'clickable': '1'}"/>
|
||||
</header>
|
||||
<sheet>
|
||||
@@ -27,6 +37,17 @@
|
||||
help="Total Registrations for this Event">
|
||||
<field name="seats_expected" widget="statinfo" string="Attendees"/>
|
||||
</button>
|
||||
|
||||
<button name="%(event_barcode_action_main_view)d"
|
||||
type="action"
|
||||
class="oe_stat_button"
|
||||
icon="fa-mobile"
|
||||
context="{'default_event_id': active_id}">
|
||||
<div class="o_stat_info">
|
||||
<span class="o_stat_text">Registration Desk</span>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
</div>
|
||||
<field name="active" invisible="1"/>
|
||||
<field name="company_id" invisible="1"/>
|
||||
@@ -303,4 +324,11 @@
|
||||
<field name="action" ref="event.action_event_view"/>
|
||||
</record>
|
||||
|
||||
<!-- EVENT.EVENT HEADER: REGISTRATION DESK MENU -->
|
||||
<menuitem name="Registration Desk"
|
||||
id="menu_event_registration_desk"
|
||||
sequence="30"
|
||||
action="event.event_barcode_action_main_view"
|
||||
parent="event.event_main_menu"
|
||||
groups="event.group_event_registration_desk"/>
|
||||
</data></odoo>
|
||||
|
||||
@@ -69,6 +69,7 @@
|
||||
<group string="Event Information" name="event">
|
||||
<field class="text-break" name="event_id" readonly="state != 'draft'"
|
||||
context="{'name_with_seats_availability': True}" options="{'no_create': True}"/>
|
||||
<field name="barcode" groups="base.group_no_one"/>
|
||||
<field name="event_ticket_id" invisible="not event_id" readonly="state != 'draft'"
|
||||
context="{'name_with_seats_availability': True}" options="{'no_open': True, 'no_create': True}"
|
||||
domain="[('event_id', '=', event_id)]"/>
|
||||
|
||||
@@ -46,8 +46,12 @@
|
||||
</setting>
|
||||
</block>
|
||||
<block title="Attendance" name="attendance_setting_container">
|
||||
<setting id="event_barcode" company_dependent="1" help="Scan badges to confirm attendances">
|
||||
<field name="module_event_barcode" widget="upgrade_boolean"/>
|
||||
<setting id="event" company_dependent="1" help="Enable barcode scanning">
|
||||
<field name="use_event_barcode"/>
|
||||
<div class="content-group row mt16" invisible="use_event_barcode==False">
|
||||
<label for="barcode_nomenclature_id" string="Barcode Nomenclature" class="col-lg-3 o_light_label"/>
|
||||
<field name="barcode_nomenclature_id" required="use_event_barcode==True"/>
|
||||
</div>
|
||||
</setting>
|
||||
</block>
|
||||
</app>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
"base.menu_theme_store",
|
||||
"base.menu_third_party",
|
||||
"account.menu_action_account_bank_journal_form",
|
||||
"event_barcode.menu_event_registration_desk", // there's no way to come back from this menu
|
||||
"event.menu_event_registration_desk", // there's no way to come back from this menu
|
||||
"hr_attendance.menu_hr_attendance_kiosk_no_user_mode", // same here
|
||||
"pos_adyen.menu_pos_adyen_account",
|
||||
"payment_odoo.menu_adyen_account",
|
||||
|
||||
@@ -422,7 +422,7 @@
|
||||
"widget": "contact",
|
||||
"fields": ["address"]
|
||||
}'/>
|
||||
<div itemprop="location" t-field="event.address_id" t-options='{
|
||||
<div itemprop="contactInfo" t-field="event.organizer_id" t-options='{
|
||||
"widget": "contact",
|
||||
"fields": ["phone", "mobile", "email"]
|
||||
}'/>
|
||||
|
||||
Reference in New Issue
Block a user