diff --git a/pharma_control_center/README.md b/pharma_control_center/README.md new file mode 100644 index 00000000000..06d8ae02a5e --- /dev/null +++ b/pharma_control_center/README.md @@ -0,0 +1,526 @@ +# πŸ’Š Pharma Control Center (Odoo 18+ Module) + +## Overview +**Pharma Control Center** is a comprehensive Odoo 18+ module for modern pharmacy management. It provides complete functionality for medicine cataloguing, batch/expiry tracking, stock management, patient records management, doctor-patient assignments, invoicing, and real-time clinical dashboards. The module follows Odoo 18 best practices (`` instead of deprecated ``, emoji-rich UI, computed fields, and modern QWeb templates) and seamlessly integrates with Odoo's accounting, sales, and product modules for complete pharmacy operations. + +--- + +## ✨ Features + +### πŸ₯ Complete Medicine Management +- **Full Medicine Catalogue** – name, batch number, manufacturer, barcode, category, sub-category +- **Pricing Management** – selling price, cost price, automatic profit margin calculation +- **Stock Control** – tracked quantity, reorder level, "Need Reorder" alerts +- **Expiry Tracking** – automatic days-to-expiry calculation and status (Fresh / Expiring Soon / Expired) +- **Storage Conditions** – room temperature, cold storage, frozen storage +- **License Categorization** – medicines marked with Pharmacy Category (Green / Blue / White) for role-based access control +- **Dosage & Safety** – dosage instructions and side effects documentation + +### πŸ‘₯ Advanced Patient Management +- **Dedicated Patient Model** – comprehensive patient records with personal info, medical history, allergies, blood group +- **Doctor Assignment** – each patient assigned to a specific doctor (user in Doctor group) +- **Role-Based Visibility** – doctors see only their own patients; managers see all patients; patients cannot access patient list +- **Medical Records** – medical history, allergies, blood group, contact information +- **Doctor-Patient Relationship** – secure patient-to-doctor assignment with edit permissions + +### πŸ›’ Order Management & Invoicing (Full Integration) +- **"Order Now" Button** – available on each medicine (form and kanban views) +- **Automatic Sale Order Creation** – creates an Odoo Sale Order with the medicine as line item +- **Automatic Invoice Generation** – automatically creates and posts an invoice upon ordering +- **Order Menu** – dedicated "Orders" menu lists all sale orders created via the module (visible to managers and doctors, read-only for doctors) +- **Order Restrictions** – record rules restrict order editing to managers only; doctors/patients have read-only access +- **Order Status Tracking** – after ordering, the button changes to "βœ… Ordered" and the medicine cannot be re-ordered +- **Product Linking** – automatically creates linked products in Odoo's product module +- **Invoice Tracking** – tracks last sale order and invoice for each medicine + +### πŸ“Š Comprehensive Dashboard +- **Medicine Statistics** – total medicines, total stock quantity, stock value (quantity Γ— price), out-of-stock count, low stock count (1-9 units), expiring soon count (≀30 days), expired count +- **Patient Statistics** – total patients, my patients (for doctors), dynamic patient list +- **Dynamic Patient List** – displayed directly on the dashboard, filtered by user role + - Doctors see only their assigned patients (read-only) + - Managers see all patients (read-only) + - Patients see nothing +- **Real-Time Updates** – statistics update automatically based on current inventory +- **Visual Alerts** – color-coded badges for low stock, expiring soon, and expired medicines + +### πŸ” Role-Based Access Control (Complete) +Three user groups with distinct permissions: + +#### 🟒 **Pharmacy Manager Group** +- **Full Access** to all medicines (all license categories) +- **Full CRUD** on patients +- **Full CRUD** on categories and configuration +- **Can Place Orders** – "Order Now" button visible and functional +- **Can Edit Orders** – full read/write access to all sale orders +- **Can View Dashboard** – see all statistics and all patients +- **Menu Access** – Dashboard, Medicines, Patients, Orders, Categories (Configuration) + +#### πŸ”΅ **Doctor Group** +- **Read-Only Access** to Blue (Limited License) and White (OTC) medicines only +- **Read & Write Own Patients** – can manage patients they are assigned to +- **Cannot Create/Edit Medicines** – read-only access only +- **Cannot Place Orders** – "Order Now" button hidden (UI + security check) +- **Can View Orders** – read-only access to all orders (record rule restricted) +- **Can View Dashboard** – see statistics and their own patients only +- **Menu Access** – Dashboard, Medicines, Patients, Orders (read-only) + +#### βšͺ **Patient Group** +- **Read-Only Access** to White (Basic OTC) medicines only +- **No Patient List Access** – cannot see patient records or list +- **No Dashboard Access** – dashboard menu hidden +- **No Ordering Capability** – "Order Now" button hidden +- **Menu Access** – Medicines (white licenses only) + +### πŸ”’ Security Layer Details + +**Medicine Visibility (Record Rules):** +- Patients see ONLY `license_category = 'white'` medicines +- Doctors see `license_category in ['blue', 'white']` medicines +- Managers see ALL medicines (no restrictions) + +**Patient Visibility (Record Rules):** +- Patients cannot view any patient records (CSV: no read access) +- Doctors can read/write/create patients assigned to them: `[('doctor_id','=',user.id)]` +- Doctors cannot delete patients (unlink=False) +- Managers have full CRUD on all patients + +**Sale Order Visibility (Record Rules):** +- Global rule: all sale orders are read-only by default +- Manager override: managers have full read/write/create/delete on sale orders +- Doctors/Patients have read-only access via global rule + +**Category Visibility:** +- Patients cannot view categories (CSV: no read access) +- Doctors can view categories (read-only) +- Managers have full CRUD on categories + +### 🎨 Modern User Interface +- **List View** – replaces deprecated ``, multi-edit support for batch operations +- **Form View** – intuitive grouped layouts with emojis, placeholders, and badges +- **Kanban View** – card-based design with license/expiry badges, stock status, and "Order Now" button +- **Search View** – advanced filters for: + - Stock status (In Stock, Out of Stock, Need Reorder) + - Expiry status (Expired, Expiring Soon) + - License category filters + - Medicine name, batch, manufacturer search + - Group-by options (Category, Expiry Status, Storage, License) +- **Dashboard Form** – statistics display with visual widgets and alerts +- **Emoji Rich** – πŸ’Š πŸ₯ πŸ“Š πŸ’° πŸ”’ for enhanced visual recognition + +### πŸ“¦ Additional Models & Data Structures + +**pharmacy.category** – Hierarchical Medicine Categories +- Parent-child relationships for taxonomy (e.g., Antibiotics β†’ Penicillin) +- Translatable names and descriptions +- Manager-only access + +**pharmacy.patient** – Patient Records +- Linked to doctors via `doctor_id` (Many2one res.users) +- Medical information (history, allergies, blood group) +- Contact details (phone, email, address) +- Active/inactive status + +--- + +## 🧱 Module Structure +``` +odoo-tutorials/ +└── pharma_control_center/ + β”œβ”€β”€ __init__.py # Module initialization + β”œβ”€β”€ __manifest__.py # Module manifest & settings + β”œβ”€β”€ README.md # This file + β”‚ + β”œβ”€β”€ data/ + β”‚ β”œβ”€β”€ demo_medicines.xml # Demo medicine records (various categories & licenses) + β”‚ └── demo_patients.xml # Demo doctor & patient records + β”‚ + β”œβ”€β”€ models/ + β”‚ β”œβ”€β”€ __init__.py # Model imports + β”‚ β”œβ”€β”€ pharma_control_center.py # Dashboard model (statistics & patient list) + β”‚ β”œβ”€β”€ pharma_medicine.py # Medicine model (CRUD, ordering, invoicing) + β”‚ β”œβ”€β”€ pharmacy_category.py # Medicine category model (hierarchical) + β”‚ └── pharmacy_patient.py # Patient model (doctor assignment, records) + β”‚ + β”œβ”€β”€ security/ + β”‚ β”œβ”€β”€ groups.xml # User groups (Patient, Doctor, Manager) + β”‚ β”œβ”€β”€ ir.model.access.csv # Model-level access permissions + β”‚ β”œβ”€β”€ pharmacy_security.xml # Record rules for medicines (license-based) + β”‚ β”œβ”€β”€ pharmacy_patient_security.xml # Record rules for patients (doctor assignment) + β”‚ └── sale_order_security.xml # Record rules for sale orders (manager access) + β”‚ + β”œβ”€β”€ views/ + β”‚ β”œβ”€β”€ pharma_control_center_views.xml # Dashboard views & root menu + β”‚ β”œβ”€β”€ pharmacy_medicine_views.xml # Medicine list/form/kanban/search & menu + β”‚ β”œβ”€β”€ pharmacy_patient_views.xml # Patient list/form/search & menu + β”‚ β”œβ”€β”€ pharmacy_category_views.xml # Category views & menu + β”‚ └── pharmacy_order_views.xml # Sale order lists & Orders menu + β”‚ + └── test/ + └── test_pharma_control_center.py # Unit tests for core functionality +``` + +--- + +## πŸ”§ Technical Details + +### Model: `pharmacy.medicine` + +| Field | Type | Description | Required | Notes | +|-------|------|-------------|----------|-------| +| `name` | Char | Medicine name | βœ“ | Max 255 chars | +| `description` | Text | Short description | - | Optional | +| `manufacturer` | Char | Manufacturer name | - | Optional | +| `barcode` | Char | Product barcode (EAN/UPC) | - | Optional, for scanning | +| `category_id` | Many2one | Link to `pharmacy.category` | βœ“ | Prevents deletion of category if medicines exist | +| `sub_category` | Char | Optional sub-category | - | Text field (not entity-linked) | +| `license_category` | Selection | `green` / `blue` / `white` | βœ“ | Controls visibility & access | +| `batch_number` | Char | Batch/Lot number | βœ“ | Required for traceability | +| `expiry_date` | Date | Expiry/Expiration date | βœ“ | Used for expiry calculations | +| `price` | Float | Selling price per unit | βœ“ | Required for invoicing | +| `cost_price` | Float | Purchase/cost price | - | Optional, used for margin calculation | +| `profit_margin` | Float | Profit margin % | - | Computed: `((price - cost_price) / cost_price) * 100` | +| `quantity` | Integer | Stock quantity | βœ“ | Default: 0, tracked for inventory | +| `reorder_level` | Integer | Reorder threshold | - | Default: 10, triggers "Need Reorder" alert | +| `need_reorder` | Boolean | Computed low stock flag | - | Computed: `quantity <= reorder_level` | +| `in_stock` | Boolean | Computed stock status | - | Computed: `quantity > 0` | +| `days_to_expiry` | Integer | Days remaining until expiry | - | Computed: `(expiry_date - today).days` | +| `expiry_status` | Selection | Status badge | - | Computed: `fresh` / `expiring_soon` / `expired` | +| `storage_location` | Selection | Storage condition | βœ“ | `room_temp` / `cold` / `frozen` | +| `dosage` | Char | Usage instructions | - | Optional, e.g., "1 tablet twice daily" | +| `side_effects` | Text | Possible side effects | - | Optional, medical notes | +| `product_id` | Many2one | Linked `product.product` | - | Auto-created on first order, read-only | +| `is_ordered` | Boolean | Order status flag | - | True after "Order Now" clicked, prevents re-ordering | +| `last_sale_order_id` | Many2one | Last `sale.order` created | - | Read-only, for tracking | +| `last_invoice_id` | Many2one | Last `account.move` created | - | Read-only, for tracking | + +**Computed Fields (Auto-updated):** +- `profit_margin` – Depends on `price`, `cost_price` +- `need_reorder` – Depends on `quantity`, `reorder_level` +- `in_stock` – Depends on `quantity` +- `days_to_expiry` – Depends on `expiry_date` (daily refresh) +- `expiry_status` – Depends on `expiry_date` (daily refresh) + +### Model: `pharma.control.center` (Dashboard) + +| Field | Type | Description | Computed | Notes | +|-------|------|-------------|----------|-------| +| `name` | Char | Dashboard name | - | Default: "Pharmacy Dashboard", required | +| `description` | Text | Dashboard description | - | Optional | +| `last_updated` | Datetime | Last update timestamp | - | Auto-set on creation | +| `total_medicines` | Integer | Count of all medicines | βœ“ | `COUNT(pharmacy.medicine)` | +| `total_stock_quantity` | Integer | Sum of all quantities | βœ“ | `SUM(quantity)` across all medicines | +| `stock_value` | Float | Inventory value (β‚Ή) | βœ“ | `SUM(quantity Γ— price)` | +| `out_of_stock_count` | Integer | Medicines with qty = 0 | βœ“ | Count where `quantity == 0` | +| `low_stock_count` | Integer | Medicines with 1-9 units | βœ“ | Count where `0 < quantity < 10` | +| `expiring_soon_count` | Integer | Medicines expiring ≀30 days | βœ“ | Count where `today < expiry_date <= today + 30 days` | +| `expired_count` | Integer | Medicines past expiry | βœ“ | Count where `expiry_date < today` | +| `total_patients` | Integer | Total patient records | βœ“ | `COUNT(pharmacy.patient)` | +| `my_patients` | Integer | Doctor's assigned patients | βœ“ | Count for doctors only, `COUNT(pharmacy.patient where doctor_id=user)` | +| `patient_ids` | One2many | Patient records list | βœ“ | Role-filtered (doctors: own patients; managers: all; patients: none) | + +**Computed Fields (Live Updates):** +- All numeric fields recompute on each dashboard access +- `patient_ids` filters based on current user's role + +### Model: `pharmacy.category` + +| Field | Type | Description | Required | Notes | +|-------|------|-------------|----------|-------| +| `name` | Char | Category name | βœ“ | Translatable | +| `code` | Char | Short code | - | Optional, e.g., "ANTIBIOTIC" | +| `description` | Text | Category description | - | Optional | +| `parent_id` | Many2one | Parent category (self) | - | For hierarchical classification | +| `child_ids` | One2many | Child categories (self) | - | Auto-computed inverse | + +**Use Case:** Create taxonomy like: +- Antibiotics (Parent) + - Penicillins (Child) + - Cephalosporins (Child) + +### Model: `pharmacy.patient` + +| Field | Type | Description | Required | Notes | +|-------|------|-------------|----------|-------| +| `name` | Char | Patient full name | βœ“ | 255 chars max | +| `age` | Integer | Patient age | - | Optional | +| `gender` | Selection | Gender | - | `male` / `female` / `other` | +| `phone` | Char | Contact number | - | Optional | +| `email` | Char | Email address | - | Optional | +| `address` | Text | Full address | - | Optional | +| `blood_group` | Selection | Blood type | - | `A+` / `A-` / `B+` / `B-` / `O+` / `O-` / `AB+` / `AB-` | +| `doctor_id` | Many2one | Assigned doctor (`res.users`) | βœ“ | Doctor group member, controls visibility | +| `medical_history` | Text | Past medical conditions | - | Free-form notes | +| `allergies` | Text | Known allergies | - | Critical for prescription safety | +| `active` | Boolean | Is active patient | - | For soft-delete, default True | + +**Access Logic:** +- Doctors can CRUD their assigned patients only +- Managers can CRUD all patients +- Patients have no access + +--- + +## πŸ” Security Groups & Record Rules + +### User Groups (3-tier) + +**1. Patient Group** (`group_pharmacy_patient`) +- Lowest privilege level +- Purpose: End-users buying OTC medicines +- Access: White-license medicines ONLY +- Actions: View, read medicine information +- Visibility: No patient list, no dashboard + +**2. Doctor Group** (`group_pharmacy_doctor`) +- Medium privilege level +- Purpose: Medical professionals prescribing medicines +- Access: Blue & White medicines (licensed + OTC) +- Actions: View medicines, manage assigned patients +- Visibility: See dashboard, patients, orders (read-only), medicines list + +**3. Manager Group** (`group_pharmacy_manager`) +- Full administrative access +- Purpose: Pharmacy administrators/owners +- Access: All medicines (Green, Blue, White) +- Actions: Full CRUD on all objects, place orders, create invoices +- Visibility: Full access to all menus, dashboards, orders + +### Record Rules (ir.rule) + +#### Medicines (`pharmacy.medicine`) +``` +Patient: domain [('license_category', '=', 'white')] β†’ read-only +Doctor: domain [('license_category', 'in', ['blue', 'white'])] β†’ read-only +Manager: no rule β†’ full CRUD +``` + +#### Patients (`pharmacy.patient`) +``` +Patient: no read access (CSV-level) +Doctor: domain [('doctor_id', '=', user.id)] β†’ read/write/create (no delete) +Manager: domain [(1, '=', 1)] β†’ full CRUD +``` + +#### Sale Orders (`sale.order`) +``` +Global: read-only for all users (domain [(1, '=', 1)]) +Manager Override: full CRUD (domain [(1, '=', 1)], all perms=True) +Doctors: read-only via global rule +``` + +#### Categories (`pharmacy.category`) +``` +Patient: no read access (CSV-level) +Doctor: read-only (CSV-level) +Manager: full CRUD (CSV-level) +``` + +### Model-Level Permissions (ir.model.access) + +| Model | Patient | Doctor | Manager | Notes | +|-------|---------|--------|---------|-------| +| `pharmacy.medicine` | Read | Read | Create/Read/Write/Delete | Via record rules | +| `pharma.control.center` | No Access | Read | Create/Read/Write/Delete | Dashboard model | +| `pharmacy.category` | No Access | Read | Create/Read/Write/Delete | Config only for managers | +| `pharmacy.patient` | No Access | Create/Read/Write | Create/Read/Write/Delete | Doc via record rule | +| `sale.order` | No Access | Read | Create/Read/Write/Delete | Via record rules | + +### Menu Item Visibility + +| Menu | Patient | Doctor | Manager | Visible | +|------|---------|--------|---------|---------| +| πŸ’Š Pharma Control Center (root) | - | - | - | Always | +| πŸ“Š Dashboard | βœ— | βœ“ | βœ“ | No | +| πŸ’Š Medicines | βœ— | βœ“ | βœ“ | Yes | +| πŸ‘₯ Patients | βœ— | βœ“ | βœ“ | Yes | +| 🧾 Orders | βœ— | βœ“ | βœ“ | Yes | +| βš™οΈ Categories | βœ— | βœ— | βœ“ | Yes (Config) | + +--- + +## πŸ“¦ Dependencies + +- `base_setup` – Odoo base setup module (users, groups, settings) +- `product` – Odoo product module (for product creation on ordering) +- `account` – Odoo accounting module (for invoice generation) +- `sale` – Odoo sales module (for sale order creation) + +All dependencies are core Odoo modules. No third-party packages required. + +--- + +## πŸš€ Installation & Setup + +### Step 1: Copy Module +```bash +cp -r pharma_control_center /path/to/odoo/addons/ +``` + +### Step 2: Update Apps List (Odoo UI) +1. Go to **Apps** menu +2. Click **Update Apps List** button +3. Click **Update** in the confirmation dialog + +### Step 3: Install Module +1. Search for "Pharma Control Center" in Apps +2. Click the module card +3. Click **Install** button +4. Wait for installation to complete (green checkmark) + +### Step 4: Configure User Groups +1. Go to **Settings** β†’ **Users & Companies** β†’ **Users** (or **Users**) +2. Select a user +3. Scroll to **Access Rights** section +4. Check one of: + - βœ“ **Pharma Control Center / Patient** – for patients/end-users + - βœ“ **Pharma Control Center / Doctor** – for medical staff + - βœ“ **Pharma Control Center / Manager** – for administrators +5. **Save** user + +### Step 5: Create Demo Data (Optional) +Demo data is loaded automatically if `demo_patients.xml` and `demo_medicines.xml` are in `__manifest__.py` `data` list. No additional steps needed. + +### Step 6: Verify Installation +1. **Logout** and **Log In** to refresh user permissions +2. Check if **πŸ’Š Pharma Control Center** menu appears in sidebar +3. Navigate through menus (Dashboard, Medicines, Patients, Orders) +4. Verify correct data is visible based on your user group + +--- + +## πŸ§ͺ Demo Data + +The module includes comprehensive demo data: + +### Demo Users +- **doctor_demo** (Doctor Group) + - Username: `doctor_demo` + - Password: `demo` + - Assigned to: Doctor group + +### Demo Patients (3 records) +- Patient 1 β†’ Assigned to doctor_demo +- Patient 2 β†’ Assigned to doctor_demo +- Patient 3 β†’ Assigned to doctor_demo + +Medical info included: +- Age, gender, blood group +- Medical history +- Allergy information +- Contact details + +### Demo Medicines (Various) +- **Green License (Full):** 1-2 medicines +- **Blue License (Limited):** 2-3 medicines +- **White License (OTC):** 3-4 medicines + +Each includes: +- Batch number & expiry date (mix of fresh, expiring, expired) +- Stock levels (mix of in-stock, low-stock, out-of-stock) +- Pricing (cost & selling price) +- Storage conditions +- Dosage & side effects + +**To Load Demo Data:** +Ensure `demo_patients.xml` and `demo_medicines.xml` are listed in `__manifest__.py` under `data:`. Demo data loads automatically during module install. + +**To Clear Demo Data:** +- Edit demo XML files or delete records via Odoo UI +- No automated cleanup provided + +--- + +## πŸ› οΈ Compatibility & Requirements + +### Odoo Compatibility +- **Odoo 18.0+** (uses `` instead of deprecated ``, modern QWeb) +- **Tested on:** Odoo 18.0 + +### System Requirements +- **Python:** 3.10+ +- **PostgreSQL:** 13+ (recommended: 14+) +- **Browser:** Modern browser supporting ES6 JavaScript (Chrome, Firefox, Safari, Edge) + +### Known Limitations +- Orders (sale orders) created via module are standard Odoo orders; full order workflow (payment, shipping) uses standard Odoo modules (not included) +- Invoices created are account.move records; additional accounting is handled by Odoo account module +- No multi-language support for medicine names (but categories are translatable) +- Medicine photo/image storage not included (use Odoo's attachment system separately) + +--- + +## πŸ§‘β€πŸ’» Development & Customization + +### Adding Custom Fields +1. Edit relevant model file (`pharma_medicine.py`, `pharmacy_patient.py`, etc.) +2. Add field definition: `custom_field = fields.Char(...)` +3. Update XML view to display field +4. Run module upgrade in Odoo UI or CLI + +### Extending Security Rules +1. Edit security XML file (`pharmacy_security.xml`, etc.) +2. Add new `` with custom domain +3. Link to appropriate group +4. Reload module + +### Customizing Dashboard Statistics +Edit `_compute_statistics` method in `pharma_control_center.py` to add custom calculations. + +### Adding Reports +Create new view type in views XML or use Odoo's PDF report engine (separate implementation). + +--- + +## πŸ“„ License + +LGPL-3.0 (GNU Lesser General Public License v3) + +Full license text in LICENSE file. + +--- + +## πŸ™Œ Contributing + +Contributions welcome! To contribute: + +1. **Fork** the module repository +2. **Create feature branch:** `git checkout -b feature/my-feature` +3. **Make changes** with clear commit messages +4. **Test** your changes thoroughly +5. **Submit pull request** with description + +**For major changes:** +- Open an issue first to discuss +- Update README.md with new features +- Add unit tests if applicable +- Provide demo data for new features + +--- + +## πŸ“§ Support & Issues + +- **Report Bugs:** Create issue with clear description, steps to reproduce, attached logs +- **Request Features:** Create issue with `[FEATURE REQUEST]` tag +- **Questions:** Check README.md first, then open discussion + +--- + +## Change History + +### Version 1.0 (Current) +- βœ… Complete medicine management module +- βœ… Patient records with doctor assignment +- βœ… Sales order & invoice integration +- βœ… Role-based access control (Patient/Doctor/Manager) +- βœ… Real-time dashboard with statistics +- βœ… Inventory tracking & expiry management +- βœ… Odoo 18 compatibility + +--- + +**Developed with ❀️ for the Odoo community.** +**Muhammad Qasim Shabbir (AI Trainer/Developer) – 2026** + +**Module Version:** 1.0 +**Last Updated:** April 29, 2026 + diff --git a/pharma_control_center/__init__.py b/pharma_control_center/__init__.py new file mode 100644 index 00000000000..0650744f6bc --- /dev/null +++ b/pharma_control_center/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/pharma_control_center/__manifest__.py b/pharma_control_center/__manifest__.py new file mode 100644 index 00000000000..d9e15bf5f42 --- /dev/null +++ b/pharma_control_center/__manifest__.py @@ -0,0 +1,40 @@ +{ + 'name': 'Pharma Control Center', + 'version': '1.0', + 'summary': 'Pharmacy Dashboard, Medicines, Inventory & Operations', + 'sequence': 10, + 'description': """ +Pharma Control Center +===================== +Central system for pharmacy operations: +- Medicine catalog with batch, expiry, price, stock +- Dashboard showing total medicines and stock value +- Role‑based access (Patient, Doctor, Manager) + """, + 'category': 'Healthcare/Pharmacy', + 'author': "Muhammad Qasim Shabbir AI developer.", + 'website': 'https://www.odoo.com/app/invoicing', + 'depends': [ + 'base_setup', + 'product', + 'account', + 'sale', + ], + 'data': [ + 'security/groups.xml', + 'security/pharmacy_security.xml', + 'security/pharmacy_patient_security.xml', + 'security/sale_order_security.xml', + 'security/ir.model.access.csv', + 'data/demo_patients.xml', + 'data/demo_medicines.xml', + 'views/pharma_control_center_views.xml', # root menu defined here + 'views/pharmacy_medicine_views.xml', + 'views/pharmacy_patient_views.xml', # now includes the menuitem + 'views/pharmacy_category_views.xml', + 'views/pharmacy_order_views.xml', + ], + 'installable': True, + 'application': True, + 'license': 'LGPL-3', +} \ No newline at end of file diff --git a/pharma_control_center/data/demo_medicines.xml b/pharma_control_center/data/demo_medicines.xml new file mode 100644 index 00000000000..afc65902ebc --- /dev/null +++ b/pharma_control_center/data/demo_medicines.xml @@ -0,0 +1,52 @@ + + + + + + Painkiller + PAIN + Medicines for pain relief + + + + + Paracetamol 500mg + BATCH-001 + 2025-12-31 + 5.99 + 3.50 + 100 + white + + room_temp + 1 tablet every 4-6 hours + + + + + Amoxicillin 500mg + BATCH-002 + 2026-06-30 + 12.99 + 7.25 + 50 + blue + + cold + 1 capsule every 8 hours + + + + Morphine Sulphate + BATCH-003 + 2025-08-15 + 49.99 + 25.00 + 10 + green + + room_temp + As prescribed by doctor + + + \ No newline at end of file diff --git a/pharma_control_center/data/demo_patients.xml b/pharma_control_center/data/demo_patients.xml new file mode 100644 index 00000000000..afc65902ebc --- /dev/null +++ b/pharma_control_center/data/demo_patients.xml @@ -0,0 +1,52 @@ + + + + + + Painkiller + PAIN + Medicines for pain relief + + + + + Paracetamol 500mg + BATCH-001 + 2025-12-31 + 5.99 + 3.50 + 100 + white + + room_temp + 1 tablet every 4-6 hours + + + + + Amoxicillin 500mg + BATCH-002 + 2026-06-30 + 12.99 + 7.25 + 50 + blue + + cold + 1 capsule every 8 hours + + + + Morphine Sulphate + BATCH-003 + 2025-08-15 + 49.99 + 25.00 + 10 + green + + room_temp + As prescribed by doctor + + + \ No newline at end of file diff --git a/pharma_control_center/models/__init__.py b/pharma_control_center/models/__init__.py new file mode 100644 index 00000000000..ca99335b447 --- /dev/null +++ b/pharma_control_center/models/__init__.py @@ -0,0 +1,4 @@ +from . import pharma_medicine +from . import pharmacy_category +from . import pharmacy_patient +from . import pharma_control_center diff --git a/pharma_control_center/models/pharma_control_center.py b/pharma_control_center/models/pharma_control_center.py new file mode 100644 index 00000000000..e7934f10f31 --- /dev/null +++ b/pharma_control_center/models/pharma_control_center.py @@ -0,0 +1,146 @@ +from odoo import models, fields, api, _ +from datetime import date, timedelta + +class PharmaControlCenter(models.Model): + _name = "pharma.control.center" + _description = "Pharma Control Center" + + name = fields.Char(string="Name", required=True, default="Pharmacy Dashboard") + description = fields.Text(string="Description") + last_updated = fields.Datetime(string="Last Updated", default=fields.Datetime.now) + + # Medicine statistics + total_medicines = fields.Integer( + string="Total Medicines", compute="_compute_statistics", store=False + ) + total_stock_quantity = fields.Integer( + string="Total Stock Quantity", compute="_compute_statistics", store=False + ) + stock_value = fields.Float( + string="Stock Value", compute="_compute_statistics", store=False + ) + out_of_stock_count = fields.Integer( + string="Out of Stock Medicines", compute="_compute_statistics", store=False + ) + low_stock_count = fields.Integer( + string="Low Stock (< 10 units)", compute="_compute_statistics", store=False + ) + expiring_soon_count = fields.Integer( + string="Expiring Within 30 Days", compute="_compute_statistics", store=False + ) + expired_count = fields.Integer( + string="Expired Medicines", compute="_compute_statistics", store=False + ) + + # Patient statistics + total_patients = fields.Integer( + string="Total Patients", compute="_compute_statistics", store=False + ) + my_patients = fields.Integer( + string="My Patients", compute="_compute_statistics", store=False + ) + patient_ids = fields.One2many( + 'pharmacy.patient', + string="Patients", + compute='_compute_patient_ids', + readonly=True + ) + + # Today's orders summary + today_order_total_qty = fields.Float( + string="Total Quantity Today", + compute="_compute_today_orders_summary", + store=False + ) + today_order_total_amount = fields.Float( + string="Total Sales Today", + compute="_compute_today_orders_summary", + store=False + ) + + @api.depends() + def _compute_statistics(self): + Medicine = self.env['pharmacy.medicine'] + Patient = self.env['pharmacy.patient'] + today = date.today() + thirty_days_later = today + timedelta(days=30) + + for record in self: + # Medicine stats + all_meds = Medicine.search([]) + record.total_medicines = len(all_meds) + record.total_stock_quantity = sum(med.quantity for med in all_meds) + record.stock_value = sum(med.quantity * med.price for med in all_meds) + record.out_of_stock_count = sum(1 for med in all_meds if med.quantity == 0) + record.low_stock_count = sum(1 for med in all_meds if 0 < med.quantity < 10) + record.expiring_soon_count = sum( + 1 for med in all_meds + if med.expiry_date and med.expiry_date <= thirty_days_later and med.expiry_date > today + ) + record.expired_count = sum( + 1 for med in all_meds if med.expiry_date and med.expiry_date < today + ) + + # Patient stats + if Patient: + record.total_patients = Patient.search_count([]) + if self.env.user.has_group('pharma_control_center.group_pharmacy_doctor'): + record.my_patients = Patient.search_count([('doctor_id', '=', self.env.user.id)]) + else: + record.my_patients = 0 + else: + record.total_patients = 0 + record.my_patients = 0 + + @api.depends() + def _compute_patient_ids(self): + Patient = self.env['pharmacy.patient'] + for record in self: + if self.env.user.has_group('pharma_control_center.group_pharmacy_doctor'): + record.patient_ids = Patient.search([('doctor_id', '=', self.env.user.id)]) + elif self.env.user.has_group('pharma_control_center.group_pharmacy_manager'): + record.patient_ids = Patient.search([]) + else: + record.patient_ids = False + + @api.depends() + def _compute_today_orders_summary(self): + """Compute total quantity and amount for today's orders. + Non‑managers see only their own orders.""" + today = fields.Date.today() + tomorrow = today + timedelta(days=1) + domain = [ + ('order_id.date_order', '>=', today), + ('order_id.date_order', '<', tomorrow), + ('order_id.state', 'not in', ['cancel']) + ] + if not self.env.user.has_group('pharma_control_center.group_pharmacy_manager'): + domain.append(('create_uid', '=', self.env.user.id)) + order_lines = self.env['sale.order.line'].search(domain) + self.today_order_total_qty = sum(order_lines.mapped('product_uom_qty')) + self.today_order_total_amount = sum(order_lines.mapped('price_subtotal')) + + def action_view_today_orders(self): + """Open a list of today's order lines.""" + today = fields.Date.today() + tomorrow = today + timedelta(days=1) + domain = [ + ('order_id.date_order', '>=', today), + ('order_id.date_order', '<', tomorrow), + ('order_id.state', 'not in', ['cancel']) + ] + if not self.env.user.has_group('pharma_control_center.group_pharmacy_manager'): + domain.append(('create_uid', '=', self.env.user.id)) + + # Use the custom view + view = self.env.ref('pharma_control_center.view_sale_order_line_today_list', raise_if_not_found=False) + view_id = view.id if view else False + return { + 'type': 'ir.actions.act_window', + 'name': "Today's Orders", + 'res_model': 'sale.order.line', + 'view_mode': 'list,form', + 'target': 'current', + 'domain': domain, + 'views': [(view_id, 'list')] if view_id else [(False, 'list')], + } \ No newline at end of file diff --git a/pharma_control_center/models/pharma_medicine.py b/pharma_control_center/models/pharma_medicine.py new file mode 100644 index 00000000000..a17a0f13fbd --- /dev/null +++ b/pharma_control_center/models/pharma_medicine.py @@ -0,0 +1,235 @@ +import json +from odoo import models, fields, api, _ +from datetime import date, timedelta, datetime +from odoo.exceptions import UserError + +class ResUsers(models.Model): + _inherit = 'res.users' + cart_data = fields.Text(string="Cart Data", default="{}", help="JSON cart for pharmacy module") + +class PharmacyMedicine(models.Model): + _name = "pharmacy.medicine" + _description = "Pharmacy Medicine" + _order = "name" + + name = fields.Char(string="Medicine Name", required=True) + description = fields.Text(string="Description") + manufacturer = fields.Char(string="Manufacturer") + barcode = fields.Char(string="Barcode") + category_id = fields.Many2one('pharmacy.category', string="Medicine Category", required=True, ondelete='restrict') + sub_category = fields.Char(string="Sub‑Category") + batch_number = fields.Char(string="Batch Number", required=True) + expiry_date = fields.Date(string="Expiry Date", required=True) + price = fields.Float(string="Selling Price", required=True) + cost_price = fields.Float(string="Cost Price") + profit_margin = fields.Float(string="Profit Margin (%)", compute="_compute_profit_margin", store=True) + quantity = fields.Integer(string="Stock Quantity", required=True, default=0) + reorder_level = fields.Integer(string="Reorder Level", default=10) + need_reorder = fields.Boolean(string="Need Reorder", compute="_compute_need_reorder", store=True) + order_qty = fields.Integer(string="Order Quantity", default=1) + in_stock = fields.Boolean(string="In Stock", compute="_compute_in_stock", store=True) + days_to_expiry = fields.Integer(string="Days to Expiry", compute="_compute_expiry_days", store=True) + expiry_status = fields.Selection([ + ('fresh', 'Fresh'), + ('expiring_soon', 'Expiring Soon'), + ('expired', 'Expired'), + ], string="Expiry Status", compute="_compute_expiry_status", store=True) + storage_location = fields.Selection([ + ('room_temp', 'Room Temperature'), + ('cold', 'Cold Storage'), + ('frozen', 'Frozen'), + ], string="Storage Condition", required=True) + side_effects = fields.Text(string="Side Effects") + dosage = fields.Char(string="Dosage") + license_category = fields.Selection([ + ('green', '🟒 Pharmacy (Category A) - Full license'), + ('blue', 'πŸ”΅ Medical Store (Category B) - Limited license'), + ('white', 'βšͺ Drug Store (Category C) - Basic OTC'), + ], string="Pharmacy License Category", required=True, default='white') + product_id = fields.Many2one('product.product', string="Linked Product", readonly=True) + last_sale_order_id = fields.Many2one('sale.order', string="Last Sale Order", readonly=True) + last_invoice_id = fields.Many2one('account.move', string="Last Invoice", readonly=True) + today_orders_qty = fields.Float(string="Today Orders", compute="_compute_today_orders", store=False) + + # Cart fields (computed from user cart data) + cart_quantity = fields.Integer(string="Cart Quantity", compute="_compute_cart_fields") + cart_subtotal = fields.Float(string="Cart Subtotal", compute="_compute_cart_fields") + cart_item_ids = fields.Many2many('pharmacy.medicine', compute="_compute_cart_item_ids") + cart_total = fields.Float(string="Cart Total", compute="_compute_cart_total") + + # ------------------------------------------------------------ + # Cart helper methods + # ------------------------------------------------------------ + def _get_cart_dict(self): + """Return dict {medicine_id: quantity} from user's cart_data""" + data = self.env.user.cart_data + if not data: + return {} + return json.loads(data) + + def _set_cart_dict(self, cart_dict): + self.env.user.sudo().write({'cart_data': json.dumps(cart_dict)}) + + def _compute_cart_fields(self): + cart = self._get_cart_dict() + for med in self: + qty = cart.get(str(med.id), 0) + med.cart_quantity = qty + med.cart_subtotal = qty * med.price + + def _compute_cart_item_ids(self): + cart = self._get_cart_dict() + self.cart_item_ids = self.browse([int(k) for k in cart.keys()]) + + def _compute_cart_total(self): + cart = self._get_cart_dict() + total = 0.0 + for med_id, qty in cart.items(): + med = self.browse(int(med_id)) + total += med.price * qty + self.cart_total = total + + # ------------------------------------------------------------ + # Cart actions + # ------------------------------------------------------------ + def add_to_cart(self): + self.ensure_one() + if self.order_qty <= 0: + raise UserError(_("Quantity must be greater than zero")) + if self.order_qty > self.quantity: + raise UserError(_("Not enough stock. Available: %s") % self.quantity) + + cart = self._get_cart_dict() + key = str(self.id) + cart[key] = cart.get(key, 0) + self.order_qty + self._set_cart_dict(cart) + self.order_qty = 1 + return { + 'type': 'ir.actions.client', + 'tag': 'display_notification', + 'params': { + 'title': _('Added to Cart'), + 'message': _("Added %s to your cart.") % self.name, + 'type': 'success', + 'sticky': False, + } + } + + def remove_from_cart(self): + self.ensure_one() + cart = self._get_cart_dict() + key = str(self.id) + if key in cart: + del cart[key] + self._set_cart_dict(cart) + return {'type': 'ir.actions.client', 'tag': 'reload'} + + def checkout(self): + cart = self._get_cart_dict() + if not cart: + raise UserError(_("Your cart is empty.")) + order_lines = [] + for med_id, qty in cart.items(): + med = self.browse(int(med_id)) + if not med.product_id: + med._create_product() + order_lines.append((0, 0, { + 'product_id': med.product_id.id, + 'product_uom_qty': qty, + 'price_unit': med.price, + })) + sale_order = self.env['sale.order'].create({ + 'partner_id': self.env.user.partner_id.id, + 'order_line': order_lines, + }) + sale_order.action_confirm() + invoice = sale_order._create_invoices() + invoice.action_post() + + for med_id, qty in cart.items(): + med = self.browse(int(med_id)) + med.quantity -= qty + + self._set_cart_dict({}) + return { + 'type': 'ir.actions.act_window', + 'res_model': 'account.move', + 'res_id': invoice.id, + 'view_mode': 'form', + 'target': 'new', + } + + # ------------------------------------------------------------ + # Helper + # ------------------------------------------------------------ + def _create_product(self): + product = self.env['product.product'].create({ + 'name': self.name, + 'list_price': self.price, + 'standard_price': self.cost_price or 0.0, + 'type': 'consu', + 'sale_ok': True, + 'purchase_ok': False, + }) + self.product_id = product + return product + + # ------------------------------------------------------------ + # Compute methods (unchanged) + # ------------------------------------------------------------ + @api.depends('price', 'cost_price') + def _compute_profit_margin(self): + for med in self: + if med.cost_price and med.cost_price > 0: + med.profit_margin = ((med.price - med.cost_price) / med.cost_price) * 100 + else: + med.profit_margin = 0.0 + + @api.depends('quantity', 'reorder_level') + def _compute_need_reorder(self): + for med in self: + med.need_reorder = med.quantity <= med.reorder_level + + @api.depends('quantity') + def _compute_in_stock(self): + for med in self: + med.in_stock = med.quantity > 0 + + @api.depends('expiry_date') + def _compute_expiry_days(self): + today = date.today() + for med in self: + if med.expiry_date: + delta = (med.expiry_date - today).days + med.days_to_expiry = delta + else: + med.days_to_expiry = 0 + + @api.depends('expiry_date') + def _compute_expiry_status(self): + today = date.today() + for med in self: + if not med.expiry_date: + med.expiry_status = 'fresh' + elif med.expiry_date < today: + med.expiry_status = 'expired' + elif med.expiry_date <= today + timedelta(days=30): + med.expiry_status = 'expiring_soon' + else: + med.expiry_status = 'fresh' + + @api.depends('product_id') + def _compute_today_orders(self): + today_start = datetime.now().replace(hour=0, minute=0, second=0) + today_end = today_start + timedelta(days=1) + for med in self: + total = 0.0 + if med.product_id: + lines = self.env['sale.order.line'].search([ + ('product_id', '=', med.product_id.id), + ('order_id.date_order', '>=', today_start), + ('order_id.date_order', '<', today_end), + ('order_id.state', 'not in', ['cancel']) + ]) + total = sum(lines.mapped('product_uom_qty')) + med.today_orders_qty = total \ No newline at end of file diff --git a/pharma_control_center/models/pharmacy_category.py b/pharma_control_center/models/pharmacy_category.py new file mode 100644 index 00000000000..5bd419a3c81 --- /dev/null +++ b/pharma_control_center/models/pharmacy_category.py @@ -0,0 +1,12 @@ +from odoo import models, fields + +class PharmacyCategory(models.Model): + _name = "pharmacy.category" + _description = "Medicine Category" + _order = "name" + + name = fields.Char(string="Category Name", required=True, translate=True) + code = fields.Char(string="Category Code", help="Short code, e.g., ANTIBIOTIC") + description = fields.Text(string="Description") + parent_id = fields.Many2one('pharmacy.category', string="Parent Category", ondelete='restrict') + child_ids = fields.One2many('pharmacy.category', 'parent_id', string="Child Categories") \ No newline at end of file diff --git a/pharma_control_center/models/pharmacy_patient.py b/pharma_control_center/models/pharmacy_patient.py new file mode 100644 index 00000000000..a7df6f7036a --- /dev/null +++ b/pharma_control_center/models/pharmacy_patient.py @@ -0,0 +1,32 @@ +from odoo import models, fields, api + +class PharmacyPatient(models.Model): + _name = "pharmacy.patient" + _description = "Patient" + _order = "name" + + doctor_id = fields.Many2one('res.users', string="Assigned Doctor", required=True) + name = fields.Char(string="Patient Name", required=True) + age = fields.Integer(string="Age") + gender = fields.Selection([ + ('male', 'Male'), + ('female', 'Female'), + ('other', 'Other'), + ], string="Gender") + phone = fields.Char(string="Phone") + email = fields.Char(string="Email") + address = fields.Text(string="Address") + blood_group = fields.Selection([ + ('A+', 'A+'), ('A-', 'A-'), + ('B+', 'B+'), ('B-', 'B-'), + ('O+', 'O+'), ('O-', 'O-'), + ('AB+', 'AB+'), ('AB-', 'AB-'), + ], string="Blood Group") + medical_history = fields.Text(string="Medical History") + allergies = fields.Text(string="Allergies") + # Removed prescription_ids – will be added later with a proper prescription model + active = fields.Boolean(string="Active", default=True) + + _sql_constraints = [ + ('unique_email', 'UNIQUE(email)', 'Email must be unique!'), + ] \ No newline at end of file diff --git a/pharma_control_center/security/groups.xml b/pharma_control_center/security/groups.xml new file mode 100644 index 00000000000..00a9392318f --- /dev/null +++ b/pharma_control_center/security/groups.xml @@ -0,0 +1,31 @@ + + + + + + Pharma Control Center + 10 + + + + + Patient + + + + + + + Doctor + + + + + + + Manager + + + + + \ No newline at end of file diff --git a/pharma_control_center/security/ir.model.access.csv b/pharma_control_center/security/ir.model.access.csv new file mode 100644 index 00000000000..2925cacd6d8 --- /dev/null +++ b/pharma_control_center/security/ir.model.access.csv @@ -0,0 +1,17 @@ +id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink +access_pharmacy_medicine_admin,pharmacy.medicine.admin,model_pharmacy_medicine,base.group_user,1,1,1,1 +access_pharmacy_medicine_patient,pharmacy.medicine.patient,model_pharmacy_medicine,pharma_control_center.group_pharmacy_patient,1,0,0,0 +access_pharmacy_medicine_doctor,pharmacy.medicine.doctor,model_pharmacy_medicine,pharma_control_center.group_pharmacy_doctor,1,0,0,0 +access_pharmacy_medicine_manager,pharmacy.medicine.manager,model_pharmacy_medicine,pharma_control_center.group_pharmacy_manager,1,1,1,1 +access_pharma_control_center_admin,pharma.control.center.admin,model_pharma_control_center,base.group_user,1,1,1,1 +access_pharma_control_center_patient,pharma.control.center.patient,model_pharma_control_center,pharma_control_center.group_pharmacy_patient,1,0,0,0 +access_pharma_control_center_doctor,pharma.control.center.doctor,model_pharma_control_center,pharma_control_center.group_pharmacy_doctor,1,0,0,0 +access_pharma_control_center_manager,pharma.control.center.manager,model_pharma_control_center,pharma_control_center.group_pharmacy_manager,1,1,1,1 +access_pharmacy_category_admin,pharmacy.category.admin,model_pharmacy_category,base.group_user,1,1,1,1 +access_pharmacy_category_patient,pharmacy.category.patient,model_pharmacy_category,pharma_control_center.group_pharmacy_patient,1,0,0,0 +access_pharmacy_category_doctor,pharmacy.category.doctor,model_pharmacy_category,pharma_control_center.group_pharmacy_doctor,1,0,0,0 +access_pharmacy_category_manager,pharmacy.category.manager,model_pharmacy_category,pharma_control_center.group_pharmacy_manager,1,1,1,1 +access_pharmacy_patient_admin,pharmacy.patient.admin,model_pharmacy_patient,base.group_user,1,1,1,1 +access_pharmacy_patient_patient,pharmacy.patient.patient,model_pharmacy_patient,pharma_control_center.group_pharmacy_patient,1,0,0,0 +access_pharmacy_patient_doctor,pharmacy.patient.doctor,model_pharmacy_patient,pharma_control_center.group_pharmacy_doctor,1,1,1,1 +access_pharmacy_patient_manager,pharmacy.patient.manager,model_pharmacy_patient,pharma_control_center.group_pharmacy_manager,1,1,1,1 \ No newline at end of file diff --git a/pharma_control_center/security/pharmacy_patient_security.xml b/pharma_control_center/security/pharmacy_patient_security.xml new file mode 100644 index 00000000000..00cd26424da --- /dev/null +++ b/pharma_control_center/security/pharmacy_patient_security.xml @@ -0,0 +1,25 @@ + + + + Doctor: own patients only + + + [('doctor_id', '=', user.id)] + + + + + + + + Manager: all patients + + + [(1, '=', 1)] + + + + + + \ No newline at end of file diff --git a/pharma_control_center/security/pharmacy_security.xml b/pharma_control_center/security/pharmacy_security.xml new file mode 100644 index 00000000000..9a326660469 --- /dev/null +++ b/pharma_control_center/security/pharmacy_security.xml @@ -0,0 +1,30 @@ + + + + + + Patient: only White (OTC) medicines + + + [('license_category', '=', 'white')] + + + + + + + + Doctor: Blue + White medicines + + + [('license_category', 'in', ['blue', 'white'])] + + + + + + + + + + \ No newline at end of file diff --git a/pharma_control_center/security/sale_order_security.xml b/pharma_control_center/security/sale_order_security.xml new file mode 100644 index 00000000000..779aa12c8d7 --- /dev/null +++ b/pharma_control_center/security/sale_order_security.xml @@ -0,0 +1,25 @@ + + + + Sale Order: Global read-only + + + + + + + [(1, '=', 1)] + + + + + Sale Order: Manager full access + + + + + + + [(1, '=', 1)] + + \ No newline at end of file diff --git a/pharma_control_center/test/test_pharma_control_center.py b/pharma_control_center/test/test_pharma_control_center.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/pharma_control_center/views/pharma_control_center_views.xml b/pharma_control_center/views/pharma_control_center_views.xml new file mode 100644 index 00000000000..854d3f68ecc --- /dev/null +++ b/pharma_control_center/views/pharma_control_center_views.xml @@ -0,0 +1,78 @@ + + + + + pharma.control.center.form + pharma.control.center + +
+ +
+
+

