Exemplo n.º 1
0
    def test_build_4_functionfield01(self):
        name = 'get_pretty_properties'
        funfield = function_field_registry.get(FakeContact, name)
        self.assertIsNotNone(funfield)

        cell = EntityCellFunctionField(model=FakeContact, func_field=funfield)
        self.assertIsInstance(cell, EntityCellFunctionField)
        self.assertEqual(name, cell.value)
        self.assertEqual(str(funfield.verbose_name), cell.title)
        self.assertEqual('function_field-{}'.format(funfield.name), cell.key)
        # self.assertIs(cell.has_a_filter, True)
        # self.assertIs(cell.editable,     False)
        # self.assertIs(cell.sortable,     False)
        self.assertIs(cell.is_hidden, False)
        self.assertIs(cell.is_multiline, True)
        # self.assertEqual('', cell.filter_string)
        self.assertEqual(settings.CSS_DEFAULT_LISTVIEW,
                         cell.listview_css_class)
        self.assertEqual(settings.CSS_DEFAULT_HEADER_LISTVIEW,
                         cell.header_listview_css_class)

        cell = EntityCellFunctionField.build(FakeContact, func_field_name=name)
        self.assertIsInstance(cell, EntityCellFunctionField)
        self.assertEqual(name, cell.value)

        self.assertIsNone(
            EntityCellFunctionField.build(FakeContact,
                                          func_field_name='invalid'))
Exemplo n.º 2
0
    def test_functionfield01(self):
        self.assertEqual(_('Computed fields'),
                         EntityCellFunctionField.verbose_name)

        name = 'get_pretty_properties'
        funfield = function_field_registry.get(FakeContact, name)
        self.assertIsNotNone(funfield)

        cell = EntityCellFunctionField(model=FakeContact, func_field=funfield)
        self.assertIsInstance(cell, EntityCellFunctionField)
        self.assertEqual(name, cell.value)
        self.assertEqual(str(funfield.verbose_name), cell.title)
        self.assertEqual(f'function_field-{funfield.name}', cell.key)
        self.assertIs(cell.is_hidden, False)
        self.assertIs(cell.is_multiline, True)
        self.assertEqual(settings.CSS_DEFAULT_LISTVIEW,
                         cell.listview_css_class)
        self.assertEqual(settings.CSS_DEFAULT_HEADER_LISTVIEW,
                         cell.header_listview_css_class)

        cell = EntityCellFunctionField.build(FakeContact, func_field_name=name)
        self.assertIsInstance(cell, EntityCellFunctionField)
        self.assertEqual(name, cell.value)

        self.assertIsNone(
            EntityCellFunctionField.build(FakeContact,
                                          func_field_name='invalid'))
Exemplo n.º 3
0
    def _get_cells(self, model):
        cells = []

        for cell_cls, data in self._cells:
            cell = None

            if cell_cls is EntityCellRegularField:
                cell = EntityCellRegularField.build(model=model, name=data)
            elif cell_cls is EntityCellFunctionField:
                cell = EntityCellFunctionField.build(model,
                                                     func_field_name=data)
            else:  # EntityCellRelation
                rtype = self._get_relationtype(data)

                if rtype is False:
                    logger.warning(
                        'SmartColumnsRegistry: relation type "%s" does not exist',
                        data)
                else:
                    cell = EntityCellRelation(model=model, rtype=rtype)

            # Has no sense here:
            #  EntityCellActions : not configurable in HeaderFilter form
            #  EntityCellCustomField : dynamically created by user
            # TODO: other types

            if cell is not None:
                cells.append(cell)

        return cells
Exemplo n.º 4
0
    def test_relation_brick01(self):
        rtype = RelationType.create(
            ('test-subject_loves', 'loves'),
            ('test-object_loved', 'is loved by'),
        )[0]

        rbi = RelationBrickItem.objects.create_if_needed(rtype.id)
        self.assertIsInstance(rbi, RelationBrickItem)
        self.assertIsNotNone(rbi.pk)
        self.assertEqual(rtype.id, rbi.relation_type_id)

        get_ct = ContentType.objects.get_for_model
        ct_contact = get_ct(FakeContact)
        ct_orga = get_ct(FakeOrganisation)
        ct_img = get_ct(FakeImage)

        rbi = self.refresh(rbi)  # Test persistence
        self.assertIsNone(rbi.get_cells(ct_contact))
        self.assertIsNone(rbi.get_cells(ct_orga))
        self.assertIsNone(rbi.get_cells(ct_img))
        self.assertIs(rbi.all_ctypes_configured, False)

        rbi.set_cells(
            ct_contact,
            [
                EntityCellRegularField.build(FakeContact, 'last_name'),
                EntityCellFunctionField.build(FakeContact,
                                              'get_pretty_properties'),
            ],
        )
        rbi.set_cells(ct_orga,
                      [EntityCellRegularField.build(FakeOrganisation, 'name')])
        rbi.save()

        rbi = self.refresh(rbi)  # Test persistence
        self.assertIsNone(rbi.get_cells(ct_img))
        self.assertIs(rbi.all_ctypes_configured, False)

        cells_contact = rbi.get_cells(ct_contact)
        self.assertEqual(2, len(cells_contact))

        cell_contact = cells_contact[0]
        self.assertIsInstance(cell_contact, EntityCellRegularField)
        self.assertEqual('last_name', cell_contact.value)

        cell_contact = cells_contact[1]
        self.assertIsInstance(cell_contact, EntityCellFunctionField)
        self.assertEqual('get_pretty_properties', cell_contact.value)

        self.assertEqual(1, len(rbi.get_cells(ct_orga)))

        # ---
        self.assertEqual(rbi,
                         RelationBrickItem.objects.create_if_needed(rtype.id))
Exemplo n.º 5
0
    def _build_hf_n_contacts(self):
        user = self.user

        create_orga = partial(FakeOrganisation.objects.create, user=user)
        self.organisations = organisations = {
                name: create_orga(name=name)
                    for name in ('Bebop', 'Swordfish')
            }

        rtype_pilots = RelationType.create(('test-subject_pilots', 'pilots'),
                                           ('test-object_pilots',  'is piloted by')
                                          )[0]

        create_ptype = CremePropertyType.create
        ptype_beautiful = create_ptype(str_pk='test-prop_beautiful', text='is beautiful')
        ptype_girl      = create_ptype(str_pk='test-prop_girl',      text='is a girl')

        create_contact = partial(FakeContact.objects.create, user=user)
        self.contacts = contacts = {
            first_name: create_contact(first_name=first_name, last_name=last_name)
                for first_name, last_name in [('Spike', 'Spiegel'),
                                              ('Jet', 'Black'),
                                              ('Faye', 'Valentine'),
                                              ('Edward', 'Wong'),
                                             ]
        }

        create_rel = partial(Relation.objects.create, user=user, type=rtype_pilots,
                             object_entity=organisations['Bebop'],
                            )
        create_rel(subject_entity=contacts['Jet'])
        create_rel(subject_entity=contacts['Spike'])
        create_rel(subject_entity=contacts['Spike'], object_entity=organisations['Swordfish'])

        create_prop = CremeProperty.objects.create
        create_prop(type=ptype_girl,      creme_entity=contacts['Faye'])
        create_prop(type=ptype_girl,      creme_entity=contacts['Edward'])
        create_prop(type=ptype_beautiful, creme_entity=contacts['Faye'])

        cells = [
            EntityCellRegularField.build(model=FakeContact, name='civility'),
            EntityCellRegularField.build(model=FakeContact, name='last_name'),
            EntityCellRegularField.build(model=FakeContact, name='first_name'),
            EntityCellRelation(model=FakeContact, rtype=rtype_pilots),
            # TODO: EntityCellCustomField
            EntityCellFunctionField.build(model=FakeContact, func_field_name='get_pretty_properties'),
        ]
        hf = HeaderFilter.create(pk='test-hf_contact', name='Contact view',
                                 model=FakeContact, cells_desc=cells,
                                )

        # return cells
        return hf
