Skip to content

Commit

Permalink
feat: Max retry limit for bill payment (#664)
Browse files Browse the repository at this point in the history
* Max retry limit for bill payment

* resolved flake

* test resolved

* reset db fixed

* fix flake8

* Fix PR workflow

* Update pytest.yml

---------

Co-authored-by: GitHub Actions <[email protected]>
  • Loading branch information
Ashutosh619-sudo and GitHub Actions committed Sep 30, 2024
1 parent fd3f6df commit 38c7516
Show file tree
Hide file tree
Showing 7 changed files with 131 additions and 11 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/pytest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
run: |
docker compose -f docker-compose-pipeline.yml build
docker compose -f docker-compose-pipeline.yml up -d
docker compose -f docker-compose-pipeline.yml exec -T api pytest tests/ --cov --cov-report=xml --cov-fail-under=94 --junit-xml=test-reports/report.xml
docker compose -f docker-compose-pipeline.yml exec -T api pytest tests/ --cov --cov-report=xml --cov-fail-under=93 --junit-xml=test-reports/report.xml
echo "STATUS=$(cat pytest-coverage.txt | grep 'Required test' | awk '{ print $1 }')" >> $GITHUB_ENV
echo "FAILED=$(cat test-reports/report.xml | awk -F'=' '{print $5}' | awk -F' ' '{gsub(/"/, "", $1); print $1}')" >> $GITHUB_ENV
env:
Expand Down
18 changes: 18 additions & 0 deletions apps/quickbooks_online/migrations/0016_bill_is_retired.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 3.2.14 on 2024-09-03 15:02

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('quickbooks_online', '0015_add_bill_number'),
]

operations = [
migrations.AddField(
model_name='bill',
name='is_retired',
field=models.BooleanField(default=False, help_text='Is Payment sync retried'),
),
]
1 change: 1 addition & 0 deletions apps/quickbooks_online/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,7 @@ class Bill(models.Model):
private_note = models.TextField(help_text='Bill Description')
payment_synced = models.BooleanField(help_text='Payment synced status', default=False)
paid_on_qbo = models.BooleanField(help_text='Payment status in QBO', default=False)
is_retired = models.BooleanField(help_text='Is Payment sync retried', default=False)
exchange_rate = models.FloatField(help_text='Exchange rate', null=True)
created_at = models.DateTimeField(auto_now_add=True, help_text='Created at')
updated_at = models.DateTimeField(auto_now=True, help_text='Updated at')
Expand Down
30 changes: 30 additions & 0 deletions apps/quickbooks_online/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
from datetime import datetime, timezone
from typing import List

from dateutil.relativedelta import relativedelta
from django.utils import timezone as django_timezone

from django.db import transaction
from fyle_qbo_api.logging_middleware import get_logger
from apps.fyle.helpers import get_filter_credit_expenses
Expand Down Expand Up @@ -628,6 +631,30 @@ def process_bill_payments(bill: Bill, workspace_id: int, task_log: TaskLog):
task_log.save()


def validate_for_skipping_payment(bill: Bill, workspace_id: int):
task_log = TaskLog.objects.filter(task_id='PAYMENT_{}'.format(bill.expense_group.id), workspace_id=workspace_id, type='CREATING_BILL_PAYMENT').first()
if task_log:
now = django_timezone.now()

if now - relativedelta(months=2) > task_log.created_at:
bill.is_retired = True
bill.save()
return True

elif now - relativedelta(months=1) > task_log.created_at and now - relativedelta(months=2) < task_log.created_at:
# if updated_at is within 1 months will be skipped
if task_log.updated_at > now - relativedelta(months=1):
return True

# If created is within 1 month
elif now - relativedelta(months=1) < task_log.created_at:
# Skip if updated within the last week
if task_log.updated_at > now - relativedelta(weeks=1):
return True

return False


def create_bill_payment(workspace_id):
fyle_credentials = FyleCredential.objects.get(workspace_id=workspace_id)

