6.5 Bedrijfsregels en validaties
Kan bedrijfsregels en validaties definiëren die bij StUF-berichten horen.
Bedrijfsregels in StUF-context
Bedrijfsregels zijn voorschriften die bepalen hoe gegevens in StUF-berichten moeten worden gevalideerd, verwerkt en geïnterpreteerd. Ze borgen de kwaliteit en consistentie van gegevensuitwisseling tussen overheidsapplicaties.
Types bedrijfsregels
1. Structurele validaties
- XML-schema conformiteit
- Verplichte velden controle
- Datatype-validaties
- Lengte-beperkingen
2. Semantische validaties
- BSN-elfproef
- Datum-consistentie
- Referentiële integriteit
- Domain-specific rules
3. Autorisatie-regels
- Toegangsrechten per organisatie
- Gegevens-classificatie
- Privacy-beperkingen
- Audit-requirements
4. Business-logica regels
- Workflow-validaties
- Status-transities
- Tijdstippen en deadlines
- Cross-system consistency
Validatie-architectuur
graph TD
A[Incoming StUF Message] --> B[XML Schema Validation]
B --> C[Structure Validation]
C --> D[Business Rules Engine]
D --> E[Authorization Check]
E --> F[Semantic Validation]
F --> G[Cross-Reference Validation]
B --> B1[XSD Compliance]
C --> C1[Mandatory Fields]
C --> C2[Data Types]
C --> C3[Field Lengths]
D --> D1[BSN Validation]
D --> D2[Date Logic]
D --> D3[Status Rules]
E --> E1[Organization Rights]
E --> E2[Data Classification]
E --> E3[Privacy Level]
F --> F1[Domain Logic]
F2[Consistency Checks] --> F
F --> F3[Reference Checks]
G --> H[Accept Message]
G --> I[Reject with Fo01]
H --> H1[Process Business Logic]
I --> I1[Detailed Error Report]
Implementatie van bedrijfsregels
XML Schema-validaties
BSN-validatie in XSD:
<!-- BSN-datatype definitie -->
<xs:simpleType name="BSN">
<xs:restriction base="xs:string">
<xs:pattern value="[0-9]{9}"/>
<xs:length value="9"/>
</xs:restriction>
</xs:simpleType>
<!-- Datum-validatie -->
<xs:simpleType name="StUF_datum">
<xs:union>
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:pattern value="[0-9]{8}"/>
<xs:length value="8"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType>
<xs:restriction base="StUF_noValue"/>
</xs:simpleType>
</xs:union>
</xs:simpleType>
<!-- Postcode-validatie -->
<xs:simpleType name="Nederlandse_postcode">
<xs:restriction base="xs:string">
<xs:pattern value="[1-9][0-9]{3} ?[A-Za-z]{2}"/>
<xs:minLength value="6"/>
<xs:maxLength value="7"/>
</xs:restriction>
</xs:simpleType>
Java-implementatie bedrijfsregels
BSN-elfproef validatie:
@Component
public class StufBusinessRulesValidator {
public ValidationResult validateBSN(String bsn) {
// Structuur-check
if (bsn == null || !bsn.matches("\\d{9}")) {
return ValidationResult.error("StUF071",
"BSN moet bestaan uit exacte 9 cijfers");
}
// Elfproef-algoritme
int[] weights = {9, 8, 7, 6, 5, 4, 3, 2, 1};
int sum = 0;
for (int i = 0; i < 9; i++) {
int digit = Character.getNumericValue(bsn.charAt(i));
sum += digit * weights[i];
}
if (sum % 11 != 0 || bsn.startsWith("0")) {
return ValidationResult.error("StUF071",
"BSN voldoet niet aan elfproef-controle");
}
return ValidationResult.success();
}
public ValidationResult validateDatum(String datum, String veldNaam) {
if (datum == null || datum.isEmpty()) {
return ValidationResult.success(); // Optioneel veld
}
// Format-check
if (!datum.matches("\\d{8}")) {
return ValidationResult.error("StUF072",
String.format("%s moet format YYYYMMDD hebben", veldNaam));
}
// Datum-parsing
try {
LocalDate parsed = LocalDate.parse(datum,
DateTimeFormatter.ofPattern("yyyyMMdd"));
// Realistische datum-check
if (parsed.isAfter(LocalDate.now().plusYears(1))) {
return ValidationResult.warning("StUF073",
String.format("%s ligt ver in de toekomst", veldNaam));
}
if (parsed.isBefore(LocalDate.of(1900, 1, 1))) {
return ValidationResult.warning("StUF074",
String.format("%s ligt ver in het verleden", veldNaam));
}
return ValidationResult.success();
} catch (DateTimeParseException e) {
return ValidationResult.error("StUF072",
String.format("%s bevat ongeldige datum", veldNaam));
}
}
public ValidationResult validateAdresConsistentie(
String postcode, String huisnummer, String woonplaats) {
// Cross-field validatie
if (StringUtils.isBlank(postcode) && StringUtils.isBlank(woonplaats)) {
return ValidationResult.error("StUF075",
"Postcode of woonplaats is verplicht voor adres");
}
// BAG-validatie (mockup)
if (StringUtils.isNotBlank(postcode) && StringUtils.isNotBlank(huisnummer)) {
boolean bagExists = bagService.existsAddress(postcode, huisnummer);
if (!bagExists) {
return ValidationResult.warning("StUF076",
"Adres niet gevonden in BAG-registratie");
}
}
return ValidationResult.success();
}
}
Autorisatie-regels
Role-based access control:
@Component
public class StufAuthorizationValidator {
public ValidationResult validateMessageAccess(
StuurGegevens stuurgegevens, String berichtType) {
String organisatie = stuurgegevens.getZender().getOrganisatie();
String applicatie = stuurgegevens.getZender().getApplicatie();
String gebruiker = stuurgegevens.getZender().getGebruiker();
// Organisatie-verificatie
if (!isValidOrganisatie(organisatie)) {
return ValidationResult.error("StUF013",
"Onbekende organisatie-code: " + organisatie);
}
// Applicatie-autorisatie
Application app = applicationService.getByNameAndOrg(applicatie, organisatie);
if (app == null || !app.isActive()) {
return ValidationResult.error("StUF013",
"Applicatie niet geautoriseerd: " + applicatie);
}
// Bericht-type autorisatie
if (!app.hasPermissionForMessageType(berichtType)) {
return ValidationResult.error("StUF013",
String.format("Geen autorisatie voor berichttype '%s'", berichtType));
}
// Gebruiker-verificatie (optioneel)
if (StringUtils.isNotBlank(gebruiker)) {
User user = userService.getByUsernameAndOrg(gebruiker, organisatie);
if (user == null || !user.isActive()) {
return ValidationResult.warning("StUF013",
"Onbekende gebruiker: " + gebruiker);
}
}
return ValidationResult.success();
}
public ValidationResult validateDataAccess(
String organisatie, String dataElement) {
DataClassification classification =
dataClassificationService.getClassification(dataElement);
OrganisationPermission permissions =
permissionService.getPermissions(organisatie);
if (classification.getLevel() > permissions.getMaxDataLevel()) {
return ValidationResult.error("StUF013",
String.format("Onvoldoende autorisatie voor '%s' (niveau %d)",
dataElement, classification.getLevel()));
}
// Privacy-restricties
if (classification.isPrivacySensitive() &&
!permissions.hasPrivacyClearance()) {
return ValidationResult.error("StUF013",
String.format("Geen privacy-autorisatie voor '%s'", dataElement));
}
return ValidationResult.success();
}
}
Domein-specifieke validaties
BRP-specifieke bedrijfsregels
@Component
public class BrpBusinessRulesValidator {
public ValidationResult validatePersonenGegevens(NpsObject persoon) {
List<ValidationResult> results = new ArrayList<>();
// Geslacht/voornaam consistentie
results.add(validateGeslachtVoornaamConsistentie(
persoon.getGeslachtsaanduiding(),
persoon.getVoornamen()));
// Leeftijd-afhankelijke validaties
results.add(validateLeeftijdAfhankelijkeGegevens(persoon));
// Adres-historie consistency
results.add(validateAdresHistorie(persoon.getAdresGegeven()));
// Burgerlijke staat logic
results.add(validateBurgerlijkeStaatTransities(
persoon.getBurgerlijkeStaat()));
return ValidationResult.combine(results);
}
private ValidationResult validateGeslachtVoornaamConsistentie(
String geslacht, String voornamen) {
if ("M".equals(geslacht) && voornamen != null) {
List<String> vrouwelijkeVoornamen = Arrays.asList(
"Maria", "Anna", "Elisabeth", "Catharina");
for (String vrouwelijkeNaam : vrouwelijkeVoornamen) {
if (voornamen.contains(vrouwelijkeNaam)) {
return ValidationResult.warning("BRP001",
String.format("Mogelijke inconsistentie: geslacht %s met voornaam %s",
geslacht, vrouwelijkeNaam));
}
}
}
return ValidationResult.success();
}
private ValidationResult validateLeeftijdAfhankelijkeGegevens(NpsObject persoon) {
LocalDate geboortedatum = parseDatum(persoon.getGeboortedatum());
if (geboortedatum == null) {
return ValidationResult.success(); // Cannot validate without birthdate
}
int leeftijd = Period.between(geboortedatum, LocalDate.now()).getYears();
// Minderjarigen mogen niet getrouwd zijn
if (leeftijd < 18 && "H".equals(persoon.getBurgerlijkeStaat().getCode())) {
return ValidationResult.error("BRP002",
"Persoon onder 18 jaar kan niet getrouwd zijn");
}
// AOW-leeftijd inconsistenties
if (leeftijd >= 67 && persoon.hasActiveEmployment()) {
return ValidationResult.warning("BRP003",
"Persoon boven AOW-leeftijd heeft actieve dienstbetrekking");
}
return ValidationResult.success();
}
}
ZKN-specifieke bedrijfsregels
@Component
public class ZknBusinessRulesValidator {
public ValidationResult validateZaakCreatie(ZakObject zaak) {
List<ValidationResult> results = new ArrayList<>();
// Zaaktype-validaties
results.add(validateZaaktype(zaak.getZaaktype()));
// Behandelaar-autorisatie
results.add(validateBehandelaarAutorisatie(
zaak.getBehandelaar(), zaak.getZaaktype()));
// Termijn-validaties
results.add(validateBehandelingsTermijn(zaak));
// Vertrouwelijkheid-regels
results.add(validateVertrouwelijkheidNiveau(zaak));
return ValidationResult.combine(results);
}
public ValidationResult validateZaakStatusTransitie(
String huidigeStatus, String nieuweStatus, ZaaktypeObject zaaktype) {
// Haal toegestane transities op uit zaaktype
List<StatusTransitie> toegestaneTransities =
zaaktype.getStatusTransities();
boolean transitieToegstaan = toegestaneTransities.stream()
.anyMatch(t -> t.getVanStatus().equals(huidigeStatus) &&
t.getNaarStatus().equals(nieuweStatus));
if (!transitieToegstaan) {
return ValidationResult.error("ZKN001",
String.format("Ongeldige status-transitie van '%s' naar '%s' voor zaaktype '%s'",
huidigeStatus, nieuweStatus, zaaktype.getOmschrijving()));
}
// Tijdstippen valideren
if (isEindStatus(nieuweStatus) && !allRequiredDocumentsPresent(zaak)) {
return ValidationResult.error("ZKN002",
"Cannot close zaak: required documents missing");
}
return ValidationResult.success();
}
}
Configureerbare bedrijfsregels
Rule-engine integratie
@Component
public class ConfigurableRulesEngine {
@Autowired
private RuleRepository ruleRepository;
public ValidationResult applyRules(String ruleContext, Object data) {
List<BusinessRule> rules = ruleRepository.findByContext(ruleContext);
List<ValidationResult> results = new ArrayList<>();
for (BusinessRule rule : rules) {
if (rule.isActive()) {
ValidationResult result = evaluateRule(rule, data);
results.add(result);
}
}
return ValidationResult.combine(results);
}
private ValidationResult evaluateRule(BusinessRule rule, Object data) {
try {
// Gebruik expression-evaluator (bijv. SpEL)
Boolean ruleResult = expressionEvaluator.evaluate(
rule.getExpression(), data, Boolean.class);
if (!ruleResult) {
return ValidationResult.of(
rule.getSeverity(),
rule.getErrorCode(),
rule.getDescription()
);
}
return ValidationResult.success();
} catch (Exception e) {
log.error("Error evaluating rule {}: {}", rule.getId(), e.getMessage());
return ValidationResult.error("RULE_ENGINE_ERROR",
"Error during rule evaluation: " + e.getMessage());
}
}
}
// Database-entity voor configureerbare regels
@Entity
@Table(name = "business_rules")
public class BusinessRule {
@Id
private String id;
@Column(name = "context")
private String context; // "BRP_PERSON_VALIDATION", "ZKN_ZAAK_CREATION", etc.
@Column(name = "expression")
private String expression; // SpEL expression
@Column(name = "description")
private String description;
@Column(name = "error_code")
private String errorCode;
@Enumerated(EnumType.STRING)
private RuleSeverity severity; // ERROR, WARNING, INFO
private boolean active;
// getters/setters...
}
Rule configuratie voorbeelden
# business-rules.yml
business_rules:
brp_person_validation:
- id: "BRP_BSN_REQUIRED"
expression: "burgerservicenummer != null && burgerservicenummer.length() == 9"
severity: "ERROR"
error_code: "BRP_001"
description: "BSN is verplicht en moet 9 cijfers bevatten"
- id: "BRP_REALISTIC_AGE"
expression: "geboortedatum != null && geboortedatum.isAfter(T(java.time.LocalDate).of(1900, 1, 1))"
severity: "WARNING"
error_code: "BRP_002"
description: "Geboortedatum lijkt onrealistisch"
- id: "BRP_ADULT_MARRIAGE"
expression: "burgerlijkeStaat?.code != 'H' || calculateAge(geboortedatum) >= 18"
severity: "ERROR"
error_code: "BRP_003"
description: "Minderjarigen kunnen niet getrouwd zijn"
zkn_zaak_validation:
- id: "ZKN_VALID_INITIATOR"
expression: "initiator != null && initiator.bsn?.matches('\\\\d{9}')"
severity: "ERROR"
error_code: "ZKN_001"
description: "Zaak-initiator moet geldig persoon zijn"
- id: "ZKN_DEADLINE_FUTURE"
expression: "streefdatum == null || streefdatum.isAfter(T(java.time.LocalDate).now())"
severity: "WARNING"
error_code: "ZKN_002"
description: "Streefdatum zou in de toekomst moeten liggen"
Error-response generatie
GeSystematic error-reporting
<!-- Enhanced Fo01 met business-rule violations -->
<StUF:Fo01Bericht>
<StUF:stuurgegevens>
<StUF:berichtcode>Fo01</StUF:berichtcode>
<StUF:crossRefnummer>ZAAK-REQ-20240305-001</StUF:crossRefnummer>
<!-- ... -->
</StUF:stuurgegevens>
<StUF:body>
<StUF:code>StUF001</StUF:code>
<StUF:plek>business-rules-engine.gemeente.nl</StUF:plek>
<StUF:omschrijving>Business-rule validatie gefaald</StUF:omschrijving>
<!-- Uitgebreide fout-details -->
<StUF:details>
<StUF:validationErrors>
<!-- BSN-validatie fout -->
<StUF:validationError>
<StUF:severity>ERROR</StUF:severity>
<StUF:code>BRP_001</StUF:code>
<StUF:field>BG:burgerservicenummer</StUF:field>
<StUF:value>12345678X</StUF:value>
<StUF:description>BSN voldoet niet aan elfproef-controle</StUF:description>
<StUF:helpUrl>https://api.gemeente.nl/help/bsn-validatie</StUF:helpUrl>
</StUF:validationError>
<!-- Datum-inconsistentie -->
<StUF:validationError>
<StUF:severity>WARNING</StUF:severity>
<StUF:code>BRP_002</StUF:code>
<StUF:field>BG:geboortedatum</StUF:field>
<StUF:value>18500315</StUF:value>
<StUF:description>Geboortedatum lijkt onrealistisch (voor 1900)</StUF:description>
<StUF:suggestion>Controleer datum-invoer</StUF:suggestion>
</StUF:validationError>
<!-- Autorisatie-probleem -->
<StUF:validationError>
<StUF:severity>ERROR</StUF:severity>
<StUF:code>AUTH_003</StUF:code>
<StUF:field>BG:burgerlijkeStaat</StUF:field>
<StUF:description>Geen autorisatie voor privacygevoelige gegevens</StUF:description>
<StUF:requiredPermission>PRIVACY_LEVEL_3</StUF:requiredPermission>
<StUF:contactInfo>privacy-officer@gemeente.nl</StUF:contactInfo>
</StUF:validationError>
</StUF:validationErrors>
</StUF:details>
</StUF:body>
</StUF:Fo01Bericht>
Het definiëren van bedrijfsregels en validaties is essentieel voor het waarborgen van data-kwaliteit en consistentie in StUF-communicatie. Door systematische implementatie van regels kunnen organisaties de betrouwbaarheid van gegevensuitwisseling significant verbeteren.
Resources: