feat(F-048): completed feature
This commit is contained in:
@@ -1,8 +1,10 @@
|
||||
import type { FastifyInstance } from 'fastify';
|
||||
import type { FastifySchema } from 'fastify';
|
||||
import type pg from 'pg';
|
||||
import { z } from 'zod';
|
||||
import { requireRole, type Authenticate } from '../../../shared/auth.js';
|
||||
import { AppError } from '../../../shared/errors.js';
|
||||
import { errorSchema } from '../../../shared/swagger.js';
|
||||
import { parseJson } from '../../../shared/http-input.js';
|
||||
import { PricingService } from '../application/pricing-service.js';
|
||||
import { InvalidPriceError, PriceNotFoundError } from '../domain/errors.js';
|
||||
@@ -35,7 +37,17 @@ export async function registerPricingRoutes(
|
||||
): Promise<void> {
|
||||
const pricing = new PricingService(new PgPricingRepository(deps.pool));
|
||||
|
||||
app.get('/pricing/variants/:variantId', async (request, reply) => {
|
||||
const getPriceSchema: FastifySchema = {
|
||||
tags: ['Pricing'],
|
||||
summary: 'Get variant price (público)',
|
||||
params: {
|
||||
type: 'object',
|
||||
required: ['variantId'],
|
||||
properties: { variantId: { type: 'string', format: 'uuid' } },
|
||||
},
|
||||
response: { 404: errorSchema },
|
||||
};
|
||||
app.get('/pricing/variants/:variantId', { schema: getPriceSchema }, async (request, reply) => {
|
||||
const { variantId } = parseJson(variantParamSchema, request.params);
|
||||
const price = await pricing.getVariantPrice(variantId);
|
||||
if (!price) {
|
||||
@@ -44,7 +56,18 @@ export async function registerPricingRoutes(
|
||||
return reply.send(serializeVariantPrice(price));
|
||||
});
|
||||
|
||||
app.put('/pricing/variants/:variantId', async (request, reply) => {
|
||||
const setPriceSchema: FastifySchema = {
|
||||
tags: ['Pricing'],
|
||||
summary: 'Set variant price (admin)',
|
||||
params: {
|
||||
type: 'object',
|
||||
required: ['variantId'],
|
||||
properties: { variantId: { type: 'string', format: 'uuid' } },
|
||||
},
|
||||
body: { type: 'object' },
|
||||
response: { 401: errorSchema, 403: errorSchema },
|
||||
};
|
||||
app.put('/pricing/variants/:variantId', { schema: setPriceSchema }, async (request, reply) => {
|
||||
const user = await deps.authenticate(request);
|
||||
requireRole(user, 'admin');
|
||||
const { variantId } = parseJson(variantParamSchema, request.params);
|
||||
@@ -57,7 +80,13 @@ export async function registerPricingRoutes(
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/pricing/calculate', async (request, reply) => {
|
||||
const calcSchema: FastifySchema = {
|
||||
tags: ['Pricing'],
|
||||
summary: 'Calculate price (público)',
|
||||
description: 'Calcula el precio con IVA para una variante y cantidad.',
|
||||
body: { type: 'object' },
|
||||
};
|
||||
app.post('/pricing/calculate', { schema: calcSchema }, async (request, reply) => {
|
||||
const input = parseJson(calculateBodySchema, request.body);
|
||||
try {
|
||||
const calculation = await pricing.calculate(input);
|
||||
@@ -68,31 +97,72 @@ export async function registerPricingRoutes(
|
||||
});
|
||||
|
||||
// ── Admin tax rates ─────────────────────────────────────────────────────────
|
||||
app.get('/admin/tax-rates', async (request, reply) => {
|
||||
const listTaxSchema: FastifySchema = {
|
||||
tags: ['Pricing'],
|
||||
summary: 'List tax rates (admin)',
|
||||
response: { 401: errorSchema, 403: errorSchema },
|
||||
};
|
||||
app.get('/admin/tax-rates', { schema: listTaxSchema }, async (request, reply) => {
|
||||
const user = await deps.authenticate(request);
|
||||
requireRole(user, 'admin');
|
||||
const result = await deps.pool.query<{
|
||||
id: string; name: string; rate_percent: number; country: string; applies_to: string; active: boolean;
|
||||
id: string;
|
||||
name: string;
|
||||
rate_percent: number;
|
||||
country: string;
|
||||
applies_to: string;
|
||||
active: boolean;
|
||||
}>('SELECT * FROM tax_rates ORDER BY rate_percent ASC');
|
||||
return reply.send({ items: result.rows.map(r => ({
|
||||
id: r.id, name: r.name, ratePercent: Number(r.rate_percent),
|
||||
country: r.country, appliesTo: r.applies_to, active: r.active,
|
||||
})) });
|
||||
return reply.send({
|
||||
items: result.rows.map((r) => ({
|
||||
id: r.id,
|
||||
name: r.name,
|
||||
ratePercent: Number(r.rate_percent),
|
||||
country: r.country,
|
||||
appliesTo: r.applies_to,
|
||||
active: r.active,
|
||||
})),
|
||||
});
|
||||
});
|
||||
|
||||
app.patch('/admin/tax-rates/:id', async (request, reply) => {
|
||||
const patchTaxSchema: FastifySchema = {
|
||||
tags: ['Pricing'],
|
||||
summary: 'Update tax rate (admin)',
|
||||
params: {
|
||||
type: 'object',
|
||||
required: ['id'],
|
||||
properties: { id: { type: 'string', format: 'uuid' } },
|
||||
},
|
||||
body: { type: 'object' },
|
||||
response: { 401: errorSchema, 403: errorSchema },
|
||||
};
|
||||
app.patch('/admin/tax-rates/:id', { schema: patchTaxSchema }, async (request, reply) => {
|
||||
const user = await deps.authenticate(request);
|
||||
requireRole(user, 'admin');
|
||||
const id = (request.params as { id: string }).id;
|
||||
const patch = parseJson(z.object({
|
||||
name: z.string().min(1).max(40).optional(),
|
||||
ratePercent: z.number().min(0).max(100).optional(),
|
||||
active: z.boolean().optional(),
|
||||
}), request.body);
|
||||
const sets: string[] = []; const values: unknown[] = []; let i = 1;
|
||||
if (patch.name !== undefined) { sets.push(`name = $${i++}`); values.push(patch.name); }
|
||||
if (patch.ratePercent !== undefined) { sets.push(`rate_percent = $${i++}`); values.push(patch.ratePercent); }
|
||||
if (patch.active !== undefined) { sets.push(`active = $${i++}`); values.push(patch.active); }
|
||||
const patch = parseJson(
|
||||
z.object({
|
||||
name: z.string().min(1).max(40).optional(),
|
||||
ratePercent: z.number().min(0).max(100).optional(),
|
||||
active: z.boolean().optional(),
|
||||
}),
|
||||
request.body,
|
||||
);
|
||||
const sets: string[] = [];
|
||||
const values: unknown[] = [];
|
||||
let i = 1;
|
||||
if (patch.name !== undefined) {
|
||||
sets.push(`name = $${i++}`);
|
||||
values.push(patch.name);
|
||||
}
|
||||
if (patch.ratePercent !== undefined) {
|
||||
sets.push(`rate_percent = $${i++}`);
|
||||
values.push(patch.ratePercent);
|
||||
}
|
||||
if (patch.active !== undefined) {
|
||||
sets.push(`active = $${i++}`);
|
||||
values.push(patch.active);
|
||||
}
|
||||
if (!sets.length) return reply.send({ ok: true });
|
||||
values.push(id);
|
||||
await deps.pool.query(`UPDATE tax_rates SET ${sets.join(', ')} WHERE id = $${i}`, values);
|
||||
|
||||
Reference in New Issue
Block a user