@open-mercato/core 0.10.1-develop.7723.1.3b2ea82ea7 → 0.10.1-develop.7726.1.a447cc5525
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/dist/generated/entities/sales_order/index.js +2 -0
- package/dist/generated/entities/sales_order/index.js.map +2 -2
- package/dist/generated/entities/sales_order_line/index.js +2 -0
- package/dist/generated/entities/sales_order_line/index.js.map +2 -2
- package/dist/generated/entity-fields-registry.js +2 -0
- package/dist/generated/entity-fields-registry.js.map +2 -2
- package/dist/modules/planner/api/availability-date-specific.js +2 -7
- package/dist/modules/planner/api/availability-date-specific.js.map +2 -2
- package/dist/modules/planner/commands/availability-date-specific.js +14 -31
- package/dist/modules/planner/commands/availability-date-specific.js.map +2 -2
- package/dist/modules/planner/commands/availability-weekly.js +8 -22
- package/dist/modules/planner/commands/availability-weekly.js.map +2 -2
- package/dist/modules/planner/lib/availabilityMerge.js +10 -18
- package/dist/modules/planner/lib/availabilityMerge.js.map +2 -2
- package/dist/modules/planner/lib/availabilityTimezone.js +161 -0
- package/dist/modules/planner/lib/availabilityTimezone.js.map +7 -0
- package/dist/modules/sales/api/documents/factory.js +3 -0
- package/dist/modules/sales/api/documents/factory.js.map +2 -2
- package/dist/modules/sales/api/order-lines/route.js +2 -1
- package/dist/modules/sales/api/order-lines/route.js.map +2 -2
- package/dist/modules/sales/backend/sales/documents/[id]/page.js +83 -6
- package/dist/modules/sales/backend/sales/documents/[id]/page.js.map +2 -2
- package/dist/modules/sales/commands/documents.js +141 -18
- package/dist/modules/sales/commands/documents.js.map +2 -2
- package/dist/modules/sales/commands/returns.js +24 -7
- package/dist/modules/sales/commands/returns.js.map +2 -2
- package/dist/modules/sales/components/documents/AdjustmentsSection.js +17 -11
- package/dist/modules/sales/components/documents/AdjustmentsSection.js.map +2 -2
- package/dist/modules/sales/components/documents/DocumentTotals.js +21 -3
- package/dist/modules/sales/components/documents/DocumentTotals.js.map +2 -2
- package/dist/modules/sales/components/documents/ItemsSection.js +23 -12
- package/dist/modules/sales/components/documents/ItemsSection.js.map +2 -2
- package/dist/modules/sales/data/entities.js +10 -0
- package/dist/modules/sales/data/entities.js.map +2 -2
- package/dist/modules/sales/data/validators.js +9 -1
- package/dist/modules/sales/data/validators.js.map +2 -2
- package/dist/modules/sales/lib/calculations.js +85 -6
- package/dist/modules/sales/lib/calculations.js.map +2 -2
- package/dist/modules/sales/lib/externalAmounts.js +143 -0
- package/dist/modules/sales/lib/externalAmounts.js.map +7 -0
- package/dist/modules/sales/lib/lineSnapshots.js +6 -1
- package/dist/modules/sales/lib/lineSnapshots.js.map +2 -2
- package/dist/modules/sales/lib/makeSalesLineRoute.js +8 -2
- package/dist/modules/sales/lib/makeSalesLineRoute.js.map +2 -2
- package/dist/modules/sales/lib/providers/totals.js +3 -2
- package/dist/modules/sales/lib/providers/totals.js.map +2 -2
- package/dist/modules/sales/migrations/Migration20260910120000_sales_external_amounts_mode.js +19 -0
- package/dist/modules/sales/migrations/Migration20260910120000_sales_external_amounts_mode.js.map +7 -0
- package/generated/entities/sales_order/index.ts +1 -0
- package/generated/entities/sales_order_line/index.ts +1 -0
- package/generated/entity-fields-registry.ts +2 -0
- package/package.json +8 -7
- package/src/modules/planner/api/availability-date-specific.ts +5 -10
- package/src/modules/planner/commands/availability-date-specific.ts +14 -33
- package/src/modules/planner/commands/availability-weekly.ts +15 -24
- package/src/modules/planner/lib/availabilityMerge.ts +13 -19
- package/src/modules/planner/lib/availabilityTimezone.ts +242 -0
- package/src/modules/sales/api/documents/factory.ts +3 -0
- package/src/modules/sales/api/order-lines/route.ts +2 -1
- package/src/modules/sales/backend/sales/documents/[id]/page.tsx +96 -0
- package/src/modules/sales/commands/documents.ts +219 -44
- package/src/modules/sales/commands/returns.ts +27 -3
- package/src/modules/sales/components/documents/AdjustmentsSection.tsx +34 -25
- package/src/modules/sales/components/documents/DocumentTotals.tsx +26 -2
- package/src/modules/sales/components/documents/ItemsSection.tsx +27 -12
- package/src/modules/sales/data/entities.ts +14 -0
- package/src/modules/sales/data/validators.ts +10 -0
- package/src/modules/sales/i18n/de.json +12 -0
- package/src/modules/sales/i18n/en.json +12 -0
- package/src/modules/sales/i18n/es.json +12 -0
- package/src/modules/sales/i18n/ko.json +12 -0
- package/src/modules/sales/i18n/pl.json +12 -0
- package/src/modules/sales/lib/calculations.ts +99 -4
- package/src/modules/sales/lib/externalAmounts.ts +174 -0
- package/src/modules/sales/lib/lineSnapshots.ts +6 -1
- package/src/modules/sales/lib/makeSalesLineRoute.ts +8 -0
- package/src/modules/sales/lib/providers/totals.ts +5 -2
- package/src/modules/sales/lib/types.ts +19 -2
- package/src/modules/sales/migrations/.snapshot-open-mercato.json +33 -1
- package/src/modules/sales/migrations/Migration20260910120000_sales_external_amounts_mode.ts +25 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/planner/lib/availabilityMerge.ts"],
|
|
4
|
-
"sourcesContent": ["type AvailabilityKind = 'availability' | 'unavailability'\n\nexport type AvailabilityRuleLike = {\n id?: string\n rrule: string\n timezone?: string\n exdates?: string[]\n kind?: AvailabilityKind\n note?: string | null\n}\n\nexport type AvailabilityRange = {\n start: Date\n end: Date\n}\n\nexport type AvailabilityWindow = {\n start: Date\n end: Date\n ruleId?: string\n}\n\ntype ParsedRule = {\n startAt: Date\n durationMinutes: number\n freq: 'DAILY' | 'WEEKLY'\n repeat: 'once' | 'daily' | 'weekly'\n count?: number\n}\n\nconst DAY_MS = 24 * 60 * 60 * 1000\nconst timezoneFormatters = new Map<string, Intl.DateTimeFormat>()\n\nfunction zonedParts(instant: Date, timezone: string): { year: number; month: number; day: number; hour: number; minute: number; second: number } {\n let formatter = timezoneFormatters.get(timezone)\n if (!formatter) {\n formatter = new Intl.DateTimeFormat('en-US', {\n timeZone: timezone, year: 'numeric', month: '2-digit', day: '2-digit',\n hour: '2-digit', minute: '2-digit', second: '2-digit', hourCycle: 'h23',\n })\n if (timezoneFormatters.size >= 64) timezoneFormatters.delete(timezoneFormatters.keys().next().value!)\n timezoneFormatters.set(timezone, formatter)\n }\n const parts = formatter.formatToParts(instant)\n const number = (kind: string) => Number(parts.find((part) => part.type === kind)?.value)\n return { year: number('year'), month: number('month'), day: number('day'), hour: number('hour'), minute: number('minute'), second: number('second') }\n}\n\nfunction zonedWallToInstant(wall: Date, timezone: string): Date | null {\n const target = wall.getTime()\n let candidate = target\n for (let attempt = 0; attempt < 4; attempt += 1) {\n const parts = zonedParts(new Date(candidate), timezone)\n const displayed = Date.UTC(parts.year, parts.month - 1, parts.day, parts.hour, parts.minute, parts.second)\n const next = candidate + target - displayed\n if (next === candidate) return new Date(candidate)\n candidate = next\n }\n return null\n}\n\nfunction zonedStartOfDay(dayKey: string, timezone: string): Date | null {\n return zonedWallToInstant(new Date(`${dayKey}T00:00:00Z`), timezone)\n}\n\nfunction dayKeyForRule(instant: Date, timezone?: string): string {\n if (!timezone || timezone === 'UTC' || timezone === 'Etc/UTC' || timezone === 'Etc/GMT') return toDayKey(instant)\n const parts = zonedParts(instant, timezone)\n return `${parts.year}-${String(parts.month).padStart(2, '0')}-${String(parts.day).padStart(2, '0')}`\n}\n\nfunction parseRrule(rule: string): ParsedRule | null {\n const dtStartMatch = rule.match(/DTSTART[:=](\\d{8}T\\d{6}Z?)/)\n const durationMatch = rule.match(/DURATION:PT(?:(\\d+)H)?(?:(\\d+)M)?/)\n const freqMatch = rule.match(/FREQ=([A-Z]+)/)\n if (!dtStartMatch?.[1] || !durationMatch || !freqMatch?.[1]) return null\n const raw = dtStartMatch[1].replace(/Z$/, '')\n const parts = raw.match(/(\\d{4})(\\d{2})(\\d{2})T(\\d{2})(\\d{2})(\\d{2})/)\n if (!parts) return null\n const [, year, month, day, hour, minute, second] = parts\n const iso = `${year}-${month}-${day}T${hour}:${minute}:${second}Z`\n const startAt = new Date(iso)\n if (Number.isNaN(startAt.getTime())) return null\n\n const hours = durationMatch[1] ? Number(durationMatch[1]) : 0\n const minutes = durationMatch[2] ? Number(durationMatch[2]) : 0\n const durationMinutes = Math.max(1, hours * 60 + minutes)\n const freq = freqMatch[1]\n if (freq !== 'DAILY' && freq !== 'WEEKLY') return null\n\n const countMatch = rule.match(/COUNT=(\\d+)/)\n const count = countMatch?.[1] ? Number(countMatch[1]) : undefined\n const repeat = freq === 'WEEKLY'\n ? 'weekly'\n : freq === 'DAILY' && count === 1\n ? 'once'\n : 'daily'\n return { startAt, durationMinutes, freq, count, repeat }\n}\n\nfunction buildExdateSets(exdates?: string[]) {\n const dateOnly = new Set<string>()\n const dateTime = new Set<number>()\n ;(exdates ?? []).forEach((value) => {\n if (typeof value !== 'string') return\n const trimmed = value.trim()\n if (!trimmed) return\n if (/^\\d{4}-\\d{2}-\\d{2}$/.test(trimmed)) {\n dateOnly.add(trimmed)\n return\n }\n const parsed = new Date(trimmed)\n if (!Number.isNaN(parsed.getTime())) {\n dateTime.add(parsed.getTime())\n }\n })\n return { dateOnly, dateTime }\n}\n\nfunction shouldExcludeOccurrence(startAt: Date, exdates?: string[], timezone?: string): boolean {\n const { dateOnly, dateTime } = buildExdateSets(exdates)\n if (dateTime.has(startAt.getTime())) return true\n const dayKey = dayKeyForRule(startAt, timezone)\n return dateOnly.has(dayKey)\n}\n\nfunction toDayKey(value: Date): string {\n return value.toISOString().slice(0, 10)\n}\n\nfunction startOfDay(value: Date): Date {\n return new Date(Date.UTC(value.getUTCFullYear(), value.getUTCMonth(), value.getUTCDate()))\n}\n\nfunction expandZonedRule(rule: AvailabilityRuleLike, parsed: ParsedRule, range: AvailabilityRange, weeklyScheduleTemplate: boolean): AvailabilityWindow[] {\n const timezone = rule.timezone!\n const anchor = zonedParts(parsed.startAt, timezone)\n const anchorWall = Date.UTC(anchor.year, anchor.month - 1, anchor.day, anchor.hour, anchor.minute, anchor.second)\n const step = parsed.freq === 'WEEKLY' ? 7 * DAY_MS : DAY_MS\n const duration = parsed.durationMinutes * 60000\n const windows: AvailabilityWindow[] = []\n const rangeAnchor = zonedParts(range.start, timezone)\n const rangeStartWall = Date.UTC(rangeAnchor.year, rangeAnchor.month - 1, rangeAnchor.day, rangeAnchor.hour, rangeAnchor.minute, rangeAnchor.second)\n const rangeOffset = Math.floor((rangeStartWall - duration - DAY_MS - anchorWall) / step)\n const skipped = weeklyScheduleTemplate && parsed.freq === 'WEEKLY' && parsed.count === undefined ? rangeOffset : Math.max(0, rangeOffset)\n let wall = new Date(anchorWall + skipped * step)\n let remaining = (parsed.count ?? Number.POSITIVE_INFINITY) - skipped\n while (wall.getTime() <= range.end.getTime() + DAY_MS && remaining > 0) {\n const start = zonedWallToInstant(wall, timezone)\n if (start && !shouldExcludeOccurrence(start, rule.exdates, timezone)) {\n const end = new Date(start.getTime() + duration)\n if (end > range.start && start < range.end) windows.push({ start, end, ruleId: rule.id })\n }\n wall = new Date(wall.getTime() + step)\n remaining -= 1\n }\n return windows\n}\n\nfunction expandRule(rule: AvailabilityRuleLike, parsed: ParsedRule, range: AvailabilityRange, weeklyScheduleTemplate: boolean): AvailabilityWindow[] {\n const { startAt, durationMinutes, freq, count, repeat } = parsed\n if (repeat === 'once') {\n if (shouldExcludeOccurrence(startAt, rule.exdates, rule.timezone)) return []\n const dayKey = dayKeyForRule(startAt, rule.timezone)\n const start = rule.timezone ? zonedStartOfDay(dayKey, rule.timezone) : startOfDay(startAt)\n if (!start) return []\n const nextDay = new Date(new Date(`${dayKey}T00:00:00Z`).getTime() + DAY_MS).toISOString().slice(0, 10)\n const end = rule.timezone ? zonedStartOfDay(nextDay, rule.timezone) : new Date(start.getTime() + DAY_MS)\n if (!end) return []\n if (end <= range.start || start >= range.end) return []\n return [{ start, end, ruleId: rule.id }]\n }\n if (rule.timezone && !['UTC', 'Etc/UTC', 'Etc/GMT'].includes(rule.timezone)) {\n return expandZonedRule(rule, parsed, range, weeklyScheduleTemplate)\n }\n const durationMs = durationMinutes * 60000\n const windows: AvailabilityWindow[] = []\n const addWindow = (start: Date) => {\n if (shouldExcludeOccurrence(start, rule.exdates)) return\n const end = new Date(start.getTime() + durationMs)\n if (end <= range.start || start >= range.end) return\n windows.push({ start, end, ruleId: rule.id })\n }\n\n const step = freq === 'WEEKLY' ? 7 * DAY_MS : DAY_MS\n const rangeOffset = Math.floor((range.start.getTime() - durationMs - startAt.getTime()) / step)\n const skipped = weeklyScheduleTemplate && freq === 'WEEKLY' && count === undefined ? rangeOffset : Math.max(0, rangeOffset)\n let cursor = new Date(startAt.getTime() + skipped * step)\n let remaining = (count ?? Number.POSITIVE_INFINITY) - skipped\n while (cursor < range.end && remaining > 0) {\n addWindow(new Date(cursor))\n cursor = new Date(cursor.getTime() + step)\n remaining -= 1\n }\n return windows\n}\n\nfunction expandRules(rules: AvailabilityRuleLike[], range: AvailabilityRange, weeklyScheduleTemplate: boolean): AvailabilityWindow[] {\n const expanded = rules.flatMap((rule) => {\n const parsed = parseRrule(rule.rrule)\n if (!parsed) return []\n return expandRule(rule, parsed, range, weeklyScheduleTemplate)\n })\n return expanded.sort((a, b) => a.start.getTime() - b.start.getTime())\n}\n\nfunction subtractWindow(window: AvailabilityWindow, blockers: AvailabilityWindow[]): AvailabilityWindow[] {\n let segments = [window]\n blockers.forEach((blocker) => {\n const next: AvailabilityWindow[] = []\n segments.forEach((segment) => {\n if (blocker.end <= segment.start || blocker.start >= segment.end) {\n next.push(segment)\n return\n }\n if (blocker.start > segment.start) {\n next.push({ ...segment, end: new Date(blocker.start) })\n }\n if (blocker.end < segment.end) {\n next.push({ ...segment, start: new Date(blocker.end) })\n }\n })\n segments = next\n })\n return segments\n}\n\nexport function getMergedAvailabilityWindows(params: {\n rules: AvailabilityRuleLike[]\n range: AvailabilityRange\n respectTimezone?: boolean\n weeklyScheduleTemplate?: boolean\n}): AvailabilityWindow[] {\n const rules = params.respectTimezone ? params.rules : params.rules.map((rule) => ({ ...rule, timezone: undefined }))\n const parsedRules = rules\n .map((rule) => {\n const parsed = parseRrule(rule.rrule)\n if (!parsed) return null\n return { rule, parsed }\n })\n .filter((entry): entry is { rule: AvailabilityRuleLike; parsed: ParsedRule } => entry !== null)\n\n const overrideDays = new Map<string, { kind: AvailabilityKind; dayKey: string; timezone?: string }>()\n parsedRules\n .filter(({ parsed }) => parsed.repeat === 'once')\n .forEach(({ rule, parsed }) => {\n const dayKey = dayKeyForRule(parsed.startAt, rule.timezone)\n const overrideKey = `${rule.timezone ?? 'UTC'}|${dayKey}`\n const kind = rule.kind === 'unavailability' ? 'unavailability' : 'availability'\n if (kind === 'unavailability') {\n overrideDays.set(overrideKey, { kind, dayKey, timezone: rule.timezone })\n } else if (!overrideDays.has(overrideKey)) {\n overrideDays.set(overrideKey, { kind, dayKey, timezone: rule.timezone })\n }\n })\n\n const availabilityRules = parsedRules\n .filter(({ parsed, rule }) => parsed.repeat !== 'once' && rule.kind !== 'unavailability')\n .map(({ rule }) => rule)\n const unavailabilityRules = parsedRules\n .filter(({ parsed, rule }) => parsed.repeat !== 'once' && rule.kind === 'unavailability')\n .map(({ rule }) => rule)\n\n const availabilityWindows = availabilityRules.flatMap((rule) =>\n expandRules([rule], params.range, params.weeklyScheduleTemplate === true).filter((window) =>\n !overrideDays.has(`${rule.timezone ?? 'UTC'}|${dayKeyForRule(window.start, rule.timezone)}`)))\n const unavailabilityWindows = expandRules(unavailabilityRules, params.range, params.weeklyScheduleTemplate === true)\n\n const merged = unavailabilityWindows.length === 0\n ? availabilityWindows\n : availabilityWindows.flatMap((window) => subtractWindow(window, unavailabilityWindows))\n\n const overrideWindows: AvailabilityWindow[] = []\n overrideDays.forEach(({ kind, dayKey, timezone }) => {\n if (kind !== 'availability') return\n const start = timezone ? zonedStartOfDay(dayKey, timezone) : new Date(`${dayKey}T00:00:00Z`)\n if (!start) return\n if (Number.isNaN(start.getTime())) return\n const nextDay = new Date(new Date(`${dayKey}T00:00:00Z`).getTime() + DAY_MS).toISOString().slice(0, 10)\n const end = timezone ? zonedStartOfDay(nextDay, timezone) : new Date(start.getTime() + DAY_MS)\n if (!end) return\n if (end <= params.range.start || start >= params.range.end) return\n overrideWindows.push({ start, end })\n })\n\n return [...merged, ...overrideWindows].sort((a, b) => a.start.getTime() - b.start.getTime())\n}\n"],
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["import { resolveTimeZone, zonedWallDateToInstant } from './availabilityTimezone'\n\ntype AvailabilityKind = 'availability' | 'unavailability'\n\nexport type AvailabilityRuleLike = {\n id?: string\n rrule: string\n timezone?: string\n exdates?: string[]\n kind?: AvailabilityKind\n note?: string | null\n}\n\nexport type AvailabilityRange = {\n start: Date\n end: Date\n}\n\nexport type AvailabilityWindow = {\n start: Date\n end: Date\n ruleId?: string\n}\n\ntype ParsedRule = {\n startAt: Date\n durationMinutes: number\n freq: 'DAILY' | 'WEEKLY'\n repeat: 'once' | 'daily' | 'weekly'\n count?: number\n}\n\nconst DAY_MS = 24 * 60 * 60 * 1000\nconst timezoneFormatters = new Map<string, Intl.DateTimeFormat>()\n\nfunction zonedParts(instant: Date, timezone: string): { year: number; month: number; day: number; hour: number; minute: number; second: number } {\n let formatter = timezoneFormatters.get(timezone)\n if (!formatter) {\n formatter = new Intl.DateTimeFormat('en-US', {\n timeZone: timezone, year: 'numeric', month: '2-digit', day: '2-digit',\n hour: '2-digit', minute: '2-digit', second: '2-digit', hourCycle: 'h23',\n })\n if (timezoneFormatters.size >= 64) timezoneFormatters.delete(timezoneFormatters.keys().next().value!)\n timezoneFormatters.set(timezone, formatter)\n }\n const parts = formatter.formatToParts(instant)\n const number = (kind: string) => Number(parts.find((part) => part.type === kind)?.value)\n return { year: number('year'), month: number('month'), day: number('day'), hour: number('hour'), minute: number('minute'), second: number('second') }\n}\n\nfunction zonedStartOfDay(dayKey: string, timezone: string): Date | null {\n return zonedWallDateToInstant(new Date(`${dayKey}T00:00:00Z`), timezone)\n}\n\nfunction dayKeyForRule(instant: Date, timezone?: string): string {\n if (!timezone || timezone === 'UTC' || timezone === 'Etc/UTC' || timezone === 'Etc/GMT') return toDayKey(instant)\n const parts = zonedParts(instant, timezone)\n return `${parts.year}-${String(parts.month).padStart(2, '0')}-${String(parts.day).padStart(2, '0')}`\n}\n\nfunction parseRrule(rule: string): ParsedRule | null {\n const dtStartMatch = rule.match(/DTSTART[:=](\\d{8}T\\d{6}Z?)/)\n const durationMatch = rule.match(/DURATION:PT(?:(\\d+)H)?(?:(\\d+)M)?/)\n const freqMatch = rule.match(/FREQ=([A-Z]+)/)\n if (!dtStartMatch?.[1] || !durationMatch || !freqMatch?.[1]) return null\n const raw = dtStartMatch[1].replace(/Z$/, '')\n const parts = raw.match(/(\\d{4})(\\d{2})(\\d{2})T(\\d{2})(\\d{2})(\\d{2})/)\n if (!parts) return null\n const [, year, month, day, hour, minute, second] = parts\n const iso = `${year}-${month}-${day}T${hour}:${minute}:${second}Z`\n const startAt = new Date(iso)\n if (Number.isNaN(startAt.getTime())) return null\n\n const hours = durationMatch[1] ? Number(durationMatch[1]) : 0\n const minutes = durationMatch[2] ? Number(durationMatch[2]) : 0\n const durationMinutes = Math.max(1, hours * 60 + minutes)\n const freq = freqMatch[1]\n if (freq !== 'DAILY' && freq !== 'WEEKLY') return null\n\n const countMatch = rule.match(/COUNT=(\\d+)/)\n const count = countMatch?.[1] ? Number(countMatch[1]) : undefined\n const repeat = freq === 'WEEKLY'\n ? 'weekly'\n : freq === 'DAILY' && count === 1\n ? 'once'\n : 'daily'\n return { startAt, durationMinutes, freq, count, repeat }\n}\n\nfunction buildExdateSets(exdates?: string[]) {\n const dateOnly = new Set<string>()\n const dateTime = new Set<number>()\n ;(exdates ?? []).forEach((value) => {\n if (typeof value !== 'string') return\n const trimmed = value.trim()\n if (!trimmed) return\n if (/^\\d{4}-\\d{2}-\\d{2}$/.test(trimmed)) {\n dateOnly.add(trimmed)\n return\n }\n const parsed = new Date(trimmed)\n if (!Number.isNaN(parsed.getTime())) {\n dateTime.add(parsed.getTime())\n }\n })\n return { dateOnly, dateTime }\n}\n\nfunction shouldExcludeOccurrence(startAt: Date, exdates?: string[], timezone?: string): boolean {\n const { dateOnly, dateTime } = buildExdateSets(exdates)\n if (dateTime.has(startAt.getTime())) return true\n const dayKey = dayKeyForRule(startAt, timezone)\n return dateOnly.has(dayKey)\n}\n\nfunction toDayKey(value: Date): string {\n return value.toISOString().slice(0, 10)\n}\n\nfunction startOfDay(value: Date): Date {\n return new Date(Date.UTC(value.getUTCFullYear(), value.getUTCMonth(), value.getUTCDate()))\n}\n\nfunction expandZonedRule(rule: AvailabilityRuleLike, parsed: ParsedRule, range: AvailabilityRange, weeklyScheduleTemplate: boolean): AvailabilityWindow[] {\n const timezone = rule.timezone!\n const anchor = zonedParts(parsed.startAt, timezone)\n const anchorWall = Date.UTC(anchor.year, anchor.month - 1, anchor.day, anchor.hour, anchor.minute, anchor.second)\n const step = parsed.freq === 'WEEKLY' ? 7 * DAY_MS : DAY_MS\n const duration = parsed.durationMinutes * 60000\n const windows: AvailabilityWindow[] = []\n const rangeAnchor = zonedParts(range.start, timezone)\n const rangeStartWall = Date.UTC(rangeAnchor.year, rangeAnchor.month - 1, rangeAnchor.day, rangeAnchor.hour, rangeAnchor.minute, rangeAnchor.second)\n const rangeOffset = Math.floor((rangeStartWall - duration - DAY_MS - anchorWall) / step)\n const skipped = weeklyScheduleTemplate && parsed.freq === 'WEEKLY' && parsed.count === undefined ? rangeOffset : Math.max(0, rangeOffset)\n let wall = new Date(anchorWall + skipped * step)\n let remaining = (parsed.count ?? Number.POSITIVE_INFINITY) - skipped\n while (wall.getTime() <= range.end.getTime() + DAY_MS && remaining > 0) {\n const start = zonedWallDateToInstant(wall, timezone)\n if (!shouldExcludeOccurrence(start, rule.exdates, timezone)) {\n const end = zonedWallDateToInstant(new Date(wall.getTime() + duration), timezone)\n if (end > start && end > range.start && start < range.end) windows.push({ start, end, ruleId: rule.id })\n }\n wall = new Date(wall.getTime() + step)\n remaining -= 1\n }\n return windows\n}\n\nfunction expandRule(rule: AvailabilityRuleLike, parsed: ParsedRule, range: AvailabilityRange, weeklyScheduleTemplate: boolean): AvailabilityWindow[] {\n const { startAt, durationMinutes, freq, count, repeat } = parsed\n if (repeat === 'once') {\n if (shouldExcludeOccurrence(startAt, rule.exdates, rule.timezone)) return []\n const dayKey = dayKeyForRule(startAt, rule.timezone)\n const start = rule.timezone ? zonedStartOfDay(dayKey, rule.timezone) : startOfDay(startAt)\n if (!start) return []\n const nextDay = new Date(new Date(`${dayKey}T00:00:00Z`).getTime() + DAY_MS).toISOString().slice(0, 10)\n const end = rule.timezone ? zonedStartOfDay(nextDay, rule.timezone) : new Date(start.getTime() + DAY_MS)\n if (!end) return []\n if (end <= range.start || start >= range.end) return []\n return [{ start, end, ruleId: rule.id }]\n }\n if (rule.timezone && !['UTC', 'Etc/UTC', 'Etc/GMT'].includes(rule.timezone)) {\n return expandZonedRule(rule, parsed, range, weeklyScheduleTemplate)\n }\n const durationMs = durationMinutes * 60000\n const windows: AvailabilityWindow[] = []\n const addWindow = (start: Date) => {\n if (shouldExcludeOccurrence(start, rule.exdates)) return\n const end = new Date(start.getTime() + durationMs)\n if (end <= range.start || start >= range.end) return\n windows.push({ start, end, ruleId: rule.id })\n }\n\n const step = freq === 'WEEKLY' ? 7 * DAY_MS : DAY_MS\n const rangeOffset = Math.floor((range.start.getTime() - durationMs - startAt.getTime()) / step)\n const skipped = weeklyScheduleTemplate && freq === 'WEEKLY' && count === undefined ? rangeOffset : Math.max(0, rangeOffset)\n let cursor = new Date(startAt.getTime() + skipped * step)\n let remaining = (count ?? Number.POSITIVE_INFINITY) - skipped\n while (cursor < range.end && remaining > 0) {\n addWindow(new Date(cursor))\n cursor = new Date(cursor.getTime() + step)\n remaining -= 1\n }\n return windows\n}\n\nfunction expandRules(rules: AvailabilityRuleLike[], range: AvailabilityRange, weeklyScheduleTemplate: boolean): AvailabilityWindow[] {\n const expanded = rules.flatMap((rule) => {\n const parsed = parseRrule(rule.rrule)\n if (!parsed) return []\n return expandRule(rule, parsed, range, weeklyScheduleTemplate)\n })\n return expanded.sort((a, b) => a.start.getTime() - b.start.getTime())\n}\n\nfunction subtractWindow(window: AvailabilityWindow, blockers: AvailabilityWindow[]): AvailabilityWindow[] {\n let segments = [window]\n blockers.forEach((blocker) => {\n const next: AvailabilityWindow[] = []\n segments.forEach((segment) => {\n if (blocker.end <= segment.start || blocker.start >= segment.end) {\n next.push(segment)\n return\n }\n if (blocker.start > segment.start) {\n next.push({ ...segment, end: new Date(blocker.start) })\n }\n if (blocker.end < segment.end) {\n next.push({ ...segment, start: new Date(blocker.end) })\n }\n })\n segments = next\n })\n return segments\n}\n\nexport function getMergedAvailabilityWindows(params: {\n rules: AvailabilityRuleLike[]\n range: AvailabilityRange\n respectTimezone?: boolean\n weeklyScheduleTemplate?: boolean\n}): AvailabilityWindow[] {\n const rules: AvailabilityRuleLike[] = params.respectTimezone\n ? params.rules.map((rule) => ({\n ...rule,\n timezone: rule.timezone ? resolveTimeZone(rule.timezone) : rule.timezone,\n }))\n : params.rules.map((rule) => ({ ...rule, timezone: undefined }))\n const parsedRules = rules\n .map((rule) => {\n const parsed = parseRrule(rule.rrule)\n if (!parsed) return null\n return { rule, parsed }\n })\n .filter((entry): entry is { rule: AvailabilityRuleLike; parsed: ParsedRule } => entry !== null)\n\n const overrideDays = new Map<string, { kind: AvailabilityKind; dayKey: string; timezone?: string }>()\n parsedRules\n .filter(({ parsed }) => parsed.repeat === 'once')\n .forEach(({ rule, parsed }) => {\n const dayKey = dayKeyForRule(parsed.startAt, rule.timezone)\n const overrideKey = `${rule.timezone ?? 'UTC'}|${dayKey}`\n const kind = rule.kind === 'unavailability' ? 'unavailability' : 'availability'\n if (kind === 'unavailability') {\n overrideDays.set(overrideKey, { kind, dayKey, timezone: rule.timezone })\n } else if (!overrideDays.has(overrideKey)) {\n overrideDays.set(overrideKey, { kind, dayKey, timezone: rule.timezone })\n }\n })\n\n const availabilityRules = parsedRules\n .filter(({ parsed, rule }) => parsed.repeat !== 'once' && rule.kind !== 'unavailability')\n .map(({ rule }) => rule)\n const unavailabilityRules = parsedRules\n .filter(({ parsed, rule }) => parsed.repeat !== 'once' && rule.kind === 'unavailability')\n .map(({ rule }) => rule)\n\n const availabilityWindows = availabilityRules.flatMap((rule) =>\n expandRules([rule], params.range, params.weeklyScheduleTemplate === true).filter((window) =>\n !overrideDays.has(`${rule.timezone ?? 'UTC'}|${dayKeyForRule(window.start, rule.timezone)}`)))\n const unavailabilityWindows = expandRules(unavailabilityRules, params.range, params.weeklyScheduleTemplate === true)\n\n const merged = unavailabilityWindows.length === 0\n ? availabilityWindows\n : availabilityWindows.flatMap((window) => subtractWindow(window, unavailabilityWindows))\n\n const overrideWindows: AvailabilityWindow[] = []\n overrideDays.forEach(({ kind, dayKey, timezone }) => {\n if (kind !== 'availability') return\n const start = timezone ? zonedStartOfDay(dayKey, timezone) : new Date(`${dayKey}T00:00:00Z`)\n if (!start) return\n if (Number.isNaN(start.getTime())) return\n const nextDay = new Date(new Date(`${dayKey}T00:00:00Z`).getTime() + DAY_MS).toISOString().slice(0, 10)\n const end = timezone ? zonedStartOfDay(nextDay, timezone) : new Date(start.getTime() + DAY_MS)\n if (!end) return\n if (end <= params.range.start || start >= params.range.end) return\n overrideWindows.push({ start, end })\n })\n\n return [...merged, ...overrideWindows].sort((a, b) => a.start.getTime() - b.start.getTime())\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { createLogger } from "@open-mercato/shared/lib/logger";
|
|
2
|
+
const logger = createLogger("planner").child({ component: "availability" });
|
|
3
|
+
const FALLBACK_TIME_ZONE = "UTC";
|
|
4
|
+
const DATE_KEY_PATTERN = /^\d{4}-\d{2}-\d{2}$/;
|
|
5
|
+
function resolveTimeZone(value) {
|
|
6
|
+
if (!isResolvableTimeZone(value)) return FALLBACK_TIME_ZONE;
|
|
7
|
+
return value.trim();
|
|
8
|
+
}
|
|
9
|
+
function isResolvableTimeZone(value) {
|
|
10
|
+
if (typeof value !== "string" || !value.trim().length) return false;
|
|
11
|
+
try {
|
|
12
|
+
new Intl.DateTimeFormat("en-US", { timeZone: value.trim() });
|
|
13
|
+
return true;
|
|
14
|
+
} catch {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function warnOnUnresolvableTimeZone(value, commandId) {
|
|
19
|
+
if (isResolvableTimeZone(value)) return;
|
|
20
|
+
logger.warn("[deprecated] Unresolvable availability timezone anchored to UTC; this will be rejected with 400 in a future minor release", {
|
|
21
|
+
commandId,
|
|
22
|
+
timezone: value,
|
|
23
|
+
fallback: FALLBACK_TIME_ZONE
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
function parseTimeOfDay(value) {
|
|
27
|
+
const [hours, minutes] = String(value).split(":").map((part) => Number(part));
|
|
28
|
+
if (!Number.isFinite(hours) || !Number.isFinite(minutes)) return null;
|
|
29
|
+
if (hours < 0 || hours > 23 || minutes < 0 || minutes > 59) return null;
|
|
30
|
+
return { hours, minutes };
|
|
31
|
+
}
|
|
32
|
+
const MINUTE_MS = 60 * 1e3;
|
|
33
|
+
const DAY_MS = 24 * 60 * MINUTE_MS;
|
|
34
|
+
const formatterCache = /* @__PURE__ */ new Map();
|
|
35
|
+
function zonedFormatter(timeZone) {
|
|
36
|
+
let formatter = formatterCache.get(timeZone);
|
|
37
|
+
if (!formatter) {
|
|
38
|
+
formatter = new Intl.DateTimeFormat("en-US", {
|
|
39
|
+
timeZone,
|
|
40
|
+
hourCycle: "h23",
|
|
41
|
+
year: "numeric",
|
|
42
|
+
month: "2-digit",
|
|
43
|
+
day: "2-digit",
|
|
44
|
+
hour: "2-digit",
|
|
45
|
+
minute: "2-digit",
|
|
46
|
+
second: "2-digit"
|
|
47
|
+
});
|
|
48
|
+
formatterCache.set(timeZone, formatter);
|
|
49
|
+
}
|
|
50
|
+
return formatter;
|
|
51
|
+
}
|
|
52
|
+
function zonedWallClock(epochMs, timeZone) {
|
|
53
|
+
const values = {};
|
|
54
|
+
for (const part of zonedFormatter(timeZone).formatToParts(new Date(epochMs))) {
|
|
55
|
+
if (part.type !== "literal") values[part.type] = Number(part.value);
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
year: values.year,
|
|
59
|
+
month: values.month,
|
|
60
|
+
day: values.day,
|
|
61
|
+
hours: values.hour,
|
|
62
|
+
minutes: values.minute,
|
|
63
|
+
seconds: values.second
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function wallClockAsUtc(clock) {
|
|
67
|
+
return Date.UTC(clock.year, clock.month - 1, clock.day, clock.hours, clock.minutes, clock.seconds);
|
|
68
|
+
}
|
|
69
|
+
function offsetAt(epochMs, timeZone) {
|
|
70
|
+
const flooredToMinute = Math.floor(epochMs / MINUTE_MS) * MINUTE_MS;
|
|
71
|
+
return wallClockAsUtc(zonedWallClock(flooredToMinute, timeZone)) - flooredToMinute;
|
|
72
|
+
}
|
|
73
|
+
function parseDateKey(dateKey) {
|
|
74
|
+
if (!DATE_KEY_PATTERN.test(String(dateKey))) return null;
|
|
75
|
+
const [year, month, day] = dateKey.split("-").map((part) => Number(part));
|
|
76
|
+
const probe = new Date(Date.UTC(year, month - 1, day));
|
|
77
|
+
if (probe.getUTCMonth() !== month - 1 || probe.getUTCDate() !== day) return null;
|
|
78
|
+
return { year, month, day };
|
|
79
|
+
}
|
|
80
|
+
function formatUtcDateKey(epochMs) {
|
|
81
|
+
return new Date(epochMs).toISOString().slice(0, 10);
|
|
82
|
+
}
|
|
83
|
+
function zonedWallTimeToInstant(dateKey, time, timeZone) {
|
|
84
|
+
const date = parseDateKey(dateKey);
|
|
85
|
+
const parsed = parseTimeOfDay(time);
|
|
86
|
+
if (!date || !parsed) return null;
|
|
87
|
+
const wall = new Date(Date.UTC(date.year, date.month - 1, date.day, parsed.hours, parsed.minutes));
|
|
88
|
+
return zonedWallDateToInstant(wall, timeZone);
|
|
89
|
+
}
|
|
90
|
+
function zonedWallDateToInstant(wall, timeZone) {
|
|
91
|
+
const zone = resolveTimeZone(timeZone);
|
|
92
|
+
const wallAsUtc = wall.getTime();
|
|
93
|
+
const offsetBefore = offsetAt(wallAsUtc - DAY_MS, zone);
|
|
94
|
+
const offsetAfter = offsetAt(wallAsUtc + DAY_MS, zone);
|
|
95
|
+
if (offsetBefore === offsetAfter) return new Date(wallAsUtc - offsetBefore);
|
|
96
|
+
const candidates = [wallAsUtc - offsetBefore, wallAsUtc - offsetAfter].sort((left, right) => left - right);
|
|
97
|
+
const matching = candidates.find((candidate) => candidate + offsetAt(candidate, zone) === wallAsUtc);
|
|
98
|
+
return new Date(matching ?? wallAsUtc - offsetBefore);
|
|
99
|
+
}
|
|
100
|
+
function zonedDayBounds(dateKey, timeZone) {
|
|
101
|
+
const date = parseDateKey(dateKey);
|
|
102
|
+
if (!date) return null;
|
|
103
|
+
const nextDateKey = formatUtcDateKey(Date.UTC(date.year, date.month - 1, date.day) + DAY_MS);
|
|
104
|
+
const start = zonedWallTimeToInstant(dateKey, "00:00", timeZone);
|
|
105
|
+
const end = zonedWallTimeToInstant(nextDateKey, "00:00", timeZone);
|
|
106
|
+
if (!start || !end || end <= start) return null;
|
|
107
|
+
return { start, end };
|
|
108
|
+
}
|
|
109
|
+
function zonedDateKey(instant, timeZone) {
|
|
110
|
+
const clock = zonedWallClock(instant.getTime(), resolveTimeZone(timeZone));
|
|
111
|
+
return formatUtcDateKey(Date.UTC(clock.year, clock.month - 1, clock.day));
|
|
112
|
+
}
|
|
113
|
+
function zonedWeekday(instant, timeZone) {
|
|
114
|
+
const clock = zonedWallClock(instant.getTime(), resolveTimeZone(timeZone));
|
|
115
|
+
return new Date(Date.UTC(clock.year, clock.month - 1, clock.day)).getUTCDay();
|
|
116
|
+
}
|
|
117
|
+
function nextWeekdayDateKey(weekday, timeZone, from = /* @__PURE__ */ new Date()) {
|
|
118
|
+
const clock = zonedWallClock(from.getTime(), resolveTimeZone(timeZone));
|
|
119
|
+
const localMidnight = Date.UTC(clock.year, clock.month - 1, clock.day);
|
|
120
|
+
const diff = (weekday - new Date(localMidnight).getUTCDay() + 7) % 7;
|
|
121
|
+
return formatUtcDateKey(localMidnight + diff * DAY_MS);
|
|
122
|
+
}
|
|
123
|
+
function zonedWeeklyAnchor(weekday, startTime, endTime, timeZone, from) {
|
|
124
|
+
const startClock = parseTimeOfDay(startTime);
|
|
125
|
+
const endClock = parseTimeOfDay(endTime);
|
|
126
|
+
if (!startClock || !endClock) return null;
|
|
127
|
+
const zone = resolveTimeZone(timeZone);
|
|
128
|
+
const startMinutes = startClock.hours * 60 + startClock.minutes;
|
|
129
|
+
const endMinutes = endClock.hours * 60 + endClock.minutes;
|
|
130
|
+
const localLengthMs = (endMinutes - startMinutes) * MINUTE_MS;
|
|
131
|
+
const upcoming = parseDateKey(nextWeekdayDateKey(weekday, timeZone, from));
|
|
132
|
+
if (!upcoming) return null;
|
|
133
|
+
const upcomingMidnight = Date.UTC(upcoming.year, upcoming.month - 1, upcoming.day);
|
|
134
|
+
let fallback = null;
|
|
135
|
+
for (const weekOffset of [0, -1]) {
|
|
136
|
+
const midnight = upcomingMidnight + weekOffset * 7 * DAY_MS;
|
|
137
|
+
const dateKey = formatUtcDateKey(midnight);
|
|
138
|
+
const start = zonedWallTimeToInstant(dateKey, startTime, timeZone);
|
|
139
|
+
const end = zonedWallTimeToInstant(dateKey, endTime, timeZone);
|
|
140
|
+
if (!start || !end) return null;
|
|
141
|
+
const startWall = wallClockAsUtc(zonedWallClock(start.getTime(), zone));
|
|
142
|
+
const endWall = wallClockAsUtc(zonedWallClock(end.getTime(), zone));
|
|
143
|
+
if (startWall !== midnight + startMinutes * MINUTE_MS || endWall !== midnight + endMinutes * MINUTE_MS) continue;
|
|
144
|
+
if (end.getTime() - start.getTime() === localLengthMs) return { start, end };
|
|
145
|
+
fallback ??= { start, end };
|
|
146
|
+
}
|
|
147
|
+
return fallback;
|
|
148
|
+
}
|
|
149
|
+
export {
|
|
150
|
+
isResolvableTimeZone,
|
|
151
|
+
nextWeekdayDateKey,
|
|
152
|
+
resolveTimeZone,
|
|
153
|
+
warnOnUnresolvableTimeZone,
|
|
154
|
+
zonedDateKey,
|
|
155
|
+
zonedDayBounds,
|
|
156
|
+
zonedWallDateToInstant,
|
|
157
|
+
zonedWallTimeToInstant,
|
|
158
|
+
zonedWeekday,
|
|
159
|
+
zonedWeeklyAnchor
|
|
160
|
+
};
|
|
161
|
+
//# sourceMappingURL=availabilityTimezone.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/planner/lib/availabilityTimezone.ts"],
|
|
4
|
+
"sourcesContent": ["import { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('planner').child({ component: 'availability' })\n\nconst FALLBACK_TIME_ZONE = 'UTC'\nconst DATE_KEY_PATTERN = /^\\d{4}-\\d{2}-\\d{2}$/\n\n/**\n * Availability rows store `timezone` as free-form `text`, so a malformed value\n * must not turn a read or a write into a 500. Unknown zones degrade to UTC,\n * which is already how the expander interprets every stored instant.\n */\nexport function resolveTimeZone(value: string | null | undefined): string {\n if (!isResolvableTimeZone(value)) return FALLBACK_TIME_ZONE\n return value.trim()\n}\n\nexport function isResolvableTimeZone(value: string | null | undefined): value is string {\n if (typeof value !== 'string' || !value.trim().length) return false\n try {\n new Intl.DateTimeFormat('en-US', { timeZone: value.trim() })\n return true\n } catch {\n return false\n }\n}\n\n/**\n * The replace endpoints anchor `DTSTART` in the declared zone, so an\n * unresolvable value is persisted as a UTC-anchored instant under a label\n * nothing can resolve. Rejecting it with a 400 would narrow the replace\n * schemas (a `data/validators.ts` contract surface), so for now the write is\n * accepted and flagged; the 400 is announced in UPGRADE_NOTES.md.\n */\nexport function warnOnUnresolvableTimeZone(value: string, commandId: string): void {\n if (isResolvableTimeZone(value)) return\n logger.warn('[deprecated] Unresolvable availability timezone anchored to UTC; this will be rejected with 400 in a future minor release', {\n commandId,\n timezone: value,\n fallback: FALLBACK_TIME_ZONE,\n })\n}\n\nfunction parseTimeOfDay(value: string): { hours: number; minutes: number } | null {\n const [hours, minutes] = String(value).split(':').map((part) => Number(part))\n if (!Number.isFinite(hours) || !Number.isFinite(minutes)) return null\n if (hours < 0 || hours > 23 || minutes < 0 || minutes > 59) return null\n return { hours, minutes }\n}\n\ntype WallClock = { year: number; month: number; day: number; hours: number; minutes: number; seconds: number }\n\nconst MINUTE_MS = 60 * 1000\nconst DAY_MS = 24 * 60 * MINUTE_MS\nconst formatterCache = new Map<string, Intl.DateTimeFormat>()\n\nfunction zonedFormatter(timeZone: string): Intl.DateTimeFormat {\n let formatter = formatterCache.get(timeZone)\n if (!formatter) {\n formatter = new Intl.DateTimeFormat('en-US', {\n timeZone,\n hourCycle: 'h23',\n year: 'numeric',\n month: '2-digit',\n day: '2-digit',\n hour: '2-digit',\n minute: '2-digit',\n second: '2-digit',\n })\n formatterCache.set(timeZone, formatter)\n }\n return formatter\n}\n\n/**\n * Wall clock of an instant in `timeZone`, read through `Intl` only. `date-fns-tz`\n * seeds its offset search with host-local getters, so around DST transitions\n * the same input resolved differently depending on the serving machine's `TZ`.\n */\nfunction zonedWallClock(epochMs: number, timeZone: string): WallClock {\n const values: Record<string, number> = {}\n for (const part of zonedFormatter(timeZone).formatToParts(new Date(epochMs))) {\n if (part.type !== 'literal') values[part.type] = Number(part.value)\n }\n return {\n year: values.year,\n month: values.month,\n day: values.day,\n hours: values.hour,\n minutes: values.minute,\n seconds: values.second,\n }\n}\n\nfunction wallClockAsUtc(clock: WallClock): number {\n return Date.UTC(clock.year, clock.month - 1, clock.day, clock.hours, clock.minutes, clock.seconds)\n}\n\nfunction offsetAt(epochMs: number, timeZone: string): number {\n const flooredToMinute = Math.floor(epochMs / MINUTE_MS) * MINUTE_MS\n return wallClockAsUtc(zonedWallClock(flooredToMinute, timeZone)) - flooredToMinute\n}\n\nfunction parseDateKey(dateKey: string): { year: number; month: number; day: number } | null {\n if (!DATE_KEY_PATTERN.test(String(dateKey))) return null\n const [year, month, day] = dateKey.split('-').map((part) => Number(part))\n const probe = new Date(Date.UTC(year, month - 1, day))\n if (probe.getUTCMonth() !== month - 1 || probe.getUTCDate() !== day) return null\n return { year, month, day }\n}\n\nfunction formatUtcDateKey(epochMs: number): string {\n return new Date(epochMs).toISOString().slice(0, 10)\n}\n\n/**\n * `2026-06-15` + `09:00` + `Europe/Warsaw` \u2192 the absolute instant that wall\n * time denotes. Replaces `new Date(y, m, d, h, min)`, whose result depended on\n * the host process's `TZ` rather than on the zone the caller declared (#5862).\n *\n * DST transitions follow the `compatible` disambiguation of RFC 5545 \u00A73.3.5 and\n * Temporal: a repeated wall time resolves to its EARLIER occurrence, and a\n * skipped one moves forward by the length of the gap (`Africa/Cairo` has no\n * 2026-04-24 00:00, so it becomes 01:00 \u2014 the first instant of that date).\n */\nexport function zonedWallTimeToInstant(dateKey: string, time: string, timeZone: string): Date | null {\n const date = parseDateKey(dateKey)\n const parsed = parseTimeOfDay(time)\n if (!date || !parsed) return null\n const wall = new Date(Date.UTC(date.year, date.month - 1, date.day, parsed.hours, parsed.minutes))\n return zonedWallDateToInstant(wall, timeZone)\n}\n\n/** Resolve a UTC-encoded wall clock with compatible DST disambiguation. */\nexport function zonedWallDateToInstant(wall: Date, timeZone: string): Date {\n const zone = resolveTimeZone(timeZone)\n const wallAsUtc = wall.getTime()\n const offsetBefore = offsetAt(wallAsUtc - DAY_MS, zone)\n const offsetAfter = offsetAt(wallAsUtc + DAY_MS, zone)\n if (offsetBefore === offsetAfter) return new Date(wallAsUtc - offsetBefore)\n const candidates = [wallAsUtc - offsetBefore, wallAsUtc - offsetAfter].sort((left, right) => left - right)\n const matching = candidates.find((candidate) => candidate + offsetAt(candidate, zone) === wallAsUtc)\n return new Date(matching ?? wallAsUtc - offsetBefore)\n}\n\n/**\n * The span a full-day block covers: from the first instant of `dateKey` to the\n * first instant of the following date, both in `timeZone`. That is 23 or 25\n * hours on DST days, so the block neither bleeds into nor falls short of its\n * neighbours.\n */\nexport function zonedDayBounds(dateKey: string, timeZone: string): { start: Date; end: Date } | null {\n const date = parseDateKey(dateKey)\n if (!date) return null\n const nextDateKey = formatUtcDateKey(Date.UTC(date.year, date.month - 1, date.day) + DAY_MS)\n const start = zonedWallTimeToInstant(dateKey, '00:00', timeZone)\n const end = zonedWallTimeToInstant(nextDateKey, '00:00', timeZone)\n if (!start || !end || end <= start) return null\n return { start, end }\n}\n\n/**\n * The calendar date an instant falls on, as seen from `timeZone`.\n *\n * Which day an existing rule belongs to has to be read in that rule's own zone,\n * the same zone its `DTSTART` was anchored in. Derived from the host clock\n * instead, replace-by-date selected a different set of rows \u2014 and the 403 gate\n * in `api/availability-date-specific.ts` admitted or refused the same request \u2014\n * depending on which machine served it.\n */\nexport function zonedDateKey(instant: Date, timeZone: string): string {\n const clock = zonedWallClock(instant.getTime(), resolveTimeZone(timeZone))\n return formatUtcDateKey(Date.UTC(clock.year, clock.month - 1, clock.day))\n}\n\n/** `Date.getDay()` index (0 = Sunday) of an instant, as seen from `timeZone`. */\nexport function zonedWeekday(instant: Date, timeZone: string): number {\n const clock = zonedWallClock(instant.getTime(), resolveTimeZone(timeZone))\n return new Date(Date.UTC(clock.year, clock.month - 1, clock.day)).getUTCDay()\n}\n\n/**\n * The next occurrence of `weekday` (0 = Sunday) at or after `from`, as a\n * calendar date key in `timeZone`. The zone matters: at 12:00 UTC on a Monday\n * it is already Tuesday in `Pacific/Auckland`, which shifted the whole series\n * by a week before this was zone-aware.\n */\nexport function nextWeekdayDateKey(weekday: number, timeZone: string, from: Date = new Date()): string {\n const clock = zonedWallClock(from.getTime(), resolveTimeZone(timeZone))\n const localMidnight = Date.UTC(clock.year, clock.month - 1, clock.day)\n const diff = (weekday - new Date(localMidnight).getUTCDay() + 7) % 7\n return formatUtcDateKey(localMidnight + diff * DAY_MS)\n}\n\n/**\n * The start and end instants of the occurrence a weekly window is anchored on.\n *\n * The anchor's exact length becomes the rule's `DURATION`, which every reader\n * applies to every week. An anchor that spans a DST transition (Europe/Warsaw\n * Sunday 00:00\u201308:00 on 2026-10-25 lasts 9 hours) or starts in a gap would\n * therefore misstate the window for all ordinary weeks, so the upcoming\n * occurrence is used only when both local endpoints and its exact length match\n * the request; otherwise the previous week's occurrence anchors the series. Taking\n * the earlier week keeps the upcoming occurrence inside the series for readers\n * that expand forward from `DTSTART`.\n *\n * `from` is threaded in rather than defaulted per call: resolving a window's\n * start and end against two separate `new Date()` reads can straddle midnight\n * in the declared zone, which lands the anchors seven days apart.\n */\nexport function zonedWeeklyAnchor(\n weekday: number,\n startTime: string,\n endTime: string,\n timeZone: string,\n from: Date,\n): { start: Date; end: Date } | null {\n const startClock = parseTimeOfDay(startTime)\n const endClock = parseTimeOfDay(endTime)\n if (!startClock || !endClock) return null\n const zone = resolveTimeZone(timeZone)\n const startMinutes = startClock.hours * 60 + startClock.minutes\n const endMinutes = endClock.hours * 60 + endClock.minutes\n const localLengthMs = (endMinutes - startMinutes) * MINUTE_MS\n const upcoming = parseDateKey(nextWeekdayDateKey(weekday, timeZone, from))\n if (!upcoming) return null\n const upcomingMidnight = Date.UTC(upcoming.year, upcoming.month - 1, upcoming.day)\n let fallback: { start: Date; end: Date } | null = null\n for (const weekOffset of [0, -1]) {\n const midnight = upcomingMidnight + weekOffset * 7 * DAY_MS\n const dateKey = formatUtcDateKey(midnight)\n const start = zonedWallTimeToInstant(dateKey, startTime, timeZone)\n const end = zonedWallTimeToInstant(dateKey, endTime, timeZone)\n if (!start || !end) return null\n const startWall = wallClockAsUtc(zonedWallClock(start.getTime(), zone))\n const endWall = wallClockAsUtc(zonedWallClock(end.getTime(), zone))\n if (startWall !== midnight + startMinutes * MINUTE_MS || endWall !== midnight + endMinutes * MINUTE_MS) continue\n if (end.getTime() - start.getTime() === localLengthMs) return { start, end }\n fallback ??= { start, end }\n }\n return fallback\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -325,6 +325,7 @@ function buildDocumentCrudOptions(binding) {
|
|
|
325
325
|
];
|
|
326
326
|
const orderOnlyFields = [
|
|
327
327
|
"expected_delivery_at",
|
|
328
|
+
"totals_mode",
|
|
328
329
|
"shipping_net_amount",
|
|
329
330
|
"shipping_gross_amount",
|
|
330
331
|
"surcharge_total_amount",
|
|
@@ -422,6 +423,7 @@ function buildDocumentCrudOptions(binding) {
|
|
|
422
423
|
paidTotalAmount: toNumber(item.paid_total_amount),
|
|
423
424
|
refundedTotalAmount: toNumber(item.refunded_total_amount),
|
|
424
425
|
outstandingAmount: toNumber(item.outstanding_amount),
|
|
426
|
+
...binding.kind === "order" ? { totalsMode: item.totals_mode ?? "computed" } : {},
|
|
425
427
|
customerSnapshot: normalizeJsonRecord(item.customer_snapshot),
|
|
426
428
|
billingAddressSnapshot: normalizeJsonRecord(item.billing_address_snapshot),
|
|
427
429
|
shippingAddressSnapshot: normalizeJsonRecord(item.shipping_address_snapshot),
|
|
@@ -545,6 +547,7 @@ function buildDocumentOpenApi(binding) {
|
|
|
545
547
|
paidTotalAmount: z.number().nullable().optional(),
|
|
546
548
|
refundedTotalAmount: z.number().nullable().optional(),
|
|
547
549
|
outstandingAmount: z.number().nullable().optional(),
|
|
550
|
+
totalsMode: z.enum(["computed", "external"]).optional(),
|
|
548
551
|
createdAt: z.string(),
|
|
549
552
|
updatedAt: z.string(),
|
|
550
553
|
customFields: z.record(z.string(), z.unknown()).optional(),
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/sales/api/documents/factory.ts"],
|
|
4
|
-
"sourcesContent": ["import { z } from 'zod'\nimport { makeCrudRoute, type CrudCtx } from '@open-mercato/shared/lib/crud/factory'\nimport { splitCustomFieldPayload, extractAllCustomFieldEntries } from '@open-mercato/shared/lib/crud/custom-fields'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { E } from '#generated/entities.ids.generated'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { SalesOrder, SalesQuote } from '../../data/entities'\nimport { SalesChannel, SalesDocumentTagAssignment } from '../../data/entities'\nimport { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport {\n ORDER_PAYMENT_LEDGER_FIELDS,\n ORDER_PAYMENT_LEDGER_WARNING_CODE,\n orderCreateSchema,\n quoteCreateSchema,\n type OrderPaymentLedgerWarning,\n} from '../../data/validators'\nimport {\n createPagedListResponseSchema,\n createSalesCrudOpenApi,\n defaultDeleteRequestSchema,\n} from '../openapi'\nimport { parseScopedCommandInput, resolveCrudRecordId } from '../utils'\nimport { documentUpdateSchema } from '../../commands/documents'\nimport { buildIlikeTerm } from '@open-mercato/shared/lib/db/buildIlikeTerm'\nimport { parseBooleanToken } from '@open-mercato/shared/lib/boolean'\nimport { isUuidListParam, parseIdsParam, splitIdListParam } from '@open-mercato/shared/lib/crud/ids'\nimport type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport { parseDecryptedFieldValue } from '@open-mercato/shared/lib/encryption/tenantDataEncryptionService'\n\ntype DocumentKind = 'order' | 'quote'\n\ntype DocumentBinding = {\n kind: DocumentKind\n entity: typeof SalesOrder | typeof SalesQuote\n entityId: (typeof E.sales)[keyof typeof E.sales]\n numberField: 'orderNumber' | 'quoteNumber'\n createCommandId: string\n updateCommandId: string\n deleteCommandId: string\n manageFeature: string\n viewFeature: string\n}\n\ntype DocumentCreateResult = {\n orderId?: string\n quoteId?: string\n id?: string\n warnings?: OrderPaymentLedgerWarning[]\n}\n\nconst rawBodySchema = z.object({}).passthrough()\n\nconst normalizeJsonRecord = (value: unknown): Record<string, unknown> | null => {\n if (value && typeof value === 'object' && !Array.isArray(value)) {\n return value as Record<string, unknown>\n }\n if (typeof value !== 'string') return null\n const parsed = parseDecryptedFieldValue(value)\n return parsed && typeof parsed === 'object' && !Array.isArray(parsed)\n ? parsed as Record<string, unknown>\n : null\n}\n\nconst resolveCustomerName = (snapshot: Record<string, unknown> | null, fallback?: string | null) => {\n if (!snapshot) return fallback ?? null\n const customer = snapshot.customer as Record<string, unknown> | undefined\n const contact = snapshot.contact as Record<string, unknown> | undefined\n const displayName = typeof customer?.displayName === 'string' ? customer.displayName : null\n if (displayName) return displayName\n const first = typeof contact?.firstName === 'string' ? contact.firstName : null\n const last = typeof contact?.lastName === 'string' ? contact.lastName : null\n const preferred = typeof contact?.preferredName === 'string' ? contact.preferredName : null\n const parts = [preferred ?? first, last].filter((part) => part && part.trim().length)\n if (parts.length) return parts.join(' ')\n return fallback ?? null\n}\n\nconst resolveCustomerEmail = (snapshot: Record<string, unknown> | null) => {\n if (!snapshot) return null\n const customer = snapshot.customer as Record<string, unknown> | undefined\n const primary = typeof customer?.primaryEmail === 'string' ? customer.primaryEmail : null\n return primary ?? null\n}\n\nconst listSchema = z\n .object({\n page: z.coerce.number().min(1).default(1),\n pageSize: z.coerce.number().min(1).max(100).default(50),\n search: z.string().optional(),\n id: z.string().uuid().optional(),\n customerId: z.string().uuid().optional(),\n channelId: z.string().uuid().optional(),\n channelIds: z\n .string()\n .optional()\n .describe(\n 'Comma-separated sales channel uuids; matches documents on any of them. Capped at 200 ids, malformed entries are dropped. Ignored when channelId is supplied; combines with channelIdsEmpty.',\n ),\n channelIdsEmpty: z\n .string()\n .optional()\n .describe(\n 'Boolean token; matches documents with no sales channel. Ignored when channelId is supplied; combines with channelIds.',\n ),\n lineItemCountMin: z.coerce.number().min(0).optional(),\n lineItemCountMax: z.coerce.number().min(0).optional(),\n totalNetMin: z.coerce.number().optional(),\n totalNetMax: z.coerce.number().optional(),\n totalGrossMin: z.coerce.number().optional(),\n totalGrossMax: z.coerce.number().optional(),\n dateFrom: z.string().optional(),\n dateTo: z.string().optional(),\n tagIds: z\n .string()\n .optional()\n .refine(isUuidListParam, { message: 'tagIds must be a comma-separated list of UUIDs' }),\n tagIdsEmpty: z.string().optional(),\n sortField: z.string().optional(),\n sortDir: z.enum(['asc', 'desc']).optional(),\n withDeleted: z.coerce.boolean().optional(),\n })\n .passthrough()\n\ntype ListQuery = z.infer<typeof listSchema>\n\nfunction buildFilters(query: ListQuery, numberColumn: string, kind: DocumentKind) {\n const filters: Record<string, unknown> = {}\n if (query.id) filters.id = { $eq: query.id }\n if (query.search && query.search.trim().length > 0) {\n const term = buildIlikeTerm(query.search.trim())\n filters[numberColumn] = { $ilike: term }\n }\n if (query.customerId) {\n filters.customer_entity_id = { $eq: query.customerId }\n }\n // Singular wins over plural, mirroring how `api/channels/route.ts` resolves `id` before `ids`.\n // An all-malformed `channelIds` narrows to no channel filter rather than an empty-set filter, so\n // a typo returns the unfiltered page instead of silently returning zero rows.\n if (query.channelId) {\n filters.channel_id = { $eq: query.channelId }\n } else {\n const channelIds = parseIdsParam(query.channelIds)\n const wantsUnassigned = parseBooleanToken(query.channelIdsEmpty) === true\n if (channelIds.length && wantsUnassigned) {\n // A channel multi-select with an \"(No channel)\" entry produces both at once, so they combine\n // rather than one silently dropping the other. `filters.$or` is a single key \u2014 a future filter\n // that also needs `$or` would clobber this one; nothing else in this factory writes it today.\n filters.$or = [{ channel_id: { $in: channelIds } }, { channel_id: { $exists: false } }]\n } else if (wantsUnassigned) {\n filters.channel_id = { $exists: false }\n } else if (channelIds.length) {\n filters.channel_id = { $in: channelIds }\n }\n }\n const lineRange: Record<string, number> = {}\n if (typeof query.lineItemCountMin === 'number') lineRange.$gte = query.lineItemCountMin\n if (typeof query.lineItemCountMax === 'number') lineRange.$lte = query.lineItemCountMax\n if (Object.keys(lineRange).length) {\n filters.line_item_count = lineRange\n }\n const netRange: Record<string, number> = {}\n if (typeof query.totalNetMin === 'number') netRange.$gte = query.totalNetMin\n if (typeof query.totalNetMax === 'number') netRange.$lte = query.totalNetMax\n if (Object.keys(netRange).length) {\n filters.grand_total_net_amount = netRange\n }\n const grossRange: Record<string, number> = {}\n if (typeof query.totalGrossMin === 'number') grossRange.$gte = query.totalGrossMin\n if (typeof query.totalGrossMax === 'number') grossRange.$lte = query.totalGrossMax\n if (Object.keys(grossRange).length) {\n filters.grand_total_gross_amount = grossRange\n }\n const dateRange: Record<string, Date> = {}\n if (query.dateFrom) {\n const from = new Date(query.dateFrom)\n if (!Number.isNaN(from.getTime())) dateRange.$gte = from\n }\n if (query.dateTo) {\n const to = new Date(query.dateTo)\n if (!Number.isNaN(to.getTime())) dateRange.$lte = to\n }\n if (Object.keys(dateRange).length) {\n filters.created_at = dateRange\n }\n const tagIds = splitIdListParam(query.tagIds)\n if (parseBooleanToken(query.tagIdsEmpty) === true) {\n filters.id = { $eq: '00000000-0000-0000-0000-000000000000' }\n } else if (tagIds.length) {\n filters['tag_assignments.tag_id'] = { $in: tagIds }\n filters['tag_assignments.document_kind'] = { $eq: kind }\n }\n return filters\n}\n\nfunction buildSortMap(numberColumn: string) {\n return {\n id: 'id',\n number: numberColumn,\n placedAt: 'placed_at',\n lineItemCount: 'line_item_count',\n grandTotalNetAmount: 'grand_total_net_amount',\n grandTotalGrossAmount: 'grand_total_gross_amount',\n createdAt: 'created_at',\n updatedAt: 'updated_at',\n }\n}\n\nconst mapUpdateResponse = (entity: any) => {\n const customerSnapshot = normalizeJsonRecord(entity?.customerSnapshot)\n const metadata = normalizeJsonRecord(entity?.metadata)\n\n return {\n id: entity?.id ?? null,\n orderNumber: entity?.orderNumber ?? null,\n quoteNumber: entity?.quoteNumber ?? null,\n customerEntityId: entity?.customerEntityId ?? null,\n customerContactId: entity?.customerContactId ?? null,\n customerSnapshot,\n metadata,\n externalReference: entity?.externalReference ?? null,\n customerReference: entity?.customerReference ?? null,\n comment: entity?.comments ?? null,\n statusEntryId: (entity as any)?.statusEntryId ?? null,\n status: (entity as any)?.status ?? null,\n channelId: (entity as any)?.channelId ?? null,\n customerName: resolveCustomerName(customerSnapshot, entity?.customerEntityId ?? null),\n contactEmail:\n resolveCustomerEmail(customerSnapshot) ??\n (typeof metadata?.customerEmail === 'string' ? metadata.customerEmail : null),\n currencyCode: entity?.currencyCode ?? null,\n placedAt: entity?.placedAt ? entity.placedAt.toISOString() : null,\n expectedDeliveryAt: entity?.expectedDeliveryAt ? entity.expectedDeliveryAt.toISOString() : null,\n shippingAddressId: entity?.shippingAddressId ?? null,\n billingAddressId: entity?.billingAddressId ?? null,\n shippingAddressSnapshot: normalizeJsonRecord(entity?.shippingAddressSnapshot),\n billingAddressSnapshot: normalizeJsonRecord(entity?.billingAddressSnapshot),\n shippingMethodId: entity?.shippingMethodId ?? null,\n shippingMethodCode: entity?.shippingMethodCode ?? null,\n shippingMethodSnapshot: normalizeJsonRecord(entity?.shippingMethodSnapshot),\n paymentMethodId: entity?.paymentMethodId ?? null,\n paymentMethodCode: entity?.paymentMethodCode ?? null,\n paymentMethodSnapshot: normalizeJsonRecord(entity?.paymentMethodSnapshot),\n // Return the fresh version so the client can refresh its optimistic-lock\n // token after a successful inline save \u2014 otherwise a second save on the same\n // page sends the now-stale updatedAt and falsely 409s (#2055 QA).\n updatedAt: entity?.updatedAt\n ? (entity.updatedAt instanceof Date ? entity.updatedAt.toISOString() : entity.updatedAt)\n : null,\n }\n}\n\nconst attachTags = async (payload: any, ctx: any) => {\n const items = Array.isArray(payload?.items) ? (payload.items as Array<Record<string, any>>) : []\n if (!items.length) return\n const ids = items\n .map((item) => (item && typeof item.id === 'string' ? item.id : null))\n .filter((id): id is string => !!id)\n if (!ids.length) return\n const em = ctx?.container?.resolve ? (ctx.container.resolve('em') as any) : null\n if (!em) return\n const where: Record<string, unknown> = {\n documentId: { $in: ids },\n documentKind: ctx?.bindingKind ?? null,\n }\n if (ctx?.auth?.tenantId) where.tenantId = ctx.auth.tenantId\n const orgIds =\n Array.isArray(ctx?.organizationIds) && ctx.organizationIds.length\n ? ctx.organizationIds.filter((val: string | null) => !!val)\n : ctx?.selectedOrganizationId\n ? [ctx.selectedOrganizationId]\n : []\n if (orgIds.length) where.organizationId = { $in: orgIds }\n const assignments = await em.find(\n SalesDocumentTagAssignment,\n where,\n { populate: ['tag'] },\n )\n const grouped = new Map<string, Array<{ id: string; label: string; color: string | null }>>()\n assignments.forEach((assignment: any) => {\n const tag = assignment?.tag\n const documentId = assignment?.documentId\n if (!tag || typeof tag.id !== 'string' || typeof documentId !== 'string') return\n const entry = {\n id: tag.id,\n label: typeof tag.label === 'string' && tag.label.trim().length ? tag.label : tag.slug ?? tag.id,\n color: typeof tag.color === 'string' && tag.color.trim().length ? tag.color : null,\n }\n const list = grouped.get(documentId) ?? []\n list.push(entry)\n grouped.set(documentId, list)\n })\n items.forEach((item: Record<string, any>) => {\n const id = item && typeof item.id === 'string' ? item.id : null\n if (!id) return\n const list = grouped.get(id)\n if (list) item.tags = list\n })\n}\n\n// Liveness note: this hook runs BEFORE the CRUD list cache stores the payload, so `channelName`\n// and `channelCode` are embedded in the cached entry. What keeps them fresh is that the hook also\n// runs on the cache-HIT path (shared/lib/crud/factory.ts:1683) and reassigns both fields\n// unconditionally for every item carrying a channel id. Anything that later skips this hook on a\n// hit \u2014 the way `skipEnrichersOnCacheHit` does for record-pure enrichers \u2014 would start serving the\n// stale names baked into the entry.\nexport const attachChannelNames = async (\n payload: { items?: Array<Record<string, unknown>> },\n ctx: CrudCtx,\n) => {\n const items = Array.isArray(payload?.items) ? payload.items : []\n if (!items.length) return\n const channelIds = Array.from(\n new Set(\n items\n .map((item) => (item && typeof item.channelId === 'string' ? item.channelId : null))\n .filter((value): value is string => !!value)\n )\n )\n if (!channelIds.length) return\n const em = ctx?.container?.resolve ? (ctx.container.resolve('em') as EntityManager) : null\n if (!em) return\n\n const where: Record<string, unknown> = { id: { $in: channelIds } }\n if (ctx?.auth?.tenantId) where.tenantId = ctx.auth.tenantId\n const orgIds =\n Array.isArray(ctx?.organizationIds) && ctx.organizationIds.length\n ? ctx.organizationIds.filter((val: string | null) => !!val)\n : ctx?.selectedOrganizationId\n ? [ctx.selectedOrganizationId]\n : []\n if (orgIds.length) where.organizationId = { $in: orgIds }\n\n // Only `name` and `code` are read. Without this projection the query loads the whole channel row,\n // and `sales/encryption.ts` declares eight of its columns encrypted (contact email/phone, the\n // address block) \u2014 so every documents-list request would decrypt PII it never renders, on the\n // cache-hit path too.\n const channels = await findWithDecryption(\n em,\n SalesChannel,\n where,\n { fields: ['id', 'name', 'code'] },\n {\n tenantId: ctx?.auth?.tenantId ?? null,\n organizationId: ctx?.selectedOrganizationId ?? ctx?.auth?.orgId ?? null,\n }\n )\n const byId = new Map(channels.map((channel) => [channel.id, channel]))\n items.forEach((item) => {\n if (!item || typeof item.channelId !== 'string') return\n const channel = byId.get(item.channelId)\n item.channelName = channel?.name ?? null\n item.channelCode = channel?.code ?? null\n })\n}\n\nasync function ensureNumberEditPermission(\n ctx: CrudCtx,\n translate: (key: string, fallback?: string) => string\n) {\n const rbac = ctx.container?.resolve?.('rbacService') as RbacService | null\n const auth = ctx.auth\n if (!rbac || !auth?.sub) return\n const ok = await rbac.userHasAllFeatures(auth.sub, ['sales.documents.number.edit'], {\n tenantId: auth.tenantId ?? null,\n organizationId: ctx.selectedOrganizationId ?? auth.orgId ?? null,\n })\n if (!ok) {\n throw new CrudHttpError(403, {\n error: translate('sales.documents.errors.number_edit_forbidden', 'You cannot edit document numbers.'),\n })\n }\n}\n\nexport function buildDocumentCrudOptions(binding: DocumentBinding) {\n const numberColumn = binding.numberField === 'orderNumber' ? 'order_number' : 'quote_number'\n const createSchema = binding.kind === 'order' ? orderCreateSchema : quoteCreateSchema\n\n const routeMetadata = {\n GET: { requireAuth: true, requireFeatures: [binding.viewFeature] },\n POST: { requireAuth: true, requireFeatures: [binding.manageFeature] },\n PUT: { requireAuth: true, requireFeatures: [binding.manageFeature] },\n DELETE: { requireAuth: true, requireFeatures: [binding.manageFeature] },\n }\n\n const commonFields = [\n 'id',\n numberColumn,\n 'status',\n 'status_entry_id',\n 'customer_entity_id',\n 'customer_contact_id',\n 'billing_address_id',\n 'shipping_address_id',\n 'customer_snapshot',\n 'billing_address_snapshot',\n 'shipping_address_snapshot',\n 'shipping_method_id',\n 'shipping_method_code',\n 'shipping_method_snapshot',\n 'payment_method_id',\n 'payment_method_code',\n 'payment_method_snapshot',\n 'customer_reference',\n 'metadata',\n 'external_reference',\n 'currency_code',\n 'comments',\n 'channel_id',\n 'placed_at',\n 'line_item_count',\n 'subtotal_net_amount',\n 'subtotal_gross_amount',\n 'tax_total_amount',\n 'discount_total_amount',\n 'grand_total_net_amount',\n 'grand_total_gross_amount',\n 'totals_snapshot',\n 'organization_id',\n 'tenant_id',\n 'created_at',\n 'updated_at',\n ]\n\n const orderOnlyFields = [\n 'expected_delivery_at',\n 'shipping_net_amount',\n 'shipping_gross_amount',\n 'surcharge_total_amount',\n 'paid_total_amount',\n 'refunded_total_amount',\n 'outstanding_amount',\n ]\n\n const quoteOnlyFields = ['valid_from', 'valid_until']\n\n const listFields = [\n ...commonFields,\n ...(binding.kind === 'order' ? orderOnlyFields : quoteOnlyFields),\n ]\n\n // Large JSONB snapshot/payload columns that only the detail page renders. The\n // grid never reads them, so selecting them for list pages fetches and decrypts\n // blobs over the wire for nothing (#2233). `customer_snapshot` is intentionally\n // kept because the grid derives the customer name/email column from it.\n const detailOnlyProjectionFields = new Set([\n 'billing_address_snapshot',\n 'shipping_address_snapshot',\n 'shipping_method_snapshot',\n 'payment_method_snapshot',\n 'totals_snapshot',\n 'metadata',\n ])\n\n const gridFields = listFields.filter((field) => !detailOnlyProjectionFields.has(field))\n\n // The detail page fetches a single document through this same list route with an\n // `?id=` filter (there is no separate detail endpoint), so it needs the full\n // projection. Grid listings (no `id`) use the trimmed projection.\n const resolveListFields = (query: ListQuery) =>\n query && typeof query.id === 'string' && query.id.length ? listFields : gridFields\n\n return {\n metadata: routeMetadata,\n orm: {\n entity: binding.entity as any,\n idField: 'id',\n orgField: 'organizationId',\n tenantField: 'tenantId',\n softDeleteField: 'deletedAt',\n },\n indexer: {\n entityType: binding.entityId,\n },\n enrichers: binding.kind === 'order' ? { entityId: binding.entityId } : undefined,\n list: {\n schema: listSchema,\n entityId: binding.entityId,\n fields: (query: ListQuery) => resolveListFields(query),\n sortFieldMap: buildSortMap(numberColumn),\n buildFilters: async (query: any) => buildFilters(query, numberColumn, binding.kind),\n decorateCustomFields: { entityIds: [binding.entityId] },\n joins: [\n {\n alias: 'tag_assignments',\n table: 'sales_document_tag_assignments',\n from: { field: 'id' },\n to: { field: 'document_id' },\n type: 'left' as const,\n },\n ],\n transformItem: (item: any) => {\n const toNumber = (value: unknown): number | null => {\n if (typeof value === 'number') return Number.isNaN(value) ? null : value\n if (typeof value === 'string' && value.trim().length) {\n const parsed = Number(value)\n return Number.isNaN(parsed) ? null : parsed\n }\n return null\n }\n const base = {\n id: item.id,\n [binding.numberField]: item[numberColumn] ?? null,\n status: item.status ?? null,\n statusEntryId: item.status_entry_id ?? null,\n customerEntityId: item.customer_entity_id ?? null,\n customerContactId: item.customer_contact_id ?? null,\n billingAddressId: item.billing_address_id ?? null,\n shippingAddressId: item.shipping_address_id ?? null,\n shippingMethodId: item.shipping_method_id ?? null,\n shippingMethodCode: item.shipping_method_code ?? null,\n shippingMethodSnapshot: normalizeJsonRecord(item.shipping_method_snapshot),\n paymentMethodId: item.payment_method_id ?? null,\n paymentMethodCode: item.payment_method_code ?? null,\n paymentMethodSnapshot: normalizeJsonRecord(item.payment_method_snapshot),\n currencyCode: item.currency_code ?? null,\n channelId: item.channel_id ?? null,\n externalReference: item.external_reference ?? null,\n customerReference: item.customer_reference ?? null,\n placedAt: item.placed_at ?? null,\n expectedDeliveryAt: item.expected_delivery_at ?? null,\n comment: item.comments ?? null,\n validFrom: item.valid_from ?? null,\n validUntil: item.valid_until ?? null,\n lineItemCount: toNumber(item.line_item_count),\n subtotalNetAmount: toNumber(item.subtotal_net_amount),\n subtotalGrossAmount: toNumber(item.subtotal_gross_amount),\n discountTotalAmount: toNumber(item.discount_total_amount),\n taxTotalAmount: toNumber(item.tax_total_amount),\n shippingNetAmount: toNumber(item.shipping_net_amount),\n shippingGrossAmount: toNumber(item.shipping_gross_amount),\n surchargeTotalAmount: toNumber(item.surcharge_total_amount),\n grandTotalNetAmount: toNumber(item.grand_total_net_amount),\n grandTotalGrossAmount: toNumber(item.grand_total_gross_amount),\n paidTotalAmount: toNumber(item.paid_total_amount),\n refundedTotalAmount: toNumber(item.refunded_total_amount),\n outstandingAmount: toNumber(item.outstanding_amount),\n customerSnapshot: normalizeJsonRecord(item.customer_snapshot),\n billingAddressSnapshot: normalizeJsonRecord(item.billing_address_snapshot),\n shippingAddressSnapshot: normalizeJsonRecord(item.shipping_address_snapshot),\n metadata: normalizeJsonRecord(item.metadata),\n organizationId: item.organization_id ?? null,\n tenantId: item.tenant_id ?? null,\n createdAt: item.created_at,\n updatedAt: item.updated_at,\n }\n const cfEntries = extractAllCustomFieldEntries(item as Record<string, unknown>)\n const normalized = { ...base }\n Object.keys(normalized).forEach((key) => {\n if (key.startsWith('cf:')) delete (normalized as any)[key]\n })\n return Object.keys(cfEntries).length ? { ...normalized, ...cfEntries } : normalized\n },\n },\n actions: {\n create: {\n commandId: binding.createCommandId,\n schema: rawBodySchema,\n mapInput: async ({ raw, ctx }: { raw: unknown; ctx: CrudCtx }) => {\n const { translate } = await resolveTranslations()\n const { base, custom } = splitCustomFieldPayload(raw ?? {})\n const parsed = parseScopedCommandInput(\n createSchema,\n Object.keys(custom).length ? { ...base, customFields: custom } : base,\n ctx,\n translate,\n )\n return parsed\n },\n response: ({ result }: { result?: DocumentCreateResult | null }) => ({\n id: result?.orderId ?? result?.quoteId ?? result?.id ?? null,\n ...(binding.kind === 'order' && Array.isArray(result?.warnings)\n ? { warnings: result.warnings }\n : {}),\n }),\n status: 201,\n },\n update: {\n commandId: binding.updateCommandId,\n schema: rawBodySchema,\n mapInput: async ({ raw, ctx }: { raw: unknown; ctx: CrudCtx }) => {\n const { translate } = await resolveTranslations()\n const { base, custom } = splitCustomFieldPayload(raw ?? {})\n const numberValue =\n binding.kind === 'order'\n ? (base as Record<string, unknown>).orderNumber\n : (base as Record<string, unknown>).quoteNumber\n if (typeof numberValue === 'string') {\n await ensureNumberEditPermission(ctx, translate)\n }\n const parsed = parseScopedCommandInput(\n documentUpdateSchema,\n Object.keys(custom).length ? { ...base, customFields: custom } : base,\n ctx,\n translate,\n )\n return parsed\n },\n response: ({ result }: { result: any }) =>\n mapUpdateResponse((result as any)?.order ?? (result as any)?.quote ?? result),\n },\n delete: {\n commandId: binding.deleteCommandId,\n schema: rawBodySchema,\n mapInput: async ({ parsed, ctx }: { parsed: any; ctx: CrudCtx }) => {\n const { translate } = await resolveTranslations()\n const id = resolveCrudRecordId(parsed, ctx, translate)\n return { id }\n },\n response: () => ({ ok: true }),\n },\n },\n hooks: {\n afterList: async (payload: any, ctx: CrudCtx) => {\n await attachTags(payload, { ...ctx, bindingKind: binding.kind })\n await attachChannelNames(payload, ctx)\n },\n },\n }\n}\n\nexport function buildDocumentOpenApi(binding: DocumentBinding) {\n const createSchema = binding.kind === 'order' ? orderCreateSchema : quoteCreateSchema\n const itemSchema = z.object({\n id: z.string().uuid(),\n [binding.numberField]: z.string().nullable(),\n status: z.string().nullable(),\n statusEntryId: z.string().uuid().nullable().optional(),\n customerEntityId: z.string().uuid().nullable(),\n customerContactId: z.string().uuid().nullable(),\n billingAddressId: z.string().uuid().nullable(),\n shippingAddressId: z.string().uuid().nullable(),\n customerReference: z.string().nullable().optional(),\n externalReference: z.string().nullable().optional(),\n comment: z.string().nullable().optional(),\n placedAt: z.string().nullable().optional(),\n expectedDeliveryAt: z.string().nullable().optional(),\n customerSnapshot: z.record(z.string(), z.unknown()).nullable().optional(),\n billingAddressSnapshot: z.record(z.string(), z.unknown()).nullable().optional(),\n shippingAddressSnapshot: z.record(z.string(), z.unknown()).nullable().optional(),\n shippingMethodId: z.string().uuid().nullable().optional(),\n shippingMethodCode: z.string().nullable().optional(),\n shippingMethodSnapshot: z.record(z.string(), z.unknown()).nullable().optional(),\n paymentMethodId: z.string().uuid().nullable().optional(),\n paymentMethodCode: z.string().nullable().optional(),\n paymentMethodSnapshot: z.record(z.string(), z.unknown()).nullable().optional(),\n currencyCode: z.string().nullable(),\n channelId: z.string().uuid().nullable(),\n channelName: z.string().nullable().optional(),\n channelCode: z.string().nullable().optional(),\n organizationId: z.string().uuid().nullable(),\n tenantId: z.string().uuid().nullable(),\n validFrom: z.string().nullable().optional(),\n validUntil: z.string().nullable().optional(),\n lineItemCount: z.number().nullable().optional(),\n subtotalNetAmount: z.number().nullable().optional(),\n subtotalGrossAmount: z.number().nullable().optional(),\n discountTotalAmount: z.number().nullable().optional(),\n taxTotalAmount: z.number().nullable().optional(),\n shippingNetAmount: z.number().nullable().optional(),\n shippingGrossAmount: z.number().nullable().optional(),\n surchargeTotalAmount: z.number().nullable().optional(),\n grandTotalNetAmount: z.number().nullable().optional(),\n grandTotalGrossAmount: z.number().nullable().optional(),\n paidTotalAmount: z.number().nullable().optional(),\n refundedTotalAmount: z.number().nullable().optional(),\n outstandingAmount: z.number().nullable().optional(),\n createdAt: z.string(),\n updatedAt: z.string(),\n customFields: z.record(z.string(), z.unknown()).optional(),\n customValues: z.record(z.string(), z.unknown()).optional(),\n })\n\n const listResponseSchema = createPagedListResponseSchema(itemSchema)\n\n return createSalesCrudOpenApi({\n resourceName: binding.kind === 'order' ? 'Order' : 'Quote',\n querySchema: listSchema,\n listResponseSchema,\n create: {\n schema: createSchema,\n responseSchema: binding.kind === 'order'\n ? z.object({\n id: z.string().uuid().nullable(),\n warnings: z.array(z.object({\n code: z.literal(ORDER_PAYMENT_LEDGER_WARNING_CODE),\n fields: z.array(z.enum(ORDER_PAYMENT_LEDGER_FIELDS)),\n })).optional(),\n })\n : z.object({ id: z.string().uuid().nullable() }),\n description: binding.kind === 'order'\n ? 'Creates a new sales order. paidTotalAmount, refundedTotalAmount, and outstandingAmount are deprecated compatibility inputs: supplied values are ignored and reported in warnings. Record payments through sales.payments.create or POST /api/sales/payments.'\n : 'Creates a new sales quote.',\n },\n del: {\n schema: defaultDeleteRequestSchema,\n responseSchema: z.object({ ok: z.boolean() }),\n description: `Deletes a sales ${binding.kind}.`,\n },\n })\n}\n\n// Compatibility wrapper\nexport function createDocumentCrudRoute(binding: DocumentBinding) {\n const crud = makeCrudRoute(buildDocumentCrudOptions(binding))\n const { GET, POST, PUT, DELETE } = crud\n return { GET, POST, PUT, DELETE, openApi: buildDocumentOpenApi(binding), metadata: crud.metadata }\n}\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,SAAS;AAClB,SAAS,qBAAmC;AAC5C,SAAS,yBAAyB,oCAAoC;AACtE,SAAS,2BAA2B;AACpC,SAAS,qBAAqB;AAI9B,SAAS,cAAc,kCAAkC;AACzD,SAAS,0BAA0B;AACnC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,yBAAyB,2BAA2B;AAC7D,SAAS,4BAA4B;AACrC,SAAS,sBAAsB;AAC/B,SAAS,yBAAyB;AAClC,SAAS,iBAAiB,eAAe,wBAAwB;AAEjE,SAAS,gCAAgC;AAuBzC,MAAM,gBAAgB,EAAE,OAAO,CAAC,CAAC,EAAE,YAAY;AAE/C,MAAM,sBAAsB,CAAC,UAAmD;AAC9E,MAAI,SAAS,OAAO,UAAU,YAAY,CAAC,MAAM,QAAQ,KAAK,GAAG;AAC/D,WAAO;AAAA,EACT;AACA,MAAI,OAAO,UAAU,SAAU,QAAO;AACtC,QAAM,SAAS,yBAAyB,KAAK;AAC7C,SAAO,UAAU,OAAO,WAAW,YAAY,CAAC,MAAM,QAAQ,MAAM,IAChE,SACA;AACN;AAEA,MAAM,sBAAsB,CAAC,UAA0C,aAA6B;AAClG,MAAI,CAAC,SAAU,QAAO,YAAY;AAClC,QAAM,WAAW,SAAS;AAC1B,QAAM,UAAU,SAAS;AACzB,QAAM,cAAc,OAAO,UAAU,gBAAgB,WAAW,SAAS,cAAc;AACvF,MAAI,YAAa,QAAO;AACxB,QAAM,QAAQ,OAAO,SAAS,cAAc,WAAW,QAAQ,YAAY;AAC3E,QAAM,OAAO,OAAO,SAAS,aAAa,WAAW,QAAQ,WAAW;AACxE,QAAM,YAAY,OAAO,SAAS,kBAAkB,WAAW,QAAQ,gBAAgB;AACvF,QAAM,QAAQ,CAAC,aAAa,OAAO,IAAI,EAAE,OAAO,CAAC,SAAS,QAAQ,KAAK,KAAK,EAAE,MAAM;AACpF,MAAI,MAAM,OAAQ,QAAO,MAAM,KAAK,GAAG;AACvC,SAAO,YAAY;AACrB;AAEA,MAAM,uBAAuB,CAAC,aAA6C;AACzE,MAAI,CAAC,SAAU,QAAO;AACtB,QAAM,WAAW,SAAS;AAC1B,QAAM,UAAU,OAAO,UAAU,iBAAiB,WAAW,SAAS,eAAe;AACrF,SAAO,WAAW;AACpB;AAEA,MAAM,aAAa,EAChB,OAAO;AAAA,EACN,MAAM,EAAE,OAAO,OAAO,EAAE,IAAI,CAAC,EAAE,QAAQ,CAAC;AAAA,EACxC,UAAU,EAAE,OAAO,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,EAAE,QAAQ,EAAE;AAAA,EACtD,QAAQ,EAAE,OAAO,EAAE,SAAS;AAAA,EAC5B,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS;AAAA,EAC/B,YAAY,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS;AAAA,EACvC,WAAW,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS;AAAA,EACtC,YAAY,EACT,OAAO,EACP,SAAS,EACT;AAAA,IACC;AAAA,EACF;AAAA,EACF,iBAAiB,EACd,OAAO,EACP,SAAS,EACT;AAAA,IACC;AAAA,EACF;AAAA,EACF,kBAAkB,EAAE,OAAO,OAAO,EAAE,IAAI,CAAC,EAAE,SAAS;AAAA,EACpD,kBAAkB,EAAE,OAAO,OAAO,EAAE,IAAI,CAAC,EAAE,SAAS;AAAA,EACpD,aAAa,EAAE,OAAO,OAAO,EAAE,SAAS;AAAA,EACxC,aAAa,EAAE,OAAO,OAAO,EAAE,SAAS;AAAA,EACxC,eAAe,EAAE,OAAO,OAAO,EAAE,SAAS;AAAA,EAC1C,eAAe,EAAE,OAAO,OAAO,EAAE,SAAS;AAAA,EAC1C,UAAU,EAAE,OAAO,EAAE,SAAS;AAAA,EAC9B,QAAQ,EAAE,OAAO,EAAE,SAAS;AAAA,EAC5B,QAAQ,EACL,OAAO,EACP,SAAS,EACT,OAAO,iBAAiB,EAAE,SAAS,iDAAiD,CAAC;AAAA,EACxF,aAAa,EAAE,OAAO,EAAE,SAAS;AAAA,EACjC,WAAW,EAAE,OAAO,EAAE,SAAS;AAAA,EAC/B,SAAS,EAAE,KAAK,CAAC,OAAO,MAAM,CAAC,EAAE,SAAS;AAAA,EAC1C,aAAa,EAAE,OAAO,QAAQ,EAAE,SAAS;AAC3C,CAAC,EACA,YAAY;AAIf,SAAS,aAAa,OAAkB,cAAsB,MAAoB;AAChF,QAAM,UAAmC,CAAC;AAC1C,MAAI,MAAM,GAAI,SAAQ,KAAK,EAAE,KAAK,MAAM,GAAG;AAC3C,MAAI,MAAM,UAAU,MAAM,OAAO,KAAK,EAAE,SAAS,GAAG;AAClD,UAAM,OAAO,eAAe,MAAM,OAAO,KAAK,CAAC;AAC/C,YAAQ,YAAY,IAAI,EAAE,QAAQ,KAAK;AAAA,EACzC;AACA,MAAI,MAAM,YAAY;AACpB,YAAQ,qBAAqB,EAAE,KAAK,MAAM,WAAW;AAAA,EACvD;AAIA,MAAI,MAAM,WAAW;AACnB,YAAQ,aAAa,EAAE,KAAK,MAAM,UAAU;AAAA,EAC9C,OAAO;AACL,UAAM,aAAa,cAAc,MAAM,UAAU;AACjD,UAAM,kBAAkB,kBAAkB,MAAM,eAAe,MAAM;AACrE,QAAI,WAAW,UAAU,iBAAiB;AAIxC,cAAQ,MAAM,CAAC,EAAE,YAAY,EAAE,KAAK,WAAW,EAAE,GAAG,EAAE,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;AAAA,IACxF,WAAW,iBAAiB;AAC1B,cAAQ,aAAa,EAAE,SAAS,MAAM;AAAA,IACxC,WAAW,WAAW,QAAQ;AAC5B,cAAQ,aAAa,EAAE,KAAK,WAAW;AAAA,IACzC;AAAA,EACF;AACA,QAAM,YAAoC,CAAC;AAC3C,MAAI,OAAO,MAAM,qBAAqB,SAAU,WAAU,OAAO,MAAM;AACvE,MAAI,OAAO,MAAM,qBAAqB,SAAU,WAAU,OAAO,MAAM;AACvE,MAAI,OAAO,KAAK,SAAS,EAAE,QAAQ;AACjC,YAAQ,kBAAkB;AAAA,EAC5B;AACA,QAAM,WAAmC,CAAC;AAC1C,MAAI,OAAO,MAAM,gBAAgB,SAAU,UAAS,OAAO,MAAM;AACjE,MAAI,OAAO,MAAM,gBAAgB,SAAU,UAAS,OAAO,MAAM;AACjE,MAAI,OAAO,KAAK,QAAQ,EAAE,QAAQ;AAChC,YAAQ,yBAAyB;AAAA,EACnC;AACA,QAAM,aAAqC,CAAC;AAC5C,MAAI,OAAO,MAAM,kBAAkB,SAAU,YAAW,OAAO,MAAM;AACrE,MAAI,OAAO,MAAM,kBAAkB,SAAU,YAAW,OAAO,MAAM;AACrE,MAAI,OAAO,KAAK,UAAU,EAAE,QAAQ;AAClC,YAAQ,2BAA2B;AAAA,EACrC;AACA,QAAM,YAAkC,CAAC;AACzC,MAAI,MAAM,UAAU;AAClB,UAAM,OAAO,IAAI,KAAK,MAAM,QAAQ;AACpC,QAAI,CAAC,OAAO,MAAM,KAAK,QAAQ,CAAC,EAAG,WAAU,OAAO;AAAA,EACtD;AACA,MAAI,MAAM,QAAQ;AAChB,UAAM,KAAK,IAAI,KAAK,MAAM,MAAM;AAChC,QAAI,CAAC,OAAO,MAAM,GAAG,QAAQ,CAAC,EAAG,WAAU,OAAO;AAAA,EACpD;AACA,MAAI,OAAO,KAAK,SAAS,EAAE,QAAQ;AACjC,YAAQ,aAAa;AAAA,EACvB;AACA,QAAM,SAAS,iBAAiB,MAAM,MAAM;AAC5C,MAAI,kBAAkB,MAAM,WAAW,MAAM,MAAM;AACjD,YAAQ,KAAK,EAAE,KAAK,uCAAuC;AAAA,EAC7D,WAAW,OAAO,QAAQ;AACxB,YAAQ,wBAAwB,IAAI,EAAE,KAAK,OAAO;AAClD,YAAQ,+BAA+B,IAAI,EAAE,KAAK,KAAK;AAAA,EACzD;AACA,SAAO;AACT;AAEA,SAAS,aAAa,cAAsB;AAC1C,SAAO;AAAA,IACL,IAAI;AAAA,IACJ,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,eAAe;AAAA,IACf,qBAAqB;AAAA,IACrB,uBAAuB;AAAA,IACvB,WAAW;AAAA,IACX,WAAW;AAAA,EACb;AACF;AAEA,MAAM,oBAAoB,CAAC,WAAgB;AACzC,QAAM,mBAAmB,oBAAoB,QAAQ,gBAAgB;AACrE,QAAM,WAAW,oBAAoB,QAAQ,QAAQ;AAErD,SAAO;AAAA,IACL,IAAI,QAAQ,MAAM;AAAA,IAClB,aAAa,QAAQ,eAAe;AAAA,IACpC,aAAa,QAAQ,eAAe;AAAA,IACpC,kBAAkB,QAAQ,oBAAoB;AAAA,IAC9C,mBAAmB,QAAQ,qBAAqB;AAAA,IAChD;AAAA,IACA;AAAA,IACA,mBAAmB,QAAQ,qBAAqB;AAAA,IAChD,mBAAmB,QAAQ,qBAAqB;AAAA,IAChD,SAAS,QAAQ,YAAY;AAAA,IAC7B,eAAgB,QAAgB,iBAAiB;AAAA,IACjD,QAAS,QAAgB,UAAU;AAAA,IACnC,WAAY,QAAgB,aAAa;AAAA,IACzC,cAAc,oBAAoB,kBAAkB,QAAQ,oBAAoB,IAAI;AAAA,IACpF,cACE,qBAAqB,gBAAgB,MACpC,OAAO,UAAU,kBAAkB,WAAW,SAAS,gBAAgB;AAAA,IAC1E,cAAc,QAAQ,gBAAgB;AAAA,IACtC,UAAU,QAAQ,WAAW,OAAO,SAAS,YAAY,IAAI;AAAA,IAC7D,oBAAoB,QAAQ,qBAAqB,OAAO,mBAAmB,YAAY,IAAI;AAAA,IAC3F,mBAAmB,QAAQ,qBAAqB;AAAA,IAChD,kBAAkB,QAAQ,oBAAoB;AAAA,IAC9C,yBAAyB,oBAAoB,QAAQ,uBAAuB;AAAA,IAC5E,wBAAwB,oBAAoB,QAAQ,sBAAsB;AAAA,IAC1E,kBAAkB,QAAQ,oBAAoB;AAAA,IAC9C,oBAAoB,QAAQ,sBAAsB;AAAA,IAClD,wBAAwB,oBAAoB,QAAQ,sBAAsB;AAAA,IAC1E,iBAAiB,QAAQ,mBAAmB;AAAA,IAC5C,mBAAmB,QAAQ,qBAAqB;AAAA,IAChD,uBAAuB,oBAAoB,QAAQ,qBAAqB;AAAA;AAAA;AAAA;AAAA,IAIxE,WAAW,QAAQ,YACd,OAAO,qBAAqB,OAAO,OAAO,UAAU,YAAY,IAAI,OAAO,YAC5E;AAAA,EACN;AACF;AAEA,MAAM,aAAa,OAAO,SAAc,QAAa;AACnD,QAAM,QAAQ,MAAM,QAAQ,SAAS,KAAK,IAAK,QAAQ,QAAuC,CAAC;AAC/F,MAAI,CAAC,MAAM,OAAQ;AACnB,QAAM,MAAM,MACT,IAAI,CAAC,SAAU,QAAQ,OAAO,KAAK,OAAO,WAAW,KAAK,KAAK,IAAK,EACpE,OAAO,CAAC,OAAqB,CAAC,CAAC,EAAE;AACpC,MAAI,CAAC,IAAI,OAAQ;AACjB,QAAM,KAAK,KAAK,WAAW,UAAW,IAAI,UAAU,QAAQ,IAAI,IAAY;AAC5E,MAAI,CAAC,GAAI;AACT,QAAM,QAAiC;AAAA,IACrC,YAAY,EAAE,KAAK,IAAI;AAAA,IACvB,cAAc,KAAK,eAAe;AAAA,EACpC;AACA,MAAI,KAAK,MAAM,SAAU,OAAM,WAAW,IAAI,KAAK;AACnD,QAAM,SACJ,MAAM,QAAQ,KAAK,eAAe,KAAK,IAAI,gBAAgB,SACvD,IAAI,gBAAgB,OAAO,CAAC,QAAuB,CAAC,CAAC,GAAG,IACxD,KAAK,yBACH,CAAC,IAAI,sBAAsB,IAC3B,CAAC;AACT,MAAI,OAAO,OAAQ,OAAM,iBAAiB,EAAE,KAAK,OAAO;AACxD,QAAM,cAAc,MAAM,GAAG;AAAA,IAC3B;AAAA,IACA;AAAA,IACA,EAAE,UAAU,CAAC,KAAK,EAAE;AAAA,EACtB;AACA,QAAM,UAAU,oBAAI,IAAwE;AAC5F,cAAY,QAAQ,CAAC,eAAoB;AACvC,UAAM,MAAM,YAAY;AACxB,UAAM,aAAa,YAAY;AAC/B,QAAI,CAAC,OAAO,OAAO,IAAI,OAAO,YAAY,OAAO,eAAe,SAAU;AAC1E,UAAM,QAAQ;AAAA,MACZ,IAAI,IAAI;AAAA,MACR,OAAO,OAAO,IAAI,UAAU,YAAY,IAAI,MAAM,KAAK,EAAE,SAAS,IAAI,QAAQ,IAAI,QAAQ,IAAI;AAAA,MAC9F,OAAO,OAAO,IAAI,UAAU,YAAY,IAAI,MAAM,KAAK,EAAE,SAAS,IAAI,QAAQ;AAAA,IAChF;AACA,UAAM,OAAO,QAAQ,IAAI,UAAU,KAAK,CAAC;AACzC,SAAK,KAAK,KAAK;AACf,YAAQ,IAAI,YAAY,IAAI;AAAA,EAC9B,CAAC;AACD,QAAM,QAAQ,CAAC,SAA8B;AAC3C,UAAM,KAAK,QAAQ,OAAO,KAAK,OAAO,WAAW,KAAK,KAAK;AAC3D,QAAI,CAAC,GAAI;AACT,UAAM,OAAO,QAAQ,IAAI,EAAE;AAC3B,QAAI,KAAM,MAAK,OAAO;AAAA,EACxB,CAAC;AACH;AAQO,MAAM,qBAAqB,OAChC,SACA,QACG;AACH,QAAM,QAAQ,MAAM,QAAQ,SAAS,KAAK,IAAI,QAAQ,QAAQ,CAAC;AAC/D,MAAI,CAAC,MAAM,OAAQ;AACnB,QAAM,aAAa,MAAM;AAAA,IACvB,IAAI;AAAA,MACF,MACG,IAAI,CAAC,SAAU,QAAQ,OAAO,KAAK,cAAc,WAAW,KAAK,YAAY,IAAK,EAClF,OAAO,CAAC,UAA2B,CAAC,CAAC,KAAK;AAAA,IAC/C;AAAA,EACF;AACA,MAAI,CAAC,WAAW,OAAQ;AACxB,QAAM,KAAK,KAAK,WAAW,UAAW,IAAI,UAAU,QAAQ,IAAI,IAAsB;AACtF,MAAI,CAAC,GAAI;AAET,QAAM,QAAiC,EAAE,IAAI,EAAE,KAAK,WAAW,EAAE;AACjE,MAAI,KAAK,MAAM,SAAU,OAAM,WAAW,IAAI,KAAK;AACnD,QAAM,SACJ,MAAM,QAAQ,KAAK,eAAe,KAAK,IAAI,gBAAgB,SACvD,IAAI,gBAAgB,OAAO,CAAC,QAAuB,CAAC,CAAC,GAAG,IACxD,KAAK,yBACH,CAAC,IAAI,sBAAsB,IAC3B,CAAC;AACT,MAAI,OAAO,OAAQ,OAAM,iBAAiB,EAAE,KAAK,OAAO;AAMxD,QAAM,WAAW,MAAM;AAAA,IACrB;AAAA,IACA;AAAA,IACA;AAAA,IACA,EAAE,QAAQ,CAAC,MAAM,QAAQ,MAAM,EAAE;AAAA,IACjC;AAAA,MACE,UAAU,KAAK,MAAM,YAAY;AAAA,MACjC,gBAAgB,KAAK,0BAA0B,KAAK,MAAM,SAAS;AAAA,IACrE;AAAA,EACF;AACA,QAAM,OAAO,IAAI,IAAI,SAAS,IAAI,CAAC,YAAY,CAAC,QAAQ,IAAI,OAAO,CAAC,CAAC;AACrE,QAAM,QAAQ,CAAC,SAAS;AACtB,QAAI,CAAC,QAAQ,OAAO,KAAK,cAAc,SAAU;AACjD,UAAM,UAAU,KAAK,IAAI,KAAK,SAAS;AACvC,SAAK,cAAc,SAAS,QAAQ;AACpC,SAAK,cAAc,SAAS,QAAQ;AAAA,EACtC,CAAC;AACH;AAEA,eAAe,2BACb,KACA,WACA;AACA,QAAM,OAAO,IAAI,WAAW,UAAU,aAAa;AACnD,QAAM,OAAO,IAAI;AACjB,MAAI,CAAC,QAAQ,CAAC,MAAM,IAAK;AACzB,QAAM,KAAK,MAAM,KAAK,mBAAmB,KAAK,KAAK,CAAC,6BAA6B,GAAG;AAAA,IAClF,UAAU,KAAK,YAAY;AAAA,IAC3B,gBAAgB,IAAI,0BAA0B,KAAK,SAAS;AAAA,EAC9D,CAAC;AACD,MAAI,CAAC,IAAI;AACP,UAAM,IAAI,cAAc,KAAK;AAAA,MAC3B,OAAO,UAAU,gDAAgD,mCAAmC;AAAA,IACtG,CAAC;AAAA,EACH;AACF;AAEO,SAAS,yBAAyB,SAA0B;AACjE,QAAM,eAAe,QAAQ,gBAAgB,gBAAgB,iBAAiB;AAC9E,QAAM,eAAe,QAAQ,SAAS,UAAU,oBAAoB;AAEpE,QAAM,gBAAgB;AAAA,IACpB,KAAK,EAAE,aAAa,MAAM,iBAAiB,CAAC,QAAQ,WAAW,EAAE;AAAA,IACjE,MAAM,EAAE,aAAa,MAAM,iBAAiB,CAAC,QAAQ,aAAa,EAAE;AAAA,IACpE,KAAK,EAAE,aAAa,MAAM,iBAAiB,CAAC,QAAQ,aAAa,EAAE;AAAA,IACnE,QAAQ,EAAE,aAAa,MAAM,iBAAiB,CAAC,QAAQ,aAAa,EAAE;AAAA,EACxE;AAEA,QAAM,eAAe;AAAA,IACnB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAEA,QAAM,kBAAkB;AAAA,IACtB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAEA,QAAM,kBAAkB,CAAC,cAAc,aAAa;AAEpD,QAAM,aAAa;AAAA,IACjB,GAAG;AAAA,IACH,GAAI,QAAQ,SAAS,UAAU,kBAAkB;AAAA,EACnD;AAMA,QAAM,6BAA6B,oBAAI,IAAI;AAAA,IACzC;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AAED,QAAM,aAAa,WAAW,OAAO,CAAC,UAAU,CAAC,2BAA2B,IAAI,KAAK,CAAC;AAKtF,QAAM,oBAAoB,CAAC,UACzB,SAAS,OAAO,MAAM,OAAO,YAAY,MAAM,GAAG,SAAS,aAAa;AAE1E,SAAO;AAAA,IACL,UAAU;AAAA,IACV,KAAK;AAAA,MACH,QAAQ,QAAQ;AAAA,MAChB,SAAS;AAAA,MACT,UAAU;AAAA,MACV,aAAa;AAAA,MACb,iBAAiB;AAAA,IACnB;AAAA,IACA,SAAS;AAAA,MACP,YAAY,QAAQ;AAAA,IACtB;AAAA,IACA,WAAW,QAAQ,SAAS,UAAU,EAAE,UAAU,QAAQ,SAAS,IAAI;AAAA,IACvE,MAAM;AAAA,MACJ,QAAQ;AAAA,MACR,UAAU,QAAQ;AAAA,MAClB,QAAQ,CAAC,UAAqB,kBAAkB,KAAK;AAAA,MACrD,cAAc,aAAa,YAAY;AAAA,MACvC,cAAc,OAAO,UAAe,aAAa,OAAO,cAAc,QAAQ,IAAI;AAAA,MAClF,sBAAsB,EAAE,WAAW,CAAC,QAAQ,QAAQ,EAAE;AAAA,MACtD,OAAO;AAAA,QACL;AAAA,UACE,OAAO;AAAA,UACP,OAAO;AAAA,UACP,MAAM,EAAE,OAAO,KAAK;AAAA,UACpB,IAAI,EAAE,OAAO,cAAc;AAAA,UAC3B,MAAM;AAAA,QACR;AAAA,MACF;AAAA,MACA,eAAe,CAAC,SAAc;AAC5B,cAAM,WAAW,CAAC,UAAkC;AAClD,cAAI,OAAO,UAAU,SAAU,QAAO,OAAO,MAAM,KAAK,IAAI,OAAO;AACnE,cAAI,OAAO,UAAU,YAAY,MAAM,KAAK,EAAE,QAAQ;AACpD,kBAAM,SAAS,OAAO,KAAK;AAC3B,mBAAO,OAAO,MAAM,MAAM,IAAI,OAAO;AAAA,UACvC;AACA,iBAAO;AAAA,QACT;AACA,cAAM,OAAO;AAAA,UACX,IAAI,KAAK;AAAA,UACT,CAAC,QAAQ,WAAW,GAAG,KAAK,YAAY,KAAK;AAAA,UAC7C,QAAQ,KAAK,UAAU;AAAA,UACvB,eAAe,KAAK,mBAAmB;AAAA,UACvC,kBAAkB,KAAK,sBAAsB;AAAA,UAC7C,mBAAmB,KAAK,uBAAuB;AAAA,UAC/C,kBAAkB,KAAK,sBAAsB;AAAA,UAC7C,mBAAmB,KAAK,uBAAuB;AAAA,UAC/C,kBAAkB,KAAK,sBAAsB;AAAA,UAC7C,oBAAoB,KAAK,wBAAwB;AAAA,UACjD,wBAAwB,oBAAoB,KAAK,wBAAwB;AAAA,UACzE,iBAAiB,KAAK,qBAAqB;AAAA,UAC3C,mBAAmB,KAAK,uBAAuB;AAAA,UAC/C,uBAAuB,oBAAoB,KAAK,uBAAuB;AAAA,UACvE,cAAc,KAAK,iBAAiB;AAAA,UACpC,WAAW,KAAK,cAAc;AAAA,UAC9B,mBAAmB,KAAK,sBAAsB;AAAA,UAC9C,mBAAmB,KAAK,sBAAsB;AAAA,UAC9C,UAAU,KAAK,aAAa;AAAA,UAC5B,oBAAoB,KAAK,wBAAwB;AAAA,UACjD,SAAS,KAAK,YAAY;AAAA,UAC1B,WAAW,KAAK,cAAc;AAAA,UAC9B,YAAY,KAAK,eAAe;AAAA,UAChC,eAAe,SAAS,KAAK,eAAe;AAAA,UAC5C,mBAAmB,SAAS,KAAK,mBAAmB;AAAA,UACpD,qBAAqB,SAAS,KAAK,qBAAqB;AAAA,UACxD,qBAAqB,SAAS,KAAK,qBAAqB;AAAA,UACxD,gBAAgB,SAAS,KAAK,gBAAgB;AAAA,UAC9C,mBAAmB,SAAS,KAAK,mBAAmB;AAAA,UACpD,qBAAqB,SAAS,KAAK,qBAAqB;AAAA,UACxD,sBAAsB,SAAS,KAAK,sBAAsB;AAAA,UAC1D,qBAAqB,SAAS,KAAK,sBAAsB;AAAA,UACzD,uBAAuB,SAAS,KAAK,wBAAwB;AAAA,UAC7D,iBAAiB,SAAS,KAAK,iBAAiB;AAAA,UAChD,qBAAqB,SAAS,KAAK,qBAAqB;AAAA,UACxD,mBAAmB,SAAS,KAAK,kBAAkB;AAAA,UACnD,kBAAkB,oBAAoB,KAAK,iBAAiB;AAAA,UAC5D,wBAAwB,oBAAoB,KAAK,wBAAwB;AAAA,UACzE,yBAAyB,oBAAoB,KAAK,yBAAyB;AAAA,UAC3E,UAAU,oBAAoB,KAAK,QAAQ;AAAA,UAC3C,gBAAgB,KAAK,mBAAmB;AAAA,UACxC,UAAU,KAAK,aAAa;AAAA,UAC5B,WAAW,KAAK;AAAA,UAChB,WAAW,KAAK;AAAA,QAClB;AACA,cAAM,YAAY,6BAA6B,IAA+B;AAC9E,cAAM,aAAa,EAAE,GAAG,KAAK;AAC7B,eAAO,KAAK,UAAU,EAAE,QAAQ,CAAC,QAAQ;AACvC,cAAI,IAAI,WAAW,KAAK,EAAG,QAAQ,WAAmB,GAAG;AAAA,QAC3D,CAAC;AACD,eAAO,OAAO,KAAK,SAAS,EAAE,SAAS,EAAE,GAAG,YAAY,GAAG,UAAU,IAAI;AAAA,MAC3E;AAAA,IACF;AAAA,IACA,SAAS;AAAA,MACP,QAAQ;AAAA,QACN,WAAW,QAAQ;AAAA,QACnB,QAAQ;AAAA,QACR,UAAU,OAAO,EAAE,KAAK,IAAI,MAAsC;AAChE,gBAAM,EAAE,UAAU,IAAI,MAAM,oBAAoB;AAChD,gBAAM,EAAE,MAAM,OAAO,IAAI,wBAAwB,OAAO,CAAC,CAAC;AAC1D,gBAAM,SAAS;AAAA,YACb;AAAA,YACA,OAAO,KAAK,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,cAAc,OAAO,IAAI;AAAA,YACjE;AAAA,YACA;AAAA,UACF;AACA,iBAAO;AAAA,QACT;AAAA,QACA,UAAU,CAAC,EAAE,OAAO,OAAiD;AAAA,UACnE,IAAI,QAAQ,WAAW,QAAQ,WAAW,QAAQ,MAAM;AAAA,UACxD,GAAI,QAAQ,SAAS,WAAW,MAAM,QAAQ,QAAQ,QAAQ,IAC1D,EAAE,UAAU,OAAO,SAAS,IAC5B,CAAC;AAAA,QACP;AAAA,QACA,QAAQ;AAAA,MACV;AAAA,MACA,QAAQ;AAAA,QACN,WAAW,QAAQ;AAAA,QACnB,QAAQ;AAAA,QACR,UAAU,OAAO,EAAE,KAAK,IAAI,MAAsC;AAChE,gBAAM,EAAE,UAAU,IAAI,MAAM,oBAAoB;AAChD,gBAAM,EAAE,MAAM,OAAO,IAAI,wBAAwB,OAAO,CAAC,CAAC;AAC1D,gBAAM,cACJ,QAAQ,SAAS,UACZ,KAAiC,cACjC,KAAiC;AACxC,cAAI,OAAO,gBAAgB,UAAU;AACnC,kBAAM,2BAA2B,KAAK,SAAS;AAAA,UACjD;AACA,gBAAM,SAAS;AAAA,YACb;AAAA,YACA,OAAO,KAAK,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,cAAc,OAAO,IAAI;AAAA,YACjE;AAAA,YACA;AAAA,UACF;AACA,iBAAO;AAAA,QACT;AAAA,QACA,UAAU,CAAC,EAAE,OAAO,MAClB,kBAAmB,QAAgB,SAAU,QAAgB,SAAS,MAAM;AAAA,MAChF;AAAA,MACA,QAAQ;AAAA,QACN,WAAW,QAAQ;AAAA,QACnB,QAAQ;AAAA,QACR,UAAU,OAAO,EAAE,QAAQ,IAAI,MAAqC;AAClE,gBAAM,EAAE,UAAU,IAAI,MAAM,oBAAoB;AAChD,gBAAM,KAAK,oBAAoB,QAAQ,KAAK,SAAS;AACrD,iBAAO,EAAE,GAAG;AAAA,QACd;AAAA,QACA,UAAU,OAAO,EAAE,IAAI,KAAK;AAAA,MAC9B;AAAA,IACF;AAAA,IACA,OAAO;AAAA,MACL,WAAW,OAAO,SAAc,QAAiB;AAC/C,cAAM,WAAW,SAAS,EAAE,GAAG,KAAK,aAAa,QAAQ,KAAK,CAAC;AAC/D,cAAM,mBAAmB,SAAS,GAAG;AAAA,MACvC;AAAA,IACF;AAAA,EACF;AACF;AAEO,SAAS,qBAAqB,SAA0B;AAC7D,QAAM,eAAe,QAAQ,SAAS,UAAU,oBAAoB;AACpE,QAAM,aAAa,EAAE,OAAO;AAAA,IAC1B,IAAI,EAAE,OAAO,EAAE,KAAK;AAAA,IACpB,CAAC,QAAQ,WAAW,GAAG,EAAE,OAAO,EAAE,SAAS;AAAA,IAC3C,QAAQ,EAAE,OAAO,EAAE,SAAS;AAAA,IAC5B,eAAe,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS;AAAA,IACrD,kBAAkB,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS;AAAA,IAC7C,mBAAmB,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS;AAAA,IAC9C,kBAAkB,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS;AAAA,IAC7C,mBAAmB,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS;AAAA,IAC9C,mBAAmB,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,IAClD,mBAAmB,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,IAClD,SAAS,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,IACxC,UAAU,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,IACzC,oBAAoB,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,IACnD,kBAAkB,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE,QAAQ,CAAC,EAAE,SAAS,EAAE,SAAS;AAAA,IACxE,wBAAwB,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE,QAAQ,CAAC,EAAE,SAAS,EAAE,SAAS;AAAA,IAC9E,yBAAyB,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE,QAAQ,CAAC,EAAE,SAAS,EAAE,SAAS;AAAA,IAC/E,kBAAkB,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS;AAAA,IACxD,oBAAoB,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,IACnD,wBAAwB,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE,QAAQ,CAAC,EAAE,SAAS,EAAE,SAAS;AAAA,IAC9E,iBAAiB,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS;AAAA,IACvD,mBAAmB,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,IAClD,uBAAuB,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE,QAAQ,CAAC,EAAE,SAAS,EAAE,SAAS;AAAA,IAC7E,cAAc,EAAE,OAAO,EAAE,SAAS;AAAA,IAClC,WAAW,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS;AAAA,IACtC,aAAa,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,IAC5C,aAAa,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,IAC5C,gBAAgB,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS;AAAA,IAC3C,UAAU,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS;AAAA,IACrC,WAAW,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,IAC1C,YAAY,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,IAC3C,eAAe,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,IAC9C,mBAAmB,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,IAClD,qBAAqB,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,IACpD,qBAAqB,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,IACpD,gBAAgB,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,IAC/C,mBAAmB,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,IAClD,qBAAqB,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,IACpD,sBAAsB,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,IACrD,qBAAqB,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,IACpD,uBAAuB,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,IACtD,iBAAiB,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,IAChD,qBAAqB,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,IACpD,mBAAmB,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,IAClD,WAAW,EAAE,OAAO;AAAA,IACpB,WAAW,EAAE,OAAO;AAAA,IACpB,cAAc,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE,QAAQ,CAAC,EAAE,SAAS;AAAA,IACzD,cAAc,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE,QAAQ,CAAC,EAAE,SAAS;AAAA,EAC3D,CAAC;AAED,QAAM,qBAAqB,8BAA8B,UAAU;AAEnE,SAAO,uBAAuB;AAAA,IAC5B,cAAc,QAAQ,SAAS,UAAU,UAAU;AAAA,IACnD,aAAa;AAAA,IACb;AAAA,IACA,QAAQ;AAAA,MACN,QAAQ;AAAA,MACR,gBAAgB,QAAQ,SAAS,UAC7B,EAAE,OAAO;AAAA,QACP,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS;AAAA,QAC/B,UAAU,EAAE,MAAM,EAAE,OAAO;AAAA,UACzB,MAAM,EAAE,QAAQ,iCAAiC;AAAA,UACjD,QAAQ,EAAE,MAAM,EAAE,KAAK,2BAA2B,CAAC;AAAA,QACrD,CAAC,CAAC,EAAE,SAAS;AAAA,MACf,CAAC,IACD,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC;AAAA,MACjD,aAAa,QAAQ,SAAS,UAC1B,iQACA;AAAA,IACN;AAAA,IACA,KAAK;AAAA,MACH,QAAQ;AAAA,MACR,gBAAgB,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;AAAA,MAC5C,aAAa,mBAAmB,QAAQ,IAAI;AAAA,IAC9C;AAAA,EACF,CAAC;AACH;AAGO,SAAS,wBAAwB,SAA0B;AAChE,QAAM,OAAO,cAAc,yBAAyB,OAAO,CAAC;AAC5D,QAAM,EAAE,KAAK,MAAM,KAAK,OAAO,IAAI;AACnC,SAAO,EAAE,KAAK,MAAM,KAAK,QAAQ,SAAS,qBAAqB,OAAO,GAAG,UAAU,KAAK,SAAS;AACnG;",
|
|
4
|
+
"sourcesContent": ["import { z } from 'zod'\nimport { makeCrudRoute, type CrudCtx } from '@open-mercato/shared/lib/crud/factory'\nimport { splitCustomFieldPayload, extractAllCustomFieldEntries } from '@open-mercato/shared/lib/crud/custom-fields'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { E } from '#generated/entities.ids.generated'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { SalesOrder, SalesQuote } from '../../data/entities'\nimport { SalesChannel, SalesDocumentTagAssignment } from '../../data/entities'\nimport { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport {\n ORDER_PAYMENT_LEDGER_FIELDS,\n ORDER_PAYMENT_LEDGER_WARNING_CODE,\n orderCreateSchema,\n quoteCreateSchema,\n type OrderPaymentLedgerWarning,\n} from '../../data/validators'\nimport {\n createPagedListResponseSchema,\n createSalesCrudOpenApi,\n defaultDeleteRequestSchema,\n} from '../openapi'\nimport { parseScopedCommandInput, resolveCrudRecordId } from '../utils'\nimport { documentUpdateSchema } from '../../commands/documents'\nimport { buildIlikeTerm } from '@open-mercato/shared/lib/db/buildIlikeTerm'\nimport { parseBooleanToken } from '@open-mercato/shared/lib/boolean'\nimport { isUuidListParam, parseIdsParam, splitIdListParam } from '@open-mercato/shared/lib/crud/ids'\nimport type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport { parseDecryptedFieldValue } from '@open-mercato/shared/lib/encryption/tenantDataEncryptionService'\n\ntype DocumentKind = 'order' | 'quote'\n\ntype DocumentBinding = {\n kind: DocumentKind\n entity: typeof SalesOrder | typeof SalesQuote\n entityId: (typeof E.sales)[keyof typeof E.sales]\n numberField: 'orderNumber' | 'quoteNumber'\n createCommandId: string\n updateCommandId: string\n deleteCommandId: string\n manageFeature: string\n viewFeature: string\n}\n\ntype DocumentCreateResult = {\n orderId?: string\n quoteId?: string\n id?: string\n warnings?: OrderPaymentLedgerWarning[]\n}\n\nconst rawBodySchema = z.object({}).passthrough()\n\nconst normalizeJsonRecord = (value: unknown): Record<string, unknown> | null => {\n if (value && typeof value === 'object' && !Array.isArray(value)) {\n return value as Record<string, unknown>\n }\n if (typeof value !== 'string') return null\n const parsed = parseDecryptedFieldValue(value)\n return parsed && typeof parsed === 'object' && !Array.isArray(parsed)\n ? parsed as Record<string, unknown>\n : null\n}\n\nconst resolveCustomerName = (snapshot: Record<string, unknown> | null, fallback?: string | null) => {\n if (!snapshot) return fallback ?? null\n const customer = snapshot.customer as Record<string, unknown> | undefined\n const contact = snapshot.contact as Record<string, unknown> | undefined\n const displayName = typeof customer?.displayName === 'string' ? customer.displayName : null\n if (displayName) return displayName\n const first = typeof contact?.firstName === 'string' ? contact.firstName : null\n const last = typeof contact?.lastName === 'string' ? contact.lastName : null\n const preferred = typeof contact?.preferredName === 'string' ? contact.preferredName : null\n const parts = [preferred ?? first, last].filter((part) => part && part.trim().length)\n if (parts.length) return parts.join(' ')\n return fallback ?? null\n}\n\nconst resolveCustomerEmail = (snapshot: Record<string, unknown> | null) => {\n if (!snapshot) return null\n const customer = snapshot.customer as Record<string, unknown> | undefined\n const primary = typeof customer?.primaryEmail === 'string' ? customer.primaryEmail : null\n return primary ?? null\n}\n\nconst listSchema = z\n .object({\n page: z.coerce.number().min(1).default(1),\n pageSize: z.coerce.number().min(1).max(100).default(50),\n search: z.string().optional(),\n id: z.string().uuid().optional(),\n customerId: z.string().uuid().optional(),\n channelId: z.string().uuid().optional(),\n channelIds: z\n .string()\n .optional()\n .describe(\n 'Comma-separated sales channel uuids; matches documents on any of them. Capped at 200 ids, malformed entries are dropped. Ignored when channelId is supplied; combines with channelIdsEmpty.',\n ),\n channelIdsEmpty: z\n .string()\n .optional()\n .describe(\n 'Boolean token; matches documents with no sales channel. Ignored when channelId is supplied; combines with channelIds.',\n ),\n lineItemCountMin: z.coerce.number().min(0).optional(),\n lineItemCountMax: z.coerce.number().min(0).optional(),\n totalNetMin: z.coerce.number().optional(),\n totalNetMax: z.coerce.number().optional(),\n totalGrossMin: z.coerce.number().optional(),\n totalGrossMax: z.coerce.number().optional(),\n dateFrom: z.string().optional(),\n dateTo: z.string().optional(),\n tagIds: z\n .string()\n .optional()\n .refine(isUuidListParam, { message: 'tagIds must be a comma-separated list of UUIDs' }),\n tagIdsEmpty: z.string().optional(),\n sortField: z.string().optional(),\n sortDir: z.enum(['asc', 'desc']).optional(),\n withDeleted: z.coerce.boolean().optional(),\n })\n .passthrough()\n\ntype ListQuery = z.infer<typeof listSchema>\n\nfunction buildFilters(query: ListQuery, numberColumn: string, kind: DocumentKind) {\n const filters: Record<string, unknown> = {}\n if (query.id) filters.id = { $eq: query.id }\n if (query.search && query.search.trim().length > 0) {\n const term = buildIlikeTerm(query.search.trim())\n filters[numberColumn] = { $ilike: term }\n }\n if (query.customerId) {\n filters.customer_entity_id = { $eq: query.customerId }\n }\n // Singular wins over plural, mirroring how `api/channels/route.ts` resolves `id` before `ids`.\n // An all-malformed `channelIds` narrows to no channel filter rather than an empty-set filter, so\n // a typo returns the unfiltered page instead of silently returning zero rows.\n if (query.channelId) {\n filters.channel_id = { $eq: query.channelId }\n } else {\n const channelIds = parseIdsParam(query.channelIds)\n const wantsUnassigned = parseBooleanToken(query.channelIdsEmpty) === true\n if (channelIds.length && wantsUnassigned) {\n // A channel multi-select with an \"(No channel)\" entry produces both at once, so they combine\n // rather than one silently dropping the other. `filters.$or` is a single key \u2014 a future filter\n // that also needs `$or` would clobber this one; nothing else in this factory writes it today.\n filters.$or = [{ channel_id: { $in: channelIds } }, { channel_id: { $exists: false } }]\n } else if (wantsUnassigned) {\n filters.channel_id = { $exists: false }\n } else if (channelIds.length) {\n filters.channel_id = { $in: channelIds }\n }\n }\n const lineRange: Record<string, number> = {}\n if (typeof query.lineItemCountMin === 'number') lineRange.$gte = query.lineItemCountMin\n if (typeof query.lineItemCountMax === 'number') lineRange.$lte = query.lineItemCountMax\n if (Object.keys(lineRange).length) {\n filters.line_item_count = lineRange\n }\n const netRange: Record<string, number> = {}\n if (typeof query.totalNetMin === 'number') netRange.$gte = query.totalNetMin\n if (typeof query.totalNetMax === 'number') netRange.$lte = query.totalNetMax\n if (Object.keys(netRange).length) {\n filters.grand_total_net_amount = netRange\n }\n const grossRange: Record<string, number> = {}\n if (typeof query.totalGrossMin === 'number') grossRange.$gte = query.totalGrossMin\n if (typeof query.totalGrossMax === 'number') grossRange.$lte = query.totalGrossMax\n if (Object.keys(grossRange).length) {\n filters.grand_total_gross_amount = grossRange\n }\n const dateRange: Record<string, Date> = {}\n if (query.dateFrom) {\n const from = new Date(query.dateFrom)\n if (!Number.isNaN(from.getTime())) dateRange.$gte = from\n }\n if (query.dateTo) {\n const to = new Date(query.dateTo)\n if (!Number.isNaN(to.getTime())) dateRange.$lte = to\n }\n if (Object.keys(dateRange).length) {\n filters.created_at = dateRange\n }\n const tagIds = splitIdListParam(query.tagIds)\n if (parseBooleanToken(query.tagIdsEmpty) === true) {\n filters.id = { $eq: '00000000-0000-0000-0000-000000000000' }\n } else if (tagIds.length) {\n filters['tag_assignments.tag_id'] = { $in: tagIds }\n filters['tag_assignments.document_kind'] = { $eq: kind }\n }\n return filters\n}\n\nfunction buildSortMap(numberColumn: string) {\n return {\n id: 'id',\n number: numberColumn,\n placedAt: 'placed_at',\n lineItemCount: 'line_item_count',\n grandTotalNetAmount: 'grand_total_net_amount',\n grandTotalGrossAmount: 'grand_total_gross_amount',\n createdAt: 'created_at',\n updatedAt: 'updated_at',\n }\n}\n\nconst mapUpdateResponse = (entity: any) => {\n const customerSnapshot = normalizeJsonRecord(entity?.customerSnapshot)\n const metadata = normalizeJsonRecord(entity?.metadata)\n\n return {\n id: entity?.id ?? null,\n orderNumber: entity?.orderNumber ?? null,\n quoteNumber: entity?.quoteNumber ?? null,\n customerEntityId: entity?.customerEntityId ?? null,\n customerContactId: entity?.customerContactId ?? null,\n customerSnapshot,\n metadata,\n externalReference: entity?.externalReference ?? null,\n customerReference: entity?.customerReference ?? null,\n comment: entity?.comments ?? null,\n statusEntryId: (entity as any)?.statusEntryId ?? null,\n status: (entity as any)?.status ?? null,\n channelId: (entity as any)?.channelId ?? null,\n customerName: resolveCustomerName(customerSnapshot, entity?.customerEntityId ?? null),\n contactEmail:\n resolveCustomerEmail(customerSnapshot) ??\n (typeof metadata?.customerEmail === 'string' ? metadata.customerEmail : null),\n currencyCode: entity?.currencyCode ?? null,\n placedAt: entity?.placedAt ? entity.placedAt.toISOString() : null,\n expectedDeliveryAt: entity?.expectedDeliveryAt ? entity.expectedDeliveryAt.toISOString() : null,\n shippingAddressId: entity?.shippingAddressId ?? null,\n billingAddressId: entity?.billingAddressId ?? null,\n shippingAddressSnapshot: normalizeJsonRecord(entity?.shippingAddressSnapshot),\n billingAddressSnapshot: normalizeJsonRecord(entity?.billingAddressSnapshot),\n shippingMethodId: entity?.shippingMethodId ?? null,\n shippingMethodCode: entity?.shippingMethodCode ?? null,\n shippingMethodSnapshot: normalizeJsonRecord(entity?.shippingMethodSnapshot),\n paymentMethodId: entity?.paymentMethodId ?? null,\n paymentMethodCode: entity?.paymentMethodCode ?? null,\n paymentMethodSnapshot: normalizeJsonRecord(entity?.paymentMethodSnapshot),\n // Return the fresh version so the client can refresh its optimistic-lock\n // token after a successful inline save \u2014 otherwise a second save on the same\n // page sends the now-stale updatedAt and falsely 409s (#2055 QA).\n updatedAt: entity?.updatedAt\n ? (entity.updatedAt instanceof Date ? entity.updatedAt.toISOString() : entity.updatedAt)\n : null,\n }\n}\n\nconst attachTags = async (payload: any, ctx: any) => {\n const items = Array.isArray(payload?.items) ? (payload.items as Array<Record<string, any>>) : []\n if (!items.length) return\n const ids = items\n .map((item) => (item && typeof item.id === 'string' ? item.id : null))\n .filter((id): id is string => !!id)\n if (!ids.length) return\n const em = ctx?.container?.resolve ? (ctx.container.resolve('em') as any) : null\n if (!em) return\n const where: Record<string, unknown> = {\n documentId: { $in: ids },\n documentKind: ctx?.bindingKind ?? null,\n }\n if (ctx?.auth?.tenantId) where.tenantId = ctx.auth.tenantId\n const orgIds =\n Array.isArray(ctx?.organizationIds) && ctx.organizationIds.length\n ? ctx.organizationIds.filter((val: string | null) => !!val)\n : ctx?.selectedOrganizationId\n ? [ctx.selectedOrganizationId]\n : []\n if (orgIds.length) where.organizationId = { $in: orgIds }\n const assignments = await em.find(\n SalesDocumentTagAssignment,\n where,\n { populate: ['tag'] },\n )\n const grouped = new Map<string, Array<{ id: string; label: string; color: string | null }>>()\n assignments.forEach((assignment: any) => {\n const tag = assignment?.tag\n const documentId = assignment?.documentId\n if (!tag || typeof tag.id !== 'string' || typeof documentId !== 'string') return\n const entry = {\n id: tag.id,\n label: typeof tag.label === 'string' && tag.label.trim().length ? tag.label : tag.slug ?? tag.id,\n color: typeof tag.color === 'string' && tag.color.trim().length ? tag.color : null,\n }\n const list = grouped.get(documentId) ?? []\n list.push(entry)\n grouped.set(documentId, list)\n })\n items.forEach((item: Record<string, any>) => {\n const id = item && typeof item.id === 'string' ? item.id : null\n if (!id) return\n const list = grouped.get(id)\n if (list) item.tags = list\n })\n}\n\n// Liveness note: this hook runs BEFORE the CRUD list cache stores the payload, so `channelName`\n// and `channelCode` are embedded in the cached entry. What keeps them fresh is that the hook also\n// runs on the cache-HIT path (shared/lib/crud/factory.ts:1683) and reassigns both fields\n// unconditionally for every item carrying a channel id. Anything that later skips this hook on a\n// hit \u2014 the way `skipEnrichersOnCacheHit` does for record-pure enrichers \u2014 would start serving the\n// stale names baked into the entry.\nexport const attachChannelNames = async (\n payload: { items?: Array<Record<string, unknown>> },\n ctx: CrudCtx,\n) => {\n const items = Array.isArray(payload?.items) ? payload.items : []\n if (!items.length) return\n const channelIds = Array.from(\n new Set(\n items\n .map((item) => (item && typeof item.channelId === 'string' ? item.channelId : null))\n .filter((value): value is string => !!value)\n )\n )\n if (!channelIds.length) return\n const em = ctx?.container?.resolve ? (ctx.container.resolve('em') as EntityManager) : null\n if (!em) return\n\n const where: Record<string, unknown> = { id: { $in: channelIds } }\n if (ctx?.auth?.tenantId) where.tenantId = ctx.auth.tenantId\n const orgIds =\n Array.isArray(ctx?.organizationIds) && ctx.organizationIds.length\n ? ctx.organizationIds.filter((val: string | null) => !!val)\n : ctx?.selectedOrganizationId\n ? [ctx.selectedOrganizationId]\n : []\n if (orgIds.length) where.organizationId = { $in: orgIds }\n\n // Only `name` and `code` are read. Without this projection the query loads the whole channel row,\n // and `sales/encryption.ts` declares eight of its columns encrypted (contact email/phone, the\n // address block) \u2014 so every documents-list request would decrypt PII it never renders, on the\n // cache-hit path too.\n const channels = await findWithDecryption(\n em,\n SalesChannel,\n where,\n { fields: ['id', 'name', 'code'] },\n {\n tenantId: ctx?.auth?.tenantId ?? null,\n organizationId: ctx?.selectedOrganizationId ?? ctx?.auth?.orgId ?? null,\n }\n )\n const byId = new Map(channels.map((channel) => [channel.id, channel]))\n items.forEach((item) => {\n if (!item || typeof item.channelId !== 'string') return\n const channel = byId.get(item.channelId)\n item.channelName = channel?.name ?? null\n item.channelCode = channel?.code ?? null\n })\n}\n\nasync function ensureNumberEditPermission(\n ctx: CrudCtx,\n translate: (key: string, fallback?: string) => string\n) {\n const rbac = ctx.container?.resolve?.('rbacService') as RbacService | null\n const auth = ctx.auth\n if (!rbac || !auth?.sub) return\n const ok = await rbac.userHasAllFeatures(auth.sub, ['sales.documents.number.edit'], {\n tenantId: auth.tenantId ?? null,\n organizationId: ctx.selectedOrganizationId ?? auth.orgId ?? null,\n })\n if (!ok) {\n throw new CrudHttpError(403, {\n error: translate('sales.documents.errors.number_edit_forbidden', 'You cannot edit document numbers.'),\n })\n }\n}\n\nexport function buildDocumentCrudOptions(binding: DocumentBinding) {\n const numberColumn = binding.numberField === 'orderNumber' ? 'order_number' : 'quote_number'\n const createSchema = binding.kind === 'order' ? orderCreateSchema : quoteCreateSchema\n\n const routeMetadata = {\n GET: { requireAuth: true, requireFeatures: [binding.viewFeature] },\n POST: { requireAuth: true, requireFeatures: [binding.manageFeature] },\n PUT: { requireAuth: true, requireFeatures: [binding.manageFeature] },\n DELETE: { requireAuth: true, requireFeatures: [binding.manageFeature] },\n }\n\n const commonFields = [\n 'id',\n numberColumn,\n 'status',\n 'status_entry_id',\n 'customer_entity_id',\n 'customer_contact_id',\n 'billing_address_id',\n 'shipping_address_id',\n 'customer_snapshot',\n 'billing_address_snapshot',\n 'shipping_address_snapshot',\n 'shipping_method_id',\n 'shipping_method_code',\n 'shipping_method_snapshot',\n 'payment_method_id',\n 'payment_method_code',\n 'payment_method_snapshot',\n 'customer_reference',\n 'metadata',\n 'external_reference',\n 'currency_code',\n 'comments',\n 'channel_id',\n 'placed_at',\n 'line_item_count',\n 'subtotal_net_amount',\n 'subtotal_gross_amount',\n 'tax_total_amount',\n 'discount_total_amount',\n 'grand_total_net_amount',\n 'grand_total_gross_amount',\n 'totals_snapshot',\n 'organization_id',\n 'tenant_id',\n 'created_at',\n 'updated_at',\n ]\n\n const orderOnlyFields = [\n 'expected_delivery_at',\n 'totals_mode',\n 'shipping_net_amount',\n 'shipping_gross_amount',\n 'surcharge_total_amount',\n 'paid_total_amount',\n 'refunded_total_amount',\n 'outstanding_amount',\n ]\n\n const quoteOnlyFields = ['valid_from', 'valid_until']\n\n const listFields = [\n ...commonFields,\n ...(binding.kind === 'order' ? orderOnlyFields : quoteOnlyFields),\n ]\n\n // Large JSONB snapshot/payload columns that only the detail page renders. The\n // grid never reads them, so selecting them for list pages fetches and decrypts\n // blobs over the wire for nothing (#2233). `customer_snapshot` is intentionally\n // kept because the grid derives the customer name/email column from it.\n const detailOnlyProjectionFields = new Set([\n 'billing_address_snapshot',\n 'shipping_address_snapshot',\n 'shipping_method_snapshot',\n 'payment_method_snapshot',\n 'totals_snapshot',\n 'metadata',\n ])\n\n const gridFields = listFields.filter((field) => !detailOnlyProjectionFields.has(field))\n\n // The detail page fetches a single document through this same list route with an\n // `?id=` filter (there is no separate detail endpoint), so it needs the full\n // projection. Grid listings (no `id`) use the trimmed projection.\n const resolveListFields = (query: ListQuery) =>\n query && typeof query.id === 'string' && query.id.length ? listFields : gridFields\n\n return {\n metadata: routeMetadata,\n orm: {\n entity: binding.entity as any,\n idField: 'id',\n orgField: 'organizationId',\n tenantField: 'tenantId',\n softDeleteField: 'deletedAt',\n },\n indexer: {\n entityType: binding.entityId,\n },\n enrichers: binding.kind === 'order' ? { entityId: binding.entityId } : undefined,\n list: {\n schema: listSchema,\n entityId: binding.entityId,\n fields: (query: ListQuery) => resolveListFields(query),\n sortFieldMap: buildSortMap(numberColumn),\n buildFilters: async (query: any) => buildFilters(query, numberColumn, binding.kind),\n decorateCustomFields: { entityIds: [binding.entityId] },\n joins: [\n {\n alias: 'tag_assignments',\n table: 'sales_document_tag_assignments',\n from: { field: 'id' },\n to: { field: 'document_id' },\n type: 'left' as const,\n },\n ],\n transformItem: (item: any) => {\n const toNumber = (value: unknown): number | null => {\n if (typeof value === 'number') return Number.isNaN(value) ? null : value\n if (typeof value === 'string' && value.trim().length) {\n const parsed = Number(value)\n return Number.isNaN(parsed) ? null : parsed\n }\n return null\n }\n const base = {\n id: item.id,\n [binding.numberField]: item[numberColumn] ?? null,\n status: item.status ?? null,\n statusEntryId: item.status_entry_id ?? null,\n customerEntityId: item.customer_entity_id ?? null,\n customerContactId: item.customer_contact_id ?? null,\n billingAddressId: item.billing_address_id ?? null,\n shippingAddressId: item.shipping_address_id ?? null,\n shippingMethodId: item.shipping_method_id ?? null,\n shippingMethodCode: item.shipping_method_code ?? null,\n shippingMethodSnapshot: normalizeJsonRecord(item.shipping_method_snapshot),\n paymentMethodId: item.payment_method_id ?? null,\n paymentMethodCode: item.payment_method_code ?? null,\n paymentMethodSnapshot: normalizeJsonRecord(item.payment_method_snapshot),\n currencyCode: item.currency_code ?? null,\n channelId: item.channel_id ?? null,\n externalReference: item.external_reference ?? null,\n customerReference: item.customer_reference ?? null,\n placedAt: item.placed_at ?? null,\n expectedDeliveryAt: item.expected_delivery_at ?? null,\n comment: item.comments ?? null,\n validFrom: item.valid_from ?? null,\n validUntil: item.valid_until ?? null,\n lineItemCount: toNumber(item.line_item_count),\n subtotalNetAmount: toNumber(item.subtotal_net_amount),\n subtotalGrossAmount: toNumber(item.subtotal_gross_amount),\n discountTotalAmount: toNumber(item.discount_total_amount),\n taxTotalAmount: toNumber(item.tax_total_amount),\n shippingNetAmount: toNumber(item.shipping_net_amount),\n shippingGrossAmount: toNumber(item.shipping_gross_amount),\n surchargeTotalAmount: toNumber(item.surcharge_total_amount),\n grandTotalNetAmount: toNumber(item.grand_total_net_amount),\n grandTotalGrossAmount: toNumber(item.grand_total_gross_amount),\n paidTotalAmount: toNumber(item.paid_total_amount),\n refundedTotalAmount: toNumber(item.refunded_total_amount),\n outstandingAmount: toNumber(item.outstanding_amount),\n ...(binding.kind === 'order' ? { totalsMode: item.totals_mode ?? 'computed' } : {}),\n customerSnapshot: normalizeJsonRecord(item.customer_snapshot),\n billingAddressSnapshot: normalizeJsonRecord(item.billing_address_snapshot),\n shippingAddressSnapshot: normalizeJsonRecord(item.shipping_address_snapshot),\n metadata: normalizeJsonRecord(item.metadata),\n organizationId: item.organization_id ?? null,\n tenantId: item.tenant_id ?? null,\n createdAt: item.created_at,\n updatedAt: item.updated_at,\n }\n const cfEntries = extractAllCustomFieldEntries(item as Record<string, unknown>)\n const normalized = { ...base }\n Object.keys(normalized).forEach((key) => {\n if (key.startsWith('cf:')) delete (normalized as any)[key]\n })\n return Object.keys(cfEntries).length ? { ...normalized, ...cfEntries } : normalized\n },\n },\n actions: {\n create: {\n commandId: binding.createCommandId,\n schema: rawBodySchema,\n mapInput: async ({ raw, ctx }: { raw: unknown; ctx: CrudCtx }) => {\n const { translate } = await resolveTranslations()\n const { base, custom } = splitCustomFieldPayload(raw ?? {})\n const parsed = parseScopedCommandInput(\n createSchema,\n Object.keys(custom).length ? { ...base, customFields: custom } : base,\n ctx,\n translate,\n )\n return parsed\n },\n response: ({ result }: { result?: DocumentCreateResult | null }) => ({\n id: result?.orderId ?? result?.quoteId ?? result?.id ?? null,\n ...(binding.kind === 'order' && Array.isArray(result?.warnings)\n ? { warnings: result.warnings }\n : {}),\n }),\n status: 201,\n },\n update: {\n commandId: binding.updateCommandId,\n schema: rawBodySchema,\n mapInput: async ({ raw, ctx }: { raw: unknown; ctx: CrudCtx }) => {\n const { translate } = await resolveTranslations()\n const { base, custom } = splitCustomFieldPayload(raw ?? {})\n const numberValue =\n binding.kind === 'order'\n ? (base as Record<string, unknown>).orderNumber\n : (base as Record<string, unknown>).quoteNumber\n if (typeof numberValue === 'string') {\n await ensureNumberEditPermission(ctx, translate)\n }\n const parsed = parseScopedCommandInput(\n documentUpdateSchema,\n Object.keys(custom).length ? { ...base, customFields: custom } : base,\n ctx,\n translate,\n )\n return parsed\n },\n response: ({ result }: { result: any }) =>\n mapUpdateResponse((result as any)?.order ?? (result as any)?.quote ?? result),\n },\n delete: {\n commandId: binding.deleteCommandId,\n schema: rawBodySchema,\n mapInput: async ({ parsed, ctx }: { parsed: any; ctx: CrudCtx }) => {\n const { translate } = await resolveTranslations()\n const id = resolveCrudRecordId(parsed, ctx, translate)\n return { id }\n },\n response: () => ({ ok: true }),\n },\n },\n hooks: {\n afterList: async (payload: any, ctx: CrudCtx) => {\n await attachTags(payload, { ...ctx, bindingKind: binding.kind })\n await attachChannelNames(payload, ctx)\n },\n },\n }\n}\n\nexport function buildDocumentOpenApi(binding: DocumentBinding) {\n const createSchema = binding.kind === 'order' ? orderCreateSchema : quoteCreateSchema\n const itemSchema = z.object({\n id: z.string().uuid(),\n [binding.numberField]: z.string().nullable(),\n status: z.string().nullable(),\n statusEntryId: z.string().uuid().nullable().optional(),\n customerEntityId: z.string().uuid().nullable(),\n customerContactId: z.string().uuid().nullable(),\n billingAddressId: z.string().uuid().nullable(),\n shippingAddressId: z.string().uuid().nullable(),\n customerReference: z.string().nullable().optional(),\n externalReference: z.string().nullable().optional(),\n comment: z.string().nullable().optional(),\n placedAt: z.string().nullable().optional(),\n expectedDeliveryAt: z.string().nullable().optional(),\n customerSnapshot: z.record(z.string(), z.unknown()).nullable().optional(),\n billingAddressSnapshot: z.record(z.string(), z.unknown()).nullable().optional(),\n shippingAddressSnapshot: z.record(z.string(), z.unknown()).nullable().optional(),\n shippingMethodId: z.string().uuid().nullable().optional(),\n shippingMethodCode: z.string().nullable().optional(),\n shippingMethodSnapshot: z.record(z.string(), z.unknown()).nullable().optional(),\n paymentMethodId: z.string().uuid().nullable().optional(),\n paymentMethodCode: z.string().nullable().optional(),\n paymentMethodSnapshot: z.record(z.string(), z.unknown()).nullable().optional(),\n currencyCode: z.string().nullable(),\n channelId: z.string().uuid().nullable(),\n channelName: z.string().nullable().optional(),\n channelCode: z.string().nullable().optional(),\n organizationId: z.string().uuid().nullable(),\n tenantId: z.string().uuid().nullable(),\n validFrom: z.string().nullable().optional(),\n validUntil: z.string().nullable().optional(),\n lineItemCount: z.number().nullable().optional(),\n subtotalNetAmount: z.number().nullable().optional(),\n subtotalGrossAmount: z.number().nullable().optional(),\n discountTotalAmount: z.number().nullable().optional(),\n taxTotalAmount: z.number().nullable().optional(),\n shippingNetAmount: z.number().nullable().optional(),\n shippingGrossAmount: z.number().nullable().optional(),\n surchargeTotalAmount: z.number().nullable().optional(),\n grandTotalNetAmount: z.number().nullable().optional(),\n grandTotalGrossAmount: z.number().nullable().optional(),\n paidTotalAmount: z.number().nullable().optional(),\n refundedTotalAmount: z.number().nullable().optional(),\n outstandingAmount: z.number().nullable().optional(),\n totalsMode: z.enum(['computed', 'external']).optional(),\n createdAt: z.string(),\n updatedAt: z.string(),\n customFields: z.record(z.string(), z.unknown()).optional(),\n customValues: z.record(z.string(), z.unknown()).optional(),\n })\n\n const listResponseSchema = createPagedListResponseSchema(itemSchema)\n\n return createSalesCrudOpenApi({\n resourceName: binding.kind === 'order' ? 'Order' : 'Quote',\n querySchema: listSchema,\n listResponseSchema,\n create: {\n schema: createSchema,\n responseSchema: binding.kind === 'order'\n ? z.object({\n id: z.string().uuid().nullable(),\n warnings: z.array(z.object({\n code: z.literal(ORDER_PAYMENT_LEDGER_WARNING_CODE),\n fields: z.array(z.enum(ORDER_PAYMENT_LEDGER_FIELDS)),\n })).optional(),\n })\n : z.object({ id: z.string().uuid().nullable() }),\n description: binding.kind === 'order'\n ? 'Creates a new sales order. paidTotalAmount, refundedTotalAmount, and outstandingAmount are deprecated compatibility inputs: supplied values are ignored and reported in warnings. Record payments through sales.payments.create or POST /api/sales/payments.'\n : 'Creates a new sales quote.',\n },\n del: {\n schema: defaultDeleteRequestSchema,\n responseSchema: z.object({ ok: z.boolean() }),\n description: `Deletes a sales ${binding.kind}.`,\n },\n })\n}\n\n// Compatibility wrapper\nexport function createDocumentCrudRoute(binding: DocumentBinding) {\n const crud = makeCrudRoute(buildDocumentCrudOptions(binding))\n const { GET, POST, PUT, DELETE } = crud\n return { GET, POST, PUT, DELETE, openApi: buildDocumentOpenApi(binding), metadata: crud.metadata }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { SalesOrderLine } from "../../data/entities.js";
|
|
2
|
-
import { orderLineCreateSchema } from "../../data/validators.js";
|
|
2
|
+
import { orderHeaderTotalsSchema, orderLineCreateSchema } from "../../data/validators.js";
|
|
3
3
|
import { E } from "../../../../generated/entities.ids.generated.js";
|
|
4
4
|
import * as F from "../../../../generated/entities/sales_order_line/index.js";
|
|
5
5
|
import { makeSalesLineRoute } from "../../lib/makeSalesLineRoute.js";
|
|
@@ -10,6 +10,7 @@ const route = makeSalesLineRoute({
|
|
|
10
10
|
parentFkColumn: "order_id",
|
|
11
11
|
parentFkParam: "orderId",
|
|
12
12
|
createSchema: orderLineCreateSchema,
|
|
13
|
+
writeExtensionShape: { orderTotals: orderHeaderTotalsSchema.optional() },
|
|
13
14
|
features: { view: "sales.orders.view", manage: "sales.orders.manage" },
|
|
14
15
|
commandPrefix: "sales.orders.lines",
|
|
15
16
|
openApi: {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/sales/api/order-lines/route.ts"],
|
|
4
|
-
"sourcesContent": ["import { SalesOrderLine } from \"../../data/entities\";\nimport { orderLineCreateSchema } from \"../../data/validators\";\nimport { E } from \"#generated/entities.ids.generated\";\nimport * as F from \"#generated/entities/sales_order_line\";\nimport { makeSalesLineRoute } from \"../../lib/makeSalesLineRoute\";\n\nconst route = makeSalesLineRoute({\n entity: SalesOrderLine,\n entityId: E.sales.sales_order_line,\n fieldConstants: F,\n parentFkColumn: \"order_id\",\n parentFkParam: \"orderId\",\n createSchema: orderLineCreateSchema,\n features: { view: \"sales.orders.view\", manage: \"sales.orders.manage\" },\n commandPrefix: \"sales.orders.lines\",\n openApi: {\n resourceName: \"Order line\",\n description: \"an order line and recalculates totals\",\n },\n});\n\nexport const metadata = route.metadata;\nexport const { GET, POST, PUT, DELETE } = route;\nexport const openApi = route.openApi;\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,sBAAsB;AAC/B,SAAS,6BAA6B;
|
|
4
|
+
"sourcesContent": ["import { SalesOrderLine } from \"../../data/entities\";\nimport { orderHeaderTotalsSchema, orderLineCreateSchema } from \"../../data/validators\";\nimport { E } from \"#generated/entities.ids.generated\";\nimport * as F from \"#generated/entities/sales_order_line\";\nimport { makeSalesLineRoute } from \"../../lib/makeSalesLineRoute\";\n\nconst route = makeSalesLineRoute({\n entity: SalesOrderLine,\n entityId: E.sales.sales_order_line,\n fieldConstants: F,\n parentFkColumn: \"order_id\",\n parentFkParam: \"orderId\",\n createSchema: orderLineCreateSchema,\n writeExtensionShape: { orderTotals: orderHeaderTotalsSchema.optional() },\n features: { view: \"sales.orders.view\", manage: \"sales.orders.manage\" },\n commandPrefix: \"sales.orders.lines\",\n openApi: {\n resourceName: \"Order line\",\n description: \"an order line and recalculates totals\",\n },\n});\n\nexport const metadata = route.metadata;\nexport const { GET, POST, PUT, DELETE } = route;\nexport const openApi = route.openApi;\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,sBAAsB;AAC/B,SAAS,yBAAyB,6BAA6B;AAC/D,SAAS,SAAS;AAClB,YAAY,OAAO;AACnB,SAAS,0BAA0B;AAEnC,MAAM,QAAQ,mBAAmB;AAAA,EAC/B,QAAQ;AAAA,EACR,UAAU,EAAE,MAAM;AAAA,EAClB,gBAAgB;AAAA,EAChB,gBAAgB;AAAA,EAChB,eAAe;AAAA,EACf,cAAc;AAAA,EACd,qBAAqB,EAAE,aAAa,wBAAwB,SAAS,EAAE;AAAA,EACvE,UAAU,EAAE,MAAM,qBAAqB,QAAQ,sBAAsB;AAAA,EACrE,eAAe;AAAA,EACf,SAAS;AAAA,IACP,cAAc;AAAA,IACd,aAAa;AAAA,EACf;AACF,CAAC;AAEM,MAAM,WAAW,MAAM;AACvB,MAAM,EAAE,KAAK,MAAM,KAAK,OAAO,IAAI;AACnC,MAAM,UAAU,MAAM;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|