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, )
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 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: # - let the model compute it's business logic (if there is one). # - get an HistoryLine for entities. # NB2: as in edition view, we perform a select_for_update() to avoid # overriding other fields (if there are concurrent accesses) with atomic(): tpl = tpl_mngr.select_for_update().filter(pk=pk).first() tpl.status_id = replacing_instance.id tpl.save()
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)
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)
ExporterConfigItem, InvoiceStatus, PaymentTerms, QuoteStatus, SalesOrderStatus, SettlementTerms, ) from .registry import lines_registry logger = logging.getLogger(__name__) CreditNote = billing.get_credit_note_model() Invoice = billing.get_invoice_model() Quote = billing.get_quote_model() SalesOrder = billing.get_sales_order_model() TemplateBase = billing.get_template_base_model() ProductLine = billing.get_product_line_model() ServiceLine = billing.get_service_line_model() class Populator(BasePopulator): dependencies = ['creme_core', 'persons', 'activities'] def populate(self): already_populated = RelationType.objects.filter( pk=constants.REL_SUB_BILL_ISSUED, ).exists() Contact = persons.get_contact_model() Organisation = persons.get_organisation_model() Product = products.get_product_model()
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')
def populate(self): already_populated = RelationType.objects.filter( pk=constants.REL_SUB_SOLD).exists() Act = commercial.get_act_model() ActObjectivePattern = commercial.get_pattern_model() Strategy = commercial.get_strategy_model() Contact = persons.get_contact_model() Organisation = persons.get_organisation_model() Product = products.get_product_model() Service = products.get_service_model() RelationType.create( (constants.REL_SUB_SOLD, _('has sold'), [Contact, Organisation]), (constants.REL_OBJ_SOLD, _('has been sold by'), [Product, Service ]), ) complete_goal_models = {*creme_registry.iter_entity_models()} complete_goal_models.discard(Strategy) if apps.is_installed('creme.billing'): from creme import billing from creme.billing.registry import lines_registry complete_goal_models.discard(billing.get_credit_note_model()) complete_goal_models.discard(billing.get_template_base_model()) complete_goal_models.difference_update(lines_registry) RelationType.create( ( constants.REL_SUB_COMPLETE_GOAL, _('completes a goal of the commercial action'), complete_goal_models, ), ( constants.REL_OBJ_COMPLETE_GOAL, _('is completed thanks to'), [Act], ), ) # --------------------------- CremePropertyType.create(constants.PROP_IS_A_SALESMAN, _('is a salesman'), [Contact]) # --------------------------- MarketSegment.objects.get_or_create( property_type=None, defaults={'name': _('All the organisations')}, ) # --------------------------- for i, title in enumerate( [_('Phone calls'), _('Show'), _('Demo')], start=1): create_if_needed(ActType, {'pk': i}, title=title, is_custom=False) # --------------------------- create_hf = HeaderFilter.objects.create_if_needed create_hf( pk=constants.DEFAULT_HFILTER_ACT, model=Act, name=_('Com Action view'), cells_desc=[ (EntityCellRegularField, { 'name': 'name' }), (EntityCellRegularField, { 'name': 'expected_sales' }), (EntityCellRegularField, { 'name': 'due_date' }), ], ) create_hf( pk=constants.DEFAULT_HFILTER_STRATEGY, model=Strategy, name=_('Strategy view'), cells_desc=[(EntityCellRegularField, { 'name': 'name' })], ) create_hf( pk=constants.DEFAULT_HFILTER_PATTERN, model=ActObjectivePattern, name=_('Objective pattern view'), cells_desc=[ (EntityCellRegularField, { 'name': 'name' }), (EntityCellRegularField, { 'name': 'segment' }), ], ) # --------------------------- def build_custom_form_items(creation_descriptor, edition_descriptor, field_names): base_groups_desc = [ { 'name': _('General information'), 'layout': LAYOUT_DUAL_FIRST, 'cells': [ *((EntityCellRegularField, { 'name': fname }) for fname in field_names), ( EntityCellCustomFormSpecial, { 'name': EntityCellCustomFormSpecial. REMAINING_REGULARFIELDS }, ), ], }, { 'name': _('Description'), 'layout': LAYOUT_DUAL_SECOND, 'cells': [ (EntityCellRegularField, { 'name': 'description' }), ], }, { 'name': _('Custom fields'), 'layout': LAYOUT_DUAL_SECOND, 'cells': [ ( EntityCellCustomFormSpecial, { 'name': EntityCellCustomFormSpecial. REMAINING_CUSTOMFIELDS }, ), ], }, ] CustomFormConfigItem.objects.create_if_needed( descriptor=creation_descriptor, groups_desc=[ *base_groups_desc, { 'name': _('Properties'), 'cells': [ ( EntityCellCustomFormSpecial, { 'name': EntityCellCustomFormSpecial. CREME_PROPERTIES }, ), ], }, { 'name': _('Relationships'), 'cells': [ ( EntityCellCustomFormSpecial, { 'name': EntityCellCustomFormSpecial.RELATIONS }, ), ], }, ], ) CustomFormConfigItem.objects.create_if_needed( descriptor=edition_descriptor, groups_desc=base_groups_desc, ) build_custom_form_items( creation_descriptor=custom_forms.ACT_CREATION_CFORM, edition_descriptor=custom_forms.ACT_EDITION_CFORM, field_names=[ 'user', 'name', 'expected_sales', 'cost', 'goal', 'start', 'due_date', 'act_type', 'segment', ], ) build_custom_form_items( creation_descriptor=custom_forms.PATTERN_CREATION_CFORM, edition_descriptor=custom_forms.PATTERN_EDITION_CFORM, field_names=[ 'user', 'name', 'average_sales', 'segment', ], ) build_custom_form_items( creation_descriptor=custom_forms.STRATEGY_CREATION_CFORM, edition_descriptor=custom_forms.STRATEGY_EDITION_CFORM, field_names=[ 'user', 'name', ], ) # --------------------------- create_searchconf = SearchConfigItem.objects.create_if_needed create_searchconf(Act, ['name', 'expected_sales', 'cost', 'goal']) create_searchconf(Strategy, ['name']) create_searchconf(ActObjectivePattern, [], disabled=True) # --------------------------- SettingValue.objects.get_or_create( key_id=setting_keys.orga_approaches_key.id, defaults={'value': True}, ) # --------------------------- Job.objects.get_or_create( type_id=creme_jobs.com_approaches_emails_send_type.id, defaults={ 'language': settings.LANGUAGE_CODE, 'periodicity': date_period_registry.get_period('days', 1), 'status': Job.STATUS_OK, # The CommercialApproach field for Activities' CustomForms is not # in the default configuration, so a enabled job would be annoying. 'enabled': False, }, ) # --------------------------- # TODO: move to "not already_populated" section in creme2.4 if not MenuConfigItem.objects.filter( entry_id__startswith='commercial-').exists(): container = MenuConfigItem.objects.get_or_create( entry_id=ContainerEntry.id, entry_data={'label': _('Commercial')}, defaults={'order': 30}, )[0] create_mitem = MenuConfigItem.objects.create create_mitem(entry_id=menu.ActsEntry.id, order=50, parent=container) create_mitem(entry_id=menu.StrategiesEntry.id, order=55, parent=container) create_mitem(entry_id=menu.SegmentsEntry.id, order=60, parent=container) create_mitem(entry_id=menu.PatternsEntry.id, order=70, parent=container) directory = MenuConfigItem.objects.filter( entry_id=ContainerEntry.id, entry_data={ 'label': _('Directory') }, ).first() if directory is not None: create_mitem(entry_id=menu.SalesmenEntry.id, order=100, parent=directory) creations = MenuConfigItem.objects.filter( entry_id=ContainerEntry.id, entry_data={ 'label': _('+ Creation') }, ).first() if creations is not None: create_mitem(entry_id=menu.ActCreationEntry.id, order=40, parent=creations) # --------------------------- if not already_populated: ButtonMenuItem.objects.create_if_needed( button=buttons.CompleteGoalButton, order=60, ) TOP = BrickDetailviewLocation.TOP RIGHT = BrickDetailviewLocation.RIGHT LEFT = BrickDetailviewLocation.LEFT # BrickDetailviewLocation.objects.multi_create( # defaults={'brick': bricks.ApproachesBrick, 'order': 10, 'zone': RIGHT}, # data=[ # {}, # default configuration # {'model': Contact}, # {'model': Organisation}, # ] # ) BrickDetailviewLocation.objects.multi_create( defaults={ 'model': Act, 'zone': LEFT }, data=[ { 'order': 5 }, # generic information brick { 'brick': bricks.ActObjectivesBrick, 'order': 10 }, { 'brick': bricks.RelatedOpportunitiesBrick, 'order': 20 }, { 'brick': core_bricks.CustomFieldsBrick, 'order': 40 }, { 'brick': core_bricks.PropertiesBrick, 'order': 450 }, { 'brick': core_bricks.RelationsBrick, 'order': 500 }, { 'brick': core_bricks.HistoryBrick, 'order': 20, 'zone': RIGHT }, ], ) BrickDetailviewLocation.objects.multi_create( defaults={ 'model': ActObjectivePattern, 'zone': LEFT }, data=[ { 'brick': bricks.PatternComponentsBrick, 'order': 10, 'zone': TOP }, { 'order': 5 }, { 'brick': core_bricks.CustomFieldsBrick, 'order': 40 }, { 'brick': core_bricks.PropertiesBrick, 'order': 450 }, { 'brick': core_bricks.RelationsBrick, 'order': 500 }, { 'brick': core_bricks.HistoryBrick, 'order': 20, 'zone': RIGHT }, ], ) BrickDetailviewLocation.objects.multi_create( defaults={ 'model': Strategy, 'zone': LEFT }, data=[ { 'brick': bricks.SegmentDescriptionsBrick, 'order': 10, 'zone': TOP }, { 'order': 5 }, { 'brick': core_bricks.CustomFieldsBrick, 'order': 40 }, { 'brick': bricks.EvaluatedOrgasBrick, 'order': 50 }, { 'brick': bricks.AssetsBrick, 'order': 60 }, { 'brick': bricks.CharmsBrick, 'order': 70 }, { 'brick': core_bricks.PropertiesBrick, 'order': 450 }, { 'brick': core_bricks.RelationsBrick, 'order': 500 }, { 'brick': core_bricks.HistoryBrick, 'order': 20, 'zone': RIGHT }, ], ) if apps.is_installed('creme.assistants'): logger.info('Assistants app is installed ' '=> we use the assistants blocks on detail views') from creme.assistants import bricks as a_bricks for model in (Act, ActObjectivePattern, Strategy): BrickDetailviewLocation.objects.multi_create( defaults={ 'model': model, 'zone': RIGHT }, data=[ { 'brick': a_bricks.TodosBrick, 'order': 100 }, { 'brick': a_bricks.MemosBrick, 'order': 200 }, { 'brick': a_bricks.AlertsBrick, 'order': 300 }, { 'brick': a_bricks.UserMessagesBrick, 'order': 400 }, ], ) if apps.is_installed('creme.documents'): # logger.info("Documents app is installed # => we use the documents blocks on Strategy's detail views") from creme.documents.bricks import LinkedDocsBrick BrickDetailviewLocation.objects.multi_create( defaults={ 'brick': LinkedDocsBrick, 'order': 600, 'zone': RIGHT }, data=[ { 'model': Act }, { 'model': ActObjectivePattern }, { 'model': Strategy }, ], )
# -*- 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(), ]