# -*- coding: utf-8
from __future__ import unicode_literals
import datetime
import uuid
from django.conf import settings
from django.core import urlresolvers
# from django.core.validators import MinLengthValidator
from django.core.validators import MinValueValidator
from django.db import models
from django.db.models import Q
from django.db.models import Sum
from django.utils import timezone
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.formats import number_format
from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _
import bankaccount
import invoice
import offeritem
from repanier.const import *
from repanier.fields.RepanierMoneyField import ModelMoneyField, RepanierMoney
@python_2_unicode_compatible
class Producer(models.Model):
short_profile_name = models.CharField(
_("short_profile_name"), max_length=25, null=False, default=EMPTY_STRING,
db_index=True, unique=True)
long_profile_name = models.CharField(
_("long_profile_name"), max_length=100, null=True, default=EMPTY_STRING)
email = models.EmailField(
_("email"), null=True, blank=True, default=EMPTY_STRING)
email2 = models.EmailField(
_("secondary email"), null=True, blank=True, default=EMPTY_STRING)
email3 = models.EmailField(
_("secondary email"), null=True, blank=True, default=EMPTY_STRING)
language = models.CharField(
max_length=5,
choices=settings.LANGUAGES,
default=settings.LANGUAGE_CODE,
verbose_name=_("language"))
phone1 = models.CharField(
_("phone1"),
max_length=25,
null=True, blank=True, default=EMPTY_STRING)
phone2 = models.CharField(
_("phone2"), max_length=25, null=True, blank=True, default=EMPTY_STRING)
bank_account = models.CharField(_("bank account"), max_length=100, null=True, blank=True, default=EMPTY_STRING)
vat_id = models.CharField(
_("vat_id"), max_length=20, null=True, blank=True, default=EMPTY_STRING)
fax = models.CharField(
_("fax"), max_length=100, null=True, blank=True, default=EMPTY_STRING)
address = models.TextField(_("address"), null=True, blank=True, default=EMPTY_STRING)
memo = models.TextField(
_("memo"), null=True, blank=True, default=EMPTY_STRING)
reference_site = models.URLField(
_("reference site"), null=True, blank=True, default=EMPTY_STRING)
web_services_activated = models.BooleanField(_('Web services activated'), default=False)
# uuid used to access to producer invoices without login
uuid = models.CharField(
_("uuid"), max_length=36, null=True, default=EMPTY_STRING,
db_index=True
)
offer_uuid = models.CharField(
_("uuid"), max_length=36, null=True, default=EMPTY_STRING,
db_index=True
)
offer_filled = models.BooleanField(_("offer filled"), default=False)
invoice_by_basket = models.BooleanField(_("invoice by basket"), default=False)
manage_replenishment = models.BooleanField(_("manage stock"), default=False)
manage_production = models.BooleanField(_("manage production"), default=False)
producer_pre_opening = models.BooleanField(_("producer pre-opening"), default=False)
producer_price_are_wo_vat = models.BooleanField(_("producer price are wo vat"), default=False)
sort_products_by_reference = models.BooleanField(_("sort products by reference"), default=False)
price_list_multiplier = models.DecimalField(
_("price_list_multiplier"),
help_text=_("This multiplier is applied to each price automatically imported/pushed."),
default=DECIMAL_ONE, max_digits=5, decimal_places=4, blank=True,
validators=[MinValueValidator(0)])
is_resale_price_fixed = models.BooleanField(_("the resale price is set by the producer"),
default=False)
minimum_order_value = ModelMoneyField(
_("minimum order value"),
help_text=_("0 mean : no minimum order value."),
max_digits=8, decimal_places=2, default=DECIMAL_ZERO,
validators=[MinValueValidator(0)])
date_balance = models.DateField(
_("date_balance"), default=datetime.date.today)
balance = ModelMoneyField(
_("balance"), max_digits=8, decimal_places=2, default=DECIMAL_ZERO)
initial_balance = ModelMoneyField(
_("initial balance"), max_digits=8, decimal_places=2, default=DECIMAL_ZERO)
represent_this_buyinggroup = models.BooleanField(
_("represent_this_buyinggroup"), default=False)
is_active = models.BooleanField(_("is_active"), default=True)
def get_negative_balance(self):
return - self.balance
def get_products(self):
# This producer may have product's list
if self.is_active:
changeproductslist_url = urlresolvers.reverse(
'admin:repanier_product_changelist',
)
link = ' %s' \
% (changeproductslist_url, str(self.id), _("his_products"))
return link
return EMPTY_STRING
get_products.short_description = (_("link to his products"))
get_products.allow_tags = True
def get_admin_date_balance(self):
if self.id is not None:
bank_account = bankaccount.BankAccount.objects.filter(
producer_id=self.id, producer_invoice__isnull=True
).order_by("-operation_date").only("operation_date").first()
if bank_account is not None:
return bank_account.operation_date
return self.date_balance
else:
return timezone.now().date()
get_admin_date_balance.short_description = (_("date_balance"))
get_admin_date_balance.allow_tags = False
def get_admin_balance(self):
if self.id is not None:
return self.balance - self.get_bank_not_invoiced() + self.get_order_not_invoiced()
else:
return REPANIER_MONEY_ZERO
get_admin_balance.short_description = (_("balance"))
get_admin_balance.allow_tags = False
def get_order_not_invoiced(self):
from repanier.apps import REPANIER_SETTINGS_INVOICE
if REPANIER_SETTINGS_INVOICE:
result_set = invoice.ProducerInvoice.objects.filter(
producer_id=self.id,
status__gte=PERMANENCE_OPENED,
status__lte=PERMANENCE_SEND
).order_by('?').aggregate(Sum('total_price_with_tax'), Sum('delta_price_with_tax'), Sum('delta_transport'))
if result_set["total_price_with_tax__sum"] is not None:
order_not_invoiced = RepanierMoney(result_set["total_price_with_tax__sum"])
else:
order_not_invoiced = REPANIER_MONEY_ZERO
if result_set["delta_price_with_tax__sum"] is not None:
order_not_invoiced += RepanierMoney(result_set["delta_price_with_tax__sum"])
if result_set["delta_transport__sum"] is not None:
order_not_invoiced += RepanierMoney(result_set["delta_transport__sum"])
else:
order_not_invoiced = REPANIER_MONEY_ZERO
return order_not_invoiced
def get_bank_not_invoiced(self):
from repanier.apps import REPANIER_SETTINGS_INVOICE
if REPANIER_SETTINGS_INVOICE:
result_set = bankaccount.BankAccount.objects.filter(
producer_id=self.id, producer_invoice__isnull=True
).order_by('?').aggregate(Sum('bank_amount_in'), Sum('bank_amount_out'))
if result_set["bank_amount_in__sum"] is not None:
bank_in = RepanierMoney(result_set["bank_amount_in__sum"])
else:
bank_in = REPANIER_MONEY_ZERO
if result_set["bank_amount_out__sum"] is not None:
bank_out = RepanierMoney(result_set["bank_amount_out__sum"])
else:
bank_out = REPANIER_MONEY_ZERO
bank_not_invoiced = bank_in - bank_out
else:
bank_not_invoiced = REPANIER_MONEY_ZERO
return bank_not_invoiced
def get_calculated_invoiced_balance(self, permanence_id):
bank_not_invoiced = self.get_bank_not_invoiced()
# IMPORTANT : when is_resale_price_fixed=True then price_list_multiplier == 1
# Do not take into account product whose order unit is >= PRODUCT_ORDER_UNIT_DEPOSIT
result_set = offeritem.OfferItem.objects.filter(
permanence_id=permanence_id,
producer_id=self.id,
price_list_multiplier__lt=1
).exclude(
order_unit__gte=PRODUCT_ORDER_UNIT_DEPOSIT
).order_by('?').aggregate(
Sum('total_selling_with_tax')
)
if result_set["total_selling_with_tax__sum"] is not None:
payment_needed = result_set["total_selling_with_tax__sum"]
else:
payment_needed = DECIMAL_ZERO
# print("payment_needed (1) %f" % payment_needed)
result_set = offeritem.OfferItem.objects.filter(
permanence_id=permanence_id,
producer_id=self.id,
price_list_multiplier__gte=1,
).exclude(
order_unit__gte=PRODUCT_ORDER_UNIT_DEPOSIT
).order_by('?').aggregate(
Sum('total_purchase_with_tax'),
)
if result_set["total_purchase_with_tax__sum"] is not None:
payment_needed += result_set["total_purchase_with_tax__sum"]
# print("payment_needed (2) %f" % payment_needed)
calculated_invoiced_balance = self.balance - bank_not_invoiced + payment_needed
# print("calculated_invoiced_balance %f" % calculated_invoiced_balance)
if self.manage_replenishment:
for offer_item in offeritem.OfferItem.objects.filter(
is_active=True,
permanence_id=permanence_id,
producer_id=self.id,
manage_replenishment=True,
).order_by('?'):
invoiced_qty, taken_from_stock, customer_qty = offer_item.get_producer_qty_stock_invoiced()
if offer_item.price_list_multiplier < DECIMAL_ONE: # or offer_item.is_resale_price_fixed:
unit_price = offer_item.customer_unit_price.amount
else:
unit_price = offer_item.producer_unit_price.amount
if taken_from_stock > DECIMAL_ZERO:
delta_price_with_tax = (
(unit_price + offer_item.unit_deposit.amount)
* taken_from_stock
).quantize(TWO_DECIMALS)
calculated_invoiced_balance -= delta_price_with_tax
return calculated_invoiced_balance
get_calculated_invoiced_balance.short_description = (_("balance"))
get_calculated_invoiced_balance.allow_tags = False
def get_balance(self):
last_producer_invoice_set = invoice.ProducerInvoice.objects.filter(
producer_id=self.id, invoice_sort_order__isnull=False
).order_by('?')
# label = "%s, %s, %s, %s" % (self.balance, self.get_bank_not_invoiced(), self.get_order_not_invoiced(), self.get_admin_balance())
balance = self.get_admin_balance()
if last_producer_invoice_set.exists():
if balance.amount < 0:
return '' + (
'%s' % (-balance,)) + ''
elif balance.amount == 0:
return '' + (
'%s' % (-balance,)) + ''
elif balance.amount > 30:
return '' + (
'%s' % (-balance,)) + ''
else:
return '' + (
'%s' % (-balance,)) + ''
else:
if balance.amount < 0:
return '%s' % (-balance,)
elif balance.amount == 0:
return '%s' % (-balance,)
elif balance.amount > 30:
return '%s' % (-balance,)
else:
return '%s' % (-balance,)
get_balance.short_description = _("balance")
get_balance.allow_tags = True
get_balance.admin_order_field = 'balance'
def get_last_invoice(self):
producer_last_invoice = invoice.ProducerInvoice.objects.filter(
producer_id=self.id, invoice_sort_order__isnull=False
).order_by("-id").first()
if producer_last_invoice is not None:
if producer_last_invoice.total_price_with_tax < DECIMAL_ZERO:
return '%s' % (
number_format(producer_last_invoice.total_price_with_tax, 2))
elif producer_last_invoice.total_price_with_tax == DECIMAL_ZERO:
return '%s' % (
number_format(producer_last_invoice.total_price_with_tax, 2))
elif producer_last_invoice.total_price_with_tax > 30:
return '%s' % (
number_format(producer_last_invoice.total_price_with_tax, 2))
else:
return '%s' % (
number_format(producer_last_invoice.total_price_with_tax, 2))
else:
return '%s' % (number_format(0, 2))
get_last_invoice.short_description = _("last invoice")
get_last_invoice.allow_tags = True
def get_on_hold_movement_json(self, to_json):
bank_not_invoiced = self.get_bank_not_invoiced()
order_not_invoiced = self.get_order_not_invoiced()
if order_not_invoiced.amount != DECIMAL_ZERO or bank_not_invoiced.amount != DECIMAL_ZERO:
if order_not_invoiced.amount != DECIMAL_ZERO:
if bank_not_invoiced.amount == DECIMAL_ZERO:
producer_on_hold_movement = \
_('This balance does not take account of any unbilled sales %(other_order)s.') % {
'other_order': order_not_invoiced
}
else:
producer_on_hold_movement = \
_(
'This balance does not take account of any unrecognized payments %(bank)s and any unbilled order %(other_order)s.') \
% {
'bank' : bank_not_invoiced,
'other_order': order_not_invoiced
}
else:
producer_on_hold_movement = \
_(
'This balance does not take account of any unrecognized payments %(bank)s.') % {
'bank': bank_not_invoiced
}
option_dict = {'id': "#basket_message", 'html': mark_safe(producer_on_hold_movement)}
to_json.append(option_dict)
return
def __str__(self):
if self.producer_price_are_wo_vat:
return "%s %s" % (self.short_profile_name, _("wo tax"))
return self.short_profile_name
class Meta:
verbose_name = _("producer")
verbose_name_plural = _("producers")
ordering = ("short_profile_name",)
@receiver(pre_save, sender=Producer)
def producer_pre_save(sender, **kwargs):
producer = kwargs["instance"]
if producer.represent_this_buyinggroup:
# The buying group may not be de activated
producer.is_active = True
if producer.email:
producer.email = producer.email.lower()
if producer.email2:
producer.email2 = producer.email2.lower()
if producer.email3:
producer.email3 = producer.email3.lower()
if producer.producer_pre_opening:
# Important to make difference between the stock of the group and the stock of the producer
producer.manage_replenishment = False
producer.manage_production = True
producer.is_resale_price_fixed = False
elif producer.manage_replenishment:
# Important to compute ProducerInvoice.total_price_with_tax
producer.invoice_by_basket = False
if producer.price_list_multiplier <= DECIMAL_ZERO:
producer.price_list_multiplier = DECIMAL_ONE
if not producer.uuid:
producer.uuid = uuid.uuid4()
if producer.bank_account is not None and len(producer.bank_account.strip()) == 0:
producer.bank_account = None