RAP Determinations y Validations: La Guía Completa

Kategorie
ABAP-Statements
Veröffentlicht
Autor
Johannes

Determinations y Validations son los dos mecanismos en RAP para ejecutar lógica de negocio automáticamente. Las Determinations establecen valores, las Validations los verifican. La comprensión correcta del timing (cuándo se ejecuta qué) es decisiva para RAP Business Objects sin errores.

La Diferencia de un Vistazo

┌─────────────────────────────────────────────────────────────┐
User Action (Create/Update) │
└────────────────────┬────────────────────────────────────────┘
┌───────────────────────┐
│ DETERMINATION │ ← Setzt automatisch Werte
"Was fehlt?" │ (Status, Nummern, Defaults)
└───────────┬───────────┘
┌───────────────────────┐
│ VALIDATION │ ← Prüft Geschäftsregeln
"Ist es korrekt?" │ (Datumslogik, Pflichtfelder)
└───────────┬───────────┘
▼ (nur bei Erfolg)
┌───────────────────────┐
Save to Database
└───────────────────────┘
AspectoDeterminationValidation
PropósitoEstablecer/calcular valoresVerificar valores
Timingon modify u on saveon save
CambiosPuede modificar EntitySolo lectura, sin cambios
ErroresSin errores directosLlena FAILED y REPORTED
EjemploEstablecer Status a ‘O’Verificar fecha fin después de inicio
OrdenPrimero (antes de Validation)Después (tras Determination)

Determinations: Establecimiento Automático de Valores

¿Cuándo usar Determinations?

Perfecto para:

  • Establecer valores por defecto (Status = ‘Open’)
  • Calcular campos (PrecioTotal = Precio x Cantidad)
  • Actualizar campos dependientes
  • Establecer timestamps (CreatedAt, LastChangedAt)
  • Valores basados en lógica de negocio (Descuento según categoría de cliente)

Sintaxis en Behavior Definition

define behavior for ZI_Travel alias Travel
{
// Determination Syntax:
determination <MethodName> on <Trigger> { <TriggerCondition>; }
// Trigger: modify (sofort) oder save (vor DB-Commit)
// TriggerCondition: create, update, field <FieldName>
}

Ejemplo 1: Establecer Status en Create

// Behavior Definition
define behavior for ZI_Travel alias Travel
persistent table ztravel
{
create;
update;
field ( readonly ) TravelId, Status;
field ( readonly ) CreatedBy, CreatedAt;
// Determination: Bei Create → Status auf 'O' (Open) setzen
determination setInitialStatus on modify { create; }
}
// Behavior Implementation
CLASS lhc_travel DEFINITION INHERITING FROM cl_abap_behavior_handler.
PRIVATE SECTION.
METHODS setInitialStatus FOR DETERMINE ON MODIFY
IMPORTING keys FOR Travel~setInitialStatus.
ENDCLASS.
CLASS lhc_travel IMPLEMENTATION.
METHOD setInitialStatus.
" 1. Aktuelle Daten lesen
READ ENTITIES OF zi_travel IN LOCAL MODE
ENTITY Travel
FIELDS ( Status )
WITH CORRESPONDING #( keys )
RESULT DATA(lt_travel).
" 2. Nur Entities ohne Status aktualisieren
MODIFY ENTITIES OF zi_travel IN LOCAL MODE
ENTITY Travel
UPDATE FIELDS ( Status CreatedBy CreatedAt )
WITH VALUE #( FOR travel IN lt_travel WHERE ( Status IS INITIAL )
( %tky = travel-%tky
Status = 'O' " Open
CreatedBy = cl_abap_context_info=>get_user_name( )
CreatedAt = cl_abap_context_info=>get_system_date( ) ) )
REPORTED DATA(update_reported).
ENDMETHOD.
ENDCLASS.

Flujo:

User: CREATE Travel mit Description = 'Geschäftsreise'
Framework: Create in Memory
Determination: setInitialStatus
→ Status = 'O'
→ CreatedBy = sy-uname
→ CreatedAt = today
(Validations werden ausgeführt)
Framework: INSERT INTO ztravel

Ejemplo 2: Calcular Precio Total (on modify)

