2013年5月17日

利用拖拉的功能,實現資料排序、複製或移除

在 Genero 有提供 Drag & Drop 功能,也就是使用滑鼠(或觸控螢幕)來進行資料的搬移。
這樣就可以調整資料的順序,或是將資料由 A 搬到 B ,或者可以把資料做移除的動作。

DEFINE l_dd ui.DragDrop 要先宣告螢幕拖拉的變數
必須要在 DISPLAY ARRAY 再加上 Drag 和 Drop 的區段,使用方法如下:
ON DRAG_START(l_dd) :當開始拖拉來源端 Object 的某一筆資料時
ON DRAG_FINISHED(l_dd):當來源端的 Object 拖拉完成時
ON DRAG_ENTER(l_dd):當拖拉到目的端 Object 時
ON DRAG_OVER(l_dd):當拖拉到目的端 Object 的某一個 Cursor 時
ON DROP(l_dd):當拖拉到目的端 Object 完成時

所以要在 DISPLAY ARRAY 可以進行拖的動作就要加上 DRAG ,要可以進行拉的動作就要加上 DROP ,不然滑鼠就會出現禁止的圖示而無法完成拖拉。

再來就是相關的指令說明:
1. setOperation:預設的拖拉動作
    CALL l_dd.setOperation(""):預設不能拖拉,其實你覺得拖拉很討厭的話,可以執行此指令
    CALL l_dd.setOperation("move"):預設為資料搬移
    CALL l_dd.setOperation("copy"):預設為資料複製

2. addPossibleOperation:額外增加拖拉的動作
    CALL l_dd.addPossibleOperation("copy"):通常都是預設設定為搬移,然後拖拉時加上 Ctrl 鍵,就可以 copy 的動作

3. setMimeType:設定 MIME 的格式,當要拉到外部的程式時,必須要可以支援拖拉的程式才行(記事本就沒這個功能了)
    CALL l_dd.setMimeType("text/plain")
    CALL l_dd.setMimeType("text/uri-list")
    CALL l_dd.setMimeType("text/x-vcard")
                         :
    (自定義的 MIME 格式)

4. setBuffer:設定拖資料時暫存的資料
    CALL l_dd.setBuffer(g_sfa[arr_curr()].sfa03):舉例要把 sfa03 的欄位內容拖拉的方式複製到 Excel 來貼上

5. setFeedback:設定拖拉的模式為取代或是新增
    CALL l_dd.setFeedback("all") :可取代也可以新增
    CALL l_dd.setFeedback("insert"):只能新增
    CALL l_dd.setFeedback("select"):只能取代

6. getLocationRow() :回傳目前要拖拉的 cursor 行號
    LET l_ac = l_dd.getLocationRow():舉例目前要拖拉的 cursor 行號指派給 l_ac 變數

7. getLocationParent():回傳 Tree View 的 Node,用法和 getLocationRow() 相同

8. getOperation():回傳拖拉動作是搬移還是複製
    CASE l_dd.getOperation()
          WHEN "move"
                 MESSAGE "MOVE"
          WHEN "copy"
                 MESSAGE "COPY"
    END CASE

9. selectMimeType():所拖拉的資料格式
    CASE
            WHEN dnd.selectMimeType("text/plain") 
            WHEN dnd.selectMimeType("text/uri-list") 
    END CASE

範例為要把單身的資料用拖拉的方式進行資料的排序
DEFINE   l_dd      ui.DragDrop,
              l_n_old   LIKE type_file.num5,
              l_n_new   LIKE type_file.num5
DEFINE   l_i       LIKE type_file.num5
DISPLAY ARRAY g_oeb TO s_oeb.* ATTRIBUTE(COUNT=g_rec_b) 
         ON DRAG_START(l_dd)
            LET l_n_old = arr_curr()
            CALL l_dd.setOperation("move")
         ON DRAG_FINISHED(l_dd)
            IF l_n_old <> l_n_new THEN
               IF l_n_old < l_n_new THEN
                  FOR l_i = l_n_old+1 TO l_n_new 
                      LET g_oeb[l_i].oeb03n= l_i-1
                  END FOR 
                  LET g_oeb[l_n_old].oeb03n= l_n_new
               ELSE 
                  FOR l_i = l_n_new TO l_n_old-1
                      LET g_oeb[l_i].oeb03n= l_i+1
                  END FOR 
                  LET g_oeb[l_n_old].oeb03n= l_n_new
               END IF 
               FOR l_i = 1 TO g_tc_sfb1.getlength()
                   UPDATE oeb_file SET oeb03= g_oeb[l_i].oeb03n
                    WHERE oeb01 = g_oea.oea01
                      AND oeb03 = g_oeb[l_i].oeb03
               END FOR 
            END IF
            CALL DIALOG.setCurrentRow("s_oeb",l_n_new)     # 將 cursor 移到新的位置
            CALL t400_b_fill()                                                     # 更新資料
            CONTINUE DISPLAY 
         ON DRAG_OVER(l_dd)
            LET l_n_new = l_dd.getLocationRow()
         ON DROP(l_dd)
END DISPLAY
此範例只提供參考用,要注意欄位是否為 INDEX 有重複的情況,會造成 update 失敗。

結論:拖拉方式的操作在某些情況可以帶來使用者更方便的使用,試試看吧~~

2013年5月16日

Linux 檔案同步,AP主機程式保持一致

當有二台 TIPTOP 主機提供 AP 服務時,希望程式修改後也能夠定期同步到另一台主機,
這時候就可以用 rsync 的指令,快速的把不相同的檔案複製到另一台主機上,權限和檔案屬性也不會改變。

rsync 的指令就不加以敘述,請參考其他的網站的說明。

同步指令如下:
rsync -av -e ssh root@192.168.1.100:/u1/topprod /u1
命令的說明是用 ssh 以 root 帳號登入到 192.168.1.100 主機,然後將 /u1/topprod / 目錄的所有檔案同步到此主機的 /u1 目錄。
記得目的端就不要再加上 topprod 不然會建立新的資料夾。

因為不同主機 IP 位置不同,所以必須排除 tiptop_env 檔案,所以再加上 --exclude 後面加上相對的路徑名稱。
不能加上絕對的路徑,會沒辦法排除。
rsync -av -e ssh --exclude tiptop/bin/tiptop_env root@192.168.1.100:/u1/topprod /u1
再來就輸入來源主機的 root 的密碼就開始進行檔案同步。

如果檔案很多的話,需建立 list 檔來排除。
建立 vi /root/sync2.list 檔,排除以下的檔案。
tiptop/bin/tiptop_env
tiptop/lib/4gl/cl_user.4gl
tiptop/lib/42m/lib_cl_user.42m
tiptop/lib/42m/lib_cl_user.4gl
tiptop/lib/42m/lib.42x

然後再加上 --exclude-from 參數來排除指定的 list 檔案,
rsync -av -e ssh --exclude-from=/root/sync2.list root@192.168.1.100:/u1/topprod /u1

如果希望能夠排程的同步檔案,必須要先讓 ssh 不需要密碼就可以登入。
在目的主機 /root 目錄建立 .ssh 的資料夾, mkdir /root/.ssh 。
到 .ssh 目錄執行 ssh-keygen -d ,會產生二個檔案 id_dsa 和 id_dsa.pub 。
然後再到來源主機,一樣建立相同的資料夾 /root/.ssh 。
將 id_dsa.pub 檔案傳送到來源主機,並更名為 authorized_keys2 。
執行:scp id_dsa.pub 192.168.1.100:/root/.ssh/authorized_keys2

測試看看是否不需輸入密碼:ssh root@192.168.1.100
如果還是會跳出密碼的驗証,請修改 authorized_keys2 權限改為 640 (chmod 640 authorized_keys2 )

