changes 1
This commit is contained in:
@@ -11,5 +11,11 @@ if [ -z "$PRISMA_QUERY_ENGINE_LIBRARY" ]; then
|
||||
export PRISMA_QUERY_ENGINE_LIBRARY=/app/runtime/libquery_engine-debian-openssl-3.0.x.so.node
|
||||
fi
|
||||
|
||||
if [ -n "${MONTACT_API_TOKEN:-}" ]; then
|
||||
echo "MONTACT_API_TOKEN: set"
|
||||
else
|
||||
echo "MONTACT_API_TOKEN: NOT SET (only admin JWT works for /montact)"
|
||||
fi
|
||||
|
||||
echo "Starting API on port ${PORT:-3000}..."
|
||||
exec node runtime/server.cjs
|
||||
|
||||
+35
-7
@@ -135632,9 +135632,20 @@ var require_check_rass = __commonJS({
|
||||
"dist/common/utils/check-rass.js"(exports2) {
|
||||
"use strict";
|
||||
Object.defineProperty(exports2, "__esModule", { value: true });
|
||||
exports2.invoiceAnchorDate = invoiceAnchorDate;
|
||||
exports2.calculateCheckRass = calculateCheckRass;
|
||||
var tehran_time_1 = require_tehran_time();
|
||||
function calculateCheckRass(checks, baseDate = /* @__PURE__ */ new Date()) {
|
||||
var INVOICE_ISSUED_STATUS = "awaiting_settlement";
|
||||
function invoiceAnchorDate(createdAt, history = []) {
|
||||
for (let i = history.length - 1; i >= 0; i -= 1) {
|
||||
const row = history[i];
|
||||
if (row?.toStatus === INVOICE_ISSUED_STATUS)
|
||||
return row.changedAt;
|
||||
}
|
||||
return createdAt;
|
||||
}
|
||||
__name(invoiceAnchorDate, "invoiceAnchorDate");
|
||||
function calculateCheckRass(checks, baseDate) {
|
||||
if (checks.length === 0) {
|
||||
return {
|
||||
checkCount: 0,
|
||||
@@ -136730,7 +136741,7 @@ var require_orders_service = __commonJS({
|
||||
throw new common_12.BadRequestException("\u062D\u062F\u0627\u0642\u0644 \u06CC\u06A9 \u0686\u06A9 \u0644\u0627\u0632\u0645 \u0627\u0633\u062A");
|
||||
}
|
||||
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 })), (0, check_rass_1.invoiceAnchorDate)(order.createdAt, order.statusHistory));
|
||||
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;
|
||||
@@ -136933,6 +136944,10 @@ var require_orders_service = __commonJS({
|
||||
mapOrder(order, adminView = false) {
|
||||
const customerName = (0, name_1.joinFullName)(order.user.firstName, order.user.lastName);
|
||||
const hidePrices = !adminView && PRICE_HIDDEN_STATUSES.includes(order.status);
|
||||
const liveRass = order.checks.length > 0 ? (0, check_rass_1.calculateCheckRass)(order.checks.map((check) => ({
|
||||
amount: check.amount,
|
||||
dueDate: check.dueDate
|
||||
})), (0, check_rass_1.invoiceAnchorDate)(order.createdAt, order.statusHistory)) : null;
|
||||
const base = {
|
||||
id: order.id,
|
||||
orderNumber: order.orderNumber,
|
||||
@@ -136953,8 +136968,8 @@ var require_orders_service = __commonJS({
|
||||
finalAmount: hidePrices ? null : order.finalAmount,
|
||||
pricesVisible: !hidePrices,
|
||||
notes: order.notes ?? void 0,
|
||||
checkRassDays: order.checkRassDays ?? void 0,
|
||||
checkRassDate: order.checkRassDate?.toISOString(),
|
||||
checkRassDays: liveRass ? liveRass.rassDays : order.checkRassDays ?? void 0,
|
||||
checkRassDate: liveRass?.rassDate ?? order.checkRassDate?.toISOString(),
|
||||
cashInvoiceExpiresAt: order.cashInvoiceExpiresAt?.toISOString(),
|
||||
createdAt: order.createdAt.toISOString(),
|
||||
updatedAt: order.updatedAt.toISOString(),
|
||||
@@ -138371,7 +138386,7 @@ var require_cash_payments_service = __commonJS({
|
||||
paidAmount: dto.paidAmount ?? dto.amount,
|
||||
transferDate: new Date(dto.transferDate),
|
||||
transferTime: dto.transferTime.trim(),
|
||||
trackingNumber: dto.trackingNumber.trim(),
|
||||
trackingNumber: dto.trackingNumber?.trim() || null,
|
||||
destinationAccountId: dto.destinationAccountId,
|
||||
receiptImage: dto.receiptImage || null,
|
||||
notes: dto.notes?.trim() || null,
|
||||
@@ -138563,6 +138578,7 @@ var require_cash_payment_dto = __commonJS({
|
||||
__metadata2("design:type", String)
|
||||
], SubmitCashReceiptDto.prototype, "transferTime", void 0);
|
||||
__decorate2([
|
||||
(0, class_validator_1.IsOptional)(),
|
||||
(0, class_validator_1.IsString)(),
|
||||
(0, class_validator_1.MinLength)(1),
|
||||
__metadata2("design:type", String)
|
||||
@@ -141367,7 +141383,8 @@ var require_suggestions_service = __commonJS({
|
||||
colorsJson: true,
|
||||
imageUrlsJson: true,
|
||||
widthCm: true,
|
||||
composition: true
|
||||
composition: true,
|
||||
weightGrams: true
|
||||
}
|
||||
},
|
||||
customer: { select: { firstName: true, lastName: true, phone: true } }
|
||||
@@ -141486,6 +141503,7 @@ var require_suggestions_service = __commonJS({
|
||||
colorImages,
|
||||
widthCm: r.product.widthCm ?? void 0,
|
||||
composition: r.product.composition?.trim() || void 0,
|
||||
weightGrams: r.product.weightGrams ?? void 0,
|
||||
isNew: r.product.isNew,
|
||||
productIsActive: r.product.isActive,
|
||||
note: r.note ?? void 0,
|
||||
@@ -142775,6 +142793,16 @@ var require_montact_auth_guard = __commonJS({
|
||||
var config_1 = require_config2();
|
||||
var jwt_1 = require_jwt();
|
||||
var jwt_secret_1 = require_jwt_secret();
|
||||
function normalizeMontactToken(raw2) {
|
||||
if (!raw2)
|
||||
return void 0;
|
||||
let t = raw2.trim();
|
||||
if (t.startsWith('"') && t.endsWith('"') || t.startsWith("'") && t.endsWith("'")) {
|
||||
t = t.slice(1, -1).trim();
|
||||
}
|
||||
return t.length > 0 ? t : void 0;
|
||||
}
|
||||
__name(normalizeMontactToken, "normalizeMontactToken");
|
||||
var MontactAuthGuard = class MontactAuthGuard {
|
||||
static {
|
||||
__name(this, "MontactAuthGuard");
|
||||
@@ -142792,7 +142820,7 @@ var require_montact_auth_guard = __commonJS({
|
||||
throw new common_12.UnauthorizedException("\u062A\u0648\u06A9\u0646 \u0646\u0627\u0645\u0639\u062A\u0628\u0631 \u06CC\u0627 \u0645\u0646\u0642\u0636\u06CC \u0634\u062F\u0647 \u0627\u0633\u062A");
|
||||
}
|
||||
const token = authHeader.slice(7).trim();
|
||||
const montactToken = this.config.get("MONTACT_API_TOKEN")?.trim();
|
||||
const montactToken = normalizeMontactToken(this.config.get("MONTACT_API_TOKEN"));
|
||||
if (montactToken && token === montactToken) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ export class CashPaymentsService {
|
||||
paidAmount: dto.paidAmount ?? dto.amount,
|
||||
transferDate: new Date(dto.transferDate),
|
||||
transferTime: dto.transferTime.trim(),
|
||||
trackingNumber: dto.trackingNumber.trim(),
|
||||
trackingNumber: dto.trackingNumber?.trim() || null,
|
||||
destinationAccountId: dto.destinationAccountId,
|
||||
receiptImage: dto.receiptImage || null,
|
||||
notes: dto.notes?.trim() || null,
|
||||
|
||||
@@ -29,9 +29,10 @@ export class SubmitCashReceiptDto {
|
||||
@MinLength(1)
|
||||
transferTime!: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MinLength(1)
|
||||
trackingNumber!: string;
|
||||
trackingNumber?: string;
|
||||
|
||||
@IsString()
|
||||
destinationAccountId!: string;
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
import { calculateCheckRass, invoiceAnchorDate } from './check-rass';
|
||||
|
||||
const checks = [
|
||||
{ amount: 100, dueDate: '2026-11-01T00:00:00.000Z' },
|
||||
{ amount: 100, dueDate: '2026-12-01T00:00:00.000Z' },
|
||||
];
|
||||
|
||||
describe('invoiceAnchorDate', () => {
|
||||
const createdAt = new Date('2026-09-01T06:00:00.000Z');
|
||||
|
||||
it('uses the latest invoice issue, not order creation', () => {
|
||||
const firstIssue = new Date('2026-10-01T08:00:00.000Z');
|
||||
const reissue = new Date('2026-10-05T08:00:00.000Z');
|
||||
expect(
|
||||
invoiceAnchorDate(createdAt, [
|
||||
{ toStatus: 'pricing', changedAt: new Date('2026-09-20T08:00:00.000Z') },
|
||||
{ toStatus: 'awaiting_settlement', changedAt: firstIssue },
|
||||
{ toStatus: 'pricing', changedAt: new Date('2026-10-04T08:00:00.000Z') },
|
||||
{ toStatus: 'awaiting_settlement', changedAt: reissue },
|
||||
]),
|
||||
).toEqual(reissue);
|
||||
});
|
||||
|
||||
it('falls back to the order creation date when no invoice was issued', () => {
|
||||
expect(
|
||||
invoiceAnchorDate(createdAt, [
|
||||
{ toStatus: 'awaiting_review', changedAt: createdAt },
|
||||
]),
|
||||
).toEqual(createdAt);
|
||||
});
|
||||
});
|
||||
|
||||
describe('calculateCheckRass', () => {
|
||||
const invoice = new Date('2026-10-01T08:00:00.000Z');
|
||||
|
||||
it('stays the same when the calendar day changes but the invoice date does not', () => {
|
||||
const onIssueDay = calculateCheckRass(checks, invoice);
|
||||
const stillOnInvoice = calculateCheckRass(checks, invoice);
|
||||
expect(stillOnInvoice).toEqual(onIssueDay);
|
||||
expect(onIssueDay.rassDays).toBe(46);
|
||||
expect(onIssueDay.rassDate).toBe('2026-11-16T00:00:00.000Z');
|
||||
});
|
||||
|
||||
it('is shorter when measured from a later day than the invoice', () => {
|
||||
const fromInvoice = calculateCheckRass(checks, invoice);
|
||||
const fromLater = calculateCheckRass(
|
||||
checks,
|
||||
new Date('2026-10-20T08:00:00.000Z'),
|
||||
);
|
||||
expect(fromLater.rassDays).toBeLessThan(fromInvoice.rassDays);
|
||||
});
|
||||
});
|
||||
@@ -19,9 +19,27 @@ import {
|
||||
tehranCalendarDay,
|
||||
} from './tehran-time';
|
||||
|
||||
/** وضعیتی که فاکتور قیمتگذاریشده برای مشتری ارسال میشود. */
|
||||
const INVOICE_ISSUED_STATUS = 'awaiting_settlement';
|
||||
|
||||
/**
|
||||
* مبدأ راس چک، تاریخ صدور فاکتور است نه امروز و نه تاریخ ثبت پیشسفارش.
|
||||
* اگر فاکتور دوباره ارسال شده باشد، آخرین صدور ملاک است.
|
||||
*/
|
||||
export function invoiceAnchorDate(
|
||||
createdAt: Date,
|
||||
history: { toStatus: string; changedAt: Date }[] = [],
|
||||
): Date {
|
||||
for (let i = history.length - 1; i >= 0; i -= 1) {
|
||||
const row = history[i];
|
||||
if (row?.toStatus === INVOICE_ISSUED_STATUS) return row.changedAt;
|
||||
}
|
||||
return createdAt;
|
||||
}
|
||||
|
||||
export function calculateCheckRass(
|
||||
checks: CheckRassInput[],
|
||||
baseDate: Date = new Date(),
|
||||
baseDate: Date,
|
||||
): CheckRassResult {
|
||||
if (checks.length === 0) {
|
||||
return {
|
||||
|
||||
@@ -8,6 +8,18 @@ import { ConfigService } from '@nestjs/config';
|
||||
import { JwtService } from '@nestjs/jwt';
|
||||
import { resolveJwtSecret } from '../common/utils/jwt-secret';
|
||||
|
||||
function normalizeMontactToken(raw?: string): string | undefined {
|
||||
if (!raw) return undefined;
|
||||
let t = raw.trim();
|
||||
if (
|
||||
(t.startsWith('"') && t.endsWith('"')) ||
|
||||
(t.startsWith("'") && t.endsWith("'"))
|
||||
) {
|
||||
t = t.slice(1, -1).trim();
|
||||
}
|
||||
return t.length > 0 ? t : undefined;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class MontactAuthGuard implements CanActivate {
|
||||
constructor(
|
||||
@@ -24,7 +36,9 @@ export class MontactAuthGuard implements CanActivate {
|
||||
throw new UnauthorizedException('توکن نامعتبر یا منقضی شده است');
|
||||
}
|
||||
const token = authHeader.slice(7).trim();
|
||||
const montactToken = this.config.get<string>('MONTACT_API_TOKEN')?.trim();
|
||||
const montactToken = normalizeMontactToken(
|
||||
this.config.get<string>('MONTACT_API_TOKEN'),
|
||||
);
|
||||
if (montactToken && token === montactToken) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -22,7 +22,10 @@ import {
|
||||
UpdateOrderStatusDto,
|
||||
} from './dto/order.dto';
|
||||
import { generateOrderNumber } from '../common/utils/order-number';
|
||||
import { calculateCheckRass } from '../common/utils/check-rass';
|
||||
import {
|
||||
calculateCheckRass,
|
||||
invoiceAnchorDate,
|
||||
} from '../common/utils/check-rass';
|
||||
import {
|
||||
usesCashReceiptPhase,
|
||||
usesCheckSettlement,
|
||||
@@ -616,7 +619,7 @@ export class OrdersService {
|
||||
dto.checkMode ?? order.checkMode ?? CheckRegistrationMode.full;
|
||||
const rass = calculateCheckRass(
|
||||
dto.checks.map((c) => ({ amount: c.amount, dueDate: c.dueDate })),
|
||||
order.createdAt,
|
||||
invoiceAnchorDate(order.createdAt, order.statusHistory),
|
||||
);
|
||||
|
||||
// ثبت اولیه و اصلاح، هر دو سفارش را به صف بررسی چک میبرند.
|
||||
@@ -890,6 +893,16 @@ export class OrdersService {
|
||||
);
|
||||
const hidePrices =
|
||||
!adminView && PRICE_HIDDEN_STATUSES.includes(order.status);
|
||||
const liveRass =
|
||||
order.checks.length > 0
|
||||
? calculateCheckRass(
|
||||
order.checks.map((check) => ({
|
||||
amount: check.amount,
|
||||
dueDate: check.dueDate,
|
||||
})),
|
||||
invoiceAnchorDate(order.createdAt, order.statusHistory),
|
||||
)
|
||||
: null;
|
||||
const base = {
|
||||
id: order.id,
|
||||
orderNumber: order.orderNumber,
|
||||
@@ -910,8 +923,10 @@ export class OrdersService {
|
||||
finalAmount: hidePrices ? null : order.finalAmount,
|
||||
pricesVisible: !hidePrices,
|
||||
notes: order.notes ?? undefined,
|
||||
checkRassDays: order.checkRassDays ?? undefined,
|
||||
checkRassDate: order.checkRassDate?.toISOString(),
|
||||
checkRassDays: liveRass
|
||||
? liveRass.rassDays
|
||||
: (order.checkRassDays ?? undefined),
|
||||
checkRassDate: liveRass?.rassDate ?? order.checkRassDate?.toISOString(),
|
||||
cashInvoiceExpiresAt: order.cashInvoiceExpiresAt?.toISOString(),
|
||||
createdAt: order.createdAt.toISOString(),
|
||||
updatedAt: order.updatedAt.toISOString(),
|
||||
|
||||
@@ -20,6 +20,7 @@ const PRODUCT_INCLUDE = {
|
||||
imageUrlsJson: true,
|
||||
widthCm: true,
|
||||
composition: true,
|
||||
weightGrams: true,
|
||||
},
|
||||
},
|
||||
customer: { select: { firstName: true, lastName: true, phone: true } },
|
||||
@@ -148,6 +149,7 @@ export class SuggestionsService {
|
||||
imageUrlsJson: unknown;
|
||||
widthCm: number | null;
|
||||
composition: string | null;
|
||||
weightGrams: number | null;
|
||||
};
|
||||
}) {
|
||||
const colorEntries = parseColorEntries(r.product.colorsJson, r.product.color);
|
||||
@@ -181,6 +183,7 @@ export class SuggestionsService {
|
||||
colorImages,
|
||||
widthCm: r.product.widthCm ?? undefined,
|
||||
composition: r.product.composition?.trim() || undefined,
|
||||
weightGrams: r.product.weightGrams ?? undefined,
|
||||
isNew: r.product.isNew,
|
||||
productIsActive: r.product.isActive,
|
||||
note: r.note ?? undefined,
|
||||
|
||||
Reference in New Issue
Block a user