Merge branch 'master' into aureplop/kfet_config

This commit is contained in:
Aurélien Delobelle 2017-04-03 15:22:03 +02:00
commit a4a854bc50
23 changed files with 454 additions and 164 deletions

View file

@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('bda', '0010_spectaclerevente_shotgun'),
]
operations = [
migrations.AddField(
model_name='tirage',
name='appear_catalogue',
field=models.BooleanField(
default=False,
verbose_name='Tirage à afficher dans le catalogue'
),
),
]

View file

@ -18,6 +18,7 @@ class Tirage(models.Model):
fermeture = models.DateTimeField("Date et heure de fermerture du tirage") fermeture = models.DateTimeField("Date et heure de fermerture du tirage")
tokens = models.TextField("Graine(s) du tirage", blank=True) tokens = models.TextField("Graine(s) du tirage", blank=True)
active = models.BooleanField("Tirage actif", default=False) active = models.BooleanField("Tirage actif", default=False)
appear_catalogue = models.BooleanField("Tirage à afficher dans le catalogue", default=False)
enable_do_tirage = models.BooleanField("Le tirage peut être lancé", enable_do_tirage = models.BooleanField("Le tirage peut être lancé",
default=False) default=False)
@ -78,6 +79,15 @@ class Spectacle(models.Model):
self.price self.price
) )
def getImgUrl(self):
"""
Cette fonction permet d'obtenir l'URL de l'image, si elle existe
"""
try:
return self.image.url
except:
return None
def send_rappel(self): def send_rappel(self):
""" """
Envoie un mail de rappel à toutes les personnes qui ont une place pour Envoie un mail de rappel à toutes les personnes qui ont une place pour

View file

@ -47,4 +47,6 @@ urlpatterns = [
url(r'^mails-rappel/(?P<spectacle_id>\d+)$', views.send_rappel), url(r'^mails-rappel/(?P<spectacle_id>\d+)$', views.send_rappel),
url(r'^descriptions/(?P<tirage_id>\d+)$', views.descriptions_spectacles, url(r'^descriptions/(?P<tirage_id>\d+)$', views.descriptions_spectacles,
name='bda-descriptions'), name='bda-descriptions'),
url(r'^catalogue/(?P<request_type>[a-z]+)$', views.catalogue,
name='bda-catalogue'),
] ]

View file

@ -3,11 +3,11 @@
import random import random
import hashlib import hashlib
import time import time
import json
from datetime import timedelta from datetime import timedelta
from custommail.shortcuts import ( from custommail.shortcuts import (
send_mass_custom_mail, send_custom_mail, render_custom_mail send_mass_custom_mail, send_custom_mail, render_custom_mail
) )
from django.shortcuts import render, get_object_or_404 from django.shortcuts import render, get_object_or_404
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.contrib import messages from django.contrib import messages
@ -15,18 +15,24 @@ from django.db import models, transaction
from django.core import serializers from django.core import serializers
from django.db.models import Count, Q, Sum from django.db.models import Count, Q, Sum
from django.forms.models import inlineformset_factory from django.forms.models import inlineformset_factory
from django.http import HttpResponseBadRequest, HttpResponseRedirect from django.http import (
HttpResponseBadRequest, HttpResponseRedirect, JsonResponse
)
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.conf import settings from django.conf import settings
from django.utils import timezone, formats from django.utils import timezone, formats
from django.views.generic.list import ListView from django.views.generic.list import ListView
from django.core.exceptions import ObjectDoesNotExist
from gestioncof.decorators import cof_required, buro_required from gestioncof.decorators import cof_required, buro_required
from bda.models import Spectacle, Participant, ChoixSpectacle, Attribution,\ from bda.models import (
Tirage, SpectacleRevente Spectacle, Participant, ChoixSpectacle, Attribution, Tirage,
SpectacleRevente, Salle, Quote, CategorieSpectacle
)
from bda.algorithm import Algorithm from bda.algorithm import Algorithm
from bda.forms import BaseBdaFormSet, TokenForm, ResellForm, AnnulForm,\ from bda.forms import (
InscriptionReventeForm, SoldForm BaseBdaFormSet, TokenForm, ResellForm, AnnulForm, InscriptionReventeForm,
SoldForm
)
@cof_required @cof_required
@ -639,3 +645,98 @@ def descriptions_spectacles(request, tirage_id):
return HttpResponseBadRequest( return HttpResponseBadRequest(
"La variable GET 'location' doit contenir un entier") "La variable GET 'location' doit contenir un entier")
return render(request, 'descriptions.html', {'shows': shows_qs.all()}) return render(request, 'descriptions.html', {'shows': shows_qs.all()})
def catalogue(request, request_type):
"""
Vue destinée à communiquer avec un client AJAX, fournissant soit :
- la liste des tirages
- les catégories et salles d'un tirage
- les descriptions d'un tirage (filtrées selon la catégorie et la salle)
"""
if request_type == "list":
# Dans ce cas on retourne la liste des tirages et de leur id en JSON
data_return = list(
Tirage.objects.filter(appear_catalogue=True).values('id', 'title'))
return JsonResponse(data_return, safe=False)
if request_type == "details":
# Dans ce cas on retourne une liste des catégories et des salles
tirage_id = request.GET.get('id', '')
try:
tirage = Tirage.objects.get(id=tirage_id)
except ObjectDoesNotExist:
return HttpResponseBadRequest(
"Aucun tirage correspondant à l'id "
+ tirage_id)
except ValueError:
return HttpResponseBadRequest(
"Mauvais format d'identifiant : "
+ tirage_id)
categories = list(
CategorieSpectacle.objects.filter(
spectacle__in=tirage.spectacle_set.all())
.distinct().values('id', 'name'))
locations = list(
Salle.objects.filter(
spectacle__in=tirage.spectacle_set.all())
.distinct().values('id', 'name'))
data_return = {'categories': categories, 'locations': locations}
return JsonResponse(data_return, safe=False)
if request_type == "descriptions":
# Ici on retourne les descriptions correspondant à la catégorie et
# à la salle spécifiées
tirage_id = request.GET.get('id', '')
categories = request.GET.get('category', '[0]')
locations = request.GET.get('location', '[0]')
try:
category_id = json.loads(categories)
location_id = json.loads(locations)
tirage = Tirage.objects.get(id=tirage_id)
shows_qs = tirage.spectacle_set
if not(0 in category_id):
shows_qs = shows_qs.filter(
category__id__in=category_id)
if not(0 in location_id):
shows_qs = shows_qs.filter(
location__id__in=location_id)
except ObjectDoesNotExist:
return HttpResponseBadRequest(
"Impossible de trouver des résultats correspondant "
"à ces caractéristiques : "
+ "id = " + tirage_id
+ ", catégories = " + categories
+ ", salles = " + locations)
except ValueError: # Contient JSONDecodeError
return HttpResponseBadRequest(
"Impossible de parser les paramètres donnés : "
+ "id = " + request.GET.get('id', '')
+ ", catégories = " + request.GET.get('category', '[0]')
+ ", salles = " + request.GET.get('location', '[0]'))
# On convertit les descriptions à envoyer en une liste facilement
# JSONifiable (il devrait y avoir un moyen plus efficace en
# redéfinissant le serializer de JSON)
data_return = [{
'title': spectacle.title,
'category': str(spectacle.category),
'date': str(formats.date_format(
timezone.localtime(spectacle.date),
"SHORT_DATETIME_FORMAT")),
'location': str(spectacle.location),
'vips': spectacle.vips,
'description': spectacle.description,
'slots_description': spectacle.slots_description,
'quotes': list(Quote.objects.filter(spectacle=spectacle).values(
'author', 'text')),
'image': spectacle.getImgUrl(),
'ext_link': spectacle.ext_link,
'price': spectacle.price,
'slots': spectacle.slots
}
for spectacle in shows_qs.all()
]
return JsonResponse(data_return, safe=False)
# Si la requête n'est pas de la forme attendue, on quitte avec une erreur
return HttpResponseBadRequest()

