feat: add REQ0187 frontend party-order CRUD functionality and backend API endpoints

This commit is contained in:
louiscklaw
2025-06-15 22:27:44 +08:00
parent 53b112e488
commit dfc9873815
24 changed files with 780 additions and 5 deletions

View File

@@ -0,0 +1,14 @@
Hi,
i copied from
`03_source/cms_backend/src/app/api/party-event`
to
`03_source/cms_backend/src/app/api/party-order`
with knowledge in `schema.prisma` file, and take a look into the sibling files in the same directory.
i want you to update `03_source/cms_backend/src/app/api/party-order` content to handle `party-order` (the purchase order of the party)
`party-order.service.ts` is already prepared and you can use it
please maintain same format and level of detail when you edit.
thanks.

View File

@@ -0,0 +1,35 @@
<!-- AI: please maintain same format and level of detail when edit this file -->
# GUIDELINE
- Party Event API endpoint for managing party events
- Handles CRUD operations for party events
- Follows single file for single db table/collection pattern
## `route.ts`
Handles HTTP methods:
- `GET` - Retrieve party events
- `POST` - Create new party event
- `PUT` - Update existing party event
- `DELETE` - Remove party event
## `test.http`
Contains test requests for:
- Listing all party events
- Creating new party event
- Updating existing party event
- Deleting party event
## `../../services/party-event.service.ts`
Party Event CRUD operations:
`listPartyEvents` - List all party events with optional filters
`getPartyEvent` - Get single party event by ID
`createNewPartyEvent` - Create new party event record
`updatePartyEvent` - Update existing party event by ID
`deletePartyEvent` - Delete party event by ID

View File

@@ -0,0 +1,35 @@
<!-- AI: please maintain same format and level of detail when edit this file -->
# GUIDELINE
- Party Order API endpoint for managing party orders
- Handles CRUD operations for party orders
- Follows single file for single db table/collection pattern
## `route.ts`
Handles HTTP methods:
- `GET` - Retrieve party orders
- `POST` - Create new party order
- `PUT` - Update existing party order
- `DELETE` - Remove party order
## `test.http`
Contains test requests for:
- Listing all party orders
- Creating new party order
- Updating existing party order
- Deleting party order
## `../../services/party-order.service.ts`
Party Order CRUD operations:
`listPartyOrders` - List all party orders with optional filters
`getPartyOrder` - Get single party order by ID
`createNewPartyOrder` - Create new party order record
`updatePartyOrder` - Update existing party order by ID
`deletePartyOrder` - Delete party order by ID

View File

@@ -0,0 +1,40 @@
// src/app/api/party-order/create/route.ts
//
// PURPOSE:
// Create new party order in db
//
// RULES:
// T.B.A.
import type { NextRequest } from 'next/server';
import { STATUS, response, handleError } from 'src/utils/response';
import { isDev } from 'src/constants';
import { createOrder } from 'src/app/services/party-order.service';
// ----------------------------------------------------------------------
/** **************************************
* POST - Create PartyOrder
*************************************** */
export async function POST(req: NextRequest) {
const { partyOrderData } = await req.json();
try {
if (isDev) {
console.log({ partyOrderData });
}
const created = await createOrder(partyOrderData);
if (isDev) {
console.log('Order created successfully');
}
return response(created, STATUS.OK);
} catch (error) {
console.error('Error creating order:', { partyOrderData });
return handleError('PartyOrder - Create', error);
}
}

View File

@@ -0,0 +1,31 @@
###
POST http://localhost:7272/api/party-order/create
Content-Type: application/json
{
"partyOrderData": {
"taxes": 15.00,
"status": "pending",
"shipping": 10.00,
"discount": 20.00,
"subtotal": 290.00,
"orderNumber": "ORD_20231115001",
"totalAmount": 300.00,
"totalQuantity": 2.0,
"history": "{\"actions\":[{\"timestamp\":\"2023-11-15T10:00:00Z\",\"action\":\"order_created\"}]}",
"payment": "{\"method\":\"credit_card\",\"status\":\"unpaid\",\"transaction_id\":\"txn_123456\"}",
"customer": "{\"name\":\"John Doe\",\"email\":\"john.doe@example.com\",\"phone\":\"+1234567890\"}",
"delivery": "{\"method\":\"courier\",\"estimated_delivery\":\"2023-11-18\"}",
"items": [
{
"id": "ticket_001",
"name": "General Admission",
"quantity": 2,
"price": 150.00
}
],
"shippingAddress": "{\"street\":\"123 Main St\",\"city\":\"New York\",\"state\":\"NY\",\"zip\":\"10001\",\"country\":\"USA\"}"
}
}

