Initial commit: Sale packages

This commit is contained in:
Ernad Husremovic 2025-08-29 15:20:49 +02:00
commit 14e3d26998
6469 changed files with 2479670 additions and 0 deletions

View file

@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import event_sale_report

View file

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="event_report_template_full_page_ticket_inherit_sale" inherit_id="event.event_report_template_full_page_ticket">
<xpath expr="//div[hasclass('o_event_full_page_ticket_side_info_booked_by')]" position="before">
<div t-if="attendee and attendee.sale_order_id" class="mb-2">
<div class="o_event_full_page_ticket_font_faded o_event_full_page_ticket_small_caps fw-bold">Order Ref</div>
<div class="o_event_full_page_ticket_small" t-field="attendee.sale_order_id"/>
</div>
</xpath>
<xpath expr="//div[hasclass('o_event_full_page_ticket_side_info')]" position="inside">
<div t-if="attendee and attendee.sale_order_id" class="mb-2">
<div class="o_event_full_page_ticket_font_faded o_event_full_page_ticket_small_caps fw-bold">Order Date</div>
<div class="o_event_full_page_ticket_small" t-out="attendee.sale_order_id.sudo().date_order.date()"/>
</div>
<div t-if="attendee and attendee.sale_order_line_id.sudo().price_unit">
<div class="o_event_full_page_ticket_font_faded o_event_full_page_ticket_small_caps fw-bold">Price</div>
<div class="o_event_full_page_ticket_small" t-field="attendee.sale_order_line_id.sudo().price_unit"
t-options="{'widget': 'monetary', 'display_currency': attendee.sale_order_line_id.sudo().currency_id}"/>
</div>
</xpath>
</template>
</odoo>

View file

