[ADD] crm_reveal: New IAP module
crm_reveal will allow the automated generation of leads based on the visits of the website.
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import controllers
|
||||
from . import models
|
||||
@@ -0,0 +1,20 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
{
|
||||
'name': 'Lead Generation',
|
||||
'summary': 'Create Leads/Opportunities from your website\'s traffic',
|
||||
'category': 'CRM',
|
||||
'depends': ['iap', 'crm', 'website_form'],
|
||||
'data': [
|
||||
'data/crm.reveal.industry.csv',
|
||||
'data/crm.reveal.role.csv',
|
||||
'data/crm.reveal.seniority.csv',
|
||||
'data/message_template.xml',
|
||||
'data/reveal_data.xml',
|
||||
'security/ir.model.access.csv',
|
||||
'views/crm_lead_view.xml',
|
||||
'views/crm_reveal_views.xml',
|
||||
'views/res_config_settings_views.xml'
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import main
|
||||
@@ -0,0 +1,19 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import json
|
||||
|
||||
from odoo import http
|
||||
from odoo.addons.website_form.controllers.main import WebsiteForm
|
||||
from odoo.http import request
|
||||
|
||||
|
||||
class ContactController(WebsiteForm):
|
||||
|
||||
@http.route('/website_form/<string:model_name>', type='http', auth="public", methods=['POST'], website=True)
|
||||
def website_form(self, model_name, **kwargs):
|
||||
if model_name == 'crm.lead':
|
||||
# Add the ip_address to the request in order to add this to the lead
|
||||
# that will be created. With this, we avoid to create a lead from
|
||||
# reveal if a lead is already created from the contact form.
|
||||
request.params['reveal_ip'] = request.httprequest.remote_addr
|
||||
|
||||
return super(ContactController, self).website_form(model_name, **kwargs)
|
||||
@@ -0,0 +1,238 @@
|
||||
"id",name,reveal_id
|
||||
"crm_reveal_industry_1","3D Printing","1"
|
||||
"crm_reveal_industry_2","Accounting","2"
|
||||
"crm_reveal_industry_3","Aerospace & Defense","3"
|
||||
"crm_reveal_industry_4","Agriculture","4"
|
||||
"crm_reveal_industry_5","Airlines","5"
|
||||
"crm_reveal_industry_6","Alternative Medicine","6"
|
||||
"crm_reveal_industry_7","Animation","7"
|
||||
"crm_reveal_industry_8","Apparel & Footwear","8"
|
||||
"crm_reveal_industry_9","Architecture","9"
|
||||
"crm_reveal_industry_10","Arts","10"
|
||||
"crm_reveal_industry_11","Arts & Crafts","11"
|
||||
"crm_reveal_industry_12","Asset Management","12"
|
||||
"crm_reveal_industry_13","Audio","13"
|
||||
"crm_reveal_industry_14","Automation","14"
|
||||
"crm_reveal_industry_15","Automotive","15"
|
||||
"crm_reveal_industry_16","Banking & Mortgages","16"
|
||||
"crm_reveal_industry_17","Beverages","17"
|
||||
"crm_reveal_industry_18","Biotechnology","18"
|
||||
"crm_reveal_industry_19","Broadcasting","19"
|
||||
"crm_reveal_industry_20","Building Materials","20"
|
||||
"crm_reveal_industry_21","Business Supplies","21"
|
||||
"crm_reveal_industry_22","Chemicals","22"
|
||||
"crm_reveal_industry_23","Civil Engineering","23"
|
||||
"crm_reveal_industry_24","Cloud Services","24"
|
||||
"crm_reveal_industry_25","Communications","25"
|
||||
"crm_reveal_industry_26","Computer Hardware","26"
|
||||
"crm_reveal_industry_27","Construction","27"
|
||||
"crm_reveal_industry_28","Construction Contractors & Services","28"
|
||||
"crm_reveal_industry_29","Consulting & Professional Services","29"
|
||||
"crm_reveal_industry_30","Consumer Discretionary","30"
|
||||
"crm_reveal_industry_31","Consumer Electronics","31"
|
||||
"crm_reveal_industry_32","Consumer Goods","32"
|
||||
"crm_reveal_industry_33","Consumer Staples","33"
|
||||
"crm_reveal_industry_34","Corporate & Business","34"
|
||||
"crm_reveal_industry_35","Cosmetics","35"
|
||||
"crm_reveal_industry_36","Design","36"
|
||||
"crm_reveal_industry_37","E-Commerce & Marketplaces","37"
|
||||
"crm_reveal_industry_38","E-Learning","38"
|
||||
"crm_reveal_industry_39","Education","39"
|
||||
"crm_reveal_industry_40","Electrical","40"
|
||||
"crm_reveal_industry_41","Energy","41"
|
||||
"crm_reveal_industry_42","Energy & Utilities","42"
|
||||
"crm_reveal_industry_43","Entertainment & Recreation","43"
|
||||
"crm_reveal_industry_44","Events","44"
|
||||
"crm_reveal_industry_45","Eyewear","45"
|
||||
"crm_reveal_industry_46","Facilities","46"
|
||||
"crm_reveal_industry_47","Family Services","47"
|
||||
"crm_reveal_industry_48","Finance","48"
|
||||
"crm_reveal_industry_49","Financial Services","49"
|
||||
"crm_reveal_industry_50","Fine Art","50"
|
||||
"crm_reveal_industry_51","Firearms","51"
|
||||
"crm_reveal_industry_52","Fishery","52"
|
||||
"crm_reveal_industry_53","Food","53"
|
||||
"crm_reveal_industry_54","Food Production","54"
|
||||
"crm_reveal_industry_55","Forums","55"
|
||||
"crm_reveal_industry_56","Fundraising","56"
|
||||
"crm_reveal_industry_57","Gambling & Casinos","57"
|
||||
"crm_reveal_industry_58","Government","58"
|
||||
"crm_reveal_industry_59","Ground Transportation","59"
|
||||
"crm_reveal_industry_60","Health & Wellness","60"
|
||||
"crm_reveal_industry_61","Health Care","61"
|
||||
"crm_reveal_industry_62","Higher Education","62"
|
||||
"crm_reveal_industry_63","Home & Furniture","63"
|
||||
"crm_reveal_industry_64","Home Improvement","64"
|
||||
"crm_reveal_industry_65","Human Resources","65"
|
||||
"crm_reveal_industry_66","Import & Export","66"
|
||||
"crm_reveal_industry_67","Industrials & Manufacturing","67"
|
||||
"crm_reveal_industry_68","Information Technology & Services","68"
|
||||
"crm_reveal_industry_69","Insurance","69"
|
||||
"crm_reveal_industry_70","International Relations","70"
|
||||
"crm_reveal_industry_71","International Trade","71"
|
||||
"crm_reveal_industry_72","Internet","72"
|
||||
"crm_reveal_industry_73","Investment","73"
|
||||
"crm_reveal_industry_74","Investment Banking","74"
|
||||
"crm_reveal_industry_75","Investment Management","75"
|
||||
"crm_reveal_industry_76","Jewelry, Watches & Luxury Goods","76"
|
||||
"crm_reveal_industry_77","Judiciary","77"
|
||||
"crm_reveal_industry_78","Law Enforcement","78"
|
||||
"crm_reveal_industry_79","Legal Services","79"
|
||||
"crm_reveal_industry_80","Libraries","80"
|
||||
"crm_reveal_industry_81","Machinery","81"
|
||||
"crm_reveal_industry_82","Maritime","82"
|
||||
"crm_reveal_industry_83","Market Research","83"
|
||||
"crm_reveal_industry_84","Marketing & Advertising","84"
|
||||
"crm_reveal_industry_85","Mechanical Engineering","85"
|
||||
"crm_reveal_industry_86","Media","86"
|
||||
"crm_reveal_industry_87","Medicine","87"
|
||||
"crm_reveal_industry_88","Military","88"
|
||||
"crm_reveal_industry_89","Mining & Metals","89"
|
||||
"crm_reveal_industry_90","Movies & TV","90"
|
||||
"crm_reveal_industry_91","Museums","91"
|
||||
"crm_reveal_industry_92","Music","92"
|
||||
"crm_reveal_industry_93","Nanotechnology","93"
|
||||
"crm_reveal_industry_94","Networking","94"
|
||||
"crm_reveal_industry_95","Non-Profit & Philanthropy","95"
|
||||
"crm_reveal_industry_96","Oil & Gas","96"
|
||||
"crm_reveal_industry_97","Outsourcing","97"
|
||||
"crm_reveal_industry_98","Packaging & Containers","98"
|
||||
"crm_reveal_industry_99","Paper Goods","99"
|
||||
"crm_reveal_industry_100","Payments","100"
|
||||
"crm_reveal_industry_101","Performing Arts","101"
|
||||
"crm_reveal_industry_102","Pharmaceuticals","102"
|
||||
"crm_reveal_industry_103","Pharmacy","103"
|
||||
"crm_reveal_industry_104","Photography","104"
|
||||
"crm_reveal_industry_105","Plastics","105"
|
||||
"crm_reveal_industry_106","Plumbing","106"
|
||||
"crm_reveal_industry_107","Political Organization","107"
|
||||
"crm_reveal_industry_108","Pornography","108"
|
||||
"crm_reveal_industry_109","Primary & Secondary Education","109"
|
||||
"crm_reveal_industry_110","Printing","110"
|
||||
"crm_reveal_industry_111","Public Relations","111"
|
||||
"crm_reveal_industry_112","Publishing","112"
|
||||
"crm_reveal_industry_113","Ranching","113"
|
||||
"crm_reveal_industry_114","Real Estate","114"
|
||||
"crm_reveal_industry_115","Religion","115"
|
||||
"crm_reveal_industry_116","Renewables & Environment","116"
|
||||
"crm_reveal_industry_117","Restaurants","117"
|
||||
"crm_reveal_industry_118","Retail","118"
|
||||
"crm_reveal_industry_119","Sanitization Services","119"
|
||||
"crm_reveal_industry_120","Scientific & Academic Research","120"
|
||||
"crm_reveal_industry_121","Security","121"
|
||||
"crm_reveal_industry_122","Services","122"
|
||||
"crm_reveal_industry_123","Shipbuilding","123"
|
||||
"crm_reveal_industry_124","Shipping & Logistics","124"
|
||||
"crm_reveal_industry_125","Society","125"
|
||||
"crm_reveal_industry_126","Sporting Goods","126"
|
||||
"crm_reveal_industry_127","Sports & Fitness","127"
|
||||
"crm_reveal_industry_128","Stores","128"
|
||||
"crm_reveal_industry_129","Talent Agencies","129"
|
||||
"crm_reveal_industry_130","Technology","130"
|
||||
"crm_reveal_industry_131","Telecommunications","131"
|
||||
"crm_reveal_industry_132","Textiles","132"
|
||||
"crm_reveal_industry_133","Tobacco","133"
|
||||
"crm_reveal_industry_134","Tools","134"
|
||||
"crm_reveal_industry_135","Translation","135"
|
||||
"crm_reveal_industry_136","Transportation","136"
|
||||
"crm_reveal_industry_137","Travel & Leisure","137"
|
||||
"crm_reveal_industry_138","Utilities","138"
|
||||
"crm_reveal_industry_139","Venture Capital","139"
|
||||
"crm_reveal_industry_140","Veterinary","140"
|
||||
"crm_reveal_industry_141","Video Games","141"
|
||||
"crm_reveal_industry_142","Warehousing","142"
|
||||
"crm_reveal_industry_143","Web Services & Apps","143"
|
||||
"crm_reveal_industry_144","Wholesale","144"
|
||||
"crm_reveal_industry_145","Financials","145"
|
||||
"crm_reveal_industry_146","Industrials","146"
|
||||
"crm_reveal_industry_147","Information Technology","147"
|
||||
"crm_reveal_industry_148","Materials","148"
|
||||
"crm_reveal_industry_149","Telecommunication Services","149"
|
||||
"crm_reveal_industry_150","Consumer Services","150"
|
||||
"crm_reveal_industry_151","Diversified Consumer Services","151"
|
||||
"crm_reveal_industry_152","Retailing","152"
|
||||
"crm_reveal_industry_153","Food & Staples Retailing","153"
|
||||
"crm_reveal_industry_154","Food, Beverage & Tobacco","154"
|
||||
"crm_reveal_industry_155","Household & Personal Products","155"
|
||||
"crm_reveal_industry_156","Energy Equipment & Services","156"
|
||||
"crm_reveal_industry_157","Banks","157"
|
||||
"crm_reveal_industry_158","Diversified Financial Services","158"
|
||||
"crm_reveal_industry_159","Diversified Financials","159"
|
||||
"crm_reveal_industry_160","Health Care Equipment & Services","160"
|
||||
"crm_reveal_industry_161","Pharmaceuticals, Biotechnology & Life Sciences","161"
|
||||
"crm_reveal_industry_162","Capital Goods","162"
|
||||
"crm_reveal_industry_163","Commercial & Professional Services","163"
|
||||
"crm_reveal_industry_164","Semiconductors & Semiconductor Equipment","164"
|
||||
"crm_reveal_industry_165","Software & Services","165"
|
||||
"crm_reveal_industry_166","Technology Hardware & Equipment","166"
|
||||
"crm_reveal_industry_167","Construction Materials","167"
|
||||
"crm_reveal_industry_168","Independent Power and Renewable Electricity Producers","168"
|
||||
"crm_reveal_industry_169","Hotels, Restaurants & Leisure","169"
|
||||
"crm_reveal_industry_170","Education Services","170"
|
||||
"crm_reveal_industry_171","Specialized Consumer Services","171"
|
||||
"crm_reveal_industry_172","Distributors","172"
|
||||
"crm_reveal_industry_173","Specialty Retail","173"
|
||||
"crm_reveal_industry_174","Food Products","174"
|
||||
"crm_reveal_industry_175","Personal Products","175"
|
||||
"crm_reveal_industry_176","Gas Utilities","176"
|
||||
"crm_reveal_industry_177","Capital Markets","177"
|
||||
"crm_reveal_industry_178","Health Care Equipment & Supplies","178"
|
||||
"crm_reveal_industry_179","Health Care Providers & Services","179"
|
||||
"crm_reveal_industry_180","Life Sciences Tools & Services","180"
|
||||
"crm_reveal_industry_181","Commercial Services & Supplies","181"
|
||||
"crm_reveal_industry_182","Construction & Engineering","182"
|
||||
"crm_reveal_industry_183","Electrical Equipment","183"
|
||||
"crm_reveal_industry_184","Industrial Conglomerates","184"
|
||||
"crm_reveal_industry_185","Trading Companies & Distributors","185"
|
||||
"crm_reveal_industry_186","Professional Services","186"
|
||||
"crm_reveal_industry_187","Air Freight & Logistics","187"
|
||||
"crm_reveal_industry_188","Marine","188"
|
||||
"crm_reveal_industry_189","Road & Rail","189"
|
||||
"crm_reveal_industry_190","Internet Software & Services","190"
|
||||
"crm_reveal_industry_191","IT Services","191"
|
||||
"crm_reveal_industry_192","Software","192"
|
||||
"crm_reveal_industry_193","Communications Equipment","193"
|
||||
"crm_reveal_industry_194","Electronic Equipment, Instruments & Components","194"
|
||||
"crm_reveal_industry_195","Technology Hardware, Storage & Peripherals","195"
|
||||
"crm_reveal_industry_196","Containers & Packaging","196"
|
||||
"crm_reveal_industry_197","Metals & Mining","197"
|
||||
"crm_reveal_industry_198","Paper & Forest Products","198"
|
||||
"crm_reveal_industry_199","Diversified Telecommunication Services","199"
|
||||
"crm_reveal_industry_200","Wireless Telecommunication Services","200"
|
||||
"crm_reveal_industry_201","Renewable Electricity","201"
|
||||
"crm_reveal_industry_202","Electric Utilities","202"
|
||||
"crm_reveal_industry_203","Casinos & Gaming","203"
|
||||
"crm_reveal_industry_204","Leisure Facilities","204"
|
||||
"crm_reveal_industry_205","Advertising","205"
|
||||
"crm_reveal_industry_206","Movies & Entertainment","206"
|
||||
"crm_reveal_industry_207","Home Improvement Retail","207"
|
||||
"crm_reveal_industry_208","Homefurnishing Retail","208"
|
||||
"crm_reveal_industry_209","Food Retail","209"
|
||||
"crm_reveal_industry_210","Agricultural Products","210"
|
||||
"crm_reveal_industry_211","Packaged Foods & Meats","211"
|
||||
"crm_reveal_industry_212","Asset Management & Custody Banks","212"
|
||||
"crm_reveal_industry_213","Diversified Capital Markets","213"
|
||||
"crm_reveal_industry_214","Investment Banking & Brokerage","214"
|
||||
"crm_reveal_industry_215","Health Care Services","215"
|
||||
"crm_reveal_industry_216","Commercial Printing","216"
|
||||
"crm_reveal_industry_217","Industrial Machinery","217"
|
||||
"crm_reveal_industry_218","Consulting","218"
|
||||
"crm_reveal_industry_219","Human Resource & Employment Services","219"
|
||||
"crm_reveal_industry_220","Research & Consulting Services","220"
|
||||
"crm_reveal_industry_221","Semiconductors","221"
|
||||
"crm_reveal_industry_222","Data Processing & Outsourced Services","222"
|
||||
"crm_reveal_industry_223","Graphic Design","223"
|
||||
"crm_reveal_industry_224","Computer Networking","224"
|
||||
"crm_reveal_industry_225","Commodity Chemicals","225"
|
||||
"crm_reveal_industry_226","Gold","226"
|
||||
"crm_reveal_industry_227","Paper Products","227"
|
||||
"crm_reveal_industry_228","Integrated Telecommunication Services","228"
|
||||
"crm_reveal_industry_229","Renewable Energy","229"
|
||||
"crm_reveal_industry_230","B2B","230"
|
||||
"crm_reveal_industry_231","B2C","231"
|
||||
"crm_reveal_industry_232","E-Commerce","232"
|
||||
"crm_reveal_industry_233","Enterprise","233"
|
||||
"crm_reveal_industry_234","ISP","234"
|
||||
"crm_reveal_industry_235","Marketplace","235"
|
||||
"crm_reveal_industry_236","Mobile","236"
|
||||
"crm_reveal_industry_237","SAAS","237"
|
||||
|
@@ -0,0 +1,23 @@
|
||||
"id",name,reveal_id
|
||||
"crm_reveal_role_1","ceo","ceo"
|
||||
"crm_reveal_role_2","communications","communications"
|
||||
"crm_reveal_role_3","consulting","consulting"
|
||||
"crm_reveal_role_4","customer_service","customer_service"
|
||||
"crm_reveal_role_5","education","education"
|
||||
"crm_reveal_role_6","engineering","engineering"
|
||||
"crm_reveal_role_7","finance","finance"
|
||||
"crm_reveal_role_8","founder","founder"
|
||||
"crm_reveal_role_9","health_professional","health_professional"
|
||||
"crm_reveal_role_10","human_resources","human_resources"
|
||||
"crm_reveal_role_11","information_technology","information_technology"
|
||||
"crm_reveal_role_12","legal","legal"
|
||||
"crm_reveal_role_13","marketing","marketing"
|
||||
"crm_reveal_role_14","operations","operations"
|
||||
"crm_reveal_role_15","owner","owner"
|
||||
"crm_reveal_role_16","president","president"
|
||||
"crm_reveal_role_17","product","product"
|
||||
"crm_reveal_role_18","public_relations","public_relations"
|
||||
"crm_reveal_role_19","real_estate","real_estate"
|
||||
"crm_reveal_role_20","recruiting","recruiting"
|
||||
"crm_reveal_role_21","research","research"
|
||||
"crm_reveal_role_22","sale","sale"
|
||||
|
@@ -0,0 +1,4 @@
|
||||
"id",name,reveal_id
|
||||
"crm_reveal_seniority_1","director","director"
|
||||
"crm_reveal_seniority_2","executive","executive"
|
||||
"crm_reveal_seniority_3","manager","manager"
|
||||
|
@@ -0,0 +1,123 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<odoo>
|
||||
<template id="lead_message_template">
|
||||
<p>Opportunity created by Odoo Lead Generation</p>
|
||||
<div style="width:80%;background-color:#ffffff;padding:15px;margin:16px 10%;">
|
||||
<table style="width:100%">
|
||||
<tr>
|
||||
<td></td>
|
||||
<td style="padding: 25px;" rowspan="6">
|
||||
<img style="max-width: 80px;" t-attf-src="{{logo}}"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 0 0 5px 0;">
|
||||
<span style="font-size:25px;vertical-align: middle;">
|
||||
<b><t t-esc="name"/></b>
|
||||
</span>
|
||||
<span style="">
|
||||
<a t-if="twitter" style="margin: 0 0 0 8px;" target="blank" t-attf-href="http://www.twitter.com/{{twitter}}">
|
||||
<img src="/web_editor/font_to_img/61569/rgb(0,132,180)/22"/>
|
||||
</a>
|
||||
<a t-if="facebook" style="margin: 0 0 0 8px;" target="blank" t-attf-href="http://www.facebook.com/{{facebook}}">
|
||||
<img src="/web_editor/font_to_img/61570/rgb(59,89,152)/22"/>
|
||||
</a>
|
||||
<a t-if="linkedin" style="margin: 0 0 0 8px;" target="blank" t-attf-href="https://www.linkedin.com/{{linkedin}}">
|
||||
<img src="/web_editor/font_to_img/61580/rgb(0,119,181)/22"/>
|
||||
</a>
|
||||
<a t-if="crunchbase" style="margin: 0 0 0 8px;" target="blank" t-attf-href="https://www.crunchbase.com/{{crunchbase}}">
|
||||
<img width="18px" height="18px" src="/crm_reveal/static/img/crunchbase.ico"/>
|
||||
</a>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 0 0 5px 0;">
|
||||
<span><t t-esc="description"/></span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td t-if="timezone" style="padding: 0 0 5px 0;">
|
||||
<img src="/web_editor/font_to_img/61463/rgb(135,90,123)/16"/>
|
||||
<b>Timezone : </b>
|
||||
<span><t t-esc="timezone"/></span>
|
||||
<span>
|
||||
<a target="blank" t-attf-href="https://www.timeanddate.com/worldclock/{{timezone_link}}">(Time Now)</a>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td t-if="phone_numbers" style="padding: 0 0 5px 0;">
|
||||
<img src="/web_editor/font_to_img/61589/rgb(135,90,123)/16"/>
|
||||
<b>Phone :</b>
|
||||
<t t-foreach="phone_numbers" t-as="phone_number">
|
||||
<span style="margin:0 5px;">
|
||||
<a target="blank" t-attf-href="tel:{{phone_number}}"><t t-esc="phone_number"/></a>
|
||||
</span>
|
||||
</t>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td t-if="tech">
|
||||
<b><img src="/web_editor/font_to_img/61874/rgb(135,90,123)/16"/> Technology Used :</b>
|
||||
<t t-foreach="tech" t-as="t">
|
||||
<label style="font-weight:normal;padding: 2px 10px;background-color: #eeeeee;margin: 0 0 4px 2px;border-radius: 13px;">
|
||||
<t t-esc="t"/>
|
||||
</label>
|
||||
</t>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<div t-if="people_data" style="font-size:16px;margin: 9px 0;">
|
||||
<b>Contacts</b>
|
||||
</div>
|
||||
<table style="width:100%;
|
||||
border-top-style: solid;border-top-color: #eeeeee;border-top-width: 1px;
|
||||
border-bottom-style: solid;border-bottom-color: #eeeeee;border-bottom-width: 1px;
|
||||
border-left-style: solid;border-left-color: #eeeeee;border-left-width: 1px;
|
||||
border-right-style: solid;border-right-color: #eeeeee;border-right-width: 1px;" t-if="people_data">
|
||||
<thead>
|
||||
<tr style="background-color: #eeeeee">
|
||||
<th style="padding: 5px">
|
||||
Name
|
||||
</th>
|
||||
<th style="padding: 5px">
|
||||
Title
|
||||
</th>
|
||||
<th style="padding: 5px">
|
||||
<img src="web_editor/font_to_img/61664/rgb(102,102,102)/13"/>
|
||||
Email
|
||||
</th>
|
||||
<th style="padding: 5px">
|
||||
<img src="web_editor/font_to_img/61589/rgb(102,102,102)/13"/>
|
||||
Phone
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<t t-foreach="people_data" t-as="people">
|
||||
<tr t-att-style="people_odd and 'background-color:#eeeeee' or None">
|
||||
<td style="padding: 5px">
|
||||
<t t-esc="people['full_name'] or ''"/>
|
||||
</td>
|
||||
<td style="padding: 5px">
|
||||
<t t-esc="people['title'] or ''"/>
|
||||
</td>
|
||||
<td style="padding: 5px">
|
||||
<a t-if="people['email']" t-attf-href="mailto:{{people['email']}}" target="_top">
|
||||
<t t-esc="people['email']"/>
|
||||
</a>
|
||||
</td>
|
||||
<td style="padding: 5px">
|
||||
<a t-if="people['phone']" t-attf-href="tel:{{people['phone']}}">
|
||||
<t t-esc="people['phone']"/>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</t>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</template>
|
||||
</odoo>
|
||||
@@ -0,0 +1,40 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
<!-- Scheduler for Lead Generation -->
|
||||
<record id="ir_cron_crm_reveal_lead" model="ir.cron">
|
||||
<field name="name">Lead Generation: Leads/Opportunities Generation</field>
|
||||
<field name="model_id" ref="model_crm_reveal_rule"/>
|
||||
<field name="state">code</field>
|
||||
<field name="code">model._process_lead_generation()</field>
|
||||
<field name="user_id" ref="base.user_root"/>
|
||||
<field name="interval_number">1</field>
|
||||
<field name="interval_type">days</field>
|
||||
<field name="numbercall">-1</field>
|
||||
<field name="doall" eval="False"/>
|
||||
</record>
|
||||
|
||||
<record id="reveal_no_credits" model="mail.template">
|
||||
<field name="name">IAP Lead Generation Notification</field>
|
||||
<field name="email_from">iap@odoo.com</field>
|
||||
<field name="email_to">iap@odoo.com</field>
|
||||
<field name="subject">IAP Lead Generation Notification</field>
|
||||
<field name="model_id" ref="iap.model_iap_account"/>
|
||||
<field name="body_html">
|
||||
<![CDATA[
|
||||
<p>Dear,</p>
|
||||
<p>There is no more credits on your IAP Lead Generation account.<br/>
|
||||
You can charge your IAP Lead Generation account in the settings of the CRM app.<br/>
|
||||
|
||||
<p>Best regards,</p>
|
||||
<p>Odoo S.A.</p>
|
||||
]]>
|
||||
</field>
|
||||
</record>
|
||||
</data>
|
||||
|
||||
<function model="ir.model.fields" name="formbuilder_whitelist">
|
||||
<value>crm.lead</value>
|
||||
<value eval="['reveal_ip']"/>
|
||||
</function>
|
||||
</odoo>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
from . import crm_lead
|
||||
from . import crm_reveal_rule
|
||||
from . import crm_reveal_view
|
||||
from . import ir
|
||||
from . import res_config_settings
|
||||
@@ -0,0 +1,13 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class Lead(models.Model):
|
||||
_inherit = 'crm.lead'
|
||||
|
||||
reveal_ip = fields.Char(string='IP Address')
|
||||
reveal_iap_credits = fields.Integer(string='IAP Credits')
|
||||
reveal_rule_id = fields.Many2one('crm.reveal.rule', string='Lead Generation Rule', index=True)
|
||||
reveal_id = fields.Char(string='Reveal ID', index=True)
|
||||
@@ -0,0 +1,517 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import datetime
|
||||
import itertools
|
||||
import logging
|
||||
import re
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from math import floor, log10
|
||||
|
||||
from odoo import api, fields, models, tools, _
|
||||
from odoo.addons.iap import jsonrpc
|
||||
from odoo.addons.crm.models import crm_stage
|
||||
from odoo.exceptions import ValidationError
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
DEFAULT_ENDPOINT = 'https://iap-services.odoo.com'
|
||||
DEFAULT_REVEAL_BATCH_LIMIT = 25
|
||||
DEFAULT_REVEAL_MONTH_VALID = 6
|
||||
|
||||
class CRMRevealRule(models.Model):
|
||||
_name = 'crm.reveal.rule'
|
||||
_description = 'CRM Lead Generation Rules'
|
||||
_order = 'sequence'
|
||||
|
||||
name = fields.Char(string='Rule Name', required=True)
|
||||
active = fields.Boolean(default=True)
|
||||
|
||||
# Website Traffic Filter
|
||||
country_ids = fields.Many2many('res.country', string='Countries', help='Only visitors of following countries will be converted into leads/opportunities (using GeoIP).')
|
||||
regex_url = fields.Char(string='URL Expression', help='Regex to track website pages. Leave empty to track the entire website, or / to target the homepage. Example: /page* to track all the pages which begin with /page')
|
||||
sequence = fields.Integer(help='Used to order the rules with same URL and countries. '
|
||||
'Rules with a lower sequence number will be processed first.')
|
||||
|
||||
# Company Criteria Filter
|
||||
industry_tag_ids = fields.Many2many('crm.reveal.industry', string='Industry Tags', help='Leave empty to always match. Odoo will not create lead if no match')
|
||||
company_size_min = fields.Integer(string='Min Company Size', help="Leave it as 0 if you don't want to use this filter.")
|
||||
company_size_max = fields.Integer(string='Max Company Size', help="Leave it as 0 if you don't want to use this filter.")
|
||||
|
||||
# Contact Generation Filter
|
||||
preferred_role_id = fields.Many2one('crm.reveal.role', string='Preferred Role')
|
||||
other_role_ids = fields.Many2many('crm.reveal.role', string='Other Roles')
|
||||
seniority_id = fields.Many2one('crm.reveal.seniority', string='Seniority')
|
||||
extra_contacts = fields.Integer(string='Extra Contacts', help='This is the number of extra contacts to track if their role and seniority match your criteria.Their details will show up in the history thread of generated leads/opportunities. One credit is consumed per tracked contact.')
|
||||
|
||||
calculate_credits = fields.Integer(compute='_compute_credit_count', string='Credit Used', readonly=True)
|
||||
|
||||
# Lead / Opportunity Data
|
||||
lead_for = fields.Selection([('companies', 'Companies'), ('people', 'Companies + Contacts')], string='Data Tracking', required=True, default='companies', help='If you track company data, one credit will be consumed per lead/opportunity created. If you track company and contacts data, two credits will be consumed. Such data will be visible in the lead/opportunity.')
|
||||
lead_type = fields.Selection([('lead', 'Lead'), ('opportunity', 'Opportunity')], string='Type', required=True, default='opportunity')
|
||||
suffix = fields.Char(string='Suffix', help='This will be appended in name of generated lead so you can identify lead/opportunity is generated with this rule')
|
||||
team_id = fields.Many2one('crm.team', string='Sales Channel')
|
||||
tag_ids = fields.Many2many('crm.lead.tag', string='Tags')
|
||||
user_id = fields.Many2one('res.users', string='Salesperson')
|
||||
priority = fields.Selection(crm_stage.AVAILABLE_PRIORITIES, string='Priority')
|
||||
lead_ids = fields.One2many('crm.lead', 'reveal_rule_id', string='Generated Lead / Opportunity')
|
||||
leads_count = fields.Integer(compute='_compute_leads_count', string='Number of Generated Leads')
|
||||
opportunity_count = fields.Integer(compute='_compute_leads_count', string='Number of Generated Opportunity')
|
||||
|
||||
# This limits the number of extra contact.
|
||||
# Even if more than 5 extra contacts provided service will return only 5 contacts (see service module for more)
|
||||
_sql_constraints = [
|
||||
('limit_extra_contacts', 'check(extra_contacts >= 0 and extra_contacts <= 5)', 'Maximum 5 extra contacts are allowed!'),
|
||||
]
|
||||
|
||||
@api.constrains('regex_url')
|
||||
def _check_regex_url(self):
|
||||
try:
|
||||
if self.regex_url:
|
||||
re.compile(self.regex_url)
|
||||
except Exception:
|
||||
raise ValidationError(_('Enter Valid Regex.'))
|
||||
|
||||
@api.model
|
||||
def create(self, vals):
|
||||
self.clear_caches() # Clear the cache in order to recompute _get_active_rules
|
||||
return super(CRMRevealRule, self).create(vals)
|
||||
|
||||
def write(self, vals):
|
||||
fields_set = {
|
||||
'country_ids', 'regex_url', 'active'
|
||||
}
|
||||
if set(vals.keys()) & fields_set:
|
||||
self.clear_caches() # Clear the cache in order to recompute _get_active_rules
|
||||
return super(CRMRevealRule, self).write(vals)
|
||||
|
||||
def unlink(self):
|
||||
self.clear_caches() # Clear the cache in order to recompute _get_active_rules
|
||||
return super(CRMRevealRule, self).unlink()
|
||||
|
||||
@api.depends('extra_contacts', 'lead_for')
|
||||
def _compute_credit_count(self):
|
||||
""" Computes maximum IAP credit can be consumed per lead """
|
||||
credit = 1
|
||||
if self.lead_for == 'people':
|
||||
credit += 1
|
||||
if self.extra_contacts:
|
||||
credit += self.extra_contacts
|
||||
self.calculate_credits = credit
|
||||
|
||||
def _compute_leads_count(self):
|
||||
leads = self.env['crm.lead'].read_group([
|
||||
('reveal_rule_id', 'in', self.ids)
|
||||
], fields=['reveal_rule_id', 'type'], groupby=['reveal_rule_id', 'type'], lazy=False)
|
||||
mapping = {(lead['reveal_rule_id'][0], lead['type']): lead['__count'] for lead in leads}
|
||||
for rule in self:
|
||||
rule.leads_count = mapping.get((rule.id, 'lead'), 0)
|
||||
rule.opportunity_count = mapping.get((rule.id, 'opportunity'), 0)
|
||||
|
||||
def action_get_lead_tree_view(self):
|
||||
action = self.env.ref('crm.crm_lead_all_leads').read()[0]
|
||||
action['domain'] = [('id', 'in', self.lead_ids.ids), ('type', '=', 'lead')]
|
||||
return action
|
||||
|
||||
def action_get_opportunity_tree_view(self):
|
||||
action = self.env.ref('crm.crm_lead_opportunities').read()[0]
|
||||
action['domain'] = [('id', 'in', self.lead_ids.ids), ('type', '=', 'opportunity')]
|
||||
return action
|
||||
|
||||
@api.model
|
||||
@tools.ormcache()
|
||||
def _get_active_rules(self):
|
||||
"""
|
||||
Returns informations about the all rules.
|
||||
The return is in the form :
|
||||
{
|
||||
'country_rules': {
|
||||
'BE': [0, 1],
|
||||
'US': [0]
|
||||
},
|
||||
'rules': [
|
||||
{
|
||||
'id': 0,
|
||||
'url': ***,
|
||||
'country_codes': ['BE', 'US']
|
||||
},
|
||||
{
|
||||
'id': 1,
|
||||
'url': ***,
|
||||
'country_codes': ['BE']
|
||||
}
|
||||
]
|
||||
}
|
||||
"""
|
||||
country_rules = {}
|
||||
rules_records = self.search([])
|
||||
rules = []
|
||||
# Fixes for special cases
|
||||
for rule in rules_records:
|
||||
regex_url = rule['regex_url']
|
||||
if not regex_url:
|
||||
regex_url = '.*' # for all pages if url not given
|
||||
elif regex_url == '/':
|
||||
regex_url = '.*/$' # for home
|
||||
countries = rule.country_ids.mapped('code')
|
||||
rules.append({
|
||||
'id': rule.id,
|
||||
'regex': regex_url,
|
||||
'country_codes': countries
|
||||
})
|
||||
for country in countries:
|
||||
country_rules = self._add_to_country(country_rules, country, len(rules) - 1)
|
||||
return {
|
||||
'country_rules': country_rules,
|
||||
'rules': rules,
|
||||
}
|
||||
|
||||
def _add_to_country(self, country_rules, country, rule_index):
|
||||
"""
|
||||
Add the rule index to the country code in the country_rules
|
||||
"""
|
||||
if country not in country_rules:
|
||||
country_rules[country] = []
|
||||
country_rules[country].append(rule_index)
|
||||
return country_rules
|
||||
|
||||
def _match_url(self, url, country_code, rules_excluded):
|
||||
"""
|
||||
Return the matching rule based on the country and URL.
|
||||
"""
|
||||
all_rules = self._get_active_rules()
|
||||
rules_id = all_rules['country_rules'].get(country_code, [])
|
||||
rules_matched = []
|
||||
for rule_index in rules_id:
|
||||
rule = all_rules['rules'][rule_index]
|
||||
if str(rule['id']) not in rules_excluded and re.search(rule['regex'], url):
|
||||
rules_matched.append(rule)
|
||||
return rules_matched
|
||||
|
||||
@api.model
|
||||
def _process_lead_generation(self, autocommit=True):
|
||||
""" Cron Job for lead generation from page view """
|
||||
_logger.info('Start Reveal Lead Generation')
|
||||
self.env['crm.reveal.view']._clean_reveal_views()
|
||||
self._unlink_unrelevant_reveal_view()
|
||||
reveal_views = self._get_reveal_views_to_process()
|
||||
view_count = 0
|
||||
while reveal_views:
|
||||
view_count += len(reveal_views)
|
||||
server_payload = self._prepare_iap_payload(dict(reveal_views))
|
||||
enough_credit = self._perform_reveal_service(server_payload)
|
||||
if autocommit:
|
||||
# auto-commit for batch processing
|
||||
self._cr.commit()
|
||||
if enough_credit:
|
||||
reveal_views = self._get_reveal_views_to_process()
|
||||
else:
|
||||
reveal_views = False
|
||||
_logger.info('End Reveal Lead Generation - %s views processed', view_count)
|
||||
|
||||
@api.model
|
||||
def _unlink_unrelevant_reveal_view(self):
|
||||
"""
|
||||
We don't want to create the lead if in past (<6 months) we already
|
||||
created lead with given IP. So, we unlink crm.reveal.view with same IP
|
||||
as a already created lead.
|
||||
"""
|
||||
months_valid = self.env['ir.config_parameter'].sudo().get_param('reveal.lead_month_valid', DEFAULT_REVEAL_MONTH_VALID)
|
||||
try:
|
||||
months_valid = int(months_valid)
|
||||
except ValueError:
|
||||
months_valid = DEFAULT_REVEAL_MONTH_VALID
|
||||
domain = []
|
||||
domain.append(('reveal_ip', '!=', False))
|
||||
domain.append(('create_date', '>', fields.Datetime.to_string(datetime.date.today() - relativedelta(months=months_valid))))
|
||||
leads = self.env['crm.lead'].with_context(active_test=False).search(domain)
|
||||
self.env['crm.reveal.view'].search([('reveal_ip', 'in', [lead.reveal_ip for lead in leads])]).unlink()
|
||||
|
||||
@api.model
|
||||
def _get_reveal_views_to_process(self):
|
||||
""" Return list of reveal rule ids grouped by IPs """
|
||||
batch_limit = self.env['ir.config_parameter'].sudo().get_param('reveal.fetch_limit', DEFAULT_REVEAL_BATCH_LIMIT)
|
||||
try:
|
||||
batch_limit = int(batch_limit)
|
||||
except ValueError:
|
||||
batch_limit = DEFAULT_REVEAL_BATCH_LIMIT
|
||||
|
||||
query = """
|
||||
SELECT v.reveal_ip, array_agg(v.reveal_rule_id ORDER BY r.sequence)
|
||||
FROM crm_reveal_view v
|
||||
INNER JOIN crm_reveal_rule r
|
||||
ON v.reveal_rule_id = r.id
|
||||
WHERE v.reveal_state='to_process'
|
||||
GROUP BY v.reveal_ip
|
||||
LIMIT %d
|
||||
""" % batch_limit
|
||||
|
||||
self.env.cr.execute(query)
|
||||
return self.env.cr.fetchall()
|
||||
|
||||
def _prepare_iap_payload(self, pgv):
|
||||
""" This will prepare the page view and returns payload
|
||||
Payload sample
|
||||
{
|
||||
ips: {
|
||||
'192.168.1.1': [1,4],
|
||||
'192.168.1.6': [2,4]
|
||||
},
|
||||
rules: {
|
||||
1: {rule_data},
|
||||
2: {rule_data},
|
||||
4: {rule_data}
|
||||
}
|
||||
}
|
||||
"""
|
||||
new_list = list(set(itertools.chain.from_iterable(pgv.values())))
|
||||
rule_records = self.browse(new_list)
|
||||
return {
|
||||
'ips': pgv,
|
||||
'rules': rule_records._get_rules_payload()
|
||||
}
|
||||
|
||||
def _get_rules_payload(self):
|
||||
company_country = self.env.user.company_id.country_id
|
||||
rule_payload = {}
|
||||
for rule in self:
|
||||
data = {
|
||||
'rule_id': rule.id,
|
||||
'lead_for': rule.lead_for,
|
||||
'countries': rule.country_ids.mapped('code'),
|
||||
'company_size_min': rule.company_size_min,
|
||||
'company_size_max': rule.company_size_max,
|
||||
'industry_tags': rule.industry_tag_ids.mapped('reveal_id'),
|
||||
'user_country': company_country and company_country.code or False
|
||||
}
|
||||
if rule.lead_for == 'people':
|
||||
data.update({
|
||||
'preferred_role': rule.preferred_role_id.reveal_id or '',
|
||||
'other_roles': rule.other_role_ids.mapped('reveal_id'),
|
||||
'seniority': rule.seniority_id.reveal_id or '',
|
||||
'extra_contacts': rule.extra_contacts
|
||||
})
|
||||
rule_payload[rule.id] = data
|
||||
return rule_payload
|
||||
|
||||
def _perform_reveal_service(self, server_payload):
|
||||
result = False
|
||||
account_token = self.env['iap.account'].get('reveal')
|
||||
endpoint = self.env['ir.config_parameter'].sudo().get_param('reveal.endpoint', DEFAULT_ENDPOINT) + '/iap/clearbit/1/reveal'
|
||||
params = {
|
||||
'account_token': account_token.account_token,
|
||||
'data': server_payload
|
||||
}
|
||||
result = jsonrpc(endpoint, params=params)
|
||||
for res in result.get('reveal_data', []):
|
||||
if not res.get('not_found'):
|
||||
lead = self._create_lead_from_response(res)
|
||||
self.env['crm.reveal.view'].search([('reveal_ip', '=', res['ip'])]).unlink()
|
||||
else:
|
||||
self.env['crm.reveal.view'].search([('reveal_ip', '=', res['ip'])]).write({
|
||||
'reveal_state': 'not_found'
|
||||
})
|
||||
if result.get('credit_error'):
|
||||
self._notify_no_more_credit()
|
||||
return False
|
||||
else:
|
||||
self.env['ir.config_parameter'].sudo().set_param('reveal.already_notified', False)
|
||||
return True
|
||||
|
||||
def _notify_no_more_credit(self):
|
||||
"""
|
||||
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('reveal.already_notified', False)
|
||||
if already_notified:
|
||||
return
|
||||
mail_template = self.env.ref('crm_reveal.reveal_no_credits')
|
||||
iap_account = self.env['iap.account'].search([('service_name', '=', 'reveal')], limit=1)
|
||||
# Get the email address of the creators of the Lead Generation Rules
|
||||
res = self.env['crm.reveal.rule'].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('reveal.already_notified', True)
|
||||
|
||||
|
||||
def _create_lead_from_response(self, result):
|
||||
""" This method will get response from service and create the lead accordingly """
|
||||
if result['rule_id']:
|
||||
rule = self.browse(result['rule_id'])
|
||||
else:
|
||||
# Not create a lead if the information match no rule
|
||||
# If there is no match, the service still returns all informations
|
||||
# in order to let custom code use it.
|
||||
return False
|
||||
if not result['clearbit_id']:
|
||||
return False
|
||||
already_created_lead = self.env['crm.lead'].search([('reveal_id', '=', result['clearbit_id'])])
|
||||
if already_created_lead:
|
||||
_logger.info('Existing lead for this clearbit_id [%s]', result['clearbit_id'])
|
||||
# Does not create a lead if the reveal_id is already known
|
||||
return False
|
||||
lead_vals = rule._lead_vals_from_response(result)
|
||||
lead = self.env['crm.lead'].create(lead_vals)
|
||||
lead.message_post_with_view(
|
||||
'crm_reveal.lead_message_template',
|
||||
values=self._format_data_for_message_post(result),
|
||||
subtype_id=self.env.ref('mail.mt_note').id
|
||||
)
|
||||
return lead
|
||||
|
||||
# Methods responsible for format response data in to valid odoo lead data
|
||||
def _lead_vals_from_response(self, result):
|
||||
self.ensure_one()
|
||||
reveal_data = result['reveal_data']
|
||||
people_data = result.get('people_data')
|
||||
country_id = self.env['res.country'].search([('code', '=', reveal_data['country_code'])]).id
|
||||
website_url = 'https://www.%s' % reveal_data['domain'] if reveal_data['domain'] else False
|
||||
lead_vals = {
|
||||
# Lead vals from rule itself
|
||||
'type': self.lead_type,
|
||||
'team_id': self.team_id.id,
|
||||
'tag_ids': [(6, 0, self.tag_ids.ids)],
|
||||
'priority': self.priority,
|
||||
'user_id': self.user_id.id,
|
||||
'reveal_ip': result['ip'],
|
||||
'reveal_rule_id': self.id,
|
||||
'reveal_id': result['clearbit_id'],
|
||||
'referred': 'Website Visitor',
|
||||
# Lead vals from response
|
||||
'name': reveal_data['name'],
|
||||
'reveal_iap_credits': result['credit'],
|
||||
'partner_name': reveal_data['legal_name'] or reveal_data['name'],
|
||||
'email_from': ",".join(reveal_data['email'] or []),
|
||||
'phone': reveal_data['phone'] or (reveal_data['phone_numbers'] and reveal_data['phone_numbers'][0]) or '',
|
||||
'website': website_url,
|
||||
'street': reveal_data['location'],
|
||||
'city': reveal_data['city'],
|
||||
'zip': reveal_data['postal_code'],
|
||||
'country_id': country_id,
|
||||
'state_id': self._find_state_id(reveal_data['state_name'], reveal_data['state_code'], country_id),
|
||||
'description': self._prepare_lead_description(reveal_data),
|
||||
}
|
||||
|
||||
if self.suffix:
|
||||
lead_vals['name'] = '%s - %s' % (lead_vals['name'], self.suffix)
|
||||
|
||||
# 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
|
||||
|
||||
def _find_state_id(self, state_code, state_name, 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
|
||||
|
||||
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
|
||||
|
||||
def _format_data_for_message_post(self, result):
|
||||
reveal_data = result['reveal_data']
|
||||
people_data = result.get('people_data')
|
||||
log_data = {
|
||||
'twitter': reveal_data['twitter'],
|
||||
'description': reveal_data['description'],
|
||||
'logo': reveal_data['logo'],
|
||||
'name': reveal_data['name'],
|
||||
'phone_numbers': reveal_data['phone_numbers'],
|
||||
'facebook': reveal_data['facebook'],
|
||||
'linkedin': reveal_data['linkedin'],
|
||||
'crunchbase': reveal_data['crunchbase'],
|
||||
'tech': [t.replace('_', ' ').title() for t in reveal_data['tech']],
|
||||
'people_data': people_data,
|
||||
}
|
||||
timezone = result['ip_time_zone'] or reveal_data['timezone']
|
||||
if timezone:
|
||||
log_data.update({
|
||||
'timezone': timezone.replace('_', ' ').title(),
|
||||
'timezone_link': timezone.lower().replace('_', '-'),
|
||||
})
|
||||
return log_data
|
||||
|
||||
|
||||
class IndustryTag(models.Model):
|
||||
""" Industry Tags of Acquisition Rules """
|
||||
_name = 'crm.reveal.industry'
|
||||
_description = 'Industry Tag'
|
||||
|
||||
name = fields.Char(string='Tag Name', required=True, translate=True)
|
||||
reveal_id = fields.Char(required=True)
|
||||
color = fields.Integer(string='Color Index')
|
||||
|
||||
_sql_constraints = [
|
||||
('name_uniq', 'unique (name)', 'Tag name already exists!'),
|
||||
]
|
||||
|
||||
|
||||
class PeopleRole(models.Model):
|
||||
""" CRM Reveal People Roles for People """
|
||||
_name = 'crm.reveal.role'
|
||||
_description = 'People Role'
|
||||
|
||||
name = fields.Char(string='Role Name', required=True, translate=True)
|
||||
reveal_id = fields.Char(required=True)
|
||||
color = fields.Integer(string='Color Index')
|
||||
|
||||
_sql_constraints = [
|
||||
('name_uniq', 'unique (name)', 'Role name already exists!'),
|
||||
]
|
||||
|
||||
@api.depends('name')
|
||||
def name_get(self):
|
||||
return [(role.id, role.name.replace('_', ' ').title()) for role in self]
|
||||
|
||||
|
||||
class PeopleSeniority(models.Model):
|
||||
""" Seniority for People Rules """
|
||||
_name = 'crm.reveal.seniority'
|
||||
_description = 'People Seniority'
|
||||
|
||||
name = fields.Char(string='Name', required=True, translate=True)
|
||||
reveal_id = fields.Char(required=True)
|
||||
|
||||
_sql_constraints = [
|
||||
('name_uniq', 'unique (name)', 'Name already exists!'),
|
||||
]
|
||||
|
||||
@api.depends('name')
|
||||
def name_get(self):
|
||||
return [(seniority.id, seniority.name.replace('_', ' ').title()) for seniority in self]
|
||||
@@ -0,0 +1,57 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import datetime
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from odoo import api, fields, models
|
||||
|
||||
DEFAULT_REVEAL_VIEW_WEEKS_VALID = 5
|
||||
|
||||
class CRMRevealView(models.Model):
|
||||
_name = 'crm.reveal.view'
|
||||
_order = 'id desc'
|
||||
|
||||
reveal_ip = fields.Char(string='IP Address', index=True)
|
||||
reveal_rule_id = fields.Many2one('crm.reveal.rule', string='Lead Generation Rule', index=True)
|
||||
reveal_state = fields.Selection([('to_process', 'To Process'), ('not_found', 'Not Found')], default='to_process', string="State", index=True)
|
||||
create_date = fields.Datetime(index=True)
|
||||
|
||||
@api.model_cr
|
||||
def init(self):
|
||||
self._cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = %s', ('crm_reveal_view_ip_rule_id',))
|
||||
if not self._cr.fetchone():
|
||||
self._cr.execute('CREATE UNIQUE INDEX crm_reveal_view_ip_rule_id ON crm_reveal_view (reveal_rule_id,reveal_ip)')
|
||||
self._cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = %s', ('crm_reveal_view_state_create_date',))
|
||||
if not self._cr.fetchone():
|
||||
self._cr.execute('CREATE INDEX crm_reveal_view_state_create_date ON crm_reveal_view (reveal_state,create_date)')
|
||||
|
||||
|
||||
@api.model
|
||||
def _clean_reveal_views(self):
|
||||
""" Remove old views (> 1 month) """
|
||||
weeks_valid = self.env['ir.config_parameter'].sudo().get_param('reveal.view_weeks_valid', DEFAULT_REVEAL_VIEW_WEEKS_VALID)
|
||||
try:
|
||||
weeks_valid = int(weeks_valid)
|
||||
except ValueError:
|
||||
weeks_valid = DEFAULT_REVEAL_VIEW_WEEKS_VALID
|
||||
domain = []
|
||||
domain.append(('reveal_state', '=', 'not_found'))
|
||||
domain.append(('create_date', '<', fields.Datetime.to_string(datetime.date.today() - relativedelta(weeks=weeks_valid))))
|
||||
self.search(domain).unlink()
|
||||
|
||||
def _create_reveal_view(self, url, ip_address, country_code, rules_excluded):
|
||||
# we are avoiding reveal if reveal_view already created for this IP
|
||||
rules = self.env['crm.reveal.rule']._match_url(url, country_code, rules_excluded)
|
||||
if rules:
|
||||
query = """
|
||||
INSERT INTO crm_reveal_view (reveal_ip, reveal_rule_id, reveal_state, create_date)
|
||||
VALUES (%s, %s, 'to_process', now() at time zone 'UTC')
|
||||
ON CONFLICT DO NOTHING;
|
||||
""" * len(rules)
|
||||
params = []
|
||||
for rule in rules:
|
||||
params += [ip_address, rule['id']]
|
||||
rules_excluded.append(str(rule['id']))
|
||||
self.env.cr.execute(query, params)
|
||||
return rules_excluded
|
||||
return False
|
||||
@@ -0,0 +1,38 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import logging
|
||||
import time
|
||||
|
||||
from odoo import models
|
||||
from odoo.http import request
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
class IrHttp(models.AbstractModel):
|
||||
_inherit = 'ir.http'
|
||||
|
||||
@classmethod
|
||||
def _serve_page(cls):
|
||||
response = super(IrHttp, cls)._serve_page()
|
||||
if response and getattr(response, 'status_code', 0) == 200 and request.env.user._is_public():
|
||||
lead_id = request.httprequest.cookies.get('lead_id')
|
||||
# We are avoiding to create a reveal_view if a lead is already
|
||||
# created from another module, e.g. website_form
|
||||
if not lead_id:
|
||||
country_code = 'geoip' in request.session and request.session['geoip'].get('country_code')
|
||||
if country_code:
|
||||
try:
|
||||
url = request.httprequest.url
|
||||
ip_address = request.httprequest.remote_addr
|
||||
rules_excluded = (request.httprequest.cookies.get('rule_ids') or '').split(',')
|
||||
before = time.time()
|
||||
new_rules_excluded = request.env['crm.reveal.view'].sudo()._create_reveal_view(url, ip_address, country_code, rules_excluded)
|
||||
# even when we match, no view may have been created if this is a duplicate
|
||||
_logger.info('Reveal process time: [%s], match rule: [%s?], country code: [%s], ip: [%s]',
|
||||
time.time() - before, new_rules_excluded == rules_excluded, country_code,
|
||||
ip_address)
|
||||
if new_rules_excluded:
|
||||
response.set_cookie('rule_ids', ','.join(new_rules_excluded))
|
||||
except Exception:
|
||||
# just in case - we never want to crash a page view
|
||||
_logger.exception("Failed to process reveal rules")
|
||||
return response
|
||||
@@ -0,0 +1,16 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import api, fields, models
|
||||
|
||||
class ResConfigSettings(models.TransientModel):
|
||||
_inherit = 'res.config.settings'
|
||||
|
||||
@api.multi
|
||||
def action_buy_reveal_iap_credits(self):
|
||||
url = self.env['iap.account'].get_credits_url('reveal', 'https://iap.odoo.com/iap/1/credit', 0)
|
||||
return {
|
||||
'type': 'ir.actions.act_url',
|
||||
'url': url,
|
||||
'target': 'new',
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_crm_reveal_rule,access_crm_reveal_rule,model_crm_reveal_rule,sales_team.group_sale_manager,1,1,1,1
|
||||
access_crm_reveal_view,access_crm_reveal_view,model_crm_reveal_view,sales_team.group_sale_manager,1,1,1,1
|
||||
access_crm_reveal_industry,access_crm_reveal_industry,model_crm_reveal_industry,sales_team.group_sale_manager,1,1,1,1
|
||||
access_crm_reveal_role,access_crm_reveal_role,model_crm_reveal_role,sales_team.group_sale_manager,1,1,1,1
|
||||
access_crm_reveal_seniority,access_crm_reveal_seniority,model_crm_reveal_seniority,sales_team.group_sale_manager,1,1,1,1
|
||||
|
@@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<odoo>
|
||||
<record id="crm_reveal_lead_form" model="ir.ui.view">
|
||||
<field name="name">crm.lead.form</field>
|
||||
<field name="model">crm.lead</field>
|
||||
<field name="inherit_id" ref="crm.crm_case_form_view_leads" />
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//page[@name='extra']/group" position="inside">
|
||||
<group string="Lead Generation Information" attrs="{'invisible': [('reveal_ip', '=', False)]}" groups="base.group_no_one">
|
||||
<field name="reveal_ip"/>
|
||||
<field name="reveal_iap_credits"/>
|
||||
<field name="reveal_rule_id"/>
|
||||
</group>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="crm_reveal_lead_opportunity_form" model="ir.ui.view">
|
||||
<field name="name">crm.lead.opportunity.inherited.crm</field>
|
||||
<field name="model">crm.lead</field>
|
||||
<field name="inherit_id" ref="crm.crm_case_form_view_oppor"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//page[@name='lead']/group" position="after">
|
||||
<group string="Lead Generation Information" attrs="{'invisible': [('reveal_ip', '=', False)]}" groups="base.group_no_one">
|
||||
<field name="reveal_ip"/>
|
||||
<field name="reveal_iap_credits"/>
|
||||
<field name="reveal_rule_id"/>
|
||||
</group>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -0,0 +1,165 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="crm_reveal_rule_form" model="ir.ui.view">
|
||||
<field name="name">crm.reveal.rule.form</field>
|
||||
<field name="model">crm.reveal.rule</field>
|
||||
<field name="arch" type="xml">
|
||||
<form>
|
||||
<sheet>
|
||||
<div class="oe_button_box" name="button_box">
|
||||
<button name="action_get_opportunity_tree_view" class="oe_stat_button" type="object" icon="fa-handshake-o" attrs="{'invisible': [('lead_type', '!=', 'opportunity'), ('opportunity_count', '=' , 0)]}">
|
||||
<div class="o_stat_info">
|
||||
<field name="opportunity_count"/>
|
||||
<span class="o_stat_text"> Opportunities </span>
|
||||
</div>
|
||||
</button>
|
||||
<button name="action_get_lead_tree_view" class="oe_stat_button" type="object" icon="fa-handshake-o" groups="crm.group_use_lead" attrs="{'invisible': [('lead_type', '!=', 'lead'), ('leads_count', '=' , 0)]}">
|
||||
<div class="o_stat_info">
|
||||
<field name="leads_count"/>
|
||||
<span class="o_stat_text"> Leads </span>
|
||||
</div>
|
||||
</button>
|
||||
<button name="toggle_active" type="object" groups="base.group_user" class="oe_stat_button" icon="fa-archive">
|
||||
<field name="active" widget="boolean_button" options="{'terminology': 'archive'}"/>
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
<div class="alert alert-info pull-right" role="alert">
|
||||
No credit is consumed if the company is not found.<br/>
|
||||
1 credit is consumed if the company is found.<br/>
|
||||
<span attrs="{'invisible': [('lead_for', '!=', 'people')]}"><field name="calculate_credits" class="mb0"/> credits are consumed if the company matches the rule.</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oe_title">
|
||||
<label for="name" class="oe_edit_only"/>
|
||||
<h1 class="o_row">
|
||||
<field name="name" placeholder="e.g. US Visitors"/>
|
||||
</h1>
|
||||
</div>
|
||||
<group>
|
||||
<field name="lead_for" widget="radio"/>
|
||||
</group>
|
||||
<group>
|
||||
<group string="Website Traffic Conditions">
|
||||
<field name="country_ids" widget="many2many_tags"/>
|
||||
<field name="regex_url" widget="website_urls"/>
|
||||
<field name="sequence"/>
|
||||
</group>
|
||||
</group>
|
||||
<group>
|
||||
<group string="Opportunity Generation Conditions">
|
||||
<field name="industry_tag_ids" widget="many2many_tags" options="{'color_field': 'color', 'no_create_edit': True, 'no_quick_create': True}"/>
|
||||
<label for="company_size_min"/>
|
||||
<div>
|
||||
<field name="company_size_min" class="oe_inline" />
|
||||
<span> people </span>
|
||||
</div>
|
||||
<label for="company_size_max"/>
|
||||
<div>
|
||||
<field name="company_size_max" class="oe_inline" />
|
||||
<span> people </span>
|
||||
</div>
|
||||
</group>
|
||||
<group string="Contact Filter" attrs="{'invisible': [('lead_for', '!=', 'people')]}">
|
||||
<field name="preferred_role_id" options="{'no_create_edit': True, 'no_quick_create': True}"/>
|
||||
<field name="other_role_ids" widget="many2many_tags" options="{'color_field': 'color', 'no_create_edit': True, 'no_quick_create': True}" attrs="{'invisible': [('preferred_role_id','=', False)]}"/>
|
||||
<field name="seniority_id" options="{'no_create_edit': True, 'no_quick_create': True}"/>
|
||||
<field name="extra_contacts"/>
|
||||
</group>
|
||||
</group>
|
||||
<separator attrs="{'invisible': [('lead_type', '!=', 'lead')]}" string="Lead Data" />
|
||||
<separator attrs="{'invisible': [('lead_type', '!=', 'opportunity')]}" string="Opportunity Data" />
|
||||
<group>
|
||||
<group>
|
||||
<field name="lead_type" groups="crm.group_use_lead"/>
|
||||
<field name="suffix"/>
|
||||
<field name="team_id"/>
|
||||
<field name="user_id"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="tag_ids" widget="many2many_tags"/>
|
||||
<field name="priority" widget="priority"/>
|
||||
</group>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="crm_reveal_rule_tree" model="ir.ui.view">
|
||||
<field name="name">crm.reveal.rule.tree</field>
|
||||
<field name="model">crm.reveal.rule</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree>
|
||||
<field name="sequence" widget="handle"/>
|
||||
<field name="name"/>
|
||||
<field name="lead_type"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="crm_reveal_rule_action" model="ir.actions.act_window">
|
||||
<field name="name">Lead Generation Rules</field>
|
||||
<field name="res_model">crm.reveal.rule</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
No rule found
|
||||
</p><p>
|
||||
Create rules to generate B2B leads/opportunities by tracking data of your website visitors.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="crm_reveal_view_form" model="ir.ui.view">
|
||||
<field name="name">crm.reveal.view.form</field>
|
||||
<field name="model">crm.reveal.view</field>
|
||||
<field name="arch" type="xml">
|
||||
<form>
|
||||
<header>
|
||||
<field name="reveal_state" widget="statusbar"/>
|
||||
</header>
|
||||
<sheet>
|
||||
<group>
|
||||
<field name="reveal_ip"/>
|
||||
<field name="reveal_rule_id"/>
|
||||
<field name="reveal_state"/>
|
||||
<field name="create_date"/>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="crm_reveal_view_tree" model="ir.ui.view">
|
||||
<field name="name">crm.reveal.view.tree</field>
|
||||
<field name="model">crm.reveal.view</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree>
|
||||
<field name="reveal_ip"/>
|
||||
<field name="reveal_rule_id"/>
|
||||
<field name="reveal_state"/>
|
||||
<field name="create_date"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="crm_reveal_view_action" model="ir.actions.act_window">
|
||||
<field name="name">Lead Generation Views</field>
|
||||
<field name="res_model">crm.reveal.view</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
</record>
|
||||
|
||||
<!-- This menu is display in CRM app when crm_reveal is installed-->
|
||||
<menuitem
|
||||
id="crm_reveal_rule_menu_action"
|
||||
action="crm_reveal_rule_action"
|
||||
parent="crm.crm_menu_config"/>
|
||||
|
||||
<menuitem
|
||||
id="crm_reveal_view_menu_action"
|
||||
parent="crm.crm_menu_report"
|
||||
action="crm_reveal_view_action"
|
||||
groups="base.group_no_one"/>
|
||||
|
||||
</odoo>
|
||||
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="res_config_settings_view_form" model="ir.ui.view">
|
||||
<field name="name">res.config.settings.view.form.inherit.crm.reveal</field>
|
||||
<field name="model">res.config.settings</field>
|
||||
<field name="inherit_id" ref="crm.res_config_settings_view_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<div id="crm_lead" position="after">
|
||||
<div class="col-xs-12 col-md-6 o_setting_box" id="crm_reveal">
|
||||
<div class="o_setting_right_pane">
|
||||
<label string="IAP Lead Generations" for="action_buy_reveal_iap_credits"/>
|
||||
<div class="text-muted">
|
||||
Charge your IAP account to generate Leads/Opportunities from your website's traffic
|
||||
</div>
|
||||
<div class="content-group">
|
||||
<button name="action_buy_reveal_iap_credits" string="Buy IAP Credits" type="object" class="oe_link" icon="fa-arrow-right"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
Reference in New Issue
Block a user