Plank help · updated 2026-09-21

Importing incoming ЭСФ into 1C

How an incoming electronic invoice becomes a linked СчетФактураПолученный + ПоступлениеТоваровУслуг pair in 1C: the three-way link that must exist, which section a line belongs in (the nomenclature card decides, per line — never a per-base setting), the receipt-line analytics 1C will not infer and will not complain about, which of the ЭСФ's two numbers is «the number», which ЭСФ a batch may start from, how to pick a historical template, resolving nomenclature and units without inventing catalog records, and the stop conditions.

Agents: fetch the raw markdown of this page at /en/help/1c-incoming-esf-import.md

Importing incoming ЭСФ into 1C

An incoming ЭСФ is not a document. It is a notification that a supplier issued one, and the accounting only exists once 1C holds a received invoice and a goods/services receipt, linked to each other and to the ЭСФ.

First: the ЭСФ has to already be inside 1C

Everything on this page starts from a row in Document_ЭСФ. Getting it there is a separate job that neither this page nor any script here performs, and three things are easy to confuse:

What it doesPuts ЭСФ into Document_ЭСФ?
1C's own «Обмен с ИС ЭСФ»The exchange inside 1C, set up by the accountant with their ЭЦПYes — this is the prerequisite
Connecting ЭСФA direct client to esf.gov.kz: lists issued and received invoices, issues new onesNo — it hands invoice data back as files
This page / 1c-esf-import-kzTurns an ЭСФ already in 1C into a linked invoice + receiptReads them; never creates them

The trap is the middle row. connecting-esf can see received invoices, so a workspace that has connected ЭСФ reasonably expects the import to work — and it will find nothing, because that connection reaches the portal and this one reads 1C.

If the base holds no ЭСФ, the question is whether the 1C-side exchange is configured and has run. No script substitutes for it. check-access.py reports Document_ЭСФ as published-but-empty, and the importer distinguishes "this base has no ЭСФ at all" from "that registration number is not here" — different problems, different next steps.

Going the other way — issuing your own invoices to the portal — is Connecting ЭСФ. This page is only about ЭСФ that have arrived.

There is a kit for this: 1c-esf-import-kz. Install it (see the 1C starter kit) rather than writing a fresh script:

python3 scripts/1c/esf/import-esf.py --first 5              # dry run — read the dates it picked
python3 scripts/1c/esf/import-esf.py --registration 35318 --apply

--first N sorts ascending, so it hands you the oldest unprocessed ЭСФ. In a base that has been running for years those are almost never the ones being asked about — see "Batches, and which ЭСФ a batch starts from" below. Read the dates in the dry run before --apply, and when they are not the period you want, drive the write with --registration per document instead of trusting the ordering.

The rules below are what it enforces, and what you have to honour if you adapt it.

Everything here assumes you have already read Working with 1C over OData. The rules there — match by identifier, never post, re-read after every write, never hard-code a Ref_Key — apply unchanged and are not repeated.

The chain, and why two documents is not enough

Incoming ЭСФ ──▶ СчетФактураПолученный ◀──▶ ПоступлениеТоваровУслуг

Three links, all of them required:

  1. the ЭСФ's СчетФактура field points at the received invoice;
  2. the received invoice points at the receipt, both as its main reference and as a row in ДокументыОснования;
  3. the receipt points back at the received invoice.

An ЭСФ is not processed because two new documents exist. Miss the first link and 1C's own interface still shows the ЭСФ as unhandled, however complete the two documents look — so the accountant opens it and finds the chain broken. Write the ЭСФ→invoice link even when the user asked only for a draft.

Choose the target database from the ЭСФ, not from credentials

The organisation named in the ЭСФ decides which 1C base you write to. Resolve it through the workspace's register of bases. Never take the address that happens to be in a shared credentials.json — a practice with 17 client companies has 17 bases, and the cost of guessing is a document in someone else's accounting.

Pick a template from history, then take only structure from it

Before creating anything, read this base's own already posted incoming ЭСФ and their linked documents. Prefer, in order:

  1. the same supplier, matched by exact Ref_Key;
  2. the same operation kind — goods or services;
  3. the largest overlap of confirmed nomenclature;
  4. on a tie, the most recent correct document.

