NEW: Improve phpunit test coverage for Commande, Propal and Facture (#39517)
* NEW: Improve phpunit test coverage for Commande, Propal and Facture Add line CRUD (addline/updateline/deleteLine) and total-consistency assertions, reuse the specimen-invariant regression check (previously only in FactureTest) across all three, and add a new integration test covering the full Propal -> Commande -> Facture conversion chain (thirdparty, notes, totals and object_linked propagation). Two shared assertions were added to CommonClassTest for this: assertLineTotalsMatchHeader() and assertMatchesFreshSpecimen(). * Update commande.class.php * FIX PropalCommandeFactureWorkflowTest reads wrong invoice after createFromOrder Facture::createFromOrder() returns a status flag (1/-1), not the new invoice id, unlike Commande::createFromProposal(). The test was doing fetch($result) with $result==1, which happened to no-op locally (no invoice with rowid=1) but fetched an unrelated invoice in CI, causing a spurious thirdparty mismatch. Use $facture->id, already set by create() inside createFromOrder(), like every other caller of this method does.
This commit is contained in:
parent
b254e27660
commit
b7413ee622
7 changed files with 650 additions and 49 deletions
|
|
@ -11,7 +11,7 @@
|
||||||
* Copyright (C) 2014-2015 Marcos García <marcosgdf@gmail.com>
|
* Copyright (C) 2014-2015 Marcos García <marcosgdf@gmail.com>
|
||||||
* Copyright (C) 2018 Nicolas ZABOURI <info@inovea-conseil.com>
|
* Copyright (C) 2018 Nicolas ZABOURI <info@inovea-conseil.com>
|
||||||
* Copyright (C) 2016-2022 Ferran Marcet <fmarcet@2byte.es>
|
* Copyright (C) 2016-2022 Ferran Marcet <fmarcet@2byte.es>
|
||||||
* Copyright (C) 2021-2025 Frédéric France <frederic.france@free.fr>
|
* Copyright (C) 2021-2026 Frédéric France <frederic.france@free.fr>
|
||||||
* Copyright (C) 2022 Gauthier VERDOL <gauthier.verdol@atm-consulting.fr>
|
* Copyright (C) 2022 Gauthier VERDOL <gauthier.verdol@atm-consulting.fr>
|
||||||
* Copyright (C) 2024-2026 MDW <mdeweerd@users.noreply.github.com>
|
* Copyright (C) 2024-2026 MDW <mdeweerd@users.noreply.github.com>
|
||||||
* Copyright (C) 2024 William Mead <william.mead@manchenumerique.fr>
|
* Copyright (C) 2024 William Mead <william.mead@manchenumerique.fr>
|
||||||
|
|
|
||||||
|
|
@ -242,6 +242,8 @@ class AllTests
|
||||||
$suite->addTestSuite('CommonInvoiceTest');
|
$suite->addTestSuite('CommonInvoiceTest');
|
||||||
require_once dirname(__FILE__).'/FactureTest.php';
|
require_once dirname(__FILE__).'/FactureTest.php';
|
||||||
$suite->addTestSuite('FactureTest');
|
$suite->addTestSuite('FactureTest');
|
||||||
|
require_once dirname(__FILE__).'/PropalCommandeFactureWorkflowTest.php';
|
||||||
|
$suite->addTestSuite('PropalCommandeFactureWorkflowTest');
|
||||||
require_once dirname(__FILE__).'/FactureRecTest.php';
|
require_once dirname(__FILE__).'/FactureRecTest.php';
|
||||||
$suite->addTestSuite('FactureRecTest');
|
$suite->addTestSuite('FactureRecTest');
|
||||||
require_once dirname(__FILE__).'/FactureTestRounding.php';
|
require_once dirname(__FILE__).'/FactureTestRounding.php';
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<?php
|
<?php
|
||||||
/* Copyright (C) 2010 Laurent Destailleur <eldy@users.sourceforge.net>
|
/* Copyright (C) 2010 Laurent Destailleur <eldy@users.sourceforge.net>
|
||||||
* Copyright (C) 2023 Alexandre Janniaux <alexandre.janniaux@gmail.com>
|
* Copyright (C) 2023 Alexandre Janniaux <alexandre.janniaux@gmail.com>
|
||||||
* Copyright (C) 2024-2025 Frédéric France <frederic.france@free.fr>
|
* Copyright (C) 2024-2026 Frédéric France <frederic.france@free.fr>
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify
|
* This program is free software; you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
|
@ -119,6 +119,12 @@ class CommandeTest extends CommonClassTest
|
||||||
|
|
||||||
$this->assertLessThan($result, 0);
|
$this->assertLessThan($result, 0);
|
||||||
print __METHOD__." id=".$id." result=".$result."\n";
|
print __METHOD__." id=".$id." result=".$result."\n";
|
||||||
|
|
||||||
|
// Specimen lines are built from real products picked at random (see Commande::initAsSpecimen), so the
|
||||||
|
// exact line count is not stable (a kit/BOM product can expand into extra lines) - only check totals coherence.
|
||||||
|
$this->assertNotEmpty($localobject->lines);
|
||||||
|
$this->assertLineTotalsMatchHeader($localobject, 'after fetch');
|
||||||
|
|
||||||
return $localobject;
|
return $localobject;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -147,13 +153,116 @@ class CommandeTest extends CommonClassTest
|
||||||
return $localobject;
|
return $localobject;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* testCommandeAddLine
|
||||||
|
*
|
||||||
|
* @param Commande $localobject Commande
|
||||||
|
* @return array{0:Commande,1:int} Commande and id of the line added
|
||||||
|
*
|
||||||
|
* @depends testCommandeUpdate
|
||||||
|
* The depends says test is run only if previous is ok
|
||||||
|
*/
|
||||||
|
public function testCommandeAddLine($localobject)
|
||||||
|
{
|
||||||
|
global $conf,$user,$langs,$db;
|
||||||
|
$conf = $this->savconf;
|
||||||
|
$user = $this->savuser;
|
||||||
|
$langs = $this->savlangs;
|
||||||
|
$db = $this->savdb;
|
||||||
|
|
||||||
|
$localobject->fetch_thirdparty();
|
||||||
|
$beforelinecount = count($localobject->lines);
|
||||||
|
$beforetotalht = (float) $localobject->total_ht;
|
||||||
|
|
||||||
|
$lineid = $localobject->addline('PHPUnit addline test', 100, 2, 20); // 2 x 100 HT at 20% VAT = 200 HT / 40 VAT / 240 TTC
|
||||||
|
|
||||||
|
print __METHOD__." id=".$localobject->id." lineid=".$lineid."\n";
|
||||||
|
$this->assertGreaterThan(0, $lineid, $localobject->errorsToString());
|
||||||
|
|
||||||
|
$localobject->fetch($localobject->id);
|
||||||
|
$this->assertCount($beforelinecount + 1, $localobject->lines);
|
||||||
|
$this->assertEqualsWithDelta($beforetotalht + 200, (float) $localobject->total_ht, 0.01, 'total_ht not updated after addline');
|
||||||
|
$this->assertLineTotalsMatchHeader($localobject, 'after addline');
|
||||||
|
|
||||||
|
return array($localobject, $lineid);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* testCommandeUpdateLine
|
||||||
|
*
|
||||||
|
* @param array{0:Commande,1:int} $params Commande and id of the line to update
|
||||||
|
* @return array{0:Commande,1:int} Commande and id of the line updated
|
||||||
|
*
|
||||||
|
* @depends testCommandeAddLine
|
||||||
|
* The depends says test is run only if previous is ok
|
||||||
|
*/
|
||||||
|
public function testCommandeUpdateLine($params)
|
||||||
|
{
|
||||||
|
global $conf,$user,$langs,$db;
|
||||||
|
$conf = $this->savconf;
|
||||||
|
$user = $this->savuser;
|
||||||
|
$langs = $this->savlangs;
|
||||||
|
$db = $this->savdb;
|
||||||
|
|
||||||
|
list($localobject, $lineid) = $params;
|
||||||
|
$beforelinecount = count($localobject->lines);
|
||||||
|
$beforetotalht = (float) $localobject->total_ht;
|
||||||
|
|
||||||
|
$result = $localobject->updateline($lineid, 'PHPUnit addline test', 100, 3, 0, 20); // qty 2 -> 3, so +100 HT / +20 VAT / +120 TTC
|
||||||
|
|
||||||
|
print __METHOD__." id=".$localobject->id." lineid=".$lineid." result=".$result."\n";
|
||||||
|
$this->assertGreaterThan(0, $result, $localobject->errorsToString());
|
||||||
|
|
||||||
|
$localobject->fetch($localobject->id);
|
||||||
|
$this->assertCount($beforelinecount, $localobject->lines);
|
||||||
|
$this->assertEqualsWithDelta($beforetotalht + 100, (float) $localobject->total_ht, 0.01, 'total_ht not updated after updateline');
|
||||||
|
$this->assertLineTotalsMatchHeader($localobject, 'after updateline');
|
||||||
|
|
||||||
|
return array($localobject, $lineid);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* testCommandeDeleteLine
|
||||||
|
*
|
||||||
|
* @param array{0:Commande,1:int} $params Commande and id of the line to delete
|
||||||
|
* @return Commande
|
||||||
|
*
|
||||||
|
* @depends testCommandeUpdateLine
|
||||||
|
* The depends says test is run only if previous is ok
|
||||||
|
*/
|
||||||
|
public function testCommandeDeleteLine($params)
|
||||||
|
{
|
||||||
|
global $conf,$user,$langs,$db;
|
||||||
|
$conf = $this->savconf;
|
||||||
|
$user = $this->savuser;
|
||||||
|
$langs = $this->savlangs;
|
||||||
|
$db = $this->savdb;
|
||||||
|
|
||||||
|
list($localobject, $lineid) = $params;
|
||||||
|
$beforelinecount = count($localobject->lines);
|
||||||
|
$beforetotalht = (float) $localobject->total_ht;
|
||||||
|
|
||||||
|
$result = $localobject->deleteLine($user, $lineid);
|
||||||
|
|
||||||
|
print __METHOD__." id=".$localobject->id." lineid=".$lineid." result=".$result."\n";
|
||||||
|
$this->assertGreaterThan(0, $result, $localobject->errorsToString());
|
||||||
|
|
||||||
|
$localobject->fetch($localobject->id);
|
||||||
|
// Back to the original specimen lines, with the same totals
|
||||||
|
$this->assertCount($beforelinecount - 1, $localobject->lines);
|
||||||
|
$this->assertEqualsWithDelta($beforetotalht - 300, (float) $localobject->total_ht, 0.01, 'total_ht not updated after deleteLine');
|
||||||
|
$this->assertLineTotalsMatchHeader($localobject, 'after deleteLine');
|
||||||
|
|
||||||
|
return $localobject;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* testCommandeValid
|
* testCommandeValid
|
||||||
*
|
*
|
||||||
* @param Commande $localobject Order
|
* @param Commande $localobject Order
|
||||||
* @return Commande
|
* @return Commande
|
||||||
*
|
*
|
||||||
* @depends testCommandeUpdate
|
* @depends testCommandeDeleteLine
|
||||||
* The depends says test is run only if previous is ok
|
* The depends says test is run only if previous is ok
|
||||||
*/
|
*/
|
||||||
public function testCommandeValid($localobject)
|
public function testCommandeValid($localobject)
|
||||||
|
|
@ -168,6 +277,30 @@ class CommandeTest extends CommonClassTest
|
||||||
|
|
||||||
print __METHOD__." id=".$localobject->id." result=".$result."\n";
|
print __METHOD__." id=".$localobject->id." result=".$result."\n";
|
||||||
$this->assertLessThan($result, 0);
|
$this->assertLessThan($result, 0);
|
||||||
|
|
||||||
|
// Test everything is still the same as a freshly built specimen with the same mutation applied
|
||||||
|
// (catches unwanted field changes introduced by update()/valid())
|
||||||
|
$this->assertMatchesFreshSpecimen(
|
||||||
|
$localobject,
|
||||||
|
function ($specimen) {
|
||||||
|
$specimen->note_private = 'New note private after update';
|
||||||
|
},
|
||||||
|
array(
|
||||||
|
'newref', 'oldcopy', 'oldref', 'id', 'lines', 'line', 'client', 'thirdparty', 'brouillon', 'specimen',
|
||||||
|
'fk_user_author', 'user_author_id', 'user_creation_id', 'user_modification_id',
|
||||||
|
'date', 'date_commande', 'date_creation', 'date_validation', 'date_lim_reglement', 'datem', 'date_modification',
|
||||||
|
'ref', 'statut', 'status', 'socid', 'billed', 'fk_incoterms', 'actiontypecode', 'actionmsg2', 'actionmsg',
|
||||||
|
'mode_reglement', 'cond_reglement', 'mode_reglement_code', 'cond_reglement_code', 'availability_code', 'demand_reason_code',
|
||||||
|
'cond_reglement_doc', 'modelpdf',
|
||||||
|
// Totals are ignored here: specimen lines reference random real products, and a kit/BOM product can
|
||||||
|
// expand into extra lines with a different amount - total correctness is checked by assertLineTotalsMatchHeader() instead.
|
||||||
|
'total_ht', 'total_tva', 'total_ttc', 'total_localtax1', 'total_localtax2',
|
||||||
|
'multicurrency_total_ht', 'multicurrency_total_tva', 'multicurrency_total_ttc', 'fk_multicurrency', 'multicurrency_code', 'multicurrency_tx',
|
||||||
|
'trackid', 'user_creat', 'user_valid', 'note',
|
||||||
|
),
|
||||||
|
array('tosell' => 1)
|
||||||
|
);
|
||||||
|
|
||||||
return $localobject;
|
return $localobject;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -212,11 +345,6 @@ class CommandeTest extends CommonClassTest
|
||||||
$langs = $this->savlangs;
|
$langs = $this->savlangs;
|
||||||
$db = $this->savdb;
|
$db = $this->savdb;
|
||||||
|
|
||||||
/*$result=$localobject->setstatus(0);
|
|
||||||
print __METHOD__." id=".$localobject->id." result=".$result."\n";
|
|
||||||
$this->assertLessThan($result, 0);
|
|
||||||
*/
|
|
||||||
|
|
||||||
$localobject->info($localobject->id);
|
$localobject->info($localobject->id);
|
||||||
print __METHOD__." localobject->date_creation=".$localobject->date_creation."\n";
|
print __METHOD__." localobject->date_creation=".$localobject->date_creation."\n";
|
||||||
$this->assertNotEquals($localobject->date_creation, '');
|
$this->assertNotEquals($localobject->date_creation, '');
|
||||||
|
|
|
||||||
|
|
@ -382,6 +382,56 @@ abstract class CommonClassTest extends TestCase
|
||||||
return $retAr;
|
return $retAr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assert that the sum of the persisted line totals matches the object header totals.
|
||||||
|
* Catches bugs where update_price() forgets a line, or a total is not recalculated after a line change.
|
||||||
|
*
|
||||||
|
* @param CommonObject $localobject Object with a ->lines array of line objects having total_ht/total_tva/total_ttc
|
||||||
|
* @param string $message Extra message to show on failure
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
protected function assertLineTotalsMatchHeader($localobject, $message = '')
|
||||||
|
{
|
||||||
|
$sumht = 0.0;
|
||||||
|
$sumtva = 0.0;
|
||||||
|
$sumttc = 0.0;
|
||||||
|
foreach ($localobject->lines as $line) {
|
||||||
|
$sumht += (float) $line->total_ht;
|
||||||
|
$sumtva += (float) $line->total_tva;
|
||||||
|
$sumttc += (float) $line->total_ttc;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->assertEqualsWithDelta($sumht, (float) $localobject->total_ht, 0.01, 'total_ht does not match sum of lines. '.$message);
|
||||||
|
$this->assertEqualsWithDelta($sumtva, (float) $localobject->total_tva, 0.01, 'total_tva does not match sum of lines. '.$message);
|
||||||
|
$this->assertEqualsWithDelta($sumttc, (float) $localobject->total_ttc, 0.01, 'total_ttc does not match sum of lines. '.$message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compare $localobject against a freshly built specimen of the same class (with the same mutation applied)
|
||||||
|
* to detect fields unexpectedly changed by a lifecycle action such as update() or valid().
|
||||||
|
*
|
||||||
|
* @param object $localobject Object to check, already gone through create()/update()/valid()...
|
||||||
|
* @param callable $mutate Callback(object $specimen): void applying the same mutation that was applied to $localobject
|
||||||
|
* @param array<int|string> $fieldstoignorearray Fields to ignore in the comparison (passed to objCompare)
|
||||||
|
* @param array<mixed> $specimenparam Param array passed to initAsSpecimen()
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
protected function assertMatchesFreshSpecimen($localobject, callable $mutate, array $fieldstoignorearray, array $specimenparam = array())
|
||||||
|
{
|
||||||
|
global $db;
|
||||||
|
|
||||||
|
$class = get_class($localobject);
|
||||||
|
$newlocalobject = new $class($db);
|
||||||
|
$newlocalobject->initAsSpecimen($specimenparam);
|
||||||
|
$mutate($newlocalobject);
|
||||||
|
|
||||||
|
$clonedobject = clone $localobject;
|
||||||
|
unset($clonedobject->array_options);
|
||||||
|
|
||||||
|
$arraywithdiff = $this->objCompare($clonedobject, $newlocalobject, true, $fieldstoignorearray);
|
||||||
|
$this->assertEquals(array(), $arraywithdiff, 'Found differences '.var_export($arraywithdiff, true));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Map deprecated module names to new module names
|
* Map deprecated module names to new module names
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<?php
|
<?php
|
||||||
/* Copyright (C) 2010 Laurent Destailleur <eldy@users.sourceforge.net>
|
/* Copyright (C) 2010 Laurent Destailleur <eldy@users.sourceforge.net>
|
||||||
* Copyright (C) 2018-2025 Frédéric France <frederic.france@free.fr>
|
* Copyright (C) 2018-2026 Frédéric France <frederic.france@free.fr>
|
||||||
* Copyright (C) 2023 Alexandre Janniaux <alexandre.janniaux@gmail.com>
|
* Copyright (C) 2023 Alexandre Janniaux <alexandre.janniaux@gmail.com>
|
||||||
* Copyright (C) 2024 MDW <mdeweerd@users.noreply.github.com>
|
* Copyright (C) 2024 MDW <mdeweerd@users.noreply.github.com>
|
||||||
*
|
*
|
||||||
|
|
@ -112,6 +112,12 @@ class FactureTest extends CommonClassTest
|
||||||
|
|
||||||
$this->assertLessThan($result, 0);
|
$this->assertLessThan($result, 0);
|
||||||
print __METHOD__." id=".$id." result=".$result."\n";
|
print __METHOD__." id=".$id." result=".$result."\n";
|
||||||
|
|
||||||
|
// Specimen lines are built from real products picked at random (see Facture::initAsSpecimen), so the
|
||||||
|
// exact line count is not stable (a kit/BOM product can expand into extra lines) - only check totals coherence.
|
||||||
|
$this->assertNotEmpty($localobject->lines);
|
||||||
|
$this->assertLineTotalsMatchHeader($localobject, 'after fetch');
|
||||||
|
|
||||||
return $localobject;
|
return $localobject;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -140,13 +146,116 @@ class FactureTest extends CommonClassTest
|
||||||
return $localobject;
|
return $localobject;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* testFactureAddLine
|
||||||
|
*
|
||||||
|
* @param Facture $localobject Invoice
|
||||||
|
* @return array{0:Facture,1:int} Invoice and id of the line added
|
||||||
|
*
|
||||||
|
* @depends testFactureUpdate
|
||||||
|
* The depends says test is run only if previous is ok
|
||||||
|
*/
|
||||||
|
public function testFactureAddLine($localobject)
|
||||||
|
{
|
||||||
|
global $conf,$user,$langs,$db;
|
||||||
|
$conf = $this->savconf;
|
||||||
|
$user = $this->savuser;
|
||||||
|
$langs = $this->savlangs;
|
||||||
|
$db = $this->savdb;
|
||||||
|
|
||||||
|
$localobject->fetch_thirdparty();
|
||||||
|
$beforelinecount = count($localobject->lines);
|
||||||
|
$beforetotalht = (float) $localobject->total_ht;
|
||||||
|
|
||||||
|
$lineid = $localobject->addline('PHPUnit addline test', 100, 2, 20); // 2 x 100 HT at 20% VAT = 200 HT / 40 VAT / 240 TTC
|
||||||
|
|
||||||
|
print __METHOD__." id=".$localobject->id." lineid=".$lineid."\n";
|
||||||
|
$this->assertGreaterThan(0, $lineid, $localobject->errorsToString());
|
||||||
|
|
||||||
|
$localobject->fetch($localobject->id);
|
||||||
|
$this->assertCount($beforelinecount + 1, $localobject->lines);
|
||||||
|
$this->assertEqualsWithDelta($beforetotalht + 200, (float) $localobject->total_ht, 0.01, 'total_ht not updated after addline');
|
||||||
|
$this->assertLineTotalsMatchHeader($localobject, 'after addline');
|
||||||
|
|
||||||
|
return array($localobject, $lineid);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* testFactureUpdateLine
|
||||||
|
*
|
||||||
|
* @param array{0:Facture,1:int} $params Invoice and id of the line to update
|
||||||
|
* @return array{0:Facture,1:int} Invoice and id of the line updated
|
||||||
|
*
|
||||||
|
* @depends testFactureAddLine
|
||||||
|
* The depends says test is run only if previous is ok
|
||||||
|
*/
|
||||||
|
public function testFactureUpdateLine($params)
|
||||||
|
{
|
||||||
|
global $conf,$user,$langs,$db;
|
||||||
|
$conf = $this->savconf;
|
||||||
|
$user = $this->savuser;
|
||||||
|
$langs = $this->savlangs;
|
||||||
|
$db = $this->savdb;
|
||||||
|
|
||||||
|
list($localobject, $lineid) = $params;
|
||||||
|
$beforelinecount = count($localobject->lines);
|
||||||
|
$beforetotalht = (float) $localobject->total_ht;
|
||||||
|
|
||||||
|
$result = $localobject->updateline($lineid, 'PHPUnit addline test', 100, 3, 0, '', '', 20); // qty 2 -> 3, so +100 HT / +20 VAT / +120 TTC
|
||||||
|
|
||||||
|
print __METHOD__." id=".$localobject->id." lineid=".$lineid." result=".$result."\n";
|
||||||
|
$this->assertGreaterThan(0, $result, $localobject->errorsToString());
|
||||||
|
|
||||||
|
$localobject->fetch($localobject->id);
|
||||||
|
$this->assertCount($beforelinecount, $localobject->lines);
|
||||||
|
$this->assertEqualsWithDelta($beforetotalht + 100, (float) $localobject->total_ht, 0.01, 'total_ht not updated after updateline');
|
||||||
|
$this->assertLineTotalsMatchHeader($localobject, 'after updateline');
|
||||||
|
|
||||||
|
return array($localobject, $lineid);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* testFactureDeleteLine
|
||||||
|
*
|
||||||
|
* @param array{0:Facture,1:int} $params Invoice and id of the line to delete
|
||||||
|
* @return Facture
|
||||||
|
*
|
||||||
|
* @depends testFactureUpdateLine
|
||||||
|
* The depends says test is run only if previous is ok
|
||||||
|
*/
|
||||||
|
public function testFactureDeleteLine($params)
|
||||||
|
{
|
||||||
|
global $conf,$user,$langs,$db;
|
||||||
|
$conf = $this->savconf;
|
||||||
|
$user = $this->savuser;
|
||||||
|
$langs = $this->savlangs;
|
||||||
|
$db = $this->savdb;
|
||||||
|
|
||||||
|
list($localobject, $lineid) = $params;
|
||||||
|
$beforelinecount = count($localobject->lines);
|
||||||
|
$beforetotalht = (float) $localobject->total_ht;
|
||||||
|
|
||||||
|
$result = $localobject->deleteLine($lineid);
|
||||||
|
|
||||||
|
print __METHOD__." id=".$localobject->id." lineid=".$lineid." result=".$result."\n";
|
||||||
|
$this->assertGreaterThan(0, $result, $localobject->errorsToString());
|
||||||
|
|
||||||
|
$localobject->fetch($localobject->id);
|
||||||
|
// Back to the original specimen lines, with the same totals
|
||||||
|
$this->assertCount($beforelinecount - 1, $localobject->lines);
|
||||||
|
$this->assertEqualsWithDelta($beforetotalht - 300, (float) $localobject->total_ht, 0.01, 'total_ht not updated after deleteLine');
|
||||||
|
$this->assertLineTotalsMatchHeader($localobject, 'after deleteLine');
|
||||||
|
|
||||||
|
return $localobject;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* testFactureValid
|
* testFactureValid
|
||||||
*
|
*
|
||||||
* @param Facture $localobject Invoice
|
* @param Facture $localobject Invoice
|
||||||
* @return void
|
* @return Facture
|
||||||
*
|
*
|
||||||
* @depends testFactureUpdate
|
* @depends testFactureDeleteLine
|
||||||
* The depends says test is run only if previous is ok
|
* The depends says test is run only if previous is ok
|
||||||
*/
|
*/
|
||||||
public function testFactureValid($localobject)
|
public function testFactureValid($localobject)
|
||||||
|
|
@ -166,33 +275,26 @@ class FactureTest extends CommonClassTest
|
||||||
|
|
||||||
$this->assertLessThan($result, 0);
|
$this->assertLessThan($result, 0);
|
||||||
|
|
||||||
// Test everything is still the same as specimen
|
// Test everything is still the same as a freshly built specimen with the same mutation applied
|
||||||
$newlocalobject = new Facture($db);
|
// (catches unwanted field changes introduced by update()/validate())
|
||||||
$newlocalobject->initAsSpecimen();
|
$this->assertMatchesFreshSpecimen(
|
||||||
$this->changeProperties($newlocalobject);
|
|
||||||
|
|
||||||
// Hack to avoid test to be wrong when some modules are one
|
|
||||||
unset($localobject->array_options);
|
|
||||||
//unset($localobject->array_options['options_reseller']);
|
|
||||||
//unset($localobject->array_options['options_reseller']);
|
|
||||||
|
|
||||||
$arraywithdiff = $this->objCompare(
|
|
||||||
$localobject,
|
$localobject,
|
||||||
$newlocalobject,
|
function ($specimen) {
|
||||||
true,
|
$this->changeProperties($specimen);
|
||||||
// Not comparing:
|
},
|
||||||
array(
|
array(
|
||||||
'newref','oldcopy','oldref','id','lines','client','thirdparty','brouillon', 'fk_user_author', 'user_modification_id', 'date_creation','date_validation','datem','date_modification',
|
'newref', 'oldcopy', 'oldref', 'id', 'lines', 'line', 'client', 'thirdparty', 'brouillon', 'fk_user_author', 'fk_user_modif', 'user_modification_id', 'date_creation', 'date_validation', 'datem', 'date_modification',
|
||||||
'ref','statut','status','paye','ref','actiontypecode','actionmsg2','actionmsg','mode_reglement','cond_reglement',
|
'ref', 'statut', 'status', 'paye', 'ref', 'actiontypecode', 'actionmsg2', 'actionmsg', 'mode_reglement', 'cond_reglement',
|
||||||
'cond_reglement_doc', 'modelpdf',
|
'cond_reglement_doc', 'modelpdf',
|
||||||
'multicurrency_total_ht','multicurrency_total_tva', 'multicurrency_total_ttc','fk_multicurrency','multicurrency_code','multicurrency_tx',
|
// Totals are ignored here: specimen lines reference random real products, and a kit/BOM product can
|
||||||
'retained_warranty' ,'retained_warranty_date_limit', 'retained_warranty_fk_cond_reglement', 'specimen', 'situation_cycle_ref', 'situation_counter', 'situation_final',
|
// expand into extra lines with a different amount - total correctness is checked by assertLineTotalsMatchHeader() instead.
|
||||||
'trackid','user_creat','user_valid', 'note'
|
'total_ht', 'total_tva', 'total_ttc',
|
||||||
|
'multicurrency_total_ht', 'multicurrency_total_tva', 'multicurrency_total_ttc', 'fk_multicurrency', 'multicurrency_code', 'multicurrency_tx',
|
||||||
|
'retained_warranty', 'retained_warranty_date_limit', 'retained_warranty_fk_cond_reglement', 'specimen', 'situation_cycle_ref', 'situation_counter', 'situation_final',
|
||||||
|
'trackid', 'user_creat', 'user_valid', 'note'
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
$this->assertEquals($arraywithdiff, array(), 'Found differences '.var_export($arraywithdiff, true)); // Actual, Expected. If it differs, do a var_dump($arraywithdiff) to see what differs
|
|
||||||
|
|
||||||
return $localobject;
|
return $localobject;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -213,11 +315,6 @@ class FactureTest extends CommonClassTest
|
||||||
$langs = $this->savlangs;
|
$langs = $this->savlangs;
|
||||||
$db = $this->savdb;
|
$db = $this->savdb;
|
||||||
|
|
||||||
/*$result=$localobject->setstatus(0);
|
|
||||||
print __METHOD__." id=".$localobject->id." result=".$result."\n";
|
|
||||||
$this->assertLessThan($result, 0);
|
|
||||||
*/
|
|
||||||
|
|
||||||
$localobject->info($localobject->id);
|
$localobject->info($localobject->id);
|
||||||
print __METHOD__." localobject->date_creation=".$localobject->date_creation."\n";
|
print __METHOD__." localobject->date_creation=".$localobject->date_creation."\n";
|
||||||
$this->assertNotEquals($localobject->date_creation, '');
|
$this->assertNotEquals($localobject->date_creation, '');
|
||||||
|
|
|
||||||
222
test/phpunit/PropalCommandeFactureWorkflowTest.php
Normal file
222
test/phpunit/PropalCommandeFactureWorkflowTest.php
Normal file
|
|
@ -0,0 +1,222 @@
|
||||||
|
<?php
|
||||||
|
/* Copyright (C) 2026 Frédéric France <frederic.france@free.fr>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
* or see https://www.gnu.org/
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \file test/phpunit/PropalCommandeFactureWorkflowTest.php
|
||||||
|
* \ingroup test
|
||||||
|
* \brief PHPUnit test of the Propal -> Commande -> Facture business workflow
|
||||||
|
* \remarks To run this script as CLI: phpunit filename.php
|
||||||
|
*/
|
||||||
|
|
||||||
|
global $conf,$user,$langs,$db;
|
||||||
|
require_once dirname(__FILE__).'/../../htdocs/master.inc.php';
|
||||||
|
require_once dirname(__FILE__).'/../../htdocs/comm/propal/class/propal.class.php';
|
||||||
|
require_once dirname(__FILE__).'/../../htdocs/commande/class/commande.class.php';
|
||||||
|
require_once dirname(__FILE__).'/../../htdocs/compta/facture/class/facture.class.php';
|
||||||
|
require_once dirname(__FILE__).'/CommonClassTest.class.php';
|
||||||
|
|
||||||
|
if (empty($user->id)) {
|
||||||
|
print "Load permissions for admin user nb 1\n";
|
||||||
|
$user->fetch(1);
|
||||||
|
$user->loadRights();
|
||||||
|
}
|
||||||
|
$conf->global->MAIN_DISABLE_ALL_MAILS = 1;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests that a customer proposal, once converted into an order and then into an invoice,
|
||||||
|
* carries its thirdparty, lines, totals and notes correctly through the chain, and that
|
||||||
|
* each object is linked back to the one it was created from.
|
||||||
|
*
|
||||||
|
* @backupGlobals disabled
|
||||||
|
* @backupStaticAttributes enabled
|
||||||
|
* @remarks backupGlobals must be disabled to have db,conf,user and lang not erased.
|
||||||
|
*/
|
||||||
|
class PropalCommandeFactureWorkflowTest extends CommonClassTest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* setUpBeforeClass
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public static function setUpBeforeClass(): void
|
||||||
|
{
|
||||||
|
self::assertTrue(isModEnabled('propal'), 'module commercial proposal must be enabled');
|
||||||
|
self::assertTrue(isModEnabled('order'), 'module customer order must be enabled');
|
||||||
|
self::assertTrue(isModEnabled('invoice'), 'module customer invoice must be enabled');
|
||||||
|
parent::setUpBeforeClass();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* testWorkflowCreateAndValidatePropal
|
||||||
|
*
|
||||||
|
* Entry point of the chain: create a thirdparty and a proposal for it, then validate the proposal.
|
||||||
|
*
|
||||||
|
* @return Propal
|
||||||
|
*/
|
||||||
|
public function testWorkflowCreateAndValidatePropal()
|
||||||
|
{
|
||||||
|
global $conf,$user,$langs,$db;
|
||||||
|
$conf = $this->savconf;
|
||||||
|
$user = $this->savuser;
|
||||||
|
$langs = $this->savlangs;
|
||||||
|
$db = $this->savdb;
|
||||||
|
|
||||||
|
$soc = new Societe($db);
|
||||||
|
$soc->name = 'PropalCommandeFactureWorkflowTest Unittest';
|
||||||
|
$socid = $soc->create($user);
|
||||||
|
$this->assertGreaterThan(0, $socid, $soc->errorsToString());
|
||||||
|
|
||||||
|
// Use initAsSpecimen() only for realistic header defaults (payment terms, availability, ...).
|
||||||
|
// Its lines reference random real products, which can be kits/BOMs that silently expand into extra
|
||||||
|
// lines on each create()/createFrom...() down the chain: replace them with controlled, product-less
|
||||||
|
// lines so the totals asserted at each step of the chain are deterministic.
|
||||||
|
$propal = new Propal($db);
|
||||||
|
$propal->initAsSpecimen(array('tosell' => 1));
|
||||||
|
$propal->lines = array();
|
||||||
|
$propal->total_ht = 0;
|
||||||
|
$propal->total_tva = 0;
|
||||||
|
$propal->total_ttc = 0;
|
||||||
|
$propal->socid = $socid;
|
||||||
|
$propal->note_public = 'PHPUnit workflow test note (public)';
|
||||||
|
$propal->note_private = 'PHPUnit workflow test note (private)';
|
||||||
|
$result = $propal->create($user);
|
||||||
|
$this->assertGreaterThan(0, $result, $propal->errorsToString());
|
||||||
|
|
||||||
|
$lineid1 = $propal->addline('Workflow line A', 100, 2, 20); // 200 HT / 40 VAT / 240 TTC
|
||||||
|
$this->assertGreaterThan(0, $lineid1, $propal->errorsToString());
|
||||||
|
$lineid2 = $propal->addline('Workflow line B', 50, 3, 10); // 150 HT / 15 VAT / 165 TTC
|
||||||
|
$this->assertGreaterThan(0, $lineid2, $propal->errorsToString());
|
||||||
|
|
||||||
|
$propal->fetch($propal->id);
|
||||||
|
$this->assertEqualsWithDelta(350.0, (float) $propal->total_ht, 0.01, 'total_ht of the 2 controlled lines');
|
||||||
|
|
||||||
|
$result = $propal->valid($user);
|
||||||
|
$this->assertGreaterThan(0, $result, $propal->errorsToString());
|
||||||
|
|
||||||
|
$propal->fetch($propal->id);
|
||||||
|
$this->assertEquals(Propal::STATUS_VALIDATED, $propal->status);
|
||||||
|
$this->assertLineTotalsMatchHeader($propal, 'on validated proposal');
|
||||||
|
|
||||||
|
return $propal;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* testWorkflowCreateOrderFromPropal
|
||||||
|
*
|
||||||
|
* @param Propal $propal Proposal validated by the previous test
|
||||||
|
* @return Commande
|
||||||
|
*
|
||||||
|
* @depends testWorkflowCreateAndValidatePropal
|
||||||
|
* The depends says test is run only if previous is ok
|
||||||
|
*/
|
||||||
|
public function testWorkflowCreateOrderFromPropal($propal)
|
||||||
|
{
|
||||||
|
global $conf,$user,$langs,$db;
|
||||||
|
$conf = $this->savconf;
|
||||||
|
$user = $this->savuser;
|
||||||
|
$langs = $this->savlangs;
|
||||||
|
$db = $this->savdb;
|
||||||
|
|
||||||
|
$commande = new Commande($db);
|
||||||
|
$result = $commande->createFromProposal($propal, $user);
|
||||||
|
|
||||||
|
print __METHOD__." propalid=".$propal->id." result=".$result."\n";
|
||||||
|
$this->assertGreaterThan(0, $result, $commande->errorsToString());
|
||||||
|
|
||||||
|
$commande->fetch($result);
|
||||||
|
|
||||||
|
$this->assertEquals($propal->socid, $commande->socid, 'Order thirdparty must match proposal thirdparty');
|
||||||
|
$this->assertEquals($propal->note_public, $commande->note_public, 'Order public note must be propagated from proposal');
|
||||||
|
$this->assertEquals($propal->note_private, $commande->note_private, 'Order private note must be propagated from proposal');
|
||||||
|
$this->assertCount(count($propal->lines), $commande->lines, 'Order must have the same number of lines as the proposal');
|
||||||
|
$this->assertEqualsWithDelta((float) $propal->total_ht, (float) $commande->total_ht, 0.01, 'Order total_ht must match proposal total_ht');
|
||||||
|
$this->assertEqualsWithDelta((float) $propal->total_tva, (float) $commande->total_tva, 0.01, 'Order total_tva must match proposal total_tva');
|
||||||
|
$this->assertEqualsWithDelta((float) $propal->total_ttc, (float) $commande->total_ttc, 0.01, 'Order total_ttc must match proposal total_ttc');
|
||||||
|
$this->assertLineTotalsMatchHeader($commande, 'on order created from proposal');
|
||||||
|
|
||||||
|
// The order must be linked back to its originating proposal (llx_element_element)
|
||||||
|
$commande->fetchObjectLinked();
|
||||||
|
$this->assertArrayHasKey('propal', $commande->linkedObjectsIds, 'Order must be linked to its originating proposal');
|
||||||
|
$this->assertContains($propal->id, $commande->linkedObjectsIds['propal']);
|
||||||
|
|
||||||
|
$result = $commande->valid($user);
|
||||||
|
print __METHOD__." id=".$commande->id." valid result=".$result."\n";
|
||||||
|
$this->assertGreaterThan(0, $result, $commande->errorsToString());
|
||||||
|
|
||||||
|
$commande->fetch($commande->id);
|
||||||
|
$this->assertEquals(Commande::STATUS_VALIDATED, $commande->status);
|
||||||
|
|
||||||
|
return $commande;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* testWorkflowCreateInvoiceFromOrder
|
||||||
|
*
|
||||||
|
* @param Commande $commande Order validated by the previous test
|
||||||
|
* @return Facture
|
||||||
|
*
|
||||||
|
* @depends testWorkflowCreateOrderFromPropal
|
||||||
|
* The depends says test is run only if previous is ok
|
||||||
|
*/
|
||||||
|
public function testWorkflowCreateInvoiceFromOrder($commande)
|
||||||
|
{
|
||||||
|
global $conf,$user,$langs,$db;
|
||||||
|
$conf = $this->savconf;
|
||||||
|
$user = $this->savuser;
|
||||||
|
$langs = $this->savlangs;
|
||||||
|
$db = $this->savdb;
|
||||||
|
|
||||||
|
$facture = new Facture($db);
|
||||||
|
$result = $facture->createFromOrder($commande, $user);
|
||||||
|
|
||||||
|
// createFromOrder() returns a status flag (1/-1), not the new invoice id (unlike
|
||||||
|
// Commande::createFromProposal()) - the created invoice's id must be read from $facture->id.
|
||||||
|
print __METHOD__." commandeid=".$commande->id." result=".$result."\n";
|
||||||
|
$this->assertGreaterThan(0, $result, $facture->errorsToString());
|
||||||
|
|
||||||
|
$facture->fetch($facture->id);
|
||||||
|
|
||||||
|
$this->assertEquals($commande->socid, $facture->socid, 'Invoice thirdparty must match order thirdparty');
|
||||||
|
$this->assertEquals($commande->note_public, $facture->note_public, 'Invoice public note must be propagated from order');
|
||||||
|
$this->assertEquals($commande->note_private, $facture->note_private, 'Invoice private note must be propagated from order');
|
||||||
|
$this->assertCount(count($commande->lines), $facture->lines, 'Invoice must have the same number of lines as the order');
|
||||||
|
$this->assertEqualsWithDelta((float) $commande->total_ht, (float) $facture->total_ht, 0.01, 'Invoice total_ht must match order total_ht');
|
||||||
|
$this->assertEqualsWithDelta((float) $commande->total_tva, (float) $facture->total_tva, 0.01, 'Invoice total_tva must match order total_tva');
|
||||||
|
$this->assertEqualsWithDelta((float) $commande->total_ttc, (float) $facture->total_ttc, 0.01, 'Invoice total_ttc must match order total_ttc');
|
||||||
|
$this->assertLineTotalsMatchHeader($facture, 'on invoice created from order');
|
||||||
|
|
||||||
|
// The invoice must be linked back to its originating order (llx_element_element)
|
||||||
|
$facture->fetchObjectLinked();
|
||||||
|
$this->assertArrayHasKey('commande', $facture->linkedObjectsIds, 'Invoice must be linked to its originating order');
|
||||||
|
$this->assertContains($commande->id, $facture->linkedObjectsIds['commande']);
|
||||||
|
|
||||||
|
// Force to default setup, same as FactureTest::testFactureValid
|
||||||
|
$conf->global->FAC_FORCE_DATE_VALIDATION = 0;
|
||||||
|
$conf->global->INVOICE_CHECK_POSTERIOR_DATE = 0;
|
||||||
|
|
||||||
|
$result = $facture->validate($user);
|
||||||
|
print __METHOD__." id=".$facture->id." validate result=".$result."\n";
|
||||||
|
$this->assertGreaterThan(0, $result, $facture->errorsToString());
|
||||||
|
|
||||||
|
$facture->fetch($facture->id);
|
||||||
|
$this->assertEquals(Facture::STATUS_VALIDATED, $facture->status);
|
||||||
|
|
||||||
|
return $facture;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<?php
|
<?php
|
||||||
/* Copyright (C) 2010 Laurent Destailleur <eldy@users.sourceforge.net>
|
/* Copyright (C) 2010 Laurent Destailleur <eldy@users.sourceforge.net>
|
||||||
* Copyright (C) 2023 Alexandre Janniaux <alexandre.janniaux@gmail.com>
|
* Copyright (C) 2023 Alexandre Janniaux <alexandre.janniaux@gmail.com>
|
||||||
* Copyright (C) 2024 Frédéric France <frederic.france@free.fr>
|
* Copyright (C) 2024-2026 Frédéric France <frederic.france@free.fr>
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify
|
* This program is free software; you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
|
@ -94,6 +94,12 @@ class PropalTest extends CommonClassTest
|
||||||
|
|
||||||
$this->assertLessThan($result, 0);
|
$this->assertLessThan($result, 0);
|
||||||
print __METHOD__." id=".$id." result=".$result."\n";
|
print __METHOD__." id=".$id." result=".$result."\n";
|
||||||
|
|
||||||
|
// Specimen lines are built from real products picked at random (see Propal::initAsSpecimen), so the
|
||||||
|
// exact line count is not stable (a kit/BOM product can expand into extra lines) - only check totals coherence.
|
||||||
|
$this->assertNotEmpty($localobject->lines);
|
||||||
|
$this->assertLineTotalsMatchHeader($localobject, 'after fetch');
|
||||||
|
|
||||||
return $localobject;
|
return $localobject;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -125,8 +131,8 @@ class PropalTest extends CommonClassTest
|
||||||
/**
|
/**
|
||||||
* testPropalAddLine
|
* testPropalAddLine
|
||||||
*
|
*
|
||||||
* @param Propal $localobject Proposal
|
* @param Propal $localobject Proposal
|
||||||
* @return Propal
|
* @return array{0:Propal,1:int} Proposal and id of the line added
|
||||||
*
|
*
|
||||||
* @depends testPropalUpdate
|
* @depends testPropalUpdate
|
||||||
* The depends says test is run only if previous is ok
|
* The depends says test is run only if previous is ok
|
||||||
|
|
@ -140,10 +146,88 @@ class PropalTest extends CommonClassTest
|
||||||
$db = $this->savdb;
|
$db = $this->savdb;
|
||||||
|
|
||||||
$localobject->fetch_thirdparty();
|
$localobject->fetch_thirdparty();
|
||||||
$result = $localobject->addline('Added line', 10, 2, 19.6);
|
$beforelinecount = count($localobject->lines);
|
||||||
|
$beforetotalht = (float) $localobject->total_ht;
|
||||||
|
|
||||||
|
$lineid = $localobject->addline('PHPUnit addline test', 100, 2, 20); // 2 x 100 HT at 20% VAT = 200 HT / 40 VAT / 240 TTC
|
||||||
|
|
||||||
|
print __METHOD__." id=".$localobject->id." lineid=".$lineid."\n";
|
||||||
|
$this->assertGreaterThan(0, $lineid, $localobject->errorsToString());
|
||||||
|
|
||||||
|
$localobject->fetch($localobject->id);
|
||||||
|
$this->assertCount($beforelinecount + 1, $localobject->lines);
|
||||||
|
$this->assertEqualsWithDelta($beforetotalht + 200, (float) $localobject->total_ht, 0.01, 'total_ht not updated after addline');
|
||||||
|
$this->assertLineTotalsMatchHeader($localobject, 'after addline');
|
||||||
|
|
||||||
|
return array($localobject, $lineid);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* testPropalUpdateLine
|
||||||
|
*
|
||||||
|
* @param array{0:Propal,1:int} $params Proposal and id of the line to update
|
||||||
|
* @return array{0:Propal,1:int} Proposal and id of the line updated
|
||||||
|
*
|
||||||
|
* @depends testPropalAddLine
|
||||||
|
* The depends says test is run only if previous is ok
|
||||||
|
*/
|
||||||
|
public function testPropalUpdateLine($params)
|
||||||
|
{
|
||||||
|
global $conf,$user,$langs,$db;
|
||||||
|
$conf = $this->savconf;
|
||||||
|
$user = $this->savuser;
|
||||||
|
$langs = $this->savlangs;
|
||||||
|
$db = $this->savdb;
|
||||||
|
|
||||||
|
list($localobject, $lineid) = $params;
|
||||||
|
$beforelinecount = count($localobject->lines);
|
||||||
|
$beforetotalht = (float) $localobject->total_ht;
|
||||||
|
|
||||||
|
$result = $localobject->updateline($lineid, 100, 3, 0, 20, 0, 0, 'PHPUnit addline test'); // qty 2 -> 3, so +100 HT / +20 VAT / +120 TTC
|
||||||
|
|
||||||
|
print __METHOD__." id=".$localobject->id." lineid=".$lineid." result=".$result."\n";
|
||||||
|
$this->assertGreaterThan(0, $result, $localobject->errorsToString());
|
||||||
|
|
||||||
|
$localobject->fetch($localobject->id);
|
||||||
|
$this->assertCount($beforelinecount, $localobject->lines);
|
||||||
|
$this->assertEqualsWithDelta($beforetotalht + 100, (float) $localobject->total_ht, 0.01, 'total_ht not updated after updateline');
|
||||||
|
$this->assertLineTotalsMatchHeader($localobject, 'after updateline');
|
||||||
|
|
||||||
|
return array($localobject, $lineid);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* testPropalDeleteLine
|
||||||
|
*
|
||||||
|
* @param array{0:Propal,1:int} $params Proposal and id of the line to delete
|
||||||
|
* @return Propal
|
||||||
|
*
|
||||||
|
* @depends testPropalUpdateLine
|
||||||
|
* The depends says test is run only if previous is ok
|
||||||
|
*/
|
||||||
|
public function testPropalDeleteLine($params)
|
||||||
|
{
|
||||||
|
global $conf,$user,$langs,$db;
|
||||||
|
$conf = $this->savconf;
|
||||||
|
$user = $this->savuser;
|
||||||
|
$langs = $this->savlangs;
|
||||||
|
$db = $this->savdb;
|
||||||
|
|
||||||
|
list($localobject, $lineid) = $params;
|
||||||
|
$beforelinecount = count($localobject->lines);
|
||||||
|
$beforetotalht = (float) $localobject->total_ht;
|
||||||
|
|
||||||
|
$result = $localobject->deleteLine($lineid);
|
||||||
|
|
||||||
|
print __METHOD__." id=".$localobject->id." lineid=".$lineid." result=".$result."\n";
|
||||||
|
$this->assertGreaterThan(0, $result, $localobject->errorsToString());
|
||||||
|
|
||||||
|
$localobject->fetch($localobject->id);
|
||||||
|
// Back to the original specimen lines, with the same totals
|
||||||
|
$this->assertCount($beforelinecount - 1, $localobject->lines);
|
||||||
|
$this->assertEqualsWithDelta($beforetotalht - 300, (float) $localobject->total_ht, 0.01, 'total_ht not updated after deleteLine');
|
||||||
|
$this->assertLineTotalsMatchHeader($localobject, 'after deleteLine');
|
||||||
|
|
||||||
$this->assertLessThan($result, 0);
|
|
||||||
print __METHOD__." id=".$localobject->id." result=".$result."\n";
|
|
||||||
return $localobject;
|
return $localobject;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -153,7 +237,7 @@ class PropalTest extends CommonClassTest
|
||||||
* @param Propal $localobject Proposal
|
* @param Propal $localobject Proposal
|
||||||
* @return Propal
|
* @return Propal
|
||||||
*
|
*
|
||||||
* @depends testPropalAddLine
|
* @depends testPropalDeleteLine
|
||||||
* The depends says test is run only if previous is ok
|
* The depends says test is run only if previous is ok
|
||||||
*/
|
*/
|
||||||
public function testPropalValid($localobject)
|
public function testPropalValid($localobject)
|
||||||
|
|
@ -168,6 +252,29 @@ class PropalTest extends CommonClassTest
|
||||||
|
|
||||||
print __METHOD__." id=".$localobject->id." result=".$result."\n";
|
print __METHOD__." id=".$localobject->id." result=".$result."\n";
|
||||||
$this->assertLessThan($result, 0);
|
$this->assertLessThan($result, 0);
|
||||||
|
|
||||||
|
// Test everything is still the same as a freshly built specimen with the same mutation applied
|
||||||
|
// (catches unwanted field changes introduced by update()/valid())
|
||||||
|
$this->assertMatchesFreshSpecimen(
|
||||||
|
$localobject,
|
||||||
|
function ($specimen) {
|
||||||
|
$specimen->note_private = 'New note private after update';
|
||||||
|
},
|
||||||
|
array(
|
||||||
|
'newref', 'oldcopy', 'oldref', 'id', 'entity', 'lines', 'line', 'client', 'thirdparty', 'brouillon', 'specimen',
|
||||||
|
'fk_user_author', 'user_author_id', 'user_creation_id', 'user_modification_id', 'user_validation_id',
|
||||||
|
'date', 'datec', 'datev', 'datep', 'date_creation', 'date_validation', 'date_lim_reglement', 'fin_validite', 'datem', 'date_modification',
|
||||||
|
'ref', 'statut', 'status', 'socid', 'billed', 'fk_incoterms', 'actiontypecode', 'actionmsg2', 'actionmsg',
|
||||||
|
'mode_reglement', 'cond_reglement', 'mode_reglement_code', 'cond_reglement_code', 'availability', 'availability_code', 'demand_reason', 'demand_reason_code',
|
||||||
|
'cond_reglement_doc', 'modelpdf', 'total',
|
||||||
|
// Totals are ignored here: specimen lines reference random real products, and a kit/BOM product can
|
||||||
|
// expand into extra lines with a different amount - total correctness is checked by assertLineTotalsMatchHeader() instead.
|
||||||
|
'total_ht', 'total_tva', 'total_ttc', 'total_localtax1', 'total_localtax2',
|
||||||
|
'multicurrency_total_ht', 'multicurrency_total_tva', 'multicurrency_total_ttc', 'fk_multicurrency', 'multicurrency_code', 'multicurrency_tx',
|
||||||
|
'trackid', 'user_creat', 'user_valid', 'note',
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
return $localobject;
|
return $localobject;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -188,11 +295,6 @@ class PropalTest extends CommonClassTest
|
||||||
$langs = $this->savlangs;
|
$langs = $this->savlangs;
|
||||||
$db = $this->savdb;
|
$db = $this->savdb;
|
||||||
|
|
||||||
/*$result=$localobject->setstatus(0);
|
|
||||||
print __METHOD__." id=".$localobject->id." result=".$result."\n";
|
|
||||||
$this->assertLessThan($result, 0);
|
|
||||||
*/
|
|
||||||
|
|
||||||
$localobject->info($localobject->id);
|
$localobject->info($localobject->id);
|
||||||
print __METHOD__." localobject->date_creation=".$localobject->date_creation."\n";
|
print __METHOD__." localobject->date_creation=".$localobject->date_creation."\n";
|
||||||
$this->assertNotEquals($localobject->date_creation, '');
|
$this->assertNotEquals($localobject->date_creation, '');
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue