# Procurement SaaS — Laravel Cursor Build Prompts

Build a production-ready, multi-tenant, AI-assisted procurement SaaS platform.

Paste each prompt into Cursor **in order**. Later prompts depend on the migrations, services, models, middleware, and security architecture created in earlier prompts.

Do not skip the multi-tenancy and authorization work in Prompt 1.

---

# Mandatory Technology Stack

Use only the following stack:

* PHP 8.2+
* Laravel 10
* MySQL 8+
* Laravel Blade templates
* HTML5
* CSS3
* Bootstrap 5
* Vanilla JavaScript
* jQuery
* Chart.js for dashboard charts
* Laravel Breeze with Blade for authentication
* Laravel queues and scheduled commands
* Laravel Storage for uploaded documents
* Laravel Excel for CSV/XLSX processing
* Twilio SDK for WhatsApp integration

Do not use:

* React
* Vue
* Next.js
* Nuxt
* TypeScript
* Supabase
* Firebase
* Livewire
* Inertia
* Alpine.js
* Any JavaScript SPA framework

The application must use server-rendered Laravel Blade pages. Use jQuery AJAX only where asynchronous behavior is necessary.

---

# Global Development Rules

Follow these rules throughout the entire project:

1. Use Laravel migrations for all database changes.
2. Use Eloquent models and relationships.
3. Use Form Request classes for validation.
4. Use service classes for business logic.
5. Use policies and middleware for authorization.
6. Use database transactions for multi-table operations.
7. Use queued jobs for large imports, notifications, analytics recalculation, and long-running tasks.
8. Use named routes and resource controllers.
9. Use PHP enums or Laravel-compatible enum classes for fixed statuses and types.
10. Add indexes, unique constraints, and foreign keys where appropriate.
11. Add Pest or PHPUnit feature tests for all critical workflows.
12. Never accept `organization_id` directly from a browser request.
13. Always obtain the current organization from the authenticated user's active organization context.
14. Do not place business logic inside Blade templates or controllers.
15. Do not create placeholder pages when functional pages can be implemented.
16. Do not modify unrelated existing modules.
17. Use clean, reusable Blade layouts, components, partials, and modals.
18. Protect all forms with CSRF.
19. Escape user-provided content when displaying it.
20. Keep an audit trail for sensitive procurement and approval actions.

---

# UI and Design Requirements

Create a professional, clean, modern procurement dashboard.

Use:

* Dark navy sidebar
* White or very light grey content background
* Blue, cyan, green, purple, and orange accent colors
* Clean cards with subtle borders and shadows
* Consistent spacing and typography
* Responsive Bootstrap grid
* Status badges
* Searchable and filterable tables
* Confirmation modals for sensitive actions
* Loading states for AJAX operations
* Empty states when no data exists
* Toast alerts for success and error messages
* Chart.js for line, bar, and doughnut charts

The sidebar should contain:

* Dashboard
* Procurement Requests
* Purchase Orders
* Suppliers
* Items
* Contracts
* Inventory
* Stock Batches
* Receipts
* Invoices
* Imports
* Approvals
* Recommendations
* Savings
* Reports
* Organization Users
* Settings

Use reusable Blade components for:

* KPI cards
* Data tables
* Filters
* Status badges
* Page headers
* Confirmation modals
* Form fields
* Empty states
* Alerts
* Dashboard chart cards

---

# Data Availability Note

Current source data consists of two Odoo Sales Analysis exports.

These exports contain customer-demand information, including:

* Product
* Customer
* Ordered quantity
* Order date

They currently do not contain reliable supplier or purchase-price information.

Use this data for:

* Sales demand
* Consumption trends
* Reorder calculations
* Future forecasting preparation

Purchase Order and Vendor Bill exports will be required later to populate:

* Suppliers
* Supplier prices
* Purchase orders
* Vendor invoices
* Price history

The import-column mapping must remain configurable because Odoo export columns may vary by client and locale.

---

# Prompt 1 — Laravel Foundation, Authentication and Multi-Tenancy

```text
Set up the foundation of a Laravel 11 multi-tenant procurement SaaS application.

Technology requirements:

- PHP 8.2+
- Laravel 10
- MySQL 8+
- Laravel  Blade
- Bootstrap 5
- HTML, CSS, vanilla JavaScript and jQuery
- No React, Vue, Livewire, Inertia, Alpine, TypeScript, Supabase or Firebase

Do not build procurement features yet. Complete the application foundation, authentication, organization tenancy, role authorization and security tests first.

1. Authentication

Install and configure Laravel bootstrap auth using the Blade stack.

Create:

- Login
- Registration
- Forgot password
- Reset password
- Email verification
- Logout

Use Bootstrap styling instead of Tailwind.

Remove Tailwind dependencies and convert the authentication views to professional Bootstrap 5 Blade templates.

2. Organization structure

Create the following tables:

organizations:
- id
- name
- slug
- email nullable
- phone nullable
- country nullable
- currency default SAR
- timezone default Asia/Riyadh
- is_active boolean default true
- created_by nullable
- timestamps

organization_user:
- id
- organization_id
- user_id
- role
- is_active boolean default true
- joined_at nullable
- timestamps

users:
- id
- name
- email
- phone nullable
- password
- current_organization_id nullable
- email_verified_at nullable
- remember_token
- timestamps

The organization role must support:

- requester
- approver
- procurement_manager
- finance
- admin
- viewer

Use a PHP enum for organization roles.

A user may belong to multiple organizations, but only one organization is active in the session at a time.

During registration:

- Create a new organization.
- Create the user.
- Attach the user to the organization.
- Assign the first user the admin role.
- Set current_organization_id.
- Wrap the full registration process in a database transaction.

3. Organization context

Create an OrganizationContext service that:

- Resolves the current organization from the authenticated user.
- Confirms the user has an active membership.
- Stores the resolved organization for the current request.
- Can be explicitly set inside queue jobs and console commands.
- Throws a controlled authorization exception if no valid organization exists.

Create middleware named ResolveOrganization that:

- Requires authentication.
- Resolves the current organization.
- Confirms the organization is active.
- Confirms the membership is active.
- Shares the current organization with Blade views.
- Rejects unauthorized organization access.

Create an organization-switching route that:

- Accepts an organization ID.
- Confirms the user belongs to that organization.
- Updates current_organization_id.
- Regenerates the session.
- Redirects to the dashboard.

4. Tenant model protection

Every tenant-owned table created now or later must contain:

- organization_id
- A foreign key to organizations
- An index on organization_id

Create a reusable BelongsToOrganization trait.

The trait must:

- Automatically apply an Eloquent global scope for organization_id.
- Automatically set organization_id during model creation.
- Obtain organization_id from OrganizationContext.
- Prevent changing organization_id after the record is created.
- Provide an organization relationship.

Create a reusable OrganizationScope class.

Do not manually repeat the same organization query logic inside every controller.

Because MySQL does not provide Supabase-style Row-Level Security, enforce tenant isolation through:

- Organization middleware
- Eloquent global scopes
- Model observers or model boot methods
- Policies
- Service-layer organization validation
- Foreign keys
- Feature tests
- Strictly controlled raw queries

Do not use DB::table directly for tenant-owned data unless organization_id is explicitly added from OrganizationContext.

Never accept organization_id from:

- Request forms
- Query strings
- AJAX payloads
- Uploaded files
- Hidden input fields

5. Authorization

Create policies and middleware for the six organization roles.

Permission expectations:

Requester:
- Create procurement requests
- View their own requests
- View approved catalog information

Approver:
- View assigned approvals
- Approve, reject or return assigned requests

Procurement Manager:
- Manage suppliers
- Manage items
- Manage purchase orders
- Review recommendations
- Access procurement reports

Finance:
- Review invoices
- View financial analytics
- Confirm realized savings
- Handle budget exceptions

Admin:
- Full organization-level access
- Manage users, settings, approval rules and integrations

Viewer:
- Read-only access to authorized organization records

Use policies instead of checking role strings directly in Blade templates.

6. Basic application shell

Create:

- Main authenticated Blade layout
- Responsive dark navy sidebar
- Top navigation
- Organization switcher
- User profile menu
- Empty dashboard route
- Breadcrumb component
- Alert component
- Reusable confirmation modal
- 403 page
- 404 page
- 500 page

7. Security tests

Write feature tests that create:

- Organization A
- Organization B
- User A in Organization A
- User B in Organization B
- Dummy tenant-owned records for both organizations

Prove that:

- User A cannot view Organization B records.
- User A cannot update Organization B records.
- User A cannot delete Organization B records.
- User A cannot access a URL containing Organization B's record ID.
- User A cannot switch to Organization B.
- organization_id sent in a browser request is ignored.
- A new tenant-owned model automatically receives the authenticated organization ID.
- Queue jobs fail safely when no OrganizationContext has been provided.
- Admin access is limited to the admin's own organization.

Use factories and seeders for test data.

Do not proceed to procurement features until these tests pass.
```

---

# Prompt 2 — Odoo CSV/XLSX Import Pipeline

