diff --git a/.gitignore b/.gitignore index 10d75a6f27..85e42d1b49 100755 --- a/.gitignore +++ b/.gitignore @@ -101,4 +101,5 @@ settings.json codeception.yml !.gitkeep .vscode -.php-cs-fixer.cache \ No newline at end of file +.php-cs-fixer.cache +.phpunit.* \ No newline at end of file diff --git a/.php-cs-fixer.php b/.php-cs-fixer.php index 5533414b6a..5efed0280a 100644 --- a/.php-cs-fixer.php +++ b/.php-cs-fixer.php @@ -5,7 +5,6 @@ ->exclude('.couscous') ->exclude('node_modules') ->exclude('vendor') - ->exclude('tests') ->ignoreDotFiles(true) ->ignoreVCS(true) ->in(__DIR__); diff --git a/composer.json b/composer.json index 387dc0be34..084c5c7b5b 100755 --- a/composer.json +++ b/composer.json @@ -68,8 +68,9 @@ }, "require-dev": { "friendsofphp/php-cs-fixer": "^3.53", - "phpunit/phpunit": "^11.5", - "rector/rector": "^2.0" + "mockery/mockery": "^1.6", + "phpunit/phpunit": "^10|^11", + "rector/rector": "^1.0" }, "autoload": { "psr-4": { @@ -151,5 +152,9 @@ "allow-plugins": { "kylekatarnls/update-helper": true } + }, + "scripts": { + "unit-tests": "phpunit --configuration phpunit.xml", + "format": "php-cs-fixer fix" } } diff --git a/modules/fatture/src/Gestori/Scadenze.php b/modules/fatture/src/Gestori/Scadenze.php index 0ab90adc11..a5f31a5345 100644 --- a/modules/fatture/src/Gestori/Scadenze.php +++ b/modules/fatture/src/Gestori/Scadenze.php @@ -21,6 +21,7 @@ namespace Modules\Fatture\Gestori; use Modules\Fatture\Fattura; +use Modules\Pagamenti\Pagamento; use Modules\Scadenzario\Scadenza; use Plugins\AssicurazioneCrediti\AssicurazioneCrediti; use Plugins\ImportFE\FatturaElettronica as FatturaElettronicaImport; @@ -33,8 +34,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 } /** @@ -49,11 +51,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 @@ -78,8 +80,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, 'ritenutaacconto'); + $scadenze[] = self::registraScadenza($this->fattura, $importo, $scadenza, $is_pagato, $id_pagamento, $id_banca_azienda, $id_banca_controparte, 'ritenutaacconto'); } + + return $scadenze; } /** @@ -88,14 +92,15 @@ public function registra($is_pagato = false, $ignora_fe = false) public function rimuovi() { $scadenze = $this->fattura->scadenze; + $assicurazioni = []; foreach ($scadenze as $scadenza) { - $assicurazione_crediti = AssicurazioneCrediti::where('id_anagrafica', $scadenza->idanagrafica)->where('data_inizio', '<=', $scadenza->scadenza)->where('data_fine', '>=', $scadenza->scadenza)->first(); + $assicurazione_crediti = this->trovaAssicurazioneCrediti($scadenza->idanagrafica, $scadenza->scadenza); if (!empty($assicurazione_crediti)) { $assicurazioni[] = $assicurazione_crediti; } } - database()->delete('co_scadenziario', ['iddocumento' => $this->fattura->id]); + $this->database->delete('co_scadenziario', ['iddocumento' => $this->fattura->id]); foreach ($assicurazioni as $assicurazione) { $assicurazione->fixTotale(); @@ -117,18 +122,35 @@ protected function registraScadenza(Fattura $fattura, $importo, $data_scadenza, $descrizione = $fattura->tipo->getTranslation('title').' numero '.$numero; $idanagrafica = $fattura->idanagrafica; - $scadenza = Scadenza::build($idanagrafica, $descrizione, $importo, $data_scadenza, $id_pagamento, $id_banca_azienda, $id_banca_controparte, $type, $is_pagato); + $scadenza = $this->generaScadenza($idanagrafica, $descrizione, $importo, $data_scadenza, $id_pagamento, $id_banca_azienda, $id_banca_controparte, $type, $is_pagato); $scadenza->documento()->associate($fattura); $scadenza->data_emissione = $fattura->data; $scadenza->save(); - $assicurazione_crediti = AssicurazioneCrediti::where('id_anagrafica', $scadenza->idanagrafica)->where('data_inizio', '<=', $scadenza->scadenza)->where('data_fine', '>=', $scadenza->scadenza)->first(); + $assicurazione_crediti = $this->trovaAssicurazioneCrediti($scadenza->idanagrafica, $scadenza->scadenza); if (!empty($assicurazione_crediti)) { $assicurazione_crediti->fixTotale(); $assicurazione_crediti->save(); } + + return $scadenza; + } + + protected function trovaPagamento($idpagamento): ?Pagamento + { + return Pagamento::where('id', $idpagamento)->first(); + } + + protected function trovaAssicurazioneCrediti($idanagrafica, $data_scadenza): ?AssicurazioneCrediti + { + return AssicurazioneCrediti::where('id_anagrafica', $idanagrafica)->where('data_inizio', '<=', $data_scadenza)->where('data_fine', '>=', $data_scadenza)->first(); + } + + protected function generaScadenza($idanagrafica, $descrizione, $importo, $data_scadenza, $id_pagamento, $id_banca_azienda, $id_banca_controparte, $type, $is_pagato): Scadenza + { + return Scadenza::build($idanagrafica, $descrizione, $importo, $data_scadenza, $id_pagamento, $id_banca_azienda, $id_banca_controparte, $type, $is_pagato); } /** @@ -151,6 +173,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]; @@ -162,11 +185,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; } /** @@ -181,9 +204,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->idpagamento)->first())->calcola($netto, $this->fattura->data, $this->fattura->idanagrafica); + $rate = ($this->fattura->pagamento ?: $this->trovaPagamento($this->fattura->idpagamento))->calcola($netto, $this->fattura->data, $this->fattura->idanagrafica, $this->database); $direzione = $this->fattura->tipo->dir; + $results = []; foreach ($rate as $rata) { $scadenza = $rata['scadenza']; $importo = $direzione == 'uscita' ? -$rata['importo'] : $rata['importo']; @@ -191,7 +215,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..5a66cd0b9a --- /dev/null +++ b/modules/fatture/tests/GenerazioneScadenzeTest.php @@ -0,0 +1,187 @@ +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); + + $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); + $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); + + $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); + $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); + + $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); + $this->assertEquals(198, $scadenze[0]->da_pagare); + + $this->assertEquals('2025-03-30', $scadenze[1]->data_emissione); + $this->assertEquals('2025-09-26', $scadenze[1]->scadenza); + $this->assertEquals(198, $scadenze[1]->da_pagare); + + $this->assertEquals('2025-03-30', $scadenze[2]->data_emissione); + $this->assertEquals('2025-12-25', $scadenze[2]->scadenza); + $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' => [], + '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' => $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 2bfafa9b2e..e054226cbf 100755 --- a/modules/pagamenti/src/Pagamento.php +++ b/modules/pagamenti/src/Pagamento.php @@ -54,10 +54,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; @@ -66,7 +73,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 @@ -112,7 +118,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', '*', ['idanagrafica' => $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->addDay(); diff --git a/package.json b/package.json index a38b54126e..46d4f8e066 100644 --- a/package.json +++ b/package.json @@ -96,9 +96,11 @@ "build-OSM": "gulp", "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" + "setup-corepack": "npm install -g corepack && corepack enable", + "unit-tests": "php composer.phar run unit-tests" }, "packageManager": "yarn@4.6.0" } + 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/src/Util/Singleton.php b/src/Util/Singleton.php index 9cd2c72a66..9445155b5d 100755 --- a/src/Util/Singleton.php +++ b/src/Util/Singleton.php @@ -47,7 +47,7 @@ private function __clone() /** * Private unserialize method to prevent unserializing of the Singleton instance. */ - private function __wakeup() + public function __wakeup() { } diff --git a/tests/_bootstrap.php b/tests/_bootstrap.php deleted file mode 100644 index cbcb837df3..0000000000 --- a/tests/_bootstrap.php +++ /dev/null @@ -1,10 +0,0 @@ - $namespace) { - Autoload::addNamespace($namespace.'\\', __DIR__.'/../'.$path.'/custom/src'); - Autoload::addNamespace($namespace.'\\', __DIR__.'/../'.$path.'/src'); -} \ No newline at end of file