# service-project-members ## Purpose Service-to-service API для upsert участника проекта Merakomis без impersonation сотрудника (только API-ключ). ## Requirements ### Requirement: Service upsert project team member The system SHALL expose `PUT /api/service/project-members` that creates or updates a row in `tMerakomisTeamMember` for the given `project_id` and `emp_id`. The endpoint MUST resolve `team` from `tMerakomisProject.team` for `project_id`. If a row with the same `(team, emp)` already exists, the system MUST UPDATE it; otherwise MUST INSERT. The request MUST require `X-Api-Key` (or `Authorization: Bearer`) when the server API key is configured. The request MUST NOT require `X-Acting-Emp-Id`. The system MUST NOT apply portal `Rules::isRwMember` / `can_write_project_member` checks for this endpoint. Required JSON fields: `project_id`, `emp_id`, `section_id`, `role`. Optional JSON fields: `active` (default `true`), `text` (default empty string). The response MUST include `ok: true` and a member object with the same fields as one item of `GET /api/project-members` (`member_id`, `emp_id`, `emp_name`, `project_id`, `project_code`, `project_name`, `step_name`, `status`, `status_name`, `archive`, `archive_date`, `section_id`, `section_name`, `role`, `active`). #### Scenario: Add new member with API key only - **WHEN** a valid API key is provided - **AND** `X-Acting-Emp-Id` is omitted - **AND** no `tMerakomisTeamMember` row exists for `(team, emp)` - **AND** `section_id` is allowed for `project_id` - **AND** `emp_id` refers to a non-archived, non-removed employee - **AND** `role` is a valid member role - **THEN** system inserts a new TeamMember row - **AND** returns `200` with `ok: true` and the new member object including `member_id` #### Scenario: Update existing member with API key only - **WHEN** a TeamMember row already exists for `(team, emp)` - **AND** a valid API key is provided without acting header - **THEN** system updates `section`, `role`, `active`, and `text` - **AND** returns `200` with updated member object (same `member_id`) #### Scenario: Deactivate member via service endpoint - **WHEN** client sends `PUT /api/service/project-members` with `active: false` - **AND** a valid API key is provided - **THEN** system sets `active` to 0 - **AND** member no longer appears in `GET /api/project-members?active_only=true` #### Scenario: Missing API key rejected - **WHEN** server has `USER_READER_API_KEY` configured - **AND** client calls `PUT /api/service/project-members` without a valid key - **THEN** system returns `401 Unauthorized` ### Requirement: Service endpoint reuses portal write validation The system MUST apply the same business validation as `PUT /api/project-members` for project existence, employee existence and eligibility, section membership in the project, role validity, and presence of a project team. #### Scenario: Invalid section for project - **WHEN** `section_id` is not in `tMerakomisProjectSection` for `project_id` - **THEN** system returns `400` with a structured error indicating invalid section #### Scenario: Project has no configured sections - **WHEN** `tMerakomisProjectSection` has no rows for `project_id` - **AND** client attempts to add a member via the service endpoint - **THEN** system returns `400` indicating sections must be configured on the project first #### Scenario: Archived employee rejected - **WHEN** `emp_id` refers to an employee with `archive=1` or `removed=1` - **THEN** system returns `400` with validation error for employee #### Scenario: Unknown employee - **WHEN** `emp_id` does not exist in the employee table - **THEN** system returns `404` #### Scenario: Unknown project - **WHEN** `project_id` does not exist - **THEN** system returns `404` #### Scenario: Invalid role - **WHEN** `role` is 0, omitted, or not a valid member role - **THEN** system returns `400` with validation error for role ### Requirement: Portal project-members write remains unchanged The system MUST continue to require `X-Acting-Emp-Id` and `isRwMember` authorization for `PUT /api/project-members`. #### Scenario: Portal PUT still requires acting - **WHEN** client calls `PUT /api/project-members` without `X-Acting-Emp-Id` - **THEN** system returns an error indicating missing acting identity (same behavior as before this change) #### Scenario: Portal PUT still enforces permissions - **WHEN** acting employee does not meet `isRwMember` conditions - **AND** client calls `PUT /api/project-members` - **THEN** system returns `403` with structured error `forbidden`