Passed
Push — development ( 223a11...811359 )
by Karl
24:36 queued 11:37
created

backend/src/travel/travel.service.ts   A

Complexity

Total Complexity 22
Complexity/F 2.44

Size

Lines of Code 180
Function Count 9

Duplication

Duplicated Lines 0
Ratio 0 %

Test Coverage

Coverage 77.78%

Importance

Changes 0
Metric Value
eloc 143
dl 0
loc 180
ccs 49
cts 63
cp 0.7778
rs 10
c 0
b 0
f 0
wmc 22
mnd 13
bc 13
fnc 9
bpm 1.4443
cpm 2.4444
noi 0

9 Functions

Rating   Name   Duplication   Size   Complexity  
A TravelService.findActiveTravelForBike 0 15 2
A TravelService.findTravelsForCustomer 0 3 1
A TravelService.endActiveTravelForBike 0 4 1
A TravelService.endAllTravelsForCustomer 0 24 3
A TravelService.calculateCost 0 20 3
A TravelService.findById 0 7 2
A TravelService.findAll 0 3 1
A TravelService.startRentingBike 0 19 2
B TravelService.endTravel 0 58 7
1 9
import { Injectable, BadRequestException, NotFoundException } from '@nestjs/common';
2 9
import { InjectRepository } from '@nestjs/typeorm';
3 9
import { IsNull, Not, Repository } from 'typeorm';
4 9
import { Travel } from './entities/travel.entity';
5 9
import { BicyclesService } from '../bicycles/bicycles.service';
6 9
import { ZonesService } from 'src/zones/zones.service';
7
8
@Injectable()
9 9
export class TravelService {
10
  async findTravelsForCustomer(customerId: string) {
11
    return await this.travelRepository.find({
12
      where: { customer: { githubId: customerId } },
13
    });
14
  }
15
  constructor(
16
    @InjectRepository(Travel)
17 7
    private readonly travelRepository: Repository<Travel>,
18 7
    private readonly bicyclesService: BicyclesService,
19 7
    private readonly zonesService: ZonesService,
20
  ) {}
21
22
  async findAll(): Promise<Travel[]> {
23 1
    return await this.travelRepository.find();
24
  }
25
26
  async findById(id: number): Promise<Travel> {
27 2
    const travel = await this.travelRepository.findOne({ where: { id } });
28 2
    if (!travel) {
29 1
      throw new NotFoundException('Travel not found');
30
    }
31 1
    return travel;
32
  }
33
34
  async findActiveTravelForBike(bikeId: string): Promise<Travel> {
35
    const activeTravel = await this.travelRepository.findOne({
36
      where: {
37
        bike: { id: bikeId },
38
        startTime: Not(IsNull()),
39
        stopTime: IsNull(),
40
      },
41
    });
42
43 1
    if (!activeTravel) {
44
      throw new NotFoundException(`No active travel found for bike ${bikeId}`);
45
    }
46
47
    return activeTravel;
48
  }
49
50
  async startRentingBike(bikeId: string, customerId: string): Promise<Travel> {
51 1
    const bike = await this.bicyclesService.setRented(bikeId);
52 2
    const zoneType = this.zonesService.pointInParkingZone(bike.latitude, bike.longitude)
53
      ? 'Parking'
54
      : 'Free';
55
56 1
    const travel = this.travelRepository.create({
57
      bike,
58
      startTime: new Date(),
59
      latStart: bike.latitude,
60
      longStart: bike.longitude,
61
      customer: { githubId: customerId },
62
      startZoneType: zoneType,
63
      endZoneType: null,
64
      cost: 0,
65
    });
66
67 1
    return this.travelRepository.save(travel);
68
  }
69
70
  async endActiveTravelForBike(bikeId: string) {
71
    const activeTravel = await this.findActiveTravelForBike(bikeId);
72
    return this.endTravel(activeTravel.id);
73
  }
74
75
  async endTravel(travelId: number): Promise<Travel> {
76 2
    const travel = await this.travelRepository.findOne({
77
      where: { id: travelId },
78
      relations: ['bike', 'customer'], // Load bike and customer relations
79
    });
80
81 2
    if (!travel) {
82 1
      throw new NotFoundException('Travel not found');
83
    }
84
85 1
    if (travel.stopTime) {
86
      throw new BadRequestException('Travel has already ended');
87
    }
88
89
    // Get current bike location (from bike entity)
90 1
    const bike = await this.bicyclesService.findById(travel.bike.id);
91
92
    // Get the end zone type
93 2
    const endZoneType = this.zonesService.pointInParkingZone(bike.latitude, bike.longitude)
94
      ? 'Parking'
95
      : 'Free';
96
97
    // Set end time to current server time
98 1
    const endTime = new Date();
99
100
    // Calculate cost
101 1
    const cost = this.calculateCost(travel.startTime, endTime, travel.startZoneType, endZoneType);
102
103
    // Update travel record
104 1
    travel.stopTime = endTime;
105 1
    travel.latStop = bike.latitude;
106 1
    travel.longStop = bike.longitude;
107 1
    travel.endZoneType = endZoneType;
108 1
    travel.cost = cost;
109
110 2
    const newStatus = endZoneType === 'Parking' ? 'Service' : 'Available';
111 1
    await this.bicyclesService.update(bike.id, { status: newStatus });
112
113
    // Update user account
114 1
    const customer = travel.customer;
115
116 2
    if (customer.isMonthlyPayment) {
117
      // Accumulate cost for monthly payment users
118 1
      customer.accumulatedCost += cost;
119
    } else {
120
      // Deduct from balance for prepaid users
121 1
      if (customer.balance < cost) {
122
        throw new BadRequestException('Insufficient balance. Please insert funds.');
123
      }
124
      customer.balance -= cost;
125
    }
126
127
    // Save updated user
128 1
    await this.travelRepository.manager.getRepository('User').save(customer);
129
130
    // Save and return updated travel
131 1
    return this.travelRepository.save(travel);
132
  }
133
134
  async endAllTravelsForCustomer(githubId: string): Promise<string> {
135
    // Find all active travels for the customer
136
    const activeTravels = await this.travelRepository.find({
137
      where: {
138
        customer: { githubId: githubId },
139
        startTime: Not(IsNull()),
140
        stopTime: IsNull(),
141
      },
142
      relations: ['bike', 'customer'],
143
    });
144
145 1
    if (activeTravels.length === 0) {
146
      throw new NotFoundException(
147
        `No active travels found for customer with GitHub ID ${githubId}.`,
148
      );
149
    }
150
151
    // Loop through each active travel and end it using the existing endTravel method
152
    for (const travel of activeTravels) {
153
      await this.endTravel(travel.id);
154
    }
155
156
    return `All active travels for customer with GitHub ID ${githubId} have been successfully ended.`;
157
  }
158
159
  calculateCost(
160
    startTime: Date,
161
    endTime: Date,
162
    startZoneType: string,
163
    endZoneType: string,
164
  ): number {
165 1
    const timeDiff = endTime.getTime() - startTime.getTime();
166 1
    const timeDiffInMinutes = timeDiff / 1000 / 60;
167
168 1
    const parkingFee = 10;
169 1
    const startFee = 10;
170 1
    const costPerMinute = 1;
171
172
    const cost =
173 2
      (endZoneType === 'Parking' ? 0 : parkingFee) +
174
      (startZoneType === 'Free' && endZoneType === 'Parking' ? startFee / 2 : startFee) +
175
      timeDiffInMinutes * costPerMinute;
176
177 1
    return cost;
178
  }
179
}
180