Expand All @@ -641,6 +668,9 @@ def create_bill_payment(workspace_id):
for bill in bills:
expense_group_reimbursement_status = check_expenses_reimbursement_status(bill.expense_group.expenses.all(), workspace_id=workspace_id, platform=platform, filter_credit_expenses=filter_credit_expenses)
if expense_group_reimbursement_status:
skip_payment = validate_for_skipping_payment(bill=bill, workspace_id=workspace_id)
if skip_payment:
continue
task_log, _ = TaskLog.objects.update_or_create(workspace_id=workspace_id, task_id='PAYMENT_{}'.format(bill.expense_group.id), defaults={'status': 'IN_PROGRESS', 'type': 'CREATING_BILL_PAYMENT'})
process_bill_payments(bill, workspace_id, task_log)

Expand Down
2 changes: 1 addition & 1 deletion tests/sql_fixtures/migration_fixtures/create_migration.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ bash tests/sql_fixtures/reset_db_fixtures/reset_db.sh
export DATABASE_URL=postgres://postgres:postgres@db:5432/test_qbo_db

# # Running migrations on the fixture database
python manage.py migrate
# python manage.py migrate

read -p "Add SQL script paths separated by spaces if any, else press enter to continue? " scripts

Expand Down
19 changes: 11 additions & 8 deletions tests/sql_fixtures/reset_db_fixtures/reset_db.sql
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,8 @@ CREATE TABLE public.bills (
paid_on_qbo boolean NOT NULL,
payment_synced boolean NOT NULL,
exchange_rate double precision,
bill_number character varying(255)
bill_number character varying(255),
is_retired boolean NOT NULL
);


