🔊
Narrador inactivo
Pulsa 🎙 en cualquier sección
Formación SAP · Edición 2026

Restful ABAP Programming Model
aplicado al ciclo de compras

Cinco fases — Solicitud, Pedido, Entrada de Mercancías, Verificación de Factura y Liquidación/Pago — implementadas con CDS, BDEF y RAP, expuestas como OData v4 y consumidas desde Fiori Elements. Con gemelo digital interactivo y narración por voz.

05  Fases P2P
8  Capítulos teóricos
5  Piezas RAP
16h  Duración
1 SOLICITUD 2 PEDIDO 3 ENTRADA 4 FACTURA 5 PAGO RAP CICLO P2P 2026 CDS BDEF IMPL SRVD SRVB
¿Por qué RAP?

De BAPI y dynpros a un Business Object declarativo

RAP es la respuesta moderna de SAP a la pregunta: cómo escribo un Business Object transaccional en ABAP que se exponga automáticamente como OData v4 y se renderice con Fiori Elements. La respuesta cabe en cinco archivos: una CDS de datos, un Behavior Definition, una implementación local, un Service Definition y un Service Binding. El framework se encarga del resto: locking, draft, validaciones, autorización, transaccionalidad.

Antes — BAPI + Dynpro + SAPGUI

Procedural, 2000+ líneas, lock manuales, sin draft, expuesto solo a usuarios SAPGUI. Mantener un nuevo campo era días de trabajo.

Ahora — RAP managed

200 líneas declarativas. CRUD generado. Draft, lock, validaciones, autorización: todo configuración. Service OData v4 listo en minutos.

Después — Clean Core

Mismo BO compilando en S/4 Cloud sin tocar nada. Cero modificaciones. Cero acoplamiento al estándar. Upgrade transparente.

Temario · 8 capítulos

Lo que aprenderás paso a paso

Ocho capítulos teóricos. Pulsa cualquier tarjeta para desplegar Teoría · Práctica · Ejemplo real en código.

1

Fundamentos RAP

Qué es, por qué reemplaza a BOPF, dónde encaja.
+
Teoría

RAP — Restful ABAP Programming Model — es el framework oficial de SAP, a partir de S/4HANA 1909, para construir Business Objects transaccionales ABAP, exponerlos como OData v4 y consumirlos desde Fiori Elements. Sustituye a BOPF (Business Object Processing Framework) y a SADL. Encaja en S/4HANA on-premise (con ABAP for Cloud Development opcional), en S/4HANA Cloud y en Steampunk/Embedded Steampunk.

Práctica — tres implementaciones

Managed: RAP genera el código de persistencia sobre una tabla DDIC creada por ti. Ideal para BOs greenfield. Unmanaged: tú escribes el CRUD entero en handlers; ideal para envolver BAPIs o código legacy. Managed with additional save: RAP gestiona la persistencia y tú añades lógica en el saver (típicamente para cross-BO updates).

Ejemplo — la declaración elige el tipo
BDEF
" 1) Managed — RAP persiste
managed implementation in class zbp_i_pr_head unique;

" 2) Unmanaged — yo persisto (llamo a BAPI)
unmanaged implementation in class zbp_i_gr_head unique;

" 3) Managed con saver adicional
managed with additional save implementation
  in class zbp_i_po_head unique;
2

CDS para RAP

Root, child, associations, composition, draft.
+
Teoría

El modelo de datos de un BO RAP se expresa como un grafo de CDS jerárquico. La raíz se declara con define root view entity; los hijos se enlazan a la raíz con composition [0..*] of (relación de pertenencia) y con association (relación a otros BOs). Cada nivel requiere un orden de keys consistente. Anotaciones obligatorias: @AccessControl.authorizationCheck, @Semantics.systemDateTime.* para los campos administrativos. Opcionales pero recomendadas: @UI.headerInfo, @UI.lineItem, @UI.identification, @Search.searchable.

Práctica — dibujar el grafo antes de codificar

En el papel: rectángulo "Cabecera" con keys → flecha "composition" → rectángulo "Items" con keys compuestas. Para cada caja, listar campos. Solo entonces se empieza a teclear.

Ejemplo — root + child con composition
CDS
@AccessControl.authorizationCheck: #CHECK
define root view entity ZI_Order
  as select from zorder
  composition [0..*] of ZI_Order_Item as _Item
{
  key order_id as OrderId,
      customer as Customer,
      _Item
}

define view entity ZI_Order_Item
  as select from zorder_item
  association to parent ZI_Order as _Order
    on $projection.OrderId = _Order.OrderId
{
  key order_id, key item_no,
      material, _Order
}
3

Behavior Definition

CRUD, field control, validations, determinations, actions.
+
Teoría

El .bdef.asbdef es la pieza más importante de un BO RAP: define qué se puede hacer de forma declarativa. Sus secciones esenciales son: tipo de implementación (managed/unmanaged); CRUD (create / update / delete); field control (readonly, mandatory, numbering); validations (reglas que se ejecutan en save); determinations (cálculos en modify u on save); actions (operaciones invocables); y las asociaciones con su semántica.

Práctica

Crear la BDEF desde ADT con clic derecho en la CDS → New Behavior Definition. ADT genera un esqueleto con el tipo y la entidad. Editar añadiendo CRUD, field control y validations. Activar.

Ejemplo — un BDEF mínimo pero completo
BDEF
managed implementation in class zbp_i_order unique;
strict ( 2 );

define behavior for ZI_Order alias Order
persistent table zorder
lock master
authorization master ( instance )
etag master LastChangedAt
{
  create; update; delete;

  field ( readonly, numbering:managed ) OrderId;
  field ( mandatory ) Customer;

  validation validateCustomer on save { create; update; }
  determination calcTotal     on modify { field _Item; }
  action ( features:instance ) Confirm result [1] $self;
  association _Item { create; }
}
4

Behavior Implementation

Local handlers (lhc) y local saver (lsc).
+
Teoría

La Behavior Implementation es la clase ABAP que da vida al BDEF. Se compone de dos tipos de clases locales: Local Handler Class (lhc): implementa los handlers de validations, determinations y actions. Recibe keys con las instancias afectadas; debe rellenar failed, mapped y reported. Local Saver Class (lsc): implementa el ciclo de save (cleanup, finalize, save_modified, adjust_numbers) — utilizado cuando hay efectos cruzados con otros BOs o numeración late.

Práctica

Pulsar F2 en una validation del BDEF abre el quick-fix "Implement validation in class…". ADT genera el método dentro de la lhc class. El cuerpo lee con READ ENTITIES OF … IN LOCAL MODE, comprueba, y rellena los appends a failed y reported.

Ejemplo — validation handler
ABAP
METHOD validateCustomer.
  READ ENTITIES OF zi_order IN LOCAL MODE
    ENTITY Order FIELDS ( Customer )
      WITH CORRESPONDING #( keys )
    RESULT DATA(ls).

  LOOP AT ls INTO DATA(l).
    IF l-Customer IS INITIAL.
      APPEND VALUE #( %tky = l-%tky ) TO failed-order.
      APPEND VALUE #(
        %tky = l-%tky
        %msg = NEW zcm_order(
                 textid   = zcm_order=>customer_missing
                 severity = if_abap_behv_message=>severity-error )
      ) TO reported-order.
    ENDIF.
  ENDLOOP.
ENDMETHOD.
5

Service Definition & Binding

Exponer como OData v4 UI o Web API.
+
Teoría

Dos archivos minúsculos publican el BO al mundo. El Service Definition (.srvd) selecciona qué CDS forman parte del API (la raíz expuesta y, opcionalmente, las hijas). El Service Binding (.srvb) hace la publicación y elige el sabor: OData v4 UI (con draft, anotaciones UI, para Fiori Elements) o OData v4 Web API (sin draft, para sistemas externos M2M).

Práctica

Crear el .srvd → New ▸ Service Definition. Crear el .srvb → New ▸ Service Binding tipo OData v4 UI. Activar, Publicar local. Botón derecho en el binding → Open Fiori Elements App Preview.

