Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions Controller/Modelo347.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
use FacturaScripts\Core\DataSrc\Empresas;
use FacturaScripts\Core\Tools;
use FacturaScripts\Core\Where;
use FacturaScripts\Core\WorkQueue;
use FacturaScripts\Dinamic\Lib\Export\XLSExport;
use FacturaScripts\Dinamic\Model\Cliente;
use FacturaScripts\Dinamic\Model\Cuenta;
Expand Down Expand Up @@ -131,7 +132,76 @@ public function privateCore(&$response, $user, $permissions)
case 'download-txt':
$this->downloadTxtAction();
break;

case 'send-emails':
$this->sendEmailsAction();
break;
}
}

protected function sendEmailsAction(): void
{
$sendtype = $this->request->request->get('sendtype', 'both');

if (in_array($sendtype, ['customers', 'both']) && false === empty($this->customersData)) {
$data = [];
$sentEmails = [];
foreach ($this->customersData as $codcliente => $row) {
$cliente = new Cliente();
if (false === $cliente->load($codcliente)) {
continue;
}
$email = $cliente->email;
if (empty($email) || isset($sentEmails[$email])) {
continue;
}
$sentEmails[$email] = true;
$data[] = [
'email' => $email,
'name' => $row['cliente'],
'cifnif' => $row['cifnif'],
't1' => $row['t1'],
't2' => $row['t2'],
't3' => $row['t3'],
't4' => $row['t4'],
'total' => $row['total'],
];
}
if (false === empty($data)) {
WorkQueue::send('Modelo347.SendCustomersEmails', $this->codejercicio, ['data' => $data, 'amount' => $this->amount]);
}
}

if (in_array($sendtype, ['suppliers', 'both']) && false === empty($this->suppliersData)) {
$data = [];
$sentEmails = [];
foreach ($this->suppliersData as $codproveedor => $row) {
$proveedor = new Proveedor();
if (false === $proveedor->load($codproveedor)) {
continue;
}
$email = $proveedor->email;
if (empty($email) || isset($sentEmails[$email])) {
continue;
}
$sentEmails[$email] = true;
$data[] = [
'email' => $email,
'name' => $row['proveedor'],
'cifnif' => $row['cifnif'],
't1' => $row['t1'],
't2' => $row['t2'],
't3' => $row['t3'],
't4' => $row['t4'],
'total' => $row['total'],
];
}
if (false === empty($data)) {
WorkQueue::send('Modelo347.SendSuppliersEmails', $this->codejercicio, ['data' => $data, 'amount' => $this->amount]);
}
}

Tools::log()->info('347-emails-queued');
}

