Thursday, June 22, 2017

About persons, organizations and partners

I have been meditating and experimenting about a stupid and subtle old problem. Lydia had a customer called “Jugendgericht / John Doe”. In TIM this was a partner of type “Zahler” which became a Company in Lino. The “Jugendgericht” part was imported from TIM into the prefix field. The first name John wasn’t imported at all, and the last name was imported as the name.

Which caused Lydia to not find her customer because the combobox for partner (when creating a new invoice) didn’t include the prefix. So she saw only “Doe”.

  • str() of any Partner now includes the prefix
  • the prefix is now included to the quick_search_fields of a Partner.
  • which required us to move the definition of the prefix field from the Company model (and the Household model) to the Partner model. The field might even be useful for persons to handle special cases of titles like “Graf” or “Kardinal” which must display after the first name.

I tried (in a branch which I later threw away) whether things get easier if we replace the single partner field of an invoice by two separate fields company and person. It doesn’t. Because we also want households and partner lists to be able to receive invoices. Theoretically they can’t because they are neither a physical nor a legal person, but “partner” is per definition the general term for “any kind of business partner” and should include them. For example a calendar event or service report in Lino Tera can be assigned to a single client, a household or a therapeutic group.

As a side effect, Lino now supports remote fields to virtual FK fields.

I committed and pushed my changes, though a few test cases in welfare are still failing (because Partner and Person now have one field more).

Also some test cases in book are still failing due to changes by Tonis.

Diamond inheritance

I have the feeling that I need to fix #1908 before releasing a new version of Lino Tera for Lydia.

The following test cases (in The Lino Book) are currently failing:

$ python setup.py test -s tests.DocsTests.test_diamond
$ python setup.py test -s tests.DocsTests.test_diamond2

The error message is:

SystemCheckError: System check identified some issues:
ERRORS:
main.PizzeriaBar: (models.E005) The field 'restaurant_ptr' from parent model 'main.bar' clashes with the field 'restaurant_ptr' from parent model 'main.pizzeria'.

I created Django ticket 28332 <https://code.djangoproject.com/ticket/28332> for this.

But it seems that this problem is actually not related to our ticket #1908, it just was hidden before 1.11 and started to appear because 1.11 appearetly runs check as part of test.

So here is once more my test case of last Friday:

>>> from lino import startup
>>> startup('lino_book.projects.lydia.settings.demo')
>>> from lino.modlib.lino_startup.management.commands.dump2py import write_create_function
>>> import sys
>>> from lino.api import rt
>>> write_create_function(rt.models.lists.List, sys.stdout)
def create_lists_list(partner_ptr_id, ref, list_type_id):
    return create_mti_child(contacts_Partner, partner_ptr_id, lists_List,ref=ref,list_type_id=list_type_id,name_de=name_de,name_fr=name_fr)

The lino_tera.lib.lists.List model in Tera inherits from two concrete models lino_xl.lib.lists.List and lino_xl.lib.contacts.Partner:

lino_tera.lib.lists.List (no local fields)
<- lino_xl.lib.lists.List (defines fields list_type and remarks)
<- lino_xl.lib.contacts.Partner (defines fields name, ...)
   <- mixins.BabelNamed (defines fields name_xx)
   <- mixins.Referrable (defines field ref)
>>> model = rt.models.lists.List
>>> for f in model._meta.get_fields():
...     # if f.concrete and f.model is model:
...     if f.model is model:
...        print(repr(f), getattr(f, '_lino_babel_field', False))
<django.db.models.fields.related.OneToOneField: partner_ptr>
<lino.core.fields.NullCharField: ref>
<django.db.models.fields.related.ForeignKey: list_type>
<django.db.models.fields.CharField: name_de>
<django.db.models.fields.CharField: name_fr>
>>> model._meta.parents
OrderedDict([(<class 'lino_tera.lib.contacts.models.Partner'>, <django.db.models.fields.related.OneToOneField: partner_ptr>)])

Ahhaa! Now I see why it is: BabelNamed defines a BabelField name which clashes with the name field of Partner. It seems that Django doesn’t detect this clash because a BabelField isn’t a real field.

I solved this by creating a new mixin BabelDesignated which is the same as BabelNamed but uses a field designation instead of name. And then to use this mixin for the List model in Tera.

