Skip to content

Commit 19933ea

Browse files
[IT-4485] Add endpoint to show GL account balances
Add a new API endpoint `/balances` to output a CSV of GL account balances appropriate for sending to FloQast. The pre-existing endpoints used the Program segment of the chart of accounts, but the new endpoint uses the GL segment of the chart of accounts. Refactor fetching the chart of accounts to support arbitrary segments.
1 parent d1c2e33 commit 19933ea

File tree

13 files changed

+1178
-540
lines changed

13 files changed

+1178
-540
lines changed

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ repos:
1717
hooks:
1818
- id: yamllint
1919
- repo: https://github.com/awslabs/cfn-python-lint
20-
rev: v1.35.3
20+
rev: v1.38.0
2121
hooks:
2222
- id: cfn-python-lint
2323
files: template\.(json|yml|yaml)$

Pipfile.lock

Lines changed: 308 additions & 296 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 33 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,19 @@
11
# lambda-mips-api
22

3-
An AWS Lambda microservice presenting MIPS chart of accounts data
3+
An AWS Lambda microservice providing limited read-only access to MIP Cloud.
44

55
## Architecture
66

7-
This microservice is designed to retrieve a chart of accounts from a third-party
8-
API and present the data in a useful format.
7+
This microservice is designed to retrieve data using the MIP Cloud API and
8+
present the data in a useful format.
99

1010
Formats available:
1111

12-
| API Route | Description |
13-
| --------- | ------------------------------------------------------------------------ |
14-
| /accounts | A dictionary mapping the chart of accounts to their friendly names. |
15-
| /tags | A list of valid tag values for either `CostCenter` or `CostCenterOther`. |
12+
| API Route | Description |
13+
| --------- | --------------------------------------------------------------------------- |
14+
| /accounts | A dictionary mapping the chart of program accounts to their friendly names. |
15+
| /balances | A CSV listing GL account balances, appropriate for FloQast consumption. |
16+
| /tags | A list of valid tag values for either `CostCenter` or `CostCenterOther`. |
1617

1718
Since we reach out to a third-party API across the internet, responses are
1819
cached to minimize interaction with the API and mitigate potential environmental
@@ -28,7 +29,7 @@ data to be stored in Cloudfront for a default of one day.
2829
In the event of a cache hit, Cloudfront will return the cached value without
2930
triggering an API gateway event.
3031

31-
### Default Behavior
32+
### Chart of Accounts Behavior
3233

3334
By default, the lambda will process the chart of accounts received to remove
3435
inactive codes, deduplicate the significant portion of active codes, and add a
@@ -41,6 +42,12 @@ the `CodesToOmit` template parameter. Remaining codes will be returned in
4142
numeric order as either a list of strings or a json dictionary depending on the
4243
API route.
4344

45+
#### Current Balances
46+
47+
When retrieving a CSV of current balances, the full chart of accounts will be
48+
processed according to query-string parameters, and then collated with MIP trial
49+
balance data.
50+
4451
### Required Secure Parameters
4552

4653
User credentials for logging in to the finance system are stored as secure
@@ -65,24 +72,31 @@ environment:
6572

6673
| Template Parameter | Environment Variable | Description |
6774
| ------------------ | -------------------- | -------------------------------------------------- |
68-
| MipsOrganization | MipsOrg | Log in to this organization in the finance system. |
75+
| MipOrganization | MipOrg | Log in to this organization in the finance system. |
6976
| SsmParamPrefix | SsmPath | Path prefix for required secure parameters. |
7077
| CodesToOmit | CodesToOmit | List of numeric codes to remove from output. |
7178
| NoProgramCode | NoProgramCode | Numeric code to use for "No Program" entry. |
7279
| OtherCode | OtherCode | Numeric code to use for "Other" entry. |
7380

7481
### Query String Parameters
7582

