Skip to content

Payment

hypha.apply.projects.models.payment

EXPORT_STATUS_ERROR module-attribute

EXPORT_STATUS_ERROR = 'error'

EXPORT_STATUS_SUCCESS module-attribute

EXPORT_STATUS_SUCCESS = 'success'

EXPORT_STATUS_GENERATING module-attribute

EXPORT_STATUS_GENERATING = 'generating'

EXPORT_STATUS_CHOICES module-attribute

EXPORT_STATUS_CHOICES = [(EXPORT_STATUS_ERROR, _('Failed')), (EXPORT_STATUS_SUCCESS, _('Success')), (EXPORT_STATUS_GENERATING, _('In Progress'))]

SUBMITTED module-attribute

SUBMITTED = 'submitted'

RESUBMITTED module-attribute

RESUBMITTED = 'resubmitted'

CHANGES_REQUESTED_BY_STAFF module-attribute

CHANGES_REQUESTED_BY_STAFF = 'changes_requested_staff'

CHANGES_REQUESTED_BY_FINANCE module-attribute

CHANGES_REQUESTED_BY_FINANCE = 'changes_requested_finance_1'

APPROVED_BY_STAFF module-attribute

APPROVED_BY_STAFF = 'approved_by_staff'

APPROVED_BY_FINANCE module-attribute

APPROVED_BY_FINANCE = 'approved_by_finance_1'

PAID module-attribute

PAID = 'paid'

PAYMENT_FAILED module-attribute

PAYMENT_FAILED = 'payment_failed'

DECLINED module-attribute

DECLINED = 'declined'

INVOICE_STATUS_CHOICES module-attribute

INVOICE_STATUS_CHOICES = [(SUBMITTED, _('Submitted')), (RESUBMITTED, _('Resubmitted')), (CHANGES_REQUESTED_BY_STAFF, _('Changes requested by staff')), (CHANGES_REQUESTED_BY_FINANCE, _('Changes requested by finance')), (APPROVED_BY_STAFF, _('Approved by staff')), (APPROVED_BY_FINANCE, _('Approved by finance')), (PAID, _('Paid')), (PAYMENT_FAILED, _('Payment failed')), (DECLINED, _('Declined'))]

INVOICE_TRANSITION_TO_RESUBMITTED module-attribute

INVOICE_STATUS_PM_CHOICES module-attribute

INVOICE_STATUS_PM_CHOICES = [CHANGES_REQUESTED_BY_STAFF, APPROVED_BY_STAFF, DECLINED]

INVOICE_STATUS_FINANCE_1_CHOICES module-attribute

InvoiceTag

Bases: Model

name class-attribute instance-attribute

name = models.CharField(max_length=100, unique=True)

Meta

verbose_name class-attribute instance-attribute
verbose_name = _('invoice tag')
verbose_name_plural class-attribute instance-attribute
verbose_name_plural = _('invoice tags')
ordering class-attribute instance-attribute
ordering = ['name']

InvoiceQueryset

Bases: QuerySet

in_progress

in_progress()
Source code in hypha/apply/projects/models/payment.py
def in_progress(self):
    return self.exclude(status__in=[DECLINED, PAID])

approved_by_staff

approved_by_staff()
Source code in hypha/apply/projects/models/payment.py
def approved_by_staff(self):
    return self.filter(status=APPROVED_BY_STAFF)

approved_by_finance_1

approved_by_finance_1()
Source code in hypha/apply/projects/models/payment.py
def approved_by_finance_1(self):
    return self.filter(status=APPROVED_BY_FINANCE)

waiting_to_convert

waiting_to_convert()
Source code in hypha/apply/projects/models/payment.py
def waiting_to_convert(self):
    return self.filter(status=APPROVED_BY_FINANCE)

for_finance_1

for_finance_1()
Source code in hypha/apply/projects/models/payment.py
def for_finance_1(self):
    return self.filter(status__in=[APPROVED_BY_STAFF, APPROVED_BY_FINANCE])

rejected

rejected()
Source code in hypha/apply/projects/models/payment.py
def rejected(self):
    return self.filter(status=DECLINED).order_by("-requested_at")

not_rejected

not_rejected()
Source code in hypha/apply/projects/models/payment.py
def not_rejected(self):
    return self.exclude(status=DECLINED).order_by("-requested_at")

total_value

total_value(field)
Source code in hypha/apply/projects/models/payment.py
def total_value(self, field):
    return self.aggregate(
        total=Coalesce(Sum(field), Value(0), output_field=models.DecimalField())
    )["total"]

paid_value

