Beispiel #1
0
    def register_billing(self):
        from creme.creme_core.models import RelationType

        from creme import billing
        from creme.billing.registry import relationtype_converter

        from . import constants

        get_rtype = RelationType.objects.get

        try:
            linked_salesorder = get_rtype(
                id=constants.REL_SUB_LINKED_SALESORDER)
            linked_invoice = get_rtype(id=constants.REL_SUB_LINKED_INVOICE)
            linked_quote = get_rtype(id=constants.REL_SUB_LINKED_QUOTE)
        except Exception as e:
            logger.info(
                'A problem occurred: %s\n'
                'It can happen during unit tests or during the "populate" phase. '
                'Otherwise, has the database correctly been populated?', e)
        else:
            Invoice = billing.get_invoice_model()
            Quote = billing.get_quote_model()
            SalesOrder = billing.get_sales_order_model()

            register_rtype = relationtype_converter.register
            register_rtype(Quote, linked_quote, SalesOrder, linked_salesorder)
            register_rtype(Quote, linked_quote, Invoice, linked_invoice)
            register_rtype(SalesOrder, linked_salesorder, Invoice,
                           linked_invoice)
Beispiel #2
0
class LatexExportEngine(base.BillingExportEngine):
    id = base.BillingExportEngine.generate_id('billing', 'latex')

    FLAVOURS_INFO = {
        l10n.FR: {
            'fr_FR': {
                # Ubuntu packages needed to render correctly this theme
                #  - texlive-latex-recommended
                #  - texlive-fonts-extra
                #  - texlive-lang-french
                #  - texlive-latex-extra
                'clear':
                LatexTheme(
                    verbose_name='.pdf - LateX - Thème clair (France)',
                    templates={
                        billing.get_invoice_model():
                        'billing/export/latex/FR/fr_FR/clear/invoice.tex',
                        billing.get_credit_note_model():
                        'billing/export/latex/FR/fr_FR/clear/credit_note.tex',
                        billing.get_quote_model():
                        'billing/export/latex/FR/fr_FR/clear/quote.tex',
                        billing.get_sales_order_model():
                        'billing/export/latex/FR/fr_FR/clear/sales_order.tex',
                        billing.get_template_base_model():
                        'billing/export/latex/FR/fr_FR/clear/template.tex',
                    },
                    # TODO: by ContentType ?
                    screenshots=['billing/sample_latex.png'],
                ),
            },
        },
    }

    @property
    def flavours(self):
        model = self.model

        for country, languages in self.FLAVOURS_INFO.items():
            for language, themes in languages.items():
                for theme_id, theme_info in themes.items():
                    if model in theme_info.templates:
                        yield base.ExporterFlavour(country, language, theme_id)

    def exporter(self, flavour):
        try:
            theme = self.FLAVOURS_INFO[flavour.country][flavour.language][
                flavour.theme]
            template_path = theme.templates[self.model]
        except KeyError as e:
            logger.warning('LatexExportEngine.exporter(): invalid data [%s].',
                           e)
            return None

        return LatexExporter(
            verbose_name=theme.verbose_name,
            engine=self,
            flavour=flavour,
            template_path=template_path,
            screenshots=theme.screenshots,
        )
Beispiel #3
0
class WeasyprintExportEngine(base.BillingExportEngine):
    id = base.BillingExportEngine.generate_id('billing', 'weasyprint')

    FLAVOURS_INFO = {
        l10n.FR: {
            'fr_FR': {
                'mint':
                WeasyprintTheme(
                    verbose_name='.pdf - WeasyPrint - Thème Menthe (France)',
                    # TODO: attribute "directory" ?
                    templates={
                        billing.get_invoice_model():
                        'billing/export/weasyprint/FR/fr_FR/mint/invoice.html',
                        billing.get_credit_note_model():
                        'billing/export/weasyprint/FR/fr_FR/mint/credit_note.html',
                        billing.get_quote_model():
                        'billing/export/weasyprint/FR/fr_FR/mint/quote.html',
                        billing.get_sales_order_model():
                        'billing/export/weasyprint/FR/fr_FR/mint/sales_order.html',
                        billing.get_template_base_model():
                        'billing/export/weasyprint/FR/fr_FR/mint/template.html',
                    },
                    css='billing/export/weasyprint/FR/fr_FR/mint/mint.css',
                    # TODO: by ContentType ?
                    screenshots=['billing/sample_weasyprint.png'],
                ),
            },
        },
    }

    @property
    def flavours(self):
        model = self.model

        for country, languages in self.FLAVOURS_INFO.items():
            for language, themes in languages.items():
                for theme_id, theme_info in themes.items():
                    if model in theme_info.templates:
                        yield base.ExporterFlavour(country, language, theme_id)

    def exporter(self, flavour):
        # TODO: factorise with LateX ?
        try:
            theme = self.FLAVOURS_INFO[flavour.country][flavour.language][
                flavour.theme]
            template_path = theme.templates[self.model]
        except KeyError as e:
            logger.warning(
                'WeasyprintExportEngine.exporter(): invalid data [%s].', e)
            return None

        return WeasyprintExporter(
            verbose_name=theme.verbose_name,
            engine=self,
            flavour=flavour,
            html_template_path=template_path,
            css_template_path=theme.css,
            screenshots=theme.screenshots,
        )
