Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 18 additions & 3 deletions src/common/entities/payment.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,20 @@ import { User } from './user.entity';
import { AssetType, PaymentStatus } from '../enums';

/**
* A single payout leg from an escrow release — one per recipient (team splits
* A single payout leg from an escrow release -- one per recipient (team splits
* produce many).
*
* `IDX_payment_escrow` serves `WHERE escrowId = :escrowId`, the lookup
* `EscrowService.releasePartial` runs on every call to compute the
* cumulative released-so-far balance before allowing a further partial
* payout — a hot path on every milestone-driven incremental release
* payout -- a hot path on every milestone-driven incremental release
* (#307). The same gap class as `IDX_escrow_sponsor_status` (#97) and the
* `Bounty.claimedById` index (#148).
*
* `issueId` (nullable) is set by `MaintenancePoolService.assignReward` so
* that the double-payout guard (#458) can be keyed on (pool, issue) rather
* than (pool, recipient) -- preventing the same issue being rewarded twice
* regardless of who the recipient is.
*/
@Entity('payments')
@Index('IDX_payment_escrow', ['escrowId'])
Expand All @@ -31,7 +36,7 @@ export class Payment {

// RESTRICT, not CASCADE: a Payment is a record of money that actually
// moved. Deleting its parent Escrow must never silently delete that
// payout record too — the database refuses the delete instead. See #27.
// payout record too -- the database refuses the delete instead. See #27.
@ManyToOne(() => Escrow, (escrow) => escrow.payments, {
onDelete: 'RESTRICT',
})
Expand Down Expand Up @@ -67,6 +72,16 @@ export class Payment {
@Column({ type: 'varchar', nullable: true })
txHash: string | null;

/**
* Issue ID that triggered this maintenance-pool reward. Null for bounty/
* milestone payments. Set by MaintenancePoolService.assignReward() so the
* double-payout guard can query (escrow.maintenancePoolId, issueId) instead
* of (escrow.maintenancePoolId, recipientId), closing the gap reported in
* #458 where the same issue could be paid twice to different recipients.
*/
@Column({ type: 'varchar', nullable: true })
issueId: string | null;

@CreateDateColumn()
createdAt: Date;

Expand Down
Loading