Commit 6f4929c2 authored by Patrick's avatar Patrick

Django 1.9.11, CMS 3.4.1

parent c36d88d9
# -*- coding: utf-8
from __future__ import unicode_literals
import sys
from decimal import *
from django.conf import settings
from django.core.exceptions import ValidationError
from django.db import models
from django.db.models.expressions import BaseExpression, Expression
from django.forms import DecimalField, NumberInput
from django.forms.utils import flatatt
from django.utils.encoding import force_text, python_2_unicode_compatible
from django.utils.html import format_html
import repanier.apps
DECIMAL_ZERO = Decimal('0')
PYTHON2 = sys.version_info[0] == 2
# class RepanierMoneyComparisonError(TypeError):
# # This exception was needed often enough to merit its own
# # Exception class.
#
# def __init__(self, other):
# assert not isinstance(other, RepanierMoney)
# self.other = other
#
# def __str__(self):
# # Note: at least w/ Python 2.x, use __str__, not __unicode__.
# return "Cannot compare instances of RepanierMoney and %s" \
# % self.other.__class__.__name__
@python_2_unicode_compatible
class RepanierMoney(object):
def __init__(self, amount=DECIMAL_ZERO, decimal_places=2):
if not isinstance(amount, Decimal):
amount = Decimal(str(amount))
self.decimal_places = decimal_places
self.rounding = Decimal('10') ** -self.decimal_places # 2 places --> '0.01'
self.amount = amount.quantize(self.rounding, ROUND_HALF_UP)
def __float__(self):
return float(self.amount)
def __repr__(self):
return "%s %d" % (self.amount, self.decimal_places)
def __pos__(self):
return RepanierMoney(amount=self.amount, decimal_places=self.decimal_places)
def __neg__(self):
return RepanierMoney(amount=-self.amount, decimal_places=self.decimal_places)
def __add__(self, other):
if isinstance(other, RepanierMoney):
return RepanierMoney(amount=self.amount + other.amount, decimal_places=self.decimal_places)
else:
return RepanierMoney(amount=self.amount + other, decimal_places=self.decimal_places)
def __sub__(self, other):
return self.__add__(-other)
def __mul__(self, other):
if isinstance(other, RepanierMoney):
return RepanierMoney(amount=self.amount * other.amount, decimal_places=self.decimal_places)
else:
return RepanierMoney(amount=self.amount * other, decimal_places=self.decimal_places)
def __truediv__(self, other):
if isinstance(other, RepanierMoney):
return RepanierMoney(amount=self.amount / other.amount, decimal_places=self.decimal_places)
else:
return RepanierMoney(amount=self.amount / other, decimal_places=self.decimal_places)
def __abs__(self):
return RepanierMoney(amount=abs(self.amount), decimal_places=self.decimal_places)
# def __bool__(self):
# return bool(self.amount)
#
# if PYTHON2:
# __nonzero__ = __bool__
def __rmod__(self, other):
"""
Calculate percentage of an amount. The left-hand side of the
operator must be a numeric value.
Example:
>>> money = RepanierMoney(200)
>>> 5 % money
USD 10.00
"""
if isinstance(other, RepanierMoney):
raise TypeError('Invalid __rmod__ operation')
else:
return RepanierMoney(amount=Decimal(self.amount * (other / 100)), decimal_places=self.decimal_places)
__radd__ = __add__
def __rsub__(self, other):
if isinstance(other, RepanierMoney):
return RepanierMoney(amount=other.amount - self.amount, decimal_places=self.decimal_places)
else:
return RepanierMoney(amount=other - self.amount, decimal_places=self.decimal_places)
__rmul__ = __mul__
__rtruediv__ = __truediv__
# _______________________________________
# Override comparison operators
def __eq__(self, other):
return (isinstance(other, RepanierMoney)
and (self.amount == other.amount)) or self.amount == other
def __ne__(self, other):
result = self.__eq__(other)
return not result
def __lt__(self, other):
return (isinstance(other, RepanierMoney)
and (self.amount < other.amount)) or self.amount < other
def __gt__(self, other):
return (isinstance(other, RepanierMoney)
and (self.amount > other.amount)) or self.amount > other
def __le__(self, other):
return self < other or self == other
def __ge__(self, other):
return self.amount > other or self == other
def __str__(self):
negative, digits, e = self.amount.as_tuple()
result = []
digits = list(map(str, digits))
build, next = result.append, digits.pop
# Suffix currency
if repanier.apps.REPANIER_SETTINGS_AFTER_AMOUNT:
build(" %s" % repanier.apps.REPANIER_SETTINGS_CURRENCY_DISPLAY)
# Decimals
for i in range(self.decimal_places):
build(next() if digits else '0')
# Decimal points
if self.decimal_places:
build(settings.DECIMAL_SEPARATOR)
# Grouped number
if not digits:
build('0')
else:
i = 0
while digits:
build(next())
i += 1
if i == settings.NUMBER_GROUPING and digits:
i = 0
build(settings.THOUSAND_SEPARATOR)
# Prefix sign
if negative:
build("- ")
# Prefix currency
if not repanier.apps.REPANIER_SETTINGS_AFTER_AMOUNT:
build("%s " % repanier.apps.REPANIER_SETTINGS_CURRENCY_DISPLAY)
return u''.join(reversed(result))
def as_tuple(self):
"""Represents the number as a triple tuple.
To show the internals exactly as they are.
"""
return self.amount.as_tuple()
class MoneyFieldProxy(object):
def __init__(self, field):
self.field = field
def _money_from_obj(self, obj):
amount = obj.__dict__[self.field.name]
if amount is None:
return None
return RepanierMoney(amount=amount, decimal_places=self.field.decimal_places)
def __get__(self, obj, type=None):
if obj is None:
raise AttributeError('Can only be accessed via an instance.')
if isinstance(obj.__dict__[self.field.name], BaseExpression):
return obj.__dict__[self.field.name]
if not isinstance(obj.__dict__[self.field.name], RepanierMoney):
obj.__dict__[self.field.name] = self._money_from_obj(obj)
return obj.__dict__[self.field.name]
def __set__(self, obj, value):
if isinstance(value, tuple):
value = RepanierMoney(amount=value[0], decimal_places=self.field.decimal_places)
if isinstance(value, RepanierMoney):
obj.__dict__[self.field.name] = value.amount
elif isinstance(value, BaseExpression):
obj.__dict__[self.field.name] = value
else:
if value:
value = str(value)
obj.__dict__[self.field.name] = self.field.to_python(value)
class ModelMoneyField(models.DecimalField):
def formfield(self, **kwargs):
defaults = {'form_class': FormMoneyField}
defaults.update(kwargs)
return super(ModelMoneyField, self).formfield(**defaults)
def to_python(self, value):
if isinstance(value, Expression):
return value
if isinstance(value, RepanierMoney):
value = value.amount
if isinstance(value, tuple):
value = value[0]
return super(ModelMoneyField, self).to_python(value)
def get_db_prep_save(self, value, connection):
if isinstance(value, Expression):
return value
if isinstance(value, RepanierMoney):
value = value.amount
return super(ModelMoneyField, self).get_db_prep_save(value, connection)
def contribute_to_class(self, cls, name):
super(ModelMoneyField, self).contribute_to_class(cls, name)
setattr(cls, self.name, MoneyFieldProxy(self))
class MoneyInput(NumberInput):
def render(self, name, value, attrs=None):
if value is None:
value = ''
final_attrs = self.build_attrs(attrs, type=self.input_type, name=name)
if value != '':
# Only add the 'value' attribute if a value is non-empty.
final_attrs['value'] = force_text(self._format_value(value))
if repanier.apps.REPANIER_SETTINGS_AFTER_AMOUNT:
return format_html('<input{} /> {}', flatatt(final_attrs), repanier.apps.REPANIER_SETTINGS_CURRENCY_DISPLAY)
else:
return format_html('{} <input{} />', repanier.apps.REPANIER_SETTINGS_CURRENCY_DISPLAY, flatatt(final_attrs))
class FormMoneyField(DecimalField):
widget = MoneyInput
def to_python(self, value):
# Important : Do not validate if self.disabled
value = (not self.disabled and super(FormMoneyField, self).to_python(value)) or DECIMAL_ZERO
return RepanierMoney(value)
def prepare_value(self, value):
try:
return value.amount
except:
return value
# def validate(self, value):
# super(DecimalField, self).validate(value)
# if value in self.empty_values or not self.required:
# return
# # Check for NaN, Inf and -Inf values. We can't compare directly for NaN,
# # since it is never equal to itself. However, NaN is the only value that
# # isn't equal to itself, so we can use this to identify NaN
# if value != value or value == Decimal("Inf") or value == Decimal("-Inf"):
# raise ValidationError(self.error_messages['invalid'], code='invalid')
# sign, digittuple, exponent = value.amount.as_tuple()
# decimals = abs(exponent)
# # digittuple doesn't include any leading zeros.
# digits = len(digittuple)
# if decimals > digits:
# # We have leading zeros up to or past the decimal point. Count
# # everything past the decimal point as a digit. We do not count
# # 0 before the decimal point as a digit since that would mean
# # we would not allow max_digits = decimal_places.
# digits = decimals
# whole_digits = digits - decimals
#
# if self.max_digits is not None and digits > self.max_digits:
# raise ValidationError(
# self.error_messages['max_digits'],
# code='max_digits',
# params={'max': self.max_digits},
# )
# if self.decimal_places is not None and decimals > self.decimal_places:
# raise ValidationError(
# self.error_messages['max_decimal_places'],
# code='max_decimal_places',
# params={'max': self.decimal_places},
# )
# if (self.max_digits is not None and self.decimal_places is not None
# and whole_digits > (self.max_digits - self.decimal_places)):
# raise ValidationError(
# self.error_messages['max_whole_digits'],
# code='max_whole_digits',
# params={'max': (self.max_digits - self.decimal_places)},
# )
# return value
# -*- coding: utf-8 -*-
from django.core.cache import cache
from django.core.management.base import BaseCommand
from menus.menu_pool import menu_pool
from repanier.const import *
......@@ -10,10 +11,11 @@ class Command(BaseCommand):
help = 'Back to send'
def handle(self, *args, **options):
permanence = Permanence.objects.filter(id=6).order_by().first()
permanence = Permanence.objects.filter(id=6).order_by('?').first()
if PERMANENCE_PLANNED == permanence.status and permanence.highest_status == PERMANENCE_SEND:
OfferItem.objects.filter(permanence_id=permanence.id).update(is_active=True)
permanence.status = PERMANENCE_SEND
permanence.save(update_fields=['status'])
menu_pool.clear()
cache.clear()
......@@ -47,7 +47,7 @@ class Command(BaseCommand):
for product in Product.objects.all():
if product.picture is not None:
self.move(record=product, to_subdir="product" + os.sep + str(product.producer.id), size="M")
for obj in OfferItem.objects.filter(product_id=product.id).order_by():
for obj in OfferItem.objects.filter(product_id=product.id).order_by('?'):
obj.picture2 = product.picture2
obj.save()
......
# -*- coding: utf-8
# non proxies
from bankaccount import BankAccount
from configuration import Configuration
from customer import Customer
from deliveryboard import DeliveryBoard
from invoice import CustomerInvoice, ProducerInvoice, CustomerProducerInvoice
from lut import LUT_ProductionMode, LUT_DeliveryPoint, LUT_DepartmentForCustomer, LUT_PermanenceRole
from offeritem import OfferItem
from permanence import Permanence
from permanenceboard import PermanenceBoard
from producer import Producer
from product import Product, Product_Translation
from purchase import Purchase
from staff import Staff
# after Producer and Product
from box import BoxContent
# proxies
from box import Box
from invoice import CustomerSend
from offeritem import OfferItemSend, OfferItemClosed
from permanence import PermanenceInPreparation, PermanenceDone
\ No newline at end of file
# -*- coding: utf-8
from __future__ import unicode_literals
from django.core.validators import MinValueValidator
from django.db import models
from django.db.models.signals import pre_save
from django.dispatch import receiver
from django.utils.translation import ugettext_lazy as _
from repanier.apps import REPANIER_SETTINGS_PERMANENCE_NAME
from repanier.const import *
from repanier.fields.RepanierMoneyField import ModelMoneyField
class BankAccount(models.Model):
permanence = models.ForeignKey(
'Permanence', verbose_name=REPANIER_SETTINGS_PERMANENCE_NAME,
on_delete=models.PROTECT, blank=True, null=True)
producer = models.ForeignKey(
'Producer', verbose_name=_("producer"),
on_delete=models.PROTECT, blank=True, null=True)
customer = models.ForeignKey(
'Customer', verbose_name=_("customer"),
on_delete=models.PROTECT, blank=True, null=True)
operation_date = models.DateField(_("operation_date"),
db_index=True)
operation_comment = models.CharField(
_("operation_comment"), max_length=100, null=True, blank=True)
operation_status = models.CharField(
max_length=3,
choices=LUT_BANK_TOTAL,
default=BANK_NOT_LATEST_TOTAL,
verbose_name=_("Bank balance status"),
db_index=True
)
bank_amount_in = ModelMoneyField(
_("bank_amount_in"), help_text=_('payment_on_the_account'),
max_digits=8, decimal_places=2, default=DECIMAL_ZERO,
validators=[MinValueValidator(0)])
bank_amount_out = ModelMoneyField(
_("bank_amount_out"), help_text=_('payment_from_the_account'),
max_digits=8, decimal_places=2, default=DECIMAL_ZERO,
validators=[MinValueValidator(0)])
producer_invoice = models.ForeignKey(
'ProducerInvoice', verbose_name=_("producer_invoice"),
blank=True, null=True, on_delete=models.PROTECT, db_index=True)
customer_invoice = models.ForeignKey(
'CustomerInvoice', verbose_name=_("customer_invoice"),
blank=True, null=True, on_delete=models.PROTECT, db_index=True)
is_updated_on = models.DateTimeField(
_("is_updated_on"), auto_now=True)
def get_bank_amount_in(self):
if self.operation_status in [BANK_PROFIT, BANK_TAX]:
return "<i>%s</i>" % (self.bank_amount_in if self.bank_amount_in.amount != DECIMAL_ZERO else EMPTY_STRING)
else:
return self.bank_amount_in if self.bank_amount_in.amount != DECIMAL_ZERO else EMPTY_STRING
get_bank_amount_in.short_description = (_("bank_amount_in"))
get_bank_amount_in.allow_tags = True
get_bank_amount_in.admin_order_field = 'bank_amount_in'
def get_bank_amount_out(self):
if self.operation_status in [BANK_PROFIT, BANK_TAX]:
return "<i>%s</i>" % (self.bank_amount_out if self.bank_amount_out.amount != DECIMAL_ZERO else EMPTY_STRING)
else:
return self.bank_amount_out if self.bank_amount_out.amount != DECIMAL_ZERO else EMPTY_STRING
get_bank_amount_out.short_description = (_("bank_amount_out"))
get_bank_amount_out.allow_tags = True
get_bank_amount_out.admin_order_field = 'bank_amount_out'
def get_producer(self):
if self.producer is not None:
return self.producer.short_profile_name
else:
if self.customer is None:
# This is a total, show it
from repanier.apps import REPANIER_SETTINGS_GROUP_NAME
if self.operation_status == BANK_LATEST_TOTAL:
return "<b>%s</b>" % "=== %s" % REPANIER_SETTINGS_GROUP_NAME
else:
return "<b>%s</b>" % "--- %s" % REPANIER_SETTINGS_GROUP_NAME
return EMPTY_STRING
get_producer.short_description = (_("producer"))
get_producer.allow_tags = True
get_producer.admin_order_field = 'producer'
def get_customer(self):
if self.customer is not None:
return self.customer.short_basket_name
else:
if self.producer is None:
# This is a total, show it
from repanier.apps import REPANIER_SETTINGS_BANK_ACCOUNT
if self.operation_status == BANK_LATEST_TOTAL:
if REPANIER_SETTINGS_BANK_ACCOUNT is not None:
return "<b>%s</b>" % REPANIER_SETTINGS_BANK_ACCOUNT
else:
return "<b>%s</b>" % "=============="
else:
if REPANIER_SETTINGS_BANK_ACCOUNT is not None:
return "<b>%s</b>" % REPANIER_SETTINGS_BANK_ACCOUNT
else:
return "<b>%s</b>" % "--------------"
return EMPTY_STRING
get_customer.short_description = (_("customer"))
get_customer.allow_tags = True
get_customer.admin_order_field = 'customer'
class Meta:
verbose_name = _("bank account movement")
verbose_name_plural = _("bank account movements")
ordering = ('-operation_date', '-id')
index_together = [
['operation_date', 'id'],
['customer_invoice', 'operation_date', 'id'],
['producer_invoice', 'operation_date', 'operation_date', 'id'],
['permanence', 'customer', 'producer', 'operation_date', 'id'],
]
@receiver(pre_save, sender=BankAccount)
def bank_account_pre_save(sender, **kwargs):
bank_account = kwargs["instance"]
if bank_account.producer is None and bank_account.customer is None:
initial_balance = BankAccount.objects.filter(
producer__isnull=True, customer__isnull=True).order_by('?').first()
if initial_balance is None:
bank_account.operation_status = BANK_LATEST_TOTAL
bank_account.permanence = None
bank_account.operation_comment = _("Initial balance")
bank_account.producer_invoice = None
bank_account.customer_invoice = None
# -*- coding: utf-8
from __future__ import unicode_literals
from django.core.validators import MinValueValidator
from django.db import models
from django.db.models.signals import pre_save
from django.dispatch import receiver
from django.utils.encoding import python_2_unicode_compatible
from django.utils.translation import ugettext_lazy as _
from repanier.const import *
from repanier.fields.RepanierMoneyField import ModelMoneyField
from repanier.models.producer import Producer
from repanier.models.product import Product, product_pre_save
@python_2_unicode_compatible
class Box(Product):
def save_update_stock(self):
# stock : max_digits=9, decimal_places=3 => 1000000 > max(stock)
stock = 1000000
for box_content in BoxContent.objects.filter(
box_id=self.id,
product__limit_order_quantity_to_stock=True,
content_quantity__gt=DECIMAL_ZERO,
product__is_box=False # Disallow recursivity
).prefetch_related(
"product"
).only(
"content_quantity", "product__stock", "product__limit_order_quantity_to_stock"
).order_by('?'):
stock = min(stock, box_content.product.stock // box_content.content_quantity)
if stock < 1000000:
self.stock = stock
else:
self.stock = DECIMAL_ZERO
self.limit_order_quantity_to_stock = True
self.save(update_fields=['stock', 'limit_order_quantity_to_stock'])
class Meta:
proxy = True
verbose_name = _("box")
verbose_name_plural = _("boxes")
# ordering = ("sort_order",)
def __str__(self):
return '%s' % self.long_name
@receiver(pre_save, sender=Box)
def box_pre_save(sender, **kwargs):
box = kwargs["instance"]
box.is_box = True
box.producer_id = Producer.objects.filter(
represent_this_buyinggroup=True
).order_by('?').only('id').first().id
box.order_unit = PRODUCT_ORDER_UNIT_PC
box.producer_unit_price = box.customer_unit_price
box.producer_vat = box.customer_vat
# ! Important to initialise all fields of the box. Remember : a box is a product.
product_pre_save(sender, **kwargs)
@python_2_unicode_compatible
class BoxContent(models.Model):
box = models.ForeignKey(
'Box', verbose_name=_("box"),
null=True, blank=True, db_index=True, on_delete=models.PROTECT)
product = models.ForeignKey(
'Product', verbose_name=_("product"), related_name='box_content',
null=True, blank=True, db_index=True, on_delete=models.PROTECT)
content_quantity = models.DecimalField(
_("content quantity"),
default=DECIMAL_ZERO, max_digits=6, decimal_places=3,
validators=[MinValueValidator(0)])
calculated_customer_content_price = ModelMoneyField(
_("customer content price"),
default=DECIMAL_ZERO, max_digits=8, decimal_places=2)
calculated_content_deposit = ModelMoneyField(
_("content deposit"),
help_text=_('deposit to add to the original content price'),
default=DECIMAL_ZERO, max_digits=8, decimal_places=2)
def get_calculated_customer_content_price(self):
# workaround for a display problem with Money field in the admin list_display
return self.calculated_customer_content_price + self.calculated_content_deposit