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.
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.
Module-level feature toggles per tenant. Controls which major platform modules are available to each company.
Configurable process stage ordering per tenant. Defines the sequence and rules for each stage in a business process.
Granular feature toggles within modules. Controls individual features and their parameters per tenant.
Reusable workflow templates. Defines standard procurement paths with ordered stage definitions and default approvers.
Per-tenant customizations applied on top of process templates. Allows stage reordering, skipping, additions, and approval modifications.
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);
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);
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());
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);
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);
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");
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 | — |
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.
| Module | Feature Code | Feature Name | Default | Config Value (JSONB) |
|---|---|---|---|---|
| PROKURE | REVERSE_AUCTION | Reverse Auction | Enabled | {"min_vendors": 3, "auto_extend_mins": 5} |
| PROKURE | OFFLINE_NEGOTIATION | Offline Negotiation | Enabled | {"max_rounds": 3, "escalation_days": 5} |
| PROKURE | MULTI_PR_RFQ | Multi-PR Consolidation | Enabled | {"max_prs_per_rfq": 10} |
| PROKURE | DIRECT_PO | Direct PO (skip RFQ) | Enabled | {"requires_approval": true, "max_amount": 500000} |
| PROKURE | SPOT_PURCHASE | Spot Purchase (no PR) | Disabled | {"max_amount": 100000} |
| PROKURE | THREE_WAY_MATCH | Three-Way Match | Enabled | {"tolerance_percent": 5} |
| PROKURE | MSME_COMPLIANCE | MSME Compliance | Enabled | {"payment_sla_days": 45, "interest_multiplier": 3} |
| Module | Feature Code | Feature Name | Default | Config Value (JSONB) |
|---|---|---|---|---|
| PROKURE | APPROVAL_DELEGATION | Approval Delegation | Enabled | {"allow_delegation": true, "max_delegation_days": 30} |
| PROKURE | APPROVAL_SKIP_LEVEL | Skip-Level Approval | Disabled | {"allow_skip": false, "min_authority_level": 3} |
| PROKURE | AUTO_APPROVAL | Auto-Approval on SLA Breach | Disabled | {"enabled": false, "max_amount": 50000, "sla_hours_before_auto": 48} |
| PROKURE | APPROVAL_SLA | Approval SLA Enforcement | Enabled | {"pr_sla_hours": 24, "po_sla_hours": 48, "rfq_sla_hours": 24, "escalation_after_hours": 72} |
| PROKURE | BUDGET_ENFORCEMENT | Budget Enforcement | Enabled | {"alert_threshold_pct": 80, "block_on_exceed": true, "carry_forward": false} |
| PROKURE | REJECTION_CYCLE_LIMITS | Rejection Cycle Limits | Enabled | {"pr_max_cycles": 3, "po_max_cycles": 2, "escalation_on_max": true} |
| PROKURE | PARALLEL_APPROVAL | Parallel Approval | Disabled | {"enabled": false, "min_approvers": 2, "consensus_type": "ALL"} |
| Module | Feature Code | Feature Name | Default | Config Value (JSONB) |
|---|---|---|---|---|
| VENDOR | SELF_REGISTRATION | Vendor Self Registration | Enabled | {"auto_approve": false} |
| VENDOR | VENDOR_ONBOARDING_LEVELS | Onboarding Approval Levels | Enabled | {"approval_levels": 3, "level_roles": ["PROCUREMENT_OFFICER", "FINANCE", "ADMIN"]} |
| VENDOR | VENDOR_DOCUMENT_REQUIREMENTS | Document Requirements | Enabled | {"mandatory": ["GST", "PAN", "INCORPORATION", "BANK"], "conditional": ["MSME", "ISO", "NDA"]} |
| VENDOR | VENDOR_ONBOARDING_SLA | Onboarding SLA | Enabled | {"approval_sla_days": 5, "doc_expiry_alert_days": 30} |
| VENDOR | VENDOR_CODE_FORMAT | Vendor Code Format | Enabled | {"pattern": "VND-{CAT}-{SEQ}", "padding_length": 5} |
| VENDOR | VENDOR_BANK_VERIFICATION | Bank Verification Method | Enabled | {"method": "PENNY_DROP", "auto_verify": true} |
| Module | Feature Code | Feature Name | Default | Config Value (JSONB) |
|---|---|---|---|---|
| VENDOR | VENDOR_RATING_WEIGHTS | Rating Category Weights | Enabled | {"delivery": 25, "quality": 30, "price": 20, "compliance": 15, "responsiveness": 10} |
| VENDOR | VENDOR_RATING_THRESHOLDS | Tier Classification | Enabled | {"excellent": 90, "good": 75, "average": 60, "poor": 40} |
| VENDOR | VENDOR_TIER_REVIEW_FREQUENCY | Tier Review Schedule | Enabled | {"platinum_days": 365, "gold_days": 180, "silver_days": 90, "bronze_days": 30} |
| VENDOR | VENDOR_RATING_ACTIONS | Automated Rating Actions | Enabled | {"suspend_below": 25, "restrict_below": 40, "blacklist_consecutive": 3, "grace_period_days": 90} |
| VENDOR | VENDOR_RATING_CATEGORY_WEIGHTS | Category-Specific Weights | Enabled | {"raw_materials": {"quality": 40, "delivery": 30}, "services": {"responsiveness": 25}} |
| Module | Feature Code | Feature Name | Default | Config Value (JSONB) |
|---|---|---|---|---|
| PROKURE | BID_EVALUATION_WEIGHTS | Technical vs. Commercial Weight | Enabled | {"technical_pct": 40, "commercial_pct": 60} |
| PROKURE | BID_SCORING_CRITERIA | Custom Scoring Criteria | Enabled | {"criteria": [{"name": "Price", "weight": 30}, {"name": "Quality", "weight": 25}]} |
| PROKURE | SEALED_BID | Sealed Bid Mode | Enabled | {"enabled": true, "reveal_after_deadline": true} |
| PROKURE | EMD_MANAGEMENT | EMD / Bid Security | Enabled | {"required": true, "exemption_for_msme": true, "amount_type": "PERCENTAGE", "amount": 2} |
| PROKURE | RFQ_MINIMUM_VENDORS | Minimum Vendors per RFQ | Enabled | {"min_vendors": 3, "allow_single_source": true, "single_source_approval": true} |
| Module | Feature Code | Feature Name | Default | Config Value (JSONB) |
|---|---|---|---|---|
| PROKURE | AUCTION_EXTENSION_RULES | Auction Extension Rules | Enabled | {"extension_window_mins": 5, "extension_duration_mins": 3, "max_extensions": 10} |
| PROKURE | AUCTION_BID_RULES | Auction Bid Rules | Enabled | {"min_decrement_type": "PCT", "min_decrement_value": 1, "anonymize_vendors": true} |
| PROKURE | AUCTION_RESERVE_PRICE | Reserve Price | Disabled | {"enabled": false, "action_on_not_met": "NEGOTIATE"} |
| Module | Feature Code | Feature Name | Default | Config Value (JSONB) |
|---|---|---|---|---|
| PROKURE | THREE_WAY_MATCH_DETAIL | Detailed Match Tolerances | Enabled | {"qty_tolerance_pct": 0, "price_tolerance_pct": 2, "tax_tolerance_pct": 1, "auto_approve_on_match": true, "require_grn": true} |
| Module | Feature Code | Feature Name | Default | Config Value (JSONB) |
|---|---|---|---|---|
| PROKURE | PAYMENT_METHODS | Allowed Payment Methods | Enabled | {"enabled_methods": ["CHECK", "WIRE", "ACH", "CREDIT_CARD", "CASH"]} |
| PROKURE | EARLY_PAYMENT_DISCOUNT | Early Payment Discount | Disabled | {"enabled": false, "default_terms": "2/10 Net 30", "auto_calculate": true} |
| PROKURE | PAYMENT_REMINDER | Payment Due Reminders | Enabled | {"days_before_due": 3, "escalation_on_overdue_days": 7} |
| PROKURE | CREDIT_NOTE_REASONS | Credit Note Reasons | Enabled | {"allowed_reasons": ["RETURN", "PRICE_ADJUSTMENT", "QUALITY_ISSUE", "SHORT_SHIPMENT"]} |
| PROKURE | MSME_ALERT_SCHEDULE | MSME Payment Alerts | Enabled | {"reminder_day": 30, "escalation_day": 40, "breach_day": 45} |
| Module | Feature Code | Feature Name | Default | Config Value (JSONB) |
|---|---|---|---|---|
| PROKURE | GRN_INSPECTION | GRN Quality Inspection | Enabled | {"mandatory_for_tracked": true, "quarantine_hold": true, "inspection_sla_hours": 24} |
| PROKURE | INVENTORY_REORDER | Auto Reorder / PR Generation | Enabled | {"auto_pr_on_reorder": true, "reorder_method": "FIXED_QTY", "alert_on_critical": true} |
| PROKURE | INVENTORY_EXPIRY | Expiry Management | Enabled | {"alert_days_before": 30, "fifo_enabled": true, "fefo_enabled": true} |
| PROKURE | STOCK_VALUATION | Stock Valuation Method | Enabled | {"method": "WEIGHTED_AVG", "auto_recalculate": true} |
| PROKURE | BARCODE_SCANNING | Barcode / QR Scanning | Disabled | {"enabled": false, "format": "QR", "auto_grn": false} |
| Module | Feature Code | Feature Name | Default | Config Value (JSONB) |
|---|---|---|---|---|
| CONFIG | DOCUMENT_NUMBERING | Document Number Format | Enabled | {"formats": {"PR": "PR-YYYY-NNNNN", "PO": "PO-YYYY-NNNNN"}, "reset": "YEARLY", "padding": 5} |
| CONFIG | DOCUMENT_CHAIN_ENFORCEMENT | Document Chain Enforcement | Enabled | {"enforce_pr_before_rfq": true, "enforce_rfq_before_po": true, "allow_exceptions": true} |
| CONFIG | E_SIGNATURE | Electronic Signature | Disabled | {"enabled": false, "provider": "INTERNAL", "required_for": ["PO", "CONTRACT"]} |
| Module | Feature Code | Feature Name | Default | Config Value (JSONB) |
|---|---|---|---|---|
| CONFIG | AUDIT_RETENTION | Audit Log Retention | Enabled | {"financial_years": 7, "general_years": 3, "login_years": 1, "session_days": 90} |
| CONFIG | INTEGRATION_LOG_RETENTION | Integration Log TTL | Enabled | {"ttl_days": 90, "max_payload_mb": 1, "redact_sensitive": true} |
| CONFIG | DATA_REDACTION | Sensitive Data Redaction | Enabled | {"fields": ["password_hash", "bank_account", "gstin", "pan", "api_keys"]} |
| Module | Feature Code | Feature Name | Default | Config Value (JSONB) |
|---|---|---|---|---|
| CONFIG | NOTIFICATION_CHANNELS | Notification Channels | Enabled | {"email": true, "sms": true, "in_app": true, "slack": false, "teams": false} |
| CONFIG | NOTIFICATION_DIGEST | Notification Digest | Disabled | {"enabled": false, "frequency": "DAILY", "send_time": "09:00"} |
| CONFIG | CRITICAL_NOTIFICATIONS | Non-Suppressible Alerts | Enabled | {"codes": ["AUTH_ACCOUNT_LOCKED", "PO_RELEASED", "INV_REJECTED"]} |
| Module | Feature Code | Feature Name | Default | Config Value (JSONB) |
|---|---|---|---|---|
| ENGINEERING | VENDOR_DRAWINGS | Vendor Drawing Submission | Enabled | {"max_revisions": 5} |
| ENGINEERING | AUTO_BOM | Auto BOM Generation | Disabled | {} |
| PROJECT | MILESTONE_TRACKING | Milestone Tracking | Enabled | {"max_levels": 5} |
| Module | Feature Code | Feature Name | Default | Config Value (JSONB) |
|---|---|---|---|---|
| PLATFORM | MULTI_CURRENCY | Multi-Currency Support | Enabled | {"base_currency": "INR", "auto_conversion": true, "rate_source": "RBI"} |
| PLATFORM | MULTI_LANGUAGE | Multi-Language Support | Disabled | {"default_locale": "en-IN", "supported": ["en-IN", "hi-IN"]} |
| PLATFORM | TAX_CONFIGURATION | Tax System Configuration | Enabled | {"tax_system": "GST", "auto_calculate": true, "reverse_charge": true} |
| PLATFORM | SSO_INTEGRATION | SSO / Identity Provider | Disabled | {"enabled": false, "provider": "AZURE_AD", "enforce_sso": false} |
| PLATFORM | API_RATE_LIMITING | API Rate Limiting | Enabled | {"requests_per_minute": 60, "burst_limit": 100} |
| PLATFORM | DATA_EXPORT | Data Export | Enabled | {"formats": ["CSV", "XLSX", "PDF"], "scheduled_exports": true} |
| PLATFORM | DASHBOARD_CUSTOMIZATION | Dashboard Customization | Enabled | {"max_widgets": 12, "shared_dashboards": true} |
| PLATFORM | AI_RECOMMENDATIONS | AI-Powered Recommendations | Disabled | {"spend_analysis": false, "vendor_suggestion": false, "price_prediction": false} |
The ConfigurationService provides cached, tenant-aware lookups for module status, feature flags, feature configuration values, and ordered process stages with overrides applied.
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(); } }
// 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... }
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.