# Business Rules

## Application Workflows

### Lead Lifecycle

```
[Raw Lead] -> [Verified Lead] -> [Client]
                |
                v
         [Not Interested]
                |
                v
          [Call Back]
```

- Leads are created as "raw" (default status)
- `POST /lead/verified/convert` - Converts raw to verified (sets `cnvt_to_verified_at`)
- `POST /lead/client/all/fetch` - Clients (further conversion; `cnvt_to_client_at` tracked)
- `POST /lead/not_interest/convert` - Marks as not interested
- `POST /lead/call_back/convert` - Creates callback entry
- Leads can be claimed, transferred (single/bulk), shared, and prioritized
- Lead phone numbers are normalized in `lead_phones` table (supports multiple phones per lead)
- Lead emails are normalized in `lead_emails` table (supports multiple emails per lead)

### Meeting Lifecycle

```
[Created] -> [Arrived] -> [Started] -> [OTP Sent] -> [OTP Verified] -> [Completed]
                                                                             |
                                                                        [Interested/Not Interested]
                              [Cancelled] (any stage)
```

**Status flow rules:**
1. `meeting_arrived`: Lead arrives within 30-minute window of scheduled time
2. `meeting_started`: Meeting started with GPS coordinates (`start_lat`, `start_long`)
3. `meeting_otp_send`: Sends 4-digit OTP to lead's phone number (country_code + phone)
4. `meeting_otp_verify`: Verifies OTP; clears OTP fields after success
5. `meeting_completed`: Marks completion with interest status, reason, end GPS
6. `meeting_cancel`: Can be cancelled at any stage with reason
7. Active user tracked via `meeting_users.is_active`
8. Rescheduling creates new `meeting_schedule` record (history preserved)

**OTP rules:**
- 4-digit random (1000-9999)
- Expires in 5 minutes
- Sent via SMS API to lead's phone number
- Cleared after successful verification (one-time use)

### Demo Lifecycle

```
[Created] -> [Scheduled] -> [Started] -> [Completed]
                                       -> [Cancelled]
```

- Demos integrate with Google Calendar (optional)
- Google Meet link generated for online demos
- Attendee responses synced via Google Calendar API (`syncAttendeeResponses`)
- Multiple assignees, products, and schedules supported

### Sales & Finance Flow

1. Sale created on a lead (`/lead/sale/create`)
2. Sale has approval status: pending -> approved/rejected
3. Finance team approves via `/finance/sale/approve`
4. Bill raised via `/finance/sale/bill/mark`
5. Payments recorded via `/lead/sale/payment/create`
6. Payment status auto-synced: `unpaid` -> `partial` -> `paid` (via `syncPaymentStatus()`)
7. Incentives can be manually overridden on sales (`/lead/sale/incentive/update`, requires `report_management.sales.override`)

### Sale Incentive Slabs

- Slabs are managed via `/incentive/slab/{create,update,delete,fetch}` and are scoped to a single product **and** a single user (`user_id` is required) — there is no product-wide slab that applies to all salespeople.
- A slab also defines a `min_quantity`/`max_quantity` and `min_price`/`max_price` range (nullable max = unbounded).
- When a sale is created or its price/quantity/product is edited, `SaleIncentiveService::findMatchingSlab()` looks for an active slab matching the sale's `product_id`, `created_by` (the salesperson), and whose ranges contain the sale's quantity and price. If multiple slabs match, the narrowest combined range wins (ties broken by lowest id).
- If no slab matches (including when no slab exists for that user at all), the sale's `incentive` is `0` and `incentive_slab_id` is `null`.
- The matched slab's `incentive_percentage` is applied to the sale's `total` to compute `incentive`, and `incentive_slab_id`/`incentive_percentage` are stored on the `lead_sales` row.

### Payment Reminders

1. A sale is a reminder candidate while `payment_status` is `unpaid` or `partial` and not deleted
   — regardless of age. Every candidate is annotated with two independent escalation flags:
   - `is_overdue` — the sale's last activity (most recent `SalePayment.payment_date`, or
     `LeadSale.sale_date` if nothing has been paid yet) is more than 2 months old.
   - `credit_term_exceeded` — the sale's company is currently over its `credit_limit`
     (`Company::is_credit_limit_exceeded`) AND has been over it for longer than the company's
     `credit_duration_days` credit term (falls back to 60 days if unset). "How long over limit" is
     approximated as the oldest last-activity date among the company's unpaid/partial sales.