Beispiel #4
0
class Xhtml2pdfExportEngine(base.BillingExportEngine):
    id = base.BillingExportEngine.generate_id('billing', 'xhtml2pdf')

    FLAVOURS_INFO = {
        l10n.FR: {
            'fr_FR': {
                'cappuccino':
                Xhtml2pdfTheme(
                    verbose_name='.pdf - Xhtml2pdf - Thème Cappuccino (France)',
                    # description='...',  # TODO ?
                    # TODO: attribute "directory" ?
                    templates={
                        billing.get_invoice_model():
                        'billing/export/xhtml2pdf/FR/fr_FR/cappuccino/invoice.html',
                        billing.get_credit_note_model():
                        'billing/export/xhtml2pdf/FR/fr_FR/cappuccino/credit_note.html',
                        billing.get_quote_model():
                        'billing/export/xhtml2pdf/FR/fr_FR/cappuccino/quote.html',
                        billing.get_sales_order_model():
                        'billing/export/xhtml2pdf/FR/fr_FR/cappuccino/sales_order.html',
                        billing.get_template_base_model():
                        'billing/export/xhtml2pdf/FR/fr_FR/cappuccino/template.html',
                    },
                    # TODO: by ContentType ?
                    screenshots=['billing/sample_xhtml2pdf.png'],
                ),
            },
        },
    }

    @property
    def flavours(self):
        model = self.model

        for country, languages in self.FLAVOURS_INFO.items():
            for language, themes in languages.items():
                for theme_id, theme_info in themes.items():
                    if model in theme_info.templates:
                        yield base.ExporterFlavour(country, language, theme_id)

    def exporter(self, flavour):
        try:
            theme = self.FLAVOURS_INFO[flavour.country][flavour.language][
                flavour.theme]
            template_path = theme.templates[self.model]
        except KeyError as e:
            logger.warning(
                'Xhtml2pdfExportEngine.exporter(): invalid data [%s].', e)
            return None

        return Xhtml2pdfExporter(
            verbose_name=theme.verbose_name,
            engine=self,
            flavour=flavour,
            template_path=template_path,
            screenshots=theme.screenshots,
        )
Beispiel #5
0
        def get_current_quote_ids(self):
            from django.contrib.contenttypes.models import ContentType

            from creme.billing import get_quote_model

            ct = ContentType.objects.get_for_model(get_quote_model())

            return Relation.objects.filter(object_entity=self.id,
                                           type=constants.REL_SUB_CURRENT_DOC,
                                           subject_entity__entity_type=ct,
                                          ) \
                                   .values_list('subject_entity_id', flat=True)
Beispiel #6
0
class Export(base.EntityRelatedMixin, base.CheckedView):
    permissions = 'billing'
    entity_classes = [
        billing.get_invoice_model(),
        billing.get_credit_note_model(),
        billing.get_quote_model(),
        billing.get_sales_order_model(),
        billing.get_template_base_model(),
    ]

    def check_related_entity_permissions(self, entity, user):
        has_perm = user.has_perm_to_view_or_die
        has_perm(entity)
        has_perm(entity.source)
        has_perm(entity.target)

    def get(self, request, *args, **kwargs):
        entity = self.get_related_entity()
        config_item = get_object_or_404(
            ExporterConfigItem,
            content_type=entity.entity_type,
        )

        engine_id = config_item.engine_id
        if not engine_id:
            raise ConflictError(_(
                'The engine is not configured ; '
                'go to the configuration of the app «Billing».'
            ))

        exporter = BillingExportEngineManager().exporter(
            engine_id=engine_id,
            flavour_id=config_item.flavour_id,
            model=type(entity),
        )

        if exporter is None:
            raise ConflictError(_(
                'The configured exporter is invalid ; '
                'go to the configuration of the app «Billing».'
            ))

        export_result = exporter.export(
            entity=entity, user=request.user,
        )

        if isinstance(export_result, HttpResponse):
            return export_result

        assert export_result, FileRef

        return HttpResponseRedirect(export_result.get_download_absolute_url())
Beispiel #7
0
    def all_apps_ready(self):
        from creme import billing

        self.CreditNote = billing.get_credit_note_model()
        self.Invoice = billing.get_invoice_model()
        self.Quote = billing.get_quote_model()
        self.SalesOrder = billing.get_sales_order_model()
        self.TemplateBase = billing.get_template_base_model()
        self.ProductLine = billing.get_product_line_model()
        self.ServiceLine = billing.get_service_line_model()
        super().all_apps_ready()

        self.register_billing_algorithm()
        self.register_billing_lines()

        from . import signals
def set_simple_conf_billing(sender, instance, **kwargs):
    if not instance.is_managed:
        return

    if ConfigBillingAlgo.objects.filter(organisation=instance).exists():
        return

    get_ct = ContentType.objects.get_for_model

    for model, prefix in [(billing.get_quote_model(),       settings.QUOTE_NUMBER_PREFIX),
                          (billing.get_invoice_model(),     settings.INVOICE_NUMBER_PREFIX),
                          (billing.get_sales_order_model(), settings.SALESORDER_NUMBER_PREFIX),
                         ]:
        ct = get_ct(model)
        ConfigBillingAlgo.objects.create(organisation=instance, ct=ct, name_algo=SimpleBillingAlgo.ALGO_NAME)
        SimpleBillingAlgo.objects.create(organisation=instance, last_number=0, prefix=prefix, ct=ct)
Beispiel #9
0
    def test_populate(self):
        # self.assertEqual(REL_SUB_SOLD, REL_SUB_SOLD_BY)
        # self.assertEqual(REL_OBJ_SOLD, REL_OBJ_SOLD_BY)

        sold = self.get_relationtype_or_fail(
            REL_SUB_SOLD,
            [Contact, Organisation],
            [Product, Service],
        )
        self.assertEqual(REL_OBJ_SOLD, sold.symmetric_type_id)

        complete_goal = self.get_object_or_fail(RelationType,
                                                id=REL_SUB_COMPLETE_GOAL)
        self.assertEqual(REL_OBJ_COMPLETE_GOAL,
                         complete_goal.symmetric_type_id)
        self.assertEqual(
            [Act],
            [ct.model_class() for ct in complete_goal.object_ctypes.all()])
        subject_models = {
            ct.model_class()
            for ct in complete_goal.subject_ctypes.all()
        }
        self.assertIn(Contact, subject_models)
        self.assertIn(Organisation, subject_models)

        if apps.is_installed('creme.billing'):
            from creme import billing
            self.assertIn(billing.get_invoice_model(), subject_models)
            self.assertIn(billing.get_quote_model(), subject_models)
            self.assertIn(billing.get_sales_order_model(), subject_models)

            self.assertNotIn(billing.get_product_line_model(), subject_models)
            self.assertNotIn(billing.get_service_line_model(), subject_models)
            self.assertNotIn(billing.get_template_base_model(), subject_models)

        self.get_propertytype_or_fail(PROP_IS_A_SALESMAN, [Contact])

        self.assertEqual(3, ActType.objects.count())

        self.get_object_or_fail(MarketSegment, property_type=None)
