Tower: unpublish laundry_management — remove source from 19.0 branch

This commit is contained in:
2026-05-02 11:15:37 +00:00
parent 22162f9d47
commit 26ae0e14df
230 changed files with 0 additions and 17001 deletions

View File

@@ -1,30 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Hide standard "Register Payment" buttons on laundry invoices.
Cashiers must pay via the laundry order wizard, not the invoice form. -->
<record id="view_move_form_laundry_inherit" model="ir.ui.view">
<field name="name">account.move.form.laundry</field>
<field name="model">account.move</field>
<field name="inherit_id" ref="account.view_move_form"/>
<field name="arch" type="xml">
<!-- Inject hidden helper field so invisible expressions can reference it -->
<xpath expr="//header" position="inside">
<field name="is_laundry_invoice" invisible="1"/>
</xpath>
<!-- Primary Pay button (no outstanding) -->
<xpath expr="//button[@id='account_invoice_payment_btn']" position="attributes">
<attribute name="invisible">is_laundry_invoice or state != 'posted' or payment_state not in ('not_paid', 'partial', 'in_payment') or move_type not in ('out_invoice', 'out_refund', 'in_invoice', 'in_refund', 'out_receipt', 'in_receipt') or invoice_has_outstanding</attribute>
</xpath>
<!-- Secondary Pay button (with outstanding) -->
<xpath expr="//button[@id='account_invoice_payment_secondary_btn']" position="attributes">
<attribute name="invisible">is_laundry_invoice or state != 'posted' or payment_state not in ('not_paid', 'partial', 'in_payment') or move_type not in ('out_invoice', 'out_refund', 'in_invoice', 'in_refund', 'out_receipt', 'in_receipt') or not invoice_has_outstanding</attribute>
</xpath>
</field>
</record>
</odoo>

View File

@@ -1,51 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<!-- Views of laundry label -->
<template id="laundry_order_label">
<t t-call="web.html_container">
<t t-foreach="docs" t-as="obj">
<t t-call="web.external_layout">
<div class="page">
<div class="col-xs-4" style="padding:0;">
<table style="border: 2px solid black;border-spacing:0;margin-bottom:0;height:122px;"
class="table">
<tbody>
<tr>
<th style="text-align: left; vertical-align: middle; padding-left: 15px; font-weight: bold;"
colspan="2">
<span t-esc="obj.name"/>
</th>
<th style="text-align: right; vertical-align: middle; padding-right: 15px; font-weight: bold;"
colspan="2">
<span t-esc="obj.order_date"/>
</th>
</tr>
<tr>
<td style="border: 2px solid black;text-align: center; vertical-align: middle;"
colspan="2">
<span>CUSTOMER</span>
</td>
<td style="border: 2px solid black;text-align: center; vertical-align: middle;"
colspan="2">
<span t-esc="obj.partner_id.name"/>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</t>
</t>
</t>
</template>
<!-- Report menu of Printing label-->
<record id="laundry_label_report" model="ir.actions.report">
<field name="name">Laundry Order Label</field>
<field name="model">laundry.order</field>
<field name="report_type">qweb-pdf</field>
<field name="report_name">laundry_management.laundry_order_label</field>
<field name="report_file">laundry_management.laundry_order_label</field>
<field name="binding_model_id" ref="model_laundry_order"/>
<field name="binding_type">report</field>
</record>
</odoo>

View File

@@ -1,252 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- ═══════════════════════════════════════════════════════════════
COMMISSION — Form View (PART 3: 3-state workflow)
═══════════════════════════════════════════════════════════════ -->
<record id="view_laundry_commission_form" model="ir.ui.view">
<field name="name">laundry.commission.form</field>
<field name="model">laundry.commission</field>
<field name="arch" type="xml">
<form string="Commission">
<header>
<button name="action_confirm"
string="✓ Confirm Commission"
type="object"
class="btn-primary"
invisible="state != 'pending'"
groups="laundry_management.group_laundry_manager"/>
<button name="action_mark_paid"
string="✓ Mark as Paid"
type="object"
class="btn-success"
invisible="state == 'paid'"/>
<button name="action_reset_pending"
string="↩ Reset to Pending"
type="object"
invisible="state == 'paid' or state == 'pending'"
groups="laundry_management.group_laundry_manager"/>
<field name="state"
widget="statusbar"
statusbar_visible="pending,confirmed,paid"/>
</header>
<sheet>
<div class="oe_title">
<h1><field name="name" readonly="1"/></h1>
</div>
<group>
<group string="Staff &amp; Role / الموظف والدور">
<field name="order_id"
options="{'no_create_edit': True}"/>
<field name="employee_id"
options="{'no_create_edit': True}"/>
<field name="role"
widget="badge"
decoration-info="role == 'reception'"
decoration-warning="role == 'processing'"
decoration-success="role == 'delivery'"/>
<field name="date"/>
</group>
<group string="Commission Calculation / حساب العمولة">
<field name="commission_type"/>
<field name="rate" string="Rate / Amount"/>
<field name="base_amount" readonly="1"/>
<field name="commission_amount"
readonly="1"
class="fw-bold fs-5"/>
</group>
</group>
<group>
<field name="notes" placeholder="Optional notes…"/>
</group>
</sheet>
<chatter/>
</form>
</field>
</record>
<!-- ═══════════════════════════════════════════════════════════════
COMMISSION — List View
═══════════════════════════════════════════════════════════════ -->
<record id="view_laundry_commission_list" model="ir.ui.view">
<field name="name">laundry.commission.list</field>
<field name="model">laundry.commission</field>
<field name="arch" type="xml">
<list string="Commissions"
decoration-muted="state == 'paid'"
decoration-info="state == 'confirmed'"
decoration-warning="state == 'pending'">
<field name="name"/>
<field name="order_id"/>
<field name="employee_id"/>
<field name="role"
widget="badge"
decoration-info="role == 'reception'"
decoration-warning="role == 'processing'"
decoration-success="role == 'delivery'"/>
<field name="date"/>
<field name="commission_type" optional="show"/>
<field name="rate" optional="show"/>
<field name="base_amount" optional="show"/>
<field name="commission_amount" sum="Total Commission"/>
<field name="state"
widget="badge"
decoration-warning="state == 'pending'"
decoration-info="state == 'confirmed'"
decoration-success="state == 'paid'"/>
<!-- Quick actions -->
<button name="action_confirm"
string="Confirm"
type="object"
icon="fa-check"
invisible="state != 'pending'"
title="Confirm commission"
groups="laundry_management.group_laundry_manager"/>
<button name="action_mark_paid"
string="Pay"
type="object"
icon="fa-check-circle"
invisible="state == 'paid'"
title="Mark as paid"/>
</list>
</field>
</record>
<!-- ═══════════════════════════════════════════════════════════════
COMMISSION — Graph View (PART 7)
═══════════════════════════════════════════════════════════════ -->
<record id="view_laundry_commission_graph" model="ir.ui.view">
<field name="name">laundry.commission.graph</field>
<field name="model">laundry.commission</field>
<field name="arch" type="xml">
<graph string="Commission Analysis" type="bar">
<field name="employee_id" type="row"/>
<field name="commission_amount" type="measure"/>
</graph>
</field>
</record>
<!-- ═══════════════════════════════════════════════════════════════
COMMISSION — Pivot View (PART 7)
═══════════════════════════════════════════════════════════════ -->
<record id="view_laundry_commission_pivot" model="ir.ui.view">
<field name="name">laundry.commission.pivot</field>
<field name="model">laundry.commission</field>
<field name="arch" type="xml">
<pivot string="Commission Analysis">
<field name="employee_id" type="row"/>
<field name="role" type="col"/>
<field name="commission_amount" type="measure"/>
<field name="base_amount" type="measure"/>
</pivot>
</field>
</record>
<!-- ═══════════════════════════════════════════════════════════════
COMMISSION — Search View
═══════════════════════════════════════════════════════════════ -->
<record id="view_laundry_commission_search" model="ir.ui.view">
<field name="name">laundry.commission.search</field>
<field name="model">laundry.commission</field>
<field name="arch" type="xml">
<search string="Search Commissions">
<field name="name" string="Reference"/>
<field name="order_id" string="Order"/>
<field name="employee_id" string="Staff Member"/>
<!-- State filters -->
<filter string="Pending"
name="pending"
domain="[('state','=','pending')]"/>
<filter string="Confirmed"
name="confirmed"
domain="[('state','=','confirmed')]"/>
<filter string="Paid"
name="paid"
domain="[('state','=','paid')]"/>
<!-- Unpaid (pending + confirmed) -->
<filter string="Unpaid / Unsettled"
name="unpaid"
domain="[('state','!=','paid')]"/>
<separator/>
<!-- Date filters -->
<filter string="This Month"
name="this_month"
domain="[('date','&gt;=',context_today().strftime('%Y-%m-01'))]"/>
<filter string="Today"
name="today"
domain="[('date','=',context_today().strftime('%Y-%m-%d'))]"/>
<separator/>
<!-- Role filters -->
<filter string="Reception"
name="reception"
domain="[('role','=','reception')]"/>
<filter string="Processing"
name="processing"
domain="[('role','=','processing')]"/>
<filter string="Delivery"
name="delivery"
domain="[('role','=','delivery')]"/>
<!-- Group-by -->
<separator/>
<filter string="By Staff"
name="by_employee"
context="{'group_by':'employee_id'}"/>
<filter string="By Role"
name="by_role"
context="{'group_by':'role'}"/>
<filter string="By Status"
name="by_state"
context="{'group_by':'state'}"/>
<filter string="By Order"
name="by_order"
context="{'group_by':'order_id'}"/>
<filter string="By Month"
name="by_month"
context="{'group_by':'date:month'}"/>
</search>
</field>
</record>
<!-- ═══════════════════════════════════════════════════════════════
ACTIONS
═══════════════════════════════════════════════════════════════ -->
<record id="action_laundry_commission" model="ir.actions.act_window">
<field name="name">Staff Commissions / العمولات</field>
<field name="res_model">laundry.commission</field>
<field name="view_mode">list,graph,pivot,form</field>
<field name="search_view_id" ref="view_laundry_commission_search"/>
<field name="context">{'search_default_unpaid': 1}</field>
<field name="view_ids" eval="[
(5, 0, 0),
(0, 0, {'view_mode': 'list', 'view_id': ref('view_laundry_commission_list')}),
(0, 0, {'view_mode': 'graph', 'view_id': ref('view_laundry_commission_graph')}),
(0, 0, {'view_mode': 'pivot', 'view_id': ref('view_laundry_commission_pivot')}),
(0, 0, {'view_mode': 'form', 'view_id': ref('view_laundry_commission_form')}),
]"/>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
No commissions yet.
</p>
<p>
Enable commission tracking in
<strong>Laundry → Configuration → Settings</strong>
and set commission rates for each stage.
</p>
</field>
</record>
<record id="action_laundry_commission_all" model="ir.actions.act_window">
<field name="name">All Commissions</field>
<field name="res_model">laundry.commission</field>
<field name="view_mode">list,graph,pivot,form</field>
<field name="search_view_id" ref="view_laundry_commission_search"/>
<field name="view_ids" eval="[
(5, 0, 0),
(0, 0, {'view_mode': 'list', 'view_id': ref('view_laundry_commission_list')}),
(0, 0, {'view_mode': 'graph', 'view_id': ref('view_laundry_commission_graph')}),
(0, 0, {'view_mode': 'pivot', 'view_id': ref('view_laundry_commission_pivot')}),
(0, 0, {'view_mode': 'form', 'view_id': ref('view_laundry_commission_form')}),
]"/>
</record>
</odoo>

