Tägliche Statistiken mit Drill-Down und Jahresvergleich
- Tägliche Statistiken (DailyStats) implementiert mit 2034 Datensätzen - Wochenende vs. Wochentag farblich unterschieden in Tagesansicht - Jahr/Monat Felder für Jahresvergleich hinzugefügt - Button 'Tagesansicht' in Kanban-Dashboard für Drill-Down zu täglichen Statistiken - Alle Dashboard-Graphen auf Jahresvergleich umgestellt (Monat × Jahr, nicht gestapelt) - Jahresvergleich, Ø Nutzer/Tag, Wiederholungstäter, Nutzer pro Monat - alle mit Jahresvergleich
This commit is contained in:
parent
d6a98cfbd0
commit
9be8f320f7
|
|
@ -25,6 +25,8 @@
|
|||
'data/cron_data.xml',
|
||||
'views/dashboard_views.xml',
|
||||
'views/usage_stats_views.xml',
|
||||
'views/daily_stats_views.xml',
|
||||
'views/specialized_dashboards.xml',
|
||||
'reports/usage_report_templates.xml',
|
||||
'reports/usage_reports.xml',
|
||||
],
|
||||
|
|
|
|||
59
open_workshop_report/generate_daily_stats.py
Normal file
59
open_workshop_report/generate_daily_stats.py
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Generiere Tagesstatistiken für Open Workshop
|
||||
"""
|
||||
import sys
|
||||
sys.path.insert(0, '/odoo')
|
||||
|
||||
import odoo
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
# Initialisiere Odoo
|
||||
odoo.tools.config['db_name'] = 'hh18'
|
||||
odoo.tools.config.parse_config(['-c', '/etc/odoo/odoo-dev.conf'])
|
||||
|
||||
# Registry laden
|
||||
registry = odoo.registry('hh18')
|
||||
with registry.cursor() as cr:
|
||||
env = odoo.api.Environment(cr, odoo.SUPERUSER_ID, {})
|
||||
|
||||
DailyStats = env['open_workshop.daily.stats']
|
||||
|
||||
# Finde älteste und neuste Session
|
||||
Session = env['pos.session']
|
||||
sessions = Session.search([('state', '=', 'closed')], order='start_at', limit=1)
|
||||
if not sessions:
|
||||
print('Keine Sessions gefunden!')
|
||||
sys.exit(1)
|
||||
|
||||
start_date = sessions[0].start_at.date()
|
||||
end_date = datetime.now().date()
|
||||
|
||||
print(f'Generiere Tagesstatistiken von {start_date} bis {end_date}')
|
||||
|
||||
current_date = start_date
|
||||
created_count = 0
|
||||
updated_count = 0
|
||||
|
||||
while current_date <= end_date:
|
||||
# Prüfe ob schon vorhanden
|
||||
existing = DailyStats.search([('date', '=', current_date)], limit=1)
|
||||
if existing:
|
||||
# Neu berechnen
|
||||
existing._compute_stats()
|
||||
updated_count += 1
|
||||
else:
|
||||
DailyStats.create({'date': current_date})
|
||||
created_count += 1
|
||||
|
||||
if (created_count + updated_count) % 100 == 0:
|
||||
print(f' Verarbeitet: {created_count + updated_count} Tage...')
|
||||
cr.commit()
|
||||
|
||||
current_date += timedelta(days=1)
|
||||
|
||||
cr.commit()
|
||||
print(f'\n✅ {created_count} neue Tagesstatistiken erstellt')
|
||||
print(f'✅ {updated_count} Tagesstatistiken aktualisiert')
|
||||
print(f'✅ Gesamt: {created_count + updated_count} Tage')
|
||||
|
|
@ -1 +1,2 @@
|
|||
from . import usage_stats
|
||||
from . import daily_stats
|
||||
|
|
|
|||
121
open_workshop_report/models/daily_stats.py
Normal file
121
open_workshop_report/models/daily_stats.py
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from odoo import api, fields, models, _
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
|
||||
class DailyStats(models.Model):
|
||||
"""Tägliche Nutzungsstatistiken"""
|
||||
_name = 'open_workshop.daily.stats'
|
||||
_description = 'Tägliche Nutzungsstatistiken'
|
||||
_order = 'date desc'
|
||||
|
||||
date = fields.Date('Datum', required=True, index=True)
|
||||
year = fields.Integer('Jahr', compute='_compute_weekday', store=True, group_operator='')
|
||||
month = fields.Integer('Monat', compute='_compute_weekday', store=True)
|
||||
month_name = fields.Char('Monat Name', compute='_compute_weekday', store=True)
|
||||
weekday = fields.Selection([
|
||||
('0', 'Montag'),
|
||||
('1', 'Dienstag'),
|
||||
('2', 'Mittwoch'),
|
||||
('3', 'Donnerstag'),
|
||||
('4', 'Freitag'),
|
||||
('5', 'Samstag'),
|
||||
('6', 'Sonntag'),
|
||||
], string='Wochentag', compute='_compute_weekday', store=True)
|
||||
|
||||
is_weekend = fields.Char('Tag-Typ', compute='_compute_weekday', store=True, group_expand='_group_expand_weekend')
|
||||
|
||||
# Buchungen
|
||||
total_bookings = fields.Integer('Buchungen gesamt', compute='_compute_stats', store=True)
|
||||
total_bookings_no_heroes = fields.Integer('Buchungen ohne Thekenhelden', compute='_compute_stats', store=True)
|
||||
|
||||
# Nutzer
|
||||
unique_users = fields.Integer('Nutzer gesamt', compute='_compute_stats', store=True)
|
||||
unique_users_no_heroes = fields.Integer('Nutzer ohne Thekenhelden', compute='_compute_stats', store=True)
|
||||
|
||||
# Monatszugehörigkeit
|
||||
month_stats_id = fields.Many2one('open_workshop.usage.stats', string='Monatsstatistik',
|
||||
compute='_compute_month_stats', store=True)
|
||||
|
||||
@api.depends('date')
|
||||
def _compute_weekday(self):
|
||||
month_names = {
|
||||
1: 'Januar', 2: 'Februar', 3: 'März', 4: 'April',
|
||||
5: 'Mai', 6: 'Juni', 7: 'Juli', 8: 'August',
|
||||
9: 'September', 10: 'Oktober', 11: 'November', 12: 'Dezember'
|
||||
}
|
||||
for record in self:
|
||||
wd = record.date.weekday()
|
||||
record.weekday = str(wd)
|
||||
record.is_weekend = 'Wochenende' if wd >= 5 else 'Wochentag' # Sa=5, So=6
|
||||
record.year = record.date.year
|
||||
record.month = record.date.month
|
||||
record.month_name = month_names.get(record.date.month, '')
|
||||
|
||||
def _group_expand_weekend(self, weekends, domain, order):
|
||||
"""Stelle sicher dass beide Kategorien immer angezeigt werden"""
|
||||
return ['Wochentag', 'Wochenende']
|
||||
|
||||
@api.depends('date')
|
||||
def _compute_month_stats(self):
|
||||
"""Verknüpfe mit Monatsstatistik"""
|
||||
for record in self:
|
||||
month_stat = self.env['open_workshop.usage.stats'].search([
|
||||
('period_type', '=', 'month'),
|
||||
('period_start', '<=', record.date),
|
||||
('period_end', '>=', record.date),
|
||||
], limit=1)
|
||||
record.month_stats_id = month_stat
|
||||
|
||||
@api.depends('date')
|
||||
def _compute_stats(self):
|
||||
for record in self:
|
||||
start_datetime = datetime.combine(record.date, datetime.min.time())
|
||||
end_datetime = datetime.combine(record.date, datetime.max.time())
|
||||
|
||||
# Alle POS Orders an diesem Tag
|
||||
orders = self.env['pos.order'].search([
|
||||
('date_order', '>=', start_datetime),
|
||||
('date_order', '<=', end_datetime),
|
||||
('partner_id', '!=', False),
|
||||
])
|
||||
|
||||
record.total_bookings = len(orders)
|
||||
|
||||
# Thekenheld-Kategorie
|
||||
hero_category = self.env['res.partner.category'].search([
|
||||
'|', ('name', 'ilike', 'thekenheld'), ('name', 'ilike', 'thk')
|
||||
], limit=1)
|
||||
|
||||
if hero_category:
|
||||
orders_no_heroes = orders.filtered(lambda o: hero_category not in o.partner_id.category_id)
|
||||
partners_no_heroes = orders_no_heroes.mapped('partner_id')
|
||||
else:
|
||||
orders_no_heroes = orders
|
||||
partners_no_heroes = orders.mapped('partner_id')
|
||||
|
||||
record.total_bookings_no_heroes = len(orders_no_heroes)
|
||||
record.unique_users = len(orders.mapped('partner_id'))
|
||||
record.unique_users_no_heroes = len(partners_no_heroes)
|
||||
|
||||
@api.model
|
||||
def generate_daily_stats_for_month(self, year, month):
|
||||
"""Generiere Tagesstatistiken für einen Monat"""
|
||||
start_date = datetime(year, month, 1).date()
|
||||
if month == 12:
|
||||
end_date = datetime(year + 1, 1, 1).date() - timedelta(days=1)
|
||||
else:
|
||||
end_date = datetime(year, month + 1, 1).date() - timedelta(days=1)
|
||||
|
||||
current_date = start_date
|
||||
created_count = 0
|
||||
|
||||
while current_date <= end_date:
|
||||
# Prüfe ob schon vorhanden
|
||||
existing = self.search([('date', '=', current_date)], limit=1)
|
||||
if not existing:
|
||||
self.create({'date': current_date})
|
||||
created_count += 1
|
||||
current_date += timedelta(days=1)
|
||||
|
||||
return created_count
|
||||
|
|
@ -22,17 +22,60 @@ class UsageStats(models.Model):
|
|||
('year', 'Jahr'),
|
||||
], string='Periodentyp', default='month', required=True)
|
||||
|
||||
# Statistiken
|
||||
total_sessions = fields.Integer('Gesamt Nutzungen', compute='_compute_stats', store=True)
|
||||
# Für Jahresvergleich
|
||||
year = fields.Integer('Jahr', compute='_compute_year', store=True)
|
||||
month = fields.Integer('Monat', compute='_compute_year', store=True)
|
||||
month_name = fields.Char('Monat Name', compute='_compute_year', store=True)
|
||||
|
||||
# Statistiken - Alle Nutzer
|
||||
total_sessions = fields.Integer('Gesamt Buchungen', compute='_compute_stats', store=True)
|
||||
total_revenue = fields.Monetary('Gesamtumsatz', compute='_compute_stats', store=True, currency_field='currency_id')
|
||||
avg_per_day = fields.Float('Durchschnitt pro Tag', compute='_compute_stats', store=True, digits=(5, 2))
|
||||
unique_users = fields.Integer('Einzigartige Nutzer', compute='_compute_stats', store=True)
|
||||
|
||||
# Statistiken - Ohne Thekenhelden
|
||||
total_bookings_no_heroes = fields.Integer('Buchungen ohne Thekenhelden', compute='_compute_stats', store=True)
|
||||
unique_users_no_heroes = fields.Integer('Nutzer ohne Thekenhelden', compute='_compute_stats', store=True)
|
||||
avg_per_day_no_heroes = fields.Float('Ø Nutzer pro Tag ohne Thekenhelden', compute='_compute_stats', store=True, digits=(10, 2))
|
||||
|
||||
# Wiederholungstäter
|
||||
repeat_users = fields.Integer('Wiederholungstäter', compute='_compute_stats', store=True,
|
||||
help='Nutzer die mehr als 1x im Zeitraum gebucht haben')
|
||||
repeat_users_no_heroes = fields.Integer('Wiederholungstäter ohne Thekenhelden', compute='_compute_stats', store=True)
|
||||
|
||||
currency_id = fields.Many2one('res.currency', string='Währung',
|
||||
default=lambda self: self.env.company.currency_id)
|
||||
|
||||
# POS Session IDs für diese Periode
|
||||
session_ids = fields.Many2many('pos.session', string='POS Sessions', compute='_compute_stats', store=True)
|
||||
|
||||
# Tägliche Statistiken für diese Periode
|
||||
daily_stats_ids = fields.One2many('open_workshop.daily.stats', compute='_compute_daily_stats', string='Tägliche Statistiken')
|
||||
|
||||
@api.depends('period_start', 'period_end')
|
||||
def _compute_daily_stats(self):
|
||||
"""Lädt die täglichen Statistiken für diese Periode"""
|
||||
for record in self:
|
||||
record.daily_stats_ids = self.env['open_workshop.daily.stats'].search([
|
||||
('date', '>=', record.period_start),
|
||||
('date', '<=', record.period_end)
|
||||
], order='date asc')
|
||||
|
||||
def action_view_details(self):
|
||||
"""Öffnet die Detailansicht mit Tages-Diagramm"""
|
||||
self.ensure_one()
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': f'Tagesansicht: {self.period_name}',
|
||||
'res_model': 'open_workshop.daily.stats',
|
||||
'view_mode': 'graph,list',
|
||||
'domain': [('date', '>=', self.period_start), ('date', '<=', self.period_end)],
|
||||
'context': {
|
||||
'search_default_period': 1,
|
||||
'default_date': self.period_start,
|
||||
},
|
||||
'target': 'current',
|
||||
}
|
||||
|
||||
@api.depends('period_start', 'period_end', 'period_type')
|
||||
def _compute_period_name(self):
|
||||
|
|
@ -46,6 +89,18 @@ class UsageStats(models.Model):
|
|||
else:
|
||||
record.period_name = f"{record.period_start.strftime('%d.%m.%Y')} - {record.period_end.strftime('%d.%m.%Y')}"
|
||||
|
||||
@api.depends('period_start')
|
||||
def _compute_year(self):
|
||||
month_names = {
|
||||
1: 'Januar', 2: 'Februar', 3: 'März', 4: 'April',
|
||||
5: 'Mai', 6: 'Juni', 7: 'Juli', 8: 'August',
|
||||
9: 'September', 10: 'Oktober', 11: 'November', 12: 'Dezember'
|
||||
}
|
||||
for record in self:
|
||||
record.year = record.period_start.year
|
||||
record.month = record.period_start.month
|
||||
record.month_name = month_names.get(record.period_start.month, '')
|
||||
|
||||
@api.depends('period_start', 'period_end')
|
||||
def _compute_stats(self):
|
||||
for record in self:
|
||||
|
|
@ -68,12 +123,45 @@ class UsageStats(models.Model):
|
|||
days = (record.period_end - record.period_start).days + 1
|
||||
record.avg_per_day = record.total_sessions / days if days > 0 else 0.0
|
||||
|
||||
# Einzigartige Nutzer (über POS Orders)
|
||||
# Hole Thekenheld-Kategorie
|
||||
hero_category = self.env['res.partner.category'].search([
|
||||
'|', ('name', 'ilike', 'thekenheld'), ('name', 'ilike', 'thk')
|
||||
], limit=1)
|
||||
|
||||
# Alle POS Orders im Zeitraum
|
||||
orders = self.env['pos.order'].search([
|
||||
('session_id', 'in', sessions.ids),
|
||||
('partner_id', '!=', False),
|
||||
])
|
||||
record.unique_users = len(orders.mapped('partner_id'))
|
||||
|
||||
# Einzigartige Nutzer (alle)
|
||||
all_partners = orders.mapped('partner_id')
|
||||
record.unique_users = len(all_partners)
|
||||
|
||||
# Filter: Ohne Thekenhelden
|
||||
if hero_category:
|
||||
partners_no_heroes = all_partners.filtered(lambda p: hero_category not in p.category_id)
|
||||
orders_no_heroes = orders.filtered(lambda o: hero_category not in o.partner_id.category_id)
|
||||
else:
|
||||
partners_no_heroes = all_partners
|
||||
orders_no_heroes = orders
|
||||
|
||||
record.unique_users_no_heroes = len(partners_no_heroes)
|
||||
record.total_bookings_no_heroes = len(orders_no_heroes)
|
||||
record.avg_per_day_no_heroes = record.unique_users_no_heroes / days if days > 0 else 0.0
|
||||
|
||||
# Wiederholungstäter: Partner die mehr als 1 Order haben
|
||||
partner_order_count = {}
|
||||
for order in orders:
|
||||
partner_id = order.partner_id.id
|
||||
partner_order_count[partner_id] = partner_order_count.get(partner_id, 0) + 1
|
||||
record.repeat_users = len([p for p, count in partner_order_count.items() if count > 1])
|
||||
|
||||
partner_order_count_no_heroes = {}
|
||||
for order in orders_no_heroes:
|
||||
partner_id = order.partner_id.id
|
||||
partner_order_count_no_heroes[partner_id] = partner_order_count_no_heroes.get(partner_id, 0) + 1
|
||||
record.repeat_users_no_heroes = len([p for p, count in partner_order_count_no_heroes.items() if count > 1])
|
||||
|
||||
@api.model
|
||||
def generate_monthly_stats(self, year=None, month=None):
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_usage_stats_user,access.usage.stats.user,model_open_workshop_usage_stats,base.group_user,1,0,0,0
|
||||
access_usage_stats_manager,access.usage.stats.manager,model_open_workshop_usage_stats,point_of_sale.group_pos_manager,1,1,1,1
|
||||
access_daily_stats_user,access.daily.stats.user,model_open_workshop_daily_stats,base.group_user,1,0,0,0
|
||||
access_daily_stats_manager,access.daily.stats.manager,model_open_workshop_daily_stats,point_of_sale.group_pos_manager,1,1,1,1
|
||||
|
|
|
|||
|
67
open_workshop_report/views/daily_stats_views.xml
Normal file
67
open_workshop_report/views/daily_stats_views.xml
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<!-- Tree View -->
|
||||
<record id="view_daily_stats_tree" model="ir.ui.view">
|
||||
<field name="name">open_workshop.daily.stats.tree</field>
|
||||
<field name="model">open_workshop.daily.stats</field>
|
||||
<field name="arch" type="xml">
|
||||
<list string="Tägliche Statistiken" create="false">
|
||||
<field name="date"/>
|
||||
<field name="weekday"/>
|
||||
<field name="total_bookings_no_heroes" string="Buchungen"/>
|
||||
<field name="unique_users_no_heroes" string="Nutzer"/>
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Graph View - Nutzerzahlen pro Tag -->
|
||||
<record id="view_daily_stats_graph" model="ir.ui.view">
|
||||
<field name="name">open_workshop.daily.stats.graph</field>
|
||||
<field name="model">open_workshop.daily.stats</field>
|
||||
<field name="arch" type="xml">
|
||||
<graph string="Nutzerzahlen pro Tag" type="bar" stacked="False">
|
||||
<field name="date" interval="day"/>
|
||||
<field name="is_weekend" type="col"/>
|
||||
<field name="unique_users_no_heroes" type="measure"/>
|
||||
</graph>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Pivot View -->
|
||||
<record id="view_daily_stats_pivot" model="ir.ui.view">
|
||||
<field name="name">open_workshop.daily.stats.pivot</field>
|
||||
<field name="model">open_workshop.daily.stats</field>
|
||||
<field name="arch" type="xml">
|
||||
<pivot string="Tägliche Analyse">
|
||||
<field name="date" interval="day" type="row"/>
|
||||
<field name="weekday" type="col"/>
|
||||
<field name="total_bookings_no_heroes" type="measure"/>
|
||||
<field name="unique_users_no_heroes" type="measure"/>
|
||||
</pivot>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Action für Tagesstatistiken -->
|
||||
<record id="action_daily_stats" model="ir.actions.act_window">
|
||||
<field name="name">Tägliche Nutzung</field>
|
||||
<field name="res_model">open_workshop.daily.stats</field>
|
||||
<field name="view_mode">graph,pivot,list</field>
|
||||
<field name="context">{}</field>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
Keine täglichen Statistiken vorhanden
|
||||
</p>
|
||||
<p>
|
||||
Tägliche Statistiken werden automatisch generiert.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Menu für Tagesstatistiken -->
|
||||
<menuitem id="menu_daily_stats"
|
||||
name="Tägliche Nutzung"
|
||||
parent="menu_open_workshop_stats"
|
||||
action="action_daily_stats"
|
||||
sequence="30"/>
|
||||
|
||||
</odoo>
|
||||
|
|
@ -5,39 +5,52 @@
|
|||
<field name="name">open_workshop.usage.dashboard</field>
|
||||
<field name="model">open_workshop.usage.stats</field>
|
||||
<field name="arch" type="xml">
|
||||
<kanban class="o_kanban_dashboard">
|
||||
<field name="total_sessions"/>
|
||||
<field name="avg_per_day"/>
|
||||
<field name="unique_users"/>
|
||||
<kanban class="o_kanban_dashboard" default_order="period_start desc">
|
||||
<field name="id"/>
|
||||
<field name="total_bookings_no_heroes"/>
|
||||
<field name="avg_per_day_no_heroes"/>
|
||||
<field name="unique_users_no_heroes"/>
|
||||
<field name="repeat_users_no_heroes"/>
|
||||
<field name="total_revenue"/>
|
||||
<field name="period_name"/>
|
||||
<templates>
|
||||
<t t-name="kanban-box">
|
||||
<div class="oe_kanban_global_click">
|
||||
<div class="o_kanban_record" style="cursor: pointer;">
|
||||
<div class="o_kanban_record_top">
|
||||
<strong><field name="period_name"/></strong>
|
||||
</div>
|
||||
<div class="o_kanban_record_body">
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<div class="text-muted">Nutzungen</div>
|
||||
<strong><field name="total_sessions"/></strong>
|
||||
<div class="text-muted">Buchungen (ohne Theken)</div>
|
||||
<strong class="fs-3"><field name="total_bookings_no_heroes"/></strong>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<div class="text-muted">⌀/Tag</div>
|
||||
<strong><field name="avg_per_day"/></strong>
|
||||
<div class="text-muted">Nutzer (ohne Theken)</div>
|
||||
<strong class="fs-3"><field name="unique_users_no_heroes"/></strong>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-2">
|
||||
<div class="col-6">
|
||||
<div class="text-muted">Nutzer</div>
|
||||
<strong><field name="unique_users"/></strong>
|
||||
<div class="text-muted">⌀/Tag (ohne Theken)</div>
|
||||
<strong><field name="avg_per_day_no_heroes" widget="float" digits="[10,2]"/></strong>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<div class="text-muted">Wiederholungstäter</div>
|
||||
<strong><field name="repeat_users_no_heroes"/></strong>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-2">
|
||||
<div class="col-12">
|
||||
<div class="text-muted">Umsatz</div>
|
||||
<strong><field name="total_revenue" widget="monetary"/></strong>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-3">
|
||||
<div class="col-12">
|
||||
<button type="object" name="action_view_details" class="btn btn-primary w-100">📊 Tagesansicht</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
|
|
@ -46,24 +59,24 @@
|
|||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Dashboard Action -->
|
||||
<!-- Dashboard Action - Alle -->
|
||||
<record id="action_usage_dashboard" model="ir.actions.act_window">
|
||||
<field name="name">Nutzungs-Dashboard</field>
|
||||
<field name="res_model">open_workshop.usage.stats</field>
|
||||
<field name="view_mode">kanban,graph,pivot</field>
|
||||
<field name="view_mode">kanban,form,graph,pivot</field>
|
||||
<field name="view_id" ref="view_usage_dashboard"/>
|
||||
</record>
|
||||
|
||||
<!-- Submenu unter Point of Sale > Berichtswesen -->
|
||||
<menuitem id="menu_open_workshop_reports"
|
||||
<menuitem id="menu_open_workshop_stats"
|
||||
name="Open Workshop"
|
||||
parent="point_of_sale.menu_point_rep"
|
||||
sequence="100"/>
|
||||
|
||||
<!-- Dashboard Menu -->
|
||||
<menuitem id="menu_usage_dashboard"
|
||||
name="Nutzungs-Dashboard"
|
||||
parent="menu_open_workshop_reports"
|
||||
name="📊 Alle Statistiken"
|
||||
parent="menu_open_workshop_stats"
|
||||
action="action_usage_dashboard"
|
||||
sequence="1"/>
|
||||
</odoo>
|
||||
|
|
|
|||
119
open_workshop_report/views/specialized_dashboards.xml
Normal file
119
open_workshop_report/views/specialized_dashboards.xml
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<!-- Graph: Jahresvergleich Buchungen -->
|
||||
<record id="view_usage_stats_yearly_comparison" model="ir.ui.view">
|
||||
<field name="name">open_workshop.usage.stats.graph.yearly</field>
|
||||
<field name="model">open_workshop.usage.stats</field>
|
||||
<field name="arch" type="xml">
|
||||
<graph string="Buchungen pro Monat im Jahresvergleich" type="bar" stacked="0" order="month asc">
|
||||
<field name="month" type="row"/>
|
||||
<field name="year" type="col"/>
|
||||
<field name="total_bookings_no_heroes" type="measure"/>
|
||||
</graph>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Graph: Durchschnittliche Nutzer Trend -->
|
||||
<record id="view_usage_stats_avg_users_trend" model="ir.ui.view">
|
||||
<field name="name">open_workshop.usage.stats.graph.avg.trend</field>
|
||||
<field name="model">open_workshop.usage.stats</field>
|
||||
<field name="arch" type="xml">
|
||||
<graph string="Durchschnittliche Nutzer pro Tag (Trend)" type="bar" stacked="0" order="month asc">
|
||||
<field name="month" type="row"/>
|
||||
<field name="year" type="col"/>
|
||||
<field name="avg_per_day_no_heroes" type="measure"/>
|
||||
</graph>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Graph: Wiederholungstäter -->
|
||||
<record id="view_usage_stats_repeat_users" model="ir.ui.view">
|
||||
<field name="name">open_workshop.usage.stats.graph.repeat</field>
|
||||
<field name="model">open_workshop.usage.stats</field>
|
||||
<field name="arch" type="xml">
|
||||
<graph string="Wiederholungstäter pro Monat" type="bar" stacked="0" order="month asc">
|
||||
<field name="month" type="row"/>
|
||||
<field name="year" type="col"/>
|
||||
<field name="repeat_users_no_heroes" type="measure"/>
|
||||
</graph>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Graph: Nutzer pro Monat -->
|
||||
<record id="view_usage_stats_users_per_month" model="ir.ui.view">
|
||||
<field name="name">open_workshop.usage.stats.graph.users</field>
|
||||
<field name="model">open_workshop.usage.stats</field>
|
||||
<field name="arch" type="xml">
|
||||
<graph string="Anzahl Nutzer pro Monat" type="bar" stacked="0" order="month asc">
|
||||
<field name="month" type="row"/>
|
||||
<field name="year" type="col"/>
|
||||
<field name="unique_users_no_heroes" type="measure"/>
|
||||
</graph>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- ACTION: Jahresvergleich -->
|
||||
<record id="action_yearly_comparison" model="ir.actions.act_window">
|
||||
<field name="name">Jahresvergleich Buchungen</field>
|
||||
<field name="res_model">open_workshop.usage.stats</field>
|
||||
<field name="view_mode">graph</field>
|
||||
<field name="view_id" ref="view_usage_stats_yearly_comparison"/>
|
||||
<field name="domain">[('period_type', '=', 'month')]</field>
|
||||
</record>
|
||||
|
||||
<!-- ACTION: Durchschnitt Trend -->
|
||||
<record id="action_avg_users_trend" model="ir.actions.act_window">
|
||||
<field name="name">Ø Nutzer pro Tag (Trend)</field>
|
||||
<field name="res_model">open_workshop.usage.stats</field>
|
||||
<field name="view_mode">graph</field>
|
||||
<field name="view_id" ref="view_usage_stats_avg_users_trend"/>
|
||||
<field name="domain">[('period_type', '=', 'month')]</field>
|
||||
</record>
|
||||
|
||||
<!-- ACTION: Wiederholungstäter -->
|
||||
<record id="action_repeat_users" model="ir.actions.act_window">
|
||||
<field name="name">Wiederholungstäter</field>
|
||||
<field name="res_model">open_workshop.usage.stats</field>
|
||||
<field name="view_mode">graph</field>
|
||||
<field name="view_id" ref="view_usage_stats_repeat_users"/>
|
||||
<field name="domain">[('period_type', '=', 'month')]</field>
|
||||
</record>
|
||||
|
||||
<!-- ACTION: Nutzer pro Monat -->
|
||||
<record id="action_users_per_month" model="ir.actions.act_window">
|
||||
<field name="name">Nutzer pro Monat</field>
|
||||
<field name="res_model">open_workshop.usage.stats</field>
|
||||
<field name="view_mode">graph</field>
|
||||
<field name="view_id" ref="view_usage_stats_users_per_month"/>
|
||||
<field name="domain">[('period_type', '=', 'month')]</field>
|
||||
</record>
|
||||
|
||||
<!-- MENU: Jahresvergleich -->
|
||||
<menuitem id="menu_yearly_comparison"
|
||||
name="📊 Jahresvergleich"
|
||||
parent="menu_open_workshop_stats"
|
||||
action="action_yearly_comparison"
|
||||
sequence="20"/>
|
||||
|
||||
<!-- MENU: Durchschnitt Trend -->
|
||||
<menuitem id="menu_avg_users_trend"
|
||||
name="📈 Ø Nutzer/Tag Trend"
|
||||
parent="menu_open_workshop_stats"
|
||||
action="action_avg_users_trend"
|
||||
sequence="21"/>
|
||||
|
||||
<!-- MENU: Wiederholungstäter -->
|
||||
<menuitem id="menu_repeat_users"
|
||||
name="🔁 Wiederholungstäter"
|
||||
parent="menu_open_workshop_stats"
|
||||
action="action_repeat_users"
|
||||
sequence="22"/>
|
||||
|
||||
<!-- MENU: Nutzer pro Monat -->
|
||||
<menuitem id="menu_users_per_month"
|
||||
name="👥 Nutzer pro Monat"
|
||||
parent="menu_open_workshop_stats"
|
||||
action="action_users_per_month"
|
||||
sequence="23"/>
|
||||
|
||||
</odoo>
|
||||
|
|
@ -9,9 +9,10 @@
|
|||
<field name="period_name"/>
|
||||
<field name="period_start"/>
|
||||
<field name="period_end"/>
|
||||
<field name="total_sessions" string="Gesamt Nutzungen"/>
|
||||
<field name="avg_per_day" string="⌀ pro Tag"/>
|
||||
<field name="unique_users" string="Einz. Nutzer"/>
|
||||
<field name="total_bookings_no_heroes" string="Buchungen (ohne Theken)"/>
|
||||
<field name="unique_users_no_heroes" string="Nutzer (ohne Theken)"/>
|
||||
<field name="avg_per_day_no_heroes" string="⌀/Tag (ohne Theken)"/>
|
||||
<field name="repeat_users_no_heroes" string="Wiederholungstäter"/>
|
||||
<field name="total_revenue" widget="monetary"/>
|
||||
</list>
|
||||
</field>
|
||||
|
|
@ -33,12 +34,19 @@
|
|||
<field name="period_start"/>
|
||||
<field name="period_end"/>
|
||||
</group>
|
||||
<group string="Statistiken">
|
||||
<group string="Statistiken (Alle)">
|
||||
<field name="total_sessions"/>
|
||||
<field name="avg_per_day"/>
|
||||
<field name="unique_users"/>
|
||||
<field name="repeat_users"/>
|
||||
<field name="total_revenue"/>
|
||||
</group>
|
||||
<group string="Statistiken (Ohne Thekenhelden)">
|
||||
<field name="total_bookings_no_heroes"/>
|
||||
<field name="avg_per_day_no_heroes"/>
|
||||
<field name="unique_users_no_heroes"/>
|
||||
<field name="repeat_users_no_heroes"/>
|
||||
</group>
|
||||
</group>
|
||||
<notebook>
|
||||
<page string="POS Sessions">
|
||||
|
|
@ -73,14 +81,14 @@
|
|||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Graph View - Bar Chart -->
|
||||
<!-- Graph View - Bar Chart (Jahresvergleich) -->
|
||||
<record id="view_usage_stats_graph_bar" model="ir.ui.view">
|
||||
<field name="name">open_workshop.usage.stats.graph.bar</field>
|
||||
<field name="model">open_workshop.usage.stats</field>
|
||||
<field name="arch" type="xml">
|
||||
<graph string="Nutzungen pro Monat" type="bar" sample="1" stacked="0">
|
||||
<graph string="Buchungen pro Monat im Jahresvergleich (ohne Thekenhelden)" type="bar" sample="1" stacked="0">
|
||||
<field name="period_start" interval="month"/>
|
||||
<field name="total_sessions" type="measure"/>
|
||||
<field name="total_bookings_no_heroes" type="measure"/>
|
||||
</graph>
|
||||
</field>
|
||||
</record>
|
||||
|
|
@ -90,10 +98,10 @@
|
|||
<field name="name">open_workshop.usage.stats.graph.line</field>
|
||||
<field name="model">open_workshop.usage.stats</field>
|
||||
<field name="arch" type="xml">
|
||||
<graph string="Nutzungen (Historie)" type="line" sample="1">
|
||||
<graph string="Durchschnittliche Nutzer pro Tag (ohne Thekenhelden)" type="line" sample="1">
|
||||
<field name="period_start" interval="month"/>
|
||||
<field name="total_sessions" type="measure"/>
|
||||
<field name="avg_per_day" type="measure"/>
|
||||
<field name="avg_per_day_no_heroes" type="measure"/>
|
||||
<field name="unique_users_no_heroes" type="measure"/>
|
||||
</graph>
|
||||
</field>
|
||||
</record>
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user