protected function checkAddress(array $data, string $type): void
Expand Down
7 changes: 6 additions & 1 deletion Init.php
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?php
/**
* This file is part of Modelo347 plugin for FacturaScripts
* Copyright (C) 2017-2025 Carlos Garcia Gomez <carlos@facturascripts.com>
* Copyright (C) 2017-2026 Carlos Garcia Gomez <carlos@facturascripts.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
Expand All @@ -22,6 +22,7 @@
use FacturaScripts\Core\Lib\AjaxForms\PurchasesHeaderHTML;
use FacturaScripts\Core\Lib\AjaxForms\SalesHeaderHTML;
use FacturaScripts\Core\Template\InitClass;
use FacturaScripts\Core\WorkQueue;
use FacturaScripts\Dinamic\Model\FacturaCliente;
use FacturaScripts\Dinamic\Model\FacturaProveedor;

Expand All @@ -43,6 +44,10 @@ public function init(): void
// mods
PurchasesHeaderHTML::addMod(new Mod\PurchasesHeaderHTMLMod());
SalesHeaderHTML::addMod(new Mod\SalesHeaderHTMLMod());

// workers
WorkQueue::addWorker('Modelo347CustomersEmailWorker', 'Modelo347.SendCustomersEmails');
WorkQueue::addWorker('Modelo347SuppliersEmailWorker', 'Modelo347.SendSuppliersEmails');
}

public function uninstall(): void
Expand Down
15 changes: 15 additions & 0 deletions Translation/es_ES.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,21 @@
{
"347-confirm-send": "Enviar emails del Modelo 347",
"347-confirm-send-body": "Se enviarán emails a los destinatarios del Modelo 347 con el resumen de operaciones del ejercicio.",
"347-email-amounts-title": "IMPORTE TRIMESTRAL OPERACIONES",
"347-email-cifnif-line": "DNI\/NIF: <strong>%cifnif%<\/strong>",
"347-email-footer": "De no recibir disconformidad por su parte antes del <strong>20 de febrero<\/strong>, consideraremos que nuestra cifra es correcta.\n\nSin otro particular, les saludamos cordialmente.",
"347-email-greeting": "Muy Sres. Nuestros:",
"347-email-included": "Hallándose Vds. incluidos en la relación que hacemos referencia, les informamos que el importe a declarar por este concepto es:",
"347-email-intro": "El próximo mes de febrero presentaremos relación de todos nuestros clientes cuya facturación haya superado la cantidad de <strong>%amount%<\/strong> durante el año <strong>%year%<\/strong>.",
"347-email-name-line": "NOMBRE\/RAZÓN SOCIAL: <strong>%name%<\/strong>",
"347-email-subject": "Modelo 347 - Ejercicio %year%",
"347-email-title": "DECLARACIÓN ANUAL DE OPERACIONES CON TERCERAS PERSONAS.",
"347-send-email": "Enviar Email",
"347-emails-queued": "Los emails del Modelo 347 han sido encolados para su envío.",
"347-emails-sent": "Modelo 347: enviados %sent% emails, sin email: %noEmail%.",
"347-no-country": "El %type% %name% con cif\/nif %cifnif% no tiene país y es obligatorio",
"347-no-data": "Sin resultados",
"347-no-email": "El destinatario %name% con cif\/nif %cifnif% no tiene email y no se le puede notificar.",
"347-no-province": "El %type% %name% con cif\/nif %cifnif% no tiene provincia y es obligatorio",
"company-admin-no-data": "La empresa %company% no tiene administrador y es obligatorio",
"company-phone-no-data": "La empresa %company% no tiene teléfono y es obligatorio",
Expand Down
48 changes: 48 additions & 0 deletions View/Modelo347.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,22 @@
document.forms['modelo347'].submit();
}

function confirmSendEmails() {
const sendtype = document.getElementById('emailSendType').value;
const form = document.forms['modelo347'];
const actionInput = document.createElement('input');
actionInput.type = 'hidden';
actionInput.name = 'action';
actionInput.value = 'send-emails';
form.appendChild(actionInput);
const sendtypeInput = document.createElement('input');
sendtypeInput.type = 'hidden';
sendtypeInput.name = 'sendtype';
sendtypeInput.value = sendtype;
form.appendChild(sendtypeInput);
form.submit();
}

function toggleDeclarationType() {
const type = document.getElementById('declarationtype').value;
const prevGroup = document.getElementById('justificante-anterior-group');
Expand Down Expand Up @@ -135,6 +151,10 @@
</div>
{# Buttons #}
<div class="col-sm-auto">
<button type="button" class="btn btn-success mb-2 me-2" data-bs-toggle="modal"
data-bs-target="#emailConfirmModal">
<i class="fa-solid fa-envelope fa-fw me-1"></i> {{ trans('347-send-email') }}
</button>
<div class="btn-group mb-2" role="group">
<button type="submit" name="action" value="show" class="btn btn-primary">
<i class="fa-solid fa-eye fa-fw me-1"></i> {{ trans('preview') }}
Expand All @@ -159,6 +179,34 @@
</form>
</div>

<div class="modal fade" id="emailConfirmModal" tabindex="-1" aria-labelledby="emailConfirmModalLabel"
aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="emailConfirmModalLabel">
<i class="fa-solid fa-envelope fa-fw me-1"></i> {{ trans('347-confirm-send') }}
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<p>{{ trans('347-confirm-send-body') }}</p>
<select id="emailSendType" class="form-select">
<option value="both">{{ trans('customers') }} + {{ trans('suppliers') }}</option>
<option value="customers">{{ trans('customers') }}</option>
<option value="suppliers">{{ trans('suppliers') }}</option>
</select>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary"
data-bs-dismiss="modal">{{ trans('cancel') }}</button>
<button type="button" class="btn btn-primary"
onclick="confirmSendEmails()">{{ trans('send') }}</button>
</div>
</div>
</div>
</div>

<ul class="nav nav-tabs" role="tablist">
<li class="nav-item">
<a class="nav-link {{ fsc.activetab == 'customers' ? 'active' : '' }}" onclick="activeTab('customers')"
Expand Down
97 changes: 97 additions & 0 deletions Worker/Modelo347CustomersEmailWorker.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
<?php
/**
* This file is part of Modelo347 plugin for FacturaScripts
* Copyright (C) 2026 Carlos Garcia Gomez <carlos@facturascripts.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

namespace FacturaScripts\Plugins\Modelo347\Worker;

use FacturaScripts\Core\Model\WorkEvent;
use FacturaScripts\Core\Template\WorkerClass;
use FacturaScripts\Core\Tools;
use FacturaScripts\Core\Validator;
use FacturaScripts\Dinamic\Lib\Email\NewMail;
use FacturaScripts\Dinamic\Lib\Email\TableBlock;
use FacturaScripts\Dinamic\Lib\Email\TextBlock;
use FacturaScripts\Dinamic\Lib\Email\TitleBlock;

/**
* @author Esteban Sánchez Martínez <esteban@factura.city>
*/

