Files
portal/app/app/Services/ClientTg/RobotResult.php
T
Дмитрий 5211048bce feat(телеграм-реклама): Этап 2 закрыт — робот несёт фактическую стоимость (2.2), билинг МТС подтверждён
Задача 2.0 (разведка) — РЕШЕНА без входа в кабинет: ответ уже был в
находках Фазы 2. Билинг МТС за «показы своей базе» — НАКОПИТЕЛЬНЫЙ
(резерв → списание по факту показов → возврат остатка, экран /payment).
Следствие: списание «по факту» (была задача 2.4) на успехе отправки честно
сделать нельзя — показов ещё нет; перенесено в Этап 3 (опрос завершения).
Этап 2 закрыт составом 2.1 (отмена) + 2.3 (возврат брони) + 2.2.

- 2.2 PHP: RobotResult несёт `actualCostRub` (nullable string) — заготовка,
  чтобы позже (Этап 3) прочитать фактическую стоимость из кабинета и списать
  её с кошелька клиента. Проброшено в fromRobotJson (робот начнёт класть поле
  позже; нет поля → null). Мёртвых фабрик launched()/draftReady() не добавлял.
- 2.2 Node: чистая утилита `parseCost(text)` в cabinet.js — «Стоимость
  кампании от 201,6 ₽» → «201.60» (запятая→точка, разделители тысяч включая
  неразрывный пробел код 160, дробь до 2 знаков без округления, нет числа →
  null). Вынесена отдельной покрытой функцией; к DOM-потоку НЕ подключена
  (селектор строки стоимости подтвердим живьём в Этапе 3).
- Разведка билинга и решение по 2.4 зафиксированы в FLOW-FINDINGS.md; план
  Этапа 2 обновлён (2.0 решён, 2.4 → Этап 3, Этап 2 закрыт).

TDD, робот замокан, тесты на liderra_testing.
Приёмка: Node 40/40 (npm test), Pest RobotResultTest 5/5 + RobotRunnerTest
(потребитель) зелёный, регрессия ClientTg 120/120, phpstan RobotResult.php 0,
pint чисто, deptrac 0.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-28 07:52:56 +03:00

55 lines
2.2 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
declare(strict_types=1);
namespace App\Services\ClientTg;
/**
* Итог запуска Node-робота (bin/run.js). Зеркалит контракт runner.js:
* успех → {ok:true, matched, launched?, campaignId?, actualCostRub?}
* отказ → {ok:false, step, reason}
* А также «обёрточные» отказы (таймаут/непарсабельный вывод) — тоже ok:false с
* причиной, чтобы воркер не падал.
*
* actualCostRub — фактическая стоимость кампании из кабинета, строка вида
* «201.60» (или отсутствует → null; робот начнёт класть это поле позже).
*/
final class RobotResult
{
public function __construct(
public readonly bool $ok,
public readonly int $matched = 0,
public readonly bool $launched = false,
public readonly ?string $campaignId = null,
public readonly ?string $step = null,
public readonly ?string $reason = null,
public readonly ?string $actualCostRub = null,
) {}
/**
* Разбирает JSON робота. Отсутствие/ложь поля ok трактуется как отказ.
*
* @param array<string, mixed> $json
*/
public static function fromRobotJson(array $json): self
{
$ok = ($json['ok'] ?? false) === true;
return new self(
ok: $ok,
matched: (int) ($json['matched'] ?? 0),
launched: (bool) ($json['launched'] ?? false),
campaignId: isset($json['campaignId']) ? (string) $json['campaignId'] : null,
step: isset($json['step']) ? (string) $json['step'] : null,
reason: isset($json['reason']) ? (string) $json['reason'] : null,
actualCostRub: isset($json['actualCostRub']) ? (string) $json['actualCostRub'] : null,
);
}
/** Обёрточный отказ (таймаут, непарсабельный вывод, сбой процесса). */
public static function failed(string $reason, ?string $step = null): self
{
return new self(ok: false, step: $step, reason: $reason);
}
}