Abo: individuelle Angebote (z. B. erste 2 Monate je 25 EUR) mit Stripe-Coupon
- Admin-Tab "Abo-Angebote" und Button "Angebot" in der Kundenliste: Preis, Monate, Gueltigkeit, Hinweis fuer den Kunden, interne Notiz, Vorschau vor dem Anlegen - Je Angebot sofort ein Stripe-Coupon (amount_off, repeating ueber die Angebotsmonate, max. 1 Einloesung); danach automatisch wieder Normalpreis - Kunde ohne Abo: Angebot im Portal, Angebotspreis in der Bestelluebersicht, Coupon im Checkout; Angebot schaltet die Bestellung auch ausserhalb der Pilotliste frei; Bestellung prueft, dass das angezeigte Angebot noch gilt - Laufendes Abo: Coupon sofort am Abo fuer die naechsten Abbuchungen - Testkonten: Testrabatt im Checkout, Angebot per Webhook zusaetzlich am Abo - Angebot in Abo-Metadaten, Vertragsbestaetigung, Admin-Mail und Abo-Karte - Zurueckziehen loescht den Coupon; abgelaufene Angebote nicht mehr bestellbar - Stundenkontingent nach invoice.subtotal: auch voll rabattierte oder per Guthaben bezahlte Monate bringen die Stunde, die Probezeit nicht - setup-abo-collections.mjs: aboOffers, subscriptions.offerId/offerText, Attribut-Abfrage mit Limit (sonst Haenger ab 26 Attributen) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
48b13f4488
commit
bb2f46e191
11 files changed
+856
-30
No files matched your search
@@ -1,8 +1,8 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Wartungs-Abo: legt die Collections subscriptions, hourCredits und takeoverCredits
|
||||
* (Uebernahmegutschriften) in woms-database an und ergaenzt worksheets um das Flag
|
||||
* countsToPlan ("zaehlt aufs Abo-Stundenkonto").
|
||||
* Wartungs-Abo: legt die Collections subscriptions, hourCredits, takeoverCredits
|
||||
* (Uebernahmegutschriften) und aboOffers (individuelle Angebote) in woms-database an und
|
||||
* ergaenzt worksheets um das Flag countsToPlan ("zaehlt aufs Abo-Stundenkonto").
|
||||
* Idempotent: existierende Collection/Attribute/Indexe werden uebersprungen.
|
||||
* Usage (im Container): docker exec -w /app webklar-kundenbereich-server node scripts/setup-abo-collections.mjs
|
||||
*/
|
||||
@@ -49,8 +49,10 @@ async function ensure(label, fn) {
|
||||
}
|
||||
|
||||
async function waitForAttributes(collectionId, keys, maxSec = 60) {
|
||||
// Ohne Limit liefert Appwrite nur 25 Attribute -> spaetere Attribute waeren nie "verfuegbar"
|
||||
const limit = encodeURIComponent(JSON.stringify({ method: 'limit', values: [100] }))
|
||||
for (let i = 0; i < maxSec; i += 1) {
|
||||
const { attributes } = await aw(`/databases/${databaseId}/collections/${collectionId}/attributes`)
|
||||
const { attributes } = await aw(`/databases/${databaseId}/collections/${collectionId}/attributes?queries[]=${limit}`)
|
||||
const pending = keys.filter((k) => attributes.find((a) => a.key === k)?.status !== 'available')
|
||||
if (!pending.length) return
|
||||
await new Promise((r) => setTimeout(r, 1000))
|
||||
@@ -112,6 +114,9 @@ await ensureCollection(
|
||||
{ type: 'string', key: 'consentTextVersion', size: 32 },
|
||||
// v3: Beendigungszeitpunkt (Mindestlaufzeit / Wunschtermin)
|
||||
{ type: 'datetime', key: 'cancelAt' },
|
||||
// Individuelles Angebot, mit dem das Abo abgeschlossen wurde (Nachweis der Preisabrede)
|
||||
{ type: 'string', key: 'offerId', size: 64 },
|
||||
{ type: 'string', key: 'offerText', size: 300 },
|
||||
],
|
||||
[
|
||||
{ key: 'idx_customer', type: 'key', attributes: ['customerId'] },
|
||||
@@ -173,6 +178,34 @@ await ensureCollection(
|
||||
]
|
||||
)
|
||||
|
||||
// Individuelle Abo-Angebote: Admin legt vorab fest (z. B. erste 2 Monate je 25 €), Stripe-Coupon je Angebot
|
||||
await ensureCollection(
|
||||
'aboOffers',
|
||||
'Abo-Angebote',
|
||||
[
|
||||
{ type: 'string', key: 'customerId', size: 64 },
|
||||
{ type: 'string', key: 'status', size: 20 },
|
||||
{ type: 'string', key: 'scope', size: 16 },
|
||||
{ type: 'integer', key: 'monthlyPriceCents' },
|
||||
{ type: 'integer', key: 'regularPriceCents' },
|
||||
{ type: 'integer', key: 'months' },
|
||||
{ type: 'datetime', key: 'validUntil' },
|
||||
{ type: 'string', key: 'note', size: 500 },
|
||||
{ type: 'string', key: 'internalNote', size: 2000 },
|
||||
{ type: 'string', key: 'stripeCouponId', size: 64 },
|
||||
{ type: 'string', key: 'stripeSubscriptionId', size: 64 },
|
||||
{ type: 'datetime', key: 'appliedAt' },
|
||||
{ type: 'string', key: 'createdBy', size: 255 },
|
||||
{ type: 'datetime', key: 'withdrawnAt' },
|
||||
{ type: 'string', key: 'withdrawnBy', size: 255 },
|
||||
{ type: 'datetime', key: 'notifiedAt' },
|
||||
],
|
||||
[
|
||||
{ key: 'idx_customer', type: 'key', attributes: ['customerId'] },
|
||||
{ key: 'idx_status', type: 'key', attributes: ['status'] },
|
||||
]
|
||||
)
|
||||
|
||||
// Worksheet-Flag: nur markierte Arbeitszeit wird vom Abo-Guthaben abgezogen
|
||||
await ensure('worksheets.countsToPlan', () =>
|
||||
aw(`${col('worksheets')}/attributes/boolean`, {
|
||||
|
||||
Reference in new issue
Block a user