AkomptaBackend / api /models.py
rinogeek's picture
Initial commit of backend code to Hugging Face Space
385a349
raw
history blame
10.4 kB
from django.db import models
from django.contrib.auth.models import AbstractUser, BaseUserManager
from django.core.validators import RegexValidator
from decimal import Decimal
class UserManager(BaseUserManager):
"""Custom user manager for email-based authentication"""
def create_user(self, email, password=None, **extra_fields):
"""Create and save a regular user with the given email and password"""
if not email:
raise ValueError('L\'adresse email est obligatoire')
email = self.normalize_email(email)
user = self.model(email=email, **extra_fields)
user.set_password(password)
user.save(using=self._db)
return user
def create_superuser(self, email, password=None, **extra_fields):
"""Create and save a superuser with the given email and password"""
extra_fields.setdefault('is_staff', True)
extra_fields.setdefault('is_superuser', True)
extra_fields.setdefault('is_active', True)
if extra_fields.get('is_staff') is not True:
raise ValueError('Le superutilisateur doit avoir is_staff=True.')
if extra_fields.get('is_superuser') is not True:
raise ValueError('Le superutilisateur doit avoir is_superuser=True.')
return self.create_user(email, password, **extra_fields)
class User(AbstractUser):
"""Modèle utilisateur étendu pour Akompta"""
ACCOUNT_TYPE_CHOICES = [
('personal', 'Personnel'),
('business', 'Professionnel'),
]
# Utiliser email comme identifiant
username = None
email = models.EmailField(unique=True, verbose_name="Email")
# Champs communs
phone_number = models.CharField(max_length=20, blank=True)
avatar = models.ImageField(upload_to='avatars/', blank=True, null=True)
account_type = models.CharField(
max_length=10,
choices=ACCOUNT_TYPE_CHOICES,
default='personal'
)
is_premium = models.BooleanField(default=False)
initial_balance = models.DecimalField(max_digits=15, decimal_places=2, default=Decimal('0.00'))
# Champs Business
business_name = models.CharField(max_length=255, blank=True)
sector = models.CharField(max_length=100, blank=True)
location = models.CharField(max_length=255, blank=True)
ifu = models.CharField(
max_length=50,
blank=True,
verbose_name="Identifiant Fiscal Unique"
)
business_logo = models.ImageField(upload_to='business_logos/', blank=True, null=True)
# Settings (Stockés en JSON)
currency = models.CharField(max_length=10, default='XOF')
language = models.CharField(max_length=5, default='FR')
dark_mode = models.BooleanField(default=False)
# Acceptation des conditions
agreed_terms = models.BooleanField(default=False)
business_agreed = models.BooleanField(default=False)
business_agreed_at = models.DateTimeField(blank=True, null=True)
# Timestamps
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
# Custom manager
objects = UserManager()
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['first_name', 'last_name']
class Meta:
verbose_name = "Utilisateur"
verbose_name_plural = "Utilisateurs"
def __str__(self):
return self.email
def save(self, *args, **kwargs):
# Validation IFU pour les comptes business
if self.account_type == 'business' and not self.ifu:
from django.core.exceptions import ValidationError
raise ValidationError(
"Le champ IFU est obligatoire pour les comptes professionnels."
)
super().save(*args, **kwargs)
class Product(models.Model):
"""Modèle pour les produits/inventaire"""
CATEGORY_CHOICES = [
('vente', 'Vente'),
('depense', 'Dépense'),
('stock', 'Stock'),
]
STOCK_STATUS_CHOICES = [
('ok', 'OK'),
('low', 'Faible'),
('rupture', 'Rupture'),
]
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='products')
name = models.CharField(max_length=255)
description = models.TextField(blank=True)
price = models.DecimalField(max_digits=15, decimal_places=2)
unit = models.CharField(max_length=50, default='Unité')
image = models.ImageField(upload_to='products/', blank=True, null=True)
category = models.CharField(max_length=20, choices=CATEGORY_CHOICES)
stock_status = models.CharField(
max_length=10,
choices=STOCK_STATUS_CHOICES,
default='ok'
)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
verbose_name = "Produit"
verbose_name_plural = "Produits"
ordering = ['-created_at']
def __str__(self):
return f"{self.name} - {self.user.email}"
class Transaction(models.Model):
"""Modèle pour les transactions financières"""
TYPE_CHOICES = [
('income', 'Revenu'),
('expense', 'Dépense'),
]
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='transactions')
name = models.CharField(max_length=255)
amount = models.DecimalField(max_digits=15, decimal_places=2)
type = models.CharField(max_length=10, choices=TYPE_CHOICES)
category = models.CharField(max_length=100)
date = models.DateTimeField()
currency = models.CharField(max_length=10, default='FCFA')
# Support pour la synchro hors-ligne
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
verbose_name = "Transaction"
verbose_name_plural = "Transactions"
ordering = ['-date']
indexes = [
models.Index(fields=['user', 'type']),
models.Index(fields=['user', 'date']),
models.Index(fields=['user', 'category']),
]
def __str__(self):
return f"{self.name} - {self.amount} {self.currency}"
class Budget(models.Model):
"""Modèle pour les budgets suivis"""
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='budgets')
category = models.CharField(max_length=100)
limit = models.DecimalField(max_digits=15, decimal_places=2)
color = models.CharField(max_length=7, default='#4F46E5') # Hex color
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
verbose_name = "Budget"
verbose_name_plural = "Budgets"
unique_together = ['user', 'category']
def __str__(self):
return f"{self.category} - {self.limit}"
def get_spent_amount(self):
"""Calcule le montant dépensé pour cette catégorie"""
from django.db.models import Sum
result = self.user.transactions.filter(
type='expense',
category=self.category
).aggregate(total=Sum('amount'))
return result['total'] or Decimal('0.00')
class Ad(models.Model):
"""Modèle pour les annonces partenaires"""
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='ads')
product_name = models.CharField(max_length=255)
owner_name = models.CharField(max_length=255)
description = models.TextField()
image = models.ImageField(upload_to='ads/')
whatsapp = models.CharField(max_length=20)
website = models.URLField(blank=True, null=True)
location = models.CharField(max_length=255)
is_verified = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
verbose_name = "Annonce"
verbose_name_plural = "Annonces"
ordering = ['-created_at']
def __str__(self):
return f"{self.product_name} - {self.owner_name}"
class Notification(models.Model):
"""Modèle pour les notifications"""
TYPE_CHOICES = [
('reminder', 'Rappel'),
('profit', 'Profit'),
('promo', 'Promo'),
('system', 'Système'),
]
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='notifications')
type = models.CharField(max_length=20, choices=TYPE_CHOICES, default='system')
title = models.CharField(max_length=255)
message = models.TextField()
is_read = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
verbose_name = "Notification"
verbose_name_plural = "Notifications"
ordering = ['-created_at']
def __str__(self):
return f"{self.title} - {self.user.email}"
class SupportTicket(models.Model):
"""Modèle pour le support client"""
STATUS_CHOICES = [
('open', 'Ouvert'),
('in_progress', 'En cours'),
('closed', 'Fermé'),
]
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='support_tickets')
subject = models.CharField(max_length=255)
message = models.TextField()
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='open')
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
verbose_name = "Ticket Support"
verbose_name_plural = "Tickets Support"
ordering = ['-created_at']
def __str__(self):
return f"{self.subject} - {self.status}"
class AIInsight(models.Model):
"""Modèle pour stocker les insights générés par l'IA"""
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='ai_insights')
content = models.JSONField() # Liste de phrases
context_hash = models.CharField(max_length=64) # Hash des données utilisées pour la génération
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
verbose_name = "Insight IA"
verbose_name_plural = "Insights IA"
ordering = ['-created_at']
def __str__(self):
return f"Insight pour {self.user.email} - {self.created_at}"