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
|
||||
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
|
||||
- Records the state of the Kitchen Display System or Preparation Display permanently.
|
||||
- Generates reports showing the average time to complete an order.
|
||||
- Generates reports showing the average time to complete certain products or product categories.
|
||||
- Allows filtering and grouping globally for a POS Shop or individually per KDS.
|
||||
- **Historical Persistence**: Permanently records Kitchen Display System (KDS) states and times, overcoming Odoo's default 1-day deletion behavior.
|
||||
- **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.
|
||||
- **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`.
|
||||
- **Advanced Filtering and Grouping**: Multi-dimension analysis by POS Shop, individual KDS Display, Product Category, Product, or Status.
|
||||
|
||||
## Installation
|
||||
Install out-of-the-box like a standard Odoo module on an Odoo 19 environment possessing POS Enterprise (`pos_enterprise`).
|
||||
|
||||
## 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.
|
||||
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',
|
||||
'version': '1.0',
|
||||
'version': '19.0.1.0.2',
|
||||
'category': 'Sales/Point of Sale',
|
||||
'summary': 'Tracks Kitchen Display System completion times for analysis',
|
||||
'description': """
|
||||
@ -14,6 +14,12 @@
|
||||
'security/ir.model.access.csv',
|
||||
'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,
|
||||
'application': False,
|
||||
'auto_install': False,
|
||||
|
||||
@ -1,2 +1,3 @@
|
||||
from . import pos_kds_report
|
||||
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):
|
||||
_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):
|
||||
"""
|
||||
Synchronous KDS report update.
|
||||
@ -69,6 +78,7 @@ class PosPreparationState(models.Model):
|
||||
], limit=1)
|
||||
if line_report:
|
||||
line_report.write({'state': 'in_prep'})
|
||||
pdis_state.kds_accumulated_time = line_report.preparation_time
|
||||
|
||||
order_report = KdsOrderReport.search([
|
||||
('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