# Technical Specification: Sales Estimate Vue Refactor & Reusable Sales Document System

**Document Version:** 2.0  
**Target Module:** `Modules/Sales`  
**Primary Target Views:**  
- `Modules/Sales/Resources/views/estimates/create.blade.php`  
- `Modules/Sales/Resources/views/estimates/edit.blade.php`  
- `Modules/Sales/Resources/views/estimates/clone.blade.php`  
**Frozen line-items host:** `Modules/Sales/Resources/assets/js/components/CreateEstimateComponent.vue` (do not edit internals this run)  
**Future Consumers:** Proforma Invoices, Tax Invoices  
**Design Standard:** [VOM Frontend Style Guide](../design/VOM_Style_Guide.md) (Vue 2.7 `<script setup>` · RTL-First Arabic · `_tokens.scss` · `Base*` Components)  
**Status:** Implementing — Phase 1 (structural shell)  
**Supersedes:** [`sales_estimate_creation_specification.md`](./sales_estimate_creation_specification.md) (DEPRECATED — UI IA archive)

---

## Locked decisions

| Decision | Choice |
|---|---|
| Living spec | This document |
| Creation spec | Deprecated archive — banner + pointer only |
| Goal | Structural Blade → Vue; **no new UI design** — create’s modern IA is the target for edit/clone too |
| This phase shape | Page shell **+** Tier-2 document primitives; **Base\*** on new shell only |
| Frozen | `CreateEstimateComponent.vue`, `OtherFeesComponent.vue` internals |
| Totals | Stay inside frozen create-estimate; **no** `DocumentTotalsSummary` yet |
| Screens | Create **+** edit **+** clone |
| Dates / modals | `date-picker-component` in Vue; new **`BaseDialog`**; **zero jQuery** on these pages |
| Styles | Move into Vue SFCs (`<style scoped>` + tokens); retire `estimate-modern.css` from the three pages when unused |
| Other fees | **Explore-only** appendix (below) — no rewrite |
| Items table (future) | Remains **estimate-specific** (`EstimateItemsTable`); not shared across sales docs |
| Assembly | Shared `EstimateForm.vue` + three thin page wrappers |
| Submit | Classic **multipart POST/PUT**, **same field names**; no request-shape change |
| Explicitly out | Math engine / `useSalesCalculations`; Zod + API v2; OtherFees rewrite; piercing create-estimate |

**Visual note:** Cards use locked [`BaseCard`](../../resources/js/components/base/BaseCard.vue) geometry (ADR [`0001`](../adr/0001-basecard-locked-geometry.md): 12px / 0.5px). Accepted Base\* adoption delta vs old `estimate-modern` 8px cards — not a redesign pass.

**Calculations (documented, not built this run):** Frontend calc inside frozen `create-estimate-component` remains draft UX only. Authoritative calc = **future backend endpoint**. Do not extract a frontend math engine in Phase 1.

---

## Phase roadmap

### Phase 1 (now) — Structural shell

1. **1a** — `BaseDialog` + register Base\* used by the shell; design-system showcase.
2. **1b** — Tier-2 document SFCs under `document/` (customer, overview, alerts, header actions, collateral tabs, `PastDateModal`). **No** `DocumentTotalsSummary`. **No** OtherFees rewrite.
3. **1c** — `EstimateForm` + `Create` / `Edit` / `Clone` pages; thin Blade mounts; embed frozen `create-estimate-component` with the same props create/edit/clone pass today; remove page-level jQuery.
4. **1d** — Harden (double-submit, unsaved guard, responsive order, fiscal/print); retire `estimate-modern.css` from the three views when unused.

### Next (Phase 2)

- Extract `EstimateItemsTable` / row from frozen create-estimate (estimate-specific).
- Introduce `DocumentTotalsSummary` once items extraction lands.
- Optional `useFiscalYearCheck` composable extracted from form shell.

### Later

- `useSalesCalculations` + unit suite (only after backend calc endpoint strategy is clear).
- Zod validation on **API v2** (not multipart v1).
- Refactor / replace `OtherFeesComponent` after exploration gates are met.
- Wire Tier-2 into Proforma / Invoices.

### Explicitly deferred this run

| Item | Status |
|---|---|
| Math engine / `useSalesCalculations` | Later |
| Zod / API v2 payload | Later |
| `DocumentTotalsSummary` | Next (after items extraction) |
| Items table extraction | Next |
| OtherFees rewrite | Later (explore-only now) |

---

## Architecture (Phase 1 shape)

```
┌────────────────────────────────────────────────────────────────────────┐
│  Thin Blade shells — @json initial-data + page mount only              │
│  create.blade.php / edit.blade.php / clone.blade.php                   │
└────────────────────────────────────────────────────────────────────────┘
                                    │
                                    ▼
┌────────────────────────────────────────────────────────────────────────┐
│  Page wrappers — mode + initial-data                                   │
│  CreateEstimatePage / EditEstimatePage / CloneEstimatePage             │
└────────────────────────────────────────────────────────────────────────┘
                                    │
                                    ▼
┌────────────────────────────────────────────────────────────────────────┐
│  EstimateForm.vue — layout, shell state, fiscal gate, submit           │
│  + Tier-2 document/*  + Base*  + date-picker  + customers-dropdown     │
│  + FROZEN create-estimate-component (same props as today)              │
└────────────────────────────────────────────────────────────────────────┘
```

---

## Directory structure & file map (Phase 1)

