"feat: enhance invoice management with schema updates, seed data, and new APIs"

This commit is contained in:
louiscklaw
2025-05-30 16:48:54 +08:00
parent 5a707427c6
commit fd20a3531b
48 changed files with 1541 additions and 179 deletions

View File

@@ -0,0 +1,98 @@
// src/app/api/product/saveProduct/route.ts
//
// PURPOSE:
// save product to db by id
//
// RULES:
// T.B.A.
import type { NextRequest } from 'next/server';
import { STATUS, response, handleError } from 'src/utils/response';
import prisma from '../../../lib/prisma';
// ----------------------------------------------------------------------
/** **************************************
* PUT - change order status
*************************************** */
export async function PUT(req: NextRequest) {
// logger('[Invoice] list', products.length);
const { searchParams } = req.nextUrl;
const invoiceId = searchParams.get('invoiceId');
// RULES: invoiceId must exist
if (!invoiceId) {
return response({ message: 'Invoice ID is required!' }, STATUS.BAD_REQUEST);
}
const { data } = await req.json();
try {
const order = await prisma.invoiceItem.updateMany({
where: { id: invoiceId },
data: { status: data.status },
});
return response({ order }, STATUS.OK);
} catch (error) {
console.log({ data });
return handleError('Invoice - Get list', error);
}
}
export type IProductItem = {
id: string;
sku: string;
name: string;
code: string;
price: number;
taxes: number;
tags: string[];
sizes: string[];
publish: string;
gender: string[];
coverUrl: string;
images: string[];
colors: string[];
quantity: number;
category: string;
available: number;
totalSold: number;
description: string;
totalRatings: number;
totalReviews: number;
// createdAt: IDateValue;
inventoryType: string;
subDescription: string;
priceSale: number | null;
// reviews: IProductReview[];
newLabel: {
content: string;
enabled: boolean;
};
saleLabel: {
content: string;
enabled: boolean;
};
ratings: {
name: string;
starCount: number;
reviewCount: number;
}[];
};
export type IDateValue = string | number | null;
export type IProductReview = {
id: string;
name: string;
rating: number;
comment: string;
helpful: number;
avatarUrl: string;
postedAt: IDateValue;
isPurchased: boolean;
attachments?: string[];
};

View File

@@ -0,0 +1,9 @@
###
PUT http://localhost:7272/api/invoice/changeStatus?orderId=1
content-type: application/json
{
"data":{"status": "helloworld"}
}

View File

@@ -0,0 +1,53 @@
// src/app/api/user/createUser/route.ts
//
// PURPOSE:
// create user to db
//
// RULES:
// T.B.A.
//
import type { NextRequest } from 'next/server';
import { STATUS, response, handleError } from 'src/utils/response';
import prisma from '../../../lib/prisma';
// ----------------------------------------------------------------------
/**
***************************************
* POST - create User
***************************************
*/
export async function POST(req: NextRequest) {
// logger('[User] list', users.length);
const { data } = await req.json();
const createForm: CreateUserData = data as unknown as CreateUserData;
try {
const user = await prisma.userItem.create({ data: createForm });
return response({ user }, STATUS.OK);
} catch (error) {
return handleError('User - Create', error);
}
}
type CreateUserData = {
name: string;
city: string;
role: string;
email: string;
state: string;
status: string;
address: string;
country: string;
zipCode: string;
company: string;
avatarUrl: string;
phoneNumber: string;
isVerified: boolean;
//
username: string;
password: string;
};

View File

@@ -0,0 +1,4 @@
###
POST http://localhost:7272/api/user/createUser

View File

@@ -0,0 +1,47 @@
// src/app/api/product/deleteUser/route.ts
//
// PURPOSE:
// delete product from db by id
//
// RULES:
// T.B.A.
import type { NextRequest } from 'next/server';
import { logger } from 'src/utils/logger';
import { STATUS, response, handleError } from 'src/utils/response';
import prisma from '../../../lib/prisma';
// ----------------------------------------------------------------------
/** **************************************
* handle Delete Users
*************************************** */
export async function DELETE(req: NextRequest) {
try {
const { searchParams } = req.nextUrl;
// RULES: userId must exist
const userId = searchParams.get('userId');
if (!userId) {
return response({ message: 'User ID is required!' }, STATUS.BAD_REQUEST);
}
// NOTE: userId confirmed exist, run below
const user = await prisma.userItem.delete({
//
where: { id: userId },
});
if (!user) {
return response({ message: 'User not found!' }, STATUS.NOT_FOUND);
}
logger('[User] details', user.id);
return response({ user }, STATUS.OK);
} catch (error) {
return handleError('User - Get details', error);
}
}