Exemplo n.º 6
0
    def test_search_invalid_cell_type(self):
        "No error."
        self.login()

        ct = ContentType.objects.get_for_model(FakeOrganisation)
        cell = EntityCellFunctionField.build(FakeOrganisation,
                                             'get_pretty_properties')
        self.assertIsNotNone(cell)

        SearchConfigItem.objects.create(content_type=ct, cells=[cell])

        response = self._search('cool', ct.id)
        self.assertEqual(200, response.status_code)
Exemplo n.º 7
0
    def test_average01(self):
        "Regular field."
        agg = RGAAverage(
            cell=EntityCellRegularField.build(FakeOrganisation, 'capital'))
        self.assertIsNone(agg.error)

        # ---
        with self.assertRaises(ValueError) as cm1:
            RGAAverage(cell=None)

        self.assertEqual(_('the field does not exist any more.'),
                         str(cm1.exception))

        # ---
        with self.assertRaises(ValueError) as cm2:
            RGAAverage(cell=EntityCellFunctionField.build(
                FakeOrganisation, 'get_pretty_properties'), )

        self.assertIn('invalid type of cell', str(cm2.exception))
Exemplo n.º 8
0
    def test_functionfields(self):
        field = EntityCellsField(model=FakeContact)
        name1 = 'get_pretty_properties'
        value = f'function_field-{name1}'
        self.assertCellInChoices(
            value,
            choices=self._find_sub_widget(field, 'function_field').choices,
        )
        self.assertListEqual(
            [EntityCellFunctionField.build(FakeContact, name1)],
            field.clean(value))

        name2 = 'invalid'
        self.assertFieldValidationError(
            UniformEntityCellsField,
            'invalid_value',
            field.clean,
            f'function_field-{name2}',
            message_args={'value': name2},
        )
Exemplo n.º 9
0
    def test_functionfield01(self):
        "Function field are not sortable by default."
        sorter = QuerySorter()

        field_name = 'name'
        cells = [
            EntityCellRegularField.build(model=FakeOrganisation,
                                         name=field_name),
            EntityCellFunctionField.build(
                model=FakeContact, func_field_name='get_pretty_properties'),
        ]

        sortinfo = sorter.get(
            model=FakeInvoice,
            cells=cells,
            cell_key=cells[1].key,
            order=Order(),
        )
        self.assertEqual((field_name, 'cremeentity_ptr_id'),
                         sortinfo.field_names)
        self.assertEqual(cells[0].key, sortinfo.main_cell_key)
        self.assertTrue(sortinfo.main_order.asc)
Exemplo n.º 10
0
    def _create_report(self, name='Report #1', efilter=None, extra_cells=()):
        cells = [
            EntityCellRegularField.build(model=FakeContact, name='last_name'),
            EntityCellRegularField.build(model=FakeContact, name='user'),
            EntityCellRelation(model=FakeContact, rtype=RelationType.objects.get(pk=REL_SUB_HAS)),
            EntityCellFunctionField.build(model=FakeContact, func_field_name='get_pretty_properties'),
            *extra_cells,
        ]

        hf = HeaderFilter.create(pk='test_hf', name='name', model=FakeContact, cells_desc=cells)

        response = self.client.post(self.ADD_URL, follow=True,
                                    data={'user':   self.user.pk,
                                          'name':   name,
                                          'ct':     self.ct_contact.id,
                                          'hf':     hf.id,
                                          'filter': efilter.id if efilter else '',
                                         },
                                   )
        self.assertNoFormError(response)

        return self.get_object_or_fail(Report, name=name)
Exemplo n.º 11
0
    def test_copy02(self):
        "Attribute <_sub_fields> (container)."
        field1 = EntityCellsField(model=FakeContact)
        field2 = deepcopy(field1)

        registry = EntityCellsRegistry()
        registry(EntityCellRegularField)
        registry(EntityCellRelation)

        field1.cell_registry = registry

        ffield_name = 'get_pretty_properties'
        value = f'function_field-{ffield_name}'
        self.assertFieldValidationError(
            EntityCellsField,
            'invalid_type',
            field1.clean,
            value,
            message_args={'type_id': 'function_field'},
        )
        self.assertListEqual(
            [EntityCellFunctionField.build(FakeContact, ffield_name)],
            field2.clean(value))
