ad2app-lib 1.49.0 → 1.59.0
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/dist/analytics/index.d.ts +85 -2
- package/dist/analytics/index.js +26 -0
- package/dist/types/I_AccessDenial.d.ts +7 -0
- package/dist/types/I_AccessDenial.js +7 -0
- package/dist/types/agency-campaigns.d.ts +108 -5
- package/dist/types/agency-campaigns.js +111 -5
- package/dist/types/agency-pots.d.ts +518 -0
- package/dist/types/agency-pots.js +393 -0
- package/dist/types/agency-threads.d.ts +22 -1
- package/dist/types/agency.d.ts +93 -6
- package/dist/types/agency.js +70 -3
- package/dist/types/dealroom.d.ts +651 -0
- package/dist/types/dealroom.js +387 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.js +3 -0
- package/package.json +1 -1
- package/src/analytics/index.test.ts +23 -1
- package/src/analytics/index.ts +69 -2
- package/src/types/I_AccessDenial.ts +7 -0
- package/src/types/access-denial.test.ts +2 -1
- package/src/types/agency-campaigns.ts +187 -5
- package/src/types/agency-pots.test.ts +255 -0
- package/src/types/agency-pots.ts +751 -0
- package/src/types/agency-threads.ts +14 -3
- package/src/types/agency.ts +120 -7
- package/src/types/consumer-compat.test.ts +90 -0
- package/src/types/dealroom-audit1.test.ts +56 -0
- package/src/types/dealroom-wave2.test.ts +61 -0
- package/src/types/dealroom-wave3.test.ts +68 -0
- package/src/types/dealroom-wave4.test.ts +103 -0
- package/src/types/dealroom.test.ts +231 -0
- package/src/types/dealroom.ts +868 -0
- package/src/types/index.ts +4 -0
|
@@ -0,0 +1,868 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dealroom (spec 148): only what spec 175 has no concept of.
|
|
3
|
+
*
|
|
4
|
+
* The organization, membership, grant, campaign, offer loop, materials,
|
|
5
|
+
* threads and pots are 175's (`agency*.ts`); 148 adds unions there and keeps
|
|
6
|
+
* this file for the open listing, matching, the coarse creator row, its codes,
|
|
7
|
+
* the dormant money layer and the commission lane.
|
|
8
|
+
*
|
|
9
|
+
* The money types below are DORMANT: the backend refuses every money act with
|
|
10
|
+
* `dealroom-money-disabled` until the money release (FR-027). Their state
|
|
11
|
+
* lists mirror data-model.md's state machine exactly.
|
|
12
|
+
*
|
|
13
|
+
* See specs/148-dealroom/contracts/lib-types.md.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { ArrayMaxSize, ArrayNotEmpty, IsArray, IsBoolean, IsDateString, IsIn, IsInt, IsObject, IsOptional, IsString, Matches, Max, MaxLength, Min } from 'class-validator';
|
|
17
|
+
|
|
18
|
+
import { AGENCY_GRANT_SCOPES, type AgencyOrganizationType, type I_AgencyGrantScope } from './agency';
|
|
19
|
+
import type {
|
|
20
|
+
AgencyCampaignKind,
|
|
21
|
+
AgencyMilestoneKind,
|
|
22
|
+
CampaignPlatform,
|
|
23
|
+
I_CampaignDeliverable,
|
|
24
|
+
I_CreatorCampaignView,
|
|
25
|
+
I_Money,
|
|
26
|
+
SettlementForm,
|
|
27
|
+
} from './agency-campaigns';
|
|
28
|
+
|
|
29
|
+
// ── Open listing ─────────────────────────────────────────────────────────────
|
|
30
|
+
|
|
31
|
+
/** `private` is every 175 campaign today (members only); `open` is listed in the feed to matching creators. */
|
|
32
|
+
export const CAMPAIGN_VISIBILITIES = ['private', 'open'] as const;
|
|
33
|
+
export type CampaignVisibility = (typeof CAMPAIGN_VISIBILITIES)[number];
|
|
34
|
+
|
|
35
|
+
export const isCampaignVisibility = (value: string): value is CampaignVisibility =>
|
|
36
|
+
(CAMPAIGN_VISIBILITIES as readonly string[]).includes(value);
|
|
37
|
+
|
|
38
|
+
/** A follower band on one platform; no `max` means no upper bound. */
|
|
39
|
+
export interface I_FollowerBand {
|
|
40
|
+
min: number;
|
|
41
|
+
max?: number;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Who a listing is for. A BLANK constraint means "no filter" — which is a
|
|
46
|
+
* listing's meaning only. A creator's missing country or niches never reads
|
|
47
|
+
* that way: it is an incomplete profile (FR-026).
|
|
48
|
+
*/
|
|
49
|
+
export interface I_TargetProfile {
|
|
50
|
+
platforms: CampaignPlatform[];
|
|
51
|
+
followerBand: Partial<Record<CampaignPlatform, I_FollowerBand>>;
|
|
52
|
+
niches: string[];
|
|
53
|
+
/** ISO-3166 alpha-2. */
|
|
54
|
+
country?: string;
|
|
55
|
+
language?: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// ── Matching ─────────────────────────────────────────────────────────────────
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* The verifiable parts of "why you see this", built from the same row the
|
|
62
|
+
* match query returned — never composed separately. The screen writes the
|
|
63
|
+
* sentence in the reader's language.
|
|
64
|
+
*/
|
|
65
|
+
export interface I_MatchExplanation {
|
|
66
|
+
platform: CampaignPlatform;
|
|
67
|
+
followers: number;
|
|
68
|
+
/** The niches the creator and the listing share. */
|
|
69
|
+
niches: string[];
|
|
70
|
+
country?: string;
|
|
71
|
+
language?: string;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** The one dimension a near match misses (US11): followers within ±20%, or one platform not connected. */
|
|
75
|
+
export type I_NearMatchGap =
|
|
76
|
+
| {
|
|
77
|
+
kind: 'followers';
|
|
78
|
+
/**
|
|
79
|
+
* The platform whose followers miss the band, so the sentence names it on
|
|
80
|
+
* a multi-platform listing. Always sent by the current backend;
|
|
81
|
+
* optional only so older fixtures still type.
|
|
82
|
+
*/
|
|
83
|
+
platform?: CampaignPlatform;
|
|
84
|
+
followers: number;
|
|
85
|
+
band: I_FollowerBand;
|
|
86
|
+
percent: number;
|
|
87
|
+
direction: 'under' | 'over';
|
|
88
|
+
}
|
|
89
|
+
| { kind: 'missing_platform'; platform: CampaignPlatform };
|
|
90
|
+
|
|
91
|
+
export interface I_DealroomListingOrganization {
|
|
92
|
+
id: string;
|
|
93
|
+
name: string;
|
|
94
|
+
logoUrl: string | null;
|
|
95
|
+
type: AgencyOrganizationType;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** One open listing in a creator's feed: coarse campaign data, never another creator. */
|
|
99
|
+
export interface I_DealroomListing {
|
|
100
|
+
campaignId: string;
|
|
101
|
+
/** The campaign's own name, the one the deal carries once joined (audit #1). Absent on older backends: read the brief. */
|
|
102
|
+
campaignName?: string;
|
|
103
|
+
kind: AgencyCampaignKind;
|
|
104
|
+
organization: I_DealroomListingOrganization;
|
|
105
|
+
briefSummary: string;
|
|
106
|
+
/** Filtered to the creator's platforms (175 rule). */
|
|
107
|
+
deliverables: I_CampaignDeliverable[];
|
|
108
|
+
currency: string;
|
|
109
|
+
/** Null = unlimited. */
|
|
110
|
+
slots: number | null;
|
|
111
|
+
/** Confirmed offers (offer kind) or accepted entries (pot kind) — a count, never who. */
|
|
112
|
+
acceptedCount: number;
|
|
113
|
+
applicationDeadline: string | null;
|
|
114
|
+
explanation: I_MatchExplanation;
|
|
115
|
+
/**
|
|
116
|
+
* What joining shares: the scopes the pre-join (175 consent) screen names.
|
|
117
|
+
* An open listing asks for every read an organization may ask for; the
|
|
118
|
+
* creator ticks a subset when applying. ABSENT = all of AGENCY_GRANT_SCOPES
|
|
119
|
+
* (the backend does not send it today).
|
|
120
|
+
*/
|
|
121
|
+
askedScopes?: I_AgencyGrantScope[];
|
|
122
|
+
/** Money release only; absent while money is off (interpretation 8). */
|
|
123
|
+
guaranteedAmount?: I_Money;
|
|
124
|
+
/** Money release only; absent while money is off. */
|
|
125
|
+
depositPct?: number;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** A listing the creator would match but for one gap. Never appliable, by construction. */
|
|
129
|
+
export interface I_NearMatch {
|
|
130
|
+
listing: Omit<I_DealroomListing, 'explanation'>;
|
|
131
|
+
explanation: I_MatchExplanation;
|
|
132
|
+
gap: I_NearMatchGap;
|
|
133
|
+
applicationAllowed: false;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** The profile fields whose absence makes the feed `profile_incomplete` (FR-026). */
|
|
137
|
+
export const DEALROOM_PROFILE_FIELDS = ['country', 'niches'] as const;
|
|
138
|
+
export type DealroomProfileField = (typeof DEALROOM_PROFILE_FIELDS)[number];
|
|
139
|
+
|
|
140
|
+
/** `GET /dealroom/feed[?broaden=true]`. */
|
|
141
|
+
export interface I_DealroomFeed {
|
|
142
|
+
state: 'ok' | 'profile_incomplete';
|
|
143
|
+
listings: I_DealroomListing[];
|
|
144
|
+
/** Present only when the creator broadened the view. */
|
|
145
|
+
nearMatches?: I_NearMatch[];
|
|
146
|
+
/** Distinct organizations in `listings`; empty when there are none, never filler. */
|
|
147
|
+
logoStrip: I_DealroomListingOrganization[];
|
|
148
|
+
/** With `profile_incomplete`: which fields to complete. */
|
|
149
|
+
missingProfileFields?: DealroomProfileField[];
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* A creator as an organization sees them on a match list, before any grant:
|
|
154
|
+
* handle, platforms, follower BAND per platform, niches, country. Never a
|
|
155
|
+
* rate, never an exact or rich metric (FR-007).
|
|
156
|
+
*/
|
|
157
|
+
export interface I_CoarseCreatorProfile {
|
|
158
|
+
userId: string;
|
|
159
|
+
handle: string;
|
|
160
|
+
platforms: CampaignPlatform[];
|
|
161
|
+
followerBand: Partial<Record<CampaignPlatform, I_FollowerBand>>;
|
|
162
|
+
niches: string[];
|
|
163
|
+
country: string | null;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// ── The organization's side of a listing (US3) ──────────────────────────────
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Derived, never stored: `draft` (175 isDraft) → `live` (open, not closed,
|
|
170
|
+
* before its deadline, a slot free) → `frozen` (live, and a creator has been
|
|
171
|
+
* invited or applied: only the deadline, the slots and closing may change) →
|
|
172
|
+
* `closed` (closed by the organization, past its deadline, or full).
|
|
173
|
+
*/
|
|
174
|
+
export const DEALROOM_LISTING_STATES = ['draft', 'live', 'frozen', 'closed'] as const;
|
|
175
|
+
export type DealroomListingState = (typeof DEALROOM_LISTING_STATES)[number];
|
|
176
|
+
|
|
177
|
+
/** `I_AgencyCampaign.listing`: the listing fields and where the listing stands. */
|
|
178
|
+
export interface I_CampaignListing {
|
|
179
|
+
visibility: CampaignVisibility;
|
|
180
|
+
targetProfile: I_TargetProfile | null;
|
|
181
|
+
/** Null = unlimited. */
|
|
182
|
+
slots: number | null;
|
|
183
|
+
/** Confirmed offers (offer kind) or accepted entries (pot kind): a count, never who. */
|
|
184
|
+
acceptedCount: number;
|
|
185
|
+
/** The offer's own deadline, or the pot's application deadline. */
|
|
186
|
+
applicationDeadline: string | null;
|
|
187
|
+
closedAt: string | null;
|
|
188
|
+
state: DealroomListingState;
|
|
189
|
+
/** True once a creator was invited or applied: terms are fixed (FR-004). */
|
|
190
|
+
frozen: boolean;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/** `GET /agency/campaigns/:id/matches`: coarse rows only, each invitable through 175's invitation route. */
|
|
194
|
+
export interface I_CampaignMatches {
|
|
195
|
+
matches: I_CoarseCreatorProfile[];
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// ── Applying (US4) ───────────────────────────────────────────────────────────
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* `POST /dealroom/listings/:campaignId/apply`. `scopes`: what the creator
|
|
202
|
+
* shares, a non-empty subset of the listing's `askedScopes`. An offer
|
|
203
|
+
* listing takes the opening price (`priceMinor` + `currency`); a pot listing
|
|
204
|
+
* takes the creator's own terms (`guaranteeMinor` + `ratePer1000Minor`).
|
|
205
|
+
*/
|
|
206
|
+
export class ApplyToDealroomListingDto {
|
|
207
|
+
@IsArray()
|
|
208
|
+
@ArrayNotEmpty()
|
|
209
|
+
@IsIn(AGENCY_GRANT_SCOPES as unknown as string[], { each: true })
|
|
210
|
+
scopes: I_AgencyGrantScope[];
|
|
211
|
+
|
|
212
|
+
@IsOptional()
|
|
213
|
+
@IsInt()
|
|
214
|
+
@Min(0)
|
|
215
|
+
@Max(1_000_000_000)
|
|
216
|
+
priceMinor?: number;
|
|
217
|
+
|
|
218
|
+
@IsOptional()
|
|
219
|
+
@IsString()
|
|
220
|
+
@Matches(/^[A-Z]{3}$/)
|
|
221
|
+
currency?: string;
|
|
222
|
+
|
|
223
|
+
@IsOptional()
|
|
224
|
+
@IsInt()
|
|
225
|
+
@Min(0)
|
|
226
|
+
@Max(1_000_000_000)
|
|
227
|
+
guaranteeMinor?: number;
|
|
228
|
+
|
|
229
|
+
@IsOptional()
|
|
230
|
+
@IsInt()
|
|
231
|
+
@Min(0)
|
|
232
|
+
@Max(1_000_000_000)
|
|
233
|
+
ratePer1000Minor?: number;
|
|
234
|
+
|
|
235
|
+
constructor(data?: Partial<ApplyToDealroomListingDto>) {
|
|
236
|
+
this.scopes = data?.scopes ?? [];
|
|
237
|
+
this.priceMinor = data?.priceMinor;
|
|
238
|
+
this.currency = data?.currency;
|
|
239
|
+
this.guaranteeMinor = data?.guaranteeMinor;
|
|
240
|
+
this.ratePer1000Minor = data?.ratePer1000Minor;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/** The apply answer: the creator's campaign as 175 shows it; `alreadyMember` when they were on it before. */
|
|
245
|
+
export interface I_DealroomApplication {
|
|
246
|
+
alreadyMember: boolean;
|
|
247
|
+
campaign: I_CreatorCampaignView;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// ── Codes ────────────────────────────────────────────────────────────────────
|
|
251
|
+
|
|
252
|
+
/** Refusal codes the frontend owes its own copy for (contracts/dealroom-api.md). */
|
|
253
|
+
export const DEALROOM_CODES = {
|
|
254
|
+
OFFER_FILLED: 'dealroom-offer-filled',
|
|
255
|
+
LISTING_FROZEN: 'dealroom-listing-frozen',
|
|
256
|
+
NEAR_MATCH_NOT_APPLIABLE: 'dealroom-near-match-not-appliable',
|
|
257
|
+
PROFILE_INCOMPLETE: 'dealroom-profile-incomplete',
|
|
258
|
+
BRAND_DOOR_CLOSED: 'dealroom-brand-door-closed',
|
|
259
|
+
BRAND_NO_ROSTER: 'dealroom-brand-no-roster',
|
|
260
|
+
DELEGATE_FORBIDDEN: 'dealroom-delegate-forbidden',
|
|
261
|
+
DELEGATE_OWN_DEAL: 'dealroom-delegate-own-deal',
|
|
262
|
+
/** A delegate never writes the creator's grant consent: joining with an access request, or an apply that would create or widen the grant's reads. */
|
|
263
|
+
DELEGATE_CONSENT_FORBIDDEN: 'dealroom-delegate-consent-forbidden',
|
|
264
|
+
/** The money switch is off: every money route and act answers 404 with this code (FR-027). Means nothing else. */
|
|
265
|
+
MONEY_DISABLED: 'dealroom-money-disabled',
|
|
266
|
+
/** Money on, but a value the act needs is not configured yet (e.g. the deal's deposit %). 422. */
|
|
267
|
+
CONFIG_MISSING: 'dealroom-config-missing',
|
|
268
|
+
/** Money on, but the platform contract template is still waiting for counsel: upload your own. 422. */
|
|
269
|
+
TEMPLATE_PENDING: 'dealroom-template-pending',
|
|
270
|
+
/** An open listing needs the organization's logo first (US2). 400. */
|
|
271
|
+
LOGO_REQUIRED: 'dealroom-logo-required',
|
|
272
|
+
CONTRACT_UNSIGNED: 'dealroom-contract-unsigned',
|
|
273
|
+
DEPOSIT_REQUIRED: 'dealroom-deposit-required',
|
|
274
|
+
NOT_FUNDED: 'dealroom-not-funded',
|
|
275
|
+
/** Money release: the agreed amount is under the fee config's minimum deal. */
|
|
276
|
+
DEAL_BELOW_MINIMUM: 'dealroom-deal-below-minimum',
|
|
277
|
+
/** Money release: that money act is not open at the deal's current step. */
|
|
278
|
+
MONEY_WRONG_STEP: 'dealroom-money-wrong-step',
|
|
279
|
+
/** Money release: the deal already has a contract out for signature or signed. */
|
|
280
|
+
CONTRACT_EXISTS: 'dealroom-contract-exists',
|
|
281
|
+
/** Money release: no guarantee is configured, or the claim is not open yet. */
|
|
282
|
+
GUARANTEE_UNAVAILABLE: 'dealroom-guarantee-unavailable',
|
|
283
|
+
/** Commission lane (2026-10-07): the creator's Admitad Ad Space is still under admin review; no link yet. */
|
|
284
|
+
COMMISSION_AD_SPACE_PENDING: 'commission-ad-space-pending',
|
|
285
|
+
/** Commission lane (2026-10-07): the creator's Admitad Ad Space was rejected or suspended; no Admitad link. */
|
|
286
|
+
COMMISSION_AD_SPACE_UNAVAILABLE: 'commission-ad-space-unavailable',
|
|
287
|
+
/** Admin: that decision is not open from the Ad Space's state (a suspended or rejected Ad Space is never re-registered under a new id). */
|
|
288
|
+
COMMISSION_AD_SPACE_DECISION_CLOSED: 'commission-ad-space-decision-closed',
|
|
289
|
+
/** The network did not answer a join or a registration; nothing was written. */
|
|
290
|
+
COMMISSION_NETWORK_UNAVAILABLE: 'commission-network-unavailable',
|
|
291
|
+
} as const;
|
|
292
|
+
export type DealroomCode = (typeof DEALROOM_CODES)[keyof typeof DEALROOM_CODES];
|
|
293
|
+
|
|
294
|
+
// ── Money (DORMANT until the money release) ──────────────────────────────────
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* The deal's money state (FR-010), exactly data-model.md's machine:
|
|
298
|
+
*
|
|
299
|
+
* awaiting_material → material_submitted → deposit_paid → funded → approved → published
|
|
300
|
+
* deposit declined → closed_unviewed
|
|
301
|
+
* funded/approved, never published → refund_pending → refunded
|
|
302
|
+
* delivered, never funded → guarantee_claimed
|
|
303
|
+
*
|
|
304
|
+
* The payout after publication is its own column (DEAL_PAYOUT_STATES).
|
|
305
|
+
*/
|
|
306
|
+
export const DEAL_MONEY_STATES = [
|
|
307
|
+
'awaiting_material',
|
|
308
|
+
'material_submitted',
|
|
309
|
+
'deposit_paid',
|
|
310
|
+
'funded',
|
|
311
|
+
'approved',
|
|
312
|
+
'published',
|
|
313
|
+
'closed_unviewed',
|
|
314
|
+
'refund_pending',
|
|
315
|
+
'refunded',
|
|
316
|
+
'guarantee_claimed',
|
|
317
|
+
] as const;
|
|
318
|
+
export type DealMoneyState = (typeof DEAL_MONEY_STATES)[number];
|
|
319
|
+
|
|
320
|
+
export const DEAL_PAYOUT_STATES = ['none', 'initiated', 'paid'] as const;
|
|
321
|
+
export type DealPayoutState = (typeof DEAL_PAYOUT_STATES)[number];
|
|
322
|
+
|
|
323
|
+
/** One payment-rail cost, passed through at cost and shown, never part of our fee. */
|
|
324
|
+
export interface I_RailCostItem {
|
|
325
|
+
kind: 'processing' | 'payout' | 'account' | 'fx' | 'other';
|
|
326
|
+
amount: I_Money;
|
|
327
|
+
note?: string;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/** The FX conversion a snapshot used (175 `fx_rates`, ECB daily). */
|
|
331
|
+
export interface I_FxSnapshot {
|
|
332
|
+
from: string;
|
|
333
|
+
to: string;
|
|
334
|
+
rate: number;
|
|
335
|
+
rateDate: string;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/** Frozen at signing: what the fees were, which cap bound, and whether the floor guard did. */
|
|
339
|
+
export interface I_DealFeeSnapshot {
|
|
340
|
+
agencyFee: I_Money;
|
|
341
|
+
creatorFee: I_Money;
|
|
342
|
+
capsApplied: { agency: boolean; creator: boolean };
|
|
343
|
+
railCosts: I_RailCostItem[];
|
|
344
|
+
floorGuardApplied: boolean;
|
|
345
|
+
fx?: I_FxSnapshot;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
export interface I_DealroomDeal {
|
|
349
|
+
id: string;
|
|
350
|
+
/** Offer deals; null for a pot deal. */
|
|
351
|
+
collaborationId: string | null;
|
|
352
|
+
/** Pot deals; null for an offer deal. */
|
|
353
|
+
potEntryId: string | null;
|
|
354
|
+
campaignId: string;
|
|
355
|
+
organizationId: string;
|
|
356
|
+
creatorUserId: string | null;
|
|
357
|
+
agreed: I_Money;
|
|
358
|
+
feeSnapshot: I_DealFeeSnapshot;
|
|
359
|
+
moneyState: DealMoneyState;
|
|
360
|
+
payoutState: DealPayoutState;
|
|
361
|
+
/** The cap the guarantee covers, never more (US7 scenario 4); zero when no guarantee is configured. */
|
|
362
|
+
guaranteeCap: I_Money;
|
|
363
|
+
/** Snapshotted from the settlement form when the contract is signed; null before, or for barter / international. */
|
|
364
|
+
payoutTrack: 1 | 2 | null;
|
|
365
|
+
createdAt: string;
|
|
366
|
+
/** The 175 membership the deal belongs to (wave 4). */
|
|
367
|
+
memberId?: string;
|
|
368
|
+
/** When the fee snapshot was frozen (contract signed); null while it is still a quote. */
|
|
369
|
+
feeFrozenAt?: string | null;
|
|
370
|
+
/** The zadatek %, snapshotted at confirmation; null while counsel has not set it. */
|
|
371
|
+
depositPct?: number | null;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
export interface I_DealContract {
|
|
375
|
+
collaborationId: string;
|
|
376
|
+
source: 'template' | 'uploaded';
|
|
377
|
+
documentUrl: string | null;
|
|
378
|
+
signatureLevel: 'SES' | 'QES';
|
|
379
|
+
provider: string;
|
|
380
|
+
state: 'draft' | 'sent' | 'signed';
|
|
381
|
+
signedAt: string | null;
|
|
382
|
+
/** Wave 4 (additive). */
|
|
383
|
+
id?: string;
|
|
384
|
+
/** The template's form fields as sent (template contracts). */
|
|
385
|
+
terms?: I_DealContractTerms | null;
|
|
386
|
+
/** The uploaded file's name (uploaded contracts). */
|
|
387
|
+
fileName?: string | null;
|
|
388
|
+
sentAt?: string | null;
|
|
389
|
+
createdAt?: string;
|
|
390
|
+
/**
|
|
391
|
+
* The e-sign provider's signing link for the CALLER's own party (the
|
|
392
|
+
* organization's signer or the creator), while the contract is `sent` and
|
|
393
|
+
* that party has not signed; null otherwise. Never the other party's link.
|
|
394
|
+
*/
|
|
395
|
+
signingUrl?: string | null;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
export interface I_DealPayment {
|
|
399
|
+
id: string;
|
|
400
|
+
collaborationId: string;
|
|
401
|
+
type: 'deposit' | 'funding' | 'refund' | 'payout';
|
|
402
|
+
amount: I_Money;
|
|
403
|
+
railCostsItemized: I_RailCostItem[];
|
|
404
|
+
state: 'pending' | 'succeeded' | 'failed';
|
|
405
|
+
fxSnapshot?: I_FxSnapshot;
|
|
406
|
+
createdAt: string;
|
|
407
|
+
/** Wave 4 (additive): the deal it belongs to. */
|
|
408
|
+
dealroomDealId?: string;
|
|
409
|
+
/** Wave 4: what the payer is charged in all: the amount, the organization's fee when it is due, and the rail costs. */
|
|
410
|
+
total?: I_Money;
|
|
411
|
+
/** Wave 4: the organization's fee carried by this payment (funding only). */
|
|
412
|
+
agencyFee?: I_Money | null;
|
|
413
|
+
method?: DealPaymentMethod;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/** Derived from 175's settlement form (`invoice` → faktura_vat, `contract` → umowa_o_dzielo); `international` is 148's. */
|
|
417
|
+
export const PAYOUT_BILLING_VARIANTS = ['faktura_vat', 'umowa_o_dzielo', 'international'] as const;
|
|
418
|
+
export type PayoutBillingVariant = (typeof PAYOUT_BILLING_VARIANTS)[number];
|
|
419
|
+
|
|
420
|
+
/** Settings → Payouts. The payment provider's account id is never surfaced. */
|
|
421
|
+
export interface I_PayoutProfile {
|
|
422
|
+
/** Wave 4 (additive): the 175 settlement form the track is read from (one setting, not two). */
|
|
423
|
+
settlementForm?: SettlementForm | null;
|
|
424
|
+
/** Wave 4: everything a payout needs is on file. Joining and working never wait for it; the first payout does. */
|
|
425
|
+
complete?: boolean;
|
|
426
|
+
/** Wave 4: what is still missing for the first payout. */
|
|
427
|
+
missing?: PayoutProfileField[];
|
|
428
|
+
/** Null while the settlement form is unset or barter (no payout) and the international variant is not chosen. */
|
|
429
|
+
billingVariant: PayoutBillingVariant | null;
|
|
430
|
+
/** 1 = invoice, 2 = umowa o dzieło (withholding); null for barter or international. */
|
|
431
|
+
track: 1 | 2 | null;
|
|
432
|
+
nip?: string;
|
|
433
|
+
taxStatement?: string;
|
|
434
|
+
intlBillingData?: Record<string, string>;
|
|
435
|
+
iban?: string;
|
|
436
|
+
payoutCurrency: string;
|
|
437
|
+
kycState: 'none' | 'pending' | 'verified' | 'rejected';
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
export const PAYOUT_LEDGER_SOURCES = ['deal', 'pot', 'commission'] as const;
|
|
441
|
+
export type PayoutLedgerSource = (typeof PAYOUT_LEDGER_SOURCES)[number];
|
|
442
|
+
|
|
443
|
+
/** Commission entries are `validated` only after the network pays us. */
|
|
444
|
+
export const PAYOUT_LEDGER_STATES = ['earned', 'validated', 'paid'] as const;
|
|
445
|
+
export type PayoutLedgerState = (typeof PAYOUT_LEDGER_STATES)[number];
|
|
446
|
+
|
|
447
|
+
export interface I_PayoutLedgerEntry {
|
|
448
|
+
id: string;
|
|
449
|
+
source: PayoutLedgerSource;
|
|
450
|
+
dealroomDealId?: string;
|
|
451
|
+
potEntryId?: string;
|
|
452
|
+
network?: CommissionNetwork;
|
|
453
|
+
amount: I_Money;
|
|
454
|
+
feesItemized: I_RailCostItem[];
|
|
455
|
+
state: PayoutLedgerState;
|
|
456
|
+
availableAt?: string;
|
|
457
|
+
createdAt: string;
|
|
458
|
+
/** Wave 4 (additive): track 2's withheld PIT, itemized; absent when nothing is withheld. */
|
|
459
|
+
withheld?: I_Money | null;
|
|
460
|
+
/** Wave 4: why the entry exists. */
|
|
461
|
+
reason?: 'publication' | 'guarantee' | 'deposit_forfeit' | 'pot' | 'commission';
|
|
462
|
+
/** Wave 4: why a due payout waits (`profile_incomplete`, `below_threshold`, …); null when it does not. */
|
|
463
|
+
holdReason?: string | null;
|
|
464
|
+
paidAt?: string | null;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// ── Money release, wave 4 (DORMANT): contract step, deal money, payouts ──────
|
|
468
|
+
|
|
469
|
+
/** The contract, money and payout kinds this release writes into 175's event log (FR-008a). */
|
|
470
|
+
export const DEALROOM_MONEY_EVENT_KINDS = [
|
|
471
|
+
'contract_sent',
|
|
472
|
+
'contract_signed',
|
|
473
|
+
'contract_declined',
|
|
474
|
+
'deposit_paid',
|
|
475
|
+
'deal_funded',
|
|
476
|
+
'deal_closed_unviewed',
|
|
477
|
+
'deal_refund_requested',
|
|
478
|
+
'deal_refunded',
|
|
479
|
+
'guarantee_claimed',
|
|
480
|
+
'payout_initiated',
|
|
481
|
+
'payout_paid',
|
|
482
|
+
] as const;
|
|
483
|
+
export type DealroomMoneyEventKind = (typeof DEALROOM_MONEY_EVENT_KINDS)[number];
|
|
484
|
+
|
|
485
|
+
export const DEAL_CONTRACT_SOURCES = ['template', 'uploaded'] as const;
|
|
486
|
+
export type DealContractSource = (typeof DEAL_CONTRACT_SOURCES)[number];
|
|
487
|
+
|
|
488
|
+
/** `draft` again after a signer declines; `signed` once every party signed (the e-sign provider's word). */
|
|
489
|
+
export const DEAL_CONTRACT_STATES = ['draft', 'sent', 'signed'] as const;
|
|
490
|
+
export type DealContractState = (typeof DEAL_CONTRACT_STATES)[number];
|
|
491
|
+
|
|
492
|
+
/** SES by default, QES as an option (research R2; counsel point 3 may require QES for some documents). */
|
|
493
|
+
export const DEAL_SIGNATURE_LEVELS = ['SES', 'QES'] as const;
|
|
494
|
+
export type DealSignatureLevel = (typeof DEAL_SIGNATURE_LEVELS)[number];
|
|
495
|
+
|
|
496
|
+
/** What a template contract carries (the organization's form-fill). Parties and rate come from the deal, never the form. */
|
|
497
|
+
export interface I_DealContractTerms {
|
|
498
|
+
parties: { organization: string; creator: string };
|
|
499
|
+
deliverables: string[];
|
|
500
|
+
rate: I_Money;
|
|
501
|
+
draftDueAt: string | null;
|
|
502
|
+
liveByAt: string | null;
|
|
503
|
+
licenceMonths: number | null;
|
|
504
|
+
paidPromoMonths: number | null;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
/**
|
|
508
|
+
* `POST /agency/campaigns/:id/members/:memberId/contract` (organization side).
|
|
509
|
+
* `template`: the form fields (deliverables default to the brief's, the rate is
|
|
510
|
+
* the agreed price). `uploaded`: the organization's own file, base64.
|
|
511
|
+
*/
|
|
512
|
+
export class CreateDealContractDto {
|
|
513
|
+
@IsIn(DEAL_CONTRACT_SOURCES as unknown as string[])
|
|
514
|
+
source: DealContractSource;
|
|
515
|
+
|
|
516
|
+
@IsOptional()
|
|
517
|
+
@IsIn(DEAL_SIGNATURE_LEVELS as unknown as string[])
|
|
518
|
+
signatureLevel?: DealSignatureLevel;
|
|
519
|
+
|
|
520
|
+
@IsOptional()
|
|
521
|
+
@IsArray()
|
|
522
|
+
@ArrayMaxSize(20)
|
|
523
|
+
@IsString({ each: true })
|
|
524
|
+
@MaxLength(500, { each: true })
|
|
525
|
+
deliverables?: string[];
|
|
526
|
+
|
|
527
|
+
@IsOptional()
|
|
528
|
+
@IsDateString()
|
|
529
|
+
draftDueAt?: string;
|
|
530
|
+
|
|
531
|
+
@IsOptional()
|
|
532
|
+
@IsDateString()
|
|
533
|
+
liveByAt?: string;
|
|
534
|
+
|
|
535
|
+
@IsOptional()
|
|
536
|
+
@IsInt()
|
|
537
|
+
@Min(0)
|
|
538
|
+
@Max(120)
|
|
539
|
+
licenceMonths?: number;
|
|
540
|
+
|
|
541
|
+
@IsOptional()
|
|
542
|
+
@IsInt()
|
|
543
|
+
@Min(0)
|
|
544
|
+
@Max(120)
|
|
545
|
+
paidPromoMonths?: number;
|
|
546
|
+
|
|
547
|
+
@IsOptional()
|
|
548
|
+
@IsString()
|
|
549
|
+
@MaxLength(255)
|
|
550
|
+
fileName?: string;
|
|
551
|
+
|
|
552
|
+
/** The uploaded file, base64; the backend checks the type and the size. */
|
|
553
|
+
@IsOptional()
|
|
554
|
+
@IsString()
|
|
555
|
+
fileBase64?: string;
|
|
556
|
+
|
|
557
|
+
constructor(data?: Partial<CreateDealContractDto>) {
|
|
558
|
+
this.source = data?.source ?? 'template';
|
|
559
|
+
this.signatureLevel = data?.signatureLevel;
|
|
560
|
+
this.deliverables = data?.deliverables;
|
|
561
|
+
this.draftDueAt = data?.draftDueAt;
|
|
562
|
+
this.liveByAt = data?.liveByAt;
|
|
563
|
+
this.licenceMonths = data?.licenceMonths;
|
|
564
|
+
this.paidPromoMonths = data?.paidPromoMonths;
|
|
565
|
+
this.fileName = data?.fileName;
|
|
566
|
+
this.fileBase64 = data?.fileBase64;
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
/** Card (PaymentIntent, confirmed in the browser) or invoice (a Stripe invoice the organization pays by transfer). */
|
|
571
|
+
export const DEAL_PAYMENT_METHODS = ['card', 'invoice'] as const;
|
|
572
|
+
export type DealPaymentMethod = (typeof DEAL_PAYMENT_METHODS)[number];
|
|
573
|
+
|
|
574
|
+
/** `POST /agency/campaigns/:id/members/:memberId/deal/deposit|fund`. Big sums go by invoice whatever is asked. */
|
|
575
|
+
export class StartDealPaymentDto {
|
|
576
|
+
@IsOptional()
|
|
577
|
+
@IsIn(DEAL_PAYMENT_METHODS as unknown as string[])
|
|
578
|
+
method?: DealPaymentMethod;
|
|
579
|
+
|
|
580
|
+
constructor(data?: Partial<StartDealPaymentDto>) {
|
|
581
|
+
this.method = data?.method;
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
/** A started payment: confirm `clientSecret` with Stripe.js (card), or send the payer to `invoiceUrl` (invoice). */
|
|
586
|
+
export interface I_DealPaymentStart {
|
|
587
|
+
payment: I_DealPayment;
|
|
588
|
+
clientSecret: string | null;
|
|
589
|
+
invoiceUrl: string | null;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
/** The money acts a deal view may offer; the server decides which, per side and step. */
|
|
593
|
+
export const DEAL_MONEY_ACTIONS = ['send_contract', 'pay_deposit', 'decline_deposit', 'fund', 'refund', 'claim_guarantee'] as const;
|
|
594
|
+
export type DealMoneyAction = (typeof DEAL_MONEY_ACTIONS)[number];
|
|
595
|
+
|
|
596
|
+
/**
|
|
597
|
+
* One deal's money, as one side reads it:
|
|
598
|
+
* `GET /agency/campaigns/:id/members/:memberId/deal` (organization) and
|
|
599
|
+
* `GET /dealroom/my-deals/:memberId` (creator). Null `deal` ⇒ no money row
|
|
600
|
+
* (money was off at confirmation, or a pot entry with no guarantee).
|
|
601
|
+
*/
|
|
602
|
+
export interface I_DealroomDealView {
|
|
603
|
+
deal: I_DealroomDeal;
|
|
604
|
+
contract: I_DealContract | null;
|
|
605
|
+
/** The zadatek and the rest; null while counsel has not set the deposit %. */
|
|
606
|
+
deposit: I_Money | null;
|
|
607
|
+
remaining: I_Money | null;
|
|
608
|
+
payments: I_DealPayment[];
|
|
609
|
+
/** The organization may open the submitted material (deposit paid). */
|
|
610
|
+
materialViewable: boolean;
|
|
611
|
+
/** The organization may approve it (fully funded). */
|
|
612
|
+
approvable: boolean;
|
|
613
|
+
actions: DealMoneyAction[];
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
/** What the first payout needs and is missing; the provider's KYC runs behind the form and is never a field. */
|
|
617
|
+
export const PAYOUT_PROFILE_FIELDS = ['settlement_form', 'nip', 'tax_statement', 'intl_billing_data', 'iban', 'payout_currency'] as const;
|
|
618
|
+
export type PayoutProfileField = (typeof PAYOUT_PROFILE_FIELDS)[number];
|
|
619
|
+
|
|
620
|
+
/**
|
|
621
|
+
* `PUT /dealroom/payouts/profile`. The track is NOT here: it is read from the
|
|
622
|
+
* 175 settlement form. `international` picks the international variant.
|
|
623
|
+
*/
|
|
624
|
+
export class UpsertPayoutProfileDto {
|
|
625
|
+
@IsOptional()
|
|
626
|
+
@IsBoolean()
|
|
627
|
+
international?: boolean;
|
|
628
|
+
|
|
629
|
+
@IsOptional()
|
|
630
|
+
@IsString()
|
|
631
|
+
@Matches(/^[0-9]{10}$/)
|
|
632
|
+
nip?: string;
|
|
633
|
+
|
|
634
|
+
@IsOptional()
|
|
635
|
+
@IsString()
|
|
636
|
+
@MaxLength(2000)
|
|
637
|
+
taxStatement?: string;
|
|
638
|
+
|
|
639
|
+
@IsOptional()
|
|
640
|
+
@IsObject()
|
|
641
|
+
intlBillingData?: Record<string, string>;
|
|
642
|
+
|
|
643
|
+
/** IBAN: country code, check digits, 11-30 alphanumerics, spaces allowed. */
|
|
644
|
+
@IsString()
|
|
645
|
+
@Matches(/^[A-Z]{2}[0-9]{2}(?:\s?[A-Z0-9]){11,30}$/)
|
|
646
|
+
iban: string;
|
|
647
|
+
|
|
648
|
+
@IsString()
|
|
649
|
+
@Matches(/^[A-Z]{3}$/)
|
|
650
|
+
payoutCurrency: string;
|
|
651
|
+
|
|
652
|
+
constructor(data?: Partial<UpsertPayoutProfileDto>) {
|
|
653
|
+
this.international = data?.international;
|
|
654
|
+
this.nip = data?.nip;
|
|
655
|
+
this.taxStatement = data?.taxStatement;
|
|
656
|
+
this.intlBillingData = data?.intlBillingData;
|
|
657
|
+
this.iban = data?.iban ?? '';
|
|
658
|
+
this.payoutCurrency = data?.payoutCurrency ?? '';
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
/** `GET /dealroom/payouts/ledger`: every entry and the totals per currency, honestly per state. */
|
|
663
|
+
export interface I_PayoutLedger {
|
|
664
|
+
entries: I_PayoutLedgerEntry[];
|
|
665
|
+
totals: Array<{ currency: string; earned: number; validated: number; paid: number }>;
|
|
666
|
+
/** Commission payouts batch monthly from this amount up; below it they accrue. */
|
|
667
|
+
commissionThreshold: I_Money;
|
|
668
|
+
profileComplete: boolean;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
// ── Commission lane ──────────────────────────────────────────────────────────
|
|
672
|
+
|
|
673
|
+
export const COMMISSION_NETWORKS = ['awin', 'admitad'] as const;
|
|
674
|
+
export type CommissionNetwork = (typeof COMMISSION_NETWORKS)[number];
|
|
675
|
+
|
|
676
|
+
export const COMMISSION_OFFER_STATES = ['live', 'paused', 'gone'] as const;
|
|
677
|
+
export type CommissionOfferState = (typeof COMMISSION_OFFER_STATES)[number];
|
|
678
|
+
|
|
679
|
+
export const COMMISSION_LINK_STATES = ['active', 'suspended'] as const;
|
|
680
|
+
export type CommissionLinkState = (typeof COMMISSION_LINK_STATES)[number];
|
|
681
|
+
|
|
682
|
+
/**
|
|
683
|
+
* `subnetwork`: minted from OUR publisher account with the creator's own
|
|
684
|
+
* attribution (Awin `clickref` = creator id; Admitad = the creator's registered
|
|
685
|
+
* Ad Space). `deeplink`: the fallback while a network's subnetwork mode is off,
|
|
686
|
+
* a plain link to the programme on the network, carrying no attribution of ours.
|
|
687
|
+
*/
|
|
688
|
+
export const COMMISSION_LINK_MODES = ['subnetwork', 'deeplink'] as const;
|
|
689
|
+
export type CommissionLinkMode = (typeof COMMISSION_LINK_MODES)[number];
|
|
690
|
+
|
|
691
|
+
/** An advertiser creative as the network feed carries it (2026-10-07). */
|
|
692
|
+
export const COMMISSION_MATERIAL_KINDS = ['logo', 'banner', 'landing_preview', 'coupon'] as const;
|
|
693
|
+
export type CommissionMaterialKind = (typeof COMMISSION_MATERIAL_KINDS)[number];
|
|
694
|
+
|
|
695
|
+
export interface I_CommissionMaterial {
|
|
696
|
+
kind: CommissionMaterialKind;
|
|
697
|
+
/** Image or landing page; absent on a text-only coupon. */
|
|
698
|
+
url?: string;
|
|
699
|
+
/** Coupon code or creative text. */
|
|
700
|
+
text?: string;
|
|
701
|
+
width?: number;
|
|
702
|
+
height?: number;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
export interface I_CommissionOffer {
|
|
706
|
+
id: string;
|
|
707
|
+
/** Always 'commission': a row of this lane is never an agency listing. */
|
|
708
|
+
kind: 'commission';
|
|
709
|
+
network: CommissionNetwork;
|
|
710
|
+
/** The advertiser (programme) name, as the network names it. */
|
|
711
|
+
title: string;
|
|
712
|
+
/** The commercial terms, as the network states them (e.g. "5% od sprzedaży", "12,00 PLN za lead"). */
|
|
713
|
+
terms: string;
|
|
714
|
+
commissionType: 'CPS' | 'CPL' | 'CPC' | 'CPI';
|
|
715
|
+
plEligible: boolean;
|
|
716
|
+
/** country is always PL; niches from the programme's categories when mappable, else blank (all). */
|
|
717
|
+
derivedTargetProfile: I_TargetProfile;
|
|
718
|
+
state: CommissionOfferState;
|
|
719
|
+
lastSyncAt: string;
|
|
720
|
+
/**
|
|
721
|
+
* Advertiser materials (2026-10-07). PRESENT ONLY when that network's
|
|
722
|
+
* written-consent flag is on (Awin: DEALROOM_COMMISSION_AWIN_MATERIALS_CONSENT;
|
|
723
|
+
* Admitad: never in v1). The server strips them; the client never decides.
|
|
724
|
+
*/
|
|
725
|
+
materials?: I_CommissionMaterial[];
|
|
726
|
+
/** The caller's own link to this offer, when they joined it. */
|
|
727
|
+
myLink?: I_CommissionLink;
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
export interface I_CommissionLink {
|
|
731
|
+
id: string;
|
|
732
|
+
commissionOfferId: string;
|
|
733
|
+
mode: CommissionLinkMode;
|
|
734
|
+
url: string;
|
|
735
|
+
state: CommissionLinkState;
|
|
736
|
+
/** Admitad in subnetwork mode: the creator's registered Ad Space id. */
|
|
737
|
+
adSpaceId?: string;
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
/**
|
|
741
|
+
* `GET /dealroom/commission/offers`. The lane, never mixed with agency offers.
|
|
742
|
+
* A network that failed its last sync contributes NO rows (never a stale
|
|
743
|
+
* cache shown as live) and is named in `unavailableNetworks`.
|
|
744
|
+
*/
|
|
745
|
+
export interface I_CommissionLane {
|
|
746
|
+
state: 'ok' | 'profile_incomplete';
|
|
747
|
+
offers: I_CommissionOffer[];
|
|
748
|
+
unavailableNetworks: CommissionNetwork[];
|
|
749
|
+
/** With `profile_incomplete`: which fields to complete (the feed's rule, FR-026). */
|
|
750
|
+
missingProfileFields?: DealroomProfileField[];
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
/** `POST /dealroom/commission/offers/:id/join`: what the join produced. */
|
|
754
|
+
export const COMMISSION_JOIN_STATES = ['link', 'deeplink', 'ad_space_pending'] as const;
|
|
755
|
+
export type CommissionJoinState = (typeof COMMISSION_JOIN_STATES)[number];
|
|
756
|
+
|
|
757
|
+
/**
|
|
758
|
+
* `link` (200): a subnetwork link from our account, attributed to the creator.
|
|
759
|
+
* `deeplink` (200): the network's own programme page (subnetwork mode off).
|
|
760
|
+
* `ad_space_pending` (202): the creator's first Admitad join created their Ad
|
|
761
|
+
* Space for admin review; no link until it is registered.
|
|
762
|
+
*/
|
|
763
|
+
export interface I_CommissionJoinResult {
|
|
764
|
+
state: CommissionJoinState;
|
|
765
|
+
url?: string;
|
|
766
|
+
link?: I_CommissionLink;
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
// ── Commission lane: the Admitad Ad Space per creator (2026-10-07) ───────────
|
|
770
|
+
|
|
771
|
+
/**
|
|
772
|
+
* One static Ad Space per creator (Admitad Subnetwork Code of Conduct §3.1/§5):
|
|
773
|
+
* created `pending_review` on the first Admitad join, `registered` through the
|
|
774
|
+
* Partner networks API after an admin approves, or `rejected` / `suspended`.
|
|
775
|
+
* Its id is never rotated, deleted or reassigned.
|
|
776
|
+
*/
|
|
777
|
+
export const COMMISSION_AD_SPACE_STATES = ['pending_review', 'registered', 'rejected', 'suspended'] as const;
|
|
778
|
+
export type CommissionAdSpaceState = (typeof COMMISSION_AD_SPACE_STATES)[number];
|
|
779
|
+
|
|
780
|
+
export const COMMISSION_AD_SPACE_DECISIONS = ['approve', 'reject', 'suspend'] as const;
|
|
781
|
+
export type CommissionAdSpaceDecision = (typeof COMMISSION_AD_SPACE_DECISIONS)[number];
|
|
782
|
+
|
|
783
|
+
/** A row of `GET /admin/dealroom/commission/ad-spaces` (admin only). */
|
|
784
|
+
export interface I_CommissionAdSpace {
|
|
785
|
+
id: string;
|
|
786
|
+
network: 'admitad';
|
|
787
|
+
state: CommissionAdSpaceState;
|
|
788
|
+
creator: { userId: string; email: string | null; displayName: string | null };
|
|
789
|
+
/** The creator's connected profile URL, sent with the registration. */
|
|
790
|
+
sourceUrl: string;
|
|
791
|
+
/** Set at registration; static from then on. */
|
|
792
|
+
adSpaceId: string | null;
|
|
793
|
+
reviewedAt: string | null;
|
|
794
|
+
registeredAt: string | null;
|
|
795
|
+
createdAt: string;
|
|
796
|
+
/** How many of the creator's Admitad links are active. */
|
|
797
|
+
activeLinks: number;
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
/** `PATCH /admin/dealroom/commission/ad-spaces/:id`. */
|
|
801
|
+
export class ReviewCommissionAdSpaceDto {
|
|
802
|
+
@IsIn(COMMISSION_AD_SPACE_DECISIONS as unknown as string[])
|
|
803
|
+
decision: CommissionAdSpaceDecision;
|
|
804
|
+
|
|
805
|
+
constructor(data?: Partial<ReviewCommissionAdSpaceDto>) {
|
|
806
|
+
this.decision = data?.decision as CommissionAdSpaceDecision;
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
// ── The calendar (US9, T041) ─────────────────────────────────────────────────
|
|
811
|
+
|
|
812
|
+
/**
|
|
813
|
+
* What a deal date on the creator's calendar is. No second deadline store:
|
|
814
|
+
* `milestone` is a 175 milestone (`campaign-deadlines`, the creator's own or
|
|
815
|
+
* campaign-wide); the pot dates are the US9 schedule; `publish_at` is the
|
|
816
|
+
* agreed publication time (a pot entry's, or an offer deal's, T067).
|
|
817
|
+
*/
|
|
818
|
+
export const DEALROOM_CALENDAR_KINDS = [
|
|
819
|
+
'milestone',
|
|
820
|
+
'application_deadline',
|
|
821
|
+
'materials_due',
|
|
822
|
+
'publication_due',
|
|
823
|
+
'publish_at',
|
|
824
|
+
'bonus_ends',
|
|
825
|
+
'keep_up_ends',
|
|
826
|
+
] as const;
|
|
827
|
+
export type DealroomCalendarKind = (typeof DEALROOM_CALENDAR_KINDS)[number];
|
|
828
|
+
|
|
829
|
+
/** One marker on the Dashboard calendar; the marker IS the tap target. */
|
|
830
|
+
export interface I_DealroomCalendarItem {
|
|
831
|
+
/** Stable per marker: `<kind>:<memberId>[:<milestoneId>]`. */
|
|
832
|
+
id: string;
|
|
833
|
+
kind: DealroomCalendarKind;
|
|
834
|
+
/** ISO timestamp of the deadline. */
|
|
835
|
+
at: string;
|
|
836
|
+
/** `milestone` only: draft_due | live_by | custom; `label` is set for custom. */
|
|
837
|
+
milestoneKind?: AgencyMilestoneKind;
|
|
838
|
+
label?: string | null;
|
|
839
|
+
/** `milestone` only: the milestone is confirmed. */
|
|
840
|
+
done?: boolean;
|
|
841
|
+
organization: { id: string; name: string };
|
|
842
|
+
campaign: { id: string; name: string; kind: AgencyCampaignKind };
|
|
843
|
+
/** The deep link into the deal: the creator's campaign + their membership + the step the date is about. */
|
|
844
|
+
target: { campaignId: string; memberId: string; step: string | null };
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
/** `GET /dealroom/calendar?from&to` — the creator's own deal dates in the window, oldest first. */
|
|
848
|
+
export interface I_DealroomCalendar {
|
|
849
|
+
from: string;
|
|
850
|
+
to: string;
|
|
851
|
+
items: I_DealroomCalendarItem[];
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
/** The calendar window: both ends required, at most DEALROOM_CALENDAR_MAX_DAYS apart. */
|
|
855
|
+
export const DEALROOM_CALENDAR_MAX_DAYS = 400;
|
|
856
|
+
|
|
857
|
+
export class DealroomCalendarQueryDto {
|
|
858
|
+
@IsDateString()
|
|
859
|
+
from: string;
|
|
860
|
+
|
|
861
|
+
@IsDateString()
|
|
862
|
+
to: string;
|
|
863
|
+
|
|
864
|
+
constructor(data?: Partial<DealroomCalendarQueryDto>) {
|
|
865
|
+
this.from = data?.from ?? '';
|
|
866
|
+
this.to = data?.to ?? '';
|
|
867
|
+
}
|
|
868
|
+
}
|