Skip to content
This is my space, where experience meets the will to start over. This is my space, where experience meets the will to start over.

The first step is knowing where you want to go.

  • Home
  • Coding Hub
    • Software & Project
      • Small Biz Ops – S.B.O.
        • SmallBizOps – Day 10/90
      • CRM/ERP
      • MyTracker
      • My Budget
    • Form Zero to “WoW”
      • JavaScript from Zero (Completed)
        • 2. Remove and Edit List Items
        • 3. Separate HTML and JavaScript, Use addEventListener and Conditional Logic
        • 4. Add Dynamic CSS Classes
        • 5. Save & Restore Your List with localStorage
        • 6 – Turn Your App into a Full To-Do List
      • Python from Zero (Completed)
        • 2. Lists & Loops
        • 3. Conditional Menus
        • 4. Edit & Remove Tasks (with closing: Python vs PHP and Large Data)
        • 5 – Save to File: Make Your Tasks Survive Restarts
        • 6 — Pythin from zero – Final Project Polishing: Numbering, Formatting, and Preparing for CSV
      • Rust – From Zero to “WoW” (Completed)
        • 1 – Setup and Project Structure in Rust
        • 2 – User input: validation and error handling
        • 3 – Rust from Zero to “WoW – BMI Calculation and Conditional Logic
        • 4 –Rust – Clear, Formatted Output
        • 5 – Rust – Final Thoughts: Precision as a Form of Respect
      • Go from Zero to “WoW” (Completed)
        • 1 – Why Go Is Perfect for a Personal Expense Tracker
        • 2 – Logging Expenses and Console Input
        • 3 – Go from Zero to “WoW” – Smart Filtering & Display Logic
        • 4 – Go – Saving Data to Local Files
        • 5 – Go – Final Project – Expense Tracker in Go
      • C++ from Zero to “WoW” (Completed)
        • 1 – Why C++ for file organization?
        • 2 – C++ – File Type Detection and Classification
        • 3 – C++ – Creating & Managing Subfolders
        • 4 – C++ – Safe File Movement and User Feedback
        • 5 – C++ – Order as Mental Clarity
      • Ubuntu – From Zero to “WoW” (Completed)
        • 2 – Ubuntu – The Desktop Environment and Essential Commands
        • 3 – Ubuntu – Managing Files, Folders, and Permissions
        • 4 – Ubuntu – Installing and Updating Software with APT and Snap
        • 5 – Ubuntu – Customizing the Desktop Environment
        • 6 – Ubuntu – Network and Device Configuration
        • 7 – Ubuntu – User Management & System Security — “The Cathedral of Permissions”
        • 8 – Ubuntu – The Talking Machine: Terminal & Bash Scripting
        • 9 – Ubuntu – Ubuntu as a Server or Development Environment
        • 10 – Ubuntu – Backup, Maintenance & Troubleshooting
    • Git Hub Repository
      • Small Biz Ops – S.B.O.
      • Mini ERP – PHP & MySQL
      • CleverCRM (Java, Spring Boot)
      • FraudWatch (Python, FastAPI + scikit-learn)
      • OnboardIQ – Smart Onboarding Portal (Flask + SQLite Demo)
    • ArchPilot
      • 1-Users & Roles, End-to-End (Architecture, Database, and Cross-Framework Code)
      • 2 – Client Registry (CRM) Across Frameworks
      • 3 – Project & Budget Tracker (ERP)
      • 4 – Approval Workflow Engine Multi-step routing, status tracking, escalation paths
      • 5 – Audit Trail & Versioning
    • Small Biz Ops – S.B.O.
  • Vivere in USA
  • P4Y
  • Testi poetici
    • 1 – Sospeso
    • 2 – Il bicchiere di vetro quieto
    • 3 – Quando l’amore inciampa
    • 4 – Ma chi siete davvero?
    • 5 – Above the Thread of Day
    • 6 – The Truth That Doesn’t Exist
    • 7 – All of You, I Miss
    • 8 – The Captain and the Ocean
    • 9 – Between Light and Mist
    • 10 – Il peso delle scelte
  • Contact
  • Admin
This is my space, where experience meets the will to start over.
This is my space, where experience meets the will to start over.

The first step is knowing where you want to go.

ArchPilot – Step 4 – Approval Workflow Engine Multi-step routing, status tracking, escalation paths

Posted on 6 Dicembre 20256 Dicembre 2025 By Francesco

In this Step 4, we want to show that a serious ERP/CRM doesn’t just have a simple “status” field, but a real approval engine:

  • requests that go through multiple steps (e.g., Manager → Director → Finance)
  • a status that evolves over time (PENDING → APPROVED → REJECTED → ESCALATED)
  • assignment of the task to different people (assigned_to)
  • possible escalations if something stays stuck for too long