```
Modules/Sales/Resources/
├── assets/js/
│   └── components/
│       ├── document/                     # Tier 2 (estimate consumers first)
│       │   ├── CustomerContactCard.vue
│       │   ├── DocumentOverviewCard.vue
│       │   ├── DocumentAlertsCard.vue
│       │   ├── DocumentHeaderActions.vue
│       │   ├── DocumentCollateralTabs.vue
│       │   └── PastDateModal.vue         # uses BaseDialog
│       │
│       └── estimates/
│           ├── EstimateForm.vue
│           ├── CreateEstimatePage.vue
│           ├── EditEstimatePage.vue
│           └── CloneEstimatePage.vue
│
└── views/estimates/                      # Slim Blade entry points
    ├── create.blade.php
    ├── edit.blade.php
    └── clone.blade.php

resources/js/components/base/
└── BaseDialog.vue                        # Phase 1a
```

**Not built this run:** `useSalesCalculations`, `DocumentTotalsSummary`, `DocumentOtherFeesTable`, `EstimateItemsTable`, `EstimateItemRow`.

---

## Technical contract (unchanged request shape)

### Client → server (multipart)

Same field names as today’s validators (`CreatingEstimateValidator` / update equivalents):

- `code`, `customer_id`, `issue_date`, `due_date`, `description`
- `taxes_status`, `products[]`, `other_fees_details[]`
- `terms`, `notes`, `attachments`, `custom_fields[]`
- `action` (`save` | `save_new`), `is_print` (`0` | `1`)
- `form_design_id`, CSRF / `_method` as today

### Modes

| Mode | Method / route | Code | Status badge |
|---|---|---|---|
| Create | `POST` `company.estimates.store` | New suggested code | Pre-persist **Draft** |
| Edit | `PUT` `company.estimates.update` | Existing estimate code | Domain status |
| Clone | `POST` `company.estimates.store` | **New** suggested code | Pre-persist **Draft** |

Same modern IA for all three; only titles, status, hydration, and form action differ.

---

## Non-functional requirements

1. Vue 2.7 `<script setup>` for all **new** components.
2. Tokens only — `var(--color-*)`, `var(--space-*)`, etc.
3. RTL-first logical properties.
4. Base UI first: `BaseCard`, `BaseButton`, `BaseAlert`, `BaseLabel`, `BaseInputWrapper`, `BaseInputCounter`, `BaseDialog`, `BaseTag`.
5. **No jQuery** on estimate create/edit/clone page scripts.
6. Do **not** edit `CreateEstimateComponent` or `OtherFeesComponent` internals.

---

## Verification (Phase 1)

- Create / edit / clone: Save & Close, Save & New, Save & Print.
- Fiscal past-date: `PastDateModal` (`BaseDialog`) continue / cancel.
- Customer rail updates from dropdown `@change` with no jQuery.
- Network payload field names match current validators.
- No page-level jQuery in the three estimate form views.
- `npm run dev` / Mix compile succeeds.

---

## Appendix A — Other Fees exploration (contract only; no rewrite)

Source: `Modules/Settings/Resources/assets/js/components/OtherFeesComponent.vue` and how `CreateEstimateComponent.vue` seeds / consumes it.

### Props accepted by `OtherFeesComponent`

| Prop | Type | Role |
|---|---|---|
| `screenName` | String (required) | Screen identifier |
| `subdomain` | String | Tenant subdomain |
| `feeCatalog` | Array | Selectable fee catalog |
| `existingRows` | Array | Seed rows (`other_fee_id`, `quantity`, `value`, `tax_id`, `tax_val`, …) |
| `disabled` | Boolean | Read-only |
| `currency` | String | Display currency HTML |
| `lang` | String | `ar` / `en` (RTL) |
| `translations` | Object | Label overrides |
| `quickCreateAllowed` | Boolean | Show quick-create |
| `quickCreateUrl` | String | `POST` store URL |
| `quickCreateDataUrl` | String | Taxes / accounts for quick-create |

### Emit contract

`change` payload:

```js
{
  rows: [{
    other_fee_id, quantity, value, account_id, tax_id, tax_val,
    total_value, total_vat, total_with_vat
  }],
  totals: { totalValue, totalVat }
}
```

### Hidden inputs posted (multipart)

Per row index `n`:

- `other_fees_details[n][other_fee_id|quantity|value|account_id|total_value|tax_id|tax_val|total_vat|total_with_vat]`

### How create-estimate seeds / consumes

1. **Catalog prop:** Blade → `:other_fee_services` → computed `otherFeesCatalog`.
2. **Seed:** `seedOtherFeesFromCatalog()` builds `otherFeesSeedRows` from catalog activities / defaults; remounts via `otherFeesComponentKey`.
3. **Old / edit hydrate:** `applyOldOtherFeesValues(old.other_fees_details || old.fee_services)`.
4. **Change handler:** `onOtherFeesChanged` maps rows into internal `feeServices` and updates `totalValueWithFees` for the frozen totals UI.
5. **Quick-create props:** `other_fees_can_create`, `other_fees_quick_create_url`, `other_fees_quick_create_data_url`, `fee_services_lang`.

### Gates before any OtherFees rewrite

- Preserve identical multipart field names.
- Preserve seed behavior for create (catalog defaults) and edit/clone (old / estimate fees).
- Preserve totals feedback into the parent line-items host (or successor) without rounding drift.
- Prefer shared Tier-2 only after Phase 2 items extraction clarifies ownership of document totals.
