Files
Dynamic-Form-Tools/DevResources/full-examples/bdgt04/view/frm/bdgt-0401010.frml
skidus f705cd11b9 feat: implement advanced bidirectional field referencing and cross-module path resolution
- Core Logic Enhancements:
    - Implement bidirectional field referencing between <FIELDS>, <LAYOUT>, and <TITLES> tags in .frml files, enabling seamless navigation from definitions to usages and vice versa.
    - Add robust support for AJAX-OPTION field mapping:
        - SRC attribute: Links to field definitions within defs/ajax.xml datasets.
        - TARGET attribute: Links to local field definitions within the same form.
    - Implement global grid resolution: GRID-ID now searches across the current file and all recursively included files (<INCLUDE>).
    - Enhance deep recursive search for fields/sections within nested tags like <SECTION>, <ROW>, and <FIELD-LIST>.

- Path Resolution & Helpers (DynFormPathUtils):
    - Added support for module-relative paths starting with # (mapping to view/frm/).
    - Added support for cross-module paths starting with / (mapping to WEB-INF/app/module/{module}/view/frm/).
    - Implemented auto-correction for common keyboard typos (Thai 'ิ' instead of /).
    - Added specialized helpers for locating ajax.xml and included files within the framework's structure.

- Smart Completion Enhancements:
    - Added context-aware completion for TARGET and SRC fields in AJAX update-fields.
    - Enabled global GRID-ID completion by scanning all included resources.
    - Improved dataset completion to include both local and AJAX-defined datasets.

- Test Resources:
    - Added a comprehensive set of real-world examples (bdgt04, bdgt05, bdgt06) in DevResources/full-examples/ to validate complex cross-module and master-detail scenarios.
2026-04-10 12:56:04 +07:00

250 lines
9.0 KiB
XML