```text
Build a secure organization-scoped CSV and XLSX upload, preview, mapping, validation and import pipeline for Odoo exports.

Use:

- Laravel Blade
- Bootstrap 5
- jQuery AJAX
- Laravel Excel
- Laravel Storage
- Laravel queued jobs
- MySQL
- Existing OrganizationContext and BelongsToOrganization architecture

Do not use Supabase Storage, React, Vue or TypeScript.

1. Database tables

Create import_files:

- id
- organization_id
- uploaded_by
- original_name
- stored_name
- storage_disk
- storage_path
- mime_type
- file_size
- source_type
- status
- total_rows default 0
- valid_rows default 0
- invalid_rows default 0
- imported_rows default 0
- processing_started_at nullable
- processing_completed_at nullable
- failure_message nullable
- timestamps

source_type enum:

- purchase_order
- vendor_bill
- sales_order

status enum:

- uploaded
- analyzing
- mapping_required
- ready
- processing
- completed
- completed_with_errors
- failed

Create import_column_mappings:

- id
- organization_id
- source_type
- source_column
- canonical_field
- is_required boolean default false
- created_by
- timestamps

Add a unique constraint on:

- organization_id
- source_type
- source_column

Create import_rows:

- id
- organization_id
- import_file_id
- row_number
- raw_data JSON
- normalized_data JSON nullable
- status
- validation_errors JSON nullable
- imported_record_type nullable
- imported_record_id nullable
- timestamps

Row status enum:

- pending
- valid
- quarantined
- imported
- skipped
- failed

2. Upload interface

Create an Imports module with:

- Import listing page
- Upload page
- File details page
- Column-mapping page
- Data-preview page
- Invalid-row page
- Import-progress page

The upload form must:

- Accept CSV and XLSX only.
- Validate extension and MIME type.
- Apply a configurable maximum upload size.
- Require a source type.
- Store files using Laravel Storage.
- Store files under an organization-specific directory.
- Generate a unique stored filename.
- Never use a browser-provided organization_id.

Example storage path:

procurement-imports/{organization_id}/{year}/{month}/{unique-filename}

3. File inspection

After upload:

- Dispatch a queued file-inspection job.
- Detect file headers.
- Detect whether the spreadsheet is flat, hierarchical or pivoted.
- Save a sample of rows for preview.
- Find a saved mapping for the organization and source type.
- Automatically apply known mappings where possible.
- Send the user to the mapping page if required columns remain unmapped.

4. Column mapping

Show detected source columns on the left.

Allow the user to map each source column to canonical fields such as:

- item_code
- item_name
- item_name_ar
- item_name_en
- supplier_name
- customer_name
- unit_price
- quantity
- order_date
- invoice_date
- invoice_number
- purchase_order_number
- sales_order_number
- currency
- unit_of_measure
- category
- batch_number
- expiry_date
- warehouse_location

Save mappings by:

- organization_id
- source_type
- source_column

For future uploads:

- Apply saved mappings automatically.
- Allow users to review and change the mapping.
- Do not overwrite previous mappings until the user confirms.

5. Odoo product parsing

Handle product strings such as:

[P02087] مياه غازية كينزا (250ml*30pcs) Kenza sparkling water

Create an OdooProductParser service.

It must:

- Extract the bracketed product code.
- Remove the code from the remaining name.
- Detect Arabic Unicode text.
- Separate Arabic and English names where reasonably possible.
- Preserve measurements, packaging details and model numbers.
- Avoid losing original text.
- Save the original source value in raw_data.

Expected normalized result:

- item_code: P02087
- name_ar: مياه غازية كينزا
- name_en: Kenza sparkling water
- package_description: 250ml*30pcs

The parser must also work when:

- No code exists.
- Only Arabic exists.
- Only English exists.
- The product contains numbers.
- The product contains parentheses.
- The value is blank.
- Multiple bracket groups exist.

Add unit tests for the parser.

6. Odoo Sales Analysis pivot handling

Odoo Sales Analysis exports may arrive as:

- Hierarchical rows such as Month > Customer > Order > Product
- Wide spreadsheets with dates or months as columns
- Pivot tables with totals and subtotals
- Repeated group headings
- Blank cells representing inherited parent values

Create an OdooSalesPivotFlattener service.

It must:

- Detect hierarchical and wide pivot structures.
- Carry parent values into child rows.
- Ignore total and subtotal rows.
- Unpivot date columns.
- Produce one normalized row for each:
  customer + date + product + quantity
- Preserve order references when available.
- Send flattened rows into the normal mapping and validation pipeline.

Do not assume the uploaded spreadsheet is already flat.

7. Validation

Validate normalized rows.

Quarantine rows with:

- Missing item code when an item code is required
- Missing product name
- Negative quantity
- Zero quantity where not permitted
- Invalid unit price
- Negative unit price
- Unparseable date
- Missing supplier for purchase-order or vendor-bill imports
- Missing customer for sales-order imports
- Missing required mapped fields

Do not silently drop invalid rows.

The invalid-row interface must show:

- Row number
- Original source values
- Normalized values
- Validation reason
- Search
- Filters
- Pagination
- CSV export of rejected rows

8. Import execution

Create queued jobs that:

- Process rows in chunks.
- Use database transactions.
- Avoid duplicate imports.
- Create or match suppliers.
- Create or match customers.
- Create or match items.
- Create purchase orders, invoices or sales orders according to source type.
- Create related line items.
- Populate price_history where supplier-price data exists.
- Record imported model IDs against import_rows.
- Update progress counters.

Use deterministic duplicate rules based on:

- Organization
- Source type
- Document number
- Date
- Supplier or customer
- Item
- Quantity
- Price

Do not create duplicate documents when the same file is uploaded again.

9. User experience

Use jQuery AJAX to:

- Check import status.
- Update progress bars.
- Preview mapped data.
- Filter invalid rows.
- Confirm final import.

Show clear statuses:

- Uploaded
- Analyzing
- Mapping required
- Ready to import
- Processing
- Completed
- Completed with errors
- Failed

10. Tests

Add tests proving:

- One organization cannot see another organization's imports.
- organization_id from the request is ignored.
- Invalid files are rejected.
- Saved mappings are organization-specific.
- Pivot spreadsheets are flattened.
- Invalid rows are quarantined.
- Duplicate files do not create duplicate records.
- Large imports are processed through queues.
```

---

# Prompt 3 — Core Procurement Schema

