Back to ER Diagram
Module Configurability

Module Configurability Engine

Tenant-level configuration of modules, process stages, feature toggles, and workflow templates without code changes. Enable each organization to tailor the platform to their unique procurement and engineering workflows.

PostgreSQL
5 Tables | 65 Feature Flags
Schema: config
Multi-Tenant

Configuration Architecture Overview

The configurability engine allows each tenant (company) to customize the platform behavior without code changes. This includes enabling/disabling entire modules, toggling individual features within modules, defining process stage ordering (e.g., TDS before or after PO), and creating custom workflow templates for procurement paths.

Tenant Setup
Company Onboarding
Module Activation
Enable/Disable Modules
Feature Flags
Granular Toggles
Process Templates
Workflow Definitions
Stage Ordering
Sequence Configuration
Per-Tenant Overrides
Custom Stage Modifications

What Can Be Configured

  • Enable/disable entire modules (ProKure, Engineering, CRM, etc.)
  • Toggle individual features within each module
  • Define process stage ordering per procurement path
  • Create and assign workflow templates
  • Override template stages for specific tenants

Design Principles

  • Zero code changes for tenant customization
  • JSONB-based flexible configuration parameters
  • Sensible defaults for all settings
  • Cached configuration with 30-minute TTL
  • Full audit trail on every configuration change

Database Tables

config.module_configurations

Module-level feature toggles per tenant. Controls which major platform modules are available to each company.

  • company_id : FK → companies.id — Tenant identifier
  • module_code : VARCHAR, IDX — PROKURE | ENGINEERING | PROJECT | CRM | ANALYTICS | VENDOR_PORTAL
  • module_name : VARCHAR — Human-readable module name
  • is_enabled : BOOLEAN — Whether the module is active for this tenant
  • license_type : VARCHAR — BASIC | STANDARD | PREMIUM
  • max_users : INTEGER — Maximum concurrent users for this module
  • config_params : JSONB — Module-specific settings (thresholds, limits, defaults)
  • activated_at : TIMESTAMPTZ — When the module was first enabled
  • updated_by : FK → users.id — Last admin who modified the config

config.process_stage_configs

Configurable process stage ordering per tenant. Defines the sequence and rules for each stage in a business process.

  • company_id : FK → companies.id — Tenant identifier
  • process_code : VARCHAR, IDX — PR_TO_PO | ENGINEERING_CYCLE | VENDOR_ONBOARDING | GRN_INSPECTION
  • stage_code : VARCHAR — e.g., TDS_REVIEW, DRAWING_APPROVAL, QUALITY_CHECK
  • stage_name : VARCHAR — Human-readable stage name
  • stage_order : INTEGER — Execution sequence number
  • is_mandatory : BOOLEAN — Whether stage can be skipped
  • is_skippable : BOOLEAN — Whether stage supports conditional skipping
  • skip_condition : JSONB — Conditions under which stage is auto-skipped
  • predecessor_stage : VARCHAR — Stage that must complete before this one

config.feature_flags

Granular feature toggles within modules. Controls individual features and their parameters per tenant.

  • company_id : FK → companies.id — Tenant identifier
  • module_code : VARCHAR — Parent module (PROKURE, ENGINEERING, etc.)
  • feature_code : VARCHAR, IDX — REVERSE_AUCTION | OFFLINE_NEGOTIATION | MSME_COMPLIANCE | MULTI_PR_RFQ | DIRECT_PO | THREE_WAY_MATCH
  • feature_name : VARCHAR — Human-readable feature name
  • is_enabled : BOOLEAN — Whether the feature is active
  • config_value : JSONB — Feature-specific configuration parameters
  • effective_from : TIMESTAMPTZ — When the flag takes effect
  • effective_until : TIMESTAMPTZ — Optional expiry (null = permanent)

config.process_templates

Reusable workflow templates. Defines standard procurement paths with ordered stage definitions and default approvers.

  • company_id : FK → companies.id — Tenant identifier
  • template_code : VARCHAR, IDX — STANDARD_PROCUREMENT | DIRECT_PO | SPOT_PURCHASE | EMERGENCY_PO | CONTRACT_RELEASE
  • template_name : VARCHAR — Human-readable template name
  • process_type : VARCHAR — Process category (PROCUREMENT, ENGINEERING, VENDOR)
  • stages : JSONB — Ordered array of stage definitions with codes, names, rules
  • default_approvers : JSONB — Role-based approver mappings per stage
  • is_default : BOOLEAN — Whether this is the default template for the process type
  • is_active : BOOLEAN — Whether this template is currently available