View File

@@ -1,277 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- ═══════════════════════════════════════════════════════════════
LAUNDRY DASHBOARD — KPI Form View
Opened via server action so data is always fresh.
═══════════════════════════════════════════════════════════════ -->
<record id="view_laundry_dashboard_form" model="ir.ui.view">
<field name="name">laundry.dashboard.form</field>
<field name="model">laundry.dashboard</field>
<field name="arch" type="xml">
<form string="Laundry Dashboard" create="false" edit="false" delete="false">
<sheet>
<!-- ── Header toolbar ─────────────────────────────── -->
<div class="d-flex justify-content-between align-items-center mb-3">
<h2 class="mb-0">Laundry Dashboard</h2>
<div class="d-flex gap-2">
<button name="action_refresh"
string="Refresh"
type="object"
class="btn btn-sm btn-secondary"/>
<button name="action_new_order"
string="+ New Order"
type="object"
class="btn btn-sm btn-primary"/>
</div>
</div>
<!-- ── Session banner ─────────────────────────────── -->
<div class="alert alert-success mb-3"
role="alert"
invisible="not session_is_open">
<div class="d-flex justify-content-between align-items-center">
<div>
<strong>Session Open: </strong>
<field name="session_name" readonly="1" class="d-inline fw-bold"/>
&#160;|&#160;
Opening Float:
<field name="session_opening_cash" readonly="1"
widget="monetary"
options="{'currency_field': 'currency_id'}"
class="d-inline"/>
&#160;|&#160;
Session Sales:
<field name="session_sales" readonly="1"
widget="monetary"
options="{'currency_field': 'currency_id'}"
class="d-inline fw-bold text-success"/>
</div>
<button name="action_open_session"
string="View Session"
type="object"
class="btn btn-sm btn-success"/>
</div>
</div>
<div class="alert alert-warning mb-3"
role="alert"
invisible="session_is_open">
<div class="d-flex justify-content-between align-items-center">
<span>
<strong>No Active Session</strong>
Open a session before creating orders.
</span>
<button name="action_new_session"
string="Open Session"
type="object"
class="btn btn-sm btn-warning"/>
</div>
</div>
<!-- ── Today KPI row ──────────────────────────────── -->
<div class="row g-3 mb-4">
<div class="col-md-3">
<div class="card text-center border-primary h-100">
<div class="card-body">
<div class="display-6 fw-bold text-primary">
<field name="today_orders" readonly="1"/>
</div>
<div class="text-muted small mt-1">Today's Orders</div>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card text-center border-success h-100">
<div class="card-body">
<div class="display-6 fw-bold text-success">
<field name="today_revenue" readonly="1"
widget="monetary"
options="{'currency_field': 'currency_id'}"/>
</div>
<div class="text-muted small mt-1">Today's Revenue</div>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card text-center border-info h-100">
<div class="card-body">
<div class="display-6 fw-bold text-info">
<field name="today_collected" readonly="1"
widget="monetary"
options="{'currency_field': 'currency_id'}"/>
</div>
<div class="text-muted small mt-1">Collected Today</div>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card text-center border-danger h-100">
<div class="card-body">
<div class="display-6 fw-bold text-danger">
<field name="today_outstanding" readonly="1"
widget="monetary"
options="{'currency_field': 'currency_id'}"/>
</div>
<div class="text-muted small mt-1">Outstanding Today</div>
</div>
</div>
</div>
</div>
<!-- ── Operations Pipeline ────────────────────────── -->
<h5 class="mb-3 border-bottom pb-2">Operations Pipeline</h5>
<div class="row g-3 mb-4">
<div class="col-md-3">
<div class="card h-100 bg-success bg-opacity-10 border-success">
<div class="card-body text-center">
<div class="fs-2 fw-bold text-success">
<field name="ready_count" readonly="1"/>
</div>
<div class="small text-success fw-semibold">Ready for Pickup</div>
<button name="action_view_ready_orders"
string="View"
type="object"
class="btn btn-sm btn-outline-success mt-2"/>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card h-100 bg-warning bg-opacity-10 border-warning">
<div class="card-body text-center">
<div class="fs-2 fw-bold text-warning">
<field name="in_progress_count" readonly="1"/>
</div>
<div class="small text-warning fw-semibold">In Processing</div>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card h-100 bg-secondary bg-opacity-10">
<div class="card-body text-center">
<div class="fs-2 fw-bold text-secondary">
<field name="draft_count" readonly="1"/>
</div>
<div class="small text-muted">Draft / Quotes</div>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card h-100">
<div class="card-body text-center">
<div class="fs-2 fw-bold">
<field name="pending_count" readonly="1"/>
</div>
<div class="small text-muted">Total Pending</div>
<button name="action_view_pending_orders"
string="View All"
type="object"
class="btn btn-sm btn-outline-secondary mt-2"/>
</div>
</div>
</div>
</div>
<!-- ── Session breakdown (if open) ────────────────── -->
<div invisible="not session_is_open">
<h5 class="mb-3 border-bottom pb-2">Current Session Breakdown</h5>
<div class="row g-3 mb-4">
<div class="col-md-4">
<div class="card h-100">
<div class="card-body">
<div class="small text-muted">Total Session Sales</div>
<div class="fs-4 fw-bold">
<field name="session_sales" readonly="1"
widget="monetary"
options="{'currency_field': 'currency_id'}"/>
</div>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card h-100">
<div class="card-body">
<div class="small text-muted">Cash Collected</div>
<div class="fs-4 fw-bold text-success">
<field name="session_cash" readonly="1"
widget="monetary"
options="{'currency_field': 'currency_id'}"/>
</div>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card h-100">
<div class="card-body">
<div class="small text-muted">Bank / Card</div>
<div class="fs-4 fw-bold text-info">
<field name="session_bank" readonly="1"
widget="monetary"
options="{'currency_field': 'currency_id'}"/>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- ── Month summary ──────────────────────────────── -->
<h5 class="mb-3 border-bottom pb-2">This Month</h5>
<div class="row g-3 mb-2">
<div class="col-md-4">
<div class="card h-100">
<div class="card-body">
<div class="small text-muted">Orders This Month</div>
<div class="fs-4 fw-bold">
<field name="month_orders" readonly="1"/>
</div>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card h-100">
<div class="card-body">
<div class="small text-muted">Revenue This Month</div>
<div class="fs-4 fw-bold">
<field name="month_revenue" readonly="1"
widget="monetary"
options="{'currency_field': 'currency_id'}"/>
</div>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card h-100">
<div class="card-body">
<div class="small text-muted">Collected This Month</div>
<div class="fs-4 fw-bold text-success">
<field name="month_paid" readonly="1"
widget="monetary"
options="{'currency_field': 'currency_id'}"/>
</div>
</div>
</div>
</div>
</div>
<!-- hidden fields needed for widget resolution -->
<field name="currency_id" invisible="1"/>
<field name="session_is_open" invisible="1"/>
</sheet>
</form>
</field>
</record>
<!-- ═══════════════════════════════════════════════════════════════
SERVER ACTION — creates fresh dashboard record and opens it
═══════════════════════════════════════════════════════════════ -->
<record id="action_laundry_dashboard_server" model="ir.actions.server">
<field name="name">Open Laundry Dashboard</field>
<field name="model_id" ref="model_laundry_dashboard"/>
<field name="binding_model_id" ref="model_laundry_dashboard"/>
<field name="state">code</field>
<field name="code">action = model.action_open_dashboard()</field>
</record>
</odoo>

View File

@@ -1,163 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- ROOT ─────────────────────────────────────────────────────── -->
<menuitem id="menu_laundry_root"
name="Laundry"
sequence="60"
web_icon="laundry_management,static/description/icon.png"/>
<!-- DASHBOARD ─────────────────────────────────────────────── -->
<!-- Standard Odoo behavior: kanban grouped by state.
Real KPI dashboards live inside the Sales / Accounting
"Reporting" menus as graph/pivot views (see
views/laundry_reporting_views.xml). -->
<menuitem id="menu_laundry_dashboard"
name="Dashboard"
parent="menu_laundry_root"
action="action_laundry_dashboard"
sequence="5"
groups="laundry_management.group_laundry_operator"/>
<!-- ORDERS ────────────────────────────────────────────────── -->
<menuitem id="menu_laundry_orders"
name="Orders"
parent="menu_laundry_root"
sequence="10"
groups="laundry_management.group_laundry_operator"/>
<menuitem id="menu_laundry_all_orders"
name="All Orders"
parent="menu_laundry_orders"
action="action_laundry_order"
sequence="10"
groups="laundry_management.group_laundry_operator"/>
<menuitem id="menu_laundry_ready"
name="Ready for Pickup"
parent="menu_laundry_orders"
action="action_laundry_ready_orders"
sequence="30"
groups="laundry_management.group_laundry_operator"/>
<menuitem id="menu_laundry_items"
name="Items (Tracking)"
parent="menu_laundry_orders"
action="action_laundry_items"
sequence="40"
groups="laundry_management.group_laundry_operator"/>
<!-- UNPAID ─────────────────────────────────────────────── -->
<menuitem id="menu_laundry_unpaid"
name="Unpaid"
parent="menu_laundry_root"
sequence="20"
groups="laundry_management.group_laundry_cashier"/>
<menuitem id="menu_laundry_unpaid_orders"
name="Unpaid Orders"
parent="menu_laundry_unpaid"
action="action_laundry_unpaid_orders"
sequence="10"
groups="laundry_management.group_laundry_cashier"/>
<menuitem id="menu_laundry_unpaid_customers"
name="Unpaid Customers"
parent="menu_laundry_unpaid"
action="action_laundry_unpaid_customers"
sequence="20"
groups="laundry_management.group_laundry_cashier"/>
<!-- 3. CUSTOMERS ─────────────────────────────────────────────── -->
<menuitem id="menu_laundry_customers"
name="Customers"
parent="menu_laundry_root"
action="base.action_partner_form"
sequence="30"
groups="laundry_management.group_laundry_cashier"/>
<!-- 4. PRODUCTS ──────────────────────────────────────────────── -->
<menuitem id="menu_laundry_products"
name="Services"
parent="menu_laundry_root"
sequence="40"
groups="laundry_management.group_laundry_manager"/>
<menuitem id="menu_laundry_product_services"
name="Service Catalog"
parent="menu_laundry_products"
action="action_laundry_product_catalog"
sequence="10"
groups="laundry_management.group_laundry_manager"/>
<menuitem id="menu_laundry_product_categories"
name="Service Categories"
parent="menu_laundry_products"
action="product.product_category_action_form"
sequence="20"
groups="laundry_management.group_laundry_manager"/>
<!-- 5. REPORTS ───────────────────────────────────────────────── -->
<menuitem id="menu_laundry_reports"
name="Reports"
parent="menu_laundry_root"
sequence="50"
groups="laundry_management.group_laundry_manager"/>
<menuitem id="menu_laundry_report_orders"
name="Orders Analysis"
parent="menu_laundry_reports"
action="action_laundry_order"
sequence="10"
groups="laundry_management.group_laundry_manager"/>
<menuitem id="menu_laundry_report_commissions"
name="Commissions"
parent="menu_laundry_reports"
action="action_laundry_commission_all"
sequence="20"
groups="laundry_management.group_laundry_manager"/>
<!-- 6. CONFIGURATION ─────────────────────────────────────────── -->
<menuitem id="menu_laundry_config"
name="Configuration"
parent="menu_laundry_root"
sequence="90"
groups="laundry_management.group_laundry_manager"/>
<menuitem id="menu_laundry_settings"
name="Settings"
parent="menu_laundry_config"
action="action_laundry_settings"
sequence="5"
groups="laundry_management.group_laundry_manager"/>
<menuitem id="menu_laundry_payment_methods"
name="Payment Methods"
parent="menu_laundry_config"
action="action_laundry_payment_method"
sequence="10"
groups="laundry_management.group_laundry_manager"/>
<menuitem id="menu_laundry_commissions_config"
name="Commissions"
parent="menu_laundry_config"
action="action_laundry_commission"
sequence="20"
groups="laundry_management.group_laundry_manager"/>
<menuitem id="menu_laundry_order_types"
name="Order Types"
parent="menu_laundry_config"
action="action_laundry_order_type"
sequence="30"
groups="laundry_management.group_laundry_manager"/>
<menuitem id="menu_laundry_order_attributes"
name="Order Attributes"
parent="menu_laundry_config"
action="action_laundry_order_attribute"
sequence="40"
groups="laundry_management.group_laundry_manager"/>
</odoo>

