teacher-earned-months.sql 3.8 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950
  1. -- Derived earned-income summaries, not PayrollBatch settlements or payments.
  2. -- Every reader supplies a signature of fresh inputs and the complete calculator.
  3. CREATE TABLE IF NOT EXISTS "TeacherEarnedMonth" (
  4. company text NOT NULL,"coachId" numeric NOT NULL,month text NOT NULL,
  5. signature text NOT NULL CHECK(signature ~ '^[0-9a-f]{64}$'),
  6. "amountCents" bigint NOT NULL CHECK("amountCents" BETWEEN 0 AND 9007199254740991),
  7. "pendingPayCount" bigint NOT NULL CHECK("pendingPayCount">=0),
  8. "lineCount" bigint NOT NULL CHECK("lineCount">=0 AND "pendingPayCount"<="lineCount"),
  9. "updatedAt" timestamptz NOT NULL DEFAULT clock_timestamp(),
  10. PRIMARY KEY(company,"coachId",month),
  11. CHECK(month='undated' OR month ~ '^\d{4}-(0[1-9]|1[0-2])$')
  12. );
  13. REVOKE ALL ON "TeacherEarnedMonth" FROM PUBLIC;
  14. CREATE OR REPLACE FUNCTION xs_teacher_earned_month_guard() RETURNS trigger LANGUAGE plpgsql AS $$
  15. BEGIN
  16. IF current_setting('xiaoshu.teacher_month_write',true) IS DISTINCT FROM 'yes' THEN RAISE EXCEPTION 'derived teacher months require controlled writer'; END IF;
  17. IF TG_OP='DELETE' THEN RETURN OLD; END IF;RETURN NEW;
  18. END $$;
  19. DROP TRIGGER IF EXISTS teacher_earned_month_guard ON "TeacherEarnedMonth";
  20. CREATE TRIGGER teacher_earned_month_guard BEFORE INSERT OR UPDATE OR DELETE ON "TeacherEarnedMonth" FOR EACH ROW EXECUTE FUNCTION xs_teacher_earned_month_guard();
  21. CREATE OR REPLACE FUNCTION xs_teacher_earned_month_read(co text,coach numeric,months jsonb)
  22. RETURNS jsonb LANGUAGE sql STABLE AS $$
  23. SELECT COALESCE(jsonb_agg(jsonb_build_object('month',m.month,'signature',m.signature,'amountCents',m."amountCents",'pendingPayCount',m."pendingPayCount",'lineCount',m."lineCount") ORDER BY m.month),'[]'::jsonb)
  24. FROM "TeacherEarnedMonth" m WHERE m.company=co AND m."coachId"=coach
  25. AND (months IS NULL OR m.month IN (SELECT jsonb_array_elements_text(months)));
  26. $$;
  27. CREATE OR REPLACE FUNCTION xs_teacher_earned_month_write(co text,coach numeric,entries jsonb)
  28. RETURNS integer LANGUAGE plpgsql AS $$
  29. DECLARE item jsonb; saved integer:=0; changed integer; previous_flag text:=current_setting('xiaoshu.teacher_month_write',true);
  30. BEGIN
  31. IF NULLIF(co,'') IS NULL OR coach IS NULL OR jsonb_typeof(entries)<>'array' THEN RAISE EXCEPTION 'invalid teacher monthly write'; END IF;
  32. PERFORM set_config('xiaoshu.teacher_month_write','yes',true);
  33. FOR item IN SELECT value FROM jsonb_array_elements(entries) LOOP
  34. IF COALESCE(item->>'signature','') !~ '^[0-9a-f]{64}$' OR item->>'month' IS NULL
  35. OR item->>'amountCents' IS NULL OR item->>'pendingPayCount' IS NULL OR item->>'lineCount' IS NULL
  36. OR (item->>'amountCents')::numeric<>trunc((item->>'amountCents')::numeric)
  37. OR (item->>'pendingPayCount')::numeric<>trunc((item->>'pendingPayCount')::numeric)
  38. OR (item->>'lineCount')::numeric<>trunc((item->>'lineCount')::numeric) THEN RAISE EXCEPTION 'invalid teacher monthly summary'; END IF;
  39. INSERT INTO "TeacherEarnedMonth"(company,"coachId",month,signature,"amountCents","pendingPayCount","lineCount")
  40. VALUES(co,coach,item->>'month',item->>'signature',(item->>'amountCents')::bigint,(item->>'pendingPayCount')::bigint,(item->>'lineCount')::bigint)
  41. ON CONFLICT(company,"coachId",month) DO UPDATE SET signature=EXCLUDED.signature,"amountCents"=EXCLUDED."amountCents","pendingPayCount"=EXCLUDED."pendingPayCount","lineCount"=EXCLUDED."lineCount","updatedAt"=clock_timestamp()
  42. -- An older in-flight calculation cannot overwrite a newer stored generation.
  43. WHERE "TeacherEarnedMonth".signature IS NOT DISTINCT FROM item->>'expectedSignature';
  44. GET DIAGNOSTICS changed=ROW_COUNT;saved:=saved+changed;
  45. END LOOP;
  46. PERFORM set_config('xiaoshu.teacher_month_write',COALESCE(previous_flag,''),true);
  47. RETURN saved;
  48. END $$;
  49. REVOKE ALL ON FUNCTION xs_teacher_earned_month_read(text,numeric,jsonb),xs_teacher_earned_month_write(text,numeric,jsonb) FROM PUBLIC;