From 17db839dacef530a783e984937d96a4f19a08504 Mon Sep 17 00:00:00 2001
From: Davide Silvestri <davide@baserow.io>
Date: Mon, 17 Mar 2025 11:08:41 +0100
Subject: [PATCH] Resolve "Fix number formatting to the rollup field to a
 formula number field"

---
 changelog/entries/unreleased/bug/3443_.json   |   8 ++
 web-frontend/modules/core/mixins/form.js      |   6 +
 .../components/field/FieldLookupSubForm.vue   |  75 +------------
 .../components/field/FieldRollupSubForm.vue   |  39 +------
 .../formula/FormulaTypeSubForms.vue           |   8 ++
 .../database/mixins/lookupFieldSubForm.js     | 106 ++++++++++++++++++
 6 files changed, 135 insertions(+), 107 deletions(-)
 create mode 100644 changelog/entries/unreleased/bug/3443_.json
 create mode 100644 web-frontend/modules/database/mixins/lookupFieldSubForm.js

diff --git a/changelog/entries/unreleased/bug/3443_.json b/changelog/entries/unreleased/bug/3443_.json
new file mode 100644
index 000000000..089ec0fce
--- /dev/null
+++ b/changelog/entries/unreleased/bug/3443_.json
@@ -0,0 +1,8 @@
+{
+    "type": "bug",
+    "message": "Fix field formatting options in rollup fields targeting formula fields",
+    "domain": "database",
+    "issue_number": 3443,
+    "bullet_points": [],
+    "created_at": "2025-03-13"
+}
\ No newline at end of file
diff --git a/web-frontend/modules/core/mixins/form.js b/web-frontend/modules/core/mixins/form.js
index 1ec7b8841..43bf24c90 100644
--- a/web-frontend/modules/core/mixins/form.js
+++ b/web-frontend/modules/core/mixins/form.js
@@ -210,6 +210,12 @@ export default {
         })
       )
     },