再來就是建立一個檔名為 sync2 批次檔。
vi  /root/sync2
rsync -av -e ssh --exclude-from=/root/sync2.list root@192.168.1.100:/u1/topprod /u1
檔案權限改為 700  (chmod 700 sync2),可執行檔。

執行看看是否成功:./sync2

再來就是加到 cron job 每 5 分鐘同步一次。
執行 crontab -e 編輯排程檔:
加上:*/5 * * * * /root/sync2

這樣每 5 分鐘就會將來源主機和目的主機有差異的檔案進行複製的動作。
這樣AP主機的程式就會保持一致。

2013年5月9日

鼎新所提供的 Java Mail 的功能

鼎新提供 TIPTOP 可以寄送 Mail 的功能,
也有許多程式也已經有把程式碼都加進去裡面。
所以備忘一下,日後比較方式使用。

先維護 xml 檔:/u1/topprod/tiptop/ds4gl2/bin/javamail/genxml
維護 mail server 主機、SMTP Port、驗証使用者名稱、密碼…等。

再來就是 4GL 的部份:
g_xml.subject:郵件主旨

g_xml.body:郵件內容的檔案路徑,所以要先把內容存成一個 Temp File 的方式
將郵件的內文存到 Temp File:
LET l_buf1 = FGL_GETENV("TEMPDIR")
LET l_buf1 = l_buf1,"/report_context_" || FGL_GETPID() || ".txt"
LET l_buf = "echo '" || l_buf || "' > " || l_buf1
RUN l_buf WITHOUT WAITING
LET g_xml.body = l_buf1

g_xml.sender:寄件者 Mail Address:寄件者名稱 (中間用冒號 : 區隔)

g_xml.recipient:收件者 Mail Address:收件者名稱 (中間用冒號 : 區隔),多個用收件件用分號 ; 來區別
g_xml.ccrecipient:複本 Mail Address:收件者名稱 (中間用冒號 : 區隔),多個用收件件用分號 ; 來區別
g_xml.bccrecipient:密件複本 Mail Address:收件者名稱 (中間用冒號 : 區隔),多個用收件件用分號 ; 來區別

g_xml.attach:附件的檔案位置,多個附件用分號 ; 來區別

CALL cl_jmail():發送 e-mail

就可以把 e-mail 透過鼎新所提供的 Java Mail 寄送出去,
收件者建議在 aooi998 進行維護,寄件者可以抓 gen_file 資料。

郵件的內容為 HTML 的格式,所以如果是資料的話可以用 TABLE 的方式來呈現。
<TABLE BORDER=1 WIDTH=400px STYLE=border-collapse:collapse; BORDERCOLOR=black>
<TR align=center><TD>訂單+項次</TD><TD>原交期</TD><TD>新交期</TD></TR>
<TR><TD>g_oea.oea01</TD><TD>g_oeb.oeb15</TD><TD>g_oeb.oeb16</TD></TR>
</TABLE>

進階的 Dialog 說明-可多重選取的模式

先前有討論過更便利的 DIALOG - 同時單頭單身查詢/輸入的 Multiple Dialogs 功能,
可以同時進行多個 Display、Input、Display Array、Input Array 的操作。
Genero 不斷的加強 Dialog 的功能,提供更方便的操作使用。
像是 Input 和 Display Array 在同一個 Dialog 時,就可以輸入資料,然後同步 Display Array 一直 Insert 或是 Append 資料,
在 Display Array 選擇時,也同步顯示資料在 Input ,可以快速進行資料的修改,
就不用局限一定要輸入或維護完一筆資料後就異動一次資料庫了,全部資料維護完後再一次更新到資料庫。
另外還提供多重選擇的功能,搭配之前在畫面加上資料清單,從此不必再下一筆查了的方式,
就可以讓使用者選擇多筆資料後,按下過帳或是確認的按鈕,就可以一次完成所有的動作。

Dialog 的函數說明:
CLASS Method:
1. getCurrent():回傳目前是在那一個 Object 的操作。
2. setDefaultUnbuffered(boolean):設定取消 Buffer ,才可以修改資料。
Object Method:
1. accept():確認所有的欄位是否完整。
2. insertNode(Object Name, 第幾個 Node):新增 Tree View 的 Node
3. appendNode(Object Name, 第幾個 Node):增加 Tree Viw 的 Node
4. deleteNode(Object Name,第幾個 Node):刪除 Tree Viw 的 Node
5. insertRow(Object Name,第幾行):新增 Object 畫面上的一行紀錄
6. appendRow(Object Name):增加 Object 畫面上的一行紀錄
7. deleteRow(Object Name, 第幾行 ):刪除 Object 畫面上的一行紀錄
8. deleteAllRows(Object Name):刪除 Object 畫面上的所有紀錄
9. getArrayLength(Object Name):回傳 Object 的筆數
10. getCurrentItem( ):回傳目前 Object 的在那一個欄位、List、Action
11. getCurrentRow(Object Name):回傳目前在那一個 Object 上的第幾行
12. getFieldBuffer(Field Name):回傳 Construct 或是 Input Buffered Mode 的 Buffer 值
13. getFieldTouched(Field List):回傳欄位有動到的清單
14. setFieldTouched(Field List,boolean):設定或取消欄位的 Touch Flag 的值來判斷是否欄位有動過
15. setArrayLength(Object Name,數量):設定總共 Object 的行數
16. getForm():回傳目前的 Form 名稱
17. nextField(Field Name):將游標跳到下一個欄位
18. setActionActive(Object,bollean):設定 Object 的 Action 是否要開啟或關閉
19. setActionHidden(Object Name,bollean):設定 Object 的 Action 是否要顯示或是隱藏
20. setCurrentRow(Object Name,第幾行):跳到所指定的第 n 行
21. setFieldActive(Field List,bollean):設定欄位是否可以編輯
22. setCellAttributes(Object Array):設定欄位的屬性(單一個 Dialog)
23. setArrayAttributes(Object Name,Object Array):設定欄位的屬性(多個 Dialog)
24. validate(Field List):驗証欄位是否為必須輸入、Not Null 或是 Validation Rules 的規則
25. isRowSelected(Object Name,第幾行):判斷第幾行是否有選取反白
26. selectionToString(Object Name):回傳所選擇的資料行的所有欄位內容
27. setSelectionMode(Object Name,bollean):開啟或關閉是否可以多筆的功能,0 為單選,1為多選
28. setSelectionRange(Object Name,開始行,結束行,選或不選):設定開始行到結束行要 1 為選取或是 0 為取消選取,結束行是 -1 表示為最後一行

範例多重選擇後然後將所選取的全部確認:
DISPLAY ARRAY g_oea_l TO s_oea_l.* ATTRIBUTE(COUNT=g_row_count)
   BEFORE DISPLAY
       CALL DIALOG.setSelectionMode( "s_oea_l", 1 )
   ON ACTION confirm
       LET g_action_choice="confirm"
       IF cl_chk_act_auth() THEN
          IF cl_confirm('axm-351') THEN
             FOR l_i=1 TO g_row_count
                IF DIALOG.isRowSelected( "s_oea_l", l_i) THEN
                     LET g_oea.oea01 = g_oea_l[l_i].oea01_l
                     CALL i101_y()
               END IF
            END FOR
         END IF
      END IF
      CONTINUE DIALOG
END DISPLAY

2013年4月22日

GDC 提供 Windows DDE 的支援

Dynamic Data Exchange (DDE) 就是二個不同的應用程式,能夠彼此相互共享記憶體的資料,
讓資料可以即時更新,不同應用程式資料保持一致。
MS Office 有提供 DDE 的功能,以一般常用的 Excel 來說明,
當 TIPTOP 資料進行修改的時候,Excel 資料也會跟著馬上進行同步的修改。