Ejemplo — service definition + binding
SRVD
@EndUserText.label: 'Service de pedidos'
define service ZSD_ORDER {
  expose ZC_Order      as Order;
  expose ZC_Order_Item as OrderItem;
}

El binding se configura visualmente en ADT — propiedades: Binding Type = OData v4 UI, Service Name = ZUI_ORDER_O4, Category = Business Service. Publicar local.

6

Draft y locking

Borradores, ETag, optimista vs pesimista.
+
Teoría

Draft permite guardar borradores en una tabla paralela (_d) antes de activar el registro como persistente. Es indispensable en móvil y en formularios largos. Locking evita colisiones: optimista via ETag (compara timestamp/version al guardar; si cambió, falla con error 409) y pesimista con lock SAP clásico (reserva el registro durante la edición). RAP elige el comportamiento según las cláusulas etag y lock del BDEF.

Práctica

Para habilitar draft en un BO managed: añadir with draft al inicio del BDEF, crear la tabla draft (ZORDER_D con misma estructura + 4 campos administrativos draft), añadir draft table … draft action Edit/Activate/Discard/Resume/Prepare. Para locking optimista: definir etag master LastChangedAt.

Ejemplo — habilitar draft
BDEF
managed implementation in class zbp_i_order unique;
with draft;

define behavior for ZI_Order
persistent table zorder
draft table zorder_d
lock master total etag LocalLastChangedAt
etag master LastChangedAt
{
  create; update; delete;

  draft action Edit;
  draft action Activate optimized;
  draft action Discard;
  draft action Resume;
  draft determine action Prepare;
}
7

Autorización (DCL)

Aspect pfcg_auth, instance authorization.
+
Teoría

DCL (Data Control Language) define en una vista define role las reglas de visibilidad por fila. El aspecto pfcg_auth mapea un objeto de autorización SAP clásico a campos de la CDS. Para autorizaciones por instancia (ej: "puedo aprobar éste pero no aquél"), el BDEF declara authorization master ( instance ) y la implementación expone un handler FOR INSTANCE AUTHORIZATION que decide por cada combinación key + acción.

Práctica

Crear archivo .dcls.asdcls al lado de la CDS. Definir define role. Activar. La DCL se aplica automáticamente cuando se consulta la CDS desde RAP/Fiori. Verificar el efecto cambiando los datos del usuario (SU01) y reabriendo la app.

Ejemplo — limitar por sociedad y organización compras
DCL
@MappingRole: true
define role ZI_PO_Auth {
  grant select on ZI_PO_Head
    where ( CompanyCode )      = aspect pfcg_auth(
                                  M_BEST_BSA, BUKRS, ACTVT = '03' )
      and ( PurchasingOrg )    = aspect pfcg_auth(
                                  M_BEST_EKO, EKORG, ACTVT = '03' );
}
8

Eventos y composición

Cross-BO, Event Mesh, numbering.
+
Teoría

Composition: la relación cabecera-items dentro del mismo BO, declarada en CDS como composition; transaccional (un commit atómico). Eventos: notificaciones publicadas a SAP Event Mesh cuando ocurre algo significativo; consumibles desde BTP, AWS, Azure, otros SAP. Numbering: early (cliente asigna), late (server asigna al save), managed (RAP asigna).

Práctica

Declarar eventos en el BDEF con event. Implementar la publicación en el handler que corresponda (saver finalize o action). Mapear el evento en Communication Arrangement → Event Mesh queue. Consumir desde una iflow CPI con adaptador AMQP.

Ejemplo — publicar un evento de negocio
BDEF
define behavior for ZI_PO_Head
{
  create; update; delete;
  action SendToSupplier result [1] $self;

  // declarar el evento
  event POSent
    parameter ZS_POSentParam;
}
ABAP
" en el handler SendToSupplier:
RAISE ENTITY EVENT ZI_PO_Head~POSent
  FROM VALUE #( ( %key   = ls_po-%key
                  %param = VALUE #(
                    PoId     = ls_po-PoId
                    Supplier = ls_po-Supplier ) ) ).
Anatomía

Un BO RAP en 5 piezas

Independientemente de la complejidad, todo Business Object RAP se compone exactamente de estas cinco piezas. Si entiendes éstas, entiendes RAP.

1
CDS Data Model

Estructura de datos: cabecera, items, asociaciones, composición.

.ddls.asddls
Para qué sirve

Define la forma de los datos del BO y el grafo de relaciones. Es el "esqueleto" sobre el que se monta todo lo demás.

Reglas básicas
  • La raíz: define root view entity.
  • Hijos enganchados con composition [0..*] of y association to parent.
  • Anotación obligatoria: @AccessControl.authorizationCheck: #CHECK.
  • Campos administrativos con @Semantics.systemDateTime.*.
  • Si quieres draft: provider contract transactional_query.
Archivo mínimo
@AccessControl.authorizationCheck: #CHECK
define root view entity ZI_Order
  as select from zorder
  composition [0..*]
        of ZI_Order_Item as _Item
{
  key order_id,
      customer,
      _Item
}
2
Behavior Definition

Qué se puede hacer: CRUD, actions, validations, draft.

.bdef.asbdef
Para qué sirve

Declara las reglas: qué operaciones se permiten, qué campos son obligatorios, qué validaciones, qué actions, si hay draft, cómo se hace lock.

Estructura típica
  • Cabecera: managed implementation in class …
  • Cuerpo: define behavior for ZI_Xxx con persistent table, lock, authorization, etag.
  • CRUD: create / update / delete.
  • Field control: readonly / mandatory / numbering.
  • Lógica declarada: validation / determination / action.
Archivo mínimo
managed implementation in class
       zbp_i_order unique;
strict ( 2 );

define behavior for ZI_Order alias Order
persistent table zorder
lock master
etag master LastChangedAt
{
  create; update; delete;
  field ( mandatory ) Customer;
  association _Item { create; }
}
3
Behavior Implementation

Lógica ABAP de validations, determinations, actions.

CLASS lhc_*
Para qué sirve

Es el código. Implementa todo lo que el BDEF declara: cada validation, cada determination, cada action.

Anatomía de la clase
  • lhc — Local Handler Class: validations, determinations, actions.
  • lsc — Local Saver Class: finalize, adjust_numbers, save_modified.
  • Cada handler recibe keys y rellena failed, mapped, reported.
  • Las lecturas usan READ ENTITIES OF … IN LOCAL MODE.
  • Los cambios, MODIFY ENTITIES OF … IN LOCAL MODE.
Esqueleto
CLASS lhc_order DEFINITION
  INHERITING FROM
  cl_abap_behavior_handler.

  PRIVATE SECTION.
    METHODS validateCustomer
      FOR VALIDATE ON SAVE
        IMPORTING keys
        FOR Order~validateCustomer.
ENDCLASS.

CLASS lhc_order IMPLEMENTATION.
  METHOD validateCustomer.
    " lectura, chequeo, append failed/reported
  ENDMETHOD.
ENDCLASS.
4
Service Definition

Selección de qué CDS se exponen como API.

.srvd.srvd
Para qué sirve

Selecciona qué entidades CDS forman parte del API. Es la "lista de invitados". Sin él, las CDS no son visibles desde fuera, aunque el BDEF las soporte.

Conceptos
  • Un service puede agrupar varias raíces relacionadas.
  • Cada entidad expuesta puede tener alias.
  • Puedes ocultar parte del modelo (ej. tablas de auditoría).
Archivo mínimo
@EndUserText.label: 'Service de pedidos'
define service ZSD_ORDER {
  expose ZC_Order      as Order;
  expose ZC_Order_Item as OrderItem;
}
5
Service Binding

Publica el servicio: OData v4 UI o Web API.

.srvb.srvb
Para qué sirve

Publica el service definition como un endpoint OData real, accesible por URL. Aquí eliges el sabor.

Tipos de binding
  • OData V4 UI — con draft, anotaciones UI, listo para Fiori Elements.
  • OData V4 Web API — sin draft, para M2M (sistemas externos).
  • OData V2 UI — compatibilidad con apps Fiori legadas.
  • InA — analítico, para SAP Analytics Cloud.
Tras activar

