From 463cd23cd057cd3f89666543bc71ca74cacff5a3 Mon Sep 17 00:00:00 2001 From: Thomas Zilio Date: Thu, 27 Mar 2025 10:55:53 +0100 Subject: [PATCH] Introduzione di nuovi unit test con mocking --- modules/fatture/src/Gestori/Scadenze.php | 60 ++++-- .../fatture/tests/GenerazioneScadenzeTest.php | 190 ++++++++++++++++++ modules/pagamenti/src/Pagamento.php | 14 +- package.json | 2 +- phpunit.xml | 16 ++ tests/GeneratorTest.php | 70 +++++++ 6 files changed, 332 insertions(+), 20 deletions(-) create mode 100644 modules/fatture/tests/GenerazioneScadenzeTest.php create mode 100644 phpunit.xml create mode 100644 tests/GeneratorTest.php diff --git a/modules/fatture/src/Gestori/Scadenze.php b/modules/fatture/src/Gestori/Scadenze.php index 481f7566aa..2394dadf2b 100644 --- a/modules/fatture/src/Gestori/Scadenze.php +++ b/modules/fatture/src/Gestori/Scadenze.php @@ -22,6 +22,7 @@ use Modules\Fatture\Fattura; use Modules\PrimaNota\Movimento; +use Modules\Pagamenti\Pagamento; use Modules\Scadenzario\Scadenza; use Plugins\AssicurazioneCrediti\AssicurazioneCrediti; use Plugins\ImportFE\FatturaElettronica as FatturaElettronicaImport; @@ -34,8 +35,9 @@ */ class Scadenze { - public function __construct(private readonly Fattura $fattura) + public function __construct(private readonly Fattura $fattura, $database = null) { + $this->database = $database ?: database(); // Allow mocking } /** @@ -50,11 +52,11 @@ public function registra($is_pagato = false, $ignora_fe = false) $this->rimuovi(); if (!$ignora_fe && $this->fattura->module == 'Fatture di acquisto' && $this->fattura->isFE()) { - $scadenze_fe = $this->registraScadenzeFE($is_pagato); + $scadenze = $this->registraScadenzeFE($is_pagato); } - if (empty($scadenze_fe)) { - $this->registraScadenzeTradizionali($is_pagato); + if (empty($scadenze)) { + $scadenze = $this->registraScadenzeTradizionali($is_pagato); } // Registrazione scadenza per Ritenuta d'Acconto @@ -79,8 +81,10 @@ public function registra($is_pagato = false, $ignora_fe = false) $id_banca_controparte = $this->fattura->id_banca_controparte; $importo = -$ritenuta_acconto; - self::registraScadenza($this->fattura, $importo, $scadenza, $is_pagato, $id_pagamento, $id_banca_azienda, $id_banca_controparte, 'ritenuta_acconto'); + $scadenze[] = self::registraScadenza($this->fattura, $importo, $scadenza, $is_pagato, $id_pagamento, $id_banca_azienda, $id_banca_controparte, 'ritenuta_acconto'); } + + return $scadenze; } /** @@ -96,7 +100,7 @@ public function rimuovi() $assicurazioni_map = []; if ($id_anagrafiche->isNotEmpty()) { - $assicurazioni_list = AssicurazioneCrediti::whereIn('id_anagrafica', $id_anagrafiche)->get(); + $assicurazioni_list = $this->trovaAssicurazioneCrediti($scadenza->id_anagrafica); // Crea una mappa per accesso rapido foreach ($assicurazioni_list as $assicurazione) { @@ -112,7 +116,7 @@ public function rimuovi() } } - database()->delete('co_scadenzario', ['id_documento' => $this->fattura->id]); + $this->database->delete('co_scadenzario', ['id_documento' => $this->fattura->id]); foreach ($assicurazioni as $assicurazione) { $assicurazione->fixTotale(); @@ -134,13 +138,13 @@ protected function registraScadenza(Fattura $fattura, $importo, $data_scadenza, $descrizione = $fattura->tipo->getTranslation('title').' numero '.$numero; $id_anagrafica = $fattura->id_anagrafica; - $scadenza = Scadenza::build($id_anagrafica, $descrizione, $importo, $data_scadenza, $id_pagamento, $id_banca_azienda, $id_banca_controparte, $type, $is_pagato, $fattura->id); + $scadenza = $this->generaScadenza($id_anagrafica, $descrizione, $importo, $data_scadenza, $id_pagamento, $id_banca_azienda, $id_banca_controparte, $type, $is_pagato, $fattura->id); $scadenza->data_emissione = $fattura->data; $scadenza->save(); // TODO: Considerare di passare le assicurazioni come parametro se chiamato da rimuovi() - $assicurazione_crediti = AssicurazioneCrediti::where('id_anagrafica', $scadenza->id_anagrafica)->where('data_inizio', '<=', $scadenza->scadenza)->where('data_fine', '>=', $scadenza->scadenza)->first(); + $assicurazione_crediti = $this->trovaAssicurazioneCreditiConScadenze($scadenza->id_anagrafica, $scadenza->scadenza); if (!empty($assicurazione_crediti)) { $assicurazione_crediti->fixTotale(); $assicurazione_crediti->save(); @@ -148,13 +152,13 @@ protected function registraScadenza(Fattura $fattura, $importo, $data_scadenza, // Pagamento automatico se scadenza = data fattura e flag attivo if (!$is_pagato && $scadenza->scadenza->format('Y-m-d') <= date('Y-m-d') && $importo) { - $pagamento = \Modules\Pagamenti\Pagamento::find($id_pagamento); + $pagamento = $this->trovaPagamento($id_pagamento); if (!empty($pagamento) && $pagamento->registra_pagamento_automatico) { $importo_da_registrare = abs($importo); $dir = $fattura->tipo->dir; // Recupero conto anagrafica - $id_conto_anagrafica = database()->selectOne('an_anagrafiche', $dir == 'entrata' ? 'id_conto_cliente' : 'id_conto_fornitore', ['id_anagrafica' => $id_anagrafica]); + $id_conto_anagrafica = $this->database->selectOne('an_anagrafiche', $dir == 'entrata' ? 'id_conto_cliente' : 'id_conto_fornitore', ['id_anagrafica' => $id_anagrafica]); $id_conto_anagrafica = $id_conto_anagrafica[$dir == 'entrata' ? 'id_conto_cliente' : 'id_conto_fornitore']; // Recupero conto contropartita @@ -165,6 +169,28 @@ protected function registraScadenza(Fattura $fattura, $importo, $data_scadenza, } } } + return $scadenza; + } + + protected function trovaPagamento($id_pagamento): ?Pagamento + { + return Pagamento::where('id', $id_pagamento)->first(); + } + + protected function trovaAssicurazioniCrediti($id_anagrafica) + { + return AssicurazioneCrediti::where('id_anagrafica', $id_anagrafica)->get(); + } + + + protected function trovaAssicurazioneCreditiConScadenze($id_anagrafica, $data_scadenza): ?AssicurazioneCrediti + { + return AssicurazioneCrediti::where('id_anagrafica', $id_anagrafica)->where('data_inizio', '<=', $data_scadenza)->where('data_fine', '>=', $data_scadenza)->first(); + } + + protected function generaScadenza($id_anagrafica, $descrizione, $importo, $data_scadenza, $id_pagamento, $id_banca_azienda, $id_banca_controparte, $type, $is_pagato): Scadenza + { + return Scadenza::build($id_anagrafica, $descrizione, $importo, $data_scadenza, $id_pagamento, $id_banca_azienda, $id_banca_controparte, $type, $is_pagato); } /** @@ -187,6 +213,7 @@ protected function registraScadenzeFE($is_pagato = false) $pagamenti = isset($pagamenti[0]) ? $pagamenti : [$pagamenti]; } + $results = []; foreach ($pagamenti as $pagamento) { $rate = $pagamento['DettaglioPagamento']; $rate = isset($rate[0]) ? $rate : [$rate]; @@ -198,11 +225,11 @@ protected function registraScadenzeFE($is_pagato = false) $scadenza = !empty($rata['DataScadenzaPagamento']) ? FatturaElettronicaImport::parseDate($rata['DataScadenzaPagamento']) : $this->fattura->data; $importo = $this->fattura->isNota() ? $rata['ImportoPagamento'] : -$rata['ImportoPagamento']; - self::registraScadenza($this->fattura, $importo, $scadenza, $is_pagato, $id_pagamento, $id_banca_azienda, $id_banca_controparte); + $results[] = self::registraScadenza($this->fattura, $importo, $scadenza, $is_pagato, $id_pagamento, $id_banca_azienda, $id_banca_controparte); } } - return !empty($pagamenti); + return $results; } /** @@ -217,9 +244,10 @@ protected function registraScadenzeTradizionali($is_pagato = false) $netto = $this->fattura->isNota() ? -$netto : $netto; // Calcolo delle rate - $rate = ($this->fattura->pagamento ?: \Modules\Pagamenti\Pagamento::where('id', $this->fattura->id_pagamento)->first())->calcola($netto, $this->fattura->data, $this->fattura->id_anagrafica); + $rate = ($this->fattura->pagamento ?: $this->trovaPagamento($this->fattura->id_pagamento))->calcola($netto, $this->fattura->data, $this->fattura->id_anagrafica, $this->database); $direzione = $this->fattura->tipo->dir; + $results = []; foreach ($rate as $rata) { $scadenza = $rata['scadenza']; $importo = $direzione == 'uscita' ? -$rata['importo'] : $rata['importo']; @@ -227,7 +255,9 @@ protected function registraScadenzeTradizionali($is_pagato = false) $id_banca_azienda = $this->fattura->id_banca_azienda; $id_banca_controparte = $this->fattura->id_banca_controparte; - self::registraScadenza($this->fattura, $importo, $scadenza, $is_pagato, $id_pagamento, $id_banca_azienda, $id_banca_controparte); + $results[] = self::registraScadenza($this->fattura, $importo, $scadenza, $is_pagato, $id_pagamento, $id_banca_azienda, $id_banca_controparte); } + + return $results; } } diff --git a/modules/fatture/tests/GenerazioneScadenzeTest.php b/modules/fatture/tests/GenerazioneScadenzeTest.php new file mode 100644 index 0000000000..85b80b50e4 --- /dev/null +++ b/modules/fatture/tests/GenerazioneScadenzeTest.php @@ -0,0 +1,190 @@ +getFatturaConRate('2025-03-30', 500, [ + $this->mockModel(Pagamento::class, [ + 'giorno' => 0, + 'num_giorni' => 90, + 'prc' => 100, + ]), + ]); + + $gestore = $this->getGestore($fattura); + $gestore->shouldReceive('trovaPagamento')->andReturn(null); + $gestore->shouldReceive('trovaAssicurazioneCrediti')->andReturn(null); + $gestore->shouldReceive('trovaAssicurazioneCreditiConScadenze')->andReturn(null); + + $scadenze = $gestore->registra(false, true); + + $this->assertEquals(1, count($scadenze)); + + $this->assertEquals('2025-03-30', $scadenze[0]->data_emissione); + $this->assertEquals('2025-06-28', $scadenze[0]->scadenza->format('Y-m-d')); + $this->assertEquals(500, $scadenze[0]->da_pagare); + } + + public function testRimessaDiretta90GiorniAl15() + { + $fattura = $this->getFatturaConRate('2025-03-30', 500, [ + $this->mockModel(Pagamento::class, [ + 'giorno' => 15, + 'num_giorni' => 90, + 'prc' => 100, + ]), + ]); + + $gestore = $this->getGestore($fattura); + $gestore->shouldReceive('trovaPagamento')->andReturn(null); + $gestore->shouldReceive('trovaAssicurazioneCrediti')->andReturn(null); + $gestore->shouldReceive('trovaAssicurazioneCreditiConScadenze')->andReturn(null); + + $scadenze = $gestore->registra(false, true); + + $this->assertEquals(1, count($scadenze)); + + $this->assertEquals('2025-03-30', $scadenze[0]->data_emissione); + $this->assertEquals('2025-06-15', $scadenze[0]->scadenza->format('Y-m-d')); + $this->assertEquals(500, $scadenze[0]->da_pagare); + } + + public function test3RateStatiche() + { + $fattura = $this->getFatturaConRate('2025-03-30', 600, [ + $this->mockModel(Pagamento::class, [ + 'giorno' => 0, + 'num_giorni' => 90, + 'prc' => 33, + ]), + $this->mockModel(Pagamento::class, [ + 'giorno' => 0, + 'num_giorni' => 180, + 'prc' => 33, + ]), + $this->mockModel(Pagamento::class, [ + 'giorno' => 0, + 'num_giorni' => 270, + 'prc' => 34, + ]), + ]); + + $gestore = $this->getGestore($fattura); + $gestore->shouldReceive('trovaPagamento')->andReturn(null); + $gestore->shouldReceive('trovaAssicurazioneCrediti')->andReturn(null); + $gestore->shouldReceive('trovaAssicurazioneCreditiConScadenze')->andReturn(null); + + $scadenze = $gestore->registra(false, true); + + $this->assertEquals(3, count($scadenze)); + + $this->assertEquals('2025-03-30', $scadenze[0]->data_emissione); + $this->assertEquals('2025-06-28', $scadenze[0]->scadenza->format('Y-m-d')); + $this->assertEquals(198, $scadenze[0]->da_pagare); + + $this->assertEquals('2025-03-30', $scadenze[1]->data_emissione); + $this->assertEquals('2025-09-26', $scadenze[1]->scadenza->format('Y-m-d')); + $this->assertEquals(198, $scadenze[1]->da_pagare); + + $this->assertEquals('2025-03-30', $scadenze[2]->data_emissione); + $this->assertEquals('2025-12-25', $scadenze[2]->scadenza->format('Y-m-d')); + $this->assertEquals(204, $scadenze[2]->da_pagare); + } + + protected function mockModel($class, $attributes = null) + { + $ref = Mockery::mock($class)->shouldAllowMockingProtectedMethods()->makePartial(); + + $ref->shouldReceive('save')->andReturn(null); + $ref->shouldReceive('delete')->andReturn(null); + $ref->shouldReceive('getDateFormat')->andReturn('Y-m-d'); + + // Fix per gestione attributi su modello mocked di Eloquent + $ref->shouldReceive('getMutatedAttributes')->andReturnUsing(function () use ($class) { + preg_match_all('/(?<=^|;)get([^;]+?)Attribute(;|$)/', implode(';', get_class_methods($class)), $matches); + + $ref = $matches[1]; + + return collect($ref) + ->merge($attributeMutatorMethods) + ->map(function ($match) { + return lcfirst(static::$snakeAttributes ? Str::snake($match) : $match); + })->all(); + }); + + if (!empty($attributes)) { + foreach ($attributes as $key => $value) { + $ref->shouldReceive('getAttribute')->with($key)->andReturn($value); + } + } + + return $ref; + } + + protected function getFatturaConRate($data, $netto, $rate): Fattura + { + $tipo = $this->mockModel(Tipo::class); + $tipo->shouldReceive('getAttribute')->with('dir')->andReturn('entrata'); + $tipo->shouldReceive('getTranslation')->andReturn('Fattura di vendita'); + + $pagamento = $this->mockModel(Pagamento::class); + $pagamento->shouldReceive('trovaRate')->andReturn($rate); + + $fattura = $this->mockModel(Fattura::class, [ + 'numero_esterno' => '2025-01', + 'idpagamento' => null, + 'id_banca_controparte' => null, + 'id_banca_azienda' => null, + 'idanagrafica' => 'test-an-1', + 'data' => $data, + 'id' => 'test-1', + 'netto' => $netto, + 'scadenze' => collect([]), + 'pagamento' => $pagamento, + 'tipo' => $tipo, + 'ritenuta_acconto' => null, + ]); + $fattura->shouldReceive('associate')->andReturn(null); + $fattura->shouldReceive('isNota')->andReturn(false); + + return $fattura; + } + + protected function getGestore($fattura): Scadenze + { + $database = $this->mockModel(Database::class); + $database->shouldReceive('delete')->andReturn(null); + $database->shouldReceive('selectOne')->andReturn(null); + + $gestore = Mockery::mock(Scadenze::class, [$fattura, $database])->shouldAllowMockingProtectedMethods()->makePartial(); + $gestore->shouldReceive('generaScadenza')->andReturnUsing(function ($idanagrafica, $descrizione, $importo, $data_scadenza, $id_pagamento, $id_banca_azienda, $id_banca_controparte, $type, $is_pagato) use ($fattura) { + $scadenza = $this->mockModel(Scadenza::class, [ + 'idanagrafica' => $idanagrafica, + 'descrizione' => $descrizione, + 'scadenza' => \Carbon\Carbon::create($data_scadenza), + 'da_pagare' => $importo, + 'tipo' => $type, + 'id_pagamento' => $id_pagamento, + 'id_banca_azienda' => $id_banca_azienda, + 'id_banca_controparte' => $id_banca_controparte, + + 'pagato' => $is_pagato ? $importo : 0, + 'data_pagamento' => $is_pagato ? $data_scadenza : null, + ]); + + $scadenza->shouldReceive('documento')->andReturn($fattura); + + return $scadenza; + }); + + return $gestore; + } +} diff --git a/modules/pagamenti/src/Pagamento.php b/modules/pagamenti/src/Pagamento.php index 02a2e9783d..f4b7b62f13 100755 --- a/modules/pagamenti/src/Pagamento.php +++ b/modules/pagamenti/src/Pagamento.php @@ -55,10 +55,17 @@ public function rate() { return $this->hasMany(Pagamento::class, 'id'); } + + protected function trovaRate() + { + return Pagamento::where('name', '=', $this->name)->get()->sortBy('num_giorni')->toArray(); + } - public function calcola($importo, $data, $id_anagrafica) + public function calcola($importo, $data, $id_anagrafica, $database = null) { - $rate = Pagamento::where('name', '=', $this->name)->get()->sortBy('num_giorni')->pluck('id')->toArray(); + $database = $database ?: database(); // Allow mocking + + $rate = $this->trovaRate(); $number = count($rate); $totale = 0.0; @@ -67,7 +74,6 @@ public function calcola($importo, $data, $id_anagrafica) $count = 0; foreach ($rate as $key => $rata) { $date = new Carbon($data); - $rata = Pagamento::find($rata); // X giorni esatti if ($rata->giorno == 0) { // Offset della rata @@ -113,7 +119,7 @@ public function calcola($importo, $data, $id_anagrafica) } // Posticipo la scadenza in base alle regole pagamenti dell'anagrafica - $regola_pagamento = database()->selectOne('an_pagamenti_anagrafiche', '*', ['id_anagrafica' => $id_anagrafica, 'mese' => $date->format('m')]); + $regola_pagamento = $database->selectOne('an_pagamenti_anagrafiche', '*', ['idanagrafica' => $id_anagrafica, 'mese' => $date->format('m')]); if (!empty($regola_pagamento)) { $date->modify('last day of this month'); $date->addDays($regola_pagamento['giorno_fisso']); diff --git a/package.json b/package.json index 2c51a41321..7b319cae8d 100644 --- a/package.json +++ b/package.json @@ -101,7 +101,7 @@ "build-OSM": "gulp && npm run-script update-openapi-client", "dump-OSM": "php composer.phar dump-autoload", "windows-fix": "yarn global add windows-build-tools", - "php-cs-fix": "vendor/bin/php-cs-fixer fix", + "php-cs-fix": "php composer.phar run format", "rector": "vendor/bin/rector process", "setup-corepack": "npm install -g corepack && corepack enable", "export-openapi": "php artisan api:openapi:export --output=openapi.json", diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000000..3b387e9a57 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,16 @@ + + + + + tests/ + + + + ./modules/*/tests/ + + + \ No newline at end of file diff --git a/tests/GeneratorTest.php b/tests/GeneratorTest.php new file mode 100644 index 0000000000..f0a002a2be --- /dev/null +++ b/tests/GeneratorTest.php @@ -0,0 +1,70 @@ +test(null, '|TEST'); + } + + public function testNumbersWithSuffix() + { + $this->test('|TEST'); + } + + public function testCommonNumbers() + { + $this->test(); + } + + public function testDates() + { + $this->test('/YYYY'); + $this->test('/yy'); + + $this->test(null, 'YYYY-'); + $this->test(null, 'yy-'); + } + + protected function test($prefix = null, $suffix = null) + { + $date = date('Y-m-d H:i:s'); + $info = Generator::dateToPattern($date); + + // Individuazione valori relativi a suffisso e prefisso + $prefix_value = Generator::complete($prefix, $info); + $suffix_value = Generator::complete($suffix, $info); + + $step = 3; + + // Pattern di base con numero di caratteri incrementale + $pattern = $prefix.'#'.$suffix; + + $previous = null; + for ($i = 0; $i < 10000; $i = $i + $step) { + $value = $prefix_value.($i + 1).$suffix_value; + $this->assertEquals($value, Generator::generate($pattern, $previous, $step, $info)); + + $previous = $value; + } + + // Pattern con padding + $length = 5; + $pattern = $prefix.str_repeat('#', $length).$suffix; + + $previous = null; + for ($i = 0; $i < 10000; $i = $i + $step) { + $value = $prefix_value.$this->pad($i + 1, $length).$suffix_value; + $this->assertEquals($value, Generator::generate($pattern, $previous, $step, $info)); + + $previous = $value; + } + } + + protected function pad($number, $length) + { + return str_pad($number, $length, '0', STR_PAD_LEFT); + } +}