舉例 Excel DDE 語法如下說明:
CONSTANT file = "Sheet1"
CONSTANT prog = "EXCEL"
DEFINE val, rval STRING
DEFINE res INTEGER
CALL ui.Interface.frontCall("WINDDE","DDEConnect", [prog,file], [res] ) 開啟 Excel DDE
CALL ui.Interface.frontCall("WINDDE","DDEPoke", [prog,file,"R1C1",val], [res] ); 將 val 值傳送給 Excel 第1行第1欄
CALL ui.Interface.frontCall("WINDDE","DDEPeek", [prog,file,"R1C1"], [res,rval] ); 接收 Excel 第1 行第1欄的 rval 值
CALL ui.Interface.frontCall("WINDDE","DDEError",[],[mess]); # 顯示錯誤訊息
CALL ui.Interface.frontCall("WINDDE","DDEExecute", [prog,file,"[save]"], [res] ); 將 Excel 存檔
CALL ui.Interface.frontCall("WINDDE","DDEFinish", [prog,file], [res] ); 關閉此 DDE
CALL ui.Interface.frontCall("WINDDE","DDEFinishAll", [], [res] ); 關閉所有的 DDE

要注意,要啟動 Excel DDE 時,必須先開啟 Excel 且有 Sheet1 才可以使用。
可以使用 Windows Path 或是用絕對路徑方式,用 cl_open_prog("excel","/p c:\\tiptop") 來開啟。

範例,程式讀取 Excel 的料號,並將品名和規格回傳給 Excel 。
#宣告
CONSTANT file = "Sheet1"
CONSTANT prog = "EXCEL"
DEFINE val, rval,colrow STRING
DEFINE res INTEGER

#讀取 Excel
CALL ui.Interface.frontCall("WINDDE","DDEConnect", [prog,file], [res] )
WHILE TRUE
   LET l_ac = l_ac + 1
   LET colrow = "R",l_ac USING '<<<<<',"C1"
   CALL ui.Interface.frontCall("WINDDE","DDEPeek", [prog,file,colrow], [res,rval] );
   LET g_ima[l_ac].ima01 = rval
   LET colrow = "R",l_ac USING '<<<<<',"C2"
   CALL ui.Interface.frontCall("WINDDE","DDEPeek", [prog,file,colrow], [res,rval] );
   LET g_ima[l_ac].ima02 = rval
   LET colrow = "R",l_ac USING '<<<<<',"C3"
   CALL ui.Interface.frontCall("WINDDE","DDEPeek", [prog,file,colrow], [res,rval] );
   LET g_ima[l_ac].ima021 = rval
   IF cl_null(g_ima[l_ac].ima01) THEN
      EXIT WHILE
   END IF
END WHILE

# 回傳到 Excel
LET val = g_ima[l_ac].ima01
LET colrow = "R",l_ac USING '<<<<<',"C1"
CALL ui.Interface.frontCall("WINDDE","DDEPoke", [prog,file,colrow,val], [res] );
LET val = g_ima[l_ac].ima02
LET colrow = "R",l_ac USING '<<<<<',"C2"
CALL ui.Interface.frontCall("WINDDE","DDEPoke", [prog,file,colrow,val], [res] );
LET val = g_ima[l_ac].ima021
LET colrow = "R",l_ac USING '<<<<<',"C3"
CALL ui.Interface.frontCall("WINDDE","DDEPoke", [prog,file,colrow,val], [res] );

#存檔
CALL ui.Interface.frontCall("WINDDE","DDEExecute", [prog,file,"[save]"], [res] );

# 關閉 DDE
CALL ui.Interface.frontCall("WINDDE","DDEFinish", [prog,file], [res] );

# 關閉所有 DDE
CALL ui.Interface.frontCall("WINDDE","DDEFinishAll", [], [res] );

因 Genero 的畫面不像 Excel 隨時偵測記憶體並更新顯示資料,TIPTOP 畫面只能下達 DISPLAY 指令手動更新資料,
所以用 TIPTOP 程式來更新 Excel 的資料對此 DDE 的功能使用上或許會比較適合,
或是將 TIOTOP 資料匯出 Excel 的功能並寄送 e-mail 等功能會實用一點。

利用 Client 端 GDC 發送 e-mail

現今 e-mail 已是公司通知不可獲缺的工具,
在 GDC 有提供寄送 e-mail 的功能,直接使用者的電腦寄送 e-mail 。
當使用者執行某一個動作時,就可以發送 e-mail 給相關的人員,可做為溝通連絡的用途。
舉例公司就是當修改料件製程時,有工單生產此料件還未結案時,就發通知給相關人員做檢查。

GDC 是屬於 Client 端的應用程式,所以 e-mail 是由 Client 端來發出,
可以透過 MAPI (如 Outlook) 或是 SMTP (郵件伺服器) 方式寄送。

語法說明:
需要注意 l_buf1、l_buf 變數必需定義為 STRING ,不能為 varchar 的型態,l_result、l_id 為 INTEGER 型態。

CALL ui.Interface.frontCall("WinMail", "Init", [], [l_id])     宣告。
CALL ui.interface.frontCall("WinMail", "SetSubject", [l_id, l_buf], [l_result])    設定 e-mail 主旨
CALL ui.interface.frontCall("WinMail", "SetBody", [l_id, l_buf], [l_result])       設定 e-mail 的內容
CALL ui.Interface.frontCall("WinMail", "AddTo", [l_id, l_buf1, l_buf], [l_result])    設定多個收件者名稱、e-mail

CALL ui.Interface.frontCall("WinMail", "AddCC", [l_id, l_buf1, l_buf], [l_result])   設定多個複本收件者名稱、e-mail

CALL ui.Interface.frontCall("WinMail", "AddBCC", [l_id, l_buf1, l_buf], [l_result])   設定多個密件複本收件者名稱、e-mail

CALL ui.Interface.frontCall("WinMail", "AddAttachment", [l_id, "c:\\mydocs\report.doc"], [result])  設定多個附件


GDC 有二個方式寄送 e-mail ,透過 SMTP 直送發送 e-mail 或是開啟 Client 郵件軟體(如 Outlook)
A. MAPI 透過使用者端的郵件軟體來發送。
CALL ui.Interface.frontCall("WinMail","SendMailMAPI", [l_id], [l_result] )    

B. SMTP 直接傳送郵件到郵件主機。
CALL ui.Interface.frontCall("WinMail", "SetFrom", [l_id, l_buf1, l_buf], [l_result])    設定 e-mail 的寄件者名稱、e-mail
CALL ui.Interface.frontCall("WinMail", "SetSmtp", [l_id, "192.168.1.100"], [l_result])    設定 SMTP Server
CALL ui.Interface.frontCall("WinMail", "SendMailSMTP", [l_id], [l_result])    利用 SMTP 發送 e-mail

CALL ui.Interface.frontCall("WinMail", "GetError", [l_id], [l_str])     # 錯誤訊息
CALL ui.Interface.frontCall("WinMail", "Close", [l_id], [l_result])    # 關閉

雖然提供 Client 端的 e-mail 功能,不過使用上就顯的很陽春,只能用純文字發送 e-mail ,不能改為 HTML 樣式,也不能修改 e-mail 的編碼,所以中文字就不能顯示,再來就是 SMTP 沒有提供帳號驗証的功能,必須公司內部郵件主機是不需身份驗証的,所以使用此功能要特別注意。

在 TIPTOP 設定 e-mail 的收件者,儘可能在 aooi998 來設定,日後比較方便維護和紀錄。

2013年4月9日

Oracle 發送 e-mail 的功能