To avoid building a full workflow engine (too big for a “showcase” page), we focus on one very clear piece:

The “approve current step” logic:
given an ApprovalRequest in PENDING status, when the user approves:

  • if there are more steps → move to the next step and reassign
  • if this is the last step → mark the request as APPROVED.

This micro-function is perfect to compare frameworks:
it’s small, but it contains state, business logic, and persistence.


2. Shared scenario and selected piece to implement

We use the same simplified entity in all frameworks:

Entity: ApprovalRequest

Minimal fields:

  • id
  • target_type (e.g., "PROJECT", "DISCOUNT")
  • target_id (project id, client id, etc.)
  • current_step (int)
  • status (string: PENDING, APPROVED, REJECTED, ESCALATED)
  • assigned_to (string: email or user id)

Function we want to demonstrate:

approveStep(requestId, totalSteps, approvedByUser)

Behavior:

  1. Load the ApprovalRequest
  2. If status ≠ PENDING → do nothing
  3. If current_step < totalSteps:
    • increment current_step
    • reassign assigned_to (e.g., to the next role/user)
  4. If current_step == totalSteps:
    • set status = APPROVED
    • optionally keep or clear assigned_to (depending on the policy)

We don’t handle REJECT or ESCALATE here: we simply mention them as possible states, but the example stays focused on the approval transition.


3. How each framework handles “Approve Step”

Below we only show the core approveStep logic, so the page stays readable.

3.1 PHP + MySQL (procedural)

function approve_step(mysqli $db, int $requestId, int $totalSteps, string $approvedBy): void
{
    $sql  = "SELECT * FROM approval_requests WHERE id = ?";
    $stmt = $db->prepare($sql);
    $stmt->bind_param("i", $requestId);
    $stmt->execute();
    $result  = $stmt->get_result();
    $request = $result->fetch_assoc();

    if (!$request || $request['status'] !== 'PENDING') {
        return; // nothing to do
    }

    $currentStep = (int)$request['current_step'];

    if ($currentStep < $totalSteps) {
        $nextStep     = $currentStep + 1;
        // in a real system we would resolve the next approver based on role/step
        $nextAssignee = 'director@company.com';

        $updateSql = "
            UPDATE approval_requests
            SET current_step = ?, assigned_to = ?
            WHERE id = ?
        ";
        $up = $db->prepare($updateSql);
        $up->bind_param("isi", $nextStep, $nextAssignee, $requestId);
        $up->execute();
    } else {
        $updateSql = "
            UPDATE approval_requests
            SET status = 'APPROVED'
            WHERE id = ?
        ";
        $up = $db->prepare($updateSql);
        $up->bind_param("i", $requestId);
        $up->execute();
    }
}

Here you clearly see the “bare metal” version: manual queries, full control.

3.2 Laravel (PHP – Eloquent ORM)

// App/Models/ApprovalRequest.php
class ApprovalRequest extends Model
{
    protected $fillable = [
        'target_type',
        'target_id',
        'current_step',
        'status',
        'assigned_to',
    ];
}

// App/Services/ApprovalWorkflowService.php
class ApprovalWorkflowService
{
    public function approveStep(int $requestId, int $totalSteps, string $approvedBy): void
    {
        $request = ApprovalRequest::find($requestId);

        if (! $request || $request->status !== 'PENDING') {
            return;
        }

        if ($request->current_step < $totalSteps) {
            $request->current_step++;
            // in a real case: resolve the next approver from a roles table
            $request->assigned_to = 'director@company.com';
        } else {
            $request->status = 'APPROVED';
        }

        $request->save();
    }
}

Same logic, but much more readable: database access is abstracted behind Eloquent.

3.3 Django (Python – ORM)

# models.py
from django.db import models

class ApprovalRequest(models.Model):
    target_type  = models.CharField(max_length=50)
    target_id    = models.IntegerField()
    current_step = models.IntegerField(default=1)
    status       = models.CharField(max_length=20, default='PENDING')
    assigned_to  = models.EmailField()

# services.py
from .models import ApprovalRequest

def approve_step(request_id: int, total_steps: int, approved_by: str) -> None:
    try:
        req = ApprovalRequest.objects.get(pk=request_id)
    except ApprovalRequest.DoesNotExist:
        return

    if req.status != 'PENDING':
        return

    if req.current_step < total_steps:
        req.current_step += 1
        # in a real application, resolve the next approver by role/team
        req.assigned_to = 'director@company.com'
    else:
        req.status = 'APPROVED'

    req.save()