Note that I also have to define a full_clean() method which automatically fills the :name` field from the value of the designation field (similarily to Person and Household)

implement therapeutic groups as something else than lists.List.

Number formatting in grids

I started to work on #1925.

The lino_xl.lib.finan.ItemsByPaymentOrder table is the place where we want to see amounts with a space for separating thousands and always 2 positions after the comma. For example a value 1234.5 should display as 1 234,50.

The grid is defined as Lino.finan.ItemsByPaymentOrder.GridPanel in our linoweb.js file.

In ExtJS3 the Ext.grid.NumberColumn() class is responsible for rendering numbers. It has a config attribute format which can have the following values (taken from ExtJS 3.3 docs):

examples (123456.789):
0 - (123456) show only digits, no precision
0.00 - (123456.78) show only digits, 2 precision
0.0000 - (123456.7890) show only digits, 4 precision
0,000 - (123,456) show comma and digits, no precision
0,000.00 - (123,456.78) show comma and digits, 2 precision
0,0.00 - (123,456.78) shortcut method, show comma and digits, 2 precision
To reverse the grouping (,) and decimal (.) for international numbers, add /i to the end. For example: 0.000,00/i

According to these docs I just need to chang the format from 0.00/i to 0,000.00/i.

The amount field is represented in lino.modlib.extjs.elems by DecimalFieldElement() which inherits from NumberFieldElement(). Note the number_format and the get_column_options() method of these classes.

I noticed that get_column_options() didn’t set xtype to numbercolumn. That’s why the number format was being ignored. Fixed that.

Now it seems correctly defined, e.g. for Lino.finan.ItemsByPaymentOrder.GridPanel the amount column is defined as this:

{ "align": "right", "colIndex": 5, "dataIndex": "amount", "editable":
  true, "editor": amount402, "filter": { "type": "numeric" },
  "format": "0,000.00/i", "header": "Amount", "sortable": true,
  "tooltip": "(finan.ItemsByPaymentOrder.amount) ", "width":
  Lino.chars2width(13), "xtype": "numbercolumn" }

Now, strangely, a value of 3240,58 is being displayed as 3.240,58000!

When the format is 0,0.00/i, the result is 3.240,580

Note that the format is only for rendering, i.e. as long as they are not being edited. Editing behaviour not the problem here, and it is handled completely differently by Ext.form.NumberField().

Note Lino.NullNumberColumn()

lino.core.site.Site.default_number_format_extjs

release@spz

I deployed the new version to spz.

One problem when restoring their data was as expected:

File "contacts_company.py", line 4, in <module>
  loader.save(create_contacts_company(u'1',u'',None,u''))
File "snapshot/restore.py", line 522, in create_contacts_company
File "/home/lsaffre/repositories/lino/lino/utils/dpy.py", line 78, in create_mti_child
  ignored))
Exception: create_mti_child() Company 1 from Partner : ignored non-local fields {'prefix': u''}

That’s normal because I moved prefix from Company to Partner. As a temporary fix, I ignore the field for now:

def create_contacts_company(partner_ptr_id, prefix, type_id, vat_id):
    #return create_mti_child(contacts_Partner, partner_ptr_id, contacts_Company,prefix=prefix,type_id=type_id,vat_id=vat_id)
    return create_mti_child(contacts_Partner, partner_ptr_id, contacts_Company,type_id=type_id,vat_id=vat_id)

TODO: This is a case of migration which might need some more work on other sites where we will want to preserve these prefixes. I guess that I must extend create_mti_child() to exceptionally accept also non-local fields.

Session log:

  • make snapshot a : before upgrade

  • pull.sh

  • django run a/restore.py

  • make snapshot b : after restoring a into new version (Partner.prefix are lost)

  • Run ./init_demo.sh in order to (correctly) re-import all data from TIM. This will destroy all bookings which Lydia has already done.

  • snapshot c : with correct partners but without bookings.

  • copy the following files from b to c (overriding files in c):

    finan_*.py
    sales_*.py
    ledger_*.py
    accounts_*.py
    products_*.py
    vat_*.py
    
Traceback (most recent call last):
  ...
  File "lists_list.py", line 4, in <module>
    loader.save(create_lists_list(u'1000119',None,[u'HYPERKINESIE-KINDERGRUPPE 2000', u''],None))
  File "20170623/c/restore.py", line 621, in create_lists_list
NameError: global name 'designation_fr' is not defined

I found and reported #1926 (BabelFields on MTI parent fail to dump/restore). This is a plain design bug and the lists.List in Lino Tera is indeed the first case in the history of Lion where this happens.

Here is how to manually correct the create_lists_list() function in the restore.py file. Lino creates something like this:

def create_lists_list(partner_ptr_id, ref, designation, list_type_id):
    return create_mti_child(contacts_Partner, partner_ptr_id,
    lists_List,ref=ref,
    designation=designation,
    list_type_id=list_type_id,
    designation_fr=designation_fr)

And as long as #1926 is not fixed you must modify this into:

def create_lists_list(partner_ptr_id, ref, designation, list_type_id):
    kw = dict()
    if designation is not None:
        kw.update(bv2kw('designation', designation))
    return create_mti_child(contacts_Partner, partner_ptr_id,
    lists_List,ref=ref,
    list_type_id=list_type_id,
    **kw)