207 lines
9.3 KiB
PHP
207 lines
9.3 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Models\SalesProspect;
|
|
use App\Models\SalesUser;
|
|
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
|
|
|
uses(DatabaseTransactions::class);
|
|
|
|
function int_user(string $role = 'manager', bool $active = true): SalesUser
|
|
{
|
|
return SalesUser::create([
|
|
'name' => 'U '.uniqid(), 'email' => 'in'.uniqid().'@s.local',
|
|
'password' => bcrypt('secret'), 'role' => $role, 'is_active' => $active,
|
|
]);
|
|
}
|
|
|
|
function int_head(): SalesUser
|
|
{
|
|
return SalesUser::where('role', 'head')->first() ?? int_user('head');
|
|
}
|
|
|
|
beforeEach(function () {
|
|
config(['sales.integration_token' => 'TESTTOKEN']);
|
|
});
|
|
|
|
// ── сервис-токен ───────────────────────────────────────────────────────────────
|
|
|
|
test('без токена сервис-канал → 401', function () {
|
|
$this->getJson('/api/sales/integration/managers')->assertUnauthorized();
|
|
});
|
|
|
|
test('неверный токен → 401', function () {
|
|
$this->getJson('/api/sales/integration/managers', ['X-Sales-Token' => 'WRONG'])
|
|
->assertUnauthorized();
|
|
});
|
|
|
|
test('пустой конфиг-токен → 401 даже при совпадении пустого', function () {
|
|
config(['sales.integration_token' => null]);
|
|
$this->getJson('/api/sales/integration/managers', ['X-Sales-Token' => ''])
|
|
->assertUnauthorized();
|
|
});
|
|
|
|
// ── managers ────────────────────────────────────────────────────────────────────
|
|
|
|
test('managers отдаёт активных менеджеров (вкл. начальника), неактивных нет', function () {
|
|
$head = int_head();
|
|
$m = int_user('manager', true);
|
|
$off = int_user('manager', false);
|
|
|
|
$res = $this->getJson('/api/sales/integration/managers', ['X-Sales-Token' => 'TESTTOKEN']);
|
|
|
|
$res->assertOk();
|
|
$ids = collect($res->json('data'))->pluck('id');
|
|
expect($ids)->toContain($head->id)->toContain($m->id)->not->toContain($off->id);
|
|
});
|
|
|
|
// ── ingest ────────────────────────────────────────────────────────────────────
|
|
|
|
test('ingest создаёт карточку stage=new с payload; повтор по inn → skipped, payload обновлён', function () {
|
|
$head = int_head();
|
|
$mgr = int_user('manager');
|
|
|
|
$body = ['sales_user_id' => $mgr->id, 'firms' => [[
|
|
'firm_name' => 'ООО Ромашка', 'city' => 'Ростов', 'phone' => '+7863', 'site' => 'romashka.ru',
|
|
'inn' => '6161234567', 'rating_label' => 'горячая', 'payload' => ['director' => 'Иванов'],
|
|
]]];
|
|
|
|
$r1 = $this->postJson('/api/sales/integration/prospects', $body, ['X-Sales-Token' => 'TESTTOKEN']);
|
|
$r1->assertOk();
|
|
expect($r1->json('created'))->toBe(1);
|
|
$p = SalesProspect::where('sales_user_id', $mgr->id)->where('inn', '6161234567')->first();
|
|
expect($p->stage)->toBe('new');
|
|
expect($p->payload['director'])->toBe('Иванов');
|
|
|
|
// Повтор той же фирмы → не плодит, payload обновляется.
|
|
$body['firms'][0]['payload'] = ['director' => 'Петров'];
|
|
$r2 = $this->postJson('/api/sales/integration/prospects', $body, ['X-Sales-Token' => 'TESTTOKEN']);
|
|
$r2->assertOk();
|
|
expect($r2->json('skipped'))->toBe(1);
|
|
expect(SalesProspect::where('sales_user_id', $mgr->id)->where('inn', '6161234567')->count())->toBe(1);
|
|
expect(SalesProspect::where('sales_user_id', $mgr->id)->where('inn', '6161234567')->first()->payload['director'])
|
|
->toBe('Петров');
|
|
});
|
|
|
|
test('ingest дедуп по phone, когда inn пуст', function () {
|
|
$mgr = int_user('manager');
|
|
int_head();
|
|
|
|
$body = ['sales_user_id' => $mgr->id, 'firms' => [[
|
|
'firm_name' => 'Без ИНН', 'city' => 'Ростов', 'phone' => '+79990001122', 'site' => null,
|
|
'inn' => null, 'rating_label' => null, 'payload' => [],
|
|
]]];
|
|
|
|
$this->postJson('/api/sales/integration/prospects', $body, ['X-Sales-Token' => 'TESTTOKEN'])->assertOk();
|
|
$r2 = $this->postJson('/api/sales/integration/prospects', $body, ['X-Sales-Token' => 'TESTTOKEN']);
|
|
|
|
expect($r2->json('skipped'))->toBe(1);
|
|
expect(SalesProspect::where('sales_user_id', $mgr->id)->where('phone', '+79990001122')->count())->toBe(1);
|
|
});
|
|
|
|
test('ingest: невалидный sales_user_id → 422', function () {
|
|
int_head();
|
|
$this->postJson('/api/sales/integration/prospects', [
|
|
'sales_user_id' => 999999, 'firms' => [['firm_name' => 'X']],
|
|
], ['X-Sales-Token' => 'TESTTOKEN'])->assertStatus(422);
|
|
});
|
|
|
|
test('ingest проставляет assigned_by = начальник', function () {
|
|
$head = int_head();
|
|
$mgr = int_user('manager');
|
|
$this->postJson('/api/sales/integration/prospects', [
|
|
'sales_user_id' => $mgr->id,
|
|
'firms' => [['firm_name' => 'A', 'inn' => '7700000001', 'payload' => []]],
|
|
], ['X-Sales-Token' => 'TESTTOKEN'])->assertOk();
|
|
|
|
$p = SalesProspect::where('sales_user_id', $mgr->id)->where('inn', '7700000001')->first();
|
|
expect($p->assigned_by)->toBe($head->id);
|
|
});
|
|
|
|
// ── ниша списка ────────────────────────────────────────────────────────────────
|
|
|
|
test('приём фирм пишет нишу в карточку', function () {
|
|
int_head();
|
|
$mgr = int_user('manager');
|
|
|
|
$this->postJson('/api/sales/integration/prospects', [
|
|
'sales_user_id' => $mgr->id,
|
|
'firms' => [['firm_name' => 'Оконикс', 'inn' => '7701'.random_int(100000, 999999), 'rubric' => 'Окна']],
|
|
], ['X-Sales-Token' => 'TESTTOKEN'])->assertOk();
|
|
|
|
expect(SalesProspect::where('sales_user_id', $mgr->id)->value('rubric'))->toBe('Окна');
|
|
});
|
|
|
|
test('приём без ниши по-прежнему проходит', function () {
|
|
int_head();
|
|
$mgr = int_user('manager');
|
|
|
|
$this->postJson('/api/sales/integration/prospects', [
|
|
'sales_user_id' => $mgr->id,
|
|
'firms' => [['firm_name' => 'Без ниши', 'inn' => '7702'.random_int(100000, 999999)]],
|
|
], ['X-Sales-Token' => 'TESTTOKEN'])->assertOk();
|
|
|
|
expect(SalesProspect::where('sales_user_id', $mgr->id)->value('rubric'))->toBeNull();
|
|
});
|
|
|
|
test('ниша приклеивается к уже существующему написанию', function () {
|
|
int_head();
|
|
$mgr = int_user('manager');
|
|
SalesProspect::factory()->for($mgr, 'salesUser')->create(['rubric' => 'Окна']);
|
|
|
|
$this->postJson('/api/sales/integration/prospects', [
|
|
'sales_user_id' => $mgr->id,
|
|
'firms' => [['firm_name' => 'Второй', 'inn' => '7703'.random_int(100000, 999999), 'rubric' => 'ОКНА']],
|
|
], ['X-Sales-Token' => 'TESTTOKEN'])->assertOk();
|
|
|
|
expect(SalesProspect::where('firm_name', 'Второй')->value('rubric'))->toBe('Окна');
|
|
});
|
|
|
|
test('повторная отдача не затирает нишу, поправленную руками', function () {
|
|
int_head();
|
|
$mgr = int_user('manager');
|
|
$inn = '7704'.random_int(100000, 999999);
|
|
$body = [
|
|
'sales_user_id' => $mgr->id,
|
|
'firms' => [['firm_name' => 'Дубль', 'inn' => $inn, 'rubric' => 'Окна']],
|
|
];
|
|
|
|
$this->postJson('/api/sales/integration/prospects', $body, ['X-Sales-Token' => 'TESTTOKEN'])->assertOk();
|
|
SalesProspect::where('inn', $inn)->update(['rubric' => 'Кровля']);
|
|
$this->postJson('/api/sales/integration/prospects', $body, ['X-Sales-Token' => 'TESTTOKEN'])->assertOk();
|
|
|
|
expect(SalesProspect::where('inn', $inn)->value('rubric'))->toBe('Кровля');
|
|
});
|
|
|
|
// ── разовая проставка ниш ─────────────────────────────────────────────────────
|
|
|
|
test('ручка проставки заполняет пустую нишу по ИНН', function () {
|
|
$mgr = int_user('manager');
|
|
$p = SalesProspect::factory()->for($mgr, 'salesUser')->create(['inn' => '7706123456', 'rubric' => null]);
|
|
|
|
$this->postJson('/api/sales/integration/prospect-rubrics', [
|
|
'items' => [['rubric' => 'Окна', 'inn' => '7706123456']],
|
|
], ['X-Sales-Token' => 'TESTTOKEN'])
|
|
->assertOk()
|
|
->assertJson(['filled' => 1, 'not_found' => 0]);
|
|
|
|
expect($p->fresh()->rubric)->toBe('Окна');
|
|
});
|
|
|
|
test('ручка проставки не трогает уже проставленную нишу', function () {
|
|
$mgr = int_user('manager');
|
|
$p = SalesProspect::factory()->for($mgr, 'salesUser')->create(['inn' => '7706999999', 'rubric' => 'Кровля']);
|
|
|
|
$this->postJson('/api/sales/integration/prospect-rubrics', [
|
|
'items' => [['rubric' => 'Окна', 'inn' => '7706999999']],
|
|
], ['X-Sales-Token' => 'TESTTOKEN'])->assertOk();
|
|
|
|
expect($p->fresh()->rubric)->toBe('Кровля');
|
|
});
|
|
|
|
test('ручка проставки без токена → 401', function () {
|
|
$this->postJson('/api/sales/integration/prospect-rubrics', ['items' => []])
|
|
->assertUnauthorized();
|
|
});
|