コード例 #1
0
ファイル: models.py プロジェクト: unicef/unicef-restlib
class Author(models.Model):
    first_name = models.CharField(max_length=150)
    last_name = models.CharField(max_length=150)
    profile_images = CodedGenericRelation(Image, code="author_profile_image")
    full_images = CodedGenericRelation(Image, code="author_full_image")
    activities = GenericRelation(Activity)
    active = models.BooleanField(default=True)

    def __str__(self):
        return "{} {}".format(self.first_name, self.last_name)
コード例 #2
0
ファイル: models.py プロジェクト: unicef/unicef-djangolib
class Book(models.Model):
    name = models.CharField(max_length=150)
    author = models.ForeignKey(Author, on_delete=models.CASCADE)
    cover_image = CodedGenericRelation(
        Image,
        code="book_cover_image",
        null=True,
        blank=True,
    )
    image_1 = CodedGenericRelation(
        Image,
        code="book_image_1",
        null=True,
        blank=True,
    )
コード例 #3
0
ファイル: models.py プロジェクト: unicef/unicef-djangolib
class Author(models.Model):
    first_name = models.CharField(max_length=150)
    last_name = models.CharField(max_length=150)
    profile_image = CodedGenericRelation(
        Image,
        code="author_profile_image",
        null=True,
        blank=True,
    )
コード例 #4
0
ファイル: models.py プロジェクト: azizur77/etools
class SpecialAudit(Engagement):
    final_report = CodedGenericRelation(
        Attachment,
        verbose_name=_('Special Audit Final Report'),
        code='special_audit_final_report',
        blank=True,
    )

    objects = models.Manager()

    class Meta:
        ordering = ('id', )
        verbose_name = _('Special Audit')
        verbose_name_plural = _('Special Audits')

    def save(self, *args, **kwargs):
        self.engagement_type = Engagement.TYPES.sa
        return super().save(*args, **kwargs)

    @transition('status',
                source=Engagement.STATUSES.partner_contacted,
                target=Engagement.STATUSES.report_submitted,
                conditions=[
                    EngagementSubmitReportRequiredFieldsCheck.as_condition(),
                    SpecialAuditSubmitRelatedModelsCheck.as_condition(),
                    EngagementHasReportAttachmentsCheck.as_condition(),
                ],
                permission=has_action_permission(action='submit'))
    def submit(self, *args, **kwargs):
        return super().submit(*args, **kwargs)

    @transition('status',
                source=Engagement.STATUSES.report_submitted,
                target=Engagement.STATUSES.final,
                permission=has_action_permission(action='finalize'))
    def finalize(self, *args, **kwargs):
        self.partner.audits_completed(update_one=True)
        return super().finalize(*args, **kwargs)

    def get_object_url(self, **kwargs):
        return build_frontend_url('ap', 'special-audits', self.id, 'overview',
                                  **kwargs)

    def generate_final_report(self):
        from etools.applications.audit.serializers.engagement import SpecialAuditSerializer
        from etools.applications.audit.serializers.export import SpecialAuditPDFSerializer
        generate_final_report(
            self,
            'special_audit_final_report',
            SpecialAuditSerializer,
            SpecialAuditPDFSerializer,
            'audit/special_audit_pdf.html',
            'special_audit_final_report.pdf',
        )
コード例 #5
0
class Author(models.Model):
    first_name = models.CharField(max_length=150)
    last_name = models.CharField(max_length=150)
    image = models.FileField(null=True, blank=True)
    profile_image = CodedGenericRelation(
        Attachment,
        verbose_name='Profile Image',
        code='author_profile_image',
        blank=True,
        null=True,
    )

    def __str__(self):
        return "{} {}".format(self.first_name, self.last_name)
コード例 #6
0
ファイル: models.py プロジェクト: azizur77/etools
class MicroAssessment(Engagement):
    final_report = CodedGenericRelation(
        Attachment,
        verbose_name=_('Micro Assessment Final Report'),
        code='micro_assessment_final_report',
        blank=True,
    )

    objects = models.Manager()

    class Meta:
        ordering = ('id', )
        verbose_name = _('Micro Assessment')
        verbose_name_plural = _('Micro Assessments')

    def save(self, *args, **kwargs):
        self.engagement_type = Engagement.TYPES.ma
        return super().save(*args, **kwargs)

    @transition('status',
                source=Engagement.STATUSES.partner_contacted,
                target=Engagement.STATUSES.report_submitted,
                conditions=[
                    EngagementSubmitReportRequiredFieldsCheck.as_condition(),
                    ValidateMARiskCategories.as_condition(),
                    ValidateMARiskExtra.as_condition(),
                    EngagementHasReportAttachmentsCheck.as_condition(),
                ],
                permission=has_action_permission(action='submit'))
    def submit(self, *args, **kwargs):
        return super().submit(*args, **kwargs)

    def get_object_url(self, **kwargs):
        return build_frontend_url('ap', 'micro-assessments', self.id,
                                  'overview', **kwargs)

    def generate_final_report(self):
        from etools.applications.audit.serializers.engagement import MicroAssessmentSerializer
        from etools.applications.audit.serializers.export import MicroAssessmentPDFSerializer
        generate_final_report(
            self,
            'micro_assessment_final_report',
            MicroAssessmentSerializer,
            MicroAssessmentPDFSerializer,
            'audit/microassessment_pdf.html',
            'microassessment_final_report.pdf',
        )
