Merge commit '0a968bdc61e0598c65c68b3a2afe3aa205041830'

This commit is contained in:
Flavio Barachino 2025-09-26 08:15:27 +02:00
commit bc769008b3
17 changed files with 686 additions and 262 deletions

View File

@ -2,6 +2,7 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\Models\Categorie;
use App\Models\ImportRule; use App\Models\ImportRule;
use Illuminate\Http\Request; use Illuminate\Http\Request;
@ -13,6 +14,8 @@ class ImportRuleController extends Controller
public function index() public function index()
{ {
// //
$rules = ImportRule::all();
return view('import_rules.index', compact('rules'));
} }
/** /**
@ -21,6 +24,11 @@ class ImportRuleController extends Controller
public function create() public function create()
{ {
// //
// Return a view to create a new import rule
$categorie = Categorie::all();
return view('import_rules.create',compact('categorie'));
//'categories'=>$categorie);
} }
/** /**
@ -29,6 +37,23 @@ class ImportRuleController extends Controller
public function store(Request $request) public function store(Request $request)
{ {
// //
$request->validate([
'pattern' => 'required|string|max:255',
'category_id' => 'required|exists:categories,id',
'description' => 'nullable|string|max:255',
'is_active' => 'boolean',
'created_by' => 'nullable|string|max:255',
]);
ImportRule::create(
[
'category_id'=>$request->category_id,
'pattern'=>$request->pattern,
'description'=>$request->description,
'is_active'=>1,
]
);
return redirect()->route('import_rules.index'); // Redirect to the index after storing the rule
} }
/** /**

View File

@ -13,9 +13,14 @@ use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Rap2hpoutre\FastExcel\FastExcel; use Rap2hpoutre\FastExcel\FastExcel;
use Illuminate\Support\Facades\Storage;
use DateTime;
class MovimentiController extends Controller class MovimentiController extends Controller
{ {
public $map;
// Gestione dei movimenti // Gestione dei movimenti
public static function newMovimenti() { public static function newMovimenti() {
$categorie=Categorie::list(); // TODO: da risolvere con jquery nella pagina blade conti.movimenti.list $categorie=Categorie::list(); // TODO: da risolvere con jquery nella pagina blade conti.movimenti.list
@ -29,13 +34,15 @@ class MovimentiController extends Controller
public static function listMovimenti(){ public static function listMovimenti(){
$categorie=Categorie::list(); $categorie=Categorie::list();
$tags=tag::getList(); $tags=tag::getList();
$contos=Conto::all();
/* Query per visualizzare anche il totale dei documenti presenti per il record */ /* Query per visualizzare anche il totale dei documenti presenti per il record */
$movimenti=Movimenti::getList(); $movimenti=Movimenti::getList();
//dd($movimenti);
return view('conti.movimenti.list',[ return view('conti.movimenti.list',[
'categorie'=>$categorie, 'categorie'=>$categorie,
'movimenti'=>$movimenti, 'movimenti'=>$movimenti,
'contos'=>$contos,
'tags'=>$tags 'tags'=>$tags
]); ]);
} }
@ -222,14 +229,32 @@ class MovimentiController extends Controller
$ncategoria=$categoria->cat_name; $ncategoria=$categoria->cat_name;
for ($i=1;$i<=12;$i++) for ($i=1;$i<=12;$i++)
{ {
$movrow=DB::table('movimentis') $importo_dare = DB::table('movimentis')
->whereMonth('mov_data','=',$i) ->whereMonth('mov_data', '=', $i)
->whereYear('mov_data','=',$anno) ->whereYear('mov_data', '=', $anno)
->where('mov_fk_categoria','=',$id) ->where('mov_fk_categoria', '=', $id)
->sum('mov_importo'); ->sum('mov_importo_dare');
$importo_avere = DB::table('movimentis')
->whereMonth('mov_data', '=', $i)
->whereYear('mov_data', '=', $anno)
->where('mov_fk_categoria', '=', $id)
->sum('mov_importo_avere');
// $movrow=DB::table('movimentis')
// ->whereMonth('mov_data','=',$i)
// ->whereYear('mov_data','=',$anno)
// ->where('mov_fk_categoria','=',$id)
// ->sum('mov_importo_dare')
// ->sum('mov_importo_avere');
$movrow = $importo_avere - $importo_dare;
$coll[]=$movrow; $coll[]=$movrow;
$collx[]=$movrow; $collx[]=$movrow;
} }
//TEST
// dd($movrow);
// /TEST
$totale[]=array_sum($collx); $totale[]=array_sum($collx);
unset($collx); unset($collx);
} }
@ -335,6 +360,84 @@ class MovimentiController extends Controller
} }
} }
public function importGenericCsv(Request $request)
{
if ($request->hasFile('filename'))
{
$filename=$request->file('filename')->store();
$csv_headers=Movimenti::retrieveHeaders($filename);
$db_fields=Movimenti::getDbFields();
return view('conti.importGeneric',['csv'=>$csv_headers,'db'=>$db_fields,'filename'=>$filename]);
}
else {
return 'Nessun File trovato';
}
}
public function importmappedCsv(Request $request)
{
$this->prepareMapping($request['mapping']);
$filename = Storage::path($request->filename);
(new FastExcel)->configureCsv(';')->import($filename, function($line) {
$data = $this->mapCsvLineToDb($line);
// dd($data);
if (isset($data['mov_data'])) {
Movimenti::importNoDuplicate($data);
//Movimenti::create($data);
}
});
return redirect(Route('movimenti'));
}
/**
* Prepara la mappatura tra i campi CSV e i campi DB
*/
private function prepareMapping($mapping)
{
foreach ($mapping as $key => $value) {
if ($value != null) {
$this->map[$key] = $value;
}
}
}
/**
* Mappa una riga del CSV ai campi del database
*/
private function mapCsvLineToDb($line)
{
if (isset($line[$this->map['mov_data']])) {
$data = [
'mov_data' => $this->parseDate($line[$this->map['mov_data']]),
'mov_descrizione' => $line[$this->map['mov_descrizione']] ?? null,
'mov_importo_dare' => Movimenti::cleanImporto($line[$this->map['mov_importo_dare']]?? null) ,
'mov_importo_avere' => Movimenti::cleanImporto($line[$this->map['mov_importo_avere']] ?? null),
'mov_fk_categoria' => Movimenti::setCategoriesFromCsv($line[$this->map['mov_descrizione']] ?? ''),
'mov_fk_tags' => 1,
'mov_inserito_da' => Auth::id(),
'conto_id_da' => 1,
'conto_id_a' => 1,
];
return $data;
}
return null; // Se non c'è la data, non importa la riga
}
/**
* Converte la data dal formato d/m/Y a Y-m-d
*/
private function parseDate($dateString)
{
$date = DateTime::createFromFormat('d/m/Y', $dateString);
return $date ? $date->format('Y-m-d') : null;
}
public function importFile() public function importFile()
{ {
return view('conti.import'); return view('conti.import');
@ -345,6 +448,13 @@ class MovimentiController extends Controller
return view('conti.importCR'); return view('conti.importCR');
} }
public function importFileGen()
{
return view('conti.importGen');
}
/* public function test() /* public function test()
{ {
Movimenti::getYearsFromMovimenti(); Movimenti::getYearsFromMovimenti();
@ -360,4 +470,5 @@ class MovimentiController extends Controller
$mov=Movimenti::getMovimentoById($id); $mov=Movimenti::getMovimentoById($id);
return json_encode($mov); return json_encode($mov);
} }
} }

View File

@ -8,4 +8,14 @@ use Illuminate\Database\Eloquent\Model;
class ImportRule extends Model class ImportRule extends Model
{ {
use HasFactory; use HasFactory;
protected $fillable = [
'category_id',
'pattern',
'description',
'is_active',
'created_by',
];
// In MovimentiController o direttamente nel model Movimenti
} }

View File

@ -5,10 +5,13 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
use Rap2hpoutre\FastExcel\FastExcel; use Rap2hpoutre\FastExcel\FastExcel;
use App\Models\Categorie; use App\Models\Categorie;
use App\Models\Conto; use App\Models\Conto;
use App\Models\User; use App\Models\User;
use App\Models\ImportRule;
class Movimenti extends Model class Movimenti extends Model
{ {
@ -16,10 +19,24 @@ class Movimenti extends Model
protected $dates = ['mov_data']; protected $dates = ['mov_data'];
protected $casts = [ 'mov_data'=>'datetime']; protected $casts = [ 'mov_data'=>'datetime'];
protected $fillable = [
'mov_data',
'mov_fk_categoria',
'mov_descrizione',
'mov_importo_dare',
'mov_importo_avere',
'mov_inserito_da',
'mov_fk_tags',
'conto_id_da',
'conto_id_a',
'import_hash',
];
public static $query= 'SELECT public static $query= 'SELECT
a.id, a.id,
a.mov_data, a.mov_data,
a.mov_importo, a.mov_importo_dare,
a.mov_importo_avere,
a.mov_descrizione, a.mov_descrizione,
c.cat_name, c.cat_name,
t.tag_name, t.tag_name,
@ -28,10 +45,15 @@ FROM movimentis as a
JOIN tags as t ON a.mov_fk_tags=t.id JOIN tags as t ON a.mov_fk_tags=t.id
JOIN categories as c ON a.mov_fk_categoria=c.id'; JOIN categories as c ON a.mov_fk_categoria=c.id';
public function Conto() public function ContoDa()
{ {
return $this->belongsTo(Conto::class); return $this->belongsTo(Conto::class, 'conto_id_da');
} }
public function ContoA()
{
return $this->belongsTo(Conto::class, 'conto_id_a');
}
public function User() public function User()
{ {
@ -40,35 +62,20 @@ JOIN categories as c ON a.mov_fk_categoria=c.id';
public function Categorie() public function Categorie()
{ {
return $this->belongsTo(Categorie::class); return $this->belongsTo(Categorie::class, 'mov_fk_categoria');
} }
public function Tags() public function Tags()
{ {
return $this->belongsTo(Tags::class); return $this->belongsTo(tag::class);
} }
public static function getList() { public static function getList() {
// ISSUE #3 - Aggiunta visualizzazione conto da e conto a nella lista dei movimenti
// e risoluzione duplicazione movimenti.
$expression=DB::raw( return self::with(['Tags', 'Categorie', 'ContoDa', 'ContoA'])->orderBy('mov_data','desc')->get();
'SELECT
a.id,
a.mov_data,
a.mov_importo,
a.mov_descrizione,
c.cat_name,
t.tag_name,
co.nomeConto,
(SELECT Count(entity_id) as quanti FROM gen_docs WHERE entity=0 AND entity_id = a.id) as quanti
FROM movimentis as a
JOIN tags as t ON a.mov_fk_tags=t.id
JOIN categories as c ON a.mov_fk_categoria=c.id
JOIN contos as co ON a.conto_id=co.id'
);
$query = $expression->getValue(DB::connection()->getQueryGrammar());
return DB::select($query);
} }
public static function getSaldo($date) { public static function getSaldo($date) {
@ -89,6 +96,9 @@ JOIN categories as c ON a.mov_fk_categoria=c.id';
'mov_fk_tags'=>$request->mov_fk_tags, 'mov_fk_tags'=>$request->mov_fk_tags,
'mov_inserito_da'=>$request->userid, 'mov_inserito_da'=>$request->userid,
'conto_id'=>$request->conto_id, 'conto_id'=>$request->conto_id,
// 'conto_id_da'=>$request->conto_id_da,
// 'conto_id_a'=>$request->conto_id_a,
]); ]);
} }
@ -165,16 +175,17 @@ JOIN categories as c ON a.mov_fk_categoria=c.id';
} }
public static function updateMovimenti($request) { public static function updateMovimenti($request) {
DB::table('movimentis') $updateData = [];
->where('id','=', $request['id']) if (isset($request['mov_data'])) $updateData['mov_data'] = $request['mov_data'];
->update([ if (isset($request['mov_fk_categoria'])) $updateData['mov_fk_categoria'] = $request['mov_fk_categoria'];
'mov_data' => $request['mov_data'], if (isset($request['mov_descrizione'])) $updateData['mov_descrizione'] = $request['mov_descrizione'];
'mov_fk_categoria'=>$request['mov_fk_categoria'], if (isset($request['mov_importo_dare'])) $updateData['mov_importo_dare'] = $request['mov_importo_dare'];
'mov_descrizione'=>$request['mov_descrizione'], if (isset($request['mov_importo_avere'])) $updateData['mov_importo_avere'] = $request['mov_importo_avere'];
'mov_importo'=>$request['mov_importo'], if (isset($request['mov_fk_tags'])) $updateData['mov_fk_tags'] = $request['mov_fk_tags'];
'mov_fk_tags'=>$request['mov_fk_tags'], if (isset($request['userid'])) $updateData['mov_inserito_da'] = $request['userid'];
'mov_inserito_da'=>$request['userid'],
]); self::where('id', $request['id'])->update($updateData);
} }
public static function deleteMovimento($id) { public static function deleteMovimento($id) {
@ -185,48 +196,90 @@ JOIN categories as c ON a.mov_fk_categoria=c.id';
public static function listByCatMonth($month,$cat,$year) { public static function listByCatMonth($month,$cat,$year) {
$expression= DB::raw('SELECT // $expression= DB::raw('SELECT
a.id, // a.id,
a.mov_data, // a.mov_data,
a.mov_importo, // a.mov_importo_dare,
a.mov_descrizione, // a.mov_importo_avere,
c.cat_name, // a.mov_descrizione,
t.tag_name, // c.cat_name,
(SELECT Count(entity_id) as quanti FROM gen_docs WHERE entity=0 AND entity_id = a.id) as quanti // t.tag_name,
FROM movimentis as a // (SELECT Count(entity_id) as quanti FROM gen_docs WHERE entity=0 AND entity_id = a.id) as quanti
JOIN tags as t ON a.mov_fk_tags=t.id // FROM movimentis as a
JOIN categories as c ON a.mov_fk_categoria=c.id // JOIN tags as t ON a.mov_fk_tags=t.id
WHERE Month(a.mov_data)='.$month.' AND Year(a.mov_data)='.$year.' AND a.mov_fk_categoria='.$cat // JOIN categories as c ON a.mov_fk_categoria=c.id
); // WHERE Month(a.mov_data)='.$month.' AND Year(a.mov_data)='.$year.' AND a.mov_fk_categoria='.$cat
$query = $expression->getValue(DB::connection()->getQueryGrammar()); // );
return DB::select($query); // $query = $expression->getValue(DB::connection()->getQueryGrammar());
// return DB::select($query);
return self::with(['Tags', 'Categorie'])
->whereMonth('mov_data', $month)
->whereYear('mov_data', $year)
->where('mov_fk_categoria', $cat)
->get();
} }
public static function listByCategory($cat) { public static function listByCategory($cat) {
$expression=DB::raw(' SELECT a.id, // $expression=DB::raw(' SELECT a.id,
a.mov_data, // a.mov_data,
a.mov_importo, // a.mov_importo_dare,
a.mov_descrizione, // a.mov_importo_avere,
c.cat_name, // a.mov_descrizione,
t.tag_name, // c.cat_name,
(SELECT Count(entity_id) as quanti FROM gen_docs WHERE entity=0 AND entity_id = a.id) as quanti // t.tag_name,
FROM movimentis as a // (SELECT Count(entity_id) as quanti FROM gen_docs WHERE entity=0 AND entity_id = a.id) as quanti
JOIN tags as t ON a.mov_fk_tags=t.id // FROM movimentis as a
JOIN categories as c ON a.mov_fk_categoria=c.id // JOIN tags as t ON a.mov_fk_tags=t.id
WHERE a.mov_fk_categoria = '.$cat ); // JOIN categories as c ON a.mov_fk_categoria=c.id
// WHERE a.mov_fk_categoria = '.$cat );
$query = $expression->getValue(DB::connection()->getQueryGrammar()); // $query = $expression->getValue(DB::connection()->getQueryGrammar());
return DB::select($query); // return DB::select($query);
return self::with(['Tags', 'Categorie'])
->where('mov_fk_categoria', $cat)
->get();
} }
public static function makeHash($data,$movimento_dare, $movimento_avere, $descrizione)
{
// crea l'hash (serve per evitare duplicati nei movimenti)
return md5($data.$movimento_dare.$movimento_avere.$descrizione);
}
public static function importHashExists($hash)
{
return self::where('import_hash', $hash)->exists();
}
public static function importNoDuplicate($data)
{
$hash = Movimenti::makeHash($data['mov_data'], $data['mov_importo_dare'], $data['mov_importo_avere'], $data['mov_descrizione']);
$exists = self::where('import_hash', $hash)->exists();
if (!$exists) {
self::create([
// TODO: ...altri campi...
'mov_data' => $data['mov_data'],
'mov_fk_categoria' => $data['mov_fk_categoria'],
'mov_descrizione' => $data['mov_descrizione'],
'mov_importo_dare' => $data['mov_importo_dare'],
'mov_importo_avere' => $data['mov_importo_avere'],
'mov_inserito_da' => 1,
'conto_id_da' => 1,
'conto_id_a' => 1,
'mov_fk_tags' => 1,
'import_hash' => $hash,
]);
}
}
public static function getByTag($tag) { public static function getByTag($tag) {
$expression=DB::raw('SELECT $expression=DB::raw('SELECT
a.id, a.id,
a.mov_data, a.mov_data,
a.mov_importo, a.mov_importo_dare,
a.mov_importo_avere,
a.mov_descrizione, a.mov_descrizione,
c.cat_name, c.cat_name,
t.tag_name, t.tag_name,
@ -242,7 +295,7 @@ JOIN categories as c ON a.mov_fk_categoria=c.id';
public static function importEstrattoIng($filename) public static function importEstrattoIng($filename)
{ {
$inputPath='/var/www/html/gestionale_mt/public/storage/tenant'.tenant('id').'/'.$filename; $inputPath='/var/www/html/gestionale2025/storage/app/public/'.$filename;
// $outputPath='/var/www/html/gestionale_mt/public/tenant'.tenant('id').'/import/'.$filename.'.csv'; // $outputPath='/var/www/html/gestionale_mt/public/tenant'.tenant('id').'/import/'.$filename.'.csv';
$outputPath = $inputPath.'.csv'; $outputPath = $inputPath.'.csv';
rename($inputPath,$outputPath); rename($inputPath,$outputPath);
@ -275,7 +328,7 @@ JOIN categories as c ON a.mov_fk_categoria=c.id';
public static function importEstrattoCR($filename) public static function importEstrattoCR($filename)
{ {
$inputPath='/var/www/html/gestionale_mt/public/storage/tenant'.tenant('id').'/'.$filename; $inputPath='/var/www/html/gestionale2025/storage/app/public/'.$filename;
// $outputPath='/var/www/html/gestionale_mt/public/'.tenant('id')."/app/".$filename; // $outputPath='/var/www/html/gestionale_mt/public/'.tenant('id')."/app/".$filename;
$outputPath = $inputPath.'.csv'; $outputPath = $inputPath.'.csv';
rename($inputPath,$outputPath); rename($inputPath,$outputPath);
@ -317,46 +370,72 @@ JOIN categories as c ON a.mov_fk_categoria=c.id';
} }
// Test filtri categoria // Test filtri categoria
public function mapFieldByName($name) public static function retrieveHeaders($filename)
{ {
// definisce lo schema per rigenerare il file CSV secondo standard di importazione $inputPath= Storage::path($filename);
$fields = Illuminate\Support\Facade\Schema::getColumnListing('movimentis')->mapFieldByName; $outputPath = $inputPath.'.csv';
foreach ($fields as $field) rename($inputPath,$outputPath);
{
echo $column . "\n";
}
$rows = (new FastExcel)->configureCsv(';')->import($outputPath);
$headers = array_keys($rows->first());
return $headers;
} }
// Test applicazione regole public static function getDbFields()
public function setCategoriaMovimento($movimento)
{ {
$rules= \App\Models\ImportRule::all(); return [
"Data movimento"=>"mov_data",
"Descrizione"=>"mov_descrizione",
"Importo Dare"=>"mov_importo_dare",
"Importo Avere"=>"mov_importo_avere",
"Categoria"=>"mov_fk_categoria",
"Tag"=>"mov_fk_tags",
"Conto prelievo"=>"conto_id_da",
"Conto versamento"=>"conto_id_a",
];
}
foreach ($rules as $role)
{
if (preg_match("/$role->parola/i","$movimento->descrizione")) // Ritorna la categoria suggerita in base alla descrizione del movimento da utilizzare sul db
{ public static function getSuggestedCategory($descrizione)
$movimento->catemov_fk_categoria=$role->categoria_id; {
} $rule = ImportRule::whereRaw('? LIKE CONCAT("%", pattern, "%")', [$descrizione])->first();
else return $rule ? $rule->category_id : 1;
{ }
$movimento->catemov_fk_categoria=1;
public static function setCategoriesFromCsv($descrizione)
{
$rules = ImportRule::all();
$categoria = 1; // Default category
foreach ($rules as $rule) {
if (preg_match("/$rule->pattern/i", $descrizione)) {
$categoria = $rule->category_id;
break;
} }
} }
return $categoria;
} }
// Ritorna gli anni presenti nei movimenti
public static function getYearsFromMovimenti() public static function getYearsFromMovimenti()
{ {
$anni=DB::table('movimentis')->select(DB::raw('DISTINCT YEAR(mov_data) as anno'))->get(); if (env('DB_CONNECTION')=='mysql') {
// dd($anni); // for test purposes return self::selectRaw('DISTINCT YEAR(mov_data) as anno')
return $anni; ->orderBy('anno', 'desc')
->get();
} else
{
return self::selectRaw("strftime('%Y', mov_data) as anno")
->distinct()
->orderBy('anno', 'desc')
->get();
}
} }
@ -375,24 +454,27 @@ JOIN categories as c ON a.mov_fk_categoria=c.id';
} }
} }
// Ritorna la somma delle entrate per un anno specifico
public static function getEntrate($year) public static function getEntrate($year)
{ {
$entrate_anno=DB::table('movimentis') return self::whereYear('mov_data', $year)
->where('mov_importo','>',0) ->sum('mov_importo_avere');
->whereYear('mov_data', '=' , $year)
->sum('mov_importo');
//->get();
return $entrate_anno;
} }
// Ritorna la somma delle uscite per un anno specifico
public static function getUscite($year) public static function getUscite($year)
{ {
$uscite_anno=DB::table('movimentis') return self::whereYear('mov_data', $year)
->where('mov_importo','<',0) ->sum('mov_importo_dare');
->whereYear('mov_data', '=' , $year) }
->sum('mov_importo');
//->get(); public static function cleanImporto($importo){
return ($uscite_anno); // Rimuove i punti e sostituisce la virgola con un punto
$importo = str_replace('.', '', $importo);
$importo = str_replace(',', '.', $importo);
$importo = str_replace('-', '', $importo);
$importo = str_replace('+', '', $importo);
return $importo;
} }
} }

View File

@ -383,6 +383,16 @@ return [
'text' => 'Importa Estratto conto Cassa Rurale', 'text' => 'Importa Estratto conto Cassa Rurale',
'route' => 'importCR', 'route' => 'importCR',
], ],
[
'text' => 'Importa Generic CSV',
'route' => 'importGen',
],
[
'text' => 'Regole importazione',
'route' => 'import_rules.index',
// 'can' => ['import_rules'],
],
], ],
], ],
], ],

View File

@ -65,6 +65,14 @@ return [
'replace_placeholders' => true, 'replace_placeholders' => true,
], ],
'app' => [
'driver' => 'single',
'path' => storage_path('logs/gestionale2025.log'),
'level' => env('LOG_LEVEL', 'debug'),
'days' => env('LOG_DAILY_DAYS', 14),
'replace_placeholders' => true,
],
'daily' => [ 'daily' => [
'driver' => 'daily', 'driver' => 'daily',
'path' => storage_path('logs/laravel.log'), 'path' => storage_path('logs/laravel.log'),

View File

@ -20,12 +20,15 @@ class CreateMovimentisTable extends Migration
$table->unsignedBigInteger('mov_fk_categoria'); $table->unsignedBigInteger('mov_fk_categoria');
$table->foreign('mov_fk_categoria')->references('id')->on('categories'); $table->foreign('mov_fk_categoria')->references('id')->on('categories');
$table->longText('mov_descrizione'); $table->longText('mov_descrizione');
$table->decimal('mov_importo',8,2); $table->decimal('mov_importo_dare',8,2)->nullable();
$table->decimal('mov_importo_avere',8,2)->nullable();
$table->unsignedBigInteger('mov_inserito_da'); $table->unsignedBigInteger('mov_inserito_da');
$table->foreign('mov_inserito_da')->references('id')->on('users'); $table->foreign('mov_inserito_da')->references('id')->on('users');
$table->unsignedBigInteger('mov_fk_tags'); $table->unsignedBigInteger('mov_fk_tags');
$table->foreign('mov_fk_tags')->references('id')->on('tags'); $table->foreign('mov_fk_tags')->references('id')->on('tags');
$table->foreignId('conto_id')->constrained('contos'); $table->foreignId('conto_id_da')->constrained('contos')->nullable();
$table->foreignId('conto_id_a')->constrained('contos')->nullable();
$table->string('import_hash')->nullable()->index();
}); });
} }

View File

@ -14,6 +14,13 @@ return new class extends Migration
Schema::create('import_rules', function (Blueprint $table) { Schema::create('import_rules', function (Blueprint $table) {
$table->id(); $table->id();
$table->timestamps(); $table->timestamps();
$table->string('pattern');
$table->unsignedBigInteger('category_id');
$table->foreign('category_id')->references('id')->on('categories')->onDelete('cascade');
// $table->unique(['pattern', 'category_id'], 'unique_import_rule');
$table->string('description')->nullable(); // Optional description for the rule
$table->boolean('is_active')->default(true); // Flag to enable/disable the rule
$table->string('created_by')->nullable(); // User who created the rule
}); });
} }

View File

@ -17,9 +17,9 @@ class CategorieSeeder extends Seeder
// Inserisce le categorie necessarie // Inserisce le categorie necessarie
DB::table('categories')->insert( DB::table('categories')->insert(
[ [
'cat_name'=>'Automobili', 'cat_name'=>'Da Selezionare',
'cat_uscita'=>1, 'cat_uscita'=>1,
'cat_entrata'=>0, 'cat_entrata'=>1,
] ]
); );
DB::table('categories')->insert( DB::table('categories')->insert(
@ -30,9 +30,23 @@ class CategorieSeeder extends Seeder
); );
DB::table('categories')->insert( DB::table('categories')->insert(
[ [
'cat_name'=>'Utenze', 'cat_name'=>'Automobili',
'cat_uscita'=>1, 'cat_uscita'=>1,
'cat_entrata'=>0] 'cat_entrata'=>0]
); );
DB::table('categories')->insert(
[
'cat_name'=>'Alimentari',
'cat_uscita'=>1,
'cat_entrata'=>0,
]
);
DB::table('categories')->insert(
[
'cat_name'=>'Utenze',
'cat_uscita'=>1,
'cat_entrata'=>0,
]
);
} }
} }

View File

@ -0,0 +1,31 @@
$(document).ready(function() {
$('#category_id').change(function() {
if ($(this).val() === 'nuovo') {
$('#nuovo_valore').show();
$('#nuovo_aggiungi').show();
} else {
$('#nuovo_valore').hide();
$('#nuovo_aggiungi').hide();
}
});
$('#nuovo_aggiungi').click(function() {
var selectedValue = $('#category_id').val();
var nuovoValore = $('#nuovo_valore').val().trim();
if (selectedValue === 'nuovo' && nuovoValore !== '') {
$.post('/api/categorie', { cat_name: nuovoValore,cat_entrata: 1,cat_uscita: 1, _token: $('meta[name="csrf-token"]').attr('content') }, function(data) {
if (data.success) {
// Aggiungi la nuova categoria alla select e selezionala
$('#category_id').append(new Option(nuovoValore, data.id, true, true)).trigger('change');
$('#nuovo_valore').val('').hide();
$('#nuovo_aggiungi').hide();
} else {
alert('Errore durante l\'aggiunta della categoria: ' + data.message);
}
}, 'json');
} else {
alert('Per favore, inserisci un nome valido per la nuova categoria.');
}
});
});

View File

@ -1,161 +1,87 @@
$(document).ready(function() { $(document).ready(function() {
$('#listamovimenti').DataTable({ $('#listamovimenti').DataTable({
"responsive": true, responsive: true,
columnDefs: [ columnDefs: [{
{ target: 0,
target: 0, render: DataTable.render.date(),
render: DataTable.render.date(), }],
} order: [[0, "desc"]]
],
"order": [[0, "desc"]]
}); });
// $('.select2').select2(); // $('.select2').select2();
}); });
$(".draggable").draggable(); $(".draggable").draggable();
var d = new Date(); var d = new Date();
var month = d.getMonth()+1; var month = d.getMonth() + 1;
var day = d.getDate(); var day = d.getDate();
var strDate = d.getFullYear() + '-' + var strDate = d.getFullYear() + '-' + (month < 10 ? '0' : '') + month + '-' + (day < 10 ? '0' : '') + day;
(month<10 ? '0' : '') + month + '-' +
(day<10 ? '0' : '') + day; function fillSelect(url, selectName, selectedValue) {
$(selectName).empty();
$.getJSON(url, {}, function(items) {
$.each(items, function(i, item) {
var label = item.cat_name || item.tag_name || item.nomeConto;
$(selectName).append(new Option(label, item.id));
});
if (selectedValue) {
$(selectName).val(selectedValue).trigger('change');
}
});
}
function resetForm(formSelector, dateValue) {
$(formSelector).find('input[type="text"], textarea, input[type="number"], input[type="date"], option').val('');
$(formSelector).find('input[type="date"]').val(dateValue);
}
function openModal(tipo, actionUrl, catUrl, tagUrl, contoUrl, modalTitle) {
resetForm('#form', strDate);
$('#myModal').modal('show');
$('.modal-title').text(modalTitle);
$('#form').attr('action', actionUrl);
fillSelect(catUrl, "select[name='mov_fk_categoria']");
fillSelect(tagUrl, "select[name='mov_fk_tags']");
fillSelect(contoUrl, "select[name='conto_id']");
}
$(document).on('click', '.open_modal_spesa', function() { $(document).on('click', '.open_modal_spesa', function() {
console.log(strDate); openModal('spesa', '/admin/movimenti/spesa', '/admin/service/catlistSpesa', '/admin/service/taglist', '/admin/service/contolist', 'Nuovo movimento in uscita');
$("#categoria").empty(); $('#importo').attr('name', 'mov_importo_dare');
$("#tags").empty();
$('#form').find('input[type="text"], textarea, input[type="number"],input[type="date"],option').val("");
$('#form').find('input[type="date"]').val(strDate);
$('#myModal').modal('show');
$('.modal-title').text(' Nuovo movimento in uscita');
$('#form').attr('action', '/admin/movimenti/spesa');
$.getJSON("/admin/service/catlistSpesa", {}, function(cats) {
$.each(cats, function(i, cat) {
$("select[name='mov_fk_categoria']").append(
new Option(cat.cat_name, cat.id)
)
}
);
});
$.getJSON("/admin/service/taglist", {}, function(tags) {
$.each(tags, function(i, tag) {
$("select[name='mov_fk_tags']").append(
new Option(tag.tag_name, tag.id)
)
});
});
$.getJSON("/admin/service/contolist", {}, function(contis) {
$.each(contis, function(i, conto) {
$("select[name='conto_id']").append(
new Option(conto.nomeConto, conto.id)
)
}
);
});
}); });
$(document).on('click', '.open_modal_entrata', function() { $(document).on('click', '.open_modal_entrata', function() {
console.log(strDate); openModal('entrata', '/admin/movimenti/entrata', '/admin/service/catlistEntrata', '/admin/service/taglist', '/admin/service/contolist', 'Nuovo movimento in entrata');
$("#categoria").empty(); $('#importo').attr('name', 'mov_importo_avere');
$("#tags").empty();
$('#form').find('input[type="text"], textarea, input[type="number"],option').val("");
$('#form').find('input[type="date"]').val(strDate);
$('#myModal').modal('show');
$('.modal-title').text('Nuovo movimento in entrata');
$('#form').attr('action', '/admin/movimenti/entrata');
$.getJSON("/admin/service/catlistEntrata", {}, function(data) {
$.each(data, function(i, item) {
$("select[name='mov_fk_categoria']").append(
new Option(item.cat_name, item.id)
)
}
);
});
$.getJSON("/admin/service/taglist", {}, function(data) {
$.each(data, function(i, item) {
$("select[name='mov_fk_tags']").append(
new Option(item.tag_name, item.id)
)
});
});
$.getJSON("/admin/service/contolist", {}, function(contis) {
$.each(contis, function(i, conto) {
$("select[name='conto_id']").append(
new Option(conto.nomeConto, conto.id)
)
}
);
});
}); });
$(document).on('click', '.open_modal_modifica', function() { $(document).on('click', '.open_modal_modifica', function() {
var url = "/admin/movimenti/modify";
var riga_id = $(this).val(); var riga_id = $(this).val();
$("#categoria").empty(); $.getJSON('/admin/movimenti/modify/' + riga_id, function(data) {
$("#tags").empty(); resetForm('#form', data[0].mov_data.substring(0, 10));
$.getJSON(url + '/' + riga_id, function(data) { fillSelect('/admin/service/catlist', "select[name='mov_fk_categoria']", data[0].cat_name);
// success data fillSelect('/admin/service/taglist', "select[name='mov_fk_tags']", data[0].tag_name);
console.log(data[0]); fillSelect('/admin/service/contolist', "select[name='conto_id']", data[0].nomeConto);
$.getJSON("/admin/service/taglist", {}, function(tags) {
$.each(tags, function(i, tag) {
$("select[name='mov_fk_tags']").append(
new Option(tag.tag_name, tag.id)
)
$('#tags')
.find('option:contains(' + data[0].tag_name + ')')
.prop('selected', true)
.trigger('change');
});
});
$.getJSON("/admin/service/catlist", {}, function(cats) {
$.each(cats, function(i, cat) {
$("select[name='mov_fk_categoria']").append(
new Option(cat.cat_name, cat.id)
)
$('#categoria')
.find('option:contains(' + data[0].cat_name + ')')
.prop('selected', true)
.trigger('change');
}
);
});
$.getJSON("/admin/service/contolist", {}, function(contis) {
$.each(contis, function(i, conto) {
$("select[name='conto_id']").append(
new Option(conto.nomeConto, conto.id)
)
$('#conto_id')
.find('option:contains(' + data[0].nomeConto + ')')
.prop('selected', true)
.trigger('change');
}
);
});
$('.modal-title').text('Modifica movimento');
$('#data').val(data[0].mov_data);
$('#descrizione').val(data[0].mov_descrizione); $('#descrizione').val(data[0].mov_descrizione);
$('#importo').val(data[0].mov_importo);
$('#myModal').modal('show'); // Imposta importo e name in base a DARE/AVERE
// $('.panel-heading').text('Modifica movimento'); if (data[0].mov_importo_dare && parseFloat(data[0].mov_importo_dare) !== 0) {
$('#importo').val(data[0].mov_importo_dare);
$('#importo').attr('name', 'mov_importo_dare');
} else if (data[0].mov_importo_avere && parseFloat(data[0].mov_importo_avere) !== 0) {
$('#importo').val(data[0].mov_importo_avere);
$('#importo').attr('name', 'mov_importo_avere');
} else {
$('#importo').val('');
$('#importo').attr('name', 'mov_importo');
}
$('.modal-title').text('Modifica movimento');
$('#form').attr('action', '/admin/movimenti/modify'); $('#form').attr('action', '/admin/movimenti/modify');
$('#form').append('<input type="hidden" name="id" value="' + riga_id + '">'); $('#form').append('<input type="hidden" name="id" value="' + riga_id + '">');
$('#myModal').modal('show');
});
});
/*
$.getJSON("/admin/service/taglist", {}, function(data) {
$.each(data, function(i, item) {
$("select[name='mov_fk_tags']").append(
new Option(item.tag_name, item.id)
)
}); });
}); });
*/