View file

@ -14,6 +14,10 @@ from gestioncof.decorators import buro_required
class Clipper(object): class Clipper(object):
def __init__(self, clipper, fullname): def __init__(self, clipper, fullname):
if fullname is None:
fullname = ""
assert isinstance(clipper, str)
assert isinstance(fullname, str)
self.clipper = clipper self.clipper = clipper
self.fullname = fullname self.fullname = fullname
@ -62,17 +66,19 @@ def autocomplete(request):
)) ))
if ldap_query != "(&)": if ldap_query != "(&)":
# If none of the bits were legal, we do not perform the query # If none of the bits were legal, we do not perform the query
entries = None
with Connection(settings.LDAP_SERVER_URL) as conn: with Connection(settings.LDAP_SERVER_URL) as conn:
conn.search( conn.search(
'dc=spi,dc=ens,dc=fr', ldap_query, 'dc=spi,dc=ens,dc=fr', ldap_query,
attributes=['uid', 'cn'] attributes=['uid', 'cn']
) )
queries['clippers'] = conn.entries entries = conn.entries
# Clearing redundancies # Clearing redundancies
queries['clippers'] = [ queries['clippers'] = [
Clipper(clipper.uid, clipper.cn) Clipper(entry.uid.value, entry.cn.value)
for clipper in queries['clippers'] for entry in entries
if str(clipper.uid) not in usernames if entry.uid.value
and entry.uid.value not in usernames
] ]
# Resulting data # Resulting data

View file

@ -13,6 +13,10 @@ from kfet.models import Account
class Clipper(object): class Clipper(object):
def __init__(self, clipper, fullname): def __init__(self, clipper, fullname):
if fullname is None:
fullname = ""
assert isinstance(clipper, str)
assert isinstance(fullname, str)
self.clipper = clipper self.clipper = clipper
self.fullname = fullname self.fullname = fullname
@ -80,17 +84,19 @@ def account_create(request):
)) ))
if ldap_query != "(&)": if ldap_query != "(&)":
# If none of the bits were legal, we do not perform the query # If none of the bits were legal, we do not perform the query
entries = None
with Connection(settings.LDAP_SERVER_URL) as conn: with Connection(settings.LDAP_SERVER_URL) as conn:
conn.search( conn.search(
'dc=spi,dc=ens,dc=fr', ldap_query, 'dc=spi,dc=ens,dc=fr', ldap_query,
attributes=['uid', 'cn'] attributes=['uid', 'cn']
) )
queries['clippers'] = conn.entries entries = conn.entries
# Clearing redundancies # Clearing redundancies
queries['clippers'] = [ queries['clippers'] = [
Clipper(clipper.uid, clipper.cn) Clipper(entry.uid.value, entry.cn.value)
for clipper in queries['clippers'] for entry in entries
if str(clipper.uid) not in usernames if entry.uid.value
and entry.uid.value not in usernames
] ]
# Resulting data # Resulting data

View file