2. `POST /lead/sale/reminder/fetch` lists all candidates, each annotated with `due_amount`,
   `last_payment_at`, `days_since_last_payment`, `is_overdue`, `credit_term_exceeded`, and the
   company's credit context (`credit_limit`, `credit_duration_days`, `outstanding_balance`,
   `credit_remaining`, `is_credit_limit_exceeded`) for reference (see `Company` credit accessors
   under Company/Finance rules). An optional `bucket=current|overdue|credit_exceeded` request
   param filters to one of the three groups.
3. `SendPaymentRemindersJob` runs daily (09:00) and pushes an FCM reminder to both the lead's
   assigned user (`Lead.assigned_to`) and the sale's assigned/sales-by user, deduplicated per
   recipient. Message wording escalates by flag: routine "Payment Reminder" when neither flag is
   set, "Overdue Payment Reminder" when `is_overdue`, "Credit Limit Overdue" when
   `credit_term_exceeded`. Each send is logged in `scheduled_notification_logs` with
   `reminder_type = 'payment_reminder'`, keyed by `(user_id, notifiable_type=LeadSale,
   notifiable_id=sale_id, reminder_at=today)` so a given sale/recipient pair is reminded at most
   once per day.

### Leave Management

1. Leave types defined (Sick, Casual, Annual, etc.)
2. Leave entitlements assigned to users per period
3. User applies for leave (`/leave/application/create`)
4. Manager approves/rejects (`/leave/application/approve` or `/leave/application/reject`)
5. Approval recorded in `leave_approvals` table
6. Attendance system respects approved leaves

### Attendance Rules

- Check-in and check-out with GPS coordinates and photo
- Boolean status flags (mutually exclusive):
  - `is_present`, `is_absent`, `is_holiday`, `is_weekoff`, `is_halfday`, `is_late`
- Backfill service auto-marks attendance based on:
  - Holidays (from holidays table)
  - Week-offs (from user_working_days)
  - Approved leave applications
  - Remaining: marked absent
- Week-off is always derived per-user from `user_working_days.day_index` (`App\Support\AttendanceSchedule::isWeekOff`),
  never a hardcoded Saturday/Sunday check. A user with no working-days schedule configured is never
  auto-marked as week-off. When an attendance record already exists for the day, its stored
  `is_present`/`is_absent`/etc. always win over the schedule-derived flag in the formatted API response
  (`FormatsEntityData::formatAttendanceData`) — e.g. checking in on a day nominally outside the schedule
  still reports `status: "present"`, not `"weekoff"`.
- Location tracking during check-in/out period
- Late/halfday determined by shift settings
- Check-in is still allowed on a day with an approved leave (`attendance.leave_type_id` set): the
  punch just records `check_in_time`/lat/lng/image/remarks on top of the existing record. It does
  **not** clear `leave_type_id` or flip `is_present`/`is_absent` — those stay whatever the approved
  leave set them to, since leave applications have no full-day/half-day distinction and the leave
  status must not be silently overwritten by a punch-in.

### Task Management

- Tasks can be assigned to users (via `assigned_to` field)
- Support recurring tasks: daily, weekly, fortnightly, monthly, quarterly, half-yearly, yearly
- Tasks have queries (Q&A thread between assignee and creator)
- Task stages trackable
- Products can be linked to tasks

### Campaign & Marketing Lifecycle

```
[Draft] -> [Scheduled] -> [Running] -> [Completed]
                                           |
                                      [Failed] (unrecoverable job error)
```

- A campaign sends a provider-approved template across SMS, RCS, and/or WhatsApp.
- `strategy: parallel` sends via every enabled channel to each recipient (recipient marked `sent` if any
  channel succeeds); `strategy: failover` tries channels in `priority` order, stopping at first success.
- Audience comes from a reusable Lead Group (preferred), a one-off `lead_filters` object, or an Excel
  upload — there is no way to manually type in a single ad-hoc recipient.
- Template variables (`{{1}}`, `{{2}}`...) resolve **per channel** — the same variable name can
  independently resolve to different values on SMS vs. RCS vs. WhatsApp within one campaign, via
  `campaign_variable_mappings.channel_type`.
- Launching requires a persistently running queue worker (`php artisan queue:work --queue=campaigns`) — a
  "launched" API response only means the job was queued, not that anything has sent yet.
- `sent` means the provider **accepted** the message; it does not mean it was delivered. There is no
  webhook receiver for provider delivery reports, so `delivered_count` reads `0` in practice — real
  delivery/rejection status must be checked on the provider's own dashboard.
- Pause/Resume/Stop endpoints exist but currently return `400` ("temporarily unavailable") — the execution
  job never re-checks campaign status mid-run, so it can't yet safely interrupt an in-progress send.