paid_value()
Source code in hypha/apply/projects/models/payment.py
def paid_value(self):
    return self.filter(status=PAID).total_value("paid_value")

unpaid_value

unpaid_value()
Source code in hypha/apply/projects/models/payment.py
def unpaid_value(self):
    return self.filter(~Q(status=PAID)).total_value("paid_value")

Invoice

Bases: Model

project class-attribute instance-attribute

project = models.ForeignKey('Project', on_delete=models.CASCADE, related_name='invoices')

by class-attribute instance-attribute

by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='invoices')

paid_value class-attribute instance-attribute

paid_value = models.DecimalField(_('paid value'), max_digits=10, decimal_places=2, validators=[MinValueValidator(decimal.Decimal('0.01'))], null=True)

document class-attribute instance-attribute

document = models.FileField(_('document'), upload_to=invoice_path, storage=PrivateStorage())

message_for_pm class-attribute instance-attribute

message_for_pm = models.TextField(blank=True, verbose_name=_('comment'), help_text=_('This will be displayed as a comment in the comments tab'))

comment class-attribute instance-attribute

comment = models.TextField(_('comment'), blank=True)

invoice_number class-attribute instance-attribute

invoice_number = models.CharField(max_length=50, null=True, verbose_name=_('invoice number'))

invoice_amount class-attribute instance-attribute

invoice_amount = models.DecimalField(max_digits=10, decimal_places=2, validators=[MinValueValidator(decimal.Decimal('0.01'))], null=True, verbose_name=_('invoice amount'))

invoice_date class-attribute instance-attribute

invoice_date = models.DateField(null=True, verbose_name=_('invoice date'))

paid_date class-attribute instance-attribute

paid_date = models.DateField(null=True, verbose_name=_('paid date'))

status class-attribute instance-attribute

status = models.CharField(_('status'), default=SUBMITTED, choices=INVOICE_STATUS_CHOICES, max_length=30)

status_field class-attribute instance-attribute

status_field = State(default=SUBMITTED, states=INVOICE_STATUS_CHOICES)

requested_at class-attribute instance-attribute

requested_at = models.DateTimeField(auto_now_add=True)

tags class-attribute instance-attribute

tags = models.ManyToManyField(InvoiceTag, blank=True, related_name='invoices', verbose_name=_('tags'))

objects class-attribute instance-attribute

objects = InvoiceQueryset.as_manager()

wagtail_reference_index_ignore class-attribute instance-attribute

wagtail_reference_index_ignore = True

has_changes_requested property

has_changes_requested

status_display property

status_display

value property

value

filename property

filename

Meta

verbose_name class-attribute instance-attribute
verbose_name = _('invoice')
verbose_name_plural class-attribute instance-attribute
verbose_name_plural = _('invoices')

transition_invoice_to_resubmitted

transition_invoice_to_resubmitted()

Transition invoice to resubmitted status. This method generally gets used on invoice edit.

Source code in hypha/apply/projects/models/payment.py
@status_field.transition(
    source=INVOICE_TRANSITION_TO_RESUBMITTED, target=RESUBMITTED
)
def transition_invoice_to_resubmitted(self):
    """
    Transition invoice to resubmitted status.
    This method generally gets used on invoice edit.
    """
    pass

can_user_delete

can_user_delete(user)
Source code in hypha/apply/projects/models/payment.py
def can_user_delete(self, user):
    from hypha.apply.funds.models.co_applicants import (
        CoApplicantProjectPermission,
        CoApplicantRole,
    )

    if self.status == SUBMITTED:
        if user.is_apply_staff:
            return True
        if user.is_applicant:
            if user == self.project.user:
                return True
            co_applicant = self.project.submission.co_applicants.filter(
                user=user
            ).first()
            if (
                co_applicant
                and CoApplicantProjectPermission.INVOICES
                in co_applicant.project_permission
                and co_applicant.role == CoApplicantRole.EDIT
            ):
                return True

    return False

can_user_edit

can_user_edit(user)
Source code in hypha/apply/projects/models/payment.py
def can_user_edit(self, user):
    from hypha.apply.funds.models.co_applicants import (
        CoApplicantProjectPermission,
        CoApplicantRole,
    )

    """
    Check when an user can edit an invoice.
    Only applicant and staff have permission to edit invoice based on its current status.
    """
    if user.is_applicant:
        if self.status in {SUBMITTED, CHANGES_REQUESTED_BY_STAFF, RESUBMITTED}:
            if user == self.project.user:
                return True
            co_applicant = self.project.submission.co_applicants.filter(
                user=user
            ).first()
            if (
                co_applicant
                and CoApplicantProjectPermission.INVOICES
                in co_applicant.project_permission
                and co_applicant.role == CoApplicantRole.EDIT
            ):
                return True
        return False

    if user.is_apply_staff:
        if self.status in {SUBMITTED, RESUBMITTED, CHANGES_REQUESTED_BY_FINANCE}:
            return True

    return False