@ -74,8 +74,11 @@ class AccountRestrictForm(AccountForm):
class AccountPwdForm(forms.Form): class AccountPwdForm(forms.Form):
pwd1 = forms.CharField( pwd1 = forms.CharField(
label="Mot de passe K-Fêt",
help_text="Le mot de passe doit contenir au moins huit caractères",
widget=forms.PasswordInput) widget=forms.PasswordInput)
pwd2 = forms.CharField( pwd2 = forms.CharField(
label="Confirmer le mot de passe",
widget=forms.PasswordInput) widget=forms.PasswordInput)
def clean(self): def clean(self):
@ -128,6 +131,7 @@ class UserRestrictTeamForm(UserForm):
class UserGroupForm(forms.ModelForm): class UserGroupForm(forms.ModelForm):
groups = forms.ModelMultipleChoiceField( groups = forms.ModelMultipleChoiceField(
Group.objects.filter(name__icontains='K-Fêt'), Group.objects.filter(name__icontains='K-Fêt'),
label='Statut équipe',
required=False) required=False)
def clean_groups(self): def clean_groups(self):
@ -235,16 +239,20 @@ class CheckoutStatementUpdateForm(forms.ModelForm):
class ArticleForm(forms.ModelForm): class ArticleForm(forms.ModelForm):
category_new = forms.CharField( category_new = forms.CharField(
label="Créer une catégorie",
max_length=45, max_length=45,
required = False) required = False)
category = forms.ModelChoiceField( category = forms.ModelChoiceField(
label="Catégorie",
queryset = ArticleCategory.objects.all(), queryset = ArticleCategory.objects.all(),
required = False) required = False)
suppliers = forms.ModelMultipleChoiceField( suppliers = forms.ModelMultipleChoiceField(
label="Fournisseurs",
queryset = Supplier.objects.all(), queryset = Supplier.objects.all(),
required = False) required = False)
supplier_new = forms.CharField( supplier_new = forms.CharField(
label="Créer un fournisseur",
max_length = 45, max_length = 45,
required = False) required = False)
@ -318,11 +326,10 @@ class KPsulOperationForm(forms.ModelForm):
widget = forms.HiddenInput()) widget = forms.HiddenInput())
class Meta: class Meta:
model = Operation model = Operation
fields = ['type', 'amount', 'is_checkout', 'article', 'article_nb'] fields = ['type', 'amount', 'article', 'article_nb']
widgets = { widgets = {
'type': forms.HiddenInput(), 'type': forms.HiddenInput(),
'amount': forms.HiddenInput(), 'amount': forms.HiddenInput(),
'is_checkout': forms.HiddenInput(),
'article_nb': forms.HiddenInput(), 'article_nb': forms.HiddenInput(),
} }
@ -338,7 +345,6 @@ class KPsulOperationForm(forms.ModelForm):
"Un achat nécessite un article et une quantité") "Un achat nécessite un article et une quantité")
if article_nb < 1: if article_nb < 1:
raise ValidationError("Impossible d'acheter moins de 1 article") raise ValidationError("Impossible d'acheter moins de 1 article")
self.cleaned_data['is_checkout'] = True
elif type_ope and type_ope in [Operation.DEPOSIT, Operation.WITHDRAW]: elif type_ope and type_ope in [Operation.DEPOSIT, Operation.WITHDRAW]:
if not amount or article or article_nb: if not amount or article or article_nb:
raise ValidationError("Bad request") raise ValidationError("Bad request")
@ -478,9 +484,7 @@ class OrderArticleForm(forms.Form):
queryset=Article.objects.all(), queryset=Article.objects.all(),
widget=forms.HiddenInput(), widget=forms.HiddenInput(),
) )
quantity_ordered = forms.IntegerField( quantity_ordered = forms.IntegerField(required=False)
required=False,
widget=forms.NumberInput(attrs={'class': 'form-control'}))
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(OrderArticleForm, self).__init__(*args, **kwargs) super(OrderArticleForm, self).__init__(*args, **kwargs)
@ -507,18 +511,14 @@ class OrderArticleToInventoryForm(forms.Form):
) )
price_HT = forms.DecimalField( price_HT = forms.DecimalField(
max_digits = 7, decimal_places = 4, max_digits = 7, decimal_places = 4,
required = False, required = False)
widget=forms.NumberInput(attrs={'class': 'form-control'}))
TVA = forms.DecimalField( TVA = forms.DecimalField(
max_digits = 7, decimal_places = 2, max_digits = 7, decimal_places = 2,
required = False, required = False)
widget=forms.NumberInput(attrs={'class': 'form-control'}))
rights = forms.DecimalField( rights = forms.DecimalField(
max_digits = 7, decimal_places = 4, max_digits = 7, decimal_places = 4,
required = False, required = False)
widget=forms.NumberInput(attrs={'class': 'form-control'})) quantity_received = forms.IntegerField()
quantity_received = forms.IntegerField(
widget=forms.NumberInput(attrs={'class': 'form-control'}))
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(OrderArticleToInventoryForm, self).__init__(*args, **kwargs) super(OrderArticleToInventoryForm, self).__init__(*args, **kwargs)

View file

@ -0,0 +1,15 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('kfet', '0048_article_hidden'),
('kfet', '0048_default_datetime'),
]
operations = [
]

View file

@ -0,0 +1,23 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('kfet', '0049_merge'),
]
operations = [
migrations.RemoveField(
model_name='operation',
name='is_checkout',
),
migrations.AlterField(
model_name='operation',
name='type',
field=models.CharField(choices=[('purchase', 'Achat'), ('deposit', 'Charge'), ('withdraw', 'Retrait'), ('initial', 'Initial'), ('edit', 'Édition')], max_length=8),
),
]

View file

