Welcome | Get started | Dive | Contribute | Topics | Reference | Changes | More

Invoicing in Lino Voga (lino_voga.lib.invoicing)

This document explains how Lino Voga generates invoices.

General functionality for automatically generating invoices is defined in lino_xl.lib.invoicing.

Side note: Code snippets (lines starting with >>>) in this document get tested as part of our development workflow. The following initialization snippet tells you which demo project is being used in this document.

>>> from lino import startup
>>> startup('lino_book.projects.voga2.settings')
>>> from lino.api.doctest import *

Overview

In Lino Voga, enrolments are the things that generate invoices. This is implemented by extending Enrolment so that it inherits from Invoiceable.

Another invoiceable thing in Lino Voga is when they rent a room to a third-party organisation. This is called a Booking.

IOW, in Lino Voga both Enrolment and Booking are InvoiceGenerator:

>>> pprint(rt.models_by_base(rt.models.invoicing.InvoiceGenerator))
[<class 'lino_voga.lib.roger.courses.models.Enrolment'>,
 <class 'lino_voga.lib.rooms.models.Booking'>,
 <class 'lino_xl.lib.storage.models.Filler'>,
 <class 'lino_xl.lib.trading.models.InvoiceItem'>]

Note that lino_xl.lib.trading.InvoiceItem is inactive as an invoice generator because there are no Follow-up rules in this demo:

User interface

On the user-visible level this plugin adds

  • a menu entry Journals ‣ Generate invoices,

and a StartInvoicing action (with a basket as icon, referring to a shopping basket) at four places:

  • as a menu command Accounting ‣ Generate invoices

  • on every partner (generate invoices for this partner)

  • on every course (generate invoices for all enrolments of this course)

  • on every journal which supports automatic invoice generation.

>>> rt.models.contacts.Partner.start_invoicing_1
<lino_xl.lib.invoicing.actions.StartInvoicingForPartner start_invoicing_1>
>>> rt.models.courses.Course.start_invoicing_1
<lino_xl.lib.invoicing.actions.StartInvoicingForOrder start_invoicing_1>

Enrolments are invoice generators

Enrolment.invoicing_info is a summary of what has been invoiced by a given enrolment.

TODO: This behaviour is implemented differently since 20230710

