HEX
Server: nginx/1.27.0
System: Linux ns31201788 5.4.0-216-generic #236-Ubuntu SMP Fri Apr 11 19:53:21 UTC 2025 x86_64
User: www-data (33)
PHP: 8.1.31
Disabled: NONE
Upload Files
File: //tmp/mago_export_fix.py
#!/usr/bin/env python3
"""
MAGO BRIDGE - Export fattura diretta in Mago via SQL
"""
import json, sys, argparse, configparser, os
from datetime import datetime

def get_connection(config):
    import pyodbc
    conn_str = (
        f"DRIVER={{ODBC Driver 18 for SQL Server}};"
        f"SERVER={config['server']},{config.get('port','1433')};"
        f"DATABASE={config['database']};"
        f"UID={config['user']};"
        f"PWD={config['password']};"
        f"TrustServerCertificate=yes;"
        f"Connection Timeout=10;"
    )
    return pyodbc.connect(conn_str, autocommit=False)

def get_next_id(cursor, table, id_col):
    cursor.execute(f"SELECT ISNULL(MAX({id_col}), 0) + 1 FROM {table}")
    return cursor.fetchone()[0]

def get_next_docno(cursor, doc_type):
    from datetime import datetime
    year_suffix = datetime.now().strftime('%y') + 'F'
    cursor.execute(
        "SELECT ISNULL(MAX(CAST(LEFT(DocNo, CHARINDEX('/', DocNo + '/') - 1) AS INT)), 0) + 1 "
        "FROM MA_SaleDoc WHERE DocumentType = ? AND DocNo LIKE ?",
        [doc_type, '%/' + year_suffix])
    next_num = cursor.fetchone()[0]
    return str(next_num).zfill(6) + '/' + year_suffix


def export_invoice(config, invoice_data):
    conn = get_connection(config)
    cursor = conn.cursor()

    try:
        doc_type = 3407874  # Fattura vendita
        sale_doc_id = get_next_id(cursor, 'MA_SaleDoc', 'SaleDocId')
        doc_no = get_next_docno(cursor, doc_type)
        now = datetime.now()
        doc_date = invoice_data.get('document_date', now.strftime('%Y-%m-%d'))

        cust_supp = invoice_data['cust_code']
        payment = invoice_data.get('payment_code', 'CONTRASS')
        inv_rsn = invoice_data.get('inv_rsn', 'S-VEND')
        acc_tpl = invoice_data.get('acc_tpl', 'FE')
        tax_journal = invoice_data.get('tax_journal', 'VENFE')
        ei_doc_type = 22151168  # TD24

        # === TESTATA ===
        cursor.execute("""
            INSERT INTO MA_SaleDoc (
                SaleDocId, DocumentType, DocNo, DocumentDate, CustSuppType, CustSupp,
                Payment, InvRsn, AccTpl, TaxJournal, EIDocumentType, Currency,
                PostingDate, InstallmStartDate, InstallmStartDateIsAuto,
                NetOfTax, Summarized, InvoiceFollows, Printed, SentByEMail,
                PostedToAccounting, PostedToCommissionEntries, PostedToInventory,
                Issued, PostedToIntrastat, PostedToPyblsRcvbls,
                FixingIsManual, IncludedInTurnover, PostedToCostAccounting,
                NoChangeExigibility, SalespersonCommAuto, AreaManagerCommAuto,
                SalespersonCommPercAuto, AreaManagerCommPercAuto,
                IntrastatBis, IntrastatTer, CorrectionDocument, IsParagon,
                FiscalPrinted, InvoiceForAdvanceLinked, ProFormaInvoiceLinked,
                ProFormaDDTLinked, CorrectionForReturn, DocumentCorrectionInCN,
                Delivered, Triangulation, ModifyOriginalPymtSched,
                GenerateEAT, ReceiptISM, SOSDone, Archived, Cancelled,
                LinkedDocReopened, IsInvoiceByFiscalBill, Replacement, Exported,
                ExcludedFromEI, FromExternalProgram,
                LastSubId, LastSubIdPymtSched, LastVariantSubId, LastAccDefSubId,
                TBCreated, TBModified, TBCreatedID, TBModifiedID, TBCompanyID,
                Fixing, OurReference, NTSendingStatus, TotalsIsAuto,
                SentByPostaLite, SplitPaymentActive, UseFatelWeb2
            ) VALUES (
                ?, ?, ?, ?, 3211264, ?,
                ?, ?, ?, ?, ?, 'EUR',
                ?, ?, '1',
                '0', '0', '0', '0', '0',
                '0', '0', '0',
                '0', '0', '0',
                '0', '1', '0',
                '0', '1', '1',
                '1', '1',
                '0', '0', '0', '0',
                '0', '0', '0',
                '0', '0', '0',
                '0', '0', '0',
                '0', '0', '0', '0', '0',
                '0', '0', '0', '0',
                '0', 1,
                0, 0, 0, 0,
                ?, ?, 0, 0, 0,
                1.0, ?, '0', '1',
                '0', '0', '0'
            )
        """, [
            sale_doc_id, doc_type, doc_no, doc_date, cust_supp,
            payment, inv_rsn, acc_tpl, tax_journal, ei_doc_type,
            doc_date, doc_date,
            now, now,
            invoice_data.get('our_reference', '')
        ])

        # === RIGHE ===
        items = invoice_data.get('items', [])
        total_taxable = 0
        total_tax = 0
        total_amount = 0

        for i, item in enumerate(items):
            line = i + 1
            qty = float(item.get('qty', 1))
            unit_value = float(item.get('unit_value', 0))
            tax_rate = float(item.get('tax_rate', 22))
            discount = float(item.get('discount', 0))
            
            taxable = round((unit_value * qty) - discount, 2)
            tax_amount = round(taxable * tax_rate / 100, 2)
            line_total = round(taxable + tax_amount, 2)
            
            total_taxable += taxable
            total_tax += tax_amount
            total_amount += line_total

            cursor.execute("""
                INSERT INTO MA_SaleDocDetail (
                    SaleDocId, Line, LineType, Description, UoM, Qty,
                    UnitValue, TaxableAmount, TaxCode, TotalAmount,
                    DiscountFormula, DiscountAmount, Offset,
                    NoInvoice, NoPrint, IncludedInTurnover,
                    DocumentType, DocumentDate, CustSuppType, CustSupp,
                    Contribution, FixedCost, CorrectionDocChargeLine,
                    CorrectedDocumentLine, CancelledInCD, NotPostableInInventory,
                    SubjectToWithholdingTax, Delivered, LoadedInCN,
                    Invoiced, InvoiceForAdvanceLinked, Deposit, InEI,
                    NetPriceIsAuto, ActualRetailPriceWithTax, DistributeCharges,
                    CommPercAuto, AreaManagerCommPercAuto,
                    SalespersonCommAuto, AreaManagerCommAuto,
                    SalespersonCommCtgAuto, AreaManagerCommCtgAuto,
                    ExcludeICMSST, ExcludeFromTot,
                    TBCreated, TBModified, TBCreatedID, TBModifiedID, TBCompanyID
                ) VALUES (
                    ?, ?, 3538947, ?, 'NR', ?,
                    ?, ?, ?, ?,
                    ?, ?, ?,
                    '0', '0', '1',
                    ?, ?, 3211264, ?,
                    '0', '0', '0',
                    '0', '0', '0',
                    '0', '0', '0',
                    '0', '0', '0', '1',
                    '1', '0', '0',
                    '1', '1',
                    '1', '1',
                    '1', '1',
                    '0', '0',
                    ?, ?, 0, 0, 0
                )
            """, [
                sale_doc_id, line, item.get('description', ''),
                qty,
                unit_value, taxable, str(int(tax_rate)), line_total,
                item.get('discount_formula', ''), discount, item.get('offset', '07052000'),
                doc_type, doc_date, cust_supp,
                now, now
            ])

        # === SUMMARY ===
        advance = float(invoice_data.get('advance_amount', 0))
        payable = round(total_amount - advance, 2)
        post_advance = '1' if advance > 0 else '0'

        cursor.execute("""
            INSERT INTO MA_SaleDocSummary (
                SaleDocId, TaxableAmount, TaxAmount, TotalAmount,
                GoodsAmount, ServiceAmounts, PayableAmount,
                Advance, PostAdvancesToAcc,
                FreeSamples, PayableAmountInBaseCurr,
                Discounts, Allowances, ReturnedMaterial,
                PackagingCharges, PackagingChargesIsAuto,
                ShippingCharges, ShippingChargesIsAuto,
                StampsCharges, StampsChargesIsAuto,
                CollectionCharges, CollectionChargesIsAuto,
                AdditionalCharges, AdditionalChargesIsAuto,
                StatisticalCharges, StatisticalChargesIsAuto,
                CashOnDeliveryChargesIsAuto,
                TotalAmountDocCurr, FreeSamplesDocCurr,
                TaxableAmountDocCurr, TaxAmountDocCurr,
                DiscountsIsAuto, WithholdingTaxManagement,
                ProfessionalsCashAuto, AmountsWithWHTax,
                CreditNotePreviousPeriod, VirtualStampFulfilled,
                DiscTaxBreakManual, InsuranceChargesIsAuto,
                NoPayDiscOnShipping, NoPayDiscOnPacking,
                NoPayDiscOnCollection, NoPayDiscOnAdditional,
                TBCreated, TBModified, TBCreatedID, TBModifiedID, TBCompanyID
            ) VALUES (
                ?, ?, ?, ?,
                ?, 0, ?,
                ?, ?,
                0, ?,
                0, 0, 0,
                0, '1',
                0, '1',
                0, '1',
                0, '1',
                0, '1',
                0, '1',
                '1',
                ?, 0,
                ?, ?,
                '1', '0',
                '1', '0',
                '0', '0',
                '0', '1',
                '0', '0',
                '0', '0',
                ?, ?, 0, 0, 0
            )
        """, [
            sale_doc_id, total_taxable, total_tax, total_amount,
            total_taxable, payable,
            advance, post_advance,
            payable,
            total_amount,
            total_taxable, total_tax,
            now, now
        ])

        # === REFERENCES ===
        rif_testo = invoice_data.get('riferimento_testo', '')
        rif_numero = invoice_data.get('riferimento_numero', '')
        
        # Riga 1: Riferimento scontrino
        cursor.execute("""
            INSERT INTO MA_SaleDocReferences (
                SaleDocId, Line, DocumentId, DocumentType, DocumentDate, DocumentNumber,
                ReferenceIsAuto, Notes,
                TBCreated, TBModified, TBCreatedID, TBModifiedID, TBCompanyID
            ) VALUES (?, 1, 0, 6684673, ?, ?, 0, ?, ?, ?, 1, 1, 0)
        """, [sale_doc_id, doc_date, rif_numero or '', 
               f"Scontrino {rif_testo} - {rif_numero}" if rif_testo else '', now, now])
        
        # Riga 2: Riferimento fattura (auto)
        cursor.execute("""
            INSERT INTO MA_SaleDocReferences (
                SaleDocId, Line, DocumentId, DocumentType, DocumentDate, DocumentNumber,
                ReferenceIsAuto, Notes,
                TBCreated, TBModified, TBCreatedID, TBModifiedID, TBCompanyID
            ) VALUES (?, 2, ?, 6684699, ?, ?, 1, '', ?, ?, 1, 1, 0)
        """, [sale_doc_id, sale_doc_id, doc_date, doc_no, now, now])
        
        # Riga 3: Riferimento registrazione (auto)
        cursor.execute("""
            INSERT INTO MA_SaleDocReferences (
                SaleDocId, Line, DocumentId, DocumentType, DocumentDate, DocumentNumber,
                ReferenceIsAuto, Notes,
                TBCreated, TBModified, TBCreatedID, TBModifiedID, TBCompanyID
            ) VALUES (?, 3, ?, 6684696, ?, ?, 1, '', ?, ?, 1, 1, 0)
        """, [sale_doc_id, sale_doc_id * 10, doc_date, doc_no, now, now])

        conn.commit()

        return {
            "success": True,
            "sale_doc_id": sale_doc_id,
            "doc_no": doc_no,
            "message": f"Fattura {doc_no} creata in Mago (ID: {sale_doc_id})"
        }

    except Exception as e:
        conn.rollback()
        return {"success": False, "message": str(e)}
    finally:
        conn.close()

def test_connection(config):
    try:
        conn = get_connection(config)
        cursor = conn.cursor()
        cursor.execute("SELECT COUNT(*) FROM MA_SaleDoc")
        count = cursor.fetchone()[0]
        next_id = get_next_id(cursor, 'MA_SaleDoc', 'SaleDocId')
        next_no = get_next_docno(cursor, 3407874)
        conn.close()
        return {
            "success": True,
            "message": f"OK. {count} documenti. Prossimo ID: {next_id}, N. doc: {next_no}"
        }
    except Exception as e:
        return {"success": False, "message": str(e)}

if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument('--action', required=True, choices=['export', 'test'])
    parser.add_argument('--config', required=True)
    parser.add_argument('--data', default=None)
    args = parser.parse_args()

    cp = configparser.ConfigParser()
    cp.read(args.config)
    config = dict(cp['mago'])

    try:
        if args.action == 'test':
            result = test_connection(config)
        elif args.action == 'export':
            if not args.data:
                result = {"success": False, "message": "Parametro --data mancante"}
            else:
                invoice_data = json.loads(args.data)
                result = export_invoice(config, invoice_data)
        print(json.dumps(result, ensure_ascii=False, default=str))
    except Exception as e:
        print(json.dumps({"success": False, "message": str(e)}))
        sys.exit(1)