# -*- coding: utf-8 -*- import babel.dates import pytz import logging import odoo from odoo import models, api from odoo.osv import expression from odoo.tools.misc import DEFAULT_SERVER_DATETIME_FORMAT, DEFAULT_SERVER_DATE_FORMAT from odoo.tools.misc import posix_to_ldml, file_open from odoo import http from odoo.http import request import werkzeug from datetime import datetime _logger = logging.getLogger(__name__) def format_date(env, value, lang_code=False, date_format=False): if not value: return '' if isinstance(value, str): try: # Convert string to datetime object for Odoo 18 if len(value) > 10: # datetime value = datetime.fromisoformat(value.replace('Z', '+00:00')) value = odoo.fields.Datetime.context_timestamp(env['res.lang'], value) else: # date value = datetime.fromisoformat(value) except ValueError: return '' lang = env['res.lang']._lang_get(lang_code or env.context.get('lang') or 'en_US') locale = babel.Locale.parse(lang.code) locale = str(locale) if locale.lower().startswith('ar'): locale = "ar" if not date_format: date_format = posix_to_ldml(lang.date_format, locale=locale) return babel.dates.format_date(value, format=date_format, locale=locale) # Only override if not already overridden if not hasattr(odoo.tools.misc, '_original_format_date'): odoo.tools.misc._original_format_date = odoo.tools.misc.format_date odoo.tools.misc.format_date = format_date class WebClientInherit(http.Controller): @http.route('/web/webclient/locale/', type='http', auth="none") def load_locale(self, lang): magic_file_finding = [lang.replace("_", '-').lower(), lang.split('_')[0]] for code in magic_file_finding: if code.lower().startswith('ar'): code = 'ar-sa' try: # Updated for Odoo 18 file structure file_path = f'web/static/lib/moment/locale/{code}.js' file_content = file_open(file_path, 'rb').read() return request.make_response( file_content, headers=[ ('Content-Type', 'application/javascript; charset=utf-8'), ('Cache-Control', 'max-age=36000'), ] ) except (IOError, FileNotFoundError): _logger.debug("No moment locale for code %s", code) return request.make_response("", headers=[ ('Content-Type', 'application/javascript'), ('Cache-Control', 'max-age=36000'), ]) class BaseModelExtend(models.BaseModel): _name = 'basemodel.extend_custom_months' _description = 'Base Model Extension for Custom Months' def _register_hook(self): # Store original method original_method = models.BaseModel._read_group_format_result @api.model def _read_group_format_result_custom(self, rows_dict, lazy_groupby): """ Enhanced version of _read_group_format_result with Arabic locale support. Overrides Odoo 18's method to use 'ar' locale instead of 'ar_SY' for Arabic. """ from odoo.models import READ_GROUP_TIME_GRANULARITY, READ_GROUP_DISPLAY_FORMAT from odoo.tools.misc import get_lang import datetime for group in lazy_groupby: field_name = group.split(':')[0].split('.')[0] field = self._fields.get(field_name) if not field: continue if field.type in ('date', 'datetime'): granularity = group.split(':')[1] if ':' in group else 'month' if granularity in READ_GROUP_TIME_GRANULARITY: # Get locale and modify for Arabic lang = get_lang(self.env) locale = lang.code if locale.lower().startswith('ar'): locale = "ar" fmt = DEFAULT_SERVER_DATETIME_FORMAT if field.type == 'datetime' else DEFAULT_SERVER_DATE_FORMAT interval = READ_GROUP_TIME_GRANULARITY[granularity] elif field.type == "properties": self._read_group_format_result_properties(rows_dict, group) continue for row in rows_dict: value = row.get(group) if isinstance(value, models.BaseModel): row[group] = (value.id, value.sudo().display_name) if value else False value = value.id if not value and field.type == 'many2many': additional_domain = [(field_name, 'not any', [])] else: additional_domain = [(field_name, '=', value)] if field.type in ('date', 'datetime'): if value and isinstance(value, (datetime.date, datetime.datetime)): range_start = value range_end = value + interval if field.type == 'datetime': tzinfo = None if self._context.get('tz') in pytz.all_timezones_set: tzinfo = pytz.timezone(self._context['tz']) range_start = tzinfo.localize(range_start).astimezone(pytz.utc) range_end = tzinfo.localize(range_end).astimezone(pytz.utc) label = babel.dates.format_datetime( range_start, format=READ_GROUP_DISPLAY_FORMAT[granularity], tzinfo=tzinfo, locale=locale ) else: label = babel.dates.format_date( value, format=READ_GROUP_DISPLAY_FORMAT[granularity], locale=locale ) # Special case for weeks if granularity == 'week': from odoo.tools import date_utils year, week = date_utils.weeknumber( babel.Locale.parse(locale), value, ) label = f"W{week} {year:04}" range_start = range_start.strftime(fmt) range_end = range_end.strftime(fmt) row[group] = label row.setdefault('__range', {})[group] = {'from': range_start, 'to': range_end} additional_domain = [ '&', (field_name, '>=', range_start), (field_name, '<', range_end), ] elif not value: row.setdefault('__range', {})[group] = False row['__domain'] = expression.AND([row.get('__domain', []), additional_domain]) # Only override if not already overridden if not hasattr(models.BaseModel, '_months_custom_override'): models.BaseModel._months_custom_override = True models.BaseModel._read_group_format_result = _read_group_format_result_custom return super(BaseModelExtend, self)._register_hook()