```text
Create the core procurement database schema, Eloquent models, relationships, policies, services, factories and seeders.

Every tenant-owned table must:

- Contain organization_id.
- Use the BelongsToOrganization trait.
- Have an organization foreign key.
- Have an organization index.
- Be protected through policies.
- Never accept organization_id through mass assignment from browser input.

Use consistent organization_id naming throughout the application.

1. Suppliers

Create suppliers:

- id
- organization_id
- name
- code nullable
- channel_type
- is_regional_reexporter boolean default false
- contact_person nullable
- email nullable
- phone nullable
- address nullable
- city nullable
- country nullable
- tax_number nullable
- payment_terms nullable
- lead_time_days nullable
- status default active
- notes nullable
- created_by
- timestamps
- soft deletes

channel_type enum:

- equipment
- chemistry_consumables
- packaging
- direct_import
- other

is_regional_reexporter identifies a UAE or regional company that re-exports goods into Saudi Arabia instead of being a local distributor.

Create:

- Supplier listing
- Supplier create/edit
- Supplier details page
- Supplier item-price history
- Supplier purchase-order history
- Supplier performance summary

2. Items

Create items:

- id
- organization_id
- item_code
- name_en
- name_ar nullable
- category nullable
- unit_of_measure
- description nullable
- requires_batch_tracking boolean default false
- reorder_lead_time_days nullable
- safety_stock_qty nullable
- minimum_stock_qty nullable
- status default active
- created_by
- timestamps
- soft deletes

Add a unique constraint on:

- organization_id
- item_code

Items requiring batch tracking may include:

- Food
- Dairy
- Frozen products
- Cosmetics
- Chemical consumables
- Pharma-adjacent consumables
- Other expiry-sensitive products

3. Price history

Create price_history:

- id
- organization_id
- item_id
- supplier_id
- unit_price decimal
- currency
- effective_date
- quantity_break nullable
- source_type nullable
- source_document_id nullable
- import_file_id nullable
- created_by nullable
- timestamps

Create an index on:

- organization_id
- item_id
- effective_date

Also create an index on:

- organization_id
- supplier_id
- effective_date

Price history must contain one row for every observed supplier price point.

Do not overwrite historical prices.

4. Purchase orders

Create purchase_orders:

- id
- organization_id
- supplier_id
- po_number
- status
- order_date
- expected_delivery_date nullable
- currency
- subtotal default 0
- tax_amount default 0
- discount_amount default 0
- total_amount default 0
- requested_by nullable
- created_by
- approved_by nullable
- approved_at nullable
- notes nullable
- timestamps
- soft deletes

Purchase-order status enum:

- draft
- pending_approval
- approved
- issued
- partially_received
- received
- cancelled
- closed

Create purchase_order_lines:

- id
- organization_id
- purchase_order_id
- item_id
- quantity
- unit_price
- tax_rate default 0
- discount_amount default 0
- line_total
- received_quantity default 0
- timestamps

Use a PurchaseOrderService to:

- Create purchase orders.
- Calculate totals.
- Update lines.
- Submit for approval.
- Prevent unauthorized status transitions.
- Record price history when appropriate.
- Record audit logs.

5. Invoices

Create invoices:

- id
- organization_id
- supplier_id
- purchase_order_id nullable
- invoice_number
- invoice_date
- due_date nullable
- currency
- subtotal default 0
- tax_amount default 0
- discount_amount default 0
- total_amount default 0
- status
- created_by
- verified_by nullable
- verified_at nullable
- notes nullable
- timestamps
- soft deletes

Invoice status enum:

- draft
- received
- under_review
- matched
- exception
- approved
- paid
- cancelled

Create invoice_lines:

- id
- organization_id
- invoice_id
- item_id
- quantity
- unit_price
- tax_rate default 0
- discount_amount default 0
- line_total
- timestamps

6. Sales orders and demand data

Create customers:

- id
- organization_id
- name
- code nullable
- email nullable
- phone nullable
- city nullable
- country nullable
- timestamps

Create sales_orders:

- id
- organization_id
- customer_id nullable
- sales_order_number
- order_date
- status nullable
- imported_from_odoo boolean default false
- import_file_id nullable
- timestamps

Create sales_order_lines:

- id
- organization_id
- sales_order_id
- item_id
- quantity
- unit_price nullable
- timestamps

Sales-order information will be used as a demand and consumption signal.

7. Stock batches

Create stock_batches:

- id
- organization_id
- item_id
- supplier_id nullable
- purchase_order_id nullable
- batch_number
- received_date
- manufacturing_date nullable
- expiry_date nullable
- quantity_received
- quantity_remaining
- unit_cost nullable
- warehouse_location nullable
- status default active
- timestamps

Batch status enum:

- active
- consumed
- expired
- disposed
- quarantined

Create an index on:

- organization_id
- item_id
- expiry_date

Create an InventoryBatchService.

It must:

- Receive stock into batches.
- Validate that quantity_remaining cannot exceed quantity_received.
- Apply FEFO consumption for batch-tracked items.
- Consume the earliest-expiring usable batch first.
- Prevent consumption from expired, disposed or quarantined batches.
- Record inventory movements.
- Handle partial consumption across multiple batches.

8. Inventory movements

Create inventory_movements:

- id
- organization_id
- item_id
- stock_batch_id nullable
- movement_type
- quantity
- reference_type nullable
- reference_id nullable
- movement_date
- performed_by nullable
- notes nullable
- timestamps

Movement types:

- receipt
- sale
- adjustment_in
- adjustment_out
- expired
- disposed
- return
- transfer

9. Audit logs

Create procurement_audit_logs:

- id
- organization_id
- user_id nullable
- action
- auditable_type
- auditable_id
- old_values JSON nullable
- new_values JSON nullable
- ip_address nullable
- user_agent nullable
- timestamps

Record sensitive actions including:

- Purchase-order creation
- Price changes
- Approval decisions
- Invoice verification
- Batch disposal
- User and role changes

10. Tests

Add tests for:

- Tenant isolation
- Item-code uniqueness per organization
- Purchase-order calculations
- Invoice calculations
- Price-history creation
- FEFO stock consumption
- Batch expiry validation
- Invalid status transitions
- Cross-organization relationship protection
```

