Rebuild order flow: settle after pricing, notifications, and invoice expiry.

Hide prices until admin sends the invoice, enforce status transitions, add customer/admin SMS+in-app notifications, schedule cash invoice reminders, and ship updated dist/prisma-client/deploy-bundle for git-pull deploys.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-09 12:00:13 +03:30
co-authored by Cursor
parent 06e5f48d07
commit d4aaa8e997
102 changed files with 2989 additions and 509 deletions
Binary file not shown.
+4
View File
@@ -10,6 +10,7 @@ exports.AppModule = void 0;
const common_1 = require("@nestjs/common"); const common_1 = require("@nestjs/common");
const core_1 = require("@nestjs/core"); const core_1 = require("@nestjs/core");
const config_1 = require("@nestjs/config"); const config_1 = require("@nestjs/config");
const schedule_1 = require("@nestjs/schedule");
const prisma_module_1 = require("./prisma/prisma.module"); const prisma_module_1 = require("./prisma/prisma.module");
const auth_module_1 = require("./auth/auth.module"); const auth_module_1 = require("./auth/auth.module");
const jwt_auth_guard_1 = require("./common/guards/jwt-auth.guard"); const jwt_auth_guard_1 = require("./common/guards/jwt-auth.guard");
@@ -25,6 +26,7 @@ const shipments_module_1 = require("./shipments/shipments.module");
const dashboard_module_1 = require("./dashboard/dashboard.module"); const dashboard_module_1 = require("./dashboard/dashboard.module");
const settings_module_1 = require("./settings/settings.module"); const settings_module_1 = require("./settings/settings.module");
const carts_module_1 = require("./carts/carts.module"); const carts_module_1 = require("./carts/carts.module");
const notifications_module_1 = require("./notifications/notifications.module");
let AppModule = class AppModule { let AppModule = class AppModule {
}; };
exports.AppModule = AppModule; exports.AppModule = AppModule;
@@ -32,6 +34,7 @@ exports.AppModule = AppModule = __decorate([
(0, common_1.Module)({ (0, common_1.Module)({
imports: [ imports: [
config_1.ConfigModule.forRoot({ isGlobal: true }), config_1.ConfigModule.forRoot({ isGlobal: true }),
schedule_1.ScheduleModule.forRoot(),
prisma_module_1.PrismaModule, prisma_module_1.PrismaModule,
auth_module_1.AuthModule, auth_module_1.AuthModule,
companies_module_1.CompaniesModule, companies_module_1.CompaniesModule,
@@ -46,6 +49,7 @@ exports.AppModule = AppModule = __decorate([
dashboard_module_1.DashboardModule, dashboard_module_1.DashboardModule,
settings_module_1.SettingsModule, settings_module_1.SettingsModule,
carts_module_1.CartsModule, carts_module_1.CartsModule,
notifications_module_1.NotificationsModule,
], ],
providers: [ providers: [
{ {
+1 -1
View File
@@ -1 +1 @@
{"version":3,"file":"app.module.js","sourceRoot":"","sources":["../src/app.module.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAAwC;AACxC,uCAAyC;AACzC,2CAA8C;AAC9C,0DAAsD;AACtD,oDAAgD;AAChD,mEAA8D;AAC9D,mEAA+D;AAC/D,gEAA4D;AAC5D,gEAA4D;AAC5D,mEAA+D;AAC/D,yEAAoE;AACpE,0DAAsD;AACtD,0DAAsD;AACtD,+EAA0E;AAC1E,mEAA+D;AAC/D,mEAA+D;AAC/D,gEAA4D;AAC5D,uDAAmD;AA2B5C,IAAM,SAAS,GAAf,MAAM,SAAS;CAAG,CAAA;AAAZ,8BAAS;oBAAT,SAAS;IAzBrB,IAAA,eAAM,EAAC;QACN,OAAO,EAAE;YACP,qBAAY,CAAC,OAAO,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;YACxC,4BAAY;YACZ,wBAAU;YACV,kCAAe;YACf,gCAAc;YACd,gCAAc;YACd,kCAAe;YACf,qCAAgB;YAChB,4BAAY;YACZ,4BAAY;YACZ,yCAAkB;YAClB,kCAAe;YACf,kCAAe;YACf,gCAAc;YACd,0BAAW;SACZ;QACD,SAAS,EAAE;YACT;gBACE,OAAO,EAAE,gBAAS;gBAClB,QAAQ,EAAE,6BAAY;aACvB;SACF;KACF,CAAC;GACW,SAAS,CAAG"} {"version":3,"file":"app.module.js","sourceRoot":"","sources":["../src/app.module.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAAwC;AACxC,uCAAyC;AACzC,2CAA8C;AAC9C,+CAAkD;AAClD,0DAAsD;AACtD,oDAAgD;AAChD,mEAA8D;AAC9D,mEAA+D;AAC/D,gEAA4D;AAC5D,gEAA4D;AAC5D,mEAA+D;AAC/D,yEAAoE;AACpE,0DAAsD;AACtD,0DAAsD;AACtD,+EAA0E;AAC1E,mEAA+D;AAC/D,mEAA+D;AAC/D,gEAA4D;AAC5D,uDAAmD;AACnD,+EAA2E;AA6BpE,IAAM,SAAS,GAAf,MAAM,SAAS;CAAG,CAAA;AAAZ,8BAAS;oBAAT,SAAS;IA3BrB,IAAA,eAAM,EAAC;QACN,OAAO,EAAE;YACP,qBAAY,CAAC,OAAO,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;YACxC,yBAAc,CAAC,OAAO,EAAE;YACxB,4BAAY;YACZ,wBAAU;YACV,kCAAe;YACf,gCAAc;YACd,gCAAc;YACd,kCAAe;YACf,qCAAgB;YAChB,4BAAY;YACZ,4BAAY;YACZ,yCAAkB;YAClB,kCAAe;YACf,kCAAe;YACf,gCAAc;YACd,0BAAW;YACX,0CAAmB;SACpB;QACD,SAAS,EAAE;YACT;gBACE,OAAO,EAAE,gBAAS;gBAClB,QAAQ,EAAE,6BAAY;aACvB;SACF;KACF,CAAC;GACW,SAAS,CAAG"}
-6
View File
@@ -18,7 +18,6 @@ export declare class CartsController {
productCode: string; productCode: string;
color: string; color: string;
meterage: number; meterage: number;
unitPrice: number | undefined;
notes: string | undefined; notes: string | undefined;
companyId: string; companyId: string;
companyName: string; companyName: string;
@@ -42,7 +41,6 @@ export declare class CartsController {
productCode: string; productCode: string;
color: string; color: string;
meterage: number; meterage: number;
unitPrice: number | undefined;
notes: string | undefined; notes: string | undefined;
companyId: string; companyId: string;
companyName: string; companyName: string;
@@ -66,7 +64,6 @@ export declare class CartsController {
productCode: string; productCode: string;
color: string; color: string;
meterage: number; meterage: number;
unitPrice: number | undefined;
notes: string | undefined; notes: string | undefined;
companyId: string; companyId: string;
companyName: string; companyName: string;
@@ -90,7 +87,6 @@ export declare class CartsController {
productCode: string; productCode: string;
color: string; color: string;
meterage: number; meterage: number;
unitPrice: number | undefined;
notes: string | undefined; notes: string | undefined;
companyId: string; companyId: string;
companyName: string; companyName: string;
@@ -114,7 +110,6 @@ export declare class CartsController {
productCode: string; productCode: string;
color: string; color: string;
meterage: number; meterage: number;
unitPrice: number | undefined;
notes: string | undefined; notes: string | undefined;
companyId: string; companyId: string;
companyName: string; companyName: string;
@@ -138,7 +133,6 @@ export declare class CartsController {
productCode: string; productCode: string;
color: string; color: string;
meterage: number; meterage: number;
unitPrice: number | undefined;
notes: string | undefined; notes: string | undefined;
companyId: string; companyId: string;
companyName: string; companyName: string;
-6
View File
@@ -17,7 +17,6 @@ export declare class CartsService {
productCode: string; productCode: string;
color: string; color: string;
meterage: number; meterage: number;
unitPrice: number | undefined;
notes: string | undefined; notes: string | undefined;
companyId: string; companyId: string;
companyName: string; companyName: string;
@@ -41,7 +40,6 @@ export declare class CartsService {
productCode: string; productCode: string;
color: string; color: string;
meterage: number; meterage: number;
unitPrice: number | undefined;
notes: string | undefined; notes: string | undefined;
companyId: string; companyId: string;
companyName: string; companyName: string;
@@ -65,7 +63,6 @@ export declare class CartsService {
productCode: string; productCode: string;
color: string; color: string;
meterage: number; meterage: number;
unitPrice: number | undefined;
notes: string | undefined; notes: string | undefined;
companyId: string; companyId: string;
companyName: string; companyName: string;
@@ -89,7 +86,6 @@ export declare class CartsService {
productCode: string; productCode: string;
color: string; color: string;
meterage: number; meterage: number;
unitPrice: number | undefined;
notes: string | undefined; notes: string | undefined;
companyId: string; companyId: string;
companyName: string; companyName: string;
@@ -113,7 +109,6 @@ export declare class CartsService {
productCode: string; productCode: string;
color: string; color: string;
meterage: number; meterage: number;
unitPrice: number | undefined;
notes: string | undefined; notes: string | undefined;
companyId: string; companyId: string;
companyName: string; companyName: string;
@@ -137,7 +132,6 @@ export declare class CartsService {
productCode: string; productCode: string;
color: string; color: string;
meterage: number; meterage: number;
unitPrice: number | undefined;
notes: string | undefined; notes: string | undefined;
companyId: string; companyId: string;
companyName: string; companyName: string;
-3
View File
@@ -47,7 +47,6 @@ let CartsService = class CartsService {
data: { data: {
meterage: newMeterage, meterage: newMeterage,
notes: dto.notes?.trim() || existing.notes, notes: dto.notes?.trim() || existing.notes,
unitPrice: product.basePrice,
}, },
}); });
} }
@@ -61,7 +60,6 @@ let CartsService = class CartsService {
productCode: product.code, productCode: product.code,
color: product.color, color: product.color,
meterage: dto.meterage, meterage: dto.meterage,
unitPrice: product.basePrice,
notes: dto.notes?.trim() || null, notes: dto.notes?.trim() || null,
companyId: product.companyId, companyId: product.companyId,
companyName: product.company.name, companyName: product.company.name,
@@ -168,7 +166,6 @@ let CartsService = class CartsService {
productCode: item.productCode, productCode: item.productCode,
color: item.color, color: item.color,
meterage: item.meterage, meterage: item.meterage,
unitPrice: item.unitPrice ?? undefined,
notes: item.notes ?? undefined, notes: item.notes ?? undefined,
companyId: item.companyId, companyId: item.companyId,
companyName: item.companyName, companyName: item.companyName,
+1 -1
View File
File diff suppressed because one or more lines are too long
+2
View File
@@ -10,11 +10,13 @@ exports.CashPaymentsModule = void 0;
const common_1 = require("@nestjs/common"); const common_1 = require("@nestjs/common");
const cash_payments_controller_1 = require("./cash-payments.controller"); const cash_payments_controller_1 = require("./cash-payments.controller");
const cash_payments_service_1 = require("./cash-payments.service"); const cash_payments_service_1 = require("./cash-payments.service");
const notifications_module_1 = require("../notifications/notifications.module");
let CashPaymentsModule = class CashPaymentsModule { let CashPaymentsModule = class CashPaymentsModule {
}; };
exports.CashPaymentsModule = CashPaymentsModule; exports.CashPaymentsModule = CashPaymentsModule;
exports.CashPaymentsModule = CashPaymentsModule = __decorate([ exports.CashPaymentsModule = CashPaymentsModule = __decorate([
(0, common_1.Module)({ (0, common_1.Module)({
imports: [notifications_module_1.NotificationsModule],
controllers: [cash_payments_controller_1.CashPaymentsController], controllers: [cash_payments_controller_1.CashPaymentsController],
providers: [cash_payments_service_1.CashPaymentsService], providers: [cash_payments_service_1.CashPaymentsService],
exports: [cash_payments_service_1.CashPaymentsService], exports: [cash_payments_service_1.CashPaymentsService],
+1 -1
View File
@@ -1 +1 @@
{"version":3,"file":"cash-payments.module.js","sourceRoot":"","sources":["../../src/cash-payments/cash-payments.module.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAAwC;AACxC,yEAAoE;AACpE,mEAA8D;AAOvD,IAAM,kBAAkB,GAAxB,MAAM,kBAAkB;CAAG,CAAA;AAArB,gDAAkB;6BAAlB,kBAAkB;IAL9B,IAAA,eAAM,EAAC;QACN,WAAW,EAAE,CAAC,iDAAsB,CAAC;QACrC,SAAS,EAAE,CAAC,2CAAmB,CAAC;QAChC,OAAO,EAAE,CAAC,2CAAmB,CAAC;KAC/B,CAAC;GACW,kBAAkB,CAAG"} {"version":3,"file":"cash-payments.module.js","sourceRoot":"","sources":["../../src/cash-payments/cash-payments.module.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAAwC;AACxC,yEAAoE;AACpE,mEAA8D;AAC9D,gFAA4E;AAQrE,IAAM,kBAAkB,GAAxB,MAAM,kBAAkB;CAAG,CAAA;AAArB,gDAAkB;6BAAlB,kBAAkB;IAN9B,IAAA,eAAM,EAAC;QACN,OAAO,EAAE,CAAC,0CAAmB,CAAC;QAC9B,WAAW,EAAE,CAAC,iDAAsB,CAAC;QACrC,SAAS,EAAE,CAAC,2CAAmB,CAAC;QAChC,OAAO,EAAE,CAAC,2CAAmB,CAAC;KAC/B,CAAC;GACW,kBAAkB,CAAG"}
+3 -1
View File
@@ -1,9 +1,11 @@
import { CashPaymentStatus } from '@prisma/client'; import { CashPaymentStatus } from '@prisma/client';
import { PrismaService } from '../prisma/prisma.service'; import { PrismaService } from '../prisma/prisma.service';
import { SubmitCashReceiptDto, UpdateCashPaymentStatusDto } from './dto/cash-payment.dto'; import { SubmitCashReceiptDto, UpdateCashPaymentStatusDto } from './dto/cash-payment.dto';
import { NotificationsService } from '../notifications/notifications.service';
export declare class CashPaymentsService { export declare class CashPaymentsService {
private readonly prisma; private readonly prisma;
constructor(prisma: PrismaService); private readonly notifications;
constructor(prisma: PrismaService, notifications: NotificationsService);
submitReceipt(userId: string, orderId: string, dto: SubmitCashReceiptDto): Promise<{ submitReceipt(userId: string, orderId: string, dto: SubmitCashReceiptDto): Promise<{
id: string; id: string;
orderId: string; orderId: string;
+21 -8
View File
@@ -14,12 +14,8 @@ const common_1 = require("@nestjs/common");
const client_1 = require("@prisma/client"); const client_1 = require("@prisma/client");
const prisma_service_1 = require("../prisma/prisma.service"); const prisma_service_1 = require("../prisma/prisma.service");
const name_1 = require("../common/utils/name"); const name_1 = require("../common/utils/name");
const notifications_service_1 = require("../notifications/notifications.service");
const RECEIPT_SUBMITTABLE_STATUSES = [ const RECEIPT_SUBMITTABLE_STATUSES = [
client_1.OrderStatus.awaiting_review,
client_1.OrderStatus.pricing,
client_1.OrderStatus.needs_correction,
client_1.OrderStatus.awaiting_settlement,
client_1.OrderStatus.awaiting_settlement_fix,
client_1.OrderStatus.cash_in_progress, client_1.OrderStatus.cash_in_progress,
client_1.OrderStatus.cash_invoice_issued, client_1.OrderStatus.cash_invoice_issued,
client_1.OrderStatus.awaiting_cash_payment, client_1.OrderStatus.awaiting_cash_payment,
@@ -28,8 +24,10 @@ const RECEIPT_SUBMITTABLE_STATUSES = [
]; ];
let CashPaymentsService = class CashPaymentsService { let CashPaymentsService = class CashPaymentsService {
prisma; prisma;
constructor(prisma) { notifications;
constructor(prisma, notifications) {
this.prisma = prisma; this.prisma = prisma;
this.notifications = notifications;
} }
async submitReceipt(userId, orderId, dto) { async submitReceipt(userId, orderId, dto) {
const order = await this.prisma.order.findUnique({ const order = await this.prisma.order.findUnique({
@@ -98,6 +96,11 @@ let CashPaymentsService = class CashPaymentsService {
}); });
return saved; return saved;
}); });
this.notifications.dispatchAdmin({
title: 'رسید پرداخت نقدی ثبت شد',
body: `مشتری برای سفارش ${order.orderNumber} رسید به مبلغ ${Math.round(dto.amount).toLocaleString('fa-IR')} تومان ثبت کرد.`,
link: `/orders/${orderId}`,
});
return this.mapReceipt(receipt, order.orderNumber, ''); return this.mapReceipt(receipt, order.orderNumber, '');
} }
async findAll(status) { async findAll(status) {
@@ -135,7 +138,7 @@ let CashPaymentsService = class CashPaymentsService {
nextStatus = client_1.OrderStatus.cash_confirmed; nextStatus = client_1.OrderStatus.cash_confirmed;
} }
else if (dto.status === client_1.CashPaymentStatus.rejected) { else if (dto.status === client_1.CashPaymentStatus.rejected) {
nextStatus = client_1.OrderStatus.needs_correction; nextStatus = client_1.OrderStatus.awaiting_cash_payment;
} }
else if (dto.status === client_1.CashPaymentStatus.expired) { else if (dto.status === client_1.CashPaymentStatus.expired) {
nextStatus = client_1.OrderStatus.expired; nextStatus = client_1.OrderStatus.expired;
@@ -157,6 +160,15 @@ let CashPaymentsService = class CashPaymentsService {
} }
return result; return result;
}); });
if (dto.status === client_1.CashPaymentStatus.approved) {
this.notifications.dispatchOrderEvent(receipt.orderId, 'receipt_approved');
}
else if (dto.status === client_1.CashPaymentStatus.rejected) {
this.notifications.dispatchOrderEvent(receipt.orderId, 'receipt_rejected', { note: dto.note ?? null });
}
else if (dto.status === client_1.CashPaymentStatus.expired) {
this.notifications.dispatchOrderEvent(receipt.orderId, 'cash_invoice_expired');
}
return this.mapReceipt(updated, updated.order.orderNumber, (0, name_1.joinFullName)(updated.order.user.firstName, updated.order.user.lastName)); return this.mapReceipt(updated, updated.order.orderNumber, (0, name_1.joinFullName)(updated.order.user.firstName, updated.order.user.lastName));
} }
mapReceipt(r, orderNumber, customerName) { mapReceipt(r, orderNumber, customerName) {
@@ -185,6 +197,7 @@ let CashPaymentsService = class CashPaymentsService {
exports.CashPaymentsService = CashPaymentsService; exports.CashPaymentsService = CashPaymentsService;
exports.CashPaymentsService = CashPaymentsService = __decorate([ exports.CashPaymentsService = CashPaymentsService = __decorate([
(0, common_1.Injectable)(), (0, common_1.Injectable)(),
__metadata("design:paramtypes", [prisma_service_1.PrismaService]) __metadata("design:paramtypes", [prisma_service_1.PrismaService,
notifications_service_1.NotificationsService])
], CashPaymentsService); ], CashPaymentsService);
//# sourceMappingURL=cash-payments.service.js.map //# sourceMappingURL=cash-payments.service.js.map
File diff suppressed because one or more lines are too long
+2
View File
@@ -10,11 +10,13 @@ exports.ChecksModule = void 0;
const common_1 = require("@nestjs/common"); const common_1 = require("@nestjs/common");
const checks_controller_1 = require("./checks.controller"); const checks_controller_1 = require("./checks.controller");
const checks_service_1 = require("./checks.service"); const checks_service_1 = require("./checks.service");
const notifications_module_1 = require("../notifications/notifications.module");
let ChecksModule = class ChecksModule { let ChecksModule = class ChecksModule {
}; };
exports.ChecksModule = ChecksModule; exports.ChecksModule = ChecksModule;
exports.ChecksModule = ChecksModule = __decorate([ exports.ChecksModule = ChecksModule = __decorate([
(0, common_1.Module)({ (0, common_1.Module)({
imports: [notifications_module_1.NotificationsModule],
controllers: [checks_controller_1.ChecksController], controllers: [checks_controller_1.ChecksController],
providers: [checks_service_1.ChecksService], providers: [checks_service_1.ChecksService],
exports: [checks_service_1.ChecksService], exports: [checks_service_1.ChecksService],
+1 -1
View File
@@ -1 +1 @@
{"version":3,"file":"checks.module.js","sourceRoot":"","sources":["../../src/checks/checks.module.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAAwC;AACxC,2DAAuD;AACvD,qDAAiD;AAO1C,IAAM,YAAY,GAAlB,MAAM,YAAY;CAAG,CAAA;AAAf,oCAAY;uBAAZ,YAAY;IALxB,IAAA,eAAM,EAAC;QACN,WAAW,EAAE,CAAC,oCAAgB,CAAC;QAC/B,SAAS,EAAE,CAAC,8BAAa,CAAC;QAC1B,OAAO,EAAE,CAAC,8BAAa,CAAC;KACzB,CAAC;GACW,YAAY,CAAG"} {"version":3,"file":"checks.module.js","sourceRoot":"","sources":["../../src/checks/checks.module.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAAwC;AACxC,2DAAuD;AACvD,qDAAiD;AACjD,gFAA4E;AAQrE,IAAM,YAAY,GAAlB,MAAM,YAAY;CAAG,CAAA;AAAf,oCAAY;uBAAZ,YAAY;IANxB,IAAA,eAAM,EAAC;QACN,OAAO,EAAE,CAAC,0CAAmB,CAAC;QAC9B,WAAW,EAAE,CAAC,oCAAgB,CAAC;QAC/B,SAAS,EAAE,CAAC,8BAAa,CAAC;QAC1B,OAAO,EAAE,CAAC,8BAAa,CAAC;KACzB,CAAC;GACW,YAAY,CAAG"}
+3 -1
View File
@@ -1,8 +1,10 @@
import { PrismaService } from '../prisma/prisma.service'; import { PrismaService } from '../prisma/prisma.service';
import { UpdateCheckStatusDto } from './dto/check.dto'; import { UpdateCheckStatusDto } from './dto/check.dto';
import { NotificationsService } from '../notifications/notifications.service';
export declare class ChecksService { export declare class ChecksService {
private readonly prisma; private readonly prisma;
constructor(prisma: PrismaService); private readonly notifications;
constructor(prisma: PrismaService, notifications: NotificationsService);
findByOrder(orderId: string): Promise<{ findByOrder(orderId: string): Promise<{
id: string; id: string;
orderId: string; orderId: string;
+17 -6
View File
@@ -13,10 +13,13 @@ exports.ChecksService = void 0;
const common_1 = require("@nestjs/common"); const common_1 = require("@nestjs/common");
const client_1 = require("@prisma/client"); const client_1 = require("@prisma/client");
const prisma_service_1 = require("../prisma/prisma.service"); const prisma_service_1 = require("../prisma/prisma.service");
const notifications_service_1 = require("../notifications/notifications.service");
let ChecksService = class ChecksService { let ChecksService = class ChecksService {
prisma; prisma;
constructor(prisma) { notifications;
constructor(prisma, notifications) {
this.prisma = prisma; this.prisma = prisma;
this.notifications = notifications;
} }
async findByOrder(orderId) { async findByOrder(orderId) {
const order = await this.prisma.order.findUnique({ const order = await this.prisma.order.findUnique({
@@ -37,6 +40,7 @@ let ChecksService = class ChecksService {
}); });
if (!check) if (!check)
throw new common_1.NotFoundException('چک یافت نشد'); throw new common_1.NotFoundException('چک یافت نشد');
let notifyStatus = null;
const updated = await this.prisma.$transaction(async (tx) => { const updated = await this.prisma.$transaction(async (tx) => {
const result = await tx.check.update({ const result = await tx.check.update({
where: { id }, where: { id },
@@ -54,14 +58,11 @@ let ChecksService = class ChecksService {
const anyRejected = siblings.some((c) => c.status === client_1.CheckStatus.rejected); const anyRejected = siblings.some((c) => c.status === client_1.CheckStatus.rejected);
let nextStatus = null; let nextStatus = null;
if (anyRejected) { if (anyRejected) {
nextStatus = client_1.OrderStatus.check_rejected; nextStatus = client_1.OrderStatus.check_needs_fix;
} }
else if (allApproved) { else if (allApproved) {
nextStatus = client_1.OrderStatus.checks_approved; nextStatus = client_1.OrderStatus.checks_approved;
} }
else if (dto.status === client_1.CheckStatus.rejected) {
nextStatus = client_1.OrderStatus.check_needs_fix;
}
if (nextStatus && nextStatus !== check.order.status) { if (nextStatus && nextStatus !== check.order.status) {
await tx.order.update({ await tx.order.update({
where: { id: check.orderId }, where: { id: check.orderId },
@@ -76,9 +77,18 @@ let ChecksService = class ChecksService {
note: dto.note || `وضعیت چک به ${dto.status} تغییر کرد`, note: dto.note || `وضعیت چک به ${dto.status} تغییر کرد`,
}, },
}); });
notifyStatus = nextStatus;
} }
return result; return result;
}); });
if (notifyStatus === client_1.OrderStatus.checks_approved) {
this.notifications.dispatchOrderEvent(check.orderId, 'checks_approved');
}
else if (notifyStatus === client_1.OrderStatus.check_needs_fix) {
this.notifications.dispatchOrderEvent(check.orderId, 'checks_need_fix', {
note: dto.note ?? null,
});
}
return this.map(updated); return this.map(updated);
} }
map(c) { map(c) {
@@ -108,6 +118,7 @@ let ChecksService = class ChecksService {
exports.ChecksService = ChecksService; exports.ChecksService = ChecksService;
exports.ChecksService = ChecksService = __decorate([ exports.ChecksService = ChecksService = __decorate([
(0, common_1.Injectable)(), (0, common_1.Injectable)(),
__metadata("design:paramtypes", [prisma_service_1.PrismaService]) __metadata("design:paramtypes", [prisma_service_1.PrismaService,
notifications_service_1.NotificationsService])
], ChecksService); ], ChecksService);
//# sourceMappingURL=checks.service.js.map //# sourceMappingURL=checks.service.js.map
+1 -1
View File
@@ -1 +1 @@
{"version":3,"file":"checks.service.js","sourceRoot":"","sources":["../../src/checks/checks.service.ts"],"names":[],"mappings":"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"} {"version":3,"file":"checks.service.js","sourceRoot":"","sources":["../../src/checks/checks.service.ts"],"names":[],"mappings":"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"}
+2
View File
@@ -0,0 +1,2 @@
export declare const FALLBACK_MAX_METERAGE = 5000;
export declare function resolveMaxMeterage(companyValue: number | null | undefined, settingsValue: number | null | undefined): number;
+13
View File
@@ -0,0 +1,13 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.FALLBACK_MAX_METERAGE = void 0;
exports.resolveMaxMeterage = resolveMaxMeterage;
exports.FALLBACK_MAX_METERAGE = 5000;
function resolveMaxMeterage(companyValue, settingsValue) {
if (companyValue != null && companyValue > 0)
return companyValue;
if (settingsValue != null && settingsValue > 0)
return settingsValue;
return exports.FALLBACK_MAX_METERAGE;
}
//# sourceMappingURL=meterage.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"meterage.js","sourceRoot":"","sources":["../../../src/common/utils/meterage.ts"],"names":[],"mappings":";;;AAMA,gDAOC;AAZY,QAAA,qBAAqB,GAAG,IAAI,CAAC;AAK1C,SAAgB,kBAAkB,CAChC,YAAuC,EACvC,aAAwC;IAExC,IAAI,YAAY,IAAI,IAAI,IAAI,YAAY,GAAG,CAAC;QAAE,OAAO,YAAY,CAAC;IAClE,IAAI,aAAa,IAAI,IAAI,IAAI,aAAa,GAAG,CAAC;QAAE,OAAO,aAAa,CAAC;IACrE,OAAO,6BAAqB,CAAC;AAC/B,CAAC"}
+10 -8
View File
@@ -7,22 +7,24 @@ const client_1 = require("@prisma/client");
exports.ADMIN_STATUS_MAP = { exports.ADMIN_STATUS_MAP = {
pending_review: [ pending_review: [
client_1.OrderStatus.cart, client_1.OrderStatus.cart,
client_1.OrderStatus.awaiting_settlement,
client_1.OrderStatus.cash_in_progress,
client_1.OrderStatus.check_in_progress,
client_1.OrderStatus.submitted, client_1.OrderStatus.submitted,
client_1.OrderStatus.awaiting_review, client_1.OrderStatus.awaiting_review,
client_1.OrderStatus.pricing, client_1.OrderStatus.pricing,
],
awaiting_customer: [
client_1.OrderStatus.awaiting_settlement,
client_1.OrderStatus.awaiting_settlement_fix,
client_1.OrderStatus.check_in_progress,
client_1.OrderStatus.cash_in_progress,
client_1.OrderStatus.cash_invoice_issued, client_1.OrderStatus.cash_invoice_issued,
client_1.OrderStatus.awaiting_cash_payment, client_1.OrderStatus.awaiting_cash_payment,
],
pending_check: [client_1.OrderStatus.awaiting_check_review],
pending_payment: [
client_1.OrderStatus.cash_payment_submitted, client_1.OrderStatus.cash_payment_submitted,
client_1.OrderStatus.awaiting_cash_confirmation, client_1.OrderStatus.awaiting_cash_confirmation,
], ],
pending_check: [client_1.OrderStatus.awaiting_check_review], needs_correction: [client_1.OrderStatus.needs_correction],
needs_correction: [
client_1.OrderStatus.needs_correction,
client_1.OrderStatus.awaiting_settlement_fix,
],
check_correction: [client_1.OrderStatus.check_needs_fix, client_1.OrderStatus.check_rejected], check_correction: [client_1.OrderStatus.check_needs_fix, client_1.OrderStatus.check_rejected],
final_approval: [ final_approval: [
client_1.OrderStatus.final_approved, client_1.OrderStatus.final_approved,
+1 -1
View File
@@ -1 +1 @@
{"version":3,"file":"order-status.js","sourceRoot":"","sources":["../../../src/common/utils/order-status.ts"],"names":[],"mappings":"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"} {"version":3,"file":"order-status.js","sourceRoot":"","sources":["../../../src/common/utils/order-status.ts"],"names":[],"mappings":";;;AA2CA,4DASC;AAED,sCAMC;AA5DD,2CAA6C;AAGhC,QAAA,gBAAgB,GAAkC;IAC7D,cAAc,EAAE;QACd,oBAAW,CAAC,IAAI;QAChB,oBAAW,CAAC,SAAS;QACrB,oBAAW,CAAC,eAAe;QAC3B,oBAAW,CAAC,OAAO;KACpB;IACD,iBAAiB,EAAE;QACjB,oBAAW,CAAC,mBAAmB;QAC/B,oBAAW,CAAC,uBAAuB;QACnC,oBAAW,CAAC,iBAAiB;QAC7B,oBAAW,CAAC,gBAAgB;QAC5B,oBAAW,CAAC,mBAAmB;QAC/B,oBAAW,CAAC,qBAAqB;KAClC;IACD,aAAa,EAAE,CAAC,oBAAW,CAAC,qBAAqB,CAAC;IAClD,eAAe,EAAE;QACf,oBAAW,CAAC,sBAAsB;QAClC,oBAAW,CAAC,0BAA0B;KACvC;IACD,gBAAgB,EAAE,CAAC,oBAAW,CAAC,gBAAgB,CAAC;IAChD,gBAAgB,EAAE,CAAC,oBAAW,CAAC,eAAe,EAAE,oBAAW,CAAC,cAAc,CAAC;IAC3E,cAAc,EAAE;QACd,oBAAW,CAAC,cAAc;QAC1B,oBAAW,CAAC,eAAe;QAC3B,oBAAW,CAAC,cAAc;QAC1B,oBAAW,CAAC,SAAS;QACrB,oBAAW,CAAC,aAAa;KAC1B;IACD,OAAO,EAAE,CAAC,oBAAW,CAAC,OAAO,CAAC;IAC9B,SAAS,EAAE,CAAC,oBAAW,CAAC,SAAS,CAAC;CACnC,CAAC;AAGF,MAAM,iBAAiB,GAAkB;IACvC,oBAAW,CAAC,SAAS;IACrB,oBAAW,CAAC,QAAQ;IACpB,oBAAW,CAAC,OAAO;CACpB,CAAC;AAEF,SAAgB,wBAAwB,CACtC,MAAe;IAEf,IAAI,CAAC,MAAM;QAAE,OAAO,SAAS,CAAC;IAC9B,IAAI,wBAAgB,CAAC,MAAM,CAAC;QAAE,OAAO,wBAAgB,CAAC,MAAM,CAAC,CAAC;IAC9D,IAAI,MAAM,CAAC,MAAM,CAAC,oBAAW,CAAC,CAAC,QAAQ,CAAC,MAAqB,CAAC,EAAE,CAAC;QAC/D,OAAO,CAAC,MAAqB,CAAC,CAAC;IACjC,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,SAAgB,aAAa,CAAC,MAAmB;IAC/C,KAAK,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,wBAAgB,CAAC,EAAE,CAAC;QAC7D,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC;YAAE,OAAO,KAAK,CAAC;IAC1C,CAAC;IACD,IAAI,iBAAiB,CAAC,QAAQ,CAAC,MAAM,CAAC;QAAE,OAAO,MAAM,CAAC;IACtD,OAAO,gBAAgB,CAAC;AAC1B,CAAC;AAEY,QAAA,mBAAmB,GAAgC;IAC9D,IAAI,EAAE,UAAU;IAChB,mBAAmB,EAAE,iBAAiB;IACtC,gBAAgB,EAAE,iBAAiB;IACnC,iBAAiB,EAAE,eAAe;IAClC,SAAS,EAAE,SAAS;IACpB,eAAe,EAAE,iBAAiB;IAClC,OAAO,EAAE,YAAY;IACrB,gBAAgB,EAAE,eAAe;IACjC,uBAAuB,EAAE,aAAa;IACtC,mBAAmB,EAAE,kBAAkB;IACvC,qBAAqB,EAAE,uBAAuB;IAC9C,sBAAsB,EAAE,qBAAqB;IAC7C,0BAA0B,EAAE,mBAAmB;IAC/C,cAAc,EAAE,sBAAsB;IACtC,qBAAqB,EAAE,UAAU;IACjC,eAAe,EAAE,UAAU;IAC3B,cAAc,EAAE,OAAO;IACvB,eAAe,EAAE,aAAa;IAC9B,cAAc,EAAE,aAAa;IAC7B,SAAS,EAAE,YAAY;IACvB,aAAa,EAAE,aAAa;IAC5B,OAAO,EAAE,WAAW;IACpB,SAAS,EAAE,WAAW;IACtB,SAAS,EAAE,SAAS;IACpB,QAAQ,EAAE,QAAQ;IAClB,OAAO,EAAE,OAAO;CACjB,CAAC"}
+6
View File
@@ -0,0 +1,6 @@
import { OrderStatus } from '@prisma/client';
export declare const ORDER_TRANSITIONS: Record<OrderStatus, OrderStatus[]>;
export declare function isFinalStatus(status: OrderStatus): boolean;
export declare function allowedTransitions(from: OrderStatus): OrderStatus[];
export declare function canTransition(from: OrderStatus, to: OrderStatus): boolean;
export declare function assertTransition(from: OrderStatus, to: OrderStatus): void;
+83
View File
@@ -0,0 +1,83 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ORDER_TRANSITIONS = void 0;
exports.isFinalStatus = isFinalStatus;
exports.allowedTransitions = allowedTransitions;
exports.canTransition = canTransition;
exports.assertTransition = assertTransition;
const common_1 = require("@nestjs/common");
const client_1 = require("@prisma/client");
const order_status_1 = require("./order-status");
exports.ORDER_TRANSITIONS = {
awaiting_review: [client_1.OrderStatus.pricing],
pricing: [client_1.OrderStatus.awaiting_settlement],
awaiting_settlement: [
client_1.OrderStatus.awaiting_cash_payment,
client_1.OrderStatus.check_in_progress,
client_1.OrderStatus.pricing,
],
awaiting_cash_payment: [
client_1.OrderStatus.awaiting_cash_confirmation,
client_1.OrderStatus.expired,
],
awaiting_cash_confirmation: [
client_1.OrderStatus.cash_confirmed,
client_1.OrderStatus.awaiting_cash_payment,
],
cash_confirmed: [client_1.OrderStatus.final_approved],
check_in_progress: [client_1.OrderStatus.awaiting_check_review],
awaiting_check_review: [
client_1.OrderStatus.checks_approved,
client_1.OrderStatus.check_needs_fix,
],
check_needs_fix: [client_1.OrderStatus.awaiting_check_review],
checks_approved: [client_1.OrderStatus.final_approved],
final_approved: [client_1.OrderStatus.preparing],
preparing: [client_1.OrderStatus.ready_to_ship],
ready_to_ship: [client_1.OrderStatus.shipped],
shipped: [client_1.OrderStatus.delivered],
delivered: [],
cancelled: [],
rejected: [],
expired: [],
cart: [client_1.OrderStatus.awaiting_review],
submitted: [client_1.OrderStatus.awaiting_review],
needs_correction: [client_1.OrderStatus.awaiting_review, client_1.OrderStatus.pricing],
awaiting_settlement_fix: [client_1.OrderStatus.awaiting_settlement],
cash_in_progress: [client_1.OrderStatus.awaiting_cash_payment],
cash_invoice_issued: [client_1.OrderStatus.awaiting_cash_payment],
cash_payment_submitted: [client_1.OrderStatus.awaiting_cash_confirmation],
check_rejected: [client_1.OrderStatus.check_needs_fix],
};
const ADMIN_TERMINAL_TRANSITIONS = [
client_1.OrderStatus.cancelled,
client_1.OrderStatus.rejected,
];
const FINAL_STATUSES = [
client_1.OrderStatus.delivered,
client_1.OrderStatus.cancelled,
client_1.OrderStatus.rejected,
client_1.OrderStatus.expired,
];
function isFinalStatus(status) {
return FINAL_STATUSES.includes(status);
}
function allowedTransitions(from) {
const next = [...(exports.ORDER_TRANSITIONS[from] ?? [])];
if (!isFinalStatus(from)) {
for (const terminal of ADMIN_TERMINAL_TRANSITIONS) {
if (!next.includes(terminal))
next.push(terminal);
}
}
return next;
}
function canTransition(from, to) {
return allowedTransitions(from).includes(to);
}
function assertTransition(from, to) {
if (canTransition(from, to))
return;
throw new common_1.BadRequestException(`تغییر وضعیت از «${order_status_1.ORDER_STATUS_LABELS[from]}» به «${order_status_1.ORDER_STATUS_LABELS[to]}» مجاز نیست`);
}
//# sourceMappingURL=order-transitions.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"order-transitions.js","sourceRoot":"","sources":["../../../src/common/utils/order-transitions.ts"],"names":[],"mappings":"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"}
+6
View File
@@ -13,6 +13,7 @@ export declare class CompaniesController {
isActive: boolean; isActive: boolean;
displayOrder: number; displayOrder: number;
maxMeteragePerInvoice: number; maxMeteragePerInvoice: number;
ownMaxMeteragePerInvoice: number | undefined;
cashInvoiceValidityHours: number; cashInvoiceValidityHours: number;
cashInvoiceValidUntilHour: number | undefined; cashInvoiceValidUntilHour: number | undefined;
catalogCount: number; catalogCount: number;
@@ -31,6 +32,7 @@ export declare class CompaniesController {
isActive: boolean; isActive: boolean;
displayOrder: number; displayOrder: number;
maxMeteragePerInvoice: number; maxMeteragePerInvoice: number;
ownMaxMeteragePerInvoice: number | undefined;
cashInvoiceValidityHours: number; cashInvoiceValidityHours: number;
cashInvoiceValidUntilHour: number | undefined; cashInvoiceValidUntilHour: number | undefined;
catalogCount: number; catalogCount: number;
@@ -63,6 +65,7 @@ export declare class CompaniesController {
isActive: boolean; isActive: boolean;
displayOrder: number; displayOrder: number;
maxMeteragePerInvoice: number; maxMeteragePerInvoice: number;
ownMaxMeteragePerInvoice: number | undefined;
cashInvoiceValidityHours: number; cashInvoiceValidityHours: number;
cashInvoiceValidUntilHour: number | undefined; cashInvoiceValidUntilHour: number | undefined;
catalogCount: number; catalogCount: number;
@@ -81,6 +84,7 @@ export declare class CompaniesController {
isActive: boolean; isActive: boolean;
displayOrder: number; displayOrder: number;
maxMeteragePerInvoice: number; maxMeteragePerInvoice: number;
ownMaxMeteragePerInvoice: number | undefined;
cashInvoiceValidityHours: number; cashInvoiceValidityHours: number;
cashInvoiceValidUntilHour: number | undefined; cashInvoiceValidUntilHour: number | undefined;
catalogCount: number; catalogCount: number;
@@ -99,6 +103,7 @@ export declare class CompaniesController {
isActive: boolean; isActive: boolean;
displayOrder: number; displayOrder: number;
maxMeteragePerInvoice: number; maxMeteragePerInvoice: number;
ownMaxMeteragePerInvoice: number | undefined;
cashInvoiceValidityHours: number; cashInvoiceValidityHours: number;
cashInvoiceValidUntilHour: number | undefined; cashInvoiceValidUntilHour: number | undefined;
catalogCount: number; catalogCount: number;
@@ -117,6 +122,7 @@ export declare class CompaniesController {
isActive: boolean; isActive: boolean;
displayOrder: number; displayOrder: number;
maxMeteragePerInvoice: number; maxMeteragePerInvoice: number;
ownMaxMeteragePerInvoice: number | undefined;
cashInvoiceValidityHours: number; cashInvoiceValidityHours: number;
cashInvoiceValidUntilHour: number | undefined; cashInvoiceValidUntilHour: number | undefined;
catalogCount: number; catalogCount: number;
+6
View File
@@ -13,6 +13,7 @@ export declare class CompaniesService {
isActive: boolean; isActive: boolean;
displayOrder: number; displayOrder: number;
maxMeteragePerInvoice: number; maxMeteragePerInvoice: number;
ownMaxMeteragePerInvoice: number | undefined;
cashInvoiceValidityHours: number; cashInvoiceValidityHours: number;
cashInvoiceValidUntilHour: number | undefined; cashInvoiceValidUntilHour: number | undefined;
catalogCount: number; catalogCount: number;
@@ -45,6 +46,7 @@ export declare class CompaniesService {
isActive: boolean; isActive: boolean;
displayOrder: number; displayOrder: number;
maxMeteragePerInvoice: number; maxMeteragePerInvoice: number;
ownMaxMeteragePerInvoice: number | undefined;
cashInvoiceValidityHours: number; cashInvoiceValidityHours: number;
cashInvoiceValidUntilHour: number | undefined; cashInvoiceValidUntilHour: number | undefined;
catalogCount: number; catalogCount: number;
@@ -53,6 +55,7 @@ export declare class CompaniesService {
createdAt: string; createdAt: string;
updatedAt: string; updatedAt: string;
}>; }>;
private settingsMaxMeterage;
create(dto: CreateCompanyDto): Promise<{ create(dto: CreateCompanyDto): Promise<{
id: string; id: string;
name: string; name: string;
@@ -63,6 +66,7 @@ export declare class CompaniesService {
isActive: boolean; isActive: boolean;
displayOrder: number; displayOrder: number;
maxMeteragePerInvoice: number; maxMeteragePerInvoice: number;
ownMaxMeteragePerInvoice: number | undefined;
cashInvoiceValidityHours: number; cashInvoiceValidityHours: number;
cashInvoiceValidUntilHour: number | undefined; cashInvoiceValidUntilHour: number | undefined;
catalogCount: number; catalogCount: number;
@@ -81,6 +85,7 @@ export declare class CompaniesService {
isActive: boolean; isActive: boolean;
displayOrder: number; displayOrder: number;
maxMeteragePerInvoice: number; maxMeteragePerInvoice: number;
ownMaxMeteragePerInvoice: number | undefined;
cashInvoiceValidityHours: number; cashInvoiceValidityHours: number;
cashInvoiceValidUntilHour: number | undefined; cashInvoiceValidUntilHour: number | undefined;
catalogCount: number; catalogCount: number;
@@ -99,6 +104,7 @@ export declare class CompaniesService {
isActive: boolean; isActive: boolean;
displayOrder: number; displayOrder: number;
maxMeteragePerInvoice: number; maxMeteragePerInvoice: number;
ownMaxMeteragePerInvoice: number | undefined;
cashInvoiceValidityHours: number; cashInvoiceValidityHours: number;
cashInvoiceValidUntilHour: number | undefined; cashInvoiceValidUntilHour: number | undefined;
catalogCount: number; catalogCount: number;
+36 -21
View File
@@ -13,40 +13,54 @@ exports.CompaniesService = void 0;
const common_1 = require("@nestjs/common"); const common_1 = require("@nestjs/common");
const client_1 = require("@prisma/client"); const client_1 = require("@prisma/client");
const prisma_service_1 = require("../prisma/prisma.service"); const prisma_service_1 = require("../prisma/prisma.service");
const meterage_1 = require("../common/utils/meterage");
let CompaniesService = class CompaniesService { let CompaniesService = class CompaniesService {
prisma; prisma;
constructor(prisma) { constructor(prisma) {
this.prisma = prisma; this.prisma = prisma;
} }
async findAll(activeOnly = false) { async findAll(activeOnly = false) {
const companies = await this.prisma.company.findMany({ const [companies, fallbackMeterage] = await Promise.all([
where: activeOnly ? { isActive: true } : undefined, this.prisma.company.findMany({
orderBy: [{ displayOrder: 'asc' }, { name: 'asc' }], where: activeOnly ? { isActive: true } : undefined,
include: { orderBy: [{ displayOrder: 'asc' }, { name: 'asc' }],
_count: { include: {
select: { catalogs: true, products: true, bankAccounts: true }, _count: {
select: { catalogs: true, products: true, bankAccounts: true },
},
}, },
}, }),
}); this.settingsMaxMeterage(),
return companies.map((c) => this.mapCompany(c)); ]);
return companies.map((c) => this.mapCompany(c, fallbackMeterage));
} }
async findOne(id) { async findOne(id) {
const company = await this.prisma.company.findUnique({ const [company, fallbackMeterage] = await Promise.all([
where: { id }, this.prisma.company.findUnique({
include: { where: { id },
bankAccounts: { orderBy: { displayOrder: 'asc' } }, include: {
_count: { bankAccounts: { orderBy: { displayOrder: 'asc' } },
select: { catalogs: true, products: true, bankAccounts: true }, _count: {
select: { catalogs: true, products: true, bankAccounts: true },
},
}, },
}, }),
}); this.settingsMaxMeterage(),
]);
if (!company) if (!company)
throw new common_1.NotFoundException('شرکت یافت نشد'); throw new common_1.NotFoundException('شرکت یافت نشد');
return { return {
...this.mapCompany(company), ...this.mapCompany(company, fallbackMeterage),
bankAccounts: company.bankAccounts.map((b) => this.mapBankAccount(b)), bankAccounts: company.bankAccounts.map((b) => this.mapBankAccount(b)),
}; };
} }
async settingsMaxMeterage() {
const settings = await this.prisma.systemSettings.findUnique({
where: { id: 'default' },
select: { maxMeteragePerInvoice: true },
});
return settings?.maxMeteragePerInvoice ?? null;
}
async create(dto) { async create(dto) {
try { try {
const company = await this.prisma.company.create({ const company = await this.prisma.company.create({
@@ -56,7 +70,7 @@ let CompaniesService = class CompaniesService {
description: dto.description?.trim() || null, description: dto.description?.trim() || null,
logoUrl: dto.logoUrl?.trim() || null, logoUrl: dto.logoUrl?.trim() || null,
displayOrder: dto.displayOrder ?? 0, displayOrder: dto.displayOrder ?? 0,
maxMeteragePerInvoice: dto.maxMeteragePerInvoice ?? 5000, maxMeteragePerInvoice: dto.maxMeteragePerInvoice ?? null,
cashInvoiceValidityHours: dto.cashInvoiceValidityHours ?? 8, cashInvoiceValidityHours: dto.cashInvoiceValidityHours ?? 8,
cashInvoiceValidUntilHour: dto.cashInvoiceValidUntilHour ?? null, cashInvoiceValidUntilHour: dto.cashInvoiceValidUntilHour ?? null,
isActive: dto.isActive ?? true, isActive: dto.isActive ?? true,
@@ -186,7 +200,7 @@ let CompaniesService = class CompaniesService {
throw new common_1.NotFoundException('حساب بانکی یافت نشد'); throw new common_1.NotFoundException('حساب بانکی یافت نشد');
return account; return account;
} }
mapCompany(company) { mapCompany(company, fallbackMaxMeterage = null) {
return { return {
id: company.id, id: company.id,
name: company.name, name: company.name,
@@ -196,7 +210,8 @@ let CompaniesService = class CompaniesService {
logo: company.logoUrl ?? undefined, logo: company.logoUrl ?? undefined,
isActive: company.isActive, isActive: company.isActive,
displayOrder: company.displayOrder, displayOrder: company.displayOrder,
maxMeteragePerInvoice: company.maxMeteragePerInvoice, maxMeteragePerInvoice: (0, meterage_1.resolveMaxMeterage)(company.maxMeteragePerInvoice, fallbackMaxMeterage),
ownMaxMeteragePerInvoice: company.maxMeteragePerInvoice ?? undefined,
cashInvoiceValidityHours: company.cashInvoiceValidityHours, cashInvoiceValidityHours: company.cashInvoiceValidityHours,
cashInvoiceValidUntilHour: company.cashInvoiceValidUntilHour ?? undefined, cashInvoiceValidUntilHour: company.cashInvoiceValidUntilHour ?? undefined,
catalogCount: company._count?.catalogs ?? 0, catalogCount: company._count?.catalogs ?? 0,
File diff suppressed because one or more lines are too long
+6 -4
View File
@@ -18,17 +18,19 @@ export declare class DashboardController {
userId: string; userId: string;
companyId: string; companyId: string;
companyName: string; companyName: string;
settlementType: import("@prisma/client").$Enums.SettlementType; settlementType: import("@prisma/client").$Enums.SettlementType | null;
settlementMethod: import("@prisma/client").$Enums.SettlementType; settlementMethod: import("@prisma/client").$Enums.SettlementType | null;
checkMode: import("@prisma/client").$Enums.CheckRegistrationMode | undefined; checkMode: import("@prisma/client").$Enums.CheckRegistrationMode | undefined;
status: import("@prisma/client").$Enums.OrderStatus; status: import("@prisma/client").$Enums.OrderStatus;
adminStatus: string; adminStatus: string;
allowedTransitions: import("@prisma/client").$Enums.OrderStatus[];
totalMeterage: number; totalMeterage: number;
meterage: number; meterage: number;
totalAmount: number | null; totalAmount: number | null;
shippingCost: number | null; shippingCost: number | null;
totalDiscount: number | null; totalDiscount: number | null;
finalAmount: number | null; finalAmount: number | null;
pricesVisible: boolean;
notes: string | undefined; notes: string | undefined;
checkRassDays: number | undefined; checkRassDays: number | undefined;
checkRassDate: string | undefined; checkRassDate: string | undefined;
@@ -50,8 +52,8 @@ export declare class DashboardController {
confirmedMeterage: number | null; confirmedMeterage: number | null;
unitPrice: number | null; unitPrice: number | null;
supplyPrice: number | null; supplyPrice: number | null;
unitDiscount: number; unitDiscount: number | null;
discount: number; discount: number | null;
lineTotal: number | null; lineTotal: number | null;
totalAmount: number | null; totalAmount: number | null;
notes: string | undefined; notes: string | undefined;
+6 -4
View File
@@ -20,17 +20,19 @@ export declare class DashboardService {
userId: string; userId: string;
companyId: string; companyId: string;
companyName: string; companyName: string;
settlementType: import("@prisma/client").$Enums.SettlementType; settlementType: import("@prisma/client").$Enums.SettlementType | null;
settlementMethod: import("@prisma/client").$Enums.SettlementType; settlementMethod: import("@prisma/client").$Enums.SettlementType | null;
checkMode: import("@prisma/client").$Enums.CheckRegistrationMode | undefined; checkMode: import("@prisma/client").$Enums.CheckRegistrationMode | undefined;
status: import("@prisma/client").$Enums.OrderStatus; status: import("@prisma/client").$Enums.OrderStatus;
adminStatus: string; adminStatus: string;
allowedTransitions: import("@prisma/client").$Enums.OrderStatus[];
totalMeterage: number; totalMeterage: number;
meterage: number; meterage: number;
totalAmount: number | null; totalAmount: number | null;
shippingCost: number | null; shippingCost: number | null;
totalDiscount: number | null; totalDiscount: number | null;
finalAmount: number | null; finalAmount: number | null;
pricesVisible: boolean;
notes: string | undefined; notes: string | undefined;
checkRassDays: number | undefined; checkRassDays: number | undefined;
checkRassDate: string | undefined; checkRassDate: string | undefined;
@@ -52,8 +54,8 @@ export declare class DashboardService {
confirmedMeterage: number | null; confirmedMeterage: number | null;
unitPrice: number | null; unitPrice: number | null;
supplyPrice: number | null; supplyPrice: number | null;
unitDiscount: number; unitDiscount: number | null;
discount: number; discount: number | null;
lineTotal: number | null; lineTotal: number | null;
totalAmount: number | null; totalAmount: number | null;
notes: string | undefined; notes: string | undefined;
+22
View File
@@ -0,0 +1,22 @@
import { NotificationTone, OrderStatus } from '@prisma/client';
export type CustomerEvent = 'order_submitted' | 'invoice_priced' | 'checks_approved' | 'checks_need_fix' | 'receipt_approved' | 'receipt_rejected' | 'cash_invoice_reminder' | 'cash_invoice_expired' | 'order_preparing' | 'order_ready_to_ship' | 'order_shipped' | 'order_delivered' | 'order_cancelled';
type SmsSwitch = 'smsOrderRegistered' | 'smsPricingReady' | 'smsPaymentReminder' | 'smsCheckReview' | 'smsShippingUpdate' | null;
export type CustomerEventTemplate = {
title: string;
body: (ctx: EventContext) => string;
sms?: (ctx: EventContext) => string;
tone: NotificationTone;
smsSwitch: SmsSwitch;
};
export type EventContext = {
orderNumber: string;
appName: string;
amount?: number | null;
note?: string | null;
deadline?: string | null;
carrier?: string | null;
trackingNumber?: string | null;
};
export declare function eventForStatus(status: OrderStatus): CustomerEvent | null;
export declare const CUSTOMER_EVENTS: Record<CustomerEvent, CustomerEventTemplate>;
export {};
+125
View File
@@ -0,0 +1,125 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.CUSTOMER_EVENTS = void 0;
exports.eventForStatus = eventForStatus;
const client_1 = require("@prisma/client");
function money(amount) {
if (!amount)
return '—';
return `${Math.round(amount).toLocaleString('fa-IR')} تومان`;
}
function eventForStatus(status) {
switch (status) {
case client_1.OrderStatus.awaiting_settlement:
return 'invoice_priced';
case client_1.OrderStatus.preparing:
case client_1.OrderStatus.final_approved:
return 'order_preparing';
case client_1.OrderStatus.ready_to_ship:
return 'order_ready_to_ship';
case client_1.OrderStatus.shipped:
return 'order_shipped';
case client_1.OrderStatus.delivered:
return 'order_delivered';
case client_1.OrderStatus.cancelled:
case client_1.OrderStatus.rejected:
return 'order_cancelled';
case client_1.OrderStatus.expired:
return 'cash_invoice_expired';
default:
return null;
}
}
exports.CUSTOMER_EVENTS = {
order_submitted: {
title: 'سفارش شما ثبت شد',
body: (c) => `سفارش ${c.orderNumber} ثبت شد و برای بررسی و قیمت‌گذاری به کارشناسان ما رسید.`,
sms: (c) => `سفارش ${c.orderNumber} در ${c.appName} ثبت شد. پس از قیمت‌گذاری، فاکتور برای شما ارسال می‌شود.`,
tone: client_1.NotificationTone.info,
smsSwitch: 'smsOrderRegistered',
},
invoice_priced: {
title: 'فاکتور سفارش شما صادر شد',
body: (c) => `مبلغ فاکتور سفارش ${c.orderNumber} ${money(c.amount)} است. برای ادامه، روش تسویه را انتخاب کنید.`,
sms: (c) => `فاکتور سفارش ${c.orderNumber} به مبلغ ${money(c.amount)} صادر شد. برای انتخاب روش تسویه به پنل ${c.appName} مراجعه کنید.`,
tone: client_1.NotificationTone.success,
smsSwitch: 'smsPricingReady',
},
checks_approved: {
title: 'چک‌های شما تایید شد',
body: (c) => `همه چک‌های سفارش ${c.orderNumber} تایید شدند.`,
sms: (c) => `چک‌های سفارش ${c.orderNumber} تایید شد و سفارش به مرحله آماده‌سازی می‌رود.`,
tone: client_1.NotificationTone.success,
smsSwitch: 'smsCheckReview',
},
checks_need_fix: {
title: 'چک شما نیاز به اصلاح دارد',
body: (c) => `یک یا چند چک سفارش ${c.orderNumber} تایید نشد${c.note ? `: ${c.note}` : '.'} لطفاً چک‌ها را اصلاح و مجدداً ارسال کنید.`,
sms: (c) => `چک سفارش ${c.orderNumber} تایید نشد. برای اصلاح چک‌ها به پنل ${c.appName} مراجعه کنید.`,
tone: client_1.NotificationTone.danger,
smsSwitch: 'smsCheckReview',
},
receipt_approved: {
title: 'پرداخت شما تایید شد',
body: (c) => `رسید پرداخت سفارش ${c.orderNumber} تایید شد.`,
sms: (c) => `رسید پرداخت سفارش ${c.orderNumber} تایید شد. سفارش شما در حال آماده‌سازی است.`,
tone: client_1.NotificationTone.success,
smsSwitch: 'smsPaymentReminder',
},
receipt_rejected: {
title: 'رسید پرداخت تایید نشد',
body: (c) => `رسید پرداخت سفارش ${c.orderNumber} تایید نشد${c.note ? `: ${c.note}` : '.'} لطفاً رسید صحیح را ثبت کنید.`,
sms: (c) => `رسید پرداخت سفارش ${c.orderNumber} تایید نشد. لطفاً در پنل ${c.appName} رسید را اصلاح کنید.`,
tone: client_1.NotificationTone.danger,
smsSwitch: 'smsPaymentReminder',
},
cash_invoice_reminder: {
title: 'یادآوری مهلت پرداخت',
body: (c) => `مهلت پرداخت فاکتور سفارش ${c.orderNumber} به پایان نزدیک است. مبلغ قابل پرداخت ${money(c.amount)}.`,
sms: (c) => `مهلت پرداخت فاکتور سفارش ${c.orderNumber} در حال پایان است. مبلغ ${money(c.amount)}.`,
tone: client_1.NotificationTone.warning,
smsSwitch: 'smsPaymentReminder',
},
cash_invoice_expired: {
title: 'مهلت پرداخت فاکتور تمام شد',
body: (c) => `مهلت پرداخت فاکتور سفارش ${c.orderNumber} به پایان رسید. برای ثبت سفارش تازه با پشتیبانی تماس بگیرید.`,
sms: (c) => `مهلت پرداخت فاکتور سفارش ${c.orderNumber} به پایان رسید.`,
tone: client_1.NotificationTone.danger,
smsSwitch: 'smsPaymentReminder',
},
order_preparing: {
title: 'سفارش در حال آماده‌سازی',
body: (c) => `سفارش ${c.orderNumber} تایید نهایی شد و در حال آماده‌سازی است.`,
tone: client_1.NotificationTone.info,
smsSwitch: null,
},
order_ready_to_ship: {
title: 'سفارش آماده ارسال است',
body: (c) => `سفارش ${c.orderNumber} آماده ارسال شد.`,
sms: (c) => `سفارش ${c.orderNumber} آماده ارسال شد.`,
tone: client_1.NotificationTone.info,
smsSwitch: 'smsShippingUpdate',
},
order_shipped: {
title: 'سفارش ارسال شد',
body: (c) => `سفارش ${c.orderNumber} ارسال شد${c.carrier ? ` (${c.carrier})` : ''}${c.trackingNumber ? ` با کد رهگیری ${c.trackingNumber}` : ''}.`,
sms: (c) => `سفارش ${c.orderNumber} ارسال شد${c.trackingNumber ? `. کد رهگیری: ${c.trackingNumber}` : '.'}`,
tone: client_1.NotificationTone.success,
smsSwitch: 'smsShippingUpdate',
},
order_delivered: {
title: 'سفارش تحویل شد',
body: (c) => `سفارش ${c.orderNumber} تحویل داده شد. از خرید شما سپاسگزاریم.`,
sms: (c) => `سفارش ${c.orderNumber} تحویل داده شد. از خرید شما سپاسگزاریم.`,
tone: client_1.NotificationTone.success,
smsSwitch: 'smsShippingUpdate',
},
order_cancelled: {
title: 'سفارش لغو شد',
body: (c) => `سفارش ${c.orderNumber} لغو شد${c.note ? `: ${c.note}` : '.'} برای اطلاعات بیشتر با پشتیبانی تماس بگیرید.`,
sms: (c) => `سفارش ${c.orderNumber} لغو شد.`,
tone: client_1.NotificationTone.danger,
smsSwitch: null,
},
};
//# sourceMappingURL=notification-events.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"notification-events.js","sourceRoot":"","sources":["../../src/notifications/notification-events.ts"],"names":[],"mappings":"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"}
+36
View File
@@ -0,0 +1,36 @@
import { NotificationsService } from './notifications.service';
import type { AuthPrincipal } from '../common/decorators/current-user.decorator';
export declare class CustomerNotificationsController {
private readonly notifications;
constructor(notifications: NotificationsService);
list(user: AuthPrincipal): Promise<{
id: string;
orderId: string | undefined;
event: string;
title: string;
body: string;
tone: import("@prisma/client").$Enums.NotificationTone;
link: string | undefined;
isRead: boolean;
sentAt: string;
}[]>;
unreadCount(user: AuthPrincipal): Promise<{
count: number;
}>;
markAllRead(user: AuthPrincipal): Promise<{
count: number;
}>;
markRead(user: AuthPrincipal, id: string): Promise<{
count: number;
}>;
}
export declare class AdminNotificationsController {
private readonly notifications;
constructor(notifications: NotificationsService);
markAllRead(): Promise<{
ok: boolean;
}>;
markRead(id: string): Promise<{
ok: boolean;
}>;
}
+104
View File
@@ -0,0 +1,104 @@
"use strict";
var __decorate = (this && this.__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 __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.AdminNotificationsController = exports.CustomerNotificationsController = void 0;
const common_1 = require("@nestjs/common");
const notifications_service_1 = require("./notifications.service");
const auth_decorators_1 = require("../common/decorators/auth.decorators");
const current_user_decorator_1 = require("../common/decorators/current-user.decorator");
let CustomerNotificationsController = class CustomerNotificationsController {
notifications;
constructor(notifications) {
this.notifications = notifications;
}
list(user) {
return this.notifications.listForCustomer(user.id);
}
unreadCount(user) {
return this.notifications.unreadCount(user.id);
}
markAllRead(user) {
return this.notifications.markAllRead(user.id);
}
markRead(user, id) {
return this.notifications.markRead(user.id, id);
}
};
exports.CustomerNotificationsController = CustomerNotificationsController;
__decorate([
(0, common_1.Get)(),
__param(0, (0, current_user_decorator_1.CurrentUser)()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object]),
__metadata("design:returntype", void 0)
], CustomerNotificationsController.prototype, "list", null);
__decorate([
(0, common_1.Get)('unread-count'),
__param(0, (0, current_user_decorator_1.CurrentUser)()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object]),
__metadata("design:returntype", void 0)
], CustomerNotificationsController.prototype, "unreadCount", null);
__decorate([
(0, common_1.Patch)('read-all'),
__param(0, (0, current_user_decorator_1.CurrentUser)()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object]),
__metadata("design:returntype", void 0)
], CustomerNotificationsController.prototype, "markAllRead", null);
__decorate([
(0, common_1.Patch)(':id/read'),
__param(0, (0, current_user_decorator_1.CurrentUser)()),
__param(1, (0, common_1.Param)('id')),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object, String]),
__metadata("design:returntype", void 0)
], CustomerNotificationsController.prototype, "markRead", null);
exports.CustomerNotificationsController = CustomerNotificationsController = __decorate([
(0, common_1.Controller)('notifications'),
(0, auth_decorators_1.AuthType)('customer'),
__metadata("design:paramtypes", [notifications_service_1.NotificationsService])
], CustomerNotificationsController);
let AdminNotificationsController = class AdminNotificationsController {
notifications;
constructor(notifications) {
this.notifications = notifications;
}
markAllRead() {
return this.notifications.markAllAdminRead();
}
markRead(id) {
return this.notifications.markAdminRead(id);
}
};
exports.AdminNotificationsController = AdminNotificationsController;
__decorate([
(0, common_1.Patch)('read-all'),
__metadata("design:type", Function),
__metadata("design:paramtypes", []),
__metadata("design:returntype", void 0)
], AdminNotificationsController.prototype, "markAllRead", null);
__decorate([
(0, common_1.Patch)(':id/read'),
__param(0, (0, common_1.Param)('id')),
__metadata("design:type", Function),
__metadata("design:paramtypes", [String]),
__metadata("design:returntype", void 0)
], AdminNotificationsController.prototype, "markRead", null);
exports.AdminNotificationsController = AdminNotificationsController = __decorate([
(0, common_1.Controller)('admin/notifications'),
(0, auth_decorators_1.AuthType)('admin'),
__metadata("design:paramtypes", [notifications_service_1.NotificationsService])
], AdminNotificationsController);
//# sourceMappingURL=notifications.controller.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"notifications.controller.js","sourceRoot":"","sources":["../../src/notifications/notifications.controller.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,2CAA+D;AAC/D,mEAA+D;AAC/D,0EAAgE;AAChE,wFAA0E;AAKnE,IAAM,+BAA+B,GAArC,MAAM,+BAA+B;IACb;IAA7B,YAA6B,aAAmC;QAAnC,kBAAa,GAAb,aAAa,CAAsB;IAAG,CAAC;IAGpE,IAAI,CAAgB,IAAmB;QACrC,OAAO,IAAI,CAAC,aAAa,CAAC,eAAe,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACrD,CAAC;IAGD,WAAW,CAAgB,IAAmB;QAC5C,OAAO,IAAI,CAAC,aAAa,CAAC,WAAW,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACjD,CAAC;IAGD,WAAW,CAAgB,IAAmB;QAC5C,OAAO,IAAI,CAAC,aAAa,CAAC,WAAW,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACjD,CAAC;IAGD,QAAQ,CAAgB,IAAmB,EAAe,EAAU;QAClE,OAAO,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;IAClD,CAAC;CACF,CAAA;AAtBY,0EAA+B;AAI1C;IADC,IAAA,YAAG,GAAE;IACA,WAAA,IAAA,oCAAW,GAAE,CAAA;;;;2DAElB;AAGD;IADC,IAAA,YAAG,EAAC,cAAc,CAAC;IACP,WAAA,IAAA,oCAAW,GAAE,CAAA;;;;kEAEzB;AAGD;IADC,IAAA,cAAK,EAAC,UAAU,CAAC;IACL,WAAA,IAAA,oCAAW,GAAE,CAAA;;;;kEAEzB;AAGD;IADC,IAAA,cAAK,EAAC,UAAU,CAAC;IACR,WAAA,IAAA,oCAAW,GAAE,CAAA;IAAuB,WAAA,IAAA,cAAK,EAAC,IAAI,CAAC,CAAA;;;;+DAExD;0CArBU,+BAA+B;IAF3C,IAAA,mBAAU,EAAC,eAAe,CAAC;IAC3B,IAAA,0BAAQ,EAAC,UAAU,CAAC;qCAEyB,4CAAoB;GADrD,+BAA+B,CAsB3C;AAIM,IAAM,4BAA4B,GAAlC,MAAM,4BAA4B;IACV;IAA7B,YAA6B,aAAmC;QAAnC,kBAAa,GAAb,aAAa,CAAsB;IAAG,CAAC;IAGpE,WAAW;QACT,OAAO,IAAI,CAAC,aAAa,CAAC,gBAAgB,EAAE,CAAC;IAC/C,CAAC;IAGD,QAAQ,CAAc,EAAU;QAC9B,OAAO,IAAI,CAAC,aAAa,CAAC,aAAa,CAAC,EAAE,CAAC,CAAC;IAC9C,CAAC;CACF,CAAA;AAZY,oEAA4B;AAIvC;IADC,IAAA,cAAK,EAAC,UAAU,CAAC;;;;+DAGjB;AAGD;IADC,IAAA,cAAK,EAAC,UAAU,CAAC;IACR,WAAA,IAAA,cAAK,EAAC,IAAI,CAAC,CAAA;;;;4DAEpB;uCAXU,4BAA4B;IAFxC,IAAA,mBAAU,EAAC,qBAAqB,CAAC;IACjC,IAAA,0BAAQ,EAAC,OAAO,CAAC;qCAE4B,4CAAoB;GADrD,4BAA4B,CAYxC"}
+2
View File
@@ -0,0 +1,2 @@
export declare class NotificationsModule {
}
+25
View File
@@ -0,0 +1,25 @@
"use strict";
var __decorate = (this && this.__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(exports, "__esModule", { value: true });
exports.NotificationsModule = void 0;
const common_1 = require("@nestjs/common");
const notifications_service_1 = require("./notifications.service");
const notifications_controller_1 = require("./notifications.controller");
const sms_module_1 = require("../sms/sms.module");
let NotificationsModule = class NotificationsModule {
};
exports.NotificationsModule = NotificationsModule;
exports.NotificationsModule = NotificationsModule = __decorate([
(0, common_1.Module)({
imports: [sms_module_1.SmsModule],
controllers: [notifications_controller_1.CustomerNotificationsController, notifications_controller_1.AdminNotificationsController],
providers: [notifications_service_1.NotificationsService],
exports: [notifications_service_1.NotificationsService],
})
], NotificationsModule);
//# sourceMappingURL=notifications.module.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"notifications.module.js","sourceRoot":"","sources":["../../src/notifications/notifications.module.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAAwC;AACxC,mEAA+D;AAC/D,yEAGoC;AACpC,kDAA8C;AAQvC,IAAM,mBAAmB,GAAzB,MAAM,mBAAmB;CAAG,CAAA;AAAtB,kDAAmB;8BAAnB,mBAAmB;IAN/B,IAAA,eAAM,EAAC;QACN,OAAO,EAAE,CAAC,sBAAS,CAAC;QACpB,WAAW,EAAE,CAAC,0DAA+B,EAAE,uDAA4B,CAAC;QAC5E,SAAS,EAAE,CAAC,4CAAoB,CAAC;QACjC,OAAO,EAAE,CAAC,4CAAoB,CAAC;KAChC,CAAC;GACW,mBAAmB,CAAG"}
+61
View File
@@ -0,0 +1,61 @@
import { ConfigService } from '@nestjs/config';
import { NotificationTone } from '@prisma/client';
import { PrismaService } from '../prisma/prisma.service';
import { SmsService } from '../sms/sms.service';
import { type CustomerEvent } from './notification-events';
type NotifyOptions = {
amount?: number | null;
note?: string | null;
carrier?: string | null;
trackingNumber?: string | null;
};
export declare class NotificationsService {
private readonly prisma;
private readonly sms;
private readonly config;
private readonly logger;
constructor(prisma: PrismaService, sms: SmsService, config: ConfigService);
notifyOrderEvent(orderId: string, event: CustomerEvent, options?: NotifyOptions): Promise<void>;
dispatchOrderEvent(orderId: string, event: CustomerEvent, options?: NotifyOptions): void;
notifyAdmin(input: {
title: string;
body: string;
tone?: NotificationTone;
link?: string;
}): Promise<void>;
dispatchAdmin(input: {
title: string;
body: string;
tone?: NotificationTone;
link?: string;
}): void;
listForCustomer(customerId: string, limit?: number): Promise<{
id: string;
orderId: string | undefined;
event: string;
title: string;
body: string;
tone: import("@prisma/client").$Enums.NotificationTone;
link: string | undefined;
isRead: boolean;
sentAt: string;
}[]>;
unreadCount(customerId: string): Promise<{
count: number;
}>;
markRead(customerId: string, id: string): Promise<{
count: number;
}>;
markAllRead(customerId: string): Promise<{
count: number;
}>;
markAdminRead(id: string): Promise<{
ok: boolean;
}>;
markAllAdminRead(): Promise<{
ok: boolean;
}>;
private appName;
private isSmsEventEnabled;
}
export {};
+185
View File
@@ -0,0 +1,185 @@
"use strict";
var __decorate = (this && this.__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 __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var NotificationsService_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.NotificationsService = void 0;
const common_1 = require("@nestjs/common");
const config_1 = require("@nestjs/config");
const client_1 = require("@prisma/client");
const prisma_service_1 = require("../prisma/prisma.service");
const sms_service_1 = require("../sms/sms.service");
const notification_events_1 = require("./notification-events");
let NotificationsService = NotificationsService_1 = class NotificationsService {
prisma;
sms;
config;
logger = new common_1.Logger(NotificationsService_1.name);
constructor(prisma, sms, config) {
this.prisma = prisma;
this.sms = sms;
this.config = config;
}
async notifyOrderEvent(orderId, event, options = {}) {
try {
const order = await this.prisma.order.findUnique({
where: { id: orderId },
select: {
id: true,
orderNumber: true,
userId: true,
finalAmount: true,
totalAmount: true,
user: { select: { phone: true } },
},
});
if (!order)
return;
const template = notification_events_1.CUSTOMER_EVENTS[event];
const ctx = {
orderNumber: order.orderNumber,
appName: this.appName(),
amount: options.amount ?? order.finalAmount ?? order.totalAmount ?? undefined,
note: options.note ?? null,
carrier: options.carrier ?? null,
trackingNumber: options.trackingNumber ?? null,
};
const notification = await this.prisma.customerNotification.create({
data: {
customerId: order.userId,
orderId: order.id,
event,
title: template.title,
body: template.body(ctx),
tone: template.tone,
link: `/orders/${order.id}`,
},
});
if (!template.sms || !template.smsSwitch)
return;
const enabled = await this.isSmsEventEnabled(template.smsSwitch);
if (!enabled || !this.sms.isEnabled())
return;
const result = await this.sms.send(order.user.phone, template.sms(ctx));
if (result.success) {
await this.prisma.customerNotification.update({
where: { id: notification.id },
data: { smsSent: true },
});
}
}
catch (err) {
this.logger.error(`notifyOrderEvent(${event}) failed for order ${orderId}`, err instanceof Error ? err.stack : err);
}
}
dispatchOrderEvent(orderId, event, options = {}) {
void this.notifyOrderEvent(orderId, event, options);
}
async notifyAdmin(input) {
try {
await this.prisma.adminNotification.create({
data: {
title: input.title,
body: input.body,
tone: input.tone ?? client_1.NotificationTone.info,
link: input.link ?? null,
},
});
}
catch (err) {
this.logger.error('notifyAdmin failed', err instanceof Error ? err.stack : err);
}
}
dispatchAdmin(input) {
void this.notifyAdmin(input);
}
async listForCustomer(customerId, limit = 50) {
const rows = await this.prisma.customerNotification.findMany({
where: { customerId },
orderBy: { sentAt: 'desc' },
take: limit,
});
return rows.map((n) => ({
id: n.id,
orderId: n.orderId ?? undefined,
event: n.event,
title: n.title,
body: n.body,
tone: n.tone ?? client_1.NotificationTone.info,
link: n.link ?? undefined,
isRead: n.isRead,
sentAt: n.sentAt.toISOString(),
}));
}
async unreadCount(customerId) {
const count = await this.prisma.customerNotification.count({
where: { customerId, isRead: false },
});
return { count };
}
async markRead(customerId, id) {
const existing = await this.prisma.customerNotification.findFirst({
where: { id, customerId },
select: { id: true },
});
if (!existing)
throw new common_1.NotFoundException('اعلان یافت نشد');
await this.prisma.customerNotification.update({
where: { id },
data: { isRead: true },
});
return this.unreadCount(customerId);
}
async markAllRead(customerId) {
await this.prisma.customerNotification.updateMany({
where: { customerId, isRead: false },
data: { isRead: true },
});
return { count: 0 };
}
async markAdminRead(id) {
await this.prisma.adminNotification.updateMany({
where: { id },
data: { isRead: true },
});
return { ok: true };
}
async markAllAdminRead() {
await this.prisma.adminNotification.updateMany({
where: { isRead: false },
data: { isRead: true },
});
return { ok: true };
}
appName() {
return this.config.get('SMS_APP_NAME', 'پارچه') ?? 'پارچه';
}
async isSmsEventEnabled(key) {
const settings = await this.prisma.systemSettings.findUnique({
where: { id: 'default' },
select: {
smsOrderRegistered: true,
smsPricingReady: true,
smsPaymentReminder: true,
smsCheckReview: true,
smsShippingUpdate: true,
},
});
return settings ? settings[key] : true;
}
};
exports.NotificationsService = NotificationsService;
exports.NotificationsService = NotificationsService = NotificationsService_1 = __decorate([
(0, common_1.Injectable)(),
__metadata("design:paramtypes", [prisma_service_1.PrismaService,
sms_service_1.SmsService,
config_1.ConfigService])
], NotificationsService);
//# sourceMappingURL=notifications.service.js.map
File diff suppressed because one or more lines are too long
+11
View File
@@ -0,0 +1,11 @@
import { PrismaService } from '../prisma/prisma.service';
import { NotificationsService } from '../notifications/notifications.service';
export declare class CashInvoiceSchedulerService {
private readonly prisma;
private readonly notifications;
private readonly logger;
constructor(prisma: PrismaService, notifications: NotificationsService);
handleCashInvoices(): Promise<void>;
sendReminders(): Promise<number>;
expireOverdueInvoices(): Promise<number>;
}
+118
View File
@@ -0,0 +1,118 @@
"use strict";
var __decorate = (this && this.__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 __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var CashInvoiceSchedulerService_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.CashInvoiceSchedulerService = void 0;
const common_1 = require("@nestjs/common");
const schedule_1 = require("@nestjs/schedule");
const client_1 = require("@prisma/client");
const prisma_service_1 = require("../prisma/prisma.service");
const notifications_service_1 = require("../notifications/notifications.service");
const PAYABLE_STATUSES = [
client_1.OrderStatus.awaiting_cash_payment,
client_1.OrderStatus.cash_invoice_issued,
];
const REMINDER_WINDOW_MS = 60 * 60 * 1000;
let CashInvoiceSchedulerService = CashInvoiceSchedulerService_1 = class CashInvoiceSchedulerService {
prisma;
notifications;
logger = new common_1.Logger(CashInvoiceSchedulerService_1.name);
constructor(prisma, notifications) {
this.prisma = prisma;
this.notifications = notifications;
}
async handleCashInvoices() {
await this.sendReminders();
await this.expireOverdueInvoices();
}
async sendReminders() {
const now = new Date();
const soon = new Date(now.getTime() + REMINDER_WINDOW_MS);
try {
const orders = await this.prisma.order.findMany({
where: {
status: { in: PAYABLE_STATUSES },
cashReminderSentAt: null,
cashInvoiceExpiresAt: { gt: now, lte: soon },
},
select: { id: true, finalAmount: true },
});
for (const order of orders) {
await this.prisma.order.update({
where: { id: order.id },
data: { cashReminderSentAt: now },
});
await this.notifications.notifyOrderEvent(order.id, 'cash_invoice_reminder', { amount: order.finalAmount });
}
return orders.length;
}
catch (err) {
this.logger.error('sendReminders failed', err instanceof Error ? err.stack : err);
return 0;
}
}
async expireOverdueInvoices() {
const now = new Date();
try {
const orders = await this.prisma.order.findMany({
where: {
status: { in: PAYABLE_STATUSES },
cashInvoiceExpiresAt: { lt: now },
},
select: { id: true, orderNumber: true, status: true },
});
for (const order of orders) {
await this.prisma.$transaction([
this.prisma.order.update({
where: { id: order.id },
data: { status: client_1.OrderStatus.expired },
}),
this.prisma.orderStatusHistory.create({
data: {
orderId: order.id,
fromStatus: order.status,
toStatus: client_1.OrderStatus.expired,
changedBy: 'system',
note: 'پایان مهلت پرداخت فاکتور نقدی',
},
}),
]);
await this.notifications.notifyOrderEvent(order.id, 'cash_invoice_expired');
this.notifications.dispatchAdmin({
title: 'فاکتور نقدی منقضی شد',
body: `مهلت پرداخت سفارش ${order.orderNumber} به پایان رسید و سفارش منقضی شد.`,
link: '/orders',
});
}
if (orders.length) {
this.logger.log(`${orders.length} فاکتور نقدی منقضی شد`);
}
return orders.length;
}
catch (err) {
this.logger.error('expireOverdueInvoices failed', err instanceof Error ? err.stack : err);
return 0;
}
}
};
exports.CashInvoiceSchedulerService = CashInvoiceSchedulerService;
__decorate([
(0, schedule_1.Cron)(schedule_1.CronExpression.EVERY_10_MINUTES),
__metadata("design:type", Function),
__metadata("design:paramtypes", []),
__metadata("design:returntype", Promise)
], CashInvoiceSchedulerService.prototype, "handleCashInvoices", null);
exports.CashInvoiceSchedulerService = CashInvoiceSchedulerService = CashInvoiceSchedulerService_1 = __decorate([
(0, common_1.Injectable)(),
__metadata("design:paramtypes", [prisma_service_1.PrismaService,
notifications_service_1.NotificationsService])
], CashInvoiceSchedulerService);
//# sourceMappingURL=cash-invoice-scheduler.service.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"cash-invoice-scheduler.service.js","sourceRoot":"","sources":["../../src/orders/cash-invoice-scheduler.service.ts"],"names":[],"mappings":"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"}
+6 -1
View File
@@ -22,13 +22,17 @@ export declare class SubmitOrderCheckDto {
} }
export declare class SubmitOrderDto { export declare class SubmitOrderDto {
companyId: string; companyId: string;
settlementType: SettlementType; settlementType?: SettlementType;
settlementMethod?: SettlementType; settlementMethod?: SettlementType;
checkMode?: CheckRegistrationMode; checkMode?: CheckRegistrationMode;
notes?: string; notes?: string;
items: SubmitOrderItemDto[]; items: SubmitOrderItemDto[];
checks?: SubmitOrderCheckDto[]; checks?: SubmitOrderCheckDto[];
} }
export declare class ChooseSettlementDto {
settlementType: SettlementType;
checkMode?: CheckRegistrationMode;
}
export declare class AdminCreateOrderDto { export declare class AdminCreateOrderDto {
userId?: string; userId?: string;
customerPhone?: string; customerPhone?: string;
@@ -57,6 +61,7 @@ export declare class UpdateOrderStatusDto {
status: OrderStatus; status: OrderStatus;
note?: string; note?: string;
reason?: string; reason?: string;
force?: boolean;
} }
export declare class UpdateOrderChecksDto { export declare class UpdateOrderChecksDto {
checks: SubmitOrderCheckDto[]; checks: SubmitOrderCheckDto[];
+23 -1
View File
@@ -9,7 +9,7 @@ var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
}; };
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "__esModule", { value: true });
exports.UpdateOrderChecksDto = exports.UpdateOrderStatusDto = exports.UpdateOrderPricingDto = exports.OrderItemPricingDto = exports.AdminCreateOrderDto = exports.SubmitOrderDto = exports.SubmitOrderCheckDto = exports.SubmitOrderItemDto = void 0; exports.UpdateOrderChecksDto = exports.UpdateOrderStatusDto = exports.UpdateOrderPricingDto = exports.OrderItemPricingDto = exports.AdminCreateOrderDto = exports.ChooseSettlementDto = exports.SubmitOrderDto = exports.SubmitOrderCheckDto = exports.SubmitOrderItemDto = void 0;
const class_validator_1 = require("class-validator"); const class_validator_1 = require("class-validator");
const class_transformer_1 = require("class-transformer"); const class_transformer_1 = require("class-transformer");
const client_1 = require("@prisma/client"); const client_1 = require("@prisma/client");
@@ -137,6 +137,7 @@ __decorate([
__metadata("design:type", String) __metadata("design:type", String)
], SubmitOrderDto.prototype, "companyId", void 0); ], SubmitOrderDto.prototype, "companyId", void 0);
__decorate([ __decorate([
(0, class_validator_1.IsOptional)(),
(0, class_validator_1.IsEnum)(client_1.SettlementType), (0, class_validator_1.IsEnum)(client_1.SettlementType),
__metadata("design:type", String) __metadata("design:type", String)
], SubmitOrderDto.prototype, "settlementType", void 0); ], SubmitOrderDto.prototype, "settlementType", void 0);
@@ -169,6 +170,20 @@ __decorate([
(0, class_transformer_1.Type)(() => SubmitOrderCheckDto), (0, class_transformer_1.Type)(() => SubmitOrderCheckDto),
__metadata("design:type", Array) __metadata("design:type", Array)
], SubmitOrderDto.prototype, "checks", void 0); ], SubmitOrderDto.prototype, "checks", void 0);
class ChooseSettlementDto {
settlementType;
checkMode;
}
exports.ChooseSettlementDto = ChooseSettlementDto;
__decorate([
(0, class_validator_1.IsEnum)(client_1.SettlementType),
__metadata("design:type", String)
], ChooseSettlementDto.prototype, "settlementType", void 0);
__decorate([
(0, class_validator_1.IsOptional)(),
(0, class_validator_1.IsEnum)(client_1.CheckRegistrationMode),
__metadata("design:type", String)
], ChooseSettlementDto.prototype, "checkMode", void 0);
class AdminCreateOrderDto { class AdminCreateOrderDto {
userId; userId;
customerPhone; customerPhone;
@@ -305,6 +320,7 @@ class UpdateOrderStatusDto {
status; status;
note; note;
reason; reason;
force;
} }
exports.UpdateOrderStatusDto = UpdateOrderStatusDto; exports.UpdateOrderStatusDto = UpdateOrderStatusDto;
__decorate([ __decorate([
@@ -321,6 +337,12 @@ __decorate([
(0, class_validator_1.IsString)(), (0, class_validator_1.IsString)(),
__metadata("design:type", String) __metadata("design:type", String)
], UpdateOrderStatusDto.prototype, "reason", void 0); ], UpdateOrderStatusDto.prototype, "reason", void 0);
__decorate([
(0, class_validator_1.IsOptional)(),
(0, class_transformer_1.Type)(() => Boolean),
(0, class_validator_1.IsBoolean)(),
__metadata("design:type", Boolean)
], UpdateOrderStatusDto.prototype, "force", void 0);
class UpdateOrderChecksDto { class UpdateOrderChecksDto {
checks; checks;
checkMode; checkMode;
+1 -1
View File
File diff suppressed because one or more lines are too long
+158 -37
View File
@@ -1,6 +1,6 @@
import { OrdersService } from './orders.service'; import { OrdersService } from './orders.service';
import type { AuthPrincipal } from '../common/decorators/current-user.decorator'; import type { AuthPrincipal } from '../common/decorators/current-user.decorator';
import { AdminCreateOrderDto, SubmitOrderDto, UpdateOrderChecksDto, UpdateOrderPricingDto, UpdateOrderStatusDto } from './dto/order.dto'; import { AdminCreateOrderDto, ChooseSettlementDto, SubmitOrderDto, UpdateOrderChecksDto, UpdateOrderPricingDto, UpdateOrderStatusDto } from './dto/order.dto';
export declare class CustomerOrdersController { export declare class CustomerOrdersController {
private readonly ordersService; private readonly ordersService;
constructor(ordersService: OrdersService); constructor(ordersService: OrdersService);
@@ -10,17 +10,19 @@ export declare class CustomerOrdersController {
userId: string; userId: string;
companyId: string; companyId: string;
companyName: string; companyName: string;
settlementType: import("@prisma/client").$Enums.SettlementType; settlementType: import("@prisma/client").$Enums.SettlementType | null;
settlementMethod: import("@prisma/client").$Enums.SettlementType; settlementMethod: import("@prisma/client").$Enums.SettlementType | null;
checkMode: import("@prisma/client").$Enums.CheckRegistrationMode | undefined; checkMode: import("@prisma/client").$Enums.CheckRegistrationMode | undefined;
status: import("@prisma/client").$Enums.OrderStatus; status: import("@prisma/client").$Enums.OrderStatus;
adminStatus: string; adminStatus: string;
allowedTransitions: import("@prisma/client").$Enums.OrderStatus[];
totalMeterage: number; totalMeterage: number;
meterage: number; meterage: number;
totalAmount: number | null; totalAmount: number | null;
shippingCost: number | null; shippingCost: number | null;
totalDiscount: number | null; totalDiscount: number | null;
finalAmount: number | null; finalAmount: number | null;
pricesVisible: boolean;
notes: string | undefined; notes: string | undefined;
checkRassDays: number | undefined; checkRassDays: number | undefined;
checkRassDate: string | undefined; checkRassDate: string | undefined;
@@ -42,8 +44,8 @@ export declare class CustomerOrdersController {
confirmedMeterage: number | null; confirmedMeterage: number | null;
unitPrice: number | null; unitPrice: number | null;
supplyPrice: number | null; supplyPrice: number | null;
unitDiscount: number; unitDiscount: number | null;
discount: number; discount: number | null;
lineTotal: number | null; lineTotal: number | null;
totalAmount: number | null; totalAmount: number | null;
notes: string | undefined; notes: string | undefined;
@@ -111,17 +113,19 @@ export declare class CustomerOrdersController {
userId: string; userId: string;
companyId: string; companyId: string;
companyName: string; companyName: string;
settlementType: import("@prisma/client").$Enums.SettlementType; settlementType: import("@prisma/client").$Enums.SettlementType | null;
settlementMethod: import("@prisma/client").$Enums.SettlementType; settlementMethod: import("@prisma/client").$Enums.SettlementType | null;
checkMode: import("@prisma/client").$Enums.CheckRegistrationMode | undefined; checkMode: import("@prisma/client").$Enums.CheckRegistrationMode | undefined;
status: import("@prisma/client").$Enums.OrderStatus; status: import("@prisma/client").$Enums.OrderStatus;
adminStatus: string; adminStatus: string;
allowedTransitions: import("@prisma/client").$Enums.OrderStatus[];
totalMeterage: number; totalMeterage: number;
meterage: number; meterage: number;
totalAmount: number | null; totalAmount: number | null;
shippingCost: number | null; shippingCost: number | null;
totalDiscount: number | null; totalDiscount: number | null;
finalAmount: number | null; finalAmount: number | null;
pricesVisible: boolean;
notes: string | undefined; notes: string | undefined;
checkRassDays: number | undefined; checkRassDays: number | undefined;
checkRassDate: string | undefined; checkRassDate: string | undefined;
@@ -143,8 +147,8 @@ export declare class CustomerOrdersController {
confirmedMeterage: number | null; confirmedMeterage: number | null;
unitPrice: number | null; unitPrice: number | null;
supplyPrice: number | null; supplyPrice: number | null;
unitDiscount: number; unitDiscount: number | null;
discount: number; discount: number | null;
lineTotal: number | null; lineTotal: number | null;
totalAmount: number | null; totalAmount: number | null;
notes: string | undefined; notes: string | undefined;
@@ -212,17 +216,19 @@ export declare class CustomerOrdersController {
userId: string; userId: string;
companyId: string; companyId: string;
companyName: string; companyName: string;
settlementType: import("@prisma/client").$Enums.SettlementType; settlementType: import("@prisma/client").$Enums.SettlementType | null;
settlementMethod: import("@prisma/client").$Enums.SettlementType; settlementMethod: import("@prisma/client").$Enums.SettlementType | null;
checkMode: import("@prisma/client").$Enums.CheckRegistrationMode | undefined; checkMode: import("@prisma/client").$Enums.CheckRegistrationMode | undefined;
status: import("@prisma/client").$Enums.OrderStatus; status: import("@prisma/client").$Enums.OrderStatus;
adminStatus: string; adminStatus: string;
allowedTransitions: import("@prisma/client").$Enums.OrderStatus[];
totalMeterage: number; totalMeterage: number;
meterage: number; meterage: number;
totalAmount: number | null; totalAmount: number | null;
shippingCost: number | null; shippingCost: number | null;
totalDiscount: number | null; totalDiscount: number | null;
finalAmount: number | null; finalAmount: number | null;
pricesVisible: boolean;
notes: string | undefined; notes: string | undefined;
checkRassDays: number | undefined; checkRassDays: number | undefined;
checkRassDate: string | undefined; checkRassDate: string | undefined;
@@ -244,8 +250,111 @@ export declare class CustomerOrdersController {
confirmedMeterage: number | null; confirmedMeterage: number | null;
unitPrice: number | null; unitPrice: number | null;
supplyPrice: number | null; supplyPrice: number | null;
unitDiscount: number; unitDiscount: number | null;
discount: number; discount: number | null;
lineTotal: number | null;
totalAmount: number | null;
notes: string | undefined;
}[];
checks: {
id: string;
orderId: string;
row: number;
amount: number;
dueDate: string;
sayadId: string | undefined;
checkNumber: string | undefined;
ownerNationalId: string | undefined;
ownerFirstName: string | undefined;
ownerLastName: string | undefined;
bankName: string;
branchName: string | undefined;
frontImage: string | undefined;
backImage: string | undefined;
notes: string | undefined;
mode: import("@prisma/client").$Enums.CheckRegistrationMode;
status: import("@prisma/client").$Enums.CheckStatus;
createdAt: string;
updatedAt: string;
}[];
cashReceipt: {
id: string;
amount: number;
paidAmount: number | null;
transferDate: string | null;
transferTime: string | undefined;
trackingNumber: string | undefined;
trackingCode: string | undefined;
destinationAccountId: string | undefined;
receiptImage: string | undefined;
notes: string | undefined;
expiresAt: string | undefined;
status: import("@prisma/client").$Enums.CashPaymentStatus;
submittedAt: string | undefined;
createdAt: string;
} | undefined;
shipment: {
id: string;
orderId: string;
carrier: string;
trackingNumber: string;
meterage: number;
status: import("@prisma/client").$Enums.ShipmentStatus;
shippedAt: string | null;
deliveredAt: string | null;
} | undefined;
statusHistory: {
id: string;
fromStatus: import("@prisma/client").$Enums.OrderStatus | null;
toStatus: import("@prisma/client").$Enums.OrderStatus;
changedBy: string;
note: string | undefined;
reason: string | undefined;
changedAt: string;
}[];
}>;
chooseSettlement(user: AuthPrincipal, id: string, dto: ChooseSettlementDto): Promise<{
id: string;
orderNumber: string;
userId: string;
companyId: string;
companyName: string;
settlementType: import("@prisma/client").$Enums.SettlementType | null;
settlementMethod: import("@prisma/client").$Enums.SettlementType | null;
checkMode: import("@prisma/client").$Enums.CheckRegistrationMode | undefined;
status: import("@prisma/client").$Enums.OrderStatus;
adminStatus: string;
allowedTransitions: import("@prisma/client").$Enums.OrderStatus[];
totalMeterage: number;
meterage: number;
totalAmount: number | null;
shippingCost: number | null;
totalDiscount: number | null;
finalAmount: number | null;
pricesVisible: boolean;
notes: string | undefined;
checkRassDays: number | undefined;
checkRassDate: string | undefined;
cashInvoiceExpiresAt: string | undefined;
createdAt: string;
updatedAt: string;
customerName: string;
customerPhone: string;
customerNationalId: string | undefined;
customerType: import("@prisma/client").$Enums.CustomerType;
items: {
id: string;
productId: string | undefined;
catalogCode: string;
productName: string;
productCode: string | undefined;
color: string;
requestedMeterage: number;
confirmedMeterage: number | null;
unitPrice: number | null;
supplyPrice: number | null;
unitDiscount: number | null;
discount: number | null;
lineTotal: number | null; lineTotal: number | null;
totalAmount: number | null; totalAmount: number | null;
notes: string | undefined; notes: string | undefined;
@@ -313,17 +422,19 @@ export declare class CustomerOrdersController {
userId: string; userId: string;
companyId: string; companyId: string;
companyName: string; companyName: string;
settlementType: import("@prisma/client").$Enums.SettlementType; settlementType: import("@prisma/client").$Enums.SettlementType | null;
settlementMethod: import("@prisma/client").$Enums.SettlementType; settlementMethod: import("@prisma/client").$Enums.SettlementType | null;
checkMode: import("@prisma/client").$Enums.CheckRegistrationMode | undefined; checkMode: import("@prisma/client").$Enums.CheckRegistrationMode | undefined;
status: import("@prisma/client").$Enums.OrderStatus; status: import("@prisma/client").$Enums.OrderStatus;
adminStatus: string; adminStatus: string;
allowedTransitions: import("@prisma/client").$Enums.OrderStatus[];
totalMeterage: number; totalMeterage: number;
meterage: number; meterage: number;
totalAmount: number | null; totalAmount: number | null;
shippingCost: number | null; shippingCost: number | null;
totalDiscount: number | null; totalDiscount: number | null;
finalAmount: number | null; finalAmount: number | null;
pricesVisible: boolean;
notes: string | undefined; notes: string | undefined;
checkRassDays: number | undefined; checkRassDays: number | undefined;
checkRassDate: string | undefined; checkRassDate: string | undefined;
@@ -345,8 +456,8 @@ export declare class CustomerOrdersController {
confirmedMeterage: number | null; confirmedMeterage: number | null;
unitPrice: number | null; unitPrice: number | null;
supplyPrice: number | null; supplyPrice: number | null;
unitDiscount: number; unitDiscount: number | null;
discount: number; discount: number | null;
lineTotal: number | null; lineTotal: number | null;
totalAmount: number | null; totalAmount: number | null;
notes: string | undefined; notes: string | undefined;
@@ -418,17 +529,19 @@ export declare class AdminOrdersController {
userId: string; userId: string;
companyId: string; companyId: string;
companyName: string; companyName: string;
settlementType: import("@prisma/client").$Enums.SettlementType; settlementType: import("@prisma/client").$Enums.SettlementType | null;
settlementMethod: import("@prisma/client").$Enums.SettlementType; settlementMethod: import("@prisma/client").$Enums.SettlementType | null;
checkMode: import("@prisma/client").$Enums.CheckRegistrationMode | undefined; checkMode: import("@prisma/client").$Enums.CheckRegistrationMode | undefined;
status: import("@prisma/client").$Enums.OrderStatus; status: import("@prisma/client").$Enums.OrderStatus;
adminStatus: string; adminStatus: string;
allowedTransitions: import("@prisma/client").$Enums.OrderStatus[];
totalMeterage: number; totalMeterage: number;
meterage: number; meterage: number;
totalAmount: number | null; totalAmount: number | null;
shippingCost: number | null; shippingCost: number | null;
totalDiscount: number | null; totalDiscount: number | null;
finalAmount: number | null; finalAmount: number | null;
pricesVisible: boolean;
notes: string | undefined; notes: string | undefined;
checkRassDays: number | undefined; checkRassDays: number | undefined;
checkRassDate: string | undefined; checkRassDate: string | undefined;
@@ -450,8 +563,8 @@ export declare class AdminOrdersController {
confirmedMeterage: number | null; confirmedMeterage: number | null;
unitPrice: number | null; unitPrice: number | null;
supplyPrice: number | null; supplyPrice: number | null;
unitDiscount: number; unitDiscount: number | null;
discount: number; discount: number | null;
lineTotal: number | null; lineTotal: number | null;
totalAmount: number | null; totalAmount: number | null;
notes: string | undefined; notes: string | undefined;
@@ -519,17 +632,19 @@ export declare class AdminOrdersController {
userId: string; userId: string;
companyId: string; companyId: string;
companyName: string; companyName: string;
settlementType: import("@prisma/client").$Enums.SettlementType; settlementType: import("@prisma/client").$Enums.SettlementType | null;
settlementMethod: import("@prisma/client").$Enums.SettlementType; settlementMethod: import("@prisma/client").$Enums.SettlementType | null;
checkMode: import("@prisma/client").$Enums.CheckRegistrationMode | undefined; checkMode: import("@prisma/client").$Enums.CheckRegistrationMode | undefined;
status: import("@prisma/client").$Enums.OrderStatus; status: import("@prisma/client").$Enums.OrderStatus;
adminStatus: string; adminStatus: string;
allowedTransitions: import("@prisma/client").$Enums.OrderStatus[];
totalMeterage: number; totalMeterage: number;
meterage: number; meterage: number;
totalAmount: number | null; totalAmount: number | null;
shippingCost: number | null; shippingCost: number | null;
totalDiscount: number | null; totalDiscount: number | null;
finalAmount: number | null; finalAmount: number | null;
pricesVisible: boolean;
notes: string | undefined; notes: string | undefined;
checkRassDays: number | undefined; checkRassDays: number | undefined;
checkRassDate: string | undefined; checkRassDate: string | undefined;
@@ -551,8 +666,8 @@ export declare class AdminOrdersController {
confirmedMeterage: number | null; confirmedMeterage: number | null;
unitPrice: number | null; unitPrice: number | null;
supplyPrice: number | null; supplyPrice: number | null;
unitDiscount: number; unitDiscount: number | null;
discount: number; discount: number | null;
lineTotal: number | null; lineTotal: number | null;
totalAmount: number | null; totalAmount: number | null;
notes: string | undefined; notes: string | undefined;
@@ -620,17 +735,19 @@ export declare class AdminOrdersController {
userId: string; userId: string;
companyId: string; companyId: string;
companyName: string; companyName: string;
settlementType: import("@prisma/client").$Enums.SettlementType; settlementType: import("@prisma/client").$Enums.SettlementType | null;
settlementMethod: import("@prisma/client").$Enums.SettlementType; settlementMethod: import("@prisma/client").$Enums.SettlementType | null;
checkMode: import("@prisma/client").$Enums.CheckRegistrationMode | undefined; checkMode: import("@prisma/client").$Enums.CheckRegistrationMode | undefined;
status: import("@prisma/client").$Enums.OrderStatus; status: import("@prisma/client").$Enums.OrderStatus;
adminStatus: string; adminStatus: string;
allowedTransitions: import("@prisma/client").$Enums.OrderStatus[];
totalMeterage: number; totalMeterage: number;
meterage: number; meterage: number;
totalAmount: number | null; totalAmount: number | null;
shippingCost: number | null; shippingCost: number | null;
totalDiscount: number | null; totalDiscount: number | null;
finalAmount: number | null; finalAmount: number | null;
pricesVisible: boolean;
notes: string | undefined; notes: string | undefined;
checkRassDays: number | undefined; checkRassDays: number | undefined;
checkRassDate: string | undefined; checkRassDate: string | undefined;
@@ -652,8 +769,8 @@ export declare class AdminOrdersController {
confirmedMeterage: number | null; confirmedMeterage: number | null;
unitPrice: number | null; unitPrice: number | null;
supplyPrice: number | null; supplyPrice: number | null;
unitDiscount: number; unitDiscount: number | null;
discount: number; discount: number | null;
lineTotal: number | null; lineTotal: number | null;
totalAmount: number | null; totalAmount: number | null;
notes: string | undefined; notes: string | undefined;
@@ -721,17 +838,19 @@ export declare class AdminOrdersController {
userId: string; userId: string;
companyId: string; companyId: string;
companyName: string; companyName: string;
settlementType: import("@prisma/client").$Enums.SettlementType; settlementType: import("@prisma/client").$Enums.SettlementType | null;
settlementMethod: import("@prisma/client").$Enums.SettlementType; settlementMethod: import("@prisma/client").$Enums.SettlementType | null;
checkMode: import("@prisma/client").$Enums.CheckRegistrationMode | undefined; checkMode: import("@prisma/client").$Enums.CheckRegistrationMode | undefined;
status: import("@prisma/client").$Enums.OrderStatus; status: import("@prisma/client").$Enums.OrderStatus;
adminStatus: string; adminStatus: string;
allowedTransitions: import("@prisma/client").$Enums.OrderStatus[];
totalMeterage: number; totalMeterage: number;
meterage: number; meterage: number;
totalAmount: number | null; totalAmount: number | null;
shippingCost: number | null; shippingCost: number | null;
totalDiscount: number | null; totalDiscount: number | null;
finalAmount: number | null; finalAmount: number | null;
pricesVisible: boolean;
notes: string | undefined; notes: string | undefined;
checkRassDays: number | undefined; checkRassDays: number | undefined;
checkRassDate: string | undefined; checkRassDate: string | undefined;
@@ -753,8 +872,8 @@ export declare class AdminOrdersController {
confirmedMeterage: number | null; confirmedMeterage: number | null;
unitPrice: number | null; unitPrice: number | null;
supplyPrice: number | null; supplyPrice: number | null;
unitDiscount: number; unitDiscount: number | null;
discount: number; discount: number | null;
lineTotal: number | null; lineTotal: number | null;
totalAmount: number | null; totalAmount: number | null;
notes: string | undefined; notes: string | undefined;
@@ -822,17 +941,19 @@ export declare class AdminOrdersController {
userId: string; userId: string;
companyId: string; companyId: string;
companyName: string; companyName: string;
settlementType: import("@prisma/client").$Enums.SettlementType; settlementType: import("@prisma/client").$Enums.SettlementType | null;
settlementMethod: import("@prisma/client").$Enums.SettlementType; settlementMethod: import("@prisma/client").$Enums.SettlementType | null;
checkMode: import("@prisma/client").$Enums.CheckRegistrationMode | undefined; checkMode: import("@prisma/client").$Enums.CheckRegistrationMode | undefined;
status: import("@prisma/client").$Enums.OrderStatus; status: import("@prisma/client").$Enums.OrderStatus;
adminStatus: string; adminStatus: string;
allowedTransitions: import("@prisma/client").$Enums.OrderStatus[];
totalMeterage: number; totalMeterage: number;
meterage: number; meterage: number;
totalAmount: number | null; totalAmount: number | null;
shippingCost: number | null; shippingCost: number | null;
totalDiscount: number | null; totalDiscount: number | null;
finalAmount: number | null; finalAmount: number | null;
pricesVisible: boolean;
notes: string | undefined; notes: string | undefined;
checkRassDays: number | undefined; checkRassDays: number | undefined;
checkRassDate: string | undefined; checkRassDate: string | undefined;
@@ -854,8 +975,8 @@ export declare class AdminOrdersController {
confirmedMeterage: number | null; confirmedMeterage: number | null;
unitPrice: number | null; unitPrice: number | null;
supplyPrice: number | null; supplyPrice: number | null;
unitDiscount: number; unitDiscount: number | null;
discount: number; discount: number | null;
lineTotal: number | null; lineTotal: number | null;
totalAmount: number | null; totalAmount: number | null;
notes: string | undefined; notes: string | undefined;
+13
View File
@@ -32,6 +32,9 @@ let CustomerOrdersController = class CustomerOrdersController {
submit(user, dto) { submit(user, dto) {
return this.ordersService.submitOrder(user.id, dto); return this.ordersService.submitOrder(user.id, dto);
} }
chooseSettlement(user, id, dto) {
return this.ordersService.chooseSettlement(user.id, id, dto);
}
updateChecks(user, id, dto) { updateChecks(user, id, dto) {
return this.ordersService.updateChecks(user.id, id, dto); return this.ordersService.updateChecks(user.id, id, dto);
} }
@@ -63,6 +66,16 @@ __decorate([
__metadata("design:paramtypes", [Object, order_dto_1.SubmitOrderDto]), __metadata("design:paramtypes", [Object, order_dto_1.SubmitOrderDto]),
__metadata("design:returntype", void 0) __metadata("design:returntype", void 0)
], CustomerOrdersController.prototype, "submit", null); ], CustomerOrdersController.prototype, "submit", null);
__decorate([
(0, auth_decorators_1.AuthType)('customer'),
(0, common_1.Post)(':id/settlement'),
__param(0, (0, current_user_decorator_1.CurrentUser)()),
__param(1, (0, common_1.Param)('id')),
__param(2, (0, common_1.Body)()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object, String, order_dto_1.ChooseSettlementDto]),
__metadata("design:returntype", void 0)
], CustomerOrdersController.prototype, "chooseSettlement", null);
__decorate([ __decorate([
(0, auth_decorators_1.AuthType)('customer'), (0, auth_decorators_1.AuthType)('customer'),
(0, common_1.Patch)(':id/checks'), (0, common_1.Patch)(':id/checks'),
+1 -1
View File
@@ -1 +1 @@
{"version":3,"file":"orders.controller.js","sourceRoot":"","sources":["../../src/orders/orders.controller.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,2CAQwB;AACxB,qDAAiD;AACjD,0EAAgE;AAChE,wFAA0E;AAE1E,+CAMyB;AAGlB,IAAM,wBAAwB,GAA9B,MAAM,wBAAwB;IACN;IAA7B,YAA6B,aAA4B;QAA5B,kBAAa,GAAb,aAAa,CAAe;IAAG,CAAC;IAI7D,QAAQ,CAAgB,IAAmB;QACzC,OAAO,IAAI,CAAC,aAAa,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAClD,CAAC;IAID,OAAO,CAAgB,IAAmB,EAAe,EAAU;QACjE,OAAO,IAAI,CAAC,aAAa,CAAC,WAAW,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;IACrD,CAAC;IAID,MAAM,CAAgB,IAAmB,EAAU,GAAmB;QACpE,OAAO,IAAI,CAAC,aAAa,CAAC,WAAW,CAAC,IAAI,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC;IACtD,CAAC;IAID,YAAY,CACK,IAAmB,EACrB,EAAU,EACf,GAAyB;QAEjC,OAAO,IAAI,CAAC,aAAa,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,EAAE,GAAG,CAAC,CAAC;IAC3D,CAAC;CACF,CAAA;AA9BY,4DAAwB;AAKnC;IAFC,IAAA,0BAAQ,EAAC,UAAU,CAAC;IACpB,IAAA,YAAG,GAAE;IACI,WAAA,IAAA,oCAAW,GAAE,CAAA;;;;wDAEtB;AAID;IAFC,IAAA,0BAAQ,EAAC,UAAU,CAAC;IACpB,IAAA,YAAG,EAAC,KAAK,CAAC;IACF,WAAA,IAAA,oCAAW,GAAE,CAAA;IAAuB,WAAA,IAAA,cAAK,EAAC,IAAI,CAAC,CAAA;;;;uDAEvD;AAID;IAFC,IAAA,0BAAQ,EAAC,UAAU,CAAC;IACpB,IAAA,aAAI,GAAE;IACC,WAAA,IAAA,oCAAW,GAAE,CAAA;IAAuB,WAAA,IAAA,aAAI,GAAE,CAAA;;6CAAM,0BAAc;;sDAErE;AAID;IAFC,IAAA,0BAAQ,EAAC,UAAU,CAAC;IACpB,IAAA,cAAK,EAAC,YAAY,CAAC;IAEjB,WAAA,IAAA,oCAAW,GAAE,CAAA;IACb,WAAA,IAAA,cAAK,EAAC,IAAI,CAAC,CAAA;IACX,WAAA,IAAA,aAAI,GAAE,CAAA;;qDAAM,gCAAoB;;4DAGlC;mCA7BU,wBAAwB;IADpC,IAAA,mBAAU,EAAC,QAAQ,CAAC;qCAEyB,8BAAa;GAD9C,wBAAwB,CA8BpC;AAIM,IAAM,qBAAqB,GAA3B,MAAM,qBAAqB;IACH;IAA7B,YAA6B,aAA4B;QAA5B,kBAAa,GAAb,aAAa,CAAe;IAAG,CAAC;IAG7D,OAAO,CAAkB,MAAe,EAAc,CAAU;QAC9D,OAAO,IAAI,CAAC,aAAa,CAAC,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC;IAC3D,CAAC;IAGD,OAAO,CAAc,EAAU;QAC7B,OAAO,IAAI,CAAC,aAAa,CAAC,cAAc,CAAC,EAAE,CAAC,CAAC;IAC/C,CAAC;IAGD,MAAM,CAAgB,IAAmB,EAAU,GAAwB;QACzE,OAAO,IAAI,CAAC,aAAa,CAAC,WAAW,CAAC,IAAI,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC;IACtD,CAAC;IAGD,aAAa,CACI,IAAmB,EACrB,EAAU,EACf,GAA0B;QAElC,OAAO,IAAI,CAAC,aAAa,CAAC,aAAa,CAAC,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC;IAC5D,CAAC;IAGD,YAAY,CACK,IAAmB,EACrB,EAAU,EACf,GAAyB;QAEjC,OAAO,IAAI,CAAC,aAAa,CAAC,YAAY,CAAC,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC;IAC3D,CAAC;CACF,CAAA;AAnCY,sDAAqB;AAIhC;IADC,IAAA,YAAG,GAAE;IACG,WAAA,IAAA,cAAK,EAAC,QAAQ,CAAC,CAAA;IAAmB,WAAA,IAAA,cAAK,EAAC,GAAG,CAAC,CAAA;;;;oDAEpD;AAGD;IADC,IAAA,YAAG,EAAC,KAAK,CAAC;IACF,WAAA,IAAA,cAAK,EAAC,IAAI,CAAC,CAAA;;;;oDAEnB;AAGD;IADC,IAAA,aAAI,GAAE;IACC,WAAA,IAAA,oCAAW,GAAE,CAAA;IAAuB,WAAA,IAAA,aAAI,GAAE,CAAA;;6CAAM,+BAAmB;;mDAE1E;AAGD;IADC,IAAA,cAAK,EAAC,aAAa,CAAC;IAElB,WAAA,IAAA,oCAAW,GAAE,CAAA;IACb,WAAA,IAAA,cAAK,EAAC,IAAI,CAAC,CAAA;IACX,WAAA,IAAA,aAAI,GAAE,CAAA;;qDAAM,iCAAqB;;0DAGnC;AAGD;IADC,IAAA,cAAK,EAAC,YAAY,CAAC;IAEjB,WAAA,IAAA,oCAAW,GAAE,CAAA;IACb,WAAA,IAAA,cAAK,EAAC,IAAI,CAAC,CAAA;IACX,WAAA,IAAA,aAAI,GAAE,CAAA;;qDAAM,gCAAoB;;yDAGlC;gCAlCU,qBAAqB;IAFjC,IAAA,mBAAU,EAAC,cAAc,CAAC;IAC1B,IAAA,0BAAQ,EAAC,OAAO,CAAC;qCAE4B,8BAAa;GAD9C,qBAAqB,CAmCjC"} {"version":3,"file":"orders.controller.js","sourceRoot":"","sources":["../../src/orders/orders.controller.ts"],"names":[],"mappings":"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"}
+4 -1
View File
@@ -10,13 +10,16 @@ exports.OrdersModule = void 0;
const common_1 = require("@nestjs/common"); const common_1 = require("@nestjs/common");
const orders_controller_1 = require("./orders.controller"); const orders_controller_1 = require("./orders.controller");
const orders_service_1 = require("./orders.service"); const orders_service_1 = require("./orders.service");
const cash_invoice_scheduler_service_1 = require("./cash-invoice-scheduler.service");
const notifications_module_1 = require("../notifications/notifications.module");
let OrdersModule = class OrdersModule { let OrdersModule = class OrdersModule {
}; };
exports.OrdersModule = OrdersModule; exports.OrdersModule = OrdersModule;
exports.OrdersModule = OrdersModule = __decorate([ exports.OrdersModule = OrdersModule = __decorate([
(0, common_1.Module)({ (0, common_1.Module)({
imports: [notifications_module_1.NotificationsModule],
controllers: [orders_controller_1.CustomerOrdersController, orders_controller_1.AdminOrdersController], controllers: [orders_controller_1.CustomerOrdersController, orders_controller_1.AdminOrdersController],
providers: [orders_service_1.OrdersService], providers: [orders_service_1.OrdersService, cash_invoice_scheduler_service_1.CashInvoiceSchedulerService],
exports: [orders_service_1.OrdersService], exports: [orders_service_1.OrdersService],
}) })
], OrdersModule); ], OrdersModule);
+1 -1
View File
@@ -1 +1 @@
{"version":3,"file":"orders.module.js","sourceRoot":"","sources":["../../src/orders/orders.module.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAAwC;AACxC,2DAG6B;AAC7B,qDAAiD;AAO1C,IAAM,YAAY,GAAlB,MAAM,YAAY;CAAG,CAAA;AAAf,oCAAY;uBAAZ,YAAY;IALxB,IAAA,eAAM,EAAC;QACN,WAAW,EAAE,CAAC,4CAAwB,EAAE,yCAAqB,CAAC;QAC9D,SAAS,EAAE,CAAC,8BAAa,CAAC;QAC1B,OAAO,EAAE,CAAC,8BAAa,CAAC;KACzB,CAAC;GACW,YAAY,CAAG"} {"version":3,"file":"orders.module.js","sourceRoot":"","sources":["../../src/orders/orders.module.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAAwC;AACxC,2DAG6B;AAC7B,qDAAiD;AACjD,qFAA+E;AAC/E,gFAA4E;AAQrE,IAAM,YAAY,GAAlB,MAAM,YAAY;CAAG,CAAA;AAAf,oCAAY;uBAAZ,YAAY;IANxB,IAAA,eAAM,EAAC;QACN,OAAO,EAAE,CAAC,0CAAmB,CAAC;QAC9B,WAAW,EAAE,CAAC,4CAAwB,EAAE,yCAAqB,CAAC;QAC9D,SAAS,EAAE,CAAC,8BAAa,EAAE,4DAA2B,CAAC;QACvD,OAAO,EAAE,CAAC,8BAAa,CAAC;KACzB,CAAC;GACW,YAAY,CAAG"}
+167 -42
View File
@@ -1,6 +1,7 @@
import { Prisma } from '@prisma/client'; import { Prisma } from '@prisma/client';
import { PrismaService } from '../prisma/prisma.service'; import { PrismaService } from '../prisma/prisma.service';
import { AdminCreateOrderDto, SubmitOrderDto, UpdateOrderChecksDto, UpdateOrderPricingDto, UpdateOrderStatusDto } from './dto/order.dto'; import { AdminCreateOrderDto, ChooseSettlementDto, SubmitOrderDto, UpdateOrderChecksDto, UpdateOrderPricingDto, UpdateOrderStatusDto } from './dto/order.dto';
import { NotificationsService } from '../notifications/notifications.service';
declare const orderInclude: { declare const orderInclude: {
items: true; items: true;
checks: { checks: {
@@ -23,24 +24,27 @@ type OrderFull = Prisma.OrderGetPayload<{
}>; }>;
export declare class OrdersService { export declare class OrdersService {
private readonly prisma; private readonly prisma;
constructor(prisma: PrismaService); private readonly notifications;
constructor(prisma: PrismaService, notifications: NotificationsService);
findMyOrders(userId: string): Promise<{ findMyOrders(userId: string): Promise<{
id: string; id: string;
orderNumber: string; orderNumber: string;
userId: string; userId: string;
companyId: string; companyId: string;
companyName: string; companyName: string;
settlementType: import("@prisma/client").$Enums.SettlementType; settlementType: import("@prisma/client").$Enums.SettlementType | null;
settlementMethod: import("@prisma/client").$Enums.SettlementType; settlementMethod: import("@prisma/client").$Enums.SettlementType | null;
checkMode: import("@prisma/client").$Enums.CheckRegistrationMode | undefined; checkMode: import("@prisma/client").$Enums.CheckRegistrationMode | undefined;
status: import("@prisma/client").$Enums.OrderStatus; status: import("@prisma/client").$Enums.OrderStatus;
adminStatus: string; adminStatus: string;
allowedTransitions: import("@prisma/client").$Enums.OrderStatus[];
totalMeterage: number; totalMeterage: number;
meterage: number; meterage: number;
totalAmount: number | null; totalAmount: number | null;
shippingCost: number | null; shippingCost: number | null;
totalDiscount: number | null; totalDiscount: number | null;
finalAmount: number | null; finalAmount: number | null;
pricesVisible: boolean;
notes: string | undefined; notes: string | undefined;
checkRassDays: number | undefined; checkRassDays: number | undefined;
checkRassDate: string | undefined; checkRassDate: string | undefined;
@@ -62,8 +66,8 @@ export declare class OrdersService {
confirmedMeterage: number | null; confirmedMeterage: number | null;
unitPrice: number | null; unitPrice: number | null;
supplyPrice: number | null; supplyPrice: number | null;
unitDiscount: number; unitDiscount: number | null;
discount: number; discount: number | null;
lineTotal: number | null; lineTotal: number | null;
totalAmount: number | null; totalAmount: number | null;
notes: string | undefined; notes: string | undefined;
@@ -131,17 +135,19 @@ export declare class OrdersService {
userId: string; userId: string;
companyId: string; companyId: string;
companyName: string; companyName: string;
settlementType: import("@prisma/client").$Enums.SettlementType; settlementType: import("@prisma/client").$Enums.SettlementType | null;
settlementMethod: import("@prisma/client").$Enums.SettlementType; settlementMethod: import("@prisma/client").$Enums.SettlementType | null;
checkMode: import("@prisma/client").$Enums.CheckRegistrationMode | undefined; checkMode: import("@prisma/client").$Enums.CheckRegistrationMode | undefined;
status: import("@prisma/client").$Enums.OrderStatus; status: import("@prisma/client").$Enums.OrderStatus;
adminStatus: string; adminStatus: string;
allowedTransitions: import("@prisma/client").$Enums.OrderStatus[];
totalMeterage: number; totalMeterage: number;
meterage: number; meterage: number;
totalAmount: number | null; totalAmount: number | null;
shippingCost: number | null; shippingCost: number | null;
totalDiscount: number | null; totalDiscount: number | null;
finalAmount: number | null; finalAmount: number | null;
pricesVisible: boolean;
notes: string | undefined; notes: string | undefined;
checkRassDays: number | undefined; checkRassDays: number | undefined;
checkRassDate: string | undefined; checkRassDate: string | undefined;
@@ -163,8 +169,8 @@ export declare class OrdersService {
confirmedMeterage: number | null; confirmedMeterage: number | null;
unitPrice: number | null; unitPrice: number | null;
supplyPrice: number | null; supplyPrice: number | null;
unitDiscount: number; unitDiscount: number | null;
discount: number; discount: number | null;
lineTotal: number | null; lineTotal: number | null;
totalAmount: number | null; totalAmount: number | null;
notes: string | undefined; notes: string | undefined;
@@ -235,17 +241,19 @@ export declare class OrdersService {
userId: string; userId: string;
companyId: string; companyId: string;
companyName: string; companyName: string;
settlementType: import("@prisma/client").$Enums.SettlementType; settlementType: import("@prisma/client").$Enums.SettlementType | null;
settlementMethod: import("@prisma/client").$Enums.SettlementType; settlementMethod: import("@prisma/client").$Enums.SettlementType | null;
checkMode: import("@prisma/client").$Enums.CheckRegistrationMode | undefined; checkMode: import("@prisma/client").$Enums.CheckRegistrationMode | undefined;
status: import("@prisma/client").$Enums.OrderStatus; status: import("@prisma/client").$Enums.OrderStatus;
adminStatus: string; adminStatus: string;
allowedTransitions: import("@prisma/client").$Enums.OrderStatus[];
totalMeterage: number; totalMeterage: number;
meterage: number; meterage: number;
totalAmount: number | null; totalAmount: number | null;
shippingCost: number | null; shippingCost: number | null;
totalDiscount: number | null; totalDiscount: number | null;
finalAmount: number | null; finalAmount: number | null;
pricesVisible: boolean;
notes: string | undefined; notes: string | undefined;
checkRassDays: number | undefined; checkRassDays: number | undefined;
checkRassDate: string | undefined; checkRassDate: string | undefined;
@@ -267,8 +275,8 @@ export declare class OrdersService {
confirmedMeterage: number | null; confirmedMeterage: number | null;
unitPrice: number | null; unitPrice: number | null;
supplyPrice: number | null; supplyPrice: number | null;
unitDiscount: number; unitDiscount: number | null;
discount: number; discount: number | null;
lineTotal: number | null; lineTotal: number | null;
totalAmount: number | null; totalAmount: number | null;
notes: string | undefined; notes: string | undefined;
@@ -336,17 +344,19 @@ export declare class OrdersService {
userId: string; userId: string;
companyId: string; companyId: string;
companyName: string; companyName: string;
settlementType: import("@prisma/client").$Enums.SettlementType; settlementType: import("@prisma/client").$Enums.SettlementType | null;
settlementMethod: import("@prisma/client").$Enums.SettlementType; settlementMethod: import("@prisma/client").$Enums.SettlementType | null;
checkMode: import("@prisma/client").$Enums.CheckRegistrationMode | undefined; checkMode: import("@prisma/client").$Enums.CheckRegistrationMode | undefined;
status: import("@prisma/client").$Enums.OrderStatus; status: import("@prisma/client").$Enums.OrderStatus;
adminStatus: string; adminStatus: string;
allowedTransitions: import("@prisma/client").$Enums.OrderStatus[];
totalMeterage: number; totalMeterage: number;
meterage: number; meterage: number;
totalAmount: number | null; totalAmount: number | null;
shippingCost: number | null; shippingCost: number | null;
totalDiscount: number | null; totalDiscount: number | null;
finalAmount: number | null; finalAmount: number | null;
pricesVisible: boolean;
notes: string | undefined; notes: string | undefined;
checkRassDays: number | undefined; checkRassDays: number | undefined;
checkRassDate: string | undefined; checkRassDate: string | undefined;
@@ -368,8 +378,8 @@ export declare class OrdersService {
confirmedMeterage: number | null; confirmedMeterage: number | null;
unitPrice: number | null; unitPrice: number | null;
supplyPrice: number | null; supplyPrice: number | null;
unitDiscount: number; unitDiscount: number | null;
discount: number; discount: number | null;
lineTotal: number | null; lineTotal: number | null;
totalAmount: number | null; totalAmount: number | null;
notes: string | undefined; notes: string | undefined;
@@ -437,17 +447,19 @@ export declare class OrdersService {
userId: string; userId: string;
companyId: string; companyId: string;
companyName: string; companyName: string;
settlementType: import("@prisma/client").$Enums.SettlementType; settlementType: import("@prisma/client").$Enums.SettlementType | null;
settlementMethod: import("@prisma/client").$Enums.SettlementType; settlementMethod: import("@prisma/client").$Enums.SettlementType | null;
checkMode: import("@prisma/client").$Enums.CheckRegistrationMode | undefined; checkMode: import("@prisma/client").$Enums.CheckRegistrationMode | undefined;
status: import("@prisma/client").$Enums.OrderStatus; status: import("@prisma/client").$Enums.OrderStatus;
adminStatus: string; adminStatus: string;
allowedTransitions: import("@prisma/client").$Enums.OrderStatus[];
totalMeterage: number; totalMeterage: number;
meterage: number; meterage: number;
totalAmount: number | null; totalAmount: number | null;
shippingCost: number | null; shippingCost: number | null;
totalDiscount: number | null; totalDiscount: number | null;
finalAmount: number | null; finalAmount: number | null;
pricesVisible: boolean;
notes: string | undefined; notes: string | undefined;
checkRassDays: number | undefined; checkRassDays: number | undefined;
checkRassDate: string | undefined; checkRassDate: string | undefined;
@@ -469,8 +481,111 @@ export declare class OrdersService {
confirmedMeterage: number | null; confirmedMeterage: number | null;
unitPrice: number | null; unitPrice: number | null;
supplyPrice: number | null; supplyPrice: number | null;
unitDiscount: number; unitDiscount: number | null;
discount: number; discount: number | null;
lineTotal: number | null;
totalAmount: number | null;
notes: string | undefined;
}[];
checks: {
id: string;
orderId: string;
row: number;
amount: number;
dueDate: string;
sayadId: string | undefined;
checkNumber: string | undefined;
ownerNationalId: string | undefined;
ownerFirstName: string | undefined;
ownerLastName: string | undefined;
bankName: string;
branchName: string | undefined;
frontImage: string | undefined;
backImage: string | undefined;
notes: string | undefined;
mode: import("@prisma/client").$Enums.CheckRegistrationMode;
status: import("@prisma/client").$Enums.CheckStatus;
createdAt: string;
updatedAt: string;
}[];
cashReceipt: {
id: string;
amount: number;
paidAmount: number | null;
transferDate: string | null;
transferTime: string | undefined;
trackingNumber: string | undefined;
trackingCode: string | undefined;
destinationAccountId: string | undefined;
receiptImage: string | undefined;
notes: string | undefined;
expiresAt: string | undefined;
status: import("@prisma/client").$Enums.CashPaymentStatus;
submittedAt: string | undefined;
createdAt: string;
} | undefined;
shipment: {
id: string;
orderId: string;
carrier: string;
trackingNumber: string;
meterage: number;
status: import("@prisma/client").$Enums.ShipmentStatus;
shippedAt: string | null;
deliveredAt: string | null;
} | undefined;
statusHistory: {
id: string;
fromStatus: import("@prisma/client").$Enums.OrderStatus | null;
toStatus: import("@prisma/client").$Enums.OrderStatus;
changedBy: string;
note: string | undefined;
reason: string | undefined;
changedAt: string;
}[];
}>;
chooseSettlement(userId: string, id: string, dto: ChooseSettlementDto): Promise<{
id: string;
orderNumber: string;
userId: string;
companyId: string;
companyName: string;
settlementType: import("@prisma/client").$Enums.SettlementType | null;
settlementMethod: import("@prisma/client").$Enums.SettlementType | null;
checkMode: import("@prisma/client").$Enums.CheckRegistrationMode | undefined;
status: import("@prisma/client").$Enums.OrderStatus;
adminStatus: string;
allowedTransitions: import("@prisma/client").$Enums.OrderStatus[];
totalMeterage: number;
meterage: number;
totalAmount: number | null;
shippingCost: number | null;
totalDiscount: number | null;
finalAmount: number | null;
pricesVisible: boolean;
notes: string | undefined;
checkRassDays: number | undefined;
checkRassDate: string | undefined;
cashInvoiceExpiresAt: string | undefined;
createdAt: string;
updatedAt: string;
customerName: string;
customerPhone: string;
customerNationalId: string | undefined;
customerType: import("@prisma/client").$Enums.CustomerType;
items: {
id: string;
productId: string | undefined;
catalogCode: string;
productName: string;
productCode: string | undefined;
color: string;
requestedMeterage: number;
confirmedMeterage: number | null;
unitPrice: number | null;
supplyPrice: number | null;
unitDiscount: number | null;
discount: number | null;
lineTotal: number | null; lineTotal: number | null;
totalAmount: number | null; totalAmount: number | null;
notes: string | undefined; notes: string | undefined;
@@ -538,17 +653,19 @@ export declare class OrdersService {
userId: string; userId: string;
companyId: string; companyId: string;
companyName: string; companyName: string;
settlementType: import("@prisma/client").$Enums.SettlementType; settlementType: import("@prisma/client").$Enums.SettlementType | null;
settlementMethod: import("@prisma/client").$Enums.SettlementType; settlementMethod: import("@prisma/client").$Enums.SettlementType | null;
checkMode: import("@prisma/client").$Enums.CheckRegistrationMode | undefined; checkMode: import("@prisma/client").$Enums.CheckRegistrationMode | undefined;
status: import("@prisma/client").$Enums.OrderStatus; status: import("@prisma/client").$Enums.OrderStatus;
adminStatus: string; adminStatus: string;
allowedTransitions: import("@prisma/client").$Enums.OrderStatus[];
totalMeterage: number; totalMeterage: number;
meterage: number; meterage: number;
totalAmount: number | null; totalAmount: number | null;
shippingCost: number | null; shippingCost: number | null;
totalDiscount: number | null; totalDiscount: number | null;
finalAmount: number | null; finalAmount: number | null;
pricesVisible: boolean;
notes: string | undefined; notes: string | undefined;
checkRassDays: number | undefined; checkRassDays: number | undefined;
checkRassDate: string | undefined; checkRassDate: string | undefined;
@@ -570,8 +687,8 @@ export declare class OrdersService {
confirmedMeterage: number | null; confirmedMeterage: number | null;
unitPrice: number | null; unitPrice: number | null;
supplyPrice: number | null; supplyPrice: number | null;
unitDiscount: number; unitDiscount: number | null;
discount: number; discount: number | null;
lineTotal: number | null; lineTotal: number | null;
totalAmount: number | null; totalAmount: number | null;
notes: string | undefined; notes: string | undefined;
@@ -639,17 +756,19 @@ export declare class OrdersService {
userId: string; userId: string;
companyId: string; companyId: string;
companyName: string; companyName: string;
settlementType: import("@prisma/client").$Enums.SettlementType; settlementType: import("@prisma/client").$Enums.SettlementType | null;
settlementMethod: import("@prisma/client").$Enums.SettlementType; settlementMethod: import("@prisma/client").$Enums.SettlementType | null;
checkMode: import("@prisma/client").$Enums.CheckRegistrationMode | undefined; checkMode: import("@prisma/client").$Enums.CheckRegistrationMode | undefined;
status: import("@prisma/client").$Enums.OrderStatus; status: import("@prisma/client").$Enums.OrderStatus;
adminStatus: string; adminStatus: string;
allowedTransitions: import("@prisma/client").$Enums.OrderStatus[];
totalMeterage: number; totalMeterage: number;
meterage: number; meterage: number;
totalAmount: number | null; totalAmount: number | null;
shippingCost: number | null; shippingCost: number | null;
totalDiscount: number | null; totalDiscount: number | null;
finalAmount: number | null; finalAmount: number | null;
pricesVisible: boolean;
notes: string | undefined; notes: string | undefined;
checkRassDays: number | undefined; checkRassDays: number | undefined;
checkRassDate: string | undefined; checkRassDate: string | undefined;
@@ -671,8 +790,8 @@ export declare class OrdersService {
confirmedMeterage: number | null; confirmedMeterage: number | null;
unitPrice: number | null; unitPrice: number | null;
supplyPrice: number | null; supplyPrice: number | null;
unitDiscount: number; unitDiscount: number | null;
discount: number; discount: number | null;
lineTotal: number | null; lineTotal: number | null;
totalAmount: number | null; totalAmount: number | null;
notes: string | undefined; notes: string | undefined;
@@ -740,17 +859,19 @@ export declare class OrdersService {
userId: string; userId: string;
companyId: string; companyId: string;
companyName: string; companyName: string;
settlementType: import("@prisma/client").$Enums.SettlementType; settlementType: import("@prisma/client").$Enums.SettlementType | null;
settlementMethod: import("@prisma/client").$Enums.SettlementType; settlementMethod: import("@prisma/client").$Enums.SettlementType | null;
checkMode: import("@prisma/client").$Enums.CheckRegistrationMode | undefined; checkMode: import("@prisma/client").$Enums.CheckRegistrationMode | undefined;
status: import("@prisma/client").$Enums.OrderStatus; status: import("@prisma/client").$Enums.OrderStatus;
adminStatus: string; adminStatus: string;
allowedTransitions: import("@prisma/client").$Enums.OrderStatus[];
totalMeterage: number; totalMeterage: number;
meterage: number; meterage: number;
totalAmount: number | null; totalAmount: number | null;
shippingCost: number | null; shippingCost: number | null;
totalDiscount: number | null; totalDiscount: number | null;
finalAmount: number | null; finalAmount: number | null;
pricesVisible: boolean;
notes: string | undefined; notes: string | undefined;
checkRassDays: number | undefined; checkRassDays: number | undefined;
checkRassDate: string | undefined; checkRassDate: string | undefined;
@@ -772,8 +893,8 @@ export declare class OrdersService {
confirmedMeterage: number | null; confirmedMeterage: number | null;
unitPrice: number | null; unitPrice: number | null;
supplyPrice: number | null; supplyPrice: number | null;
unitDiscount: number; unitDiscount: number | null;
discount: number; discount: number | null;
lineTotal: number | null; lineTotal: number | null;
totalAmount: number | null; totalAmount: number | null;
notes: string | undefined; notes: string | undefined;
@@ -841,17 +962,19 @@ export declare class OrdersService {
userId: string; userId: string;
companyId: string; companyId: string;
companyName: string; companyName: string;
settlementType: import("@prisma/client").$Enums.SettlementType; settlementType: import("@prisma/client").$Enums.SettlementType | null;
settlementMethod: import("@prisma/client").$Enums.SettlementType; settlementMethod: import("@prisma/client").$Enums.SettlementType | null;
checkMode: import("@prisma/client").$Enums.CheckRegistrationMode | undefined; checkMode: import("@prisma/client").$Enums.CheckRegistrationMode | undefined;
status: import("@prisma/client").$Enums.OrderStatus; status: import("@prisma/client").$Enums.OrderStatus;
adminStatus: string; adminStatus: string;
allowedTransitions: import("@prisma/client").$Enums.OrderStatus[];
totalMeterage: number; totalMeterage: number;
meterage: number; meterage: number;
totalAmount: number | null; totalAmount: number | null;
shippingCost: number | null; shippingCost: number | null;
totalDiscount: number | null; totalDiscount: number | null;
finalAmount: number | null; finalAmount: number | null;
pricesVisible: boolean;
notes: string | undefined; notes: string | undefined;
checkRassDays: number | undefined; checkRassDays: number | undefined;
checkRassDate: string | undefined; checkRassDate: string | undefined;
@@ -873,8 +996,8 @@ export declare class OrdersService {
confirmedMeterage: number | null; confirmedMeterage: number | null;
unitPrice: number | null; unitPrice: number | null;
supplyPrice: number | null; supplyPrice: number | null;
unitDiscount: number; unitDiscount: number | null;
discount: number; discount: number | null;
lineTotal: number | null; lineTotal: number | null;
totalAmount: number | null; totalAmount: number | null;
notes: string | undefined; notes: string | undefined;
@@ -946,17 +1069,19 @@ export declare class OrdersService {
userId: string; userId: string;
companyId: string; companyId: string;
companyName: string; companyName: string;
settlementType: import("@prisma/client").$Enums.SettlementType; settlementType: import("@prisma/client").$Enums.SettlementType | null;
settlementMethod: import("@prisma/client").$Enums.SettlementType; settlementMethod: import("@prisma/client").$Enums.SettlementType | null;
checkMode: import("@prisma/client").$Enums.CheckRegistrationMode | undefined; checkMode: import("@prisma/client").$Enums.CheckRegistrationMode | undefined;
status: import("@prisma/client").$Enums.OrderStatus; status: import("@prisma/client").$Enums.OrderStatus;
adminStatus: string; adminStatus: string;
allowedTransitions: import("@prisma/client").$Enums.OrderStatus[];
totalMeterage: number; totalMeterage: number;
meterage: number; meterage: number;
totalAmount: number | null; totalAmount: number | null;
shippingCost: number | null; shippingCost: number | null;
totalDiscount: number | null; totalDiscount: number | null;
finalAmount: number | null; finalAmount: number | null;
pricesVisible: boolean;
notes: string | undefined; notes: string | undefined;
checkRassDays: number | undefined; checkRassDays: number | undefined;
checkRassDate: string | undefined; checkRassDate: string | undefined;
@@ -978,8 +1103,8 @@ export declare class OrdersService {
confirmedMeterage: number | null; confirmedMeterage: number | null;
unitPrice: number | null; unitPrice: number | null;
supplyPrice: number | null; supplyPrice: number | null;
unitDiscount: number; unitDiscount: number | null;
discount: number; discount: number | null;
lineTotal: number | null; lineTotal: number | null;
totalAmount: number | null; totalAmount: number | null;
notes: string | undefined; notes: string | undefined;
+132 -72
View File
@@ -17,6 +17,10 @@ const order_number_1 = require("../common/utils/order-number");
const check_rass_1 = require("../common/utils/check-rass"); const check_rass_1 = require("../common/utils/check-rass");
const cash_expiry_1 = require("../common/utils/cash-expiry"); const cash_expiry_1 = require("../common/utils/cash-expiry");
const order_status_1 = require("../common/utils/order-status"); const order_status_1 = require("../common/utils/order-status");
const order_transitions_1 = require("../common/utils/order-transitions");
const meterage_1 = require("../common/utils/meterage");
const notifications_service_1 = require("../notifications/notifications.service");
const notification_events_1 = require("../notifications/notification-events");
const name_1 = require("../common/utils/name"); const name_1 = require("../common/utils/name");
const phone_1 = require("../common/utils/phone"); const phone_1 = require("../common/utils/phone");
const orderInclude = { const orderInclude = {
@@ -32,19 +36,21 @@ function roundMoney(value) {
return Math.round(value * 100) / 100; return Math.round(value * 100) / 100;
} }
const CHECK_EDITABLE_STATUSES = [ const CHECK_EDITABLE_STATUSES = [
client_1.OrderStatus.awaiting_review,
client_1.OrderStatus.pricing,
client_1.OrderStatus.needs_correction,
client_1.OrderStatus.awaiting_settlement_fix,
client_1.OrderStatus.check_in_progress, client_1.OrderStatus.check_in_progress,
client_1.OrderStatus.awaiting_check_review, client_1.OrderStatus.awaiting_check_review,
client_1.OrderStatus.check_needs_fix, client_1.OrderStatus.check_needs_fix,
client_1.OrderStatus.check_rejected, client_1.OrderStatus.check_rejected,
]; ];
const PRICE_HIDDEN_STATUSES = [
client_1.OrderStatus.awaiting_review,
client_1.OrderStatus.pricing,
];
let OrdersService = class OrdersService { let OrdersService = class OrdersService {
prisma; prisma;
constructor(prisma) { notifications;
constructor(prisma, notifications) {
this.prisma = prisma; this.prisma = prisma;
this.notifications = notifications;
} }
async findMyOrders(userId) { async findMyOrders(userId) {
const orders = await this.prisma.order.findMany({ const orders = await this.prisma.order.findMany({
@@ -95,12 +101,17 @@ let OrdersService = class OrdersService {
return this.mapOrder(order, true); return this.mapOrder(order, true);
} }
async submitOrder(userId, dto) { async submitOrder(userId, dto) {
const company = await this.prisma.company.findUnique({ const [company, settings] = await Promise.all([
where: { id: dto.companyId }, this.prisma.company.findUnique({ where: { id: dto.companyId } }),
}); this.prisma.systemSettings.findUnique({
where: { id: 'default' },
select: { maxMeteragePerInvoice: true },
}),
]);
if (!company || !company.isActive) { if (!company || !company.isActive) {
throw new common_1.NotFoundException('شرکت یافت نشد یا غیرفعال است'); throw new common_1.NotFoundException('شرکت یافت نشد یا غیرفعال است');
} }
const maxMeterage = (0, meterage_1.resolveMaxMeterage)(company.maxMeteragePerInvoice, settings?.maxMeteragePerInvoice);
const productIds = dto.items.map((i) => i.productId); const productIds = dto.items.map((i) => i.productId);
const products = await this.prisma.product.findMany({ const products = await this.prisma.product.findMany({
where: { id: { in: productIds }, companyId: company.id, isActive: true }, where: { id: { in: productIds }, companyId: company.id, isActive: true },
@@ -128,31 +139,12 @@ let OrdersService = class OrdersService {
productCode: product.code, productCode: product.code,
color: product.color, color: product.color,
requestedMeterage: item.meterage, requestedMeterage: item.meterage,
unitPrice: product.basePrice,
notes: item.notes?.trim() || null, notes: item.notes?.trim() || null,
}; };
}); });
if (totalMeterage > company.maxMeteragePerInvoice) { if (totalMeterage > maxMeterage) {
throw new common_1.BadRequestException(`حداکثر متراژ مجاز هر فاکتور ${company.maxMeteragePerInvoice} متر است`); throw new common_1.BadRequestException(`حداکثر متراژ مجاز هر فاکتور ${maxMeterage} متر است`);
} }
const settlementType = dto.settlementType;
if (settlementType === client_1.SettlementType.check) {
if (!dto.checks?.length) {
throw new common_1.BadRequestException('برای تسویه چکی حداقل یک چک لازم است');
}
}
const checkMode = settlementType === client_1.SettlementType.check
? (dto.checkMode ?? client_1.CheckRegistrationMode.full)
: null;
const rass = settlementType === client_1.SettlementType.check && dto.checks?.length
? (0, check_rass_1.calculateCheckRass)(dto.checks.map((c) => ({
amount: c.amount,
dueDate: c.dueDate,
})))
: null;
const cashInvoiceExpiresAt = settlementType === client_1.SettlementType.cash
? (0, cash_expiry_1.computeCashInvoiceExpiry)(company)
: null;
const order = await this.createWithUniqueOrderNumber((orderNumber) => this.prisma.$transaction(async (tx) => { const order = await this.createWithUniqueOrderNumber((orderNumber) => this.prisma.$transaction(async (tx) => {
const created = await tx.order.create({ const created = await tx.order.create({
data: { data: {
@@ -160,33 +152,19 @@ let OrdersService = class OrdersService {
userId, userId,
companyId: company.id, companyId: company.id,
companyName: company.name, companyName: company.name,
settlementType, settlementType: null,
checkMode, checkMode: null,
status: client_1.OrderStatus.awaiting_review, status: client_1.OrderStatus.awaiting_review,
totalMeterage, totalMeterage,
notes: dto.notes?.trim() || null, notes: dto.notes?.trim() || null,
checkRassDays: rass?.rassDays ?? null,
checkRassDate: rass?.rassDate ? new Date(rass.rassDate) : null,
cashInvoiceExpiresAt,
items: { create: itemsData }, items: { create: itemsData },
checks: settlementType === client_1.SettlementType.check && dto.checks
? {
create: dto.checks.map((c, index) => this.mapCheckCreate(c, index + 1, checkMode)),
}
: undefined,
statusHistory: { statusHistory: {
create: [ create: [
{ {
fromStatus: null, fromStatus: null,
toStatus: client_1.OrderStatus.submitted,
changedBy: 'customer',
note: 'سفارش ثبت شد',
},
{
fromStatus: client_1.OrderStatus.submitted,
toStatus: client_1.OrderStatus.awaiting_review, toStatus: client_1.OrderStatus.awaiting_review,
changedBy: 'customer', changedBy: 'customer',
note: 'ارسال برای بررسی مدیریت', note: 'سفارش ثبت شد و برای بررسی مدیریت ارسال شد',
}, },
], ],
}, },
@@ -201,13 +179,57 @@ let OrdersService = class OrdersService {
} }
return created; return created;
})); }));
this.notifications.dispatchOrderEvent(order.id, 'order_submitted');
this.notifications.dispatchAdmin({
title: 'سفارش جدید ثبت شد',
body: `سفارش ${order.orderNumber} با ${order.items.length} قلم و ${order.totalMeterage} متر در انتظار قیمت‌گذاری است.`,
link: `/orders/${order.id}`,
});
return this.mapOrder(order); return this.mapOrder(order);
} }
async adminCreate(adminId, dto) { async chooseSettlement(userId, id, dto) {
const settlementType = dto.settlementType ?? dto.settlementMethod; const order = await this.getOrder(id);
if (!settlementType) { if (order.userId !== userId) {
throw new common_1.BadRequestException('نوع تسویه الزامی است'); throw new common_1.ForbiddenException('دسترسی به این سفارش مجاز نیست');
} }
if (order.status !== client_1.OrderStatus.awaiting_settlement) {
throw new common_1.BadRequestException('انتخاب روش تسویه فقط پس از صدور فاکتور توسط مدیریت امکان‌پذیر است');
}
const isCash = dto.settlementType === client_1.SettlementType.cash;
const nextStatus = isCash
? client_1.OrderStatus.awaiting_cash_payment
: client_1.OrderStatus.check_in_progress;
(0, order_transitions_1.assertTransition)(order.status, nextStatus);
await this.prisma.$transaction(async (tx) => {
await tx.order.update({
where: { id },
data: {
settlementType: dto.settlementType,
checkMode: isCash
? null
: (dto.checkMode ?? client_1.CheckRegistrationMode.full),
cashInvoiceExpiresAt: isCash
? (0, cash_expiry_1.computeCashInvoiceExpiry)(order.company)
: null,
cashReminderSentAt: null,
status: nextStatus,
},
});
await tx.orderStatusHistory.create({
data: {
orderId: id,
fromStatus: order.status,
toStatus: nextStatus,
changedBy: 'customer',
note: isCash
? 'مشتری تسویه نقدی را انتخاب کرد'
: 'مشتری تسویه چکی را انتخاب کرد',
},
});
});
return this.findMyOrder(userId, id);
}
async adminCreate(adminId, dto) {
const company = await this.prisma.company.findUnique({ const company = await this.prisma.company.findUnique({
where: { id: dto.companyId }, where: { id: dto.companyId },
}); });
@@ -245,7 +267,7 @@ let OrdersService = class OrdersService {
userId: ownerId, userId: ownerId,
companyId: company.id, companyId: company.id,
companyName: company.name, companyName: company.name,
settlementType, settlementType: null,
status: client_1.OrderStatus.awaiting_review, status: client_1.OrderStatus.awaiting_review,
totalMeterage, totalMeterage,
notes: dto.notes?.trim() || null, notes: dto.notes?.trim() || null,
@@ -338,6 +360,18 @@ let OrdersService = class OrdersService {
if (order.status === dto.status) { if (order.status === dto.status) {
return this.mapOrder(order, true); return this.mapOrder(order, true);
} }
if (dto.force) {
if (!dto.reason?.trim()) {
throw new common_1.BadRequestException('برای تغییر دستی وضعیت، ذکر دلیل الزامی است');
}
}
else {
(0, order_transitions_1.assertTransition)(order.status, dto.status);
}
if (dto.status === client_1.OrderStatus.awaiting_settlement &&
(order.finalAmount ?? 0) <= 0) {
throw new common_1.BadRequestException('ابتدا سفارش را قیمت‌گذاری کنید تا فاکتور قابل ارسال باشد');
}
await this.prisma.$transaction(async (tx) => { await this.prisma.$transaction(async (tx) => {
await tx.order.update({ await tx.order.update({
where: { id }, where: { id },
@@ -349,7 +383,9 @@ let OrdersService = class OrdersService {
fromStatus: order.status, fromStatus: order.status,
toStatus: dto.status, toStatus: dto.status,
changedBy: `admin:${adminId}`, changedBy: `admin:${adminId}`,
note: dto.note || null, note: dto.force
? `تغییر دستی وضعیت${dto.note ? ` — ${dto.note}` : ''}`
: dto.note || null,
reason: dto.reason || null, reason: dto.reason || null,
}, },
}); });
@@ -365,6 +401,12 @@ let OrdersService = class OrdersService {
} }
} }
}); });
const event = (0, notification_events_1.eventForStatus)(dto.status);
if (event) {
this.notifications.dispatchOrderEvent(id, event, {
note: dto.reason ?? dto.note ?? null,
});
}
return this.findAdminOrder(id); return this.findAdminOrder(id);
} }
async updateChecks(userId, id, dto) { async updateChecks(userId, id, dto) {
@@ -378,8 +420,18 @@ let OrdersService = class OrdersService {
if (!CHECK_EDITABLE_STATUSES.includes(order.status)) { if (!CHECK_EDITABLE_STATUSES.includes(order.status)) {
throw new common_1.BadRequestException('در وضعیت فعلی سفارش امکان ویرایش چک‌ها وجود ندارد'); throw new common_1.BadRequestException('در وضعیت فعلی سفارش امکان ویرایش چک‌ها وجود ندارد');
} }
if (!dto.checks.length) {
throw new common_1.BadRequestException('حداقل یک چک لازم است');
}
const checkMode = dto.checkMode ?? order.checkMode ?? client_1.CheckRegistrationMode.full; const checkMode = dto.checkMode ?? order.checkMode ?? client_1.CheckRegistrationMode.full;
const rass = (0, check_rass_1.calculateCheckRass)(dto.checks.map((c) => ({ amount: c.amount, dueDate: c.dueDate })), order.createdAt); const rass = (0, check_rass_1.calculateCheckRass)(dto.checks.map((c) => ({ amount: c.amount, dueDate: c.dueDate })), order.createdAt);
const goesToReview = order.status === client_1.OrderStatus.check_in_progress ||
order.status === client_1.OrderStatus.check_needs_fix ||
order.status === client_1.OrderStatus.check_rejected;
const nextStatus = goesToReview
? client_1.OrderStatus.awaiting_check_review
: order.status;
const isFix = order.status !== client_1.OrderStatus.check_in_progress;
await this.prisma.$transaction(async (tx) => { await this.prisma.$transaction(async (tx) => {
await tx.check.deleteMany({ where: { orderId: id } }); await tx.check.deleteMany({ where: { orderId: id } });
await tx.check.createMany({ await tx.check.createMany({
@@ -394,23 +446,30 @@ let OrdersService = class OrdersService {
checkMode, checkMode,
checkRassDays: rass.rassDays, checkRassDays: rass.rassDays,
checkRassDate: rass.rassDate ? new Date(rass.rassDate) : null, checkRassDate: rass.rassDate ? new Date(rass.rassDate) : null,
status: order.status === client_1.OrderStatus.check_needs_fix status: nextStatus,
? client_1.OrderStatus.awaiting_check_review
: order.status,
}, },
}); });
if (order.status === client_1.OrderStatus.check_needs_fix) { if (nextStatus !== order.status) {
await tx.orderStatusHistory.create({ await tx.orderStatusHistory.create({
data: { data: {
orderId: id, orderId: id,
fromStatus: order.status, fromStatus: order.status,
toStatus: client_1.OrderStatus.awaiting_check_review, toStatus: nextStatus,
changedBy: 'customer', changedBy: 'customer',
note: 'چک‌ها اصلاح و مجدداً ارسال شد', note: isFix
? 'چک‌ها اصلاح و مجدداً ارسال شد'
: 'چک‌ها برای بررسی ارسال شد',
}, },
}); });
} }
}); });
if (nextStatus !== order.status) {
this.notifications.dispatchAdmin({
title: isFix ? 'چک‌های اصلاح‌شده رسید' : 'چک‌های سفارش ثبت شد',
body: `مشتری ${dto.checks.length} چک برای سفارش ${order.orderNumber} ثبت کرد و منتظر بررسی است.`,
link: `/orders/${id}`,
});
}
return this.findMyOrder(userId, id); return this.findMyOrder(userId, id);
} }
mapCheckCreate(c, row, defaultMode) { mapCheckCreate(c, row, defaultMode) {
@@ -467,6 +526,7 @@ let OrdersService = class OrdersService {
} }
mapOrder(order, adminView = false) { mapOrder(order, adminView = false) {
const customerName = (0, name_1.joinFullName)(order.user.firstName, order.user.lastName); const customerName = (0, name_1.joinFullName)(order.user.firstName, order.user.lastName);
const hidePrices = !adminView && PRICE_HIDDEN_STATUSES.includes(order.status);
const base = { const base = {
id: order.id, id: order.id,
orderNumber: order.orderNumber, orderNumber: order.orderNumber,
@@ -478,12 +538,14 @@ let OrdersService = class OrdersService {
checkMode: order.checkMode ?? undefined, checkMode: order.checkMode ?? undefined,
status: order.status, status: order.status,
adminStatus: (0, order_status_1.toAdminStatus)(order.status), adminStatus: (0, order_status_1.toAdminStatus)(order.status),
allowedTransitions: adminView ? (0, order_transitions_1.allowedTransitions)(order.status) : [],
totalMeterage: order.totalMeterage, totalMeterage: order.totalMeterage,
meterage: order.totalMeterage, meterage: order.totalMeterage,
totalAmount: order.totalAmount, totalAmount: hidePrices ? null : order.totalAmount,
shippingCost: order.shippingCost, shippingCost: hidePrices ? null : order.shippingCost,
totalDiscount: order.totalDiscount, totalDiscount: hidePrices ? null : order.totalDiscount,
finalAmount: order.finalAmount, finalAmount: hidePrices ? null : order.finalAmount,
pricesVisible: !hidePrices,
notes: order.notes ?? undefined, notes: order.notes ?? undefined,
checkRassDays: order.checkRassDays ?? undefined, checkRassDays: order.checkRassDays ?? undefined,
checkRassDate: order.checkRassDate?.toISOString(), checkRassDate: order.checkRassDate?.toISOString(),
@@ -503,12 +565,12 @@ let OrdersService = class OrdersService {
color: item.color, color: item.color,
requestedMeterage: item.requestedMeterage, requestedMeterage: item.requestedMeterage,
confirmedMeterage: item.confirmedMeterage, confirmedMeterage: item.confirmedMeterage,
unitPrice: item.unitPrice, unitPrice: hidePrices ? null : item.unitPrice,
supplyPrice: item.unitPrice, supplyPrice: hidePrices ? null : item.unitPrice,
unitDiscount: item.unitDiscount ?? 0, unitDiscount: hidePrices ? null : (item.unitDiscount ?? 0),
discount: item.unitDiscount ?? 0, discount: hidePrices ? null : (item.unitDiscount ?? 0),
lineTotal: item.lineTotal, lineTotal: hidePrices ? null : item.lineTotal,
totalAmount: item.lineTotal, totalAmount: hidePrices ? null : item.lineTotal,
notes: item.notes ?? undefined, notes: item.notes ?? undefined,
})), })),
checks: order.checks.map((c) => ({ checks: order.checks.map((c) => ({
@@ -573,15 +635,13 @@ let OrdersService = class OrdersService {
changedAt: h.changedAt.toISOString(), changedAt: h.changedAt.toISOString(),
})), })),
}; };
if (adminView) {
return base;
}
return base; return base;
} }
}; };
exports.OrdersService = OrdersService; exports.OrdersService = OrdersService;
exports.OrdersService = OrdersService = __decorate([ exports.OrdersService = OrdersService = __decorate([
(0, common_1.Injectable)(), (0, common_1.Injectable)(),
__metadata("design:paramtypes", [prisma_service_1.PrismaService]) __metadata("design:paramtypes", [prisma_service_1.PrismaService,
notifications_service_1.NotificationsService])
], OrdersService); ], OrdersService);
//# sourceMappingURL=orders.service.js.map //# sourceMappingURL=orders.service.js.map
+1 -1
View File
File diff suppressed because one or more lines are too long
+1
View File
@@ -4,6 +4,7 @@ export declare class UpdateSettingsDto {
defaultSettlementCheck?: boolean; defaultSettlementCheck?: boolean;
defaultSettlementCash?: boolean; defaultSettlementCash?: boolean;
smsOrderRegistered?: boolean; smsOrderRegistered?: boolean;
smsPricingReady?: boolean;
smsPaymentReminder?: boolean; smsPaymentReminder?: boolean;
smsCheckReview?: boolean; smsCheckReview?: boolean;
smsShippingUpdate?: boolean; smsShippingUpdate?: boolean;
+6
View File
@@ -18,6 +18,7 @@ class UpdateSettingsDto {
defaultSettlementCheck; defaultSettlementCheck;
defaultSettlementCash; defaultSettlementCash;
smsOrderRegistered; smsOrderRegistered;
smsPricingReady;
smsPaymentReminder; smsPaymentReminder;
smsCheckReview; smsCheckReview;
smsShippingUpdate; smsShippingUpdate;
@@ -54,6 +55,11 @@ __decorate([
(0, class_validator_1.IsBoolean)(), (0, class_validator_1.IsBoolean)(),
__metadata("design:type", Boolean) __metadata("design:type", Boolean)
], UpdateSettingsDto.prototype, "smsOrderRegistered", void 0); ], UpdateSettingsDto.prototype, "smsOrderRegistered", void 0);
__decorate([
(0, class_validator_1.IsOptional)(),
(0, class_validator_1.IsBoolean)(),
__metadata("design:type", Boolean)
], UpdateSettingsDto.prototype, "smsPricingReady", void 0);
__decorate([ __decorate([
(0, class_validator_1.IsOptional)(), (0, class_validator_1.IsOptional)(),
(0, class_validator_1.IsBoolean)(), (0, class_validator_1.IsBoolean)(),
+1 -1
View File
@@ -1 +1 @@
{"version":3,"file":"settings.dto.js","sourceRoot":"","sources":["../../../src/settings/dto/settings.dto.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,qDAA8E;AAC9E,yDAAyC;AAEzC,MAAa,iBAAiB;IAK5B,qBAAqB,CAAU;IAM/B,wBAAwB,CAAU;IAIlC,sBAAsB,CAAW;IAIjC,qBAAqB,CAAW;IAIhC,kBAAkB,CAAW;IAI7B,kBAAkB,CAAW;IAI7B,cAAc,CAAW;IAIzB,iBAAiB,CAAW;IAI5B,YAAY,CAAiB;IAI7B,YAAY,CAAiB;CAC9B;AA5CD,8CA4CC;AAvCC;IAJC,IAAA,4BAAU,GAAE;IACZ,IAAA,wBAAI,EAAC,GAAG,EAAE,CAAC,MAAM,CAAC;IAClB,IAAA,uBAAK,GAAE;IACP,IAAA,qBAAG,EAAC,CAAC,CAAC;;gEACwB;AAM/B;IAJC,IAAA,4BAAU,GAAE;IACZ,IAAA,wBAAI,EAAC,GAAG,EAAE,CAAC,MAAM,CAAC;IAClB,IAAA,uBAAK,GAAE;IACP,IAAA,qBAAG,EAAC,CAAC,CAAC;;mEAC2B;AAIlC;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,2BAAS,GAAE;;iEACqB;AAIjC;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,2BAAS,GAAE;;gEACoB;AAIhC;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,2BAAS,GAAE;;6DACiB;AAI7B;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,2BAAS,GAAE;;6DACiB;AAI7B;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,2BAAS,GAAE;;yDACa;AAIzB;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,2BAAS,GAAE;;4DACgB;AAI5B;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;;uDACkB;AAI7B;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;;uDACkB"} {"version":3,"file":"settings.dto.js","sourceRoot":"","sources":["../../../src/settings/dto/settings.dto.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,qDAA8E;AAC9E,yDAAyC;AAEzC,MAAa,iBAAiB;IAK5B,qBAAqB,CAAU;IAM/B,wBAAwB,CAAU;IAIlC,sBAAsB,CAAW;IAIjC,qBAAqB,CAAW;IAIhC,kBAAkB,CAAW;IAI7B,eAAe,CAAW;IAI1B,kBAAkB,CAAW;IAI7B,cAAc,CAAW;IAIzB,iBAAiB,CAAW;IAI5B,YAAY,CAAiB;IAI7B,YAAY,CAAiB;CAC9B;AAhDD,8CAgDC;AA3CC;IAJC,IAAA,4BAAU,GAAE;IACZ,IAAA,wBAAI,EAAC,GAAG,EAAE,CAAC,MAAM,CAAC;IAClB,IAAA,uBAAK,GAAE;IACP,IAAA,qBAAG,EAAC,CAAC,CAAC;;gEACwB;AAM/B;IAJC,IAAA,4BAAU,GAAE;IACZ,IAAA,wBAAI,EAAC,GAAG,EAAE,CAAC,MAAM,CAAC;IAClB,IAAA,uBAAK,GAAE;IACP,IAAA,qBAAG,EAAC,CAAC,CAAC;;mEAC2B;AAIlC;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,2BAAS,GAAE;;iEACqB;AAIjC;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,2BAAS,GAAE;;gEACoB;AAIhC;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,2BAAS,GAAE;;6DACiB;AAI7B;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,2BAAS,GAAE;;0DACc;AAI1B;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,2BAAS,GAAE;;6DACiB;AAI7B;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,2BAAS,GAAE;;yDACa;AAIzB;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,2BAAS,GAAE;;4DACgB;AAI5B;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;;uDACkB;AAI7B;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;;uDACkB"}
+2
View File
@@ -10,6 +10,7 @@ export declare class SettingsController {
defaultSettlementCheck: boolean; defaultSettlementCheck: boolean;
defaultSettlementCash: boolean; defaultSettlementCash: boolean;
smsOrderRegistered: boolean; smsOrderRegistered: boolean;
smsPricingReady: boolean;
smsPaymentReminder: boolean; smsPaymentReminder: boolean;
smsCheckReview: boolean; smsCheckReview: boolean;
smsShippingUpdate: boolean; smsShippingUpdate: boolean;
@@ -24,6 +25,7 @@ export declare class SettingsController {
defaultSettlementCheck: boolean; defaultSettlementCheck: boolean;
defaultSettlementCash: boolean; defaultSettlementCash: boolean;
smsOrderRegistered: boolean; smsOrderRegistered: boolean;
smsPricingReady: boolean;
smsPaymentReminder: boolean; smsPaymentReminder: boolean;
smsCheckReview: boolean; smsCheckReview: boolean;
smsShippingUpdate: boolean; smsShippingUpdate: boolean;
+2
View File
@@ -10,6 +10,7 @@ export declare class SettingsService {
defaultSettlementCheck: boolean; defaultSettlementCheck: boolean;
defaultSettlementCash: boolean; defaultSettlementCash: boolean;
smsOrderRegistered: boolean; smsOrderRegistered: boolean;
smsPricingReady: boolean;
smsPaymentReminder: boolean; smsPaymentReminder: boolean;
smsCheckReview: boolean; smsCheckReview: boolean;
smsShippingUpdate: boolean; smsShippingUpdate: boolean;
@@ -24,6 +25,7 @@ export declare class SettingsService {
defaultSettlementCheck: boolean; defaultSettlementCheck: boolean;
defaultSettlementCash: boolean; defaultSettlementCash: boolean;
smsOrderRegistered: boolean; smsOrderRegistered: boolean;
smsPricingReady: boolean;
smsPaymentReminder: boolean; smsPaymentReminder: boolean;
smsCheckReview: boolean; smsCheckReview: boolean;
smsShippingUpdate: boolean; smsShippingUpdate: boolean;
+2
View File
@@ -31,6 +31,7 @@ let SettingsService = class SettingsService {
defaultSettlementCheck: dto.defaultSettlementCheck, defaultSettlementCheck: dto.defaultSettlementCheck,
defaultSettlementCash: dto.defaultSettlementCash, defaultSettlementCash: dto.defaultSettlementCash,
smsOrderRegistered: dto.smsOrderRegistered, smsOrderRegistered: dto.smsOrderRegistered,
smsPricingReady: dto.smsPricingReady,
smsPaymentReminder: dto.smsPaymentReminder, smsPaymentReminder: dto.smsPaymentReminder,
smsCheckReview: dto.smsCheckReview, smsCheckReview: dto.smsCheckReview,
smsShippingUpdate: dto.smsShippingUpdate, smsShippingUpdate: dto.smsShippingUpdate,
@@ -59,6 +60,7 @@ let SettingsService = class SettingsService {
defaultSettlementCheck: settings.defaultSettlementCheck, defaultSettlementCheck: settings.defaultSettlementCheck,
defaultSettlementCash: settings.defaultSettlementCash, defaultSettlementCash: settings.defaultSettlementCash,
smsOrderRegistered: settings.smsOrderRegistered, smsOrderRegistered: settings.smsOrderRegistered,
smsPricingReady: settings.smsPricingReady,
smsPaymentReminder: settings.smsPaymentReminder, smsPaymentReminder: settings.smsPaymentReminder,
smsCheckReview: settings.smsCheckReview, smsCheckReview: settings.smsCheckReview,
smsShippingUpdate: settings.smsShippingUpdate, smsShippingUpdate: settings.smsShippingUpdate,
+1 -1
View File
@@ -1 +1 @@
{"version":3,"file":"settings.service.js","sourceRoot":"","sources":["../../src/settings/settings.service.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,2CAA4C;AAC5C,6DAAyD;AAIlD,IAAM,eAAe,GAArB,MAAM,eAAe;IACG;IAA7B,YAA6B,MAAqB;QAArB,WAAM,GAAN,MAAM,CAAe;IAAG,CAAC;IAEtD,KAAK,CAAC,GAAG;QACP,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,cAAc,EAAE,CAAC;QAC7C,OAAO,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IAC5B,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,GAAsB;QACjC,MAAM,IAAI,CAAC,cAAc,EAAE,CAAC;QAC5B,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,cAAc,CAAC,MAAM,CAAC;YACvD,KAAK,EAAE,EAAE,EAAE,EAAE,SAAS,EAAE;YACxB,IAAI,EAAE;gBACJ,qBAAqB,EAAE,GAAG,CAAC,qBAAqB;gBAChD,wBAAwB,EAAE,GAAG,CAAC,wBAAwB;gBACtD,sBAAsB,EAAE,GAAG,CAAC,sBAAsB;gBAClD,qBAAqB,EAAE,GAAG,CAAC,qBAAqB;gBAChD,kBAAkB,EAAE,GAAG,CAAC,kBAAkB;gBAC1C,kBAAkB,EAAE,GAAG,CAAC,kBAAkB;gBAC1C,cAAc,EAAE,GAAG,CAAC,cAAc;gBAClC,iBAAiB,EAAE,GAAG,CAAC,iBAAiB;gBACxC,YAAY,EACV,GAAG,CAAC,YAAY,KAAK,SAAS;oBAC5B,CAAC,CAAC,SAAS;oBACX,CAAC,CAAC,GAAG,CAAC,YAAY,EAAE,IAAI,EAAE,IAAI,IAAI;gBACtC,YAAY,EACV,GAAG,CAAC,YAAY,KAAK,SAAS;oBAC5B,CAAC,CAAC,SAAS;oBACX,CAAC,CAAC,GAAG,CAAC,YAAY,EAAE,IAAI,EAAE,IAAI,IAAI;aACvC;SACF,CAAC,CAAC;QACH,OAAO,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IAC5B,CAAC;IAEO,KAAK,CAAC,cAAc;QAC1B,OAAO,IAAI,CAAC,MAAM,CAAC,cAAc,CAAC,MAAM,CAAC;YACvC,KAAK,EAAE,EAAE,EAAE,EAAE,SAAS,EAAE;YACxB,MAAM,EAAE,EAAE,EAAE,EAAE,SAAS,EAAE;YACzB,MAAM,EAAE,EAAE;SACX,CAAC,CAAC;IACL,CAAC;IAEO,GAAG,CAAC,QAaX;QACC,OAAO;YACL,EAAE,EAAE,QAAQ,CAAC,EAAE;YACf,qBAAqB,EAAE,QAAQ,CAAC,qBAAqB;YACrD,wBAAwB,EAAE,QAAQ,CAAC,wBAAwB;YAC3D,sBAAsB,EAAE,QAAQ,CAAC,sBAAsB;YACvD,qBAAqB,EAAE,QAAQ,CAAC,qBAAqB;YACrD,kBAAkB,EAAE,QAAQ,CAAC,kBAAkB;YAC/C,kBAAkB,EAAE,QAAQ,CAAC,kBAAkB;YAC/C,cAAc,EAAE,QAAQ,CAAC,cAAc;YACvC,iBAAiB,EAAE,QAAQ,CAAC,iBAAiB;YAC7C,YAAY,EAAE,QAAQ,CAAC,YAAY;YACnC,YAAY,EAAE,QAAQ,CAAC,YAAY;YACnC,SAAS,EAAE,QAAQ,CAAC,SAAS,CAAC,WAAW,EAAE;SAC5C,CAAC;IACJ,CAAC;CACF,CAAA;AAvEY,0CAAe;0BAAf,eAAe;IAD3B,IAAA,mBAAU,GAAE;qCAE0B,8BAAa;GADvC,eAAe,CAuE3B"} {"version":3,"file":"settings.service.js","sourceRoot":"","sources":["../../src/settings/settings.service.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,2CAA4C;AAC5C,6DAAyD;AAIlD,IAAM,eAAe,GAArB,MAAM,eAAe;IACG;IAA7B,YAA6B,MAAqB;QAArB,WAAM,GAAN,MAAM,CAAe;IAAG,CAAC;IAEtD,KAAK,CAAC,GAAG;QACP,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,cAAc,EAAE,CAAC;QAC7C,OAAO,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IAC5B,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,GAAsB;QACjC,MAAM,IAAI,CAAC,cAAc,EAAE,CAAC;QAC5B,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,cAAc,CAAC,MAAM,CAAC;YACvD,KAAK,EAAE,EAAE,EAAE,EAAE,SAAS,EAAE;YACxB,IAAI,EAAE;gBACJ,qBAAqB,EAAE,GAAG,CAAC,qBAAqB;gBAChD,wBAAwB,EAAE,GAAG,CAAC,wBAAwB;gBACtD,sBAAsB,EAAE,GAAG,CAAC,sBAAsB;gBAClD,qBAAqB,EAAE,GAAG,CAAC,qBAAqB;gBAChD,kBAAkB,EAAE,GAAG,CAAC,kBAAkB;gBAC1C,eAAe,EAAE,GAAG,CAAC,eAAe;gBACpC,kBAAkB,EAAE,GAAG,CAAC,kBAAkB;gBAC1C,cAAc,EAAE,GAAG,CAAC,cAAc;gBAClC,iBAAiB,EAAE,GAAG,CAAC,iBAAiB;gBACxC,YAAY,EACV,GAAG,CAAC,YAAY,KAAK,SAAS;oBAC5B,CAAC,CAAC,SAAS;oBACX,CAAC,CAAC,GAAG,CAAC,YAAY,EAAE,IAAI,EAAE,IAAI,IAAI;gBACtC,YAAY,EACV,GAAG,CAAC,YAAY,KAAK,SAAS;oBAC5B,CAAC,CAAC,SAAS;oBACX,CAAC,CAAC,GAAG,CAAC,YAAY,EAAE,IAAI,EAAE,IAAI,IAAI;aACvC;SACF,CAAC,CAAC;QACH,OAAO,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IAC5B,CAAC;IAEO,KAAK,CAAC,cAAc;QAC1B,OAAO,IAAI,CAAC,MAAM,CAAC,cAAc,CAAC,MAAM,CAAC;YACvC,KAAK,EAAE,EAAE,EAAE,EAAE,SAAS,EAAE;YACxB,MAAM,EAAE,EAAE,EAAE,EAAE,SAAS,EAAE;YACzB,MAAM,EAAE,EAAE;SACX,CAAC,CAAC;IACL,CAAC;IAEO,GAAG,CAAC,QAcX;QACC,OAAO;YACL,EAAE,EAAE,QAAQ,CAAC,EAAE;YACf,qBAAqB,EAAE,QAAQ,CAAC,qBAAqB;YACrD,wBAAwB,EAAE,QAAQ,CAAC,wBAAwB;YAC3D,sBAAsB,EAAE,QAAQ,CAAC,sBAAsB;YACvD,qBAAqB,EAAE,QAAQ,CAAC,qBAAqB;YACrD,kBAAkB,EAAE,QAAQ,CAAC,kBAAkB;YAC/C,eAAe,EAAE,QAAQ,CAAC,eAAe;YACzC,kBAAkB,EAAE,QAAQ,CAAC,kBAAkB;YAC/C,cAAc,EAAE,QAAQ,CAAC,cAAc;YACvC,iBAAiB,EAAE,QAAQ,CAAC,iBAAiB;YAC7C,YAAY,EAAE,QAAQ,CAAC,YAAY;YACnC,YAAY,EAAE,QAAQ,CAAC,YAAY;YACnC,SAAS,EAAE,QAAQ,CAAC,SAAS,CAAC,WAAW,EAAE;SAC5C,CAAC;IACJ,CAAC;CACF,CAAA;AA1EY,0CAAe;0BAAf,eAAe;IAD3B,IAAA,mBAAU,GAAE;qCAE0B,8BAAa;GADvC,eAAe,CA0E3B"}
+2
View File
@@ -10,11 +10,13 @@ exports.ShipmentsModule = void 0;
const common_1 = require("@nestjs/common"); const common_1 = require("@nestjs/common");
const shipments_controller_1 = require("./shipments.controller"); const shipments_controller_1 = require("./shipments.controller");
const shipments_service_1 = require("./shipments.service"); const shipments_service_1 = require("./shipments.service");
const notifications_module_1 = require("../notifications/notifications.module");
let ShipmentsModule = class ShipmentsModule { let ShipmentsModule = class ShipmentsModule {
}; };
exports.ShipmentsModule = ShipmentsModule; exports.ShipmentsModule = ShipmentsModule;
exports.ShipmentsModule = ShipmentsModule = __decorate([ exports.ShipmentsModule = ShipmentsModule = __decorate([
(0, common_1.Module)({ (0, common_1.Module)({
imports: [notifications_module_1.NotificationsModule],
controllers: [shipments_controller_1.ShipmentsController], controllers: [shipments_controller_1.ShipmentsController],
providers: [shipments_service_1.ShipmentsService], providers: [shipments_service_1.ShipmentsService],
exports: [shipments_service_1.ShipmentsService], exports: [shipments_service_1.ShipmentsService],
+1 -1
View File
@@ -1 +1 @@
{"version":3,"file":"shipments.module.js","sourceRoot":"","sources":["../../src/shipments/shipments.module.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAAwC;AACxC,iEAA6D;AAC7D,2DAAuD;AAOhD,IAAM,eAAe,GAArB,MAAM,eAAe;CAAG,CAAA;AAAlB,0CAAe;0BAAf,eAAe;IAL3B,IAAA,eAAM,EAAC;QACN,WAAW,EAAE,CAAC,0CAAmB,CAAC;QAClC,SAAS,EAAE,CAAC,oCAAgB,CAAC;QAC7B,OAAO,EAAE,CAAC,oCAAgB,CAAC;KAC5B,CAAC;GACW,eAAe,CAAG"} {"version":3,"file":"shipments.module.js","sourceRoot":"","sources":["../../src/shipments/shipments.module.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAAwC;AACxC,iEAA6D;AAC7D,2DAAuD;AACvD,gFAA4E;AAQrE,IAAM,eAAe,GAArB,MAAM,eAAe;CAAG,CAAA;AAAlB,0CAAe;0BAAf,eAAe;IAN3B,IAAA,eAAM,EAAC;QACN,OAAO,EAAE,CAAC,0CAAmB,CAAC;QAC9B,WAAW,EAAE,CAAC,0CAAmB,CAAC;QAClC,SAAS,EAAE,CAAC,oCAAgB,CAAC;QAC7B,OAAO,EAAE,CAAC,oCAAgB,CAAC;KAC5B,CAAC;GACW,eAAe,CAAG"}
+4 -1
View File
@@ -1,9 +1,11 @@
import { ShipmentStatus } from '@prisma/client'; import { ShipmentStatus } from '@prisma/client';
import { PrismaService } from '../prisma/prisma.service'; import { PrismaService } from '../prisma/prisma.service';
import { AdvanceShipmentStatusDto, CreateShipmentDto, UpdateShipmentDto } from './dto/shipment.dto'; import { AdvanceShipmentStatusDto, CreateShipmentDto, UpdateShipmentDto } from './dto/shipment.dto';
import { NotificationsService } from '../notifications/notifications.service';
export declare class ShipmentsService { export declare class ShipmentsService {
private readonly prisma; private readonly prisma;
constructor(prisma: PrismaService); private readonly notifications;
constructor(prisma: PrismaService, notifications: NotificationsService);
findAll(status?: ShipmentStatus): Promise<{ findAll(status?: ShipmentStatus): Promise<{
id: string; id: string;
orderId: string; orderId: string;
@@ -74,6 +76,7 @@ export declare class ShipmentsService {
createdAt: string; createdAt: string;
updatedAt: string; updatedAt: string;
}>; }>;
private notifyOrderStatus;
private ensure; private ensure;
private map; private map;
} }
+21 -2
View File
@@ -14,6 +14,8 @@ const common_1 = require("@nestjs/common");
const client_1 = require("@prisma/client"); const client_1 = require("@prisma/client");
const prisma_service_1 = require("../prisma/prisma.service"); const prisma_service_1 = require("../prisma/prisma.service");
const name_1 = require("../common/utils/name"); const name_1 = require("../common/utils/name");
const notifications_service_1 = require("../notifications/notifications.service");
const notification_events_1 = require("../notifications/notification-events");
const STATUS_FLOW = [ const STATUS_FLOW = [
client_1.ShipmentStatus.preparing, client_1.ShipmentStatus.preparing,
client_1.ShipmentStatus.ready, client_1.ShipmentStatus.ready,
@@ -28,8 +30,10 @@ const SHIPMENT_TO_ORDER = {
}; };
let ShipmentsService = class ShipmentsService { let ShipmentsService = class ShipmentsService {
prisma; prisma;
constructor(prisma) { notifications;
constructor(prisma, notifications) {
this.prisma = prisma; this.prisma = prisma;
this.notifications = notifications;
} }
async findAll(status) { async findAll(status) {
const shipments = await this.prisma.shipment.findMany({ const shipments = await this.prisma.shipment.findMany({
@@ -92,6 +96,10 @@ let ShipmentsService = class ShipmentsService {
} }
return created; return created;
}); });
this.notifyOrderStatus(order.id, SHIPMENT_TO_ORDER[status], {
carrier: shipment.carrier,
trackingNumber: shipment.trackingNumber,
});
return this.map(shipment); return this.map(shipment);
} }
async update(id, dto) { async update(id, dto) {
@@ -160,8 +168,18 @@ let ShipmentsService = class ShipmentsService {
} }
return result; return result;
}); });
this.notifyOrderStatus(shipment.orderId, SHIPMENT_TO_ORDER[nextStatus], {
carrier: updated.carrier,
trackingNumber: updated.trackingNumber,
});
return this.map(updated); return this.map(updated);
} }
notifyOrderStatus(orderId, status, context) {
const event = (0, notification_events_1.eventForStatus)(status);
if (event) {
this.notifications.dispatchOrderEvent(orderId, event, context);
}
}
async ensure(id) { async ensure(id) {
const shipment = await this.prisma.shipment.findUnique({ where: { id } }); const shipment = await this.prisma.shipment.findUnique({ where: { id } });
if (!shipment) if (!shipment)
@@ -188,6 +206,7 @@ let ShipmentsService = class ShipmentsService {
exports.ShipmentsService = ShipmentsService; exports.ShipmentsService = ShipmentsService;
exports.ShipmentsService = ShipmentsService = __decorate([ exports.ShipmentsService = ShipmentsService = __decorate([
(0, common_1.Injectable)(), (0, common_1.Injectable)(),
__metadata("design:paramtypes", [prisma_service_1.PrismaService]) __metadata("design:paramtypes", [prisma_service_1.PrismaService,
notifications_service_1.NotificationsService])
], ShipmentsService); ], ShipmentsService);
//# sourceMappingURL=shipments.service.js.map //# sourceMappingURL=shipments.service.js.map
File diff suppressed because one or more lines are too long
+46
View File
@@ -15,6 +15,7 @@
"@nestjs/jwt": "^11.0.2", "@nestjs/jwt": "^11.0.2",
"@nestjs/passport": "^11.0.5", "@nestjs/passport": "^11.0.5",
"@nestjs/platform-express": "^11.0.1", "@nestjs/platform-express": "^11.0.1",
"@nestjs/schedule": "^6.1.3",
"@nestjs/swagger": "^11.4.6", "@nestjs/swagger": "^11.4.6",
"@prisma/client": "^6.19.3", "@prisma/client": "^6.19.3",
"bcryptjs": "^3.0.3", "bcryptjs": "^3.0.3",
@@ -2452,6 +2453,19 @@
"@nestjs/core": "^11.0.0" "@nestjs/core": "^11.0.0"
} }
}, },
"node_modules/@nestjs/schedule": {
"version": "6.1.3",
"resolved": "https://registry.npmmirror.com/@nestjs/schedule/-/schedule-6.1.3.tgz",
"integrity": "sha512-RflMFOpR16Dwd1jAUbeB4mfGTCh65fvEdL4mSjQPJChpkRGRjIXjb+6YQcK2faQrVT60c9DmLmoVR7/ONCtuYQ==",
"license": "MIT",
"dependencies": {
"cron": "4.4.0"
},
"peerDependencies": {
"@nestjs/common": "^10.0.0 || ^11.0.0",
"@nestjs/core": "^10.0.0 || ^11.0.0"
}
},
"node_modules/@nestjs/schematics": { "node_modules/@nestjs/schematics": {
"version": "11.1.0", "version": "11.1.0",
"resolved": "https://registry.npmjs.org/@nestjs/schematics/-/schematics-11.1.0.tgz", "resolved": "https://registry.npmjs.org/@nestjs/schematics/-/schematics-11.1.0.tgz",
@@ -3073,6 +3087,12 @@
"@types/node": "*" "@types/node": "*"
} }
}, },
"node_modules/@types/luxon": {
"version": "3.7.4",
"resolved": "https://registry.npmmirror.com/@types/luxon/-/luxon-3.7.4.tgz",
"integrity": "sha512-V536ZAd6ZJztrrBlLcDFaaZrXNAL2E5uGmssWf/dpSiLkmkLScXUYhUBnWPmtW+cIqnNHzf6//TCMpIc9SCRRQ==",
"license": "MIT"
},
"node_modules/@types/methods": { "node_modules/@types/methods": {
"version": "1.1.4", "version": "1.1.4",
"resolved": "https://registry.npmjs.org/@types/methods/-/methods-1.1.4.tgz", "resolved": "https://registry.npmjs.org/@types/methods/-/methods-1.1.4.tgz",
@@ -5146,6 +5166,23 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/cron": {
"version": "4.4.0",
"resolved": "https://registry.npmmirror.com/cron/-/cron-4.4.0.tgz",
"integrity": "sha512-fkdfq+b+AHI4cKdhZlppHveI/mgz2qpiYxcm+t5E5TsxX7QrLS1VE0+7GENEk9z0EeGPcpSciGv6ez24duWhwQ==",
"license": "MIT",
"dependencies": {
"@types/luxon": "~3.7.0",
"luxon": "~3.7.0"
},
"engines": {
"node": ">=18.x"
},
"funding": {
"type": "ko-fi",
"url": "https://ko-fi.com/intcreator"
}
},
"node_modules/cross-spawn": { "node_modules/cross-spawn": {
"version": "7.0.6", "version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
@@ -7979,6 +8016,15 @@
"yallist": "^3.0.2" "yallist": "^3.0.2"
} }
}, },
"node_modules/luxon": {
"version": "3.7.2",
"resolved": "https://registry.npmmirror.com/luxon/-/luxon-3.7.2.tgz",
"integrity": "sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew==",
"license": "MIT",
"engines": {
"node": ">=12"
}
},
"node_modules/magic-string": { "node_modules/magic-string": {
"version": "0.30.17", "version": "0.30.17",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz",
+1
View File
@@ -43,6 +43,7 @@
"@nestjs/jwt": "^11.0.2", "@nestjs/jwt": "^11.0.2",
"@nestjs/passport": "^11.0.5", "@nestjs/passport": "^11.0.5",
"@nestjs/platform-express": "^11.0.1", "@nestjs/platform-express": "^11.0.1",
"@nestjs/schedule": "^6.1.3",
"@nestjs/swagger": "^11.4.6", "@nestjs/swagger": "^11.4.6",
"@prisma/client": "^6.19.3", "@prisma/client": "^6.19.3",
"bcryptjs": "^3.0.3", "bcryptjs": "^3.0.3",
Binary file not shown.
@@ -0,0 +1,2 @@
-- روش تسویه دیگر در لحظه ثبت سفارش گرفته نمی‌شود؛ مشتری بعد از قیمت‌گذاری انتخاب می‌کند.
ALTER TABLE "Order" ALTER COLUMN "settlementType" DROP NOT NULL;
@@ -0,0 +1,24 @@
-- اعلان درون‌برنامه‌ای مشتری به‌همراه سوئیچ پیامک «فاکتور آماده شد»
ALTER TABLE "SystemSettings" ADD COLUMN "smsPricingReady" BOOLEAN NOT NULL DEFAULT true;
CREATE TABLE "CustomerNotification" (
"id" TEXT NOT NULL,
"customerId" TEXT NOT NULL,
"orderId" TEXT,
"event" TEXT NOT NULL,
"title" TEXT NOT NULL,
"body" TEXT NOT NULL,
"tone" "NotificationTone" DEFAULT 'info',
"link" TEXT,
"isRead" BOOLEAN NOT NULL DEFAULT false,
"smsSent" BOOLEAN NOT NULL DEFAULT false,
"sentAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "CustomerNotification_pkey" PRIMARY KEY ("id")
);
CREATE INDEX "CustomerNotification_customerId_isRead_idx" ON "CustomerNotification"("customerId", "isRead");
CREATE INDEX "CustomerNotification_sentAt_idx" ON "CustomerNotification"("sentAt");
ALTER TABLE "CustomerNotification" ADD CONSTRAINT "CustomerNotification_customerId_fkey" FOREIGN KEY ("customerId") REFERENCES "Customer"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "CustomerNotification" ADD CONSTRAINT "CustomerNotification_orderId_fkey" FOREIGN KEY ("orderId") REFERENCES "Order"("id") ON DELETE SET NULL ON UPDATE CASCADE;
@@ -0,0 +1,3 @@
-- سقف متراژ شرکت اختیاری می‌شود؛ خالی یعنی از SystemSettings ارث می‌برد.
ALTER TABLE "Company" ALTER COLUMN "maxMeteragePerInvoice" DROP NOT NULL;
ALTER TABLE "Company" ALTER COLUMN "maxMeteragePerInvoice" DROP DEFAULT;
@@ -0,0 +1,4 @@
-- یادآوری پایان مهلت پرداخت فقط یک‌بار برای هر فاکتور ارسال می‌شود.
ALTER TABLE "Order" ADD COLUMN "cashReminderSentAt" TIMESTAMP(3);
CREATE INDEX "Order_status_cashInvoiceExpiresAt_idx" ON "Order"("status", "cashInvoiceExpiresAt");
+35 -7
View File
@@ -94,7 +94,8 @@ model Company {
logoUrl String? logoUrl String?
isActive Boolean @default(true) isActive Boolean @default(true)
displayOrder Int @default(0) displayOrder Int @default(0)
maxMeteragePerInvoice Int @default(5000) /// خالی یعنی از تنظیمات سیستم ارث می‌برد
maxMeteragePerInvoice Int?
cashInvoiceValidityHours Int @default(8) cashInvoiceValidityHours Int @default(8)
cashInvoiceValidUntilHour Int? cashInvoiceValidUntilHour Int?
createdAt DateTime @default(now()) createdAt DateTime @default(now())
@@ -183,9 +184,10 @@ model Customer {
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
company Company? @relation(fields: [companyId], references: [id], onDelete: SetNull) company Company? @relation(fields: [companyId], references: [id], onDelete: SetNull)
carts Cart[] carts Cart[]
orders Order[] orders Order[]
notifications CustomerNotification[]
@@index([companyId]) @@index([companyId])
} }
@@ -258,7 +260,8 @@ model Order {
userId String userId String
companyId String companyId String
companyName String companyName String
settlementType SettlementType // روش تسویه بعد از قیمت‌گذاری ادمین توسط مشتری انتخاب می‌شود
settlementType SettlementType?
checkMode CheckRegistrationMode? checkMode CheckRegistrationMode?
status OrderStatus @default(awaiting_review) status OrderStatus @default(awaiting_review)
totalMeterage Float @default(0) totalMeterage Float @default(0)
@@ -270,21 +273,25 @@ model Order {
checkRassDays Int? checkRassDays Int?
checkRassDate DateTime? checkRassDate DateTime?
cashInvoiceExpiresAt DateTime? cashInvoiceExpiresAt DateTime?
/// زمان ارسال یادآوری پایان مهلت پرداخت تا دوباره ارسال نشود
cashReminderSentAt DateTime?
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
user Customer @relation(fields: [userId], references: [id], onDelete: Restrict) user Customer @relation(fields: [userId], references: [id], onDelete: Restrict)
company Company @relation(fields: [companyId], references: [id], onDelete: Restrict) company Company @relation(fields: [companyId], references: [id], onDelete: Restrict)
items OrderItem[] items OrderItem[]
checks Check[] checks Check[]
cashReceipt CashPaymentReceipt? cashReceipt CashPaymentReceipt?
shipment Shipment? shipment Shipment?
statusHistory OrderStatusHistory[] statusHistory OrderStatusHistory[]
notifications CustomerNotification[]
@@index([userId]) @@index([userId])
@@index([companyId]) @@index([companyId])
@@index([status]) @@index([status])
@@index([createdAt]) @@index([createdAt])
@@index([status, cashInvoiceExpiresAt])
} }
model OrderItem { model OrderItem {
@@ -393,6 +400,7 @@ model SystemSettings {
defaultSettlementCheck Boolean @default(true) defaultSettlementCheck Boolean @default(true)
defaultSettlementCash Boolean @default(true) defaultSettlementCash Boolean @default(true)
smsOrderRegistered Boolean @default(true) smsOrderRegistered Boolean @default(true)
smsPricingReady Boolean @default(true)
smsPaymentReminder Boolean @default(true) smsPaymentReminder Boolean @default(true)
smsCheckReview Boolean @default(true) smsCheckReview Boolean @default(true)
smsShippingUpdate Boolean @default(true) smsShippingUpdate Boolean @default(true)
@@ -411,6 +419,26 @@ model AdminNotification {
sentAt DateTime @default(now()) sentAt DateTime @default(now())
} }
model CustomerNotification {
id String @id @default(cuid())
customerId String
orderId String?
event String
title String
body String
tone NotificationTone? @default(info)
link String?
isRead Boolean @default(false)
smsSent Boolean @default(false)
sentAt DateTime @default(now())
customer Customer @relation(fields: [customerId], references: [id], onDelete: Cascade)
order Order? @relation(fields: [orderId], references: [id], onDelete: SetNull)
@@index([customerId, isRead])
@@index([sentAt])
}
model InternalMessage { model InternalMessage {
id String @id @default(cuid()) id String @id @default(cuid())
senderName String senderName String
+2 -2
View File
@@ -91,7 +91,7 @@ async function main() {
description: 'تولیدکننده پارچه‌های باکیفیت', description: 'تولیدکننده پارچه‌های باکیفیت',
isActive: true, isActive: true,
displayOrder: 1, displayOrder: 1,
maxMeteragePerInvoice: 5000, maxMeteragePerInvoice: null,
cashInvoiceValidityHours: 8, cashInvoiceValidityHours: 8,
cashInvoiceValidUntilHour: 17, cashInvoiceValidUntilHour: 17,
}, },
@@ -102,7 +102,7 @@ async function main() {
description: 'عرضه پارچه‌های تخصصی', description: 'عرضه پارچه‌های تخصصی',
isActive: true, isActive: true,
displayOrder: 2, displayOrder: 2,
maxMeteragePerInvoice: 5000, maxMeteragePerInvoice: null,
cashInvoiceValidityHours: 8, cashInvoiceValidityHours: 8,
cashInvoiceValidUntilHour: 17, cashInvoiceValidUntilHour: 17,
}, },
+308 -113
View File
@@ -1,6 +1,7 @@
/** /**
* تست دود جریان کامل سفارش: ورود مشتری، سبد، ثبت چکی و نقدی، * تست دود گردش کامل سفارش مطابق روال کارفرما:
* قیمت‌گذاری ادمین، رسید نقدی و بررسی وضعیت‌ها. * مشتری بدون دیدن قیمت سفارش می‌دهد، ادمین قیمت می‌گذارد و فاکتور می‌فرستد،
* مشتری روش تسویه را انتخاب می‌کند و مسیر نقدی/چکی تا تایید نهایی پیش می‌رود.
* اجرا: node scripts/smoke-order-flow.mjs * اجرا: node scripts/smoke-order-flow.mjs
*/ */
const BASE = process.env.SMOKE_BASE_URL || 'http://127.0.0.1:3999/api'; const BASE = process.env.SMOKE_BASE_URL || 'http://127.0.0.1:3999/api';
@@ -41,9 +42,8 @@ async function api(path, { method = 'GET', body, token } = {}) {
return { status: res.status, data }; return { status: res.status, data };
} }
function jalaliIsoInDays(days) { function isoInDays(days) {
const d = new Date(Date.now() + days * 86400000); return new Date(Date.now() + days * 86400000).toISOString().slice(0, 10);
return d.toISOString().slice(0, 10);
} }
/** تصویر base64 بزرگ‌تر از سقف پیش‌فرض ۱۰۰KB اکسپرس */ /** تصویر base64 بزرگ‌تر از سقف پیش‌فرض ۱۰۰KB اکسپرس */
@@ -51,6 +51,22 @@ function bigBase64(kb) {
return `data:image/jpeg;base64,${'A'.repeat(kb * 1024)}`; return `data:image/jpeg;base64,${'A'.repeat(kb * 1024)}`;
} }
function checkPayload(amount, days, extra = {}) {
return {
amount,
dueDate: isoInDays(days),
sayadId: '1234567890123456',
checkNumber: '123456',
ownerNationalId: '0012345678',
ownerFirstName: 'علی',
ownerLastName: 'رضایی',
bankName: 'ملی',
branchName: 'مرکزی',
mode: 'full',
...extra,
};
}
async function main() { async function main() {
console.log(`\n== Smoke test @ ${BASE} ==\n`); console.log(`\n== Smoke test @ ${BASE} ==\n`);
@@ -65,7 +81,7 @@ async function main() {
const badVerify = await api('/auth/otp/verify', { const badVerify = await api('/auth/otp/verify', {
method: 'POST', method: 'POST',
body: { phone: CUSTOMER_PHONE, code: '0000' === code ? '1111' : '0000' }, body: { phone: CUSTOMER_PHONE, code: String(code) === '0000' ? '1111' : '0000' },
}); });
check('کد اشتباه رد می‌شود', badVerify.status === 401, `status=${badVerify.status}`); check('کد اشتباه رد می‌شود', badVerify.status === 401, `status=${badVerify.status}`);
@@ -91,10 +107,15 @@ async function main() {
const noToken = await api('/orders'); const noToken = await api('/orders');
check('بدون توکن ۴۰۱', noToken.status === 401, `status=${noToken.status}`); check('بدون توکن ۴۰۱', noToken.status === 401, `status=${noToken.status}`);
console.log('\n[3] سبد خرید'); console.log('\n[3] سبد خرید بدون قیمت');
const companies = await api('/companies/public'); const companies = await api('/companies/public');
const company = companies.data?.[0]; const company = companies.data?.[0];
check('لیست شرکت‌ها', !!company, JSON.stringify(companies.data).slice(0, 120)); check('لیست شرکت‌ها', !!company, JSON.stringify(companies.data).slice(0, 120));
check(
'سقف متراژ موثر برگردانده می‌شود',
typeof company?.maxMeteragePerInvoice === 'number' && company.maxMeteragePerInvoice > 0,
`max=${company?.maxMeteragePerInvoice}`,
);
const products = await api(`/products?companyId=${company.id}`, { token: userToken }); const products = await api(`/products?companyId=${company.id}`, { token: userToken });
const product = (Array.isArray(products.data) ? products.data : products.data?.items)?.find( const product = (Array.isArray(products.data) ? products.data : products.data?.items)?.find(
@@ -110,89 +131,63 @@ async function main() {
body: { productId: product.id, meterage }, body: { productId: product.id, meterage },
}); });
check('افزودن به سبد', addItem.status < 300, `status=${addItem.status} ${JSON.stringify(addItem.data).slice(0, 120)}`); check('افزودن به سبد', addItem.status < 300, `status=${addItem.status} ${JSON.stringify(addItem.data).slice(0, 120)}`);
const cartItem = addItem.data?.items?.[0];
check('سبد قیمت واحد ندارد', cartItem?.unitPrice === undefined, `unitPrice=${cartItem?.unitPrice}`);
console.log('\n[4] ثبت سفارش چکی با تصویر بزرگ'); console.log('\n[4] ثبت سفارش بدون روش تسویه و بدون قیمت');
const checkOrder = await api('/orders', {
method: 'POST',
token: userToken,
body: {
companyId: company.id,
settlementType: 'check',
checkMode: 'full',
items: [{ productId: product.id, meterage }],
checks: [
{
amount: 2000000,
dueDate: jalaliIsoInDays(30),
sayadId: '1234567890123456',
checkNumber: '123456',
ownerNationalId: '0012345678',
ownerFirstName: 'علی',
ownerLastName: 'رضایی',
bankName: 'ملی',
branchName: 'مرکزی',
frontImage: bigBase64(300),
backImage: bigBase64(300),
mode: 'full',
},
],
},
});
check(
'ثبت سفارش چکی (بدنه ~۶۰۰KB)',
checkOrder.status < 300,
`status=${checkOrder.status} ${JSON.stringify(checkOrder.data).slice(0, 200)}`,
);
const checkOrderId = checkOrder.data?.id;
check('راس چک محاسبه شد', checkOrder.data?.checkRassDays === 30, `rass=${checkOrder.data?.checkRassDays}`);
console.log('\n[5] فیلدهای اضافه باید رد شوند');
const extraField = await api('/orders', {
method: 'POST',
token: userToken,
body: {
companyId: company.id,
settlementType: 'cash',
items: [{ productId: product.id, meterage }],
bogusField: 'x',
},
});
check('فیلد ناشناخته ۴۰۰ می‌دهد', extraField.status === 400, `status=${extraField.status}`);
console.log('\n[6] ثبت سفارش نقدی');
await api('/carts/items', {
method: 'POST',
token: userToken,
body: { productId: product.id, meterage },
});
const cashOrder = await api('/orders', { const cashOrder = await api('/orders', {
method: 'POST',
token: userToken,
body: { companyId: company.id, items: [{ productId: product.id, meterage }] },
});
check('ثبت سفارش', cashOrder.status < 300, `status=${cashOrder.status} ${JSON.stringify(cashOrder.data).slice(0, 200)}`);
const cashOrderId = cashOrder.data?.id;
check('وضعیت اولیه در انتظار بررسی', cashOrder.data?.status === 'awaiting_review', `status=${cashOrder.data?.status}`);
check('روش تسویه خالی است', cashOrder.data?.settlementType == null, `settlement=${cashOrder.data?.settlementType}`);
check('مبلغ برای مشتری پنهان است', cashOrder.data?.finalAmount == null && cashOrder.data?.totalAmount == null);
check('پرچم pricesVisible خاموش است', cashOrder.data?.pricesVisible === false, `flag=${cashOrder.data?.pricesVisible}`);
check(
'قیمت واحد آیتم‌ها پنهان است',
(cashOrder.data?.items ?? []).every((i) => i.unitPrice == null),
);
console.log('\n[5] اعتبارسنجی ورودی');
const extraField = await api('/orders', {
method: 'POST',
token: userToken,
body: { companyId: company.id, items: [{ productId: product.id, meterage }], bogusField: 'x' },
});
check('فیلد ناشناخته ۴۰۰ می‌دهد', extraField.status === 400, `status=${extraField.status}`);
const earlySettlement = await api(`/orders/${cashOrderId}/settlement`, {
method: 'POST',
token: userToken,
body: { settlementType: 'cash' },
});
check('انتخاب تسویه قبل از فاکتور رد می‌شود', earlySettlement.status === 400, `status=${earlySettlement.status}`);
const earlyReceipt = await api(`/orders/${cashOrderId}/cash-receipt`, {
method: 'POST', method: 'POST',
token: userToken, token: userToken,
body: { body: {
companyId: company.id, amount: 1000,
settlementType: 'cash', transferDate: isoInDays(0),
items: [{ productId: product.id, meterage }], transferTime: '12:00',
trackingNumber: '1',
destinationAccountId: 'x',
}, },
}); });
check('ثبت سفارش نقدی', cashOrder.status < 300, `status=${cashOrder.status} ${JSON.stringify(cashOrder.data).slice(0, 200)}`); check('رسید قبل از فاکتور رد می‌شود', earlyReceipt.status === 400, `status=${earlyReceipt.status}`);
const cashOrderId = cashOrder.data?.id;
const expiry = cashOrder.data?.cashInvoiceExpiresAt;
check('انقضای فاکتور در آینده است', !!expiry && new Date(expiry) > new Date(), `expiry=${expiry}`);
console.log('\n[7] سفارش در لیست مشتری دیده می‌شود'); console.log('\n[6] سطل‌های وضعیت ادمین');
const myOrders = await api('/orders', { token: userToken });
const ids = (myOrders.data ?? []).map((o) => o.id);
check('سفارش چکی در لیست', ids.includes(checkOrderId));
check('سفارش نقدی در لیست', ids.includes(cashOrderId));
console.log('\n[8] لیست و فیلتر ادمین');
const adminOrders = await api('/admin/orders', { token: adminToken }); const adminOrders = await api('/admin/orders', { token: adminToken });
const adminIds = (adminOrders.data ?? []).map((o) => o.id); const adminIds = (adminOrders.data ?? []).map((o) => o.id);
check('هر دو سفارش برای ادمین', adminIds.includes(checkOrderId) && adminIds.includes(cashOrderId)); check('سفارش برای ادمین دیده می‌شود', adminIds.includes(cashOrderId));
const adminStatuses = new Set((adminOrders.data ?? []).map((o) => o.adminStatus));
const allowedTabs = new Set([ const allowedTabs = new Set([
'pending_review', 'pending_review',
'awaiting_customer',
'pending_check', 'pending_check',
'pending_payment',
'needs_correction', 'needs_correction',
'check_correction', 'check_correction',
'final_approval', 'final_approval',
@@ -202,20 +197,35 @@ async function main() {
'rejected', 'rejected',
'expired', 'expired',
]); ]);
const unknown = [...adminStatuses].filter((s) => !allowedTabs.has(s)); const unknown = [...new Set((adminOrders.data ?? []).map((o) => o.adminStatus))].filter(
(s) => !allowedTabs.has(s),
);
check('همه وضعیت‌ها به تب معتبر نگاشت شده‌اند', unknown.length === 0, `unknown=${unknown.join(',')}`); check('همه وضعیت‌ها به تب معتبر نگاشت شده‌اند', unknown.length === 0, `unknown=${unknown.join(',')}`);
const filtered = await api('/admin/orders?status=pending_review', { token: adminToken }); const filtered = await api('/admin/orders?status=pending_review', { token: adminToken });
check( check(
'فیلتر «در انتظار بررسی» سفارش‌ها را برمی‌گرداند', 'فیلتر «در انتظار بررسی» سفارش را برمی‌گرداند',
(filtered.data ?? []).some((o) => o.id === cashOrderId), (filtered.data ?? []).some((o) => o.id === cashOrderId),
`count=${(filtered.data ?? []).length}`, `count=${(filtered.data ?? []).length}`,
); );
console.log('\n[9] قیمت‌گذاری ادمین'); console.log('\n[7] قیمت‌گذاری ادمین');
const detail = await api(`/admin/orders/${cashOrderId}`, { token: adminToken }); const detail = await api(`/admin/orders/${cashOrderId}`, { token: adminToken });
const items = detail.data?.items ?? []; const items = detail.data?.items ?? [];
check('سفارش آیتم دارد', items.length > 0); check('سفارش آیتم دارد', items.length > 0);
check(
'ادمین گذرهای مجاز را می‌بیند',
Array.isArray(detail.data?.allowedTransitions) && detail.data.allowedTransitions.includes('pricing'),
`transitions=${JSON.stringify(detail.data?.allowedTransitions)}`,
);
const earlyInvoice = await api(`/admin/orders/${cashOrderId}/status`, {
method: 'PATCH',
token: adminToken,
body: { status: 'awaiting_settlement' },
});
check('ارسال فاکتور بدون قیمت‌گذاری رد می‌شود', earlyInvoice.status === 400, `status=${earlyInvoice.status}`);
const pricing = await api(`/admin/orders/${cashOrderId}/pricing`, { const pricing = await api(`/admin/orders/${cashOrderId}/pricing`, {
method: 'PATCH', method: 'PATCH',
token: adminToken, token: adminToken,
@@ -225,17 +235,13 @@ async function main() {
}, },
}); });
const expectedTotal = 100000 * meterage; const expectedTotal = 100000 * meterage;
const expectedFinal = expectedTotal + 50000;
check('قیمت‌گذاری موفق', pricing.status < 300, `status=${pricing.status} ${JSON.stringify(pricing.data).slice(0, 150)}`); check('قیمت‌گذاری موفق', pricing.status < 300, `status=${pricing.status} ${JSON.stringify(pricing.data).slice(0, 150)}`);
check( check('جمع کل درست است', Math.abs((pricing.data?.totalAmount ?? 0) - expectedTotal) < 0.01, `total=${pricing.data?.totalAmount}`);
'جمع کل درست است', check('مبلغ نهایی = جمع + حمل', Math.abs((pricing.data?.finalAmount ?? 0) - expectedFinal) < 0.01, `final=${pricing.data?.finalAmount}`);
Math.abs((pricing.data?.totalAmount ?? 0) - expectedTotal) < 0.01,
`total=${pricing.data?.totalAmount} expected=${expectedTotal}`, const stillHidden = await api(`/orders/${cashOrderId}`, { token: userToken });
); check('قیمت تا ارسال فاکتور برای مشتری پنهان است', stillHidden.data?.finalAmount == null, `final=${stillHidden.data?.finalAmount}`);
check(
'مبلغ نهایی = جمع + حمل',
Math.abs((pricing.data?.finalAmount ?? 0) - (expectedTotal + 50000)) < 0.01,
`final=${pricing.data?.finalAmount}`,
);
const badDiscount = await api(`/admin/orders/${cashOrderId}/pricing`, { const badDiscount = await api(`/admin/orders/${cashOrderId}/pricing`, {
method: 'PATCH', method: 'PATCH',
@@ -244,7 +250,64 @@ async function main() {
}); });
check('تخفیف بیشتر از قیمت رد می‌شود', badDiscount.status === 400, `status=${badDiscount.status}`); check('تخفیف بیشتر از قیمت رد می‌شود', badDiscount.status === 400, `status=${badDiscount.status}`);
console.log('\n[10] رسید پرداخت نقدی'); console.log('\n[8] ارسال فاکتور برای مشتری');
await api(`/admin/orders/${cashOrderId}/pricing`, {
method: 'PATCH',
token: adminToken,
body: {
items: [{ id: items[0].id, confirmedMeterage: meterage, unitPrice: 100000, unitDiscount: 0 }],
shippingCost: 50000,
},
});
const invoice = await api(`/admin/orders/${cashOrderId}/status`, {
method: 'PATCH',
token: adminToken,
body: { status: 'awaiting_settlement', note: 'فاکتور نهایی شد' },
});
check('ارسال فاکتور', invoice.status < 300, `status=${invoice.status} ${JSON.stringify(invoice.data).slice(0, 150)}`);
const priced = await api(`/orders/${cashOrderId}`, { token: userToken });
check('مشتری مبلغ فاکتور را می‌بیند', Math.abs((priced.data?.finalAmount ?? 0) - expectedFinal) < 0.01, `final=${priced.data?.finalAmount}`);
check('pricesVisible روشن شد', priced.data?.pricesVisible !== false);
const illegal = await api(`/admin/orders/${cashOrderId}/status`, {
method: 'PATCH',
token: adminToken,
body: { status: 'delivered' },
});
check('گذر غیرمجاز بدون force رد می‌شود', illegal.status === 400, `status=${illegal.status}`);
const forceNoReason = await api(`/admin/orders/${cashOrderId}/status`, {
method: 'PATCH',
token: adminToken,
body: { status: 'delivered', force: true },
});
check('force بدون دلیل رد می‌شود', forceNoReason.status === 400, `status=${forceNoReason.status}`);
console.log('\n[9] اعلان مشتری');
const notifs = await api('/notifications', { token: userToken });
const invoiceNotif = (notifs.data ?? []).find((n) => n.event === 'invoice_priced');
check('اعلان صدور فاکتور ساخته شد', !!invoiceNotif, JSON.stringify(notifs.data)?.slice(0, 200));
check('اعلان به سفارش لینک دارد', invoiceNotif?.link === `/orders/${cashOrderId}`, `link=${invoiceNotif?.link}`);
const unread = await api('/notifications/unread-count', { token: userToken });
check('شمارنده خوانده‌نشده مثبت است', (unread.data?.count ?? 0) > 0, `count=${unread.data?.count}`);
if (invoiceNotif) {
const marked = await api(`/notifications/${invoiceNotif.id}/read`, { method: 'PATCH', token: userToken });
check('علامت خوانده‌شده', marked.status < 300 && marked.data?.count < (unread.data?.count ?? 0), JSON.stringify(marked.data));
}
const readAll = await api('/notifications/read-all', { method: 'PATCH', token: userToken });
check('خواندن همه', readAll.status < 300 && (readAll.data?.count ?? -1) === 0, JSON.stringify(readAll.data));
console.log('\n[10] مسیر نقدی');
const chooseCash = await api(`/orders/${cashOrderId}/settlement`, {
method: 'POST',
token: userToken,
body: { settlementType: 'cash' },
});
check('انتخاب تسویه نقدی', chooseCash.status < 300, `status=${chooseCash.status} ${JSON.stringify(chooseCash.data).slice(0, 150)}`);
check('وضعیت در انتظار پرداخت', chooseCash.data?.status === 'awaiting_cash_payment', `status=${chooseCash.data?.status}`);
const expiry = chooseCash.data?.cashInvoiceExpiresAt;
check('انقضای فاکتور در آینده است', !!expiry && new Date(expiry) > new Date(), `expiry=${expiry}`);
const accounts = await api(`/companies/${company.id}/bank-accounts`, { token: userToken }); const accounts = await api(`/companies/${company.id}/bank-accounts`, { token: userToken });
const account = (accounts.data ?? [])[0]; const account = (accounts.data ?? [])[0];
check('حساب بانکی موجود', !!account, JSON.stringify(accounts.data).slice(0, 120)); check('حساب بانکی موجود', !!account, JSON.stringify(accounts.data).slice(0, 120));
@@ -253,8 +316,8 @@ async function main() {
method: 'POST', method: 'POST',
token: userToken, token: userToken,
body: { body: {
amount: expectedTotal + 50000, amount: expectedFinal,
transferDate: jalaliIsoInDays(0), transferDate: isoInDays(0),
transferTime: '12:00', transferTime: '12:00',
trackingNumber: '987654321', trackingNumber: '987654321',
destinationAccountId: account.id, destinationAccountId: account.id,
@@ -262,15 +325,151 @@ async function main() {
}, },
}); });
check('ثبت رسید (تصویر ۴۰۰KB)', receipt.status < 300, `status=${receipt.status} ${JSON.stringify(receipt.data).slice(0, 200)}`); check('ثبت رسید (تصویر ۴۰۰KB)', receipt.status < 300, `status=${receipt.status} ${JSON.stringify(receipt.data).slice(0, 200)}`);
const afterReceipt = await api(`/orders/${cashOrderId}`, { token: userToken }); const afterReceipt = await api(`/orders/${cashOrderId}`, { token: userToken });
check('وضعیت به تأیید پرداخت رفت', afterReceipt.data?.status === 'awaiting_cash_confirmation', `status=${afterReceipt.data?.status}`);
const receipts = await api('/admin/cash-payments', { token: adminToken });
const pendingReceipt = (receipts.data ?? []).find((r) => r.orderId === cashOrderId);
check('رسید در صف ادمین است', !!pendingReceipt, JSON.stringify(receipts.data)?.slice(0, 160));
const rejectReceipt = await api(`/admin/cash-payments/${pendingReceipt.id}/status`, {
method: 'PATCH',
token: adminToken,
body: { status: 'rejected', note: 'مبلغ نامطابق' },
});
check('رد رسید', rejectReceipt.status < 300, `status=${rejectReceipt.status}`);
const afterReject = await api(`/orders/${cashOrderId}`, { token: userToken });
check( check(
'وضعیت به تأیید پرداخت رفت', 'رد رسید مشتری را به مرحله پرداخت برمی‌گرداند',
afterReceipt.data?.status === 'awaiting_cash_confirmation', afterReject.data?.status === 'awaiting_cash_payment',
`status=${afterReceipt.data?.status}`, `status=${afterReject.data?.status}`,
); );
console.log('\n[11] دسترسی متقاطع بین مشتری‌ها'); const approveReceipt = await api(`/admin/cash-payments/${pendingReceipt.id}/status`, {
method: 'PATCH',
token: adminToken,
body: { status: 'approved' },
});
check('تأیید رسید', approveReceipt.status < 300, `status=${approveReceipt.status}`);
const afterApprove = await api(`/orders/${cashOrderId}`, { token: userToken });
check('وضعیت پرداخت تأییدشده', afterApprove.data?.status === 'cash_confirmed', `status=${afterApprove.data?.status}`);
const finalApprove = await api(`/admin/orders/${cashOrderId}/status`, {
method: 'PATCH',
token: adminToken,
body: { status: 'final_approved' },
});
check('تأیید نهایی سفارش نقدی', finalApprove.status < 300, `status=${finalApprove.status}`);
console.log('\n[11] مسیر چکی با حلقه اصلاح');
await api('/carts', { method: 'DELETE', token: userToken });
await api('/carts/items', {
method: 'POST',
token: userToken,
body: { productId: product.id, meterage },
});
const checkOrder = await api('/orders', {
method: 'POST',
token: userToken,
body: { companyId: company.id, items: [{ productId: product.id, meterage }] },
});
const checkOrderId = checkOrder.data?.id;
check('ثبت سفارش دوم', checkOrder.status < 300, `status=${checkOrder.status}`);
const checkDetail = await api(`/admin/orders/${checkOrderId}`, { token: adminToken });
const checkItem = checkDetail.data?.items?.[0];
await api(`/admin/orders/${checkOrderId}/pricing`, {
method: 'PATCH',
token: adminToken,
body: { items: [{ id: checkItem.id, confirmedMeterage: meterage, unitPrice: 100000 }] },
});
await api(`/admin/orders/${checkOrderId}/status`, {
method: 'PATCH',
token: adminToken,
body: { status: 'awaiting_settlement' },
});
const chooseCheck = await api(`/orders/${checkOrderId}/settlement`, {
method: 'POST',
token: userToken,
body: { settlementType: 'check', checkMode: 'full' },
});
check('انتخاب تسویه چکی', chooseCheck.status < 300, `status=${chooseCheck.status} ${JSON.stringify(chooseCheck.data).slice(0, 150)}`);
check('وضعیت در حال ثبت چک', chooseCheck.data?.status === 'check_in_progress', `status=${chooseCheck.data?.status}`);
const submitChecks = await api(`/orders/${checkOrderId}/checks`, {
method: 'PATCH',
token: userToken,
body: {
checkMode: 'full',
checks: [
checkPayload(expectedTotal, 30, {
frontImage: bigBase64(300),
backImage: bigBase64(300),
}),
],
},
});
check('ارسال چک‌ها (بدنه ~۶۰۰KB)', submitChecks.status < 300, `status=${submitChecks.status} ${JSON.stringify(submitChecks.data).slice(0, 200)}`);
check('وضعیت بررسی چک', submitChecks.data?.status === 'awaiting_check_review', `status=${submitChecks.data?.status}`);
check('راس چک محاسبه شد', submitChecks.data?.checkRassDays === 30, `rass=${submitChecks.data?.checkRassDays}`);
const orderChecks = await api(`/admin/orders/${checkOrderId}/checks`, { token: adminToken });
const firstCheck = (orderChecks.data ?? [])[0];
check('چک برای ادمین دیده می‌شود', !!firstCheck, JSON.stringify(orderChecks.data)?.slice(0, 160));
const rejectCheck = await api(`/admin/checks/${firstCheck.id}/status`, {
method: 'PATCH',
token: adminToken,
body: { status: 'rejected', note: 'تصویر ناخوانا' },
});
check('رد چک', rejectCheck.status < 300, `status=${rejectCheck.status}`);
const afterCheckReject = await api(`/orders/${checkOrderId}`, { token: userToken });
check(
'رد چک به اصلاح می‌رود نه بن‌بست',
afterCheckReject.data?.status === 'check_needs_fix',
`status=${afterCheckReject.data?.status}`,
);
const fixChecks = await api(`/orders/${checkOrderId}/checks`, {
method: 'PATCH',
token: userToken,
body: { checkMode: 'full', checks: [checkPayload(expectedTotal, 45)] },
});
check('اصلاح چک‌ها', fixChecks.status < 300, `status=${fixChecks.status} ${JSON.stringify(fixChecks.data).slice(0, 200)}`);
check('بازگشت به بررسی چک', fixChecks.data?.status === 'awaiting_check_review', `status=${fixChecks.data?.status}`);
const fixedChecks = await api(`/admin/orders/${checkOrderId}/checks`, { token: adminToken });
const approveCheck = await api(`/admin/checks/${fixedChecks.data[0].id}/status`, {
method: 'PATCH',
token: adminToken,
body: { status: 'approved' },
});
check('تأیید چک', approveCheck.status < 300, `status=${approveCheck.status}`);
const afterCheckApprove = await api(`/orders/${checkOrderId}`, { token: userToken });
check('وضعیت تأیید چک‌ها', afterCheckApprove.data?.status === 'checks_approved', `status=${afterCheckApprove.data?.status}`);
console.log('\n[12] تنظیمات سیستم');
const settings = await api('/admin/settings', { token: adminToken });
check('خواندن تنظیمات', settings.status < 300 && typeof settings.data?.maxMeteragePerInvoice === 'number', JSON.stringify(settings.data)?.slice(0, 160));
const savedSettings = await api('/admin/settings', {
method: 'PATCH',
token: adminToken,
body: { smsPricingReady: false, maxMeteragePerInvoice: 6000 },
});
check('ذخیره تنظیمات', savedSettings.data?.maxMeteragePerInvoice === 6000 && savedSettings.data?.smsPricingReady === false, JSON.stringify(savedSettings.data)?.slice(0, 200));
const companyAfter = await api('/companies/public');
check(
'سقف متراژ شرکت از تنظیمات ارث می‌برد',
companyAfter.data?.[0]?.maxMeteragePerInvoice === 6000,
`max=${companyAfter.data?.[0]?.maxMeteragePerInvoice}`,
);
await api('/admin/settings', {
method: 'PATCH',
token: adminToken,
body: { smsPricingReady: true, maxMeteragePerInvoice: 5000 },
});
console.log('\n[13] دسترسی متقاطع بین مشتری‌ها');
const otherPhone = '09129999001'; const otherPhone = '09129999001';
await api('/auth/otp/request', { await api('/auth/otp/request', {
method: 'POST', method: 'POST',
@@ -285,31 +484,27 @@ async function main() {
if (otherToken) { if (otherToken) {
const foreign = await api(`/orders/${cashOrderId}`, { token: otherToken }); const foreign = await api(`/orders/${cashOrderId}`, { token: otherToken });
check('سفارش دیگری قابل خواندن نیست', foreign.status === 403, `status=${foreign.status}`); check('سفارش دیگری قابل خواندن نیست', foreign.status === 403, `status=${foreign.status}`);
const foreignReceipt = await api(`/orders/${cashOrderId}/cash-receipt`, { const foreignSettlement = await api(`/orders/${checkOrderId}/settlement`, {
method: 'POST', method: 'POST',
token: otherToken, token: otherToken,
body: { body: { settlementType: 'cash' },
amount: 1000,
transferDate: jalaliIsoInDays(0),
transferTime: '12:00',
trackingNumber: '1',
destinationAccountId: account.id,
},
}); });
check('رسید روی سفارش دیگری رد می‌شود', foreignReceipt.status === 403, `status=${foreignReceipt.status}`); check('انتخاب تسویه روی سفارش دیگری رد می‌شود', foreignSettlement.status === 403, `status=${foreignSettlement.status}`);
const foreignNotifs = await api('/notifications', { token: otherToken });
check('اعلان‌ها بین مشتری‌ها جدا است', (foreignNotifs.data ?? []).length === 0, `count=${(foreignNotifs.data ?? []).length}`);
} }
console.log('\n[12] محافظت از ویرایش چک بعد از تأیید'); console.log('\n[14] محافظت از ویرایش بعد از تحویل');
const approve = await api(`/admin/orders/${checkOrderId}/status`, { const forceDeliver = await api(`/admin/orders/${checkOrderId}/status`, {
method: 'PATCH', method: 'PATCH',
token: adminToken, token: adminToken,
body: { status: 'delivered' }, body: { status: 'delivered', force: true, reason: 'تحویل دستی برای تست' },
}); });
check('تغییر وضعیت توسط ادمین', approve.status < 300, `status=${approve.status}`); check('گذر دستی با دلیل پذیرفته می‌شود', forceDeliver.status < 300, `status=${forceDeliver.status} ${JSON.stringify(forceDeliver.data).slice(0, 150)}`);
const lateEdit = await api(`/orders/${checkOrderId}/checks`, { const lateEdit = await api(`/orders/${checkOrderId}/checks`, {
method: 'PATCH', method: 'PATCH',
token: userToken, token: userToken,
body: { checks: [{ amount: 1, dueDate: jalaliIsoInDays(10), mode: 'brief' }] }, body: { checks: [checkPayload(1000, 10)] },
}); });
check('ویرایش چک بعد از تحویل رد می‌شود', lateEdit.status === 400, `status=${lateEdit.status}`); check('ویرایش چک بعد از تحویل رد می‌شود', lateEdit.status === 400, `status=${lateEdit.status}`);
const lateReceipt = await api(`/orders/${checkOrderId}/cash-receipt`, { const lateReceipt = await api(`/orders/${checkOrderId}/cash-receipt`, {
@@ -317,7 +512,7 @@ async function main() {
token: userToken, token: userToken,
body: { body: {
amount: 1000, amount: 1000,
transferDate: jalaliIsoInDays(0), transferDate: isoInDays(0),
transferTime: '12:00', transferTime: '12:00',
trackingNumber: '1', trackingNumber: '1',
destinationAccountId: account.id, destinationAccountId: account.id,
+4
View File
@@ -1,6 +1,7 @@
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { APP_GUARD } from '@nestjs/core'; import { APP_GUARD } from '@nestjs/core';
import { ConfigModule } from '@nestjs/config'; import { ConfigModule } from '@nestjs/config';
import { ScheduleModule } from '@nestjs/schedule';
import { PrismaModule } from './prisma/prisma.module'; import { PrismaModule } from './prisma/prisma.module';
import { AuthModule } from './auth/auth.module'; import { AuthModule } from './auth/auth.module';
import { JwtAuthGuard } from './common/guards/jwt-auth.guard'; import { JwtAuthGuard } from './common/guards/jwt-auth.guard';
@@ -16,10 +17,12 @@ import { ShipmentsModule } from './shipments/shipments.module';
import { DashboardModule } from './dashboard/dashboard.module'; import { DashboardModule } from './dashboard/dashboard.module';
import { SettingsModule } from './settings/settings.module'; import { SettingsModule } from './settings/settings.module';
import { CartsModule } from './carts/carts.module'; import { CartsModule } from './carts/carts.module';
import { NotificationsModule } from './notifications/notifications.module';
@Module({ @Module({
imports: [ imports: [
ConfigModule.forRoot({ isGlobal: true }), ConfigModule.forRoot({ isGlobal: true }),
ScheduleModule.forRoot(),
PrismaModule, PrismaModule,
AuthModule, AuthModule,
CompaniesModule, CompaniesModule,
@@ -34,6 +37,7 @@ import { CartsModule } from './carts/carts.module';
DashboardModule, DashboardModule,
SettingsModule, SettingsModule,
CartsModule, CartsModule,
NotificationsModule,
], ],
providers: [ providers: [
{ {
-4
View File
@@ -47,7 +47,6 @@ export class CartsService {
data: { data: {
meterage: newMeterage, meterage: newMeterage,
notes: dto.notes?.trim() || existing.notes, notes: dto.notes?.trim() || existing.notes,
unitPrice: product.basePrice,
}, },
}); });
} else { } else {
@@ -60,7 +59,6 @@ export class CartsService {
productCode: product.code, productCode: product.code,
color: product.color, color: product.color,
meterage: dto.meterage, meterage: dto.meterage,
unitPrice: product.basePrice,
notes: dto.notes?.trim() || null, notes: dto.notes?.trim() || null,
companyId: product.companyId, companyId: product.companyId,
companyName: product.company.name, companyName: product.company.name,
@@ -188,7 +186,6 @@ export class CartsService {
productCode: string; productCode: string;
color: string; color: string;
meterage: number; meterage: number;
unitPrice: number | null;
notes: string | null; notes: string | null;
companyId: string; companyId: string;
companyName: string; companyName: string;
@@ -209,7 +206,6 @@ export class CartsService {
productCode: item.productCode, productCode: item.productCode,
color: item.color, color: item.color,
meterage: item.meterage, meterage: item.meterage,
unitPrice: item.unitPrice ?? undefined,
notes: item.notes ?? undefined, notes: item.notes ?? undefined,
companyId: item.companyId, companyId: item.companyId,
companyName: item.companyName, companyName: item.companyName,
@@ -1,8 +1,10 @@
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { CashPaymentsController } from './cash-payments.controller'; import { CashPaymentsController } from './cash-payments.controller';
import { CashPaymentsService } from './cash-payments.service'; import { CashPaymentsService } from './cash-payments.service';
import { NotificationsModule } from '../notifications/notifications.module';
@Module({ @Module({
imports: [NotificationsModule],
controllers: [CashPaymentsController], controllers: [CashPaymentsController],
providers: [CashPaymentsService], providers: [CashPaymentsService],
exports: [CashPaymentsService], exports: [CashPaymentsService],
+33 -7
View File
@@ -11,14 +11,10 @@ import {
UpdateCashPaymentStatusDto, UpdateCashPaymentStatusDto,
} from './dto/cash-payment.dto'; } from './dto/cash-payment.dto';
import { joinFullName } from '../common/utils/name'; import { joinFullName } from '../common/utils/name';
import { NotificationsService } from '../notifications/notifications.service';
/** بعد از تأیید پرداخت یا ارسال، رسید جدید نباید وضعیت را به عقب برگرداند. */ /** بعد از تأیید پرداخت یا ارسال، رسید جدید نباید وضعیت را به عقب برگرداند. */
const RECEIPT_SUBMITTABLE_STATUSES: OrderStatus[] = [ const RECEIPT_SUBMITTABLE_STATUSES: OrderStatus[] = [
OrderStatus.awaiting_review,
OrderStatus.pricing,
OrderStatus.needs_correction,
OrderStatus.awaiting_settlement,
OrderStatus.awaiting_settlement_fix,
OrderStatus.cash_in_progress, OrderStatus.cash_in_progress,
OrderStatus.cash_invoice_issued, OrderStatus.cash_invoice_issued,
OrderStatus.awaiting_cash_payment, OrderStatus.awaiting_cash_payment,
@@ -28,7 +24,10 @@ const RECEIPT_SUBMITTABLE_STATUSES: OrderStatus[] = [
@Injectable() @Injectable()
export class CashPaymentsService { export class CashPaymentsService {
constructor(private readonly prisma: PrismaService) {} constructor(
private readonly prisma: PrismaService,
private readonly notifications: NotificationsService,
) {}
async submitReceipt( async submitReceipt(
userId: string, userId: string,
@@ -112,6 +111,14 @@ export class CashPaymentsService {
return saved; return saved;
}); });
this.notifications.dispatchAdmin({
title: 'رسید پرداخت نقدی ثبت شد',
body: `مشتری برای سفارش ${order.orderNumber} رسید به مبلغ ${Math.round(
dto.amount,
).toLocaleString('fa-IR')} تومان ثبت کرد.`,
link: `/orders/${orderId}`,
});
return this.mapReceipt(receipt, order.orderNumber, ''); return this.mapReceipt(receipt, order.orderNumber, '');
} }
@@ -157,11 +164,12 @@ export class CashPaymentsService {
include: { order: { include: { user: true } } }, include: { order: { include: { user: true } } },
}); });
// رد رسید باید مشتری را به مرحله پرداخت برگرداند تا بتواند رسید تازه بفرستد.
let nextStatus: OrderStatus | null = null; let nextStatus: OrderStatus | null = null;
if (dto.status === CashPaymentStatus.approved) { if (dto.status === CashPaymentStatus.approved) {
nextStatus = OrderStatus.cash_confirmed; nextStatus = OrderStatus.cash_confirmed;
} else if (dto.status === CashPaymentStatus.rejected) { } else if (dto.status === CashPaymentStatus.rejected) {
nextStatus = OrderStatus.needs_correction; nextStatus = OrderStatus.awaiting_cash_payment;
} else if (dto.status === CashPaymentStatus.expired) { } else if (dto.status === CashPaymentStatus.expired) {
nextStatus = OrderStatus.expired; nextStatus = OrderStatus.expired;
} }
@@ -185,6 +193,24 @@ export class CashPaymentsService {
return result; return result;
}); });
if (dto.status === CashPaymentStatus.approved) {
this.notifications.dispatchOrderEvent(
receipt.orderId,
'receipt_approved',
);
} else if (dto.status === CashPaymentStatus.rejected) {
this.notifications.dispatchOrderEvent(
receipt.orderId,
'receipt_rejected',
{ note: dto.note ?? null },
);
} else if (dto.status === CashPaymentStatus.expired) {
this.notifications.dispatchOrderEvent(
receipt.orderId,
'cash_invoice_expired',
);
}
return this.mapReceipt( return this.mapReceipt(
updated, updated,
updated.order.orderNumber, updated.order.orderNumber,
+2
View File
@@ -1,8 +1,10 @@
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { ChecksController } from './checks.controller'; import { ChecksController } from './checks.controller';
import { ChecksService } from './checks.service'; import { ChecksService } from './checks.service';
import { NotificationsModule } from '../notifications/notifications.module';
@Module({ @Module({
imports: [NotificationsModule],
controllers: [ChecksController], controllers: [ChecksController],
providers: [ChecksService], providers: [ChecksService],
exports: [ChecksService], exports: [ChecksService],
+17 -4
View File
@@ -2,10 +2,14 @@ import { Injectable, NotFoundException } from '@nestjs/common';
import { CheckStatus, OrderStatus } from '@prisma/client'; import { CheckStatus, OrderStatus } from '@prisma/client';
import { PrismaService } from '../prisma/prisma.service'; import { PrismaService } from '../prisma/prisma.service';
import { UpdateCheckStatusDto } from './dto/check.dto'; import { UpdateCheckStatusDto } from './dto/check.dto';
import { NotificationsService } from '../notifications/notifications.service';
@Injectable() @Injectable()
export class ChecksService { export class ChecksService {
constructor(private readonly prisma: PrismaService) {} constructor(
private readonly prisma: PrismaService,
private readonly notifications: NotificationsService,
) {}
async findByOrder(orderId: string) { async findByOrder(orderId: string) {
const order = await this.prisma.order.findUnique({ const order = await this.prisma.order.findUnique({
@@ -27,6 +31,7 @@ export class ChecksService {
}); });
if (!check) throw new NotFoundException('چک یافت نشد'); if (!check) throw new NotFoundException('چک یافت نشد');
let notifyStatus: OrderStatus | null = null;
const updated = await this.prisma.$transaction(async (tx) => { const updated = await this.prisma.$transaction(async (tx) => {
const result = await tx.check.update({ const result = await tx.check.update({
where: { id }, where: { id },
@@ -48,13 +53,12 @@ export class ChecksService {
(c) => c.status === CheckStatus.rejected, (c) => c.status === CheckStatus.rejected,
); );
// رد چک باید قابل اصلاح بماند؛ check_rejected راه برگشتی برای مشتری ندارد.
let nextStatus: OrderStatus | null = null; let nextStatus: OrderStatus | null = null;
if (anyRejected) { if (anyRejected) {
nextStatus = OrderStatus.check_rejected; nextStatus = OrderStatus.check_needs_fix;
} else if (allApproved) { } else if (allApproved) {
nextStatus = OrderStatus.checks_approved; nextStatus = OrderStatus.checks_approved;
} else if (dto.status === CheckStatus.rejected) {
nextStatus = OrderStatus.check_needs_fix;
} }
if (nextStatus && nextStatus !== check.order.status) { if (nextStatus && nextStatus !== check.order.status) {
@@ -71,11 +75,20 @@ export class ChecksService {
note: dto.note || `وضعیت چک به ${dto.status} تغییر کرد`, note: dto.note || `وضعیت چک به ${dto.status} تغییر کرد`,
}, },
}); });
notifyStatus = nextStatus;
} }
return result; return result;
}); });
if (notifyStatus === OrderStatus.checks_approved) {
this.notifications.dispatchOrderEvent(check.orderId, 'checks_approved');
} else if (notifyStatus === OrderStatus.check_needs_fix) {
this.notifications.dispatchOrderEvent(check.orderId, 'checks_need_fix', {
note: dto.note ?? null,
});
}
return this.map(updated); return this.map(updated);
} }
+14
View File
@@ -0,0 +1,14 @@
/** وقتی نه شرکت و نه تنظیمات سیستم مقداری ندارند. */
export const FALLBACK_MAX_METERAGE = 5000;
/**
* سقف متراژ موثر هر فاکتور: مقدار شرکت، در نبودش تنظیمات سیستم.
*/
export function resolveMaxMeterage(
companyValue: number | null | undefined,
settingsValue: number | null | undefined,
): number {
if (companyValue != null && companyValue > 0) return companyValue;
if (settingsValue != null && settingsValue > 0) return settingsValue;
return FALLBACK_MAX_METERAGE;
}
+10 -8
View File
@@ -4,22 +4,24 @@ import { OrderStatus } from '@prisma/client';
export const ADMIN_STATUS_MAP: Record<string, OrderStatus[]> = { export const ADMIN_STATUS_MAP: Record<string, OrderStatus[]> = {
pending_review: [ pending_review: [
OrderStatus.cart, OrderStatus.cart,
OrderStatus.awaiting_settlement,
OrderStatus.cash_in_progress,
OrderStatus.check_in_progress,
OrderStatus.submitted, OrderStatus.submitted,
OrderStatus.awaiting_review, OrderStatus.awaiting_review,
OrderStatus.pricing, OrderStatus.pricing,
],
awaiting_customer: [
OrderStatus.awaiting_settlement,
OrderStatus.awaiting_settlement_fix,
OrderStatus.check_in_progress,
OrderStatus.cash_in_progress,
OrderStatus.cash_invoice_issued, OrderStatus.cash_invoice_issued,
OrderStatus.awaiting_cash_payment, OrderStatus.awaiting_cash_payment,
],
pending_check: [OrderStatus.awaiting_check_review],
pending_payment: [
OrderStatus.cash_payment_submitted, OrderStatus.cash_payment_submitted,
OrderStatus.awaiting_cash_confirmation, OrderStatus.awaiting_cash_confirmation,
], ],
pending_check: [OrderStatus.awaiting_check_review], needs_correction: [OrderStatus.needs_correction],
needs_correction: [
OrderStatus.needs_correction,
OrderStatus.awaiting_settlement_fix,
],
check_correction: [OrderStatus.check_needs_fix, OrderStatus.check_rejected], check_correction: [OrderStatus.check_needs_fix, OrderStatus.check_rejected],
final_approval: [ final_approval: [
OrderStatus.final_approved, OrderStatus.final_approved,
+101
View File
@@ -0,0 +1,101 @@
import { BadRequestException } from '@nestjs/common';
import { OrderStatus } from '@prisma/client';
import { ORDER_STATUS_LABELS } from './order-status';
/**
* گذرهای مجاز گردش سفارش. enum دیتابیس وضعیت‌های بیشتری دارد که از نسخه‌های
* قبلی مانده‌اند؛ فقط همین‌ها در جریان واقعی استفاده می‌شوند.
*
* ترتیب کار: ثبت سفارش ← قیمت‌گذاری ادمین ← ارسال فاکتور ← انتخاب تسویه توسط
* مشتری ← شاخه نقدی یا چکی ← تأیید نهایی ← ارسال.
*/
export const ORDER_TRANSITIONS: Record<OrderStatus, OrderStatus[]> = {
awaiting_review: [OrderStatus.pricing],
pricing: [OrderStatus.awaiting_settlement],
awaiting_settlement: [
OrderStatus.awaiting_cash_payment,
OrderStatus.check_in_progress,
OrderStatus.pricing,
],
// شاخه نقدی
awaiting_cash_payment: [
OrderStatus.awaiting_cash_confirmation,
OrderStatus.expired,
],
awaiting_cash_confirmation: [
OrderStatus.cash_confirmed,
OrderStatus.awaiting_cash_payment,
],
cash_confirmed: [OrderStatus.final_approved],
// شاخه چکی
check_in_progress: [OrderStatus.awaiting_check_review],
awaiting_check_review: [
OrderStatus.checks_approved,
OrderStatus.check_needs_fix,
],
check_needs_fix: [OrderStatus.awaiting_check_review],
checks_approved: [OrderStatus.final_approved],
// مسیر مشترک تا تحویل
final_approved: [OrderStatus.preparing],
preparing: [OrderStatus.ready_to_ship],
ready_to_ship: [OrderStatus.shipped],
shipped: [OrderStatus.delivered],
delivered: [],
// پایانی‌ها
cancelled: [],
rejected: [],
expired: [],
// وضعیت‌های قدیمی: فقط راه خروج دارند تا رکوردهای موجود قفل نشوند
cart: [OrderStatus.awaiting_review],
submitted: [OrderStatus.awaiting_review],
needs_correction: [OrderStatus.awaiting_review, OrderStatus.pricing],
awaiting_settlement_fix: [OrderStatus.awaiting_settlement],
cash_in_progress: [OrderStatus.awaiting_cash_payment],
cash_invoice_issued: [OrderStatus.awaiting_cash_payment],
cash_payment_submitted: [OrderStatus.awaiting_cash_confirmation],
check_rejected: [OrderStatus.check_needs_fix],
};
/** ادمین در هر مرحله‌ای می‌تواند سفارش را ببندد. */
const ADMIN_TERMINAL_TRANSITIONS: OrderStatus[] = [
OrderStatus.cancelled,
OrderStatus.rejected,
];
const FINAL_STATUSES: OrderStatus[] = [
OrderStatus.delivered,
OrderStatus.cancelled,
OrderStatus.rejected,
OrderStatus.expired,
];
export function isFinalStatus(status: OrderStatus): boolean {
return FINAL_STATUSES.includes(status);
}
/** گذرهایی که ادمین بدون حالت اجباری می‌تواند انتخاب کند. */
export function allowedTransitions(from: OrderStatus): OrderStatus[] {
const next = [...(ORDER_TRANSITIONS[from] ?? [])];
if (!isFinalStatus(from)) {
for (const terminal of ADMIN_TERMINAL_TRANSITIONS) {
if (!next.includes(terminal)) next.push(terminal);
}
}
return next;
}
export function canTransition(from: OrderStatus, to: OrderStatus): boolean {
return allowedTransitions(from).includes(to);
}
export function assertTransition(from: OrderStatus, to: OrderStatus): void {
if (canTransition(from, to)) return;
throw new BadRequestException(
`تغییر وضعیت از «${ORDER_STATUS_LABELS[from]}» به «${ORDER_STATUS_LABELS[to]}» مجاز نیست`,
);
}
+59 -35
View File
@@ -5,6 +5,7 @@ import {
} from '@nestjs/common'; } from '@nestjs/common';
import { Prisma } from '@prisma/client'; import { Prisma } from '@prisma/client';
import { PrismaService } from '../prisma/prisma.service'; import { PrismaService } from '../prisma/prisma.service';
import { resolveMaxMeterage } from '../common/utils/meterage';
import { import {
CreateBankAccountDto, CreateBankAccountDto,
CreateCompanyDto, CreateCompanyDto,
@@ -17,35 +18,50 @@ export class CompaniesService {
constructor(private readonly prisma: PrismaService) {} constructor(private readonly prisma: PrismaService) {}
async findAll(activeOnly = false) { async findAll(activeOnly = false) {
const companies = await this.prisma.company.findMany({ const [companies, fallbackMeterage] = await Promise.all([
where: activeOnly ? { isActive: true } : undefined, this.prisma.company.findMany({
orderBy: [{ displayOrder: 'asc' }, { name: 'asc' }], where: activeOnly ? { isActive: true } : undefined,
include: { orderBy: [{ displayOrder: 'asc' }, { name: 'asc' }],
_count: { include: {
select: { catalogs: true, products: true, bankAccounts: true }, _count: {
select: { catalogs: true, products: true, bankAccounts: true },
},
}, },
}, }),
}); this.settingsMaxMeterage(),
return companies.map((c) => this.mapCompany(c)); ]);
return companies.map((c) => this.mapCompany(c, fallbackMeterage));
} }
async findOne(id: string) { async findOne(id: string) {
const company = await this.prisma.company.findUnique({ const [company, fallbackMeterage] = await Promise.all([
where: { id }, this.prisma.company.findUnique({
include: { where: { id },
bankAccounts: { orderBy: { displayOrder: 'asc' } }, include: {
_count: { bankAccounts: { orderBy: { displayOrder: 'asc' } },
select: { catalogs: true, products: true, bankAccounts: true }, _count: {
select: { catalogs: true, products: true, bankAccounts: true },
},
}, },
}, }),
}); this.settingsMaxMeterage(),
]);
if (!company) throw new NotFoundException('شرکت یافت نشد'); if (!company) throw new NotFoundException('شرکت یافت نشد');
return { return {
...this.mapCompany(company), ...this.mapCompany(company, fallbackMeterage),
bankAccounts: company.bankAccounts.map((b) => this.mapBankAccount(b)), bankAccounts: company.bankAccounts.map((b) => this.mapBankAccount(b)),
}; };
} }
/** سقف متراژ تنظیمات سیستم که شرکت‌های بدون سقف اختصاصی از آن ارث می‌برند. */
private async settingsMaxMeterage(): Promise<number | null> {
const settings = await this.prisma.systemSettings.findUnique({
where: { id: 'default' },
select: { maxMeteragePerInvoice: true },
});
return settings?.maxMeteragePerInvoice ?? null;
}
async create(dto: CreateCompanyDto) { async create(dto: CreateCompanyDto) {
try { try {
const company = await this.prisma.company.create({ const company = await this.prisma.company.create({
@@ -55,7 +71,7 @@ export class CompaniesService {
description: dto.description?.trim() || null, description: dto.description?.trim() || null,
logoUrl: dto.logoUrl?.trim() || null, logoUrl: dto.logoUrl?.trim() || null,
displayOrder: dto.displayOrder ?? 0, displayOrder: dto.displayOrder ?? 0,
maxMeteragePerInvoice: dto.maxMeteragePerInvoice ?? 5000, maxMeteragePerInvoice: dto.maxMeteragePerInvoice ?? null,
cashInvoiceValidityHours: dto.cashInvoiceValidityHours ?? 8, cashInvoiceValidityHours: dto.cashInvoiceValidityHours ?? 8,
cashInvoiceValidUntilHour: dto.cashInvoiceValidUntilHour ?? null, cashInvoiceValidUntilHour: dto.cashInvoiceValidUntilHour ?? null,
isActive: dto.isActive ?? true, isActive: dto.isActive ?? true,
@@ -196,21 +212,24 @@ export class CompaniesService {
return account; return account;
} }
private mapCompany(company: { private mapCompany(
id: string; company: {
name: string; id: string;
slug: string; name: string;
description: string | null; slug: string;
logoUrl: string | null; description: string | null;
isActive: boolean; logoUrl: string | null;
displayOrder: number; isActive: boolean;
maxMeteragePerInvoice: number; displayOrder: number;
cashInvoiceValidityHours: number; maxMeteragePerInvoice: number | null;
cashInvoiceValidUntilHour: number | null; cashInvoiceValidityHours: number;
createdAt: Date; cashInvoiceValidUntilHour: number | null;
updatedAt: Date; createdAt: Date;
_count?: { catalogs: number; products: number; bankAccounts: number }; updatedAt: Date;
}) { _count?: { catalogs: number; products: number; bankAccounts: number };
},
fallbackMaxMeterage: number | null = null,
) {
return { return {
id: company.id, id: company.id,
name: company.name, name: company.name,
@@ -220,7 +239,12 @@ export class CompaniesService {
logo: company.logoUrl ?? undefined, logo: company.logoUrl ?? undefined,
isActive: company.isActive, isActive: company.isActive,
displayOrder: company.displayOrder, displayOrder: company.displayOrder,
maxMeteragePerInvoice: company.maxMeteragePerInvoice, maxMeteragePerInvoice: resolveMaxMeterage(
company.maxMeteragePerInvoice,
fallbackMaxMeterage,
),
/** مقدار خام شرکت؛ خالی یعنی از تنظیمات سیستم ارث می‌برد */
ownMaxMeteragePerInvoice: company.maxMeteragePerInvoice ?? undefined,
cashInvoiceValidityHours: company.cashInvoiceValidityHours, cashInvoiceValidityHours: company.cashInvoiceValidityHours,
cashInvoiceValidUntilHour: company.cashInvoiceValidUntilHour ?? undefined, cashInvoiceValidUntilHour: company.cashInvoiceValidUntilHour ?? undefined,
catalogCount: company._count?.catalogs ?? 0, catalogCount: company._count?.catalogs ?? 0,
+193
View File
@@ -0,0 +1,193 @@
import { NotificationTone, OrderStatus } from '@prisma/client';
/** رویدادهایی که برای مشتری اعلان و پیامک تولید می‌کنند. */
export type CustomerEvent =
| 'order_submitted'
| 'invoice_priced'
| 'checks_approved'
| 'checks_need_fix'
| 'receipt_approved'
| 'receipt_rejected'
| 'cash_invoice_reminder'
| 'cash_invoice_expired'
| 'order_preparing'
| 'order_ready_to_ship'
| 'order_shipped'
| 'order_delivered'
| 'order_cancelled';
/** کلید سوئیچ‌های پیامک در SystemSettings؛ null یعنی پیامک ندارد. */
type SmsSwitch =
| 'smsOrderRegistered'
| 'smsPricingReady'
| 'smsPaymentReminder'
| 'smsCheckReview'
| 'smsShippingUpdate'
| null;
export type CustomerEventTemplate = {
title: string;
/** متن اعلان درون‌برنامه‌ای */
body: (ctx: EventContext) => string;
/** متن پیامک؛ نبودنش یعنی فقط اعلان درون‌برنامه‌ای */
sms?: (ctx: EventContext) => string;
tone: NotificationTone;
smsSwitch: SmsSwitch;
};
export type EventContext = {
orderNumber: string;
appName: string;
amount?: number | null;
note?: string | null;
deadline?: string | null;
carrier?: string | null;
trackingNumber?: string | null;
};
function money(amount?: number | null): string {
if (!amount) return '—';
return `${Math.round(amount).toLocaleString('fa-IR')} تومان`;
}
/**
* وضعیت‌هایی که به‌تنهایی برای مشتری خبر دارند. وضعیت‌های میانی مثل
* awaiting_check_review خبری برای مشتری ندارند و اینجا نمی‌آیند.
*/
export function eventForStatus(status: OrderStatus): CustomerEvent | null {
switch (status) {
case OrderStatus.awaiting_settlement:
return 'invoice_priced';
case OrderStatus.preparing:
case OrderStatus.final_approved:
return 'order_preparing';
case OrderStatus.ready_to_ship:
return 'order_ready_to_ship';
case OrderStatus.shipped:
return 'order_shipped';
case OrderStatus.delivered:
return 'order_delivered';
case OrderStatus.cancelled:
case OrderStatus.rejected:
return 'order_cancelled';
case OrderStatus.expired:
return 'cash_invoice_expired';
default:
return null;
}
}
export const CUSTOMER_EVENTS: Record<CustomerEvent, CustomerEventTemplate> = {
order_submitted: {
title: 'سفارش شما ثبت شد',
body: (c) =>
`سفارش ${c.orderNumber} ثبت شد و برای بررسی و قیمت‌گذاری به کارشناسان ما رسید.`,
sms: (c) =>
`سفارش ${c.orderNumber} در ${c.appName} ثبت شد. پس از قیمت‌گذاری، فاکتور برای شما ارسال می‌شود.`,
tone: NotificationTone.info,
smsSwitch: 'smsOrderRegistered',
},
invoice_priced: {
title: 'فاکتور سفارش شما صادر شد',
body: (c) =>
`مبلغ فاکتور سفارش ${c.orderNumber} ${money(c.amount)} است. برای ادامه، روش تسویه را انتخاب کنید.`,
sms: (c) =>
`فاکتور سفارش ${c.orderNumber} به مبلغ ${money(c.amount)} صادر شد. برای انتخاب روش تسویه به پنل ${c.appName} مراجعه کنید.`,
tone: NotificationTone.success,
smsSwitch: 'smsPricingReady',
},
checks_approved: {
title: 'چک‌های شما تایید شد',
body: (c) => `همه چک‌های سفارش ${c.orderNumber} تایید شدند.`,
sms: (c) =>
`چک‌های سفارش ${c.orderNumber} تایید شد و سفارش به مرحله آماده‌سازی می‌رود.`,
tone: NotificationTone.success,
smsSwitch: 'smsCheckReview',
},
checks_need_fix: {
title: 'چک شما نیاز به اصلاح دارد',
body: (c) =>
`یک یا چند چک سفارش ${c.orderNumber} تایید نشد${c.note ? `: ${c.note}` : '.'} لطفاً چک‌ها را اصلاح و مجدداً ارسال کنید.`,
sms: (c) =>
`چک سفارش ${c.orderNumber} تایید نشد. برای اصلاح چک‌ها به پنل ${c.appName} مراجعه کنید.`,
tone: NotificationTone.danger,
smsSwitch: 'smsCheckReview',
},
receipt_approved: {
title: 'پرداخت شما تایید شد',
body: (c) => `رسید پرداخت سفارش ${c.orderNumber} تایید شد.`,
sms: (c) =>
`رسید پرداخت سفارش ${c.orderNumber} تایید شد. سفارش شما در حال آماده‌سازی است.`,
tone: NotificationTone.success,
smsSwitch: 'smsPaymentReminder',
},
receipt_rejected: {
title: 'رسید پرداخت تایید نشد',
body: (c) =>
`رسید پرداخت سفارش ${c.orderNumber} تایید نشد${c.note ? `: ${c.note}` : '.'} لطفاً رسید صحیح را ثبت کنید.`,
sms: (c) =>
`رسید پرداخت سفارش ${c.orderNumber} تایید نشد. لطفاً در پنل ${c.appName} رسید را اصلاح کنید.`,
tone: NotificationTone.danger,
smsSwitch: 'smsPaymentReminder',
},
cash_invoice_reminder: {
title: 'یادآوری مهلت پرداخت',
body: (c) =>
`مهلت پرداخت فاکتور سفارش ${c.orderNumber} به پایان نزدیک است. مبلغ قابل پرداخت ${money(c.amount)}.`,
sms: (c) =>
`مهلت پرداخت فاکتور سفارش ${c.orderNumber} در حال پایان است. مبلغ ${money(c.amount)}.`,
tone: NotificationTone.warning,
smsSwitch: 'smsPaymentReminder',
},
cash_invoice_expired: {
title: 'مهلت پرداخت فاکتور تمام شد',
body: (c) =>
`مهلت پرداخت فاکتور سفارش ${c.orderNumber} به پایان رسید. برای ثبت سفارش تازه با پشتیبانی تماس بگیرید.`,
sms: (c) => `مهلت پرداخت فاکتور سفارش ${c.orderNumber} به پایان رسید.`,
tone: NotificationTone.danger,
smsSwitch: 'smsPaymentReminder',
},
order_preparing: {
title: 'سفارش در حال آماده‌سازی',
body: (c) =>
`سفارش ${c.orderNumber} تایید نهایی شد و در حال آماده‌سازی است.`,
tone: NotificationTone.info,
smsSwitch: null,
},
order_ready_to_ship: {
title: 'سفارش آماده ارسال است',
body: (c) => `سفارش ${c.orderNumber} آماده ارسال شد.`,
sms: (c) => `سفارش ${c.orderNumber} آماده ارسال شد.`,
tone: NotificationTone.info,
smsSwitch: 'smsShippingUpdate',
},
order_shipped: {
title: 'سفارش ارسال شد',
body: (c) =>
`سفارش ${c.orderNumber} ارسال شد${c.carrier ? ` (${c.carrier})` : ''}${
c.trackingNumber ? ` با کد رهگیری ${c.trackingNumber}` : ''
}.`,
sms: (c) =>
`سفارش ${c.orderNumber} ارسال شد${
c.trackingNumber ? `. کد رهگیری: ${c.trackingNumber}` : '.'
}`,
tone: NotificationTone.success,
smsSwitch: 'smsShippingUpdate',
},
order_delivered: {
title: 'سفارش تحویل شد',
body: (c) =>
`سفارش ${c.orderNumber} تحویل داده شد. از خرید شما سپاسگزاریم.`,
sms: (c) => `سفارش ${c.orderNumber} تحویل داده شد. از خرید شما سپاسگزاریم.`,
tone: NotificationTone.success,
smsSwitch: 'smsShippingUpdate',
},
order_cancelled: {
title: 'سفارش لغو شد',
body: (c) =>
`سفارش ${c.orderNumber} لغو شد${c.note ? `: ${c.note}` : '.'} برای اطلاعات بیشتر با پشتیبانی تماس بگیرید.`,
sms: (c) => `سفارش ${c.orderNumber} لغو شد.`,
tone: NotificationTone.danger,
smsSwitch: null,
},
};
@@ -0,0 +1,47 @@
import { Controller, Get, Param, Patch } from '@nestjs/common';
import { NotificationsService } from './notifications.service';
import { AuthType } from '../common/decorators/auth.decorators';
import { CurrentUser } from '../common/decorators/current-user.decorator';
import type { AuthPrincipal } from '../common/decorators/current-user.decorator';
@Controller('notifications')
@AuthType('customer')
export class CustomerNotificationsController {
constructor(private readonly notifications: NotificationsService) {}
@Get()
list(@CurrentUser() user: AuthPrincipal) {
return this.notifications.listForCustomer(user.id);
}
@Get('unread-count')
unreadCount(@CurrentUser() user: AuthPrincipal) {
return this.notifications.unreadCount(user.id);
}
@Patch('read-all')
markAllRead(@CurrentUser() user: AuthPrincipal) {
return this.notifications.markAllRead(user.id);
}
@Patch(':id/read')
markRead(@CurrentUser() user: AuthPrincipal, @Param('id') id: string) {
return this.notifications.markRead(user.id, id);
}
}
@Controller('admin/notifications')
@AuthType('admin')
export class AdminNotificationsController {
constructor(private readonly notifications: NotificationsService) {}
@Patch('read-all')
markAllRead() {
return this.notifications.markAllAdminRead();
}
@Patch(':id/read')
markRead(@Param('id') id: string) {
return this.notifications.markAdminRead(id);
}
}
+15
View File
@@ -0,0 +1,15 @@
import { Module } from '@nestjs/common';
import { NotificationsService } from './notifications.service';
import {
AdminNotificationsController,
CustomerNotificationsController,
} from './notifications.controller';
import { SmsModule } from '../sms/sms.module';
@Module({
imports: [SmsModule],
controllers: [CustomerNotificationsController, AdminNotificationsController],
providers: [NotificationsService],
exports: [NotificationsService],
})
export class NotificationsModule {}
+224
View File
@@ -0,0 +1,224 @@
import { Injectable, Logger, NotFoundException } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { NotificationTone } from '@prisma/client';
import { PrismaService } from '../prisma/prisma.service';
import { SmsService } from '../sms/sms.service';
import {
CUSTOMER_EVENTS,
type CustomerEvent,
type EventContext,
} from './notification-events';
type NotifyOptions = {
amount?: number | null;
note?: string | null;
carrier?: string | null;
trackingNumber?: string | null;
};
@Injectable()
export class NotificationsService {
private readonly logger = new Logger(NotificationsService.name);
constructor(
private readonly prisma: PrismaService,
private readonly sms: SmsService,
private readonly config: ConfigService,
) {}
/**
* باید **بعد از commit تراکنش** صدا زده شود؛ وگرنه با rollback هم پیامک رفته
* است و هم اعلانی برای سفارشی ساخته می‌شود که وجود ندارد.
*/
async notifyOrderEvent(
orderId: string,
event: CustomerEvent,
options: NotifyOptions = {},
): Promise<void> {
try {
const order = await this.prisma.order.findUnique({
where: { id: orderId },
select: {
id: true,
orderNumber: true,
userId: true,
finalAmount: true,
totalAmount: true,
user: { select: { phone: true } },
},
});
if (!order) return;
const template = CUSTOMER_EVENTS[event];
const ctx: EventContext = {
orderNumber: order.orderNumber,
appName: this.appName(),
amount:
options.amount ?? order.finalAmount ?? order.totalAmount ?? undefined,
note: options.note ?? null,
carrier: options.carrier ?? null,
trackingNumber: options.trackingNumber ?? null,
};
const notification = await this.prisma.customerNotification.create({
data: {
customerId: order.userId,
orderId: order.id,
event,
title: template.title,
body: template.body(ctx),
tone: template.tone,
link: `/orders/${order.id}`,
},
});
if (!template.sms || !template.smsSwitch) return;
const enabled = await this.isSmsEventEnabled(template.smsSwitch);
if (!enabled || !this.sms.isEnabled()) return;
const result = await this.sms.send(order.user.phone, template.sms(ctx));
if (result.success) {
await this.prisma.customerNotification.update({
where: { id: notification.id },
data: { smsSent: true },
});
}
} catch (err) {
// اعلان هیچ‌وقت نباید عملیات اصلی را بشکند
this.logger.error(
`notifyOrderEvent(${event}) failed for order ${orderId}`,
err instanceof Error ? err.stack : err,
);
}
}
/** نسخه بدون await برای مسیرهایی که نباید منتظر پیامک بمانند. */
dispatchOrderEvent(
orderId: string,
event: CustomerEvent,
options: NotifyOptions = {},
): void {
void this.notifyOrderEvent(orderId, event, options);
}
async notifyAdmin(input: {
title: string;
body: string;
tone?: NotificationTone;
link?: string;
}): Promise<void> {
try {
await this.prisma.adminNotification.create({
data: {
title: input.title,
body: input.body,
tone: input.tone ?? NotificationTone.info,
link: input.link ?? null,
},
});
} catch (err) {
this.logger.error(
'notifyAdmin failed',
err instanceof Error ? err.stack : err,
);
}
}
dispatchAdmin(input: {
title: string;
body: string;
tone?: NotificationTone;
link?: string;
}): void {
void this.notifyAdmin(input);
}
async listForCustomer(customerId: string, limit = 50) {
const rows = await this.prisma.customerNotification.findMany({
where: { customerId },
orderBy: { sentAt: 'desc' },
take: limit,
});
return rows.map((n) => ({
id: n.id,
orderId: n.orderId ?? undefined,
event: n.event,
title: n.title,
body: n.body,
tone: n.tone ?? NotificationTone.info,
link: n.link ?? undefined,
isRead: n.isRead,
sentAt: n.sentAt.toISOString(),
}));
}
async unreadCount(customerId: string) {
const count = await this.prisma.customerNotification.count({
where: { customerId, isRead: false },
});
return { count };
}
async markRead(customerId: string, id: string) {
const existing = await this.prisma.customerNotification.findFirst({
where: { id, customerId },
select: { id: true },
});
if (!existing) throw new NotFoundException('اعلان یافت نشد');
await this.prisma.customerNotification.update({
where: { id },
data: { isRead: true },
});
return this.unreadCount(customerId);
}
async markAllRead(customerId: string) {
await this.prisma.customerNotification.updateMany({
where: { customerId, isRead: false },
data: { isRead: true },
});
return { count: 0 };
}
async markAdminRead(id: string) {
await this.prisma.adminNotification.updateMany({
where: { id },
data: { isRead: true },
});
return { ok: true };
}
async markAllAdminRead() {
await this.prisma.adminNotification.updateMany({
where: { isRead: false },
data: { isRead: true },
});
return { ok: true };
}
private appName(): string {
return this.config.get<string>('SMS_APP_NAME', 'پارچه') ?? 'پارچه';
}
private async isSmsEventEnabled(
key:
| 'smsOrderRegistered'
| 'smsPricingReady'
| 'smsPaymentReminder'
| 'smsCheckReview'
| 'smsShippingUpdate',
): Promise<boolean> {
const settings = await this.prisma.systemSettings.findUnique({
where: { id: 'default' },
select: {
smsOrderRegistered: true,
smsPricingReady: true,
smsPaymentReminder: true,
smsCheckReview: true,
smsShippingUpdate: true,
},
});
// در نبود ردیف تنظیمات، پیش‌فرض‌های اسکیمای فعال در نظر گرفته می‌شوند
return settings ? settings[key] : true;
}
}
@@ -0,0 +1,120 @@
import { Injectable, Logger } from '@nestjs/common';
import { Cron, CronExpression } from '@nestjs/schedule';
import { OrderStatus } from '@prisma/client';
import { PrismaService } from '../prisma/prisma.service';
import { NotificationsService } from '../notifications/notifications.service';
/** وضعیت‌هایی که فاکتور نقدی در آن‌ها منتظر پرداخت مشتری است. */
const PAYABLE_STATUSES: OrderStatus[] = [
OrderStatus.awaiting_cash_payment,
OrderStatus.cash_invoice_issued,
];
/** چقدر پیش از پایان مهلت، یادآوری فرستاده شود. */
const REMINDER_WINDOW_MS = 60 * 60 * 1000;
@Injectable()
export class CashInvoiceSchedulerService {
private readonly logger = new Logger(CashInvoiceSchedulerService.name);
constructor(
private readonly prisma: PrismaService,
private readonly notifications: NotificationsService,
) {}
@Cron(CronExpression.EVERY_10_MINUTES)
async handleCashInvoices(): Promise<void> {
await this.sendReminders();
await this.expireOverdueInvoices();
}
/** یادآوری یک‌بار برای هر فاکتوری که کمتر از یک ساعت به پایان مهلتش مانده. */
async sendReminders(): Promise<number> {
const now = new Date();
const soon = new Date(now.getTime() + REMINDER_WINDOW_MS);
try {
const orders = await this.prisma.order.findMany({
where: {
status: { in: PAYABLE_STATUSES },
cashReminderSentAt: null,
cashInvoiceExpiresAt: { gt: now, lte: soon },
},
select: { id: true, finalAmount: true },
});
for (const order of orders) {
await this.prisma.order.update({
where: { id: order.id },
data: { cashReminderSentAt: now },
});
await this.notifications.notifyOrderEvent(
order.id,
'cash_invoice_reminder',
{ amount: order.finalAmount },
);
}
return orders.length;
} catch (err) {
this.logger.error(
'sendReminders failed',
err instanceof Error ? err.stack : err,
);
return 0;
}
}
/** فاکتورهای نقدی که مهلتشان تمام شده به وضعیت منقضی می‌روند. */
async expireOverdueInvoices(): Promise<number> {
const now = new Date();
try {
const orders = await this.prisma.order.findMany({
where: {
status: { in: PAYABLE_STATUSES },
cashInvoiceExpiresAt: { lt: now },
},
select: { id: true, orderNumber: true, status: true },
});
for (const order of orders) {
await this.prisma.$transaction([
this.prisma.order.update({
where: { id: order.id },
data: { status: OrderStatus.expired },
}),
this.prisma.orderStatusHistory.create({
data: {
orderId: order.id,
fromStatus: order.status,
toStatus: OrderStatus.expired,
changedBy: 'system',
note: 'پایان مهلت پرداخت فاکتور نقدی',
},
}),
]);
await this.notifications.notifyOrderEvent(
order.id,
'cash_invoice_expired',
);
this.notifications.dispatchAdmin({
title: 'فاکتور نقدی منقضی شد',
body: `مهلت پرداخت سفارش ${order.orderNumber} به پایان رسید و سفارش منقضی شد.`,
link: '/orders',
});
}
if (orders.length) {
this.logger.log(`${orders.length} فاکتور نقدی منقضی شد`);
}
return orders.length;
} catch (err) {
this.logger.error(
'expireOverdueInvoices failed',
err instanceof Error ? err.stack : err,
);
return 0;
}
}
}
+22 -2
View File
@@ -1,6 +1,7 @@
import { import {
ArrayMinSize, ArrayMinSize,
IsArray, IsArray,
IsBoolean,
IsDateString, IsDateString,
IsEnum, IsEnum,
IsNumber, IsNumber,
@@ -94,10 +95,14 @@ export class SubmitOrderDto {
@IsString() @IsString()
companyId!: string; companyId!: string;
/**
* روش تسویه بعد از قیمت‌گذاری ادمین انتخاب می‌شود، نه اینجا.
* برای سازگاری با کلاینت‌های قدیمی پذیرفته و نادیده گرفته می‌شود.
*/
@IsOptional()
@IsEnum(SettlementType) @IsEnum(SettlementType)
settlementType!: SettlementType; settlementType?: SettlementType;
/** Alias پذیرفته‌شده از کلاینت‌های قدیمی؛ نادیده گرفته می‌شود */
@IsOptional() @IsOptional()
@IsEnum(SettlementType) @IsEnum(SettlementType)
settlementMethod?: SettlementType; settlementMethod?: SettlementType;
@@ -123,6 +128,15 @@ export class SubmitOrderDto {
checks?: SubmitOrderCheckDto[]; checks?: SubmitOrderCheckDto[];
} }
export class ChooseSettlementDto {
@IsEnum(SettlementType)
settlementType!: SettlementType;
@IsOptional()
@IsEnum(CheckRegistrationMode)
checkMode?: CheckRegistrationMode;
}
export class AdminCreateOrderDto { export class AdminCreateOrderDto {
@IsOptional() @IsOptional()
@IsString() @IsString()
@@ -229,6 +243,12 @@ export class UpdateOrderStatusDto {
@IsOptional() @IsOptional()
@IsString() @IsString()
reason?: string; reason?: string;
/** عبور از ماشین وضعیت؛ فقط با ذکر دلیل و با ثبت در تاریخچه. */
@IsOptional()
@Type(() => Boolean)
@IsBoolean()
force?: boolean;
} }
export class UpdateOrderChecksDto { export class UpdateOrderChecksDto {
+11
View File
@@ -13,6 +13,7 @@ import { CurrentUser } from '../common/decorators/current-user.decorator';
import type { AuthPrincipal } from '../common/decorators/current-user.decorator'; import type { AuthPrincipal } from '../common/decorators/current-user.decorator';
import { import {
AdminCreateOrderDto, AdminCreateOrderDto,
ChooseSettlementDto,
SubmitOrderDto, SubmitOrderDto,
UpdateOrderChecksDto, UpdateOrderChecksDto,
UpdateOrderPricingDto, UpdateOrderPricingDto,
@@ -41,6 +42,16 @@ export class CustomerOrdersController {
return this.ordersService.submitOrder(user.id, dto); return this.ordersService.submitOrder(user.id, dto);
} }
@AuthType('customer')
@Post(':id/settlement')
chooseSettlement(
@CurrentUser() user: AuthPrincipal,
@Param('id') id: string,
@Body() dto: ChooseSettlementDto,
) {
return this.ordersService.chooseSettlement(user.id, id, dto);
}
@AuthType('customer') @AuthType('customer')
@Patch(':id/checks') @Patch(':id/checks')
updateChecks( updateChecks(
+4 -1
View File
@@ -4,10 +4,13 @@ import {
CustomerOrdersController, CustomerOrdersController,
} from './orders.controller'; } from './orders.controller';
import { OrdersService } from './orders.service'; import { OrdersService } from './orders.service';
import { CashInvoiceSchedulerService } from './cash-invoice-scheduler.service';
import { NotificationsModule } from '../notifications/notifications.module';
@Module({ @Module({
imports: [NotificationsModule],
controllers: [CustomerOrdersController, AdminOrdersController], controllers: [CustomerOrdersController, AdminOrdersController],
providers: [OrdersService], providers: [OrdersService, CashInvoiceSchedulerService],
exports: [OrdersService], exports: [OrdersService],
}) })
export class OrdersModule {} export class OrdersModule {}
+169 -84
View File
@@ -13,6 +13,7 @@ import {
import { PrismaService } from '../prisma/prisma.service'; import { PrismaService } from '../prisma/prisma.service';
import { import {
AdminCreateOrderDto, AdminCreateOrderDto,
ChooseSettlementDto,
SubmitOrderCheckDto, SubmitOrderCheckDto,
SubmitOrderDto, SubmitOrderDto,
UpdateOrderChecksDto, UpdateOrderChecksDto,
@@ -26,6 +27,13 @@ import {
resolveAdminStatusFilter, resolveAdminStatusFilter,
toAdminStatus, toAdminStatus,
} from '../common/utils/order-status'; } from '../common/utils/order-status';
import {
allowedTransitions,
assertTransition,
} from '../common/utils/order-transitions';
import { resolveMaxMeterage } from '../common/utils/meterage';
import { NotificationsService } from '../notifications/notifications.service';
import { eventForStatus } from '../notifications/notification-events';
import { joinFullName, splitFullName } from '../common/utils/name'; import { joinFullName, splitFullName } from '../common/utils/name';
import { import {
isValidPhone, isValidPhone,
@@ -52,19 +60,27 @@ function roundMoney(value: number): number {
/** بعد از تأیید چک‌ها یا ارسال سفارش، مشتری نباید بتواند چک‌ها را بازنویسی کند. */ /** بعد از تأیید چک‌ها یا ارسال سفارش، مشتری نباید بتواند چک‌ها را بازنویسی کند. */
const CHECK_EDITABLE_STATUSES: OrderStatus[] = [ const CHECK_EDITABLE_STATUSES: OrderStatus[] = [
OrderStatus.awaiting_review,
OrderStatus.pricing,
OrderStatus.needs_correction,
OrderStatus.awaiting_settlement_fix,
OrderStatus.check_in_progress, OrderStatus.check_in_progress,
OrderStatus.awaiting_check_review, OrderStatus.awaiting_check_review,
OrderStatus.check_needs_fix, OrderStatus.check_needs_fix,
OrderStatus.check_rejected, OrderStatus.check_rejected,
]; ];
/**
* تا وقتی ادمین فاکتور را نفرستاده، مشتری نباید هیچ مبلغی ببیند.
* پنهان‌سازی روی خود پاسخ API انجام می‌شود، نه در کلاینت.
*/
const PRICE_HIDDEN_STATUSES: OrderStatus[] = [
OrderStatus.awaiting_review,
OrderStatus.pricing,
];
@Injectable() @Injectable()
export class OrdersService { export class OrdersService {
constructor(private readonly prisma: PrismaService) {} constructor(
private readonly prisma: PrismaService,
private readonly notifications: NotificationsService,
) {}
async findMyOrders(userId: string) { async findMyOrders(userId: string) {
const orders = await this.prisma.order.findMany({ const orders = await this.prisma.order.findMany({
@@ -119,12 +135,20 @@ export class OrdersService {
} }
async submitOrder(userId: string, dto: SubmitOrderDto) { async submitOrder(userId: string, dto: SubmitOrderDto) {
const company = await this.prisma.company.findUnique({ const [company, settings] = await Promise.all([
where: { id: dto.companyId }, this.prisma.company.findUnique({ where: { id: dto.companyId } }),
}); this.prisma.systemSettings.findUnique({
where: { id: 'default' },
select: { maxMeteragePerInvoice: true },
}),
]);
if (!company || !company.isActive) { if (!company || !company.isActive) {
throw new NotFoundException('شرکت یافت نشد یا غیرفعال است'); throw new NotFoundException('شرکت یافت نشد یا غیرفعال است');
} }
const maxMeterage = resolveMaxMeterage(
company.maxMeteragePerInvoice,
settings?.maxMeteragePerInvoice,
);
const productIds = dto.items.map((i) => i.productId); const productIds = dto.items.map((i) => i.productId);
const products = await this.prisma.product.findMany({ const products = await this.prisma.product.findMany({
@@ -159,44 +183,17 @@ export class OrdersService {
productCode: product.code, productCode: product.code,
color: product.color, color: product.color,
requestedMeterage: item.meterage, requestedMeterage: item.meterage,
unitPrice: product.basePrice,
notes: item.notes?.trim() || null, notes: item.notes?.trim() || null,
}; };
}); });
if (totalMeterage > company.maxMeteragePerInvoice) { if (totalMeterage > maxMeterage) {
throw new BadRequestException( throw new BadRequestException(
`حداکثر متراژ مجاز هر فاکتور ${company.maxMeteragePerInvoice} متر است`, `حداکثر متراژ مجاز هر فاکتور ${maxMeterage} متر است`,
); );
} }
const settlementType = dto.settlementType; // روش تسویه و چک‌ها بعد از قیمت‌گذاری ادمین گرفته می‌شوند، نه اینجا.
if (settlementType === SettlementType.check) {
if (!dto.checks?.length) {
throw new BadRequestException('برای تسویه چکی حداقل یک چک لازم است');
}
}
const checkMode =
settlementType === SettlementType.check
? (dto.checkMode ?? CheckRegistrationMode.full)
: null;
const rass =
settlementType === SettlementType.check && dto.checks?.length
? calculateCheckRass(
dto.checks.map((c) => ({
amount: c.amount,
dueDate: c.dueDate,
})),
)
: null;
const cashInvoiceExpiresAt =
settlementType === SettlementType.cash
? computeCashInvoiceExpiry(company)
: null;
const order = await this.createWithUniqueOrderNumber((orderNumber) => const order = await this.createWithUniqueOrderNumber((orderNumber) =>
this.prisma.$transaction(async (tx) => { this.prisma.$transaction(async (tx) => {
const created = await tx.order.create({ const created = await tx.order.create({
@@ -205,36 +202,19 @@ export class OrdersService {
userId, userId,
companyId: company.id, companyId: company.id,
companyName: company.name, companyName: company.name,
settlementType, settlementType: null,
checkMode, checkMode: null,
status: OrderStatus.awaiting_review, status: OrderStatus.awaiting_review,
totalMeterage, totalMeterage,
notes: dto.notes?.trim() || null, notes: dto.notes?.trim() || null,
checkRassDays: rass?.rassDays ?? null,
checkRassDate: rass?.rassDate ? new Date(rass.rassDate) : null,
cashInvoiceExpiresAt,
items: { create: itemsData }, items: { create: itemsData },
checks:
settlementType === SettlementType.check && dto.checks
? {
create: dto.checks.map((c, index) =>
this.mapCheckCreate(c, index + 1, checkMode!),
),
}
: undefined,
statusHistory: { statusHistory: {
create: [ create: [
{ {
fromStatus: null, fromStatus: null,
toStatus: OrderStatus.submitted,
changedBy: 'customer',
note: 'سفارش ثبت شد',
},
{
fromStatus: OrderStatus.submitted,
toStatus: OrderStatus.awaiting_review, toStatus: OrderStatus.awaiting_review,
changedBy: 'customer', changedBy: 'customer',
note: 'ارسال برای بررسی مدیریت', note: 'سفارش ثبت شد و برای بررسی مدیریت ارسال شد',
}, },
], ],
}, },
@@ -254,15 +234,69 @@ export class OrdersService {
}), }),
); );
this.notifications.dispatchOrderEvent(order.id, 'order_submitted');
this.notifications.dispatchAdmin({
title: 'سفارش جدید ثبت شد',
body: `سفارش ${order.orderNumber} با ${order.items.length} قلم و ${order.totalMeterage} متر در انتظار قیمت‌گذاری است.`,
link: `/orders/${order.id}`,
});
return this.mapOrder(order); return this.mapOrder(order);
} }
async adminCreate(adminId: string, dto: AdminCreateOrderDto) { /**
const settlementType = dto.settlementType ?? dto.settlementMethod; * مشتری بعد از دیدن جمع فاکتور روش تسویه را انتخاب می‌کند.
if (!settlementType) { * نقدی مستقیم به مرحله پرداخت می‌رود، چکی به مرحله ثبت چک‌ها.
throw new BadRequestException('نوع تسویه الزامی است'); */
async chooseSettlement(userId: string, id: string, dto: ChooseSettlementDto) {
const order = await this.getOrder(id);
if (order.userId !== userId) {
throw new ForbiddenException('دسترسی به این سفارش مجاز نیست');
}
if (order.status !== OrderStatus.awaiting_settlement) {
throw new BadRequestException(
'انتخاب روش تسویه فقط پس از صدور فاکتور توسط مدیریت امکان‌پذیر است',
);
} }
const isCash = dto.settlementType === SettlementType.cash;
const nextStatus = isCash
? OrderStatus.awaiting_cash_payment
: OrderStatus.check_in_progress;
assertTransition(order.status, nextStatus);
await this.prisma.$transaction(async (tx) => {
await tx.order.update({
where: { id },
data: {
settlementType: dto.settlementType,
checkMode: isCash
? null
: (dto.checkMode ?? CheckRegistrationMode.full),
cashInvoiceExpiresAt: isCash
? computeCashInvoiceExpiry(order.company)
: null,
cashReminderSentAt: null,
status: nextStatus,
},
});
await tx.orderStatusHistory.create({
data: {
orderId: id,
fromStatus: order.status,
toStatus: nextStatus,
changedBy: 'customer',
note: isCash
? 'مشتری تسویه نقدی را انتخاب کرد'
: 'مشتری تسویه چکی را انتخاب کرد',
},
});
});
return this.findMyOrder(userId, id);
}
async adminCreate(adminId: string, dto: AdminCreateOrderDto) {
const company = await this.prisma.company.findUnique({ const company = await this.prisma.company.findUnique({
where: { id: dto.companyId }, where: { id: dto.companyId },
}); });
@@ -303,7 +337,7 @@ export class OrdersService {
userId: ownerId, userId: ownerId,
companyId: company.id, companyId: company.id,
companyName: company.name, companyName: company.name,
settlementType, settlementType: null,
status: OrderStatus.awaiting_review, status: OrderStatus.awaiting_review,
totalMeterage, totalMeterage,
notes: dto.notes?.trim() || null, notes: dto.notes?.trim() || null,
@@ -422,6 +456,26 @@ export class OrdersService {
return this.mapOrder(order, true); return this.mapOrder(order, true);
} }
if (dto.force) {
if (!dto.reason?.trim()) {
throw new BadRequestException(
'برای تغییر دستی وضعیت، ذکر دلیل الزامی است',
);
}
} else {
assertTransition(order.status, dto.status);
}
// فاکتور بدون مبلغ نباید برای مشتری ارسال شود
if (
dto.status === OrderStatus.awaiting_settlement &&
(order.finalAmount ?? 0) <= 0
) {
throw new BadRequestException(
'ابتدا سفارش را قیمت‌گذاری کنید تا فاکتور قابل ارسال باشد',
);
}
await this.prisma.$transaction(async (tx) => { await this.prisma.$transaction(async (tx) => {
await tx.order.update({ await tx.order.update({
where: { id }, where: { id },
@@ -433,7 +487,9 @@ export class OrdersService {
fromStatus: order.status, fromStatus: order.status,
toStatus: dto.status, toStatus: dto.status,
changedBy: `admin:${adminId}`, changedBy: `admin:${adminId}`,
note: dto.note || null, note: dto.force
? `تغییر دستی وضعیت${dto.note ? ` — ${dto.note}` : ''}`
: dto.note || null,
reason: dto.reason || null, reason: dto.reason || null,
}, },
}); });
@@ -454,6 +510,13 @@ export class OrdersService {
} }
}); });
const event = eventForStatus(dto.status);
if (event) {
this.notifications.dispatchOrderEvent(id, event, {
note: dto.reason ?? dto.note ?? null,
});
}
return this.findAdminOrder(id); return this.findAdminOrder(id);
} }
@@ -471,6 +534,10 @@ export class OrdersService {
); );
} }
if (!dto.checks.length) {
throw new BadRequestException('حداقل یک چک لازم است');
}
const checkMode = const checkMode =
dto.checkMode ?? order.checkMode ?? CheckRegistrationMode.full; dto.checkMode ?? order.checkMode ?? CheckRegistrationMode.full;
const rass = calculateCheckRass( const rass = calculateCheckRass(
@@ -478,6 +545,16 @@ export class OrdersService {
order.createdAt, order.createdAt,
); );
// ثبت اولیه و اصلاح، هر دو سفارش را به صف بررسی چک می‌برند.
const goesToReview =
order.status === OrderStatus.check_in_progress ||
order.status === OrderStatus.check_needs_fix ||
order.status === OrderStatus.check_rejected;
const nextStatus = goesToReview
? OrderStatus.awaiting_check_review
: order.status;
const isFix = order.status !== OrderStatus.check_in_progress;
await this.prisma.$transaction(async (tx) => { await this.prisma.$transaction(async (tx) => {
await tx.check.deleteMany({ where: { orderId: id } }); await tx.check.deleteMany({ where: { orderId: id } });
await tx.check.createMany({ await tx.check.createMany({
@@ -492,25 +569,32 @@ export class OrdersService {
checkMode, checkMode,
checkRassDays: rass.rassDays, checkRassDays: rass.rassDays,
checkRassDate: rass.rassDate ? new Date(rass.rassDate) : null, checkRassDate: rass.rassDate ? new Date(rass.rassDate) : null,
status: status: nextStatus,
order.status === OrderStatus.check_needs_fix
? OrderStatus.awaiting_check_review
: order.status,
}, },
}); });
if (order.status === OrderStatus.check_needs_fix) { if (nextStatus !== order.status) {
await tx.orderStatusHistory.create({ await tx.orderStatusHistory.create({
data: { data: {
orderId: id, orderId: id,
fromStatus: order.status, fromStatus: order.status,
toStatus: OrderStatus.awaiting_check_review, toStatus: nextStatus,
changedBy: 'customer', changedBy: 'customer',
note: 'چک‌ها اصلاح و مجدداً ارسال شد', note: isFix
? 'چک‌ها اصلاح و مجدداً ارسال شد'
: 'چک‌ها برای بررسی ارسال شد',
}, },
}); });
} }
}); });
if (nextStatus !== order.status) {
this.notifications.dispatchAdmin({
title: isFix ? 'چک‌های اصلاح‌شده رسید' : 'چک‌های سفارش ثبت شد',
body: `مشتری ${dto.checks.length} چک برای سفارش ${order.orderNumber} ثبت کرد و منتظر بررسی است.`,
link: `/orders/${id}`,
});
}
return this.findMyOrder(userId, id); return this.findMyOrder(userId, id);
} }
@@ -579,6 +663,8 @@ export class OrdersService {
order.user.firstName, order.user.firstName,
order.user.lastName, order.user.lastName,
); );
const hidePrices =
!adminView && PRICE_HIDDEN_STATUSES.includes(order.status);
const base = { const base = {
id: order.id, id: order.id,
orderNumber: order.orderNumber, orderNumber: order.orderNumber,
@@ -590,12 +676,14 @@ export class OrdersService {
checkMode: order.checkMode ?? undefined, checkMode: order.checkMode ?? undefined,
status: order.status, status: order.status,
adminStatus: toAdminStatus(order.status), adminStatus: toAdminStatus(order.status),
allowedTransitions: adminView ? allowedTransitions(order.status) : [],
totalMeterage: order.totalMeterage, totalMeterage: order.totalMeterage,
meterage: order.totalMeterage, meterage: order.totalMeterage,
totalAmount: order.totalAmount, totalAmount: hidePrices ? null : order.totalAmount,
shippingCost: order.shippingCost, shippingCost: hidePrices ? null : order.shippingCost,
totalDiscount: order.totalDiscount, totalDiscount: hidePrices ? null : order.totalDiscount,
finalAmount: order.finalAmount, finalAmount: hidePrices ? null : order.finalAmount,
pricesVisible: !hidePrices,
notes: order.notes ?? undefined, notes: order.notes ?? undefined,
checkRassDays: order.checkRassDays ?? undefined, checkRassDays: order.checkRassDays ?? undefined,
checkRassDate: order.checkRassDate?.toISOString(), checkRassDate: order.checkRassDate?.toISOString(),
@@ -615,12 +703,12 @@ export class OrdersService {
color: item.color, color: item.color,
requestedMeterage: item.requestedMeterage, requestedMeterage: item.requestedMeterage,
confirmedMeterage: item.confirmedMeterage, confirmedMeterage: item.confirmedMeterage,
unitPrice: item.unitPrice, unitPrice: hidePrices ? null : item.unitPrice,
supplyPrice: item.unitPrice, supplyPrice: hidePrices ? null : item.unitPrice,
unitDiscount: item.unitDiscount ?? 0, unitDiscount: hidePrices ? null : (item.unitDiscount ?? 0),
discount: item.unitDiscount ?? 0, discount: hidePrices ? null : (item.unitDiscount ?? 0),
lineTotal: item.lineTotal, lineTotal: hidePrices ? null : item.lineTotal,
totalAmount: item.lineTotal, totalAmount: hidePrices ? null : item.lineTotal,
notes: item.notes ?? undefined, notes: item.notes ?? undefined,
})), })),
checks: order.checks.map((c) => ({ checks: order.checks.map((c) => ({
@@ -688,9 +776,6 @@ export class OrdersService {
})), })),
}; };
if (adminView) {
return base;
}
return base; return base;
} }
} }
+4
View File
@@ -26,6 +26,10 @@ export class UpdateSettingsDto {
@IsBoolean() @IsBoolean()
smsOrderRegistered?: boolean; smsOrderRegistered?: boolean;
@IsOptional()
@IsBoolean()
smsPricingReady?: boolean;
@IsOptional() @IsOptional()
@IsBoolean() @IsBoolean()
smsPaymentReminder?: boolean; smsPaymentReminder?: boolean;
+3
View File
@@ -21,6 +21,7 @@ export class SettingsService {
defaultSettlementCheck: dto.defaultSettlementCheck, defaultSettlementCheck: dto.defaultSettlementCheck,
defaultSettlementCash: dto.defaultSettlementCash, defaultSettlementCash: dto.defaultSettlementCash,
smsOrderRegistered: dto.smsOrderRegistered, smsOrderRegistered: dto.smsOrderRegistered,
smsPricingReady: dto.smsPricingReady,
smsPaymentReminder: dto.smsPaymentReminder, smsPaymentReminder: dto.smsPaymentReminder,
smsCheckReview: dto.smsCheckReview, smsCheckReview: dto.smsCheckReview,
smsShippingUpdate: dto.smsShippingUpdate, smsShippingUpdate: dto.smsShippingUpdate,
@@ -52,6 +53,7 @@ export class SettingsService {
defaultSettlementCheck: boolean; defaultSettlementCheck: boolean;
defaultSettlementCash: boolean; defaultSettlementCash: boolean;
smsOrderRegistered: boolean; smsOrderRegistered: boolean;
smsPricingReady: boolean;
smsPaymentReminder: boolean; smsPaymentReminder: boolean;
smsCheckReview: boolean; smsCheckReview: boolean;
smsShippingUpdate: boolean; smsShippingUpdate: boolean;
@@ -66,6 +68,7 @@ export class SettingsService {
defaultSettlementCheck: settings.defaultSettlementCheck, defaultSettlementCheck: settings.defaultSettlementCheck,
defaultSettlementCash: settings.defaultSettlementCash, defaultSettlementCash: settings.defaultSettlementCash,
smsOrderRegistered: settings.smsOrderRegistered, smsOrderRegistered: settings.smsOrderRegistered,
smsPricingReady: settings.smsPricingReady,
smsPaymentReminder: settings.smsPaymentReminder, smsPaymentReminder: settings.smsPaymentReminder,
smsCheckReview: settings.smsCheckReview, smsCheckReview: settings.smsCheckReview,
smsShippingUpdate: settings.smsShippingUpdate, smsShippingUpdate: settings.smsShippingUpdate,

Some files were not shown because too many files have changed in this diff Show More