# AST-1.8.2 Import Upload and Staging Foundation Handoff

## Scope and status

This foundation reads the approved opening-balance workbook and stages rows only through test fakes. It has no API route, controller, Form Request, Eloquent model, production repository, storage adapter, service-provider binding, database write, asset creation, stock posting, finance posting, or approval execution.

**Status:** FOUNDATION READY — INTEGRATION BLOCKED.

The production integration stays blocked by genuine AST-1.8.1 sign-off and the `asset_import_rows` multi-sheet identity defect documented below.

## New files and responsibilities

| Path | Responsibility |
|---|---|
| `backend/Modules/Asset/app/Import/Contracts/` | Database-independent reader, batch, row, storage, tenant-context, and template-sign-off contracts. |
| `backend/Modules/Asset/app/Import/Data/` | Immutable file, batch, row, upload, and summary data objects. |
| `backend/Modules/Asset/app/Import/Readers/MaatwebsiteOpeningBalanceWorkbookReader.php` | Maatwebsite Excel reader for the approved XLSX structure, exact headers, original Excel rows, empty rows, formulas, duplicate Asset Codes, and limits. |
| `backend/Modules/Asset/app/Import/Services/OpeningBalanceStagingService.php` | Orchestrates fakes/contracts only; never touches Asset, inventory, Employee, finance, or approval tables. |
| `backend/Modules/Asset/tests/Fakes/Import/` | In-memory batch/row, private-storage, tenant-context, template-reviewer, and runtime XLSX fixture implementations. |
| `backend/Modules/Asset/tests/Unit/OpeningBalanceStagingFoundationTest.php` | Focused foundation coverage. |

## Public contracts

```php
OpeningBalanceWorkbookReaderInterface::read(string $filePath): array;
OpeningBalanceImportBatchRepositoryInterface::create(OpeningBalanceImportBatchData $batch): OpeningBalanceImportBatchData;
OpeningBalanceImportRowStagingRepositoryInterface::stage(OpeningBalanceStagedRowData $row): void;
OpeningBalanceImportFileStorageInterface::store(OpeningBalanceImportUploadData $upload): OpeningBalanceImportFileMetadataData;
OpeningBalanceImportTenantContextInterface::companyId(): int;
OpeningBalanceTemplateSignOffVerifierInterface::assertAuthorized(
    OpeningBalanceImportFileMetadataData $file,
    OpeningBalanceImportTenantContextInterface $context,
): void;
```

`OpeningBalanceStagingService::stage(OpeningBalanceImportFileMetadataData $file)` returns `OpeningBalanceStagingSummaryData` after every row has been staged through the configured repository.

## Workbook contract

The reader accepts exactly these sheets in this order: `Serialized Assets`, `Quantity Assets`, and `Optional Warranty`. It maps exact headers from [the column dictionary](./OPENING_BALANCE_IMPORT_COLUMN_DICTIONARY.md), preserves the source sheet and physical Excel row number, skips fully empty rows, rejects formulas and structure/header mismatches, rejects duplicate serialized Asset Codes, and enforces a 10 MB / 10,000 staged-row limit.

The reader does not create domain objects. Its payload keys are dictionary-derived snake case, for example `asset_code`, `item_code`, `occurred_on`, and `provider_code`.

## Demo fixtures and fakes

`OpeningBalanceWorkbookFixture` produces temporary XLSX files with serialized, quantity, and warranty rows. The unit suite covers same-row-number rows on all three sheets, invalid headers, empty workbooks, empty source rows, duplicate Asset Codes, formulas, fake storage checksums, cleanup, company A/company B boundaries, and authorized/unauthorized fake reviewers.

The fake row repository’s identity is:

```text
(batch_id, sheet_name, row_number)
```

It intentionally differs from the current database key so tests cannot hide the production schema flaw.

## Required AST-SCHEMA-MIG work

The current `asset_import_rows` key is `UNIQUE(batch_id, row_number)`. It cannot store row 2 from three sheets in one workbook batch. The schema owner must add a non-null sheet identity and enforce:

```text
UNIQUE(batch_id, sheet_name, row_number)
```

The owner must also preserve original source row numbers, prohibit client-controlled tenant identity, and enforce consistency between a row’s `company_id` and its batch’s `company_id`.

## File metadata mapping

The storage contract keeps these values separate:

```text
company_id
original_filename
storage_path
template_version
file_sha256
```

The real persistence adapter must map all five values after schema approval. Existing `asset_import_batches.file_name` alone is insufficient to represent private storage location, original filename, template version, and digest distinctly. Storage must remain private and must never return a public URL. The foundation deletes the uploaded private object if staging fails after file verification; a production adapter must make deletion idempotent and log cleanup failures.

## RBAC and sign-off integration

The fake verifier is test evidence only. It grants a reviewer through `(company_id, user_id)` and never writes an approval record or marks AST-1.8.1 signed off.

The production verifier must use effective permission-based eligibility and the existing `ApprovalGatewayContract` / registered executor path where the platform supports it. It must not use a hardcoded role, user ID, or `asset.import.confirm` as a substitute for template-sign-off authorization. Genuine reviewer identity, time, decision, and attributable approval evidence remain required.

## Replacing fakes with real adapters

After AST-SCHEMA-MIG and sign-off completion, an integration developer should:

1. Add schema-approved Eloquent models and repository implementations for batches and rows.
2. Bind the existing contracts in `AssetServiceProvider` in the same change that introduces real adapters.
3. Implement private file storage with collision-resistant paths and checksum verification.
4. Add an approved tenant context from the existing authentication/company boundary.
5. Implement a production sign-off verifier that reads, but does not fabricate, platform approval evidence.
6. Add the upload Form Request, controller, route middleware, and API Resource only after the preceding work is merged.

## Required real integration tests

- Database uniqueness permits row 2 on each of the three sheets but rejects a duplicate within the same sheet.
- Batch and row tenant IDs cannot diverge, and company A cannot list or retrieve company B data.
- Private storage path, original filename, version, and checksum persist correctly.
- Upload does not create assets, stock, ledger, finance, Employee asset, or approval records.
- Authorization denies unqualified users and permits only reviewers with the designated effective permission.
- Real platform sign-off evidence is required before staging is enabled.

## Remaining blockers and ownership

| Blocker | Owner |
|---|---|
| Genuine authorized AST-1.8.1 sign-off | Authorized human reviewer / platform approval owner |
| Sheet-aware row uniqueness and tenant invariant | AST-SCHEMA-MIG owner |
| Separate batch file metadata mapping | Schema owner and storage integration developer |
| Upload route, request, controller, resource, and feature tests | AST-1.8.2 integration developer after blockers clear |