@ -0,0 +1,137 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import fields, models, tools
class EventSaleReport(models.Model):
"""Event Registrations-based sales report, allowing to analyze sales and number of seats
by event (type), ticket, etc. Each opened record will also give access to all this information."""
_name = 'event.sale.report'
_description = 'Event Sales Report'
_auto = False
_rec_name = 'sale_order_line_id'
event_type_id = fields.Many2one('event.type', string='Event Type', readonly=True)
event_id = fields.Many2one('event.event', string='Event', readonly=True)
event_date_begin = fields.Date(string='Event Start Date', readonly=True)
event_date_end = fields.Date(string='Event End Date', readonly=True)
event_ticket_id = fields.Many2one('event.event.ticket', string='Event Ticket', readonly=True)
event_ticket_price = fields.Float(string='Ticket price', readonly=True)
event_registration_create_date = fields.Date(string='Registration Date', readonly=True)
event_registration_state = fields.Selection([
('draft', 'Unconfirmed'), ('cancel', 'Cancelled'),
('open', 'Confirmed'), ('done', 'Attended')],
string='Registration Status', readonly=True)
active = fields.Boolean('Is registration active (not archived)?')
event_registration_id = fields.Many2one('event.registration', readonly=True)
event_registration_name = fields.Char('Attendee Name', readonly=True)
product_id = fields.Many2one('product.product', string='Product', readonly=True)
sale_order_id = fields.Many2one('sale.order', readonly=True)
sale_order_date = fields.Datetime('Order Date', readonly=True)
sale_order_partner_id = fields.Many2one('res.partner', string='Customer', readonly=True)
sale_order_state = fields.Selection([
('draft', 'Quotation'),
('sent', 'Quotation Sent'),
('sale', 'Sales Order'),
('done', 'Locked'),
('cancel', 'Cancelled'),
], string='Sale Order Status', readonly=True)
sale_order_user_id = fields.Many2one('res.users', string='Salesperson', readonly=True)
sale_order_line_id = fields.Many2one('sale.order.line', readonly=True)
sale_price = fields.Float('Revenues', readonly=True)
sale_price_untaxed = fields.Float('Untaxed Revenues', readonly=True)
invoice_partner_id = fields.Many2one('res.partner', string='Invoice Address', readonly=True)
is_paid = fields.Boolean('Is Paid', readonly=True)
payment_status = fields.Selection(string="Payment Status", selection=[
('to_pay', 'Not Paid'),
('paid', 'Paid'),
('free', 'Free'),
])
company_id = fields.Many2one('res.company', string='Company', readonly=True)
def init(self):
tools.drop_view_if_exists(self._cr, self._table)
self._cr.execute('CREATE OR REPLACE VIEW %s AS (%s);' % (self._table, self._query()))
def _query(self, with_=None, select=None, join=None, group_by=None):
return "\n".join([
self._with_clause(*(with_ or [])),
self._select_clause(*(select or [])),
self._from_clause(*(join or [])),
self._group_by_clause(*(group_by or []))
])
def _with_clause(self, *with_):
# Extra clauses formatted as `cte1 AS (SELECT ...)`, `cte2 AS (SELECT ...)`...
return """
WITH
""" + ',\n '.join(with_) if with_ else ''
def _select_clause(self, *select):
# Extra clauses formatted as `cte1.column1 AS new_column1`, `table1.column2 AS new_column2`...
return """
SELECT
ROW_NUMBER() OVER (ORDER BY event_registration.id) AS id,
event_registration.id AS event_registration_id,
event_registration.company_id AS company_id,
event_registration.event_id AS event_id,
event_registration.event_ticket_id AS event_ticket_id,
event_registration.create_date AS event_registration_create_date,
event_registration.name AS event_registration_name,
event_registration.state AS event_registration_state,
event_registration.active AS active,
event_registration.sale_order_id AS sale_order_id,
event_registration.sale_order_line_id AS sale_order_line_id,
event_registration.is_paid AS is_paid,
event_event.event_type_id AS event_type_id,
event_event.date_begin AS event_date_begin,
event_event.date_end AS event_date_end,
event_event_ticket.price AS event_ticket_price,
sale_order.date_order AS sale_order_date,
sale_order.partner_invoice_id AS invoice_partner_id,
sale_order.partner_id AS sale_order_partner_id,
sale_order.state AS sale_order_state,
sale_order.user_id AS sale_order_user_id,
sale_order_line.product_id AS product_id,
CASE
WHEN sale_order_line.product_uom_qty = 0 THEN 0
ELSE
sale_order_line.price_total
/ CASE COALESCE(sale_order.currency_rate, 0) WHEN 0 THEN 1.0 ELSE sale_order.currency_rate END
/ sale_order_line.product_uom_qty
END AS sale_price,
CASE
WHEN sale_order_line.product_uom_qty = 0 THEN 0
ELSE
sale_order_line.price_subtotal
/ CASE COALESCE(sale_order.currency_rate, 0) WHEN 0 THEN 1.0 ELSE sale_order.currency_rate END
/ sale_order_line.product_uom_qty
END AS sale_price_untaxed,
CASE
WHEN sale_order_line.price_total = 0 THEN 'free'
WHEN event_registration.is_paid THEN 'paid'
ELSE 'to_pay'
END payment_status""" + (',\n ' + ',\n '.join(select) if select else '')
def _from_clause(self, *join_):
# Extra clauses formatted as `column1`, `column2`...
return """
FROM event_registration
LEFT JOIN event_event ON event_event.id = event_registration.event_id
LEFT JOIN event_event_ticket ON event_event_ticket.id = event_registration.event_ticket_id
LEFT JOIN sale_order ON sale_order.id = event_registration.sale_order_id
LEFT JOIN sale_order_line ON sale_order_line.id = event_registration.sale_order_line_id
""" + ('\n'.join(join_) + '\n' if join_ else '')
def _group_by_clause(self, *group_by):
# Extra clauses formatted like `column1`, `column2`...
return """
GROUP BY
""" + ',\n '.join(group_by) if group_by else ''

View file