>>> from textwrap import wrap
>>> for obj in courses.Enrolment.objects.order_by("id"):
...     ii = '\n'.join(wrap(to_rst(obj.invoicing_info), 80))
...     print(u"{} : {} {}\n{}".format(obj.id, obj.course, obj.pupil, ii))
...     
1 : 001 Greece 2014 Hans Altenberg (MEL)
Invoiced : 14.08.
2 : 002 London 2014 Laurent Bastiaensen (ME)
Invoiced : 14.07.
3 : Five Weekends 2015 Laurent Bastiaensen (ME)
No invoiced events
4 : 004 comp (First Steps) Laurent Bastiaensen (ME)
Invoiced : (...) 23.04., 30.04., 07.05.
5 : 007C WWW (Internet for beginners) Ulrike Charlier (ME)
Invoiced : (...) 29.04., 06.05., 13.05. Not invoiced : 12.11., 19.11., 03.12.,
10.12., 17.12., 24.12., 31.12., 07.01., 14.01., 28.01., 04.02., 11.02., 25.02.,
04.03., 11.03., 18.03.
6 : 009C BT (Belly dancing) Ulrike Charlier (ME)
Not invoiced : 02.04., 09.04., 16.04.
7 : 009C BT (Belly dancing) Ulrike Charlier (ME)
Not invoiced : 21.05., 28.05., 04.06., 11.06., 18.06., 02.07., 09.07., 16.07.,
23.07., 30.07., 06.08., 13.08., 27.08., 03.09., 10.09., 17.09., 24.09., 01.10.,
08.10., 22.10., 29.10., 05.11., 12.11., 19.11., 26.11., 03.12., 17.12., 24.12.,
31.12., 07.01., 14.01., 21.01., 28.01., 11.02., 25.02., 04.03., 11.03., 18.03.,
25.03., 01.04., 15.04., 22.04., 29.04., 06.05., 13.05., 20.05.
8 : 010C FG (Functional gymnastics) Ulrike Charlier (ME)
Invoiced : (...) 04.05., 11.05., 18.05. Not invoiced : 06.10., 20.10., 27.10.,
03.11., 10.11., 17.11., 24.11., 01.12., 15.12., 22.12., 29.12., 05.01., 12.01.,
19.01., 26.01., 09.02., 23.02., 02.03., 09.03., 16.03.
9 : 011C FG (Functional gymnastics) Ulrike Charlier (ME)
Invoiced : (...) 04.05., 11.05., 18.05. Not invoiced : 06.10., 13.10., 27.10.,
03.11., 10.11., 17.11., 24.11., 01.12., 08.12., 22.12., 29.12., 05.01., 12.01.,
19.01., 26.01., 02.02., 23.02., 02.03., 09.03., 16.03.
10 : 012 Rücken (Swimming) Ulrike Charlier (ME)
No invoiced events
11 : 013 Rücken (Swimming) Daniel Dericum (ME)
No invoiced events
12 : 018 SV (Self-defence) Dorothée Demeulenaere (ME)
Invoiced : 20.03., 10.04., 17.04.
13 : 019 SV (Self-defence) Dorothée Demeulenaere (ME)
Invoiced : 06.03., 13.03., 20.03.
14 : 019 SV (Self-defence) Dorothée Demeulenaere (ME)
No invoiced events
15 : 020C GLQ (GuoLin-Qigong) Dorothée Dobbelstein-Demeulenaere (ME)
Invoiced : (...) 27.04., 04.05., 18.05. Not invoiced : 28.07., 04.08., 11.08.,
18.08., 25.08., 01.09., 08.09., 22.09., 29.09., 06.10., 13.10., 20.10., 27.10.,
03.11., 17.11., 24.11., 01.12., 08.12., 15.12., 22.12., 29.12., 12.01., 19.01.,
26.01., 02.02., 09.02., 23.02., 02.03., 16.03., 23.03., 30.03., 13.04.
16 : 021C GLQ (GuoLin-Qigong) Dorothée Dobbelstein-Demeulenaere (ME)
Not invoiced : 18.07., 25.07., 01.08., 08.08., 22.08., 29.08., 12.09., 19.09.,
26.09., 03.10., 10.10., 17.10., 24.10., 14.11., 21.11., 28.11., 05.12., 12.12.,
19.12., 26.12., 09.01., 16.01., 23.01., 30.01., 06.02., 13.02., 20.02., 24.04.,
08.05., 15.05.
17 : 005 comp (First Steps) Dorothée Dobbelstein-Demeulenaere (ME)
Invoiced : 28.03., 04.04., 11.04.
18 : 008C WWW (Internet for beginners) Eberhart Evers (ME)
Not invoiced : 24.10., 07.11., 14.11., 21.11., 28.11., 05.12., 12.12., 26.12.,
02.01., 09.01., 16.01., 23.01., 30.01., 06.02., 20.02., 27.02., 06.03., 13.03.,
20.03., 27.03., 10.04., 24.04., 08.05., 15.05.
19 : 016 Rücken (Swimming) Daniel Emonts (MES)
No invoiced events
20 : 017 Rücken (Swimming) Daniel Emonts (MES)
No invoiced events
21 : 017 Rücken (Swimming) Daniel Emonts (MES)
No invoiced events
22 : 003 comp (First Steps) Edgar Engels (ME)
Invoiced : (...) 05.05., 12.05., 19.05.
23 : 006C WWW (Internet for beginners) Edgar Engels (ME)
Invoiced : (...) 04.05., 11.05., 18.05. Not invoiced : 03.11., 10.11., 17.11.,
24.11., 01.12., 08.12., 15.12., 29.12., 05.01., 12.01., 19.01., 26.01., 02.02.,
09.02., 02.03., 09.03.
24 : 022C MED (Finding your inner peace) Edgar Engels (ME)
Not invoiced : 23.09., 30.09., 07.10., 14.10.
25 : 023C MED (Finding your inner peace) Luc Faymonville (MEL)
Not invoiced : 06.02., 13.02., 20.02., 27.02., 13.03., 20.03., 27.03., 10.04.,
17.04., 24.04., 08.05.
26 : 024C Yoga Luc Faymonville (MEL)
Invoiced : 04.05., 11.05. Not invoiced : 23.03., 30.03., 13.04., 20.04., 27.04.
27 : 025C Yoga Luc Faymonville (MEL)
Not invoiced : 08.11., 15.11., 22.11., 29.11.
28 : 025C Yoga Luc Faymonville (MEL)
Not invoiced : 03.01., 10.01., 17.01., 24.01., 31.01., 07.02., 14.02., 28.02.,
07.03., 14.03., 21.03., 28.03., 04.04., 11.04., 02.05., 09.05., 16.05., 23.05.,
30.05., 06.06., 13.06., 27.06., 04.07., 11.07., 18.07., 25.07., 01.08., 08.08.,
29.08., 05.09., 12.09., 19.09., 26.09., 03.10., 10.10., 24.10., 07.11., 14.11.,
21.11., 28.11., 05.12., 12.12., 26.12., 02.01., 09.01., 16.01., 23.01., 30.01.
29 : 014 Rücken (Swimming) Luc Faymonville (MEL)
No invoiced events
30 : 015 Rücken (Swimming) Hildegard Hilgers (ME)
No invoiced events
31 : 001 Greece 2014 Jacqueline Jacobs (ME)
Invoiced : 14.08.
32 : 002 London 2014 Jacqueline Jacobs (ME)
Invoiced : 14.07.
33 : Five Weekends 2015 Jacqueline Jacobs (ME)
No invoiced events
34 : 004 comp (First Steps) Jacqueline Jacobs (ME)
Invoiced : 26.03., 02.04.
35 : 004 comp (First Steps) Jacqueline Jacobs (ME)
Invoiced : 07.05.
36 : 007C WWW (Internet for beginners) Jacqueline Jacobs (ME)
Invoiced : 13.05. Not invoiced : 29.10., 05.11., 12.11., 19.11., 03.12., 10.12.,
17.12., 24.12., 31.12., 07.01., 14.01., 28.01., 04.02., 11.02., 25.02., 04.03.,
11.03., 18.03., 01.04., 08.04., 15.04., 22.04., 29.04., 06.05.
37 : 009C BT (Belly dancing) Josef Jonas (ME)
Invoiced : (...) 06.05., 13.05., 20.05. Not invoiced : 02.04., 09.04., 16.04.,
23.04., 07.05., 14.05., 21.05., 28.05., 04.06., 11.06., 18.06., 02.07., 09.07.,
16.07., 23.07., 30.07., 06.08., 13.08., 27.08., 03.09., 10.09., 17.09., 24.09.,
01.10., 08.10., 22.10., 29.10., 05.11., 12.11., 19.11., 26.11., 03.12., 17.12.,
24.12., 31.12., 07.01., 14.01., 21.01., 28.01., 11.02., 25.02., 04.03., 11.03.,
18.03., 25.03., 01.04., 15.04., 22.04.
38 : 010C FG (Functional gymnastics) Karl Kaivers (ME)
Not invoiced : 06.10., 20.10., 27.10., 03.11.
39 : 011C FG (Functional gymnastics) Karl Kaivers (ME)
Invoiced : (...) 04.05., 11.05., 18.05. Not invoiced : 06.10., 13.10., 27.10.,
03.11., 10.11., 17.11., 24.11., 01.12., 08.12., 22.12., 29.12., 05.01., 12.01.,
19.01., 26.01., 02.02., 23.02., 02.03., 09.03., 16.03.
40 : 012 Rücken (Swimming) Karl Kaivers (ME)
No invoiced events
41 : 013 Rücken (Swimming) Laura Laschet (ME)
No invoiced events
42 : 013 Rücken (Swimming) Laura Laschet (ME)
No invoiced events
43 : 018 SV (Self-defence) Laura Laschet (ME)
Invoiced : (...) 20.03., 10.04., 17.04.
44 : 019 SV (Self-defence) Laura Laschet (ME)
Invoiced : (...) 20.03., 27.03., 10.04.
45 : 020C GLQ (GuoLin-Qigong) Laura Laschet (ME)
Not invoiced : 28.07., 04.08., 11.08., 18.08.
46 : 021C GLQ (GuoLin-Qigong) Laura Laschet (ME)
Not invoiced : 18.07., 25.07., 01.08., 08.08., 22.08., 29.08., 12.09., 19.09.,
26.09., 03.10., 10.10., 17.10., 24.10., 14.11., 21.11., 28.11., 05.12., 12.12.,
19.12., 26.12., 09.01., 16.01., 23.01., 30.01., 06.02., 13.02., 20.02., 24.04.,
08.05., 15.05.
47 : 005 comp (First Steps) Josefine Leffin (MEL)
Invoiced : (...) 02.05., 09.05., 16.05.
48 : 008C WWW (Internet for beginners) Marie-Louise Meier (ME)
Not invoiced : 24.10., 07.11., 14.11.
49 : 008C WWW (Internet for beginners) Marie-Louise Meier (ME)
Invoiced : 08.05., 15.05. Not invoiced : 12.12., 26.12., 02.01., 09.01., 16.01.,
23.01., 30.01., 06.02., 20.02., 27.02., 06.03., 13.03., 20.03., 27.03., 10.04.,
24.04.
50 : 016 Rücken (Swimming) Marie-Louise Meier (ME)
No invoiced events
51 : 017 Rücken (Swimming) Marie-Louise Meier (ME)
No invoiced events
52 : 003 comp (First Steps) Marie-Louise Meier (ME)
Invoiced : 31.03., 07.04., 14.04.
53 : 006C WWW (Internet for beginners) Marie-Louise Meier (ME)
Invoiced : (...) 04.05., 11.05., 18.05. Not invoiced : 03.11., 10.11., 17.11.,
24.11., 01.12., 08.12., 15.12., 29.12., 05.01., 12.01., 19.01., 26.01., 02.02.,
09.02., 02.03., 09.03.
54 : 022C MED (Finding your inner peace) Erna Emonts-Gast (MS)
Not invoiced : 07.10., 14.10., 28.10., 04.11., 18.11., 25.11., 02.12., 09.12.,
16.12., 30.12., 06.01., 13.01., 20.01., 27.01., 03.02., 10.02., 24.02., 10.03.,
17.03., 24.03., 31.03., 07.04., 14.04., 05.05., 12.05., 19.05., 26.05., 02.06.,
16.06., 23.06., 07.07., 14.07., 28.07., 04.08., 11.08., 18.08., 25.08., 08.09.,
15.09., 22.09., 29.09., 06.10., 13.10., 20.10., 03.11., 10.11., 17.11., 24.11.,
01.12., 08.12., 15.12., 29.12., 05.01., 12.01., 19.01., 26.01., 02.02., 09.02.,
02.03., 09.03., 16.03.
55 : 023C MED (Finding your inner peace) Erna Emonts-Gast (MS)
Not invoiced : 06.02., 13.02., 20.02.
56 : 023C MED (Finding your inner peace) Erna Emonts-Gast (MS)
Not invoiced : 27.03., 10.04., 17.04., 24.04., 08.05.
57 : 024C Yoga Alfons Radermacher (ME)
Invoiced : 04.05., 11.05. Not invoiced : 23.03., 30.03., 13.04., 20.04., 27.04.
58 : 025C Yoga Alfons Radermacher (ME)
Not invoiced : 08.11., 15.11., 22.11., 29.11., 06.12., 13.12., 20.12., 03.01.,
10.01., 17.01., 24.01., 31.01., 07.02., 14.02., 28.02., 07.03., 14.03., 21.03.,
28.03., 04.04., 11.04., 02.05., 09.05., 16.05., 23.05., 30.05., 06.06., 13.06.,
27.06., 04.07., 11.07., 18.07., 25.07., 01.08., 08.08., 29.08., 05.09., 12.09.,
19.09., 26.09., 03.10., 10.10., 24.10., 07.11., 14.11., 21.11., 28.11., 05.12.,
12.12., 26.12., 02.01., 09.01., 16.01., 23.01., 30.01.
59 : 014 Rücken (Swimming) Alfons Radermacher (ME)
No invoiced events
60 : 015 Rücken (Swimming) Christian Radermacher (MEL)
No invoiced events
61 : 001 Greece 2014 Christian Radermacher (MEL)
No invoiced events
62 : 002 London 2014 Christian Radermacher (MEL)
Invoiced : 14.07.
63 : 002 London 2014 Christian Radermacher (MEL)
No invoiced events
64 : Five Weekends 2015 Christian Radermacher (MEL)
No invoiced events
65 : 004 comp (First Steps) Christian Radermacher (MEL)
Invoiced : (...) 23.04., 30.04., 07.05.
66 : 007C WWW (Internet for beginners) Edgard Radermacher (ME)
Not invoiced : 29.10., 05.11., 12.11., 19.11.
67 : 009C BT (Belly dancing) Guido Radermacher (ME)
Invoiced : (...) 06.05., 13.05., 20.05. Not invoiced : 02.04., 09.04., 16.04.,
23.04., 07.05., 14.05., 21.05., 28.05., 04.06., 11.06., 18.06., 02.07., 09.07.,
16.07., 23.07., 30.07., 06.08., 13.08., 27.08., 03.09., 10.09., 17.09., 24.09.,
01.10., 08.10., 22.10., 29.10., 05.11., 12.11., 19.11., 26.11., 03.12., 17.12.,
24.12., 31.12., 07.01., 14.01., 21.01., 28.01., 11.02., 25.02., 04.03., 11.03.,
18.03., 25.03., 01.04., 15.04., 22.04.
68 : 010C FG (Functional gymnastics) Guido Radermacher (ME)
Invoiced : (...) 04.05., 11.05., 18.05. Not invoiced : 20.10., 27.10., 03.11.,
10.11., 17.11., 24.11., 01.12., 15.12., 22.12., 29.12., 05.01., 12.01., 19.01.,
26.01., 09.02., 23.02., 02.03., 09.03., 16.03., 23.03.
69 : 011C FG (Functional gymnastics) Guido Radermacher (ME)
Not invoiced : 06.10., 13.10.
70 : 011C FG (Functional gymnastics) Guido Radermacher (ME)
Invoiced : (...) 04.05., 11.05., 18.05. Not invoiced : 24.11., 01.12., 08.12.,
22.12., 29.12., 05.01., 12.01., 19.01., 26.01., 02.02., 23.02., 02.03., 09.03.,
16.03., 23.03.
71 : 012 Rücken (Swimming) Hedi Radermacher (ME)
No invoiced events
72 : 013 Rücken (Swimming) Hedi Radermacher (ME)
No invoiced events
73 : 018 SV (Self-defence) Hedi Radermacher (ME)
Invoiced : 06.03., 13.03., 20.03.
74 : 019 SV (Self-defence) Hedi Radermacher (ME)
Invoiced : (...) 20.03., 27.03., 10.04.
75 : 020C GLQ (GuoLin-Qigong) Hedi Radermacher (ME)
Invoiced : 27.04., 04.05., 18.05. Not invoiced : 04.08., 11.08., 18.08., 25.08.,
01.09., 08.09., 22.09., 29.09., 06.10., 13.10., 20.10., 27.10., 03.11., 17.11.,
24.11., 01.12., 08.12., 15.12., 22.12., 29.12., 12.01., 19.01., 26.01., 02.02.,
09.02., 23.02., 02.03., 16.03., 23.03., 30.03., 13.04., 20.04.
76 : 021C GLQ (GuoLin-Qigong) Jean Radermacher (ME)
Not invoiced : 18.07., 25.07., 01.08.
77 : 021C GLQ (GuoLin-Qigong) Jean Radermacher (ME)
Not invoiced : 12.09., 19.09., 26.09., 03.10., 10.10., 17.10., 24.10., 14.11.,
21.11., 28.11., 05.12., 12.12., 19.12., 26.12., 09.01., 16.01., 23.01., 30.01.,
06.02., 13.02., 20.02., 24.04., 08.05., 15.05.
78 : 005 comp (First Steps) Didier di Rupo (MS)
Invoiced : (...) 02.05., 09.05., 16.05.
79 : 008C WWW (Internet for beginners) Didier di Rupo (MS)
Not invoiced : 24.10., 07.11., 14.11., 21.11., 28.11., 05.12., 12.12., 26.12.,
02.01., 09.01., 16.01., 23.01., 30.01., 06.02., 20.02., 27.02., 06.03., 13.03.,
20.03., 27.03., 10.04., 24.04., 08.05., 15.05.
80 : 016 Rücken (Swimming) Erna Ärgerlich (ME)
No invoiced events
81 : 017 Rücken (Swimming) Jean Dupont (ME)
No invoiced events
82 : 003 comp (First Steps) Jean Dupont (ME)
Invoiced : (...) 05.05., 12.05., 19.05.
83 : 006C WWW (Internet for beginners) Jean Dupont (ME)
Not invoiced : 03.11., 10.11.
84 : 006C WWW (Internet for beginners) Jean Dupont (ME)
Invoiced : (...) 04.05., 11.05., 18.05. Not invoiced : 15.12., 29.12., 05.01.,
12.01., 19.01., 26.01., 02.02., 09.02., 02.03., 09.03.
85 : 022C MED (Finding your inner peace) Mark Martelaer (MS)
Not invoiced : 23.09., 30.09., 07.10., 14.10., 28.10., 04.11., 18.11., 25.11.,
02.12., 09.12., 16.12., 30.12., 06.01., 13.01., 20.01., 27.01., 03.02., 10.02.,
24.02., 10.03., 17.03., 24.03., 31.03., 07.04., 14.04., 05.05., 12.05., 19.05.,
26.05., 02.06., 16.06., 23.06., 07.07., 14.07., 28.07., 04.08., 11.08., 18.08.,
25.08., 08.09., 15.09., 22.09., 29.09., 06.10., 13.10., 20.10., 03.11., 10.11.,
17.11., 24.11., 01.12., 08.12., 15.12., 29.12., 05.01., 12.01., 19.01., 26.01.,
02.02., 09.02., 02.03., 09.03., 16.03.
86 : 023C MED (Finding your inner peace) Mark Martelaer (MS)
Not invoiced : 06.02., 13.02., 20.02., 27.02., 13.03., 20.03., 27.03., 10.04.,
17.04., 24.04., 08.05.
87 : 024C Yoga Mark Martelaer (MS)
No invoiced events
88 : 025C Yoga Mark Martelaer (MS)
Not invoiced : 08.11., 15.11., 22.11., 29.11., 06.12., 13.12., 20.12., 03.01.,
10.01., 17.01., 24.01., 31.01., 07.02., 14.02., 28.02., 07.03., 14.03., 21.03.,
28.03., 04.04., 11.04., 02.05., 09.05., 16.05., 23.05., 30.05., 06.06., 13.06.,
27.06., 04.07., 11.07., 18.07., 25.07., 01.08., 08.08., 29.08., 05.09., 12.09.,
19.09., 26.09., 03.10., 10.10., 24.10., 07.11., 14.11., 21.11., 28.11., 05.12.,
12.12., 26.12., 02.01., 09.01., 16.01., 23.01., 30.01.
89 : 014 Rücken (Swimming) Lisa Lahm (MS)
No invoiced events
90 : 015 Rücken (Swimming) Lisa Lahm (MS)
No invoiced events
91 : 015 Rücken (Swimming) Lisa Lahm (MS)
No invoiced events
92 : 001 Greece 2014 Bernd Brecht (ME)
Invoiced : 14.08.
93 : 002 London 2014 Bernd Brecht (ME)
Invoiced : 14.07.
94 : Five Weekends 2015 Bernd Brecht (ME)
No invoiced events
95 : 004 comp (First Steps) Jérôme Jeanémart (ME)
Invoiced : (...) 23.04., 30.04., 07.05.