config.tenant_process_overrides

Per-tenant customizations applied on top of process templates. Allows stage reordering, skipping, additions, and approval modifications.

  • company_id : FK → companies.id — Tenant identifier
  • template_id : FK → process_templates.id — Base template being overridden
  • override_type : VARCHAR — STAGE_ORDER | SKIP_STAGE | ADD_STAGE | MODIFY_APPROVAL
  • stage_code : VARCHAR — Target stage for the override
  • override_params : JSONB — Override-specific parameters (new_order, conditions, approvers)
  • reason : TEXT — Business justification for the override
  • updated_by : FK → users.id — Admin who created the override

Configuration Use Cases

1

Enable/Disable Modules

Admin activates modules per license tier. Each company gets access to only the modules included in their subscription.

-- Activate ProKure module for company with PREMIUM license
INSERT INTO config.module_configurations
    (company_id, module_code, module_name, is_enabled, license_type, max_users, config_params, activated_at, updated_by)
VALUES
    (101, 'PROKURE', 'Procurement Management', true, 'PREMIUM', 50,
     '{"default_currency": "INR", "auto_numbering": true, "pr_prefix": "PR"}'::jsonb,
     NOW(), 1);

-- Activate Engineering module
INSERT INTO config.module_configurations
    (company_id, module_code, module_name, is_enabled, license_type, max_users, config_params, activated_at, updated_by)
VALUES
    (101, 'ENGINEERING', 'Engineering Management', true, 'STANDARD', 20,
     '{"drawing_formats": ["DWG", "PDF"], "max_revisions": 10}'::jsonb,
     NOW(), 1);
2

Configure Process Stages

Define stage ordering for the procurement cycle. Different tenants can have different stage sequences. For example, Client A wants TDS review after PO creation, while Client B wants TDS review before RFQ.

-- Client A: TDS review AFTER PO (stage_order = 11)
INSERT INTO config.process_stage_configs
    (company_id, process_code, stage_code, stage_name, stage_order, is_mandatory, is_skippable)
VALUES
    (101, 'PR_TO_PO', 'TDS_REVIEW', 'Technical Data Sheet Review', 11, false, true);

-- Client B: TDS review BEFORE RFQ (stage_order = 3)
INSERT INTO config.process_stage_configs
    (company_id, process_code, stage_code, stage_name, stage_order, is_mandatory, is_skippable)
VALUES
    (202, 'PR_TO_PO', 'TDS_REVIEW', 'Technical Data Sheet Review', 3, true, false);
3

Toggle Features

Enable or disable specific features like reverse auction, offline negotiation, or MSME compliance within a module.

-- Enable Reverse Auction with custom parameters
INSERT INTO config.feature_flags
    (company_id, module_code, feature_code, feature_name, is_enabled, config_value, effective_from)
VALUES
    (101, 'PROKURE', 'REVERSE_AUCTION', 'Reverse Auction', true,
     '{"min_vendors": 3, "auto_extend_mins": 5, "max_extensions": 10}'::jsonb,
     NOW());

-- Disable Spot Purchase for this tenant
INSERT INTO config.feature_flags
    (company_id, module_code, feature_code, feature_name, is_enabled, config_value, effective_from)
VALUES
    (101, 'PROKURE', 'SPOT_PURCHASE', 'Spot Purchase', false,
     '{"max_amount": 100000}'::jsonb,
     NOW());
4

Define Procurement Templates

Create templates for different PO paths: Standard (PR → RFQ → PO), Direct (PR → PO), and Spot (PO only).

-- Standard Procurement Template: PR → RFQ → Bid Eval → Negotiation → PO
INSERT INTO config.process_templates
    (company_id, template_code, template_name, process_type, stages, default_approvers, is_default, is_active)