Expand Down Expand Up @@ -2638,7 +2639,7 @@ COPY public.bill_payments (id, private_note, vendor_id, amount, currency, paymen
-- Data for Name: bills; Type: TABLE DATA; Schema: public; Owner: postgres
--

COPY public.bills (id, accounts_payable_id, vendor_id, department_id, transaction_date, currency, private_note, created_at, updated_at, expense_group_id, paid_on_qbo, payment_synced, exchange_rate, bill_number) FROM stdin;
COPY public.bills (id, accounts_payable_id, vendor_id, department_id, transaction_date, currency, private_note, created_at, updated_at, expense_group_id, paid_on_qbo, payment_synced, exchange_rate, bill_number, is_retired) FROM stdin;
\.


Expand Down Expand Up @@ -2916,6 +2917,7 @@ COPY public.destination_attributes (id, attribute_type, display_name, value, des
225 ACCOUNTS_PAYABLE Accounts Payable Sales of Product Income 79 2022-05-23 04:13:50.086029+00 2022-05-23 04:13:50.086076+00 2 t {"account_type": "Income", "fully_qualified_name": "Sales of Product Income"} f \N
226 ACCOUNTS_PAYABLE Accounts Payable Services 1 2022-05-23 04:13:50.086195+00 2022-05-23 04:13:50.086238+00 2 t {"account_type": "Income", "fully_qualified_name": "Services"} f \N
227 ACCOUNTS_PAYABLE Accounts Payable Stationery & Printing 19 2022-05-23 04:13:50.086351+00 2022-05-23 04:13:50.086392+00 2 t {"account_type": "Expense", "fully_qualified_name": "Stationery & Printing"} f \N
830 CLASS class cc2 5000000000000142240 2022-05-23 11:33:57.280177+00 2022-05-23 11:33:57.280207+00 4 t \N f \N
228 ACCOUNTS_PAYABLE Accounts Payable Supplies 20 2022-05-23 04:13:50.08669+00 2022-05-23 04:13:50.08674+00 2 t {"account_type": "Expense", "fully_qualified_name": "Supplies"} f \N
229 ACCOUNTS_PAYABLE Accounts Payable Taxes & Licenses 21 2022-05-23 04:13:50.086858+00 2022-05-23 04:13:50.086901+00 2 t {"account_type": "Expense", "fully_qualified_name": "Taxes & Licenses"} f \N
230 ACCOUNTS_PAYABLE Accounts Payable Travel 22 2022-05-23 04:13:50.087009+00 2022-05-23 04:13:50.087126+00 2 t {"account_type": "Expense", "fully_qualified_name": "Travel"} f \N
Expand Down Expand Up @@ -3284,6 +3286,7 @@ COPY public.destination_attributes (id, attribute_type, display_name, value, des
593 ACCOUNT Account Cost of Goods Sold 80 2022-05-23 11:21:16.959749+00 2022-05-23 11:21:16.959775+00 3 t {"account_type": "Cost of Goods Sold", "fully_qualified_name": "Cost of Goods Sold"} f \N
594 ACCOUNT Account Depreciation 40 2022-05-23 11:21:16.959937+00 2022-05-23 11:21:16.959965+00 3 t {"account_type": "Other Expense", "fully_qualified_name": "Depreciation"} f \N
595 ACCOUNT Account Disposal Fees 28 2022-05-23 11:21:16.960029+00 2022-05-23 11:21:16.960057+00 3 t {"account_type": "Expense", "fully_qualified_name": "Disposal Fees"} f \N
755 VENDOR vendor Brosnahan Insurance Agency 31 2022-05-23 11:33:51.369394+00 2022-05-23 11:33:51.369422+00 4 t {"email": null} f \N
596 ACCOUNT Account Dues & Subscriptions 10 2022-05-23 11:21:16.96012+00 2022-05-23 11:21:16.960148+00 3 t {"account_type": "Expense", "fully_qualified_name": "Dues & Subscriptions"} f \N
597 ACCOUNT Account Equipment Rental 29 2022-05-23 11:21:16.960211+00 2022-05-23 11:21:16.960239+00 3 t {"account_type": "Expense", "fully_qualified_name": "Equipment Rental"} f \N
598 ACCOUNT Account Insurance 11 2022-05-23 11:21:16.960303+00 2022-05-23 11:21:16.960331+00 3 t {"account_type": "Expense", "fully_qualified_name": "Insurance"} f \N
Expand Down Expand Up @@ -3364,6 +3367,7 @@ COPY public.destination_attributes (id, attribute_type, display_name, value, des
665 ACCOUNTS_PAYABLE Accounts Payable Board of Equalization Payable 90 2022-05-23 11:33:46.953486+00 2022-05-23 11:33:46.953514+00 4 t {"account_type": "Other Current Liability", "fully_qualified_name": "Board of Equalization Payable"} f \N
666 ACCOUNTS_PAYABLE Accounts Payable Commissions & fees 9 2022-05-23 11:33:46.953577+00 2022-05-23 11:33:46.953727+00 4 t {"account_type": "Expense", "fully_qualified_name": "Commissions & fees"} f \N
667 ACCOUNTS_PAYABLE Accounts Payable Cost of Goods Sold 80 2022-05-23 11:33:46.953807+00 2022-05-23 11:33:46.953835+00 4 t {"account_type": "Cost of Goods Sold", "fully_qualified_name": "Cost of Goods Sold"} f \N
756 VENDOR vendor Cal Telephone 32 2022-05-23 11:33:51.369484+00 2022-05-23 11:33:51.369512+00 4 t {"email": null} f \N
668 ACCOUNTS_PAYABLE Accounts Payable Depreciation 40 2022-05-23 11:33:46.953898+00 2022-05-23 11:33:46.953926+00 4 t {"account_type": "Other Expense", "fully_qualified_name": "Depreciation"} f \N
669 ACCOUNTS_PAYABLE Accounts Payable Design income 82 2022-05-23 11:33:46.953989+00 2022-05-23 11:33:46.954017+00 4 t {"account_type": "Income", "fully_qualified_name": "Design income"} f \N
670 ACCOUNTS_PAYABLE Accounts Payable Discounts given 86 2022-05-23 11:33:46.95408+00 2022-05-23 11:33:46.954107+00 4 t {"account_type": "Income", "fully_qualified_name": "Discounts given"} f \N
Expand Down Expand Up @@ -3401,6 +3405,7 @@ COPY public.destination_attributes (id, attribute_type, display_name, value, des
707 ACCOUNTS_PAYABLE Accounts Payable Maintenance and Repair:Equipment Repairs 75 2022-05-23 11:33:46.965624+00 2022-05-23 11:33:46.965846+00 4 t {"account_type": "Expense", "fully_qualified_name": "Maintenance and Repair:Equipment Repairs"} f \N
708 ACCOUNTS_PAYABLE Accounts Payable Meals and Entertainment 13 2022-05-23 11:33:46.965977+00 2022-05-23 11:33:46.966009+00 4 t {"account_type": "Expense", "fully_qualified_name": "Meals and Entertainment"} f \N
709 ACCOUNTS_PAYABLE Accounts Payable Miscellaneous 14 2022-05-23 11:33:46.966294+00 2022-05-23 11:33:46.96634+00 4 t {"account_type": "Other Expense", "fully_qualified_name": "Miscellaneous"} f \N
757 VENDOR vendor Chin's Gas and Oil 33 2022-05-23 11:33:51.369573+00 2022-05-23 11:33:51.369601+00 4 t {"email": null} f \N
710 ACCOUNTS_PAYABLE Accounts Payable Notes Payable 44 2022-05-23 11:33:46.966481+00 2022-05-23 11:33:46.966513+00 4 t {"account_type": "Long Term Liability", "fully_qualified_name": "Notes Payable"} f \N
711 ACCOUNTS_PAYABLE Accounts Payable Office Expenses 15 2022-05-23 11:33:46.966588+00 2022-05-23 11:33:46.96673+00 4 t {"account_type": "Expense", "fully_qualified_name": "Office Expenses"} f \N
712 ACCOUNTS_PAYABLE Accounts Payable Opening Balance Equity 34 2022-05-23 11:33:46.966996+00 2022-05-23 11:33:46.96716+00 4 t {"account_type": "Equity", "fully_qualified_name": "Opening Balance Equity"} f \N
Expand Down Expand Up @@ -3446,9 +3451,6 @@ COPY public.destination_attributes (id, attribute_type, display_name, value, des
752 VENDOR vendor Bob's Burger Joint 56 2022-05-23 11:33:51.369122+00 2022-05-23 11:33:51.36915+00 4 t {"email": null} f \N
753 VENDOR vendor Books by Bessie 30 2022-05-23 11:33:51.369213+00 2022-05-23 11:33:51.369241+00 4 t {"email": "[email protected]"} f \N
754 VENDOR vendor Brian Foster 76 2022-05-23 11:33:51.369303+00 2022-05-23 11:33:51.369331+00 4 t {"email": "[email protected]"} f \N
755 VENDOR vendor Brosnahan Insurance Agency 31 2022-05-23 11:33:51.369394+00 2022-05-23 11:33:51.369422+00 4 t {"email": null} f \N
756 VENDOR vendor Cal Telephone 32 2022-05-23 11:33:51.369484+00 2022-05-23 11:33:51.369512+00 4 t {"email": null} f \N
757 VENDOR vendor Chin's Gas and Oil 33 2022-05-23 11:33:51.369573+00 2022-05-23 11:33:51.369601+00 4 t {"email": null} f \N
758 VENDOR vendor Cigna Health Care 34 2022-05-23 11:33:51.369761+00 2022-05-23 11:33:51.369789+00 4 t {"email": null} f \N
759 VENDOR vendor Computers by Jenni 35 2022-05-23 11:33:51.369852+00 2022-05-23 11:33:51.36988+00 4 t {"email": "[email protected]"} f \N
760 VENDOR vendor Credit Card Misc 74 2022-05-23 11:33:51.369942+00 2022-05-23 11:33:51.369969+00 4 t {"email": null} f \N
Expand Down Expand Up @@ -3516,7 +3518,6 @@ COPY public.destination_attributes (id, attribute_type, display_name, value, des
827 CUSTOMER customer Weiskopf Consulting 29 2022-05-23 11:33:54.828007+00 2022-05-23 11:33:54.828035+00 4 t \N f \N
828 CLASS class Adidas 5000000000000142238 2022-05-23 11:33:57.279868+00 2022-05-23 11:33:57.279915+00 4 t \N f \N
829 CLASS class cc1 5000000000000142239 2022-05-23 11:33:57.280087+00 2022-05-23 11:33:57.280118+00 4 t \N f \N
830 CLASS class cc2 5000000000000142240 2022-05-23 11:33:57.280177+00 2022-05-23 11:33:57.280207+00 4 t \N f \N
831 CLASS class Coachella 5000000000000142241 2022-05-23 11:33:57.280265+00 2022-05-23 11:33:57.280295+00 4 t \N f \N
832 CLASS class Radio 5000000000000142242 2022-05-23 11:33:57.280352+00 2022-05-23 11:33:57.280382+00 4 t \N f \N
833 DEPARTMENT Department Bangalore 2 2022-05-23 11:34:01.996179+00 2022-05-23 11:34:01.996254+00 4 t \N f \N
Expand Down Expand Up @@ -3602,6 +3603,7 @@ COPY public.destination_attributes (id, attribute_type, display_name, value, des
897 CREDIT_CARD_ACCOUNT Credit Card Account 2285 Fyle Credit Card 106 2022-05-25 14:39:12.017261+00 2022-05-25 14:39:12.017339+00 5 t {"account_type": "Credit Card", "fully_qualified_name": "2285 Fyle Credit Card"} f \N
898 CREDIT_CARD_ACCOUNT Credit Card Account 3420 Fyle Credit Card 107 2022-05-25 14:39:12.017495+00 2022-05-25 14:39:12.017545+00 5 t {"account_type": "Credit Card", "fully_qualified_name": "3420 Fyle Credit Card"} f \N
899 CREDIT_CARD_ACCOUNT Credit Card Account Credit Card 103 2022-05-25 14:39:12.017683+00 2022-05-25 14:39:12.017731+00 5 t {"account_type": "Credit Card", "fully_qualified_name": "Credit Card"} f \N
1022 CUSTOMER customer Whitehead and Sons 5 2022-05-25 14:39:19.866292+00 2022-05-25 14:39:19.866335+00 5 t \N f \N
900 BANK_ACCOUNT Bank Account Auto 95 2022-05-25 14:39:12.032124+00 2022-05-25 14:39:12.032169+00 5 t {"account_type": "Bank", "fully_qualified_name": "Auto"} f \N
901 BANK_ACCOUNT Bank Account Cash on hand 94 2022-05-25 14:39:12.032243+00 2022-05-25 14:39:12.032274+00 5 t {"account_type": "Bank", "fully_qualified_name": "Cash on hand"} f \N
902 BANK_ACCOUNT Bank Account Current 81 2022-05-25 14:39:12.032344+00 2022-05-25 14:39:12.032375+00 5 t {"account_type": "Bank", "fully_qualified_name": "Current"} f \N
Expand Down Expand Up @@ -3714,7 +3716,6 @@ COPY public.destination_attributes (id, attribute_type, display_name, value, des
1019 CUSTOMER customer Oxon Insurance Agency:Oxon - Retreat 2014 62 2022-05-25 14:39:19.865835+00 2022-05-25 14:39:19.865864+00 5 t \N f \N
1020 CUSTOMER customer Rob deMontarnal 22 2022-05-25 14:39:19.865921+00 2022-05-25 14:39:19.86607+00 5 t \N f \N
1021 CUSTOMER customer Vendor KSKS 90 2022-05-25 14:39:19.866163+00 2022-05-25 14:39:19.866204+00 5 t \N f \N
1022 CUSTOMER customer Whitehead and Sons 5 2022-05-25 14:39:19.866292+00 2022-05-25 14:39:19.866335+00 5 t \N f \N
1023 CUSTOMER customer Whitehead and Sons:QBO 77 2022-05-25 14:39:19.866426+00 2022-05-25 14:39:19.866471+00 5 t \N f \N
1024 CUSTOMER customer Whitehead and Sons:Whitehead - Employee celebration 60 2022-05-25 14:39:19.866563+00 2022-05-25 14:39:19.866638+00 5 t \N f \N
1025 CLASS class Adidas 5100000000000030664 2022-05-25 14:39:22.066011+00 2022-05-25 14:39:22.066058+00 5 t \N f \N
Expand Down Expand Up @@ -4045,6 +4046,8 @@ COPY public.django_migrations (id, app, name, applied) FROM stdin;
187 workspaces 0046_workspacegeneralsettings_import_code_fields 2024-08-02 07:52:56.494868+00
188 fyle_accounting_mappings 0026_destinationattribute_code 2024-08-02 08:35:52.537882+00
189 quickbooks_online 0015_add_bill_number 2024-08-29 14:29:50.588003+00
190 quickbooks_online 0015_bill_is_retired 2024-09-03 15:08:15.085332+00
191 fyle 0038_expensegroup_export_url 2024-08-03 14:24:57.600169+00
\.


Expand Down Expand Up @@ -33973,7 +33976,7 @@ SELECT pg_catalog.setval('public.django_content_type_id_seq', 47, true);
-- Name: django_migrations_id_seq; Type: SEQUENCE SET; Schema: public; Owner: postgres
--

SELECT pg_catalog.setval('public.django_migrations_id_seq', 188, true);
SELECT pg_catalog.setval('public.django_migrations_id_seq', 191, true);


--
Expand Down
70 changes: 69 additions & 1 deletion tests/test_quickbooks_online/test_tasks.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import json
import logging
from datetime import datetime
import random
from datetime import datetime, timedelta, timezone
from unittest import mock

from django_q.models import Schedule
Expand Down Expand Up @@ -1284,3 +1284,71 @@ def test_skipping_cheque_creation(db, mocker):

task_log = TaskLog.objects.filter(expense_group_id=expense_group.id).first()
assert task_log.type == 'CREATING_CHECK'


def test_skipping_bill_payment(mocker, db):
mocker.patch('apps.quickbooks_online.tasks.load_attachments', return_value=[])
mocker.patch('fyle_integrations_platform_connector.apis.Reimbursements.sync', return_value=None)
mocker.patch('fyle_integrations_platform_connector.apis.Expenses.get', return_value=[])
mocker.patch('qbosdk.apis.Bills.post', return_value=data['post_bill'])
mocker.patch('qbosdk.apis.BillPayments.post', return_value=data['post_bill'])
mocker.patch('qbosdk.apis.Attachments.post', return_value=None)
workspace_id = 3
task_log = TaskLog.objects.filter(workspace_id=workspace_id).first()
task_log.status = 'READY'
task_log.save()

expense_group = ExpenseGroup.objects.get(id=14)
expenses = expense_group.expenses.all()

expense_group.id = random.randint(100, 1500000)
expense_group.save()

for expense in expenses:
expense.expense_group_id = expense_group.id
expense.save()

expense_group.expenses.set(expenses)
expense_group.save()

create_bill(expense_group, task_log.id, False)

bill = Bill.objects.last()
task_log = TaskLog.objects.get(id=task_log.id)
task_log.expense_group = bill.expense_group
task_log.save()

reimbursements = data['reimbursements']

Reimbursement.create_or_update_reimbursement_objects(reimbursements=reimbursements, workspace_id=workspace_id)

task_log = TaskLog.objects.create(workspace_id=workspace_id, type='CREATING_BILL_PAYMENT', task_id='PAYMENT_{}'.format(bill.expense_group.id), status='FAILED')
updated_at = task_log.updated_at
create_bill_payment(workspace_id)

task_log = TaskLog.objects.get(workspace_id=workspace_id, type='CREATING_BILL_PAYMENT', task_id='PAYMENT_{}'.format(bill.expense_group.id))
assert task_log.updated_at == updated_at

now = datetime.now().replace(tzinfo=timezone.utc)
updated_at = now - timedelta(days=25)
# Update created_at to more than 2 months ago (more than 60 days)
TaskLog.objects.filter(task_id='PAYMENT_{}'.format(bill.expense_group.id)).update(
created_at=now - timedelta(days=61), # More than 2 months ago
updated_at=updated_at # Updated within the last 1 month
)

task_log = TaskLog.objects.get(task_id='PAYMENT_{}'.format(bill.expense_group.id))

create_bill_payment(workspace_id)
task_log.refresh_from_db()
assert task_log.updated_at == updated_at

updated_at = now - timedelta(days=25)
# Update created_at to between 1 and 2 months ago (between 30 and 60 days)
TaskLog.objects.filter(task_id='PAYMENT_{}'.format(bill.expense_group.id)).update(
created_at=now - timedelta(days=45), # Between 1 and 2 months ago
updated_at=updated_at # Updated within the last 1 month
)
create_bill_payment(workspace_id)
task_log.refresh_from_db()
assert task_log.updated_at == updated_at

0 comments on commit 38c7516

Please sign in to comment.