Implement the initial Kitchen Display System (KDS) module, introducing order line tracking, product configuration, and dedicated kanban and tree views with custom styling.

This commit is contained in:
Alaguraj0361 2026-02-07 15:18:26 +05:30
parent 2358e2e0ff
commit 7241758204
11 changed files with 307 additions and 0 deletions

View File

@ -0,0 +1 @@
from . import models

View File

@ -0,0 +1,30 @@
{
'name': 'Dine360 Kitchen Display System',
'version': '1.0',
'category': 'Sales/Point of Sale',
'summary': 'Dedicated KDS for Restaurant Kitchen',
'description': """
Professional Kitchen Display System:
- Real-time order tracking
- Preparation status management
- Cooking time tracking
- Kanban dashboard for chefs
- Floor/Table based organization
""",
'author': 'Dine360',
'depends': ['point_of_sale', 'pos_restaurant', 'dine360_restaurant'],
'data': [
'security/ir.model.access.csv',
'views/pos_order_line_views.xml',
'views/product_views.xml',
'views/kds_menus.xml',
],
'assets': {
'web.assets_backend': [
'dine360_kds/static/src/css/kds_style.css',
],
},
'installable': True,
'application': True,
'license': 'LGPL-3',
}

View File

@ -0,0 +1,2 @@
from . import pos_order_line
from . import product

View File

@ -0,0 +1,46 @@
from odoo import models, fields, api, _
class PosOrderLine(models.Model):
_inherit = 'pos.order.line'
preparation_status = fields.Selection([
('waiting', 'Waiting'),
('preparing', 'Preparing'),
('ready', 'Ready'),
('served', 'Served'),
('cancelled', 'Cancelled')
], string='Preparation Status', default='waiting', tracking=True)
color = fields.Integer(string='Color', default=0)
preparation_time_start = fields.Datetime(string='Start Time')
preparation_time_end = fields.Datetime(string='Ready Time')
cooking_time = fields.Integer(string='Cooking Time (min)', compute='_compute_cooking_time', store=True)
table_id = fields.Many2one('restaurant.table', related='order_id.table_id', string='Table', store=True)
floor_id = fields.Many2one('restaurant.floor', related='order_id.table_id.floor_id', string='Floor', store=True)
@api.depends('preparation_time_start', 'preparation_time_end')
def _compute_cooking_time(self):
for line in self:
if line.preparation_time_start and line.preparation_time_end:
diff = line.preparation_time_end - line.preparation_time_start
line.cooking_time = int(diff.total_seconds() / 60)
else:
line.cooking_time = 0
def action_start_preparing(self):
self.write({
'preparation_status': 'preparing',
'preparation_time_start': fields.Datetime.now()
})
def action_mark_ready(self):
self.write({
'preparation_status': 'ready',
'preparation_time_end': fields.Datetime.now()
})
def action_mark_served(self):
self.write({
'preparation_status': 'served'
})

View File

@ -0,0 +1,10 @@
from odoo import models, fields
class ProductTemplate(models.Model):
_inherit = 'product.template'
is_kitchen_item = fields.Boolean(
string='Show in KDS',
default=True,
help="If checked, this product will appear in the Kitchen Display System when ordered."
)

View File

@ -0,0 +1,4 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_kds_order_line_kitchen,pos.order.line.kitchen,point_of_sale.model_pos_order_line,dine360_restaurant.group_restaurant_kitchen,1,1,0,0
access_kds_order_line_manager,pos.order.line.manager,point_of_sale.model_pos_order_line,dine360_restaurant.group_restaurant_manager,1,1,1,1
access_kds_order_line_user,pos.order.line.user,point_of_sale.model_pos_order_line,base.group_user,1,1,1,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_kds_order_line_kitchen pos.order.line.kitchen point_of_sale.model_pos_order_line dine360_restaurant.group_restaurant_kitchen 1 1 0 0
3 access_kds_order_line_manager pos.order.line.manager point_of_sale.model_pos_order_line dine360_restaurant.group_restaurant_manager 1 1 1 1
4 access_kds_order_line_user pos.order.line.user point_of_sale.model_pos_order_line base.group_user 1 1 1 0