View File

@@ -1,68 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_laundry_order_attribute_list" model="ir.ui.view">
<field name="name">laundry.order.attribute.list</field>
<field name="model">laundry.order.attribute</field>
<field name="arch" type="xml">
<list string="Order Attributes" editable="bottom">
<field name="sequence" widget="handle"/>
<field name="name"/>
<field name="code"/>
<field name="is_priority_related"/>
<field name="is_delivery_related"/>
<field name="pos_available"/>
<field name="active" widget="boolean_toggle"/>
<field name="company_id" groups="base.group_multi_company" optional="hide"/>
</list>
</field>
</record>
<record id="view_laundry_order_attribute_form" model="ir.ui.view">
<field name="name">laundry.order.attribute.form</field>
<field name="model">laundry.order.attribute</field>
<field name="arch" type="xml">
<form string="Order Attribute">
<sheet>
<widget name="web_ribbon" title="Archived" bg_color="bg-danger"
invisible="active"/>
<field name="active" invisible="1"/>
<field name="icon_image" widget="image" class="oe_avatar"/>
<div class="oe_title">
<label for="name"/>
<h1><field name="name" placeholder="e.g. Urgent"/></h1>
</div>
<group>
<group>
<field name="code"/>
<field name="sequence"/>
<field name="color" widget="color"/>
</group>
<group>
<field name="is_priority_related"/>
<field name="is_delivery_related"/>
<field name="pos_available"/>
<field name="extra_price"/>
<field name="company_id" groups="base.group_multi_company"/>
</group>
</group>
<group>
<field name="description" nolabel="1"/>
</group>
</sheet>
</form>
</field>
</record>
<record id="action_laundry_order_attribute" model="ir.actions.act_window">
<field name="name">Order Attributes</field>
<field name="res_model">laundry.order.attribute</field>
<field name="view_mode">list,form</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">Configure laundry order attributes</p>
<p>Attributes are optional per-order tags (Urgent, Fold, Hanger, Delicate...).
Use the semantic flags to drive behavior without hardcoding names.</p>
</field>
</record>
</odoo>

View File

@@ -1,108 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_laundry_order_type_list" model="ir.ui.view">
<field name="name">laundry.order.type.list</field>
<field name="model">laundry.order.type</field>
<field name="arch" type="xml">
<list string="Order Types">
<field name="sequence" widget="handle"/>
<field name="name"/>
<field name="code"/>
<field name="priority"/>
<field name="is_delivery"/>
<field name="requires_address"/>
<field name="requires_scheduled_time"/>
<field name="pos_available"/>
<field name="active" widget="boolean_toggle"/>
<field name="company_id" groups="base.group_multi_company" optional="hide"/>
</list>
</field>
</record>
<record id="view_laundry_order_type_form" model="ir.ui.view">
<field name="name">laundry.order.type.form</field>
<field name="model">laundry.order.type</field>
<field name="arch" type="xml">
<form string="Order Type">
<sheet>
<widget name="web_ribbon" title="Archived" bg_color="bg-danger"
invisible="active"/>
<field name="active" invisible="1"/>
<field name="icon_image" widget="image" class="oe_avatar"/>
<div class="oe_title">
<label for="name"/>
<h1><field name="name" placeholder="e.g. Express"/></h1>
</div>
<group>
<group>
<field name="code"/>
<field name="sequence"/>
<field name="priority"/>
<field name="color" widget="color"/>
</group>
<group>
<field name="is_delivery"/>
<field name="requires_address"/>
<field name="requires_scheduled_time"/>
<field name="pos_available"/>
<field name="extra_price"/>
<field name="company_id" groups="base.group_multi_company"/>
</group>
</group>
<notebook>
<page string="Default Attributes" name="attributes">
<field name="attribute_ids" widget="many2many_tags"
options="{'color_field': 'color'}"/>
</page>
<page string="Description" name="description">
<field name="description" nolabel="1"
placeholder="Shown as a tooltip on the POS card."/>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
<record id="view_laundry_order_type_kanban" model="ir.ui.view">
<field name="name">laundry.order.type.kanban</field>
<field name="model">laundry.order.type</field>
<field name="arch" type="xml">
<kanban>
<field name="name"/>
<field name="priority"/>
<field name="is_delivery"/>
<field name="color"/>
<templates>
<t t-name="card">
<div class="d-flex flex-column gap-1 p-2">
<div class="fw-bold fs-5"><field name="name"/></div>
<div class="text-muted small">
<field name="code"/>
</div>
<div class="d-flex gap-2 mt-1">
<span t-if="record.priority.raw_value === 'urgent'"
class="badge bg-danger">Urgent</span>
<span t-if="record.is_delivery.raw_value"
class="badge bg-info">Delivery</span>
</div>
</div>
</t>
</templates>
</kanban>
</field>
</record>
<record id="action_laundry_order_type" model="ir.actions.act_window">
<field name="name">Order Types</field>
<field name="res_model">laundry.order.type</field>
<field name="view_mode">list,kanban,form</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">Configure laundry order types</p>
<p>Examples: Standard, Express, Home Delivery, VIP.
Types drive priority, delivery, and workflow hints.</p>
</field>
</record>
</odoo>

View File

@@ -1,686 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- ================================================================
POS ORDER — Smart Button to Laundry Order
================================================================ -->
<record id="view_pos_order_form_laundry_button" model="ir.ui.view">
<field name="name">pos.order.form.laundry.button</field>
<field name="model">pos.order</field>
<field name="inherit_id" ref="point_of_sale.view_pos_pos_form"/>
<field name="arch" type="xml">
<xpath expr="//div[@name='button_box']" position="inside">
<button name="action_open_laundry_order"
type="object"
class="oe_stat_button"
icon="fa-tshirt"
invisible="not laundry_order_id">
<field name="laundry_order_id" widget="statinfo" string="Laundry"/>
</button>
</xpath>
</field>
</record>
<!-- ================================================================
LAUNDRY ORDER — Form View
================================================================ -->
<record id="view_laundry_order_form" model="ir.ui.view">
<field name="name">laundry.order.form</field>
<field name="model">laundry.order</field>
<field name="arch" type="xml">
<form string="Laundry Order" create="0">
<header>
<button name="action_process"
string="Start Processing"
type="object"
class="btn-primary"
invisible="state != 'intake'"/>
<button name="action_ready"
string="Mark Ready"
type="object"
class="btn-primary"
invisible="state != 'processing'"/>
<button name="action_deliver"
string="Deliver"
type="object"
class="btn-success"
invisible="state != 'ready'"/>
<button name="action_cancel"
string="Cancel Order"
type="object"
class="btn-danger"
confirm="Cancel this laundry order? This is permanent."
invisible="source_type == 'pos' or state in ('delivered', 'cancelled')"/>
<button name="action_open_unlock_wizard"
string="Unlock for Editing"
type="object"
class="btn-warning"
icon="fa-unlock-alt"
invisible="not locked"
groups="laundry_management.group_laundry_manager_override"/>
<button name="%(action_report_laundry_work_order)d"
string="Print Work Order"
type="action"
class="btn-secondary"/>
<field name="state"
widget="statusbar"
statusbar_visible="intake,processing,ready,delivered"/>
</header>
<sheet>
<!-- Lock ribbon — visible at a glance whether the order
is currently editable. Source + manager-unlock
metadata are below in the Order Info group. -->
<widget name="web_ribbon" title="Locked"
bg_color="text-bg-warning"
invisible="not locked"/>
<div class="oe_button_box" name="button_box">
<button name="action_open_pos_order"
type="object"
class="oe_stat_button"
icon="fa-shopping-cart"
invisible="not pos_order_id">
<span class="o_stat_text">POS Order</span>
</button>
</div>
<div class="oe_title">
<h1><field name="name" readonly="1"/></h1>
</div>
<group>
<group string="Customer">
<field name="partner_id" readonly="locked"/>
<field name="partner_phone" readonly="1"/>
<field name="pos_reference" readonly="1"
invisible="not pos_reference"/>
</group>
<group string="Order Info">
<field name="source_type" readonly="1"/>
<field name="is_from_pos" invisible="1"/>
<field name="locked" invisible="1"/>
<field name="tracking_enabled"
readonly="locked"
help="Phase-4 prep: per-item tracking will be wired in a follow-up."/>
<field name="amount_total" readonly="1"/>
<field name="amount_paid" readonly="1"/>
<field name="amount_due" readonly="1"/>
<field name="item_count" readonly="1"/>
<field name="company_id" groups="base.group_multi_company" readonly="1"/>
<field name="currency_id" invisible="1"/>
<field name="pos_order_id" invisible="1"/>
</group>
</group>
<!-- Manager unlock window — info-only block, visible when
a manager has granted a temporary edit window. -->
<div class="alert alert-info mt-2"
role="alert"
invisible="not manager_unlocked_until">
<strong>Manager unlock active</strong> until
<field name="manager_unlocked_until" readonly="1"
class="d-inline-block mx-1"/>
by
<field name="manager_unlocked_by" readonly="1"
class="d-inline-block mx-1"
options="{'no_open': True}"/>.
Reason: <em><field name="manager_unlock_reason" readonly="1"
class="d-inline-block"/></em>
</div>
<group>
<group string="Type &amp; Attributes">
<!-- Lock-driven readonly. POS-sourced orders
stay readonly until a manager unlock window
is active. Manual orders editable until
final state. -->
<field name="order_type_id" readonly="locked"/>
<field name="attribute_ids" widget="many2many_tags"
options="{'color_field': 'color'}"
readonly="locked"/>
<field name="priority_level" widget="badge"
decoration-danger="priority_level == 'urgent'"
decoration-muted="priority_level == 'normal'"
readonly="locked"/>
</group>
<group string="Delivery">
<field name="is_delivery" readonly="locked"/>
<field name="delivery_address"
invisible="not is_delivery"
required="is_delivery and not locked"
readonly="locked"/>
<field name="delivery_scheduled_at"
invisible="not is_delivery"
readonly="locked"/>
</group>
</group>
<notebook>
<page string="Items" name="lines">
<!-- Line list is editable only when the order
is unlocked (manual + non-final, OR an
active manager unlock window). The model
layer enforces the same rule independently. -->
<field name="line_ids" readonly="locked">
<list editable="bottom">
<field name="tracking_code" readonly="1"/>
<field name="product_id"
readonly="parent.locked"/>
<field name="description"
readonly="parent.locked"/>
<field name="qty"
readonly="parent.locked"/>
<field name="price_unit"
readonly="parent.locked"/>
<field name="subtotal" readonly="1"/>
<field name="customer_note"/>
<field name="state" widget="badge"
decoration-info="state == 'received'"
decoration-warning="state == 'processing'"
decoration-primary="state == 'ready'"
decoration-success="state == 'delivered'"/>
<button name="action_line_process" type="object"
string="Process" icon="fa-cog"
invisible="state != 'received'"/>
<button name="action_line_ready" type="object"
string="Ready" icon="fa-check"
invisible="state != 'processing'"/>
<button name="action_line_deliver" type="object"
string="Deliver" icon="fa-truck"
invisible="state != 'ready'"/>
</list>
</field>
</page>
<page string="Notes" name="notes">
<field name="notes" placeholder="Internal notes..."/>
</page>
</notebook>
</sheet>
<chatter/>
</form>
</field>
</record>
<!-- ================================================================
LAUNDRY ORDER — Main List View (with quick-action buttons)
================================================================ -->
<record id="view_laundry_order_list" model="ir.ui.view">
<field name="name">laundry.order.list</field>
<field name="model">laundry.order</field>
<field name="arch" type="xml">
<list string="Laundry Orders" create="0" multi_edit="1"
decoration-danger="priority_level == 'urgent'"
default_order="priority_level desc, is_delivery desc, create_date desc">
<field name="name" string="Order No." readonly="1"/>
<field name="partner_id" string="Customer" readonly="1"/>
<field name="order_type_id" string="Type" optional="show" readonly="1"/>
<field name="attribute_ids" widget="many2many_tags"
options="{'color_field': 'color'}"
optional="show" readonly="1"/>
<field name="priority_level" widget="badge" optional="show"
decoration-danger="priority_level == 'urgent'"
readonly="1"/>
<field name="is_delivery" widget="boolean_toggle"
optional="show" readonly="1"/>
<field name="delivery_scheduled_at" optional="hide" readonly="1"/>
<field name="pos_reference" string="POS Ref" optional="hide" readonly="1"/>
<field name="item_count" string="Items" readonly="1"/>
<field name="amount_total" string="Total" sum="Total" readonly="1"/>
<field name="amount_due" string="Due" optional="show" readonly="1"
decoration-danger="amount_due &gt; 0"/>
<field name="state" widget="badge" readonly="1"
decoration-info="state == 'intake'"
decoration-warning="state == 'processing'"
decoration-primary="state == 'ready'"
decoration-success="state == 'delivered'"
decoration-muted="state == 'cancelled'"/>
<field name="source_type" optional="hide" readonly="1"/>
<field name="locked" widget="boolean_toggle" readonly="1"
optional="hide"/>
<field name="create_date" string="Date" optional="show" readonly="1"/>
<button name="action_process" string="Process"
type="object" icon="fa-cog"
invisible="state != 'intake'"/>
<button name="action_ready" string="Ready"
type="object" icon="fa-check"
invisible="state != 'processing'"/>
<button name="action_deliver" string="Deliver"
type="object" icon="fa-truck"
invisible="state != 'ready'"/>
</list>
</field>
</record>
<!-- ================================================================
LAUNDRY ORDER — Ready for Pickup List (simplified)
================================================================ -->
<record id="view_laundry_order_ready_list" model="ir.ui.view">
<field name="name">laundry.order.ready.list</field>
<field name="model">laundry.order</field>
<field name="arch" type="xml">
<list string="Ready for Pickup" create="0">
<field name="name" string="Order No."/>
<field name="partner_id" string="Customer"/>
<field name="partner_phone" string="Phone"/>
<field name="item_count" string="Items"/>
<field name="amount_total" string="Total"/>
<field name="amount_due" string="Due"
decoration-danger="amount_due &gt; 0"/>
<button name="action_deliver" string="Deliver Now"
type="object" icon="fa-truck"
class="btn-success"/>
</list>
</field>
</record>
<!-- ================================================================
LAUNDRY ORDER — Operational Control Board (Smart Kanban)
================================================================ -->
<record id="view_laundry_order_kanban" model="ir.ui.view">
<field name="name">laundry.order.kanban</field>
<field name="model">laundry.order</field>
<field name="arch" type="xml">
<kanban class="o_kanban_mobile laundry-board" create="0"
group_create="0" group_delete="0" group_edit="0"
default_group_by="state"
default_order="priority_level desc, is_delivery desc, create_date asc">
<field name="name"/>
<field name="partner_id"/>
<field name="state"/>
<field name="amount_total"/>
<field name="amount_due"/>
<field name="item_count"/>
<field name="currency_id"/>
<field name="order_type_id"/>
<field name="priority_level"/>
<field name="is_delivery"/>
<field name="create_date"/>
<field name="amount_deferred"/>
<progressbar field="state"
colors='{"intake": "info", "processing": "warning", "ready": "success", "delivered": "muted"}'/>
<templates>
<t t-name="card">
<div class="laundry-board__card"
t-att-data-priority="record.priority_level.raw_value"
t-att-data-state="record.state.raw_value"
t-att-data-delivery="record.is_delivery.raw_value ? '1' : '0'">
<div class="laundry-board__strip"/>
<div class="laundry-board__head">
<div class="laundry-board__title-row">
<strong class="laundry-board__name">
<field name="name"/>
</strong>
<span class="laundry-board__total">
<field name="amount_total" widget="monetary"/>
</span>
</div>
<div class="laundry-board__customer">
<i class="fa fa-user-o me-1"/>
<field name="partner_id"/>
</div>
</div>
<div class="laundry-board__badges">
<span t-if="record.priority_level.raw_value === 'urgent'"
class="laundry-pill laundry-pill--urgent">
<i class="fa fa-bolt me-1"/>Urgent
</span>
<span t-if="record.is_delivery.raw_value"
class="laundry-pill laundry-pill--delivery">
<i class="fa fa-truck me-1"/>Delivery
</span>
<span t-if="record.order_type_id.raw_value"
class="laundry-pill laundry-pill--type">
<i class="fa fa-tag me-1"/>
<field name="order_type_id"/>
</span>
<span t-if="record.amount_deferred.raw_value &gt; 0"
class="laundry-pill laundry-pill--deferred"
title="Paid later via Customer Account">
<i class="fa fa-hourglass-half me-1"/>Deferred
</span>
</div>
<div class="laundry-board__meta">
<span class="laundry-board__items">
<i class="fa fa-shopping-bag me-1"/>
<field name="item_count"/> items
</span>
<span t-if="record.amount_due.raw_value &gt; 0"
class="laundry-board__due">
Due: <field name="amount_due" widget="monetary"/>
</span>
<span class="laundry-board__time">
<i class="fa fa-clock-o me-1"/>
<field name="create_date" widget="date"/>
</span>
</div>
<div class="laundry-board__actions">
<button t-if="record.state.raw_value == 'intake'"
name="action_process" type="object"
class="btn btn-sm btn-primary w-100">
<i class="fa fa-play me-1"/>Start Processing
</button>
<button t-if="record.state.raw_value == 'processing'"
name="action_ready" type="object"
class="btn btn-sm btn-primary w-100">
<i class="fa fa-check me-1"/>Mark Ready
</button>
<button t-if="record.state.raw_value == 'ready'"
name="action_deliver" type="object"
class="btn btn-sm btn-success w-100">
<i class="fa fa-truck me-1"/>Deliver
</button>
</div>
</div>
</t>
</templates>
</kanban>
</field>
</record>
<!-- ================================================================
LAUNDRY ORDER — Search View
================================================================ -->
<record id="view_laundry_order_search" model="ir.ui.view">
<field name="name">laundry.order.search</field>
<field name="model">laundry.order</field>
<field name="arch" type="xml">
<search string="Search Laundry Orders">
<field name="name" string="Order No."/>
<field name="partner_id" string="Customer"/>
<field name="pos_reference" string="POS Reference"/>
<separator/>
<filter string="Intake" name="intake" domain="[('state','=','intake')]"/>
<filter string="Processing" name="processing" domain="[('state','=','processing')]"/>
<filter string="Ready" name="ready" domain="[('state','=','ready')]"/>
<filter string="Delivered" name="delivered" domain="[('state','=','delivered')]"/>
<separator/>
<filter string="Active" name="active" domain="[('state','!=','delivered')]"/>
<filter string="Unpaid" name="unpaid" domain="[('amount_due','&gt;',0)]"/>
<filter string="Urgent" name="urgent" domain="[('priority_level','=','urgent')]"/>
<filter string="Delivery" name="delivery" domain="[('is_delivery','=',True)]"/>
<separator/>
<filter string="By Status" name="by_state" context="{'group_by':'state'}"/>
<filter string="By Customer" name="by_customer" context="{'group_by':'partner_id'}"/>
<filter string="By Order Type" name="by_order_type" context="{'group_by':'order_type_id'}"/>
</search>
</field>
</record>
<!-- ================================================================
ACTIONS
================================================================ -->
<record id="action_laundry_order" model="ir.actions.act_window">
<field name="name">Laundry Orders</field>
<field name="res_model">laundry.order</field>
<field name="view_mode">list,form</field>
<field name="context">{'search_default_active': 1}</field>
<field name="view_ids" eval="[
(5, 0, 0),
(0, 0, {'view_mode': 'list', 'view_id': ref('view_laundry_order_list')}),
(0, 0, {'view_mode': 'form', 'view_id': ref('view_laundry_order_form')}),
]"/>
<field name="search_view_id" ref="view_laundry_order_search"/>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
No laundry orders yet.
</p>
<p>Orders are created automatically from POS.</p>
</field>
</record>
<record id="action_laundry_ready_orders" model="ir.actions.act_window">
<field name="name">Ready for Pickup</field>
<field name="res_model">laundry.order</field>
<field name="view_mode">list,form</field>
<field name="domain">[('state','=','ready')]</field>
<field name="view_ids" eval="[
(5, 0, 0),
(0, 0, {'view_mode': 'list', 'view_id': ref('view_laundry_order_ready_list')}),
(0, 0, {'view_mode': 'form', 'view_id': ref('view_laundry_order_form')}),
]"/>
<field name="search_view_id" ref="view_laundry_order_search"/>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
No orders ready for pickup.
</p>
</field>
</record>
<!-- Dashboard: kanban grouped by state -->
<record id="action_laundry_dashboard" model="ir.actions.act_window">
<field name="name">Operations Dashboard</field>
<field name="res_model">laundry.order</field>
<field name="view_mode">kanban,list,form</field>
<field name="context">{'search_default_active': 1}</field>
<field name="view_ids" eval="[
(5, 0, 0),
(0, 0, {'view_mode': 'kanban', 'view_id': ref('view_laundry_order_kanban')}),
(0, 0, {'view_mode': 'list', 'view_id': ref('view_laundry_order_list')}),
(0, 0, {'view_mode': 'form', 'view_id': ref('view_laundry_order_form')}),
]"/>
<field name="search_view_id" ref="view_laundry_order_search"/>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
No active orders.
</p>
</field>
</record>
<!-- Unpaid orders -->
<record id="action_laundry_unpaid_orders" model="ir.actions.act_window">
<field name="name">Unpaid Orders</field>
<field name="res_model">laundry.order</field>
<field name="view_mode">list,form</field>
<field name="domain">[('amount_due', '&gt;', 0)]</field>
<field name="view_ids" eval="[
(5, 0, 0),
(0, 0, {'view_mode': 'list', 'view_id': ref('view_laundry_order_list')}),
(0, 0, {'view_mode': 'form', 'view_id': ref('view_laundry_order_form')}),
]"/>
<field name="search_view_id" ref="view_laundry_order_search"/>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">All orders are fully paid!</p>
</field>
</record>
<!-- Unpaid customers -->
<record id="action_laundry_unpaid_customers" model="ir.actions.act_window">
<field name="name">Unpaid Customers</field>
<field name="res_model">laundry.order</field>
<field name="view_mode">list,form</field>
<field name="domain">[('amount_due', '&gt;', 0)]</field>
<field name="context">{'search_default_by_customer': 1}</field>
<field name="view_ids" eval="[
(5, 0, 0),
(0, 0, {'view_mode': 'list', 'view_id': ref('view_laundry_order_list')}),
(0, 0, {'view_mode': 'form', 'view_id': ref('view_laundry_order_form')}),
]"/>
<field name="search_view_id" ref="view_laundry_order_search"/>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">No customers with unpaid orders!</p>
</field>
</record>
<!-- ================================================================
LAUNDRY ITEM (line-level) — Tracking views
================================================================ -->
<record id="view_laundry_order_line_search" model="ir.ui.view">
<field name="name">laundry.order.line.search</field>
<field name="model">laundry.order.line</field>
<field name="arch" type="xml">
<search string="Search Items">
<field name="tracking_code"/>
<field name="order_id"/>
<field name="order_partner_id" string="Customer"/>
<field name="product_id"/>
<separator/>
<filter string="Received" name="received" domain="[('state','=','received')]"/>
<filter string="Processing" name="processing" domain="[('state','=','processing')]"/>
<filter string="Ready" name="ready" domain="[('state','=','ready')]"/>
<filter string="Delivered" name="delivered" domain="[('state','=','delivered')]"/>
<separator/>
<filter string="Active" name="active" domain="[('state','!=','delivered')]"/>
<separator/>
<filter string="By Status" name="by_state" context="{'group_by':'state'}"/>
<filter string="By Customer" name="by_customer" context="{'group_by':'order_partner_id'}"/>
<filter string="By Order" name="by_order" context="{'group_by':'order_id'}"/>
</search>
</field>
</record>
<record id="view_laundry_order_line_list" model="ir.ui.view">
<field name="name">laundry.order.line.list</field>
<field name="model">laundry.order.line</field>
<field name="arch" type="xml">
<list string="Laundry Items" create="0" edit="0">
<field name="tracking_code" readonly="1"/>
<field name="order_id" readonly="1"/>
<field name="order_partner_id" string="Customer" readonly="1"/>
<field name="product_id" readonly="1"/>
<field name="qty" readonly="1"/>
<field name="state" widget="badge" readonly="1"
decoration-info="state == 'received'"
decoration-warning="state == 'processing'"
decoration-primary="state == 'ready'"
decoration-success="state == 'delivered'"/>
<button name="action_line_process" string="Process"
type="object" icon="fa-cog"
invisible="state != 'received'"/>
<button name="action_line_ready" string="Ready"
type="object" icon="fa-check"
invisible="state != 'processing'"/>
<button name="action_line_deliver" string="Deliver"
type="object" icon="fa-truck"
invisible="state != 'ready'"/>
</list>
</field>
</record>
<record id="view_laundry_order_line_kanban" model="ir.ui.view">
<field name="name">laundry.order.line.kanban</field>
<field name="model">laundry.order.line</field>
<field name="arch" type="xml">
<kanban class="laundry-board" default_group_by="state"
create="0" group_create="0" group_delete="0" group_edit="0">
<field name="tracking_code"/>
<field name="product_id"/>
<field name="order_id"/>
<field name="order_partner_id"/>
<field name="qty"/>
<field name="state"/>
<progressbar field="state"
colors='{"received": "info", "processing": "warning", "ready": "success", "delivered": "muted"}'/>
<templates>
<t t-name="card">
<div class="laundry-board__card" t-att-data-state="record.state.raw_value">
<div class="laundry-board__strip"/>
<div class="laundry-board__head">
<div class="laundry-board__title-row">
<strong class="laundry-board__name">
<field name="tracking_code"/>
</strong>
<span class="laundry-board__total">
x<field name="qty"/>
</span>
</div>
<div class="laundry-board__customer">
<i class="fa fa-tag me-1" title="Product"/>
<field name="product_id"/>
</div>
<div class="laundry-board__customer">
<i class="fa fa-user-o me-1" title="Customer"/>
<field name="order_partner_id"/>
&#160;·&#160;
<field name="order_id"/>
</div>
</div>
<div class="laundry-board__actions">
<button t-if="record.state.raw_value == 'received'"
name="action_line_process" type="object"
class="btn btn-sm btn-primary w-100">
<i class="fa fa-play me-1"/>Start Processing
</button>
<button t-if="record.state.raw_value == 'processing'"
name="action_line_ready" type="object"
class="btn btn-sm btn-primary w-100">
<i class="fa fa-check me-1"/>Mark Ready
</button>
<button t-if="record.state.raw_value == 'ready'"
name="action_line_deliver" type="object"
class="btn btn-sm btn-success w-100">
<i class="fa fa-truck me-1"/>Deliver
</button>
</div>
</div>
</t>
</templates>
</kanban>
</field>
</record>
<record id="action_laundry_items" model="ir.actions.act_window">
<field name="name">Laundry Items</field>
<field name="res_model">laundry.order.line</field>
<field name="view_mode">kanban,list</field>
<field name="context">{'search_default_active': 1}</field>
<field name="search_view_id" ref="view_laundry_order_line_search"/>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">No items yet.</p>
<p>Items appear here once an order is created from POS.</p>
</field>
</record>
<!-- ================================================================
SERVICE CATALOG
================================================================ -->
<record id="action_laundry_product_catalog" model="ir.actions.act_window">
<field name="name">Service Catalog</field>
<field name="res_model">product.template</field>
<field name="view_mode">list,form</field>
<field name="domain">[('is_laundry_service','=',True)]</field>
<field name="context">{
'default_is_laundry_service': True,
'default_type': 'service',
'default_sale_ok': True,
}</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
Create your first laundry service product.
</p>
</field>
</record>
<!-- ================================================================
SERVER ACTIONS — Bulk Operations
================================================================ -->
<record id="action_bulk_process" model="ir.actions.server">
<field name="name">Start Processing</field>
<field name="model_id" ref="model_laundry_order"/>
<field name="binding_model_id" ref="model_laundry_order"/>
<field name="binding_view_types">list</field>
<field name="state">code</field>
<field name="code">
if records:
records.filtered(lambda o: o.state == 'intake').action_process()
</field>
</record>
<record id="action_bulk_ready" model="ir.actions.server">
<field name="name">Mark Ready</field>
<field name="model_id" ref="model_laundry_order"/>
<field name="binding_model_id" ref="model_laundry_order"/>
<field name="binding_view_types">list</field>
<field name="state">code</field>
<field name="code">
if records:
records.filtered(lambda o: o.state == 'processing').action_ready()
</field>
</record>
<record id="action_bulk_deliver" model="ir.actions.server">
<field name="name">Deliver</field>
<field name="model_id" ref="model_laundry_order"/>
<field name="binding_model_id" ref="model_laundry_order"/>
<field name="binding_view_types">list</field>
<field name="state">code</field>
<field name="code">
if records:
records.filtered(lambda o: o.state == 'ready').action_deliver()
</field>
</record>
</odoo>

