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:
+308
-113
@@ -1,6 +1,7 @@
|
||||
/**
|
||||
* تست دود جریان کامل سفارش: ورود مشتری، سبد، ثبت چکی و نقدی،
|
||||
* قیمتگذاری ادمین، رسید نقدی و بررسی وضعیتها.
|
||||
* تست دود گردش کامل سفارش مطابق روال کارفرما:
|
||||
* مشتری بدون دیدن قیمت سفارش میدهد، ادمین قیمت میگذارد و فاکتور میفرستد،
|
||||
* مشتری روش تسویه را انتخاب میکند و مسیر نقدی/چکی تا تایید نهایی پیش میرود.
|
||||
* اجرا: node scripts/smoke-order-flow.mjs
|
||||
*/
|
||||
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 };
|
||||
}
|
||||
|
||||
function jalaliIsoInDays(days) {
|
||||
const d = new Date(Date.now() + days * 86400000);
|
||||
return d.toISOString().slice(0, 10);
|
||||
function isoInDays(days) {
|
||||
return new Date(Date.now() + days * 86400000).toISOString().slice(0, 10);
|
||||
}
|
||||
|
||||
/** تصویر base64 بزرگتر از سقف پیشفرض ۱۰۰KB اکسپرس */
|
||||
@@ -51,6 +51,22 @@ function bigBase64(kb) {
|
||||
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() {
|
||||
console.log(`\n== Smoke test @ ${BASE} ==\n`);
|
||||
|
||||
@@ -65,7 +81,7 @@ async function main() {
|
||||
|
||||
const badVerify = await api('/auth/otp/verify', {
|
||||
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}`);
|
||||
|
||||
@@ -91,10 +107,15 @@ async function main() {
|
||||
const noToken = await api('/orders');
|
||||
check('بدون توکن ۴۰۱', noToken.status === 401, `status=${noToken.status}`);
|
||||
|
||||
console.log('\n[3] سبد خرید');
|
||||
console.log('\n[3] سبد خرید بدون قیمت');
|
||||
const companies = await api('/companies/public');
|
||||
const company = companies.data?.[0];
|
||||
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 product = (Array.isArray(products.data) ? products.data : products.data?.items)?.find(
|
||||
@@ -110,89 +131,63 @@ async function main() {
|
||||
body: { productId: product.id, meterage },
|
||||
});
|
||||
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] ثبت سفارش چکی با تصویر بزرگ');
|
||||
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 },
|
||||
});
|
||||
console.log('\n[4] ثبت سفارش بدون روش تسویه و بدون قیمت');
|
||||
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',
|
||||
token: userToken,
|
||||
body: {
|
||||
companyId: company.id,
|
||||
settlementType: 'cash',
|
||||
items: [{ productId: product.id, meterage }],
|
||||
amount: 1000,
|
||||
transferDate: isoInDays(0),
|
||||
transferTime: '12:00',
|
||||
trackingNumber: '1',
|
||||
destinationAccountId: 'x',
|
||||
},
|
||||
});
|
||||
check('ثبت سفارش نقدی', cashOrder.status < 300, `status=${cashOrder.status} ${JSON.stringify(cashOrder.data).slice(0, 200)}`);
|
||||
const cashOrderId = cashOrder.data?.id;
|
||||
const expiry = cashOrder.data?.cashInvoiceExpiresAt;
|
||||
check('انقضای فاکتور در آینده است', !!expiry && new Date(expiry) > new Date(), `expiry=${expiry}`);
|
||||
check('رسید قبل از فاکتور رد میشود', earlyReceipt.status === 400, `status=${earlyReceipt.status}`);
|
||||
|
||||
console.log('\n[7] سفارش در لیست مشتری دیده میشود');
|
||||
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] لیست و فیلتر ادمین');
|
||||
console.log('\n[6] سطلهای وضعیت ادمین');
|
||||
const adminOrders = await api('/admin/orders', { token: adminToken });
|
||||
const adminIds = (adminOrders.data ?? []).map((o) => o.id);
|
||||
check('هر دو سفارش برای ادمین', adminIds.includes(checkOrderId) && adminIds.includes(cashOrderId));
|
||||
const adminStatuses = new Set((adminOrders.data ?? []).map((o) => o.adminStatus));
|
||||
check('سفارش برای ادمین دیده میشود', adminIds.includes(cashOrderId));
|
||||
const allowedTabs = new Set([
|
||||
'pending_review',
|
||||
'awaiting_customer',
|
||||
'pending_check',
|
||||
'pending_payment',
|
||||
'needs_correction',
|
||||
'check_correction',
|
||||
'final_approval',
|
||||
@@ -202,20 +197,35 @@ async function main() {
|
||||
'rejected',
|
||||
'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(',')}`);
|
||||
|
||||
const filtered = await api('/admin/orders?status=pending_review', { token: adminToken });
|
||||
check(
|
||||
'فیلتر «در انتظار بررسی» سفارشها را برمیگرداند',
|
||||
'فیلتر «در انتظار بررسی» سفارش را برمیگرداند',
|
||||
(filtered.data ?? []).some((o) => o.id === cashOrderId),
|
||||
`count=${(filtered.data ?? []).length}`,
|
||||
);
|
||||
|
||||
console.log('\n[9] قیمتگذاری ادمین');
|
||||
console.log('\n[7] قیمتگذاری ادمین');
|
||||
const detail = await api(`/admin/orders/${cashOrderId}`, { token: adminToken });
|
||||
const items = detail.data?.items ?? [];
|
||||
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`, {
|
||||
method: 'PATCH',
|
||||
token: adminToken,
|
||||
@@ -225,17 +235,13 @@ async function main() {
|
||||
},
|
||||
});
|
||||
const expectedTotal = 100000 * meterage;
|
||||
const expectedFinal = expectedTotal + 50000;
|
||||
check('قیمتگذاری موفق', pricing.status < 300, `status=${pricing.status} ${JSON.stringify(pricing.data).slice(0, 150)}`);
|
||||
check(
|
||||
'جمع کل درست است',
|
||||
Math.abs((pricing.data?.totalAmount ?? 0) - expectedTotal) < 0.01,
|
||||
`total=${pricing.data?.totalAmount} expected=${expectedTotal}`,
|
||||
);
|
||||
check(
|
||||
'مبلغ نهایی = جمع + حمل',
|
||||
Math.abs((pricing.data?.finalAmount ?? 0) - (expectedTotal + 50000)) < 0.01,
|
||||
`final=${pricing.data?.finalAmount}`,
|
||||
);
|
||||
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}`);
|
||||
|
||||
const stillHidden = await api(`/orders/${cashOrderId}`, { token: userToken });
|
||||
check('قیمت تا ارسال فاکتور برای مشتری پنهان است', stillHidden.data?.finalAmount == null, `final=${stillHidden.data?.finalAmount}`);
|
||||
|
||||
const badDiscount = await api(`/admin/orders/${cashOrderId}/pricing`, {
|
||||
method: 'PATCH',
|
||||
@@ -244,7 +250,64 @@ async function main() {
|
||||
});
|
||||
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 account = (accounts.data ?? [])[0];
|
||||
check('حساب بانکی موجود', !!account, JSON.stringify(accounts.data).slice(0, 120));
|
||||
@@ -253,8 +316,8 @@ async function main() {
|
||||
method: 'POST',
|
||||
token: userToken,
|
||||
body: {
|
||||
amount: expectedTotal + 50000,
|
||||
transferDate: jalaliIsoInDays(0),
|
||||
amount: expectedFinal,
|
||||
transferDate: isoInDays(0),
|
||||
transferTime: '12:00',
|
||||
trackingNumber: '987654321',
|
||||
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)}`);
|
||||
|
||||
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(
|
||||
'وضعیت به تأیید پرداخت رفت',
|
||||
afterReceipt.data?.status === 'awaiting_cash_confirmation',
|
||||
`status=${afterReceipt.data?.status}`,
|
||||
'رد رسید مشتری را به مرحله پرداخت برمیگرداند',
|
||||
afterReject.data?.status === 'awaiting_cash_payment',
|
||||
`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';
|
||||
await api('/auth/otp/request', {
|
||||
method: 'POST',
|
||||
@@ -285,31 +484,27 @@ async function main() {
|
||||
if (otherToken) {
|
||||
const foreign = await api(`/orders/${cashOrderId}`, { token: otherToken });
|
||||
check('سفارش دیگری قابل خواندن نیست', foreign.status === 403, `status=${foreign.status}`);
|
||||
const foreignReceipt = await api(`/orders/${cashOrderId}/cash-receipt`, {
|
||||
const foreignSettlement = await api(`/orders/${checkOrderId}/settlement`, {
|
||||
method: 'POST',
|
||||
token: otherToken,
|
||||
body: {
|
||||
amount: 1000,
|
||||
transferDate: jalaliIsoInDays(0),
|
||||
transferTime: '12:00',
|
||||
trackingNumber: '1',
|
||||
destinationAccountId: account.id,
|
||||
},
|
||||
body: { settlementType: 'cash' },
|
||||
});
|
||||
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] محافظت از ویرایش چک بعد از تأیید');
|
||||
const approve = await api(`/admin/orders/${checkOrderId}/status`, {
|
||||
console.log('\n[14] محافظت از ویرایش بعد از تحویل');
|
||||
const forceDeliver = await api(`/admin/orders/${checkOrderId}/status`, {
|
||||
method: 'PATCH',
|
||||
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`, {
|
||||
method: 'PATCH',
|
||||
token: userToken,
|
||||
body: { checks: [{ amount: 1, dueDate: jalaliIsoInDays(10), mode: 'brief' }] },
|
||||
body: { checks: [checkPayload(1000, 10)] },
|
||||
});
|
||||
check('ویرایش چک بعد از تحویل رد میشود', lateEdit.status === 400, `status=${lateEdit.status}`);
|
||||
const lateReceipt = await api(`/orders/${checkOrderId}/cash-receipt`, {
|
||||
@@ -317,7 +512,7 @@ async function main() {
|
||||
token: userToken,
|
||||
body: {
|
||||
amount: 1000,
|
||||
transferDate: jalaliIsoInDays(0),
|
||||
transferDate: isoInDays(0),
|
||||
transferTime: '12:00',
|
||||
trackingNumber: '1',
|
||||
destinationAccountId: account.id,
|
||||
|
||||
Reference in New Issue
Block a user