rubis/apps/api/commands/demo_schedule_relance.ts
ordinarthur 1633fb9bf0
All checks were successful
Build & Deploy Web / build-and-deploy (push) Successful in 59s
Build & Deploy API / build-and-deploy (push) Successful in 1m37s
add factories
2026-05-07 11:34:00 +02:00

135 lines
4.3 KiB
TypeScript

import { BaseCommand, flags } from '@adonisjs/core/ace'
import type { CommandOptions } from '@adonisjs/core/types/ace'
import { DateTime } from 'luxon'
import User from '#models/user'
import Invoice from '#models/invoice'
import RelanceTask from '#models/relance_task'
/**
* Programme une RelanceTask à une date arbitraire pour la démo.
*
* node ace demo:schedule-relance --email arthurbarre.js@gmail.com --date 2026-05-09
* node ace demo:schedule-relance --email ... --date 2026-05-09 --hour 14
* node ace demo:schedule-relance --email ... --date 2026-05-09 --invoice F-2026-0042
*
* Logique :
* - Trouve une facture active (pending / in_relance / awaiting) avec un plan
* - Pioche le premier step du plan (amical en général) — sinon `--step-order N`
* - Crée une RelanceTask `scheduled` à la date demandée
*
* Utilise l'horloge virtuelle si la démo est active (compare-toi à la date
* que tu vois sur l'horloge top-right).
*/
export default class DemoScheduleRelance extends BaseCommand {
static commandName = 'demo:schedule-relance'
static description = 'Programme une RelanceTask à une date arbitraire (démo)'
static options: CommandOptions = {
startApp: true,
}
@flags.string({ description: 'Email du user', required: true })
declare email: string
@flags.string({ description: 'Date YYYY-MM-DD', required: true })
declare date: string
@flags.number({ description: 'Heure d\'envoi (0-23)', default: 9 })
declare hour: number
@flags.string({
description: 'Numéro de facture spécifique (sinon : 1re active trouvée)',
})
declare invoice?: string
@flags.number({
description: 'Order du step plan à utiliser (0 = premier)',
default: 0,
})
declare stepOrder: number
async run() {
const user = await User.findBy('email', this.email.toLowerCase())
if (!user || !user.organizationId) {
this.logger.error(`User introuvable ou sans org : ${this.email}`)
this.exitCode = 1
return
}
let invoiceQuery = Invoice.query()
.where('organization_id', user.organizationId)
.preload('plan', (q) => q.preload('steps'))
.preload('client')
.whereIn('status', ['pending', 'in_relance', 'awaiting_user_confirmation'])
.orderBy('due_date', 'asc')
if (this.invoice) {
invoiceQuery = invoiceQuery.where('numero', this.invoice)
}
const invoice = await invoiceQuery.first()
if (!invoice) {
this.logger.error(
this.invoice
? `Facture ${this.invoice} non trouvée ou inactive.`
: 'Aucune facture active (pending/in_relance/awaiting) dans cette org.'
)
this.exitCode = 1
return
}
if (!invoice.plan?.steps?.length) {
this.logger.error(
`Facture ${invoice.numero} sans plan ou plan sans étapes — assignez un plan d'abord.`
)
this.exitCode = 1
return
}
const sortedSteps = invoice.plan.steps.slice().sort((a, b) => a.order - b.order)
const step = sortedSteps[this.stepOrder]
if (!step) {
this.logger.error(
`Step order ${this.stepOrder} introuvable. Plan a ${sortedSteps.length} étape(s).`
)
this.exitCode = 1
return
}
const sendAt = DateTime.fromISO(
`${this.date}T${String(this.hour).padStart(2, '0')}:00:00.000`,
{ zone: 'utc' }
)
if (!sendAt.isValid) {
this.logger.error(
`Date invalide : "${this.date}" (attendu YYYY-MM-DD), heure ${this.hour}.`
)
this.exitCode = 1
return
}
const task = await RelanceTask.create({
organizationId: user.organizationId,
invoiceId: invoice.id,
planStepId: step.id,
sendAt,
status: 'scheduled',
sentAt: null,
queueJobId: null,
})
this.logger.success('Relance programmée pour la démo :')
this.logger.info(` · facture : ${invoice.numero}${invoice.client.name}`)
this.logger.info(
` · step : J${step.offsetDays >= 0 ? '+' : ''}${step.offsetDays} (${step.tone}) — "${step.subject}"`
)
this.logger.info(` · sendAt : ${sendAt.toFormat('cccc dd LLLL yyyy HH:mm')} UTC`)
this.logger.info(` · task id : ${task.id}`)
this.logger.info('')
this.logger.info(
"→ En mode démo, l'horloge déclenchera l'envoi quand virtualNow ≥ sendAt."
)
}
}