feat(payments): track household payment confirmations

This commit is contained in:
2026-03-10 17:00:45 +04:00
parent fb85219409
commit 1988521931
31 changed files with 4795 additions and 19 deletions

View File

@@ -13,6 +13,7 @@
"0009_quiet_wallflower.sql": "c5bcb6a01b6f22a9e64866ac0d11468105aad8b2afb248296370f15b462e3087",
"0010_wild_molecule_man.sql": "46027a6ac770cdc2efd4c3eb5bb53f09d1b852c70fdc46a2434e5a7064587245",
"0011_previous_ezekiel_stane.sql": "d996e64d3854de22e36dedeaa94e46774399163d90263bbb05e0b9199af79b70",
"0012_clumsy_maestro.sql": "173797fb435c6acd7c268c624942d6f19a887c329bcef409a3dde1249baaeb8a"
"0012_clumsy_maestro.sql": "173797fb435c6acd7c268c624942d6f19a887c329bcef409a3dde1249baaeb8a",
"0013_wild_avengers.sql": "76254db09c9d623134712aee57a5896aa4a5b416e45d0f6c69dec1fec5b32af4"
}
}

View File

@@ -0,0 +1,51 @@
CREATE TABLE "payment_confirmations" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"household_id" uuid NOT NULL,
"cycle_id" uuid,
"member_id" uuid,
"sender_telegram_user_id" text NOT NULL,
"raw_text" text NOT NULL,
"normalized_text" text NOT NULL,
"detected_kind" text,
"explicit_amount_minor" bigint,
"explicit_currency" text,
"resolved_amount_minor" bigint,
"resolved_currency" text,
"status" text NOT NULL,
"review_reason" text,
"attachment_count" integer DEFAULT 0 NOT NULL,
"telegram_chat_id" text NOT NULL,
"telegram_message_id" text NOT NULL,
"telegram_thread_id" text NOT NULL,
"telegram_update_id" text NOT NULL,
"message_sent_at" timestamp with time zone,
"created_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "payment_records" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"household_id" uuid NOT NULL,
"cycle_id" uuid NOT NULL,
"member_id" uuid NOT NULL,
"kind" text NOT NULL,
"amount_minor" bigint NOT NULL,
"currency" text NOT NULL,
"confirmation_id" uuid,
"recorded_at" timestamp with time zone NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
ALTER TABLE "payment_confirmations" ADD CONSTRAINT "payment_confirmations_household_id_households_id_fk" FOREIGN KEY ("household_id") REFERENCES "public"."households"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "payment_confirmations" ADD CONSTRAINT "payment_confirmations_cycle_id_billing_cycles_id_fk" FOREIGN KEY ("cycle_id") REFERENCES "public"."billing_cycles"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "payment_confirmations" ADD CONSTRAINT "payment_confirmations_member_id_members_id_fk" FOREIGN KEY ("member_id") REFERENCES "public"."members"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "payment_records" ADD CONSTRAINT "payment_records_household_id_households_id_fk" FOREIGN KEY ("household_id") REFERENCES "public"."households"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "payment_records" ADD CONSTRAINT "payment_records_cycle_id_billing_cycles_id_fk" FOREIGN KEY ("cycle_id") REFERENCES "public"."billing_cycles"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "payment_records" ADD CONSTRAINT "payment_records_member_id_members_id_fk" FOREIGN KEY ("member_id") REFERENCES "public"."members"("id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "payment_records" ADD CONSTRAINT "payment_records_confirmation_id_payment_confirmations_id_fk" FOREIGN KEY ("confirmation_id") REFERENCES "public"."payment_confirmations"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
CREATE UNIQUE INDEX "payment_confirmations_household_tg_message_unique" ON "payment_confirmations" USING btree ("household_id","telegram_chat_id","telegram_message_id");--> statement-breakpoint
CREATE UNIQUE INDEX "payment_confirmations_household_tg_update_unique" ON "payment_confirmations" USING btree ("household_id","telegram_update_id");--> statement-breakpoint
CREATE INDEX "payment_confirmations_household_status_idx" ON "payment_confirmations" USING btree ("household_id","status");--> statement-breakpoint
CREATE INDEX "payment_confirmations_member_created_idx" ON "payment_confirmations" USING btree ("member_id","created_at");--> statement-breakpoint
CREATE INDEX "payment_records_cycle_member_idx" ON "payment_records" USING btree ("cycle_id","member_id");--> statement-breakpoint
CREATE INDEX "payment_records_cycle_kind_idx" ON "payment_records" USING btree ("cycle_id","kind");--> statement-breakpoint
CREATE UNIQUE INDEX "payment_records_confirmation_unique" ON "payment_records" USING btree ("confirmation_id");

File diff suppressed because it is too large Load Diff

View File

@@ -92,6 +92,13 @@
"when": 1773146577992,
"tag": "0012_clumsy_maestro",
"breakpoints": true
},
{
"idx": 13,
"version": "7",
"when": 1773147481265,
"tag": "0013_wild_avengers",
"breakpoints": true
}
]
}

