diff --git a/tests/e2e/scenarios/stage-ui-negative-stock.spec.ts b/tests/e2e/scenarios/stage-ui-negative-stock.spec.ts index 566e9d7..b8d9502 100644 --- a/tests/e2e/scenarios/stage-ui-negative-stock.spec.ts +++ b/tests/e2e/scenarios/stage-ui-negative-stock.spec.ts @@ -64,4 +64,45 @@ test.describe('negative-stock admin UI', () => { // В таблице есть хотя бы одна строка с отрицательным остатком (красная). await expect(page.locator('tbody tr').first()).toBeVisible({ timeout: 15_000 }) }) + + test('колокольчик: фоновый джоб кладёт агрегат «N товаров ушли в минус»', async ({ page }) => { + test.setTimeout(480_000) // ждём cron-джоб (*/5), до ~6.5 мин + + const sess = await apiSignup('negbell') + const ctx = await apiRequest.newContext({ + baseURL: BASE, ignoreHTTPSErrors: true, timeout: 60_000, + extraHTTPHeaders: { Authorization: `Bearer ${sess.accessToken}` }, + }) + await ctx.post('/api/admin/seed-demo', { timeout: 60_000 }) + // интервал 1 мин — чтобы джоб разослал на ближайшем тике + const s = (await (await ctx.get('/api/organization/settings')).json()) + await ctx.put('/api/organization/settings', { data: { ...s, negStockNotifyEnabled: true, negStockNotifyIntervalMinutes: 1 } }) + + const storeId = (await (await ctx.get('/api/catalog/stores?pageSize=50')).json()).items[0].id + const currencyId = (await (await ctx.get('/api/catalog/currencies?pageSize=50')).json()) + .items.find((c: { code: string }) => c.code === 'KZT').id + const stock = (await (await ctx.get(`/api/inventory/stock?storeId=${storeId}&pageSize=200`)).json()).items + for (const row of stock.filter((x: { quantity: number }) => x.quantity > 0).slice(0, 2)) { + const loss = await ctx.post('/api/inventory/losses', { + data: { date: new Date().toISOString(), storeId, currencyId, reason: 0, notes: 'bell e2e', + lines: [{ productId: row.productId, quantity: Number(row.quantity) + 3, unitCost: 10 }] }, + }) + await ctx.post(`/api/inventory/losses/${(await loss.json()).id}/post`, { data: {} }) + } + + // Ждём, пока cron-джоб создаст персистентное уведомление. + let unread = 0 + for (let i = 0; i < 40 && unread === 0; i++) { + unread = (await (await ctx.get('/api/notifications/count')).json()).unread + if (unread === 0) await page.waitForTimeout(10_000) + } + expect(unread, 'джоб создал уведомление').toBeGreaterThan(0) + await ctx.dispose() + + // Открываем колокольчик — видно агрегат и кнопку «Пересчитать». + await attachSession(page, sess, '/dashboard') + await page.getByRole('button', { name: /Уведомления/ }).first().click() + await expect(page.getByText(/ушл.* в минус/i).first()).toBeVisible({ timeout: 10_000 }) + await expect(page.getByRole('button', { name: /Пересчитать/ }).first()).toBeVisible() + }) })