Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature/1658 - Loans, debts, and repayments with future reports can't be deleted. Loans and debts with past reports can't be deleted. #1165

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
# Generated by Django 5.0.8 on 2024-08-28 15:19

from django.db import migrations


def create_update_blocking_reports_function(apps, schema_editor):
schema_editor.execute(
"""
CREATE OR REPLACE FUNCTION update_blocking_reports()
RETURNS TRIGGER AS $$
DECLARE
committee_id UUID;
transaction RECORD;
report RECORD;
blocking_report_ids UUID[];
is_repayment BOOLEAN;
BEGIN
-- Fetch the committee_id for the new transaction
SELECT committee_account_id
INTO committee_id
FROM transactions_transaction
WHERE id = NEW.transaction_id;

IF EXISTS (
SELECT 1
FROM transactions_transaction
WHERE id = NEW.transaction_id
AND (schedule_c_id IS NOT NULL OR
schedule_c1_id IS NOT NULL OR
schedule_c2_id IS NOT NULL OR
schedule_d_id IS NOT NULL OR
transaction_type_identifier LIKE '%%REPAYMENT%%' OR
transaction_type_identifier LIKE 'LOAN%%RECEIPT' OR
transaction_type_identifier LIKE 'DEBT%%RECEIPT')
) THEN
-- Loop through each transaction with the same committee ID
-- and a relevant schedule
FOR transaction IN
SELECT id, transaction_type_identifier
FROM transactions_transaction
WHERE committee_account_id = committee_id
AND (schedule_c_id IS NOT NULL OR
schedule_c1_id IS NOT NULL OR
schedule_c2_id IS NOT NULL OR
schedule_d_id IS NOT NULL OR
loan_id IS NOT NULL OR
debt_id IS NOT NULL OR
transaction_type_identifier LIKE 'LOAN%%RECEIPT' OR
transaction_type_identifier LIKE 'DEBT%%RECEIPT')
LOOP
-- Determine if the transaction is a repayment
is_repayment := transaction.transaction_type_identifier
LIKE '%%REPAYMENT%%';

-- Loop through each report associated with the current transaction
FOR report IN
SELECT r.id, r.coverage_through_date
FROM reports_report AS r
INNER JOIN reports_reporttransaction AS rt
ON rt.report_id = r.id
WHERE rt.transaction_id = transaction.id
LOOP
-- Find other reports with the same committee_id and
-- form_3x not null
-- Ensure that reports are only added if they match the conditions
SELECT ARRAY(
SELECT DISTINCT r2.id
FROM reports_report AS r2
WHERE r2.committee_account_id = committee_id
AND r2.form_3x_id IS NOT NULL
AND r2.id != report.id
AND (NOT is_repayment OR
r2.coverage_through_date > report.coverage_through_date)
) INTO blocking_report_ids;

-- Update the blocking_reports array, ensuring no duplicates
IF blocking_report_ids IS NOT NULL THEN
UPDATE transactions_transaction
SET blocking_reports = ARRAY(
SELECT DISTINCT
unnest(blocking_reports || blocking_report_ids)
)
WHERE id = transaction.id;
END IF;
END LOOP;
END LOOP;
END IF;

RETURN NEW;
END;
$$ LANGUAGE plpgsql;
"""
)


def create_trigger(apps, schema_editor):
schema_editor.execute(
"""
CREATE TRIGGER update_blocking_reports
AFTER INSERT ON reports_reporttransaction
FOR EACH ROW EXECUTE FUNCTION update_blocking_reports();
"""
)


def drop_trigger(apps, schema_editor):
schema_editor.execute(
"DROP TRIGGER IF EXISTS update_blocking_reports ON reports_reporttransaction;"
)


def drop_update_blocking_reports_function(apps, schema_editor):
schema_editor.execute("DROP FUNCTION IF EXISTS update_blocking_reports()")


class Migration(migrations.Migration):
dependencies = [
("transactions", "0011_transaction_can_delete"),
]

operations = [
migrations.RunPython(
create_update_blocking_reports_function, drop_update_blocking_reports_function
),
migrations.RunPython(create_trigger, reverse_code=drop_trigger),
]
153 changes: 84 additions & 69 deletions django-backend/fecfiler/transactions/tests/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,15 @@ class TransactionModelTestCase(TestCase):
def setUp(self):
self.committee = CommitteeAccount.objects.create(committee_id="C00000000")
create_committee_view(self.committee.id)
self.committee2 = CommitteeAccount.objects.create(committee_id="C00000001")
self.com2_q1_report = create_form3x(
self.committee2, "2024-01-01", "2024-02-01", {}
)
self.com2_contact_1 = create_test_individual_contact(
"last name", "First name", self.committee.id
)
create_committee_view(self.committee2.id)

