Sith/counter/models.py

524 lines
21 KiB
Python
Raw Normal View History

2016-08-01 22:32:55 +00:00
from django.db import models, DataError
2016-03-28 12:54:35 +00:00
from django.utils.translation import ugettext_lazy as _
from django.utils import timezone
2016-03-28 12:54:35 +00:00
from django.conf import settings
2016-03-29 08:30:24 +00:00
from django.core.urlresolvers import reverse
2016-08-01 22:32:55 +00:00
from django.forms import ValidationError
from django.contrib.sites.shortcuts import get_current_site
2016-03-28 12:54:35 +00:00
2016-08-18 19:06:10 +00:00
from datetime import timedelta
2016-08-04 22:50:25 +00:00
import random
import string
2016-10-03 17:30:05 +00:00
import os
import base64
2016-10-16 16:52:04 +00:00
import datetime
2016-03-28 12:54:35 +00:00
from club.models import Club
from accounting.models import CurrencyField
2016-12-08 18:47:28 +00:00
from core.models import Group, User, Notification
2016-12-10 00:58:30 +00:00
from subscription.models import Subscription
2016-03-28 12:54:35 +00:00
class Customer(models.Model):
"""
This class extends a user to make a customer. It adds some basic customers informations, such as the accound ID, and
is used by other accounting classes as reference to the customer, rather than using User
"""
user = models.OneToOneField(User, primary_key=True)
account_id = models.CharField(_('account id'), max_length=10, unique=True)
2016-05-31 17:32:15 +00:00
amount = CurrencyField(_('amount'))
class Meta:
verbose_name = _('customer')
verbose_name_plural = _('customers')
2016-08-04 22:50:25 +00:00
ordering = ['account_id',]
def __str__(self):
2016-08-14 17:28:14 +00:00
return "%s - %s" % (self.user.username, self.account_id)
2016-08-04 22:50:25 +00:00
def generate_account_id(number):
number = str(number)
letter = random.choice(string.ascii_lowercase)
while Customer.objects.filter(account_id=number+letter).exists():
letter = random.choice(string.ascii_lowercase)
return number+letter
2016-08-01 22:32:55 +00:00
def save(self, *args, **kwargs):
if self.amount < 0:
raise ValidationError(_("Not enough money"))
super(Customer, self).save(*args, **kwargs)
2016-09-21 12:09:16 +00:00
def recompute_amount(self):
self.amount = 0
for r in self.refillings.all():
self.amount += r.amount
for s in self.buyings.filter(payment_method="SITH_ACCOUNT"):
self.amount -= s.quantity * s.unit_price
self.save()
def get_absolute_url(self):
return reverse('core:user_account', kwargs={'user_id': self.user.pk})
def get_full_url(self):
2016-10-25 16:25:59 +00:00
return ''.join(['https://', settings.SITH_URL, self.get_absolute_url()])
class ProductType(models.Model):
"""
This describes a product type
Useful only for categorizing, changes are made at the product level for now
"""
name = models.CharField(_('name'), max_length=30)
description = models.TextField(_('description'), null=True, blank=True)
icon = models.ImageField(upload_to='products', null=True, blank=True)
2016-07-27 18:05:45 +00:00
class Meta:
verbose_name = _('product type')
def is_owned_by(self, user):
"""
Method to see if that object can be edited by the given user
"""
2016-12-10 00:29:56 +00:00
if user.is_in_group(settings.SITH_GROUP_ACCOUNTING_ADMIN_ID):
return True
return False
def __str__(self):
return self.name
2016-07-27 18:05:45 +00:00
def get_absolute_url(self):
return reverse('counter:producttype_list')
class Product(models.Model):
"""
This describes a product, with all its related informations
"""
name = models.CharField(_('name'), max_length=64)
description = models.TextField(_('description'), blank=True)
product_type = models.ForeignKey(ProductType, related_name='products', verbose_name=_("product type"), null=True, blank=True,
on_delete=models.SET_NULL)
code = models.CharField(_('code'), max_length=16, blank=True)
purchase_price = CurrencyField(_('purchase price'))
selling_price = CurrencyField(_('selling price'))
special_selling_price = CurrencyField(_('special selling price'))
icon = models.ImageField(upload_to='products', null=True, blank=True, verbose_name=_("icon"))
club = models.ForeignKey(Club, related_name="products", verbose_name=_("club"))
limit_age = models.IntegerField(_('limit age'), default=0)
tray = models.BooleanField(_('tray price'), default=False)
parent_product = models.ForeignKey('self', related_name='children_products', verbose_name=_("parent product"), null=True,
blank=True, on_delete=models.SET_NULL)
2016-08-31 13:29:16 +00:00
buying_groups = models.ManyToManyField(Group, related_name='products', verbose_name=_("buying groups"), blank=True)
archived = models.BooleanField(_("archived"), default=False)
2016-07-27 18:05:45 +00:00
class Meta:
verbose_name = _('product')
def is_owned_by(self, user):
"""
Method to see if that object can be edited by the given user
"""
2016-12-10 00:29:56 +00:00
if user.is_in_group(settings.SITH_GROUP_ACCOUNTING_ADMIN_ID) or user.is_in_group(settings.SITH_GROUP_COUNTER_ADMIN_ID):
return True
return False
def __str__(self):
2016-10-04 14:40:43 +00:00
return "%s (%s)" % (self.name, self.code)
2016-07-27 15:23:02 +00:00
def get_absolute_url(self):
return reverse('counter:product_list')
2016-03-28 12:54:35 +00:00
class Counter(models.Model):
name = models.CharField(_('name'), max_length=30)
club = models.ForeignKey(Club, related_name="counters", verbose_name=_("club"))
products = models.ManyToManyField(Product, related_name="counters", verbose_name=_("products"), blank=True)
type = models.CharField(_('counter type'),
2016-03-29 08:30:24 +00:00
max_length=255,
2016-07-21 18:03:31 +00:00
choices=[('BAR',_('Bar')), ('OFFICE',_('Office')), ('EBOUTIC',_('Eboutic'))])
2016-12-10 00:58:30 +00:00
sellers = models.ManyToManyField(User, verbose_name=_('sellers'), related_name='counters', blank=True)
2016-03-28 12:54:35 +00:00
edit_groups = models.ManyToManyField(Group, related_name="editable_counters", blank=True)
view_groups = models.ManyToManyField(Group, related_name="viewable_counters", blank=True)
token = models.CharField(_('token'), max_length=30, null=True, blank=True)
2016-03-28 12:54:35 +00:00
2016-07-27 18:05:45 +00:00
class Meta:
verbose_name = _('counter')
2016-03-28 12:54:35 +00:00
def __getattribute__(self, name):
if name == "edit_groups":
return Group.objects.filter(name=self.club.unix_name+settings.SITH_BOARD_SUFFIX).all()
2016-03-28 12:54:35 +00:00
return object.__getattribute__(self, name)
def __str__(self):
return self.name
2016-03-29 08:30:24 +00:00
def get_absolute_url(self):
2016-07-22 11:34:34 +00:00
if self.type == "EBOUTIC":
return reverse('eboutic:main')
2016-03-29 08:30:24 +00:00
return reverse('counter:details', kwargs={'counter_id': self.id})
def is_owned_by(self, user):
mem = self.club.get_membership_for(user)
if mem and mem.role >= 7:
return True
2016-12-10 00:29:56 +00:00
return user.is_in_group(settings.SITH_GROUP_COUNTER_ADMIN_ID)
2016-03-29 08:30:24 +00:00
def can_be_viewed_by(self, user):
if self.type == "BAR" or self.type == "EBOUTIC":
return True
2016-12-10 00:58:30 +00:00
sub = request.user
return user.is_in_group(settings.SITH_MAIN_BOARD_GROUP) or sub in self.sellers
def gen_token(self):
"""Generate a new token for this counter"""
self.token = ''.join(random.choice(string.ascii_letters + string.digits) for x in range(30))
self.save()
def add_barman(self, user):
2016-07-18 11:22:50 +00:00
"""
Logs a barman in to the given counter
A user is stored as a tuple with its login time
"""
Permanency(user=user, counter=self, start=timezone.now(), end=None).save()
def del_barman(self, user):
2016-07-18 11:22:50 +00:00
"""
Logs a barman out and store its permanency
"""
perm = Permanency.objects.filter(counter=self, user=user, end=None).all()
for p in perm:
p.end = p.activity
p.save()
2016-07-18 11:22:50 +00:00
2016-08-06 10:37:36 +00:00
def get_barmen_list(self):
2016-07-18 11:22:50 +00:00
"""
2016-07-21 18:03:31 +00:00
Returns the barman list as list of User
2016-07-18 11:22:50 +00:00
Also handle the timeout of the barmen
"""
pl = Permanency.objects.filter(counter=self, end=None).all()
bl = []
for p in pl:
if timezone.now() - p.activity < timedelta(minutes=settings.SITH_BARMAN_TIMEOUT):
bl.append(p.user)
else:
p.end = p.activity
p.save()
return bl
2016-08-06 10:37:36 +00:00
def get_random_barman(self):
2016-09-12 15:34:33 +00:00
"""
Return a random user being currently a barman
"""
2016-08-06 10:37:36 +00:00
bl = self.get_barmen_list()
2016-08-18 19:06:10 +00:00
return bl[random.randrange(0, len(bl))]
2016-05-31 17:32:15 +00:00
2016-09-12 15:34:33 +00:00
def update_activity(self):
"""
Update the barman activity to prevent timeout
"""
for p in Permanency.objects.filter(counter=self, end=None).all():
p.save() # Update activity
2016-08-05 18:01:23 +00:00
def is_open(self):
return len(self.get_barmen_list()) > 0
2016-08-05 18:01:23 +00:00
2016-10-16 16:52:04 +00:00
def is_inactive(self):
"""
Returns True if the counter self is inactive from SITH_COUNTER_MINUTE_INACTIVE's value minutes, else False
2016-10-16 16:52:04 +00:00
"""
return self.is_open() and ((timezone.now() - self.permanencies.order_by('-activity').first().activity) > datetime.timedelta(minutes=settings.SITH_COUNTER_MINUTE_INACTIVE))
2016-10-16 16:52:04 +00:00
2016-08-06 10:37:36 +00:00
def barman_list(self):
2016-09-12 15:34:33 +00:00
"""
Returns the barman id list
"""
2016-08-06 10:37:36 +00:00
return [b.id for b in self.get_barmen_list()]
2016-05-31 17:32:15 +00:00
class Refilling(models.Model):
"""
Handle the refilling
"""
counter = models.ForeignKey(Counter, related_name="refillings", blank=False)
amount = CurrencyField(_('amount'))
operator = models.ForeignKey(User, related_name="refillings_as_operator", blank=False)
customer = models.ForeignKey(Customer, related_name="refillings", blank=False)
date = models.DateTimeField(_('date'))
2016-05-31 17:32:15 +00:00
payment_method = models.CharField(_('payment method'), max_length=255,
choices=settings.SITH_COUNTER_PAYMENT_METHOD, default='CASH')
bank = models.CharField(_('bank'), max_length=255,
choices=settings.SITH_COUNTER_BANK, default='OTHER')
2016-08-01 22:32:55 +00:00
is_validated = models.BooleanField(_('is validated'), default=False)
2016-05-31 17:32:15 +00:00
2016-07-27 18:05:45 +00:00
class Meta:
verbose_name = _("refilling")
2016-05-31 17:32:15 +00:00
def __str__(self):
2016-06-26 18:07:29 +00:00
return "Refilling: %.2f for %s" % (self.amount, self.customer.user.get_display_name())
2016-05-31 17:32:15 +00:00
2016-08-18 19:06:10 +00:00
def is_owned_by(self, user):
return user.is_owner(self.counter) and self.payment_method != "CARD"
2016-05-31 17:32:15 +00:00
2016-08-18 19:06:10 +00:00
def delete(self, *args, **kwargs):
self.customer.amount -= self.amount
self.customer.save()
super(Refilling, self).delete(*args, **kwargs)
2016-05-31 17:32:15 +00:00
def save(self, *args, **kwargs):
if not self.date:
2016-08-18 19:06:10 +00:00
self.date = timezone.now()
2016-05-31 17:32:15 +00:00
self.full_clean()
2016-08-01 22:32:55 +00:00
if not self.is_validated:
self.customer.amount += self.amount
self.customer.save()
self.is_validated = True
2016-12-09 23:06:17 +00:00
Notification(user=self.customer.user, url=reverse('core:user_account_detail',
2016-12-08 18:47:28 +00:00
kwargs={'user_id': self.customer.user.id, 'year': self.date.year, 'month': self.date.month}),
2016-12-09 23:06:17 +00:00
param=str(self.amount),
type="REFILLING",
2016-12-08 18:47:28 +00:00
).save()
2016-06-26 18:07:29 +00:00
super(Refilling, self).save(*args, **kwargs)
2016-05-31 17:32:15 +00:00
class Selling(models.Model):
"""
Handle the sellings
"""
label = models.CharField(_("label"), max_length=64)
product = models.ForeignKey(Product, related_name="sellings", null=True, blank=True, on_delete=models.SET_NULL)
counter = models.ForeignKey(Counter, related_name="sellings", null=True, blank=False, on_delete=models.SET_NULL)
club = models.ForeignKey(Club, related_name="sellings", null=True, blank=False, on_delete=models.SET_NULL)
2016-05-31 17:32:15 +00:00
unit_price = CurrencyField(_('unit price'))
quantity = models.IntegerField(_('quantity'))
seller = models.ForeignKey(User, related_name="sellings_as_operator", null=True, blank=False, on_delete=models.SET_NULL)
customer = models.ForeignKey(Customer, related_name="buyings", null=True, blank=False, on_delete=models.SET_NULL)
date = models.DateTimeField(_('date'))
payment_method = models.CharField(_('payment method'), max_length=255,
choices=[('SITH_ACCOUNT', _('Sith account')), ('CARD', _('Credit card'))], default='SITH_ACCOUNT')
2016-08-01 22:32:55 +00:00
is_validated = models.BooleanField(_('is validated'), default=False)
2016-05-31 17:32:15 +00:00
2016-07-27 18:05:45 +00:00
class Meta:
verbose_name = _("selling")
2016-05-31 17:32:15 +00:00
def __str__(self):
2016-08-01 22:32:55 +00:00
return "Selling: %d x %s (%f) for %s" % (self.quantity, self.label,
2016-05-31 17:32:15 +00:00
self.quantity*self.unit_price, self.customer.user.get_display_name())
2016-08-18 19:06:10 +00:00
def is_owned_by(self, user):
return user.is_owner(self.counter) and self.payment_method != "CARD"
2016-08-18 19:06:10 +00:00
2016-10-03 17:30:05 +00:00
def can_be_viewed_by(self, user):
return user == self.customer.user
2016-08-18 19:06:10 +00:00
def delete(self, *args, **kwargs):
if self.payment_method == "SITH_ACCOUNT":
self.customer.amount += self.quantity * self.unit_price
self.customer.save()
2016-08-18 19:06:10 +00:00
super(Selling, self).delete(*args, **kwargs)
def send_mail_customer(self):
2016-10-25 19:53:40 +00:00
event = self.product.eticket.event_title or _("Unknown event")
2016-10-25 16:25:59 +00:00
subject = _('Eticket bought for the event %(event)s') % {'event': event}
message_html = _(
"You bought an eticket for the event %(event)s.\nYou can download it on this page %(url)s."
) % {
2016-10-25 16:25:59 +00:00
'event': event,
'url':''.join((
'<a href="',
self.customer.get_full_url(),
'">',
self.customer.get_full_url(),
'</a>'
))
}
message_txt = _(
"You bought an eticket for the event %(event)s.\nYou can download it on this page %(url)s."
) % {
2016-10-25 16:25:59 +00:00
'event': event,
'url': self.customer.get_full_url(),
}
self.customer.user.email_user(
subject,
message_txt,
html_message=message_html
)
2016-05-31 17:32:15 +00:00
def save(self, *args, **kwargs):
if not self.date:
2016-08-18 19:06:10 +00:00
self.date = timezone.now()
2016-05-31 17:32:15 +00:00
self.full_clean()
2016-08-01 22:32:55 +00:00
if not self.is_validated:
self.customer.amount -= self.quantity * self.unit_price
self.customer.save()
self.is_validated = True
2016-09-08 00:09:17 +00:00
if self.product and self.product.id == settings.SITH_PRODUCT_SUBSCRIPTION_ONE_SEMESTER:
2016-12-10 00:58:30 +00:00
s = User.objects.filter(id=self.customer.user.id).first()
2016-09-04 16:34:34 +00:00
sub = Subscription(
member=s,
subscription_type='un-semestre',
payment_method="EBOUTIC",
location="EBOUTIC",
)
sub.subscription_start = Subscription.compute_start()
sub.subscription_start = Subscription.compute_start(
duration=settings.SITH_SUBSCRIPTIONS[sub.subscription_type]['duration'])
sub.subscription_end = Subscription.compute_end(
duration=settings.SITH_SUBSCRIPTIONS[sub.subscription_type]['duration'],
start=sub.subscription_start)
sub.save()
2016-09-08 00:09:17 +00:00
elif self.product and self.product.id == settings.SITH_PRODUCT_SUBSCRIPTION_TWO_SEMESTERS:
2016-12-10 00:58:30 +00:00
s = User.objects.filter(id=self.customer.user.id).first()
2016-09-04 16:34:34 +00:00
sub = Subscription(
member=s,
subscription_type='deux-semestres',
payment_method="EBOUTIC",
location="EBOUTIC",
)
sub.subscription_start = Subscription.compute_start()
sub.subscription_start = Subscription.compute_start(
duration=settings.SITH_SUBSCRIPTIONS[sub.subscription_type]['duration'])
sub.subscription_end = Subscription.compute_end(
duration=settings.SITH_SUBSCRIPTIONS[sub.subscription_type]['duration'],
start=sub.subscription_start)
sub.save()
try:
if self.product.eticket:
self.send_mail_customer()
2016-10-25 16:25:59 +00:00
except: pass
2016-12-08 18:47:28 +00:00
Notification(
user=self.customer.user,
url=reverse('core:user_account_detail',
kwargs={'user_id': self.customer.user.id, 'year': self.date.year, 'month': self.date.month}),
2016-12-09 23:06:17 +00:00
param="%d x %s" % (self.quantity, self.label),
type="SELLING",
2016-12-08 18:47:28 +00:00
).save()
2016-05-31 17:32:15 +00:00
super(Selling, self).save(*args, **kwargs)
2016-07-18 11:22:50 +00:00
class Permanency(models.Model):
"""
This class aims at storing a traceability of who was barman where and when
"""
user = models.ForeignKey(User, related_name="permanencies", verbose_name=_("user"))
counter = models.ForeignKey(Counter, related_name="permanencies", verbose_name=_("counter"))
2016-07-18 11:22:50 +00:00
start = models.DateTimeField(_('start date'))
end = models.DateTimeField(_('end date'), null=True)
activity = models.DateTimeField(_('last activity date'), auto_now=True)
2016-07-18 11:22:50 +00:00
2016-07-27 18:05:45 +00:00
class Meta:
verbose_name = _("permanency")
2016-07-18 11:22:50 +00:00
def __str__(self):
2016-09-12 15:34:33 +00:00
return "%s in %s from %s (last activity: %s) to %s" % (self.user, self.counter,
self.start.strftime("%Y-%m-%d %H:%M:%S"),
self.activity.strftime("%Y-%m-%d %H:%M:%S"),
self.end.strftime("%Y-%m-%d %H:%M:%S") if self.end else "",
)
2016-07-18 11:22:50 +00:00
2016-08-26 18:57:04 +00:00
class CashRegisterSummary(models.Model):
user = models.ForeignKey(User, related_name="cash_summaries", verbose_name=_("user"))
counter = models.ForeignKey(Counter, related_name="cash_summaries", verbose_name=_("counter"))
date = models.DateTimeField(_('date'))
comment = models.TextField(_('comment'), null=True, blank=True)
emptied = models.BooleanField(_('emptied'), default=False)
class Meta:
verbose_name = _("cash register summary")
def __str__(self):
return "At %s by %s - Total: %s" % (self.counter, self.user, self.get_total())
def __getattribute__(self, name):
if name[:5] == 'check':
checks = self.items.filter(check=True).order_by('value').all()
if name == 'ten_cents':
return self.items.filter(value=0.1, check=False).first()
elif name == 'twenty_cents':
return self.items.filter(value=0.2, check=False).first()
elif name == 'fifty_cents':
return self.items.filter(value=0.5, check=False).first()
elif name == 'one_euro':
return self.items.filter(value=1, check=False).first()
elif name == 'two_euros':
return self.items.filter(value=2, check=False).first()
elif name == 'five_euros':
return self.items.filter(value=5, check=False).first()
elif name == 'ten_euros':
return self.items.filter(value=10, check=False).first()
elif name == 'twenty_euros':
return self.items.filter(value=20, check=False).first()
elif name == 'fifty_euros':
return self.items.filter(value=50, check=False).first()
elif name == 'hundred_euros':
return self.items.filter(value=100, check=False).first()
elif name == 'check_1':
return checks[0] if 0 < len(checks) else None
elif name == 'check_2':
return checks[1] if 1 < len(checks) else None
elif name == 'check_3':
return checks[2] if 2 < len(checks) else None
elif name == 'check_4':
return checks[3] if 3 < len(checks) else None
elif name == 'check_5':
return checks[4] if 4 < len(checks) else None
else:
return object.__getattribute__(self, name)
2016-09-13 00:04:49 +00:00
def is_owned_by(self, user):
"""
Method to see if that object can be edited by the given user
"""
2016-12-10 00:29:56 +00:00
if user.is_in_group(settings.SITH_GROUP_COUNTER_ADMIN_ID):
2016-09-13 00:04:49 +00:00
return True
return False
2016-08-26 18:57:04 +00:00
def get_total(self):
t = 0
for it in self.items.all():
t += it.quantity * it.value
return t
def save(self, *args, **kwargs):
if not self.id:
self.date = timezone.now()
return super(CashRegisterSummary, self).save(*args, **kwargs)
def get_absolute_url(self):
return reverse('counter:cash_summary_list')
2016-08-26 18:57:04 +00:00
class CashRegisterSummaryItem(models.Model):
cash_summary = models.ForeignKey(CashRegisterSummary, related_name="items", verbose_name=_("cash summary"))
value = CurrencyField(_("value"))
quantity = models.IntegerField(_('quantity'), default=0)
check = models.BooleanField(_('check'), default=False)
class Meta:
verbose_name = _("cash register summary item")
2016-10-03 17:30:05 +00:00
class Eticket(models.Model):
"""
Eticket can be linked to a product an allows PDF generation
"""
product = models.OneToOneField(Product, related_name='eticket', verbose_name=_("product"))
banner = models.ImageField(upload_to='etickets', null=True, blank=True, verbose_name=_("banner"))
event_date = models.DateField(_('event date'), null=True, blank=True)
event_title = models.CharField(_('event title'), max_length=64, null=True, blank=True)
secret = models.CharField(_('secret'), max_length=64, unique=True)
def __str__(self):
return "%s" % (self.product.name)
def get_absolute_url(self):
return reverse('counter:eticket_list')
def save(self, *args, **kwargs):
if not self.id:
self.secret = base64.b64encode(os.urandom(32))
return super(Eticket, self).save(*args, **kwargs)
def is_owned_by(self, user):
"""
Method to see if that object can be edited by the given user
"""
2016-12-10 00:29:56 +00:00
return user.is_in_group(settings.SITH_GROUP_COUNTER_ADMIN_ID)
2016-10-03 17:30:05 +00:00
def get_hash(self, string):
import hashlib, hmac
return hmac.new(bytes(self.secret, 'utf-8'), bytes(string, 'utf-8'), hashlib.sha1).hexdigest()