πŸ₯ Pharma Control Center

+

πŸ“Š Your pharmacy at a glance

+
+ + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+
+ + + pharmacy.medicine.search + pharmacy.medicine + + + + + + + + + + + + + + + + + + + + + + + + + + + πŸ’Š Medicines + pharmacy.medicine + kanban,list,form + + + + + +
\ No newline at end of file diff --git a/pharma_control_center/views/pharmacy_order_views.xml b/pharma_control_center/views/pharmacy_order_views.xml new file mode 100644 index 00000000000..d471b64c459 --- /dev/null +++ b/pharma_control_center/views/pharmacy_order_views.xml @@ -0,0 +1,32 @@ + + + + + pharmacy.sale.order.list + sale.order + + + + + + + + + + + + + + 🧾 Orders + sale.order + list,form + + + + + \ No newline at end of file diff --git a/pharma_control_center/views/pharmacy_patient_views.xml b/pharma_control_center/views/pharmacy_patient_views.xml new file mode 100644 index 00000000000..b432b464848 --- /dev/null +++ b/pharma_control_center/views/pharmacy_patient_views.xml @@ -0,0 +1,64 @@ + + + + + pharmacy.patient.list + pharmacy.patient + + + + + + + + + + + + + + pharmacy.patient.form + pharmacy.patient + +
+ + + + + + + + + + + + + + + + + + +
+
+
+ + + + Patients + pharmacy.patient + list,form + +

+ No patients yet. Create your first patient. +

+
+
+ + +
\ No newline at end of file