Here is a list of all enrolments:

>>> rt.show(rt.models.courses.Enrolments)
...     
================= ===================================== ========= ======================================== =============== =================
 Date of request   Activity                              State     Participant                              Workflow        Author
----------------- ------------------------------------- --------- ---------------------------------------- --------------- -----------------
 30/08/2013        022C MED (Finding your inner peace)   Started   Mark Martelaer (MS)                      **Confirmed**   Tom Thess
 14/09/2013        022C MED (Finding your inner peace)   Started   Edgar Engels (ME)                        **Confirmed**   Monique Mommer
 04/10/2013        022C MED (Finding your inner peace)   Started   Erna Emonts-Gast (MS)                    **Confirmed**   Rolf Rompen
 19/10/2013        024C Yoga                             Started   Alfons Radermacher (ME)                  **Confirmed**   Tom Thess
 03/11/2013        025C Yoga                             Started   Alfons Radermacher (ME)                  **Requested**   Marianne Martin
 03/11/2013        024C Yoga                             Started   Mark Martelaer (MS)                      **Confirmed**   Monique Mommer
 08/11/2013        025C Yoga                             Started   Luc Faymonville (MEL)                    **Confirmed**   Robin Rood
 08/11/2013        025C Yoga                             Started   Luc Faymonville (MEL)                    **Confirmed**   Robin Rood
 08/11/2013        025C Yoga                             Started   Mark Martelaer (MS)                      **Confirmed**   Romain Raffault
 23/11/2013        024C Yoga                             Started   Luc Faymonville (MEL)                    **Confirmed**   Rolf Rompen
 26/02/2014        003 comp (First Steps)                Started   Edgar Engels (ME)                        **Confirmed**   Tom Thess
 26/02/2014        005 comp (First Steps)                Started   Didier di Rupo (MS)                      **Confirmed**   Tom Thess
 ...
 11/07/2015        017 Rücken (Swimming)                 Started   Daniel Emonts (MES)                      **Confirmed**   Robin Rood
 11/07/2015        017 Rücken (Swimming)                 Started   Daniel Emonts (MES)                      **Confirmed**   Robin Rood
 11/07/2015        013 Rücken (Swimming)                 Started   Laura Laschet (ME)                       **Confirmed**   Robin Rood
 11/07/2015        013 Rücken (Swimming)                 Started   Laura Laschet (ME)                       **Confirmed**   Robin Rood
 11/07/2015        017 Rücken (Swimming)                 Started   Jean Dupont (ME)                         **Requested**   Romain Raffault
 26/07/2015        016 Rücken (Swimming)                 Started   Daniel Emonts (MES)                      **Confirmed**   Rolf Rompen
 26/07/2015        012 Rücken (Swimming)                 Started   Karl Kaivers (ME)                        **Confirmed**   Rolf Rompen
 26/07/2015        014 Rücken (Swimming)                 Started   Lisa Lahm (MS)                           **Confirmed**   Rolf Rompen