@ -40,7 +40,7 @@ class Account(models.Model):
balance = models.DecimalField( balance = models.DecimalField(
max_digits = 6, decimal_places = 2, max_digits = 6, decimal_places = 2,
default = 0) default = 0)
is_frozen = models.BooleanField(default = False) is_frozen = models.BooleanField("est gelé", default = False)
created_at = models.DateTimeField(auto_now_add = True, null = True) created_at = models.DateTimeField(auto_now_add = True, null = True)
# Optional # Optional
PROMO_CHOICES = [(r,r) for r in range(1980, date.today().year+1)] PROMO_CHOICES = [(r,r) for r in range(1980, date.today().year+1)]
@ -48,6 +48,7 @@ class Account(models.Model):
choices = PROMO_CHOICES, choices = PROMO_CHOICES,
blank = True, null = True, default = default_promo()) blank = True, null = True, default = default_promo())
nickname = models.CharField( nickname = models.CharField(
"surnom(s)",
max_length = 255, max_length = 255,
blank = True, default = "") blank = True, default = "")
password = models.CharField( password = models.CharField(
@ -224,14 +225,18 @@ class AccountNegative(models.Model):
start = models.DateTimeField( start = models.DateTimeField(
blank = True, null = True, default = None) blank = True, null = True, default = None)
balance_offset = models.DecimalField( balance_offset = models.DecimalField(
"décalage de balance",
help_text="Montant non compris dans l'autorisation de négatif",
max_digits = 6, decimal_places = 2, max_digits = 6, decimal_places = 2,
blank = True, null = True, default = None) blank = True, null = True, default = None)
authz_overdraft_amount = models.DecimalField( authz_overdraft_amount = models.DecimalField(
"négatif autorisé",
max_digits = 6, decimal_places = 2, max_digits = 6, decimal_places = 2,
blank = True, null = True, default = None) blank = True, null = True, default = None)
authz_overdraft_until = models.DateTimeField( authz_overdraft_until = models.DateTimeField(
"expiration du négatif",
blank = True, null = True, default = None) blank = True, null = True, default = None)
comment = models.CharField(max_length = 255, blank = True) comment = models.CharField("commentaire", max_length = 255, blank = True)
@python_2_unicode_compatible @python_2_unicode_compatible
class Checkout(models.Model): class Checkout(models.Model):
@ -273,29 +278,35 @@ class CheckoutStatement(models.Model):
checkout = models.ForeignKey( checkout = models.ForeignKey(
Checkout, on_delete = models.PROTECT, Checkout, on_delete = models.PROTECT,
related_name = "statements") related_name = "statements")
balance_old = models.DecimalField(max_digits = 6, decimal_places = 2) balance_old = models.DecimalField("ancienne balance",
balance_new = models.DecimalField(max_digits = 6, decimal_places = 2) max_digits = 6, decimal_places = 2)
amount_taken = models.DecimalField(max_digits = 6, decimal_places = 2) balance_new = models.DecimalField("nouvelle balance",
amount_error = models.DecimalField(max_digits = 6, decimal_places = 2) max_digits = 6, decimal_places = 2)
amount_taken = models.DecimalField("montant pris",
max_digits = 6, decimal_places = 2)
amount_error = models.DecimalField("montant de l'erreur",
max_digits = 6, decimal_places = 2)
at = models.DateTimeField(auto_now_add = True) at = models.DateTimeField(auto_now_add = True)
not_count = models.BooleanField(default=False) not_count = models.BooleanField("caisse non comptée", default=False)
taken_001 = models.PositiveSmallIntegerField(default=0) taken_001 = models.PositiveSmallIntegerField("pièces de 1¢", default=0)
taken_002 = models.PositiveSmallIntegerField(default=0) taken_002 = models.PositiveSmallIntegerField("pièces de 2¢", default=0)
taken_005 = models.PositiveSmallIntegerField(default=0) taken_005 = models.PositiveSmallIntegerField("pièces de 5¢", default=0)
taken_01 = models.PositiveSmallIntegerField(default=0) taken_01 = models.PositiveSmallIntegerField("pièces de 10¢", default=0)
taken_02 = models.PositiveSmallIntegerField(default=0) taken_02 = models.PositiveSmallIntegerField("pièces de 20¢", default=0)
taken_05 = models.PositiveSmallIntegerField(default=0) taken_05 = models.PositiveSmallIntegerField("pièces de 50¢", default=0)
taken_1 = models.PositiveSmallIntegerField(default=0) taken_1 = models.PositiveSmallIntegerField("pièces de 1€", default=0)
taken_2 = models.PositiveSmallIntegerField(default=0) taken_2 = models.PositiveSmallIntegerField("pièces de 2€", default=0)
taken_5 = models.PositiveSmallIntegerField(default=0) taken_5 = models.PositiveSmallIntegerField("billets de 5€", default=0)
taken_10 = models.PositiveSmallIntegerField(default=0) taken_10 = models.PositiveSmallIntegerField("billets de 10€", default=0)
taken_20 = models.PositiveSmallIntegerField(default=0) taken_20 = models.PositiveSmallIntegerField("billets de 20€", default=0)
taken_50 = models.PositiveSmallIntegerField(default=0) taken_50 = models.PositiveSmallIntegerField("billets de 50€", default=0)
taken_100 = models.PositiveSmallIntegerField(default=0) taken_100 = models.PositiveSmallIntegerField("billets de 100€", default=0)
taken_200 = models.PositiveSmallIntegerField(default=0) taken_200 = models.PositiveSmallIntegerField("billets de 200€", default=0)
taken_500 = models.PositiveSmallIntegerField(default=0) taken_500 = models.PositiveSmallIntegerField("billets de 500€", default=0)
taken_cheque = models.DecimalField(default=0, max_digits=6, decimal_places=2) taken_cheque = models.DecimalField(
"montant des chèques",
default=0, max_digits=6, decimal_places=2)
def __str__(self): def __str__(self):
return '%s %s' % (self.checkout, self.at) return '%s %s' % (self.checkout, self.at)
@ -336,19 +347,21 @@ class ArticleCategory(models.Model):
@python_2_unicode_compatible @python_2_unicode_compatible
class Article(models.Model): class Article(models.Model):
name = models.CharField(max_length = 45) name = models.CharField("nom", max_length = 45)
is_sold = models.BooleanField(default = True) is_sold = models.BooleanField("en vente", default = True)
hidden = models.BooleanField(default=False, hidden = models.BooleanField("caché",
default=False,
help_text="Si oui, ne sera pas affiché " help_text="Si oui, ne sera pas affiché "
"au public ; par exemple " "au public ; par exemple "
"sur la carte.") "sur la carte.")
price = models.DecimalField( price = models.DecimalField(
"prix",
max_digits = 6, decimal_places = 2, max_digits = 6, decimal_places = 2,
default = 0) default = 0)
stock = models.IntegerField(default = 0) stock = models.IntegerField(default = 0)
category = models.ForeignKey( category = models.ForeignKey(
ArticleCategory, on_delete = models.PROTECT, ArticleCategory, on_delete = models.PROTECT,
related_name = "articles") related_name = "articles", verbose_name='catégorie')
BOX_TYPE_CHOICES = ( BOX_TYPE_CHOICES = (
("caisse", "caisse"), ("caisse", "caisse"),
("carton", "carton"), ("carton", "carton"),
@ -356,10 +369,12 @@ class Article(models.Model):
("fût", "fût"), ("fût", "fût"),
) )
box_type = models.CharField( box_type = models.CharField(
"type de contenant",
choices = BOX_TYPE_CHOICES, choices = BOX_TYPE_CHOICES,
max_length = choices_length(BOX_TYPE_CHOICES), max_length = choices_length(BOX_TYPE_CHOICES),
blank = True, null = True, default = None) blank = True, null = True, default = None)
box_capacity = models.PositiveSmallIntegerField( box_capacity = models.PositiveSmallIntegerField(
"capacité du contenant",
blank = True, null = True, default = None) blank = True, null = True, default = None)
def __str__(self): def __str__(self):
@ -417,11 +432,11 @@ class Supplier(models.Model):
Article, Article,
through = 'SupplierArticle', through = 'SupplierArticle',
related_name = "suppliers") related_name = "suppliers")
name = models.CharField(max_length = 45) name = models.CharField("nom", max_length = 45)
address = models.TextField() address = models.TextField("adresse")
email = models.EmailField() email = models.EmailField("adresse mail")
phone = models.CharField(max_length = 10) phone = models.CharField("téléphone", max_length = 10)
comment = models.TextField() comment = models.TextField("commentaire")
def __str__(self): def __str__(self):
return self.name return self.name
@ -525,51 +540,60 @@ class Operation(models.Model):
DEPOSIT = 'deposit' DEPOSIT = 'deposit'
WITHDRAW = 'withdraw' WITHDRAW = 'withdraw'
INITIAL = 'initial' INITIAL = 'initial'
EDIT = 'edit'
TYPE_ORDER_CHOICES = ( TYPE_ORDER_CHOICES = (
(PURCHASE, 'Achat'), (PURCHASE, 'Achat'),
(DEPOSIT, 'Charge'), (DEPOSIT, 'Charge'),
(WITHDRAW, 'Retrait'), (WITHDRAW, 'Retrait'),
(INITIAL, 'Initial'), (INITIAL, 'Initial'),
(EDIT, 'Édition'),
) )
group = models.ForeignKey( group = models.ForeignKey(
OperationGroup, on_delete = models.PROTECT, OperationGroup, on_delete=models.PROTECT,
related_name = "opes") related_name="opes")
type = models.CharField( type = models.CharField(
choices = TYPE_ORDER_CHOICES, choices=TYPE_ORDER_CHOICES,
max_length = choices_length(TYPE_ORDER_CHOICES)) max_length=choices_length(TYPE_ORDER_CHOICES))
amount = models.DecimalField( amount = models.DecimalField(
max_digits = 6, decimal_places = 2, max_digits=6, decimal_places=2,
blank = True, default = 0) blank=True, default=0)
is_checkout = models.BooleanField(default = True)
# Optional # Optional
article = models.ForeignKey( article = models.ForeignKey(
Article, on_delete = models.PROTECT, Article, on_delete=models.PROTECT,
related_name = "operations", related_name="operations",
blank = True, null = True, default = None) blank=True, null=True, default=None)
article_nb = models.PositiveSmallIntegerField( article_nb = models.PositiveSmallIntegerField(
blank = True, null = True, default = None) blank=True, null=True, default=None)
canceled_by = models.ForeignKey( canceled_by = models.ForeignKey(
Account, on_delete = models.PROTECT, Account, on_delete=models.PROTECT,
related_name = "+", related_name="+",
blank = True, null = True, default = None) blank=True, null=True, default=None)
canceled_at = models.DateTimeField( canceled_at = models.DateTimeField(
blank = True, null = True, default = None) blank=True, null=True, default=None)
addcost_for = models.ForeignKey( addcost_for = models.ForeignKey(
Account, on_delete = models.PROTECT, Account, on_delete=models.PROTECT,
related_name = "addcosts", related_name="addcosts",
blank = True, null = True, default = None) blank=True, null=True, default=None)
addcost_amount = models.DecimalField( addcost_amount = models.DecimalField(
max_digits = 6, decimal_places = 2, max_digits=6, decimal_places=2,
blank = True, null = True, default = None) blank=True, null=True, default=None)
@property
def is_checkout(self):
return (self.type == Operation.DEPOSIT or
self.type == Operation.WITHDRAW or
(self.type == Operation.PURCHASE and self.group.on_acc.is_cash)
)
def __str__(self): def __str__(self):
templates = { templates = {
self.PURCHASE: "{nb} {article.name} ({amount}€)", self.PURCHASE: "{nb} {article.name} ({amount}€)",
self.DEPOSIT: "charge ({amount})", self.DEPOSIT: "charge ({amount}€)",
self.WITHDRAW: "retrait ({amount})", self.WITHDRAW: "retrait ({amount}€)",
self.INITIAL: "initial ({amount})", self.INITIAL: "initial ({amount}€)",
self.EDIT: "édition ({amount}€)",
} }
return templates[self.type].format(nb=self.article_nb, return templates[self.type].format(nb=self.article_nb,
article=self.article, article=self.article,

View file

@ -31,13 +31,22 @@ function KHistory(options={}) {
if (ope['type'] == 'purchase') { if (ope['type'] == 'purchase') {
infos1 = ope['article_nb']; infos1 = ope['article_nb'];
infos2 = ope['article__name']; infos2 = ope['article__name'];
} else if (ope['type'] == 'initial') {
infos1 = parsed_amount.toFixed(2)+'€';
infos2 = 'Initial';
} else { } else {
infos1 = parsed_amount.toFixed(2)+'€'; infos1 = parsed_amount.toFixed(2)+'€';
infos2 = (ope['type'] == 'deposit') ? 'Charge' : 'Retrait'; switch (ope['type']) {
infos2 = ope['is_checkout'] ? infos2 : 'Édition'; case 'initial':
infos2 = 'Initial';
break;
case 'withdraw':
infos2 = 'Retrait';
break;
case 'deposit':
infos2 = 'Charge';
break;
case 'edit':
infos2 = 'Édition';
break;
}
} }
$ope_html $ope_html

View file

@ -134,7 +134,10 @@ function getErrorsHtml(data) {
content += '</ul>'; content += '</ul>';
} }
if ('account' in data['errors']) { if ('account' in data['errors']) {
content += data['errors']['account']; content += 'Général';
content += '<ul>';
content += '<li>Opération invalide sur le compte '+data['errors']['account']+'</li>';
content += '</ul>';
} }
return content; return content;
} }