有些時候能夠定期由 Oracle 來寄送一些資料庫的狀況和資訊或是建立 Alert 機制,
對於 DBA 來說工作就可以輕鬆不少,也可以避免一些預期上的問題。
或是有一些報表或是執行的結果也可以透過這個方式,將  SQL 結果寄送到相關人員的 e-mail 信箱。

Oracle 提供 e-mail 傳送的功能 UTL_SMTP 來發送郵件,
只要放到 Oracle 排程裡就會定期發送 e-mail 給相關的人員,
使用此功能必須要以 sysdba 的角色來登入才能使用 (用 sys 的帳號) 。

指令看了就知道作用了,就不詳細介紹。

範例:發送 Oracle Tablespace 可用容量的 e-mail 。

declare
 --宣告
  l_mail_conn   UTL_SMTP.connection;
  l_boundary    VARCHAR2(50) := '----=*#abc1234321cba#*=';
BEGIN
 --定義郵件主機,不能用 IP 只能用 host name,可以在 /etc/hosts 增加
  l_mail_conn := UTL_SMTP.open_connection('mailserver', '25');
  UTL_SMTP.helo(l_mail_conn, 'mailserver');

  --寄件者
  UTL_SMTP.mail(l_mail_conn, '4shiun@gmail.com');

  --多個收件者
  UTL_SMTP.rcpt(l_mail_conn, '4shiun@gmail.com');

  UTL_SMTP.open_data(l_mail_conn);

  --以 HTML 方式來傳送,定義收件者和寄件者名稱
  UTL_SMTP.write_data(l_mail_conn, 'Date: ' || TO_CHAR(SYSDATE, 'DD-MON-YYYY HH24:MI:SS') || UTL_TCP.crlf);
  UTL_SMTP.write_data(l_mail_conn, 'To: ' || 'Adam' || UTL_TCP.crlf);
  UTL_SMTP.write_data(l_mail_conn, 'From: ' || 'Adam' || UTL_TCP.crlf);
  UTL_SMTP.write_data(l_mail_conn, 'Subject: ' || 'Tablespace Information' || UTL_TCP.crlf);
  UTL_SMTP.write_data(l_mail_conn, 'Reply-To: ' || 'Adam' || UTL_TCP.crlf);
  UTL_SMTP.write_data(l_mail_conn, 'MIME-Version: 1.0' || UTL_TCP.crlf);
  UTL_SMTP.write_data(l_mail_conn, 'Content-Type: multipart/alternative; boundary="' || l_boundary || '"' || UTL_TCP.crlf || UTL_TCP.crlf);

  UTL_SMTP.write_data(l_mail_conn, '--' || l_boundary || UTL_TCP.crlf);
  UTL_SMTP.write_data(l_mail_conn, 'Content-Type: text/html; charset="iso-8859-1"' || UTL_TCP.crlf || UTL_TCP.crlf);

  --把 Tablespace 的資料用 Table 來顯示
  UTL_SMTP.write_data(l_mail_conn, '<table border=1 width=800px>');
  UTL_SMTP.write_data(l_mail_conn, '<TR align=center><TD>tablespace_name</TD><TD>free</TD><TD>used</TD><TD>total</TD><TD>used_percent</TD><TD>free_percent</TD></TR>');
  for i in(select a.tablespace_name,to_char(b.free,'fm999,999,999,999') free,to_char(a.total-b.free,'fm999,999,999,999') used,to_char(a.total,'fm999,999,999,999') total,to_char(((a.total - b.free)/a.total)*100,'999.99')||'%' used_percent,to_char((b.free/a.total)*100,'999.99')||'%' free_percent from (select tablespace_name,sum(bytes) total from dba_data_files group  by tablespace_name)a,(select tablespace_name,sum(bytes) free from dba_free_space group by tablespace_name) b where a.tablespace_name= b.tablespace_name order by 1)
      loop
      UTL_SMTP.write_data(l_mail_conn, '<TR align=right><TD align=left>'||i.tablespace_name||'</TD><TD>'||i.free||'</TD><TD>'||i.used||'</TD><TD>'||i.total||'</TD><TD>'||i.used_percent||'</TD><TD>'||i.free_percent||'</TD></TR>');
      end loop;
  UTL_SMTP.write_data(l_mail_conn, '</table>');
  UTL_SMTP.write_data(l_mail_conn, UTL_TCP.crlf || UTL_TCP.crlf);

  UTL_SMTP.write_data(l_mail_conn, '--' || l_boundary || '--' || UTL_TCP.crlf);
  UTL_SMTP.close_data(l_mail_conn);

  UTL_SMTP.quit(l_mail_conn);
END;

發送 e-mail 當然也可以把 Blob 欄位的資料當成附件的方式來傳送。
要注意的是,如果 mail server 有認證才能發送 e-mail 的話,就需要再加上帳號和密碼,
發送的 e-mail 如果是中文的話,HTML 的文字編碼也需要修改。
建議是把此 SQL 指令寫成 PROCEDURE 的方式方便執行。

2013年4月2日

SQLCA.SQLCODE 和 SQLCA.SQLERRD 和 STATUS 錯誤碼查詢

要判斷程式在執行的時候,SQL 指令回傳結果正確與否,
在 4GL 就提供以下三個變數來使用:

SQLCA.SQLCODE:
執行 SQL 指令後,回傳 Informix 4GL 所表示的 Error Code。

SQLCA.SQLERRD:
SQLERRD[1]~SQLERRD[6],SQLERRD[2] 表示資料庫的錯誤訊息,SQLERRD[3] 表示執行的 row 數,其他特別功能使用就不參考。

STATUS:
通常會等於 SQLCA.SQLCODE ,但是多了系統的錯誤碼,所以執行 SQL 指令後系統 I/O 錯誤時就會顯示另一個錯誤碼。

程式執行時會回傳 SQLCA.SQLCODE 訊息碼,有以下三種情況:
0 表示執行成功。
100 表示成功但無資料,所以當 Update 沒有符合條件的資料可以用。
<0 負數表示有錯誤。

當訊息碼為負數時,表示為錯誤訊息,但是我們要怎麼查是什麼錯誤,
只要在命令環境下,執行 finderr 錯誤碼 就會出現錯誤的說明了。

例:執行 finderr -201 會出現以下訊息,英文應該大概上就知道什麼涵義:
-201    A syntax error has occurred.

This is the general error code for all types of mistakes in the form of an SQL statement. Look for missing or extra punctuation (for example, missing or extra commas, omission of parentheses around a subquery, etc.), keywords misspelled (for example VALEUS for VALUES), keywords misused (for example, SET in an INSERT statement, INTO in a subquery), keywords out of sequence (for example a condition of "value IS NOT" instead of "NOT value IS"), or the use of a reserved word as an identifier.
Note: Database servers that support "full NIST compliance" do not reserve any words; queries that work with these database servers may fail with error -201 when used with earlier implementations.

不過因為 SQLCA.SQLCODE 是 Informix 在 4GL 所規範出來的錯誤訊息,所以比較籠統的方式來呈現,舉例 -201 表示語法錯誤,但是沒辦法詳細說明錯誤在那,因此在 Debug 時可以用 SQLERRD[2] 就可以知道 Oracle 的 ORA 的錯誤碼,比較快速的知道語法是那裡的錯誤。

要得知 SQL 執行的筆數 SQLCA.SQLERRD[3] 就可以知道 SQL 指令所處理的筆數,在程式上也可以多加利用。

另外 Genero 還有提供二個指令,要注意不是變數型態的 SQL Error Code 而是指令,所以 Debug 不能顯示內容,
要先 LET 到變數或是 DISPLAY 才看的到,也是可以參考使用,功能和 SQLERRD[2] 相同。

SQLSTATE:
回傳 ANSI SQLSTATE 錯誤碼,現在應該所有商用資料庫都有支援 ANSI SQL。