View File

@@ -1,101 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- ═══════════════════════════════════════════════════════════════
LAUNDRY PAYMENT METHOD — Form View
═══════════════════════════════════════════════════════════════ -->
<record id="view_laundry_payment_method_form" model="ir.ui.view">
<field name="name">laundry.payment.method.form</field>
<field name="model">laundry.payment.method</field>
<field name="arch" type="xml">
<form string="Payment Method">
<sheet>
<div class="oe_title">
<h1><field name="name" placeholder="e.g. Cash, Visa, Bank Transfer"/></h1>
</div>
<group>
<group string="Method Settings">
<field name="payment_type"/>
<field name="journal_id"
options="{'no_create': True}"
invisible="payment_type == 'credit'"
required="payment_type in ('cash','bank')"/>
<field name="is_default"/>
<field name="active"/>
</group>
<group string="Display">
<field name="sequence"/>
<field name="company_id"
groups="base.group_multi_company"
options="{'no_create': True}"/>
</group>
</group>
</sheet>
</form>
</field>
</record>
<!-- ═══════════════════════════════════════════════════════════════
LAUNDRY PAYMENT METHOD — List View
═══════════════════════════════════════════════════════════════ -->
<record id="view_laundry_payment_method_list" model="ir.ui.view">
<field name="name">laundry.payment.method.list</field>
<field name="model">laundry.payment.method</field>
<field name="arch" type="xml">
<list string="Payment Methods" editable="top">
<field name="sequence" widget="handle"/>
<field name="name"/>
<field name="payment_type"
widget="badge"
decoration-success="payment_type == 'cash'"
decoration-info="payment_type == 'bank'"
decoration-warning="payment_type == 'credit'"/>
<field name="journal_id"/>
<field name="is_default"/>
<field name="active"/>
</list>
</field>
</record>
<!-- ═══════════════════════════════════════════════════════════════
LAUNDRY PAYMENT METHOD — Search View
═══════════════════════════════════════════════════════════════ -->
<record id="view_laundry_payment_method_search" model="ir.ui.view">
<field name="name">laundry.payment.method.search</field>
<field name="model">laundry.payment.method</field>
<field name="arch" type="xml">
<search string="Search Payment Methods">
<field name="name" string="Name"/>
<filter string="Active" name="active"
domain="[('active','=',True)]"/>
<filter string="Cash" name="cash"
domain="[('payment_type','=','cash')]"/>
<filter string="Bank / Card" name="bank"
domain="[('payment_type','=','bank')]"/>
<filter string="Credit / Deferred" name="credit"
domain="[('payment_type','=','credit')]"/>
</search>
</field>
</record>
<!-- ═══════════════════════════════════════════════════════════════
ACTION
═══════════════════════════════════════════════════════════════ -->
<record id="action_laundry_payment_method" model="ir.actions.act_window">
<field name="name">Payment Methods</field>
<field name="res_model">laundry.payment.method</field>
<field name="view_mode">list,form</field>
<field name="search_view_id" ref="view_laundry_payment_method_search"/>
<field name="context">{'search_default_active': 1}</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
No payment methods configured yet.
</p>
<p>
Create payment methods (Cash, Card, Bank Transfer, Credit)
linked to accounting journals for session reconciliation.
</p>
</field>
</record>
</odoo>

View File