View File

@ -0,0 +1,49 @@
.role_kitchen_card {
transition: transform 0.2s ease, box-shadow 0.2s ease;
cursor: default !important;
}
.role_kitchen_card:hover {
transform: translateY(-2px);
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1) !important;
}
.o_kanban_group {
background-color: #f4f7fa !important;
border-radius: 15px !important;
margin: 10px !important;
padding: 10px !important;
}
.o_kanban_header {
background: transparent !important;
padding: 15px 10px !important;
}
.o_kanban_header_title {
font-weight: 700 !important;
text-transform: uppercase !important;
letter-spacing: 1px !important;
}
/* Specific colors for status columns */
.o_kanban_group[data-id="waiting"] .o_kanban_header_title {
color: #f0ad4e;
}
.o_kanban_group[data-id="preparing"] .o_kanban_header_title {
color: #5bc0de;
}
.o_kanban_group[data-id="ready"] .o_kanban_header_title {
color: #5cb85c;
}
.o_kanban_group[data-id="served"] .o_kanban_header_title {
color: #777;
}
.badge.rounded-pill {
padding: 0.5em 1em;
font-weight: 600;
}

View File

@ -0,0 +1,14 @@
<odoo>
<menuitem id="menu_kds_root"
name="Kitchen (KDS)"
web_icon="point_of_sale,static/src/img/icon.png"
groups="base.group_user"
sequence="20"/>
<menuitem id="menu_kds_orders"
name="Preparation Orders"
parent="menu_kds_root"
action="action_kds_dashboard"
groups="base.group_user"
sequence="10"/>
</odoo>

View File