---

# Prompt 4 — Procurement Analytics Dashboard

```text
Build a professional organization-scoped procurement analytics dashboard using Laravel Blade, Bootstrap 5, jQuery and Chart.js.

Do not use React, Vue, TypeScript or an SPA framework.

Create analytics using MySQL queries, Laravel query builders, service classes and, where beneficial, database views.

No machine learning is required at this stage.

1. Dashboard filters

Add filters for:

- Date range
- Supplier
- Category
- Item
- Purchase-order status
- Currency
- Warehouse location

Filters must:

- Be organization-scoped.
- Persist through query parameters.
- Work with normal page requests.
- Support AJAX chart refresh through jQuery.
- Never accept organization_id.

2. KPI cards

Display:

- Total spend
- Total purchase orders
- Active suppliers
- Pending approvals
- Total invoices
- Estimated savings
- Near-expiry stock value
- Waste value
- Contract compliance percentage

Each card should show:

- Current value
- Percentage change
- Previous-period comparison
- Relevant icon
- Click-through link

3. Spend analysis

Create reports for:

- Total spend by supplier
- Total spend by item category
- Total spend by month
- Total spend by item
- Total spend by channel type
- Average purchase-order value

Use:

- Line chart for monthly spend
- Doughnut chart for category spend
- Horizontal bar chart for supplier spend

4. Item price trends

For a selected item:

- Show price history over time.
- Show separate lines per supplier.
- Show latest price.
- Show lowest historical price.
- Show highest historical price.
- Show percentage change.
- Show currency.
- Show associated source document where available.

5. Volume-discount detection

Using purchase_order_lines and price_history:

- Group purchases by item and supplier.
- Compare ordered quantities against unit prices.
- Detect cases where larger quantities have historically resulted in lower unit prices.
- Calculate estimated discount opportunity.
- Show evidence from previous purchase orders.
- Do not present a recommendation without supporting data.

6. Supplier comparison

For a selected item:

- Show every supplier that sold the item.
- Show each supplier's most recent price.
- Show average historical price.
- Show lowest observed price.
- Show lead time.
- Show local or regional re-export status.
- Highlight the lowest eligible supplier.
- Calculate potential savings against the currently selected supplier.

7. Reorder-point suggestions

Calculate:

average consumption rate × lead time + safety stock

Use:

- sales_order_lines
- inventory_movements
- stock_batches.quantity_remaining
- item lead time
- item safety stock

Allow calculation periods such as:

- Last 30 days
- Last 60 days
- Last 90 days
- Custom date range

Show:

- Current available stock
- Average daily consumption
- Lead time
- Safety stock
- Suggested reorder point
- Suggested reorder quantity
- Estimated stock-out date

8. Expiry-risk report

For items where requires_batch_tracking is true:

- List active stock batches expiring within a configurable number of days.
- Default the warning window to 14 days.
- Allow organization-level configuration.
- Show batch number.
- Show item.
- Show expiry date.
- Show days remaining.
- Show quantity remaining.
- Show latest known unit price.
- Calculate estimated at-risk value.
- Highlight expired and critically near-expiry batches.

Use status colors:

- Red: expired or 0–3 days
- Orange: 4–7 days
- Yellow: 8–14 days
- Green: outside the warning window

9. Waste tracking

Create waste_events:

- id
- organization_id
- stock_batch_id
- item_id
- quantity_wasted
- estimated_value
- disposed_at
- reason
- created_by
- timestamps

Waste reasons:

- expired
- damaged
- contamination
- quality_failure
- temperature_issue
- overstock
- other

When a batch is marked expired or disposed:

- Create a waste event.
- Create an inventory movement.
- Decrease available stock.
- Record an audit log.
- Use a database transaction.

10. Re-export markup detection

For each item:

- Compare regional re-export suppliers against local non-re-export suppliers.
- Detect when a local supplier has a lower comparable price.
- Calculate the price difference.
- Calculate the percentage markup.
- Calculate estimated savings for typical order quantity.
- Surface this as a re-export markup recommendation.
- Keep it separate from general supplier comparison recommendations.

11. Dashboard tables

Create:

- Recent purchase orders
- Pending approvals
- Top suppliers by spend
- Price-drop opportunities
- Near-expiry batches
- Waste summary
- Recent recommendations
- Contracts renewing soon

All tables must include:

- Search
- Filters
- Sorting
- Pagination
- Responsive layout
- Export to CSV where appropriate

12. Performance

Add indexes for analytics hot paths.

Use:

- Eager loading
- Aggregated queries
- Cached dashboard statistics
- Organization-specific cache keys
- Queued analytics recalculation where required

Never cache results under a shared key that could expose one organization's data to another.

13. Tests

Test:

- Tenant-scoped analytics
- Date filters
- Spend calculations
- Supplier-price comparison
- Reorder calculations
- Expiry-risk calculations
- Waste-event creation
- Re-export markup detection
- Organization-specific cache isolation
```

---

# Prompt 5 — Configurable Approval Engine