View File

@@ -481,6 +481,83 @@ export const anonymousMessages = pgTable(
})
)
export const paymentConfirmations = pgTable(
'payment_confirmations',
{
id: uuid('id').defaultRandom().primaryKey(),
householdId: uuid('household_id')
.notNull()
.references(() => households.id, { onDelete: 'cascade' }),
cycleId: uuid('cycle_id').references(() => billingCycles.id, { onDelete: 'set null' }),
memberId: uuid('member_id').references(() => members.id, { onDelete: 'set null' }),
senderTelegramUserId: text('sender_telegram_user_id').notNull(),
rawText: text('raw_text').notNull(),
normalizedText: text('normalized_text').notNull(),
detectedKind: text('detected_kind'),
explicitAmountMinor: bigint('explicit_amount_minor', { mode: 'bigint' }),
explicitCurrency: text('explicit_currency'),
resolvedAmountMinor: bigint('resolved_amount_minor', { mode: 'bigint' }),
resolvedCurrency: text('resolved_currency'),
status: text('status').notNull(),
reviewReason: text('review_reason'),
attachmentCount: integer('attachment_count').default(0).notNull(),
telegramChatId: text('telegram_chat_id').notNull(),
telegramMessageId: text('telegram_message_id').notNull(),
telegramThreadId: text('telegram_thread_id').notNull(),
telegramUpdateId: text('telegram_update_id').notNull(),
messageSentAt: timestamp('message_sent_at', { withTimezone: true }),
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull()
},
(table) => ({
householdMessageUnique: uniqueIndex('payment_confirmations_household_tg_message_unique').on(
table.householdId,
table.telegramChatId,
table.telegramMessageId
),
householdUpdateUnique: uniqueIndex('payment_confirmations_household_tg_update_unique').on(
table.householdId,
table.telegramUpdateId
),
householdStatusIdx: index('payment_confirmations_household_status_idx').on(
table.householdId,
table.status
),
memberCreatedIdx: index('payment_confirmations_member_created_idx').on(
table.memberId,
table.createdAt
)
})
)
export const paymentRecords = pgTable(
'payment_records',
{
id: uuid('id').defaultRandom().primaryKey(),
householdId: uuid('household_id')
.notNull()
.references(() => households.id, { onDelete: 'cascade' }),
cycleId: uuid('cycle_id')
.notNull()
.references(() => billingCycles.id, { onDelete: 'cascade' }),
memberId: uuid('member_id')
.notNull()
.references(() => members.id, { onDelete: 'restrict' }),
kind: text('kind').notNull(),
amountMinor: bigint('amount_minor', { mode: 'bigint' }).notNull(),
currency: text('currency').notNull(),
confirmationId: uuid('confirmation_id').references(() => paymentConfirmations.id, {
onDelete: 'set null'
}),
recordedAt: timestamp('recorded_at', { withTimezone: true }).notNull(),
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull()
},
(table) => ({
cycleMemberIdx: index('payment_records_cycle_member_idx').on(table.cycleId, table.memberId),
cycleKindIdx: index('payment_records_cycle_kind_idx').on(table.cycleId, table.kind),
confirmationUnique: uniqueIndex('payment_records_confirmation_unique').on(table.confirmationId)
})
)
export const settlements = pgTable(
'settlements',
{
@@ -548,4 +625,6 @@ export type UtilityBill = typeof utilityBills.$inferSelect
export type PurchaseEntry = typeof purchaseEntries.$inferSelect
export type PurchaseMessage = typeof purchaseMessages.$inferSelect
export type AnonymousMessage = typeof anonymousMessages.$inferSelect
export type PaymentConfirmation = typeof paymentConfirmations.$inferSelect
export type PaymentRecord = typeof paymentRecords.$inferSelect
export type Settlement = typeof settlements.$inferSelect