βš™οΈ Transformation Methodology

Systematic and rigorous 8-phase process to transform each Dolibarr module from traditional PHP to PostgreSQL client/server model, ensuring maximum quality and consistency at every step.

πŸ“‹

Systematized Process

8 clearly defined and documented phases for each module

πŸ§ͺ

Rigorous Testing

Comparative tests that guarantee 100% functional parity

πŸ”„

Iterative and Reproducible

Each module follows exactly the same proven process

πŸ”„ Module Migration Process

Systematic methodology developed to transform each Dolibarr module from traditional PHP model to PostgreSQL client/server model.

1

Analysis and Preparation

πŸ” Component Identification

  • Map all module tables
  • Identify relationships and dependencies
  • Analyze existing PHP methods (create, update, validate)
  • Document current business rules
  • Verify relationship tables expected by code
Analysis command:
# Search all table references in PHP code
grep -r "llx_tabla" htdocs/modulo/ | grep -E "(DELETE|INSERT|UPDATE)"

# Verify structure in PostgreSQL
\d llx_tabla*
2

Tests de Estructura

πŸ—οΈ Infrastructure Validation

CRITICAL: Before writing any function, verify that all tables and fields exist.

Structure test example:
-- test-modulo-estructura.sql
BEGIN;
SELECT plan(15);

-- Verify table existence
SELECT has_table('llx_tabla_principal', 'Main table must exist');
SELECT has_table('llx_tabla_det', 'Detail table must exist');

-- Verify required fields
SELECT has_column('llx_tabla', 'rowid', 'rowid field must exist');
SELECT has_column('llx_tabla', 'entity', 'entity field must exist');

-- Verify data types
SELECT col_type_is('llx_tabla', 'amount', 'double precision', 'amount must be double precision');

-- Verify constraints
SELECT col_not_null('llx_tabla', 'fk_user_author', 'fk_user_author must be NOT NULL');

SELECT * FROM finish();
ROLLBACK;
3

ImplementaciΓ³n PostgreSQL

βš™οΈ Development of Functions and Triggers

  • Create specific validation functions
  • Implement BEFORE INSERT/UPDATE triggers
  • Develop automatic calculation functions
  • Establish unique code generation
  • Configure audit and logging
Standard function pattern:
CREATE OR REPLACE FUNCTION llx_tabla_before_insert() 
RETURNS trigger 
LANGUAGE plpgsql
AS $$
BEGIN
    -- 1. Validaciones obligatorias
    IF NEW.campo_obligatorio IS NULL OR trim(NEW.campo_obligatorio) = '' THEN
        RAISE EXCEPTION 'Required field cannot be empty';
    END IF;
    
    -- 2. Limpieza de datos
    NEW.campo_texto = trim(NEW.campo_texto);
    
    -- 3. Valores por defecto
    NEW.entity = COALESCE(NEW.entity, 1);
    NEW.datec = COALESCE(NEW.datec, NOW());
    
    -- 4. Automatic code generation
    IF NEW.ref IS NULL OR NEW.ref = '' THEN
        NEW.ref := llx_tabla_get_next_ref();
    END IF;
    
    RETURN NEW;
END;
$$;
4

Unit Tests pgTAP

πŸ§ͺ Complete Functional Validation

  • Tests de existencia de functions y triggers
  • Tests de validaciones (casos exitosos y fallidos)
  • Automatic calculation tests
  • Code generation tests
  • Tests de casos extremos y errores
πŸ“‹ Testing Categories
  • Structure: Existence of functions/triggers
  • Validations: Required fields, formats
  • Calculations: Totals, prices, discounts
  • Codes: Automatic generation and uniqueness
  • States: Valid and invalid transitions
  • Relations: Referential integrity
5

PHP Modification

πŸ”§ Code Simplification

Objective: Convert PHP into a thin client that only executes SQL.

❌ Before: PHP with Logic
public function verify() {
    if (empty($this->nom)) {
        $this->errors[] = 'Nom obligatoire';
        return -1;
    }
    
    if (!empty($this->email) && !filter_var($this->email, FILTER_VALIDATE_EMAIL)) {
        $this->errors[] = 'Email invalide';
        return -1;
    }
    
    // More validations...
    return 0;
}
βœ… After: Simplified PHP
public function verify() {
    return 0; // PostgreSQL triggers handle ALL validation
}
πŸ—‘οΈ Elements to REMOVE from PHP:
  • Methods verify(), validate(), check()
  • Code and reference generation
  • Total and subtotal calculations
  • Format and required field validations
  • MySQL compatibility conditionals
  • State logic and transitions
6

Comparative Tests

βš–οΈ Functional Parity Validation

Rigorous process to ensure PostgreSQL behavior is identical to original PHP.

1. PHP Capture

Execute operations in database without triggers

β†’
2. PostgreSQL Execution

Same operations with active triggers

β†’
3. Comparison

