ShuanghongS 6 ay önce
ebeveyn
işleme
0cd20df3ef
5 değiştirilmiş dosya ile 1033 ekleme ve 274 silme
  1. 3 0
      include.ini.php
  2. 1 1
      service/robot.class.php
  3. 549 3
      service/tools.class.php
  4. 136 92
      utils/common.class.php
  5. 344 178
      utils/utils.class.php

+ 3 - 0
include.ini.php

@@ -31,6 +31,9 @@ define('DOCUMENT_OCEAN_UPLOAD_PATH', "/DOC" . DS);
 define('DOCUMENT_SFSAIR_UPLOAD_PATH', "/DOC_AIR" . DS);
 define('DOCUMENT_SFSOCEAN_UPLOAD_PATH', "/DOC" . DS);
 
+//define('SERVER_PAHT', 'http://192.168.0.161/k_new_online/');
+define('SERVER_PAHT', 'https://online-beta.kln.com/');
+
 if (preg_match("/(bot|crawl|spider|slurp)/i", $_SERVER['HTTP_USER_AGENT'])) {
     header('HTTP/1.1 403 Forbidden');
     exit;

+ 1 - 1
service/robot.class.php

@@ -365,7 +365,7 @@ class robot{
         }
 
         if ($operate == "ai_chat_fixed_init"){
-            $rs = common::excuteListSql("select fixed_faq,secondary_interaction_content from public.kln_robot_chat_fixed where active = true");
+            $rs = common::excuteListSql("select fixed_faq,secondary_interaction_content from public.kln_robot_chat_fixed where active = true order by id");
             $data = array();
             foreach($rs as $v){
                 $data[] = array("label" => $v['fixed_faq'],"value" =>$v['fixed_faq'],"isLong" => strlen($v['fixed_faq']) >60);

+ 549 - 3
service/tools.class.php

@@ -487,7 +487,7 @@ class tools {
         $operate = utils::_get('operate');
         $operate = strtolower($operate);
 
-        if ($operate == "notifications_init"){
+        if ($operate == "notifications_init_old"){
             $rules_type = common::check_input($_REQUEST['rules_type']);
             $milestoneData = array();
             $containerData = array();
@@ -567,7 +567,55 @@ class tools {
 
         }
 
-        if($operate == "notifications_see_all"){
+        if ($operate == "notifications_init"){
+            $rules_type = common::check_input($_REQUEST['rules_type']);
+            if ($rules_type == "all"){
+                $rules_type = "Milestone_Update;Container_Status_Update;Departure/Arrival_Delay;ETD/ETA_Change;Feature_Update;Passwond_Notifcations";
+                $data = $this->getNotificationsNew($rules_type,"all");
+            } else {
+                $data = $this->getNotificationsNew($rules_type,"all");
+                
+            }
+            foreach($data as $k => $v){
+                if($v['frequency_type'] == 'Daily' || $v['frequency_type'] == 'Weekly'){
+                    $numericRecords = $v['total_count'];
+                    $numericRecords_one = 0;
+                    $numericRecords_two = 0;
+                    if($v['notifiation_type'] == 'Departure/Arrival_Delay'){
+                        $numericRecords_one = $v['departure_count'];
+                        $numericRecords_two = $v['arrival_count'];
+                    }
+                    if ($v['notifiation_type'] == 'ETD/ETA_Change'){
+                        $numericRecords_one = $v['etd_count'];
+                        $numericRecords_two = $v['eta_count'];
+                    }
+                        
+                    $data[$k]["numericRecords"]= intval($numericRecords);
+                    //对Delay and change 特殊处理
+                    $data[$k]["numericRecords_one"]= intval($numericRecords_one);
+                    $data[$k]["numericRecords_two"]= intval($numericRecords_two);
+                }
+            }
+
+            $info = array();
+            foreach($data as $mInfo){
+                $eventCard = $this->getEventCard($mInfo);
+                if(!empty($mInfo['other_type']) && $mInfo['other_type'] == "password"){
+                    $info[] = array("notificationType"=>"password","info" =>$eventCard);
+                }elseif(!empty($mInfo['other_type']) && $mInfo['other_type'] == "feature"){
+                    $info[] = array("notificationType"=>"feature","info" =>$eventCard);
+                }else{
+                    $info[] = array("notificationType"=>"event","info" =>$eventCard);
+                }
+            }
+
+            $returnData = $info;
+            common::echo_json_encode(200,$returnData);
+            exit();
+
+        }
+
+        if($operate == "notifications_see_all_old"){
             $rules_type = common::check_input($_REQUEST['rules_type']);
             $frequency_type = common::check_input($_REQUEST['frequency_type']);  //这个只会传daily 和weekly
             $insert_date_format = common::check_input($_REQUEST['insert_date_format']);
@@ -613,6 +661,50 @@ class tools {
             exit();
         }
 
+        if($operate == "notifications_see_all"){
+            $rules_type = common::check_input($_REQUEST['rules_type']);
+            $frequency_type = common::check_input($_REQUEST['frequency_type']);  //这个只会传daily 和weekly
+            $insert_date_format = common::check_input($_REQUEST['insert_date_format']);
+
+            $moreData = $this->getNotificationsSeeAll($rules_type,$frequency_type,$insert_date_format);
+
+            //这个函数里面带有分开计数的信息
+            $dataInfo =utils::getDailyAndweeklyFrist($moreData);
+            $returnData = array();
+            $notificationList = array();
+            $ids = array();
+            foreach($moreData as $key => $data){
+                $eventCard = $this->getEventCard($data);
+                //sea all的数据格式和查询全部的格式有区别
+                if($key == 0){
+                    $returnData["title"] = $eventCard["title"];
+                    if($eventCard["type"] == "change" || $eventCard["type"] == "delay"){
+                        $returnData["etdOrdeparturNum"] = $dataInfo["numericRecords_one"];
+                        $returnData["etaOrarrivalNum"] =$dataInfo["numericRecords_two"];
+                        $returnData["type"] =$eventCard["type"];
+                    }else{
+                        $returnData["numericRecords"] = $dataInfo["numericRecords"];
+                    }
+                }
+                //移除不需要的字段
+                unset($eventCard["title"]);
+                $notificationList[] = $eventCard;
+                $ids[] =  $data['id'];
+            }
+            if(!empty($notificationList)){
+                $returnData["notificationList"] = $notificationList;
+            }
+
+            //点击seall会默认全部标记为已读
+            if(!empty($ids)){
+                $more_param = common::getInNotInSqlForSearch(strtolower(utils::implode(';',$ids)));
+                $markReadSql = "update public.kln_notifiation_info set is_send_message = now(),readed_date = now() where id in ($more_param)";
+                common::excuteUpdateSql($markReadSql);
+            }
+            common::echo_json_encode(200,$returnData);
+            exit();
+        }
+
         if($operate == "notifications_read"){
             $read_type = common::check_input($_POST["read_type"]);
             $id = $_POST["id"];
@@ -646,7 +738,7 @@ class tools {
             exit();
         }
 
-        if ($operate == "notifications_message_init"){
+        if ($operate == "notifications_message_init_old"){
             //查询所有情况得未读情况 查询最近一年的情况
             $unreadSql = "with countTbale as (
                 select ni.notifiation_type,
@@ -770,6 +862,114 @@ class tools {
             exit();
         }
 
+        if ($operate == "notifications_message_init"){
+            //查询所有情况得未读情况 查询最近一年的情况
+            $unreadSql = "with countTbale as (
+                select ni.notifiation_type,
+                    case when COALESCE(ni.frequency_type,'') = 'Daily'
+                            then to_char(timezone(ni.daily_time_zone, ni.insert_date),'Mon DD, YYYY')
+                    when COALESCE(ni.frequency_type,'') = 'Weekly' 
+                            then to_char(timezone(ni.weekly_time_zone,ni.insert_date)::date - (((EXTRACT(dow FROM timezone(ni.weekly_time_zone,ni.insert_date)::date)::integer - ni.weekly_week::integer + 7) % 7) ||' days')::INTERVAL,'Mon DD, YYYY')
+                            || ' - ' ||
+                            to_char(timezone(ni.weekly_time_zone,ni.insert_date)::date + (((ni.weekly_week::integer - EXTRACT(dow FROM timezone(ni.weekly_time_zone,ni.insert_date)::date)::integer + 7) % 7)-1 + CASE WHEN EXTRACT(DOW FROM timezone(ni.weekly_time_zone,ni.insert_date)::date)::integer=ni.weekly_week::integer THEN 7 ELSE 0 END ||' days')::INTERVAL,'Mon DD, YYYY')
+                    else '' 
+                    end  as insert_date_format  
+                from  public.kln_notifiation_info ni
+                where ni.insert_date > NOW() - INTERVAL '1 year' 
+                    and ni.notifications_method = 'true' and ni.is_send_message is null
+                    and lower(ni.user_login) = '".strtolower(_getLoginName())."'
+                    and ((ni.frequency_type = 'Daily'  
+                                and (case  when (timezone(ni.daily_time_zone, NOW())::time > ni.daily_time::time)
+		                                then timezone(ni.daily_time_zone,ni.insert_date)::date < timezone(ni.daily_time_zone, NOW())::date
+	                                else
+		                                timezone(ni.daily_time_zone,ni.insert_date)::date < timezone(ni.daily_time_zone, NOW())::date - '1 days'::INTERVAL
+	                                end))
+                            or (ni.frequency_type = 'Weekly' 
+                                and (case when (timezone(ni.weekly_time_zone, NOW())::time < ni.weekly_time::time 
+                                            and timezone(ni.weekly_time_zone,NOW())::date = (timezone(ni.weekly_time_zone,NOW())::date - (((EXTRACT(dow FROM timezone(ni.weekly_time_zone,NOW())::date)::integer - ni.weekly_week::integer + 7) % 7) ||' days')::INTERVAL)::date)
+                                        then timezone(ni.weekly_time_zone,ni.insert_date)::date < ((timezone(ni.weekly_time_zone,NOW())::date - (((EXTRACT(dow FROM timezone(ni.weekly_time_zone,NOW())::date)::integer - ni.weekly_week::integer + 7) % 7) ||' days')::INTERVAL)::date - '7 days'::INTERVAL)::date
+                                    else 
+                                        timezone(ni.weekly_time_zone,ni.insert_date)::date < (timezone(ni.weekly_time_zone,NOW())::date - (((EXTRACT(dow FROM timezone(ni.weekly_time_zone,NOW())::date)::integer - ni.weekly_week::integer + 7) % 7) ||' days')::INTERVAL)::date
+                                    end))) 
+                      group by ni.notifiation_type,insert_date_format 
+            union all
+                select ni.notifiation_type, '' as insert_date_format  
+                    from  public.kln_notifiation_info ni
+                    where ni.insert_date > NOW() - INTERVAL '1 year' 
+                        and lower(ni.user_login) in ('".strtolower(_getLoginName())."','all_user')
+                        and ni.notifications_method = 'true' and ni.is_send_message is null
+                        and frequency_type = 'Instant'  
+            )
+            select 
+                    sum(case when (1<>1 or (notifiation_type='Milestone_Update')) then 1 else 0 end) as m_rc,
+                    sum(case when (1<>1 or (notifiation_type='Container_Status_Update')) then 1 else 0 end) as cs_rc,
+                    sum(case when (1<>1 or (notifiation_type='Departure/Arrival_Delay')) then 1 else 0 end) as da_rc,
+                    sum(case when (1<>1 or (notifiation_type='ETD/ETA_Change')) then 1 else 0 end) as ec_rc,
+                    sum(case when (1<>1 or (notifiation_type='Feature_Update')) then 1 else 0 end) as f_rc
+                from  countTbale ";
+            
+            $count = common::excuteObjectSql($unreadSql);
+
+            //单独的选中的数据
+            $rules_type = common::check_input($_REQUEST['rules_type']);
+            $data = $this->getNotificationsNew($rules_type,"all");
+
+            $unreadCount = 0;
+            $readCount = 0;
+            foreach($data as $k => $v){
+                if(!empty($v["is_send_message"])){
+                    $readCount +=1;
+                }else{
+                    $unreadCount +=1;
+                }
+                if($v['frequency_type'] == 'Daily' || $v['frequency_type'] == 'Weekly'){
+                    $numericRecords = $v['total_count'];
+                    $numericRecords_one = 0;
+                    $numericRecords_two = 0;
+                    if($v['notifiation_type'] == 'Departure/Arrival_Delay'){
+                        $numericRecords_one = $v['departure_count'];
+                        $numericRecords_two = $v['arrival_count'];
+                    }
+                    if ($v['notifiation_type'] == 'ETD/ETA_Change'){
+                        $numericRecords_one = $v['etd_count'];
+                        $numericRecords_two = $v['eta_count'];
+                    }
+                        
+                    $data[$k]["numericRecords"]= intval($numericRecords);
+                    //对Delay and change 特殊处理
+                    $data[$k]["numericRecords_one"]= intval($numericRecords_one);
+                    $data[$k]["numericRecords_two"]= intval($numericRecords_two);
+                }
+            }
+            $info = array();
+            foreach($data as $mInfo){
+                $eventCard = $this->getEventCard($mInfo);
+                if(!empty($mInfo['other_type']) && $mInfo['other_type'] == "password"){
+                    $info[] = array("notificationType"=>"password","info" =>$eventCard);
+                }elseif(!empty($mInfo['other_type']) && $mInfo['other_type'] == "feature"){
+                    $info[] = array("notificationType"=>"feature","info" =>$eventCard);
+                }else{
+                    $info[] = array("notificationType"=>"event","info" =>$eventCard);
+                }
+            }
+
+            //返回数据结构
+            $returnData = array();
+            $m_rc = empty($count['m_rc']) ? 0 : intval($count['m_rc']);
+            $cs_rc = empty($count['cs_rc']) ? 0 : intval($count['cs_rc']);
+            $da_rc = empty($count['da_rc']) ? 0 : intval($count['da_rc']);
+            $ec_rc = empty($count['ec_rc']) ? 0 : intval($count['ec_rc']);
+            $f_rc = empty($count['f_rc']) ? 0 : intval($count['f_rc']);
+            $returnData['countList'] = array($m_rc,$cs_rc,$da_rc,$ec_rc,$f_rc);
+            $returnData['allCount'] =count($data);
+            $returnData['unreadCount'] =$unreadCount;
+            $returnData['readCount'] =$readCount;
+            $returnData['cardList'] =$info;
+
+            common::echo_json_encode(200,$returnData);
+            exit();
+        }
+
         if ($operate == "check_notifications_message"){
             $checkUnread = "select id
                 from public.kln_notifiation_info ni 
@@ -1527,6 +1727,352 @@ class tools {
         return $retData;
     }
 
+    public function getNotificationsNew($notifiation_type,$frequency_type,$insert_date_format = null){
+        if ($frequency_type == "all"){
+            //Daily 频率:超过昨天的数据,不在限制提醒的daily_time,全部应该查出来
+            //比如今天周四 17号, 上第一周截止是10号, 10号以后的数据,不在做weekly_time和weekly_week 的限制,直接查出来
+            $sql_where = " and (ni.frequency_type = 'Instant' 
+                            or (ni.frequency_type = 'Daily'  
+                                and (case  when (timezone(ni.daily_time_zone, NOW())::time > ni.daily_time::time)
+		                                then timezone(ni.daily_time_zone,ni.insert_date)::date < timezone(ni.daily_time_zone, NOW())::date
+	                                else
+		                                timezone(ni.daily_time_zone,ni.insert_date)::date < timezone(ni.daily_time_zone, NOW())::date - '1 days'::INTERVAL
+	                                end))
+                                    
+                            or (ni.frequency_type = 'Weekly' 
+                                and (case when (timezone(ni.weekly_time_zone, NOW())::time < ni.weekly_time::time 
+                                            and timezone(ni.weekly_time_zone,NOW())::date = (timezone(ni.weekly_time_zone,NOW())::date - (((EXTRACT(dow FROM timezone(ni.weekly_time_zone,NOW())::date)::integer - ni.weekly_week::integer + 7) % 7) ||' days')::INTERVAL)::date)
+                                        then timezone(ni.weekly_time_zone,ni.insert_date)::date < ((timezone(ni.weekly_time_zone,NOW())::date - (((EXTRACT(dow FROM timezone(ni.weekly_time_zone,NOW())::date)::integer - ni.weekly_week::integer + 7) % 7) ||' days')::INTERVAL)::date - '7 days'::INTERVAL)::date
+                                    else 
+                                        timezone(ni.weekly_time_zone,ni.insert_date)::date < (timezone(ni.weekly_time_zone,NOW())::date - (((EXTRACT(dow FROM timezone(ni.weekly_time_zone,NOW())::date)::integer - ni.weekly_week::integer + 7) % 7) ||' days')::INTERVAL)::date
+                                    end)))";
+        } elseif($frequency_type == "Daily"){
+            $sql_where = " and (ni.frequency_type = 'Daily'  
+                                and (case  when (timezone(ni.daily_time_zone, NOW())::time > ni.daily_time::time)
+		                                then timezone(ni.daily_time_zone,ni.insert_date)::date < timezone(ni.daily_time_zone, NOW())::date
+	                                else
+		                                timezone(ni.daily_time_zone,ni.insert_date)::date < timezone(ni.daily_time_zone, NOW())::date - '1 days'::INTERVAL
+	                                end))";
+
+        } elseif($frequency_type == "Weekly"){
+            //当前now()的配置是周2。 如今天是4-17,最小边界值是4.15- 4.21
+            //                     如今天是4-15(这个是周二),最小边界值是4.15- 4.21
+            //                     如今天是4-14,最小边界值是4.8- 4.14
+            //   当 前时间是 4.15时,但没到规定的time,取4.8,但此时的边界值4.15- 4.21(这个是一个例外,除此之外,可以直接用当前now的最小边界值);
+            //   当 前时间是 4.15时,但到规定的time,取4.15
+
+            $sql_where = " and (ni.frequency_type = 'Weekly' 
+                                and (case when (timezone(ni.weekly_time_zone, NOW())::time < ni.weekly_time::time 
+                                            and timezone(ni.weekly_time_zone,NOW())::date = (timezone(ni.weekly_time_zone,NOW())::date - (((EXTRACT(dow FROM timezone(ni.weekly_time_zone,NOW())::date)::integer - ni.weekly_week::integer + 7) % 7) ||' days')::INTERVAL)::date)
+                                        then timezone(ni.weekly_time_zone,ni.insert_date)::date < ((timezone(ni.weekly_time_zone,NOW())::date - (((EXTRACT(dow FROM timezone(ni.weekly_time_zone,NOW())::date)::integer - ni.weekly_week::integer + 7) % 7) ||' days')::INTERVAL)::date - '7 days'::INTERVAL)::date
+                                    else 
+                                        timezone(ni.weekly_time_zone,ni.insert_date)::date < (timezone(ni.weekly_time_zone,NOW())::date - (((EXTRACT(dow FROM timezone(ni.weekly_time_zone,NOW())::date)::integer - ni.weekly_week::integer + 7) % 7) ||' days')::INTERVAL)::date
+                                    end))";
+        }
+        if (!empty($_REQUEST['current_time'])){
+            // $sql_where .= " and ni.insert_date >= TO_TIMESTAMP('".$_REQUEST['current_time']."', 'MM/DD/YYYY HH24:MI:SS') - interval '5 minutes'
+            //     and ni.is_send_message is null";
+            //这里都以服务器时间,检查标准
+            $sql_where .= " and ni.insert_date >= now() - interval '5 minutes' and ni.is_send_message is null";
+        }
+        if (!empty($_REQUEST['info_type']) && $_REQUEST['info_type'] == 'true'){
+            $sql_where .= " and ni.is_send_message is null";
+        }
+        $more_param = common::getInNotInSqlForSearch($notifiation_type);
+
+        $sql = "WITH base_data AS (
+                    SELECT 
+                        id,
+                        notifiation_type,
+                        frequency_type,
+                        insert_date,
+                        case when COALESCE(ni.frequency_type,'') = 'Daily'
+                                then to_char(timezone(ni.daily_time_zone, ni.insert_date),'Mon DD, YYYY')
+                            when COALESCE(ni.frequency_type,'') = 'Weekly' 
+                                then to_char(timezone(ni.weekly_time_zone,ni.insert_date)::date - (((EXTRACT(dow FROM timezone(ni.weekly_time_zone,ni.insert_date)::date)::integer - ni.weekly_week::integer + 7) % 7) ||' days')::INTERVAL,'Mon DD, YYYY')
+                                    || ' - ' ||
+                                    to_char(timezone(ni.weekly_time_zone,ni.insert_date)::date + (((ni.weekly_week::integer - EXTRACT(dow FROM timezone(ni.weekly_time_zone,ni.insert_date)::date)::integer + 7) % 7)-1 + CASE WHEN EXTRACT(DOW FROM timezone(ni.weekly_time_zone,ni.insert_date)::date)::integer=ni.weekly_week::integer THEN 7 ELSE 0 END ||' days')::INTERVAL,'Mon DD, YYYY')
+                                else '' 
+                        end  as insert_date_format,
+                        -- 提取用于去重的字段
+                        serial_no,
+                        milestone_code,
+                        ctnr,
+                        ctnr_status_code,
+                        delay_name,
+                        date_change_name
+                    FROM public.kln_notifiation_info ni
+                    WHERE lower(ni.user_login) in ('".strtolower(_getLoginName())."','all_user')
+                    AND ni.insert_date > NOW() - INTERVAL '1 year' 
+                    and lower(ni.notifiation_type) in ($more_param)
+                    AND frequency_type IN ('Daily', 'Weekly')
+                    ".$sql_where." and ni.notifications_method = true
+                ),
+                base_instant_data AS (
+                    SELECT id,insert_date FROM public.kln_notifiation_info ni
+                    WHERE  lower(ni.user_login) in ('".strtolower(_getLoginName())."','all_user')
+                    AND ni.insert_date > NOW() - INTERVAL '1 year'
+                    and lower(ni.notifiation_type) in ($more_param)
+                    AND frequency_type  = 'Instant'
+                    ".$sql_where." and ni.notifications_method = true
+                ),
+                -- Step 1: 按 insert_date_format + notifiation_type 分组,统计去重数量
+                grouped_stats AS (
+                    SELECT
+                        insert_date_format,
+                        notifiation_type,
+
+                        COUNT(DISTINCT 
+                            CASE WHEN notifiation_type = 'Milestone_Update' THEN (serial_no || '_' || milestone_code)
+                                WHEN notifiation_type = 'Container_Status_Update' THEN (ctnr || '_' || ctnr_status_code)
+                                WHEN notifiation_type = 'Departure/Arrival_Delay' THEN (serial_no || '_' || delay_name)
+                                WHEN notifiation_type = 'ETD/ETA_Change' THEN (serial_no || '_' || date_change_name)
+                            END
+                        ) AS total_count,
+
+                        -- Departure / Arrival 单独统计
+                        COUNT(DISTINCT 
+                            CASE WHEN notifiation_type = 'Departure/Arrival_Delay' AND delay_name LIKE '%Departure%' THEN (serial_no || '_' || delay_name) END
+                        ) AS departure_count,
+
+                        COUNT(DISTINCT 
+                            CASE WHEN notifiation_type = 'Departure/Arrival_Delay' AND delay_name LIKE '%Arrival%' THEN (serial_no || '_' || delay_name) END
+                        ) AS arrival_count,
+
+                        -- ETD / ETA 单独统计
+                        COUNT(DISTINCT 
+                            CASE WHEN notifiation_type = 'ETD/ETA_Change' AND date_change_name LIKE '%ETD%' THEN (serial_no || '_' || date_change_name) END
+                        ) AS etd_count,
+
+                        COUNT(DISTINCT 
+                            CASE WHEN notifiation_type = 'ETD/ETA_Change' AND date_change_name LIKE '%ETA%' THEN (serial_no || '_' || date_change_name) END
+                        ) AS eta_count
+
+                    FROM base_data 
+                        where notifiation_type in ('Milestone_Update','Container_Status_Update','Departure/Arrival_Delay','ETD/ETA_Change')
+                    GROUP BY insert_date_format, notifiation_type
+                ),
+                -- Step 2: 找出每个 insert_date_format + notifiation_type 组内的最新一条记录
+                latest_records AS (
+                    SELECT DISTINCT ON (insert_date_format, notifiation_type)
+                        ni.id,
+                        ni.notifiation_type,
+                        ni.insert_date,
+                        bd.insert_date_format
+                    FROM public.kln_notifiation_info ni
+                    INNER JOIN base_data bd
+                        ON ni.id = bd.id
+                    ORDER BY insert_date_format, notifiation_type, ni.insert_date DESC
+                ),
+                daily_weekly_summary AS (
+                    SELECT
+                        lr.id,
+                        lr.insert_date,
+                        gs.total_count,
+                        gs.departure_count,
+                        gs.arrival_count,
+                        gs.etd_count,
+                        gs.eta_count
+                    FROM latest_records lr
+                    LEFT JOIN grouped_stats gs
+                        ON lr.insert_date_format = gs.insert_date_format
+                        AND lr.notifiation_type = gs.notifiation_type
+                    UNION ALL
+                        SELECT 
+                            id,
+                            insert_date,
+                            NULL::INT AS total_count,
+                            NULL::INT AS departure_count,
+                            NULL::INT AS arrival_count,
+                            NULL::INT AS etd_count,
+                            NULL::INT AS eta_count
+                        FROM base_instant_data    
+                ),
+                summary AS (
+                    SELECT id ,
+                        insert_date,
+                        total_count,
+                        departure_count,
+                        arrival_count,
+                        etd_count,
+                        eta_count
+                    FROM daily_weekly_summary order by insert_date desc
+                )
+                select *
+                 from  (select ni.*,
+                    case when ni.notifiation_type = 'Departure/Arrival_Delay' and ni.delay_unit = 'days'
+                            then (EXTRACT(DAY FROM ((delay_act_date||' '||delay_act_time)::timestamp - (delay_est_date||' '||delay_est_time)::timestamp)))
+                        when ni.notifiation_type = 'Departure/Arrival_Delay' and ni.delay_unit = 'hours'
+                            then (FLOOR(EXTRACT(epoch FROM ((delay_act_date||' '||delay_act_time)::timestamp - (delay_est_date||' '||delay_est_time)::timestamp))/3600))
+                        else 0 
+                    end  as _delay_diff,
+
+                    case when COALESCE(ni.frequency_type,'') = 'Daily'
+                            then to_char(timezone(ni.daily_time_zone, ni.insert_date),'Mon DD, YYYY')
+                        when COALESCE(ni.frequency_type,'') = 'Weekly' 
+                            then to_char(timezone(ni.weekly_time_zone,ni.insert_date)::date - (((EXTRACT(dow FROM timezone(ni.weekly_time_zone,ni.insert_date)::date)::integer - ni.weekly_week::integer + 7) % 7) ||' days')::INTERVAL,'Mon DD, YYYY')
+                            || ' - ' ||
+                            to_char(timezone(ni.weekly_time_zone,ni.insert_date)::date + (((ni.weekly_week::integer - EXTRACT(dow FROM timezone(ni.weekly_time_zone,ni.insert_date)::date)::integer + 7) % 7)-1 + CASE WHEN EXTRACT(DOW FROM timezone(ni.weekly_time_zone,ni.insert_date)::date)::integer=ni.weekly_week::integer THEN 7 ELSE 0 END ||' days')::INTERVAL,'Mon DD, YYYY')
+                        else '' 
+                    end  as insert_date_format,
+
+                    case when  COALESCE(ni.frequency_type,'') = 'Instant'
+                            then  to_char(timezone(ddd.default_time_zone, ni.insert_date),'YYYY-mm-dd HH24:MI:SS')
+                        when COALESCE(ni.frequency_type,'') = 'Daily'
+                            then to_char(timezone(ni.daily_time_zone, ni.insert_date),'YYYY-mm-dd')||' '||ni.daily_time
+                        when   COALESCE(ni.frequency_type,'') = 'Weekly'  
+                            then to_char(timezone(ni.weekly_time_zone,ni.insert_date)::date + (((ni.weekly_week::integer - EXTRACT(dow FROM timezone(ni.weekly_time_zone,ni.insert_date)::date)::integer + 7) % 7) + CASE WHEN EXTRACT(DOW FROM timezone(ni.weekly_time_zone,ni.insert_date)::date)::integer=ni.weekly_week::integer THEN 7 ELSE 0 END ||' days')::INTERVAL,'YYYY-mm-dd')||' '||ni.weekly_time
+                        else ''    
+                    end  as first_notifiation_date,
+
+                    case when ni.notifiation_type ='Milestone_Update' 
+                        then public.getPreviousMilestone(ni.serial_no,ni.milestone_code,ccc.transport_mode,ccc.order_from)
+                        else ''
+                    end as milestone_previous_json,
+
+                    case when ni.notifiation_type ='Container_Status_Update' 
+                        then public.getPreviousCtnrStatus(ni.serial_no,ni.ctnr,ni.ctnr_status_code)
+                        else ''
+                    end as ctnr_previous_json,
+
+                    case when ni.notifiation_type ='Container_Status_Update' 
+                        then (select description 
+                                    from public.ra_online_edi_event e where e.ra_name = ni.ctnr_status_code limit 1)
+                        else ''
+                    end as ctnr_desc,
+
+                    ccc.order_from,ccc.h_bol,ccc.transport_mode,ccc.m_bol,eee.*
+                from public.kln_notifiation_info ni
+                        inner join LATERAL (select oo.h_bol,oo.transport_mode,oo.order_from,oo.m_bol 
+                            from public.kln_ocean oo 
+                            where oo.serial_no = ni.serial_no limit 1) ccc on true
+                        inner join LATERAL (select id,total_count,departure_count,arrival_count,etd_count,eta_count
+                            from summary 
+                            where id = ni.id ) eee on true
+                        left join LATERAL (select COALESCE(ke.default_time_zone,'UTC-08') as default_time_zone from public.kln_user_extend ke where lower(ke.user_login) = lower(ni.user_login) limit 1) ddd on true    
+                order by ni.insert_date desc) aa";
+        error_log($sql);        
+        $retData = common::excuteListSql($sql);
+        return $retData;
+    }
+
+    public function getNotificationsSeeAll($notifiation_type,$frequency_type,$insert_date_format = null){
+        if ($frequency_type == "all"){
+            //Daily 频率:超过昨天的数据,不在限制提醒的daily_time,全部应该查出来
+            //比如今天周四 17号, 上第一周截止是10号, 10号以后的数据,不在做weekly_time和weekly_week 的限制,直接查出来
+            $sql_where = " and (ni.frequency_type = 'Instant' 
+                            or (ni.frequency_type = 'Daily'  
+                                and (case  when (timezone(ni.daily_time_zone, NOW())::time > ni.daily_time::time)
+		                                then timezone(ni.daily_time_zone,ni.insert_date)::date < timezone(ni.daily_time_zone, NOW())::date
+	                                else
+		                                timezone(ni.daily_time_zone,ni.insert_date)::date < timezone(ni.daily_time_zone, NOW())::date - '1 days'::INTERVAL
+	                                end))
+                                    
+                            or (ni.frequency_type = 'Weekly' 
+                                and (case when (timezone(ni.weekly_time_zone, NOW())::time < ni.weekly_time::time 
+                                            and timezone(ni.weekly_time_zone,NOW())::date = (timezone(ni.weekly_time_zone,NOW())::date - (((EXTRACT(dow FROM timezone(ni.weekly_time_zone,NOW())::date)::integer - ni.weekly_week::integer + 7) % 7) ||' days')::INTERVAL)::date)
+                                        then timezone(ni.weekly_time_zone,ni.insert_date)::date < ((timezone(ni.weekly_time_zone,NOW())::date - (((EXTRACT(dow FROM timezone(ni.weekly_time_zone,NOW())::date)::integer - ni.weekly_week::integer + 7) % 7) ||' days')::INTERVAL)::date - '7 days'::INTERVAL)::date
+                                    else 
+                                        timezone(ni.weekly_time_zone,ni.insert_date)::date < (timezone(ni.weekly_time_zone,NOW())::date - (((EXTRACT(dow FROM timezone(ni.weekly_time_zone,NOW())::date)::integer - ni.weekly_week::integer + 7) % 7) ||' days')::INTERVAL)::date
+                                    end)))";
+        } elseif($frequency_type == "Daily"){
+            $sql_where = " and (ni.frequency_type = 'Daily'  
+                                and (case  when (timezone(ni.daily_time_zone, NOW())::time > ni.daily_time::time)
+		                                then timezone(ni.daily_time_zone,ni.insert_date)::date < timezone(ni.daily_time_zone, NOW())::date
+	                                else
+		                                timezone(ni.daily_time_zone,ni.insert_date)::date < timezone(ni.daily_time_zone, NOW())::date - '1 days'::INTERVAL
+	                                end))";
+
+        } elseif($frequency_type == "Weekly"){
+            //当前now()的配置是周2。 如今天是4-17,最小边界值是4.15- 4.21
+            //                     如今天是4-15(这个是周二),最小边界值是4.15- 4.21
+            //                     如今天是4-14,最小边界值是4.8- 4.14
+            //   当 前时间是 4.15时,但没到规定的time,取4.8,但此时的边界值4.15- 4.21(这个是一个例外,除此之外,可以直接用当前now的最小边界值);
+            //   当 前时间是 4.15时,但到规定的time,取4.15
+
+            $sql_where = " and (ni.frequency_type = 'Weekly' 
+                                and (case when (timezone(ni.weekly_time_zone, NOW())::time < ni.weekly_time::time 
+                                            and timezone(ni.weekly_time_zone,NOW())::date = (timezone(ni.weekly_time_zone,NOW())::date - (((EXTRACT(dow FROM timezone(ni.weekly_time_zone,NOW())::date)::integer - ni.weekly_week::integer + 7) % 7) ||' days')::INTERVAL)::date)
+                                        then timezone(ni.weekly_time_zone,ni.insert_date)::date < ((timezone(ni.weekly_time_zone,NOW())::date - (((EXTRACT(dow FROM timezone(ni.weekly_time_zone,NOW())::date)::integer - ni.weekly_week::integer + 7) % 7) ||' days')::INTERVAL)::date - '7 days'::INTERVAL)::date
+                                    else 
+                                        timezone(ni.weekly_time_zone,ni.insert_date)::date < (timezone(ni.weekly_time_zone,NOW())::date - (((EXTRACT(dow FROM timezone(ni.weekly_time_zone,NOW())::date)::integer - ni.weekly_week::integer + 7) % 7) ||' days')::INTERVAL)::date
+                                    end))";
+        }
+        //这里的查询会把不同日期的但hbol相同的信息,过滤掉只剩下最新的那一条。所以移除
+        if (!empty($insert_date_format)){
+            $sql_where = " and  eee.insert_date_format = '$insert_date_format'";
+        }
+        
+        $more_param = common::getInNotInSqlForSearch($notifiation_type);
+        $sql = "select *
+                 from  (select ni.*,
+                 ROW_NUMBER() OVER (
+                        PARTITION BY 
+                            CASE 
+                                WHEN notifiation_type = 'Milestone_Update' THEN (serial_no || '_' || milestone_code)
+                                WHEN notifiation_type = 'Container_Status_Update' THEN (ctnr || '_' || ctnr_status_code)
+                                WHEN notifiation_type = 'Departure/Arrival_Delay' THEN (serial_no || '_' || delay_name)
+                                WHEN notifiation_type = 'ETD/ETA_Change' THEN (serial_no || '_' || date_change_name)
+                            END
+                        ORDER BY insert_date DESC
+                    ) AS rn,
+                    case when ni.notifiation_type = 'Departure/Arrival_Delay' and ni.delay_unit = 'days'
+                            then (EXTRACT(DAY FROM ((delay_act_date||' '||delay_act_time)::timestamp - (delay_est_date||' '||delay_est_time)::timestamp)))
+                        when ni.notifiation_type = 'Departure/Arrival_Delay' and ni.delay_unit = 'hours'
+                            then (FLOOR(EXTRACT(epoch FROM ((delay_act_date||' '||delay_act_time)::timestamp - (delay_est_date||' '||delay_est_time)::timestamp))/3600))
+                        else 0 
+                    end  as _delay_diff,
+                    eee.insert_date_format,
+                    case when  COALESCE(ni.frequency_type,'') = 'Instant'
+                            then  to_char(timezone(ddd.default_time_zone, ni.insert_date),'YYYY-mm-dd HH24:MI:SS')
+                        when COALESCE(ni.frequency_type,'') = 'Daily'
+                            then to_char(timezone(ni.daily_time_zone, ni.insert_date),'YYYY-mm-dd')||' '||ni.daily_time
+                        when   COALESCE(ni.frequency_type,'') = 'Weekly'  
+                            then to_char(timezone(ni.weekly_time_zone,ni.insert_date)::date + (((ni.weekly_week::integer - EXTRACT(dow FROM timezone(ni.weekly_time_zone,ni.insert_date)::date)::integer + 7) % 7) + CASE WHEN EXTRACT(DOW FROM timezone(ni.weekly_time_zone,ni.insert_date)::date)::integer=ni.weekly_week::integer THEN 7 ELSE 0 END ||' days')::INTERVAL,'YYYY-mm-dd')||' '||ni.weekly_time
+                        else ''    
+                    end  as first_notifiation_date,
+
+                    case when ni.notifiation_type ='Milestone_Update' 
+                        then public.getPreviousMilestone(ni.serial_no,ni.milestone_code,ccc.transport_mode,ccc.order_from)
+                        else ''
+                    end as milestone_previous_json,
+
+                    case when ni.notifiation_type ='Container_Status_Update' 
+                        then public.getPreviousCtnrStatus(ni.serial_no,ni.ctnr,ni.ctnr_status_code)
+                        else ''
+                    end as ctnr_previous_json,
+
+                    case when ni.notifiation_type ='Container_Status_Update' 
+                        then (select description 
+                                    from public.ra_online_edi_event e where e.ra_name = ni.ctnr_status_code limit 1)
+                        else ''
+                    end as ctnr_desc,
+
+                    ccc.order_from,ccc.h_bol,ccc.transport_mode,ccc.m_bol
+                from public.kln_notifiation_info ni
+                        inner join LATERAL (select oo.h_bol,oo.transport_mode,oo.order_from,oo.m_bol 
+                            from public.kln_ocean oo 
+                            where oo.serial_no = ni.serial_no limit 1) ccc on true
+                        left join LATERAL (select COALESCE(ke.default_time_zone,'UTC-08') as default_time_zone from public.kln_user_extend ke where lower(ke.user_login) = lower(ni.user_login) limit 1) ddd on true
+                        left join LATERAL (select  case when COALESCE(ni.frequency_type,'') = 'Daily'
+							then to_char(timezone(ni.daily_time_zone, ni.insert_date),'Mon DD, YYYY')
+							when COALESCE(ni.frequency_type,'') = 'Weekly' 
+						then to_char(timezone(ni.weekly_time_zone,ni.insert_date)::date - (((EXTRACT(dow FROM timezone(ni.weekly_time_zone,ni.insert_date)::date)::integer - ni.weekly_week::integer + 7) % 7) ||' days')::INTERVAL,'Mon DD, YYYY')
+						|| ' - ' ||
+						to_char(timezone(ni.weekly_time_zone,ni.insert_date)::date + (((ni.weekly_week::integer - EXTRACT(dow FROM timezone(ni.weekly_time_zone,ni.insert_date)::date)::integer + 7) % 7)-1 + CASE WHEN EXTRACT(DOW FROM timezone(ni.weekly_time_zone,ni.insert_date)::date)::integer=ni.weekly_week::integer THEN 7 ELSE 0 END ||' days')::INTERVAL,'Mon DD, YYYY')
+                        else '' 
+                    end  as insert_date_format) eee on true      
+                where lower(ni.user_login) in ('".strtolower(_getLoginName())."','all_user')
+                        and ni.insert_date > NOW() - INTERVAL '1 year' 
+                        and lower(ni.notifiation_type) in ($more_param)
+                    ".$sql_where." and ni.notifications_method = true order by ni.insert_date desc) aa 
+                WHERE rn = 1 ORDER BY insert_date DESC";
+        error_log($sql);
+
+        $retData = common::excuteListSql($sql);
+        return $retData;
+    }
+
     public function getEventCard($mInfo){
         $eventCard = array();
         $notifiation_type = $mInfo['notifiation_type'];

+ 136 - 92
utils/common.class.php

@@ -2568,20 +2568,25 @@ class common {
     }
 
     public static function getUserCountry(){
-        return "deepseek";
-        // $contact_id = $_SESSION['ONLINE_USER']['contact_id'];
-        // if(empty($contact_id)){
-        //     return "claude";
-        // }
-
-        // $model = "deepseek";
-        // $data = common::excuteListSql("select LEFT(country, 2) as country from ocean.contacts where contact_id = '$contact_id'");
-        // foreach($data as $country){
-        //     if($country['country'] != "CN"){
-        //         $model = "claude";
-        //     }
-        // }
-        // return $model;
+        $contact_id = $_SESSION['ONLINE_USER']['contact_id'];
+        if(empty($contact_id)){
+            return "claude";
+        }
+
+        $model = "deepseek";
+        $data = common::excuteListSql("select LEFT(country, 2) as country from ocean.contacts where contact_id = '$contact_id'");
+        foreach($data as $country){
+            if($country['country'] != "CN"){
+                $model = "claude";
+            }
+        }
+        $data = common::excuteListSql("select LEFT(country, 2) as country from sfs.contacts where contact_id = '$contact_id'");
+        foreach($data as $country){
+            if($country['country'] != "CN"){
+                $model = "claude";
+            }
+        }
+        return $model;
     }
 
     public static function FixedAnswerAndLogData($fixedChat,$question_content){
@@ -2592,20 +2597,33 @@ class common {
 
             $reference = $fixedChat["answer_style"];
             $sql = $fixedChat["fixed_sql"];
-            if($_POST['is_demo'] == 't'){
+            //if($_POST['is_demo'] == 't'){
                 $sql = utils::getDmoeSqlForAi($fixedChat["fixed_faq"]);
-            }
+            //}
+
+            $sqlArr = explode(";", $sql);
+            $sqlCount = $sqlArr[0];
             //拼接用户权限
             $sqlWhere = '  ' . common::searchExtendHand_KLN("ocean", $_SESSION["ONLINE_USER"]);
-            $_sql = str_replace('<{ExtendHand_KLN}>', $sqlWhere, $sql);
-            error_log($_sql);
-            $data = common::excuteListSql($_sql);
+            $sqlCount = str_replace('<{ExtendHand_KLN}>', $sqlWhere, $sqlCount);
+            error_log("countSql:".$sqlCount);
+            $count = common::excuteOneSql($sqlCount);
+            //替换总数
+            $total = array("total" =>$count);
+            $reference = utils::replacementsFixed($total,$reference,array("total"));
+
+            $sqlDetail = $sqlArr[1];
+            //拼接用户权限
+            $sqlDetail = str_replace('<{ExtendHand_KLN}>', $sqlWhere, $sqlDetail);
+
+            error_log("detail:".$sqlDetail);
+            $data = common::excuteListSql($sqlDetail);
 
             if($fixedChat["fixed_faq"] == "Shipments arriving in the next 7 days."){   
                 foreach($data as $key =>$d){
                     $serial_no = common::deCode($d['serial_no'], 'E');
-                    $httpUrl = "tracking/detail?a=".$serial_no."&_schemas=".$d["order_from"];
-                    $data[$key]['action'] = "[Details](".$httpUrl.") [Notify](SystemSettings)";
+                    $httpUrl = SERVER_PAHT."tracking/detail?a=".$serial_no."&_schemas=".$d["order_from"];
+                    $data[$key]['action'] = '<a href="'.$httpUrl.'" target="_blank">Details</a> <a href="'.SERVER_PAHT.'SystemSettings" target="_blank">Notify</a>';
                 }
             }
             if($fixedChat["fixed_faq"] == "What is the current status of my active shipments?"){   
@@ -2621,17 +2639,14 @@ class common {
                     $temp['cargo_type'] = $d['cargo_type'];
 
                     $serial_no = common::deCode($d['serial_no'], 'E');
-                    $httpUrl = "tracking/detail?a=".$serial_no."&_schemas=".$d["order_from"];
-                    $temp['action'] = "[Details](".$httpUrl.") [Notify](SystemSettings)";
+                    $httpUrl = SERVER_PAHT."tracking/detail?a=".$serial_no."&_schemas=".$d["order_from"];
+                    $temp['action'] = '<a href="'.$httpUrl.'" target="_blank">Details</a> <a href="SystemSettings" target="_blank">Notify</a>';
                     $refer_data[] = $temp;
                 }
                 $data = $refer_data;
             }
-            $reference = utils::replacementsFixedMultilineForFixed($data,$fixedChat['answer_style'],$fixedChat['table_format_tr']);
-
-            //替换总数
-            $total = array("total" =>count($data));
-            $reference = utils::replacementsFixed($total,$reference,array("total"));
+            $reference = utils::replacementsFixedMultilineForFixed($data,$reference,$fixedChat['table_format_tr']);
+            //$data_download = "main_new_version.php?action=ocean_order&operate=download&temp=". common::deCode($fixedChat["fixed_faq"]);
         }
 
         if($fixedChat["fixed_faq"] == "List shipments with milestone updates in the last 7 days."){
@@ -2640,11 +2655,23 @@ class common {
             if($_POST['is_demo'] == 't'){
                 $sql = utils::getDmoeSqlForAi($fixedChat["fixed_faq"]);
             }
+
+            $sqlArr = explode(";", $sql);
+            $sqlCount = $sqlArr[0];
             //拼接用户权限
             $sqlWhere = '  ' . common::searchExtendHand_KLN("ocean", $_SESSION["ONLINE_USER"]);
-            $_sql = str_replace('<{ExtendHand_KLN}>', $sqlWhere, $sql);
-            error_log($_sql);
-            $data = common::excuteListSql($_sql);
+            $sqlCount = str_replace('<{ExtendHand_KLN}>', $sqlWhere, $sqlCount);
+            error_log("countSql:".$sqlCount);
+            $count = common::excuteOneSql($sqlCount);
+            //替换总数
+            $total = array("total" =>$count);
+            $reference = utils::replacementsFixed($total,$reference,array("total"));
+
+            $sqlDetail = $sqlArr[1];
+            //拼接用户权限
+            $sqlDetail = str_replace('<{ExtendHand_KLN}>', $sqlWhere, $sqlDetail);
+            error_log("detail:".$sqlDetail);
+            $data = common::excuteListSql($sqlDetail);
 
             $refer_data =array();    
             foreach($data as $key =>$d){
@@ -2655,24 +2682,25 @@ class common {
                 $temp['locations'] = $d['locations'];
 
                 $serial_no = common::deCode($d['serial_no'], 'E');
-                $httpUrl = "tracking/detail?a=".$serial_no."&_schemas=".$d["order_from"];
-                $temp['action'] = "[Details](".$httpUrl.") [Notify](SystemSettings)";
+                $httpUrl = SERVER_PAHT."tracking/detail?a=".$serial_no."&_schemas=".$d["order_from"];
+                $temp['action'] = '<a href="'.$httpUrl.'" target="_blank">Details</a> <a href="SystemSettings" target="_blank">Notify</a>';
                 $refer_data[] = $temp;
             }
-            $reference = utils::replacementsFixedMultilineForFixed($refer_data,$fixedChat['answer_style'],$fixedChat['table_format_tr']);
-
-            //替换总数
-            $total = array("total" =>count($data));
-            $reference = utils::replacementsFixed($total,$reference,array("total"));
+            $reference = utils::replacementsFixedMultilineForFixed($refer_data,$reference,$fixedChat['table_format_tr']);
 
             //Timeline View
-            $dateGroups = utils::uniqueGroupbyData("h_bol","update_date_format",$data);
+            $sqlview = $sqlArr[2];
+            //拼接用户权限
+            $sqlview = str_replace('<{ExtendHand_KLN}>', $sqlWhere, $sqlview);
+            error_log($sqlview);
+            $dateGroups = common::excuteListSql($sqlview);
+
             $timeline_view = "";
             if(!empty($dateGroups)){
                 $timeline_view = "## Timeline View: \n";
             }
-            foreach($dateGroups as $date => $count){
-                $timeline_view.="- ".$date.": ".$count." shipments reached milestones \n";
+            foreach($dateGroups as $key => $count){
+                $timeline_view.="- ".$count['update_date_format'].": ".$count['total_count']." shipments reached milestones \n";
             }
             $total = array("Timeline View" =>$timeline_view);
             $reference = utils::replacementsFixed($total,$reference,array("Timeline View"));
@@ -2754,6 +2782,12 @@ class common {
             //Container Information替换
             $reference = utils::replacementsFixed($data[0],$reference,$fileds);
 
+            $ref_data = array();
+            $serial_no = common::deCode($data[0]['serial_no'], 'E');
+            $httpUrl = SERVER_PAHT."tracking/detail?a=".$serial_no."&_schemas=".$data[0]['order_from'];
+            $ref_data['Track Shipment'] = '<a href="'.$httpUrl.'" target="_blank">Track Shipment</a>';
+            $reference = utils::replacementsFixed($ref_data,$reference,$fileds);
+
             //Complete Container Status
             //根据第一个sql 查出来的serial_no,和 container_no.
             $complete_container_status = "";
@@ -2775,12 +2809,22 @@ class common {
             if($_POST['is_demo'] == 't'){
                 $sql = utils::getDmoeSqlForAi($fixedChat["fixed_faq"]);
             }
+            $sqlArr = explode(";", $sql);
+            $sqlCount = $sqlArr[0];
             //拼接用户权限
             $sqlWhere = '  ' . common::searchExtendHand_KLN("ocean", $_SESSION["ONLINE_USER"]);
-            $_sql = str_replace('<{ExtendHand_KLN}>', $sqlWhere, $sql);
+            $sqlCount = str_replace('<{ExtendHand_KLN}>', $sqlWhere, $sqlCount);
+            error_log("countSql:".$sqlCount);
+            $count = common::excuteOneSql($sqlCount);
+            //替换总数
+            $total = array("total" =>$count);
+            $reference = utils::replacementsFixed($total,$reference,array("total"));
 
-            error_log($_sql);
-            $data = common::excuteListSql($_sql);
+            $sqlDetail = $sqlArr[1];
+            //拼接用户权限
+            $sqlDetail = str_replace('<{ExtendHand_KLN}>', $sqlWhere, $sqlDetail);
+            error_log("detail:".$sqlDetail);
+            $data = common::excuteListSql($sqlDetail);
 
             $refer_data =array();    
             foreach($data as $key =>$d){
@@ -2791,24 +2835,24 @@ class common {
                 $temp['uncity'] = $d['uncity'];
 
                 $serial_no = common::deCode($d['serial_no'], 'E');
-                $httpUrl = "tracking/detail?a=".$serial_no."&_schemas=".$d["order_from"];
-                $temp['action'] = "[Details](".$httpUrl.") [Notify](SystemSettings)";
+                $httpUrl = SERVER_PAHT."tracking/detail?a=".$serial_no."&_schemas=".$d["order_from"];
+                $temp['action'] = '<a href="'.$httpUrl.'" target="_blank">Details</a> <a href="SystemSettings" target="_blank">Notify</a>';
                 $refer_data[] = $temp;
             }
-            $reference = utils::replacementsFixedMultilineForFixed($refer_data,$fixedChat['answer_style'],$fixedChat['table_format_tr']);
-
-            //替换总数
-            $total = array("total" =>count($data));
-            $reference = utils::replacementsFixed($total,$reference,array("total"));
+            $reference = utils::replacementsFixedMultilineForFixed($refer_data,$reference,$fixedChat['table_format_tr']);
 
             //Timeline View
-            $dateGroups = utils::uniqueGroupbyData("container_no","_eventdate",$data);
+            $sqlview = $sqlArr[2];
+            //拼接用户权限
+            $sqlview = str_replace('<{ExtendHand_KLN}>', $sqlWhere, $sqlview);
+            error_log("viewSql:".$sqlview);
+            $dateGroups = common::excuteListSql($sqlview);
             $timeline_view = "";
             if(!empty($dateGroups)){
                 $timeline_view = "## Timeline View: \n";
             }
-            foreach($dateGroups as $date => $count){
-                $timeline_view.="- ".$date.": ".$count." containers have been updated \n";
+            foreach($dateGroups as $key => $count){
+                $timeline_view.="- ".$count['_eventdate'].": ".$count['total_count']." containers have been updated \n";
             }
             $total = array("Timeline View" =>$timeline_view);
             $reference = utils::replacementsFixed($total,$reference,array("Timeline View"));
@@ -2820,12 +2864,23 @@ class common {
             if($_POST['is_demo'] == 't'){
                 $sql = utils::getDmoeSqlForAi($fixedChat["fixed_faq"]);
             }
+
+            $sqlArr = explode(";", $sql);
+            $sqlCount = $sqlArr[0];
             //拼接用户权限
             $sqlWhere = '  ' . common::searchExtendHand_KLN("ocean", $_SESSION["ONLINE_USER"]);
-            $_sql = str_replace('<{ExtendHand_KLN}>', $sqlWhere, $sql);
-            error_log($_sql);
-            $data = common::excuteListSql($_sql);
+            $sqlCount = str_replace('<{ExtendHand_KLN}>', $sqlWhere, $sqlCount);
+            error_log("countSql:".$sqlCount);
+            $count = common::excuteOneSql($sqlCount);
+            //替换总数
+            $total = array("total" =>$count);
+            $reference = utils::replacementsFixed($total,$reference,array("total"));
 
+            $sqlDetail = $sqlArr[1];
+            //拼接用户权限
+            $sqlDetail = str_replace('<{ExtendHand_KLN}>', $sqlWhere, $sqlDetail);
+            error_log("detail:".$sqlDetail);
+            $data = common::excuteListSql($sqlDetail);
             $refer_data =array();    
             foreach($data as $key =>$d){
                 $temp = array();
@@ -2838,34 +2893,23 @@ class common {
                 $temp['locations'] = $d['locations'];
 
                 $serial_no = common::deCode($d['serial_no'], 'E');
-                $httpUrl = "tracking/detail?a=".$serial_no."&_schemas=".$d["order_from"];
-                $temp['action'] = "[Details](".$httpUrl.") [Notify](SystemSettings)";
+                $httpUrl = SERVER_PAHT."tracking/detail?a=".$serial_no."&_schemas=".$d["order_from"];
+                $temp['action'] = '<a href="'.$httpUrl.'" target="_blank">Details</a> <a href="SystemSettings" target="_blank">Notify</a>';
                 $refer_data[] = $temp;
             }
-            $reference = utils::replacementsFixedMultilineForFixed($refer_data,$fixedChat['answer_style'],$fixedChat['table_format_tr']);
-
-            //替换总数
+            $reference = utils::replacementsFixedMultilineForFixed($refer_data,$reference,$fixedChat['table_format_tr']);
+            //替换开头日期
             $d1_day = empty($data) ? "" : $data[0]['_update_date'];
-            $total = array("total" =>count($data),"date" => $d1_day);
-            $reference = utils::replacementsFixed($total,$reference,[]);
+            $dateData = array("date" => $d1_day);
+            $reference = utils::replacementsFixed($dateData,$reference,[]);
 
-            
-            $dep_total = 0;
-            $arr_total = 0;
-            $del_total = 0;
-            $dateGroups = utils::uniqueGroupbyData("","action_type",$data);
-            foreach($dateGroups as $action_type => $count){
-                if($action_type == "Departure"){
-                    $dep_total = $count;
-                }
-                if($action_type == "Arrived"){
-                    $arr_total = $count;
-                }
-                if($action_type == "Delivered"){
-                    $del_total = $count;
-                }
-            }
-            $total = array("dep" =>$dep_total,"arr" => $arr_total,"del" => $del_total);
+            //dep arr del total
+            $sqlview = $sqlArr[2];
+            //拼接用户权限
+            $sqlview = str_replace('<{ExtendHand_KLN}>', $sqlWhere, $sqlview);
+            error_log("depArrDelSql:".$sqlview);
+            $dateGroups = common::excuteListSql($sqlview);
+            $total = array("dep" =>$dateGroups[0]['dep'],"arr" => $dateGroups[0]['arr'],"del" => $dateGroups[0]['del']);
             $reference = utils::replacementsFixed($total,$reference,[]);
         }
 
@@ -2875,7 +2919,6 @@ class common {
             if($_POST['is_demo'] == 't'){
                  $question_content = 'DRYU9375994';
             }
-
             $sqlArr = explode(";", $sql);
             $sqlOne = $sqlArr[0];
             //拼接用户权限
@@ -2887,19 +2930,18 @@ class common {
             if(empty($data)){
                 return "No valid Container/BOL number detected. Please try clicking on other FAQ questions or input your own question. Thank you.";
             }
-
             //如果数据为空,用这个fileds配置的 把模板里值逐个替换为空
-            $fileds = array("tracking_no","h_bol","question_content","transport_mode","place_of_receipt_exp","place_of_delivery_exp","carrier","vessel","voyage","link");
+            $fileds = array("tracking_no","h_bol","question_content","transport_mode","place_of_receipt_exp","place_of_delivery_exp","carrier","vessel","voyage","Shipment Detail Page Link");
             $ref_data = array();
             if(!empty($data)){
                 $ref_data = $data[0];
                 $ref_data['question_content'] = $question_content;
 
                 $serial_no = common::deCode($data[0]['serial_no'], 'E');
-                $httpUrl = "tracking/detail?a=".$serial_no."&_schemas=".$data[0]['order_from'];
-                $ref_data['link'] = $httpUrl;
+                $httpUrl = SERVER_PAHT."tracking/detail?a=".$serial_no."&_schemas=".$data[0]['order_from'];
+                $ref_data['Shipment Detail Page Link'] = '<a href="'.$httpUrl.'" target="_blank">Shipment Detail Page Link</a>';
             }
-            $reference = utils::replacementsFixed($data[0],$reference,$fileds);
+            $reference = utils::replacementsFixed($ref_data,$reference,$fileds);
         }
 
         if($fixedChat["fixed_faq"] == "Sort my active shipments by earliest arrival date."){
@@ -2908,11 +2950,13 @@ class common {
             if($_POST['is_demo'] == 't'){
                 $sql = utils::getDmoeSqlForAi($fixedChat["fixed_faq"]);
             }
+
+            $sqlDetail = $sql;
             //拼接用户权限
             $sqlWhere = '  ' . common::searchExtendHand_KLN("ocean", $_SESSION["ONLINE_USER"]);
-            $_sql = str_replace('<{ExtendHand_KLN}>', $sqlWhere, $sql);
-            error_log($_sql);
-            $data = common::excuteListSql($_sql);
+            $sqlDetail = str_replace('<{ExtendHand_KLN}>', $sqlWhere, $sqlDetail);
+            error_log("detail:".$sqlDetail);
+            $data = common::excuteListSql($sqlDetail);
 
             $refer_data =array();    
             foreach($data as $key =>$d){
@@ -2925,8 +2969,8 @@ class common {
                 $temp['day_to_arr'] = $d['day_to_arr'];
 
                 $serial_no = common::deCode($d['serial_no'], 'E');
-                $httpUrl = "tracking/detail?a=".$serial_no."&_schemas=".$d["order_from"];
-                $temp['action'] = "[Details](".$httpUrl.") [Notify](SystemSettings)";
+                $httpUrl = SERVER_PAHT."tracking/detail?a=".$serial_no."&_schemas=".$d["order_from"];
+                $temp['action'] = '<a href="'.$httpUrl.'" target="_blank">Details</a> <a href="SystemSettings" target="_blank">Notify</a>';
                 $refer_data[] = $temp;
             }
             $data = $refer_data;

+ 344 - 178
utils/utils.class.php

@@ -1098,7 +1098,22 @@ class utils {
 
     public static function getDmoeSqlForAi($type){
         $data= array();
-        $data["Shipments arriving in the next 7 days."] =  "select serial_no,h_bol,place_of_receipt_exp,place_of_delivery_exp,description,eta,order_from,cargo_type 
+        $data["Shipments arriving in the next 7 days."] =  "select count(*)
+         from (
+         	SELECT  oo.serial_no,h_bol, place_of_receipt_exp, place_of_delivery_exp,m.description,eta,order_from, o.cargo_type
+         		FROM public.kln_ocean oo
+                inner join LATERAL (select case when is_hazardous = 't' then 'Dangerous Goods'::text else 'General'::text end as cargo_type 
+			        from public.ocean o where o.serial_no = oo.serial_no) o on true
+         		left join LATERAL (select a.code,a.description 
+         				from public.ocean_milestone a 
+         					inner join public.customer_service_milestone_sno s 
+         				on a.code = s.code 
+         					and s.type = 'sea' 
+         					and a.serial_no = oo.serial_no
+         					and a.act_date is not null 
+         				order by s.sno desc limit 1) m on true
+         	WHERE  <{ExtendHand_KLN}> and  oo.transport_mode = 'sea' and order_from = 'public' and m.code <> ''  limit 20
+         ) t;select serial_no,h_bol,place_of_receipt_exp,place_of_delivery_exp,description,eta,order_from,cargo_type 
          from (
          	SELECT  oo.serial_no,h_bol, place_of_receipt_exp, place_of_delivery_exp,m.description,eta,order_from, o.cargo_type
          		FROM public.kln_ocean oo
@@ -1115,187 +1130,338 @@ class utils {
          	WHERE  <{ExtendHand_KLN}> and  oo.transport_mode = 'sea' and order_from = 'public' and m.code <> ''  limit 20
          ) t order by eta";
 
-        $data["List shipments with milestone updates in the last 7 days."] =  "select serial_no,order_from,h_bol, description,update_date_format,update_date,timezone,locations
+        $data["List shipments with milestone updates in the last 7 days."] =  "select count(*)
+                            from (
+                            select serial_no,order_from,h_bol,description,to_char(update_date,'Mon DD') as update_date_format,update_date,
+                                    COALESCE(jsonb_data->>'milestone','')::jsonb->>'timezone' as timezone,
+                                    COALESCE(jsonb_data->>'milestone','')::jsonb->>'locations' as locations 
+                            from (SELECT oo.serial_no,oo.order_from,oo.h_bol,s.description,a.update_date,public.getTimeAndLocationForKln(oo.serial_no,a.code,''::text)::jsonb as jsonb_data
+                                    from public.ocean_milestone a 
+                                        inner join public.customer_service_milestone_sno s on a.code = s.code 
+                                        inner join public.kln_ocean oo on oo.serial_no = a.serial_no 
+                                    where s.type = 'sea' 
+                                        and a.act_date is not null
+                                        and a.update_date is not null
+                                        and a.update_date > '2025-04-06') po
+                            )t;select serial_no,order_from,h_bol, description,update_date_format,update_date,timezone,locations
+                            from (
+                            select serial_no,order_from,h_bol,description,to_char(update_date,'Mon DD') as update_date_format,update_date,
+                                    COALESCE(jsonb_data->>'milestone','')::jsonb->>'timezone' as timezone,
+                                    COALESCE(jsonb_data->>'milestone','')::jsonb->>'locations' as locations 
+                            from (SELECT oo.serial_no,oo.order_from,oo.h_bol,s.description,a.update_date,public.getTimeAndLocationForKln(oo.serial_no,a.code,''::text)::jsonb as jsonb_data
+                                    from public.ocean_milestone a 
+                                        inner join public.customer_service_milestone_sno s on a.code = s.code 
+                                        inner join public.kln_ocean oo on oo.serial_no = a.serial_no 
+                                    where s.type = 'sea' 
+                                        and a.act_date is not null
+                                        and a.update_date is not null
+                                        and a.update_date > '2025-04-06') po
+                            )t;select aa.update_date_format, COUNT(*) AS total_count
+                from (
+                    select DISTINCT ON (h_bol) h_bol, update_date_format
+                    from (
+                    select serial_no,order_from,h_bol,description,to_char(update_date,'Mon DD') as update_date_format,update_date
+                    from (SELECT oo.serial_no,oo.order_from,oo.h_bol,s.description,a.update_date
+                            from public.ocean_milestone a 
+                                inner join public.customer_service_milestone_sno s on a.code = s.code 
+                                inner join public.kln_ocean oo on oo.serial_no = a.serial_no 
+                            where s.type = 'sea' 
+                                and a.act_date is not null
+                                and a.update_date > '2025-04-06') po
+
+                    )t order by h_bol
+                ) aa
+                group by aa.update_date_format order by aa.update_date_format ";
+        $data["What is the current status of my active shipments?"] ="SELECT count(*)
+                FROM public.kln_ocean oo
+                WHERE  <{ExtendHand_KLN}> and ((oo.ata is not null and oo.ata >= CURRENT_DATE - INTERVAL '3 months' AND oo.ata < CURRENT_DATE) 
+                    or not exists( select 1 from public.ocean_milestone a where a.serial_no = oo.serial_no and a.act_date is not null  and a.code = 'IFFDEL')) and oo.transport_mode = 'sea' and order_from = 'public';with oo as(
+                SELECT h_bol, place_of_receipt_exp, place_of_delivery_exp,serial_no,transport_mode,order_from
+                FROM public.kln_ocean oo
+                WHERE  <{ExtendHand_KLN}> and ((oo.ata is not null and oo.ata >= CURRENT_DATE - INTERVAL '3 months' AND oo.ata < CURRENT_DATE) 
+                    or not exists( select 1 from public.ocean_milestone a where a.serial_no = oo.serial_no and a.act_date is not null  and a.code = 'IFFDEL')) and oo.transport_mode = 'sea' and order_from = 'public' order by id limit 10 
+            )
+
+            SELECT oo.*,mil.description,mil.act_date,mil.act_time,o.cargo_type,
+                COALESCE(jsonb_data->>'milestone','')::jsonb->>'timezone' as timezone,
+                COALESCE(jsonb_data->>'milestone','')::jsonb->>'locations' as locations
+            from oo  
+            inner join LATERAL (select case when is_hazardous = 't' then 'Dangerous Goods'::text else 'General'::text end as cargo_type 
+                from public.ocean o where o.serial_no = oo.serial_no) o on true
+            left join LATERAL (select a.code,s.description,to_char(a.act_date, 'YYYY-MM-DD') as act_date ,a.act_time
+                from public.ocean_milestone a 
+                    left join public.customer_service_milestone_sno s on a.code = s.code 
+                    and s.type = 'sea' 
+                    and a.serial_no = oo.serial_no
+                    and a.act_date is not null 
+                    order by s.sno desc limit 1) mil on true
+            left join LATERAL (select public.getTimeAndLocationForKln(oo.serial_no,mil.code,''::text)::jsonb as jsonb_data) lt on true
+            where oo.transport_mode = 'sea' and order_from = 'public'
+            union all 
+            SELECT oo.*,mil.description,mil.act_date,mil.act_time,o.cargo_type,
+                COALESCE(jsonb_data->>'milestone','')::jsonb->>'timezone' as timezone,
+                COALESCE(jsonb_data->>'milestone','')::jsonb->>'locations' as locations
+            from oo  
+            inner join LATERAL (select case when is_hazardous = 't' then 'Dangerous Goods'::text else 'General'::text end as cargo_type 
+                from sfs.ocean o where o.serial_no = oo.serial_no) o on true
+            left join LATERAL (select a.code,s.description,to_char(a.act_date, 'YYYY-MM-DD') as act_date ,a.act_time
+                from public.ocean_milestone a 
+                    left join public.customer_service_milestone_sno s on a.code = s.code 
+                    and s.type = 'air' 
+                    and a.serial_no = oo.serial_no
+                    and a.act_date is not null 
+                    order by s.sno desc limit 1) mil on true
+                left join LATERAL (select public.getTimeAndLocationForKln(oo.serial_no,mil.code,''::text)::jsonb as jsonb_data) lt on true		
+            where oo.transport_mode = 'sea' and order_from = 'sfs'
+            union all 
+            SELECT oo.*,mil.description,mil.act_date,mil.act_time,o.cargo_type,
+                COALESCE(jsonb_data->>'milestone','')::jsonb->>'timezone' as timezone,
+                COALESCE(jsonb_data->>'milestone','')::jsonb->>'locations' as locations
+            from oo  
+            inner join LATERAL (select case when is_hazardous = 't' then 'Dangerous Goods'::text else 'General'::text end as cargo_type 
+                from public.ocean o where o.serial_no = oo.serial_no) o on true
+            left join LATERAL (select a.code,s.description,to_char(a.act_date, 'YYYY-MM-DD') as act_date ,a.act_time
+                from public.air_milestone a 
+                    left join public.customer_service_milestone_sno s on a.code = s.code 
+                    and s.type = 'air' 
+                    and a.serial_no = oo.serial_no
+                    and a.act_date is not null 
+                    order by s.sno desc limit 1) mil on true
+            left join LATERAL (select public.getTimeAndLocationForKln(oo.serial_no,mil.code,''::text)::jsonb as jsonb_data) lt on true		
+            where oo.transport_mode = 'air' and order_from = 'public'
+            union all 
+            SELECT oo.*,mil.description,mil.act_date,mil.act_time,o.cargo_type,
+                COALESCE(jsonb_data->>'milestone','')::jsonb->>'timezone' as timezone,
+                COALESCE(jsonb_data->>'milestone','')::jsonb->>'locations' as locations
+            from oo  
+            inner join LATERAL (select case when is_hazardous = 't' then 'Dangerous Goods'::text else 'General'::text end as cargo_type 
+                from sfs.ocean o where o.serial_no = oo.serial_no) o on true
+            left join LATERAL (select a.code,s.description,to_char(a.act_date, 'YYYY-MM-DD') as act_date ,a.act_time
+                from sfs.air_milestone a 
+                    left join public.customer_service_milestone_sno s on a.code = s.code 
+                    and s.type = 'air' 
+                    and a.serial_no = oo.serial_no
+                    and a.act_date is not null 
+                    order by s.sno desc limit 1) mil on true
+            left join LATERAL (select public.getTimeAndLocationForKln(oo.serial_no,mil.code,''::text)::jsonb as jsonb_data) lt on true		
+            where oo.transport_mode = 'air' and order_from = 'sfs'";
+
+        $data["List shipments with container status updates in the last 7 days."] = "select count(*)			
+            FROM ra_online_container_status s
+                LEFT JOIN oc_container oc ON s.status_id = oc.status_id
+                LEFT JOIN ocean o ON o.serial_no::text = oc.serial_no::text
+                LEFT JOIN public.ra_online_edi_event e on s.event_base = e.ra_name 
+            WHERE o.status::text <> 'Cancelled'::text 
+                and is_display = true  
+                and s.insert_date <= CURRENT_DATE AND s.insert_date >='2023-02-23'
+                and exists(select 1 from kln_ocean oo where <{ExtendHand_KLN}> and oo.serial_no = o.serial_no);select oo.serial_no,oo.order_from,s.event_base as event,s.container_no,
+                to_char(to_timestamp(s.event_date, 'YYYYMMDD'), 'YYYY-MM-DD') as eventdate,
+                to_char(to_timestamp(s.event_date, 'YYYYMMDD'),'Mon DD') as _eventdate,
+                to_char(to_timestamp(s.event_time, 'HH24MI'), 'HH24:MI') as eventtime,
+                (select time_zone from public.city_timezone where uncode = s.event_code) as timezone,
+                e.description,
+                s.event_city as uncity				
+            FROM ra_online_container_status s
+                LEFT JOIN oc_container oc ON s.status_id = oc.status_id
+                LEFT JOIN ocean o ON o.serial_no::text = oc.serial_no::text
+                LEFT JOIN public.ra_online_edi_event e on s.event_base = e.ra_name 
+                LEFT JOIN public.kln_ocean oo ON oo.serial_no::text = o.serial_no::text
+            WHERE o.status::text <> 'Cancelled'::text 
+                and is_display = true  
+                and s.insert_date <= CURRENT_DATE AND s.insert_date >='2023-02-23'
+                and exists(select 1 from kln_ocean oo where  <{ExtendHand_KLN}> and oo.serial_no = o.serial_no) limit 10;select aa._eventdate, COUNT(*) AS total_count
+            from (select DISTINCT ON (s.container_no) s.container_no,
+                to_char(to_timestamp(s.event_date, 'YYYYMMDD'),'Mon DD') as _eventdate
+                FROM ra_online_container_status s
+                    LEFT JOIN oc_container oc ON s.status_id = oc.status_id
+                    LEFT JOIN ocean o ON o.serial_no::text = oc.serial_no::text
+                    LEFT JOIN public.ra_online_edi_event e on s.event_base = e.ra_name 
+                WHERE o.status::text <> 'Cancelled'::text 
+                    and is_display = true  
+                    and s.insert_date <= CURRENT_DATE AND s.insert_date >='2023-02-23'
+                    and exists(select 1 from kln_ocean oo where <{ExtendHand_KLN}> and oo.serial_no = o.serial_no)
+                    order by s.container_no
+            )aa group by _eventdate order by _eventdate";
+
+        $data["Today's shipments summary."] = "select count(*)
+            from (
+            select oo.serial_no
+                from (
+                    select t.serial_no from (
+                        SELECT 
+                            a.serial_no,
+                            ROW_NUMBER() OVER(PARTITION BY a.serial_no ORDER BY s.sno DESC) AS rn
+                            from public.ocean_milestone a 
+                                left join public.customer_service_milestone_sno s on a.code = s.code 
+                            where s.type = 'sea' 
+                                and a.act_date is not null
+                                --and a.update_date >= CURRENT_DATE AND a.update_date < CURRENT_DATE + INTERVAL '1 day'
+                        )t WHERE rn = 1
+                    ) po inner join public.kln_ocean oo on oo.serial_no = po.serial_no and (<{ExtendHand_KLN}>) 
+            union all 
+            select oo.serial_no
+                from (
+                    select t.serial_no from (
+                        SELECT 
+                            a.code,a.serial_no,
+                            ROW_NUMBER() OVER(PARTITION BY a.serial_no ORDER BY s.sno DESC) AS rn
+                            from public.air_milestone a 
+                                left join public.customer_service_milestone_sno s on a.code = s.code 
+                            where s.type = 'sea' 
+                                and a.act_date is not null
+                                --and a.update_date >= CURRENT_DATE AND a.update_date < CURRENT_DATE + INTERVAL '1 day'
+                        )t WHERE rn = 1
+                    ) pa inner join public.kln_ocean oo on oo.serial_no = pa.serial_no and (<{ExtendHand_KLN}>)
+            union all 
+            select oo.serial_no
+                from (
+                    select t.serial_no from (
+                        SELECT 
+                            a.serial_no,
+                            ROW_NUMBER() OVER(PARTITION BY a.serial_no ORDER BY s.sno DESC) AS rn
+                            from sfs.air_milestone a 
+                                left join public.customer_service_milestone_sno s on a.code = s.code 
+                            where s.type = 'sea' 
+                                and a.act_date is not null
+                                --and a.update_date >= CURRENT_DATE AND a.update_date < CURRENT_DATE + INTERVAL '1 day'
+                        )t WHERE rn = 1
+                    ) sa inner join public.kln_ocean oo on oo.serial_no = sa.serial_no and (<{ExtendHand_KLN}>)
+            )t;select *
             from (
-            select serial_no,order_from,h_bol,description,to_char(update_date,'Mon DD') as update_date_format,update_date,
+            select *,oo.serial_no,oo.order_from,oo.h_bol,oo.transport_mode,oo.place_of_receipt_exp, oo.place_of_delivery_exp,
+                    COALESCE(jsonb_data->>'milestone','')::jsonb->>'timezone' as timezone,
+                    COALESCE(jsonb_data->>'milestone','')::jsonb->>'locations' as locations 
+                from (
+                    select *,public.getTimeAndLocationForKln(t.serial_no,t.code,''::text)::jsonb as jsonb_data 
+                        from (
+                        SELECT 
+                            case when a.code = 'IFFDEP' then 'Departure'
+                                when a.code = 'IFFARR' then 'Arrived'
+                                when a.code = 'IFFDEL' then 'Delivered'
+                            else  s.description end as action_type, 	
+                            a.update_date,a.code,a.serial_no,
+                        to_char(a.update_date, 'Mon_DD_YYYY') as _update_date,
+                            to_char(a.act_date, 'YYYY-MM-DD') as act_date ,
+                            a.act_time,
+                            ROW_NUMBER() OVER(PARTITION BY a.serial_no ORDER BY s.sno DESC) AS rn
+                            from public.ocean_milestone a 
+                                left join public.customer_service_milestone_sno s on a.code = s.code 
+                            where s.type = 'sea' 
+                                and a.act_date is not null
+                                --and a.update_date >= CURRENT_DATE AND a.update_date < CURRENT_DATE + INTERVAL '1 day'
+                        )t WHERE rn = 1
+                    ) po inner join public.kln_ocean oo on oo.serial_no = po.serial_no 
+            union all 
+            select *,oo.serial_no,oo.order_from,oo.h_bol,oo.transport_mode,oo.place_of_receipt_exp, oo.place_of_delivery_exp,
+                    COALESCE(jsonb_data->>'milestone','')::jsonb->>'timezone' as timezone,
+                    COALESCE(jsonb_data->>'milestone','')::jsonb->>'locations' as locations 
+                from (
+                    select *,public.getTimeAndLocationForKln(t.serial_no,t.code,''::text)::jsonb as jsonb_data 
+                        from (
+                        SELECT 
+                            case when a.code = 'IFFDEP' then 'Departure'
+                                when a.code = 'IFFARR' then 'Arrived'
+                                when a.code = 'IFFDEL' then 'Delivered'
+                            else  s.description end as action_type, 	
+                            a.update_date,a.code,a.serial_no,
+                            to_char(a.update_date, 'Mon_DD_YYYY') as _update_date,
+                            to_char(a.act_date, 'YYYY-MM-DD') as act_date ,
+                            a.act_time,
+                            ROW_NUMBER() OVER(PARTITION BY a.serial_no ORDER BY s.sno DESC) AS rn
+                            from public.air_milestone a 
+                                left join public.customer_service_milestone_sno s on a.code = s.code 
+                            where s.type = 'sea' 
+                                and a.act_date is not null
+                                --and a.update_date >= CURRENT_DATE AND a.update_date < CURRENT_DATE + INTERVAL '1 day'
+                        )t WHERE rn = 1
+                    ) pa inner join public.kln_ocean oo on oo.serial_no = pa.serial_no 
+            union all 
+            select *,oo.serial_no,oo.order_from,oo.h_bol,oo.transport_mode,oo.place_of_receipt_exp, oo.place_of_delivery_exp,
                     COALESCE(jsonb_data->>'milestone','')::jsonb->>'timezone' as timezone,
                     COALESCE(jsonb_data->>'milestone','')::jsonb->>'locations' as locations 
-            from (SELECT oo.serial_no,oo.order_from,oo.h_bol,s.description,a.update_date,public.getTimeAndLocationForKln(oo.serial_no,a.code,''::text)::jsonb as jsonb_data
-                    from public.ocean_milestone a 
-                        inner join public.customer_service_milestone_sno s on a.code = s.code 
-                        inner join public.kln_ocean oo on oo.serial_no = a.serial_no and (<{ExtendHand_KLN}>)
-                    where s.type = 'sea' 
-                        and a.act_date is not null
-                        and a.update_date is not null
-                        and a.update_date > '2025-04-06') po
+                from (
+                    select *,public.getTimeAndLocationForKln(t.serial_no,t.code,''::text)::jsonb as jsonb_data 
+                        from (
+                        SELECT 
+                            case when a.code = 'IFFDEP' then 'Departure'
+                                when a.code = 'IFFARR' then 'Arrived'
+                                when a.code = 'IFFDEL' then 'Delivered'
+                            else  s.description end as action_type, 	
+                            a.update_date,a.code,a.serial_no,
+                            to_char(a.update_date, 'Mon_DD_YYYY') as _update_date,
+                            to_char(a.act_date, 'YYYY-MM-DD') as act_date ,
+                            a.act_time,
+                            ROW_NUMBER() OVER(PARTITION BY a.serial_no ORDER BY s.sno DESC) AS rn
+                            from sfs.air_milestone a 
+                                left join public.customer_service_milestone_sno s on a.code = s.code 
+                            where s.type = 'sea' 
+                                and a.act_date is not null
+                                --and a.update_date >= CURRENT_DATE AND a.update_date < CURRENT_DATE + INTERVAL '1 day'
+                        )t WHERE rn = 1
+                    ) sa inner join public.kln_ocean oo on oo.serial_no = sa.serial_no 
+            )t  limit 10;select sum(case when (action_type='Departure') then 1 else 0 end) as dep, 
+                sum(case when (action_type='Arrived') then 1 else 0 end) as arr,
+                sum(case when (action_type='Delivered') then 1 else 0 end) as del
+            from (
+            select action_type
+                from (
+                    select t.action_type,t.serial_no from (
+                        SELECT 
+                            case when a.code = 'IFFDEP' then 'Departure'
+                                when a.code = 'IFFARR' then 'Arrived'
+                                when a.code = 'IFFDEL' then 'Delivered'
+                            else  s.description end as action_type,
+                            a.serial_no,				
+                            ROW_NUMBER() OVER(PARTITION BY a.serial_no ORDER BY s.sno DESC) AS rn
+                            from public.ocean_milestone a 
+                                left join public.customer_service_milestone_sno s on a.code = s.code 
+                            where s.type = 'sea' 
+                                and a.act_date is not null
+                                --and a.update_date >= CURRENT_DATE AND a.update_date < CURRENT_DATE + INTERVAL '1 day'
+                        )t WHERE rn = 1
+                    ) po inner join public.kln_ocean oo on oo.serial_no = po.serial_no and (<{ExtendHand_KLN}>) 
+            union all 
+            select action_type
+                from (
+                    select t.action_type,t.serial_no from (
+                        SELECT 
+                            case when a.code = 'IFFDEP' then 'Departure'
+                                when a.code = 'IFFARR' then 'Arrived'
+                                when a.code = 'IFFDEL' then 'Delivered'
+                            else  s.description end as action_type,
+                            a.serial_no,
+                            ROW_NUMBER() OVER(PARTITION BY a.serial_no ORDER BY s.sno DESC) AS rn
+                            from public.air_milestone a 
+                                left join public.customer_service_milestone_sno s on a.code = s.code 
+                            where s.type = 'sea' 
+                                and a.act_date is not null
+                                --and a.update_date >= CURRENT_DATE AND a.update_date < CURRENT_DATE + INTERVAL '1 day'
+                        )t WHERE rn = 1
+                    ) pa inner join public.kln_ocean oo on oo.serial_no = pa.serial_no and (<{ExtendHand_KLN}>)
+            union all 
+            select action_type
+                from (
+                    select t.action_type,t.serial_no from (
+                        SELECT 
+                            case when a.code = 'IFFDEP' then 'Departure'
+                                when a.code = 'IFFARR' then 'Arrived'
+                                when a.code = 'IFFDEL' then 'Delivered'
+                            else  s.description end as action_type,
+                            a.serial_no,
+                            ROW_NUMBER() OVER(PARTITION BY a.serial_no ORDER BY s.sno DESC) AS rn
+                            from sfs.air_milestone a 
+                                left join public.customer_service_milestone_sno s on a.code = s.code 
+                            where s.type = 'sea' 
+                                and a.act_date is not null
+                                --and a.update_date >= CURRENT_DATE AND a.update_date < CURRENT_DATE + INTERVAL '1 day'
+                        )t WHERE rn = 1
+                    ) sa inner join public.kln_ocean oo on oo.serial_no = sa.serial_no and (<{ExtendHand_KLN}>)
             )t";
-        $data["What is the current status of my active shipments?"] ="with oo as(
-            SELECT h_bol, place_of_receipt_exp, place_of_delivery_exp,serial_no,transport_mode,order_from
-            FROM public.kln_ocean oo
-            WHERE  <{ExtendHand_KLN}> and ((oo.ata is not null and oo.ata >= CURRENT_DATE - INTERVAL '3 months' AND oo.ata < CURRENT_DATE) 
-                or not exists( select 1 from public.ocean_milestone a where a.serial_no = oo.serial_no and a.act_date is not null  and a.code = 'IFFDEL')) and oo.transport_mode = 'sea' and order_from = 'public' order by id limit 10 
-        )
-
-        SELECT oo.*,mil.description,mil.act_date,mil.act_time,o.cargo_type,
-            COALESCE(jsonb_data->>'milestone','')::jsonb->>'timezone' as timezone,
-            COALESCE(jsonb_data->>'milestone','')::jsonb->>'locations' as locations
-        from oo  
-        inner join LATERAL (select case when is_hazardous = 't' then 'Dangerous Goods'::text else 'General'::text end as cargo_type 
-            from public.ocean o where o.serial_no = oo.serial_no) o on true
-        left join LATERAL (select a.code,s.description,to_char(a.act_date, 'YYYY-MM-DD') as act_date ,a.act_time
-            from public.ocean_milestone a 
-                left join public.customer_service_milestone_sno s on a.code = s.code 
-                and s.type = 'sea' 
-                and a.serial_no = oo.serial_no
-                and a.act_date is not null 
-                order by s.sno desc limit 1) mil on true
-        left join LATERAL (select public.getTimeAndLocationForKln(oo.serial_no,mil.code,''::text)::jsonb as jsonb_data) lt on true
-        where oo.transport_mode = 'sea' and order_from = 'public'
-        union all 
-        SELECT oo.*,mil.description,mil.act_date,mil.act_time,o.cargo_type,
-            COALESCE(jsonb_data->>'milestone','')::jsonb->>'timezone' as timezone,
-            COALESCE(jsonb_data->>'milestone','')::jsonb->>'locations' as locations
-        from oo  
-        inner join LATERAL (select case when is_hazardous = 't' then 'Dangerous Goods'::text else 'General'::text end as cargo_type 
-            from sfs.ocean o where o.serial_no = oo.serial_no) o on true
-        left join LATERAL (select a.code,s.description,to_char(a.act_date, 'YYYY-MM-DD') as act_date ,a.act_time
-            from public.ocean_milestone a 
-                left join public.customer_service_milestone_sno s on a.code = s.code 
-                and s.type = 'air' 
-                and a.serial_no = oo.serial_no
-                and a.act_date is not null 
-                order by s.sno desc limit 1) mil on true
-            left join LATERAL (select public.getTimeAndLocationForKln(oo.serial_no,mil.code,''::text)::jsonb as jsonb_data) lt on true		
-        where oo.transport_mode = 'sea' and order_from = 'sfs'
-        union all 
-        SELECT oo.*,mil.description,mil.act_date,mil.act_time,o.cargo_type,
-            COALESCE(jsonb_data->>'milestone','')::jsonb->>'timezone' as timezone,
-            COALESCE(jsonb_data->>'milestone','')::jsonb->>'locations' as locations
-        from oo  
-        inner join LATERAL (select case when is_hazardous = 't' then 'Dangerous Goods'::text else 'General'::text end as cargo_type 
-            from public.ocean o where o.serial_no = oo.serial_no) o on true
-        left join LATERAL (select a.code,s.description,to_char(a.act_date, 'YYYY-MM-DD') as act_date ,a.act_time
-            from public.air_milestone a 
-                left join public.customer_service_milestone_sno s on a.code = s.code 
-                and s.type = 'air' 
-                and a.serial_no = oo.serial_no
-                and a.act_date is not null 
-                order by s.sno desc limit 1) mil on true
-        left join LATERAL (select public.getTimeAndLocationForKln(oo.serial_no,mil.code,''::text)::jsonb as jsonb_data) lt on true		
-        where oo.transport_mode = 'air' and order_from = 'public'
-        union all 
-        SELECT oo.*,mil.description,mil.act_date,mil.act_time,o.cargo_type,
-            COALESCE(jsonb_data->>'milestone','')::jsonb->>'timezone' as timezone,
-            COALESCE(jsonb_data->>'milestone','')::jsonb->>'locations' as locations
-        from oo  
-        inner join LATERAL (select case when is_hazardous = 't' then 'Dangerous Goods'::text else 'General'::text end as cargo_type 
-            from sfs.ocean o where o.serial_no = oo.serial_no) o on true
-        left join LATERAL (select a.code,s.description,to_char(a.act_date, 'YYYY-MM-DD') as act_date ,a.act_time
-            from sfs.air_milestone a 
-                left join public.customer_service_milestone_sno s on a.code = s.code 
-                and s.type = 'air' 
-                and a.serial_no = oo.serial_no
-                and a.act_date is not null 
-                order by s.sno desc limit 1) mil on true
-        left join LATERAL (select public.getTimeAndLocationForKln(oo.serial_no,mil.code,''::text)::jsonb as jsonb_data) lt on true		
-        where oo.transport_mode = 'air' and order_from = 'sfs'";
-
-        $data["List shipments with container status updates in the last 7 days."] = "select oo.serial_no,oo.order_from,s.event_base as event,s.container_no,
-    to_char(to_timestamp(s.event_date, 'YYYYMMDD'), 'YYYY-MM-DD') as eventdate,
-    to_char(to_timestamp(s.event_date, 'YYYYMMDD'),'Mon DD') as _eventdate,
-    to_char(to_timestamp(s.event_time, 'HH24MI'), 'HH24:MI') as eventtime,
-	(select time_zone from public.city_timezone where uncode = s.event_code) as timezone,
-    e.description,
-    s.event_city as uncity				
-   FROM ra_online_container_status s
-    LEFT JOIN oc_container oc ON s.status_id = oc.status_id
-    LEFT JOIN ocean o ON o.serial_no::text = oc.serial_no::text
-	 LEFT JOIN public.ra_online_edi_event e on s.event_base = e.ra_name 
-     LEFT JOIN public.kln_ocean oo ON oo.serial_no::text = o.serial_no::text
-  WHERE o.status::text <> 'Cancelled'::text 
-	and is_display = true  
-	--and s.insert_date BETWEEN CURRENT_DATE AND (CURRENT_DATE + INTERVAL '7 days')
-	and exists(select 1 from kln_ocean oo where  <{ExtendHand_KLN}> and oo.serial_no = o.serial_no) limit 10";
-
-    $data["Today's shipments summary."] = "select *
-from (
-select *,oo.serial_no,oo.order_from,oo.h_bol,oo.transport_mode,oo.place_of_receipt_exp, oo.place_of_delivery_exp,
-		COALESCE(jsonb_data->>'milestone','')::jsonb->>'timezone' as timezone,
-		COALESCE(jsonb_data->>'milestone','')::jsonb->>'locations' as locations 
-	from (
-		select *,public.getTimeAndLocationForKln(t.serial_no,t.code,''::text)::jsonb as jsonb_data 
-			from (
-			SELECT 
-				case when a.code = 'IFFDEP' then 'Departure'
-					when a.code = 'IFFARR' then 'Arrived'
-					when a.code = 'IFFDEL' then 'Delivered'
-				else  s.description end as action_type, 	
-				a.update_date,a.code,a.serial_no,
-               to_char(a.update_date, 'Mon_DD_YYYY') as _update_date,
-				to_char(a.act_date, 'YYYY-MM-DD') as act_date ,
-				a.act_time,
-				ROW_NUMBER() OVER(PARTITION BY a.serial_no ORDER BY s.sno DESC) AS rn
-				from public.ocean_milestone a 
-					left join public.customer_service_milestone_sno s on a.code = s.code 
-				where s.type = 'sea' 
-					and a.act_date is not null
-					--and a.update_date >= CURRENT_DATE AND a.update_date < CURRENT_DATE + INTERVAL '1 day'
-			)t WHERE rn = 1
-		) po inner join public.kln_ocean oo on oo.serial_no = po.serial_no 
-union all 
-select *,oo.serial_no,oo.order_from,oo.h_bol,oo.transport_mode,oo.place_of_receipt_exp, oo.place_of_delivery_exp,
-		COALESCE(jsonb_data->>'milestone','')::jsonb->>'timezone' as timezone,
-		COALESCE(jsonb_data->>'milestone','')::jsonb->>'locations' as locations 
-	from (
-		select *,public.getTimeAndLocationForKln(t.serial_no,t.code,''::text)::jsonb as jsonb_data 
-			from (
-			SELECT 
-				case when a.code = 'IFFDEP' then 'Departure'
-					when a.code = 'IFFARR' then 'Arrived'
-					when a.code = 'IFFDEL' then 'Delivered'
-				else  s.description end as action_type, 	
-				a.update_date,a.code,a.serial_no,
-                to_char(a.update_date, 'Mon_DD_YYYY') as _update_date,
-				to_char(a.act_date, 'YYYY-MM-DD') as act_date ,
-				a.act_time,
-				ROW_NUMBER() OVER(PARTITION BY a.serial_no ORDER BY s.sno DESC) AS rn
-				from public.air_milestone a 
-					left join public.customer_service_milestone_sno s on a.code = s.code 
-				where s.type = 'sea' 
-					and a.act_date is not null
-					--and a.update_date >= CURRENT_DATE AND a.update_date < CURRENT_DATE + INTERVAL '1 day'
-			)t WHERE rn = 1
-		) pa inner join public.kln_ocean oo on oo.serial_no = pa.serial_no 
-union all 
-select *,oo.serial_no,oo.order_from,oo.h_bol,oo.transport_mode,oo.place_of_receipt_exp, oo.place_of_delivery_exp,
-		COALESCE(jsonb_data->>'milestone','')::jsonb->>'timezone' as timezone,
-		COALESCE(jsonb_data->>'milestone','')::jsonb->>'locations' as locations 
-	from (
-		select *,public.getTimeAndLocationForKln(t.serial_no,t.code,''::text)::jsonb as jsonb_data 
-			from (
-			SELECT 
-				case when a.code = 'IFFDEP' then 'Departure'
-					when a.code = 'IFFARR' then 'Arrived'
-					when a.code = 'IFFDEL' then 'Delivered'
-				else  s.description end as action_type, 	
-				a.update_date,a.code,a.serial_no,
-                to_char(a.update_date, 'Mon_DD_YYYY') as _update_date,
-				to_char(a.act_date, 'YYYY-MM-DD') as act_date ,
-				a.act_time,
-				ROW_NUMBER() OVER(PARTITION BY a.serial_no ORDER BY s.sno DESC) AS rn
-				from sfs.air_milestone a 
-					left join public.customer_service_milestone_sno s on a.code = s.code 
-				where s.type = 'sea' 
-					and a.act_date is not null
-					--and a.update_date >= CURRENT_DATE AND a.update_date < CURRENT_DATE + INTERVAL '1 day'
-			)t WHERE rn = 1
-		) sa inner join public.kln_ocean oo on oo.serial_no = sa.serial_no 
-)t  limit 10";
-
-$data["Sort my active shipments by earliest arrival date."] = "select to_char(oo.eta,'DD-Mon') as eta, oo.h_bol,oo.transport_mode, oo.place_of_receipt_exp, oo.place_of_delivery_exp,oo.serial_no,oo.order_from,
-	case when oo.eta - CURRENT_DATE <= 0 then '< 1 days'::text
-		else (oo.eta - CURRENT_DATE)||' days'::text end as day_to_arr
-from  public.kln_ocean oo where 1=1   order by eta  limit 10";
+
+        $data["Sort my active shipments by earliest arrival date."] = "select to_char(oo.eta,'DD-Mon') as eta, oo.h_bol,oo.transport_mode, oo.place_of_receipt_exp, oo.place_of_delivery_exp,oo.serial_no,oo.order_from,
+                    case when oo.eta - CURRENT_DATE <= 0 then '< 1 days'::text
+                        else (oo.eta - CURRENT_DATE)||' days'::text end as day_to_arr
+                from  public.kln_ocean oo where 1=1   order by eta  limit 10";
 
         return $data[$type];
     }