Clic derecho → Open Fiori Elements App Preview abre la app generada por SAP en runtime. Cero JavaScript escrito a mano.

Configuración (editor visual ADT)
// El .srvb se edita visualmente en ADT
// Propiedades clave:
Service Name      = ZUI_ORDER_O4
Binding Type      = OData V4 UI
Category          = Business Service
Version           = 0001

// Resultado en runtime:
URL = /sap/opu/odata4/sap/zui_order_o4/srvd/sap/zsd_order/0001/

// Endpoint accesible desde:
- Fiori Elements Preview
- Postman / curl
- BAS Apps
- SAP Build Apps
Regla mnemotécnica: "Datos · Reglas · Código · Selección · Publicación". CDS define los datos, BDEF las reglas, la implementación el código, Service Definition selecciona, Service Binding publica.
Caso de uso

El ciclo de compras P2P

Procure-to-Pay (P2P) es el ciclo más representativo del módulo MM/FI-AP: alguien necesita comprar, se aprueba, se pide, se recibe, se paga la factura y se ejecuta el pago al proveedor. En el estándar SAP esto se hace con transacciones distintas (ME51N, ME21N, MIGO, MIRO, F110). Vamos a reconstruir las cinco etapas como cinco Business Objects RAP encadenados por _PrecedingDocument.

1 Solicitud de Pedido EBAN · BANFN ME51N · ME52N Quien necesita pide. 2 Pedido de Compra EKKO · EKPO ME21N · ME22N Compromiso al proveedor. 3 Entrada Mercancías MKPF · MSEG / MATDOC MIGO Movimiento 101. 4 Verificación de Factura RBKP · RSEG MIRO 3-way match · FI. 5 Liquidación y Pago REGUH · REGUP · PAYR F110 · F-53 Pago automático · SEPA. PROCURE-TO-PAY · P2P · 5 FASES
Decisión de diseño: en el estándar S/4, estas cinco entidades viven en módulos distintos (MM-PUR, MM-IM, FI-AP, FI-BL/Tesorería) con BOs separados. Para didáctica, vamos a modelar cinco BOs RAP propios en paquete Z, encadenados por _PrecedingDocument. En un proyecto real, lo correcto sería consumir las APIs released de SAP (API_PURCHASEREQ_PROCESS_SRV, API_PURCHASEORDER_PROCESS_SRV, API_MATERIAL_DOCUMENT_SRV, API_SUPPLIERINVOICE_PROCESS_SRV, API_PAYMENTREQUEST_SRV). El didáctico no contradice el correcto; demuestra la mecánica RAP completa de extremo a extremo.
1

Solicitud de Pedido (Purchase Requisition)

EBAN · BANFN — Donde alguien dice "necesito comprar esto"
ME51N · ME52N · ME53N

SSD — Software Specification Document

Identificador
SSD-PUR-001 — Z_BO_PURCHASEREQ
Tipo
Business Object RAP — managed implementation
Sistema destino
S/4HANA 2023 (on-prem) y S/4HANA Cloud (Embedded Steampunk). Clean-Core compatible.
Objetivo de negocio
Permitir a un solicitante registrar la necesidad de compra de uno o varios materiales con fecha requerida y centro de coste imputable, mantener borradores antes de liberar, y dejar el documento listo para conversión en pedido por un comprador.
Alcance funcional
CRUD sobre cabecera + items; draft habilitado; liberación opcional vía action ZRelease; numeración interna por intervalo NRSO BANFN; autorización por sociedad y grupo de compras.
Entidades
Cabecera ZPR_HEAD (req_id, requester, plant, status, created_by, created_at, last_changed_at, total_estimated) · Items ZPR_ITEM (req_id, item_no, material, qty, unit, unit_price, delivery_date, cost_center, currency)
Reglas
R1 — delivery_date ≥ hoy + 1. R2 — qty > 0. R3 — total_estimated = SUM(qty × unit_price) por items. R4 — la action ZRelease sólo es válida si status='DRAFT' y todos los items pasan R1–R2.
UX
Fiori Elements List Report + Object Page con sección de items, generados desde anotaciones @UI. Hot-key Ctrl+S guarda como draft.
APIs
OData v4 UI para Fiori Elements. OData v4 Web API para consumo M2M (Salesforce, portal proveedores) — sólo lectura y release.
Eventos publicados
ZPR_RELEASED a Event Mesh cuando se ejecuta ZRelease con éxito. Lo escucha el iflow de notificación al comprador.
Autorización
S_PR_BUKRS = sociedad; S_PR_EKGRP = grupo de compras. DCL por aspect pfcg_auth.
Tests
ABAP Unit ltcl_validations (R1–R4), ltcl_release_action, ltcl_authorization. Coverage objetivo > 80%.
Aceptación
UAT: usuario funcional crea SP con 3 items desde Fiori, guarda draft, edita, libera. Verifica EBAN populada y mensaje en SLG1.

Pasos en SAP / ADT

Crear paquete Z en ADT

En Eclipse → File ▸ New ▸ ABAP Package. Nombre ZPR_PURCHASEREQ, software component ZLOCAL, application component MM-PUR, structure package no. Asigna transport request.

Crear las tablas DDIC

Botón derecho sobre el paquete → New ▸ Database Table. Crea ZPR_HEAD y ZPR_ITEM con los campos del SSD. Campos administrativos obligatorios: client, created_by, created_at, last_changed_by, last_changed_at, local_last_changed_at.

Crear las CDS interface y consumption

Nuevo Data Definition ZI_PR_Head y ZI_PR_Item (interface, root y child con composition). Después ZC_PR_Head y ZC_PR_Item (consumption) con anotaciones @UI completas.

Definir el behavior

Botón derecho sobre ZI_PR_HeadNew Behavior Definition. Tipo Managed. Editar el .bdef tal como se ve en la pestaña BDEF.

Implementar la lógica

En la BDEF, F2 sobre validation y determination → ADT genera el esqueleto de la clase ZBP_I_PR_HEAD. Implementar handlers en la clase local lhc_PrHead.

Crear Service Definition y Service Binding

New ▸ Service Definition ZSD_PR. New ▸ Service Binding ZUI_PR_O4 tipo OData v4 UI. Activate, Publish.

Probar con Preview

Botón derecho sobre el Service Binding → Open Fiori Elements App Preview. SAP genera la app Fiori Elements en runtime. Verificar List Report + Object Page con secciones, sin haber escrito ni una línea de JavaScript.

Registrar el catalog/tile en FLP

Para producción: usar SAP Fiori Tools en BAS para empaquetar la app como BSP, generar manifest.json, registrar tile en catalog Z, asignar a grupo, y a rol PFCG.

Transportar

Liberar el transport request desde SE10 (on-prem) o vía gCTS si está configurado. Importar en QAS, ejecutar ATC y ABAP Unit, importar en PRD.

CDS — Data Model

Estructura jerárquica: una raíz (head) con composición hacia los items. Las anotaciones @UI controlarán el comportamiento de Fiori Elements en runtime.

CDS interface root
@AbapCatalog.sqlViewName: 'ZIPRHEAD'
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'PR head — interface'
@VDM.viewType: #BASIC
@ObjectModel.semanticKey: ['ReqId']
define root view entity ZI_PR_Head
  as select from zpr_head
  composition [0..*] of ZI_PR_Item as _Item
{
  key req_id              as ReqId,
      requester           as Requester,
      plant               as Plant,
      status              as Status,
      total_estimated     as TotalEstimated,
      currency            as Currency,
      // admin
      @Semantics.user.createdBy: true
      created_by          as CreatedBy,
      @Semantics.systemDateTime.createdAt: true
      created_at          as CreatedAt,
      @Semantics.user.lastChangedBy: true
      last_changed_by     as LastChangedBy,
      @Semantics.systemDateTime.lastChangedAt: true
      last_changed_at     as LastChangedAt,
      @Semantics.systemDateTime.localInstanceLastChangedAt: true
      local_last_changed_at as LocalLastChangedAt,
      _Item
}
CDS consumption + @UI
@UI.headerInfo: { typeName: 'Solicitud', typeNamePlural: 'Solicitudes',
                title: { value: 'ReqId' }, description: { value: 'Requester' } }
