added fatturato
This commit is contained in:
@@ -59,6 +59,39 @@ class MagoSalesStats
|
||||
return "DATE_ADD($z, INTERVAL (" . (int)self::ZIBO_WEEK_END_DAYS . " - WEEKDAY($z)) DAY)";
|
||||
}
|
||||
|
||||
/**
|
||||
* FATTURATO: tipi di documento di vendita che contano (verificati sui dati reali il 14/10/2026)
|
||||
* e segno con cui contano. Esclusi: DDT 3407873, DDT non valorizzati 3407881,
|
||||
* proforma 3407888, movimenti deposito 3407890.
|
||||
*/
|
||||
const INVOICE_TYPES = [
|
||||
3407874 => 1, // Fattura (immediata / differita) serie A ...
|
||||
3407883 => 1, // Fattura d'acconto (TD02) serie B
|
||||
3407876 => -1, // Nota di credito (TD04) → in negativo serie D ...
|
||||
];
|
||||
|
||||
/** Vista fatture/note di credito (alias i) con imponibile già col segno giusto (revenue). */
|
||||
public static function invoiceFrom()
|
||||
{
|
||||
$types = implode(',', array_map('intval', array_keys(self::INVOICE_TYPES)));
|
||||
$sign = 'CASE d.document_type';
|
||||
foreach (self::INVOICE_TYPES as $t => $sg) $sign .= ' WHEN ' . (int)$t . ' THEN ' . ((int)$sg < 0 ? -1 : 1);
|
||||
$sign .= ' ELSE 0 END';
|
||||
|
||||
return "(
|
||||
SELECT d.sale_doc_id, d.document_type, d.doc_no, d.document_date,
|
||||
d.customer, d.salesperson, d.area,
|
||||
COALESCE(NULLIF(c.company_name, ''), d.customer) AS company_name,
|
||||
($sign) * COALESCE(d.taxable_amount, 0) AS revenue,
|
||||
($sign) < 0 AS is_credit_note,
|
||||
d.summary_tb_modified IS NULL AS no_amount
|
||||
FROM mago_sale_doc d
|
||||
LEFT JOIN mago_cust_supp c
|
||||
ON c.cust_supp_type = " . self::CUSTOMER_TYPE . " AND c.cust_supp = d.customer
|
||||
WHERE d.document_type IN ($types) AND d.cancelled = 0
|
||||
) i";
|
||||
}
|
||||
|
||||
/** Scadenza di testata (usata solo per ordini senza righe). */
|
||||
public static function headerDue($a)
|
||||
{
|
||||
|
||||
@@ -21,6 +21,8 @@ class MagoSync
|
||||
const ENTITY_CUSTOMER = 'cust_supp';
|
||||
const ENTITY_SUMMARY = 'sale_ord_summary';
|
||||
const ENTITY_DETAILS = 'sale_ord_details';
|
||||
const ENTITY_DOCS = 'sale_doc';
|
||||
const ENTITY_DOC_SUMM = 'sale_doc_summary';
|
||||
|
||||
/** @var PDO */
|
||||
private $pdo;
|
||||
@@ -65,6 +67,15 @@ class MagoSync
|
||||
$report['ordini'] = $this->syncSaleOrders($from, $limit, $full);
|
||||
$report['righe'] = $this->syncSaleOrderDetails($from, $full, (bool)$limit);
|
||||
$report['importi'] = $this->syncSaleOrderSummaries($from, $full, (bool)$limit);
|
||||
|
||||
// Documenti di vendita (fatture / note di credito) per il fatturato
|
||||
if ($this->tableExists('mago_sale_doc')) {
|
||||
$report['documenti'] = $this->syncSaleDocs($from, $limit, $full);
|
||||
$report['importi_documenti'] = $this->syncSaleDocSummaries($from, $full, (bool)$limit);
|
||||
} else {
|
||||
$report['documenti'] = 'tabella mago_sale_doc mancante: eseguire la migrazione Phinx';
|
||||
$this->log('Documenti: tabella mago_sale_doc mancante, salto (eseguire la migrazione)');
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
$report['ok'] = false;
|
||||
$report['message'] = $e->getMessage();
|
||||
@@ -525,6 +536,239 @@ class MagoSync
|
||||
return ['records' => $read, 'aggiornati' => $updated, 'incrementale' => $incremental, 'ms' => $this->ms($t0)];
|
||||
}
|
||||
|
||||
/* =========================================================
|
||||
* DOCUMENTI DI VENDITA (MA_SaleDoc → mago_sale_doc) per il fatturato
|
||||
* ========================================================= */
|
||||
|
||||
/**
|
||||
* Testate documenti (tutti i tipi: fatture, note di credito, DDT...), filtrate per DocumentDate.
|
||||
* Lettura completa: alla fine cancella in locale i documenti del periodo non più presenti in Mago.
|
||||
*/
|
||||
public function syncSaleDocs($from, $limit = null, $full = false)
|
||||
{
|
||||
$t0 = microtime(true);
|
||||
$state = $this->getState(self::ENTITY_DOCS);
|
||||
|
||||
$filters = [['key' => 'DocumentDate', 'value' => $from . 'T00:00:00', 'operator' => '>=']];
|
||||
$incremental = !$full && !$limit && !empty($state['last_tb_modified']);
|
||||
if ($incremental) {
|
||||
$filters[] = [
|
||||
'key' => 'TbModified',
|
||||
'value' => date('Y-m-d\TH:i:s', strtotime($state['last_tb_modified'])),
|
||||
'operator' => '>=',
|
||||
];
|
||||
}
|
||||
|
||||
// solo i campi che servono (la testata ha ~200 campi: così è molto più veloce)
|
||||
$fields = [
|
||||
'SaleDocId',
|
||||
'DocumentType',
|
||||
'DocNo',
|
||||
'DocumentDate',
|
||||
'PostingDate',
|
||||
'CustSupp',
|
||||
'InvoicingCustomer',
|
||||
'Salesperson',
|
||||
'Area',
|
||||
'Currency',
|
||||
'TaxJournal',
|
||||
'InvRsn',
|
||||
'EIDocumentType',
|
||||
'Issued',
|
||||
'Cancelled',
|
||||
'PostedToAccounting',
|
||||
'TbCreated',
|
||||
'TbModified'
|
||||
];
|
||||
|
||||
$this->log('Documenti: ' . ($incremental ? 'incrementale da ' . $state['last_tb_modified'] : 'completa dal ' . $from) . ($limit ? " (prova: max $limit)" : ''));
|
||||
|
||||
$stmt = $this->pdo->prepare("
|
||||
INSERT INTO mago_sale_doc (
|
||||
sale_doc_id, document_type, doc_no, document_date, posting_date, customer, invoicing_customer,
|
||||
salesperson, area, currency, tax_journal, inv_rsn, ei_document_type,
|
||||
issued, cancelled, posted_to_accounting, tb_created, tb_modified, synced_at
|
||||
) VALUES (
|
||||
:id, :type, :doc_no, :doc_date, :posting_date, :customer, :inv_customer,
|
||||
:salesperson, :area, :currency, :tax_journal, :inv_rsn, :ei_type,
|
||||
:issued, :cancelled, :posted, :tb_created, :tb_modified, NOW()
|
||||
)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
document_type = VALUES(document_type), doc_no = VALUES(doc_no),
|
||||
document_date = VALUES(document_date), posting_date = VALUES(posting_date),
|
||||
customer = VALUES(customer), invoicing_customer = VALUES(invoicing_customer),
|
||||
salesperson = VALUES(salesperson), area = VALUES(area), currency = VALUES(currency),
|
||||
tax_journal = VALUES(tax_journal), inv_rsn = VALUES(inv_rsn), ei_document_type = VALUES(ei_document_type),
|
||||
issued = VALUES(issued), cancelled = VALUES(cancelled), posted_to_accounting = VALUES(posted_to_accounting),
|
||||
tb_created = VALUES(tb_created), tb_modified = VALUES(tb_modified), synced_at = NOW()
|
||||
");
|
||||
|
||||
$count = 0;
|
||||
$seen = [];
|
||||
$maxModified = $state['last_tb_modified'] ?? null;
|
||||
|
||||
$this->pdo->beginTransaction();
|
||||
try {
|
||||
foreach ($this->mago->iterate('MA_SaleDoc', ['filters' => $filters, 'fields' => $fields]) as $r) {
|
||||
$s = function ($k, $len) use ($r) {
|
||||
return mb_substr(trim((string)($r[$k] ?? '')), 0, $len);
|
||||
};
|
||||
$id = (int)($r['SaleDocId'] ?? 0);
|
||||
$mod = MagoClient::toMysqlDate($r['TbModified'] ?? null, true);
|
||||
|
||||
$stmt->execute([
|
||||
'id' => $id,
|
||||
'type' => (int)($r['DocumentType'] ?? 0),
|
||||
'doc_no' => $s('DocNo', 30),
|
||||
'doc_date' => MagoClient::toMysqlDate($r['DocumentDate'] ?? null),
|
||||
'posting_date' => MagoClient::toMysqlDate($r['PostingDate'] ?? null),
|
||||
'customer' => $s('CustSupp', 20),
|
||||
'inv_customer' => $s('InvoicingCustomer', 20),
|
||||
'salesperson' => $s('Salesperson', 20),
|
||||
'area' => $s('Area', 20),
|
||||
'currency' => $s('Currency', 8),
|
||||
'tax_journal' => $s('TaxJournal', 20),
|
||||
'inv_rsn' => $s('InvRsn', 20),
|
||||
'ei_type' => (int)($r['EIDocumentType'] ?? 0),
|
||||
'issued' => MagoClient::toBool($r['Issued'] ?? false),
|
||||
'cancelled' => MagoClient::toBool($r['Cancelled'] ?? false),
|
||||
'posted' => MagoClient::toBool($r['PostedToAccounting'] ?? false),
|
||||
'tb_created' => MagoClient::toMysqlDate($r['TbCreated'] ?? null, true),
|
||||
'tb_modified' => $mod,
|
||||
]);
|
||||
$count++;
|
||||
$seen[$id] = true;
|
||||
if ($mod && (!$maxModified || $mod > $maxModified)) $maxModified = $mod;
|
||||
|
||||
if ($count % 200 === 0) {
|
||||
$this->pdo->commit();
|
||||
$this->pdo->beginTransaction();
|
||||
$this->log(" documenti salvati: $count");
|
||||
}
|
||||
if ($limit && $count >= $limit) break;
|
||||
}
|
||||
|
||||
// lettura completa riuscita: tolgo i documenti del periodo cancellati in Mago
|
||||
$deleted = 0;
|
||||
if (!$incremental && !$limit) {
|
||||
$st = $this->pdo->prepare("SELECT sale_doc_id FROM mago_sale_doc WHERE document_date >= ?");
|
||||
$st->execute([$from]);
|
||||
$del = $this->pdo->prepare("DELETE FROM mago_sale_doc WHERE sale_doc_id = ?");
|
||||
foreach ($st->fetchAll(PDO::FETCH_COLUMN) as $id) {
|
||||
if (!isset($seen[(int)$id])) {
|
||||
$del->execute([$id]);
|
||||
$deleted++;
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->pdo->commit();
|
||||
} catch (Throwable $e) {
|
||||
if ($this->pdo->inTransaction()) $this->pdo->rollBack();
|
||||
$this->saveState(self::ENTITY_DOCS, null, 'errore', $e->getMessage(), $count, $t0, false);
|
||||
throw $e;
|
||||
}
|
||||
|
||||
$msg = ($limit ? "Prova: $count documenti" : ($incremental ? "Incrementale: $count documenti" : "Completa: $count documenti")) . ($deleted ? ", $deleted cancellati" : '');
|
||||
$this->saveState(self::ENTITY_DOCS, $limit ? null : $maxModified, 'ok', $msg, $count, $t0, !$limit);
|
||||
$this->log("Documenti sincronizzati: $count" . ($deleted ? " ($deleted non più presenti in Mago, cancellati)" : ''));
|
||||
|
||||
return ['records' => $count, 'cancellati' => $deleted, 'incrementale' => $incremental, 'ms' => $this->ms($t0)];
|
||||
}
|
||||
|
||||
/**
|
||||
* Importi dei documenti (MA_SaleDocSummary). Come per gli ordini si filtra per TbModified
|
||||
* partendo dalla creazione del primo documento senza importo (o di tutti, in lettura completa).
|
||||
*/
|
||||
public function syncSaleDocSummaries($from, $full = false, $testMode = false)
|
||||
{
|
||||
$t0 = microtime(true);
|
||||
$state = $this->getState(self::ENTITY_DOC_SUMM);
|
||||
|
||||
$info = $this->pdo->query(
|
||||
"
|
||||
SELECT MIN(COALESCE(tb_created, document_date)) AS first_created,
|
||||
MIN(CASE WHEN summary_tb_modified IS NULL THEN COALESCE(tb_created, document_date) END) AS first_missing,
|
||||
COUNT(*) AS n
|
||||
FROM mago_sale_doc
|
||||
WHERE document_date >= " . $this->pdo->quote($from)
|
||||
)->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (empty($info['n'])) {
|
||||
return ['records' => 0, 'aggiornati' => 0, 'incrementale' => false, 'ms' => $this->ms($t0)];
|
||||
}
|
||||
|
||||
// Incrementale: dal punto di ripresa, ma se ci sono documenti nuovi senza importo parto dalla loro creazione
|
||||
$incremental = !$full && !$testMode && !empty($state['last_tb_modified']);
|
||||
if ($incremental) {
|
||||
$since = $state['last_tb_modified'];
|
||||
if (!empty($info['first_missing'])) {
|
||||
$miss = date('Y-m-d 00:00:00', strtotime($info['first_missing'] . ' -1 day'));
|
||||
if ($miss < $since) $since = $miss;
|
||||
}
|
||||
} else {
|
||||
$since = date('Y-m-d 00:00:00', strtotime($info['first_created'] . ' -1 day'));
|
||||
}
|
||||
|
||||
$this->log('Importi documenti: ' . ($incremental ? 'incrementale' : 'completa') . ' da ' . $since);
|
||||
|
||||
$filters = [['key' => 'TbModified', 'value' => date('Y-m-d\TH:i:s', strtotime($since)), 'operator' => '>=']];
|
||||
|
||||
$stmt = $this->pdo->prepare("
|
||||
UPDATE mago_sale_doc SET
|
||||
taxable_amount = :taxable, tax_amount = :tax, total_amount = :total, summary_tb_modified = :mod
|
||||
WHERE sale_doc_id = :id
|
||||
");
|
||||
|
||||
$read = 0;
|
||||
$updated = 0;
|
||||
$maxModified = $state['last_tb_modified'] ?? null;
|
||||
|
||||
$this->pdo->beginTransaction();
|
||||
try {
|
||||
foreach (
|
||||
$this->mago->iterate('MA_SaleDocSummary', [
|
||||
'filters' => $filters,
|
||||
'fields' => ['SaleDocId', 'TaxableAmount', 'TaxAmount', 'TotalAmount', 'TbModified'],
|
||||
]) as $r
|
||||
) {
|
||||
$read++;
|
||||
$mod = MagoClient::toMysqlDate($r['TbModified'] ?? null, true);
|
||||
$stmt->execute([
|
||||
'taxable' => round(MagoClient::toDecimal($r['TaxableAmount'] ?? 0), 2),
|
||||
'tax' => round(MagoClient::toDecimal($r['TaxAmount'] ?? 0), 2),
|
||||
'total' => round(MagoClient::toDecimal($r['TotalAmount'] ?? 0), 2),
|
||||
'mod' => $mod ?: date('Y-m-d H:i:s'),
|
||||
'id' => (int)($r['SaleDocId'] ?? 0),
|
||||
]);
|
||||
$updated += $stmt->rowCount() > 0 ? 1 : 0;
|
||||
if ($mod && (!$maxModified || $mod > $maxModified)) $maxModified = $mod;
|
||||
|
||||
if ($read % 200 === 0) {
|
||||
$this->pdo->commit();
|
||||
$this->pdo->beginTransaction();
|
||||
$this->log(" totali documenti letti: $read");
|
||||
}
|
||||
}
|
||||
$this->pdo->commit();
|
||||
} catch (Throwable $e) {
|
||||
if ($this->pdo->inTransaction()) $this->pdo->rollBack();
|
||||
$this->saveState(self::ENTITY_DOC_SUMM, null, 'errore', $e->getMessage(), $read, $t0, false);
|
||||
throw $e;
|
||||
}
|
||||
|
||||
$this->saveState(self::ENTITY_DOC_SUMM, $testMode ? null : $maxModified, 'ok', "Letti $read totali, $updated documenti aggiornati", $read, $t0, !$testMode);
|
||||
$this->log("Importi documenti: letti $read, documenti aggiornati $updated");
|
||||
|
||||
return ['records' => $read, 'aggiornati' => $updated, 'incrementale' => $incremental, 'ms' => $this->ms($t0)];
|
||||
}
|
||||
|
||||
private function tableExists($name)
|
||||
{
|
||||
$st = $this->pdo->prepare("SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = ?");
|
||||
$st->execute([$name]);
|
||||
return (int)$st->fetchColumn() > 0;
|
||||
}
|
||||
|
||||
/* =========================================================
|
||||
* ANAGRAFICA CLIENTI (per decodificare i codici cliente)
|
||||
* ========================================================= */
|
||||
|
||||
Reference in New Issue
Block a user