Beispiel #10
0
    def register_menu(self, creme_menu):
        Opportunity = self.Opportunity
        URLItem = creme_menu.URLItem
        container = creme_menu.get('features') \
                              .get_or_create(creme_menu.ContainerItem, 'opportunities-commercial', priority=30,
                                             defaults={'label': _('Commercial')},
                                            ) \
                              .add(URLItem.list_view('opportunities-opportunities', model=Opportunity), priority=10)
        creme_menu.get('creation', 'main_entities') \
                  .add(URLItem.creation_view('opportunities-create_opportunity', model=Opportunity), priority=50)

        create_any = creme_menu.get('creation', 'any_forms') \
                               .get_or_create_group('opportunities-commercial', _('Commercial'), priority=20) \
                               .add_link('opportunities-create_opportunity', Opportunity, priority=3)

        if self.billing_installed:
            from creme.billing import get_quote_model
            Quote = get_quote_model()

            container.add(URLItem.list_view('opportunities-quotes',
                                            model=Quote),
                          priority=20)
            create_any.add_link('create_quote', Quote, priority=20)
from django.utils.encoding import smart_str
from django.utils.translation import gettext as _

from creme.creme_core.auth import decorators as auth_dec
from creme.creme_core.core.exceptions import ConflictError
from creme.creme_core.models import CremeEntity, FileRef
from creme.creme_core.utils.file_handling import FileCreator
from creme.creme_core.utils.secure_filename import secure_filename

from creme import billing

logger = logging.getLogger(__name__)
TEMPLATE_PATHS = {
    billing.get_invoice_model(): 'billing/templates/invoice.tex',
    billing.get_credit_note_model(): 'billing/templates/billings.tex',
    billing.get_quote_model(): 'billing/templates/billings.tex',
    billing.get_sales_order_model(): 'billing/templates/billings.tex',
    billing.get_template_base_model(): 'billing/templates/billings.tex',
}


@auth_dec.login_required
@auth_dec.permission_required('billing')
def export_as_pdf(request, base_id):
    entity = get_object_or_404(CremeEntity, pk=base_id).get_real_entity()

    has_perm = request.user.has_perm_to_view_or_die
    has_perm(entity)

    template_path = TEMPLATE_PATHS.get(entity.__class__)
    if template_path is None:
Beispiel #12
0
# -*- coding: utf-8 -*-

from creme import billing
from .forms.templatebase import TemplateBaseCreateForm

TemplateBase = billing.get_template_base_model()
to_register = (
    (billing.get_invoice_model(), TemplateBase, TemplateBaseCreateForm),
    (billing.get_quote_model(), TemplateBase, TemplateBaseCreateForm),
    (billing.get_sales_order_model(), TemplateBase, TemplateBaseCreateForm),
    (billing.get_credit_note_model(), TemplateBase, TemplateBaseCreateForm),
)
Beispiel #13
0
from django.db.models import signals
from django.db.transaction import atomic
from django.dispatch import receiver

from creme import billing, persons
from creme.creme_core import signals as core_signals
from creme.creme_core.models import Relation
from creme.persons import workflow

from . import constants
from .models import ConfigBillingAlgo, SimpleBillingAlgo

Organisation = persons.get_organisation_model()

Invoice = billing.get_invoice_model()
Quote = billing.get_quote_model()