View File

@@ -0,0 +1,3 @@
###
DELETE http://localhost:7272/api/user/deleteUser?userId=3f431e6f-ad05-4d60-9c25-6a7e92a954ad

View File

@@ -0,0 +1,47 @@
// src/app/api/invoice/details/route.ts
//
// PURPOSE:
// read invoice from db by id
//
// RULES:
// T.B.A.
import type { NextRequest } from 'next/server';
import { logger } from 'src/utils/logger';
import { STATUS, response, handleError } from 'src/utils/response';
import prisma from '../../../lib/prisma';
// ----------------------------------------------------------------------
/** **************************************
* GET Invoice detail
*************************************** */
export async function GET(req: NextRequest) {
try {
const { searchParams } = req.nextUrl;
// RULES: invoiceId must exist
const invoiceId = searchParams.get('invoiceId');
if (!invoiceId) {
return response({ message: 'invoiceId is required!' }, STATUS.BAD_REQUEST);
}
// NOTE: invoiceId confirmed exist, run below
const invoice = await prisma.invoiceItem.findFirst({
// include: { reviews: true },
where: { id: invoiceId.toString() },
});
if (!invoice) {
return response({ message: 'Invoice not found!' }, STATUS.NOT_FOUND);
}
logger('[Invoice] details', invoice.id);
return response({ invoice }, STATUS.OK);
} catch (error) {
return handleError('Product - Get details', error);
}
}

View File

@@ -0,0 +1,4 @@
###
GET http://localhost:7272/api/invoice/details?invoiceId=1

View File

@@ -0,0 +1,30 @@
// src/app/api/product/image/upload/route.ts
//
// PURPOSE:
// handle upload product image
//
// RULES:
// T.B.A.
import type { NextRequest } from 'next/server';
import { STATUS, response, handleError } from 'src/utils/response';
// import prisma from '../../../lib/prisma';
// ----------------------------------------------------------------------
/** **************************************
* GET - Products
*************************************** */
export async function POST(req: NextRequest) {
try {
const { data } = await req.json();
console.log('helloworld');
return response({ hello: 'world' }, STATUS.OK);
} catch (error) {
console.log({ hello: 'world' });
return handleError('Product - store product image', error);
}
}

View File

@@ -0,0 +1,22 @@
// src/app/api/invoice/list/route.ts
import { logger } from 'src/utils/logger';
import { STATUS, response, handleError } from 'src/utils/response';
import prisma from '../../../lib/prisma';
// ----------------------------------------------------------------------
/** **************************************
* GET - InvoiceItem
*************************************** */
export async function GET() {
try {
const invoices = await prisma.invoiceItem.findMany();
logger('[Invoice] list', invoices.length);
return response({ invoices }, STATUS.OK);
} catch (error) {
return handleError('InvoiceItem - Get list', error);
}
}

View File

@@ -0,0 +1,3 @@
###
GET http://localhost:7272/api/invoice/list

View File

