@ -0,0 +1,52 @@ |
|||||
|
.. image:: https://img.shields.io/badge/license-AGPL--3-blue.svg |
||||
|
:target: https://www.gnu.org/licenses/agpl-3.0-standalone.html |
||||
|
:alt: License: AGPL-3 |
||||
|
|
||||
|
Event Ticket QR Code Scanner |
||||
|
============================ |
||||
|
This module helps to create QR codes for event tickets, |
||||
|
scan them, and manage invalid tickets. |
||||
|
|
||||
|
Configuration |
||||
|
============= |
||||
|
* Install qrcode package. |
||||
|
|
||||
|
License |
||||
|
------- |
||||
|
Affero General Public License v3.0 (AGPL v3) |
||||
|
(https://www.gnu.org/licenses/agpl-3.0-standalone.html) |
||||
|
|
||||
|
Company |
||||
|
------- |
||||
|
* `Cybrosys Techno Solutions <https://cybrosys.com/>`__ |
||||
|
|
||||
|
Credits |
||||
|
------- |
||||
|
* Developers: (V15) Mufeeda Shirin , |
||||
|
(V16) Anagha S, |
||||
|
(V18) Mruthul Raj, |
||||
|
* Contact: odoo@cybrosys.com |
||||
|
|
||||
|
|
||||
|
Contacts |
||||
|
-------- |
||||
|
* Mail Contact : odoo@cybrosys.com |
||||
|
* Website : https://cybrosys.com |
||||
|
|
||||
|
Bug Tracker |
||||
|
----------- |
||||
|
Bugs are tracked on GitHub Issues. In case of trouble, please check there if |
||||
|
your issue has already been reported. |
||||
|
|
||||
|
Maintainer |
||||
|
========== |
||||
|
.. image:: https://cybrosys.com/images/logo.png |
||||
|
:target: https://cybrosys.com |
||||
|
|
||||
|
This module is maintained by Cybrosys Technologies. |
||||
|
|
||||
|
For support and more information, please visit `Our Website <https://cybrosys.com/>`__ |
||||
|
|
||||
|
Further information |
||||
|
=================== |
||||
|
HTML Description: `<static/description/index.html>`__ |
@ -0,0 +1,22 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
############################################################################### |
||||
|
# |
||||
|
# Cybrosys Technologies Pvt. Ltd. |
||||
|
# |
||||
|
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
||||
|
# Author: Cybrosys Techno Solutions (odoo@cybrosys.com) |
||||
|
# |
||||
|
# You can modify it under the terms of the GNU AFFERO |
||||
|
# GENERAL PUBLIC LICENSE (AGPL v3), Version 3. |
||||
|
# |
||||
|
# This program is distributed in the hope that it will be useful, |
||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
|
# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. |
||||
|
# |
||||
|
# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE |
||||
|
# (AGPL v3) along with this program. |
||||
|
# If not, see <http://www.gnu.org/licenses/>. |
||||
|
# |
||||
|
############################################################################### |
||||
|
from . import models |
@ -0,0 +1,52 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
############################################################################### |
||||
|
# |
||||
|
# Cybrosys Technologies Pvt. Ltd. |
||||
|
# |
||||
|
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
||||
|
# Author: Cybrosys Techno Solutions (odoo@cybrosys.com) |
||||
|
# |
||||
|
# You can modify it under the terms of the GNU AFFERO |
||||
|
# GENERAL PUBLIC LICENSE (AGPL v3), Version 3. |
||||
|
# |
||||
|
# This program is distributed in the hope that it will be useful, |
||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
|
# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. |
||||
|
# |
||||
|
# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE |
||||
|
# (AGPL v3) along with this program. |
||||
|
# If not, see <http://www.gnu.org/licenses/>. |
||||
|
# |
||||
|
############################################################################### |
||||
|
{ |
||||
|
'name': 'Event Ticket QR Code Scanner', |
||||
|
'version': '18.0.1.0.0', |
||||
|
'category': 'Marketing', |
||||
|
'summary': 'QR Code Scanner for Event Ticket', |
||||
|
'description': """This module helps to create QR codes for event tickets, |
||||
|
scan them, and manage invalid tickets.""", |
||||
|
'author': 'Cybrosys Techno Solutions', |
||||
|
'company': 'Cybrosys Techno Solutions', |
||||
|
'maintainer': 'Cybrosys Techno Solutions', |
||||
|
'website': "https://www.cybrosys.com", |
||||
|
'depends': ['website_event', 'sale_management'], |
||||
|
'data': ['views/event_event_ticket.xml', |
||||
|
'views/event_event_views.xml', |
||||
|
'views/event_template.xml', |
||||
|
'report/event_event_templates.xml', |
||||
|
], |
||||
|
'assets': { |
||||
|
'web.assets_backend': [ |
||||
|
'event_ticket_qr_scanner/static/src/**/*', |
||||
|
], |
||||
|
}, |
||||
|
'external_dependencies': { |
||||
|
'python': ['qrcode'] |
||||
|
}, |
||||
|
'images': ['static/description/banner.jpg'], |
||||
|
'license': 'AGPL-3', |
||||
|
'installable': True, |
||||
|
'application': False, |
||||
|
'auto_install': False, |
||||
|
} |
@ -0,0 +1,6 @@ |
|||||
|
## Module <event_ticket_qr_scanner> |
||||
|
|
||||
|
#### 13.03.2025 |
||||
|
#### Version 18.0.1.0.0 |
||||
|
#### ADD |
||||
|
- Initial commit for Event Ticket QR Code Scanner |
@ -0,0 +1,22 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
############################################################################### |
||||
|
# |
||||
|
# Cybrosys Technologies Pvt. Ltd. |
||||
|
# |
||||
|
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
||||
|
# Author: Cybrosys Techno Solutions (odoo@cybrosys.com) |
||||
|
# |
||||
|
# You can modify it under the terms of the GNU AFFERO |
||||
|
# GENERAL PUBLIC LICENSE (AGPL v3), Version 3. |
||||
|
# |
||||
|
# This program is distributed in the hope that it will be useful, |
||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
|
# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. |
||||
|
# |
||||
|
# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE |
||||
|
# (AGPL v3) along with this program. |
||||
|
# If not, see <http://www.gnu.org/licenses/>. |
||||
|
# |
||||
|
############################################################################### |
||||
|
from . import event_event_ticket |
@ -0,0 +1,68 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
############################################################################### |
||||
|
# |
||||
|
# Cybrosys Technologies Pvt. Ltd. |
||||
|
# |
||||
|
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
||||
|
# Author: Cybrosys Techno Solutions (odoo@cybrosys.com) |
||||
|
# |
||||
|
# You can modify it under the terms of the GNU AFFERO |
||||
|
# GENERAL PUBLIC LICENSE (AGPL v3), Version 3. |
||||
|
# |
||||
|
# This program is distributed in the hope that it will be useful, |
||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
|
# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. |
||||
|
# |
||||
|
# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE |
||||
|
# (AGPL v3) along with this program. |
||||
|
# If not, see <http://www.gnu.org/licenses/>. |
||||
|
# |
||||
|
############################################################################### |
||||
|
import base64 |
||||
|
from io import BytesIO |
||||
|
|
||||
|
from odoo import fields, models |
||||
|
|
||||
|
try: |
||||
|
import qrcode |
||||
|
except ImportError: |
||||
|
qrcode = None |
||||
|
|
||||
|
|
||||
|
class EventEventTicket(models.Model): |
||||
|
"""This class extends the 'event.event.ticket' model to include |
||||
|
the generation of QR codes for event tickets.""" |
||||
|
_inherit = 'event.event.ticket' |
||||
|
|
||||
|
ticket_qr_code_image = fields.Binary(string='QRcode', attachment=False, |
||||
|
help='QR code for Event Tickets.', |
||||
|
readonly=True) |
||||
|
|
||||
|
def create(self, vals): |
||||
|
"""Generate qrcode when create new product.""" |
||||
|
res = super(EventEventTicket, self).create(vals) |
||||
|
ean = self.generate_ticket_qr(res.id) |
||||
|
res.ticket_qr_code_image = ean |
||||
|
return res |
||||
|
|
||||
|
def generate_ticket_qr(self, val_id): |
||||
|
"""Action to generate QR code for Tickets. |
||||
|
:param int val_id: id of the event. |
||||
|
:return: event ticket qrcode image.""" |
||||
|
data = self.browse(val_id) |
||||
|
if qrcode and base64: |
||||
|
code = qrcode.QRCode( |
||||
|
version=1, |
||||
|
error_correction=qrcode.constants.ERROR_CORRECT_L, |
||||
|
box_size=3, border=4) |
||||
|
code.add_data(f"Event: {data.event_id.name},") |
||||
|
code.add_data(f"code: {data.event_id.id},") |
||||
|
code.add_data(f"Ticket: {val_id},") |
||||
|
code.add_data(f"Price: {data.price}") |
||||
|
code.make(fit=True) |
||||
|
img = code.make_image() |
||||
|
temp = BytesIO() |
||||
|
img.save(temp, format="PNG") |
||||
|
qr_image = base64.b64encode(temp.getvalue()) |
||||
|
return qr_image |
@ -0,0 +1,15 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<odoo> |
||||
|
<!-- QR Code On Full page ticket --> |
||||
|
<template id="event_report_template_full_page_ticket_inherited" |
||||
|
inherit_id="event.event_report_template_full_page_ticket"> |
||||
|
<xpath expr="//div[hasclass('o_event_full_page_left_details_top')]" |
||||
|
position="inside"> |
||||
|
<t t-if="attendee"> |
||||
|
<h5>Scan The QR Code for Tickets</h5> |
||||
|
<img style="height: 100px; width: 100px;" |
||||
|
t-attf-src="data:image/png;base64,{{attendee.event_ticket_id.ticket_qr_code_image}}"/> |
||||
|
</t> |
||||
|
</xpath> |
||||
|
</template> |
||||
|
</odoo> |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 210 KiB |
After Width: | Height: | Size: 209 KiB |
After Width: | Height: | Size: 109 KiB |
After Width: | Height: | Size: 495 B |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 624 B |
After Width: | Height: | Size: 136 KiB |
After Width: | Height: | Size: 214 KiB |
After Width: | Height: | Size: 36 KiB |
After Width: | Height: | Size: 3.6 KiB |
After Width: | Height: | Size: 310 B |
After Width: | Height: | Size: 929 B |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 17 KiB |
After Width: | Height: | Size: 542 B |
After Width: | Height: | Size: 576 B |
After Width: | Height: | Size: 733 B |
After Width: | Height: | Size: 4.3 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 4.0 KiB |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 738 KiB |
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 911 B |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 600 B |
After Width: | Height: | Size: 673 B |
After Width: | Height: | Size: 2.0 KiB |
After Width: | Height: | Size: 462 B |
After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 926 B |
After Width: | Height: | Size: 9.0 KiB |
After Width: | Height: | Size: 23 KiB |
After Width: | Height: | Size: 7.0 KiB |
After Width: | Height: | Size: 878 B |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 653 B |
After Width: | Height: | Size: 800 B |
After Width: | Height: | Size: 905 B |
After Width: | Height: | Size: 189 KiB |
After Width: | Height: | Size: 4.3 KiB |
After Width: | Height: | Size: 839 B |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 5.9 KiB |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 34 KiB |
After Width: | Height: | Size: 26 KiB |
After Width: | Height: | Size: 3.8 KiB |
After Width: | Height: | Size: 23 KiB |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 2.3 KiB |
After Width: | Height: | Size: 427 B |
After Width: | Height: | Size: 627 B |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 988 B |
After Width: | Height: | Size: 3.7 KiB |
After Width: | Height: | Size: 5.0 KiB |
After Width: | Height: | Size: 875 B |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 767 KiB |
After Width: | Height: | Size: 138 KiB |
After Width: | Height: | Size: 697 KiB |
After Width: | Height: | Size: 89 KiB |
After Width: | Height: | Size: 126 KiB |
After Width: | Height: | Size: 776 KiB |
After Width: | Height: | Size: 154 KiB |
After Width: | Height: | Size: 103 KiB |
After Width: | Height: | Size: 138 KiB |
After Width: | Height: | Size: 301 KiB |
After Width: | Height: | Size: 144 KiB |
After Width: | Height: | Size: 105 KiB |
After Width: | Height: | Size: 117 KiB |
After Width: | Height: | Size: 122 KiB |
After Width: | Height: | Size: 287 KiB |
After Width: | Height: | Size: 103 KiB |
After Width: | Height: | Size: 115 KiB |
After Width: | Height: | Size: 363 KiB |
After Width: | Height: | Size: 743 KiB |
After Width: | Height: | Size: 46 KiB |
@ -0,0 +1,94 @@ |
|||||
|
/** @odoo-module **/ |
||||
|
import { registry } from "@web/core/registry"; |
||||
|
import { useService } from "@web/core/utils/hooks"; |
||||
|
import { Component, onMounted, useRef } from "@odoo/owl"; |
||||
|
import { loadJS } from "@web/core/assets"; |
||||
|
|
||||
|
export class QrcodeScanner extends Component { |
||||
|
setup() { |
||||
|
super.setup(); |
||||
|
this.result = useRef("result"); |
||||
|
this.reader = useRef("reader"); |
||||
|
this.orm = useService("orm"); |
||||
|
|
||||
|
// Ensure QR scanner loads only when the component is mounted
|
||||
|
onMounted(() => { |
||||
|
this.loadQRScanner(); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
async loadQRScanner() { |
||||
|
await loadJS("https://cdn.jsdelivr.net/npm/html5-qrcode@2.3.8/html5-qrcode.min.js"); |
||||
|
this.startQRScanner(); |
||||
|
} |
||||
|
|
||||
|
startQRScanner() { |
||||
|
if (!this.reader.el) { |
||||
|
console.error("❌ QR Scanner Error: Element with ID 'reader' not found!"); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
this.qrScanner = new Html5QrcodeScanner("reader", { |
||||
|
qrbox: { width: 250, height: 250 }, |
||||
|
fps: 20, |
||||
|
}); |
||||
|
|
||||
|
this.qrScanner.render( |
||||
|
(data) => this.onScanSuccess(data), |
||||
|
(err) => console.warn("QR Scan Error:", err) |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
async onScanSuccess(data) { |
||||
|
console.log("✅ Scanned Data:", data); |
||||
|
this.qrScanner.clear(); |
||||
|
this.reader.el.classList.add("d-none"); |
||||
|
|
||||
|
// Parsing scanned QR data
|
||||
|
const keyValuePairs = data.split(','); |
||||
|
const result = {}; |
||||
|
for (const pair of keyValuePairs) { |
||||
|
const [key, value] = pair.split(':'); |
||||
|
if (value) result[key.trim()] = value.trim(); |
||||
|
} |
||||
|
|
||||
|
if (!result['Event'] || !result['Ticket'] || !result['code']) { |
||||
|
this.result.el.innerHTML = `<h2 style="color:red;">Invalid QR Code!</h2>`; |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
var domain = [['event_ticket_id', '=', parseInt(result['Ticket'])], |
||||
|
['event_id', '=', parseInt(result['code'])], ['state', '=', 'open']]; |
||||
|
var fields = ['name', 'partner_id']; |
||||
|
|
||||
|
const attendees = await this.orm.call('event.registration', 'search_read', [domain, fields]); |
||||
|
|
||||
|
let successHTML = `<h2 style="color:green;">Success! Ticket Scanned</h2>
|
||||
|
<p>Event: <strong>${result['Event']}</strong></p>`; |
||||
|
|
||||
|
if (attendees.length === 0) { |
||||
|
successHTML += `<p style="color:red;">No Attendee Found!</p>`; |
||||
|
} else { |
||||
|
successHTML += `<label for="select_attendee_id">Select Attendee:</label>
|
||||
|
<select id="select_attendee_id" class="attendee_selection">`;
|
||||
|
attendees.forEach(attendee => { |
||||
|
successHTML += `<option value="${attendee.id}">${attendee.name}</option>`; |
||||
|
}); |
||||
|
successHTML += `</select><br/><br/>
|
||||
|
<button id="mark_as_attending" class="btn btn-primary">Mark as Attending</button>`; |
||||
|
} |
||||
|
|
||||
|
this.result.el.innerHTML = successHTML; |
||||
|
|
||||
|
if (attendees.length > 0) { |
||||
|
document.getElementById("mark_as_attending").addEventListener("click", async () => { |
||||
|
const selectedId = document.getElementById("select_attendee_id").value; |
||||
|
await this.orm.call('event.registration', 'action_set_done', [parseInt(selectedId)]); |
||||
|
location.reload(); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
QrcodeScanner.template = "event_ticket_qr_scanner.QrCodeScanner"; |
||||
|
registry.category("actions").add("js_function", QrcodeScanner); |
@ -0,0 +1,14 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8" ?> |
||||
|
<templates id="template" xml:space="preserve" owl="1"> |
||||
|
<!-- Qr Code Scanner --> |
||||
|
<t t-name="event_ticket_qr_scanner.QrCodeScanner"> |
||||
|
<t t-set="_activeClasses">border-primary</t> |
||||
|
<div> |
||||
|
<div id="reader" style="width: 600px; |
||||
|
margin-left: 182px;" t-ref="reader"> |
||||
|
</div> |
||||
|
<div id="result" style="text-align: center; font-size: 1.5rem;" t-ref="result"> |
||||
|
</div> |
||||
|
</div> |
||||
|
</t> |
||||
|
</templates> |
@ -0,0 +1,19 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||
|
<odoo> |
||||
|
<!-- Qr code image on event ticket form view --> |
||||
|
<record id="event_event_ticket_view_tree_from_event" model="ir.ui.view"> |
||||
|
<field name="name"> |
||||
|
event.event.ticket.view.tree.inherit.event.ticket.qr.scanner |
||||
|
</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"> |
||||
|
<field name="description" position="after"> |
||||
|
<field name="ticket_qr_code_image" widget="image" |
||||
|
class="oe_avatar" |
||||
|
options='{"zoom": true, "size": [30, 40]}'/> |
||||
|
</field> |
||||
|
</field> |
||||
|
</record> |
||||
|
</odoo> |
@ -0,0 +1,28 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||
|
<odoo> |
||||
|
<!-- Open Scanner--> |
||||
|
<record id="scan_ticket_qrcode" model="ir.actions.client"> |
||||
|
<field name="name">Scan Ticket Qr Code</field> |
||||
|
<field name="tag">js_function</field> |
||||
|
<field name="target">new</field> |
||||
|
</record> |
||||
|
<!-- New Button on Event form to scan Ticket QR code--> |
||||
|
<record id="view_event_form" model="ir.ui.view"> |
||||
|
<field name="name"> |
||||
|
event.event.view.form.inherit.event.ticket.qr.scanner |
||||
|
</field> |
||||
|
<field name="model">event.event</field> |
||||
|
<field name="inherit_id" ref="event.view_event_form"/> |
||||
|
<field name="priority" eval="20"/> |
||||
|
<field name="arch" type="xml"> |
||||
|
<header position="inside"> |
||||
|
<button type="action" |
||||
|
name="%(event_ticket_qr_scanner.scan_ticket_qrcode)d" |
||||
|
string="Scan Tickets" icon="fa-qrcode" |
||||
|
class="btn btn-primary scan_tickets" |
||||
|
invisible="seats_taken == 0"/> |
||||
|
<field name="seats_taken" invisible="1"/> |
||||
|
</header> |
||||
|
</field> |
||||
|
</record> |
||||
|
</odoo> |
@ -0,0 +1,16 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<odoo> |
||||
|
<!-- Add a QR Code on Event registration --> |
||||
|
<template id="registration_complete" |
||||
|
inherit_id="website_event.registration_complete"> |
||||
|
<xpath expr="//div[hasclass('o_wereg_confirmed')]/div/div" |
||||
|
position="inside"> |
||||
|
<t t-if="attendees.event_ticket_id"> |
||||
|
<div id="qr_code_image"> |
||||
|
<img style="height: 100px; width: 100px;" |
||||
|
t-attf-src="data:image/png;base64,{{attendees.event_ticket_id.ticket_qr_code_image}}"/> |
||||
|
</div> |
||||
|
</t> |
||||
|
</xpath> |
||||
|
</template> |
||||
|
</odoo> |