e27559d123
En appuyant sur F9, il est possible de définir une majoration destinée à un compte en donnant le trigramme du compte destinataire et la valeur en euro de la majoration par article
579 lines
20 KiB
Python
579 lines
20 KiB
Python
from django.db import models
|
|
from django.core.urlresolvers import reverse
|
|
from django.core.exceptions import PermissionDenied, ValidationError
|
|
from django.core.validators import RegexValidator
|
|
from django.contrib.auth.models import User
|
|
from gestioncof.models import CofProfile
|
|
from django.utils.six.moves import reduce
|
|
from django.utils import timezone
|
|
from django.db import transaction
|
|
from django.core.cache import cache
|
|
from datetime import date, timedelta
|
|
import re
|
|
|
|
def choices_length(choices):
|
|
return reduce(lambda m, choice: max(m, len(choice[0])), choices, 0)
|
|
|
|
def default_promo():
|
|
now = date.today()
|
|
return now.month <= 8 and now.year-1 or now.year
|
|
|
|
class Account(models.Model):
|
|
cofprofile = models.OneToOneField(
|
|
CofProfile, on_delete = models.PROTECT,
|
|
related_name = "account_kfet")
|
|
trigramme = models.CharField(
|
|
unique = True,
|
|
max_length = 3,
|
|
validators = [RegexValidator(regex='^[^a-z]{3}$')],
|
|
db_index = True)
|
|
balance = models.DecimalField(
|
|
max_digits = 6, decimal_places = 2,
|
|
default = 0)
|
|
is_frozen = models.BooleanField(default = False)
|
|
# Optional
|
|
PROMO_CHOICES = [(r,r) for r in range(1980, date.today().year+1)]
|
|
promo = models.IntegerField(
|
|
choices = PROMO_CHOICES,
|
|
blank = True, null = True, default = default_promo())
|
|
nickname = models.CharField(
|
|
max_length = 255,
|
|
blank = True, default = "")
|
|
password = models.CharField(
|
|
max_length = 255,
|
|
unique = True,
|
|
blank = True, null = True, default = None)
|
|
|
|
def __str__(self):
|
|
return '%s (%s)' % (self.trigramme, self.name)
|
|
|
|
# Propriétés pour accéder aux attributs de user et cofprofile et user
|
|
@property
|
|
def user(self):
|
|
return self.cofprofile.user
|
|
@property
|
|
def username(self):
|
|
return self.cofprofile.user.username
|
|
@property
|
|
def first_name(self):
|
|
return self.cofprofile.user.first_name
|
|
@property
|
|
def last_name(self):
|
|
return self.cofprofile.user.last_name
|
|
@property
|
|
def email(self):
|
|
return self.cofprofile.user.email
|
|
@property
|
|
def departement(self):
|
|
return self.cofprofile.departement
|
|
@property
|
|
def is_cof(self):
|
|
return self.cofprofile.is_cof
|
|
|
|
# Propriétés supplémentaires
|
|
@property
|
|
def real_balance(self):
|
|
if (hasattr(self, 'negative')):
|
|
return self.balance + self.negative.balance_offset
|
|
return self.balance
|
|
|
|
@property
|
|
def name(self):
|
|
return self.user.get_full_name()
|
|
|
|
@property
|
|
def is_cash(self):
|
|
return self.trigramme == 'LIQ'
|
|
|
|
@staticmethod
|
|
def is_validandfree(trigramme):
|
|
data = { 'is_valid' : False, 'is_free' : False }
|
|
pattern = re.compile("^[^a-z]{3}$")
|
|
data['is_valid'] = pattern.match(trigramme) and True or False
|
|
try:
|
|
account = Account.objects.get(trigramme=trigramme)
|
|
except Account.DoesNotExist:
|
|
data['is_free'] = True
|
|
return data
|
|
|
|
def perms_to_perform_operation(self, amount, overdraft_duration_max=None, \
|
|
overdraft_amount_max=None):
|
|
if overdraft_duration_max is None:
|
|
overdraft_duration_max = Settings.OVERDRAFT_DURATION()
|
|
if overdraft_amount_max is None:
|
|
overdraft_amount_max = Settings.OVERDRAFT_AMOUNT()
|
|
perms = set()
|
|
stop_ope = False
|
|
# Checking is cash account
|
|
if self.is_cash:
|
|
# Yes, so no perms and no stop
|
|
return set(), False
|
|
# Checking is frozen account
|
|
if self.is_frozen:
|
|
perms.add('kfet.override_frozen_protection')
|
|
new_balance = self.balance + amount
|
|
if new_balance < 0 and amount < 0:
|
|
print(new_balance)
|
|
print(amount)
|
|
# Retrieving overdraft amount limit
|
|
if (hasattr(self, 'negative')
|
|
and self.negative.authz_overdraft_amount is not None):
|
|
overdraft_amount = - self.negative.authz_overdraft_amount
|
|
else:
|
|
overdraft_amount = - overdraft_amount_max
|
|
# Retrieving overdraft datetime limit
|
|
if (hasattr(self, 'negative')
|
|
and self.negative.authz_overdraft_until is not None):
|
|
overdraft_until = self.negative.authz_overdraft_until
|
|
elif hasattr(self, 'negative'):
|
|
overdraft_until = \
|
|
self.negative.start + overdraft_duration_max
|
|
else:
|
|
overdraft_until = timezone.now() + overdraft_duration_max
|
|
# Checking it doesn't break 1 rule
|
|
if new_balance < overdraft_amount or timezone.now() > overdraft_until:
|
|
stop_ope = True
|
|
perms.add('kfet.perform_negative_operations')
|
|
return perms, stop_ope
|
|
|
|
# Surcharge Méthode save() avec gestions de User et CofProfile
|
|
# Args:
|
|
# - data : datas pour User et CofProfile
|
|
# Action:
|
|
# - Enregistre User, CofProfile à partir de "data"
|
|
# - Enregistre Account
|
|
def save(self, data = {}, *args, **kwargs):
|
|
if self.pk and data:
|
|
# Account update
|
|
|
|
# Updating User with data
|
|
user = self.user
|
|
user.first_name = data.get("first_name", user.first_name)
|
|
user.last_name = data.get("last_name", user.last_name)
|
|
user.email = data.get("email", user.email)
|
|
user.save()
|
|
# Updating CofProfile with data
|
|
cof = self.cofprofile
|
|
cof.departement = data.get("departement", cof.departement)
|
|
cof.save()
|
|
elif data:
|
|
# New account
|
|
|
|
# Checking if user has already an account
|
|
username = data.get("username")
|
|
try:
|
|
user = User.objects.get(username=username)
|
|
if hasattr(user.profile, "account_kfet"):
|
|
trigramme = user.profile.account_kfet.trigramme
|
|
raise Account.UserHasAccount(trigramme)
|
|
except User.DoesNotExist:
|
|
pass
|
|
|
|
# Creating or updating User instance
|
|
(user, _) = User.objects.get_or_create(username=username)
|
|
if "first_name" in data:
|
|
user.first_name = data['first_name']
|
|
if "last_name" in data:
|
|
user.last_name = data['last_name']
|
|
if "email" in data:
|
|
user.email = data['email']
|
|
user.save()
|
|
# Creating or updating CofProfile instance
|
|
(cof, _) = CofProfile.objects.get_or_create(user=user)
|
|
if "login_clipper" in data:
|
|
cof.login_clipper = data['login_clipper']
|
|
if "departement" in data:
|
|
cof.departement = data['departement']
|
|
cof.save()
|
|
if data:
|
|
self.cofprofile = cof
|
|
super(Account, self).save(*args, **kwargs)
|
|
|
|
# Surcharge de delete
|
|
# Pas de suppression possible
|
|
# Cas à régler plus tard
|
|
def delete(self, *args, **kwargs):
|
|
pass
|
|
|
|
class UserHasAccount(Exception):
|
|
def __init__(self, trigramme):
|
|
self.trigramme = trigramme
|
|
|
|
class AccountNegative(models.Model):
|
|
account = models.OneToOneField(
|
|
Account, on_delete = models.PROTECT,
|
|
related_name = "negative")
|
|
start = models.DateTimeField(
|
|
blank = True, null = True, default = None)
|
|
balance_offset = models.DecimalField(
|
|
max_digits = 6, decimal_places = 2,
|
|
blank = True, null = True, default = None)
|
|
authz_overdraft_amount = models.DecimalField(
|
|
max_digits = 6, decimal_places = 2,
|
|
blank = True, null = True, default = None)
|
|
authz_overdraft_until = models.DateTimeField(
|
|
blank = True, null = True, default = None)
|
|
comment = models.CharField(max_length = 255, blank = True)
|
|
|
|
class Checkout(models.Model):
|
|
created_by = models.ForeignKey(
|
|
Account, on_delete = models.PROTECT,
|
|
related_name = "+")
|
|
name = models.CharField(max_length = 45)
|
|
valid_from = models.DateTimeField()
|
|
valid_to = models.DateTimeField()
|
|
balance = models.DecimalField(
|
|
max_digits = 6, decimal_places = 2,
|
|
default = 0)
|
|
is_protected = models.BooleanField(default = False)
|
|
|
|
def get_absolute_url(self):
|
|
return reverse('kfet.checkout.read', kwargs={'pk': self.pk})
|
|
|
|
class Meta:
|
|
ordering = ['-valid_to']
|
|
|
|
def __str__(self):
|
|
return self.name
|
|
|
|
class CheckoutTransfer(models.Model):
|
|
from_checkout = models.ForeignKey(
|
|
Checkout, on_delete = models.PROTECT,
|
|
related_name = "transfers_from")
|
|
to_checkout = models.ForeignKey(
|
|
Checkout, on_delete = models.PROTECT,
|
|
related_name = "transfers_to")
|
|
amount = models.DecimalField(
|
|
max_digits = 6, decimal_places = 2)
|
|
|
|
class CheckoutStatement(models.Model):
|
|
by = models.ForeignKey(
|
|
Account, on_delete = models.PROTECT,
|
|
related_name = "+")
|
|
checkout = models.ForeignKey(
|
|
Checkout, on_delete = models.PROTECT,
|
|
related_name = "statements")
|
|
balance_old = models.DecimalField(max_digits = 6, decimal_places = 2)
|
|
balance_new = models.DecimalField(max_digits = 6, decimal_places = 2)
|
|
amount_taken = models.DecimalField(max_digits = 6, decimal_places = 2)
|
|
amount_error = models.DecimalField(max_digits = 6, decimal_places = 2)
|
|
at = models.DateTimeField(auto_now_add = True)
|
|
|
|
def __str__(self):
|
|
return '%s %s' % (self.checkout, self.at)
|
|
|
|
def save(self, *args, **kwargs):
|
|
if not self.pk:
|
|
checkout_id = self.checkout_id
|
|
self.balance_old = (Checkout.objects
|
|
.values_list('balance', flat=True).get(pk=checkout_id))
|
|
self.amount_error = (
|
|
self.balance_new + self.amount_taken - self.balance_old)
|
|
with transaction.atomic():
|
|
Checkout.objects.filter(pk=checkout_id).update(balance=self.balance_new)
|
|
super(CheckoutStatement, self).save(*args, **kwargs)
|
|
else:
|
|
super(CheckoutStatement, self).save(*args, **kwargs)
|
|
|
|
class ArticleCategory(models.Model):
|
|
name = models.CharField(max_length = 45)
|
|
|
|
def __str__(self):
|
|
return self.name
|
|
|
|
class Article(models.Model):
|
|
name = models.CharField(max_length = 45)
|
|
is_sold = models.BooleanField(default = True)
|
|
price = models.DecimalField(
|
|
max_digits = 6, decimal_places = 2,
|
|
default = 0)
|
|
stock = models.IntegerField(default = 0)
|
|
category = models.ForeignKey(
|
|
ArticleCategory, on_delete = models.PROTECT,
|
|
related_name = "articles")
|
|
|
|
def __str__(self):
|
|
return '%s - %s' % (self.category.name, self.name)
|
|
|
|
def get_absolute_url(self):
|
|
return reverse('kfet.article.read', kwargs={'pk': self.pk})
|
|
|
|
class ArticleRule(models.Model):
|
|
article_on = models.OneToOneField(
|
|
Article, on_delete = models.PROTECT,
|
|
related_name = "rule_on")
|
|
article_to = models.OneToOneField(
|
|
Article, on_delete = models.PROTECT,
|
|
related_name = "rule_to")
|
|
ratio = models.PositiveSmallIntegerField()
|
|
|
|
class Inventory(models.Model):
|
|
articles = models.ManyToManyField(
|
|
Article,
|
|
through = 'InventoryArticle',
|
|
related_name = "inventories")
|
|
by = models.ForeignKey(
|
|
Account, on_delete = models.PROTECT,
|
|
related_name = "+")
|
|
at = models.DateTimeField(auto_now_add = True)
|
|
# Optional
|
|
order = models.OneToOneField(
|
|
'Order', on_delete = models.PROTECT,
|
|
related_name = "inventory",
|
|
blank = True, null = True, default = None)
|
|
|
|
class InventoryArticle(models.Model):
|
|
inventory = models.ForeignKey(
|
|
Inventory, on_delete = models.PROTECT)
|
|
article = models.ForeignKey(
|
|
Article, on_delete = models.PROTECT)
|
|
stock_old = models.IntegerField()
|
|
stock_new = models.IntegerField()
|
|
stock_error = models.IntegerField(default = 0)
|
|
|
|
class Supplier(models.Model):
|
|
articles = models.ManyToManyField(
|
|
Article,
|
|
through = 'SupplierArticle',
|
|
related_name = "suppliers")
|
|
name = models.CharField(max_length = 45)
|
|
address = models.TextField()
|
|
email = models.EmailField()
|
|
phone = models.CharField(max_length = 10)
|
|
comment = models.TextField()
|
|
|
|
class SupplierArticle(models.Model):
|
|
supplier = models.ForeignKey(
|
|
Supplier, on_delete = models.PROTECT)
|
|
article = models.ForeignKey(
|
|
Article, on_delete = models.PROTECT)
|
|
BOX_TYPE_CHOICES = (
|
|
("caisse", "Caisse"),
|
|
("carton", "Carton"),
|
|
("palette", "Palette"),
|
|
("fût", "Fût"),
|
|
)
|
|
box_type = models.CharField(
|
|
choices = BOX_TYPE_CHOICES,
|
|
max_length = choices_length(BOX_TYPE_CHOICES))
|
|
box_capacity = models.PositiveSmallIntegerField()
|
|
price_HT = models.DecimalField(max_digits = 7, decimal_places = 4)
|
|
TVA = models.DecimalField(max_digits = 4, decimal_places = 2)
|
|
rights = models.DecimalField(max_digits = 7, decimal_places = 4)
|
|
|
|
class Order(models.Model):
|
|
supplier = models.ForeignKey(
|
|
Supplier, on_delete = models.PROTECT,
|
|
related_name = "orders")
|
|
articles = models.ManyToManyField(
|
|
Article,
|
|
through = "OrderArticle",
|
|
related_name = "orders")
|
|
at = models.DateTimeField(auto_now_add = True)
|
|
amount = models.DecimalField(max_digits = 6, decimal_places = 2)
|
|
|
|
class OrderArticle(models.Model):
|
|
order = models.ForeignKey(
|
|
Order, on_delete = models.PROTECT)
|
|
article = models.ForeignKey(
|
|
Article, on_delete = models.PROTECT)
|
|
quantity_ordered = models.IntegerField()
|
|
quantity_received = models.IntegerField()
|
|
|
|
class TransferGroup(models.Model):
|
|
at = models.DateTimeField(auto_now_add = True)
|
|
# Optional
|
|
comment = models.CharField(
|
|
max_length = 255,
|
|
blank = True, default = "")
|
|
valid_by = models.ForeignKey(
|
|
Account, on_delete = models.PROTECT,
|
|
related_name = "+",
|
|
blank = True, null = True, default = None)
|
|
|
|
class Transfer(models.Model):
|
|
group = models.ForeignKey(
|
|
TransferGroup, on_delete = models.PROTECT,
|
|
related_name = "transfers")
|
|
from_acc = models.ForeignKey(
|
|
Account, on_delete = models.PROTECT,
|
|
related_name = "transfers_from")
|
|
to_acc = models.ForeignKey(
|
|
Account, on_delete = models.PROTECT,
|
|
related_name = "transfers_to")
|
|
amount = models.DecimalField(max_digits = 6, decimal_places = 2)
|
|
# Optional
|
|
canceled_by = models.ForeignKey(
|
|
Account, on_delete = models.PROTECT,
|
|
null = True, blank = True, default = None,
|
|
related_name = "+")
|
|
canceled_at = models.DateTimeField(
|
|
null = True, blank = True, default = None)
|
|
|
|
class OperationGroup(models.Model):
|
|
on_acc = models.ForeignKey(
|
|
Account, on_delete = models.PROTECT,
|
|
related_name = "opesgroup")
|
|
checkout = models.ForeignKey(
|
|
Checkout, on_delete = models.PROTECT,
|
|
related_name = "opesgroup")
|
|
at = models.DateTimeField(auto_now_add = True)
|
|
amount = models.DecimalField(
|
|
max_digits = 6, decimal_places = 2,
|
|
default = 0)
|
|
is_cof = models.BooleanField(default = False)
|
|
# Optional
|
|
comment = models.CharField(
|
|
max_length = 255,
|
|
blank = True, default = "")
|
|
valid_by = models.ForeignKey(
|
|
Account, on_delete = models.PROTECT,
|
|
related_name = "+",
|
|
blank = True, null = True, default = None)
|
|
|
|
class Operation(models.Model):
|
|
PURCHASE = 'purchase'
|
|
DEPOSIT = 'deposit'
|
|
WITHDRAW = 'withdraw'
|
|
|
|
TYPE_ORDER_CHOICES = (
|
|
(PURCHASE, 'Achat'),
|
|
(DEPOSIT, 'Charge'),
|
|
(WITHDRAW, 'Retrait'),
|
|
)
|
|
|
|
group = models.ForeignKey(
|
|
OperationGroup, on_delete = models.PROTECT,
|
|
related_name = "opes")
|
|
type = models.CharField(
|
|
choices = TYPE_ORDER_CHOICES,
|
|
max_length = choices_length(TYPE_ORDER_CHOICES))
|
|
amount = models.DecimalField(
|
|
max_digits = 6, decimal_places = 2,
|
|
blank = True, default = 0)
|
|
is_checkout = models.BooleanField(default = True)
|
|
# Optional
|
|
article = models.ForeignKey(
|
|
Article, on_delete = models.PROTECT,
|
|
related_name = "operations",
|
|
blank = True, null = True, default = None)
|
|
article_nb = models.PositiveSmallIntegerField(
|
|
blank = True, null = True, default = None)
|
|
canceled_by = models.ForeignKey(
|
|
Account, on_delete = models.PROTECT,
|
|
related_name = "+",
|
|
blank = True, null = True, default = None)
|
|
canceled_at = models.DateTimeField(
|
|
blank = True, null = True, default = None)
|
|
addcost_for = models.ForeignKey(
|
|
Account, on_delete = models.PROTECT,
|
|
related_name = "addcosts",
|
|
blank = True, null = True, default = None)
|
|
addcost_amount = models.DecimalField(
|
|
max_digits = 6, decimal_places = 2,
|
|
blank = True, null = True, default = None)
|
|
|
|
class GlobalPermissions(models.Model):
|
|
class Meta:
|
|
managed = False
|
|
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'),
|
|
)
|
|
|
|
class Settings(models.Model):
|
|
name = models.CharField(
|
|
max_length = 45,
|
|
unique = True,
|
|
db_index = True)
|
|
value_decimal = models.DecimalField(
|
|
max_digits = 6, decimal_places = 2,
|
|
blank = True, null = True, default = None)
|
|
value_account = models.ForeignKey(
|
|
Account, on_delete = models.PROTECT,
|
|
blank = True, null = True, default = None)
|
|
value_duration = models.DurationField(
|
|
blank = True, null = True, default = None)
|
|
|
|
@staticmethod
|
|
def setting_inst(name):
|
|
return Settings.objects.get(name=name)
|
|
|
|
@staticmethod
|
|
def SUBVENTION_COF():
|
|
subvention_cof = cache.get('SUBVENTION_COF')
|
|
if subvention_cof:
|
|
return subvention_cof
|
|
try:
|
|
subvention_cof = Settings.setting_inst("SUBVENTION_COF").value_decimal
|
|
except Settings.DoesNotExist:
|
|
subvention_cof = 0
|
|
cache.set('SUBVENTION_COF', subvention_cof)
|
|
return subvention_cof
|
|
|
|
@staticmethod
|
|
def ADDCOST_AMOUNT():
|
|
try:
|
|
return Settings.setting_inst("ADDCOST_AMOUNT").value_decimal
|
|
except Settings.DoesNotExist:
|
|
return 0
|
|
|
|
@staticmethod
|
|
def ADDCOST_FOR():
|
|
try:
|
|
return Settings.setting_inst("ADDCOST_FOR").value_account
|
|
except Settings.DoesNotExist:
|
|
return None;
|
|
|
|
@staticmethod
|
|
def OVERDRAFT_DURATION():
|
|
try:
|
|
return Settings.setting_inst("OVERDRAFT_DURATION").value_duration
|
|
except Settings.DoesNotExist:
|
|
return timedelta()
|
|
|
|
@staticmethod
|
|
def OVERDRAFT_AMOUNT():
|
|
try:
|
|
return Settings.setting_inst("OVERDRAFT_AMOUNT").value_decimal
|
|
except Settings.DoesNotExist:
|
|
return 0
|
|
|
|
def CANCEL_DURATION():
|
|
try:
|
|
return Settings.setting_inst("CANCEL_DURATION").value_duration
|
|
except Settings.DoesNotExist:
|
|
return timedelta()
|
|
|
|
@staticmethod
|
|
def create_missing():
|
|
s, created = Settings.objects.get_or_create(name='SUBVENTION_COF')
|
|
if created:
|
|
s.value_decimal = 25
|
|
s.save()
|
|
s, created = Settings.objects.get_or_create(name='ADDCOST_AMOUNT')
|
|
if created:
|
|
s.value_decimal = 0.5
|
|
s.save()
|
|
s, created = Settings.objects.get_or_create(name='ADDCOST_FOR')
|
|
s, created = Settings.objects.get_or_create(name='OVERDRAFT_DURATION')
|
|
if created:
|
|
s.value_duration = timedelta(days=1) # 24h
|
|
s.save()
|
|
s, created = Settings.objects.get_or_create(name='OVERDRAFT_AMOUNT')
|
|
if created:
|
|
s.value_decimal = 20
|
|
s.save()
|
|
s, created = Settings.objects.get_or_create(name='CANCEL_DURATION')
|
|
if created:
|
|
s.value_duration = timedelta(minutes=5) # 5min
|
|
s.save()
|
|
|
|
class GenericTeamToken(models.Model):
|
|
token = models.CharField(max_length = 50, unique = True)
|