@UI.presentationVariant: [{ sortOrder: [{ by: 'ReqId', direction: #DESC }] }]
@Search.searchable: true
define root view entity ZC_PR_Head provider contract transactional_query
  as projection on ZI_PR_Head
{
      @UI.selectionField: [{ position: 10 }]
      @UI.lineItem: [{ position: 10, label: 'Solicitud' }]
      @UI.identification: [{ position: 10 }]
  key ReqId,

      @UI.lineItem: [{ position: 20, label: 'Solicitante' }]
      @UI.identification: [{ position: 20 }]
      Requester,

      @UI.lineItem: [{ position: 30, label: 'Centro' }]
      @UI.selectionField: [{ position: 20 }]
      Plant,

      @UI.lineItem: [{ position: 40, label: 'Estado',
                       criticality: 'StatusCriticality' }]
      Status,

      @UI.lineItem: [{ position: 50, label: 'Total estim.' }]
      @Semantics.amount.currencyCode: 'Currency'
      TotalEstimated,
      Currency,

      @UI.facet: [{ id: 'Items', type: #LINEITEM_REFERENCE,
                  label: 'Posiciones', targetElement: '_Item' }]
      _Item : redirected to composition child ZC_PR_Item
}

Behavior Definition (.bdef.asbdef)

Declara qué se puede hacer con el BO: CRUD, draft, field control, validations, determinations, actions. Cero lógica imperativa aquí.

BDEF managed
managed implementation in class zbp_i_pr_head unique;
strict ( 2 );
with draft;

define behavior for ZI_PR_Head alias PrHead
persistent table zpr_head
draft table zpr_head_d
lock master total etag LocalLastChangedAt
authorization master ( instance )
etag master LastChangedAt
{
  create;
  update;
  delete;

  field ( readonly, numbering : managed ) ReqId;
  field ( readonly ) CreatedBy, CreatedAt, LastChangedBy, LastChangedAt,
                    LocalLastChangedAt, TotalEstimated, Status;
  field ( mandatory ) Requester, Plant, Currency;

  // reglas de negocio
  validation validateHeader on save
    { create; update; }

  determination calculateTotal on modify
    { field _Item; }

  determination setStatusDraft on save
    { create; }

  // acción
  action ( features:instance, authorization:instance )
         Release result [1] $self;

  draft action Edit;
  draft action Activate optimized;
  draft action Discard;
  draft action Resume;
  draft determine action Prepare;

  // composición
  association _Item { create ( features:instance );
                       with draft; }
}

define behavior for ZI_PR_Item alias PrItem
persistent table zpr_item
draft table zpr_item_d
lock dependent by _Head
authorization dependent by _Head
{
  update;
  delete;

  field ( readonly, numbering:managed ) ReqId, ItemNo;
  field ( mandatory ) Material, Qty, Unit, UnitPrice, DeliveryDate, CostCenter;

  validation validateItem on save
    { create; update;
      field Qty, DeliveryDate, UnitPrice; }

  association _Head { with draft; }
}

Behavior Implementation — la clase ABAP

Aquí va la lógica ABAP de validations, determinations y actions. Cada handler responde a una entidad concreta. RAP entrega los registros afectados; el handler los lee, valida, y devuelve failed, mapped y reported.

CLASS lhc_PrHead
CLASS lhc_prhead DEFINITION INHERITING FROM cl_abap_behavior_handler.
  PRIVATE SECTION.
    METHODS:
      validateHeader FOR VALIDATE ON SAVE
        IMPORTING keys FOR PrHead~validateHeader,

      calculateTotal FOR DETERMINE ON MODIFY
        IMPORTING keys FOR PrHead~calculateTotal,

      setStatusDraft FOR DETERMINE ON SAVE
        IMPORTING keys FOR PrHead~setStatusDraft,

      Release FOR MODIFY
        IMPORTING keys FOR ACTION PrHead~Release RESULT result.
ENDCLASS.

CLASS lhc_prhead IMPLEMENTATION.

  METHOD validateHeader.
    READ ENTITIES OF zi_pr_head IN LOCAL MODE
      ENTITY PrHead
        FIELDS ( Requester Plant Currency )
        WITH CORRESPONDING #( keys )
      RESULT DATA(heads).

    LOOP AT heads INTO DATA(ls_head).
      IF ls_head-Requester IS INITIAL
         OR ls_head-Plant IS INITIAL
         OR ls_head-Currency IS INITIAL.
        APPEND VALUE #( %tky = ls_head-%tky ) TO failed-prhead.
        APPEND VALUE #(
          %tky = ls_head-%tky
          %msg = NEW zcm_pr(
                    textid = zcm_pr=>mandatory_missing
                    severity = if_abap_behv_message=>severity-error )
        ) TO reported-prhead.
      ENDIF.
    ENDLOOP.
  ENDMETHOD.

  METHOD calculateTotal.
    READ ENTITIES OF zi_pr_head IN LOCAL MODE
      ENTITY PrHead BY \_Item
        FIELDS ( Qty UnitPrice )
        WITH CORRESPONDING #( keys )
      RESULT DATA(items)
      LINK DATA(links).

    DATA updates TYPE TABLE FOR UPDATE zi_pr_head.

    LOOP AT links INTO DATA(lk) GROUP BY lk-source-%tky.
      DATA(total) = REDUCE p( INIT s = 0
                          FOR ls IN items
                            WHERE ( ReqId = lk-source-ReqId )
                          NEXT s = s + ls-Qty * ls-UnitPrice ).

      APPEND VALUE #(
        %tky = lk-source-%tky
        TotalEstimated = total
        %control-TotalEstimated = if_abap_behv=>mk-on
      ) TO updates.
    ENDLOOP.

    MODIFY ENTITIES OF zi_pr_head IN LOCAL MODE
      ENTITY PrHead UPDATE FIELDS ( TotalEstimated )
      WITH updates.
  ENDMETHOD.

  METHOD setStatusDraft.
    MODIFY ENTITIES OF zi_pr_head IN LOCAL MODE
      ENTITY PrHead UPDATE FIELDS ( Status )
      WITH VALUE #( FOR k IN keys
        ( %tky = k-%tky Status = 'DRAFT'
          %control-Status = if_abap_behv=>mk-on ) ).
  ENDMETHOD.

  METHOD Release.
    MODIFY ENTITIES OF zi_pr_head IN LOCAL MODE
      ENTITY PrHead UPDATE FIELDS ( Status )
      WITH VALUE #( FOR k IN keys
        ( %tky = k-%tky Status = 'RELEASED'
          %control-Status = if_abap_behv=>mk-on ) ).

    READ ENTITIES OF zi_pr_head IN LOCAL MODE
      ENTITY PrHead ALL FIELDS
        WITH CORRESPONDING #( keys )
      RESULT DATA(refreshed).
    result = VALUE #( FOR ls IN refreshed
                    ( %tky = ls-%tky %param = ls ) ).
  ENDMETHOD.

ENDCLASS.

🖥️ Gemelo Digital — Crear y liberar una Solicitud

Simula la app Fiori Elements que generaría RAP para este BO. Toca los botones, edita campos, guarda, libera. El estado se mantiene en memoria del navegador; sirve como demo sin necesidad de un sistema SAP.

Solicitudes de Pedido › Lista M. Álvarez MA

Solicitudes de Pedido

3 elementos · sincronizado hace 12s
SolicitudSolicitanteCentroItemsEstadoTotal estim.
10000123M. Álvarez10002Released1 240,00 EUR
10000122J. Pérez20001Draft340,00 EUR
10000121L. Gómez10003Released2 875,50 EUR
2

Pedido de Compra (Purchase Order)

EKKO · EKPO — El compromiso de compra al proveedor
ME21N · ME22N · ME23N

SSD — Software Specification Document

