update address
This commit is contained in:
@@ -63,6 +63,7 @@ app.include_router(users.logs, prefix='/users')
|
|||||||
app.include_router(users.emails, prefix='/users')
|
app.include_router(users.emails, prefix='/users')
|
||||||
app.include_router(users.orgs, prefix='/users')
|
app.include_router(users.orgs, prefix='/users')
|
||||||
app.include_router(orgs.policies, prefix='/orgs')
|
app.include_router(orgs.policies, prefix='/orgs')
|
||||||
|
app.include_router(orgs.address, prefix='/orgs')
|
||||||
app.include_router(webhooks.router, prefix='/webhooks')
|
app.include_router(webhooks.router, prefix='/webhooks')
|
||||||
app.include_router(settings.router, prefix='/settings')
|
app.include_router(settings.router, prefix='/settings')
|
||||||
app.include_router(lookup.router, prefix='/lookup')
|
app.include_router(lookup.router, prefix='/lookup')
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ class AuditLogMiddleware(BaseMiddlewareHandler):
|
|||||||
self.collection.put_item(
|
self.collection.put_item(
|
||||||
key=KeyPair(
|
key=KeyPair(
|
||||||
# Post-migration: remove `delimiter` and update prefix
|
# Post-migration: remove `delimiter` and update prefix
|
||||||
# from `log` to `logs` in ComposeKey.
|
# from `log` to `logs#user` in ComposeKey.
|
||||||
pk=ComposeKey(user.id, prefix='log', delimiter=':'),
|
pk=ComposeKey(user.id, prefix='log', delimiter=':'),
|
||||||
sk=now_.isoformat(),
|
sk=now_.isoformat(),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
from .address import router as address
|
||||||
from .policies import router as policies
|
from .policies import router as policies
|
||||||
|
|
||||||
__all__ = ['policies']
|
__all__ = ['policies', 'address']
|
||||||
|
|||||||
51
http-api/app/routes/orgs/address.py
Normal file
51
http-api/app/routes/orgs/address.py
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
from http import HTTPStatus
|
||||||
|
|
||||||
|
from aws_lambda_powertools.event_handler.api_gateway import Router
|
||||||
|
from layercake.dynamodb import (
|
||||||
|
DynamoDBPersistenceLayer,
|
||||||
|
KeyPair,
|
||||||
|
)
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
from api_gateway import JSONResponse
|
||||||
|
from boto3clients import dynamodb_client
|
||||||
|
from config import USER_TABLE
|
||||||
|
|
||||||
|
router = Router()
|
||||||
|
org_layer = DynamoDBPersistenceLayer(USER_TABLE, dynamodb_client)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get(
|
||||||
|
'/<id>/address',
|
||||||
|
compress=True,
|
||||||
|
tags=['Organization'],
|
||||||
|
summary='Get organization address',
|
||||||
|
)
|
||||||
|
def get_address(id: str):
|
||||||
|
return org_layer.collection.get_item(
|
||||||
|
KeyPair(id, 'metadata#address'),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Address(BaseModel):
|
||||||
|
postcode: str
|
||||||
|
address1: str
|
||||||
|
address2: str | None = None
|
||||||
|
neighborhood: str
|
||||||
|
city: str
|
||||||
|
state: str
|
||||||
|
|
||||||
|
|
||||||
|
@router.post('/<id>/address', compress=True, tags=['Organization'])
|
||||||
|
def post_address(id: str, payload: Address):
|
||||||
|
address = payload.model_dump()
|
||||||
|
org_layer.collection.put_item(
|
||||||
|
key=KeyPair(id, 'metadata#address'),
|
||||||
|
cond_expr='attribute_exists(sk)',
|
||||||
|
**address,
|
||||||
|
)
|
||||||
|
|
||||||
|
return JSONResponse(
|
||||||
|
body=payload,
|
||||||
|
status_code=HTTPStatus.OK,
|
||||||
|
)
|
||||||
@@ -1,26 +1,21 @@
|
|||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
from typing import Literal
|
from typing import Literal
|
||||||
|
|
||||||
from aws_lambda_powertools.event_handler import Response, content_types
|
|
||||||
from aws_lambda_powertools.event_handler.api_gateway import Router
|
from aws_lambda_powertools.event_handler.api_gateway import Router
|
||||||
from aws_lambda_powertools.event_handler.exceptions import (
|
|
||||||
BadRequestError,
|
|
||||||
)
|
|
||||||
from layercake.dynamodb import (
|
from layercake.dynamodb import (
|
||||||
DynamoDBCollection,
|
|
||||||
DynamoDBPersistenceLayer,
|
DynamoDBPersistenceLayer,
|
||||||
SortKey,
|
SortKey,
|
||||||
TransactKey,
|
TransactKey,
|
||||||
)
|
)
|
||||||
from pydantic.main import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
from api_gateway import JSONResponse
|
||||||
from boto3clients import dynamodb_client
|
from boto3clients import dynamodb_client
|
||||||
from config import USER_TABLE
|
from config import USER_TABLE
|
||||||
from rules.org import update_policies
|
from rules.org import update_policies
|
||||||
|
|
||||||
router = Router()
|
router = Router()
|
||||||
org_layer = DynamoDBPersistenceLayer(USER_TABLE, dynamodb_client)
|
org_layer = DynamoDBPersistenceLayer(USER_TABLE, dynamodb_client)
|
||||||
org_collect = DynamoDBCollection(org_layer, exc_cls=BadRequestError)
|
|
||||||
|
|
||||||
|
|
||||||
@router.get(
|
@router.get(
|
||||||
@@ -30,7 +25,7 @@ org_collect = DynamoDBCollection(org_layer, exc_cls=BadRequestError)
|
|||||||
summary='Get organization policies',
|
summary='Get organization policies',
|
||||||
)
|
)
|
||||||
def get_policies(id: str):
|
def get_policies(id: str):
|
||||||
return org_collect.get_items(
|
return org_layer.collection.get_items(
|
||||||
TransactKey(id)
|
TransactKey(id)
|
||||||
+ SortKey('metadata#billing_policy', remove_prefix='metadata#')
|
+ SortKey('metadata#billing_policy', remove_prefix='metadata#')
|
||||||
+ SortKey('metadata#payment_policy', remove_prefix='metadata#'),
|
+ SortKey('metadata#payment_policy', remove_prefix='metadata#'),
|
||||||
@@ -40,7 +35,7 @@ def get_policies(id: str):
|
|||||||
|
|
||||||
class BillingPolicy(BaseModel):
|
class BillingPolicy(BaseModel):
|
||||||
billing_day: int
|
billing_day: int
|
||||||
payment_method: Literal['PIX', 'BANK_SLIP', 'MANUAL']
|
payment_method: Literal['BANK_SLIP', 'MANUAL']
|
||||||
|
|
||||||
|
|
||||||
class PaymentPolicy(BaseModel):
|
class PaymentPolicy(BaseModel):
|
||||||
@@ -64,8 +59,7 @@ def put_policies(id: str, payload: Policies):
|
|||||||
persistence_layer=org_layer,
|
persistence_layer=org_layer,
|
||||||
)
|
)
|
||||||
|
|
||||||
return Response(
|
return JSONResponse(
|
||||||
body=payload,
|
body=payload,
|
||||||
content_type=content_types.APPLICATION_JSON,
|
|
||||||
status_code=HTTPStatus.OK,
|
status_code=HTTPStatus.OK,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -35,9 +35,11 @@ user_collect = DynamoDBCollection(user_layer, exc_cls=BadRequestError)
|
|||||||
summary='Get user emails',
|
summary='Get user emails',
|
||||||
)
|
)
|
||||||
def get_emails(id: str):
|
def get_emails(id: str):
|
||||||
|
start_key = router.current_event.get_query_string_value('start_key', None)
|
||||||
|
|
||||||
return user_collect.query(
|
return user_collect.query(
|
||||||
KeyPair(id, PrefixKey('emails')),
|
KeyPair(id, PrefixKey('emails')),
|
||||||
start_key=router.current_event.get_query_string_value('start_key', None),
|
start_key=start_key,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -54,6 +56,7 @@ class Email(BaseModel):
|
|||||||
)
|
)
|
||||||
def post_email(id: str, payload: Email):
|
def post_email(id: str, payload: Email):
|
||||||
add_email(id, payload.email, persistence_layer=user_layer)
|
add_email(id, payload.email, persistence_layer=user_layer)
|
||||||
|
|
||||||
return JSONResponse(
|
return JSONResponse(
|
||||||
body=payload,
|
body=payload,
|
||||||
status_code=HTTPStatus.CREATED,
|
status_code=HTTPStatus.CREATED,
|
||||||
@@ -90,7 +93,11 @@ def patch_email(id: str, payload: EmailAsPrimary):
|
|||||||
email_verified=payload.email_verified,
|
email_verified=payload.email_verified,
|
||||||
persistence_layer=user_layer,
|
persistence_layer=user_layer,
|
||||||
)
|
)
|
||||||
return JSONResponse(body=payload, status_code=HTTPStatus.OK)
|
|
||||||
|
return JSONResponse(
|
||||||
|
body=payload,
|
||||||
|
status_code=HTTPStatus.OK,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@router.delete(
|
@router.delete(
|
||||||
@@ -101,5 +108,9 @@ def patch_email(id: str, payload: EmailAsPrimary):
|
|||||||
middlewares=[AuditLogMiddleware('EMAIL_DEL', user_collect, ('email',))],
|
middlewares=[AuditLogMiddleware('EMAIL_DEL', user_collect, ('email',))],
|
||||||
)
|
)
|
||||||
def delete_email(id: str, payload: Email):
|
def delete_email(id: str, payload: Email):
|
||||||
del_email(id, payload.email, persistence_layer=user_layer)
|
del_email(
|
||||||
|
id,
|
||||||
|
payload.email,
|
||||||
|
persistence_layer=user_layer,
|
||||||
|
)
|
||||||
return payload
|
return payload
|
||||||
|
|||||||
@@ -5,36 +5,38 @@ from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair
|
|||||||
def update_policies(
|
def update_policies(
|
||||||
id: str,
|
id: str,
|
||||||
/,
|
/,
|
||||||
payment_policy: dict = {},
|
payment_policy: dict | None = None,
|
||||||
billing_policy: dict = {},
|
billing_policy: dict | None = None,
|
||||||
*,
|
*,
|
||||||
persistence_layer: DynamoDBPersistenceLayer,
|
persistence_layer: DynamoDBPersistenceLayer,
|
||||||
):
|
):
|
||||||
now_ = now()
|
now_ = now()
|
||||||
|
payment_sk = 'metadata#payment_policy'
|
||||||
|
billing_sk = 'metadata#billing_policy'
|
||||||
|
|
||||||
with persistence_layer.transact_writer() as transact:
|
with persistence_layer.transact_writer() as transact:
|
||||||
if payment_policy:
|
if payment_policy:
|
||||||
transact.put(
|
transact.put(
|
||||||
item={
|
item={
|
||||||
'id': id,
|
'id': id,
|
||||||
'sk': 'metadata#payment_policy',
|
'sk': payment_sk,
|
||||||
'create_date': now_,
|
'created_at': now_,
|
||||||
}
|
}
|
||||||
| payment_policy
|
| payment_policy
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
transact.delete(key=KeyPair(id, 'metadata#payment_policy'))
|
transact.delete(key=KeyPair(id, payment_sk))
|
||||||
|
|
||||||
if billing_policy:
|
if billing_policy:
|
||||||
transact.put(
|
transact.put(
|
||||||
item={
|
item={
|
||||||
'id': id,
|
'id': id,
|
||||||
'sk': 'metadata#billing_policy',
|
'sk': billing_sk,
|
||||||
'create_date': now_,
|
'created_at': now_,
|
||||||
}
|
}
|
||||||
| billing_policy
|
| billing_policy
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
transact.delete(key=KeyPair(id, 'metadata#billing_policy'))
|
transact.delete(key=KeyPair(id, billing_sk))
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ from aws_lambda_powertools.event_handler.exceptions import (
|
|||||||
)
|
)
|
||||||
from layercake.dateutils import now, ttl
|
from layercake.dateutils import now, ttl
|
||||||
from layercake.dynamodb import (
|
from layercake.dynamodb import (
|
||||||
ComposeKey,
|
|
||||||
DynamoDBPersistenceLayer,
|
DynamoDBPersistenceLayer,
|
||||||
KeyPair,
|
KeyPair,
|
||||||
SortKey,
|
SortKey,
|
||||||
@@ -35,14 +34,14 @@ def update_user(
|
|||||||
with persistence_layer.transact_writer() as transact:
|
with persistence_layer.transact_writer() as transact:
|
||||||
transact.update(
|
transact.update(
|
||||||
key=KeyPair(user.id, '0'),
|
key=KeyPair(user.id, '0'),
|
||||||
update_expr='SET #name = :name, cpf = :cpf, update_date = :update_date',
|
update_expr='SET #name = :name, cpf = :cpf, updated_at = :updated_at',
|
||||||
expr_attr_names={
|
expr_attr_names={
|
||||||
'#name': 'name',
|
'#name': 'name',
|
||||||
},
|
},
|
||||||
expr_attr_values={
|
expr_attr_values={
|
||||||
':name': user.name,
|
':name': user.name,
|
||||||
':cpf': user.cpf,
|
':cpf': user.cpf,
|
||||||
':update_date': now_,
|
':updated_at': now_,
|
||||||
},
|
},
|
||||||
cond_expr='attribute_exists(sk)',
|
cond_expr='attribute_exists(sk)',
|
||||||
)
|
)
|
||||||
@@ -56,7 +55,7 @@ def update_user(
|
|||||||
item={
|
item={
|
||||||
'id': user.id,
|
'id': user.id,
|
||||||
'sk': 'rate_limit#user_update',
|
'sk': 'rate_limit#user_update',
|
||||||
'create_date': now_,
|
'created_at': now_,
|
||||||
'ttl': ttl(start_dt=now_ + timedelta(hours=24)),
|
'ttl': ttl(start_dt=now_ + timedelta(hours=24)),
|
||||||
},
|
},
|
||||||
exc_cls=RateLimitError,
|
exc_cls=RateLimitError,
|
||||||
@@ -73,7 +72,7 @@ def update_user(
|
|||||||
'id': 'cpf',
|
'id': 'cpf',
|
||||||
'sk': user.cpf,
|
'sk': user.cpf,
|
||||||
'user_id': user.id,
|
'user_id': user.id,
|
||||||
'create_date': now_,
|
'created_at': now_,
|
||||||
},
|
},
|
||||||
cond_expr='attribute_not_exists(sk)',
|
cond_expr='attribute_not_exists(sk)',
|
||||||
exc_cls=CPFConflictError,
|
exc_cls=CPFConflictError,
|
||||||
@@ -107,7 +106,7 @@ def add_email(
|
|||||||
transact.put(
|
transact.put(
|
||||||
item={
|
item={
|
||||||
'id': id,
|
'id': id,
|
||||||
'sk': ComposeKey(email, prefix='emails'),
|
'sk': f'emails#{email}',
|
||||||
'email_primary': False,
|
'email_primary': False,
|
||||||
'email_verified': False,
|
'email_verified': False,
|
||||||
'create_date': now_,
|
'create_date': now_,
|
||||||
@@ -145,7 +144,7 @@ def del_email(
|
|||||||
with persistence_layer.transact_writer() as transact:
|
with persistence_layer.transact_writer() as transact:
|
||||||
transact.delete(key=KeyPair('email', email))
|
transact.delete(key=KeyPair('email', email))
|
||||||
transact.delete(
|
transact.delete(
|
||||||
key=KeyPair(id, ComposeKey(email, prefix='emails')),
|
key=KeyPair(id, f'emails#{email}'),
|
||||||
cond_expr='email_primary <> :email_primary',
|
cond_expr='email_primary <> :email_primary',
|
||||||
expr_attr_values={
|
expr_attr_values={
|
||||||
':email_primary': True,
|
':email_primary': True,
|
||||||
@@ -177,7 +176,7 @@ def set_email_as_primary(
|
|||||||
with persistence_layer.transact_writer() as transact:
|
with persistence_layer.transact_writer() as transact:
|
||||||
# Set the old email as non-primary
|
# Set the old email as non-primary
|
||||||
transact.update(
|
transact.update(
|
||||||
key=KeyPair(id, ComposeKey(old_email, prefix='emails')),
|
key=KeyPair(id, f'emails#{old_email}'),
|
||||||
update_expr=expr,
|
update_expr=expr,
|
||||||
expr_attr_values={
|
expr_attr_values={
|
||||||
':email_primary': False,
|
':email_primary': False,
|
||||||
@@ -186,7 +185,7 @@ def set_email_as_primary(
|
|||||||
)
|
)
|
||||||
# Set the new email as primary
|
# Set the new email as primary
|
||||||
transact.update(
|
transact.update(
|
||||||
key=KeyPair(id, ComposeKey(new_email, 'emails')),
|
key=KeyPair(id, f'emails#{new_email}'),
|
||||||
update_expr=expr,
|
update_expr=expr,
|
||||||
expr_attr_values={
|
expr_attr_values={
|
||||||
':email_primary': True,
|
':email_primary': True,
|
||||||
@@ -214,19 +213,22 @@ def del_org_member(
|
|||||||
org_id: str,
|
org_id: str,
|
||||||
persistence_layer: DynamoDBPersistenceLayer,
|
persistence_layer: DynamoDBPersistenceLayer,
|
||||||
) -> bool:
|
) -> bool:
|
||||||
|
now_ = now()
|
||||||
with persistence_layer.transact_writer() as transact:
|
with persistence_layer.transact_writer() as transact:
|
||||||
# Remove the user's relationship with the organization and their privileges
|
# Remove the user's relationship with the organization and their privileges
|
||||||
transact.delete(key=KeyPair(id, ComposeKey(org_id, prefix='acls')))
|
transact.delete(key=KeyPair(id, f'acls#{org_id}'))
|
||||||
transact.delete(key=KeyPair(id, ComposeKey(org_id, prefix='orgs')))
|
transact.delete(key=KeyPair(id, f'orgs#{org_id}'))
|
||||||
transact.update(
|
transact.update(
|
||||||
key=KeyPair(id, '0'),
|
key=KeyPair(id, '0'),
|
||||||
update_expr='DELETE #tenant :org_id',
|
update_expr='DELETE tenant_id :tenant_id SET updated_at = :updated_at',
|
||||||
expr_attr_names={'#tenant': 'tenant__org_id'},
|
expr_attr_values={
|
||||||
expr_attr_values={':org_id': {org_id}},
|
':tenant_id': {org_id},
|
||||||
|
':updated_at': now_,
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
# Remove the user from the organization's admins and members list
|
# Remove the user from the organization's admins and members list
|
||||||
transact.delete(key=KeyPair(org_id, ComposeKey(id, prefix='admins')))
|
transact.delete(key=KeyPair(org_id, f'admins#{id}'))
|
||||||
transact.delete(key=KeyPair(ComposeKey(org_id, prefix='orgmembers'), id))
|
transact.delete(key=KeyPair(f'orgmembers#{org_id}', id))
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|||||||
251
http-api/tests/routes/sample.html
Normal file
251
http-api/tests/routes/sample.html
Normal file
@@ -0,0 +1,251 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<title>NR-10 Complementar (SEP)</title>
|
||||||
|
<meta name="author" content="EDUSEG® <https://eduseg.com.br>" />
|
||||||
|
<style>
|
||||||
|
html,
|
||||||
|
body,
|
||||||
|
div,
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
ul,
|
||||||
|
p,
|
||||||
|
a {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
border: 0;
|
||||||
|
font-size: 100%;
|
||||||
|
font: inherit;
|
||||||
|
vertical-align: baseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: "SF-Pro";
|
||||||
|
src: url("fonts/SF-Pro.ttf") format("truetype");
|
||||||
|
}
|
||||||
|
|
||||||
|
@page {
|
||||||
|
size: A4 landscape;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
font-family: SF-Pro, Helvetica, Arial, sans-serif;
|
||||||
|
font-size: 13pt;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
section {
|
||||||
|
width: 29.7cm;
|
||||||
|
height: 21cm;
|
||||||
|
break-after: page;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
strong {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
#cover {
|
||||||
|
background-color: #a7e400;
|
||||||
|
justify-content: center;
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#cover h1 {
|
||||||
|
font-weight: bolder;
|
||||||
|
font-size: 26pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
#cover .qrcode {
|
||||||
|
width: 120px;
|
||||||
|
height: 120px;
|
||||||
|
background-color: #fff;
|
||||||
|
position: absolute;
|
||||||
|
top: 5rem;
|
||||||
|
right: 5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#cover .signatures {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-top: 2.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sign1 {
|
||||||
|
width: 250px;
|
||||||
|
border-top: #000 solid 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#back {
|
||||||
|
background-color: white;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#back h1,
|
||||||
|
#back h2 {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 16pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
#back ul {
|
||||||
|
padding-left: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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 id="cover">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 1072.73 329.6"
|
||||||
|
style="width: 14rem"
|
||||||
|
>
|
||||||
|
<g>
|
||||||
|
<g>
|
||||||
|
<path
|
||||||
|
fill="#8cd366"
|
||||||
|
d="M152.18,217.62l-68.61-30.91c-1.88-1.2-4.28-1.2-6.16,0l-68.61,30.91c-3.81,2.43-8.8-.3-8.8-4.82V8.98C0,5.82,2.56,3.26,5.72,3.26h149.54c3.16,0,5.72,2.56,5.72,5.72v203.81c0,4.52-5,7.26-8.8,4.82Z"
|
||||||
|
></path>
|
||||||
|
<path
|
||||||
|
fill="#2e3524"
|
||||||
|
d="M93.97,74.01H26.61v20.16h67.36v-20.16Z"
|
||||||
|
></path>
|
||||||
|
<path
|
||||||
|
fill="#2e3524"
|
||||||
|
d="M107.44,111.16H26.61v26.8h80.83v-26.8Z"
|
||||||
|
></path>
|
||||||
|
<path
|
||||||
|
fill="#2e3524"
|
||||||
|
d="M107.44,30.27H26.61v26.8h80.83v-26.8Z"
|
||||||
|
></path>
|
||||||
|
<path
|
||||||
|
fill="#2e3524"
|
||||||
|
d="M134.38,131.23c0-3.72-3.02-6.73-6.73-6.73s-6.73,3.02-6.73,6.73,3.02,6.73,6.73,6.73,6.73-3.02,6.73-6.73Z"
|
||||||
|
></path>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path
|
||||||
|
fill="#f9f7e8"
|
||||||
|
d="M244.7,3.24h92.33v44.43h-44.15v88.85h39.38v39.62h-39.38v105.77h44.15v44.42h-92.33V3.24Z"
|
||||||
|
></path>
|
||||||
|
<path
|
||||||
|
fill="#f9f7e8"
|
||||||
|
d="M362.72,3.24h57.79c10.71,0,20.47,2.35,29.29,7.06,8.83,4.7,15.79,11.18,20.87,19.39,5.08,8.21,7.63,17.45,7.63,27.67v214.88c0,10.22-2.48,19.46-7.42,27.67-4.96,8.21-11.83,14.69-20.68,19.39-8.83,4.7-18.73,7.08-29.7,7.08h-57.79V3.24ZM427.55,283.88c1.74-1.87,2.6-4.18,2.6-6.86V52.56c-.26-2.69-1.34-4.97-3.22-6.86-1.88-1.87-4.15-2.83-6.82-2.83h-14v243.85h14.41c2.93,0,5.27-.94,7.01-2.83h.02Z"
|
||||||
|
></path>
|
||||||
|
<path
|
||||||
|
fill="#f9f7e8"
|
||||||
|
d="M531.5,322.49c-8.71-4.7-15.6-11.16-20.68-19.39-5.08-8.21-7.63-17.42-7.63-27.67V3.24h48.15v279.41c0,2.69.93,4.99,2.82,6.86,1.86,1.9,4.15,2.83,6.82,2.83,2.93,0,5.27-.94,7.01-2.83,1.74-1.87,2.6-4.18,2.6-6.86V3.24h48.16v272.21c0,10.25-2.48,19.46-7.42,27.67-4.96,8.21-11.83,14.69-20.68,19.39-8.85,4.7-18.73,7.08-29.7,7.08s-20.8-2.35-29.5-7.08l.05-.02Z"
|
||||||
|
></path>
|
||||||
|
<path
|
||||||
|
fill="#f9f7e8"
|
||||||
|
d="M672.79,322.49c-8.7-4.7-15.6-11.16-20.68-19.39-5.08-8.21-7.63-17.42-7.63-27.67v-78.75h48.16v85.95c0,2.69.93,4.99,2.82,6.87,1.86,1.9,4.15,2.83,6.82,2.83,2.93,0,5.27-.94,7.01-2.83,1.74-1.87,2.6-4.18,2.6-6.87v-77.88c0-5.66-2.22-10.3-6.63-13.94-4.41-3.62-11.57-8.02-21.47-13.13-8.3-4.3-15.05-8.14-20.27-11.52-5.22-3.36-9.71-7.87-13.45-13.54-3.75-5.66-5.63-12.24-5.63-19.8V54.12c0-10.22,2.53-19.44,7.63-27.67,5.08-8.21,11.97-14.66,20.68-19.39,8.68-4.7,18.53-7.06,29.5-7.06s20.87,2.35,29.69,7.06c8.83,4.7,15.72,11.18,20.68,19.39,4.96,8.21,7.42,17.45,7.42,27.67v71.09h-48.16V46.92c0-2.69-.88-4.97-2.6-6.86-1.74-1.87-4.08-2.83-7.01-2.83-2.67,0-4.96.94-6.82,2.83-1.89,1.9-2.82,4.18-2.82,6.86v69.79c0,6.19,2.34,11.26,7.04,15.14,4.67,3.91,12.24,8.83,22.68,14.74,8.04,4.32,14.57,8.09,19.68,11.3,5.08,3.24,9.37,7.46,12.83,12.72,3.48,5.26,5.22,11.26,5.22,17.98v86.83c0,10.25-2.48,19.46-7.42,27.67-4.96,8.21-11.83,14.69-20.68,19.39-8.85,4.71-18.72,7.08-29.7,7.08s-20.8-2.35-29.5-7.08Z"
|
||||||
|
></path>
|
||||||
|
<path
|
||||||
|
fill="#f9f7e8"
|
||||||
|
d="M784.56,3.24h92.33v44.43h-44.15v88.85h39.38v39.62h-39.38v105.77h44.15v44.42h-92.33V3.24Z"
|
||||||
|
></path>
|
||||||
|
<path
|
||||||
|
fill="#f9f7e8"
|
||||||
|
d="M920.63,322.49c-5.63-4.18-10.11-10.1-13.45-17.76-3.34-7.68-5.01-16.49-5.01-26.45V53.71c0-9.96,2.53-19.06,7.63-27.26,5.08-8.21,12.05-14.66,20.87-19.39,8.83-4.7,18.6-7.06,29.32-7.06s20.54,2.35,29.5,7.06c8.97,4.7,15.91,11.18,20.87,19.39,4.96,8.21,7.42,17.3,7.42,27.26v94.51h-48.16V46.92c0-2.69-.88-4.97-2.6-6.86-1.74-1.87-4.08-2.83-7.01-2.83-2.67,0-4.96.94-6.82,2.83-1.89,1.9-2.82,4.18-2.82,6.86v231.36c0,2.69.93,4.99,2.82,6.87,1.86,1.9,4.15,2.83,6.82,2.83,2.93,0,5.27-.94,7.01-2.83,1.74-1.87,2.6-4.18,2.6-6.87v-46.03h-11.64v-51.29h59.8v145.4h-48.16v-14.14c-2.96,5.4-6.82,9.48-11.64,12.31-4.82,2.83-10.83,4.25-18.06,4.25s-13.64-2.09-19.27-6.26l-.02-.02Z"
|
||||||
|
></path>
|
||||||
|
<path
|
||||||
|
fill="#f9f7e8"
|
||||||
|
d="M1053.27,25.05h-6.13l-.06-3.69h5.48c.83-.02,1.61-.15,2.33-.4.72-.27,1.3-.64,1.73-1.14.44-.51.65-1.14.65-1.87,0-.93-.16-1.67-.48-2.22-.3-.55-.83-.94-1.59-1.16-.74-.25-1.74-.37-3.01-.37h-3.78v20.42h-4.12V10.54h7.9c1.87,0,3.49.27,4.86.82,1.38.53,2.44,1.34,3.18,2.44.76,1.08,1.14,2.43,1.14,4.06,0,1.02-.24,1.93-.71,2.73-.47.8-1.17,1.49-2.1,2.07-.91.57-2.03,1.03-3.35,1.39-.06,0-.12.07-.2.2-.06.13-.11.2-.17.2-.32.19-.53.33-.63.43-.08.08-.16.12-.25.14-.08.02-.31.03-.68.03ZM1052.99,25.05l.6-2.81c2.95,0,4.97.64,6.05,1.93,1.08,1.27,1.62,2.89,1.62,4.86v1.53c0,.7.03,1.37.08,2.02.08.62.21,1.15.4,1.59v.45h-4.23c-.19-.49-.3-1.19-.34-2.1-.02-.91-.03-1.57-.03-1.99v-1.48c0-1.38-.31-2.39-.94-3.04s-1.69-.97-3.21-.97ZM1035.75,22.92c0,2.52.43,4.87,1.28,7.04.87,2.16,2.08,4.05,3.64,5.68,1.55,1.61,3.34,2.87,5.37,3.78,2.05.89,4.22,1.33,6.53,1.33s4.51-.44,6.53-1.33c2.03-.91,3.8-2.17,5.34-3.78,1.53-1.63,2.74-3.52,3.61-5.68.87-2.18,1.31-4.52,1.31-7.04s-.44-4.86-1.31-7.01c-.87-2.16-2.07-4.04-3.61-5.65-1.53-1.61-3.31-2.86-5.34-3.75-2.03-.91-4.2-1.36-6.53-1.36s-4.49.45-6.53,1.36c-2.03.89-3.81,2.14-5.37,3.75-1.55,1.61-2.77,3.49-3.64,5.65-.85,2.16-1.28,4.5-1.28,7.01ZM1032.4,22.92c0-3.01.52-5.8,1.56-8.38,1.04-2.57,2.49-4.82,4.34-6.73,1.86-1.93,4-3.43,6.42-4.49,2.44-1.08,5.06-1.62,7.84-1.62s5.39.54,7.81,1.62c2.44,1.06,4.58,2.56,6.42,4.49,1.86,1.91,3.31,4.16,4.35,6.73,1.06,2.57,1.59,5.37,1.59,8.38s-.53,5.8-1.59,8.38c-1.04,2.57-2.49,4.84-4.35,6.79-1.83,1.93-3.97,3.44-6.42,4.52-2.42,1.08-5.03,1.62-7.81,1.62s-5.39-.54-7.84-1.62c-2.42-1.08-4.56-2.58-6.42-4.52-1.85-1.95-3.3-4.21-4.34-6.79-1.04-2.57-1.56-5.37-1.56-8.38Z"
|
||||||
|
></path>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
<p>Certificamos que</p>
|
||||||
|
<h1>{{ name }}</h1>
|
||||||
|
<p>
|
||||||
|
Portador(a) do CPF <strong>{{ cpf }} </strong>, concluiu o curso
|
||||||
|
de <strong>NR-10 Complementar (SEP)</strong> com aproveitamento
|
||||||
|
de
|
||||||
|
<strong>{{ progress }}%</strong>
|
||||||
|
</p>
|
||||||
|
<p>Realizado entre {{ started_date }} e {{ finished_date }}</p>
|
||||||
|
<p>Florianópolis, SC, {{ today }}</p>
|
||||||
|
|
||||||
|
<div class="signatures">
|
||||||
|
<div class="sign1"></div>
|
||||||
|
<div class="sign2">
|
||||||
|
<p>Tiago Maciel do Santos</p>
|
||||||
|
<p>CEO/Diretor</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="qrcode">
|
||||||
|
<img src="{{ qrcode }}" />
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="back">
|
||||||
|
<div class="space-y-2.5">
|
||||||
|
<h1>Conteúdo programático ministrado</h1>
|
||||||
|
<ul>
|
||||||
|
<li>Organização do sistema elétrico de potência</li>
|
||||||
|
<li>Organização do trabalho</li>
|
||||||
|
<li>Aspectos comportamentais</li>
|
||||||
|
<li>Condições impeditivas para serviços</li>
|
||||||
|
<li>Riscos típicos no SEP e sua prevenção</li>
|
||||||
|
<li>Técnicas de análise de riscos no SEP</li>
|
||||||
|
<li>Procedimentos de trabalho (análise e discussão)</li>
|
||||||
|
<li>Técnicas de análise de riscos no SEP</li>
|
||||||
|
<li>Equipamentos e ferramentas de trabalho</li>
|
||||||
|
<li>Sistemas de proteção coletiva</li>
|
||||||
|
<li>Equipamentos de proteção individual</li>
|
||||||
|
<li>Posturas e vestuários de trabalhos</li>
|
||||||
|
<li>
|
||||||
|
Segurança com veículos e transporte de pessoas,
|
||||||
|
materiais e equipamentos
|
||||||
|
</li>
|
||||||
|
<li>Sinalização e isolamento de áreas de trabalho</li>
|
||||||
|
<li>
|
||||||
|
Liberação de instalação para serviço, operação e uso
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Treinamento em técnicas de remoção, atendimento e
|
||||||
|
transporte de acidentados
|
||||||
|
</li>
|
||||||
|
<li>Acidentes típicos</li>
|
||||||
|
<li>Responsabilidades</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="space-y-2.5">
|
||||||
|
<dd class="space-y-0.5">
|
||||||
|
<h2>Carga horária</h2>
|
||||||
|
<p>40 horas</p>
|
||||||
|
</dd>
|
||||||
|
|
||||||
|
<dd class="space-y-0.5">
|
||||||
|
<h2>Instrutor e responsável técnico</h2>
|
||||||
|
<div>
|
||||||
|
<p>Francis Ricardo Baretta</p>
|
||||||
|
<p>CPF 039.539.409-02</p>
|
||||||
|
<p>Eng. de Segurança no Trabalho Eng. Eletricista</p>
|
||||||
|
<p>CREA/SC 126693-0</p>
|
||||||
|
</div>
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import json
|
import json
|
||||||
from http import HTTPMethod, HTTPStatus
|
from http import HTTPMethod, HTTPStatus
|
||||||
|
|
||||||
from layercake.dynamodb import DynamoDBCollection, DynamoDBPersistenceLayer, KeyPair
|
from layercake.dynamodb import DynamoDBPersistenceLayer, KeyPair
|
||||||
|
|
||||||
from ..conftest import HttpApiProxy, LambdaContext
|
from ..conftest import HttpApiProxy, LambdaContext
|
||||||
|
|
||||||
@@ -46,6 +46,66 @@ def test_put_policies(
|
|||||||
)
|
)
|
||||||
assert r['statusCode'] == HTTPStatus.OK
|
assert r['statusCode'] == HTTPStatus.OK
|
||||||
|
|
||||||
collect = DynamoDBCollection(dynamodb_persistence_layer)
|
course = dynamodb_persistence_layer.collection.get_item(
|
||||||
course = collect.get_item(KeyPair('cJtK9SsnJhKPyxESe7g3DG', '0'))
|
KeyPair('cJtK9SsnJhKPyxESe7g3DG', '0')
|
||||||
|
)
|
||||||
assert course['name'] == 'EDUSEG'
|
assert course['name'] == 'EDUSEG'
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_address(
|
||||||
|
mock_app,
|
||||||
|
dynamodb_seeds,
|
||||||
|
dynamodb_persistence_layer: DynamoDBPersistenceLayer,
|
||||||
|
http_api_proxy: HttpApiProxy,
|
||||||
|
lambda_context: LambdaContext,
|
||||||
|
):
|
||||||
|
r = mock_app.lambda_handler(
|
||||||
|
http_api_proxy(
|
||||||
|
raw_path='/orgs/cJtK9SsnJhKPyxESe7g3DG/address',
|
||||||
|
method=HTTPMethod.GET,
|
||||||
|
body={'payment_policy': None},
|
||||||
|
),
|
||||||
|
lambda_context,
|
||||||
|
)
|
||||||
|
address = {
|
||||||
|
'id': 'cJtK9SsnJhKPyxESe7g3DG',
|
||||||
|
'sk': 'metadata#address',
|
||||||
|
'postcode': '88101001',
|
||||||
|
'address1': 'Av. Presidente Kennedy, 815',
|
||||||
|
'address2': 'Sala 1',
|
||||||
|
'neighborhood': 'Campinas',
|
||||||
|
'city': 'São José',
|
||||||
|
'state': 'SC',
|
||||||
|
}
|
||||||
|
assert r['statusCode'] == HTTPStatus.OK
|
||||||
|
assert json.loads(r['body']) == address
|
||||||
|
|
||||||
|
|
||||||
|
def test_post_address(
|
||||||
|
mock_app,
|
||||||
|
dynamodb_seeds,
|
||||||
|
dynamodb_persistence_layer: DynamoDBPersistenceLayer,
|
||||||
|
http_api_proxy: HttpApiProxy,
|
||||||
|
lambda_context: LambdaContext,
|
||||||
|
):
|
||||||
|
r = mock_app.lambda_handler(
|
||||||
|
http_api_proxy(
|
||||||
|
raw_path='/orgs/cJtK9SsnJhKPyxESe7g3DG/address',
|
||||||
|
method=HTTPMethod.POST,
|
||||||
|
body={
|
||||||
|
'postcode': '81280350',
|
||||||
|
'address1': 'Rua Monsenhor Ivo Zanlorenzi, 5190',
|
||||||
|
'address2': 'ap 1802',
|
||||||
|
'neighborhood': 'Cidade Industrial',
|
||||||
|
'city': 'Curitiba',
|
||||||
|
'state': 'PR',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
lambda_context,
|
||||||
|
)
|
||||||
|
assert r['statusCode'] == HTTPStatus.OK
|
||||||
|
|
||||||
|
data = dynamodb_persistence_layer.collection.get_item(
|
||||||
|
KeyPair('cJtK9SsnJhKPyxESe7g3DG', 'metadata#address')
|
||||||
|
)
|
||||||
|
assert data['address1'] == 'Rua Monsenhor Ivo Zanlorenzi, 5190'
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import json
|
|||||||
from http import HTTPMethod, HTTPStatus
|
from http import HTTPMethod, HTTPStatus
|
||||||
|
|
||||||
from layercake.dynamodb import (
|
from layercake.dynamodb import (
|
||||||
DynamoDBCollection,
|
|
||||||
DynamoDBPersistenceLayer,
|
DynamoDBPersistenceLayer,
|
||||||
KeyPair,
|
KeyPair,
|
||||||
SortKey,
|
SortKey,
|
||||||
@@ -32,13 +31,12 @@ def test_update_user_cpf(
|
|||||||
)
|
)
|
||||||
assert r['statusCode'] == HTTPStatus.OK
|
assert r['statusCode'] == HTTPStatus.OK
|
||||||
|
|
||||||
collect = DynamoDBCollection(dynamodb_persistence_layer)
|
user = dynamodb_persistence_layer.collection.get_items(
|
||||||
user = collect.get_items(
|
|
||||||
TransactKey('5OxmMjL-ujoR5IMGegQz')
|
TransactKey('5OxmMjL-ujoR5IMGegQz')
|
||||||
+ SortKey('0')
|
+ SortKey('0')
|
||||||
+ SortKey('last_profile_edit')
|
+ SortKey('rate_limit#user_update')
|
||||||
)
|
)
|
||||||
assert 'last_profile_edit' in user
|
assert 'rate_limit#user_update' in user
|
||||||
|
|
||||||
|
|
||||||
def test_update_user_name(
|
def test_update_user_name(
|
||||||
@@ -216,8 +214,9 @@ def test_post_email(
|
|||||||
|
|
||||||
assert r['statusCode'] == HTTPStatus.CREATED
|
assert r['statusCode'] == HTTPStatus.CREATED
|
||||||
|
|
||||||
collect = DynamoDBCollection(dynamodb_persistence_layer)
|
user = dynamodb_persistence_layer.collection.get_item(
|
||||||
user = collect.get_item(KeyPair('5OxmMjL-ujoR5IMGegQz', '0'))
|
KeyPair('5OxmMjL-ujoR5IMGegQz', '0')
|
||||||
|
)
|
||||||
assert user['emails'] == {
|
assert user['emails'] == {
|
||||||
'sergio@somosbeta.com.br',
|
'sergio@somosbeta.com.br',
|
||||||
'osergiosiqueira@gmail.com',
|
'osergiosiqueira@gmail.com',
|
||||||
@@ -274,8 +273,9 @@ def test_delete_email(
|
|||||||
|
|
||||||
assert r['statusCode'] == HTTPStatus.OK
|
assert r['statusCode'] == HTTPStatus.OK
|
||||||
|
|
||||||
collect = DynamoDBCollection(dynamodb_persistence_layer)
|
user = dynamodb_persistence_layer.collection.get_item(
|
||||||
user = collect.get_item(KeyPair('5OxmMjL-ujoR5IMGegQz', '0'))
|
KeyPair('5OxmMjL-ujoR5IMGegQz', '0')
|
||||||
|
)
|
||||||
assert user['emails'] == {
|
assert user['emails'] == {
|
||||||
'sergio@somosbeta.com.br',
|
'sergio@somosbeta.com.br',
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
{"id": {"S": "cJtK9SsnJhKPyxESe7g3DG"}, "sk": {"S": "0"}, "name": {"S": "EDUSEG"}, "cnpj": {"S": "15608435000190"}, "email": {"S": "org+15608435000190@users.noreply.betaeducacao.com.br"}}
|
{"id": {"S": "cJtK9SsnJhKPyxESe7g3DG"}, "sk": {"S": "0"}, "name": {"S": "EDUSEG"}, "cnpj": {"S": "15608435000190"}, "email": {"S": "org+15608435000190@users.noreply.betaeducacao.com.br"}}
|
||||||
{"id": {"S": "cJtK9SsnJhKPyxESe7g3DG"}, "sk": {"S": "metadata#payment_policy"}, "due_days": {"N": "90"}}
|
{"id": {"S": "cJtK9SsnJhKPyxESe7g3DG"}, "sk": {"S": "metadata#payment_policy"}, "due_days": {"N": "90"}}
|
||||||
{"id": {"S": "cJtK9SsnJhKPyxESe7g3DG"}, "sk": {"S": "metadata#billing_policy"}, "billing_day": {"N": "1"}, "payment_method": {"S": "PIX"}}
|
{"id": {"S": "cJtK9SsnJhKPyxESe7g3DG"}, "sk": {"S": "metadata#billing_policy"}, "billing_day": {"N": "1"}, "payment_method": {"S": "PIX"}}
|
||||||
|
{"id": {"S": "cJtK9SsnJhKPyxESe7g3DG"}, "sk": {"S": "metadata#address"}, "address1": {"S": "Av. Presidente Kennedy, 815"}, "address2": {"S": "Sala 1"}, "city": {"S": "São José"}, "state": {"S": "SC"}, "postcode": {"S": "88101001"}, "neighborhood": {"S": "Campinas"}}
|
||||||
{"id": {"S": "90d7f0d2-d9a4-4467-a31c-f9a7955964cf"}, "sk": {"S": "0"}, "access_period": {"N": "720"}, "create_date": {"S": "2024-12-30T00:00:33.088916-03:00"},"konviva__class_id": {"N": "266"},"name": {"S": "Reciclagem em NR-18 Básico"},"tenant__org_id": {"SS": ["cJtK9SsnJhKPyxESe7g3DG"]}}
|
{"id": {"S": "90d7f0d2-d9a4-4467-a31c-f9a7955964cf"}, "sk": {"S": "0"}, "access_period": {"N": "720"}, "create_date": {"S": "2024-12-30T00:00:33.088916-03:00"},"konviva__class_id": {"N": "266"},"name": {"S": "Reciclagem em NR-18 Básico"},"tenant__org_id": {"SS": ["cJtK9SsnJhKPyxESe7g3DG"]}}
|
||||||
{"id": {"S": "43ea4475-c369-4f90-b576-135b7df5106b"}, "sk": {"S": "0"}, "course": {"M": {"id": {"S": "a6775b71-d68a-4263-8ab4-acb3a4f8a8b9"}, "name": {"S": "NR-18 PEMT PTA"}, "time_in_days": {"N": "365"}}}, "status": {"S": "PENDING"}}
|
{"id": {"S": "43ea4475-c369-4f90-b576-135b7df5106b"}, "sk": {"S": "0"}, "course": {"M": {"id": {"S": "a6775b71-d68a-4263-8ab4-acb3a4f8a8b9"}, "name": {"S": "NR-18 PEMT PTA"}, "time_in_days": {"N": "365"}}}, "status": {"S": "PENDING"}}
|
||||||
{"id": {"S": "43ea4475-c369-4f90-b576-135b7df5106b"}, "sk": {"S": "cancel_policy"}}
|
{"id": {"S": "43ea4475-c369-4f90-b576-135b7df5106b"}, "sk": {"S": "cancel_policy"}}
|
||||||
|
|||||||
Reference in New Issue
Block a user