From 7410de111fe33731ba79f68f53e13dd3d511fb43 Mon Sep 17 00:00:00 2001 From: Pierre Masereel Date: Mon, 14 Jan 2019 12:37:23 +0000 Subject: [PATCH] [ADD] reveal_service : allow user to generate leads based on filters Purpose : Allow the user to generate a certain number of leads based on certain criteria like localization, technologies used, or size. Available filters are : - company size - localization (country and states) - industries - technologies The user can also ask for a few contacts that can be filtered based on their role or seniority. Each company found generates a new lead/opportunity for which the sales team, salesperson and tags can be preset from the lead mining request. This commit includes the followings changes : - New module crm_iap_lead - New model crm.iap.lead.helpers which contains helper methods used by both modules crm_iap_lead and crm_iap_lead_website - New model crm.iap.lead.mining.request that holds the data of a lead mining request - An option in the CRM settings to install this feature - New button 'Generate Leads' in the leads/opportunity ListView and KanbanView that opens a modal allowing to create a lead mining request - New section 'Lead Generation' in menuitem 'Configuration' of CRM TaskID : 1902172 closes odoo/odoo#30189 --- addons/crm/models/res_config_settings.py | 2 + .../crm/views/res_config_settings_views.xml | 18 +- addons/crm_iap_lead/__manifest__.py | 9 + .../data/crm.iap.lead.technology.csv | 348 ++++++++++++++++++ addons/crm_iap_lead/data/ir_sequence_data.xml | 14 + addons/crm_iap_lead/models/__init__.py | 2 + addons/crm_iap_lead/models/crm_iap_lead.py | 13 + .../models/crm_iap_lead_helpers.py | 121 ++++++ .../models/crm_iap_lead_mining_request.py | 233 ++++++++++++ addons/crm_iap_lead/models/crm_lead.py | 1 + .../crm_iap_lead/security/ir.model.access.csv | 3 +- .../src/js/leads_tree_generate_leads.js | 76 ++++ .../xml/leads_tree_generate_leads_views.xml | 31 ++ .../views/crm_iap_lead_templates.xml | 8 + .../crm_iap_lead/views/crm_iap_lead_views.xml | 183 +++++++++ addons/crm_iap_lead/views/crm_lead_view.xml | 47 +++ .../views/res_config_settings_views.xml | 13 + .../models/crm_reveal_rule.py | 119 +----- .../views/crm_reveal_views.xml | 4 +- .../views/res_config_settings_views.xml | 4 +- addons/iap/models/iap.py | 2 +- 21 files changed, 1132 insertions(+), 119 deletions(-) create mode 100644 addons/crm_iap_lead/data/crm.iap.lead.technology.csv create mode 100644 addons/crm_iap_lead/data/ir_sequence_data.xml create mode 100644 addons/crm_iap_lead/models/crm_iap_lead_helpers.py create mode 100644 addons/crm_iap_lead/models/crm_iap_lead_mining_request.py create mode 100644 addons/crm_iap_lead/static/src/js/leads_tree_generate_leads.js create mode 100644 addons/crm_iap_lead/static/src/xml/leads_tree_generate_leads_views.xml create mode 100644 addons/crm_iap_lead/views/crm_iap_lead_templates.xml create mode 100644 addons/crm_iap_lead/views/crm_iap_lead_views.xml create mode 100644 addons/crm_iap_lead/views/crm_lead_view.xml create mode 100644 addons/crm_iap_lead/views/res_config_settings_views.xml diff --git a/addons/crm/models/res_config_settings.py b/addons/crm/models/res_config_settings.py index cd97f4c29d8..40d09ddc2e1 100644 --- a/addons/crm/models/res_config_settings.py +++ b/addons/crm/models/res_config_settings.py @@ -11,7 +11,9 @@ class ResConfigSettings(models.TransientModel): generate_lead_from_alias = fields.Boolean('Manual Assignation of Emails', config_parameter='crm.generate_lead_from_alias') group_use_lead = fields.Boolean(string="Leads", implied_group='crm.group_use_lead') module_crm_phone_validation = fields.Boolean("Phone Formatting") + module_crm_iap_lead = fields.Boolean("Generate new leads based on their country, technologies, size, etc.") module_crm_iap_lead_website = fields.Boolean("Create Leads/Opportunities from your website's traffic") + lead_mining_in_pipeline = fields.Boolean("Create a lead mining request directly from the opportunity pipeline.", config_parameter='crm.lead_mining_in_pipeline') def _find_default_lead_alias_id(self): alias = self.env.ref('crm.mail_alias_lead_info', False) diff --git a/addons/crm/views/res_config_settings_views.xml b/addons/crm/views/res_config_settings_views.xml index 42e1def165a..3520a1a7109 100644 --- a/addons/crm/views/res_config_settings_views.xml +++ b/addons/crm/views/res_config_settings_views.xml @@ -52,21 +52,35 @@ +

Lead Generation

-
-
+
+
+
+ +
+
+
+
+

Contacts