VALUES
    (101, 'STANDARD_PROCUREMENT', 'Standard Procurement', 'PROCUREMENT',
     '[
        {"stage_code": "PR_CREATION", "stage_name": "Purchase Requisition", "order": 1, "mandatory": true},
        {"stage_code": "PR_APPROVAL", "stage_name": "PR Approval", "order": 2, "mandatory": true},
        {"stage_code": "RFQ_CREATION", "stage_name": "RFQ Creation", "order": 3, "mandatory": true},
        {"stage_code": "BID_EVALUATION", "stage_name": "Bid Evaluation", "order": 4, "mandatory": true},
        {"stage_code": "NEGOTIATION", "stage_name": "Negotiation", "order": 5, "mandatory": false},
        {"stage_code": "PO_CREATION", "stage_name": "PO Creation", "order": 6, "mandatory": true},
        {"stage_code": "PO_APPROVAL", "stage_name": "PO Approval", "order": 7, "mandatory": true}
     ]'::jsonb,
     '{"PR_APPROVAL": ["DEPT_HEAD"], "PO_APPROVAL": ["PURCHASE_MANAGER", "CFO"]}'::jsonb,
     true, true);

-- Direct PO Template: PR → PO (skip RFQ)
INSERT INTO config.process_templates
    (company_id, template_code, template_name, process_type, stages, default_approvers, is_default, is_active)
VALUES
    (101, 'DIRECT_PO', 'Direct Purchase Order', 'PROCUREMENT',
     '[
        {"stage_code": "PR_CREATION", "stage_name": "Purchase Requisition", "order": 1, "mandatory": true},
        {"stage_code": "PR_APPROVAL", "stage_name": "PR Approval", "order": 2, "mandatory": true},
        {"stage_code": "PO_CREATION", "stage_name": "PO Creation", "order": 3, "mandatory": true},
        {"stage_code": "PO_APPROVAL", "stage_name": "PO Approval", "order": 4, "mandatory": true}
     ]'::jsonb,
     '{"PR_APPROVAL": ["DEPT_HEAD"], "PO_APPROVAL": ["PURCHASE_MANAGER"]}'::jsonb,
     false, true);
5

Per-Tenant Overrides

Override template stages for specific tenant needs without modifying the base template.

-- Override: Move TDS Review to after PO for tenant 101
INSERT INTO config.tenant_process_overrides
    (company_id, template_id, override_type, stage_code, override_params, reason, updated_by)
VALUES
    (101, 1, 'STAGE_ORDER', 'TDS_REVIEW',
     '{"new_order": 9, "original_order": 5}'::jsonb,
     'Client requires TDS to be submitted after PO creation per internal policy', 1);

-- Override: Skip Stock Check for Services category
INSERT INTO config.tenant_process_overrides
    (company_id, template_id, override_type, stage_code, override_params, reason, updated_by)
VALUES
    (101, 1, 'SKIP_STAGE', 'STOCK_CHECK',
     '{"condition": "category = Services", "skip_reason": "Not applicable for services"}'::jsonb,
     'Stock check is irrelevant for service procurements', 1);
6

Runtime Configuration Check

The application checks configuration before executing any workflow step. The ConfigurationService provides cached lookups for module status, feature flags, and stage ordering.

// Runtime check before executing a workflow step
var isModuleActive = await _configService.IsModuleEnabled(companyId, "PROKURE");
if (!isModuleActive)
    throw new ModuleDisabledException("Procurement module is not enabled");

var canUseReverseAuction = await _configService.IsFeatureEnabled(companyId, "PROKURE", "REVERSE_AUCTION");
var stages = await _configService.GetOrderedStages(companyId, "PR_TO_PO");

Configurable Process Stages

The following table shows the default stage configurations for each process. Stages marked as skippable can be conditionally bypassed. The "Example Override" column demonstrates common tenant customizations.

Process Stage Code Stage Name Default Order Skippable Example Override
PR_TO_PO PR_CREATION Purchase Requisition 1 No
PR_TO_PO PR_APPROVAL PR Approval 2 No
PR_TO_PO STOCK_CHECK Stock Availability 3 Yes Skip if category=Services
PR_TO_PO CONTRACT_CHECK Rate Contract Check 4 Yes
PR_TO_PO TDS_REVIEW Technical Data Sheet 5 Yes Move to after PO (order=9)
PR_TO_PO RFQ_CREATION RFQ Creation 6 Yes Skip for Direct PO
PR_TO_PO BID_EVALUATION Bid Evaluation 7 Yes Skip for Direct PO
PR_TO_PO NEGOTIATION Negotiation 8 Yes
PR_TO_PO PO_CREATION PO Creation 9 No
PR_TO_PO PO_APPROVAL PO Approval 10 No
ENGINEERING_CYCLE DRAWING_CREATION Drawing Creation 1 No
ENGINEERING_CYCLE TDS_GENERATION TDS Generation 2 No Move before drawing
ENGINEERING_CYCLE DRAWING_APPROVAL Drawing Approval 3 No
ENGINEERING_CYCLE VENDOR_SUBMISSION Vendor Drawing Submission 4 Yes

Feature Flags Reference

Complete reference of all 65 configurable features across 12 categories. Each feature can be independently enabled or disabled per tenant with custom configuration parameters stored as JSONB. Based on comprehensive audit of all logic documentation and market research across SAP Ariba, Coupa, JAGGAER, Ivalua, and GEP SMART.

A. Procurement Core (7 flags)

ModuleFeature CodeFeature NameDefaultConfig Value (JSONB)
PROKUREREVERSE_AUCTIONReverse AuctionEnabled{"min_vendors": 3, "auto_extend_mins": 5}
PROKUREOFFLINE_NEGOTIATIONOffline NegotiationEnabled{"max_rounds": 3, "escalation_days": 5}
PROKUREMULTI_PR_RFQMulti-PR ConsolidationEnabled{"max_prs_per_rfq": 10}
PROKUREDIRECT_PODirect PO (skip RFQ)Enabled{"requires_approval": true, "max_amount": 500000}
PROKURESPOT_PURCHASESpot Purchase (no PR)Disabled{"max_amount": 100000}
PROKURETHREE_WAY_MATCHThree-Way MatchEnabled{"tolerance_percent": 5}
PROKUREMSME_COMPLIANCEMSME ComplianceEnabled{"payment_sla_days": 45, "interest_multiplier": 3}

B. Approval & Workflow Engine (7 flags)

ModuleFeature CodeFeature NameDefaultConfig Value (JSONB)
PROKUREAPPROVAL_DELEGATIONApproval DelegationEnabled{"allow_delegation": true, "max_delegation_days": 30}
PROKUREAPPROVAL_SKIP_LEVELSkip-Level ApprovalDisabled{"allow_skip": false, "min_authority_level": 3}
PROKUREAUTO_APPROVALAuto-Approval on SLA BreachDisabled{"enabled": false, "max_amount": 50000, "sla_hours_before_auto": 48}
PROKUREAPPROVAL_SLAApproval SLA EnforcementEnabled{"pr_sla_hours": 24, "po_sla_hours": 48, "rfq_sla_hours": 24, "escalation_after_hours": 72}
PROKUREBUDGET_ENFORCEMENTBudget EnforcementEnabled{"alert_threshold_pct": 80, "block_on_exceed": true, "carry_forward": false}
PROKUREREJECTION_CYCLE_LIMITSRejection Cycle LimitsEnabled{"pr_max_cycles": 3, "po_max_cycles": 2, "escalation_on_max": true}
PROKUREPARALLEL_APPROVALParallel ApprovalDisabled{"enabled": false, "min_approvers": 2, "consensus_type": "ALL"}

C. Vendor Management (6 flags)

ModuleFeature CodeFeature NameDefaultConfig Value (JSONB)
VENDORSELF_REGISTRATIONVendor Self RegistrationEnabled{"auto_approve": false}
VENDORVENDOR_ONBOARDING_LEVELSOnboarding Approval LevelsEnabled{"approval_levels": 3, "level_roles": ["PROCUREMENT_OFFICER", "FINANCE", "ADMIN"]}
VENDORVENDOR_DOCUMENT_REQUIREMENTSDocument RequirementsEnabled{"mandatory": ["GST", "PAN", "INCORPORATION", "BANK"], "conditional": ["MSME", "ISO", "NDA"]}
VENDORVENDOR_ONBOARDING_SLAOnboarding SLAEnabled{"approval_sla_days": 5, "doc_expiry_alert_days": 30}
VENDORVENDOR_CODE_FORMATVendor Code FormatEnabled{"pattern": "VND-{CAT}-{SEQ}", "padding_length": 5}
VENDORVENDOR_BANK_VERIFICATIONBank Verification MethodEnabled{"method": "PENNY_DROP", "auto_verify": true}

D. Vendor Rating & Scoring (5 flags)