Exemplo n.º 12
0
    def populate(self):
        already_populated = RelationType.objects.filter(
            pk=constants.REL_SUB_BILL_ISSUED, ).exists()

        Contact = persons.get_contact_model()
        Organisation = persons.get_organisation_model()
        Product = products.get_product_model()
        Service = products.get_service_model()

        # Relationships ---------------------------
        line_entities = [*lines_registry]
        create_rtype = RelationType.create
        create_rtype(
            (constants.REL_SUB_BILL_ISSUED, _('issued by'), BILLING_MODELS),
            (constants.REL_OBJ_BILL_ISSUED, _('has issued'), [Organisation]),
            is_internal=True,
            minimal_display=(False, True),
        )
        rt_sub_bill_received = create_rtype(
            (constants.REL_SUB_BILL_RECEIVED, _('received by'),
             BILLING_MODELS),
            (constants.REL_OBJ_BILL_RECEIVED, _('has received'),
             [Organisation, Contact]),
            is_internal=True,
            minimal_display=(False, True),
        )[0]
        create_rtype(
            (constants.REL_SUB_HAS_LINE, _('had the line'), BILLING_MODELS),
            (constants.REL_OBJ_HAS_LINE, _('is the line of'), line_entities),
            is_internal=True,
            minimal_display=(True, True),
        )
        create_rtype(
            (constants.REL_SUB_LINE_RELATED_ITEM, _('has the related item'),
             line_entities),
            (constants.REL_OBJ_LINE_RELATED_ITEM, _('is the related item of'),
             [Product, Service]),
            is_internal=True,
        )
        create_rtype(
            (
                constants.REL_SUB_CREDIT_NOTE_APPLIED,
                _('is used in the billing document'),
                [CreditNote],
            ),
            (
                constants.REL_OBJ_CREDIT_NOTE_APPLIED,
                _('uses the credit note'),
                [Quote, SalesOrder, Invoice],
            ),
            is_internal=True,
            minimal_display=(True, True),
        )

        if apps.is_installed('creme.activities'):
            logger.info(
                'Activities app is installed '
                '=> an Invoice/Quote/SalesOrder can be the subject of an Activity'
            )

            from creme.activities.constants import REL_SUB_ACTIVITY_SUBJECT

            RelationType.objects.get(
                pk=REL_SUB_ACTIVITY_SUBJECT, ).add_subject_ctypes(
                    Invoice, Quote, SalesOrder)

        # Payment Terms ---------------------------
        create_if_needed(
            PaymentTerms,
            {'pk': 1},
            name=_('Deposit'),
            description=_(r'20% deposit will be required'),
            is_custom=False,
        )

        # SalesOrder Status ---------------------------
        def create_order_status(pk, name, **kwargs):
            create_if_needed(SalesOrderStatus, {'pk': pk}, name=name, **kwargs)

        # NB: pk=1 + is_custom=False --> default status
        #     (used when a quote is converted in invoice for example)
        create_order_status(1,
                            pgettext('billing-salesorder', 'Issued'),
                            order=1,
                            is_custom=False)

        if not already_populated:
            create_order_status(2,
                                pgettext('billing-salesorder', 'Accepted'),
                                order=3)
            create_order_status(3,
                                pgettext('billing-salesorder', 'Rejected'),
                                order=4)
            create_order_status(4,
                                pgettext('billing-salesorder', 'Created'),
                                order=2)

        # Invoice Status ---------------------------
        def create_invoice_status(pk, name, **kwargs):
            create_if_needed(InvoiceStatus, {'pk': pk}, name=name, **kwargs)

        create_invoice_status(
            1,
            pgettext('billing-invoice', 'Draft'),
            order=1,
            is_custom=False,
        )  # Default status
        create_invoice_status(
            2,
            pgettext('billing-invoice', 'To be sent'),
            order=2,
            is_custom=False,
        )

        if not already_populated:
            create_invoice_status(
                3,
                pgettext('billing-invoice', 'Sent'),
                order=3,
                pending_payment=True,
            )
            create_invoice_status(
                4,
                pgettext('billing-invoice', 'Resulted'),
                order=5,
            )
            create_invoice_status(
                5,
                pgettext('billing-invoice', 'Partly resulted'),
                order=4,
                pending_payment=True,
            )
            create_invoice_status(
                6,
                _('Collection'),
                order=7,
            )
            create_invoice_status(
                7,
                _('Resulted collection'),
                order=6,
            )
            create_invoice_status(
                8,
                pgettext('billing-invoice', 'Canceled'),
                order=8,
            )

        # CreditNote Status ---------------------------
        def create_cnote_status(pk, name, **kwargs):
            create_if_needed(CreditNoteStatus, {'pk': pk}, name=name, **kwargs)

        create_cnote_status(1,
                            pgettext('billing-creditnote', 'Draft'),
                            order=1,
                            is_custom=False)

        if not already_populated:
            create_cnote_status(2,
                                pgettext('billing-creditnote', 'Issued'),
                                order=2)
            create_cnote_status(3,
                                pgettext('billing-creditnote', 'Consumed'),
                                order=3)
            create_cnote_status(4,
                                pgettext('billing-creditnote', 'Out of date'),
                                order=4)

        # ---------------------------
        EntityFilter.objects.smart_update_or_create(
            'billing-invoices_unpaid',
            name=_('Invoices unpaid'),
            model=Invoice,
            user='******',
            conditions=[
                condition_handler.RegularFieldConditionHandler.build_condition(
                    model=Invoice,
                    operator=operators.EqualsOperator,
                    field_name='status__pending_payment',
                    values=[True],
                ),
            ],
        )
        EntityFilter.objects.smart_update_or_create(
            'billing-invoices_unpaid_late',
            name=_('Invoices unpaid and late'),
            model=Invoice,
            user='******',
            conditions=[
                condition_handler.RegularFieldConditionHandler.build_condition(
                    model=Invoice,
                    operator=operators.EqualsOperator,
                    field_name='status__pending_payment',
                    values=[True],
                ),
                condition_handler.DateRegularFieldConditionHandler.
                build_condition(
                    model=Invoice,
                    field_name='expiration_date',
                    date_range='in_past',
                ),
            ],
        )
        current_year_invoice_filter = EntityFilter.objects.smart_update_or_create(
            'billing-current_year_invoices',
            name=_('Current year invoices'),
            model=Invoice,
            user='******',
            conditions=[
                condition_handler.DateRegularFieldConditionHandler.
                build_condition(
                    model=Invoice,
                    field_name='issuing_date',
                    date_range='current_year',
                ),
            ],
        )
        current_year_unpaid_invoice_filter = EntityFilter.objects.smart_update_or_create(
            'billing-current_year_unpaid_invoices',
            name=_('Current year and unpaid invoices'),
            model=Invoice,
            user='******',
            conditions=[
                condition_handler.DateRegularFieldConditionHandler.
                build_condition(
                    model=Invoice,
                    field_name='issuing_date',
                    date_range='current_year',
                ),
                condition_handler.RegularFieldConditionHandler.build_condition(
                    model=Invoice,
                    operator=operators.EqualsOperator,
                    field_name='status__pending_payment',
                    values=[True],
                ),
            ],
        )

        # ---------------------------
        def create_hf(hf_pk, name, model, status=True):
            HeaderFilter.objects.create_if_needed(
                pk=hf_pk,
                name=name,
                model=model,
                cells_desc=[
                    (EntityCellRegularField, {
                        'name': 'name'
                    }),
                    EntityCellRelation(model=model,
                                       rtype=rt_sub_bill_received),
                    (EntityCellRegularField, {
                        'name': 'number'
                    }),
                    (EntityCellRegularField, {
                        'name': 'status'
                    }) if status else None,
                    (EntityCellRegularField, {
                        'name': 'total_no_vat'
                    }),
                    (EntityCellRegularField, {
                        'name': 'issuing_date'
                    }),
                    (EntityCellRegularField, {
                        'name': 'expiration_date'
                    }),
                ],
            )

        create_hf(constants.DEFAULT_HFILTER_INVOICE, _('Invoice view'),
                  Invoice)
        create_hf(constants.DEFAULT_HFILTER_QUOTE, _('Quote view'), Quote)
        create_hf(constants.DEFAULT_HFILTER_ORDER, _('Sales order view'),
                  SalesOrder)
        create_hf(constants.DEFAULT_HFILTER_CNOTE, _('Credit note view'),
                  CreditNote)
        create_hf(
            constants.DEFAULT_HFILTER_TEMPLATE,
            _('Template view'),
            TemplateBase,
            status=False,
        )

        def create_hf_lines(hf_pk, name, model):
            build_cell = EntityCellRegularField.build
            HeaderFilter.objects.create_if_needed(
                pk=hf_pk,
                name=name,
                model=model,
                cells_desc=[
                    build_cell(model=model, name='on_the_fly_item'),
                    build_cell(model=model, name='quantity'),
                    build_cell(model=model, name='unit_price'),
                ],
            )

        create_hf_lines('billing-hg_product_lines', _('Product lines view'),
                        ProductLine)
        create_hf_lines('billing-hg_service_lines', _('Service lines view'),
                        ServiceLine)

        # ---------------------------
        creation_only_groups_desc = [
            {
                'name':
                _('Properties'),
                'cells': [
                    (
                        EntityCellCustomFormSpecial,
                        {
                            'name':
                            EntityCellCustomFormSpecial.CREME_PROPERTIES
                        },
                    ),
                ],
            },
            {
                'name':
                _('Relationships'),
                'cells': [
                    (
                        EntityCellCustomFormSpecial,
                        {
                            'name': EntityCellCustomFormSpecial.RELATIONS
                        },
                    ),
                ],
            },
        ]

        def build_custom_form_items(model, creation_descriptor,
                                    edition_descriptor, field_names):
            base_groups = [
                {
                    'name':
                    _('General information'),
                    'layout':
                    LAYOUT_DUAL_FIRST,
                    'cells': [
                        *((EntityCellRegularField, {
                            'name': fname
                        }) for fname in field_names),
                        (
                            EntityCellCustomFormSpecial,
                            {
                                'name':
                                EntityCellCustomFormSpecial.
                                REMAINING_REGULARFIELDS
                            },
                        ),
                    ],
                },
                {
                    'name':
                    _('Organisations'),
                    'layout':
                    LAYOUT_DUAL_SECOND,
                    'cells': [
                        BillingSourceSubCell(model=model).into_cell(),
                        BillingTargetSubCell(model=model).into_cell(),
                    ],
                },
                {
                    'name': _('Description'),
                    'layout': LAYOUT_DUAL_SECOND,
                    'cells': [
                        (EntityCellRegularField, {
                            'name': 'description'
                        }),
                    ],
                },
                {
                    'name':
                    _('Custom fields'),
                    'layout':
                    LAYOUT_DUAL_SECOND,
                    'cells': [
                        (
                            EntityCellCustomFormSpecial,
                            {
                                'name':
                                EntityCellCustomFormSpecial.
                                REMAINING_CUSTOMFIELDS
                            },
                        ),
                    ],
                },
            ]

            CustomFormConfigItem.objects.create_if_needed(
                descriptor=creation_descriptor,
                groups_desc=[
                    *base_groups,
                    *creation_only_groups_desc,
                ],
            )
            CustomFormConfigItem.objects.create_if_needed(
                descriptor=edition_descriptor,
                groups_desc=base_groups,
            )

        build_custom_form_items(
            model=Invoice,
            creation_descriptor=custom_forms.INVOICE_CREATION_CFORM,
            edition_descriptor=custom_forms.INVOICE_EDITION_CFORM,
            field_names=[
                'user',
                'name',
                'number',
                'status',
                'issuing_date',
                'expiration_date',
                'discount',
                'currency',
                'comment',
                'additional_info',
                'payment_terms',
                'payment_type',
                'buyers_order_number',
            ],
        )
        build_custom_form_items(
            model=Quote,
            creation_descriptor=custom_forms.QUOTE_CREATION_CFORM,
            edition_descriptor=custom_forms.QUOTE_EDITION_CFORM,
            field_names=[
                'user',
                'name',
                'number',
                'status',
                'issuing_date',
                'expiration_date',
                'discount',
                'currency',
                'comment',
                'additional_info',
                'payment_terms',
                'acceptation_date',
            ],
        )
        build_custom_form_items(
            model=SalesOrder,
            creation_descriptor=custom_forms.ORDER_CREATION_CFORM,
            edition_descriptor=custom_forms.ORDER_EDITION_CFORM,
            field_names=[
                'user',
                'name',
                'number',
                'status',
                'issuing_date',
                'expiration_date',
                'discount',
                'currency',
                'comment',
                'additional_info',
                'payment_terms',
            ],
        )
        build_custom_form_items(
            model=CreditNote,
            creation_descriptor=custom_forms.CNOTE_CREATION_CFORM,
            edition_descriptor=custom_forms.CNOTE_EDITION_CFORM,
            field_names=[
                'user',
                'name',
                'number',
                'status',
                'issuing_date',
                'expiration_date',
                'discount',
                'currency',
                'comment',
                'additional_info',
                'payment_terms',
            ],
        )

        common_template_groups_desc = [
            {
                'name':
                _('General information'),
                'cells': [
                    (EntityCellRegularField, {
                        'name': 'user'
                    }),
                    (EntityCellRegularField, {
                        'name': 'name'
                    }),
                    (EntityCellRegularField, {
                        'name': 'number'
                    }),
                    BillingTemplateStatusSubCell(
                        model=TemplateBase).into_cell(),
                    (EntityCellRegularField, {
                        'name': 'issuing_date'
                    }),
                    (EntityCellRegularField, {
                        'name': 'expiration_date'
                    }),
                    (EntityCellRegularField, {
                        'name': 'discount'
                    }),
                    (EntityCellRegularField, {
                        'name': 'currency'
                    }),
                    (EntityCellRegularField, {
                        'name': 'comment'
                    }),
                    (EntityCellRegularField, {
                        'name': 'additional_info'
                    }),
                    (EntityCellRegularField, {
                        'name': 'payment_terms'
                    }),
                    (
                        EntityCellCustomFormSpecial,
                        {
                            'name':
                            EntityCellCustomFormSpecial.REMAINING_REGULARFIELDS
                        },
                    ),
                ],
            },
            {
                'name':
                _('Organisations'),
                'cells': [
                    BillingSourceSubCell(model=TemplateBase).into_cell(),
                    BillingTargetSubCell(model=TemplateBase).into_cell(),
                ],
            },
            {
                'name': _('Description'),
                'cells': [
                    (EntityCellRegularField, {
                        'name': 'description'
                    }),
                ],
            },
            {
                'name':
                _('Custom fields'),
                'cells': [
                    (
                        EntityCellCustomFormSpecial,
                        {
                            'name':
                            EntityCellCustomFormSpecial.REMAINING_CUSTOMFIELDS
                        },
                    ),
                ],
            },
        ]
        CustomFormConfigItem.objects.create_if_needed(
            descriptor=custom_forms.BTEMPLATE_CREATION_CFORM,
            groups_desc=[
                *common_template_groups_desc,
                *creation_only_groups_desc,
            ],
        )
        CustomFormConfigItem.objects.create_if_needed(
            descriptor=custom_forms.BTEMPLATE_EDITION_CFORM,
            groups_desc=common_template_groups_desc,
        )

        # ---------------------------
        get_ct = ContentType.objects.get_for_model
        engine_id = ''
        flavour_id = ''

        if 'creme.billing.exporters.xhtml2pdf.Xhtml2pdfExportEngine' in settings.BILLING_EXPORTERS:
            from creme.billing.exporters.xhtml2pdf import Xhtml2pdfExportEngine
            from creme.creme_core.utils import l10n

            # TODO: add the country in settings & use it...
            country = l10n.FR
            language = 'fr_FR'
            theme = 'cappuccino'
            try:
                Xhtml2pdfExportEngine.FLAVOURS_INFO[country][language][theme]
            except KeyError:
                pass
            else:
                engine_id = Xhtml2pdfExportEngine.id
                flavour_id = f'{country}/{language}/{theme}'

        for model in (CreditNote, Invoice, Quote, SalesOrder, TemplateBase):
            ExporterConfigItem.objects.get_or_create(
                content_type=get_ct(model),
                defaults={
                    'engine_id': engine_id,
                    'flavour_id': flavour_id,
                },
            )

        # ---------------------------
        for model in (Invoice, CreditNote, Quote, SalesOrder):
            SearchConfigItem.objects.create_if_needed(
                model, ['name', 'number', 'status__name'])

        for model in (ProductLine, ServiceLine):
            SearchConfigItem.objects.create_if_needed(model, [], disabled=True)

        # ---------------------------
        create_svalue = SettingValue.objects.get_or_create
        create_svalue(key_id=setting_keys.payment_info_key.id,
                      defaults={'value': True})
        create_svalue(key_id=setting_keys.button_redirection_key.id,
                      defaults={'value': True})

        # ---------------------------
        # TODO: move to "not already_populated" section in creme2.4
        if not MenuConfigItem.objects.filter(
                entry_id__startswith='billing-').exists():
            container = MenuConfigItem.objects.get_or_create(
                entry_id=ContainerEntry.id,
                entry_data={'label': _('Management')},
                defaults={'order': 50},
            )[0]

            create_mitem = partial(MenuConfigItem.objects.create,
                                   parent=container)
            create_mitem(entry_id=menu.QuotesEntry.id, order=10)
            create_mitem(entry_id=menu.InvoicesEntry.id, order=15)
            create_mitem(entry_id=menu.CreditNotesEntry.id, order=50)
            create_mitem(entry_id=menu.SalesOrdersEntry.id, order=55)
            create_mitem(entry_id=menu.ProductLinesEntry.id, order=200)
            create_mitem(entry_id=menu.ServiceLinesEntry.id, order=210)

        # ---------------------------
        if not already_populated:

            def create_quote_status(pk, name, **kwargs):
                create_if_needed(QuoteStatus, {'pk': pk}, name=name, **kwargs)

            # Default status
            create_quote_status(1,
                                pgettext('billing-quote', 'Pending'),
                                order=2)

            create_quote_status(2,
                                pgettext('billing-quote', 'Accepted'),
                                order=3,
                                won=True)
            create_quote_status(3,
                                pgettext('billing-quote', 'Rejected'),
                                order=4)
            create_quote_status(4,
                                pgettext('billing-quote', 'Created'),
                                order=1)

            # ---------------------------
            create_if_needed(SettlementTerms, {'pk': 1}, name=_('30 days'))
            create_if_needed(SettlementTerms, {'pk': 2}, name=_('Cash'))
            create_if_needed(SettlementTerms, {'pk': 3}, name=_('45 days'))
            create_if_needed(SettlementTerms, {'pk': 4}, name=_('60 days'))
            create_if_needed(SettlementTerms, {'pk': 5},
                             name=_('30 days, end month the 10'))

            # ---------------------------
            create_if_needed(
                AdditionalInformation, {'pk': 1},
                name=_('Trainer accreditation'),
                description=
                _('being certified trainer courses could be supported by your OPCA'
                  ))

            # ---------------------------
            create_bmi = ButtonMenuItem.objects.create_if_needed
            create_bmi(model=Invoice,
                       button=buttons.GenerateInvoiceNumberButton,
                       order=0)

            create_bmi(model=Quote,
                       button=buttons.ConvertToInvoiceButton,
                       order=0)
            create_bmi(model=Quote,
                       button=buttons.ConvertToSalesOrderButton,
                       order=1)

            create_bmi(model=SalesOrder,
                       button=buttons.ConvertToInvoiceButton,
                       order=0)

            create_bmi(model=Organisation,
                       button=buttons.AddQuoteButton,
                       order=100)
            create_bmi(model=Organisation,
                       button=buttons.AddSalesOrderButton,
                       order=101)
            create_bmi(model=Organisation,
                       button=buttons.AddInvoiceButton,
                       order=102)

            create_bmi(model=Contact, button=buttons.AddQuoteButton, order=100)
            create_bmi(model=Contact,
                       button=buttons.AddSalesOrderButton,
                       order=101)
            create_bmi(model=Contact,
                       button=buttons.AddInvoiceButton,
                       order=102)

            # ---------------------------
            create_cbci = CustomBrickConfigItem.objects.create
            build_cell = EntityCellRegularField.build

            def build_cells(model, *extra_cells):
                return [
                    build_cell(model, 'name'),
                    build_cell(model, 'number'),
                    build_cell(model, 'issuing_date'),
                    build_cell(model, 'expiration_date'),
                    build_cell(model, 'discount'),
                    build_cell(model, 'additional_info'),
                    build_cell(model, 'payment_terms'),
                    build_cell(model, 'currency'),
                    *extra_cells,
                    build_cell(model, 'comment'),
                    build_cell(model, 'description'),
                    # --
                    build_cell(model, 'created'),
                    build_cell(model, 'modified'),
                    build_cell(model, 'user'),
                ]

            cbci_invoice = create_cbci(
                id='billing-invoice_info',
                name=_('Invoice information'),
                content_type=Invoice,
                cells=build_cells(
                    Invoice,
                    build_cell(Invoice, 'status'),
                    build_cell(Invoice, 'payment_type'),
                    build_cell(Invoice, 'buyers_order_number'),
                ),
            )
            cbci_c_note = create_cbci(
                id='billing-creditnote_info',
                name=_('Credit note information'),
                content_type=CreditNote,
                cells=build_cells(
                    CreditNote,
                    build_cell(CreditNote, 'status'),
                ),
            )
            cbci_quote = create_cbci(
                id='billing-quote_info',
                name=_('Quote information'),
                content_type=Quote,
                cells=build_cells(
                    Quote,
                    build_cell(Quote, 'status'),
                    build_cell(Quote, 'acceptation_date'),
                ),
            )
            cbci_s_order = create_cbci(
                id='billing-salesorder_info',
                name=_('Salesorder information'),
                content_type=SalesOrder,
                cells=build_cells(
                    SalesOrder,
                    build_cell(SalesOrder, 'status'),
                ),
            )
            cbci_tbase = create_cbci(
                id='billing-templatebase_info',
                name=pgettext('billing', 'Template information'),
                content_type=TemplateBase,
                cells=build_cells(
                    TemplateBase,
                    EntityCellFunctionField.build(TemplateBase,
                                                  'get_verbose_status'),
                ),
            )

            models_4_blocks = [
                (Invoice, cbci_invoice,
                 True),  # Boolean -> insert CreditNote block
                (CreditNote, cbci_c_note, False),
                (Quote, cbci_quote, True),
                (SalesOrder, cbci_s_order, True),
                (TemplateBase, cbci_tbase, False),
            ]

            TOP = BrickDetailviewLocation.TOP
            LEFT = BrickDetailviewLocation.LEFT
            RIGHT = BrickDetailviewLocation.RIGHT

            for model, cbci, has_credit_notes in models_4_blocks:
                data = [
                    # LEFT
                    {
                        'brick': cbci.brick_id,
                        'order': 5
                    },
                    {
                        'brick': core_bricks.CustomFieldsBrick,
                        'order': 40
                    },
                    {
                        'brick': bricks.BillingPaymentInformationBrick,
                        'order': 60
                    },
                    {
                        'brick': bricks.BillingPrettyAddressBrick,
                        'order': 70
                    },
                    {
                        'brick': core_bricks.PropertiesBrick,
                        'order': 450
                    },
                    {
                        'brick': core_bricks.RelationsBrick,
                        'order': 500
                    },
                    {
                        'brick': bricks.TargetBrick,
                        'order': 2,
                        'zone': RIGHT
                    },
                    {
                        'brick': bricks.TotalBrick,
                        'order': 3,
                        'zone': RIGHT
                    },
                    {
                        'brick': core_bricks.HistoryBrick,
                        'order': 20,
                        'zone': RIGHT
                    },
                    {
                        'brick': bricks.ProductLinesBrick,
                        'order': 10,
                        'zone': TOP
                    },
                    {
                        'brick': bricks.ServiceLinesBrick,
                        'order': 20,
                        'zone': TOP
                    },
                ]
                if has_credit_notes:
                    data.append({
                        'brick': bricks.CreditNotesBrick,
                        'order': 30,
                        'zone': TOP
                    })

                BrickDetailviewLocation.objects.multi_create(
                    defaults={
                        'model': model,
                        'zone': LEFT
                    },
                    data=data,
                )

            if apps.is_installed('creme.assistants'):
                logger.info(
                    'Assistants app is installed => we use the assistants blocks on detail views'
                )

                from creme.assistants import bricks as a_bricks

                for t in models_4_blocks:
                    BrickDetailviewLocation.objects.multi_create(
                        defaults={
                            'model': t[0],
                            'zone': RIGHT
                        },
                        data=[
                            {
                                'brick': a_bricks.TodosBrick,
                                'order': 100
                            },
                            {
                                'brick': a_bricks.MemosBrick,
                                'order': 200
                            },
                            {
                                'brick': a_bricks.AlertsBrick,
                                'order': 300
                            },
                            {
                                'brick': a_bricks.UserMessagesBrick,
                                'order': 400
                            },
                        ],
                    )

            if apps.is_installed('creme.documents'):
                # logger.info(
                #   'Documents app is installed
                #   => we use the documents block on detail views'
                # )

                from creme.documents.bricks import LinkedDocsBrick

                for t in models_4_blocks:
                    BrickDetailviewLocation.objects.create_if_needed(
                        brick=LinkedDocsBrick,
                        order=600,
                        zone=RIGHT,
                        model=t[0],
                    )

            BrickDetailviewLocation.objects.multi_create(
                defaults={
                    'model': Organisation,
                    'zone': RIGHT
                },
                data=[
                    {
                        'brick': bricks.PaymentInformationBrick,
                        'order': 300,
                        'zone': LEFT
                    },
                    {
                        'brick': bricks.ReceivedInvoicesBrick,
                        'order': 14
                    },
                    {
                        'brick': bricks.ReceivedQuotesBrick,
                        'order': 18
                    },
                ],
            )

            # ---------------------------
            if apps.is_installed('creme.reports'):
                logger.info(
                    'Reports app is installed '
                    '=> we create 2 billing reports, with 3 graphs, and related blocks in home'
                )
                self.create_reports(
                    rt_sub_bill_received,
                    current_year_invoice_filter,
                    current_year_unpaid_invoice_filter,
                )
