diff --git a/streetup/stock/models.py b/streetup/stock/models.py index 04f5295..ba3f3be 100644 --- a/streetup/stock/models.py +++ b/streetup/stock/models.py @@ -1387,12 +1387,55 @@ class PreparationOrder(models.Model): self.save() self.update_reserved_quantities() + def reopen(self): + """Réouvre un bon de préparation traité pour modification (statut : to_process).""" + if self.status == "processed": + self.status = "to_process" + self.save() + self.update_reserved_quantities() + def cancel(self): """Annule le bon et libère les quantités réservées""" self.status = "cancelled" self.save() self.update_reserved_quantities() + def get_executed_outputs_by_product_and_location(self): + """ + Retourne un dictionnaire des mouvements de sortie nets déjà exécutés pour ce bon. + Structure : { product_id: { location_id: net_quantity } } + Calcul : sorties (TYPE_OUT) moins entrées/retours (TYPE_IN). + """ + executed = {} + for batch in self.stock_batches.all(): + for move in batch.movements.all(): + prod_id = move.product_id + if prod_id not in executed: + executed[prod_id] = {} + + if batch.movement_type == "out" and move.source_location_id: + loc_id = move.source_location_id + executed[prod_id][loc_id] = executed[prod_id].get(loc_id, 0) + move.quantity + elif batch.movement_type == "in" and move.destination_location_id: + loc_id = move.destination_location_id + executed[prod_id][loc_id] = executed[prod_id].get(loc_id, 0) - move.quantity + + res = {} + for p_id, loc_dict in executed.items(): + filtered_locs = {l_id: qty for l_id, qty in loc_dict.items() if qty > 0} + if filtered_locs: + res[p_id] = filtered_locs + return res + + def get_executed_output_quantities(self): + """ + Retourne la quantité totale nette sortie par produit pour ce bon. + Structure : { product_id: net_total_quantity } + """ + outputs_by_loc = self.get_executed_outputs_by_product_and_location() + return {p_id: sum(loc_dict.values()) for p_id, loc_dict in outputs_by_loc.items()} + + class PreparationOrderItem(models.Model): preparation_order = models.ForeignKey(PreparationOrder, on_delete=models.CASCADE) diff --git a/streetup/stock/templates/stock/preparation_order_detail.html b/streetup/stock/templates/stock/preparation_order_detail.html index 65adfba..d05bc88 100644 --- a/streetup/stock/templates/stock/preparation_order_detail.html +++ b/streetup/stock/templates/stock/preparation_order_detail.html @@ -21,7 +21,11 @@
{% translate "Ce bon a déjà fait l'objet d'un traitement initial. Les modifications apportées seront prises en compte lors du prochain traitement par le magasinier (restitutions et sorties complémentaires automatiques)." %}
+| {% translate "Produit" %} | {% translate "Code" %} | {% translate "Catégorie" %} | {% translate "Quantité demandée" %} | + {% if has_executed_outputs %} +{% translate "À prélever / à restituer" %} | + {% endif %} {% if preparation_order.status == 'to_process' or preparation_order.status == 'pending' %}{% translate "Quantité réservée" %} | {% translate "Stock disponible" %} | {% translate "Emplacements" %} | {% endif %}{% translate "Bon de confection" %} | +{% translate "Préparé" %} | {% if item_data.item.quantity > 0 %} - {{ item_data.item.quantity }} {{ item_data.item.product.unit }} + {{ item_data.item.quantity }} {{ item_data.item.product.unit }} {% else %} {% translate "Ajouté" %} {% endif %} | + {% if has_executed_outputs %} +
+ {% if item_data.executed_qty > 0 %}
+ {% if item_data.delta > 0 %}
+
+ +{{ item_data.delta }} {% translate "à prélever" %}
+
+ {% translate "Sorti" %}: {{ item_data.executed_qty }} + {% elif item_data.delta < 0 %} + + {{ item_data.delta }} {% translate "à restituer" %} + + {% translate "Sorti" %}: {{ item_data.executed_qty }} + {% else %} + + {% translate "Inchangé" %} ({{ item_data.executed_qty }}) + + {% endif %} + {% else %} + + {{ item_data.item.quantity }} {% translate "à prélever" %} + + {% endif %} + |
+ {% endif %}
{% if preparation_order.status == 'to_process' or preparation_order.status == 'pending' %}
@@ -316,11 +361,67 @@ — {% endif %} | +
+
+
+
+ |
{% endfor %}
|---|
| {% translate "Produit" %} | +{% translate "Emplacement(s) d'origine" %} | +{% translate "Quantité à restituer" %} | +{% translate "Préparé" %} | +
|---|---|---|---|
{{ del.product.name }} ({{ del.product.code }}) |
+ {{ del.location_info }} | ++ +{{ del.executed_qty }} {{ del.product.unit }} {% translate "à restituer" %} + | +
+ {% if del.item %}
+
+
+
+ {% else %}
+ —
+ {% endif %}
+ |
+
| {% translate "Description" %} | @@ -343,6 +444,7 @@{% translate "Type (Signalisation)" %} | {% translate "Quantité" %} | {% if can_manage %}{% endif %} + | {% translate "Préparé" %} |
+
+
+
+ |
{% endfor %}
@@ -449,11 +562,11 @@
${item.product_name} ${lockBadge} | ${item.product_code} |
-
-
-
-
+
|
|---|