Identificador
SSD-PUR-002 — Z_BO_PURCHASEORDER
Tipo
RAP managed with additional save
Precedente
Asociación _PrecedingPR → ZI_PR_Head (debe estar en estado RELEASED).
Objetivo
Convertir una o varias solicitudes liberadas en un pedido de compra firme con proveedor seleccionado, condiciones de pago y vencimiento.
Reglas
R1 — proveedor obligatorio. R2 — precio neto ≤ presupuestado en PR + 10%. R3 — al guardar, mover status de PR a CONVERTED. R4 — action SendToSupplier dispara IDoc ORDERS05 y deja status=SENT.
Eventos
ZPO_RELEASED, ZPO_SENT a Event Mesh.
UX
List Report con filtros por proveedor, fecha y estado. Object Page con cabecera + items + histórico de cambios.
Autorización
S_PO_BUKRS, S_PO_EKORG, S_PO_EKGRP. DCL deriva sociedad del proveedor.
Aceptación
UAT: convertir SP-10000122 en PO, enviar al proveedor, verificar IDoc en WE02 y mensaje de éxito.

Pasos en SAP / ADT

Crear tablas DDIC

ZPO_HEAD (po_id, supplier, currency, payment_terms, status, ref_pr_id) y ZPO_ITEM (po_id, item_no, material, qty, unit, net_price, delivery_date).

CDS con asociación a la fase anterior

En ZI_PO_Head añadir association [0..1] to ZI_PR_Head as _PrecedingPR on $projection.RefPrId = _PrecedingPR.ReqId.

BDEF managed with additional save

Necesario porque en el saver hay que actualizar el status de la PR origen (interferencia con otro BO). Implementar finalize en la saver class.

Action SendToSupplier

Llama internamente a la API IDoc estándar (MASTER_IDOC_DISTRIBUTE en on-prem) o a una iflow CPI (en cloud). Marca el PO como SENT y publica el evento.

Determination para chequeo de presupuesto

Lee el TotalEstimated de la PR via association _PrecedingPR; calcula el delta; si supera el umbral, reporta error.

Probar en Preview de Fiori Elements

Verificar que la Object Page muestra la facet "PR original" con navegación de vuelta a la fase 1.

CDS — Modelo de pedido con asociación a PR

CDS root con asociación cross-BO
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'PO head — interface'
define root view entity ZI_PO_Head
  as select from zpo_head
  composition [0..*] of ZI_PO_Item as _Item
  association [0..1] to ZI_PR_Head as _PrecedingPR
    on $projection.RefPrId = _PrecedingPR.ReqId
  association [0..1] to I_Supplier as _Supplier
    on $projection.Supplier = _Supplier.Supplier
{
  key po_id           as PoId,
      supplier         as Supplier,
      currency         as Currency,
      payment_terms    as PaymentTerms,
      status           as Status,
      ref_pr_id        as RefPrId,
      total_net        as TotalNet,
      @Semantics.systemDateTime.createdAt: true
      created_at       as CreatedAt,
      @Semantics.systemDateTime.lastChangedAt: true
      last_changed_at  as LastChangedAt,
      _Item, _PrecedingPR, _Supplier
}

BDEF con additional save

BDEF
managed with additional save implementation in class zbp_i_po_head unique;
strict ( 2 );

define behavior for ZI_PO_Head alias PoHead
persistent table zpo_head
lock master
etag master LastChangedAt
{
  create;
  update;
  delete;

  field ( readonly, numbering:managed ) PoId;
  field ( readonly ) Status, TotalNet, CreatedAt, LastChangedAt;
  field ( mandatory ) Supplier, Currency, PaymentTerms;

  validation validateBudget on save { create; update; }
  determination setStatusOpen on save { create; }
  determination calcTotal on modify { field _Item; }

  action ( features:instance ) SendToSupplier
                                result [1] $self;
  action ( features:instance ) CancelOrder
                                result [1] $self;

  association _Item { create; }
}

Saver class — actualizar la PR origen

CLASS lsc_po_head_saver
CLASS lsc_po_head_saver DEFINITION INHERITING FROM
                              cl_abap_behavior_saver.
  PROTECTED SECTION.
    METHODS finalize REDEFINITION.
    METHODS adjust_numbers REDEFINITION.
ENDCLASS.

CLASS lsc_po_head_saver IMPLEMENTATION.

  METHOD finalize.
    " 1) leer todos los PO recién creados/modificados que tengan ref_pr_id
    READ ENTITIES OF zi_po_head IN LOCAL MODE
      ENTITY PoHead ALL INSTANCES
        FIELDS ( RefPrId Status )
      RESULT DATA(po_all).

    " 2) para cada PR origen, marcar status='CONVERTED'
    LOOP AT po_all INTO DATA(ls_po) WHERE RefPrId IS NOT INITIAL.
      MODIFY ENTITIES OF zi_pr_head IN LOCAL MODE
        ENTITY PrHead UPDATE FIELDS ( Status )
        WITH VALUE #( ( %tky = VALUE #( ReqId = ls_po-RefPrId )
                          Status = 'CONVERTED'
                          %control-Status = if_abap_behv=>mk-on ) ).
    ENDLOOP.
  ENDMETHOD.

  METHOD adjust_numbers.
    " Asignar PO_ID definitivos cuando la numeración es late.
    " En este caso usamos managed numbering, así que aquí no hace falta nada.
    RETURN.
  ENDMETHOD.

ENDCLASS.
METHOD SendToSupplier — la action
METHOD SendToSupplier.
  " 1) cambiar status a SENT
  MODIFY ENTITIES OF zi_po_head IN LOCAL MODE
    ENTITY PoHead UPDATE FIELDS ( Status )
    WITH VALUE #( FOR k IN keys
      ( %tky = k-%tky Status = 'SENT'
        %control-Status = if_abap_behv=>mk-on ) ).

  " 2) publicar evento
  READ ENTITIES OF zi_po_head IN LOCAL MODE
    ENTITY PoHead ALL FIELDS
      WITH CORRESPONDING #( keys )
    RESULT DATA(refreshed).

  LOOP AT refreshed INTO DATA(ls_po).
    cl_zpo_event_pub=>publish_sent(
      iv_po_id    = ls_po-PoId
      iv_supplier = ls_po-Supplier ).
  ENDLOOP.

  result = VALUE #( FOR ls IN refreshed
                  ( %tky = ls-%tky %param = ls ) ).
ENDMETHOD.

🖥️ Gemelo Digital — Convertir PR en Pedido y enviar al proveedor

Simulación de la app Fiori Elements para el BO Pedido. Selecciona la PR fuente, ajusta proveedor y precios, y observa la cadena de status hasta el envío de IDoc al proveedor.

Pedidos › 4500001234 M. Álvarez MA

Pedido 4500001234 · Open

Proveedor 100023 · Acme Industries · Convertido desde PR 10000122
8 425,00 EUR
Importe neto
2
Posiciones
+ 4,2%
vs presupuesto PR
GeneralPosicionesHistóricosAdjuntosRecepciones
Cabecera
Proveedor100023 · Acme Industries
Organización compras1000
Grupo comprasG01
MonedaEUR
Cond. pagoZB01 (30 días)
Fecha doc.22.05.2026
Posiciones
#MaterialCant.UMPrecioEntregaImporte
10MAT-001 · Tornillo M610UN35,5015.06.2026355,00
20MAT-002 · Engranaje 30T5UN125,0020.06.2026625,00
30MAT-009 · Caja embalaje50UN148,9020.06.20267 445,00
Estado del flujo
PR Liberada
Pedido creado
Pendiente envío
Enviado proveedor
Recepción
3

Entrada de Mercancías (Goods Receipt)

MKPF · MSEG (o MATDOC en S/4) — Movimiento 101 contra pedido
MIGO · MB01

SSD

Identificador
SSD-PUR-003 — Z_BO_GOODSRECEIPT
Tipo
RAP unmanaged — invoca BAPI estándar (BAPI_GOODSMVT_CREATE)
Por qué unmanaged
Crear movimientos de mercancía implica integraciones con WM/EWM, inventory, lote, características clave de SAP. No tiene sentido replicar la persistencia: se delega en la BAPI estándar.
Precedente
Asociación _PrecedingPO → ZI_PO_Head (status SENT).
Objetivo
Registrar la recepción física, validar contra cantidades pedidas, actualizar status del PO.
Reglas
R1 — qty_received ≤ qty_ordered. R2 — fecha de recepción no en futuro. R3 — actualizar status del PO. R4 — disparar movimiento 101.
Eventos
ZGR_POSTED publicado al confirmar.