The structure is very similar to Laravel: a simple model plus a service function.

3.4 Node.js + Express + Sequelize

// models/ApprovalRequest.js
const { DataTypes } = require("sequelize");
const sequelize = require("../db");

const ApprovalRequest = sequelize.define("ApprovalRequest", {
  target_type:  DataTypes.STRING,
  target_id:    DataTypes.INTEGER,
  current_step: { type: DataTypes.INTEGER, defaultValue: 1 },
  status:       { type: DataTypes.STRING,  defaultValue: "PENDING" },
  assigned_to:  DataTypes.STRING,
});

module.exports = ApprovalRequest;

// workflow.js
const ApprovalRequest = require("./models/ApprovalRequest");

async function approveStep(requestId, totalSteps, approvedBy) {
  const req = await ApprovalRequest.findByPk(requestId);
  if (!req || req.status !== "PENDING") return;

  if (req.current_step < totalSteps) {
    req.current_step += 1;
    req.assigned_to = "director@company.com";
  } else {
    req.status = "APPROVED";
  }

  await req.save();
}

module.exports = { approveStep };

Here the pattern is asynchronous, but the application logic stays identical.

3.5 Spring Boot (Java – JPA + Service)

// Entity
@Entity
public class ApprovalRequest {

    @Id @GeneratedValue
    private Long id;

    private String targetType;
    private Long targetId;
    private int currentStep;
    private String status;     // PENDING, APPROVED, ...
    private String assignedTo; // email or username

    // getters/setters
}

// Repository
public interface ApprovalRequestRepository extends JpaRepository<ApprovalRequest, Long> { }

// Service
@Service
public class ApprovalWorkflowService {

    @Autowired
    private ApprovalRequestRepository repo;

    public void approveStep(Long requestId, int totalSteps, String approvedBy) {
        ApprovalRequest req = repo.findById(requestId)
            .orElseThrow(() -> new IllegalArgumentException("Not found"));

        if (!"PENDING".equals(req.getStatus())) {
            return;
        }

        if (req.getCurrentStep() < totalSteps) {
            req.setCurrentStep(req.getCurrentStep() + 1);
            req.setAssignedTo("director@company.com");
        } else {
            req.setStatus("APPROVED");
        }

        repo.save(req);
    }
}

Here you see strong typing and the classic repository + service pattern.

