Skip to content

Commit

Permalink
feat: add bill numbers to the qbo create bill payload (#663)
Browse files Browse the repository at this point in the history
* feat: add bill numbers to the qbo bill payload

* test: update fixtures

* fix: update get_bill logic to account for bad data in the expense group's description

* fix: add script to fix inconsistent expense group fields data

* refactor: code style

* test: unit test `get_bill_number`

(cherry picked from commit 94f4a67)
  • Loading branch information
1 parent a83a2c1 commit 43a1c7c
Show file tree
Hide file tree
Showing 7 changed files with 136 additions and 4 deletions.
18 changes: 18 additions & 0 deletions apps/quickbooks_online/migrations/0015_add_bill_number.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 3.2.14 on 2024-08-28 12:56

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('quickbooks_online', '0014_auto_20230422_2007'),
]

operations = [
migrations.AddField(
model_name='bill',
name='bill_number',
field=models.CharField(help_text='Bill Number', max_length=255, null=True),
),
]
17 changes: 17 additions & 0 deletions apps/quickbooks_online/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,21 @@ def get_credit_card_purchase_number(expense_group: ExpenseGroup, expense: Expens
return expense.expense_number


def get_bill_number(expense_group: ExpenseGroup):
expense_group_settings = ExpenseGroupSettings.objects.get(workspace_id=expense_group.workspace_id)

group_fields = expense_group_settings.corporate_credit_card_expense_group_fields
if expense_group.fund_source == 'PERSONAL':
group_fields = expense_group_settings.reimbursable_expense_group_fields

bill_number_field = 'claim_number'
if 'expense_id' in group_fields:
bill_number_field = 'expense_number'

bill_number = expense_group.expenses.first().__getattribute__(bill_number_field)
return bill_number


class Bill(models.Model):
"""
QBO Bill
Expand All @@ -197,6 +212,7 @@ class Bill(models.Model):
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')
bill_number = models.CharField(max_length=255, help_text='Bill Number', null=True)

class Meta:
db_table = 'bills'
Expand Down Expand Up @@ -235,6 +251,7 @@ def create_bill(expense_group: ExpenseGroup):
'transaction_date': get_transaction_date(expense_group),
'private_note': private_note,
'currency': expense.currency,
'bill_number': get_bill_number(expense_group),
},
)
return bill_object
Expand Down
1 change: 1 addition & 0 deletions apps/quickbooks_online/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -700,6 +700,7 @@ def __construct_bill(self, bill: Bill, bill_lineitems: List[BillLineitem]) -> Di
'TxnDate': bill.transaction_date,
'CurrencyRef': {'value': bill.currency},
'PrivateNote': bill.private_note,
'DocNumber': bill.bill_number,
'Line': lines,
}

Expand Down
38 changes: 38 additions & 0 deletions sql/scripts/027-fix-expense-group-settings.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
-- Script to fix expense groups (reimbursable and CCC)
-- by adding expense_number/claim_number to groups having
-- expense_id but no expense_number OR having report_id but no claim_number

rollback;
begin;

-- expense_group_settings.reimbursable_expense_group_fields
update expense_group_settings
set reimbursable_expense_group_fields = array_append(reimbursable_expense_group_fields, 'expense_number')
where reimbursable_expense_group_fields::text ilike '%expense_id%'
and reimbursable_expense_group_fields::text not ilike '%expense_number%';

update expense_group_settings
set reimbursable_expense_group_fields = array_append(reimbursable_expense_group_fields, 'claim_number')
where reimbursable_expense_group_fields::text ilike '%report_id%'
and reimbursable_expense_group_fields::text not ilike '%claim_number%';


-- expense_group_settings.corporate_credit_card_expense_group_fields
update expense_group_settings
set corporate_credit_card_expense_group_fields = array_append(corporate_credit_card_expense_group_fields, 'expense_number')
where corporate_credit_card_expense_group_fields::text ilike '%expense_id%'
and corporate_credit_card_expense_group_fields::text not ilike '%expense_number%';

update expense_group_settings
set corporate_credit_card_expense_group_fields = array_append(corporate_credit_card_expense_group_fields, 'claim_number')
where corporate_credit_card_expense_group_fields::text ilike '%report_id%'
and corporate_credit_card_expense_group_fields::text not ilike '%claim_number%';

-- Check if all groups have been fixed: Should result in 0s
select count(*) from expense_group_settings where reimbursable_expense_group_fields::text ilike '%expense_id%' and reimbursable_expense_group_fields::text not ilike '%expense_number%';

select count(*) from expense_group_settings where reimbursable_expense_group_fields::text ilike '%report_id%' and reimbursable_expense_group_fields::text not ilike '%claim_number%';

select count(*) from expense_group_settings where corporate_credit_card_expense_group_fields::text ilike '%report_id%' and corporate_credit_card_expense_group_fields::text not ilike '%claim_number%';

select count(*) from expense_group_settings where corporate_credit_card_expense_group_fields::text ilike '%expense_id%' and corporate_credit_card_expense_group_fields::text not ilike '%expense_number%';
11 changes: 7 additions & 4 deletions tests/sql_fixtures/reset_db_fixtures/reset_db.sql
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
--

-- Dumped from database version 15.7 (Debian 15.7-1.pgdg120+1)
-- Dumped by pg_dump version 15.7 (Debian 15.7-1.pgdg120+1)
-- Dumped by pg_dump version 15.8 (Debian 15.8-1.pgdg120+1)

SET statement_timeout = 0;
SET lock_timeout = 0;
Expand Down Expand Up @@ -263,7 +263,8 @@ CREATE TABLE public.bills (
expense_group_id integer NOT NULL,
paid_on_qbo boolean NOT NULL,
payment_synced boolean NOT NULL,
exchange_rate double precision
exchange_rate double precision,
bill_number character varying(255)
);


Expand Down Expand Up @@ -2635,7 +2636,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) 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) FROM stdin;
\.


Expand Down Expand Up @@ -4040,6 +4041,7 @@ COPY public.django_migrations (id, app, name, applied) FROM stdin;
185 fyle 0037_auto_20240625_1035 2024-06-28 11:32:54.506849+00
186 fyle 0038_expensegroup_export_url 2024-08-03 14:24:57.600169+00
187 workspaces 0045_alter_workspacegeneralsettings_is_tax_override_enabled 2024-08-03 14:24:57.840963+00
188 quickbooks_online 0015_add_bill_number 2024-08-29 14:29:50.588003+00
\.


Expand Down Expand Up @@ -33968,7 +33970,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', 187, true);
SELECT pg_catalog.setval('public.django_migrations_id_seq', 188, true);


--
Expand Down Expand Up @@ -35858,3 +35860,4 @@ ALTER TABLE ONLY public.workspaces_user
--
-- PostgreSQL database dump complete
--

6 changes: 6 additions & 0 deletions tests/test_quickbooks_online/fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@
"AccountBasedExpenseLineDetail": {"AccountRef": {"value": "57"},"CustomerRef": {"value": "None"},"ClassRef": {"value": "None"},"TaxCodeRef": {"value": "None"},"TaxAmount": 0.0,"BillableStatus": "NotBillable"},
}
],
"DocNumber": "C/2022/01/R/8",
},
"bill_payload_with_tax_override": {
"DocNumber": "C/2022/01/R/8",
'VendorRef': {'value': '84'},
'APAccountRef': {'value': '33'},
'DepartmentRef': {'value': None},
Expand Down Expand Up @@ -55,6 +57,7 @@
}
},
"bill_payload_item_based_payload": {
"DocNumber": "C/2022/01/R/8",
"VendorRef": {"value": "43"},
"APAccountRef": {"value": "33"},
"DepartmentRef": {"value": "None"},
Expand All @@ -71,6 +74,7 @@
],
},
"bill_payload_item_based_payload_with_tax_override":{
"DocNumber": "C/2022/01/R/8",
"VendorRef": {"value": "84"},
"APAccountRef": {"value": "33"},
"DepartmentRef": {"value": None},
Expand Down Expand Up @@ -117,6 +121,7 @@
},
},
"bill_payload_item_and_account_based_payload": {
"DocNumber": "C/2022/01/R/8",
'VendorRef': {'value': '84'},
'APAccountRef': {'value': '33'},
'DepartmentRef': {'value': None},
Expand All @@ -139,6 +144,7 @@
],
},
"bill_payload_item_and_account_based_payload_with_tax_override": {
"DocNumber": "C/2022/01/R/8",
"VendorRef": {"value": "84"},
"APAccountRef": {"value": "33"},
"DepartmentRef": {"value": None},
Expand Down
49 changes: 49 additions & 0 deletions tests/test_quickbooks_online/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
CreditCardPurchaseLineitem,
JournalEntry,
JournalEntryLineitem,
get_bill_number,
get_ccc_account_id,
get_class_id_or_none,
get_customer_id_or_none,
Expand Down Expand Up @@ -305,6 +306,54 @@ def test_get_ccc_account_id():
assert ccc_account_id == '41'


@pytest.mark.django_db(databases=['default'])
def test_get_bill_number():
expense_group = ExpenseGroup.objects.get(id=3)
expense_group_settings = ExpenseGroupSettings.objects.get(workspace_id=expense_group.workspace_id)

# Reimbursable - group by report
expense_group.fund_source = 'PERSONAL'
expense_group.save()

if 'expense_id' in expense_group_settings.reimbursable_expense_group_fields:
expense_group_settings.reimbursable_expense_group_fields.remove('expense_id')
expense_group_settings.save()

bill_number = get_bill_number(expense_group)
assert bill_number.startswith('C/'), 'Bill numbers for bills grouped by report should be report numbers'

# Reimbursable - group by expense
expense_group.fund_source = 'PERSONAL'
expense_group.save()

expense_group_settings.reimbursable_expense_group_fields.append('expense_id')
expense_group_settings.save()

bill_number = get_bill_number(expense_group)
assert bill_number.startswith('E/'), 'Bill numbers for bills grouped by expense should be expense numbers'

# CCC - group by report
expense_group.fund_source = 'CCC'
expense_group.save()

if 'expense_id' in expense_group_settings.corporate_credit_card_expense_group_fields:
expense_group_settings.corporate_credit_card_expense_group_fields.remove('expense_id')
expense_group_settings.save()

bill_number = get_bill_number(expense_group)
assert bill_number.startswith('C/'), 'Bill numbers for bills grouped by report should be report numbers'

# CCC - group by expense
expense_group.fund_source = 'CCC'
expense_group.save()

expense_group_settings.corporate_credit_card_expense_group_fields.append('expense_id')
expense_group_settings.save()

bill_number = get_bill_number(expense_group)
assert bill_number.startswith('E/'), 'Bill numbers for bills grouped by expense should be expense numbers'


def test_support_post_date_integrations(mocker, db):
workspace_id = 1

Expand Down

0 comments on commit 43a1c7c

Please sign in to comment.