feat(purchase): import alış aktı (purchase acts) from e-taxes
Acts become a second e-taxes source alongside EQF invoices. The act seller
is a physical person identified by FIN, not a company TIN.
- fetch: get_purchase_acts (act/find.sent), get_purchase_act_details,
load_single_act_batch
- reference loading: Data Loading dialog now has two groups — data types
(items/units/customers/suppliers) and sources (inbox/outbox/acts). Acts
create individual E-Taxes Suppliers (is_individual + fin) plus their
items/units
- mapping: get_unmapped_suppliers / match_similar_suppliers /
create_unmapped_suppliers carry is_individual + fin and create an
Individual ERP Supplier (matched by FIN)
- import: import_act_with_mapping builds PO+PI with purchase_type=1,
act_type="Purchase Act", act_kind, per-line tax_type and 5%
purchase_tax_amount, and update_stock=1 (acts are always goods)
- EQF import now stamps purchase_type=1 / act_type="EQF"
- unified bulk job import_bulk_purchase_documents routes by kind; the PO
list dialog gains an Invoices / Acts / Both source selector
Fixes found while building this:
- update_mapped_statuses reset blocks compared on an unfetched
etaxes_party_name (always None), resetting every Mapped supplier and
customer back to New on each settings save
- E-Taxes Item.etaxes_item_code is unique, but act lines carry no itemId;
the shared CODE-{serial} fallback silently dropped all but the first act
line. Now uses detailOid, else a per-index code
- act items are goods: from_act forces is_service=0 so they default to
stock items
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
a8e33d4153
commit
b2a58e833c
File diff suppressed because it is too large
Load Diff
|
|
@ -635,7 +635,9 @@ frappe.ui.form.on('Purchase Invoice', {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate Tax Type for all items
|
// Tax Type is only required for a Purchase Act (matches the field's
|
||||||
|
// mandatory_depends_on). EQF / Import documents don't use it.
|
||||||
|
if (frm.doc.act_type === 'Purchase Act') {
|
||||||
let missing_tax_type = [];
|
let missing_tax_type = [];
|
||||||
|
|
||||||
for (let item of frm.doc.items) {
|
for (let item of frm.doc.items) {
|
||||||
|
|
@ -652,6 +654,7 @@ frappe.ui.form.on('Purchase Invoice', {
|
||||||
});
|
});
|
||||||
frappe.validated = false;
|
frappe.validated = false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// NOTE: Metal scrap validation removed from client-side to avoid duplicate error messages
|
// NOTE: Metal scrap validation removed from client-side to avoid duplicate error messages
|
||||||
// Server-side validation in send_purchase_api.py will catch this error
|
// Server-side validation in send_purchase_api.py will catch this error
|
||||||
|
|
|
||||||
|
|
@ -873,6 +873,14 @@ ETaxes.import = {
|
||||||
fieldtype: 'Section Break',
|
fieldtype: 'Section Break',
|
||||||
label: __('Settings')
|
label: __('Settings')
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
fieldname: 'source',
|
||||||
|
fieldtype: 'Select',
|
||||||
|
label: __('What to import'),
|
||||||
|
options: 'Invoices\nActs\nBoth',
|
||||||
|
default: 'Invoices',
|
||||||
|
reqd: true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
fieldname: 'warehouse',
|
fieldname: 'warehouse',
|
||||||
fieldtype: 'Link',
|
fieldtype: 'Link',
|
||||||
|
|
@ -924,9 +932,10 @@ ETaxes.import = {
|
||||||
|
|
||||||
const fromDate = moment(values.creationDateFrom).format('DD-MM-YYYY 00:00');
|
const fromDate = moment(values.creationDateFrom).format('DD-MM-YYYY 00:00');
|
||||||
const toDate = moment(values.creationDateTo).format('DD-MM-YYYY 23:59');
|
const toDate = moment(values.creationDateTo).format('DD-MM-YYYY 23:59');
|
||||||
|
const source = (values.source || 'Invoices').toLowerCase();
|
||||||
|
|
||||||
d.hide();
|
d.hide();
|
||||||
ETaxes.import.loadInvoices(fromDate, toDate, values.warehouse);
|
ETaxes.import.loadInvoices(fromDate, toDate, values.warehouse, source);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -935,10 +944,16 @@ ETaxes.import = {
|
||||||
},
|
},
|
||||||
|
|
||||||
// Загрузить инвойсы с фильтрацией дубликатов
|
// Загрузить инвойсы с фильтрацией дубликатов
|
||||||
loadInvoices: function(fromDate, toDate, warehouse, accumulatedInvoices = [], offset = 0) {
|
loadInvoices: function(fromDate, toDate, warehouse, source = 'invoices', accumulatedInvoices = [], offset = 0) {
|
||||||
ETaxes.cancelLoading = false;
|
ETaxes.cancelLoading = false;
|
||||||
$(document).off('progress-cancel.etaxes_import');
|
$(document).off('progress-cancel.etaxes_import');
|
||||||
|
|
||||||
|
// Acts-only: skip the invoice fetch and go straight to acts.
|
||||||
|
if (source === 'acts') {
|
||||||
|
ETaxes.import.loadActs(fromDate, toDate, warehouse, source, [], [], 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (offset === 0) {
|
if (offset === 0) {
|
||||||
frappe.show_alert({
|
frappe.show_alert({
|
||||||
message: __('Fetching E-Taxes invoices...'),
|
message: __('Fetching E-Taxes invoices...'),
|
||||||
|
|
@ -984,9 +999,15 @@ ETaxes.import = {
|
||||||
|
|
||||||
if (hasMore && currentInvoices.length > 0) {
|
if (hasMore && currentInvoices.length > 0) {
|
||||||
const newOffset = offset + currentInvoices.length;
|
const newOffset = offset + currentInvoices.length;
|
||||||
ETaxes.import.loadInvoices(fromDate, toDate, warehouse, allInvoices, newOffset);
|
ETaxes.import.loadInvoices(fromDate, toDate, warehouse, source, allInvoices, newOffset);
|
||||||
} else {
|
} else {
|
||||||
if (allInvoices.length > 0) {
|
// Tag invoices so the selection + import can tell them from acts.
|
||||||
|
allInvoices.forEach(function(inv) { inv._doc_kind = 'invoice'; });
|
||||||
|
|
||||||
|
if (source === 'both') {
|
||||||
|
// Also fetch acts, then show the combined list.
|
||||||
|
ETaxes.import.loadActs(fromDate, toDate, warehouse, source, allInvoices, [], 0);
|
||||||
|
} else if (allInvoices.length > 0) {
|
||||||
frappe.show_alert({
|
frappe.show_alert({
|
||||||
message: __('Processing ') + allInvoices.length + __(' invoices...'),
|
message: __('Processing ') + allInvoices.length + __(' invoices...'),
|
||||||
indicator: 'blue'
|
indicator: 'blue'
|
||||||
|
|
@ -1006,7 +1027,7 @@ ETaxes.import = {
|
||||||
__('Your E-Taxes session has expired. Would you like to authenticate now?'),
|
__('Your E-Taxes session has expired. Would you like to authenticate now?'),
|
||||||
function() {
|
function() {
|
||||||
ETaxes.auth.startProcess(function() {
|
ETaxes.auth.startProcess(function() {
|
||||||
ETaxes.import.loadInvoices(fromDate, toDate, warehouse);
|
ETaxes.import.loadInvoices(fromDate, toDate, warehouse, source);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
function() {
|
function() {
|
||||||
|
|
@ -1043,6 +1064,87 @@ ETaxes.import = {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Загрузить закупочные акты (alış aktı) постранично, затем (с учётом уже
|
||||||
|
// загруженных инвойсов в режиме «Both») отфильтровать дубликаты и показать.
|
||||||
|
loadActs: function(fromDate, toDate, warehouse, source, existingDocs = [], accumulatedActs = [], offset = 0) {
|
||||||
|
ETaxes.cancelLoading = false;
|
||||||
|
|
||||||
|
if (offset === 0) {
|
||||||
|
frappe.show_alert({ message: __('Fetching E-Taxes purchase acts...'), indicator: 'blue' }, 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
ETaxes.utils.getDefaultLogin(function(loginResponse) {
|
||||||
|
if (ETaxes.cancelLoading) return;
|
||||||
|
|
||||||
|
if (!(loginResponse && loginResponse.found && loginResponse.main_token)) {
|
||||||
|
frappe.msgprint({ title: __('Error'), indicator: 'red',
|
||||||
|
message: __('E-Taxes authentication settings not found') });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const mainToken = loginResponse.main_token;
|
||||||
|
|
||||||
|
frappe.call({
|
||||||
|
method: 'invoice_az.api.get_purchase_acts',
|
||||||
|
args: {
|
||||||
|
token: mainToken,
|
||||||
|
filters: JSON.stringify({
|
||||||
|
creationDateFrom: fromDate,
|
||||||
|
creationDateTo: toDate,
|
||||||
|
maxCount: 100,
|
||||||
|
offset: offset
|
||||||
|
})
|
||||||
|
},
|
||||||
|
callback: function(r) {
|
||||||
|
if (ETaxes.cancelLoading) return;
|
||||||
|
|
||||||
|
if (r.message && !r.message.error) {
|
||||||
|
const currentActs = r.message.acts || [];
|
||||||
|
const hasMore = r.message.hasMore || false;
|
||||||
|
const allActs = accumulatedActs.concat(currentActs);
|
||||||
|
|
||||||
|
if (hasMore && currentActs.length > 0) {
|
||||||
|
ETaxes.import.loadActs(fromDate, toDate, warehouse, source,
|
||||||
|
existingDocs, allActs, offset + currentActs.length);
|
||||||
|
} else {
|
||||||
|
allActs.forEach(function(a) { a._doc_kind = 'act'; });
|
||||||
|
const combined = (existingDocs || []).concat(allActs);
|
||||||
|
if (combined.length > 0) {
|
||||||
|
frappe.show_alert({
|
||||||
|
message: __('Processing ') + combined.length + __(' documents...'),
|
||||||
|
indicator: 'blue'
|
||||||
|
}, 3);
|
||||||
|
ETaxes.import.filterDuplicates(combined, mainToken, warehouse);
|
||||||
|
} else {
|
||||||
|
frappe.msgprint({ title: __('Information'), indicator: 'blue',
|
||||||
|
message: __('No documents found for the specified period') });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (r.message && r.message.error === 'unauthorized') {
|
||||||
|
frappe.confirm(
|
||||||
|
__('Your E-Taxes session has expired. Would you like to authenticate now?'),
|
||||||
|
function() {
|
||||||
|
ETaxes.auth.startProcess(function() {
|
||||||
|
ETaxes.import.loadActs(fromDate, toDate, warehouse, source, existingDocs, [], 0);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
function() {
|
||||||
|
frappe.show_alert({ message: __('Authentication canceled. Operation cannot be completed.'),
|
||||||
|
indicator: 'red' }, 5);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
frappe.msgprint({ title: __('Error'), indicator: 'red',
|
||||||
|
message: r.message ? r.message.message : __('Error loading purchase acts') });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function(xhr, status, error) {
|
||||||
|
frappe.msgprint({ title: __('Network Error'), indicator: 'red',
|
||||||
|
message: __('Error fetching data from server: ') + (error || 'Unknown error') });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
// Фильтрация дубликатов
|
// Фильтрация дубликатов
|
||||||
filterDuplicates: function(allInvoices, token, warehouse) {
|
filterDuplicates: function(allInvoices, token, warehouse) {
|
||||||
frappe.call({
|
frappe.call({
|
||||||
|
|
@ -1107,21 +1209,25 @@ ETaxes.import = {
|
||||||
showInvoiceSelection: function(invoices, token, warehouse) {
|
showInvoiceSelection: function(invoices, token, warehouse) {
|
||||||
let invoiceTable = '<div class="etaxes-purchase-scroll" style="max-height: calc(100vh - 360px); min-height: 220px; overflow-y: auto; border: 1px solid var(--border-color); border-radius: 6px;"><table class="table table-bordered etaxes-invoices-table" style="width: 100%; table-layout: fixed; margin-bottom: 0;">';
|
let invoiceTable = '<div class="etaxes-purchase-scroll" style="max-height: calc(100vh - 360px); min-height: 220px; overflow-y: auto; border: 1px solid var(--border-color); border-radius: 6px;"><table class="table table-bordered etaxes-invoices-table" style="width: 100%; table-layout: fixed; margin-bottom: 0;">';
|
||||||
invoiceTable += '<thead style="position: sticky; top: 0; z-index: 1;"><tr>' +
|
invoiceTable += '<thead style="position: sticky; top: 0; z-index: 1;"><tr>' +
|
||||||
'<th style="width: 6%; background: var(--control-bg, var(--bg-color));"><input type="checkbox" class="select-all-invoices"></th>' +
|
'<th style="width: 5%; background: var(--control-bg, var(--bg-color));"><input type="checkbox" class="select-all-invoices"></th>' +
|
||||||
'<th style="width: 24%; background: var(--control-bg, var(--bg-color));">' + __('Number') + '</th>' +
|
'<th style="width: 11%; background: var(--control-bg, var(--bg-color));">' + __('Type') + '</th>' +
|
||||||
'<th style="width: 16%; background: var(--control-bg, var(--bg-color));">' + __('Date') + '</th>' +
|
'<th style="width: 21%; background: var(--control-bg, var(--bg-color));">' + __('Number') + '</th>' +
|
||||||
'<th style="width: 38%; background: var(--control-bg, var(--bg-color));">' + __('Supplier') + '</th>' +
|
'<th style="width: 14%; background: var(--control-bg, var(--bg-color));">' + __('Date') + '</th>' +
|
||||||
|
'<th style="width: 33%; background: var(--control-bg, var(--bg-color));">' + __('Supplier') + '</th>' +
|
||||||
'<th style="width: 16%; background: var(--control-bg, var(--bg-color));">' + __('Amount') + '</th>' +
|
'<th style="width: 16%; background: var(--control-bg, var(--bg-color));">' + __('Amount') + '</th>' +
|
||||||
'</tr></thead><tbody>';
|
'</tr></thead><tbody>';
|
||||||
|
|
||||||
// Remember the serial from the selection list per invoice id. The detail
|
// Remember the serial + kind from the selection list per document id. The
|
||||||
// endpoint sometimes returns it empty (e.g. old documents), so we fall
|
// detail endpoint sometimes returns the serial empty (e.g. old documents),
|
||||||
// back to this during import instead of showing the internal id.
|
// so we fall back to this during import instead of showing the internal id.
|
||||||
ETaxes.import.serialByInvoiceId = {};
|
ETaxes.import.serialByInvoiceId = {};
|
||||||
|
ETaxes.import.kindByInvoiceId = {};
|
||||||
|
|
||||||
invoices.forEach(function(invoice) {
|
invoices.forEach(function(invoice) {
|
||||||
|
const kind = invoice._doc_kind || 'invoice';
|
||||||
const serialNumber = invoice.serialNumber || '';
|
const serialNumber = invoice.serialNumber || '';
|
||||||
ETaxes.import.serialByInvoiceId[String(invoice.id)] = serialNumber;
|
ETaxes.import.serialByInvoiceId[String(invoice.id)] = serialNumber;
|
||||||
|
ETaxes.import.kindByInvoiceId[String(invoice.id)] = kind;
|
||||||
|
|
||||||
let creationDate = '';
|
let creationDate = '';
|
||||||
if (invoice.createdAt) {
|
if (invoice.createdAt) {
|
||||||
|
|
@ -1130,14 +1236,21 @@ ETaxes.import = {
|
||||||
creationDate = moment(invoice.creationDate).format('DD.MM.YYYY');
|
creationDate = moment(invoice.creationDate).format('DD.MM.YYYY');
|
||||||
}
|
}
|
||||||
|
|
||||||
const senderName = invoice.sender ? invoice.sender.name : (invoice.senderName || '');
|
let partyName;
|
||||||
|
if (kind === 'act') {
|
||||||
|
partyName = (invoice.seller && invoice.seller.fullName) ? invoice.seller.fullName : (invoice.sellerName || '');
|
||||||
|
} else {
|
||||||
|
partyName = invoice.sender ? invoice.sender.name : (invoice.senderName || '');
|
||||||
|
}
|
||||||
const amount = invoice.totalAmount || invoice.amount || 0;
|
const amount = invoice.totalAmount || invoice.amount || 0;
|
||||||
|
const typeLabel = (kind === 'act') ? __('Act') : __('Invoice');
|
||||||
|
|
||||||
invoiceTable += '<tr>' +
|
invoiceTable += '<tr>' +
|
||||||
'<td><input type="checkbox" class="select-invoice" data-id="' + invoice.id + '"></td>' +
|
'<td><input type="checkbox" class="select-invoice" data-id="' + invoice.id + '" data-kind="' + kind + '"></td>' +
|
||||||
|
'<td>' + typeLabel + '</td>' +
|
||||||
'<td style="word-break: break-word;">' + serialNumber + '</td>' +
|
'<td style="word-break: break-word;">' + serialNumber + '</td>' +
|
||||||
'<td>' + creationDate + '</td>' +
|
'<td>' + creationDate + '</td>' +
|
||||||
'<td style="word-break: break-word;">' + senderName + '</td>' +
|
'<td style="word-break: break-word;">' + (partyName || '') + '</td>' +
|
||||||
'<td style="text-align: right;">' + ETaxes.utils.formatCurrency(amount) + '</td>' +
|
'<td style="text-align: right;">' + ETaxes.utils.formatCurrency(amount) + '</td>' +
|
||||||
'</tr>';
|
'</tr>';
|
||||||
});
|
});
|
||||||
|
|
@ -1171,23 +1284,26 @@ ETaxes.import = {
|
||||||
],
|
],
|
||||||
primary_action_label: __('Load selected'),
|
primary_action_label: __('Load selected'),
|
||||||
primary_action: function() {
|
primary_action: function() {
|
||||||
const selectedInvoiceIds = [];
|
const selectedDocs = [];
|
||||||
d.$wrapper.find('.select-invoice:checked').each(function() {
|
d.$wrapper.find('.select-invoice:checked').each(function() {
|
||||||
selectedInvoiceIds.push($(this).data('id'));
|
selectedDocs.push({
|
||||||
|
id: $(this).data('id'),
|
||||||
|
kind: $(this).data('kind') || 'invoice'
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
if (selectedInvoiceIds.length === 0) {
|
if (selectedDocs.length === 0) {
|
||||||
frappe.msgprint({
|
frappe.msgprint({
|
||||||
title: __('Warning'),
|
title: __('Warning'),
|
||||||
indicator: 'orange',
|
indicator: 'orange',
|
||||||
message: __('No invoices selected')
|
message: __('No documents selected')
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
d.hide();
|
d.hide();
|
||||||
ETaxes.cancelLoading = false;
|
ETaxes.cancelLoading = false;
|
||||||
ETaxes.import.loadSelectedInvoicesSocketIO(selectedInvoiceIds, token, warehouse);
|
ETaxes.import.loadSelectedInvoicesSocketIO(selectedDocs, token, warehouse);
|
||||||
},
|
},
|
||||||
secondary_action_label: __('Cancel'),
|
secondary_action_label: __('Cancel'),
|
||||||
secondary_action: function() {
|
secondary_action: function() {
|
||||||
|
|
@ -1478,10 +1594,10 @@ ETaxes.import = {
|
||||||
}, ETaxes.PROGRESS_UPDATE_DELAY);
|
}, ETaxes.PROGRESS_UPDATE_DELAY);
|
||||||
},
|
},
|
||||||
|
|
||||||
// Socket.IO bulk import
|
// Socket.IO bulk import (documents = [{id, kind:'invoice'|'act'}])
|
||||||
loadSelectedInvoicesSocketIO: function(invoiceIds, token, warehouse) {
|
loadSelectedInvoicesSocketIO: function(documents, token, warehouse) {
|
||||||
ETaxes.loadingErrors = [];
|
ETaxes.loadingErrors = [];
|
||||||
const total = invoiceIds.length;
|
const total = documents.length;
|
||||||
// Защита от гонки realtime: 'complete' вызывает hide_progress() (обнуляет
|
// Защита от гонки realtime: 'complete' вызывает hide_progress() (обнуляет
|
||||||
// frappe.cur_progress). Опоздавший 'progress' иначе пересоздаёт модал, который
|
// frappe.cur_progress). Опоздавший 'progress' иначе пересоздаёт модал, который
|
||||||
// уже никто не закрывает. Флаг делает обработчик прогресса no-op после finish.
|
// уже никто не закрывает. Флаг делает обработчик прогресса no-op после finish.
|
||||||
|
|
@ -1521,11 +1637,13 @@ ETaxes.import = {
|
||||||
error_type: err.unmatched_items ? 'Unmapped Items'
|
error_type: err.unmatched_items ? 'Unmapped Items'
|
||||||
: err.unmatched_parties ? 'Unmapped Parties'
|
: err.unmatched_parties ? 'Unmapped Parties'
|
||||||
: err.unmatched_units ? 'Unmapped Units'
|
: err.unmatched_units ? 'Unmapped Units'
|
||||||
|
: err.unmatched_suppliers ? 'Unmapped Suppliers'
|
||||||
: 'Import Error',
|
: 'Import Error',
|
||||||
error_message: err.error || 'Unknown error',
|
error_message: err.error || 'Unknown error',
|
||||||
unmatched_items: err.unmatched_items,
|
unmatched_items: err.unmatched_items,
|
||||||
unmatched_parties: err.unmatched_parties,
|
unmatched_parties: err.unmatched_parties,
|
||||||
unmatched_units: err.unmatched_units
|
unmatched_units: err.unmatched_units,
|
||||||
|
unmatched_suppliers: err.unmatched_suppliers
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -1538,9 +1656,9 @@ ETaxes.import = {
|
||||||
});
|
});
|
||||||
|
|
||||||
frappe.call({
|
frappe.call({
|
||||||
method: 'invoice_az.api.import_bulk_purchase_invoices',
|
method: 'invoice_az.api.import_bulk_purchase_documents',
|
||||||
args: {
|
args: {
|
||||||
invoice_ids: JSON.stringify(invoiceIds),
|
documents: JSON.stringify(documents),
|
||||||
token: token,
|
token: token,
|
||||||
warehouse: warehouse
|
warehouse: warehouse
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -592,6 +592,8 @@ frappe.ui.form.on('E-Taxes Settings', {
|
||||||
|
|
||||||
row.etaxes_supplier_name = supplier.etaxes_party_name;
|
row.etaxes_supplier_name = supplier.etaxes_party_name;
|
||||||
row.etaxes_tax_id = supplier.etaxes_tax_id;
|
row.etaxes_tax_id = supplier.etaxes_tax_id;
|
||||||
|
row.is_individual = supplier.is_individual ? 1 : 0;
|
||||||
|
row.fin = supplier.fin || '';
|
||||||
|
|
||||||
if (supplier.supplier_group)
|
if (supplier.supplier_group)
|
||||||
row.supplier_group = supplier.supplier_group;
|
row.supplier_group = supplier.supplier_group;
|
||||||
|
|
@ -936,6 +938,40 @@ function show_load_dialog_with_summary(frm, summary) {
|
||||||
label: __('Load Suppliers'),
|
label: __('Load Suppliers'),
|
||||||
default: 1,
|
default: 1,
|
||||||
description: __('Load suppliers from invoice senders')
|
description: __('Load suppliers from invoice senders')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldname: 'sources_section',
|
||||||
|
fieldtype: 'Section Break',
|
||||||
|
label: __('Sources to Load From')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldname: 'load_inbox',
|
||||||
|
fieldtype: 'Check',
|
||||||
|
label: __('Incoming Invoices'),
|
||||||
|
default: 1,
|
||||||
|
description: __('Purchase invoices from the e-taxes inbox')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldname: 'src_col_break_1',
|
||||||
|
fieldtype: 'Column Break'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldname: 'load_outbox',
|
||||||
|
fieldtype: 'Check',
|
||||||
|
label: __('Outgoing Invoices'),
|
||||||
|
default: 1,
|
||||||
|
description: __('Sales invoices from the e-taxes outbox')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldname: 'src_col_break_2',
|
||||||
|
fieldtype: 'Column Break'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldname: 'load_acts',
|
||||||
|
fieldtype: 'Check',
|
||||||
|
label: __('Purchase Acts'),
|
||||||
|
default: 0,
|
||||||
|
description: __('Purchase acts (alış aktı) — individual sellers identified by FIN')
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
size: 'large',
|
size: 'large',
|
||||||
|
|
@ -943,11 +979,15 @@ function show_load_dialog_with_summary(frm, summary) {
|
||||||
primary_action: function() {
|
primary_action: function() {
|
||||||
const values = d.get_values();
|
const values = d.get_values();
|
||||||
|
|
||||||
// Validate selections
|
// Validate selections — need at least one data type AND one source.
|
||||||
if (!values.load_items && !values.load_customers && !values.load_suppliers && !values.load_units) {
|
if (!values.load_items && !values.load_customers && !values.load_suppliers && !values.load_units) {
|
||||||
frappe.msgprint(__('Please select at least one data type to load.'));
|
frappe.msgprint(__('Please select at least one data type to load.'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (!values.load_inbox && !values.load_outbox && !values.load_acts) {
|
||||||
|
frappe.msgprint(__('Please select at least one source to load from.'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (values.date_from > values.date_to) {
|
if (values.date_from > values.date_to) {
|
||||||
frappe.msgprint(__('Date From cannot be later than Date To.'));
|
frappe.msgprint(__('Date From cannot be later than Date To.'));
|
||||||
|
|
@ -959,22 +999,27 @@ function show_load_dialog_with_summary(frm, summary) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show confirmation with selected types
|
// Show confirmation with selected types + sources
|
||||||
const selected_types = [];
|
const selected_types = [];
|
||||||
if (values.load_items) selected_types.push(__('Items'));
|
if (values.load_items) selected_types.push(__('Items'));
|
||||||
if (values.load_customers) selected_types.push(__('Customers'));
|
if (values.load_customers) selected_types.push(__('Customers'));
|
||||||
if (values.load_suppliers) selected_types.push(__('Suppliers'));
|
if (values.load_suppliers) selected_types.push(__('Suppliers'));
|
||||||
if (values.load_units) selected_types.push(__('Units'));
|
if (values.load_units) selected_types.push(__('Units'));
|
||||||
|
|
||||||
|
const selected_sources = [];
|
||||||
|
if (values.load_inbox) selected_sources.push(__('Incoming Invoices'));
|
||||||
|
if (values.load_outbox) selected_sources.push(__('Outgoing Invoices'));
|
||||||
|
if (values.load_acts) selected_sources.push(__('Purchase Acts'));
|
||||||
|
|
||||||
// УЛУЧШЕНО: Более подробное сообщение о процессе
|
// УЛУЧШЕНО: Более подробное сообщение о процессе
|
||||||
frappe.confirm(
|
frappe.confirm(
|
||||||
__('This will load {0} from E-Taxes for the period {1} to {2}.<br><br>' +
|
__('This will load {0} from {3} for the period {1} to {2}.<br><br>' +
|
||||||
'<strong>Process:</strong><br>' +
|
'<strong>Process:</strong><br>' +
|
||||||
'1. First, all invoices will be fetched (may take 1-2 minutes)<br>' +
|
'1. First, all documents will be fetched (may take 1-2 minutes)<br>' +
|
||||||
'2. Then each invoice will be processed for reference data<br><br>' +
|
'2. Then each document will be processed for reference data<br><br>' +
|
||||||
'<strong>This operation may take several minutes depending on the number of invoices.</strong><br><br>' +
|
'<strong>This operation may take several minutes depending on the number of documents.</strong><br><br>' +
|
||||||
'Continue?',
|
'Continue?',
|
||||||
[selected_types.join(', '), values.date_from, values.date_to]),
|
[selected_types.join(', '), values.date_from, values.date_to, selected_sources.join(', ')]),
|
||||||
function() {
|
function() {
|
||||||
d.hide();
|
d.hide();
|
||||||
start_reference_data_loading(frm, values);
|
start_reference_data_loading(frm, values);
|
||||||
|
|
@ -1065,7 +1110,10 @@ function start_reference_data_loading(frm, values) {
|
||||||
load_items: values.load_items ? 1 : 0,
|
load_items: values.load_items ? 1 : 0,
|
||||||
load_customers: values.load_customers ? 1 : 0,
|
load_customers: values.load_customers ? 1 : 0,
|
||||||
load_suppliers: values.load_suppliers ? 1 : 0,
|
load_suppliers: values.load_suppliers ? 1 : 0,
|
||||||
load_units: values.load_units ? 1 : 0
|
load_units: values.load_units ? 1 : 0,
|
||||||
|
load_inbox: values.load_inbox ? 1 : 0,
|
||||||
|
load_outbox: values.load_outbox ? 1 : 0,
|
||||||
|
load_acts: values.load_acts ? 1 : 0
|
||||||
},
|
},
|
||||||
callback: function(r) {
|
callback: function(r) {
|
||||||
const msg = r.message || {};
|
const msg = r.message || {};
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,8 @@
|
||||||
"field_order": [
|
"field_order": [
|
||||||
"etaxes_supplier_name",
|
"etaxes_supplier_name",
|
||||||
"etaxes_tax_id",
|
"etaxes_tax_id",
|
||||||
|
"is_individual",
|
||||||
|
"fin",
|
||||||
"erp_supplier",
|
"erp_supplier",
|
||||||
"supplier_group",
|
"supplier_group",
|
||||||
"payment_terms",
|
"payment_terms",
|
||||||
|
|
@ -25,6 +27,20 @@
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Tax ID"
|
"label": "Tax ID"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"description": "Physical person (Purchase Act seller). Matched by FIN on act import.",
|
||||||
|
"fieldname": "is_individual",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Is Individual"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "is_individual",
|
||||||
|
"fieldname": "fin",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "FIN"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "erp_supplier",
|
"fieldname": "erp_supplier",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,14 @@
|
||||||
"etaxes_address",
|
"etaxes_address",
|
||||||
"source_invoice",
|
"source_invoice",
|
||||||
"status",
|
"status",
|
||||||
|
"is_individual",
|
||||||
|
"individual_section",
|
||||||
|
"fin",
|
||||||
|
"passport_serial_number",
|
||||||
|
"first_name",
|
||||||
|
"last_name",
|
||||||
|
"phone_number",
|
||||||
|
"date_of_birth",
|
||||||
"supplier_mapping_section",
|
"supplier_mapping_section",
|
||||||
"mapped_supplier",
|
"mapped_supplier",
|
||||||
"supplier_group",
|
"supplier_group",
|
||||||
|
|
@ -64,6 +72,58 @@
|
||||||
"options": "New\nMapped\nProcessing",
|
"options": "New\nMapped\nProcessing",
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"description": "Set for sellers imported from Purchase Acts (physical persons identified by FIN)",
|
||||||
|
"fieldname": "is_individual",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Is Individual",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"collapsible": 1,
|
||||||
|
"depends_on": "is_individual",
|
||||||
|
"fieldname": "individual_section",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Individual (Purchase Act Seller)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "fin",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "FIN",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "passport_serial_number",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Passport Serial Number",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "first_name",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "First Name",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "last_name",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Last Name",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "phone_number",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Phone Number",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "date_of_birth",
|
||||||
|
"fieldtype": "Date",
|
||||||
|
"label": "Date of Birth",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "supplier_mapping_section",
|
"fieldname": "supplier_mapping_section",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue