# WP2 — the setting

**Flags:** no migration. **Repo:** BE `/home/moonui2/moon-erp-be`, branch `hazemdev2`.
**Depends on:** WP1 (schema + `PartiallyConverted` status already exist).

## Goal

Introduce one company-scoped boolean, `purchases.allow_partial_request_conversion`,
defaulting to **`false`**, so partial conversion is something a company opts into. With it
off, WP3's code path must be byte-for-byte today's behaviour. This package only defines and
exposes the setting and provides the single reader other code will call — it changes no
behaviour on its own.

## The recurring defect this package must not repeat

This codebase has a documented family of bugs where a setting is **stored, validated and
rendered in the settings screen — and never read by anything**. Four such defects were found
in POS alone. So: the reader is part of this package, and WP3's acceptance depends on it.

There is also a subtler trap in the existing read idiom. `PurchaseRequestController:110-122`
does:

```php
$enabled = app(SettingsService::class)->get('purchases.enable_purchase_requests', $companyId);
if ($enabled === false) { … }
```

If the definition row is missing, `get()` returns `null`, `=== false` is false, and **the
gate silently opens**. Do not copy that. Use:

```php
app(SettingsService::class)->getBool('purchases.allow_partial_request_conversion', $companyId, false)
```

`getBool()` is at `Modules/Core/app/Services/SettingsService.php:83-88` and is the idiom
`ProcurementPolicy` uses throughout (e.g. `:56`, `:70`, `:84`).

## Exact changes

### 1. Definition — `Modules/Core/database/seeders/SettingDefinitionSeeder.php`

Copy the shape of `purchases.enable_purchase_requests` at `:1400-1415` exactly (note
`default_value` is the **string** `'false'`, not a bool):

- `setting_key`: `purchases.allow_partial_request_conversion`
- `module`: `purchases` · `value_type`: `boolean` · `default_value`: `'false'`
- `scope`: `company` · `display_group`: `purchases` · `is_visible`: `true`
- `display_order`: pick the next free number in the purchases group — read the neighbours
  rather than guessing.
- `label_en` / `label_ar` / `description_en` / `description_ar`: write these carefully. The
  description is what the owner reads in the settings screen to decide, so say what actually
  changes: with it on, a purchase request can be converted into more than one purchase order,
  line by line and quantity by quantity, and it stays open until everything has been ordered.

The seeder is idempotent — check how neighbouring entries handle re-runs (`updateOrCreate`)
and follow it, because the MoonStack updater re-runs this seeder on every install.

### 2. Validation — `Modules/Purchases/app/Http/Requests/UpdatePurchasesSettingsRequest.php`

Add `'allow_partial_request_conversion' => ['sometimes', 'boolean'],` inside the existing
`withDefinitionRules('purchases', [...])` block (the file's pattern is visible at `:48`,
`:69`).

### 3. Lang keys — `Modules/Purchases/lang/en/purchases.php` and `…/ar/purchases.php`

Add the key alongside `enable_purchase_requests` (`:29` in both). Keep both files in step.

### 4. The reader

Add ONE place that answers "is partial conversion allowed for this company". Prefer a small
method on an existing service over a free function. Two candidates — read both and choose,
then say in the commit message why:

- `Modules/Purchases/app/Services/ProcurementPolicy.php` — the module's settings-reading
  service. **But** it has a drift-guard test,
  `Modules/Purchases/tests/Feature/ProcurementPolicySoleReaderTest.php:14-22`, listing
  `PROCUREMENT_POLICY_GOVERNED_KEYS`. That guard is **opt-in per key**: adding the key to the
  list means no other file may ever read it directly. That is a good property here.
- A direct `getBool` call at the one call site in WP3.

Recommendation: put it on `ProcurementPolicy` **and** add the key to the governed list, so
the guard test enforces the single-reader property from day one. If you do, make sure the
guard test still passes.

## Acceptance criteria

1. The setting appears in `GET /api/purchases/settings` with `default_value` false, its
   bilingual label and its description — `PurchasesSettingController::index()` surfaces
   seeded definitions automatically (`:43`), so this should need no controller change. Verify
   that it does.
2. It can be turned on and off through the settings endpoint and the value round-trips.
3. The reader returns `false` when **no** setting row exists for the company (the missing-row
   case is the one that bites — assert it explicitly).
4. Nothing else in the system behaves differently. No existing test changes.

## Tests

New file `Modules/Purchases/tests/Feature/PartialConversionSettingTest.php`:

- the definition is seeded and exposed by the settings endpoint;
- default reads `false`;
- **reader returns `false` when the definition row is absent entirely** (the silent-open trap);
- after setting it to `true`, the reader returns `true`;
- if you added the key to `PROCUREMENT_POLICY_GOVERNED_KEYS`, `ProcurementPolicySoleReaderTest`
  still passes.

⛔ Prefix every top-level helper with `pcset…` — Pest loads all test files into one process
and a duplicate top-level function is a fatal redeclare (exit 255, zero output).

Note: `PurchasesSettingApiTest` carries ~8 pre-existing double-seed failures. Know that
number before you start so you neither blame yourself for them nor hide a new one among them.

## Out of scope

Any use of the setting to change behaviour — that is WP3. Do not touch the conversion
controller in this package.

## Finish

`./vendor/bin/pint` touched files only · `chown moonui2:moonui2` · `bash local-deploy.sh`
(re-seeds definitions) · conventional commit on `hazemdev2` · no push, no merge.
No CHANGELOG bullet yet — the capability ships in WP3.
