ADM-008: Puntos de Venta (CRUD fundacional) #19

Merged
dmolinari merged 18 commits from feature/ADM-008 into main 2026-04-17 17:31:21 +00:00
5 changed files with 268 additions and 0 deletions
Showing only changes of commit d61292afa4 - Show all commits

View File

@@ -0,0 +1,62 @@
import { axiosClient } from '@/api/axiosClient'
import type {
CreatePuntoDeVentaRequest,
PuntoDeVentaCreated,
PuntoDeVentaDetail,
PuntoDeVentaListItem,
PuntosDeVentaQuery,
UpdatePuntoDeVentaRequest,
PagedResult,
} from '../types'
export async function listPuntosDeVenta(
query: PuntosDeVentaQuery,
): Promise<PagedResult<PuntoDeVentaListItem>> {
const params = new URLSearchParams()
if (query.page !== undefined) params.set('page', String(query.page))
if (query.pageSize !== undefined) params.set('pageSize', String(query.pageSize))
if (query.medioId !== undefined) params.set('medioId', String(query.medioId))
if (query.activo !== undefined) params.set('activo', String(query.activo))
const response = await axiosClient.get<PagedResult<PuntoDeVentaListItem>>(
'/api/v1/admin/puntos-de-venta',
{ params },
)
return response.data
}
export async function getPuntoDeVenta(id: number): Promise<PuntoDeVentaDetail> {
const response = await axiosClient.get<PuntoDeVentaDetail>(
`/api/v1/admin/puntos-de-venta/${id}`,
)
return response.data
}
export async function createPuntoDeVenta(
payload: CreatePuntoDeVentaRequest,
): Promise<PuntoDeVentaCreated> {
const response = await axiosClient.post<PuntoDeVentaCreated>(
'/api/v1/admin/puntos-de-venta',
payload,
)
return response.data
}
export async function updatePuntoDeVenta(
id: number,
payload: UpdatePuntoDeVentaRequest,
): Promise<PuntoDeVentaDetail> {
const response = await axiosClient.put<PuntoDeVentaDetail>(
`/api/v1/admin/puntos-de-venta/${id}`,
payload,
)
return response.data
}
export async function deactivatePuntoDeVenta(id: number): Promise<void> {
await axiosClient.post(`/api/v1/admin/puntos-de-venta/${id}/deactivate`)
}
export async function reactivatePuntoDeVenta(id: number): Promise<void> {
await axiosClient.post(`/api/v1/admin/puntos-de-venta/${id}/reactivate`)
}

View File

@@ -0,0 +1,22 @@
import { axiosClient } from '@/api/axiosClient'
import type { TipoComprobante, ReservarNumeroResponse, ProximoNumeroResponse } from '../types'
export async function reservarNumero(
puntoDeVentaId: number,
tipoComprobante: TipoComprobante,
): Promise<ReservarNumeroResponse> {
const response = await axiosClient.post<ReservarNumeroResponse>(
`/api/v1/admin/puntos-de-venta/${puntoDeVentaId}/secuencias/${tipoComprobante}/reservar`,
)
return response.data
}
export async function getProximoNumero(
puntoDeVentaId: number,
tipoComprobante: TipoComprobante,
): Promise<ProximoNumeroResponse> {
const response = await axiosClient.get<ProximoNumeroResponse>(
`/api/v1/admin/puntos-de-venta/${puntoDeVentaId}/secuencias/${tipoComprobante}/proximo`,
)
return response.data
}

View File