<?xml version="1.0" encoding="UTF-8"?>
<FORMS xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="xsd/#dynf_form_def.xsd">
<INCLUDES>
<INCLUDE FILE="#grids/grid-construct-budget.frml"></INCLUDE>
</INCLUDES>
<DATASETS>
<DATASET ID="DS-MASTER">
<SCHEMA>APP</SCHEMA>
<TABLENAME>PROJECTS</TABLENAME>
<KEYFIELDS>PROJ_ID</KEYFIELDS>
<SQL>
<SELECT>SELECT PROJ.PROJ_ID
, PROJ.PROJ_TYPE
, PROJ.PROJ_YEAR
, PROJ.PJM_CODE
, PROJ.STM_CODE
, PROJ.BTM_CODE
, PROJ.ACM_CODE
, BUD.GET_ACTIVITY(PROJ.ACM_CODE) ACM_NAME
, PROJ.PLAN_TYPE
, PROJ.PROJ_DURATION
, PROJ.PROJ_START_YEAR
, PROJ.PROJ_END_YEAR
, PROJ.PROJ_FOR_STAFF
, PROJ.PROJ_FROM_YEAR
, 0 BDGT_TOTAL
</SELECT>
<FROM>FROM PROJECTS PROJ
INNER JOIN REFER_CODE RFC ON RFC.RFG_GRP='PROJ-TYPE' AND RFC.RFC_CODE=PROJ.PROJ_TYPE AND RFC.RFC_FLAG = 'GENERAL'
</FROM>
<FILTER>WHERE STM_CODE = :STM_CODE</FILTER>
<ORDER>ORDER BY RFC.RFC_ORDER, PROJ.PROJ_ID</ORDER>
</SQL>
<FIELDS>
<FIELD NAME="PROJ_ID" TYPE="TEXT" LABEL="รหัสอ้างอิงโครงการ" WIDTH="32"/>
<FIELD NAME="PROJ_TYPE" TYPE="TEXT" LABEL="ประเภทโครงการ" WIDTH="25"/>
<FIELD NAME="PROJ_YEAR" TYPE="NUMBER" LABEL="ปีงบประมาณ" WIDTH="15"/>
<FIELD NAME="PJM_CODE" TYPE="TEXT" LABEL="รหัสโครงการ (BUD)" WIDTH="20"/>
<FIELD NAME="STM_CODE" TYPE="TEXT" LABEL="รหัสหน่วยงาน" WIDTH="10"/>
<FIELD NAME="BTM_CODE" TYPE="TEXT" LABEL="รหัสประเภทงบประมาณ" WIDTH="10"/>
<FIELD NAME="ACM_CODE" TYPE="TEXT" LABEL="รหัสกิจกรรม" WIDTH="20"/>
<FIELD NAME="PLAN_TYPE" TYPE="TEXT" LABEL="ประเภทแผน" WIDTH="15"/>
<FIELD NAME="PROJ_DURATION" TYPE="NUMBER" LABEL="ระยะเวลาดำเนินการ (วัน)" WIDTH="15"/>
<FIELD NAME="PROJ_START_YEAR" TYPE="NUMBER" LABEL="ปีที่เริ่มโครงการ (ก่อสร้าง)" WIDTH="15"/>
<FIELD NAME="PROJ_END_YEAR" TYPE="NUMBER" LABEL="ปีที่สิ้นสุดโครงการ (ก่อสร้าง)" WIDTH="15"/>
<FIELD NAME="PROJ_FOR_STAFF" TYPE="NUMBER" LABEL="จำนวนบุคลากรที่รองรับ" WIDTH="15"/>
<FIELD NAME="PROJ_FROM_YEAR" TYPE="NUMBER" LABEL="ต่อเนื่องจากปี" WIDTH="15"/>
<FIELD NAME="CREATE_BY" TYPE="TEXT" LABEL="ผู้สร้างรายการ" WIDTH="50"/>
<FIELD NAME="CREATE_TIME" TYPE="DATE" LABEL="เวลาที่สร้างรายการ" WIDTH="0"/>
<FIELD NAME="UPDATE_BY" TYPE="TEXT" LABEL="ผู้แก้ไขรายการล่าสุด" WIDTH="50"/>
<FIELD NAME="UPDATE_TIME" TYPE="DATE" LABEL="เวลาที่แก้ไขรายการล่าสุด" WIDTH="0"/>
</FIELDS>
<SUBDATASETS>
<SUBDATASET NAME="ACTIVITIES" DATASET-ID="DS-ACTIVITY-TREE" LINK-FIELDS="PROJ_ID"/>
</SUBDATASETS>
</DATASET>
<DATASET ID="DS-ACTIVITY-TREE">
<SCHEMA>APP</SCHEMA>
<TABLENAME>V_ACTIVITY_TREE</TABLENAME>
<SQL>
<SELECT>
SELECT PACT.PROJ_ID
, PACT.PROJ_GROUP
, PACT.PROJ_TYPE
, ACTT.ACM_CODE
, ACTT.ACM_NAME
, ACTT.ACM_SEQ
, ACTT.NODE_LEVEL+1 NODE_LEVEL
, ACTT.MAIN_NODE
, ACTT.ACM_UNIT
, ACTT.ACM_START_YEAR
, ACTT.ACM_END_YEAR
, ACTT.NODE_TYPE
, PACT.BDGT_COUNT
, PACT.BDGT_TOTAL
</SELECT>
<FROM>
FROM BGT.V_ACTIVITY_TREE ACTT
INNER JOIN (SELECT DISTINCT ACM.ACM_CODE
FROM BUD.ACTIVITY_M ACM
INNER JOIN BUD.ACTIVITY_CTRL_H ACH ON ACM.ACM_CODE = ACH.ACH_CODE
START WITH ACM.ACM_CODE IN (SELECT ACM_CODE FROM V_PROJECT_ACTIVITY PACT WHERE PACT.PROJ_ID = :PROJ_ID)
CONNECT BY PRIOR ACH.ACH_CTRL_CODE = ACH.ACH_CODE) FLTR ON FLTR.ACM_CODE = ACTT.ACM_CODE
LEFT OUTER JOIN V_PROJECT_ACTIVITY PACT ON PACT.ACM_CODE = ACTT.ACM_CODE AND PACT.PROJ_ID = :PROJ_ID
</FROM>
<ORDER>
ORDER BY ACTT.ACM_SEQ
</ORDER>
</SQL>
</DATASET>
</DATASETS>
<FORM>
<FORM_BROWSE DATAID="DS-MASTER">
<HEADER NAVI="N" EDIT="N" ADD="N" DELETE="N" VIEW="N"/>
<PAGESIZE>0</PAGESIZE>
<FIELDS>
<FIELD NAME="ACM_CODE" LABEL="project.const_code" ALIGN="left">
<DATA-FORMATTER>
<![CDATA[(value,row,idx)=>{return (row.node_type === "P")?"":$(`<div class="col-24 row text-nowrap"><div class="col offset-${+(row.node_level)-1}">${value}</div></div>`);}]]>
</DATA-FORMATTER>
</FIELD>
<FIELD NAME="ACM_NAME" LABEL="project.const_name" ALIGN="left">
<DATA-FORMATTER>
<![CDATA[(value,row,idx)=>{return (row.node_type === "P")?"":$(`<div class="col-24 row text-nowrap"><div class="col offset-${+(row.node_level)-1}">${value}</div></div>`);}]]>
</DATA-FORMATTER>
</FIELD>
<FIELD NAME="BDGT_COUNT" LABEL="pbdg.acm_count" ALIGN="right" WIDTH="10em">
<DATA-FORMATTER>
<![CDATA[(value, row, idx) => {
if (row["node_type"] === "D") {
return `<div class="col-24 text-nowrap">${formatNumber(value)} ${row["acm_unit"]}</div>`;
// return formatNumber(value)+" "+row["acm_unit"];
} else {
return ""
}
}]]>
</DATA-FORMATTER>
</FIELD>
<FIELD NAME="BDGT_TOTAL" LABEL="project.bgt_amount" ALIGN="right" WIDTH="10em">
<DATA-FORMATTER>
<![CDATA[(value, row, idx) => {return(row.proj_type !=="RUTN") ? "":formatNumber(value);}]]>
</DATA-FORMATTER>
</FIELD>
<COMMAND-BUTTONS>
<BUTTONS-FILTER>
<EDIT><![CDATA[(row)=>{return ["D","P"].includes(row["node_type"]);}]]></EDIT>
<VIEW><![CDATA[(row)=>{return ["D","P"].includes(row["node_type"]);}]]></VIEW>
</BUTTONS-FILTER>
</COMMAND-BUTTONS>
</FIELDS>
<FILTERS AUTO-APPLY="Y" ALLOW-NO-FILTER="Y">
<FIELDS>
<FIELD NAME="PROJ_YEAR" CAPTION="project.year" INPUTTYPE="COMBOBOX" SEARCH-ORIGIN="PROJ_YEAR=${VALUE}">
<LIST-OPTION TABLE="VL_YEAR" TEXT="#DV_YEAR" VALUE="DV_YEAR" ORDER="DV_YEAR" FIRSTLIST="@{NONE}"/>
</FIELD>
</FIELDS>
<LAYOUT>
<ROW>
<FIELD NAME="PROJ_YEAR" LAYOUT_WIDTH="12"/>
</ROW>
</LAYOUT>
</FILTERS>
<FOOTER SHOW="Y"/>
<BUTTONS>
<BUTTON NAME="btnButton1" CAPTION="ส่งผู้อำนวยการสำนักพิจารณา" CLASS="btn btn-warning" SECTION="RIGHT"/>
</BUTTONS>
<SCRIPT>
<EVENTS>
<AFTER-LOAD>
<![CDATA[
($ctx,data)=>{
let src = [...data]; // clone data to src
data.splice(0); // clear data element
let id = 1;
for (let item of src) {
item["node_type"] = "P";
item["node_level"] = "0";
item["$itemno"] = id++;
data.push(item);
if (isArray(item.activities)) {
for (let sitem of item.activities) {
sitem["$itemno"] = id++;
data.push(sitem);
}
}
}
console.log(data);
}
]]>
</AFTER-LOAD>
</EVENTS>
<INITIALIZE>
<![CDATA[
{
console.log("init activity grid");
const fieldFormat = (value,row) => {
console.log(row["proj_type"]);
if (row["node_type"] === "P" || row["proj_type"] === "RUTN") {
return "";
}
return formatNumber(value, 0);
}
const summary = (field, data) => {
let total = 0;
for (let id=0; id < data.length; id++) {
let row = data[id];
if (row["node_type"] === "D") {
total += (+row[field]) || 0;
}
}
if ($PageCtx.$dataGrid.load && !$PageCtx.$dataGrid.reload) {
$PageCtx.$dataGrid.reload = true;
let proj_type = "";
for (let id=data.length-1; id>=0; id--) {
let row = data[id];
proj_type = (row["proj_type"])||proj_type;
if (row["node_type"] === "C") {
let grpTotal = 0;
let mainNode = row["acm_code"];
let nodes = [];
nodes.push($$("main_node="+mainNode,data));
for (let node of nodes.flat()) {
grpTotal += +node[field];
}
data[id][field] = grpTotal;
data[id]["proj_type"] = proj_type;
}
}
$PageCtx.$dataGrid.load(data);
$PageCtx.$dataGrid.reload = false;
}
return formatNumber(total, 0);
}
const $ctx = $PageCtx;
const gridFields = [$$("field=bdgt_total", $ctx.gridColumn.flat())].flat();
for (let field of gridFields) {
field.formatter = fieldFormat;
field.footerFormatter = function (data) {
return summary(this.field, data);
}
}
$ctx.rowStyle = function (row, index) {
if (row["node_type"] === "C") {
return {classes : `group-level-${row["node_level"]}`};
}
if (row["node_type"] === "P") {
return {classes : `group-level-0`};
}
return {}; // Default style for other rows
}
}
]]>
</INITIALIZE>
</SCRIPT>
</FORM_BROWSE>
</FORM>
</FORMS>