Gift-card rolling expiry, SvelteDate→Date purge, strict DST tests, UTC scan-location + settings legal floor
Gift-card rolling expiry (setting-driven, was dead config): - GetGiftCardExpiryMonths(): single source of truth (business_settings gift_card_expiry_months, fallback 24) shared by payment handlers and the CleanupExpiredGiftCards job (was hardcoded 24). - expiry_date now maintained on ALL 9 gift-card write sites (buy, topup, transfer, redeem, terminal payment, refund credit, till) so the refund-time guard at refunds.go actually fires. Schema default 12->24 + migration note; test-DB seed aligned. Stale "expiry_date IS NULL" test rewritten; new expired-card-rejected regression test. Frontend SvelteDate purge (docs' stated convention, wide): - All 180+ raw `new SvelteDate(...)` uses across routes/components replaced with parseWallClockDate (backend UTC ISO) or new Date (wall-clock constructors). SvelteDate imports removed. timeSlots.ts getDayWithOrdinal fixed. Zero SvelteDate references remain; svelte-check clean. Strict timezone/DST testing + QA fixes: - 8 new hermetic boundary tests: clock.DST transitions (both 2026 folds), closing-hours GMT vs BST, booking date-window midnight, refund-tier elapsed-time independence, deposit-window UTC-instant, scheduling LondonDateString midnight, today AT TIME ZONE window + UTC round-trip. - today.go summary date labels fixed to London wall-clock (were showing the previous UTC day during BST) + regression test. - pgx ScanLocation fixed to UTC via AfterConnect (was host-local -> JSON offsets depended on deployment TZ, contradicting the documented UTC invariant) + regression test. Registered as a new *Type to avoid a data race on the shared type map (caught by -race). Admin Business Settings (setting now functional => legal floor): - gift_card_expiry_months validation floor raised 1 -> 12 months (CMA/ Consumer Rights Act 2015 unfair-contract-term guidance) in endpoint + UI, with rolling-expiry semantics shown in both display and edit form. - 3 new expiry validation tests; 2 pre-existing message assertions updated. Full suite 25/25 + race clean via run-tests.sh lockfile; svelte-check 0 errors/warnings; production build succeeds.
This commit is contained in:
@@ -27,7 +27,6 @@
|
||||
import { toast } from 'svelte-sonner';
|
||||
import { extractErrorMessage } from '$lib/utils/toast-safe';
|
||||
import { onDestroy } from 'svelte';
|
||||
import { SvelteDate } from 'svelte/reactivity';
|
||||
|
||||
// Components
|
||||
import BookingActions from '$lib/components/booking/BookingActions.svelte';
|
||||
@@ -41,7 +40,7 @@
|
||||
import { POLICY } from '$lib/constants/policy';
|
||||
import UserPaymentModal from '$lib/components/payments/UserPaymentModal.svelte';
|
||||
import { extractBookedSlots, getLunchProtectionForSlots } from '$lib/lunchProtection';
|
||||
import { formatLocalDateTime, getLondonTodayCalendarDate } from '$lib/utils/timeSlots';
|
||||
import { formatLocalDateTime, getLondonTodayCalendarDate, parseWallClockDate } from '$lib/utils/timeSlots';
|
||||
import { generateUUID } from '$lib/utils/uuid';
|
||||
|
||||
import type {
|
||||
@@ -575,7 +574,7 @@
|
||||
return;
|
||||
}
|
||||
|
||||
const now = new SvelteDate();
|
||||
const now = new Date();
|
||||
const diff = reservationExpiresAt.getTime() - now.getTime();
|
||||
|
||||
if (diff <= 0) {
|
||||
@@ -685,7 +684,7 @@
|
||||
// Initialize date boundaries
|
||||
const today = getLondonTodayCalendarDate();
|
||||
const minDate = today;
|
||||
const maxDate = new SvelteDate(today.year, today.month - 1, today.day);
|
||||
const maxDate = new Date(today.year, today.month - 1, today.day);
|
||||
maxDate.setMonth(today.month - 1 + 6);
|
||||
const maxCalendarDate = new CalendarDate(
|
||||
maxDate.getFullYear(),
|
||||
@@ -756,8 +755,8 @@
|
||||
) {
|
||||
bookingFlowAutoSelectDone = true;
|
||||
|
||||
const currentDate = new SvelteDate(getLondonTodayCalendarDate().toString() + 'T00:00:00');
|
||||
const maxDateJs = new SvelteDate(
|
||||
const currentDate = new Date(getLondonTodayCalendarDate().toString() + 'T00:00:00');
|
||||
const maxDateJs = new Date(
|
||||
maxCalendarDate.year,
|
||||
maxCalendarDate.month - 1,
|
||||
maxCalendarDate.day
|
||||
@@ -769,7 +768,7 @@
|
||||
const daysToCheck = Math.min(daysDifference, 180);
|
||||
|
||||
for (let i = 1; i <= daysToCheck; i++) {
|
||||
const nextDate = new SvelteDate(currentDate);
|
||||
const nextDate = new Date(currentDate);
|
||||
nextDate.setDate(currentDate.getDate() + i);
|
||||
const dateStr = nextDate.toLocaleDateString('en-CA', { timeZone: 'Europe/London' });
|
||||
|
||||
@@ -992,7 +991,7 @@
|
||||
// =============== Time Slot Generation ===============
|
||||
function calculateEndTime(startTime: string, durationMinutes: number): string {
|
||||
const [hours, minutes] = startTime.split(':').map(Number);
|
||||
const date = new SvelteDate();
|
||||
const date = new Date();
|
||||
date.setHours(hours, minutes, 0, 0);
|
||||
date.setMinutes(date.getMinutes() + durationMinutes);
|
||||
const endHours = date.getHours().toString().padStart(2, '0');
|
||||
@@ -1253,7 +1252,7 @@
|
||||
15,
|
||||
false
|
||||
);
|
||||
const now = new SvelteDate();
|
||||
const now = new Date();
|
||||
const validSlots = availableSlots.filter((t) => {
|
||||
if (lunchProtection.get(t)?.isBlocked) return false;
|
||||
if (userDepositsRequired > 0) {
|
||||
@@ -1410,7 +1409,7 @@
|
||||
}
|
||||
|
||||
function getDayWithOrdinal(date: CalendarDate): string {
|
||||
const monthName = new SvelteDate(date.year, date.month - 1, date.day).toLocaleDateString(
|
||||
const monthName = new Date(date.year, date.month - 1, date.day).toLocaleDateString(
|
||||
'en-GB',
|
||||
{
|
||||
month: 'long'
|
||||
@@ -2131,7 +2130,7 @@
|
||||
{#if currentStep === finalStep}
|
||||
{#if confirmedBooking}
|
||||
{@const isRequested = confirmedBooking.notes && confirmedBooking.notes.length > 0}
|
||||
{@const bookingDate = new SvelteDate(confirmedBooking.start_time)}
|
||||
{@const bookingDate = parseWallClockDate(confirmedBooking.start_time)}
|
||||
{@const dateStr = bookingDate.toLocaleDateString('en-GB', {
|
||||
weekday: 'long',
|
||||
day: 'numeric',
|
||||
@@ -2446,8 +2445,8 @@
|
||||
deposit_paid: bk.deposit_paid,
|
||||
payments: bk.payments,
|
||||
duration_minutes: bk.duration_minutes,
|
||||
created_at: new SvelteDate().toISOString(),
|
||||
updated_at: new SvelteDate().toISOString()
|
||||
created_at: new Date().toISOString(),
|
||||
updated_at: new Date().toISOString()
|
||||
}}
|
||||
onClose={() => (showPayEarlyModal = false)}
|
||||
onComplete={() => {
|
||||
|
||||
Reference in New Issue
Block a user