import 'dotenv/config' import { getPayload } from 'payload' import config from '../payload.config' const offers = [ { slug: 'checkup-health', sortOrder: 10, price: 12900, oldPrice: 18900, endsAt: '2026-10-12T20:59:00.000Z', imageUrl: '/promotions/checkup.jpg', bookingHref: '/booking', ru: { title: 'Чек-ап «Здоровье»', badge: '−32%', description: 'Комплексное обследование за один визит: приём терапевта, лабораторный минимум, ЭКГ и заключение с рекомендациями.', conditions: 'Для новых пациентов КДЦ МОНИКИ. Пакет нельзя разбить на отдельные услуги. Запись только онлайн до окончания акции. Действует в КДЦ на ул. Щепкина, 61/2.', ctaLabel: 'Записаться на чек-ап', }, en: { title: 'Health check-up', badge: '−32%', description: 'A one-visit screening: internist consult, basic lab tests, ECG, and a written care plan.', conditions: 'For new CDC MONIKI patients. The package cannot be split. Online booking only until the offer ends. Valid at 61/2 Shchepkina St.', ctaLabel: 'Book the check-up', }, }, { slug: 'mri-brain', sortOrder: 20, price: 7500, oldPrice: 9900, endsAt: '2026-10-20T20:59:00.000Z', imageUrl: '/promotions/mri.jpg', bookingHref: '/booking', ru: { title: 'МРТ головного мозга', badge: 'Скидка', description: 'Исследование на экспертном томографе со скидкой при записи через сайт. Результат и описание врача-рентгенолога.', conditions: 'Без контрастного усиления. Будние дни, 08:00–16:00. Нужно направление или заполненная анкета противопоказаний. Не суммируется с другими скидками.', ctaLabel: 'Записаться на МРТ', }, en: { title: 'Brain MRI', badge: 'Sale', description: 'Expert MRI at a reduced price when booked online, with a radiologist report.', conditions: 'Without contrast. Weekdays 08:00–16:00. A referral or contraindications form is required. Cannot be combined with other discounts.', ctaLabel: 'Book an MRI', }, }, { slug: 'dental-hygiene', sortOrder: 30, price: 3900, oldPrice: 5500, endsAt: '2026-11-01T20:59:00.000Z', imageUrl: '/promotions/dental.jpg', bookingHref: '/booking', ru: { title: 'Профессиональная гигиена полости рта', badge: '−29%', description: 'Снятие налёта, полировка и рекомендации стоматолога. Подходит как самостоятельная процедура и как подготовка к лечению.', conditions: 'Один пациент — одна процедура по акции. Первичный приём в стоматологии КДЦ. Не распространяется на отбеливание и лечение кариеса.', ctaLabel: 'Записаться к стоматологу', }, en: { title: 'Professional dental cleaning', badge: '−29%', description: 'Plaque removal, polishing, and dentist advice — as a standalone visit or before treatment.', conditions: 'One promotional procedure per patient. First visit at the CDC dental unit. Whitening and caries treatment are not included.', ctaLabel: 'Book a dentist', }, }, { slug: 'cardio-package', sortOrder: 40, price: 6400, oldPrice: 8800, endsAt: '2026-11-15T20:59:00.000Z', imageUrl: '/promotions/cardio.jpg', bookingHref: '/booking', ru: { title: 'Кардиолог + ЭКГ + холтер', badge: 'Пакет', description: 'Консультация кардиолога, электрокардиограмма и суточное мониторирование в одном пакете — со скидкой при записи онлайн.', conditions: 'Пакет оказывается целиком, услуги по отдельности по акции не продаются. Холтер выдаётся в день приёма. Действует в КДЦ МОНИКИ (Москва).', ctaLabel: 'Записаться к кардиологу', }, en: { title: 'Cardiologist + ECG + Holter', badge: 'Bundle', description: 'Cardiology consult, ECG, and 24-hour Holter monitoring as one discounted online package.', conditions: 'The bundle cannot be split. The Holter device is issued on the visit day. Valid at MONIKI CDC in Moscow.', ctaLabel: 'Book a cardiologist', }, }, ] as const export async function ensurePromotionsSchema(payload: Awaited>) { const adapter = payload.db as { drizzle: { execute: (query: unknown) => Promise } } const { sql } = await import('@payloadcms/db-postgres') await adapter.drizzle.execute(sql` CREATE TABLE IF NOT EXISTS promotions ( id serial PRIMARY KEY, slug character varying NOT NULL, price numeric NOT NULL, old_price numeric, starts_at timestamp(3) with time zone, ends_at timestamp(3) with time zone NOT NULL, show_countdown boolean DEFAULT true, hide_when_expired boolean DEFAULT true, booking_enabled boolean DEFAULT true, booking_href character varying DEFAULT '/booking', service_id integer, image_id integer, image_url character varying, sort_order numeric DEFAULT 0, is_published boolean DEFAULT true, updated_at timestamp(3) with time zone NOT NULL DEFAULT now(), created_at timestamp(3) with time zone NOT NULL DEFAULT now() ) `) await adapter.drizzle.execute(sql` CREATE TABLE IF NOT EXISTS promotions_locales ( id serial PRIMARY KEY, title character varying NOT NULL, badge character varying, description character varying, conditions character varying, cta_label character varying, _locale _locales NOT NULL, _parent_id integer NOT NULL ) `) await adapter.drizzle.execute(sql`CREATE UNIQUE INDEX IF NOT EXISTS promotions_slug_idx ON promotions (slug)`) await adapter.drizzle.execute(sql`CREATE INDEX IF NOT EXISTS promotions_created_at_idx ON promotions (created_at)`) await adapter.drizzle.execute(sql`CREATE INDEX IF NOT EXISTS promotions_updated_at_idx ON promotions (updated_at)`) await adapter.drizzle.execute(sql`CREATE INDEX IF NOT EXISTS promotions_image_idx ON promotions (image_id)`) await adapter.drizzle.execute(sql`CREATE INDEX IF NOT EXISTS promotions_service_idx ON promotions (service_id)`) await adapter.drizzle.execute(sql` CREATE UNIQUE INDEX IF NOT EXISTS promotions_locales_locale_parent_id_unique ON promotions_locales (_locale, _parent_id) `) await adapter.drizzle.execute(sql` DO $$ BEGIN ALTER TABLE promotions ADD CONSTRAINT promotions_image_id_media_id_fk FOREIGN KEY (image_id) REFERENCES media(id) ON DELETE SET NULL; EXCEPTION WHEN duplicate_object THEN NULL; END $$ `) await adapter.drizzle.execute(sql` DO $$ BEGIN ALTER TABLE promotions ADD CONSTRAINT promotions_service_id_services_id_fk FOREIGN KEY (service_id) REFERENCES services(id) ON DELETE SET NULL; EXCEPTION WHEN duplicate_object THEN NULL; END $$ `) await adapter.drizzle.execute(sql` DO $$ BEGIN ALTER TABLE promotions_locales ADD CONSTRAINT promotions_locales_parent_id_fk FOREIGN KEY (_parent_id) REFERENCES promotions(id) ON DELETE CASCADE; EXCEPTION WHEN duplicate_object THEN NULL; END $$ `) await adapter.drizzle.execute(sql` ALTER TABLE payload_locked_documents_rels ADD COLUMN IF NOT EXISTS promotions_id integer `) await adapter.drizzle.execute(sql` CREATE INDEX IF NOT EXISTS payload_locked_documents_rels_promotions_id_idx ON payload_locked_documents_rels (promotions_id) `) await adapter.drizzle.execute(sql` DO $$ BEGIN ALTER TABLE payload_locked_documents_rels ADD CONSTRAINT payload_locked_documents_rels_promotions_fk FOREIGN KEY (promotions_id) REFERENCES promotions(id) ON DELETE CASCADE; EXCEPTION WHEN duplicate_object THEN NULL; END $$ `) } export async function seedPromotions(payload: Awaited>) { await ensurePromotionsSchema(payload) for (const offer of offers) { const existing = await payload.find({ collection: 'promotions', where: { slug: { equals: offer.slug } }, limit: 1, depth: 0, }) const data = { slug: offer.slug, sortOrder: offer.sortOrder, price: offer.price, oldPrice: offer.oldPrice, endsAt: offer.endsAt, imageUrl: offer.imageUrl, bookingHref: offer.bookingHref, bookingEnabled: true, showCountdown: true, hideWhenExpired: true, isPublished: true, ...offer.ru, } let id = existing.docs[0]?.id if (id) { await payload.update({ collection: 'promotions', id, locale: 'ru', data, }) console.log('updated', offer.slug) } else { const created = await payload.create({ collection: 'promotions', locale: 'ru', data, }) id = created.id console.log('created', offer.slug) } await payload.update({ collection: 'promotions', id, locale: 'en', data: offer.en, }) } console.log('promotions imported', offers.length) } async function main() { const payload = await getPayload({ config }) await seedPromotions(payload) process.exit(0) } if (process.argv[1]?.includes('import-promotions')) { main().catch((err) => { console.error(err) process.exit(1) }) }