@@ -0,0 +1,98 @@
// src/app/api/invoice/saveInvoice/route.ts
//
// PURPOSE:
// save invoice to db by id
//
// RULES:
// T.B.A.
import type { NextRequest } from 'next/server';
import { STATUS, response, handleError } from 'src/utils/response';
import prisma from '../../../lib/prisma';
// ----------------------------------------------------------------------
/** **************************************
* PUT - Update invoice
*************************************** */
export async function PUT(req: NextRequest) {
// logger('[Invoice] list', invoices.length);
const { searchParams } = req.nextUrl;
const invoiceId = searchParams.get('invoiceId');
// RULES: invoiceId must exist
if (!invoiceId) {
return response({ message: 'Invoice ID is required!' }, STATUS.BAD_REQUEST);
}
const { data } = await req.json();
try {
const invoice = await prisma.invoiceItem.updateMany({
where: { id: invoiceId },
data,
});
return response({ invoice }, STATUS.OK);
} catch (error) {
console.log({ data });
return handleError('Invoice - Update invoice', error);
}
}
export type IInvoiceItem = {
id: string;
sku: string;
name: string;
code: string;
price: number;
taxes: number;
tags: string[];
sizes: string[];
publish: string;
gender: string[];
coverUrl: string;
images: string[];
colors: string[];
quantity: number;
category: string;
available: number;
totalSold: number;
description: string;
totalRatings: number;
totalReviews: number;
// createdAt: IDateValue;
inventoryType: string;
subDescription: string;
priceSale: number | null;
// reviews: IInvoiceReview[];
newLabel: {
content: string;
enabled: boolean;
};
saleLabel: {
content: string;
enabled: boolean;
};
ratings: {
name: string;
starCount: number;
reviewCount: number;
}[];
};
export type IDateValue = string | number | null;
export type IInvoiceReview = {
id: string;
name: string;
rating: number;
comment: string;
helpful: number;
avatarUrl: string;
postedAt: IDateValue;
isPurchased: boolean;
attachments?: string[];
};

View File

@@ -0,0 +1,58 @@
###
PUT http://localhost:7272/api/invoice/saveInvoice?invoiceId=1
content-type: application/json
{
"data": {
"id": "1",
"taxes": 10,
"status": "paid",
"discount": 10,
"shipping": 10,
"subtotal": 921.14,
"totalAmount": 993.254,
"items": [
{
"title": "Urban Explorer Sneakers 1111",
"service": "Technology",
"quantity": 11,
"price": 83.74,
"total": 921.14,
"description": "The sun slowly set over the horizon, painting the sky in vibrant hues of orange and pink."
},
{
"price": 83.74,
"title": "Urban Explorer Sneakers 22222",
"total": 921.14,
"service": "Technology",
"quantity": 11,
"description": "The sun slowly set over the horizon, painting the sky in vibrant hues of orange and pink."
}
],
"invoiceNumber": "INV-1991",
"invoiceFrom": {
"id": "e99f09a7-dd88-49d5-b1c8-1daf80c2d7b02",
"name": "Lucian Obrien",
"email": "milo.farrell@hotmail.com",
"company": "Nikolaus - Leuschke",
"primary": false,
"addressType": "Office",
"fullAddress": "1147 Rohan Drive Suite 819 - Burlington, VT / 82021",
"phoneNumber": "+1 416-555-0198"
},
"invoiceTo": {
"id": "e99f09a7-dd88-49d5-b1c8-1daf80c2d7b03",
"name": "Deja Brady",
"email": "violet.ratke86@yahoo.com",
"company": "Hegmann, Kreiger and Bayer",
"primary": false,
"addressType": "Office",
"fullAddress": "18605 Thompson Circle Apt. 086 - Idaho Falls, WV / 50337",
"phoneNumber": "+44 20 7946 0958"
},
"sent": 10,
"createdDate": "2025-06-15T17:07:24+08:00",
"dueDate": "2025-06-15T17:07:24+08:00"
}
}

View File

@@ -0,0 +1,37 @@
import type { NextRequest } from 'next/server';
import { logger } from 'src/utils/logger';
import { STATUS, response, handleError } from 'src/utils/response';
import { _products } from 'src/_mock/_product';
// ----------------------------------------------------------------------
export const runtime = 'edge';
/** **************************************
* GET - Search products
*************************************** */
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 products = _products();
// Accept search by name or sku
const results = products.filter(
({ name, sku }) => name.toLowerCase().includes(query) || sku?.toLowerCase().includes(query)
);
logger('[Product] search-results', results.length);
return response({ results }, STATUS.OK);
} catch (error) {
return handleError('Product - Get search', error);
}
}

View File

@@ -66,9 +66,9 @@ export async function POST(req: NextRequest) {
where: { id: data.id },
});
return response({ hello: 'world', data }, STATUS.OK);
return response({ data }, STATUS.OK);
} catch (error) {
console.log({ hello: 'world', data });
console.log({ data });
return handleError('Product - Get list', error);
}
}