= {
+ PENDING: 'Pendente',
+ EXECUTED: 'Executado',
+ SCHEDULED: 'Agendado',
+ ROLLBACK: 'Revogado'
+}
+
+function Status({ status: s }: { status: string }) {
+ const status = labels[s] ?? s
+ const { icon: Icon, color } = statuses?.[s] ?? { icon: HelpCircleIcon }
+
+ return (
+
+
+ {status}
+
+ )
+}
diff --git a/apps/admin.saladeaula.digital/app/routes/_.$orgid.payments.$id._index/route.tsx b/apps/admin.saladeaula.digital/app/routes/_.$orgid.payments.$id._index/route.tsx
index eed76e1..2c77677 100644
--- a/apps/admin.saladeaula.digital/app/routes/_.$orgid.payments.$id._index/route.tsx
+++ b/apps/admin.saladeaula.digital/app/routes/_.$orgid.payments.$id._index/route.tsx
@@ -17,7 +17,10 @@ import { useForm } from 'react-hook-form'
import { Link, useRevalidator } from 'react-router'
import { z } from 'zod'
+import { Abbr } from '@repo/ui/components/abbr'
import { Currency } from '@repo/ui/components/currency'
+import { DateTime } from '@repo/ui/components/datetime'
+import { Badge } from '@repo/ui/components/ui/badge'
import {
Breadcrumb,
BreadcrumbItem,
@@ -26,34 +29,13 @@ import {
BreadcrumbPage,
BreadcrumbSeparator
} from '@repo/ui/components/ui/breadcrumb'
+import { Button } from '@repo/ui/components/ui/button'
import {
Card,
CardContent,
CardHeader,
CardTitle
} from '@repo/ui/components/ui/card'
-import {
- Item,
- ItemActions,
- ItemContent,
- ItemGroup,
- ItemTitle
-} from '@repo/ui/components/ui/item'
-import {
- Table,
- TableBody,
- TableCell,
- TableFooter,
- TableHead,
- TableHeader,
- TableRow
-} from '@repo/ui/components/ui/table'
-import { request as req } from '@repo/util/request'
-
-import { Abbr } from '@repo/ui/components/abbr'
-import { DateTime } from '@repo/ui/components/datetime'
-import { Badge } from '@repo/ui/components/ui/badge'
-import { Button } from '@repo/ui/components/ui/button'
import {
Dialog,
DialogClose,
@@ -64,6 +46,13 @@ import {
DialogTitle,
DialogTrigger
} from '@repo/ui/components/ui/dialog'
+import {
+ Item,
+ ItemActions,
+ ItemContent,
+ ItemGroup,
+ ItemTitle
+} from '@repo/ui/components/ui/item'
import { Kbd } from '@repo/ui/components/ui/kbd'
import {
Popover,
@@ -72,12 +61,22 @@ import {
} from '@repo/ui/components/ui/popover'
import { Separator } from '@repo/ui/components/ui/separator'
import { Spinner } from '@repo/ui/components/ui/spinner'
+import {
+ Table,
+ TableBody,
+ TableCell,
+ TableFooter,
+ TableHead,
+ TableHeader,
+ TableRow
+} from '@repo/ui/components/ui/table'
import { cn } from '@repo/ui/lib/utils'
import {
labels,
statuses,
type Order as Order_
} from '@repo/ui/routes/orders/data'
+import { request as req } from '@repo/util/request'
import {
CreditCard,
creditCardSchema,
@@ -85,6 +84,7 @@ import {
} from '../_.$orgid.enrollments.buy/payment'
import type { Address } from '../_.$orgid.enrollments.buy/review'
import { useWizardStore } from '../_.$orgid.enrollments.buy/store'
+import { Enrollments } from './enrollments'
export function meta() {
return [
@@ -131,6 +131,24 @@ type Attempts = {
last4: string
}
+type Course = {
+ id: string
+ name: string
+}
+
+export type Enrollment = {
+ status: 'PENDING' | 'EXECUTED' | 'ROLLBACK'
+ user: { id: string; name: string; email: string }
+ course: Course
+ executed_at?: string
+ rollback_at?: string
+ scheduled_at?: string
+}
+
+export type Seat = {
+ course: Course
+}
+
type Order = Order_ & {
items: Item[]
interest_amount: number
@@ -142,6 +160,8 @@ type Order = Order_ & {
payment_attempts: Attempts[]
credit_card?: CreditCardProps
coupon?: string
+ enrollments?: Enrollment[]
+ seats?: Seat[]
installments?: number
created_by?: User
invoice: Invoice
@@ -173,6 +193,8 @@ export default function Route({ loaderData: { order } }: Route.ComponentProps) {
discount,
invoice,
payment_attempts = [],
+ enrollments = [],
+ seats = [],
items = [],
subtotal
} = order
@@ -185,7 +207,7 @@ export default function Route({ loaderData: { order } }: Route.ComponentProps) {
useEffect(() => {
reset()
}, [])
-
+ console.log(seats)
return (
@@ -320,6 +342,10 @@ export default function Route({ loaderData: { order } }: Route.ComponentProps) {
+
+ {enrollments.length > 0 ? (
+
+ ) : null}
)
}
diff --git a/enrollments-events/app/enrollment.py b/enrollments-events/app/enrollment.py
index 46c1364..260e899 100644
--- a/enrollments-events/app/enrollment.py
+++ b/enrollments-events/app/enrollment.py
@@ -18,7 +18,7 @@ from pydantic import (
)
from typing_extensions import NotRequired
-from config import DEDUP_WINDOW_OFFSET_DAYS, USER_TABLE
+from config import DEDUP_WINDOW_OFFSET_DAYS, ORDER_TABLE, USER_TABLE
class User(BaseModel):
@@ -107,6 +107,11 @@ class SeatNotFoundError(Exception):
super().__init__('Seat required')
+class OrderNotFoundError(Exception):
+ def __init__(self, msg: str | dict):
+ super().__init__('Order not found')
+
+
def enroll(
enrollment: Enrollment,
*,
@@ -150,6 +155,26 @@ def enroll(
| ({'seat': seat} if seat else {})
)
+ if seat:
+ transact.condition(
+ key=KeyPair(str(seat['order_id']), '0'),
+ cond_expr='attribute_exists(sk)',
+ exc_cls=OrderNotFoundError,
+ table_name=ORDER_TABLE,
+ )
+ transact.put(
+ item={
+ 'id': seat['order_id'],
+ 'sk': f'ENROLLMENT#{enrollment.id}',
+ 'course': course.model_dump(),
+ 'user': user.model_dump(),
+ 'status': 'EXECUTED',
+ 'executed_at': now_,
+ 'created_at': now_,
+ },
+ table_name=ORDER_TABLE,
+ )
+
# Relationships between this enrollment and its related entities
for entity in linked_entities:
# Parent knows the child
diff --git a/enrollments-events/uv.lock b/enrollments-events/uv.lock
index c27000a..68e2117 100644
--- a/enrollments-events/uv.lock
+++ b/enrollments-events/uv.lock
@@ -683,7 +683,7 @@ wheels = [
[[package]]
name = "layercake"
-version = "0.13.1"
+version = "0.13.4"
source = { directory = "../layercake" }
dependencies = [
{ name = "arnparse" },
diff --git a/orders-events/app/events/start_fulfillment.py b/orders-events/app/events/start_fulfillment.py
index 92868d4..8f91af7 100644
--- a/orders-events/app/events/start_fulfillment.py
+++ b/orders-events/app/events/start_fulfillment.py
@@ -118,6 +118,8 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
org_id=org_id,
)
+ logger.debug('Some enrollments failed', failed=failed)
+
with dyn.transact_writer() as transact:
for x in failed:
reason = _friendly_reason(x.cause['type']) # type: ignore
@@ -163,7 +165,7 @@ def _release_seats(
) -> None:
now_ = now()
- with dyn.transact_writer(table_name=ORDER_TABLE) as transact:
+ with dyn.transact_writer(table_name=ENROLLMENT_TABLE) as transact:
for course in courses:
transact.put(
item={
@@ -172,7 +174,6 @@ def _release_seats(
'course': course.model_dump(),
'created_at': now_,
},
- table_name=ORDER_TABLE,
)
@@ -263,6 +264,26 @@ def _enroll_now(enrollment: Enrollment, context: Context) -> None:
'seat': {'order_id': order_id},
}
)
+ # Relationships between this enrollment and its related entities
+ transact.put(
+ item={
+ 'id': order_id,
+ 'sk': f'LINKED_ENTITIES#CHILD#ENROLLMENT#{enrollment.id}',
+ 'created_at': now_,
+ },
+ cond_expr='attribute_not_exists(sk)',
+ table_name=ORDER_TABLE,
+ )
+ # Child knows the parent
+ transact.put(
+ item={
+ 'id': enrollment.id,
+ 'sk': f'LINKED_ENTITIES#PARENT#ORDER#{order_id}',
+ 'created_at': now_,
+ },
+ cond_expr='attribute_not_exists(sk)',
+ )
+
transact.update(
key=KeyPair(order_id, f'ENROLLMENT#{enrollment.id}'),
update_expr='SET #status = :executed, \
diff --git a/orders-events/template.yaml b/orders-events/template.yaml
index dc2013e..4ee16c9 100644
--- a/orders-events/template.yaml
+++ b/orders-events/template.yaml
@@ -67,7 +67,7 @@ Resources:
LoggingConfig:
LogGroup: !Ref HttpLog
Policies:
- - DynamoDBWritePolicy:
+ - DynamoDBCrudPolicy:
TableName: !Ref OrderTable
Events:
Post:
diff --git a/packages/ui/src/routes/orders/data.tsx b/packages/ui/src/routes/orders/data.tsx
index ce8ba89..e0fe974 100644
--- a/packages/ui/src/routes/orders/data.tsx
+++ b/packages/ui/src/routes/orders/data.tsx
@@ -1,10 +1,10 @@
import {
- CheckCircle2Icon,
- ClockAlertIcon,
- RotateCcwIcon,
- CircleXIcon,
- ClockIcon,
BanIcon,
+ CheckCircle2Icon,
+ CircleXIcon,
+ ClockAlertIcon,
+ ClockIcon,
+ RotateCcwIcon,
type LucideIcon
} from 'lucide-react'
@@ -21,39 +21,30 @@ export type Order = {
email: string
}
-export const statuses: Record<
- string,
- { icon: LucideIcon; color?: string; label: string }
-> = {
+export const statuses: Record = {
PENDING: {
icon: ClockIcon,
- label: 'Pendente',
color: 'text-blue-400 [&_svg]:text-blue-500'
},
PAID: {
icon: CheckCircle2Icon,
- color: 'text-green-400 [&_svg]:text-green-500',
- label: 'Pago'
+ color: 'text-green-400 [&_svg]:text-green-500'
},
DECLINED: {
icon: BanIcon,
- color: 'text-red-400 [&_svg]:text-red-500',
- label: 'Negado'
+ color: 'text-red-400 [&_svg]:text-red-500'
},
EXPIRED: {
icon: ClockAlertIcon,
- color: 'text-orange-400 [&_svg]:text-orange-500',
- label: 'Expirado'
+ color: 'text-orange-400 [&_svg]:text-orange-500'
},
REFUNDED: {
icon: RotateCcwIcon,
- color: 'text-orange-400 [&_svg]:text-orange-500',
- label: 'Estornado'
+ color: 'text-orange-400 [&_svg]:text-orange-500'
},
CANCELED: {
icon: CircleXIcon,
- color: 'text-red-400 [&_svg]:text-red-500',
- label: 'Cancelado'
+ color: 'text-red-400 [&_svg]:text-red-500'
}
}