View File

@@ -0,0 +1,22 @@
import type { NextRequest } from 'next/server';
import { STATUS, response, handleError } from 'src/utils/response';
import { deleteOrder } from 'src/app/services/party-order.service';
/** **************************************
* PATCH - Delete PartyOrder
*************************************** */
export async function PATCH(req: NextRequest) {
try {
const { orderId } = await req.json();
if (!orderId) throw new Error('orderId cannot be null');
await deleteOrder(orderId);
return response({ orderId }, STATUS.OK);
} catch (error) {
return handleError('PartyOrder - Delete', error);
}
}

View File

@@ -0,0 +1,8 @@
###
PATCH http://localhost:7272/api/party-order/delete
Content-Type: application/json
{
"orderId": "e99f09a7-dd88-49d5-b1c8-1daf80c2d7b02"
}

View File

@@ -0,0 +1,56 @@
// src/app/api/party-order/details/route.ts
//
// PURPOSE:
// Get party order from db by id
//
// RULES:
// Do not modify the format and level of details thanks.
import type { NextRequest } from 'next/server';
import { logger } from 'src/utils/logger';
import { STATUS, response, handleError } from 'src/utils/response';
import { L_INFO, L_ERROR } from 'src/constants';
import { getOrder } from 'src/app/services/party-order.service';
import { createAppLog } from 'src/app/services/app-log.service';
import { flattenNextjsRequest } from '../../auth/sign-in/flattenNextjsRequest';
// ----------------------------------------------------------------------
/**
**************************************
* GET PartyOrder detail
***************************************
*/
export async function GET(req: NextRequest) {
const debug = { 'req.headers': flattenNextjsRequest(req) };
try {
const { searchParams } = req.nextUrl;
// RULES: partyOrderId must exist
const partyOrderId = searchParams.get('partyOrderId');
if (!partyOrderId) {
return response({ message: 'Order ID is required!' }, STATUS.BAD_REQUEST);
}
// NOTE: partyOrderId confirmed exist, run below
const order = await getOrder(partyOrderId);
if (!order) {
return response({ message: 'Order not found!' }, STATUS.NOT_FOUND);
}
logger('[PartyOrder] details', order.id);
createAppLog(L_INFO, 'Get order detail OK', debug);
return response({ order }, STATUS.OK);
} catch (error) {
createAppLog(L_ERROR, 'order detail error', debug);
return handleError('PartyOrder - Get details', error);
}
}

View File

@@ -0,0 +1,9 @@
###
# Get details for a specific party order
GET http://localhost:7272/api/party-order/details?partyOrderId=e99f09a7-dd88-49d5-b1c8-1daf80c2d7b02
###
# Alternative format with different ID
GET http://localhost:7272/api/party-order/details?id=ord_987654321

View File

@@ -0,0 +1,16 @@
import type { NextRequest, NextResponse } from 'next/server';
import { STATUS, response, handleError } from 'src/utils/response';
/**
***************************************
* GET - helloworld
***************************************
*/
export async function GET(req: NextRequest, res: NextResponse) {
try {
return response({ helloworld: 'party-order' }, STATUS.OK);
} catch (error) {
return handleError('Helloworld - Get all', error);
}
}

View File

@@ -0,0 +1,2 @@
###
GET http://localhost:7272/api/party-order/helloworld

View File

@@ -0,0 +1,38 @@
// src/app/api/party-order/list/route.ts
//
// PURPOSE:
// List all party orders from db
//
// RULES:
// T.B.A.
import type { NextRequest } from 'next/server';
import { STATUS, response, handleError } from 'src/utils/response';
import { L_INFO, L_ERROR } from 'src/constants';
import { createAppLog } from 'src/app/services/app-log.service';
import { listPartyOrders } from 'src/app/services/party-order.service';
import { flattenNextjsRequest } from '../../auth/sign-in/flattenNextjsRequest';
// ----------------------------------------------------------------------
/** **************************************
* GET - PartyOrders list
*************************************** */
export async function GET(req: NextRequest) {
const debug = { 'req.headers': flattenNextjsRequest(req) };
try {
const orders = await listPartyOrders();
createAppLog(L_INFO, 'party-order list ok', {});
return response({ orders }, STATUS.OK);
} catch (error) {
createAppLog(L_ERROR, 'party-order list error', debug);
return handleError('PartyOrder - Get list', error);
}
}