コード例 #7
0
ファイル: models.py プロジェクト: azizur77/etools
class TravelAttachment(models.Model):
    travel = models.ForeignKey(
        'Travel',
        related_name='attachments',
        verbose_name=_('Travel'),
        on_delete=models.CASCADE,
    )
    type = models.CharField(max_length=64, verbose_name=_('Type'))

    name = models.CharField(max_length=255, verbose_name=_('Name'))
    file = models.FileField(
        upload_to=determine_file_upload_path,
        max_length=255,
        verbose_name=_('File'),
        blank=True,
        null=True,
    )
    attachment = CodedGenericRelation(
        Attachment,
        verbose_name=_('Travel File'),
        blank=True,
        null=True,
        code='t2f_travel_attachment',
    )
コード例 #8
0
class TPMActivity(Activity):
    tpm_visit = models.ForeignKey(
        TPMVisit,
        verbose_name=_('Visit'),
        related_name='tpm_activities',
        on_delete=models.CASCADE,
    )

    unicef_focal_points = models.ManyToManyField(
        settings.AUTH_USER_MODEL,
        verbose_name=_('UNICEF Focal Points'),
        related_name='+',
        blank=True)

    offices = models.ManyToManyField(
        'users.Office',
        related_name='+',
        blank=True,
        verbose_name=_('Office(s) of UNICEF Focal Point(s)'))

    section = models.ForeignKey(
        'reports.Sector',
        related_name='tpm_activities',
        verbose_name=_('Section'),
        on_delete=models.CASCADE,
    )

    additional_information = models.TextField(
        verbose_name=_('Additional Information'), blank=True)

    attachments = CodedGenericRelation(Attachment,
                                       verbose_name=_('Activity Attachments'),
                                       code='activity_attachments',
                                       blank=True)
    report_attachments = CodedGenericRelation(
        Attachment,
        verbose_name=_('Activity Report'),
        code='activity_report',
        blank=True)

    is_pv = models.BooleanField(default=False,
                                verbose_name=_('HACT Programmatic Visit'))

    objects = models.Manager()

    class Meta:
        verbose_name_plural = _('TPM Activities')
        ordering = [
            'tpm_visit',
            'id',
        ]

    def __str__(self):
        return 'Task #{0} for {1}'.format(self.id, self.tpm_visit)

    @cached_property
    def task_number(self):
        return list(self.tpm_visit.tpm_activities.values_list(
            'id', flat=True)).index(self.id) + 1

    @property
    def reference_number(self):
        return self.tpm_visit.reference_number

    def get_object_url(self):
        return self.tpm_visit.get_object_url()

    @property
    def related_reports(self):
        return Attachment.objects.filter(
            models.Q(object_id=self.tpm_visit_id,
                     content_type__app_label=TPMVisit._meta.app_label,
                     content_type__model=TPMVisit._meta.model_name,
                     file_type__name='overall_report')
            | models.Q(object_id=self.id,
                       content_type__app_label=TPMActivity._meta.app_label,
                       content_type__model=TPMActivity._meta.model_name,
                       file_type__name='report'))

    @property
    def pv_applicable(self):
        return self.related_reports.exists()

    def get_mail_context(self, user=None, include_visit=True):
        context = {
            'locations': ', '.join(map(force_text, self.locations.all())),
            'intervention':
            self.intervention.title if self.intervention else '-',
            'cp_output': force_text(self.cp_output) if self.cp_output else '-',
            'section': force_text(self.section) if self.section else '-',
            'partner': self.partner.name if self.partner else '-',
        }
        if include_visit:
            context['tpm_visit'] = self.tpm_visit.get_mail_context(
                user=user, include_activities=False)

        return context
