update table
This commit is contained in:
@@ -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,
|
||||||
|
|||||||
@@ -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_,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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 há 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()
|
||||||
|
|||||||
@@ -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_,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
Reference in New Issue
Block a user