add server to gen pdf
This commit is contained in:
Binary file not shown.
@@ -22,8 +22,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: "SF-Pro";
|
font-family: "Arial";
|
||||||
src: url("fonts/SF-Pro.ttf") format("truetype");
|
src: url("fonts/Arial.ttf") format("truetype");
|
||||||
}
|
}
|
||||||
|
|
||||||
@page {
|
@page {
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
html {
|
html {
|
||||||
font-family: SF-Pro;
|
font-family: Arial;
|
||||||
font-size: 13pt;
|
font-size: 13pt;
|
||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
}
|
}
|
||||||
@@ -115,7 +115,7 @@
|
|||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
viewBox="0 0 1072.73 329.6"
|
viewBox="0 0 1072.73 329.6"
|
||||||
style="width: 14rem"
|
style="width: 14rem; margin-bottom: 1.5rem"
|
||||||
>
|
>
|
||||||
<g>
|
<g>
|
||||||
<g>
|
<g>
|
||||||
@@ -181,8 +181,8 @@
|
|||||||
de
|
de
|
||||||
<strong>{{ progress }}%</strong>
|
<strong>{{ progress }}%</strong>
|
||||||
</p>
|
</p>
|
||||||
<p>Realizado entre {{ started_date }} e {{ finished_date }}</p>
|
<p>Realizado entre {{ started_at }} e {{ completed_at }}</p>
|
||||||
<p>Florianópolis, SC, {{ today }}</p>
|
<p>São José, SC, {{ today }}</p>
|
||||||
|
|
||||||
<div class="signatures">
|
<div class="signatures">
|
||||||
<div class="sign1"></div>
|
<div class="sign1"></div>
|
||||||
|
|||||||
@@ -11,3 +11,4 @@ def get_dynamodb_client():
|
|||||||
|
|
||||||
|
|
||||||
dynamodb_client = get_dynamodb_client()
|
dynamodb_client = get_dynamodb_client()
|
||||||
|
s3_client = boto3.client('s3')
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ ORDER_TABLE: str = os.getenv('ORDER_TABLE') # type: ignore
|
|||||||
COURSE_TABLE: str = os.getenv('COURSE_TABLE') # type: ignore
|
COURSE_TABLE: str = os.getenv('COURSE_TABLE') # type: ignore
|
||||||
ENROLLMENT_TABLE: str = os.getenv('ENROLLMENT_TABLE') # type: ignore
|
ENROLLMENT_TABLE: str = os.getenv('ENROLLMENT_TABLE') # type: ignore
|
||||||
|
|
||||||
|
BUCKET_NAME: str = os.getenv('BUCKET_NAME') # type: ignore
|
||||||
|
|
||||||
# Post-migration: Remove the following lines
|
# Post-migration: Remove the following lines
|
||||||
if os.getenv('AWS_LAMBDA_FUNCTION_NAME'):
|
if os.getenv('AWS_LAMBDA_FUNCTION_NAME'):
|
||||||
SQLITE_DATABASE = 'courses_export_2025-06-18_110214.db'
|
SQLITE_DATABASE = 'courses_export_2025-06-18_110214.db'
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import json
|
||||||
|
import sqlite3
|
||||||
from datetime import datetime, time, timedelta
|
from datetime import datetime, time, timedelta
|
||||||
|
|
||||||
from aws_lambda_powertools import Logger
|
from aws_lambda_powertools import Logger
|
||||||
@@ -14,9 +16,16 @@ from layercake.dynamodb import (
|
|||||||
TransactKey,
|
TransactKey,
|
||||||
)
|
)
|
||||||
from layercake.funcs import pick
|
from layercake.funcs import pick
|
||||||
|
from sqlite_utils import Database
|
||||||
|
|
||||||
from boto3clients import dynamodb_client
|
from boto3clients import dynamodb_client
|
||||||
from config import COURSE_TABLE, ENROLLMENT_TABLE, ORDER_TABLE
|
from config import (
|
||||||
|
COURSE_TABLE,
|
||||||
|
ENROLLMENT_TABLE,
|
||||||
|
ORDER_TABLE,
|
||||||
|
SQLITE_DATABASE,
|
||||||
|
SQLITE_TABLE,
|
||||||
|
)
|
||||||
from utils import get_billing_period
|
from utils import get_billing_period
|
||||||
|
|
||||||
logger = Logger(__name__)
|
logger = Logger(__name__)
|
||||||
@@ -24,6 +33,8 @@ order_layer = DynamoDBPersistenceLayer(ORDER_TABLE, dynamodb_client)
|
|||||||
enrollment_layer = DynamoDBPersistenceLayer(ENROLLMENT_TABLE, dynamodb_client)
|
enrollment_layer = DynamoDBPersistenceLayer(ENROLLMENT_TABLE, dynamodb_client)
|
||||||
course_layer = DynamoDBPersistenceLayer(COURSE_TABLE, dynamodb_client)
|
course_layer = DynamoDBPersistenceLayer(COURSE_TABLE, dynamodb_client)
|
||||||
|
|
||||||
|
sqlite3.register_converter('json', json.loads)
|
||||||
|
|
||||||
|
|
||||||
@event_source(data_class=EventBridgeEvent)
|
@event_source(data_class=EventBridgeEvent)
|
||||||
@logger.inject_lambda_context
|
@logger.inject_lambda_context
|
||||||
@@ -41,6 +52,11 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
|
|||||||
logger.debug('Enrollment not found')
|
logger.debug('Enrollment not found')
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
# Keep it until the migration has been completed
|
||||||
|
old_course = _get_course(data['course']['id'])
|
||||||
|
if old_course:
|
||||||
|
data['course'] = old_course
|
||||||
|
|
||||||
start_date, end_date = get_billing_period(
|
start_date, end_date = get_billing_period(
|
||||||
new_image['billing_day'],
|
new_image['billing_day'],
|
||||||
year=created_at.year,
|
year=created_at.year,
|
||||||
@@ -52,6 +68,8 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
|
|||||||
end=end_date.isoformat(),
|
end=end_date.isoformat(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
logger.info('Enrollment found', data=data)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with order_layer.transact_writer() as transact:
|
with order_layer.transact_writer() as transact:
|
||||||
transact.put(
|
transact.put(
|
||||||
@@ -78,7 +96,6 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with order_layer.transact_writer() as transact:
|
|
||||||
author = data['author']
|
author = data['author']
|
||||||
course_id = data['course']['id']
|
course_id = data['course']['id']
|
||||||
course = course_layer.collection.get_items(
|
course = course_layer.collection.get_items(
|
||||||
@@ -94,13 +111,7 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
|
|||||||
),
|
),
|
||||||
flatten_top=False,
|
flatten_top=False,
|
||||||
)
|
)
|
||||||
|
order_layer.put_item(
|
||||||
transact.condition(
|
|
||||||
key=KeyPair(pk, sk),
|
|
||||||
cond_expr='attribute_exists(sk)',
|
|
||||||
exc_cls=BillingNotFoundError,
|
|
||||||
)
|
|
||||||
transact.put(
|
|
||||||
item={
|
item={
|
||||||
'id': pk,
|
'id': pk,
|
||||||
'sk': f'{sk}#ENROLLMENT#{enrollment_id}',
|
'sk': f'{sk}#ENROLLMENT#{enrollment_id}',
|
||||||
@@ -118,7 +129,11 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
|
|||||||
},
|
},
|
||||||
cond_expr='attribute_not_exists(sk)',
|
cond_expr='attribute_not_exists(sk)',
|
||||||
)
|
)
|
||||||
except Exception:
|
except Exception as exc:
|
||||||
|
logger.exception(
|
||||||
|
exc,
|
||||||
|
keypair={'pk': pk, 'sk': sk},
|
||||||
|
)
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
return True
|
return True
|
||||||
@@ -128,3 +143,18 @@ class ExistingBillingConflictError(Exception): ...
|
|||||||
|
|
||||||
|
|
||||||
class BillingNotFoundError(Exception): ...
|
class BillingNotFoundError(Exception): ...
|
||||||
|
|
||||||
|
|
||||||
|
def _get_course(course_id: str) -> dict | None:
|
||||||
|
with sqlite3.connect(
|
||||||
|
database=SQLITE_DATABASE, detect_types=sqlite3.PARSE_DECLTYPES
|
||||||
|
) as conn:
|
||||||
|
db = Database(conn)
|
||||||
|
rows = db[SQLITE_TABLE].rows_where(
|
||||||
|
"json->>'$.metadata__betaeducacao_id' = ?", [course_id]
|
||||||
|
)
|
||||||
|
|
||||||
|
for row in rows:
|
||||||
|
return row['json']
|
||||||
|
|
||||||
|
return None
|
||||||
|
|||||||
@@ -1,68 +1,93 @@
|
|||||||
import locale
|
import json
|
||||||
import os
|
|
||||||
from datetime import date
|
|
||||||
|
|
||||||
|
import requests
|
||||||
from aws_lambda_powertools import Logger
|
from aws_lambda_powertools import Logger
|
||||||
|
from aws_lambda_powertools.shared.json_encoder import Encoder
|
||||||
from aws_lambda_powertools.utilities.data_classes import (
|
from aws_lambda_powertools.utilities.data_classes import (
|
||||||
EventBridgeEvent,
|
EventBridgeEvent,
|
||||||
event_source,
|
event_source,
|
||||||
)
|
)
|
||||||
from aws_lambda_powertools.utilities.typing import LambdaContext
|
from aws_lambda_powertools.utilities.typing import LambdaContext
|
||||||
from jinja2 import Environment, FileSystemLoader
|
from layercake.dateutils import now
|
||||||
from layercake.dateutils import fromisoformat
|
|
||||||
from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair
|
from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair
|
||||||
from weasyprint import HTML
|
|
||||||
|
|
||||||
from boto3clients import dynamodb_client
|
from boto3clients import dynamodb_client, s3_client
|
||||||
from config import ORDER_TABLE
|
from config import BUCKET_NAME, ORDER_TABLE
|
||||||
|
|
||||||
logger = Logger(__name__)
|
logger = Logger(__name__)
|
||||||
order_layer = DynamoDBPersistenceLayer(ORDER_TABLE, dynamodb_client)
|
order_layer = DynamoDBPersistenceLayer(ORDER_TABLE, dynamodb_client)
|
||||||
curdir = os.path.dirname(__file__)
|
|
||||||
env = Environment(loader=FileSystemLoader(curdir))
|
|
||||||
locale.setlocale(locale.LC_ALL, 'pt_BR.UTF-8')
|
|
||||||
|
|
||||||
|
|
||||||
def currency(value: float | int) -> str:
|
|
||||||
return locale.currency(value, grouping=True)
|
|
||||||
|
|
||||||
|
|
||||||
def datetime_format(dt: date, fmt='%H:%M %d-%m-%y'):
|
|
||||||
if isinstance(dt, str):
|
|
||||||
dt = fromisoformat(dt) # type: ignore
|
|
||||||
|
|
||||||
return dt.strftime(fmt)
|
|
||||||
|
|
||||||
|
|
||||||
env.filters['datetime_format'] = datetime_format
|
|
||||||
env.filters['currency'] = currency
|
|
||||||
|
|
||||||
|
|
||||||
@event_source(data_class=EventBridgeEvent)
|
@event_source(data_class=EventBridgeEvent)
|
||||||
@logger.inject_lambda_context
|
@logger.inject_lambda_context
|
||||||
def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
|
def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
|
||||||
new_image = event.detail['new_image']
|
keys = event.detail['keys']
|
||||||
_, start_date, _, end_date, *_ = new_image['sk'].split('#')
|
now_ = now()
|
||||||
|
# Key pattern `BILLING#ORG#{org_id}`
|
||||||
|
*_, org_id = keys['id'].split('#')
|
||||||
|
# Key pattern `START#{start_date}#END#{end_date}#SCHEDULE#AUTO_CLOSE`
|
||||||
|
_, start_date, _, end_date, *_ = keys['sk'].split('#')
|
||||||
|
|
||||||
result = order_layer.collection.query(
|
result = order_layer.collection.query(
|
||||||
KeyPair(
|
KeyPair(
|
||||||
pk=new_image['id'],
|
pk=keys['id'],
|
||||||
sk=f'START#{start_date}#END#{end_date}#ENROLLMENT',
|
sk=f'START#{start_date}#END#{end_date}#ENROLLMENT',
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
template = env.get_template('tmpl.html')
|
r = requests.post(
|
||||||
html_rendered = template.render(
|
'https://weasyprint.saladeaula.digital',
|
||||||
start_date=start_date,
|
data=json.dumps(
|
||||||
end_date=end_date,
|
{
|
||||||
items=result['items'],
|
'template_s3_uri': 's3://saladeaula.digital/billing/template.html',
|
||||||
|
'template_vars': {
|
||||||
|
'start_date': start_date,
|
||||||
|
'end_date': end_date,
|
||||||
|
'items': result['items'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
cls=Encoder,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
r.raise_for_status()
|
||||||
|
|
||||||
HTML(string=html_rendered, base_url='').write_pdf('cert.pdf')
|
object_key = f'billing/{org_id}/{start_date}_{end_date}.pdf'
|
||||||
|
s3_uri = f's3://{BUCKET_NAME}/{object_key}'
|
||||||
|
|
||||||
return order_layer.update_item(
|
try:
|
||||||
key=KeyPair(new_image['id'], new_image['sk']),
|
s3_client.put_object(
|
||||||
update_expr='SET #status = :status',
|
Bucket=BUCKET_NAME,
|
||||||
|
Key=object_key,
|
||||||
|
Body=r.content,
|
||||||
|
ContentType='application/pdf',
|
||||||
|
)
|
||||||
|
except Exception as exc:
|
||||||
|
logger.exception(exc)
|
||||||
|
raise
|
||||||
|
|
||||||
|
with order_layer.transact_writer() as transact:
|
||||||
|
transact.update(
|
||||||
|
key=KeyPair(
|
||||||
|
pk=keys['id'],
|
||||||
|
sk=f'START#{start_date}#END#{end_date}',
|
||||||
|
),
|
||||||
|
update_expr='SET #status = :status, s3_uri = :s3_uri, \
|
||||||
|
updated_at = :updated_at',
|
||||||
expr_attr_names={'#status': 'status'},
|
expr_attr_names={'#status': 'status'},
|
||||||
expr_attr_values={':status': 'CLOSED'},
|
expr_attr_values={
|
||||||
|
':status': 'CLOSED',
|
||||||
|
':s3_uri': s3_uri,
|
||||||
|
':updated_at': now_,
|
||||||
|
},
|
||||||
|
cond_expr='attribute_exists(sk)',
|
||||||
)
|
)
|
||||||
|
transact.put(
|
||||||
|
item={
|
||||||
|
'id': keys['id'],
|
||||||
|
'sk': '{sk}#EXECUTED'.format(sk=keys['sk']),
|
||||||
|
'created_at': now_,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.info(f'PDF uploaded successfully to {s3_uri}')
|
||||||
|
return True
|
||||||
|
|||||||
Binary file not shown.
@@ -1,200 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<title></title>
|
|
||||||
<meta name="author" content="EDUSEG® <https://eduseg.com.br>" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
||||||
<style>
|
|
||||||
@page {
|
|
||||||
size: A4 portrait;
|
|
||||||
margin: 0.458cm;
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: "Arial";
|
|
||||||
src: url("fonts/Arial.ttf") format("truetype");
|
|
||||||
}
|
|
||||||
|
|
||||||
html,
|
|
||||||
body,
|
|
||||||
div,
|
|
||||||
h1,
|
|
||||||
h2,
|
|
||||||
ul,
|
|
||||||
p,
|
|
||||||
a {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
border: 0;
|
|
||||||
font-size: 100%;
|
|
||||||
font: inherit;
|
|
||||||
vertical-align: baseline;
|
|
||||||
}
|
|
||||||
|
|
||||||
html {
|
|
||||||
font-family: SF-Pro;
|
|
||||||
font-size: 11pt;
|
|
||||||
line-height: 1.4;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
section {
|
|
||||||
width: 100%;
|
|
||||||
break-after: page;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
strong {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
table {
|
|
||||||
table-layout: auto;
|
|
||||||
width: 100%;
|
|
||||||
border-spacing: 0;
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
border: 1px solid #efefef;
|
|
||||||
overflow: visible;
|
|
||||||
}
|
|
||||||
|
|
||||||
td {
|
|
||||||
padding: 0.625rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
tbody > tr > td {
|
|
||||||
border-top: 1px solid #efefef;
|
|
||||||
}
|
|
||||||
|
|
||||||
thead {
|
|
||||||
background-color: #f3f4f680;
|
|
||||||
font-weight: 600;
|
|
||||||
display: table-header-group;
|
|
||||||
}
|
|
||||||
tr {
|
|
||||||
page-break-inside: avoid;
|
|
||||||
}
|
|
||||||
|
|
||||||
.space-y-0\.5 > :not(:last-child) {
|
|
||||||
margin-bottom: 0.125rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.space-y-2\.5 > :not(:last-child) {
|
|
||||||
margin-bottom: 0.625rem;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<section class="space-y-2.5">
|
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
viewBox="0 0 1143.4 257.88"
|
|
||||||
style="width: 10rem"
|
|
||||||
>
|
|
||||||
<defs>
|
|
||||||
<style>
|
|
||||||
.cls-1 {
|
|
||||||
fill: #8cd366;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cls-2 {
|
|
||||||
fill: #2e3524;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</defs>
|
|
||||||
<g>
|
|
||||||
<g>
|
|
||||||
<path
|
|
||||||
class="cls-1"
|
|
||||||
d="M119.06,170.25l-53.68-24.18c-1.47-.94-3.35-.94-4.82,0l-53.68,24.18c-2.98,1.9-6.89-.24-6.89-3.77V7.01C0,4.54,2.01,2.54,4.48,2.54h117c2.47,0,4.48,2.01,4.48,4.48v159.46c0,3.54-3.91,5.68-6.89,3.77Z"
|
|
||||||
/>
|
|
||||||
<g>
|
|
||||||
<path
|
|
||||||
class="cls-2"
|
|
||||||
d="M73.52,57.89H20.82v15.77h52.7v-15.77Z"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
class="cls-2"
|
|
||||||
d="M84.06,86.96H20.82v20.97h63.24v-20.97Z"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
class="cls-2"
|
|
||||||
d="M84.06,23.67H20.82v20.97h63.24v-20.97Z"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
class="cls-2"
|
|
||||||
d="M105.14,102.66c0-2.91-2.36-5.27-5.27-5.27s-5.27,2.36-5.27,5.27,2.36,5.27,5.27,5.27,5.27-2.36,5.27-5.27Z"
|
|
||||||
/>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
<path
|
|
||||||
class="cls-2"
|
|
||||||
d="M191.46,2.54h72.24v34.76h-34.54v69.52h30.81v31h-30.81v82.76h34.54v34.76h-72.24V2.54Z"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
class="cls-2"
|
|
||||||
d="M283.8,2.54h45.22c8.38,0,16.01,1.84,22.92,5.52,6.91,3.68,12.35,8.75,16.33,15.17,3.98,6.42,5.97,13.65,5.97,21.65v168.12c0,8-1.94,15.23-5.8,21.65-3.88,6.42-9.26,11.49-16.18,15.17-6.91,3.68-14.65,5.54-23.23,5.54h-45.22V2.54ZM334.52,222.11c1.36-1.46,2.03-3.27,2.03-5.37V41.12c-.2-2.1-1.04-3.89-2.52-5.37-1.47-1.46-3.25-2.22-5.34-2.22h-10.96v190.79h11.27c2.29,0,4.12-.73,5.49-2.22h.02Z"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
class="cls-2"
|
|
||||||
d="M415.84,252.32c-6.81-3.68-12.2-8.73-16.18-15.17-3.97-6.42-5.97-13.63-5.97-21.65V2.54h37.68v218.61c0,2.1.73,3.91,2.2,5.37,1.46,1.48,3.25,2.22,5.34,2.22,2.29,0,4.12-.73,5.49-2.22,1.36-1.46,2.03-3.27,2.03-5.37V2.54h37.68v212.98c0,8.02-1.94,15.23-5.8,21.65-3.88,6.42-9.26,11.49-16.18,15.17-6.92,3.68-14.65,5.54-23.23,5.54s-16.27-1.84-23.08-5.54l.04-.02Z"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
class="cls-2"
|
|
||||||
d="M526.39,252.32c-6.81-3.68-12.2-8.73-16.18-15.17-3.97-6.42-5.97-13.63-5.97-21.65v-61.61h37.68v67.24c0,2.1.73,3.91,2.2,5.37,1.45,1.48,3.25,2.21,5.34,2.21,2.29,0,4.12-.73,5.49-2.21,1.36-1.47,2.03-3.27,2.03-5.37v-60.94c0-4.43-1.73-8.06-5.19-10.91-3.45-2.84-9.05-6.27-16.8-10.27-6.49-3.36-11.78-6.37-15.86-9.01-4.09-2.63-7.6-6.16-10.52-10.59-2.93-4.43-4.4-9.58-4.4-15.49v-61.57c0-8,1.98-15.21,5.97-21.65,3.98-6.42,9.37-11.47,16.18-15.17,6.79-3.68,14.5-5.52,23.08-5.52s16.33,1.84,23.23,5.52c6.91,3.68,12.3,8.75,16.18,15.17,3.88,6.42,5.8,13.65,5.8,21.65v55.62h-37.68v-61.25c0-2.1-.69-3.89-2.03-5.37-1.36-1.46-3.19-2.22-5.49-2.22-2.09,0-3.88.73-5.34,2.22-1.48,1.48-2.2,3.27-2.2,5.37v54.61c0,4.84,1.83,8.81,5.51,11.85,3.66,3.06,9.57,6.91,17.75,11.53,6.29,3.38,11.4,6.33,15.4,8.84,3.97,2.53,7.33,5.84,10.04,9.95,2.72,4.11,4.09,8.81,4.09,14.06v67.94c0,8.02-1.94,15.23-5.8,21.65-3.88,6.42-9.26,11.49-16.18,15.17-6.92,3.68-14.65,5.54-23.23,5.54s-16.27-1.84-23.08-5.54Z"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
class="cls-2"
|
|
||||||
d="M613.84,2.54h72.24v34.76h-34.54v69.52h30.81v31h-30.81v82.76h34.54v34.76h-72.24V2.54Z"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
class="cls-2"
|
|
||||||
d="M720.31,252.32c-4.4-3.27-7.91-7.9-10.52-13.9-2.61-6.01-3.92-12.9-3.92-20.69V42.03c0-7.79,1.98-14.91,5.97-21.33,3.98-6.42,9.43-11.47,16.33-15.17,6.91-3.68,14.56-5.52,22.94-5.52s16.07,1.84,23.08,5.52c7.02,3.68,12.45,8.75,16.33,15.17,3.88,6.42,5.8,13.54,5.8,21.33v73.95h-37.68V36.71c0-2.1-.69-3.89-2.03-5.37-1.36-1.46-3.19-2.22-5.49-2.22-2.09,0-3.88.73-5.34,2.22-1.48,1.48-2.2,3.27-2.2,5.37v181.02c0,2.1.73,3.91,2.2,5.37,1.45,1.48,3.25,2.22,5.34,2.22,2.3,0,4.13-.73,5.49-2.22,1.36-1.47,2.03-3.27,2.03-5.37v-36.02h-9.11v-40.13h46.78v113.76h-37.68v-11.06c-2.31,4.23-5.34,7.42-9.11,9.63-3.77,2.22-8.47,3.32-14.13,3.32s-10.67-1.63-15.08-4.9l-.02-.02Z"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
class="cls-2"
|
|
||||||
d="M824.08,19.6h-4.8l-.04-2.89h4.29c.65-.01,1.26-.12,1.82-.31.56-.21,1.01-.5,1.36-.89.34-.4.51-.89.51-1.47,0-.73-.13-1.3-.38-1.73-.24-.43-.65-.73-1.24-.91-.58-.19-1.36-.29-2.35-.29h-2.96v15.98h-3.22V8.24h6.18c1.47,0,2.73.21,3.8.64,1.08.41,1.91,1.05,2.49,1.91.59.84.89,1.9.89,3.18,0,.8-.19,1.51-.56,2.13-.37.62-.92,1.16-1.64,1.62-.71.44-1.59.81-2.62,1.09-.04,0-.1.05-.16.16-.04.1-.09.16-.13.16-.25.15-.42.26-.49.33-.06.06-.13.1-.2.11-.06.01-.24.02-.53.02ZM823.86,19.6l.47-2.2c2.31,0,3.89.5,4.73,1.51.85.99,1.27,2.26,1.27,3.8v1.2c0,.55.02,1.07.07,1.58.06.49.16.9.31,1.24v.36h-3.31c-.15-.39-.24-.93-.27-1.64-.02-.71-.02-1.23-.02-1.56v-1.16c0-1.08-.24-1.87-.73-2.38-.49-.5-1.33-.76-2.51-.76ZM810.38,17.93c0,1.97.33,3.81,1,5.51.68,1.69,1.63,3.17,2.84,4.44,1.22,1.26,2.61,2.24,4.2,2.96,1.6.7,3.3,1.04,5.11,1.04s3.53-.35,5.11-1.04c1.59-.71,2.98-1.7,4.18-2.96,1.2-1.27,2.14-2.76,2.82-4.44.68-1.7,1.02-3.54,1.02-5.51s-.34-3.8-1.02-5.49c-.68-1.69-1.62-3.16-2.82-4.42-1.2-1.26-2.59-2.24-4.18-2.93-1.59-.71-3.29-1.07-5.11-1.07s-3.51.36-5.11,1.07c-1.59.7-2.98,1.67-4.2,2.93-1.22,1.26-2.16,2.73-2.84,4.42-.67,1.69-1,3.52-1,5.49ZM807.75,17.93c0-2.36.41-4.54,1.22-6.55.81-2.01,1.95-3.77,3.4-5.27,1.45-1.51,3.13-2.68,5.02-3.51,1.91-.84,3.96-1.27,6.13-1.27s4.21.42,6.11,1.27c1.91.83,3.59,2,5.02,3.51,1.45,1.5,2.59,3.25,3.4,5.27.83,2.01,1.25,4.2,1.25,6.55s-.42,4.54-1.25,6.55c-.81,2.01-1.95,3.78-3.4,5.31-1.44,1.51-3.11,2.69-5.02,3.53-1.9.84-3.93,1.27-6.11,1.27s-4.22-.42-6.13-1.27c-1.9-.84-3.57-2.02-5.02-3.53-1.45-1.53-2.58-3.3-3.4-5.31-.82-2.01-1.22-4.2-1.22-6.55Z"
|
|
||||||
/>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
Matrículas realizadas entre
|
|
||||||
<strong>{{ start_date|datetime_format('%d/%m/%Y') }}</strong> e
|
|
||||||
<strong>{{ end_date|datetime_format('%d/%m/%Y') }}</strong>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<table class="table-layout border border-gray-300">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<td>Curso</td>
|
|
||||||
<td>Colaborador</td>
|
|
||||||
<td>Matrículado em</td>
|
|
||||||
<td>Valor unit.</td>
|
|
||||||
<td>Autor</td>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{% for x in items %}
|
|
||||||
<tr>
|
|
||||||
<td>{{ x.course.name }}</td>
|
|
||||||
<td>{{ x.user.name }}</td>
|
|
||||||
<td>
|
|
||||||
{{ x.enrolled_at|datetime_format('%d/%m/%Y, %H:%M')
|
|
||||||
}}
|
|
||||||
</td>
|
|
||||||
<td>{{ x.unit_price|currency }}</td>
|
|
||||||
<td>{{ x.author.name }}</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</section>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -2,6 +2,9 @@ AWSTemplateFormatVersion: 2010-09-09
|
|||||||
Transform: AWS::Serverless-2016-10-31
|
Transform: AWS::Serverless-2016-10-31
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
|
BucketName:
|
||||||
|
Type: String
|
||||||
|
Default: saladeaula.digital
|
||||||
UserTable:
|
UserTable:
|
||||||
Type: String
|
Type: String
|
||||||
Default: betaeducacao-prod-users_d2o3r5gmm4it7j
|
Default: betaeducacao-prod-users_d2o3r5gmm4it7j
|
||||||
@@ -35,6 +38,7 @@ Globals:
|
|||||||
ORDER_TABLE: !Ref OrderTable
|
ORDER_TABLE: !Ref OrderTable
|
||||||
ENROLLMENT_TABLE: !Ref EnrollmentTable
|
ENROLLMENT_TABLE: !Ref EnrollmentTable
|
||||||
COURSE_TABLE: !Ref CourseTable
|
COURSE_TABLE: !Ref CourseTable
|
||||||
|
BUCKET_NAME: !Ref BucketName
|
||||||
|
|
||||||
Resources:
|
Resources:
|
||||||
EventLog:
|
EventLog:
|
||||||
@@ -45,7 +49,7 @@ Resources:
|
|||||||
EventBillingAddEnrollmentFunction:
|
EventBillingAddEnrollmentFunction:
|
||||||
Type: AWS::Serverless::Function
|
Type: AWS::Serverless::Function
|
||||||
Properties:
|
Properties:
|
||||||
Handler: events.billing.add_enrollment.lambda_handler
|
Handler: events.billing.append_enrollment.lambda_handler
|
||||||
LoggingConfig:
|
LoggingConfig:
|
||||||
LogGroup: !Ref EventLog
|
LogGroup: !Ref EventLog
|
||||||
Policies:
|
Policies:
|
||||||
@@ -70,11 +74,14 @@ Resources:
|
|||||||
Type: AWS::Serverless::Function
|
Type: AWS::Serverless::Function
|
||||||
Properties:
|
Properties:
|
||||||
Handler: events.billing.close_window.lambda_handler
|
Handler: events.billing.close_window.lambda_handler
|
||||||
|
Timeout: 26
|
||||||
LoggingConfig:
|
LoggingConfig:
|
||||||
LogGroup: !Ref EventLog
|
LogGroup: !Ref EventLog
|
||||||
Policies:
|
Policies:
|
||||||
- DynamoDBCrudPolicy:
|
- DynamoDBCrudPolicy:
|
||||||
TableName: !Ref OrderTable
|
TableName: !Ref OrderTable
|
||||||
|
- S3WritePolicy:
|
||||||
|
BucketName: !Ref BucketName
|
||||||
Events:
|
Events:
|
||||||
Event:
|
Event:
|
||||||
Type: EventBridgeRule
|
Type: EventBridgeRule
|
||||||
@@ -83,7 +90,7 @@ Resources:
|
|||||||
resources: [!Ref OrderTable]
|
resources: [!Ref OrderTable]
|
||||||
detail-type: [EXPIRE]
|
detail-type: [EXPIRE]
|
||||||
detail:
|
detail:
|
||||||
new_image:
|
keys:
|
||||||
sk:
|
sk:
|
||||||
- suffix: SCHEDULE#AUTO_CLOSE
|
- suffix: SCHEDULE#AUTO_CLOSE
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ def pytest_configure():
|
|||||||
os.environ['ENROLLMENT_TABLE'] = PYTEST_TABLE_NAME
|
os.environ['ENROLLMENT_TABLE'] = PYTEST_TABLE_NAME
|
||||||
os.environ['ORDER_TABLE'] = PYTEST_TABLE_NAME
|
os.environ['ORDER_TABLE'] = PYTEST_TABLE_NAME
|
||||||
os.environ['LOG_LEVEL'] = 'DEBUG'
|
os.environ['LOG_LEVEL'] = 'DEBUG'
|
||||||
|
os.environ['BUCKET_NAME'] = 'saladeaula.digital'
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
from aws_lambda_powertools.utilities.typing import LambdaContext
|
from aws_lambda_powertools.utilities.typing import LambdaContext
|
||||||
from layercake.dynamodb import DynamoDBPersistenceLayer, PartitionKey
|
from layercake.dynamodb import (
|
||||||
|
DynamoDBPersistenceLayer,
|
||||||
|
SortKey,
|
||||||
|
TransactKey,
|
||||||
|
)
|
||||||
|
|
||||||
import events.billing.close_window as app
|
import events.billing.close_window as app
|
||||||
|
|
||||||
|
|
||||||
def test_append_enrollment(
|
def test_close_window(
|
||||||
dynamodb_seeds,
|
dynamodb_seeds,
|
||||||
dynamodb_persistence_layer: DynamoDBPersistenceLayer,
|
dynamodb_persistence_layer: DynamoDBPersistenceLayer,
|
||||||
lambda_context: LambdaContext,
|
lambda_context: LambdaContext,
|
||||||
@@ -22,8 +26,15 @@ def test_append_enrollment(
|
|||||||
|
|
||||||
assert app.lambda_handler(event, lambda_context) # type: ignore
|
assert app.lambda_handler(event, lambda_context) # type: ignore
|
||||||
|
|
||||||
# r = dynamodb_persistence_layer.collection.query(
|
# r = dynamodb_persistence_layer.collection.get_items(
|
||||||
# PartitionKey('BILLING#ORG#edp8njvgQuzNkLx2ySNfAD')
|
# TransactKey('BILLING#ORG#BES6dmWgTMXRYmfDyYYXUF')
|
||||||
|
# + SortKey('START#2025-07-01#END#2025-07-31')
|
||||||
|
# + SortKey('START#2025-07-01#END#2025-07-31#SCHEDULE#AUTO_CLOSE#EXECUTED'),
|
||||||
|
# flatten_top=False,
|
||||||
# )
|
# )
|
||||||
|
|
||||||
# print(r)
|
# assert 's3_uri' in r['START#2025-07-01#END#2025-07-31']
|
||||||
|
# assert 'created_at' in r['START#2025-07-01#END#2025-07-31']
|
||||||
|
# assert 'updated_at' in r['START#2025-07-01#END#2025-07-31']
|
||||||
|
# assert r['START#2025-07-01#END#2025-07-31']['status'] == 'CLOSED'
|
||||||
|
# assert 'START#2025-07-01#END#2025-07-31#SCHEDULE#AUTO_CLOSE#EXECUTED' in r
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ def record_handler(record: DynamoDBRecord):
|
|||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
logger.info('Event result', result=result)
|
logger.info('Event result', detail=detail, detail_type=detail_type, result=result)
|
||||||
|
|
||||||
|
|
||||||
@tracer.capture_lambda_handler
|
@tracer.capture_lambda_handler
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ Globals:
|
|||||||
- !Sub arn:aws:lambda:sa-east-1:336641857101:layer:layercake:83
|
- !Sub arn:aws:lambda:sa-east-1:336641857101:layer:layercake:83
|
||||||
Environment:
|
Environment:
|
||||||
Variables:
|
Variables:
|
||||||
LOG_LEVEL: DEBUG
|
LOG_LEVEL: INFO
|
||||||
TZ: America/Sao_Paulo
|
TZ: America/Sao_Paulo
|
||||||
POWERTOOLS_LOGGER_SAMPLE_RATE: 0.1
|
POWERTOOLS_LOGGER_SAMPLE_RATE: 0.1
|
||||||
POWERTOOLS_LOGGER_LOG_EVENT: true
|
POWERTOOLS_LOGGER_LOG_EVENT: true
|
||||||
|
|||||||
Reference in New Issue
Block a user