// Behavior Definition
define behavior for ZI_Travel alias Travel
{
// Bei Änderung von BeginDate oder EndDate → Preis neu berechnen
determination calculateTotalPrice on modify {
field BeginDate, EndDate;
}
}
METHOD calculateTotalPrice.
" Daten lesen
READ ENTITIES OF zi_travel IN LOCAL MODE
ENTITY Travel
FIELDS ( TravelId BeginDate EndDate )
WITH CORRESPONDING #( keys )
RESULT DATA(lt_travel).
LOOP AT lt_travel INTO DATA(ls_travel).
" Anzahl Tage berechnen
DATA(lv_days) = ls_travel-EndDate - ls_travel-BeginDate + 1.
" Basis-Preis pro Tag (z.B. aus Customizing)
DATA(lv_price_per_day) = 100. " EUR
" Gesamtpreis
DATA(lv_total) = lv_days * lv_price_per_day.
" Rabatt für längere Reisen
IF lv_days > 7.
lv_total = lv_total * '0.90'. " 10% Rabatt
ENDIF.
" Wert setzen
MODIFY ENTITIES OF zi_travel IN LOCAL MODE
ENTITY Travel
UPDATE FIELDS ( TotalPrice )
WITH VALUE #( ( %tky = ls_travel-%tky
TotalPrice = lv_total ) ).
ENDLOOP.
ENDMETHOD.

Ejemplos de Trigger:

" Bei CREATE ausführen
determination setDefaults on modify { create; }
" Bei UPDATE ausführen
determination recalculate on modify { update; }
" Bei CREATE und UPDATE
determination calculate on modify { create; update; }
" Nur wenn bestimmte Felder geändert werden
determination updateDependentFields on modify {
field Price, Quantity, Discount;
}
" Bei SAVE (kurz vor DB-Commit)
determination finalizeData on save { create; update; }

Ejemplo 3: Establecer Campos Dependientes (on save)

// Behavior Definition
define behavior for ZI_Travel alias Travel
{
// Kurz vor Save: Approval-Felder setzen
determination setApprovalData on save { update; }
}
METHOD setApprovalData.
READ ENTITIES OF zi_travel IN LOCAL MODE
ENTITY Travel
FIELDS ( Status ApprovedBy ApprovedAt )
WITH CORRESPONDING #( keys )
RESULT DATA(lt_travel).
MODIFY ENTITIES OF zi_travel IN LOCAL MODE
ENTITY Travel
UPDATE FIELDS ( ApprovedBy ApprovedAt )
WITH VALUE #( FOR travel IN lt_travel
WHERE ( Status = 'A' AND ApprovedBy IS INITIAL )
( %tky = travel-%tky
ApprovedBy = cl_abap_context_info=>get_user_name( )
ApprovedAt = cl_abap_context_info=>get_system_date( ) ) )
REPORTED DATA(update_reported).
ENDMETHOD.

Validations: Verificar Reglas de Negocio

¿Cuándo usar Validations?

Perfecto para:

  • Verificar campos obligatorios
  • Lógica de fechas (fecha fin después de inicio)
  • Verificaciones de rango de valores (descuento <= 100%)
  • Verificar claves foráneas (¿existe el cliente?)
  • Reglas de negocio complejas

NO para:

  • Establecer valores (-> Determination!)
  • Cálculos (-> Determination!)
  • Solo registrar warnings (-> OK, pero no como error)

Sintaxis en Behavior Definition

define behavior for ZI_Travel alias Travel
{
// Validation Syntax:
validation <MethodName> on save { <TriggerCondition>; }
// Trigger: IMMER "on save" (kurz vor DB-Commit)
// TriggerCondition: create, update, field <FieldName>
}

Ejemplo 1: Verificar Lógica de Fechas