@@ -1,144 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- ═══════════════════════════════════════════════════════════════
LAUNDRY PAYMENT WIZARD — POS-like payment screen (PART 2)
Model: laundry.payment.wizard
═══════════════════════════════════════════════════════════════ -->
<record id="view_laundry_payment_wizard_form" model="ir.ui.view">
<field name="name">laundry.payment.wizard.form</field>
<field name="model">laundry.payment.wizard</field>
<field name="arch" type="xml">
<form string="Register Payment / تسجيل الدفع">
<sheet>
<!-- ── Order / Customer Header ── -->
<div class="row mb-3 p-3"
style="background:linear-gradient(135deg,#1a73e8,#0d5cb6);
border-radius:8px;color:white;">
<div class="col-6">
<div style="font-size:0.8em;opacity:0.8;">Order / الطلب</div>
<div class="fw-bold fs-5">
<field name="order_name" readonly="1" class="text-white"/>
</div>
<div style="font-size:0.85em;opacity:0.9;" class="mt-1">
<field name="partner_id" readonly="1" class="text-white"/>
</div>
<div style="font-size:0.8em;opacity:0.8;" class="mt-1">
<field name="mobile" readonly="1" class="text-white"/>
</div>
</div>
<div class="col-6 text-end">
<div style="font-size:0.8em;opacity:0.8;">Processing Status</div>
<div class="fw-bold">
<field name="laundry_state" readonly="1" class="text-white"/>
</div>
<div style="font-size:0.8em;opacity:0.8;" class="mt-2">Order Total</div>
<div class="fw-bold fs-4">
<field name="order_total" readonly="1"
widget="monetary"
options="{'currency_field': 'currency_id'}"
class="text-white"/>
</div>
</div>
</div>
<!-- ── Balance Due Banner ── -->
<div class="alert mb-3"
t-attf-class="alert #{amount_due > 0 ? 'alert-danger' : 'alert-success'}"
role="alert">
<div class="d-flex justify-content-between align-items-center">
<span class="fw-bold">
<span invisible="amount_due == 0">
💰 Balance Due / المبلغ المتبقي:
</span>
<span invisible="amount_due != 0">
✅ Order is fully paid!
</span>
</span>
<span class="fs-4 fw-bold">
<field name="amount_due" readonly="1"
widget="monetary"
options="{'currency_field': 'currency_id'}"/>
</span>
</div>
</div>
<!-- ── Payment Entry ── -->
<group string="Payment Details / تفاصيل الدفع">
<group>
<field name="journal_id"
string="Journal / الدفتر"
options="{'no_create': True, 'no_create_edit': True}"
required="1"/>
<field name="payment_method_line_id"
string="Payment Method / طريقة الدفع"
options="{'no_create': True, 'no_create_edit': True}"
required="1"/>
<field name="amount"
string="Amount Received / المبلغ المستلم"
required="1"
widget="monetary"
options="{'currency_field': 'currency_id'}"/>
</group>
<group>
<field name="payment_date"
string="Payment Date / تاريخ الدفع"
required="1"/>
<field name="memo"
string="Reference / المرجع"
placeholder="e.g. Cash payment for order…"/>
</group>
</group>
<!-- ── Change Display (cash overpayment) ── -->
<div class="alert alert-warning mt-2"
invisible="not is_overpayment"
role="alert">
<div class="d-flex justify-content-between align-items-center">
<span class="fw-bold">💵 Change to Return / الباقي:</span>
<span class="fs-5 fw-bold">
<field name="change_amount" readonly="1"
widget="monetary"
options="{'currency_field': 'currency_id'}"/>
</span>
</div>
<div class="small text-muted mt-1">
Payment recorded for balance due. Return change to customer.
</div>
</div>
<!-- Hidden fields -->
<field name="currency_id" invisible="1"/>
<field name="company_id" invisible="1"/>
<field name="is_overpayment" invisible="1"/>
</sheet>
<footer>
<button name="action_confirm_payment"
string="✓ Confirm Payment / تأكيد الدفع"
type="object"
class="btn-primary btn-lg"/>
<button name="action_view_invoice"
string="View Invoice"
type="object"
class="btn-secondary"
groups="laundry_management.group_laundry_manager"/>
<button string="Cancel"
class="btn-secondary"
special="cancel"/>
</footer>
</form>
</field>
</record>
<!-- Action to open the payment wizard (also used by button on order) -->
<record id="action_laundry_payment_wizard" model="ir.actions.act_window">
<field name="name">Register Payment</field>
<field name="res_model">laundry.payment.wizard</field>
<field name="view_mode">form</field>
<field name="target">new</field>
<field name="context">{'default_order_id': active_id}</field>
</record>
</odoo>

View File

@@ -1,46 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- ═══════════════════════════════════════════════════════════════
PRINT WIZARD — Form View
═══════════════════════════════════════════════════════════════ -->
<record id="view_laundry_print_wizard_form" model="ir.ui.view">
<field name="name">laundry.print.wizard.form</field>
<field name="model">laundry.print.wizard</field>
<field name="arch" type="xml">
<form string="Print / طباعة">
<sheet>
<div class="alert alert-info mb-3" role="alert">
<strong>
<field name="order_name" readonly="1" nolabel="1"/>
</strong>
&#160;&#160;
<field name="customer_name" readonly="1" nolabel="1"/>
&#160;|&#160; Total:
<field name="total_amount" readonly="1" nolabel="1"/>
</div>
<group string="Print Template / قالب الطباعة">
<field name="template" widget="radio"/>
</group>
<group string="Options">
<field name="include_prices"/>
<field name="include_notes"/>
</group>
</sheet>
<footer>
<button name="action_print"
string="Print"
type="object"
class="btn-primary"
icon="fa-print"/>
<button string="Cancel"
class="btn-secondary"
special="cancel"/>
</footer>
</form>
</field>
</record>
</odoo>

View File

@@ -1,197 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- ════════════════════════════════════════════════════════════════
Laundry Reporting — extends standard Odoo dashboards.
────────────────────────────────────────────────────────────────
Strategy:
• Pure standard view types (graph + pivot) — no OWL, no
client action, no SCSS. Identical to the way
sale.order/account.move ship their analysis views.
• New menu items hang off the EXISTING reporting menus that
core already exposes:
sale.menu_sale_report (Sales → Reporting)
account.menu_finance_reports (Accounting → Reporting)
Plus a convenience entry under the Laundry app root.
• No view inheritance against core dashboards — adding our
own graph/pivot under their reporting menus is the safe,
standard pattern; overriding `account.dashboard.kanban`
would be invasive and version-fragile.
════════════════════════════════════════════════════════════════ -->
<!-- ── laundry.order — Operations & Financial graph/pivot ──────── -->
<record id="view_laundry_order_graph" model="ir.ui.view">
<field name="name">laundry.order.graph</field>
<field name="model">laundry.order</field>
<field name="arch" type="xml">
<graph string="Laundry Orders Analysis" type="bar"
stacked="0" sample="1">
<field name="create_date" interval="day" type="row"/>
<field name="state" type="col"/>
<field name="amount_total" type="measure"/>
<field name="amount_due" type="measure"/>
</graph>
</field>
</record>
<record id="view_laundry_order_pivot" model="ir.ui.view">
<field name="name">laundry.order.pivot</field>
<field name="model">laundry.order</field>
<field name="arch" type="xml">
<pivot string="Laundry Orders Analysis" sample="1">
<field name="create_date" interval="month" type="row"/>
<field name="state" type="col"/>
<field name="amount_total" type="measure"/>
<field name="amount_paid_cash" type="measure"/>
<field name="amount_due" type="measure"/>
<field name="item_count" type="measure"/>
</pivot>
</field>
</record>
<!-- Action: Sales-side analysis (orders + revenue + AOV via measures) -->
<record id="action_laundry_orders_reporting" model="ir.actions.act_window">
<field name="name">Laundry Orders</field>
<field name="res_model">laundry.order</field>
<field name="view_mode">graph,pivot,list,form</field>
<field name="view_ids" eval="[
(5, 0, 0),
(0, 0, {'view_mode': 'graph', 'view_id': ref('view_laundry_order_graph')}),
(0, 0, {'view_mode': 'pivot', 'view_id': ref('view_laundry_order_pivot')}),
(0, 0, {'view_mode': 'list', 'view_id': ref('view_laundry_order_list')}),
(0, 0, {'view_mode': 'form', 'view_id': ref('view_laundry_order_form')}),
]"/>
<field name="search_view_id" ref="view_laundry_order_search"/>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
No laundry orders for this period.
</p>
<p>
Switch to the Pivot view for a customer / month / state
breakdown, or use the date filters to narrow the range.
</p>
</field>
</record>
<!-- ── laundry.order — Operations-only KPI (Processing/Ready/Delayed) -->
<!-- Same model, different default filter — surfaces the operational
in-flight pipeline so the cashier / operator can spot bottlenecks. -->
<record id="action_laundry_operations_kpi" model="ir.actions.act_window">
<field name="name">Laundry Operations (Live)</field>
<field name="res_model">laundry.order</field>
<field name="view_mode">graph,pivot,list</field>
<field name="domain">[('state', 'in', ['intake', 'processing', 'ready'])]</field>
<field name="context">{'search_default_groupby_state': 1}</field>
<field name="view_ids" eval="[
(5, 0, 0),
(0, 0, {'view_mode': 'graph', 'view_id': ref('view_laundry_order_graph')}),
(0, 0, {'view_mode': 'pivot', 'view_id': ref('view_laundry_order_pivot')}),
(0, 0, {'view_mode': 'list', 'view_id': ref('view_laundry_order_list')}),
]"/>
<field name="search_view_id" ref="view_laundry_order_search"/>
</record>
<!-- ── account.move — Laundry-flagged invoices analysis ────────── -->
<!-- Uses the existing `is_laundry_invoice` computed flag on
account.move (from models/account_move.py). No new accounting
logic — pure read filter. -->
<record id="view_laundry_invoice_graph" model="ir.ui.view">
<field name="name">account.move.graph.laundry</field>
<field name="model">account.move</field>
<field name="arch" type="xml">
<graph string="Laundry Invoices" type="bar" stacked="0" sample="1">
<field name="invoice_date" interval="month" type="row"/>
<field name="state" type="col"/>
<field name="amount_total_signed" type="measure"/>
<field name="amount_residual_signed" type="measure"/>
</graph>
</field>
</record>
<record id="view_laundry_invoice_pivot" model="ir.ui.view">
<field name="name">account.move.pivot.laundry</field>
<field name="model">account.move</field>
<field name="arch" type="xml">
<pivot string="Laundry Invoices" sample="1">
<field name="partner_id" type="row"/>
<field name="invoice_date" interval="month" type="col"/>
<field name="amount_total_signed" type="measure"/>
<field name="amount_residual_signed" type="measure"/>
</pivot>
</field>
</record>
<record id="action_laundry_invoices_reporting" model="ir.actions.act_window">
<field name="name">Laundry Invoices</field>
<field name="res_model">account.move</field>
<field name="view_mode">graph,pivot,list,form</field>
<field name="domain">[
('is_laundry_invoice', '=', True),
('move_type', 'in', ('out_invoice', 'out_refund')),
]</field>
<field name="view_ids" eval="[
(5, 0, 0),
(0, 0, {'view_mode': 'graph', 'view_id': ref('view_laundry_invoice_graph')}),
(0, 0, {'view_mode': 'pivot', 'view_id': ref('view_laundry_invoice_pivot')}),
]"/>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
No laundry invoices for this period.
</p>
<p>
Switch to the Pivot view for a customer / month
breakdown of revenue, settled, and outstanding.
</p>
</field>
</record>
<!-- ════════════════════════════════════════════════════════════════
MENU INTEGRATION — extend existing core reporting menus.
No menu override; pure additive child entries.
════════════════════════════════════════════════════════════════ -->
<!-- Sales → Reporting → Laundry Orders -->
<menuitem id="menu_sale_report_laundry_orders"
name="Laundry Orders"
parent="sale.menu_sale_report"
action="action_laundry_orders_reporting"
sequence="100"
groups="laundry_management.group_laundry_operator"/>
<!-- Accounting → Reporting → Laundry Invoices -->
<menuitem id="menu_finance_report_laundry_invoices"
name="Laundry Invoices"
parent="account.menu_finance_reports"
action="action_laundry_invoices_reporting"
sequence="100"
groups="laundry_management.group_laundry_manager"/>
<!-- Convenience entry under the Laundry app: same actions surfaced
here so operators don't have to hop into Sales/Accounting. -->
<menuitem id="menu_laundry_reports_root"
name="Reports"
parent="menu_laundry_root"
sequence="80"
groups="laundry_management.group_laundry_operator"/>
<menuitem id="menu_laundry_reports_orders"
name="Orders Analysis"
parent="menu_laundry_reports_root"
action="action_laundry_orders_reporting"
sequence="10"
groups="laundry_management.group_laundry_operator"/>
<menuitem id="menu_laundry_reports_operations"
name="Operations (Live)"
parent="menu_laundry_reports_root"
action="action_laundry_operations_kpi"
sequence="20"
groups="laundry_management.group_laundry_operator"/>
<menuitem id="menu_laundry_reports_invoices"
name="Invoices Analysis"
parent="menu_laundry_reports_root"
action="action_laundry_invoices_reporting"
sequence="30"
groups="laundry_management.group_laundry_manager"/>
</odoo>

