feat: add KDS accumulated time tracking to preserve timer continuity during state transitions
This commit is contained in:
parent
32352f0449
commit
c4201055fd
15
README.md
15
README.md
@ -3,16 +3,19 @@
|
|||||||
## Overview
|
## Overview
|
||||||
Standard Odoo 19 deletes POS Preparation Display (KDS) records a day after completion, preventing historical analysis of preparation times. This module creates a persistent log of completed orders and order lines from the Kitchen Display System (KDS) to enable detailed historical reporting.
|
Standard Odoo 19 deletes POS Preparation Display (KDS) records a day after completion, preventing historical analysis of preparation times. This module creates a persistent log of completed orders and order lines from the Kitchen Display System (KDS) to enable detailed historical reporting.
|
||||||
|
|
||||||
|
Additionally, this module preserves timer continuity for orders and lines on the Kitchen Preparation Display UI when they are recalled or resetted back to preparation stages.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
- Records the state of the Kitchen Display System or Preparation Display permanently.
|
- **Historical Persistence**: Permanently records Kitchen Display System (KDS) states and times, overcoming Odoo's default 1-day deletion behavior.
|
||||||
- Generates reports showing the average time to complete an order.
|
- **Average Preparation Time Reports**: Generates pivot and graph reports showing the average preparation time globally across all POS Shops, categories, products, or individual Kitchen Displays.
|
||||||
- Generates reports showing the average time to complete certain products or product categories.
|
- **Timer Continuity on Reset/Recall**: When an order or order line is resetted or recalled back from Completed to a preparation stage, the UI timer automatically continues counting upwards from its last elapsed time instead of resetting back to `0`.
|
||||||
- Allows filtering and grouping globally for a POS Shop or individually per KDS.
|
- **Advanced Filtering and Grouping**: Multi-dimension analysis by POS Shop, individual KDS Display, Product Category, Product, or Status.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
Install out-of-the-box like a standard Odoo module on an Odoo 19 environment possessing POS Enterprise (`pos_enterprise`).
|
Install out-of-the-box like a standard Odoo module on an Odoo 19 environment possessing POS Enterprise (`pos_enterprise`).
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
1. Open the Point of Sale and place orders to KDS.
|
1. Open the Point of Sale and place orders to the KDS.
|
||||||
2. Complete stages in the Preparation Display.
|
2. Complete stages in the Preparation Display.
|
||||||
3. Go to **Point of Sale > Reporting > KDS Product Analysis** or **KDS Order Analysis** to view Pivot and Graph summaries.
|
3. If an order or line needs to be recalled or resetted back to preparation, the display UI timer will resume incrementing starting from the previous session's elapsed time.
|
||||||
|
4. Go to **Point of Sale > Reporting > KDS Product Analysis** or **KDS Order Analysis** to view Pivot and Graph summaries.
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
'name': 'POS KDS Tracker and Report',
|
'name': 'POS KDS Tracker and Report',
|
||||||
'version': '1.0',
|
'version': '19.0.1.0.2',
|
||||||
'category': 'Sales/Point of Sale',
|
'category': 'Sales/Point of Sale',
|
||||||
'summary': 'Tracks Kitchen Display System completion times for analysis',
|
'summary': 'Tracks Kitchen Display System completion times for analysis',
|
||||||
'description': """
|
'description': """
|
||||||
@ -14,6 +14,12 @@
|
|||||||
'security/ir.model.access.csv',
|
'security/ir.model.access.csv',
|
||||||
'views/pos_kds_report_views.xml',
|
'views/pos_kds_report_views.xml',
|
||||||
],
|
],
|
||||||
|
'assets': {
|
||||||
|
'pos_preparation_display.assets': [
|
||||||
|
'pos_kds_report/static/src/app/models/pos_preparation_state.js',
|
||||||
|
'pos_kds_report/static/src/app/services/preparation_display_service.js',
|
||||||
|
],
|
||||||
|
},
|
||||||
'installable': True,
|
'installable': True,
|
||||||
'application': False,
|
'application': False,
|
||||||
'auto_install': False,
|
'auto_install': False,
|
||||||
|
|||||||
@ -1,2 +1,3 @@
|
|||||||
from . import pos_kds_report
|
from . import pos_kds_report
|
||||||
from . import pos_prep_state
|
from . import pos_prep_state
|
||||||
|
from . import pos_prep_display
|
||||||
|
|||||||
32
models/pos_prep_display.py
Normal file
32
models/pos_prep_display.py
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
from odoo import models
|
||||||
|
|
||||||
|
|
||||||
|
class PosPrepDisplay(models.Model):
|
||||||
|
_inherit = 'pos.prep.display'
|
||||||
|
|
||||||
|
def _notify(self, *notifications, private=True) -> None:
|
||||||
|
new_notifications = []
|
||||||
|
if isinstance(notifications[0], str):
|
||||||
|
if len(notifications) == 2:
|
||||||
|
name, message = notifications
|
||||||
|
if name == 'CHANGE_STATE_STAGE' and isinstance(message, dict):
|
||||||
|
stages = message.get('pdis_state_stages', [])
|
||||||
|
for stage_data in stages:
|
||||||
|
state = self.env['pos.prep.state'].browse(stage_data['id'])
|
||||||
|
if state.exists():
|
||||||
|
stage_data['kds_accumulated_time'] = state.kds_accumulated_time
|
||||||
|
new_notifications = notifications
|
||||||
|
else:
|
||||||
|
for item in notifications:
|
||||||
|
if isinstance(item, tuple) and len(item) == 2:
|
||||||
|
name, message = item
|
||||||
|
if name == 'CHANGE_STATE_STAGE' and isinstance(message, dict):
|
||||||
|
stages = message.get('pdis_state_stages', [])
|
||||||
|
for stage_data in stages:
|
||||||
|
state = self.env['pos.prep.state'].browse(stage_data['id'])
|
||||||
|
if state.exists():
|
||||||
|
stage_data['kds_accumulated_time'] = state.kds_accumulated_time
|
||||||
|
new_notifications.append((name, message))
|
||||||
|
else:
|
||||||
|
new_notifications.append(item)
|
||||||
|
super()._notify(*new_notifications, private=private)
|
||||||
@ -8,6 +8,15 @@ _logger = logging.getLogger(__name__)
|
|||||||
class PosPreparationState(models.Model):
|
class PosPreparationState(models.Model):
|
||||||
_inherit = 'pos.prep.state'
|
_inherit = 'pos.prep.state'
|
||||||
|
|
||||||
|
kds_accumulated_time = fields.Integer(string="KDS Accumulated Time", default=0)
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _load_pos_preparation_data_fields(self):
|
||||||
|
fields_list = super()._load_pos_preparation_data_fields()
|
||||||
|
if fields_list:
|
||||||
|
fields_list.append('kds_accumulated_time')
|
||||||
|
return fields_list
|
||||||
|
|
||||||
def _update_kds_report(self, pdis_state, old_last_stage_change=None):
|
def _update_kds_report(self, pdis_state, old_last_stage_change=None):
|
||||||
"""
|
"""
|
||||||
Synchronous KDS report update.
|
Synchronous KDS report update.
|
||||||
@ -69,6 +78,7 @@ class PosPreparationState(models.Model):
|
|||||||
], limit=1)
|
], limit=1)
|
||||||
if line_report:
|
if line_report:
|
||||||
line_report.write({'state': 'in_prep'})
|
line_report.write({'state': 'in_prep'})
|
||||||
|
pdis_state.kds_accumulated_time = line_report.preparation_time
|
||||||
|
|
||||||
order_report = KdsOrderReport.search([
|
order_report = KdsOrderReport.search([
|
||||||
('pos_order_id', '=', order_id),
|
('pos_order_id', '=', order_id),
|
||||||
|
|||||||
11
static/src/app/models/pos_preparation_state.js
Normal file
11
static/src/app/models/pos_preparation_state.js
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { PosPreparationState } from "@pos_enterprise/app/models/pos_preparation_state";
|
||||||
|
import { patch } from "@web/core/utils/patch";
|
||||||
|
import { computeDurationSinceDate } from "@pos_enterprise/app/utils/utils";
|
||||||
|
|
||||||
|
patch(PosPreparationState.prototype, {
|
||||||
|
computeDuration() {
|
||||||
|
const baseDuration = computeDurationSinceDate(this.write_date);
|
||||||
|
const accumulatedMinutes = Math.floor((this.kds_accumulated_time || 0) / 60);
|
||||||
|
return baseDuration + accumulatedMinutes;
|
||||||
|
},
|
||||||
|
});
|
||||||
16
static/src/app/services/preparation_display_service.js
Normal file
16
static/src/app/services/preparation_display_service.js
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { PrepDisplay } from "@pos_enterprise/app/services/preparation_display_service";
|
||||||
|
import { patch } from "@web/core/utils/patch";
|
||||||
|
|
||||||
|
patch(PrepDisplay.prototype, {
|
||||||
|
async setup() {
|
||||||
|
await super.setup(...arguments);
|
||||||
|
this.onNotified("CHANGE_STATE_STAGE", (data) => {
|
||||||
|
for (const stage of data["pdis_state_stages"]) {
|
||||||
|
const state = this.data.models["pos.prep.state"].get(stage.id);
|
||||||
|
if (state && stage.kds_accumulated_time !== undefined) {
|
||||||
|
state.kds_accumulated_time = stage.kds_accumulated_time;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue
Block a user