@ -0,0 +1,138 @@
<odoo>
<!-- KDS Kanban View -->
<record id="view_pos_order_line_kds_kanban" model="ir.ui.view">
<field name="name">pos.order.line.kds.kanban</field>
<field name="model">pos.order.line</field>
<field name="arch" type="xml">
<kanban default_group_by="preparation_status" create="false" class="o_kanban_small_column o_kanban_project_tasks" sample="1">
<field name="preparation_status"/>
<field name="color"/>
<field name="product_id"/>
<field name="qty"/>
<field name="order_id"/>
<field name="table_id"/>
<field name="floor_id"/>
<field name="customer_note"/>
<field name="create_date"/>
<templates>
<t t-name="kanban-box">
<div t-attf-class="oe_kanban_global_click role_kitchen_card {{'oe_kanban_color_' + kanban_getcolor(record.color.raw_value)}} shadow-sm border-0 mb-3" style="border-radius: 12px; border-left: 5px solid #fecd4f !important;">
<div class="oe_kanban_content p-3">
<div class="o_kanban_record_top mb-2">
<div class="o_kanban_record_headings">
<strong class="o_kanban_record_title" style="font-size: 1.2rem; color: #171422;">
<field name="qty"/> x <field name="product_id"/>
</strong>
</div>
<div class="ms-auto h5 mb-0">
<span class="badge rounded-pill bg-light text-dark border">
<i class="fa fa-cutlery me-1"/> <field name="table_id"/>
</span>
</div>
</div>
<t t-if="record.customer_note.raw_value">
<div class="alert alert-warning py-2 px-3 mb-3 border-0" style="background: rgba(254, 205, 79, 0.1); border-radius: 8px;">
<i class="fa fa-sticky-note-o me-2"/> <strong>Note:</strong> <field name="customer_note"/>
</div>
</t>
<div class="o_kanban_record_body small text-muted mb-3">
<div class="d-flex justify-content-between align-items-center">
<span><i class="fa fa-clock-o me-1"/> <field name="create_date" widget="relative_time"/></span>
<span class="text-uppercase fw-bold" style="font-size: 0.7rem;"><field name="floor_id"/></span>
</div>
</div>
<div class="o_kanban_record_bottom border-top pt-3 mt-2">
<div class="oe_kanban_bottom_left">
<field name="preparation_status" widget="label_selection" options="{'classes': {'waiting': 'warning', 'preparing': 'info', 'ready': 'success', 'served': 'muted'}}"/>
</div>
<div class="oe_kanban_bottom_right">
<button t-if="record.preparation_status.raw_value == 'waiting'"
name="action_start_preparing" type="object"
class="btn btn-sm btn-primary px-3 shadow-sm" style="background: #171422; border: none; border-radius: 8px;">
Start Cooking
</button>
<button t-if="record.preparation_status.raw_value == 'preparing'"
name="action_mark_ready" type="object"
class="btn btn-sm btn-success px-3 shadow-sm" style="background: #28a745; border: none; border-radius: 8px;">
Mark Ready
</button>
<button t-if="record.preparation_status.raw_value == 'ready'"
name="action_mark_served" type="object"
class="btn btn-sm btn-outline-dark px-3 shadow-sm" style="border-radius: 8px;">
Served
</button>
</div>
</div>
</div>
</div>
</t>
</templates>
</kanban>
</field>
</record>
<!-- KDS Tree View -->
<record id="view_pos_order_line_kds_tree" model="ir.ui.view">
<field name="name">pos.order.line.kds.tree</field>
<field name="model">pos.order.line</field>
<field name="arch" type="xml">
<tree string="Kitchen Orders" create="false" edit="false" decoration-info="preparation_status == 'preparing'" decoration-success="preparation_status == 'ready'" decoration-muted="preparation_status == 'served'">
<field name="order_id"/>
<field name="floor_id"/>
<field name="table_id"/>
<field name="product_id"/>
<field name="qty"/>
<field name="customer_note" string="Notes"/>
<field name="preparation_status" widget="badge" decoration-info="preparation_status == 'preparing'" decoration-success="preparation_status == 'ready'" decoration-warning="preparation_status == 'waiting'"/>
<field name="create_date" string="Ordered At" widget="remaining_days"/>
</tree>
</field>
</record>
<!-- KDS Search View -->
<record id="view_pos_order_line_kds_search" model="ir.ui.view">
<field name="name">pos.order.line.kds.search</field>
<field name="model">pos.order.line</field>
<field name="arch" type="xml">
<search string="Kitchen Orders Search">
<field name="order_id"/>
<field name="product_id"/>
<field name="table_id"/>
<field name="floor_id"/>
<separator/>
<filter string="Waiting" name="waiting" domain="[('preparation_status', '=', 'waiting')]"/>
<filter string="Preparing" name="preparing" domain="[('preparation_status', '=', 'preparing')]"/>
<filter string="Ready" name="ready" domain="[('preparation_status', '=', 'ready')]"/>
<filter string="In Progress" name="in_progress" domain="[('preparation_status', 'in', ['waiting', 'preparing'])]"/>
<separator/>
<filter string="Today" name="today" domain="[('create_date', '>=', context_today().strftime('%Y-%m-%d 00:00:00'))]"/>
<group expand="0" string="Group By">
<filter string="Status" name="group_status" context="{'group_by': 'preparation_status'}"/>
<filter string="Table" name="group_table" context="{'group_by': 'table_id'}"/>
<filter string="Floor" name="group_floor" context="{'group_by': 'floor_id'}"/>
</group>
</search>
</field>
</record>
<!-- Window Action -->
<record id="action_kds_dashboard" model="ir.actions.act_window">
<field name="name">Kitchen Display System</field>
<field name="res_model">pos.order.line</field>
<field name="view_mode">kanban,tree,form</field>
<field name="domain">[('product_id.is_kitchen_item', '=', True)]</field>
<field name="search_view_id" ref="view_pos_order_line_kds_search"/>
<field name="context">{'search_default_in_progress': 1, 'search_default_group_status': 1}</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
Welcome to the Kitchen!
</p>
<p>
Orders sent from the POS will appear here for preparation.
</p>
</field>
</record>
</odoo>

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="product_template_form_view_kds" model="ir.ui.view">
<field name="name">product.template.form.kds</field>
<field name="model">product.template</field>
<field name="inherit_id" ref="product.product_template_only_form_view"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='categ_id']" position="after">
<field name="is_kitchen_item"/>
</xpath>
</field>
</record>
</odoo>