ShuanghongS 6 mesi fa
parent
commit
189877f3c9
5 ha cambiato i file con 520 aggiunte e 61 eliminazioni
  1. 16 9
      service/login.class.php
  2. 21 3
      service/robot.class.php
  3. 19 0
      service/tools.class.php
  4. 260 49
      utils/common.class.php
  5. 204 0
      utils/utils.class.php

+ 16 - 9
service/login.class.php

@@ -26,7 +26,7 @@ class login {
         customer_search_type, customer_destination, can_add_ams, can_add_isf, air_station, air_sales, ocean_station, ocean_sales,ocean_following_sales,ocean_following_sales_or,air_following_sales,air_following_sales_or, trucking_station, ocean_dest_op, can_see_password, can_add_opsales_code, ocean_station_or, ocean_agent_or, ocean_sales_or, ocean_dest_op_or, air_station_or, air_sales_or, trucking_station_or, 
         can_add_user, can_add_employee, can_add_contact, company_name, ams_email, isf_email, customer_discharge, online_active, is_super, ocean_agent,active, can_send_email, view_file_format as docdownload, container_status, consolidated_cbsa_code, can_add_aci, 
         air_customers, air_customer_search_type,trucking_customers,trucking_customer_search_type, upload_document, view_file_format, event_type, belong_schemas, main_schemas, error_login_count, EXTRACT(EPOCH FROM (now()-COALESCE(error_login_time, now()))) as second, po_status, view_air_file_format, 
-        special_customer_event, can_edi_vgm, isf_aci_ams_station,login_version,is_kerry_shipment,can_visit_delivery,currency_group,revenue_active from public.ra_online_user u where lower(user_login) = ?";
+        special_customer_event, can_edi_vgm, isf_aci_ams_station,login_version,is_kerry_shipment,can_visit_delivery,currency_group,revenue_active from public.ra_online_user u where md5(lower(user_login)) = ?";
     }
 
     public function do_login() {
@@ -62,7 +62,7 @@ class login {
             common::checkUserNameLength($uname);
 
             $sql = $this->getLoginSql();
-            $rs = common::excuteObjectPrepareSql($sql,[strtolower($uname)]);
+            $rs = common::excuteObjectPrepareSql($sql,[md5(strtolower($uname))]);
 
             if (!empty($rs)) {
                 if (empty($rs['belong_schemas'])) {
@@ -284,11 +284,15 @@ class login {
 
                 //kln新版查询 date_format,numbers_format
                 $kln_user = common::excuteObjectSql("select * from public.kln_user_extend where lower(user_login) = '".strtolower($uname)."'");
+
                 //检查用户是否是设置过subscribe_notification,加在这里,少一次请求
                 $count = common::excuteOneSql("select count(*) from public.notifications_rules where 
                     notifications_type = 'Subscribe' 
                     and lower(user_login) = '".strtolower($uname)."'");
                 $subscribe_notification_default_init = $count > 0 ? false:true;
+
+                //添加FAQ客户的访问类型
+                $loginCount = common::excuteOneSql("select count(*) from public.ra_online_user_login_log where lower(user_name) = '".strtolower(common::check_input($uname))."' and date_time >= CURRENT_DATE - INTERVAL '30 day' AND date_time <= CURRENT_DATE");
                 $kln_user_info = array("uname"=>$uname,
                                     "user_type"=>strtolower($rs['user_type']),
                                     "first_name"=>$rs['first_name'],
@@ -299,7 +303,8 @@ class login {
                                     "subscribe_notification_default_init"=>$subscribe_notification_default_init,
                                     "last_pwd_change"=>$rs['last_pwd_change'],
                                     "date_format"=>$kln_user['date_format'],
-                                    "numbers_format"=>$kln_user['numbers_format']);
+                                    "numbers_format"=>$kln_user['numbers_format'],
+                                    "loginCount"=>intval($loginCount));
                 //添加密码是否快过期的消息通知 7 天内,3天内的通知
                 $expire_day = ($PASSWORD_CHANGE_CYCLE - $rs['last_pwd_change_date']);
                 if($expire_day <= 7){ 
@@ -508,7 +513,7 @@ class login {
     public function check_uname(){
         $uname = common::check_input($_POST['uname']);
         $sql = $this->getLoginSql();
-        $rs = common::excuteObjectPrepareSql($sql,[strtolower($uname)]);
+        $rs = common::excuteObjectPrepareSql($sql,[md5(strtolower($uname))]);
         if (!empty($rs)) {
             //只是验证用户是否存在,是否激活
             //验证employee是否active
@@ -625,8 +630,10 @@ class login {
 
         $msg = "";
         if (!empty($email) || !empty($login)) {
-            $sql_p = "select User_Login, ra_password as password from public.ra_online_user where lower(user_login) = '" . strtolower($login) . "' and lower(email) = '" . strtolower($email) . "'";
-            $rs = common::excuteObjectSql($sql_p);
+            $sql_p = "select User_Login, ra_password as password from public.ra_online_user where md5(lower(user_login)) = ? and md5(lower(email)) = ?";
+            //$rs = common::excuteObjectSql($sql_p);
+            $rs = common::excuteObjectPrepareSql($sql_p,[md5(strtolower($login)),md5(strtolower($email))]);
+
             if (!empty($rs)) {
                 $r = utils::sendEmailByPassword($login, $rs['password'], $email);
                 if ($r == 'success') {
@@ -676,7 +683,7 @@ class login {
         } else {
             $uname = common::check_input($_POST['uname']);
             $sql = $this->getLoginSql();
-            $rs = common::excuteObjectPrepareSql($sql,[strtolower($uname)]);
+            $rs = common::excuteObjectPrepareSql($sql,[md5(strtolower($uname))]);
             if (empty($rs['belong_schemas'])) {
                 $rs['belong_schemas'] = "public";
             }
@@ -1087,8 +1094,8 @@ class login {
         //     exit();
         // }
         
-        $sql = "select ra_password as password from ra_online_user where lower(user_login) = '" . strtolower($loginName) . "'";
-        $rs = common::excuteObjectSql($sql);
+        $sql = "select ra_password as password from ra_online_user where md5(lower(user_login)) = ?";
+        $rs = common::excuteObjectPrepareSql($sql,[md5(strtolower($loginName))]);
         $str = '';
         if (!empty($rs)) {
             if ($rs['password'] != $old_password) {

+ 21 - 3
service/robot.class.php

@@ -364,6 +364,17 @@ class robot{
             exit();
         }
 
+        if ($operate == "ai_chat_fixed_init"){
+            $rs = common::excuteListSql("select fixed_faq,secondary_interaction_content from public.kln_robot_chat_fixed where active = true");
+            $data = array();
+            foreach($rs as $v){
+                $data[] = array("label" => $v['fixed_faq'],"value" =>$v['fixed_faq'],"isLong" => strlen($v['fixed_faq']) >60);
+            }
+            $data = array("msg" => "succssful","fixed_question"=>$data);
+            common::echo_json_encode(200,$data);
+            exit();
+        }
+
         /**
          * ai 自由聊天
         */
@@ -403,12 +414,20 @@ class robot{
                 exit();
             }
 
+            $is_fixedAnswer_end = false;
             if ($question_type == 'Predefined Question'){
                 $answer = $this->doFixedAnswerAndLog($serial_no,$fixed_faq,$question_content);
+
+                //判断固定回答是否结束
+                $fixedChat = common::excuteObjectSql("select * from public.kln_robot_chat_fixed where fixed_faq = '$fixed_faq'");
+                if (empty($fixedChat['secondary_interaction_content']) || $question_content != $fixedChat['fixed_faq'] ){
+                    $is_fixedAnswer_end = true;
+                } 
             } else {
                 $answer = $this->doFreeAnswerAndLog($serial_no,$model,$systemPrompt, $question_content, $history = []);
             }
-            $return = array("type"=>"markdown","data" =>$answer);
+
+            $return = array("type"=>"markdown","is_fixedAnswer_end"=>$is_fixedAnswer_end,"data" =>$answer);
             common::echo_json_encode(200,$return);            
             exit();
         }
@@ -631,8 +650,7 @@ class robot{
         $fixedChat = common::excuteObjectSql("select * from public.kln_robot_chat_fixed where fixed_faq = '$fixed_faq'");
         if (empty($fixedChat['secondary_interaction_content']) || $question_content != $fixedChat['fixed_faq'] ){
             $answer_template = common::check_input($fixedChat["answer_style"]);
-            $reference = common::FixedAnswerAndLogData($fixedChat,$question_content);
-            $answer = $reference;
+            $answer =  common::FixedAnswerAndLogData($fixedChat,$question_content);
         } else {
             $answer = $fixedChat['secondary_interaction_content'];
         }

+ 19 - 0
service/tools.class.php

@@ -1224,6 +1224,11 @@ class tools {
                     '$shipment_transport_mode','$shipment_etd_limit','$shipment_eta_limit','$shipment_etd_limit_from','$shipment_eta_limit_from');";
             }
         }
+
+        //保存用户默认的时区
+        $default_time_zone = common::check_input($_POST['default_time_zone']);
+        $default_time_zone_db = utils::comvertutcinfo($default_time_zone);
+        $sql .= "update public.kln_user_extend set default_time_zone = '$default_time_zone_db' where lower(user_login) = '".strtolower(_getLoginName())."';";
         return $sql;
     }
 
@@ -1426,6 +1431,15 @@ class tools {
                         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 ''
@@ -1447,6 +1461,7 @@ class tools {
                         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    
                 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)
@@ -1535,6 +1550,7 @@ class tools {
                     "insert_date_format"=>'',
                     "rules_type"=>$mInfo["notifiation_type"],
                     "is_display_hbol"=>!empty($mInfo["h_bol"]),
+                    "first_notifiation_date"=>$mInfo["first_notifiation_date"],
                     "info"=>new stdClass());
 
             if ($mInfo["frequency_type"] == "Daily"){
@@ -1589,6 +1605,7 @@ class tools {
                     "id"=>$mInfo["id"],
                     "insert_date_format"=>'',
                     "rules_type"=>$mInfo["notifiation_type"],
+                    "first_notifiation_date"=>$mInfo["first_notifiation_date"],
                     "info"=>new stdClass());
 
             if ($mInfo["frequency_type"] == "Daily"){
@@ -1682,6 +1699,7 @@ class tools {
                     "insert_date_format"=>'',
                     "rules_type"=>$mInfo["notifiation_type"],
                     "is_display_hbol"=>!empty($mInfo["h_bol"]),
+                    "first_notifiation_date"=>$mInfo["first_notifiation_date"],
                     "info"=>array("route"=>$route,
                         "leg"=>$leg,
                         "etdOrdeparturNum"=>0,
@@ -1751,6 +1769,7 @@ class tools {
                     "insert_date_format"=>'',
                     "rules_type"=>$mInfo["notifiation_type"],
                     "is_display_hbol"=>!empty($mInfo["h_bol"]),
+                    "first_notifiation_date"=>$mInfo["first_notifiation_date"],
                     "info"=>array("route"=>$route,
                         "leg"=>$leg,
                         "etdOrdeparturNum"=>0,

+ 260 - 49
utils/common.class.php

@@ -2559,71 +2559,66 @@ class common {
 
     public static function FixedAnswerAndLogData($fixedChat,$question_content){
         $reference = "";
-        //单个处理:Shipments arriving in the next 7 days.
-        if($fixedChat["fixed_faq"] == "Shipments arriving in the next 7 days."){
+        //返回格式相同的放到一起处理
+        if($fixedChat["fixed_faq"] == "Shipments arriving in the next 7 days."
+            || $fixedChat["fixed_faq"] == "What is the current status of my active shipments?"){
+
             $reference = $fixedChat["answer_style"];
-            //执行sql 语句
             $sql = $fixedChat["fixed_sql"];
-            // $sql = "select h_bol,place_of_receipt_exp,place_of_delivery_exp,description,eta,cargo_type 
-            // from (
-            // 	SELECT h_bol, place_of_receipt_exp, place_of_delivery_exp,m.description,eta, '' as cargo_type
-            // 		FROM public.kln_ocean oo
-            // 		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 order by eta";
-            //给所有sql 拼接用户权限
+            if($_POST['is_demo'] == 't'){
+                $sql = utils::getDmoeSqlForAi($fixedChat["fixed_faq"]);
+            }
+            //拼接用户权限
             $sqlWhere = '  ' . common::searchExtendHand_KLN("ocean", $_SESSION["ONLINE_USER"]);
-            //根据public.kln_ocean 和 WHERE 的位置关系,带入权限
             $_sql = str_replace('<{ExtendHand_KLN}>', $sqlWhere, $sql);
             error_log($_sql);
             $data = common::excuteListSql($_sql);
 
-            //--[Details](https://example.com/details) [Notify](https://example.com/notify)
-            foreach($data as $key =>$d){
-                $data[$key]['action'] = "[Details](https://example.com/details) [Notify](https://example.com/notify)";
+            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)";
+                }
+            }
+            if($fixedChat["fixed_faq"] == "What is the current status of my active shipments?"){   
+                $refer_data =array();    
+                foreach($data as $key =>$d){
+                    $temp = array();
+                    $temp['h_bol'] = $d['h_bol'];
+                    $temp['place_of_receipt_exp'] = $d['place_of_receipt_exp'];
+                    $temp['place_of_delivery_exp'] = $d['place_of_delivery_exp'];
+                    $temp['description'] = $d['description'];
+                    $temp['time'] = common::dealDateTime($d['act_date'],$d['act_time'],$d['timezone'],"m/d/Y");
+                    $temp['locations'] = $d['locations'];
+                    $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)";
+                    $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"));
-            error_log($reference);
         }
 
         if($fixedChat["fixed_faq"] == "List shipments with milestone updates in the last 7 days."){
             $reference = $fixedChat["answer_style"];
-            //执行sql 语句
             $sql = $fixedChat["fixed_sql"];
-            // $sql = "select h_bol, description,update_date_format,update_date,timezone,locations
-            // from (
-            // select 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.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
-            // )t";
-           
-            //给所有sql 拼接用户权限
+            if($_POST['is_demo'] == 't'){
+                $sql = utils::getDmoeSqlForAi($fixedChat["fixed_faq"]);
+            }
+            //拼接用户权限
             $sqlWhere = '  ' . common::searchExtendHand_KLN("ocean", $_SESSION["ONLINE_USER"]);
-            //根据public.kln_ocean 和 WHERE 的位置关系,带入权限
             $_sql = str_replace('<{ExtendHand_KLN}>', $sqlWhere, $sql);
             error_log($_sql);
             $data = common::excuteListSql($_sql);
 
-            //--[Details](https://example.com/details) [Notify](https://example.com/notify)
             $refer_data =array();    
             foreach($data as $key =>$d){
                 $temp = array();
@@ -2631,7 +2626,10 @@ class common {
                 $temp['description'] = $d['description'];
                 $temp['update_date'] = common::dealDateTime($d['update_date'],"",$d['timezone'],"m/d/Y H:i:s");
                 $temp['locations'] = $d['locations'];
-                $temp['action'] = "[Details](https://example.com/details) [Notify](https://example.com/notify)";
+
+                $serial_no = common::deCode($d['serial_no'], 'E');
+                $httpUrl = "tracking/detail?a=".$serial_no."&_schemas=".$d["order_from"];
+                $temp['action'] = "[Details](".$httpUrl.") [Notify](SystemSettings)";
                 $refer_data[] = $temp;
             }
             $reference = utils::replacementsFixedMultilineForFixed($refer_data,$fixedChat['answer_style'],$fixedChat['table_format_tr']);
@@ -2657,19 +2655,22 @@ class common {
 
         if($fixedChat["fixed_faq"] == "Show me the full history of my container."){
             $reference = $fixedChat["answer_style"];
-            //执行sql 语句
             $sql = $fixedChat["fixed_sql"];
-          
+            if($_POST['is_demo'] == 't'){
+                 $question_content = 'DRYU9375994';
+            }
             $sqlArr = explode(";", $sql);
-
             $sqlOne = $sqlArr[0];
-            //给所有sql 拼接用户权限
+            //拼接用户权限
             $sqlWhere = '  ' . common::searchExtendHand_KLN("ocean", $_SESSION["ONLINE_USER"]);
-            //根据public.kln_ocean 和 WHERE 的位置关系,带入权限
             $sqlOne = str_replace('<{ExtendHand_KLN}>', $sqlWhere, $sqlOne);
             $sqlOne = str_replace('<{ctnr}>', strtolower($question_content), $sqlOne);
+
             error_log($sqlOne);
             $data = common::excuteListSql($sqlOne);
+            if(empty($data)){
+                return "No valid Container number detected. Please try clicking on other FAQ questions or input your own question. Thank you.";
+            }
 
             //如果数据为空,用这个fileds配置的 把模板里值逐个替换为空
             $fileds = array("ctnr","size","h_bol","carrier","vessel","voyage","grs_kgs","ams_commodity","seal_no",
@@ -2699,8 +2700,218 @@ class common {
             } 
             $rsdata = array("complete_container_status" =>$complete_container_status);
             $reference = utils::replacementsFixed($rsdata,$reference,[]);
-            error_log($reference);
         }
+
+        if($fixedChat["fixed_faq"] == "Show me the full history of my shipment."){
+            $reference = $fixedChat["answer_style"];
+            $sql = $fixedChat["fixed_sql"];
+            if($_POST['is_demo'] == 't'){
+                 $question_content = 'XSTSNA003195';
+            }
+            $sqlArr = explode(";", $sql);
+            $sqlOne = $sqlArr[0];
+            //拼接用户权限
+            $sqlWhere = '  ' . common::searchExtendHand_KLN("ocean", $_SESSION["ONLINE_USER"]);
+            $sqlOne = str_replace('<{ExtendHand_KLN}>', $sqlWhere, $sqlOne);
+            $sqlOne = str_replace('<h_bol>', strtolower($question_content), $sqlOne);
+
+            error_log($sqlOne);
+            $data = common::excuteListSql($sqlOne);
+            if(empty($data)){
+                return "No valid Shipment number detected. Please try clicking on other FAQ questions or input your own question. Thank you.";
+            }
+
+            //如果数据为空,用这个fileds配置的 把模板里值逐个替换为空
+            $fileds = array("h_bol","carrier_booking","po_no","service","incoterms","shipper_city","consignee_city","etd","eta",
+                "shipper","consignee","notify_party","origin_agent","destination_agent","carrier");
+            //Container Information替换
+            $reference = utils::replacementsFixed($data[0],$reference,$fileds);
+
+            //Complete Container Status
+            //根据第一个sql 查出来的serial_no,和 container_no.
+            $complete_container_status = "";
+            if (!empty($data)) {
+                $sqltwo = $sqlArr[1];
+                $sqltwo = str_replace('<{serial_no}>', $data[0]['serial_no'], $sqltwo);
+                $csdata = common::excuteListSql($sqltwo);
+                foreach($csdata as $csd){
+                    $complete_container_status .= "- **".$csd['eventdate']." ".$csd['eventtime']."** ".$csd['description']." | ".$csd['uncity']."  \n";
+                }
+            } 
+            $rsdata = array("complete_container_status" =>$complete_container_status);
+            $reference = utils::replacementsFixed($rsdata,$reference,[]);
+        }
+
+        if($fixedChat["fixed_faq"] == "List shipments with container status updates in the last 7 days."){
+            $reference = $fixedChat["answer_style"];
+            $sql = $fixedChat["fixed_sql"];
+            if($_POST['is_demo'] == 't'){
+                $sql = utils::getDmoeSqlForAi($fixedChat["fixed_faq"]);
+            }
+            //拼接用户权限
+            $sqlWhere = '  ' . common::searchExtendHand_KLN("ocean", $_SESSION["ONLINE_USER"]);
+            $_sql = str_replace('<{ExtendHand_KLN}>', $sqlWhere, $sql);
+
+            error_log($_sql);
+            $data = common::excuteListSql($_sql);
+
+            $refer_data =array();    
+            foreach($data as $key =>$d){
+                $temp = array();
+                $temp['container_no'] = $d['container_no'];
+                $temp['description'] = $d['description'];
+                $temp['time'] = common::dealDateTime($d['eventdate'],$d['eventtime'],$d['timezone'],"m/d/Y H:i:s");
+                $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)";
+                $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"));
+
+            //Timeline View
+            $dateGroups = utils::uniqueGroupbyData("container_no","_eventdate",$data);
+            $timeline_view = "";
+            if(!empty($dateGroups)){
+                $timeline_view = "## Timeline View: \n";
+            }
+            foreach($dateGroups as $date => $count){
+                $timeline_view.="- ".$date.": ".$count." containers have been updated \n";
+            }
+            $total = array("Timeline View" =>$timeline_view);
+            $reference = utils::replacementsFixed($total,$reference,array("Timeline View"));
+        }
+
+        if($fixedChat["fixed_faq"] == "Today's shipments summary."){
+            $reference = $fixedChat["answer_style"];
+            $sql = $fixedChat["fixed_sql"];
+            if($_POST['is_demo'] == 't'){
+                $sql = utils::getDmoeSqlForAi($fixedChat["fixed_faq"]);
+            }
+            //拼接用户权限
+            $sqlWhere = '  ' . common::searchExtendHand_KLN("ocean", $_SESSION["ONLINE_USER"]);
+            $_sql = str_replace('<{ExtendHand_KLN}>', $sqlWhere, $sql);
+            error_log($_sql);
+            $data = common::excuteListSql($_sql);
+
+            $refer_data =array();    
+            foreach($data as $key =>$d){
+                $temp = array();
+                $temp['h_bol'] = $d['h_bol'];
+                $temp['transport_mode'] = $d['transport_mode'];
+                $temp['place_of_receipt_exp'] = $d['place_of_receipt_exp'];
+                $temp['place_of_delivery_exp'] = $d['place_of_delivery_exp'];
+                $temp['action_type'] = $d['action_type'];
+                $temp['time'] = common::dealDateTime($d['act_date'],$d['act_time'],$d['timezone'],"m/d/Y H:i:s");
+                $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)";
+                $refer_data[] = $temp;
+            }
+            $reference = utils::replacementsFixedMultilineForFixed($refer_data,$fixedChat['answer_style'],$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,[]);
+
+            
+            $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);
+            $reference = utils::replacementsFixed($total,$reference,[]);
+        }
+
+        if($fixedChat["fixed_faq"] == "Show me the current location of my shipment."){
+            $reference = $fixedChat["answer_style"];
+            $sql = $fixedChat["fixed_sql"];
+            if($_POST['is_demo'] == 't'){
+                 $question_content = 'DRYU9375994';
+            }
+
+            $sqlArr = explode(";", $sql);
+            $sqlOne = $sqlArr[0];
+            //拼接用户权限
+            $sqlWhere = '  ' . common::searchExtendHand_KLN("ocean", $_SESSION["ONLINE_USER"]);
+            $sqlOne = str_replace('<{ExtendHand_KLN}>', $sqlWhere, $sqlOne);
+            $sqlOne = str_replace('<{ctnr}>', strtolower($question_content), $sqlOne);
+            error_log($sqlOne);
+            $data = common::excuteListSql($sqlOne);
+            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","shipper_city","consignee_city","carrier","vessel","voyage","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;
+            }
+            $reference = utils::replacementsFixed($data[0],$reference,$fileds);
+        }
+
+        if($fixedChat["fixed_faq"] == "Sort my active shipments by earliest arrival date."){
+            $reference = $fixedChat["answer_style"];
+            $sql = $fixedChat["fixed_sql"];
+            if($_POST['is_demo'] == 't'){
+                $sql = utils::getDmoeSqlForAi($fixedChat["fixed_faq"]);
+            }
+            //拼接用户权限
+            $sqlWhere = '  ' . common::searchExtendHand_KLN("ocean", $_SESSION["ONLINE_USER"]);
+            $_sql = str_replace('<{ExtendHand_KLN}>', $sqlWhere, $sql);
+            error_log($_sql);
+            $data = common::excuteListSql($_sql);
+
+            $refer_data =array();    
+            foreach($data as $key =>$d){
+                $temp = array();
+                $temp['eta'] = $d['eta'];
+                $temp['h_bol'] = $d['h_bol'];
+                $temp['transport_mode'] = $d['transport_mode'];
+                $temp['place_of_receipt_exp'] = $d['place_of_receipt_exp'];
+                $temp['place_of_delivery_exp'] = $d['place_of_delivery_exp'];
+                $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)";
+                $refer_data[] = $temp;
+            }
+            $data = $refer_data;
+            $reference = utils::replacementsFixedMultilineForFixed($data,$fixedChat['answer_style'],$fixedChat['table_format_tr']);
+        }
+
+        if($fixedChat["fixed_faq"] == "Set up automatic notifications for shipment updates." 
+            || $fixedChat["fixed_faq"] == "How can I view the detailed shipping route of my package?" ){
+            $reference = $fixedChat["answer_style"];
+        }
+
+        error_log($reference);
         return $reference; 
     }
 

+ 204 - 0
utils/utils.class.php

@@ -1095,5 +1095,209 @@ class utils {
         } 
         return $dateGroups;
     }
+
+    public static function getDmoeSqlForAi($type){
+        $data= array();
+        $data["Shipments arriving in the next 7 days."] =  "select oo.serial_no,h_bol,place_of_receipt_exp,place_of_delivery_exp,description,eta,order_from,cargo_type 
+         from (
+         	SELECT 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 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
+            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 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
+            )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,oo.serial_no
+            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";
+
+        return $data[$type];
+    }
 }
 ?>