Search by

mar-pod-b2b / module-b2b-advanced-approval

Conditional, parallel, delegated, and escalated approval rules for Marpod B2B Advanced.

Maintainers

Package info

gitlab.com/mar-pod-b2b/module-b2b-advanced-approval

Issues

Type:magento2-module

pkg:composer/mar-pod-b2b/module-b2b-advanced-approval

Transparency log

Statistics

Installs: 1

Dependents: 0

Suggesters: 0

Stars: 0

1.1.11 2026-08-24 16:09 UTC

README

Conditional approval rule groups, delegation, and escalation, layered on top of Marpod_B2bPurchaseApproval's frozen workflow contracts.

Status

Full rule engine, delegation, escalation, and Admin UI shipped.

Implemented foundation

  • declarative schema: marpod_b2b_advanced_approval_rule_group (per company, with an AND/OR operator), marpod_b2b_advanced_approval_rule_condition (one or more conditions per group), marpod_b2b_advanced_approval_delegate (date-windowed reassignment from one approver to another), marpod_b2b_advanced_approval_deadline (due-soon tracking);
  • conditions are evaluated through the ConditionMatcherInterface pool: amount, category, and sku are real, live matchers against the purchase request. payment_method and cost_center matcher entries exist but are placeholders — the frozen Marpod_B2bPurchaseApproval\Api\Data\PurchaseRequestInterface has no such fields, so these two conditions never match anything today. They are kept as documented, honest placeholders for a future request-data extension rather than removed or faked; group-level AND/OR combines however many conditions a group has;
  • delegation: ApprovalDelegateRepositoryInterface lets one approver hand off pending approvals to another for a date range; ApprovalStepRepositoryInterface::save() performs the actual reassignment for steps that fall inside an active delegate's window; getListDueSoon() returns steps with a deadline in the next N days — this is only a query, used to build the due-soon reminder view; there is no automatic notification dispatch;
  • a rule simulator lets an admin test a group's conditions against a sample request without creating one, using the same ConditionMatcherInterface pool the live workflow uses;
  • approval remains strictly sequentialMarpod_B2bPurchaseApproval's ApprovalStepInterface has no concept of parallel/any-of steps, and this module does not add one; multiple conditions in a group combine logically (AND/OR), they do not create parallel approval steps.

Admin UI

Marpod_B2bAdvancedApproval::manage provides two screens, both a standard grid + dedicated add/edit form: Approval Rule Groups (marpod_b2b_advanced_approval/group/index, with inline condition add/delete) and Approval Delegates (marpod_b2b_advanced_approval/delegate/index, a sortable/filterable grid of all delegates across every company, with marpod_b2b_advanced_approval/delegate/edit for creating or editing one). Approval Rule Group edit uses persistent General and Conditions tabs with a native Magento action bar. The Conditions grid resolves category/product details and provides filtering, record counts, page-size selection, and pagination; the add form is separated below the grid.

Requirements

  • PHP 8.5
  • Magento 2.4.9-compatible framework packages
  • mar-pod-b2b/module-b2b-core, mar-pod-b2b/module-b2b-purchase-approval

Installation

Run from the Magento project root:

composer require mar-pod-b2b/module-b2b-advanced-approval:^1.1
bin/magento module:enable Marpod_B2bAdvancedApproval
bin/magento setup:upgrade
bin/magento setup:di:compile
bin/magento cache:clean

Development

The planned scope, dependency rules, cache boundaries, security requirements, and test strategy are documented in packages/B2B_LIGHT_PLAN.md in the development Magento project (section 9.5).

Unit tests:

vendor/bin/phpunit -c dev/tests/unit/phpunit.xml.dist packages/module-b2b-advanced-approval/Test/Unit

Admin smoke test:

cd packages/module-b2b-advanced-approval/Test/Playwright
npm install
npm test

REST/GraphQL API tests (real HTTP against a running instance — routing, authentication, ACL enforcement, and cross-company isolation; see mar-pod-b2b/module-b2b-core's README for the shared Test/Api base classes these build on):

vendor/bin/phpunit -c dev/tests/api-functional/phpunit_rest.xml \
    packages/module-b2b-advanced-approval/Test/Api/Rest/AdvancedApprovalRestTest.php

vendor/bin/phpunit -c dev/tests/api-functional/phpunit_graphql.xml \
    packages/module-b2b-advanced-approval/Test/Api/GraphQl/SimulatePurchaseApprovalGraphQlTest.php

Integration surfaces

Admin REST routes below /rest/V1/marpod-b2b/advanced-approval require the existing Marpod_B2bAdvancedApproval::manage ACL resource and cover groups, conditions, and delegates — the same CRUD surface the Admin UI already exposes. Deadlines, escalation, and the simulator have no Admin UI either, so they stay unexposed via REST too.

Authenticated customers can query marpodB2bSimulatePurchaseApproval(totalAmount, skus, categoryIds) through GraphQL — a read-only preview of how many approval steps a hypothetical purchase would require for their own company (company ID is always derived from the customer token, never accepted as an argument). Nothing else in this module is customer-facing: ApprovalRuleGroupRepositoryInterface/ApprovalRuleConditionRepositoryInterface/ ApprovalDelegateRepositoryInterface all perform structural validation only, never caller-identity or company-scoping — safe behind the ACL-gated Admin REST above, but never safe to expose directly to a customer.

Limitations

  • payment_method and cost_center conditions are UI-visible placeholders that never match — see above;
  • no parallel ("any N of M") approval;
  • due-soon reminders are a query only, with no email/notification dispatch.
  • the rule simulator has a customer-facing GraphQL query (see Integration surfaces) but no Admin form yet.

Uninstallation

Do not remove the package while another installed module requires it. The marpod_b2b_advanced_approval_* tables contain business data and are retained when the module is disabled or its package is removed; the base Marpod_B2bPurchaseApproval workflow continues to function with its own, unconditioned amount-threshold rules once this module is removed.