Never template off a document with a material amount discrepancy, wrong VAT, or incomplete links — or off a document that reads «Проведён» and moved no register. 1C «posts» a document it refused in silence: the flag is set and nothing reaches the books. import-esf.py reads which posted documents actually recorded a movement and ignores the rest as templates and as evidence; the report lists them (posted-without-movements) — check those in the 1C client.

From the template, take only settings. Supplier contract, warehouse, settlement and advance accounts, author and responsible, currency, line-level accounts and analytics, operation kind, the shape of the tabular sections.

The contract is not from the template either. It belongs to one supplier. When the ЭСФ names its contract, the import tries, in this order, and the dry run says which step answered:

  1. a choice the accountant recorded for this ЭСФ (esf.contract_by_registration);
  2. the same number and date;
  3. the same contract written differently — same number with another date, or the same words («Договор оферты» and «Оферта»); only if exactly one card fits;
  4. what the accountant's own recent posted documents for this supplier all used;
  5. the supplier's only contract — used, but the write waits until somebody has looked (--accept-warning contract-substituted).

If none of these answers, the ЭСФ is not written and the report lists every candidate contract with its reference, so the choice can be recorded and the import run again. When the ЭСФ names no contract: the one this supplier's posted documents use, otherwise the newest card.

Never take content. Dates, amounts, numbers, quantities, prices, VAT and the primary document all come from the ЭСФ in front of you. Copying an amount off a template is how a plausible document ends up describing a delivery that never happened.

What "stable" looks like in practice: in a surveyed base, every posted chain used the same warehouse, the same settlement account, the same advance account and the same responsible person — while every supplier had its own contract, and one field (УчитыватьКПН) flipped for every ЭСФ dated on or after a particular day. So: settings are stable per base, contracts are stable per supplier, and anything that changed mid-year needs its cutover date established rather than guessed.

What the import fills by rule instead of asking

