feat(order): add customer email and optional fields, update order creation flow

- Added `customerEmail` field to Order model.
- Made `customerNote` optional in the Order model.
- Updated order creation logic to send a tracking email.
- Updated order validation to handle optional fields.
- Refactored configurations import path from `errors/configs` to `configs`.
- Minor modifications across account and order services for consistency.
This commit is contained in:
2026-04-13 00:13:52 +06:00
parent ee5eb0f0f5
commit 78a9b99aae
18 changed files with 103 additions and 83 deletions
@@ -0,0 +1,3 @@
-- AlterTable
ALTER TABLE "Order" ADD COLUMN "customerEmail" TEXT,
ALTER COLUMN "customerNote" DROP NOT NULL;
+2 -1
View File
@@ -20,8 +20,9 @@ model Order {
status STATUS
customerName String
customerPhone String
customerEmail String?
customerAddress String
customerNote String
customerNote String?
paymentType PAYMENT_TYPE
createdAt DateTime @default(now())
+1 -1
View File
@@ -1,5 +1,5 @@
import { NextFunction, Request, Response } from "express";
import { configs } from "../errors/configs";
import { configs } from "../configs";
import { AppError } from "../utils/app_error";
import { jwtHelpers, JwtPayloadType } from "../utils/JWT";
+1 -1
View File
@@ -1,6 +1,6 @@
import { ErrorRequestHandler } from "express";
import { ZodError } from "zod";
import { configs } from "../errors/configs";
import { configs } from "../configs";
import handleZodError from "../errors/zodError";
import { TErrorSources } from "../types/error";
import { AppError } from "../utils/app_error";
@@ -1,4 +1,4 @@
import { configs } from "../../errors/configs";
import { configs } from "../../configs";
import catchAsync from "../../utils/catch_async";
import manageResponse from "../../utils/manage_response";
import { account_services } from "./account.service";
+2 -2
View File
@@ -1,6 +1,6 @@
import bcrypt from "bcrypt";
import { Request } from "express";
import { configs } from "../../errors/configs";
import { configs } from "../../configs";
import { prisma } from "../../lib/prisma";
import { emailQueue } from "../../queues/email/email.queue";
import { AppError } from "../../utils/app_error";
@@ -72,7 +72,7 @@ const create_account_into_db = async (req: Request) => {
email: payload.email,
textBody: "You can use otp or verification link for verifying your account"
})
return result;
return null;
};
const verify_account_using_otp_into_db = async (req: Request) => {
+19 -44
View File
@@ -1,7 +1,8 @@
import { Request } from "express";
import { configs } from "../../configs";
import { prisma } from "../../lib/prisma";
import { AppError } from "../../utils/app_error";
import { orderEmailQueue } from "../../queues/email/order/order.email.queue";
const get_all_order_from_db = async (req: Request) => {
// define your own login here
@@ -11,68 +12,42 @@ const get_all_order_from_db = async (req: Request) => {
const get_single_order_from_db = async (req: Request) => {
// define your own login here
const { id } = req.params;
const { id } = req.params as { id: string };
const result = await prisma.order.findUnique({ where: { id } });
return result;
};
const create_order_into_db = async (req: Request) => {
// define your own login here
const payload = req?.body;
payload.status = "INITIATED";
payload.paymentType = "COD"
const user = req.user
console.log(user)
const {
shopAccountId,
productPrice,
productQuantity,
productName,
customerName,
customerPhone,
customerAddress,
customerNote,
paymentType,
status,
} = req.body
const isUserExists = await prisma.account.findFirst({
where: {
id: user?.accountId
}
// nwo init order
const result = await prisma.order.create({ data: payload });
// if email exist sent tracking link
if (payload.customerEmail) {
const trackingLink = `${configs.jwt.front_end_url}/track-order/${result.id}`;
await orderEmailQueue.add("order-email-queue", {
email: payload.customerEmail,
subject: "Order Tracking",
textBody: `Your order has been created. Track your order here: ${trackingLink}`,
htmlBody: `<p>Your order has been created. Track your order here: <a href="${trackingLink}">Track Order</a></p>`
})
if (!isUserExists) {
throw new AppError("Account not found", 404);
}
const result = await prisma.order.create({
data: {
productPrice,
productQuantity,
productName,
customerName,
customerPhone,
customerAddress,
customerNote,
paymentType,
status,
account: {
connect: {
id: user?.accountId,
},
},
}
});
return result;
};
const update_order_into_db = async (req: Request) => {
// define your own login here
const { id } = req.params;
const { id } = req.params as { id: string };
const result = await prisma.order.update({ where: { id }, data: req.body });
return result;
};
const delete_order_from_db = async (req: Request) => {
// define your own login here
const { id } = req.params;
const { id } = req.params as { id: string };
const result = await prisma.order.delete({ where: { id } });
return result;
};
+4 -6
View File
@@ -10,18 +10,16 @@ export const orderSwaggerDocs = {
content: {
"application/json": {
example: JSON.stringify({
"shopAccountId": "",
"productPrice": 1500,
"productQuantity": 2,
"productName": "Wireless Mouse",
"customerName": "Rahim Uddin",
"customerPhone": "+8801712345678",
"customerEmail": "softvence.abumahid@gmail.com",
"customerAddress": "Rangpur, Bangladesh",
"customerNote": "Please deliver between 3-5 PM",
"paymentType": "Cash on Delivery",
"status": "Pending"
}), // put your request body
"customerNote": "Please deliver between 3-5 PM"
})
},
},
},
+20 -21
View File
@@ -1,29 +1,28 @@
import { uuid, z } from "zod";
import { z } from "zod";
const create_order = z.object({
shopAccountId:z.string(),
productPrice:z.number(),
productQuantity:z.number(),
productName:z.string(),
customerName:z.string(),
customerPhone:z.string(),
customerAddress:z.string(),
customerNote:z.string(),
paymentType:z.string(),
status:z.string()
shopAccountId: z.string(),
productPrice: z.number(),
productQuantity: z.number(),
productName: z.string(),
customerName: z.string(),
customerPhone: z.string(),
customerEmail: z.string().optional(),
customerAddress: z.string(),
customerNote: z.string().optional()
});
const update_order = z.object({
shopAccountId:z.string().optional(),
productPrice:z.number().optional(),
productQuantity:z.number().optional(),
productName:z.string().optional(),
customerName:z.string().optional(),
customerPhone:z.string().optional(),
customerAddress:z.string().optional(),
customerNote:z.string().optional(),
paymentType:z.string().optional(),
status:z.string().optional()
shopAccountId: z.string().optional(),
productPrice: z.number().optional(),
productQuantity: z.number().optional(),
productName: z.string().optional(),
customerName: z.string().optional(),
customerPhone: z.string().optional(),
customerEmail: z.string().optional(),
customerAddress: z.string().optional(),
customerNote: z.string().optional(),
status: z.string().optional()
});
export const order_validations = {
@@ -0,0 +1,14 @@
import sendMail from "../../../utils/mail_sender";
import { TOrderEmailQueue } from "./order.email.queue";
// email.processor.ts
export const orderEmailProcessor = async (job: any) => {
const payload: TOrderEmailQueue = job.data;
await sendMail({
to: payload.email as string,
subject: payload.subject,
htmlBody: payload.htmlBody as string,
textBody: payload.textBody as string,
});
console.log("Sending email job complete:", job.id);
};
@@ -0,0 +1,15 @@
import { Queue } from "bullmq";
import { redisConnection } from "../../connection";
export type TOrderEmailQueue = {
email: string;
subject: string;
textBody?: string;
htmlBody?: string;
}
export const orderEmailQueue = new Queue<TOrderEmailQueue>("order-email-queue", {
connection: redisConnection,
});
@@ -0,0 +1,14 @@
// email.worker.ts
import { Worker } from "bullmq";
import { redisConnection } from "../../connection";
import { orderEmailProcessor } from "./order.email.processor";
import { TOrderEmailQueue } from "./order.email.queue";
export const emailWorker = new Worker<TOrderEmailQueue>(
"order-email-queue",
async (job) => orderEmailProcessor(job),
{
connection: redisConnection,
}
);
+1
View File
@@ -1,3 +1,4 @@
import "./email/email.worker";
import "./email/order/order.email.worker";
console.log("Workers running...");
+1 -1
View File
@@ -1,6 +1,6 @@
import { v2 as cloudinary } from 'cloudinary';
import fs from 'fs';
import { configs } from '../errors/configs';
import { configs } from '../configs';
type ICloudinaryResponse = {
asset_id: string;
+1 -1
View File
@@ -1,5 +1,5 @@
import nodemailer from 'nodemailer';
import { configs } from '../errors/configs';
import { configs } from '../configs';
type TMailContent = {
to: string,
subject: string,
+2 -2
View File
@@ -1,7 +1,7 @@
import app from "./app";
import { configs } from "./app/errors/configs/index";
import { configs } from "./app/configs/index";
import { prisma } from "./app/lib/prisma";
// import "./app/queues/worker";
import "./app/queues/worker";
async function main() {
try {
+1 -1
View File
@@ -1,6 +1,6 @@
import { fileURLToPath } from "node:url";
import path from "path";
import { configs } from "./app/errors/configs";
import { configs } from "./app/configs";
import { accountSwaggerDocs } from "./app/modules/account/account.swagger";
import { orderSwaggerDocs } from "./app/modules/order/order.swagger";
import { planSwaggerDocs } from "./app/modules/plan/plan.swagger";