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)
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, )
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), (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, )
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, )
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, )
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())
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 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)
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: raise ConflictError('This type of entity cannot be exported as pdf')
# -*- 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), )
organisation__in=(sender, other_entity), ).values_list( 'organisation', flat=True) } if len(orga_ids) == 2: orga_2_clean = orga_2_clean or get_orga_2_clean() model_filter(organisation=orga_2_clean).delete() else: return # We avoid the queries for the next model (if it's the first iteration) STATUSES_REPLACEMENTS = { billing.get_credit_note_model(): 'status', Invoice: 'status', billing.get_quote_model(): 'status', billing.get_sales_order_model(): 'status', } @receiver(core_signals.pre_replace_and_delete) def handle_replace_statuses(sender, model_field, replacing_instance, **kwargs): model = model_field.model if STATUSES_REPLACEMENTS.get(model) == model_field.name: tpl_mngr = billing.get_template_base_model().objects for pk in tpl_mngr.filter(status_id=sender.pk, ct=ContentType.objects.get_for_model(model), )\ .values_list('pk', flat=True): # NB1: we perform a .save(), not an .update() in order to:
# -*- 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), )
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)
# -*- coding: utf-8 -*- ################################################################################ # Creme is a free/open-source Customer Relationship Management software # Copyright (C) 2018 Hybird # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. ################################################################################ from creme import billing BILLING_MODELS = [ billing.get_credit_note_model(), billing.get_invoice_model(), billing.get_quote_model(), billing.get_sales_order_model(), billing.get_template_base_model(), ]
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): return skip('App "billing" not installed')(test_func)
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 def post(self, request, *args, **kwargs):
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)