View File

@ -0,0 +1,27 @@
@extends('adminlte::page')
@section('content_header')
<h1>Importazione Estratto conto Generico</h1>
@endsection
@section('content')
<div class="container">
<!-- Content here -->
<div class="row">
<div class="col-lg-10">
<form action="" method="POST" enctype='multipart/form-data'>
@csrf
<div class="mb-3">
<label for="file" class="form-label">File</label>
<input type="file" class="form-control" id="file" name="filename">
</div>
<div class="mb-3">
<button type="submit" class="btn btn-primary">Submit</button>
</div>
</form>
</div>
</div>
</div>
@endsection

View File

@ -0,0 +1,29 @@
@extends('adminlte::page')
@section('content_header')
<h3> Mappa i campi </h3>
@endsection
@section('content')
<form action="{{ route('conti.map.store') }}" method="POST">
@csrf
@foreach ($db as $header=>$table)
<div class="row">
<div class="col-md-3">
<label>{{ $header }}</label>
</div>
<div class="col-md-6">
<select name="mapping[{{ $table }}]">
<option value="">--Nessuno o Selezionare una colonna--</option>
@foreach($csv as $csv_header)
<option value="{{ $csv_header }}">{{ $csv_header }}</option>
@endforeach
</select>
</div>
</div>
@endforeach
<input type="hidden" name="filename" value="{{ $filename .".csv" }}">
<button type="submit">Salva</button>
</form>
@endsection