================= ===================================== ========= ======================================== =============== =================

Invoicings

The lino_xl.lib.invoicing.InvoicingsByGenerator panel in the detail window of an enrolment shows all invoicings of that enrolment:

>>> obj = courses.Enrolment.objects.get(pk=67)
>>> rt.show('invoicing.InvoicingsByGenerator', obj)
... 
==================== ================================================== ========== ============== =============== ==================
 Sales invoice        Heading                                            Quantity   Voucher date   Voucher state   Number of events
-------------------- -------------------------------------------------- ---------- -------------- --------------- ------------------
 SLS 20/2014          [1] Enrolment to 009C BT (Belly dancing)           1          01/01/2014     Registered      12
 SLS 44/2014          [2] Renewal Enrolment to 009C BT (Belly dancing)   1          01/07/2014     Registered      12
 SLS 56/2014          [3] Renewal Enrolment to 009C BT (Belly dancing)   1          01/10/2014     Registered      12
 SLS 9/2015           [4] Renewal Enrolment to 009C BT (Belly dancing)   1          01/01/2015     Registered      12
 **Total (4 rows)**                                                      **4**                                     **48**
==================== ================================================== ========== ============== =============== ==================

Subscription courses

Subscription courses are courses for which the customer pays a given number of events, not simply all events of that course. This means that the presences for these courses must have been entered.

