@@ -46,6 +46,17 @@ public static function isSessionActive()
4646 * Syncs sub-table data by deleting existing records and inserting new ones.
4747 * Uses dynamic field detection from DDL for future-proofing across different schema versions.
4848 *
49+ * Flow:
50+ * 1. Bail out early if there's nothing to sync.
51+ * 2. DELETE all existing rows for this foreign key (full replace strategy).
52+ * 3. Detect the table's columns from its DDL (excluding auto-increment/internal fields).
53+ * 4. Loop through $remoteData and build an insert row for each entry:
54+ * - If $keyValueMapping is provided, the data is a simple key => value map
55+ * (e.g., risk_factor_id => detected_value), handled by buildKeyValueInsertData().
56+ * - Otherwise, each entry is an associative array of column values,
57+ * handled by buildStandardInsertData().
58+ * 5. Insert each built row into the table.
59+ *
4960 * @param string $tableName Target table name
5061 * @param string $foreignKey Foreign key column name
5162 * @param mixed $foreignKeyValue Foreign key value
@@ -67,40 +78,73 @@ public function syncSubTable(
6778 return ;
6879 }
6980
81+ // Step 1: Wipe existing child rows for this foreign key
7082 $ this ->db ->where ($ foreignKey , $ foreignKeyValue );
7183 $ this ->db ->delete ($ tableName );
7284
85+ // Step 2: Get the table's writable columns (auto-increment/internal fields excluded)
7386 $ fields = $ this ->getTableFieldsAsArray ($ tableName , $ excludeFields );
7487
88+ // Step 3: Re-insert each remote row
7589 foreach ($ remoteData as $ key => $ row ) {
90+ // Every row must reference the parent record
7691 $ insertData = [$ foreignKey => $ foreignKeyValue ];
7792
78- if (!empty ($ keyValueMapping )) {
79- // Key-value pair mapping (e.g., hepatitis risk factors: [id => detected_value])
80- if (isset ($ keyValueMapping ['keyField ' ])) {
81- $ insertData [$ keyValueMapping ['keyField ' ]] = $ key ;
82- }
83- if (isset ($ keyValueMapping ['valueField ' ])) {
84- $ insertData [$ keyValueMapping ['valueField ' ]] = $ row ;
85- }
86- } else {
87- // Standard array-of-objects mapping
88- foreach ($ fields as $ field => $ default ) {
89- if ($ field === $ foreignKey ) {
90- continue ;
91- }
92- if ($ setUpdatedDatetime && $ field === 'updated_datetime ' ) {
93- $ insertData [$ field ] = DateUtility::getCurrentDateTime ();
94- } elseif (isset ($ row [$ field ])) {
95- $ insertData [$ field ] = $ row [$ field ];
96- }
97- }
98- }
93+ // Two data shapes are supported:
94+ // - Key-value map: $remoteData = [risk_factor_id => 'yes', ...]
95+ // - Standard rows: $remoteData = [['col1' => val, 'col2' => val], ...]
96+ $ insertData = !empty ($ keyValueMapping )
97+ ? $ this ->buildKeyValueInsertData ($ insertData , $ keyValueMapping , $ key , $ row )
98+ : $ this ->buildStandardInsertData ($ insertData , $ fields , $ foreignKey , $ row , $ setUpdatedDatetime );
9999
100100 $ this ->db ->insert ($ tableName , $ insertData );
101101 }
102102 }
103103
104+ /**
105+ * Builds an insert row for key-value pair data.
106+ *
107+ * Used when $remoteData is a flat map like [risk_factor_id => detected_value].
108+ * $keyValueMapping tells us which columns to put the key and value into:
109+ * ['keyField' => 'risk_factor', 'valueField' => 'detected_value']
110+ *
111+ * Result example: ['sample_id' => 123, 'risk_factor' => 'hep_b', 'detected_value' => 'yes']
112+ */
113+ private function buildKeyValueInsertData (array $ insertData , array $ keyValueMapping , mixed $ key , mixed $ row ): array
114+ {
115+ if (isset ($ keyValueMapping ['keyField ' ])) {
116+ $ insertData [$ keyValueMapping ['keyField ' ]] = $ key ;
117+ }
118+ if (isset ($ keyValueMapping ['valueField ' ])) {
119+ $ insertData [$ keyValueMapping ['valueField ' ]] = $ row ;
120+ }
121+ return $ insertData ;
122+ }
123+
124+ /**
125+ * Builds an insert row by matching remote data fields to the table's DDL columns.
126+ *
127+ * Iterates over every column the table has (from DDL), and if the remote $row
128+ * contains a value for that column, includes it. Skips the foreign key column
129+ * (already set by the caller) and optionally stamps updated_datetime.
130+ */
131+ private function buildStandardInsertData (array $ insertData , array $ fields , string $ foreignKey , mixed $ row , bool $ setUpdatedDatetime ): array
132+ {
133+ foreach ($ fields as $ field => $ default ) {
134+ if ($ field === $ foreignKey ) {
135+ continue ; // Already set by the caller
136+ }
137+ if ($ setUpdatedDatetime && $ field === 'updated_datetime ' ) {
138+ $ insertData [$ field ] = DateUtility::getCurrentDateTime ();
139+ } elseif (isset ($ row [$ field ])) {
140+ $ insertData [$ field ] = $ row [$ field ];
141+ }
142+ // Fields not present in $row are intentionally skipped —
143+ // the DB will use its column default.
144+ }
145+ return $ insertData ;
146+ }
147+
104148
105149 public function getAppVersion ($ composerFilePath = ROOT_PATH . '/composer.json ' )
106150 {
0 commit comments