3.6 ASP.NET Core (C# – EF Core)

public class ApprovalRequest
{
    public int Id { get; set; }
    public string TargetType { get; set; } = "";
    public int TargetId { get; set; }
    public int CurrentStep { get; set; } = 1;
    public string Status { get; set; } = "PENDING";
    public string AssignedTo { get; set; } = "";
}

public class AppDbContext : DbContext
{
    public DbSet<ApprovalRequest> ApprovalRequests { get; set; }
}

public class ApprovalWorkflowService
{
    private readonly AppDbContext _context;

    public ApprovalWorkflowService(AppDbContext context)
    {
        _context = context;
    }

    public void ApproveStep(int requestId, int totalSteps, string approvedBy)
    {
        var req = _context.ApprovalRequests.Find(requestId);
        if (req == null || req.Status != "PENDING")
            return;

        if (req.CurrentStep < totalSteps)
        {
            req.CurrentStep += 1;
            req.AssignedTo = "director@company.com";
        }
        else
        {
            req.Status = "APPROVED";
        }

        _context.SaveChanges();
    }
}

Very similar to Spring in architecture, but in the Microsoft ecosystem.

3.7 Drupal (PHP – Entity API)

/**
 * Approve step for a custom approval_request entity.
 */
function mymodule_approve_step($request_id, $total_steps, $approved_by) {
  $storage = \Drupal::entityTypeManager()->getStorage('approval_request');
  /** @var \Drupal\Core\Entity\EntityInterface $approval */
  $approval = $storage->load($request_id);

  if (!$approval) {
    return;
  }

  $status = $approval->get('field_status')->value;
  if ($status !== 'PENDING') {
    return;
  }

  $current_step = (int) $approval->get('field_current_step')->value;

  if ($current_step < $total_steps) {
    $approval->set('field_current_step', $current_step + 1);
    $approval->set('field_assigned_to', 'director@company.com');
  }
  else {
    $approval->set('field_status', 'APPROVED');
  }

  $approval->save();
}

Here everything goes through Drupal’s Entity API and field system.


4. Pros and cons table (for this single “Approve Step” function)

FrameworkMain prosMain cons
PHP + MySQLMaximum control, no magic, runs on almost any hostingLots of boilerplate, risk of SQL errors, no built-in validation/abstraction
LaravelExpressive syntax, powerful Eloquent, easy to add events, logs, policiesRequires knowledge of the Laravel ecosystem, some “magic” under the hood
DjangoMature ORM, very readable code, easy integration with Django RESTLess flexible for very custom SQL, learning curve for Python + Django
Node + SequelizeGreat for JSON APIs, async-ready, easy to integrate with SPA frontendsTooling less opinionated, must pay attention to async error handling and typing
Spring BootStrong typing, fits enterprise architectures, highly testable servicesMore verbose, heavier initial setup
ASP.NET CoreHigh performance, excellent in Microsoft environments, powerful EF CoreHigher lock-in to MS stack, less immediate for PHP/Python developers
DrupalWorkflow integrated with content and permissions, ideal for editorial portalsOverkill if you only need business logic, APIs feel heavier for pure PHP devs

5. Final disclaimer (ArchPilot style)

Disclaimer
The contents of this page, including code examples and architectural descriptions, are provided for informational and educational purposes only. They do not constitute professional advice (technical, legal, tax, or otherwise), nor any guarantee of accuracy, completeness, or suitability for production use.

Any use of the information provided is entirely at the user’s own risk and responsibility. Always perform your own technical, legal, and security reviews before adopting any solution in real-world environments.

All trademarks mentioned are the property of their respective owners. Any references to third-party technologies, frameworks, or products are for descriptive purposes only and do not imply any affiliation, endorsement, or sponsorship.

Post Views: 445

Condividi:

  • Condividi su Facebook (Si apre in una nuova finestra) Facebook
  • Condividi su X (Si apre in una nuova finestra) X
Coding ArchPilot

Navigazione articoli

Previous post
Next post

Francesco

My name is Francesco Boschi, originally from Italy and currently based in the United States. For over twenty years, I’ve worked as a manager and consultant across diverse sectors — from education and cultural institutions to the food industry — developing skills in operational management, strategic consulting, and complex problem-solving. In recent years, I’ve combined this experience with a strong passion for software development, creating custom tools designed to simplify workflows and meet real business needs.

Relocating to the U.S. marks the beginning of a new chapter: a personal and professional decision driven by the desire to be close to my son and to embrace new challenges in a different environment. Today, my goal is to turn my experience into meaningful solutions, blending strategic vision with technical expertise to help people and organizations work more effectively.

I enjoy moving between different worlds, adapting tools and approaches to people and contexts. I bring leadership, flexibility, attention to detail, analytical thinking, and a strong problem-solving mindset — along with a deep curiosity to learn and grow. Above all, I believe in sharing: I’m always eager to offer my experience to support the growth of others.

Related Posts

Coding

Coding – Step 14.5 – Ubuntu – Customizing the Desktop Environment

Posted on 27 Ottobre 202527 Ottobre 2025

Ubuntu offers a flexible desktop you can shape to your style and needs. This chapter shows how to change themes, icons, fonts, and workspace behavior — even if you’re just starting out.

Condividi:

  • Condividi su Facebook (Si apre in una nuova finestra) Facebook
  • Condividi su X (Si apre in una nuova finestra) X
Read More
Coding

Coding – Step 10.3 – Python from Zero – Conditional Menus

Posted on 9 Agosto 202517 Agosto 2025

Step 10.3 – Python from Zero – Conditional Menus Create a command-based menu with options: Add, View, and Exit 🧠 Why this lesson is important In the first two lessons, we learned how to: Now, we’ll combine these skills to create an interactive program where the user selects actions from…

Condividi:

  • Condividi su Facebook (Si apre in una nuova finestra) Facebook
  • Condividi su X (Si apre in una nuova finestra) X
Read More
Coding

From Zero to “WoW”

Posted on 11 Ottobre 202511 Ottobre 2025

From Zero to WoW is a hands-on coding series that guides learners through practical, incremental exercises in JavaScript, Python, Go, Rust, C++, and Ubuntu. Each path focuses on building real tools with clarity, autonomy, and purpose—no shortcuts, just meaningful progress.

Condividi:

  • Condividi su Facebook (Si apre in una nuova finestra) Facebook
  • Condividi su X (Si apre in una nuova finestra) X
Read More

Iscriviti alla nostra Newsletter

🤞 Let's keep in touch

We do not send spam! Read our Privacy policy for more information.

Controlla la tua casella di posta o la cartella spam per confermare la tua iscrizione

Cerca nel sito

©2026 This is my space, where experience meets the will to start over. | WordPress Theme by SuperbThemes