mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-08 15:58:18 +00:00
feat: ( audit ) resolve the actor from the session and audit all backoffice mutations
This commit is contained in:
@@ -4,6 +4,37 @@ import { CreateTrainDto, CreateCoachDto, UpdateCoachDto, AssignCoachDto, ListCoa
|
||||
import { SeatKind } from '@prisma/client';
|
||||
import { DeleteOperationException } from '../../common/exceptions/delete-operation.exception';
|
||||
import { AuditService } from '../../common/audit.service';
|
||||
import { AUDIT_ACTIONS, AUDIT_ENTITIES } from '../../common/audit.actions';
|
||||
import { snapshot } from '../../common/audit-snapshot';
|
||||
|
||||
/** Fields carried into audit rows, per entity. Deliberately narrow — see audit-snapshot.ts. */
|
||||
const COACH_TYPE_AUDIT_FIELDS = ['code', 'name', 'type'] as const;
|
||||
const SEAT_CLASS_AUDIT_FIELDS = [
|
||||
'coachTypeId',
|
||||
'name',
|
||||
'description',
|
||||
'baseFareMinor',
|
||||
'premiumMinor',
|
||||
'insuranceFeeMinor',
|
||||
'isActive',
|
||||
] as const;
|
||||
const TRAIN_AUDIT_FIELDS = [
|
||||
'number',
|
||||
'name',
|
||||
'operatorId',
|
||||
'operatorName',
|
||||
'description',
|
||||
'isActive',
|
||||
] as const;
|
||||
const COACH_AUDIT_FIELDS = [
|
||||
'number',
|
||||
'coachTypeId',
|
||||
'arrangement',
|
||||
'capacity',
|
||||
'status',
|
||||
'sequence',
|
||||
] as const;
|
||||
const COACH_ASSIGNMENT_AUDIT_FIELDS = ['scheduleId', 'coachId', 'positionNumber', 'isOperational'] as const;
|
||||
|
||||
// Parses '2+2' → [2, 2], '2+2+2' → [2, 2, 2]
|
||||
function parseArrangement(arrangement: string): number[] {
|
||||
@@ -151,7 +182,7 @@ export class FleetService {
|
||||
constructor(private prisma: PrismaService, private auditService: AuditService) {}
|
||||
|
||||
async createCoachType(dto: CreateCoachTypeDto) {
|
||||
return this.prisma.coachType.create({
|
||||
const coachType = await this.prisma.coachType.create({
|
||||
data: {
|
||||
code: dto.code,
|
||||
name: dto.name,
|
||||
@@ -162,6 +193,13 @@ export class FleetService {
|
||||
coaches: true,
|
||||
},
|
||||
});
|
||||
await this.auditService.log({
|
||||
action: AUDIT_ACTIONS.CREATE,
|
||||
entityType: AUDIT_ENTITIES.CoachType,
|
||||
entityId: coachType.id,
|
||||
newData: snapshot(coachType, COACH_TYPE_AUDIT_FIELDS),
|
||||
});
|
||||
return coachType;
|
||||
}
|
||||
|
||||
async getCoachTypes() {
|
||||
@@ -183,7 +221,7 @@ export class FleetService {
|
||||
if (dto.name !== undefined) data.name = dto.name;
|
||||
if (dto.type !== undefined) data.type = dto.type;
|
||||
|
||||
return this.prisma.coachType.update({
|
||||
const updated = await this.prisma.coachType.update({
|
||||
where: { id },
|
||||
data,
|
||||
include: {
|
||||
@@ -191,6 +229,14 @@ export class FleetService {
|
||||
coaches: true,
|
||||
},
|
||||
});
|
||||
await this.auditService.log({
|
||||
action: AUDIT_ACTIONS.UPDATE,
|
||||
entityType: AUDIT_ENTITIES.CoachType,
|
||||
entityId: id,
|
||||
oldData: snapshot(coachType, COACH_TYPE_AUDIT_FIELDS),
|
||||
newData: snapshot(updated, COACH_TYPE_AUDIT_FIELDS),
|
||||
});
|
||||
return updated;
|
||||
}
|
||||
|
||||
async deleteCoachType(id: string) {
|
||||
@@ -224,11 +270,18 @@ export class FleetService {
|
||||
throw new DeleteOperationException('Coach Type', coachType.name, constraints);
|
||||
}
|
||||
|
||||
return this.prisma.coachType.delete({ where: { id } });
|
||||
const deleted = await this.prisma.coachType.delete({ where: { id } });
|
||||
await this.auditService.log({
|
||||
action: AUDIT_ACTIONS.DELETE,
|
||||
entityType: AUDIT_ENTITIES.CoachType,
|
||||
entityId: id,
|
||||
oldData: snapshot(coachType, COACH_TYPE_AUDIT_FIELDS),
|
||||
});
|
||||
return deleted;
|
||||
}
|
||||
|
||||
async createClass(dto: CreateClassDto) {
|
||||
return this.prisma.seatClass.create({
|
||||
const seatClass = await this.prisma.seatClass.create({
|
||||
data: {
|
||||
coachTypeId: dto.coachTypeId,
|
||||
name: dto.name,
|
||||
@@ -239,6 +292,13 @@ export class FleetService {
|
||||
...(dto.isActive !== undefined && { isActive: dto.isActive }),
|
||||
},
|
||||
});
|
||||
await this.auditService.log({
|
||||
action: AUDIT_ACTIONS.CREATE,
|
||||
entityType: AUDIT_ENTITIES.SeatClass,
|
||||
entityId: seatClass.id,
|
||||
newData: snapshot(seatClass, SEAT_CLASS_AUDIT_FIELDS),
|
||||
});
|
||||
return seatClass;
|
||||
}
|
||||
|
||||
async getClasses(coachTypeId?: string) {
|
||||
@@ -267,11 +327,19 @@ export class FleetService {
|
||||
updateData.isActive = dto.isActive;
|
||||
}
|
||||
|
||||
return this.prisma.seatClass.update({
|
||||
const updated = await this.prisma.seatClass.update({
|
||||
where: { id },
|
||||
data: updateData,
|
||||
include: { coachType: true },
|
||||
});
|
||||
await this.auditService.log({
|
||||
action: AUDIT_ACTIONS.UPDATE,
|
||||
entityType: AUDIT_ENTITIES.SeatClass,
|
||||
entityId: id,
|
||||
oldData: snapshot(seatClass, SEAT_CLASS_AUDIT_FIELDS),
|
||||
newData: snapshot(updated, SEAT_CLASS_AUDIT_FIELDS),
|
||||
});
|
||||
return updated;
|
||||
}
|
||||
|
||||
async deleteClass(id: string, cascade = false) {
|
||||
@@ -308,7 +376,15 @@ export class FleetService {
|
||||
await this.prisma.segmentFareRule.deleteMany({ where: { seatClassId: id } });
|
||||
}
|
||||
|
||||
return this.prisma.seatClass.delete({ where: { id } });
|
||||
const deleted = await this.prisma.seatClass.delete({ where: { id } });
|
||||
await this.auditService.log({
|
||||
action: AUDIT_ACTIONS.DELETE,
|
||||
entityType: AUDIT_ENTITIES.SeatClass,
|
||||
entityId: id,
|
||||
oldData: snapshot(seatClass, SEAT_CLASS_AUDIT_FIELDS),
|
||||
newData: { cascade },
|
||||
});
|
||||
return deleted;
|
||||
}
|
||||
|
||||
createSeatClass(dto: CreateClassDto) {
|
||||
@@ -345,7 +421,12 @@ export class FleetService {
|
||||
isActive: dto.isActive ?? true,
|
||||
},
|
||||
});
|
||||
await this.auditService.log({ action: 'CREATE', entityType: 'Train', entityId: train.id, newData: { number: train.number, name: train.name } });
|
||||
await this.auditService.log({
|
||||
action: AUDIT_ACTIONS.CREATE,
|
||||
entityType: AUDIT_ENTITIES.Train,
|
||||
entityId: train.id,
|
||||
newData: snapshot(train, TRAIN_AUDIT_FIELDS),
|
||||
});
|
||||
return train;
|
||||
}
|
||||
|
||||
@@ -363,7 +444,13 @@ export class FleetService {
|
||||
...(dto.isActive !== undefined && { isActive: dto.isActive }),
|
||||
},
|
||||
});
|
||||
await this.auditService.log({ action: 'UPDATE', entityType: 'Train', entityId: id, newData: { number: dto.number, name: dto.name } });
|
||||
await this.auditService.log({
|
||||
action: AUDIT_ACTIONS.UPDATE,
|
||||
entityType: AUDIT_ENTITIES.Train,
|
||||
entityId: id,
|
||||
oldData: snapshot(train, TRAIN_AUDIT_FIELDS),
|
||||
newData: snapshot(updated, TRAIN_AUDIT_FIELDS),
|
||||
});
|
||||
return updated;
|
||||
}
|
||||
|
||||
@@ -442,14 +529,28 @@ export class FleetService {
|
||||
}
|
||||
|
||||
const deleted = await this.prisma.train.delete({ where: { id } });
|
||||
await this.auditService.log({ action: 'DELETE', entityType: 'Train', entityId: id, oldData: { number: train.number, name: train.name } });
|
||||
await this.auditService.log({
|
||||
action: AUDIT_ACTIONS.DELETE,
|
||||
entityType: AUDIT_ENTITIES.Train,
|
||||
entityId: id,
|
||||
oldData: snapshot(train, TRAIN_AUDIT_FIELDS),
|
||||
newData: { cascade },
|
||||
});
|
||||
return deleted;
|
||||
}
|
||||
|
||||
async restoreTrain(id: string) {
|
||||
const train = await this.prisma.train.findUnique({ where: { id } });
|
||||
if (!train) throw new NotFoundException('Train not found');
|
||||
return this.prisma.train.update({ where: { id }, data: { isActive: true } });
|
||||
const restored = await this.prisma.train.update({ where: { id }, data: { isActive: true } });
|
||||
await this.auditService.log({
|
||||
action: AUDIT_ACTIONS.RESTORE,
|
||||
entityType: AUDIT_ENTITIES.Train,
|
||||
entityId: id,
|
||||
oldData: { isActive: train.isActive },
|
||||
newData: { isActive: true },
|
||||
});
|
||||
return restored;
|
||||
}
|
||||
|
||||
async getCoach(id: string) {
|
||||
@@ -526,7 +627,12 @@ export class FleetService {
|
||||
await this.prisma.seat.createMany({ data: seats });
|
||||
}
|
||||
|
||||
await this.auditService.log({ action: 'CREATE', entityType: 'Coach', entityId: coach.id, newData: { number: coach.number, capacity: coach.capacity } });
|
||||
await this.auditService.log({
|
||||
action: AUDIT_ACTIONS.CREATE,
|
||||
entityType: AUDIT_ENTITIES.Coach,
|
||||
entityId: coach.id,
|
||||
newData: snapshot(coach, COACH_AUDIT_FIELDS),
|
||||
});
|
||||
return coach;
|
||||
}
|
||||
|
||||
@@ -545,7 +651,13 @@ export class FleetService {
|
||||
},
|
||||
include: { coachType: true },
|
||||
});
|
||||
await this.auditService.log({ action: 'UPDATE', entityType: 'Coach', entityId: id, newData: { number: dto.number, status: dto.status } });
|
||||
await this.auditService.log({
|
||||
action: AUDIT_ACTIONS.UPDATE,
|
||||
entityType: AUDIT_ENTITIES.Coach,
|
||||
entityId: id,
|
||||
oldData: snapshot(coach, COACH_AUDIT_FIELDS),
|
||||
newData: snapshot(updated, COACH_AUDIT_FIELDS),
|
||||
});
|
||||
return updated;
|
||||
}
|
||||
|
||||
@@ -622,7 +734,13 @@ export class FleetService {
|
||||
await this.prisma.seat.deleteMany({ where: { coachId: id } });
|
||||
|
||||
const deleted = await this.prisma.coach.delete({ where: { id } });
|
||||
await this.auditService.log({ action: 'DELETE', entityType: 'Coach', entityId: id, oldData: { number: coach.number } });
|
||||
await this.auditService.log({
|
||||
action: AUDIT_ACTIONS.DELETE,
|
||||
entityType: AUDIT_ENTITIES.Coach,
|
||||
entityId: id,
|
||||
oldData: snapshot(coach, COACH_AUDIT_FIELDS),
|
||||
newData: { cascade },
|
||||
});
|
||||
return deleted;
|
||||
}
|
||||
|
||||
@@ -634,13 +752,30 @@ export class FleetService {
|
||||
if (!schedule) throw new NotFoundException('Schedule not found');
|
||||
if (!coach) throw new NotFoundException('Coach not found');
|
||||
if (coach.status !== 'ACTIVE') throw new BadRequestException('Coach is not active');
|
||||
return this.prisma.coachAssignment.create({ data: dto });
|
||||
const assignment = await this.prisma.coachAssignment.create({ data: dto });
|
||||
await this.auditService.log({
|
||||
action: AUDIT_ACTIONS.ASSIGN,
|
||||
entityType: AUDIT_ENTITIES.CoachAssignment,
|
||||
entityId: assignment.id,
|
||||
newData: {
|
||||
...snapshot(assignment, COACH_ASSIGNMENT_AUDIT_FIELDS),
|
||||
coachNumber: coach.number,
|
||||
},
|
||||
});
|
||||
return assignment;
|
||||
}
|
||||
|
||||
async removeAssignment(id: string) {
|
||||
const assignment = await this.prisma.coachAssignment.findUnique({ where: { id } });
|
||||
if (!assignment) throw new NotFoundException('Assignment not found');
|
||||
return this.prisma.coachAssignment.delete({ where: { id } });
|
||||
const deleted = await this.prisma.coachAssignment.delete({ where: { id } });
|
||||
await this.auditService.log({
|
||||
action: AUDIT_ACTIONS.UNASSIGN,
|
||||
entityType: AUDIT_ENTITIES.CoachAssignment,
|
||||
entityId: id,
|
||||
oldData: snapshot(assignment, COACH_ASSIGNMENT_AUDIT_FIELDS),
|
||||
});
|
||||
return deleted;
|
||||
}
|
||||
|
||||
async generateSeatMapPreview(dto: GenerateSeatMapDto) {
|
||||
|
||||
Reference in New Issue
Block a user