diff --git a/addons/crm_iap_lead/__manifest__.py b/addons/crm_iap_lead/__manifest__.py index fa61c3d83e4..93975ae8220 100644 --- a/addons/crm_iap_lead/__manifest__.py +++ b/addons/crm_iap_lead/__manifest__.py @@ -10,8 +10,17 @@ 'data/crm.iap.lead.industry.csv', 'data/crm.iap.lead.role.csv', 'data/crm.iap.lead.seniority.csv', + 'data/crm.iap.lead.technology.csv', 'data/message_template.xml', 'data/crm_iap_lead_data.xml', + 'data/ir_sequence_data.xml', 'security/ir.model.access.csv', + 'views/crm_lead_view.xml', + 'views/crm_iap_lead_views.xml', + 'views/res_config_settings_views.xml', + 'views/crm_iap_lead_templates.xml', ], + 'qweb': [ + 'static/src/xml/leads_tree_generate_leads_views.xml', + ] } diff --git a/addons/crm_iap_lead/data/crm.iap.lead.technology.csv b/addons/crm_iap_lead/data/crm.iap.lead.technology.csv new file mode 100644 index 00000000000..6f978e64840 --- /dev/null +++ b/addons/crm_iap_lead/data/crm.iap.lead.technology.csv @@ -0,0 +1,348 @@ +id,name,tech_tag +crm_iap_lead_technology_1,1&1 Hosting,1_and_1_hosting +crm_iap_lead_technology_2,3d Cart,3d_cart +crm_iap_lead_technology_3,Acquia,acquia +crm_iap_lead_technology_4,Acquisio,acquisio +crm_iap_lead_technology_5,Act On,act_on +crm_iap_lead_technology_6,Action Network,action_network +crm_iap_lead_technology_7,Active Demand,active_demand +crm_iap_lead_technology_8,Activecampaign,activecampaign +crm_iap_lead_technology_9,Add To Any,add_to_any +crm_iap_lead_technology_10,Adform Advertiser,adform_advertiser +crm_iap_lead_technology_11,Adobe Business Catalyst,adobe_business_catalyst +crm_iap_lead_technology_12,Adobe Connect,adobe_connect +crm_iap_lead_technology_13,Adobe Dynamic Tag Management,adobe_dynamic_tag_management +crm_iap_lead_technology_14,Adobe Experience Manager,adobe_experience_manager +crm_iap_lead_technology_15,AdRoll,adroll +crm_iap_lead_technology_16,Airbrake,airbrake +crm_iap_lead_technology_17,Akamai,akamai +crm_iap_lead_technology_18,Akamai DNS,akamai_dns +crm_iap_lead_technology_19,Alexa,alexa +crm_iap_lead_technology_20,Altocloud,altocloud +crm_iap_lead_technology_21,Amazon Associates,amazon_associates +crm_iap_lead_technology_22,Amazon Payments,amazon_payments +crm_iap_lead_technology_23,Amazon S3,amazon_s3 +crm_iap_lead_technology_24,Amazon Simple Email Service,amazon_ses +crm_iap_lead_technology_25,Amazon Web Services,aws_ec2 +crm_iap_lead_technology_26,amCharts,amcharts +crm_iap_lead_technology_27,Amplitude,amplitude +crm_iap_lead_technology_28,Android,android +crm_iap_lead_technology_29,Apache,apache +crm_iap_lead_technology_30,App Nexus,app_nexus +crm_iap_lead_technology_31,Appboy,appboy +crm_iap_lead_technology_32,Appcues,appcues +crm_iap_lead_technology_33,AppDynamics,appdynamics +crm_iap_lead_technology_34,Apteligent,apteligent +crm_iap_lead_technology_35,ASP.Net,asp_net +crm_iap_lead_technology_36,Attribution,attribution +crm_iap_lead_technology_37,Authorizenet,authorizenet +crm_iap_lead_technology_38,Autopilot,autopilot +crm_iap_lead_technology_39,Aweber,aweber +crm_iap_lead_technology_40,Bazaarvoice,bazaarvoice +crm_iap_lead_technology_41,Benchmarkemail,benchmarkemail +crm_iap_lead_technology_42,Big Cartel,big_cartel +crm_iap_lead_technology_43,Bigcommerce,bigcommerce +crm_iap_lead_technology_44,Bing Ads,bing_ads +crm_iap_lead_technology_45,Bing Advertiser,bing_advertiser +crm_iap_lead_technology_46,Bing Maps,bing_maps +crm_iap_lead_technology_47,Bloomreach,bloomreach +crm_iap_lead_technology_48,Bluehost Hosting,bluehost_hosting +crm_iap_lead_technology_49,Braintree,braintree +crm_iap_lead_technology_50,Branch,branch +crm_iap_lead_technology_51,Brightcove,brightcove +crm_iap_lead_technology_52,Bronto,bronto +crm_iap_lead_technology_53,Bug Herd,bug_herd +crm_iap_lead_technology_54,Bugsnag,bugsnag +crm_iap_lead_technology_55,C3 Metrics,c3_metrics +crm_iap_lead_technology_56,Calq,calq +crm_iap_lead_technology_57,Campaignmonitor,campaignmonitor +crm_iap_lead_technology_58,Castle,castle +crm_iap_lead_technology_59,CentOS,centos +crm_iap_lead_technology_60,Chargify,chargify +crm_iap_lead_technology_61,Chart Beat,chart_beat +crm_iap_lead_technology_62,Chartio,chartio +crm_iap_lead_technology_63,Chatlio,chatlio +crm_iap_lead_technology_64,Chownow,chownow +crm_iap_lead_technology_65,ClickFunnels,clickfunnels +crm_iap_lead_technology_66,Clicktale,clicktale +crm_iap_lead_technology_67,Clicky,clicky +crm_iap_lead_technology_68,Cloud Flare,cloud_flare +crm_iap_lead_technology_69,Cloudinary,cloudinary +crm_iap_lead_technology_70,Commerce Sciences,commerce_sciences +crm_iap_lead_technology_71,Constant Contact,constant_contact +crm_iap_lead_technology_72,Contently,contently +crm_iap_lead_technology_73,Convert Kit,convert_kit +crm_iap_lead_technology_74,Convertro,convertro +crm_iap_lead_technology_75,Crazy Egg,crazy_egg +crm_iap_lead_technology_76,Criteo,criteo +crm_iap_lead_technology_77,Crowdskout,crowdskout +crm_iap_lead_technology_78,Customer.io,customer_io +crm_iap_lead_technology_79,Dailymotion,dailymotion +crm_iap_lead_technology_80,Debian,debian +crm_iap_lead_technology_81,Demandbase,demandbase +crm_iap_lead_technology_82,Demandware,demandware +crm_iap_lead_technology_83,Digital Ocean,digital_ocean +crm_iap_lead_technology_84,Disqus,disqus +crm_iap_lead_technology_85,Django,django +crm_iap_lead_technology_86,DNS Made Easy,dns_made_easy +crm_iap_lead_technology_87,DoubleClick Ads,double_click +crm_iap_lead_technology_88,DoubleClick Advertiser,double_click_advertiser +crm_iap_lead_technology_89,DreamHost Hosting,dreamhost_hosting +crm_iap_lead_technology_90,Drift,drift +crm_iap_lead_technology_91,Drip,drip +crm_iap_lead_technology_92,Drupal,drupal +crm_iap_lead_technology_93,Dwolla,dwolla +crm_iap_lead_technology_94,Dyn DNS,dyn_dns +crm_iap_lead_technology_95,Dynamic Yield,dynamic_yield +crm_iap_lead_technology_96,Ecwid,ecwid +crm_iap_lead_technology_97,ElasticEmail,elasticemail +crm_iap_lead_technology_98,Elevio,elevio +crm_iap_lead_technology_99,Eloqua,eloqua +crm_iap_lead_technology_100,Emma,emma +crm_iap_lead_technology_101,Engagio,engagio +crm_iap_lead_technology_102,Errorception,errorception +crm_iap_lead_technology_103,Express,express +crm_iap_lead_technology_104,Facebook Advertiser,facebook_advertiser +crm_iap_lead_technology_105,Facebook Beacon,facebook_beacon +crm_iap_lead_technology_106,Facebook Comments,facebook_comments +crm_iap_lead_technology_107,Facebook Connect,facebook_connect +crm_iap_lead_technology_108,Facebook Like Button,facebook_like_button +crm_iap_lead_technology_109,Facebook Social Plugins,facebook_social_plugins +crm_iap_lead_technology_110,Fedora,fedora +crm_iap_lead_technology_111,Flowplayer,flowplayer +crm_iap_lead_technology_112,Formstack,formstack +crm_iap_lead_technology_113,FreeBSD,freebsd +crm_iap_lead_technology_114,Freshdesk,freshdesk +crm_iap_lead_technology_115,Fullcontact,fullcontact +crm_iap_lead_technology_116,Fullstory,fullstory +crm_iap_lead_technology_117,FusionCharts,fusioncharts +crm_iap_lead_technology_118,Gauges,gauges +crm_iap_lead_technology_119,Get Response,get_response +crm_iap_lead_technology_120,Get Satisfaction,get_satisfaction +crm_iap_lead_technology_121,Go Squared,go_squared +crm_iap_lead_technology_122,Gocardless,gocardless +crm_iap_lead_technology_123,GoDaddy Hosting,godaddy_hosting +crm_iap_lead_technology_124,Godaddy Nameserver,godaddy_nameserver +crm_iap_lead_technology_125,Google Adsense,google_adsense +crm_iap_lead_technology_126,Google Adwords,google_adwords +crm_iap_lead_technology_127,Google Affiliate Network,google_affiliate_network +crm_iap_lead_technology_128,Google Analytics,google_analytics +crm_iap_lead_technology_129,Google Apps,google_apps +crm_iap_lead_technology_130,Google Charts,google_charts +crm_iap_lead_technology_131,Google Cloud,google_cloud +crm_iap_lead_technology_132,Google Forms,google_forms +crm_iap_lead_technology_133,Google Maps,google_maps +crm_iap_lead_technology_134,Google Tag Manager,google_tag_manager +crm_iap_lead_technology_135,Google Website Optimizer,google_website_optimizer +crm_iap_lead_technology_136,Google Widgets,google_widgets +crm_iap_lead_technology_137,GraphIQ,graphiq +crm_iap_lead_technology_138,Gravity Forms,gravity_forms +crm_iap_lead_technology_139,Greenhouse,greenhouse +crm_iap_lead_technology_140,Heap,heap +crm_iap_lead_technology_141,Hello Bar,hello_bar +crm_iap_lead_technology_142,Help Scout,help_scout +crm_iap_lead_technology_143,Heroku,heroku +crm_iap_lead_technology_144,Highcharts,highcharts +crm_iap_lead_technology_145,Hotjar,hotjar +crm_iap_lead_technology_146,Hoverowl,hoverowl +crm_iap_lead_technology_147,HubSpot,hubspot +crm_iap_lead_technology_148,Icims,icims +crm_iap_lead_technology_149,Indicative,indicative +crm_iap_lead_technology_150,Infusionsoft,infusionsoft +crm_iap_lead_technology_151,Inside Sales,inside_sales +crm_iap_lead_technology_152,Inspectlet,inspectlet +crm_iap_lead_technology_153,Instagram,instagram +crm_iap_lead_technology_154,Instagram Links,instagram_links +crm_iap_lead_technology_155,Instart Logic,instart_logic +crm_iap_lead_technology_156,Intense Debate,intense_debate +crm_iap_lead_technology_157,Intercom,intercom +crm_iap_lead_technology_158,ios,ios +crm_iap_lead_technology_159,Iterable,iterable +crm_iap_lead_technology_160,Java,java +crm_iap_lead_technology_161,Jobvite,jobvite +crm_iap_lead_technology_162,Joomla,joomla +crm_iap_lead_technology_163,Jw Player,jw_player +crm_iap_lead_technology_164,Kaltura,kaltura +crm_iap_lead_technology_165,Kapost,kapost +crm_iap_lead_technology_166,Keen IO,keen_io +crm_iap_lead_technology_167,Kissmetrics,kiss_metrics +crm_iap_lead_technology_168,Klaviyo,klaviyo +crm_iap_lead_technology_169,Knowtify,knowtify +crm_iap_lead_technology_170,Lead Dyno,lead_dyno +crm_iap_lead_technology_171,Lever,lever +crm_iap_lead_technology_172,Lightspeed,lightspeed +crm_iap_lead_technology_173,LinkedIn Advertiser,linked_in_advertiser +crm_iap_lead_technology_174,Linkwithin,linkwithin +crm_iap_lead_technology_175,Linode Hosting,linode_hosting +crm_iap_lead_technology_176,Live Chat,live_chat +crm_iap_lead_technology_177,Livefyre,livefyre +crm_iap_lead_technology_178,Liveramp,liveramp +crm_iap_lead_technology_179,LKQD,lkqd +crm_iap_lead_technology_180,Localytics,localytics +crm_iap_lead_technology_181,Looker,looker +crm_iap_lead_technology_182,Lucky Orange,lucky_orange +crm_iap_lead_technology_183,Mad Kudu,mad_kudu +crm_iap_lead_technology_184,Magento,magento +crm_iap_lead_technology_185,Magento Enterprise,magento_enterprise +crm_iap_lead_technology_186,Magento V1,magento_v1 +crm_iap_lead_technology_187,Magento V2,magento_v2 +crm_iap_lead_technology_188,Mailchimp,mailchimp +crm_iap_lead_technology_189,Mailgun,mailgun +crm_iap_lead_technology_190,Mailjet,mailjet +crm_iap_lead_technology_191,Mandrill,mandrill +crm_iap_lead_technology_192,Marin,marin +crm_iap_lead_technology_193,Marketo,marketo +crm_iap_lead_technology_194,Maxmind,maxmind +crm_iap_lead_technology_195,Microsoft Azure,microsoft_azure +crm_iap_lead_technology_196,Microsoft Exchange Online,microsoft_exchange_online +crm_iap_lead_technology_197,Microsoft Office 365,microsoft_office_365 +crm_iap_lead_technology_198,Mindbody,mindbody +crm_iap_lead_technology_199,Mixpanel,mixpanel +crm_iap_lead_technology_200,Mode,mode +crm_iap_lead_technology_201,Mouseflow,mouseflow +crm_iap_lead_technology_202,Mousestats,mousestats +crm_iap_lead_technology_203,My Site Auditor,my_site_auditor +crm_iap_lead_technology_204,Namecheap Dns,namecheap_dns +crm_iap_lead_technology_205,NationBuilder,nation_builder +crm_iap_lead_technology_206,Neustar Ad Advisor,neustar_ad_advisor +crm_iap_lead_technology_207,New Relic,new_relic +crm_iap_lead_technology_208,Nextable,nextable +crm_iap_lead_technology_209,Nginx,nginx +crm_iap_lead_technology_210,Nosto,nosto +crm_iap_lead_technology_211,Ns1,ns1 +crm_iap_lead_technology_212,Olark,olark +crm_iap_lead_technology_213,Omniconvert,omniconvert +crm_iap_lead_technology_214,Omniture (Adobe Analytics),omniture_adobe_analytics +crm_iap_lead_technology_215,Ooyala,ooyala +crm_iap_lead_technology_216,Open Table,open_table +crm_iap_lead_technology_217,Optimizely,optimizely +crm_iap_lead_technology_218,Optimonk,optimonk +crm_iap_lead_technology_219,Oscommerce,oscommerce +crm_iap_lead_technology_220,Outbound,outbound +crm_iap_lead_technology_221,Outbrain Ads,outbrain_ads +crm_iap_lead_technology_222,Outlook,outlook +crm_iap_lead_technology_223,ownerIQ,owneriq +crm_iap_lead_technology_224,Pardot,pardot +crm_iap_lead_technology_225,PayPal,paypal +crm_iap_lead_technology_226,Payza,payza +crm_iap_lead_technology_227,Percolate,percolate +crm_iap_lead_technology_228,Perfect Audience,perfect_audience +crm_iap_lead_technology_229,Periscope Data,periscope_data +crm_iap_lead_technology_230,Picreel,picreel +crm_iap_lead_technology_231,Pingdom,pingdom +crm_iap_lead_technology_232,Pinterest,pinterest +crm_iap_lead_technology_233,Pipedrive,pipedrive +crm_iap_lead_technology_234,Piwik,piwik +crm_iap_lead_technology_235,Playwire,playwire +crm_iap_lead_technology_236,Postmark,postmark +crm_iap_lead_technology_237,PowerReviews,powerreviews +crm_iap_lead_technology_238,Presta Shop,presta_shop +crm_iap_lead_technology_239,Privy,privy +crm_iap_lead_technology_240,Qbrick,qbrick +crm_iap_lead_technology_241,Qualaroo,qualaroo +crm_iap_lead_technology_242,Qualtrics,qualtrics +crm_iap_lead_technology_243,Quantcast,quantcast +crm_iap_lead_technology_244,Qubit,qubit +crm_iap_lead_technology_245,Rackspace,rackspace +crm_iap_lead_technology_246,Rackspace Email,rackspace_email +crm_iap_lead_technology_247,Recaptcha,recaptcha +crm_iap_lead_technology_248,Recruiterbox,recruiterbox +crm_iap_lead_technology_249,Recurly,recurly +crm_iap_lead_technology_250,Red Hat,red_hat +crm_iap_lead_technology_251,ReSRCit,resrcit +crm_iap_lead_technology_252,Resumator,resumator +crm_iap_lead_technology_253,RetailRocket,retailrocket +crm_iap_lead_technology_254,Rezdy,rezdy +crm_iap_lead_technology_255,Rezgo,rezgo +crm_iap_lead_technology_256,Robly,robly +crm_iap_lead_technology_257,Rollbar,rollbar +crm_iap_lead_technology_258,Route 53,aws_route_53 +crm_iap_lead_technology_259,Ruby On Rails,ruby_on_rails +crm_iap_lead_technology_260,Salesforce,salesforce +crm_iap_lead_technology_261,Salesmachine,salesmachine +crm_iap_lead_technology_262,Salsa,salsa +crm_iap_lead_technology_263,Satismeter,satismeter +crm_iap_lead_technology_264,Segment,segment +crm_iap_lead_technology_265,Sendgrid,sendgrid +crm_iap_lead_technology_266,Sentry,sentry +crm_iap_lead_technology_267,Sessioncam,sessioncam +crm_iap_lead_technology_268,Sharepoint,sharepoint +crm_iap_lead_technology_269,Sharp Spring,sharp_spring +crm_iap_lead_technology_270,Shopify,shopify +crm_iap_lead_technology_271,Shopify Plus,shopify_plus +crm_iap_lead_technology_272,Shopware,shopware +crm_iap_lead_technology_273,Sidecar,sidecar +crm_iap_lead_technology_274,Silkroad,silkroad +crm_iap_lead_technology_275,Simplicant,simplicant +crm_iap_lead_technology_276,Slack,slack +crm_iap_lead_technology_277,Smartlook,smartlook +crm_iap_lead_technology_278,Snowplow Analytics,snowplow_analytics +crm_iap_lead_technology_279,Sociomantic,sociomantic +crm_iap_lead_technology_280,Socket.io,socket_io +crm_iap_lead_technology_281,Softlayer Hosting,softlayer_hosting +crm_iap_lead_technology_282,Squarespace,squarespace +crm_iap_lead_technology_283,SteelHouse,steel_house +crm_iap_lead_technology_284,Stripe,stripe +crm_iap_lead_technology_285,Success Factors,success_factors +crm_iap_lead_technology_286,Sumo,sumo +crm_iap_lead_technology_287,Survey Monkey,survey_monkey +crm_iap_lead_technology_288,Tableau,tableau +crm_iap_lead_technology_289,Taboola Ads,taboola_ads +crm_iap_lead_technology_290,Taleo,taleo +crm_iap_lead_technology_291,Talkable,talkable +crm_iap_lead_technology_292,Tawk,tawk +crm_iap_lead_technology_293,Tealium,tealium +crm_iap_lead_technology_294,Terminus,terminus +crm_iap_lead_technology_295,Top Producer,top_producer +crm_iap_lead_technology_296,Totango,totango +crm_iap_lead_technology_297,Tout,tout +crm_iap_lead_technology_298,Toutapp,toutapp +crm_iap_lead_technology_299,TowerData,towerdata +crm_iap_lead_technology_300,Track JS,track_js +crm_iap_lead_technology_301,Trustpilot,trustpilot +crm_iap_lead_technology_302,Twitter Advertiser,twitter_advertiser +crm_iap_lead_technology_303,Twitter Badge,twitter_badge +crm_iap_lead_technology_304,Twitter Button,twitter_button +crm_iap_lead_technology_305,Typeform,typeform +crm_iap_lead_technology_306,Typekit By Adobe,typekit_by_adobe +crm_iap_lead_technology_307,Uber Cart,uber_cart +crm_iap_lead_technology_308,Ubuntu,ubuntu +crm_iap_lead_technology_309,Ultipro,ultipro +crm_iap_lead_technology_310,Unix,unix +crm_iap_lead_technology_311,UserVoice,user_voice +crm_iap_lead_technology_312,Vero,vero +crm_iap_lead_technology_313,Verticalresponse,verticalresponse +crm_iap_lead_technology_314,Video.js,videojs +crm_iap_lead_technology_315,Vidyard,vidyard +crm_iap_lead_technology_316,Vimeo,vimeo +crm_iap_lead_technology_317,Virtue Mart,virtue_mart +crm_iap_lead_technology_318,Visual IQ,visual_iq +crm_iap_lead_technology_319,Visual Website Optimizer,visual_website_optimizer +crm_iap_lead_technology_320,Volusion,volusion +crm_iap_lead_technology_321,Vzaar,vzaar +crm_iap_lead_technology_322,WebEngage,webengage +crm_iap_lead_technology_323,Weebly,weebly +crm_iap_lead_technology_324,Wistia,wistia +crm_iap_lead_technology_325,Wix Stores,wix_stores +crm_iap_lead_technology_326,Woo Commerce,woo_commerce +crm_iap_lead_technology_327,Woopra,woopra +crm_iap_lead_technology_328,Wootric,wootric +crm_iap_lead_technology_329,Wordpress,wordpress +crm_iap_lead_technology_330,Workable,workable +crm_iap_lead_technology_331,Wp Engine,wp_engine +crm_iap_lead_technology_332,Wufoo,wufoo +crm_iap_lead_technology_333,xt:Commerce,xt_commerce +crm_iap_lead_technology_334,Yahoo Stores,yahoo_stores +crm_iap_lead_technology_335,Yandex Metrica,yandex_metrica +crm_iap_lead_technology_336,Yelp Seatme,yelp_seatme +crm_iap_lead_technology_337,Yesware,yesware +crm_iap_lead_technology_338,Yieldify,yieldify +crm_iap_lead_technology_339,Yotpo,yotpo +crm_iap_lead_technology_340,Youtube,youtube +crm_iap_lead_technology_341,Zeit Now,zeit_now +crm_iap_lead_technology_342,Zen Cart,zen_cart +crm_iap_lead_technology_343,Zendesk,zendesk +crm_iap_lead_technology_344,Zenreach,zenreach +crm_iap_lead_technology_345,Zopim,zopim +crm_iap_lead_technology_346,Zuora,zuora +crm_iap_lead_technology_347,Zype,zype diff --git a/addons/crm_iap_lead/data/ir_sequence_data.xml b/addons/crm_iap_lead/data/ir_sequence_data.xml new file mode 100644 index 00000000000..89be4f1607b --- /dev/null +++ b/addons/crm_iap_lead/data/ir_sequence_data.xml @@ -0,0 +1,14 @@ + + + + + + Lead Mining Request + crm.iap.lead.mining.request + LMR + 3 + + + + + diff --git a/addons/crm_iap_lead/models/__init__.py b/addons/crm_iap_lead/models/__init__.py index 5fc7bce13c3..d529ee24678 100644 --- a/addons/crm_iap_lead/models/__init__.py +++ b/addons/crm_iap_lead/models/__init__.py @@ -2,3 +2,5 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. from . import crm_lead from . import crm_iap_lead +from . import crm_iap_lead_helpers +from . import crm_iap_lead_mining_request diff --git a/addons/crm_iap_lead/models/crm_iap_lead.py b/addons/crm_iap_lead/models/crm_iap_lead.py index 1383027b560..0e3d60febd9 100644 --- a/addons/crm_iap_lead/models/crm_iap_lead.py +++ b/addons/crm_iap_lead/models/crm_iap_lead.py @@ -48,3 +48,16 @@ class PeopleSeniority(models.Model): @api.depends('name') def name_get(self): return [(seniority.id, seniority.name.replace('_', ' ').title()) for seniority in self] + + +class TechnologyTag(models.Model): + """ Technology Tags of Clearbit """ + _name = 'crm.iap.lead.technology' + _description = 'Technology Tag' + + name = fields.Char(string='Name', required=True, translate=True) + tech_tag = fields.Char(string="Tech Tag", required=True) + + _sql_constraints = [ + ('name_uniq', 'unique (name)', 'Technology tag name already exists!'), + ] diff --git a/addons/crm_iap_lead/models/crm_iap_lead_helpers.py b/addons/crm_iap_lead/models/crm_iap_lead_helpers.py new file mode 100644 index 00000000000..3a42c0cbf97 --- /dev/null +++ b/addons/crm_iap_lead/models/crm_iap_lead_helpers.py @@ -0,0 +1,121 @@ +from math import floor, log10 +from odoo import api, models + + +class CRMHelpers(models.Model): + _name = 'crm.iap.lead.helpers' + _description = 'Helper methods for crm_iap_lead modules' + + @api.model + def notify_no_more_credit(self, service_name, model_name, notification_parameter): + """ + Notify about the number of credit. + In order to avoid to spam people each hour, an ir.config_parameter is set + """ + already_notified = self.env['ir.config_parameter'].sudo().get_param(notification_parameter, False) + if already_notified: + return + mail_template = self.env.ref('crm_iap_lead.lead_generation_no_credits') + iap_account = self.env['iap.account'].search([('service_name', '=', service_name)], limit=1) + # Get the email address of the creators of the records + res = self.env[model_name].search_read([], ['create_uid']) + uids = set(r['create_uid'][0] for r in res if r.get('create_uid')) + res = self.env['res.users'].search_read([('id', 'in', list(uids))], ['email']) + emails = set(r['email'] for r in res if r.get('email')) + + mail_values = mail_template.generate_email(iap_account.id) + mail_values['email_to'] = ','.join(emails) + mail = self.env['mail.mail'].create(mail_values) + mail.send() + self.env['ir.config_parameter'].sudo().set_param(notification_parameter, True) + + @api.model + def lead_vals_from_response(self, lead_type, team_id, tag_ids, user_id, company_data, people_data): + country_id = self.env['res.country'].search([('code', '=', company_data['country_code'])]).id + website_url = 'https://www.%s' % company_data['domain'] if company_data['domain'] else False + lead_vals = { + # Lead vals from record itself + 'type': lead_type, + 'team_id': team_id, + 'tag_ids': [(6, 0, tag_ids)], + 'user_id': user_id, + 'reveal_id': company_data['clearbit_id'], + # Lead vals from data + 'name': company_data['name'], + 'partner_name': company_data['legal_name'] or company_data['name'], + 'email_from': ",".join(company_data['email'] or []), + 'phone': company_data['phone'] or (company_data['phone_numbers'] and company_data['phone_numbers'][0]) or '', + 'website': website_url, + 'street': company_data['location'], + 'city': company_data['city'], + 'zip': company_data['postal_code'], + 'country_id': country_id, + 'state_id': self._find_state_id(company_data['state_code'], country_id), + 'description': self._prepare_lead_description(company_data), + } + + # If type is people then add first contact in lead data + if people_data: + lead_vals.update({ + 'contact_name': people_data[0]['full_name'], + 'email_from': people_data[0]['email'], + 'function': people_data[0]['title'], + }) + return lead_vals + + @api.model + def _find_state_id(self, state_code, country_id): + state_id = self.env['res.country.state'].search([('code', '=', state_code), ('country_id', '=', country_id)]) + if state_id: + return state_id.id + return False + + @api.model + def _prepare_lead_description(self, reveal_data): + description = '' + if reveal_data['sector']: + description += reveal_data['sector'] + if reveal_data['website_title']: + description += '\n' + reveal_data['website_title'] + if reveal_data['twitter_bio']: + description += '\n' + "Twitter Bio: " + reveal_data['twitter_bio'] + if reveal_data['twitter_followers']: + description += ('\nTwitter %s followers, %s \n') % (reveal_data['twitter_followers'], reveal_data['twitter_location'] or '') + + numbers = ['raised', 'market_cap', 'employees', 'estimated_annual_revenue'] + millnames = ['', ' K', ' M', ' B', 'T'] + + def millify(n): + try: + n = float(n) + millidx = max(0, min(len(millnames) - 1, int(floor(0 if n == 0 else log10(abs(n)) / 3)))) + return '{:.0f}{}'.format(n / 10**(3 * millidx), millnames[millidx]) + except Exception: + return n + + for key in numbers: + if reveal_data.get(key): + description += ' %s : %s,' % (key.replace('_', ' ').title(), millify(reveal_data[key])) + return description + + @api.model + def format_data_for_message_post(self, company_data, people_data): + log_data = { + 'twitter': company_data['twitter'], + 'description': company_data['description'], + 'logo': company_data['logo'], + 'name': company_data['name'], + 'phone_numbers': company_data['phone_numbers'], + 'facebook': company_data['facebook'], + 'linkedin': company_data['linkedin'], + 'crunchbase': company_data['crunchbase'], + 'tech': [t.replace('_', ' ').title() for t in company_data['tech']], + 'people_data': people_data, + } + timezone = company_data['timezone'] + if timezone: + log_data.update({ + 'timezone': timezone.replace('_', ' ').title(), + 'timezone_url': company_data['timezone_url'], + }) + return log_data diff --git a/addons/crm_iap_lead/models/crm_iap_lead_mining_request.py b/addons/crm_iap_lead/models/crm_iap_lead_mining_request.py new file mode 100644 index 00000000000..84b23c9151e --- /dev/null +++ b/addons/crm_iap_lead/models/crm_iap_lead_mining_request.py @@ -0,0 +1,233 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +import logging + +from odoo import api, fields, models, _ +from odoo.exceptions import ValidationError +from odoo.addons.iap import jsonrpc, InsufficientCreditError + +_logger = logging.getLogger(__name__) + +DEFAULT_ENDPOINT = 'https://iap-services.odoo.com' + +MAX_LEAD = 200 +MAX_CONTACT = 5 + +CREDIT_PER_COMPANY = 1 +CREDIT_PER_CONTACT = 1 + + +class CRMLeadMiningRequest(models.Model): + _name = 'crm.iap.lead.mining.request' + _description = 'CRM Lead Mining Request' + + def _default_lead_type(self): + if self.env.user.has_group('crm.group_use_lead'): + return 'lead' + else: + return 'opportunity' + + name = fields.Char(string='Request Number', required=True, readonly=True, default=lambda self: _('New'), copy=False) + state = fields.Selection([('draft', 'Draft'), ('done', 'Done'), ('error', 'Error')], string='Status', required=True, default='draft') + + # Request Data + lead_number = fields.Integer(string='Number of Leads', required=True, default=10) + search_type = fields.Selection([('companies', 'Companies'), ('people', 'Companies and their Contacts')], string='Target', required=True, default='companies') + error = fields.Text(string='Error', readonly=True) + + # Lead / Opportunity Data + lead_type = fields.Selection([('lead', 'Lead'), ('opportunity', 'Opportunity')], string='Type', required=True, default=_default_lead_type) + team_id = fields.Many2one('crm.team', string='Sales Team', domain="[('use_opportunities', '=', True)]") + user_id = fields.Many2one('res.users', string='Salesperson') + tag_ids = fields.Many2many('crm.lead.tag', string='Tags') + lead_ids = fields.One2many('crm.lead', 'lead_mining_request_id', string='Generated Lead / Opportunity') + leads_count = fields.Integer(compute='_compute_leads_count', string='Number of Generated Leads') + + # Company Criteria Filter + filter_on_size = fields.Boolean(string='Filter on Size', default=False) + company_size_min = fields.Integer(string='Size', default=1) + company_size_max = fields.Integer(default=1000) + country_ids = fields.Many2many('res.country', string='Countries') + state_ids = fields.Many2many('res.country.state', string='States') + industry_ids = fields.Many2many('crm.iap.lead.industry', string='Industries') + technology_ids = fields.Many2many('crm.iap.lead.technology', string='Technologies') + + # Contact Generation Filter + contact_number = fields.Integer(string='Number of Contacts', default=1) + contact_filter_type = fields.Selection([('role', 'Role'), ('seniority', 'Seniority')], string='Filter on', default='role') + preferred_role_id = fields.Many2one('crm.iap.lead.role', string='Preferred Role') + role_ids = fields.Many2many('crm.iap.lead.role', string='Other Roles') + seniority_id = fields.Many2one('crm.iap.lead.seniority', string='Seniority') + + # Fields for the blue tooltip + lead_number_info = fields.Integer(string='Number of Leads ', related='lead_number', readonly=True) + lead_total_credit = fields.Integer(compute='_compute_lead_total_credit') + contact_credit_per_company = fields.Integer(compute='_compute_contact_credit_per_company') + contact_total_credit = fields.Integer(compute='_compute_contact_total_credit') + + @api.depends('lead_number') + def _compute_lead_total_credit(self): + for req in self: + req.lead_total_credit = CREDIT_PER_COMPANY * req.lead_number + + @api.depends('contact_number') + def _compute_contact_credit_per_company(self): + for req in self: + req.contact_credit_per_company = CREDIT_PER_CONTACT * req.contact_number + + @api.depends('contact_number', 'lead_number') + def _compute_contact_total_credit(self): + for req in self: + req.contact_total_credit = CREDIT_PER_CONTACT * req.contact_number * req.lead_number + + @api.depends('lead_ids') + def _compute_leads_count(self): + for req in self: + req.leads_count = len(req.lead_ids) + + @api.onchange('lead_number') + def _onchange_lead_number(self): + if self.lead_number <= 0: + self.lead_number = 1 + elif self.lead_number > MAX_LEAD: + self.lead_number = MAX_LEAD + + @api.onchange('contact_number') + def _onchange_contact_number(self): + if self.contact_number <= 0: + self.contact_number = 1 + elif self.contact_number > MAX_CONTACT: + self.contact_number = MAX_CONTACT + + @api.onchange('country_ids') + def _onchange_country_ids(self): + self.state_ids = [] + + @api.onchange('company_size_min') + def _onchange_company_size_min(self): + if self.company_size_min <= 0: + self.company_size_min = 1 + elif self.company_size_min > self.company_size_max: + self.company_size_min = self.company_size_max + + @api.onchange('company_size_max') + def _onchange_company_size_max(self): + if self.company_size_max < self.company_size_min: + self.company_size_max = self.company_size_min + + def _prepare_iap_payload(self): + """ + This will prepare the data to send to the server + """ + self.ensure_one() + payload = {'lead_number': self.lead_number, + 'search_type': self.search_type, + 'countries': self.country_ids.mapped('code')} + if self.state_ids: + payload['states'] = self.state_ids.mapped('code') + if self.filter_on_size: + payload.update({'company_size_min': self.company_size_min, + 'company_size_max': self.company_size_max}) + if self.technology_ids: + payload['technology_tags'] = self.technology_ids.mapped('tech_tag') + if self.search_type == 'people': + payload.update({'contact_number': self.contact_number, + 'contact_filter_type': self.contact_filter_type}) + if self.contact_filter_type == 'role': + payload.update({'preferred_role': self.preferred_role_id.reveal_id, + 'other_roles': self.role_ids.mapped('reveal_id')}) + elif self.contact_filter_type == 'seniority': + payload['seniority'] = self.seniority_id.reveal_id + return payload + + def _perform_request(self): + """ + This will perform the request and create the corresponding leads. + The user will be notified if he hasn't enough credits. + """ + server_payload = self._prepare_iap_payload() + reveal_account = self.env['iap.account'].get('reveal') + dbuuid = self.env['ir.config_parameter'].sudo().get_param('database.uuid') + endpoint = self.env['ir.config_parameter'].sudo().get_param('reveal.endpoint', DEFAULT_ENDPOINT) + '/iap/clearbit/1/lead_mining_request' + params = { + 'account_token': reveal_account.account_token, + 'dbuuid': dbuuid, + 'data': server_payload + } + try: + response = jsonrpc(endpoint, params=params, timeout=300) + return response['data'] + except InsufficientCreditError as e: + self.error = 'Insufficient credits. Recharge your account and retry.' + self.state = 'error' + self._cr.commit() + raise e + + @api.multi + def _create_leads_from_response(self, result): + """ This method will get the response from the service and create the leads accordingly """ + self.ensure_one() + lead_vals = [] + messages_to_post = {} + for data in result: + lead_vals.append(self._lead_vals_from_response(data)) + messages_to_post[data['company_data']['clearbit_id']] = self.env['crm.iap.lead.helpers'].format_data_for_message_post(data['company_data'], data.get('people_data')) + leads = self.env['crm.lead'].create(lead_vals) + for lead in leads: + if messages_to_post.get(lead.reveal_id): + lead.message_post_with_view('crm_iap_lead.lead_message_template', values=messages_to_post[lead.reveal_id], subtype_id=self.env.ref('mail.mt_note').id) + + # Methods responsible for format response data into valid odoo lead data + @api.model + def _lead_vals_from_response(self, data): + self.ensure_one() + company_data = data.get('company_data') + people_data = data.get('people_data') + lead_vals = self.env['crm.iap.lead.helpers'].lead_vals_from_response(self.lead_type, self.team_id.id, self.tag_ids.ids, self.user_id.id, company_data, people_data) + lead_vals['lead_mining_request_id'] = self.id + return lead_vals + + @api.multi + def action_draft(self): + self.ensure_one() + self.name = _('New') + self.state = 'draft' + + @api.multi + def action_submit(self): + self.ensure_one() + if self.name == _('New'): + self.name = self.env['ir.sequence'].next_by_code('crm.iap.lead.mining.request') or _('New') + results = self._perform_request() + if results: + self._create_leads_from_response(results) + self.state = 'done' + if self.lead_type == 'lead': + return self.action_get_lead_action() + elif self.lead_type == 'opportunity': + return self.action_get_opportunity_action() + + @api.multi + def action_get_lead_action(self): + self.ensure_one() + action = self.env.ref('crm.crm_lead_all_leads').read()[0] + action['domain'] = [('id', 'in', self.lead_ids.ids), ('type', '=', 'lead')] + action['help'] = _("""