SQLERRMESSAGE:
回傳資料庫的錯誤碼和錯誤的說明。

2013年3月26日

建立 Oracle 觸發 Trigger 來 Debug

有時候系統會到遇到靈異事件,
經常發生還容易查,用 r.d2+ 就大概可以 debug 出來問題在那,
偶發情況然後又只要重新再做一次又正常了,不知道到底是時間差、還是資料的問題,或是程式的 bug。
系統又沒有記錄的話,使用者刪除或修改資料是不會承認的,都是朝系統的問題來處理。

只好建立 Trigger 來記錄資料的異常歷史記錄了,看到底是誰搞的鬼。
在 TIPTOP 有提供 log_file 和 aooq040 提供 Trigger log 的查詢,但是 Trigger 要自已在 Oracle 建立。

Trigger 的語法:
CREATE OR REPLACE TRIGGER 觸發名稱
BEFORE INSERT OR UPDATE OR DELETE OF 觸發的欄位 ON 觸發的表
AFTER INSERT OR UPDATE OR DELETE OF 觸發的欄位 ON 觸發的表
FOR EACH ROW
記錄異動的資料
END;

bmd_file 異動資料的範例,把異動的欄位記錄在 log_file:
create or replace trigger bmd_file_log
--只記錄4 個欄位的異動
after insert or update or delete of bmd01,bmd02,bmd03,bmd04,bmd08 on bmd_file
for each row
declare l_col varchar2(3);
        l_user varchar2(6);
        l_type varchar2(20);
        l_cnt  number;
begin
l_col :='';
--先找有沒有資料,當執行失敗時 trigger 就會中止,所以要先找是否有資料,SID 是唯一值,SESSION有可能重複。
select count(*) into l_cnt from v$session,gbq_file where process=gbq01 and sid = userenv('sid') and process not like '%:%';
if l_cnt > 0 then
--把TIPTOP使用者和程式代號找出來
   select gbq03,gbq04 into l_user,l_type from v$session,gbq_file where process=gbq01 and audsid = userenv('sessionid') and process not like '%:%';
else
--把非TIPTOP的使用者和程式找出來
   l_user:=sys_context('userenv','os_user'); l_type:=substr(sys_context('userenv','module'),1,10);
end if ;
--新增
if inserting then
   l_col :='ins';
   insert into log_file values('bmd_file',:new.bmd01,:new.bmd02,:new.bmd03,:new.bmd04,:new.bmd08,l_col,sysdate,l_user,l_type,'','');
end if;
--修改就記錄修改前和修改後
if updating then
   l_col :='upd';
   insert into log_file values('bmd_file',:old.bmd01,:old.bmd02,:old.bmd03,:old.bmd04,:old.bmd08,l_col||'-old',sysdate,l_user,l_type,'','');
   insert into log_file values('bmd_file',:new.bmd01,:new.bmd02,:new.bmd03,:new.bmd04,:new.bmd08,l_col||'-new',sysdate,l_user,l_type,'','');
end if;
--刪除
if deleting then
   l_col :='del';
   insert into log_file values('bmd_file',:old.bmd01,:old.bmd02,:old.bmd03,:old.bmd04,:old.bmd08,l_col,sysdate,l_user,l_type,'','');
end if;
end;

要刪除 TRIGGER ,就用 drop trigger 名稱,就可以刪除了。

2013年3月25日

更便利的 DIALOG - 同時單頭單身查詢/輸入

剛開始使用 TIPTOP 很多人都會問為什麼查詢要按二次確定,
需要單頭按確認後再到單身再按確認,才會執行查詢的動作,
用 DIALOG 就可以同時做單頭、單身的查詢。

再來操作上不方便的就是,像工單發料單 asfi510 或多主件工程變異單 abmi710 ,
像這種多單身的樣式,就不能同時將二個單身做 scroll bar 的捲動和資料不能複製到剪貼簿,
一般都會再做一個按鈕查詢另一個單身,然後操作完後要再跳回去原本的單身,實在是很不方便。
就是可以用 DIALOG 來達到比較人性化的介面。

在 Genero 官方文件說明:
The DIALOG block is an interactive instruction that executes multiple kinds of sub-controllers simultaneously to drive different parts of a form.

只要將 CONSTRUCT 和 CONSTRUCT ARRAY 都包在 DIALOG 裡面,就可以做到單頭和單身同時查詢。
將二個不同的 DISPLAY ARRAY 也包在 DIALOG 裡面,也一樣可以同時操作這二個單身的 Action 和 Scroll Bar。

DIALOG ATTRIBUTES(UNBUFFERED)
     DISPLAY ARRAY g_bmz to s_bmz.* ATTRIBUTE(COUNT=g_rec_b)
     END DISPLAY
     DISPLAY ARRAY g_bmy to s_bmy.* ATTRIBUTE(COUNT=g_rec_d)
     END DISPLAY
END DIALOG

DIALOG 提供 INPUT、CONSTRUCT、DISPLAY ARRAY、INPUT ARRAY 的多重控制功能,
利用這個功能就可以做到單頭、單身輸入完資料後,才會產生單號和同時寫入資料到 TABLE,
作廢的單子或許就可以少了一些。

要判斷目前 DIALOG 目前控制在那一個的話,提供一個函式 DIALOG.getCurrentItem() 回傳就是畫面的變數值。
這樣多單身就匯出 Excel 就可以判斷要匯出是那一個單身的資料。

官方網站提供其中一個例子做動態的資料查詢,單頭輸入條件後單身就馬上顯示出資料,
原本是寫在 AFTER FEILD 後加上 DISPLAY ARRAY 也是可以做到,但是缺點就是資料的捲軸沒辦法動。
用 DIALOG 就可以做到像 Windows 應用程式類似的作業,盡可能用 DIALOG 來開發程式吧!!

2013年3月15日

回復 Oracle 刪除或是已更新的資料

我們都知道還原資料最多就是只能還原到最近一前的備份點,
但是備份不可能是隨時隨地都在備份,
Oracle 有一個 undo 的機制,會將資料進行異動之前就進行 Data Image,
原本的用意為 A 進行 Query 之後 B 又進行 Update 時,A 的 Query 結果會是當時下達指令時點的資料,
就不會被 B 在 Update 時所影響,也不會造成資料不一致。
因此我們就可以利用此功能回復到你所想要的某一時間的資料。
undo 區分為 ACTIVE、UNEXPIRED、EXPIRED 三種,
當資料 commit 或 rollback 後就會改變為非活動中的狀態。

查看目前 undo 的狀態:
select * from DBA_UNDO_EXTENTS

利用 Oracle 的 undo log 來查到底是誰把資料做刪除或變更的,
使用 LogMiner 來開啟 undo log 或 archived log 檔案,就可以查出 SQL_TEXT 和 Session ,
就知道到底是誰幹的好事。

先來看 Oracle 的參數:
sqlplus: show parameter undo;

select * from v$parameter where name like '%undo%'

NAME                   VALUE
--------------------------
undo_management  AUTO
undo_tablespace     UNDOTBS1
undo_retention        900

undo_retention 保存時間(秒) 900 秒,
設定 undo_retention 越大就會佔用更多的 undo tablespace 的空間。
要是 undo 空間不足時,才會把 undo_retention 以前的資料蓋掉,
當 undo tablespace 設定 RETENTION GUARANTEE 時,
還是不夠 undo 空間就會自動擴展,增加 undo tablespace 檔案容量,
必須要保証磁碟空間是足夠的,當有大量資料異動時,會造成 undo 檔案過大。
設定為 RETENTION NOGUARANTEE , undo tablespace 不會擴增,
當容量不足時就會一直把非活動的空間蓋掉,所以 undo_retention 不保証一定會保存完整資料。

將 undo_retention 改為 1 小時,指令如下:
alter system set undo_retention = 3600