ModuleFeature CodeFeature NameDefaultConfig Value (JSONB)
VENDORVENDOR_RATING_WEIGHTSRating Category WeightsEnabled{"delivery": 25, "quality": 30, "price": 20, "compliance": 15, "responsiveness": 10}
VENDORVENDOR_RATING_THRESHOLDSTier ClassificationEnabled{"excellent": 90, "good": 75, "average": 60, "poor": 40}
VENDORVENDOR_TIER_REVIEW_FREQUENCYTier Review ScheduleEnabled{"platinum_days": 365, "gold_days": 180, "silver_days": 90, "bronze_days": 30}
VENDORVENDOR_RATING_ACTIONSAutomated Rating ActionsEnabled{"suspend_below": 25, "restrict_below": 40, "blacklist_consecutive": 3, "grace_period_days": 90}
VENDORVENDOR_RATING_CATEGORY_WEIGHTSCategory-Specific WeightsEnabled{"raw_materials": {"quality": 40, "delivery": 30}, "services": {"responsiveness": 25}}

E. RFQ & Bid Evaluation (5 flags)

ModuleFeature CodeFeature NameDefaultConfig Value (JSONB)
PROKUREBID_EVALUATION_WEIGHTSTechnical vs. Commercial WeightEnabled{"technical_pct": 40, "commercial_pct": 60}
PROKUREBID_SCORING_CRITERIACustom Scoring CriteriaEnabled{"criteria": [{"name": "Price", "weight": 30}, {"name": "Quality", "weight": 25}]}
PROKURESEALED_BIDSealed Bid ModeEnabled{"enabled": true, "reveal_after_deadline": true}
PROKUREEMD_MANAGEMENTEMD / Bid SecurityEnabled{"required": true, "exemption_for_msme": true, "amount_type": "PERCENTAGE", "amount": 2}
PROKURERFQ_MINIMUM_VENDORSMinimum Vendors per RFQEnabled{"min_vendors": 3, "allow_single_source": true, "single_source_approval": true}

F. Reverse Auction Extended (3 flags)

ModuleFeature CodeFeature NameDefaultConfig Value (JSONB)
PROKUREAUCTION_EXTENSION_RULESAuction Extension RulesEnabled{"extension_window_mins": 5, "extension_duration_mins": 3, "max_extensions": 10}
PROKUREAUCTION_BID_RULESAuction Bid RulesEnabled{"min_decrement_type": "PCT", "min_decrement_value": 1, "anonymize_vendors": true}
PROKUREAUCTION_RESERVE_PRICEReserve PriceDisabled{"enabled": false, "action_on_not_met": "NEGOTIATE"}

G. Three-Way Match Extended (1 flag)

ModuleFeature CodeFeature NameDefaultConfig Value (JSONB)
PROKURETHREE_WAY_MATCH_DETAILDetailed Match TolerancesEnabled{"qty_tolerance_pct": 0, "price_tolerance_pct": 2, "tax_tolerance_pct": 1, "auto_approve_on_match": true, "require_grn": true}

H. Payment & Finance (5 flags)

ModuleFeature CodeFeature NameDefaultConfig Value (JSONB)
PROKUREPAYMENT_METHODSAllowed Payment MethodsEnabled{"enabled_methods": ["CHECK", "WIRE", "ACH", "CREDIT_CARD", "CASH"]}
PROKUREEARLY_PAYMENT_DISCOUNTEarly Payment DiscountDisabled{"enabled": false, "default_terms": "2/10 Net 30", "auto_calculate": true}
PROKUREPAYMENT_REMINDERPayment Due RemindersEnabled{"days_before_due": 3, "escalation_on_overdue_days": 7}
PROKURECREDIT_NOTE_REASONSCredit Note ReasonsEnabled{"allowed_reasons": ["RETURN", "PRICE_ADJUSTMENT", "QUALITY_ISSUE", "SHORT_SHIPMENT"]}
PROKUREMSME_ALERT_SCHEDULEMSME Payment AlertsEnabled{"reminder_day": 30, "escalation_day": 40, "breach_day": 45}

I. GRN & Inventory (5 flags)

ModuleFeature CodeFeature NameDefaultConfig Value (JSONB)
PROKUREGRN_INSPECTIONGRN Quality InspectionEnabled{"mandatory_for_tracked": true, "quarantine_hold": true, "inspection_sla_hours": 24}
PROKUREINVENTORY_REORDERAuto Reorder / PR GenerationEnabled{"auto_pr_on_reorder": true, "reorder_method": "FIXED_QTY", "alert_on_critical": true}
PROKUREINVENTORY_EXPIRYExpiry ManagementEnabled{"alert_days_before": 30, "fifo_enabled": true, "fefo_enabled": true}
PROKURESTOCK_VALUATIONStock Valuation MethodEnabled{"method": "WEIGHTED_AVG", "auto_recalculate": true}
PROKUREBARCODE_SCANNINGBarcode / QR ScanningDisabled{"enabled": false, "format": "QR", "auto_grn": false}