View file

@ -1,17 +1,27 @@
{% extends 'kfet/base.html' %} {% extends 'kfet/base.html' %}
{% load widget_tweaks %}
{% load staticfiles %}
{% block title %}Nouvel article{% endblock %} {% block title %}Nouvel article{% endblock %}
{% block content-header-title %}Création d'un article{% endblock %} {% block content-header-title %}Création d'un article{% endblock %}
{% block content %} {% block content %}
<form submit="" method="post"> {% include "kfet/base_messages.html" %}
<div class="row form-only">
<div class="col-sm-12 col-md-8 col-md-offset-2">
<div class="content-form">
<form submit="" method="post" class="form-horizontal">
{% csrf_token %} {% csrf_token %}
{{ form.as_p }} {% include 'kfet/form_snippet.html' with form=form %}
{% if not perms.kfet.add_article %} {% if not perms.kfet.add_article %}
<input type="password" name="KFETPASSWORD"> {% include 'kfet/form_authentication_snippet.html' %}
{% endif %} {% endif %}
<input type="submit" value="Enregistrer"> {% include 'kfet/form_submit_snippet.html' with value="Enregistrer" %}
</form> </form>
</div>
</div>
</div>
{% endblock %} {% endblock %}

View file

@ -101,9 +101,8 @@
<script type="text/javascript" src="{% static 'kfet/js/statistic.js' %}"></script> <script type="text/javascript" src="{% static 'kfet/js/statistic.js' %}"></script>
<script> <script>
jQuery(document).ready(function() { jQuery(document).ready(function() {
var stat_last = $("#stat_last"); var stat_last = new StatsGroup("{% url 'kfet.article.stat.last' article.id %}",
var stat_last_url = "{% url 'kfet.article.stat.last' article.id %}"; $("#stat_last"));
STAT.get_thing(stat_last_url, stat_last, "Stat non trouvées :(");
}); });
</script> </script>
{% endblock %} {% endblock %}

View file

@ -1,17 +1,27 @@
{% extends 'kfet/base.html' %} {% extends 'kfet/base.html' %}
{% load widget_tweaks %}
{% load staticfiles %}
{% block title %}Édition de l'article {{ article.name }}{% endblock %} {% block title %}Édition de l'article {{ article.name }}{% endblock %}
{% block content-header-title %}Article {{ article.name }} - Édition{% endblock %} {% block content-header-title %}Article {{ article.name }} - Édition{% endblock %}
{% block content %} {% block content %}
<form action="" method="post"> {% include "kfet/base_messages.html" %}
<div class="row form-only">
<div class="col-sm-12 col-md-8 col-md-offset-2">
<div class="content-form">
<form submit="" method="post" class="form-horizontal">
{% csrf_token %} {% csrf_token %}
{{ form.as_p }} {% include 'kfet/form_snippet.html' with form=form %}
{% if not perms.kfet.change_article %} {% if not perms.kfet.change_article %}
<input type="password" name="KFETPASSWORD"> {% include 'kfet/form_authentication_snippet.html' %}
{% endif %} {% endif %}
<input type="submit" value="Mettre à jour"> {% include 'kfet/form_submit_snippet.html' with value="Mettre à jour" %}
</form> </form>
</div>
</div>
</div>
{% endblock %} {% endblock %}

View file

@ -80,7 +80,7 @@
<td><input id="id_taken_001" name="taken_001" data-value="0.01" min="0" value="0" type="number" class="form-control" required></td> <td><input id="id_taken_001" name="taken_001" data-value="0.01" min="0" value="0" type="number" class="form-control" required></td>
</tr> </tr>
</table> </table>
Chèque: <input id="id_taken_cheque" name="taken_cheque" data-value="1" min="0" step="0.01" value="0" type="number" class="form-control" required> <p style="font-weight:bold"> Chèque:</p> <input id="id_taken_cheque" name="taken_cheque" data-value="1" min="0" step="0.01" value="0" type="number" class="form-control" required>
</div> </div>
</div> </div>
<div class="content-right-block"> <div class="content-right-block">

View file

@ -15,15 +15,15 @@ Caisse {{ checkout.name }} - Modification relevé {{ checkoutstatement.at }}
</div> </div>
<div class="col-sm-8 col-md-9 col-content-right"> <div class="col-sm-8 col-md-9 col-content-right">
{% include 'kfet/base_messages.html' %} {% include 'kfet/base_messages.html' %}
<div class="content-right"> <div class="content-right form-only">
<div class="content-right-block"> <div class="content-form">
<form action="" method="post"> <form submit="" method="post" class="form-horizontal">
{% csrf_token %} {% csrf_token %}
{{ form.as_p }} {% include 'kfet/form_snippet.html' with form=form %}
{% if not perms.kfet.change_checkoutstatement %} {% if not perms.kfet.change_checkoutstatement %}
<input type="password" name="KFETPASSWORD"> {% include 'kfet/form_authentication_snippet.html' %}
{% endif %} {% endif %}
<input type="submit" value="Enregistrer"> {% include 'kfet/form_submit_snippet.html' with value="Enregistrer" %}
</form> </form>
</div> </div>
</div> </div>

View file

@ -4,6 +4,11 @@
<label for="{{ field.id_for_label }}" class="col-sm-2 control-label">{{ field.label }}</label> <label for="{{ field.id_for_label }}" class="col-sm-2 control-label">{{ field.label }}</label>
<div class="col-sm-10"> <div class="col-sm-10">
{{ field|add_class:'form-control' }} {{ field|add_class:'form-control' }}
<span class="help-block">{{ field.errors }}</span> {% if field.errors %}
<span class="help-block">{{field.errors}}</span>
{% endif %}
{% if field.help_text %}
<span class="help-block">{{field.help_text}}</span>
{% endif %}
</div> </div>
</div> </div>

View file

