create employee table
Some checks failed
CI / test (push) Failing after 4m38s
CI / fixture (push) Failing after 9s

This commit is contained in:
2026-09-05 10:44:47 +05:30
parent dc785b917c
commit cf99866e12
11 changed files with 820 additions and 26 deletions

View File

@@ -29,6 +29,7 @@ import (
"context"
"errors"
"fmt"
"strings"
"time"
"github.com/jackc/pgx/v5"
@@ -110,6 +111,107 @@ type HireResult struct {
Staff domain.Record `json:"staff"`
}
// WorkerWithRoleResult is what recording a new worker returns: both records the
// flow created, so the caller renders the outcome without a follow-up read.
type WorkerWithRoleResult struct {
Worker domain.Record `json:"worker"`
Role domain.Record `json:"role"`
}
// CreateWorkerWithRole records a NEW person and their first declared role, atomically.
//
// The third flow this file exists for, and it has the same shape as the two
// above: the frontend would otherwise POST a worker profile, then POST an
// employee role against the id it came back with, and a failure between them
// leaves a worker nobody meant to create with no role and no way to tell them
// apart from a real one.
//
// WHY THIS IS NOT "FIND THE WORKER, THEN ADD A ROLE"
//
// It is a creation flow, deliberately. A name is not an identity — an
// organization may employ any number of people who share one — so nothing here
// searches for an existing worker by name, and the caller cannot supply a
// worker id. The identity is the email, which the caller states outright, and
// the schema decides whether it is already taken: `worker_profiles` carries
// UNIQUE (org_id, email) over a `citext` column, so a repeat is a 409 from the
// database rather than a check this code could race against.
//
// Recording a SECOND role for someone who already exists is a different
// operation and stays where it was: POST /api/v1/employee-roles, which takes
// the worker's email and does not touch worker_profiles.
func (s *WorkflowService) CreateWorkerWithRole(ctx context.Context, ident authctx.Identity,
body domain.Record) (*WorkerWithRoleResult, error) {
profiles, err := resourceByPath("worker-profiles")
if err != nil {
return nil, err
}
roles, err := resourceByPath("employee-roles")
if err != nil {
return nil, err
}
// The two fields that identify the person. Both are the caller's to supply:
// an email is never derived from a name, and a missing one is asked for
// rather than invented.
name, _ := body["full_name"].(string)
email, _ := body["email"].(string)
if strings.TrimSpace(name) == "" || strings.TrimSpace(email) == "" {
return nil, domain.Validation(
"a new worker needs both a name and an email address",
map[string]string{"full_name": "required", "email": "required"})
}
// The role's own fields, taken from a nested object so that a key meant for
// the role can never land on the worker or the other way about.
roleBody, _ := body["role"].(map[string]any)
if roleBody == nil {
roleBody = domain.Record{}
}
var out WorkerWithRoleResult
err = s.inTx(ctx, func(tx pgx.Tx) error {
workerRecord := domain.Record{"full_name": name, "email": email}
for _, k := range []string{"phone", "availability", "certifications", "experience_years", "skills"} {
if v, ok := body[k]; ok {
workerRecord[k] = v
}
}
worker, err := repo.New(profiles, tx).Insert(ctx, ident, workerRecord)
if err != nil {
// A duplicate identity arrives here as the contract's conflict,
// translated by the repository from the unique violation. Returned
// as it is: the transaction rolls back, so no role is written for a
// worker that was never created.
return err
}
// The role points at the worker this transaction just made, never at
// one found by name. org_id and created_by are the repository's, from
// the session.
roleRecord := domain.Record{}
for k, v := range roleBody {
roleRecord[k] = v
}
roleRecord["worker_profile_id"] = worker["id"]
roleRecord["worker_email"] = worker["email"]
roleRecord["worker_name"] = worker["full_name"]
role, err := repo.New(roles, tx).Insert(ctx, ident, roleRecord)
if err != nil {
return err
}
out = WorkerWithRoleResult{Worker: worker, Role: role}
return nil
})
if err != nil {
return nil, err
}
return &out, nil
}
// Hire moves an application to `hired` and creates the staff record, atomically.
//
// Replaces the two-call sequence at krowHooks.js:302-303. The failure that