changes 1

This commit is contained in:
2026-10-29 15:37:44 +03:30
parent 68752f0403
commit 7b25c565fc
9 changed files with 152 additions and 15 deletions
+6
View File
@@ -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
View File
@@ -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;
}
+1 -1
View File
@@ -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,
+2 -1
View File
@@ -29,9 +29,10 @@ export class SubmitCashReceiptDto {
@MinLength(1)
transferTime!: string;
@IsOptional()
@IsString()
@MinLength(1)
trackingNumber!: string;
trackingNumber?: string;
@IsString()
destinationAccountId!: string;
+52
View File
@@ -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 -1
View File
@@ -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 {
+15 -1
View File
@@ -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;
}
+19 -4
View File
@@ -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(),
+3
View File
@@ -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,