瀏覽代碼

feat: add validation error handling for ProposalPricingTab and update CreateContractDialog date validation

alvesantos 14 小時之前
父節點
當前提交
bcadefde2f

+ 36 - 20
src/pages/packages/components/AddEditPackageDialog.vue

@@ -75,8 +75,10 @@
                 <ProposalPricingTab
                   v-model="form.pavao"
                   v-model:enabled="form.pavao_enabled"
+                  field-prefix="pavao"
                   :product-options="productOptions"
                   section-label="Pavão"
+                  :validation-errors="validationErrors"
                 />
               </div>
 
@@ -87,9 +89,11 @@
                 <ProposalPricingTab
                   v-model="form.irrecusavel"
                   v-model:enabled="form.irrecusavel_enabled"
+                  field-prefix="irrecusavel"
                   :product-options="productOptions"
                   section-label="Irrecusável"
                   show-condition
+                  :validation-errors="validationErrors"
                 />
               </div>
             </q-card-section>
@@ -329,32 +333,44 @@ const onOKClick = async () => {
     quantity_classes: form.quantity_classes,
   };
 
-  await execute(() => {
-    if (props.package?.id) {
-      const updated = {
-        ...getUpdatedFields.value,
-      };
+  try {
+    await execute(() => {
+      if (props.package?.id) {
+        const updated = {
+          ...getUpdatedFields.value,
+        };
 
-      if (updated.class_duration_hours !== undefined) {
-        updated.class_duration_minutes = payload.class_duration_minutes;
+        if (updated.class_duration_hours !== undefined) {
+          updated.class_duration_minutes = payload.class_duration_minutes;
 
-        delete updated.class_duration_hours;
-      }
+          delete updated.class_duration_hours;
+        }
 
-      // Pavão/Irrecusável têm validação cruzada entre campos (par exclusivo
-      // de checkbox) e um sinal explícito de "removido" (null) — não é
-      // seguro fatiar isso num diff parcial, vão sempre inteiros.
-      updated.pavao = payload.pavao;
-      updated.irrecusavel = payload.irrecusavel;
+        // Pavão/Irrecusável têm validação cruzada entre campos (par exclusivo
+        // de checkbox) e um sinal explícito de "removido" (null) — não é
+        // seguro fatiar isso num diff parcial, vão sempre inteiros.
+        updated.pavao = payload.pavao;
+        updated.irrecusavel = payload.irrecusavel;
 
-      delete updated.pavao_enabled;
-      delete updated.irrecusavel_enabled;
+        delete updated.pavao_enabled;
+        delete updated.irrecusavel_enabled;
 
-      return updatePackage(props.package.id, updated);
-    }
+        return updatePackage(props.package.id, updated);
+      }
 
-    return createPackage(payload);
-  });
+      return createPackage(payload);
+    });
+  } finally {
+    // Se o erro veio de dentro de Pavão/Irrecusável, troca pra aba certa —
+    // senão a mensagem fica escondida numa tab que não está visível.
+    const errorKeys = Object.keys(validationErrors);
+
+    if (errorKeys.some((key) => key.startsWith("pavao."))) {
+      currentTab.value = "pavao";
+    } else if (errorKeys.some((key) => key.startsWith("irrecusavel."))) {
+      currentTab.value = "irrecusavel";
+    }
+  }
 };
 
 onMounted(loadData);

+ 23 - 0
src/pages/packages/components/ProposalPricingTab.vue

@@ -24,6 +24,10 @@
       @update:installments="setExclusive('registration', 'installments')"
     />
 
+    <div v-if="sectionError('registration')" class="col-12 text-negative text-caption">
+      {{ sectionError('registration') }}
+    </div>
+
     <template v-if="modelValue.registration_installments_allowed">
       <DefaultInput
         v-model="modelValue.registration_max_installments"
@@ -55,6 +59,10 @@
       @update:installments="setExclusive('classes', 'installments')"
     />
 
+    <div v-if="sectionError('classes')" class="col-12 text-negative text-caption">
+      {{ sectionError('classes') }}
+    </div>
+
     <template v-if="modelValue.classes_installments_allowed">
       <DefaultInput
         v-model="modelValue.classes_max_installments"
@@ -144,6 +152,10 @@
       @update:installments="setExclusive('materials', 'installments')"
     />
 
+    <div v-if="sectionError('materials')" class="col-12 text-negative text-caption">
+      {{ sectionError('materials') }}
+    </div>
+
     <template v-if="modelValue.materials_installments_allowed">
       <DefaultInput
         v-model="modelValue.materials_max_installments"
@@ -215,6 +227,10 @@ const modelValue = defineModel({ type: Object, required: true });
 const enabled = defineModel("enabled", { type: Boolean, default: false });
 
 const props = defineProps({
+  fieldPrefix: {
+    default: "",
+    type: String,
+  },
   productOptions: {
     default: () => [],
     type: Array,
@@ -227,8 +243,15 @@ const props = defineProps({
     default: false,
     type: Boolean,
   },
+  validationErrors: {
+    default: () => ({}),
+    type: Object,
+  },
 });
 
+const sectionError = (section) =>
+  props.validationErrors[`${props.fieldPrefix}.${section}_included_in_course`];
+
 const materialsTotalValue = computed(() =>
   modelValue.value.materials.reduce(
     (sum, material) =>

+ 7 - 3
src/pages/unit/components/CreateContractDialog.vue

@@ -301,10 +301,14 @@ const percentageRules = [
   inputRules.maxValue(100),
 ];
 
-const endDateRule = (value) =>
-  !value ||
+// form.start_date/form.end_date são o "untreated-date" do DefaultInputDatePicker
+// (formato YYYY-MM-DD), então comparam corretamente como string. O "value" que o
+// q-input passa pra regra é o "treated-date" (DD/MM/YYYY) do campo — formato
+// diferente de form.start_date, o que fazia a comparação dar falso positivo.
+const endDateRule = () =>
+  !form.end_date ||
   !form.start_date ||
-  value > form.start_date ||
+  form.end_date > form.start_date ||
   "A data de fim deve ser posterior à data de início";
 
 const invoiceDueDateRule = (value) =>