View File

@ -32,21 +32,26 @@
<tr> <tr>
<th>Data</th> <th>Data</th>
<th>Categoria</th> <th>Categoria</th>
<th>Conto</th> <th>Conto Dare</th>
<th>Conto Avere</th>
<th>Descrizione</th> <th>Descrizione</th>
<th>Importo</th> <th>Importo Dare</th>
<th>Importo Avere</th>
<th>Azione</th> <th>Azione</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@foreach( $movimenti as $movimento ) @foreach( $movimenti as $movimento )
<tr> <tr>
<td>{{ $movimento->mov_data}}</td> <td>{{ $movimento->mov_data}}</td>
<td>{{ $movimento->cat_name }}</td> <td>{{ $movimento->Categorie->cat_name ?? ''}}</td>
<td>{{ $movimento->nomeConto }}</td> <!-- Uso l'operatore di coalescenza nulla per evitare errori se il conto non esiste -->
<td>{{ $movimento->ContoDa->nomeConto ?? '' }}</td>
<td>{{ $movimento->ContoA->nomeConto ?? '' }}</td>
<!-- Uso l'operatore di coalescenza nulla per evitare errori se il tag non esiste -->
<td>{{ $movimento->mov_descrizione }}</td> <td>{{ $movimento->mov_descrizione }}</td>
<td>&euro; {{ $movimento->mov_importo }}</td> <td>{{ $movimento->mov_importo_dare .""}}</td>
<td>{{ $movimento->mov_importo_avere.""}}</td>
<td> <td>
<button class="btn btn-warning btn-detail open_modal_modifica" value="{{ $movimento->id }}"><i class="fa-solid fa-pencil"></i></button>&nbsp; <button class="btn btn-warning btn-detail open_modal_modifica" value="{{ $movimento->id }}"><i class="fa-solid fa-pencil"></i></button>&nbsp;
<a class="btn btn-danger" href="/admin/movimenti/delete?id={{ $movimento->id }}"><i class="fa-solid fa-trash-can"></i></a>&nbsp; <a class="btn btn-danger" href="/admin/movimenti/delete?id={{ $movimento->id }}"><i class="fa-solid fa-trash-can"></i></a>&nbsp;
@ -69,6 +74,8 @@
</div> </div>
</div> </div>
</div> </div>
<!-- MODAL NEW --> <!-- MODAL NEW -->
<div class="modal fade " id="myModal" tabindex="-1" role="dialog" <div class="modal fade " id="myModal" tabindex="-1" role="dialog"
aria-labelledby="myModalLabel" aria-hidden="true"> aria-labelledby="myModalLabel" aria-hidden="true">
@ -108,10 +115,10 @@
<div class="col-xs-5"> <div class="col-xs-5">
<label for="importo" class="form-label">Importo</label> <label for="importo" class="form-label">Importo</label>
<div class="input-group"> <div class="input-group">
<span class="input-group-addon"> <i class="fa fa-eur"></i> <span class="input-group-addon"> <i class="fa fa-eur"></i></span>
</span> <input type="number" step="0.01" min="-999999" <input type="number" step="0.01" min="-999999"
max="999999" class="form-control" id="importo" size="50" max="999999" class="form-control" id="importo" size="50"
name="mov_importo" aria-describedby="importo"> name="mov_importo_dare" aria-describedby="importo">
</div> </div>
</div> </div>
<div class="col-xs-7"> <div class="col-xs-7">