Pasos en SAP / ADT

BDEF unmanaged

Para BO sobre BAPIs estándar. La persistencia la hace SAP, no tú. En el saver: llamar a BAPI_GOODSMVT_CREATE en finalize(), tras commit lógico.

CDS sobre tablas SAP

On-prem: SELECT FROM matdoc (S/4) o mkpf+mseg (ECC). Cloud: usar I_MaterialDocument released VDM.

Action PostGR — "Contabilizar"

a) Validar items contra PO. b) Construir tabla GOODSMVT_ITEM. c) BAPI_GOODSMVT_CREATE con GM_CODE='01'. d) BAPI_TRANSACTION_COMMIT WAIT='X'. e) Retornar mat_doc en RESULT.

Probar y depurar

SE37 con datos manuales primero. Después, ejecutar desde Fiori Preview. Si falla, revisar logs en SLG1.

CDS sobre documento de material

CDS unmanaged BO
@AccessControl.authorizationCheck: '#CHECK'
@EndUserText.label: 'GR head over MATDOC'
define root view entity ZI_GR_Head
  as select from matdoc
  association [0..1] to ZI_PO_Head as _PrecedingPO
    on $projection.RefPoId = _PrecedingPO.PoId
{
  key mblnr            as MaterialDocument,
  key mjahr            as DocumentYear,
      bldat            as DocumentDate,
      ebeln            as RefPoId,
      werks            as Plant,
      _PrecedingPO
}

BDEF unmanaged

BDEF
unmanaged implementation in class zbp_i_gr_head unique;
strict ( 2 );

define behavior for ZI_GR_Head alias GrHead
lock master
{
  create;
  action ( features:instance ) PostGR result [1] $self;
  field ( mandatory ) RefPoId, Plant;
  field ( readonly, numbering:managed ) MaterialDocument, DocumentYear;
  validation validateAgainstPO on save { create; }
}

Implementación — BAPI_GOODSMVT_CREATE

METHOD PostGR
METHOD PostGR.
  DATA: ls_header TYPE bapi2017_gm_head_01,
        ls_code   TYPE bapi2017_gm_code,
        lt_items  TYPE TABLE OF bapi2017_gm_item_create,
        lt_return TYPE TABLE OF bapiret2,
        ls_ret    TYPE bapi2017_gm_head_ret.

  ls_code = VALUE #( gm_code = '01' ).
  ls_header = VALUE #( pstng_date = sy-datum doc_date = sy-datum ).

  CALL FUNCTION 'BAPI_GOODSMVT_CREATE'
    EXPORTING goodsmvt_header = ls_header
                goodsmvt_code   = ls_code
    IMPORTING goodsmvt_headret = ls_ret
    TABLES    goodsmvt_item    = lt_items
                return           = lt_return.

  IF line_exists( lt_return[ type = 'E' ] ).
    CALL FUNCTION 'BAPI_TRANSACTION_ROLLBACK'.
  ELSE.
    CALL FUNCTION 'BAPI_TRANSACTION_COMMIT' EXPORTING wait = 'X'.
  ENDIF.
ENDMETHOD.

🖥️ Gemelo Digital — Recibir mercancía contra PO

Ajusta cantidades recibidas, contabiliza y observa el movimiento 101.

Entrada de Mercancías › PO 4500001234 M. Álvarez MA

Contabilizar entrada de mercancías

Pedido 4500001234 · Acme Industries · Listo para recibir
Posiciones a recibir
#MaterialPedidoRecibidoUMAlmacénOK
10MAT-001 · Tornillo M610 UN0001 - Materiales OK
20MAT-002 · Engranaje 30T5 UN0001 - Materiales OK
30MAT-009 · Caja embalaje50 UN0002 - Embalajes Parcial
Resultado del movimiento
PO enviado
Recibiendo
Movimiento 101
PO actualizado
4

Verificación de Factura (Invoice Verification)

RBKP · RSEG — 3-way match: PO vs GR vs Invoice
MIRO · MIR4 · MIR7

SSD

Identificador
SSD-PUR-004 — Z_BO_SUPPLIERINVOICE
Tipo
RAP unmanaged — invoca BAPI_INCOMINGINVOICE_CREATE
Precedentes
Asociación _PrecedingPO y _PrecedingGR.
Objetivo
Validar la factura contra PO y GR, contabilizar en FI-AP si coincide.
Reglas
R1 — invoice_amount = Σ(GR_qty × PO_price) ± tolerancia 1%. R2 — moneda factura = moneda PO. R3 — al contabilizar, generar documento BKPF.
Eventos
ZINV_POSTED publicado al contabilizar.
UX
Object Page con widget 3-way match (semáforo). Adjunto del PDF.

Pasos en SAP / ADT

Diseñar el widget 3-way match en la CDS

Calcular en CDS los tres totales (sumar EKPO, MSEG, RSEG) y exponerlos como campos virtuales con anotación @UI.dataPoint.

BDEF unmanaged con action PostInvoice

Determination de validación en save: si delta > tolerancia, status='HOLD', si no, llamar BAPI_INCOMINGINVOICE_CREATE.

Documento FI

El BAPI genera el documento FI con BELNR.

Event hooks para Cash Management

Publicar evento ZINV_POSTED a Event Mesh.

CDS con 3-way match

CDS con dataPoints
@AccessControl.authorizationCheck: '#CHECK'
define root view entity ZI_Invoice_Head
  as select from zinv_head
  association [0..1] to ZI_PO_Head as _PrecedingPO
    on $projection.RefPoId = _PrecedingPO.PoId
{
  key invoice_id        as InvoiceId,
      supplier_invoice  as SupplierInvoice,
      ref_po_id         as RefPoId,
      invoice_amount    as InvoiceAmount,
      currency          as Currency,
      status            as Status,
      @UI.dataPoint: { title: 'PO total',
                       criticality: 'MatchCriticality' }
      _PrecedingPO.TotalNet  as PoTotal,
      _PrecedingPO
}

BDEF

BDEF unmanaged
unmanaged implementation in class zbp_i_invoice_head unique;
strict ( 2 );

define behavior for ZI_Invoice_Head alias Invoice
lock master
{
  create;
  field ( mandatory ) Supplier, SupplierInvoice, InvoiceAmount, Currency;
  field ( readonly ) Status, FiDocNo;

  action ( features:instance ) PostInvoice result [1] $self;
  action ( features:instance ) Release     result [1] $self;
  validation threeWayMatch on save { create; }
}

Implementación — 3-way match

VALIDATION threeWayMatch
METHOD threeWayMatch.
  READ ENTITIES OF zi_invoice_head IN LOCAL MODE
    ENTITY Invoice ALL FIELDS
      WITH CORRESPONDING #( keys )
    RESULT DATA(invs).

  LOOP AT invs INTO DATA(ls_inv).
    SELECT SUM( gr~entry_qnt * po~net_price )
      FROM mseg AS gr
      JOIN ekpo AS po
        ON gr~ebeln = po~ebeln AND gr~ebelp = po~ebelp
      WHERE gr~mblnr = @ls_inv-RefGrId AND gr~bwart = '101'
      INTO @DATA(expected).

    DATA(delta) = abs( ls_inv-InvoiceAmount - expected ).
    IF delta > expected * 0.01.
      MODIFY ENTITIES OF zi_invoice_head IN LOCAL MODE
        ENTITY Invoice UPDATE FIELDS ( Status )
        WITH VALUE #( ( %tky = ls_inv-%tky Status = 'HOLD'
                          %control-Status = if_abap_behv=>mk-on ) ).
    ENDIF.
  ENDLOOP.
ENDMETHOD.

🖥️ Gemelo Digital — 3-way match e Invoice Posting

Modifica el importe factura y observa cómo el semáforo se vuelve rojo si supera la tolerancia del 1%.

Verificación Factura › 5105600234 M. Álvarez MA

Factura 5105600234 · PENDING