修改 undo tablespace 的屬性,開啟 retention 自動擴展:
ALTER TABLESPACE undotbs1 RETENTION GUARANTEE

關閉 retention 自動擴展:
ALTER TABLESPACE undotbs1 RETENTION NOGUARANTEE

預設 undo_management 都是 auto 管理,
undo_retention 的值會由 Oracle 自動進行最佳的調整,
所以超過 undo_retention 的時間,也有可能狀態還是在 UNEXPIRED 。
因為 Oracle 每 10 分鐘就會依 undo tablespace 的空間和 Oracle 操作狀況來設定最佳的 undo_retention 值。

查看 Oracle 所設定的 undo_retention (秒)
SELECT * FROM v$undostat;

要如何將資料還原,只要加上 timestamp 就可以查到當時的時間點上資料內容。
select * from ds.zx_file as of timestamp to_timestamp('2013-03-15 15:55:29', 'yyyy-mm-dd hh24:mi:ss');

查出來結果應該會很開心,好險資料還在。
再來就是把資料還原就可以了,一般都是用 create table 的方式,再比對資料後做更新。
create table ds.zx_temp as
select * from ds.zx_file as of timestamp to_timestamp('2013-03-15 15:55:29', 'yyyy-mm-dd hh24:mi:ss');

scn (System Change Number) 當資料異動時,就會將 scn + 1 保存在 undo log 。
比較建議還是用 scn 會比較避免同一秒的資料異動時,資料還原的完整性。
再來就是 scn 和 timestamp 在 9i 會有 5 分鐘的資料同步,
所以 9i 用 timestamp 進行 flashback query 時會查不到 5 分鐘內異動的資料。
在 10g 以後的版本就修正此情況, scn 和 timestamp 會在 LogMiner 取得。

改用 scn 的方式來查詢:
select * from ds.zx_file as of scn 2267300000

查詢最近一次資料異動的 scn 和 timestamp :
select scn_to_timestamp(dbms_flashback.get_system_change_number) ,dbms_flashback.get_system_change_number from dual;

雖然 Oracle 這個機制可以讓我們放心資料異動的問題,但還是有限制:
1. Table Schema 變更就不能回復,因 flashback query 是用目前的資料字典。
2. 不能 flashback 到 5 天前的資料。
3. 不能保証 undo_retention 所保存資料是否完整,undo tablespace 過小會蓋掉舊的。
4. drop、truncate 不需 commit ,所以就不能回復,所以要把 truncate 和 drop 指令鎖住。

一般的帳號也可以使用 flashback 的功能,設定權限:
grant execute on dbms_flashback to USER ;

想要看某筆資料所有異動的紀錄,在 Oracle 有 versions 的指令來查看每個版本的資料和起始、結束時間
欄位 versions_starttime 表示版本開始時間,欄位 versions_endtime  表示版本結束時間
使用方式只要在 table 後面加上 versions between timestamp minvalue and maxvalue 就可以了。
範例:select zx_file.*,versions_starttime,versions_endtime from ds.zx_file versions between timestamp minvalue and maxvalue

2013年2月22日

設定報表排程自動發送 Mail 給相關人員

應該是從 TIPTOP 5.2 版之後就有 CR 發送 mail 的功能,
這樣就可以讓系統將異常檢查報表每天都寄給相關人員,
IT 人員也就輕鬆很多了。

系統的運作是:
TIPTOP 主機執行背景程式 → 呼叫 CR 主機報表 → 執行結果存在 CR 主機的 temp 目錄 →
CR 主機將 temp 目錄的檔案發送 mail 。

執行報表後會有約 5 分鐘延遲時間。
執行過報表也都會存在 CR 主機的 temp 目錄,看要不要固定時間清理一下。
因 CR 報表 IIS 為匿名的驗証方式,所以必需將 temp 的目錄改為 everyone 讀取。

首先要先設定 aooi998 Mail 收件人維護作業,
也可以選擇要寄送的是 PDF、Word、Excel …等。

再來就是在 p_cron 建立排程,
通常報表程式的參數五 g_prtway 表示報表的執行結果的顯示方式,
參數四表示是背景作業。
只要參數五設定為 A ,然後背景作業設定為 Y ,
這樣就會自動寄報表的到相關人員的 E-mail 了。

或是只要加上三個變數也可以有寄送 Mail 的功能。
CALL FGL_SETENV("MAIL_TO",g_receiver)
CALL FGL_SETENV("MAIL_CC",g_cc)
CALL FGL_SETENV("MAIL_BCC",g_bcc)
變數的格式為:
收件者名稱:收件者 E-Mail:格式(1.PDF,2.RPT,3.DOC,4.XLS,5.XLS,6.XLS)

2013年2月6日

畫面能夠依資料內容來變更文字顏色、樣式

TIPTOP的畫面設計比較偏向 TEXT-Mode ,所以會以比較單調樣式來呈現,
現在應該沒有人在用這種方式了吧,都是用 GUI 的方式來使用。
這樣就可以添加一些色彩、底線、粗體,來增加使用者操作上資料的辦識能力。

依 4js 的說明,在 DISPLAY 可以加上屬性,如下:
1. BLACK, BLUE, CYAN, GREEN, MAGENTA, RED, WHITE, YELLOW – 顯示顏色
2. BOLD, DIM, NORMAL – 顯示字體樣式
3. REVERSE, BLINK, UNDERLINE – 顯示文字的屬性

屬性的字面上的意思就能知道用途了,多重屬性時只要加上逗號來區隔,就不多加說明了。

例如我們想要 sfb01 這個欄位在某些情況下顯示藍色時,只要加判斷再加上 ATTRIBUTE,
例:
IF g_sfb01.sfb04 = '8' THEN
    DISPLAY BY NAME g_sfb01.sfb01 ATTRIBUTE(BLUE)
ELSE
    DISPLAY BY NAME g_sfb01.sfb01 ATTRIBUTE(BLACK)
END IF

鼎新也有提供 lib 的函式來改變顏色:
CALL cl_set_comp_font_color("sfb01,sfb02","RED")

但是如果是單身的話會用 DISPLAY ARRAY 就只能所有單身全部的顏色都變一樣,
要改用 DIALOG.setCellAttributes 的方式來顯示欄位的屬性。
例:
要先定義單身有那些是要動態變更欄位屬性的陣列變數:
DEFINE g_gen_attr DYNAMIC ARRAY OF RECORD
        gen01       STRING,
        gen02       STRING,
        gen03       STRING
END RECORD

記得要先做 g_gen_attr.clear()。

在 fill() 函數時 FOREACH 的時候判斷:
IF g_gen[g_cnt].gen03 IS NULL TEHN
    LET g_gen_attr[g_cnt].gen01 = 'MAGENTA REVERSE'
ELSE
    LET g_gen_attr[g_cnt].gen01 = ''
END IF

再來就是 DISPLAY ARRAY 顯示的時候依定義好的屬性來顯示:
BEFORE DISPLAY
         CALL cl_navigator_setting( g_curs_index, g_row_count )
         CALL DIALOG.setCellAttributes(g_gen_attr)    #加上這一句

這樣單身也可以依資料來變化顏色了。

2013年1月30日

增加是否有相關文件的圖示

查詢資料的時候,都看不出來那些是有附上相關文件的檔案,
需使用者點擊相關文件 action ,再看有沒有資料才知道有沒有附上去。


我們可以參照 E-Mail 的樣式,只要在程式上面加上一個迴紋針的圖示,
就知道那些單子有沒有相關文件了。

首先要把圖先上傳,一般是上傳到 /u1/topprod/tiptop/doc/pic 目錄,
TIPTOP 的圖示是透過 web 的方式,將網址傳給 GDC ,
然後 GDC 再依 HTTP 網址抓取圖片來顯示。

