Skip to content

Commit 6a36c24

Browse files
committed
Refactored PI-specific invoices
The PI-specific is now implemented in the `PIInvoice` class. This class takes in the entire billable invoice and will export the PI invoices to local storage and S3 Because the `upload_to_s3` function is no longer used, it is removed. Since the `export_s3` function in the Invoice class does not need to be concerned about file extensions, the test case for s3 exporting is somewhat simplified, only checking that the format of the output paths are correct.
1 parent 19ca40b commit 6a36c24

File tree

4 files changed

+107
-56
lines changed

4 files changed

+107
-56
lines changed
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import os
2+
from dataclasses import dataclass
3+
4+
import pandas
5+
6+
import process_report.invoices.invoice as invoice
7+
import process_report.util as util
8+
9+
10+
@dataclass
11+
class PIInvoice(invoice.Invoice):
12+
def _prepare(self):
13+
self.pi_list = self.data[invoice.PI_FIELD].unique()
14+
15+
def export(self):
16+
def _export_pi_invoice(pi):
17+
if pandas.isna(pi):
18+
return
19+
pi_projects = self.data[self.data[invoice.PI_FIELD] == pi]
20+
pi_instituition = pi_projects[invoice.INSTITUTION_FIELD].iat[0]
21+
pi_projects.to_csv(
22+
f"{self.name}/{pi_instituition}_{pi} {self.invoice_month}.csv"
23+
)
24+
25+
if not os.path.exists(
26+
self.name
27+
): # self.name is name of folder storing invoices
28+
os.mkdir(self.name)
29+
30+
for pi in self.pi_list:
31+
_export_pi_invoice(pi)
32+
33+
def export_s3(self, s3_bucket):
34+
def _export_s3_pi_invoice(pi_invoice):
35+
pi_invoice_path = os.path.join(self.name, pi_invoice)
36+
striped_invoice_path = os.path.splitext(pi_invoice_path)[0]
37+
output_s3_path = f"Invoices/{self.invoice_month}/{striped_invoice_path}.csv"
38+
output_s3_archive_path = f"Invoices/{self.invoice_month}/Archive/{striped_invoice_path} {util.get_iso8601_time()}.csv"
39+
s3_bucket.upload_file(pi_invoice_path, output_s3_path)
40+
s3_bucket.upload_file(pi_invoice_path, output_s3_archive_path)
41+
42+
for pi_invoice in os.listdir(self.name):
43+
_export_s3_pi_invoice(pi_invoice)

process_report/process_report.py

Lines changed: 7 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import argparse
2-
import os
32
import sys
43
import datetime
54

@@ -14,6 +13,7 @@
1413
billable_invoice,
1514
NERC_total_invoice,
1615
bu_internal_invoice,
16+
pi_specific_invoice,
1717
)
1818

1919

@@ -262,17 +262,13 @@ def main():
262262
subsidy_amount=args.BU_subsidy_amount,
263263
)
264264

265-
process_and_export_invoices([nerc_total_inv, bu_internal_inv], args.upload_to_s3)
266-
267-
export_pi_billables(billable_inv.data.copy(), args.output_folder, invoice_month)
268-
269-
if args.upload_to_s3:
270-
invoice_list = list()
271-
272-
for pi_invoice in os.listdir(args.output_folder):
273-
invoice_list.append(os.path.join(args.output_folder, pi_invoice))
265+
pi_inv = pi_specific_invoice.PIInvoice(
266+
name=args.output_folder, invoice_month=invoice_month, data=billable_inv.data
267+
)
274268

275-
upload_to_s3(invoice_list, invoice_month)
269+
process_and_export_invoices(
270+
[nerc_total_inv, bu_internal_inv, pi_inv], args.upload_to_s3
271+
)
276272

277273

278274
def fetch_s3_invoices(invoice_month):
@@ -390,33 +386,5 @@ def export_billables(dataframe, output_file):
390386
dataframe.to_csv(output_file, index=False)
391387

392388

393-
def export_pi_billables(dataframe: pandas.DataFrame, output_folder, invoice_month):
394-
if not os.path.exists(output_folder):
395-
os.mkdir(output_folder)
396-
397-
pi_list = dataframe[PI_FIELD].unique()
398-
399-
for pi in pi_list:
400-
if pandas.isna(pi):
401-
continue
402-
pi_projects = dataframe[dataframe[PI_FIELD] == pi]
403-
pi_instituition = pi_projects[INSTITUTION_FIELD].iat[0]
404-
pi_projects.to_csv(
405-
output_folder + f"/{pi_instituition}_{pi}_{invoice_month}.csv", index=False
406-
)
407-
408-
409-
def upload_to_s3(invoice_list: list, invoice_month):
410-
invoice_bucket = get_invoice_bucket()
411-
for invoice_filename in invoice_list:
412-
striped_filename = os.path.splitext(invoice_filename)[0]
413-
invoice_s3_path = (
414-
f"Invoices/{invoice_month}/{striped_filename} {invoice_month}.csv"
415-
)
416-
invoice_s3_path_archive = f"Invoices/{invoice_month}/Archive/{striped_filename} {invoice_month} {get_iso8601_time()}.csv"
417-
invoice_bucket.upload_file(invoice_filename, invoice_s3_path)
418-
invoice_bucket.upload_file(invoice_filename, invoice_s3_path_archive)
419-
420-
421389
if __name__ == "__main__":
422390
main()