View File

@@ -1,309 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- ═══════════════════════════════════════════════════════════════
SESSION — Form View
═══════════════════════════════════════════════════════════════ -->
<record id="view_laundry_session_form" model="ir.ui.view">
<field name="name">laundry.session.form</field>
<field name="model">laundry.session</field>
<field name="arch" type="xml">
<form string="Laundry Session">
<header>
<button name="action_open_session"
string="Open Session"
type="object"
class="btn-primary"
invisible="state != 'new'"/>
<button name="action_close_session"
string="Close Session"
type="object"
class="btn-warning"
invisible="state != 'opened'"/>
<button name="action_print_session_report"
string="Print Report"
type="object"
icon="fa-print"
invisible="state == 'new'"/>
<field name="state" widget="statusbar"
statusbar_visible="new,opened,closed"/>
</header>
<sheet>
<!-- Smart buttons -->
<div class="oe_button_box" name="button_box">
<button name="action_view_orders"
type="object"
class="oe_stat_button"
icon="fa-list-alt"
invisible="order_count == 0">
<field name="order_count"
widget="statinfo"
string="Orders"/>
</button>
<button class="oe_stat_button"
type="object"
name="action_view_orders"
icon="fa-money">
<field name="total_sales"
widget="statinfo"
string="Sales"/>
</button>
</div>
<div class="oe_title">
<h1><field name="name" readonly="1"/></h1>
</div>
<group>
<group string="Session Info">
<field name="user_id"
options="{'no_create_edit': True}"/>
<field name="opening_datetime" readonly="1"/>
<field name="closing_datetime" readonly="1"
invisible="state != 'closed'"/>
<field name="company_id"
groups="base.group_multi_company"/>
</group>
<group string="Opening Cash">
<field name="opening_cash"
readonly="state != 'new'"/>
<field name="difference_account_id"
options="{'no_create': True, 'no_create_edit': True}"
readonly="state == 'closed'"/>
</group>
</group>
<notebook>
<!-- FINANCIAL SUMMARY TAB -->
<page string="Summary" name="summary">
<group>
<group string="Sales Breakdown">
<field name="total_sales" readonly="1"/>
<field name="total_paid" readonly="1"/>
<field name="outstanding_amount" readonly="1"/>
</group>
<group string="Payment Methods">
<field name="total_cash" readonly="1"/>
<field name="total_bank" readonly="1"/>
<field name="total_credit" readonly="1"/>
</group>
</group>
<group string="Cash Control"
invisible="state == 'new'">
<group>
<field name="opening_cash" readonly="1"
string="Opening Float"/>
<field name="total_cash" readonly="1"
string="+ Cash Received"/>
<field name="expected_closing_cash" readonly="1"/>
</group>
<group invisible="state != 'closed'">
<field name="actual_closing_cash" readonly="1"/>
<field name="cash_difference" readonly="1"
widget="monetary"
decoration-danger="cash_difference &lt; 0"
decoration-success="cash_difference >= 0"/>
</group>
</group>
<group string="Notes" invisible="not notes">
<field name="notes" nolabel="1" readonly="1"/>
</group>
</page>
<!-- ORDERS TAB — uses sale.order fields -->
<page string="Orders" name="orders">
<field name="order_ids" readonly="1">
<list>
<field name="name" string="Order No."/>
<field name="partner_id" string="Customer"/>
<field name="mobile" optional="show"/>
<field name="date_order" string="Date"/>
<field name="amount_total" string="Total" sum="Total"/>
<field name="amount_due" string="Balance Due" optional="show"/>
<field name="laundry_payment_status"
string="Payment"
widget="badge"
decoration-success="laundry_payment_status == 'paid'"
decoration-warning="laundry_payment_status == 'partial'"
decoration-danger="laundry_payment_status == 'unpaid'"/>
<field name="laundry_state"
string="Processing"
widget="badge"
decoration-success="laundry_state == 'delivered'"
decoration-warning="laundry_state == 'processing'"
decoration-primary="laundry_state == 'ready'"
decoration-info="laundry_state == 'intake'"/>
<field name="state"
widget="badge"
decoration-info="state == 'draft'"
decoration-success="state == 'sale'"
decoration-danger="state == 'cancel'"
optional="show"/>
</list>
</field>
</page>
</notebook>
</sheet>
<chatter/>
</form>
</field>
</record>
<!-- ═══════════════════════════════════════════════════════════════
SESSION — List View
═══════════════════════════════════════════════════════════════ -->
<record id="view_laundry_session_list" model="ir.ui.view">
<field name="name">laundry.session.list</field>
<field name="model">laundry.session</field>
<field name="arch" type="xml">
<list string="Laundry Sessions">
<field name="name"/>
<field name="user_id"/>
<field name="opening_datetime"/>
<field name="closing_datetime" optional="show"/>
<field name="order_count"/>
<field name="total_sales" sum="Total Sales"/>
<field name="total_cash" sum="Cash" optional="show"/>
<field name="total_bank" sum="Bank/Card" optional="show"/>
<field name="total_credit" sum="Credit" optional="show"/>
<field name="state"
widget="badge"
decoration-success="state == 'closed'"
decoration-warning="state == 'opened'"
decoration-info="state == 'new'"/>
</list>
</field>
</record>
<!-- ═══════════════════════════════════════════════════════════════
SESSION — Search View
═══════════════════════════════════════════════════════════════ -->
<record id="view_laundry_session_search" model="ir.ui.view">
<field name="name">laundry.session.search</field>
<field name="model">laundry.session</field>
<field name="arch" type="xml">
<search string="Search Sessions">
<field name="name" string="Session"/>
<field name="user_id" string="Staff"/>
<filter string="Open" name="opened"
domain="[('state','=','opened')]"/>
<filter string="Closed" name="closed"
domain="[('state','=','closed')]"/>
<filter string="Today" name="today"
domain="[('opening_datetime','&gt;=',context_today().strftime('%Y-%m-%d'))]"/>
<separator/>
<filter string="By Staff" name="by_user"
context="{'group_by':'user_id'}"/>
<filter string="By Month" name="by_month"
context="{'group_by':'opening_datetime:month'}"/>
</search>
</field>
</record>
<!-- ═══════════════════════════════════════════════════════════════
SESSION CLOSE WIZARD — View
═══════════════════════════════════════════════════════════════ -->
<record id="view_laundry_session_close_wizard_form" model="ir.ui.view">
<field name="name">laundry.session.close.wizard.form</field>
<field name="model">laundry.session.close.wizard</field>
<field name="arch" type="xml">
<form string="Close Session">
<sheet>
<div class="alert alert-warning mb-3" role="alert">
<strong>Closing Session: </strong>
<field name="session_id" readonly="1" nolabel="1"/>
<br/>
Count the cash in the drawer and enter the total below.
</div>
<group string="Session Summary">
<group>
<field name="order_count" string="Orders Processed" readonly="1"/>
<field name="total_sales" readonly="1"/>
<field name="total_paid" readonly="1"/>
<field name="outstanding_amount" readonly="1"/>
</group>
<group>
<field name="total_cash" readonly="1"/>
<field name="total_bank" readonly="1"/>
<field name="total_credit" readonly="1"/>
</group>
</group>
<group string="Cash Control">
<group>
<field name="opening_cash" readonly="1" string="Opening Float"/>
<field name="total_cash" readonly="1" string="+ Cash Received"/>
<field name="expected_closing_cash" readonly="1"/>
</group>
<group>
<field name="actual_closing_cash"/>
<field name="cash_difference" readonly="1"
decoration-danger="cash_difference &lt; 0"
decoration-success="cash_difference &gt;= 0"/>
</group>
</group>
<group string="Cash Difference Entry"
invisible="cash_difference == 0">
<group>
<field name="post_difference_entry"/>
</group>
<group invisible="not post_difference_entry">
<field name="difference_account_id"
options="{'no_create': True}"/>
</group>
</group>
<group string="Notes">
<field name="closing_notes" nolabel="1"
placeholder="Any remarks about this session…"/>
</group>
</sheet>
<footer>
<button name="action_close"
string="Close Session"
type="object"
class="btn-primary"/>
<button string="Cancel"
class="btn-secondary"
special="cancel"/>
</footer>
</form>
</field>
</record>
<!-- ═══════════════════════════════════════════════════════════════
ACTIONS
═══════════════════════════════════════════════════════════════ -->
<record id="action_laundry_session" model="ir.actions.act_window">
<field name="name">Sessions</field>
<field name="res_model">laundry.session</field>
<field name="view_mode">list,form</field>
<field name="search_view_id" ref="view_laundry_session_search"/>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
No sessions yet — open your first daily session!
</p>
<p>
Sessions track daily orders and cash flow,
similar to a Point of Sale day shift.
</p>
</field>
</record>
<record id="action_laundry_session_open" model="ir.actions.act_window">
<field name="name">Current Session</field>
<field name="res_model">laundry.session</field>
<field name="view_mode">list,form</field>
<field name="domain">[('state','=','opened')]</field>
<field name="search_view_id" ref="view_laundry_session_search"/>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
No session is currently open.
</p>
<p>Go to All Sessions to open a new session for today.</p>
</field>
</record>
</odoo>

View File