程式的部份,比照 cl_set_pic1 函式的寫法就差不多了,
因 cl_set_pic1 的畫面變數一定要命名為 imgmksg ,
要顯示是否有相關文件的圖示的話,就必須要另外寫程式了。

以 aqct110 當範例:

FUNCTION t110_show_attache()
   DEFINE   l_cnt              LIKE   type_file.num5,
            ls_sql             STRING,
            l_wc               STRING
   DEFINE   ls_pic_url         STRING,
            ls_imgmksg         STRING
   DEFINE   lr_key       RECORD
               gca01 LIKE gca_file.gca01,
               gca02 LIKE gca_file.gca02,
               gca03 LIKE gca_file.gca03
            END RECORD
           
   IF g_qcs.qcs01 IS NOT NULL THEN
      LET l_cnt = 0
      LET g_doc.column1 = "qcs01"
      LET g_doc.column2 = "qcs02"
      LET g_doc.column3 = "qcs05"
      LET g_doc.value1 = g_qcs.qcs01
      LET g_doc.value2 = g_qcs.qcs02
      LET g_doc.value3 = g_qcs.qcs05
   
      LET lr_key.gca01 = g_doc.column1 CLIPPED || "=" || g_doc.value1 CLIPPED
      LET lr_key.gca02 = g_doc.column2 CLIPPED || "=" || g_doc.value2 CLIPPED
      LET lr_key.gca03 = g_doc.column3 CLIPPED || "=" || g_doc.value3 CLIPPED
   
      LET l_wc = "gca01 = '", lr_key.gca01 CLIPPED, "' AND gca02 = '", lr_key.gca02 CLIPPED,
                         "' AND gca03 = '", lr_key.gca03 CLIPPED, "'  AND gca04 = ' ' AND gca05 = ' '"
   
      # 是否有相關文件的紀錄
      LET ls_sql = "SELECT COUNT(*) FROM gca_file WHERE ", l_wc,
                   " AND gca08 IN ('DOC','TXT') AND gca11 = 'Y' ORDER BY gca06"
   
      PREPARE t110_doc_p FROM ls_sql
      DECLARE t110_doc_d CURSOR FOR t110_doc_p
   
      OPEN t110_doc_d
      FETCH t110_doc_d INTO l_cnt
      # 有的話顯示迴紋針圖檔,不然就顯示空白的圖檔,
      #因為下一筆可能沒有要給他一個空白來更新畫面
      IF SQLCA.SQLCODE = 0 AND l_cnt > 0 THEN
        LET ls_pic_url = FGL_GETENV("FGLASIP") || "/tiptop/pic/"
        LET ls_imgmksg = ls_pic_url.trim() || "attache.jpg"
        # 傳送網址給畫面變數  imgattch 
        DISPLAY ls_imgmksg TO imgattach
      ELSE
        LET ls_pic_url = FGL_GETENV("FGLASIP") || "/tiptop/pic/"
        LET ls_imgmksg = ls_pic_url.trim() || "space_0.gif"
        DISPLAY ls_imgmksg TO imgattach
      END IF
   END IF
END FUNCTION

最後只要再 show 的 Function 加 CALL t110_show_attache() 就可以了。

2013年1月25日

指定畫面單身資料匯出 Excel

TIPTOP 提供一個的 Library 可以將畫面單身的資料匯出到 Excel ,
例如:CALL cl_export_to_excel(ui.Interface.getRootNode(),base.TypeInfo.create(g_sfq),'','')

但是有程式在視窗上是有二個單身,像是工單發料、工單退料、付款沖帳、收款沖帳…等,
或是有程式會開啟另一個視窗的明細資料,像出貨單的單價,應付帳款的多發票…等。
同一支程式會呼叫多個畫面或是有多單身的情況時,
用 cl_export_to_excel 就會發現,怎麼匯出到 Excel 的資料是對的,但是欄位的名稱是錯的。
造成匯出來就會不整齊,不會依序排好。
因為 ui.Interface.getRootNode() 都是抓第一個顯示的單身來當作匯出的資料陣列。
所以我們就要把 ui.Interface.getRootNode() 改成正確的 node (節點)。
TIPTOP 並非 MDI 視窗架構,所以一個 Windows 只會有一個 Form。

畫面的架構:
UserInterface
 |--Windows
 |        |-- Form
 |        |        |--Table
 |        |--Dialog
 |--TopMenu
 |--ToolBar

如下圖所示:


參考 4js 網站說明,提供 ui.window 的函數說明:
CLASS Method:
forName( name STRING ):回傳目前是否有開啟 name 視窗,並傳回位置
getCurrent():回傳目前的視窗位置

OBJECT Method:
findNode( t STRING, n STRING ):找尋類型為 t,且名稱為 n 的節點
createForm( n STRING ):開啟一個新的畫面,且傳回新畫面的位置
getForm():回傳目前畫面的位置
getNode():回傳目前節點的位置
setText( t STRING ) :設定視窗的標題名稱
getText():回傳視窗的標題名稱
setImage( n STRING ):設定視窗的 icon 圖示
getImage():回傳視窗的 icon 圖示

首先要先宣告二個變數:
DEFINE w ui.Window
DEFINE n om.DomNode

再來就是要取得目前的視窗和要匯出 Excel 的 Node:
如果是新的視窗來顯示的話,只要用 getNode 即可,
LET w = ui.Window.getCurrent()
LET n = w.getNode()

如果是多單身的視窗,就要告訴系統是那一個單身 Node:
LET w = ui.Window.getCurrent()
LET n = w.findNode("Table","s_sfs")

然後再修改  cl_export_to_excel 把 ui.Interface.getRootNode() 改為 n 就可以了,
CALL cl_export_to_excel(n,base.TypeInfo.create(g_sfs),'','')

SQL 語法將群組的資料全部列出來

我們都知道 GROUP 可以用 AVG、SUM、COUNT、MIN、MAX…等,
可以做數字、日期或是字串的運算。

但是如果群組不想要運算,要合起來全部都列出來呢 ?
通常我們就用程式跑迴圈的方式來將資料連接成一個字串。
我們知道 Oracle 有一個函數叫 concat 也就是將二個字串連接在一起,
這時候也可以用在 group 囉~~

介紹一個函數 wm_concat ,但重複資料不會排除,所以再加上 distinct 就更完美了。
欄位內容會以逗號來分隔。
不想用逗號來分隔的話,就只能用 replace 的方式來取代了。

範例:顯示部門內的所有員工資料。
SELECT gen03,wm_concat(gen01) FROM gen_file
GROUP BY gen03
ORDER BY 1

要注意,wm_concat 的欄位是以 CLOB 格式來呈現的,
所以如果有建立 view 或是直接在 p_query 使用的話,記得要轉成 VARCHAR 的格式。
原因就是 p_query 所有欄位都是依照 gaq_file.gaq03 欄位來宣告的。
用 CAST 將 CLOB 改為 VARCHAR 的格式。
範例:
SELECT gen03,cast(wm_concat(gen01) as varchar(255)) FROM gen_file
GROUP BY gen03
ORDER BY 1

另一個需注意就是 wm_concat 的資料是不能排序,
所以有可能資料所列出來的順序會不一樣。必需改為先 wm_concat 合併再 GROUP來處理。
範例:
SELECT gen03,max(cast(gen01 as varchar(255))) FROM (
SELECT gen03,wm_concat(gen01) OVER (PARTITION BY gen03 ORDER BY gen03,gen01) gen01 FROM gen_file
)
GROUP BY gen03

2013年1月23日

當站下線的成本該如何計算?

