837 lines
25 KiB
PHP
837 lines
25 KiB
PHP
<?php
|
||
|
||
if (!function_exists('persian_number')) {
|
||
/**
|
||
* Convert English numbers to Persian numbers
|
||
*
|
||
* @param string|int $string
|
||
* @return string
|
||
*/
|
||
function persian_number($string)
|
||
{
|
||
$persian = ['۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹'];
|
||
$english = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
|
||
|
||
return str_replace($english, $persian, $string);
|
||
}
|
||
}
|
||
|
||
if (!function_exists('english_number')) {
|
||
/**
|
||
* Convert Persian numbers to English numbers
|
||
*
|
||
* @param string $string
|
||
* @return string
|
||
*/
|
||
function english_number($string)
|
||
{
|
||
$persian = ['۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹'];
|
||
$english = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
|
||
|
||
return str_replace($persian, $english, $string);
|
||
}
|
||
}
|
||
|
||
if (!function_exists('persian_price')) {
|
||
/**
|
||
* Format price with Persian numbers and thousand separator
|
||
*
|
||
* @param float|int $price
|
||
* @param int $decimals
|
||
* @return string
|
||
*/
|
||
function persian_price($price, $decimals = 0)
|
||
{
|
||
$formatted = number_format($price, $decimals, '.', ',');
|
||
return persian_number($formatted);
|
||
}
|
||
}
|
||
|
||
if (!function_exists('is_rtl_lang')) {
|
||
/**
|
||
* Check if current language is RTL
|
||
*
|
||
* @return bool
|
||
*/
|
||
function is_rtl_lang()
|
||
{
|
||
$current_lang = session()->get('user.language', config('app.locale'));
|
||
return in_array($current_lang, config('constants.langs_rtl', []));
|
||
}
|
||
}
|
||
|
||
if (!function_exists('is_persian_lang')) {
|
||
/**
|
||
* Check if current language is Persian
|
||
*
|
||
* @return bool
|
||
*/
|
||
function is_persian_lang()
|
||
{
|
||
$current_lang = session()->get('user.language', config('app.locale'));
|
||
return $current_lang === 'fa';
|
||
}
|
||
}
|
||
|
||
if (!function_exists('get_name_prefixes')) {
|
||
/**
|
||
* Get selectable name prefixes for the current locale.
|
||
*
|
||
* @param string|null $selected Preserve a legacy/custom value not in the list.
|
||
* @return array<string, string>
|
||
*/
|
||
function get_name_prefixes($selected = null)
|
||
{
|
||
if (! is_persian_lang()) {
|
||
return [];
|
||
}
|
||
|
||
$options = [];
|
||
|
||
foreach (config('constants.name_prefixes_fa', []) as $prefix) {
|
||
$options[$prefix] = $prefix;
|
||
}
|
||
|
||
if (! empty($selected) && is_scalar($selected) && ! array_key_exists($selected, $options)) {
|
||
$options = [$selected => $selected] + $options;
|
||
}
|
||
|
||
return $options;
|
||
}
|
||
}
|
||
|
||
if (!function_exists('peek_next_contact_id')) {
|
||
/**
|
||
* Preview the next auto-generated contact ID without incrementing the counter.
|
||
*
|
||
* @param int|null $business_id
|
||
* @return string
|
||
*/
|
||
function peek_next_contact_id($business_id = null)
|
||
{
|
||
if (empty($business_id)) {
|
||
$business_id = request()->session()->get('user.business_id');
|
||
}
|
||
|
||
$ref = \App\ReferenceCount::where('ref_type', 'contacts')
|
||
->where('business_id', $business_id)
|
||
->first();
|
||
|
||
$ref_count = ! empty($ref) ? ((int) $ref->ref_count + 1) : 1;
|
||
|
||
return app(\App\Utils\Util::class)->generateReferenceNumber('contacts', $ref_count, $business_id);
|
||
}
|
||
}
|
||
|
||
if (!function_exists('peek_next_product_sku')) {
|
||
/**
|
||
* Preview the next auto-generated product SKU without creating a product.
|
||
*
|
||
* @param int|null $business_id
|
||
* @return string
|
||
*/
|
||
function peek_next_product_sku($business_id = null)
|
||
{
|
||
if (empty($business_id)) {
|
||
$business_id = request()->session()->get('user.business_id');
|
||
}
|
||
|
||
$next_id = (int) \App\Product::where('business_id', $business_id)->max('id') + 1;
|
||
|
||
return app(\App\Utils\ProductUtil::class)->generateProductSku($next_id);
|
||
}
|
||
}
|
||
|
||
if (!function_exists('persian_date')) {
|
||
/**
|
||
* Convert Gregorian date to Persian (Jalali) date
|
||
* Note: Requires jdf() function or morilog/jalali package
|
||
*
|
||
* @param string $date
|
||
* @param string $format
|
||
* @return string
|
||
*/
|
||
function persian_date($date = null, $format = 'Y/m/d')
|
||
{
|
||
if (!$date) {
|
||
$date = now();
|
||
}
|
||
|
||
if (is_string($date)) {
|
||
$date = \Carbon\Carbon::parse($date);
|
||
}
|
||
|
||
if (is_persian_lang()) {
|
||
return persian_number(format_jalali_date($date, $format));
|
||
}
|
||
|
||
return $date->format($format);
|
||
}
|
||
}
|
||
|
||
if (!function_exists('format_currency_persian')) {
|
||
/**
|
||
* Format currency for Persian locale
|
||
* Format: 100,000 ریال (no decimals, comma separator, "ریال" at the end)
|
||
*
|
||
* @param float|int $amount
|
||
* @param string $currency
|
||
* @return string
|
||
*/
|
||
function format_currency_persian($amount, $currency = 'ریال')
|
||
{
|
||
// Format number with comma as thousand separator, no decimals
|
||
$formatted = number_format((float) $amount, 0, '.', ',');
|
||
// Convert to Persian numbers
|
||
$formatted = persian_number($formatted);
|
||
return $formatted . ' ' . $currency;
|
||
}
|
||
}
|
||
|
||
if (!function_exists('persian_month_name')) {
|
||
/**
|
||
* Get Persian month name
|
||
*
|
||
* @param int $month
|
||
* @return string
|
||
*/
|
||
function persian_month_name($month)
|
||
{
|
||
$months = [
|
||
1 => 'فروردین',
|
||
2 => 'اردیبهشت',
|
||
3 => 'خرداد',
|
||
4 => 'تیر',
|
||
5 => 'مرداد',
|
||
6 => 'شهریور',
|
||
7 => 'مهر',
|
||
8 => 'آبان',
|
||
9 => 'آذر',
|
||
10 => 'دی',
|
||
11 => 'بهمن',
|
||
12 => 'اسفند',
|
||
];
|
||
|
||
return $months[$month] ?? '';
|
||
}
|
||
}
|
||
|
||
if (!function_exists('persian_day_name')) {
|
||
/**
|
||
* Get Persian day name
|
||
*
|
||
* @param int $day (0 = Sunday, 6 = Saturday)
|
||
* @return string
|
||
*/
|
||
function persian_day_name($day)
|
||
{
|
||
$days = [
|
||
0 => 'یکشنبه',
|
||
1 => 'دوشنبه',
|
||
2 => 'سهشنبه',
|
||
3 => 'چهارشنبه',
|
||
4 => 'پنجشنبه',
|
||
5 => 'جمعه',
|
||
6 => 'شنبه',
|
||
];
|
||
|
||
return $days[$day] ?? '';
|
||
}
|
||
}
|
||
|
||
if (!function_exists('gregorian_to_jalali_parts')) {
|
||
/**
|
||
* Convert a Gregorian date to Jalali parts [year, month, day].
|
||
*/
|
||
function gregorian_to_jalali_parts(int $gy, int $gm, int $gd): array
|
||
{
|
||
$g_d_m = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334];
|
||
$gy2 = ($gm > 2) ? ($gy + 1) : $gy;
|
||
$days = 355666 + (365 * $gy) + intdiv($gy2 + 3, 4) - intdiv($gy2 + 99, 100) + intdiv($gy2 + 399, 400) + $gd + $g_d_m[$gm - 1];
|
||
$jy = -1595 + (33 * intdiv($days, 12053));
|
||
$days %= 12053;
|
||
$jy += 4 * intdiv($days, 1461);
|
||
$days %= 1461;
|
||
|
||
if ($days > 365) {
|
||
$jy += intdiv($days - 1, 365);
|
||
$days = ($days - 1) % 365;
|
||
}
|
||
|
||
if ($days < 186) {
|
||
$jm = 1 + intdiv($days, 31);
|
||
$jd = 1 + ($days % 31);
|
||
} else {
|
||
$jm = 7 + intdiv($days - 186, 30);
|
||
$jd = 1 + (($days - 186) % 30);
|
||
}
|
||
|
||
return [$jy, $jm, $jd];
|
||
}
|
||
}
|
||
|
||
if (!function_exists('jalali_to_gregorian_parts')) {
|
||
/**
|
||
* Convert a Jalali date to Gregorian parts [year, month, day].
|
||
*/
|
||
function jalali_to_gregorian_parts(int $jy, int $jm, int $jd): array
|
||
{
|
||
$jy -= 979;
|
||
$days = (365 * $jy) + (intdiv($jy, 33) * 8) + intdiv((($jy % 33) + 3), 4) + 78 + $jd
|
||
+ (($jm < 7) ? (($jm - 1) * 31) : ((($jm - 7) * 30) + 186));
|
||
$gy = 1600 + (400 * intdiv($days, 146097));
|
||
$days %= 146097;
|
||
|
||
if ($days > 36524) {
|
||
$gy += 100 * intdiv(--$days, 36524);
|
||
$days %= 36524;
|
||
if ($days >= 365) {
|
||
$days++;
|
||
}
|
||
}
|
||
|
||
$gy += 4 * intdiv($days, 1461);
|
||
$days %= 1461;
|
||
|
||
if ($days > 365) {
|
||
$gy += intdiv($days - 1, 365);
|
||
$days = ($days - 1) % 365;
|
||
}
|
||
|
||
$gd = $days + 1;
|
||
$month_days = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
||
|
||
if (($gy % 4 === 0 && $gy % 100 !== 0) || ($gy % 400 === 0)) {
|
||
$month_days[2] = 29;
|
||
}
|
||
|
||
$gm = 1;
|
||
while ($gm <= 12 && $gd > $month_days[$gm]) {
|
||
$gd -= $month_days[$gm];
|
||
$gm++;
|
||
}
|
||
|
||
return [$gy, $gm, $gd];
|
||
}
|
||
}
|
||
|
||
if (!function_exists('jalali_format_from_carbon')) {
|
||
/**
|
||
* Format a Carbon date using Jalali calendar tokens.
|
||
*/
|
||
function jalali_format_from_carbon(\Carbon\Carbon $carbon, string $format): string
|
||
{
|
||
[$jy, $jm, $jd] = gregorian_to_jalali_parts(
|
||
(int) $carbon->format('Y'),
|
||
(int) $carbon->format('n'),
|
||
(int) $carbon->format('j')
|
||
);
|
||
|
||
$formatted = strtr($format, [
|
||
'%Y' => (string) $jy,
|
||
'%y' => substr((string) $jy, -2),
|
||
'%m' => sprintf('%02d', $jm),
|
||
'%n' => (string) $jm,
|
||
'%d' => sprintf('%02d', $jd),
|
||
'%j' => (string) $jd,
|
||
'%B' => persian_month_name($jm),
|
||
'%F' => persian_month_name($jm),
|
||
'%M' => persian_month_name($jm),
|
||
]);
|
||
|
||
return preg_replace_callback(
|
||
'/[YymndFjM]|[HhisAag]/',
|
||
function (array $matches) use ($jy, $jm, $jd, $carbon) {
|
||
$token = $matches[0];
|
||
|
||
return match ($token) {
|
||
'Y' => (string) $jy,
|
||
'y' => substr((string) $jy, -2),
|
||
'm' => sprintf('%02d', $jm),
|
||
'n' => (string) $jm,
|
||
'd' => sprintf('%02d', $jd),
|
||
'j' => (string) $jd,
|
||
'F', 'M' => persian_month_name($jm),
|
||
default => $carbon->format($token),
|
||
};
|
||
},
|
||
$formatted
|
||
);
|
||
}
|
||
}
|
||
|
||
if (!function_exists('format_jalali_date')) {
|
||
/**
|
||
* Format a date in Jalali using morilog/jalali when available, otherwise native conversion.
|
||
*/
|
||
function format_jalali_date(\Carbon\Carbon $carbon, string $format): string
|
||
{
|
||
if (class_exists('\Morilog\Jalali\Jalalian')) {
|
||
return \Morilog\Jalali\Jalalian::fromCarbon($carbon)->format($format);
|
||
}
|
||
|
||
return jalali_format_from_carbon($carbon, $format);
|
||
}
|
||
}
|
||
|
||
if (!function_exists('format_shamsi_date')) {
|
||
/**
|
||
* Format date/time in Jalali if Persian is enabled, otherwise fallback to Gregorian.
|
||
*
|
||
* @param \Carbon\Carbon|string $date
|
||
* @param string|null $format
|
||
* @param bool $include_time
|
||
* @param int|null $time_format
|
||
* @return string|null
|
||
*/
|
||
function format_shamsi_date($date, ?string $format = null, bool $include_time = false, ?int $time_format = null)
|
||
{
|
||
$format = $format ?? session('business.date_format', 'Y/m/d');
|
||
$time_format = $time_format ?? session('business.time_format', 24);
|
||
|
||
if ($include_time) {
|
||
$format .= ((int) $time_format === 12) ? ' h:i A' : ' H:i';
|
||
}
|
||
|
||
try {
|
||
// Handle 'now' string specially
|
||
if (is_string($date) && strtolower(trim($date)) === 'now') {
|
||
$carbon_date = \Carbon\Carbon::now();
|
||
} elseif (empty($date)) {
|
||
return null;
|
||
} elseif ($date instanceof \Carbon\Carbon) {
|
||
$carbon_date = $date->copy();
|
||
} elseif (is_string($date)) {
|
||
// Try to parse different date formats
|
||
try {
|
||
$carbon_date = \Carbon\Carbon::parse($date);
|
||
} catch (\Exception $e) {
|
||
// Try MySQL date format
|
||
try {
|
||
$carbon_date = \Carbon\Carbon::createFromFormat('Y-m-d H:i:s', $date);
|
||
} catch (\Exception $e2) {
|
||
try {
|
||
$carbon_date = \Carbon\Carbon::createFromFormat('Y-m-d', $date);
|
||
} catch (\Exception $e3) {
|
||
\Log::error('Error parsing date in format_shamsi_date: ' . $date);
|
||
return null;
|
||
}
|
||
}
|
||
}
|
||
} else {
|
||
return null;
|
||
}
|
||
|
||
if (is_persian_lang()) {
|
||
$formatted = format_jalali_date($carbon_date, $format);
|
||
|
||
return function_exists('persian_number') ? persian_number($formatted) : $formatted;
|
||
}
|
||
|
||
$formatted = $carbon_date->format($format);
|
||
|
||
return $formatted;
|
||
} catch (\Exception $e) {
|
||
\Log::error('Error in format_shamsi_date: ' . $e->getMessage() . ' | Date: ' . (is_string($date) ? $date : 'non-string'));
|
||
return null;
|
||
}
|
||
}
|
||
}
|
||
|
||
if (!function_exists('format_date')) {
|
||
/**
|
||
* Format a date using business settings (Jalali when Persian is enabled).
|
||
*
|
||
* @param mixed $date
|
||
* @param bool $show_time
|
||
* @param object|null $business_details
|
||
* @return string|null
|
||
*/
|
||
function format_date($date, $show_time = false, $business_details = null)
|
||
{
|
||
if (empty($date) && ! (is_string($date) && in_array(strtolower(trim($date)), ['now', 'today'], true))) {
|
||
return null;
|
||
}
|
||
|
||
$format = ! empty($business_details) && ! empty($business_details->date_format)
|
||
? $business_details->date_format
|
||
: session('business.date_format', 'Y/m/d');
|
||
|
||
$time_format = ! empty($business_details) && isset($business_details->time_format)
|
||
? $business_details->time_format
|
||
: session('business.time_format', 24);
|
||
|
||
if (is_string($date) && strtolower(trim($date)) === 'today') {
|
||
$date = \Carbon\Carbon::today();
|
||
}
|
||
|
||
return format_shamsi_date($date, $format, (bool) $show_time, (int) $time_format);
|
||
}
|
||
}
|
||
|
||
if (!function_exists('format_datetime')) {
|
||
/**
|
||
* Format a date with time using business settings.
|
||
*
|
||
* @param mixed $date
|
||
* @param object|null $business_details
|
||
* @return string|null
|
||
*/
|
||
function format_datetime($date, $business_details = null)
|
||
{
|
||
return format_date($date, true, $business_details);
|
||
}
|
||
}
|
||
|
||
if (!function_exists('format_time')) {
|
||
/**
|
||
* Format time using business settings.
|
||
*
|
||
* @param mixed $date
|
||
* @return string|null
|
||
*/
|
||
function format_time($date)
|
||
{
|
||
if (empty($date) && ! (is_string($date) && strtolower(trim($date)) === 'now')) {
|
||
return null;
|
||
}
|
||
|
||
$time_format = session('business.time_format') == 24 ? 'H:i' : 'h:i A';
|
||
|
||
try {
|
||
if (is_string($date) && strtolower(trim($date)) === 'now') {
|
||
$carbon_date = \Carbon\Carbon::now();
|
||
} else {
|
||
$carbon_date = \Carbon\Carbon::createFromTimestamp(strtotime($date));
|
||
}
|
||
|
||
$formatted = $carbon_date->format($time_format);
|
||
|
||
return is_persian_lang() ? persian_number($formatted) : $formatted;
|
||
} catch (\Exception $e) {
|
||
return null;
|
||
}
|
||
}
|
||
}
|
||
|
||
if (!function_exists('format_jalali_month')) {
|
||
/**
|
||
* Get month name from a date (Jalali when Persian is enabled).
|
||
*/
|
||
function format_jalali_month($date): string
|
||
{
|
||
if (empty($date)) {
|
||
return '';
|
||
}
|
||
|
||
$carbon = $date instanceof \Carbon\Carbon ? $date->copy() : \Carbon\Carbon::parse($date);
|
||
|
||
if (is_persian_lang()) {
|
||
return format_jalali_date($carbon, 'F');
|
||
}
|
||
|
||
return $carbon->format('F');
|
||
}
|
||
}
|
||
|
||
if (!function_exists('format_jalali_year')) {
|
||
/**
|
||
* Get year from a date (Jalali when Persian is enabled).
|
||
*/
|
||
function format_jalali_year($date): string
|
||
{
|
||
if (empty($date)) {
|
||
return '';
|
||
}
|
||
|
||
$carbon = $date instanceof \Carbon\Carbon ? $date->copy() : \Carbon\Carbon::parse($date);
|
||
|
||
if (is_persian_lang()) {
|
||
return persian_number(format_jalali_date($carbon, 'Y'));
|
||
}
|
||
|
||
return $carbon->format('Y');
|
||
}
|
||
}
|
||
|
||
if (!function_exists('format_month_year')) {
|
||
/**
|
||
* Format month and year label (Jalali when Persian is enabled).
|
||
*/
|
||
function format_month_year($date, string $format = 'F Y'): string
|
||
{
|
||
if (empty($date)) {
|
||
return '';
|
||
}
|
||
|
||
$carbon = $date instanceof \Carbon\Carbon ? $date->copy() : \Carbon\Carbon::parse($date);
|
||
|
||
if (is_persian_lang()) {
|
||
$formatted = format_jalali_date($carbon, $format);
|
||
|
||
return persian_number($formatted);
|
||
}
|
||
|
||
return $carbon->format($format);
|
||
}
|
||
}
|
||
|
||
if (!function_exists('format_month_number_label')) {
|
||
/**
|
||
* Format a month number (1-12) as a label for tables/charts.
|
||
*/
|
||
function format_month_number_label(int $month, bool $abbreviated = true): string
|
||
{
|
||
if (is_persian_lang()) {
|
||
return persian_month_name($month);
|
||
}
|
||
|
||
return \Carbon\Carbon::createFromFormat('m', (string) $month)->format($abbreviated ? 'M' : 'F');
|
||
}
|
||
}
|
||
|
||
if (!function_exists('format_relative_time')) {
|
||
/**
|
||
* Human-readable relative time (localized for Persian).
|
||
*/
|
||
function format_relative_time($date): string
|
||
{
|
||
if (empty($date)) {
|
||
return '';
|
||
}
|
||
|
||
$carbon = $date instanceof \Carbon\Carbon ? $date->copy() : \Carbon\Carbon::parse($date);
|
||
|
||
if (is_persian_lang()) {
|
||
return persian_number($carbon->locale('fa')->diffForHumans());
|
||
}
|
||
|
||
return $carbon->diffForHumans();
|
||
}
|
||
}
|
||
|
||
if (!function_exists('format_shamsi_chart_label')) {
|
||
/**
|
||
* Helper to format chart labels in Jalali when needed.
|
||
*
|
||
* @param \Carbon\Carbon|string $date
|
||
* @param string $gregorianFormat
|
||
* @param string|null $jalaliFormat
|
||
* @return string
|
||
*/
|
||
function format_shamsi_chart_label($date, string $gregorianFormat, ?string $jalaliFormat = null): string
|
||
{
|
||
$carbon_date = $date instanceof \Carbon\Carbon ? $date->copy() : \Carbon\Carbon::parse($date);
|
||
|
||
if (is_persian_lang()) {
|
||
$format = $jalaliFormat ?? $gregorianFormat;
|
||
$formatted = format_jalali_date($carbon_date, $format);
|
||
|
||
return function_exists('persian_number') ? persian_number($formatted) : $formatted;
|
||
}
|
||
|
||
return $carbon_date->format($gregorianFormat);
|
||
}
|
||
}
|
||
|
||
if (!function_exists('persian_today')) {
|
||
/**
|
||
* Get today's date (or provided date) formatted in Jalali.
|
||
*
|
||
* @param string $format
|
||
* @param \Carbon\Carbon|string|null $date
|
||
* @return string|null
|
||
*/
|
||
function persian_today(string $format = 'Y/m/d', $date = null)
|
||
{
|
||
$date = $date ?? now();
|
||
|
||
return format_shamsi_date($date, $format);
|
||
}
|
||
}
|
||
|
||
if (!function_exists('jalali_to_gregorian')) {
|
||
/**
|
||
* Convert Jalali date string to Gregorian date string for database storage
|
||
*
|
||
* @param string $jalali_date Jalali date string (e.g., "1403/06/13")
|
||
* @param string|null $jalali_format Format of input date (default: 'Y/m/d')
|
||
* @param bool $include_time Whether to include time
|
||
* @return string|null Gregorian date string (e.g., "2024-09-04")
|
||
*/
|
||
function jalali_to_gregorian($jalali_date, $jalali_format = null, $include_time = false)
|
||
{
|
||
if (empty($jalali_date)) {
|
||
return null;
|
||
}
|
||
|
||
if (function_exists('english_number')) {
|
||
$jalali_date = english_number(trim($jalali_date));
|
||
} else {
|
||
$jalali_date = trim($jalali_date);
|
||
}
|
||
|
||
if (class_exists('\Morilog\Jalali\Jalalian') && is_persian_lang()) {
|
||
$jalali_format = $jalali_format ?? session('business.date_format', 'Y/m/d');
|
||
$gregorian_format = $include_time ? 'Y-m-d H:i:s' : 'Y-m-d';
|
||
|
||
try {
|
||
if ($include_time) {
|
||
$time_format = session('business.time_format', 24) == 12 ? ' h:i A' : ' H:i';
|
||
$jalali_date_obj = \Morilog\Jalali\Jalalian::fromFormat($jalali_format.$time_format, $jalali_date);
|
||
} else {
|
||
$jalali_date_obj = \Morilog\Jalali\Jalalian::fromFormat($jalali_format, $jalali_date);
|
||
}
|
||
|
||
return $jalali_date_obj->toCarbon()->format($gregorian_format);
|
||
} catch (\Exception $e) {
|
||
\Log::error('Error converting Jalali to Gregorian: '.$e->getMessage().' | Date: '.$jalali_date);
|
||
}
|
||
}
|
||
|
||
$normalized = str_replace('/', '-', $jalali_date);
|
||
$time_suffix = '';
|
||
if ($include_time && preg_match('/(\d{1,2}):(\d{2})/', $jalali_date, $time_matches)) {
|
||
$hour = (int) $time_matches[1];
|
||
$minute = (int) $time_matches[2];
|
||
|
||
if (session('business.time_format', 24) == 12 && preg_match('/\b(AM|PM)\b/i', $jalali_date, $ampm_matches)) {
|
||
$ampm = strtoupper($ampm_matches[1]);
|
||
if ($ampm === 'PM' && $hour < 12) {
|
||
$hour += 12;
|
||
} elseif ($ampm === 'AM' && $hour === 12) {
|
||
$hour = 0;
|
||
}
|
||
}
|
||
|
||
$time_suffix = sprintf(' %02d:%02d:00', $hour, $minute);
|
||
}
|
||
|
||
if (preg_match('/^(\d{4})-(\d{1,2})-(\d{1,2})/', $normalized, $matches)) {
|
||
[$gy, $gm, $gd] = jalali_to_gregorian_parts((int) $matches[1], (int) $matches[2], (int) $matches[3]);
|
||
|
||
return sprintf('%04d-%02d-%02d', $gy, $gm, $gd).$time_suffix;
|
||
}
|
||
|
||
return $jalali_date;
|
||
}
|
||
}
|
||
|
||
if (!function_exists('gregorian_to_jalali')) {
|
||
/**
|
||
* Convert Gregorian date string to Jalali date string for display
|
||
*
|
||
* @param string|\Carbon\Carbon $gregorian_date Gregorian date string or Carbon instance
|
||
* @param string|null $jalali_format Format of output date (default: 'Y/m/d')
|
||
* @param bool $include_time Whether to include time
|
||
* @return string|null Jalali date string (e.g., "1403/06/13")
|
||
*/
|
||
function gregorian_to_jalali($gregorian_date, $jalali_format = null, $include_time = false)
|
||
{
|
||
if (empty($gregorian_date)) {
|
||
return null;
|
||
}
|
||
|
||
if (!is_persian_lang()) {
|
||
if ($gregorian_date instanceof \Carbon\Carbon) {
|
||
$format = $jalali_format ?? session('business.date_format', 'Y/m/d');
|
||
if ($include_time) {
|
||
$time_format = session('business.time_format', 24) == 12 ? ' h:i A' : ' H:i';
|
||
$format .= $time_format;
|
||
}
|
||
|
||
return $gregorian_date->format($format);
|
||
}
|
||
|
||
return $gregorian_date;
|
||
}
|
||
|
||
$jalali_format = $jalali_format ?? session('business.date_format', 'Y/m/d');
|
||
|
||
try {
|
||
if ($gregorian_date instanceof \Carbon\Carbon) {
|
||
$carbon_date = $gregorian_date->copy();
|
||
} else {
|
||
$carbon_date = \Carbon\Carbon::parse($gregorian_date);
|
||
}
|
||
|
||
if ($include_time) {
|
||
$time_format = session('business.time_format', 24) == 12 ? ' h:i A' : ' H:i';
|
||
$formatted = format_jalali_date($carbon_date, $jalali_format.$time_format);
|
||
} else {
|
||
$formatted = format_jalali_date($carbon_date, $jalali_format);
|
||
}
|
||
|
||
return persian_number($formatted);
|
||
} catch (\Exception $e) {
|
||
\Log::error('Error converting Gregorian to Jalali: '.$e->getMessage().' | Date: '.$gregorian_date);
|
||
|
||
return null;
|
||
}
|
||
}
|
||
}
|
||
|
||
if (!function_exists('persian_words')) {
|
||
/**
|
||
* Convert number to Persian words
|
||
*
|
||
* @param int $number
|
||
* @return string
|
||
*/
|
||
function persian_words($number)
|
||
{
|
||
$ones = ['', 'یک', 'دو', 'سه', 'چهار', 'پنج', 'شش', 'هفت', 'هشت', 'نه'];
|
||
$tens = ['', '', 'بیست', 'سی', 'چهل', 'پنجاه', 'شصت', 'هفتاد', 'هشتاد', 'نود'];
|
||
$hundreds = ['', 'یکصد', 'دویست', 'سیصد', 'چهارصد', 'پانصد', 'ششصد', 'هفتصد', 'هشتصد', 'نهصد'];
|
||
$teens = ['ده', 'یازده', 'دوازده', 'سیزده', 'چهارده', 'پانزده', 'شانزده', 'هفده', 'هجده', 'نوزده'];
|
||
|
||
if ($number == 0) {
|
||
return 'صفر';
|
||
}
|
||
|
||
if ($number < 0) {
|
||
return 'منفی ' . persian_words(abs($number));
|
||
}
|
||
|
||
$result = '';
|
||
|
||
// Thousands
|
||
if ($number >= 1000) {
|
||
$thousands = floor($number / 1000);
|
||
$result .= persian_words($thousands) . ' هزار';
|
||
$number %= 1000;
|
||
if ($number > 0) {
|
||
$result .= ' و ';
|
||
}
|
||
}
|
||
|
||
// Hundreds
|
||
if ($number >= 100) {
|
||
$result .= $hundreds[floor($number / 100)];
|
||
$number %= 100;
|
||
if ($number > 0) {
|
||
$result .= ' و ';
|
||
}
|
||
}
|
||
|
||
// Tens and ones
|
||
if ($number >= 20) {
|
||
$result .= $tens[floor($number / 10)];
|
||
$number %= 10;
|
||
if ($number > 0) {
|
||
$result .= ' و ';
|
||
}
|
||
} elseif ($number >= 10) {
|
||
$result .= $teens[$number - 10];
|
||
$number = 0;
|
||
}
|
||
|
||
if ($number > 0) {
|
||
$result .= $ones[$number];
|
||
}
|
||
|
||
return trim($result);
|
||
}
|
||
}
|
||
|