ultimatepos/app/Http/persian_helpers.php

837 lines
25 KiB
PHP
Raw Permalink 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
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);
}
}