class Modelo347CustomersEmailWorker extends WorkerClass
{
public function run(WorkEvent $event): bool
{
$codejercicio = $event->value;
$data = $event->param('data');

if (empty($data)) {
return $this->done();
}

$sent = 0;
$noEmail = 0;

$i18n = Tools::lang();
$amount = Tools::money((float)$event->param('amount'));

foreach ($data as $row) {
$email = $row['email'] ?? '';
if (empty($email) || false === Validator::email($email)) {
$noEmail++;
Tools::log()->warning('347-no-email', [
'%name%' => $row['name'] ?? '',
'%cifnif%' => $row['cifnif'] ?? '',
]);
continue;
}

$contactInfo = $i18n->trans('347-email-cifnif-line', ['%cifnif%' => $row['cifnif'] ?? ''])
. "\n"
. $i18n->trans('347-email-name-line', ['%name%' => $row['name'] ?? '']);

$header = [$i18n->trans('concept'), $i18n->trans('amount')];
$rows = [
[$i18n->trans('first-trimester'), Tools::money((float)$row['t1'])],
[$i18n->trans('second-trimester'), Tools::money((float)$row['t2'])],
[$i18n->trans('third-trimester'), Tools::money((float)$row['t3'])],
[$i18n->trans('fourth-trimester'), Tools::money((float)$row['t4'])],
[$i18n->trans('total'), Tools::money((float)$row['total'])],
];

$mail = NewMail::create()
->to($email, $row['name'] ?? '')
->subject($i18n->trans('347-email-subject', ['%year%' => $codejercicio]))
->addMainBlock(new TitleBlock($i18n->trans('347-email-title'), 'h3'))
->addMainBlock(new TextBlock($i18n->trans('347-email-greeting')))
->addMainBlock(new TextBlock($i18n->trans('347-email-intro', ['%year%' => $codejercicio, '%amount%' => $amount])))
->addMainBlock(new TextBlock($i18n->trans('347-email-included')))
->addMainBlock(new TextBlock($contactInfo))
->addMainBlock(new TextBlock($i18n->trans('347-email-amounts-title')))
->addMainBlock(new TableBlock($header, $rows))
->addMainBlock(new TextBlock($i18n->trans('347-email-footer')));

if ($mail->send()) {
$sent++;
}
}

Tools::log()->info('347-emails-sent', ['%sent%' => $sent, '%noEmail%' => $noEmail]);

return $this->done();
}
}
96 changes: 96 additions & 0 deletions Worker/Modelo347SuppliersEmailWorker.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
<?php
/**
* This file is part of Modelo347 plugin for FacturaScripts
* Copyright (C) 2026 Carlos Garcia Gomez <carlos@facturascripts.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

namespace FacturaScripts\Plugins\Modelo347\Worker;

use FacturaScripts\Core\Model\WorkEvent;
use FacturaScripts\Core\Template\WorkerClass;
use FacturaScripts\Core\Tools;
use FacturaScripts\Core\Validator;
use FacturaScripts\Dinamic\Lib\Email\NewMail;
use FacturaScripts\Dinamic\Lib\Email\TableBlock;
use FacturaScripts\Dinamic\Lib\Email\TextBlock;
use FacturaScripts\Dinamic\Lib\Email\TitleBlock;

/**
* @author Esteban Sánchez Martínez <esteban@factura.city>
*/
class Modelo347SuppliersEmailWorker extends WorkerClass
{
public function run(WorkEvent $event): bool
{
$codejercicio = $event->value;
$data = $event->param('data');

if (empty($data)) {
return $this->done();
}

$sent = 0;
$noEmail = 0;

$i18n = Tools::lang();
$amount = Tools::money((float)$event->param('amount'));

foreach ($data as $row) {
$email = $row['email'] ?? '';
if (empty($email) || false === Validator::email($email)) {
$noEmail++;
Tools::log()->warning('347-no-email', [
'%name%' => $row['name'] ?? '',
'%cifnif%' => $row['cifnif'] ?? '',
]);
continue;
}

$contactInfo = $i18n->trans('347-email-cifnif-line', ['%cifnif%' => $row['cifnif'] ?? ''])
. "\n"
. $i18n->trans('347-email-name-line', ['%name%' => $row['name'] ?? '']);

$header = [$i18n->trans('concept'), $i18n->trans('amount')];
$rows = [
[$i18n->trans('first-trimester'), Tools::money((float)$row['t1'])],
[$i18n->trans('second-trimester'), Tools::money((float)$row['t2'])],
[$i18n->trans('third-trimester'), Tools::money((float)$row['t3'])],
[$i18n->trans('fourth-trimester'), Tools::money((float)$row['t4'])],
[$i18n->trans('total'), Tools::money((float)$row['total'])],
];

$mail = NewMail::create()
->to($email, $row['name'] ?? '')
->subject($i18n->trans('347-email-subject', ['%year%' => $codejercicio]))
->addMainBlock(new TitleBlock($i18n->trans('347-email-title'), 'h3'))
->addMainBlock(new TextBlock($i18n->trans('347-email-greeting')))
->addMainBlock(new TextBlock($i18n->trans('347-email-intro', ['%year%' => $codejercicio, '%amount%' => $amount])))
->addMainBlock(new TextBlock($i18n->trans('347-email-included')))
->addMainBlock(new TextBlock($contactInfo))
->addMainBlock(new TextBlock($i18n->trans('347-email-amounts-title')))
->addMainBlock(new TableBlock($header, $rows))
->addMainBlock(new TextBlock($i18n->trans('347-email-footer')));

if ($mail->send()) {
$sent++;
}
}

Tools::log()->info('347-emails-sent', ['%sent%' => $sent, '%noEmail%' => $noEmail]);

return $this->done();
}
}
Loading