loko/streetup/stock/migrations/0001_initial.py

484 lines
38 KiB
Python

# Generated by Django 6.0.7 on 2026-07-22 13:42
import common.storages
import django.contrib.gis.db.models.fields
import django.db.models.deletion
import django.utils.timezone
import stock.models
import uuid
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
('contracts', '0001_initial'),
('interventions', '0001_initial'),
('sign', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Product',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('code', models.CharField(max_length=100, unique=True)),
('name', models.CharField(max_length=200)),
('sku', models.CharField(max_length=100, unique=True)),
('description', models.TextField(blank=True, null=True)),
('price', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True)),
('unit', models.CharField(choices=[('pc', 'Pièce(s)'), ('kg', 'Kilogramme(s)'), ('g', 'Gramme(s)'), ('t', 'Tonne(s)'), ('L', 'Litre(s)'), ('m', 'Mètre(s)'), ('m2', 'Mètre(s) carré(s)'), ('m3', 'Mètre(s) cube(s)')], default='pc', max_length=10, verbose_name='Unité')),
('threshold', models.IntegerField(default=10)),
('total_stock', models.IntegerField(default=0)),
('reserved_quantity', models.IntegerField(default=0)),
('qr_code', models.ImageField(blank=True, null=True, upload_to='stock/qrcodes/products/', verbose_name='QR Code')),
('is_serialized', models.BooleanField(default=False, help_text='Si activé, chaque article de ce produit devra avoir un numéro de série unique.', verbose_name='Articles tracés (numéro de série)')),
('is_active', models.BooleanField(default=True, help_text='Si désactivé, le produit ne sera plus disponible pour les opérations (nouvelles entrées, sorties, transferts, interventions).', verbose_name='Actif')),
],
),
migrations.CreateModel(
name='ProductCategory',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('code', models.CharField(max_length=100, unique=True)),
('name_fr', models.CharField(max_length=100, unique=True)),
('name_en', models.CharField(blank=True, max_length=100, null=True, unique=True)),
('name_nl', models.CharField(blank=True, max_length=100, null=True, unique=True)),
('description', models.TextField(blank=True, null=True)),
],
),
migrations.CreateModel(
name='PurchaseOrder',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('code', models.CharField(blank=True, max_length=50, unique=True)),
('uuid', models.UUIDField(default=uuid.uuid4, editable=False)),
('date_ordered', models.DateField(auto_now_add=True)),
('status', models.CharField(choices=[('pending', 'En attente'), ('ordered', 'Envoyée'), ('partial', 'Partiellement reçue'), ('received', 'Reçue'), ('cancelled', 'Annulée')], default='pending', max_length=50, verbose_name='Statut')),
('received_at', models.DateTimeField(blank=True, null=True)),
],
options={
'ordering': ('-date_ordered', '-id'),
},
),
migrations.CreateModel(
name='Supplier',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=200)),
('contact_email', models.EmailField(blank=True, max_length=254, null=True)),
('phone_number', models.CharField(blank=True, max_length=20, null=True)),
('address', models.TextField(blank=True, null=True)),
('is_active', models.BooleanField(default=True)),
('enterprise_number', models.CharField(blank=True, max_length=100, null=True)),
],
options={
'verbose_name': 'Fournisseur',
'verbose_name_plural': 'Fournisseurs',
'ordering': ('name',),
},
),
migrations.CreateModel(
name='Warehouse',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('code', models.CharField(max_length=100, unique=True)),
('name', models.CharField(max_length=100, unique=True)),
('location', models.TextField(blank=True, null=True)),
('description', models.TextField(blank=True, null=True)),
('manager', models.CharField(max_length=200)),
('lon', models.FloatField(blank=True, null=True)),
('lat', models.FloatField(blank=True, null=True)),
('geom', django.contrib.gis.db.models.fields.PointField(blank=True, null=True, srid=3812)),
],
options={
'verbose_name': 'Entrepôt',
'verbose_name_plural': 'Entrepôts',
'ordering': ('name',),
},
),
migrations.CreateModel(
name='PreparationOrder',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('order_number', models.CharField(max_length=100, unique=True)),
('date', models.DateTimeField(auto_now_add=True)),
('status', models.CharField(choices=[('to_process', 'A traiter'), ('pending', 'En attente'), ('processed', 'Traité'), ('cancelled', 'Annulé')], default='to_process', max_length=50)),
('priority', models.CharField(choices=[('1', 'Urgent'), ('2', 'Elevé'), ('3', 'Normal'), ('4', 'Bas')], default='3', max_length=50, verbose_name='Priorité')),
('requester', models.CharField(blank=True, help_text="Personne ou service à l'origine de la demande", max_length=255, null=True, verbose_name='Demandeur')),
('is_loan', models.BooleanField(default=False, verbose_name='Prêt de matériel')),
('estimated_return_date', models.DateField(blank=True, null=True, verbose_name='Date de retour attendue')),
('loan_returned', models.BooleanField(default=False, verbose_name='Prêt retourné')),
('qr_code', models.ImageField(blank=True, null=True, upload_to='stock/qrcodes/preparation_orders/', verbose_name='QR Code')),
('intervention', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='preparation_orders', to='interventions.intervention', verbose_name='Intervention')),
],
),
migrations.CreateModel(
name='PreparationOrderDocument',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('file', models.FileField(storage=common.storages.PrivateMediaStorage(), upload_to=stock.models.preparation_order_document_upload_path, verbose_name='Fichier')),
('name', models.CharField(blank=True, max_length=255, verbose_name='Nom')),
('uploaded_at', models.DateTimeField(auto_now_add=True)),
('preparation_order', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='documents', to='stock.preparationorder', verbose_name='Bon de préparation')),
],
),
migrations.CreateModel(
name='PreparationOrderItem',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('quantity', models.IntegerField()),
('is_prepared', models.BooleanField(default=False)),
('quantity_modified', models.BooleanField(default=False, help_text="Indique que la quantité a été modifiée après que l'article ait été marqué comme préparé.", verbose_name='Quantité modifiée')),
('previous_quantity', models.IntegerField(blank=True, help_text='Ancienne quantité avant modification (pour information du magasinier).', null=True, verbose_name='Quantité précédente')),
('preparation_order', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='stock.preparationorder')),
('product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='stock.product')),
],
),
migrations.AddField(
model_name='preparationorder',
name='items',
field=models.ManyToManyField(through='stock.PreparationOrderItem', to='stock.product'),
),
migrations.AddField(
model_name='product',
name='category',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='products', to='stock.productcategory'),
),
migrations.CreateModel(
name='PreparationOrderOtherItem',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('description', models.TextField(verbose_name='Description')),
('quantity', models.IntegerField(default=1, verbose_name='Quantité')),
('is_prepared', models.BooleanField(default=False, verbose_name='Préparé')),
('preparation_order', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='other_items', to='stock.preparationorder')),
('sign_panel_type', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='sign.signpaneltype', verbose_name='Type de panneau')),
('category', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='stock.productcategory', verbose_name='Catégorie')),
],
),
migrations.CreateModel(
name='ProductionOrder',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('code', models.CharField(blank=True, max_length=100, unique=True)),
('quantity', models.IntegerField(default=1, help_text="Nombre d'unités à concevoir")),
('status', models.CharField(choices=[('draft', 'Brouillon'), ('to_process', 'À traiter'), ('in_production', 'En confection'), ('completed', 'Terminé'), ('cancelled', 'Annulé')], default='to_process', max_length=20)),
('destination', models.CharField(choices=[('internal', 'Interne'), ('external', 'Externe')], default='internal', help_text="Indiquer si la confection est destinée à l'usage interne ou externe.", max_length=20, verbose_name='Destination')),
('notes', models.TextField(blank=True, null=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('completed_at', models.DateTimeField(blank=True, null=True)),
('qr_code', models.ImageField(blank=True, null=True, upload_to='stock/qrcodes/conception_orders/', verbose_name='QR Code')),
('output_product_name', models.CharField(blank=True, help_text="Nom du nouveau produit à créer si 'Produit fini à stocker' n'est pas sélectionné.", max_length=300, null=True, verbose_name='Nom du produit fini (nouveau)')),
('output_product', models.ForeignKey(blank=True, help_text="Produit existant dont la quantité sera augmentée à la fin de la confection. Si vide et qu'un nom est fourni, un nouveau produit sera créé.", null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='conception_orders_output', to='stock.product', verbose_name='Produit fini à stocker')),
('preparation_order', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='production_orders', to='stock.preparationorder')),
('preparation_order_item', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='production_orders', to='stock.preparationorderitem', verbose_name='Article du bon de préparation')),
('preparation_order_other_item', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='production_orders', to='stock.preparationorderotheritem', verbose_name='Autre produit du bon de préparation')),
('warehouse', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='production_orders', to='stock.warehouse')),
],
options={
'ordering': ('-created_at',),
},
),
migrations.CreateModel(
name='ProductionOrderDocument',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('file', models.FileField(storage=common.storages.PrivateMediaStorage(), upload_to=stock.models.production_order_document_upload_path, verbose_name='Fichier')),
('name', models.CharField(blank=True, max_length=255, verbose_name='Nom')),
('uploaded_at', models.DateTimeField(auto_now_add=True)),
('production_order', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='documents', to='stock.productionorder', verbose_name='Bon de confection')),
],
),
migrations.CreateModel(
name='PurchaseOrderDocument',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('file', models.FileField(storage=common.storages.PrivateMediaStorage(), upload_to=stock.models.purchase_order_document_upload_path, verbose_name='Fichier')),
('name', models.CharField(blank=True, max_length=255, verbose_name='Nom')),
('uploaded_at', models.DateTimeField(auto_now_add=True)),
('purchase_order', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='documents', to='stock.purchaseorder', verbose_name='Commande fournisseur')),
],
),
migrations.CreateModel(
name='RequestForQuote',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('code', models.CharField(blank=True, max_length=50, unique=True)),
('uuid', models.UUIDField(default=uuid.uuid4, editable=False)),
('date_created', models.DateField(auto_now_add=True)),
('status', models.CharField(choices=[('draft', 'Brouillon'), ('sent', 'Envoyée'), ('completed', 'Convertie en commande'), ('cancelled', 'Annulée')], default='draft', max_length=50, verbose_name='Statut')),
('notes', models.TextField(blank=True, null=True, verbose_name='Notes')),
('created_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='created_request_for_quotes', to=settings.AUTH_USER_MODEL, verbose_name='Créé par')),
('suppliers', models.ManyToManyField(blank=True, related_name='request_for_quotes', to='stock.supplier', verbose_name='Fournisseurs sollicités')),
('warehouse', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='request_for_quotes', to='stock.warehouse', verbose_name='Entrepôt')),
],
options={
'verbose_name': "Demande d'offre",
'verbose_name_plural': "Demandes d'offres",
'ordering': ('-date_created', '-id'),
},
),
migrations.AddField(
model_name='purchaseorder',
name='request_for_quote',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='purchase_orders', to='stock.requestforquote', verbose_name="Demande d'offre d'origine"),
),
migrations.CreateModel(
name='RequestForQuoteItem',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('quantity', models.IntegerField(help_text='Quantité totale souhaitée', verbose_name='Quantité totale')),
('num_articles', models.PositiveIntegerField(blank=True, null=True, verbose_name="Nombre d'articles")),
('quantity_per_article', models.DecimalField(blank=True, decimal_places=3, max_digits=10, null=True, verbose_name='Quantité par article')),
('product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='stock.product', verbose_name='Produit')),
('request_for_quote', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='items', to='stock.requestforquote')),
],
),
migrations.CreateModel(
name='SerializedItem',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('serial_number', models.CharField(max_length=200, unique=True, verbose_name='Numéro de série')),
('status', models.CharField(choices=[('available', 'Disponible'), ('assigned', 'Attribué'), ('lost', 'Perdu'), ('retired', 'Retiré du service')], default='available', max_length=20, verbose_name='Statut')),
('notes', models.TextField(blank=True, default='', verbose_name='Notes')),
('qr_code', models.ImageField(blank=True, null=True, upload_to='stock/qrcodes/serialized_items/', verbose_name='QR Code')),
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Créé le')),
('is_active', models.BooleanField(default=True, verbose_name='Actif')),
('created_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='created_serialized_items', to=settings.AUTH_USER_MODEL, verbose_name='Créé par')),
('current_holder', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='held_serialized_items', to='contracts.companymember', verbose_name='Détenteur actuel')),
('product', models.ForeignKey(limit_choices_to={'is_serialized': True}, on_delete=django.db.models.deletion.CASCADE, related_name='serialized_items', to='stock.product', verbose_name='Produit')),
],
options={
'verbose_name': 'Article tracé',
'verbose_name_plural': 'Articles tracés',
'ordering': ('product__name', 'serial_number'),
},
),
migrations.CreateModel(
name='StockMovementBatch',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('movement_type', models.CharField(choices=[('in', 'Entrée'), ('out', 'Sortie'), ('transfer', 'Transfert')], max_length=20, verbose_name='Type')),
('movement_subtype', models.CharField(blank=True, choices=[('purchase', 'Achat'), ('return', 'Retour'), ('inventory', 'Inventaire'), ('consumption', 'Consommation'), ('loss', 'Perte'), ('donation', 'Don'), ('internal', 'Interne'), ('external', 'Externe'), ('correction', 'Correction'), ('confection', 'Confection'), ('preparation_order', 'Bon de préparation'), ('epi', 'EPI'), ('loan', 'Emprunt'), ('worksite', 'Chantier'), ('other', 'Autre')], max_length=30, null=True, verbose_name='Sous-type')),
('date', models.DateTimeField(auto_now_add=True, verbose_name='Date')),
('reason', models.TextField(blank=True, null=True, verbose_name='Raison')),
('requester', models.CharField(blank=True, help_text="Personne ou service à l'origine de la demande", max_length=255, null=True, verbose_name='Demandeur')),
('signature', models.ImageField(blank=True, help_text='Signature du réceptionnaire (image depuis tablette)', null=True, upload_to='stock_movements/signatures/', verbose_name='Signature')),
('signature_name', models.CharField(blank=True, max_length=255, null=True, verbose_name='Nom du signataire')),
('is_loan', models.BooleanField(default=False, verbose_name='Prêt de matériel')),
('estimated_return_date', models.DateField(blank=True, null=True, verbose_name='Date de retour attendue')),
('loan_returned', models.BooleanField(default=False, verbose_name='Prêt retourné')),
('company', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='stock_batches', to='contracts.company', verbose_name='Entreprise / Organisation')),
('contract', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='stock_batches', to='contracts.contract', verbose_name='Contrat')),
('created_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='created_stock_batches', to=settings.AUTH_USER_MODEL, verbose_name='Créé par')),
('person', models.ForeignKey(blank=True, help_text='Personne à qui le matériel est attribué ou qui le restitue', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='stock_batches', to='contracts.companymember', verbose_name='Personne')),
('preparation_order', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='stock_batches', to='stock.preparationorder', verbose_name='Bon de préparation')),
('purchase_order', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='stock_batches', to='stock.purchaseorder', verbose_name="Commande d'achat")),
],
options={
'verbose_name': 'Lot de mouvements de stock',
'verbose_name_plural': 'Lots de mouvements de stock',
'ordering': ('-date',),
},
),
migrations.CreateModel(
name='StockBatchSignature',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('image', models.ImageField(upload_to=stock.models.stock_batch_signature_upload_path, verbose_name='Image de signature')),
('signed_at', models.DateTimeField(auto_now_add=True, verbose_name='Signé le')),
('signature_name', models.CharField(blank=True, max_length=255, null=True, verbose_name='Nom du signataire')),
('signed_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='batch_signatures', to=settings.AUTH_USER_MODEL, verbose_name='Signé par')),
('batch', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='signatures', to='stock.stockmovementbatch', verbose_name='Lot')),
],
options={
'verbose_name': 'Signature de lot',
'verbose_name_plural': 'Signatures de lot',
'ordering': ('signed_at',),
},
),
migrations.CreateModel(
name='StockMovementDocument',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('file', models.FileField(storage=common.storages.PrivateMediaStorage(), upload_to=stock.models.stock_movement_document_upload_path, verbose_name='Fichier')),
('name', models.CharField(blank=True, max_length=255, verbose_name='Nom')),
('uploaded_at', models.DateTimeField(auto_now_add=True)),
('batch', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='documents', to='stock.stockmovementbatch', verbose_name='Lot')),
],
),
migrations.AddField(
model_name='purchaseorder',
name='supplier',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='purchase_orders', to='stock.supplier', verbose_name='Fournisseur'),
),
migrations.AddField(
model_name='purchaseorder',
name='warehouse',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='purchase_orders', to='stock.warehouse', verbose_name='Entrepôt'),
),
migrations.CreateModel(
name='WarehouseLocation',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('code', models.CharField(max_length=100, unique=True)),
('name', models.CharField(max_length=100)),
('description', models.TextField(blank=True, null=True)),
('is_default', models.BooleanField(default=False, help_text='Si activé, cet emplacement sera proposé par défaut dans les formulaires pour cet entrepôt.', verbose_name='Emplacement par défaut')),
('qr_code', models.ImageField(blank=True, null=True, upload_to='stock/qrcodes/locations/', verbose_name='QR Code')),
('warehouse', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='locations', to='stock.warehouse')),
],
options={
'unique_together': {('warehouse', 'name')},
},
),
migrations.CreateModel(
name='StockMovement',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('quantity', models.IntegerField()),
('date', models.DateTimeField(auto_now_add=True)),
('reason', models.TextField(blank=True, null=True)),
('product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='stock_movements', to='stock.product')),
('serialized_items', models.ManyToManyField(blank=True, related_name='stock_movements', to='stock.serializeditem', verbose_name='Articles tracés')),
('batch', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='movements', to='stock.stockmovementbatch', verbose_name='Lot')),
('destination_location', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='incoming_movements', to='stock.warehouselocation')),
('source_location', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='outgoing_movements', to='stock.warehouselocation')),
],
),
migrations.CreateModel(
name='SerializedItemEvent',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('event_type', models.CharField(choices=[('entry', 'Entrée en stock'), ('assigned', 'Attribué'), ('returned', 'Rendu'), ('transferred', 'Transféré'), ('lost', 'Perdu'), ('retired', 'Retiré du service'), ('note', 'Note')], max_length=20, verbose_name="Type d'événement")),
('date', models.DateTimeField(default=django.utils.timezone.now, verbose_name='Date')),
('notes', models.TextField(blank=True, default='', verbose_name='Remarques')),
('created_at', models.DateTimeField(auto_now_add=True)),
('created_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='serialized_item_events', to=settings.AUTH_USER_MODEL, verbose_name='Enregistré par')),
('item', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='events', to='stock.serializeditem', verbose_name='Article')),
('person', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='serialized_item_events', to='contracts.companymember', verbose_name='Personne')),
('from_location', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='serialized_events_from', to='stock.warehouselocation', verbose_name='Depuis')),
('to_location', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='serialized_events_to', to='stock.warehouselocation', verbose_name='Vers')),
],
options={
'verbose_name': 'Événement article tracé',
'verbose_name_plural': 'Événements articles tracés',
'ordering': ('-date', '-created_at'),
},
),
migrations.AddField(
model_name='serializeditem',
name='current_location',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='serialized_items', to='stock.warehouselocation', verbose_name='Emplacement actuel'),
),
migrations.CreateModel(
name='PurchaseOrderItem',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('quantity', models.IntegerField(help_text="Quantité totale dans l'unité du produit à ajouter au stock", verbose_name='Quantité totale')),
('num_articles', models.PositiveIntegerField(blank=True, help_text="Nombre d'articles commandés (ex: nombre de sacs)", null=True, verbose_name="Nombre d'articles")),
('quantity_per_article', models.DecimalField(blank=True, decimal_places=3, help_text="Quantité dans l'unité du produit par article (ex: 10 kg par sac)", max_digits=10, null=True, verbose_name='Quantité par article')),
('received_quantity', models.IntegerField(default=0, help_text='Quantité déjà réceptionnée pour cet article', verbose_name='Quantité reçue')),
('unit_price', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True, verbose_name='Prix unitaire (HTVA)')),
('vat_rate', models.DecimalField(decimal_places=2, default=21.0, max_digits=5, verbose_name='Taux de TVA (%)')),
('product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='stock.product')),
('purchase_order', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='items', to='stock.purchaseorder')),
('location', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='stock.warehouselocation')),
],
),
migrations.AddField(
model_name='productionorder',
name='output_location',
field=models.ForeignKey(blank=True, help_text="Emplacement où le produit fini sera ajouté au stock. Si vide, utilise l'emplacement par défaut de la conception.", null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='conception_orders_output', to='stock.warehouselocation', verbose_name='Emplacement de stockage du produit fini'),
),
migrations.AddField(
model_name='product',
name='authorized_locations',
field=models.ManyToManyField(blank=True, help_text='Emplacements dans lesquels ce produit est autorisé à être stocké (vide = tous autorisés).', related_name='authorized_products', to='stock.warehouselocation', verbose_name='Emplacements autorisés'),
),
migrations.CreateModel(
name='Inventory',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('performed_by', models.CharField(blank=True, max_length=255)),
('status', models.CharField(choices=[('draft', 'Brouillon'), ('in_progress', 'En cours'), ('done', 'Terminé'), ('validated', 'Validé')], default='draft', max_length=20)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('validated_at', models.DateTimeField(blank=True, null=True)),
('validator_comment', models.TextField(blank=True, default='', verbose_name='Commentaire du validateur')),
('qr_code', models.ImageField(blank=True, null=True, upload_to='stock/qrcodes/inventories/', verbose_name='QR Code')),
('validated_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='validated_inventories', to=settings.AUTH_USER_MODEL, verbose_name='Validé par')),
('warehouse_location', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='inventories', to='stock.warehouselocation')),
],
options={
'ordering': ('-created_at', '-id'),
},
),
migrations.CreateModel(
name='InventoryItem',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('expected_quantity', models.IntegerField(blank=True, null=True)),
('counted_quantity', models.IntegerField()),
('is_validated', models.BooleanField(default=False, verbose_name='Validé')),
('validated_at', models.DateTimeField(blank=True, null=True, verbose_name='Validé le')),
('comment', models.TextField(blank=True, default='', verbose_name='Commentaire')),
('validator_comment', models.TextField(blank=True, default='', verbose_name='Commentaire du validateur')),
('comment_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='inventory_item_comments', to=settings.AUTH_USER_MODEL, verbose_name='Commentaire par')),
('inventory', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='items', to='stock.inventory')),
('validated_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='validated_inventory_items', to=settings.AUTH_USER_MODEL, verbose_name='Validé par')),
('validator_comment_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='inventory_item_validator_comments', to=settings.AUTH_USER_MODEL, verbose_name='Commentaire validateur par')),
('product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='stock.product')),
],
options={
'unique_together': {('inventory', 'product')},
},
),
migrations.CreateModel(
name='ProductionOrderComponent',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('quantity', models.IntegerField(default=1)),
('notes', models.CharField(blank=True, max_length=500, null=True)),
('product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='production_order_usages', to='stock.product')),
('production_order', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='order_components', to='stock.productionorder')),
],
options={
'ordering': ('product__name',),
'unique_together': {('production_order', 'product')},
},
),
migrations.CreateModel(
name='StockAlert',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('current_quantity', models.IntegerField(default=0)),
('triggered_at', models.DateTimeField(auto_now_add=True)),
('resolved_at', models.DateTimeField(blank=True, null=True)),
('is_active', models.BooleanField(default=True)),
('product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='alerts', to='stock.product')),
('warehouse', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='alerts', to='stock.warehouse')),
],
options={
'unique_together': {('product', 'warehouse', 'is_active')},
},
),
migrations.CreateModel(
name='Stock',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('quantity', models.IntegerField(default=0)),
('product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='stocks', to='stock.product')),
('location', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='stocks', to='stock.warehouselocation')),
],
options={
'unique_together': {('product', 'location')},
},
),
]