archetecture security fix
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
declare const router: import("express-serve-static-core").Router;
|
||||
export default router;
|
||||
//# sourceMappingURL=analytics.routes.d.ts.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"analytics.routes.d.ts","sourceRoot":"","sources":["../../../src/modules/analytics/analytics.routes.ts"],"names":[],"mappings":"AAUA,QAAA,MAAM,MAAM,4CAAW,CAAA;AAsCvB,eAAe,MAAM,CAAA"}
|
||||
@@ -0,0 +1,90 @@
|
||||
"use strict";
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || (function () {
|
||||
var ownKeys = function(o) {
|
||||
ownKeys = Object.getOwnPropertyNames || function (o) {
|
||||
var ar = [];
|
||||
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
||||
return ar;
|
||||
};
|
||||
return ownKeys(o);
|
||||
};
|
||||
return function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
})();
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const express_1 = require("express");
|
||||
const requireCompanyAuth_1 = require("../../middleware/requireCompanyAuth");
|
||||
const requireTenant_1 = require("../../middleware/requireTenant");
|
||||
const requireSubscription_1 = require("../../middleware/requireSubscription");
|
||||
const requireRole_1 = require("../../middleware/requireRole");
|
||||
const validate_1 = require("../../http/validate");
|
||||
const respond_1 = require("../../http/respond");
|
||||
const service = __importStar(require("./analytics.service"));
|
||||
const analytics_schemas_1 = require("./analytics.schemas");
|
||||
const router = (0, express_1.Router)();
|
||||
router.use(requireCompanyAuth_1.requireCompanyAuth, requireTenant_1.requireTenant, requireSubscription_1.requireSubscription);
|
||||
router.get('/summary', async (req, res, next) => {
|
||||
try {
|
||||
const { period } = (0, validate_1.parseQuery)(analytics_schemas_1.summaryQuerySchema, req);
|
||||
(0, respond_1.ok)(res, await service.getSummary(req.companyId, period));
|
||||
}
|
||||
catch (err) {
|
||||
next(err);
|
||||
}
|
||||
});
|
||||
router.get('/dashboard', async (req, res, next) => {
|
||||
try {
|
||||
(0, respond_1.ok)(res, await service.getDashboard(req.companyId));
|
||||
}
|
||||
catch (err) {
|
||||
next(err);
|
||||
}
|
||||
});
|
||||
router.get('/sources', async (req, res, next) => {
|
||||
try {
|
||||
(0, respond_1.ok)(res, await service.getSources(req.companyId));
|
||||
}
|
||||
catch (err) {
|
||||
next(err);
|
||||
}
|
||||
});
|
||||
router.get('/report', (0, requireRole_1.requireRole)('MANAGER'), async (req, res, next) => {
|
||||
try {
|
||||
const query = (0, validate_1.parseQuery)(analytics_schemas_1.reportQuerySchema, req);
|
||||
const result = await service.getReport(req.companyId, query);
|
||||
if (result.format === 'CSV') {
|
||||
const start = result.startDate.toISOString().slice(0, 10);
|
||||
const end = result.endDate.toISOString().slice(0, 10);
|
||||
res.setHeader('Content-Type', 'text/csv');
|
||||
res.setHeader('Content-Disposition', `attachment; filename="report-${start}-${end}.csv"`);
|
||||
return res.send(result.csv);
|
||||
}
|
||||
(0, respond_1.ok)(res, result.report);
|
||||
}
|
||||
catch (err) {
|
||||
next(err);
|
||||
}
|
||||
});
|
||||
exports.default = router;
|
||||
//# sourceMappingURL=analytics.routes.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"analytics.routes.js","sourceRoot":"","sources":["../../../src/modules/analytics/analytics.routes.ts"],"names":[],"mappings":"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"}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { z } from 'zod';
|
||||
export declare const summaryQuerySchema: z.ZodObject<{
|
||||
period: z.ZodDefault<z.ZodString>;
|
||||
}, "strip", z.ZodTypeAny, {
|
||||
period: string;
|
||||
}, {
|
||||
period?: string | undefined;
|
||||
}>;
|
||||
export declare const reportQuerySchema: z.ZodObject<{
|
||||
from: z.ZodOptional<z.ZodString>;
|
||||
to: z.ZodOptional<z.ZodString>;
|
||||
format: z.ZodDefault<z.ZodString>;
|
||||
period: z.ZodOptional<z.ZodString>;
|
||||
}, "strip", z.ZodTypeAny, {
|
||||
format: string;
|
||||
from?: string | undefined;
|
||||
to?: string | undefined;
|
||||
period?: string | undefined;
|
||||
}, {
|
||||
format?: string | undefined;
|
||||
from?: string | undefined;
|
||||
to?: string | undefined;
|
||||
period?: string | undefined;
|
||||
}>;
|
||||
//# sourceMappingURL=analytics.schemas.d.ts.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"analytics.schemas.d.ts","sourceRoot":"","sources":["../../../src/modules/analytics/analytics.schemas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,eAAO,MAAM,kBAAkB;;;;;;EAE7B,CAAA;AAEF,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;EAK5B,CAAA"}
|
||||
@@ -0,0 +1,14 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.reportQuerySchema = exports.summaryQuerySchema = void 0;
|
||||
const zod_1 = require("zod");
|
||||
exports.summaryQuerySchema = zod_1.z.object({
|
||||
period: zod_1.z.string().default('30d'),
|
||||
});
|
||||
exports.reportQuerySchema = zod_1.z.object({
|
||||
from: zod_1.z.string().optional(),
|
||||
to: zod_1.z.string().optional(),
|
||||
format: zod_1.z.string().default('JSON'),
|
||||
period: zod_1.z.string().optional(),
|
||||
});
|
||||
//# sourceMappingURL=analytics.schemas.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"analytics.schemas.js","sourceRoot":"","sources":["../../../src/modules/analytics/analytics.schemas.ts"],"names":[],"mappings":";;;AAAA,6BAAuB;AAEV,QAAA,kBAAkB,GAAG,OAAC,CAAC,MAAM,CAAC;IACzC,MAAM,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC;CAClC,CAAC,CAAA;AAEW,QAAA,iBAAiB,GAAG,OAAC,CAAC,MAAM,CAAC;IACxC,IAAI,EAAI,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC7B,EAAE,EAAM,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC7B,MAAM,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC;IAClC,MAAM,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CAC9B,CAAC,CAAA"}
|
||||
@@ -0,0 +1,70 @@
|
||||
export declare function getSummary(companyId: string, period: string): Promise<{
|
||||
totalReservations: any;
|
||||
activeVehicles: any;
|
||||
totalRevenue: any;
|
||||
totalCustomers: any;
|
||||
period: string;
|
||||
}>;
|
||||
export declare function getDashboard(companyId: string): Promise<{
|
||||
kpis: {
|
||||
totalBookings: any;
|
||||
activeVehicles: any;
|
||||
monthlyRevenue: any;
|
||||
totalCustomers: any;
|
||||
bookingsChange: number;
|
||||
vehiclesChange: number;
|
||||
revenueChange: number;
|
||||
customersChange: number;
|
||||
};
|
||||
recentReservations: {
|
||||
id: any;
|
||||
bookingRef: any;
|
||||
customerName: string;
|
||||
vehicleName: string;
|
||||
startDate: any;
|
||||
endDate: any;
|
||||
status: any;
|
||||
totalAmount: any;
|
||||
}[];
|
||||
sourceBreakdown: {
|
||||
source: any;
|
||||
count: any;
|
||||
revenue: any;
|
||||
}[];
|
||||
subscription: {
|
||||
status: any;
|
||||
planName: any;
|
||||
trialEndsAt: any;
|
||||
} | null;
|
||||
}>;
|
||||
export declare function getSources(companyId: string): Promise<any>;
|
||||
export declare function getReport(companyId: string, query: {
|
||||
from?: string;
|
||||
to?: string;
|
||||
format: string;
|
||||
period?: string;
|
||||
}): Promise<{
|
||||
report: {
|
||||
summary: {
|
||||
totalReservations: any;
|
||||
totalRentalRevenue: any;
|
||||
totalDiscounts: any;
|
||||
totalInsurance: any;
|
||||
totalAdditionalDrivers: any;
|
||||
totalPricingRulesAdj: any;
|
||||
totalDeposits: any;
|
||||
totalCollected: any;
|
||||
averageRentalDays: number;
|
||||
};
|
||||
rows: any;
|
||||
period: {
|
||||
startDate: Date;
|
||||
endDate: Date;
|
||||
};
|
||||
};
|
||||
startDate: Date;
|
||||
endDate: Date;
|
||||
format: string;
|
||||
csv: string | null;
|
||||
}>;
|
||||
//# sourceMappingURL=analytics.service.d.ts.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"analytics.service.d.ts","sourceRoot":"","sources":["../../../src/modules/analytics/analytics.service.ts"],"names":[],"mappings":"AAkBA,wBAAsB,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;;;;;;GAUjE;AAED,wBAAsB,YAAY,CAAC,SAAS,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwDnD;AAED,wBAAsB,UAAU,CAAC,SAAS,EAAE,MAAM,gBAEjD;AAED,wBAAsB,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,EAAE,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE;;;;;;;;;;;;;;;;;;;;;;;GAOxH"}
|
||||
@@ -0,0 +1,96 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.getSummary = getSummary;
|
||||
exports.getDashboard = getDashboard;
|
||||
exports.getSources = getSources;
|
||||
exports.getReport = getReport;
|
||||
const prisma_1 = require("../../lib/prisma");
|
||||
const financialReportService_1 = require("../../services/financialReportService");
|
||||
function getRangeFromPeriod(period) {
|
||||
const now = new Date();
|
||||
switch (period) {
|
||||
case 'WEEKLY': return { from: new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000), to: now };
|
||||
case 'QUARTERLY': return { from: new Date(now.getFullYear(), now.getMonth() - 2, 1), to: now };
|
||||
case 'ANNUAL': return { from: new Date(now.getFullYear(), 0, 1), to: now };
|
||||
default: return { from: new Date(now.getFullYear(), now.getMonth(), 1), to: now };
|
||||
}
|
||||
}
|
||||
function pct(current, previous) {
|
||||
if (previous === 0)
|
||||
return current > 0 ? 100 : 0;
|
||||
return Math.round(((current - previous) / previous) * 100);
|
||||
}
|
||||
async function getSummary(companyId, period) {
|
||||
const days = parseInt(period.replace('d', '')) || 30;
|
||||
const since = new Date(Date.now() - days * 24 * 60 * 60 * 1000);
|
||||
const [totalReservations, activeVehicles, totalRevenue, totalCustomers] = await Promise.all([
|
||||
prisma_1.prisma.reservation.count({ where: { companyId, createdAt: { gte: since } } }),
|
||||
prisma_1.prisma.vehicle.count({ where: { companyId, status: 'AVAILABLE' } }),
|
||||
prisma_1.prisma.reservation.aggregate({ where: { companyId, status: { in: ['COMPLETED'] }, createdAt: { gte: since } }, _sum: { totalAmount: true } }),
|
||||
prisma_1.prisma.customer.count({ where: { companyId } }),
|
||||
]);
|
||||
return { totalReservations, activeVehicles, totalRevenue: totalRevenue._sum.totalAmount ?? 0, totalCustomers, period };
|
||||
}
|
||||
async function getDashboard(companyId) {
|
||||
const now = new Date();
|
||||
const monthStart = new Date(now.getFullYear(), now.getMonth(), 1);
|
||||
const prevMonthStart = new Date(now.getFullYear(), now.getMonth() - 1, 1);
|
||||
const prevMonthEnd = new Date(monthStart.getTime() - 1);
|
||||
const [totalBookings, previousBookings, activeVehicles, previousActiveVehicles, totalCustomers, previousCustomers, monthlyRevenueAgg, previousRevenueAgg, recentReservations, sourceGroups, subscription,] = await Promise.all([
|
||||
prisma_1.prisma.reservation.count({ where: { companyId, createdAt: { gte: monthStart } } }),
|
||||
prisma_1.prisma.reservation.count({ where: { companyId, createdAt: { gte: prevMonthStart, lte: prevMonthEnd } } }),
|
||||
prisma_1.prisma.vehicle.count({ where: { companyId, status: { in: ['AVAILABLE', 'RENTED'] } } }),
|
||||
prisma_1.prisma.vehicle.count({ where: { companyId, createdAt: { lte: prevMonthEnd }, status: { in: ['AVAILABLE', 'RENTED'] } } }),
|
||||
prisma_1.prisma.customer.count({ where: { companyId } }),
|
||||
prisma_1.prisma.customer.count({ where: { companyId, createdAt: { lte: prevMonthEnd } } }),
|
||||
prisma_1.prisma.reservation.aggregate({ where: { companyId, status: { in: ['CONFIRMED', 'ACTIVE', 'COMPLETED'] }, createdAt: { gte: monthStart } }, _sum: { totalAmount: true } }),
|
||||
prisma_1.prisma.reservation.aggregate({ where: { companyId, status: { in: ['CONFIRMED', 'ACTIVE', 'COMPLETED'] }, createdAt: { gte: prevMonthStart, lte: prevMonthEnd } }, _sum: { totalAmount: true } }),
|
||||
prisma_1.prisma.reservation.findMany({ where: { companyId }, include: { customer: true, vehicle: true }, orderBy: { createdAt: 'desc' }, take: 8 }),
|
||||
prisma_1.prisma.reservation.groupBy({ by: ['source'], where: { companyId }, _count: { id: true }, _sum: { totalAmount: true } }),
|
||||
prisma_1.prisma.subscription.findUnique({ where: { companyId } }),
|
||||
]);
|
||||
return {
|
||||
kpis: {
|
||||
totalBookings,
|
||||
activeVehicles,
|
||||
monthlyRevenue: monthlyRevenueAgg._sum.totalAmount ?? 0,
|
||||
totalCustomers,
|
||||
bookingsChange: pct(totalBookings, previousBookings),
|
||||
vehiclesChange: pct(activeVehicles, previousActiveVehicles),
|
||||
revenueChange: pct(monthlyRevenueAgg._sum.totalAmount ?? 0, previousRevenueAgg._sum.totalAmount ?? 0),
|
||||
customersChange: pct(totalCustomers, previousCustomers),
|
||||
},
|
||||
recentReservations: recentReservations.map((r) => ({
|
||||
id: r.id,
|
||||
bookingRef: r.contractNumber ?? r.id.slice(-8).toUpperCase(),
|
||||
customerName: `${r.customer.firstName} ${r.customer.lastName}`,
|
||||
vehicleName: `${r.vehicle.make} ${r.vehicle.model}`,
|
||||
startDate: r.startDate,
|
||||
endDate: r.endDate,
|
||||
status: r.status,
|
||||
totalAmount: r.totalAmount,
|
||||
})),
|
||||
sourceBreakdown: sourceGroups.map((g) => ({
|
||||
source: g.source,
|
||||
count: g._count.id,
|
||||
revenue: g._sum.totalAmount ?? 0,
|
||||
})),
|
||||
subscription: subscription ? {
|
||||
status: subscription.status,
|
||||
planName: subscription.plan,
|
||||
trialEndsAt: subscription.trialEndAt,
|
||||
} : null,
|
||||
};
|
||||
}
|
||||
async function getSources(companyId) {
|
||||
return prisma_1.prisma.reservation.groupBy({ by: ['source'], where: { companyId }, _count: { id: true } });
|
||||
}
|
||||
async function getReport(companyId, query) {
|
||||
const accountingSettings = await prisma_1.prisma.accountingSettings.findUnique({ where: { companyId } });
|
||||
const derivedRange = getRangeFromPeriod(query.period || accountingSettings?.reportingPeriod || 'MONTHLY');
|
||||
const startDate = query.from ? new Date(query.from) : derivedRange.from;
|
||||
const endDate = query.to ? new Date(query.to) : derivedRange.to;
|
||||
const report = await (0, financialReportService_1.generateFinancialReport)(companyId, startDate, endDate);
|
||||
return { report, startDate, endDate, format: query.format, csv: query.format === 'CSV' ? (0, financialReportService_1.toCsv)(report.rows) : null };
|
||||
}
|
||||
//# sourceMappingURL=analytics.service.js.map
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user