From 4261ec5ed057c0949ebf5c82ec9a027eb36d9287 Mon Sep 17 00:00:00 2001 From: git_admin Date: Fri, 1 May 2026 15:01:08 +0000 Subject: [PATCH] Tower: upload laundry_management 19.0.19.0.4 (via marketplace) --- .../models/laundry_dashboard.py | 180 ++++++++++++++++++ 1 file changed, 180 insertions(+) create mode 100644 addons/laundry_management/models/laundry_dashboard.py diff --git a/addons/laundry_management/models/laundry_dashboard.py b/addons/laundry_management/models/laundry_dashboard.py new file mode 100644 index 0000000..1e6be43 --- /dev/null +++ b/addons/laundry_management/models/laundry_dashboard.py @@ -0,0 +1,180 @@ +from odoo import models, fields, api + + +class LaundryDashboard(models.TransientModel): + """Live KPI dashboard — queries sale.order with is_laundry_order = True.""" + _name = 'laundry.dashboard' + _description = 'Laundry Dashboard' + + today_orders = fields.Integer(string="Today's Orders") + today_revenue = fields.Monetary(string="Today's Revenue", currency_field='currency_id') + today_collected = fields.Monetary(string='Collected Today', currency_field='currency_id') + today_outstanding = fields.Monetary(string='Outstanding Today', currency_field='currency_id') + + pending_count = fields.Integer(string='Pending Orders') + ready_count = fields.Integer(string='Ready for Pickup') + in_progress_count = fields.Integer(string='In Processing') + draft_count = fields.Integer(string='Quotes / Draft') + + session_is_open = fields.Boolean(string='Session Open') + session_name = fields.Char(string='Session') + session_opening_cash = fields.Monetary(string='Opening Float', currency_field='currency_id') + session_sales = fields.Monetary(string='Session Sales', currency_field='currency_id') + session_cash = fields.Monetary(string='Session Cash', currency_field='currency_id') + session_bank = fields.Monetary(string='Session Bank', currency_field='currency_id') + session_id = fields.Many2one('laundry.session', string='Session Link') + + month_orders = fields.Integer(string='Orders This Month') + month_revenue = fields.Monetary(string='Revenue This Month', currency_field='currency_id') + month_paid = fields.Monetary(string='Collected This Month', currency_field='currency_id') + + currency_id = fields.Many2one( + 'res.currency', + default=lambda self: self.env.company.currency_id, + ) + + @api.model + def _build(self): + today = fields.Date.today() + month_start = today.replace(day=1) + company = self.env.company + Order = self.env['sale.order'] + Payment = self.env['account.payment'] + + _base_domain = [ + ('is_laundry_order', '=', True), + ('company_id', '=', company.id), + ] + + # ── Today ────────────────────────────────────────────────────── + today_orders = Order.search(_base_domain + [ + ('date_order', '>=', fields.Datetime.to_datetime(today)), + ('state', 'not in', ['cancel', 'draft']), + ]) + today_invoices = today_orders.mapped('invoice_ids').filtered( + lambda i: i.state == 'posted' and i.move_type == 'out_invoice' + ) + today_revenue = sum(today_orders.mapped('amount_total')) + today_outstanding = sum( + max(i.amount_residual, 0.0) for i in today_invoices + ) + today_collected = today_revenue - today_outstanding + + # ── Pipeline (all active laundry orders) ────────────────────── + pipeline = Order.search(_base_domain + [ + ('state', '=', 'sale'), + ]) + pending_count = len(pipeline) + ready_count = len(pipeline.filtered(lambda o: o.laundry_state == 'ready')) + in_progress_count = len(pipeline.filtered(lambda o: o.laundry_state == 'processing')) + draft_count = len(Order.search(_base_domain + [('state', '=', 'draft')])) + + # ── Session ──────────────────────────────────────────────────── + session = self.env['laundry.session'].search([ + ('state', '=', 'opened'), + ('company_id', '=', company.id), + ], limit=1) + + # ── Month ────────────────────────────────────────────────────── + month_orders = Order.search(_base_domain + [ + ('date_order', '>=', fields.Datetime.to_datetime(month_start)), + ('state', 'not in', ['cancel', 'draft']), + ]) + month_invoices = month_orders.mapped('invoice_ids').filtered( + lambda i: i.state == 'posted' and i.move_type == 'out_invoice' + ) + month_revenue = sum(month_orders.mapped('amount_total')) + month_outstanding = sum(max(i.amount_residual, 0.0) for i in month_invoices) + month_paid = month_revenue - month_outstanding + + return self.create({ + 'today_orders' : len(today_orders), + 'today_revenue' : today_revenue, + 'today_collected' : max(today_collected, 0.0), + 'today_outstanding' : today_outstanding, + 'pending_count' : pending_count, + 'ready_count' : ready_count, + 'in_progress_count' : in_progress_count, + 'draft_count' : draft_count, + 'session_is_open' : bool(session), + 'session_name' : session.name if session else '', + 'session_opening_cash' : session.opening_cash if session else 0.0, + 'session_sales' : session.total_sales if session else 0.0, + 'session_cash' : session.total_cash if session else 0.0, + 'session_bank' : session.total_bank if session else 0.0, + 'session_id' : session.id if session else False, + 'month_orders' : len(month_orders), + 'month_revenue' : month_revenue, + 'month_paid' : max(month_paid, 0.0), + }) + + @api.model + def action_open_dashboard(self): + rec = self._build() + return { + 'type': 'ir.actions.act_window', + 'name': 'Dashboard', + 'res_model': 'laundry.dashboard', + 'res_id': rec.id, + 'view_mode': 'form', + 'target': 'main', + 'flags': {'mode': 'readonly'}, + } + + def action_refresh(self): + return self.action_open_dashboard() + + def action_new_order(self): + return { + 'type': 'ir.actions.act_window', + 'name': 'New Laundry Order', + 'res_model': 'sale.order', + 'view_mode': 'form', + 'target': 'current', + 'context': { + 'default_is_laundry_order': True, + }, + } + + def action_open_session(self): + if self.session_id: + return { + 'type': 'ir.actions.act_window', + 'name': 'Session', + 'res_model': 'laundry.session', + 'res_id': self.session_id.id, + 'view_mode': 'form', + } + return { + 'type': 'ir.actions.act_window', + 'name': 'Sessions', + 'res_model': 'laundry.session', + 'view_mode': 'list,form', + } + + def action_new_session(self): + return { + 'type': 'ir.actions.act_window', + 'name': 'New Session', + 'res_model': 'laundry.session', + 'view_mode': 'form', + } + + def action_view_ready_orders(self): + return { + 'type': 'ir.actions.act_window', + 'name': 'Ready for Pickup', + 'res_model': 'sale.order', + 'view_mode': 'list,form', + 'domain': [('is_laundry_order', '=', True), ('laundry_state', '=', 'ready')], + } + + def action_view_pending_orders(self): + return { + 'type': 'ir.actions.act_window', + 'name': 'Pending Orders', + 'res_model': 'sale.order', + 'view_mode': 'list,form', + 'domain': [('is_laundry_order', '=', True), ('state', '=', 'sale'), + ('laundry_state', 'not in', ['delivered'])], + }