A subscription course does not end and start at a given date, the course itself is continously being given. Participants can start on any time of the year. They usually pay for 12 sessions in advance (the first invoice for that enrolment), and Lino must write a new invoice every 12 weeks.

Descriptions

The items of automatically generated invoices have a description field whose context is defined by the courses/Enrolment/item_description.html template and can be complex and application specific.

See the config/courses/Enrolment/item_description.html file in lino_voga.lib.voga.

Scheduled dates

For enrolments in non-continuous courses (i.e. with a fee having an empty number_of_events), the description on the invoice includes a list of “Scheduled dates”. This is basically an enumeration of the planned events of that course.

It can happen that a participant joins a started course afterwards and pays less, in function of the events he didn’t attend. The amount to be invoiced in such cases is subject to individual discussion, and the user simply enters that amount in the enrolment.

The following code snippets test whether above is true.

There are 12 enrolments in non-continuous courses:

>>> Enrolment = rt.models.courses.Enrolment
>>> EnrolmentStates = rt.models.courses.EnrolmentStates
>>> qs = Enrolment.objects.filter(start_date__isnull=False)
>>> qs = qs.filter(state=EnrolmentStates.confirmed)
>>> qs = qs.filter(fee__tariff__number_of_events__isnull=True)
>>> qs = qs.order_by('request_date')
>>> qs.count() 
12

We want only those for which an invoice has been generated.

