## ADDED Requirements

### Requirement: Company Access on Employee Import Endpoints
The employee import endpoint (`POST /admin/employees/import`) and the bulk update import endpoint (`POST /admin/employees/update-multi`) SHALL require the `company_mode` parameter and SHALL reject a request with HTTP 403 when the authenticated admin cannot access that company mode. The value `all` SHALL be accepted only for admins with access to all companies.

#### Scenario: Admin restricted to one company sends company_mode all
- **WHEN** an admin whose company access is limited to company A calls the import or update-multi endpoint with `company_mode=all`
- **THEN** the request is rejected with HTTP 403 and no employee is created or updated

#### Scenario: Admin sends a company they cannot access
- **WHEN** an admin with access only to company A calls the import or update-multi endpoint with `company_mode` set to company B's uuid
- **THEN** the request is rejected with HTTP 403

#### Scenario: Missing company_mode
- **WHEN** an admin calls the import or update-multi endpoint without `company_mode`
- **THEN** the request is rejected with HTTP 403

#### Scenario: Admin sends an accessible company
- **WHEN** an admin with access to company A calls the import endpoint with `company_mode` set to company A's uuid
- **THEN** the import is processed

### Requirement: Import File Department and Company Columns
The employee import file SHALL accept two optional columns, "Phòng ban" (heading key `phong_ban`) and "Công ty" (heading key `cong_ty`), in addition to "Vị trí". Their values SHALL be trimmed and matched exactly against the department name and the company name. Files without these columns SHALL still be processed, and the missing columns SHALL be treated as blank.

#### Scenario: File without the new columns
- **WHEN** an admin imports a file that has "Vị trí" but no "Phòng ban" or "Công ty" columns
- **THEN** each row is processed with blank department and company values

### Requirement: Position Resolution in Employee Import
For each row, the system SHALL resolve exactly one position from "Vị trí", narrowed by the company scope and, when filled, by "Phòng ban". The company scope SHALL be:
- when `company_mode` is a specific company X: company X;
- when `company_mode` is `all` and "Công ty" is filled: the single company whose name equals "Công ty";
- when `company_mode` is `all` and "Công ty" is blank: all companies.

The row SHALL be imported with the resolved position only when exactly one position matches. The employee's department, company, default leader and the company used for annual-leave conversion SHALL be derived from that resolved position. The system SHALL NOT fall back to a default position.

#### Scenario: Same position name in two departments, disambiguated by department
- **WHEN** positions named "Developer" exist in department "Web" and in department "Game", and an admin imports two rows with "Vị trí" = "Developer" and "Phòng ban" = "Web" and "Game" respectively
- **THEN** the first employee is assigned the "Developer" position of department "Web"
- **AND** the second employee is assigned the "Developer" position of department "Game"

#### Scenario: Same position name in two companies, disambiguated by company
- **WHEN** positions named "Accountant" exist in company "TAT" and company "Suga", `company_mode` is `all`, and a row has "Vị trí" = "Accountant" and "Công ty" = "Suga"
- **THEN** the employee is assigned the "Accountant" position of company "Suga"

#### Scenario: Unique position name without department or company
- **WHEN** exactly one position named "CEO (TAT)" exists within the company scope and a row has "Vị trí" = "CEO (TAT)" with blank "Phòng ban" and "Công ty"
- **THEN** the employee is assigned that position

#### Scenario: Ambiguous position
- **WHEN** more than one position named "Developer" matches within the company scope and the department filter
- **THEN** the row is not imported
- **AND** the row is reported with reason code `position_ambiguous`

#### Scenario: Position not found
- **WHEN** no position matches "Vị trí" within the company scope and the department filter
- **THEN** the row is not imported
- **AND** the row is reported with reason code `position_not_found`

#### Scenario: Blank position
- **WHEN** a row has a blank "Vị trí"
- **THEN** the row is not imported
- **AND** the row is reported with reason code `position_required`

#### Scenario: Specific company mode scopes the lookup
- **WHEN** `company_mode` is company X, a row has blank "Công ty", and "Vị trí" matches exactly one position in company X and other positions in other companies
- **THEN** the employee is assigned the position in company X

#### Scenario: Specific company mode with a different company in the file
- **WHEN** `company_mode` is company X and a row has "Công ty" filled with a name other than company X's name
- **THEN** the row is not imported
- **AND** the row is reported with reason code `company_mismatch`

#### Scenario: Unknown company name in all mode
- **WHEN** `company_mode` is `all` and "Công ty" does not match any company name
- **THEN** the row is not imported
- **AND** the row is reported with reason code `company_not_found`