當站下線的意思就是,工單生產的途中要進行在製品的入庫。
可能是要抽原料來變更生產料號,可能是設計變更換新料號生產…情況。
因已經在製程中生產了,用挪料、退料的方式也是可行,
但所投入的人工、製費在成本計算,就會造成舊工單結案變調整金額,
新工單的在製金額也會低估,就不會是那麼的恰當。
需要進行在製成本的調整,料件的單位成本才能夠準確。
所以當站下線的料號就必須要再編一個新的料號,一般都是工單生產的料號+製程碼。

我們瞭解了當站下線的意思,報工有當站下線時製程移轉和 WIP 量是不會有問題的,
比較有問題的就是工單是不會自動結案,
另一個就是下線的庫存單位必須要和工單製程的轉出單位要一樣。
但是成本計算時在製金額是如何表示的呢?

在 TIPTOP 系統當站下線是以退料的方式來呈現的,
也就是在製的本期投入的數量和金額為負數,
和正常完工入庫是在轉出數量和金額概念是不同的。
再加上成本計算時沒辦法知道當站下線的金額是多少,所以我們就要附予單價啦~~

當站下線在成本計算前要先在 axct510 先填入材料、人工、製費、加工的單價,
然後要注意一點,如果是同月當站下線所入庫當月又被另一工單領用接下去生產的時候,
則需要在 axct001 庫存開帳補上此料件的前期單價資料,避免領用當線下線的料號單價為 0 ,
因 axcp500 執行的先後順序的關係,當站下站是退料並非入庫而計算月加權平均,
所以工單領用的單價就抓不到的關係。

最後成本計算後檢查相關資料時,
當站下線和拆件工單在 axcr370 成本計算後勾稽報表都會出現本期投入金額為負的錯誤訊息,
就把他當作是正常吧~~~

2013年1月17日

開啟 GDC 的 Debug 模式

找系統問題的時候,需要找畫面的欄位對應到資料庫那一個 Column、
action 按鈕對應是 4gl 程式那一個 action 名稱…等,這些除錯常常會遇到。

GDC 有提供一個 Debug 的功能,可以直接看到目前視窗所有欄位或按鈕的變數值 or 名稱。

1. 在程式集的捷徑加上參數 -aD (-M 是最小化開啟)。


GDC 就會多出二個按鈕。


2. 使用 ssh 或是 telnet 的方式來登入 TIPTOP,開啟你所要的畫面。

3. 將滑鼠游標移到你要看的上方,然後按下組合鍵 Ctrl + 滑鼠右鍵,就出現 Debug 視窗。
    然後你所點選的地方也會有紅色的標示框在那閃動著,就可以快速知道 action 的名稱。
    也試試看看其他欄位,可以顯示欄位的變數值喔~





讓 TIPTOP 下拉式選單可以動態的顯示

在 Genero Studio 設計 ComboBox 下拉式選單的時候,
通常都是直接在 4fd 檔上面定義固定的 item 項目。
然後再到 p_perlang 修改多國語言顯示的名稱。

提供一個方式可以讓 ComboBox 物件可以隨著其他欄位的變化,來改變下拉式選單的項目。
就是用 ui.ComboBox 的方法。

參考 Genero 的說明文件,ui.ComboBox 提供下式的 Function。
CLASS Method:
1. forname :物件名稱

2. setDefaultInitializer:設定初始的項目

OBJECT Method:
1. addItem(name,text) :增加項目,name 是值,text 是顯示名稱

2. getColumnName():回傳欄位的名稱

3. getIndexOf(name):回傳 name 是在項目的第幾個,沒有回傳 0

4. getItemCount():回傳項目的數量

5. getItemName(index):回傳第 index 個的項目 name 值

6. getItemText(index):回傳第 index 個的項目 text 值

7. getTableName():回傳欄位的 table_name 或是 FORMONLY

8. getTag():回傳欄位的 tag 值

9. getTextOf(name) :回傳 name 是在項目的第幾個的 text 值,沒有回傳 null

10. removeItem(name):刪除名稱是 name 的項目

11. clear():清除所有的項目

範例1:ComboBox 依開啟時可以顯示 1~5 的項目選單
方法1:
1. 在 4fd 檔中的 ComboBox 欄位 Initializer 屬性加上 cb_load 字串

2. 在 4gl 檔中增加一個 Function
    FUNCTION cb_load(combox)
         DEFINE combox ui.ComboBox
         LET combox = ui.ComboBox.forname("ima12")
         CALL combox.clear()
         FOR g_num = 1 TO 5
             CALL combox.addItem(g_num ,g_num)
         END FOR
    END FUNCTION

方法2:
1. 在 OPEN WINDOW 之前加上:
     CALL ui.ComboBox.setDefaultInitializer("cb_load")

2. 同上增加 cb_load 的 Function

範例2:ComboBox 可以隨著某個欄位所選擇的,列出對應的選單
 1. 新增 cb_load 的 Function
    FUNCTION cb_load(combox)
         DEFINE combox ui.ComboBox
         DEFINE l_azf01   LIKE  azf_file.azf01,
                       l_azf03    LIKE azf_file.azf03
         LET combox = ui.ComboBox.forname("ima12")
         DECLARE ima12 CURSOR FOR
            SELECT azf01,azf03 FROM azf_file
                WHERE azf02 = g_ima.ima12
         FOREACH ima12  INTO l_azf01,l_azf03
                 CALL combox.addItem( l_azf01,l_azf03)
         END FOREACH
    END FUNCTION

2. 然後再 INPUT 段的 BEFORE FIELD 欄位的時候,呼叫就可以變化了
    BEFORE FIELD ima12
           CALL cb_load("")

當欄位 ima12 選擇是那一個的時候,跳到 ComboxBox 欄位的時候,
就會帶出不同的下拉式選單

總結:在 Genero 的 ComboBox 有一些可惜的地方,只能在查詢的時候才能夠輸入資料
(要開啟 queryEditabile ),不能提供新增的時候可以輸入選單以外的值。
再來就是如果是用此方式的話,多國語言的功能就不能再維護了,
如果是帶 Table 的資料多國語言就沒問題,但是是自定的話,當然也可以用 p_ze 來定義啦~~~
要注意的另一點,如果下拉式選單因其他欄位的變化而造成值是相同但是顯示的名稱不同,
再查詢的時候就要再多判斷要帶出來的顯示名稱,並上下筆做查詢的時候也要判斷。

Genero 的檔案管理指令

需要檔案管理的時候,都要再查一次 Genero 的文件,
所以就把常用的指令都整理一下啦~~

其實就是可以用 Genero 進行檔案或資料夾的新增、修改、刪除,或是權限設定的功能。
有時候報表的資料太多,或是其他系統做資料交換的時候,就可以轉文字檔的方式來匯出。

首先要記得在程式最上方加上 Library:
IMPORT os

再來利用 CALL Function RETURNING value 的方式來使用。
以下就是常用的 Function:

1. os.Path.separator() :路徑的系統符號,傳回 / 或是 \ 符號

2. ps.Path.pathseparator() :磁碟的系統符號,傳回  : 或是 ; 的符號

3. os.Path.exists(filename) :傳回 true 或是 false

4. os.Path.basename(Fullname) :去除路徑只回傳檔案名稱

5. os.Path.dirname(Fullname) :去除檔案名稱只回傳路徑

6. os.Path.rootname(filename):回傳路徑+檔案名稱

7.  os.Path.extension(filename):回傳檔案的 extension

8.  os.Path.join ( "/u1", "topprod") :回傳 /u1/topprod

9.  os.Path.chrwx(file,511) :變更檔案的權限,回傳 true 或是 false,十進位的 511 = 八進位的 777

當然也可以不要用 os 的 Library ,大多的指令也是直接也可用 RUN 的指令來達成。

例:
LET l_cmd = "chmod 777 ", filename
RUN l_cmd