feat(purchase): import comments + split serial, atomic rollback, serial in error log
- Import invoiceComment/invoiceComment2 and split serialNumber into series/number onto Purchase Order/Invoice and the E-Taxes Purchase tracking record - Roll back the whole import (delete PO/PI + tracking record) when submit fails, so a non-submittable document leaves nothing half-imported behind - Show the real serial number (not the internal e-taxes id) in the bulk-import error log; clean up "undefined"/"No date" placeholders Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
8e3cc32501
commit
b3a549e80b
|
|
@ -16,6 +16,75 @@ from .utils import resolve_customer_group, exceeds_one_year
|
|||
from .sales_api import get_sales_invoices
|
||||
|
||||
|
||||
def split_serial_number(serial):
|
||||
"""Split an e-taxes serialNumber into (series, number).
|
||||
|
||||
Two known formats, distinguished by the count of leading letters:
|
||||
- 2 letters + 4 date digits (YYMM) + number, e.g. "MT240912345678"
|
||||
-> series "MT2409", number "12345678"
|
||||
- 4 letters + number, e.g. "EBEA057741"
|
||||
-> series "EBEA", number "057741"
|
||||
|
||||
Anything else (empty, unexpected prefix) -> the whole string becomes the
|
||||
series and the number is left empty (per agreed fallback).
|
||||
"""
|
||||
serial = (serial or "").strip()
|
||||
if not serial:
|
||||
return "", ""
|
||||
|
||||
match = re.match(r"^([A-Za-z]+)(\d+)$", serial)
|
||||
if match:
|
||||
letters, digits = match.group(1), match.group(2)
|
||||
if len(letters) == 2 and len(digits) > 4:
|
||||
# new format: letters + YYMM are the series, the rest is the number
|
||||
return letters + digits[:4], digits[4:]
|
||||
if len(letters) == 4:
|
||||
# old format: letters are the series, digits are the number
|
||||
return letters, digits
|
||||
|
||||
# Unknown / unexpected format: keep the whole value as the series.
|
||||
return serial, ""
|
||||
|
||||
|
||||
def _rollback_import(po_name=None, etaxes_purchase_name=None, pi_name=None):
|
||||
"""Remove any partially-imported docs so a failed import leaves nothing behind.
|
||||
|
||||
Used when a Purchase Order or its Purchase Invoice cannot be submitted: we
|
||||
must not keep a half-imported draft or an orphan tracking record. Deletes in
|
||||
dependency order (PI -> PO -> E-Taxes Purchase). Deleting the PO also fires
|
||||
the on_trash hook that removes the linked E-Taxes Purchase, but we delete it
|
||||
explicitly too in case linking never happened.
|
||||
"""
|
||||
# Purchase Invoice first (it depends on the PO)
|
||||
if pi_name and frappe.db.exists("Purchase Invoice", pi_name):
|
||||
try:
|
||||
pi = frappe.get_doc("Purchase Invoice", pi_name)
|
||||
if pi.docstatus == 1:
|
||||
pi.cancel()
|
||||
frappe.delete_doc("Purchase Invoice", pi_name, force=True, ignore_permissions=True)
|
||||
except Exception as e:
|
||||
frappe.log_error(f"Failed to delete PI {pi_name}: {e}\n{frappe.get_traceback()}", "Import Rollback Error")
|
||||
|
||||
# Purchase Order
|
||||
if po_name and frappe.db.exists("Purchase Order", po_name):
|
||||
try:
|
||||
po = frappe.get_doc("Purchase Order", po_name)
|
||||
if po.docstatus == 1:
|
||||
po.cancel()
|
||||
frappe.delete_doc("Purchase Order", po_name, force=True, ignore_permissions=True)
|
||||
except Exception as e:
|
||||
frappe.log_error(f"Failed to delete PO {po_name}: {e}\n{frappe.get_traceback()}", "Import Rollback Error")
|
||||
|
||||
# E-Taxes Purchase tracking record (may already be gone via PO on_trash hook)
|
||||
if etaxes_purchase_name and frappe.db.exists("E-Taxes Purchase", etaxes_purchase_name):
|
||||
try:
|
||||
frappe.delete_doc("E-Taxes Purchase", etaxes_purchase_name, force=True, ignore_permissions=True)
|
||||
except Exception as e:
|
||||
frappe.log_error(f"Failed to delete E-Taxes Purchase {etaxes_purchase_name}: {e}\n{frappe.get_traceback()}", "Import Rollback Error")
|
||||
|
||||
frappe.db.commit()
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_invoices(token, filters=None):
|
||||
"""Getting list of invoices with filtering options"""
|
||||
|
|
@ -906,6 +975,15 @@ def create_purchase_invoice_from_order(purchase_order_name):
|
|||
|
||||
# ДОБАВЛЕНО: Устанавливаем галочку is_taxes_doc для Purchase Invoice
|
||||
pi_doc.is_taxes_doc = 1
|
||||
|
||||
# Переносим комментарии и серию/номер из Purchase Order.
|
||||
# remarks (коммент 1) ставим только если он непустой — иначе пусть
|
||||
# ERPNext сам подставит стандартное "Against Supplier Invoice ...".
|
||||
if po_doc.get("remarks"):
|
||||
pi_doc.remarks = po_doc.remarks
|
||||
pi_doc.etaxes_invoice_comment_2 = po_doc.get("etaxes_invoice_comment_2")
|
||||
pi_doc.etaxes_invoice_series = po_doc.get("etaxes_invoice_series")
|
||||
pi_doc.etaxes_invoice_number = po_doc.get("etaxes_invoice_number")
|
||||
|
||||
# ДОБАВЛЕНО: Копируем налоги из Purchase Order
|
||||
if po_doc.taxes:
|
||||
|
|
@ -1105,6 +1183,14 @@ def import_invoice_with_mapping(invoice_data, purchase_order_name=None, schedule
|
|||
# Add/update additional fields from invoice
|
||||
po.title = f"Invoice {invoice_data.get('serialNumber', '')}"
|
||||
po.invoice_serial_number = invoice_data.get("serialNumber") or invoice_data.get("number", "")
|
||||
|
||||
# Comments from e-taxes: comment 1 -> general remarks (human-editable),
|
||||
# comment 2 -> dedicated field. Series/number split from serialNumber.
|
||||
po.remarks = invoice_data.get("invoiceComment") or ""
|
||||
po.etaxes_invoice_comment_2 = invoice_data.get("invoiceComment2") or ""
|
||||
etaxes_series, etaxes_number = split_serial_number(invoice_data.get("serialNumber"))
|
||||
po.etaxes_invoice_series = etaxes_series
|
||||
po.etaxes_invoice_number = etaxes_number
|
||||
|
||||
# Clear existing items if need to update them completely
|
||||
if purchase_order_name and invoice_data.get("items"):
|
||||
|
|
@ -1296,52 +1382,57 @@ def import_invoice_with_mapping(invoice_data, purchase_order_name=None, schedule
|
|||
total = invoice_data.get('totalAmount', 0) or invoice_data.get('amount', 0)
|
||||
|
||||
# Создаем E-Taxes Purchase
|
||||
etaxes_purchase_result = create_etaxes_purchase(invoice_id, date_to_use, sender_name, total)
|
||||
etaxes_purchase_result = create_etaxes_purchase(
|
||||
invoice_id, date_to_use, sender_name, total,
|
||||
series=etaxes_series,
|
||||
number=etaxes_number,
|
||||
comment=invoice_data.get("invoiceComment") or "",
|
||||
comment2=invoice_data.get("invoiceComment2") or "",
|
||||
)
|
||||
etaxes_purchase_name = None
|
||||
if etaxes_purchase_result and etaxes_purchase_result.get('success'):
|
||||
etaxes_purchase_name = etaxes_purchase_result.get('name')
|
||||
# Устанавливаем поля E-Taxes ДО submit'а
|
||||
po.is_taxes_doc = 1
|
||||
po.taxes_doc = etaxes_purchase_result.get('name')
|
||||
po.taxes_doc = etaxes_purchase_name
|
||||
# Сохраняем изменения
|
||||
po.save()
|
||||
|
||||
# ИЗМЕНЕНИЕ: Делаем Submit для Purchase Order
|
||||
|
||||
# Submit Purchase Order. Если документ не сабмитится — НЕ оставляем его:
|
||||
# откатываем весь импорт (черновик PO + трекинг-запись) и сообщаем об ошибке.
|
||||
try:
|
||||
po.submit()
|
||||
frappe.log_error(f"Purchase Order {po.name} submitted successfully", "Import Invoice Success")
|
||||
except Exception as e:
|
||||
frappe.log_error(f"Error submitting Purchase Order {po.name}: {str(e)}", "Submit PO Error")
|
||||
_rollback_import(po_name=po.name, etaxes_purchase_name=etaxes_purchase_name)
|
||||
return {
|
||||
'success': False,
|
||||
'message': f'Failed to submit Purchase Order: {str(e)}'
|
||||
}
|
||||
|
||||
# ИЗМЕНЕНИЕ: Создаем Purchase Invoice
|
||||
|
||||
# Создаём и сабмитим Purchase Invoice. Любая неудача на этом этапе —
|
||||
# тоже полный откат (PO уже засабмичен, поэтому отменяем и удаляем его).
|
||||
pi_name = None
|
||||
try:
|
||||
pi_name = create_purchase_invoice_from_order(po.name)
|
||||
if pi_name:
|
||||
# Делаем Submit для Purchase Invoice
|
||||
pi = frappe.get_doc("Purchase Invoice", pi_name)
|
||||
pi.submit()
|
||||
frappe.log_error(f"Purchase Invoice {pi_name} created and submitted successfully", "Import Invoice Success")
|
||||
|
||||
return {
|
||||
'success': True,
|
||||
'message': 'Invoice data imported successfully. Purchase Order and Purchase Invoice created.',
|
||||
'purchase_order': po.name,
|
||||
'purchase_invoice': pi_name
|
||||
}
|
||||
else:
|
||||
return {
|
||||
'success': True,
|
||||
'message': 'Invoice data imported successfully. Purchase Order created, but Purchase Invoice creation failed.',
|
||||
'purchase_order': po.name
|
||||
}
|
||||
except Exception as e:
|
||||
frappe.log_error(f"Error creating or submitting Purchase Invoice: {str(e)}", "Submit PI Error")
|
||||
if not pi_name:
|
||||
raise Exception("create_purchase_invoice_from_order returned no name")
|
||||
pi = frappe.get_doc("Purchase Invoice", pi_name)
|
||||
pi.submit()
|
||||
frappe.log_error(f"Purchase Invoice {pi_name} created and submitted successfully", "Import Invoice Success")
|
||||
return {
|
||||
'success': True,
|
||||
'message': 'Invoice data imported successfully. Purchase Order created, but Purchase Invoice creation failed.',
|
||||
'purchase_order': po.name
|
||||
'message': 'Invoice data imported successfully. Purchase Order and Purchase Invoice created.',
|
||||
'purchase_order': po.name,
|
||||
'purchase_invoice': pi_name
|
||||
}
|
||||
except Exception as e:
|
||||
frappe.log_error(f"Error creating or submitting Purchase Invoice: {str(e)}\n{frappe.get_traceback()}", "Submit PI Error")
|
||||
_rollback_import(po_name=po.name, etaxes_purchase_name=etaxes_purchase_name, pi_name=pi_name)
|
||||
return {
|
||||
'success': False,
|
||||
'message': f'Failed to submit Purchase Invoice: {str(e)}'
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
|
|
@ -1373,37 +1464,42 @@ def get_etaxes_purchases():
|
|||
}
|
||||
|
||||
@frappe.whitelist()
|
||||
def create_etaxes_purchase(etaxes_id, date, party, total):
|
||||
def create_etaxes_purchase(etaxes_id, date, party, total,
|
||||
series=None, number=None, comment=None, comment2=None):
|
||||
"""Creates E-Taxes Purchase record for tracking imported invoices"""
|
||||
# Записываем активность
|
||||
record_etaxes_activity()
|
||||
|
||||
|
||||
try:
|
||||
# Normalize ID for storage
|
||||
etaxes_id = str(etaxes_id).strip() if etaxes_id else ""
|
||||
|
||||
|
||||
# Debug logging
|
||||
frappe.logger().info(f"Creating E-Taxes Purchase: ID={etaxes_id}, date={date}, party={party}, total={total}")
|
||||
|
||||
|
||||
# Check if record with such etaxes_id already exists
|
||||
existing = frappe.db.get_value('E-Taxes Purchase', {'etaxes_id': etaxes_id}, 'name')
|
||||
if existing:
|
||||
# Debug logging
|
||||
frappe.logger().info(f"E-Taxes Purchase already exists: {existing}")
|
||||
|
||||
|
||||
return {
|
||||
'success': True,
|
||||
'message': 'Record already exists',
|
||||
'name': existing
|
||||
}
|
||||
|
||||
|
||||
# Create new record
|
||||
etaxes_purchase = frappe.new_doc('E-Taxes Purchase')
|
||||
etaxes_purchase.etaxes_id = etaxes_id
|
||||
etaxes_purchase.date = date
|
||||
etaxes_purchase.party = party
|
||||
etaxes_purchase.total = total
|
||||
|
||||
etaxes_purchase.series = series or ""
|
||||
etaxes_purchase.number = number or ""
|
||||
etaxes_purchase.comment = comment or ""
|
||||
etaxes_purchase.comment2 = comment2 or ""
|
||||
|
||||
etaxes_purchase.insert()
|
||||
frappe.db.commit() # Explicit commit
|
||||
|
||||
|
|
@ -4471,7 +4567,11 @@ def _process_bulk_purchase_import(invoice_ids, token, warehouse, user, bg_job_id
|
|||
imported_count += 1
|
||||
else:
|
||||
error_msg = result.get("message", "Unknown import error") if result else "Empty result"
|
||||
error_entry = {"invoice_id": invoice_id, "error": error_msg}
|
||||
error_entry = {
|
||||
"invoice_id": invoice_id,
|
||||
"serial_number": details.get("serialNumber") or "",
|
||||
"error": error_msg,
|
||||
}
|
||||
if result:
|
||||
for key in ("unmatched_items", "unmatched_parties", "unmatched_units"):
|
||||
if result.get(key):
|
||||
|
|
|
|||
|
|
@ -557,11 +557,12 @@ ETaxes.auth = {
|
|||
ETaxes.errors = {
|
||||
// Добавить ошибку в лог
|
||||
add: function(invoiceData, errorType, errorMessage, additionalData = {}) {
|
||||
const invoiceData_ = invoiceData || {};
|
||||
const errorEntry = {
|
||||
invoice_id: invoiceData.id || 'Unknown',
|
||||
serial_number: invoiceData.serialNumber || 'Unknown',
|
||||
sender_name: invoiceData.sender ? invoiceData.sender.name : 'Unknown',
|
||||
creation_date: invoiceData.creationDate || invoiceData.createdAt || invoiceData.date || 'Unknown',
|
||||
invoice_id: invoiceData_.id || '',
|
||||
serial_number: invoiceData_.serialNumber || invoiceData_.id || '',
|
||||
sender_name: (invoiceData_.sender && invoiceData_.sender.name) || invoiceData_.senderName || '',
|
||||
creation_date: invoiceData_.creationDate || invoiceData_.createdAt || invoiceData_.date || invoiceData_.operationDate || '',
|
||||
error_type: errorType,
|
||||
error_message: errorMessage,
|
||||
timestamp: new Date().toISOString(),
|
||||
|
|
@ -596,12 +597,11 @@ ETaxes.errors = {
|
|||
let errorsHtml = '<div class="error-log-container">';
|
||||
|
||||
ETaxes.loadingErrors.forEach(function(error, index) {
|
||||
let documentDate = 'No date';
|
||||
let documentDate = '';
|
||||
if (error.creation_date && error.creation_date !== 'Unknown') {
|
||||
try {
|
||||
documentDate = moment(error.creation_date).format('DD.MM.YYYY');
|
||||
} catch (e) {
|
||||
documentDate = 'Invalid date';
|
||||
const m = moment(error.creation_date);
|
||||
if (m.isValid()) {
|
||||
documentDate = m.format('DD.MM.YYYY');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -610,11 +610,13 @@ ETaxes.errors = {
|
|||
// Заголовок ошибки
|
||||
errorsHtml += '<div class="error-header" style="margin-bottom: 10px; padding-bottom: 8px; border-bottom: 1px solid var(--border-color);">';
|
||||
errorsHtml += '<div style="display: flex; justify-content: space-between; align-items: center;">';
|
||||
errorsHtml += '<h5 style="margin: 0; color: var(--text-color);">' +
|
||||
'<strong>' + (error.serial_number || error.invoice_id) + '</strong></h5>';
|
||||
errorsHtml += '<h5 style="margin: 0; color: var(--text-color);">' +
|
||||
'<strong>' + (error.serial_number || error.invoice_id || __('Unknown')) + '</strong></h5>';
|
||||
errorsHtml += '<span class="label label-danger">' + error.error_type + '</span>';
|
||||
errorsHtml += '</div>';
|
||||
errorsHtml += '<small style="color: var(--text-muted);">' + error.sender_name + '</small>';
|
||||
if (error.sender_name) {
|
||||
errorsHtml += '<small style="color: var(--text-muted);">' + error.sender_name + '</small>';
|
||||
}
|
||||
errorsHtml += '</div>';
|
||||
|
||||
// Сообщение об ошибке
|
||||
|
|
@ -660,9 +662,11 @@ ETaxes.errors = {
|
|||
errorsHtml += '</ul></div>';
|
||||
}
|
||||
|
||||
errorsHtml += '<div style="text-align: right; margin-top: 10px;">';
|
||||
errorsHtml += '<small style="color: var(--text-muted);"><em>' + documentDate + '</em></small>';
|
||||
errorsHtml += '</div>';
|
||||
if (documentDate) {
|
||||
errorsHtml += '<div style="text-align: right; margin-top: 10px;">';
|
||||
errorsHtml += '<small style="color: var(--text-muted);"><em>' + documentDate + '</em></small>';
|
||||
errorsHtml += '</div>';
|
||||
}
|
||||
|
||||
errorsHtml += '</div>';
|
||||
});
|
||||
|
|
|
|||
|
|
@ -559,11 +559,12 @@ ETaxes.auth = {
|
|||
ETaxes.errors = {
|
||||
// Добавить ошибку в лог
|
||||
add: function(invoiceData, errorType, errorMessage, additionalData = {}) {
|
||||
const invoiceData_ = invoiceData || {};
|
||||
const errorEntry = {
|
||||
invoice_id: invoiceData.id || 'Unknown',
|
||||
serial_number: invoiceData.serialNumber || 'Unknown',
|
||||
sender_name: invoiceData.sender ? invoiceData.sender.name : 'Unknown',
|
||||
creation_date: invoiceData.creationDate || invoiceData.createdAt || invoiceData.date || 'Unknown',
|
||||
invoice_id: invoiceData_.id || '',
|
||||
serial_number: invoiceData_.serialNumber || invoiceData_.id || '',
|
||||
sender_name: (invoiceData_.sender && invoiceData_.sender.name) || invoiceData_.senderName || '',
|
||||
creation_date: invoiceData_.creationDate || invoiceData_.createdAt || invoiceData_.date || invoiceData_.operationDate || '',
|
||||
error_type: errorType,
|
||||
error_message: errorMessage,
|
||||
timestamp: new Date().toISOString(),
|
||||
|
|
@ -598,12 +599,11 @@ ETaxes.errors = {
|
|||
let errorsHtml = '<div class="error-log-container">';
|
||||
|
||||
ETaxes.loadingErrors.forEach(function(error, index) {
|
||||
let documentDate = 'No date';
|
||||
let documentDate = '';
|
||||
if (error.creation_date && error.creation_date !== 'Unknown') {
|
||||
try {
|
||||
documentDate = moment(error.creation_date).format('DD.MM.YYYY');
|
||||
} catch (e) {
|
||||
documentDate = 'Invalid date';
|
||||
const m = moment(error.creation_date);
|
||||
if (m.isValid()) {
|
||||
documentDate = m.format('DD.MM.YYYY');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -612,11 +612,13 @@ ETaxes.errors = {
|
|||
// Заголовок ошибки
|
||||
errorsHtml += '<div class="error-header" style="margin-bottom: 10px; padding-bottom: 8px; border-bottom: 1px solid var(--border-color);">';
|
||||
errorsHtml += '<div style="display: flex; justify-content: space-between; align-items: center;">';
|
||||
errorsHtml += '<h5 style="margin: 0; color: var(--text-color);">' +
|
||||
'<strong>' + (error.serial_number || error.invoice_id) + '</strong></h5>';
|
||||
errorsHtml += '<h5 style="margin: 0; color: var(--text-color);">' +
|
||||
'<strong>' + (error.serial_number || error.invoice_id || __('Unknown')) + '</strong></h5>';
|
||||
errorsHtml += '<span class="label label-danger">' + error.error_type + '</span>';
|
||||
errorsHtml += '</div>';
|
||||
errorsHtml += '<small style="color: var(--text-muted);">' + error.sender_name + '</small>';
|
||||
if (error.sender_name) {
|
||||
errorsHtml += '<small style="color: var(--text-muted);">' + error.sender_name + '</small>';
|
||||
}
|
||||
errorsHtml += '</div>';
|
||||
|
||||
// Сообщение об ошибке
|
||||
|
|
@ -662,10 +664,12 @@ ETaxes.errors = {
|
|||
errorsHtml += '</ul></div>';
|
||||
}
|
||||
|
||||
errorsHtml += '<div style="text-align: right; margin-top: 10px;">';
|
||||
errorsHtml += '<small style="color: var(--text-muted);"><em>' + documentDate + '</em></small>';
|
||||
errorsHtml += '</div>';
|
||||
|
||||
if (documentDate) {
|
||||
errorsHtml += '<div style="text-align: right; margin-top: 10px;">';
|
||||
errorsHtml += '<small style="color: var(--text-muted);"><em>' + documentDate + '</em></small>';
|
||||
errorsHtml += '</div>';
|
||||
}
|
||||
|
||||
errorsHtml += '</div>';
|
||||
});
|
||||
|
||||
|
|
@ -743,8 +747,8 @@ ETaxes.errors = {
|
|||
csvContent += "Invoice ID,Serial Number,Date,Supplier,Error Type,Error Message,Timestamp\n";
|
||||
|
||||
ETaxes.loadingErrors.forEach(function(error) {
|
||||
const cleanMessage = error.error_message.replace(/"/g, '""').replace(/\n/g, ' ').replace(/\r/g, ' ');
|
||||
const cleanSender = error.sender_name.replace(/"/g, '""');
|
||||
const cleanMessage = (error.error_message || '').replace(/"/g, '""').replace(/\n/g, ' ').replace(/\r/g, ' ');
|
||||
const cleanSender = (error.sender_name || '').replace(/"/g, '""');
|
||||
|
||||
const row = [
|
||||
error.invoice_id,
|
||||
|
|
@ -1110,9 +1114,15 @@ ETaxes.import = {
|
|||
'<th style="width: 16%; background: var(--control-bg, var(--bg-color));">' + __('Amount') + '</th>' +
|
||||
'</tr></thead><tbody>';
|
||||
|
||||
// Remember the serial from the selection list per invoice id. The detail
|
||||
// endpoint sometimes returns it empty (e.g. old documents), so we fall
|
||||
// back to this during import instead of showing the internal id.
|
||||
ETaxes.import.serialByInvoiceId = {};
|
||||
|
||||
invoices.forEach(function(invoice) {
|
||||
const serialNumber = invoice.serialNumber || '';
|
||||
|
||||
ETaxes.import.serialByInvoiceId[String(invoice.id)] = serialNumber;
|
||||
|
||||
let creationDate = '';
|
||||
if (invoice.createdAt) {
|
||||
creationDate = moment(invoice.createdAt).format('DD.MM.YYYY');
|
||||
|
|
@ -1281,7 +1291,11 @@ ETaxes.import = {
|
|||
if (r.message && !r.message.error) {
|
||||
const creationDate = r.message.creationDate || r.message.date || frappe.datetime.nowdate();
|
||||
const scheduleDate = moment(creationDate).format('YYYY-MM-DD');
|
||||
const serialNumber = r.message.serialNumber || '';
|
||||
// Fall back to the serial captured from the selection list when the
|
||||
// detail omits it, and write it back so the import payload (series/
|
||||
// number, title), the success alert and the error log all use it.
|
||||
const serialNumber = r.message.serialNumber || (ETaxes.import.serialByInvoiceId || {})[String(invoiceId)] || '';
|
||||
r.message.serialNumber = serialNumber;
|
||||
const senderName = r.message.sender ? r.message.sender.name : '';
|
||||
const total = r.message.totalAmount || r.message.amount || 0;
|
||||
|
||||
|
|
@ -1500,7 +1514,10 @@ ETaxes.import = {
|
|||
|
||||
(data.errors || []).forEach(function(err) {
|
||||
ETaxes.loadingErrors.push({
|
||||
invoice_id: err.invoice_id || 'Unknown',
|
||||
invoice_id: err.invoice_id || '',
|
||||
// Prefer the serial the backend resolved; otherwise fall back to the
|
||||
// serial captured from the selection list so we never show the raw id.
|
||||
serial_number: err.serial_number || (ETaxes.import.serialByInvoiceId || {})[String(err.invoice_id)] || '',
|
||||
error_type: err.unmatched_items ? 'Unmapped Items'
|
||||
: err.unmatched_parties ? 'Unmapped Parties'
|
||||
: err.unmatched_units ? 'Unmapped Units'
|
||||
|
|
|
|||
|
|
@ -8,7 +8,11 @@
|
|||
"date",
|
||||
"total",
|
||||
"party",
|
||||
"etaxes_id"
|
||||
"etaxes_id",
|
||||
"series",
|
||||
"number",
|
||||
"comment",
|
||||
"comment2"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
|
|
@ -30,6 +34,26 @@
|
|||
"fieldname": "etaxes_id",
|
||||
"fieldtype": "Data",
|
||||
"label": "etaxes_id"
|
||||
},
|
||||
{
|
||||
"fieldname": "series",
|
||||
"fieldtype": "Data",
|
||||
"label": "Series"
|
||||
},
|
||||
{
|
||||
"fieldname": "number",
|
||||
"fieldtype": "Data",
|
||||
"label": "Number"
|
||||
},
|
||||
{
|
||||
"fieldname": "comment",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Comment"
|
||||
},
|
||||
{
|
||||
"fieldname": "comment2",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Comment 2"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
|
|
|
|||
Loading…
Reference in New Issue