added automation
This commit is contained in:
@@ -0,0 +1,222 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\DB\Te\TableEngine;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Automation;
|
||||
use App\Services\Automation\AutomationCatalog;
|
||||
use App\Services\Automation\AutomationEngine;
|
||||
use App\Services\Automation\AutomationFingerprint;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Str;
|
||||
use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException;
|
||||
|
||||
class AutomationController extends Controller
|
||||
{
|
||||
public function table(Request $request)
|
||||
{
|
||||
$query = Automation::query();
|
||||
TableEngine::create($query, $request->all())
|
||||
->initSearch(['name', 'code', 'title', 'body'])
|
||||
->select(['*'])
|
||||
->export($query);
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
public function catalog()
|
||||
{
|
||||
return response()->json(app(AutomationCatalog::class)->toArray());
|
||||
}
|
||||
|
||||
public function preview(Request $request)
|
||||
{
|
||||
$conditions = array_values((array) $request->input('conditions', []));
|
||||
$count = app(AutomationEngine::class)->previewCount($conditions);
|
||||
|
||||
return response()->json(['count' => $count]);
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$data = $this->validated($request);
|
||||
$data['created_by'] = auth()->id();
|
||||
$row = Automation::create($data);
|
||||
|
||||
return response()->json($row);
|
||||
}
|
||||
|
||||
public function update(Request $request, $id)
|
||||
{
|
||||
$row = Automation::query()->findOrFail($id);
|
||||
$data = $this->validated($request, (int) $row->id);
|
||||
$row->fill($data)->save();
|
||||
|
||||
return response()->json($row);
|
||||
}
|
||||
|
||||
public function toggle($id)
|
||||
{
|
||||
$row = Automation::query()->findOrFail($id);
|
||||
if (! $row->is_active) {
|
||||
$dup = $this->duplicateOf($row->logic_fingerprint, (int) $row->id, true);
|
||||
if ($dup) {
|
||||
throw new UnprocessableEntityHttpException(
|
||||
'اتوماسیون مشابه فعال است: '.$dup->name
|
||||
);
|
||||
}
|
||||
}
|
||||
$row->is_active = ! $row->is_active;
|
||||
$row->save();
|
||||
|
||||
return response()->json(['is_active' => $row->is_active]);
|
||||
}
|
||||
|
||||
public function destroy($id)
|
||||
{
|
||||
Automation::query()->findOrFail($id)->delete();
|
||||
|
||||
return response()->json(true);
|
||||
}
|
||||
|
||||
private function validated(Request $request, ?int $ignoreId = null): array
|
||||
{
|
||||
$name = trim((string) $request->input('name', ''));
|
||||
$title = trim((string) $request->input('title', ''));
|
||||
$body = trim((string) $request->input('body', ''));
|
||||
if ($name === '' || $title === '' || $body === '') {
|
||||
throw new UnprocessableEntityHttpException('نام، عنوان و متن الزامی است.');
|
||||
}
|
||||
|
||||
$triggerType = (string) $request->input('trigger_type', AutomationCatalog::TRIGGER_SCHEDULE);
|
||||
if (! in_array($triggerType, [
|
||||
AutomationCatalog::TRIGGER_SCHEDULE,
|
||||
AutomationCatalog::TRIGGER_EVENT,
|
||||
AutomationCatalog::TRIGGER_PRICE,
|
||||
], true)) {
|
||||
throw new UnprocessableEntityHttpException('نوع تریگر نامعتبر است.');
|
||||
}
|
||||
|
||||
$channels = array_values(array_intersect(
|
||||
(array) $request->input('channels', []),
|
||||
[AutomationCatalog::CHANNEL_SMS, AutomationCatalog::CHANNEL_IN_APP]
|
||||
));
|
||||
if ($channels === []) {
|
||||
throw new UnprocessableEntityHttpException('حداقل یک کانال انتخاب کنید.');
|
||||
}
|
||||
|
||||
$smsHour = $request->input('sms_send_hour');
|
||||
$inAppHour = $request->input('in_app_send_hour');
|
||||
if (in_array(AutomationCatalog::CHANNEL_SMS, $channels, true) && $smsHour === null) {
|
||||
throw new UnprocessableEntityHttpException('ساعت ارسال پیامک را مشخص کنید.');
|
||||
}
|
||||
if (in_array(AutomationCatalog::CHANNEL_IN_APP, $channels, true) && $inAppHour === null) {
|
||||
throw new UnprocessableEntityHttpException('ساعت ارسال اعلان را مشخص کنید.');
|
||||
}
|
||||
|
||||
$conditions = array_values(array_filter(
|
||||
(array) $request->input('conditions', []),
|
||||
fn ($row) => is_array($row) && filled($row['field'] ?? null)
|
||||
));
|
||||
$catalog = app(AutomationCatalog::class);
|
||||
$allowed = $catalog->fieldKeys();
|
||||
foreach ($conditions as $row) {
|
||||
if (! in_array($row['field'] ?? '', $allowed, true)) {
|
||||
throw new UnprocessableEntityHttpException('شرط نامعتبر است.');
|
||||
}
|
||||
}
|
||||
|
||||
$triggerConfig = (array) $request->input('trigger_config', []);
|
||||
if ($triggerType === AutomationCatalog::TRIGGER_SCHEDULE) {
|
||||
$triggerConfig = [];
|
||||
} elseif ($triggerType === AutomationCatalog::TRIGGER_EVENT) {
|
||||
$eventKeys = array_column(AutomationCatalog::EVENTS, 'key');
|
||||
if (! in_array((string) ($triggerConfig['event'] ?? ''), $eventKeys, true)) {
|
||||
throw new UnprocessableEntityHttpException('رویداد را انتخاب کنید.');
|
||||
}
|
||||
$triggerConfig = [
|
||||
'event' => (string) $triggerConfig['event'],
|
||||
'delay_hours' => max(0, (int) ($triggerConfig['delay_hours'] ?? 0)),
|
||||
];
|
||||
} elseif ($triggerType === AutomationCatalog::TRIGGER_PRICE) {
|
||||
$step = (float) str_replace(',', '', (string) ($triggerConfig['step_toman'] ?? 0));
|
||||
if ($step <= 0) {
|
||||
throw new UnprocessableEntityHttpException('رقم تغییر قیمت باید بزرگتر از صفر باشد.');
|
||||
}
|
||||
$dir = (string) ($triggerConfig['direction'] ?? 'both');
|
||||
if (! in_array($dir, ['up', 'down', 'both'], true)) {
|
||||
$dir = 'both';
|
||||
}
|
||||
$triggerConfig = [
|
||||
'step_toman' => $step,
|
||||
'direction' => $dir,
|
||||
'product_id' => (int) ($triggerConfig['product_id'] ?? 0) ?: null,
|
||||
];
|
||||
}
|
||||
|
||||
$fingerprint = app(AutomationFingerprint::class)->make($triggerType, $triggerConfig, $conditions);
|
||||
$dup = $this->duplicateOf($fingerprint, $ignoreId, false);
|
||||
if ($dup) {
|
||||
throw new UnprocessableEntityHttpException(
|
||||
'اتوماسیون شبیه وجود دارد: '.$dup->name.' — همان را ویرایش کنید.'
|
||||
);
|
||||
}
|
||||
|
||||
$starts = $request->input('starts_at') ?: null;
|
||||
$ends = $request->input('ends_at') ?: null;
|
||||
|
||||
return [
|
||||
'name' => $name,
|
||||
'code' => $this->uniqueCode($name, $ignoreId),
|
||||
'is_active' => (bool) $request->input('is_active', true),
|
||||
'starts_at' => $starts,
|
||||
'ends_at' => $ends,
|
||||
'sms_send_hour' => in_array(AutomationCatalog::CHANNEL_SMS, $channels, true) ? (int) $smsHour : null,
|
||||
'in_app_send_hour' => in_array(AutomationCatalog::CHANNEL_IN_APP, $channels, true) ? (int) $inAppHour : null,
|
||||
'trigger_type' => $triggerType,
|
||||
'trigger_config' => $triggerConfig,
|
||||
'conditions' => $conditions,
|
||||
'channels' => $channels,
|
||||
'title' => mb_substr($title, 0, 191),
|
||||
'body' => $body,
|
||||
'cooldown_hours' => max(0, (int) $request->input('cooldown_hours', 24)),
|
||||
'max_sends' => $request->filled('max_sends') ? max(1, (int) $request->input('max_sends')) : null,
|
||||
'logic_fingerprint' => $fingerprint,
|
||||
];
|
||||
}
|
||||
|
||||
private function duplicateOf(string $fingerprint, ?int $ignoreId, bool $activeOnly): ?Automation
|
||||
{
|
||||
$q = Automation::query()->where('logic_fingerprint', $fingerprint);
|
||||
if ($ignoreId) {
|
||||
$q->where('id', '!=', $ignoreId);
|
||||
}
|
||||
if ($activeOnly) {
|
||||
$q->where('is_active', true);
|
||||
}
|
||||
|
||||
return $q->first();
|
||||
}
|
||||
|
||||
private function uniqueCode(string $name, ?int $ignoreId): string
|
||||
{
|
||||
$base = Str::slug($name);
|
||||
if ($base === '') {
|
||||
$base = 'auto';
|
||||
}
|
||||
$code = $base;
|
||||
$i = 2;
|
||||
while (
|
||||
Automation::query()
|
||||
->where('code', $code)
|
||||
->when($ignoreId, fn ($q) => $q->where('id', '!=', $ignoreId))
|
||||
->exists()
|
||||
) {
|
||||
$code = $base.'-'.$i;
|
||||
$i++;
|
||||
}
|
||||
|
||||
return mb_substr($code, 0, 64);
|
||||
}
|
||||
}
|
||||
@@ -212,6 +212,7 @@ class WalletController extends Controller
|
||||
} catch (\Throwable $e) {
|
||||
report($e);
|
||||
}
|
||||
\App\Services\Automation\AutomationEvents::fire('wallet.rial_charge', (int) $customer->id);
|
||||
return response()->json(true);
|
||||
}
|
||||
|
||||
@@ -324,6 +325,10 @@ class WalletController extends Controller
|
||||
report($e);
|
||||
}
|
||||
|
||||
if ((float) $data['value'] > 0) {
|
||||
\App\Services\Automation\AutomationEvents::fire('wallet.rial_charge', (int) $customer->id);
|
||||
}
|
||||
|
||||
$this->broadcastCustomerWalletAssets($customer);
|
||||
|
||||
return response()->json(true);
|
||||
|
||||
Reference in New Issue
Block a user