Inventaire depuis une commande

- Possible de générer un inventaire à partir d'une commande passée.
  Préremplissage avec les valeurs commandées.
- Possible d'indiquer les prix d'achat pour avoir l'historique des prix
  d'un article chez un fournisseur. Et bientôt, une proposition
  automatique de prix.
- L'erreur sur le stock d'un article lors d'un inventaire n'est pas mise
  à jour dans le cas où l'inventaire est généré à partir d'une commande.
- Ajout d'un champ `at` au modèle `SupplierArticle` afin de conserver
  l'historique des prix d'achat
- Fix sur la vue `order_create`
This commit is contained in:
Aurélien Delobelle 2016-08-30 15:35:30 +02:00
parent 61feb9bbcd
commit 9467103879
9 changed files with 218 additions and 7 deletions

View file

@ -447,3 +447,27 @@ class OrderArticleForm(forms.Form):
self.v_et = kwargs['initial']['v_et']
self.v_prev = kwargs['initial']['v_prev']
self.c_rec = kwargs['initial']['c_rec']
class OrderArticleToInventoryForm(forms.Form):
article = forms.ModelChoiceField(
queryset = Article.objects.all(),
widget = forms.HiddenInput(),
)
price_HT = forms.DecimalField(
max_digits = 7, decimal_places = 4,
required = False)
TVA = forms.DecimalField(
max_digits = 7, decimal_places = 2,
required = False)
rights = forms.DecimalField(
max_digits = 7, decimal_places = 4,
required = False)
quantity_received = forms.IntegerField()
def __init__(self, *args, **kwargs):
super(OrderArticleToInventoryForm, self).__init__(*args, **kwargs)
if 'initial' in kwargs:
self.name = kwargs['initial']['name']
self.category = kwargs['initial']['category']
self.category_name = kwargs['initial']['category__name']
self.quantity_ordered = kwargs['initial']['quantity_ordered']

View file

@ -0,0 +1,31 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
import datetime
from django.utils.timezone import utc
class Migration(migrations.Migration):
dependencies = [
('kfet', '0039_auto_20160828_0430'),
]
operations = [
migrations.AlterModelOptions(
name='order',
options={'ordering': ['-at']},
),
migrations.AddField(
model_name='supplierarticle',
name='at',
field=models.DateTimeField(auto_now_add=True, default=datetime.datetime(2016, 8, 29, 18, 35, 3, 419033, tzinfo=utc)),
preserve_default=False,
),
migrations.AlterField(
model_name='article',
name='box_type',
field=models.CharField(choices=[('caisse', 'caisse'), ('carton', 'carton'), ('palette', 'palette'), ('fût', 'fût')], null=True, max_length=7, blank=True, default=None),
),
]

View file

@ -0,0 +1,18 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('kfet', '0040_auto_20160829_2035'),
]
operations = [
migrations.AlterModelOptions(
name='globalpermissions',
options={'permissions': (('is_team', 'Is part of the team'), ('perform_deposit', 'Effectuer une charge'), ('perform_negative_operations', 'Enregistrer des commandes en négatif'), ('override_frozen_protection', "Forcer le gel d'un compte"), ('cancel_old_operations', 'Annuler des commandes non récentes'), ('manage_perms', 'Gérer les permissions K-Fêt'), ('manage_addcosts', 'Gérer les majorations'), ('perform_commented_operations', 'Enregistrer des commandes avec commentaires'), ('view_negs', 'Voir la liste des négatifs'), ('order_to_inventory', "Générer un inventaire à partir d'une commande")), 'managed': False},
),
]

View file

