167 lines
6.3 KiB
PHP
167 lines
6.3 KiB
PHP
<?php
|
|||
|
|
|
||
|
|
namespace App\Services\Automation;
|
||
|
|
|
||
|
|
use App\Models\Customer;
|
||
|
|
use Illuminate\Database\Eloquent\Builder;
|
||
|
|
|
||
|
|
class AutomationConditionQuery
|
||
|
|
{
|
||
|
|
public function baseQuery(): Builder
|
||
|
|
{
|
||
|
|
return Customer::query()
|
||
|
|
->where('type', 2)
|
||
|
|
->where('is_archive', false);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function apply(Builder $query, array $conditions): Builder
|
||
|
|
{
|
||
|
|
$rows = array_values(array_filter($conditions, fn ($r) => is_array($r) && ! empty($r['field'])));
|
||
|
|
if ($rows === []) {
|
||
|
|
return $query;
|
||
|
|
}
|
||
|
|
|
||
|
|
return $query->where(function (Builder $outer) use ($rows) {
|
||
|
|
foreach ($rows as $i => $row) {
|
||
|
|
$or = $i > 0 && (($row['cr'] ?? 'and') === 'or');
|
||
|
|
$this->applyOne($outer, $row, $or);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
public function matchesCustomer(Customer $customer, array $conditions): bool
|
||
|
|
{
|
||
|
|
return $this->apply($this->baseQuery()->where('id', $customer->id), $conditions)->exists();
|
||
|
|
}
|
||
|
|
|
||
|
|
private function applyOne(Builder $q, array $row, bool $or): void
|
||
|
|
{
|
||
|
|
$field = (string) $row['field'];
|
||
|
|
$op = (string) ($row['op'] ?? 'eq');
|
||
|
|
$value = $row['value'] ?? null;
|
||
|
|
if (is_string($value)) {
|
||
|
|
$value = str_replace(',', '', $value);
|
||
|
|
}
|
||
|
|
$days = max(0, (int) $value);
|
||
|
|
|
||
|
|
match ($field) {
|
||
|
|
'never_login' => $this->nullCol($q, 'last_seen_at', $or),
|
||
|
|
'last_seen_older_days' => $this->where($q, $or, function (Builder $inner) use ($days) {
|
||
|
|
$cutoff = now()->subDays($days);
|
||
|
|
$inner->whereNull('last_seen_at')->orWhere('last_seen_at', '<', $cutoff);
|
||
|
|
}),
|
||
|
|
'last_seen_within_days' => $this->cmp($q, 'last_seen_at', 'gte', now()->subDays($days), $or),
|
||
|
|
'is_legal' => $this->cmp($q, 'is_legal', 'eq', (int) $value, $or),
|
||
|
|
'active' => $this->cmp($q, 'active', 'eq', (int) $value, $or),
|
||
|
|
'registered_older_days' => $this->cmp($q, 'created_at', 'lt', now()->subDays($days), $or),
|
||
|
|
'registered_within_days' => $this->cmp($q, 'created_at', 'gte', now()->subDays($days), $or),
|
||
|
|
'orders_count' => $this->cmp($q, 'orders_count', $op, (int) $value, $or),
|
||
|
|
'wallet_cash' => $this->wallet($q, 'cash', $op, (float) $value, $or),
|
||
|
|
'wallet_gold' => $this->wallet($q, 'gold', $op, (float) $value, $or),
|
||
|
|
'never_ordered' => $this->orderExists($q, null, null, $or, true),
|
||
|
|
'has_buy_within_days' => $this->orderExists($q, 1, $days, $or, false),
|
||
|
|
'has_sell_within_days' => $this->orderExists($q, 2, $days, $or, false),
|
||
|
|
'last_order_older_days' => $this->lastOrderOlder($q, $days, $or),
|
||
|
|
'has_deposit_within_days' => $this->depositExists($q, $days, $or),
|
||
|
|
'has_withdraw_pending' => $this->withdrawPending($q, $or),
|
||
|
|
default => null,
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
private function where(Builder $q, bool $or, callable $fn): void
|
||
|
|
{
|
||
|
|
$method = $or ? 'orWhere' : 'where';
|
||
|
|
$q->{$method}($fn);
|
||
|
|
}
|
||
|
|
|
||
|
|
private function nullCol(Builder $q, string $col, bool $or): void
|
||
|
|
{
|
||
|
|
$method = $or ? 'orWhereNull' : 'whereNull';
|
||
|
|
$q->{$method}($col);
|
||
|
|
}
|
||
|
|
|
||
|
|
private function cmp(Builder $q, string $col, string $op, mixed $value, bool $or): void
|
||
|
|
{
|
||
|
|
$sqlOp = match ($op) {
|
||
|
|
'gt' => '>',
|
||
|
|
'gte' => '>=',
|
||
|
|
'lt' => '<',
|
||
|
|
'lte' => '<=',
|
||
|
|
'not' => '<>',
|
||
|
|
default => '=',
|
||
|
|
};
|
||
|
|
$method = $or ? 'orWhere' : 'where';
|
||
|
|
$q->{$method}($col, $sqlOp, $value);
|
||
|
|
}
|
||
|
|
|
||
|
|
private function wallet(Builder $q, string $col, string $op, float $value, bool $or): void
|
||
|
|
{
|
||
|
|
$sqlOp = match ($op) {
|
||
|
|
'gt' => '>',
|
||
|
|
'gte' => '>=',
|
||
|
|
'lt' => '<',
|
||
|
|
'lte' => '<=',
|
||
|
|
'not' => '<>',
|
||
|
|
default => '=',
|
||
|
|
};
|
||
|
|
$expr = "(select w.{$col} from wallets w where w.owner = customers.id and w.fund_id is null and w.product_id is null order by w.id asc limit 1)";
|
||
|
|
$method = $or ? 'orWhereRaw' : 'whereRaw';
|
||
|
|
$q->{$method}("coalesce({$expr}, 0) {$sqlOp} ?", [$value]);
|
||
|
|
}
|
||
|
|
|
||
|
|
private function orderExists(Builder $q, ?int $direction, ?int $days, bool $or, bool $notExists): void
|
||
|
|
{
|
||
|
|
$method = $or
|
||
|
|
? ($notExists ? 'orWhereNotExists' : 'orWhereExists')
|
||
|
|
: ($notExists ? 'whereNotExists' : 'whereExists');
|
||
|
|
$q->{$method}(function ($sub) use ($direction, $days) {
|
||
|
|
$sub->from('orders')
|
||
|
|
->whereColumn('orders.customer_id', 'customers.id')
|
||
|
|
->where('orders.sts', 2);
|
||
|
|
if ($direction) {
|
||
|
|
$sub->where('orders.direction', $direction);
|
||
|
|
}
|
||
|
|
if ($days !== null) {
|
||
|
|
$sub->where('orders.created_at', '>=', now()->subDays($days));
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
private function lastOrderOlder(Builder $q, int $days, bool $or): void
|
||
|
|
{
|
||
|
|
$cutoff = now()->subDays($days);
|
||
|
|
$this->where($q, $or, function (Builder $inner) use ($cutoff) {
|
||
|
|
$inner->whereNotExists(function ($sub) {
|
||
|
|
$sub->from('orders')
|
||
|
|
->whereColumn('orders.customer_id', 'customers.id')
|
||
|
|
->where('orders.sts', 2);
|
||
|
|
})->orWhereRaw(
|
||
|
|
'(select max(orders.created_at) from orders where orders.customer_id = customers.id and orders.sts = 2) < ?',
|
||
|
|
[$cutoff]
|
||
|
|
);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
private function depositExists(Builder $q, int $days, bool $or): void
|
||
|
|
{
|
||
|
|
$method = $or ? 'orWhereExists' : 'whereExists';
|
||
|
|
$q->{$method}(function ($sub) use ($days) {
|
||
|
|
$sub->from('withdraws')
|
||
|
|
->whereColumn('withdraws.customer_id', 'customers.id')
|
||
|
|
->where('withdraws.cash', '>', 0)
|
||
|
|
->whereIn('withdraws.sts', [2, 4])
|
||
|
|
->where('withdraws.created_at', '>=', now()->subDays($days));
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
private function withdrawPending(Builder $q, bool $or): void
|
||
|
|
{
|
||
|
|
$method = $or ? 'orWhereExists' : 'whereExists';
|
||
|
|
$q->{$method}(function ($sub) {
|
||
|
|
$sub->from('withdraws')
|
||
|
|
->whereColumn('withdraws.customer_id', 'customers.id')
|
||
|
|
->where('withdraws.sts', 1);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
}
|