added time working
This commit is contained in:
@@ -108,17 +108,35 @@ class AutomationController extends Controller
|
||||
|
||||
$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('ساعت ارسال اعلان را مشخص کنید.');
|
||||
$triggerConfigRaw = (array) $request->input('trigger_config', []);
|
||||
$priceWindow = (string) ($triggerConfigRaw['price_window'] ?? 'calendar_day');
|
||||
$needsChannelHour = ! (
|
||||
(
|
||||
$triggerType === AutomationCatalog::TRIGGER_EVENT
|
||||
&& (int) ($triggerConfigRaw['delay_hours'] ?? 0) === 0
|
||||
)
|
||||
|| (
|
||||
$triggerType === AutomationCatalog::TRIGGER_PRICE
|
||||
&& $priceWindow === 'calendar_day'
|
||||
)
|
||||
);
|
||||
if ($needsChannelHour) {
|
||||
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)
|
||||
));
|
||||
$audienceMode = (string) $request->input('audience_mode', $conditions === [] ? 'all' : 'filter');
|
||||
if ($audienceMode === 'all') {
|
||||
$conditions = [];
|
||||
}
|
||||
$catalog = app(AutomationCatalog::class);
|
||||
$allowed = $catalog->fieldKeys();
|
||||
foreach ($conditions as $row) {
|
||||
@@ -128,8 +146,12 @@ class AutomationController extends Controller
|
||||
}
|
||||
|
||||
$triggerConfig = (array) $request->input('trigger_config', []);
|
||||
$cooldownHours = max(0, (int) $request->input('cooldown_hours', 24));
|
||||
if ($triggerType === AutomationCatalog::TRIGGER_SCHEDULE) {
|
||||
$triggerConfig = [];
|
||||
$everyDays = max(1, (int) ($triggerConfig['every_days'] ?? 1));
|
||||
$triggerConfig = ['every_days' => $everyDays];
|
||||
// هر مشتری حداکثر یکبار در همین بازهٔ تکرار
|
||||
$cooldownHours = max($cooldownHours, $everyDays * 24);
|
||||
} elseif ($triggerType === AutomationCatalog::TRIGGER_EVENT) {
|
||||
$eventKeys = array_column(AutomationCatalog::EVENTS, 'key');
|
||||
if (! in_array((string) ($triggerConfig['event'] ?? ''), $eventKeys, true)) {
|
||||
@@ -148,11 +170,20 @@ class AutomationController extends Controller
|
||||
if (! in_array($dir, ['up', 'down', 'both'], true)) {
|
||||
$dir = 'both';
|
||||
}
|
||||
$window = (string) ($triggerConfig['price_window'] ?? 'calendar_day');
|
||||
if (! in_array($window, ['calendar_day', 'from_last'], true)) {
|
||||
$window = 'calendar_day';
|
||||
}
|
||||
$triggerConfig = [
|
||||
'step_toman' => $step,
|
||||
'direction' => $dir,
|
||||
'product_id' => (int) ($triggerConfig['product_id'] ?? 0) ?: null,
|
||||
'price_window' => $window,
|
||||
];
|
||||
// برای «در یک روز» معمولاً یکبار در روز کافی است
|
||||
if ($window === 'calendar_day') {
|
||||
$cooldownHours = max($cooldownHours, 20);
|
||||
}
|
||||
}
|
||||
|
||||
$fingerprint = app(AutomationFingerprint::class)->make($triggerType, $triggerConfig, $conditions);
|
||||
@@ -172,15 +203,19 @@ class AutomationController extends Controller
|
||||
'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,
|
||||
'sms_send_hour' => in_array(AutomationCatalog::CHANNEL_SMS, $channels, true)
|
||||
? ($smsHour === null ? null : (int) $smsHour)
|
||||
: null,
|
||||
'in_app_send_hour' => in_array(AutomationCatalog::CHANNEL_IN_APP, $channels, true)
|
||||
? ($inAppHour === null ? null : (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)),
|
||||
'cooldown_hours' => $cooldownHours,
|
||||
'max_sends' => $request->filled('max_sends') ? max(1, (int) $request->input('max_sends')) : null,
|
||||
'logic_fingerprint' => $fingerprint,
|
||||
];
|
||||
|
||||
@@ -17,7 +17,7 @@ class AutomationCatalog
|
||||
public const CHANNEL_IN_APP = 'in_app';
|
||||
|
||||
public const EVENTS = [
|
||||
['key' => 'customer.login', 'name' => 'ورود مشتری'],
|
||||
['key' => 'customer.login', 'name' => 'ورود کاربر'],
|
||||
['key' => 'order.buy_success', 'name' => 'خرید موفق'],
|
||||
['key' => 'order.sell_success', 'name' => 'فروش موفق'],
|
||||
['key' => 'wallet.rial_charge', 'name' => 'شارژ ریالی کیف'],
|
||||
@@ -29,10 +29,18 @@ class AutomationCatalog
|
||||
{
|
||||
return [
|
||||
'triggers' => [
|
||||
['key' => self::TRIGGER_SCHEDULE, 'name' => 'زماندار (اسکن ساعتی)'],
|
||||
['key' => self::TRIGGER_EVENT, 'name' => 'بعد از رویداد'],
|
||||
['key' => self::TRIGGER_SCHEDULE, 'name' => 'زماندار (هر N روز)'],
|
||||
['key' => self::TRIGGER_EVENT, 'name' => 'رویداد (سر ضرب / تأخیر)'],
|
||||
['key' => self::TRIGGER_PRICE, 'name' => 'حرکت قیمت'],
|
||||
],
|
||||
'price_windows' => [
|
||||
['key' => 'calendar_day', 'name' => 'در یک روز (از شروع امروز)'],
|
||||
['key' => 'from_last', 'name' => 'از آخرین ارسال (پلهای)'],
|
||||
],
|
||||
'audience_modes' => [
|
||||
['key' => 'all', 'name' => 'همه کاربران خانگی'],
|
||||
['key' => 'filter', 'name' => 'با فیلتر شرط'],
|
||||
],
|
||||
'events' => self::EVENTS,
|
||||
'channels' => [
|
||||
['key' => self::CHANNEL_SMS, 'name' => 'پیامک'],
|
||||
@@ -66,13 +74,13 @@ class AutomationCatalog
|
||||
return [
|
||||
[
|
||||
'key' => 'never_login',
|
||||
'group' => 'حضور',
|
||||
'group' => 'ورود و بازدید',
|
||||
'name' => 'هرگز وارد اپ نشده',
|
||||
'inputs' => [],
|
||||
],
|
||||
[
|
||||
'key' => 'last_seen_older_days',
|
||||
'group' => 'حضور',
|
||||
'group' => 'ورود و بازدید',
|
||||
'name' => 'آخرین بازدید قدیمیتر از N روز',
|
||||
'inputs' => [
|
||||
['key' => 'value', 'type' => 'number', 'label' => 'تعداد روز'],
|
||||
@@ -80,7 +88,7 @@ class AutomationCatalog
|
||||
],
|
||||
[
|
||||
'key' => 'last_seen_within_days',
|
||||
'group' => 'حضور',
|
||||
'group' => 'ورود و بازدید',
|
||||
'name' => 'آخرین بازدید در N روز اخیر',
|
||||
'inputs' => [
|
||||
['key' => 'value', 'type' => 'number', 'label' => 'تعداد روز'],
|
||||
@@ -88,10 +96,10 @@ class AutomationCatalog
|
||||
],
|
||||
[
|
||||
'key' => 'is_legal',
|
||||
'group' => 'مشتری',
|
||||
'name' => 'وضعیت احراز',
|
||||
'group' => 'حساب کاربر',
|
||||
'name' => 'وضعیت احراز هویت',
|
||||
'inputs' => [
|
||||
['key' => 'value', 'type' => 'select', 'label' => 'وضعیت', 'options' => [
|
||||
['key' => 'value', 'type' => 'select', 'label' => 'وضعیت احراز', 'options' => [
|
||||
['key' => 0, 'name' => 'احراز نشده'],
|
||||
['key' => 1, 'name' => 'احراز خودکار'],
|
||||
['key' => 2, 'name' => 'تأیید ادمین'],
|
||||
@@ -100,10 +108,10 @@ class AutomationCatalog
|
||||
],
|
||||
[
|
||||
'key' => 'active',
|
||||
'group' => 'مشتری',
|
||||
'name' => 'حساب فعال',
|
||||
'group' => 'حساب کاربر',
|
||||
'name' => 'حساب فعال / غیرفعال',
|
||||
'inputs' => [
|
||||
['key' => 'value', 'type' => 'select', 'label' => 'وضعیت', 'options' => [
|
||||
['key' => 'value', 'type' => 'select', 'label' => 'وضعیت حساب', 'options' => [
|
||||
['key' => 1, 'name' => 'فعال'],
|
||||
['key' => 0, 'name' => 'غیرفعال'],
|
||||
]],
|
||||
@@ -111,7 +119,7 @@ class AutomationCatalog
|
||||
],
|
||||
[
|
||||
'key' => 'registered_older_days',
|
||||
'group' => 'مشتری',
|
||||
'group' => 'حساب کاربر',
|
||||
'name' => 'ثبتنام قدیمیتر از N روز',
|
||||
'inputs' => [
|
||||
['key' => 'value', 'type' => 'number', 'label' => 'تعداد روز'],
|
||||
@@ -119,7 +127,7 @@ class AutomationCatalog
|
||||
],
|
||||
[
|
||||
'key' => 'registered_within_days',
|
||||
'group' => 'مشتری',
|
||||
'group' => 'حساب کاربر',
|
||||
'name' => 'ثبتنام در N روز اخیر',
|
||||
'inputs' => [
|
||||
['key' => 'value', 'type' => 'number', 'label' => 'تعداد روز'],
|
||||
@@ -127,40 +135,40 @@ class AutomationCatalog
|
||||
],
|
||||
[
|
||||
'key' => 'orders_count',
|
||||
'group' => 'مشتری',
|
||||
'group' => 'حساب کاربر',
|
||||
'name' => 'تعداد سفارش',
|
||||
'inputs' => [
|
||||
['key' => 'op', 'type' => 'operator', 'label' => 'عملگر'],
|
||||
['key' => 'value', 'type' => 'number', 'label' => 'عدد'],
|
||||
['key' => 'op', 'type' => 'operator', 'label' => 'مقایسه'],
|
||||
['key' => 'value', 'type' => 'number', 'label' => 'تعداد'],
|
||||
],
|
||||
],
|
||||
[
|
||||
'key' => 'wallet_cash',
|
||||
'group' => 'کیف',
|
||||
'group' => 'کیف پول',
|
||||
'name' => 'موجودی ریال کیف اصلی',
|
||||
'inputs' => [
|
||||
['key' => 'op', 'type' => 'operator', 'label' => 'عملگر'],
|
||||
['key' => 'value', 'type' => 'number', 'label' => 'ریال'],
|
||||
['key' => 'op', 'type' => 'operator', 'label' => 'مقایسه'],
|
||||
['key' => 'value', 'type' => 'number', 'label' => 'مبلغ (ریال)'],
|
||||
],
|
||||
],
|
||||
[
|
||||
'key' => 'wallet_gold',
|
||||
'group' => 'کیف',
|
||||
'group' => 'کیف پول',
|
||||
'name' => 'موجودی طلا کیف اصلی',
|
||||
'inputs' => [
|
||||
['key' => 'op', 'type' => 'operator', 'label' => 'عملگر'],
|
||||
['key' => 'value', 'type' => 'number', 'label' => 'گرم'],
|
||||
['key' => 'op', 'type' => 'operator', 'label' => 'مقایسه'],
|
||||
['key' => 'value', 'type' => 'number', 'label' => 'مقدار (گرم)'],
|
||||
],
|
||||
],
|
||||
[
|
||||
'key' => 'never_ordered',
|
||||
'group' => 'معامله',
|
||||
'group' => 'خرید و فروش',
|
||||
'name' => 'هرگز معامله موفق نداشته',
|
||||
'inputs' => [],
|
||||
],
|
||||
[
|
||||
'key' => 'has_buy_within_days',
|
||||
'group' => 'معامله',
|
||||
'group' => 'خرید و فروش',
|
||||
'name' => 'خرید موفق در N روز اخیر',
|
||||
'inputs' => [
|
||||
['key' => 'value', 'type' => 'number', 'label' => 'تعداد روز'],
|
||||
@@ -168,7 +176,7 @@ class AutomationCatalog
|
||||
],
|
||||
[
|
||||
'key' => 'has_sell_within_days',
|
||||
'group' => 'معامله',
|
||||
'group' => 'خرید و فروش',
|
||||
'name' => 'فروش موفق در N روز اخیر',
|
||||
'inputs' => [
|
||||
['key' => 'value', 'type' => 'number', 'label' => 'تعداد روز'],
|
||||
@@ -176,7 +184,7 @@ class AutomationCatalog
|
||||
],
|
||||
[
|
||||
'key' => 'last_order_older_days',
|
||||
'group' => 'معامله',
|
||||
'group' => 'خرید و فروش',
|
||||
'name' => 'آخرین معامله موفق قدیمیتر از N روز',
|
||||
'inputs' => [
|
||||
['key' => 'value', 'type' => 'number', 'label' => 'تعداد روز'],
|
||||
@@ -184,7 +192,7 @@ class AutomationCatalog
|
||||
],
|
||||
[
|
||||
'key' => 'has_deposit_within_days',
|
||||
'group' => 'واریز',
|
||||
'group' => 'شارژ کیف',
|
||||
'name' => 'شارژ ریالی در N روز اخیر',
|
||||
'inputs' => [
|
||||
['key' => 'value', 'type' => 'number', 'label' => 'تعداد روز'],
|
||||
|
||||
@@ -88,6 +88,74 @@ class AutomationEngine
|
||||
return;
|
||||
}
|
||||
|
||||
$cfg = $auto->trigger_config ?? [];
|
||||
$step = (float) ($cfg['step_toman'] ?? 0);
|
||||
if ($step <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
$window = (string) ($cfg['price_window'] ?? 'calendar_day');
|
||||
$dir = (string) ($cfg['direction'] ?? 'both');
|
||||
|
||||
if ($window === 'calendar_day') {
|
||||
$this->tryPriceFireCalendarDay($auto, $current, $step, $dir, $cfg);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->tryPriceFireFromLast($auto, $current, $step, $dir);
|
||||
}
|
||||
|
||||
/** تغییر از شروع امروز تا الان؛ حداکثر یکبار در روز */
|
||||
private function tryPriceFireCalendarDay(
|
||||
Automation $auto,
|
||||
float $current,
|
||||
float $step,
|
||||
string $dir,
|
||||
array $cfg
|
||||
): void {
|
||||
$today = now('Asia/Tehran')->toDateString();
|
||||
$dayKey = (string) ($cfg['day_key'] ?? '');
|
||||
$dayOpen = (float) ($cfg['day_open'] ?? 0);
|
||||
$dayFired = (bool) ($cfg['day_fired'] ?? false);
|
||||
|
||||
if ($dayKey !== $today || $dayOpen <= 0) {
|
||||
$cfg['day_key'] = $today;
|
||||
$cfg['day_open'] = $current;
|
||||
$cfg['day_fired'] = false;
|
||||
Automation::query()->where('id', $auto->id)->update([
|
||||
'trigger_config' => $cfg,
|
||||
'last_anchor_price' => $current,
|
||||
]);
|
||||
$auto->trigger_config = $cfg;
|
||||
$auto->last_anchor_price = $current;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($dayFired) {
|
||||
return;
|
||||
}
|
||||
|
||||
$delta = $current - $dayOpen;
|
||||
if (! $this->priceDirectionMet($dir, $delta, $step)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// برای اعلان قیمت روزانه معمولاً سر ضرب به همه میرود
|
||||
$this->enqueueAudience($auto, 0);
|
||||
$cfg['day_fired'] = true;
|
||||
Automation::query()->where('id', $auto->id)->update([
|
||||
'trigger_config' => $cfg,
|
||||
'last_anchor_price' => $current,
|
||||
]);
|
||||
$auto->trigger_config = $cfg;
|
||||
$auto->last_anchor_price = $current;
|
||||
}
|
||||
|
||||
/** پلهای از آخرین ارسال */
|
||||
private function tryPriceFireFromLast(Automation $auto, float $current, float $step, string $dir): void
|
||||
{
|
||||
$anchor = $auto->last_anchor_price;
|
||||
if ($anchor === null || (float) $anchor <= 0) {
|
||||
Automation::query()->where('id', $auto->id)->update(['last_anchor_price' => $current]);
|
||||
@@ -96,20 +164,8 @@ class AutomationEngine
|
||||
return;
|
||||
}
|
||||
|
||||
$cfg = $auto->trigger_config ?? [];
|
||||
$step = (float) ($cfg['step_toman'] ?? 0);
|
||||
if ($step <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
$delta = $current - (float) $anchor;
|
||||
$dir = (string) ($cfg['direction'] ?? 'both');
|
||||
$moved = match ($dir) {
|
||||
'up' => $delta >= $step,
|
||||
'down' => $delta <= -$step,
|
||||
default => abs($delta) >= $step,
|
||||
};
|
||||
if (! $moved) {
|
||||
if (! $this->priceDirectionMet($dir, $delta, $step)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -129,6 +185,15 @@ class AutomationEngine
|
||||
$auto->last_anchor_price = $current;
|
||||
}
|
||||
|
||||
private function priceDirectionMet(string $dir, float $delta, float $step): bool
|
||||
{
|
||||
return match ($dir) {
|
||||
'up' => $delta >= $step,
|
||||
'down' => $delta <= -$step,
|
||||
default => abs($delta) >= $step,
|
||||
};
|
||||
}
|
||||
|
||||
private function runSchedule(): void
|
||||
{
|
||||
$rows = Automation::query()
|
||||
@@ -140,11 +205,12 @@ class AutomationEngine
|
||||
if (! $this->inWindow($auto)) {
|
||||
continue;
|
||||
}
|
||||
$everyDays = max(1, (int) (($auto->trigger_config ?? [])['every_days'] ?? 1));
|
||||
foreach ($this->channels($auto) as $channel) {
|
||||
if (! $this->hourMatches($auto, $channel)) {
|
||||
continue;
|
||||
}
|
||||
if ($this->alreadyRanThisHour($auto, $channel)) {
|
||||
if (! $this->scheduleDue($auto, $channel, $everyDays)) {
|
||||
continue;
|
||||
}
|
||||
$this->sendAudienceNow($auto, $channel);
|
||||
@@ -153,6 +219,22 @@ class AutomationEngine
|
||||
}
|
||||
}
|
||||
|
||||
/** آیا از آخرین اجرای موفق این کانال، حداقل N روز گذشته؟ */
|
||||
private function scheduleDue(Automation $auto, string $channel, int $everyDays): bool
|
||||
{
|
||||
$at = $channel === AutomationCatalog::CHANNEL_SMS
|
||||
? $auto->last_sms_run_at
|
||||
: $auto->last_in_app_run_at;
|
||||
if (! $at) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$lastDay = $at->copy()->timezone('Asia/Tehran')->startOfDay();
|
||||
$today = now('Asia/Tehran')->startOfDay();
|
||||
|
||||
return $lastDay->diffInDays($today) >= $everyDays;
|
||||
}
|
||||
|
||||
private function processDelayedJobs(): void
|
||||
{
|
||||
$jobs = AutomationDelayedJob::query()
|
||||
@@ -215,7 +297,8 @@ class AutomationEngine
|
||||
if ($exists) {
|
||||
continue;
|
||||
}
|
||||
if ($delayHours === 0 && $this->hourMatches($auto, $channel) && $this->sender->maySend($auto, $customer, $channel)) {
|
||||
if ($delayHours === 0 && $this->sender->maySend($auto, $customer, $channel)) {
|
||||
// سر ضرب: بلافاصله، بدون انتظار ساعت کانال
|
||||
$this->sender->send($auto, $customer, $channel);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -9,9 +9,11 @@ class AutomationFingerprint
|
||||
$cfg = [
|
||||
'event' => (string) ($triggerConfig['event'] ?? ''),
|
||||
'delay_hours' => (int) ($triggerConfig['delay_hours'] ?? 0),
|
||||
'every_days' => (int) ($triggerConfig['every_days'] ?? 1),
|
||||
'step_toman' => (float) ($triggerConfig['step_toman'] ?? 0),
|
||||
'direction' => (string) ($triggerConfig['direction'] ?? ''),
|
||||
'product_id' => (int) ($triggerConfig['product_id'] ?? 0),
|
||||
'price_window' => (string) ($triggerConfig['price_window'] ?? 'calendar_day'),
|
||||
];
|
||||
|
||||
$norm = [];
|
||||
|
||||
@@ -6,6 +6,7 @@ use App\Models\Automation;
|
||||
use App\Models\AutomationSend;
|
||||
use App\Models\Customer;
|
||||
use App\Models\Message;
|
||||
use App\Services\ConfigService;
|
||||
use App\Services\CustomerAppNotifyService;
|
||||
use App\Services\SmsBilling\SmsGatewayService;
|
||||
use App\Exceptions\SmsBlockedException;
|
||||
@@ -16,6 +17,7 @@ class AutomationSender
|
||||
public function __construct(
|
||||
private readonly SmsGatewayService $sms,
|
||||
private readonly CustomerAppNotifyService $notify,
|
||||
private readonly ConfigService $config,
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -34,7 +36,7 @@ class AutomationSender
|
||||
if ($mobile === '') {
|
||||
throw new \RuntimeException('موبایل خالی است');
|
||||
}
|
||||
$text = trim($title."\n".$body);
|
||||
$text = $this->ensureAppNameFooter(trim($title."\n".$body));
|
||||
$result = $this->sms->sendOrThrow($mobile, $text, 'automation');
|
||||
if (! $result->sent) {
|
||||
throw new \RuntimeException($result->errorMessage ?: 'ارسال پیامک ناموفق');
|
||||
@@ -105,15 +107,42 @@ class AutomationSender
|
||||
return true;
|
||||
}
|
||||
|
||||
private function ensureAppNameFooter(string $message): string
|
||||
{
|
||||
$appName = trim($this->config->getString('name_fa'));
|
||||
$trimmed = rtrim($message);
|
||||
if ($appName === '') {
|
||||
return $trimmed;
|
||||
}
|
||||
|
||||
$lines = preg_split('/\R/u', $trimmed) ?: [];
|
||||
$last = trim((string) end($lines));
|
||||
if ($last === $appName) {
|
||||
return $trimmed;
|
||||
}
|
||||
|
||||
return $trimmed === '' ? $appName : ($trimmed."\n".$appName);
|
||||
}
|
||||
|
||||
private function render(string $text, Customer $customer): string
|
||||
{
|
||||
$name = trim((string) ($customer->name ?: trim(($customer->fname ?? '').' '.($customer->lname ?? ''))));
|
||||
$fname = (string) ($customer->fname ?? '');
|
||||
$mobile = (string) ($customer->mobile ?? '');
|
||||
|
||||
return strtr($text, [
|
||||
// سازگاری با قالب قدیمی {{name}}
|
||||
$out = strtr($text, [
|
||||
'{{name}}' => $name,
|
||||
'{{fname}}' => (string) ($customer->fname ?? ''),
|
||||
'{{mobile}}' => (string) ($customer->mobile ?? ''),
|
||||
'{{fname}}' => $fname,
|
||||
'{{mobile}}' => $mobile,
|
||||
]);
|
||||
|
||||
// الگوی پیامک/تنظیمات: %نام ، ٪نام ، % name ، %name%
|
||||
$out = preg_replace('/[%٪]\s*(?:name|نام)[%٪]?/iu', $name, $out) ?? $out;
|
||||
$out = preg_replace('/[%٪]\s*(?:fname|نام_کوچک|نامکوچک)[%٪]?/iu', $fname, $out) ?? $out;
|
||||
$out = preg_replace('/[%٪]\s*(?:mobile|موبایل)[%٪]?/iu', $mobile, $out) ?? $out;
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
private function log(Automation $auto, Customer $customer, string $channel, string $status, ?string $error = null): void
|
||||
|
||||
Reference in New Issue
Block a user