Validate identical results

Comparative test script:
-- Temporary table for results
CREATE TEMP TABLE test_results (
    test_name varchar(100),
    php_result text,
    pgsql_result text,
    match boolean
);

-- Example comparative test
DO $$
DECLARE
    v_error text;
BEGIN
    -- Attempt invalid insertion in PostgreSQL
    BEGIN
        INSERT INTO llx_tabla (campo_obligatorio) VALUES ('');
        PERFORM fail('Should have failed');
    EXCEPTION WHEN OTHERS THEN
        GET STACKED DIAGNOSTICS v_error = MESSAGE_TEXT;
        INSERT INTO test_results VALUES 
        ('empty_field', 'ErrorFieldRequired', v_error, v_error LIKE '%required%');
    END;
END $$;

-- Show results
SELECT * FROM test_results WHERE NOT match;
7

Documentation

πŸ“š Complete Process Record

  • MIGRATION_PLAN.md: Detailed pre-implementation plan
  • MIGRATION_LOG.md: Chronological change log
  • functions-module.sql: Commented PostgreSQL code
  • test-module.sql: Complete pgTAP tests
  • Screenshots: Evidence of passing tests
πŸ“ Documentation Structure:
htdocs/modulo/
β”œβ”€β”€ MIGRATION_PLAN.md          # Migration plan
β”œβ”€β”€ MIGRATION_LOG.md           # Change log
β”œβ”€β”€ class/modulo.class.php     # Simplified PHP
└── class/modulo.class.php.orig # Backup original

htdocs/install/pgsql/
β”œβ”€β”€ functions/
β”‚   └── functions-modulo.sql   # PostgreSQL Functions
└── tests/
    β”œβ”€β”€ test-modulo.sql        # Functional tests
    └── test-modulo-estructura.sql # Structure tests

πŸ§ͺ Testing Strategy

Implemented Testing Pyramid

Comparative Tests

PHP ↔ PostgreSQL parity validation

68 tests

Unit Tests pgTAP

Functions, triggers y validaciones

1,477 tests

Tests de Estructura

Tablas, campos y constraints

56 tests

πŸ”§ Herramientas de Testing

pgTAP

Framework de testing unitario para PostgreSQL

  • Tests de existencia de functions
  • Behavior validation
  • Exception verification
  • Tests de tipos de datos

Comparative Tests

PHP scripts to capture original behavior

  • Controlled execution of operations
  • Error and exception capture
  • Automated comparison
  • Discrepancy reporting

Test Databases

Isolated environment for validation

  • dolibarr_test_php (without triggers)
  • dolibarr_test_postgresql (with triggers)
  • Automated synchronization
  • Controlled test data

βœ… Migration Checklist

Mandatory checklist to ensure each migrated module meets all quality standards.

πŸ” Preparation

πŸ—οΈ Structure Tests

βš™οΈ PostgreSQL Implementation

πŸ§ͺ Testing

πŸ”§ PHP Modification

βš–οΈ Comparative Tests

πŸ“š Documentation

πŸ“‹ Templates and Patterns

πŸ“„ MIGRATION_PLAN.md

Standard template for planning each module migration

# MIGRATION_PLAN - Module X

## Initial Analysis
- Identified tables: X
- Relations: Y 
- Business rules: Z

## PostgreSQL Implementation
- [ ] Validation function
- [ ] Automatic triggers
- [ ] Code generation

## Testing
- [ ] Structure tests
- [ ] pgTAP tests
- [ ] Comparative tests

## PHP Modification
- [ ] Simplify verify()
- [ ] Remove calculations
- [ ] Remove validations

πŸ”§ PostgreSQL Function

Standard pattern for validation functions

CREATE OR REPLACE FUNCTION llx_tabla_before_insert() 
RETURNS trigger 
LANGUAGE plpgsql
AS $$
BEGIN
    -- Required validations
    IF NEW.campo IS NULL OR trim(NEW.campo) = '' THEN
        RAISE EXCEPTION 'ErrorFieldRequired: campo';
    END IF;
    
    -- Data cleanup
    NEW.campo_texto = trim(NEW.campo_texto);
    
    -- Default values
    NEW.entity = COALESCE(NEW.entity, 1);
    NEW.datec = COALESCE(NEW.datec, NOW());
    
    RETURN NEW;
END;
$$;

πŸ§ͺ Test pgTAP

Standard structure for unit tests

BEGIN;
SELECT plan(X);

-- Existence tests
SELECT has_function('llx_tabla_validate');
SELECT has_trigger('llx_tabla', 'trigger_name');

-- Validation tests
PREPARE test_invalid AS
    INSERT INTO llx_tabla (campo) VALUES ('');
SELECT throws_ok(
    'test_invalid',
    'P0001',
    'ErrorFieldRequired',
    'Must fail with empty field'
);

SELECT * FROM finish();
ROLLBACK;