+    isDirty() {
+      for (const [key, value] of Object.entries(this.getDefaultValues())) {
+        if (this.values[key] !== value) return true
+      }
+      return false
+    },
     /**
      * Resets the form and the child forms to its original state.
      *
diff --git a/web-frontend/modules/database/components/field/FieldLookupSubForm.vue b/web-frontend/modules/database/components/field/FieldLookupSubForm.vue
index fc1e0a68c..f656cc686 100644
--- a/web-frontend/modules/database/components/field/FieldLookupSubForm.vue
+++ b/web-frontend/modules/database/components/field/FieldLookupSubForm.vue
@@ -16,6 +16,7 @@
     ></FieldSelectTargetFieldSubForm>
     <template v-if="selectedTargetField">
       <FormulaTypeSubForms
+        ref="subForm"
         :default-values="subFormDefaultValues"
         :formula-type="targetFieldFormulaType"
         :table="table"
@@ -31,6 +32,7 @@
 <script>
 import form from '@baserow/modules/core/mixins/form'
 import fieldSubForm from '@baserow/modules/database/mixins/fieldSubForm'
+import lookupFieldSubForm from '@baserow/modules/database/mixins/lookupFieldSubForm'
 import FormulaTypeSubForms from '@baserow/modules/database/components/formula/FormulaTypeSubForms'
 import FieldSelectThroughFieldSubForm from '@baserow/modules/database/components/field/FieldSelectThroughFieldSubForm'
 import FieldSelectTargetFieldSubForm from '@baserow/modules/database/components/field/FieldSelectTargetFieldSubForm'
@@ -42,77 +44,6 @@ export default {
     FieldSelectTargetFieldSubForm,
     FormulaTypeSubForms,
   },
-  mixins: [form, fieldSubForm],
-  data() {
-    return {
-      selectedThroughField: null,
-      selectedTargetField: null,
-      allowedValues: [],
-      values: {},
-      errorFromServer: null,
-      subFormDefaultValues: {},
-    }
-  },
-  computed: {
-    targetFieldFormulaType() {
-      if (this.selectedTargetField) {
-        return this.getFormulaType(this.selectedTargetField)
-      }
-      return 'unknown'
-    },
-  },
-  watch: {
-    defaultValues: {
-      handler(newDefaultValues) {
-        this.subFormDefaultValues = { ...newDefaultValues }
-      },
-      immediate: true,
-    },
-    selectedTargetField: {
-      handler(newTargetField) {
-        if (!newTargetField) {
-          return
-        }
-        const fieldType = this.$registry.get('field', newTargetField.type)
-        const formulaType = fieldType.toBaserowFormulaType(newTargetField)
-
-        const formulaTypeChanged =
-          formulaType && this.getFormulaType(this.defaultValues) !== formulaType
-
-        // New field or different type, use the relevant settings from the target field
-        const fieldValues = this.defaultValues
-        if (!fieldValues.id || formulaTypeChanged) {
-          for (const key in this.selectedTargetField) {
-            if (key.startsWith(formulaType)) {
-              this.subFormDefaultValues[key] = this.selectedTargetField[key]
-            }
-          }
-        }
-      },
-    },
-  },
-  methods: {
-    getFormulaType(field) {
-      return field.array_formula_type || field.formula_type || field.type
-    },
-    handleErrorByForm(error) {
-      if (
-        [
-          'ERROR_WITH_FORMULA',
-          'ERROR_FIELD_SELF_REFERENCE',
-          'ERROR_FIELD_CIRCULAR_REFERENCE',
-        ].includes(error.handler.code)
-      ) {
-        this.errorFromServer = error.handler.detail
-        return true
-      } else {
-        return false
-      }
-    },
-    reset() {
-      form.methods.reset.call(this)
-      this.errorFromServer = null
-    },
-  },
+  mixins: [form, fieldSubForm, lookupFieldSubForm],
 }
 </script>
diff --git a/web-frontend/modules/database/components/field/FieldRollupSubForm.vue b/web-frontend/modules/database/components/field/FieldRollupSubForm.vue
index 8d7dca4ca..914947125 100644
--- a/web-frontend/modules/database/components/field/FieldRollupSubForm.vue
+++ b/web-frontend/modules/database/components/field/FieldRollupSubForm.vue
@@ -39,7 +39,8 @@
       </FormGroup>
 
       <FormulaTypeSubForms
-        :default-values="defaultValues"
+        ref="subForm"
+        :default-values="subFormDefaultValues"
         :formula-type="targetFieldFormulaType"
         :table="table"
         :view="view"
@@ -60,6 +61,7 @@ import { required } from '@vuelidate/validators'
 
 import form from '@baserow/modules/core/mixins/form'
 import fieldSubForm from '@baserow/modules/database/mixins/fieldSubForm'
+import lookupFieldSubForm from '@baserow/modules/database/mixins/lookupFieldSubForm'
 import FormulaTypeSubForms from '@baserow/modules/database/components/formula/FormulaTypeSubForms'
 import FieldSelectThroughFieldSubForm from '@baserow/modules/database/components/field/FieldSelectThroughFieldSubForm'
 import FieldSelectTargetFieldSubForm from '@baserow/modules/database/components/field/FieldSelectTargetFieldSubForm'
@@ -71,58 +73,25 @@ export default {
     FieldSelectTargetFieldSubForm,
     FormulaTypeSubForms,
   },
-  mixins: [form, fieldSubForm],
+  mixins: [form, fieldSubForm, lookupFieldSubForm],
   setup() {
     return { v$: useVuelidate({ $lazy: true }) }
   },
   data() {
     return {
-      selectedThroughField: null,
-      selectedTargetField: null,
       allowedValues: ['rollup_function'],
       values: {
         rollup_function: null,
       },
-      errorFromServer: null,
     }
   },
   computed: {
-    targetFieldFormulaType() {
-      if (this.selectedTargetField) {
-        return (
-          this.selectedTargetField.array_formula_type ||
-          this.selectedTargetField.type
-        )
-      }
-      return 'unknown'
-    },
     rollupFunctions() {
       return Object.values(this.$registry.getAll('formula_function')).filter(
         (f) => f.isRollupCompatible(this.targetFieldFormulaType)
       )
     },
   },
-
-  methods: {
-    handleErrorByForm(error) {
-      if (
-        [
-          'ERROR_WITH_FORMULA',
-          'ERROR_FIELD_SELF_REFERENCE',
-          'ERROR_FIELD_CIRCULAR_REFERENCE',
-        ].includes(error.handler.code)
-      ) {
-        this.errorFromServer = error.handler.detail
-        return true
-      } else {
-        return false
-      }
-    },
-    reset() {
-      form.methods.reset.call(this)
-      this.errorFromServer = null
-    },
-  },
   validations() {
     return {
       values: {
diff --git a/web-frontend/modules/database/components/formula/FormulaTypeSubForms.vue b/web-frontend/modules/database/components/formula/FormulaTypeSubForms.vue
index 6dec9b97c..cb39b9e5b 100644
--- a/web-frontend/modules/database/components/formula/FormulaTypeSubForms.vue
+++ b/web-frontend/modules/database/components/formula/FormulaTypeSubForms.vue
@@ -1,6 +1,7 @@
 <template>
   <FieldNumberSubForm
     v-if="formulaType === 'number'"
+    ref="subForm"
     :default-values="defaultValues"
     :table="table"
     :view="view"
@@ -12,6 +13,7 @@
   </FieldNumberSubForm>
   <FieldDateSubForm
     v-else-if="['date', 'last_modified', 'created_on'].includes(formulaType)"
+    ref="subForm"
     :default-values="defaultValues"
     :table="table"
     :view="view"
@@ -22,6 +24,7 @@
   </FieldDateSubForm>
   <FieldDurationSubForm
     v-else-if="formulaType === 'duration'"
+    ref="subForm"
     :default-values="defaultValues"
     :table="table"
     :view="view"
@@ -67,5 +70,10 @@ export default {
       values: {},
     }
   },
+  methods: {
+    isDirty() {
+      return this.$refs.subForm?.isDirty()
+    },
+  },
 }
 </script>
diff --git a/web-frontend/modules/database/mixins/lookupFieldSubForm.js b/web-frontend/modules/database/mixins/lookupFieldSubForm.js
new file mode 100644
index 000000000..3cd23e87f
--- /dev/null
+++ b/web-frontend/modules/database/mixins/lookupFieldSubForm.js
@@ -0,0 +1,106 @@
+import form from '@baserow/modules/core/mixins/form'
+
+export default {
+  data() {
+    return {
+      selectedThroughField: null,
+      selectedTargetField: null,
+      allowedValues: [],
+      values: {},
+      errorFromServer: null,
+      subFormDefaultValues: {},
+    }
+  },
+  computed: {
+    targetFieldFormulaType() {
+      if (this.selectedTargetField) {
+        return this.getFormulaType(this.selectedTargetField)
+      }
+      return 'unknown'
+    },
+  },
+  watch: {
+    defaultValues: {
+      handler(newDefaultValues) {
+        this.subFormDefaultValues = { ...newDefaultValues }
+      },
+      immediate: true,
+    },
+    selectedTargetField: {
+      /**
+       * Updates sub form defaults based on the selected target field.
+       * For new fields with untouched forms, always suggest target field settings.
+       * For existing fields, suggest settings only if the formula type changes.
+       */
+      handler(newTargetField, oldTargetField) {
+        if (!newTargetField) {
+          return
+        }
+        const fieldId = this.defaultValues.id
+        const newField = !fieldId
+        const cleanForm = !this.$refs.subForm?.isDirty()
+
+        const newFieldCleanFormAndNewTarget =
+          newField && cleanForm && newTargetField?.id !== oldTargetField?.id
+
+        const existingFieldButDifferentType =
+          !newField && !this.matchTargetFieldType(newTargetField)
+
+        const shouldSuggestDefaults =
+          newFieldCleanFormAndNewTarget || existingFieldButDifferentType
+
+        const fieldType = this.$registry.get('field', newTargetField.type)
+        const formulaType = fieldType.toBaserowFormulaType(newTargetField)
+
+        // New field or different type, use the relevant settings from the target field
+        if (shouldSuggestDefaults) {
+          const defaults = {}
+          for (const key in this.selectedTargetField) {
+            if (key.startsWith(formulaType)) {
+              defaults[key] = this.selectedTargetField[key]
+            }
+          }
+          this.subFormDefaultValues = {
+            ...this.subFormDefaultValues,
+            ...defaults,
+          }
+          this.$nextTick(() => this.$refs.subForm.reset())
+        }
+      },
+    },
+  },
+  methods: {
+    /**
+     * Verify if the final formula type match the target field type.
+     */
+    matchTargetFieldType(targetField) {
+      const field = this.defaultValues
+      return (
+        field?.type === this.fieldType &&
+        (field?.formula_type === targetField?.type ||
+          field?.array_formula_type === targetField?.type)
+      )
+    },
+    getFormulaType(field) {
+      return field.array_formula_type || field.formula_type || field.type
+    },
+    handleErrorByForm(error) {
+      if (
+        [
+          'ERROR_WITH_FORMULA',
+          'ERROR_FIELD_SELF_REFERENCE',
+          'ERROR_FIELD_CIRCULAR_REFERENCE',
+        ].includes(error.handler.code)
+      ) {
+        this.errorFromServer = error.handler.detail
+        return true
+      } else {
+        return false
+      }
+    },
+    reset() {
+      form.methods.reset.call(this)
+      this.errorFromServer = null
+    },
+  },
+}