J. Document & Numbering (3 flags)

ModuleFeature CodeFeature NameDefaultConfig Value (JSONB)
CONFIGDOCUMENT_NUMBERINGDocument Number FormatEnabled{"formats": {"PR": "PR-YYYY-NNNNN", "PO": "PO-YYYY-NNNNN"}, "reset": "YEARLY", "padding": 5}
CONFIGDOCUMENT_CHAIN_ENFORCEMENTDocument Chain EnforcementEnabled{"enforce_pr_before_rfq": true, "enforce_rfq_before_po": true, "allow_exceptions": true}
CONFIGE_SIGNATUREElectronic SignatureDisabled{"enabled": false, "provider": "INTERNAL", "required_for": ["PO", "CONTRACT"]}

K. Audit & Compliance (3 flags)

ModuleFeature CodeFeature NameDefaultConfig Value (JSONB)
CONFIGAUDIT_RETENTIONAudit Log RetentionEnabled{"financial_years": 7, "general_years": 3, "login_years": 1, "session_days": 90}
CONFIGINTEGRATION_LOG_RETENTIONIntegration Log TTLEnabled{"ttl_days": 90, "max_payload_mb": 1, "redact_sensitive": true}
CONFIGDATA_REDACTIONSensitive Data RedactionEnabled{"fields": ["password_hash", "bank_account", "gstin", "pan", "api_keys"]}

L. Notification Configuration (3 flags)

ModuleFeature CodeFeature NameDefaultConfig Value (JSONB)
CONFIGNOTIFICATION_CHANNELSNotification ChannelsEnabled{"email": true, "sms": true, "in_app": true, "slack": false, "teams": false}
CONFIGNOTIFICATION_DIGESTNotification DigestDisabled{"enabled": false, "frequency": "DAILY", "send_time": "09:00"}
CONFIGCRITICAL_NOTIFICATIONSNon-Suppressible AlertsEnabled{"codes": ["AUTH_ACCOUNT_LOCKED", "PO_RELEASED", "INV_REJECTED"]}

M. Engineering & Project (3 flags)

ModuleFeature CodeFeature NameDefaultConfig Value (JSONB)
ENGINEERINGVENDOR_DRAWINGSVendor Drawing SubmissionEnabled{"max_revisions": 5}
ENGINEERINGAUTO_BOMAuto BOM GenerationDisabled{}
PROJECTMILESTONE_TRACKINGMilestone TrackingEnabled{"max_levels": 5}

N. Platform-Level (8 flags)

ModuleFeature CodeFeature NameDefaultConfig Value (JSONB)
PLATFORMMULTI_CURRENCYMulti-Currency SupportEnabled{"base_currency": "INR", "auto_conversion": true, "rate_source": "RBI"}
PLATFORMMULTI_LANGUAGEMulti-Language SupportDisabled{"default_locale": "en-IN", "supported": ["en-IN", "hi-IN"]}
PLATFORMTAX_CONFIGURATIONTax System ConfigurationEnabled{"tax_system": "GST", "auto_calculate": true, "reverse_charge": true}
PLATFORMSSO_INTEGRATIONSSO / Identity ProviderDisabled{"enabled": false, "provider": "AZURE_AD", "enforce_sso": false}
PLATFORMAPI_RATE_LIMITINGAPI Rate LimitingEnabled{"requests_per_minute": 60, "burst_limit": 100}
PLATFORMDATA_EXPORTData ExportEnabled{"formats": ["CSV", "XLSX", "PDF"], "scheduled_exports": true}
PLATFORMDASHBOARD_CUSTOMIZATIONDashboard CustomizationEnabled{"max_widgets": 12, "shared_dashboards": true}
PLATFORMAI_RECOMMENDATIONSAI-Powered RecommendationsDisabled{"spend_analysis": false, "vendor_suggestion": false, "price_prediction": false}

.NET Implementation

The ConfigurationService provides cached, tenant-aware lookups for module status, feature flags, feature configuration values, and ordered process stages with overrides applied.

ConfigurationService

public class ConfigurationService : IConfigurationService
{
    private readonly IModuleConfigRepository _moduleConfigRepo;
    private readonly IFeatureFlagRepository _featureFlagRepo;
    private readonly IProcessStageRepository _stageRepo;
    private readonly IMemoryCache _cache;