@ -875,15 +875,27 @@ $(document).ready(function() {
return (-5 <= stock - nb && stock - nb <= 5); return (-5 <= stock - nb && stock - nb <= 5);
} }
function addDeposit(amount, is_checkout=1) { function addDeposit(amount) {
var deposit_basket_html = $(item_basket_default_html); var deposit_basket_html = $(item_basket_default_html);
var amount = parseFloat(amount).toFixed(2); var amount = parseFloat(amount).toFixed(2);
var index = addDepositToFormset(amount, is_checkout); var index = addDepositToFormset(amount);
var text = is_checkout ? 'Charge' : 'Édition';
deposit_basket_html deposit_basket_html
.attr('data-opeindex', index) .attr('data-opeindex', index)
.find('.number').text(amount+"€").end() .find('.number').text(amount+"€").end()
.find('.name').text(text).end() .find('.name').text('Charge').end()
.find('.amount').text(amountToUKF(amount, account_data['is_cof'], false));
basket_container.prepend(deposit_basket_html);
updateBasketRel();
}
function addEdit(amount) {
var deposit_basket_html = $(item_basket_default_html);
var amount = parseFloat(amount).toFixed(2);
var index = addEditToFormset(amount);
deposit_basket_html
.attr('data-opeindex', index)
.find('.number').text(amount+"€").end()
.find('.name').text('Édition').end()
.find('.amount').text(amountToUKF(amount, account_data['is_cof'], false)); .find('.amount').text(amountToUKF(amount, account_data['is_cof'], false));
basket_container.prepend(deposit_basket_html); basket_container.prepend(deposit_basket_html);
updateBasketRel(); updateBasketRel();
@ -1046,11 +1058,10 @@ $(document).ready(function() {
// Ask deposit or withdraw // Ask deposit or withdraw
// ----- // -----
function askDeposit(is_checkout=1) { function askDeposit() {
var title = is_checkout ? 'Montant de la charge' : "Montant de l'édition";
$.confirm({ $.confirm({
title: title, title: 'Montant de la charge',
content: '<input type="number" lang="en" step="0.01" min="0.01" on autofocus placeholder="€">', content: '<input type="number" lang="en" step="0.01" min="0.01" autofocus placeholder="€">',
backgroundDismiss: true, backgroundDismiss: true,
animation:'top', animation:'top',
closeAnimation:'bottom', closeAnimation:'bottom',
@ -1059,7 +1070,34 @@ $(document).ready(function() {
var amount = this.$content.find('input').val(); var amount = this.$content.find('input').val();
if (!$.isNumeric(amount) || amount <= 0) if (!$.isNumeric(amount) || amount <= 0)
return false; return false;
addDeposit(amount, is_checkout); addDeposit(amount);
},
onOpen: function() {
var that = this
this.$content.find('input').on('keydown', function(e) {
if (e.keyCode == 13) {
e.preventDefault();
that.$confirmButton.click();
}
});
},
onClose: function() { this._lastFocused = (articleSelect.val() ? articleNb : articleSelect) ; }
});
}
function askEdit() {
$.confirm({
title: "Montant de l'édition",
content: '<input type="number" lang="en" step="0.01" min="0.01" autofocus placeholder="€">',
backgroundDismiss: true,
animation:'top',
closeAnimation:'bottom',
keyboardEnabled: true,
confirm: function() {
var amount = this.$content.find('input').val();
if (!$.isNumeric(amount))
return false;
addEdit(amount);
}, },
onOpen: function() { onOpen: function() {
var that = this var that = this
@ -1077,7 +1115,7 @@ $(document).ready(function() {
function askWithdraw() { function askWithdraw() {
$.confirm({ $.confirm({
title: 'Montant du retrait', title: 'Montant du retrait',
content: '<input type="number" lang="en" step="0.01" min="0.01" on autofocus placeholder="€">', content: '<input type="number" lang="en" step="0.01" min="0.01" autofocus placeholder="€">',
backgroundDismiss: true, backgroundDismiss: true,
animation:'top', animation:'top',
closeAnimation:'bottom', closeAnimation:'bottom',
@ -1124,7 +1162,7 @@ $(document).ready(function() {
var mngmt_total_forms = 1; var mngmt_total_forms = 1;
var prefix_regex = /__prefix__/; var prefix_regex = /__prefix__/;
function addOperationToFormset(type, amount, article='', article_nb='', is_checkout=1) { function addOperationToFormset(type, amount, article='', article_nb='') {
var operation_html = operation_empty_html.clone(); var operation_html = operation_empty_html.clone();
var index = mngmt_total_forms; var index = mngmt_total_forms;
@ -1134,8 +1172,7 @@ $(document).ready(function() {
.find('#id_form-__prefix__-type').val(type).end() .find('#id_form-__prefix__-type').val(type).end()
.find('#id_form-__prefix__-amount').val((parseFloat(amount)).toFixed(2)).end() .find('#id_form-__prefix__-amount').val((parseFloat(amount)).toFixed(2)).end()
.find('#id_form-__prefix__-article').val(article).end() .find('#id_form-__prefix__-article').val(article).end()
.find('#id_form-__prefix__-article_nb').val(article_nb).end() .find('#id_form-__prefix__-article_nb').val(article_nb).end();
.find('#id_form-__prefix__-is_checkout').val(is_checkout);
mngmt_total_forms_input.val(index+1); mngmt_total_forms_input.val(index+1);
mngmt_total_forms++; mngmt_total_forms++;
@ -1150,12 +1187,16 @@ $(document).ready(function() {
return index; return index;
} }
function addDepositToFormset(amount, is_checkout=1) { function addDepositToFormset(amount) {
return addOperationToFormset('deposit', amount, '', '', is_checkout); return addOperationToFormset('deposit', amount, '', '');
} }
function addWithdrawToFormset(amount, is_checkout=1) { function addEditToFormset(amount) {
return addOperationToFormset('withdraw', amount, '', '', is_checkout); return addOperationToFormset('edit', amount, '', '');
}
function addWithdrawToFormset(amount) {
return addOperationToFormset('withdraw', amount, '', '');
} }
function addPurchaseToFormset(article_id, article_nb, amount=0) { function addPurchaseToFormset(article_id, article_nb, amount=0) {
@ -1440,7 +1481,7 @@ $(document).ready(function() {
return false; return false;
case 119: case 119:
// F8 - Edition // F8 - Edition
askDeposit(0); askEdit();
return false; return false;
case 120: case 120:
// F9 - Addcost // F9 - Addcost

View file

@ -1,4 +1,5 @@
{% extends 'kfet/base.html' %} {% extends 'kfet/base.html' %}
{% load widget_tweaks %}
{% block title %}Nouvelle commande{% endblock %} {% block title %}Nouvelle commande{% endblock %}
{% block content-header-title %}Nouvelle commande {{ supplier.name }}{% endblock %} {% block content-header-title %}Nouvelle commande {{ supplier.name }}{% endblock %}
@ -60,7 +61,7 @@
<td>{{ form.v_prev }}</td> <td>{{ form.v_prev }}</td>
<td>{{ form.stock }}</td> <td>{{ form.stock }}</td>
<td>{{ form.c_rec }}</td> <td>{{ form.c_rec }}</td>
<td>{{ form.quantity_ordered }}</td> <td>{{ form.quantity_ordered | add_class:"form-control" }}</td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>

View file

@ -1,4 +1,5 @@
{% extends 'kfet/base.html' %} {% extends 'kfet/base.html' %}
{% load widget_tweaks %}
{% block title %}{% endblock %} {% block title %}{% endblock %}
{% block content-header-title %}{% endblock %} {% block content-header-title %}{% endblock %}
@ -33,11 +34,11 @@
<tr> <tr>
{{ form.article }} {{ form.article }}
<td>{{ form.name }}</td> <td>{{ form.name }}</td>
<td>{{ form.price_HT }}</td> <td>{{ form.price_HT | add_class:"form-control" }}</td>
<td>{{ form.TVA }}</td> <td>{{ form.TVA | add_class:"form-control" }}</td>
<td>{{ form.rights }}</td> <td>{{ form.rights | add_class:"form-control" }}</td>
<td>{{ form.quantity_ordered }}</td> <td>{{ form.quantity_ordered }}</td>
<td>{{ form.quantity_received }}</td> <td>{{ form.quantity_received | add_class:"form-control" }}</td>
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>

View file

@ -1,4 +1,6 @@
{% extends 'kfet/base.html' %} {% extends 'kfet/base.html' %}
{% load widget_tweaks %}
{% load staticfiles %}
{% block title %}Fournisseur - Modification{% endblock %} {% block title %}Fournisseur - Modification{% endblock %}
{% block content-header-title %}Fournisseur - Modification{% endblock %} {% block content-header-title %}Fournisseur - Modification{% endblock %}
@ -7,13 +9,19 @@
{% include 'kfet/base_messages.html' %} {% include 'kfet/base_messages.html' %}
<form action="" method="post"> <div class="row form-only">
<div class="col-sm-12 col-md-8 col-md-offset-2">
<div class="content-form">
<form submit="" method="post" class="form-horizontal">
{% csrf_token %} {% csrf_token %}
{{ form.as_p }} {% include 'kfet/form_snippet.html' with form=form %}
{% if not perms.kfet.change_supplier %} {% if not perms.kfet.change_supplier %}
<input type="password" name="KFETPASSWORD"> {% include 'kfet/form_authentication_snippet.html' %}
{% endif %} {% endif %}
<input type="submit" class="btn btn-primary btn-lg"> {% include 'kfet/form_submit_snippet.html' with value="Mettre à jour" %}
</form> </form>
</div>
</div>
</div>
{% endblock %} {% endblock %}

View file

@ -165,8 +165,7 @@ def account_create_special(request):
ope = Operation.objects.create( ope = Operation.objects.create(
group = opegroup, group = opegroup,
type = Operation.INITIAL, type = Operation.INITIAL,
amount = amount, amount = amount)
is_checkout = False)
messages.success(request, 'Compte créé : %s' % account.trigramme) messages.success(request, 'Compte créé : %s' % account.trigramme)
return redirect('kfet.account.create') return redirect('kfet.account.create')
except Account.UserHasAccount as e: except Account.UserHasAccount as e:
@ -1010,10 +1009,7 @@ def kpsul_perform_operations(request):
operation.amount -= operation.addcost_amount operation.amount -= operation.addcost_amount
to_addcost_for_balance += operation.addcost_amount to_addcost_for_balance += operation.addcost_amount
if operationgroup.on_acc.is_cash: if operationgroup.on_acc.is_cash:
operation.is_checkout = True
to_checkout_balance += -operation.amount to_checkout_balance += -operation.amount
else:
operation.is_checkout = False
if operationgroup.on_acc.is_cof: if operationgroup.on_acc.is_cof:
if is_addcost: if is_addcost:
operation.addcost_amount = operation.addcost_amount / cof_grant_divisor operation.addcost_amount = operation.addcost_amount / cof_grant_divisor
@ -1021,13 +1017,13 @@ def kpsul_perform_operations(request):
to_articles_stocks[operation.article] -= operation.article_nb to_articles_stocks[operation.article] -= operation.article_nb
else: else:
if operationgroup.on_acc.is_cash: if operationgroup.on_acc.is_cash:
data['errors']['account'] = 'Charge et retrait impossible sur LIQ' data['errors']['account'] = 'LIQ'
if operation.type != Operation.EDIT:
to_checkout_balance += operation.amount to_checkout_balance += operation.amount
operationgroup.amount += operation.amount operationgroup.amount += operation.amount
if operation.type == Operation.DEPOSIT: if operation.type == Operation.DEPOSIT:
required_perms.add('kfet.perform_deposit') required_perms.add('kfet.perform_deposit')
if (not operation.is_checkout if operation.type == Operation.EDIT:
and operation.type in [Operation.DEPOSIT, Operation.WITHDRAW]):
required_perms.add('kfet.edit_balance_account') required_perms.add('kfet.edit_balance_account')
need_comment = True need_comment = True
if operationgroup.on_acc.is_cof: if operationgroup.on_acc.is_cof:
@ -1124,8 +1120,7 @@ def kpsul_perform_operations(request):
ope_data = { ope_data = {
'id': operation.pk, 'type': operation.type, 'amount': operation.amount, 'id': operation.pk, 'type': operation.type, 'amount': operation.amount,
'addcost_amount': operation.addcost_amount, 'addcost_amount': operation.addcost_amount,
'addcost_for__trigramme': is_addcost and addcost_for.trigramme or None, 'addcost_for__trigramme': operation.addcost_for and addcost_for.trigramme or None,
'is_checkout': operation.is_checkout,
'article__name': operation.article and operation.article.name or None, 'article__name': operation.article and operation.article.name or None,
'article_nb': operation.article_nb, 'article_nb': operation.article_nb,
'group_id': operationgroup.pk, 'group_id': operationgroup.pk,
@ -1215,7 +1210,7 @@ def kpsul_cancel_operations(request):
.order_by('at') .order_by('at')
.last()) .last())
if not last_statement or last_statement.at < ope.group.at: if not last_statement or last_statement.at < ope.group.at:
if ope.type == Operation.PURCHASE: if ope.is_checkout:
if ope.group.on_acc.is_cash: if ope.group.on_acc.is_cash:
to_checkouts_balances[ope.group.checkout] -= - ope.amount to_checkouts_balances[ope.group.checkout] -= - ope.amount
else: else:
@ -1380,7 +1375,6 @@ def history_json(request):
'type' : ope.type, 'type' : ope.type,
'amount' : ope.amount, 'amount' : ope.amount,
'article_nb' : ope.article_nb, 'article_nb' : ope.article_nb,
'is_checkout' : ope.is_checkout,
'addcost_amount': ope.addcost_amount, 'addcost_amount': ope.addcost_amount,
'canceled_at' : ope.canceled_at, 'canceled_at' : ope.canceled_at,
'article__name': 'article__name':