Exemplo n.º 13
0
    def populate(self):
        already_populated = RelationType.objects.filter(pk=constants.REL_SUB_BILL_ISSUED).exists()

        Contact      = persons.get_contact_model()
        Organisation = persons.get_organisation_model()
        Product = products.get_product_model()
        Service = products.get_service_model()

        # ---------------------------
        # line_entities = [ProductLine, ServiceLine]
        line_entities = list(lines_registry)
        RelationType.create((constants.REL_SUB_BILL_ISSUED, _('issued by'),  BILLING_MODELS),
                            (constants.REL_OBJ_BILL_ISSUED, _('has issued'), [Organisation]),
                            is_internal=True,
                            minimal_display=(False, True),
                           )
        rt_sub_bill_received = \
        RelationType.create((constants.REL_SUB_BILL_RECEIVED, _('received by'),  BILLING_MODELS),
                            (constants.REL_OBJ_BILL_RECEIVED, _('has received'), [Organisation, Contact]),
                            is_internal=True,
                            minimal_display=(False, True),
                           )[0]
        RelationType.create((constants.REL_SUB_HAS_LINE, _('had the line'),   BILLING_MODELS),
                            (constants.REL_OBJ_HAS_LINE, _('is the line of'), line_entities),
                            is_internal=True,
                            minimal_display=(True, True),
                           )
        RelationType.create((constants.REL_SUB_LINE_RELATED_ITEM, _('has the related item'),   line_entities),
                            (constants.REL_OBJ_LINE_RELATED_ITEM, _('is the related item of'), [Product, Service]),
                            is_internal=True,
                           )
        RelationType.create((constants.REL_SUB_CREDIT_NOTE_APPLIED, _('is used in the billing document'), [CreditNote]),
                            (constants.REL_OBJ_CREDIT_NOTE_APPLIED, _('used the credit note'),            [Quote, SalesOrder, Invoice]),
                            is_internal=True,
                            minimal_display=(True, True),
                           )

        if apps.is_installed('creme.activities'):
            logger.info('Activities app is installed => an Invoice/Quote/SalesOrder can be the subject of an Activity')

            from creme.activities.constants import REL_SUB_ACTIVITY_SUBJECT

            RelationType.objects.get(pk=REL_SUB_ACTIVITY_SUBJECT) \
                                .add_subject_ctypes(Invoice, Quote, SalesOrder)

        # ---------------------------
        create_if_needed(PaymentTerms, {'pk': 1}, name=_('Deposit'),
                         description=_(r'20% deposit will be required'),
                         is_custom=False,
                        )

        # ---------------------------
        # NB: pk=1 + is_custom=False --> default status (used when a quote is converted in invoice for example)
        create_if_needed(SalesOrderStatus, {'pk': 1}, name=pgettext('billing-salesorder', 'Issued'), order=1, is_custom=False) # Default status
        if not already_populated:
            create_if_needed(SalesOrderStatus, {'pk': 2}, name=pgettext('billing-salesorder', 'Accepted'), order=3)
            create_if_needed(SalesOrderStatus, {'pk': 3}, name=pgettext('billing-salesorder', 'Rejected'), order=4)
            create_if_needed(SalesOrderStatus, {'pk': 4}, name=pgettext('billing-salesorder', 'Created'),  order=2)

        # ---------------------------
        def create_invoice_status(pk, name, **kwargs):
            create_if_needed(InvoiceStatus, {'pk': pk}, name=name, **kwargs)

        create_invoice_status(1, pgettext('billing-invoice', 'Draft'),      order=1, is_custom=False)  # Default status
        create_invoice_status(2, pgettext('billing-invoice', 'To be sent'), order=2, is_custom=False)
        if not already_populated:
            create_invoice_status(3, pgettext('billing-invoice', 'Sent'),            order=3, pending_payment=True)
            create_invoice_status(4, pgettext('billing-invoice', 'Resulted'),        order=5)
            create_invoice_status(5, pgettext('billing-invoice', 'Partly resulted'), order=4, pending_payment=True)
            create_invoice_status(6, _('Collection'),                                order=7)
            create_invoice_status(7, _('Resulted collection'),                       order=6)
            create_invoice_status(8, pgettext('billing-invoice', 'Canceled'),        order=8)

        # ---------------------------
        create_if_needed(CreditNoteStatus, {'pk': 1}, name=pgettext('billing-creditnote', 'Draft'), order=1, is_custom=False)
        if not already_populated:
            create_if_needed(CreditNoteStatus, {'pk': 2}, name=pgettext('billing-creditnote', 'Issued'),      order=2)
            create_if_needed(CreditNoteStatus, {'pk': 3}, name=pgettext('billing-creditnote', 'Consumed'),    order=3)
            create_if_needed(CreditNoteStatus, {'pk': 4}, name=pgettext('billing-creditnote', 'Out of date'), order=4)

        # ---------------------------
        EntityFilter.create(
                'billing-invoices_unpaid', name=_('Invoices unpaid'),
                model=Invoice, user='******',
                conditions=[EntityFilterCondition.build_4_field(
                                    model=Invoice,
                                    operator=EntityFilterCondition.EQUALS,
                                    name='status__pending_payment', values=[True],
                                ),
                           ],
            )
        EntityFilter.create(
                'billing-invoices_unpaid_late', name=_('Invoices unpaid and late'),
                model=Invoice, user='******',
                conditions=[EntityFilterCondition.build_4_field(
                                    model=Invoice,
                                    operator=EntityFilterCondition.EQUALS,
                                    name='status__pending_payment', values=[True],
                                ),
                            EntityFilterCondition.build_4_date(
                                    model=Invoice,
                                    name='expiration_date', date_range='in_past',
                                ),
                           ],
            )
        current_year_invoice_filter = EntityFilter.create(
                'billing-current_year_invoices', name=_('Current year invoices'),
                model=Invoice, user='******',
                conditions=[EntityFilterCondition.build_4_date(
                                    model=Invoice,
                                    name='issuing_date', date_range='current_year',
                                ),
                           ],
            )
        current_year_unpaid_invoice_filter = EntityFilter.create(
                'billing-current_year_unpaid_invoices',
                name=_('Current year and unpaid invoices'),
                model=Invoice, user='******',
                conditions=[EntityFilterCondition.build_4_date(
                                    model=Invoice,
                                    name='issuing_date', date_range='current_year',
                                ),
                            EntityFilterCondition.build_4_field(
                                    model=Invoice,
                                    operator=EntityFilterCondition.EQUALS,
                                    name='status__pending_payment', values=[True],
                                ),
                           ],
            )

        # ---------------------------
        def create_hf(hf_pk, name, model, status=True):
            HeaderFilter.create(pk=hf_pk, name=name, model=model,
                                cells_desc=[(EntityCellRegularField, {'name': 'name'}),
                                            EntityCellRelation(model=model, rtype=rt_sub_bill_received),
                                            (EntityCellRegularField, {'name': 'number'}),
                                            (EntityCellRegularField, {'name': 'status'}) if status else None,
                                            (EntityCellRegularField, {'name': 'total_no_vat'}),
                                            (EntityCellRegularField, {'name': 'issuing_date'}),
                                            (EntityCellRegularField, {'name': 'expiration_date'}),
                                           ],
                               )

        create_hf(constants.DEFAULT_HFILTER_INVOICE,  _('Invoice view'),     Invoice)
        create_hf(constants.DEFAULT_HFILTER_QUOTE,    _('Quote view'),       Quote)
        create_hf(constants.DEFAULT_HFILTER_ORDER,    _('Sales order view'), SalesOrder)
        create_hf(constants.DEFAULT_HFILTER_CNOTE,    _('Credit note view'), CreditNote)
        create_hf(constants.DEFAULT_HFILTER_TEMPLATE, _('Template view'),    TemplateBase, status=False)

        def create_hf_lines(hf_pk, name, model):
            build_cell = EntityCellRegularField.build
            HeaderFilter.create(pk=hf_pk, name=name, model=model,
                                cells_desc=[build_cell(model=model, name='on_the_fly_item'),
                                            build_cell(model=model, name='quantity'),
                                            build_cell(model=model, name='unit_price'),
                                           ]
                               )

        create_hf_lines('billing-hg_product_lines', _('Product lines view'), ProductLine)
        create_hf_lines('billing-hg_service_lines', _('Service lines view'), ServiceLine)

        # ---------------------------
        for model in (Invoice, CreditNote, Quote, SalesOrder):
            SearchConfigItem.create_if_needed(model, ['name', 'number', 'status__name'])

        for model in (ProductLine, ServiceLine):
            SearchConfigItem.create_if_needed(model, [], disabled=True)

        # ---------------------------
        SettingValue.objects.get_or_create(key_id=setting_keys.payment_info_key.id, defaults={'value': True})

        # ---------------------------
        if not already_populated:
            create_if_needed(QuoteStatus, {'pk': 1}, name=pgettext('billing-quote', 'Pending'),  order=2)  # Default status
            create_if_needed(QuoteStatus, {'pk': 2}, name=pgettext('billing-quote', 'Accepted'), order=3, won=True)
            create_if_needed(QuoteStatus, {'pk': 3}, name=pgettext('billing-quote', 'Rejected'), order=4)
            create_if_needed(QuoteStatus, {'pk': 4}, name=pgettext('billing-quote', 'Created'),  order=1)

            # ---------------------------
            create_if_needed(SettlementTerms, {'pk': 1}, name=_('30 days'))
            create_if_needed(SettlementTerms, {'pk': 2}, name=_('Cash'))
            create_if_needed(SettlementTerms, {'pk': 3}, name=_('45 days'))
            create_if_needed(SettlementTerms, {'pk': 4}, name=_('60 days'))
            create_if_needed(SettlementTerms, {'pk': 5}, name=_('30 days, end month the 10'))

            # ---------------------------
            create_if_needed(AdditionalInformation, {'pk': 1}, name=_('Trainer accreditation'),
                             description=_('being certified trainer courses could be supported by your OPCA')
                            )

            # ---------------------------
            create_bmi = ButtonMenuItem.create_if_needed
            create_bmi(pk='billing-generate_invoice_number', model=Invoice, button=buttons.GenerateInvoiceNumberButton, order=0)

            create_bmi(pk='billing-quote_orga_button',      model=Organisation, button=buttons.AddQuoteButton,      order=100)
            create_bmi(pk='billing-salesorder_orga_button', model=Organisation, button=buttons.AddSalesOrderButton, order=101)
            create_bmi(pk='billing-invoice_orga_button',    model=Organisation, button=buttons.AddInvoiceButton,    order=102)

            create_bmi(pk='billing-quote_contact_button',      model=Contact, button=buttons.AddQuoteButton,      order=100)
            create_bmi(pk='billing-salesorder_contact_button', model=Contact, button=buttons.AddSalesOrderButton, order=101)
            create_bmi(pk='billing-invoice_contact_button',    model=Contact, button=buttons.AddInvoiceButton,    order=102)

            # ---------------------------
            get_ct = ContentType.objects.get_for_model
            create_cbci = CustomBrickConfigItem.objects.create
            build_cell = EntityCellRegularField.build

            def build_cells(model, *extra_cells):
                return [
                    build_cell(model, 'name'),
                    build_cell(model, 'number'),
                    build_cell(model, 'issuing_date'),
                    build_cell(model, 'expiration_date'),
                    build_cell(model, 'discount'),
                    build_cell(model, 'additional_info'),
                    build_cell(model, 'payment_terms'),
                    build_cell(model, 'currency'),
                ] + list(extra_cells) + [
                    build_cell(model, 'comment'),
                    # --
                    build_cell(model, 'created'),
                    build_cell(model, 'modified'),
                    build_cell(model, 'user'),
                ]

            cbci_invoice = create_cbci(id='billing-invoice_info',
                                       name=_('Invoice information'),
                                       content_type=get_ct(Invoice),
                                       cells=build_cells(Invoice,
                                                         build_cell(Invoice, 'status'),
                                                         build_cell(Invoice, 'payment_type'),
                                                        ),
                                      )
            cbci_c_note   = create_cbci(id='billing-creditnote_info',
                                        name=_('Credit note information'),
                                        content_type=get_ct(CreditNote),
                                        cells=build_cells(CreditNote, build_cell(CreditNote, 'status')),
                                       )
            cbci_quote   = create_cbci(id='billing-quote_info',
                                       name=_('Quote information'),
                                       content_type=get_ct(Quote),
                                       cells=build_cells(Quote,
                                                         build_cell(Quote, 'status'),
                                                         build_cell(Quote, 'acceptation_date'),
                                                        ),
                                      )
            cbci_s_order = create_cbci(id='billing-salesorder_info',
                                       name=_('Salesorder information'),
                                       content_type=get_ct(SalesOrder),
                                       cells=build_cells(SalesOrder, build_cell(SalesOrder, 'status')),
                                      )
            cbci_tbase   = create_cbci(id='billing-templatebase_info',
                                       name=pgettext('billing', 'Template information'),
                                       content_type=get_ct(TemplateBase),
                                       cells=build_cells(TemplateBase,
                                                         EntityCellFunctionField.build(TemplateBase, 'get_verbose_status'),
                                                        ),
                                      )

            models_4_blocks = [(Invoice,      cbci_invoice, True),  # Boolean -> insert CreditNote block
                               (CreditNote,   cbci_c_note,  False),
                               (Quote,        cbci_quote,   True),
                               (SalesOrder,   cbci_s_order, True),
                               (TemplateBase, cbci_tbase,   False),
                              ]
            create_bdl = BrickDetailviewLocation.create_if_needed
            TOP   = BrickDetailviewLocation.TOP
            LEFT  = BrickDetailviewLocation.LEFT
            RIGHT = BrickDetailviewLocation.RIGHT

            for model, cbci, has_credit_notes in models_4_blocks:
                create_bdl(brick_id=bricks.ProductLinesBrick.id_, order=10, zone=TOP, model=model)
                create_bdl(brick_id=bricks.ServiceLinesBrick.id_, order=20, zone=TOP, model=model)

                if has_credit_notes:
                    create_bdl(brick_id=bricks.CreditNotesBrick.id_, order=30, zone=TOP, model=model)

                create_bdl(brick_id=cbci.generate_id(),                        order=5,   zone=LEFT, model=model)
                create_bdl(brick_id=core_bricks.CustomFieldsBrick.id_,         order=40,  zone=LEFT, model=model)
                create_bdl(brick_id=bricks.BillingPaymentInformationBrick.id_, order=60,  zone=LEFT, model=model)
                create_bdl(brick_id=bricks.BillingPrettyAddressBrick.id_,      order=70,  zone=LEFT, model=model)
                create_bdl(brick_id=core_bricks.PropertiesBrick.id_,           order=450, zone=LEFT, model=model)
                create_bdl(brick_id=core_bricks.RelationsBrick.id_,            order=500, zone=LEFT, model=model)

                create_bdl(brick_id=bricks.TargetBrick.id_,       order=2,  zone=RIGHT, model=model)
                create_bdl(brick_id=bricks.TotalBrick.id_,        order=3,  zone=RIGHT, model=model)
                create_bdl(brick_id=core_bricks.HistoryBrick.id_, order=20, zone=RIGHT, model=model)

            if apps.is_installed('creme.assistants'):
                logger.info('Assistants app is installed => we use the assistants blocks on detail views')

                from creme.assistants.bricks import AlertsBrick, MemosBrick, TodosBrick, UserMessagesBrick

                for t in models_4_blocks:
                    model = t[0]
                    create_bdl(brick_id=TodosBrick.id_,        order=100, zone=RIGHT, model=model)
                    create_bdl(brick_id=MemosBrick.id_,        order=200, zone=RIGHT, model=model)
                    create_bdl(brick_id=AlertsBrick.id_,       order=300, zone=RIGHT, model=model)
                    create_bdl(brick_id=UserMessagesBrick.id_, order=400, zone=RIGHT, model=model)

            if apps.is_installed('creme.documents'):
                # logger.info('Documents app is installed => we use the documents block on detail views')

                from creme.documents.bricks import LinkedDocsBrick

                for t in models_4_blocks:
                    create_bdl(brick_id=LinkedDocsBrick.id_, order=600, zone=RIGHT, model=t[0])

            create_bdl(brick_id=bricks.PaymentInformationBrick.id_, order=300, zone=LEFT,  model=Organisation)
            create_bdl(brick_id=bricks.ReceivedInvoicesBrick.id_,   order=14,  zone=RIGHT, model=Organisation)
            create_bdl(brick_id=bricks.ReceivedQuotesBrick.id_,     order=18,  zone=RIGHT, model=Organisation)

            # ---------------------------
            if apps.is_installed('creme.reports'):
                logger.info('Reports app is installed => we create 2 billing reports, with 3 graphs, and related blocks in home')
                self.create_reports(rt_sub_bill_received,
                                    current_year_invoice_filter,
                                    current_year_unpaid_invoice_filter,
                                   )