```text
Build a reusable, configurable approval-matrix system for the Laravel procurement application.

Do not hardcode approval amounts or approvers.

Every organization must manage its own approval configuration.

1. Approval rules

Create approval_rules:

- id
- organization_id
- name
- request_type
- category nullable
- minimum_amount nullable
- maximum_amount nullable
- approver_role
- sequence_order
- is_parallel boolean default false
- escalation_hours nullable
- is_active boolean default true
- created_by
- timestamps

Supported request types:

- purchase_order
- procurement_request
- budget_exception
- off_catalog_purchase
- supplier_onboarding
- invoice_exception

Approver roles:

- approver
- procurement_manager
- finance
- admin

Rules must support:

- Amount thresholds
- Optional category scoping
- Sequential approval
- Parallel approval
- Multiple approval levels
- High-value approval rules
- Organization-specific configuration

2. Approval requests

Create approvals:

- id
- organization_id
- request_type
- request_id
- approval_rule_id nullable
- sequence_order
- requested_by
- approver_id nullable
- approver_role
- status
- decision_comment nullable
- decided_at nullable
- delegated_from nullable
- escalated_from nullable
- due_at nullable
- timestamps

Approval status enum:

- pending
- approved
- rejected
- returned
- delegated
- escalated
- cancelled
- skipped

Use a Laravel morph map or controlled request_type mapping so the system can resolve the underlying request safely.

Do not allow arbitrary class names from request payloads.

3. Approval workflow service

Create ApprovalWorkflowService.

It must:

- Find matching approval rules.
- Order rules by sequence.
- Create approval steps.
- Support parallel approvers.
- Activate only the correct approval level.
- Advance after required approvals are completed.
- Stop the workflow after rejection.
- Return a request for revision.
- Update the underlying request status.
- Record every action in audit logs.
- Dispatch notification events.

4. Approval delegation

Create approval_delegations:

- id
- organization_id
- user_id
- delegate_user_id
- starts_at
- ends_at
- reason nullable
- is_active boolean default true
- timestamps

Delegation rules:

- User and delegate must belong to the same organization.
- Delegate must have a suitable approval role.
- Delegation must only apply during the configured date range.
- The original approver must remain visible in the audit trail.
- Delegation must not grant unrelated organization access.

5. Time-based escalation

Create a scheduled command that:

- Runs every five minutes.
- Finds overdue pending approvals.
- Checks escalation_hours.
- Escalates to the configured next role.
- Prevents duplicate escalation.
- Records an audit entry.
- Sends a notification.

Use Laravel Scheduler and queued jobs.

6. Approval inbox

Create an Approval Inbox Blade page.

Include:

- Pending approvals assigned to the current user
- Delegated approvals
- Escalated approvals
- Completed decisions
- Filters by type, status, requester and date
- Search
- Pagination
- Approval details modal or page
- Supporting documents
- Approval history timeline

Actions:

- Approve
- Reject
- Return for revision
- Delegate

Require a comment for:

- Reject
- Return for revision
- Delegate

7. Purchase-order integration

When a purchase order is submitted:

- Resolve applicable approval rules.
- Create the approval workflow.
- Change status to pending_approval.
- Prevent issuing the PO before approval.
- Change status to approved after all required approvals.
- Record approved_by and approved_at where applicable.

8. Authorization and concurrency

Ensure:

- Only the assigned active approver can act.
- Users cannot approve their own request unless an organization rule explicitly allows it.
- A completed approval cannot be decided again.
- Concurrent approval attempts are handled safely.
- Use transactions and row locking for decisions.
- Cross-organization approval access is impossible.

9. Tests

Test:

- Amount-based rules
- Category-specific rules
- Sequential approvals
- Parallel approvals
- Delegation
- Escalation
- Rejection
- Return for revision
- Unauthorized approval attempts
- Duplicate decisions
- Concurrent decisions
- Cross-organization protection
```

---

# Prompt 6 — Recommendations, Feedback and Savings Tracking