self.q1_report = create_form3x(self.committee, "2024-01-01", "2024-02-01", {})
self.m1_report = create_form3x(self.committee, "2024-01-01", "2024-01-31", {})
self.m2_report = create_form3x(self.committee, "2024-02-01", "2024-02-28", {})
Expand Down Expand Up @@ -201,84 +210,86 @@ def test_delete_coupled_transactions(self):
self.assertIsNone(Transaction.objects.filter(id=self.earmark_receipt.id).first())

def test_delete_debt_transactions(self):
"""Deleting a debt must delete any payments made towards the debt and
carried forward copies of it (along with repayments to them)"""
original_debt = create_debt(self.committee, self.contact_1, Decimal("123.00"))
original_debt.save()
original_debt.reports.add(self.q1_report)
carried_forward_debt = carry_forward_debt(original_debt, self.m1_report)
"""Deleting a debt must delete any payments made towards the debt, but
cannot delete if there are carried forward copies of it"""
original_debt = create_debt(
self.committee2,
self.com2_contact_1,
Decimal("123.00"),
report=self.com2_q1_report,
)
first_repayment = create_schedule_b(
"OPERATING_EXPENDITURE",
self.committee,
self.contact_1,
"DEBT_REPAYMENT_MADE",
self.committee2,
self.com2_contact_1,
"2024-01-02",
Decimal("1.23"),
"GENERAL_DISBURSEMENT",
report=self.com2_q1_report,
)
first_repayment.debt = carried_forward_debt
first_repayment.debt = original_debt
first_repayment.save()
second_repayment = create_schedule_b(
"OPERATING_EXPENDITURE",
self.committee,
self.contact_1,
"2024-01-02",
Decimal("2.27"),
"GENERAL_DISBURSEMENT",
)
second_repayment.debt = carried_forward_debt
second_repayment.save()

original_debt.delete()
original_debt.refresh_from_db()
carried_forward_debt.refresh_from_db()
first_repayment.refresh_from_db()
second_repayment.refresh_from_db()
self.assertIsNotNone(original_debt.deleted)
self.assertIsNotNone(carried_forward_debt.deleted)
self.assertIsNotNone(first_repayment.deleted)
self.assertIsNotNone(second_repayment.deleted)

undelete(original_debt)
undelete(carried_forward_debt)
undelete(first_repayment)
undelete(second_repayment)

def test_delete_loan_by_committee(self):
self.assertIsNone(self.loan.deleted)
self.assertIsNone(self.loan_made.deleted)
self.assertIsNone(self.carried_forward_loan.deleted)
self.assertIsNone(self.payment_1.deleted)
self.assertIsNone(self.payment_2.deleted)

self.loan.delete()
self.loan.refresh_from_db()
self.loan_made.refresh_from_db()
self.carried_forward_loan.refresh_from_db()
self.payment_1.refresh_from_db()
self.payment_2.refresh_from_db()
loan2 = create_loan(
self.committee2,
self.com2_contact_1,
"5000.00",
"2024-07-01",
"7%",
loan_incurred_date="2024-01-01",
report=self.com2_q1_report,
)
loan2_made = create_schedule_b(
"LOAN_RECEIVED_FROM_INDIVIDUAL_RECEIPT",
self.committee2,
self.com2_contact_1,
"2024-01-02",
"1000.00",
loan_id=loan2.id,
report=self.com2_q1_report,
)
payment_1 = create_schedule_b(
"LOAN_REPAYMENT_MADE",
self.committee2,
self.com2_contact_1,
"2024-01-02",
"1000.00",
loan_id=loan2.id,
report=self.com2_q1_report,
)

self.assertIsNotNone(self.loan.deleted)
self.assertIsNotNone(self.loan_made.deleted)
self.assertIsNotNone(self.carried_forward_loan.deleted)
self.assertIsNotNone(self.payment_1.deleted)
self.assertIsNotNone(self.payment_2.deleted)
self.assertIsNone(loan2.deleted)
self.assertIsNone(loan2_made.deleted)
self.assertIsNone(payment_1.deleted)

undelete(self.loan)
undelete(self.loan_made)
undelete(self.carried_forward_loan)
undelete(self.payment_1)
undelete(self.payment_2)
loan2.delete()
loan2.refresh_from_db()
loan2_made.refresh_from_db()
self.carried_forward_loan.refresh_from_db()
payment_1.refresh_from_db()

self.assertIsNotNone(loan2.deleted)
self.assertIsNotNone(loan2_made.deleted)
self.assertIsNotNone(payment_1.deleted)

