# Server-Side Filtering Design

**Date:** 2026-06-24  
**Scope:** Add server-side filtering to five API areas, backed by the existing `FilterService`.

---

## Goals

1. Owner promo-codes list (`GET /promo-codes`) — filter by validity state, discount type, date range, and code search.
2. Admin geo/config lists (`GET /countries`, `/cities`, `/regions`, `/currencies`) — filter by search, parent IDs, and active state.
3. Owner revenue endpoints (`GET /owner/revenue/*`) — filter by period, building, transaction type, payment method, and collected-vs-outstanding.
4. New global admin revenue endpoint (`GET /admin/revenue`) — list platform transactions with totals/pending approvals.
5. Owner calendar endpoint (`GET /owner/calendar`) — filter by availability status and region.

All filter logic lives in `App\Services\Filter\FilterService`; controllers only apply role/owner scoping and return responses.

---

## Assumptions

- Auto-permission mode is active; implementation proceeds after this spec.
- Existing `PromoCodeController` is at `app/Http/Controllers/PromoCodeController.php` and is already routed as `promo-codes` under the owner middleware.
- `Owner/RevenueController` and `Owner/CalendarController` do not exist and will be created.
- `Admin/RevenueController` will be created at the user-specified path `app/Http/Controllers/Admin/RevenueController.php`.
- Owner-scoping uses `auth()->user()->owner->id` (or `employee->owner_id` where applicable).
- Derived validity state for promo codes is computed from `starts_at`, `expires_at`, `usage_limit`, and `uses_count`:
  - `active`: current datetime is between `starts_at` and `expires_at` (or no expiry) and not exhausted.
  - `expired`: current datetime is past `expires_at`, or usage exhausted.
  - `upcoming`: current datetime is before `starts_at`.

---

## FilterService extensions

Add these public methods to `App\Services\Filter\FilterService`:

```php
public function applyToPromoCodeQuery(Builder $query, array $filters): Builder
public function applyToCountryQuery(Builder $query, array $filters): Builder
public function applyToCityQuery(Builder $query, array $filters): Builder
public function applyToRegionQuery(Builder $query, array $filters): Builder
public function applyToCurrencyQuery(Builder $query, array $filters): Builder
public function applyToOwnerRevenueQuery(Builder $query, array $filters): Builder
public function applyToAdminRevenueQuery(Builder $query, array $filters): Builder
public function applyToOwnerCalendarQuery(Builder $query, array $filters): Builder
```

### 1. Promo code filters

- `status` → `active`, `expired`, `upcoming`
- `discount_type` → `percentage`, `fixed`
- `starts_from`, `starts_until` → date range on `starts_at`
- `expires_from`, `expires_until` → date range on `expires_at`
- `search` → `like` on `code`

### 2. Geo/config filters

- `CountryController::index`: `search` (name), `currency_id`
- `CityController::index`: `search` (name), `country_id`, `is_active`
- `RegionController::index`: `search` (name), `city_id`, `is_active`, `per_page`
- `CurrencyController::index`: `search` (name/code)

### 3. Owner revenue filters

The owner revenue query starts from `Transaction` joined through `invoice.reservation.unit.building` scoped to the owner.

- `period` → preset: `today`, `week`, `month`, `year`, `last_30_days`, `last_90_days`, `last_12_months`
- `from`, `to` → explicit date range (overrides `period`)
- `building_id` → filter by building
- `transaction_type` → `payment`, `payout`
- `type` → `payment`, `refund`
- `payment_method` → `cash`, `card`, `wallet`
- `collected_status` → `collected` (paid invoices), `outstanding` (remaining > 0)

Endpoints:
- `GET /owner/revenue` → paginated transactions
- `GET /owner/revenue/summary` → totals and outstanding
- `GET /owner/revenue/chart` → time-series revenue data

### 4. Admin revenue filters

Global `Transaction` query, admin-scoped via `role:admin` middleware.

- `period`, `from`, `to`
- `building_id`
- `transaction_type`, `type`, `payment_method`
- `search` → customer name / invoice number / transaction id

Endpoint:
- `GET /admin/revenue` → paginated transactions + summary totals + pending approvals count

### 5. Owner calendar filters

Query starts from `UnitAvailability` scoped to the owner through `unit.building.owner_id`.

- `month`, `year` → required-ish; default to current month/year
- `building_id` → filter by building
- `unit_id` → filter by unit
- `status` → `available`, `blocked`, `booked`
- `region_id` → filter by building region

---

## New and modified files

### Modified
- `app/Services/Filter/FilterService.php` — add filter methods
- `app/Http/Controllers/PromoCodeController.php` — filter `index()`
- `app/Http/Controllers/CountryController.php` — filter `index()`
- `app/Http/Controllers/CityController.php` — filter `index()`
- `app/Http/Controllers/RegionController.php` — filter `index()`
- `app/Http/Controllers/CurrencyController.php` — filter `index()`

### New
- `app/Http/Controllers/Api/Owner/RevenueController.php`
- `app/Http/Controllers/Api/Owner/CalendarController.php`
- `app/Http/Controllers/Admin/RevenueController.php`
- `app/Http/Requests/Owner/IndexPromoCodeRequest.php`
- `app/Http/Requests/Owner/IndexRevenueRequest.php`
- `app/Http/Requests/Owner/IndexCalendarRequest.php`
- `app/Http/Requests/Admin/IndexRevenueRequest.php`
- `app/Http/Requests/ListCountriesRequest.php` (extend)
- `app/Http/Requests/ListCitiesRequest.php` (extend)
- `app/Http/Requests/ListRegionsRequest.php` (extend)
- `app/Http/Requests/ListCurrenciesRequest.php` (new)

### Routes
- Add owner revenue routes inside `role:owner` group.
- Add owner calendar route inside `role:owner` group.
- Add admin revenue route inside `role:super_admin|admin` group.

---

## Response shapes

Keep responses simple and consistent with existing patterns:

- Promo codes: `PromoCodeResource::collection(...)`
- Geo/config: existing resource collections
- Owner revenue: `TransactionResource::collection($transactions)` with summary metadata where applicable
- Admin revenue: `TransactionResource::collection($transactions)` + summary object
- Owner calendar: lightweight per-day array grouped by date

---

## Testing strategy

1. Run full test suite after implementation.
2. Add/adjust feature tests for:
   - Promo code filtering by status and search
   - Geo/config list filters
   - Owner revenue scoping (owner cannot see other owners' transactions)
   - Admin revenue global access
   - Calendar status/region filters
3. Run Laravel Pint.

---

## Success criteria

- All five areas accept and apply the specified filters.
- Owner/admin scoping is enforced on every query.
- Filter logic is centralized in `FilterService`.
- Existing tests pass; new tests cover filter behavior.
