feat: add ownership-costs feature capsule (refs #15)

- Create ownership_costs table for recurring vehicle costs
- Add backend feature capsule with types, repository, service, routes
- Update TCO calculation to use ownership_costs (with fallback to legacy vehicle fields)
- Add taxCosts and otherCosts to TCO response
- Create frontend ownership-costs feature with form, list, API, hooks
- Update TCODisplay to show all cost types

This implements a more flexible approach to tracking recurring ownership costs
(insurance, registration, tax, other) with explicit date ranges and optional
document association.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Eric Gullickson
2026-01-12 21:28:25 -06:00
parent 5c93150a58
commit a8c4eba8d1
19 changed files with 1644 additions and 15 deletions

View File

@@ -0,0 +1,56 @@
/**
* @ai-summary Fastify routes for ownership costs API
* @ai-context Route definitions with Fastify plugin pattern and authentication
*/
import { FastifyInstance, FastifyPluginOptions } from 'fastify';
import { FastifyPluginAsync } from 'fastify';
import { OwnershipCostsController } from './ownership-costs.controller';
export const ownershipCostsRoutes: FastifyPluginAsync = async (
fastify: FastifyInstance,
_opts: FastifyPluginOptions
) => {
const controller = new OwnershipCostsController();
// POST /api/ownership-costs - Create new ownership cost
fastify.post('/ownership-costs', {
preHandler: [fastify.authenticate],
handler: controller.create.bind(controller)
});
// GET /api/ownership-costs/:id - Get specific ownership cost
fastify.get('/ownership-costs/:id', {
preHandler: [fastify.authenticate],
handler: controller.getById.bind(controller)
});
// PUT /api/ownership-costs/:id - Update ownership cost
fastify.put('/ownership-costs/:id', {
preHandler: [fastify.authenticate],
handler: controller.update.bind(controller)
});
// DELETE /api/ownership-costs/:id - Delete ownership cost
fastify.delete('/ownership-costs/:id', {
preHandler: [fastify.authenticate],
handler: controller.delete.bind(controller)
});
// GET /api/ownership-costs/vehicle/:vehicleId - Get costs for a vehicle
fastify.get('/ownership-costs/vehicle/:vehicleId', {
preHandler: [fastify.authenticate],
handler: controller.getByVehicle.bind(controller)
});
// GET /api/ownership-costs/vehicle/:vehicleId/stats - Get aggregated cost stats
fastify.get('/ownership-costs/vehicle/:vehicleId/stats', {
preHandler: [fastify.authenticate],
handler: controller.getVehicleStats.bind(controller)
});
};
// For backward compatibility during migration
export function registerOwnershipCostsRoutes() {
throw new Error('registerOwnershipCostsRoutes is deprecated - use ownershipCostsRoutes Fastify plugin instead');
}