billing invoice
This commit is contained in:
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -10,9 +10,11 @@ import {
|
||||
companiesQuerySchema, rentersQuerySchema, auditLogQuerySchema, billingQuerySchema,
|
||||
invoicesQuerySchema, adminCompanyUpdateSchema, companyStatusSchema,
|
||||
createAdminSchema, adminRoleSchema, adminPermissionsSchema,
|
||||
homepageUpdateSchema, idParamSchema, companyIdParamSchema, invoiceIdParamSchema,
|
||||
homepageUpdateSchema, idParamSchema, companyIdParamSchema, billingAccountIdParamSchema, invoiceIdParamSchema,
|
||||
pricingUpdateSchema, planFeatureCreateSchema, planFeatureUpdateSchema, planFeatureIdParamSchema,
|
||||
promotionCreateSchema, promotionUpdateSchema, promotionIdParamSchema,
|
||||
billingAccountUpdateSchema, createBillingInvoiceSchema, payBillingInvoiceSchema,
|
||||
retryBillingInvoiceSchema, billingReasonSchema, billingCreditNoteSchema, billingRefundSchema,
|
||||
} from './admin.schemas'
|
||||
import { z } from 'zod'
|
||||
|
||||
@@ -203,6 +205,24 @@ router.get('/billing', requireAdminAuth, requireAdminRole('FINANCE'), async (req
|
||||
} catch (err) { next(err) }
|
||||
})
|
||||
|
||||
router.get('/billing/invoices/:invoiceId/pdf', requireAdminAuth, requireAdminRole('FINANCE'), async (req, res, next) => {
|
||||
try {
|
||||
const { invoiceId } = parseParams(invoiceIdParamSchema, req)
|
||||
const { pdfBuffer, invoiceNumber } = await service.getInvoicePdf(invoiceId)
|
||||
res.setHeader('Content-Type', 'application/pdf')
|
||||
res.setHeader('Content-Disposition', `attachment; filename="${invoiceNumber}.pdf"`)
|
||||
res.setHeader('Content-Length', pdfBuffer.length)
|
||||
res.end(pdfBuffer)
|
||||
} catch (err) { next(err) }
|
||||
})
|
||||
|
||||
router.get('/billing/:companyId', requireAdminAuth, requireAdminRole('FINANCE'), async (req, res, next) => {
|
||||
try {
|
||||
const { companyId } = parseParams(companyIdParamSchema, req)
|
||||
ok(res, await service.getBillingAccountDetail(companyId))
|
||||
} catch (err) { next(err) }
|
||||
})
|
||||
|
||||
router.get('/billing/:companyId/invoices', requireAdminAuth, requireAdminRole('FINANCE'), async (req, res, next) => {
|
||||
try {
|
||||
const { companyId } = parseParams(companyIdParamSchema, req)
|
||||
@@ -210,14 +230,82 @@ router.get('/billing/:companyId/invoices', requireAdminAuth, requireAdminRole('F
|
||||
} catch (err) { next(err) }
|
||||
})
|
||||
|
||||
router.get('/billing/invoices/:invoiceId/pdf', requireAdminAuth, requireAdminRole('FINANCE'), async (req, res, next) => {
|
||||
router.patch('/billing/accounts/:billingAccountId', requireAdminAuth, requireAdminRole('FINANCE'), async (req, res, next) => {
|
||||
try {
|
||||
const { invoiceId } = parseParams(invoiceIdParamSchema, req)
|
||||
const { pdfBuffer, invoiceNumber } = await service.getInvoicePdf(invoiceId)
|
||||
res.setHeader('Content-Type', 'application/pdf')
|
||||
res.setHeader('Content-Disposition', `attachment; filename="${invoiceNumber}.pdf"`)
|
||||
res.setHeader('Content-Length', pdfBuffer.length)
|
||||
res.end(pdfBuffer)
|
||||
const { billingAccountId } = parseParams(billingAccountIdParamSchema, req)
|
||||
ok(res, await service.updateBillingAccount(billingAccountId, parseBody(billingAccountUpdateSchema, req), req.admin.id, req.ip))
|
||||
} catch (err) { next(err) }
|
||||
})
|
||||
|
||||
router.post('/billing/accounts/:billingAccountId/pause-dunning', requireAdminAuth, requireAdminRole('FINANCE'), async (req, res, next) => {
|
||||
try {
|
||||
const { billingAccountId } = parseParams(billingAccountIdParamSchema, req)
|
||||
ok(res, await service.setDunningPaused(billingAccountId, true, req.admin.id, req.ip))
|
||||
} catch (err) { next(err) }
|
||||
})
|
||||
|
||||
router.post('/billing/accounts/:billingAccountId/resume-dunning', requireAdminAuth, requireAdminRole('FINANCE'), async (req, res, next) => {
|
||||
try {
|
||||
const { billingAccountId } = parseParams(billingAccountIdParamSchema, req)
|
||||
ok(res, await service.setDunningPaused(billingAccountId, false, req.admin.id, req.ip))
|
||||
} catch (err) { next(err) }
|
||||
})
|
||||
|
||||
router.post('/billing/accounts/:billingAccountId/invoices', requireAdminAuth, requireAdminRole('FINANCE'), async (req, res, next) => {
|
||||
try {
|
||||
const { billingAccountId } = parseParams(billingAccountIdParamSchema, req)
|
||||
created(res, await service.createBillingInvoice(billingAccountId, parseBody(createBillingInvoiceSchema, req), req.admin.id, req.ip))
|
||||
} catch (err) { next(err) }
|
||||
})
|
||||
|
||||
router.post('/billing/invoices/:invoiceId/finalize', requireAdminAuth, requireAdminRole('FINANCE'), async (req, res, next) => {
|
||||
try {
|
||||
const { invoiceId } = parseParams(invoiceIdParamSchema, req)
|
||||
ok(res, await service.finalizeBillingInvoice(invoiceId, req.admin.id, req.ip))
|
||||
} catch (err) { next(err) }
|
||||
})
|
||||
|
||||
router.post('/billing/invoices/:invoiceId/pay', requireAdminAuth, requireAdminRole('FINANCE'), async (req, res, next) => {
|
||||
try {
|
||||
const { invoiceId } = parseParams(invoiceIdParamSchema, req)
|
||||
ok(res, await service.payBillingInvoice(invoiceId, parseBody(payBillingInvoiceSchema, req), req.admin.id, req.ip))
|
||||
} catch (err) { next(err) }
|
||||
})
|
||||
|
||||
router.post('/billing/invoices/:invoiceId/retry-payment', requireAdminAuth, requireAdminRole('FINANCE'), async (req, res, next) => {
|
||||
try {
|
||||
const { invoiceId } = parseParams(invoiceIdParamSchema, req)
|
||||
ok(res, await service.retryBillingInvoicePayment(invoiceId, parseBody(retryBillingInvoiceSchema, req), req.admin.id, req.ip))
|
||||
} catch (err) { next(err) }
|
||||
})
|
||||
|
||||
router.post('/billing/invoices/:invoiceId/void', requireAdminAuth, requireAdminRole('FINANCE'), async (req, res, next) => {
|
||||
try {
|
||||
const { invoiceId } = parseParams(invoiceIdParamSchema, req)
|
||||
const { reason } = parseBody(billingReasonSchema, req)
|
||||
ok(res, await service.voidBillingInvoice(invoiceId, reason, req.admin.id, req.ip))
|
||||
} catch (err) { next(err) }
|
||||
})
|
||||
|
||||
router.post('/billing/invoices/:invoiceId/uncollectible', requireAdminAuth, requireAdminRole('FINANCE'), async (req, res, next) => {
|
||||
try {
|
||||
const { invoiceId } = parseParams(invoiceIdParamSchema, req)
|
||||
const { reason } = parseBody(billingReasonSchema, req)
|
||||
ok(res, await service.markBillingInvoiceUncollectible(invoiceId, reason, req.admin.id, req.ip))
|
||||
} catch (err) { next(err) }
|
||||
})
|
||||
|
||||
router.post('/billing/invoices/:invoiceId/credit-notes', requireAdminAuth, requireAdminRole('FINANCE'), async (req, res, next) => {
|
||||
try {
|
||||
const { invoiceId } = parseParams(invoiceIdParamSchema, req)
|
||||
ok(res, await service.issueBillingCreditNote(invoiceId, parseBody(billingCreditNoteSchema, req), req.admin.id, req.ip))
|
||||
} catch (err) { next(err) }
|
||||
})
|
||||
|
||||
router.post('/billing/invoices/:invoiceId/refunds', requireAdminAuth, requireAdminRole('FINANCE'), async (req, res, next) => {
|
||||
try {
|
||||
const { invoiceId } = parseParams(invoiceIdParamSchema, req)
|
||||
ok(res, await service.issueBillingRefund(invoiceId, parseBody(billingRefundSchema, req), req.admin.id, req.ip))
|
||||
} catch (err) { next(err) }
|
||||
})
|
||||
|
||||
|
||||
@@ -45,6 +45,7 @@ export const auditLogQuerySchema = z.object({
|
||||
})
|
||||
|
||||
export const billingQuerySchema = z.object({
|
||||
q: z.string().optional(),
|
||||
status: z.string().optional(),
|
||||
plan: z.string().optional(),
|
||||
page: z.coerce.number().int().min(1).default(1),
|
||||
@@ -167,10 +168,91 @@ export const companyIdParamSchema = z.object({
|
||||
companyId: z.string().min(1),
|
||||
})
|
||||
|
||||
export const billingAccountIdParamSchema = z.object({
|
||||
billingAccountId: z.string().min(1),
|
||||
})
|
||||
|
||||
export const invoiceIdParamSchema = z.object({
|
||||
invoiceId: z.string().min(1),
|
||||
})
|
||||
|
||||
export const billingAccountUpdateSchema = z.object({
|
||||
legalName: z.string().min(1).max(255).optional(),
|
||||
billingEmail: z.string().email().optional(),
|
||||
billingAddress: z.any().optional(),
|
||||
taxId: z.union([z.string().max(120), z.null()]).optional(),
|
||||
taxExempt: z.boolean().optional(),
|
||||
invoiceTerms: z.enum(['DUE_ON_RECEIPT', 'NET_7', 'NET_15', 'NET_30', 'NET_45', 'NET_60']).optional(),
|
||||
netTermsDays: z.number().int().min(0).max(365).optional(),
|
||||
})
|
||||
|
||||
export const billingLineItemInputSchema = z.object({
|
||||
type: z.enum([
|
||||
'SUBSCRIPTION_FEE',
|
||||
'SEAT_FEE',
|
||||
'USAGE_FEE',
|
||||
'SETUP_FEE',
|
||||
'DISCOUNT',
|
||||
'TAX',
|
||||
'CREDIT',
|
||||
'PRORATION',
|
||||
'REFUND_ADJUSTMENT',
|
||||
'MANUAL_ADJUSTMENT',
|
||||
'PROFESSIONAL_SERVICES',
|
||||
'CANCELLATION_FEE',
|
||||
]),
|
||||
description: z.string().min(1).max(255),
|
||||
quantity: z.number().int().positive().max(100000),
|
||||
unitAmount: z.number().int(),
|
||||
periodStart: nullableDate,
|
||||
periodEnd: nullableDate,
|
||||
})
|
||||
|
||||
export const createBillingInvoiceSchema = z.object({
|
||||
subscriptionId: z.union([z.string(), z.null()]).optional(),
|
||||
invoiceType: z.enum([
|
||||
'SUBSCRIPTION_INITIAL',
|
||||
'SUBSCRIPTION_RENEWAL',
|
||||
'TRIAL_CONVERSION',
|
||||
'SUBSCRIPTION_UPGRADE',
|
||||
'SUBSCRIPTION_DOWNGRADE_CREDIT',
|
||||
'USAGE_BASED',
|
||||
'ONE_TIME',
|
||||
'MANUAL',
|
||||
'CORRECTION',
|
||||
'CANCELLATION_FEE',
|
||||
]),
|
||||
currency: z.string().min(3).max(8).optional(),
|
||||
dueAt: nullableDate,
|
||||
isSubscriptionBlocking: z.boolean().optional(),
|
||||
adminReason: z.union([z.string().max(500), z.null()]).optional(),
|
||||
lineItems: z.array(billingLineItemInputSchema).min(1),
|
||||
})
|
||||
|
||||
export const payBillingInvoiceSchema = z.object({
|
||||
amount: z.number().int().positive().optional(),
|
||||
paymentMethodId: z.union([z.string(), z.null()]).optional(),
|
||||
providerPaymentId: z.union([z.string(), z.null()]).optional(),
|
||||
})
|
||||
|
||||
export const retryBillingInvoiceSchema = z.object({
|
||||
paymentMethodId: z.union([z.string(), z.null()]).optional(),
|
||||
})
|
||||
|
||||
export const billingReasonSchema = z.object({
|
||||
reason: z.string().min(1).max(500),
|
||||
})
|
||||
|
||||
export const billingCreditNoteSchema = z.object({
|
||||
amount: z.number().int().positive(),
|
||||
reason: z.string().min(1).max(500),
|
||||
})
|
||||
|
||||
export const billingRefundSchema = z.object({
|
||||
amount: z.number().int().positive(),
|
||||
reason: z.string().min(1).max(500),
|
||||
})
|
||||
|
||||
export const homepageUpdateSchema = z.object({
|
||||
homepage: marketplaceHomepageConfigSchema,
|
||||
})
|
||||
|
||||
@@ -4,19 +4,13 @@ import crypto from 'crypto'
|
||||
import { authenticator } from 'otplib'
|
||||
import qrcode from 'qrcode'
|
||||
import { getMarketplaceHomepageContent, saveMarketplaceHomepageContent } from '../../services/platformContentService'
|
||||
import { generateInvoicePdf, buildInvoiceNumber } from '../../services/invoicePdfService'
|
||||
import { sendTransactionalEmail } from '../../services/notificationService'
|
||||
import * as presenter from './admin.presenter'
|
||||
import * as repo from './admin.repo'
|
||||
import * as billingService from './admin.billing.service'
|
||||
|
||||
const ADMIN_RESET_TTL_MINUTES = 60
|
||||
|
||||
const PLAN_MONTHLY_AMOUNT: Record<string, number> = {
|
||||
STARTER: 2990,
|
||||
GROWTH: 3990,
|
||||
PRO: 99900,
|
||||
}
|
||||
|
||||
function signAdminToken(adminId: string) {
|
||||
return jwt.sign({ sub: adminId, type: 'admin' }, process.env.JWT_SECRET!, { expiresIn: '8h' })
|
||||
}
|
||||
@@ -226,58 +220,96 @@ export async function updateAdminPermissions(id: string, permissions: any[]) {
|
||||
return presenter.presentAdminUser(admin)
|
||||
}
|
||||
|
||||
export async function getBilling(query: { status?: string; plan?: string; page: number; pageSize: number }) {
|
||||
const [{ data, total }, activeSubscriptions, stats] = await Promise.all([
|
||||
repo.listBillingPage(query),
|
||||
repo.listActiveSubscriptionsForMrr(),
|
||||
repo.getBillingStatusCounts(),
|
||||
])
|
||||
|
||||
const mrr = activeSubscriptions.reduce((acc, subscription) => {
|
||||
const monthlyAmount = PLAN_MONTHLY_AMOUNT[subscription.plan] ?? 0
|
||||
return acc + (subscription.billingPeriod === 'ANNUAL' ? Math.round(monthlyAmount * 10 / 12) : monthlyAmount)
|
||||
}, 0)
|
||||
|
||||
return presenter.presentPaginated(data, total, query.page, query.pageSize, {
|
||||
stats: { mrr, ...stats },
|
||||
})
|
||||
export async function getBilling(query: { q?: string; status?: string; plan?: string; page: number; pageSize: number }) {
|
||||
const { data, total, stats } = await billingService.listBillingAccounts(query)
|
||||
return presenter.presentPaginated(data, total, query.page, query.pageSize, { stats })
|
||||
}
|
||||
|
||||
export async function getCompanyInvoices(companyId: string, query: { page: number; pageSize: number }) {
|
||||
const { data, total } = await repo.listCompanyInvoicesPage(companyId, query)
|
||||
const detail = await billingService.getBillingAccountDetail(companyId)
|
||||
const start = (query.page - 1) * query.pageSize
|
||||
const end = start + query.pageSize
|
||||
const data = detail.invoices.slice(start, end)
|
||||
const total = detail.invoices.length
|
||||
return presenter.presentPaginated(data, total, query.page, query.pageSize)
|
||||
}
|
||||
|
||||
export async function getInvoicePdf(invoiceId: string) {
|
||||
const invoice = await repo.getInvoicePdfRecord(invoiceId)
|
||||
const invoiceNumber = buildInvoiceNumber(invoice.id, invoice.createdAt)
|
||||
const transactionId = invoice.amanpayTransactionId ?? invoice.paypalCaptureId ?? null
|
||||
const pdfBuffer = await generateInvoicePdf({
|
||||
invoiceNumber,
|
||||
issueDate: invoice.createdAt.toISOString(),
|
||||
dueDate: invoice.status === 'PENDING' ? invoice.createdAt.toISOString() : null,
|
||||
company: {
|
||||
name: invoice.company.name,
|
||||
email: invoice.company.email,
|
||||
phone: invoice.company.phone,
|
||||
address: invoice.company.address,
|
||||
},
|
||||
subscription: {
|
||||
plan: invoice.subscription.plan,
|
||||
billingPeriod: invoice.subscription.billingPeriod,
|
||||
currentPeriodStart: invoice.subscription.currentPeriodStart?.toISOString(),
|
||||
currentPeriodEnd: invoice.subscription.currentPeriodEnd?.toISOString(),
|
||||
currency: invoice.subscription.currency,
|
||||
},
|
||||
amount: invoice.amount,
|
||||
currency: invoice.currency,
|
||||
status: invoice.status,
|
||||
paymentProvider: invoice.paymentProvider,
|
||||
transactionId,
|
||||
paidAt: invoice.paidAt?.toISOString(),
|
||||
})
|
||||
return billingService.getInvoicePdf(invoiceId)
|
||||
}
|
||||
|
||||
return { pdfBuffer, invoiceNumber }
|
||||
export function getBillingAccountDetail(companyId: string) {
|
||||
return billingService.getBillingAccountDetail(companyId)
|
||||
}
|
||||
|
||||
export function updateBillingAccount(
|
||||
billingAccountId: string,
|
||||
data: Parameters<typeof billingService.updateBillingAccount>[1],
|
||||
adminId: string,
|
||||
ip?: string,
|
||||
) {
|
||||
return billingService.updateBillingAccount(billingAccountId, data, adminId, ip)
|
||||
}
|
||||
|
||||
export function setDunningPaused(billingAccountId: string, paused: boolean, adminId: string, ip?: string) {
|
||||
return billingService.setDunningPaused(billingAccountId, paused, adminId, ip)
|
||||
}
|
||||
|
||||
export function createBillingInvoice(
|
||||
billingAccountId: string,
|
||||
data: Parameters<typeof billingService.createDraftInvoice>[1],
|
||||
adminId: string,
|
||||
ip?: string,
|
||||
) {
|
||||
return billingService.createDraftInvoice(billingAccountId, data, adminId, ip)
|
||||
}
|
||||
|
||||
export function finalizeBillingInvoice(invoiceId: string, adminId: string, ip?: string) {
|
||||
return billingService.finalizeInvoice(invoiceId, adminId, ip)
|
||||
}
|
||||
|
||||
export function payBillingInvoice(
|
||||
invoiceId: string,
|
||||
data: Parameters<typeof billingService.payInvoice>[1],
|
||||
adminId: string,
|
||||
ip?: string,
|
||||
) {
|
||||
return billingService.payInvoice(invoiceId, data, adminId, ip)
|
||||
}
|
||||
|
||||
export function retryBillingInvoicePayment(
|
||||
invoiceId: string,
|
||||
data: Parameters<typeof billingService.retryInvoicePayment>[1],
|
||||
adminId: string,
|
||||
ip?: string,
|
||||
) {
|
||||
return billingService.retryInvoicePayment(invoiceId, data, adminId, ip)
|
||||
}
|
||||
|
||||
export function voidBillingInvoice(invoiceId: string, reason: string, adminId: string, ip?: string) {
|
||||
return billingService.voidInvoice(invoiceId, reason, adminId, ip)
|
||||
}
|
||||
|
||||
export function markBillingInvoiceUncollectible(invoiceId: string, reason: string, adminId: string, ip?: string) {
|
||||
return billingService.markInvoiceUncollectible(invoiceId, reason, adminId, ip)
|
||||
}
|
||||
|
||||
export function issueBillingCreditNote(
|
||||
invoiceId: string,
|
||||
data: Parameters<typeof billingService.issueCreditNote>[1],
|
||||
adminId: string,
|
||||
ip?: string,
|
||||
) {
|
||||
return billingService.issueCreditNote(invoiceId, data, adminId, ip)
|
||||
}
|
||||
|
||||
export function issueBillingRefund(
|
||||
invoiceId: string,
|
||||
data: Parameters<typeof billingService.issueRefund>[1],
|
||||
adminId: string,
|
||||
ip?: string,
|
||||
) {
|
||||
return billingService.issueRefund(invoiceId, data, adminId, ip)
|
||||
}
|
||||
|
||||
export function getMarketplaceHomepage() {
|
||||
|
||||
@@ -11,7 +11,7 @@ interface InvoiceData {
|
||||
phone?: string | null
|
||||
address?: any
|
||||
}
|
||||
subscription: {
|
||||
subscription?: {
|
||||
plan: string
|
||||
billingPeriod: string
|
||||
currentPeriodStart?: string | null
|
||||
@@ -24,6 +24,24 @@ interface InvoiceData {
|
||||
paymentProvider: string
|
||||
transactionId?: string | null
|
||||
paidAt?: string | null
|
||||
lineItems?: Array<{
|
||||
description: string
|
||||
amount: number
|
||||
currency: string
|
||||
quantity?: number
|
||||
unitAmount?: number
|
||||
periodStart?: string | null
|
||||
periodEnd?: string | null
|
||||
}>
|
||||
totals?: {
|
||||
subtotalAmount: number
|
||||
discountAmount: number
|
||||
creditAmount: number
|
||||
taxAmount: number
|
||||
totalAmount: number
|
||||
amountPaid: number
|
||||
amountDue: number
|
||||
}
|
||||
}
|
||||
|
||||
const PLAN_LABEL: Record<string, string> = {
|
||||
@@ -40,8 +58,15 @@ const PERIOD_LABEL: Record<string, string> = {
|
||||
const STATUS_COLORS: Record<string, string> = {
|
||||
PAID: '#10b981',
|
||||
PENDING: '#f59e0b',
|
||||
OPEN: '#f59e0b',
|
||||
PAYMENT_PENDING: '#f59e0b',
|
||||
PARTIALLY_PAID: '#f59e0b',
|
||||
PAST_DUE: '#ef4444',
|
||||
FAILED: '#ef4444',
|
||||
REFUNDED: '#6b7280',
|
||||
PARTIALLY_REFUNDED: '#6b7280',
|
||||
VOID: '#6b7280',
|
||||
UNCOLLECTIBLE: '#6b7280',
|
||||
}
|
||||
|
||||
const colors = {
|
||||
@@ -307,12 +332,26 @@ function formatAddress(address: any): string {
|
||||
function InvoiceDocument({ data }: { data: InvoiceData }) {
|
||||
const statusColor = STATUS_COLORS[data.status] ?? '#6b7280'
|
||||
const addressStr = formatAddress(data.company.address)
|
||||
const planLabel = PLAN_LABEL[data.subscription.plan] ?? data.subscription.plan
|
||||
const periodLabel = PERIOD_LABEL[data.subscription.billingPeriod] ?? data.subscription.billingPeriod
|
||||
const description = `${planLabel} Plan — ${periodLabel} Subscription`
|
||||
const periodStr = data.subscription.currentPeriodStart && data.subscription.currentPeriodEnd
|
||||
? `${fmtDate(data.subscription.currentPeriodStart)} – ${fmtDate(data.subscription.currentPeriodEnd)}`
|
||||
: '—'
|
||||
const planLabel = data.subscription ? (PLAN_LABEL[data.subscription.plan] ?? data.subscription.plan) : 'Manual'
|
||||
const periodLabel = data.subscription ? (PERIOD_LABEL[data.subscription.billingPeriod] ?? data.subscription.billingPeriod) : 'Custom'
|
||||
const lineItems = data.lineItems?.length
|
||||
? data.lineItems
|
||||
: [{
|
||||
description: `${planLabel} Plan — ${periodLabel} Subscription`,
|
||||
amount: data.amount,
|
||||
currency: data.currency,
|
||||
periodStart: data.subscription?.currentPeriodStart ?? null,
|
||||
periodEnd: data.subscription?.currentPeriodEnd ?? null,
|
||||
}]
|
||||
const totals = data.totals ?? {
|
||||
subtotalAmount: data.amount,
|
||||
discountAmount: 0,
|
||||
creditAmount: 0,
|
||||
taxAmount: 0,
|
||||
totalAmount: data.amount,
|
||||
amountPaid: data.paidAt ? data.amount : 0,
|
||||
amountDue: data.paidAt ? 0 : data.amount,
|
||||
}
|
||||
|
||||
return React.createElement(
|
||||
Document,
|
||||
@@ -397,12 +436,14 @@ function InvoiceDocument({ data }: { data: InvoiceData }) {
|
||||
React.createElement(Text, { style: s.metaKey }, 'Billing Period'),
|
||||
React.createElement(Text, { style: s.metaValue }, periodLabel),
|
||||
),
|
||||
React.createElement(
|
||||
View,
|
||||
{ style: s.metaRow },
|
||||
React.createElement(Text, { style: s.metaKey }, 'Plan'),
|
||||
React.createElement(Text, { style: s.metaValue }, planLabel),
|
||||
),
|
||||
data.subscription
|
||||
? React.createElement(
|
||||
View,
|
||||
{ style: s.metaRow },
|
||||
React.createElement(Text, { style: s.metaKey }, 'Plan'),
|
||||
React.createElement(Text, { style: s.metaValue }, planLabel),
|
||||
)
|
||||
: null,
|
||||
),
|
||||
),
|
||||
|
||||
@@ -417,13 +458,21 @@ function InvoiceDocument({ data }: { data: InvoiceData }) {
|
||||
React.createElement(Text, { style: [s.tableHeaderCell, s.col20Center] }, 'Period'),
|
||||
React.createElement(Text, { style: [s.tableHeaderCell, s.col20] }, 'Amount'),
|
||||
),
|
||||
React.createElement(
|
||||
View,
|
||||
{ style: s.tableRow },
|
||||
React.createElement(Text, { style: [s.tableCell, s.col60] }, description),
|
||||
React.createElement(Text, { style: [s.tableCellMuted, s.col20Center, { textAlign: 'center' }] }, periodStr),
|
||||
React.createElement(Text, { style: [s.tableCell, s.col20, { textAlign: 'right' }] }, fmt(data.amount, data.currency)),
|
||||
),
|
||||
...lineItems.map((item) => {
|
||||
const periodStr = item.periodStart && item.periodEnd
|
||||
? `${fmtDate(item.periodStart)} – ${fmtDate(item.periodEnd)}`
|
||||
: '—'
|
||||
const description = item.quantity && item.quantity > 1 && item.unitAmount !== undefined
|
||||
? `${item.description} (${item.quantity} × ${fmt(item.unitAmount, item.currency)})`
|
||||
: item.description
|
||||
return React.createElement(
|
||||
View,
|
||||
{ key: `${item.description}-${item.amount}-${item.periodStart ?? ''}`, style: s.tableRow },
|
||||
React.createElement(Text, { style: [s.tableCell, s.col60] }, description),
|
||||
React.createElement(Text, { style: [s.tableCellMuted, s.col20Center, { textAlign: 'center' }] }, periodStr),
|
||||
React.createElement(Text, { style: [s.tableCell, s.col20, { textAlign: 'right' }] }, fmt(item.amount, item.currency)),
|
||||
)
|
||||
}),
|
||||
),
|
||||
|
||||
// ── Totals ───────────────────────────────────────────────────
|
||||
@@ -431,13 +480,37 @@ function InvoiceDocument({ data }: { data: InvoiceData }) {
|
||||
View,
|
||||
{ style: s.totalRow },
|
||||
React.createElement(Text, { style: s.totalLabel }, 'Subtotal'),
|
||||
React.createElement(Text, { style: s.totalValue }, fmt(data.amount, data.currency)),
|
||||
React.createElement(Text, { style: s.totalValue }, fmt(totals.subtotalAmount, data.currency)),
|
||||
),
|
||||
totals.discountAmount > 0
|
||||
? React.createElement(
|
||||
View,
|
||||
{ style: s.totalRow },
|
||||
React.createElement(Text, { style: s.totalLabel }, 'Discounts'),
|
||||
React.createElement(Text, { style: s.totalValue }, `- ${fmt(totals.discountAmount, data.currency)}`),
|
||||
)
|
||||
: null,
|
||||
totals.creditAmount > 0
|
||||
? React.createElement(
|
||||
View,
|
||||
{ style: s.totalRow },
|
||||
React.createElement(Text, { style: s.totalLabel }, 'Credits'),
|
||||
React.createElement(Text, { style: s.totalValue }, `- ${fmt(totals.creditAmount, data.currency)}`),
|
||||
)
|
||||
: null,
|
||||
totals.taxAmount > 0
|
||||
? React.createElement(
|
||||
View,
|
||||
{ style: s.totalRow },
|
||||
React.createElement(Text, { style: s.totalLabel }, 'Tax'),
|
||||
React.createElement(Text, { style: s.totalValue }, fmt(totals.taxAmount, data.currency)),
|
||||
)
|
||||
: null,
|
||||
React.createElement(
|
||||
View,
|
||||
{ style: s.grandTotalRow },
|
||||
React.createElement(Text, { style: s.grandTotalLabel }, 'Total Due'),
|
||||
React.createElement(Text, { style: s.grandTotalValue }, fmt(data.amount, data.currency)),
|
||||
React.createElement(Text, { style: s.grandTotalValue }, fmt(totals.totalAmount, data.currency)),
|
||||
),
|
||||
|
||||
// ── Payment Info ─────────────────────────────────────────────
|
||||
|
||||
@@ -136,4 +136,194 @@ describe('Admin API', () => {
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Billing admin workflows', () => {
|
||||
let billingAccountId: string
|
||||
|
||||
beforeAll(async () => {
|
||||
const res = await request(app)
|
||||
.get('/api/v1/admin/billing')
|
||||
.set(authHeader(financeToken))
|
||||
|
||||
expect(res.status).toBe(200)
|
||||
const account = res.body.data.data.find((item: any) => item.company.id === companyId)
|
||||
expect(account).toBeTruthy()
|
||||
billingAccountId = account.id
|
||||
})
|
||||
|
||||
it('returns billing account detail', async () => {
|
||||
const res = await request(app)
|
||||
.get(`/api/v1/admin/billing/${companyId}`)
|
||||
.set(authHeader(financeToken))
|
||||
|
||||
expect(res.status).toBe(200)
|
||||
expect(res.body.data.id).toBe(billingAccountId)
|
||||
expect(res.body.data.company.id).toBe(companyId)
|
||||
expect(Array.isArray(res.body.data.invoices)).toBe(true)
|
||||
})
|
||||
|
||||
it('supports create, finalize, pay, refund, and pdf download', async () => {
|
||||
const draftRes = await request(app)
|
||||
.post(`/api/v1/admin/billing/accounts/${billingAccountId}/invoices`)
|
||||
.set(authHeader(financeToken))
|
||||
.send({
|
||||
invoiceType: 'MANUAL',
|
||||
adminReason: 'Enterprise onboarding',
|
||||
lineItems: [
|
||||
{
|
||||
type: 'SETUP_FEE',
|
||||
description: 'Onboarding setup',
|
||||
quantity: 1,
|
||||
unitAmount: 50000,
|
||||
},
|
||||
{
|
||||
type: 'PROFESSIONAL_SERVICES',
|
||||
description: 'Migration assistance',
|
||||
quantity: 2,
|
||||
unitAmount: 15000,
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
expect(draftRes.status).toBe(201)
|
||||
expect(draftRes.body.data.status).toBe('DRAFT')
|
||||
const invoiceId = draftRes.body.data.id as string
|
||||
|
||||
const finalizeRes = await request(app)
|
||||
.post(`/api/v1/admin/billing/invoices/${invoiceId}/finalize`)
|
||||
.set(authHeader(financeToken))
|
||||
.send({})
|
||||
|
||||
expect(finalizeRes.status).toBe(200)
|
||||
expect(finalizeRes.body.data.status).toBe('OPEN')
|
||||
expect(finalizeRes.body.data.invoiceNumber).toMatch(/^INV-\d{4}-\d{6}$/)
|
||||
expect(finalizeRes.body.data.totalAmount).toBe(80000)
|
||||
|
||||
const pdfRes = await request(app)
|
||||
.get(`/api/v1/admin/billing/invoices/${invoiceId}/pdf`)
|
||||
.set(authHeader(financeToken))
|
||||
|
||||
expect(pdfRes.status).toBe(200)
|
||||
expect(pdfRes.headers['content-type']).toContain('application/pdf')
|
||||
|
||||
const payRes = await request(app)
|
||||
.post(`/api/v1/admin/billing/invoices/${invoiceId}/pay`)
|
||||
.set(authHeader(financeToken))
|
||||
.send({ amount: 80000, providerPaymentId: 'manual-payment-1' })
|
||||
|
||||
expect(payRes.status).toBe(200)
|
||||
expect(payRes.body.data.status).toBe('PAID')
|
||||
expect(payRes.body.data.amountDue).toBe(0)
|
||||
|
||||
const refundRes = await request(app)
|
||||
.post(`/api/v1/admin/billing/invoices/${invoiceId}/refunds`)
|
||||
.set(authHeader(financeToken))
|
||||
.send({ amount: 20000, reason: 'Service credit after go-live issue' })
|
||||
|
||||
expect(refundRes.status).toBe(200)
|
||||
expect(refundRes.body.data.status).toBe('PARTIALLY_REFUNDED')
|
||||
})
|
||||
|
||||
it('supports credit notes, dunning controls, voids, and write-offs', async () => {
|
||||
const pauseRes = await request(app)
|
||||
.post(`/api/v1/admin/billing/accounts/${billingAccountId}/pause-dunning`)
|
||||
.set(authHeader(financeToken))
|
||||
.send({})
|
||||
|
||||
expect(pauseRes.status).toBe(200)
|
||||
expect(pauseRes.body.data.dunningPaused).toBe(true)
|
||||
|
||||
const invoiceRes = await request(app)
|
||||
.post(`/api/v1/admin/billing/accounts/${billingAccountId}/invoices`)
|
||||
.set(authHeader(financeToken))
|
||||
.send({
|
||||
invoiceType: 'MANUAL',
|
||||
lineItems: [
|
||||
{
|
||||
type: 'MANUAL_ADJUSTMENT',
|
||||
description: 'Support package',
|
||||
quantity: 1,
|
||||
unitAmount: 30000,
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
expect(invoiceRes.status).toBe(201)
|
||||
const invoiceId = invoiceRes.body.data.id as string
|
||||
|
||||
const finalizeRes = await request(app)
|
||||
.post(`/api/v1/admin/billing/invoices/${invoiceId}/finalize`)
|
||||
.set(authHeader(financeToken))
|
||||
.send({})
|
||||
|
||||
expect(finalizeRes.status).toBe(200)
|
||||
expect(finalizeRes.body.data.status).toBe('OPEN')
|
||||
|
||||
const creditRes = await request(app)
|
||||
.post(`/api/v1/admin/billing/invoices/${invoiceId}/credit-notes`)
|
||||
.set(authHeader(financeToken))
|
||||
.send({ amount: 5000, reason: 'Manual goodwill credit' })
|
||||
|
||||
expect(creditRes.status).toBe(200)
|
||||
expect(creditRes.body.data.amountDue).toBe(25000)
|
||||
|
||||
const retryRes = await request(app)
|
||||
.post(`/api/v1/admin/billing/invoices/${invoiceId}/retry-payment`)
|
||||
.set(authHeader(financeToken))
|
||||
.send({})
|
||||
|
||||
expect(retryRes.status).toBe(200)
|
||||
expect(retryRes.body.data.status).toBe('PAYMENT_PENDING')
|
||||
|
||||
const voidRes = await request(app)
|
||||
.post(`/api/v1/admin/billing/invoices/${invoiceId}/void`)
|
||||
.set(authHeader(financeToken))
|
||||
.send({ reason: 'Customer rolled into replacement invoice' })
|
||||
|
||||
expect(voidRes.status).toBe(200)
|
||||
expect(voidRes.body.data.status).toBe('VOID')
|
||||
|
||||
const badDebtRes = await request(app)
|
||||
.post(`/api/v1/admin/billing/accounts/${billingAccountId}/invoices`)
|
||||
.set(authHeader(financeToken))
|
||||
.send({
|
||||
invoiceType: 'SUBSCRIPTION_RENEWAL',
|
||||
isSubscriptionBlocking: true,
|
||||
lineItems: [
|
||||
{
|
||||
type: 'SUBSCRIPTION_FEE',
|
||||
description: 'Renewal period',
|
||||
quantity: 1,
|
||||
unitAmount: 12000,
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
expect(badDebtRes.status).toBe(201)
|
||||
const badDebtInvoiceId = badDebtRes.body.data.id as string
|
||||
|
||||
const finalizeBadDebtRes = await request(app)
|
||||
.post(`/api/v1/admin/billing/invoices/${badDebtInvoiceId}/finalize`)
|
||||
.set(authHeader(financeToken))
|
||||
.send({})
|
||||
|
||||
expect(finalizeBadDebtRes.status).toBe(200)
|
||||
|
||||
const writeoffRes = await request(app)
|
||||
.post(`/api/v1/admin/billing/invoices/${badDebtInvoiceId}/uncollectible`)
|
||||
.set(authHeader(financeToken))
|
||||
.send({ reason: 'Customer account closed after failed recovery' })
|
||||
|
||||
expect(writeoffRes.status).toBe(200)
|
||||
expect(writeoffRes.body.data.status).toBe('UNCOLLECTIBLE')
|
||||
|
||||
const resumeRes = await request(app)
|
||||
.post(`/api/v1/admin/billing/accounts/${billingAccountId}/resume-dunning`)
|
||||
.set(authHeader(financeToken))
|
||||
.send({})
|
||||
|
||||
expect(resumeRes.status).toBe(200)
|
||||
expect(resumeRes.body.data.dunningPaused).toBe(false)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -2,6 +2,17 @@ import { beforeAll, afterAll } from 'vitest'
|
||||
import { prisma } from '../lib/prisma'
|
||||
|
||||
const delegates = [
|
||||
'billingEvent',
|
||||
'billingRefund',
|
||||
'billingCreditNote',
|
||||
'billingCreditLedgerEntry',
|
||||
'billingCreditBalance',
|
||||
'billingPaymentAttempt',
|
||||
'billingPaymentIntent',
|
||||
'billingInvoiceLineItem',
|
||||
'billingInvoice',
|
||||
'billingPaymentMethod',
|
||||
'billingAccount',
|
||||
'auditLog',
|
||||
'adminPermission',
|
||||
'adminUser',
|
||||
|
||||
@@ -0,0 +1,634 @@
|
||||
# Contrat de Location de Véhicule
|
||||
|
||||
**Contrat n° :** [●]
|
||||
**Date de signature :** [●]
|
||||
**Lieu de signature :** [Ville, Maroc]
|
||||
|
||||
---
|
||||
|
||||
## 1. Parties au contrat
|
||||
|
||||
Entre les soussignés :
|
||||
|
||||
### Le Loueur / Agence
|
||||
|
||||
**Nom commercial :** [●]
|
||||
**Raison sociale :** [●]
|
||||
**Forme juridique :** [●]
|
||||
**RC n° :** [●]
|
||||
**ICE n° :** [●]
|
||||
**IF n° :** [●]
|
||||
**Agrément / Autorisation d’exercice n° :** [●]
|
||||
**Date de délivrance de l’agrément :** [●]
|
||||
**Autorité délivrante :** [●]
|
||||
**Adresse :** [●]
|
||||
**Téléphone :** [●]
|
||||
**Email :** [●]
|
||||
**Représenté par :** [Nom, prénom, fonction]
|
||||
|
||||
Ci-après désigné **« le Loueur »**,
|
||||
|
||||
Et :
|
||||
|
||||
### Le Locataire / Conducteur principal
|
||||
|
||||
**Nom :** [●]
|
||||
**Prénom :** [●]
|
||||
**Date de naissance :** [●]
|
||||
**Nationalité :** [●]
|
||||
**CIN / Passeport n° :** [●]
|
||||
**Adresse complète :** [●]
|
||||
**Téléphone :** [●]
|
||||
**Email :** [●]
|
||||
|
||||
Ci-après désigné **« le Locataire »**.
|
||||
|
||||
Le Loueur et le Locataire sont ensemble désignés **« les Parties »**.
|
||||
|
||||
---
|
||||
|
||||
## 2. Responsable légal et conformité réglementaire du Loueur
|
||||
|
||||
Le Loueur déclare exercer l’activité de location de véhicules sans chauffeur conformément à la réglementation marocaine applicable.
|
||||
|
||||
La personne responsable de l’activité est :
|
||||
|
||||
**Nom et prénom :** [●]
|
||||
**Qualité :** [Dirigeant / Actionnaire / Salarié]
|
||||
**Pièce d’identité n° :** [●]
|
||||
**Qualification / diplôme / expérience :** [●]
|
||||
**Téléphone :** [●]
|
||||
**Email :** [●]
|
||||
|
||||
Cette personne est responsable du suivi des contrats, de l’entretien des véhicules, de leur conformité aux normes de sécurité routière et du respect des obligations administratives applicables.
|
||||
|
||||
Le Loueur déclare notamment :
|
||||
|
||||
- disposer des autorisations administratives requises pour l’exercice de l’activité ;
|
||||
- être régulièrement immatriculé auprès des administrations compétentes ;
|
||||
- disposer d’un siège social ou local professionnel déclaré ;
|
||||
- respecter les obligations sociales, fiscales et administratives applicables ;
|
||||
- exploiter une flotte conforme aux seuils et conditions réglementaires applicables ;
|
||||
- maintenir les véhicules loués en état conforme aux normes de sécurité routière.
|
||||
|
||||
---
|
||||
|
||||
## 3. Permis de conduire
|
||||
|
||||
Le Locataire déclare être titulaire d’un permis de conduire valide :
|
||||
|
||||
**Numéro du permis :** [●]
|
||||
**Catégorie :** [B / autre]
|
||||
**Date de délivrance :** [●]
|
||||
**Date d’expiration :** [●]
|
||||
**Pays de délivrance :** [●]
|
||||
**Permis international, le cas échéant :** [Oui / Non, n° ●]
|
||||
|
||||
Le Locataire garantit que son permis est valide pendant toute la durée de location et qu’il n’est frappé d’aucune suspension, annulation ou restriction incompatible avec la conduite du véhicule loué.
|
||||
|
||||
---
|
||||
|
||||
## 4. Conducteur(s) autorisé(s)
|
||||
|
||||
Le véhicule ne peut être conduit que par le Locataire et, le cas échéant, par les conducteurs additionnels expressément mentionnés ci-dessous :
|
||||
|
||||
### Conducteur additionnel 1
|
||||
|
||||
**Nom et prénom :** [●]
|
||||
**CIN / Passeport :** [●]
|
||||
**Permis n° :** [●]
|
||||
**Catégorie :** [●]
|
||||
**Date de délivrance :** [●]
|
||||
**Téléphone :** [●]
|
||||
|
||||
Tout conducteur non déclaré est interdit. En cas d’accident, vol, infraction ou dommage causé par un conducteur non autorisé, le Locataire assume l’entière responsabilité des conséquences financières, civiles, pénales et assurantielles.
|
||||
|
||||
---
|
||||
|
||||
## 5. Véhicule loué
|
||||
|
||||
Le Loueur met à disposition du Locataire le véhicule suivant :
|
||||
|
||||
**Marque :** [●]
|
||||
**Modèle :** [●]
|
||||
**Année :** [●]
|
||||
**Couleur :** [●]
|
||||
**Immatriculation :** [●]
|
||||
**Numéro de châssis :** [●]
|
||||
**Propriétaire du véhicule :** [Loueur / Autre agence / Société de financement / Autre]
|
||||
**Justificatif d’exploitation si le véhicule appartient à un tiers :** [Contrat / Autorisation / Mandat / Autre]
|
||||
**Type de motorisation :** [Thermique / Hybride / Électrique]
|
||||
**Date de première mise en circulation :** [●]
|
||||
**Âge du véhicule à la date de location :** [●]
|
||||
**Véhicule conforme aux limites réglementaires d’exploitation :** [Oui / Non]
|
||||
**Kilométrage au départ :** [●] km
|
||||
**Niveau de carburant au départ :** [●]
|
||||
**Carte grise :** [Oui / Non]
|
||||
**Assurance :** [Oui / Non]
|
||||
**Visite technique :** [Oui / Non / Non applicable]
|
||||
|
||||
Un état des lieux contradictoire au départ et au retour est annexé au présent contrat et en fait partie intégrante.
|
||||
|
||||
Le Loueur déclare que le véhicule mis à disposition est conforme aux conditions réglementaires d’exploitation applicables à sa catégorie, notamment en matière d’âge, d’immatriculation, d’assurance, d’entretien et de sécurité routière.
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 6. Entretien et conformité du véhicule
|
||||
|
||||
Le Loueur déclare que le véhicule est régulièrement entretenu, assuré, immatriculé et conforme aux normes de sécurité routière applicables.
|
||||
|
||||
Le Locataire reconnaît avoir reçu un véhicule en état apparent de fonctionnement, sous réserve des observations mentionnées dans l’état des lieux de départ.
|
||||
|
||||
En cas d’anomalie mécanique, voyant d’alerte, bruit anormal, crevaison, panne ou incident susceptible d’affecter la sécurité, le Locataire doit cesser l’utilisation du véhicule dès que les conditions de sécurité le permettent et informer immédiatement le Loueur.
|
||||
|
||||
---
|
||||
|
||||
## 7. Durée de location
|
||||
|
||||
La location commence le :
|
||||
|
||||
**Date :** [●]
|
||||
**Heure :** [●]
|
||||
**Lieu de prise en charge :** [●]
|
||||
|
||||
La location prend fin le :
|
||||
|
||||
**Date :** [●]
|
||||
**Heure :** [●]
|
||||
**Lieu de restitution :** [●]
|
||||
|
||||
Toute prolongation doit être acceptée préalablement par écrit par le Loueur. À défaut, le véhicule sera considéré comme conservé sans autorisation, et le Loueur pourra facturer les jours supplémentaires, pénalités de retard et frais éventuels.
|
||||
|
||||
---
|
||||
|
||||
## 8. Prix de location et modalités de paiement
|
||||
|
||||
Le prix de location est fixé comme suit :
|
||||
|
||||
| Désignation | Montant |
|
||||
|---|---:|
|
||||
| Tarif journalier HT | [●] MAD |
|
||||
| Nombre de jours | [●] |
|
||||
| Sous-total HT | [●] MAD |
|
||||
| TVA applicable | [●] % |
|
||||
| Montant TVA | [●] MAD |
|
||||
| Total TTC | [●] MAD |
|
||||
| Conducteur additionnel | [●] MAD |
|
||||
| Livraison / récupération | [●] MAD |
|
||||
| Siège enfant | [●] MAD |
|
||||
| GPS / accessoires | [●] MAD |
|
||||
| Autres frais | [●] MAD |
|
||||
|
||||
**Montant total à payer TTC : [●] MAD**
|
||||
|
||||
**Mode de paiement :** [Espèces / carte bancaire / virement / chèque / autre]
|
||||
**Date de paiement :** [●]
|
||||
**Référence de paiement :** [●]
|
||||
|
||||
Le Locataire reconnaît avoir reçu une information claire sur le prix total TTC avant la signature du contrat.
|
||||
|
||||
---
|
||||
|
||||
## 9. Dépôt de garantie / caution
|
||||
|
||||
Le Locataire verse au Loueur un dépôt de garantie de :
|
||||
|
||||
**Montant : [●] MAD**
|
||||
|
||||
**Forme de la caution :** [Préautorisation bancaire / espèces / chèque / autre]
|
||||
**Date de constitution :** [●]
|
||||
**Conditions de libération :** [●]
|
||||
|
||||
Le dépôt de garantie garantit notamment :
|
||||
|
||||
- les dommages non couverts par l’assurance ;
|
||||
- la franchise d’assurance ;
|
||||
- les kilomètres supplémentaires ;
|
||||
- le carburant manquant ;
|
||||
- les frais de nettoyage exceptionnel ;
|
||||
- les contraventions, amendes, péages ou frais administratifs ;
|
||||
- les retards de restitution ;
|
||||
- la perte de documents, clés, accessoires ou équipements.
|
||||
|
||||
La caution sera restituée après vérification de l’état du véhicule, du kilométrage, du carburant, des éventuelles infractions et des sommes restant dues.
|
||||
|
||||
---
|
||||
|
||||
## 10. Assurance
|
||||
|
||||
Le véhicule est assuré auprès de :
|
||||
|
||||
**Compagnie d’assurance :** [●]
|
||||
**Numéro de police :** [●]
|
||||
**Type de couverture :** [Responsabilité civile / Tous risques / autre]
|
||||
**Franchise applicable :** [●] MAD
|
||||
**Assistance :** [Oui / Non]
|
||||
**Numéro d’assistance :** [●]
|
||||
|
||||
Le Locataire reconnaît avoir été informé des garanties, exclusions et franchises applicables.
|
||||
|
||||
Sont notamment exclus de la couverture, sauf stipulation contraire de l’assureur :
|
||||
|
||||
- conduite par une personne non autorisée ;
|
||||
- conduite sans permis valide ;
|
||||
- conduite sous l’emprise d’alcool, stupéfiants ou substances interdites ;
|
||||
- usage du véhicule hors des voies autorisées ;
|
||||
- participation à des courses, essais ou compétitions ;
|
||||
- négligence grave ;
|
||||
- fausse déclaration ;
|
||||
- absence de déclaration d’accident dans les délais ;
|
||||
- vol avec clés laissées dans le véhicule ;
|
||||
- transport illicite de personnes ou de marchandises.
|
||||
|
||||
---
|
||||
|
||||
## 11. Kilométrage
|
||||
|
||||
**Kilométrage inclus :** [●] km par jour / [●] km pour toute la durée de location.
|
||||
**Kilométrage au départ :** [●] km.
|
||||
**Kilométrage au retour :** [●] km.
|
||||
|
||||
Tout kilomètre supplémentaire sera facturé au tarif de :
|
||||
|
||||
**[●] MAD TTC par kilomètre supplémentaire.**
|
||||
|
||||
Le kilométrage fait foi sur la base du compteur du véhicule, constaté dans l’état des lieux de départ et de retour.
|
||||
|
||||
---
|
||||
|
||||
## 12. Utilisation du véhicule
|
||||
|
||||
Le Locataire s’engage à utiliser le véhicule avec prudence, conformément à sa destination normale et à la législation marocaine.
|
||||
|
||||
Il est interdit notamment :
|
||||
|
||||
- de sous-louer le véhicule ;
|
||||
- de transporter des marchandises dangereuses ou illicites ;
|
||||
- d’utiliser le véhicule pour des activités illégales ;
|
||||
- de participer à des compétitions ou essais sportifs ;
|
||||
- de tracter un autre véhicule sans autorisation écrite ;
|
||||
- de circuler hors du territoire marocain sans autorisation écrite du Loueur ;
|
||||
- de modifier le véhicule ou ses équipements ;
|
||||
- de fumer dans le véhicule si l’agence l’interdit ;
|
||||
- de transporter un nombre de passagers supérieur à celui autorisé.
|
||||
|
||||
Le Locataire est responsable des infractions au Code de la route commises pendant la période de location.
|
||||
|
||||
---
|
||||
|
||||
## 13. Restitution du véhicule
|
||||
|
||||
Le Locataire doit restituer le véhicule à la date, à l’heure et au lieu prévus au contrat, dans l’état où il lui a été remis, sous réserve de l’usure normale.
|
||||
|
||||
Le véhicule doit être restitué avec :
|
||||
|
||||
- le même niveau de carburant qu’au départ ;
|
||||
- les papiers du véhicule ;
|
||||
- les clés ;
|
||||
- les accessoires et équipements remis ;
|
||||
- un état de propreté normal ;
|
||||
- aucun dommage nouveau non déclaré.
|
||||
|
||||
En cas de retard, le Loueur pourra facturer :
|
||||
|
||||
- [●] MAD par heure de retard ; ou
|
||||
- une journée supplémentaire au tarif contractuel au-delà de [●] heures de retard.
|
||||
|
||||
En cas de carburant manquant, les frais seront facturés selon le coût réel augmenté de frais de service de [●] MAD.
|
||||
|
||||
En cas de salissure excessive, le Loueur pourra facturer des frais de nettoyage de [●] MAD à [●] MAD selon l’état du véhicule.
|
||||
|
||||
---
|
||||
|
||||
## 14. État des lieux départ et retour
|
||||
|
||||
Les Parties établissent un état des lieux au départ et au retour. Celui-ci peut être accompagné de photographies datées du véhicule, du compteur, du carburant, de la carrosserie, de l’intérieur, des pneus, des vitres et des équipements.
|
||||
|
||||
Les éléments à contrôler comprennent notamment :
|
||||
|
||||
- Kilométrage compteur ;
|
||||
- Niveau de carburant ;
|
||||
- Carrosserie ;
|
||||
- Pare-chocs ;
|
||||
- Rétroviseurs ;
|
||||
- Phares et feux ;
|
||||
- Pare-brise et vitres ;
|
||||
- Pneus et jantes ;
|
||||
- Intérieur et sièges ;
|
||||
- Tableau de bord ;
|
||||
- Roue de secours ;
|
||||
- Outillage ;
|
||||
- Documents du véhicule ;
|
||||
- Clés et accessoires.
|
||||
|
||||
Tout dommage constaté au retour et non mentionné dans l’état des lieux de départ pourra être facturé au Locataire, sous réserve des garanties d’assurance applicables.
|
||||
|
||||
---
|
||||
|
||||
## 15. Accident, panne, vol ou sinistre
|
||||
|
||||
En cas d’accident, panne, vol, tentative de vol, incendie ou tout autre sinistre, le Locataire doit immédiatement :
|
||||
|
||||
- informer le Loueur par téléphone et par écrit ;
|
||||
- prévenir les autorités compétentes si nécessaire ;
|
||||
- établir un constat amiable en cas d’accident ;
|
||||
- obtenir un procès-verbal ou document officiel en cas de vol, blessure, délit ou dommage important ;
|
||||
- ne pas abandonner le véhicule sans autorisation du Loueur ;
|
||||
- ne pas reconnaître de responsabilité sans accord préalable du Loueur ou de l’assureur.
|
||||
|
||||
Le Locataire doit transmettre au Loueur tous les documents utiles dans un délai maximum de **48 heures** à compter du sinistre.
|
||||
|
||||
En cas de non-respect de cette procédure, le Locataire pourra être tenu responsable des conséquences financières, notamment si l’assureur refuse sa garantie.
|
||||
|
||||
---
|
||||
|
||||
## 16. Responsabilité du Locataire
|
||||
|
||||
Le Locataire est responsable :
|
||||
|
||||
- des dommages causés au véhicule pendant la période de location ;
|
||||
- des dommages causés aux tiers dans les limites prévues par la loi et l’assurance ;
|
||||
- des amendes, contraventions, frais de fourrière, péages et frais administratifs ;
|
||||
- des frais liés à une mauvaise utilisation du véhicule ;
|
||||
- des pertes de clés, documents ou accessoires ;
|
||||
- des dommages non couverts par l’assurance ;
|
||||
- de la franchise prévue au contrat d’assurance.
|
||||
|
||||
Le Locataire reste responsable jusqu’à la restitution effective du véhicule au Loueur et la signature de l’état des lieux de retour.
|
||||
|
||||
---
|
||||
|
||||
## 17. Infractions, amendes et frais administratifs
|
||||
|
||||
Toutes les infractions commises pendant la durée de location sont à la charge du Locataire.
|
||||
|
||||
Le Loueur pourra communiquer l’identité du Locataire aux autorités compétentes et facturer au Locataire :
|
||||
|
||||
- le montant des amendes ;
|
||||
- les frais de dossier ;
|
||||
- les frais de fourrière ;
|
||||
- les frais de notification ;
|
||||
- tout coût lié au traitement administratif de l’infraction.
|
||||
|
||||
**Frais administratifs par infraction : [●] MAD TTC.**
|
||||
|
||||
---
|
||||
|
||||
## 18. Annulation, non-présentation et résiliation
|
||||
|
||||
En cas d’annulation par le Locataire :
|
||||
|
||||
- plus de [●] heures avant le départ : [conditions de remboursement] ;
|
||||
- moins de [●] heures avant le départ : [conditions] ;
|
||||
- non-présentation : [conditions].
|
||||
|
||||
Le Loueur peut résilier immédiatement le contrat, sans indemnité pour le Locataire, en cas de :
|
||||
|
||||
- fausse déclaration ;
|
||||
- permis invalide ;
|
||||
- défaut de paiement ;
|
||||
- utilisation interdite du véhicule ;
|
||||
- conduite dangereuse ;
|
||||
- non-respect grave du présent contrat ;
|
||||
- suspicion légitime de fraude ou d’usage illicite.
|
||||
|
||||
Dans ce cas, le Locataire doit restituer immédiatement le véhicule.
|
||||
|
||||
---
|
||||
|
||||
## 19. Indisponibilité administrative, réglementaire ou technique
|
||||
|
||||
En cas d’indisponibilité du véhicule ou d’impossibilité d’exécuter la location pour une raison administrative, réglementaire, technique ou de sécurité, le Loueur pourra proposer au Locataire un véhicule équivalent, sous réserve de disponibilité.
|
||||
|
||||
À défaut de véhicule équivalent accepté par le Locataire, les sommes payées au titre de la période non exécutée seront remboursées, sans préjudice des droits légalement reconnus aux Parties.
|
||||
|
||||
---
|
||||
|
||||
## 20. Données personnelles
|
||||
|
||||
Le Locataire autorise le Loueur à collecter et conserver les données nécessaires à l’exécution du présent contrat, notamment son identité, ses coordonnées, son permis de conduire, les informations de paiement, les documents liés au véhicule, les états des lieux et les données relatives aux éventuels incidents.
|
||||
|
||||
Ces données sont utilisées pour :
|
||||
|
||||
- l’exécution du contrat ;
|
||||
- la facturation ;
|
||||
- la gestion des sinistres ;
|
||||
- la gestion des infractions ;
|
||||
- les obligations comptables, fiscales et légales ;
|
||||
- la défense des droits du Loueur en cas de litige.
|
||||
|
||||
Le Loueur s’engage à protéger les données personnelles du Locataire, à limiter leur accès aux personnes habilitées et à les conserver uniquement pendant la durée nécessaire aux finalités prévues et aux obligations légales applicables.
|
||||
|
||||
---
|
||||
|
||||
## 21. Documents annexés
|
||||
|
||||
Les documents suivants sont annexés au présent contrat :
|
||||
|
||||
- Copie CIN ou passeport du Locataire ;
|
||||
- Copie du permis de conduire ;
|
||||
- Copie permis international, le cas échéant ;
|
||||
- État des lieux de départ ;
|
||||
- État des lieux de retour ;
|
||||
- Photos du véhicule au départ ;
|
||||
- Photos du véhicule au retour ;
|
||||
- Copie de la carte grise ;
|
||||
- Copie de l’attestation d’assurance ;
|
||||
- Justificatif autorisant l’exploitation du véhicule par le Loueur, si le véhicule n’appartient pas directement au Loueur ;
|
||||
- Reçu de paiement ;
|
||||
- Justificatif de dépôt de garantie ;
|
||||
- Conditions générales de location, le cas échéant.
|
||||
|
||||
Les annexes font partie intégrante du présent contrat.
|
||||
|
||||
---
|
||||
|
||||
## 22. Loi applicable et juridiction compétente
|
||||
|
||||
Le présent contrat est soumis au droit marocain.
|
||||
|
||||
En cas de litige relatif à sa validité, son interprétation, son exécution ou sa résiliation, les Parties s’efforceront de trouver une solution amiable.
|
||||
|
||||
À défaut d’accord amiable, le litige sera porté devant le tribunal compétent du ressort de :
|
||||
|
||||
**[Ville du siège de l’agence / tribunal compétent]**
|
||||
|
||||
---
|
||||
|
||||
## 23. Déclaration finale
|
||||
|
||||
Le Locataire déclare :
|
||||
|
||||
- avoir lu et compris le présent contrat ;
|
||||
- avoir reçu toutes les informations utiles avant signature ;
|
||||
- avoir vérifié l’état du véhicule au départ ;
|
||||
- être titulaire d’un permis valide ;
|
||||
- s’engager à respecter les conditions du présent contrat ;
|
||||
- accepter les prix, caution, franchises, pénalités et frais indiqués.
|
||||
|
||||
**Fait à :** [●]
|
||||
**Le :** [●]
|
||||
**En deux exemplaires originaux.**
|
||||
|
||||
Chaque page du présent contrat doit être paraphée par les deux Parties.
|
||||
|
||||
### Signature du Loueur
|
||||
|
||||
**Nom :** [●]
|
||||
**Signature et cachet :**
|
||||
|
||||
<br><br>
|
||||
|
||||
### Signature du Locataire
|
||||
|
||||
**Nom :** [●]
|
||||
**Signature :**
|
||||
|
||||
<br><br>
|
||||
|
||||
---
|
||||
|
||||
# Annexe 1 — État des lieux de départ
|
||||
|
||||
**Contrat n° :** [●]
|
||||
**Date :** [●]
|
||||
**Heure :** [●]
|
||||
**Lieu :** [●]
|
||||
|
||||
**Véhicule :** [Marque / Modèle]
|
||||
**Immatriculation :** [●]
|
||||
**Kilométrage départ :** [●] km
|
||||
**Carburant départ :** [●]
|
||||
|
||||
| Élément | État au départ | Observations |
|
||||
|---|---|---|
|
||||
| Carrosserie avant | Bon / Rayé / Endommagé | [●] |
|
||||
| Carrosserie arrière | Bon / Rayé / Endommagé | [●] |
|
||||
| Côté droit | Bon / Rayé / Endommagé | [●] |
|
||||
| Côté gauche | Bon / Rayé / Endommagé | [●] |
|
||||
| Pare-brise | Bon / Impact / Fissure | [●] |
|
||||
| Vitres | Bon / Endommagé | [●] |
|
||||
| Pneus | Bon / Usé / Endommagé | [●] |
|
||||
| Jantes | Bon / Rayé / Endommagé | [●] |
|
||||
| Intérieur | Bon / Taché / Endommagé | [●] |
|
||||
| Sièges | Bon / Taché / Déchiré | [●] |
|
||||
| Tableau de bord | Bon / Endommagé | [●] |
|
||||
| Climatisation | Fonctionne / Ne fonctionne pas | [●] |
|
||||
| Feux | Fonctionnent / Défaut | [●] |
|
||||
| Roue de secours | Présente / Absente | [●] |
|
||||
| Outillage | Présent / Absent | [●] |
|
||||
| Carte grise | Présente / Absente | [●] |
|
||||
| Assurance | Présente / Absente | [●] |
|
||||
| Clés | [Nombre] | [●] |
|
||||
|
||||
**Photos prises au départ :** Oui / Non
|
||||
**Nombre de photos :** [●]
|
||||
|
||||
**Signature du Loueur :**
|
||||
|
||||
<br><br>
|
||||
|
||||
**Signature du Locataire :**
|
||||
|
||||
<br><br>
|
||||
|
||||
---
|
||||
|
||||
# Annexe 2 — État des lieux de retour
|
||||
|
||||
**Contrat n° :** [●]
|
||||
**Date :** [●]
|
||||
**Heure :** [●]
|
||||
**Lieu :** [●]
|
||||
|
||||
**Kilométrage retour :** [●] km
|
||||
**Kilométrage parcouru :** [●] km
|
||||
**Kilométrage inclus :** [●] km
|
||||
**Kilométrage supplémentaire :** [●] km
|
||||
**Montant km supplémentaires :** [●] MAD
|
||||
|
||||
**Carburant retour :** [●]
|
||||
**Carburant manquant :** Oui / Non
|
||||
**Montant carburant :** [●] MAD
|
||||
|
||||
| Élément | État au retour | Nouveau dommage ? | Observations |
|
||||
|---|---|---|---|
|
||||
| Carrosserie avant | Bon / Rayé / Endommagé | Oui / Non | [●] |
|
||||
| Carrosserie arrière | Bon / Rayé / Endommagé | Oui / Non | [●] |
|
||||
| Côté droit | Bon / Rayé / Endommagé | Oui / Non | [●] |
|
||||
| Côté gauche | Bon / Rayé / Endommagé | Oui / Non | [●] |
|
||||
| Pare-brise | Bon / Impact / Fissure | Oui / Non | [●] |
|
||||
| Vitres | Bon / Endommagé | Oui / Non | [●] |
|
||||
| Pneus | Bon / Usé / Endommagé | Oui / Non | [●] |
|
||||
| Jantes | Bon / Rayé / Endommagé | Oui / Non | [●] |
|
||||
| Intérieur | Bon / Taché / Endommagé | Oui / Non | [●] |
|
||||
| Sièges | Bon / Taché / Déchiré | Oui / Non | [●] |
|
||||
| Documents | Complets / Manquants | Oui / Non | [●] |
|
||||
| Clés | Restituées / Manquantes | Oui / Non | [●] |
|
||||
|
||||
## Frais constatés au retour
|
||||
|
||||
| Frais | Montant |
|
||||
|---|---:|
|
||||
| Dommages | [●] MAD |
|
||||
| Franchise assurance | [●] MAD |
|
||||
| Kilométrage supplémentaire | [●] MAD |
|
||||
| Carburant | [●] MAD |
|
||||
| Nettoyage | [●] MAD |
|
||||
| Retard | [●] MAD |
|
||||
| Autres frais | [●] MAD |
|
||||
|
||||
**Total à retenir sur la caution : [●] MAD**
|
||||
**Solde de caution à restituer : [●] MAD**
|
||||
|
||||
**Photos prises au retour :** Oui / Non
|
||||
**Nombre de photos :** [●]
|
||||
|
||||
**Signature du Loueur :**
|
||||
|
||||
<br><br>
|
||||
|
||||
**Signature du Locataire :**
|
||||
|
||||
<br><br>
|
||||
|
||||
---
|
||||
|
||||
# Checklist pratique avant signature
|
||||
|
||||
- Ne laisser aucun champ `[●]` vide dans la version signée.
|
||||
- Faire parapher chaque page par les deux Parties.
|
||||
- Joindre la copie CIN ou passeport du Locataire.
|
||||
- Joindre la copie du permis de conduire.
|
||||
- Photographier le compteur et le niveau de carburant au départ et au retour.
|
||||
- Photographier toutes les faces du véhicule au départ et au retour.
|
||||
- Écrire clairement le montant de la caution.
|
||||
- Mentionner la compagnie d’assurance, le numéro de police et la franchise.
|
||||
- Conserver une preuve de paiement.
|
||||
- Conserver les états des lieux signés.
|
||||
|
||||
---
|
||||
|
||||
# Note importante
|
||||
|
||||
Ce modèle est un document pratique destiné à structurer une location de voiture au Maroc. Il doit être adapté à l’activité réelle du Loueur, à ses conditions d’assurance, à son statut fiscal et aux règles applicables. Pour un usage commercial régulier, une validation par un juriste ou avocat marocain est recommandée.
|
||||
|
||||
---
|
||||
|
||||
# Checklist interne de conformité du Loueur
|
||||
|
||||
Cette checklist est destinée au Loueur et ne remplace pas les documents administratifs obligatoires.
|
||||
|
||||
- Agrément / autorisation d’exercice disponible.
|
||||
- Responsable réglementaire désigné.
|
||||
- Casier judiciaire conforme, si exigé.
|
||||
- Société inscrite à la CNSS, si applicable.
|
||||
- Siège social ou local professionnel justifié.
|
||||
- Capital social conforme au seuil applicable.
|
||||
- Flotte conforme au seuil réglementaire applicable.
|
||||
- Âge des véhicules conforme selon motorisation.
|
||||
- Documents de propriété ou d’exploitation des véhicules disponibles.
|
||||
- Assurance et visite technique valides pour chaque véhicule.
|
||||
- Procédure de déclaration en cas de suspension ou cessation d’activité.
|
||||
|
||||
@@ -0,0 +1,699 @@
|
||||
# Contrat de Location de Véhicule
|
||||
|
||||
**Contrat n° :** [●]
|
||||
**Date de signature :** [●]
|
||||
**Lieu de signature :** [Ville, Maroc]
|
||||
|
||||
---
|
||||
|
||||
## 1. Parties au contrat
|
||||
|
||||
Entre les soussignés :
|
||||
|
||||
### Le Loueur / Agence
|
||||
|
||||
**Nom commercial :** [●]
|
||||
**Raison sociale :** [●]
|
||||
**Forme juridique :** [●]
|
||||
**RC n° :** [●]
|
||||
**ICE n° :** [●]
|
||||
**IF n° :** [●]
|
||||
**Agrément / Autorisation d’exercice n° :** [●]
|
||||
**Date de délivrance de l’agrément :** [●]
|
||||
**Autorité délivrante :** [●]
|
||||
**Adresse :** [●]
|
||||
**Téléphone :** [●]
|
||||
**Email :** [●]
|
||||
**Représenté par :** [Nom, prénom, fonction]
|
||||
|
||||
Ci-après désigné **« le Loueur »**,
|
||||
|
||||
Et :
|
||||
|
||||
### Le Locataire / Conducteur principal
|
||||
|
||||
**Nom :** [●]
|
||||
**Prénom :** [●]
|
||||
**Date de naissance :** [●]
|
||||
**Nationalité :** [●]
|
||||
**CIN / Passeport n° :** [●]
|
||||
**Adresse complète :** [●]
|
||||
**Téléphone :** [●]
|
||||
**Email :** [●]
|
||||
|
||||
Ci-après désigné **« le Locataire »**.
|
||||
|
||||
Le Loueur et le Locataire sont ensemble désignés **« les Parties »**.
|
||||
|
||||
---
|
||||
|
||||
## 2. Responsable légal et conformité réglementaire du Loueur
|
||||
|
||||
Le Loueur déclare exercer l’activité de location de véhicules sans chauffeur conformément à la réglementation marocaine applicable.
|
||||
|
||||
Le Loueur reconnaît que son activité doit respecter les textes mentionnés dans la section **Références légales et réglementaires applicables** du présent document.
|
||||
|
||||
La personne responsable de l’activité est :
|
||||
|
||||
**Nom et prénom :** [●]
|
||||
**Qualité :** [Dirigeant / Actionnaire / Salarié]
|
||||
**Pièce d’identité n° :** [●]
|
||||
**Qualification / diplôme / expérience :** [●]
|
||||
**Téléphone :** [●]
|
||||
**Email :** [●]
|
||||
|
||||
Cette personne est responsable du suivi des contrats, de l’entretien des véhicules, de leur conformité aux normes de sécurité routière et du respect des obligations administratives applicables.
|
||||
|
||||
Le Loueur déclare notamment :
|
||||
|
||||
- disposer des autorisations administratives requises pour l’exercice de l’activité ;
|
||||
- être régulièrement immatriculé auprès des administrations compétentes ;
|
||||
- disposer d’un siège social ou local professionnel déclaré ;
|
||||
- respecter les obligations sociales, fiscales et administratives applicables ;
|
||||
- exploiter une flotte conforme aux seuils et conditions réglementaires applicables ;
|
||||
- maintenir les véhicules loués en état conforme aux normes de sécurité routière.
|
||||
|
||||
---
|
||||
|
||||
## 3. Permis de conduire
|
||||
|
||||
Le Locataire déclare être titulaire d’un permis de conduire valide :
|
||||
|
||||
**Numéro du permis :** [●]
|
||||
**Catégorie :** [B / autre]
|
||||
**Date de délivrance :** [●]
|
||||
**Date d’expiration :** [●]
|
||||
**Pays de délivrance :** [●]
|
||||
**Permis international, le cas échéant :** [Oui / Non, n° ●]
|
||||
|
||||
Le Locataire garantit que son permis est valide pendant toute la durée de location et qu’il n’est frappé d’aucune suspension, annulation ou restriction incompatible avec la conduite du véhicule loué.
|
||||
|
||||
---
|
||||
|
||||
## 4. Conducteur(s) autorisé(s)
|
||||
|
||||
Le véhicule ne peut être conduit que par le Locataire et, le cas échéant, par les conducteurs additionnels expressément mentionnés ci-dessous :
|
||||
|
||||
### Conducteur additionnel 1
|
||||
|
||||
**Nom et prénom :** [●]
|
||||
**CIN / Passeport :** [●]
|
||||
**Permis n° :** [●]
|
||||
**Catégorie :** [●]
|
||||
**Date de délivrance :** [●]
|
||||
**Téléphone :** [●]
|
||||
|
||||
Tout conducteur non déclaré est interdit. En cas d’accident, vol, infraction ou dommage causé par un conducteur non autorisé, le Locataire assume l’entière responsabilité des conséquences financières, civiles, pénales et assurantielles.
|
||||
|
||||
---
|
||||
|
||||
## 5. Véhicule loué
|
||||
|
||||
Le Loueur met à disposition du Locataire le véhicule suivant :
|
||||
|
||||
**Marque :** [●]
|
||||
**Modèle :** [●]
|
||||
**Année :** [●]
|
||||
**Couleur :** [●]
|
||||
**Immatriculation :** [●]
|
||||
**Numéro de châssis :** [●]
|
||||
**Propriétaire du véhicule :** [Loueur / Autre agence / Société de financement / Autre]
|
||||
**Justificatif d’exploitation si le véhicule appartient à un tiers :** [Contrat / Autorisation / Mandat / Autre]
|
||||
**Type de motorisation :** [Thermique / Hybride / Électrique]
|
||||
**Date de première mise en circulation :** [●]
|
||||
**Âge du véhicule à la date de location :** [●]
|
||||
**Véhicule conforme aux limites réglementaires d’exploitation :** [Oui / Non]
|
||||
**Kilométrage au départ :** [●] km
|
||||
**Niveau de carburant au départ :** [●]
|
||||
**Carte grise :** [Oui / Non]
|
||||
**Assurance :** [Oui / Non]
|
||||
**Visite technique :** [Oui / Non / Non applicable]
|
||||
|
||||
Un état des lieux contradictoire au départ et au retour est annexé au présent contrat et en fait partie intégrante.
|
||||
|
||||
Le Loueur déclare que le véhicule mis à disposition est conforme aux conditions réglementaires d’exploitation applicables à sa catégorie, notamment en matière d’âge, d’immatriculation, d’assurance, d’entretien et de sécurité routière.
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 6. Entretien et conformité du véhicule
|
||||
|
||||
Le Loueur déclare que le véhicule est régulièrement entretenu, assuré, immatriculé et conforme aux normes de sécurité routière applicables.
|
||||
|
||||
Le Locataire reconnaît avoir reçu un véhicule en état apparent de fonctionnement, sous réserve des observations mentionnées dans l’état des lieux de départ.
|
||||
|
||||
En cas d’anomalie mécanique, voyant d’alerte, bruit anormal, crevaison, panne ou incident susceptible d’affecter la sécurité, le Locataire doit cesser l’utilisation du véhicule dès que les conditions de sécurité le permettent et informer immédiatement le Loueur.
|
||||
|
||||
---
|
||||
|
||||
## 7. Durée de location
|
||||
|
||||
La location commence le :
|
||||
|
||||
**Date :** [●]
|
||||
**Heure :** [●]
|
||||
**Lieu de prise en charge :** [●]
|
||||
|
||||
La location prend fin le :
|
||||
|
||||
**Date :** [●]
|
||||
**Heure :** [●]
|
||||
**Lieu de restitution :** [●]
|
||||
|
||||
Toute prolongation doit être acceptée préalablement par écrit par le Loueur. À défaut, le véhicule sera considéré comme conservé sans autorisation, et le Loueur pourra facturer les jours supplémentaires, pénalités de retard et frais éventuels.
|
||||
|
||||
---
|
||||
|
||||
## 8. Prix de location et modalités de paiement
|
||||
|
||||
Le prix de location est fixé comme suit :
|
||||
|
||||
| Désignation | Montant |
|
||||
|---|---:|
|
||||
| Tarif journalier HT | [●] MAD |
|
||||
| Nombre de jours | [●] |
|
||||
| Sous-total HT | [●] MAD |
|
||||
| TVA applicable | [●] % |
|
||||
| Montant TVA | [●] MAD |
|
||||
| Total TTC | [●] MAD |
|
||||
| Conducteur additionnel | [●] MAD |
|
||||
| Livraison / récupération | [●] MAD |
|
||||
| Siège enfant | [●] MAD |
|
||||
| GPS / accessoires | [●] MAD |
|
||||
| Autres frais | [●] MAD |
|
||||
|
||||
**Montant total à payer TTC : [●] MAD**
|
||||
|
||||
**Mode de paiement :** [Espèces / carte bancaire / virement / chèque / autre]
|
||||
**Date de paiement :** [●]
|
||||
**Référence de paiement :** [●]
|
||||
|
||||
Le Locataire reconnaît avoir reçu une information claire sur le prix total TTC avant la signature du contrat.
|
||||
|
||||
---
|
||||
|
||||
## 9. Dépôt de garantie / caution
|
||||
|
||||
Le Locataire verse au Loueur un dépôt de garantie de :
|
||||
|
||||
**Montant : [●] MAD**
|
||||
|
||||
**Forme de la caution :** [Préautorisation bancaire / espèces / chèque / autre]
|
||||
**Date de constitution :** [●]
|
||||
**Conditions de libération :** [●]
|
||||
|
||||
Le dépôt de garantie garantit notamment :
|
||||
|
||||
- les dommages non couverts par l’assurance ;
|
||||
- la franchise d’assurance ;
|
||||
- les kilomètres supplémentaires ;
|
||||
- le carburant manquant ;
|
||||
- les frais de nettoyage exceptionnel ;
|
||||
- les contraventions, amendes, péages ou frais administratifs ;
|
||||
- les retards de restitution ;
|
||||
- la perte de documents, clés, accessoires ou équipements.
|
||||
|
||||
La caution sera restituée après vérification de l’état du véhicule, du kilométrage, du carburant, des éventuelles infractions et des sommes restant dues.
|
||||
|
||||
---
|
||||
|
||||
## 10. Assurance
|
||||
|
||||
Le véhicule est assuré auprès de :
|
||||
|
||||
**Compagnie d’assurance :** [●]
|
||||
**Numéro de police :** [●]
|
||||
**Type de couverture :** [Responsabilité civile / Tous risques / autre]
|
||||
**Franchise applicable :** [●] MAD
|
||||
**Assistance :** [Oui / Non]
|
||||
**Numéro d’assistance :** [●]
|
||||
|
||||
Le Locataire reconnaît avoir été informé des garanties, exclusions et franchises applicables.
|
||||
|
||||
Sont notamment exclus de la couverture, sauf stipulation contraire de l’assureur :
|
||||
|
||||
- conduite par une personne non autorisée ;
|
||||
- conduite sans permis valide ;
|
||||
- conduite sous l’emprise d’alcool, stupéfiants ou substances interdites ;
|
||||
- usage du véhicule hors des voies autorisées ;
|
||||
- participation à des courses, essais ou compétitions ;
|
||||
- négligence grave ;
|
||||
- fausse déclaration ;
|
||||
- absence de déclaration d’accident dans les délais ;
|
||||
- vol avec clés laissées dans le véhicule ;
|
||||
- transport illicite de personnes ou de marchandises.
|
||||
|
||||
---
|
||||
|
||||
## 11. Kilométrage
|
||||
|
||||
**Kilométrage inclus :** [●] km par jour / [●] km pour toute la durée de location.
|
||||
**Kilométrage au départ :** [●] km.
|
||||
**Kilométrage au retour :** [●] km.
|
||||
|
||||
Tout kilomètre supplémentaire sera facturé au tarif de :
|
||||
|
||||
**[●] MAD TTC par kilomètre supplémentaire.**
|
||||
|
||||
Le kilométrage fait foi sur la base du compteur du véhicule, constaté dans l’état des lieux de départ et de retour.
|
||||
|
||||
---
|
||||
|
||||
## 12. Utilisation du véhicule
|
||||
|
||||
Le Locataire s’engage à utiliser le véhicule avec prudence, conformément à sa destination normale et à la législation marocaine.
|
||||
|
||||
Il est interdit notamment :
|
||||
|
||||
- de sous-louer le véhicule ;
|
||||
- de transporter des marchandises dangereuses ou illicites ;
|
||||
- d’utiliser le véhicule pour des activités illégales ;
|
||||
- de participer à des compétitions ou essais sportifs ;
|
||||
- de tracter un autre véhicule sans autorisation écrite ;
|
||||
- de circuler hors du territoire marocain sans autorisation écrite du Loueur ;
|
||||
- de modifier le véhicule ou ses équipements ;
|
||||
- de fumer dans le véhicule si l’agence l’interdit ;
|
||||
- de transporter un nombre de passagers supérieur à celui autorisé.
|
||||
|
||||
Le Locataire est responsable des infractions au Code de la route commises pendant la période de location.
|
||||
|
||||
---
|
||||
|
||||
## 13. Restitution du véhicule
|
||||
|
||||
Le Locataire doit restituer le véhicule à la date, à l’heure et au lieu prévus au contrat, dans l’état où il lui a été remis, sous réserve de l’usure normale.
|
||||
|
||||
Le véhicule doit être restitué avec :
|
||||
|
||||
- le même niveau de carburant qu’au départ ;
|
||||
- les papiers du véhicule ;
|
||||
- les clés ;
|
||||
- les accessoires et équipements remis ;
|
||||
- un état de propreté normal ;
|
||||
- aucun dommage nouveau non déclaré.
|
||||
|
||||
En cas de retard, le Loueur pourra facturer :
|
||||
|
||||
- [●] MAD par heure de retard ; ou
|
||||
- une journée supplémentaire au tarif contractuel au-delà de [●] heures de retard.
|
||||
|
||||
En cas de carburant manquant, les frais seront facturés selon le coût réel augmenté de frais de service de [●] MAD.
|
||||
|
||||
En cas de salissure excessive, le Loueur pourra facturer des frais de nettoyage de [●] MAD à [●] MAD selon l’état du véhicule.
|
||||
|
||||
---
|
||||
|
||||
## 14. État des lieux départ et retour
|
||||
|
||||
Les Parties établissent un état des lieux au départ et au retour. Celui-ci peut être accompagné de photographies datées du véhicule, du compteur, du carburant, de la carrosserie, de l’intérieur, des pneus, des vitres et des équipements.
|
||||
|
||||
Les éléments à contrôler comprennent notamment :
|
||||
|
||||
- Kilométrage compteur ;
|
||||
- Niveau de carburant ;
|
||||
- Carrosserie ;
|
||||
- Pare-chocs ;
|
||||
- Rétroviseurs ;
|
||||
- Phares et feux ;
|
||||
- Pare-brise et vitres ;
|
||||
- Pneus et jantes ;
|
||||
- Intérieur et sièges ;
|
||||
- Tableau de bord ;
|
||||
- Roue de secours ;
|
||||
- Outillage ;
|
||||
- Documents du véhicule ;
|
||||
- Clés et accessoires.
|
||||
|
||||
Tout dommage constaté au retour et non mentionné dans l’état des lieux de départ pourra être facturé au Locataire, sous réserve des garanties d’assurance applicables.
|
||||
|
||||
---
|
||||
|
||||
## 15. Accident, panne, vol ou sinistre
|
||||
|
||||
En cas d’accident, panne, vol, tentative de vol, incendie ou tout autre sinistre, le Locataire doit immédiatement :
|
||||
|
||||
- informer le Loueur par téléphone et par écrit ;
|
||||
- prévenir les autorités compétentes si nécessaire ;
|
||||
- établir un constat amiable en cas d’accident ;
|
||||
- obtenir un procès-verbal ou document officiel en cas de vol, blessure, délit ou dommage important ;
|
||||
- ne pas abandonner le véhicule sans autorisation du Loueur ;
|
||||
- ne pas reconnaître de responsabilité sans accord préalable du Loueur ou de l’assureur.
|
||||
|
||||
Le Locataire doit transmettre au Loueur tous les documents utiles dans un délai maximum de **48 heures** à compter du sinistre.
|
||||
|
||||
En cas de non-respect de cette procédure, le Locataire pourra être tenu responsable des conséquences financières, notamment si l’assureur refuse sa garantie.
|
||||
|
||||
---
|
||||
|
||||
## 16. Responsabilité du Locataire
|
||||
|
||||
Le Locataire est responsable :
|
||||
|
||||
- des dommages causés au véhicule pendant la période de location ;
|
||||
- des dommages causés aux tiers dans les limites prévues par la loi et l’assurance ;
|
||||
- des amendes, contraventions, frais de fourrière, péages et frais administratifs ;
|
||||
- des frais liés à une mauvaise utilisation du véhicule ;
|
||||
- des pertes de clés, documents ou accessoires ;
|
||||
- des dommages non couverts par l’assurance ;
|
||||
- de la franchise prévue au contrat d’assurance.
|
||||
|
||||
Le Locataire reste responsable jusqu’à la restitution effective du véhicule au Loueur et la signature de l’état des lieux de retour.
|
||||
|
||||
---
|
||||
|
||||
## 17. Infractions, amendes et frais administratifs
|
||||
|
||||
Toutes les infractions commises pendant la durée de location sont à la charge du Locataire.
|
||||
|
||||
Le Loueur pourra communiquer l’identité du Locataire aux autorités compétentes et facturer au Locataire :
|
||||
|
||||
- le montant des amendes ;
|
||||
- les frais de dossier ;
|
||||
- les frais de fourrière ;
|
||||
- les frais de notification ;
|
||||
- tout coût lié au traitement administratif de l’infraction.
|
||||
|
||||
**Frais administratifs par infraction : [●] MAD TTC.**
|
||||
|
||||
---
|
||||
|
||||
## 18. Annulation, non-présentation et résiliation
|
||||
|
||||
En cas d’annulation par le Locataire :
|
||||
|
||||
- plus de [●] heures avant le départ : [conditions de remboursement] ;
|
||||
- moins de [●] heures avant le départ : [conditions] ;
|
||||
- non-présentation : [conditions].
|
||||
|
||||
Le Loueur peut résilier immédiatement le contrat, sans indemnité pour le Locataire, en cas de :
|
||||
|
||||
- fausse déclaration ;
|
||||
- permis invalide ;
|
||||
- défaut de paiement ;
|
||||
- utilisation interdite du véhicule ;
|
||||
- conduite dangereuse ;
|
||||
- non-respect grave du présent contrat ;
|
||||
- suspicion légitime de fraude ou d’usage illicite.
|
||||
|
||||
Dans ce cas, le Locataire doit restituer immédiatement le véhicule.
|
||||
|
||||
---
|
||||
|
||||
## 19. Indisponibilité administrative, réglementaire ou technique
|
||||
|
||||
En cas d’indisponibilité du véhicule ou d’impossibilité d’exécuter la location pour une raison administrative, réglementaire, technique ou de sécurité, le Loueur pourra proposer au Locataire un véhicule équivalent, sous réserve de disponibilité.
|
||||
|
||||
À défaut de véhicule équivalent accepté par le Locataire, les sommes payées au titre de la période non exécutée seront remboursées, sans préjudice des droits légalement reconnus aux Parties.
|
||||
|
||||
---
|
||||
|
||||
## 20. Données personnelles
|
||||
|
||||
Le Locataire autorise le Loueur à collecter et conserver les données nécessaires à l’exécution du présent contrat, notamment son identité, ses coordonnées, son permis de conduire, les informations de paiement, les documents liés au véhicule, les états des lieux et les données relatives aux éventuels incidents.
|
||||
|
||||
Ces données sont utilisées pour :
|
||||
|
||||
- l’exécution du contrat ;
|
||||
- la facturation ;
|
||||
- la gestion des sinistres ;
|
||||
- la gestion des infractions ;
|
||||
- les obligations comptables, fiscales et légales ;
|
||||
- la défense des droits du Loueur en cas de litige.
|
||||
|
||||
Le Loueur s’engage à protéger les données personnelles du Locataire, à limiter leur accès aux personnes habilitées et à les conserver uniquement pendant la durée nécessaire aux finalités prévues et aux obligations légales applicables.
|
||||
|
||||
---
|
||||
|
||||
## 21. Documents annexés
|
||||
|
||||
Les documents suivants sont annexés au présent contrat :
|
||||
|
||||
- Copie CIN ou passeport du Locataire ;
|
||||
- Copie du permis de conduire ;
|
||||
- Copie permis international, le cas échéant ;
|
||||
- État des lieux de départ ;
|
||||
- État des lieux de retour ;
|
||||
- Photos du véhicule au départ ;
|
||||
- Photos du véhicule au retour ;
|
||||
- Copie de la carte grise ;
|
||||
- Copie de l’attestation d’assurance ;
|
||||
- Justificatif autorisant l’exploitation du véhicule par le Loueur, si le véhicule n’appartient pas directement au Loueur ;
|
||||
- Reçu de paiement ;
|
||||
- Justificatif de dépôt de garantie ;
|
||||
- Conditions générales de location, le cas échéant.
|
||||
- Liste des références légales et réglementaires applicables ;
|
||||
|
||||
Les annexes font partie intégrante du présent contrat.
|
||||
|
||||
---
|
||||
|
||||
## 22. Loi applicable et juridiction compétente
|
||||
|
||||
Le présent contrat est soumis au droit marocain.
|
||||
|
||||
En cas de litige relatif à sa validité, son interprétation, son exécution ou sa résiliation, les Parties s’efforceront de trouver une solution amiable.
|
||||
|
||||
À défaut d’accord amiable, le litige sera porté devant le tribunal compétent du ressort de :
|
||||
|
||||
**[Ville du siège de l’agence / tribunal compétent]**
|
||||
|
||||
---
|
||||
|
||||
## 23. Déclaration finale
|
||||
|
||||
Le Locataire déclare :
|
||||
|
||||
- avoir lu et compris le présent contrat ;
|
||||
- avoir reçu toutes les informations utiles avant signature ;
|
||||
- avoir vérifié l’état du véhicule au départ ;
|
||||
- être titulaire d’un permis valide ;
|
||||
- s’engager à respecter les conditions du présent contrat ;
|
||||
- accepter les prix, caution, franchises, pénalités et frais indiqués.
|
||||
|
||||
**Fait à :** [●]
|
||||
**Le :** [●]
|
||||
**En deux exemplaires originaux.**
|
||||
|
||||
Chaque page du présent contrat doit être paraphée par les deux Parties.
|
||||
|
||||
### Signature du Loueur
|
||||
|
||||
**Nom :** [●]
|
||||
**Signature et cachet :**
|
||||
|
||||
<br><br>
|
||||
|
||||
### Signature du Locataire
|
||||
|
||||
**Nom :** [●]
|
||||
**Signature :**
|
||||
|
||||
<br><br>
|
||||
|
||||
---
|
||||
|
||||
# Annexe 1 — État des lieux de départ
|
||||
|
||||
**Contrat n° :** [●]
|
||||
**Date :** [●]
|
||||
**Heure :** [●]
|
||||
**Lieu :** [●]
|
||||
|
||||
**Véhicule :** [Marque / Modèle]
|
||||
**Immatriculation :** [●]
|
||||
**Kilométrage départ :** [●] km
|
||||
**Carburant départ :** [●]
|
||||
|
||||
| Élément | État au départ | Observations |
|
||||
|---|---|---|
|
||||
| Carrosserie avant | Bon / Rayé / Endommagé | [●] |
|
||||
| Carrosserie arrière | Bon / Rayé / Endommagé | [●] |
|
||||
| Côté droit | Bon / Rayé / Endommagé | [●] |
|
||||
| Côté gauche | Bon / Rayé / Endommagé | [●] |
|
||||
| Pare-brise | Bon / Impact / Fissure | [●] |
|
||||
| Vitres | Bon / Endommagé | [●] |
|
||||
| Pneus | Bon / Usé / Endommagé | [●] |
|
||||
| Jantes | Bon / Rayé / Endommagé | [●] |
|
||||
| Intérieur | Bon / Taché / Endommagé | [●] |
|
||||
| Sièges | Bon / Taché / Déchiré | [●] |
|
||||
| Tableau de bord | Bon / Endommagé | [●] |
|
||||
| Climatisation | Fonctionne / Ne fonctionne pas | [●] |
|
||||
| Feux | Fonctionnent / Défaut | [●] |
|
||||
| Roue de secours | Présente / Absente | [●] |
|
||||
| Outillage | Présent / Absent | [●] |
|
||||
| Carte grise | Présente / Absente | [●] |
|
||||
| Assurance | Présente / Absente | [●] |
|
||||
| Clés | [Nombre] | [●] |
|
||||
|
||||
**Photos prises au départ :** Oui / Non
|
||||
**Nombre de photos :** [●]
|
||||
|
||||
**Signature du Loueur :**
|
||||
|
||||
<br><br>
|
||||
|
||||
**Signature du Locataire :**
|
||||
|
||||
<br><br>
|
||||
|
||||
---
|
||||
|
||||
# Annexe 2 — État des lieux de retour
|
||||
|
||||
**Contrat n° :** [●]
|
||||
**Date :** [●]
|
||||
**Heure :** [●]
|
||||
**Lieu :** [●]
|
||||
|
||||
**Kilométrage retour :** [●] km
|
||||
**Kilométrage parcouru :** [●] km
|
||||
**Kilométrage inclus :** [●] km
|
||||
**Kilométrage supplémentaire :** [●] km
|
||||
**Montant km supplémentaires :** [●] MAD
|
||||
|
||||
**Carburant retour :** [●]
|
||||
**Carburant manquant :** Oui / Non
|
||||
**Montant carburant :** [●] MAD
|
||||
|
||||
| Élément | État au retour | Nouveau dommage ? | Observations |
|
||||
|---|---|---|---|
|
||||
| Carrosserie avant | Bon / Rayé / Endommagé | Oui / Non | [●] |
|
||||
| Carrosserie arrière | Bon / Rayé / Endommagé | Oui / Non | [●] |
|
||||
| Côté droit | Bon / Rayé / Endommagé | Oui / Non | [●] |
|
||||
| Côté gauche | Bon / Rayé / Endommagé | Oui / Non | [●] |
|
||||
| Pare-brise | Bon / Impact / Fissure | Oui / Non | [●] |
|
||||
| Vitres | Bon / Endommagé | Oui / Non | [●] |
|
||||
| Pneus | Bon / Usé / Endommagé | Oui / Non | [●] |
|
||||
| Jantes | Bon / Rayé / Endommagé | Oui / Non | [●] |
|
||||
| Intérieur | Bon / Taché / Endommagé | Oui / Non | [●] |
|
||||
| Sièges | Bon / Taché / Déchiré | Oui / Non | [●] |
|
||||
| Documents | Complets / Manquants | Oui / Non | [●] |
|
||||
| Clés | Restituées / Manquantes | Oui / Non | [●] |
|
||||
|
||||
## Frais constatés au retour
|
||||
|
||||
| Frais | Montant |
|
||||
|---|---:|
|
||||
| Dommages | [●] MAD |
|
||||
| Franchise assurance | [●] MAD |
|
||||
| Kilométrage supplémentaire | [●] MAD |
|
||||
| Carburant | [●] MAD |
|
||||
| Nettoyage | [●] MAD |
|
||||
| Retard | [●] MAD |
|
||||
| Autres frais | [●] MAD |
|
||||
|
||||
**Total à retenir sur la caution : [●] MAD**
|
||||
**Solde de caution à restituer : [●] MAD**
|
||||
|
||||
**Photos prises au retour :** Oui / Non
|
||||
**Nombre de photos :** [●]
|
||||
|
||||
**Signature du Loueur :**
|
||||
|
||||
<br><br>
|
||||
|
||||
**Signature du Locataire :**
|
||||
|
||||
<br><br>
|
||||
|
||||
---
|
||||
|
||||
# Checklist pratique avant signature
|
||||
|
||||
- Ne laisser aucun champ `[●]` vide dans la version signée.
|
||||
- Faire parapher chaque page par les deux Parties.
|
||||
- Joindre la copie CIN ou passeport du Locataire.
|
||||
- Joindre la copie du permis de conduire.
|
||||
- Photographier le compteur et le niveau de carburant au départ et au retour.
|
||||
- Photographier toutes les faces du véhicule au départ et au retour.
|
||||
- Écrire clairement le montant de la caution.
|
||||
- Mentionner la compagnie d’assurance, le numéro de police et la franchise.
|
||||
- Conserver une preuve de paiement.
|
||||
- Conserver les états des lieux signés.
|
||||
|
||||
---
|
||||
|
||||
# Note importante
|
||||
|
||||
Ce modèle est un document pratique destiné à structurer une location de voiture au Maroc. Il doit être adapté à l’activité réelle du Loueur, à ses conditions d’assurance, à son statut fiscal et aux règles applicables. Pour un usage commercial régulier, une validation par un juriste ou avocat marocain est recommandée.
|
||||
|
||||
|
||||
---
|
||||
|
||||
# Références légales et réglementaires applicables
|
||||
|
||||
Les références suivantes sont indiquées à titre de base juridique générale pour l’activité de location de véhicules sans chauffeur au Maroc. Elles doivent être vérifiées et actualisées selon les textes officiels en vigueur, notamment le Bulletin Officiel et les circulaires ou cahiers des charges applicables.
|
||||
|
||||
## Textes principaux relatifs à la location de véhicules
|
||||
|
||||
| Référence | Objet | Utilité pour le présent contrat |
|
||||
|---|---|---|
|
||||
| **Dahir n° 1-63-260 du 12 novembre 1963** | Transports par véhicules automobiles sur route | Cadre général applicable au transport routier et aux activités connexes. |
|
||||
| **Décret n° 2.69.351 du 4 avril 1970** | Conditions d’exploitation des voitures louées sans chauffeur | Texte spécifique encadrant l’exploitation des véhicules loués sans chauffeur. |
|
||||
| **Cahier des charges relatif à la location de voitures sans chauffeur, tel qu’en vigueur** | Conditions administratives, techniques et professionnelles de l’activité | Référence pratique pour l’agrément, la flotte, le responsable d’activité, l’âge des véhicules, les obligations administratives et les sanctions. |
|
||||
|
||||
## Circulation, sécurité routière et véhicules
|
||||
|
||||
| Référence | Objet | Utilité pour le présent contrat |
|
||||
|---|---|---|
|
||||
| **Loi n° 52-05 portant Code de la route** | Règles de circulation, infractions, sécurité routière, véhicules et conducteurs | Encadre l’usage du véhicule, les infractions, la responsabilité du conducteur et les obligations de sécurité. |
|
||||
| **Dahir n° 1-10-07 du 11 février 2010** | Promulgation de la Loi n° 52-05 portant Code de la route | Texte de promulgation du Code de la route. |
|
||||
|
||||
## Assurance, responsabilité et obligations contractuelles
|
||||
|
||||
| Référence | Objet | Utilité pour le présent contrat |
|
||||
|---|---|---|
|
||||
| **Loi n° 17-99 portant Code des assurances** | Assurance automobile, garanties, exclusions, franchises et responsabilité assurantielle | Encadre les assurances liées au véhicule loué, les garanties, les exclusions et les franchises. |
|
||||
| **Dahir des Obligations et des Contrats du 12 août 1913** | Règles générales des contrats, obligations, responsabilité civile, consentement et inexécution | Base générale du contrat de location et de la responsabilité des Parties. |
|
||||
|
||||
## Droit commercial et structure de l’agence
|
||||
|
||||
| Référence | Objet | Utilité pour le présent contrat |
|
||||
|---|---|---|
|
||||
| **Loi n° 15-95 formant Code de commerce** | Activité commerciale, commerçants, obligations commerciales, registre de commerce | Applicable à l’activité commerciale du Loueur. |
|
||||
| **Loi n° 5-96** | Sociétés à responsabilité limitée, sociétés en nom collectif, sociétés en commandite simple, sociétés en commandite par actions et sociétés en participation | Applicable si le Loueur est constitué sous l’une de ces formes, notamment SARL. |
|
||||
| **Loi n° 17-95 relative aux sociétés anonymes** | Sociétés anonymes | Applicable si le Loueur est constitué sous forme de société anonyme. |
|
||||
| **Loi n° 69-21 relative aux délais de paiement** | Délais de paiement entre professionnels | Applicable notamment aux locations ou relations commerciales B2B. |
|
||||
|
||||
## Protection du consommateur, données personnelles et signature électronique
|
||||
|
||||
| Référence | Objet | Utilité pour le présent contrat |
|
||||
|---|---|---|
|
||||
| **Loi n° 31-08 édictant des mesures de protection du consommateur** | Information du consommateur, transparence des prix, clauses abusives, droits du client | Applicable lorsque le Locataire agit comme consommateur. |
|
||||
| **Loi n° 09-08 relative à la protection des personnes physiques à l’égard du traitement des données à caractère personnel** | Collecte, traitement, conservation et protection des données personnelles | Applicable aux données du Locataire : CIN, passeport, permis, coordonnées, signatures, documents et photos. |
|
||||
| **Loi n° 53-05 relative à l’échange électronique de données juridiques** | Documents électroniques, signature électronique et valeur juridique des échanges numériques | Applicable si le contrat est signé, transmis ou conservé électroniquement. |
|
||||
|
||||
## Obligations sociales, fiscales et administratives
|
||||
|
||||
| Référence | Objet | Utilité pour le présent contrat |
|
||||
|---|---|---|
|
||||
| **Dahir n° 1-72-184 relatif au régime de sécurité sociale** | Régime CNSS et obligations sociales | Pertinent pour la conformité sociale du Loueur lorsqu’il emploie du personnel. |
|
||||
| **Loi n° 47-06 relative à la fiscalité des collectivités locales** | Fiscalité locale, taxe professionnelle et obligations locales | Pertinente pour les obligations fiscales locales de l’agence. |
|
||||
| **Code Général des Impôts** | TVA, impôt sur les sociétés, impôt sur le revenu, facturation et obligations fiscales | Pertinent pour la facturation, les déclarations fiscales et le traitement de la TVA. |
|
||||
|
||||
## Clause de prudence juridique
|
||||
|
||||
Le présent contrat doit être interprété et appliqué conformément aux textes marocains en vigueur à la date de signature.
|
||||
|
||||
Lorsque le présent contrat mentionne le **cahier des charges relatif à la location de voitures sans chauffeur**, cette référence vise le cahier des charges applicable tel qu’en vigueur, y compris ses modifications, circulaires d’application ou textes complémentaires.
|
||||
|
||||
En cas de contradiction entre le présent contrat et une disposition légale ou réglementaire impérative, la disposition légale ou réglementaire prévaut. Les autres clauses du contrat demeurent applicables dans la mesure permise par la loi.
|
||||
|
||||
|
||||
---
|
||||
|
||||
# Checklist interne de conformité du Loueur
|
||||
|
||||
Cette checklist est destinée au Loueur et ne remplace pas les documents administratifs obligatoires.
|
||||
|
||||
- Agrément / autorisation d’exercice disponible.
|
||||
- Responsable réglementaire désigné.
|
||||
- Casier judiciaire conforme, si exigé.
|
||||
- Société inscrite à la CNSS, si applicable.
|
||||
- Siège social ou local professionnel justifié.
|
||||
- Capital social conforme au seuil applicable.
|
||||
- Flotte conforme au seuil réglementaire applicable.
|
||||
- Âge des véhicules conforme selon motorisation.
|
||||
- Documents de propriété ou d’exploitation des véhicules disponibles.
|
||||
- Assurance et visite technique valides pour chaque véhicule.
|
||||
- Procédure de déclaration en cas de suspension ou cessation d’activité.
|
||||
+493
@@ -0,0 +1,493 @@
|
||||
CREATE TYPE "BillingInvoiceTerms" AS ENUM (
|
||||
'DUE_ON_RECEIPT',
|
||||
'NET_7',
|
||||
'NET_15',
|
||||
'NET_30',
|
||||
'NET_45',
|
||||
'NET_60'
|
||||
);
|
||||
|
||||
CREATE TYPE "BillingInvoiceStatus" AS ENUM (
|
||||
'DRAFT',
|
||||
'OPEN',
|
||||
'PAID',
|
||||
'PARTIALLY_PAID',
|
||||
'PAYMENT_PENDING',
|
||||
'PAST_DUE',
|
||||
'VOID',
|
||||
'UNCOLLECTIBLE',
|
||||
'REFUNDED',
|
||||
'PARTIALLY_REFUNDED'
|
||||
);
|
||||
|
||||
CREATE TYPE "BillingInvoiceType" AS ENUM (
|
||||
'SUBSCRIPTION_INITIAL',
|
||||
'SUBSCRIPTION_RENEWAL',
|
||||
'TRIAL_CONVERSION',
|
||||
'SUBSCRIPTION_UPGRADE',
|
||||
'SUBSCRIPTION_DOWNGRADE_CREDIT',
|
||||
'USAGE_BASED',
|
||||
'ONE_TIME',
|
||||
'MANUAL',
|
||||
'CORRECTION',
|
||||
'CANCELLATION_FEE'
|
||||
);
|
||||
|
||||
CREATE TYPE "BillingLineItemType" AS ENUM (
|
||||
'SUBSCRIPTION_FEE',
|
||||
'SEAT_FEE',
|
||||
'USAGE_FEE',
|
||||
'SETUP_FEE',
|
||||
'DISCOUNT',
|
||||
'TAX',
|
||||
'CREDIT',
|
||||
'PRORATION',
|
||||
'REFUND_ADJUSTMENT',
|
||||
'MANUAL_ADJUSTMENT',
|
||||
'PROFESSIONAL_SERVICES',
|
||||
'CANCELLATION_FEE'
|
||||
);
|
||||
|
||||
CREATE TYPE "BillingPaymentMethodType" AS ENUM (
|
||||
'CARD',
|
||||
'ACH_DEBIT',
|
||||
'BANK_TRANSFER',
|
||||
'WIRE_TRANSFER',
|
||||
'MANUAL_INVOICE',
|
||||
'PURCHASE_ORDER'
|
||||
);
|
||||
|
||||
CREATE TYPE "BillingPaymentIntentStatus" AS ENUM (
|
||||
'REQUIRES_PAYMENT_METHOD',
|
||||
'REQUIRES_CONFIRMATION',
|
||||
'REQUIRES_ACTION',
|
||||
'PROCESSING',
|
||||
'SUCCEEDED',
|
||||
'FAILED',
|
||||
'CANCELED',
|
||||
'REFUNDED',
|
||||
'PARTIALLY_REFUNDED'
|
||||
);
|
||||
|
||||
CREATE TYPE "BillingPaymentAttemptStatus" AS ENUM (
|
||||
'PENDING',
|
||||
'PROCESSING',
|
||||
'SUCCEEDED',
|
||||
'FAILED',
|
||||
'CANCELED',
|
||||
'REFUNDED',
|
||||
'PARTIALLY_REFUNDED'
|
||||
);
|
||||
|
||||
CREATE TYPE "BillingRefundStatus" AS ENUM (
|
||||
'PENDING',
|
||||
'SUCCEEDED',
|
||||
'FAILED'
|
||||
);
|
||||
|
||||
CREATE TYPE "BillingCreditNoteStatus" AS ENUM (
|
||||
'ISSUED',
|
||||
'APPLIED',
|
||||
'VOID'
|
||||
);
|
||||
|
||||
CREATE SEQUENCE "billing_invoice_sequence_seq" START 1;
|
||||
|
||||
CREATE TABLE "billing_accounts" (
|
||||
"id" TEXT NOT NULL,
|
||||
"companyId" TEXT NOT NULL,
|
||||
"isPrimary" BOOLEAN NOT NULL DEFAULT true,
|
||||
"legalName" TEXT NOT NULL,
|
||||
"billingEmail" TEXT NOT NULL,
|
||||
"billingAddress" JSONB,
|
||||
"taxId" TEXT,
|
||||
"taxExempt" BOOLEAN NOT NULL DEFAULT false,
|
||||
"defaultCurrency" TEXT NOT NULL DEFAULT 'MAD',
|
||||
"defaultPaymentMethodId" TEXT,
|
||||
"invoiceTerms" "BillingInvoiceTerms" NOT NULL DEFAULT 'DUE_ON_RECEIPT',
|
||||
"netTermsDays" INTEGER NOT NULL DEFAULT 0,
|
||||
"providerCustomerId" TEXT,
|
||||
"dunningPaused" BOOLEAN NOT NULL DEFAULT false,
|
||||
"dunningPausedAt" TIMESTAMP(3),
|
||||
"dunningPausedBy" TEXT,
|
||||
"metadata" JSONB NOT NULL DEFAULT '{}',
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "billing_accounts_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE "billing_payment_methods" (
|
||||
"id" TEXT NOT NULL,
|
||||
"billingAccountId" TEXT NOT NULL,
|
||||
"type" "BillingPaymentMethodType" NOT NULL,
|
||||
"providerPaymentMethodId" TEXT,
|
||||
"label" TEXT,
|
||||
"brand" TEXT,
|
||||
"last4" TEXT,
|
||||
"expMonth" INTEGER,
|
||||
"expYear" INTEGER,
|
||||
"isDefault" BOOLEAN NOT NULL DEFAULT false,
|
||||
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||
"metadata" JSONB NOT NULL DEFAULT '{}',
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "billing_payment_methods_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE "billing_invoices" (
|
||||
"id" TEXT NOT NULL,
|
||||
"billingAccountId" TEXT NOT NULL,
|
||||
"companyId" TEXT NOT NULL,
|
||||
"subscriptionId" TEXT,
|
||||
"invoiceNumber" TEXT,
|
||||
"invoiceSequence" INTEGER,
|
||||
"invoiceType" "BillingInvoiceType" NOT NULL,
|
||||
"status" "BillingInvoiceStatus" NOT NULL DEFAULT 'DRAFT',
|
||||
"currency" TEXT NOT NULL DEFAULT 'MAD',
|
||||
"subtotalAmount" INTEGER NOT NULL DEFAULT 0,
|
||||
"discountAmount" INTEGER NOT NULL DEFAULT 0,
|
||||
"creditAmount" INTEGER NOT NULL DEFAULT 0,
|
||||
"taxAmount" INTEGER NOT NULL DEFAULT 0,
|
||||
"totalAmount" INTEGER NOT NULL DEFAULT 0,
|
||||
"amountPaid" INTEGER NOT NULL DEFAULT 0,
|
||||
"amountDue" INTEGER NOT NULL DEFAULT 0,
|
||||
"invoiceDate" TIMESTAMP(3),
|
||||
"dueAt" TIMESTAMP(3),
|
||||
"finalizedAt" TIMESTAMP(3),
|
||||
"paidAt" TIMESTAMP(3),
|
||||
"voidedAt" TIMESTAMP(3),
|
||||
"markedUncollectibleAt" TIMESTAMP(3),
|
||||
"billingName" TEXT,
|
||||
"billingEmail" TEXT,
|
||||
"billingAddress" JSONB,
|
||||
"providerInvoiceId" TEXT,
|
||||
"paymentProvider" "PaymentProvider",
|
||||
"isSubscriptionBlocking" BOOLEAN NOT NULL DEFAULT false,
|
||||
"adminReason" TEXT,
|
||||
"metadata" JSONB NOT NULL DEFAULT '{}',
|
||||
"createdByAdminId" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "billing_invoices_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE "billing_invoice_line_items" (
|
||||
"id" TEXT NOT NULL,
|
||||
"invoiceId" TEXT NOT NULL,
|
||||
"subscriptionId" TEXT,
|
||||
"plan" "Plan",
|
||||
"type" "BillingLineItemType" NOT NULL,
|
||||
"description" TEXT NOT NULL,
|
||||
"quantity" INTEGER NOT NULL DEFAULT 1,
|
||||
"unitAmount" INTEGER NOT NULL,
|
||||
"amount" INTEGER NOT NULL,
|
||||
"currency" TEXT NOT NULL DEFAULT 'MAD',
|
||||
"periodStart" TIMESTAMP(3),
|
||||
"periodEnd" TIMESTAMP(3),
|
||||
"metadata" JSONB NOT NULL DEFAULT '{}',
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "billing_invoice_line_items_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE "billing_payment_intents" (
|
||||
"id" TEXT NOT NULL,
|
||||
"invoiceId" TEXT NOT NULL,
|
||||
"billingAccountId" TEXT NOT NULL,
|
||||
"paymentMethodId" TEXT,
|
||||
"providerPaymentIntentId" TEXT,
|
||||
"status" "BillingPaymentIntentStatus" NOT NULL DEFAULT 'REQUIRES_PAYMENT_METHOD',
|
||||
"amount" INTEGER NOT NULL,
|
||||
"currency" TEXT NOT NULL DEFAULT 'MAD',
|
||||
"metadata" JSONB NOT NULL DEFAULT '{}',
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "billing_payment_intents_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE "billing_payment_attempts" (
|
||||
"id" TEXT NOT NULL,
|
||||
"invoiceId" TEXT NOT NULL,
|
||||
"billingAccountId" TEXT NOT NULL,
|
||||
"paymentIntentId" TEXT,
|
||||
"paymentMethodId" TEXT,
|
||||
"providerPaymentId" TEXT,
|
||||
"status" "BillingPaymentAttemptStatus" NOT NULL,
|
||||
"amount" INTEGER NOT NULL,
|
||||
"currency" TEXT NOT NULL DEFAULT 'MAD',
|
||||
"failureCode" TEXT,
|
||||
"failureMessage" TEXT,
|
||||
"attemptedAt" TIMESTAMP(3) NOT NULL,
|
||||
"metadata" JSONB NOT NULL DEFAULT '{}',
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "billing_payment_attempts_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE "billing_credit_balances" (
|
||||
"id" TEXT NOT NULL,
|
||||
"billingAccountId" TEXT NOT NULL,
|
||||
"currency" TEXT NOT NULL DEFAULT 'MAD',
|
||||
"balanceAmount" INTEGER NOT NULL DEFAULT 0,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "billing_credit_balances_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE "billing_credit_ledger_entries" (
|
||||
"id" TEXT NOT NULL,
|
||||
"billingAccountId" TEXT NOT NULL,
|
||||
"invoiceId" TEXT,
|
||||
"type" TEXT NOT NULL,
|
||||
"amount" INTEGER NOT NULL,
|
||||
"currency" TEXT NOT NULL DEFAULT 'MAD',
|
||||
"reason" TEXT NOT NULL,
|
||||
"createdBy" TEXT,
|
||||
"metadata" JSONB NOT NULL DEFAULT '{}',
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "billing_credit_ledger_entries_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE "billing_credit_notes" (
|
||||
"id" TEXT NOT NULL,
|
||||
"invoiceId" TEXT NOT NULL,
|
||||
"billingAccountId" TEXT NOT NULL,
|
||||
"amount" INTEGER NOT NULL,
|
||||
"currency" TEXT NOT NULL DEFAULT 'MAD',
|
||||
"reason" TEXT NOT NULL,
|
||||
"status" "BillingCreditNoteStatus" NOT NULL DEFAULT 'ISSUED',
|
||||
"createdBy" TEXT,
|
||||
"metadata" JSONB NOT NULL DEFAULT '{}',
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "billing_credit_notes_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE "billing_refunds" (
|
||||
"id" TEXT NOT NULL,
|
||||
"invoiceId" TEXT NOT NULL,
|
||||
"paymentAttemptId" TEXT,
|
||||
"billingAccountId" TEXT NOT NULL,
|
||||
"amount" INTEGER NOT NULL,
|
||||
"currency" TEXT NOT NULL DEFAULT 'MAD',
|
||||
"reason" TEXT NOT NULL,
|
||||
"status" "BillingRefundStatus" NOT NULL DEFAULT 'PENDING',
|
||||
"providerRefundId" TEXT,
|
||||
"createdBy" TEXT,
|
||||
"metadata" JSONB NOT NULL DEFAULT '{}',
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "billing_refunds_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE "billing_tax_records" (
|
||||
"id" TEXT NOT NULL,
|
||||
"invoiceId" TEXT NOT NULL,
|
||||
"jurisdiction" TEXT,
|
||||
"taxRate" DOUBLE PRECISION,
|
||||
"taxAmount" INTEGER NOT NULL,
|
||||
"taxType" TEXT,
|
||||
"taxExempt" BOOLEAN NOT NULL DEFAULT false,
|
||||
"exemptionReason" TEXT,
|
||||
"providerTaxId" TEXT,
|
||||
"metadata" JSONB NOT NULL DEFAULT '{}',
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "billing_tax_records_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE TABLE "billing_events" (
|
||||
"id" TEXT NOT NULL,
|
||||
"billingAccountId" TEXT,
|
||||
"invoiceId" TEXT,
|
||||
"subscriptionId" TEXT,
|
||||
"companyId" TEXT,
|
||||
"eventType" TEXT NOT NULL,
|
||||
"source" TEXT NOT NULL,
|
||||
"payload" JSONB NOT NULL DEFAULT '{}',
|
||||
"occurredAt" TIMESTAMP(3) NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "billing_events_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
ALTER TABLE "subscription_invoices"
|
||||
ADD COLUMN "billingInvoiceId" TEXT;
|
||||
|
||||
CREATE UNIQUE INDEX "subscription_invoices_billingInvoiceId_key" ON "subscription_invoices"("billingInvoiceId");
|
||||
CREATE UNIQUE INDEX "billing_invoices_invoiceNumber_key" ON "billing_invoices"("invoiceNumber");
|
||||
CREATE UNIQUE INDEX "billing_invoices_invoiceSequence_key" ON "billing_invoices"("invoiceSequence");
|
||||
CREATE UNIQUE INDEX "billing_credit_balances_billingAccountId_currency_key" ON "billing_credit_balances"("billingAccountId", "currency");
|
||||
|
||||
CREATE INDEX "billing_accounts_companyId_idx" ON "billing_accounts"("companyId");
|
||||
CREATE INDEX "billing_accounts_companyId_isPrimary_idx" ON "billing_accounts"("companyId", "isPrimary");
|
||||
CREATE INDEX "billing_payment_methods_billingAccountId_idx" ON "billing_payment_methods"("billingAccountId");
|
||||
CREATE INDEX "billing_invoices_billingAccountId_idx" ON "billing_invoices"("billingAccountId");
|
||||
CREATE INDEX "billing_invoices_companyId_idx" ON "billing_invoices"("companyId");
|
||||
CREATE INDEX "billing_invoices_subscriptionId_idx" ON "billing_invoices"("subscriptionId");
|
||||
CREATE INDEX "billing_invoice_line_items_invoiceId_idx" ON "billing_invoice_line_items"("invoiceId");
|
||||
CREATE INDEX "billing_payment_intents_invoiceId_idx" ON "billing_payment_intents"("invoiceId");
|
||||
CREATE INDEX "billing_payment_intents_billingAccountId_idx" ON "billing_payment_intents"("billingAccountId");
|
||||
CREATE INDEX "billing_payment_attempts_invoiceId_idx" ON "billing_payment_attempts"("invoiceId");
|
||||
CREATE INDEX "billing_payment_attempts_billingAccountId_idx" ON "billing_payment_attempts"("billingAccountId");
|
||||
CREATE INDEX "billing_credit_ledger_entries_billingAccountId_idx" ON "billing_credit_ledger_entries"("billingAccountId");
|
||||
CREATE INDEX "billing_credit_notes_invoiceId_idx" ON "billing_credit_notes"("invoiceId");
|
||||
CREATE INDEX "billing_credit_notes_billingAccountId_idx" ON "billing_credit_notes"("billingAccountId");
|
||||
CREATE INDEX "billing_refunds_invoiceId_idx" ON "billing_refunds"("invoiceId");
|
||||
CREATE INDEX "billing_refunds_billingAccountId_idx" ON "billing_refunds"("billingAccountId");
|
||||
CREATE INDEX "billing_tax_records_invoiceId_idx" ON "billing_tax_records"("invoiceId");
|
||||
CREATE INDEX "billing_events_billingAccountId_idx" ON "billing_events"("billingAccountId");
|
||||
CREATE INDEX "billing_events_invoiceId_idx" ON "billing_events"("invoiceId");
|
||||
CREATE INDEX "billing_events_subscriptionId_idx" ON "billing_events"("subscriptionId");
|
||||
CREATE INDEX "billing_events_companyId_idx" ON "billing_events"("companyId");
|
||||
|
||||
ALTER TABLE "billing_accounts"
|
||||
ADD CONSTRAINT "billing_accounts_companyId_fkey"
|
||||
FOREIGN KEY ("companyId") REFERENCES "companies"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "billing_payment_methods"
|
||||
ADD CONSTRAINT "billing_payment_methods_billingAccountId_fkey"
|
||||
FOREIGN KEY ("billingAccountId") REFERENCES "billing_accounts"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "billing_accounts"
|
||||
ADD CONSTRAINT "billing_accounts_defaultPaymentMethodId_fkey"
|
||||
FOREIGN KEY ("defaultPaymentMethodId") REFERENCES "billing_payment_methods"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "billing_invoices"
|
||||
ADD CONSTRAINT "billing_invoices_billingAccountId_fkey"
|
||||
FOREIGN KEY ("billingAccountId") REFERENCES "billing_accounts"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "billing_invoices"
|
||||
ADD CONSTRAINT "billing_invoices_companyId_fkey"
|
||||
FOREIGN KEY ("companyId") REFERENCES "companies"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "billing_invoices"
|
||||
ADD CONSTRAINT "billing_invoices_subscriptionId_fkey"
|
||||
FOREIGN KEY ("subscriptionId") REFERENCES "subscriptions"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "subscription_invoices"
|
||||
ADD CONSTRAINT "subscription_invoices_billingInvoiceId_fkey"
|
||||
FOREIGN KEY ("billingInvoiceId") REFERENCES "billing_invoices"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "billing_invoice_line_items"
|
||||
ADD CONSTRAINT "billing_invoice_line_items_invoiceId_fkey"
|
||||
FOREIGN KEY ("invoiceId") REFERENCES "billing_invoices"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "billing_payment_intents"
|
||||
ADD CONSTRAINT "billing_payment_intents_invoiceId_fkey"
|
||||
FOREIGN KEY ("invoiceId") REFERENCES "billing_invoices"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "billing_payment_intents"
|
||||
ADD CONSTRAINT "billing_payment_intents_billingAccountId_fkey"
|
||||
FOREIGN KEY ("billingAccountId") REFERENCES "billing_accounts"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "billing_payment_intents"
|
||||
ADD CONSTRAINT "billing_payment_intents_paymentMethodId_fkey"
|
||||
FOREIGN KEY ("paymentMethodId") REFERENCES "billing_payment_methods"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "billing_payment_attempts"
|
||||
ADD CONSTRAINT "billing_payment_attempts_invoiceId_fkey"
|
||||
FOREIGN KEY ("invoiceId") REFERENCES "billing_invoices"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "billing_payment_attempts"
|
||||
ADD CONSTRAINT "billing_payment_attempts_billingAccountId_fkey"
|
||||
FOREIGN KEY ("billingAccountId") REFERENCES "billing_accounts"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "billing_payment_attempts"
|
||||
ADD CONSTRAINT "billing_payment_attempts_paymentIntentId_fkey"
|
||||
FOREIGN KEY ("paymentIntentId") REFERENCES "billing_payment_intents"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "billing_payment_attempts"
|
||||
ADD CONSTRAINT "billing_payment_attempts_paymentMethodId_fkey"
|
||||
FOREIGN KEY ("paymentMethodId") REFERENCES "billing_payment_methods"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "billing_credit_balances"
|
||||
ADD CONSTRAINT "billing_credit_balances_billingAccountId_fkey"
|
||||
FOREIGN KEY ("billingAccountId") REFERENCES "billing_accounts"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "billing_credit_ledger_entries"
|
||||
ADD CONSTRAINT "billing_credit_ledger_entries_billingAccountId_fkey"
|
||||
FOREIGN KEY ("billingAccountId") REFERENCES "billing_accounts"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "billing_credit_ledger_entries"
|
||||
ADD CONSTRAINT "billing_credit_ledger_entries_invoiceId_fkey"
|
||||
FOREIGN KEY ("invoiceId") REFERENCES "billing_invoices"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "billing_credit_notes"
|
||||
ADD CONSTRAINT "billing_credit_notes_invoiceId_fkey"
|
||||
FOREIGN KEY ("invoiceId") REFERENCES "billing_invoices"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "billing_credit_notes"
|
||||
ADD CONSTRAINT "billing_credit_notes_billingAccountId_fkey"
|
||||
FOREIGN KEY ("billingAccountId") REFERENCES "billing_accounts"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "billing_refunds"
|
||||
ADD CONSTRAINT "billing_refunds_invoiceId_fkey"
|
||||
FOREIGN KEY ("invoiceId") REFERENCES "billing_invoices"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "billing_refunds"
|
||||
ADD CONSTRAINT "billing_refunds_paymentAttemptId_fkey"
|
||||
FOREIGN KEY ("paymentAttemptId") REFERENCES "billing_payment_attempts"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "billing_refunds"
|
||||
ADD CONSTRAINT "billing_refunds_billingAccountId_fkey"
|
||||
FOREIGN KEY ("billingAccountId") REFERENCES "billing_accounts"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "billing_tax_records"
|
||||
ADD CONSTRAINT "billing_tax_records_invoiceId_fkey"
|
||||
FOREIGN KEY ("invoiceId") REFERENCES "billing_invoices"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "billing_events"
|
||||
ADD CONSTRAINT "billing_events_billingAccountId_fkey"
|
||||
FOREIGN KEY ("billingAccountId") REFERENCES "billing_accounts"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "billing_events"
|
||||
ADD CONSTRAINT "billing_events_invoiceId_fkey"
|
||||
FOREIGN KEY ("invoiceId") REFERENCES "billing_invoices"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "billing_events"
|
||||
ADD CONSTRAINT "billing_events_subscriptionId_fkey"
|
||||
FOREIGN KEY ("subscriptionId") REFERENCES "subscriptions"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "billing_events"
|
||||
ADD CONSTRAINT "billing_events_companyId_fkey"
|
||||
FOREIGN KEY ("companyId") REFERENCES "companies"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
INSERT INTO "billing_accounts" (
|
||||
"id",
|
||||
"companyId",
|
||||
"isPrimary",
|
||||
"legalName",
|
||||
"billingEmail",
|
||||
"billingAddress",
|
||||
"taxExempt",
|
||||
"defaultCurrency",
|
||||
"invoiceTerms",
|
||||
"netTermsDays",
|
||||
"metadata"
|
||||
)
|
||||
SELECT
|
||||
'ba_' || c."id",
|
||||
c."id",
|
||||
true,
|
||||
c."name",
|
||||
c."email",
|
||||
c."address",
|
||||
false,
|
||||
COALESCE(a."currency", 'MAD'),
|
||||
'DUE_ON_RECEIPT'::"BillingInvoiceTerms",
|
||||
0,
|
||||
'{}'::jsonb
|
||||
FROM "companies" c
|
||||
LEFT JOIN "accounting_settings" a ON a."companyId" = c."id"
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM "billing_accounts" ba
|
||||
WHERE ba."companyId" = c."id" AND ba."isPrimary" = true
|
||||
);
|
||||
@@ -52,6 +52,99 @@ enum InvoiceStatus {
|
||||
VOIDED
|
||||
}
|
||||
|
||||
enum BillingInvoiceTerms {
|
||||
DUE_ON_RECEIPT
|
||||
NET_7
|
||||
NET_15
|
||||
NET_30
|
||||
NET_45
|
||||
NET_60
|
||||
}
|
||||
|
||||
enum BillingInvoiceStatus {
|
||||
DRAFT
|
||||
OPEN
|
||||
PAID
|
||||
PARTIALLY_PAID
|
||||
PAYMENT_PENDING
|
||||
PAST_DUE
|
||||
VOID
|
||||
UNCOLLECTIBLE
|
||||
REFUNDED
|
||||
PARTIALLY_REFUNDED
|
||||
}
|
||||
|
||||
enum BillingInvoiceType {
|
||||
SUBSCRIPTION_INITIAL
|
||||
SUBSCRIPTION_RENEWAL
|
||||
TRIAL_CONVERSION
|
||||
SUBSCRIPTION_UPGRADE
|
||||
SUBSCRIPTION_DOWNGRADE_CREDIT
|
||||
USAGE_BASED
|
||||
ONE_TIME
|
||||
MANUAL
|
||||
CORRECTION
|
||||
CANCELLATION_FEE
|
||||
}
|
||||
|
||||
enum BillingLineItemType {
|
||||
SUBSCRIPTION_FEE
|
||||
SEAT_FEE
|
||||
USAGE_FEE
|
||||
SETUP_FEE
|
||||
DISCOUNT
|
||||
TAX
|
||||
CREDIT
|
||||
PRORATION
|
||||
REFUND_ADJUSTMENT
|
||||
MANUAL_ADJUSTMENT
|
||||
PROFESSIONAL_SERVICES
|
||||
CANCELLATION_FEE
|
||||
}
|
||||
|
||||
enum BillingPaymentMethodType {
|
||||
CARD
|
||||
ACH_DEBIT
|
||||
BANK_TRANSFER
|
||||
WIRE_TRANSFER
|
||||
MANUAL_INVOICE
|
||||
PURCHASE_ORDER
|
||||
}
|
||||
|
||||
enum BillingPaymentIntentStatus {
|
||||
REQUIRES_PAYMENT_METHOD
|
||||
REQUIRES_CONFIRMATION
|
||||
REQUIRES_ACTION
|
||||
PROCESSING
|
||||
SUCCEEDED
|
||||
FAILED
|
||||
CANCELED
|
||||
REFUNDED
|
||||
PARTIALLY_REFUNDED
|
||||
}
|
||||
|
||||
enum BillingPaymentAttemptStatus {
|
||||
PENDING
|
||||
PROCESSING
|
||||
SUCCEEDED
|
||||
FAILED
|
||||
CANCELED
|
||||
REFUNDED
|
||||
PARTIALLY_REFUNDED
|
||||
}
|
||||
|
||||
enum BillingRefundStatus {
|
||||
PENDING
|
||||
SUCCEEDED
|
||||
FAILED
|
||||
}
|
||||
|
||||
enum BillingCreditNoteStatus {
|
||||
ISSUED
|
||||
APPLIED
|
||||
VOID
|
||||
}
|
||||
|
||||
enum PaymentProvider {
|
||||
AMANPAY
|
||||
PAYPAL
|
||||
@@ -340,54 +433,59 @@ model Company {
|
||||
subscriptionPaymentRef String?
|
||||
apiKey String @unique @default(cuid())
|
||||
|
||||
subscription Subscription?
|
||||
brand BrandSettings?
|
||||
employees Employee[]
|
||||
vehicles Vehicle[]
|
||||
offers Offer[]
|
||||
reservations Reservation[]
|
||||
customers Customer[]
|
||||
rentalPayments RentalPayment[]
|
||||
subscriptionInvoices SubscriptionInvoice[]
|
||||
contractSettings ContractSettings?
|
||||
accountingSettings AccountingSettings?
|
||||
insurancePolicies InsurancePolicy[]
|
||||
pricingRules PricingRule[]
|
||||
notifications Notification[] @relation("CompanyNotifications")
|
||||
subscription Subscription?
|
||||
billingAccounts BillingAccount[]
|
||||
billingInvoices BillingInvoice[]
|
||||
brand BrandSettings?
|
||||
employees Employee[]
|
||||
vehicles Vehicle[]
|
||||
offers Offer[]
|
||||
reservations Reservation[]
|
||||
customers Customer[]
|
||||
rentalPayments RentalPayment[]
|
||||
subscriptionInvoices SubscriptionInvoice[]
|
||||
contractSettings ContractSettings?
|
||||
accountingSettings AccountingSettings?
|
||||
insurancePolicies InsurancePolicy[]
|
||||
pricingRules PricingRule[]
|
||||
notifications Notification[] @relation("CompanyNotifications")
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
BillingEvent BillingEvent[]
|
||||
|
||||
@@map("companies")
|
||||
}
|
||||
|
||||
model Subscription {
|
||||
id String @id @default(cuid())
|
||||
companyId String @unique
|
||||
company Company @relation(fields: [companyId], references: [id], onDelete: Cascade)
|
||||
plan Plan @default(STARTER)
|
||||
billingPeriod BillingPeriod @default(MONTHLY)
|
||||
status SubscriptionStatus @default(TRIALING)
|
||||
currency String @default("MAD")
|
||||
trialStartAt DateTime?
|
||||
trialEndAt DateTime?
|
||||
trialUsed Boolean @default(false)
|
||||
currentPeriodStart DateTime?
|
||||
currentPeriodEnd DateTime?
|
||||
paymentPendingSince DateTime?
|
||||
paymentDueAt DateTime?
|
||||
pastDueSince DateTime?
|
||||
suspendedAt DateTime?
|
||||
endedAt DateTime?
|
||||
cancelledAt DateTime?
|
||||
cancelAtPeriodEnd Boolean @default(false)
|
||||
retryCount Int @default(0)
|
||||
maxRetryCount Int @default(5)
|
||||
invoices SubscriptionInvoice[]
|
||||
events SubscriptionEvent[]
|
||||
id String @id @default(cuid())
|
||||
companyId String @unique
|
||||
company Company @relation(fields: [companyId], references: [id], onDelete: Cascade)
|
||||
plan Plan @default(STARTER)
|
||||
billingPeriod BillingPeriod @default(MONTHLY)
|
||||
status SubscriptionStatus @default(TRIALING)
|
||||
currency String @default("MAD")
|
||||
trialStartAt DateTime?
|
||||
trialEndAt DateTime?
|
||||
trialUsed Boolean @default(false)
|
||||
currentPeriodStart DateTime?
|
||||
currentPeriodEnd DateTime?
|
||||
paymentPendingSince DateTime?
|
||||
paymentDueAt DateTime?
|
||||
pastDueSince DateTime?
|
||||
suspendedAt DateTime?
|
||||
endedAt DateTime?
|
||||
cancelledAt DateTime?
|
||||
cancelAtPeriodEnd Boolean @default(false)
|
||||
retryCount Int @default(0)
|
||||
maxRetryCount Int @default(5)
|
||||
invoices SubscriptionInvoice[]
|
||||
billingInvoices BillingInvoice[]
|
||||
events SubscriptionEvent[]
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
BillingEvent BillingEvent[]
|
||||
|
||||
@@map("subscriptions")
|
||||
}
|
||||
@@ -421,6 +519,8 @@ model SubscriptionInvoice {
|
||||
amanpayTransactionId String? @unique
|
||||
paypalCaptureId String? @unique
|
||||
paymentProvider PaymentProvider @default(AMANPAY)
|
||||
billingInvoiceId String? @unique
|
||||
billingInvoice BillingInvoice? @relation(fields: [billingInvoiceId], references: [id])
|
||||
dueAt DateTime?
|
||||
paidAt DateTime?
|
||||
failedAt DateTime?
|
||||
@@ -433,6 +533,314 @@ model SubscriptionInvoice {
|
||||
@@map("subscription_invoices")
|
||||
}
|
||||
|
||||
model BillingAccount {
|
||||
id String @id @default(cuid())
|
||||
companyId String
|
||||
company Company @relation(fields: [companyId], references: [id], onDelete: Cascade)
|
||||
isPrimary Boolean @default(true)
|
||||
legalName String
|
||||
billingEmail String
|
||||
billingAddress Json?
|
||||
taxId String?
|
||||
taxExempt Boolean @default(false)
|
||||
defaultCurrency String @default("MAD")
|
||||
defaultPaymentMethodId String?
|
||||
defaultPaymentMethod BillingPaymentMethod? @relation("BillingAccountDefaultPaymentMethod", fields: [defaultPaymentMethodId], references: [id])
|
||||
invoiceTerms BillingInvoiceTerms @default(DUE_ON_RECEIPT)
|
||||
netTermsDays Int @default(0)
|
||||
providerCustomerId String?
|
||||
dunningPaused Boolean @default(false)
|
||||
dunningPausedAt DateTime?
|
||||
dunningPausedBy String?
|
||||
metadata Json @default("{}")
|
||||
paymentMethods BillingPaymentMethod[] @relation("BillingAccountPaymentMethods")
|
||||
invoices BillingInvoice[]
|
||||
paymentIntents BillingPaymentIntent[]
|
||||
paymentAttempts BillingPaymentAttempt[]
|
||||
creditBalances BillingCreditBalance[]
|
||||
creditLedgerEntries BillingCreditLedgerEntry[]
|
||||
creditNotes BillingCreditNote[]
|
||||
refunds BillingRefund[]
|
||||
events BillingEvent[]
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@index([companyId])
|
||||
@@index([companyId, isPrimary])
|
||||
@@map("billing_accounts")
|
||||
}
|
||||
|
||||
model BillingPaymentMethod {
|
||||
id String @id @default(cuid())
|
||||
billingAccountId String
|
||||
billingAccount BillingAccount @relation("BillingAccountPaymentMethods", fields: [billingAccountId], references: [id], onDelete: Cascade)
|
||||
type BillingPaymentMethodType
|
||||
providerPaymentMethodId String?
|
||||
label String?
|
||||
brand String?
|
||||
last4 String?
|
||||
expMonth Int?
|
||||
expYear Int?
|
||||
isDefault Boolean @default(false)
|
||||
isActive Boolean @default(true)
|
||||
metadata Json @default("{}")
|
||||
defaultForBillingAccounts BillingAccount[] @relation("BillingAccountDefaultPaymentMethod")
|
||||
paymentIntents BillingPaymentIntent[]
|
||||
paymentAttempts BillingPaymentAttempt[]
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@index([billingAccountId])
|
||||
@@map("billing_payment_methods")
|
||||
}
|
||||
|
||||
model BillingInvoice {
|
||||
id String @id @default(cuid())
|
||||
billingAccountId String
|
||||
billingAccount BillingAccount @relation(fields: [billingAccountId], references: [id], onDelete: Cascade)
|
||||
companyId String
|
||||
company Company @relation(fields: [companyId], references: [id], onDelete: Cascade)
|
||||
subscriptionId String?
|
||||
subscription Subscription? @relation(fields: [subscriptionId], references: [id])
|
||||
legacySubscriptionInvoice SubscriptionInvoice?
|
||||
invoiceNumber String? @unique
|
||||
invoiceSequence Int? @unique
|
||||
invoiceType BillingInvoiceType
|
||||
status BillingInvoiceStatus @default(DRAFT)
|
||||
currency String @default("MAD")
|
||||
subtotalAmount Int @default(0)
|
||||
discountAmount Int @default(0)
|
||||
creditAmount Int @default(0)
|
||||
taxAmount Int @default(0)
|
||||
totalAmount Int @default(0)
|
||||
amountPaid Int @default(0)
|
||||
amountDue Int @default(0)
|
||||
invoiceDate DateTime?
|
||||
dueAt DateTime?
|
||||
finalizedAt DateTime?
|
||||
paidAt DateTime?
|
||||
voidedAt DateTime?
|
||||
markedUncollectibleAt DateTime?
|
||||
billingName String?
|
||||
billingEmail String?
|
||||
billingAddress Json?
|
||||
providerInvoiceId String?
|
||||
paymentProvider PaymentProvider?
|
||||
isSubscriptionBlocking Boolean @default(false)
|
||||
adminReason String?
|
||||
metadata Json @default("{}")
|
||||
createdByAdminId String?
|
||||
lineItems BillingInvoiceLineItem[]
|
||||
paymentIntents BillingPaymentIntent[]
|
||||
paymentAttempts BillingPaymentAttempt[]
|
||||
taxRecords BillingTaxRecord[]
|
||||
creditNotes BillingCreditNote[]
|
||||
refunds BillingRefund[]
|
||||
events BillingEvent[]
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
BillingCreditLedgerEntry BillingCreditLedgerEntry[]
|
||||
|
||||
@@index([billingAccountId])
|
||||
@@index([companyId])
|
||||
@@index([subscriptionId])
|
||||
@@map("billing_invoices")
|
||||
}
|
||||
|
||||
model BillingInvoiceLineItem {
|
||||
id String @id @default(cuid())
|
||||
invoiceId String
|
||||
invoice BillingInvoice @relation(fields: [invoiceId], references: [id], onDelete: Cascade)
|
||||
subscriptionId String?
|
||||
plan Plan?
|
||||
type BillingLineItemType
|
||||
description String
|
||||
quantity Int @default(1)
|
||||
unitAmount Int
|
||||
amount Int
|
||||
currency String @default("MAD")
|
||||
periodStart DateTime?
|
||||
periodEnd DateTime?
|
||||
metadata Json @default("{}")
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@index([invoiceId])
|
||||
@@map("billing_invoice_line_items")
|
||||
}
|
||||
|
||||
model BillingPaymentIntent {
|
||||
id String @id @default(cuid())
|
||||
invoiceId String
|
||||
invoice BillingInvoice @relation(fields: [invoiceId], references: [id], onDelete: Cascade)
|
||||
billingAccountId String
|
||||
billingAccount BillingAccount @relation(fields: [billingAccountId], references: [id], onDelete: Cascade)
|
||||
paymentMethodId String?
|
||||
paymentMethod BillingPaymentMethod? @relation(fields: [paymentMethodId], references: [id])
|
||||
providerPaymentIntentId String?
|
||||
status BillingPaymentIntentStatus @default(REQUIRES_PAYMENT_METHOD)
|
||||
amount Int
|
||||
currency String @default("MAD")
|
||||
metadata Json @default("{}")
|
||||
paymentAttempts BillingPaymentAttempt[]
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@index([invoiceId])
|
||||
@@index([billingAccountId])
|
||||
@@map("billing_payment_intents")
|
||||
}
|
||||
|
||||
model BillingPaymentAttempt {
|
||||
id String @id @default(cuid())
|
||||
invoiceId String
|
||||
invoice BillingInvoice @relation(fields: [invoiceId], references: [id], onDelete: Cascade)
|
||||
billingAccountId String
|
||||
billingAccount BillingAccount @relation(fields: [billingAccountId], references: [id], onDelete: Cascade)
|
||||
paymentIntentId String?
|
||||
paymentIntent BillingPaymentIntent? @relation(fields: [paymentIntentId], references: [id])
|
||||
paymentMethodId String?
|
||||
paymentMethod BillingPaymentMethod? @relation(fields: [paymentMethodId], references: [id])
|
||||
providerPaymentId String?
|
||||
status BillingPaymentAttemptStatus
|
||||
amount Int
|
||||
currency String @default("MAD")
|
||||
failureCode String?
|
||||
failureMessage String?
|
||||
attemptedAt DateTime
|
||||
metadata Json @default("{}")
|
||||
refunds BillingRefund[]
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@index([invoiceId])
|
||||
@@index([billingAccountId])
|
||||
@@map("billing_payment_attempts")
|
||||
}
|
||||
|
||||
model BillingCreditBalance {
|
||||
id String @id @default(cuid())
|
||||
billingAccountId String
|
||||
billingAccount BillingAccount @relation(fields: [billingAccountId], references: [id], onDelete: Cascade)
|
||||
currency String @default("MAD")
|
||||
balanceAmount Int @default(0)
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@unique([billingAccountId, currency])
|
||||
@@map("billing_credit_balances")
|
||||
}
|
||||
|
||||
model BillingCreditLedgerEntry {
|
||||
id String @id @default(cuid())
|
||||
billingAccountId String
|
||||
billingAccount BillingAccount @relation(fields: [billingAccountId], references: [id], onDelete: Cascade)
|
||||
invoiceId String?
|
||||
invoice BillingInvoice? @relation(fields: [invoiceId], references: [id], onDelete: SetNull)
|
||||
type String
|
||||
amount Int
|
||||
currency String @default("MAD")
|
||||
reason String
|
||||
createdBy String?
|
||||
metadata Json @default("{}")
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@index([billingAccountId])
|
||||
@@map("billing_credit_ledger_entries")
|
||||
}
|
||||
|
||||
model BillingCreditNote {
|
||||
id String @id @default(cuid())
|
||||
invoiceId String
|
||||
invoice BillingInvoice @relation(fields: [invoiceId], references: [id], onDelete: Cascade)
|
||||
billingAccountId String
|
||||
billingAccount BillingAccount @relation(fields: [billingAccountId], references: [id], onDelete: Cascade)
|
||||
amount Int
|
||||
currency String @default("MAD")
|
||||
reason String
|
||||
status BillingCreditNoteStatus @default(ISSUED)
|
||||
createdBy String?
|
||||
metadata Json @default("{}")
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@index([invoiceId])
|
||||
@@index([billingAccountId])
|
||||
@@map("billing_credit_notes")
|
||||
}
|
||||
|
||||
model BillingRefund {
|
||||
id String @id @default(cuid())
|
||||
invoiceId String
|
||||
invoice BillingInvoice @relation(fields: [invoiceId], references: [id], onDelete: Cascade)
|
||||
paymentAttemptId String?
|
||||
paymentAttempt BillingPaymentAttempt? @relation(fields: [paymentAttemptId], references: [id], onDelete: SetNull)
|
||||
billingAccountId String
|
||||
billingAccount BillingAccount @relation(fields: [billingAccountId], references: [id], onDelete: Cascade)
|
||||
amount Int
|
||||
currency String @default("MAD")
|
||||
reason String
|
||||
status BillingRefundStatus @default(PENDING)
|
||||
providerRefundId String?
|
||||
createdBy String?
|
||||
metadata Json @default("{}")
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@index([invoiceId])
|
||||
@@index([billingAccountId])
|
||||
@@map("billing_refunds")
|
||||
}
|
||||
|
||||
model BillingTaxRecord {
|
||||
id String @id @default(cuid())
|
||||
invoiceId String
|
||||
invoice BillingInvoice @relation(fields: [invoiceId], references: [id], onDelete: Cascade)
|
||||
jurisdiction String?
|
||||
taxRate Float?
|
||||
taxAmount Int
|
||||
taxType String?
|
||||
taxExempt Boolean @default(false)
|
||||
exemptionReason String?
|
||||
providerTaxId String?
|
||||
metadata Json @default("{}")
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@index([invoiceId])
|
||||
@@map("billing_tax_records")
|
||||
}
|
||||
|
||||
model BillingEvent {
|
||||
id String @id @default(cuid())
|
||||
billingAccountId String?
|
||||
billingAccount BillingAccount? @relation(fields: [billingAccountId], references: [id], onDelete: SetNull)
|
||||
invoiceId String?
|
||||
invoice BillingInvoice? @relation(fields: [invoiceId], references: [id], onDelete: SetNull)
|
||||
subscriptionId String?
|
||||
subscription Subscription? @relation(fields: [subscriptionId], references: [id], onDelete: SetNull)
|
||||
companyId String?
|
||||
company Company? @relation(fields: [companyId], references: [id], onDelete: SetNull)
|
||||
eventType String
|
||||
source String
|
||||
payload Json @default("{}")
|
||||
occurredAt DateTime
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@index([billingAccountId])
|
||||
@@index([invoiceId])
|
||||
@@index([subscriptionId])
|
||||
@@index([companyId])
|
||||
@@map("billing_events")
|
||||
}
|
||||
|
||||
model PaymentAttempt {
|
||||
id String @id @default(cuid())
|
||||
invoiceId String
|
||||
@@ -450,19 +858,19 @@ model PaymentAttempt {
|
||||
}
|
||||
|
||||
model BrandSettings {
|
||||
id String @id @default(cuid())
|
||||
companyId String @unique
|
||||
company Company @relation(fields: [companyId], references: [id], onDelete: Cascade)
|
||||
id String @id @default(cuid())
|
||||
companyId String @unique
|
||||
company Company @relation(fields: [companyId], references: [id], onDelete: Cascade)
|
||||
displayName String
|
||||
tagline String?
|
||||
logoUrl String?
|
||||
faviconUrl String?
|
||||
heroImageUrl String?
|
||||
primaryColor String @default("#1A56DB")
|
||||
accentColor String @default("#F59E0B")
|
||||
subdomain String @unique
|
||||
customDomain String? @unique
|
||||
customDomainVerified Boolean @default(false)
|
||||
primaryColor String @default("#1A56DB")
|
||||
accentColor String @default("#F59E0B")
|
||||
subdomain String @unique
|
||||
customDomain String? @unique
|
||||
customDomainVerified Boolean @default(false)
|
||||
customDomainAddedAt DateTime?
|
||||
publicEmail String?
|
||||
publicPhone String?
|
||||
@@ -473,17 +881,17 @@ model BrandSettings {
|
||||
whatsappNumber String?
|
||||
facebookUrl String?
|
||||
instagramUrl String?
|
||||
defaultLocale String @default("en")
|
||||
defaultCurrency String @default("MAD")
|
||||
defaultLocale String @default("en")
|
||||
defaultCurrency String @default("MAD")
|
||||
amanpayMerchantId String?
|
||||
amanpaySecretKey String?
|
||||
paypalEmail String?
|
||||
paypalMerchantId String?
|
||||
paymentMethodsEnabled PaymentProvider[]
|
||||
isListedOnMarketplace Boolean @default(true)
|
||||
isListedOnMarketplace Boolean @default(true)
|
||||
marketplaceRating Float?
|
||||
homePageConfig Json? @map("home_page_config")
|
||||
menuConfig Json? @map("menu_config")
|
||||
homePageConfig Json? @map("home_page_config")
|
||||
menuConfig Json? @map("menu_config")
|
||||
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@ -491,20 +899,20 @@ model BrandSettings {
|
||||
}
|
||||
|
||||
model Employee {
|
||||
id String @id @default(cuid())
|
||||
companyId String
|
||||
company Company @relation(fields: [companyId], references: [id], onDelete: Cascade)
|
||||
clerkUserId String @unique
|
||||
firstName String
|
||||
lastName String
|
||||
email String
|
||||
phone String?
|
||||
passwordHash String?
|
||||
passwordResetToken String? @unique
|
||||
passwordResetExpiresAt DateTime?
|
||||
role EmployeeRole @default(AGENT)
|
||||
preferredLanguage String @default("en")
|
||||
isActive Boolean @default(true)
|
||||
id String @id @default(cuid())
|
||||
companyId String
|
||||
company Company @relation(fields: [companyId], references: [id], onDelete: Cascade)
|
||||
clerkUserId String @unique
|
||||
firstName String
|
||||
lastName String
|
||||
email String
|
||||
phone String?
|
||||
passwordHash String?
|
||||
passwordResetToken String? @unique
|
||||
passwordResetExpiresAt DateTime?
|
||||
role EmployeeRole @default(AGENT)
|
||||
preferredLanguage String @default("en")
|
||||
isActive Boolean @default(true)
|
||||
|
||||
notifications Notification[] @relation("EmployeeNotifications")
|
||||
notificationPreferences NotificationPreference[]
|
||||
@@ -517,34 +925,34 @@ model Employee {
|
||||
}
|
||||
|
||||
model Vehicle {
|
||||
id String @id @default(cuid())
|
||||
companyId String
|
||||
company Company @relation(fields: [companyId], references: [id], onDelete: Cascade)
|
||||
make String
|
||||
model String
|
||||
year Int
|
||||
color String
|
||||
licensePlate String
|
||||
vin String?
|
||||
category VehicleCategory @default(ECONOMY)
|
||||
seats Int @default(5)
|
||||
transmission Transmission @default(AUTOMATIC)
|
||||
fuelType FuelType @default(GASOLINE)
|
||||
features String[]
|
||||
dailyRate Int
|
||||
status VehicleStatus @default(AVAILABLE)
|
||||
photos String[]
|
||||
mileage Int?
|
||||
notes String?
|
||||
isPublished Boolean @default(true)
|
||||
id String @id @default(cuid())
|
||||
companyId String
|
||||
company Company @relation(fields: [companyId], references: [id], onDelete: Cascade)
|
||||
make String
|
||||
model String
|
||||
year Int
|
||||
color String
|
||||
licensePlate String
|
||||
vin String?
|
||||
category VehicleCategory @default(ECONOMY)
|
||||
seats Int @default(5)
|
||||
transmission Transmission @default(AUTOMATIC)
|
||||
fuelType FuelType @default(GASOLINE)
|
||||
features String[]
|
||||
dailyRate Int
|
||||
status VehicleStatus @default(AVAILABLE)
|
||||
photos String[]
|
||||
mileage Int?
|
||||
notes String?
|
||||
isPublished Boolean @default(true)
|
||||
pickupLocations String[] @default([])
|
||||
allowDifferentDropoff Boolean @default(false)
|
||||
dropoffLocations String[] @default([])
|
||||
|
||||
reservations Reservation[]
|
||||
maintenance MaintenanceLog[]
|
||||
offerVehicles OfferVehicle[]
|
||||
calendarBlocks VehicleCalendarBlock[]
|
||||
reservations Reservation[]
|
||||
maintenance MaintenanceLog[]
|
||||
offerVehicles OfferVehicle[]
|
||||
calendarBlocks VehicleCalendarBlock[]
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
@@ -556,28 +964,28 @@ model Vehicle {
|
||||
}
|
||||
|
||||
model Offer {
|
||||
id String @id @default(cuid())
|
||||
companyId String
|
||||
company Company @relation(fields: [companyId], references: [id], onDelete: Cascade)
|
||||
title String
|
||||
description String?
|
||||
termsAndConds String?
|
||||
imageUrl String?
|
||||
type OfferType
|
||||
discountValue Int
|
||||
specialRate Int?
|
||||
appliesToAll Boolean @default(true)
|
||||
categories VehicleCategory[]
|
||||
minRentalDays Int?
|
||||
maxRentalDays Int?
|
||||
promoCode String? @unique
|
||||
maxRedemptions Int?
|
||||
redemptionCount Int @default(0)
|
||||
validFrom DateTime
|
||||
validUntil DateTime
|
||||
isActive Boolean @default(true)
|
||||
isPublic Boolean @default(true)
|
||||
isFeatured Boolean @default(false)
|
||||
id String @id @default(cuid())
|
||||
companyId String
|
||||
company Company @relation(fields: [companyId], references: [id], onDelete: Cascade)
|
||||
title String
|
||||
description String?
|
||||
termsAndConds String?
|
||||
imageUrl String?
|
||||
type OfferType
|
||||
discountValue Int
|
||||
specialRate Int?
|
||||
appliesToAll Boolean @default(true)
|
||||
categories VehicleCategory[]
|
||||
minRentalDays Int?
|
||||
maxRentalDays Int?
|
||||
promoCode String? @unique
|
||||
maxRedemptions Int?
|
||||
redemptionCount Int @default(0)
|
||||
validFrom DateTime
|
||||
validUntil DateTime
|
||||
isActive Boolean @default(true)
|
||||
isPublic Boolean @default(true)
|
||||
isFeatured Boolean @default(false)
|
||||
|
||||
vehicles OfferVehicle[]
|
||||
reservations Reservation[]
|
||||
@@ -621,10 +1029,10 @@ model Renter {
|
||||
phoneVerified Boolean @default(false)
|
||||
isActive Boolean @default(true)
|
||||
|
||||
reservations Reservation[] @relation("RenterReservations")
|
||||
reservations Reservation[] @relation("RenterReservations")
|
||||
savedCompanies RenterSavedCompany[]
|
||||
reviews Review[]
|
||||
notifications Notification[] @relation("RenterNotifications")
|
||||
notifications Notification[] @relation("RenterNotifications")
|
||||
notificationPreferences NotificationPreference[]
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
@@ -635,7 +1043,7 @@ model Renter {
|
||||
|
||||
model RenterSavedCompany {
|
||||
renterId String
|
||||
renter Renter @relation(fields: [renterId], references: [id], onDelete: Cascade)
|
||||
renter Renter @relation(fields: [renterId], references: [id], onDelete: Cascade)
|
||||
companyId String
|
||||
savedAt DateTime @default(now())
|
||||
|
||||
@@ -684,52 +1092,52 @@ model Customer {
|
||||
}
|
||||
|
||||
model Reservation {
|
||||
id String @id @default(cuid())
|
||||
companyId String
|
||||
company Company @relation(fields: [companyId], references: [id], onDelete: Cascade)
|
||||
vehicleId String
|
||||
vehicle Vehicle @relation(fields: [vehicleId], references: [id])
|
||||
customerId String
|
||||
customer Customer @relation(fields: [customerId], references: [id])
|
||||
renterId String?
|
||||
renter Renter? @relation("RenterReservations", fields: [renterId], references: [id])
|
||||
offerId String?
|
||||
offer Offer? @relation(fields: [offerId], references: [id])
|
||||
promoCodeUsed String?
|
||||
vehicleCategory VehicleCategory?
|
||||
source BookingSource @default(DASHBOARD)
|
||||
marketplaceRef String?
|
||||
status ReservationStatus @default(DRAFT)
|
||||
startDate DateTime
|
||||
endDate DateTime
|
||||
pickupLocation String?
|
||||
returnLocation String?
|
||||
dailyRate Int
|
||||
discountAmount Int @default(0)
|
||||
totalDays Int
|
||||
totalAmount Int
|
||||
depositAmount Int @default(0)
|
||||
paymentStatus String @default("UNPAID")
|
||||
paidAmount Int @default(0)
|
||||
checkedInAt DateTime?
|
||||
checkedOutAt DateTime?
|
||||
checkInMileage Int?
|
||||
checkOutMileage Int?
|
||||
notes String?
|
||||
cancelReason String?
|
||||
cancelledBy CancelledBy?
|
||||
contractNumber String? @unique
|
||||
invoiceNumber String? @unique
|
||||
reviewToken String? @unique
|
||||
id String @id @default(cuid())
|
||||
companyId String
|
||||
company Company @relation(fields: [companyId], references: [id], onDelete: Cascade)
|
||||
vehicleId String
|
||||
vehicle Vehicle @relation(fields: [vehicleId], references: [id])
|
||||
customerId String
|
||||
customer Customer @relation(fields: [customerId], references: [id])
|
||||
renterId String?
|
||||
renter Renter? @relation("RenterReservations", fields: [renterId], references: [id])
|
||||
offerId String?
|
||||
offer Offer? @relation(fields: [offerId], references: [id])
|
||||
promoCodeUsed String?
|
||||
vehicleCategory VehicleCategory?
|
||||
source BookingSource @default(DASHBOARD)
|
||||
marketplaceRef String?
|
||||
status ReservationStatus @default(DRAFT)
|
||||
startDate DateTime
|
||||
endDate DateTime
|
||||
pickupLocation String?
|
||||
returnLocation String?
|
||||
dailyRate Int
|
||||
discountAmount Int @default(0)
|
||||
totalDays Int
|
||||
totalAmount Int
|
||||
depositAmount Int @default(0)
|
||||
paymentStatus String @default("UNPAID")
|
||||
paidAmount Int @default(0)
|
||||
checkedInAt DateTime?
|
||||
checkedOutAt DateTime?
|
||||
checkInMileage Int?
|
||||
checkOutMileage Int?
|
||||
notes String?
|
||||
cancelReason String?
|
||||
cancelledBy CancelledBy?
|
||||
contractNumber String? @unique
|
||||
invoiceNumber String? @unique
|
||||
reviewToken String? @unique
|
||||
checkInFuelLevel String?
|
||||
checkOutFuelLevel String?
|
||||
|
||||
insurances ReservationInsurance[]
|
||||
insuranceTotal Int @default(0)
|
||||
insuranceTotal Int @default(0)
|
||||
additionalDrivers AdditionalDriver[]
|
||||
additionalDriverTotal Int @default(0)
|
||||
additionalDriverTotal Int @default(0)
|
||||
pricingRulesApplied Json?
|
||||
pricingRulesTotal Int @default(0)
|
||||
pricingRulesTotal Int @default(0)
|
||||
damageReports DamageReport[]
|
||||
rentalPayments RentalPayment[]
|
||||
inspections DamageInspection[]
|
||||
@@ -843,9 +1251,9 @@ model NotificationPreference {
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
|
||||
model MaintenanceLog {
|
||||
id String @id @default(cuid())
|
||||
id String @id @default(cuid())
|
||||
vehicleId String
|
||||
vehicle Vehicle @relation(fields: [vehicleId], references: [id], onDelete: Cascade)
|
||||
vehicle Vehicle @relation(fields: [vehicleId], references: [id], onDelete: Cascade)
|
||||
type String
|
||||
description String?
|
||||
cost Int?
|
||||
@@ -882,37 +1290,37 @@ model VehicleCalendarBlock {
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
|
||||
model ContractSettings {
|
||||
id String @id @default(cuid())
|
||||
companyId String @unique
|
||||
company Company @relation(fields: [companyId], references: [id], onDelete: Cascade)
|
||||
legalName String?
|
||||
registrationNumber String?
|
||||
taxId String?
|
||||
terms String @default("")
|
||||
fuelPolicy String @default("The vehicle must be returned with the same fuel level as at pickup.")
|
||||
depositPolicy String @default("The security deposit is refundable within 7 days after return, subject to vehicle inspection.")
|
||||
lateFeePolicy String @default("Late returns will incur a charge of 1 additional day rate per hour of delay.")
|
||||
damagePolicy String @default("The renter is liable for any damage not covered by insurance.")
|
||||
additionalClauses String[] @default([])
|
||||
signatureRequired Boolean @default(true)
|
||||
contractFooterNote String?
|
||||
invoiceFooterNote String?
|
||||
showTax Boolean @default(false)
|
||||
taxRate Float?
|
||||
taxLabel String?
|
||||
contractPrefix String @default("CNT")
|
||||
invoicePrefix String @default("INV")
|
||||
fuelPolicyType FuelPolicyType @default(FULL_TO_FULL)
|
||||
fuelPolicyNote String?
|
||||
fuelChargePerLiter Int?
|
||||
fuelShortfallFee Int?
|
||||
lateFeePerHour Int?
|
||||
taxLines Json?
|
||||
lastContractSeq Int @default(0)
|
||||
lastInvoiceSeq Int @default(0)
|
||||
additionalDriverCharge AdditionalDriverCharge @default(FREE)
|
||||
additionalDriverDailyRate Int @default(0)
|
||||
additionalDriverFlatRate Int @default(0)
|
||||
id String @id @default(cuid())
|
||||
companyId String @unique
|
||||
company Company @relation(fields: [companyId], references: [id], onDelete: Cascade)
|
||||
legalName String?
|
||||
registrationNumber String?
|
||||
taxId String?
|
||||
terms String @default("")
|
||||
fuelPolicy String @default("The vehicle must be returned with the same fuel level as at pickup.")
|
||||
depositPolicy String @default("The security deposit is refundable within 7 days after return, subject to vehicle inspection.")
|
||||
lateFeePolicy String @default("Late returns will incur a charge of 1 additional day rate per hour of delay.")
|
||||
damagePolicy String @default("The renter is liable for any damage not covered by insurance.")
|
||||
additionalClauses String[] @default([])
|
||||
signatureRequired Boolean @default(true)
|
||||
contractFooterNote String?
|
||||
invoiceFooterNote String?
|
||||
showTax Boolean @default(false)
|
||||
taxRate Float?
|
||||
taxLabel String?
|
||||
contractPrefix String @default("CNT")
|
||||
invoicePrefix String @default("INV")
|
||||
fuelPolicyType FuelPolicyType @default(FULL_TO_FULL)
|
||||
fuelPolicyNote String?
|
||||
fuelChargePerLiter Int?
|
||||
fuelShortfallFee Int?
|
||||
lateFeePerHour Int?
|
||||
taxLines Json?
|
||||
lastContractSeq Int @default(0)
|
||||
lastInvoiceSeq Int @default(0)
|
||||
additionalDriverCharge AdditionalDriverCharge @default(FREE)
|
||||
additionalDriverDailyRate Int @default(0)
|
||||
additionalDriverFlatRate Int @default(0)
|
||||
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@ -1046,16 +1454,16 @@ model DamageInspection {
|
||||
}
|
||||
|
||||
model DamagePoint {
|
||||
id String @id @default(cuid())
|
||||
inspectionId String
|
||||
inspection DamageInspection @relation(fields: [inspectionId], references: [id], onDelete: Cascade)
|
||||
viewType DiagramView
|
||||
x Float
|
||||
y Float
|
||||
damageType DamageType
|
||||
severity DamageSeverity @default(MINOR)
|
||||
description String?
|
||||
isPreExisting Boolean @default(false)
|
||||
id String @id @default(cuid())
|
||||
inspectionId String
|
||||
inspection DamageInspection @relation(fields: [inspectionId], references: [id], onDelete: Cascade)
|
||||
viewType DiagramView
|
||||
x Float
|
||||
y Float
|
||||
damageType DamageType
|
||||
severity DamageSeverity @default(MINOR)
|
||||
description String?
|
||||
isPreExisting Boolean @default(false)
|
||||
|
||||
@@index([inspectionId])
|
||||
@@map("damage_points")
|
||||
@@ -1168,7 +1576,7 @@ model AuditLog {
|
||||
}
|
||||
|
||||
model PricingConfig {
|
||||
id String @id @default(cuid())
|
||||
id String @id @default(cuid())
|
||||
plan String
|
||||
billingPeriod String
|
||||
amount Int
|
||||
@@ -1197,10 +1605,10 @@ model PricingPromotion {
|
||||
code String @unique
|
||||
name String
|
||||
description String?
|
||||
discountType String // 'PERCENTAGE' | 'FIXED'
|
||||
discountValue Int // percentage (1-100) or centimes for FIXED
|
||||
plans String[] // empty array = all plans
|
||||
periods String[] // empty array = all periods
|
||||
discountType String // 'PERCENTAGE' | 'FIXED'
|
||||
discountValue Int // percentage (1-100) or centimes for FIXED
|
||||
plans String[] // empty array = all plans
|
||||
periods String[] // empty array = all periods
|
||||
maxUses Int?
|
||||
usedCount Int @default(0)
|
||||
validFrom DateTime
|
||||
|
||||
Reference in New Issue
Block a user