mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-10 05:12:14 +00:00
Merge branch 'v1424/fixes' into release/v1.42.4
This commit is contained in:
commit
de40db7ad0
49 files changed
+2579
-269
No files matched your search
@@ -63,6 +63,7 @@ export const GET = apiHandler(async (request: NextRequest) => {
|
||||
practitionerId: params.get("practitionerId") ?? undefined,
|
||||
episodeId: params.get("episodeId") ?? undefined,
|
||||
limit: params.get("limit") ?? undefined,
|
||||
offset: params.get("offset") ?? undefined,
|
||||
});
|
||||
if (!parsed.success) {
|
||||
return returnAllZodIssues(parsed.error, 422, {
|
||||
@@ -72,6 +73,7 @@ export const GET = apiHandler(async (request: NextRequest) => {
|
||||
|
||||
const query = parsed.data;
|
||||
const take = query.limit ?? 100;
|
||||
const skip = query.offset ?? 0;
|
||||
const now = new Date();
|
||||
|
||||
const where = {
|
||||
@@ -112,8 +114,9 @@ export const GET = apiHandler(async (request: NextRequest) => {
|
||||
// `gt` and `gte` are different keys, so the caller's lower bound and this
|
||||
// arm's floor both survive the spread; only the `lte` pair collided.
|
||||
where: { ...where, occurredAt: { ...where.occurredAt, gt: now } },
|
||||
orderBy: { occurredAt: "asc" },
|
||||
orderBy: [{ occurredAt: "asc" }, { id: "asc" }],
|
||||
take,
|
||||
skip,
|
||||
include: ENCOUNTER_INCLUDE,
|
||||
}),
|
||||
prisma.encounter.findMany({
|
||||
@@ -121,8 +124,9 @@ export const GET = apiHandler(async (request: NextRequest) => {
|
||||
...where,
|
||||
occurredAt: { ...where.occurredAt, lte: pastCeiling },
|
||||
},
|
||||
orderBy: { occurredAt: "desc" },
|
||||
orderBy: [{ occurredAt: "desc" }, { id: "desc" }],
|
||||
take,
|
||||
skip,
|
||||
include: ENCOUNTER_INCLUDE,
|
||||
}),
|
||||
]);
|
||||
|
||||
@@ -43,6 +43,11 @@ export const GET = apiHandler(async (request: NextRequest) => {
|
||||
const parsed = illnessEpisodeListQuerySchema.safeParse({
|
||||
limit: params.get("limit") ?? undefined,
|
||||
includeResolved: params.get("includeResolved") ?? undefined,
|
||||
offset: params.get("offset") ?? undefined,
|
||||
from: params.get("from") ?? undefined,
|
||||
to: params.get("to") ?? undefined,
|
||||
sortDir: params.get("sortDir") ?? undefined,
|
||||
q: params.get("q") ?? undefined,
|
||||
});
|
||||
if (!parsed.success) {
|
||||
return returnAllZodIssues(parsed.error, 422, {
|
||||
@@ -50,15 +55,28 @@ export const GET = apiHandler(async (request: NextRequest) => {
|
||||
});
|
||||
}
|
||||
|
||||
const { from, to, q, offset, sortDir } = parsed.data;
|
||||
const limit = parsed.data.limit ?? 50;
|
||||
const order = sortDir ?? "desc";
|
||||
const rows = await prisma.illnessEpisode.findMany({
|
||||
where: {
|
||||
userId: user.id,
|
||||
deletedAt: null,
|
||||
...(parsed.data.includeResolved === "false" ? { resolvedAt: null } : {}),
|
||||
...(from || to
|
||||
? {
|
||||
onsetAt: {
|
||||
...(from ? { gte: new Date(from) } : {}),
|
||||
...(to ? { lte: new Date(to) } : {}),
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
...(q ? { label: { contains: q, mode: "insensitive" as const } } : {}),
|
||||
},
|
||||
orderBy: { onsetAt: "desc" },
|
||||
// `id` breaks ties so offset paging never repeats or drops a row.
|
||||
orderBy: [{ onsetAt: order }, { id: order }],
|
||||
take: limit,
|
||||
skip: offset ?? 0,
|
||||
});
|
||||
|
||||
annotate({
|
||||
|
||||
@@ -85,7 +85,7 @@ export const GET = apiHandler(async (request: NextRequest) => {
|
||||
return returnAllZodIssues(parsed.error, 422);
|
||||
}
|
||||
|
||||
const { biomarkerId, analyte, panel, from, to, limit, offset, sortDir } =
|
||||
const { biomarkerId, analyte, panel, q, from, to, limit, offset, sortDir } =
|
||||
parsed.data;
|
||||
|
||||
const where = {
|
||||
@@ -94,6 +94,12 @@ export const GET = apiHandler(async (request: NextRequest) => {
|
||||
...(biomarkerId && { biomarkerId }),
|
||||
...(analyte && { analyte }),
|
||||
...(panel && { panel }),
|
||||
...(q && {
|
||||
OR: [
|
||||
{ analyte: { contains: q, mode: "insensitive" as const } },
|
||||
{ panel: { contains: q, mode: "insensitive" as const } },
|
||||
],
|
||||
}),
|
||||
...(from || to
|
||||
? {
|
||||
takenAt: {
|
||||
|
||||
@@ -1380,9 +1380,11 @@ describe("POST /api/medications/intake/bulk — v1.28 Apple Health dose events",
|
||||
});
|
||||
|
||||
it("stamps the externalId onto the pending slot row when the Apple dose converges by band", async () => {
|
||||
// Dedup pre-check first (empty), then the canonical-slot find returns
|
||||
// the worker-minted pending REMINDER row for the 19:00 slot.
|
||||
// Dedup pre-check first (empty), then the held-dose probe (no take on
|
||||
// the slot yet), then the canonical-slot find returns the worker-minted
|
||||
// pending REMINDER row for the 19:00 slot.
|
||||
vi.mocked(prisma.medicationIntakeEvent.findMany)
|
||||
.mockResolvedValueOnce([] as never)
|
||||
.mockResolvedValueOnce([] as never)
|
||||
.mockResolvedValueOnce([
|
||||
{
|
||||
|
||||
@@ -65,6 +65,7 @@ import {
|
||||
resolveForcedSlotForWrite,
|
||||
resolveSlotForWriteByBand,
|
||||
resolveSlotInstantForWrite,
|
||||
slotHeldByAnotherDose,
|
||||
} from "@/lib/medications/scheduling/slot-upsert";
|
||||
import { resolveInjectionSiteForWrite } from "@/lib/medications/injection-site-write";
|
||||
import {
|
||||
@@ -338,6 +339,9 @@ async function postBulk(request: NextRequest): Promise<Response> {
|
||||
let inserted = 0;
|
||||
let updated = 0;
|
||||
let duplicates = 0;
|
||||
// External dose records kept as their own unscheduled dose because their
|
||||
// band slot already carried a different one.
|
||||
let extraDosesUnscheduled = 0;
|
||||
const skipped: Array<{ index: number; reason: string }> = [];
|
||||
const unstableShapes: UnstableExternalIdShape[] = [];
|
||||
// v1.4.39 W-MED — collect distinct `(medicationId, dayKey)` pairs
|
||||
@@ -443,6 +447,9 @@ async function postBulk(request: NextRequest): Promise<Response> {
|
||||
// a band decision. Pending echoes / skips carry no decision
|
||||
// (undefined → the upsert leaves an existing USER_PIN untouched).
|
||||
let attributionSource: "AUTO" | "USER_PIN" | undefined;
|
||||
// Set when the band slot is held by a different dose and this take is
|
||||
// recorded unscheduled; it must not converge onto any slot row below.
|
||||
let heldByAnotherDose = false;
|
||||
if (isExplicitTaken && entry.forceSlotInstant !== undefined) {
|
||||
canonicalSlot = await resolveForcedSlotForWrite({
|
||||
userId: user.id,
|
||||
@@ -486,6 +493,26 @@ async function postBulk(request: NextRequest): Promise<Response> {
|
||||
});
|
||||
canonicalSlot = attribution.slotInstant;
|
||||
attributionSource = "AUTO";
|
||||
// An external dose record (a HealthKit dose event) whose band slot
|
||||
// already carries a different recorded dose is an extra dose, not a
|
||||
// correction of that one. Converging would overwrite the slot's
|
||||
// take last-write-wins and the extra dose would exist nowhere; it is
|
||||
// recorded as its own unscheduled dose instead.
|
||||
if (
|
||||
canonicalSlot !== null &&
|
||||
entry.externalId !== undefined &&
|
||||
(await slotHeldByAnotherDose({
|
||||
userId: user.id,
|
||||
medicationId: entry.medicationId,
|
||||
canonicalSlot,
|
||||
takenAt: entry.takenAt as Date,
|
||||
externalId: entry.externalId,
|
||||
}))
|
||||
) {
|
||||
canonicalSlot = null;
|
||||
heldByAnotherDose = true;
|
||||
extraDosesUnscheduled += 1;
|
||||
}
|
||||
} else if (isExplicitSkip && entry.scheduledFor === undefined) {
|
||||
// v1.16.9 — a slot-less deliberate skip resolves by band
|
||||
// membership on the skip moment, exactly like a take does. The
|
||||
@@ -666,6 +693,7 @@ async function postBulk(request: NextRequest): Promise<Response> {
|
||||
// late-morning dose consuming the evening slot). It records
|
||||
// standalone (ad-hoc) instead.
|
||||
const existingSlotRow =
|
||||
!heldByAnotherDose &&
|
||||
entry.scheduledFor !== undefined &&
|
||||
mayConvergeOntoSuppliedSlot({
|
||||
skipped: entry.skipped,
|
||||
@@ -878,6 +906,7 @@ async function postBulk(request: NextRequest): Promise<Response> {
|
||||
updated,
|
||||
duplicates,
|
||||
skipped: skipped.length,
|
||||
extra_doses_unscheduled: extraDosesUnscheduled,
|
||||
...(unstableShapes.length > 0
|
||||
? unstableExternalIdMeta("medication.intake.bulk", unstableShapes)
|
||||
: {}),
|
||||
|
||||
Reference in new issue
Block a user