Welcome | Get started | Dive | Contribute | Topics | Reference | Changes | More
Send and receive Peppol documents¶
With this usage scenario of the lino_xl.lib.peppol
plugin you can send
and receive your invoices and credit notes via the Peppol network.
To activate this scenario, set the supplier_id
plugin setting to the
supplier id you received from your Ibanity hosting provider who
registered you as an Ibanity supplier.
This page contains code snippets (lines starting with >>>
), which are
being tested during our development workflow. The following
snippet initializes the demo project used throughout this page.
>>> from lino_book.projects.cosi1.startup import *
The tests in this document are skipped unless you also have Ibanity credentials installed. See How to set up your credentials for details.
>>> if dd.plugins.peppol.credentials is None:
... pytest.skip('this doctest requires Ibanity credentials')
>>> translation.activate('en')
>>> outbound_model = dd.plugins.peppol.outbound_model
The test snippets in is document write to the database, that’s why we tidy up after previous test runs:
>>> import shutil
>>> from lino.core.gfks import gfk2lookup
>>> def tidy_up():
... for obj in peppol.InboundDocument.objects.filter(voucher_id__isnull=False):
... flt = gfk2lookup(uploads.Upload.owner, obj.voucher)
... uploads.Upload.objects.filter(**flt).delete()
... obj.voucher.delete()
... peppol.InboundDocument.objects.all().delete()
... shutil.rmtree(dd.plugins.peppol.inbox_dir, ignore_errors=True)
... peppol.OutboundDocument.objects.all().delete()
... excerpts.Excerpt.objects.filter(id__gt=6).delete()
... contacts.Partner.objects.exclude(peppol_id='').update(peppol_id='')
We need to write our own tidy_up()
function because
lino.utils.dbhash.check_virgin()
doesn’t tidy up because rows that have
been updated.
>>> tidy_up()
Database models¶
The peppol plugin defines two model mixins that add a fields to
lino_xl.lib.accounting.Journal
and
lino_xl.lib.contacts.Partner
.
- class lino_xl.lib.peppol.PeppolPartner¶
- send_peppol¶
Whether sales invoices and credit notes to this partner should be sent via the Peppol network.
In the demo data this field is checked for some partners (a subset of those with a vat_id).
- class lino_xl.lib.peppol.PeppolJournal¶
- is_outbound¶
Whether vouchers of this journal should be sent via the Peppol network.
Data tables¶
- class lino_xl.lib.peppol.Inbox¶
Shows the Peppol documents that were received and have not yet been processed.
- class lino_xl.lib.peppol.Archive¶
Shows the Peppol documents that were received and have been processed.
- class lino_xl.lib.peppol.Outbox¶
Shows the documents to be sent to the Peppol network.
- class lino_xl.lib.peppol.Sent¶
Shows the documents that have been sent to the Peppol network.
Suppliers management¶
The cosi1 demo sites has no suppliers management, this fictive company has just received a supplier_id from their Lino provider.
>>> ar = rt.login("robin")
>>> dd.plugins.peppol.with_suppliers
False
>>> ar.show(peppol.OnboardingStates)
Traceback (most recent call last):
...
AttributeError: module 'lino_xl.lib.peppol.models' has no attribute 'OnboardingStates'...
>>> ar.show(peppol.Suppliers)
Traceback (most recent call last):
...
AttributeError: module 'lino_xl.lib.peppol.models' has no attribute 'Suppliers'
>>> dd.plugins.peppol.supplier_id
'273c1bdf-6258-4484-b6fb-74363721d51f'
Outbound documents¶
In the beginning our Outbox is empty:
>>> rt.show(peppol.Outbox)
No data to display
Fill the outbox with invoices to send:
>>> with ar.print_logger("DEBUG"):
... rt.models.peppol.collect_outbound(ar)
Collect outbound invoices into outbox
Scan 1 outbound journal(s): ['SLS']
Collect 5 new invoices into outbox
>>> ar.show(peppol.Outbox)
==================== ===================== ================ ============ ================= ==============
Invoice Partner VAT regime Entry date Total excl. VAT VAT
-------------------- --------------------- ---------------- ------------ ----------------- --------------
SLS 1/2014 Bestbank Subject to VAT 07/01/2014 2 999,85 629,97
SLS 3/2014 Bäckerei Ausdemwald Subject to VAT 09/01/2014 679,81 142,76
SLS 4/2014 Bäckerei Mießen Subject to VAT 10/01/2014 280,00 58,80
SLS 5/2014 Bäckerei Schmitz Subject to VAT 11/01/2014 535,00 112,35
SLS 6/2014 Garage Mergelsberg Subject to VAT 07/02/2014 1 110,16 203,87
**Total (5 rows)** **5 604,82** **1 147,75**
==================== ===================== ================ ============ ================= ==============
>>> ses = dd.plugins.peppol.get_ibanity_session(ar)
Send outbound documents:
>>> with ar.print_logger("INFO"):
... peppol.send_outbound(ses)
...
Send outbound documents
weasy2pdf render .../media/cache/weasy2pdf/SLS-2014-3.pdf ('de', {})
weasy2pdf render .../media/cache/weasy2pdf/SLS-2014-4.pdf ('de', {})
weasy2pdf render .../media/cache/weasy2pdf/SLS-2014-5.pdf ('de', {})
weasy2pdf render .../media/cache/weasy2pdf/SLS-2014-6.pdf ('de', {})
>>> rt.show(excerpts.Excerpts, column_names="excerpt_type owner")
========================= ====================
Excerpt Type Controlled by
------------------------- --------------------
Trading invoice `SLS 6/2014 <…>`__
Trading invoice `SLS 5/2014 <…>`__
Trading invoice `SLS 4/2014 <…>`__
Trading invoice `SLS 3/2014 <…>`__
Payment reminder `Bestbank <…>`__
Trading invoice `SLS 1/2014 <…>`__
Payment Order `PMO 1/2014 <…>`__
Journal Entry `PRE 1/2014 <…>`__
Bank Statement `BNK 1/2014 <…>`__
Belgian VAT declaration `VAT 1/2014 <…>`__
========================= ====================
The Outbox table is now empty, and the invoices have moved to the Sent table.
>>> rt.show(peppol.Outbox)
No data to display
>>> rt.show(peppol.Sent)
============ ===================== ============================ ========= =================
Invoice Partner Created at State Transmission ID
------------ --------------------- ---------------------------- --------- -----------------
SLS 1/2014 Bestbank 2019-07-17 07:31:30.763402 Created
SLS 3/2014 Bäckerei Ausdemwald 2019-07-17 07:31:30.763402 Created
SLS 4/2014 Bäckerei Mießen 2019-07-17 07:31:30.763402 Created
SLS 5/2014 Bäckerei Schmitz 2019-07-17 07:31:30.763402 Created
SLS 6/2014 Garage Mergelsberg 2019-07-17 07:31:30.763402 Created
============ ===================== ============================ ========= =================
The status is “created” and they do not yet have any transmission ID. This will change with the next synchronization step:
>>> with ar.print_logger("INFO"):
... peppol.followup_outbound(ses)
...
Check status of sent documents
SLS 1/2014 (ba6c26fa-f47c-4ef1-866b-71e4ef02f15a5) state created becomes sent
SLS 3/2014 (ba6c26fa-f47c-4ef1-866b-71e4ef02f15a5) state created becomes sent
SLS 4/2014 (ba6c26fa-f47c-4ef1-866b-71e4ef02f15a5) state created becomes sent
SLS 5/2014 (ba6c26fa-f47c-4ef1-866b-71e4ef02f15a5) state created becomes sent
SLS 6/2014 (ba6c26fa-f47c-4ef1-866b-71e4ef02f15a5) state created becomes sent
>>> rt.show(peppol.Sent)
============ ===================== ============================ ======= =======================================
Invoice Partner Created at State Transmission ID
------------ --------------------- ---------------------------- ------- ---------------------------------------
SLS 1/2014 Bestbank 2019-07-17 07:31:30.763402 Sent ba6c26fa-f47c-4ef1-866b-71e4ef02f15a5
SLS 3/2014 Bäckerei Ausdemwald 2019-07-17 07:31:30.763402 Sent ba6c26fa-f47c-4ef1-866b-71e4ef02f15a5
SLS 4/2014 Bäckerei Mießen 2019-07-17 07:31:30.763402 Sent ba6c26fa-f47c-4ef1-866b-71e4ef02f15a5
SLS 5/2014 Bäckerei Schmitz 2019-07-17 07:31:30.763402 Sent ba6c26fa-f47c-4ef1-866b-71e4ef02f15a5
SLS 6/2014 Garage Mergelsberg 2019-07-17 07:31:30.763402 Sent ba6c26fa-f47c-4ef1-866b-71e4ef02f15a5
============ ===================== ============================ ======= =======================================
Inbound documents¶
In the beginning our Inbox is empty:
>>> rt.show(peppol.Inbox)
No data to display
Checking our inbox means that we ask whether we have received any new inbound documents.
>>> with ar.print_logger("INFO"):
... peppol.check_inbox(ses)
...
Check for new inbound documents
Receive document 431cb851-5bb2-4526-8149-5655d648292f
New documents are now visible in our database:
>>> rt.show(peppol.Inbox)
============================ ======================================= ========= ========= ========
Created at Transmission ID Invoice Partner Amount
---------------------------- --------------------------------------- --------- --------- --------
... c038dbdc1-26ed-41bf-9ebf-37g3c4ceaa58
============================ ======================================= ========= ========= ========
Now Lino can download the detail of every single document.
>>> with ar.print_logger("INFO"):
... peppol.download_inbound(ses)
...
Download inbound documents
Found 1 inbound documents to download
Created INB 1/2014 from 431cb851-5bb2-4526-8149-5655d648292f
Now the Lino invoice has been created but is not yet registered.
>>> rt.show(peppol.Inbox)
============================ ======================================= ============ =============== ============
Created at Transmission ID Invoice Partner Amount
---------------------------- --------------------------------------- ------------ --------------- ------------
... c038dbdc1-26ed-41bf-9ebf-37g3c4ceaa58 INB 1/2014 Rumma & Ko OÜ 822,57
**Total (1 rows)** **822,57**
============================ ======================================= ============ =============== ============
>>> print(dd.plugins.peppol.inbox_dir)
/.../projects/cosi1/media/ibanity_inbox
>>> for fn in dd.plugins.peppol.inbox_dir.iterdir():
... print(fn)
/.../projects/cosi1/media/ibanity_inbox/431cb851-5bb2-4526-8149-5655d648292f.xml
Choicelists¶
- class lino_xl.lib.peppol.OutboundStates¶
>>> rt.show(peppol.OutboundStates)
======= ============ ============
value name text
------- ------------ ------------
10 created Created
20 sending Sending
30 sent Sent
40 invalid Invalid
50 send_error Send-Error
======= ============ ============
The following choicelist is not needed because we just store the text version in
OutboundDocument.error_message
.
>>> rt.show(peppol.OutboundErrors)
======= ========================= =========================
value name text
------- ------------------------- -------------------------
010 malicious Malicious
020 format Invalid format
030 xsd Invalid XML
040 schematron Invalid Schematron
050 identifiers Invalid identifiers
060 size Invalid size
070 invalid_type Invalid type
080 customer_not_registered Customer not registered
090 unsupported Type not supported
100 access_point Access Point issue
110 unspecified Unspecified error
======= ========================= =========================
At the end of this page we tidy up the database to avoid side effects in following tests:
>>> tidy_up()
>>> dbhash.check_virgin()