Cart Module Integration Details
Cart Module Integration Details
Okay, so I have a really big task for you. I've been working on food and beverages system for my
company. So basically, let me walk you through how the modules are structured. Okay, so the first
thing is that product. So in that product, the items are listed globally. Okay, after that, we have
listing. So listing is basically the items being associated to a particular property or a villa. Okay, after
that, we have a collection. That collection is basically like multiple listing items that are clubbed
together on basis of some rules. Okay, so after the collection, I have plants. So plants are nothing
but just a bunch of collections together. Okay, and the final is catalog for that. Okay, so catalog
contains all the items overview and all. So now, what I want is to create a cart. Okay, and the cart
should be, I should be able to add items. I have already defined the schema and tell me what are
the things you're going to require. Do I have to share each module files? Do I have to share the DTO
controllers and everything for each module? Just specify to me what the information you need for
creating the cart. So I will share you the schema, obviously, of the existing complete like my model,
whole schema. And apart from that, tell me if you need the service files for various modules of the
controllers and stuff like that. Okay.
Product Schema:
• Definitions of key attributes such as product ID, name, description, base price, variants (if any),
and any attributes affecting cart logic (e.g., available stock, weight for shipping calculations, etc.).
• Information on indexing or any hierarchical classification that might affect filtering or searching
within the cart.
Listing Schema:
• The linkage between a product and a specific property/villa (including foreign keys, unique
identifiers, and relevant metadata like localized pricing, availability, or display priority).
• Any additional fields that might control how listings are featured or modified before adding to a
cart (for example, limited availability times or property-specific discounts).
Collection Schema:
• The rules or criteria used to group multiple listing items together. This could include flags, priority
rules, or dynamic grouping strategies.
• How collections are referenced when retrieving listing information in the context of a cart.
Plants Schema:
• How multiple collections are combined and the overall attributes for the plant.
• Any aggregated data (such as overall discounts, bundle pricing, or inventory status) that would
impact the pricing or checkout process in the cart.
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 1/112
Catalog Schema:
• The overall overview of items as presented to end users.
• Details about categorizations, search filters, or ordering rules that might influence how items are
added or displayed in the cart.
Having complete schemas (or at least the relevant portions) will help clarify data relationships,
constraints, and dependencies.
DTO Definitions:
• The input and output DTOs for each module (product, listing, collection, plants, and catalog).
• Validation rules, data type constraints, and required vs. optional properties that need to be
honored when items are added to the cart.
• Any transformation logic, if present, that might affect how data is represented when moving
between modules (for instance, a transformation of price data or a mapping of product options).
These details ensure the cart module processes data consistently with the rest of the system.
This helps in outlining how the cart will interact (and possibly orchestrate data) from the multiple
modules.
4. Service/Business Logic
Business Rules:
• The rules from service files that govern how prices, discounts, or availability are calculated.
• Any service-level validations or events (e.g., stock updates, discount validation) that could affect
the cart contents.
• Details on how cascading updates are managed when product or listing information changes.
Inter-module Communication:
• How the different modules currently communicate (for instance, whether they use direct service
calls, events, or messaging queues).
• A description of whether the cart should leverage these same patterns.
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 2/112
Understanding these aspects allows for a consistent and integrated cart behavior across modules.
Authentication/Session Context:
• How user sessions or authentication tokens are managed.
• Any user-specific logic that might modify the cart (for example, loyalty discounts, specific user
groups, etc.).
To ensure a complete understanding and proper integration, please share the following parts:
1. Schema Files:
• Detailed models for the product, listing, collection, plants, and catalog.
• Any relationships/associations defined between these models.
2. DTO Definitions:
• Input/output DTOs for each module, including validation rules and transformation logic.
Having these details will allow me to architect the cart module in alignment with your system's existing
patterns while ensuring robust functionality and data consistency across modules.
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 3/112
Feel free to ask if you need further clarification or if you’d like to walk through any part of the integration
process in more detail.
okay so do one thing I will share you because like there's a lot of code in that module so I'll share
you the code module by module like I will share two modules code at once so here's the structure
gonna be first I'm gonna show you the schema that is there okay and after that I'm gonna be
sharing you the code of the modules the first one would be the product module and the second one
would be the listing module okay these two I'm gonna share to you keep that in memory and in that
next one I'm gonna be sharing you the collections plans and the catalog module so if you have a
complete idea about the code base right now so you can help me in creating the card for it
this will be the schema next is product listing and the collection then plan then ctalog module along
with the tags modules
enum fnb_exp_status {
ACTIVE
INACTIVE
DRAFT
ARCHIVED
@@schema("public")
}
enum classification {
Food
Beverage
Experience
Misc
@@schema("public")
}
enum experience_type {
INDOOR
OUTDOOR
@@schema("public")
}
enum rule_condition {
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 4/112
X_LT_Y
X_EQ_Y
X_OR_Y
@@schema("public")
}
model product {
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
name String
description String?
media Json?
classification classification?
remarks String?
sku String @unique // Unique stock keeping unit; options: 1-4
created_at DateTime @default(now())
updated_at DateTime @updatedAt
deleted_at DateTime?
status fnb_exp_status? @default(ACTIVE)
food_beverage_detail food_beverage_detail[]
experience_detail experience_detail[]
product_listing product_listing[]
product_tag product_tag[]
model food_beverage_detail {
product_id String @id @db.Uuid
product product @relation(fields: [product_id], references: [id])
is_vegan Boolean? @default(false)
is_vegetarian Boolean? @default(true)
is_nonvegetarian Boolean? @default(false)
is_eggetarian Boolean? @default(false)
is_gluten_free Boolean? @default(true)
is_sugar_free Boolean? @default(true)
is_low_calorie Boolean? @default(false)
contains_allergens String? // e.g., "Dairxy, Fish"
contains_nuts Boolean? @default(false)
contains_shellfish Boolean? @default(false)
contains_dairy Boolean? @default(true)
is_alcoholic Boolean? @default(false)
is_non_alcoholic Boolean? @default(true)
is_kid_friendly Boolean? @default(false)
is_halal Boolean? @default(true)
is_kosher Boolean? @default(false)
@@schema("public")
}
model experience_detail {
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 5/112
product_id String @id @db.Uuid
product product @relation(fields: [product_id], references: [id])
experience_type experience_type?
@@schema("public")
}
model product_listing {
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
product_id String @db.Uuid
listing_code String
sell_as_unit Boolean @default(false)
price_per_unit Float?
available Boolean @default(true)
available_units Int?
max_units_per_order Int?
original_price Float?
discount_price Float?
on_request_price Boolean @default(false)
version Int @default(1)
created_at DateTime @default(now())
updated_at DateTime @updatedAt
deleted_at DateTime?
model collection {
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
name String
description String?
listing_code String
display_order Int? @default(1)
created_by String?
created_at DateTime @default(now())
updated_at DateTime @updatedAt
deleted_at DateTime?
bundle_price Float?
is_temporary Boolean @default(false)
visible_from DateTime?
visible_to DateTime?
media Json?
is_available Boolean @default(true)
max_order_per_pax Int?
max_concurrent_pax_allowed Int?
time_slots Json?
classification String?
status fnb_exp_status? @default(ACTIVE)
listing Listing @relation(fields: [listing_code], references: [code])
collection_product collection_product[]
collection_rule collection_rule[]
collection_tag collection_tag[]
plan_collection plan_collection[]
@@index([is_available, listing_code])
@@schema("public")
}
model collection_product {
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
collection_id String @db.Uuid
listing_product_id String @db.Uuid
collection collection @relation(fields: [collection_id], references: [id])
product_listing product_listing @relation(fields: [listing_product_id], references: [id])
@@unique([collection_id, listing_product_id])
@@schema("public")
}
model collection_rule {
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
collection_id String @unique @db.Uuid
rule_type rule_condition
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 7/112
x Int
y Int
rule_parameters Json?
priority Int?
active Boolean @default(true)
collection collection @relation(fields: [collection_id], references: [id])
@@schema("public")
}
model plan {
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
name String
price Float?
listing_code String
banner Json?
display_order Int?
media Json?
created_by String?
created_at DateTime @default(now())
updated_at DateTime @updatedAt
deleted_at DateTime?
is_temporary Boolean @default(false)
available_days Json? // e.g., ["Mon", "Tue", "Wed", "Thu", "Fri"]
visible_from_date DateTime? // e.g., 2025-11-01T00:00:00.000Z for start of season
visible_to_date DateTime? // e.g., 2026-03-01T00:00:00.000Z for end of season
visible_from_time DateTime? @db.Time(0) // e.g., 06:00:00 for breakfast start
visible_to_time DateTime? @db.Time(0) // e.g., 10:00:00 for breakfast end
is_visible Boolean @default(true)
max_allowed Int?
status fnb_exp_status? @default(ACTIVE)
version Int @default(1)
listing Listing @relation(fields: [listing_code], references: [code])
catalog_plan catalog_plan[]
plan_collection plan_collection[]
@@schema("public")
}
model plan_collection {
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
plan_id String @db.Uuid
collection_id String @db.Uuid
@@unique([plan_id, collection_id])
@@schema("public")
}
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 8/112
model catalog {
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
name String
listing_code String
banner String?
display_order Int?
media Json?
created_by String?
created_at DateTime @default(now())
updated_at DateTime @updatedAt
deleted_at DateTime?
is_temporary Boolean @default(false)
visible_from_date DateTime? // e.g., start date of the season
visible_to_date DateTime? // e.g., end date of the season
visible_from_time DateTime? @db.Time(0) // e.g., 06:00:00 for start of daily availability
visible_to_time DateTime? @db.Time(0) // e.g., 10:00:00 for end of daily availability
is_visible Boolean @default(true)
max_allowed Int?
category String?
status fnb_exp_status? @default(ACTIVE)
listing Listing @relation(fields: [listing_code], references: [code])
catalog_plan catalog_plan[]
catalog_tag catalog_tag[]
@@unique([listing_code])
@@schema("public")
}
model catalog_plan {
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
catalog_id String @db.Uuid
plan_id String @db.Uuid
catalog catalog @relation(fields: [catalog_id], references: [id])
plan plan @relation(fields: [plan_id], references: [id])
@@unique([catalog_id, plan_id])
@@schema("public")
}
model tag {
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
name String @unique
description String?
media Json?
catogory classification?
product_tag product_tag[]
collection_tag collection_tag[]
catalog_tag catalog_tag[]
@@schema("public")
}
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 9/112
model product_tag {
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
product_id String @db.Uuid
tag_id String @db.Uuid
// Relations
product product @relation(fields: [product_id], references: [id])
tag tag @relation(fields: [tag_id], references: [id])
@@unique([product_id, tag_id])
@@schema("public")
}
model collection_tag {
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
collection_id String @db.Uuid
tag_id String @db.Uuid
collection collection @relation(fields: [collection_id], references: [id])
tag tag @relation(fields: [tag_id], references: [id])
@@unique([collection_id, tag_id])
@@schema("public")
}
model catalog_tag {
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
catalog_id String @db.Uuid
tag_id String @db.Uuid
catalog catalog @relation(fields: [catalog_id], references: [id])
tag tag @relation(fields: [tag_id], references: [id])
@@unique([catalog_id, tag_id])
@@schema("public")
}
enum order_status {
PAID
OVERPAID
FAILED
CREATED
PARTIAL_PAID
EXPIRED
ATTEMPTED
@@schema("public")
}
enum order_type {
BOOKING
FNB
MISC
MISC2
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 10/112
@@schema("public")
}
model payment_orders {
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
instrument_type String?
gtw String? @default("razorpay")
gtw_code String?
paid_amount String?
initiated_amount String?
booking_code String?
meta Json?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
paidAt DateTime?
status order_status @default(CREATED)
expiryAt DateTime?
order_type order_type
referenceId String?
referenceId2 String? @db.Uuid
referenceId3 String?
booking bookings_master? @relation(fields: [booking_code], references: [code])
executed_by_user String? @db.Uuid
user_executed users? @relation(fields: [executed_by_user], references: [id])
executed_by_guest String? @db.Uuid
guest_executed guests? @relation(fields: [executed_by_guest], references: [id])
@@schema("public")
payment_orders_entity payment_orders_entity[]
}
model payment_orders_entity {
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
payment_order_id String @db.Uuid
payment_orders payment_orders @relation(fields: [payment_order_id], references: [id])
@@schema("public")
payment_entity payment_entity[]
payment_entity_qr payment_entity_qr[]
}
model payment_entity {
id String @id
accept_partial Boolean
amount Int
amount_paid Int
callback_method String?
callback_url String?
cancelled_at DateTime? @db.Timestamp
created_at DateTime @db.Timestamp
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 11/112
currency String
description String?
expire_by DateTime? @db.Timestamp
expired_at DateTime? @db.Timestamp
first_min_partial_amount Int?
short_url String?
status String
updated_at DateTime @db.Timestamp
user_id String?
reference_id String @db.Uuid @unique
reminder_enable Boolean?
// Customer information
customer_contact String?
customer_email String?
customer_name String?
// Notify preferences
notify_email Boolean?
notify_sms Boolean?
@@schema("public")
}
model payment_entity_qr {
id String @id
entity String
created_at DateTime
name String
usage String
type String
image_url String
payment_amount Int
status String
description String?
fixed_amount Boolean
payments_amount_received Int
payments_count_received Int
notes Json?
customer_id String?
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 12/112
close_by DateTime?
closed_at DateTime?
close_reason String?
reference_id String @db.Uuid @unique
payment_orders_entity payment_orders_entity @relation(fields: [reference_id], references: [id])
@@schema("public")
}
enum Cart_Status{
PENDING
CHECKED_OUT
CANCELLED
COMPLETED
@@map("cart_status")
@@schema("public")
}
model Cart {
id String @id @default(uuid())
booking_code String @unique
status Cart_Status @default(PENDING)
subtotal Float
discount Float
tax Float
total Float
item_instructions String
created_at DateTime @default(now())
updated_at DateTime @default(now())
booking bookings_master @relation(fields: [booking_code], references: [code])
cartItems CartItem[]
@@map("cart")
@@schema("public")
}
model CartItem {
id String @id @default(uuid())
cart_id String
item_type String?
quantity Int
unit_price Float
total_price Float
order_json Json?
promotion_id String?
order_instructions String?
created_at DateTime @default(now())
updated_at DateTime @default(now())
cart Cart @relation(fields: [cart_id], references: [id])
@@map("cart_item")
@@schema("public")
}
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 13/112
enum fnb_exp_status {
ACTIVE
INACTIVE
DRAFT
ARCHIVED
@@schema("public")
}
enum classification {
Food
Beverage
Experience
Misc
@@schema("public")
}
enum experience_type {
INDOOR
OUTDOOR
@@schema("public")
}
enum rule_condition {
X_LT_Y
X_EQ_Y
X_OR_Y
@@schema("public")
}
model product {
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
name String
description String?
media Json?
classification classification?
remarks String?
sku String @unique // Unique stock keeping unit; options: 1-4
created_at DateTime @default(now())
updated_at DateTime @updatedAt
deleted_at DateTime?
status fnb_exp_status? @default(ACTIVE)
food_beverage_detail food_beverage_detail[]
experience_detail experience_detail[]
product_listing product_listing[]
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 14/112
product_tag product_tag[]
model food_beverage_detail {
product_id String @id @db.Uuid
product product @relation(fields: [product_id], references: [id])
is_vegan Boolean? @default(false)
is_vegetarian Boolean? @default(true)
is_nonvegetarian Boolean? @default(false)
is_eggetarian Boolean? @default(false)
is_gluten_free Boolean? @default(true)
is_sugar_free Boolean? @default(true)
is_low_calorie Boolean? @default(false)
contains_allergens String? // e.g., "Dairxy, Fish"
contains_nuts Boolean? @default(false)
contains_shellfish Boolean? @default(false)
contains_dairy Boolean? @default(true)
is_alcoholic Boolean? @default(false)
is_non_alcoholic Boolean? @default(true)
is_kid_friendly Boolean? @default(false)
is_halal Boolean? @default(true)
is_kosher Boolean? @default(false)
@@schema("public")
}
model experience_detail {
product_id String @id @db.Uuid
product product @relation(fields: [product_id], references: [id])
experience_type experience_type?
@@schema("public")
}
model product_listing {
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
product_id String @db.Uuid
listing_code String
sell_as_unit Boolean @default(false)
price_per_unit Float?
available Boolean @default(true)
available_units Int?
max_units_per_order Int?
original_price Float?
discount_price Float?
on_request_price Boolean @default(false)
version Int @default(1)
created_at DateTime @default(now())
updated_at DateTime @updatedAt
deleted_at DateTime?
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 15/112
visible_from_date DateTime? // e.g., start date of the season
visible_to_date DateTime? // e.g., end date of the season
visible_from_time DateTime? @db.Time(0) // e.g., 06:00:00 for start of daily availability
visible_to_time DateTime? @db.Time(0) // e.g., 10:00:00 for end of daily availability
available_days Json? // e.g., ["Mon", "Tue", "Wed", "Thu", "Fri"]
// Product flags
is_bestseller Boolean? @default(true)
is_chefs_special Boolean? @default(true)
is_local_cuisine Boolean? @default(false)
requires_preparation_time Boolean? @default(true)
preparation_time Int? @default(20)
is_in_high_demand Boolean? @default(true)
is_special_order Boolean? @default(false)
is_exclusive Boolean? @default(false)
is_trial Boolean? @default(false)
is_seasonal Boolean? @default(false) //change
is_gourmet Boolean? @default(true)
special_request_eligible Boolean? @default(true)
spice_intensity String?
is_customizable Boolean? @default(true)
customization_options String?
@@unique([product_id, listing_code])
@@schema("public")
}
model collection {
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
name String
description String?
listing_code String
display_order Int? @default(1)
created_by String?
created_at DateTime @default(now())
updated_at DateTime @updatedAt
deleted_at DateTime?
bundle_price Float?
is_temporary Boolean @default(false)
visible_from DateTime?
visible_to DateTime?
media Json?
is_available Boolean @default(true)
max_order_per_pax Int?
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 16/112
max_concurrent_pax_allowed Int?
time_slots Json?
classification String?
status fnb_exp_status? @default(ACTIVE)
listing Listing @relation(fields: [listing_code], references: [code])
collection_product collection_product[]
collection_rule collection_rule[]
collection_tag collection_tag[]
plan_collection plan_collection[]
@@index([is_available, listing_code])
@@schema("public")
}
model collection_product {
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
collection_id String @db.Uuid
listing_product_id String @db.Uuid
collection collection @relation(fields: [collection_id], references: [id])
product_listing product_listing @relation(fields: [listing_product_id], references: [id])
@@unique([collection_id, listing_product_id])
@@schema("public")
}
model collection_rule {
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
collection_id String @unique @db.Uuid
rule_type rule_condition
x Int
y Int
rule_parameters Json?
priority Int?
active Boolean @default(true)
collection collection @relation(fields: [collection_id], references: [id])
@@schema("public")
}
model plan {
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
name String
price Float?
listing_code String
banner Json?
display_order Int?
media Json?
created_by String?
created_at DateTime @default(now())
updated_at DateTime @updatedAt
deleted_at DateTime?
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 17/112
is_temporary Boolean @default(false)
available_days Json? // e.g., ["Mon", "Tue", "Wed", "Thu", "Fri"]
visible_from_date DateTime? // e.g., 2025-11-01T00:00:00.000Z for start of season
visible_to_date DateTime? // e.g., 2026-03-01T00:00:00.000Z for end of season
visible_from_time DateTime? @db.Time(0) // e.g., 06:00:00 for breakfast start
visible_to_time DateTime? @db.Time(0) // e.g., 10:00:00 for breakfast end
is_visible Boolean @default(true)
max_allowed Int?
status fnb_exp_status? @default(ACTIVE)
version Int @default(1)
listing Listing @relation(fields: [listing_code], references: [code])
catalog_plan catalog_plan[]
plan_collection plan_collection[]
@@schema("public")
}
model plan_collection {
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
plan_id String @db.Uuid
collection_id String @db.Uuid
@@unique([plan_id, collection_id])
@@schema("public")
}
model catalog {
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
name String
listing_code String
banner String?
display_order Int?
media Json?
created_by String?
created_at DateTime @default(now())
updated_at DateTime @updatedAt
deleted_at DateTime?
is_temporary Boolean @default(false)
visible_from_date DateTime? // e.g., start date of the season
visible_to_date DateTime? // e.g., end date of the season
visible_from_time DateTime? @db.Time(0) // e.g., 06:00:00 for start of daily availability
visible_to_time DateTime? @db.Time(0) // e.g., 10:00:00 for end of daily availability
is_visible Boolean @default(true)
max_allowed Int?
category String?
status fnb_exp_status? @default(ACTIVE)
listing Listing @relation(fields: [listing_code], references: [code])
catalog_plan catalog_plan[]
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 18/112
catalog_tag catalog_tag[]
@@unique([listing_code])
@@schema("public")
}
model catalog_plan {
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
catalog_id String @db.Uuid
plan_id String @db.Uuid
catalog catalog @relation(fields: [catalog_id], references: [id])
plan plan @relation(fields: [plan_id], references: [id])
@@unique([catalog_id, plan_id])
@@schema("public")
}
model tag {
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
name String @unique
description String?
media Json?
catogory classification?
product_tag product_tag[]
collection_tag collection_tag[]
catalog_tag catalog_tag[]
@@schema("public")
}
model product_tag {
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
product_id String @db.Uuid
tag_id String @db.Uuid
// Relations
product product @relation(fields: [product_id], references: [id])
tag tag @relation(fields: [tag_id], references: [id])
@@unique([product_id, tag_id])
@@schema("public")
}
model collection_tag {
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
collection_id String @db.Uuid
tag_id String @db.Uuid
collection collection @relation(fields: [collection_id], references: [id])
tag tag @relation(fields: [tag_id], references: [id])
@@unique([collection_id, tag_id])
@@schema("public")
}
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 19/112
model catalog_tag {
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
catalog_id String @db.Uuid
tag_id String @db.Uuid
catalog catalog @relation(fields: [catalog_id], references: [id])
tag tag @relation(fields: [tag_id], references: [id])
@@unique([catalog_id, tag_id])
@@schema("public")
}
enum order_status {
PAID
OVERPAID
FAILED
CREATED
PARTIAL_PAID
EXPIRED
ATTEMPTED
@@schema("public")
}
enum order_type {
BOOKING
FNB
MISC
MISC2
@@schema("public")
}
model payment_orders {
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
instrument_type String?
gtw String? @default("razorpay")
gtw_code String?
paid_amount String?
initiated_amount String?
booking_code String?
meta Json?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
paidAt DateTime?
status order_status @default(CREATED)
expiryAt DateTime?
order_type order_type
referenceId String?
referenceId2 String? @db.Uuid
referenceId3 String?
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 20/112
booking bookings_master? @relation(fields: [booking_code], references: [code])
executed_by_user String? @db.Uuid
user_executed users? @relation(fields: [executed_by_user], references: [id])
executed_by_guest String? @db.Uuid
guest_executed guests? @relation(fields: [executed_by_guest], references: [id])
@@schema("public")
payment_orders_entity payment_orders_entity[]
}
model payment_orders_entity {
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
payment_order_id String @db.Uuid
payment_orders payment_orders @relation(fields: [payment_order_id], references: [id])
@@schema("public")
payment_entity payment_entity[]
payment_entity_qr payment_entity_qr[]
}
model payment_entity {
id String @id
accept_partial Boolean
amount Int
amount_paid Int
callback_method String?
callback_url String?
cancelled_at DateTime? @db.Timestamp
created_at DateTime @db.Timestamp
currency String
description String?
expire_by DateTime? @db.Timestamp
expired_at DateTime? @db.Timestamp
first_min_partial_amount Int?
short_url String?
status String
updated_at DateTime @db.Timestamp
user_id String?
reference_id String @db.Uuid @unique
reminder_enable Boolean?
// Customer information
customer_contact String?
customer_email String?
customer_name String?
// Notify preferences
notify_email Boolean?
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 21/112
notify_sms Boolean?
@@schema("public")
}
model payment_entity_qr {
id String @id
entity String
created_at DateTime
name String
usage String
type String
image_url String
payment_amount Int
status String
description String?
fixed_amount Boolean
payments_amount_received Int
payments_count_received Int
notes Json?
customer_id String?
close_by DateTime?
closed_at DateTime?
close_reason String?
reference_id String @db.Uuid @unique
payment_orders_entity payment_orders_entity @relation(fields: [reference_id], references: [id])
@@schema("public")
}
enum Cart_Status{
PENDING
CHECKED_OUT
CANCELLED
COMPLETED
@@map("cart_status")
@@schema("public")
}
model Cart {
id String @id @default(uuid())
booking_code String @unique
status Cart_Status @default(PENDING)
subtotal Float
discount Float
tax Float
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 22/112
total Float
item_instructions String
created_at DateTime @default(now())
updated_at DateTime @default(now())
booking bookings_master @relation(fields: [booking_code], references: [code])
cartItems CartItem[]
@@map("cart")
@@schema("public")
}
model CartItem {
id String @id @default(uuid())
cart_id String
item_type String?
quantity Int
unit_price Float
total_price Float
order_json Json?
promotion_id String?
order_instructions String?
created_at DateTime @default(now())
updated_at DateTime @default(now())
cart Cart @relation(fields: [cart_id], references: [id])
@@map("cart_item")
@@schema("public")
}
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 23/112
based on collections or plans (for example, bundle pricing or rules defined in collection_rule), then
understanding these relationships is essential.
3. Payment Integration & Order Metadata:
• The payment_orders and related payment entities outline how orders are tracked and their
various statuses.
• Cart to Order Transition: The cart’s state will eventually need to translate into a payment order.
Fields such as subtotal, discount, tax, and total in your Cart model are computed based on the
items being added (via CartItem).
Now that I have a clear picture of the schema, the next step will be to understand how your modules
(especially product and listing) are implemented in code. This includes:
Next Steps
Based on the schema you shared, the next set of code (modules for product and listing) will enable me
to see the full context of how your system operates. This will help in designing a cart module that:
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 24/112
Integrates seamlessly with the existing product, listing, and other related modules,
Implements consistent data validation and transformation, and
Adheres to your established business rules for pricing, availability, and discounts.
Could you please share the implementation details (DTOs, controllers, services) for the Product and
Listing modules? This will allow me to provide a robust blueprint or guidance on designing the Cart
module to ensure smooth integration with your current architecture.
Feel free to post the code or any specific logic you wish to highlight, and I’ll review it accordingly.
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 25/112
@ApiPropertyOptional({ description: 'Is the product gluten-free?', default: true })
@IsOptional()
@IsBoolean()
is_gluten_free?: boolean;
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 26/112
@ApiPropertyOptional({ description: 'Is the product halal?', default: true })
@IsOptional()
@IsBoolean()
is_halal?: boolean;
@ApiPropertyOptional({
description: 'Description of the product',
example: 'Crispy fermented rice and lentil crepe'
})
@IsOptional()
@IsString()
description?: string;
@ApiPropertyOptional({
description: 'Media URLs as an array of strings',
type: [String],
example: [
'https://example.com/dosa1.jpg',
'https://example.com/dosa2.jpg'
]
})
@IsOptional()
@IsArray()
@IsString({ each: true })
media?: string[];
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 27/112
@ApiPropertyOptional({
enum: classification,
description: 'Classification of the product',
example: 'Food'
})
@IsOptional()
@IsEnum(classification)
classification?: classification;
@ApiPropertyOptional({
description: 'Remarks about the product',
example: 'Served with chutney and sambar'
})
@IsOptional()
@IsString()
remarks?: string;
@ApiPropertyOptional({
description: 'Optional: Unique SKU of the product (e.g., F-011). Auto-generated if omitted.',
example: 'F-011'
})
@IsOptional()
@IsString()
@Matches(/^(F|B|E|M)-\d{3,}$/, {
message: 'SKU must follow format: [F|B|E|M]- followed by 3+ digits (e.g., F-001)'
})
sku?: string; // Changed to optional type
@ApiPropertyOptional({
enum: fnb_exp_status,
description: 'Status of the product',
example: 'ACTIVE'
})
@IsOptional()
@IsEnum(fnb_exp_status)
status?: fnb_exp_status;
@ApiPropertyOptional({
description: 'List of tag IDs associated with the product',
type: [String],
example: ['tag-uuid-1', 'tag-uuid-2']
})
@IsOptional()
@IsArray()
@IsString({ each: true })
tagIds?: string[];
@ApiPropertyOptional({
description: 'Required for Food/Beverage products',
type: FoodBeverageDetailsDto,
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 28/112
example: {
is_nonvegetarian: false,
is_eggetarian: false,
is_vegetarian: false,
is_vegan: false,
is_gluten_free: false,
is_sugar_free: false,
is_low_calorie: false,
contains_allergens: 'None',
contains_nuts: false,
contains_shellfish: false,
contains_dairy: false,
is_alcoholic: false,
is_non_alcoholic: true,
is_kid_friendly: true,
is_halal: true,
is_kosher: true,
}
})
@ValidateIf(o => o.classification === 'Food' || o.classification === 'Beverage')
@IsNotEmptyObject()
foodBeverageDetails?: FoodBeverageDetailsDto;
@ApiPropertyOptional({
description: 'Required for Experience products',
type: ExperienceDetailsDto,
example: {
experience_type: 'INDOOR'
}
})
@ValidateIf(o => o.classification === 'Experience')
@IsNotEmptyObject()
experienceDetails?: ExperienceDetailsDto;
}
import { ApiPropertyOptional } from '@nestjs/swagger';
import { IsEnum, IsInt, IsOptional, IsString, Min } from 'class-validator';
import { Type } from 'class-transformer';
import { classification, fnb_exp_status } from '@prisma/client';
@ApiPropertyOptional({
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 29/112
description: 'Filter by product SKU',
example: 'E-018'
})
@IsOptional()
@IsString()
sku?: string;
@ApiPropertyOptional({
enum: classification,
description: 'Filter by product classification',
example: 'Experience'
})
@IsOptional()
@IsEnum(classification)
classification?: classification;
@ApiPropertyOptional({
enum: fnb_exp_status,
description: 'Filter by product status',
example: 'ACTIVE'
})
@IsOptional()
@IsEnum(fnb_exp_status)
status?: fnb_exp_status;
@ApiPropertyOptional({
description: 'Filter by tag name (case-insensitive)',
example: 'camping'
})
@IsOptional()
@IsString()
tag?: string;
@ApiPropertyOptional({
description: 'Filter by allergens (for Food/Beverage only)',
example: ''
})
@IsOptional()
@IsString()
allergen?: string;
@ApiPropertyOptional({
description: 'Filter vegetarian products only (for Food/Beverage only)',
example: true
})
@IsOptional()
@IsString()
isVegetarian?: string;
@ApiPropertyOptional({
description: 'Filter vegan products only (for Food/Beverage only)',
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 30/112
example: true
})
@IsOptional()
@IsString()
isVegan?: string;
@ApiPropertyOptional({
description: 'Page number (starts from 1)',
example: 1,
default: 1
})
@IsOptional()
@Type(() => Number)
@IsInt()
@Min(1)
page?: number = 1;
@ApiPropertyOptional({
description: 'Number of items per page',
example: 10,
default: 10
})
@IsOptional()
@Type(() => Number)
@IsInt()
@Min(1)
limit?: number = 10;
}
@ApiPropertyOptional({
description: 'Current page number'
})
page: number;
@ApiPropertyOptional({
description: 'Number of pages'
})
pages: number;
@ApiPropertyOptional({
description: 'Number of items per page'
})
limit: number;
@ApiPropertyOptional({
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 31/112
description: 'List of products'
})
data: any[];
}
import { PartialType } from '@nestjs/mapped-types';
import { CreateProductDto } from './create-product.dto';
@ApiTags('products')
@ApiBearerAuth("JWT-auth")
@Controller('products')
@UseGuards(JwtAuthGuard)
@ApiBearerAuth()
export class ProductController {
constructor(private readonly productService: ProductService) {}
@Get()
@ApiOperation({ summary: 'Get all products with optional filters' })
@ApiResponse({ status: 200, description: 'Paginated list of products' })
async findAll(@Query() query: SearchProductDto): Promise<PaginatedProducts> {
return this.productService.search(query);
}
@Get(':id')
@ApiOperation({ summary: 'Get a product by ID' })
@ApiResponse({ status: 200, description: 'Product details' })
@ApiResponse({ status: 404, description: 'Product not found' })
async findOne(@Param('id') id: string): Promise<ProductResponse> {
return this.productService.findOne(id);
}
@Post()
@ApiOperation({ summary: 'Create a new product' })
@ApiResponse({ status: 201, description: 'Product created' })
async create(@Body() createProductDto: CreateProductDto): Promise<ProductResponse> {
return this.productService.create(createProductDto);
}
@Put(':id')
@ApiOperation({ summary: 'Update a product' })
@ApiResponse({ status: 200, description: 'Product updated' })
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 32/112
@ApiResponse({ status: 404, description: 'Product not found' })
@ApiBody({
type: UpdateProductDto,
examples: {
updateFoodProduct: {
summary: 'Update a Food Product',
description: 'Example for updating a Food product when foodBeverageDetails is required.',
value: {
name: 'Updated Dosa',
foodBeverageDetails: {
is_vegetarian: true,
contains_allergens: 'None'
}
}
},
updateExperienceProduct: {
summary: 'Update an Experience Product',
description: 'Example for updating an Experience product when experienceDetails is required.',
value: {
name: 'Updated Jet Ski',
experienceDetails: {
experience_type: 'OUTDOOR'
}
}
}
}
})
async update(@Param('id') id: string, @Body() updateProductDto: UpdateProductDto):
Promise<ProductResponse> {
return this.productService.update(id, updateProductDto);
}
@Delete(':id')
@HttpCode(HttpStatus.NO_CONTENT)
@ApiOperation({ summary: 'Delete a product' })
@ApiResponse({ status: 204, description: 'Product deleted' })
@ApiResponse({ status: 404, description: 'Product not found' })
async delete(@Param('id') id: string): Promise<void> {
return this.productService.delete(id);
}
}
import { Module } from '@nestjs/common';
import { ProductService } from './products.service';
import { ProductController } from './products.controller';
import { PrismaModule } from '../../prisma/prisma.module';
@Module({
imports: [PrismaModule],
controllers: [ProductController],
providers: [ProductService],
})
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 33/112
export class ProductModule {}
import { Injectable, NotFoundException, ConflictException, BadRequestException } from
'@nestjs/common';
import { PrismaService } from '../../prisma/prisma.service';
import { product, food_beverage_detail, product_tag, classification, experience_detail } from
'@prisma/client';
import { CreateProductDto } from './dto/create-product.dto';
import { UpdateProductDto } from './dto/update-product.dto';
import { SearchProductDto } from './dto/search-product.dto';
@Injectable()
export class ProductService {
constructor(private prisma: PrismaService) {}
if (search) {
where.OR = [
{ name: { contains: search, mode: 'insensitive' } },
{ description: { contains: search, mode: 'insensitive' } },
{ remarks: { contains: search, mode: 'insensitive' } },
{ sku: { contains: search, mode: 'insensitive' } },
];
}
if (sku) {
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 35/112
where.sku = { contains: sku, mode: 'insensitive' };
}
if (classification) {
where.classification = classification;
}
if (status) {
where.status = status;
}
if (tag) {
where.product_tag = {
some: {
tag: {
name: { contains: tag, mode: 'insensitive' },
},
},
};
}
return {
data: mappedProducts,
total,
page,
limit,
pages: Math.ceil(total / limit),
};
}
if (!product) {
throw new NotFoundException(`Product with ID ${id} not found`);
}
return {
id: product.id,
name: product.name,
description: product.description,
media: Array.isArray(product.media)
? product.media
.filter((url): url is string => typeof url === 'string')
.map(url => ({
url,
type: this.determineMediaType(url),
}))
: [],
classification: product.classification,
remarks: product.remarks,
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 38/112
sku: product.sku,
status: product.status,
created_at: product.created_at,
updated_at: product.updated_at,
deleted_at: product.deleted_at,
food_beverage_detail: product.food_beverage_detail,
experience_detail: product.experience_detail,
product_tag: product.product_tag,
listing_count: product.product_listing.length,
listing_codes: product.product_listing.map(pl => pl.listing_code),
};
}
if (sku) {
const providedPrefix = sku.split('-')[0];
if (providedPrefix !== prefix) {
throw new BadRequestException(
`SKU prefix must be ${prefix} for classification ${createProductDto.classification}`
);
}
await this.validateSkuUnique(sku);
} else {
const existingSKUs = await this.prisma.product.findMany({
where: {
sku: {
startsWith: prefix + '-',
},
},
select: {
sku: true,
},
});
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 39/112
const numbers = existingSKUs.map((skuObj) => {
const numberPart = skuObj.sku.split('-')[1];
return parseInt(numberPart, 10);
});
const maxNumber = numbers.length > 0 ? Math.max(...numbers) : 0;
if (createProductDto.tagIds?.length) {
await tx.product_tag.createMany({
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 40/112
data: createProductDto.tagIds.map((tag_id) => ({
product_id: productData.id,
tag_id,
})),
});
}
if (updateProductDto.tagIds) {
await tx.product_tag.deleteMany({ where: { product_id: id } });
if (updateProductDto.tagIds.length) {
await tx.product_tag.createMany({
data: updateProductDto.tagIds.map(tag_id => ({
product_id: id,
tag_id
}))
});
}
}
2.listing
// create-product-listing.dto.ts
import {
IsString,
IsBoolean,
IsNumber,
IsOptional,
IsDateString,
IsArray,
IsJSON,
IsIn,
Validate,
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 42/112
} from 'class-validator';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { Transform } from 'class-transformer';
import { DaysOfWeek } from '../../types';
import { ValidAddOnOptions } from '../../validators/addon-options.validator';
@ApiProperty({
description:
'Listing code(s) to map these product(s) to. Accepts a single code or an array of codes.',
example: ['APU250326TDJNI2CT','VIL250407B6EN9PJD']
})
@Transform(({ value }) => (typeof value === 'string' ? [value] : value))
@IsArray()
@IsString({ each: true })
listing_codes: string[];
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 43/112
@ApiPropertyOptional({ description: 'Original price', example: 2999 })
@IsOptional()
@IsNumber()
original_price?: number;
@ApiPropertyOptional({
description: 'Days of the week when available',
example: [DaysOfWeek.Mon, DaysOfWeek.Wed, DaysOfWeek.Fri],
isArray: true,
enum: DaysOfWeek,
enumName: 'DaysOfWeek',
})
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 44/112
@IsOptional()
@IsArray()
@IsIn(DAYS_OF_WEEK, { each: true })
available_days?: DaysOfWeek[];
@ApiPropertyOptional({
description: 'Add-on options as JSON (e.g., {"Extra Cheese": 99})',
example: { "Extra Cheese": 99 },
})
@IsOptional()
@IsJSON()
@Validate(ValidAddOnOptions)
@Transform(({ value }) => (typeof value === 'string' ? value : JSON.stringify(value)))
add_on_options?: string;
@ApiPropertyOptional({
description: 'Filter by listing code',
example: 'APU250326TDJNI2CT',
})
@IsOptional()
@IsString()
listing_code?: string;
@ApiPropertyOptional({
description: 'Filter by product ID',
example: '1f265b36-4907-4608-bec5-535b6271e320',
})
@IsOptional()
@IsString()
product_id?: string;
@ApiPropertyOptional({
description: 'Filter by availability status',
example: true,
})
@IsOptional()
@IsBoolean()
available?: boolean;
@ApiPropertyOptional({
description: 'Filter by product SKU (from related product)',
example: 'F-028',
})
@IsOptional()
@IsString()
sku?: string;
@ApiPropertyOptional({
description: 'Filter by product classification (from related product)',
example: 'Food',
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 47/112
})
@IsOptional()
@IsString()
classification?: string;
@ApiPropertyOptional({
description: 'Page number (starts from 1)',
example: 1,
default: 1,
})
@IsOptional()
@Type(() => Number)
@IsInt()
@Min(1)
page?: number = 1;
@ApiPropertyOptional({
description: 'Number of items per page',
example: 10,
default: 10,
})
@IsOptional()
@Type(() => Number)
@IsInt()
@Min(1)
limit?: number = 10;
}
@ApiPropertyOptional({
description: 'Current page number',
})
page: number;
@ApiPropertyOptional({
description: 'Number of pages',
})
pages: number;
@ApiPropertyOptional({
description: 'Number of items per page',
})
limit: number;
@ApiPropertyOptional({
description: 'List of product listings',
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 48/112
})
data: any[];
}
// update-inventory.dto.ts
import { IsNumber } from 'class-validator';
import { ApiProperty } from '@nestjs/swagger';
@ApiTags('Product Listings')
@ApiBearerAuth('JWT-auth')
@Controller('product-listings')
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 49/112
@UseGuards(JwtAuthGuard)
export class ProductListingsController {
constructor(private readonly productListingsService: ProductListingsService) {}
@Post()
@ApiOperation({ summary: 'Create a new product-listing mapping' })
@ApiResponse({ status: 201, description: 'Product-listing created successfully' })
@ApiResponse({ status: 409, description: 'Conflict - Product-listing already exists' })
async create(@Body() createProductListingDto: CreateProductListingDto) {
return this.productListingsService.create(createProductListingDto);
}
@Get('search')
@ApiOperation({ summary: 'Search and filter product listings with pagination' })
@ApiResponse({
status: 200,
description: 'Paginated list of product listings',
type: PaginatedProductListingsResponse,
})
@ApiQuery({ name: 'search', required: false, type: String, description: 'General search term for
listing_code, product_id, and customization_options' })
@ApiQuery({ name: 'listing_code', required: false, type: String, description: 'Filter by listing code' })
@ApiQuery({ name: 'product_id', required: false, type: String, description: 'Filter by product ID' })
@ApiQuery({ name: 'available', required: false, type: Boolean, description: 'Filter by availability
status' })
@ApiQuery({ name: 'name', required: false, type: String, description: 'Search by product name (from
related product)' })
@ApiQuery({ name: 'sku', required: false, type: String, description: 'Filter by product SKU (from
related product)' })
@ApiQuery({ name: 'classification', required: false, type: String, description: 'Filter by product
classification (from related product)' })
@ApiQuery({ name: 'page', required: false, type: Number, description: 'Page number (default 1)' })
@ApiQuery({ name: 'limit', required: false, type: Number, description: 'Number of items per page
(default 10)' })
async search(@Query() searchDto: SearchProductListingDto):
Promise<PaginatedProductListingsResponse> {
return this.productListingsService.search(searchDto);
}
@Get(':id/inventory')
@ApiOperation({ summary: 'Get inventory for a product-listing' })
@ApiParam({
name: 'id',
description: 'Product-listing ID (must be a valid UUID)',
example: 'db055d77-a103-407b-b69c-78c6461b2e45',
})
@ApiResponse({ status: 200, description: 'Inventory details' })
@ApiResponse({ status: 404, description: 'Product-listing not found' })
@ApiResponse({ status: 400, description: 'Invalid UUID format' })
async getInventory(@Param('id', new ParseUUIDPipe()) id: string) {
const listing = await this.productListingsService.findOne(id);
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 50/112
return { available_units: listing.available_units };
}
@Put(':id')
@ApiOperation({ summary: 'Update a product-listing' })
@ApiParam({
name: 'id',
description: 'Product-listing ID (must be a valid UUID)',
example: 'db055d77-a103-407b-b69c-78c6461b2e45',
})
@ApiResponse({ status: 200, description: 'Product-listing updated successfully' })
@ApiResponse({ status: 404, description: 'Product-listing not found' })
@ApiResponse({ status: 400, description: 'Invalid UUID format' })
async update(
@Param('id', new ParseUUIDPipe()) id: string,
@Body() updateProductListingDto: UpdateProductListingDto,
){
return this.productListingsService.update(id, updateProductListingDto);
}
@Put(':id/inventory')
@ApiOperation({ summary: 'Update inventory for a product-listing' })
@ApiParam({
name: 'id',
description: 'Product-listing ID (must be a valid UUID)',
example: 'db055d77-a103-407b-b69c-78c6461b2e45',
})
@ApiResponse({ status: 200, description: 'Inventory updated successfully' })
@ApiResponse({ status: 404, description: 'Product-listing not found' })
@ApiResponse({ status: 400, description: 'Invalid UUID format' })
async updateInventory(
@Param('id', new ParseUUIDPipe()) id: string,
@Body() updateInventoryDto: UpdateInventoryDto,
){
return this.productListingsService.updateInventory(id, updateInventoryDto.available_units);
}
@Delete(':id')
@HttpCode(HttpStatus.NO_CONTENT)
@ApiOperation({ summary: 'Delete a product-listing' })
@ApiParam({
name: 'id',
description: 'Product-listing ID (must be a valid UUID)',
example: 'db055d77-a103-407b-b69c-78c6461b2e45',
})
@ApiResponse({ status: 204, description: 'Product-listing deleted successfully' })
@ApiResponse({ status: 404, description: 'Product-listing not found' })
@ApiResponse({ status: 400, description: 'Invalid UUID format' })
async delete(@Param('id', new ParseUUIDPipe()) id: string) {
return this.productListingsService.remove(id);
}
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 51/112
}
the next is the collections and the plan module and also I couldn't share the product listing and
service file I will shrere in the next message
CreateProductDto:
Defines mandatory fields like `name` and optional fields such as `description`, `media`, and
`remarks`.
Uses class-validator decorators to ensure that fields like SKU follow a specific format (e.g., `F-
001` for Food).
Service Logic
Controller Structure
REST Endpoints:
Endpoints are defined for listing, retrieving a single product, creation, updating, and deletion.
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 52/112
Endpoints are protected by the JWT guard, ensuring that only authenticated requests are
handled.
CreateProductListingDto:
This DTO accepts arrays of product IDs and listing codes to allow mapping multiple products
to one or more listings.
It incorporates various flags (e.g., `sell_as_unit`, `available`) and pricing fields
(`price_per_unit`, `original_price`, `discount_price`) along with dates/times for visibility.
It accepts add-on options as JSON with a custom validator (`ValidAddOnOptions`) to ensure the
format is correct.
Controller Logic
REST Endpoints:
Endpoints include creation, search (with pagination), inventory retrieval and update, and
deletion of product-listing mappings.
The controller uses validation pipes (e.g., `ParseUUIDPipe`) to enforce proper ID formats.
It integrates common CRUD operations with clear API documentation using Swagger
decorators.
1. Data Consistency:
When adding an item to the cart, you'll need to reference a specific product and its associated
listing(s) to determine the correct pricing, availability, and SKU.
Ensure that business logic applied in the Product and Listing modules (e.g., inventory checks,
discount application) is mirrored or referenced in the Cart module.
2. DTO & Validation Patterns:
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 53/112
Follow the pattern of using DTOs for input validation in the Cart module. For example, you'll
likely need a `CreateCartDto` and possibly a `CreateCartItemDto` that outline necessary fields
like product ID, listing code, quantity, unit price, and order instructions.
3. Service Logic Considerations:
Leverage transactions to update stock/inventory when moving items from the cart to a
confirmed order, similar to how product creation/updating is handled.
Validate that the products being added to the cart are still available, and check against the
related listing details for any special rules (like seasonal availability).
4. Endpoint Protection and Error Handling:
As seen in the Product and Listing controllers, use guards like `JwtAuthGuard` to protect Cart
endpoints.
Incorporate robust error handling (e.g., checking SKU uniqueness or inventory availability)
and clear error messages to the client.
Next Steps
To build a Cart module that ties into your existing system, the following additional pieces of
code/documentation would be valuable:
Details around promotion handling, special instructions, or dynamic pricing that may affect
cart behavior.
Once you share the collections and plan modules (and the remaining parts of the product listing service,
if possible), I can provide a more detailed blueprint or recommendations for your Cart module
implementation.
Feel free to send the next batch of modules (collections, plan, and additional listing service code)
whenever you're ready!
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 54/112
@Module({
imports: [PrismaModule],
controllers: [ProductListingsController],
providers: [ProductListingsService],
})
export class ProductListingsModule {}
import {
Injectable,
NotFoundException,
ConflictException,
BadRequestException,
} from '@nestjs/common';
import { PrismaService } from '../../prisma/prisma.service';
import { CreateProductListingDto } from './dto/create-product-listing.dto';
import { UpdateProductListingDto } from './dto/update-product-listing.dto';
import { product_listing } from '@prisma/client';
import { instanceToPlain } from 'class-transformer';
import {
SearchProductListingDto,
PaginatedProductListingsResponse,
} from './dto/search-product-listing.dto';
@Injectable()
export class ProductListingsService {
constructor(private readonly prisma: PrismaService) {}
async search(
searchDto: SearchProductListingDto,
): Promise<PaginatedProductListingsResponse> {
const {
listing_code,
product_id,
available,
name,
sku,
classification,
page = 1,
limit = 10,
} = searchDto;
const skip = (page - 1) * limit;
const where: any = { deleted_at: null };
if (listing_code) {
where.listing_code = { equals: listing_code };
}
if (product_id) {
where.product_id = { equals: product_id };
}
if (available !== undefined) {
where.available = available;
}
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 55/112
const productFilter: any = {};
if (name) {
productFilter.name = { contains: name, mode: 'insensitive' };
}
if (sku) {
productFilter.sku = { contains: sku, mode: 'insensitive' };
}
if (classification) {
productFilter.classification = { equals: classification };
}
if (Object.keys(productFilter).length > 0) {
where.product = { is: productFilter };
}
return {
data,
total,
page,
limit,
pages: Math.ceil(total / limit),
};
}
if (parsed.available_days) {
if (typeof parsed.available_days === 'string') {
try {
parsed.available_days = JSON.parse(parsed.available_days);
} catch (error) {
throw new BadRequestException('Invalid JSON for available_days');
}
} else if (!Array.isArray(parsed.available_days)) {
throw new BadRequestException('available_days must be an array');
}
}
if (parsed.add_on_options) {
if (typeof parsed.add_on_options === 'string') {
try {
parsed.add_on_options = JSON.parse(parsed.add_on_options);
} catch (error) {
throw new BadRequestException('Invalid JSON for add_on_options');
}
} else if (typeof parsed.add_on_options !== 'object') {
throw new BadRequestException('add_on_options must be a valid JSON object');
}
}
return parsed;
}
// product-listings.service.ts
// ...
// Create endpoint
async create(
createProductListingDto: CreateProductListingDto,
): Promise<product_listing | product_listing[]> {
const { product_ids, listing_codes } = createProductListingDto;
const parsedData = this.handleJsonFields(createProductListingDto);
const data = {
...parsedData,
product_id: pid,
listing_code: code,
version: 1,
created_at: new Date(),
updated_at: new Date(),
};
if (createdListings.length === 0) {
throw new ConflictException(
`All provided product-listings already exist for the specified product(s) and listing
code(s).`
);
}
// Return a single item if only one was created, else return the array
return createdListings.length === 1 ? createdListings[0] : createdListings;
});
}
// Bulk create
async bulkCreate(
createProductListingDtos: CreateProductListingDto[],
): Promise<product_listing[]> {
if (!Array.isArray(createProductListingDtos) || createProductListingDtos.length === 0) {
throw new BadRequestException('Request body must be a non-empty array of product listings');
}
delete parsedData.product_ids;
delete parsedData.listing_codes;
listingsToCreate.push({
...parsedData,
product_id: pid,
listing_code: code,
version: 1,
created_at: new Date(),
updated_at: new Date(),
});
}
}
}
if (
(plainDto.product_ids && newProductId !== existing.product_id) ||
(plainDto.listing_codes && newListingCode !== existing.listing_code)
){
if (await this.checkIfListingExists(newProductId, newListingCode)) {
throw new ConflictException(
`Product-listing already exists for product ${newProductId} with listing code
${newListingCode}`
);
}
}
collections module
// src/fnb/collections/dto/create-collection.dto.ts
import {
IsString, IsOptional, IsBoolean, IsNumber,
IsArray, IsEnum, ValidateNested, Length, Matches, Min, IsUUID
} from 'class-validator';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { Type } from 'class-transformer';
import { fnb_exp_status, rule_condition } from '@prisma/client';
@ApiPropertyOptional({ description: 'Description for the collection (e.g., "A selection of breakfast
items")' })
@IsOptional()
@IsString()
@Length(0, 500, { message: 'Description must be less than 500 characters' })
description?: string;
@ApiPropertyOptional({
description: 'Array of product listing IDs to include in the collection',
type: [String]
})
@IsOptional()
@IsArray()
@IsUUID('all', { each: true, message: 'Each product listing ID must be a valid UUID' })
product_listing_ids?: string[];
@ApiPropertyOptional({
description: 'Collection rules',
type: [CollectionRuleDto]
})
@IsOptional()
@IsArray()
@ValidateNested({ each: true })
@Type(() => CollectionRuleDto)
rules?: CollectionRuleDto[];
@ApiPropertyOptional({
description: 'Array of tag IDs associated with the collection',
type: [String]
})
@IsOptional()
@IsArray()
@IsUUID('all', { each: true, message: 'Each tag ID must be a valid UUID' })
tag_ids?: string[];
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 63/112
@ApiPropertyOptional({ description: 'Bundle price for the collection (must be non-negative)' })
@IsOptional()
@IsNumber()
@Min(0, { message: 'Bundle price must be non-negative' })
bundle_price?: number;
@ApiPropertyOptional({
description: 'Status of the collection',
enum: fnb_exp_status,
default: 'ACTIVE'
})
@IsOptional()
@IsEnum(fnb_exp_status)
status?: fnb_exp_status;
}
// src/fnb/collections/dto/update-collection.dto.ts
import { PartialType } from '@nestjs/mapped-types';
import { CreateCollectionDto } from './create-collection.dto';
@ApiTags('collections')
@ApiBearerAuth("JWT-auth")
@Controller('collections')
@UseGuards(JwtAuthGuard)
export class CollectionsController {
constructor(private readonly service: CollectionsService) {}
@Post()
@ApiOperation({ summary: 'Create a new collection' })
@ApiResponse({
status: 201,
description: 'Collection created successfully',
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 64/112
schema: {
example: {
id: "550e8400-e29b-41d4-a716-446655440000",
name: "Breakfast Bundle",
description: "A selection of breakfast items",
listing_code: "PROP123",
bundle_price: 25.99,
is_temporary: false,
status: "ACTIVE",
created_at: "2023-10-01T10:00:00.000Z",
updated_at: "2023-10-01T10:00:00.000Z",
deleted_at: null,
collection_product: [
{
id: "550e8400-e29b-41d4-a716-446655440001",
collection_id: "550e8400-e29b-41d4-a716-446655440000",
listing_product_id: "550e8400-e29b-41d4-a716-446655440002",
product_listing: {
id: "550e8400-e29b-41d4-a716-446655440002",
product_id: "550e8400-e29b-41d4-a716-446655440003",
listing_code: "PROP123",
price_per_unit: 10.99,
available: true
}
}
],
collection_rule: [
{
id: "550e8400-e29b-41d4-a716-446655440004",
collection_id: "550e8400-e29b-41d4-a716-446655440000",
rule_type: "X_LT_Y",
x: 2,
y: 5,
priority: 1,
active: true
}
],
collection_tag: [
{
id: "550e8400-e29b-41d4-a716-446655440005",
collection_id: "550e8400-e29b-41d4-a716-446655440000",
tag_id: "550e8400-e29b-41d4-a716-446655440006",
tag: { id: "550e8400-e29b-41d4-a716-446655440006", name: "Breakfast" }
}
]
}
}
})
@ApiResponse({
status: 404,
description: 'Listing or related entities not found',
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 65/112
schema: {
example: {
message: "Listing with code PROP123 not found",
error: "Not Found",
statusCode: 404
}
}
})
@ApiResponse({
status: 409,
description: 'Conflict with product listings or tags',
schema: {
example: {
message: "Some product listings do not exist or do not belong to the specified listing",
error: "Conflict",
statusCode: 409
}
}
})
@ApiResponse({
status: 500,
description: 'Internal server error',
schema: {
example: {
message: "Failed to create collection",
error: "Internal Server Error",
statusCode: 500
}
}
})
async create(@Body() dto: CreateCollectionDto) {
return this.service.create(dto);
}
@Get()
@ApiOperation({ summary: 'Get all collections' })
@ApiQuery({ name: 'listing_code', required: false, type: String, description: 'Filter by listing code' })
@ApiResponse({
status: 200,
description: 'List of collections',
schema: {
example: [
{
id: "550e8400-e29b-41d4-a716-446655440000",
name: "Breakfast Bundle",
description: "A selection of breakfast items",
listing_code: "PROP123",
bundle_price: 25.99,
is_temporary: false,
status: "ACTIVE",
created_at: "2023-10-01T10:00:00.000Z",
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 66/112
updated_at: "2023-10-01T10:00:00.000Z",
deleted_at: null,
collection_product: [],
collection_rule: [],
collection_tag: []
}
]
}
})
async findAll(@Query('listing_code') listingCode?: string) {
return this.service.findAll(listingCode);
}
@Get(':id')
@ApiOperation({ summary: 'Get collection by ID' })
@ApiResponse({
status: 200,
description: 'Collection details',
schema: {
example: {
id: "550e8400-e29b-41d4-a716-446655440000",
name: "Breakfast Bundle",
description: "A selection of breakfast items",
listing_code: "PROP123",
bundle_price: 25.99,
is_temporary: false,
status: "ACTIVE",
created_at: "2023-10-01T10:00:00.000Z",
updated_at: "2023-10-01T10:00:00.000Z",
deleted_at: null,
collection_product: [],
collection_rule: [],
collection_tag: []
}
}
})
@ApiResponse({
status: 404,
description: 'Collection not found',
schema: {
example: {
message: "Collection with ID 550e8400-e29b-41d4-a716-446655440000 not found",
error: "Not Found",
statusCode: 404
}
}
})
async findOne(@Param('id') id: string) {
return this.service.findOne(id);
}
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 67/112
@Put(':id')
@ApiOperation({ summary: 'Update a collection' })
@ApiResponse({
status: 200,
description: 'Collection updated successfully',
schema: {
example: {
id: "550e8400-e29b-41d4-a716-446655440000",
name: "Updated Breakfast Bundle",
description: "Updated selection of breakfast items",
listing_code: "PROP123",
bundle_price: 29.99,
is_temporary: false,
status: "ACTIVE",
created_at: "2023-10-01T10:00:00.000Z",
updated_at: "2023-10-02T12:00:00.000Z",
deleted_at: null,
collection_product: [],
collection_rule: [],
collection_tag: []
}
}
})
@ApiResponse({
status: 404,
description: 'Collection or related entities not found',
schema: {
example: {
message: "Collection with ID 550e8400-e29b-41d4-a716-446655440000 not found",
error: "Not Found",
statusCode: 404
}
}
})
async update(@Param('id') id: string, @Body() dto: UpdateCollectionDto) {
return this.service.update(id, dto);
}
@Delete(':id')
@HttpCode(HttpStatus.NO_CONTENT)
@ApiOperation({ summary: 'Delete a collection (soft delete)' })
@ApiResponse({ status: 204, description: 'Collection deleted successfully' })
@ApiResponse({
status: 404,
description: 'Collection not found',
schema: {
example: {
message: "Collection with ID 550e8400-e29b-41d4-a716-446655440000 not found",
error: "Not Found",
statusCode: 404
}
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 68/112
}
})
async remove(@Param('id') id: string) {
return this.service.remove(id);
}
}
// src/fnb/collections/collections.module.ts
import { Module } from '@nestjs/common';
import { CollectionsService } from './collections.service';
import { CollectionsController } from './collections.controller';
import { PrismaModule } from '../../prisma/prisma.module';
@Module({
imports: [PrismaModule],
controllers: [CollectionsController],
providers: [CollectionsService],
})
export class CollectionsModule {}
import {
Injectable, NotFoundException, ConflictException, InternalServerErrorException, Logger
} from '@nestjs/common';
import { PrismaService } from '../../prisma/prisma.service';
import {
collection, collection_product, collection_rule,
collection_tag, product_listing, tag, product, food_beverage_detail, product_tag
} from '@prisma/client';
import { CreateCollectionDto } from './dto/create-collection.dto';
import { UpdateCollectionDto } from './dto/update-collection.dto';
@Injectable()
export class CollectionsService {
private readonly logger = new Logger(CollectionsService.name);
if (dto.product_listing_ids?.length) {
this.logger.debug(`Validating product listing IDs: ${dto.product_listing_ids}`);
const listings = await this.prisma.product_listing.findMany({
where: {
id: { in: dto.product_listing_ids },
listing_code: listingCode
}
});
if (listings.length !== dto.product_listing_ids.length) {
this.logger.warn(`Some product listings do not exist or do not belong to listing
${listingCode}`);
throw new ConflictException(
'Some product listings do not exist or do not belong to the specified listing'
);
}
}
if (dto.tag_ids?.length) {
this.logger.debug(`Validating tag IDs: ${dto.tag_ids}`);
const tags = await this.prisma.tag.findMany({
where: { id: { in: dto.tag_ids } }
});
if (tags.length !== dto.tag_ids.length) {
this.logger.warn(`Some tags not found: ${dto.tag_ids}`);
throw new NotFoundException('One or more tags not found');
}
}
}
await this.validateListingCode(dto.listing_code);
await this.validateRelations(dto, dto.listing_code);
try {
const result = await this.prisma.$transaction(async (tx) => {
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 70/112
const { product_listing_ids, rules, tag_ids, ...collectionData } = dto;
const collectionToCreate = {
...collectionData,
status: collectionData.status ?? 'ACTIVE',
created_at: new Date(),
updated_at: new Date(),
};
if (product_listing_ids?.length) {
this.logger.debug(`Creating collection products for collection ID: ${collection.id}`);
await tx.collection_product.createMany({
data: product_listing_ids.map(listing_id => ({
collection_id: collection.id,
listing_product_id: listing_id
}))
});
}
if (rules?.length) {
this.logger.debug(`Creating collection rules for collection ID: ${collection.id}`);
await tx.collection_rule.createMany({
data: rules.map(rule => ({
collection_id: collection.id,
...rule,
active: true
}))
});
}
if (tag_ids?.length) {
this.logger.debug(`Creating collection tags for collection ID: ${collection.id}`);
await tx.collection_tag.createMany({
data: tag_ids.map(tag_id => ({
collection_id: collection.id,
tag_id
}))
});
}
if (!newCollection) {
this.logger.error(`Failed to find newly created collection with ID: ${collection.id}`);
throw new InternalServerErrorException('Failed to retrieve newly created collection');
}
return newCollection;
});
if (!collection) {
this.logger.warn(`Collection with ID ${id} not found`);
throw new NotFoundException(`Collection with ID ${id} not found`);
}
try {
const result = await this.prisma.$transaction(async (tx) => {
const { product_listing_ids, rules, tag_ids, ...collectionData } = dto;
await tx.collection.update({
where: { id },
data: {
...collectionData,
updated_at: new Date()
}
});
return this.findOne(id);
});
plan
import { IsString, IsOptional, IsNumber, IsBoolean, IsInt, IsArray, IsEnum } from 'class-validator';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { fnb_exp_status } from '@prisma/client';
@ApiPropertyOptional({ description: 'Available days for the plan', example: ['Mon', 'Tue'] })
@IsOptional()
@IsArray()
available_days?: string[];
@ApiPropertyOptional({
description: 'Visibility start date',
example: '2025-01-01T00:00:00Z',
})
@IsOptional()
visible_from_date?: Date;
@ApiPropertyOptional({
description: 'Visibility end date',
example: '2025-12-31T23:59:59Z',
})
@IsOptional()
visible_to_date?: Date;
@ApiPropertyOptional({
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 76/112
description: 'Visibility start time in HH:MM:SS format',
example: '06:00:00',
})
@IsOptional()
@IsString()
visible_from_time?: string;
@ApiPropertyOptional({
description: 'Visibility end time in HH:MM:SS format',
example: '10:00:00',
})
@IsOptional()
@IsString()
visible_to_time?: string;
@ApiPropertyOptional({
description: 'List of collection IDs to include in the plan',
type: [String],
})
@IsOptional()
@IsArray()
@IsString({ each: true })
collection_ids?: string[];
}
import { PartialType } from '@nestjs/mapped-types';
import { CreatePlanDto } from './create-plan.dto';
@ApiTags('plans')
@Controller('plans')
@ApiBearerAuth('JWT-auth')
@UseGuards(JwtAuthGuard)
export class PlanController {
constructor(private readonly planService: PlanService) {}
@Get()
@ApiOperation({ summary: 'Get all plans, optionally filtered by listing code' }) // Updated summary
@ApiQuery({ // Added ApiQuery decorator for the new parameter
name: 'listing_code',
required: false, // Make it optional
type: String,
description: 'Filter plans by a specific listing code.',
example: 'APU250326TDJNI2CT' // Example usage
})
@ApiResponse({ status: 200, description: 'List of plans' })
async findAll(
// Add @Query decorator to accept the listing_code
@Query('listing_code') listingCode?: string
): Promise<plan[]> {
// Pass the listingCode (or undefined if not provided) to the service method
// The default value handling will be managed in the service or by client assumption
// Or you could set the default explicitly here if needed:
// const codeToFilter = listingCode ?? 'APU250326TDJNI2CT';
// For now, let's pass it as potentially undefined and let the service handle it
return this.planService.findAll(listingCode);
}
@Get(':id')
@ApiOperation({ summary: 'Get a plan by ID' })
@ApiResponse({ status: 200, description: 'Plan details' })
@ApiResponse({ status: 404, description: 'Plan not found' })
async findOne(@Param('id') id: string): Promise<plan> {
// No changes needed here
return this.planService.findOne(id);
}
@Post()
@ApiOperation({ summary: 'Create a new plan' })
@ApiResponse({ status: 201, description: 'Plan created' })
async create(@Body() createPlanDto: CreatePlanDto): Promise<plan> {
// No changes needed here
return this.planService.create(createPlanDto);
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 78/112
}
@Put(':id')
@ApiOperation({ summary: 'Update a plan' })
@ApiResponse({ status: 200, description: 'Plan updated' })
@ApiResponse({ status: 404, description: 'Plan not found' })
async update(@Param('id') id: string, @Body() updatePlanDto: UpdatePlanDto): Promise<plan> {
// No changes needed here
return this.planService.update(id, updatePlanDto);
}
@Delete(':id')
@HttpCode(HttpStatus.NO_CONTENT)
@ApiOperation({ summary: 'Delete a plan' })
@ApiResponse({ status: 204, description: 'Plan deleted' })
@ApiResponse({ status: 404, description: 'Plan not found' })
async delete(@Param('id') id: string): Promise<void> {
// No changes needed here
return this.planService.delete(id);
}
}
import { Module } from '@nestjs/common';
import { PlanService } from './plans.service';
import { PlanController } from './plans.controller';
import { PrismaModule } from '../../prisma/prisma.module';
@Module({
imports: [PrismaModule],
controllers: [PlanController],
providers: [PlanService],
})
export class PlanModule {}
next is the catalog module and the tag module ill also share the plan service file in the next message
Products Module:
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 79/112
Handles product details (name, description, media, SKU, classification) and conditional logic
for food/beverage versus experience products.
Implements SKU generation, uniqueness checks, and related entity creation (e.g.,
food_beverage_detail or experience_detail).
Product Listings Module:
Maps products to specific listings (using listing codes).
Includes pricing details such as price per unit, discount price, availability, available units, and
add-on options.
Provides comprehensive search and inventory update functionalities.
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 80/112
};
}[];
};
@Injectable()
export class PlanService {
constructor(private prisma: PrismaService) {}
return this.prisma.plan.findMany({
where, // Apply the where clause
include: { // Keep the existing includes
listing: { include: { product_listing: true } },
catalog_plan: { include: { catalog: true } },
plan_collection: {
include: {
collection: {
include: {
collection_product: {
include: {
product_listing: {
include: {
product: {
include: {
food_beverage_detail: true,
product_tag: true
}
}
}
},
},
},
collection_rule: true,
collection_tag: { include: { tag: true } },
},
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 81/112
},
},
},
},
}) as Promise<PlanWithRelations[]>; // Keep the type assertion
}
if (!planData) {
throw new NotFoundException(`Plan with ID ${id} not found`);
}
return planData;
}
const {
collection_ids,
visible_from_time,
visible_to_time,
...planData
} = createPlanDto;
const data = {
...planData,
visible_from_time: this.parseTime(visible_from_time),
visible_to_time: this.parseTime(visible_to_time),
};
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 83/112
const createdPlan = await this.prisma.plan.create({ data });
if (collection_ids?.length) {
await this.prisma.plan_collection.createMany({
data: collection_ids.map(collectionId => ({
plan_id: createdPlan.id,
collection_id: collectionId,
})),
});
}
return this.findOne(createdPlan.id);
}
if (updatePlanDto.collection_ids) {
// Use the potentially updated or existing listing code for validation
await this.validatePlanCollections(planListingCode, updatePlanDto.collection_ids);
}
const {
collection_ids,
visible_from_time,
visible_to_time,
...updateData
} = updatePlanDto;
await this.prisma.plan.update({
where: { id },
data,
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 84/112
});
return this.findOne(id);
}
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 86/112
@ApiPropertyOptional({ enum: fnb_exp_status, description: 'Status of the catalog' })
@IsOptional()
@IsEnum(fnb_exp_status)
status?: fnb_exp_status;
@ApiPropertyOptional({ description: 'List of plan IDs associated with the catalog', type: [String] })
@IsOptional()
@IsArray()
@IsString({ each: true })
planIds?: string[];
@ApiPropertyOptional({ description: 'List of tag IDs associated with the catalog', type: [String] })
@IsOptional()
@IsArray()
@IsString({ each: true })
tagIds?: string[];
}
import { PartialType } from '@nestjs/mapped-types';
import { CreateCatalogDto } from './create-catalog.dto';
@ApiTags('catalogs')
@Controller('catalogs')
@ApiBearerAuth("JWT-auth")
@UseGuards(JwtAuthGuard)
@ApiBearerAuth()
export class CatalogController {
constructor(private readonly catalogService: CatalogService) {}
@Get()
@ApiOperation({ summary: 'Get all catalogs' })
@ApiResponse({ status: 200, description: 'List of catalogs' })
async findAll(): Promise<catalog[]> {
return this.catalogService.findAll();
}
@Get(':id')
@ApiOperation({ summary: 'Get a catalog by ID' })
@ApiResponse({ status: 200, description: 'Catalog details'})
@ApiResponse({ status: 404, description: 'Catalog not found' })
async findOne(@Param('id') id: string): Promise<catalog> {
return this.catalogService.findOne(id);
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 87/112
}
@Get('code/:listing_code')
@ApiOperation({ summary: 'Get a catalog by listing code' })
@ApiResponse({ status: 200, description: 'Catalog details'})
@ApiResponse({ status: 404, description: 'Catalog not found' })
async findByListingCode(@Param('listing_code') listing_code: string): Promise<catalog> {
return this.catalogService.findByListingCode(listing_code);
}
@Post()
@ApiOperation({ summary: 'Create a new catalog' })
@ApiResponse({ status: 201, description: 'Catalog created' })
async create(@Body() createCatalogDto: CreateCatalogDto): Promise<catalog> {
return this.catalogService.create(createCatalogDto);
}
@Put(':id')
@ApiOperation({ summary: 'Update a catalog' })
@ApiResponse({ status: 200, description: 'Catalog updated' })
@ApiResponse({ status: 404, description: 'Catalog not found' })
async update(@Param('id') id: string, @Body() updateCatalogDto: UpdateCatalogDto):
Promise<catalog> {
return this.catalogService.update(id, updateCatalogDto);
}
@Delete(':id')
@HttpCode(HttpStatus.NO_CONTENT)
@ApiOperation({ summary: 'Delete a catalog' })
@ApiResponse({ status: 204, description: 'Catalog deleted' })
@ApiResponse({ status: 404, description: 'Catalog not found' })
async delete(@Param('id') id: string): Promise<void> {
return this.catalogService.delete(id);
}
}
import { Module } from '@nestjs/common';
import { CatalogService } from './catalogs.service';
import { CatalogController } from './catalogs.controller';
import { PrismaModule } from '../../prisma/prisma.module';
@Module({
imports: [PrismaModule],
controllers: [CatalogController],
providers: [CatalogService],
})
export class CatalogModule {}
import { Injectable, NotFoundException, BadRequestException } from '@nestjs/common';
import { PrismaService } from '../../prisma/prisma.service';
import { catalog } from '@prisma/client';
import { CreateCatalogDto } from './dto/create-catalog.dto';
import { UpdateCatalogDto } from './dto/update-catalog.dto';
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 88/112
import { Prisma } from '@prisma/client';
@Injectable()
export class CatalogService {
constructor(private prisma: PrismaService) {}
try {
const catalog = await this.prisma.catalog.create({
data: dataToCreate,
});
return this.findOne(catalog.id);
} catch (error) {
if (error instanceof Prisma.PrismaClientKnownRequestError) {
if (error.code === 'P2002') {
throw new BadRequestException('Unique constraint violation, e.g., duplicate listing_code');
} else if (error.code === 'P2003') {
throw new BadRequestException('Foreign key constraint violation');
}
}
throw error;
}
}
await this.prisma.catalog.update({
where: { id },
data: dataToUpdate,
});
if (planIds) {
await this.prisma.catalog_plan.deleteMany({ where: { catalog_id: id } });
if (planIds.length > 0) {
await this.prisma.catalog_plan.createMany({
data: planIds.map(plan_id => ({ catalog_id: id, plan_id })),
});
}
}
if (tagIds) {
await this.prisma.catalog_tag.deleteMany({ where: { catalog_id: id } });
if (tagIds.length > 0) {
await this.prisma.catalog_tag.createMany({
data: tagIds.map(tag_id => ({ catalog_id: id, tag_id })),
});
}
}
return this.findOne(id);
}
await this.prisma.catalog_plan.deleteMany({
where: { catalog_id: id },
});
await this.prisma.catalog_tag.deleteMany({
where: { catalog_id: id },
});
await this.prisma.catalog.delete({
where: { id },
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 93/112
});
}
}
import { IsString, IsOptional, IsEnum } from 'class-validator';
import { ApiProperty } from '@nestjs/swagger';
@ApiProperty({ description: 'Description of the tag', required: false, example: 'Suitable for vegans'
})
@IsOptional()
@IsString()
description?: string;
@ApiProperty({
description: 'Category of the tag',
required: false,
enum: ClassificationEnum,
example: ClassificationEnum.Food,
})
@IsOptional()
@IsEnum(ClassificationEnum)
catogory?: ClassificationEnum;
}
import { ApiPropertyOptional } from '@nestjs/swagger';
import { IsOptional, IsString } from 'class-validator';
@ApiTags('tags')
@ApiBearerAuth("JWT-auth")
@Controller('tags')
@UseGuards(JwtAuthGuard)
@ApiBearerAuth()
export class TagController {
constructor(private readonly tagService: TagService) {}
@Get()
@ApiOperation({ summary: 'Get all tags' })
@ApiResponse({ status: 200, description: 'List of tags'})
async findAll(@Query() query: TagQueryDto): Promise<tag[]> {
return this.tagService.findAll(query);
}
@Get(':id')
@ApiOperation({ summary: 'Get a tag by ID' })
@ApiResponse({ status: 200, description: 'Tag details' })
@ApiResponse({ status: 404, description: 'Tag not found' })
async findOne(@Param('id') id: string): Promise<tag> {
return this.tagService.findOne(id);
}
@Post()
@ApiOperation({ summary: 'Create a new tag' })
@ApiResponse({ status: 201, description: 'Tag created' })
async create(@Body() createTagDto: CreateTagDto): Promise<tag> {
return this.tagService.create(createTagDto);
}
@Put(':id')
@ApiOperation({ summary: 'Update a tag' })
@ApiResponse({ status: 200, description: 'Tag updated'})
@ApiResponse({ status: 404, description: 'Tag not found' })
async update(@Param('id') id: string, @Body() updateTagDto: UpdateTagDto): Promise<tag> {
return this.tagService.update(id, updateTagDto);
}
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 95/112
@Delete(':id')
@HttpCode(HttpStatus.NO_CONTENT)
@ApiOperation({ summary: 'Delete a tag' })
@ApiResponse({ status: 204, description: 'Tag deleted' })
@ApiResponse({ status: 404, description: 'Tag not found' })
async delete(@Param('id') id: string): Promise<void> {
return this.tagService.delete(id);
}
}
import { Module } from '@nestjs/common';
import { TagService } from './tags.service';
import { TagController } from './tags.controller';
import { PrismaModule } from '../../prisma/prisma.module';
@Module({
imports: [PrismaModule],
controllers: [TagController],
providers: [TagService],
})
export class TagModule {}
import { Injectable, NotFoundException } from '@nestjs/common';
import { PrismaService } from '../../prisma/prisma.service';
import { tag } from '@prisma/client';
import { CreateTagDto } from './dto/create-tag.dto';
import { UpdateTagDto } from './dto/update-tag.dto';
import { TagQueryDto } from './dto/tag-query.dto';
@Injectable()
export class TagService {
constructor(private prisma: PrismaService) {}
now additional info for the cart that I've designed the json should be like the one in the example
Table Listing {
code TEXT [pk]
masterName TEXT
parentCode TEXT
alias TEXT
alias2 TEXT
alias3 TEXT
units INT [default: 1]
propertyType TEXT
listingType TEXT [default: "PHYSICAL"]
minMaxUnitBlock INT [default: 0]
createdAt TIMESTAMP
updatedAt TIMESTAMP
location TEXT
locationMeta JSON
}
Table bookings_master {
id UUID [default: "gen_random_uuid()"]
code TEXT [pk]
listingCode TEXT
city TEXT
meta JSON
}
Table cart {
id UUID [pk, default: "gen_random_uuid()"]
booking_code TEXT [unique, ref: > bookings_master.code]
status cart_status [default: "PENDING"]
subtotal FLOAT // Sum of all cart item totals
discount FLOAT // Total discount applied via rules/promotions
tax FLOAT // Tax applied to the cart
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 97/112
total FLOAT // Final payable total = subtotal - discount + tax
item_instructions TEXT // Special handling instructions for the order
created_at TIMESTAMP [default: "now()"]
updated_at TIMESTAMP [default: "now()"]
}
Table cart_item {
id UUID [pk, default: "gen_random_uuid()"]
cart_id UUID [ref: > cart.id]
item_type cart_item_type
quantity INT
unit_price FLOAT // Final unit price after any adjustments
total_price FLOAT // Computed as unit_price * quantity
/*
{
"plan": {
"id": "plan-uuid",
"name": "Meal Plan Name",
"price": 300,
"collections": [
{
"id": "collection-uuid-1",
"name": "Breakfast",
"rule": "Any 2 of 3",
"listing_items": [
{
"id": "listing-item-uuid-1",
"name": "Samosa",
"quantity": 1
},
{
"id": "listing-item-uuid-2",
"name": "Tea",
"quantity": 1
}
]
},
{
"id": "collection-uuid-2",
"name": "Dinner",
"rule": "Any 2 of 4",
"listing_items": [
{
"id": "listing-item-uuid-3",
"name": "Paneer",
"quantity": 1
},
{
"id": "listing-item-uuid-4",
"name": "Dal",
"quantity": 1
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 98/112
}
]
}
]
},
"pax": 3
scheduled_at :
}
*/
order_json JSON // Contains complete plan/collection structure with items
promotion_id UUID // Reference to the promotion applied to this item
order_instructions text
created_at TIMESTAMP [default: "now()"]
updated_at TIMESTAMP [default: "now()"]
}
now deeply analyze all the module and give me an blueprint for the cart as detailed as possible
Cart
Fields:
`id` (UUID, primary key)
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 99/112
`id` (UUID, primary key)
`cart_id` (foreign key to cart)
`item_type`(enum such as PLAN, COLLECTION, SINGLE_PRODUCT – this helps
determine how to process the item)
`quantity` (INT)
`unit_price` (FLOAT – final price per unit after adjustments)
`total_price` (FLOAT – typically computed as `unit_price * quantity`)
`order_json` (JSON – stores the nested structure describing the complete plan/collection
details)
`promotion_id` (UUID – if a promotion is applied)
`order_instructions` (TEXT – any specific instructions for this item)
Timestamps (`created_at`, `updated_at`)
Listing:
The `Listing` model (with fields such as `code`, `masterName`, `parentCode`, etc.) is a key
reference. Both `bookings_master.listingCode` and certain operations in product listings and
collections rely on it.
Bookings Master:
It is used for validating the booking context and ensuring that the `booking_code` of the cart
aligns with an existing booking.
Plans, Collections, Catalogs:
Your existing modules already group items, apply discounts and bundled pricing rules. The
Cart’s order_json is designed to capture the hierarchical plan/collection structure including
pricing and selected items.
Promotions (if applicable):
If promotions exist in your domain, the `promotion_id` field in `CartItem` can be used to link
to them.
CreateCartDto
Fields:
`booking_code` (required; validated against existing `bookings_master.code`)
`item_instructions` (optional)
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 100/112
(Optionally, the client may supply a set of initial CartItems in one payload)
UpdateCartDto
Fields to update cart-level instructions or allow for re-calculation (if you wish to update
subtotal, discount, tax manually, though these should typically be computed).
CreateCartItemDto
Fields:
`cart_id` (required)
`item_type` (required; use a well-defined enum, e.g., `PLAN`, `COLLECTION`, `PRODUCT`)
`quantity` (required; positive integer)
`unit_price` (can be optionally computed on the backend but can be provided for
testing and adjustments)
`order_json` (required; must follow the structure as specified below)
Example Structure:
json
`{
"plan": {
"id": "plan-uuid",
"name": "Meal Plan Name",
"price": 300,
"collections": [
{
"id": "collection-uuid-1",
"name": "Breakfast",
"rule": "Any 2 of 3",
"listing_items": [
{ "id": "listing-item-uuid-1", "name": "Samosa", "quantity": 1 },
{ "id": "listing-item-uuid-2", "name": "Tea", "quantity": 1 }
]
},
{
"id": "collection-uuid-2",
"name": "Dinner",
"rule": "Any 2 of 4",
"listing_items": [
{ "id": "listing-item-uuid-3", "name": "Paneer", "quantity": 1 },
{ "id": "listing-item-uuid-4", "name": "Dal", "quantity": 1 }
]
}
]
},
"pax": 3,
"scheduled_at": "2025-12-31T18:00:00Z"
}
`
`promotion_id` (optional)
`order_instructions` (optional)
UpdateCartItemDto
For updating quantity, order_json, or instructions.
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 101/112
Validation Considerations
Use class-validator decorators for basic checks (e.g., `@IsUUID`, `@IsEnum`, `@IsInt`, and custom
validations on nested JSON schema if needed).
Validate that the JSON structure in `order_json` conforms to expectations (e.g., required keys for
plan, collections, etc.).
B. Service Layer
CartService Responsibilities
Enforce uniqueness so that only one active cart exists per booking. Optionally, auto-
create a cart when a user initiates an order.
getCartByBookingCode(booking_code):
Retrieve cart by the booking_code.
Include related CartItems.
2. CartItem Operations
addItemToCart(CreateCartItemDto):
Validate the cart existence.
Validate that the product/listing/plan referenced in `order_json` is still available
(optionally, check against inventory in product listings).
Calculate `unit_price` and `total_price` (if not provided or if recalculation is needed).
For example, get the base price from the relevant module (ProductListingsService
or PlanService) and adjust based on quantity.
Use Prisma transactions to insert a cart item and update cart totals.
updateCartItem(itemId, UpdateCartItemDto):
Update the quantity and/or the order_json.
Recalculate the item's `total_price` and update the cart’s overall totals.
removeCartItem(itemId):
Remove an item from the cart and update totals accordingly.
3. Cart Summary Computation
calculateCartTotals(cart_id):
Sum the `total_price` of all items.
Apply any global discount rules (for example, promotions that affect the overall cart).
Calculate tax as per your business rules.
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 102/112
Compute `total = subtotal - discount + tax`.
This function should be invoked in any operation that mutates items (add, update, remove).
4. Checkout Operation
checkoutCart(cart_id):
Validate that all cart items have sufficient inventory.
Lock inventory if needed (call ProductListingsService to update available_units).
Transition cart status from `PENDING` to `CHECKED_OUT` (or to an intermediate state if the
payment process requires it).
Optionally, trigger payment order creation (integrate with your payment_orders
module).
5. Integration & Consistency
Use Prisma’s transactions to ensure cart updates and item modifications are atomic.
Leverage existing business logic from your Product, Listing, Collection, and Plan services to
validate availability, pricing, and rules.
For example, before adding an item that represents a plan or a collection bundle, verify that
its constituent product listings are still active and that pricing hasn’t changed.
C. Controller Layer
Proposed Endpoints
PUT /cart/:cartId
Update cart-level data (e.g., special instructions) if needed.
DELETE /cart/:cartId
Option to cancel/delete the cart (soft delete or mark as CANCELLED).
2. Cart Items Endpoints (under `/cart/:cartId/items`):
GET /cart/:cartId/items
List all items in the cart.
POST /cart/:cartId/items
Add a new item to the cart.
Payload: CreateCartItemDto (cart_id can be derived from URL).
PUT /cart/:cartId/items/:itemId
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 103/112
Update an existing cart item (e.g., modify quantity or instructions).
Payload: UpdateCartItemDto.
DELETE /cart/:cartId/items/:itemId
Remove a cart item from the cart.
3. Checkout Endpoint:
POST /cart/:cartId/checkout
Initiate the checkout process.
It will validate inventory, update status, compute totals, and (optionally) integrate with
the payment flow.
Secure all endpoints using the `JwtAuthGuard` similar to your other modules.
Use Swagger decorators (`@ApiTags`, `@ApiBearerAuth`, etc.) for API documentation.
Validate input parameters (e.g., using UUID pipes and DTO validations).
Price Calculation:
CartService should call out to ProductListingsService or PlanService (or use in-memory
caching of prices) to fetch current prices for items being added.
For bundled items (plans/collections), the `order_json` should include the computed prices.
On update/checkout, verify pricing to avoid conflicts if prices have changed.
Inventory Updates:
Prior to checkout, verify that `available_units` in the respective product_listing are sufficient
for the requested quantity.
Trigger inventory adjustments within a transaction so that the cart and inventory remain in
sync.
Rule Application:
If your system applies bundle discounts or rule-based promotions (as seen in collections or
catalogs), ensure that similar logic is applied here:
Either store the computed discount in the cart (using the `discount` field) or reference a
promotion via `promotion_id` in cart items.
The cart computation should consider the discount, tax, and then the final total.
Data Consistency
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 104/112
Transactional Safety:
Use Prisma transactions for operations that affect multiple tables (e.g., adding a cart item and
updating the cart summary).
Optimistic Locking:
Consider versioning fields for Cart and CartItem if concurrent updates are a possibility.
Error Handling
Clear error messages and proper HTTP status codes (e.g., 404 for not found cart/item, 400 for
invalid input, 409 for conflicts like duplicated booking_code).
Log key actions for traceability and debugging, similar to what is done in your Collections and Plan
services.
Timestamps (`created_at` and `updated_at`) must be updated appropriately in all write operations.
Optionally, maintain an audit log for cart changes if necessary for reporting.
Methods:
`createCart(CreateCartDto)`
`updateCartItem(itemId, UpdateCartItemDto)`
`removeCartItem(itemId)`
`calculateCartTotals(cart_id)`
`checkoutCart(cart_id)`
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 105/112
Include necessary validations reflecting existing patterns in Product and Listing
modules.
2. Integration with Existing Modules
Product Listings/Plans/Collections Services:
Called when verifying pricing and inventory.
Bookings Module:
To validate the `booking_code` and link carts to user bookings.
Payment Module (if applicable):
The checkout process may trigger payment order creation.
3. Database Layer
Use Prisma models for `Cart` and `CartItem` as defined.
Maintain referential integrity with bookings_master.
Ensure that any JSON stored in order_json is validated against an expected schema (you could
write a custom validator to check the nested plan/collections structure).
Operational Flow
5. Final Considerations
Scalability:
Consider caching frequently fetched pricing data if recalculations are performance intensive.
Extensibility:
The order_json format and item_type field allow flexibility for future expansion (e.g., adding
new bundle types, services, or digital items).
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 106/112
Testing:
Write unit tests and integration tests to verify that cart calculations, item additions, and
checkout operations behave as expected.
Use test cases that simulate price changes, inventory shortages, and promotion applications.
Documentation:
Document each endpoint, DTO field, and validation rule clearly using Swagger, aligning with
your current modules.
This blueprint provides a comprehensive plan to develop a Cart module that is consistent with your
existing codebase, while ensuring robust and transactional operations, secure endpoints, and clear
integration with the rest of your system. If you need further refinement on any part—such as specific
calculation logic or sample code snippets—feel free to ask!
okay so what is the crat module going to do list down everything how its going to work how will i
add
1. Overall Responsibilities
The Cart module’s primary goal is to manage a customer’s shopping basket. It serves as the staging area
for products (or bundles) that customers intend to purchase before entering the payment process. Its
responsibilities include:
Creating a Cart:
Initialize a cart associated with a unique booking code from the `bookings_master` table.
Ensure each booking has a single active cart (or a clear lifecycle if multiple carts are allowed).
Managing Cart Items:
Allow adding, updating, and removing of individual items.
Maintain a JSON structure for each item to capture complex hierarchical selections (e.g., plans
with collections and associated product listings).
Compute pricing dynamically (unit price, quantity, total for each item).
Calculating Totals and Discounts:
Sum individual item totals to compute the cart subtotal.
Apply discounts (from promotions or bundled offers) and calculate applicable taxes.
Compute the final payable total using the formula:
total = subtotal – discount + tax
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 107/112
Checkout Process:
Validate that inventory is still available.
Lock the inventory as needed (integrating with the Product Listings/Plans services).
Update cart status (e.g., from `PENDING` to `CHECKED_OUT`) and trigger subsequent payment
order creation if applicable.
Create Cart:
Input: A `CreateCartDto` containing at least the `booking_code` (which must exist in
`bookings_master`), and optionally special instructions.
Process: Validate the booking code; ensure no duplicate active cart exists for that booking (or
follow your business rule for multiple carts).
Outcome: A new cart record is created with default status (`PENDING`) and initial financial
values set to zero.
Retrieve Cart:
By Booking Code or Cart ID:
List the details of the cart along with all associated cart items.
Endpoint supports query parameters (e.g., `GET /cart?booking_code=XYZ`) to fetch the
current state of the cart.
`order_json` that encapsulates the detailed structure of the selection (for instance, a
plan and its underlying collections and items)
Optionally, a `promotion_id` if a discount is being applied, along with any special
instructions.
Process:
Validate the cart exists and is in an editable state.
Validate the structure of `order_json` (ensuring it includes required fields such as plan
id, collection details, selected product items, etc.).
Retrieve current pricing from relevant modules (e.g., check with the Product Listings or
Plan service to obtain the current unit price).
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 108/112
Calculate the item total (`unit_price * quantity`) and store the item.
Update the cart’s running totals.
Outcome: A new CartItem record is created and the Cart’s `subtotal`, `discount`, `tax`, and
`total` are recalculated.
Update Cart Item:
Input: An `UpdateCartItemDto` containing modified fields (e.g., updated quantity or a revised
`order_json`).
Process:
Validate that the cart item exists and belongs to the active cart.
Re-calculate the `total_price` for that item after applying changes.
Update the cart totals accordingly.
Outcome: The CartItem is updated and its associated financials are adjusted.
Remove Cart Item:
Input: Identifier for the cart item to be removed.
Process:
Validate existence and association with the cart.
Remove the item.
Re-compute the cart totals.
Outcome: The CartItem is deleted from the cart, and cart subtotal, discount, tax, and overall
total are updated.
Re-Calculate Totals:
When Triggered: Every time a cart item is added, updated, or removed.
Logic:
Sum up the `total_price` values of all items to create the new subtotal.
Subtract any applicable discounts (from promotion codes or rule-based bundle offers).
Add computed tax based on your business rules.
Outcome: Updated cart values in the database to reflect current financial status.
D. Checkout Process
Initiate Checkout:
Input: Invocation via a checkout endpoint (e.g., `POST /cart/:cartId/checkout`).
Process:
Validate that the cart is still in a state where checkout is allowed (e.g., status is still
`PENDING`).
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 109/112
Re-validate the availability of all items by querying the Product Listings service (and any
inventory-related fields).
Optionally lock inventory or flag issues (if an item is no longer available, throw an
exception or signal an error to the client).
Update cart status to `CHECKED_OUT` or an interim state pending payment.
Trigger creation of a payment order (integrate with your existing payment module) and
pass along the necessary payload (cart details, computed amounts, booking code
reference).
Outcome: The cart transitions out of the pending state and a payment processing workflow
begins.
2. Develop DTOs:
Create DTO files for cart creation, update, and cart item operations using class-validator,
mimicking patterns from Product/Listing modules.
Define the JSON schema for `order_json` using comments or a custom validator if necessary.
3. Implement the Service Layer (CartService):
Implement functions:
`createCart(CreateCartDto)`: Validate booking, create a cart.
`getCartByBookingCode(booking_code)`: Retrieve cart with items.
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 110/112
`addCartItem(CreateCartItemDto)`: Validate the cart exists; check product/plan
availability; calculate pricing; add an item.
`updateCartItem(itemId, UpdateCartItemDto)`: Adjust quantities and recalculate.
`removeCartItem(itemId)`: Delete item and update totals.
`calculateCartTotals(cart_id)`: Aggregate all cart items for financial calculations.
`checkoutCart(cart_id)`: Validate inventory, update cart status, and trigger payment
orders.
Ensure that all operations are wrapped in transactions where needed.
4. Build the Controller Layer (CartController):
Expose RESTful endpoints as outlined above, such as:
/cart for retrieving and creating carts.
/cart/:cartId for updating or deleting a cart.
/cart/:cartId/items for item operations.
/cart/:cartId/checkout for initiating checkout.
Utilize DTOs and validation pipes (e.g., UUID validation) as in your other modules.
Secure each route with `JwtAuthGuard`.
5. Integrate with Existing Business Logic:
In the CartService, call relevant methods from existing services (ProductListingsService,
PlanService, etc.) to verify price and inventory.
When a cart item is added or updated, re-calculate its pricing by invoking the necessary
service methods or querying cached values.
6. Testing & Documentation:
Write unit tests and integration tests covering scenarios: adding items, updating quantities,
handling price/inventory changes, and checkout flow.
Use Swagger or similar to document your Cart module endpoints for ease of integration.
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 111/112
The user views the cart via `GET /cart?booking_code=XYZ` to see all items and the calculated
subtotal, discount, tax, and total.
4. User Updates Items:
The user can update or remove items, triggering recalculations of the cart totals.
5. User Proceeds to Checkout:
When satisfied, the user calls `POST /cart/:cartId/checkout`.
The service verifies availability and inventory, locks items, updates the cart status, and sends
data to the payment module.
6. Order Completion:
The system then transitions the cart into the payment process where further order
confirmation takes place.
This detailed blueprint should give you a complete picture of the responsibilities of the Cart module, the
flow for adding items, updating the cart, and finalizing the checkout process. If you need code snippets
or further elaboration on any specific part, feel free to ask!
Printed using ChatGPT to PDF, powered by PDFCrowd HTML to PDF API. 112/112