create employee table
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user