    public async Task<bool> IsModuleEnabled(int companyId, string moduleCode)
    {
        var cacheKey = $"module:{companyId}:{moduleCode}";
        return await _cache.GetOrCreateAsync(cacheKey, async entry =>
        {
            entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(30);
            var config = await _moduleConfigRepo.GetByCompanyAndModule(companyId, moduleCode);
            return config?.IsEnabled ?? false;
        });
    }

    public async Task<bool> IsFeatureEnabled(int companyId, string moduleCode, string featureCode)
    {
        var cacheKey = $"feature:{companyId}:{moduleCode}:{featureCode}";
        return await _cache.GetOrCreateAsync(cacheKey, async entry =>
        {
            entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(30);
            var flag = await _featureFlagRepo.Get(companyId, moduleCode, featureCode);
            if (flag == null) return false;
            if (flag.EffectiveUntil.HasValue && flag.EffectiveUntil < DateTime.UtcNow) return false;
            return flag.IsEnabled;
        });
    }

    public async Task<T> GetFeatureConfig<T>(int companyId, string moduleCode, string featureCode)
    {
        var flag = await _featureFlagRepo.Get(companyId, moduleCode, featureCode);
        return flag?.ConfigValue != null
            ? JsonSerializer.Deserialize<T>(flag.ConfigValue)
            : default;
    }

    public async Task<List<ProcessStage>> GetOrderedStages(int companyId, string processCode)
    {
        var stages = await _stageRepo.GetByProcess(companyId, processCode);
        var overrides = await _stageRepo.GetOverrides(companyId, processCode);

        foreach (var ovr in overrides)
        {
            var stage = stages.FirstOrDefault(s => s.StageCode == ovr.StageCode);
            if (stage != null && ovr.OverrideType == "STAGE_ORDER")
                stage.StageOrder = ovr.OverrideParams.GetProperty("new_order").GetInt32();
            else if (ovr.OverrideType == "SKIP_STAGE")
                stages.RemoveAll(s => s.StageCode == ovr.StageCode);
        }

        return stages.OrderBy(s => s.StageOrder).ToList();
    }
}

Usage Example: PurchaseOrderService

// In PurchaseOrderService
public async Task<PurchaseOrder> CreatePO(CreatePORequest request)
{
    // Check if Direct PO feature is enabled
    if (request.PoType == "DIRECT" &&
        !await _configService.IsFeatureEnabled(request.CompanyId, "PROKURE", "DIRECT_PO"))
        throw new FeatureDisabledException("Direct PO is not enabled for your organization");

    // Get configured stages for this PO path
    var stages = await _configService.GetOrderedStages(request.CompanyId, "PR_TO_PO");

    // Check if TDS is required before PO
    var tdsStage = stages.FirstOrDefault(s => s.StageCode == "TDS_REVIEW");
    if (tdsStage != null &&
        tdsStage.StageOrder < stages.First(s => s.StageCode == "PO_CREATION").StageOrder)
    {
        if (!await _tdsService.IsTdsCompleted(request.PrId))
            throw new StageIncompleteException("TDS review must be completed before PO creation");
    }

    // Proceed with PO creation...
}

Best Practices

Cache configuration values with appropriate TTL (30 min recommended). Configuration lookups happen on every request; avoid hitting the database each time. Use IMemoryCache or IDistributedCache with a 30-minute expiration for optimal balance between freshness and performance.

Use JSONB for flexible config_value to avoid schema changes for new parameters. As new configuration options are added, JSONB columns allow storing them without database migrations. This keeps the schema stable while supporting unlimited configurability.

Provide sensible defaults -- system should work without any configuration. If no configuration record exists for a tenant, the application should fall back to reasonable defaults. Never require explicit configuration for basic functionality.

Log all configuration changes to audit trail. Every change to module configs, feature flags, stage ordering, or overrides must be recorded with who changed it, when, and what the previous value was. This is critical for compliance and troubleshooting.

Validate stage ordering -- prevent circular dependencies. When tenants reorder stages, validate that no circular predecessor dependencies are created. A stage cannot be its own predecessor, and the ordering graph must remain a DAG (directed acyclic graph).

Test with multiple tenant configurations before deployment. Maintain test tenants with different configuration profiles (minimal, standard, fully customized) and run integration tests against all profiles to ensure configuration changes do not break any tenant's workflow.