Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ApiDocs/HelloFutsal/Booking/BulkBookSlots.bru
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
meta {
name: BulkBookSlots
type: http
seq: 15
seq: 4
}

post {
Expand Down
2 changes: 1 addition & 1 deletion ApiDocs/HelloFutsal/Booking/BulkConfirmBookings.bru
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
meta {
name: BulkConfirmBookings
type: http
seq: 16
seq: 7
}

patch {
Expand Down
10 changes: 5 additions & 5 deletions ApiDocs/HelloFutsal/Booking/CancelBooking.bru
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
meta {
name: CancelBooking
type: http
seq: 15
seq: 5
}

patch {
Expand All @@ -10,10 +10,10 @@ patch {
auth: bearer
}

auth:bearer {
token: {{token}}
}

headers {
Content-Type: application/json
}

auth:bearer {
token: {{token}}
}
2 changes: 1 addition & 1 deletion ApiDocs/HelloFutsal/Booking/ConfirmBooking.bru
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
meta {
name: ConfirmBooking
type: http
seq: 14
seq: 2
}

patch {
Expand Down
4 changes: 2 additions & 2 deletions ApiDocs/HelloFutsal/Booking/CreateBooking.bru
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
meta {
name: CreateBooking
type: http
seq: 13
seq: 1
}

post {
Expand All @@ -24,4 +24,4 @@ body:json {
"userName": "John Doe",
"phoneNumber": "+9779800000000"
}
}
}
10 changes: 5 additions & 5 deletions ApiDocs/HelloFutsal/Booking/GetBookingById.bru
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
meta {
name: GetBookingById
type: http
seq: 14
seq: 3
}

get {
Expand All @@ -10,10 +10,10 @@ get {
auth: bearer
}

auth:bearer {
token: {{token}}
}

headers {
Content-Type: application/json
}

auth:bearer {
token: {{token}}
}
10 changes: 5 additions & 5 deletions ApiDocs/HelloFutsal/Booking/GetBookingsByFieldId.bru
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
meta {
name: GetBookingsByFieldId
type: http
seq: 15
seq: 6
}

get {
Expand All @@ -10,10 +10,10 @@ get {
auth: bearer
}

headers {
Content-Type: application/json
}

auth:bearer {
token: {{token}}
}

headers {
Content-Type: application/json
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
meta {
name: CancelMembership
type: http
seq: 20
seq: 4
}

patch {
Expand All @@ -10,14 +10,14 @@ patch {
auth: bearer
}

auth:bearer {
token: {{token}}
}

headers {
Content-Type: application/json
}

auth:bearer {
token: {{token}}
}

body:json {
{
"endDate": "2026-05-20"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
meta {
name: ConfirmMembershipPayment
type: http
seq: 3
seq: 1
}

post {
Expand Down
4 changes: 2 additions & 2 deletions ApiDocs/HelloFutsal/MembershipSlots/CreateMembershipPlan.bru
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
meta {
name: CreateMembershipPlan
type: http
seq: 20
seq: 6
}

post {
Expand All @@ -27,7 +27,7 @@ body:json {
"startDate": "2026-05-02",
"timeRange": [
{
"day": "tuesday",
"day": "wednesday",
"slots": [
{
"startTime": "05:00",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
meta {
name: GetFieldSlotSummary
type: http
seq: 21
seq: 7
}

get {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
meta {
name: GetMembershipDetailsByField
type: http
seq: 22
seq: 8
}

get {
Expand Down
2 changes: 1 addition & 1 deletion ApiDocs/HelloFutsal/MembershipSlots/GetPlayedSlot.bru
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
meta {
name: GetPlayedSlot
type: http
seq: 4
seq: 2
}

get {
Expand Down
2 changes: 1 addition & 1 deletion ApiDocs/HelloFutsal/MembershipSlots/PayMembershipSlot.bru
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
meta {
name: PayMembershipSlot
type: http
seq: 5
seq: 3
}

patch {
Expand Down
2 changes: 1 addition & 1 deletion ApiDocs/HelloFutsal/MembershipSlots/UpdateMembership.bru
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
meta {
name: UpdateMembership
type: http
seq: 7
seq: 5
}

patch {
Expand Down
6 changes: 3 additions & 3 deletions ApiDocs/HelloFutsal/environments/Dev.bru
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
vars {
development: http://localhost:3000
fieldId: 7e64459c-4a92-490c-bfdd-4ab660a626e9
token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIzZjljZWYwZi0zZGQ0LTQwOGMtYTI2Ni0wMDJjN2E3MjlhODUiLCJlbWFpbCI6bnVsbCwibW9iaWxlTnVtYmVyIjoiOTg2Nzc1NDczOCIsInJvbGUiOiJhZG1pbiIsImlhdCI6MTc3ODYzNzYxNywiZXhwIjoxNzc4NzI0MDE3fQ.lF51reUEi07ST1GxobnaRq_nQ9vHWKrmRwRx9yryfYU
token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIzZjljZWYwZi0zZGQ0LTQwOGMtYTI2Ni0wMDJjN2E3MjlhODUiLCJlbWFpbCI6bnVsbCwibW9iaWxlTnVtYmVyIjoiOTg2Nzc1NDczOCIsInJvbGUiOiJhZG1pbiIsImlhdCI6MTc3ODc3NjExNywiZXhwIjoxNzc4ODYyNTE3fQ.r12ozDCr4SayGiLjH4carB7n_362-DScWLTIgrVrIVA
ruleBookSpecificSlotId: 2e78fc78-08e4-423e-a8ef-5ff10c59f1a7
ruleBookAllSlotId: 81e212b2-dd86-47db-bb96-8a163b615d61
ruleBookTimeSlotId: 4aebf2ac-9651-4450-8968-812cd487261e
slotId: c7b13c7d-6d73-4e24-941e-d74a8eb18d5f
slotId: d541c26f-6811-4fa0-b9ba-bd16d69f18a0
bookingId: c04ce07f-f1af-4ac2-a854-2f4394b2c565
scheduleSettingId: c06a9396-8f7f-4158-94d9-59ebd9d0adfd
membershipId: 31b16035-8a38-45f9-9b8b-991b4aafcde1
membershipId: 9c6d67ff-c092-4a76-b4ec-80818f401c93
rulebookId: 5d6ca08c-cfad-4273-9656-66b4cdb13262
}
2 changes: 2 additions & 0 deletions src/booking/booking.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,13 @@ import { BookingRevenueModule } from "./revenue/booking-revenue.module";
import { Field } from "../fields/entities/field.entity";
import { FieldsModule } from "../fields/fields.module";
import { MembershipPricingHistory } from "./entities/membership-pricing-history.entity";
import { CancelledBooking } from "./entities/cancelled-booking.entity";

@Module({
imports: [
TypeOrmModule.forFeature([
Booking,
CancelledBooking,
FieldSlot,
UserAccount,
MembershipPlan,
Expand Down
68 changes: 64 additions & 4 deletions src/booking/booking.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { GroundOwnerAccount } from "../auth/entities/ground-owner.entity";
import { UserAccount } from "../auth/entities/user.entity";
import { AuthenticatedAccount } from "../auth/types/authenticated-account.type";
import { Booking } from "./entities/booking.entity";
import { CancelledBooking } from "./entities/cancelled-booking.entity";
import { MembershipPlan } from "./entities/membership-plan.entity";
import { Field } from "../fields/entities/field.entity";
import { FieldSlot } from "../fields/entities/field-slot.entity";
Expand All @@ -25,6 +26,8 @@ export class BookingService {
constructor(
@InjectRepository(Booking)
private readonly bookingsRepository: Repository<Booking>,
@InjectRepository(CancelledBooking)
private readonly cancelledBookingsRepository: Repository<CancelledBooking>,
@InjectRepository(FieldSlot)
private readonly fieldSlotsRepository: Repository<FieldSlot>,
@InjectRepository(UserAccount)
Expand Down Expand Up @@ -144,8 +147,25 @@ export class BookingService {
}
// ---------------------------------------------------------------------------

const booking = await manager.getRepository(Booking).save(
manager.getRepository(Booking).create({
const bookingRepo = manager.getRepository(Booking);

// Ensure there's no active booking for this slot (status <> 'cancelled').
const activeBooking = await bookingRepo
.createQueryBuilder("booking")
.where("booking.slot_id = :slotId", { slotId: slot.id })
.andWhere("booking.status <> :cancelled", {
cancelled: "cancelled",
})
.setLock("pessimistic_write")
.getOne();

if (activeBooking) {
throw new ConflictException("Slot already has an active booking");
}

// Create a new booking row (preserve cancelled history rows separately).
const booking = await bookingRepo.save(
bookingRepo.create({
fieldId: slot.fieldId,
slotId: slot.id,
userId: user.id,
Expand Down Expand Up @@ -536,8 +556,27 @@ export class BookingService {
throw new NotFoundException("Slot not found");
}

booking.status = "cancelled";
await bookingRepository.save(booking);
// Archive cancelled booking into `cancelled_bookings` and remove original
const cancelledRepo = manager.getRepository(CancelledBooking);

await cancelledRepo.save(
cancelledRepo.create({
originalBookingId: booking.id,
fieldId: booking.fieldId,
slotId: booking.slotId,
userId: booking.userId,
bookingType: booking.bookingType,
baseAmount: booking.baseAmount,
totalAmount: booking.totalAmount,
discount: booking.discount,
extraAmount: booking.extraAmount,
discountAmount: booking.discountAmount,
createdAt: booking.createdAt,
cancelledBy: account.id,
}),
);

await bookingRepository.delete({ id: booking.id });

slot.status = "available";
slot.slotType = "normal";
Expand Down Expand Up @@ -867,6 +906,27 @@ export class BookingService {
bookingType = "membership";
}

// Ensure there's no active booking for this slot (status <> 'cancelled').
const activeBooking = await bookingRepository
.createQueryBuilder("booking")
.where("booking.slot_id = :slotId", { slotId: lockedSlot.id })
.andWhere("booking.status <> :cancelled", {
cancelled: "cancelled",
})
.setLock("pessimistic_write")
.getOne();

if (activeBooking) {
failedBookings.push({
slotDate: slot.slotDate,
startTime: slot.startTime,
endTime: slot.endTime,
error: "Slot already booked",
});
continue;
}

// Create a new booking row (preserve cancelled history rows separately).
const booking = await bookingRepository.save(
bookingRepository.create({
fieldId: slot.fieldId,
Expand Down
Loading
Loading