gmao/app_new/parts/routes.py
root 0b3fff43e5
Some checks are pending
CI - Tests et Syntax / lint-and-test (push) Waiting to run
Complète le suivi opérationnel de la GMAO
2026-08-14 23:26:22 +00:00

113 lines
4.3 KiB
Python

"""
Parts Routes - GMAO Collège
Gestion des pièces détachées
"""
from flask import Blueprint, render_template, request, flash, redirect, url_for
from flask_login import login_required, current_user
from app_new.extensions import db
from ..core.models.company import Part, PartStockMovement
def _record_movement(part, new_quantity, movement_type, reason):
before = int(part.quantity or 0)
after = int(new_quantity)
if after < 0:
raise ValueError('Le stock ne peut pas devenir négatif.')
if after == before:
return None
movement = PartStockMovement(
part=part, user_id=current_user.id, movement_type=movement_type,
quantity_delta=after - before, quantity_before=before,
quantity_after=after, reason=(reason or '').strip() or 'Ajustement manuel',
)
part.quantity = after
db.session.add(movement)
return movement
parts_bp = Blueprint('parts', __name__)
@parts_bp.route('/')
@login_required
def index():
"""Liste des pièces."""
page = request.args.get('page', 1, type=int)
parts = Part.query.order_by(Part.name).paginate(page=page, per_page=20)
return render_template('parts/index.html', parts=parts)
@parts_bp.route('/<int:id>')
@login_required
def detail(id):
"""Détail d'une pièce."""
part = Part.query.get_or_404(id)
movements = part.stock_movements.order_by(PartStockMovement.created_at.desc()).limit(100).all()
return render_template('parts/detail.html', part=part, movements=movements)
@parts_bp.route('/<int:id>/edit', methods=['GET', 'POST'])
@login_required
def edit(id):
"""Modifier une pièce."""
part = Part.query.get_or_404(id)
if request.method == 'POST':
part.name = request.form.get('name')
part.reference = request.form.get('reference')
try:
_record_movement(part, request.form.get('quantity', type=int, default=0),
'correction', request.form.get('stock_reason'))
except ValueError as exc:
flash(str(exc), 'danger')
return render_template('parts/form.html', part=part), 400
part.unit = request.form.get('unit')
part.unit_price = request.form.get('unit_price', type=float)
part.supplier = request.form.get('supplier')
db.session.commit()
flash('Pièce modifiée avec succès.', 'success')
return redirect(url_for('parts.detail', id=part.id))
return render_template('parts/form.html', part=part)
@parts_bp.route('/new', methods=['GET', 'POST'])
@login_required
def create():
"""Créer une pièce."""
if request.method == 'POST':
quantity = max(0, request.form.get('quantity', type=int, default=0))
part = Part(
name=request.form.get('name'),
reference=request.form.get('reference'),
quantity=0,
unit=request.form.get('unit')
)
part.unit_price = request.form.get('unit_price', type=float)
part.supplier = request.form.get('supplier')
db.session.add(part)
db.session.flush()
if quantity:
_record_movement(part, quantity, 'entree', 'Stock initial')
db.session.commit()
flash('Pièce créée avec succès.', 'success')
return redirect(url_for('parts.index'))
return render_template('parts/new.html')
@parts_bp.route('/<int:id>/movement', methods=['POST'])
@login_required
def movement(id):
"""Enregistre une entrée, une sortie ou un inventaire sans écraser l'historique."""
part = Part.query.get_or_404(id)
movement_type = request.form.get('movement_type')
amount = request.form.get('quantity', type=int)
reason = (request.form.get('reason') or '').strip()
if movement_type not in {'entree', 'sortie', 'inventaire'} or amount is None or amount < 0 or not reason:
flash('Type, quantité positive et motif sont obligatoires.', 'danger')
return redirect(url_for('parts.detail', id=id))
new_quantity = amount if movement_type == 'inventaire' else int(part.quantity or 0) + (amount if movement_type == 'entree' else -amount)
try:
_record_movement(part, new_quantity, movement_type, reason)
db.session.commit()
flash('Mouvement de stock enregistré.', 'success')
except ValueError as exc:
db.session.rollback()
flash(str(exc), 'danger')
return redirect(url_for('parts.detail', id=id))