Bladeren bron

add the cases when we have most specific items

gdias 4 weken geleden
bovenliggende
commit
62fe31e5bc
2 gewijzigde bestanden met toevoegingen van 251 en 46 verwijderingen
  1. 86 42
      src/lib/components/commodities/cpr/EmissionCpr.svelte
  2. 165 4
      src/routes/cpr/+page.svelte

+ 86 - 42
src/lib/components/commodities/cpr/EmissionCpr.svelte

@@ -2,6 +2,11 @@
   export let formData = {};
   export let onFieldChange = () => {};
   export let requiredFields = new Set();
+  export let repeatingConfigs = [];
+  export let repeatingGroups = {};
+  export let onRepeatingFieldChange = () => {};
+  export let onAddRepeatingEntry = () => {};
+  export let onRemoveRepeatingEntry = () => {};
 
   const indicatorOptions = [
     { label: 'Selecione...', value: '' },
@@ -15,7 +20,6 @@
       description: 'Local de entrega e prazos do documento.',
       columns: 2,
       fields: [
-        { key: 'cpr_issuer_name', label: 'Razão Social do Emissor' },
         { key: 'cpr_place_name', label: 'Local de Entrega' },
         { key: 'cpr_document_deadline_days_number', label: 'Prazo para documentos (dias)' },
         { key: 'cpr_deliveryPlace_state_acronym', label: 'Estado da entrega' },
@@ -23,31 +27,6 @@
         { key: 'cpr_deliveryPlace_ibge_code', label: 'IBGE da entrega' }
       ]
     },
-    {
-      title: 'Dados do emissor',
-      description: 'Informações cadastrais do emissor.',
-      columns: 2,
-      fields: [
-        { key: 'cpr_issuer_legal_nature_code', label: 'Natureza jurídica' },
-        { key: 'cpr_issuers_person_type_acronym', label: 'Tipo de pessoa' },
-        { key: 'cpr_issuers_document_number', label: 'Documento' },
-        { key: 'cpr_issuers_state_acronym', label: 'Estado' },
-        { key: 'cpr_issuers_city_name', label: 'Cidade' },
-        { key: 'cpr_issuers_ibge_code', label: 'IBGE' }
-      ]
-    },
-    {
-      title: 'Garantias e colaterais',
-      description: 'Detalhes das garantias vinculadas à CPR.',
-      columns: 2,
-      fields: [
-        { key: 'cpr_collateral_type_code', label: 'Código do colateral' },
-        { key: 'cpr_collateral_type_name', label: 'Descrição do colateral' },
-        { key: 'cpr_constitution_process_indicator', label: 'Processo constituído', type: 'select', options: indicatorOptions },
-        { key: 'cpr_otc_bondsman_account_code', label: 'Conta OTC do fiador' },
-        { key: 'cpr_collaterals_document_number', label: 'Documento da garantia' }
-      ]
-    },
     {
       title: 'Produto e lastro',
       description: 'Características do produto e da produção.',
@@ -64,22 +43,6 @@
       ],
       textarea: { key: 'cpr_product_description', label: 'Descrição do produto' }
     },
-    {
-      title: 'Local de produção',
-      description: 'Informações da fazenda/propriedade.',
-      columns: 3,
-      fields: [
-        { key: 'cpr_production_place_name', label: 'Nome da propriedade' },
-        { key: 'cpr_property_registration_number', label: 'Registro da propriedade' },
-        { key: 'cpr_notary_name', label: 'Cartório' },
-        { key: 'cpr_total_production_area_in_hectares_number', label: 'Área de produção (ha)' },
-        { key: 'cpr_total_area_in_hectares_number', label: 'Área total (ha)' },
-        { key: 'cpr_car_code', label: 'Código CAR' },
-        { key: 'cpr_latitude_code', label: 'Latitude' },
-        { key: 'cpr_longitude_code', label: 'Longitude' },
-        { key: 'cpr_zip_code', label: 'CEP' }
-      ]
-    },
     {
       title: 'Green CPR',
       description: 'Campos específicos para CPRs verdes.',
@@ -103,6 +66,12 @@
       onFieldChange(key, event.currentTarget.value);
     };
   }