@@ -1,150 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- ═══════════════════════════════════════════════════════════════
LAUNDRY SETTINGS — Odoo 19 style settings panel (PART 9)
═══════════════════════════════════════════════════════════════ -->
<record id="view_laundry_res_config_settings" model="ir.ui.view">
<field name="name">res.config.settings.laundry</field>
<field name="model">res.config.settings</field>
<field name="inherit_id" ref="base.res_config_settings_view_form"/>
<field name="arch" type="xml">
<xpath expr="//form" position="inside">
<app data-string="Laundry"
string="Laundry"
name="laundry_management"
logo="/laundry_management/static/description/icon.png"
groups="laundry_management.group_laundry_manager">
<!-- ── WhatsApp Business ──────────────────────── -->
<block title="WhatsApp Business / واتساب الأعمال"
name="laundry_whatsapp">
<setting string="Cloud API Token"
help="Meta/Facebook WhatsApp Business Cloud API Bearer token. Leave blank to use the wa.me web link (one-click) mode.">
<field name="laundry_wa_token"
password="True"
placeholder="EAAxxxxxxxx…"/>
</setting>
<setting string="Phone Number ID"
help="WhatsApp Business Phone Number ID from Meta Developer Console.">
<field name="laundry_wa_phone_id"
placeholder="12345678901234"/>
</setting>
<setting string="Store WhatsApp Number"
help="Store number in E.164 format without + (e.g. 966501234567). Used as fallback.">
<field name="laundry_wa_store_number"
placeholder="966501234567"/>
</setting>
<setting string="Auto-send on Ready"
help="Automatically open WhatsApp Web when an order is marked Ready for Pickup.">
<field name="laundry_wa_auto_send"/>
</setting>
</block>
<!-- ── Staff Commissions ──────────────────────── -->
<block title="Staff Commissions / عمولات الموظفين"
name="laundry_commission">
<setting string="Enable Commission Tracking"
help="Automatically create commission records for reception, processing, and delivery staff as orders progress.">
<field name="laundry_commission_enabled"/>
<div class="content-group"
invisible="not laundry_commission_enabled">
<div class="row mt8">
<label for="laundry_commission_type"
class="col-lg-4 col-form-label o_light_label"
string="Commission Type"/>
<div class="col-lg-8">
<field name="laundry_commission_type"/>
</div>
</div>
<div class="row mt4">
<label for="laundry_commission_reception_rate"
class="col-lg-4 col-form-label o_light_label"
string="Reception Rate"/>
<div class="col-lg-8">
<field name="laundry_commission_reception_rate"/>
</div>
</div>
<div class="row mt4">
<label for="laundry_commission_processing_rate"
class="col-lg-4 col-form-label o_light_label"
string="Processing Rate"/>
<div class="col-lg-8">
<field name="laundry_commission_processing_rate"/>
</div>
</div>
<div class="row mt4">
<label for="laundry_commission_delivery_rate"
class="col-lg-4 col-form-label o_light_label"
string="Delivery Rate"/>
<div class="col-lg-8">
<field name="laundry_commission_delivery_rate"/>
</div>
</div>
</div>
</setting>
<setting string="Commission Expense Account"
help="Expense account for booking commission costs (for future accounting integration).">
<field name="laundry_commission_account_id"
options="{'no_create': True}"/>
</setting>
</block>
<!-- ── Session &amp; Cash Control ─────────────── -->
<block title="Session &amp; Cash Control / الجلسات وإدارة الصندوق"
name="laundry_session">
<setting string="Require Open Session"
help="Prevent confirming orders when no daily session is open.">
<field name="laundry_require_session"/>
</setting>
<setting string="Default Cash Journal"
help="Default journal used in the payment wizard for cash transactions.">
<field name="laundry_cash_journal_id"
options="{'no_create': True}"/>
</setting>
<setting string="Cash Difference Account"
help="Default account for posting session cash count variances.">
<field name="laundry_difference_account_id"
options="{'no_create': True}"/>
</setting>
</block>
<!-- ── Operations ────────────────────────────── -->
<block title="Operations / العمليات" name="laundry_operations">
<setting string="Default Print Format"
help="Default paper format for order receipts.">
<field name="laundry_default_paper"/>
</setting>
</block>
</app>
</xpath>
</field>
</record>
<!-- ═══════════════════════════════════════════════════════════════
ACTION
═══════════════════════════════════════════════════════════════ -->
<record id="action_laundry_settings" model="ir.actions.act_window">
<field name="name">Laundry Settings</field>
<field name="res_model">res.config.settings</field>
<field name="view_mode">form</field>
<field name="target">current</field>
<field name="context">{'module': 'laundry_management'}</field>
</record>
</odoo>

View File

@@ -1,35 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_pos_config_laundry_form" model="ir.ui.view">
<field name="name">pos.config.form.laundry</field>
<field name="model">pos.config</field>
<field name="inherit_id" ref="point_of_sale.pos_config_view_form"/>
<field name="arch" type="xml">
<xpath expr="//sheet" position="inside">
<group string="Laundry POS Settings" name="laundry_pos_settings">
<group string="Order Type Flow" name="laundry_order_type_flow">
<field name="enable_laundry_order_type"/>
<field name="require_laundry_order_type"
invisible="not enable_laundry_order_type"/>
<field name="ask_laundry_order_type_on_first_line"
invisible="not enable_laundry_order_type"/>
<field name="allow_change_laundry_order_type_before_payment"
invisible="not enable_laundry_order_type"/>
<field name="default_laundry_order_type_id"
invisible="not enable_laundry_order_type"/>
<field name="enable_laundry_attributes"
invisible="not enable_laundry_order_type"/>
<field name="require_delivery_details_if_needed"
invisible="not enable_laundry_order_type"/>
<field name="require_delivery_time"
invisible="not enable_laundry_order_type or not require_delivery_details_if_needed"/>
<field name="show_order_type_icons"
invisible="not enable_laundry_order_type"/>
</group>
</group>
</xpath>
</field>
</record>
</odoo>

View File

@@ -1,36 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Add laundry context fields to pos.order form (Extra Info tab). -->
<record id="view_pos_order_form_laundry_context" model="ir.ui.view">
<field name="name">pos.order.form.laundry.context</field>
<field name="model">pos.order</field>
<field name="inherit_id" ref="point_of_sale.view_pos_pos_form"/>
<field name="arch" type="xml">
<xpath expr="//page[@name='extra']" position="inside">
<group string="Laundry — Type &amp; Attributes"
name="laundry_context">
<group>
<field name="laundry_order_type_id"
options="{'no_create': True, 'no_open': True}"/>
<field name="laundry_is_delivery"/>
</group>
<group>
<field name="laundry_delivery_address"
invisible="not laundry_is_delivery"/>
<field name="laundry_delivery_scheduled_at"
invisible="not laundry_is_delivery"/>
</group>
<group colspan="2">
<field name="laundry_order_attribute_ids"
widget="many2many_tags"
options="{'no_create': True, 'color_field': 'color'}"/>
</group>
</group>
</xpath>
</field>
</record>
</odoo>

View File

@@ -1,15 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_product_template_form_laundry" model="ir.ui.view">
<field name="name">product.template.form.laundry</field>
<field name="model">product.template</field>
<field name="inherit_id" ref="product.product_template_form_view"/>
<field name="arch" type="xml">
<xpath expr="//page[@name='general_information']//field[@name='type']" position="after">
<field name="is_laundry_service"/>
</xpath>
</field>
</record>
</odoo>

View File

@@ -1,32 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<!-- Views of report of laundry orders -->
<record id="report_laundry_order_view_pivot" model="ir.ui.view">
<field name="name">report.laundry.order.pivot</field>
<field name="model">report.laundry.order</field>
<field name="arch" type="xml">
<pivot string="Laundry Order Analysis" display_quantity="true"
disable_linking="True">
<field name="name" type="row"/>
</pivot>
</field>
</record>
<!-- Defined Action-->
<record id="report_laundry_order_action"
model="ir.actions.act_window">
<field name="name">Laundry Order Analysis</field>
<field name="res_model">report.laundry.order</field>
<field name="view_mode">pivot</field>
<field name="context">{'group_by_no_leaf':1,'group_by':[]}</field>
<field name="help">This report allows you to analyse the performance
of your Laundry Management.
</field>
</record>
<!-- Defined Menus-->
<menuitem id="laundry_report_menu" name="Report"
parent="laundry_management_menu" sequence="2"/>
<menuitem name="Laundry Order Analysis"
action="report_laundry_order_action"
id="menu_laundry_order_analysis" parent="laundry_report_menu"
sequence="1"/>
</odoo>

View File

@@ -1,54 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<!-- Washing type model form view -->
<record id="washing_type_form_view" model="ir.ui.view">
<field name="name">washing.type.view.form</field>
<field name="model">washing.type</field>
<field name="arch" type="xml">
<form>
<sheet>
<group>
<group>
<field name="name"/>
<field name="assigned_person_id"/>
</group>
<group>
<field name="amount"/>
</group>
</group>
</sheet>
</form>
</field>
</record>
<!-- Washing type model list view -->
<record id="washing_type_view_list" model="ir.ui.view">
<field name="name">washing.type.view.list</field>
<field name="model">washing.type</field>
<field name="arch" type="xml">
<list>
<field name="name"/>
<field name="assigned_person_id"/>
<field name="amount"/>
</list>
</field>
</record>
<!-- Action for washing type-->
<record id="washing_type_action" model="ir.actions.act_window">
<field name="name">Washing Type</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">washing.type</field>
<field name="view_mode">list,form</field>
</record>
<!-- Defined Menus-->
<menuitem id="laundry_config_menu" name="Configuration"
parent="laundry_management_menu" sequence="3"/>
<menuitem id="laundry_customer" name="Customers"
parent="laundry_config_menu"
action="base.action_partner_customer_form" sequence="1"/>
<menuitem id="laundry_products" name="Products"
parent="laundry_config_menu"
action="product.product_normal_action_sell" sequence="2"/>
<menuitem id="laundry_washing_type" name="Washing Type"
parent="laundry_config_menu" action="washing_type_action"
sequence="3"/>
</odoo>

View File

@@ -1,85 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<!-- Washing model form view -->
<record id="washing_washing_view_form" model="ir.ui.view">
<field name="name">washing.washing.view.form</field>
<field name="model">washing.washing</field>
<field name="arch" type="xml">
<form string="Washing" create="false">
<header>
<button name="start_wash" class="oe_highlight"
string="Start" type="object"
invisible="state not in 'draft'"/>
<button name="action_set_to_done" class="oe_highlight"
string="Set to Done" type="object"
invisible="state not in 'process'"/>
<field name="state" widget="statusbar"
statusbar_visible="draft,process,done"/>
</header>
<sheet>
<group>
<group>
<field name="name" readonly="1"/>
<field name="user_id"/>
</group>
<group>
<field name="washing_date" readonly="1"/>
</group>
</group>
<notebook>
<page string="Description">
<field name="description"/>
</page>
<page string="Additional Products">
<field name="product_line_ids"
readonly="state != 'draft'">
<list editable="bottom">
<field name="product_id"/>
<field name="name"/>
<field name="quantity"/>
<field name="uom_id"/>
<field name="price_unit"/>
<field name="subtotal"/>
<field name="wash_id" invisible="1"/>
</list>
</field>
<group class="oe_subtotal_footer oe_right"
colspan="2" name="product_total">
<field name="total_amount"
widget='monetary'/>
</group>
<div class="oe_clear"/>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
<!-- Washing model list view -->
<record id="washing_washing_view_list" model="ir.ui.view">
<field name="name">washing.washing.view.list</field>
<field name="model">washing.washing</field>
<field name="arch" type="xml">
<list create="false" edit="false"
decoration-info="state == 'draft'">
<field name="name"/>
<field name="user_id"/>
<field name="washing_date"/>
<field name="state"/>
</list>
</field>
</record>
<!-- Action for washing-->
<record id="washing_washing_action" model="ir.actions.act_window">
<field name="name">Washing</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">washing.washing</field>
<field name="view_mode">list,form</field>
<field name="domain">[('user_id','=', uid)]</field>
</record>
<!-- Defined Menu-->
<menuitem id="washing_washing_menu" name="Laundry Works"
parent="laundry_management_sub_menu"
action="washing_washing_action"
sequence="2"/>
</odoo>

View File

@@ -1,46 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<!-- Washing work model form view-->
<record id="washing_work_view_form" model="ir.ui.view">
<field name="name">washing.work.view.form</field>
<field name="model">washing.work</field>
<field name="arch" type="xml">
<form>
<sheet>
<group>
<group>
<field name="name"/>
<field name="assigned_person_id"/>
</group>
<group>
<field name="amount"/>
</group>
</group>
</sheet>
</form>
</field>
</record>
<!-- Washing work model list view-->
<record id="washing_work_view_list" model="ir.ui.view">
<field name="name">washing.work.view.list</field>
<field name="model">washing.work</field>
<field name="arch" type="xml">
<list>
<field name="name"/>
<field name="assigned_person_id"/>
<field name="amount"/>
</list>
</field>
</record>
<!-- Action for washing work-->
<record id="washing_work_action" model="ir.actions.act_window">
<field name="name">Additional Works</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">washing.work</field>
<field name="view_mode">list,form</field>
</record>
<!-- Menu for Additional work-->
<menuitem id="washing_work_menu" name="Additional Works"
parent="laundry_config_menu" action="washing_work_action"
sequence="4"/>
</odoo>