コード例 #9
0
class TPMVisit(SoftDeleteMixin, TimeStampedModel, models.Model):

    DRAFT = 'draft'
    ASSIGNED = 'assigned'
    CANCELLED = 'cancelled'
    ACCEPTED = 'tpm_accepted'
    REJECTED = 'tpm_rejected'
    REPORTED = 'tpm_reported'
    REPORT_REJECTED = 'tpm_report_rejected'
    UNICEF_APPROVED = 'unicef_approved'

    STATUSES = Choices(
        (DRAFT, _('Draft')),
        (ASSIGNED, _('Assigned')),
        (CANCELLED, _('Cancelled')),
        (ACCEPTED, _('TPM Accepted')),
        (REJECTED, _('TPM Rejected')),
        (REPORTED, _('TPM Reported')),
        (REPORT_REJECTED, _('Sent Back to TPM')),
        (UNICEF_APPROVED, _('UNICEF Approved')),
    )

    STATUSES_DATES = {
        STATUSES.draft: 'date_created',
        STATUSES.assigned: 'date_of_assigned',
        STATUSES.cancelled: 'date_of_cancelled',
        STATUSES.tpm_accepted: 'date_of_tpm_accepted',
        STATUSES.tpm_rejected: 'date_of_tpm_rejected',
        STATUSES.tpm_reported: 'date_of_tpm_reported',
        STATUSES.tpm_report_rejected: 'date_of_tpm_report_rejected',
        STATUSES.unicef_approved: 'date_of_unicef_approved',
    }

    author = models.ForeignKey(settings.AUTH_USER_MODEL,
                               on_delete=models.SET_NULL,
                               blank=True,
                               null=True)

    tpm_partner = models.ForeignKey(
        TPMPartner,
        verbose_name=_('TPM Vendor'),
        null=True,
        on_delete=models.CASCADE,
    )

    status = FSMField(verbose_name=_('Status'),
                      max_length=20,
                      choices=STATUSES,
                      default=STATUSES.draft,
                      protected=True)

    # UNICEF cancelled visit
    cancel_comment = models.TextField(verbose_name=_('Cancel Comment'),
                                      blank=True)
    # TPM rejected visit
    reject_comment = models.TextField(verbose_name=_('Reason for Rejection'),
                                      blank=True)
    approval_comment = models.TextField(verbose_name=_('Approval Comments'),
                                        blank=True)

    report_attachments = CodedGenericRelation(Attachment,
                                              verbose_name=_('Visit Report'),
                                              code='visit_report_attachments',
                                              blank=True)
    attachments = CodedGenericRelation(Attachment,
                                       verbose_name=_('Activity Attachments'),
                                       code='visit_attachments',
                                       blank=True)

    visit_information = models.TextField(verbose_name=_('Visit Information'),
                                         blank=True)

    date_of_assigned = models.DateField(blank=True,
                                        null=True,
                                        verbose_name=_('Date of Assigned'))
    date_of_cancelled = models.DateField(blank=True,
                                         null=True,
                                         verbose_name=_('Date of Cancelled'))
    date_of_tpm_accepted = models.DateField(
        blank=True, null=True, verbose_name=_('Date of TPM Accepted'))
    date_of_tpm_rejected = models.DateField(
        blank=True, null=True, verbose_name=_('Date of TPM Rejected'))
    date_of_tpm_reported = models.DateField(
        blank=True, null=True, verbose_name=_('Date of TPM Reported'))
    date_of_tpm_report_rejected = models.DateField(
        blank=True, null=True, verbose_name=_('Date of Sent Back to TPM'))
    date_of_unicef_approved = models.DateField(
        blank=True, null=True, verbose_name=_('Date of UNICEF Approved'))

    tpm_partner_focal_points = models.ManyToManyField(
        TPMPartnerStaffMember,
        verbose_name=_('TPM Focal Points'),
        related_name='tpm_visits',
        blank=True)

    tpm_partner_tracker = FieldTracker(fields=[
        'tpm_partner',
    ])

    class Meta:
        ordering = ('id', )
        verbose_name = _('TPM Visit')
        verbose_name_plural = _('TPM Visits')

    @property
    def date_created(self):
        return self.created.date()

    @property
    def status_date(self):
        return getattr(self, self.STATUSES_DATES[self.status])

    @property
    def reference_number(self):
        return '{}/{}/{}/TPM'.format(
            connection.tenant.country_short_code or '',
            self.created.year,
            self.id,
        )

    @property
    def start_date(self):
        # TODO: Rewrite to reduce number of SQL queries.
        return self.tpm_activities.aggregate(models.Min('date'))['date__min']

    @property
    def end_date(self):
        # TODO: Rewrite to reduce number of SQL queries.
        return self.tpm_activities.aggregate(models.Max('date'))['date__max']

    @property
    def unicef_focal_points(self):
        return set(
            itertools.chain(*map(lambda a: a.unicef_focal_points.all(),
                                 self.tpm_activities.all())))

    @property
    def unicef_focal_points_with_emails(self):
        return list(
            filter(lambda u: u.email and u.is_active,
                   self.unicef_focal_points))

    @property
    def unicef_focal_points_and_pme(self):
        users = self.unicef_focal_points_with_emails
        if self.author and self.author.is_active and self.author.email:
            users += [self.author]

        return users

    def __str__(self):
        return 'Visit ({} to {} at {} - {})'.format(
            self.tpm_partner, ', '.join(
                filter(
                    lambda x: x,
                    self.tpm_activities.values_list('partner__name',
                                                    flat=True))),
            self.start_date, self.end_date)

    def get_mail_context(self, user=None, include_activities=True):
        object_url = self.get_object_url(user=user)

        activities = self.tpm_activities.all()
        interventions = set(a.intervention.title for a in activities
                            if a.intervention)
        partner_names = set(a.partner.name for a in activities)
        context = {
            'reference_number': self.reference_number,
            'tpm_partner': self.tpm_partner.name if self.tpm_partner else '-',
            'multiple_tpm_activities': activities.count() > 1,
            'object_url': object_url,
            'partners': ', '.join(partner_names),
            'interventions': ', '.join(interventions),
        }

        if include_activities:
            context['tpm_activities'] = [
                a.get_mail_context(user=user, include_visit=False)
                for a in activities
            ]

        return context

    def _send_email(self,
                    recipients,
                    template_name,
                    context=None,
                    user=None,
                    **kwargs):
        context = context or {}

        base_context = {
            'visit': self.get_mail_context(user=user),
            'environment': get_environment(),
        }
        base_context.update(context)
        context = base_context

        if isinstance(recipients, str):
            recipients = [
                recipients,
            ]
        else:
            recipients = list(recipients)

        # assert recipients
        if recipients:
            send_notification_with_template(
                recipients=recipients,
                template_name=template_name,
                context=context,
            )

    def _get_unicef_focal_points_as_email_recipients(self):
        return list(
            map(lambda u: u.email, self.unicef_focal_points_with_emails))

    def _get_unicef_focal_points_and_pme_as_email_recipients(self):
        return list(map(lambda u: u.email, self.unicef_focal_points_and_pme))

    def _get_tpm_focal_points_as_email_recipients(self):
        return list(
            self.tpm_partner_focal_points.filter(
                user__email__isnull=False,
                user__is_active=True).values_list('user__email', flat=True))

    @transition(
        status,
        source=[STATUSES.draft, STATUSES.tpm_rejected],
        target=STATUSES.assigned,
        conditions=[
            TPMVisitAssignRequiredFieldsCheck.as_condition(),
            ValidateTPMVisitActivities.as_condition(),
        ],
        permission=has_action_permission(action='assign'),
        custom={
            'name':
            lambda obj: _('Re-assign')
            if obj.status == TPMVisit.STATUSES.tpm_rejected else _('Assign')
        })
    def assign(self):
        self.date_of_assigned = timezone.now()

        if self.tpm_partner.email:
            self._send_email(
                self.tpm_partner.email,
                'tpm/visit/assign',
                cc=self._get_unicef_focal_points_and_pme_as_email_recipients())

        for staff_member in self.tpm_partner_focal_points.filter(
                user__email__isnull=False, user__is_active=True):
            self._send_email(
                staff_member.user.email,
                'tpm/visit/assign_staff_member',
                context={'recipient': staff_member.user.get_full_name()},
                user=staff_member.user)

    @transition(status,
                source=[
                    STATUSES.draft,
                    STATUSES.assigned,
                    STATUSES.tpm_accepted,
                    STATUSES.tpm_rejected,
                    STATUSES.tpm_reported,
                    STATUSES.tpm_report_rejected,
                ],
                target=STATUSES.cancelled,
                permission=has_action_permission(action='cancel'),
                custom={
                    'serializer': TPMVisitCancelSerializer,
                    'name': _('Cancel Visit')
                })
    def cancel(self, cancel_comment):
        self.cancel_comment = cancel_comment
        self.date_of_cancelled = timezone.now()

    @transition(status,
                source=[STATUSES.assigned],
                target=STATUSES.tpm_rejected,
                permission=has_action_permission(action='reject'),
                custom={'serializer': TPMVisitRejectSerializer})
    def reject(self, reject_comment):
        self.date_of_tpm_rejected = timezone.now()
        self.reject_comment = reject_comment

        for recipient in self.unicef_focal_points_and_pme:
            self._send_email(
                recipient.email,
                'tpm/visit/reject',
                cc=self._get_tpm_focal_points_as_email_recipients(),
                context={'recipient': recipient.get_full_name()},
                user=recipient,
            )

    @transition(status,
                source=[STATUSES.assigned],
                target=STATUSES.tpm_accepted,
                permission=has_action_permission(action='accept'))
    def accept(self):
        self.date_of_tpm_accepted = timezone.now()

    @transition(status,
                source=[STATUSES.tpm_accepted, STATUSES.tpm_report_rejected],
                target=STATUSES.tpm_reported,
                conditions=[
                    TPMVisitReportValidations.as_condition(),
                ],
                permission=has_action_permission(action='send_report'),
                custom={'name': _('Submit Report')})
    def send_report(self):
        self.date_of_tpm_reported = timezone.now()

        for recipient in self.unicef_focal_points_and_pme:
            self._send_email(
                recipient.email,
                'tpm/visit/report',
                cc=self._get_tpm_focal_points_as_email_recipients(),
                context={'recipient': recipient.get_full_name()},
                user=recipient,
            )

    @transition(status,
                source=[STATUSES.tpm_reported],
                target=STATUSES.tpm_report_rejected,
                permission=has_action_permission(action='reject_report'),
                custom={
                    'serializer': TPMVisitRejectSerializer,
                    'name': _('Send back to TPM')
                })
    def reject_report(self, reject_comment):
        self.date_of_tpm_report_rejected = timezone.now()
        TPMVisitReportRejectComment.objects.create(
            reject_reason=reject_comment, tpm_visit=self)

        for staff_user in self.tpm_partner_focal_points.filter(
                user__email__isnull=False, user__is_active=True):
            self._send_email(
                [staff_user.user.email],
                'tpm/visit/report_rejected',
                context={'recipient': staff_user.user.get_full_name()},
                user=staff_user.user)

    @transition(status,
                source=[STATUSES.tpm_reported],
                target=STATUSES.unicef_approved,
                custom={'serializer': TPMVisitApproveSerializer},
                permission=has_action_permission(action='approve'))
    def approve(self,
                mark_as_programmatic_visit=None,
                approval_comment=None,
                notify_focal_point=True,
                notify_tpm_partner=True):
        mark_as_programmatic_visit = mark_as_programmatic_visit or []

        self.tpm_activities.filter(id__in=mark_as_programmatic_visit).update(
            is_pv=True)

        self.date_of_unicef_approved = timezone.now()
        if notify_focal_point:
            for recipient in self.unicef_focal_points_with_emails:
                self._send_email(
                    recipient.email,
                    'tpm/visit/approve_report',
                    context={'recipient': recipient.get_full_name()},
                    user=recipient)

        if notify_tpm_partner:
            # TODO: Generate report as PDF attachment.
            for staff_user in self.tpm_partner_focal_points.filter(
                    user__email__isnull=False, user__is_active=True):
                self._send_email(
                    [
                        staff_user.user.email,
                    ],
                    'tpm/visit/approve_report_tpm',
                    context={'recipient': staff_user.user.get_full_name()},
                    user=staff_user.user)

        if approval_comment:
            self.approval_comment = approval_comment

    def get_object_url(self, **kwargs):
        return build_frontend_url('tpm', 'visits', self.id, 'details',
                                  **kwargs)