INV-2026-A0091 · Acme · Documento FI:
Cabecera factura
Proveedor100023 · Acme Industries
Pedido4500001234
GR ref.5000023412
Importe factura EUR
3-Way Match
PO
Pedido
4500001234
8 425,00 EUR
GR
Entrada mercancías
5000023412
8 425,00 EUR
INV
Factura
INV-2026-A0091
8 425,00 EUR
✓ 3-Way Match correcto · Delta 0,00 EUR · Tolerancia ±84,25 EUR
Estado del proceso
GR contabilizada
Factura recibida
3-way match
Contab. FI
Pago
5

Liquidación y Pago (Payment Run)

REGUH · REGUP · PAYR — Pago automático SEPA, cheque o transferencia manual
F110 · F-53 · F-58

SSD

Identificador
SSD-PUR-005 — Z_BO_PAYMENTRUN
Tipo
RAP managed (propuesta y selección) + integración batch con SAP estándar (F110 / fichero SEPA)
Precedente
Asociación _PrecedingInvoices [0..*] → ZI_Invoice_Head (status = POSTED, fecha vencimiento ≤ fecha pago).
Objetivo
Crear una propuesta de pago, agruparla por proveedor y banco emisor, permitir excluir/ajustar manualmente, ejecutar la contabilización FI y generar el fichero SEPA XML.
Reglas
R1 — solo facturas POSTED, no HOLD. R2 — fecha vencimiento ≤ payment_date. R3 — agrupación por (Supplier × HouseBank × Currency). R4 — descuento por pronto pago si invoice_date + cash_disc_days ≥ payment_date. R5 — método de pago compatible (T/S/C). R6 — bloque de pago vacío en LFB1-ZAHLS.
Eventos
ZPAY_PROPOSED, ZPAY_EXECUTED, ZPAY_FAILED.
UX
Lista de propuestas pendientes con KPI total. Detalle por proveedor con check incluir/excluir. Vista previa SEPA antes de confirmar.
Autorización
F_BKPF_BUK, F_REGU_BUK, F_REGU_KOA, F_T042_FBT. DCL filtra por sociedad.
Compliance
Logs de auditoría inmutables (ZPAY_LOG). 4-ojos obligatorio: created_by ≠ approved_by.
Aceptación
UAT: crear propuesta para sociedad 1000, ver facturas, excluir una, aprobar (otro usuario), ejecutar; verificar BSEG, REGUH, fichero SEPA en AL11.

Pasos en SAP / ADT

Tablas Z de propuesta

ZPAY_HEAD (run_id, run_date, payment_date, company_code, status, created_by, approved_by) y ZPAY_ITEM (run_id, item_no, supplier, invoice_id, fi_doc, amount, discount, net_amount, house_bank, payment_method, included).

CDS root + child + asociación al BO Invoice

ZI_PaymentRun_Head con composition a ZI_PaymentRun_Item. En el item, association to ZI_Invoice_Head as _Invoice. En cabecera, association to I_CompanyCode.

BDEF managed con actions Propose / Approve / Execute

Tres actions: Propose (precarga facturas elegibles), Approve (4-eyes), Execute (contabiliza FI + genera SEPA).

Determination 4-eyes

Validation impide ejecutar si created_by = approved_by o approved_by IS INITIAL.

Action Execute

Recorre items incluidos: BAPI_ACC_DOCUMENT_POST con asiento (Acreedor | Banco). Tras éxito, agrupa por banco y genera fichero SEPA XML via cl_dmee_facade.

Configuración SPRO previa

Sociedad de pago (T042B), Variante de pago (T042Z), Formato DMEE (árbol DMEE2 para SEPA), Banco origen (T012/T012K) con IBAN.

Probar en Fiori Elements Preview

Crear propuesta 25.05.2026 sociedad 1000, ver facturas precargadas, excluir alguna, aprobar (otro usuario), ejecutar. Verificar fichero en AL11.

CDS — Propuesta de pago con asociaciones múltiples

CDS root
@AccessControl.authorizationCheck: '#CHECK'
@UI.headerInfo: { typeName: 'Propuesta pago', typeNamePlural: 'Propuestas' }
define root view entity ZI_PaymentRun_Head
  provider contract transactional_query
  as select from zpay_head
  composition [0..*] of ZI_PaymentRun_Item as _Item
  association [0..1] to I_CompanyCode as _CompanyCode
    on $projection.CompanyCode = _CompanyCode.CompanyCode
{
  key run_id           as RunId,
      run_date         as RunDate,
      payment_date     as PaymentDate,
      company_code     as CompanyCode,
      status           as Status,
      @Semantics.amount.currencyCode: 'Currency'
      total_amount     as TotalAmount,
      currency         as Currency,
      created_by       as CreatedBy,
      approved_by      as ApprovedBy,
      _Item, _CompanyCode
}
CDS child con asociación a Invoice
define view entity ZI_PaymentRun_Item
  as select from zpay_item
  association to parent ZI_PaymentRun_Head as _Head
    on $projection.RunId = _Head.RunId
  association [1..1] to ZI_Invoice_Head as _Invoice
    on $projection.InvoiceId = _Invoice.InvoiceId
{
  key run_id, key item_no,
      supplier, invoice_id, fi_doc,
      amount, discount, net_amount,
      house_bank, payment_method, included,
      _Head, _Invoice
}

BDEF — propuesta con 4-eyes y actions

BDEF
managed implementation in class zbp_i_paymentrun_head unique;
strict ( 2 );

define behavior for ZI_PaymentRun_Head alias PayRun
persistent table zpay_head
lock master
authorization master ( instance )
etag master LastChangedAt
{
  create; update; delete;

  field ( readonly, numbering:managed ) RunId;
  field ( readonly ) Status, TotalAmount, CreatedBy, ApprovedBy;
  field ( mandatory ) CompanyCode, PaymentDate, Currency;

  validation validateRunDate on save { create; update; }
  validation validateFourEyes on save { update; field ApprovedBy; }
  determination setInitialStatus on save { create; }
  determination recalcTotal      on modify { field _Item; }

  action ( features:instance ) Propose parameter ZS_ProposeParam result [1] $self;
  action ( features:instance ) Approve result [1] $self;
  action ( features:instance ) Execute result [1] $self;
  action ( features:instance ) Cancel  result [1] $self;

  event PaymentProposed parameter ZS_PaymentEventParam;
  event PaymentExecuted parameter ZS_PaymentEventParam;

  association _Item { create; }
}

Implementación — Propose y Execute

ACTION Propose
METHOD Propose.
  READ ENTITIES OF zi_paymentrun_head IN LOCAL MODE
    ENTITY PayRun ALL FIELDS
      WITH CORRESPONDING #( keys )
    RESULT DATA(runs).

  LOOP AT runs INTO DATA(ls_run).

    " seleccionar facturas elegibles
    SELECT i~invoice_id, i~supplier, i~invoice_amount, i~currency,
           i~invoice_date, l~payment_method, l~house_bank,
           l~cash_disc_days, l~cash_disc_pct
      FROM zinv_head AS i
      JOIN lfb1 AS l
        ON i~supplier = l~lifnr AND l~bukrs = @ls_run-CompanyCode
      WHERE i~status = 'POSTED'
        AND i~due_date <= @ls_run-PaymentDate
        AND l~zahls = ' '
      ORDER BY i~supplier
      INTO TABLE @DATA(eligible).

    DATA items_to_create TYPE TABLE FOR CREATE zi_paymentrun_head\_Item.
    DATA n TYPE i VALUE 0.

    LOOP AT eligible INTO DATA(ls_e).
      n = n + 10.
      DATA(lv_disc) = COND p(
        WHEN ls_e-invoice_date + ls_e-cash_disc_days >= ls_run-PaymentDate
        THEN ls_e-invoice_amount * ls_e-cash_disc_pct / 100
        ELSE 0 ).

      APPEND VALUE #(
        %tky = ls_run-%tky
        %target = VALUE #(
          ( %cid     = |ITEM_{ n }|
            ItemNo   = n
            Supplier = ls_e-supplier
            InvoiceId = ls_e-invoice_id
            Amount   = ls_e-invoice_amount
            Discount = lv_disc
            NetAmount = ls_e-invoice_amount - lv_disc
            HouseBank = ls_e-house_bank
            PaymentMethod = ls_e-payment_method
            Included = abap_true ) )
      ) TO items_to_create.
    ENDLOOP.

    MODIFY ENTITIES OF zi_paymentrun_head IN LOCAL MODE
      ENTITY PayRun
        CREATE BY \_Item FIELDS
          ( ItemNo Supplier InvoiceId Amount Discount NetAmount
            HouseBank PaymentMethod Included )
        WITH items_to_create.

    MODIFY ENTITIES OF zi_paymentrun_head IN LOCAL MODE
      ENTITY PayRun UPDATE FIELDS ( Status )
      WITH VALUE #( ( %tky = ls_run-%tky Status = 'PROPOSED'
                          %control-Status = if_abap_behv=>mk-on ) ).

    RAISE ENTITY EVENT zi_paymentrun_head~PaymentProposed
      FROM VALUE #( ( %key = ls_run-%key
                      %param = VALUE #( RunId = ls_run-RunId
                                         ItemCount = lines( eligible ) ) ) ).
  ENDLOOP.
