Skip to content

Commit 42c283e

Browse files
committed
Exported prepay groups invoices as PDFs
1 parent 2b80988 commit 42c283e

File tree

4 files changed

+492
-0
lines changed

4 files changed

+492
-0
lines changed
Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
import os
2+
import sys
3+
from dataclasses import dataclass
4+
import subprocess
5+
import tempfile
6+
import logging
7+
8+
import pandas
9+
from jinja2 import Environment, FileSystemLoader
10+
11+
import process_report.invoices.invoice as invoice
12+
import process_report.util as util
13+
14+
15+
TEMPLATE_DIR_PATH = "process_report/templates"
16+
17+
18+
logger = logging.getLogger(__name__)
19+
logging.basicConfig(level=logging.INFO)
20+
21+
22+
@dataclass
23+
class MOCAGroupInvoice(invoice.Invoice):
24+
CREDIT_COLUMN_COPY_LIST = [
25+
invoice.INVOICE_DATE_FIELD,
26+
invoice.INVOICE_EMAIL_FIELD,
27+
invoice.GROUP_NAME_FIELD,
28+
invoice.GROUP_INSTITUTION_FIELD,
29+
]
30+
TOTAL_COLUMN_LIST = [
31+
invoice.COST_FIELD,
32+
invoice.GROUP_BALANCE_USED_FIELD,
33+
invoice.CREDIT_FIELD,
34+
invoice.BALANCE_FIELD,
35+
]
36+
37+
DOLLAR_COLUMN_LIST = [
38+
invoice.RATE_FIELD,
39+
invoice.GROUP_BALANCE_FIELD,
40+
invoice.COST_FIELD,
41+
invoice.GROUP_BALANCE_USED_FIELD,
42+
invoice.CREDIT_FIELD,
43+
invoice.BALANCE_FIELD,
44+
]
45+
46+
export_columns_list = [
47+
invoice.INVOICE_DATE_FIELD,
48+
invoice.PROJECT_FIELD,
49+
invoice.PROJECT_ID_FIELD,
50+
invoice.PI_FIELD,
51+
invoice.INVOICE_EMAIL_FIELD,
52+
invoice.INVOICE_ADDRESS_FIELD,
53+
invoice.INSTITUTION_FIELD,
54+
invoice.INSTITUTION_ID_FIELD,
55+
invoice.SU_HOURS_FIELD,
56+
invoice.SU_TYPE_FIELD,
57+
invoice.RATE_FIELD,
58+
invoice.GROUP_NAME_FIELD,
59+
invoice.GROUP_INSTITUTION_FIELD,
60+
invoice.GROUP_BALANCE_FIELD,
61+
invoice.COST_FIELD,
62+
invoice.GROUP_BALANCE_USED_FIELD,
63+
invoice.CREDIT_FIELD,
64+
invoice.CREDIT_CODE_FIELD,
65+
invoice.BALANCE_FIELD,
66+
]
67+
68+
prepay_credits: pandas.DataFrame
69+
70+
@staticmethod
71+
def _get_existing_columns(dataframe: pandas.DataFrame, colum_name_list):
72+
existing_columns = list()
73+
for column_name in colum_name_list:
74+
if column_name in dataframe.columns:
75+
existing_columns.append(column_name)
76+
77+
return existing_columns
78+
79+
def _prepare(self):
80+
self.export_data = self.data[
81+
self.data[invoice.IS_BILLABLE_FIELD] & ~self.data[invoice.MISSING_PI_FIELD]
82+
]
83+
self.export_data = self.export_data[
84+
~self.export_data[invoice.GROUP_NAME_FIELD].isna()
85+
]
86+
self.group_list = self.export_data[invoice.GROUP_NAME_FIELD].unique()
87+
88+
def _get_group_dataframe(self, data, group):
89+
def add_dollar_sign(data):
90+
if pandas.isna(data):
91+
return data
92+
else:
93+
return "$" + str(data)
94+
95+
group_projects = (
96+
data[data[invoice.GROUP_NAME_FIELD] == group].copy().reset_index(drop=True)
97+
)
98+
99+
# Add row if group has credit this month
100+
group_credit_mask = (
101+
self.prepay_credits[invoice.PREPAY_MONTH_FIELD] == self.invoice_month
102+
) & (self.prepay_credits[invoice.PREPAY_GROUP_NAME_FIELD] == group)
103+
group_credit_info = self.prepay_credits[group_credit_mask]
104+
if not group_credit_info.empty:
105+
group_credit = group_credit_info.squeeze()[invoice.PREPAY_CREDIT_FIELD]
106+
group_projects.loc[len(group_projects)] = None
107+
108+
existing_columns = self._get_existing_columns(
109+
group_projects, self.CREDIT_COLUMN_COPY_LIST
110+
)
111+
for column_name in existing_columns:
112+
group_projects.loc[
113+
group_projects.index[-1], column_name
114+
] = group_projects.loc[0, column_name]
115+
116+
group_projects.loc[
117+
group_projects.index[-1], [invoice.COST_FIELD, invoice.BALANCE_FIELD]
118+
] = [group_credit] * 2
119+
120+
# Add sum row
121+
group_projects.loc[len(group_projects)] = None
122+
existing_columns = self._get_existing_columns(
123+
group_projects, self.TOTAL_COLUMN_LIST
124+
)
125+
group_projects.loc[
126+
group_projects.index[-1], invoice.INVOICE_DATE_FIELD
127+
] = "Total"
128+
group_projects.loc[group_projects.index[-1], existing_columns] = group_projects[
129+
existing_columns
130+
].sum()
131+
132+
# Add dollar signs
133+
existing_columns = self._get_existing_columns(
134+
group_projects, self.DOLLAR_COLUMN_LIST
135+
)
136+
for column_name in existing_columns:
137+
group_projects[column_name] = group_projects[column_name].apply(
138+
add_dollar_sign
139+
)
140+
141+
group_projects.fillna("", inplace=True)
142+
143+
return group_projects
144+
145+
def export(self):
146+
def _create_html_invoice(temp_fd):
147+
environment = Environment(loader=FileSystemLoader(TEMPLATE_DIR_PATH))
148+
template = environment.get_template("pi_invoice.html")
149+
content = template.render(
150+
data=group_dataframe,
151+
)
152+
temp_fd.write(content)
153+
temp_fd.flush()
154+
155+
def _create_pdf_invoice(temp_fd_name):
156+
chrome_binary_location = os.environ.get(
157+
"CHROME_BIN_PATH", "usr/bin/chromium"
158+
)
159+
if not os.path.exists(chrome_binary_location):
160+
sys.exit(
161+
f"Chrome binary does not exist at {chrome_binary_location}. Make sure the env var CHROME_BIN_PATH is set correctly or that Google Chrome is installed"
162+
)
163+
164+
invoice_pdf_path = f"{self.name}/{group_instituition}_{group_contact_email}_{self.invoice_month}.pdf"
165+
subprocess.run(
166+
[
167+
chrome_binary_location,
168+
"--headless",
169+
"--no-sandbox",
170+
f"--print-to-pdf={invoice_pdf_path}",
171+
"--no-pdf-header-footer",
172+
"file://" + temp_fd_name,
173+
],
174+
capture_output=True,
175+
)
176+
177+
self._filter_columns()
178+
179+
if not os.path.exists(self.name):
180+
os.mkdir(self.name)
181+
182+
for group in self.group_list:
183+
group_dataframe = self._get_group_dataframe(self.export_data, group)
184+
group_instituition = group_dataframe[invoice.GROUP_INSTITUTION_FIELD].iat[0]
185+
group_contact_email = group_dataframe[invoice.INVOICE_EMAIL_FIELD].iat[0]
186+
187+
with tempfile.NamedTemporaryFile(mode="w", suffix=".html") as temp_fd:
188+
_create_html_invoice(temp_fd)
189+
_create_pdf_invoice(temp_fd.name)
190+
191+
def export_s3(self, s3_bucket):
192+
def _export_s3_group_invoice(group_invoice):
193+
group_invoice_path = os.path.join(self.name, group_invoice)
194+
striped_invoice_path = os.path.splitext(group_invoice_path)[0]
195+
output_s3_path = f"Invoices/{self.invoice_month}/{striped_invoice_path}.pdf"
196+
output_s3_archive_path = f"Invoices/{self.invoice_month}/Archive/{striped_invoice_path} {util.get_iso8601_time()}.pdf"
197+
s3_bucket.upload_file(group_invoice_path, output_s3_path)
198+
s3_bucket.upload_file(group_invoice_path, output_s3_archive_path)
199+
200+
for group_invoice in os.listdir(self.name):
201+
_export_s3_group_invoice(group_invoice)