```text
Build a deterministic procurement recommendation, feedback and savings-tracking system.

Do not build machine-learning retraining yet.

The architecture should allow recommendations to be generated by:

- Laravel system rules
- Claude or another AI provider later

1. Recommendations

Create recommendations:

- id
- organization_id
- item_id nullable
- supplier_id nullable
- recommendation_type
- title
- suggested_action
- explanation
- evidence_json nullable
- estimated_savings nullable
- currency default SAR
- priority default medium
- status default open
- generated_at
- generated_by
- expires_at nullable
- timestamps

recommendation_type enum:

- reorder
- supplier_switch
- discount_opportunity
- expiry_risk
- reexport_markup
- budget_alert

generated_by enum:

- system
- claude

priority enum:

- low
- medium
- high
- critical

status enum:

- open
- accepted
- rejected
- modified
- expired
- completed

2. Recommendation decisions

Create recommendation_decisions:

- id
- organization_id
- recommendation_id
- decision
- reason_code
- reason_text nullable
- alternative_action nullable
- alternative_supplier_id nullable
- alternative_quantity nullable
- alternative_price nullable
- decided_by
- decided_at
- timestamps

decision enum:

- accepted
- rejected
- modified

reason_code must be a fixed enum:

- price
- quality
- lead_time
- relationship
- budget
- stock_out_risk
- compliance
- waste_avoidance
- one_off

Do not use free text as the only reason.

3. Learned preferences

Create learned_preferences:

- id
- organization_id
- preference_type
- item_id nullable
- supplier_id nullable
- category nullable
- rule_json
- created_from_decision_id nullable
- confidence_count default 1
- is_active boolean default true
- timestamps

Examples:

- Never recommend Supplier Y for Category Z.
- Prefer Supplier X for Item A.
- Do not suggest reordering Item B while near-expiry stock exists.
- Minimum acceptable lead time for Category C.
- Supplier excluded due to compliance restrictions.

Create a LearnedPreferenceService.

It must:

- Review repeated recommendation rejections.
- Create deterministic preference rules after configurable repetition.
- Increase confidence_count when the same reason repeats.
- Apply active preferences before new recommendations are produced.
- Allow admins to review, edit, disable or delete learned preferences.
- Keep the original decision reference.

Do not implement model training.

4. Savings log

Create savings_log:

- id
- organization_id
- recommendation_id nullable
- savings_type
- amount_sar
- estimated_amount_sar nullable
- realized boolean default false
- realized_at nullable
- source_type nullable
- source_id nullable
- notes nullable
- logged_by nullable
- timestamps

savings_type enum:

- price
- contract
- waste_avoidance

Savings examples:

Price:
- Bought from a cheaper supplier.
- Negotiated a lower unit price.
- Used a volume discount.

Contract:
- Renegotiated contract rates.
- Avoided contract renewal increase.

Waste avoidance:
- Used near-expiry stock before ordering more.
- Used FEFO to prevent expiry.
- Transferred stock to avoid disposal.
- Reduced an order because sufficient stock existed.

5. Outcome tracking

Create recommendation_outcomes:

- id
- organization_id
- recommendation_id
- decision_id nullable
- recommended_supplier_id nullable
- recommended_price nullable
- recommended_quantity nullable
- actual_supplier_id nullable
- actual_price nullable
- actual_quantity nullable
- financial_difference nullable
- outcome
- evaluated_at nullable
- notes nullable
- timestamps

Outcome values:

- cheaper_than_recommendation
- same_as_recommendation
- more_expensive_than_recommendation
- avoided_waste
- stock_out_occurred
- unknown

When enough later data exists:

- Compare actual procurement activity with the recommendation.
- Calculate whether the chosen action was cheaper or more expensive.
- Store the outcome.
- Do not retrain any AI model.

6. Recommendation generation

Create RecommendationGenerationService with separate rule classes for:

- Reorder recommendations
- Supplier-switch recommendations
- Discount opportunities
- Expiry-risk recommendations
- Re-export markup recommendations
- Budget alerts

Each rule must:

- Use organization-scoped data.
- Include supporting evidence.
- Apply learned preferences.
- Avoid duplicate active recommendations.
- Calculate estimated savings when possible.
- Set priority based on financial and operational risk.

7. Recommendation interface

Create:

- Recommendation list
- Recommendation details
- Recommendation evidence section
- Accept action
- Reject action
- Modify action
- Decision history
- Savings confirmation
- Learned-preferences management

Use Bootstrap cards, status badges and confirmation modals.

Filters:

- Type
- Priority
- Status
- Supplier
- Item
- Date
- Generated by

8. Dashboard metrics

Display:

- Open recommendations
- Accepted recommendations
- Estimated savings
- Realized savings
- Waste avoided
- Acceptance rate
- Recommendations by type
- Top rejection reasons

9. Tests

Test:

- Recommendation generation
- Duplicate prevention
- Learned-preference application
- Fixed decision reasons
- Outcome comparison
- Realized savings
- Waste-avoidance savings
- Cross-organization isolation
```

---

# Prompt 7 — Twilio WhatsApp Approval Notifications

