来源资料待手动核验
ABAP Dynamic Assignment
ABAP Cloud-ready framework for dynamic variant assignment
项目简介
ABAP Cloud-ready framework for dynamic variant assignment
ABAP Cloud-ready framework for dynamic variant assignment
ABAP Cloud-ready framework for dynamic variant assignment
Open Source Contribution: This project is community-driven and Open Source! 🚀 If you spot a bug or have an idea for a cool enhancement, your contributions are more than welcome. Feel free to open an Issue or submit a Pull Request.
A lightweight, dynamic runtime parameter framework. It decouples configuration values from code logic, allowing developers, functional consultants or key users to maintain variables, ranges and mappings through a Fiori Elements application or a programmatic API, bypassing hardcoded values and the rigid standard TVARVC table.
This project is licensed under the MIT License.
The repository was created by George Drakos.
IF bukrs = '1000' or hardcoded configuration IDs.SELECT ... IN) and mapping tables at runtime.
Every value is converted into the type of the caller's own variable.map_value runs the configured rules against one value and
answers which bucket it falls into, comparing in the configured DDIC type rather than on strings.ZIF_DA_VARIANTS, so consumers can mock the framework
with cl_abap_testdouble instead of setting up a database.cl_abap_testdouble does the same for the RAP query interfaces.ZDA_VAR — see AuthorizationZCL_DA_VARIANTS=>default_packages to the name of your own package.
The constructor validates the configuration table against this list and rejects anything outside it.ZUI_DA_VARIANTS_O4.The framework ships an authorization object that the RAP handlers check on every create, update and delete.
Authorization field (SU20, or ADT → New → Other ABAP Repository Object → Authorization Field):
| Field | Data element |
|---|---|
ZDA_PROG | ZDE_DA_PROGNAME |
Authorization object (SU21):
| Object | Fields | Permitted activities |
|---|---|---|
ZDA_VAR | ACTVT, ZDA_PROG | 01 Create, 02 Change, 03 Display, 06 Delete |
Build two PFCG roles — most users only need to see what is configured:
| Role | ACTVT | ZDA_PROG |
|---|---|---|
| Display | 03 | * or specific programs |
| Maintain | 01, 02, 03, 06 | * or specific programs |
03is mandatory in the maintain role as well. The DCL grants read access throughaspect pfcg_auth ( ZDA_VAR, ZDA_PROG, ACTVT = '03' ), and RAP reads the instance before every update or delete. Without03the user sees "record not found" while holding change authorization.
The DCL protects the Fiori application, not the programmatic API.
get_variantreads the table directly, so a background job is never filtered by the authorizations of a user. That is intentional — a scheduled job must not depend on who happens to be logged on.set_variantanddelete_variantwrite for the same reason, without an authority check. Know that before you expose them.
ZCL_DA_VARIANTS behind ZIF_DA_VARIANTS.get_variant. Only the targets you actually supply are computed.DATA product TYPE i_product-product.
DATA product_range TYPE RANGE OF i_product-product.
DATA products TYPE STANDARD TABLE OF i_product-product WITH EMPTY KEY.
TRY.
DATA(variants) = CAST zif_da_variants( NEW zcl_da_variants( ) ).
variants->get_variant(
EXPORTING parameter_id = 'VALID_MATERIALS'
program_name = 'GLOBAL'
IMPORTING field_value = product " the first single value
range = product_range " a real RANGE OF, ready for a WHERE clause
values = products ). " one row per active variant
SELECT FROM i_product
FIELDS product, productgroup
WHERE product IN @product_range
INTO TABLE @DATA(matching_products).
CATCH zcx_da_variants INTO DATA(error).
" error->get_text( ) carries the reason
ENDTRY.
The constructor can fail. It validates the configuration table name with
cl_abap_dyn_prg=>check_table_name_str, so it raisesZCX_DA_VARIANTSif the table is unknown or lives outside the allowed packages. Wrap it in the sameTRYas the read.
Range types come from the configuration. The DDIC type of the returned range is taken from the
DataElementcolumn. Leave it empty to get the native 255 character column type, which converts cleanly into any character-like range. Set it when your caller uses a numeric or date range.
Use set_variant for initial data loads, seed data or API integrations.
TRY.
DATA(variants) = CAST zif_da_variants( NEW zcl_da_variants( ) ).
variants->set_variant(
parameter_id = 'DEFAULT_PLANTS'
program_name = 'ZTEST'
counter = '00001' " omit to append a new row
field_value = '1000'
high_value = '2000'
sign = zcl_da_variants=>sign_include
option = zcl_da_variants=>opt_bt
description = 'Default Plant for Operations' ).
CATCH zcx_da_variants INTO DATA(error).
" validation failed, or the database rejected the row
ENDTRY.
The caller owns the LUW.
set_variantdoes not commit. Passcommit = abap_trueonly from standalone scripts — never from a RAP handler, a determination or a validation.
Seed data is idempotent. Passing an existing
counterreplaces that row instead of appending a new one, so a load script can run twice without duplicating configuration.
set_variant rejects anything the framework would not be able to read back: a missing Parameterid
or Value, an unknown or non-elementary data element, BT or NB without an upper bound, and an
upper bound on any other operator. The Fiori application enforces the same rules through the same
code, so both doors accept exactly the same rows.
The value has to fit its data element.
1000configured against a one character element would come back as1, andABCagainst aNUMCelement would come back as zeros. Both are refused on write. The check knows what each type loses in silence:CHARtruncates on the right,NUMCkeeps only the digits, and a date field is character like, so20240230would be copied straight in as a day that does not exist. Leading zeros thatNUMCadds by itself are not a loss and stay accepted.
Appending never overwrites. Without a
counterthe row is inserted, never replaced. If another LUW takes the number in between, the next one is allocated and the insert is repeated. An exhausted counter range is reported as an error, not wrapped around to00000.
TRY.
DATA(variants) = CAST zif_da_variants( NEW zcl_da_variants( ) ).
" one row
DATA(removed) = variants->delete_variant( parameter_id = 'DEFAULT_PLANTS'
program_name = 'ZTEST'
counter = '00002' ).
" the whole parameter
removed = variants->delete_variant( parameter_id = 'DEFAULT_PLANTS'
program_name = 'ZTEST' ).
CATCH zcx_da_variants INTO DATA(error).
" no parameter was named, or the database refused the delete
ENDTRY.
delete_variant returns the number of rows it removed. Removing what is not there is not an error,
so a cleanup script can run twice. A blank parameter_id is refused, because without it the call
would clear whatever happens to carry a blank key.
The caller owns the LUW here as well. Pass
commit = abap_trueonly from standalone scripts.
Beyond filtering, the framework can translate. A mapping variant answers "what does X correspond to?" rather than "is X in scope?".
| Progname | Parameterid | Counter | Sign | Opt | Value | MappingValue |
|---|---|---|---|---|---|---|
| ZORDER_IF | ORDER_TYPE_MAP | 1 | I | EQ | CUST_STD | OR |
| ZORDER_IF | ORDER_TYPE_MAP | 2 | I | EQ | CUST_RET | RE |
DATA pairs_ref TYPE REF TO data.
FIELD-SYMBOLS <pairs> TYPE STANDARD TABLE.
variants->get_variant( EXPORTING parameter_id = 'ORDER_TYPE_MAP'
program_name = 'ZORDER_IF'
IMPORTING mapping_values = pairs_ref ).
ASSIGN pairs_ref->* TO <pairs>.
LOOP AT <pairs> ASSIGNING FIELD-SYMBOL(<pair>).
ASSIGN COMPONENT 'VALUE' OF STRUCTURE <pair> TO FIELD-SYMBOL(<from>).
ASSIGN COMPONENT 'MAPPING_VALUE' OF STRUCTURE <pair> TO FIELD-SYMBOL(<to>).
" <from> is the incoming code, <to> is the value to post with
ENDLOOP.
The table is returned as a data reference because the type of the MAPPING_VALUE column is only known
at runtime, from the configured MappingDataElement. If a parameter holds a single row, use
mapping_field_value instead and skip the table entirely.
Two ways to read the same rows. A mapping table built with
mapping_valuespairsValuewithMappingValueand ignores the range fields.map_valueuses them as the rule that decides which pair applies. Rows written for the first style work in the second: anEQrow simply matches its own value.
get_variant answers "which values are in scope". map_value reads the same rows the other way
round and answers "which bucket does this one value fall into". Every active row is a rule,
evaluated in counter order, with the operator and the bounds it carries.
| Progname | Parameterid | Counter | Sign | Opt | Value | HighValue | MappingValue |
|---|---|---|---|---|---|---|---|
| ZPLANTS | PLANT_REGION | 1 | E | EQ | 1500 | ||
| ZPLANTS | PLANT_REGION | 2 | I | BT | 1000 | 1999 | NORTH |
| ZPLANTS | PLANT_REGION | 3 | I | BT | 2000 | 2999 | SOUTH |
DATA region TYPE c LENGTH 10.
variants->map_value( EXPORTING parameter_id = 'PLANT_REGION'
program_name = 'ZPLANTS'
input = '1200'
IMPORTING mapping_value = region " NORTH
matched = DATA(matched) ).
The first rule that answers decides. Row 1 carries sign_exclude, so plant 1500 stops the search
and comes back unmatched even though row 2 would otherwise have covered it. That is how a hole is
punched into a range that maps as a whole.
The comparison runs in the configured DDIC type. A
BT 9 AND 100on a numeric element matches50. On the stored 255 character strings the same rule would reject it, because'50'sorts below'9'. SetDataElementwhenever the values are numeric or dates. Patterns (CP,NP) always compare character wise, wildcards included.
A value that no rule covers is not an error.
matchedcomes backabap_falseandmapping_valuestays initial. Only an unknown parameter raises.
DATA(variants) = CAST zif_da_variants(
NEW zcl_da_variants( table_name = 'ZMY_OWN_VARIANTS'
packages = 'ZMY_PACKAGE' ) ).
为保持页面易读性,当前仅展示 README 的前半部分。请打开原始 README 阅读完整内容。