Skip to content

Commit 17ba421

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 fb0be7c commit 17ba421

File tree

4 files changed

+104
-53
lines changed

4 files changed

+104
-53
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: 9 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
nonbillable_invoice,
1616
billable_invoice,
1717
NERC_total_invoice,
18+
pi_specific_invoice,
1819
)
1920

2021

@@ -283,16 +284,16 @@ def main():
283284
bucket = get_invoice_bucket()
284285
invoice.export_s3(bucket)
285286

286-
export_pi_billables(billable_inv.data, args.output_folder, invoice_month)
287-
export_BU_only(billable_inv.data, args.BU_invoice_file, args.BU_subsidy_amount)
288-
287+
pi_inv = pi_specific_invoice.PIInvoice(
288+
name=args.output_folder, invoice_month=invoice_month, data=billable_inv.data
289+
)
290+
pi_inv.process()
291+
pi_inv.export()
289292
if args.upload_to_s3:
290-
invoice_list = list()
293+
bucket = get_invoice_bucket()
294+
pi_inv.export_s3(bucket)
291295

292-
for pi_invoice in os.listdir(args.output_folder):
293-
invoice_list.append(os.path.join(args.output_folder, pi_invoice))
294-
295-
upload_to_s3(invoice_list, invoice_month)
296+
export_BU_only(billable_inv.data, args.BU_invoice_file, args.BU_subsidy_amount)
296297

297298

298299
def fetch_s3_invoices(invoice_month):
@@ -410,22 +411,6 @@ def export_billables(dataframe, output_file):
410411
dataframe.to_csv(output_file, index=False)
411412

412413

413-
def export_pi_billables(dataframe: pandas.DataFrame, output_folder, invoice_month):
414-
if not os.path.exists(output_folder):
415-
os.mkdir(output_folder)
416-
417-
pi_list = dataframe[PI_FIELD].unique()
418-
419-
for pi in pi_list:
420-
if pandas.isna(pi):
421-
continue
422-
pi_projects = dataframe[dataframe[PI_FIELD] == pi]
423-
pi_instituition = pi_projects[INSTITUTION_FIELD].iat[0]
424-
pi_projects.to_csv(
425-
output_folder + f"/{pi_instituition}_{pi}_{invoice_month}.csv", index=False
426-
)
427-
428-
429414
def export_BU_only(dataframe: pandas.DataFrame, output_file, subsidy_amount):
430415
def get_project(row):
431416
project_alloc = row[PROJECT_FIELD]
@@ -483,17 +468,5 @@ def _apply_subsidy(dataframe, subsidy_amount):
483468
return dataframe
484469

485470

486-
def upload_to_s3(invoice_list: list, invoice_month):
487-
invoice_bucket = get_invoice_bucket()
488-
for invoice_filename in invoice_list:
489-
striped_filename = os.path.splitext(invoice_filename)[0]
490-
invoice_s3_path = (
491-
f"Invoices/{invoice_month}/{striped_filename} {invoice_month}.csv"
492-
)
493-
invoice_s3_path_archive = f"Invoices/{invoice_month}/Archive/{striped_filename} {invoice_month} {get_iso8601_time()}.csv"
494-
invoice_bucket.upload_file(invoice_filename, invoice_s3_path)
495-
invoice_bucket.upload_file(invoice_filename, invoice_s3_path_archive)
496-
497-
498471
if __name__ == "__main__":
499472
main()

process_report/tests/unit_tests.py

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

184184
def test_export_pi(self):
185185
output_dir = tempfile.TemporaryDirectory()
186-
process_report.export_pi_billables(
187-
self.dataframe, output_dir.name, self.invoice_month
186+
pi_inv = test_utils.new_pi_specific_invoice(
187+
output_dir.name, invoice_month=self.invoice_month, data=self.dataframe
188188
)
189-
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'
189+
pi_inv.process()
190+
pi_inv.export()
191+
pi_csv_1 = f'{self.dataframe["Institution"][0]}_{self.dataframe["Manager (PI)"][0]} {self.dataframe["Invoice Month"][0]}.csv'
192+
pi_csv_2 = f'{self.dataframe["Institution"][3]}_{self.dataframe["Manager (PI)"][3]} {self.dataframe["Invoice Month"][3]}.csv'
192193
self.assertIn(pi_csv_1, os.listdir(output_dir.name))
193194
self.assertIn(pi_csv_2, os.listdir(output_dir.name))
194195
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: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
import pandas
22

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

513

614
def new_billable_invoice(
@@ -19,3 +27,15 @@ def new_billable_invoice(
1927
nonbillable_projects,
2028
old_pi_filepath,
2129
)
30+
31+
32+
def new_pi_specific_invoice(
33+
name="",
34+
invoice_month="0000-00",
35+
data=pandas.DataFrame(),
36+
):
37+
return pi_specific_invoice.PIInvoice(
38+
name,
39+
invoice_month,
40+
data,
41+
)

0 commit comments

Comments
 (0)