Commit 8ba5667f authored by Patrick's avatar Patrick

New version in progress

parent 02f2536b
from django import forms
from django.contrib.admin.widgets import AdminDateWidget
from django.forms.formsets import formset_factory
from django.utils.translation import ugettext_lazy as _
from djangocms_text_ckeditor.widgets import TextEditorWidget
from repanier.models import Producer
from repanier.const import REPANIER_MONEY_ZERO
from repanier.fields.RepanierMoneyField import FormMoneyField
......@@ -66,6 +66,21 @@ class PermanenceInvoicedForm(forms.Form):
self.fields['payment_date'].initial = self.payment_date
class ImportXlsxForm(forms.Form):
template = 'repanier/import_xlsx.html'
file_to_import = forms.FileField(label=_('File to import'), allow_empty_file=False)
class ImportInvoiceForm(ImportXlsxForm):
template = 'repanier/import_invoice_xlsx.html'
# Important : The length of invoice_reference must be the same as of permanence.short_name
invoice_reference = forms.CharField(label=_("invoice reference"), max_length=50, required=False)
producer = forms.ModelChoiceField(label=_('producer'), queryset=Producer.objects.filter(is_active=True).all(), required=False)
def __init__(self, *args, **kwargs):
super(ImportInvoiceForm, self).__init__(*args, **kwargs)
self.fields["invoice_reference"].widget.attrs['style'] = "width:450px !important"
class ProducerInvoicedForm(forms.Form):
selected = forms.BooleanField(required=False)
......
......@@ -2,9 +2,10 @@
from __future__ import unicode_literals
from django.conf import settings
from django.conf.urls import url
from django.contrib import admin
from django.core.checks import messages
from django.db.models import Q, F
from django.db.models import F
from django.shortcuts import render
from django.utils import timezone
from django.http import HttpResponseRedirect, HttpResponse
......@@ -16,14 +17,14 @@ from parler.admin import TranslatableAdmin
import repanier.apps
from repanier.admin.fkey_choice_cache_mixin import ForeignKeyCacheMixin
from repanier.admin.forms import InvoiceOrderForm, ProducerInvoicedFormSet, PermanenceInvoicedForm
from repanier.admin.forms import InvoiceOrderForm, ProducerInvoicedFormSet, PermanenceInvoicedForm, ImportXlsxForm, ImportInvoiceForm
from repanier.const import *
from repanier.fields.RepanierMoneyField import RepanierMoney
from repanier.models import Customer, Purchase, Permanence, Producer, PermanenceBoard, LUT_PermanenceRole, BankAccount, ProducerInvoice, Configuration
from repanier.models import Customer, Purchase, PermanenceBoard, LUT_PermanenceRole, BankAccount, ProducerInvoice
from repanier.task import task_invoice
from repanier.tools import send_email_to_who, get_signature
from repanier.xlsx.views import import_xslx_view
from repanier.xlsx.xlsx_invoice import export_bank, export_invoice
from repanier.xlsx.xlsx_invoice import export_bank, export_invoice, handle_uploaded_invoice
from repanier.xlsx.xlsx_purchase import handle_uploaded_purchase, export_purchase
from repanier.xlsx.xlsx_stock import export_permanence_stock
......@@ -56,16 +57,15 @@ class PermanenceDoneAdmin(TranslatableAdmin):
inlines = [PermanenceBoardInline]
date_hierarchy = 'permanence_date'
list_display = ('get_permanence_admin_display', 'get_producers', 'get_customers', 'get_board', 'get_full_status_display')
ordering = ('status', '-permanence_date')
ordering = ('-permanence_date', 'status')
actions = [
'add_delivery',
'cancel_delivery',
'export_xlsx',
'import_xlsx',
'generate_invoices',
'cancel_invoices',
'preview_invoices',
'send_invoices',
'cancel_delivery',
'cancel_invoices',
'generate_archive',
'cancel_archive',
]
......@@ -82,18 +82,52 @@ class PermanenceDoneAdmin(TranslatableAdmin):
return True
return False
def add_delivery(self, request, permanence_qs):
pass
def get_urls(self):
urls = super(PermanenceDoneAdmin, self).get_urls()
my_urls = [
url(r'^import_invoice/$', self.add_delivery),
]
return my_urls + urls
add_delivery.short_description = _("Add delivery")
def add_delivery(self, request):
return import_xslx_view(
self, admin, request, None, _("Import an invoice"),
handle_uploaded_invoice, action='import_xlsx', form_klass=ImportInvoiceForm
)
def cancel_delivery(self, request, permanence_qs):
pass
if 'cancel' in request.POST:
user_message = _("Action canceled by the user.")
user_message_level = messages.INFO
self.message_user(request, user_message, user_message_level)
return
permanence = permanence_qs.first()
if permanence is None or permanence.status not in [
PERMANENCE_CLOSED, PERMANENCE_SEND
]:
user_message = _("Action canceled by the system.")
user_message_level = messages.ERROR
self.message_user(request, user_message, user_message_level)
return
if 'apply' in request.POST:
task_invoice.cancel_delivery(
permanence=permanence,
)
user_message = _("Action performed.")
user_message_level = messages.INFO
self.message_user(request, user_message, user_message_level)
return
return render(request, 'repanier/confirm_admin_action.html', {
'sub_title' : _("Please, confirm the action : cancel delivery"),
'action' : 'cancel_delivery',
'permanence' : permanence,
'action_checkbox_name': admin.ACTION_CHECKBOX_NAME,
})
cancel_delivery.short_description = _("Cancel delivery")
def export_xlsx(self, request, queryset):
permanence = queryset.first()
def export_xlsx(self, request, permanence_qs):
permanence = permanence_qs.first()
if permanence is None or permanence.status != PERMANENCE_SEND:
user_message = _("Action canceled by the system.")
user_message_level = messages.ERROR
......@@ -113,16 +147,16 @@ class PermanenceDoneAdmin(TranslatableAdmin):
export_xlsx.short_description = _("Export orders prepared as XSLX file")
def import_xlsx(self, request, queryset):
permanence = queryset.first()
def import_xlsx(self, request, permanence_qs):
permanence = permanence_qs.first()
if permanence is None or permanence.status != PERMANENCE_SEND:
user_message = _("Action canceled by the system.")
user_message_level = messages.ERROR
self.message_user(request, user_message, user_message_level)
return
return import_xslx_view(
self, admin, request, queryset[:1], _("Import orders prepared"),
handle_uploaded_purchase, action='import_xlsx'
self, admin, request, permanence_qs[:1], _("Import orders prepared"),
handle_uploaded_purchase, action='import_xlsx', form_klass=ImportXlsxForm
)
import_xlsx.short_description = _("Import orders prepared from a XLSX file")
......@@ -156,7 +190,7 @@ class PermanenceDoneAdmin(TranslatableAdmin):
preview_invoices.short_description = _("Preview invoices before sending them by email")
def generate_invoices(self, request, queryset):
def generate_invoices(self, request, permanence_qs):
if 'done' in request.POST:
user_message = _("Action performed.")
user_message_level = messages.INFO
......@@ -167,7 +201,7 @@ class PermanenceDoneAdmin(TranslatableAdmin):
user_message_level = messages.INFO
self.message_user(request, user_message, user_message_level)
return
permanence = queryset.first()
permanence = permanence_qs.first()
if permanence is None or permanence.status != PERMANENCE_SEND:
user_message = _("Action canceled by the system.")
user_message_level = messages.ERROR
......@@ -309,8 +343,14 @@ class PermanenceDoneAdmin(TranslatableAdmin):
generate_invoices.short_description = _('generate invoices')
def generate_archive(self, request, queryset):
permanence = queryset.first()
def generate_archive(self, request, permanence_qs):
if 'cancel' in request.POST:
user_message = _("Action canceled by the user.")
user_message_level = messages.INFO
self.message_user(request, user_message, user_message_level)
return
permanence = permanence_qs.first()
if permanence is None or permanence.status not in [
PERMANENCE_CLOSED, PERMANENCE_SEND
]:
......@@ -318,33 +358,32 @@ class PermanenceDoneAdmin(TranslatableAdmin):
user_message_level = messages.ERROR
self.message_user(request, user_message, user_message_level)
return
if permanence.status == PERMANENCE_CLOSED:
permanence.set_status(PERMANENCE_SEND)
ProducerInvoice.objects.filter(
permanence_id=permanence.id,
invoice_sort_order__isnull=True,
).order_by('?').update(to_be_paid=True)
task_invoice.generate_invoice(
permanence=permanence,
payment_date=timezone.now().date()
)
user_message = _("Action performed.")
user_message_level = messages.INFO
self.message_user(request, user_message, user_message_level)
return
if 'apply' in request.POST:
task_invoice.generate_archive(
permanence=permanence,
)
user_message = _("Action performed.")
user_message_level = messages.INFO
self.message_user(request, user_message, user_message_level)
return
return render(request, 'repanier/confirm_admin_action.html', {
'sub_title' : _("Please, confirm the action : generate archive"),
'action' : 'generate_archive',
'permanence' : permanence,
'action_checkbox_name': admin.ACTION_CHECKBOX_NAME,
})
generate_archive.short_description = _('archive')
def cancel_invoices_or_archive(self,request, queryset, action):
def cancel_invoice_or_archive_or_cancelled(self,request, permanence_qs, action):
if 'cancel' in request.POST:
user_message = _("Action canceled by the user.")
user_message_level = messages.INFO
self.message_user(request, user_message, user_message_level)
return
permanence = queryset.first()
if permanence is None or permanence.status not in [PERMANENCE_INVOICED, PERMANENCE_ARCHIVED]:
permanence = permanence_qs.first()
if permanence is None or permanence.status not in [PERMANENCE_INVOICED, PERMANENCE_ARCHIVED, PERMANENCE_CANCELLED]:
user_message = _("Action canceled by the system.")
user_message_level = messages.ERROR
self.message_user(request, user_message, user_message_level)
......@@ -357,29 +396,30 @@ class PermanenceDoneAdmin(TranslatableAdmin):
return render(request, 'repanier/confirm_admin_action.html', {
'sub_title' : _(
"Please, confirm the action : cancel the invoices") if permanence.status == PERMANENCE_INVOICED else _(
"Please, confirm the action : cancel the archiving"),
"Please, confirm the action : cancel the archiving") if permanence.status == PERMANENCE_ARCHIVED else _(
"Please, confirm the action : restore the delivery"),
'action' : action,
'permanence' : permanence,
'action_checkbox_name': admin.ACTION_CHECKBOX_NAME,
})
def cancel_invoices(self, request, queryset):
return self.cancel_invoices_or_archive(request, queryset, 'cancel_invoices')
def cancel_invoices(self, request, permanence_qs):
return self.cancel_invoice_or_archive_or_cancelled(request, permanence_qs, 'cancel_invoices')
cancel_invoices.short_description = _('cancel latest invoices')
def cancel_archive(self, request, queryset):
return self.cancel_invoices_or_archive(request, queryset, 'cancel_archive')
def cancel_archive(self, request, permanence_qs):
return self.cancel_invoice_or_archive_or_cancelled(request, permanence_qs, 'cancel_archive')
cancel_archive.short_description = _('cancel archiving')
def send_invoices(self, request, queryset):
def send_invoices(self, request, permanence_qs):
if 'cancel' in request.POST:
user_message = _("Action canceled by the user.")
user_message_level = messages.INFO
self.message_user(request, user_message, user_message_level)
return
permanence = queryset.first()
permanence = permanence_qs.first()
if permanence is None or permanence.status != PERMANENCE_INVOICED:
user_message = _("Action canceled by the system.")
user_message_level = messages.ERROR
......@@ -446,7 +486,7 @@ class PermanenceDoneAdmin(TranslatableAdmin):
form = InvoiceOrderForm(request.POST)
if form.is_valid():
user_message, user_message_level = task_invoice.admin_send(
permanence.id
permanence
)
self.message_user(request, user_message, user_message_level)
return HttpResponseRedirect(request.get_full_path())
......@@ -520,3 +560,6 @@ class PermanenceDoneAdmin(TranslatableAdmin):
permanence_date=permanence.permanence_date)
super(PermanenceDoneAdmin, self).save_model(
request, permanence, form, change)
class Media:
js = ('js/import_invoice.js',)
......@@ -18,6 +18,7 @@ from import_export.formats.base_formats import XLS
from import_export.widgets import CharWidget
import repanier.apps
from repanier.admin.forms import ImportXlsxForm
from repanier.models import BoxContent
from repanier.const import *
from repanier.models import Permanence, Product, \
......@@ -297,8 +298,9 @@ class ProducerAdmin(ImportExportMixin, admin.ModelAdmin):
export_xlsx_stock.short_description = _("Export stock to a xlsx file")
def import_xlsx_stock(self, request):
return import_xslx_view(self, admin, request, Producer.objects.all(), _("Import stock"), handle_uploaded_stock, action='import_xlsx_stock')
# return xlsx_stock.admin_import(self, admin, request, Producer.objects.all(), action='import_xlsx_stock')
return import_xslx_view(
self, admin, request, Producer.objects.all(), _("Import stock"), handle_uploaded_stock,
action='import_xlsx_stock', form_klass=ImportXlsxForm)
import_xlsx_stock.short_description = _("Import stock from a xlsx file")
......
......@@ -268,7 +268,8 @@ class ProductDataForm(TranslatableModelForm):
model = Product
fields = "__all__"
widgets = {
'order_unit' : SelectAdminOrderUnitWidget(),
'long_name' : forms.TextInput(attrs={'style': "width:450px !important"}),
'order_unit' : SelectAdminOrderUnitWidget(attrs={'style': "width:450px !important"}),
'department_for_customer': apply_select2(forms.Select),
}
......
......@@ -13,7 +13,7 @@ from django.http import HttpResponseRedirect
from django.utils import translation
from django.utils.text import slugify
from django.utils.translation import ugettext_lazy as _
from django.views.i18n import JavaScriptCatalog
# from django.views.i18n import JavaScriptCatalog
from easy_select2 import Select2
from import_export import resources, fields
from import_export.admin import ExportMixin
......
......@@ -61,6 +61,7 @@ PERMANENCE_WAIT_FOR_INVOICED = '600'
PERMANENCE_INVOICES_VALIDATION_FAILED = '700'
PERMANENCE_INVOICED = '800'
PERMANENCE_ARCHIVED = '900'
PERMANENCE_CANCELLED = '950'
LUT_PERMANENCE_STATUS = (
(PERMANENCE_DISABLED, _('disabled')),
......@@ -76,7 +77,8 @@ LUT_PERMANENCE_STATUS = (
(PERMANENCE_WAIT_FOR_INVOICED, _('wait for done')),
(PERMANENCE_INVOICES_VALIDATION_FAILED, _('invoices validation test failed')),
(PERMANENCE_INVOICED, _('invoiced')),
(PERMANENCE_ARCHIVED, _('archived'))
(PERMANENCE_ARCHIVED, _('archived')),
(PERMANENCE_CANCELLED, _('cancelled'))
)
PRODUCT_PLACEMENT_FREEZER = '100'
......
This diff is collapsed.
......@@ -7,8 +7,8 @@ msgid ""
msgstr ""
"Project-Id-Version: repanier\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-03-09 11:31+0100\n"
"PO-Revision-Date: 2017-03-09 17:48+0100\n"
"POT-Creation-Date: 2017-04-14 13:41+0200\n"
"PO-Revision-Date: 2017-04-15 08:56+0100\n"
"Last-Translator: Patrick Colmant <pcolmant@gmail.com>\n"
"Language-Team: Patrick Colmant <pcolmant@gmail.com>\n"
"Language: fr\n"
......@@ -26,6 +26,10 @@ msgstr "Exporter le stock"
msgid "Import stock"
msgstr "Importer le stock"
#: static/js/import_invoice.js:4
msgid "Import an invoice"
msgstr "Importer une facture"
#: static/js/is_order_confirm_send.js:4
msgid "✗🔓"
msgstr "✗🔓 - déverrouiller"
......
......@@ -29,6 +29,9 @@ class CustomerInvoice(models.Model):
customer = models.ForeignKey(
'Customer', verbose_name=_("customer"),
on_delete=models.PROTECT)
customer_charged = models.ForeignKey(
'Customer', verbose_name=_("customer"), related_name='invoices_paid', blank=True, null=True,
on_delete=models.PROTECT, db_index=True)
permanence = models.ForeignKey(
'Permanence', verbose_name=permanence_verbose_name(),
on_delete=models.PROTECT, db_index=True)
......@@ -109,10 +112,10 @@ class CustomerInvoice(models.Model):
help_text=_("This is the minimum order amount to avoid shipping cost."),
default=DECIMAL_ZERO, max_digits=5, decimal_places=2,
validators=[MinValueValidator(0)])
customer_charged = models.ForeignKey(
'Customer', verbose_name=_("customer"),
related_name='invoices_paid',
on_delete=models.PROTECT, db_index=True)
# customer_charged = models.ForeignKey(
# 'Customer', verbose_name=_("customer"),
# related_name='invoices_paid',
# on_delete=models.PROTECT, db_index=True)
master_permanence = models.ForeignKey(
'Permanence', verbose_name=_("master permanence"),
related_name='child_customer_invoice',
......
......@@ -46,8 +46,9 @@ class OfferItem(TranslatableModel):
'Permanence', verbose_name=REPANIER_SETTINGS_PERMANENCE_NAME, on_delete=models.PROTECT,
db_index=True
)
# Important : Check select_related
product = models.ForeignKey(
'Product', verbose_name=_("product"), on_delete=models.PROTECT)
'Product', verbose_name=_("product"), null=True, blank=True, default=None, on_delete=models.PROTECT)
picture2 = AjaxPictureField(
verbose_name=_("picture"),
null=True, blank=True,
......
......@@ -408,8 +408,6 @@ class Permanence(TranslatableModel):
update_fields=['status', 'is_updated_on', 'highest_status', 'payment_date'])
else:
self.save(update_fields=['status', 'is_updated_on', 'highest_status'])
menu_pool.clear()
cache.clear()
if new_status == PERMANENCE_WAIT_FOR_OPEN:
for a_producer in producer.Producer.objects.filter(
permanence=self.id
......
......@@ -4,6 +4,5 @@
$(".object-tools").prepend('<li><a href="./export_stock/' + location.search + '">' + gettext('Export stock')+ '</a></li>');
$(".object-tools").prepend('<li><a href="./import_stock/' + location.search + '">' + gettext('Import stock')+ '</a></li>');
}
// $(".object-tools").append('<li><a href="./is_order_confirm_send/' + location.search + '" class="addlink">&nbsp;&nbsp;🔐&nbsp;&nbsp;</a></li>');
});
})(django.jQuery);
(function($) {
$(document).ready(function($) {
if(location.pathname.indexOf('change') <= -1) {
$(".object-tools").append('<li><a href="./import_invoice/' + location.search + '">' + gettext('Import an invoice')+ '</a></li>');
}
});
})(django.jQuery);
......@@ -523,7 +523,16 @@ def generate_invoice(permanence, payment_date):
@transaction.atomic
def cancel(permanence):
def generate_archive(permanence):
permanence.set_status(PERMANENCE_ARCHIVED)
@transaction.atomic
def cancel_delivery(permanence):
permanence.set_status(PERMANENCE_CANCELLED)
@transaction.atomic
def cancel_invoice(permanence):
if permanence.status in [PERMANENCE_INVOICED, PERMANENCE_ARCHIVED]:
last_bank_account_total = BankAccount.objects.filter(
operation_status=BANK_LATEST_TOTAL, permanence_id=permanence.id
......@@ -649,16 +658,15 @@ def cancel(permanence):
permanence.set_status(PERMANENCE_SEND)
def admin_send(permanence_id):
if repanier.apps.REPANIER_SETTINGS_INVOICE:
thread.start_new_thread(email_invoice.send_invoice, (permanence_id,))
user_message = _("Emails containing the invoices will be send to the customers and the producers.")
user_message_level = messages.INFO
@transaction.atomic
def cancel_archive(permanence):
if BankAccount.objects.filter(
operation_status=BANK_LATEST_TOTAL, permanence_id=permanence.id
).order_by('?').exists():
# old archive
cancel_invoice(permanence)
else:
user_message = _("This action is not activated for your group.")
user_message_level = messages.ERROR
return user_message, user_message_level
permanence.set_status(PERMANENCE_SEND, allow_downgrade=True)
def admin_cancel(permanence):
......@@ -672,7 +680,7 @@ def admin_cancel(permanence):
if last_permanence_invoiced_id is not None:
if last_permanence_invoiced_id == permanence.id:
# This is well the latest closed permanence. The invoices can be cancelled without damages.
cancel(permanence)
cancel_invoice(permanence)
user_message = _("The selected invoice has been canceled.")
user_message_level = messages.INFO
else:
......@@ -685,8 +693,8 @@ def admin_cancel(permanence):
user_message = _("The selected invoice has been canceled.")
user_message_level = messages.INFO
permanence.set_status(PERMANENCE_SEND)
elif permanence.status == PERMANENCE_ARCHIVED:
cancel(permanence)
elif permanence.status in [PERMANENCE_ARCHIVED, PERMANENCE_CANCELLED]:
cancel_archive(permanence)
user_message = _("The selected invoice has been restored.")
user_message_level = messages.INFO
else:
......@@ -695,3 +703,16 @@ def admin_cancel(permanence):
user_message_level = messages.ERROR
return user_message, user_message_level
def admin_send(permanence):
if permanence.status == PERMANENCE_INVOICED:
thread.start_new_thread(email_invoice.send_invoice, (permanence.id,))
user_message = _("Emails containing the invoices will be send to the customers and the producers.")
user_message_level = messages.INFO
else:
user_message = _("The status of %(permanence)s prohibit you to send invoices.") % {
'permanence': permanence}
user_message_level = messages.ERROR
return user_message, user_message_level
\ No newline at end of file
......@@ -58,6 +58,7 @@ abbr, address, article, aside, audio, b, blockquote, body, canvas, caption, cite
{# ^^^^^^^^^^^ django cms admin style 1.2.6 #}
</style>
{% block blockbots %}<meta name="robots" content="NONE,NOARCHIVE" />{% endblock %}
<script type="text/javascript" src="{% url 'javascript-catalog' %}"></script>
</head>
<body class="{% if is_popup %}popup {% endif %}{% block bodyclass %}{% endblock %}"
data-admin-utc-offset="{% now "Z" %}">
......@@ -87,7 +88,7 @@ abbr, address, article, aside, audio, b, blockquote, body, canvas, caption, cite
{# {% block content_title %}{% if title %}<h1>{{ title }}</h1>{% endif %}{% endblock %}#}
{% block content %}
{% block object-tools %}<li><a href="https://repanier.be/fr/documentation/">' + gettext('Help')+ '</a></li>{% endblock %}
{% block object-tools %}{% endblock %}
{{ content }}
{% endblock %}
......
{% extends "admin/base_site.html" %}
{% load cms_tags menu_tags compress i18n l10n static %}
{% block content %}
<link rel="stylesheet" href="{% static "bootstrap/css/bootstrap.css" %}">
<link rel="stylesheet" href="{% static "css/custom.css" %}">
<style type="text/css">
.form-group input[type="checkbox"] {
display: none;
}
.form-group input[type="checkbox"] + .btn-group > label span {
width: 20px;
}
.form-group input[type="checkbox"] + .btn-group > label span:first-child {
display: none;
}
.form-group input[type="checkbox"] + .btn-group > label span:last-child {
display: inline-block;
}
.form-group input[type="checkbox"]:checked + .btn-group > label span:first-child {
display: inline-block;
}
.form-group input[type="checkbox"]:checked + .btn-group > label span:last-child {
display: none;
}
.colorgraph {
height: 5px;
border-top: 0;
background: #c4e17f;
border-radius: 5px;
background-image: -webkit-linear-gradient(left, #c4e17f, #62c2e4);
background-image: -moz-linear-gradient(left, #c4e17f, #62c2e4);
background-image: -o-linear-gradient(left, #c4e17f, #62c2e4);
background-image: linear-gradient(to right, #c4e17f, #62c2e4);
}
</style>
<h4>{{ sub_title }}</h4>
<p></p>
<form enctype="multipart/form-data" action="" method="post">{% csrf_token %}
<fieldset class="module aligned ">
<div class="form-group">
<b>{% trans "The importation concern :" %}</b> {% for object in queryset.all %}{{ object }}<input type="hidden" name="{{ action_checkbox_name }}" value="{{ object.id|unlocalize }}"/>{% if not forloop.last %}, {% endif %}{% endfor %}
<br/>
<label for="id_file_to_import"><b>{% trans "File to import :"%}</b></label><input id="id_file_to_import" name="file_to_import" type="file" />
</div>
<div class="form-group">
<div class="fieldWrapper">
{{ form.invoice_reference.errors }}