A field every posted document fills, but not always the same way, is sometimes a real choice and sometimes a consequence of something already known. The import fills only the second kind, and the dry run names the rule (derived-by-rule):

  • rate and multiplicity — from the posted documents in the same currency, when they all agree;
  • a copy of another field — a field that on every posted document equals the counterparty, the contract or a date of that document («Поставщик_Key», the rows' «ДатаОборота») is filled from that field; it also replaces a value copied from history, because on a base with one supplier's history that value is the old supplier;
  • «УчитыватьНДС» for an ЭСФ without VAT — on, with the base's own «Без НДС» rate, and no question: the document total is the same either way, and «on» never drops the document out of VAT accounting. The one exception is a supplier whose own posted invoices without VAT are all «off» (at least three) — then the kit follows the supplier. The «Без НДС» rate is never guessed: it comes from the ЭСФ line, esf.no_vat_rate_ref, the mapping's vat_rates table, the one catalog element of that name, or the base's own posted lines without VAT; if none of them answers, that ЭСФ is refused with vat-rate-unresolved. Decided at creation, because 1C refuses it later;
  • a supplier with no posted documents of its own — the settlement account, the advance account and «СобытиеОС» from the newest twenty posted documents of the same operation kind, only when all twenty agree.

What no rule settles is still reported, with every value the base has used and how often, so one answer closes it.

Another supplier's chain as the template

A supplier with no posted chain of its own takes its settings from another supplier's chain. That stops the write (template-other-supplier) only when a field that decides how the documents post came from that chain and from nothing else. The import builds the documents a second time without the template and compares: settlement and advance accounts, «СобытиеОС», «УчитыватьНДС», «СуммаВключаетНДС», the contract, and the rows' accounts, analytics and VAT. If every one of them was set by a rule, by the base's own history or by a recorded decision, the report only notes the borrowed template and names where each field came from — no --accept-warning needed. Otherwise the warning names the fields that came from the other supplier alone; check them against this supplier's documents before writing.

Until 2026-09-18, «СуммаВключаетНДС» (does this base write amounts gross or net) could never clear this check on its own, even when the whole organisation agreed about it: its common, entirely normal answer of False (net) read the same as «this base never fills this field», so it was silently left out of every history-derived setting rather than recognised as this base's own decision. Fixed — it is measured the same way every other per-base setting is.

Dates and numbers come from specific places

FieldSource
Received invoice datethe ЭСФ date
Incoming invoice numberthe ЭСФ's Номер — see below
Receipt datethe ЭСФ turnover date (дата оборота), not the ЭСФ date
Receipt's primary-document number and datethe waybill or act, not the ЭСФ

An ЭСФ has two numbers and only one of them is "the ЭСФ number". Номер is what the supplier issued the invoice under and what the accountant reconciles against. РегистрационныйНомер is the portal's identity for the document — a long machine key, used for finding it and for duplicate protection. The received invoice's «Номер входящего документа» takes Номер.

Both are non-empty strings that look like an invoice number, so putting the wrong one there produces a document that matches nothing on paper and is unfalsifiable from inside 1C. Read it back and compare against Номер specifically.

Do not substitute either of them for the waybill number, and do not replace a technical empty date (0001-01-01) with a guess. Most receipts in a real base carry that sentinel legitimately, because no waybill date was ever known.

Goods or services: the nomenclature card decides, per line

1C keeps purchases in two tabular sections — «Товары» and «Услуги» — because they post differently: a goods row moves stock, a services row books straight to expense. Which section a line belongs in is read off the resolved nomenclature card's Услуга flag, line by line.

It is never a per-base setting, and never inherited from the template. A practice set it once, for a client whose base sold only services, and within hours every other client's goods were being filed as services — while the accountant, correctly, kept saying so in the chat and nothing changed, because the instruction was in the chat and the mistake was in a config file. A setting cannot know what a line is; the card can. The same rule decides everything downstream: the accounts, and whether a stated quantity of 0 is a lump-sum service or a credit note that needs a person.

An ЭСФ carrying both kinds becomes two receipts under one invoice. «ВидОперации» is one value on the document header, so one receipt cannot hold both. The import writes a goods receipt and a services receipt, and one received invoice that names both in «ДокументыОснования» (the ЭСФ's «СчетФактура» link is a single reference, so there is one invoice). The report says so with an esf-split warning; check both receipts against the ЭСФ before posting. The check after the write verifies each receipt against its own lines and the invoice against the whole ЭСФ.

The split happens only when every line has a known kind — resolved to a nomenclature card, or one this run has been permitted to create (see "A card this run will create is not reported as unresolved" above) — because the kind is read from the card: one line with no card and no permission is an unknown kind, and the ЭСФ is then refused instead. A base that wants every mixed ЭСФ refused and split by hand sets "split_mixed": false under esf in mapping.json. Before kit 1.18.2 a correctly split chain was reported «verification FAILED», because its first receipt was checked against every line of the ЭСФ.

A base's own operation kind is kept. If esf.copy_from_template lists «ВидОперации», the receipt takes the kind the template chain carries — some bases file every purchase as «ПокупкаКомиссия» — and the check after the write expects exactly that value. The one kind never inherited is the one the mapping declares for the other sort of line («Услуги» on goods, «Товары» on services); the kind is then read off the lines as above. Before kit 1.18.1 such a chain was written correctly and still reported «verification FAILED».

If a goods line has already been filed as a service, note that 1C will not let you PATCH the kind of an existing document: the fix is to replace the whole chain with supersede-esf.py (see «Replacing a chain that cannot be corrected»).

Receipt-line analytics: what 1C will not infer and will not complain about

These are the fields an accountant checks first and the ones a script most often leaves empty. Every one of them saves cleanly when wrong or absent — the document totals correctly and looks finished in a listing — so none of them can be verified by anything except reading the row back.

FieldWhat it isWhere it comes from
НДСВидОборотаturnover kind, e.g. «Общий»this base's rule, per base
НДСВидПоступления«Товары, приобретенные с НДС» / «…без НДС»per line, from whether that line carries VAT
СчетЗатратБУ / СчетЗатратНУcost account, e.g. 7210this base's rule, per base
СубконтоЗатратБУ1 / НУ1cost article («Статья затрат»)per line — the same supplier's own posted receipts for that product
СубконтоЗатратБУ2departmentthis base's rule, per base
СчетУчетаНДСVAT accountthis base's rule, per base

Three things worth stating separately, because each has already been shipped wrong:

  • НДСВидПоступления is decided per line, by VAT. A line carrying VAT is «приобретенные с НДС»; a line without is the other one. Getting it backwards files a wrong VAT return and leaves the document total correct, so nothing downstream catches it.
  • The cost article is per-service, not per-base. The same base books an inspection and a rent to different articles. Take it from what this supplier's own posted receipts did with that product, and leave it for the accountant when the base has never decided — an invented article is worse than an empty one.
  • Субконто* are polymorphic references and need their _Type companion (StandardODATA.Catalog_СтатьиЗатрат, …Catalog_ПодразделенияОрганизаций). Write the value alone and 1C stores it as Undefined: the field reads as filled everywhere except on the accountant's screen, where it is blank.

Take these from a row of a verified posted chain, not from its header. They are row-level fields; 1C does not put them on the document, and code that looks for them there finds nothing and writes the all-zeroes GUID without complaining.

Arithmetic, per line, before anything is written

Check to the kopeck: each line's net, VAT and gross; each document total; that net + VAT = gross; and that the sum of lines equals the document total.

The VAT rate is decided per line of the ЭСФ in front of you. Not per supplier, and never copied from an older document. Suppliers change rates mid-relationship, and a VAT figure that differs between the invoice and the receipt is exactly the signal that per-line resolution was skipped somewhere. An unexplained discrepancy is a stop condition, not a rounding note.

Then check the total 1C will actually store — before writing. 1C recomputes «СуммаДокумента» from the rows and two header flags: with «УчитыватьНДС» off it drops the VAT, and with «СуммаВключаетНДС» off it adds the VAT on top of the rows. A VAT-bearing ЭСФ written through a template that does not account for VAT is stored as its net, and once 1C holds it the flag cannot be corrected. Since kit 1.19.0 the import computes that total for every document it is about to send — in the dry run too — and refuses the ЭСФ with esf-total-mismatch if it is not the gross, naming the template, both flags, the net, the VAT and the expected gross. Do not work around it: fix the template choice or the mapping, run the dry run again, and only then --apply.

Nomenclature: resolve, don't invent

Per line, in strict order:

  1. a non-null nomenclature reference in the ЭСФ itself, if the card exists and is active;
  2. an exact external identifier (GTIN, national-catalogue code) resolving to exactly one active card;
  3. an unambiguous exact historical match of the source name for that same supplier;
  4. an unambiguous exact match against an active catalog card;
  5. creating a new card — only with the user's explicit permission.

A similar name is not an identifier. Dose, concentration, volume, pack quantity, dosage form, brand and manufacturer all have to match. Two cards sharing an identifier or an exact name means stop and ask, unless the ЭСФ carries a confirmed reference.

When you do create a card, with permission: re-check for duplicates by external identifier and exact name first, use the exact source name, keep GTIN / ТН ВЭД / full name, set the base unit from the ЭСФ code, set the VAT rate from the current line, clone only accounting fields from a comparable card in the same base, strip server-owned fields, note the source ЭСФ registration number in the comment, and re-read the card to confirm name, unit, VAT and that it is active.

The same GTIN under slightly different names is one product. Use the existing card.

A card this run will create is not reported as unresolved

Run with --create-nomenclature, a line that needs step 5 gets a card planned for it, not created on the spot — creation happens later in the same run, once every other decision for the ЭСФ has been made, so a dry run always plans and never writes. Until 2026-09-18 the pre-write check that compares a draft against this base's own posted history did not know that, and asked you to pick one of the base's existing cards to answer a question --create-nomenclature had already answered. That is fixed: a line whose card this run has been permitted to create is reported as settled — «карточка будет создана в этом прогоне» — and only that line. Without the flag, or for a line nothing was planned for, the same field still holds the chain exactly as before.

A per-line record can say this explicitly too: {"source_name": "...", "unit_ref": "...", "create": {"description": "..."}} — «no card here matches this line, make one» — instead of leaving it to the automatic steps above to fail before step 5 is reached. description is optional and, when given, overrides what the new card is called (the ЭСФ line's own name is used otherwise). Creating still needs --create-nomenclature and still goes through the same duplicate check and read-back as any other card this run creates — the record only says a person already decided, so the run does not have to fail its way there.

A planned card also settles whether an ЭСФ mixing goods and services (below) may be split. Until 2026-09-18 the split check asked the same question the paragraph above already fixed — "does this line have a card right now" — for a different purpose, and a line whose card this run had planned but not yet written still read as unknown. A dry run held such a chain as unsplittable while the very next --apply, where the card is created before the check runs, split and wrote it without a warning. Both runs now agree: a planned card is a known kind on either one, and a line nothing has planned still holds the split the same way on both.

Until 2026-09-19 the SAME mixed shape — a planned card, on an ЭСФ split into a goods receipt and a services receipt — could still disagree between a dry run and --apply, for two more reasons layered on top of each other. A goods/services split renumbers a line's position within its own section (so a second receipt does not start counting at "line 3"), and the check that excuses a planned card was still matching on the OLD position, so the excuse silently stopped applying the moment a chain split. Separately, a supplier with very few posted documents of its own could make the base appear to always use ONE fixed product for every line — which is never true of a line's own nomenclature — and that false "always the same" reading masked the first bug for a day. Both are fixed, and a new automated check now runs a dry run and --apply back to back, on the same data, across several such shapes, so the two can no longer quietly drift apart again.

Units

  1. the active unit named in the ЭСФ;
  2. an exact, unambiguous match of ЕдиницаИзмеренияКод against this base's unit catalog;
  3. the unambiguous unit of confirmed nomenclature or of that product's history.

The unit's text is routinely empty or wrong; the code wins, but only on an exact unique match. One code resolving to several active units, or no code at all, is stop-and-ask. Expect this to bite often — partial unit recognition is the norm, not the exception.

Filling units on an ЭСФ that is already in the base

The rules above are for a chain you are creating. An ЭСФ that arrived from the portal months ago and sits there with empty «Ед. изм.» and «Ед. изм. остатков» is a different job: it cannot be posted that way, and filling those rows by hand across a month of invoices is work nobody does carefully by the fortieth row.

fill-esf-units.py does it. The rule below belongs to the accounting practice that uses it — confirmed by their administrator on 2026-08-04 — so all of it is mapping.json configuration under esf.units_fill, not code:

  1. The nomenclature card's base unit wins, always. It is what this base already decided about this product, and it outranks anything read off a label.
  2. Failing that, finished pharmaceutical packaging is шт — tablets, capsules, dragee, ampoules, vials, tubes, and anything carrying мг / мл / №. What is sold is the package, not its contents. A row reading "Меновазин 40 мл" is one bottle, not forty millilitres.
  3. Loose or weighed goods are left alone, even when the name also matches rule 2.
  4. A conversion coefficient other than 1 leaves the row alone. The two units are then different measures and the relationship between them is not yours to infer. At coefficient 1 they must agree, and a payload where they don't is refused.
  5. Nothing else is guessed. A row the card is silent about and the name does not identify goes on the manual list, with the reason.

Rule 5 is the one that matters, and it is why the report is always three lists — filled (with the source of each unit), already complete, and needs-a-human. A run that fills every row and flags nothing has been lucky or has guessed, and you cannot tell which from the outcome. A unit written onto a row is indistinguishable from a unit that was checked, and it changes what every quantity on that row means.

Two mechanical traps, both of which delete data silently:

  • Send the whole tabular section. A partial section replaces the section — the rows you left out are deleted. "Only send what changed" destroys the rest of the invoice.
  • Only the item reference and the two units may move. Quantity, price, amount and VAT are the supplier's figures. The script proves this with a field-level diff before writing rather than merely intending it, because a payload assembled from a re-read row carries every field.

This is a write to a document the kit did not create, so it takes the same guard as link_esf_to_invoice: the ЭСФ must belong to the pinned organisation, and the totals and the invoice link are re-read afterwards to prove the edit moved nothing else. Nothing is posted.

Duplicate protection

Identify an ЭСФ by the whole set — Ref_Key, registration number, supplier, organisation, number and date, turnover date, amount, and the composition of its lines. Before creating, confirm: the ЭСФ is incoming, active, and belongs to the target organisation; its СчетФактура field is empty; the registration number occurs once; no invoice already exists with that supplier, incoming number, date and amount; no receipt already exists for that waybill, supplier, date and amount; and no chain already carries this ЭСФ's marker.

The same supplier number is not automatically a duplicate. When an old and a new document nearly match but the registration numbers differ, that is a re-issued or corrected ЭСФ — a separate decision, not a skip.

Writing the chain

  1. create the fully populated unposted receipt;
  2. re-read it and take its Ref_Key;
  3. create the unposted received invoice with the main reference and the ДокументыОснования row pointing at the receipt;
  4. re-read the invoice;
  5. write the receipt's back-reference to the invoice;
  6. write the ЭСФ's reference to the invoice;
  7. re-read all three.

All three stay Posted=false. Every draft carries a technical comment with the source ЭСФ registration number, and the script is idempotent: a second run finds the cards and documents it already created instead of making more.

That comment is required. It holds the anchor that lets a later run recognise a chain interrupted after step 1, so an empty one is refused before anything is read (no-import-anchor). The text comes from esf.comment in mapping.json, which applies to ЭСФ documents only. If this base has comments switched off for bank documents (documents.write_comment: false) and names no esf.comment, set one, for example «Создано из входящей ЭСФ. Требует проверки бухгалтером.». Do not switch comments back on for the whole base to get past it.

Verify by reading, separately

A successful POST is not evidence. In an independent read, confirm: one ЭСФ, one invoice, one receipt; all three on the same organisation and supplier; the same contract on invoice and receipt; the ЭСФ pointing at the right invoice; invoice and receipt linked both ways; line count matching the ЭСФ; nomenclature and unit filled on every line; quantity, price, amount, rate and VAT matching the source; each document total matching the ЭСФ; dates and numbers taken from the right sources; and nothing posted or marked for deletion.

Posting

Only on a separate, explicit instruction from the user. Re-read the whole chain first, then post receipt → invoice → ЭСФ using the standard OData Post() action, skipping anything already posted. Afterwards confirm independently that all three are Posted=true, every link survived, amounts and line counts are unchanged, and no duplicates or deletion marks appeared.

Posted=true alone proves nothing — post with scripts/1c/odata/post-documents.py, which counts a document posted only when a register movement was recorded, and clears the flag when none was.

A document that «posted» with no movements is repaired, not reposted — posting it again with the same empty fields changes nothing. Run python3 scripts/1c/esf/repair-esf-posting.py <entity set> <Ref_Key> --mapping … --credentials … (reads only) and show the user what it would fill; with their consent, run it again with --apply --confirm <digest>. It clears the false flag, fills the fields every recent working document of the same operation carries (from the same rules the import uses), posts, and proves the movement — one document per run. If it refuses, relay its reason: a field with several candidates is the accountant's choice, and a field 1C accepts only at creation means replacing the chain (below).

Replacing a chain that cannot be corrected

Some invoice fields are accepted only while the document is being created — «КурсВзаиморасчетов», «КратностьВзаиморасчетов», «УчитыватьНДС» — and a chain with one of them wrong, or with the wrong total, is replaced rather than edited. Never write a one-off script for this. A hand-written replacement that created a new invoice on each failed attempt left one ЭСФ with five live invoices, and 1C then refused every further write to that chain. Use supersede-esf.py:

  1. python3 scripts/1c/esf/supersede-esf.py --registration <ЭСФ> --replace-invoice <invoice №> — a dry run. It names the chain the ЭСФ stands on and, while that chain is still live, tells the accountant exactly what to do in 1C: unpost it if it is posted and mark the invoice and its receipt(s) for deletion. The kit does neither itself.
  2. After she has, run the same dry run again. Now it predicts the new chain and prints --apply --confirm <digest>. The digest covers the ЭСФ, the chain being replaced and every payload, so it is good only for what she read.
  3. Run it with --apply --confirm <digest>. It creates the new unposted chain with the same code the import uses, moves the ЭСФ's link from the marked invoice to the new one, and reads all three back.
  4. The accountant reviews and posts the new chain, and removes the marked documents when she chooses.

The old chain is marked before the new one exists, so there is never a moment with two live chains for one ЭСФ. An interrupted run is finished by running it again: nothing that exists is created twice, and two live pieces of one role are a refusal, never a choice. It replaces one chain per run and only the chain the ЭСФ actually points at. Exit status: 0 done or nothing to do, 1 refused, waiting for the accountant or unconfirmed.

Batches, and which ЭСФ a batch starts from

Default to the current period, newest first. An accountant asking for "the first three" means the three that are in their way today, not the three oldest rows in a register that reaches back years. Sorting the unprocessed set ascending and taking the head is how a request about this year gets answered with documents written into a period that was closed and audited two years ago — which then have to be found and deleted one at a time.

Resolve the selection before you write anything:

  • take the current year unless the user named a period;
  • inside it, sort newest turnover date first, then ЭСФ date and number;
  • state the date range in the same message that proposes the batch — "3 ЭСФ, 12.07.2026–04.08.2026". A wrong period is obvious to an accountant at a glance and completely invisible inside a registration number;
  • treat anything outside the current period as opt-in. Ask before including it, and never let a bare "the first N" reach into a closed year.

Old unprocessed ЭСФ are a question of their own, not a backlog to work through head-first. One still sitting unlinked in a base that has been closing its books normally is far more likely to be abandoned, superseded or corrected elsewhere than work waiting to be done — and posting it silently reopens a period the accountant has already filed.

Then, for the batch itself: exclude anything already linked; set aside old anomalous ЭСФ and probable re-exports; take N unique documents; analyse the whole batch and dry-run it once; create nomenclature with a shared GTIN guard so one product is not created twice across two ЭСФ in the same batch; create and verify each chain separately; post only after a separate confirmation.

Stop conditions

Stop before writing when: the target base or organisation is not established; a duplicate registration or an existing chain is found; any ЭСФ in the selected batch falls outside the current period and the user did not ask for that period; supplier, contract, warehouse, accounts or operation kind are undetermined; the arithmetic does not balance; nomenclature or unit is ambiguous; a catalog record would have to be created without permission; a VAT rate is not matched to the catalog; a goods document is about to be built from a services template or vice versa; or you cannot prove all three links.

A clean dry run followed by a held --apply is not a bug

A first ЭСФ from a supplier with no posted chain of its own, and no contract this base already holds, will show contract-unresolved and/or template-other-supplier warnings. A plain --apply (no --accept-warnings) holds the write for exactly this reason — read the warnings, check the borrowed accounts and VAT profile against what this supplier's documents should use, then re-run with --accept-warnings (or the specific --accept-warning <code> for each). That is not required-unresolved in disguise, and it is not the class of defect this kit's --create-nomenclature flag exists to prevent: a line this run is permitted and about to create a catalog card for is already excused from required-unresolved, identically on a dry run and on --apply, however many distinct nomenclature values this base's posted history otherwise carries (1c-esf-import-and-posting.md §0 #32).

Where knowledge about this goes

This page is the regulation. Do not copy it into workspaces. Writing it into each workspace's knowledge/ folder by hand produces N copies that drift and cannot be corrected together. Link to it instead; /help/1c-incoming-esf-import.md serves the raw markdown.

What does belong in a workspace's knowledge/local/: the confirmed contracts and per-supplier settings of that base, its stable document structure, its local exceptions, the user's decisions about creating catalog records or posting, and the numbers of documents already created. Never company names, GUIDs, БИН/ИИН, base addresses or banking details from one base in another workspace's shared notes.