forked from DGNum/gestioCOF
Merge branch 'master' into Kerl/drop_py2_compat
This commit is contained in:
commit
a73736bf41
84 changed files with 10296 additions and 486 deletions
|
@ -294,17 +294,17 @@ class KPsulAccountForm(forms.ModelForm):
|
|||
|
||||
class KPsulCheckoutForm(forms.Form):
|
||||
checkout = forms.ModelChoiceField(
|
||||
queryset=(
|
||||
Checkout.objects
|
||||
.filter(
|
||||
is_protected=False,
|
||||
valid_from__lte=timezone.now(),
|
||||
valid_to__gte=timezone.now(),
|
||||
)
|
||||
),
|
||||
queryset=None,
|
||||
widget=forms.Select(attrs={'id': 'id_checkout_select'}),
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
# Create the queryset on form instanciation to use the current time.
|
||||
self.fields['checkout'].queryset = (
|
||||
Checkout.objects.is_valid().filter(is_protected=False))
|
||||
|
||||
|
||||
class KPsulOperationForm(forms.ModelForm):
|
||||
article = forms.ModelChoiceField(
|
||||
|
|
20
kfet/migrations/0063_promo.py
Normal file
20
kfet/migrations/0063_promo.py
Normal file
|
@ -0,0 +1,20 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.12 on 2018-04-05 21:47
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('kfet', '0062_delete_globalpermissions'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='account',
|
||||
name='promo',
|
||||
field=models.IntegerField(blank=True, choices=[(1980, 1980), (1981, 1981), (1982, 1982), (1983, 1983), (1984, 1984), (1985, 1985), (1986, 1986), (1987, 1987), (1988, 1988), (1989, 1989), (1990, 1990), (1991, 1991), (1992, 1992), (1993, 1993), (1994, 1994), (1995, 1995), (1996, 1996), (1997, 1997), (1998, 1998), (1999, 1999), (2000, 2000), (2001, 2001), (2002, 2002), (2003, 2003), (2004, 2004), (2005, 2005), (2006, 2006), (2007, 2007), (2008, 2008), (2009, 2009), (2010, 2010), (2011, 2011), (2012, 2012), (2013, 2013), (2014, 2014), (2015, 2015), (2016, 2016), (2017, 2017), (2018, 2018)], default=2017, null=True),
|
||||
),
|
||||
]
|
|
@ -340,6 +340,13 @@ class AccountNegative(models.Model):
|
|||
return self.start + kfet_config.overdraft_duration
|
||||
|
||||
|
||||
class CheckoutQuerySet(models.QuerySet):
|
||||
|
||||
def is_valid(self):
|
||||
now = timezone.now()
|
||||
return self.filter(valid_from__lte=now, valid_to__gte=now)
|
||||
|
||||
|
||||
class Checkout(models.Model):
|
||||
created_by = models.ForeignKey(
|
||||
Account, on_delete = models.PROTECT,
|
||||
|
@ -352,6 +359,8 @@ class Checkout(models.Model):
|
|||
default = 0)
|
||||
is_protected = models.BooleanField(default = False)
|
||||
|
||||
objects = CheckoutQuerySet.as_manager()
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('kfet.checkout.read', kwargs={'pk': self.pk})
|
||||
|
||||
|
@ -361,6 +370,22 @@ class Checkout(models.Model):
|
|||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
created = self.pk is None
|
||||
|
||||
ret = super().save(*args, **kwargs)
|
||||
|
||||
if created:
|
||||
self.statements.create(
|
||||
amount_taken=0,
|
||||
balance_old=self.balance,
|
||||
balance_new=self.balance,
|
||||
by=self.created_by,
|
||||
)
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
class CheckoutTransfer(models.Model):
|
||||
from_checkout = models.ForeignKey(
|
||||
Checkout, on_delete = models.PROTECT,
|
||||
|
|
|
@ -75,6 +75,10 @@ ul {
|
|||
padding:8px !important;
|
||||
}
|
||||
|
||||
.table thead .sm-padding {
|
||||
padding:3px !important;
|
||||
}
|
||||
|
||||
.table tr.section {
|
||||
background: #c63b52 !important;
|
||||
color:#fff;
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
/* Libs customizations */
|
||||
@import url("libs/jconfirm-kfet.css");
|
||||
@import url("libs/jquery-tablesorter-kfet.css");
|
||||
@import url("libs/multiple-select-kfet.css");
|
||||
|
||||
/* Base */
|
||||
|
@ -54,6 +55,11 @@
|
|||
color: #C81022;
|
||||
}
|
||||
|
||||
.table thead .glyphicon {
|
||||
font-size: 12px;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Pages tableaux seuls
|
||||
|
@ -82,6 +88,11 @@
|
|||
border-radius: 0;
|
||||
}
|
||||
|
||||
.table td.small-width {
|
||||
/* Header still extends the width of the column, but it will be minimal. */
|
||||
width: 30px;
|
||||
}
|
||||
|
||||
.auth-form {
|
||||
padding: 15px 0;
|
||||
background: #d86c7e;
|
||||
|
|
0
kfet/static/kfet/css/libs/jquery-tablesorter-kfet.css
Normal file
0
kfet/static/kfet/css/libs/jquery-tablesorter-kfet.css
Normal file
|
@ -235,3 +235,77 @@ function submit_url(el) {
|
|||
let url = $(el).data('url');
|
||||
create_form(url).appendTo($('body')).submit();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* jquery-tablesorter
|
||||
* https://mottie.github.io/tablesorter/docs/
|
||||
*
|
||||
* Known bugs (v2.29.0):
|
||||
* - Sort order icons in sticky headers are not updated.
|
||||
* Status: Fixed in next release.
|
||||
*
|
||||
* TODO:
|
||||
* - Handle i18n.
|
||||
*/
|
||||
|
||||
|
||||
function registerBoolParser(id, true_str, false_str) {
|
||||
$.tablesorter.addParser({
|
||||
id: id,
|
||||
format: function(s) {
|
||||
return s.toLowerCase()
|
||||
.replace(true_str, 1)
|
||||
.replace(false_str, 0);
|
||||
},
|
||||
type: 'numeric'
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// Parsers for the text representations of boolean.
|
||||
registerBoolParser('yesno', 'oui', 'non');
|
||||
|
||||
registerBoolParser('article__is_sold', 'en vente', 'non vendu');
|
||||
registerBoolParser('article__hidden', 'caché', 'affiché');
|
||||
|
||||
|
||||
// https://mottie.github.io/tablesorter/docs/index.html#variable-defaults
|
||||
$.extend(true, $.tablesorter.defaults, {
|
||||
headerTemplate: '{content} {icon}',
|
||||
|
||||
cssIconAsc : 'glyphicon glyphicon-chevron-up',
|
||||
cssIconDesc : 'glyphicon glyphicon-chevron-down',
|
||||
cssIconNone : 'glyphicon glyphicon-resize-vertical',
|
||||
|
||||
// Only four-digits format year is handled by the builtin parser
|
||||
// 'shortDate'.
|
||||
dateFormat: 'ddmmyyyy',
|
||||
|
||||
// Accented characters are replaced with their non-accented one.
|
||||
sortLocaleCompare: true,
|
||||
// French format: 1 234,56
|
||||
usNumberFormat: false,
|
||||
|
||||
widgets: ['stickyHeaders'],
|
||||
widgetOptions: {
|
||||
stickyHeaders_offset: '.navbar',
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// https://mottie.github.io/tablesorter/docs/index.html#variable-language
|
||||
$.extend($.tablesorter.language, {
|
||||
sortAsc : 'Trié par ordre croissant, ',
|
||||
sortDesc : 'Trié par ordre décroissant, ',
|
||||
sortNone : 'Non trié, ',
|
||||
sortDisabled : 'tri désactivé et/ou non-modifiable',
|
||||
nextAsc : 'cliquer pour trier par ordre croissant',
|
||||
nextDesc : 'cliquer pour trier par ordre décroissant',
|
||||
nextNone : 'cliquer pour retirer le tri'
|
||||
});
|
||||
|
||||
|
||||
$( function() {
|
||||
$('.sortable').tablesorter();
|
||||
});
|
||||
|
|
6014
kfet/static/kfet/vendor/jquery-tablesorter/jquery.tablesorter.combined.js
vendored
Normal file
6014
kfet/static/kfet/vendor/jquery-tablesorter/jquery.tablesorter.combined.js
vendored
Normal file
File diff suppressed because it is too large
Load diff
|
@ -37,13 +37,16 @@
|
|||
|
||||
<section>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover table-condensed">
|
||||
<table
|
||||
class="table table-hover table-condensed sortable"
|
||||
{# Initial sort: [(trigramme,asc)] #}
|
||||
data-sortlist="[[0,0]]">
|
||||
<thead>
|
||||
<tr>
|
||||
<td class="text-center">Tri.</td>
|
||||
<td>Nom</td>
|
||||
<td class="text-right">Balance</td>
|
||||
<td class="text-center">COF</td>
|
||||
<td class="text-center" data-sorter="yesno">COF</td>
|
||||
<td>Dpt</td>
|
||||
<td class="text-center">Promo</td>
|
||||
</tr>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
{% block header-title %}Création d'un compte{% endblock %}
|
||||
|
||||
{% block extra_head %}
|
||||
<script src="{% static "autocomplete_light/autocomplete.js" %}" type="text/javascript"></script>
|
||||
<script src="{% static "vendor/jquery.autocomplete-light/3.5.0/dist/jquery.autocomplete-light.min.js" %}" type="text/javascript"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
|
|
|
@ -35,16 +35,19 @@
|
|||
{% block main %}
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover table-condensed">
|
||||
<table
|
||||
class="table table-hover table-condensed sortable"
|
||||
{# Initial sort: [(trigramme,asc)] #}
|
||||
data-sortlist="[[0,0]]">
|
||||
<thead>
|
||||
<tr>
|
||||
<td class="text-center">Tri.</td>
|
||||
<td>Nom</td>
|
||||
<td class="text-right">Balance</td>
|
||||
<td class="text-right">Réelle</td>
|
||||
<td>Début</td>
|
||||
<td data-sorter="shortDate">Début</td>
|
||||
<td>Découvert autorisé</td>
|
||||
<td>Jusqu'au</td>
|
||||
<td data-sorter="shortDate">Jusqu'au</td>
|
||||
<td>Balance offset</td>
|
||||
</tr>
|
||||
</thead>
|
||||
|
@ -63,9 +66,13 @@
|
|||
{{ neg.account.real_balance|floatformat:2 }}€
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ neg.start|date:'d/m/Y H:i:s'}}</td>
|
||||
<td title="{{ neg.start }}">
|
||||
{{ neg.start|date:'d/m/Y H:i'}}
|
||||
</td>
|
||||
<td>{{ neg.authz_overdraft_amount|default_if_none:'' }}</td>
|
||||
<td>{{ neg.authz_overdrafy_until|default_if_none:'' }}</td>
|
||||
<td title="{{ neg.authz_overdraft_until }}">
|
||||
{{ neg.authz_overdraft_until|date:'d/m/Y H:i' }}
|
||||
</td>
|
||||
<td>{{ neg.balance_offset|default_if_none:'' }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
|
|
@ -7,8 +7,11 @@
|
|||
|
||||
<aside>
|
||||
<div class="heading">
|
||||
{{ articles|length }}
|
||||
<span class="sub">article{{ articles|length|pluralize }}</span>
|
||||
{{ nb_articles }}
|
||||
<span class="sub">article{{ nb_articles|pluralize }}</span>
|
||||
</div>
|
||||
<div class="heading">
|
||||
<span class="sub">dont {{ articles|length }} en vente</span>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
|
@ -25,39 +28,99 @@
|
|||
{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
<h2>Article{{ articles|length|pluralize}} en vente</h2>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover table-condensed">
|
||||
<table
|
||||
class="table table-hover table-condensed sortable"
|
||||
{# Initial sort: [(is_sold,desc), (name,asc)] #}
|
||||
data-sortlist="[[3,1], [0,0]]">
|
||||
<thead>
|
||||
<tr>
|
||||
<td>Nom</td>
|
||||
<td class="text-right">Prix</td>
|
||||
<td class="text-right">Stock</td>
|
||||
<td class="text-right">En vente</td>
|
||||
<td class="text-right">Affiché</td>
|
||||
<td class="text-right">Dernier inventaire</td>
|
||||
<td class="text-right" data-sorter="article__is_sold">En vente</td>
|
||||
<td class="text-right" data-sorter="article__hidden">Affiché</td>
|
||||
<td class="text-right" data-sorter="shortDate">Dernier inventaire</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for article in articles %}
|
||||
{% ifchanged article.category %}
|
||||
<tr class="section">
|
||||
<td colspan="6">{{ article.category.name }}</td>
|
||||
</tr>
|
||||
{% endifchanged %}
|
||||
{% regroup articles by category as category_list %}
|
||||
|
||||
{% for category in category_list %}
|
||||
<tbody class="tablesorter-no-sort">
|
||||
<tr class="section">
|
||||
<td colspan="6">{{ category.grouper }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tbody>
|
||||
{% for article in category.list %}
|
||||
<tr>
|
||||
<td>
|
||||
<a href="{% url 'kfet.article.read' article.pk %}">
|
||||
{{ article.name }}
|
||||
</a>
|
||||
</td>
|
||||
<td class="text-right">{{ article.price }}€</td>
|
||||
<td class="text-right">{{ article.stock }}</td>
|
||||
<td class="text-right">{{ article.is_sold | yesno:"En vente,Non vendu"}}</td>
|
||||
<td class="text-right">{{ article.hidden | yesno:"Caché,Affiché" }}</td>
|
||||
{% with last_inventory=article.inventory.0 %}
|
||||
<td class="text-right" title="{{ last_inventory.at }}">
|
||||
{{ last_inventory.at|date:'d/m/Y H:i' }}
|
||||
</td>
|
||||
{% endwith %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<h2>Article{{ not_sold_articles|length|pluralize }} non vendu{{ nots_sold_article|length|pluralize }}</h2>
|
||||
<div class="table-responsive">
|
||||
<table
|
||||
class="table table-hover table-condensed sortable"
|
||||
{# Initial sort: [(is_sold,desc), (name,asc)] #}
|
||||
data-sortlist="[[3,1], [0,0]]">
|
||||
<thead>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="{% url 'kfet.article.read' article.pk %}">
|
||||
{{ article.name }}
|
||||
</a>
|
||||
</td>
|
||||
<td class="text-right">{{ article.price }}€</td>
|
||||
<td class="text-right">{{ article.stock }}</td>
|
||||
<td class="text-right">{{ article.is_sold | yesno:"En vente,Non vendu"}}</td>
|
||||
<td class="text-right">{{ article.hidden | yesno:"Caché,Affiché" }}</td>
|
||||
<td class="text-right">{{ article.inventory.0.at }}</td>
|
||||
<td>Nom</td>
|
||||
<td class="text-right">Prix</td>
|
||||
<td class="text-right">Stock</td>
|
||||
<td class="text-right" data-sorter="article__is_sold">En vente</td>
|
||||
<td class="text-right" data-sorter="article__hidden">Affiché</td>
|
||||
<td class="text-right" data-sorter="shortDate">Dernier inventaire</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</thead>
|
||||
{% regroup not_sold_articles by category as not_sold_category_list %}
|
||||
|
||||
{% for category in not_sold_category_list %}
|
||||
<tbody class="tablesorter-no-sort">
|
||||
<tr class="section">
|
||||
<td colspan="6">{{ category.grouper }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tbody>
|
||||
{% for article in category.list %}
|
||||
<tr>
|
||||
<td>
|
||||
<a href="{% url 'kfet.article.read' article.pk %}">
|
||||
{{ article.name }}
|
||||
</a>
|
||||
</td>
|
||||
<td class="text-right">{{ article.price }}€</td>
|
||||
<td class="text-right">{{ article.stock }}</td>
|
||||
<td class="text-right">{{ article.is_sold | yesno:"En vente,Non vendu"}}</td>
|
||||
<td class="text-right">{{ article.hidden | yesno:"Caché,Affiché" }}</td>
|
||||
{% with last_inventory=article.inventory.0 %}
|
||||
<td class="text-right" title="{{ last_inventory.at }}">
|
||||
{{ last_inventory.at|date:'d/m/Y H:i' }}
|
||||
</td>
|
||||
{% endwith %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
<table class="table table-hover table-condensed">
|
||||
<table
|
||||
class="table table-hover table-condensed sortable"
|
||||
{# Initial sort: [(inventory.at,desc)] #}
|
||||
data-sortlist="[[0,1]]">
|
||||
<thead>
|
||||
<tr>
|
||||
<td>Date</td>
|
||||
<td data-sorter="shortDate">Date</td>
|
||||
<td>Stock</td>
|
||||
<td>Erreur</td>
|
||||
</tr>
|
||||
|
@ -9,9 +12,9 @@
|
|||
<tbody>
|
||||
{% for inventoryart in inventoryarts %}
|
||||
<tr>
|
||||
<td>
|
||||
<td title="{{ inventoryart.inventory.at }}">
|
||||
<a href="{% url "kfet.inventory.read" inventoryart.inventory.pk %}">
|
||||
{{ inventoryart.inventory.at }}
|
||||
{{ inventoryart.inventory.at|date:'d/m/Y H:i' }}
|
||||
</a>
|
||||
</td>
|
||||
<td>{{ inventoryart.stock_new }}</td>
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
<table class="table table-hover table-condensed">
|
||||
<table
|
||||
class="table table-hover table-condensed sortable"
|
||||
{# Initial sort: [(at,desc)] #}
|
||||
data-sortlist="[[0,1]]">
|
||||
<thead>
|
||||
<tr>
|
||||
<td>Date</td>
|
||||
<td data-sorter="shortDate">Date</td>
|
||||
<td>Fournisseur</td>
|
||||
<td>HT</td>
|
||||
<td>TVA</td>
|
||||
|
@ -11,7 +14,9 @@
|
|||
<tbody>
|
||||
{% for supplierart in supplierarts %}
|
||||
<tr>
|
||||
<td>{{ supplierart.at }}</td>
|
||||
<td title="{{ supplierart.at }}">
|
||||
{{ supplierart.at|date:'d/m/Y' }}
|
||||
</td>
|
||||
<td>{{ supplierart.supplier.name }}</td>
|
||||
<td>{{ supplierart.price_HT|default_if_none:"" }}</td>
|
||||
<td>{{ supplierart.TVA|default_if_none:"" }}</td>
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
|
||||
<script type="text/javascript" src="{% static 'kfet/js/jquery-ui.min.js' %}"></script>
|
||||
<script type="text/javascript" src="{% static 'kfet/js/jquery-confirm.js' %}"></script>
|
||||
<script type="text/javascript" src="{% static 'kfet/vendor/jquery-tablesorter/jquery.tablesorter.combined.js' %}"></script>
|
||||
<script type="text/javascript" src="{% static 'kfet/js/reconnecting-websocket.js' %}"></script>
|
||||
<script type="text/javascript" src="{% static 'kfet/js/moment.js' %}"></script>
|
||||
<script type="text/javascript" src="{% static 'kfet/js/moment-fr.js' %}"></script>
|
||||
|
|
|
@ -17,12 +17,15 @@
|
|||
{% block main %}
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover table-condensed">
|
||||
<table
|
||||
class="table table-hover table-condensed sortable"
|
||||
{# Initial sort: [(name,asc)] #}
|
||||
data-sortlist="[[0,0]]">
|
||||
<thead>
|
||||
<tr>
|
||||
<td>Nom</td>
|
||||
<td class="text-right">Nombre d'articles</td>
|
||||
<td class="text-right">Peut être majorée</td>
|
||||
<td class="text-right" data-sorter="yesno">Peut être majorée</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
|
|
@ -24,13 +24,16 @@
|
|||
{% block main %}
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover table-condensed">
|
||||
<table
|
||||
class="table table-hover table-condensed sortable"
|
||||
{# Initial sort: [(valid_to,desc)] #}
|
||||
data-sortlist="[[3,1]]">
|
||||
<thead>
|
||||
<tr>
|
||||
<td>Nom</td>
|
||||
<td class="text-right">Balance</td>
|
||||
<td class="text-right">Déb. valid.</td>
|
||||
<td class="text-right">Fin valid.</td>
|
||||
<td class="text-right" data-parser="shortDate">Déb. valid.</td>
|
||||
<td class="text-right" data-parser="shortDate">Fin valid.</td>
|
||||
<td class="text-right">Protégée</td>
|
||||
</tr>
|
||||
</thead>
|
||||
|
@ -43,8 +46,12 @@
|
|||
</a>
|
||||
</td>
|
||||
<td class="text-right">{{ checkout.balance}}€</td>
|
||||
<td class="text-right">{{ checkout.valid_from }}</td>
|
||||
<td class="text-right">{{ checkout.valid_to }}</td>
|
||||
<td class="text-right" title="{{ checkout.valid_from }}">
|
||||
{{ checkout.valid_from|date:'d/m/Y H:i' }}
|
||||
</td>
|
||||
<td class="text-right" title="{{ checkout.valid_to }}">
|
||||
{{ checkout.valid_to|date:'d/m/Y H:i' }}
|
||||
</td>
|
||||
<td class="text-right">{{ checkout.is_protected|yesno }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
|
|
@ -14,10 +14,13 @@
|
|||
{% if not statements %}
|
||||
Pas de relevé
|
||||
{% else %}
|
||||
<table class="table table-hover table-condensed">
|
||||
<table
|
||||
class="table table-hover table-condensed sortable"
|
||||
{# Initial sort: [(at,desc)] #}
|
||||
data-sortlist="[[0,1]]">
|
||||
<thead>
|
||||
<tr>
|
||||
<td>Date/heure</td>
|
||||
<td data-sorter="shortDate">Date/heure</td>
|
||||
<td>Montant pris</td>
|
||||
<td>Montant laissé</td>
|
||||
<td>Erreur</td>
|
||||
|
@ -25,9 +28,9 @@
|
|||
<tbody>
|
||||
{% for statement in statements %}
|
||||
<tr>
|
||||
<td>
|
||||
<td title="{{ statement.at }}">
|
||||
<a href="{% url 'kfet.checkoutstatement.update' checkout.pk statement.pk %}">
|
||||
{{ statement.at }}
|
||||
{{ statement.at|date:'d/m/Y H:i' }}
|
||||
</a>
|
||||
</td>
|
||||
<td>{{ statement.amount_taken }}</td>
|
||||
|
|
|
@ -17,10 +17,13 @@
|
|||
{% block main %}
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover table-condensed">
|
||||
<table
|
||||
class="table table-hover table-condensed sortable"
|
||||
{# Initial sort: [(at,desc)] #}
|
||||
data-sortlist="[[0,1]]">
|
||||
<thead>
|
||||
<tr>
|
||||
<td>Date</td>
|
||||
<td data-sorter="shortDate">Date</td>
|
||||
<td>Par</td>
|
||||
<td>Nb articles</td>
|
||||
</tr>
|
||||
|
@ -28,9 +31,9 @@
|
|||
<tbody>
|
||||
{% for inventory in inventories %}
|
||||
<tr>
|
||||
<td>
|
||||
<td title="{{ inventory.at }}">
|
||||
<a href="{% url 'kfet.inventory.read' inventory.pk %}">
|
||||
<span>{{ inventory.at }}</span>
|
||||
{{ inventory.at|date:'d/m/Y H:i' }}
|
||||
</a>
|
||||
</td>
|
||||
<td>{{ inventory.by }}</td>
|
||||
|
|
|
@ -27,7 +27,10 @@
|
|||
{% block main %}
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-condensed">
|
||||
<table
|
||||
class="table table-condensed table-hover table-striped sortable"
|
||||
{# Initial sort: [(article.name,asc)] #}
|
||||
data-sortlist="[[0,0]]">
|
||||
<thead>
|
||||
<tr>
|
||||
<td>Article</td>
|
||||
|
@ -36,25 +39,28 @@
|
|||
<td>Erreur</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for inventoryart in inventoryarts %}
|
||||
{% ifchanged inventoryart.article.category %}
|
||||
<tr class="section">
|
||||
<td colspan="4">{{ inventoryart.article.category.name }}</td>
|
||||
</tr>
|
||||
{% endifchanged %}
|
||||
<tr>
|
||||
<td>
|
||||
<a href="{% url "kfet.article.read" inventoryart.article.id %}">
|
||||
{{ inventoryart.article.name }}
|
||||
</a>
|
||||
</td>
|
||||
<td>{{ inventoryart.stock_old }}</td>
|
||||
<td>{{ inventoryart.stock_new }}</td>
|
||||
<td>{{ inventoryart.stock_error }}</td>
|
||||
{% regroup inventoryarts by article.category as category_list %}
|
||||
{% for category in category_list %}
|
||||
<tbody class="tablesorter-no-sort">
|
||||
<tr class="section">
|
||||
<td colspan="4">{{ category.grouper.name }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</tbody>
|
||||
<tbody>
|
||||
{% for inventoryart in category.list %}
|
||||
<tr>
|
||||
<td>
|
||||
<a href="{% url "kfet.article.read" inventoryart.article.id %}">
|
||||
{{ inventoryart.article.name }}
|
||||
</a>
|
||||
</td>
|
||||
<td>{{ inventoryart.stock_old }}</td>
|
||||
<td>{{ inventoryart.stock_new }}</td>
|
||||
<td>{{ inventoryart.stock_error }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -55,11 +55,14 @@
|
|||
<section>
|
||||
<h2>Liste des commandes</h2>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover table-condensed">
|
||||
<table
|
||||
class="table table-hover table-condensed sortable"
|
||||
{# Initial sort: [(at,desc)] #}
|
||||
data-sortlist="[[1,1]]">
|
||||
<thead>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>Date</td>
|
||||
<td data-sorter="false"></td>
|
||||
<td data-parser="shortDate">Date</td>
|
||||
<td>Fournisseur</td>
|
||||
<td>Inventaire</td>
|
||||
</tr>
|
||||
|
@ -74,9 +77,9 @@
|
|||
</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<td tile="{{ order.at }}">
|
||||
<a href="{% url 'kfet.order.read' order.pk %}">
|
||||
{{ order.at }}
|
||||
{{ order.at|date:'d/m/Y H:i' }}
|
||||
</a>
|
||||
</td>
|
||||
<td>{{ order.supplier }}</td>
|
||||
|
|
|
@ -11,60 +11,79 @@
|
|||
<form action="" method="post">
|
||||
{% csrf_token %}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover table-condensed table-condensed-input text-center table-striped">
|
||||
<table
|
||||
class="table table-hover table-condensed table-condensed-input text-center table-striped sortable"
|
||||
{# Initial sort: [(name,asc)] #}
|
||||
data-sortlist="[[0,0]]">
|
||||
<thead>
|
||||
<tr>
|
||||
<td rowspan="2">Article</td>
|
||||
<td colspan="{{ scale|length }}">Ventes
|
||||
<span class='glyphicon glyphicon-question-sign' title="Ventes des 5 dernières semaines" data-placement="bottom"></span>
|
||||
</td>
|
||||
<td rowspan="2">V. moy.<br>
|
||||
<span class='glyphicon glyphicon-question-sign' title="Moyenne des ventes" data-placement="bottom"></span>
|
||||
</td>
|
||||
<td rowspan="2">E.T.<br>
|
||||
<span class='glyphicon glyphicon-question-sign' title="Écart-type des ventes" data-placement="bottom"></span>
|
||||
</td>
|
||||
<td rowspan="2">Prév.<br>
|
||||
<span class='glyphicon glyphicon-question-sign' title="Prévision de ventes" data-placement="bottom"></span>
|
||||
</td>
|
||||
<td colspan="{{ scale|length }}">
|
||||
Ventes
|
||||
<i class='glyphicon glyphicon-question-sign' title="Ventes des 5 dernières semaines" data-placement="bottom"></i>
|
||||
</td>
|
||||
<td rowspan="2">
|
||||
V. moy.
|
||||
<br>
|
||||
<i class='glyphicon glyphicon-question-sign' title="Moyenne des ventes" data-placement="bottom"></i>
|
||||
</td>
|
||||
<td rowspan="2" data-sorter="false">
|
||||
E.T.
|
||||
<br>
|
||||
<i class='glyphicon glyphicon-question-sign' title="Écart-type des ventes" data-placement="bottom"></i>
|
||||
</td>
|
||||
<td rowspan="2">
|
||||
Prév.
|
||||
<br>
|
||||
<i class='glyphicon glyphicon-question-sign' title="Prévision de ventes" data-placement="bottom"></i>
|
||||
</td>
|
||||
<td rowspan="2">Stock</td>
|
||||
<td rowspan="2">Box<br>
|
||||
<span class='glyphicon glyphicon-question-sign' title="Capacité d'une boite" data-placement="bottom"></span>
|
||||
</td>
|
||||
<td rowspan="2">Rec.<br>
|
||||
<span class='glyphicon glyphicon-question-sign' title="Quantité conseillée" data-placement="bottom"></span>
|
||||
</td>
|
||||
<td rowspan="2">Commande</td>
|
||||
<td rowspan="2" data-sorter="false">
|
||||
Box
|
||||
<br>
|
||||
<i class='glyphicon glyphicon-question-sign' title="Capacité d'une boite" data-placement="bottom"></i>
|
||||
</td>
|
||||
<td rowspan="2">
|
||||
Rec.
|
||||
<br>
|
||||
<i class='glyphicon glyphicon-question-sign' title="Quantité conseillée" data-placement="bottom"></i>
|
||||
</td>
|
||||
<td rowspan="2" data-sorter="false" class="small-width">
|
||||
Commande
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
{% for label in scale.get_labels %}
|
||||
<td>{{ label }}</td>
|
||||
<td class="sm-padding">{{ label }}</td>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for form in formset %}
|
||||
{% ifchanged form.category %}
|
||||
<tr class='section text-left'>
|
||||
<td colspan="{{ scale|length|add:'8' }}">{{ form.category_name }}</td>
|
||||
</tr>
|
||||
{% endifchanged %}
|
||||
<tr>
|
||||
{{ form.article }}
|
||||
<td class="text-left">{{ form.name }}</td>
|
||||
{% for v_chunk in form.v_all %}
|
||||
<td>{{ v_chunk }}</td>
|
||||
{% endfor %}
|
||||
<td>{{ form.v_moy }}</td>
|
||||
<td>{{ form.v_et }}</td>
|
||||
<td>{{ form.v_prev }}</td>
|
||||
<td>{{ form.stock }}</td>
|
||||
<td>{{ form.box_capacity|default:"" }}</td>
|
||||
<td>{{ form.c_rec }}</td>
|
||||
<td class="nopadding">{{ form.quantity_ordered | add_class:"form-control" }}</td>
|
||||
{% regroup formset by category_name as category_list %}
|
||||
{% for category in category_list %}
|
||||
<tbody class="tablesorter-no-sort">
|
||||
<tr class='section text-left'>
|
||||
<td colspan="{{ scale|length|add:'8' }}">{{ category.grouper }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</tbody>
|
||||
<tbody>
|
||||
{% for form in category.list %}
|
||||
<tr>
|
||||
{{ form.article }}
|
||||
<td class="text-left">{{ form.name }}</td>
|
||||
{% for v_chunk in form.v_all %}
|
||||
<td>{{ v_chunk }}</td>
|
||||
{% endfor %}
|
||||
<td>{{ form.v_moy }}</td>
|
||||
<td>{{ form.v_et }}</td>
|
||||
<td>{{ form.v_prev }}</td>
|
||||
<td>{{ form.stock }}</td>
|
||||
<td>{{ form.box_capacity|default:"" }}</td>
|
||||
<td>{{ form.c_rec }}</td>
|
||||
<td class="nopadding">{{ form.quantity_ordered|add_class:"form-control" }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
{{ formset.management_form }}
|
||||
|
|
|
@ -42,7 +42,10 @@
|
|||
<section>
|
||||
<h2>Détails</h2>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-condensed">
|
||||
<table
|
||||
class="table table-condensed table-hover table-striped sortable"
|
||||
{# Initial sort: [(article.name,asc)] #}
|
||||
data-sortlist="[[0,0]]">
|
||||
<thead>
|
||||
<tr>
|
||||
<td>Article</td>
|
||||
|
@ -51,32 +54,35 @@
|
|||
<td>Reçu</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for orderart in orderarts %}
|
||||
{% ifchanged orderart.article.category %}
|
||||
<tr class="section">
|
||||
<td colspan="4">{{ orderart.article.category.name }}</td>
|
||||
</tr>
|
||||
{% endifchanged %}
|
||||
<tr>
|
||||
<td>
|
||||
<a href="{% url "kfet.article.read" orderart.article.id %}">
|
||||
{{ orderart.article.name }}
|
||||
</a>
|
||||
</td>
|
||||
<td>{{ orderart.quantity_ordered }}</td>
|
||||
<td>
|
||||
{% if orderart.article.box_capacity %}
|
||||
{# c'est une division ! #}
|
||||
{% widthratio orderart.quantity_ordered orderart.article.box_capacity 1 %}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{{ orderart.quantity_received|default_if_none:'' }}
|
||||
</td>
|
||||
{% regroup orderarts by article.category as category_list %}
|
||||
{% for category in category_list %}
|
||||
<tbody class="tablesorter-no-sort">
|
||||
<tr class="section">
|
||||
<td colspan="4">{{ category.grouper.name }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</tbody>
|
||||
<tbody>
|
||||
{% for orderart in category.list %}
|
||||
<tr>
|
||||
<td>
|
||||
<a href="{% url "kfet.article.read" orderart.article.id %}">
|
||||
{{ orderart.article.name }}
|
||||
</a>
|
||||
</td>
|
||||
<td>{{ orderart.quantity_ordered }}</td>
|
||||
<td>
|
||||
{% if orderart.article.box_capacity %}
|
||||
{# c'est une division ! #}
|
||||
{% widthratio orderart.quantity_ordered orderart.article.box_capacity 1 %}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{{ orderart.quantity_received|default_if_none:'' }}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
|
48
kfet/tests/test_forms.py
Normal file
48
kfet/tests/test_forms.py
Normal file
|
@ -0,0 +1,48 @@
|
|||
import datetime
|
||||
from unittest import mock
|
||||
|
||||
from django.test import TestCase
|
||||
from django.utils import timezone
|
||||
|
||||
from kfet.forms import KPsulCheckoutForm
|
||||
from kfet.models import Checkout
|
||||
|
||||
from .utils import create_user
|
||||
|
||||
|
||||
class KPsulCheckoutFormTests(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.now = timezone.now()
|
||||
|
||||
user = create_user()
|
||||
|
||||
self.c1 = Checkout.objects.create(
|
||||
name='C1', balance=10,
|
||||
created_by=user.profile.account_kfet,
|
||||
valid_from=self.now,
|
||||
valid_to=self.now + datetime.timedelta(days=1),
|
||||
)
|
||||
|
||||
self.form = KPsulCheckoutForm()
|
||||
|
||||
def test_checkout(self):
|
||||
checkout_f = self.form.fields['checkout']
|
||||
self.assertListEqual(list(checkout_f.choices), [
|
||||
('', '---------'),
|
||||
(self.c1.pk, 'C1'),
|
||||
])
|
||||
|
||||
@mock.patch('django.utils.timezone.now')
|
||||
def test_checkout_valid(self, mock_now):
|
||||
"""
|
||||
Checkout are filtered using the current datetime.
|
||||
Regression test for #184.
|
||||
"""
|
||||
self.now += datetime.timedelta(days=2)
|
||||
mock_now.return_value = self.now
|
||||
|
||||
form = KPsulCheckoutForm()
|
||||
|
||||
checkout_f = form.fields['checkout']
|
||||
self.assertListEqual(list(checkout_f.choices), [('', '---------')])
|
|
@ -1,7 +1,12 @@
|
|||
import datetime
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.test import TestCase
|
||||
from django.utils import timezone
|
||||
|
||||
from kfet.models import Account
|
||||
from kfet.models import Account, Checkout
|
||||
|
||||
from .utils import create_user
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
|
@ -23,3 +28,33 @@ class AccountTests(TestCase):
|
|||
|
||||
with self.assertRaises(Account.DoesNotExist):
|
||||
Account.objects.get_by_password('bernard')
|
||||
|
||||
|
||||
class CheckoutTests(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.now = timezone.now()
|
||||
|
||||
self.u = create_user()
|
||||
self.u_acc = self.u.profile.account_kfet
|
||||
|
||||
self.c = Checkout(
|
||||
created_by=self.u_acc,
|
||||
valid_from=self.now,
|
||||
valid_to=self.now + datetime.timedelta(days=1),
|
||||
)
|
||||
|
||||
def test_initial_statement(self):
|
||||
"""A statement is added with initial balance on creation."""
|
||||
self.c.balance = 10
|
||||
self.c.save()
|
||||
|
||||
st = self.c.statements.get()
|
||||
self.assertEqual(st.balance_new, 10)
|
||||
self.assertEqual(st.amount_taken, 0)
|
||||
self.assertEqual(st.amount_error, 0)
|
||||
|
||||
# Saving again doesn't create a new statement.
|
||||
self.c.save()
|
||||
|
||||
self.assertEqual(self.c.statements.count(), 1)
|
||||
|
|
|
@ -746,12 +746,16 @@ class CheckoutReadViewTests(ViewTestCaseMixin, TestCase):
|
|||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.checkout = Checkout.objects.create(
|
||||
name='Checkout',
|
||||
created_by=self.accounts['team'],
|
||||
valid_from=self.now,
|
||||
valid_to=self.now + timedelta(days=5),
|
||||
)
|
||||
|
||||
with mock.patch('django.utils.timezone.now') as mock_now:
|
||||
mock_now.return_value = self.now
|
||||
|
||||
self.checkout = Checkout.objects.create(
|
||||
name='Checkout', balance=Decimal('10'),
|
||||
created_by=self.accounts['team'],
|
||||
valid_from=self.now,
|
||||
valid_to=self.now + timedelta(days=1),
|
||||
)
|
||||
|
||||
def test_ok(self):
|
||||
r = self.client.get(self.url)
|
||||
|
@ -794,7 +798,7 @@ class CheckoutUpdateViewTests(ViewTestCaseMixin, TestCase):
|
|||
name='Checkout',
|
||||
valid_from=self.now,
|
||||
valid_to=self.now + timedelta(days=5),
|
||||
balance='3.14',
|
||||
balance=Decimal('3.14'),
|
||||
is_protected=False,
|
||||
created_by=self.accounts['team'],
|
||||
)
|
||||
|
@ -864,6 +868,7 @@ class CheckoutStatementListViewTests(ViewTestCaseMixin, TestCase):
|
|||
self.assertQuerysetEqual(
|
||||
r.context['checkoutstatements'],
|
||||
map(repr, expected_statements),
|
||||
ordered=False,
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -245,13 +245,7 @@ class ViewTestCaseMixin(TestCaseMixin):
|
|||
self.register_user(label, user)
|
||||
|
||||
if self.auth_user:
|
||||
# The wrapper is a sanity check.
|
||||
self.assertTrue(
|
||||
self.client.login(
|
||||
username=self.auth_user,
|
||||
password=self.auth_user,
|
||||
)
|
||||
)
|
||||
self.client.force_login(self.users[self.auth_user])
|
||||
|
||||
def tearDown(self):
|
||||
del self.users_base
|
||||
|
|
|
@ -526,15 +526,7 @@ class CheckoutCreate(SuccessMessageMixin, CreateView):
|
|||
|
||||
# Creating
|
||||
form.instance.created_by = self.request.user.profile.account_kfet
|
||||
checkout = form.save()
|
||||
|
||||
# Création d'un relevé avec balance initiale
|
||||
CheckoutStatement.objects.create(
|
||||
checkout = checkout,
|
||||
by = self.request.user.profile.account_kfet,
|
||||
balance_old = checkout.balance,
|
||||
balance_new = checkout.balance,
|
||||
amount_taken = 0)
|
||||
form.save()
|
||||
|
||||
return super(CheckoutCreate, self).form_valid(form)
|
||||
|
||||
|
@ -712,6 +704,14 @@ class ArticleList(ListView):
|
|||
)
|
||||
template_name = 'kfet/article.html'
|
||||
context_object_name = 'articles'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
articles = context[self.context_object_name]
|
||||
context['nb_articles'] = len(articles)
|
||||
context[self.context_object_name] = articles.filter(is_sold=True)
|
||||
context['not_sold_articles'] = articles.filter(is_sold=False)
|
||||
return context
|
||||
|
||||
|
||||
# Article - Create
|
||||
|
@ -1841,7 +1841,7 @@ def order_create(request, pk):
|
|||
else:
|
||||
formset = cls_formset(initial=initial)
|
||||
|
||||
scale.label_fmt = "S -{rev_i}"
|
||||
scale.label_fmt = "S-{rev_i}"
|
||||
|
||||
return render(request, 'kfet/order_create.html', {
|
||||
'supplier': supplier,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue