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)