From 9cabe8ded44fb0aafdaec405b7bfafbf88a9e079 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9?= Date: Fri, 22 May 2026 09:32:37 +0300 Subject: [PATCH] =?UTF-8?q?feat(supplier-import):=20buildPlan=20=E2=80=94?= =?UTF-8?q?=20site/call=20=D0=B3=D1=80=D1=83=D0=BF=D0=BF=D0=B8=D1=80=D0=BE?= =?UTF-8?q?=D0=B2=D0=BA=D0=B0=20B1/B2/B3,=20=D0=BB=D0=B8=D0=BC=D0=B8=D1=82?= =?UTF-8?q?=3D=D1=81=D1=83=D0=BC=D0=BC=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Import/SupplierProjectImporter.php | 143 ++++++++++++++++++ .../Supplier/SupplierProjectImporterTest.php | 65 ++++++++ 2 files changed, 208 insertions(+) create mode 100644 app/app/Services/Supplier/Import/SupplierProjectImporter.php create mode 100644 app/tests/Feature/Supplier/SupplierProjectImporterTest.php diff --git a/app/app/Services/Supplier/Import/SupplierProjectImporter.php b/app/app/Services/Supplier/Import/SupplierProjectImporter.php new file mode 100644 index 00000000..2bab067a --- /dev/null +++ b/app/app/Services/Supplier/Import/SupplierProjectImporter.php @@ -0,0 +1,143 @@ +>, skipped: list} + */ + public function buildPlan(int $tenantId): array + { + $rows = $this->client->listProjects(); + + /** @var list $skipped */ + $skipped = []; + + /** @var array> $groups */ + $groups = []; + + foreach ($rows as $row) { + if (($row['status'] ?? false) !== true) { + continue; + } + + $platform = SupplierImportMapper::platformFromSrc((string) ($row['src'] ?? '')); + if ($platform === null) { + $skipped[] = ['reason' => 'unsupported_source', 'label' => (string) ($row['name'] ?? $row['content'] ?? '?')]; + + continue; + } + + $signalType = SupplierImportMapper::signalTypeFromType((string) ($row['type'] ?? '')); + if ($signalType === null) { + $skipped[] = ['reason' => 'unsupported_type', 'label' => (string) ($row['name'] ?? '?')]; + + continue; + } + + if ($signalType === 'sms') { + continue; + } + + $identifier = (string) ($row['content'] ?? ''); + $key = $signalType.'|'.$identifier; + + if (! isset($groups[$key])) { + $groups[$key] = [ + 'signal_type' => $signalType, + 'signal_identifier' => $identifier, + 'sms_senders' => [], + 'sms_keyword' => null, + 'tag' => '', + 'regions' => [], + 'has_all_russia' => false, + 'workdays_mask' => 0, + 'daily_limit_target' => 0, + 'platforms' => [], + ]; + } + + $this->accumulateRow($groups[$key], $row, $platform); + } + + $planned = []; + foreach ($groups as $g) { + unset($g['has_all_russia']); + $g['delivery_days_mask'] = $g['workdays_mask'] === 0 ? 127 : $g['workdays_mask']; + unset($g['workdays_mask']); + if ($g['tag'] === '') { + $g['tag'] = 'РФ'; + } + $g['name'] = $this->deriveName($g); + $planned[] = $g; + } + + return ['planned' => array_values($planned), 'skipped' => $skipped]; + } + + /** + * @param array $group + * @param array $row + */ + private function accumulateRow(array &$group, array $row, string $platform): void + { + $lim = (int) ($row['lim'] ?? 0); + $group['daily_limit_target'] += $lim; + $group['platforms'][] = [ + 'platform' => $platform, + 'external_id' => (int) ($row['id'] ?? 0), + 'lim' => $lim, + ]; + + $rowTag = trim((string) ($row['tag'] ?? '')); + if ($group['tag'] === '' && $rowTag !== '' && $rowTag !== 'РФ') { + $group['tag'] = $rowTag; + } + + $group['workdays_mask'] |= SupplierImportMapper::workdaysToMask((array) ($row['workdays'] ?? [])); + + if (! $group['has_all_russia']) { + $gibdd = SupplierImportMapper::parseGibddRegions( + is_string($row['regions'] ?? null) ? $row['regions'] : '' + ); + if ($gibdd === []) { + $group['has_all_russia'] = true; + $group['regions'] = []; + } else { + $liderra = SupplierRegions::mapFromSupplier($gibdd); + $group['regions'] = array_values(array_unique(array_merge($group['regions'], $liderra))); + sort($group['regions']); + } + } + } + + /** + * @param array $group + */ + private function deriveName(array $group): string + { + $tag = (string) $group['tag']; + $base = ($tag !== '' && $tag !== 'РФ') + ? $tag + : (string) ($group['signal_identifier'] ?? 'проект'); + + return mb_substr($base, 0, 255); + } +} diff --git a/app/tests/Feature/Supplier/SupplierProjectImporterTest.php b/app/tests/Feature/Supplier/SupplierProjectImporterTest.php new file mode 100644 index 00000000..22e01e9e --- /dev/null +++ b/app/tests/Feature/Supplier/SupplierProjectImporterTest.php @@ -0,0 +1,65 @@ +> $rows + */ +function importerWithRows(array $rows): SupplierProjectImporter +{ + $client = Mockery::mock(SupplierPortalClient::class); + $client->shouldReceive('listProjects')->andReturn($rows); + + return new SupplierProjectImporter($client); +} + +test('buildPlan groups B1/B2/B3 call rows into one planned project, limit = sum', function (): void { + $tenant = Tenant::factory()->create(); + + $plan = importerWithRows([ + ['id' => '4001', 'src' => 'rt', 'type' => 'calls', 'content' => '79991112233', 'tag' => 'Каранга', 'lim' => '6', 'status' => true, 'regions' => '', 'workdays' => ['1', '2', '3', '4', '5']], + ['id' => '4002', 'src' => 'bl', 'type' => 'calls', 'content' => '79991112233', 'tag' => 'Каранга', 'lim' => '6', 'status' => true, 'regions' => '', 'workdays' => ['1', '2', '3', '4', '5']], + ['id' => '4003', 'src' => 'mt', 'type' => 'calls', 'content' => '79991112233', 'tag' => 'Каранга', 'lim' => '6', 'status' => true, 'regions' => '', 'workdays' => ['1', '2', '3', '4', '5']], + ])->buildPlan($tenant->id); + + expect($plan['planned'])->toHaveCount(1); + $p = $plan['planned'][0]; + expect($p['signal_type'])->toBe('call'); + expect($p['signal_identifier'])->toBe('79991112233'); + expect($p['daily_limit_target'])->toBe(18); + expect($p['delivery_days_mask'])->toBe(31); + expect($p['tag'])->toBe('Каранга'); + expect($p['regions'])->toBe([]); + expect(collect($p['platforms'])->pluck('platform')->sort()->values()->all())->toBe(['B1', 'B2', 'B3']); + expect(collect($p['platforms'])->firstWhere('platform', 'B1')['external_id'])->toBe(4001); +}); + +test('buildPlan skips inactive rows (status=false)', function (): void { + $tenant = Tenant::factory()->create(); + + $plan = importerWithRows([ + ['id' => '5001', 'src' => 'rt', 'type' => 'calls', 'content' => '79995550000', 'tag' => 'X', 'lim' => '5', 'status' => false, 'regions' => '', 'workdays' => []], + ])->buildPlan($tenant->id); + + expect($plan['planned'])->toHaveCount(0); +}); + +test('buildPlan skips dop2 (unsupported source) and reports it', function (): void { + $tenant = Tenant::factory()->create(); + + $plan = importerWithRows([ + ['id' => '6001', 'src' => 'dop2', 'type' => 'calls', 'content' => '79996660000', 'tag' => 'X', 'lim' => '5', 'status' => true, 'regions' => '', 'workdays' => []], + ])->buildPlan($tenant->id); + + expect($plan['planned'])->toHaveCount(0); + expect(collect($plan['skipped'])->pluck('reason'))->toContain('unsupported_source'); +});