ソースを参照

Realese functionality of partner document upload

TonyKurts 3 ヶ月 前
コミット
aa0c37f44d

+ 10 - 1
SharixAdmin/forms/document.py

@@ -5,6 +5,8 @@ from django.core.exceptions import ValidationError
 
 
 class DocumentUploadForm(forms.Form):
+    MAX_FILES = 5  # Максимальное количество файлов для загрузки
+
     doc_expire_date = forms.DateField(
         required=False,
         widget=forms.DateInput(attrs={
@@ -14,7 +16,10 @@ class DocumentUploadForm(forms.Form):
         label='Дата окончания действия документа (при наличии)'
     )
     doc_file = forms.FileField(
-        widget=forms.ClearableFileInput(attrs={'multiple': True}),
+        widget=forms.ClearableFileInput(attrs={
+            'multiple': True,
+            'id': 'doc-file-input'
+        }),
         required=True,
     )
 
@@ -30,6 +35,10 @@ class DocumentUploadForm(forms.Form):
         files = self.files.getlist('doc_file')
         allowed_mime_types = ['image/jpeg', 'image/png', 'application/pdf']
         allowed_extensions = ['jpg', 'jpeg', 'png', 'pdf']
+
+        # Проверка на максимальное количество файлов
+        if len(files) > self.MAX_FILES:
+            raise ValidationError(f"Вы можете загрузить не более {self.MAX_FILES} файлов.")
         
         for file in files:
             # Проверяем расширение файла

+ 0 - 3
SharixAdmin/static/SharixAdmin/img/file.svg

@@ -1,3 +0,0 @@
-<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M20 8.94C19.9896 8.84813 19.9695 8.75763 19.94 8.67V8.58C19.8919 8.47718 19.8278 8.38267 19.75 8.3L13.75 2.3C13.6673 2.22222 13.5728 2.15808 13.47 2.11H13.38C13.2784 2.05174 13.1662 2.01434 13.05 2H7C6.20435 2 5.44129 2.31607 4.87868 2.87868C4.31607 3.44129 4 4.20435 4 5V19C4 19.7956 4.31607 20.5587 4.87868 21.1213C5.44129 21.6839 6.20435 22 7 22H17C17.7956 22 18.5587 21.6839 19.1213 21.1213C19.6839 20.5587 20 19.7956 20 19V9C20 9 20 9 20 8.94ZM14 5.41L16.59 8H15C14.7348 8 14.4804 7.89464 14.2929 7.70711C14.1054 7.51957 14 7.26522 14 7V5.41ZM18 19C18 19.2652 17.8946 19.5196 17.7071 19.7071C17.5196 19.8946 17.2652 20 17 20H7C6.73478 20 6.48043 19.8946 6.29289 19.7071C6.10536 19.5196 6 19.2652 6 19V5C6 4.73478 6.10536 4.48043 6.29289 4.29289C6.48043 4.10536 6.73478 4 7 4H12V7C12 7.79565 12.3161 8.55871 12.8787 9.12132C13.4413 9.68393 14.2044 10 15 10H18V19Z" fill="#479FF8"/>
-</svg>

+ 5 - 0
SharixAdmin/static/drf-yasg/colors2.css

@@ -13,6 +13,11 @@
   --btn-danger: #821e27;
   --btn-danger-txt: #821e27;
   --btn-danger-hover: #821e27;
+  --primary: #479FF8;
+}
+
+.text-primary {
+  color: var(--primary) !important;
 }
 
 .btn-primary {

+ 3 - 0
SharixAdmin/templates/SharixAdmin/base.html

@@ -12,6 +12,9 @@
     <link rel="stylesheet" href="{% static 'drf-yasg/colors2.css' %}">
     <link rel="stylesheet" href="{% static 'SharixAdmin/css/style.css' %}">
 
+    <link href="{% static 'fontawesomefree/css/fontawesome.css' %}" rel="stylesheet" type="text/css">
+    <link href="{% static 'fontawesomefree/css/brands.css' %}" rel="stylesheet" type="text/css">
+    
     <title>{{ title }}</title>
 </head>
 

+ 11 - 10
SharixAdmin/templates/SharixAdmin/partner.html

@@ -37,20 +37,21 @@
         {% for doc in docs %}
             <li class="d-flex justify-content-between">
                 <div class="d-flex align-items-center">
-                    <img class="me-2 no-user-select" style="height: 32px;" src="{% static "SharixAdmin/img/file.svg" %}">
-                    {{ doc.get_doc_type_display }}
-                    
-                    {% if doc.files.all %}
-                        <span class="me-3 text-secondary ms-1">
-                            (на проверке)
-                        </span>
+                    {% if doc.ticket_status and doc.status == "deactivated" %}
+                        <i class="fa-solid fa-clock-rotate-left me-2 no-user-select fs-4 text-warning"></i>
+                    {% elif doc.status == "activated" %}
+                        <i class="fa-solid fa-check me-2 no-user-select fs-4 text-success"></i>
+                    {% else %}
+                        <i class="fa-regular fa-file me-2 no-user-select fs-4 text-primary"></i>
                     {% endif %}
+
+                    {{ doc.get_doc_type_display }}
                 </div>
                 
-                {% if not doc.files.all %}
-                    <a class="btn btn-sm btn-primary" href="{% url "partner_doc_edit" doc.doc_type %}">Загрузить</a>
+                {% if doc.ticket_status %}
+                    <a class="btn btn-sm btn-outline-primary" href="{% url "partner_doc" doc.doc_type %}">Посмотреть</a>
                 {% else %}
-                    <a class="btn btn-sm btn-outline-primary" href="{% url "partner_doc_edit" doc.doc_type %}">Изменить</a>
+                    <a class="btn btn-sm btn-primary" href="{% url "partner_doc_upload" doc.doc_type %}">Загрузить</a>
                 {% endif %}
             </li>
         {% endfor %}

+ 26 - 0
SharixAdmin/templates/SharixAdmin/partner/doc.html

@@ -0,0 +1,26 @@
+{% extends 'SharixAdmin/index.html' %}
+{% load i18n %}
+{% load static %}
+
+
+{% block contenthome %}
+<a class="display-block btn btn-sm btn-outline-secondary" href="{% url 'partner_detail' %}"><< Назад</a>
+
+<h5 class="my-3">Общая информация</h6>
+
+<ul>
+    <li><b>Статус:</b> {{ doc.status }}</li>
+    <li><b>Дата проверки:</b> {{ doc.check_date }}</li>
+    <li><b>Срок окончания действия документа:</b> {{ doc.expire_date }}</li>
+    <li><b>Компания:</b> {{ doc.company_id.legal_name }}</li>
+</ul>
+
+<h5 class="my-3">Файлы</h6>
+<ul>
+    {% for doc_file in doc_files %}
+        <li><a href='{{ doc_file.file.url }}' target='_blank'>{{ doc_file }}</a></li>
+    {% endfor %}
+</ul>  
+
+<a class="btn btn-primary" href="{% url "partner_doc_upload" doc.doc_type %}">Загрузить новые файлы</a>
+{% endblock contenthome %}

+ 30 - 6
SharixAdmin/templates/SharixAdmin/partner_doc.html → SharixAdmin/templates/SharixAdmin/partner/doc_upload.html

@@ -17,7 +17,7 @@
         {{ form.doc_expire_date }}
         
         {% if form.doc_expire_date.errors %}
-            <ul class="alert alert-danger list-style-none mt-2 p-2">
+            <ul class="alert alert-danger list-style-none mt-2 p-3">
                 {% for error in form.doc_expire_date.errors %}
                     <li>{{ error }}</li>
                 {% endfor %}
@@ -26,14 +26,14 @@
     </div>
 
     <div class="my-3 w-100">
-        <div class="drop-container">
+        <div class="drop-container" id="doc-drop-container">
             <span class="drop-title">Перетащите файлы сюда</span>
-            или выберите их на своем устройстве (только PDF, PNG, JPG, JPEG)
+            или выберите их на своем устройстве (только PDF, PNG, JPG или JPEG)
             {{ form.doc_file }}
         </div>
 
         {% if form.doc_file.errors %}
-            <ul class="alert alert-danger list-style-none mt-2 p-2">
+            <ul class="alert alert-danger list-style-none mt-2 p-3">
                 {% for error in form.doc_file.errors %}
                     <li>{{ error }}</li>
                 {% endfor %}
@@ -52,14 +52,38 @@
               <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
             </div>
             <div class="modal-body">
-              <p>Внесение изменений приведет к деактивации партнера до момента проверки новой информации!</p>
+              <p>Загрузка новых файлов документа полностью заменит все ранее загружаемые для него файлы, а также приведет к деактивации партнера до момента проверки новой информации!</p>
             </div>
             <div class="modal-footer">
               <button type="button" class="btn btn-primary" data-bs-dismiss="modal">Отменить</button>
-              <button type="sumbit" class="btn btn-outline-primary">Подтвердить</button>
+              <button type="sumbit" class="btn btn-outline-primary" data-bs-dismiss="modal">Подтвердить</button>
             </div>
           </div>
         </div>
     </div>
 </form>
+
+<script>
+    document.addEventListener("DOMContentLoaded", function() {
+        // Настройка работы поля для загрузки файлов #doc-file-input
+        const dropContainer = document.getElementById('doc-drop-container');
+        const fileInput = document.getElementById('doc-file-input');
+
+        // Открытие окна выбора файлов при клике на контейнер
+        dropContainer.addEventListener('click', function() {
+            fileInput.click();
+        });
+
+        // Отмена стандартного поведения при перетаскивании файлов
+        dropContainer.addEventListener('dragover', function(e) {
+            e.preventDefault();
+        });
+
+        // Обработка события перетаскивания файлов
+        dropContainer.addEventListener('drop', function(e) {
+            e.preventDefault();
+            fileInput.files = e.dataTransfer.files;
+        });
+    });
+</script>
 {% endblock contenthome %}

+ 2 - 1
SharixAdmin/urls.py

@@ -22,7 +22,8 @@ urlpatterns = [
     # Страница "О партнере"
     path('partner/', login_required(PartnerDetailView.as_view()), name='partner_detail'),
     path('partner/edit/', login_required(PartnerEditView.as_view()), name='partner_edit'),
-    path('partner/doc/<str:doc_code>', login_required(PartnerDocEditView.as_view()), name='partner_doc_edit'),
+    path('partner/doc/<str:doc_code>/upload', login_required(PartnerDocUploadView.as_view()), name='partner_doc_upload'),
+    path('partner/doc/<str:doc_code>', login_required(PartnerDocView.as_view()), name='partner_doc'),
 
 
     path('accounts/login/', LoginSharix.as_view(), name='authweb'),

+ 11 - 6
SharixAdmin/utils/ticket_gen.py

@@ -1,6 +1,7 @@
 from datetime import datetime, timedelta
 
 from tickets.models import Ticket, TicketList
+from dbsynce.models import DocumentFile
 
 
 def create_ticket_partner_activation(user, сompany):
@@ -31,22 +32,26 @@ def create_ticket_partner_activation(user, сompany):
     )
 
 
-def create_ticket_partner_docs_verification(user, company, doc_name, doc_code):
+def create_ticket_partner_docs_verification(user, company, doc):
     """
     Создание тикета на проверку документов партнера.
     
     Список: 2103, METASERVICE-ADMIN: Проверка документов (ST_REQUEST)
     Тип: 1, ST_REQUEST
     """
+    doc_name = doc.get_doc_type_display()
+    doc_files = DocumentFile.objects.filter(document=doc)
+
+    note=f"Пользователь {user} #{user.pk} добавил новые файлы документа <a href='{doc.get_admin_url()}'>{doc_name}</a> партнера <a href='{company.get_admin_url()}'>{company.legal_name}</a> требующие проверки:<ul>"
+    for file in doc_files:
+        note += f"<li><a href='{file.file.url}' target='_blank'>{file}</a></li>"
+    note += "</ul>"
+    
     return Ticket.objects.create(
         title=f"Проверка документа '{doc_name}' партнера '{company.legal_name}'",
         ticket_list=TicketList.objects.get(pk=2103),
         ticket_type=1,
         due_date=datetime.now().date() + timedelta(days=30),
         created_by=user,
-        
-        note=f"""
-            Пользователь {user} #{user.pk} добавил новые файлы документа '{doc_name}' партнера <a href="{company.get_admin_url()}">{company.legal_name}</a>
-            требующие проверки.
-        """
+        note=note
     )    

+ 72 - 12
SharixAdmin/views/partner.py

@@ -1,3 +1,4 @@
+from django.core.files.storage import default_storage
 from django.views.generic import DetailView
 from django.views.generic.edit import FormView
 from django.urls import reverse_lazy
@@ -7,7 +8,7 @@ from django.contrib.auth.mixins import UserPassesTestMixin
 from django.contrib import messages
 from django.db import transaction
 
-from dbsynce.models import Company, Documents
+from dbsynce.models import Company, Documents, DocumentFile
 from SharixAdmin.forms import CompanyForm, DocumentUploadForm
 from SharixAdmin.utils import *
 
@@ -73,27 +74,86 @@ class PartnerEditView(PartnerBaseView, FormView):
         return super().form_valid(form)
 
 
-class PartnerDocEditView(PartnerBaseView, FormView):       
-    template_name = 'SharixAdmin/partner_doc.html'
+class PartnerDocUploadView(PartnerBaseView, FormView):
+    # FIXME: Загрузка новых документов должна деактивировать текущего партнера (предварительно это работа обработчиков)
+    template_name = 'SharixAdmin/partner/doc_upload.html'
     form_class = DocumentUploadForm
     success_url = reverse_lazy('partner_detail')
 
     def dispatch(self, request, *args, **kwargs):
-        self.doc_code = kwargs.get("doc_code")
-        self.doc_name = Documents.DOC_TYPES_DICT[self.doc_code]
-        self.page_title = _("Изменение документа партнера: ") + self.doc_name
         self.company = get_object_or_404(Company, repr_id=self.request.user)
+        self.doc = Documents.objects.filter(
+            user_id=self.request.user,
+            company_id=self.company,
+            doc_type=kwargs.get('doc_code')
+        ).first()
+
+        self.doc_name = self.doc.get_doc_type_display()
+        self.page_title = _("Изменение документа партнера: ") + self.doc_name
+
         return super().dispatch(request, *args, **kwargs)
 
     def form_valid(self, form):
         with transaction.atomic():
+            # Удаляем существующие файлы и записи из базы данных
+            existing_files = DocumentFile.objects.filter(document=self.doc)
+            for existing_file in existing_files:
+                # Удаляем файл с сервера
+                if default_storage.exists(existing_file.file.path):
+                    default_storage.delete(existing_file.file.path)
+
+                # Удаляем запись о файле из базы данных
+                existing_file.delete()
+
+            # Загрузка новых файлов
             doc_file = self.request.FILES.getlist("doc_file")
-            
             for file in doc_file:
-                print(file)
-
-            create_ticket_partner_docs_verification(self.request.user, self.company, self.doc_name, self.doc_code)
+                DocumentFile.objects.create(
+                    document=self.doc,
+                    file=file
+                )
+
+            # Создание нового тикета и архивация старого
+            if self.doc.ticket_status:
+                self.doc.ticket_status.archive()
+            
+            self.doc.expire_date = self.request.POST.get('doc_expire_date')
+            self.doc.ticket_status = create_ticket_partner_docs_verification(self.request.user, self.company, self.doc)
+            
+            self.doc.save()
 
-        # Отправляем полAьзователю уведомление на страницу об успехе операции
+        # Отправляем пользователю уведомление на страницу об успехе операции
         messages.success(self.request, f'Файлы документа "{self.doc_name}" успешно загружены и теперь проходят проверку!')
-        return super().form_valid(form)
+        return super().form_valid(form)
+
+
+class PartnerDocView(PartnerBaseView, DetailView):
+    model = Documents
+    template_name = 'SharixAdmin/partner/doc.html'
+    context_object_name = 'doc'
+
+    def dispatch(self, request, *args, **kwargs):
+        self.company = get_object_or_404(Company, repr_id=self.request.user)
+        
+        self.doc = Documents.objects.filter(
+            user_id=self.request.user,
+            company_id=self.company,
+            doc_type=kwargs.get('doc_code')
+        ).first()
+
+        if not self.doc:
+            return self.handle_no_permission()
+
+        self.doc_name = self.doc.get_doc_type_display()
+        self.page_title = _("Детали документа: ") + self.doc_name
+
+        return super().dispatch(request, *args, **kwargs)
+
+    def get_object(self, queryset=None):
+        return self.doc
+
+    def get_context_data(self, **kwargs):
+        context = super().get_context_data(**kwargs)
+        doc_files = DocumentFile.objects.filter(document=self.doc)
+        context.update({ "doc_files": doc_files }) 
+        return context

+ 1 - 0
core/settings.py

@@ -54,6 +54,7 @@ INSTALLED_APPS = [
     'debug_toolbar',
     'ckeditor',
     'landing.apps.LandingConfig',
+    'fontawesomefree',
 ]
 
 MIDDLEWARE = [

+ 1 - 0
requirements.txt

@@ -56,3 +56,4 @@ webencodings==0.5.1
 xmpppy==0.7.1
 django-ckeditor
 pillow
+fontawesomefree