Enable Montact CRM integration and admin CRM APIs.
This commit is contained in:
@@ -18,6 +18,10 @@ RUN_SEED=false
|
||||
|
||||
JWT_SECRET=change-me-to-a-long-random-string
|
||||
JWT_EXPIRES_IN=7d
|
||||
|
||||
# Montact CRM (اختیاری): توکن ثابت mk_live_...؛ اگر خالی باشد فقط JWT ادمین قبول میشود
|
||||
# MONTACT_API_TOKEN=
|
||||
# MONTACT_STORE_ID=1
|
||||
MOCK_OTP_CODE=1234
|
||||
OTP_TTL_MINUTES=5
|
||||
|
||||
|
||||
+592
-2
@@ -134757,6 +134757,7 @@ var require_crm_controller = __commonJS({
|
||||
Object.defineProperty(exports2, "__esModule", { value: true });
|
||||
exports2.CrmController = void 0;
|
||||
var common_12 = require_common();
|
||||
var swagger_12 = require_dist6();
|
||||
var client_1 = require_default2();
|
||||
var auth_decorators_1 = require_auth_decorators();
|
||||
var crm_service_1 = require_crm_service();
|
||||
@@ -134803,6 +134804,8 @@ var require_crm_controller = __commonJS({
|
||||
__metadata2("design:returntype", void 0)
|
||||
], CrmController.prototype, "updateOpportunity", null);
|
||||
exports2.CrmController = CrmController = __decorate2([
|
||||
(0, swagger_12.ApiTags)("Crm"),
|
||||
(0, swagger_12.ApiBearerAuth)(),
|
||||
(0, common_12.Controller)("admin/crm"),
|
||||
(0, auth_decorators_1.AuthType)("admin"),
|
||||
(0, auth_decorators_1.Roles)("admin", "sales"),
|
||||
@@ -134832,6 +134835,7 @@ var require_customer_crm_controller = __commonJS({
|
||||
Object.defineProperty(exports2, "__esModule", { value: true });
|
||||
exports2.AdminNextActionsController = exports2.CustomerCrmController = void 0;
|
||||
var common_12 = require_common();
|
||||
var swagger_12 = require_dist6();
|
||||
var auth_decorators_1 = require_auth_decorators();
|
||||
var current_user_decorator_1 = require_current_user_decorator();
|
||||
var crm_service_1 = require_crm_service();
|
||||
@@ -134912,6 +134916,8 @@ var require_customer_crm_controller = __commonJS({
|
||||
__metadata2("design:returntype", void 0)
|
||||
], CustomerCrmController.prototype, "setAssignment", null);
|
||||
exports2.CustomerCrmController = CustomerCrmController = __decorate2([
|
||||
(0, swagger_12.ApiTags)("CustomerCrm"),
|
||||
(0, swagger_12.ApiBearerAuth)(),
|
||||
(0, common_12.Controller)("admin/customers"),
|
||||
(0, auth_decorators_1.AuthType)("admin"),
|
||||
(0, auth_decorators_1.Roles)("admin", "sales"),
|
||||
@@ -134939,6 +134945,8 @@ var require_customer_crm_controller = __commonJS({
|
||||
__metadata2("design:returntype", void 0)
|
||||
], AdminNextActionsController.prototype, "update", null);
|
||||
exports2.AdminNextActionsController = AdminNextActionsController = __decorate2([
|
||||
(0, swagger_12.ApiTags)("CustomerCrm"),
|
||||
(0, swagger_12.ApiBearerAuth)(),
|
||||
(0, common_12.Controller)("admin/next-actions"),
|
||||
(0, auth_decorators_1.AuthType)("admin"),
|
||||
(0, auth_decorators_1.Roles)("admin", "sales"),
|
||||
@@ -134968,6 +134976,7 @@ var require_admin_agents_controller = __commonJS({
|
||||
Object.defineProperty(exports2, "__esModule", { value: true });
|
||||
exports2.AdminAssignmentRequestsController = exports2.AdminAgentsController = void 0;
|
||||
var common_12 = require_common();
|
||||
var swagger_12 = require_dist6();
|
||||
var client_1 = require_default2();
|
||||
var auth_decorators_1 = require_auth_decorators();
|
||||
var current_user_decorator_1 = require_current_user_decorator();
|
||||
@@ -135025,6 +135034,8 @@ var require_admin_agents_controller = __commonJS({
|
||||
__metadata2("design:returntype", void 0)
|
||||
], AdminAgentsController.prototype, "customers", null);
|
||||
exports2.AdminAgentsController = AdminAgentsController = __decorate2([
|
||||
(0, swagger_12.ApiTags)("AdminAgents"),
|
||||
(0, swagger_12.ApiBearerAuth)(),
|
||||
(0, common_12.Controller)("admin/agents"),
|
||||
(0, auth_decorators_1.AuthType)("admin"),
|
||||
(0, auth_decorators_1.Roles)("admin", "sales"),
|
||||
@@ -135063,6 +135074,8 @@ var require_admin_agents_controller = __commonJS({
|
||||
__metadata2("design:returntype", void 0)
|
||||
], AdminAssignmentRequestsController.prototype, "review", null);
|
||||
exports2.AdminAssignmentRequestsController = AdminAssignmentRequestsController = __decorate2([
|
||||
(0, swagger_12.ApiTags)("AdminAgents"),
|
||||
(0, swagger_12.ApiBearerAuth)(),
|
||||
(0, common_12.Controller)("admin/assignment-requests"),
|
||||
(0, auth_decorators_1.AuthType)("admin"),
|
||||
(0, auth_decorators_1.Roles)("admin", "sales"),
|
||||
@@ -142743,6 +142756,579 @@ var require_internal_messages_module = __commonJS({
|
||||
}
|
||||
});
|
||||
|
||||
// dist/montact/montact-auth.guard.js
|
||||
var require_montact_auth_guard = __commonJS({
|
||||
"dist/montact/montact-auth.guard.js"(exports2) {
|
||||
"use strict";
|
||||
var __decorate2 = exports2 && exports2.__decorate || function(decorators, target, key, desc) {
|
||||
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
||||
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
||||
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
||||
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
||||
};
|
||||
var __metadata2 = exports2 && exports2.__metadata || function(k, v) {
|
||||
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
||||
};
|
||||
Object.defineProperty(exports2, "__esModule", { value: true });
|
||||
exports2.MontactAuthGuard = void 0;
|
||||
var common_12 = require_common();
|
||||
var config_1 = require_config2();
|
||||
var jwt_1 = require_jwt();
|
||||
var jwt_secret_1 = require_jwt_secret();
|
||||
var MontactAuthGuard = class MontactAuthGuard {
|
||||
static {
|
||||
__name(this, "MontactAuthGuard");
|
||||
}
|
||||
jwt;
|
||||
config;
|
||||
constructor(jwt, config) {
|
||||
this.jwt = jwt;
|
||||
this.config = config;
|
||||
}
|
||||
async canActivate(context) {
|
||||
const request = context.switchToHttp().getRequest();
|
||||
const authHeader = request.headers.authorization;
|
||||
if (!authHeader?.startsWith("Bearer ")) {
|
||||
throw new common_12.UnauthorizedException("\u062A\u0648\u06A9\u0646 \u0646\u0627\u0645\u0639\u062A\u0628\u0631 \u06CC\u0627 \u0645\u0646\u0642\u0636\u06CC \u0634\u062F\u0647 \u0627\u0633\u062A");
|
||||
}
|
||||
const token = authHeader.slice(7).trim();
|
||||
const montactToken = this.config.get("MONTACT_API_TOKEN")?.trim();
|
||||
if (montactToken && token === montactToken) {
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
const payload = await this.jwt.verifyAsync(token, {
|
||||
secret: (0, jwt_secret_1.resolveJwtSecret)(this.config)
|
||||
});
|
||||
if (payload.type === "admin") {
|
||||
return true;
|
||||
}
|
||||
} catch {
|
||||
}
|
||||
throw new common_12.UnauthorizedException("\u062A\u0648\u06A9\u0646 \u0646\u0627\u0645\u0639\u062A\u0628\u0631 \u06CC\u0627 \u0645\u0646\u0642\u0636\u06CC \u0634\u062F\u0647 \u0627\u0633\u062A");
|
||||
}
|
||||
};
|
||||
exports2.MontactAuthGuard = MontactAuthGuard;
|
||||
exports2.MontactAuthGuard = MontactAuthGuard = __decorate2([
|
||||
(0, common_12.Injectable)(),
|
||||
__metadata2("design:paramtypes", [
|
||||
jwt_1.JwtService,
|
||||
config_1.ConfigService
|
||||
])
|
||||
], MontactAuthGuard);
|
||||
}
|
||||
});
|
||||
|
||||
// dist/montact/montact.service.js
|
||||
var require_montact_service = __commonJS({
|
||||
"dist/montact/montact.service.js"(exports2) {
|
||||
"use strict";
|
||||
var __decorate2 = exports2 && exports2.__decorate || function(decorators, target, key, desc) {
|
||||
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
||||
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
||||
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
||||
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
||||
};
|
||||
var __metadata2 = exports2 && exports2.__metadata || function(k, v) {
|
||||
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
||||
};
|
||||
Object.defineProperty(exports2, "__esModule", { value: true });
|
||||
exports2.MontactService = void 0;
|
||||
var common_12 = require_common();
|
||||
var client_1 = require_default2();
|
||||
var config_1 = require_config2();
|
||||
var prisma_service_1 = require_prisma_service();
|
||||
var name_1 = require_name();
|
||||
var MontactService = class MontactService {
|
||||
static {
|
||||
__name(this, "MontactService");
|
||||
}
|
||||
prisma;
|
||||
config;
|
||||
constructor(prisma, config) {
|
||||
this.prisma = prisma;
|
||||
this.config = config;
|
||||
}
|
||||
storeId() {
|
||||
const raw2 = this.config.get("MONTACT_STORE_ID");
|
||||
const n = raw2 ? Number.parseInt(raw2, 10) : 1;
|
||||
return Number.isFinite(n) ? n : 1;
|
||||
}
|
||||
async listCustomers(page = 1, limit = 50, updatedAfter) {
|
||||
const safePage = Math.max(1, page);
|
||||
const safeLimit = Math.min(100, Math.max(1, limit));
|
||||
const skip2 = (safePage - 1) * safeLimit;
|
||||
const updatedFilter = updatedAfter ? { updatedAt: { gte: new Date(updatedAfter) } } : {};
|
||||
const where = {
|
||||
appRole: client_1.AppUserRole.customer,
|
||||
...updatedFilter
|
||||
};
|
||||
const [total, rows] = await Promise.all([
|
||||
this.prisma.customer.count({ where }),
|
||||
this.prisma.customer.findMany({
|
||||
where,
|
||||
orderBy: { updatedAt: "desc" },
|
||||
skip: skip2,
|
||||
take: safeLimit,
|
||||
include: {
|
||||
orders: { select: { finalAmount: true } }
|
||||
}
|
||||
})
|
||||
]);
|
||||
const data = rows.map((c) => this.mapCustomerListItem(c, c.orders));
|
||||
const lastPage = Math.max(1, Math.ceil(total / safeLimit));
|
||||
return {
|
||||
data,
|
||||
meta: {
|
||||
current_page: safePage,
|
||||
per_page: safeLimit,
|
||||
total,
|
||||
last_page: lastPage
|
||||
}
|
||||
};
|
||||
}
|
||||
async getCustomer(id) {
|
||||
const customer = await this.prisma.customer.findFirst({
|
||||
where: { id, appRole: client_1.AppUserRole.customer },
|
||||
include: {
|
||||
orders: {
|
||||
orderBy: { createdAt: "desc" },
|
||||
include: { items: true }
|
||||
},
|
||||
addresses: true,
|
||||
favorites: { include: { product: { select: { id: true, name: true } } } }
|
||||
}
|
||||
});
|
||||
if (!customer)
|
||||
throw new common_12.NotFoundException("\u0645\u0646\u0628\u0639 \u06CC\u0627\u0641\u062A \u0646\u0634\u062F");
|
||||
const orders = customer.orders.map((o) => this.mapOrder(o, false));
|
||||
const addresses = customer.addresses.map((a) => this.mapAddress(a, customer.id));
|
||||
const summary = this.buildSummary(customer.orders);
|
||||
return {
|
||||
...this.mapCustomerBase(customer),
|
||||
orders,
|
||||
summary,
|
||||
addresses,
|
||||
transactions: [],
|
||||
engagement: this.mapEngagement(customer.favorites)
|
||||
};
|
||||
}
|
||||
async listCustomerOrders(customerId, updatedAfter) {
|
||||
await this.assertCustomer(customerId);
|
||||
const orders = await this.prisma.order.findMany({
|
||||
where: {
|
||||
userId: customerId,
|
||||
...updatedAfter ? { updatedAt: { gte: new Date(updatedAfter) } } : {}
|
||||
},
|
||||
orderBy: { createdAt: "desc" },
|
||||
include: { items: true }
|
||||
});
|
||||
return { data: orders.map((o) => this.mapOrder(o, true)) };
|
||||
}
|
||||
async listCustomerAddresses(customerId) {
|
||||
await this.assertCustomer(customerId);
|
||||
const rows = await this.prisma.customerAddress.findMany({
|
||||
where: { customerId },
|
||||
orderBy: { createdAt: "desc" }
|
||||
});
|
||||
return { data: rows.map((a) => this.mapAddress(a, customerId)) };
|
||||
}
|
||||
async listCustomerTransactions(_customerId) {
|
||||
await this.assertCustomer(_customerId);
|
||||
return { data: [] };
|
||||
}
|
||||
async getCustomerEngagement(customerId) {
|
||||
await this.assertCustomer(customerId);
|
||||
const favorites = await this.prisma.productFavorite.findMany({
|
||||
where: { customerId },
|
||||
include: { product: { select: { id: true, name: true } } },
|
||||
orderBy: { createdAt: "desc" }
|
||||
});
|
||||
return this.mapEngagement(favorites);
|
||||
}
|
||||
async getOrder(orderId) {
|
||||
const order = await this.prisma.order.findUnique({
|
||||
where: { id: orderId },
|
||||
include: {
|
||||
items: true,
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
firstName: true,
|
||||
lastName: true,
|
||||
phone: true
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
if (!order)
|
||||
throw new common_12.NotFoundException("\u0645\u0646\u0628\u0639 \u06CC\u0627\u0641\u062A \u0646\u0634\u062F");
|
||||
return this.mapOrder(order, true);
|
||||
}
|
||||
async assertCustomer(id) {
|
||||
const c = await this.prisma.customer.findFirst({
|
||||
where: { id, appRole: client_1.AppUserRole.customer },
|
||||
select: { id: true }
|
||||
});
|
||||
if (!c)
|
||||
throw new common_12.NotFoundException("\u0645\u0646\u0628\u0639 \u06CC\u0627\u0641\u062A \u0646\u0634\u062F");
|
||||
}
|
||||
mapCustomerListItem(c, orders) {
|
||||
const summary = this.buildSummary(orders);
|
||||
return {
|
||||
...this.mapCustomerBase(c),
|
||||
orders: [],
|
||||
summary,
|
||||
addresses: [],
|
||||
transactions: [],
|
||||
engagement: { saved: [], likes: [], comments: [] }
|
||||
};
|
||||
}
|
||||
mapCustomerBase(c) {
|
||||
const name = (0, name_1.joinFullName)(c.firstName, c.lastName);
|
||||
return {
|
||||
id: c.id,
|
||||
name,
|
||||
first_name: c.firstName,
|
||||
last_name: c.lastName,
|
||||
phone: c.phone,
|
||||
email: null,
|
||||
username: c.phone,
|
||||
avatar: c.avatarUrl,
|
||||
birthday: null,
|
||||
gender: null,
|
||||
type: c.customerType === "retail" ? "individual" : "business",
|
||||
store_id: this.storeId(),
|
||||
created_at: c.createdAt.toISOString(),
|
||||
updated_at: c.updatedAt.toISOString(),
|
||||
description: c.businessName,
|
||||
bio: null,
|
||||
job: null,
|
||||
cash_balance: 0,
|
||||
gold_balance: 0
|
||||
};
|
||||
}
|
||||
buildSummary(orders) {
|
||||
let totalSpent = 0;
|
||||
for (const o of orders) {
|
||||
totalSpent += o.finalAmount ?? 0;
|
||||
}
|
||||
return {
|
||||
total_orders: orders.length,
|
||||
total_spent: Math.round(totalSpent)
|
||||
};
|
||||
}
|
||||
mapOrder(o, withNested) {
|
||||
const totalPrice = o.finalAmount ?? o.totalAmount ?? 0;
|
||||
const qty = o.items.length;
|
||||
const base = {
|
||||
id: o.id,
|
||||
user_id: o.userId,
|
||||
store_id: this.storeId(),
|
||||
code: o.orderNumber,
|
||||
sts: this.mapOrderStatus(o.status),
|
||||
payment_sts: this.mapPaymentStatus(o.status),
|
||||
payment_type: "unknown",
|
||||
total_price: Math.round(totalPrice),
|
||||
discount_total_price: Math.round(totalPrice),
|
||||
total_discounts: Math.round(o.totalDiscount ?? 0),
|
||||
total_tax: 0,
|
||||
total_qty: qty,
|
||||
profit: 0,
|
||||
percent_profit: 0,
|
||||
sum_gold: 0,
|
||||
sum_silver: 0,
|
||||
address: null,
|
||||
address_id: null,
|
||||
note: o.notes,
|
||||
cancel_reason: null,
|
||||
cancelled_at: null,
|
||||
closed_at: null,
|
||||
transaction_id: null,
|
||||
code_discount: null,
|
||||
discount_code_id: null,
|
||||
created_at: o.createdAt.toISOString(),
|
||||
updated_at: o.updatedAt.toISOString(),
|
||||
deleted_at: null,
|
||||
order_items: o.items.map((item) => ({
|
||||
id: item.id,
|
||||
order_id: item.orderId,
|
||||
product_id: item.productId ?? "",
|
||||
product_name: item.productName,
|
||||
qty: 1,
|
||||
price: Math.round(item.unitPrice ?? 0),
|
||||
weight: item.confirmedMeterage ?? item.requestedMeterage,
|
||||
total_price: Math.round(item.lineTotal ?? 0)
|
||||
}))
|
||||
};
|
||||
if (withNested && o.user) {
|
||||
base.user = {
|
||||
id: o.user.id,
|
||||
name: (0, name_1.joinFullName)(o.user.firstName, o.user.lastName),
|
||||
phone: o.user.phone
|
||||
};
|
||||
base.store = { id: this.storeId() };
|
||||
}
|
||||
return base;
|
||||
}
|
||||
mapOrderStatus(status) {
|
||||
if (status === client_1.OrderStatus.cancelled || status === client_1.OrderStatus.rejected) {
|
||||
return "cancelled";
|
||||
}
|
||||
if (status === client_1.OrderStatus.delivered)
|
||||
return "delivered";
|
||||
if (status === client_1.OrderStatus.shipped || status === client_1.OrderStatus.ready_to_ship) {
|
||||
return "shipped";
|
||||
}
|
||||
if (status === client_1.OrderStatus.final_approved || status === client_1.OrderStatus.preparing) {
|
||||
return "processing";
|
||||
}
|
||||
return "pending";
|
||||
}
|
||||
mapPaymentStatus(status) {
|
||||
if (status === client_1.OrderStatus.cash_confirmed || status === client_1.OrderStatus.checks_approved || status === client_1.OrderStatus.delivered) {
|
||||
return "paid";
|
||||
}
|
||||
if (status === client_1.OrderStatus.cancelled || status === client_1.OrderStatus.rejected) {
|
||||
return "cancelled";
|
||||
}
|
||||
return "unpaid";
|
||||
}
|
||||
mapAddress(a, userId) {
|
||||
return {
|
||||
id: a.id,
|
||||
user_id: userId,
|
||||
province: "",
|
||||
city: "",
|
||||
address: a.text,
|
||||
postal_code: "",
|
||||
receiver_name: a.label ?? "",
|
||||
receiver_phone: "",
|
||||
is_default: a.isDefault
|
||||
};
|
||||
}
|
||||
mapEngagement(favorites) {
|
||||
return {
|
||||
saved: favorites.map((f) => ({
|
||||
product_id: f.product.id,
|
||||
product_name: f.product.name,
|
||||
saved_at: f.createdAt.toISOString()
|
||||
})),
|
||||
likes: [],
|
||||
comments: []
|
||||
};
|
||||
}
|
||||
};
|
||||
exports2.MontactService = MontactService;
|
||||
exports2.MontactService = MontactService = __decorate2([
|
||||
(0, common_12.Injectable)(),
|
||||
__metadata2("design:paramtypes", [
|
||||
prisma_service_1.PrismaService,
|
||||
config_1.ConfigService
|
||||
])
|
||||
], MontactService);
|
||||
}
|
||||
});
|
||||
|
||||
// dist/montact/montact.controller.js
|
||||
var require_montact_controller = __commonJS({
|
||||
"dist/montact/montact.controller.js"(exports2) {
|
||||
"use strict";
|
||||
var __decorate2 = exports2 && exports2.__decorate || function(decorators, target, key, desc) {
|
||||
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
||||
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
||||
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
||||
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
||||
};
|
||||
var __metadata2 = exports2 && exports2.__metadata || function(k, v) {
|
||||
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
||||
};
|
||||
var __param2 = exports2 && exports2.__param || function(paramIndex, decorator) {
|
||||
return function(target, key) {
|
||||
decorator(target, key, paramIndex);
|
||||
};
|
||||
};
|
||||
Object.defineProperty(exports2, "__esModule", { value: true });
|
||||
exports2.MontactController = void 0;
|
||||
var common_12 = require_common();
|
||||
var swagger_12 = require_dist6();
|
||||
var auth_decorators_1 = require_auth_decorators();
|
||||
var montact_auth_guard_1 = require_montact_auth_guard();
|
||||
var montact_service_1 = require_montact_service();
|
||||
var MontactController = class MontactController {
|
||||
static {
|
||||
__name(this, "MontactController");
|
||||
}
|
||||
montact;
|
||||
constructor(montact) {
|
||||
this.montact = montact;
|
||||
}
|
||||
listCustomers(page, limit, updatedAfter) {
|
||||
const p = page ? Number.parseInt(page, 10) : 1;
|
||||
const l = limit ? Number.parseInt(limit, 10) : 50;
|
||||
return this.montact.listCustomers(p, l, updatedAfter);
|
||||
}
|
||||
createCustomer() {
|
||||
throw new common_12.NotImplementedException("\u062B\u0628\u062A \u0645\u0634\u062A\u0631\u06CC \u0627\u0632 CRM \u062F\u0631 \u0627\u06CC\u0646 \u0641\u0627\u0632 \u0641\u0639\u0627\u0644 \u0646\u06CC\u0633\u062A");
|
||||
}
|
||||
getCustomer(id) {
|
||||
return this.montact.getCustomer(id);
|
||||
}
|
||||
customerOrders(id, updatedAfter) {
|
||||
return this.montact.listCustomerOrders(id, updatedAfter);
|
||||
}
|
||||
createOrder() {
|
||||
throw new common_12.NotImplementedException("\u062B\u0628\u062A \u0633\u0641\u0627\u0631\u0634 \u0627\u0632 CRM \u062F\u0631 \u0627\u06CC\u0646 \u0641\u0627\u0632 \u0641\u0639\u0627\u0644 \u0646\u06CC\u0633\u062A");
|
||||
}
|
||||
customerAddresses(id) {
|
||||
return this.montact.listCustomerAddresses(id);
|
||||
}
|
||||
customerTransactions(id) {
|
||||
return this.montact.listCustomerTransactions(id);
|
||||
}
|
||||
customerEngagement(id) {
|
||||
return this.montact.getCustomerEngagement(id);
|
||||
}
|
||||
getOrder(orderId) {
|
||||
return this.montact.getOrder(orderId);
|
||||
}
|
||||
registerWebhook() {
|
||||
throw new common_12.NotImplementedException("Webhook Montact \u062F\u0631 \u0641\u0627\u0632 \u06F3 \u067E\u06CC\u0627\u062F\u0647 \u0645\u06CC\u200C\u0634\u0648\u062F");
|
||||
}
|
||||
getWebhook() {
|
||||
throw new common_12.NotImplementedException("Webhook Montact \u062F\u0631 \u0641\u0627\u0632 \u06F3 \u067E\u06CC\u0627\u062F\u0647 \u0645\u06CC\u200C\u0634\u0648\u062F");
|
||||
}
|
||||
deleteWebhook() {
|
||||
throw new common_12.NotImplementedException("Webhook Montact \u062F\u0631 \u0641\u0627\u0632 \u06F3 \u067E\u06CC\u0627\u062F\u0647 \u0645\u06CC\u200C\u0634\u0648\u062F");
|
||||
}
|
||||
};
|
||||
exports2.MontactController = MontactController;
|
||||
__decorate2([
|
||||
(0, common_12.Get)("customers"),
|
||||
__param2(0, (0, common_12.Query)("page")),
|
||||
__param2(1, (0, common_12.Query)("limit")),
|
||||
__param2(2, (0, common_12.Query)("updated_after")),
|
||||
__metadata2("design:type", Function),
|
||||
__metadata2("design:paramtypes", [String, String, String]),
|
||||
__metadata2("design:returntype", void 0)
|
||||
], MontactController.prototype, "listCustomers", null);
|
||||
__decorate2([
|
||||
(0, common_12.Post)("customers"),
|
||||
__metadata2("design:type", Function),
|
||||
__metadata2("design:paramtypes", []),
|
||||
__metadata2("design:returntype", void 0)
|
||||
], MontactController.prototype, "createCustomer", null);
|
||||
__decorate2([
|
||||
(0, common_12.Get)("customers/:id"),
|
||||
__param2(0, (0, common_12.Param)("id")),
|
||||
__metadata2("design:type", Function),
|
||||
__metadata2("design:paramtypes", [String]),
|
||||
__metadata2("design:returntype", void 0)
|
||||
], MontactController.prototype, "getCustomer", null);
|
||||
__decorate2([
|
||||
(0, common_12.Get)("customers/:id/orders"),
|
||||
__param2(0, (0, common_12.Param)("id")),
|
||||
__param2(1, (0, common_12.Query)("updated_after")),
|
||||
__metadata2("design:type", Function),
|
||||
__metadata2("design:paramtypes", [String, String]),
|
||||
__metadata2("design:returntype", void 0)
|
||||
], MontactController.prototype, "customerOrders", null);
|
||||
__decorate2([
|
||||
(0, common_12.Post)("customers/:id/orders"),
|
||||
__metadata2("design:type", Function),
|
||||
__metadata2("design:paramtypes", []),
|
||||
__metadata2("design:returntype", void 0)
|
||||
], MontactController.prototype, "createOrder", null);
|
||||
__decorate2([
|
||||
(0, common_12.Get)("customers/:id/addresses"),
|
||||
__param2(0, (0, common_12.Param)("id")),
|
||||
__metadata2("design:type", Function),
|
||||
__metadata2("design:paramtypes", [String]),
|
||||
__metadata2("design:returntype", void 0)
|
||||
], MontactController.prototype, "customerAddresses", null);
|
||||
__decorate2([
|
||||
(0, common_12.Get)("customers/:id/transactions"),
|
||||
__param2(0, (0, common_12.Param)("id")),
|
||||
__metadata2("design:type", Function),
|
||||
__metadata2("design:paramtypes", [String]),
|
||||
__metadata2("design:returntype", void 0)
|
||||
], MontactController.prototype, "customerTransactions", null);
|
||||
__decorate2([
|
||||
(0, common_12.Get)("customers/:id/engagement"),
|
||||
__param2(0, (0, common_12.Param)("id")),
|
||||
__metadata2("design:type", Function),
|
||||
__metadata2("design:paramtypes", [String]),
|
||||
__metadata2("design:returntype", void 0)
|
||||
], MontactController.prototype, "customerEngagement", null);
|
||||
__decorate2([
|
||||
(0, common_12.Get)("orders/:orderId"),
|
||||
__param2(0, (0, common_12.Param)("orderId")),
|
||||
__metadata2("design:type", Function),
|
||||
__metadata2("design:paramtypes", [String]),
|
||||
__metadata2("design:returntype", void 0)
|
||||
], MontactController.prototype, "getOrder", null);
|
||||
__decorate2([
|
||||
(0, common_12.Post)("webhooks/register"),
|
||||
__metadata2("design:type", Function),
|
||||
__metadata2("design:paramtypes", []),
|
||||
__metadata2("design:returntype", void 0)
|
||||
], MontactController.prototype, "registerWebhook", null);
|
||||
__decorate2([
|
||||
(0, common_12.Get)("webhooks/:id"),
|
||||
__metadata2("design:type", Function),
|
||||
__metadata2("design:paramtypes", []),
|
||||
__metadata2("design:returntype", void 0)
|
||||
], MontactController.prototype, "getWebhook", null);
|
||||
__decorate2([
|
||||
(0, common_12.Delete)("webhooks/:id"),
|
||||
__metadata2("design:type", Function),
|
||||
__metadata2("design:paramtypes", []),
|
||||
__metadata2("design:returntype", void 0)
|
||||
], MontactController.prototype, "deleteWebhook", null);
|
||||
exports2.MontactController = MontactController = __decorate2([
|
||||
(0, auth_decorators_1.Public)(),
|
||||
(0, swagger_12.ApiTags)("Montact"),
|
||||
(0, swagger_12.ApiBearerAuth)(),
|
||||
(0, common_12.UseGuards)(montact_auth_guard_1.MontactAuthGuard),
|
||||
(0, common_12.Controller)("montact"),
|
||||
__metadata2("design:paramtypes", [montact_service_1.MontactService])
|
||||
], MontactController);
|
||||
}
|
||||
});
|
||||
|
||||
// dist/montact/montact.module.js
|
||||
var require_montact_module = __commonJS({
|
||||
"dist/montact/montact.module.js"(exports2) {
|
||||
"use strict";
|
||||
var __decorate2 = exports2 && exports2.__decorate || function(decorators, target, key, desc) {
|
||||
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
||||
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
||||
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
||||
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
||||
};
|
||||
Object.defineProperty(exports2, "__esModule", { value: true });
|
||||
exports2.MontactModule = void 0;
|
||||
var common_12 = require_common();
|
||||
var auth_module_1 = require_auth_module();
|
||||
var montact_controller_1 = require_montact_controller();
|
||||
var montact_service_1 = require_montact_service();
|
||||
var montact_auth_guard_1 = require_montact_auth_guard();
|
||||
var MontactModule = class MontactModule {
|
||||
static {
|
||||
__name(this, "MontactModule");
|
||||
}
|
||||
};
|
||||
exports2.MontactModule = MontactModule;
|
||||
exports2.MontactModule = MontactModule = __decorate2([
|
||||
(0, common_12.Module)({
|
||||
imports: [auth_module_1.AuthModule],
|
||||
controllers: [montact_controller_1.MontactController],
|
||||
providers: [montact_service_1.MontactService, montact_auth_guard_1.MontactAuthGuard]
|
||||
})
|
||||
], MontactModule);
|
||||
}
|
||||
});
|
||||
|
||||
// dist/app.module.js
|
||||
var require_app_module = __commonJS({
|
||||
"dist/app.module.js"(exports2) {
|
||||
@@ -142782,6 +143368,7 @@ var require_app_module = __commonJS({
|
||||
var addresses_module_1 = require_addresses_module();
|
||||
var internal_messages_module_1 = require_internal_messages_module();
|
||||
var crm_module_1 = require_crm_module();
|
||||
var montact_module_1 = require_montact_module();
|
||||
var AppModule = class AppModule {
|
||||
static {
|
||||
__name(this, "AppModule");
|
||||
@@ -142814,7 +143401,8 @@ var require_app_module = __commonJS({
|
||||
banners_module_1.BannersModule,
|
||||
addresses_module_1.AddressesModule,
|
||||
internal_messages_module_1.InternalMessagesModule,
|
||||
crm_module_1.CrmModule
|
||||
crm_module_1.CrmModule,
|
||||
montact_module_1.MontactModule
|
||||
],
|
||||
providers: [
|
||||
{
|
||||
@@ -143067,8 +143655,10 @@ async function bootstrap() {
|
||||
}));
|
||||
const swaggerConfig = new swagger_1.DocumentBuilder().setTitle("Fabric Trade API").setDescription("API \u0633\u0631\u0648\u0631 \u062A\u062C\u0627\u0631\u062A \u067E\u0627\u0631\u0686\u0647").setVersion("1.0").addBearerAuth().build();
|
||||
const document2 = swagger_1.SwaggerModule.createDocument(app, swaggerConfig);
|
||||
document2.security = [{ bearer: [] }];
|
||||
swagger_1.SwaggerModule.setup(`${globalPrefix}/docs`, app, document2, {
|
||||
customSwaggerUiPath: swaggerUiAssetsPath()
|
||||
customSwaggerUiPath: swaggerUiAssetsPath(),
|
||||
swaggerOptions: { persistAuthorization: true }
|
||||
});
|
||||
const port = Number(process.env.PORT) || 3e3;
|
||||
await app.listen(port);
|
||||
|
||||
@@ -25,6 +25,7 @@ import { BannersModule } from './banners/banners.module';
|
||||
import { AddressesModule } from './addresses/addresses.module';
|
||||
import { InternalMessagesModule } from './internal-messages/internal-messages.module';
|
||||
import { CrmModule } from './crm/crm.module';
|
||||
import { MontactModule } from './montact/montact.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@@ -52,6 +53,7 @@ import { CrmModule } from './crm/crm.module';
|
||||
AddressesModule,
|
||||
InternalMessagesModule,
|
||||
CrmModule,
|
||||
MontactModule,
|
||||
],
|
||||
providers: [
|
||||
{
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
Post,
|
||||
Query,
|
||||
} from '@nestjs/common';
|
||||
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
|
||||
import { AssignmentRequestStatus } from '@prisma/client';
|
||||
import { AuthType, Roles } from '../common/decorators/auth.decorators';
|
||||
import { CurrentUser } from '../common/decorators/current-user.decorator';
|
||||
@@ -18,6 +19,8 @@ import {
|
||||
UpdateAdminAgentDto,
|
||||
} from './dto/crm.dto';
|
||||
|
||||
@ApiTags('AdminAgents')
|
||||
@ApiBearerAuth()
|
||||
@Controller('admin/agents')
|
||||
@AuthType('admin')
|
||||
@Roles('admin', 'sales')
|
||||
@@ -45,6 +48,8 @@ export class AdminAgentsController {
|
||||
}
|
||||
}
|
||||
|
||||
@ApiTags('AdminAgents')
|
||||
@ApiBearerAuth()
|
||||
@Controller('admin/assignment-requests')
|
||||
@AuthType('admin')
|
||||
@Roles('admin', 'sales')
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import { Controller, Get, Param, Patch, Body, Query } from '@nestjs/common';
|
||||
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
|
||||
import { SalesPipelineStage } from '@prisma/client';
|
||||
import { AuthType, Roles } from '../common/decorators/auth.decorators';
|
||||
import { CrmService } from './crm.service';
|
||||
import { UpdateOpportunityDto } from './dto/crm.dto';
|
||||
|
||||
@ApiTags('Crm')
|
||||
@ApiBearerAuth()
|
||||
@Controller('admin/crm')
|
||||
@AuthType('admin')
|
||||
@Roles('admin', 'sales')
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
Patch,
|
||||
Post,
|
||||
} from '@nestjs/common';
|
||||
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
|
||||
import { AuthType, Roles } from '../common/decorators/auth.decorators';
|
||||
import { CurrentUser } from '../common/decorators/current-user.decorator';
|
||||
import type { AuthPrincipal } from '../common/decorators/current-user.decorator';
|
||||
@@ -18,6 +19,8 @@ import {
|
||||
UpdateNextActionDto,
|
||||
} from './dto/crm.dto';
|
||||
|
||||
@ApiTags('CustomerCrm')
|
||||
@ApiBearerAuth()
|
||||
@Controller('admin/customers')
|
||||
@AuthType('admin')
|
||||
@Roles('admin', 'sales')
|
||||
@@ -68,6 +71,8 @@ export class CustomerCrmController {
|
||||
}
|
||||
}
|
||||
|
||||
@ApiTags('CustomerCrm')
|
||||
@ApiBearerAuth()
|
||||
@Controller('admin/next-actions')
|
||||
@AuthType('admin')
|
||||
@Roles('admin', 'sales')
|
||||
|
||||
@@ -56,8 +56,11 @@ async function bootstrap() {
|
||||
.addBearerAuth()
|
||||
.build();
|
||||
const document = SwaggerModule.createDocument(app, swaggerConfig);
|
||||
// تا دکمه Authorize توکن را به همه endpointها (مثل admin/crm) بفرستد
|
||||
document.security = [{ bearer: [] }];
|
||||
SwaggerModule.setup(`${globalPrefix}/docs`, app, document, {
|
||||
customSwaggerUiPath: swaggerUiAssetsPath(),
|
||||
swaggerOptions: { persistAuthorization: true },
|
||||
});
|
||||
|
||||
const port = Number(process.env.PORT) || 3000;
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import {
|
||||
CanActivate,
|
||||
ExecutionContext,
|
||||
Injectable,
|
||||
UnauthorizedException,
|
||||
} from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { JwtService } from '@nestjs/jwt';
|
||||
import { resolveJwtSecret } from '../common/utils/jwt-secret';
|
||||
|
||||
@Injectable()
|
||||
export class MontactAuthGuard implements CanActivate {
|
||||
constructor(
|
||||
private readonly jwt: JwtService,
|
||||
private readonly config: ConfigService,
|
||||
) {}
|
||||
|
||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
const request = context.switchToHttp().getRequest<{
|
||||
headers: Record<string, string | undefined>;
|
||||
}>();
|
||||
const authHeader = request.headers.authorization;
|
||||
if (!authHeader?.startsWith('Bearer ')) {
|
||||
throw new UnauthorizedException('توکن نامعتبر یا منقضی شده است');
|
||||
}
|
||||
const token = authHeader.slice(7).trim();
|
||||
const montactToken = this.config.get<string>('MONTACT_API_TOKEN')?.trim();
|
||||
if (montactToken && token === montactToken) {
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
const payload = await this.jwt.verifyAsync<{ type?: string }>(token, {
|
||||
secret: resolveJwtSecret(this.config),
|
||||
});
|
||||
if (payload.type === 'admin') {
|
||||
return true;
|
||||
}
|
||||
} catch {
|
||||
// fall through
|
||||
}
|
||||
throw new UnauthorizedException('توکن نامعتبر یا منقضی شده است');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
import {
|
||||
Controller,
|
||||
Delete,
|
||||
Get,
|
||||
NotImplementedException,
|
||||
Param,
|
||||
Post,
|
||||
Query,
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
|
||||
import { Public } from '../common/decorators/auth.decorators';
|
||||
import { MontactAuthGuard } from './montact-auth.guard';
|
||||
import { MontactService } from './montact.service';
|
||||
|
||||
@Public()
|
||||
@ApiTags('Montact')
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(MontactAuthGuard)
|
||||
@Controller('montact')
|
||||
export class MontactController {
|
||||
constructor(private readonly montact: MontactService) {}
|
||||
|
||||
@Get('customers')
|
||||
listCustomers(
|
||||
@Query('page') page?: string,
|
||||
@Query('limit') limit?: string,
|
||||
@Query('updated_after') updatedAfter?: string,
|
||||
) {
|
||||
const p = page ? Number.parseInt(page, 10) : 1;
|
||||
const l = limit ? Number.parseInt(limit, 10) : 50;
|
||||
return this.montact.listCustomers(p, l, updatedAfter);
|
||||
}
|
||||
|
||||
@Post('customers')
|
||||
createCustomer() {
|
||||
throw new NotImplementedException('ثبت مشتری از CRM در این فاز فعال نیست');
|
||||
}
|
||||
|
||||
@Get('customers/:id')
|
||||
getCustomer(@Param('id') id: string) {
|
||||
return this.montact.getCustomer(id);
|
||||
}
|
||||
|
||||
@Get('customers/:id/orders')
|
||||
customerOrders(
|
||||
@Param('id') id: string,
|
||||
@Query('updated_after') updatedAfter?: string,
|
||||
) {
|
||||
return this.montact.listCustomerOrders(id, updatedAfter);
|
||||
}
|
||||
|
||||
@Post('customers/:id/orders')
|
||||
createOrder() {
|
||||
throw new NotImplementedException('ثبت سفارش از CRM در این فاز فعال نیست');
|
||||
}
|
||||
|
||||
@Get('customers/:id/addresses')
|
||||
customerAddresses(@Param('id') id: string) {
|
||||
return this.montact.listCustomerAddresses(id);
|
||||
}
|
||||
|
||||
@Get('customers/:id/transactions')
|
||||
customerTransactions(@Param('id') id: string) {
|
||||
return this.montact.listCustomerTransactions(id);
|
||||
}
|
||||
|
||||
@Get('customers/:id/engagement')
|
||||
customerEngagement(@Param('id') id: string) {
|
||||
return this.montact.getCustomerEngagement(id);
|
||||
}
|
||||
|
||||
@Get('orders/:orderId')
|
||||
getOrder(@Param('orderId') orderId: string) {
|
||||
return this.montact.getOrder(orderId);
|
||||
}
|
||||
|
||||
@Post('webhooks/register')
|
||||
registerWebhook() {
|
||||
throw new NotImplementedException('Webhook Montact در فاز ۳ پیاده میشود');
|
||||
}
|
||||
|
||||
@Get('webhooks/:id')
|
||||
getWebhook() {
|
||||
throw new NotImplementedException('Webhook Montact در فاز ۳ پیاده میشود');
|
||||
}
|
||||
|
||||
@Delete('webhooks/:id')
|
||||
deleteWebhook() {
|
||||
throw new NotImplementedException('Webhook Montact در فاز ۳ پیاده میشود');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { AuthModule } from '../auth/auth.module';
|
||||
import { MontactController } from './montact.controller';
|
||||
import { MontactService } from './montact.service';
|
||||
import { MontactAuthGuard } from './montact-auth.guard';
|
||||
|
||||
@Module({
|
||||
imports: [AuthModule],
|
||||
controllers: [MontactController],
|
||||
providers: [MontactService, MontactAuthGuard],
|
||||
})
|
||||
export class MontactModule {}
|
||||
@@ -0,0 +1,397 @@
|
||||
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||
import { AppUserRole, OrderStatus, Prisma } from '@prisma/client';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import { joinFullName } from '../common/utils/name';
|
||||
|
||||
type MontactCustomer = Record<string, unknown>;
|
||||
type MontactOrder = Record<string, unknown>;
|
||||
|
||||
@Injectable()
|
||||
export class MontactService {
|
||||
constructor(
|
||||
private readonly prisma: PrismaService,
|
||||
private readonly config: ConfigService,
|
||||
) {}
|
||||
|
||||
private storeId(): number {
|
||||
const raw = this.config.get<string>('MONTACT_STORE_ID');
|
||||
const n = raw ? Number.parseInt(raw, 10) : 1;
|
||||
return Number.isFinite(n) ? n : 1;
|
||||
}
|
||||
|
||||
async listCustomers(
|
||||
page = 1,
|
||||
limit = 50,
|
||||
updatedAfter?: string,
|
||||
) {
|
||||
const safePage = Math.max(1, page);
|
||||
const safeLimit = Math.min(100, Math.max(1, limit));
|
||||
const skip = (safePage - 1) * safeLimit;
|
||||
|
||||
const updatedFilter = updatedAfter
|
||||
? { updatedAt: { gte: new Date(updatedAfter) } }
|
||||
: {};
|
||||
|
||||
const where: Prisma.CustomerWhereInput = {
|
||||
appRole: AppUserRole.customer,
|
||||
...updatedFilter,
|
||||
};
|
||||
|
||||
const [total, rows] = await Promise.all([
|
||||
this.prisma.customer.count({ where }),
|
||||
this.prisma.customer.findMany({
|
||||
where,
|
||||
orderBy: { updatedAt: 'desc' },
|
||||
skip,
|
||||
take: safeLimit,
|
||||
include: {
|
||||
orders: { select: { finalAmount: true } },
|
||||
},
|
||||
}),
|
||||
]);
|
||||
|
||||
const data = rows.map((c) =>
|
||||
this.mapCustomerListItem(c, c.orders),
|
||||
);
|
||||
const lastPage = Math.max(1, Math.ceil(total / safeLimit));
|
||||
|
||||
return {
|
||||
data,
|
||||
meta: {
|
||||
current_page: safePage,
|
||||
per_page: safeLimit,
|
||||
total,
|
||||
last_page: lastPage,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async getCustomer(id: string) {
|
||||
const customer = await this.prisma.customer.findFirst({
|
||||
where: { id, appRole: AppUserRole.customer },
|
||||
include: {
|
||||
orders: {
|
||||
orderBy: { createdAt: 'desc' },
|
||||
include: { items: true },
|
||||
},
|
||||
addresses: true,
|
||||
favorites: { include: { product: { select: { id: true, name: true } } } },
|
||||
},
|
||||
});
|
||||
if (!customer) throw new NotFoundException('منبع یافت نشد');
|
||||
|
||||
const orders = customer.orders.map((o) => this.mapOrder(o, false));
|
||||
const addresses = customer.addresses.map((a) => this.mapAddress(a, customer.id));
|
||||
const summary = this.buildSummary(customer.orders);
|
||||
|
||||
return {
|
||||
...this.mapCustomerBase(customer),
|
||||
orders,
|
||||
summary,
|
||||
addresses,
|
||||
transactions: [],
|
||||
engagement: this.mapEngagement(customer.favorites),
|
||||
};
|
||||
}
|
||||
|
||||
async listCustomerOrders(
|
||||
customerId: string,
|
||||
updatedAfter?: string,
|
||||
) {
|
||||
await this.assertCustomer(customerId);
|
||||
const orders = await this.prisma.order.findMany({
|
||||
where: {
|
||||
userId: customerId,
|
||||
...(updatedAfter
|
||||
? { updatedAt: { gte: new Date(updatedAfter) } }
|
||||
: {}),
|
||||
},
|
||||
orderBy: { createdAt: 'desc' },
|
||||
include: { items: true },
|
||||
});
|
||||
return { data: orders.map((o) => this.mapOrder(o, true)) };
|
||||
}
|
||||
|
||||
async listCustomerAddresses(customerId: string) {
|
||||
await this.assertCustomer(customerId);
|
||||
const rows = await this.prisma.customerAddress.findMany({
|
||||
where: { customerId },
|
||||
orderBy: { createdAt: 'desc' },
|
||||
});
|
||||
return { data: rows.map((a) => this.mapAddress(a, customerId)) };
|
||||
}
|
||||
|
||||
async listCustomerTransactions(_customerId: string) {
|
||||
await this.assertCustomer(_customerId);
|
||||
return { data: [] as unknown[] };
|
||||
}
|
||||
|
||||
async getCustomerEngagement(customerId: string) {
|
||||
await this.assertCustomer(customerId);
|
||||
const favorites = await this.prisma.productFavorite.findMany({
|
||||
where: { customerId },
|
||||
include: { product: { select: { id: true, name: true } } },
|
||||
orderBy: { createdAt: 'desc' },
|
||||
});
|
||||
return this.mapEngagement(favorites);
|
||||
}
|
||||
|
||||
async getOrder(orderId: string) {
|
||||
const order = await this.prisma.order.findUnique({
|
||||
where: { id: orderId },
|
||||
include: {
|
||||
items: true,
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
firstName: true,
|
||||
lastName: true,
|
||||
phone: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
if (!order) throw new NotFoundException('منبع یافت نشد');
|
||||
return this.mapOrder(order, true);
|
||||
}
|
||||
|
||||
private async assertCustomer(id: string) {
|
||||
const c = await this.prisma.customer.findFirst({
|
||||
where: { id, appRole: AppUserRole.customer },
|
||||
select: { id: true },
|
||||
});
|
||||
if (!c) throw new NotFoundException('منبع یافت نشد');
|
||||
}
|
||||
|
||||
private mapCustomerListItem(
|
||||
c: {
|
||||
id: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
phone: string;
|
||||
customerType: string;
|
||||
avatarUrl: string | null;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
businessName: string | null;
|
||||
},
|
||||
orders: Array<{ finalAmount: number | null }>,
|
||||
): MontactCustomer {
|
||||
const summary = this.buildSummary(orders);
|
||||
return {
|
||||
...this.mapCustomerBase(c),
|
||||
orders: [],
|
||||
summary,
|
||||
addresses: [],
|
||||
transactions: [],
|
||||
engagement: { saved: [], likes: [], comments: [] },
|
||||
};
|
||||
}
|
||||
|
||||
private mapCustomerBase(c: {
|
||||
id: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
phone: string;
|
||||
customerType: string;
|
||||
avatarUrl: string | null;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
businessName: string | null;
|
||||
}): MontactCustomer {
|
||||
const name = joinFullName(c.firstName, c.lastName);
|
||||
return {
|
||||
id: c.id,
|
||||
name,
|
||||
first_name: c.firstName,
|
||||
last_name: c.lastName,
|
||||
phone: c.phone,
|
||||
email: null,
|
||||
username: c.phone,
|
||||
avatar: c.avatarUrl,
|
||||
birthday: null,
|
||||
gender: null,
|
||||
type: c.customerType === 'retail' ? 'individual' : 'business',
|
||||
store_id: this.storeId(),
|
||||
created_at: c.createdAt.toISOString(),
|
||||
updated_at: c.updatedAt.toISOString(),
|
||||
description: c.businessName,
|
||||
bio: null,
|
||||
job: null,
|
||||
cash_balance: 0,
|
||||
gold_balance: 0,
|
||||
};
|
||||
}
|
||||
|
||||
private buildSummary(
|
||||
orders: Array<{ finalAmount: number | null }>,
|
||||
) {
|
||||
let totalSpent = 0;
|
||||
for (const o of orders) {
|
||||
totalSpent += o.finalAmount ?? 0;
|
||||
}
|
||||
return {
|
||||
total_orders: orders.length,
|
||||
total_spent: Math.round(totalSpent),
|
||||
};
|
||||
}
|
||||
|
||||
private mapOrder(
|
||||
o: {
|
||||
id: string;
|
||||
userId: string;
|
||||
orderNumber: string;
|
||||
status: OrderStatus;
|
||||
finalAmount: number | null;
|
||||
totalAmount: number | null;
|
||||
totalDiscount: number | null;
|
||||
totalMeterage: number;
|
||||
notes: string | null;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
items: Array<{
|
||||
id: string;
|
||||
orderId: string;
|
||||
productId: string | null;
|
||||
productName: string;
|
||||
unitPrice: number | null;
|
||||
lineTotal: number | null;
|
||||
confirmedMeterage: number | null;
|
||||
requestedMeterage: number;
|
||||
}>;
|
||||
user?: {
|
||||
id: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
phone: string;
|
||||
};
|
||||
},
|
||||
withNested: boolean,
|
||||
): MontactOrder {
|
||||
const totalPrice = o.finalAmount ?? o.totalAmount ?? 0;
|
||||
const qty = o.items.length;
|
||||
const base: MontactOrder = {
|
||||
id: o.id,
|
||||
user_id: o.userId,
|
||||
store_id: this.storeId(),
|
||||
code: o.orderNumber,
|
||||
sts: this.mapOrderStatus(o.status),
|
||||
payment_sts: this.mapPaymentStatus(o.status),
|
||||
payment_type: 'unknown',
|
||||
total_price: Math.round(totalPrice),
|
||||
discount_total_price: Math.round(totalPrice),
|
||||
total_discounts: Math.round(o.totalDiscount ?? 0),
|
||||
total_tax: 0,
|
||||
total_qty: qty,
|
||||
profit: 0,
|
||||
percent_profit: 0,
|
||||
sum_gold: 0,
|
||||
sum_silver: 0,
|
||||
address: null,
|
||||
address_id: null,
|
||||
note: o.notes,
|
||||
cancel_reason: null,
|
||||
cancelled_at: null,
|
||||
closed_at: null,
|
||||
transaction_id: null,
|
||||
code_discount: null,
|
||||
discount_code_id: null,
|
||||
created_at: o.createdAt.toISOString(),
|
||||
updated_at: o.updatedAt.toISOString(),
|
||||
deleted_at: null,
|
||||
order_items: o.items.map((item) => ({
|
||||
id: item.id,
|
||||
order_id: item.orderId,
|
||||
product_id: item.productId ?? '',
|
||||
product_name: item.productName,
|
||||
qty: 1,
|
||||
price: Math.round(item.unitPrice ?? 0),
|
||||
weight: item.confirmedMeterage ?? item.requestedMeterage,
|
||||
total_price: Math.round(item.lineTotal ?? 0),
|
||||
})),
|
||||
};
|
||||
if (withNested && o.user) {
|
||||
base.user = {
|
||||
id: o.user.id,
|
||||
name: joinFullName(o.user.firstName, o.user.lastName),
|
||||
phone: o.user.phone,
|
||||
};
|
||||
base.store = { id: this.storeId() };
|
||||
}
|
||||
return base;
|
||||
}
|
||||
|
||||
private mapOrderStatus(status: OrderStatus): string {
|
||||
if (status === OrderStatus.cancelled || status === OrderStatus.rejected) {
|
||||
return 'cancelled';
|
||||
}
|
||||
if (status === OrderStatus.delivered) return 'delivered';
|
||||
if (
|
||||
status === OrderStatus.shipped ||
|
||||
status === OrderStatus.ready_to_ship
|
||||
) {
|
||||
return 'shipped';
|
||||
}
|
||||
if (
|
||||
status === OrderStatus.final_approved ||
|
||||
status === OrderStatus.preparing
|
||||
) {
|
||||
return 'processing';
|
||||
}
|
||||
return 'pending';
|
||||
}
|
||||
|
||||
private mapPaymentStatus(status: OrderStatus): string {
|
||||
if (
|
||||
status === OrderStatus.cash_confirmed ||
|
||||
status === OrderStatus.checks_approved ||
|
||||
status === OrderStatus.delivered
|
||||
) {
|
||||
return 'paid';
|
||||
}
|
||||
if (status === OrderStatus.cancelled || status === OrderStatus.rejected) {
|
||||
return 'cancelled';
|
||||
}
|
||||
return 'unpaid';
|
||||
}
|
||||
|
||||
private mapAddress(
|
||||
a: {
|
||||
id: string;
|
||||
label: string | null;
|
||||
text: string;
|
||||
isDefault: boolean;
|
||||
},
|
||||
userId: string,
|
||||
) {
|
||||
return {
|
||||
id: a.id,
|
||||
user_id: userId,
|
||||
province: '',
|
||||
city: '',
|
||||
address: a.text,
|
||||
postal_code: '',
|
||||
receiver_name: a.label ?? '',
|
||||
receiver_phone: '',
|
||||
is_default: a.isDefault,
|
||||
};
|
||||
}
|
||||
|
||||
private mapEngagement(
|
||||
favorites: Array<{
|
||||
createdAt: Date;
|
||||
product: { id: string; name: string };
|
||||
}>,
|
||||
) {
|
||||
return {
|
||||
saved: favorites.map((f) => ({
|
||||
product_id: f.product.id,
|
||||
product_name: f.product.name,
|
||||
saved_at: f.createdAt.toISOString(),
|
||||
})),
|
||||
likes: [] as unknown[],
|
||||
comments: [] as unknown[],
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user