@ -381,7 +381,10 @@ class InventoryArticle(models.Model):
stock_error = models.IntegerField(default = 0)
def save(self, *args, **kwargs):
self.stock_error = self.stock_new - self.stock_old
# S'il s'agit d'un inventaire provenant d'une livraison, il n'y a pas
# d'erreur
if not hasattr(self.inventory, 'order'):
self.stock_error = self.stock_new - self.stock_old
super(InventoryArticle, self).save(*args, **kwargs)
class Supplier(models.Model):
@ -403,6 +406,7 @@ class SupplierArticle(models.Model):
Supplier, on_delete = models.PROTECT)
article = models.ForeignKey(
Article, on_delete = models.PROTECT)
at = models.DateTimeField(auto_now_add = True)
price_HT = models.DecimalField(
max_digits = 7, decimal_places = 4,
blank = True, null = True, default = None)
@ -543,6 +547,7 @@ class GlobalPermissions(models.Model):
('manage_addcosts', 'Gérer les majorations'),
('perform_commented_operations', 'Enregistrer des commandes avec commentaires'),
('view_negs', 'Voir la liste des négatifs'),
('order_to_inventory', "Générer un inventaire à partir d'une commande")
)
class Settings(models.Model):

View file

@ -35,6 +35,7 @@
<li><a href="{% url 'kfet.checkout' %}">Caisses</a></li>
<li><a href="{% url 'kfet.article' %}">Articles</a></li>
<li><a href="{% url 'kfet.history' %}">Historique</a></li>
<li><a href="{% url 'kfet.order' %}">Commandes</a></li>
{% if user.username != 'kfet_genericteam' %}
<li><a href="{% url 'kfet.login.genericteam' %}" target="_blank" id="genericteam">Connexion standard</a></li>
{% endif %}

View file

@ -66,6 +66,14 @@
</a>
</td>
<td>{{ order.supplier }}</td>
<td>
{% if order.inventory %}
<a href="">Inventaire associé</a>
{% else %}
<a href="{% url 'kfet.order.to_inventory' order.pk %}">
Maj stock
</a>
{% endif %}
</tr>
{% endfor %}
</table>

View file

@ -0,0 +1,48 @@
{% extends 'kfet/base.html' %}
{% block title %}{% endblock %}
{% block content-header-title %}{% endblock %}
{% block content %}
{% include 'kfet/base_messages.html' %}
<form action="" method="post">
{% csrf_token %}
<table>
<thead>
<tr>
<td>Article</td>
<td>HT</td>
<td>TVA</td>
<td>Droits</td>
<td>Com.</td>
<td>Reçu</td>
</tr>
</thead>
<tbody>
{% for form in formset %}
{% ifchanged form.category %}
<tr>
<td colspan="6">{{ form.category_name }}</td>
</tr>
{% endifchanged %}
<tr>
{{ form.article }}
<td>{{ form.name }}</td>
<td>{{ form.price_HT }}</td>
<td>{{ form.TVA }}</td>
<td>{{ form.rights }}</td>
<td>{{ form.quantity_ordered }}</td>
<td>{{ form.quantity_received }}</td>
{% endfor %}
</tbody>
</table>
{% if not perms.kfet.order_to_inventory %}
<input type="password" name="KFETPASSWORD">
{% endif %}
{{ formset.management_form }}
<input type="submit" value="Enregistrer">
</form>
{% endblock %}

View file