// Behavior Definition
define behavior for ZI_Travel alias Travel
{
create;
update;
// Validation: Bei Save → Datum prüfen
validation validateDates on save {
field BeginDate, EndDate;
}
}
// Behavior Implementation
CLASS lhc_travel IMPLEMENTATION.
METHOD validateDates.
" 1. Daten lesen
READ ENTITIES OF zi_travel IN LOCAL MODE
ENTITY Travel
FIELDS ( TravelId BeginDate EndDate )
WITH CORRESPONDING #( keys )
RESULT DATA(lt_travel).
" 2. Jede Entity prüfen
LOOP AT lt_travel INTO DATA(ls_travel).
" Regel 1: EndDate muss nach BeginDate liegen
IF ls_travel-EndDate < ls_travel-BeginDate.
" FAILED füllen (technischer Fehler)
APPEND VALUE #(
%tky = ls_travel-%tky
%element-EndDate = if_abap_behv=>mk-on
) TO failed-travel.
" REPORTED füllen (Fehlermeldung für UI)
APPEND VALUE #(
%tky = ls_travel-%tky
%element-EndDate = if_abap_behv=>mk-on
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-error
text = |Enddatum ({ ls_travel-EndDate DATE = USER }) | &&
|muss nach Beginndatum ({ ls_travel-BeginDate DATE = USER }) liegen|
)
) TO reported-travel.
ENDIF.
" Regel 2: BeginDate darf nicht in Vergangenheit liegen
IF ls_travel-BeginDate < cl_abap_context_info=>get_system_date( ).
APPEND VALUE #(
%tky = ls_travel-%tky
%element-BeginDate = if_abap_behv=>mk-on
) TO failed-travel.
APPEND VALUE #(
%tky = ls_travel-%tky
%element-BeginDate = if_abap_behv=>mk-on
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-error
text = 'Reisebeginn darf nicht in der Vergangenheit liegen'
)
) TO reported-travel.
ENDIF.
ENDLOOP.
ENDMETHOD.
ENDCLASS.

¿Qué pasa con un error?

User: UPDATE Travel, EndDate = '20250101', BeginDate = '20250115'
(Determinations werden ausgeführt)
Validation: validateDates
→ Prüfung: EndDate < BeginDate? → JA!
→ FAILED-travel gefüllt
→ REPORTED-travel gefüllt
Framework: ROLLBACK (kein INSERT/UPDATE)
UI: Fehlermeldung anzeigen

Ejemplo 2: Verificar Clave Foránea

// Behavior Definition
define behavior for ZI_Travel alias Travel
{
validation validateCustomer on save {
field CustomerId;
}
}
METHOD validateCustomer.
READ ENTITIES OF zi_travel IN LOCAL MODE
ENTITY Travel
FIELDS ( CustomerId )
WITH CORRESPONDING #( keys )
RESULT DATA(lt_travel).
" Alle CustomerIds sammeln
DATA(lt_customer_ids) = VALUE string_table(
FOR travel IN lt_travel ( travel-CustomerId )
).
" Prüfen ob Kunden existieren (via Released API)
SELECT Customer
FROM I_Customer
FOR ALL ENTRIES IN @lt_customer_ids
WHERE Customer = @lt_customer_ids-table_line
INTO TABLE @DATA(lt_valid_customers).
" Fehler für nicht-existierende Kunden
LOOP AT lt_travel INTO DATA(ls_travel).
IF NOT line_exists( lt_valid_customers[ table_line = ls_travel-CustomerId ] ).
APPEND VALUE #(
%tky = ls_travel-%tky
%element-CustomerId = if_abap_behv=>mk-on
) TO failed-travel.
APPEND VALUE #(
%tky = ls_travel-%tky
%element-CustomerId = if_abap_behv=>mk-on
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-error
text = |Kunde { ls_travel-CustomerId } existiert nicht|
)
) TO reported-travel.
ENDIF.
ENDLOOP.
ENDMETHOD.

Ejemplo 3: Regla de Negocio Compleja

// Validation: Rabatt nur für Premium-Kunden
validation validateDiscount on save {
field Discount, CustomerId;
}
METHOD validateDiscount.
READ ENTITIES OF zi_travel IN LOCAL MODE
ENTITY Travel
FIELDS ( CustomerId Discount TotalPrice )
WITH CORRESPONDING #( keys )
RESULT DATA(lt_travel).
" Kundenkategorien laden
SELECT Customer, CustomerClassification
FROM I_Customer
FOR ALL ENTRIES IN @lt_travel
WHERE Customer = @lt_travel-CustomerId
INTO TABLE @DATA(lt_customers).
LOOP AT lt_travel INTO DATA(ls_travel).
" Regel: > 10% Rabatt nur für Premium-Kunden (Kategorie 'A')
IF ls_travel-Discount > 10.
DATA(ls_customer) = VALUE #( lt_customers[ Customer = ls_travel-CustomerId ] OPTIONAL ).
IF ls_customer-CustomerClassification <> 'A'.
APPEND VALUE #(
%tky = ls_travel-%tky
%element-Discount = if_abap_behv=>mk-on
) TO failed-travel.
APPEND VALUE #(
%tky = ls_travel-%tky
%element-Discount = if_abap_behv=>mk-on
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-error
text = |Rabatt über 10% nur für Premium-Kunden (aktuell: { ls_customer-CustomerClassification })|
)
) TO reported-travel.
ENDIF.
ENDIF.
" Regel 2: Rabatt max. 50% des Gesamtpreises
IF ls_travel-Discount > ( ls_travel-TotalPrice / 2 ).
APPEND VALUE #(
%tky = ls_travel-%tky
%element-Discount = if_abap_behv=>mk-on
) TO failed-travel.
APPEND VALUE #(
%tky = ls_travel-%tky
%element-Discount = if_abap_behv=>mk-on
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-error
text = 'Rabatt darf maximal 50% des Gesamtpreises betragen'
)
) TO reported-travel.
ENDIF.
ENDLOOP.
ENDMETHOD.