process_report/tests/unit_tests.py

Lines changed: 31 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -182,12 +182,13 @@ def setUp(self):
182182

183183
def test_export_pi(self):
184184
output_dir = tempfile.TemporaryDirectory()
185-
process_report.export_pi_billables(
186-
self.dataframe, output_dir.name, self.invoice_month
185+
pi_inv = test_utils.new_pi_specific_invoice(
186+
output_dir.name, invoice_month=self.invoice_month, data=self.dataframe
187187
)
188-
189-
pi_csv_1 = f'{self.dataframe["Institution"][0]}_{self.dataframe["Manager (PI)"][0]}_{self.dataframe["Invoice Month"][0]}.csv'
190-
pi_csv_2 = f'{self.dataframe["Institution"][3]}_{self.dataframe["Manager (PI)"][3]}_{self.dataframe["Invoice Month"][3]}.csv'
188+
pi_inv.process()
189+
pi_inv.export()
190+
pi_csv_1 = f'{self.dataframe["Institution"][0]}_{self.dataframe["Manager (PI)"][0]} {self.dataframe["Invoice Month"][0]}.csv'
191+
pi_csv_2 = f'{self.dataframe["Institution"][3]}_{self.dataframe["Manager (PI)"][3]} {self.dataframe["Invoice Month"][3]}.csv'
191192
self.assertIn(pi_csv_1, os.listdir(output_dir.name))
192193
self.assertIn(pi_csv_2, os.listdir(output_dir.name))
193194
self.assertEqual(
@@ -789,32 +790,46 @@ def test_process_lenovo(self):
789790

790791
class TestUploadToS3(TestCase):
791792
@mock.patch("process_report.process_report.get_invoice_bucket")
792-
@mock.patch("process_report.process_report.get_iso8601_time")
793-
def test_remove_prefix(self, mock_get_time, mock_get_bucket):
793+
@mock.patch("process_report.util.get_iso8601_time")
794+
def test_upload_to_s3(self, mock_get_time, mock_get_bucket):
794795
mock_bucket = mock.MagicMock()
795796
mock_get_bucket.return_value = mock_bucket
796797
mock_get_time.return_value = "0"
797798

798799
invoice_month = "2024-03"
799-
filenames = ["test.csv", "test2.test.csv", "test3"]
800+
filenames = ["test-test", "test2.test", "test3"]
801+
sample_base_invoice = test_utils.new_base_invoice(invoice_month=invoice_month)
802+
800803
answers = [
801-
("test.csv", f"Invoices/{invoice_month}/test {invoice_month}.csv"),
802804
(
803-
"test.csv",
804-
f"Invoices/{invoice_month}/Archive/test {invoice_month} 0.csv",
805+
f"test-test {invoice_month}.csv",
806+
f"Invoices/{invoice_month}/test-test {invoice_month}.csv",
805807
),
806808
(
807-
"test2.test.csv",
809+
f"test-test {invoice_month}.csv",
810+
f"Invoices/{invoice_month}/Archive/test-test {invoice_month} 0.csv",
811+
),
812+
(
813+
f"test2.test {invoice_month}.csv",
808814
f"Invoices/{invoice_month}/test2.test {invoice_month}.csv",
809815
),
810816
(
811-
"test2.test.csv",
817+
f"test2.test {invoice_month}.csv",
812818
f"Invoices/{invoice_month}/Archive/test2.test {invoice_month} 0.csv",
813819
),
814-
("test3", f"Invoices/{invoice_month}/test3 {invoice_month}.csv"),
815-
("test3", f"Invoices/{invoice_month}/Archive/test3 {invoice_month} 0.csv"),
820+
(
821+
f"test3 {invoice_month}.csv",
822+
f"Invoices/{invoice_month}/test3 {invoice_month}.csv",
823+
),
824+
(
825+
f"test3 {invoice_month}.csv",
826+
f"Invoices/{invoice_month}/Archive/test3 {invoice_month} 0.csv",
827+
),
816828
]
817829

818-
process_report.upload_to_s3(filenames, invoice_month)
830+
for filename in filenames:
831+
sample_base_invoice.name = filename
832+
sample_base_invoice.export_s3(mock_bucket)
833+
819834
for i, call_args in enumerate(mock_bucket.upload_file.call_args_list):
820835
self.assertTrue(answers[i] in call_args)

process_report/tests/util.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,19 @@
11
import pandas
22

3-
from process_report.invoices import billable_invoice, bu_internal_invoice
3+
from process_report.invoices import (
4+
invoice,
5+
billable_invoice,
6+
bu_internal_invoice,
7+
pi_specific_invoice,
8+
)
9+
10+
11+
def new_base_invoice(
12+
name="",
13+
invoice_month="0000-00",
14+
data=pandas.DataFrame(),
15+
):
16+
return invoice.Invoice(name, invoice_month, data)
417

518

619
def new_billable_invoice(
@@ -27,3 +40,15 @@ def new_bu_internal_invoice(
2740
return bu_internal_invoice.BUInternalInvoice(
2841
name, invoice_month, data, subsidy_amount
2942
)
43+
44+
45+
def new_pi_specific_invoice(
46+
name="",
47+
invoice_month="0000-00",
48+
data=pandas.DataFrame(),
49+
):
50+
return pi_specific_invoice.PIInvoice(
51+
name,
52+
invoice_month,
53+
data,
54+
)

0 commit comments

Comments
 (0)