diff --git a/api.saladeaula.digital/app/routes/enrollments/cancel.py b/api.saladeaula.digital/app/routes/enrollments/cancel.py index 1b4e189..900d562 100644 --- a/api.saladeaula.digital/app/routes/enrollments/cancel.py +++ b/api.saladeaula.digital/app/routes/enrollments/cancel.py @@ -48,7 +48,7 @@ def cancel( transact.put( item={ 'id': enrollment_id, - 'sk': 'CANCELED_BY', + 'sk': 'CANCELED', 'canceled_by': { 'id': canceled_by.id, 'name': canceled_by.name, diff --git a/api.saladeaula.digital/app/routes/enrollments/enroll.py b/api.saladeaula.digital/app/routes/enrollments/enroll.py index 0f9a51b..508c30b 100644 --- a/api.saladeaula.digital/app/routes/enrollments/enroll.py +++ b/api.saladeaula.digital/app/routes/enrollments/enroll.py @@ -221,10 +221,8 @@ def enroll_now(enrollment: Enrollment, context: Context): item={ 'id': enrollment.id, 'sk': 'CREATED_BY', - 'created_by': { - 'id': created_by.id, - 'name': created_by.name, - }, + 'name': created_by.name, + 'user_id': created_by.id, 'created_at': now_, } ) diff --git a/apps/admin.saladeaula.digital/app/routes/_.$orgid.billing._index/route.tsx b/apps/admin.saladeaula.digital/app/routes/_.$orgid.billing._index/route.tsx index a4d7f59..2d86c89 100644 --- a/apps/admin.saladeaula.digital/app/routes/_.$orgid.billing._index/route.tsx +++ b/apps/admin.saladeaula.digital/app/routes/_.$orgid.billing._index/route.tsx @@ -2,12 +2,11 @@ import type { Route } from './+types/route' import { DateTime } from 'luxon' import { Suspense } from 'react' -import { ClockIcon } from 'lucide-react' +import { BanIcon } from 'lucide-react' import { request as req } from '@repo/util/request' import { Skeleton } from '@repo/ui/components/skeleton' import { Await } from 'react-router' -import { billingPeriod, formatDate } from './util' import { Card, CardContent } from '@repo/ui/components/ui/card' import { Table, @@ -20,10 +19,18 @@ import { } from '@repo/ui/components/ui/table' import { Abbr } from '@repo/ui/components/abbr' import { Button } from '@repo/ui/components/ui/button' +import { cn } from '@repo/ui/lib/utils' +import { + Empty, + EmptyDescription, + EmptyHeader, + EmptyMedia, + EmptyTitle +} from '@repo/ui/components/ui/empty' +import { billingPeriod, formatDate } from './util' import { RangePeriod } from './range-period' import { statuses } from './data' -import { cn } from '@repo/ui/lib/utils' export function meta({}) { return [{ title: 'Resumo de cobranças' }] @@ -81,9 +88,16 @@ export default function Route({ label: status, color } = statuses?.[billing?.status || 'CLOSED'] + const charges = items + ?.filter((item) => 'course' in item && item?.unit_price > 0) + ?.sort(sortBy('enrolled_at')) + const credits = items + ?.filter((item) => 'course' in item && item?.unit_price < 0) + ?.sort(sortBy('created_at')) + return ( - +
- - - - Colaborador - Curso - Matriculado por - Matriculado em - Valor unit. - - - - {items - ?.filter((item) => 'course' in item) - ?.map( - ( - { - user, - course, - author: created_by, - unit_price, - enrolled_at - }, - index - ) => ( - - - {user.name} - - - {course.name} - - - - {created_by ? created_by.name : 'N/A'} - - - - {datetime.format(new Date(enrolled_at))} - - - {currency.format(unit_price)} - - - ) - )} + {items.length ? ( + <> +
+ {charges.length ? ( + <> + + + Colaborador + Curso + Matriculado por + Matriculado em + Valor unit. + + + + {charges?.map( + ( + { + user, + course, + author: created_by, + unit_price, + enrolled_at + }, + index + ) => ( + + + {user.name} + + + {course.name} + + + + {created_by ? created_by.name : 'N/A'} + + + + {datetime.format(new Date(enrolled_at))} + + + {currency.format(unit_price)} + + + ) + )} + + + ) : null} - {items.length === 0 && ( - - - Nenhum resultado. - - - )} - - - - - Total - - - {currency.format( - items - ?.filter((item) => 'course' in item) - ?.reduce( - (acc, { unit_price }) => acc + unit_price, - 0 - ) - )} - - - -
+ {credits.length ? ( + <> + + + + + + + Colaborador + Curso + Cancelado por + Cancelado em + Valor unit. + + + + {credits?.map( + ( + { + user, + course, + author: canceled_by, + unit_price, + created_at + }, + index + ) => ( + + + {user.name} + + + {course.name} + + + + {canceled_by ? canceled_by.name : 'N/A'} + + + + {datetime.format(new Date(created_at))} + + + {currency.format(unit_price)} + + + ) + )} + + + ) : null} + + + + + Total + + + {currency.format( + items + ?.filter((x) => 'course' in x) + .reduce( + (acc, { unit_price }) => acc + unit_price, + 0 + ) + )} + + + + + + ) : ( + + + + + + Nenhuma cobrança encontrada + + Não há nenhuma cobrança para este período. + + + + )}
) @@ -195,3 +275,6 @@ const datetime = new Intl.DateTimeFormat('pt-BR', { hour: '2-digit', minute: '2-digit' }) + +const sortBy = (field: 'enrolled_at' | 'created_at') => (a: any, b: any) => + new Date(a[field]).getTime() - new Date(b[field]).getTime() diff --git a/enrollments-events/app/enrollment.py b/enrollments-events/app/enrollment.py index 0689e66..afa94aa 100644 --- a/enrollments-events/app/enrollment.py +++ b/enrollments-events/app/enrollment.py @@ -94,6 +94,7 @@ def enroll( enrollment: Enrollment, *, org: Org | None = None, + cancel_policy: bool = False, subscription: Subscription | None = None, created_by: CreatedBy | None = None, scheduled_at: datetime | None = None, @@ -120,6 +121,15 @@ def enroll( | ({'scheduled_at': scheduled_at} if scheduled_at else {}) ) + if cancel_policy: + transact.put( + item={ + 'id': enrollment.id, + 'sk': 'CANCEL_POLICY', + 'created_at': now_, + } + ) + # Relationships between this enrollment and its related entities for entity in linked_entities: # Parent knows the child @@ -168,7 +178,8 @@ def enroll( item={ 'id': enrollment.id, 'sk': 'CREATED_BY', - 'created_by': created_by, + 'name': created_by['name'], + 'user_id': created_by['id'], 'created_at': now_, } ) diff --git a/enrollments-events/app/events/enroll_scheduled.py b/enrollments-events/app/events/enroll_scheduled.py index 76b1710..8fd6000 100644 --- a/enrollments-events/app/events/enroll_scheduled.py +++ b/enrollments-events/app/events/enroll_scheduled.py @@ -12,7 +12,7 @@ from layercake.funcs import pick from boto3clients import dynamodb_client from config import ENROLLMENT_TABLE -from enrollment import Enrollment, enroll +from enrollment import Enrollment, Subscription, enroll logger = Logger(__name__) dyn = DynamoDBPersistenceLayer(ENROLLMENT_TABLE, dynamodb_client) @@ -33,6 +33,14 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool: course=old_image['course'], user=old_image['user'], ) + subscription: Subscription | None = ( + { + 'org_id': org_id, + 'billing_day': int(billing_day), + } + if billing_day + else None + ) try: enroll( @@ -41,14 +49,8 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool: 'org_id': org_id, 'name': old_image['org_name'], }, - subscription=( - { - 'org_id': org_id, - 'billing_day': int(billing_day), - } - if billing_day - else None - ), + subscription=subscription, + cancel_policy=bool(subscription), created_by=created_by, scheduled_at=datetime.fromisoformat(old_image['created_at']), # Transfer the deduplication window if it exists diff --git a/orders-events/app/events/billing/append_enrollment.py b/orders-events/app/events/billing/append_enrollment.py index b6869fa..339e6fc 100644 --- a/orders-events/app/events/billing/append_enrollment.py +++ b/orders-events/app/events/billing/append_enrollment.py @@ -43,9 +43,11 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool: now_ = now() org_id = new_image['org_id'] enrollment = enrollment_layer.collection.get_items( - TransactKey(new_image['id']) + SortKey('0') + SortKey('author') - # Post-migration: uncomment the following line - # + SortKey('CREATED_BY') + TransactKey(new_image['id']) + + SortKey('0') + + SortKey('CREATED_BY', rename_key='created_by', path_spec='created_by') + # Post-migration: remove the following line + + SortKey('author', rename_key='created_by') ) if not enrollment: @@ -99,7 +101,7 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool: # Add enrollment entry to billing try: - created_by = enrollment.get('author') + created_by = enrollment.get('created_by') course_id = enrollment['course']['id'] course = course_layer.collection.get_items( KeyPair(