@ -179,7 +179,7 @@ urlpatterns = [
url(r'^orders/$',
permission_required('kfet.is_team')(views.OrderList.as_view()),
name = 'kfet.order'),
url(r'^orders/(?P<pk>\d+)',
url(r'^orders/(?P<pk>\d+)$',
permission_required('kfet.is_team')(views.OrderRead.as_view()),
name = 'kfet.order.read'),
url(r'^orders/suppliers/(?P<pk>\d+)/edit$',
@ -187,4 +187,6 @@ urlpatterns = [
name = 'kfet.order.supplier.update'),
url(r'^orders/suppliers/(?P<pk>\d+)/new-order$', views.order_create,
name = 'kfet.order.new'),
url(r'^orders/(?P<pk>\d+)/to_inventory$', views.order_to_inventory,
name = 'kfet.order.to_inventory'),
]

View file

@ -1367,15 +1367,13 @@ class OrderList(ListView):
@permission_required('kfet.is_team')
def order_create(request, pk):
supplier = Supplier.objects.get(pk=pk)
supplier = get_object_or_404(Supplier, pk=pk)
articles = (Article.objects
.filter(suppliers=supplier.pk)
.select_related('category')
.order_by('category__name', 'name'))
print(articles[0].suppliers.all())
initial = []
today = timezone.now().date()
sales_q = (Operation.objects
@ -1429,7 +1427,7 @@ def order_create(request, pk):
v_prev = v_moy + v_et
c_rec_tot = max(v_prev * 1.5 - article.stock, 0)
if article.box_capacity:
c_rec_temp = c_rec_tot / box_capacity
c_rec_temp = c_rec_tot / article.box_capacity
if c_rec_temp >= 10:
c_rec = round(c_rec_temp)
elif c_rec_temp > 5:
@ -1478,7 +1476,7 @@ def order_create(request, pk):
article = articles.get(pk=form.cleaned_data['article'].pk)
q_ordered = form.cleaned_data['quantity_ordered']
if article.box_capacity:
q_ordered /= article.box_capacity
q_ordered *= article.box_capacity
OrderArticle.objects.create(
order = order,
article = article,
@ -1534,6 +1532,82 @@ class OrderRead(DetailView):
context['mail'] = mail
return context
def order_to_inventory(request, pk):
order = get_object_or_404(Order, pk=pk)
if hasattr(order, 'inventory'):
raise Http404
articles = (Article.objects
.filter(orders=order.pk)
.select_related('category')
.prefetch_related(Prefetch('orderarticle_set',
queryset = OrderArticle.objects.filter(order=order),
to_attr = 'order'))
.prefetch_related(Prefetch('supplierarticle_set',
queryset = (SupplierArticle.objects
.filter(supplier=order.supplier)
.order_by('-at')),
to_attr = 'supplier'))
.order_by('category__name', 'name'))
initial = []
for article in articles:
initial.append({
'article': article.pk,
'name': article.name,
'category': article.category_id,
'category__name': article.category.name,
'quantity_ordered': article.order[0].quantity_ordered,
'quantity_received': article.order[0].quantity_ordered,
'price_HT': article.supplier[0].price_HT,
'TVA': article.supplier[0].TVA,
'rights': article.supplier[0].rights,
})
cls_formset = formset_factory(OrderArticleToInventoryForm, extra=0)
if request.method == 'POST':
formset = cls_formset(request.POST, initial=initial)
if not request.user.has_perm('kfet.order_to_inventory'):
message.error(request, 'Permission refusée')
elif formset.is_valid():
with transaction.atomic():
inventory = Inventory()
inventory.order = order
inventory.by = request.user.profile.account_kfet
inventory.save()
for form in formset:
q_received = form.cleaned_data['quantity_received']
article = form.cleaned_data['article']
SupplierArticle.objects.create(
supplier = order.supplier,
article = article,
price_HT = form.cleaned_data['price_HT'],
TVA = form.cleaned_data['TVA'],
rights = form.cleaned_data['rights'])
(OrderArticle.objects
.filter(order=order, article=article)
.update(quantity_received = q_received))
InventoryArticle.objects.create(
inventory = inventory,
article = article,
stock_old = article.stock,
stock_new = article.stock + q_received)
article.stock += q_received
article.save()
messages.success(request, "C'est tout bon !")
return redirect('kfet.order')
else:
messages.error(request, "Corrigez les erreurs")
else:
formset = cls_formset(initial=initial)
return render(request, 'kfet/order_to_inventory.html', {
'formset': formset,
})
class SupplierUpdate(SuccessMessageMixin, UpdateView):
model = Supplier
template_name = 'kfet/supplier_form.html'