#### Scenario: Duplicate company name in all mode
- **WHEN** `company_mode` is `all` and "Công ty" matches more than one company
- **THEN** the row is not imported
- **AND** the row is reported with reason code `company_ambiguous`

### Requirement: Per-Row Validation in Employee Import
The system SHALL validate every non-empty row before creating the employee. A row that fails validation SHALL NOT be created, and all other valid rows SHALL still be created. A row SHALL fail with the given reason code when:
- "Họ", "Tên", "Email" or "SĐT" is blank: `required_field_missing`;
- the email already exists as an account username (including soft-deleted accounts), or appears in an earlier row of the same file: `email_duplicated`;
- the phone number already exists on a profile, or appears in an earlier row of the same file: `phone_duplicated`;
- "Mã NV" is filled and already exists, or appears in an earlier row of the same file: `code_duplicated`;
- a text value is longer than its column allows, including "Vị trí HĐ" longer than 191 characters: `value_too_long`;
- position or company resolution fails, using the reason codes of the Position Resolution requirement.

A row SHALL collect all of its validation failures, not only the first one. An unexpected error while creating a row SHALL roll back that row only and SHALL be reported with reason code `unknown_error`.

#### Scenario: Duplicate employee code against the database
- **WHEN** a row's "Mã NV" equals an existing employee's code
- **THEN** the row is not imported and is reported with reason code `code_duplicated`
- **AND** the other valid rows in the file are imported

#### Scenario: Contract position too long
- **WHEN** a row's "Vị trí HĐ" is 192 characters long
- **THEN** the row is not imported and is reported with reason code `value_too_long` naming the "Vị trí HĐ" column

#### Scenario: Same email twice in one file
- **WHEN** two rows in the same file have the same email that does not exist in the database
- **THEN** the first row is imported
- **AND** the second row is not imported and is reported with reason code `email_duplicated`
- **AND** the first employee's position is not changed by the second row

#### Scenario: Same phone twice in one file
- **WHEN** two rows in the same file have the same phone number that does not exist in the database
- **THEN** the first row is imported
- **AND** the second row is reported with reason code `phone_duplicated`, not `unknown_error`

#### Scenario: Missing required field
- **WHEN** a non-empty row has a blank "Email"
- **THEN** the row is reported with reason code `required_field_missing`

#### Scenario: Multiple failures in one row
- **WHEN** a row has a duplicated "Mã NV" and a "Vị trí HĐ" longer than 191 characters
- **THEN** the reported entry for that row contains both `code_duplicated` and `value_too_long`

### Requirement: Import Result Reports Failed Rows
The import response SHALL keep the existing count fields (`imported_successful_count`, `employee_code_error_count`, `username_error_count`, `phone_number_error_count`, `position_error_count`, `unknown_error_count`) and SHALL add an `errors` array. The array SHALL contain one entry per failed row with:
- `row`: the row number as shown in Excel (the heading row is row 1), which is correct even when the file contains empty rows;
- `code`: the "Mã NV" value of the row, or `null` when blank;
- `email`: the "Email" value of the row, or `null` when blank;
- `reasons`: a list of `{ "code": <reason code>, "column": <column heading or null>, "message": <Vietnamese message> }`.

Entries SHALL be ordered by row number. `position_error_count` SHALL count rows with any position or company reason code.

#### Scenario: Response for a file with valid and invalid rows
- **WHEN** an admin imports a file where Excel rows 2 and 4 are valid and row 3 has a duplicated "Mã NV" "NV001"
- **THEN** the response has `imported_successful_count` = 2
- **AND** `errors` contains one entry with `row` = 3, `code` = "NV001" and a reason with code `code_duplicated`

#### Scenario: Row numbers with empty rows in between
- **WHEN** the file has an empty Excel row 3 and an invalid Excel row 5
- **THEN** the error entry reports `row` = 5

### Requirement: Employee Import Template Columns
The downloadable template `public/excels/hrm_employee_seeding.xlsx` SHALL contain the columns "Phòng ban" and "Công ty" directly after "Vị trí" on the "Nhân viên" sheet. The template SHALL NOT contain a hard-coded list of positions, departments or companies. Its dropdown validations SHALL target the correct columns ("Giới tính", "Hình thức nhân viên", "Loại thuế").

#### Scenario: Template headings
- **WHEN** an admin downloads the employee import template
- **THEN** the "Nhân viên" sheet has "Phòng ban" and "Công ty" headings immediately after "Vị trí"