Timing: on modify vs on save

on modify (solo Determination!)

determination calculate on modify { create; update; }

Cuándo se ejecuta:

  • Inmediatamente después de la operación CREATE/UPDATE
  • ANTES de que se ejecute la Validation
  • Puede ser múltiples veces (con cada Modify)

Casos de uso:

  • Establecer valores por defecto inmediatamente
  • Calcular campos que se verifican en Validation
  • Feedback al usuario (campo se llena inmediatamente en UI)

on save (Determination y Validation!)

determination finalize on save { update; }
validation validateData on save { field Status; }

Cuándo se ejecuta:

  • Justo antes del DB-Commit
  • DESPUÉS de todas las Determinations on modify
  • Solo 1 vez por ciclo de Save

Casos de uso Determination:

  • Cálculos finales (ej. checksums)
  • Campos de auditoría (LastChangedBy, LastChangedAt)
  • Asignación de números desde rangos de números DB

Casos de uso Validation:

  • Verificar reglas de negocio
  • Asegurar consistencia
  • Validar claves foráneas

Orden de Ejecución

User Action: CREATE/UPDATE
┌───────────────────────────────────────┐
│ 1. DETERMINATION on modify (create) │ ← Sofort
└───────────────┬───────────────────────┘
┌───────────────────────────────────────┐
│ 2. DETERMINATION on modify (update) │ ← Bei jedem Update
└───────────────┬───────────────────────┘
┌───────────────────────────────────────┐
│ User: COMMIT ENTITIES aufrufen │
└───────────────┬───────────────────────┘
┌───────────────────────────────────────┐
│ 3. DETERMINATION on save │ ← Kurz vor DB
└───────────────┬───────────────────────┘
┌───────────────────────────────────────┐
│ 4. VALIDATION on save │ ← Prüfungen
└───────────────┬───────────────────────┘
├─ Fehler? → ROLLBACK, User sieht Fehlermeldung
▼ Erfolg
┌───────────────────────────────────────┐
│ 5. INSERT/UPDATE in Datenbank │ ← Persistent!
└───────────────────────────────────────┘

Ejemplo con ambos:

define behavior for ZI_Travel alias Travel
{
create;
update;
// Schritt 1: Sofort Status setzen
determination setInitialStatus on modify { create; }
// Schritt 2: Bei Feld-Änderung neu berechnen
determination calculateTotal on modify { field BeginDate, EndDate; }
// Schritt 3: Vor Save finale Werte
determination setApprovalData on save { update; }
// Schritt 4: Validierungen
validation validateDates on save { field BeginDate, EndDate; }
validation validateCustomer on save { field CustomerId; }
}

Diseñar Mensajes de Error Correctamente

Niveles de Severity

" ERROR: Blockiert Save
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-error
text = 'Enddatum muss nach Beginndatum liegen'
)
" WARNING: Erlaubt Save, zeigt Warnung
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-warning
text = 'Reise ist sehr kurz (< 3 Tage)'
)
" INFO: Nur Information
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-information
text = 'Rabatt wurde automatisch angewendet'
)
" SUCCESS: Positive Bestätigung
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-success
text = 'Reise erfolgreich genehmigt'
)

Mensaje con Message Class