process_report/process_report.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
NERC_total_invoice,
1616
bu_internal_invoice,
1717
pi_specific_invoice,
18+
MOCA_group_specific_invoice,
1819
)
1920
from process_report.processors import (
2021
validate_pi_alias_processor,
@@ -167,6 +168,12 @@ def main():
167168
default="pi_invoices",
168169
help="Name of output folder containing pi-specific invoice csvs",
169170
)
171+
parser.add_argument(
172+
"--prepay-groups-output-folder",
173+
required=False,
174+
default="group_invoices",
175+
help="Name of output folder containing prepay-group-specific invoice PDFs",
176+
)
170177
parser.add_argument(
171178
"--BU-invoice-file",
172179
required=False,
@@ -352,6 +359,13 @@ def main():
352359
name=args.output_folder, invoice_month=invoice_month, data=processed_data
353360
)
354361

362+
moca_group_inv = MOCA_group_specific_invoice.MOCAGroupInvoice(
363+
name=args.prepay_groups_output_folder,
364+
invoice_month=invoice_month,
365+
data=processed_data,
366+
prepay_credits=prepay_credits,
367+
)
368+
355369
util.process_and_export_invoices(
356370
[
357371
lenovo_inv,
@@ -360,6 +374,7 @@ def main():
360374
nerc_total_inv,
361375
bu_internal_inv,
362376
pi_inv,
377+
moca_group_inv,
363378
],
364379
args.upload_to_s3,
365380
)

0 commit comments

Comments
 (0)