can_user_change_status

can_user_change_status(user)

Check user roles that can transition invoice status based on the current status.

Source code in hypha/apply/projects/models/payment.py
def can_user_change_status(self, user):
    """
    Check user roles that can transition invoice status based on the current status.
    """
    if not (user.is_contracting or user.is_apply_staff or user.is_finance):
        return False  # Users can't change status

    if self.status in {DECLINED}:
        return False

    if user.is_contracting:
        if self.status in {SUBMITTED, CHANGES_REQUESTED_BY_STAFF, RESUBMITTED}:
            return True

    if user.is_apply_staff:
        if self.status in {
            SUBMITTED,
            RESUBMITTED,
            CHANGES_REQUESTED_BY_STAFF,
            CHANGES_REQUESTED_BY_FINANCE,
        }:
            return True

    if user.is_finance:
        if self.status in {
            APPROVED_BY_STAFF,
            APPROVED_BY_FINANCE,
            PAID,
            PAYMENT_FAILED,
        }:
            return True

    return False

get_absolute_url

get_absolute_url()
Source code in hypha/apply/projects/models/payment.py
def get_absolute_url(self):
    return reverse(
        "apply:projects:invoice-detail",
        kwargs={"pk": self.project.pk, "invoice_pk": self.pk},
    )

SupportingDocument

Bases: Model

wagtail_reference_index_ignore class-attribute instance-attribute

wagtail_reference_index_ignore = True

document class-attribute instance-attribute

document = models.FileField(_('document'), upload_to='supporting_documents', storage=PrivateStorage())

invoice class-attribute instance-attribute

invoice = models.ForeignKey(Invoice, on_delete=models.CASCADE, related_name='supporting_documents')

filename property

filename

Meta

verbose_name class-attribute instance-attribute
verbose_name = _('supporting document')
verbose_name_plural class-attribute instance-attribute
verbose_name_plural = _('supporting documents')

get_absolute_url

get_absolute_url()
Source code in hypha/apply/projects/models/payment.py
def get_absolute_url(self):
    return reverse(
        "apply:projects:invoice-supporting-document",
        kwargs={
            "pk": self.invoice.project.pk,
            "invoice_pk": self.invoice.pk,
            "file_pk": self.pk,
        },
    )

InvoiceExportManager

Bases: Model

user class-attribute instance-attribute

user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)

export_data class-attribute instance-attribute

export_data = models.TextField()

created_time class-attribute instance-attribute

created_time = models.DateTimeField(auto_now_add=True)

completed_time class-attribute instance-attribute

completed_time = models.DateTimeField(null=True)

status class-attribute instance-attribute

status = models.CharField(choices=EXPORT_STATUS_CHOICES, default=EXPORT_STATUS_GENERATING)

total_export class-attribute instance-attribute

total_export = models.IntegerField(null=True)

Meta

verbose_name class-attribute instance-attribute
verbose_name = _('invoice export manager')
verbose_name_plural class-attribute instance-attribute
verbose_name_plural = _('invoice export managers')

set_completed_and_save

set_completed_and_save()
Source code in hypha/apply/projects/models/payment.py
def set_completed_and_save(self) -> None:
    self.status = EXPORT_STATUS_SUCCESS
    self.completed_time = timezone.now()
    self.save()

set_failed_and_save

set_failed_and_save()
Source code in hypha/apply/projects/models/payment.py
def set_failed_and_save(self) -> None:
    self.status = EXPORT_STATUS_ERROR
    self.save()

get_absolute_url

get_absolute_url()
Source code in hypha/apply/projects/models/payment.py
def get_absolute_url(self) -> str:
    return reverse("apply:projects:invoices")

invoice_status_user_choices

invoice_status_user_choices(user)
Source code in hypha/apply/projects/models/payment.py
def invoice_status_user_choices(user):
    if user.is_finance:
        return INVOICE_STATUS_FINANCE_1_CHOICES
    if user.is_apply_staff:
        return INVOICE_STATUS_PM_CHOICES
    return []

invoice_path

invoice_path(instance, filename)
Source code in hypha/apply/projects/models/payment.py
def invoice_path(instance, filename):
    return f"projects/{instance.project_id}/payment_invoices/{filename}"