" Statt Hardcoded-Text → Message Class nutzen
APPEND VALUE #(
%tky = ls_travel-%tky
%element-EndDate = if_abap_behv=>mk-on
%msg = new_message(
id = 'ZTRAVEL_MSG' " Message Class (SE91)
number = '001' " Message Number
severity = if_abap_behv_message=>severity-error
v1 = ls_travel-BeginDate " Platzhalter &1
v2 = ls_travel-EndDate " Platzhalter &2
)
) TO reported-travel.
" In SE91: Message ZTRAVEL_MSG/001
" Text: "Enddatum &2 muss nach Beginndatum &1 liegen"

Marcar Múltiples Campos

" Fehler betrifft BeginDate UND EndDate
APPEND VALUE #(
%tky = ls_travel-%tky
%element-BeginDate = if_abap_behv=>mk-on
%element-EndDate = if_abap_behv=>mk-on
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-error
text = 'Reisezeitraum ungültig'
)
) TO reported-travel.
" UI markiert BEIDE Felder rot

Mejores Prácticas

Correcto: Determinations

" ✅ Werte setzen, nicht prüfen
METHOD setDefaults.
MODIFY ENTITIES OF zi_travel IN LOCAL MODE
ENTITY Travel
UPDATE FIELDS ( Status Currency )
WITH VALUE #( FOR key IN keys
( %tky = key-%tky
Status = 'O'
Currency = 'EUR' ) ).
ENDMETHOD.
" ✅ on modify für User Feedback
determination calculatePrice on modify { field Quantity, UnitPrice; }
" → User sieht sofort neuen Preis
" ✅ on save für finale Werte
determination generateDocumentNumber on save { create; }
" → Nummer erst bei finalem Save vergeben

Correcto: Validations

" ✅ Nur prüfen, nicht ändern
METHOD validateAmount.
READ ENTITIES OF zi_travel IN LOCAL MODE
ENTITY Travel FIELDS ( Amount ) WITH CORRESPONDING #( keys )
RESULT DATA(lt_travel).
LOOP AT lt_travel INTO DATA(ls_travel) WHERE Amount <= 0.
APPEND VALUE #( %tky = ls_travel-%tky ) TO failed-travel.
APPEND VALUE #(
%tky = ls_travel-%tky
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-error
text = 'Betrag muss größer als 0 sein'
)
) TO reported-travel.
ENDLOOP.
ENDMETHOD.
" ✅ Performance: Bulk-Read statt Loop
SELECT Customer FROM I_Customer
FOR ALL ENTRIES IN @lt_travel
WHERE Customer = @lt_travel-CustomerId
INTO TABLE @DATA(lt_valid).
" ✅ Sprechende Fehlermeldungen
text = |Kunde { ls_travel-CustomerId } existiert nicht im System|
" Statt: "Validierung fehlgeschlagen"

Evitar

" ❌ NICHT in Validation ändern
METHOD validateData.
" FALSCH: Validation darf NICHT modifizieren!
MODIFY ENTITIES OF zi_travel IN LOCAL MODE
ENTITY Travel UPDATE FIELDS ( Status ) WITH ...
" → Kann zu Inkonsistenzen führen!
ENDMETHOD.
" ❌ NICHT in Determination prüfen und abbrechen
METHOD setDefaults.
IF ls_travel-Amount <= 0.
" FALSCH: Determination soll nicht validieren!
APPEND ... TO failed-travel. " ❌ Nutze Validation!
ENDIF.
ENDMETHOD.
" ❌ NICHT on modify für DB-Zugriffe
determination getCustomerData on modify { field CustomerId; }
" → Jedes Mal wenn Feld ändert = DB-Call
" → Besser: on save (nur 1x vor Commit)
" ❌ NICHT generische Fehlermeldungen
text = 'Fehler' " ❌ Nicht hilfreich
text = 'Validation failed' " ❌ Was ist das Problem?
" → Konkret: "Enddatum muss nach Beginndatum liegen"

Testing

