Meter Exchange Request Management
SAP S/4HANA RAP Transactional Application — Enterprise Design & Build
Document
SAP Utilities (IS-U) | ABAP Cloud | Clean Core
Document version: 1.0 Target release: S/4HANA Cloud, Public/Private Edition (ABAP
Cloud programming model) Author role: Senior SAP S/4HANA Solution Architect / RAP
Lead
Table of Contents
1. Functional Specification
2. Technical Design Overview & RAP Object Model Diagram
3. Database Design (DDIC Tables + ER Diagram)
4. CDS Layer Architecture (Interface, Consumption, Projection Views)
5. Behavior Definition & Behavior Implementation
6. Validations, Determinations, Actions (full logic + code)
7. Fiori Elements UI Design
8. OData V4 Service Design
9. Security & Authorization Design
10. Reporting / Analytics
11. Business Rules — Status Flow State Machine
12. Sample Data Set
13. Error Handling / Message Class Design
14. Performance Engineering
15. Clean Core Architecture & Package/Folder Structure
16. Extensibility Roadmap
17. Unit Testing Strategy
18. Deployment Steps
19. Interview Questions (based on this build)
20. Clean Core Compliance Checklist
1. Functional Specification
1.1 Business Context
Electricity distribution utilities (modeled here on companies such as Eversource, Duke
Energy, National Grid, EDF, and PG&E) run continuous smart-meter exchange programs
driven by malfunction, end-of-life replacement, accuracy disputes, customer complaints,
planned smart-meter upgrades, and physical damage (storms, tampering, accidents). Today
this is run over email and Excel: no single source of truth, no SLA tracking, no audit trail,
and no linkage to the actual device master ( Equipment / Device in IS-U terms) or the
customer's contract account.
1.2 Objective
Deliver a RAP Managed, Draft-enabled Transactional App — Meter Exchange Request
Management (MERM) — that lets CSRs log a request, Supervisors triage/approve/verify,
and Technicians execute and record the physical swap, with full status governance,
validations, automatic numbering, notifications, and reporting.
1.3 Actors / Roles
Role Responsibility
CSR (Customer Creates request from customer contact, captures
Service Rep) customer/contract/device data, sets initial priority & reason
Supervisor Reviews, approves/rejects, assigns technician, verifies completed
exchange, closes/cancels/reopens
Technician Executes exchange in the field, records old/new meter data, readings,
seal, GPS, photo, marks complete
Administrator Full CRUD, master data corrections, reason/priority configuration, all
actions, technical support
1.4 Scope — In / Out
In scope: request lifecycle management, item-level replacement capture, validations,
notifications (stubbed via Business Event/Email), PDF work order generation (stub via
Adobe Forms Cloud), reporting tiles/queries, role-based authorization.
Out of scope (see Extensibility, §16): GIS routing, SAP FSM dispatch, live IoT meter
telemetry, SAP Asset Manager offline sync, AI fault prediction — designed for, not built.
1.5 High-Level Process Flow
Customer reports fault
│
▼
CSR creates Meter Exchange Request ──────────────► Status: New
│ (Submit action)
▼
Status: Submitted ──► Supervisor reviews
│
┌────┴────┐
▼ ▼
Approve Reject ──► Status: Rejected (terminal, Reopen possible)
│
▼
Status: Approved ──► Supervisor assigns Technician + Work Order
│
▼
Status: Assigned ──► Technician Schedules Visit
│
▼
Status: In Progress ──► Technician performs exchange, captures item data
│ (Complete Exchange action — validates old/new meter, readings, dates)
▼
Status: Completed ──► Supervisor Verifies
│
▼
Status: Closed (terminal)
Cancel Request: allowed from New/Submitted/Approved/Assigned (not from
Completed/Closed)
Reopen Request: allowed from Rejected/Cancelled/Closed by Supervisor/Admin,
resets to New
1.6 Key Business Rules (summary — full detail §11)
Requests move strictly forward through the state machine except for explicit Cancel
and Reopen transitions.
Completion is blocked unless every mandatory item field (Old Meter, New Meter,
Old Reading, New Reading, Removal Date, Installation Date) is populated and passes
validation.
High priority requires Supervisor approval flag before it can be set/kept — CSR
cannot self-approve High priority.
No two open (non-Closed/Cancelled/Rejected) requests may exist for the same
Device Number — duplicate check.
Old Reading and New Reading are bounded 0 – 999999 (6-digit mechanical/AMI
register convention).
Installation Date must be >= Removal Date.
1.7 Non-Functional Requirements
Draft-enabled, ETag-protected optimistic concurrency, no classic enqueue exposed to
UI.
OData V4 exposed via
/sap/opu/odata4/sap/mer_meter_exchange/srvd/sap/mer_meter_exchange_o4/0001/ .
All custom code in ABAP Cloud (RAP-restricted ABAP), only released/public APIs —
Clean Core, no core mods.
Multi-tenant / Cloud-ready: no client-dependent customizing tables outside standard
whitelisted BC sets.
2. Technical Design Overview & RAP Object Model Diagram
2.1 Architecture Pattern
RAP Managed Scenario, single BDEF-Root with a Composition to one child (Item), draft-
enabled, unmanaged save NOT required (standard managed persistence via generated
table functions is sufficient because logic lives in validations/determinations/actions, not in
SAVE).
2.2 Object Model Diagram
┌──────────────────────────────────────────────────────────────────────────┐
│ RAP OBJECT MODEL – MERM │
│
│
│ DDIC Tables Interface Views (Root/Child, DDL) │
│ ZMER_D_HEADER ───────► I_MeterExchangeRequest (Root, Managed, Draft) │
│ ZMER_D_ITEM ───────► I_MeterExchgReplacement (Child, Managed) │
│
│
│ │ composition (1:N, "to_Item") │
│ ▼ │
│ Consumption Views │
│ C_MeterExchangeRequest ◄── Value Helps, Text Assoc, Search │
│ C_MeterExchgReplacement │
│ │ │
│ ▼ │
│ Behavior Definitions │
│ I_MeterExchangeRequest (BDEF, root, implementation type managed; │
│ unmanaged save = false, with draft) │
│ I_MeterExchgReplacement (BDEF, child, managed) │
│ │ │
│ ▼ │
│ Projection Layer (exposed to service) │
│ C_MeterExchangeRequestTP (BDEF projection, "use ... ;") │
│ │ │
│ ▼ │
│ Service Definition: MER_METER_EXCHANGE_O4 │
│ │ │
│ ▼ │
│ Service Binding (OData V4, UI): MER_METER_EXCHANGE_O4_UI │
│ │ │
│ ▼ │
│ Fiori Elements List Report / Object Page (Draft) │
└──────────────────────────────────────────────────────────────────────────┘
2.3 Naming Conventions Used in this Document
Layer Prefix Example
DDIC Table ZMER_D_ ZMER_D_HEADER , ZMER_D_ITEM
Data Element / Domain ZMER_E_ ZMER_E_REQ_NO
Interface CDS View I_ (custom namespace ZI_MeterExchangeRequest
ZI_ )
Consumption CDS View ZC_ ZC_MeterExchangeRequest
Metadata Extension ZC_..._MDE ZC_MeterExchangeRequest_MDE
Behavior Definition same name as root Interface ZI_MeterExchangeRequest
View
Behavior Implementation ZBP_ ZBP_I_MeterExchangeRequest
Class
Service Definition ZSD_ ZSD_MER_METER_EXCHANGE_O4
Service Binding ZSB_ ZSB_MER_METER_EXCHANGE_O4_UI
Message Class ZMER_MSG
Number Range Object ZMER_NR
(Throughout the remaining sections the Z prefix is retained for all custom objects, per
Clean Core rule that customer objects must live in the customer namespace and use only
released BAdIs/CDS/AMDPs.)
3. Database Design (DDIC Tables + ER Diagram)
3.1 ER Diagram
┌────────────────────────────┐ ┌────────────────────────────────┐
│ ZMER_D_HEADER │ │ ZMER_D_ITEM │
├────────────────────────────┤ 1 N ├──────────────────────────────────┤
│ PK request_uuid (RAW16) │◄──────►│ PK item_uuid (RAW16) │
│ request_no (CHAR10) │ │ FK request_uuid (RAW16) │
│ customer_id │ │ item_no (NUMC4) │
│ business_partner │ │ old_meter │
│ contract_account │ │ old_reading │
│ installation │ │ removal_date │
│ device_number │ │ new_meter │
│ meter_serial_number │ │ new_reading │
│ meter_type │ │ installation_date │
│ reason_code │ │ seal_number │
│ priority │ │ gps_latitude │
│ high_prio_approved │ │ gps_longitude │
│ status │ │ photo_attachment_id │
│ assigned_technician │ │ comments │
│ requested_date │ │ local_last_changed │
│ planned_date │ └──────────────────────────────────┘
│ completion_date │
│ request_age_days │
│ due_date │
│ work_order_id │
│ created_by / created_on │
│ changed_by / changed_on │
│ local_last_changed │
│ -- draft admin fields -- │
└────────────────────────────┘
3.2 Header Table DDL — ZMER_D_HEADER
abap
@[Link] : 'Meter Exchange Request - Header'
@[Link] : #NOT_EXTENSIBLE
@[Link] : #TRANSPARENT
@[Link] : #A
@[Link] : #RESTRICTED
define table zmer_d_header {
key client : [Link] not null;
key request_uuid : sysuuid_x16 not null;
request_no : zmer_e_req_no; "Number range generate
customer_id : zmer_e_customer_id;
business_partner : bu_partner;
contract_account : zmer_e_contract_acct;
installation : zmer_e_installation;
device_number : zmer_e_device_no;
meter_serial_number : zmer_e_meter_serial;
meter_type : zmer_e_meter_type;
reason_code : zmer_e_reason_code;
priority : zmer_e_priority;
high_prio_approved : abap_boolean;
status : zmer_e_status;
assigned_technician : zmer_e_technician_id;
requested_date : [Link];
planned_date : [Link];
completion_date : [Link];
request_age_days : abap.int4;
due_date : [Link];
work_order_id : zmer_e_work_order;
created_by : [Link];
created_on : [Link];
changed_by : [Link];
changed_on : [Link];
local_last_changed : [Link];
}
3.3 Item Table DDL — ZMER_D_ITEM
abap
@[Link] : 'Meter Exchange Request - Replacement Item'
@[Link] : #NOT_EXTENSIBLE
@[Link] : #TRANSPARENT
@[Link] : #A
@[Link] : #RESTRICTED
define table zmer_d_item {
key client : [Link] not null;
key item_uuid : sysuuid_x16 not null;
request_uuid : sysuuid_x16 not null; "FK to header
item_no : [Link](4);
old_meter : zmer_e_meter_serial;
old_reading : zmer_e_reading;
removal_date : [Link];
new_meter : zmer_e_meter_serial;
new_reading : zmer_e_reading;
installation_date : [Link];
seal_number : zmer_e_seal_no;
gps_latitude : zmer_e_geo_coord;
gps_longitude : zmer_e_geo_coord;
photo_attachment_id : sysuuid_x16;
comments : [Link](256);
local_last_changed : [Link];
(Both tables get standard RAP draft shadow tables ZMER_D_HEADER_D / ZMER_D_ITEM_D ,
generated automatically once with draft is declared in the Behavior Definition — no
manual draft table modeling needed.)
3.4 Domain / Data Element Highlights
Data Element Domain / Fixed Values
Type
zmer_e_status CHAR2 NW (New) SB (Submitted) AP (Approved)
AS (Assigned) IP (In Progress) CO (Completed)
CL (Closed) CN (Cancelled) RJ (Rejected)
zmer_e_priority CHAR1 L (Low) M (Medium) H (High)
zmer_e_reason_code CHAR2 MF (Malfunction) EL (End of Life) AC (Accuracy Issue)
CC (Customer Complaint) SU (Smart Upgrade)
PD (Physical Damage)
zmer_e_reading DEC(6,0) 0 – 999999 (enforced by validation, not just domain)
3.5 Number Range Object
ZMER_NR (object), interval 01 , from 0000000001 to 9999999999 , used by the
CalculateRequestNo determination via CL_NUMBER_RANGE_RUNTIME=>NUMBER_GET (released
API, ABAP-Cloud-compatible).
4. CDS Layer Architecture
4.1 Layered View Design
Interface View (I_) → Consumption View (C_) → Service Definition →
Service Binding
basic CDS view + Value Helps groups entities
OData V4/UI
+ associations + Text Assoc
protocol
+ compositions + Search
+ Metadata Extension (UI annotations)
4.2 Root Interface View — ZI_MeterExchangeRequest
abap
@[Link]: #CHECK
@[Link]: 'Meter Exchange Request - Interface View'
@[Link]: true
@[Link]: ['RequestNo']
@[Link]: true
define root view entity ZI_MeterExchangeRequest
as select from zmer_d_header
composition [0..*] of ZI_MeterExchgReplacement as _Item
association [0..1] to I_BusinessPartner as _BusinessPartner on $proje
association [0..1] to ZI_Technician as _Technician on $proje
association [0..1] to ZI_ReasonCodeVH as _ReasonCode on $proje
association [0..1] to ZI_StatusVH as _StatusText on $proje
{
key request_uuid as RequestUUID,
@[Link]: true
@[Link]: true
request_no as RequestNo,
customer_id as CustomerID,
business_partner as BusinessPartner,
contract_account as ContractAccount,
installation as Installation,
device_number as DeviceNumber,
meter_serial_number as MeterSerialNumber,
meter_type as MeterType,
reason_code as ReasonCode,
@[Link]: 'None'
priority as Priority,
high_prio_approved as HighPrioApproved,
status as Status,
assigned_technician as AssignedTechnician,
requested_date as RequestedDate,
planned_date as PlannedDate,
completion_date as CompletionDate,
request_age_days as RequestAgeDays,
due_date as DueDate,
work_order_id as WorkOrderID,
/* admin */
@[Link]: true
created_by as CreatedBy,
@[Link]: true
created_on as CreatedOn,
@[Link]: true
changed_by as ChangedBy,
@[Link]: true
changed_on as ChangedOn,
@[Link]: true
local_last_changed as LocalLastChanged,
/* associations */
_Item,
_BusinessPartner,
_Technician,
_ReasonCode,
_StatusText
}
4.3 Child Interface View — ZI_MeterExchgReplacement
abap
@[Link]: #CHECK
@[Link]: 'Meter Exchange - Replacement Item'
@[Link]: ['ItemNo']
define view entity ZI_MeterExchgReplacement
as select from zmer_d_item
association to parent ZI_MeterExchangeRequest as _Request on $[Link]
{
key item_uuid as ItemUUID,
request_uuid as RequestUUID,
item_no as ItemNo,
old_meter as OldMeter,
old_reading as OldReading,
removal_date as RemovalDate,
new_meter as NewMeter,
new_reading as NewReading,
installation_date as InstallationDate,
seal_number as SealNumber,
gps_latitude as GpsLatitude,
gps_longitude as GpsLongitude,
photo_attachment_id as PhotoAttachmentID,
comments as Comments,
@[Link]: true
local_last_changed as LocalLastChanged,
_Request
}
4.4 Consumption Views (Value Help / Search / Text)
abap
@[Link]: true
@UI: { headerInfo: { typeName: 'Meter Exchange Request', typeNamePlural: 'Meter
define root view entity ZC_MeterExchangeRequest
provider contract transactional_query
as projection on ZI_MeterExchangeRequest as Request
{
key RequestUUID,
RequestNo,
CustomerID,
BusinessPartner,
ContractAccount,
Installation,
DeviceNumber,
MeterSerialNumber,
MeterType,
ReasonCode,
Priority,
HighPrioApproved,
Status,
AssignedTechnician,
RequestedDate,
PlannedDate,
CompletionDate,
RequestAgeDays,
DueDate,
WorkOrderID,
CreatedBy,
CreatedOn,
ChangedBy,
ChangedOn,
LocalLastChanged,
/* Redirected assoc for FE navigation & value help */
_Item : redirected to composition child ZC_MeterExchgReplacement
_BusinessPartner,
_Technician,
_ReasonCode,
_StatusText
}
Value-help/text association targets used above:
Association Target CDS Purpose
_BusinessPartner I_BusinessPartner (standard) customer
identification,
address facet
_Technician ZI_Technician (custom, wraps HR/BP technician Assign Technician
master) value help
_ReasonCode ZI_ReasonCodeVH (custom check-table CDS) Reason Code F4 +
text
_StatusText ZI_StatusVH (custom, or status text/criticality
@[Link] on status using
domain fixed values)
4.5 Metadata Extension (excerpt) — ZC_MeterExchangeRequest_MDE
abap
@[Link]: #CORE
@UI: {
presentationVariant: [{ sortOrder: [{ by: 'RequestedDate', direction: #DESC }
}
annotate view ZC_MeterExchangeRequest with
{
@[Link]: [
{ id: 'Header', purpose: #STANDARD, type: #IDENTIFICATION_REFERENCE, label:
{ id: 'Items', purpose: #STANDARD, type: #LINEITEM_REFERENCE, targetElement
{ id: 'BP', purpose: #STANDARD, type: #FIELDGROUP_REFERENCE, targetQualifie
]
@[Link]: [{ position: 10, importance: #HIGH }]
@[Link]: [{ position: 10 }]
@[Link]: [{ position: 10 }]
RequestNo;
@[Link]: [{ position: 20, criticality: 'criticality', criticalityReprese
@[Link]: [{ position: 20 }]
@[Link]: [{ position: 20 }]
Status;
@[Link]: [{ position: 30 }]
@[Link]: [{ position: 30 }]
@[Link]: [{ position: 30 }]
Priority;
@[Link]: [{ position: 40 }]
@[Link]: [{ position: 40 }]
AssignedTechnician;
@[Link]: [{ position: 50 }]
RequestedDate;
@[Link]: [{ position: 60 }]
DueDate;
}
5. Behavior Definition & Behavior Implementation
5.1 Root Behavior Definition — ZI_MeterExchangeRequest
abap
managed implementation in class zbp_i_meterexchangerequest unique;
strict ( 2 );
define behavior for ZI_MeterExchangeRequest alias Request
persistent table zmer_d_header
draft table zmer_d_header_d
lock master
authorization master ( instance )
etag master LocalLastChanged
{
create;
update;
delete;
association _Item { create; with draft; }
field ( readonly ) RequestUUID, RequestNo, Status, RequestAgeDays
CreatedBy, CreatedOn, ChangedBy, ChangedOn;
field ( mandatory ) CustomerID, ContractAccount, DeviceNumber, Re
field ( numbering : managed ) RequestUUID;
validation validateDuplicateRequest on save { create; field DeviceNumber; }
validation validateHighPriority on save { create; update; field Priority;
validation validateCompletion on save { field Status; }
determination setRequestNo on save { create; }
determination setAuditFields on save { create; }
determination calculateRequestAge on modify { create; update; }
determination calculateDueDate on modify { create; field ReasonCode, P
determination setDefaultPriority on modify { create; }
determination setCompletionDate on modify { field Status; }
action ( features : instance ) approve result [1] $self;
action ( features : instance ) reject result [1] $self;
action ( features : instance ) assignTechnician parameter ZI_MER_AssignTe
action ( features : instance ) scheduleVisit parameter ZI_MER_Schedul
action ( features : instance ) completeExchange result [1] $self;
action ( features : instance ) verifyAndClose result [1] $self;
action ( features : instance ) cancelRequest parameter ZI_MER_CancelR
action ( features : instance ) reopenRequest result [1] $self;
action ( features : instance ) generateWorkOrder result [1] $self;
action ( features : instance ) generatePdfReport result [1] $self;
side effects
{
determination setDefaultPriority affects field Priority;
determination calculateDueDate affects field DueDate;
determination calculateRequestAge affects field RequestAgeDays;
action approve affects field Status;
action assignTechnician affects field Status, AssignedTechnicia
action completeExchange affects field Status, CompletionDate;
}
mapping for zmer_d_header
{
RequestUUID = request_uuid;
RequestNo = request_no;
CustomerID = customer_id;
BusinessPartner = business_partner;
ContractAccount = contract_account;
Installation = installation;
DeviceNumber = device_number;
MeterSerialNumber = meter_serial_number;
MeterType = meter_type;
ReasonCode = reason_code;
Priority = priority;
HighPrioApproved = high_prio_approved;
Status = status;
AssignedTechnician = assigned_technician;
RequestedDate = requested_date;
PlannedDate = planned_date;
CompletionDate = completion_date;
RequestAgeDays = request_age_days;
DueDate = due_date;
WorkOrderID = work_order_id;
CreatedBy = created_by;
CreatedOn = created_on;
ChangedBy = changed_by;
ChangedOn = changed_on;
LocalLastChanged = local_last_changed;
}
}
5.2 Child Behavior Definition — ZI_MeterExchgReplacement
abap
managed implementation in class zbp_i_meterexchgreplacemnt unique;
strict ( 2 );
define behavior for ZI_MeterExchgReplacement alias Item
persistent table zmer_d_item
draft table zmer_d_item_d
lock dependent by _Request
authorization dependent by _Request
etag dependent by _Request
{
update;
delete;
association _Request;
field ( readonly ) ItemUUID, RequestUUID;
field ( mandatory ) OldMeter, NewMeter;
validation validateOldReading on save { field OldReading; }
validation validateInstallationDate on save { field InstallationDate, Rem
validation validateMandatoryOnComplete on save { field OldMeter, NewMeter; }
mapping for zmer_d_item
{
ItemUUID = item_uuid;
RequestUUID = request_uuid;
ItemNo = item_no;
OldMeter = old_meter;
OldReading = old_reading;
RemovalDate = removal_date;
NewMeter = new_meter;
NewReading = new_reading;
InstallationDate = installation_date;
SealNumber = seal_number;
GpsLatitude = gps_latitude;
GpsLongitude = gps_longitude;
PhotoAttachmentID = photo_attachment_id;
Comments = comments;
LocalLastChanged = local_last_changed;
}
}
5.3 Projection Behavior Definition — ZC_MeterExchangeRequest
abap
projection;
strict ( 2 );
define behavior for ZC_MeterExchangeRequest alias Request
{
use create;
use update;
use delete;
use association _Item { create; with draft; }
use action approve;
use action reject;
use action assignTechnician;
use action scheduleVisit;
use action completeExchange;
use action verifyAndClose;
use action cancelRequest;
use action reopenRequest;
use action generateWorkOrder;
use action generatePdfReport;
}
5.4 Concepts Explained
Associations vs Compositions: the header→item link is a composition ( _Item ) because
items have no independent existence outside the header — deleting the header deletes
items (owned lifecycle, cascading draft). Reference associations ( _BusinessPartner ,
_Technician , _ReasonCode ) are plain associations — read-only navigation to existing
master/check-table entities that are not owned by MERM.
Semantic Keys: RequestNo is the human/business-visible semantic key
( @[Link] ) while RequestUUID remains the technical primary key — this
lets Fiori Elements display the friendly number while RAP internally manages draft/active
pairs by UUID, avoiding key-reassignment problems.
Late Numbering: RequestUUID uses field ( numbering : managed ) , i.e., early/client-side
numbering (default managed UUID generation at create time) — this is deliberate: because
RequestNo (the business number) needs true late numbering (sequential, gap-free,
assigned only on save so parallel draft creation doesn't burn numbers), the setRequestNo
determination runs on save { create; } and calls the number-range class only when the
draft is activated, not at draft creation. This is the standard RAP "late numbering" pattern:
technical UUID = early/managed, business number = late (determined on save).
Optimistic Locking / ETag: etag master LocalLastChanged on the root (and etag
dependent by _Request on the child) means every PATCH/PUT must carry the current
LocalLastChanged timestamp; RAP auto-rejects if the entity was changed concurrently,
surfacing a standard "resource has been changed" message — no custom enqueue coding
needed.
Lock: lock master on the root centralizes the enqueue for header+items as one logical unit
( lock dependent by _Request on the child) — a Supervisor approving while a Technician
edits an item is blocked, preventing lost updates.
Global vs Instance Features: actions are declared ( features : instance ) because their
availability (Approve only visible when Status = 'SB' , etc.) is entity-state-dependent,
computed in GET_INSTANCE_FEATURES (§6.4) rather than statically enabled for every record
( ( features : global ) is reserved for actions independent of instance state, e.g., a
hypothetical "Create Bulk Import" action — not used here since every MERM action
depends on current status).
6. Validations, Determinations, Actions — Full Logic
6.1 Validations
6.1.1 validateDuplicateRequest (no two open requests per device)
abap
METHOD validateDuplicateRequest.
READ ENTITIES OF ZI_MeterExchangeRequest IN LOCAL MODE
ENTITY Request
FIELDS ( DeviceNumber Status RequestUUID )
WITH CORRESPONDING #( keys )
RESULT DATA(requests).
LOOP AT requests INTO DATA(req).
SELECT COUNT( * ) FROM zmer_d_header
WHERE device_number = @req-DeviceNumber
AND request_uuid <> @req-RequestUUID
AND status NOT IN ( 'CL', 'CN', 'RJ' )
INTO @DATA(lv_count).
IF lv_count > 0.
APPEND VALUE #( %tky = req-%tky ) TO reported-request.
APPEND VALUE #( %tky = req-%tky
%msg = NEW zcx_mer_message(
textid = zcx_mer_message=>duplicate_request
severity = if_abap_behv_message=>severity-erro
%element-devicenumber = if_abap_boolean=>abap_true )
TO reported-request.
APPEND VALUE #( %tky = req-%tky ) TO failed-request.
ENDIF.
ENDLOOP.
ENDMETHOD.
6.1.2 validateHighPriority (High requires approval flag)
abap
METHOD validateHighPriority.
READ ENTITIES OF ZI_MeterExchangeRequest IN LOCAL MODE
ENTITY Request
FIELDS ( Priority HighPrioApproved )
WITH CORRESPONDING #( keys )
RESULT DATA(requests).
LOOP AT requests INTO DATA(req) WHERE Priority = 'H' AND HighPrioApproved = a
APPEND VALUE #( %tky = req-%tky ) TO failed-request.
APPEND VALUE #( %tky = req-%tky
%msg = NEW zcx_mer_message(
textid = zcx_mer_message=>high_prio_needs_appr
severity = if_abap_behv_message=>severity-error
%element-priority = if_abap_boolean=>abap_true )
TO reported-request.
ENDLOOP.
ENDMETHOD.
6.1.3 validateCompletion (block Complete unless mandatory item data present)
abap
METHOD validateCompletion.
READ ENTITIES OF ZI_MeterExchangeRequest IN LOCAL MODE
ENTITY Request
FIELDS ( Status )
WITH CORRESPONDING #( keys )
RESULT DATA(requests).
LOOP AT requests INTO DATA(req) WHERE Status = 'CO'.
READ ENTITIES OF ZI_MeterExchangeRequest IN LOCAL MODE
ENTITY Request BY \_Item
FIELDS ( OldMeter NewMeter OldReading NewReading RemovalDate Installati
WITH VALUE #( ( %tky = req-%tky ) )
RESULT DATA(items).
IF items IS INITIAL.
APPEND VALUE #( %tky = req-%tky ) TO failed-request.
APPEND VALUE #( %tky = req-%tky
%msg = NEW zcx_mer_message( textid = zcx_mer_message=>it
severity = if_abap_behv_mes
TO reported-request.
CONTINUE.
ENDIF.
LOOP AT items INTO DATA(item)
WHERE OldMeter IS INITIAL OR NewMeter IS INITIAL
OR OldReading IS INITIAL OR NewReading IS INITIAL
OR RemovalDate IS INITIAL OR InstallationDate IS INITIAL.
APPEND VALUE #( %tky = req-%tky ) TO failed-request.
APPEND VALUE #( %tky = req-%tky
%msg = NEW zcx_mer_message( textid = zcx_mer_message=>ma
severity = if_abap_behv_mes
TO reported-request.
ENDLOOP.
ENDLOOP.
ENDMETHOD.
6.1.4 Item-level validateOldReading (0 – 999999) & validateInstallationDate
abap
METHOD validateOldReading.
READ ENTITIES OF ZI_MeterExchgReplacement IN LOCAL MODE
ENTITY Item FIELDS ( OldReading NewReading ) WITH CORRESPONDING #( keys )
RESULT DATA(items).
LOOP AT items INTO DATA(item) WHERE OldReading > 999999 OR NewReading > 99999
APPEND VALUE #( %tky = item-%tky ) TO failed-item.
APPEND VALUE #( %tky = item-%tky
%msg = NEW zcx_mer_message( textid = zcx_mer_message=>read
severity = if_abap_behv_messa
%element-oldreading = if_abap_boolean=>abap_true
%element-newreading = if_abap_boolean=>abap_true )
TO reported-item.
ENDLOOP.
ENDMETHOD.
METHOD validateInstallationDate.
READ ENTITIES OF ZI_MeterExchgReplacement IN LOCAL MODE
ENTITY Item FIELDS ( InstallationDate RemovalDate ) WITH CORRESPONDING #( k
RESULT DATA(items).
LOOP AT items INTO DATA(item)
WHERE InstallationDate IS NOT INITIAL AND RemovalDate IS NOT INITIAL
AND InstallationDate < RemovalDate.
APPEND VALUE #( %tky = item-%tky ) TO failed-item.
APPEND VALUE #( %tky = item-%tky
%msg = NEW zcx_mer_message( textid = zcx_mer_message=>inst
severity = if_abap_behv_messa
%element-installationdate = if_abap_boolean=>abap_true )
TO reported-item.
ENDLOOP.
ENDMETHOD.
( Meter must exist , Customer must exist , Technician mandatory are implemented the
same pattern: read the candidate key, call the respective check-table/master read — e.g.
ZI_Technician , I_BusinessPartner — via READ ENTITIES /CDS select, and raise
zcx_mer_message=>customer_not_found / technician_not_found / meter_not_found if no hit.
Omitted here for brevity but follow the exact same LOOP ... APPEND failed/reported
skeleton.)
6.2 Determinations
abap
METHOD setRequestNo.
READ ENTITIES OF ZI_MeterExchangeRequest IN LOCAL MODE
ENTITY Request FIELDS ( RequestNo ) WITH CORRESPONDING #( keys )
RESULT DATA(requests).
DATA(nr_object) = cl_numberrange_runtime=>create( ).
LOOP AT requests INTO DATA(req) WHERE RequestNo IS INITIAL.
DATA(lv_number) = nr_object->number_get(
nr_range_nr = '01'
object = 'ZMER_NR' ).
MODIFY ENTITIES OF ZI_MeterExchangeRequest IN LOCAL MODE
ENTITY Request
UPDATE FIELDS ( RequestNo )
WITH VALUE #( ( %tky = req-%tky RequestNo = lv_number ) ).
ENDLOOP.
ENDMETHOD.
METHOD setAuditFields.
MODIFY ENTITIES OF ZI_MeterExchangeRequest IN LOCAL MODE
ENTITY Request
UPDATE FIELDS ( CreatedBy CreatedOn Status RequestedDate )
WITH VALUE #( FOR key IN keys
( %tky = key-%tky
CreatedBy = cl_abap_context_info=>get_user_technical_name( )
CreatedOn = utclong_current( )
Status = 'NW'
RequestedDate = cl_abap_context_info=>get_system_date( ) ) ).
ENDMETHOD.
METHOD calculateRequestAge.
READ ENTITIES OF ZI_MeterExchangeRequest IN LOCAL MODE
ENTITY Request FIELDS ( RequestedDate ) WITH CORRESPONDING #( keys )
RESULT DATA(requests).
MODIFY ENTITIES OF ZI_MeterExchangeRequest IN LOCAL MODE
ENTITY Request
UPDATE FIELDS ( RequestAgeDays )
WITH VALUE #( FOR req IN requests
( %tky = req-%tky
RequestAgeDays = cl_abap_context_info=>get_system_date( ) - req-Reque
ENDMETHOD.
METHOD calculateDueDate.
READ ENTITIES OF ZI_MeterExchangeRequest IN LOCAL MODE
ENTITY Request FIELDS ( Priority RequestedDate ) WITH CORRESPONDING #( keys
RESULT DATA(requests).
MODIFY ENTITIES OF ZI_MeterExchangeRequest IN LOCAL MODE
ENTITY Request
UPDATE FIELDS ( DueDate )
WITH VALUE #( FOR req IN requests
( %tky = req-%tky
DueDate = req-RequestedDate + COND #( WHEN req-Priority = 'H' THEN 2
WHEN req-Priority = 'M' THEN 5
ELSE 10 ) ) ).
ENDMETHOD.
METHOD setDefaultPriority.
MODIFY ENTITIES OF ZI_MeterExchangeRequest IN LOCAL MODE
ENTITY Request
UPDATE FIELDS ( Priority )
WITH VALUE #( FOR key IN keys ( %tky = key-%tky Priority = 'M' ) )."defau
ENDMETHOD.
METHOD setCompletionDate.
READ ENTITIES OF ZI_MeterExchangeRequest IN LOCAL MODE
ENTITY Request FIELDS ( Status ) WITH CORRESPONDING #( keys )
RESULT DATA(requests).
MODIFY ENTITIES OF ZI_MeterExchangeRequest IN LOCAL MODE
ENTITY Request
UPDATE FIELDS ( CompletionDate )
WITH VALUE #( FOR req IN requests USING KEY entity WHERE ( Status = 'CO'
( %tky = req-%tky CompletionDate = cl_abap_context_info=>get_system_dat
ENDMETHOD.
6.3 Actions (business logic table)
Action Purpose Trigger Business Logic
Approve Supervisor approval of a Button on Object Sets Status = AP ; if
submitted request Page, visible only Priority = H , also sets
for Status = SB HighPrioApproved = X
Reject Supervisor rejects Status = SB Sets Status = RJ ;
requires a rejection reason
Action Purpose Trigger Business Logic
via dialog (mapped to
Comments on a system item
or a header text)
AssignTechnician Dispatch to field resource Status = AP Parameter entity
ZI_MER_AssignTechnician
{ TechnicianID,
PlannedDate } ; validates
technician exists & is active;
sets AssignedTechnician ,
PlannedDate , Status =
AS ; triggers
generateWorkOrder
internally
ScheduleVisit Technician confirms visit Status = AS Parameter
slot ZI_MER_ScheduleVisit {
PlannedDate } ; sets
Status = IP
CompleteExchange Field technician finalizes Status = IP , all Runs
swap item mandatory validateCompletion ; sets
fields filled Status = CO ;
setCompletionDate
determination fires
VerifyAndClose Supervisor QA-checks and Status = CO Sets Status = CL ; locks all
closes fields ( field(readonly)
via feature control)
CancelRequest Abort a request Status in Parameter
{NW,SB,AP,AS} ZI_MER_CancelReason {
Reason } ; sets Status =
CN
ReopenRequest Undo a Status in Resets Status = NW ,
Rejected/Cancelled/Closed {RJ,CN,CL} , clears
Action Purpose Trigger Business Logic
request caller has AssignedTechnician ,
Supervisor/Admin CompletionDate
role
GenerateWorkOrder Produce work order Internally from Calls number range
reference AssignTechnician, ZMER_NR_WO ; sets
or manually by WorkOrderID
Supervisor
GeneratePdfReport Printable Adobe Forms Any status, on- Calls cl_fpm_... /Adobe
Cloud PDF summary demand Document Services released
API with request+item data,
returns a document
URL/attachment
6.4 Action & Feature-Control Implementation (representative excerpt)
abap
METHOD approve.
READ ENTITIES OF ZI_MeterExchangeRequest IN LOCAL MODE
ENTITY Request FIELDS ( Status Priority ) WITH CORRESPONDING #( keys )
RESULT DATA(requests).
LOOP AT requests INTO DATA(req).
IF req-Status <> 'SB'.
APPEND VALUE #( %tky = req-%tky ) TO failed-request.
APPEND VALUE #( %tky = req-%tky
%msg = NEW zcx_mer_message( textid = zcx_mer_message=>in
severity = if_abap_behv_mes
TO reported-request.
CONTINUE.
ENDIF.
MODIFY ENTITIES OF ZI_MeterExchangeRequest IN LOCAL MODE
ENTITY Request
UPDATE FIELDS ( Status HighPrioApproved )
WITH VALUE #( ( %tky = req-%tky
Status = 'AP'
HighPrioApproved = COND #( WHEN req-Priority = 'H' THE
REPORTED DATA(upd_rep).
ENDLOOP.
READ ENTITIES OF ZI_MeterExchangeRequest IN LOCAL MODE
ENTITY Request ALL FIELDS WITH CORRESPONDING #( keys )
RESULT result.
ENDMETHOD.
METHOD completeExchange.
" status guard
READ ENTITIES OF ZI_MeterExchangeRequest IN LOCAL MODE
ENTITY Request FIELDS ( Status ) WITH CORRESPONDING #( keys )
RESULT DATA(requests).
LOOP AT requests INTO DATA(req) WHERE Status <> 'IP'.
APPEND VALUE #( %tky = req-%tky ) TO failed-request.
APPEND VALUE #( %tky = req-%tky
%msg = NEW zcx_mer_message( textid = zcx_mer_message=>inva
severity = if_abap_behv_messa
TO reported-request.
ENDLOOP.
MODIFY ENTITIES OF ZI_MeterExchangeRequest IN LOCAL MODE
ENTITY Request
UPDATE FIELDS ( Status )
WITH VALUE #( FOR req IN requests USING KEY entity WHERE ( Status = 'IP'
( %tky = req-%tky Status = 'CO' ) )." validateCompletion (on save) enfo
READ ENTITIES OF ZI_MeterExchangeRequest IN LOCAL MODE
ENTITY Request ALL FIELDS WITH CORRESPONDING #( keys )
RESULT result.
ENDMETHOD.
GET_INSTANCE_FEATURES (drives button/field enablement without repeating status
checks in the UI):
abap
METHOD get_instance_features.
READ ENTITIES OF ZI_MeterExchangeRequest IN LOCAL MODE
ENTITY Request FIELDS ( Status ) WITH CORRESPONDING #( keys )
RESULT DATA(requests).
result = VALUE #( FOR req IN requests
( %tky = req-%tky
%field-Priority = COND #( WHEN req-Status <> 'NW' THEN if_aba
%action-approve = COND #( WHEN req-Status = 'SB' THEN if_abap
%action-reject = COND #( WHEN req-Status = 'SB' THEN if_abap
%action-assignTechnician = COND #( WHEN req-Status = 'AP' THEN if_abap
%action-scheduleVisit = COND #( WHEN req-Status = 'AS' THEN if_abap
%action-completeExchange = COND #( WHEN req-Status = 'IP' THEN if_abap
%action-verifyAndClose = COND #( WHEN req-Status = 'CO' THEN if_abap
%action-cancelRequest = COND #( WHEN req-Status CA 'NSAB' THEN if_a
%action-reopenRequest = COND #( WHEN req-Status CA 'RCJ' THEN if_a
ENDMETHOD.
6.5 Action Flow Diagram
[New] --Submit--> [Submitted] --Approve--> [Approved] --AssignTechnician-->
[Assigned]
|
|
Reject
ScheduleVisit
v
v
[Rejected] <---------- Reopen -------------------------
[In Progress]
|
|
|
CompleteExchange
|
v
|
[Completed]
|
|
|
VerifyAndClose
|
v
|
[Closed]
|
CancelRequest (from New/Submitted/Approved/Assigned) -->
[Cancelled] --Reopen--> [New]
6.6 Sequence Diagram — "Complete Exchange"
Technician(Fiori) OData V4 Svc BDEF Impl(ZBP_..) Validation Layer
DB
| PATCH item fields | | |
|
|--------------------->| | |
|
| |--MODIFY------>| |
|
| | |--(on save) validateOldReading-
----->|
| | |--(on save)
validateInstallationDate>|
| POST completeExchange action | |
|
|--------------------->| | |
|
| |--EXECUTE----->| |
|
| | |--status guard (IP?) |
|
| | |--UPDATE Status=CO--->|
|
| SAVE (draft activate) | |
|
|--------------------->| | |
|
| |--FINALIZE---->|--validateCompletion-->|
|
| | |--setCompletionDate-->|--
INSERT/UPDATE->|
|<--------200 OK + updated entity-------| |
|
7. Fiori Elements UI Design
7.1 App Type
List Report + Object Page (Draft-enabled), generated via [Link] , floorplan
ListReport / ObjectPage , Fiori Elements v4 flavor (annotation-driven, no custom
controller code required for the base app — freestyle extensions only where noted).
7.2 List Report
Filter Bar: Status (multi-value), Priority , ReasonCode , AssignedTechnician ,
RequestedDate (date range), DeviceNumber .
Table columns (line items): Request No, Status (criticality icon), Priority (criticality:
H=red/M=orange/L=green), Customer, Device Number, Assigned Technician, Requested
Date, Due Date, Request Age (days).
Table toolbar: Create, Delete, Export to Excel, "Group By Status", "Sort by Due Date".
KPI header (via @[Link] + Smart KPI tags):
Pending Requests (count, Status IN (NW,SB) )
Overdue Requests (count, DueDate < today AND Status NOT IN (CL,CN,RJ) , red micro-
chip)
Avg. Completion Time (days, computed KPI CDS)
High Priority Open (donut micro chart)
7.3 Object Page
Header: Request No + Status (as @[Link] / criticality), Customer name (from
_BusinessPartner ), Priority badge, Due Date, Assigned Technician avatar/initial.
Facets:
1. General Information ( IDENTIFICATION_REFERENCE ): Customer ID, Business Partner,
Contract Account, Installation, Device Number, Meter Serial Number, Meter Type,
Reason Code, Priority, Requested/Planned/Completion Date.
2. Replacement Items ( LINEITEM_REFERENCE , table facet bound to _Item ): editable grid
— Old Meter, Old Reading, Removal Date, New Meter, New Reading, Installation Date,
Seal Number, GPS Lat/Long (with a Google-Maps-link formatter action), Photo
Attachment (file upload via [Link] opening the Attachment Service),
Comments.
3. Customer & Contract ( FIELDGROUP_REFERENCE ): pulled from _BusinessPartner
association — address, contact.
4. Status & Audit ( FIELDGROUP_REFERENCE ): Status history (via a side-car CDS
ZI_MER_StatusLog populated by a determination on every status-changing action —
extensibility hook), Created/Changed By/On.
Header actions (buttons, feature-controlled per §6.4): Approve, Reject, Assign
Technician (opens a dialog for Technician + Planned Date parameters), Schedule Visit,
Complete Exchange, Verify & Close, Cancel Request, Reopen Request, Generate Work
Order, Generate PDF Report (download).
Value Helps:
Device Number → collective search help over ZI_DeviceVH (wraps IS-U
Equipment /Device master, filtered to smart electricity meters).
Assigned Technician → ZI_Technician with search by name/region, showing current
workload count as an extra column.
Reason Code / Priority → fixed-value dropdowns (domain-based) with text.
Business Partner / Contract Account → standard I_BusinessPartner /FI-CA value
helps.
Draft Indicator: standard FE draft indicator ("Unsaved changes") in the header toolbar;
LocalLastChanged powers ETag-based conflict messages ("This request was changed by
another user").
Messages: RAP-raised messages (validations/actions) surface via the standard FE Message
Popover/Message Manager — no custom message handling needed since all reported-
entries use if_abap_behv_message .
7.4 Wireframe (textual)
┌───────────────────────────────────────────────────────────────────┐
│ ≡ Meter Exchange Requests [+Create] │
├───────────────────────────────────────────────────────────────────┤
│ Filters: Status ▾ Priority ▾ Technician ▾ Requested Date ▾ 🔍 │
├───────────────────────────────────────────────────────────────────┤
│ KPI: Pending 42 | Overdue 7⚠ | Avg Completion 3.2d | High-Prio 5 │
├───────────────────────────────────────────────────────────────────┤
│ ReqNo Status● Priority Customer Device# Tech Due │
│ 0000123 ● Assigned ▲ High J. Alvarez DEV-9931 T-007 7/22 │
│ 0000124 ● Submitted ● Medium M. Chen DEV-2210 -- 7/25 │
│ ... │
└───────────────────────────────────────────────────────────────────┘
Object Page (Request 0000123)
┌───────────────────────────────────────────────────────────────────┐
│ 0000123 ● Assigned High-Priority ▲ Due 7/22 Tech: T-007 │
│ [Approve] [Reject] [Assign Technician] [Schedule Visit] [Cancel]… │
├───────────────────────────────────────────────────────────────────┤
│ ▸ General Information ▸ Replacement Items ▸ Customer & Contract│
│ ▸ Status & Audit │
├───────────────────────────────────────────────────────────────────┤
│ Replacement Items │
│ Old Meter OldRead RemovalDt NewMeter NewRead InstallDt Seal │
│ MTR-0091 004821 -- -- -- -- -- │
└───────────────────────────────────────────────────────────────────┘
8. OData V4 Service Design
8.1 Service Definition — ZSD_MER_METER_EXCHANGE_O4
abap
@[Link]: 'Meter Exchange Request - OData V4 Service'
define service ZSD_MER_METER_EXCHANGE_O4 {
expose ZC_MeterExchangeRequest as MeterExchangeRequest;
expose ZC_MeterExchgReplacement as MeterExchgReplacement;
expose ZI_Technician as Technician;
expose ZI_ReasonCodeVH as ReasonCode;
}
8.2 Service Binding
ZSB_MER_METER_EXCHANGE_O4_UI — Binding Type: OData V4 - UI, bound to the Service
Definition above; entity set root MeterExchangeRequest published as the Fiori Elements
app's mainEntity . Preview/publish via /n/IWFND/V4_ADMIN and Launchpad tile registration
( semanticObject: MeterExchangeRequest , action: manage ).
8.3 Resource URLs (illustrative)
GET
/sap/opu/odata4/sap/mer_meter_exchange/srvd/sap/mer_meter_exchange_o4/0001/Mete
GET /sap/opu/odata4/.../MeterExchangeRequest('<uuid>')?$expand=_Item
POST /sap/opu/odata4/.../MeterExchangeRequest
PATCH /sap/opu/odata4/.../MeterExchangeRequest('<uuid>')
POST
/sap/opu/odata4/.../MeterExchangeRequest('<uuid>')/[Link].mer_met
8.4 Deep Insert / Navigation
Item creation is done via the composition navigation
MeterExchangeRequest('<uuid>')/_Item (deep create supported natively by managed RAP
+ draft — Fiori Elements table facet issues this automatically).
8.5 $batch / ETag Handling
FE issues all header+item changes plus action calls inside a single $batch per user save,
each request carrying If-Match: <ETag> derived from LocalLastChanged ; RAP's generated
lock / etag handling rejects stale batches with 412 Precondition Failed , surfaced to the
user as a conflict message.
9. Security & Authorization Design
9.1 Authorization Objects
Object Fields Purpose
ZMER_REQ ACTVT (01 Create, 02 Change, 03 Display, 06 Delete), Master check on
(custom) ZMER_STAT (status), ZMER_PRIO (priority) header CRUD,
restricts who can
touch High-priority
/ which statuses
ZMER_ACT ZMER_ACTN (action name: Governs which roles
(custom) APPR/REJT/ASGN/SCHD/CMPL/VRFY/CANC/REOP) may execute which
RAP actions
9.2 DCL (Data Control Language) — ZI_MeterExchangeRequest
abap
@MappingRole: true
define role ZI_MeterExchangeRequest {
grant select on ZI_MeterExchangeRequest
where ( status ) = aspect pfcg_auth( ZMER_REQ, ZMER_STAT, ACTVT = '03' );
}
Full CRUD/action checks are additionally enforced in the behavior implementation's
AUTHORIZATION CONTROL block ( CHECKAUTHORITY method) calling AUTHORITY-CHECK OBJECT
'ZMER_REQ' / 'ZMER_ACT' , since PFCG-object gating of actions (not only field/row visibility)
requires explicit instance authorization in RAP:
abap
METHOD get_instance_authorizations.
READ ENTITIES OF ZI_MeterExchangeRequest IN LOCAL MODE
ENTITY Request FIELDS ( Status ) WITH CORRESPONDING #( keys )
RESULT DATA(requests).
LOOP AT requests INTO DATA(req).
AUTHORITY-CHECK OBJECT 'ZMER_ACT'
ID 'ZMER_ACTN' FIELD 'APPR'
ID 'ACTVT' FIELD '36'. "custom activity for 'execute action'
result = VALUE #( ( %tky = req-%tky
%action-approve = COND #( WHEN sy-subrc = 0 THEN if_ab
ENDLOOP.
ENDMETHOD.
9.3 Role-Based Access Matrix
Capability CSR Supervisor Technician Administrator
Create Request ✅ ✅ ❌ ✅
Edit header (pre- ✅ ✅ ❌ ✅
Submit)
Submit ✅ ✅ ❌ ✅
Approve / Reject ❌ ✅ ❌ ✅
Assign Technician ❌ ✅ ❌ ✅
Schedule Visit ❌ ❌ ✅ ✅
Edit Replacement Item ❌ ❌ ✅ ✅
Complete Exchange ❌ ❌ ✅ ✅
Verify & Close ❌ ✅ ❌ ✅
Cancel Request ✅ (own, pre- ✅ ❌ ✅
Assign)
Reopen Request ❌ ✅ ❌ ✅
Delete ❌ ❌ ❌ ✅
View Reports ✅ (own) ✅ (team) ✅ (own ✅ (all)
workload)
Z_MER_CSR , Z_MER_SUPERVISOR , Z_MER_TECHNICIAN , Z_MER_ADMIN — each a Business Catalog
+ Business Role combination bundling the Fiori Launchpad tile/catalog, the OData service,
and the two custom authorization objects above with role-appropriate field values (e.g.,
Technician's ZMER_REQ restricts ACTVT = 02 only where ZMER_STAT IN ('AS','IP') ).
10. Reporting / Analytics
Implemented as CDS analytical query views ( @[Link]: true ) on top of
ZC_MeterExchangeRequest , exposed either as embedded FE KPI cards or standalone Fiori
Analytical List Page / Overview Page cards.
Report CDS View Key Logic
Pending ZQ_MER_PendingRequests Status IN ('NW','SB') , grouped by
Requests Priority
Completed ZQ_MER_CompletedRequests Status = 'CL' , grouped by
Requests Month(CompletionDate)
Overdue ZQ_MER_OverdueRequests DueDate < $session.system_date AND
Requests Status NOT IN ('CL','CN','RJ')
Technician ZQ_MER_TechnicianWorkload COUNT(*) grouped by AssignedTechnician
Workload where Status IN ('AS','IP')
High Priority ZQ_MER_HighPriorityRequests Priority = 'H'
Requests
Average ZQ_MER_AvgCompletionTime AVG( CompletionDate - RequestedDate
Completion ) , measure with @DefaultAggregation:
Time #AVG
Monthly ZQ_MER_MonthlyTrend COUNT(*) grouped by dats_to_month(
Exchange CompletionDate ) , time-series line/micro
Trend chart
Example measure definition:
abap
@[Link]: true
define view entity ZQ_MER_AvgCompletionTime as select from ZC_MeterExchangeRequ
@[Link]: true
AssignedTechnician,
@[Link]: true
@DefaultAggregation: #AVG
@[Link]: 'Avg Completion Days'
cast( CompletionDate - RequestedDate as [Link]( 5,1 ) ) as AvgCompletionDay
}
where Status = 'CL';
11. Business Rules — Status Flow State Machine
11.1 Status Codes
Code Meaning Terminal?
NW New No
SB Submitted No
AP Approved No
AS Assigned No
IP In Progress No
CO Completed No
CL Closed Yes
CN Cancelled Yes (reopenable)
RJ Rejected Yes (reopenable)
11.2 Allowed Transition Table
From \ To SB AP RJ AS IP CO CL CN NW(reopen)
NW ✔ ✔
SB ✔ ✔ ✔
AP ✔ ✔
From \ To SB AP RJ AS IP CO CL CN NW(reopen)
AS ✔ ✔
IP ✔
CO ✔
CL ✔
CN ✔
RJ ✔
All transitions not marked ✔ are rejected by the relevant action's status guard (see §6.4),
each raising zcx_mer_message=>invalid_status_transition — this is enforced both by
GET_INSTANCE_FEATURES (hides the button so the transition is never attempted from the UI)
and defensively inside each action implementation (protects OData-direct/API callers).
12. Sample Data Set
12.1 Customers (10)
CustomerID Business Partner Name Contract Account
CUST-0001 1000001 J. Alvarez CA-90001
CUST-0002 1000002 M. Chen CA-90002
CUST-0003 1000003 R. Patel CA-90003
CUST-0004 1000004 S. Johnson CA-90004
CUST-0005 1000005 L. Garcia CA-90005
CUST-0006 1000006 T. Nguyen CA-90006
CUST-0007 1000007 A. Kim CA-90007
CUST-0008 1000008 D. Brown CA-90008
CUST-0009 1000009 E. Rossi CA-90009
CUST-0010 1000010 K. Müller CA-90010
12.2 Technicians (10)
TechnicianID Name Region Active Workload (open AS/IP)
T-001 Carlos Diaz North 2
T-002 Priya Shah South 1
T-003 Mark Owens East 3
T-004 Nina Petrov West 0
T-005 Sam Lee North 2
T-006 Grace Kim Central 1
T-007 Jorge Ruiz South 4
T-008 Aaliyah White East 0
T-009 Tom Becker West 2
T-010 Yuki Tanaka Central 1
12.3 Requests (20, abbreviated table)
RequestNo Customer Device# Reason Priority Status Technician
0000001 CUST-0001 DEV- Malfunction High Assigned T-007
9931
0000002 CUST-0002 DEV- Accuracy Medium Submitted --
2210
0000003 CUST-0003 DEV- End of Life Low New --
4471
0000004 CUST-0004 DEV- Smart Medium In T-003
3390 Upgrade Progress
0000005 CUST-0005 DEV- Physical High Completed T-001
5521 Damage
0000006 CUST-0006 DEV- Customer Medium Closed T-005
6634 Complaint
0000007 CUST-0007 DEV- Malfunction Low Cancelled --
7788
RequestNo Customer Device# Reason Priority Status Technician
0000008 CUST-0008 DEV- Accuracy High Rejected --
8842
0000009 CUST-0009 DEV- End of Life Medium Approved --
9903
0000010 CUST-0010 DEV- Smart Low Assigned T-009
1029 Upgrade
0000011- (mix across all 10
0000020 customers, cycling
through every
status and reason
code, priorities
weighted 50%
Medium / 30%
Low / 20% High)
(Delivered separately as an executable class ZCL_MER_GENERATE_SAMPLE_DATA with an
IF_OO_ADT_CLASSRUN main method that inserts all header/item rows transactionally via
MODIFY ENTITIES — never direct INSERT into the DB tables, so number ranges,
determinations, and validations fire exactly as they would from the UI.)
13. Error Handling / Message Class Design
13.1 Message Class ZMER_MSG
Message Text Used By
No.
001 Customer &1 not found validateCustomerExists
002 Technician &1 unavailable or inactive validateTechnicianExists /
assignTechnician
003 Meter &1 is already assigned to an open validateDuplicateRequest
request
004 A duplicate open request already exists for validateDuplicateRequest
device &1
005 Installation date cannot be before removal validateInstallationDate
date
Message Text Used By
No.
006 You are not authorized to perform this get_instance_authorizations
action
007 High priority requests require supervisor validateHighPriority
approval
008 Reading value exceeds maximum of 999999 validateOldReading
009 Old Meter and New Meter are mandatory to validateCompletion
complete exchange
010 Invalid status transition from &1 all actions (guard)
011 Meter &1 does not exist in equipment validateMeterExists
master
012 Request &1 has been changed by another RAP ETag (framework-generated)
user
13.2 Exception Class
abap
CLASS zcx_mer_message DEFINITION PUBLIC INHERITING FROM cx_abap_behv_message CR
PUBLIC SECTION.
CONSTANTS:
BEGIN OF customer_not_found,
msgid TYPE symsgid VALUE 'ZMER_MSG',
msgno TYPE symsgno VALUE '001',
attr1 TYPE scx_attrname VALUE 'CUSTOMER_ID',
END OF customer_not_found,
BEGIN OF duplicate_request,
msgid TYPE symsgid VALUE 'ZMER_MSG',
msgno TYPE symsgno VALUE '004',
END OF duplicate_request,
BEGIN OF high_prio_needs_approval,
msgid TYPE symsgid VALUE 'ZMER_MSG',
msgno TYPE symsgno VALUE '007',
END OF high_prio_needs_approval,
BEGIN OF reading_out_of_range,
msgid TYPE symsgid VALUE 'ZMER_MSG',
msgno TYPE symsgno VALUE '008',
END OF reading_out_of_range,
BEGIN OF item_required,
msgid TYPE symsgid VALUE 'ZMER_MSG',
msgno TYPE symsgno VALUE '009',
END OF item_required,
BEGIN OF mandatory_fields_missing,
msgid TYPE symsgid VALUE 'ZMER_MSG',
msgno TYPE symsgno VALUE '009',
END OF mandatory_fields_missing,
BEGIN OF install_before_removal,
msgid TYPE symsgid VALUE 'ZMER_MSG',
msgno TYPE symsgno VALUE '005',
END OF install_before_removal,
BEGIN OF invalid_status_transition,
msgid TYPE symsgid VALUE 'ZMER_MSG',
msgno TYPE symsgno VALUE '010',
END OF invalid_status_transition.
ENDCLASS.
All messages are raised through reported- tables (never MESSAGE ... TYPE 'E' ), keeping
the behavior implementation UI-agnostic and OData/Fiori/API-consistent per RAP
guidelines.
14. Performance Engineering
Indexes: secondary DB index on zmer_d_header( device_number, status ) to support
the duplicate-request validation and the Device# filter without a full table scan; index
on zmer_d_header( assigned_technician, status ) for the Technician Workload
query; index on zmer_d_item( request_uuid ) (implicit via FK but confirmed) for fast
composition reads.
CDS optimization: all filtering ( Status , Priority , date ranges) pushed into the
WHERE / $filter translated to SQL — no client-side ABAP filtering of large result sets;
analytical queries ( ZQ_MER_* ) use @[Link] so aggregation happens in
HANA, not in the application layer.
Associations vs. Joins: value-help associations ( _Technician , _ReasonCode ) are
declared to-one where possible so OData $expand compiles to efficient HANA joins
rather than N+1 calls; the Item composition is only expanded on the Object Page
request ( $expand=_Item scoped to a single key), never expanded on the List Report
collection query.
Paging: List Report table uses standard OData $top / $skip server-side paging
(default page size 50, growing threshold in FE growingScrollToLoad ), so SELECT * is
never issued against the full header table.
Lazy Loading: facets (Customer & Contract, Status & Audit) are lazy-loaded on tab
activation in the Object Page ( [Link] default facet lazy loading) rather than pre-
fetched with the header.
Search optimization: @[Link] combined with
@[Link] on RequestNo / DeviceNumber leverages the HANA
full-text/ESH search infrastructure for the FE search field instead of a LIKE '%...%'
scan.
HANA Pushdown: all determinations that only recompute a derived value
( RequestAgeDays , DueDate ) are simple date arithmetic done in MODIFY ENTITIES
against the buffered draft/active entity — no round trip to HANA calculation views
needed; the analytical AVG / COUNT reports rely on native CDS aggregation, which
HANA pushes down fully (no ABAP-side GROUP BY).
Avoiding N+1 in validations: all validation methods operate on the full keys table in
a single READ ENTITIES / LOOP , never issuing a SELECT per key inside a loop over
individual instances (the duplicate-check SELECT COUNT(*) is the one legitimate per-
row DB hit, justified because it must check against records outside the current
transaction buffer).
15. Clean Core Architecture & Package/Folder Structure
15.1 ABAP Package Hierarchy
ZMER_METER_EXCHANGE (super package, software
component ZLOCAL/customer)
├── ZMER_DB Database tables, domains, data
elements, number ranges
│ ZMER_D_HEADER, ZMER_D_ITEM, ZMER_E_*, ZMER_NR
├── ZMER_CDS_INTERFACE Interface (basic) CDS view
entities
│ ZI_MeterExchangeRequest, ZI_MeterExchgReplacement, ZI_Technician,
ZI_ReasonCodeVH, ZI_StatusVH, ZI_DeviceVH
├── ZMER_CDS_CONSUMPTION Consumption CDS views + Metadata
Extensions
│ ZC_MeterExchangeRequest(+_MDE), ZC_MeterExchgReplacement(+_MDE)
├── ZMER_CDS_ANALYTICS Analytical query CDS views
│ ZQ_MER_PendingRequests, ZQ_MER_OverdueRequests,
ZQ_MER_TechnicianWorkload, ...
├── ZMER_BEHAVIOR Behavior Definitions + Behavior
Implementation classes
│ ZBP_I_MeterExchangeRequest, ZBP_I_MeterExchgReplacement
├── ZMER_SERVICE Service Definitions & Bindings
│ ZSD_MER_METER_EXCHANGE_O4, ZSB_MER_METER_EXCHANGE_O4_UI
├── ZMER_SECURITY DCL role definitions, PFCG role
transport containers
│ ZI_MeterExchangeRequest (DCL), ZMER_REQ / ZMER_ACT auth objects
├── ZMER_UTIL Utility classes, constants,
message class
│ ZCL_MER_UTILITY, ZCL_MER_CONSTANTS, ZMER_MSG, ZCX_MER_MESSAGE
├── ZMER_TEST ABAP Unit test classes (local
test doubles via CL_ABAP_TESTDOUBLE / RAP BDEF test doubles)
│ ZCL_MER_TEST_VALIDATIONS, ZCL_MER_TEST_DETERMINATIONS,
ZCL_MER_TEST_ACTIONS
└── ZMER_SAMPLEDATA Sample-data generation
report/class (non-productive package)
ZCL_MER_GENERATE_SAMPLE_DATA
15.2 Clean Core Principles Applied
Every custom object lives in customer namespace ( Z* ), no modification of SAP
standard objects (no core mods, no implicit/classic enhancements to standard tables).
Only released APIs are used for cross-object access: I_BusinessPartner (released
CDS), CL_ABAP_CONTEXT_INFO (released class), CL_NUMBERRANGE_RUNTIME (released),
Adobe Document Services released API for PDF generation, Business Event released
API ( CL_BUSINESS_EVENT... ) for notifications.
ABAP Cloud language scope: strict syntax ( strict(2) in behavior definitions forces
the newest, most restrictive RAP syntax check), no classic SELECT ... INTO
CORRESPONDING FIELDS , no client-handling ABAP (client handling delegated to
platform), no dynamic RFC calls, no direct table INSERT / UPDATE / DELETE on custom
tables from application logic — all persistence exclusively through MODIFY ENTITIES /
EML.
In-app extensibility is designed via Key User extensibility points (custom fields on
ZC_MeterExchangeRequest using SAP's Custom Fields app) rather than developer
modification, wherever business-user-level extension is plausible (e.g., adding a
customer-specific reason code).
Side-by-side extensibility: the OData V4 service is consumable from BTP (e.g., a
CAP-based mobile companion app) without any core changes, per Clean Core's "keep
the core clean, extend on BTP" tenet.
16. Extensibility Roadmap
Enhancement How it plugs in
GIS Integration Add a _GisRoute association from ZI_MeterExchangeRequest to a BTP-
hosted GIS microservice CDS remote entity; a new action optimizeRoute
calls it via a released HTTP client destination
SAP Field generateWorkOrder action extended to call FSM's OData API (outbound
Service integration via Cloud Connector/Destination service) to create a matching FSM
Management Activity, storing the FSM Activity ID alongside WorkOrderID
(FSM)
IoT Smart Meter New inbound CDS remote table / RAP unmanaged entity fed by SAP IoT /
Events Event Mesh messages; a determination on ZI_MeterExchangeRequest auto-
creates a request when a "meter fault" telemetry event arrives, pre-filling
ReasonCode = Malfunction
SAP Asset Expose an OData V4 offline-enabled service binding variant of the same
Manager Service Definition; SAP Mobile Services handles delta sync so Technicians can
(mobile offline) complete exchanges without connectivity
Adobe Forms generatePdfReport already stubs this; production version binds an Adobe
(PDF work Forms Cloud form template to the ZI_MeterExchangeRequest + _Item
order) structure
Workflow Replace the simple Approve / Reject actions with an SAP Build Process
Approval Automation (BPA) workflow trigger for High-priority requests ( >
$threshold ), keeping Low/Medium as immediate RAP actions
SAP Event Mesh Emit a business event
( [Link].v1 ) on every terminal
status change via the RAP-native "Business Event" outbound channel, for
downstream billing/CRM subscribers
SAP Build Bot picks up "Overdue Requests" report entries and auto-nudges the assigned
Process Technician via Teams/email
Automation
Enhancement How it plugs in
AI-based Fault A BTP AI Core model scores incoming meter telemetry; predicted-fault score
Prediction surfaces as a new field ( PredictedFaultScore ) via CDS extension, feeding a
proactive "Suggested Exchange" list
Generative AI Joule/Generative AI extension summarizing a request's history and drafting
Assistant the Technician's Comments field from photo/voice input, via a released
Generative AI Hub API call inside a new action draftSummary
17. Unit Testing Strategy
Framework: ABAP Unit + RAP Business Object Test Double Framework
( cl_rap_test_environment ), which creates in-memory shadow persistence so tests
never touch real DB tables.
Coverage targets:
Every validation tested with at least one passing and one failing key
combination (e.g., validateOldReading with 999999 = pass, 1000000 = fail).
Every determination tested for both trigger conditions ( create vs field -
triggered modify ) — e.g., calculateDueDate re-fires when Priority changes
post-creation.
Every action tested for (a) valid status transition succeeds, (b) invalid status
transition is rejected with the correct message, (c) side effects (e.g.,
HighPrioApproved flag) are set correctly.
Authorization tests using cl_abap_unit_assert combined with mocked
AUTHORITY-CHECK results to confirm Technician cannot call approve , etc.
abap
CLASS ltc_validate_old_reading DEFINITION FOR TESTING
DURATION SHORT RISK LEVEL HARMLESS.
PRIVATE SECTION.
DATA cut TYPE REF TO cl_rap_test_environment.
METHODS: setup, teardown,
reading_within_range FOR TESTING,
reading_exceeds_limit FOR TESTING.
ENDCLASS.
CLASS ltc_validate_old_reading IMPLEMENTATION.
METHOD setup.
cut = cl_rap_test_environment=>create( i_for_entity = 'ZI_MeterExchgReplace
ENDMETHOD.
METHOD reading_exceeds_limit.
cut->clear_doubles( ).
MODIFY ENTITIES OF ZI_MeterExchgReplacement IN LOCAL MODE
ENTITY Item
CREATE FIELDS ( OldReading )
WITH VALUE #( ( %cid = 'A' %is_draft = if_abap_behv=>mk-on OldReading =
MAPPED DATA(mapped) FAILED DATA(failed) REPORTED DATA(reported).
cl_abap_unit_assert=>assert_not_initial( failed-item ).
ENDMETHOD.
METHOD reading_within_range.
" analogous, asserts failed-item IS INITIAL
ENDMETHOD.
METHOD teardown.
cut->destroy( ).
ENDMETHOD.
ENDCLASS.
Regression suite wired into the CI/CD pipeline ( abapGit + gCTS / abap Environment
Pipeline ) so every transport is gated on green ABAP Unit results before release to QA.
18. Deployment Steps
1. Create the ABAP package hierarchy (§15.1) in the relevant Software Component
(customer namespace) via ADT.
2. Deploy DDIC objects: domains → data elements → ZMER_D_HEADER / ZMER_D_ITEM →
number range object ZMER_NR .
3. Activate Interface CDS views ( ZI_* ), then Consumption views + Metadata
Extensions ( ZC_* ).
4. Create and activate Behavior Definitions and generate/implement Behavior
Implementation classes ( ZBP_* ) — implement validations, determinations, actions,
feature control, and authorization methods per §5–6, §9.
5. Create Service Definition and Service Binding; Publish the binding; run the built-in
Preview to smoke-test List Report/Object Page rendering.
6. Register the app on the Fiori Launchpad: create a Business Catalog, assign the app's
semantic object/action tile, and bundle into a Business Role.
7. Create DCL role + custom authorization objects ZMER_REQ / ZMER_ACT ; build
PFCG/Business Roles Z_MER_CSR , Z_MER_SUPERVISOR , Z_MER_TECHNICIAN , Z_MER_ADMIN
and assign to test users.
8. Run ZCL_MER_GENERATE_SAMPLE_DATA in a sandbox/QA client to seed the 10 customers /
10 technicians / 20 requests.
9. Execute the ABAP Unit regression suite; confirm 100% pass before transport release.
10. Transport through the standard 3-system landscape (Dev → QA → Prod) via
gCTS/standard CTS, with QA sign-off gates for the process owner (Utilities
Operations) and Security (role review).
11. Post-go-live: activate Business Event emission (if Event Mesh extension is in scope)
and monitor via /IWFND/ERROR_LOG and Application Job Monitoring for the first cycle.
19. Interview Questions Based on This Implementation
RAP fundamentals
1. Why was the managed RAP scenario chosen over unmanaged for this app, and under
what circumstances would you switch to unmanaged?
2. Explain the difference between field ( numbering : managed ) (early numbering)
and the late-numbering pattern used for RequestNo here. Why can't RequestNo also
use early numbering?
3. What's the purpose of strict ( 2 ) in a Behavior Definition, and what specifically
does it forbid compared to strict ( 1 ) ?
Data modeling 4. Why is _Item modeled as a composition and _Technician as a plain
association? What breaks if you get this backwards? 5. Why does the child Behavior
Definition use lock dependent by _Request rather than its own lock master ? 6. Walk
through why RequestUUID is the technical key while RequestNo is the semantic key — what
problem does this split solve for draft handling?
Validations/Determinations/Actions 7. Why is validateCompletion implemented as a
validation rather than embedding the check directly inside the completeExchange action?
8. Explain the difference between on save and on modify determination triggers, and give
one example of each from this app. 9. Why does completeExchange re-check Status = 'IP'
even though GET_INSTANCE_FEATURES already disables the button unless status is IP ? 10.
How would you prevent a race condition where two Supervisors try to Approve the same
request simultaneously?
Security 11. Why can't row-level "who may execute action X" be enforced purely with a
DCL role — what does get_instance_authorizations add? 12. How would you restrict a
Technician's authorization object so they can edit items only for requests assigned to them
specifically (not just any Assigned/In-Progress request)?
Performance 13. Why is the duplicate-request check implemented as a SELECT COUNT(*)
inside the validation rather than an association/CDS join? 14. What is HANA pushdown,
and which parts of this design rely on it?
Architecture / Clean Core 15. What does "Clean Core" forbid that a classic ECC custom
development might have done here, and how does that show up concretely in this design
(e.g., number ranges, BP access)? 16. How would you side-by-side extend this app on BTP
without touching the S/4HANA core, if a customer wanted a customer-facing self-service
portal for reporting meter faults?
20. Clean Core Compliance Checklist
Principle Status Evidence
Customer namespace ✅ All objects prefixed Z / ZMER_
only
No core modifications ✅ No SAP standard object touched; only released CDS
( I_BusinessPartner ) consumed
ABAP Cloud restricted ✅ strict(2) behavior definitions, EML-only persistence, no
syntax classic SELECT ... INTO CORRESPONDING
Released APIs only ✅ CL_ABAP_CONTEXT_INFO , CL_NUMBERRANGE_RUNTIME ,
I_BusinessPartner , Adobe Document Services, Business
Event API
No direct table access ✅ All CRUD via MODIFY ENTITIES /EML, never raw
outside owning BO INSERT / UPDATE / DELETE
In-app extensibility ✅ Custom Fields app recommended for business-user extension
over hard-coded fields points
Side-by-side ✅ OData V4 service consumable from BTP without core change
extensibility ready
No classic Dynpro / ✅ 100% RAP + Fiori Elements
BOPF
Principle Status Evidence
Draft & optimistic ✅ with draft , etag master LocalLastChanged
concurrency native
Role-based, least- ✅ 4 distinct PFCG/Business Roles, instance-level
privilege authorization get_instance_authorizations
End of design document. This specification, combined with the DDL/DDIC, CDS, Behavior
Definition/Implementation, and DCL code excerpts provided, gives an experienced
ABAP/RAP developer everything needed to build, test, and deploy the Meter Exchange
Request Management application end-to-end in an S/4HANA Cloud (ABAP Cloud)
environment.