コード例 #10
0
ファイル: models.py プロジェクト: azizur77/etools
class Audit(Engagement):

    OPTION_UNQUALIFIED = "unqualified"
    OPTION_QUALIFIED = "qualified"
    OPTION_DENIAL = "disclaimer_opinion"
    OPTION_ADVERSE = "adverse_opinion"

    OPTIONS = Choices(
        (OPTION_UNQUALIFIED, _("Unqualified")),
        (OPTION_QUALIFIED, _("Qualified")),
        (OPTION_DENIAL, _("Disclaimer opinion")),
        (OPTION_ADVERSE, _("Adverse opinion")),
    )

    audited_expenditure = models.DecimalField(
        verbose_name=_('Audited Expenditure $'),
        blank=True,
        default=0,
        decimal_places=2,
        max_digits=20)
    financial_findings = models.DecimalField(
        verbose_name=_('Financial Findings $'),
        blank=True,
        default=0,
        decimal_places=2,
        max_digits=20)
    audit_opinion = models.CharField(
        verbose_name=_('Audit Opinion'),
        max_length=20,
        choices=OPTIONS,
        default='',
        blank=True,
    )

    final_report = CodedGenericRelation(
        Attachment,
        verbose_name=_('Audit Final Report'),
        code='audit_final_report',
        blank=True,
    )

    objects = models.Manager()

    class Meta:
        ordering = ('id', )
        verbose_name = _('Audit')
        verbose_name_plural = _('Audits')

    def save(self, *args, **kwargs):
        self.engagement_type = Engagement.TYPES.audit
        return super().save(*args, **kwargs)

    @property
    def pending_unsupported_amount(self):
        return self.financial_findings - self.amount_refunded \
            - self.additional_supporting_documentation_provided \
            - self.justification_provided_and_accepted - self.write_off_required

    @property
    def percent_of_audited_expenditure(self):
        try:
            return 100 * self.financial_findings / self.audited_expenditure
        except (TypeError, DivisionByZero, InvalidOperation):
            return 0

    @transition('status',
                source=Engagement.STATUSES.partner_contacted,
                target=Engagement.STATUSES.report_submitted,
                conditions=[
                    AuditSubmitReportRequiredFieldsCheck.as_condition(),
                    EngagementHasReportAttachmentsCheck.as_condition(),
                ],
                permission=has_action_permission(action='submit'))
    def submit(self, *args, **kwargs):
        return super().submit(*args, **kwargs)

    @transition('status',
                source=Engagement.STATUSES.report_submitted,
                target=Engagement.STATUSES.final,
                permission=has_action_permission(action='finalize'))
    def finalize(self, *args, **kwargs):
        self.partner.audits_completed(update_one=True)
        return super().finalize(*args, **kwargs)

    def get_object_url(self, **kwargs):
        return build_frontend_url('ap', 'audits', self.id, 'overview',
                                  **kwargs)

    def generate_final_report(self):
        from etools.applications.audit.serializers.engagement import AuditSerializer
        from etools.applications.audit.serializers.export import AuditPDFSerializer
        generate_final_report(
            self,
            'audit_final_report',
            AuditSerializer,
            AuditPDFSerializer,
            'audit/audit_pdf.html',
            'audit_final_report.pdf',
        )
