mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-15 15:02:04 +00:00
feat(vaccinations): let a logged dose clear the boosters it answers
- the dose is expanded to its component antigens first, so one Tdap entry settles a tetanus booster and a pertussis booster at once; matching the preparation's own slug would leave both standing - it runs inside the create transaction and nowhere else: no cron, no ingest hook, and an edit never re-runs it, so correcting a typo in a date cannot move a reminder - forward-only, which is what makes transcribing an old record safe: a page from decades ago never clears a booster that is due now - a free-text-only dose matches nothing; a name string is not evidence about what was in the syringe - an ordinary preventive-care reminder carries no antigen key and is structurally out of reach
This commit is contained in:
1 parent
7ac81f3eed
commit
82982038cb
4 files changed
+572
No files matched your search
@@ -44,8 +44,10 @@ import {
|
||||
applyVaccinationLinks,
|
||||
resolveOwnedEncounter,
|
||||
resolveOwnedPractitioner,
|
||||
resolveOwnerTimezone,
|
||||
resolveSeriesFor,
|
||||
} from "@/lib/vaccinations/service";
|
||||
import { satisfyBoostersForDose } from "@/lib/vaccinations/satisfy-matching";
|
||||
|
||||
export const GET = apiHandler(async (request: NextRequest) => {
|
||||
const { user } = await requireRecordAuth("read", "profile");
|
||||
@@ -130,6 +132,11 @@ async function postVaccination(request: NextRequest): Promise<Response> {
|
||||
|
||||
const entry = parsed.data;
|
||||
const occurredAt = new Date(entry.occurredAt);
|
||||
const timezone = await resolveOwnerTimezone(user.id);
|
||||
|
||||
// Filled inside the transaction, read after it for the wide event: an
|
||||
// annotation is not worth holding a transaction open for.
|
||||
let satisfiedReminderIds: string[] = [];
|
||||
|
||||
const created = await prisma.$transaction(async (tx) => {
|
||||
// Every id the body carries is re-narrowed to the resolved account before
|
||||
@@ -169,6 +176,26 @@ async function postVaccination(request: NextRequest): Promise<Response> {
|
||||
|
||||
await applyVaccinationLinks(tx, user.id, row.id, entry.documentIds);
|
||||
|
||||
// The booster arm, inside the same transaction so a dose that failed to
|
||||
// save cannot leave a reminder looking settled. It runs on the OWNER's
|
||||
// reminders, which is correct even when a delegate is transcribing: it is
|
||||
// the owner's booster plan, and filing their Impfpass is exactly the work
|
||||
// that should settle it.
|
||||
const boosters = await satisfyBoostersForDose(
|
||||
tx,
|
||||
user.id,
|
||||
entry.antigenSlug ?? null,
|
||||
occurredAt,
|
||||
timezone,
|
||||
);
|
||||
satisfiedReminderIds = boosters.satisfiedReminderIds;
|
||||
if (boosters.primaryReminderId) {
|
||||
await tx.vaccinationRecord.update({
|
||||
where: { id: row.id },
|
||||
data: { reminderId: boosters.primaryReminderId },
|
||||
});
|
||||
}
|
||||
|
||||
return tx.vaccinationRecord.findUniqueOrThrow({
|
||||
where: { id: row.id },
|
||||
include: VACCINATION_INCLUDE,
|
||||
@@ -209,8 +236,19 @@ async function postVaccination(request: NextRequest): Promise<Response> {
|
||||
meta: {
|
||||
antigen_slug: created.antigenSlug ?? "free-text",
|
||||
linked_documents: entry.documentIds?.length ?? 0,
|
||||
satisfied_boosters: satisfiedReminderIds.length,
|
||||
},
|
||||
});
|
||||
for (const reminderId of satisfiedReminderIds) {
|
||||
annotate({
|
||||
action: {
|
||||
name: "vaccination.booster.satisfied",
|
||||
entity_type: "measurement-reminder",
|
||||
entity_id: reminderId,
|
||||
},
|
||||
meta: { antigen_slug: created.antigenSlug ?? "free-text" },
|
||||
});
|
||||
}
|
||||
|
||||
return apiSuccess(
|
||||
toVaccinationDTO(created, series.get(created.id) ?? []),
|
||||
|
||||
Reference in new issue
Block a user