View File

@ -0,0 +1,53 @@
@extends('adminlte::page')
@section('content_header')
<h1>Crea una regola di importazione</h1>
@stop
@section('content')
<div class="container">
<p>Compila il modulo per creare una nuova regola di importazione.</p>
<div class="card">
<div class="card-header">
<h3 class="card-title">Nuova Regola di Importazione</h3>
</div>
<div class="card-body">
<form action="{{ route('import_rules.store') }}" method="POST">
@csrf
<div class="form-group">
<label for="pattern">Pattern</label>
<input type="text" name="pattern" id="pattern" class="form-control" required>
</div>
<div class="form-group">
<label for="category_id">Categoria</label>
<select name="category_id" id="category_id" class="form-control" required>
<option value="">Seleziona una categoria</option>
@foreach($categorie as $category)
<option value="{{ $category->id }}">{{ $category->cat_name }}</option>
@endforeach
<option value="nuovo">Crea nuova categoria</option>
</select>
<input type="text" name="nuovo_valore" id="nuovo_valore" class="form-control mt-2" placeholder="Nome nuova categoria" style="display:none;">
<button type="button" class="btn btn-primary mt-2" id="nuovo_aggiungi" style="display:none;">Crea Categoria</button>
<div class="form-group">
<label for="description">Descrizione</label>
<textarea name="description" id="description" class="form-control" rows="3" required></textarea>
</div>
<div class="form-group">
<button type="submit" class="btn btn-primary">Crea Regola</button>
<a href="{{ route('import_rules.index') }}" class="btn btn-secondary">Annulla</a>
</div>
</form>
</div>
<div class="card-footer">
<a href="{{ route('import_rules.index') }}" class="btn btn-secondary">Torna alla lista</a>
</div>
</div>
</div>
@endsection
@section('js')
<script src="/js/app/import_rules.js"></script>
@endsection')

