d5112bd375
Task 4.2b — два экрана поверх готовых эндпоинтов. - SalesPayoutsView (начальник, #page-payouts): форма «Провести выплату» (менеджер из remaining, сумма, дата, комментарий) → POST /payouts → snackbar + перезагрузка; таблица «Остаток к выплате по менеджерам» (начислено/выплачено/остаток за период); «Журнал всех выплат». - SalesIncomeView (менеджер, #page-income): 4 KPI (оборот/начислено/ выплачено всего/к выплате), таблица «Начислено по клиентам» с ИТОГО, «Журнал выплат мне» с ИТОГО. - api/sales.ts: listSalesPayouts/getSalesPayoutsRemaining/createSalesPayout/ getSalesIncome. Роуты /sales/payouts и /sales/income со заглушек на экраны. Vitest 9/9, фронт-набор 1061 без регрессий, ESLint чист. Фаза 4 завершена: начальник проводит выплаты, менеджер видит начисления и выплаты. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
156 lines
6.3 KiB
TypeScript
156 lines
6.3 KiB
TypeScript
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||
import { mount, flushPromises } from '@vue/test-utils';
|
||
import { createVuetify } from 'vuetify';
|
||
import { createPinia } from 'pinia';
|
||
import { createRouter, createMemoryHistory } from 'vue-router';
|
||
import SalesPayoutsView from '../../resources/js/views/sales/SalesPayoutsView.vue';
|
||
|
||
// ─── mock api/sales ───────────────────────────────────────────────────────────
|
||
|
||
vi.mock('../../resources/js/api/sales', () => {
|
||
const remaining = [
|
||
{
|
||
manager_id: 11,
|
||
name: 'Алексей Смирнов',
|
||
clients_count: 7,
|
||
oborot_rub: 235050,
|
||
accrued_rub: 13350,
|
||
paid_period_rub: 60000,
|
||
paid_all_time_rub: 85000,
|
||
remaining_rub: -71650,
|
||
},
|
||
{
|
||
manager_id: 22,
|
||
name: 'Мария Котова',
|
||
clients_count: 5,
|
||
oborot_rub: 165300,
|
||
accrued_rub: 54795,
|
||
paid_period_rub: 30000,
|
||
paid_all_time_rub: 60000,
|
||
remaining_rub: -5205,
|
||
},
|
||
];
|
||
const payouts = [
|
||
{
|
||
id: 1,
|
||
sales_user_id: 11,
|
||
manager_name: 'Алексей Смирнов',
|
||
amount_rub: 35000,
|
||
paid_on: '2026-06-25',
|
||
comment: 'Зарплата за июнь, часть 1',
|
||
created_at: '2026-06-25T09:00:00Z',
|
||
creator_name: 'Виктор Орлов',
|
||
},
|
||
{
|
||
id: 2,
|
||
sales_user_id: 22,
|
||
manager_name: 'Мария Котова',
|
||
amount_rub: 30000,
|
||
paid_on: '2026-06-25',
|
||
comment: 'Зарплата за июнь',
|
||
created_at: '2026-06-25T09:05:00Z',
|
||
creator_name: 'Виктор Орлов',
|
||
},
|
||
];
|
||
return {
|
||
getSalesPayoutsRemaining: vi.fn().mockResolvedValue(remaining),
|
||
listSalesPayouts: vi.fn().mockResolvedValue(payouts),
|
||
createSalesPayout: vi.fn().mockResolvedValue({
|
||
id: 99,
|
||
sales_user_id: 11,
|
||
manager_name: 'Алексей Смирнов',
|
||
amount_rub: 12345,
|
||
paid_on: '2026-07-02',
|
||
comment: 'Тест',
|
||
created_at: '2026-07-02T10:00:00Z',
|
||
creator_name: 'Виктор Орлов',
|
||
}),
|
||
extractSalesErrorMessage: vi.fn().mockReturnValue('Произошла ошибка. Попробуйте позже.'),
|
||
};
|
||
});
|
||
|
||
beforeEach(() => vi.clearAllMocks());
|
||
|
||
// ─── helpers ──────────────────────────────────────────────────────────────────
|
||
|
||
function makeRouter() {
|
||
const router = createRouter({
|
||
history: createMemoryHistory(),
|
||
routes: [
|
||
{ path: '/sales/payouts', name: 'sales-payouts', component: SalesPayoutsView },
|
||
{ path: '/sales', name: 'sales-overview', component: { template: '<div/>' } },
|
||
],
|
||
});
|
||
return router.push('/sales/payouts').then(() => router);
|
||
}
|
||
|
||
async function mountView() {
|
||
const router = await makeRouter();
|
||
const wrapper = mount(SalesPayoutsView, {
|
||
global: { plugins: [createVuetify(), createPinia(), router] },
|
||
});
|
||
await flushPromises();
|
||
return wrapper;
|
||
}
|
||
|
||
describe('SalesPayoutsView', () => {
|
||
it('вызывает getSalesPayoutsRemaining и listSalesPayouts при монтировании', async () => {
|
||
await mountView();
|
||
const api = await import('../../resources/js/api/sales');
|
||
expect(api.getSalesPayoutsRemaining).toHaveBeenCalled();
|
||
expect(api.listSalesPayouts).toHaveBeenCalled();
|
||
});
|
||
|
||
it('рендерит таблицу остатка к выплате по менеджерам', async () => {
|
||
const wrapper = await mountView();
|
||
const text = wrapper.text();
|
||
expect(text).toContain('Остаток к выплате');
|
||
expect(text).toContain('Алексей Смирнов');
|
||
expect(text).toContain('Мария Котова');
|
||
// clients_count и суммы
|
||
expect(text).toContain('235');
|
||
expect(text).toContain('54');
|
||
const rows = wrapper.findAll('[data-testid="remaining-row"]');
|
||
expect(rows.length).toBe(2);
|
||
});
|
||
|
||
it('рендерит журнал всех выплат', async () => {
|
||
const wrapper = await mountView();
|
||
const rows = wrapper.findAll('[data-testid="payout-row"]');
|
||
expect(rows.length).toBe(2);
|
||
const text = wrapper.text();
|
||
expect(text).toContain('Зарплата за июнь, часть 1');
|
||
expect(text).toContain('Зарплата за июнь');
|
||
});
|
||
|
||
it('кнопка «Провести» вызывает createSalesPayout с верным payload и перезагружает', async () => {
|
||
const wrapper = await mountView();
|
||
const api = await import('../../resources/js/api/sales');
|
||
|
||
// Выбираем менеджера, сумму, дату, комментарий через модель компонента.
|
||
wrapper.vm.form.manager_id = 11;
|
||
wrapper.vm.form.amount_rub = 12345;
|
||
wrapper.vm.form.paid_on = '2026-07-02';
|
||
wrapper.vm.form.comment = 'Тест';
|
||
await flushPromises();
|
||
|
||
await wrapper.find('[data-testid="payout-submit"]').trigger('click');
|
||
await flushPromises();
|
||
|
||
expect(api.createSalesPayout).toHaveBeenCalledWith({
|
||
manager_id: 11,
|
||
amount_rub: 12345,
|
||
paid_on: '2026-07-02',
|
||
comment: 'Тест',
|
||
});
|
||
// после успешного создания — перезагрузка remaining (2 раза: mount + reload)
|
||
expect((api.getSalesPayoutsRemaining as ReturnType<typeof vi.fn>).mock.calls.length).toBeGreaterThanOrEqual(2);
|
||
expect((api.listSalesPayouts as ReturnType<typeof vi.fn>).mock.calls.length).toBeGreaterThanOrEqual(2);
|
||
});
|
||
|
||
it('заголовок «Выплаты менеджерам» отрисован', async () => {
|
||
const wrapper = await mountView();
|
||
expect(wrapper.text()).toContain('Выплаты менеджерам');
|
||
});
|
||
});
|