diff --git a/order-events/app/events/assign_tenant_cnpj.py b/order-events/app/events/assign_tenant_cnpj.py index 385c327..6cf7e67 100644 --- a/order-events/app/events/assign_tenant_cnpj.py +++ b/order-events/app/events/assign_tenant_cnpj.py @@ -43,6 +43,8 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool: if len(ids) < 2: raise ValueError('IDs not found.') + logger.info('IDs found', ids=ids) + with order_layer.transact_writer() as transact: transact.update( key=KeyPair(new_image['id'], '0'), @@ -63,14 +65,4 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool: }, ) - # Post-migration: remove the following line - transact.put( - item={ - 'id': new_image['id'], - 'sk': 'metadata#tenant', - 'tenant_id': f'ORG#{ids["org_id"]}', - 'create_date': now_, - } - ) - return True diff --git a/order-events/app/events/remove_slots_if_canceled.py b/order-events/app/events/remove_slots_if_canceled.py index 22726e4..fc9d6f0 100644 --- a/order-events/app/events/remove_slots_if_canceled.py +++ b/order-events/app/events/remove_slots_if_canceled.py @@ -8,7 +8,6 @@ from layercake.dynamodb import ( ComposeKey, DynamoDBPersistenceLayer, KeyPair, - SortKey, ) from boto3clients import dynamodb_client @@ -29,27 +28,19 @@ class TenantDoesNotExistError(Exception): def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool: new_image = event.detail['new_image'] order_id = new_image['id'] - # Post-migration: remove the following line - tenant_id = order_layer.collection.get_item( - KeyPair( - order_id, - SortKey('metadata#tenant', path_spec='tenant_id'), - ), - exc_cls=TenantDoesNotExistError, - ).removeprefix('ORG#') - - # Post-migration: uncomment the following line - # tenant_id = new_image['tenant'] + tenant_id = new_image['tenant'] result = enrollment_layer.collection.query( KeyPair( # Post-migration: uncomment the following line - # ComposeKey(tenant_id, prefix='slots'), + # ComposeKey(tenant_id, prefix='slots#org'), ComposeKey(tenant_id, prefix='vacancies'), order_id, ) ) + logger.info('Slots found', slots=result['items']) + with enrollment_layer.batch_writer() as batch: for pair in result['items']: batch.delete_item( diff --git a/order-events/app/events/set_as_expired.py b/order-events/app/events/set_as_expired.py new file mode 100644 index 0000000..2be6fcb --- /dev/null +++ b/order-events/app/events/set_as_expired.py @@ -0,0 +1,46 @@ +from aws_lambda_powertools import Logger +from aws_lambda_powertools.utilities.data_classes import ( + EventBridgeEvent, + event_source, +) +from aws_lambda_powertools.utilities.typing import LambdaContext +from layercake.dateutils import now +from layercake.dynamodb import ( + DynamoDBPersistenceLayer, + KeyPair, +) + +from boto3clients import dynamodb_client +from config import ORDER_TABLE + +logger = Logger(__name__) +order_layer = DynamoDBPersistenceLayer(ORDER_TABLE, dynamodb_client) + + +@event_source(data_class=EventBridgeEvent) +@logger.inject_lambda_context +def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool: + new_image = event.detail['new_image'] + now_ = now() + + try: + order_layer.update_item( + key=KeyPair(new_image['id'], '0'), + update_expr='SET #status = :expired, updated_at = :updated_at', + cond_expr='#status IN (:pending) and payment_method = :payment_method', + expr_attr_names={ + '#status': 'status', + }, + expr_attr_values={ + ':expired': 'EXPIRED', + ':pending': 'PENDING', + ':payment_method': 'MANUAL', + ':updated_at': now_, + }, + ) + except Exception: + logger.info('Failed to update status to EXPIRED', order_id=new_image['id']) + return False + else: + logger.info('Status set to EXPIRED', order_id=new_image['id']) + return True diff --git a/order-events/app/events/stopgap/patch_items.py b/order-events/app/events/stopgap/patch_items.py index 0b4203c..6fa9599 100644 --- a/order-events/app/events/stopgap/patch_items.py +++ b/order-events/app/events/stopgap/patch_items.py @@ -55,12 +55,7 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool: return True -class CourseNotFoundError(Exception): - def __init__(self, *args): - super().__init__('Course not found') - - -def _get_course(course_id: str) -> dict: +def _get_course(course_id: str) -> dict | None: with sqlite3.connect( database=SQLITE_DATABASE, detect_types=sqlite3.PARSE_DECLTYPES ) as conn: @@ -70,4 +65,4 @@ def _get_course(course_id: str) -> dict: for row in rows: return row['json'] - raise CourseNotFoundError + return None diff --git a/order-events/app/events/stopgap/remove_slots.py b/order-events/app/events/stopgap/remove_slots.py index ae5207d..344f6f8 100644 --- a/order-events/app/events/stopgap/remove_slots.py +++ b/order-events/app/events/stopgap/remove_slots.py @@ -8,8 +8,6 @@ from layercake.dynamodb import ( ComposeKey, DynamoDBPersistenceLayer, KeyPair, - SortKey, - TransactKey, ) from boto3clients import dynamodb_client @@ -27,21 +25,8 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool: """Remove slots if the tenant has a `metadata#billing_policy` and the total is greater than zero.""" new_image = event.detail['new_image'] - order_id = new_image['id'] - data = order_layer.collection.get_items( - TransactKey(order_id) - + SortKey('0') - + KeyPair( - pk=order_id, - sk=SortKey( - sk='metadata#tenant', - path_spec='tenant_id', - remove_prefix='metadata#', - ), - rename_key='tenant_id', - ) - ) - tenant_id = data['tenant_id'].removeprefix('ORG#') + data = order_layer.get_item(KeyPair(new_image['id'], '0')) + tenant_id = data['tenant'] policy = user_layer.collection.get_item( KeyPair(pk=tenant_id, sk='metadata#billing_policy'), @@ -49,16 +34,22 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool: default=False, ) - # Skip if missing billing policy or order is zero/negative + # Skip if billing policy is missing or order is less than or equal to zero if not policy or data['total'] <= 0: + logger.info('Missing billing policy.') return False + logger.info(f'Billing policy from Tenant ID "{tenant_id}" found', policy=policy) + result = enrollment_layer.collection.query( KeyPair( ComposeKey(tenant_id, prefix='vacancies'), - order_id, + new_image['id'], ) ) + + logger.info('Slots found', slots=result['items']) + with enrollment_layer.batch_writer() as batch: for pair in result['items']: batch.delete_item( @@ -68,4 +59,6 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool: } ) + logger.info('Deleted all slots') + return True diff --git a/order-events/tests/conftest.py b/order-events/tests/conftest.py index fc62764..6773b21 100644 --- a/order-events/tests/conftest.py +++ b/order-events/tests/conftest.py @@ -19,6 +19,7 @@ def pytest_configure(): os.environ['ENROLLMENT_TABLE'] = PYTEST_TABLE_NAME os.environ['ORDER_TABLE'] = PYTEST_TABLE_NAME os.environ['COURSE_TABLE'] = PYTEST_TABLE_NAME + os.environ['LOG_LEVEL'] = 'DEBUG' @dataclass diff --git a/order-events/tests/events/test_remove_slots_if_canceled.py b/order-events/tests/events/test_remove_slots_if_canceled.py index 709d200..85cd540 100644 --- a/order-events/tests/events/test_remove_slots_if_canceled.py +++ b/order-events/tests/events/test_remove_slots_if_canceled.py @@ -14,6 +14,7 @@ def test_remove_slots_if_canceled( 'new_image': { 'id': '9omWNKymwU5U4aeun6mWzZ', 'status': 'CANCELED', + 'tenant': 'cJtK9SsnJhKPyxESe7g3DG', } } } diff --git a/order-events/tests/events/test_set_as_expired.py b/order-events/tests/events/test_set_as_expired.py new file mode 100644 index 0000000..634f681 --- /dev/null +++ b/order-events/tests/events/test_set_as_expired.py @@ -0,0 +1,25 @@ +import app.events.set_as_expired as app + + +def test_set_as_expired(dynamodb_seeds, lambda_context): + event = { + 'detail': { + 'new_image': { + 'id': '9omWNKymwU5U4aeun6mWzZ', + 'sk': 'schedules#set_as_expired', + }, + } + } + assert app.lambda_handler(event, lambda_context) + + +def test_set_as_expired_failed(dynamodb_seeds, lambda_context): + event = { + 'detail': { + 'new_image': { + 'id': '18f934d8-035a-4ebc-9f8b-6c84782b8c73', + 'sk': 'schedules#set_as_expired', + }, + } + } + assert not app.lambda_handler(event, lambda_context) diff --git a/order-events/tests/seeds.jsonl b/order-events/tests/seeds.jsonl index bd070f3..14ced81 100644 --- a/order-events/tests/seeds.jsonl +++ b/order-events/tests/seeds.jsonl @@ -1,12 +1,13 @@ {"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": "9omWNKymwU5U4aeun6mWzZ"}, "sk": {"S": "0"}, "total": {"N": "398"}, "status": {"S": "PENDING"}} -{"id": {"S": "9omWNKymwU5U4aeun6mWzZ"}, "sk": {"S": "metadata#tenant"}, "tenant_id": {"S": "ORG#cJtK9SsnJhKPyxESe7g3DG"}} +{"id": {"S": "9omWNKymwU5U4aeun6mWzZ"}, "sk": {"S": "0"}, "total": {"N": "398"}, "status": {"S": "PENDING"}, "payment_method": {"S": "MANUAL"}, "tenant": {"S": "cJtK9SsnJhKPyxESe7g3DG"}} +{"id": {"S": "9omWNKymwU5U4aeun6mWzZ"}, "sk": {"S": "0"}, "total": {"N": "398"}, "status": {"S": "PENDING"}, "payment_method": {"S": "MANUAL"}, "tenant": {"S": "cJtK9SsnJhKPyxESe7g3DG"}} {"id": {"S": "cnpj"}, "sk": {"S": "15608435000190"}, "user_id": {"S": "cJtK9SsnJhKPyxESe7g3DG"}} {"id": {"S": "email"}, "sk": {"S": "sergio@somosbeta.com.br"}, "user_id": {"S": "5OxmMjL-ujoR5IMGegQz"}} {"id": {"S": "5OxmMjL-ujoR5IMGegQz"}, "sk": {"S": "0"}, "name": {"S": "Sérgio R Siqueira"}} {"id": {"S": "vacancies#cJtK9SsnJhKPyxESe7g3DG"}, "sk": {"S": "9omWNKymwU5U4aeun6mWzZ#1"}} {"id": {"S": "vacancies#cJtK9SsnJhKPyxESe7g3DG"}, "sk": {"S": "9omWNKymwU5U4aeun6mWzZ#2"}} {"id": {"S": "vacancies#cJtK9SsnJhKPyxESe7g3DG"}, "sk": {"S": "9omWNKymwU5U4aeun6mWzZ#3"}} +{"id": {"S": "18f934d8-035a-4ebc-9f8b-6c84782b8c73"}, "sk": {"S": "0"}, "payment_method": {"S": "PAID"}} {"id": {"S": "6a60d026-d383-4707-b093-b6eddea1a24e"}, "sk": {"S": "items"},"items": {"L": [{"M": {"id": {"S": "a810dd22-56c0-4d9b-8cd2-7e2ee9c45839"}, "name": {"S": "pytest"},"quantity": {"N": "1"},"unit_price": {"N": "109"}}}]}} {"id": {"S": "a810dd22-56c0-4d9b-8cd2-7e2ee9c45839"}, "sk": {"S": "metadata#betaeducacao"},"course_id": {"S": "dc1a0428-47bf-4db1-a5da-24be49c9fda6"},"create_date": {"S": "2025-06-05T12:13:54.371416+00:00"}} \ No newline at end of file