@@ -0,0 +1,83 @@
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
import {
listPuntosDeVenta,
getPuntoDeVenta,
createPuntoDeVenta,
updatePuntoDeVenta,
deactivatePuntoDeVenta,
reactivatePuntoDeVenta,
} from '../api/puntos-de-venta.api'
import type { CreatePuntoDeVentaRequest, PuntosDeVentaQuery, UpdatePuntoDeVentaRequest } from '../types'
export const puntosDeVentaListQueryKey = (query: PuntosDeVentaQuery) =>
['puntos-de-venta', 'list', query] as const
export const puntoDeVentaDetailQueryKey = (id: number) =>
['puntos-de-venta', 'detail', id] as const
// ─── List ────────────────────────────────────────────────────────────────────
export function usePuntosDeVentaList(query: PuntosDeVentaQuery) {
return useQuery({
queryKey: puntosDeVentaListQueryKey(query),
queryFn: () => listPuntosDeVenta(query),
staleTime: 15_000,
})
}
// ─── Detail ──────────────────────────────────────────────────────────────────
export function usePuntoDeVenta(id: number) {
return useQuery({
queryKey: puntoDeVentaDetailQueryKey(id),
queryFn: () => getPuntoDeVenta(id),
enabled: !!id,
staleTime: 15_000,
})
}
// ─── Create ──────────────────────────────────────────────────────────────────
export function useCreatePuntoDeVenta() {
const queryClient = useQueryClient()
return useMutation({
mutationFn: (payload: CreatePuntoDeVentaRequest) => createPuntoDeVenta(payload),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['puntos-de-venta'] })
},
})
}
// ─── Update ──────────────────────────────────────────────────────────────────
export function useUpdatePuntoDeVenta(id: number) {
const queryClient = useQueryClient()
return useMutation({
mutationFn: (payload: UpdatePuntoDeVentaRequest) => updatePuntoDeVenta(id, payload),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['puntos-de-venta'] })
},
})
}
// ─── Deactivate / Reactivate ─────────────────────────────────────────────────
export function useDeactivatePuntoDeVenta() {
const queryClient = useQueryClient()
return useMutation({
mutationFn: (id: number) => deactivatePuntoDeVenta(id),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['puntos-de-venta'] })
},
})
}
export function useReactivatePuntoDeVenta() {
const queryClient = useQueryClient()
return useMutation({
mutationFn: (id: number) => reactivatePuntoDeVenta(id),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['puntos-de-venta'] })
},
})
}

View File

@@ -0,0 +1,30 @@
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import { reservarNumero, getProximoNumero } from '../api/secuencias.api'
import type { TipoComprobante } from '../types'
// ─── Reservar ────────────────────────────────────────────────────────────────
export function useReservarNumero(puntoDeVentaId: number) {
const queryClient = useQueryClient()
return useMutation({
mutationFn: (tipoComprobante: TipoComprobante) =>
reservarNumero(puntoDeVentaId, tipoComprobante),
onSuccess: (_data, tipoComprobante) => {
// Invalidate the proximo query for this pdv+tipo so it refetches
queryClient.invalidateQueries({
queryKey: ['puntos-de-venta', 'proximo', puntoDeVentaId, tipoComprobante],
})
},
})
}
// ─── Próximo número (read-only) ──────────────────────────────────────────────
export function useProximoNumero(puntoDeVentaId: number, tipoComprobante: TipoComprobante) {
return useQuery({
queryKey: ['puntos-de-venta', 'proximo', puntoDeVentaId, tipoComprobante],
queryFn: () => getProximoNumero(puntoDeVentaId, tipoComprobante),
enabled: !!puntoDeVentaId,
staleTime: 5_000,
})
}

View File

@@ -0,0 +1,71 @@
// ADM-008 — shared types for puntos-de-venta feature
export enum TipoComprobante {
FacturaA = 1,
FacturaB = 2,
FacturaC = 3,
NotaCreditoA = 4,
NotaCreditoB = 5,
NotaCreditoC = 6,
}
export interface PuntoDeVentaListItem {
id: number
medioId: number
numeroAFIP: number
nombre: string
activo: boolean
}
export interface PuntoDeVentaDetail {
id: number
medioId: number
numeroAFIP: number
nombre: string
activo: boolean
fechaCreacion: string
fechaModificacion: string | null
}
export interface PuntoDeVentaCreated {
id: number
medioId: number
numeroAFIP: number
nombre: string
activo: boolean
}
export interface CreatePuntoDeVentaRequest {
medioId: number
numeroAFIP: number
nombre: string
}
export interface UpdatePuntoDeVentaRequest {
nombre: string
numeroAFIP: number
}
export interface PuntosDeVentaQuery {
page?: number
pageSize?: number
medioId?: number
activo?: boolean
}
export interface ReservarNumeroResponse {
tipoComprobante: TipoComprobante
numeroReservado: number
}
export interface ProximoNumeroResponse {
tipoComprobante: TipoComprobante
proximoNumero: number
}
export interface PagedResult<T> {
items: T[]
page: number
pageSize: number
total: number
}