+
+  function handleRepeatingInput(groupKey, index, fieldKey) {
+    return (event) => {
+      onRepeatingFieldChange(groupKey, index, fieldKey, event.currentTarget.value);
+    };
+  }
 </script>
 
 <div class="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-sm">
@@ -164,5 +133,80 @@
         {/if}
       </div>
     {/each}
+
+    {#if repeatingConfigs?.length}
+      <div class="space-y-8">
+        {#each repeatingConfigs as config}
+          <div class="space-y-4">
+            <div class="flex flex-col gap-2 md:flex-row md:items-center md:justify-between">
+              <div>
+                <h3 class="text-base font-semibold text-gray-900 dark:text-gray-100">{config.title}</h3>
+                <p class="text-sm text-gray-500 dark:text-gray-400">{config.description}</p>
+              </div>
+              <button
+                type="button"
+                class="inline-flex items-center gap-2 self-start rounded-md border border-gray-300 dark:border-gray-600 px-3 py-2 text-sm font-medium text-gray-700 dark:text-gray-100 hover:bg-gray-100 dark:hover:bg-gray-700"
+                on:click={() => onAddRepeatingEntry(config.key)}
+              >
+                + {config.addLabel ?? `Adicionar ${config.itemLabel}`}
+              </button>
+            </div>
+
+            <div class="space-y-4">
+              {#each (repeatingGroups?.[config.key] ?? []) as entry, index (index)}
+                <div class="rounded-lg border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-900/30 p-4 space-y-4">
+                  <div class="flex items-center justify-between">
+                    <p class="text-sm font-semibold text-gray-800 dark:text-gray-100">
+                      {config.itemLabel} {index + 1}
+                    </p>
+                    {#if (repeatingGroups?.[config.key]?.length ?? 0) > 1}
+                      <button
+                        type="button"
+                        class="text-sm text-red-600 hover:text-red-700"
+                        on:click={() => onRemoveRepeatingEntry(config.key, index)}
+                      >
+                        Remover
+                      </button>
+                    {/if}
+                  </div>
+                  <div class={`grid gap-4 ${config.columns === 3 ? 'md:grid-cols-3' : 'md:grid-cols-2'} grid-cols-1`}>
+                    {#each config.fields as field}
+                      <div class="space-y-1">
+                        <label class="block text-sm font-medium text-gray-700 dark:text-gray-300">
+                          {field.label}
+                          {#if requiredFields?.has(field.key)}
+                            <span class="text-red-500">*</span>
+                          {/if}
+                        </label>
+                        {#if field.type === 'select'}
+                          <select
+                            class="w-full rounded border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700/70 text-gray-900 dark:text-gray-100 px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500"
+                            value={entry[field.key] ?? ''}
+                            on:change={handleRepeatingInput(config.key, index, field.key)}
+                            required={requiredFields?.has(field.key)}
+                          >
+                            {#each field.options ?? indicatorOptions as option}
+                              <option value={option.value}>{option.label}</option>
+                            {/each}
+                          </select>
+                        {:else}
+                          <input
+                            type="text"
+                            class="w-full rounded border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700/70 text-gray-900 dark:text-gray-100 px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500"
+                            value={entry[field.key] ?? ''}
+                            on:input={handleRepeatingInput(config.key, index, field.key)}
+                            required={requiredFields?.has(field.key)}
+                          />
+                        {/if}
+                      </div>
+                    {/each}
+                  </div>
+                </div>
+              {/each}
+            </div>
+          </div>
+        {/each}
+      </div>
+    {/if}
   </form>
 </div>

+ 165 - 4
src/routes/cpr/+page.svelte

@@ -186,6 +186,84 @@
     'cpr_zip_code'
   ]);
 
+  const repeatingGroupDefinitions = [
+    {
+      key: 'issuers',
+      title: 'Dados dos emissores',
+      description: 'Adicione quantos emissores forem necessários.',
+      itemLabel: 'Emissor',
+      addLabel: 'Adicionar emissor',
+      columns: 2,
+      fields: [
+        { key: 'cpr_issuer_name', label: 'Razão Social do Emissor' },
+        { key: 'cpr_issuers_document_number', label: 'Documento' },
+        { key: 'cpr_issuers_person_type_acronym', label: 'Tipo de pessoa' },
+        { key: 'cpr_issuer_legal_nature_code', label: 'Natureza jurídica' },
+        { key: 'cpr_issuers_state_acronym', label: 'Estado' },
+        { key: 'cpr_issuers_city_name', label: 'Cidade' }
+      ]
+    },
+    {
+      key: 'collaterals',
+      title: 'Garantias e colaterais',
+      description: 'Informe todos os colaterais vinculados a esta CPR.',
+      itemLabel: 'Colateral',
+      addLabel: 'Adicionar colateral',
+      columns: 2,
+      fields: [
+        { key: 'cpr_collateral_type_code', label: 'Código do colateral' },
+        { key: 'cpr_collateral_type_name', label: 'Descrição do colateral' },
+        { key: 'cpr_constitution_process_indicator', label: 'Processo constituído', type: 'select', options: [
+          { label: 'Selecione...', value: '' },
+          { label: 'Sim', value: 'S' },
+          { label: 'Não', value: 'N' }
+        ] },
+        { key: 'cpr_otc_bondsman_account_code', label: 'Conta OTC do fiador' }
+      ]
+    },
+    {
+      key: 'productionPlaces',
+      title: 'Locais de produção',
+      description: 'Cadastre cada propriedade vinculada ao lastro.',
+      itemLabel: 'Propriedade',
+      addLabel: 'Adicionar propriedade',
+      columns: 3,
+      fields: [
+        { key: 'cpr_production_place_name', label: 'Nome da propriedade' },
+        { key: 'cpr_property_registration_number', label: 'Registro da propriedade' },
+        { key: 'cpr_notary_name', label: 'Cartório' },
+        { key: 'cpr_total_production_area_in_hectares_number', label: 'Área de produção (ha)' },
+        { key: 'cpr_total_area_in_hectares_number', label: 'Área total (ha)' },
+        { key: 'cpr_car_code', label: 'Código CAR' },
+        { key: 'cpr_latitude_code', label: 'Latitude' },
+        { key: 'cpr_longitude_code', label: 'Longitude' },
+        { key: 'cpr_zip_code', label: 'CEP' }
+      ]
+    }
+  ];
+
+  const repeatingFieldToGroup = repeatingGroupDefinitions.reduce((acc, config) => {
+    config.fields.forEach((field) => {
+      acc[field.key] = config.key;
+    });
+    return acc;
+  }, {});
+
+  function createEmptyRepeatingEntry(config) {
+    return config.fields.reduce((entry, field) => {
+      entry[field.key] = '';
+      return entry;
+    }, {});
+  }
+
+  const createInitialRepeatingGroups = () => {
+    const groups = {};
+    repeatingGroupDefinitions.forEach((config) => {
+      groups[config.key] = [createEmptyRepeatingEntry(config)];
+    });
+    return groups;
+  };
+
   const createInitialForm = () =>
     allFieldKeys.reduce((acc, key) => {
       acc[key] = '';
@@ -193,6 +271,7 @@
     }, {});
 
   let cprForm = createInitialForm();
+  let repeatingGroups = createInitialRepeatingGroups();
   let submitError = '';
   let submitSuccess = '';
   let isSubmitting = false;
@@ -226,8 +305,49 @@
     cprForm = { ...cprForm, [key]: value ?? '' };
   }
 
+  function cloneRepeatingEntries(groupKey) {
+    const entries = repeatingGroups[groupKey] ?? [];
+    return entries.map((entry) => ({ ...entry }));
+  }
+
+  function handleRepeatingFieldChange(groupKey, index, fieldKey, value) {
+    submitError = '';
+    submitSuccess = '';
+    const nextEntries = cloneRepeatingEntries(groupKey);
+    if (!nextEntries[index]) return;
+    nextEntries[index] = {
+      ...nextEntries[index],
+      [fieldKey]: value ?? ''
+    };
+    repeatingGroups = {
+      ...repeatingGroups,
+      [groupKey]: nextEntries
+    };
+  }
+
+  function handleAddRepeatingEntry(groupKey) {
+    const config = repeatingGroupDefinitions.find((c) => c.key === groupKey);
+    if (!config) return;
+    const nextEntries = cloneRepeatingEntries(groupKey);
+    nextEntries.push(createEmptyRepeatingEntry(config));
+    repeatingGroups = {
+      ...repeatingGroups,
+      [groupKey]: nextEntries
+    };
+  }
+
+  function handleRemoveRepeatingEntry(groupKey, index) {
+    const nextEntries = cloneRepeatingEntries(groupKey);
+    if (nextEntries.length <= 1) return;
+    repeatingGroups = {
+      ...repeatingGroups,
+      [groupKey]: nextEntries.filter((_, i) => i !== index)
+    };
+  }
+
   function handleAddTop() {
     cprForm = createInitialForm();
+    repeatingGroups = createInitialRepeatingGroups();
     submitError = '';
     submitSuccess = '';
     activeTab = 0;
@@ -279,9 +399,25 @@
   function getMissingRequiredFields() {
     const missing = [];
     requiredFields.forEach((key) => {
-      const raw = cprForm[key];
-      if (!raw || String(raw).trim() === '') {
-        missing.push(key);
+      const groupKey = repeatingFieldToGroup[key];
+      if (groupKey) {
+        const config = repeatingGroupDefinitions.find((c) => c.key === groupKey);
+        const entries = repeatingGroups[groupKey] ?? [];
+        if (!entries.length) {
+          missing.push(`${key} (${config?.itemLabel ?? groupKey})`);
+          return;
+        }
+        entries.forEach((entry, index) => {
+          const raw = entry?.[key];
+          if (!raw || String(raw).trim() === '') {
+            missing.push(`${key} (${config?.itemLabel ?? groupKey} ${index + 1})`);
+          }
+        });
+      } else {
+        const raw = cprForm[key];
+        if (!raw || String(raw).trim() === '') {
+          missing.push(key);
+        }
       }
     });
     return missing;
@@ -290,6 +426,21 @@
   function buildPayload() {
     const payload = {};
     for (const key of allFieldKeys) {
+      const groupKey = repeatingFieldToGroup[key];
+      if (groupKey) {
+        const entries = repeatingGroups[groupKey] ?? [];
+        const values = entries
+          .map((entry) => entry?.[key])
+          .map((value) => (typeof value === 'string' ? value.trim() : value))
+          .filter((value) => value && value !== '');
+        payload[key] = values.length
+          ? values.join('; ')
+          : requiredFields.has(key)
+          ? ''
+          : 'NA';
+        continue;
+      }
+
       const raw = cprForm[key];
       const trimmed = typeof raw === 'string' ? raw.trim() : raw;
       if (trimmed === '' || trimmed === undefined || trimmed === null) {
@@ -349,6 +500,7 @@
 
       submitSuccess = response?.msg ?? 'CPR registrada com sucesso.';
       cprForm = createInitialForm();
+      repeatingGroups = createInitialRepeatingGroups();
       activeTab = 4;
     } catch (err) {
       submitError = err?.message ?? 'Falha ao registrar CPR.';
@@ -434,7 +586,16 @@
       {:else if activeTab === 2}
       <Tabs {tabs} bind:active={activeTab} showCloseIcon={true} on:close={handleCancel} />
       <div class="mt-4">
-        <EmissionCpr formData={cprForm} onFieldChange={handleFieldChange} {requiredFields} />
+        <EmissionCpr
+          formData={cprForm}
+          onFieldChange={handleFieldChange}
+          {requiredFields}
+          repeatingConfigs={repeatingGroupDefinitions}
+          {repeatingGroups}
+          onRepeatingFieldChange={handleRepeatingFieldChange}
+          onAddRepeatingEntry={handleAddRepeatingEntry}
+          onRemoveRepeatingEntry={handleRemoveRepeatingEntry}
+        />
       </div>
       <!-- Navigation Controls -->
       <div class="flex justify-between mt-6">