Full detail (endpoints, per-channel config quirks, known provider failure codes): see
[campaign-management.md](campaign-management.md).

### Lead Groups

- A Lead Group is a reusable, named list of leads built from explicit `lead_ids` (multi-select) — not a
  live filter. Membership is a fixed snapshot, set at creation and adjusted via add/remove-members calls.
- Managed under Lead Management (`/api/lead/group/*`); consumed by campaigns via `POST
  /api/campaign/select_lead_group` (a dedicated post-creation step, not part of campaign create/update).
- Deleting a group only soft-deletes it and never retroactively affects recipients a campaign already
  imported from it.

## User Roles & Permissions

### Permission Structure

Format: `{module}.{sub_module}.{action}`

| Module | Sub-modules | Actions |
|--------|-------------|---------|
| `account_management` | account, document | create, read, update, delete, override |
| `task_management` | task | create, read, update, delete, override |
| `project_management` | project | create, read, update, delete, override |
| `user_management` | user, live_location | create, read, update, delete, override, export, contact |
| `lead_management` | company, raw_lead, verified_lead, client, lead_group | create, read, update, delete, override, export, contact |
| `quotation_management` | quotation, terms, email_body | create, read, update, delete, override, export |
| `meeting_management` | meeting | create, read, update, delete, override, export |
| `demo_management` | demo | create, read, update, delete, override, export |
| `visit_management` | visit | create, read, delete, export, override |
| `payroll_management` | payroll | create, read, update, delete, override, export |
| `product_management` | product | create, read, update, delete, override, export |
| `leave_management` | leave_type, leave_entitlement, leave_application | create, read, update, delete, override |
| `holiday_management` | holiday | create, read, update, delete, override |
| `finance_management` | finance | read, update, delete, override, export |
| `marketing_management` | templates, campaign | create, read, update, delete, override (campaign also has launch, pause, resume, stop — though pause/resume/stop are currently disabled at the controller level, see Campaign Lifecycle above) |
| `report_management` | sales, attendances, calls, activity_log, client_analytics, user_analytics, campaign | read, update, delete, override, export, create |
| `setting` | role, smtp_credential, sms_credential, waba_credential, rcs_credential, payment_type, api_credentials, payslip_signature | create, read, update, delete, override |

### "override" Permission
Acts as a super-admin permission for the given sub-module, bypassing ownership/data-scoping checks (e.g., viewing all leads vs. only assigned leads).

### "contact" Permission (Lead Management)
Allows viewing contact details (phone numbers, emails) of leads. Users without this see masked phone numbers.

## Notifications

- **Push notifications** via Firebase Cloud Messaging (FCM HTTP v1)
- Token stored in `users.fcm_token`
- Notifications triggered for upcoming activities (meetings, demos, follow-ups, callbacks)
- `DispatchUpcomingActivityNotifications` job runs every minute, checks activities within next 15 minutes
- `ScheduledNotificationLog` tracks delivery status
- Activity logging via Spatie Activitylog for audit trail

## Google Calendar Integration

- Two service implementations: `GoogleCalendarService` and `OptimizedGoogleCalendarService`
- Optimized version features: circuit breaker (5 failures = 5min cooldown), token caching (1h TTL), exponential backoff (3 retries)
- Used for demo creation/update/delete/sync
- Credentials via `credentials.json` + stored tokens in `google_tokens`/`google_access_tokens` tables
- Default timezone: `Asia/Kolkata`

## Quick Actions

These are dashboard/shortcut operations:
- Lead claiming: First user to claim gets ownership
- Lead sharing: Share lead with another user
- Impersonation: Admin can impersonate another user (for troubleshooting)
- Bulk operations: Transfer, delete leads in bulk

## Analytics Reports Available

- Dashboard counts (leads, meetings, tasks, projects, demos, visits, sales)
- Call logs chart data
- Sales chart data
- Aggregated lead sales
- Attendance reports (aggregated + detailed)
- User activity logs
- User KPI tracking (calls, meetings, visits, demos, sales, notes, callbacks, follow-ups, quotations, lead conversions)
- Timesheet data per user
- Lead analytics
- User analytics

## Assumptions & Constraints

- **Single timezone**: Asia/Kolkata (Indian market CRM)
- **Indian mobile numbers**: Country code + phone, SMS via Indian provider (Bluewaves Media)
- **Fiscal year**: Supports both calendar (Jan-Dec) and fiscal year (custom start month)
- **No multi-language support**: English only
- **No soft deletes on all tables**: Some tables hard-delete, others soft-delete
- **Phone uniqueness**: `lead_phones.phone` has unique constraint
- **Phone masking**: For users without `contact` permission, phone numbers show `******`