```text
Add Twilio WhatsApp Business notifications to the existing Laravel approval engine.

Reuse the ApprovalWorkflowService created previously.

Do not duplicate approval rules or decision logic inside the webhook controller.

1. Configuration

Add environment configuration for:

- TWILIO_ACCOUNT_SID
- TWILIO_AUTH_TOKEN
- TWILIO_WHATSAPP_NUMBER
- TWILIO_MESSAGING_SERVICE_SID nullable
- APP_URL

Create a TwilioWhatsAppService.

It must:

- Send approved WhatsApp templates.
- Send session messages where allowed.
- Log Twilio message IDs.
- Handle errors.
- Use queued jobs.
- Avoid exposing Twilio credentials.
- Support organization-specific routing.

2. Organization settings

Create organization_notification_settings:

- id
- organization_id
- whatsapp_enabled boolean default false
- whatsapp_mode default shared
- twilio_whatsapp_number nullable
- high_value_approval_threshold nullable
- default_country_code nullable
- expiry_alert_days default 14
- timestamps

whatsapp_mode:

- shared
- dedicated

Default to one shared WhatsApp Business number with organization routing.

Allow dedicated organization numbers later.

3. User WhatsApp authorization

Add or create organization_user_contacts:

- id
- organization_id
- user_id
- whatsapp_number
- whatsapp_verified_at nullable
- is_whatsapp_enabled boolean default true
- timestamps

Normalize phone numbers to E.164 format.

A WhatsApp approval action is only valid when:

- The phone number belongs to an active user.
- The user belongs to the correct organization.
- The user is the assigned approver or authorized delegate.
- The approval is still pending.
- The request belongs to the same organization.

Never trust the WhatsApp button payload by itself.

4. Message templates

Support templates for:

- Purchase-order approval request
- Budget alert
- Contract expiry
- Near-expiry stock alert
- Price-drop alert
- Approval escalation
- Approval decision confirmation

Store template configuration in the database or organization settings.

Template variables may include:

- Organization name
- Request number
- Supplier
- Amount
- Currency
- Requester
- Due date
- Approval URL
- Expiry date
- Item
- Batch number

Respect WhatsApp's 24-hour customer-service window.

All business-initiated messages outside the active window must use approved templates.

5. Notification records

Create whatsapp_messages:

- id
- organization_id
- user_id nullable
- approval_id nullable
- direction
- message_type
- template_name nullable
- twilio_message_sid nullable
- from_number
- to_number
- body nullable
- status
- error_code nullable
- error_message nullable
- sent_at nullable
- delivered_at nullable
- read_at nullable
- timestamps

Directions:

- inbound
- outbound

Statuses:

- queued
- sent
- delivered
- read
- failed
- received

6. Twilio webhook

Create public webhook routes for:

- Incoming WhatsApp messages
- Interactive approval responses
- Message-status callbacks

Exclude only these webhook routes from CSRF, while keeping the rest of the application protected.

Verify the Twilio signature on every request.

Reject:

- Invalid signatures
- Unknown phone numbers
- Unauthorized organization users
- Expired approval actions
- Reused tokens
- Invalid approval IDs
- Cross-organization actions

Use a thin controller that passes the verified request to service classes.

7. Standard-value approval action

For approvals below the organization's high-value threshold:

- Confirm the sender's identity.
- Confirm authorization.
- Confirm approval status.
- Process approve or reject through ApprovalWorkflowService.
- Use a transaction and row lock.
- Record the audit log.
- Send a confirmation message.

Require a reason for rejection.

8. High-value approval guardrail

For purchase orders above the organization's configurable high-value threshold:

Do not approve directly from a WhatsApp button.

Instead:

- Create a cryptographically secure one-time token.
- Store only its hash.
- Associate it with the approval and user.
- Add an expiry time.
- Generate a temporary signed Laravel URL.
- Send the secure link through WhatsApp.
- Require the user to authenticate in the Laravel application.
- Confirm the logged-in user matches the token user.
- Show full purchase-order information.
- Require final confirmation.
- Mark the token used after the decision.

Create approval_confirmation_tokens:

- id
- organization_id
- approval_id
- user_id
- token_hash
- expires_at
- used_at nullable
- created_at

The token must:

- Be single use.
- Expire quickly.
- Be invalid after the approval has been decided.
- Be tied to one user and one approval.
- Never be stored in plain text.

Do not remove this high-value guardrail.

9. Other alerts

Create queued notification jobs for:

Contract expiry:
- Notify configured users before expiry.

Near-expiry stock:
- Use the expiry-risk report.
- Include item, batch, expiry date, quantity and estimated value.

Price drop:
- Notify procurement managers when a meaningful supplier price drop is detected.

Budget alert:
- Notify finance and approvers when thresholds are exceeded.

Prevent duplicate alerts for the same record and warning period.

10. Shared-number routing

For the default shared WhatsApp number:

- Determine the organization from the approval, token or verified user mapping.
- Never route solely by a user-provided organization code.
- Confirm every referenced record belongs to the resolved organization.
- Include organization context in queued jobs.

Document how dedicated per-organization numbers can be enabled later.

11. Scheduler

Schedule:

- Approval escalation checks every five minutes
- Contract-expiry checks daily
- Stock-expiry checks daily
- Price-drop checks daily
- Failed WhatsApp-message retry processing

Use Laravel Scheduler and queued jobs.

12. Tests

Test:

- Valid Twilio signatures
- Invalid Twilio signatures
- Unknown phone numbers
- Unauthorized users
- Cross-organization requests
- Normal-value approvals
- High-value approval links
- Expired tokens
- Reused tokens
- Wrong authenticated user
- Duplicate webhook delivery
- Rejection reason requirement
- Shared-number organization routing
- WhatsApp status callbacks
```

---

# Final Cursor Instructions

After completing each prompt:

1. Run all migrations.
2. Run all relevant tests.
3. Fix errors before moving to the next prompt.
4. Confirm tenant isolation tests pass.
5. List every file created or changed.
6. List every migration created.
7. List every route added.
8. List any Composer package installed.
9. Explain how to test the completed module manually.
10. Do not proceed to the next prompt automatically unless explicitly instructed.

Use clean Laravel architecture:

* Controllers should remain thin.
* Form Requests should handle validation.
* Policies should handle authorization.
* Services should handle business logic.
* Jobs should handle background processing.
* Events and listeners should handle side effects.
* Models should define relationships and scopes.
* Blade components should handle repeated interface elements.

Do not replace existing working code unnecessarily.

Do not provide only sample code or pseudo-code. Implement production-ready migrations, models, controllers, services, policies, requests, jobs, routes, Blade views, JavaScript, tests, factories and seeders.

---

# Open Items Requiring Real Data

The following information will still be required before validating the system against actual client data:

1. Actual Odoo Purchase Order CSV/XLSX column names.
2. Actual Odoo Vendor Bill CSV/XLSX column names.
3. Categories requiring batch and expiry tracking.
4. Pilot organizations' reorder lead times.
5. Safety-stock rules.
6. Organization-specific approval thresholds.
7. Twilio-approved WhatsApp template names.
8. Current WhatsApp pricing and conversation rules.
9. Shared-number versus dedicated-number requirements.
10. Supplier and purchase-price data required to populate price history.