View File

@@ -0,0 +1,19 @@
###
# Basic list all orders
GET http://localhost:7272/api/party-order/list
###
# List orders by status
GET http://localhost:7272/api/party-order/list?status=completed
###
# List orders by user
GET http://localhost:7272/api/party-order/list?userId=usr_987654321
###
# List orders by payment status
GET http://localhost:7272/api/party-order/list?paymentStatus=paid

View File

@@ -0,0 +1,75 @@
import type { NextRequest, NextResponse } from 'next/server';
import { STATUS, response, handleError } from 'src/utils/response';
import { listPartyOrders, deleteOrder, updateOrder, createOrder } from 'src/app/services/party-order.service';
/**
**************************************
* GET - PartyOrder
***************************************
*/
export async function GET(req: NextRequest, res: NextResponse) {
try {
const orders = await listPartyOrders();
return response(orders, STATUS.OK);
} catch (error) {
return handleError('PartyOrder - Get list', error);
}
}
/**
***************************************
* POST - Create PartyOrder
***************************************
*/
export async function POST(req: NextRequest) {
const OPERATION = 'PartyOrder - Create';
const { data } = await req.json();
try {
const order = await createOrder(data);
return response(OPERATION, STATUS.OK);
} catch (error) {
return handleError(OPERATION, error);
}
}
/**
***************************************
* PUT - Update PartyOrder
***************************************
*/
export async function PUT(req: NextRequest) {
const { searchParams } = req.nextUrl;
const orderId = searchParams.get('orderId');
const { data } = await req.json();
try {
if (!orderId) throw new Error('orderId cannot be null');
const result = await updateOrder(orderId, data);
return response(result, STATUS.OK);
} catch (error) {
return handleError('PartyOrder - Update', error);
}
}
/**
***************************************
* DELETE - Delete PartyOrder
***************************************
*/
export async function DELETE(req: NextRequest) {
const { searchParams } = req.nextUrl;
const orderId = searchParams.get('orderId');
try {
if (!orderId) throw new Error('orderId cannot be null');
await deleteOrder(orderId);
return response({ success: true }, STATUS.OK);
} catch (error) {
return handleError('PartyOrder - Delete', error);
}
}

View File

@@ -0,0 +1,35 @@
import type { NextRequest } from 'next/server';
import { logger } from 'src/utils/logger';
import { STATUS, response, handleError } from 'src/utils/response';
import type { IOrderItem } from '../update/route';
// import { searchEvents } from 'src/app/services/party-event.service';
// ----------------------------------------------------------------------
/** **************************************
* GET - Search PartyEvents
*************************************** */
export async function GET(req: NextRequest) {
try {
const { searchParams } = req.nextUrl;
const query = searchParams.get('query')?.trim().toLowerCase();
if (!query) {
return response({ results: [] }, STATUS.OK);
}
const results: IOrderItem[] = [];
// TODO: search party event not implemented
console.log('search party event not implemented');
// const results = await searchEvents(query);
logger('[PartyEvent] search-results', results?.length);
return response({ results }, STATUS.OK);
} catch (error) {
return handleError('PartyEvent - Get search', error);
}
}

View File

@@ -0,0 +1,24 @@
###
# Search party events by title
GET http://localhost:7272/api/party-event/search?query=Music
###
# Search party events by location
GET http://localhost:7272/api/party-event/search?query=Central+Park
###
# Search party events by tag
GET http://localhost:7272/api/party-event/search?query=Festival
###
# Combined search with multiple parameters
GET http://localhost:7272/api/party-event/search?query=Summer&location=Hong+Kong&category=Music
###
# No results expected
GET http://localhost:7272/api/party-event/search?query=zzzzzz

View File

