Migration invoices : uuid id, organization_id FK CASCADE, client_id FK RESTRICT (on n'efface pas les factures si l'utilisateur supprime un client par erreur — audit/comptable), plan_id FK SET NULL, numero, amount_ttc_cents (int, jamais float), issue_date, due_date, status ENUM PG natif (pending/awaiting_user_confirmation/in_relance/paid/litigation/cancelled), pdf_storage_key, notes, rubis_earned, paid_at. Indexes (org,status), (org,client_id), (org,due_date), unique (org,numero). Modèles : Invoice avec belongsTo Organization/Client/Plan. Client et Plan étendus avec hasMany Invoice maintenant que la table existe. Endpoints : - GET /invoices : filtres status/q/clientId/page, tri actionnable (awaiting_user_confirmation puis in_relance puis pending puis litigation puis paid puis cancelled), pagination simple 50/page (cursor-based en V2). - GET /invoices/counts : compteurs par statut pour les chips dashboard, requête agrégée groupBy. - GET /invoices/:id : détail enrichi avec client + plan préchargés + timeline composée par buildTimeline() (étapes du plan calées sur due_date, états past/current/future). - POST /invoices : saisie manuelle. Résolution client en 3 étapes (clientId → match par nom → création à la volée avec email REQUIS, sinon 422 client_email_required). Bonus +1 rubis à la création. - POST /invoices/:id/mark-paid : status=paid + paid_at + bonus +1 rubis (sur invoice + sur organization.rubis_count). Idempotent. L'ordre des routes /invoices/counts AVANT /invoices/:id est critique sinon `:id` matche "counts". Branche les vraies stats : - ClientStats : agrégation PG une seule requête (count, count actives, count en retard, paid_count, sum paid_cents, sum pending_cents, last_activity) avec FILTER clauses et casting enum::text. Plus de TODO/zéros. - PlansController : usageCount calculé pareil (factures actives référençant le plan). Skip pour l'instant (ImportBatch domain à venir) : POST /invoices/upload, GET /invoices/import-batch/*, validate/skip drafts.
182 lines
5.5 KiB
TypeScript
182 lines
5.5 KiB
TypeScript
/**
|
|
* This file is automatically generated
|
|
* DO NOT EDIT manually
|
|
* Run "node ace migration:run" command to re-generate this file
|
|
*/
|
|
|
|
import { BaseModel, column } from '@adonisjs/lucid/orm'
|
|
import { DateTime } from 'luxon'
|
|
|
|
export class AuthAccessTokenSchema extends BaseModel {
|
|
static $columns = ['abilities', 'createdAt', 'expiresAt', 'hash', 'id', 'lastUsedAt', 'name', 'tokenableId', 'type', 'updatedAt'] as const
|
|
$columns = AuthAccessTokenSchema.$columns
|
|
@column()
|
|
declare abilities: string
|
|
@column.dateTime({ autoCreate: true })
|
|
declare createdAt: DateTime | null
|
|
@column.dateTime()
|
|
declare expiresAt: DateTime | null
|
|
@column()
|
|
declare hash: string
|
|
@column({ isPrimary: true })
|
|
declare id: string
|
|
@column.dateTime()
|
|
declare lastUsedAt: DateTime | null
|
|
@column()
|
|
declare name: string | null
|
|
@column()
|
|
declare tokenableId: string
|
|
@column()
|
|
declare type: string
|
|
@column.dateTime({ autoCreate: true, autoUpdate: true })
|
|
declare updatedAt: DateTime | null
|
|
}
|
|
|
|
export class ClientSchema extends BaseModel {
|
|
static $columns = ['address', 'createdAt', 'email', 'id', 'name', 'notes', 'organizationId', 'phone', 'siret', 'updatedAt'] as const
|
|
$columns = ClientSchema.$columns
|
|
@column()
|
|
declare address: string | null
|
|
@column.dateTime({ autoCreate: true })
|
|
declare createdAt: DateTime
|
|
@column()
|
|
declare email: string
|
|
@column({ isPrimary: true })
|
|
declare id: string
|
|
@column()
|
|
declare name: string
|
|
@column()
|
|
declare notes: string | null
|
|
@column()
|
|
declare organizationId: string
|
|
@column()
|
|
declare phone: string | null
|
|
@column()
|
|
declare siret: string | null
|
|
@column.dateTime({ autoCreate: true, autoUpdate: true })
|
|
declare updatedAt: DateTime | null
|
|
}
|
|
|
|
export class InvoiceSchema extends BaseModel {
|
|
static $columns = ['amountTtcCents', 'clientId', 'createdAt', 'dueDate', 'id', 'issueDate', 'notes', 'numero', 'organizationId', 'paidAt', 'pdfStorageKey', 'planId', 'rubisEarned', 'status', 'updatedAt'] as const
|
|
$columns = InvoiceSchema.$columns
|
|
@column()
|
|
declare amountTtcCents: number
|
|
@column()
|
|
declare clientId: string
|
|
@column.dateTime({ autoCreate: true })
|
|
declare createdAt: DateTime
|
|
@column.dateTime()
|
|
declare dueDate: DateTime
|
|
@column({ isPrimary: true })
|
|
declare id: string
|
|
@column.dateTime()
|
|
declare issueDate: DateTime
|
|
@column()
|
|
declare notes: string | null
|
|
@column()
|
|
declare numero: string
|
|
@column()
|
|
declare organizationId: string
|
|
@column.dateTime()
|
|
declare paidAt: DateTime | null
|
|
@column()
|
|
declare pdfStorageKey: string | null
|
|
@column()
|
|
declare planId: string | null
|
|
@column()
|
|
declare rubisEarned: number
|
|
@column()
|
|
declare status: 'pending' | 'awaiting_user_confirmation' | 'in_relance' | 'paid' | 'litigation' | 'cancelled'
|
|
@column.dateTime({ autoCreate: true, autoUpdate: true })
|
|
declare updatedAt: DateTime | null
|
|
}
|
|
|
|
export class OrganizationSchema extends BaseModel {
|
|
static $columns = ['createdAt', 'id', 'monthlyVolumeBucket', 'name', 'onboardingCompletedAt', 'rubisCount', 'siret', 'updatedAt'] as const
|
|
$columns = OrganizationSchema.$columns
|
|
@column.dateTime({ autoCreate: true })
|
|
declare createdAt: DateTime
|
|
@column({ isPrimary: true })
|
|
declare id: string
|
|
@column()
|
|
declare monthlyVolumeBucket: string | null
|
|
@column()
|
|
declare name: string
|
|
@column.dateTime()
|
|
declare onboardingCompletedAt: DateTime | null
|
|
@column()
|
|
declare rubisCount: number
|
|
@column()
|
|
declare siret: string | null
|
|
@column.dateTime({ autoCreate: true, autoUpdate: true })
|
|
declare updatedAt: DateTime | null
|
|
}
|
|
|
|
export class PlanStepSchema extends BaseModel {
|
|
static $columns = ['body', 'createdAt', 'id', 'offsetDays', 'order', 'planId', 'requiresManualValidation', 'subject', 'tone', 'updatedAt'] as const
|
|
$columns = PlanStepSchema.$columns
|
|
@column()
|
|
declare body: string
|
|
@column.dateTime({ autoCreate: true })
|
|
declare createdAt: DateTime
|
|
@column({ isPrimary: true })
|
|
declare id: string
|
|
@column()
|
|
declare offsetDays: number
|
|
@column()
|
|
declare order: number
|
|
@column()
|
|
declare planId: string
|
|
@column()
|
|
declare requiresManualValidation: boolean
|
|
@column()
|
|
declare subject: string
|
|
@column()
|
|
declare tone: 'amical' | 'courtois' | 'ferme' | 'mise_en_demeure'
|
|
@column.dateTime({ autoCreate: true, autoUpdate: true })
|
|
declare updatedAt: DateTime | null
|
|
}
|
|
|
|
export class PlanSchema extends BaseModel {
|
|
static $columns = ['createdAt', 'description', 'id', 'isDefault', 'name', 'organizationId', 'slug', 'updatedAt'] as const
|
|
$columns = PlanSchema.$columns
|
|
@column.dateTime({ autoCreate: true })
|
|
declare createdAt: DateTime
|
|
@column()
|
|
declare description: string
|
|
@column({ isPrimary: true })
|
|
declare id: string
|
|
@column()
|
|
declare isDefault: boolean
|
|
@column()
|
|
declare name: string
|
|
@column()
|
|
declare organizationId: string
|
|
@column()
|
|
declare slug: string | null
|
|
@column.dateTime({ autoCreate: true, autoUpdate: true })
|
|
declare updatedAt: DateTime | null
|
|
}
|
|
|
|
export class UserSchema extends BaseModel {
|
|
static $columns = ['createdAt', 'email', 'fullName', 'id', 'organizationId', 'password', 'signature', 'updatedAt'] as const
|
|
$columns = UserSchema.$columns
|
|
@column.dateTime({ autoCreate: true })
|
|
declare createdAt: DateTime
|
|
@column()
|
|
declare email: string
|
|
@column()
|
|
declare fullName: string | null
|
|
@column({ isPrimary: true })
|
|
declare id: string
|
|
@column()
|
|
declare organizationId: string | null
|
|
@column({ serializeAs: null })
|
|
declare password: string
|
|
@column()
|
|
declare signature: string | null
|
|
@column.dateTime({ autoCreate: true, autoUpdate: true })
|
|
declare updatedAt: DateTime | null
|
|
}
|