ENDMETHOD.
ACTION Execute — BAPI_ACC_DOCUMENT_POST + SEPA
METHOD Execute.
  READ ENTITIES OF zi_paymentrun_head IN LOCAL MODE
    ENTITY PayRun BY \_Item ALL FIELDS
      WITH CORRESPONDING #( keys )
    RESULT DATA(items).

  DATA sepa_payments TYPE cl_dmee_facade=>tt_payments.

  LOOP AT items INTO DATA(ls_it) WHERE Included = abap_true.

    DATA doc_header TYPE bapiache09.
    DATA doc_accountap TYPE TABLE OF bapiacap09.
    DATA doc_accountgl TYPE TABLE OF bapiacgl09.
    DATA doc_currency  TYPE TABLE OF bapiaccr09.
    DATA ret TYPE TABLE OF bapiret2.
    DATA belnr TYPE belnr_d.

    doc_header = VALUE #( username = sy-uname
                          comp_code = ls_it-_Head-CompanyCode
                          doc_date = sy-datum pstng_date = sy-datum
                          doc_type = 'KZ' ).

    APPEND VALUE #( itemno_acc = 1
                     vendor_no  = ls_it-Supplier
                     gl_account = '1611000000' ) TO doc_accountap.
    APPEND VALUE #( itemno_acc = 2
                     gl_account = '1131000000'
                     hbkid = ls_it-HouseBank ) TO doc_accountgl.
    APPEND VALUE #( itemno_acc = 1
                     amt_doccur = -ls_it-NetAmount
                     currency = ls_it-_Head-Currency ) TO doc_currency.
    APPEND VALUE #( itemno_acc = 2
                     amt_doccur =  ls_it-NetAmount
                     currency = ls_it-_Head-Currency ) TO doc_currency.

    CALL FUNCTION 'BAPI_ACC_DOCUMENT_POST'
      EXPORTING documentheader = doc_header
      IMPORTING obj_key        = belnr
      TABLES    accountgl      = doc_accountgl
                  accountpayable = doc_accountap
                  currencyamount = doc_currency
                  return         = ret.

    IF line_exists( ret[ type = 'E' ] ).
      CALL FUNCTION 'BAPI_TRANSACTION_ROLLBACK'.
    ELSE.
      CALL FUNCTION 'BAPI_TRANSACTION_COMMIT' EXPORTING wait = 'X'.

      APPEND VALUE #( supplier  = ls_it-Supplier
                       amount    = ls_it-NetAmount
                       currency  = ls_it-_Head-Currency
                       iban      = cl_zpay_helper=>get_iban( ls_it-Supplier )
                       reference = |INV { ls_it-InvoiceId }| )
        TO sepa_payments.
    ENDIF.
  ENDLOOP.

  " Generación del fichero SEPA XML pain.001
  IF sepa_payments IS NOT INITIAL.
    cl_dmee_facade=>create_sepa_file(
      it_payments = sepa_payments
      iv_format   = 'SEPA_CT'
      iv_path     = |/usr/sap/Z/pay/sepa_{ sy-datum }.xml| ).
  ENDIF.

  MODIFY ENTITIES OF zi_paymentrun_head IN LOCAL MODE
    ENTITY PayRun UPDATE FIELDS ( Status )
    WITH VALUE #( FOR k IN keys
                    ( %tky = k-%tky Status = 'EXECUTED'
                      %control-Status = if_abap_behv=>mk-on ) ).

  RAISE ENTITY EVENT zi_paymentrun_head~PaymentExecuted
    FROM VALUE #( FOR k IN keys
                  ( %key = k-%key
                    %param = VALUE #( RunId = k-RunId
                                       ItemCount = lines( sepa_payments ) ) ) ).
ENDMETHOD.

🖥️ Gemelo Digital — Crear propuesta, aprobar y ejecutar

Simula "Manage Payment Proposals". Pulsa Proponer para precargar facturas vencidas, marca cuáles incluir, simula la aprobación 4-ojos, ejecuta el pago. Observa la generación del fichero SEPA y la contabilización en FI.

Propuestas de Pago › PAY-2026-0042 M. Álvarez MA

Propuesta PAY-2026-0042 · DRAFT

Sociedad 1000 · Fecha pago 25.05.2026 · Creada por M. Álvarez · Aprobada por
0,00 EUR
Total a pagar
0
Facturas seleccionadas
0,00 EUR
Descuento P.P.
Parámetros de la propuesta
Sociedad1000 · ESPAÑA SA
Banco emisorBBVA-0001 · ES44 0182 0000 12 ...
Método pagoS · SEPA Credit Transfer
Formato ficheropain.001.001.03 (XML)
Facturas elegibles
ProveedorFacturaDoc FIVencimientoImporteDto. P.P.Neto
Pulsa Proponer para precargar las facturas vencidas con fecha ≤ 25.05.2026
Estado del flujo de pago
1. Borrador
2. Propuesta
3. Aprobada
4. Pagada
5. Fichero SEPA
Cierre

Qué te llevas y próximos pasos

El patrón "5 piezas"

Todo BO RAP es CDS + BDEF + Implementación + Service Def + Binding. Si lo asimilas, las 5 fases de este curso son variaciones de la misma melodía.

Managed vs unmanaged

Managed: greenfield (PR, PO, PaymentRun). Unmanaged: te apoyas en BAPIs existentes (GR, Invoice). Elegir bien evita reinventar la rueda.

Cinco fases, cinco lecciones

F1: managed + draft + action. F2: managed with additional save + cross-BO. F3: unmanaged + BAPI legacy. F4: validation con dataPoints UI. F5: action compleja + 4-eyes + integración batch + Event Mesh.

Eventos y composición

Cada BO publica eventos: ZPR_RELEASED, ZPO_SENT, ZGR_POSTED, ZINV_POSTED, ZPAY_EXECUTED. Es el cemento que une el ciclo P2P con CPI, BTP y Tesorería.

Clean Core: la regla de oro

Las APIs released de SAP son la opción correcta en S/4 Cloud. Este curso enseña a construir el modelo equivalente para entender el motor.

Siguientes pasos

① Side effects y determine actions. ② Numbering early. ③ Eventos a Event Mesh real. ④ Joule for Developers para generar la mitad del código.

Ejercicio integrador propuesto

Tomar las 5 fases y, en grupo de 2-3 personas, construir el ciclo completo en un sistema BTP Trial ABAP Cloud: tablas, CDS, BDEF, implementaciones, services, bindings y previews. Crear datos demo y mostrar el flujo punta a punta desde Solicitud hasta Pago SEPA. Entregable: vídeo de 5 minutos demostrando los Fiori Elements generados sin haber escrito una línea de JavaScript.

Una última verdad: el código que has visto en estas 5 fases (~800 líneas ABAP) sería equivalente a un desarrollo procedural clásico de 4 000-6 000 líneas con BAPIs, dynpros, ALV, autorización a mano, SmartForms y un programa Z propio que orquestara F110. Esa es la ventaja real de RAP: convertir un mes de trabajo en una semana.