update table

This commit is contained in:
2025-12-13 00:14:24 -03:00
parent 72f049babd
commit a1c0e3dcd5
6 changed files with 191 additions and 95 deletions

View File

@@ -48,7 +48,7 @@ def cancel(
transact.put( transact.put(
item={ item={
'id': enrollment_id, 'id': enrollment_id,
'sk': 'CANCELED_BY', 'sk': 'CANCELED',
'canceled_by': { 'canceled_by': {
'id': canceled_by.id, 'id': canceled_by.id,
'name': canceled_by.name, 'name': canceled_by.name,

View File

@@ -221,10 +221,8 @@ def enroll_now(enrollment: Enrollment, context: Context):
item={ item={
'id': enrollment.id, 'id': enrollment.id,
'sk': 'CREATED_BY', 'sk': 'CREATED_BY',
'created_by': { 'name': created_by.name,
'id': created_by.id, 'user_id': created_by.id,
'name': created_by.name,
},
'created_at': now_, 'created_at': now_,
} }
) )

View File

@@ -2,12 +2,11 @@ import type { Route } from './+types/route'
import { DateTime } from 'luxon' import { DateTime } from 'luxon'
import { Suspense } from 'react' import { Suspense } from 'react'
import { ClockIcon } from 'lucide-react' import { BanIcon } from 'lucide-react'
import { request as req } from '@repo/util/request' import { request as req } from '@repo/util/request'
import { Skeleton } from '@repo/ui/components/skeleton' import { Skeleton } from '@repo/ui/components/skeleton'
import { Await } from 'react-router' import { Await } from 'react-router'
import { billingPeriod, formatDate } from './util'
import { Card, CardContent } from '@repo/ui/components/ui/card' import { Card, CardContent } from '@repo/ui/components/ui/card'
import { import {
Table, Table,
@@ -20,10 +19,18 @@ import {
} from '@repo/ui/components/ui/table' } from '@repo/ui/components/ui/table'
import { Abbr } from '@repo/ui/components/abbr' import { Abbr } from '@repo/ui/components/abbr'
import { Button } from '@repo/ui/components/ui/button' 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 { RangePeriod } from './range-period'
import { statuses } from './data' import { statuses } from './data'
import { cn } from '@repo/ui/lib/utils'
export function meta({}) { export function meta({}) {
return [{ title: 'Resumo de cobranças' }] return [{ title: 'Resumo de cobranças' }]
@@ -81,9 +88,16 @@ export default function Route({
label: status, label: status,
color color
} = statuses?.[billing?.status || 'CLOSED'] } = 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 ( return (
<Card> <Card>
<CardContent className="space-y-2.5"> <CardContent className="space-y-4">
<div className="flex max-lg:flex-col gap-2.5"> <div className="flex max-lg:flex-col gap-2.5">
<Button <Button
className={cn('pointer-events-none', color)} className={cn('pointer-events-none', color)}
@@ -98,82 +112,148 @@ export default function Route({
<RangePeriod <RangePeriod
startDate={startDate} startDate={startDate}
endDate={endDate} endDate={endDate}
billingDay={subscription.billing_day || 1} billingDay={subscription?.billing_day || 1}
/> />
</div> </div>
<Table className="table-auto w-full"> {items.length ? (
<TableHeader> <>
<TableRow className="hover:bg-transparent"> <Table className="table-auto w-full">
<TableHead>Colaborador</TableHead> {charges.length ? (
<TableHead>Curso</TableHead> <>
<TableHead>Matriculado por</TableHead> <TableHeader>
<TableHead>Matriculado em</TableHead> <TableRow className="bg-muted-foreground/15 pointer-events-none">
<TableHead>Valor unit.</TableHead> <TableHead>Colaborador</TableHead>
</TableRow> <TableHead>Curso</TableHead>
</TableHeader> <TableHead>Matriculado por</TableHead>
<TableBody> <TableHead>Matriculado em</TableHead>
{items <TableHead>Valor unit.</TableHead>
?.filter((item) => 'course' in item) </TableRow>
?.map( </TableHeader>
( <TableBody>
{ {charges?.map(
user, (
course, {
author: created_by, user,
unit_price, course,
enrolled_at author: created_by,
}, unit_price,
index enrolled_at
) => ( },
<TableRow key={index}> index
<TableCell> ) => (
<Abbr>{user.name}</Abbr> <TableRow key={index}>
</TableCell> <TableCell>
<TableCell> <Abbr>{user.name}</Abbr>
<Abbr>{course.name}</Abbr> </TableCell>
</TableCell> <TableCell>
<TableCell> <Abbr>{course.name}</Abbr>
<Abbr> </TableCell>
{created_by ? created_by.name : 'N/A'} <TableCell>
</Abbr> <Abbr>
</TableCell> {created_by ? created_by.name : 'N/A'}
<TableCell> </Abbr>
{datetime.format(new Date(enrolled_at))} </TableCell>
</TableCell> <TableCell>
<TableCell> {datetime.format(new Date(enrolled_at))}
{currency.format(unit_price)} </TableCell>
</TableCell> <TableCell>
</TableRow> {currency.format(unit_price)}
) </TableCell>
)} </TableRow>
)
)}
</TableBody>
</>
) : null}
{items.length === 0 && ( {credits.length ? (
<TableRow className="hover:bg-transparent"> <>
<TableCell colSpan={5} className="h-24 text-center"> <TableRow className="pointer-events-none">
Nenhum resultado. <TableCell colSpan={5}></TableCell>
</TableCell> </TableRow>
</TableRow>
)} <TableHeader>
</TableBody> <TableRow className="bg-muted-foreground/15 pointer-events-none">
<TableFooter> <TableHead>Colaborador</TableHead>
<TableRow className="hover:bg-transparent"> <TableHead>Curso</TableHead>
<TableCell colSpan={4} className="text-right"> <TableHead>Cancelado por</TableHead>
Total <TableHead>Cancelado em</TableHead>
</TableCell> <TableHead>Valor unit.</TableHead>
<TableCell> </TableRow>
{currency.format( </TableHeader>
items <TableBody>
?.filter((item) => 'course' in item) {credits?.map(
?.reduce( (
(acc, { unit_price }) => acc + unit_price, {
0 user,
) course,
)} author: canceled_by,
</TableCell> unit_price,
</TableRow> created_at
</TableFooter> },
</Table> index
) => (
<TableRow key={index}>
<TableCell>
<Abbr>{user.name}</Abbr>
</TableCell>
<TableCell>
<Abbr>{course.name}</Abbr>
</TableCell>
<TableCell>
<Abbr>
{canceled_by ? canceled_by.name : 'N/A'}
</Abbr>
</TableCell>
<TableCell>
{datetime.format(new Date(created_at))}
</TableCell>
<TableCell>
{currency.format(unit_price)}
</TableCell>
</TableRow>
)
)}
</TableBody>
</>
) : null}
<TableFooter>
<TableRow>
<TableCell
colSpan={4}
className="text-right pointer-events-none"
>
Total
</TableCell>
<TableCell>
{currency.format(
items
?.filter((x) => 'course' in x)
.reduce(
(acc, { unit_price }) => acc + unit_price,
0
)
)}
</TableCell>
</TableRow>
</TableFooter>
</Table>
</>
) : (
<Empty className="border border-dashed">
<EmptyHeader>
<EmptyMedia variant="icon">
<BanIcon />
</EmptyMedia>
<EmptyTitle>Nenhuma cobrança encontrada</EmptyTitle>
<EmptyDescription>
Não nenhuma cobrança para este período.
</EmptyDescription>
</EmptyHeader>
</Empty>
)}
</CardContent> </CardContent>
</Card> </Card>
) )
@@ -195,3 +275,6 @@ const datetime = new Intl.DateTimeFormat('pt-BR', {
hour: '2-digit', hour: '2-digit',
minute: '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()

View File

@@ -94,6 +94,7 @@ def enroll(
enrollment: Enrollment, enrollment: Enrollment,
*, *,
org: Org | None = None, org: Org | None = None,
cancel_policy: bool = False,
subscription: Subscription | None = None, subscription: Subscription | None = None,
created_by: CreatedBy | None = None, created_by: CreatedBy | None = None,
scheduled_at: datetime | None = None, scheduled_at: datetime | None = None,
@@ -120,6 +121,15 @@ def enroll(
| ({'scheduled_at': scheduled_at} if scheduled_at else {}) | ({'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 # Relationships between this enrollment and its related entities
for entity in linked_entities: for entity in linked_entities:
# Parent knows the child # Parent knows the child
@@ -168,7 +178,8 @@ def enroll(
item={ item={
'id': enrollment.id, 'id': enrollment.id,
'sk': 'CREATED_BY', 'sk': 'CREATED_BY',
'created_by': created_by, 'name': created_by['name'],
'user_id': created_by['id'],
'created_at': now_, 'created_at': now_,
} }
) )

View File

@@ -12,7 +12,7 @@ from layercake.funcs import pick
from boto3clients import dynamodb_client from boto3clients import dynamodb_client
from config import ENROLLMENT_TABLE from config import ENROLLMENT_TABLE
from enrollment import Enrollment, enroll from enrollment import Enrollment, Subscription, enroll
logger = Logger(__name__) logger = Logger(__name__)
dyn = DynamoDBPersistenceLayer(ENROLLMENT_TABLE, dynamodb_client) dyn = DynamoDBPersistenceLayer(ENROLLMENT_TABLE, dynamodb_client)
@@ -33,6 +33,14 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
course=old_image['course'], course=old_image['course'],
user=old_image['user'], user=old_image['user'],
) )
subscription: Subscription | None = (
{
'org_id': org_id,
'billing_day': int(billing_day),
}
if billing_day
else None
)
try: try:
enroll( enroll(
@@ -41,14 +49,8 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
'org_id': org_id, 'org_id': org_id,
'name': old_image['org_name'], 'name': old_image['org_name'],
}, },
subscription=( subscription=subscription,
{ cancel_policy=bool(subscription),
'org_id': org_id,
'billing_day': int(billing_day),
}
if billing_day
else None
),
created_by=created_by, created_by=created_by,
scheduled_at=datetime.fromisoformat(old_image['created_at']), scheduled_at=datetime.fromisoformat(old_image['created_at']),
# Transfer the deduplication window if it exists # Transfer the deduplication window if it exists

View File

@@ -43,9 +43,11 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
now_ = now() now_ = now()
org_id = new_image['org_id'] org_id = new_image['org_id']
enrollment = enrollment_layer.collection.get_items( enrollment = enrollment_layer.collection.get_items(
TransactKey(new_image['id']) + SortKey('0') + SortKey('author') TransactKey(new_image['id'])
# Post-migration: uncomment the following line + SortKey('0')
# + SortKey('CREATED_BY') + 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: if not enrollment:
@@ -99,7 +101,7 @@ def lambda_handler(event: EventBridgeEvent, context: LambdaContext) -> bool:
# Add enrollment entry to billing # Add enrollment entry to billing
try: try:
created_by = enrollment.get('author') created_by = enrollment.get('created_by')
course_id = enrollment['course']['id'] course_id = enrollment['course']['id']
course = course_layer.collection.get_items( course = course_layer.collection.get_items(
KeyPair( KeyPair(