コード例 #11
0
ファイル: models.py プロジェクト: azizur77/etools
class SpotCheck(Engagement):
    total_amount_tested = models.DecimalField(
        verbose_name=_('Total Amount Tested'),
        blank=True,
        default=0,
        decimal_places=2,
        max_digits=20)
    total_amount_of_ineligible_expenditure = models.DecimalField(
        verbose_name=_('Total Amount of Ineligible Expenditure'),
        default=0,
        blank=True,
        decimal_places=2,
        max_digits=20,
    )

    internal_controls = models.TextField(verbose_name=_('Internal Controls'),
                                         blank=True)

    final_report = CodedGenericRelation(
        Attachment,
        verbose_name=_('Spot Check Final Report'),
        code='spot_check_final_report',
        blank=True,
    )

    objects = models.Manager()

    class Meta:
        ordering = ('id', )
        verbose_name = _('Spot Check')
        verbose_name_plural = _('Spot Checks')

    @property
    def pending_unsupported_amount(self):
        return self.total_amount_of_ineligible_expenditure - self.additional_supporting_documentation_provided \
            - self.justification_provided_and_accepted - self.write_off_required

    def save(self, *args, **kwargs):
        self.engagement_type = Engagement.TYPES.sc
        return super().save(*args, **kwargs)

    @transition('status',
                source=Engagement.STATUSES.partner_contacted,
                target=Engagement.STATUSES.report_submitted,
                conditions=[
                    SPSubmitReportRequiredFieldsCheck.as_condition(),
                    EngagementHasReportAttachmentsCheck.as_condition(),
                ],
                permission=has_action_permission(action='submit'))
    def submit(self, *args, **kwargs):
        return super().submit(*args, **kwargs)

    @transition('status',
                source=Engagement.STATUSES.report_submitted,
                target=Engagement.STATUSES.final,
                permission=has_action_permission(action='finalize'))
    def finalize(self, *args, **kwargs):
        self.partner.spot_checks(
            update_one=True, event_date=self.date_of_draft_report_to_unicef)
        return super().finalize(*args, **kwargs)

    def get_object_url(self, **kwargs):
        return build_frontend_url('ap', 'spot-checks', self.id, 'overview',
                                  **kwargs)

    def generate_final_report(self):
        from etools.applications.audit.serializers.engagement import SpotCheckSerializer
        from etools.applications.audit.serializers.export import SpotCheckPDFSerializer
        generate_final_report(
            self,
            'spot_check_final_report',
            SpotCheckSerializer,
            SpotCheckPDFSerializer,
            'audit/spotcheck_pdf.html',
            'spotcheck_final_report.pdf',
        )