@receiver(signals.post_save, sender=Organisation)
def set_simple_conf_billing(sender, instance, **kwargs):
    if not instance.is_managed:
        return

    if ConfigBillingAlgo.objects.filter(organisation=instance).exists():
        return

    get_ct = ContentType.objects.get_for_model

    for model, prefix in [
        (Quote, settings.QUOTE_NUMBER_PREFIX),
        (Invoice, settings.INVOICE_NUMBER_PREFIX),
Beispiel #14
0
# -*- coding: utf-8 -*-

from creme import billing

from .custom_forms import BTEMPLATE_CREATION_CFORM

TemplateBase = billing.get_template_base_model()
to_register = (
    (billing.get_invoice_model(), TemplateBase, BTEMPLATE_CREATION_CFORM),
    (billing.get_quote_model(), TemplateBase, BTEMPLATE_CREATION_CFORM),
    (billing.get_sales_order_model(), TemplateBase, BTEMPLATE_CREATION_CFORM),
    (billing.get_credit_note_model(), TemplateBase, BTEMPLATE_CREATION_CFORM),
)
Beispiel #15
0
    def populate(self):
        already_populated = RelationType.objects.filter(
            pk=constants.REL_SUB_TARGETS).exists()

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

        # ---------------------------
        create_rtype = RelationType.create
        rt_sub_targets = create_rtype(
            (
                constants.REL_SUB_TARGETS,
                _('targets the organisation/contact'),
                [Opportunity],
            ),
            (
                constants.REL_OBJ_TARGETS,
                _('targeted by the opportunity'),
                [Organisation, Contact],
            ),
            is_internal=True,
            minimal_display=(True, True),
        )[0]
        rt_obj_emit_orga = create_rtype(
            (
                constants.REL_SUB_EMIT_ORGA,
                _('has generated the opportunity'),
                [Organisation],
            ),
            (
                constants.REL_OBJ_EMIT_ORGA,
                _('has been generated by'),
                [Opportunity],
            ),
            is_internal=True,
            minimal_display=(True, True),
        )[1]
        create_rtype((constants.REL_SUB_LINKED_PRODUCT,
                      _('is linked to the opportunity'), [Product]),
                     (constants.REL_OBJ_LINKED_PRODUCT,
                      _('concerns the product'), [Opportunity]))
        create_rtype(
            (constants.REL_SUB_LINKED_SERVICE,
             _('is linked to the opportunity'), [Service]),
            (constants.REL_OBJ_LINKED_SERVICE, _('concerns the service'),
             [Opportunity]),
        )
        create_rtype(
            (constants.REL_SUB_LINKED_CONTACT,
             _('involves in the opportunity'), [Contact]),
            (constants.REL_OBJ_LINKED_CONTACT, _('stages'), [Opportunity])),
        create_rtype(
            (constants.REL_SUB_RESPONSIBLE, _('is responsible for'), [Contact
                                                                      ]),
            (constants.REL_OBJ_RESPONSIBLE, _('has as responsible contact'),
             [Opportunity]),
        )

        if apps.is_installed('creme.activities'):
            logger.info('Activities app is installed'
                        ' => an Opportunity 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(Opportunity)

        if apps.is_installed('creme.billing'):
            logger.info(
                'Billing app is installed'
                ' => we create relationships between Opportunities & billing models'
            )

            from creme import billing

            Invoice = billing.get_invoice_model()
            Quote = billing.get_quote_model()
            SalesOrder = billing.get_sales_order_model()

            create_rtype(
                (
                    constants.REL_SUB_LINKED_SALESORDER,
                    _('is associate with the opportunity'),
                    [SalesOrder],
                ),
                (constants.REL_OBJ_LINKED_SALESORDER,
                 _('has generated the salesorder'), [Opportunity]),
            )
            create_rtype(
                (
                    constants.REL_SUB_LINKED_INVOICE,
                    pgettext('opportunities-invoice',
                             'generated for the opportunity'),
                    [Invoice],
                ),
                (
                    constants.REL_OBJ_LINKED_INVOICE,
                    _('has resulted in the invoice'),
                    [Opportunity],
                ),
            )
            create_rtype(
                (
                    constants.REL_SUB_LINKED_QUOTE,
                    pgettext('opportunities-quote',
                             'generated for the opportunity'),
                    [Quote],
                ),
                (
                    constants.REL_OBJ_LINKED_QUOTE,
                    _('has resulted in the quote'),
                    [Opportunity],
                ),
            )
            create_rtype(
                (
                    constants.REL_SUB_CURRENT_DOC,
                    _('is the current accounting document of'),
                    [SalesOrder, Invoice, Quote],
                ),
                (
                    constants.REL_OBJ_CURRENT_DOC,
                    _('has as current accounting document'),
                    [Opportunity],
                ),
            )

        # ---------------------------
        create_sv = SettingValue.objects.get_or_create
        create_sv(key_id=setting_keys.quote_key.id, defaults={'value': False})
        create_sv(key_id=setting_keys.target_constraint_key.id,
                  defaults={'value': True})
        create_sv(key_id=setting_keys.emitter_constraint_key.id,
                  defaults={'value': True})

        # ---------------------------
        create_efilter = partial(
            EntityFilter.objects.smart_update_or_create,
            model=Opportunity,
            user='******',
        )
        build_cond = partial(
            condition_handler.RegularFieldConditionHandler.build_condition,
            model=Opportunity,
        )
        create_efilter(
            'opportunities-opportunities_won',
            name=_('Opportunities won'),
            conditions=[
                build_cond(
                    operator=operators.EqualsOperator,
                    field_name='sales_phase__won',
                    values=[True],
                ),
            ],
        )
        create_efilter(
            'opportunities-opportunities_lost',
            name=_('Opportunities lost'),
            conditions=[
                build_cond(
                    operator=operators.EqualsOperator,
                    field_name='sales_phase__lost',
                    values=[True],
                ),
            ],
        )
        create_efilter(
            'opportunities-neither_won_nor_lost_opportunities',
            name=_('Neither won nor lost opportunities'),
            conditions=[
                build_cond(
                    operator=operators.EqualsNotOperator,
                    field_name='sales_phase__won',
                    values=[True],
                ),
                build_cond(
                    operator=operators.EqualsNotOperator,
                    field_name='sales_phase__lost',
                    values=[True],
                ),
            ],
        )

        # ---------------------------
        HeaderFilter.objects.create_if_needed(
            pk=constants.DEFAULT_HFILTER_OPPORTUNITY,
            model=Opportunity,
            name=_('Opportunity view'),
            cells_desc=[
                (EntityCellRegularField, {
                    'name': 'name'
                }),
                EntityCellRelation(model=Opportunity, rtype=rt_sub_targets),
                (EntityCellRegularField, {
                    'name': 'sales_phase'
                }),
                (EntityCellRegularField, {
                    'name': 'estimated_sales'
                }),
                (EntityCellRegularField, {
                    'name': 'made_sales'
                }),
                (EntityCellRegularField, {
                    'name': 'closing_date'
                }),
            ],
        )

        # ---------------------------
        common_groups_desc = [
            {
                'name': _('Description'),
                'layout': LAYOUT_DUAL_SECOND,
                'cells': [
                    (EntityCellRegularField, {
                        'name': 'description'
                    }),
                ],
            },
            {
                'name':
                _('Custom fields'),
                'layout':
                LAYOUT_DUAL_SECOND,
                'cells': [
                    (
                        EntityCellCustomFormSpecial,
                        {
                            'name':
                            EntityCellCustomFormSpecial.REMAINING_CUSTOMFIELDS
                        },
                    ),
                ],
            },
        ]
        common_field_names = [
            'reference',
            'estimated_sales',
            'made_sales',
            'currency',
            'sales_phase',
            'chance_to_win',
            'expected_closing_date',
            'closing_date',
            'origin',
            'first_action_date',
        ]

        CustomFormConfigItem.objects.create_if_needed(
            descriptor=custom_forms.OPPORTUNITY_CREATION_CFORM,
            groups_desc=[
                {
                    'name':
                    _('General information'),
                    'layout':
                    LAYOUT_DUAL_FIRST,
                    'cells': [
                        (EntityCellRegularField, {
                            'name': 'user'
                        }),
                        (EntityCellRegularField, {
                            'name': 'name'
                        }),
                        OppEmitterSubCell().into_cell(),
                        OppTargetSubCell().into_cell(),
                        *((EntityCellRegularField, {
                            'name': fname
                        }) for fname in common_field_names),
                        (
                            EntityCellCustomFormSpecial,
                            {
                                'name':
                                EntityCellCustomFormSpecial.
                                REMAINING_REGULARFIELDS
                            },
                        ),
                    ],
                },
                *common_groups_desc,
                {
                    'name':
                    _('Properties'),
                    'cells': [
                        (
                            EntityCellCustomFormSpecial,
                            {
                                'name':
                                EntityCellCustomFormSpecial.CREME_PROPERTIES
                            },
                        ),
                    ],
                },
                {
                    'name':
                    _('Relationships'),
                    'cells': [
                        (
                            EntityCellCustomFormSpecial,
                            {
                                'name': EntityCellCustomFormSpecial.RELATIONS
                            },
                        ),
                    ],
                },
            ],
        )
        CustomFormConfigItem.objects.create_if_needed(
            descriptor=custom_forms.OPPORTUNITY_EDITION_CFORM,
            groups_desc=[
                {
                    'name':
                    _('General information'),
                    'cells': [
                        (EntityCellRegularField, {
                            'name': 'user'
                        }),
                        (EntityCellRegularField, {
                            'name': 'name'
                        }),
                        OppTargetSubCell().into_cell(),
                        *((EntityCellRegularField, {
                            'name': fname
                        }) for fname in common_field_names),
                        (
                            EntityCellCustomFormSpecial,
                            {
                                'name':
                                EntityCellCustomFormSpecial.
                                REMAINING_REGULARFIELDS
                            },
                        ),
                    ],
                },
                *common_groups_desc,
            ],
        )

        # ---------------------------
        SearchConfigItem.objects.create_if_needed(
            Opportunity,
            ['name', 'made_sales', 'sales_phase__name', 'origin__name'],
        )

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

            MenuConfigItem.objects.create(
                entry_id=menu.OpportunitiesEntry.id,
                order=10,
                parent=container,
            )

            creations = MenuConfigItem.objects.filter(
                entry_id=ContainerEntry.id,
                entry_data={
                    'label': _('+ Creation')
                },
            ).first()
            if creations is not None:
                MenuConfigItem.objects.create(
                    entry_id=menu.OpportunityCreationEntry.id,
                    order=30,
                    parent=creations,
                )

        # ---------------------------
        if not already_populated:
            create_sphase = SalesPhase.objects.create
            create_sphase(order=1, name=_('Forthcoming'))
            create_sphase(order=4,
                          name=pgettext('opportunities-sales_phase',
                                        'Abandoned'))
            create_sphase(order=5,
                          name=pgettext('opportunities-sales_phase', 'Won'),
                          won=True)
            create_sphase(order=6,
                          name=pgettext('opportunities-sales_phase', 'Lost'),
                          lost=True)
            create_sphase(order=3, name=_('Under negotiation'))
            create_sphase(order=2, name=_('In progress'))

            # ---------------------------
            create_origin = Origin.objects.create
            create_origin(name=pgettext('opportunities-origin', 'None'))
            create_origin(name=_('Web site'))
            create_origin(name=_('Mouth'))
            create_origin(name=_('Show'))
            create_origin(name=_('Direct email'))
            create_origin(name=_('Direct phone call'))
            create_origin(name=_('Employee'))
            create_origin(name=_('Partner'))
            create_origin(name=pgettext('opportunities-origin', 'Other'))

            # ---------------------------
            create_button = ButtonMenuItem.objects.create_if_needed
            create_button(
                model=Organisation,
                button=LinkedOpportunityButton,
                order=30,
            )
            create_button(
                model=Contact,
                button=LinkedOpportunityButton,
                order=30,
            )

            # ---------------------------
            LEFT = BrickDetailviewLocation.LEFT
            RIGHT = BrickDetailviewLocation.RIGHT

            build_cell = EntityCellRegularField.build
            cbci = CustomBrickConfigItem.objects.create(
                id='opportunities-complementary',
                name=_('Opportunity complementary information'),
                content_type=Opportunity,
                cells=[
                    build_cell(Opportunity, 'reference'),
                    build_cell(Opportunity, 'currency'),
                    build_cell(Opportunity, 'chance_to_win'),
                    build_cell(Opportunity, 'expected_closing_date'),
                    build_cell(Opportunity, 'closing_date'),
                    build_cell(Opportunity, 'origin'),
                    build_cell(Opportunity, 'first_action_date'),
                    build_cell(Opportunity, 'description'),
                ],
            )

            BrickDetailviewLocation.objects.multi_create(
                defaults={
                    'model': Opportunity,
                    'zone': LEFT
                },
                data=[
                    {
                        'brick': bricks.OpportunityCardHatBrick,
                        'order': 1,
                        'zone': BrickDetailviewLocation.HAT,
                    },
                    {
                        'brick': cbci.brick_id,
                        'order': 5
                    },
                    {
                        'brick': core_bricks.CustomFieldsBrick,
                        'order': 40
                    },
                    {
                        'brick': bricks.BusinessManagersBrick,
                        'order': 60
                    },
                    {
                        'brick': bricks.LinkedContactsBrick,
                        'order': 62
                    },
                    {
                        'brick': bricks.LinkedProductsBrick,
                        'order': 64
                    },
                    {
                        'brick': bricks.LinkedServicesBrick,
                        'order': 66
                    },
                    {
                        'brick': core_bricks.PropertiesBrick,
                        'order': 450
                    },
                    {
                        'brick': core_bricks.RelationsBrick,
                        'order': 500
                    },
                    {
                        'brick': bricks.OppTargetBrick,
                        'order': 1,
                        'zone': RIGHT
                    },
                    {
                        'brick': bricks.OppTotalBrick,
                        'order': 2,
                        'zone': RIGHT
                    },
                    {
                        'brick': core_bricks.HistoryBrick,
                        'order': 20,
                        'zone': RIGHT
                    },
                ],
            )

            if apps.is_installed('creme.activities'):
                logger.info(
                    'Activities app is installed'
                    ' => we use the "Future activities" & "Past activities" blocks'
                )

                from creme.activities import bricks as act_bricks

                BrickDetailviewLocation.objects.multi_create(
                    defaults={
                        'model': Opportunity,
                        'zone': RIGHT
                    },
                    data=[
                        {
                            'brick': act_bricks.FutureActivitiesBrick,
                            'order': 20
                        },
                        {
                            'brick': act_bricks.PastActivitiesBrick,
                            'order': 21
                        },
                    ],
                )

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

                from creme.assistants import bricks as a_bricks

                BrickDetailviewLocation.objects.multi_create(
                    defaults={
                        'model': Opportunity,
                        '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': 500
                        },
                    ],
                )

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

                from creme.documents.bricks import LinkedDocsBrick

                BrickDetailviewLocation.objects.create_if_needed(
                    brick=LinkedDocsBrick,
                    order=600,
                    zone=RIGHT,
                    model=Opportunity,
                )

            if apps.is_installed('creme.billing'):
                logger.info('Billing app is installed'
                            ' => we use the billing blocks on detail view')

                BrickDetailviewLocation.objects.multi_create(
                    defaults={
                        'model': Opportunity,
                        'zone': LEFT
                    },
                    data=[
                        {
                            'brick': bricks.QuotesBrick,
                            'order': 70
                        },
                        {
                            'brick': bricks.SalesOrdersBrick,
                            'order': 72
                        },
                        {
                            'brick': bricks.InvoicesBrick,
                            'order': 74
                        },
                    ],
                )

            if apps.is_installed('creme.emails'):
                logger.info('Emails app is installed'
                            ' => we use the emails blocks on detail view')

                from creme.emails.bricks import MailsHistoryBrick

                BrickDetailviewLocation.objects.create_if_needed(
                    brick=MailsHistoryBrick,
                    order=600,
                    zone=RIGHT,
                    model=Opportunity,
                )

            BrickDetailviewLocation.objects.create_if_needed(
                brick=bricks.TargettingOpportunitiesBrick,
                order=16,
                zone=RIGHT,
                model=Organisation,
            )

            # ---------------------------
            if apps.is_installed('creme.reports'):
                logger.info(
                    'Reports app is installed'
                    ' => we create an Opportunity report, with 2 graphs, and related blocks'
                )
                self.create_report_n_graphes(rt_obj_emit_orga)
Beispiel #16
0
    from creme.billing.models import (
        CreditNoteStatus,
        InvoiceStatus,
        QuoteStatus,
        SalesOrderStatus,
    )
    from creme.billing.tests.base import (
        skipIfCustomCreditNote,
        skipIfCustomInvoice,
        skipIfCustomQuote,
        skipIfCustomSalesOrder,
    )

    CreditNote = get_credit_note_model()
    Invoice = get_invoice_model()
    Quote = get_quote_model()
    SalesOrder = get_sales_order_model()
    TemplateBase = get_template_base_model()
else:
    from unittest import skip

    def skipIfCustomCreditNote(test_func):
        return skip('App "billing" not installed')(test_func)

    def skipIfCustomInvoice(test_func):
        return skip('App "billing" not installed')(test_func)

    def skipIfCustomQuote(test_func):
        return skip('App "billing" not installed')(test_func)

    def skipIfCustomSalesOrder(test_func):
Beispiel #17
0
from django.utils.translation import gettext as _

from creme import billing
from creme.billing.constants import REL_SUB_BILL_ISSUED, REL_SUB_BILL_RECEIVED
from creme.creme_core.http import is_ajax
from creme.creme_core.models import Relation, SettingValue, Vat
from creme.creme_core.views.generic import base
from creme.creme_core.views.relation import RelationsObjectsSelectionPopup
from creme.persons import workflow
from creme.products import get_product_model

from .. import constants, get_opportunity_model
from ..setting_keys import emitter_constraint_key, target_constraint_key

Invoice = billing.get_invoice_model()
Quote = billing.get_quote_model()
SalesOrder = billing.get_sales_order_model()
ProductLine = billing.get_product_line_model()
ServiceLine = billing.get_service_line_model()
Opportunity = get_opportunity_model()


class CurrentQuoteSetting(base.CheckedView):
    permissions = 'opportunities'

    action_url_kwarg = 'action'
    opp_id_url_kwarg = 'opp_id'
    quote_id_url_kwarg = 'quote_id'

    rtype_id = constants.REL_SUB_CURRENT_DOC
Beispiel #18
0
    def populate(self):
        already_populated = RelationType.objects.filter(
            pk=constants.REL_SUB_TARGETS).exists()

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

        # ---------------------------
        create_rtype = RelationType.create
        rt_sub_targets = create_rtype(
            (constants.REL_SUB_TARGETS, _('targets the organisation/contact'),
             [Opportunity]),
            (constants.REL_OBJ_TARGETS, _('targeted by the opportunity'),
             [Organisation, Contact]),
            is_internal=True,
            minimal_display=(True, True),
        )[0]
        rt_obj_emit_orga = create_rtype(
            (constants.REL_SUB_EMIT_ORGA, _('has generated the opportunity'),
             [Organisation]),
            (constants.REL_OBJ_EMIT_ORGA, _('has been generated by'),
             [Opportunity]),
            is_internal=True,
            minimal_display=(True, True),
        )[1]
        create_rtype((constants.REL_SUB_LINKED_PRODUCT,
                      _('is linked to the opportunity'), [Product]),
                     (constants.REL_OBJ_LINKED_PRODUCT,
                      _('concerns the product'), [Opportunity]))
        create_rtype((constants.REL_SUB_LINKED_SERVICE,
                      _('is linked to the opportunity'), [Service]),
                     (constants.REL_OBJ_LINKED_SERVICE,
                      _('concerns the service'), [Opportunity]))
        create_rtype(
            (constants.REL_SUB_LINKED_CONTACT,
             _('involves in the opportunity'), [Contact]),
            (constants.REL_OBJ_LINKED_CONTACT, _('stages'), [Opportunity]))
        create_rtype((constants.REL_SUB_RESPONSIBLE, _('is responsible for'),
                      [Contact]),
                     (constants.REL_OBJ_RESPONSIBLE,
                      _('has as responsible contact'), [Opportunity]))

        if apps.is_installed('creme.activities'):
            logger.info(
                'Activities app is installed => an Opportunity 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(Opportunity)

        if apps.is_installed('creme.billing'):
            logger.info(
                'Billing app is installed => we create relationships between Opportunities & billing models'
            )

            from creme.billing import get_invoice_model, get_quote_model, get_sales_order_model

            Invoice = get_invoice_model()
            Quote = get_quote_model()
            SalesOrder = get_sales_order_model()

            create_rtype(
                (constants.REL_SUB_LINKED_SALESORDER,
                 _('is associate with the opportunity'), [SalesOrder]),
                (constants.REL_OBJ_LINKED_SALESORDER,
                 _('has generated the salesorder'), [Opportunity]))
            create_rtype((constants.REL_SUB_LINKED_INVOICE,
                          _('generated for the opportunity'), [Invoice]),
                         (constants.REL_OBJ_LINKED_INVOICE,
                          _('has resulted in the invoice'), [Opportunity]))
            create_rtype((constants.REL_SUB_LINKED_QUOTE,
                          _('generated for the opportunity'), [Quote]),
                         (constants.REL_OBJ_LINKED_QUOTE,
                          _('has resulted in the quote'), [Opportunity]))
            create_rtype(
                (constants.REL_SUB_CURRENT_DOC,
                 _('is the current accounting document of'),
                 [SalesOrder, Invoice, Quote]),
                (constants.REL_OBJ_CURRENT_DOC,
                 _('has as current accounting document'), [Opportunity]))

        # ---------------------------
        create_sv = SettingValue.objects.get_or_create
        create_sv(key_id=setting_keys.quote_key.id, defaults={'value': False})
        create_sv(key_id=setting_keys.target_constraint_key.id,
                  defaults={'value': True})
        create_sv(key_id=setting_keys.emitter_constraint_key.id,
                  defaults={'value': True})

        # ---------------------------
        create_efilter = partial(EntityFilter.create,
                                 model=Opportunity,
                                 user='******')
        # create_cond    = partial(EntityFilterCondition.build_4_field, model=Opportunity)
        build_cond = partial(
            condition_handler.RegularFieldConditionHandler.build_condition,
            model=Opportunity,
        )
        create_efilter(
            'opportunities-opportunities_won',
            name=_('Opportunities won'),
            conditions=[
                # create_cond(operator=EntityFilterCondition.EQUALS,
                #             name='sales_phase__won',
                #             values=[True],
                #            ),
                build_cond(
                    operator=operators.EqualsOperator,
                    field_name='sales_phase__won',
                    values=[True],
                ),
            ],
        )
        create_efilter(
            'opportunities-opportunities_lost',
            name=_('Opportunities lost'),
            conditions=[
                # create_cond(operator=EntityFilterCondition.EQUALS,
                #             name='sales_phase__lost',
                #             values=[True],
                #            ),
                build_cond(
                    operator=operators.EqualsOperator,
                    field_name='sales_phase__lost',
                    values=[True],
                ),
            ],
        )
        create_efilter(
            'opportunities-neither_won_nor_lost_opportunities',
            name=_('Neither won nor lost opportunities'),
            conditions=[
                # create_cond(operator=EntityFilterCondition.EQUALS_NOT,
                #             name='sales_phase__won',
                #             values=[True],
                #           ),
                build_cond(
                    operator=operators.EqualsNotOperator,
                    field_name='sales_phase__won',
                    values=[True],
                ),
                # create_cond(operator=EntityFilterCondition.EQUALS_NOT,
                #             name='sales_phase__lost',
                #             values=[True],
                #            ),
                build_cond(
                    operator=operators.EqualsNotOperator,
                    field_name='sales_phase__lost',
                    values=[True],
                ),
            ],
        )

        # ---------------------------
        HeaderFilter.create(
            pk=constants.DEFAULT_HFILTER_OPPORTUNITY,
            model=Opportunity,
            name=_('Opportunity view'),
            cells_desc=[
                (EntityCellRegularField, {
                    'name': 'name'
                }),
                EntityCellRelation(model=Opportunity, rtype=rt_sub_targets),
                (EntityCellRegularField, {
                    'name': 'sales_phase'
                }),
                (EntityCellRegularField, {
                    'name': 'estimated_sales'
                }),
                (EntityCellRegularField, {
                    'name': 'made_sales'
                }),
                (EntityCellRegularField, {
                    'name': 'closing_date'
                }),
            ],
        )

        # ---------------------------
        SearchConfigItem.create_if_needed(
            Opportunity,
            ['name', 'made_sales', 'sales_phase__name', 'origin__name'])

        # ---------------------------
        if not already_populated:
            create_sphase = SalesPhase.objects.create
            create_sphase(name=_('Forthcoming'), order=1)
            create_sphase(name=pgettext('opportunities-sales_phase',
                                        'Abandoned'),
                          order=4)
            create_sphase(name=pgettext('opportunities-sales_phase', 'Won'),
                          order=5,
                          won=True)
            create_sphase(name=pgettext('opportunities-sales_phase', 'Lost'),
                          order=6,
                          lost=True)
            create_sphase(name=_('Under negotiation'), order=3)
            create_sphase(name=_('In progress'), order=2)

            # ---------------------------
            create_origin = Origin.objects.create
            create_origin(name=pgettext('opportunities-origin', 'None'))
            create_origin(name=_('Web site'))
            create_origin(name=_('Mouth'))
            create_origin(name=_('Show'))
            create_origin(name=_('Direct email'))
            create_origin(name=_('Direct phone call'))
            create_origin(name=_('Employee'))
            create_origin(name=_('Partner'))
            create_origin(name=pgettext('opportunities-origin', 'Other'))

            # ---------------------------
            create_button = ButtonMenuItem.create_if_needed
            create_button(pk='opportunities-linked_opp_button',
                          model=Organisation,
                          button=LinkedOpportunityButton,
                          order=30)  # TODO: This pk is kept for compatibility
            create_button(pk='opportunities-linked_opp_button_contact',
                          model=Contact,
                          button=LinkedOpportunityButton,
                          order=30)

            # ---------------------------
            create_bdl = partial(
                BrickDetailviewLocation.objects.create_if_needed,
                model=Opportunity)
            LEFT = BrickDetailviewLocation.LEFT
            RIGHT = BrickDetailviewLocation.RIGHT

            build_cell = EntityCellRegularField.build
            cbci = CustomBrickConfigItem.objects.create(
                id='opportunities-complementary',
                name=_('Opportunity complementary information'),
                content_type=Opportunity,
                cells=[
                    build_cell(Opportunity, 'reference'),
                    build_cell(Opportunity, 'currency'),
                    build_cell(Opportunity, 'chance_to_win'),
                    build_cell(Opportunity, 'expected_closing_date'),
                    build_cell(Opportunity, 'closing_date'),
                    build_cell(Opportunity, 'origin'),
                    build_cell(Opportunity, 'first_action_date'),
                    build_cell(Opportunity, 'description'),
                ],
            )

            create_bdl(brick=bricks.OpportunityCardHatBrick,
                       order=1,
                       zone=BrickDetailviewLocation.HAT)
            create_bdl(brick=cbci.generate_id(), order=5, zone=LEFT)
            create_bdl(brick=core_bricks.CustomFieldsBrick,
                       order=40,
                       zone=LEFT)
            create_bdl(brick=bricks.BusinessManagersBrick, order=60, zone=LEFT)
            create_bdl(brick=bricks.LinkedContactsBrick, order=62, zone=LEFT)
            create_bdl(brick=bricks.LinkedProductsBrick, order=64, zone=LEFT)
            create_bdl(brick=bricks.LinkedServicesBrick, order=66, zone=LEFT)
            create_bdl(brick=core_bricks.PropertiesBrick, order=450, zone=LEFT)
            create_bdl(brick=core_bricks.RelationsBrick, order=500, zone=LEFT)
            create_bdl(brick=bricks.OppTargetBrick, order=1, zone=RIGHT)
            create_bdl(brick=bricks.OppTotalBrick, order=2, zone=RIGHT)
            create_bdl(brick=core_bricks.HistoryBrick, order=20, zone=RIGHT)

            if apps.is_installed('creme.activities'):
                logger.info(
                    'Activities app is installed => we use the "Future activities" & "Past activities" blocks'
                )

                from creme.activities import bricks as act_bricks

                create_bdl(brick=act_bricks.FutureActivitiesBrick,
                           order=20,
                           zone=RIGHT)
                create_bdl(brick=act_bricks.PastActivitiesBrick,
                           order=21,
                           zone=RIGHT)

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

                from creme.assistants import bricks as assistants_bricks

                create_bdl(brick=assistants_bricks.TodosBrick,
                           order=100,
                           zone=RIGHT)
                create_bdl(brick=assistants_bricks.MemosBrick,
                           order=200,
                           zone=RIGHT)
                create_bdl(brick=assistants_bricks.AlertsBrick,
                           order=300,
                           zone=RIGHT)
                create_bdl(brick=assistants_bricks.UserMessagesBrick,
                           order=500,
                           zone=RIGHT)

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

                from creme.documents.bricks import LinkedDocsBrick

                create_bdl(brick=LinkedDocsBrick, order=600, zone=RIGHT)

            if apps.is_installed('creme.billing'):
                logger.info(
                    'Billing app is installed => we use the billing blocks on detail view'
                )

                create_bdl(brick=bricks.QuotesBrick, order=70, zone=LEFT)
                create_bdl(brick=bricks.SalesOrdersBrick, order=72, zone=LEFT)
                create_bdl(brick=bricks.InvoicesBrick, order=74, zone=LEFT)

            if apps.is_installed('creme.emails'):
                logger.info(
                    'Emails app is installed => we use the emails blocks on detail view'
                )

                from creme.emails.bricks import MailsHistoryBrick

                create_bdl(brick=MailsHistoryBrick, order=600, zone=RIGHT)

            create_bdl(brick=bricks.TargettingOpportunitiesBrick,
                       order=16,
                       zone=RIGHT,
                       model=Organisation)

            # ---------------------------
            if apps.is_installed('creme.reports'):
                logger.info(
                    'Reports app is installed => we create an Opportunity report, with 2 graphs, and related blocks'
                )
                self.create_report_n_graphes(rt_obj_emit_orga)