@saasicat/spec 1.0.0-rc.24 → 1.0.0-rc.25
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/admin-api.openapi.yaml +550 -2
- package/package.json +1 -1
- package/prisma-fragments/05-bundle.prisma +4 -3
- package/prisma-fragments/08-subscription-contract.prisma +7 -0
- package/prisma-fragments/09-pending-registration.prisma +6 -0
- package/prisma-fragments/11-subscription-bundle.prisma +12 -2
- package/prisma-fragments/13-subscriber.prisma +81 -8
- package/prisma-fragments/15-subscriber-ledger.prisma +1 -1
- package/prisma-fragments/17-subscription-notice.prisma +5 -2
- package/prisma-fragments/19-bundle-version-retirement.prisma +44 -0
- package/prisma-fragments/README.md +23 -20
- package/schemas/plan-catalog.schema.json +27 -3
- package/schemas/subscriber-ledger.schema.json +2 -1
- package/sql/1.0-a-correction-carries-its-order.postgres.sql +78 -0
- package/sql/1.0-a-sign-up-keeps-its-vat-id-check.postgres.sql +38 -0
- package/sql/1.0-a-subscriber-has-a-tax-origin.postgres.sql +113 -0
- package/sql/1.0-an-add-on-retirement-is-announced.postgres.sql +44 -0
- package/sql/1.0-an-add-on-switch-waits-for-its-term.postgres.sql +52 -0
- package/sql/constraints.postgres.sql +10 -0
- package/sql/reference-schema.postgres.sql +91 -1
package/admin-api.openapi.yaml
CHANGED
|
@@ -29,7 +29,7 @@ openapi: 3.1.0
|
|
|
29
29
|
|
|
30
30
|
info:
|
|
31
31
|
title: SaaS Platform SuperAdmin API
|
|
32
|
-
version: 1.0.0-rc.
|
|
32
|
+
version: 1.0.0-rc.25
|
|
33
33
|
description: |
|
|
34
34
|
Read and write operations for platform administration:
|
|
35
35
|
tenants, users, subscriptions, promo codes, audit log,
|
|
@@ -387,6 +387,201 @@ paths:
|
|
|
387
387
|
'404':
|
|
388
388
|
description: No tenant with this slug (`TENANT_NOT_FOUND`)
|
|
389
389
|
|
|
390
|
+
/tenants/{slug}/subscriber:
|
|
391
|
+
parameters:
|
|
392
|
+
- $ref: '#/components/parameters/TenantSlug'
|
|
393
|
+
get:
|
|
394
|
+
tags: [subscriptions]
|
|
395
|
+
summary: The tenant's subscriber, its tax details and what holds its next contract back
|
|
396
|
+
description: >-
|
|
397
|
+
Served where the platform keeps subscribers and shows tenants
|
|
398
|
+
(`adminResources`); the manifest announces it as
|
|
399
|
+
`subscribers.read`. The standing is computed when it is read,
|
|
400
|
+
and only where a tax adapter decides; elsewhere `readiness` is
|
|
401
|
+
null.
|
|
402
|
+
responses:
|
|
403
|
+
'200':
|
|
404
|
+
description: The subscriber, or none
|
|
405
|
+
content:
|
|
406
|
+
application/json:
|
|
407
|
+
schema: { $ref: '#/components/schemas/AdminTenantSubscriber' }
|
|
408
|
+
'404':
|
|
409
|
+
description: No tenant with this slug (`TENANT_NOT_FOUND`)
|
|
410
|
+
|
|
411
|
+
/tenants/{slug}/subscriber/history:
|
|
412
|
+
parameters:
|
|
413
|
+
- $ref: '#/components/parameters/TenantSlug'
|
|
414
|
+
get:
|
|
415
|
+
tags: [subscriptions]
|
|
416
|
+
summary: Every correction, change of country or business status, and check of the subscriber, the latest first
|
|
417
|
+
description: >-
|
|
418
|
+
Served beside `/tenants/{slug}/subscriber`; the manifest
|
|
419
|
+
announces it as `subscribers.correct`. A correction that gives
|
|
420
|
+
another VAT number is named once, as the correction it was. Empty
|
|
421
|
+
for a tenant without a subscriber.
|
|
422
|
+
responses:
|
|
423
|
+
'200':
|
|
424
|
+
description: The history
|
|
425
|
+
content:
|
|
426
|
+
application/json:
|
|
427
|
+
schema:
|
|
428
|
+
type: object
|
|
429
|
+
required: [entries]
|
|
430
|
+
properties:
|
|
431
|
+
entries:
|
|
432
|
+
type: array
|
|
433
|
+
items:
|
|
434
|
+
$ref: '#/components/schemas/AdminSubscriberHistoryEntry'
|
|
435
|
+
'404':
|
|
436
|
+
description: No tenant with this slug (`TENANT_NOT_FOUND`)
|
|
437
|
+
|
|
438
|
+
/tenants/{slug}/subscriber/identity:
|
|
439
|
+
parameters:
|
|
440
|
+
- $ref: '#/components/parameters/TenantSlug'
|
|
441
|
+
post:
|
|
442
|
+
tags: [subscriptions]
|
|
443
|
+
summary: Correct the legal identity of the tenant's subscriber
|
|
444
|
+
description: >-
|
|
445
|
+
A correction of the same legal entity — a field left out keeps
|
|
446
|
+
its value, `null` clears a tax identifier — recorded with the
|
|
447
|
+
values it replaced, the reason and who made it. `kind` is the
|
|
448
|
+
operator's declaration: a takeover by another entity is refused
|
|
449
|
+
(`SUBSCRIBER_TAKEOVER_IS_A_TRANSFER`). Where a tax adapter
|
|
450
|
+
decides and the correction gives another VAT number, that
|
|
451
|
+
number is checked right after and the check recorded, whatever
|
|
452
|
+
it found; the answer says how it went.
|
|
453
|
+
x-mfa-required: true
|
|
454
|
+
requestBody:
|
|
455
|
+
required: true
|
|
456
|
+
content:
|
|
457
|
+
application/json:
|
|
458
|
+
schema:
|
|
459
|
+
type: object
|
|
460
|
+
required: [kind, reason]
|
|
461
|
+
properties:
|
|
462
|
+
kind: { type: string, enum: [correction, takeover] }
|
|
463
|
+
legalName: { type: string }
|
|
464
|
+
vatId: { type: string, nullable: true }
|
|
465
|
+
taxNumber: { type: string, nullable: true }
|
|
466
|
+
reason: { type: string, maxLength: 500 }
|
|
467
|
+
responses:
|
|
468
|
+
'200':
|
|
469
|
+
description: The subscriber as it now stands, and the check its new number got
|
|
470
|
+
content:
|
|
471
|
+
application/json:
|
|
472
|
+
schema: { $ref: '#/components/schemas/AdminSubscriberCorrected' }
|
|
473
|
+
'404':
|
|
474
|
+
description: No tenant with this slug (`TENANT_NOT_FOUND`)
|
|
475
|
+
'409':
|
|
476
|
+
description: The tenant has no subscriber (`SUBSCRIBER_REQUIRED`)
|
|
477
|
+
'422':
|
|
478
|
+
description: >-
|
|
479
|
+
A takeover, no reason, or a correction that changes
|
|
480
|
+
nothing (`SUBSCRIBER_TAKEOVER_IS_A_TRANSFER`,
|
|
481
|
+
`SUBSCRIBER_CORRECTION_REASON_REQUIRED`,
|
|
482
|
+
`SUBSCRIBER_CORRECTION_CHANGES_NOTHING`)
|
|
483
|
+
|
|
484
|
+
/tenants/{slug}/subscriber/business-status:
|
|
485
|
+
parameters:
|
|
486
|
+
- $ref: '#/components/parameters/TenantSlug'
|
|
487
|
+
post:
|
|
488
|
+
tags: [subscriptions]
|
|
489
|
+
summary: Record whether the tenant's subscriber acts as a business, and why
|
|
490
|
+
description: >-
|
|
491
|
+
Recorded as a change of the tax origin with its reason and who
|
|
492
|
+
made it; it applies from the next contract. Setting the status it
|
|
493
|
+
has records nothing.
|
|
494
|
+
x-mfa-required: true
|
|
495
|
+
requestBody:
|
|
496
|
+
required: true
|
|
497
|
+
content:
|
|
498
|
+
application/json:
|
|
499
|
+
schema:
|
|
500
|
+
type: object
|
|
501
|
+
required: [business, reason]
|
|
502
|
+
properties:
|
|
503
|
+
business: { type: boolean, nullable: true }
|
|
504
|
+
reason: { type: string, maxLength: 500 }
|
|
505
|
+
responses:
|
|
506
|
+
'200':
|
|
507
|
+
description: The subscriber as it now stands
|
|
508
|
+
content:
|
|
509
|
+
application/json:
|
|
510
|
+
schema: { $ref: '#/components/schemas/AdminSubscriberCorrected' }
|
|
511
|
+
'404':
|
|
512
|
+
description: No tenant with this slug (`TENANT_NOT_FOUND`)
|
|
513
|
+
'409':
|
|
514
|
+
description: The tenant has no subscriber (`SUBSCRIBER_REQUIRED`)
|
|
515
|
+
'422':
|
|
516
|
+
description: No reason (`SUBSCRIBER_BUSINESS_STATUS_REASON_REQUIRED`)
|
|
517
|
+
|
|
518
|
+
/tenants/{slug}/subscriber/vat-id-check:
|
|
519
|
+
parameters:
|
|
520
|
+
- $ref: '#/components/parameters/TenantSlug'
|
|
521
|
+
post:
|
|
522
|
+
tags: [subscriptions]
|
|
523
|
+
summary: Check the VAT number the tenant's subscriber holds, again
|
|
524
|
+
description: >-
|
|
525
|
+
Asks the service the tax adapter names and records a completed
|
|
526
|
+
check, valid or not; a check that does not complete records
|
|
527
|
+
nothing and says why. No second factor: nothing is entered, and
|
|
528
|
+
the answer is the service's.
|
|
529
|
+
responses:
|
|
530
|
+
'200':
|
|
531
|
+
description: The subscriber as it now stands, and what the check found
|
|
532
|
+
content:
|
|
533
|
+
application/json:
|
|
534
|
+
schema: { $ref: '#/components/schemas/AdminSubscriberCorrected' }
|
|
535
|
+
'404':
|
|
536
|
+
description: No tenant with this slug (`TENANT_NOT_FOUND`)
|
|
537
|
+
'409':
|
|
538
|
+
description: >-
|
|
539
|
+
The tenant has no subscriber, or no tax adapter names a
|
|
540
|
+
service (`SUBSCRIBER_REQUIRED`, `TAX_VAT_ID_CHECK_NOT_AVAILABLE`)
|
|
541
|
+
'422':
|
|
542
|
+
description: The subscriber holds no VAT number (`SUBSCRIBER_VAT_ID_MISSING`)
|
|
543
|
+
|
|
544
|
+
/subscribers/attention:
|
|
545
|
+
get:
|
|
546
|
+
tags: [subscriptions]
|
|
547
|
+
summary: Which of these tenants hold their subscriber back from its next contract
|
|
548
|
+
description: >-
|
|
549
|
+
Served beside `/tenants/{slug}/subscriber`; the manifest
|
|
550
|
+
announces `subscribers.attention` only where a tax adapter
|
|
551
|
+
decides, and without one the answer is always empty. A tenant
|
|
552
|
+
whose subscriber is ready, and one without a subscriber, are
|
|
553
|
+
left out.
|
|
554
|
+
parameters:
|
|
555
|
+
- name: tenantId
|
|
556
|
+
in: query
|
|
557
|
+
description: The tenants of the page shown; repeat the parameter for each.
|
|
558
|
+
schema:
|
|
559
|
+
type: array
|
|
560
|
+
maxItems: 200
|
|
561
|
+
items: { type: string, minLength: 1 }
|
|
562
|
+
style: form
|
|
563
|
+
explode: true
|
|
564
|
+
responses:
|
|
565
|
+
'200':
|
|
566
|
+
description: The tenants held back, each with why
|
|
567
|
+
content:
|
|
568
|
+
application/json:
|
|
569
|
+
schema:
|
|
570
|
+
type: object
|
|
571
|
+
required: [attention]
|
|
572
|
+
properties:
|
|
573
|
+
attention:
|
|
574
|
+
type: array
|
|
575
|
+
items:
|
|
576
|
+
type: object
|
|
577
|
+
required: [tenantId, readiness]
|
|
578
|
+
properties:
|
|
579
|
+
tenantId: { type: string }
|
|
580
|
+
readiness:
|
|
581
|
+
$ref: '#/components/schemas/SubscriberReadiness'
|
|
582
|
+
'400':
|
|
583
|
+
description: More than 200 tenants, or an empty tenant id
|
|
584
|
+
|
|
390
585
|
# ────────────────────────────────────────────────────────
|
|
391
586
|
# Users
|
|
392
587
|
# ────────────────────────────────────────────────────────
|
|
@@ -812,8 +1007,9 @@ paths:
|
|
|
812
1007
|
id: { type: string }
|
|
813
1008
|
tenant:
|
|
814
1009
|
type: object
|
|
815
|
-
required: [slug, name]
|
|
1010
|
+
required: [id, slug, name]
|
|
816
1011
|
properties:
|
|
1012
|
+
id: { type: string }
|
|
817
1013
|
slug: { type: string }
|
|
818
1014
|
name: { type: string }
|
|
819
1015
|
plan: { type: string }
|
|
@@ -1084,6 +1280,80 @@ paths:
|
|
|
1084
1280
|
type: array
|
|
1085
1281
|
items: { $ref: '#/components/schemas/VersionRetirement' }
|
|
1086
1282
|
|
|
1283
|
+
# Served where the manifest carries the capability `bundleVersions.retire`. The bookings
|
|
1284
|
+
# on an add-on version no longer on sale continue on a version of the same add-on on
|
|
1285
|
+
# sale, each at the first end of its own period at least three calendar months after
|
|
1286
|
+
# its notice reached the subscriber, and may be cancelled without their minimum term
|
|
1287
|
+
# until then. Where the operator's terms are not confirmed to allow it, both routes
|
|
1288
|
+
# refuse with `RETIREMENT_TERMS_NOT_CONFIRMED` before reading anything.
|
|
1289
|
+
|
|
1290
|
+
/catalog/bundle-versions/{id}/retirement:
|
|
1291
|
+
parameters: [{ name: id, in: path, required: true, schema: { type: string } }]
|
|
1292
|
+
get:
|
|
1293
|
+
tags: [catalog]
|
|
1294
|
+
summary: What retiring the add-on version for its running bookings would do now
|
|
1295
|
+
parameters:
|
|
1296
|
+
- name: replacement
|
|
1297
|
+
in: query
|
|
1298
|
+
required: true
|
|
1299
|
+
description: The version of the same add-on the bookings continue on
|
|
1300
|
+
schema: { type: string, format: uuid }
|
|
1301
|
+
responses:
|
|
1302
|
+
'200':
|
|
1303
|
+
description: >-
|
|
1304
|
+
Which bookings it reaches and when, which it does not and why, and every
|
|
1305
|
+
blocker the announcement would refuse with
|
|
1306
|
+
content:
|
|
1307
|
+
application/json:
|
|
1308
|
+
schema: { $ref: '#/components/schemas/BundleRetirementPreview' }
|
|
1309
|
+
'404':
|
|
1310
|
+
description: Either version does not exist (`BUNDLE_VERSION_NOT_FOUND`)
|
|
1311
|
+
'422':
|
|
1312
|
+
description: >-
|
|
1313
|
+
The terms are not confirmed (`RETIREMENT_TERMS_NOT_CONFIRMED`), or the
|
|
1314
|
+
replacement is the version itself (`RETIREMENT_REPLACEMENT_IS_RETIRED`)
|
|
1315
|
+
post:
|
|
1316
|
+
tags: [catalog]
|
|
1317
|
+
summary: Announce the add-on retirement to every booking it reaches
|
|
1318
|
+
description: >-
|
|
1319
|
+
The announcement and one notice per booking are recorded in one transaction,
|
|
1320
|
+
then handed to the application's `SubscriptionNoticePort` as
|
|
1321
|
+
`bundle-version-retired`; a notice that cannot be sent now is sent by the next
|
|
1322
|
+
run. Audited as BUNDLE_VERSION_RETIRE.
|
|
1323
|
+
x-mfa-required: true
|
|
1324
|
+
requestBody:
|
|
1325
|
+
required: true
|
|
1326
|
+
content:
|
|
1327
|
+
application/json:
|
|
1328
|
+
schema: { $ref: '#/components/schemas/BundleRetirementAnnouncement' }
|
|
1329
|
+
responses:
|
|
1330
|
+
'201':
|
|
1331
|
+
description: Announced
|
|
1332
|
+
content:
|
|
1333
|
+
application/json:
|
|
1334
|
+
schema: { $ref: '#/components/schemas/BundleRetirementAnnounced' }
|
|
1335
|
+
'409':
|
|
1336
|
+
description: >-
|
|
1337
|
+
The bookings it reaches are no longer the ones named
|
|
1338
|
+
(`RETIREMENT_PREVIEW_CHANGED`); the body carries the preview as it stands
|
|
1339
|
+
'422':
|
|
1340
|
+
description: >-
|
|
1341
|
+
The first blocker the preview reports (`BUNDLE_RETIREMENT_*`,
|
|
1342
|
+
`RETIREMENT_WITHIN_TWELVE_MONTHS`), with every blocker beside it
|
|
1343
|
+
|
|
1344
|
+
/catalog/bundle-version-retirements:
|
|
1345
|
+
get:
|
|
1346
|
+
tags: [catalog]
|
|
1347
|
+
summary: Every add-on retirement announced, the most recent first
|
|
1348
|
+
responses:
|
|
1349
|
+
'200':
|
|
1350
|
+
description: List, each with how far it has come over the bookings it reached
|
|
1351
|
+
content:
|
|
1352
|
+
application/json:
|
|
1353
|
+
schema:
|
|
1354
|
+
type: array
|
|
1355
|
+
items: { $ref: '#/components/schemas/BundleVersionRetirement' }
|
|
1356
|
+
|
|
1087
1357
|
# ── Bundles ──
|
|
1088
1358
|
/catalog/bundles:
|
|
1089
1359
|
get:
|
|
@@ -2114,6 +2384,151 @@ components:
|
|
|
2114
2384
|
told: { type: integer, description: 'Notices sent at once' }
|
|
2115
2385
|
failed: { type: integer, description: 'Notices the next run sends' }
|
|
2116
2386
|
|
|
2387
|
+
RetiredBundleVersionRef:
|
|
2388
|
+
type: object
|
|
2389
|
+
required: [bundleVersionId, bundleKey, version]
|
|
2390
|
+
properties:
|
|
2391
|
+
bundleVersionId: { type: string }
|
|
2392
|
+
bundleKey: { type: string }
|
|
2393
|
+
version: { type: integer }
|
|
2394
|
+
|
|
2395
|
+
BundleVersionSide:
|
|
2396
|
+
description: >-
|
|
2397
|
+
An add-on version as the subscribers compare it, at its own prices; each booking
|
|
2398
|
+
is told the prices beside its plan. A price of null is a rhythm it is not sold in
|
|
2399
|
+
allOf:
|
|
2400
|
+
- $ref: '#/components/schemas/RetiredBundleVersionRef'
|
|
2401
|
+
- type: object
|
|
2402
|
+
required: [label, features, quotas, monthlyNet, yearlyNet]
|
|
2403
|
+
properties:
|
|
2404
|
+
label: { type: string }
|
|
2405
|
+
features: { type: array, items: { type: string } }
|
|
2406
|
+
quotas: { type: object, additionalProperties: { type: number } }
|
|
2407
|
+
monthlyNet: { type: [number, 'null'] }
|
|
2408
|
+
yearlyNet: { type: [number, 'null'] }
|
|
2409
|
+
|
|
2410
|
+
BundleRetirementPreview:
|
|
2411
|
+
type: object
|
|
2412
|
+
required: [retired, replacement, changes, asOf, reached, skipped, blockers]
|
|
2413
|
+
properties:
|
|
2414
|
+
retired: { $ref: '#/components/schemas/BundleVersionSide' }
|
|
2415
|
+
replacement: { $ref: '#/components/schemas/BundleVersionSide' }
|
|
2416
|
+
changes:
|
|
2417
|
+
type: array
|
|
2418
|
+
description: 'Every difference at the two versions own prices'
|
|
2419
|
+
items: { type: object }
|
|
2420
|
+
asOf: { type: string, description: 'ISO-8601: the moment it is computed for' }
|
|
2421
|
+
reached:
|
|
2422
|
+
type: array
|
|
2423
|
+
items:
|
|
2424
|
+
type: object
|
|
2425
|
+
required:
|
|
2426
|
+
[
|
|
2427
|
+
tenantId,
|
|
2428
|
+
subscriptionId,
|
|
2429
|
+
subscriptionBundleId,
|
|
2430
|
+
planKey,
|
|
2431
|
+
planCycle,
|
|
2432
|
+
billingCycle,
|
|
2433
|
+
effectiveAt,
|
|
2434
|
+
lastDayToCancel,
|
|
2435
|
+
reachedRecently,
|
|
2436
|
+
]
|
|
2437
|
+
properties:
|
|
2438
|
+
tenantId: { type: string }
|
|
2439
|
+
subscriptionId: { type: string }
|
|
2440
|
+
subscriptionBundleId: { type: string }
|
|
2441
|
+
planKey: { type: string, description: 'The plan beside it at the date' }
|
|
2442
|
+
planCycle: { type: string }
|
|
2443
|
+
billingCycle: { type: string, description: 'The booking rhythm' }
|
|
2444
|
+
effectiveAt:
|
|
2445
|
+
{ type: string, description: 'ISO-8601: an end of its period' }
|
|
2446
|
+
lastDayToCancel:
|
|
2447
|
+
{ type: string, description: 'ISO date, UTC: the day before' }
|
|
2448
|
+
reachedRecently:
|
|
2449
|
+
{
|
|
2450
|
+
type: boolean,
|
|
2451
|
+
description: 'Its subscription was reached by a retirement within twelve months',
|
|
2452
|
+
}
|
|
2453
|
+
skipped:
|
|
2454
|
+
type: array
|
|
2455
|
+
items:
|
|
2456
|
+
type: object
|
|
2457
|
+
required: [tenantId, subscriptionId, subscriptionBundleId, reason]
|
|
2458
|
+
properties:
|
|
2459
|
+
tenantId: { type: string }
|
|
2460
|
+
subscriptionId: { type: string }
|
|
2461
|
+
subscriptionBundleId: { type: string }
|
|
2462
|
+
reason:
|
|
2463
|
+
{
|
|
2464
|
+
type: string,
|
|
2465
|
+
enum:
|
|
2466
|
+
[
|
|
2467
|
+
ended,
|
|
2468
|
+
cancelled-before,
|
|
2469
|
+
changes-before,
|
|
2470
|
+
no-term,
|
|
2471
|
+
already-told,
|
|
2472
|
+
],
|
|
2473
|
+
}
|
|
2474
|
+
blockers:
|
|
2475
|
+
type: array
|
|
2476
|
+
description: 'Empty where it may be announced as it stands'
|
|
2477
|
+
items:
|
|
2478
|
+
type: object
|
|
2479
|
+
required: [code, message, params]
|
|
2480
|
+
properties:
|
|
2481
|
+
code: { type: string, description: 'BUNDLE_RETIREMENT_*, RETIREMENT_*' }
|
|
2482
|
+
message: { type: string }
|
|
2483
|
+
params: { type: object }
|
|
2484
|
+
|
|
2485
|
+
BundleRetirementAnnouncement:
|
|
2486
|
+
type: object
|
|
2487
|
+
required: [replacementBundleVersionId, subscriptionBundleIds]
|
|
2488
|
+
properties:
|
|
2489
|
+
replacementBundleVersionId: { type: string, format: uuid }
|
|
2490
|
+
subscriptionBundleIds:
|
|
2491
|
+
type: array
|
|
2492
|
+
description: 'The bookings the operator was shown in the preview'
|
|
2493
|
+
items: { type: string }
|
|
2494
|
+
|
|
2495
|
+
BundleVersionRetirementRecord:
|
|
2496
|
+
type: object
|
|
2497
|
+
required: [id, retired, replacement, announcedAt, announcedBy]
|
|
2498
|
+
properties:
|
|
2499
|
+
id: { type: string }
|
|
2500
|
+
retired: { $ref: '#/components/schemas/RetiredBundleVersionRef' }
|
|
2501
|
+
replacement: { $ref: '#/components/schemas/RetiredBundleVersionRef' }
|
|
2502
|
+
announcedAt: { type: string }
|
|
2503
|
+
announcedBy: { type: string }
|
|
2504
|
+
|
|
2505
|
+
BundleVersionRetirement:
|
|
2506
|
+
description: 'An add-on announcement as the list answers it, with how far it has come'
|
|
2507
|
+
allOf:
|
|
2508
|
+
- $ref: '#/components/schemas/BundleVersionRetirementRecord'
|
|
2509
|
+
- type: object
|
|
2510
|
+
required: [progress]
|
|
2511
|
+
properties:
|
|
2512
|
+
progress:
|
|
2513
|
+
type: object
|
|
2514
|
+
description: 'Counted over the bookings it reached'
|
|
2515
|
+
required: [moved, waiting, overdue, ended, notTold, reminded]
|
|
2516
|
+
properties:
|
|
2517
|
+
moved: { type: integer }
|
|
2518
|
+
waiting: { type: integer }
|
|
2519
|
+
overdue: { type: integer }
|
|
2520
|
+
ended: { type: integer }
|
|
2521
|
+
notTold: { type: integer }
|
|
2522
|
+
reminded: { type: integer }
|
|
2523
|
+
|
|
2524
|
+
BundleRetirementAnnounced:
|
|
2525
|
+
type: object
|
|
2526
|
+
required: [retirement, told, failed]
|
|
2527
|
+
properties:
|
|
2528
|
+
retirement: { $ref: '#/components/schemas/BundleVersionRetirementRecord' }
|
|
2529
|
+
told: { type: integer, description: 'Notices sent at once' }
|
|
2530
|
+
failed: { type: integer, description: 'Notices the next run sends' }
|
|
2531
|
+
|
|
2117
2532
|
CatalogMarketingProjectionUpdate:
|
|
2118
2533
|
type: object
|
|
2119
2534
|
description: 'Partial update of a marketing projection'
|
|
@@ -2432,6 +2847,139 @@ components:
|
|
|
2432
2847
|
contract was concluded; null where that line
|
|
2433
2848
|
cannot be read.
|
|
2434
2849
|
|
|
2850
|
+
SubscriberReadiness:
|
|
2851
|
+
type: object
|
|
2852
|
+
description: >-
|
|
2853
|
+
Whether a subscriber can be given its next contract where a tax
|
|
2854
|
+
adapter decides: the empty fields of the address an invoice
|
|
2855
|
+
names, and the adapter's sentence where it supports no
|
|
2856
|
+
treatment for the subscriber as it stands.
|
|
2857
|
+
required: [ready, missing, taxRefusal]
|
|
2858
|
+
properties:
|
|
2859
|
+
ready: { type: boolean }
|
|
2860
|
+
missing:
|
|
2861
|
+
type: array
|
|
2862
|
+
items:
|
|
2863
|
+
type: string
|
|
2864
|
+
enum: [addressLine1, postalCode, city, country]
|
|
2865
|
+
taxRefusal:
|
|
2866
|
+
type: string
|
|
2867
|
+
nullable: true
|
|
2868
|
+
|
|
2869
|
+
AdminTenantSubscriber:
|
|
2870
|
+
type: object
|
|
2871
|
+
required: [subscriber, readiness]
|
|
2872
|
+
properties:
|
|
2873
|
+
subscriber:
|
|
2874
|
+
type: object
|
|
2875
|
+
nullable: true
|
|
2876
|
+
description: Null for a tenant that has no subscriber.
|
|
2877
|
+
required:
|
|
2878
|
+
[
|
|
2879
|
+
id,
|
|
2880
|
+
customerNumber,
|
|
2881
|
+
legalName,
|
|
2882
|
+
addressLine1,
|
|
2883
|
+
addressLine2,
|
|
2884
|
+
postalCode,
|
|
2885
|
+
city,
|
|
2886
|
+
country,
|
|
2887
|
+
business,
|
|
2888
|
+
vatId,
|
|
2889
|
+
vatIdValidated,
|
|
2890
|
+
taxNumber,
|
|
2891
|
+
migrated,
|
|
2892
|
+
]
|
|
2893
|
+
properties:
|
|
2894
|
+
id: { type: string }
|
|
2895
|
+
customerNumber: { type: string }
|
|
2896
|
+
legalName: { type: string }
|
|
2897
|
+
addressLine1: { type: string, nullable: true }
|
|
2898
|
+
addressLine2: { type: string, nullable: true }
|
|
2899
|
+
postalCode: { type: string, nullable: true }
|
|
2900
|
+
city: { type: string, nullable: true }
|
|
2901
|
+
country: { type: string, nullable: true }
|
|
2902
|
+
business: { type: boolean, nullable: true }
|
|
2903
|
+
vatId: { type: string, nullable: true }
|
|
2904
|
+
vatIdValidated:
|
|
2905
|
+
type: boolean
|
|
2906
|
+
description: Whether the check that counts for the VAT id found it valid.
|
|
2907
|
+
taxNumber: { type: string, nullable: true }
|
|
2908
|
+
migrated:
|
|
2909
|
+
type: boolean
|
|
2910
|
+
description: Created from the application's own tenant record.
|
|
2911
|
+
readiness:
|
|
2912
|
+
nullable: true
|
|
2913
|
+
description: Null where no tax adapter decides, or the tenant has no subscriber.
|
|
2914
|
+
allOf:
|
|
2915
|
+
- $ref: '#/components/schemas/SubscriberReadiness'
|
|
2916
|
+
|
|
2917
|
+
AdminVatIdCheckOutcome:
|
|
2918
|
+
description: >-
|
|
2919
|
+
A check of a VAT number as the operator is shown it — what the
|
|
2920
|
+
service found, or why the check did not complete.
|
|
2921
|
+
oneOf:
|
|
2922
|
+
- type: object
|
|
2923
|
+
required: [completed, valid, checkedAt, service, counts]
|
|
2924
|
+
properties:
|
|
2925
|
+
completed: { type: boolean, enum: [true] }
|
|
2926
|
+
valid: { type: boolean }
|
|
2927
|
+
checkedAt: { type: string, format: date-time }
|
|
2928
|
+
service: { type: string }
|
|
2929
|
+
counts:
|
|
2930
|
+
type: boolean
|
|
2931
|
+
description: Whether it is the check that counts for the number held now.
|
|
2932
|
+
- type: object
|
|
2933
|
+
required: [completed, reason]
|
|
2934
|
+
properties:
|
|
2935
|
+
completed: { type: boolean, enum: [false] }
|
|
2936
|
+
reason: { type: string }
|
|
2937
|
+
|
|
2938
|
+
AdminSubscriberCorrected:
|
|
2939
|
+
type: object
|
|
2940
|
+
required: [subscriber, vatIdCheck]
|
|
2941
|
+
properties:
|
|
2942
|
+
subscriber: { $ref: '#/components/schemas/AdminTenantSubscriber' }
|
|
2943
|
+
vatIdCheck:
|
|
2944
|
+
nullable: true
|
|
2945
|
+
description: The check a VAT number got, where one was made.
|
|
2946
|
+
allOf:
|
|
2947
|
+
- $ref: '#/components/schemas/AdminVatIdCheckOutcome'
|
|
2948
|
+
|
|
2949
|
+
AdminSubscriberHistoryEntry:
|
|
2950
|
+
description: One correction, change of the tax origin, or check of the VAT number.
|
|
2951
|
+
oneOf:
|
|
2952
|
+
- type: object
|
|
2953
|
+
required: [kind, at, by, previous, corrected, reason]
|
|
2954
|
+
properties:
|
|
2955
|
+
kind: { type: string, enum: [identity-corrected] }
|
|
2956
|
+
at: { type: string, format: date-time }
|
|
2957
|
+
by: { type: string }
|
|
2958
|
+
previous: { type: object, additionalProperties: true }
|
|
2959
|
+
corrected: { type: object, additionalProperties: true }
|
|
2960
|
+
reason: { type: string }
|
|
2961
|
+
- type: object
|
|
2962
|
+
required: [kind, at, by, previous, changed, reason]
|
|
2963
|
+
properties:
|
|
2964
|
+
kind: { type: string, enum: [tax-origin-changed] }
|
|
2965
|
+
at: { type: string, format: date-time }
|
|
2966
|
+
by: { type: string }
|
|
2967
|
+
previous: { type: object, additionalProperties: true }
|
|
2968
|
+
changed: { type: object, additionalProperties: true }
|
|
2969
|
+
reason:
|
|
2970
|
+
type: string
|
|
2971
|
+
nullable: true
|
|
2972
|
+
description: Null for a change of the country with the contact details.
|
|
2973
|
+
- type: object
|
|
2974
|
+
required: [kind, at, vatId, valid, service, counts]
|
|
2975
|
+
properties:
|
|
2976
|
+
kind: { type: string, enum: [vat-id-checked] }
|
|
2977
|
+
at: { type: string, format: date-time }
|
|
2978
|
+
vatId: { type: string }
|
|
2979
|
+
valid: { type: boolean }
|
|
2980
|
+
service: { type: string }
|
|
2981
|
+
counts: { type: boolean }
|
|
2982
|
+
|
|
2435
2983
|
# ────────── User ──────────
|
|
2436
2984
|
AdminUserRow:
|
|
2437
2985
|
type: object
|
package/package.json
CHANGED
|
@@ -90,9 +90,10 @@ model BundleVersion {
|
|
|
90
90
|
bundle Bundle @relation(fields: [bundleId], references: [id], onDelete: Cascade)
|
|
91
91
|
baseVersion BundleVersion? @relation("BundleVersionLineage", fields: [baseVersionId], references: [id])
|
|
92
92
|
derivedVersions BundleVersion[] @relation("BundleVersionLineage")
|
|
93
|
-
// Standalone add-on bookings pinned to this version
|
|
94
|
-
// 11-subscription-bundle.prisma).
|
|
95
|
-
subscriptionBundles
|
|
93
|
+
// Standalone add-on bookings pinned to this version, and the ones set to
|
|
94
|
+
// continue on it at the end of their term (see 11-subscription-bundle.prisma).
|
|
95
|
+
subscriptionBundles SubscriptionBundle[] @relation("SubscriptionBundleVersion")
|
|
96
|
+
subscriptionBundlesPending SubscriptionBundle[] @relation("SubscriptionBundlePendingVersion")
|
|
96
97
|
|
|
97
98
|
@@unique([bundleId, version])
|
|
98
99
|
@@index([bundleId, supersededAt])
|
|
@@ -47,6 +47,13 @@ model SubscriptionContract {
|
|
|
47
47
|
// what was agreed.
|
|
48
48
|
partiesMigrated Boolean @default(false)
|
|
49
49
|
|
|
50
|
+
// The tax treatment the installation's tax adapter decided when the
|
|
51
|
+
// contract was concluded: its kind, rate and note, with the adapter's name
|
|
52
|
+
// and version (ADR 0013). Null where no adapter was asked — an installation
|
|
53
|
+
// without one, a contract from before, or a path that does not ask one
|
|
54
|
+
// yet; each invoice then takes the adapter's answer when it is issued.
|
|
55
|
+
taxTreatment Json?
|
|
56
|
+
|
|
50
57
|
status SubscriptionContractStatus @default(active)
|
|
51
58
|
effectiveFrom DateTime
|
|
52
59
|
effectiveUntil DateTime?
|
|
@@ -81,6 +81,12 @@ model PendingRegistration {
|
|
|
81
81
|
country String? // ISO 3166-1 alpha-2
|
|
82
82
|
vatId String?
|
|
83
83
|
taxNumber String?
|
|
84
|
+
// Whether the sign-up is a business, which a tax adapter decides from; and
|
|
85
|
+
// the check of `vatId` made in step 4, outside any transaction, where the
|
|
86
|
+
// adapter needed a validated number. The subscriber takes the check over
|
|
87
|
+
// when it is created, so its first contract is decided from it.
|
|
88
|
+
business Boolean?
|
|
89
|
+
vatIdCheck Json?
|
|
84
90
|
|
|
85
91
|
// The payment gateway's session for the payment method. A session id is
|
|
86
92
|
// unique only within the gateway account that opened it, so the account
|
|
@@ -46,6 +46,14 @@ model SubscriptionBundle {
|
|
|
46
46
|
currentPeriodStart DateTime?
|
|
47
47
|
currentPeriodEnd DateTime?
|
|
48
48
|
|
|
49
|
+
// A switch to a newer version of the add-on that takes something away,
|
|
50
|
+
// taken for the end of the booking's running term: the version it continues
|
|
51
|
+
// on, and from when. Both null, or both set — `constraints.postgres.sql`
|
|
52
|
+
// holds them to that. The platform's quarter-hourly run makes the switch at
|
|
53
|
+
// that moment and clears both.
|
|
54
|
+
pendingBundleVersionId String?
|
|
55
|
+
pendingVersionEffectiveAt DateTime?
|
|
56
|
+
|
|
49
57
|
// Cancellation: requested at `canceledAt`, effective at
|
|
50
58
|
// `canceledEffectiveAt` (period/term end). Both null = active booking.
|
|
51
59
|
canceledAt DateTime?
|
|
@@ -56,11 +64,13 @@ model SubscriptionBundle {
|
|
|
56
64
|
|
|
57
65
|
// The subscription this add-on is booked on. Restrict the BundleVersion so
|
|
58
66
|
// a booked version cannot be hard-deleted from the catalog.
|
|
59
|
-
subscription
|
|
60
|
-
bundleVersion
|
|
67
|
+
subscription Subscription @relation(fields: [subscriptionId], references: [id], onDelete: Cascade)
|
|
68
|
+
bundleVersion BundleVersion @relation("SubscriptionBundleVersion", fields: [bundleVersionId], references: [id], onDelete: Restrict)
|
|
69
|
+
pendingBundleVersion BundleVersion? @relation("SubscriptionBundlePendingVersion", fields: [pendingBundleVersionId], references: [id], onDelete: Restrict)
|
|
61
70
|
|
|
62
71
|
@@index([subscriptionId])
|
|
63
72
|
@@index([bundleVersionId])
|
|
64
73
|
@@index([canceledEffectiveAt])
|
|
74
|
+
@@index([pendingVersionEffectiveAt])
|
|
65
75
|
@@map("subscription_bundles")
|
|
66
76
|
}
|