76-
Several query-string parameters are available for either endpoint to configure
77-
response output.
78-
79-
| Query String Parameter | Allowed Values |
80-
| ---------------------- | ------------------------------------- |
81-
| show_other_code | "on" or "yes" or "true" |
82-
| hide_no_program_code | "on" or "yes" or "true" |
83-
| show_inactive_codes | "on" or "yes" or "true" |
84-
| priority_codes | Comma-separated list of numeric codes |
85-
| limit | Integer |
83+
Several query-string parameters are shared by the endpoints to configure the
84+
list of accounts in the response output. All query string parameters except for
85+
`year_to_date` effect the `/accounts` and `/tags` endpoints. The only parameters
86+
that effect the `/balances` endpoint are `show_inactive_codes` and
87+
`year_to_date`.
88+
89+
| Query String Parameter | Allowed Values | Default | Supported Endpoints |
90+
| ---------------------- | ------------------------------------- | ------------- | --------------------------------- |
91+
| show_other_code | Boolean value | False | `/accounts`, `/tags` |
92+
| hide_no_program_code | Boolean value | False | `/accounts`, `/tags` |
93+
| show_inactive_codes | Boolean value | False | `/accounts`, `/balances`, `/tags` |
94+
| priority_codes | Comma-separated list of numeric codes | Empty list | `/accounts`, `/tags` |
95+
| limit | Integer | 0 (unlimited) | `/accounts`, `/tags` |
96+
| year_to_date | Boolean value | False | `/balances` |
97+
98+
Boolean values: any value other than "no", "off", or "false" will be interpreted
99+
as true, including the empty string.
86100

87101
A `show_other_code` parameter is available to optionally include an "Other"
88102
entry in the output with a value from the `OtherCode` parameter. Defining any
-16.8 KB
Loading

docs/lambda-mips-api_components.drawio.svg

Lines changed: 1 addition & 1 deletion
Loading

mip_api/__init__.py

Lines changed: 83 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import json
22
import logging
33

4-
from mip_api import chart, s3, ssm, upstream, util
4+
from mip_api import balances, chart, s3, ssm, upstream, util
55

66