>>> enrolment_ids = []
>>> for obj in qs:
...     if obj.get_invoicings().count() > 0:
...         enrolment_ids.append(obj.pk)
>>> enrolment_ids 
[47, 82, 61, 14, 12, 21, 42, 19, 40, 89]

Let’s select the corresponding invoice items:

>>> InvoiceItem = rt.models.trading.InvoiceItem
>>> qs2 = InvoiceItem.objects.filter(
...     invoiceable_id__in=enrolment_ids)
>>> qs2.count() 
10

Now we define a utility function that prints out what we want to see for each of these items:

>>> def fmt(obj):
...     enr = obj.invoiceable
...     # avoid initdb_demo after change in item_description.html:
...     enr.setup_invoice_item(obj)
...     print(u"--- Invoice #{0} for enrolment #{1} ({2}):".format(
...         obj.voucher.number, enr.id, enr))
...     print(u"Title: {0}".format(obj.title))
...     print("Start date: {}".format(dd.fds(obj.invoiceable.start_date)).strip())
...     if enr.start_date:
...       missed_events = enr.course.events_by_course().filter(
...         start_date__lte=enr.start_date)
...       # if missed_events.count() == 0: return
...       missed_events = ', '.join([dd.fds(o.start_date) for o in missed_events])
...       print("Missed events: {0}".format(missed_events).strip())
...     print("Description:")
...     print(noblanklines(obj.description))

And run it:

>>> for o in qs2: fmt(o)
... 
--- Invoice #5 for enrolment #12 (018 SV (Self-defence) / Dorothée Demeulenaere (ME)):
Title: Enrolment to 018 SV (Self-defence)
Start date: 18/03/2015
Missed events: 06/03/2015, 13/03/2015
Description:
Time: Every Friday 18:00-19:00.
Fee: 20€.
Scheduled dates:
20/03/2015, 27/03/2015, 10/04/2015, 17/04/2015,
--- Invoice #5 for enrolment #14 (019 SV (Self-defence) / Dorothée Demeulenaere (ME)):
Title: Enrolment to 019 SV (Self-defence)
Start date: 21/04/2015
Missed events: 06/03/2015, 13/03/2015, 20/03/2015, 27/03/2015, 10/04/2015, 17/04/2015
Description:
Time: Every Friday 19:00-20:00.
Fee: 20€.
Scheduled dates:
--- Invoice #8 for enrolment #19 (016 Rücken (Swimming) / Daniel Emonts (MES)):
Title: Enrolment to 016 Rücken (Swimming)
Start date: 26/07/2015
Missed events: 16/07/2015, 23/07/2015
Description:
Time: Every Thursday 11:00-12:00.
Fee: 80€.
Scheduled dates:
30/07/2015, 06/08/2015, 13/08/2015, 20/08/2015, 27/08/2015, 03/09/2015, 10/09/2015, 17/09/2015,
--- Invoice #8 for enrolment #21 (017 Rücken (Swimming) / Daniel Emonts (MES)):
Title: Enrolment to 017 Rücken (Swimming)
Start date: 29/08/2015
Missed events: 16/07/2015, 23/07/2015, 30/07/2015, 06/08/2015, 13/08/2015, 20/08/2015, 27/08/2015
Description:
Time: Every Thursday 13:30-14:30.
Fee: 80€.
Scheduled dates:
03/09/2015, 10/09/2015, 17/09/2015,
--- Invoice #13 for enrolment #40 (012 Rücken (Swimming) / Karl Kaivers (ME)):
Title: Enrolment to 012 Rücken (Swimming)
Start date: 26/07/2015
Missed events:
Description:
Time: Every Monday 11:00-12:00.
Fee: 80€.
Scheduled dates:
21/03/2016, 04/04/2016, 11/04/2016, 18/04/2016, 25/04/2016, 02/05/2016, 09/05/2016, 23/05/2016, 30/05/2016, 06/06/2016,
--- Invoice #14 for enrolment #42 (013 Rücken (Swimming) / Laura Laschet (ME)):
Title: Enrolment to 013 Rücken (Swimming)
Start date: 29/08/2015
Missed events:
Description:
Time: Every Monday 13:30-14:30.
Fee: 80€.
Scheduled dates:
21/03/2016, 04/04/2016, 11/04/2016, 18/04/2016, 25/04/2016, 02/05/2016, 09/05/2016, 23/05/2016, 30/05/2016, 06/06/2016,
--- Invoice #15 for enrolment #47 (005 comp (First Steps) / Josefine Leffin (MEL)):
Title: Enrolment to 005 comp (First Steps)
Start date: 02/04/2014
Missed events: 21/03/2014, 28/03/2014
Description:
Time: Every Friday 13:30-15:00.
Fee: 20€.
Scheduled dates:
04/04/2014, 11/04/2014, 25/04/2014, 02/05/2014, 09/05/2014, 16/05/2014,
--- Invoice #18 for enrolment #61 (001 Greece 2014 / Christian Radermacher (MEL)):
Title: Enrolment to 001 Greece 2014
Start date: 29/08/2014
Missed events: 14/08/2014
Description:
Date: 14/08/2014-20/08/2014.
Fee: Journeys.
--- Invoice #25 for enrolment #82 (003 comp (First Steps) / Jean Dupont (ME)):
Title: Enrolment to 003 comp (First Steps)
Start date: 02/04/2014
Missed events: 24/03/2014, 31/03/2014
Description:
Time: Every Monday 13:30-15:00.
Fee: 20€.
Scheduled dates:
07/04/2014, 14/04/2014, 28/04/2014, 05/05/2014, 12/05/2014, 19/05/2014,
--- Invoice #26 for enrolment #89 (014 Rücken (Swimming) / Lisa Lahm (MS)):
Title: Enrolment to 014 Rücken (Swimming)
Start date: 26/07/2015
Missed events: 14/07/2015
Description:
Time: Every Tuesday 11:00-12:00.
Fee: 80€.
Scheduled dates:
28/07/2015, 04/08/2015, 11/08/2015, 18/08/2015, 25/08/2015, 01/09/2015, 08/09/2015, 15/09/2015, 22/09/2015,