@@ -0,0 +1,51 @@
// src/app/api/party-order/update/route.ts
//
// PURPOSE:
// Update party order in db by id
//
// RULES:
// T.B.A.
import type { NextRequest } from 'next/server';
import { STATUS, response, handleError } from 'src/utils/response';
import { updateOrder } from 'src/app/services/party-order.service';
// ----------------------------------------------------------------------
/** **************************************
* PUT - Update PartyOrder
*************************************** */
export async function PUT(req: NextRequest) {
const { orderData } = await req.json();
const { id } = orderData;
if (!id) return response({ message: 'id not found' }, STATUS.ERROR);
try {
const result = await updateOrder(id, orderData);
return response({ result }, STATUS.OK);
} catch (error) {
console.error('Error updating order:', { orderData });
return handleError('PartyOrder - Update', error);
}
}
export type IOrderItem = {
id: string;
eventId: string;
userId: string;
status: string;
paymentStatus: string;
totalAmount: number;
items: {
id: string;
name: string;
quantity: number;
price: number;
}[];
createdAt: Date;
updatedAt: Date;
};

View File

@@ -0,0 +1,51 @@
###
PUT http://localhost:7272/api/party-order/update
Content-Type: application/json
{
"orderData": {
"id":"e99f09a7-dd88-49d5-b1c8-1daf80c2d7b02",
"taxes": 10.50,
"status": "completed",
"shipping": 5.00,
"discount": 20.00,
"subtotal": 290.00,
"orderNumber": "ORD-2023-001",
"totalAmount": 300.00,
"totalQuantity": 2,
"history": {
"payment": "2023-01-01",
"status_changes": [
"pending",
"paid"
]
},
"payment": {
"method": "credit_card",
"card_last4": "4321"
},
"customer": {
"name": "John Doe",
"email": "john@example.com"
},
"delivery": {
"method": "express",
"tracking_number": "TRK123456"
},
"items": [
{
"id": "ticket_001",
"name": "General Admission",
"quantity": 2,
"price": 150.00,
"status": "used"
}
],
"shippingAddress": {
"street": "123 Main St",
"city": "New York",
"zip": "10001"
}
}
}

View File

@@ -1,7 +1,13 @@
with knowledge in schema.prisma file,
please refer the below helloworld example `helloworld.service.ts`
and create `user.service.ts` to cover user record
Hi,
thanks
i copied from
`03_source/cms_backend/src/app/services/party-event.service.ts`
to
`03_source/cms_backend/src/app/services/party-order.service.ts`
`/home/logic/_wsl_workspace/001_github_ws/HKSingleParty-ws/HKSingleParty/03_source/cms_backend/src/app/services/helloworld.service.ts`
with knowledge in `schema.prisma` file, and reference to the sibling files in same folder
i want you to update `party-order.service.ts` content to handle party order (the purchase order of the party)
please use the model `PartyOrderItem` to handle it.
thanks.

View File

@@ -0,0 +1,66 @@
// src/app/services/party-order.service.ts
//
// PURPOSE:
// - Service for handling PartyOrderItem Record
//
import type { PartyOrderItem } from '@prisma/client';
import prisma from '../lib/prisma';
type CreateOrder = {
status: string;
taxes: number;
shipping: number;
discount: number;
items: any[];
customer: any;
payment: any;
delivery: any;
shippingAddress: any;
billingAddress: any;
};
type UpdateOrder = {
status?: string;
taxes?: number;
shipping?: number;
discount?: number;
items?: any[];
customer?: any;
payment?: any;
delivery?: any;
shippingAddress?: any;
billingAddress?: any;
};
async function listPartyOrders(): Promise<PartyOrderItem[]> {
return prisma.partyOrderItem.findMany();
}
async function getPartyOrder(partyOrderId: string): Promise<PartyOrderItem | null> {
return prisma.partyOrderItem.findUnique({
where: { id: partyOrderId },
});
}
async function createOrder(orderData: any) {
return prisma.partyOrderItem.create({
data: orderData,
});
}
async function updateOrder(orderId: string, updateData: UpdateOrder) {
return prisma.partyOrderItem.update({
where: { id: orderId },
data: updateData,
});
}
async function deleteOrder(orderId: string) {
return prisma.partyOrderItem.delete({
where: { id: orderId },
});
}
export { getPartyOrder as getOrder, createOrder, updateOrder, deleteOrder, listPartyOrders, type CreateOrder, type UpdateOrder };