def test_delete_loan_received_from_bank(self):
loan, loan_receipt, loan_aggreement, guarantor = create_loan_from_bank(
self.committee,
self.contact_1,
self.committee2,
self.com2_contact_1,
"5000.00",
"2024-07-01",
"7%",
False,
"2024-01-01",
report=self.q1_report,
report=self.com2_q1_report,
)
# deleting guarantor does not delete loan
guarantor.delete()
Expand Down Expand Up @@ -646,11 +657,11 @@ def test_can_delete_loan(self):
self.carried_forward_loan.refresh_from_db()
self.payment_1.refresh_from_db()
self.payment_2.refresh_from_db()
self.assertTrue(self.loan.can_delete)
self.assertTrue(self.loan_made.can_delete)
self.assertFalse(self.loan.can_delete)
self.assertFalse(self.loan_made.can_delete)
# Can delete carried forward debt, but the UI won't let you
self.assertTrue(self.carried_forward_loan.can_delete)
self.assertTrue(self.payment_1.can_delete)
self.assertFalse(self.carried_forward_loan.can_delete)
self.assertFalse(self.payment_1.can_delete)
self.assertTrue(self.payment_2.can_delete)

self.m1_report.upload_submission = UploadSubmission.objects.initiate_submission(
Expand All @@ -669,7 +680,7 @@ def test_can_delete_loan(self):
self.assertFalse(self.loan_made.can_delete)
self.assertFalse(self.payment_1.can_delete)
# Can delete carried forward loan, but the UI won't let you
self.assertTrue(self.carried_forward_loan.can_delete)
self.assertFalse(self.carried_forward_loan.can_delete)
# Payment 2 can still be deleted because
# it is a repayment on the loan in an active report
self.assertTrue(self.payment_2.can_delete)
Expand All @@ -681,11 +692,11 @@ def test_can_delete_loan(self):
self.carried_forward_loan.refresh_from_db()
self.payment_1.refresh_from_db()
self.payment_2.refresh_from_db()
self.assertTrue(self.loan.can_delete)
self.assertTrue(self.loan_made.can_delete)
self.assertFalse(self.loan.can_delete)
self.assertFalse(self.loan_made.can_delete)
# Can delete carried forward loan, but the UI won't let you
self.assertTrue(self.carried_forward_loan.can_delete)
self.assertTrue(self.payment_1.can_delete)
self.assertFalse(self.carried_forward_loan.can_delete)
self.assertFalse(self.payment_1.can_delete)
self.assertTrue(self.payment_2.can_delete)

def test_can_delete_reattributed_transaction(self):
Expand Down Expand Up @@ -774,7 +785,7 @@ def test_can_delete_debt(self):
self.committee, self.contact_1, Decimal("123.00"), report=m1_report
)
m1_repayment = create_schedule_b(
"OPERATING_EXPENDITURE",
"LOAN_REPAYMENT_RECEIVED",
self.committee,
self.contact_1,
"2024-01-02",
Expand All @@ -786,7 +797,7 @@ def test_can_delete_debt(self):
m1_repayment.save()
carried_forward_debt = carry_forward_debt(original_debt, m2_report)
m2_repayment = create_schedule_b(
"OPERATING_EXPENDITURE",
"LOAN_REPAYMENT_RECEIVED",
self.committee,
self.contact_1,
"2024-02-02",
Expand All @@ -797,10 +808,14 @@ def test_can_delete_debt(self):
m2_repayment.debt = carried_forward_debt
m2_repayment.save()

self.assertTrue(original_debt.can_delete)
self.assertTrue(m1_repayment.can_delete)
original_debt.refresh_from_db()
m1_repayment.refresh_from_db()
carried_forward_debt.refresh_from_db()
m2_repayment.refresh_from_db()
self.assertFalse(original_debt.can_delete)
self.assertFalse(m1_repayment.can_delete)
# Can delete carried forward debt, but the UI won't let you
self.assertTrue(carried_forward_debt.can_delete)
self.assertFalse(carried_forward_debt.can_delete)
self.assertTrue(m2_repayment.can_delete)

m2_report.upload_submission = UploadSubmission.objects.initiate_submission(
Expand Down Expand Up @@ -830,7 +845,7 @@ def test_can_delete_debt(self):
self.assertFalse(original_debt.can_delete)
self.assertFalse(m1_repayment.can_delete)
# Can delete carried forward debt, but the UI won't let you
self.assertTrue(carried_forward_debt.can_delete)
self.assertFalse(carried_forward_debt.can_delete)
self.assertTrue(m2_repayment.can_delete)

m1_report.upload_submission = None
Expand Down Expand Up @@ -859,7 +874,7 @@ def test_can_delete_debt(self):
m3_report.save()
original_debt.refresh_from_db()
print(f"blocking reports {original_debt.blocking_reports}")
self.assertTrue(original_debt.can_delete)
self.assertFalse(original_debt.can_delete)

def set_up_jf_transfer(self):
jf_transfer = create_schedule_a(
Expand Down