View File

@ -0,0 +1,65 @@
@extends('adminlte::page')
@section('content_header')
<h1>Regole di importazione e assegnazione delle categorie</h1>
@endsection
@section('content')
<div class="container">
<p>Qui puoi gestire le regole di importazione e assegnazione delle categorie.</p>
<div class="card">
<div class="card-header">
<h3 class="card-title">Regole di Importazione</h3>
</div>
<div class="card-body">
<table class="table table-bordered" id="importRulesTable">
<thead>
<tr>
<th>ID</th>
<th>Pattern</th>
<th>Descrizione</th>
<th>Categoria</th>
<th>Azioni</th>
</tr>
</thead>
<tbody>
@foreach($rules as $rule)
<tr>
<td>{{ $rule->id }}</td>
<td>{{ $rule->pattern }}</td>
<td>{{ $rule->description }}</td>
<td>{{ \App\Models\Categorie::find($rule->category_id)->cat_name ?? 'N/A' }}</td>
<td>
<a href="{{ route('import_rules.edit', $rule->id) }}" class="btn btn-primary btn-sm">Modifica</a>
<form action="{{ route('import_rules.destroy', $rule->id) }}" method="POST" style="display:inline;">
@csrf
@method('DELETE')
<button type="submit" class="btn btn-danger btn-sm">Elimina</button>
</form>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
<div class="card-footer">
<a href="{{ route('import_rules.create') }}" class="btn btn-success">Aggiungi Nuova Regola</a>
</div>
</div>
</div>
@endsection
@section('js')
<script>
$(document).ready(function() {
$('#importRulesTable').DataTable({
responsive: true,
order: [[0, "desc"]]
});
// $('.select2').select2();
});
</script>
@endsection')