CLASS ltc_determinations DEFINITION FINAL FOR TESTING
DURATION SHORT RISK LEVEL HARMLESS.
PRIVATE SECTION.
DATA mo_env TYPE REF TO if_cds_test_environment.
METHODS:
setup,
teardown,
test_set_initial_status FOR TESTING,
test_calculate_total FOR TESTING.
ENDCLASS.
CLASS ltc_determinations IMPLEMENTATION.
METHOD setup.
mo_env = cl_cds_test_environment=>create( i_for_entity = 'ZI_Travel' ).
ENDMETHOD.
METHOD test_set_initial_status.
" Arrange: Travel ohne Status erstellen
MODIFY ENTITIES OF zi_travel
ENTITY Travel
CREATE FIELDS ( AgencyId CustomerId )
WITH VALUE #( ( %cid = 'T1' AgencyId = '001' CustomerId = '042' ) )
MAPPED DATA(mapped).
COMMIT ENTITIES.
" Act: Determination sollte Status gesetzt haben
READ ENTITIES OF zi_travel
ENTITY Travel FIELDS ( Status )
WITH VALUE #( ( %cid = 'T1' ) )
RESULT DATA(lt_travel).
" Assert: Status = 'O'
cl_abap_unit_assert=>assert_equals(
exp = 'O'
act = lt_travel[ 1 ]-Status
msg = 'Determination sollte Status auf O setzen'
).
ENDMETHOD.
METHOD test_calculate_total.
" Test für calculation determination
" ... (analog zu oben)
ENDMETHOD.
METHOD teardown.
ROLLBACK ENTITIES.
mo_env->destroy( ).
ENDMETHOD.
ENDCLASS.
CLASS ltc_validations DEFINITION FINAL FOR TESTING
DURATION SHORT RISK LEVEL HARMLESS.
PRIVATE SECTION.
DATA mo_env TYPE REF TO if_cds_test_environment.
METHODS:
setup,
test_validate_dates_error FOR TESTING,
test_validate_dates_success FOR TESTING.
ENDCLASS.
CLASS ltc_validations IMPLEMENTATION.
METHOD setup.
mo_env = cl_cds_test_environment=>create( i_for_entity = 'ZI_Travel' ).
ENDMETHOD.
METHOD test_validate_dates_error.
" Arrange: EndDate < BeginDate
MODIFY ENTITIES OF zi_travel
ENTITY Travel
CREATE FIELDS ( BeginDate EndDate )
WITH VALUE #( ( %cid = 'T1'
BeginDate = '20250615'
EndDate = '20250601' ) ) " FALSCH!
FAILED DATA(failed).
COMMIT ENTITIES
RESPONSE OF zi_travel
FAILED DATA(commit_failed)
REPORTED DATA(commit_reported).
" Assert: Fehler erwartet
cl_abap_unit_assert=>assert_not_initial(
act = commit_failed-travel
msg = 'Validation sollte Fehler melden'
).
" Message prüfen
cl_abap_unit_assert=>assert_bound(
act = commit_reported-travel[ 1 ]-%msg
msg = 'Fehlermeldung sollte vorhanden sein'
).
ENDMETHOD.
METHOD test_validate_dates_success.
" Arrange: Korrekte Daten
MODIFY ENTITIES OF zi_travel
ENTITY Travel
CREATE FIELDS ( BeginDate EndDate )
WITH VALUE #( ( %cid = 'T1'
BeginDate = '20250601'
EndDate = '20250615' ) )
FAILED DATA(failed).
COMMIT ENTITIES
RESPONSE OF zi_travel
FAILED DATA(commit_failed).
" Assert: Kein Fehler
cl_abap_unit_assert=>assert_initial(
act = commit_failed-travel
msg = 'Validation sollte erfolgreich sein'
).
ENDMETHOD.
ENDCLASS.

Notas Importantes / Mejores Prácticas

  • Determination = Establecer, Validation = Verificar: Nunca mezclar!
  • Tener en cuenta el Timing: on modify para feedback inmediato, on save para operaciones finales
  • Validation NO DEBE cambiar: Solo leer y llenar FAILED/REPORTED
  • Rendimiento: Operaciones bulk en lugar de loops con llamadas DB individuales
  • Mensajes descriptivos: Describir concretamente qué está mal
  • Marcar %element: Muestra al usuario exactamente qué campo tiene el problema
  • IN LOCAL MODE: Usar siempre en Behavior Implementations
  • Severity correcto: ERROR bloquea, WARNING permite Save
  • Usar Message Class: En lugar de textos hardcoded -> traducibilidad
  • Orden: Determinations -> Validations -> DB Save
  • Testear: Escribir Unit Tests para CADA Determination y Validation
  • Recopilar errores: Reportar todos los errores a la vez, no abortar en el primero

Recursos Adicionales