RecordOld.php 54 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410
  1. <?php
  2. namespace App\Http\Livewire;
  3. use Livewire\Component;
  4. use DateInterval;
  5. use DatePeriod;
  6. use DateTime;
  7. use PhpOffice\PhpSpreadsheet\Spreadsheet;
  8. use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
  9. use Illuminate\Support\Facades\Storage;
  10. use Illuminate\Support\Facades\Log;
  11. use Illuminate\Support\Facades\Mail;
  12. use App\Mail\ExportNotification;
  13. use App\Jobs\ExportPrimaNota;
  14. use App\Http\Middleware\TenantMiddleware;
  15. class RecordOld extends Component
  16. {
  17. public $records, $dataId, $totals;
  18. public $in;
  19. public $out;
  20. public $payments = [];
  21. public $fromDate;
  22. public $toDate;
  23. public $appliedFromDate;
  24. public $appliedToDate;
  25. public $exportFromDate;
  26. public $exportToDate;
  27. public $isExporting = false;
  28. public $selectedPeriod = 'OGGI';
  29. public $filterCausals = null;
  30. public $filterMember = null;
  31. public $isFiltering = false;
  32. public array $recordDatas = [];
  33. public array $labels = [];
  34. public array $causals = [];
  35. public $members = array();
  36. public $sendViaEmail = false;
  37. public $exportEmailAddress = '';
  38. public $exportEmailSubject = 'Prima Nota - Export';
  39. private $causalAmounts = [];
  40. protected $rules = [
  41. 'exportEmailAddress' => 'required_if:sendViaEmail,true|email',
  42. 'exportEmailSubject' => 'required_if:sendViaEmail,true|string|max:255',
  43. ];
  44. protected $messages = [
  45. 'exportEmailAddress.required_if' => 'L\'indirizzo email è obbligatorio quando si sceglie di inviare via email.',
  46. 'exportEmailAddress.email' => 'Inserisci un indirizzo email valido.',
  47. 'exportEmailSubject.required_if' => 'L\'oggetto dell\'email è obbligatorio.',
  48. 'exportEmailSubject.max' => 'L\'oggetto dell\'email non può superare i 255 caratteri.',
  49. ];
  50. public function boot()
  51. {
  52. app(TenantMiddleware::class)->setupTenantConnection();
  53. }
  54. public function hydrate()
  55. {
  56. $this->emit('load-select');
  57. }
  58. public function mount()
  59. {
  60. $this->fromDate = date("Y-m-d");
  61. $this->toDate = date("Y-m-d");
  62. $this->appliedFromDate = date("Y-m-d");
  63. $this->appliedToDate = date("Y-m-d");
  64. $this->exportFromDate = date("Y-m-d");
  65. $this->exportToDate = date("Y-m-d");
  66. $this->exportEmailSubject = 'Prima Nota - Export del ' . date('d/m/Y');
  67. $this->getCausals(\App\Models\Causal::select('id', 'name')->where('parent_id', null)->get(), 0);
  68. $this->members = \App\Models\Member::select(['id', 'first_name', 'last_name', 'fiscal_code'])->orderBy('last_name')->orderBy('first_name')->get();
  69. $this->payments = \App\Models\PaymentMethod::select('id', 'name', 'type')->where('enabled', true)->where('money', false)->get();
  70. }
  71. private function generateExportDataAndTotals($fromDate, $toDate)
  72. {
  73. Log::info('generateExportDataAndTotals: Start (combined method)', [
  74. 'from_date' => $fromDate,
  75. 'to_date' => $toDate,
  76. 'memory_before' => memory_get_usage(true)
  77. ]);
  78. $exportRecords = array();
  79. $exportTotals = array();
  80. Log::info('generateExportDataAndTotals: Getting excluded members');
  81. $exclude_from_records = \App\Models\Member::where('exclude_from_records', true)->pluck('id')->toArray();
  82. Log::info('generateExportDataAndTotals: Excluded members retrieved', ['count' => count($exclude_from_records)]);
  83. Log::info('generateExportDataAndTotals: Building main query');
  84. $datas = \App\Models\Record::with('member', 'supplier', 'payment_method')
  85. ->select(
  86. 'records.*',
  87. 'records_rows.id as row_id',
  88. 'records_rows.record_id',
  89. 'records_rows.causal_id',
  90. 'records_rows.amount',
  91. 'records_rows.note',
  92. 'records_rows.when',
  93. 'records_rows.vat_id',
  94. 'records_rows.created_at as row_created_at',
  95. 'records_rows.updated_at as row_updated_at'
  96. )
  97. ->join('records_rows', 'records.id', '=', 'records_rows.record_id')
  98. ->whereBetween('date', [$fromDate, $toDate])
  99. ->where(function ($query) {
  100. $query->where('type', 'OUT')
  101. ->orWhere(function ($query) {
  102. $query->where('records.corrispettivo_fiscale', true)
  103. ->orWhere('records.commercial', false);
  104. });
  105. })
  106. ->where(function ($query) use ($exclude_from_records) {
  107. $query->where('type', 'OUT')
  108. ->orWhere(function ($subquery) use ($exclude_from_records) {
  109. $subquery->whereNotIn('member_id', $exclude_from_records);
  110. });
  111. });
  112. Log::info('generateExportDataAndTotals: Applying causal filters');
  113. if ($this->filterCausals != null && sizeof($this->filterCausals) > 0) {
  114. $causals = array();
  115. foreach ($this->filterCausals as $z) {
  116. $causals[] = $z;
  117. $childs = \App\Models\Causal::where('parent_id', $z)->get();
  118. foreach ($childs as $c) {
  119. $causals[] = $c->id;
  120. $childsX = \App\Models\Causal::where('parent_id', $c->id)->get();
  121. foreach ($childsX as $cX) {
  122. $causals[] = $cX->id;
  123. }
  124. }
  125. }
  126. $datas->whereIn('causal_id', $causals);
  127. Log::info('generateExportDataAndTotals: Causal filters applied', ['causal_count' => count($causals)]);
  128. }
  129. if ($this->filterMember != null && $this->filterMember > 0) {
  130. $datas->where('member_id', $this->filterMember);
  131. Log::info('generateExportDataAndTotals: Member filter applied', ['member_id' => $this->filterMember]);
  132. }
  133. Log::info('generateExportDataAndTotals: Executing query');
  134. $queryStart = microtime(true);
  135. $datas = $datas->orderBy('date', 'ASC')
  136. ->orderBy('records.created_at', 'ASC')
  137. ->orderBy('records_rows.id', 'ASC')
  138. ->get();
  139. $queryTime = microtime(true) - $queryStart;
  140. Log::info('generateExportDataAndTotals: Query executed', [
  141. 'record_count' => $datas->count(),
  142. 'query_time' => $queryTime,
  143. 'memory_after_query' => memory_get_usage(true)
  144. ]);
  145. $groupedData = [];
  146. $causalsCount = [];
  147. $processedCount = 0;
  148. // Initialize totals array
  149. foreach ($this->payments as $p) {
  150. $exportTotals[$p->name] = ["IN" => 0, "OUT" => 0];
  151. }
  152. Log::info('generateExportDataAndTotals: Starting combined data processing loop');
  153. $loopStart = microtime(true);
  154. foreach ($datas as $idx => $data) {
  155. if ($processedCount % 100 == 0) {
  156. Log::info('generateExportDataAndTotals: Processing progress', [
  157. 'processed' => $processedCount,
  158. 'total' => $datas->count(),
  159. 'memory_current' => memory_get_usage(true),
  160. 'memory_peak' => memory_get_peak_usage(true)
  161. ]);
  162. }
  163. try {
  164. $causalCheck = \App\Models\Causal::findOrFail($data->causal_id);
  165. $paymentCheck = $data->payment_method->money;
  166. if (!$paymentCheck && ($causalCheck->no_first == null || !$causalCheck->no_first)) {
  167. if (!$data->deleted) {
  168. $amount = $data->amount;
  169. $amount += getVatValue($amount, $data->vat_id);
  170. } else {
  171. $amount = $data->amount;
  172. }
  173. // CALCULATE TOTALS HERE (in the same loop)
  174. if (!$data->deleted) {
  175. $exportTotals[$data->payment_method->name][$data->type] += $amount;
  176. }
  177. $isCommercial = ($data->commercial == 1 || $data->commercial === '1' || $data->commercial === true);
  178. $typeLabel = $isCommercial ? 'Commerciale' : 'Non Commerciale';
  179. $nominativo = '';
  180. if ($data->type == "IN") {
  181. if ($data->member) {
  182. $nominativo = $data->member->last_name . " " . $data->member->first_name;
  183. }
  184. } else {
  185. if ($data->supplier) {
  186. $nominativo = $data->supplier->name;
  187. }
  188. }
  189. $groupKey = $data->date . '|' . $typeLabel . '|' . $data->payment_method->name . '|' . $data->type . '|' . $nominativo;
  190. if (!isset($groupedData[$groupKey])) {
  191. $groupedData[$groupKey] = [
  192. 'date' => $data->date,
  193. 'type_label' => $typeLabel,
  194. 'payment_method' => $data->payment_method->name,
  195. 'transaction_type' => $data->type,
  196. 'nominativo' => $nominativo,
  197. 'amount' => 0,
  198. 'deleted' => false,
  199. 'causals' => [],
  200. 'notes' => []
  201. ];
  202. $causalsCount[$groupKey] = [];
  203. }
  204. $groupedData[$groupKey]['amount'] += $amount;
  205. $causalsCount[$groupKey][$causalCheck->getTree()] = true;
  206. if (!empty($data->note)) {
  207. $groupedData[$groupKey]['notes'][] = $data->note;
  208. }
  209. if ($data->deleted) {
  210. $groupedData[$groupKey]['deleted'] = true;
  211. }
  212. }
  213. $processedCount++;
  214. } catch (\Exception $e) {
  215. Log::error('generateExportDataAndTotals: Error processing individual record', [
  216. 'record_id' => $data->id ?? 'unknown',
  217. 'error' => $e->getMessage(),
  218. 'processed_so_far' => $processedCount
  219. ]);
  220. throw $e;
  221. }
  222. }
  223. $loopTime = microtime(true) - $loopStart;
  224. Log::info('generateExportDataAndTotals: Combined processing loop completed', [
  225. 'total_processed' => $processedCount,
  226. 'grouped_records' => count($groupedData),
  227. 'loop_time' => $loopTime,
  228. 'memory_after_loop' => memory_get_usage(true)
  229. ]);
  230. Log::info('generateExportDataAndTotals: Building final export records');
  231. $finalStart = microtime(true);
  232. foreach ($groupedData as $groupKey => $group) {
  233. $causalsInGroup = array_keys($causalsCount[$groupKey]);
  234. $causalDisplay = $group['type_label'];
  235. if (count($causalsInGroup) > 1) {
  236. $detailDisplay = 'Varie|' . implode('|', $causalsInGroup);
  237. } else {
  238. $detailDisplay = $causalsInGroup[0];
  239. }
  240. $recordKey = $group['date'] . "§" . $causalDisplay . "§" . $group['nominativo'] . "§" . $detailDisplay . "§" . ($group['deleted'] ? 'DELETED' : '') . "§";
  241. if (!isset($exportRecords[$recordKey][$group['payment_method']][$group['transaction_type']])) {
  242. $exportRecords[$recordKey][$group['payment_method']][$group['transaction_type']] = 0;
  243. }
  244. $exportRecords[$recordKey][$group['payment_method']][$group['transaction_type']] += $group['amount'];
  245. }
  246. $finalTime = microtime(true) - $finalStart;
  247. Log::info('generateExportDataAndTotals: Final processing completed', [
  248. 'final_export_records' => count($exportRecords),
  249. 'final_export_totals' => count($exportTotals),
  250. 'final_time' => $finalTime,
  251. 'total_time' => microtime(true) - $loopStart + $queryTime,
  252. 'memory_final' => memory_get_usage(true),
  253. 'memory_peak' => memory_get_peak_usage(true)
  254. ]);
  255. // Cleanup
  256. unset($datas, $groupedData, $causalsCount);
  257. gc_collect_cycles();
  258. Log::info('generateExportDataAndTotals: Completed with cleanup', [
  259. 'memory_after_cleanup' => memory_get_usage(true)
  260. ]);
  261. return ['records' => $exportRecords, 'totals' => $exportTotals];
  262. }
  263. private function generateExportTotals($fromDate, $toDate)
  264. {
  265. $exportTotals = array();
  266. $exclude_from_records = \App\Models\Member::where('exclude_from_records', true)->pluck('id')->toArray();
  267. $datas = \App\Models\Record::with('member', 'supplier', 'payment_method')
  268. ->select(
  269. 'records.*',
  270. 'records_rows.id as row_id',
  271. 'records_rows.record_id',
  272. 'records_rows.causal_id',
  273. 'records_rows.amount',
  274. 'records_rows.note',
  275. 'records_rows.when',
  276. 'records_rows.vat_id',
  277. )
  278. ->join('records_rows', 'records.id', '=', 'records_rows.record_id')
  279. ->whereBetween('date', [$fromDate, $toDate])
  280. ->where(function ($query) {
  281. $query->where('type', 'OUT')
  282. ->orWhere(function ($query) {
  283. $query->where('records.corrispettivo_fiscale', true)
  284. ->orWhere('records.commercial', false);
  285. });
  286. })
  287. ->where(function ($query) use ($exclude_from_records) {
  288. $query->where('type', 'OUT')
  289. ->orWhere(function ($subquery) use ($exclude_from_records) {
  290. $subquery->whereNotIn('member_id', $exclude_from_records);
  291. });
  292. });
  293. if ($this->filterCausals != null && sizeof($this->filterCausals) > 0) {
  294. $causals = array();
  295. foreach ($this->filterCausals as $z) {
  296. $causals[] = $z;
  297. $childs = \App\Models\Causal::where('parent_id', $z)->get();
  298. foreach ($childs as $c) {
  299. $causals[] = $c->id;
  300. $childsX = \App\Models\Causal::where('parent_id', $c->id)->get();
  301. foreach ($childsX as $cX) {
  302. $causals[] = $cX->id;
  303. }
  304. }
  305. }
  306. $datas->whereIn('causal_id', $causals);
  307. }
  308. if ($this->filterMember != null && $this->filterMember > 0) {
  309. $datas->where('member_id', $this->filterMember);
  310. }
  311. $datas = $datas->orderBy('date', 'ASC')
  312. ->orderBy('records.created_at', 'ASC')
  313. ->orderBy('records_rows.id', 'ASC')
  314. ->get();
  315. foreach ($datas as $data) {
  316. $causalCheck = \App\Models\Causal::findOrFail($data->causal_id);
  317. $paymentCheck = $data->payment_method->money;
  318. if (!$paymentCheck && ($causalCheck->no_first == null || !$causalCheck->no_first)) {
  319. if (!$data->deleted) {
  320. $amount = $data->amount;
  321. $amount += getVatValue($amount, $data->vat_id);
  322. } else {
  323. $amount = $data->amount;
  324. }
  325. if (!isset($exportTotals[$data->payment_method->name])) {
  326. $exportTotals[$data->payment_method->name]["IN"] = 0;
  327. $exportTotals[$data->payment_method->name]["OUT"] = 0;
  328. }
  329. if (!$data->deleted)
  330. $exportTotals[$data->payment_method->name][$data->type] += $amount;
  331. }
  332. }
  333. return $exportTotals;
  334. }
  335. public function resetFilters()
  336. {
  337. $this->selectedPeriod = 'OGGI';
  338. $this->filterCausals = [];
  339. $this->filterMember = null;
  340. $today = date("Y-m-d");
  341. $this->fromDate = $today;
  342. $this->toDate = $today;
  343. $this->appliedFromDate = $today;
  344. $this->appliedToDate = $today;
  345. $this->emit('filters-reset');
  346. }
  347. public function applyFilters()
  348. {
  349. $this->isFiltering = true;
  350. $this->setPeriodDates();
  351. $this->appliedFromDate = $this->fromDate;
  352. $this->appliedToDate = $this->toDate;
  353. $this->render();
  354. $this->isFiltering = false;
  355. $this->emit('filters-applied');
  356. }
  357. private function setPeriodDates()
  358. {
  359. $today = now();
  360. switch ($this->selectedPeriod) {
  361. case 'OGGI':
  362. $this->fromDate = $today->format('Y-m-d');
  363. $this->toDate = $today->format('Y-m-d');
  364. break;
  365. case 'IERI':
  366. $yesterday = $today->copy()->subDay();
  367. $this->fromDate = $yesterday->format('Y-m-d');
  368. $this->toDate = $yesterday->format('Y-m-d');
  369. break;
  370. case 'MESE CORRENTE':
  371. $this->fromDate = $today->copy()->startOfMonth()->format('Y-m-d');
  372. $this->toDate = $today->copy()->endOfMonth()->format('Y-m-d');
  373. break;
  374. case 'MESE PRECEDENTE':
  375. $lastMonth = $today->copy()->subMonth();
  376. $this->fromDate = $lastMonth->startOfMonth()->format('Y-m-d');
  377. $this->toDate = $lastMonth->endOfMonth()->format('Y-m-d');
  378. break;
  379. case 'ULTIMO TRIMESTRE':
  380. $this->fromDate = $today->copy()->subMonths(3)->format('Y-m-d');
  381. $this->toDate = $today->format('Y-m-d');
  382. break;
  383. case 'ULTIMO QUADRIMESTRE':
  384. $this->fromDate = $today->copy()->subMonths(4)->format('Y-m-d');
  385. $this->toDate = $today->format('Y-m-d');
  386. break;
  387. }
  388. }
  389. public function getCausals($records, $indentation)
  390. {
  391. foreach ($records as $record) {
  392. $this->causals[] = array('id' => $record->id, 'name' => $record->getTree(), 'text' => $record->getTree(), 'level' => $indentation);
  393. if (count($record->childs))
  394. $this->getCausals($record->childs, $indentation + 1);
  395. }
  396. }
  397. public function getMonth($m)
  398. {
  399. $ret = '';
  400. switch ($m) {
  401. case 1:
  402. $ret = 'Gennaio';
  403. break;
  404. case 2:
  405. $ret = 'Febbraio';
  406. break;
  407. case 3:
  408. $ret = 'Marzo';
  409. break;
  410. case 4:
  411. $ret = 'Aprile';
  412. break;
  413. case 5:
  414. $ret = 'Maggio';
  415. break;
  416. case 6:
  417. $ret = 'Giugno';
  418. break;
  419. case 7:
  420. $ret = 'Luglio';
  421. break;
  422. case 8:
  423. $ret = 'Agosto';
  424. break;
  425. case 9:
  426. $ret = 'Settembre';
  427. break;
  428. case 10:
  429. $ret = 'Ottobre';
  430. break;
  431. case 11:
  432. $ret = 'Novembre';
  433. break;
  434. case 12:
  435. $ret = 'Dicembre';
  436. break;
  437. default:
  438. $ret = '';
  439. break;
  440. }
  441. return $ret;
  442. }
  443. public function render()
  444. {
  445. $month = 0;
  446. $year = 0;
  447. $this->records = array();
  448. $this->totals = array();
  449. $this->causalAmounts = array();
  450. $exclude_from_records = \App\Models\Member::where('exclude_from_records', true)->pluck('id')->toArray();
  451. $datas = \App\Models\Record::with('member', 'supplier', 'payment_method')
  452. ->select(
  453. 'records.*',
  454. 'records_rows.id as row_id',
  455. 'records_rows.record_id',
  456. 'records_rows.causal_id',
  457. 'records_rows.amount',
  458. 'records_rows.note',
  459. 'records_rows.when',
  460. 'records_rows.vat_id',
  461. )
  462. ->join('records_rows', 'records.id', '=', 'records_rows.record_id')
  463. ->whereBetween('date', [$this->appliedFromDate, $this->appliedToDate])
  464. ->where(function ($query) {
  465. $query->where('type', 'OUT')
  466. ->orWhere(function ($query) {
  467. $query->where('records.corrispettivo_fiscale', true)
  468. ->orWhere('records.commercial', false);
  469. });
  470. })
  471. ->where(function ($query) use ($exclude_from_records) {
  472. $query->where('type', 'OUT')
  473. ->orWhere(function ($subquery) use ($exclude_from_records) {
  474. $subquery->whereNotIn('member_id', $exclude_from_records);
  475. });
  476. });
  477. if ($this->filterCausals != null && sizeof($this->filterCausals) > 0) {
  478. $causals = array();
  479. foreach ($this->filterCausals as $z) {
  480. $causals[] = $z;
  481. $childs = \App\Models\Causal::where('parent_id', $z)->get();
  482. foreach ($childs as $c) {
  483. $causals[] = $c->id;
  484. $childsX = \App\Models\Causal::where('parent_id', $c->id)->get();
  485. foreach ($childsX as $cX) {
  486. $causals[] = $cX->id;
  487. }
  488. }
  489. }
  490. $datas->whereIn('causal_id', $causals);
  491. }
  492. if ($this->filterMember != null && $this->filterMember > 0) {
  493. $datas->where('member_id', $this->filterMember);
  494. }
  495. $datas = $datas->orderBy('date', 'ASC')
  496. ->orderBy('records.created_at', 'ASC')
  497. ->orderBy('records_rows.id', 'ASC')
  498. ->get();
  499. $groupedData = [];
  500. $causalsCount = [];
  501. $causalsAmounts = [];
  502. $nominativi = [];
  503. foreach ($datas as $idx => $data) {
  504. $causalCheck = \App\Models\Causal::findOrFail($data->causal_id);
  505. $paymentCheck = $data->payment_method->money;
  506. if (!$paymentCheck && ($causalCheck->no_first == null || !$causalCheck->no_first)) {
  507. if (!$data->deleted) {
  508. $amount = $data->amount;
  509. $amount += getVatValue($amount, $data->vat_id);
  510. } else {
  511. $amount = $data->amount;
  512. }
  513. $isCommercial = ($data->commercial == 1 || $data->commercial === '1' || $data->commercial === true);
  514. $typeLabel = $isCommercial ? 'Commerciale' : 'Non Commerciale';
  515. $nominativo = '';
  516. if ($data->type == "IN") {
  517. if ($data->member) {
  518. $nominativo = $data->member->last_name . " " . $data->member->first_name;
  519. }
  520. } else {
  521. if ($data->supplier) {
  522. $nominativo = $data->supplier->name;
  523. }
  524. }
  525. $groupKey = $data->date . '|' . $typeLabel . '|' . $data->payment_method->name . '|' . $data->type . '|' . $nominativo;
  526. if (!isset($groupedData[$groupKey])) {
  527. $groupedData[$groupKey] = [
  528. 'date' => $data->date,
  529. 'type_label' => $typeLabel,
  530. 'payment_method' => $data->payment_method->name,
  531. 'transaction_type' => $data->type,
  532. 'nominativo' => $nominativo,
  533. 'amount' => 0,
  534. 'deleted' => false,
  535. 'causals' => [],
  536. 'notes' => []
  537. ];
  538. $causalsCount[$groupKey] = [];
  539. $causalsAmounts[$groupKey] = []; // Initialize causal amounts for this group
  540. $nominativi[$groupKey] = $nominativo;
  541. }
  542. $groupedData[$groupKey]['amount'] += $amount;
  543. $causalsCount[$groupKey][$causalCheck->getTree()] = true;
  544. $causalName = $causalCheck->getTree();
  545. if (!isset($causalsAmounts[$groupKey][$causalName])) {
  546. $causalsAmounts[$groupKey][$causalName] = 0;
  547. }
  548. $causalsAmounts[$groupKey][$causalName] += $amount;
  549. if (!empty($data->note)) {
  550. $groupedData[$groupKey]['notes'][] = $data->note;
  551. }
  552. if ($data->deleted) {
  553. $groupedData[$groupKey]['deleted'] = true;
  554. }
  555. }
  556. }
  557. foreach ($groupedData as $groupKey => $group) {
  558. $causalsInGroup = array_keys($causalsCount[$groupKey]);
  559. $causalDisplay = $group['type_label'];
  560. if (count($causalsInGroup) > 1) {
  561. $causalAmountsForJs = [];
  562. foreach ($causalsInGroup as $causalName) {
  563. $causalAmountsForJs[] = $causalName . ':::' . formatPrice($causalsAmounts[$groupKey][$causalName]);
  564. }
  565. $detailDisplay = 'Varie|' . implode('|', $causalAmountsForJs);
  566. } else {
  567. $detailDisplay = $causalsInGroup[0];
  568. }
  569. $recordKey = $group['date'] . "§" . $causalDisplay . "§" . $group['nominativo'] . "§" . $detailDisplay . "§" . ($group['deleted'] ? 'DELETED' : '') . "§";
  570. if (!isset($this->records[$recordKey][$group['payment_method']][$group['transaction_type']])) {
  571. $this->records[$recordKey][$group['payment_method']][$group['transaction_type']] = 0;
  572. }
  573. $this->records[$recordKey][$group['payment_method']][$group['transaction_type']] += $group['amount'];
  574. if (!isset($this->totals[$group['payment_method']])) {
  575. $this->totals[$group['payment_method']]["IN"] = 0;
  576. $this->totals[$group['payment_method']]["OUT"] = 0;
  577. }
  578. if (!$group['deleted'])
  579. $this->totals[$group['payment_method']][$group['transaction_type']] += $group['amount'];
  580. }
  581. return view('livewire.records_old');
  582. }
  583. private function getLabels($fromDate, $toDate)
  584. {
  585. $begin = new DateTime($fromDate);
  586. $end = new DateTime($toDate);
  587. $interval = DateInterval::createFromDateString('1 day');
  588. $date_range = new DatePeriod($begin, $interval, $end);
  589. foreach ($date_range as $date) {
  590. $labels[] = $date->format('d/M');
  591. }
  592. return $labels;
  593. }
  594. private function getRecordData($type, $fromDate, $toDate)
  595. {
  596. $data = [];
  597. $begin = new DateTime($fromDate);
  598. $end = new DateTime($toDate);
  599. $interval = DateInterval::createFromDateString('1 day');
  600. $date_range = new DatePeriod($begin, $interval, $end);
  601. foreach ($date_range as $date) {
  602. if ($type == 'IN') {
  603. $found = false;
  604. foreach ($this->in as $in) {
  605. if (date("Y-m-d", strtotime($in->date)) == $date->format('Y-m-d')) {
  606. $data[] = number_format($in->total, 0, "", "");
  607. $found = true;
  608. }
  609. }
  610. if (!$found)
  611. $data[] = 0;
  612. }
  613. if ($type == 'OUT') {
  614. $found = false;
  615. foreach ($this->out as $out) {
  616. if (date("Y-m-d", strtotime($out->date)) == $date->format('Y-m-d')) {
  617. $data[] = number_format($out->total, 0, "", "");
  618. $found = true;
  619. }
  620. }
  621. if (!$found)
  622. $data[] = 0;
  623. }
  624. }
  625. return $data;
  626. }
  627. public function openExportModal()
  628. {
  629. $this->exportFromDate = $this->appliedFromDate;
  630. $this->exportToDate = $this->appliedToDate;
  631. // Reset email options
  632. $this->sendViaEmail = false;
  633. $this->exportEmailAddress = $this->getPreferredEmail();
  634. $this->updateEmailSubject();
  635. $this->emit('show-export-modal');
  636. }
  637. public function exportWithDateRange()
  638. {
  639. Log::info('=== EXPORT START ===', [
  640. 'user_id' => auth()->id(),
  641. 'from_date' => $this->exportFromDate,
  642. 'to_date' => $this->exportToDate,
  643. 'send_via_email' => $this->sendViaEmail,
  644. 'memory_usage' => memory_get_usage(true),
  645. 'memory_peak' => memory_get_peak_usage(true),
  646. 'time_limit' => ini_get('max_execution_time')
  647. ]);
  648. $this->isExporting = true;
  649. $this->emit('$refresh');
  650. usleep(100000);
  651. if ($this->sendViaEmail) {
  652. Log::info('Export: Validating email fields');
  653. $this->validate([
  654. 'exportEmailAddress' => 'required|email',
  655. 'exportEmailSubject' => 'required|string|max:255',
  656. ]);
  657. Log::info('Export: Email validation passed');
  658. }
  659. $this->isExporting = true;
  660. try {
  661. Log::info('Export: Starting COMBINED data generation phase (NO SEPARATE CALLS)');
  662. $startTime = microtime(true);
  663. // *** THIS IS THE KEY CHANGE - USE ONLY THE COMBINED METHOD ***
  664. $result = $this->generateExportDataAndTotals($this->exportFromDate, $this->exportToDate);
  665. $exportRecords = $result['records'];
  666. $exportTotals = $result['totals'];
  667. $dataGenTime = microtime(true) - $startTime;
  668. Log::info('Export: COMBINED data generation completed (NO SEPARATE TOTALS CALL)', [
  669. 'records_count' => count($exportRecords),
  670. 'totals_count' => count($exportTotals),
  671. 'generation_time' => $dataGenTime,
  672. 'memory_usage' => memory_get_usage(true),
  673. 'memory_peak' => memory_get_peak_usage(true)
  674. ]);
  675. if ($this->sendViaEmail) {
  676. Log::info('Export: Dispatching to background job');
  677. $this->dispatchExportJob($exportRecords, $exportTotals);
  678. Log::info('Export: Job dispatched successfully');
  679. } else {
  680. Log::info('Export: Starting direct download export');
  681. $exportStartTime = microtime(true);
  682. $result = $this->exportWithData($exportRecords, $exportTotals);
  683. $exportTime = microtime(true) - $exportStartTime;
  684. Log::info('Export: Direct export completed', [
  685. 'export_time' => $exportTime,
  686. 'total_time' => microtime(true) - $startTime,
  687. 'memory_usage' => memory_get_usage(true),
  688. 'memory_peak' => memory_get_peak_usage(true)
  689. ]);
  690. return $result;
  691. }
  692. } catch (\Illuminate\Validation\ValidationException $e) {
  693. Log::error('Export: Validation error', [
  694. 'error' => $e->getMessage(),
  695. 'errors' => $e->errors()
  696. ]);
  697. $this->isExporting = false;
  698. throw $e;
  699. } catch (\Exception $e) {
  700. Log::error('Export: General error', [
  701. 'error' => $e->getMessage(),
  702. 'trace' => $e->getTraceAsString(),
  703. 'memory_usage' => memory_get_usage(true),
  704. 'memory_peak' => memory_get_peak_usage(true),
  705. 'execution_time' => microtime(true) - ($startTime ?? 0)
  706. ]);
  707. $this->isExporting = false;
  708. if ($this->sendViaEmail) {
  709. $this->emit('export-email-error', 'Errore durante l\'invio dell\'email: ' . $e->getMessage());
  710. } else {
  711. session()->flash('error', 'Errore durante l\'export: ' . $e->getMessage());
  712. }
  713. } finally {
  714. Log::info('Export: Cleanup phase');
  715. $this->isExporting = false;
  716. $this->emit('export-complete');
  717. $this->emit('hide-export-modal');
  718. Log::info('=== EXPORT END ===');
  719. }
  720. }
  721. private function getEstimatedRecordCount($fromDate, $toDate)
  722. {
  723. $exclude_from_records = \App\Models\Member::where('exclude_from_records', true)->pluck('id')->toArray();
  724. $query = \App\Models\Record::join('records_rows', 'records.id', '=', 'records_rows.record_id')
  725. ->whereBetween('date', [$fromDate, $toDate])
  726. ->where(function ($query) {
  727. $query->where('type', 'OUT')
  728. ->orWhere(function ($query) {
  729. $query->where('records.corrispettivo_fiscale', true)
  730. ->orWhere('records.commercial', false);
  731. });
  732. })
  733. ->where(function ($query) use ($exclude_from_records) {
  734. $query->where('type', 'OUT')
  735. ->orWhere(function ($subquery) use ($exclude_from_records) {
  736. $subquery->whereNotIn('member_id', $exclude_from_records);
  737. });
  738. });
  739. if ($this->filterCausals != null && sizeof($this->filterCausals) > 0) {
  740. $causals = array();
  741. foreach ($this->filterCausals as $z) {
  742. $causals[] = $z;
  743. $childs = \App\Models\Causal::where('parent_id', $z)->get();
  744. foreach ($childs as $c) {
  745. $causals[] = $c->id;
  746. $childsX = \App\Models\Causal::where('parent_id', $c->id)->get();
  747. foreach ($childsX as $cX) {
  748. $causals[] = $cX->id;
  749. }
  750. }
  751. }
  752. $query->whereIn('causal_id', $causals);
  753. }
  754. if ($this->filterMember != null && $this->filterMember > 0) {
  755. $query->where('member_id', $this->filterMember);
  756. }
  757. return $query->count();
  758. }
  759. private function getMemberName($memberId)
  760. {
  761. $member = \App\Models\Member::find($memberId);
  762. return $member ? $member->last_name . ' ' . $member->first_name : 'Sconosciuto';
  763. }
  764. private function getCausalsNames($causalIds)
  765. {
  766. if (!is_array($causalIds)) {
  767. return null;
  768. }
  769. $causals = \App\Models\Causal::whereIn('id', $causalIds)->pluck('name')->toArray();
  770. return implode(', ', $causals);
  771. }
  772. public function updatedExportFromDate()
  773. {
  774. $this->updateEmailSubject();
  775. }
  776. public function updatedExportToDate()
  777. {
  778. $this->updateEmailSubject();
  779. }
  780. public function updatedSendViaEmail($value)
  781. {
  782. if ($value && empty($this->exportEmailAddress)) {
  783. $this->exportEmailAddress = $this->getPreferredEmail();
  784. }
  785. }
  786. public function resetEmailForm()
  787. {
  788. $this->sendViaEmail = false;
  789. $this->exportEmailAddress = $this->getPreferredEmail();
  790. $this->updateEmailSubject();
  791. }
  792. private function updateEmailSubject()
  793. {
  794. if (!empty($this->exportFromDate) && !empty($this->exportToDate)) {
  795. $fromFormatted = date('d/m/Y', strtotime($this->exportFromDate));
  796. $toFormatted = date('d/m/Y', strtotime($this->exportToDate));
  797. if ($this->exportFromDate === $this->exportToDate) {
  798. $this->exportEmailSubject = "Prima Nota - Export del {$fromFormatted}";
  799. } else {
  800. $this->exportEmailSubject = "Prima Nota - Export dal {$fromFormatted} al {$toFormatted}";
  801. }
  802. }
  803. }
  804. /**
  805. * Dispatch export job to queue
  806. */
  807. private function dispatchExportJob($exportRecords, $exportTotals)
  808. {
  809. try {
  810. // Prepare filter descriptions for the job
  811. $filterDescriptions = [
  812. 'member' => $this->filterMember ? $this->getMemberName($this->filterMember) : null,
  813. 'causals' => $this->filterCausals ? $this->getCausalsNames($this->filterCausals) : null,
  814. ];
  815. $paymentsArray = $this->payments->map(function ($payment) {
  816. return [
  817. 'id' => $payment->id,
  818. 'name' => $payment->name,
  819. 'type' => $payment->type
  820. ];
  821. })->toArray();
  822. // Dispatch job to background queue
  823. ExportPrimaNota::dispatch(
  824. $exportRecords,
  825. $exportTotals,
  826. $this->exportEmailAddress,
  827. $this->exportEmailSubject,
  828. [
  829. 'from' => date('d/m/Y', strtotime($this->exportFromDate)),
  830. 'to' => date('d/m/Y', strtotime($this->exportToDate))
  831. ],
  832. auth()->id(),
  833. $paymentsArray,
  834. $filterDescriptions
  835. );
  836. $this->emit('export-email-queued');
  837. session()->flash('success', 'Export in corso! Riceverai l\'email a breve alla casella: ' . $this->exportEmailAddress);
  838. Log::info('Export job dispatched', [
  839. 'user_id' => auth()->id(),
  840. 'email' => $this->exportEmailAddress,
  841. 'date_range' => [$this->exportFromDate, $this->exportToDate],
  842. 'total_records' => count($exportRecords)
  843. ]);
  844. } catch (\Exception $e) {
  845. Log::error('Failed to dispatch export job', [
  846. 'user_id' => auth()->id(),
  847. 'email' => $this->exportEmailAddress,
  848. 'error' => $e->getMessage()
  849. ]);
  850. throw new \Exception('Errore nell\'avvio dell\'export: ' . $e->getMessage());
  851. }
  852. }
  853. function export()
  854. {
  855. $result = $this->generateExportDataAndTotals($this->exportFromDate, $this->exportToDate);
  856. $exportRecords = $result['records'];
  857. $exportTotals = $result['totals'];
  858. return $this->exportWithData($exportRecords, $exportTotals);
  859. }
  860. private function exportWithData($exportRecords, $exportTotals)
  861. {
  862. Log::info('exportWithData: Starting Excel generation', [
  863. 'records_count' => count($exportRecords),
  864. 'totals_count' => count($exportTotals),
  865. 'memory_before' => memory_get_usage(true)
  866. ]);
  867. $startTime = microtime(true);
  868. Log::info('exportWithData: Setting memory and GC');
  869. ini_set('memory_limit', '512M');
  870. gc_enable();
  871. Log::info('exportWithData: Preparing column letters');
  872. $letters = array('F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'AA');
  873. Log::info('exportWithData: Creating spreadsheet object');
  874. $spreadsheet = new Spreadsheet();
  875. $activeWorksheet = $spreadsheet->getActiveSheet();
  876. Log::info('exportWithData: Setting basic headers');
  877. $activeWorksheet->setCellValue('A1', "Data");
  878. $activeWorksheet->setCellValue('B1', "Tipologia");
  879. $activeWorksheet->setCellValue('C1', "Causale");
  880. $activeWorksheet->setCellValue('D1', "Nominativo");
  881. $activeWorksheet->setCellValue('E1', "Stato");
  882. $activeWorksheet->getStyle('A1:Q1')->getFont()->setBold(true);
  883. $activeWorksheet->getStyle('A2:Q2')->getFont()->setBold(true);
  884. $activeWorksheet->getStyle('A1:Q1')->getFill()
  885. ->setFillType(\PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID)
  886. ->getStartColor()->setARGB('FF0C6197');
  887. $activeWorksheet->getStyle('A1:Q1')->getFont()->getColor()->setARGB('FFFFFFFF');
  888. $idx = 0;
  889. foreach ($this->payments as $p) {
  890. if ($idx >= count($letters)) {
  891. Log::warning('exportWithData: Reached letter limit', ['payment_index' => $idx]);
  892. break;
  893. }
  894. Log::debug('exportWithData: Setting payment header', [
  895. 'payment_name' => $p->name,
  896. 'column_index' => $idx,
  897. 'column_letter' => $letters[$idx]
  898. ]);
  899. $activeWorksheet->setCellValue($letters[$idx] . '1', $p->name);
  900. $idx++;
  901. if ($idx >= count($letters)) {
  902. break;
  903. }
  904. $activeWorksheet->mergeCells($letters[$idx] . '1:' . $letters[$idx] . '1');
  905. $idx++;
  906. }
  907. Log::info('exportWithData: Setting sub-headers');
  908. $idx = 0;
  909. $activeWorksheet->setCellValue('A2', "");
  910. $activeWorksheet->setCellValue('B2', "");
  911. $activeWorksheet->setCellValue('C2', "");
  912. $activeWorksheet->setCellValue('D2', "");
  913. $activeWorksheet->setCellValue('E2', "");
  914. foreach ($this->payments as $p) {
  915. if ($p->type == 'ALL') {
  916. if ($idx >= count($letters)) {
  917. break;
  918. }
  919. $activeWorksheet->setCellValue($letters[$idx] . '2', "Entrate");
  920. $idx++;
  921. $activeWorksheet->setCellValue($letters[$idx] . '2', "Uscite");
  922. $idx++;
  923. } elseif ($p->type == 'IN') {
  924. if ($idx >= count($letters)) {
  925. break;
  926. }
  927. $activeWorksheet->setCellValue($letters[$idx] . '2', "Entrate");
  928. $idx++;
  929. $activeWorksheet->setCellValue($letters[$idx] . '2', "");
  930. $idx++;
  931. } elseif ($p->type == 'OUT') {
  932. if ($idx >= count($letters)) {
  933. break;
  934. }
  935. $activeWorksheet->setCellValue($letters[$idx] . '2', "");
  936. $idx++;
  937. $activeWorksheet->setCellValue($letters[$idx] . '2', "Uscite");
  938. $idx++;
  939. }
  940. }
  941. Log::info('exportWithData: Applying header styles');
  942. $activeWorksheet->getStyle('A1:Q1')->getFont()->setBold(true);
  943. $activeWorksheet->getStyle('A2:Q2')->getFont()->setBold(true);
  944. Log::info('exportWithData: Starting data row processing');
  945. $count = 3;
  946. $batchSize = 1000;
  947. $recordsProcessed = 0;
  948. $totalRecords = count($exportRecords);
  949. Log::info('exportWithData: Processing records in batches', [
  950. 'total_records' => $totalRecords,
  951. 'batch_size' => $batchSize
  952. ]);
  953. $recordsArray = array_chunk($exportRecords, $batchSize, true);
  954. Log::info('exportWithData: Created batches', ['batch_count' => count($recordsArray)]);
  955. foreach ($recordsArray as $batchIndex => $recordsBatch) {
  956. Log::info('exportWithData: Processing batch', [
  957. 'batch_index' => $batchIndex,
  958. 'batch_size' => count($recordsBatch),
  959. 'memory_current' => memory_get_usage(true),
  960. 'time_elapsed' => microtime(true) - $startTime
  961. ]);
  962. foreach ($recordsBatch as $causal => $record) {
  963. if ($recordsProcessed % 250 == 0) {
  964. Log::info('exportWithData: Record processing progress', [
  965. 'processed' => $recordsProcessed,
  966. 'total' => $totalRecords,
  967. 'current_row' => $count,
  968. 'memory_usage' => memory_get_usage(true),
  969. 'time_elapsed' => microtime(true) - $startTime
  970. ]);
  971. }
  972. try {
  973. $check = $causal;
  974. $parts = explode("§", $check);
  975. $d = $parts[0] ?? '';
  976. $c = $parts[1] ?? '';
  977. $j = $parts[2] ?? '';
  978. $det = $parts[3] ?? '';
  979. $deleted = $parts[4] ?? '';
  980. $detailParts = explode('|', $det);
  981. $exportDetail = count($detailParts) > 1 ? implode(', ', array_slice($detailParts, 1)) : $det;
  982. Log::debug('exportWithData: Setting row cells', ['row' => $count]);
  983. $activeWorksheet->setCellValue('A' . $count, !empty($d) ? date("d/m/Y", strtotime($d)) : '');
  984. $activeWorksheet->setCellValue('B' . $count, $c);
  985. $activeWorksheet->setCellValue('C' . $count, $exportDetail);
  986. $activeWorksheet->setCellValue('D' . $count, $j);
  987. $stato = ($deleted === 'DELETED') ? 'ANNULLATA' : '';
  988. $activeWorksheet->setCellValue('E' . $count, $stato);
  989. if ($stato === 'ANNULLATA') {
  990. $activeWorksheet->getStyle('E' . $count)->getFont()->getColor()->setARGB('FFFF0000');
  991. }
  992. $idx = 0;
  993. foreach ($this->payments as $p) {
  994. if ($idx >= count($letters) - 1) {
  995. break;
  996. }
  997. if (isset($record[$p->name])) {
  998. $inValue = isset($record[$p->name]["IN"]) ? formatPrice($record[$p->name]["IN"]) : "";
  999. $outValue = isset($record[$p->name]["OUT"]) ? formatPrice($record[$p->name]["OUT"]) : "";
  1000. $activeWorksheet->setCellValue($letters[$idx] . $count, $inValue);
  1001. $idx++;
  1002. $activeWorksheet->setCellValue($letters[$idx] . $count, $outValue);
  1003. $idx++;
  1004. } else {
  1005. $activeWorksheet->setCellValue($letters[$idx] . $count, "");
  1006. $idx++;
  1007. $activeWorksheet->setCellValue($letters[$idx] . $count, "");
  1008. $idx++;
  1009. }
  1010. }
  1011. $count++;
  1012. $recordsProcessed++;
  1013. if ($recordsProcessed % 500 === 0) {
  1014. Log::debug('exportWithData: Garbage collection');
  1015. gc_collect_cycles();
  1016. }
  1017. } catch (\Exception $e) {
  1018. Log::error('exportWithData: Error processing record row', [
  1019. 'row' => $count,
  1020. 'causal' => $causal,
  1021. 'error' => $e->getMessage(),
  1022. 'processed_so_far' => $recordsProcessed
  1023. ]);
  1024. throw $e;
  1025. }
  1026. }
  1027. Log::info('exportWithData: Batch completed', [
  1028. 'batch_index' => $batchIndex,
  1029. 'records_in_batch' => count($recordsBatch),
  1030. 'total_processed' => $recordsProcessed
  1031. ]);
  1032. unset($recordsBatch);
  1033. gc_collect_cycles();
  1034. }
  1035. Log::info('exportWithData: Adding totals row');
  1036. $count++;
  1037. $idx = 0;
  1038. $activeWorksheet->setCellValue('A' . $count, 'Totale');
  1039. $activeWorksheet->setCellValue('B' . $count, '');
  1040. $activeWorksheet->setCellValue('C' . $count, '');
  1041. $activeWorksheet->setCellValue('D' . $count, '');
  1042. $activeWorksheet->setCellValue('E' . $count, '');
  1043. foreach ($this->payments as $p) {
  1044. if ($idx >= count($letters) - 1) {
  1045. break;
  1046. }
  1047. if (isset($exportTotals[$p->name])) {
  1048. if ($p->type == 'ALL') {
  1049. $activeWorksheet->setCellValue($letters[$idx] . $count, formatPrice($exportTotals[$p->name]["IN"] ?? 0));
  1050. $idx++;
  1051. $activeWorksheet->setCellValue($letters[$idx] . $count, formatPrice($exportTotals[$p->name]["OUT"] ?? 0));
  1052. $idx++;
  1053. } elseif ($p->type == 'IN') {
  1054. $activeWorksheet->setCellValue($letters[$idx] . $count, formatPrice($exportTotals[$p->name]["IN"] ?? 0));
  1055. $idx++;
  1056. $activeWorksheet->setCellValue($letters[$idx] . $count, "");
  1057. $idx++;
  1058. } elseif ($p->type == 'OUT') {
  1059. $activeWorksheet->setCellValue($letters[$idx] . $count, "");
  1060. $idx++;
  1061. $activeWorksheet->setCellValue($letters[$idx] . $count, formatPrice($exportTotals[$p->name]["OUT"] ?? 0));
  1062. $idx++;
  1063. }
  1064. } else {
  1065. if ($p->type == 'ALL') {
  1066. $activeWorksheet->setCellValue($letters[$idx] . $count, "0");
  1067. $idx++;
  1068. $activeWorksheet->setCellValue($letters[$idx] . $count, "0");
  1069. $idx++;
  1070. } elseif ($p->type == 'IN') {
  1071. $activeWorksheet->setCellValue($letters[$idx] . $count, "0");
  1072. $idx++;
  1073. $activeWorksheet->setCellValue($letters[$idx] . $count, "");
  1074. $idx++;
  1075. } elseif ($p->type == 'OUT') {
  1076. $activeWorksheet->setCellValue($letters[$idx] . $count, "");
  1077. $idx++;
  1078. $activeWorksheet->setCellValue($letters[$idx] . $count, "0");
  1079. $idx++;
  1080. }
  1081. }
  1082. }
  1083. Log::info('exportWithData: Applying final styles');
  1084. $activeWorksheet->getStyle('A' . $count . ':Q' . $count)->getFont()->setBold(true);
  1085. Log::info('exportWithData: Setting column dimensions');
  1086. $activeWorksheet->getColumnDimension('A')->setWidth(20);
  1087. $activeWorksheet->getColumnDimension('B')->setWidth(40);
  1088. $activeWorksheet->getColumnDimension('C')->setWidth(40);
  1089. $activeWorksheet->getColumnDimension('D')->setWidth(40);
  1090. $activeWorksheet->getColumnDimension('E')->setWidth(20);
  1091. foreach ($letters as $l) {
  1092. $activeWorksheet->getColumnDimension($l)->setWidth(20);
  1093. }
  1094. $filename = 'prima_nota_' . date("YmdHis") . '.xlsx';
  1095. Log::info('exportWithData: Preparing to save file', [
  1096. 'filename' => $filename,
  1097. 'total_processing_time' => microtime(true) - $startTime,
  1098. 'memory_before_save' => memory_get_usage(true)
  1099. ]);
  1100. try {
  1101. $currentClient = session('currentClient', 'default');
  1102. $tempPath = sys_get_temp_dir() . '/' . $filename;
  1103. Log::info('exportWithData: Creating Excel writer');
  1104. $writer = new Xlsx($spreadsheet);
  1105. Log::info('exportWithData: Saving to temp path', ['temp_path' => $tempPath]);
  1106. $writerStart = microtime(true);
  1107. $writer->save($tempPath);
  1108. $writerTime = microtime(true) - $writerStart;
  1109. Log::info('exportWithData: File saved to temp', [
  1110. 'writer_time' => $writerTime,
  1111. 'file_size' => file_exists($tempPath) ? filesize($tempPath) : 'unknown',
  1112. 'memory_after_save' => memory_get_usage(true)
  1113. ]);
  1114. unset($spreadsheet, $activeWorksheet, $writer);
  1115. gc_collect_cycles();
  1116. Log::info('exportWithData: Uploading to S3');
  1117. $disk = Storage::disk('s3');
  1118. $s3Path = $currentClient . '/prima_nota/' . $filename;
  1119. $primaNotaFolderPath = $currentClient . '/prima_nota/.gitkeep';
  1120. if (!$disk->exists($primaNotaFolderPath)) {
  1121. $disk->put($primaNotaFolderPath, '');
  1122. Log::info("Created prima_nota folder for client: {$currentClient}");
  1123. }
  1124. $uploadStart = microtime(true);
  1125. $fileContent = file_get_contents($tempPath);
  1126. $uploaded = $disk->put($s3Path, $fileContent, 'private');
  1127. $uploadTime = microtime(true) - $uploadStart;
  1128. if (!$uploaded) {
  1129. throw new \Exception('Failed to upload file to Wasabi S3');
  1130. }
  1131. Log::info("Export completed successfully", [
  1132. 'client' => $currentClient,
  1133. 'path' => $s3Path,
  1134. 'file_size' => filesize($tempPath),
  1135. 'records_processed' => $recordsProcessed,
  1136. 'upload_time' => $uploadTime,
  1137. 'total_time' => microtime(true) - $startTime,
  1138. 'memory_peak' => memory_get_peak_usage(true)
  1139. ]);
  1140. if (file_exists($tempPath)) {
  1141. unlink($tempPath);
  1142. }
  1143. $downloadUrl = $disk->temporaryUrl($s3Path, now()->addHour());
  1144. return redirect($downloadUrl);
  1145. } catch (\Exception $e) {
  1146. Log::error('Export S3 error - falling back to local', [
  1147. 'error' => $e->getMessage(),
  1148. 'trace' => $e->getTraceAsString(),
  1149. 'client' => session('currentClient', 'unknown'),
  1150. 'filename' => $filename,
  1151. 'records_processed' => $recordsProcessed ?? 0,
  1152. 'time_elapsed' => microtime(true) - $startTime
  1153. ]);
  1154. // Fallback logic remains the same...
  1155. $currentClient = session('currentClient', 'default');
  1156. $clientFolder = storage_path('app/prima_nota/' . $currentClient);
  1157. if (!is_dir($clientFolder)) {
  1158. mkdir($clientFolder, 0755, true);
  1159. Log::info("Created local client prima_nota folder: {$clientFolder}");
  1160. }
  1161. $localPath = $clientFolder . '/' . $filename;
  1162. if (isset($tempPath) && file_exists($tempPath)) {
  1163. rename($tempPath, $localPath);
  1164. } else {
  1165. $writer = new Xlsx($spreadsheet);
  1166. $writer->save($localPath);
  1167. unset($spreadsheet, $activeWorksheet, $writer);
  1168. }
  1169. gc_collect_cycles();
  1170. Log::warning("Export saved locally due to S3 error", [
  1171. 'client' => $currentClient,
  1172. 'local_path' => $localPath,
  1173. 'error' => $e->getMessage()
  1174. ]);
  1175. session()->flash('warning', 'File salvato localmente a causa di un errore del cloud storage.');
  1176. return response()->download($localPath)->deleteFileAfterSend();
  1177. }
  1178. }
  1179. private function getPreferredEmail()
  1180. {
  1181. // Try multiple sources in order of preference
  1182. $email = auth()->user()->email ?? null;
  1183. if (empty($email)) {
  1184. $email = session('user_email', null);
  1185. }
  1186. if (empty($email)) {
  1187. $member = \App\Models\Member::where('user_id', auth()->id())->first();
  1188. $email = $member ? $member->email : null;
  1189. }
  1190. if (empty($email)) {
  1191. // Get from user input or company default
  1192. $email = config('mail.default_recipient', '');
  1193. }
  1194. return $email;
  1195. }
  1196. }