+ No leads found +

+ No leads could be generated according to your search criteria +

""") + return action + + @api.multi + def action_get_opportunity_action(self): + self.ensure_one() + action = self.env.ref('crm.crm_lead_opportunities').read()[0] + action['domain'] = [('id', 'in', self.lead_ids.ids), ('type', '=', 'opportunity')] + action['help'] = _("""

+ No opportunities found +

+ No opportunities could be generated according to your search criteria +

""") + return action diff --git a/addons/crm_iap_lead/models/crm_lead.py b/addons/crm_iap_lead/models/crm_lead.py index 44fabf71571..bbf70174ef7 100644 --- a/addons/crm_iap_lead/models/crm_lead.py +++ b/addons/crm_iap_lead/models/crm_lead.py @@ -8,3 +8,4 @@ class Lead(models.Model): _inherit = 'crm.lead' reveal_id = fields.Char(string='Reveal ID', index=True) + lead_mining_request_id = fields.Many2one('crm.iap.lead.mining.request', string='Lead Mining Request', index=True) diff --git a/addons/crm_iap_lead/security/ir.model.access.csv b/addons/crm_iap_lead/security/ir.model.access.csv index 84384e78140..e4e857bc2d3 100644 --- a/addons/crm_iap_lead/security/ir.model.access.csv +++ b/addons/crm_iap_lead/security/ir.model.access.csv @@ -3,4 +3,5 @@ access_crm_iap_lead_industry,access_crm_iap_lead_industry,model_crm_iap_lead_ind access_crm_iap_lead_role,access_crm_iap_lead_role,model_crm_iap_lead_role,sales_team.group_sale_manager,1,1,1,1 access_crm_iap_lead_seniority,access_crm_iap_lead_seniority,model_crm_iap_lead_seniority,sales_team.group_sale_manager,1,1,1,1 access_crm_iap_lead_technology,access_crm_iap_lead_technology,model_crm_iap_lead_technology,sales_team.group_sale_manager,1,1,1,1 -access_crm_iap_lead_request,access_crm_iap_lead_request,model_crm_iap_lead_request,sales_team.group_sale_manager,1,1,1,1 +access_crm_iap_lead_mining_request,access_crm_iap_lead_mining_request,model_crm_iap_lead_mining_request,sales_team.group_sale_manager,1,1,1,1 +access_crm_iap_lead_helpers,access_crm_iap_lead_helpers,model_crm_iap_lead_helpers,,0,0,0,0 diff --git a/addons/crm_iap_lead/static/src/js/leads_tree_generate_leads.js b/addons/crm_iap_lead/static/src/js/leads_tree_generate_leads.js new file mode 100644 index 00000000000..27059c28893 --- /dev/null +++ b/addons/crm_iap_lead/static/src/js/leads_tree_generate_leads.js @@ -0,0 +1,76 @@ +odoo.define('crm.leads.tree', function (require) { +"use strict"; + var ListController = require('web.ListController'); + var ListView = require('web.ListView'); + + var KanbanController = require('web.KanbanController'); + var KanbanView = require('web.KanbanView'); + + var viewRegistry = require('web.view_registry'); + + function renderGenerateLeadsButton() { + if (this.$buttons) { + var self = this; + var lead_type = self.initialState.getContext()['default_type']; + this.$buttons.on('click', '.o_button_generate_leads', function () { + self.do_action({ + name: 'Generate Leads', + type: 'ir.actions.act_window', + res_model: 'crm.iap.lead.mining.request', + target: 'new', + views: [[false, 'form']], + context: {'is_modal': true, 'default_lead_type': lead_type}, + }); + }); + } + } + + var LeadMiningRequestListController = ListController.extend({ + willStart: function() { + var self = this; + var ready = this.getSession().user_has_group('sales_team.group_sale_manager') + .then(function (is_sale_manager) { + if (is_sale_manager) { + self.buttons_template = 'LeadMiningRequestListView.buttons'; + } + }); + return $.when(this._super.apply(this, arguments), ready); + }, + renderButtons: function () { + this._super.apply(this, arguments); + renderGenerateLeadsButton.apply(this, arguments); + } + }); + + var LeadMiningRequestListView = ListView.extend({ + config: _.extend({}, ListView.prototype.config, { + Controller: LeadMiningRequestListController, + }), + }); + + var LeadMiningRequestKanbanController = KanbanController.extend({ + willStart: function() { + var self = this; + var ready = this.getSession().user_has_group('sales_team.group_sale_manager') + .then(function (is_sale_manager) { + if (is_sale_manager) { + self.buttons_template = 'LeadMiningRequestKanbanView.buttons'; + } + }); + return $.when(this._super.apply(this, arguments), ready); + }, + renderButtons: function () { + this._super.apply(this, arguments); + renderGenerateLeadsButton.apply(this, arguments); + } + }); + + var LeadMiningRequestKanbanView = KanbanView.extend({ + config: _.extend({}, KanbanView.prototype.config, { + Controller: LeadMiningRequestKanbanController, + }), + }); + + viewRegistry.add('crm_iap_lead_mining_request_tree', LeadMiningRequestListView); + viewRegistry.add('crm_iap_lead_mining_request_kanban', LeadMiningRequestKanbanView); +}); diff --git a/addons/crm_iap_lead/static/src/xml/leads_tree_generate_leads_views.xml b/addons/crm_iap_lead/static/src/xml/leads_tree_generate_leads_views.xml new file mode 100644 index 00000000000..b431e02601c --- /dev/null +++ b/addons/crm_iap_lead/static/src/xml/leads_tree_generate_leads_views.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + if (window.odoo._modules.indexOf("base_import") >= 0) { + r.push(context.engine.tools.call(context, 'ImportView.import_button', dict)); + }; + + + + + + + + + if (window.odoo._modules.indexOf("base_import") >= 0) { + r.push(context.engine.tools.call(context, 'ImportView.import_button', dict)); + }; + + + + diff --git a/addons/crm_iap_lead/views/crm_iap_lead_templates.xml b/addons/crm_iap_lead/views/crm_iap_lead_templates.xml new file mode 100644 index 00000000000..d6844a6a242 --- /dev/null +++ b/addons/crm_iap_lead/views/crm_iap_lead_templates.xml @@ -0,0 +1,8 @@ + + +