Skip to content

Commit

Permalink
Merge pull request #467 from dimagi/sr/payex
Browse files Browse the repository at this point in the history
Add accrued, date columns to payment export/import
  • Loading branch information
calellowitz authored Jan 30, 2025
2 parents 25758e6 + 729c24c commit 69be7fc
Show file tree
Hide file tree
Showing 5 changed files with 103 additions and 12 deletions.
24 changes: 21 additions & 3 deletions commcare_connect/opportunity/export.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import datetime
import json

from django.db.models import Sum
from django.utils.encoding import force_str
from flatten_dict import flatten
from tablib import Dataset
Expand Down Expand Up @@ -98,12 +99,29 @@ def _schema_sort(item):


def export_empty_payment_table(opportunity: Opportunity) -> Dataset:
headers = ["Username", "Phone Number", "Name", "Payment Amount"]
headers = [
"Username",
"Phone Number",
"Name",
"Payment Amount",
"Payment Date (YYYY-MM-DD)",
]
dataset = Dataset(title="Export", headers=headers)

access_objects = OpportunityAccess.objects.filter(opportunity=opportunity, suspended=False).select_related("user")
access_objects = (
OpportunityAccess.objects.filter(opportunity=opportunity, suspended=False)
.select_related("user")
.annotate(total_payments=Sum("payment__amount"))
)

for access in access_objects:
row = (access.user.username, access.user.phone_number, access.user.name, "")
row = (
access.user.username,
access.user.phone_number,
access.user.name,
"",
"",
)
dataset.append(row)
return dataset

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 4.2.5 on 2025-01-29 07:49

import datetime
from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("opportunity", "0069_alter_opportunity_total_budget"),
]

operations = [
migrations.AlterField(
model_name="payment",
name="date_paid",
field=models.DateTimeField(default=datetime.datetime.utcnow),
),
]
2 changes: 1 addition & 1 deletion commcare_connect/opportunity/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -426,7 +426,7 @@ class Meta:
class Payment(models.Model):
amount = models.PositiveIntegerField()
amount_usd = models.DecimalField(max_digits=10, decimal_places=2, null=True)
date_paid = models.DateTimeField(auto_now_add=True)
date_paid = models.DateTimeField(default=datetime.datetime.utcnow)
# This is used to indicate payments made to Opportunity Users
opportunity_access = models.ForeignKey(OpportunityAccess, on_delete=models.DO_NOTHING, null=True, blank=True)
payment_unit = models.ForeignKey(
Expand Down
39 changes: 35 additions & 4 deletions commcare_connect/opportunity/tests/test_visit_import.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import datetime
import random
import re
from datetime import timedelta
Expand Down Expand Up @@ -290,17 +291,47 @@ def test_bulk_update_payments(opportunity: Opportunity):
access_objects = []
for mobile_user in mobile_user_seen:
access_objects.append(OpportunityAccessFactory(opportunity=opportunity, user=mobile_user))
dataset = Dataset(headers=["Username", "Phone Number", "Name", "Payment Amount"])
for mobile_user in chain(mobile_user_seen, mobile_user_missing):
dataset.append((mobile_user.username, mobile_user.phone_number, mobile_user.name, 50))

dataset = Dataset(
headers=[
"Username",
"Phone Number",
"Name",
"Payment Accrued",
"Payment Completed",
"Payment Amount",
"Payment Date (YYYY-MM-DD)",
]
)

payment_date = "2025-01-15"
for index, mobile_user in enumerate(chain(mobile_user_seen, mobile_user_missing)):
dataset.append(
(
mobile_user.username,
mobile_user.phone_number,
mobile_user.name,
100, # Payment Accrued
0, # Payment Completed
50, # Payment Amount
payment_date if index != 4 else None,
)
)

payment_import_status = _bulk_update_payments(opportunity, dataset)

assert payment_import_status.seen_users == {user.username for user in mobile_user_seen}
assert payment_import_status.missing_users == {user.username for user in mobile_user_missing}

assert Payment.objects.filter(opportunity_access__opportunity=opportunity).count() == 5
for access in access_objects:

for index, access in enumerate(access_objects):
payment = Payment.objects.get(opportunity_access=access)
assert payment.amount == 50
if index == 4:
assert payment.date_paid.date() == datetime.date.today()
else:
assert payment.date_paid.strftime("%Y-%m-%d") == payment_date


@pytest.fixture
Expand Down
32 changes: 28 additions & 4 deletions commcare_connect/opportunity/visit_import.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import codecs
import datetime
import json
import textwrap
import urllib
Expand Down Expand Up @@ -33,6 +34,7 @@
STATUS_COL = "status"
USERNAME_COL = "username"
AMOUNT_COL = "payment amount"
PAYMENT_DATE_COL = "payment date (yyyy-mm-dd)"
REASON_COL = "rejected reason"
JUSTIFICATION_COL = "justification"
WORK_ID_COL = "instance id"
Expand Down Expand Up @@ -239,6 +241,7 @@ def _bulk_update_payments(opportunity: Opportunity, imported_data: Dataset) -> P

username_col_index = _get_header_index(headers, USERNAME_COL)
amount_col_index = _get_header_index(headers, AMOUNT_COL)
payment_date_col_index = _get_header_index(headers, PAYMENT_DATE_COL)
invalid_rows = []
payments = {}
exchange_rate = get_exchange_rate(opportunity.currency)
Expand All @@ -248,6 +251,7 @@ def _bulk_update_payments(opportunity: Opportunity, imported_data: Dataset) -> P
row = list(row)
username = str(row[username_col_index])
amount_raw = row[amount_col_index]
payment_date_raw = row[payment_date_col_index]
if amount_raw:
if not username:
invalid_rows.append((row, "username required"))
Expand All @@ -256,7 +260,20 @@ def _bulk_update_payments(opportunity: Opportunity, imported_data: Dataset) -> P
except ValueError:
invalid_rows.append((row, "amount must be an integer"))
else:
payments[username] = amount
payments[username] = {"amount": amount}
try:
if payment_date_raw:
if isinstance(payment_date_raw, datetime.datetime):
# Dataset autoparses valid datetime
payment_date = payment_date_raw
else:
payment_date = datetime.datetime.strptime(payment_date_raw, "%Y-%m-%d").date()
else:
payment_date = None
except ValueError:
invalid_rows.append((row, "Payment Date must be in YYYY-MM-DD format"))
else:
payments[username]["payment_date"] = payment_date

if invalid_rows:
raise ImportException(f"{len(invalid_rows)} have errors", invalid_rows)
Expand All @@ -272,9 +289,16 @@ def _bulk_update_payments(opportunity: Opportunity, imported_data: Dataset) -> P
).select_related("user")
for access in users:
username = access.user.username
amount = payments[username]
amount_usd = amount / exchange_rate
payment = Payment.objects.create(opportunity_access=access, amount=amount, amount_usd=amount_usd)
amount = payments[username]["amount"]
payment_date = payments[username]["payment_date"]
payment_data = {
"opportunity_access": access,
"amount": amount,
"amount_usd": amount / exchange_rate,
}
if payment_date:
payment_data["date_paid"] = payment_date
payment = Payment.objects.create(**payment_data)
seen_users.add(username)
payment_ids.append(payment.pk)
update_work_payment_date(access)
Expand Down

0 comments on commit 69be7fc

Please sign in to comment.