feat: Sistema de gestión de impresoras persistente
- Crear modelo Printer con campos: id, name, host, deviceId, timeout, isDefault - Almacenamiento persistente en data/printers.json - APIs CRUD: GET/POST /api/printers, GET/PUT/DELETE /api/printers/:id - API para seleccionar impresora activa: POST /api/printers/select - Endpoint de impresión ahora usa la impresora seleccionada o la especificada por printerId - Composable usePrinters() para el cliente - UI: Selector de impresora en sidebar, tab Impresoras en mobile - Componentes: PrintersList, PrintersCard, PrintersForm, PrintersSelector
This commit is contained in:
@@ -1,18 +1,20 @@
|
||||
// Endpoint genérico de impresión que acepta una lista de operaciones
|
||||
import { buildFromOperations, type Operation } from '../../utils/eposBuilder'
|
||||
import { buildSoapEnvelope, sendToPrinter, parsePrinterResponse } from '../../utils/printer'
|
||||
import { getSelectedPrinter, getPrinterById } from '../../utils/printers'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
try {
|
||||
const config = useRuntimeConfig()
|
||||
const body = await readBody(event)
|
||||
|
||||
const {
|
||||
operations = [],
|
||||
dryRun = false
|
||||
dryRun = false,
|
||||
printerId
|
||||
} = body as {
|
||||
operations?: Operation[]
|
||||
dryRun?: boolean
|
||||
printerId?: string
|
||||
}
|
||||
|
||||
// Construir el XML interior con las operaciones
|
||||
@@ -28,12 +30,24 @@ export default defineEventHandler(async (event) => {
|
||||
}
|
||||
}
|
||||
|
||||
// Obtener la impresora a usar
|
||||
let printer = printerId
|
||||
? await getPrinterById(printerId)
|
||||
: await getSelectedPrinter()
|
||||
|
||||
if (!printer) {
|
||||
return {
|
||||
ok: false,
|
||||
error: 'No hay impresora configurada. Por favor, agrega una impresora primero.'
|
||||
}
|
||||
}
|
||||
|
||||
// Enviar a la impresora
|
||||
const result = await sendToPrinter(
|
||||
soap,
|
||||
config.printerHost,
|
||||
config.printerDeviceId,
|
||||
parseInt(config.printerTimeoutMs)
|
||||
printer.host,
|
||||
printer.deviceId,
|
||||
printer.timeout
|
||||
)
|
||||
|
||||
// Parsear la respuesta
|
||||
@@ -43,7 +57,11 @@ export default defineEventHandler(async (event) => {
|
||||
ok: success,
|
||||
httpStatus: result.status,
|
||||
code,
|
||||
raw: result.data
|
||||
raw: result.data,
|
||||
printerUsed: {
|
||||
id: printer.id,
|
||||
name: printer.name
|
||||
}
|
||||
}
|
||||
} catch (err: any) {
|
||||
return {
|
||||
|
||||
34
server/api/printers/[id].delete.ts
Normal file
34
server/api/printers/[id].delete.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
// DELETE /api/printers/:id - Eliminar una impresora
|
||||
import { deletePrinter } from '../../utils/printers'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
try {
|
||||
const id = getRouterParam(event, 'id')
|
||||
|
||||
if (!id) {
|
||||
return {
|
||||
ok: false,
|
||||
error: 'ID requerido'
|
||||
}
|
||||
}
|
||||
|
||||
const deleted = await deletePrinter(id)
|
||||
|
||||
if (!deleted) {
|
||||
return {
|
||||
ok: false,
|
||||
error: 'Impresora no encontrada'
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
ok: true,
|
||||
message: 'Impresora eliminada'
|
||||
}
|
||||
} catch (err: any) {
|
||||
return {
|
||||
ok: false,
|
||||
error: err.message
|
||||
}
|
||||
}
|
||||
})
|
||||
34
server/api/printers/[id].get.ts
Normal file
34
server/api/printers/[id].get.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
// GET /api/printers/:id - Obtener una impresora por ID
|
||||
import { getPrinterById } from '../../utils/printers'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
try {
|
||||
const id = getRouterParam(event, 'id')
|
||||
|
||||
if (!id) {
|
||||
return {
|
||||
ok: false,
|
||||
error: 'ID requerido'
|
||||
}
|
||||
}
|
||||
|
||||
const printer = await getPrinterById(id)
|
||||
|
||||
if (!printer) {
|
||||
return {
|
||||
ok: false,
|
||||
error: 'Impresora no encontrada'
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
ok: true,
|
||||
printer
|
||||
}
|
||||
} catch (err: any) {
|
||||
return {
|
||||
ok: false,
|
||||
error: err.message
|
||||
}
|
||||
}
|
||||
})
|
||||
50
server/api/printers/[id].put.ts
Normal file
50
server/api/printers/[id].put.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
// PUT /api/printers/:id - Actualizar una impresora
|
||||
import { updatePrinter } from '../../utils/printers'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
try {
|
||||
const id = getRouterParam(event, 'id')
|
||||
|
||||
if (!id) {
|
||||
return {
|
||||
ok: false,
|
||||
error: 'ID requerido'
|
||||
}
|
||||
}
|
||||
|
||||
const body = await readBody(event)
|
||||
|
||||
const { name, host, deviceId, timeout, isDefault } = body as {
|
||||
name?: string
|
||||
host?: string
|
||||
deviceId?: string
|
||||
timeout?: number
|
||||
isDefault?: boolean
|
||||
}
|
||||
|
||||
const printer = await updatePrinter(id, {
|
||||
name,
|
||||
host,
|
||||
deviceId,
|
||||
timeout,
|
||||
isDefault
|
||||
})
|
||||
|
||||
if (!printer) {
|
||||
return {
|
||||
ok: false,
|
||||
error: 'Impresora no encontrada'
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
ok: true,
|
||||
printer
|
||||
}
|
||||
} catch (err: any) {
|
||||
return {
|
||||
ok: false,
|
||||
error: err.message
|
||||
}
|
||||
}
|
||||
})
|
||||
20
server/api/printers/index.get.ts
Normal file
20
server/api/printers/index.get.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
// GET /api/printers - Listar todas las impresoras
|
||||
import { getAllPrinters, getSelectedPrinter } from '../../utils/printers'
|
||||
|
||||
export default defineEventHandler(async () => {
|
||||
try {
|
||||
const printers = await getAllPrinters()
|
||||
const selected = await getSelectedPrinter()
|
||||
|
||||
return {
|
||||
ok: true,
|
||||
printers,
|
||||
selectedPrinterId: selected?.id || null
|
||||
}
|
||||
} catch (err: any) {
|
||||
return {
|
||||
ok: false,
|
||||
error: err.message
|
||||
}
|
||||
}
|
||||
})
|
||||
41
server/api/printers/index.post.ts
Normal file
41
server/api/printers/index.post.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
// POST /api/printers - Crear nueva impresora
|
||||
import { createPrinter } from '../../utils/printers'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
try {
|
||||
const body = await readBody(event)
|
||||
|
||||
const { name, host, deviceId, timeout, isDefault } = body as {
|
||||
name: string
|
||||
host: string
|
||||
deviceId: string
|
||||
timeout?: number
|
||||
isDefault?: boolean
|
||||
}
|
||||
|
||||
if (!name || !host || !deviceId) {
|
||||
return {
|
||||
ok: false,
|
||||
error: 'name, host y deviceId son requeridos'
|
||||
}
|
||||
}
|
||||
|
||||
const printer = await createPrinter({
|
||||
name,
|
||||
host,
|
||||
deviceId,
|
||||
timeout,
|
||||
isDefault
|
||||
})
|
||||
|
||||
return {
|
||||
ok: true,
|
||||
printer
|
||||
}
|
||||
} catch (err: any) {
|
||||
return {
|
||||
ok: false,
|
||||
error: err.message
|
||||
}
|
||||
}
|
||||
})
|
||||
31
server/api/printers/select.post.ts
Normal file
31
server/api/printers/select.post.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
// POST /api/printers/select - Seleccionar la impresora activa
|
||||
import { setSelectedPrinter, getPrinterById } from '../../utils/printers'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
try {
|
||||
const body = await readBody(event)
|
||||
const { printerId } = body as { printerId: string | null }
|
||||
|
||||
if (printerId) {
|
||||
const printer = await getPrinterById(printerId)
|
||||
if (!printer) {
|
||||
return {
|
||||
ok: false,
|
||||
error: 'Impresora no encontrada'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await setSelectedPrinter(printerId)
|
||||
|
||||
return {
|
||||
ok: true,
|
||||
selectedPrinterId: printerId
|
||||
}
|
||||
} catch (err: any) {
|
||||
return {
|
||||
ok: false,
|
||||
error: err.message
|
||||
}
|
||||
}
|
||||
})
|
||||
178
server/utils/printers.ts
Normal file
178
server/utils/printers.ts
Normal file
@@ -0,0 +1,178 @@
|
||||
// Gestión de impresoras persistentes
|
||||
import { readFile, writeFile, mkdir } from 'fs/promises'
|
||||
import { existsSync } from 'fs'
|
||||
import { join } from 'path'
|
||||
|
||||
export interface Printer {
|
||||
id: string
|
||||
name: string
|
||||
host: string
|
||||
deviceId: string
|
||||
timeout: number
|
||||
isDefault: boolean
|
||||
createdAt: string
|
||||
updatedAt: string
|
||||
}
|
||||
|
||||
export interface PrintersStore {
|
||||
printers: Printer[]
|
||||
selectedPrinterId: string | null
|
||||
}
|
||||
|
||||
// Directorio de datos persistentes
|
||||
const DATA_DIR = join(process.cwd(), 'data')
|
||||
const PRINTERS_FILE = join(DATA_DIR, 'printers.json')
|
||||
|
||||
// Asegurar que el directorio existe
|
||||
async function ensureDataDir(): Promise<void> {
|
||||
if (!existsSync(DATA_DIR)) {
|
||||
await mkdir(DATA_DIR, { recursive: true })
|
||||
}
|
||||
}
|
||||
|
||||
// Leer store de impresoras
|
||||
export async function readPrintersStore(): Promise<PrintersStore> {
|
||||
await ensureDataDir()
|
||||
|
||||
try {
|
||||
const data = await readFile(PRINTERS_FILE, 'utf-8')
|
||||
return JSON.parse(data)
|
||||
} catch {
|
||||
// Si no existe el archivo, retornar store vacío
|
||||
return {
|
||||
printers: [],
|
||||
selectedPrinterId: null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Guardar store de impresoras
|
||||
export async function writePrintersStore(store: PrintersStore): Promise<void> {
|
||||
await ensureDataDir()
|
||||
await writeFile(PRINTERS_FILE, JSON.stringify(store, null, 2), 'utf-8')
|
||||
}
|
||||
|
||||
// Generar ID único
|
||||
function generateId(): string {
|
||||
return `printer_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`
|
||||
}
|
||||
|
||||
// CRUD Operations
|
||||
|
||||
export async function getAllPrinters(): Promise<Printer[]> {
|
||||
const store = await readPrintersStore()
|
||||
return store.printers
|
||||
}
|
||||
|
||||
export async function getPrinterById(id: string): Promise<Printer | null> {
|
||||
const store = await readPrintersStore()
|
||||
return store.printers.find(p => p.id === id) || null
|
||||
}
|
||||
|
||||
export async function getDefaultPrinter(): Promise<Printer | null> {
|
||||
const store = await readPrintersStore()
|
||||
return store.printers.find(p => p.isDefault) || store.printers[0] || null
|
||||
}
|
||||
|
||||
export async function getSelectedPrinter(): Promise<Printer | null> {
|
||||
const store = await readPrintersStore()
|
||||
if (store.selectedPrinterId) {
|
||||
const printer = store.printers.find(p => p.id === store.selectedPrinterId)
|
||||
if (printer) return printer
|
||||
}
|
||||
// Fallback a la impresora por defecto
|
||||
return getDefaultPrinter()
|
||||
}
|
||||
|
||||
export async function setSelectedPrinter(printerId: string | null): Promise<void> {
|
||||
const store = await readPrintersStore()
|
||||
store.selectedPrinterId = printerId
|
||||
await writePrintersStore(store)
|
||||
}
|
||||
|
||||
export async function createPrinter(data: {
|
||||
name: string
|
||||
host: string
|
||||
deviceId: string
|
||||
timeout?: number
|
||||
isDefault?: boolean
|
||||
}): Promise<Printer> {
|
||||
const store = await readPrintersStore()
|
||||
|
||||
const now = new Date().toISOString()
|
||||
const printer: Printer = {
|
||||
id: generateId(),
|
||||
name: data.name,
|
||||
host: data.host,
|
||||
deviceId: data.deviceId,
|
||||
timeout: data.timeout || 60000,
|
||||
isDefault: data.isDefault || store.printers.length === 0, // Primera impresora es default
|
||||
createdAt: now,
|
||||
updatedAt: now
|
||||
}
|
||||
|
||||
// Si esta es default, quitar default de las demás
|
||||
if (printer.isDefault) {
|
||||
store.printers.forEach(p => p.isDefault = false)
|
||||
}
|
||||
|
||||
store.printers.push(printer)
|
||||
|
||||
// Si es la primera impresora, seleccionarla
|
||||
if (store.printers.length === 1) {
|
||||
store.selectedPrinterId = printer.id
|
||||
}
|
||||
|
||||
await writePrintersStore(store)
|
||||
return printer
|
||||
}
|
||||
|
||||
export async function updatePrinter(id: string, data: Partial<{
|
||||
name: string
|
||||
host: string
|
||||
deviceId: string
|
||||
timeout: number
|
||||
isDefault: boolean
|
||||
}>): Promise<Printer | null> {
|
||||
const store = await readPrintersStore()
|
||||
const index = store.printers.findIndex(p => p.id === id)
|
||||
|
||||
if (index === -1) return null
|
||||
|
||||
// Si se está estableciendo como default, quitar default de las demás
|
||||
if (data.isDefault) {
|
||||
store.printers.forEach(p => p.isDefault = false)
|
||||
}
|
||||
|
||||
store.printers[index] = {
|
||||
...store.printers[index],
|
||||
...data,
|
||||
updatedAt: new Date().toISOString()
|
||||
}
|
||||
|
||||
await writePrintersStore(store)
|
||||
return store.printers[index]
|
||||
}
|
||||
|
||||
export async function deletePrinter(id: string): Promise<boolean> {
|
||||
const store = await readPrintersStore()
|
||||
const index = store.printers.findIndex(p => p.id === id)
|
||||
|
||||
if (index === -1) return false
|
||||
|
||||
const wasDefault = store.printers[index].isDefault
|
||||
store.printers.splice(index, 1)
|
||||
|
||||
// Si era la impresora seleccionada, limpiar selección
|
||||
if (store.selectedPrinterId === id) {
|
||||
store.selectedPrinterId = store.printers[0]?.id || null
|
||||
}
|
||||
|
||||
// Si era default y hay otras impresoras, hacer la primera default
|
||||
if (wasDefault && store.printers.length > 0) {
|
||||
store.printers[0].isDefault = true
|
||||
}
|
||||
|
||||
await writePrintersStore(store)
|
||||
return true
|
||||
}
|
||||
Reference in New Issue
Block a user