Let’s have a closer look at one of above invoicings.

>>> enr = rt.models.courses.Enrolment.objects.get(pk=82)

These are the scheduled events for the course:

>>> qs = enr.course.events_by_course().order_by('start_date')
>>> print(', '.join([dd.fds(e.start_date) for e in qs]))
24/03/2014, 31/03/2014, 07/04/2014, 14/04/2014, 28/04/2014, 05/05/2014, 12/05/2014, 19/05/2014

But our enrolment starts later:

>>> print(dd.fds(enr.start_date))
02/04/2014
>>> enr.end_date

So it missed the first three events and covers only the following events:

>>> qs = rt.models.system.PeriodEvents.started.add_filter(qs, enr)
>>> print(', '.join([dd.fds(e.start_date) for e in qs]))
07/04/2014, 14/04/2014, 28/04/2014, 05/05/2014, 12/05/2014, 19/05/2014

Invoicing plan

The demo database contains exactly one invoicing plan, which still holds information about the last invoicing run.

>>> rt.show('invoicing.Plans')  
==== ============ ============ =============================================== =================== ============ ========= ==========
 ID   Author       Today        Invoicing task                                  Invoiceables from   until        Partner   Activity
---- ------------ ------------ ----------------------------------------------- ------------------- ------------ --------- ----------
 1    Robin Rood   01/03/2015   Invoicing task #1 (Make Sales invoices (SLS))   01/02/2015          28/02/2015
==== ============ ============ =============================================== =================== ============ ========= ==========
>>> obj = rt.models.invoicing.Plan.objects.first()
>>> rt.show('invoicing.ItemsByPlan', obj)  
+--------------------+-------------------+------------------------------------------------------------------------+------------+---------------+
| Selected           | Partner           | Preview                                                                | Amount     | Invoice       |
+====================+===================+========================================================================+============+===============+
| Yes                | Evers Eberhart    | [3] Renewal Enrolment to 008C WWW (Internet for beginners) (48.00 €)   | 48,00      | *SLS 22/2015* |
+--------------------+-------------------+------------------------------------------------------------------------+------------+---------------+
| Yes                | Jacobs Jacqueline | [3] Renewal Enrolment to 007C WWW (Internet for beginners) (48.00 €)   | 48,00      | *SLS 23/2015* |
+--------------------+-------------------+------------------------------------------------------------------------+------------+---------------+
| Yes                | Emonts-Gast Erna  | [6] Renewal Enrolment to 022C MED (Finding your inner peace) (64.00 €) | 64,00      | *SLS 24/2015* |
+--------------------+-------------------+------------------------------------------------------------------------+------------+---------------+
| Yes                | Radermacher Guido | [4] Renewal Enrolment to 010C FG (Functional gymnastics) (50.00 €)<br> | 100,00     | *SLS 25/2015* |
|                    |                   | [3] Renewal Enrolment to 011C FG (Functional gymnastics) (50.00 €)     |            |               |
+--------------------+-------------------+------------------------------------------------------------------------+------------+---------------+
| Yes                | di Rupo Didier    | [3] Renewal Enrolment to 008C WWW (Internet for beginners) (48.00 €)   | 48,00      | *SLS 26/2015* |
+--------------------+-------------------+------------------------------------------------------------------------+------------+---------------+
| **Total (5 rows)** |                   |                                                                        | **308,00** |               |
+--------------------+-------------------+------------------------------------------------------------------------+------------+---------------+

Item descriptions

The template courses/Enrolment/item_description.html defines the text to use as the description of an invoice item when generating invoices.

Here is an overview of the different cases of item descriptions.

>>> qs = InvoiceItem.objects.filter(invoiceable_id__isnull=False)
>>> qs.count()  
174
>>> cases = set()
>>> for i in qs:
...     e = i.invoiceable
...     k = (e.places == 1, e.start_date is None,
...         e.course.start_time is None,
...         e.start_date is None,
...         e.option_id is None,
...         e.fee.tariff is None or e.fee.tariff.number_of_events is None,
...         e.course.every_unit)
...     if k in cases: continue
...     print("=== {} ===".format(k))
...     fmt(i)
...     cases.add(k)
...  
=== (True, True, True, True, True, True, <system.Recurrences.once:O>) ===
--- Invoice #1 for enrolment #1 (001 Greece 2014 / Hans Altenberg (MEL)):
Title: Enrolment to 001 Greece 2014
Start date:
Description:
Date: 14/08/2014-20/08/2014.
Fee: Journeys.
=== (True, True, False, True, True, True, <system.Recurrences.weekly:W>) ===
--- Invoice #2 for enrolment #4 (004 comp (First Steps) / Laurent Bastiaensen (ME)):
Title: Enrolment to 004 comp (First Steps)
Start date:
Description:
Time: Every Wednesday 17:30-19:00.
Fee: 20€.
Scheduled dates:
19/03/2014, 26/03/2014, 02/04/2014, 09/04/2014, 16/04/2014, 23/04/2014, 30/04/2014, 07/05/2014,
=== (True, False, False, False, True, False, <system.Recurrences.weekly:W>) ===
--- Invoice #3 for enrolment #5 (007C WWW (Internet for beginners) / Ulrike Charlier (ME)):
Title: [1] Enrolment to 007C WWW (Internet for beginners)
Start date: 08/11/2014
Missed events: 29/10/2014, 05/11/2014
Description:
Time: Every Wednesday 17:30-19:00.
Fee: 48€/8 hours.
Your start date: 08/11/2014.
=== (True, True, False, True, True, False, <system.Recurrences.weekly:W>) ===
--- Invoice #3 for enrolment #6 (009C BT (Belly dancing) / Ulrike Charlier (ME)):
Title: [1] Enrolment to 009C BT (Belly dancing)
Start date:
Description:
Time: Every Wednesday 19:00-20:00.
Fee: 64€/12 hours.
=== (True, False, False, False, True, True, <system.Recurrences.weekly:W>) ===
--- Invoice #5 for enrolment #12 (018 SV (Self-defence) / Dorothée Demeulenaere (ME)):
Title: Enrolment to 018 SV (Self-defence)
Start date: 18/03/2015
Missed events: 06/03/2015, 13/03/2015
Description:
Time: Every Friday 18:00-19:00.
Fee: 20€.
Scheduled dates:
20/03/2015, 27/03/2015, 10/04/2015, 17/04/2015,
=== (False, True, True, True, True, True, <system.Recurrences.once:O>) ===
--- Invoice #11 for enrolment #31 (001 Greece 2014 / Jacqueline Jacobs (ME)):
Title: Enrolment to 001 Greece 2014
Start date:
Description:
Places used: 2.
Date: 14/08/2014-20/08/2014.
Fee: Journeys.
=== (True, False, True, False, True, True, <system.Recurrences.once:O>) ===
--- Invoice #18 for enrolment #61 (001 Greece 2014 / Christian Radermacher (MEL)):
Title: Enrolment to 001 Greece 2014
Start date: 29/08/2014
Missed events: 14/08/2014
Description:
Date: 14/08/2014-20/08/2014.
Fee: Journeys.

Invoice recipient

>>> show_fields(rt.models.contacts.Partners, 'salesrule__invoice_recipient', True)
- Invoicing address (salesrule__invoice_recipient) : The partner who should get the invoices caused by this partner.

List of pupils who have an invoice_recipient:

>>> for p in rt.models.contacts.Partner.objects.filter(salesrule__invoice_recipient__isnull=False):
...     print("{} --> {}".format(p, p.salesrule.invoice_recipient))
Faymonville Luc --> Engels Edgar
Radermacher Alfons --> Emonts-Gast Erna
Martelaer Mark --> Dupont Jean

We take one of the recipients and verify that PartnersByInvoiceRecipient shows as expected:

>>> recipient = rt.models.courses.Pupil.objects.get(last_name="Engels")
>>> rt.show(rt.models.invoicing.PartnersByInvoiceRecipient, recipient)
================= ===== ===========================
 Partner           ID    Address
----------------- ----- ---------------------------
 Faymonville Luc   130   Brabantstraße, 4700 Eupen
================= ===== ===========================

Here are the enrolments of the pupil:

>>> pupil = rt.models.courses.Pupil.objects.get(last_name="Faymonville")
>>> pupil
Pupil #130 ('Luc Faymonville (MEL)')
>>> rt.show('courses.EnrolmentsByPupil', pupil, column_names="id request_date course amount workflow_buttons")
==== ================= ===================================== ============ ===============
 ID   Date of request   Activity                              Amount       Workflow
---- ----------------- ------------------------------------- ------------ ---------------
 27   08/11/2013        025C Yoga                             50,00        **Confirmed**
 28   08/11/2013        025C Yoga                             50,00        **Confirmed**
 26   23/11/2013        024C Yoga                             50,00        **Confirmed**
 25   01/02/2015        023C MED (Finding your inner peace)   64,00        **Confirmed**
 29   21/06/2015        014 Rücken (Swimming)                 80,00        **Confirmed**
                                                              **294,00**
==== ================= ===================================== ============ ===============

We pick one of them and look at the issued invoices:

>>> e = rt.models.courses.Enrolment.objects.get(id=28)
>>> rt.show('invoicing.InvoicingsByGenerator', e)  
===================== ===================================== ========== ============== =============== ==================
 Sales invoice         Heading                               Quantity   Voucher date   Voucher state   Number of events
--------------------- ------------------------------------- ---------- -------------- --------------- ------------------
 SLS 9/2014            [1] Enrolment to 025C Yoga            1          01/01/2014     Registered      5
 SLS 29/2014           [2] Renewal Enrolment to 025C Yoga    1          01/02/2014     Registered      5
 SLS 34/2014           [3] Renewal Enrolment to 025C Yoga    1          01/04/2014     Registered      5
 SLS 39/2014           [4] Renewal Enrolment to 025C Yoga    1          01/06/2014     Registered      5
 SLS 42/2014           [5] Renewal Enrolment to 025C Yoga    1          01/07/2014     Registered      5
 SLS 46/2014           [6] Renewal Enrolment to 025C Yoga    1          01/08/2014     Registered      5
 SLS 52/2014           [7] Renewal Enrolment to 025C Yoga    1          01/10/2014     Registered      5
 SLS 60/2014           [8] Renewal Enrolment to 025C Yoga    1          01/11/2014     Registered      5
 SLS 65/2014           [9] Renewal Enrolment to 025C Yoga    1          01/12/2014     Registered      5
 SLS 14/2015           [10] Renewal Enrolment to 025C Yoga   1          01/02/2015     Registered      5
 **Total (10 rows)**                                         **10**                                    **50**
===================== ===================================== ========== ============== =============== ==================

These invoices are not issued to the pupil but to the recipient:

>>> rt.show('trading.InvoicesByPartner', pupil)
No data to display
>>> rt.show('trading.InvoicesByPartner', recipient)  
===================== =============== ============== ================
 Entry date            Voucher         Total to pay   Workflow
--------------------- --------------- -------------- ----------------
 01/02/2015            *SLS 14/2015*   50,00          **Registered**
 01/01/2015            *SLS 3/2015*    48,00          **Registered**
 01/12/2014            *SLS 65/2014*   50,00          **Registered**
 01/11/2014            *SLS 60/2014*   50,00          **Registered**
 01/10/2014            *SLS 52/2014*   50,00          **Registered**
 01/08/2014            *SLS 46/2014*   50,00          **Registered**
 01/07/2014            *SLS 42/2014*   50,00          **Registered**
 01/06/2014            *SLS 39/2014*   50,00          **Registered**
 01/04/2014            *SLS 34/2014*   50,00          **Registered**
 01/02/2014            *SLS 29/2014*   50,00          **Registered**
 01/01/2014            *SLS 9/2014*    426,00         **Registered**
 **Total (11 rows)**                   **924,00**
===================== =============== ============== ================