77
LOG = logging.getLogger(__name__)
@@ -30,68 +30,118 @@ def lambda_handler(event, context):
3030
Return doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html
3131
"""
3232

33-
# helper functions to encapsulate the body, headers, and status code
34-
def _build_return(code, body):
35-
return {
36-
"statusCode": code,
37-
"body": json.dumps(body, indent=2),
38-
}
39-
4033
try:
4134
# collect environment variables
42-
mip_org = util.get_os_var("MipsOrg")
35+
mip_org = util.get_os_var("MipOrg")
4336
ssm_path = util.get_os_var("SsmPath")
4437
s3_bucket = util.get_os_var("CacheBucket")
45-
s3_path = util.get_os_var("CacheBucketPath")
38+
s3_prefix = util.get_os_var("CacheBucketPrefix")
4639

4740
code_other = util.get_os_var("OtherCode")
4841
code_no_program = util.get_os_var("NoProgramCode")
4942

5043
api_routes = {
5144
"ApiChartOfAccounts": util.get_os_var("ApiChartOfAccounts"),
5245
"ApiValidTags": util.get_os_var("ApiValidTags"),
46+
"ApiTrialBalances": util.get_os_var("ApiTrialBalances"),
5347
}
5448

5549
_to_omit = util.get_os_var("CodesToOmit")
5650
omit_codes_list = util.parse_codes(_to_omit)
5751

52+
# collect query-string parameters
53+
params = util.params_dict(event)
54+
55+
# build S3 cache paths, with separate paths for each combination
56+
# of endpoint and relevant parameters
57+
s3_path_gl_coa = s3_prefix + "gl-coa"
58+
if not params["hide_inactive"]:
59+
s3_path_gl_coa += "-full"
60+
s3_path_gl_coa += ".json"
61+
62+
s3_path_program_coa = s3_prefix + "program-coa"
63+
if not params["hide_inactive"]:
64+
s3_path_program_coa += "-full"
65+
s3_path_program_coa += ".json"
66+
67+
s3_path_balances = s3_prefix + "balances"
68+
if not params["hide_inactive"]:
69+
s3_path_balances += "-full"
70+
if not params["ytd"]:
71+
s3_path_balances += "-ytd"
72+
s3_path_balances += ".json"
73+
5874
# get secure parameters
5975
ssm_secrets = ssm.get_secrets(ssm_path)
6076

61-
# get chart of accounts from mip cloud
62-
raw_chart = chart.get_chart(mip_org, ssm_secrets, s3_bucket, s3_path)
63-
LOG.debug(f"Raw chart data: {raw_chart}")
64-
65-
# collect query-string parameters
66-
params = {}
67-
if "queryStringParameters" in event:
68-
params = event["queryStringParameters"]
69-
LOG.debug(f"Query-string parameters: {params}")
70-
7177
# parse the path and return appropriate data
7278
if "path" in event:
7379
event_path = event["path"]
7480

75-
# always process the chart of accounts
76-
mip_chart = chart.process_chart(
77-
params, raw_chart, omit_codes_list, code_other, code_no_program
78-
)
81+
if event_path == api_routes["ApiTrialBalances"]:
82+
# get chart of GL accounts
83+
gl_chart = chart.get_gl_chart(
84+
mip_org,
85+
ssm_secrets,
86+
s3_bucket,
87+
s3_path_gl_coa,
88+
params["hide_inactive"],
89+
)
90+
LOG.debug(f"Raw chart data: {gl_chart}")
91+
92+
# get upstream balance data
93+
raw_bal = balances.get_balances(
94+
mip_org,
95+
ssm_secrets,
96+
s3_bucket,
97+
s3_path_balances,
98+
params["ytd"],
99+
)
100+
101+
# combine them into CSV output
102+
balances_csv = balances.format_csv(raw_bal, gl_chart)
103+
return util.build_return_text(200, balances_csv)
104+
105+
else: # common processing for '/accounts' and '/tags'
106+
107+
# get chart of Program accounts
108+
_raw_program_chart = chart.get_program_chart(
109+
mip_org,
110+
ssm_secrets,
111+
s3_bucket,
112+
s3_path_program_coa,
113+
params["hide_inactive"],
114+
)
115+
LOG.debug(f"Raw chart data: {_raw_program_chart}")
116+
117+
# always process the chart of Program accounts
118+
_program_chart = chart.process_chart(
119+
_raw_program_chart,
120+
omit_codes_list,
121+
code_other,
122+
code_no_program,
123+
params,
124+
)
125+
126+
# always limit the size of the chart
127+
program_chart = chart.limit_chart(_program_chart, params["limit"])
79128

80129
if event_path == api_routes["ApiChartOfAccounts"]:
81-
# conditionally limit the size of the output
82-
return_chart = chart.limit_chart(params, mip_chart)
83-
return _build_return(200, return_chart)
130+
# no more processing, return chart
131+
return util.build_return_json(200, program_chart)
84132

85133
elif event_path == api_routes["ApiValidTags"]:
86134
# build a list of strings from the processed dictionary
87-
valid_tags = chart.list_tags(params, mip_chart)
88-
return _build_return(200, valid_tags)
135+
valid_tags = chart.list_tags(program_chart)
136+
return util.build_return_json(200, valid_tags)
89137

90-
else:
91-
return _build_return(404, {"error": "Invalid request path"})
138+
else: # unknown API endpoint
139+
return util.build_return_json(404, {"error": "Invalid request path"})
92140

93-
return _build_return(400, {"error": f"Invalid event: No path found: {event}"})
141+
return util.build_return_json(
142+
400, {"error": f"Invalid event: No path found: {event}"}
143+
)
94144

95145
except Exception as exc:
96146
LOG.exception(exc)
97-
return _build_return(500, {"error": str(exc)})
147+
return util.build_return_json(500, {"error": str(exc)})

0 commit comments

Comments
 (0)