135 lines
4.3 KiB
TypeScript
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."
|
|
)
|
|
}
|
|
}
|