add cep and cnpj to api

This commit is contained in:
2025-12-28 18:48:55 -03:00
parent 8b81d5c245
commit f7d1854309
7 changed files with 185 additions and 151 deletions

View File

@@ -32,6 +32,7 @@ import {
import { useWizard } from '@/components/wizard' import { useWizard } from '@/components/wizard'
import { isName } from '../_.$orgid.users.add/data' import { isName } from '../_.$orgid.users.add/data'
import type { PaymentMethod } from '@repo/ui/routes/orders/data'
const creditCard = z.object({ const creditCard = z.object({
holder_name: z holder_name: z
@@ -60,6 +61,9 @@ const formSchema = z.discriminatedUnion(
z.object({ z.object({
payment_method: z.literal('BANK_SLIP') payment_method: z.literal('BANK_SLIP')
}), }),
z.object({
payment_method: z.literal('MANUAL')
}),
z.object({ z.object({
payment_method: z.literal('CREDIT_CARD'), payment_method: z.literal('CREDIT_CARD'),
credit_card: creditCard credit_card: creditCard
@@ -74,7 +78,7 @@ export type CreditCard = z.infer<typeof creditCard>
type PaymentProps = { type PaymentProps = {
onSubmit: (value: any) => void | Promise<void> onSubmit: (value: any) => void | Promise<void>
payment_method?: 'PIX' | 'BANK_SLIP' | 'CREDIT_CARD' payment_method?: PaymentMethod
credit_card?: CreditCard credit_card?: CreditCard
} }

View File

@@ -1,4 +1,4 @@
import { useToggle } from 'ahooks' import { useRequest, useToggle } from 'ahooks'
import { PatternFormat } from 'react-number-format' import { PatternFormat } from 'react-number-format'
import { ExternalLinkIcon, PencilIcon, SearchIcon } from 'lucide-react' import { ExternalLinkIcon, PencilIcon, SearchIcon } from 'lucide-react'
import { Controller, useForm } from 'react-hook-form' import { Controller, useForm } from 'react-hook-form'
@@ -229,19 +229,21 @@ const formSchema = z.object({
state: z.string().min(1, 'Digite o estado') state: z.string().min(1, 'Digite o estado')
}) })
type Schema = z.infer<typeof formSchema> type Address = z.infer<typeof formSchema>
export function AddressDialog() { export function AddressDialog() {
const { handleSubmit, control } = useForm({ const [open, { toggle, set }] = useToggle()
const { handleSubmit, control, reset } = useForm({
resolver: zodResolver(formSchema) resolver: zodResolver(formSchema)
}) })
const onSubmit = async (data: Schema) => { const onSubmit = async (data: Address) => {
set(false)
console.log(data) console.log(data)
} }
return ( return (
<Dialog> <Dialog open={open} onOpenChange={toggle}>
<DialogTrigger asChild> <DialogTrigger asChild>
<Button <Button
variant="ghost" variant="ghost"
@@ -254,70 +256,18 @@ export function AddressDialog() {
</Button> </Button>
</DialogTrigger> </DialogTrigger>
<DialogContent className="sm:max-w-[425px]"> <DialogContent className="sm:max-w-[425px]">
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4"> <DialogHeader>
<DialogHeader> <DialogTitle>Editar endereço</DialogTitle>
<DialogTitle>Editar endereço</DialogTitle> <DialogDescription>
<DialogDescription> Este endereço será utilizado para a emissão da NFS-e.
Este endereço será utilizado para a emissão da NFS-e. </DialogDescription>
</DialogDescription> </DialogHeader>
</DialogHeader>
<PostcodeForm onChange={reset} />
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
<FieldGroup> <FieldGroup>
<FieldSet> <FieldSet>
<Controller
control={control}
name="postcode"
defaultValue=""
render={({
field: { onChange, ref, ...field },
fieldState
}) => (
<Field data-invalid={fieldState.invalid}>
<FieldLabel htmlFor={field.name}>CEP</FieldLabel>
<InputGroup>
{/*<InputGroupInput
id={field.name}
aria-invalid={fieldState.invalid}
{...field}
/>*/}
<PatternFormat
id={field.name}
format="#####-###"
mask="_"
placeholder="_____-___"
customInput={InputGroupInput}
getInputRef={ref}
aria-invalid={fieldState.invalid}
onValueChange={({ value }) => {
onChange(value)
}}
{...field}
/>
<InputGroupAddon align="inline-end">
<InputGroupButton type="button" variant="secondary">
<SearchIcon />
</InputGroupButton>
</InputGroupAddon>
</InputGroup>
<FieldDescription>
<a
href="https://buscacepinter.correios.com.br/app/endereco/index.php"
target="_blank"
rel="noopener noreferrer"
className="inline-flex gap-1 items-center "
>
Não sei o CEP <ExternalLinkIcon className="size-4" />
</a>
</FieldDescription>
{fieldState.invalid && (
<FieldError errors={[fieldState.error]} />
)}
</Field>
)}
/>
<Controller <Controller
control={control} control={control}
name="address1" name="address1"
@@ -431,6 +381,7 @@ export function AddressDialog() {
<Button <Button
variant="link" variant="link"
type="button" type="button"
tabIndex={-1}
className="text-black dark:text-white" className="text-black dark:text-white"
> >
Cancel Cancel
@@ -444,3 +395,93 @@ export function AddressDialog() {
</Dialog> </Dialog>
) )
} }
type PostcodeFormProps = {
onChange: (value: Address) => void
}
function PostcodeForm({ onChange }: PostcodeFormProps) {
const formSchema = z.object({
postcode: z.string().min(1, 'Digite um CEP')
})
type Schema = z.infer<typeof formSchema>
const { control, handleSubmit, setError } = useForm({
resolver: zodResolver(formSchema)
})
const { runAsync, loading } = useRequest(
async (cep: string) => {
return await fetch(`/~/api/cep/${cep}.json`)
},
{ manual: true }
)
const onSubmit = async ({ postcode }: Schema) => {
const r = await runAsync(postcode)
if (r.ok) {
onChange?.((await r.json()) as Address)
return
}
setError('postcode', { message: 'CEP não encontrado' })
}
return (
<form onSubmit={handleSubmit(onSubmit)}>
<FieldGroup>
<Controller
control={control}
name="postcode"
defaultValue=""
render={({ field: { onChange, ref, ...field }, fieldState }) => (
<Field data-invalid={fieldState.invalid}>
{/* CEP */}
<FieldLabel htmlFor={field.name}>CEP</FieldLabel>
<InputGroup>
<PatternFormat
id={field.name}
format="#####-###"
mask="_"
placeholder="_____-___"
customInput={InputGroupInput}
getInputRef={ref}
aria-invalid={fieldState.invalid}
onValueChange={({ value }) => {
onChange(value)
}}
{...field}
/>
<InputGroupAddon align="inline-end">
<InputGroupButton
type="submit"
variant="secondary"
className="cursor-pointer"
>
{loading ? <Spinner /> : <SearchIcon />}
</InputGroupButton>
</InputGroupAddon>
</InputGroup>
{fieldState.invalid && <FieldError errors={[fieldState.error]} />}
<FieldDescription>
<a
href="https://buscacepinter.correios.com.br/app/endereco/index.php"
target="_blank"
rel="noopener noreferrer"
className="inline-flex gap-1 items-center "
tabIndex={-1}
>
Não sei o CEP <ExternalLinkIcon className="size-4" />
</a>
</FieldDescription>
</Field>
)}
/>
</FieldGroup>
</form>
)
}

View File

@@ -1,65 +0,0 @@
import type { Route } from './+types/route'
import { data } from 'react-router'
export async function loader({ params, context, request }: Route.LoaderArgs) {
const url = new URL(request.url)
const cnpj = url.searchParams.get('cnpj')
const r = await fetch(`https://brasilapi.com.br/api/cnpj/v1/${cnpj}`, {
method: 'GET',
headers: {
'User-Agent':
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36',
Accept: 'application/json'
}
})
if (!r.ok) {
throw new Response(await r.text(), { status: r.status })
}
return data({})
}
// export const prerender = false
// import type { APIRoute } from 'astro'
// import lodash from 'lodash'
// export const GET: APIRoute = async ({ params }) => {
// // await new Promise((r) => setTimeout(r, 2000))
// const cnpj = params.cnpj
// const res = await fetch(`https://brasilapi.com.br/api/cnpj/v1/${cnpj}`, {
// method: 'GET',
// headers: {
// 'User-Agent':
// 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36',
// Accept: 'application/json'
// }
// })
// if (!res.ok) {
// return new Response(null, {
// status: 404
// })
// }
// const json = await res.json()
// const addr = lodash.startCase(
// lodash.toLower(`${json.descricao_tipo_de_logradouro} ${json.logradouro}`)
// )
// return new Response(
// JSON.stringify({
// name: json.razao_social,
// address: {
// postcode: json.cep,
// address1: `${addr}, ${json.numero}`,
// neighborhood: lodash.capitalize(json.bairro),
// city: lodash.capitalize(json.municipio),
// state: json.uf
// }
// })
// )
// }

View File

@@ -0,0 +1,23 @@
import { data } from 'react-router'
import type { Route } from './+types/route'
export async function loader({ params }: Route.LoaderArgs) {
const r = await fetch(`https://opencep.com/v1/${params.cep}`, {
method: 'GET'
})
if (!r.ok) {
throw new Response(await r.text(), { status: r.status })
}
const json = (await r.json()) as any
return data({
postcode: json.cep.replace(/\D/g, ''),
address1: json.logradouro,
address2: json.complemento,
neighborhood: json.bairro,
city: json.localidade,
state: json.uf
})
}

View File

@@ -0,0 +1,30 @@
import type { Route } from './+types/route'
import { data } from 'react-router'
import lodash from 'lodash'
export async function loader({ params }: Route.LoaderArgs) {
const r = await fetch(`https://minhareceita.org/${params.cnpj}`, {
method: 'GET'
})
if (!r.ok) {
throw new Response(await r.text(), { status: r.status })
}
const json = (await r.json()) as any
const addr = lodash.startCase(
lodash.toLower(`${json.descricao_tipo_de_logradouro} ${json.logradouro}`)
)
return data({
name: json.razao_social,
address: {
postcode: json.cep,
address1: `${addr}, ${json.numero}`,
neighborhood: lodash.capitalize(json.bairro),
city: lodash.capitalize(json.municipio),
state: json.uf
}
})
}

View File

@@ -1,4 +1,3 @@
import json
from datetime import datetime, timedelta from datetime import datetime, timedelta
from typing import NotRequired, TypedDict from typing import NotRequired, TypedDict
@@ -135,24 +134,22 @@ def _generate_cert(
# Send template URI and data to Paperforge API to generate a PDF # Send template URI and data to Paperforge API to generate a PDF
r = requests.post( r = requests.post(
PAPERFORGE_API, PAPERFORGE_API,
data=json.dumps( json={
{ 'template_uri': cert['s3_uri'],
'template_uri': cert['s3_uri'], 'args': {
'args': { 'name': user['name'],
'name': user['name'], 'cpf': _cpffmt(user['cpf']),
'cpf': _cpffmt(user['cpf']), 'score': score,
'score': score, 'started_at': started_at.strftime('%d/%m/%Y'),
'started_at': started_at.strftime('%d/%m/%Y'), 'completed_at': completed_at.strftime('%d/%m/%Y'),
'completed_at': completed_at.strftime('%d/%m/%Y'), 'today': _datefmt(now_),
'today': _datefmt(now_), 'year': now_.strftime('%Y'),
'year': now_.strftime('%Y'), 'expires_at': expires_at.strftime('%d/%m/%Y')
'expires_at': expires_at.strftime('%d/%m/%Y') if expires_at
if expires_at else None,
else None,
},
}, },
), },
timeout=6, timeout=(1, 3),
) )
r.raise_for_status() r.raise_for_status()

View File

@@ -369,7 +369,7 @@ Resources:
Properties: Properties:
Handler: events.issue_cert.lambda_handler Handler: events.issue_cert.lambda_handler
Tracing: Active Tracing: Active
Timeout: 12 # Timeout: 12
LoggingConfig: LoggingConfig:
LogGroup: !Ref EventLog LogGroup: !Ref EventLog
Policies: Policies:
@@ -443,6 +443,10 @@ Resources:
sk: ['0'] sk: ['0']
new_image: new_image:
status: [COMPLETED] status: [COMPLETED]
# Post-migration: uncomment the following line
# cert:
# expires_at:
# - exists: true
cert_expires_at: cert_expires_at:
- exists: true - exists: true
org_id: org_id: