From 6c3fa4088f755920a553c6d0595900c6c9f2f18f Mon Sep 17 00:00:00 2001 From: sks444 <krishnasingh.ss30@gmail.com> Date: Wed, 30 Jun 2021 17:08:03 +0530 Subject: [PATCH] Add vendor detail page --- ..._vendor_setup_field_to_project_settings.py | 18 +++ .../0040_add_created_and_updated_at.py | 25 ++++ hypha/apply/projects/models/project.py | 1 + hypha/apply/projects/models/vendor.py | 3 +- .../includes/supporting_documents.html | 22 +-- .../application_projects/vendor_detail.html | 47 +++++++ hypha/apply/projects/urls.py | 4 + hypha/apply/projects/views/__init__.py | 4 +- hypha/apply/projects/views/vendor.py | 127 +++++++++++++++++- 9 files changed, 235 insertions(+), 16 deletions(-) create mode 100644 hypha/apply/projects/migrations/0039_add_required_vendor_setup_field_to_project_settings.py create mode 100644 hypha/apply/projects/migrations/0040_add_created_and_updated_at.py create mode 100644 hypha/apply/projects/templates/application_projects/vendor_detail.html diff --git a/hypha/apply/projects/migrations/0039_add_required_vendor_setup_field_to_project_settings.py b/hypha/apply/projects/migrations/0039_add_required_vendor_setup_field_to_project_settings.py new file mode 100644 index 000000000..dadb12e37 --- /dev/null +++ b/hypha/apply/projects/migrations/0039_add_required_vendor_setup_field_to_project_settings.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.24 on 2021-06-22 10:43 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('application_projects', '0038_rename_verbose_name_of_vendor_form_settings'), + ] + + operations = [ + migrations.AddField( + model_name='projectsettings', + name='vendor_setup_required', + field=models.BooleanField(default=True), + ), + ] diff --git a/hypha/apply/projects/migrations/0040_add_created_and_updated_at.py b/hypha/apply/projects/migrations/0040_add_created_and_updated_at.py new file mode 100644 index 000000000..557cb6f60 --- /dev/null +++ b/hypha/apply/projects/migrations/0040_add_created_and_updated_at.py @@ -0,0 +1,25 @@ +# Generated by Django 2.2.24 on 2021-06-30 04:18 + +from django.db import migrations, models +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + ('application_projects', '0039_add_required_vendor_setup_field_to_project_settings'), + ] + + operations = [ + migrations.AddField( + model_name='vendor', + name='created_at', + field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now, verbose_name='Creation time'), + preserve_default=False, + ), + migrations.AddField( + model_name='vendor', + name='updated_at', + field=models.DateTimeField(auto_now=True, verbose_name='Update time'), + ), + ] diff --git a/hypha/apply/projects/models/project.py b/hypha/apply/projects/models/project.py index 2ff288edf..5bd291fb2 100644 --- a/hypha/apply/projects/models/project.py +++ b/hypha/apply/projects/models/project.py @@ -372,6 +372,7 @@ class ProjectApprovalForm(BaseStreamForm, models.Model): @register_setting class ProjectSettings(BaseSetting): compliance_email = models.TextField("Compliance Email") + vendor_setup_required = models.BooleanField(default=True) class Approval(models.Model): diff --git a/hypha/apply/projects/models/vendor.py b/hypha/apply/projects/models/vendor.py index 887747d1d..e3b84e27f 100644 --- a/hypha/apply/projects/models/vendor.py +++ b/hypha/apply/projects/models/vendor.py @@ -45,7 +45,8 @@ class Vendor(models.Model): ('organization', _('Yes, the account belongs to the organisation above')), ('personal', _('No, it is a personal bank account')), ] - + created_at = models.DateTimeField(verbose_name=_('Creation time'), auto_now_add=True) + updated_at = models.DateTimeField(verbose_name=_('Update time'), auto_now=True) user = models.OneToOneField( settings.AUTH_USER_MODEL, on_delete=models.PROTECT, related_name='vendor' diff --git a/hypha/apply/projects/templates/application_projects/includes/supporting_documents.html b/hypha/apply/projects/templates/application_projects/includes/supporting_documents.html index 089df603a..2c058cae8 100644 --- a/hypha/apply/projects/templates/application_projects/includes/supporting_documents.html +++ b/hypha/apply/projects/templates/application_projects/includes/supporting_documents.html @@ -19,16 +19,18 @@ {% endif %} </div> </li> - - <li class="docs-block__row"> - <div class="docs-block__row-inner"> - <svg class="icon docs-block__icon"><use xlink:href="#tick"></use></svg> - <p class="docs-block__title">Contractor Setup Form</p> - </div> - <div class="docs-block__row-inner"> - <a class="docs-block__link" href="{% url 'apply:projects:vendor' pk=project.pk %}">Update info</a> - </div> - </li> + {% if settings.application_projects.ProjectSettings.vendor_setup_required and project.vendor %} + <li class="docs-block__row"> + <div class="docs-block__row-inner"> + <svg class="icon docs-block__icon"><use xlink:href="#tick"></use></svg> + <p class="docs-block__title">Contractor Setup Form</p> + </div> + <div class="docs-block__row-inner"> + <a class="docs-block__link" href="{% url 'apply:projects:vendor-detail' pk=project.pk vendor_pk=project.vendor.pk %}">View</a> + <a class="docs-block__link" href="{% url 'apply:projects:vendor' pk=project.pk %}">Update info</a> + </div> + </li> + {% endif %} <li class="docs-block__row"> <div class="docs-block__row-inner"> diff --git a/hypha/apply/projects/templates/application_projects/vendor_detail.html b/hypha/apply/projects/templates/application_projects/vendor_detail.html new file mode 100644 index 000000000..417e4ce8b --- /dev/null +++ b/hypha/apply/projects/templates/application_projects/vendor_detail.html @@ -0,0 +1,47 @@ +{% extends "base-apply.html" %} +{% load bleach_tags %} + +{% block title %}Vendor Info for {{ project.title }} {% endblock %} + +{% block content %} +<div class="admin-bar"> + <div class="admin-bar__inner"> + <h2 class="heading heading--no-margin">Vendor Information for {{ project.title }}</h2> + </div> +</div> + +<div class="grid"> + <div> + <h5 class="vendor-info">Last Updated: {{ vendor.updated_at|date:'F d, Y' }}</h5> + </div> + <div> + <a class="link link--edit-vendor is-active" href="{% url 'apply:projects:vendor' pk=project.pk %}"> + Edit + <svg class="icon icon--pen"><use xlink:href="#pen"></use></svg> + </a> + </div> +</div> + +<div class="rich-text rich-text--answers"> + {% for group in vendor_detailed_response.values %} + {% if group.title %} + <h1>{{ group.title|bleach }}</h4> + {% endif %} + {% for question, answer in group.questions %} + <h5>{{ question }}</h5> + {% if question == 'Due Diligence Documents' %} + <div class="card card--solid"> + <div class="card__inner"> + <!-- <h5 class="card__heading">Reciepts</h5> --> + {% for document in due_diligence_documents %} + <p class="card__text"><a href="{% url "apply:projects:vendor-documents" pk=project.pk vendor_pk=project.vendor.pk file_pk=document.pk %}">{{ document.document.name }}</a></p> + {% endfor %} + </div> + </div> + {% else %} + <p>{% if answer %}{% if answer == True %}{{ answer|yesno:"Yes,No" }}{% else %}{{ answer|bleach }}{% endif %}{% else %}-{% endif %}</p> + {% endif %} + {% endfor %} + {% endfor %} +</div> +{% endblock %} diff --git a/hypha/apply/projects/urls.py b/hypha/apply/projects/urls.py index 9eee94fcb..35b4a9ecc 100644 --- a/hypha/apply/projects/urls.py +++ b/hypha/apply/projects/urls.py @@ -21,6 +21,8 @@ from .views import ( ReportPrivateMedia, ReportSkipView, ReportUpdateView, + VendorDetailView, + VendorPrivateMediaView, ) app_name = 'projects' @@ -37,6 +39,8 @@ urlpatterns = [ path('simplified/', ProjectDetailSimplifiedView.as_view(), name='simplified'), path('request/', CreatePaymentRequestView.as_view(), name='request'), path('vendor/', CreateVendorView.as_view(), name='vendor'), + path('vendor/<int:vendor_pk>/', VendorDetailView.as_view(), name='vendor-detail'), + path('vendor/<int:vendor_pk>/documents/<int:file_pk>/', VendorPrivateMediaView.as_view(), name='vendor-documents'), ])), path('payment-requests/', include(([ path('', PaymentRequestListView.as_view(), name='all'), diff --git a/hypha/apply/projects/views/__init__.py b/hypha/apply/projects/views/__init__.py index c08899889..61a184d6d 100644 --- a/hypha/apply/projects/views/__init__.py +++ b/hypha/apply/projects/views/__init__.py @@ -41,7 +41,7 @@ from .report import ( ReportSkipView, ReportUpdateView, ) -from .vendor import CreateVendorView +from .vendor import CreateVendorView, VendorDetailView, VendorPrivateMediaView __all__ = [ 'ChangePaymentRequestStatusView', @@ -82,4 +82,6 @@ __all__ = [ 'ReportFrequencyUpdate', 'ReportListView', 'CreateVendorView', + 'VendorDetailView', + 'VendorPrivateMediaView', ] diff --git a/hypha/apply/projects/views/vendor.py b/hypha/apply/projects/views/vendor.py index dd98fd1d2..d6923f7e5 100644 --- a/hypha/apply/projects/views/vendor.py +++ b/hypha/apply/projects/views/vendor.py @@ -1,12 +1,20 @@ +from django.contrib.auth.models import User +from hypha.apply.projects.models.vendor import VendorFormSettings import json - +from django.contrib.auth.decorators import login_required +from django.contrib.auth.mixins import UserPassesTestMixin +from django.views.generic.detail import DetailView +from django.utils import timezone +from django.http import Http404 from django.core.exceptions import PermissionDenied +from django.utils.decorators import method_decorator from django.db.models.fields.files import FieldFile from django.shortcuts import get_object_or_404, render from formtools.wizard.views import SessionWizardView from wagtail.core.models import Site - +from addressfield.fields import ADDRESS_FIELDS_ORDER from hypha.apply.utils.storage import PrivateStorage +from hypha.apply.utils.storage import PrivateMediaView from ..forms import ( CreateVendorFormStep1, @@ -16,7 +24,7 @@ from ..forms import ( CreateVendorFormStep5, CreateVendorFormStep6, ) -from ..models import BankInformation, DueDiligenceDocument, Project +from ..models import BankInformation, DueDiligenceDocument, Project, ProjectSettings, Vendor def show_extra_info_form(wizard): @@ -28,13 +36,16 @@ def show_extra_info_form(wizard): class VendorAccessMixin: def dispatch(self, request, *args, **kwargs): + project_settings = ProjectSettings.for_request(request) + if not project_settings.vendor_setup_required: + raise PermissionDenied is_admin = request.user.is_apply_staff project = self.get_project() is_owner = request.user == project.user if not (is_owner or is_admin): raise PermissionDenied if not project.vendor: - raise PermissionDenied + raise Http404 return super().dispatch(request, *args, **kwargs) @@ -109,6 +120,7 @@ class CreateVendorView(VendorAccessMixin, SessionWizardView): vendor.contractor_name = cleaned_data['contractor_name'] vendor.type = cleaned_data['type'] vendor.required_to_pay_taxes = cleaned_data['required_to_pay_taxes'] + vendor.updated_at = timezone.now() vendor.save() not_deleted_original_filenames = [ @@ -176,3 +188,110 @@ class CreateVendorView(VendorAccessMixin, SessionWizardView): kwargs = super(CreateVendorView, self).get_form_kwargs(step) kwargs['site'] = Site.find_for_request(self.request) return kwargs + + +class VendorDetailView(VendorAccessMixin, DetailView): + model = Vendor + template_name = 'application_projects/vendor_detail.html' + + def get_object(self, queryset=None): + return get_object_or_404(self.model, id=self.kwargs['vendor_pk']) + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context['vendor_detailed_response'] = self.get_detailed_response() + context['project'] = self.get_project() + vendor = self.get_object() + context['due_diligence_documents'] = vendor.due_diligence_documents.all() + return context + + def get_project(self): + return get_object_or_404(Project, pk=self.kwargs['pk']) + + def get_detailed_response(self): + vendor = self.get_object() + vendor_form_settings = VendorFormSettings.for_request(self.request) + data = {} + group = 0 + data.setdefault(group, {'title': 'Vendor Information', 'questions': list()}) + data[group]['questions'] = [ + (getattr(vendor_form_settings, 'name_label'), vendor.name), + (getattr(vendor_form_settings, 'contractor_name_label'), vendor.contractor_name), + (getattr(vendor_form_settings, 'type_label'), vendor.type), + (getattr(vendor_form_settings, 'required_to_pay_taxes_label'), vendor.required_to_pay_taxes), + ('Due Diligence Documents', ''), + ] + group = group + 1 + data.setdefault(group, {'title': 'Bank Account Information', 'questions': list()}) + bank_info = vendor.bank_info + data[group]['questions'] = [ + (getattr(vendor_form_settings, 'account_holder_name_label'), bank_info.account_holder_name if bank_info else ''), + (getattr(vendor_form_settings, 'account_routing_number_label'), bank_info.account_routing_number if bank_info else ''), + (getattr(vendor_form_settings, 'account_number_label'), bank_info.account_number if bank_info else ''), + (getattr(vendor_form_settings, 'account_currency_label'), bank_info.account_currency if bank_info else ''), + ] + group = group + 1 + data.setdefault(group, {'title': '(Optional) Extra Information for Accepting Payments', 'questions': list()}) + data[group]['questions'] = [ + (getattr(vendor_form_settings, 'branch_address_label'), self.get_address_display(bank_info.branch_address) if bank_info else ''), + ] + group = group + 1 + data.setdefault(group, {'title': 'Intermediary Bank Account Information', 'questions': list()}) + iba_info = bank_info.iba_info if bank_info else None + data[group]['questions'] = [ + (getattr(vendor_form_settings, 'ib_account_routing_number_label'), iba_info.account_routing_number if iba_info else ''), + (getattr(vendor_form_settings, 'ib_account_number_label'), iba_info.account_number if iba_info else ''), + (getattr(vendor_form_settings, 'ib_account_currency_label'), iba_info.account_currency if iba_info else ''), + (getattr(vendor_form_settings, 'ib_branch_address_label'), self.get_address_display(iba_info.branch_address) if iba_info else ''), + ] + group = group + 1 + data.setdefault(group, {'title': 'Account Holder National Identity Document Information', 'questions': list()}) + data[group]['questions'] = [ + (getattr(vendor_form_settings, 'nid_type_label'), bank_info.nid_type if bank_info else ''), + (getattr(vendor_form_settings, 'nid_number_label'), bank_info.nid_number if bank_info else ''), + ] + group = group + 1 + data.setdefault(group, {'title': None, 'questions': list()}) + data[group]['questions'] = [ + (getattr(vendor_form_settings, 'other_info_label'), vendor.other_info), + ] + return data + + def get_address_display(self, address): + try: + address = json.loads(address) + except (json.JSONDecodeError, AttributeError): + return '' + else: + return ', '.join( + address.get(field) + for field in ADDRESS_FIELDS_ORDER + if address.get(field) + ) + + +@method_decorator(login_required, name='dispatch') +class VendorPrivateMediaView(UserPassesTestMixin, PrivateMediaView): + raise_exception = True + + def dispatch(self, *args, **kwargs): + pk = self.kwargs['pk'] + vendor_pk = self.kwargs['vendor_pk'] + self.vendor = get_object_or_404(Vendor, pk=vendor_pk) + self.project = get_object_or_404(Project, pk=pk) + + return super().dispatch(*args, **kwargs) + + def get_media(self, *args, **kwargs): + file_pk = kwargs.get('file_pk') + document = get_object_or_404(self.vendor.due_diligence_documents, pk=file_pk) + return document.document + + def test_func(self): + if self.request.user.is_apply_staff: + return True + + if self.request.user == self.project.user: + return True + + return False -- GitLab