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:
Ali 2026-07-10 10:28:22 +00:00
parent a8e33d4153
commit b2a58e833c
6 changed files with 1220 additions and 152 deletions

File diff suppressed because it is too large Load Diff

View File

@ -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

View File

@ -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
}, },

View File

@ -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 || {};

View File

@ -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",

View File

@ -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",