payroll-cutoff-fast.sql 3.3 KB

12345678910111213141516171819202122232425262728293031
  1. -- Same cutoff semantics and hashes; bulk membership join and incremental row digest.
  2. CREATE OR REPLACE FUNCTION public.xs_payroll_cutoff_snapshot_fast(co text, mon text)
  3. RETURNS jsonb
  4. LANGUAGE plpgsql
  5. SET jit TO 'off'
  6. SET work_mem TO '64MB'
  7. SET max_parallel_workers_per_gather TO '2'
  8. AS $function$
  9. DECLARE run "LegacyDirectRun"; ev record; table_name text; matches integer; complete boolean:=true; conflicts jsonb; version text; approval "PayrollCutoffApproval"; data_version text; source_version text; pending integer; datasets jsonb:='[]'; ready boolean;
  10. BEGIN
  11. IF NOT xs_payroll_authority_ready() THEN RETURN xs_payroll_cutoff_snapshot(co,mon);END IF;
  12. IF mon !~ '^\d{4}-(0[1-9]|1[0-2])$' THEN RAISE EXCEPTION '工资月份无效'; END IF;
  13. SELECT * INTO run FROM "LegacyDirectRun" WHERE company=co AND status IN ('completed','completed-with-preserved-conflicts') ORDER BY "finishedAt" DESC LIMIT 1;
  14. IF NOT FOUND THEN complete:=false; END IF;
  15. IF (SELECT count(*) FROM "LegacyDirectEvidence" WHERE company=co AND "runId"=run."runId" AND model IN (54,59,60))<>3 THEN complete:=false; END IF;
  16. FOR ev IN SELECT * FROM "LegacyDirectEvidence" WHERE company=co AND "runId"=run."runId" AND model IN (54,59,60) LOOP
  17. table_name:=CASE ev.model WHEN 54 THEN 'CourseAppointment' WHEN 59 THEN 'LessonRecord' ELSE 'MemoryPracticeRecord' END;
  18. EXECUTE format('WITH local_rows AS MATERIALIZED (SELECT c."generalId" FROM "CommonModel" c JOIN %I a ON a.company=c.company AND a.id=c."itemId" WHERE c.company=$1 AND c."modelId"=$2), wanted AS MATERIALIZED (SELECT DISTINCT jsonb_array_elements_text($3) AS id) SELECT count(*)::int FROM (SELECT l."generalId" FROM local_rows l JOIN wanted w ON w.id=l."generalId"::text GROUP BY l."generalId" HAVING count(*)=1) x',table_name) INTO matches USING co,ev.model,ev.ids;
  19. complete:=complete AND matches=ev."sourceCount" AND ev."sourceChecksum"=ev."targetChecksum" AND jsonb_array_length(ev.ids)=ev."sourceCount" AND ev."sourceChecksum"=md5((SELECT COALESCE(string_agg(v,E'\n' ORDER BY v::numeric),'') FROM jsonb_array_elements_text(ev.ids) v));
  20. datasets:=datasets||jsonb_build_array(jsonb_build_object('model',ev.model,'source',ev."sourceCount",'matched',matches,'checksum',ev."sourceChecksum"));
  21. END LOOP;
  22. SELECT COALESCE(jsonb_agg(c ORDER BY c->>'id'),'[]') INTO conflicts FROM xs_payroll_cutoff_conflicts(co,mon) c;
  23. SELECT count(*) INTO pending FROM jsonb_array_elements(conflicts) c WHERE NOT (c->>'resolved')::boolean;
  24. -- Native row serialization covers every column without constructing a JSON object per row.
  25. data_version:=xs_payroll_data_version(co);
  26. source_version:=xs_payroll_source_version_fast(co);
  27. version:=md5(jsonb_build_array(data_version,source_version,run."runId",run."finishedAt",datasets,conflicts)::text);
  28. SELECT * INTO approval FROM "PayrollCutoffApproval" WHERE company=co AND month=mon;
  29. ready:=complete AND pending=0 AND approval.version=version AND NOT EXISTS(SELECT 1 FROM "LegacyDirectRun" WHERE company=co AND status='running');
  30. RETURN jsonb_build_object('month',mon,'version',version,'sourceVersion',source_version,'runId',run."runId",'cutoffAt',run."finishedAt",'complete',complete,'pending',pending,'conflicts',conflicts,'datasets',datasets,'approved',COALESCE(ready,false),'monthClosed',COALESCE(run."finishedAt">=((mon||'-01')::date+interval '1 month') AT TIME ZONE 'Asia/Shanghai',false),'approval',to_jsonb(approval));
  31. END $function$;