コード例 #12
0
ファイル: models.py プロジェクト: azizur77/etools
class Engagement(InheritedModelMixin, TimeStampedModel, models.Model):
    TYPE_AUDIT = 'audit'
    TYPE_MICRO_ASSESSMENT = 'ma'
    TYPE_SPOT_CHECK = 'sc'
    TYPE_SPECIAL_AUDIT = 'sa'

    TYPES = Choices(
        (TYPE_AUDIT, _('Audit')),
        (TYPE_MICRO_ASSESSMENT, _('Micro Assessment')),
        (TYPE_SPOT_CHECK, _('Spot Check')),
        (TYPE_SPECIAL_AUDIT, _('Special Audit')),
    )

    PARTNER_CONTACTED = 'partner_contacted'
    REPORT_SUBMITTED = 'report_submitted'
    FINAL = 'final'
    CANCELLED = 'cancelled'

    STATUSES = Choices(
        (PARTNER_CONTACTED, _('IP Contacted')),
        (REPORT_SUBMITTED, _('Report Submitted')),
        (FINAL, _('Final Report')),
        (CANCELLED, _('Cancelled')),
    )

    DISPLAY_STATUSES = Choices(
        ('partner_contacted', _('IP Contacted')),
        ('field_visit', _('Field Visit')),
        ('draft_issued_to_partner', _('Draft Report Issued to IP')),
        ('comments_received_by_partner', _('Comments Received from IP')),
        ('draft_issued_to_unicef', _('Draft Report Issued to UNICEF')),
        ('comments_received_by_unicef', _('Comments Received from UNICEF')),
        ('report_submitted', _('Report Submitted')),
        ('final', _('Final Report')),
        ('cancelled', _('Cancelled')),
    )
    DISPLAY_STATUSES_DATES = {
        DISPLAY_STATUSES.partner_contacted: 'partner_contacted_at',
        DISPLAY_STATUSES.field_visit: 'date_of_field_visit',
        DISPLAY_STATUSES.draft_issued_to_partner: 'date_of_draft_report_to_ip',
        DISPLAY_STATUSES.comments_received_by_partner:
        'date_of_comments_by_ip',
        DISPLAY_STATUSES.draft_issued_to_unicef:
        'date_of_draft_report_to_unicef',
        DISPLAY_STATUSES.comments_received_by_unicef:
        'date_of_comments_by_unicef',
        DISPLAY_STATUSES.report_submitted: 'date_of_report_submit',
        DISPLAY_STATUSES.final: 'date_of_final_report',
        DISPLAY_STATUSES.cancelled: 'date_of_cancel'
    }

    status = FSMField(verbose_name=_('Status'),
                      max_length=30,
                      choices=STATUSES,
                      default=STATUSES.partner_contacted,
                      protected=True)

    # auditor - partner organization from agreement
    agreement = models.ForeignKey(
        PurchaseOrder,
        verbose_name=_('Purchase Order'),
        on_delete=models.CASCADE,
    )
    po_item = models.ForeignKey(
        PurchaseOrderItem,
        verbose_name=_('PO Item Number'),
        null=True,
        blank=True,
        on_delete=models.CASCADE,
    )

    partner = models.ForeignKey(
        'partners.PartnerOrganization',
        verbose_name=_('Partner'),
        on_delete=models.CASCADE,
    )
    partner_contacted_at = models.DateField(
        verbose_name=_('Date IP was contacted'), blank=True, null=True)
    engagement_type = models.CharField(verbose_name=_('Engagement Type'),
                                       max_length=10,
                                       choices=TYPES)
    start_date = models.DateField(verbose_name=_('Period Start Date'),
                                  blank=True,
                                  null=True)
    end_date = models.DateField(verbose_name=_('Period End Date'),
                                blank=True,
                                null=True)
    total_value = models.DecimalField(
        verbose_name=_('Total value of selected FACE form(s)'),
        blank=True,
        default=0,
        decimal_places=2,
        max_digits=20)
    exchange_rate = models.DecimalField(verbose_name=_('Exchange Rate'),
                                        blank=True,
                                        default=0,
                                        decimal_places=2,
                                        max_digits=20)

    engagement_attachments = CodedGenericRelation(
        Attachment,
        verbose_name=_('Related Documents'),
        code='audit_engagement',
        blank=True)
    report_attachments = CodedGenericRelation(
        Attachment,
        verbose_name=_('Report Attachments'),
        code='audit_report',
        blank=True)

    date_of_field_visit = models.DateField(
        verbose_name=_('Date of Field Visit'), null=True, blank=True)
    date_of_draft_report_to_ip = models.DateField(
        verbose_name=_('Date Draft Report Issued to IP'),
        null=True,
        blank=True)
    date_of_comments_by_ip = models.DateField(
        verbose_name=_('Date Comments Received from IP'),
        null=True,
        blank=True)
    date_of_draft_report_to_unicef = models.DateField(
        verbose_name=_('Date Draft Report Issued to UNICEF'),
        null=True,
        blank=True)
    date_of_comments_by_unicef = models.DateField(
        verbose_name=_('Date Comments Received from UNICEF'),
        null=True,
        blank=True)

    date_of_report_submit = models.DateField(
        verbose_name=_('Date Report Submitted'), null=True, blank=True)
    date_of_final_report = models.DateField(
        verbose_name=_('Date Report Finalized'), null=True, blank=True)
    date_of_cancel = models.DateField(verbose_name=_('Date Report Cancelled'),
                                      null=True,
                                      blank=True)

    amount_refunded = models.DecimalField(verbose_name=_('Amount Refunded'),
                                          blank=True,
                                          default=0,
                                          decimal_places=2,
                                          max_digits=20)
    additional_supporting_documentation_provided = models.DecimalField(
        verbose_name=_('Additional Supporting Documentation Provided'),
        blank=True,
        default=0,
        decimal_places=2,
        max_digits=20)
    justification_provided_and_accepted = models.DecimalField(
        verbose_name=_('Justification Provided and Accepted'),
        blank=True,
        default=0,
        decimal_places=2,
        max_digits=20)
    write_off_required = models.DecimalField(verbose_name=_('Impairment'),
                                             blank=True,
                                             default=0,
                                             decimal_places=2,
                                             max_digits=20)
    explanation_for_additional_information = models.TextField(verbose_name=_(
        'Provide explanation for additional information received from the IP or add attachments'
    ),
                                                              blank=True)

    joint_audit = models.BooleanField(verbose_name=_('Joint Audit'),
                                      default=False,
                                      blank=True)
    shared_ip_with = ArrayField(models.CharField(
        max_length=20, choices=PartnerOrganization.AGENCY_CHOICES),
                                blank=True,
                                default=list,
                                verbose_name=_('Shared Audit with'))

    staff_members = models.ManyToManyField(AuditorStaffMember,
                                           verbose_name=_('Staff Members'))

    cancel_comment = models.TextField(blank=True,
                                      verbose_name=_('Cancel Comment'))

    active_pd = models.ManyToManyField('partners.Intervention',
                                       verbose_name=_('Active PDs'))

    authorized_officers = models.ManyToManyField(
        PartnerStaffMember,
        verbose_name=_('Authorized Officers'),
        blank=True,
        related_name="engagement_authorizations")

    objects = InheritanceManager()

    class Meta:
        ordering = ('id', )
        verbose_name = _('Engagement')
        verbose_name_plural = _('Engagements')

    def __str__(self):
        return '{} {}'.format(self.get_engagement_type_display(),
                              self.reference_number)

    @property
    def displayed_status(self):
        if self.status != self.STATUSES.partner_contacted:
            return self.status

        if self.date_of_comments_by_unicef:
            return self.DISPLAY_STATUSES.comments_received_by_unicef
        elif self.date_of_draft_report_to_unicef:
            return self.DISPLAY_STATUSES.draft_issued_to_unicef
        elif self.date_of_comments_by_ip:
            return self.DISPLAY_STATUSES.comments_received_by_partner
        elif self.date_of_draft_report_to_ip:
            return self.DISPLAY_STATUSES.draft_issued_to_partner
        elif self.date_of_field_visit:
            return self.DISPLAY_STATUSES.field_visit

        return self.status

    @property
    def displayed_status_date(self):
        return getattr(self,
                       self.DISPLAY_STATUSES_DATES[self.displayed_status])

    def get_shared_ip_with_display(self):
        return list(
            map(
                lambda po: dict(PartnerOrganization.AGENCY_CHOICES).get(
                    po, 'Unknown'), self.shared_ip_with))

    @property
    def unique_id(self):
        engagement_code = 'a' if self.engagement_type == self.TYPES.audit else self.engagement_type
        return '{}/{}/{}/{}/{}'.format(
            connection.tenant.country_short_code or '', self.partner.name[:5],
            engagement_code.upper(), self.created.year, self.id)

    @property
    def reference_number(self):
        return self.unique_id

    def get_mail_context(self, **kwargs):
        object_url = self.get_object_url(**kwargs)

        return {
            'unique_id': self.unique_id,
            'engagement_type': self.get_engagement_type_display(),
            'object_url': object_url,
            'partner': force_text(self.partner),
            'auditor_firm': force_text(self.agreement.auditor_firm),
        }

    def _notify_focal_points(self, template_name, context=None):
        for focal_point in get_user_model().objects.filter(
                groups=UNICEFAuditFocalPoint.as_group(),
                profile__countries_available=connection.tenant):
            # Build the context in the same order the previous version of the code did,
            # just in case something relies on it (intentionally or not).
            ctx = {
                'focal_point': focal_point.get_full_name(),
            }
            if context:
                ctx.update(context)
            base_context = {
                'engagement': self.get_mail_context(user=focal_point),
                'environment': get_environment(),
            }
            base_context.update(ctx)
            context = base_context

            send_notification_with_template(
                recipients=[focal_point.email],
                template_name=template_name,
                context=context,
            )

    @transition(status,
                source=STATUSES.partner_contacted,
                target=STATUSES.report_submitted,
                permission=has_action_permission(action='submit'))
    def submit(self):
        self.date_of_report_submit = timezone.now()

        self._notify_focal_points('audit/engagement/reported_by_auditor')

    @transition(status,
                source=[STATUSES.partner_contacted, STATUSES.report_submitted],
                target=STATUSES.cancelled,
                permission=has_action_permission(action='cancel'),
                custom={'serializer': EngagementCancelSerializer})
    def cancel(self, cancel_comment):
        self.date_of_cancel = timezone.now()
        self.cancel_comment = cancel_comment

    @transition(status,
                source=STATUSES.report_submitted,
                target=STATUSES.final,
                permission=has_action_permission(action='finalize'))
    def finalize(self):
        self.date_of_final_report = timezone.now().date()
        self.generate_final_report()

    def get_object_url(self, **kwargs):
        return build_frontend_url('ap', 'engagements', self.id, 'overview',
                                  **kwargs)