@ -0,0 +1,152 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="event_sale_report_view_graph" model="ir.ui.view">
<field name="name">event.sale.report.view.graph</field>
<field name="model">event.sale.report</field>
<field name="arch" type="xml">
<graph string="Revenues" sample="1" type="line">
<field name="sale_price" type="measure"/>
<field name="event_registration_create_date" interval="day"/>
<field name="event_ticket_price" type="measure" invisible="True"/>
</graph>
</field>
</record>
<record id="event_sale_report_view_form" model="ir.ui.view">
<field name="name">event.sale.report.view.form</field>
<field name="model">event.sale.report</field>
<field name="arch" type="xml">
<form string="Registration revenues" edit="false" create="false">
<sheet>
<group col="2">
<group string="Event">
<field name="event_type_id"/>
<field name="event_id"/>
<field name="event_date_begin"/>
</group>
<group string="Registration">
<field name="event_registration_id"/>
<field name="event_registration_name"/>
<field name="event_registration_create_date"/>
<field name="event_ticket_id"/>
<field name="event_registration_state"/>
</group>
</group>
<group col="2">
<group string="Sale Order">
<field name="sale_order_partner_id"/>
<field name="sale_order_id"/>
<field name="product_id"/>
</group>
<group string="Revenues">
<field name="event_ticket_price"/>
<field name="sale_price_untaxed"/>
<field name="sale_price"/>
</group>
</group>
</sheet>
</form>
</field>
</record>
<record id="event_sale_report_view_pivot" model="ir.ui.view">
<field name="name">event.sale.report.view.pivot</field>
<field name="model">event.sale.report</field>
<field name="arch" type="xml">
<pivot string="Revenues" sample="1">
<field name="sale_price_untaxed" type="measure"/>
<field name="sale_price" type="measure"/>
<field name="event_id" type="row"/>
<field name="product_id" type="row"/>
<field name="event_ticket_price" invisible="True"/>
</pivot>
</field>
</record>
<record id="event_sale_report_view_tree" model="ir.ui.view">
<field name="name">event.sale.report.view.tree</field>
<field name="model">event.sale.report</field>
<field name="arch" type="xml">
<tree string="Revenues" edit="false" create="false">
<field name="event_id"/>
<field name="event_ticket_id"/>
<field name="product_id" optional="hide"/>
<field name="event_ticket_price"/>
<field name="sale_price_untaxed" optional="hide"/>
<field name="sale_price" optional="hide"/>
<field name="event_registration_state" optional="hide"/>
<field name="sale_order_partner_id"/>
<field name="invoice_partner_id" optional="hide"/>
<field name="event_registration_name" optional="hide"/>
<field name="sale_order_state" widget="badge"
decoration-success="sale_order_state == 'sale' or sale_order_state == 'done'"
decoration-info="sale_order_state == 'draft' or sale_order_state == 'sent'"/>
</tree>
</field>
</record>
<record id="event_sale_report_view_search" model="ir.ui.view">
<field name="name">event.sale.report.view.search</field>
<field name="model">event.sale.report</field>
<field name="arch" type="xml">
<search string="Event Sales Analysis">
<field name="event_id"/>
<field name="event_registration_name" string="Participant"/>
<field name="sale_order_partner_id" string="Booked by"/>
<field name="company_id"/>
<filter string="Non-free tickets" name="priced_tickets" domain="[('event_ticket_price', '!=', 0)]"/>
<separator/>
<filter string="Free" name="free" domain="[('payment_status', '=', 'free')]"/>
<filter string="Pending payment" name="payment_pending" domain="[('payment_status', '=', 'to_pay')]"/>
<filter string="Paid" name="is_paid" domain="[('payment_status', '=', 'paid')]"/>
<separator/>
<filter string="Registration Date" name="event_registration_create_date" date="event_registration_create_date" default_period="this_year"/>
<separator/>
<filter string="Upcoming/Running" name="upcoming" help="Upcoming events from today"
domain="[('event_date_end', '&gt;=', datetime.datetime.combine(context_today(), datetime.time(0,0,0)))]"/>
<filter string="Past Events" name="past" help="Events that have ended"
domain="[('event_date_end', '&lt;', datetime.datetime.combine(context_today(), datetime.time(0,0,0)))]"/>
<filter string="Event Start Date" name="event_date_start" date="event_date_begin" default_period="this_year"/>
<filter string="Event End Date" name="event_date_end" date="event_date_end"/>
<group expand="0" string="Group By">
<filter string="Event Type" name="group_by_event_type_id" context="{'group_by': 'event_type_id' }"/>
<filter string="Event" name="group_by_event_id" context="{'group_by': 'event_id' }"/>
<separator/>
<filter string="Product" name="group_by_product_id" context="{'group_by': 'product_id'}"/>
<filter string="Ticket" name="group_by_ticket_id" context="{'group_by': 'event_ticket_id'}"/>
<separator/>
<filter string="Registration Status" name="group_by_registration_state"
context="{'group_by': 'event_registration_state'}"/>
<filter string="Sale Order Status" name="group_by_sale_order_state"
context="{'group_by': 'sale_order_state'}"/>
<filter string="Customer" name="group_by_customer" context="{'group_by': 'sale_order_partner_id'}"/>
</group>
</search>
</field>
</record>
<record id="event_sale_report_action" model="ir.actions.act_window">
<field name="name">Revenues</field>
<field name="res_model">event.sale.report</field>
<field name="view_mode">graph,pivot</field>
<field name="context">{
'search_default_priced_tickets': 1,
'search_default_event_date_start': 1,
'pivot_measures': ['__count__', 'sale_price_untaxed', 'sale_price'],
}</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
No Event Revenues yet!
</p><p>
Come back once tickets have been sold to overview your sales income.
</p>
</field>
</record>
<menuitem name="Revenues"
id="menu_action_show_revenues"
action="event_sale_report_action"
sequence="5"
parent="event.menu_reporting_events"
groups="event.group_event_user"/>
</odoo>