View File

@ -26,6 +26,7 @@ use App\Http\Controllers\ContrattiController;
use App\Http\Controllers\TodolistController; use App\Http\Controllers\TodolistController;
use App\Http\Controllers\ContoController; use App\Http\Controllers\ContoController;
use App\Http\Controllers\GenDocController; use App\Http\Controllers\GenDocController;
use App\Http\Controllers\ImportRuleController;
use App\Mail\myTestEmail; use App\Mail\myTestEmail;
// API // API
@ -94,12 +95,26 @@ Route::middleware([
Route::post('admin/movimenti/import', [MovimentiController::class,'importEC_ING']); Route::post('admin/movimenti/import', [MovimentiController::class,'importEC_ING']);
Route::get('admin/movimenti/importcr', [MovimentiController::class,'importFileCR'])->name('importCR'); Route::get('admin/movimenti/importcr', [MovimentiController::class,'importFileCR'])->name('importCR');
Route::post('admin/movimenti/importcr', [MovimentiController::class,'importEC_CR']); Route::post('admin/movimenti/importcr', [MovimentiController::class,'importEC_CR']);
Route::get('/admin/movimenti/giroconto', [MovimentiController::class,'giroconto'])->name('giroconto'); Route::get('admin/movimenti/importgen', [MovimentiController::class,'importFileGen'])->name('importGen');
Route::post('/admin/movimenti/giroconto', [MovimentiController::class,'girocontoPost']); Route::post('admin/movimenti/importgen', [MovimentiController::class,'importGenericCsv']);
Route::post('admin/movimenti/importmapped', [MovimentiController::class,'importmappedCsv'])->name('conti.map.store');
Route::get('/admin/movimenti/import_rules', [ImportRuleController::class, 'index'])->name('import_rules.index');
Route::get('/admin/movimenti/import_rules/create', [ImportRuleController::class, 'create'])->name('import_rules.create');
Route::post('/admin/movimenti/import_rules', [ImportRuleController::class, 'store'])->name('import_rules.store');
Route::get('/admin/movimenti/import_rules/{import_rule}', [ImportRuleController::class, 'show'])->name('import_rules.show');
Route::get('/admin/movimenti/import_rules/{import_rule}/edit', [ImportRuleController::class, 'edit'])->name('import_rules.edit');
Route::put('/admin/movimenti/import_rules/{import_rule}', [ImportRuleController::class, 'update'])->name('import_rules.update');
Route::delete('/admin/movimenti/import_rules/{import_rule}', [ImportRuleController::class, 'destroy'])->name('import_rules.destroy');
Route::resource('admin/conti', ContoController::class); Route::resource('admin/conti', ContoController::class);
Route::get('/admin/movimenti/giroconto', [MovimentiController::class,'giroconto'])->name('giroconto');
Route::post('/admin/movimenti/giroconto', [MovimentiController::class,'girocontoPost']);
// CATEGORIE // CATEGORIE
Route::get('admin/categorie', [CategorieController::class,'listCategorie'])->name('categorie'); Route::get('admin/categorie', [CategorieController::class,'listCategorie'])->name('categorie');
Route::post('admin/categorie', [CategorieController::class,'insCategorie']); Route::post('admin/categorie', [CategorieController::class,'insCategorie']);
@ -262,5 +277,6 @@ Route::middleware('auth:sanctum')->group(function () {
Route::delete('/api/conti/{id}', [ApiContoController::class, 'deleteConto']); Route::delete('/api/conti/{id}', [ApiContoController::class, 'deleteConto']);
});
}); });
});