[ADD] partner_autocomplete: Added autocomplete on partners fields
For Name field or M2O, gives a list of companies Data comes from Odoo IAP Service
This commit is contained in:
@@ -462,6 +462,11 @@ file_filter = addons/pad_project/i18n/<lang>.po
|
|||||||
source_file = addons/pad_project/i18n/pad_project.pot
|
source_file = addons/pad_project/i18n/pad_project.pot
|
||||||
source_lang = en
|
source_lang = en
|
||||||
|
|
||||||
|
[odoo-12.partner_autocomplete]
|
||||||
|
file_filter = partner_autocomplete/i18n/<lang>.po
|
||||||
|
source_file = partner_autocomplete/i18n/partner_autocomplete.pot
|
||||||
|
source_lang = en
|
||||||
|
|
||||||
[odoo-12.payment]
|
[odoo-12.payment]
|
||||||
file_filter = addons/payment/i18n/<lang>.po
|
file_filter = addons/payment/i18n/<lang>.po
|
||||||
source_file = addons/payment/i18n/payment.pot
|
source_file = addons/payment/i18n/payment.pot
|
||||||
|
|||||||
@@ -279,7 +279,7 @@ class AccountInvoice(models.Model):
|
|||||||
"means direct payment.")
|
"means direct payment.")
|
||||||
partner_id = fields.Many2one('res.partner', string='Partner', change_default=True,
|
partner_id = fields.Many2one('res.partner', string='Partner', change_default=True,
|
||||||
readonly=True, states={'draft': [('readonly', False)]},
|
readonly=True, states={'draft': [('readonly', False)]},
|
||||||
track_visibility='always')
|
track_visibility='always', help="You can find a contact by its Name, TIN, Email or Internal Reference.")
|
||||||
vendor_bill_id = fields.Many2one('account.invoice', string='Vendor Bill',
|
vendor_bill_id = fields.Many2one('account.invoice', string='Vendor Bill',
|
||||||
help="Auto-complete from a past bill.")
|
help="Auto-complete from a past bill.")
|
||||||
payment_term_id = fields.Many2one('account.payment.term', string='Payment Terms', oldname='payment_term',
|
payment_term_id = fields.Many2one('account.payment.term', string='Payment Terms', oldname='payment_term',
|
||||||
|
|||||||
@@ -283,7 +283,8 @@
|
|||||||
<group>
|
<group>
|
||||||
<group>
|
<group>
|
||||||
<field string="Vendor" name="partner_id"
|
<field string="Vendor" name="partner_id"
|
||||||
context="{'default_customer': 0, 'search_default_supplier': 1, 'default_supplier': 1, 'default_company_type': 'company'}"
|
widget="res_partner_many2one"
|
||||||
|
context="{'default_customer': 0, 'search_default_supplier': 1, 'default_supplier': 1, 'default_is_company': True, 'show_vat': True}"
|
||||||
domain="[('supplier', '=', True)]"/>
|
domain="[('supplier', '=', True)]"/>
|
||||||
<field name="reference" string="Vendor Reference"/>
|
<field name="reference" string="Vendor Reference"/>
|
||||||
<field name="vendor_bill_id" attrs="{'invisible': [('state','not in',['draft'])]}"
|
<field name="vendor_bill_id" attrs="{'invisible': [('state','not in',['draft'])]}"
|
||||||
@@ -436,7 +437,8 @@
|
|||||||
<group>
|
<group>
|
||||||
<group>
|
<group>
|
||||||
<field string="Customer" name="partner_id"
|
<field string="Customer" name="partner_id"
|
||||||
context="{'search_default_customer':1, 'show_address': 1, 'default_company_type': 'company'}"
|
widget="res_partner_many2one"
|
||||||
|
context="{'search_default_customer':1, 'show_address': 1, 'default_is_company': True, 'show_vat': True}"
|
||||||
options='{"always_reload": True, "no_quick_create": True}'
|
options='{"always_reload": True, "no_quick_create": True}'
|
||||||
domain="[('customer', '=', True)]" required="1"/>
|
domain="[('customer', '=', True)]" required="1"/>
|
||||||
<field name="payment_term_id"/>
|
<field name="payment_term_id"/>
|
||||||
|
|||||||
@@ -200,6 +200,7 @@
|
|||||||
<field name="sequence" widget="handle"/>
|
<field name="sequence" widget="handle"/>
|
||||||
<field name="bank_id"/>
|
<field name="bank_id"/>
|
||||||
<field name="acc_number"/>
|
<field name="acc_number"/>
|
||||||
|
<field name="acc_holder_name" invisible="1"/>
|
||||||
</tree>
|
</tree>
|
||||||
</field>
|
</field>
|
||||||
<button type="action" class="btn-link"
|
<button type="action" class="btn-link"
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ class ResConfigSettings(models.TransientModel):
|
|||||||
module_pad = fields.Boolean("Collaborative Pads")
|
module_pad = fields.Boolean("Collaborative Pads")
|
||||||
module_voip = fields.Boolean("Asterisk (VoIP)")
|
module_voip = fields.Boolean("Asterisk (VoIP)")
|
||||||
module_web_unsplash = fields.Boolean("Unsplash Image Library")
|
module_web_unsplash = fields.Boolean("Unsplash Image Library")
|
||||||
|
module_partner_autocomplete = fields.Boolean("Auto-populate company data")
|
||||||
company_share_partner = fields.Boolean(string='Share partners to all companies',
|
company_share_partner = fields.Boolean(string='Share partners to all companies',
|
||||||
help="Share your partners to all companies defined in your instance.\n"
|
help="Share your partners to all companies defined in your instance.\n"
|
||||||
" * Checked : Partners are visible for every companies, even if a company is defined on the partner.\n"
|
" * Checked : Partners are visible for every companies, even if a company is defined on the partner.\n"
|
||||||
|
|||||||
@@ -244,6 +244,17 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-xs-12 col-md-6 o_setting_box" title="When populating your address book, Odoo provides a list of matching companies. When selecting one item, the company data and logo are auto-filled.">
|
||||||
|
<div class="o_setting_left_pane">
|
||||||
|
<field name="module_partner_autocomplete"/>
|
||||||
|
</div>
|
||||||
|
<div class="o_setting_right_pane" id="partner_autocomplete_settings">
|
||||||
|
<label for="module_partner_autocomplete"/>
|
||||||
|
<div class="text-muted">
|
||||||
|
Autocomplete company data (name, logo, address, etc.)
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</xpath>
|
</xpath>
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ class Lead(models.Model):
|
|||||||
|
|
||||||
name = fields.Char('Opportunity', required=True, index=True)
|
name = fields.Char('Opportunity', required=True, index=True)
|
||||||
partner_id = fields.Many2one('res.partner', string='Customer', track_visibility='onchange', track_sequence=1, index=True,
|
partner_id = fields.Many2one('res.partner', string='Customer', track_visibility='onchange', track_sequence=1, index=True,
|
||||||
help="Linked partner (optional). Usually created when converting the lead.")
|
help="Linked partner (optional). Usually created when converting the lead. You can find a partner by its Name, TIN, Email or Internal Reference.")
|
||||||
active = fields.Boolean('Active', default=True, track_visibility=True)
|
active = fields.Boolean('Active', default=True, track_visibility=True)
|
||||||
date_action_last = fields.Datetime('Last Action', readonly=True)
|
date_action_last = fields.Datetime('Last Action', readonly=True)
|
||||||
email_from = fields.Char('Email', help="Email address of the contact", track_visibility='onchange', track_sequence=4, index=True)
|
email_from = fields.Char('Email', help="Email address of the contact", track_visibility='onchange', track_sequence=4, index=True)
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ class ResConfigSettings(models.TransientModel):
|
|||||||
generate_lead_from_alias = fields.Boolean('Manual Assignation of Emails', config_parameter='crm.generate_lead_from_alias')
|
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')
|
group_use_lead = fields.Boolean(string="Leads", implied_group='crm.group_use_lead')
|
||||||
module_crm_phone_validation = fields.Boolean("Phone Formatting")
|
module_crm_phone_validation = fields.Boolean("Phone Formatting")
|
||||||
module_web_clearbit = fields.Boolean("Customer Autocomplete")
|
|
||||||
|
|
||||||
def _find_default_lead_alias_id(self):
|
def _find_default_lead_alias_id(self):
|
||||||
alias = self.env.ref('crm.mail_alias_lead_info', False)
|
alias = self.env.ref('crm.mail_alias_lead_info', False)
|
||||||
|
|||||||
@@ -68,7 +68,9 @@
|
|||||||
<group>
|
<group>
|
||||||
<!-- Preload all the partner's information -->
|
<!-- Preload all the partner's information -->
|
||||||
<field name="partner_id" string="Customer"
|
<field name="partner_id" string="Customer"
|
||||||
context="{'default_name': contact_name, 'default_street': street, 'default_city': city, 'default_state_id': state_id, 'default_zip': zip, 'default_country_id': country_id, 'default_function': function, 'default_phone': phone, 'default_mobile': mobile, 'default_email': email_from, 'default_user_id': user_id, 'default_team_id': team_id, 'default_website': website}" groups="base.group_no_one"/>
|
widget="res_partner_many2one"
|
||||||
|
context="{'default_name': contact_name, 'default_street': street, 'default_city': city, 'default_state_id': state_id, 'default_zip': zip, 'default_country_id': country_id, 'default_function': function, 'default_phone': phone, 'default_mobile': mobile, 'default_email': email_from, 'default_user_id': user_id, 'default_team_id': team_id, 'default_website': website, 'show_vat': True}"
|
||||||
|
groups="base.group_no_one"/>
|
||||||
<field name="partner_name" string="Company Name"/>
|
<field name="partner_name" string="Company Name"/>
|
||||||
<label for="street" string="Address"/>
|
<label for="street" string="Address"/>
|
||||||
<div class="o_address_format">
|
<div class="o_address_format">
|
||||||
@@ -240,7 +242,7 @@
|
|||||||
<group>
|
<group>
|
||||||
<group>
|
<group>
|
||||||
<field name="name" string="Opportunity Title" placeholder="e.g. Customer Deal"/>
|
<field name="name" string="Opportunity Title" placeholder="e.g. Customer Deal"/>
|
||||||
<field name="partner_id" domain="[('customer', '=', True)]" context="{'search_default_customer': 1}"/>
|
<field name="partner_id" widget="res_partner_many2one" domain="[('customer', '=', True)]" context="{'search_default_customer': 1, 'show_vat': True}"/>
|
||||||
<field name="partner_name" invisible="1"/>
|
<field name="partner_name" invisible="1"/>
|
||||||
<field name="street" invisible="1"/>
|
<field name="street" invisible="1"/>
|
||||||
<field name="street2" invisible="1"/>
|
<field name="street2" invisible="1"/>
|
||||||
@@ -298,7 +300,7 @@
|
|||||||
<form>
|
<form>
|
||||||
<group>
|
<group>
|
||||||
<field name="name"/>
|
<field name="name"/>
|
||||||
<field name="partner_id" domain="[('customer', '=', True)]" context="{'search_default_customer': 1}"/>
|
<field name="partner_id" widget="res_partner_many2one" domain="[('customer', '=', True)]" context="{'search_default_customer': 1, 'show_vat': True}"/>
|
||||||
<field name="planned_revenue" widget="monetary" options="{'currency_field': 'company_currency'}"/>
|
<field name="planned_revenue" widget="monetary" options="{'currency_field': 'company_currency'}"/>
|
||||||
<field name="company_currency" invisible="1"/>
|
<field name="company_currency" invisible="1"/>
|
||||||
<field name="company_id" invisible="1"/>
|
<field name="company_id" invisible="1"/>
|
||||||
@@ -370,7 +372,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="oe_clear"></div>
|
<div class="oe_clear"/>
|
||||||
</div>
|
</div>
|
||||||
</t>
|
</t>
|
||||||
</templates>
|
</templates>
|
||||||
@@ -493,6 +495,7 @@
|
|||||||
<group>
|
<group>
|
||||||
<group>
|
<group>
|
||||||
<field name="partner_id"
|
<field name="partner_id"
|
||||||
|
widget="res_partner_many2one"
|
||||||
string="Customer" domain="[('customer', '=', True)]"
|
string="Customer" domain="[('customer', '=', True)]"
|
||||||
context="{'search_default_customer': 1,
|
context="{'search_default_customer': 1,
|
||||||
'default_name': partner_name, 'default_street': street,
|
'default_name': partner_name, 'default_street': street,
|
||||||
@@ -501,7 +504,10 @@
|
|||||||
'default_country_id': country_id, 'default_function': function,
|
'default_country_id': country_id, 'default_function': function,
|
||||||
'default_phone': phone, 'default_mobile': mobile,
|
'default_phone': phone, 'default_mobile': mobile,
|
||||||
'default_email': email_from,
|
'default_email': email_from,
|
||||||
'default_user_id': user_id, 'default_team_id': team_id, 'default_website': website}"/>
|
'default_user_id': user_id, 'default_team_id': team_id, 'default_website': website,
|
||||||
|
'show_vat': True,
|
||||||
|
}"
|
||||||
|
/>
|
||||||
<field name="is_blacklisted" invisible="1"/>
|
<field name="is_blacklisted" invisible="1"/>
|
||||||
<field name="partner_is_blacklisted" invisible="1"/>
|
<field name="partner_is_blacklisted" invisible="1"/>
|
||||||
<label for="email_from" class="oe_inline"/>
|
<label for="email_from" class="oe_inline"/>
|
||||||
|
|||||||
@@ -66,17 +66,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 col-lg-6 o_setting_box" title="When populating your address book, Odoo relies on Clearbit’s API to provide you with a list of matching contacts or companies.When selecting one item, the partner name, logo and website get automatically set.">
|
|
||||||
<div class="o_setting_left_pane">
|
|
||||||
<field name="module_web_clearbit" widget="upgrade_boolean"/>
|
|
||||||
</div>
|
|
||||||
<div class="o_setting_right_pane">
|
|
||||||
<label for="module_web_clearbit"/>
|
|
||||||
<div class="text-muted">
|
|
||||||
Look up company information (name, logo, etc.)
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</xpath>
|
</xpath>
|
||||||
|
|||||||
@@ -32,7 +32,7 @@
|
|||||||
<group name="action" attrs="{'invisible': [('name', '!=', 'convert')]}" string="Customers" col="1">
|
<group name="action" attrs="{'invisible': [('name', '!=', 'convert')]}" string="Customers" col="1">
|
||||||
<field name="action" nolabel="1" widget="radio"/>
|
<field name="action" nolabel="1" widget="radio"/>
|
||||||
<group col="2">
|
<group col="2">
|
||||||
<field name="partner_id" domain="[('customer', '=', True)]" context="{'search_default_customer': 1}" attrs="{'required': [('action', '=', 'exist')], 'invisible':[('action','!=','exist')]}"/>
|
<field name="partner_id" widget="res_partner_many2one" domain="[('customer', '=', True)]" context="{'search_default_customer': 1, 'show_vat': True}" attrs="{'required': [('action', '=', 'exist')], 'invisible':[('action','!=','exist')]}"/>
|
||||||
</group>
|
</group>
|
||||||
</group>
|
</group>
|
||||||
<footer>
|
<footer>
|
||||||
@@ -79,7 +79,9 @@
|
|||||||
<field name="action" class="oe_inline" widget="radio"/>
|
<field name="action" class="oe_inline" widget="radio"/>
|
||||||
<group col="2">
|
<group col="2">
|
||||||
<field name="partner_id"
|
<field name="partner_id"
|
||||||
|
widget="res_partner_many2one"
|
||||||
attrs="{'required': [('action', '=', 'exist')], 'invisible':[('action','!=','exist')]}"
|
attrs="{'required': [('action', '=', 'exist')], 'invisible':[('action','!=','exist')]}"
|
||||||
|
context="{'show_vat': True}"
|
||||||
class="oe_inline"/>
|
class="oe_inline"/>
|
||||||
</group>
|
</group>
|
||||||
</group>
|
</group>
|
||||||
|
|||||||
@@ -145,7 +145,7 @@ class IapAccount(models.Model):
|
|||||||
service_name = fields.Char()
|
service_name = fields.Char()
|
||||||
account_token = fields.Char(default=lambda s: uuid.uuid4().hex)
|
account_token = fields.Char(default=lambda s: uuid.uuid4().hex)
|
||||||
company_id = fields.Many2one('res.company', default=lambda self: self.env.user.company_id)
|
company_id = fields.Many2one('res.company', default=lambda self: self.env.user.company_id)
|
||||||
insufficient_credit= fields.Boolean('Insufficient credits', default=False)
|
insufficient_credit = fields.Boolean('Insufficient credits', default=False)
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
def get(self, service_name):
|
def get(self, service_name):
|
||||||
|
|||||||
@@ -52,11 +52,11 @@ CrashManager.include({
|
|||||||
$content: content,
|
$content: content,
|
||||||
buttons: [{
|
buttons: [{
|
||||||
text: self._getButtonMessage(error_data.trial),
|
text: self._getButtonMessage(error_data.trial),
|
||||||
classes: "btn-primary",
|
classes : "btn-primary",
|
||||||
click: function () {
|
click: function () {
|
||||||
window.open(url, '_blank');
|
window.open(url, '_blank');
|
||||||
},
|
},
|
||||||
close: true,
|
close:true,
|
||||||
}, {
|
}, {
|
||||||
text: _t("Cancel"),
|
text: _t("Cancel"),
|
||||||
close: true,
|
close: true,
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from . import models
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
{
|
||||||
|
'name': "Partner Autocomplete",
|
||||||
|
'summary': """
|
||||||
|
Auto-complete partner companies' data""",
|
||||||
|
'description': """
|
||||||
|
Auto-complete partner companies' data
|
||||||
|
""",
|
||||||
|
'author': "Odoo SA",
|
||||||
|
'category': 'Tools',
|
||||||
|
'version': '1.0',
|
||||||
|
|
||||||
|
'depends': ['web', 'mail', 'iap'],
|
||||||
|
'data': [
|
||||||
|
'security/ir.model.access.csv',
|
||||||
|
'views/partner_autocomplete_assets.xml',
|
||||||
|
'views/additional_info_template.xml',
|
||||||
|
'views/res_partner_views.xml',
|
||||||
|
'views/res_company_views.xml',
|
||||||
|
'views/res_config_settings_views.xml',
|
||||||
|
'data/cron.xml',
|
||||||
|
],
|
||||||
|
'qweb': [
|
||||||
|
'static/src/xml/partner_autocomplete.xml',
|
||||||
|
],
|
||||||
|
'auto_install': True,
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
<odoo>
|
||||||
|
<record id="ir_cron_partner_autocomplete" model="ir.cron">
|
||||||
|
<field name="name">Partner Autocomplete : Sync with remote DB</field>
|
||||||
|
<field name="model_id" ref="model_res_partner_autocomplete_sync"/>
|
||||||
|
<field name="state">code</field>
|
||||||
|
<field name="code">model.start_sync()</field>
|
||||||
|
<field name="interval_number">1</field>
|
||||||
|
<field name="interval_type">minutes</field>
|
||||||
|
<field name="numbercall">-1</field>
|
||||||
|
<field eval="False" name="doall"/>
|
||||||
|
</record>
|
||||||
|
</odoo>
|
||||||
@@ -6,8 +6,8 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: Odoo Server saas~11.5\n"
|
"Project-Id-Version: Odoo Server saas~11.5\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2018-09-18 09:49+0000\n"
|
"POT-Creation-Date: 2018-09-21 11:54+0000\n"
|
||||||
"PO-Revision-Date: 2018-09-18 09:49+0000\n"
|
"PO-Revision-Date: 2018-09-21 11:54+0000\n"
|
||||||
"Last-Translator: <>\n"
|
"Last-Translator: <>\n"
|
||||||
"Language-Team: \n"
|
"Language-Team: \n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
@@ -15,26 +15,241 @@ msgstr ""
|
|||||||
"Content-Transfer-Encoding: \n"
|
"Content-Transfer-Encoding: \n"
|
||||||
"Plural-Forms: \n"
|
"Plural-Forms: \n"
|
||||||
|
|
||||||
|
#. module: partner_autocomplete
|
||||||
|
#: model_terms:ir.ui.view,arch_db:partner_autocomplete.additional_info_template
|
||||||
|
msgid "&nbsp;&nbsp;&nbsp;"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: partner_autocomplete
|
||||||
|
#: model_terms:ir.ui.view,arch_db:partner_autocomplete.additional_info_template
|
||||||
|
msgid "<b>Annual revenue:</b>"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: partner_autocomplete
|
||||||
|
#: model_terms:ir.ui.view,arch_db:partner_autocomplete.additional_info_template
|
||||||
|
msgid "<b>Description</b><br/>"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: partner_autocomplete
|
||||||
|
#: model_terms:ir.ui.view,arch_db:partner_autocomplete.additional_info_template
|
||||||
|
msgid "<b>Email addresses</b><br/>"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: partner_autocomplete
|
||||||
|
#: model_terms:ir.ui.view,arch_db:partner_autocomplete.additional_info_template
|
||||||
|
msgid "<b>Employees:</b>"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: partner_autocomplete
|
||||||
|
#: model_terms:ir.ui.view,arch_db:partner_autocomplete.additional_info_template
|
||||||
|
msgid "<b>Estimated annual revenue:</b>"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: partner_autocomplete
|
||||||
|
#: model_terms:ir.ui.view,arch_db:partner_autocomplete.additional_info_template
|
||||||
|
msgid "<b>Phone numbers</b><br/>"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: partner_autocomplete
|
||||||
|
#: model_terms:ir.ui.view,arch_db:partner_autocomplete.additional_info_template
|
||||||
|
msgid "<b>Sector:</b>"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: partner_autocomplete
|
||||||
|
#: model_terms:ir.ui.view,arch_db:partner_autocomplete.additional_info_template
|
||||||
|
msgid "<b>Social networks</b><br/>\n"
|
||||||
|
" &nbsp;&nbsp;&nbsp; <b>Facebook:</b>"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: partner_autocomplete
|
||||||
|
#: model_terms:ir.ui.view,arch_db:partner_autocomplete.additional_info_template
|
||||||
|
msgid "<b>Tech:</b>"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: partner_autocomplete
|
||||||
|
#: model_terms:ir.ui.view,arch_db:partner_autocomplete.additional_info_template
|
||||||
|
msgid "<br/>\n"
|
||||||
|
" &nbsp;&nbsp;&nbsp; <b>Crunchbase:</b>"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: partner_autocomplete
|
||||||
|
#: model_terms:ir.ui.view,arch_db:partner_autocomplete.additional_info_template
|
||||||
|
msgid "<br/>\n"
|
||||||
|
" &nbsp;&nbsp;&nbsp; <b>Linkedin:</b>"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: partner_autocomplete
|
||||||
|
#: model_terms:ir.ui.view,arch_db:partner_autocomplete.additional_info_template
|
||||||
|
msgid "<br/>\n"
|
||||||
|
" &nbsp;&nbsp;&nbsp; <b>Twitter:</b>"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: partner_autocomplete
|
||||||
|
#: model_terms:ir.ui.view,arch_db:partner_autocomplete.res_config_settings_view_form
|
||||||
|
msgid "<i class=\"fa fa-arrow-right\"/>\n"
|
||||||
|
" Buy more credits"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: partner_autocomplete
|
||||||
|
#: model_terms:ir.ui.view,arch_db:partner_autocomplete.res_config_settings_view_form
|
||||||
|
msgid "<i class=\"fa fa-exclamation-triangle text-warning\"/> &nbsp; You don't have credits to auto-complete companies' data anymore."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: partner_autocomplete
|
||||||
|
#: model_terms:ir.ui.view,arch_db:partner_autocomplete.res_config_settings_view_form
|
||||||
|
msgid "<span>&times;</span>"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: partner_autocomplete
|
||||||
|
#: model:ir.model.fields,field_description:partner_autocomplete.field_res_partner__additional_info
|
||||||
|
#: model:ir.model.fields,field_description:partner_autocomplete.field_res_users__additional_info
|
||||||
|
msgid "Additional info"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: partner_autocomplete
|
||||||
|
#. openerp-web
|
||||||
|
#: code:addons/partner_autocomplete/static/src/xml/partner_autocomplete.xml:30
|
||||||
|
#, python-format
|
||||||
|
msgid "Buy more credits"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: partner_autocomplete
|
||||||
|
#. openerp-web
|
||||||
|
#: code:addons/partner_autocomplete/static/src/xml/partner_autocomplete.xml:20
|
||||||
|
#, python-format
|
||||||
|
msgid "Checking remaining credit ..."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: partner_autocomplete
|
||||||
|
#: model_terms:ir.ui.view,arch_db:partner_autocomplete.res_config_settings_view_form
|
||||||
|
msgid "Close"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#. module: partner_autocomplete
|
#. module: partner_autocomplete
|
||||||
#: model:ir.model,name:partner_autocomplete.model_res_company
|
#: model:ir.model,name:partner_autocomplete.model_res_company
|
||||||
msgid "Companies"
|
msgid "Companies"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: partner_autocomplete
|
||||||
|
#: model:ir.model.fields,field_description:partner_autocomplete.field_res_company__partner_gid
|
||||||
|
#: model:ir.model.fields,field_description:partner_autocomplete.field_res_partner__partner_gid
|
||||||
|
#: model:ir.model.fields,field_description:partner_autocomplete.field_res_users__partner_gid
|
||||||
|
msgid "Company database ID"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#. module: partner_autocomplete
|
#. module: partner_autocomplete
|
||||||
#: model:ir.model,name:partner_autocomplete.model_res_partner
|
#: model:ir.model,name:partner_autocomplete.model_res_partner
|
||||||
msgid "Contact"
|
msgid "Contact"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#. module: partner_autocomplete
|
#. module: partner_autocomplete
|
||||||
#: model_terms:ir.ui.view,arch_db:partner_autocomplete.res_company_view_form
|
#. openerp-web
|
||||||
#: model_terms:ir.ui.view,arch_db:partner_autocomplete.view_partner_form
|
#: code:addons/partner_autocomplete/static/src/js/partner_autocomplete_many2one.js:108
|
||||||
#: model_terms:ir.ui.view,arch_db:partner_autocomplete.view_partner_short_form
|
#, python-format
|
||||||
msgid "VAT"
|
msgid "Create and Edit from Autocomplete :"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#. module: partner_autocomplete
|
#. module: partner_autocomplete
|
||||||
#: model_terms:ir.ui.view,arch_db:partner_autocomplete.view_partner_form
|
#: model:ir.model.fields,field_description:partner_autocomplete.field_res_partner_autocomplete_sync__create_uid
|
||||||
#: model_terms:ir.ui.view,arch_db:partner_autocomplete.view_partner_short_form
|
msgid "Created by"
|
||||||
msgid "e.g. BE0477472701"
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: partner_autocomplete
|
||||||
|
#: model:ir.model.fields,field_description:partner_autocomplete.field_res_partner_autocomplete_sync__create_date
|
||||||
|
msgid "Created on"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: partner_autocomplete
|
||||||
|
#: model:ir.model.fields,field_description:partner_autocomplete.field_res_partner_autocomplete_sync__display_name
|
||||||
|
msgid "Display Name"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: partner_autocomplete
|
||||||
|
#: model:ir.model.fields,field_description:partner_autocomplete.field_res_partner_autocomplete_sync__id
|
||||||
|
msgid "ID"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: partner_autocomplete
|
||||||
|
#. openerp-web
|
||||||
|
#: code:addons/partner_autocomplete/static/src/xml/partner_autocomplete.xml:23
|
||||||
|
#: model:ir.model.fields,field_description:partner_autocomplete.field_res_config_settings__partner_autocomplete_insufficient_credit
|
||||||
|
#, python-format
|
||||||
|
msgid "Insufficient credit"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: partner_autocomplete
|
||||||
|
#: model:ir.model.fields,field_description:partner_autocomplete.field_res_partner_autocomplete_sync__synched
|
||||||
|
msgid "Is synched"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: partner_autocomplete
|
||||||
|
#: model:ir.model.fields,field_description:partner_autocomplete.field_res_partner_autocomplete_sync____last_update
|
||||||
|
msgid "Last Modified on"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: partner_autocomplete
|
||||||
|
#: model:ir.model.fields,field_description:partner_autocomplete.field_res_partner_autocomplete_sync__write_uid
|
||||||
|
msgid "Last Updated by"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: partner_autocomplete
|
||||||
|
#: model:ir.model.fields,field_description:partner_autocomplete.field_res_partner_autocomplete_sync__write_date
|
||||||
|
msgid "Last Updated on"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: partner_autocomplete
|
||||||
|
#: model:ir.model.fields,field_description:partner_autocomplete.field_res_partner_autocomplete_sync__partner_id
|
||||||
|
msgid "Partner"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: partner_autocomplete
|
||||||
|
#: model:ir.actions.server,name:partner_autocomplete.ir_cron_partner_autocomplete_ir_actions_server
|
||||||
|
#: model:ir.cron,cron_name:partner_autocomplete.ir_cron_partner_autocomplete
|
||||||
|
#: model:ir.cron,name:partner_autocomplete.ir_cron_partner_autocomplete
|
||||||
|
msgid "Partner Autocomplete : Sync with remote DB"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: partner_autocomplete
|
||||||
|
#. openerp-web
|
||||||
|
#: code:addons/partner_autocomplete/static/src/xml/partner_autocomplete.xml:8
|
||||||
|
#, python-format
|
||||||
|
msgid "Placeholder"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: partner_autocomplete
|
||||||
|
#. openerp-web
|
||||||
|
#: code:addons/partner_autocomplete/static/src/xml/partner_autocomplete.xml:26
|
||||||
|
#, python-format
|
||||||
|
msgid "Remaining :"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: partner_autocomplete
|
||||||
|
#. openerp-web
|
||||||
|
#: code:addons/partner_autocomplete/static/src/js/partner_autocomplete_many2one.js:22
|
||||||
|
#, python-format
|
||||||
|
msgid "Searching Autocomplete..."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: partner_autocomplete
|
||||||
|
#: model_terms:ir.ui.view,arch_db:partner_autocomplete.view_res_partner_form_inherit_partner_autocomplete
|
||||||
|
#: model_terms:ir.ui.view,arch_db:partner_autocomplete.view_res_partner_short_form_inherit_partner_autocomplete
|
||||||
|
msgid "You can find a customer, a contact, etc. by its Name, TIN, Email or Internal Reference."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: partner_autocomplete
|
||||||
|
#. openerp-web
|
||||||
|
#: code:addons/partner_autocomplete/static/src/xml/partner_autocomplete.xml:26
|
||||||
|
#, python-format
|
||||||
|
msgid "credits"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: partner_autocomplete
|
||||||
|
#: model:ir.model,name:partner_autocomplete.model_res_config_settings
|
||||||
|
msgid "res.config.settings"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. module: partner_autocomplete
|
||||||
|
#: model:ir.model,name:partner_autocomplete.model_res_partner_autocomplete_sync
|
||||||
|
msgid "res.partner.autocomplete.sync"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||||
|
|
||||||
|
from . import res_partner
|
||||||
|
from . import res_company
|
||||||
|
from . import res_config_settings
|
||||||
|
from . import res_partner_autocomplete_sync
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||||
|
|
||||||
|
from odoo import api, fields, models
|
||||||
|
|
||||||
|
class ResCompany(models.Model):
|
||||||
|
_name = 'res.company'
|
||||||
|
_inherit = 'res.company'
|
||||||
|
|
||||||
|
partner_gid = fields.Integer('Company database ID', related="partner_id.partner_gid", inverse="_inverse_partner_gid", store=True)
|
||||||
|
|
||||||
|
def _inverse_partner_gid(self):
|
||||||
|
for company in self:
|
||||||
|
company.partner_id.partner_gid = company.partner_gid
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
# -*- 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'
|
||||||
|
|
||||||
|
partner_autocomplete_insufficient_credit = fields.Boolean('Insufficient credit', default=lambda self: self.env['iap.account'].get('partner_autocomplete').insufficient_credit)
|
||||||
|
|
||||||
|
@api.multi
|
||||||
|
def redirect_to_buy_autocmplete_credit(self):
|
||||||
|
return {
|
||||||
|
'type': 'ir.actions.act_url',
|
||||||
|
'url': self.env['iap.account'].get_credits_url('partner_autocomplete'),
|
||||||
|
'target': '_new',
|
||||||
|
}
|
||||||
@@ -0,0 +1,180 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from odoo import api, fields, models, exceptions, _
|
||||||
|
from odoo.addons.iap import jsonrpc
|
||||||
|
from requests.exceptions import ConnectionError, HTTPError
|
||||||
|
from odoo.tools import pycompat
|
||||||
|
from odoo.addons.iap.models.iap import InsufficientCreditError
|
||||||
|
|
||||||
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
PARTNER_REMOTE_URL = 'https://partner-autocomplete.odoo.com/iap/partner_autocomplete'
|
||||||
|
|
||||||
|
class ResPartner(models.Model):
|
||||||
|
_name = 'res.partner'
|
||||||
|
_inherit = 'res.partner'
|
||||||
|
|
||||||
|
partner_gid = fields.Integer('Company database ID')
|
||||||
|
additional_info = fields.Char('Additional info')
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _replace_location_code_by_id(self, record):
|
||||||
|
record['country_id'], record['state_id'] = self._find_country_data(
|
||||||
|
state_code=record.pop('state_code', False),
|
||||||
|
state_name=record.pop('state_name', False),
|
||||||
|
country_code=record.pop('country_code', False),
|
||||||
|
country_name=record.pop('country_name', False)
|
||||||
|
)
|
||||||
|
return record
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _format_data_company(self, company):
|
||||||
|
self._replace_location_code_by_id(company)
|
||||||
|
|
||||||
|
if company.get('child_ids'):
|
||||||
|
child_ids = []
|
||||||
|
for child in company.get('child_ids'):
|
||||||
|
child_ids.append(self._replace_location_code_by_id(child))
|
||||||
|
company['child_ids'] = child_ids
|
||||||
|
|
||||||
|
if company.get('additional_info'):
|
||||||
|
company['additional_info'] = self.env.ref('partner_autocomplete.additional_info_template').render(company.get('additional_info'))
|
||||||
|
|
||||||
|
return company
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _find_country_data(self, state_code, state_name, country_code, country_name):
|
||||||
|
country = self.env['res.country'].search([['code', '=ilike', country_code]])
|
||||||
|
if not country:
|
||||||
|
country = self.env['res.country'].search([['name', '=ilike', country_name]])
|
||||||
|
|
||||||
|
state_id = {}
|
||||||
|
country_id = {}
|
||||||
|
if country:
|
||||||
|
country_id = {
|
||||||
|
'id': country.id,
|
||||||
|
'display_name': country.display_name
|
||||||
|
}
|
||||||
|
if state_name or state_code:
|
||||||
|
state = self.env['res.country.state'].search([
|
||||||
|
('country_id', '=', country_id.get('id')),
|
||||||
|
'|',
|
||||||
|
('name', '=ilike', state_name),
|
||||||
|
('code', '=ilike', state_code)
|
||||||
|
], limit=1)
|
||||||
|
|
||||||
|
if state:
|
||||||
|
state_id = {
|
||||||
|
'id': state.id,
|
||||||
|
'display_name': state.display_name
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
_logger.info('Country code not found: %s', country_code)
|
||||||
|
|
||||||
|
return country_id, state_id
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _rpc_remote_api(self, action, params, timeout=15):
|
||||||
|
url = '%s/%s' % (PARTNER_REMOTE_URL, action)
|
||||||
|
account = self.env['iap.account'].get('partner_autocomplete')
|
||||||
|
params.update({
|
||||||
|
'db_uuid': self.env['ir.config_parameter'].sudo().get_param('database.uuid'),
|
||||||
|
'account_token': account.account_token,
|
||||||
|
'country_code': self.env.user.company_id.country_id.code,
|
||||||
|
'zip': self.env.user.company_id.zip,
|
||||||
|
})
|
||||||
|
try:
|
||||||
|
return jsonrpc(url=url, params=params, timeout=timeout), False
|
||||||
|
except (ConnectionError, HTTPError, exceptions.AccessError) as exception:
|
||||||
|
_logger.error('Autocomplete API error: %s' % str(exception))
|
||||||
|
return False, str(exception)
|
||||||
|
except InsufficientCreditError:
|
||||||
|
account = self.env['iap.account'].get('partner_autocomplete')
|
||||||
|
if account:
|
||||||
|
account.write({'insufficient_credit': True})
|
||||||
|
return False, 'Insufficient Credit'
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def autocomplete(self, query):
|
||||||
|
suggestions, error = self._rpc_remote_api('search', {
|
||||||
|
'query': query,
|
||||||
|
})
|
||||||
|
if suggestions:
|
||||||
|
results = []
|
||||||
|
for suggestion in suggestions:
|
||||||
|
results.append(suggestion)
|
||||||
|
return results
|
||||||
|
else:
|
||||||
|
return []
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def enrich_company(self, company_domain, partner_gid, vat):
|
||||||
|
response, error = self._rpc_remote_api('enrich', {
|
||||||
|
'domain': company_domain,
|
||||||
|
'partner_gid': partner_gid,
|
||||||
|
'vat': vat,
|
||||||
|
})
|
||||||
|
if response and response.get('company_data'):
|
||||||
|
return self._format_data_company(response.get('company_data'))
|
||||||
|
else:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def read_by_vat(self, vat):
|
||||||
|
vies_vat_data, error = self._rpc_remote_api('search_vat', {
|
||||||
|
'vat': vat,
|
||||||
|
})
|
||||||
|
if vies_vat_data:
|
||||||
|
return [self._format_data_company(vies_vat_data)]
|
||||||
|
else:
|
||||||
|
return []
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _is_company_in_europe(self, country_code):
|
||||||
|
country = self.env['res.country'].search([('code', '=ilike', country_code)])
|
||||||
|
if country:
|
||||||
|
country_id = country.id
|
||||||
|
europe = self.env.ref('base.europe')
|
||||||
|
if not europe:
|
||||||
|
europe = self.env["res.country.group"].search([('name', '=', 'Europe')], limit=1)
|
||||||
|
if not europe or country_id not in europe.country_ids.ids:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def _is_vat_syncable(self, vat):
|
||||||
|
vat_country_code = vat[:2]
|
||||||
|
partner_country_code = self.country_id and self.country_id.code
|
||||||
|
return self._is_company_in_europe(vat_country_code) and (partner_country_code == vat_country_code or not partner_country_code)
|
||||||
|
|
||||||
|
def _is_synchable(self):
|
||||||
|
already_synched = self.env['res.partner.autocomplete.sync'].search([('partner_id', '=', self.id), ('synched', '=', True)])
|
||||||
|
return self.is_company and self.partner_gid and not already_synched
|
||||||
|
|
||||||
|
def _update_autocomplete_data(self, vat):
|
||||||
|
self.ensure_one()
|
||||||
|
if vat and self._is_synchable() and self._is_vat_syncable(vat):
|
||||||
|
self.env['res.partner.autocomplete.sync'].sudo().add_to_queue(self.id)
|
||||||
|
|
||||||
|
@api.model_create_multi
|
||||||
|
def create(self, vals_list):
|
||||||
|
partners = super(ResPartner, self).create(vals_list)
|
||||||
|
if len(vals_list) == 1:
|
||||||
|
for partner, values in pycompat.izip(partners, vals_list):
|
||||||
|
partner._update_autocomplete_data(values.get('vat', False))
|
||||||
|
|
||||||
|
if partner.additional_info:
|
||||||
|
partner.message_post(body=partner.additional_info)
|
||||||
|
partner.write({'additional_info': False})
|
||||||
|
|
||||||
|
return partners
|
||||||
|
|
||||||
|
@api.multi
|
||||||
|
def write(self, values):
|
||||||
|
record = super(ResPartner, self).write(values)
|
||||||
|
if len(self) == 1:
|
||||||
|
for partner in self:
|
||||||
|
partner._update_autocomplete_data(values.get('vat', False))
|
||||||
|
|
||||||
|
return record
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from odoo import api, fields, models
|
||||||
|
|
||||||
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
class ResPartnerAutocompleteSync(models.Model):
|
||||||
|
_name = 'res.partner.autocomplete.sync'
|
||||||
|
|
||||||
|
partner_id = fields.Many2one('res.partner', string="Partner", ondelete='cascade')
|
||||||
|
synched = fields.Boolean('Is synched', default=False)
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def start_sync(self):
|
||||||
|
to_sync_items = self.search([('synched', '=', False)])
|
||||||
|
for to_sync_item in to_sync_items:
|
||||||
|
partner = to_sync_item.partner_id
|
||||||
|
|
||||||
|
params = {
|
||||||
|
'partner_gid': partner.partner_gid,
|
||||||
|
}
|
||||||
|
|
||||||
|
if partner.vat and partner._is_vat_syncable(partner.vat):
|
||||||
|
params['vat'] = partner.vat
|
||||||
|
result, error = partner._rpc_remote_api('update', params)
|
||||||
|
if error:
|
||||||
|
_logger.error('Send Partner to sync failed: %s' % str(error))
|
||||||
|
|
||||||
|
to_sync_item.write({'synched': True})
|
||||||
|
|
||||||
|
def add_to_queue(self, partner_id):
|
||||||
|
to_sync = self.search([('partner_id', '=', partner_id)])
|
||||||
|
if not to_sync:
|
||||||
|
to_sync = self.create({'partner_id': partner_id})
|
||||||
|
return to_sync
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||||
|
access_partner_autocomplete_sync_all_all,res.partner.autocomplete.sync.all,model_res_partner_autocomplete_sync,,1,0,0,0
|
||||||
|
access_partner_autocomplete_sync_portal,res.partner.autocomplete.sync.portal,model_res_partner_autocomplete_sync,base.group_portal,1,0,1,0
|
||||||
|
access_partner_autocomplete_sync_user,res.partner.autocomplete.sync.user,model_res_partner_autocomplete_sync,base.group_user,1,1,1,0
|
||||||
|
access_partner_autocomplete_sync_system,res.partner.autocomplete.sync.system,model_res_partner_autocomplete_sync,base.group_system,1,1,1,1
|
||||||
|
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,94 @@
|
|||||||
|
odoo.define('iap.credit.checker', function (require) {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var widgetRegistry = require('web.widget_registry');
|
||||||
|
var Widget = require('web.Widget');
|
||||||
|
|
||||||
|
var core = require('web.core');
|
||||||
|
var rpc = require('web.rpc');
|
||||||
|
|
||||||
|
var QWeb = core.qweb;
|
||||||
|
|
||||||
|
var IAPCreditChecker = Widget.extend({
|
||||||
|
className: 'o_field_iap_credit_checker',
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @constructor
|
||||||
|
* Prepares the basic rendering of edit mode by setting the root to be a
|
||||||
|
* div.dropdown.open.
|
||||||
|
* @see FieldChar.init
|
||||||
|
*/
|
||||||
|
init: function (parent, data, options) {
|
||||||
|
this._super.apply(this, arguments);
|
||||||
|
this.service_name = options.attrs.service_name;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @override
|
||||||
|
*/
|
||||||
|
start: function () {
|
||||||
|
this.$widget = $(QWeb.render('partner_autocomplete.iap_credit_checker'));
|
||||||
|
this.$loading = this.$widget.find('.loading');
|
||||||
|
this.$sufficient = this.$widget.find('.sufficient');
|
||||||
|
this.$insufficient = this.$widget.find('.insufficient');
|
||||||
|
this.$buyLink = this.$widget.find('.oe_link');
|
||||||
|
|
||||||
|
this.$widget.appendTo(this.$el);
|
||||||
|
|
||||||
|
this._getLink();
|
||||||
|
this._getCredits();
|
||||||
|
},
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
// Private
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
_getCredits: function () {
|
||||||
|
var self = this;
|
||||||
|
this._showLoading();
|
||||||
|
|
||||||
|
return rpc.query({
|
||||||
|
model: 'iap.account',
|
||||||
|
method: 'get_credits',
|
||||||
|
args: [this.service_name],
|
||||||
|
}, {
|
||||||
|
shadow: true,
|
||||||
|
}).then(function (credit) {
|
||||||
|
if (credit) self._showSufficient(credit);
|
||||||
|
else self._showInsufficient();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
_getLink: function () {
|
||||||
|
var self = this;
|
||||||
|
return rpc.query({
|
||||||
|
model: 'iap.account',
|
||||||
|
method: 'get_credits_url',
|
||||||
|
args: [this.service_name],
|
||||||
|
}, {
|
||||||
|
shadow: true,
|
||||||
|
}).then(function (url) {
|
||||||
|
self.$buyLink.attr('href', url);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
_showLoading: function () {
|
||||||
|
this.$loading.show();
|
||||||
|
this.$sufficient.hide();
|
||||||
|
this.$insufficient.hide();
|
||||||
|
},
|
||||||
|
_showSufficient: function (credits) {
|
||||||
|
this.$loading.hide();
|
||||||
|
this.$sufficient.show().find('.remaining_credits').text(credits);
|
||||||
|
this.$insufficient.hide();
|
||||||
|
},
|
||||||
|
_showInsufficient: function () {
|
||||||
|
this.$loading.hide();
|
||||||
|
this.$sufficient.hide();
|
||||||
|
this.$insufficient.show();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
widgetRegistry.add('iap_credit_checker', IAPCreditChecker);
|
||||||
|
|
||||||
|
return IAPCreditChecker;
|
||||||
|
});
|
||||||
@@ -0,0 +1,331 @@
|
|||||||
|
odoo.define('partner.autocomplete.core', function (require) {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var rpc = require('web.rpc');
|
||||||
|
var concurrency = require('web.concurrency');
|
||||||
|
|
||||||
|
return {
|
||||||
|
_dropPreviousOdoo: new concurrency.DropPrevious(),
|
||||||
|
_dropPreviousClearbit: new concurrency.DropPrevious(),
|
||||||
|
_timeout : 1000, // Timeout for Clearbit autocomplete in ms
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
// Public
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get list of companies via Autocomplete API
|
||||||
|
*
|
||||||
|
* @param {string} value
|
||||||
|
* @returns {Deferred}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
autocomplete: function (value) {
|
||||||
|
value = value.trim();
|
||||||
|
var def = $.Deferred(),
|
||||||
|
isVAT = this._isVAT(value),
|
||||||
|
odooSuggestions = [],
|
||||||
|
clearbitSuggestions = [];
|
||||||
|
|
||||||
|
var odooPromise = this._getOdooSuggestions(value, isVAT).then(function (suggestions){
|
||||||
|
odooSuggestions = suggestions;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Only get Clearbit suggestions if not a VAT number
|
||||||
|
var clearbitPromise = isVAT ? false : this._getClearbitSuggestions(value).then(function (suggestions){
|
||||||
|
clearbitSuggestions = suggestions;
|
||||||
|
});
|
||||||
|
|
||||||
|
var concatResults = function () {
|
||||||
|
// Add Clearbit result with Odoo result (with unique domain)
|
||||||
|
if (clearbitSuggestions && clearbitSuggestions.length) {
|
||||||
|
var websites = odooSuggestions.map(function (suggestion) {
|
||||||
|
return suggestion.website;
|
||||||
|
});
|
||||||
|
clearbitSuggestions.forEach(function (suggestion) {
|
||||||
|
if (websites.indexOf(suggestion.website) < 0) {
|
||||||
|
odooSuggestions.push(suggestion);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return def.resolve(odooSuggestions);
|
||||||
|
};
|
||||||
|
|
||||||
|
this._whenAll([odooPromise, clearbitPromise]).then(concatResults, concatResults);
|
||||||
|
|
||||||
|
return def;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get enrichment data
|
||||||
|
*
|
||||||
|
* @param {Object} company
|
||||||
|
* @returns {Deferred}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
enrichCompany: function (company) {
|
||||||
|
return rpc.query({
|
||||||
|
model: 'res.partner',
|
||||||
|
method: 'enrich_company',
|
||||||
|
args: [company.website, company.partner_gid, company.vat],
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the company logo as Base 64 image from url
|
||||||
|
*
|
||||||
|
* @param {string} url
|
||||||
|
* @returns {Deferred}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
getCompanyLogo: function (url) {
|
||||||
|
return this._getBase64Image(url).then(function (base64Image) {
|
||||||
|
// base64Image equals "data:" if image not available on given url
|
||||||
|
return base64Image ? base64Image.replace(/^data:image[^;]*;base64,?/, '') : false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get enriched data + logo before populating partner form
|
||||||
|
*
|
||||||
|
* @param {Object} company
|
||||||
|
* @returns {Deferred}
|
||||||
|
*/
|
||||||
|
getCreateData: function (company) {
|
||||||
|
var removeUselessFields = function (company) {
|
||||||
|
var fields = 'label,description,domain,logo,legal_name'.split(',');
|
||||||
|
fields.forEach(function (field) {
|
||||||
|
delete company[field];
|
||||||
|
});
|
||||||
|
|
||||||
|
var notEmptyFields = "country_id,state_id".split(',');
|
||||||
|
notEmptyFields.forEach(function (field) {
|
||||||
|
if (!company[field]) delete company[field];
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
var def = $.Deferred();
|
||||||
|
|
||||||
|
// Fetch additional company info via Autocomplete Enrichment API
|
||||||
|
var enrichPromise = this.enrichCompany(company);
|
||||||
|
|
||||||
|
// Get logo
|
||||||
|
var logoPromise = company.logo ? this.getCompanyLogo(company.logo) : false;
|
||||||
|
|
||||||
|
$.when(enrichPromise, logoPromise).done(function (company_data, logo_data) {
|
||||||
|
if (_.isEmpty(company_data)) company_data = company;
|
||||||
|
|
||||||
|
// Delete attribute to avoid "Field_changed" errors
|
||||||
|
removeUselessFields(company_data);
|
||||||
|
|
||||||
|
// Assign VAT coming from parent VIES VAT query
|
||||||
|
if (company.vat) company_data.vat = company.vat;
|
||||||
|
def.resolve({
|
||||||
|
company: company_data,
|
||||||
|
logo: logo_data
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return def;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check connectivity
|
||||||
|
*
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
isOnline: function () {
|
||||||
|
return navigator && navigator.onLine;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate: Not empty and length > 1
|
||||||
|
*
|
||||||
|
* @param {string} search_val
|
||||||
|
* @param {string} onlyVAT : Only valid VAT Number search
|
||||||
|
* @returns {boolean}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
validateSearchTerm: function (search_val, onlyVAT) {
|
||||||
|
if (onlyVAT) return this._isVAT(search_val);
|
||||||
|
else return search_val && search_val.length > 2;
|
||||||
|
},
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
// Private
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a deferred which will be resolved with the base64 data of the
|
||||||
|
* image fetched from the given url.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {string} url : the url where to find the image to fetch
|
||||||
|
* @returns {Deferred}
|
||||||
|
*/
|
||||||
|
_getBase64Image: function (url) {
|
||||||
|
var def = $.Deferred();
|
||||||
|
var xhr = new XMLHttpRequest();
|
||||||
|
xhr.onload = function () {
|
||||||
|
var reader = new FileReader();
|
||||||
|
reader.onloadend = function () {
|
||||||
|
def.resolve(reader.result);
|
||||||
|
};
|
||||||
|
reader.readAsDataURL(xhr.response);
|
||||||
|
};
|
||||||
|
xhr.open('GET', url);
|
||||||
|
xhr.responseType = 'blob';
|
||||||
|
xhr.onerror = def.reject.bind(def);
|
||||||
|
xhr.send();
|
||||||
|
return def;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use Clearbit Autocomplete API to return suggestions
|
||||||
|
*
|
||||||
|
* @param {string} value
|
||||||
|
* @returns {Deferred}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_getClearbitSuggestions: function (value) {
|
||||||
|
var url = 'https://autocomplete.clearbit.com/v1/companies/suggest?query=' + value;
|
||||||
|
var def = $.ajax({
|
||||||
|
url: url,
|
||||||
|
dataType: 'json',
|
||||||
|
timeout: this._timeout,
|
||||||
|
success: function (suggestions) {
|
||||||
|
suggestions.map(function (suggestion) {
|
||||||
|
suggestion.label = suggestion.name;
|
||||||
|
suggestion.website = suggestion.domain;
|
||||||
|
suggestion.description = suggestion.website;
|
||||||
|
return suggestion;
|
||||||
|
});
|
||||||
|
return suggestions;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return this._dropPreviousClearbit.add(def);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use Odoo Autocomplete API to return suggestions
|
||||||
|
*
|
||||||
|
* @param {string} value
|
||||||
|
* @param {boolean} isVAT
|
||||||
|
* @returns {Deferred}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_getOdooSuggestions: function (value, isVAT) {
|
||||||
|
var method = isVAT ? 'read_by_vat' : 'autocomplete';
|
||||||
|
|
||||||
|
var def = rpc.query({
|
||||||
|
model: 'res.partner',
|
||||||
|
method: method,
|
||||||
|
args: [value],
|
||||||
|
}, {
|
||||||
|
shadow: true,
|
||||||
|
}).then(function (suggestions) {
|
||||||
|
suggestions.map(function (suggestion) {
|
||||||
|
suggestion.logo = suggestion.logo || '';
|
||||||
|
suggestion.label = suggestion.legal_name || suggestion.name;
|
||||||
|
if (suggestion.vat) suggestion.description = suggestion.vat;
|
||||||
|
else if (suggestion.website) suggestion.description = suggestion.website;
|
||||||
|
|
||||||
|
if (suggestion.country_id && suggestion.country_id.display_name) {
|
||||||
|
if (suggestion.description) suggestion.description += _.str.sprintf(' (%s)', suggestion.country_id.display_name);
|
||||||
|
else suggestion.description += suggestion.country_id.display_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
return suggestion;
|
||||||
|
});
|
||||||
|
return suggestions;
|
||||||
|
});
|
||||||
|
|
||||||
|
return this._dropPreviousOdoo.add(def);
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Check if searched value is possibly a VAT : 2 first chars = alpha + min 5 numbers
|
||||||
|
*
|
||||||
|
* @param {string} search_val
|
||||||
|
* @returns {boolean}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_isVAT: function (search_val) {
|
||||||
|
var str = this._sanitizeVAT(search_val);
|
||||||
|
return checkVATNumber(str);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sanitize search value by removing all not alphanumeric
|
||||||
|
*
|
||||||
|
* @param {string} search_value
|
||||||
|
* @returns {string}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_sanitizeVAT: function (search_value) {
|
||||||
|
return search_value ? search_value.replace(/[^A-Za-z0-9]/g, '') : '';
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility to wait for multiple promises
|
||||||
|
* $.when will reject all promises whenever a promise is rejected
|
||||||
|
* This utility will continue
|
||||||
|
* Source : https://gist.github.com/fearphage/4341799
|
||||||
|
*
|
||||||
|
* @param array
|
||||||
|
* @returns {*}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_whenAll: function (array) {
|
||||||
|
var slice = [].slice;
|
||||||
|
|
||||||
|
var
|
||||||
|
resolveValues = arguments.length === 1 && $.isArray(array)
|
||||||
|
? array
|
||||||
|
: slice.call(arguments)
|
||||||
|
, length = resolveValues.length
|
||||||
|
, remaining = length
|
||||||
|
, deferred = $.Deferred()
|
||||||
|
, i = 0
|
||||||
|
, failed = 0
|
||||||
|
, rejectContexts = Array(length)
|
||||||
|
, rejectValues = Array(length)
|
||||||
|
, resolveContexts = Array(length)
|
||||||
|
, value
|
||||||
|
;
|
||||||
|
|
||||||
|
function updateFunc(index, contexts, values) {
|
||||||
|
return function () {
|
||||||
|
!(values === resolveValues) && failed++;
|
||||||
|
deferred.notifyWith(
|
||||||
|
contexts[index] = this
|
||||||
|
, values[index] = slice.call(arguments)
|
||||||
|
);
|
||||||
|
if (!(--remaining)) {
|
||||||
|
deferred[(!failed ? 'resolve' : 'reject') + 'With'](contexts, values);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
for (; i < length; i++) {
|
||||||
|
if ((value = resolveValues[i]) && $.isFunction(value.promise)) {
|
||||||
|
value.promise()
|
||||||
|
.done(updateFunc(i, resolveContexts, resolveValues))
|
||||||
|
.fail(updateFunc(i, rejectContexts, rejectValues))
|
||||||
|
;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
deferred.notifyWith(this, value);
|
||||||
|
--remaining;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!remaining) {
|
||||||
|
deferred.resolveWith(resolveContexts, resolveValues);
|
||||||
|
}
|
||||||
|
|
||||||
|
return deferred.promise();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
@@ -0,0 +1,339 @@
|
|||||||
|
odoo.define('partner.autocomplete.fieldchar', function (require) {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var basic_fields = require('web.basic_fields');
|
||||||
|
var core = require('web.core');
|
||||||
|
var field_registry = require('web.field_registry');
|
||||||
|
var Autocomplete = require('partner.autocomplete.core');
|
||||||
|
|
||||||
|
var QWeb = core.qweb;
|
||||||
|
|
||||||
|
var FieldChar = basic_fields.FieldChar;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FieldChar extension to suggest existing companies when changing the company
|
||||||
|
* name on a res.partner view (indeed, it is designed to change the "name",
|
||||||
|
* "website" and "image" fields of records of this model).
|
||||||
|
*/
|
||||||
|
var FieldAutocomplete = FieldChar.extend({
|
||||||
|
className: 'o_field_partner_autocomplete',
|
||||||
|
debounceSuggestions: 400,
|
||||||
|
resetOnAnyFieldChange: true,
|
||||||
|
|
||||||
|
jsLibs: [
|
||||||
|
'/partner_autocomplete/static/lib/jsvat.js'
|
||||||
|
],
|
||||||
|
|
||||||
|
events: _.extend({}, FieldChar.prototype.events, {
|
||||||
|
'keyup': '_onKeyup',
|
||||||
|
'mousedown .o_partner_autocomplete_suggestion': '_onMousedown',
|
||||||
|
'focusout': '_onFocusout',
|
||||||
|
'mouseenter .o_partner_autocomplete_suggestion': '_onHoverDropdown',
|
||||||
|
'click .o_partner_autocomplete_suggestion': '_onSuggestionClicked',
|
||||||
|
}),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @constructor
|
||||||
|
* Prepares the basic rendering of edit mode by setting the root to be a
|
||||||
|
* div.dropdown.open.
|
||||||
|
* @see FieldChar.init
|
||||||
|
*/
|
||||||
|
init: function () {
|
||||||
|
this._super.apply(this, arguments);
|
||||||
|
|
||||||
|
// If the autocomplete is applied to vat field, only search valid vat number
|
||||||
|
this.onlyVAT = this.name === 'vat';
|
||||||
|
|
||||||
|
if (this.mode === 'edit') {
|
||||||
|
this.tagName = 'div';
|
||||||
|
this.className += ' dropdown open';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.debounceSuggestions > 0) {
|
||||||
|
this._suggestCompanies = _.debounce(this._suggestCompanies.bind(this), this.debounceSuggestions);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
// Private
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the autocomplete should be active
|
||||||
|
* Active :
|
||||||
|
* - only when creating new record
|
||||||
|
* - on model res.partner and is_company=true
|
||||||
|
* - on model res.company
|
||||||
|
*
|
||||||
|
* @returns {boolean}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_isActive: function () {
|
||||||
|
return this.model === 'res.company' ||
|
||||||
|
(
|
||||||
|
this.model === 'res.partner'
|
||||||
|
&& this.record.data.is_company
|
||||||
|
&& !(this.record.data && this.record.data.id)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_removeDropdown: function () {
|
||||||
|
if (this.$dropdown) {
|
||||||
|
this.$dropdown.remove();
|
||||||
|
this.$dropdown = undefined;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the <input/> element and prepares it. Note: the dropdown rendering
|
||||||
|
* is handled outside of the rendering routine (but instead by reacting to
|
||||||
|
* user input).
|
||||||
|
*
|
||||||
|
* @override
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_renderEdit: function () {
|
||||||
|
this.$el.empty();
|
||||||
|
// Prepare and add the input
|
||||||
|
this._prepareInput().appendTo(this.$el);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Selects the given company suggestions by notifying changes to the view
|
||||||
|
* for the "name", "website" and "image" fields. This is of course intended
|
||||||
|
* to work only with the "res.partner" form view.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {Object} company
|
||||||
|
*/
|
||||||
|
_selectCompany: function (company) {
|
||||||
|
var self = this;
|
||||||
|
Autocomplete.getCreateData(company).then(function (data) {
|
||||||
|
if (data.logo) {
|
||||||
|
var logoField = self.model === 'res.partner' ? 'image' : 'logo';
|
||||||
|
data.company[logoField] = data.logo;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Some fields are unnecessary in res.company
|
||||||
|
if (self.model === 'res.company') {
|
||||||
|
var fields = 'comment,child_ids,bank_ids,additional_info'.split(',');
|
||||||
|
fields.forEach(function (field) {
|
||||||
|
delete data.company[field];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
self._setOne2ManyField('child_ids', data.company.child_ids);
|
||||||
|
delete data.company.child_ids;
|
||||||
|
|
||||||
|
self._setOne2ManyField('bank_ids', data.company.bank_ids);
|
||||||
|
delete data.company.bank_ids;
|
||||||
|
|
||||||
|
self.trigger_up('field_changed', {
|
||||||
|
dataPointID: self.dataPointID,
|
||||||
|
changes: data.company,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// update the input's value directly
|
||||||
|
if (this.onlyVAT)
|
||||||
|
this.$input.val(this._formatValue(company.vat));
|
||||||
|
else
|
||||||
|
this.$input.val(this._formatValue(company.name));
|
||||||
|
this._removeDropdown();
|
||||||
|
},
|
||||||
|
|
||||||
|
_setOne2ManyField: function (field, list) {
|
||||||
|
var self = this;
|
||||||
|
var viewType = this.record.viewType;
|
||||||
|
if (list && this.record.fieldsInfo[viewType] && this.record.fieldsInfo[viewType][field]) {
|
||||||
|
list.forEach(function (item) {
|
||||||
|
var changes = {};
|
||||||
|
changes[field] = {
|
||||||
|
operation: 'CREATE',
|
||||||
|
data: item,
|
||||||
|
};
|
||||||
|
|
||||||
|
self.trigger_up('field_changed', {
|
||||||
|
dataPointID: self.dataPointID,
|
||||||
|
changes: changes,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows the dropdown with the suggestions. If one is
|
||||||
|
* already opened, it removes the old one before rerendering the dropdown.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_showDropdown: function () {
|
||||||
|
this._removeDropdown();
|
||||||
|
if (this.suggestions.length > 0) {
|
||||||
|
this.$dropdown = $(QWeb.render('partner_autocomplete.dropdown', {
|
||||||
|
suggestions: this.suggestions,
|
||||||
|
}));
|
||||||
|
this.$dropdown.appendTo(this.$el);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows suggestions according to the given value.
|
||||||
|
* Note: this method is debounced (@see init).
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {string} value - searched term
|
||||||
|
*/
|
||||||
|
_suggestCompanies: function (value) {
|
||||||
|
var self = this;
|
||||||
|
if (Autocomplete.validateSearchTerm(value, this.onlyVAT) && Autocomplete.isOnline()) {
|
||||||
|
return Autocomplete.autocomplete(value).then(function (suggestions) {
|
||||||
|
if (suggestions && suggestions.length) {
|
||||||
|
self.suggestions = suggestions;
|
||||||
|
self._showDropdown();
|
||||||
|
} else {
|
||||||
|
self._removeDropdown();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this._removeDropdown();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
// Handlers
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called on focusout -> removes the suggestions dropdown.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_onFocusout: function () {
|
||||||
|
this._removeDropdown();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when hovering a suggestion in the dropdown -> sets it as active.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {Event} e
|
||||||
|
*/
|
||||||
|
_onHoverDropdown: function (e) {
|
||||||
|
this.$dropdown.find('.active').removeClass('active');
|
||||||
|
$(e.currentTarget).parent().addClass('active');
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @override of FieldChar (called when the user is typing text)
|
||||||
|
* Checks the <input/> value and shows suggestions according to
|
||||||
|
* this value.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_onInput: function () {
|
||||||
|
this._super.apply(this, arguments);
|
||||||
|
if (this._isActive()) {
|
||||||
|
this._suggestCompanies(this.$input.val());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @override of FieldChar
|
||||||
|
* Changes the "up" and "down" key behavior when the dropdown is opened (to
|
||||||
|
* navigate through dropdown suggestions).
|
||||||
|
* Triggered by keydown to execute the navigation multiple times when the
|
||||||
|
* user keeps the "down" or "up" pressed.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {Event} e
|
||||||
|
*/
|
||||||
|
_onKeydown: function (e) {
|
||||||
|
switch (e.which) {
|
||||||
|
case $.ui.keyCode.UP:
|
||||||
|
case $.ui.keyCode.DOWN:
|
||||||
|
if (!this.$dropdown) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
e.preventDefault();
|
||||||
|
var $suggestions = this.$dropdown.children();
|
||||||
|
var $active = $suggestions.filter('.active');
|
||||||
|
var $to;
|
||||||
|
if ($active.length) {
|
||||||
|
$to = e.which === $.ui.keyCode.DOWN ?
|
||||||
|
$active.next() :
|
||||||
|
$active.prev();
|
||||||
|
} else {
|
||||||
|
$to = $suggestions.first();
|
||||||
|
}
|
||||||
|
if ($to.length) {
|
||||||
|
$active.removeClass('active');
|
||||||
|
$to.addClass('active');
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._super.apply(this, arguments);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called on keyup events to:
|
||||||
|
* -> remove the suggestions dropdown when hitting the "escape" key
|
||||||
|
* -> select the highlighted suggestion when hitting the "enter" key
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {Event} e
|
||||||
|
*/
|
||||||
|
_onKeyup: function (e) {
|
||||||
|
switch (e.which) {
|
||||||
|
case $.ui.keyCode.ESCAPE:
|
||||||
|
e.preventDefault();
|
||||||
|
this._removeDropdown();
|
||||||
|
break;
|
||||||
|
case $.ui.keyCode.ENTER:
|
||||||
|
if (!this.$dropdown) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
e.preventDefault();
|
||||||
|
var $active = this.$dropdown.find('.o_partner_autocomplete_suggestion.active');
|
||||||
|
if (!$active.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._selectCompany(this.suggestions[$active.data('index')]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called on mousedown event on a suggestion -> prevent default
|
||||||
|
* action so that the <input/> element does not lose the focus.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {Event} e
|
||||||
|
*/
|
||||||
|
_onMousedown: function (e) {
|
||||||
|
e.preventDefault(); // prevent losing focus on suggestion click
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a dropdown suggestion is clicked -> trigger_up changes for
|
||||||
|
* some fields in the view (not only this <input/> one) with the associated
|
||||||
|
* data (@see _selectCompany).
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {Event} e
|
||||||
|
*/
|
||||||
|
_onSuggestionClicked: function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
this._selectCompany(this.suggestions[$(e.currentTarget).data('index')]);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
field_registry.add('field_partner_autocomplete', FieldAutocomplete);
|
||||||
|
|
||||||
|
return FieldAutocomplete;
|
||||||
|
});
|
||||||
@@ -0,0 +1,138 @@
|
|||||||
|
odoo.define('partner.autocomplete.many2one', function (require) {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var FieldMany2One = require('web.relational_fields').FieldMany2One;
|
||||||
|
var core = require('web.core');
|
||||||
|
var Autocomplete = require('partner.autocomplete.core');
|
||||||
|
var field_registry = require('web.field_registry');
|
||||||
|
|
||||||
|
var _t = core._t;
|
||||||
|
|
||||||
|
var PartnerField = FieldMany2One.extend({
|
||||||
|
jsLibs: [
|
||||||
|
'/partner_autocomplete/static/lib/jsvat.js'
|
||||||
|
],
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @override
|
||||||
|
*/
|
||||||
|
init: function () {
|
||||||
|
this._super.apply(this, arguments);
|
||||||
|
this._addAutocompleteSource(this._searchSuggestions, {
|
||||||
|
placeholder: _t('Searching Autocomplete...'),
|
||||||
|
order: 20,
|
||||||
|
validation: Autocomplete.validateSearchTerm,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.additionalContext['show_vat'] = true;
|
||||||
|
},
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
// Private
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Action : create popup form with pre-filled values from Autocomplete
|
||||||
|
*
|
||||||
|
* @param {Object} company
|
||||||
|
* @returns {Deferred}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_createPartner: function (company) {
|
||||||
|
var self = this;
|
||||||
|
self.$('input').val('');
|
||||||
|
|
||||||
|
return Autocomplete.getCreateData(company).then(function (data){
|
||||||
|
var context = {
|
||||||
|
'default_is_company': true
|
||||||
|
};
|
||||||
|
_.each(data.company, function (val, key) {
|
||||||
|
context['default_' + key] = val && val.id ? val.id : val;
|
||||||
|
});
|
||||||
|
|
||||||
|
// if(data.company.street_name && !data.company.street_number) context.default_street_number = '';
|
||||||
|
if (data.logo) context.default_image = data.logo;
|
||||||
|
|
||||||
|
return self._searchCreatePopup("form", false, context);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modify autocomplete results rendering
|
||||||
|
* Add logo in the autocomplete results if logo is provided
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_modifyAutompleteRendering: function (){
|
||||||
|
var api = this.$input.data('ui-autocomplete');
|
||||||
|
var renderWithoutLogo = api._renderItem;
|
||||||
|
api._renderItem = function ( ul, item ) {
|
||||||
|
var $li = renderWithoutLogo.call(this, ul, item);
|
||||||
|
if (item.logo){
|
||||||
|
var $a = $li.find('>a').addClass('o_partner_autocomplete_dropdown_item');
|
||||||
|
var $img = $('<img/>').attr('src', item.logo);
|
||||||
|
$a.append($img);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $li;
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @override
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_renderEdit: function (){
|
||||||
|
this._super.apply(this, arguments);
|
||||||
|
this._modifyAutompleteRendering();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Query Autocomplete and add results to the popup
|
||||||
|
*
|
||||||
|
* @override
|
||||||
|
* @param search_val {string}
|
||||||
|
* @returns {Deferred}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_searchSuggestions: function (search_val) {
|
||||||
|
var def = $.Deferred();
|
||||||
|
|
||||||
|
if (Autocomplete.isOnline()) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
Autocomplete.autocomplete(search_val).then(function (suggestions) {
|
||||||
|
var choices = [];
|
||||||
|
if (suggestions && suggestions.length) {
|
||||||
|
choices.push({
|
||||||
|
label: _t('Create and Edit from Autocomplete :'),
|
||||||
|
});
|
||||||
|
_.each(suggestions, function (suggestion) {
|
||||||
|
var label =_.str.sprintf('%s - %s', suggestion.label, suggestion.description);
|
||||||
|
label = label.replace(new RegExp(search_val, "gi"), "<b>$&</b>");
|
||||||
|
|
||||||
|
choices.push({
|
||||||
|
label: label,
|
||||||
|
action: function () {
|
||||||
|
self._createPartner(suggestion);
|
||||||
|
},
|
||||||
|
classname: 'o_m2o_dropdown_option',
|
||||||
|
logo: suggestion.logo,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
def.resolve(choices);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
def.resolve([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return def;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
field_registry.add('res_partner_many2one', PartnerField);
|
||||||
|
|
||||||
|
return PartnerField;
|
||||||
|
});
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
.o_field_partner_autocomplete.dropdown {
|
||||||
|
display: block;
|
||||||
|
|
||||||
|
> .o_partner_autocomplete_dropdown .dropdown-item {
|
||||||
|
min-width: 300px;
|
||||||
|
padding: 4px 8px;
|
||||||
|
> img {
|
||||||
|
float: left;
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
}
|
||||||
|
> .o_partner_autocomplete_info {
|
||||||
|
margin-left: 50px;
|
||||||
|
> * {
|
||||||
|
@include o-text-overflow(block);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui-autocomplete .ui-menu-item > a.o_partner_autocomplete_dropdown_item {
|
||||||
|
> img {
|
||||||
|
float: right;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<templates>
|
||||||
|
<div t-name="partner_autocomplete.dropdown" class="o_partner_autocomplete_dropdown dropdown-menu show" role="menu">
|
||||||
|
<t t-foreach="suggestions" t-as="info">
|
||||||
|
<a role="menuitem" href="#"
|
||||||
|
t-attf-class="dropdown-item o_partner_autocomplete_suggestion clearfix#{info_index == 0 and ' active' or ''}"
|
||||||
|
t-att-data-index="info_index">
|
||||||
|
<img t-att-src="info['logo']" onerror="this.src='/base/static/img/company_image.png'" alt="Placeholder"/>
|
||||||
|
<div class="o_partner_autocomplete_info">
|
||||||
|
<strong><t t-esc="info['label'] or ' '"/></strong>
|
||||||
|
<div><t t-esc="info['description']"/></div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</t>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div t-name="partner_autocomplete.iap_credit_checker" class="mt-2 row">
|
||||||
|
<div class="col-sm">
|
||||||
|
<div class="loading text-muted">
|
||||||
|
<i class="fa fa-spinner fa-spin"/> Checking remaining credit ...
|
||||||
|
</div>
|
||||||
|
<div class="insufficient">
|
||||||
|
<i class="fa fa-exclamation-triangle text-warning"/> Insufficient credit<br/>
|
||||||
|
</div>
|
||||||
|
<div class="sufficient">
|
||||||
|
<i class="fa fa-check-circle text-success"/> Remaining : <b class="remaining_credits"/> credits
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm text-right">
|
||||||
|
<a class="oe_link" target="_blank"><i class="fa fa-arrow-right"/> Buy more credits</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</templates>
|
||||||
@@ -0,0 +1,335 @@
|
|||||||
|
odoo.define('partner_autocomplete.tests', function (require) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var FormView = require('web.FormView');
|
||||||
|
var testUtils = require("web.test_utils");
|
||||||
|
var AutocompleteCore = require('partner.autocomplete.core');
|
||||||
|
var AutocompleteField = require('partner.autocomplete.fieldchar');
|
||||||
|
|
||||||
|
var createView = testUtils.createAsyncView;
|
||||||
|
|
||||||
|
function _compareResultFields(assert, form, fields, createData) {
|
||||||
|
var type, formatted, $fieldInput;
|
||||||
|
|
||||||
|
_.each(createData, function (val, key) {
|
||||||
|
if (fields[key]) {
|
||||||
|
if (key === 'image') {
|
||||||
|
if (val) val = 'data:image/png;base64,' + val;
|
||||||
|
assert.strictEqual(form.$(".o_field_image img").attr("data-src"), val, 'image value should have been updated to "' + val + '"');
|
||||||
|
} else {
|
||||||
|
type = fields[key].type;
|
||||||
|
$fieldInput = form.$('input[name="' + key + '"]');
|
||||||
|
if ($fieldInput.length) {
|
||||||
|
formatted = $fieldInput.val();
|
||||||
|
formatted = type === 'integer' ? parseInt(formatted, 10) : formatted;
|
||||||
|
assert.strictEqual(
|
||||||
|
formatted,
|
||||||
|
val === false ? 0 : val,
|
||||||
|
key + ' value should have been updated to "' + val + '"'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QUnit.module('partner_autocomplete', {
|
||||||
|
before: function () {
|
||||||
|
var suggestions = [
|
||||||
|
{name: "Odoo", website: "odoo.com", logo: "odoo.com/logo.png", vat: "BE0477472701"}
|
||||||
|
];
|
||||||
|
|
||||||
|
var enrich_data = {
|
||||||
|
country_id: 20,
|
||||||
|
state_id: false,
|
||||||
|
partner_gid: 1,
|
||||||
|
website: "odoo.com",
|
||||||
|
comment: "Comment on Odoo",
|
||||||
|
street: "40 Chaussée de Namur",
|
||||||
|
city: "Ramillies",
|
||||||
|
zip: "1367",
|
||||||
|
phone: "+1 650-691-3277",
|
||||||
|
email: "info@odoo.com",
|
||||||
|
vat: "BE0477472701",
|
||||||
|
};
|
||||||
|
|
||||||
|
testUtils.patch(AutocompleteCore, {
|
||||||
|
_getBase64Image: function (url) {
|
||||||
|
return $.when(url === "odoo.com/logo.png" ? "odoobase64" : "");
|
||||||
|
},
|
||||||
|
isOnline: function () {
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
getCreateData: function (company) {
|
||||||
|
var self = this;
|
||||||
|
var def = this._super.apply(this, arguments);
|
||||||
|
def.then(function (data) {
|
||||||
|
self._createData = data.company;
|
||||||
|
});
|
||||||
|
return def;
|
||||||
|
},
|
||||||
|
enrichCompany: function (company) {
|
||||||
|
return $.when(enrich_data);
|
||||||
|
},
|
||||||
|
_getOdooSuggestions(value, isVAT) {
|
||||||
|
var results = _.filter(suggestions, function (suggestion) {
|
||||||
|
value = value ? value.toLowerCase() : '';
|
||||||
|
if (isVAT) return (suggestion.vat.toLowerCase().indexOf(value) >= 0);
|
||||||
|
else return (suggestion.name.toLowerCase().indexOf(value) >= 0);
|
||||||
|
});
|
||||||
|
return $.when(results);
|
||||||
|
},
|
||||||
|
_getClearbitSuggestions(value) {
|
||||||
|
return this._getOdooSuggestions(value);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
testUtils.patch(AutocompleteField, {
|
||||||
|
debounceSuggestions: 0,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
beforeEach: function () {
|
||||||
|
this.data = {
|
||||||
|
'res.partner': {
|
||||||
|
fields: {
|
||||||
|
company_type: {
|
||||||
|
string: "Company Type",
|
||||||
|
type: "selection",
|
||||||
|
selection: [["company", "Company"], ["individual", "Individual"]],
|
||||||
|
searchable: true
|
||||||
|
},
|
||||||
|
name: {string: "Name", type: "char", searchable: true},
|
||||||
|
website: {string: "Website", type: "char", searchable: true},
|
||||||
|
image: {string: "Image", type: "binary", searchable: true},
|
||||||
|
email: {string: "Email", type: "char", searchable: true},
|
||||||
|
phone: {string: "Phone", type: "char", searchable: true},
|
||||||
|
street: {string: "Street", type: "char", searchable: true},
|
||||||
|
city: {string: "City", type: "char", searchable: true},
|
||||||
|
zip: {string: "Zip", type: "char", searchable: true},
|
||||||
|
state_id: {string: "State", type: "integer", searchable: true},
|
||||||
|
country_id: {string: "Country", type: "integer", searchable: true},
|
||||||
|
comment: {string: "Comment", type: "char", searchable: true},
|
||||||
|
vat: {string: "Vat", type: "char", searchable: true},
|
||||||
|
is_company: {string: "Is comapny", type: "bool", searchable: true},
|
||||||
|
partner_gid: {string: "Company data ID", type: "integer", searchable: true},
|
||||||
|
},
|
||||||
|
records: [],
|
||||||
|
onchanges: {
|
||||||
|
company_type: function (obj) {
|
||||||
|
obj.is_company = obj.company_type === 'company';
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
after: function () {
|
||||||
|
testUtils.unpatch(AutocompleteField);
|
||||||
|
testUtils.unpatch(AutocompleteCore);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
QUnit.test("Partner autocomplete : Company type = Individual", function (assert) {
|
||||||
|
assert.expect(2);
|
||||||
|
var done = assert.async();
|
||||||
|
createView({
|
||||||
|
View: FormView,
|
||||||
|
model: 'res.partner',
|
||||||
|
data: this.data,
|
||||||
|
arch:
|
||||||
|
'<form>' +
|
||||||
|
'<field name="company_type"/>' +
|
||||||
|
'<field name="name" widget="field_partner_autocomplete"/>' +
|
||||||
|
'<field name="website"/>' +
|
||||||
|
'<field name="image" widget="image"/>' +
|
||||||
|
'</form>',
|
||||||
|
}).then(function (form){
|
||||||
|
// Set company type to Individual
|
||||||
|
var $company_type = form.$("select[name='company_type']");
|
||||||
|
$company_type.val('"individual"').trigger('change');
|
||||||
|
|
||||||
|
// Check input exists
|
||||||
|
var $input = form.$(".o_field_partner_autocomplete > input:visible");
|
||||||
|
assert.strictEqual($input.length, 1, "there should be an <input/> for the Partner field");
|
||||||
|
|
||||||
|
// Change input val and assert nothing happens
|
||||||
|
$input.val("odoo").trigger("input");
|
||||||
|
var $dropdown = form.$(".o_field_partner_autocomplete .dropdown-menu:visible");
|
||||||
|
assert.strictEqual($dropdown.length, 0, "there should not be an opened dropdown");
|
||||||
|
|
||||||
|
form.destroy();
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
QUnit.test("Partner autocomplete : Company type = Company / Name search", function (assert) {
|
||||||
|
assert.expect(21);
|
||||||
|
var done = assert.async();
|
||||||
|
var fields = this.data['res.partner'].fields;
|
||||||
|
createView({
|
||||||
|
View: FormView,
|
||||||
|
model: 'res.partner',
|
||||||
|
data: this.data,
|
||||||
|
arch:
|
||||||
|
'<form>' +
|
||||||
|
'<field name="company_type"/>' +
|
||||||
|
'<field name="name" widget="field_partner_autocomplete"/>' +
|
||||||
|
'<field name="website"/>' +
|
||||||
|
'<field name="image" widget="image"/>' +
|
||||||
|
'<field name="email"/>' +
|
||||||
|
'<field name="phone"/>' +
|
||||||
|
'<field name="street"/>' +
|
||||||
|
'<field name="city"/>' +
|
||||||
|
'<field name="state_id"/>' +
|
||||||
|
'<field name="zip"/>' +
|
||||||
|
'<field name="country_id"/>' +
|
||||||
|
'<field name="comment"/>' +
|
||||||
|
'<field name="vat"/>' +
|
||||||
|
'</form>',
|
||||||
|
mockRPC: function (route, args) {
|
||||||
|
if (args.method) {
|
||||||
|
assert.step(args.method);
|
||||||
|
}
|
||||||
|
if (route === "/web/static/src/img/placeholder.png"
|
||||||
|
|| route === "odoo.com/logo.png"
|
||||||
|
|| route === "data:image/png;base64,odoobase64") { // land here as it is not valid base64 content
|
||||||
|
return $.when();
|
||||||
|
}
|
||||||
|
return this._super.apply(this, arguments);
|
||||||
|
},
|
||||||
|
}).then(function (form){
|
||||||
|
// Set company type to Company
|
||||||
|
var $company_type = form.$("select[name='company_type']");
|
||||||
|
$company_type.val('"company"').trigger('change');
|
||||||
|
|
||||||
|
// Check input exists
|
||||||
|
var $input = form.$(".o_field_partner_autocomplete > input:visible");
|
||||||
|
assert.strictEqual($input.length, 1, "there should be an <input/> for the field");
|
||||||
|
|
||||||
|
// Change input val and assert changes
|
||||||
|
$input.val("odoo").trigger("input");
|
||||||
|
|
||||||
|
var $dropdown = form.$(".o_field_partner_autocomplete .dropdown-menu:visible");
|
||||||
|
assert.strictEqual($dropdown.length, 1, "there should be an opened dropdown");
|
||||||
|
assert.strictEqual($dropdown.children().length, 1, "there should be only one proposition");
|
||||||
|
|
||||||
|
$dropdown.find("a").first().click();
|
||||||
|
$input = form.$(".o_field_partner_autocomplete > input");
|
||||||
|
assert.strictEqual($input.val(), "Odoo", "Input value should have been updated to \"Odoo\"");
|
||||||
|
assert.strictEqual(form.$("input.o_field_widget").val(), "odoo.com", "website value should have been updated to \"odoo.com\"");
|
||||||
|
|
||||||
|
_compareResultFields(assert, form, fields, AutocompleteCore._createData);
|
||||||
|
|
||||||
|
// Try suggestion with bullshit query
|
||||||
|
$input.val("ZZZZZZZZZZZZZZZZZZZZZZ").trigger("input");
|
||||||
|
$dropdown = form.$(".o_field_partner_autocomplete .dropdown-menu:visible");
|
||||||
|
assert.strictEqual($dropdown.length, 0, "there should be no opened dropdown when no result");
|
||||||
|
|
||||||
|
// Try autocomplete again
|
||||||
|
$input.val("odoo").trigger("input");
|
||||||
|
$dropdown = form.$(".o_field_partner_autocomplete .dropdown-menu:visible");
|
||||||
|
assert.strictEqual($dropdown.length, 1, "there should be an opened dropdown when typing odoo letters again");
|
||||||
|
|
||||||
|
// Test if dropdown closes on focusout
|
||||||
|
$input.trigger("focusout");
|
||||||
|
$dropdown = form.$(".o_field_partner_autocomplete .dropdown-menu:visible");
|
||||||
|
assert.strictEqual($dropdown.length, 0, "unfocusing the input should close the dropdown");
|
||||||
|
|
||||||
|
form.destroy();
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
QUnit.test("Partner autocomplete : Company type = Company / VAT search", function (assert) {
|
||||||
|
assert.expect(32);
|
||||||
|
var done = assert.async();
|
||||||
|
var fields = this.data['res.partner'].fields;
|
||||||
|
createView({
|
||||||
|
View: FormView,
|
||||||
|
model: 'res.partner',
|
||||||
|
data: this.data,
|
||||||
|
arch:
|
||||||
|
'<form>' +
|
||||||
|
'<field name="company_type"/>' +
|
||||||
|
'<field name="name" widget="field_partner_autocomplete"/>' +
|
||||||
|
'<field name="website"/>' +
|
||||||
|
'<field name="image" widget="image"/>' +
|
||||||
|
'<field name="email"/>' +
|
||||||
|
'<field name="phone"/>' +
|
||||||
|
'<field name="street"/>' +
|
||||||
|
'<field name="city"/>' +
|
||||||
|
'<field name="state_id"/>' +
|
||||||
|
'<field name="zip"/>' +
|
||||||
|
'<field name="country_id"/>' +
|
||||||
|
'<field name="comment"/>' +
|
||||||
|
'<field name="vat"/>' +
|
||||||
|
'</form>',
|
||||||
|
mockRPC: function (route, args) {
|
||||||
|
if (args.method) {
|
||||||
|
assert.step(args.method);
|
||||||
|
}
|
||||||
|
if (route === "/web/static/src/img/placeholder.png"
|
||||||
|
|| route === "odoo.com/logo.png"
|
||||||
|
|| route === "data:image/png;base64,odoobase64") { // land here as it is not valid base64 content
|
||||||
|
return $.when();
|
||||||
|
}
|
||||||
|
return this._super.apply(this, arguments);
|
||||||
|
},
|
||||||
|
}).then(function (form){
|
||||||
|
// Set company type to Company
|
||||||
|
var $company_type = form.$("select[name='company_type']");
|
||||||
|
$company_type.val('"company"').trigger('change');
|
||||||
|
|
||||||
|
|
||||||
|
// Check input exists
|
||||||
|
var $input = form.$(".o_field_partner_autocomplete > input:visible");
|
||||||
|
assert.strictEqual($input.length, 1, "there should be an <input/> for the field");
|
||||||
|
|
||||||
|
// Set incomplete VAT and assert changes
|
||||||
|
$input.val("BE047747270").trigger("input");
|
||||||
|
|
||||||
|
var $dropdown = form.$(".o_field_partner_autocomplete .dropdown-menu:visible");
|
||||||
|
assert.strictEqual($dropdown.length, 0, "there should be no opened dropdown no results with incomplete VAT number");
|
||||||
|
|
||||||
|
// Set complete VAT and assert changes
|
||||||
|
// First suggestion (only vat result)
|
||||||
|
$input.val("BE0477472701").trigger("input");
|
||||||
|
$dropdown = form.$(".o_field_partner_autocomplete .dropdown-menu:visible");
|
||||||
|
assert.strictEqual($dropdown.length, 1, "there should be an opened dropdown");
|
||||||
|
assert.strictEqual($dropdown.children().length, 1, "there should be one proposition for complete VAT number");
|
||||||
|
|
||||||
|
$dropdown.find("a").first().click();
|
||||||
|
|
||||||
|
$input = form.$(".o_field_partner_autocomplete > input");
|
||||||
|
assert.strictEqual($input.val(), "Odoo", "Input value should have been updated to \"Odoo\"");
|
||||||
|
|
||||||
|
_compareResultFields(assert, form, fields, AutocompleteCore._createData);
|
||||||
|
|
||||||
|
// Set complete VAT and assert changes
|
||||||
|
// Second suggestion (only vat + clearbit result)
|
||||||
|
$input.val("BE0477472701").trigger("input");
|
||||||
|
$dropdown = form.$(".o_field_partner_autocomplete .dropdown-menu:visible");
|
||||||
|
assert.strictEqual($dropdown.length, 1, "there should be an opened dropdown");
|
||||||
|
assert.strictEqual($dropdown.children().length, 1, "there should be one proposition for complete VAT number");
|
||||||
|
|
||||||
|
$dropdown.find("a").first().click();
|
||||||
|
|
||||||
|
$input = form.$(".o_field_partner_autocomplete > input");
|
||||||
|
assert.strictEqual($input.val(), "Odoo", "Input value should have been updated to \"Odoo\"");
|
||||||
|
|
||||||
|
_compareResultFields(assert, form, fields, AutocompleteCore._createData);
|
||||||
|
|
||||||
|
// Test if dropdown closes on focusout
|
||||||
|
$input.trigger("focusout");
|
||||||
|
$dropdown = form.$(".o_field_partner_autocomplete .dropdown-menu:visible");
|
||||||
|
assert.strictEqual($dropdown.length, 0, "unfocusing the input should close the dropdown");
|
||||||
|
|
||||||
|
form.destroy();
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
<template id="additional_info_template">
|
||||||
|
<p t-if="description">
|
||||||
|
<b>Description</b><br/>
|
||||||
|
<t t-esc="description"/><br/>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<t t-if="employees">
|
||||||
|
<b>Employees:</b> <t t-esc="employees"/><br/>
|
||||||
|
</t>
|
||||||
|
<t t-if="annual_revenue">
|
||||||
|
<b>Annual revenue:</b> <t t-esc="annual_revenue"/><br/>
|
||||||
|
</t>
|
||||||
|
<t t-if="estimated_annual_revenue">
|
||||||
|
<b>Estimated annual revenue:</b> <t t-esc="estimated_annual_revenue"/><br/>
|
||||||
|
</t>
|
||||||
|
<t t-if="sector">
|
||||||
|
<b>Sector:</b> <t t-esc="sector"/><br/>
|
||||||
|
</t>
|
||||||
|
<t t-if="tech">
|
||||||
|
<b>Tech:</b> <t t-esc="tech"/><br/>
|
||||||
|
</t>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<b>Social networks</b><br/>
|
||||||
|
&nbsp;&nbsp;&nbsp; <b>Facebook:</b> <a t-att-href="facebook" target="_blank"><t t-esc="facebook"/></a><br/>
|
||||||
|
&nbsp;&nbsp;&nbsp; <b>Linkedin:</b> <a t-att-href="linkedin" target="_blank"><t t-esc="linkedin"/></a><br/>
|
||||||
|
&nbsp;&nbsp;&nbsp; <b>Crunchbase:</b> <a t-att-href="crunchbase" target="_blank"><t t-esc="crunchbase"/></a><br/>
|
||||||
|
&nbsp;&nbsp;&nbsp; <b>Twitter:</b> <a t-att-href="twitter" target="_blank"><t t-esc="twitter"/></a><br/>
|
||||||
|
</p>
|
||||||
|
<p t-if="emails">
|
||||||
|
<b>Email addresses</b><br/>
|
||||||
|
<t t-foreach="emails" t-as="email">
|
||||||
|
&nbsp;&nbsp;&nbsp; <a t-att-href="'mailto:%s' % email"><t t-esc="email"/></a><br/>
|
||||||
|
</t>
|
||||||
|
</p>
|
||||||
|
<p t-if="phones">
|
||||||
|
<b>Phone numbers</b><br/>
|
||||||
|
<t t-foreach="phones" t-as="phone">
|
||||||
|
&nbsp;&nbsp;&nbsp; <t t-esc="phone"/><br/>
|
||||||
|
</t>
|
||||||
|
</p>
|
||||||
|
</template>
|
||||||
|
</odoo>
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
<template id="assets_backend" name="Partner Autocomplete Assets" inherit_id="web.assets_backend">
|
||||||
|
<xpath expr="." position="inside">
|
||||||
|
<link rel="stylesheet" type="text/scss" href="/partner_autocomplete/static/src/scss/partner_autocomplete.scss"/>
|
||||||
|
<script type="text/javascript" src="/partner_autocomplete/static/src/js/partner_autocomplete_core.js" />
|
||||||
|
<script type="text/javascript" src="/partner_autocomplete/static/src/js/partner_autocomplete_fieldchar.js" />
|
||||||
|
<script type="text/javascript" src="/partner_autocomplete/static/src/js/partner_autocomplete_many2one.js" />
|
||||||
|
<script type="text/javascript" src="/partner_autocomplete/static/src/js/iap_credit_checker_widget.js" />
|
||||||
|
</xpath>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template id="qunit_suite" inherit_id="web.qunit_suite">
|
||||||
|
<xpath expr="//script[last()]" position="after">
|
||||||
|
<script type="text/javascript" src="/partner_autocomplete/static/tests/partner_autocomplete_tests.js"/>
|
||||||
|
</xpath>
|
||||||
|
</template>
|
||||||
|
</odoo>
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
<record id="view_company_form_inherit_partner_autocomplete" model="ir.ui.view">
|
||||||
|
<field name="name">res.company.form.inherit.web.partner.autocomplete</field>
|
||||||
|
<field name="model">res.company</field>
|
||||||
|
<field name="inherit_id" ref="base.view_company_form"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<xpath expr="//div[hasclass('oe_title')]/h1/field[@name='name']" position="attributes">
|
||||||
|
<attribute name="widget">field_partner_autocomplete</attribute>
|
||||||
|
</xpath>
|
||||||
|
<xpath expr="//field[@name='vat']" position="attributes">
|
||||||
|
<attribute name="widget">field_partner_autocomplete</attribute>
|
||||||
|
</xpath>
|
||||||
|
<xpath expr="//field[last()]" position="after">
|
||||||
|
<field name="partner_gid" invisible="True"/>
|
||||||
|
</xpath>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
</odoo>
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
<?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.partner.autcomplete</field>
|
||||||
|
<field name="model">res.config.settings</field>
|
||||||
|
<field name="inherit_id" ref="base_setup.res_config_settings_view_form"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<div id="partner_autocomplete_settings" position="inside">
|
||||||
|
<widget name="iap_credit_checker" service_name="partner_autocomplete"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<xpath expr="//div[hasclass('app_settings_block')]" position="before">
|
||||||
|
<field name="partner_autocomplete_insufficient_credit" invisible="1"/>
|
||||||
|
<div class="alert alert-info alert-dismissible fade show pt-2 pb-2" role="alert" attrs="{'invisible': [('partner_autocomplete_insufficient_credit', '=', False)]}">
|
||||||
|
<i class="fa fa-exclamation-triangle text-warning"></i> &nbsp; You don't have credits to auto-complete companies' data anymore.
|
||||||
|
<button name="redirect_to_buy_autocmplete_credit" type="object" class="btn-link">
|
||||||
|
<i class="fa fa-arrow-right"/>
|
||||||
|
Buy more credits
|
||||||
|
</button>
|
||||||
|
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
|
||||||
|
<span>&times;</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</xpath>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
</odoo>
|
||||||
|
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
<record id="view_res_partner_form_inherit_partner_autocomplete" model="ir.ui.view">
|
||||||
|
<field name="name">res.partner.form.inherit.partner.autocomplete</field>
|
||||||
|
<field name="model">res.partner</field>
|
||||||
|
<field name="inherit_id" ref="base.view_partner_form"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<xpath expr="//div[hasclass('oe_title')]/h1/field[@name='name']" position="attributes">
|
||||||
|
<attribute name="widget">field_partner_autocomplete</attribute>
|
||||||
|
</xpath>
|
||||||
|
<xpath expr="//field[@name='vat']" position="attributes">
|
||||||
|
<attribute name="widget">field_partner_autocomplete</attribute>
|
||||||
|
</xpath>
|
||||||
|
<xpath expr="//field[last()]" position="after">
|
||||||
|
<field name="partner_gid" invisible="True"/>
|
||||||
|
<field name="additional_info" invisible="True"/>
|
||||||
|
</xpath>
|
||||||
|
<xpath expr="//div[hasclass('oe_title')]/div[hasclass('o_row')]/field[@name='parent_id']" position="attributes">
|
||||||
|
<attribute name="help">You can find a customer, a contact, etc. by its Name, TIN, Email or Internal Reference.</attribute>
|
||||||
|
</xpath>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="view_res_partner_short_form_inherit_partner_autocomplete" model="ir.ui.view">
|
||||||
|
<field name="name">res.partner.short.form.inherit.partner.autocomplete</field>
|
||||||
|
<field name="model">res.partner</field>
|
||||||
|
<field name="inherit_id" ref="base.view_partner_short_form"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<xpath expr="//div[hasclass('oe_title')]/h1/field[@name='name']" position="attributes">
|
||||||
|
<attribute name="widget">field_partner_autocomplete</attribute>
|
||||||
|
</xpath>
|
||||||
|
<xpath expr="//field[@name='vat']" position="attributes">
|
||||||
|
<attribute name="widget">field_partner_autocomplete</attribute>
|
||||||
|
</xpath>
|
||||||
|
<xpath expr="//field[last()]" position="after">
|
||||||
|
<field name="partner_gid" invisible="True"/>
|
||||||
|
<field name="additional_info" invisible="True"/>
|
||||||
|
</xpath>
|
||||||
|
<xpath expr="//div[hasclass('oe_title')]/div[hasclass('o_row')]/field[@name='parent_id']" position="attributes">
|
||||||
|
<attribute name="help">You can find a customer, a contact, etc. by its Name, TIN, Email or Internal Reference.</attribute>
|
||||||
|
</xpath>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="view_partner_simple_form_inherit_partner_autocomplete" model="ir.ui.view">
|
||||||
|
<field name="name">res.partner.simplified.form.inherit.partner.autocomplete</field>
|
||||||
|
<field name="model">res.partner</field>
|
||||||
|
<field name="inherit_id" ref="base.view_partner_simple_form"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<xpath expr="//div[hasclass('oe_title')]/h1/field[@name='name']" position="attributes">
|
||||||
|
<attribute name="widget">field_partner_autocomplete</attribute>
|
||||||
|
</xpath>
|
||||||
|
<xpath expr="//field[last()]" position="after">
|
||||||
|
<field name="partner_gid" invisible="True"/>
|
||||||
|
<field name="additional_info" invisible="True"/>
|
||||||
|
</xpath>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
</odoo>
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from . import models
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
{
|
||||||
|
'name': "Partner Autocomplete extends Address autocomplete",
|
||||||
|
'summary': """
|
||||||
|
Correct address formating when both modules are installed""",
|
||||||
|
'author': "Odoo SA",
|
||||||
|
'category': 'Tools',
|
||||||
|
'version': '1.0',
|
||||||
|
'depends': ['partner_autocomplete', 'base_address_extended'],
|
||||||
|
'auto_install': True,
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||||
|
|
||||||
|
from . import res_partner
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||||
|
|
||||||
|
import re
|
||||||
|
from odoo import api, fields, models
|
||||||
|
|
||||||
|
|
||||||
|
class ResPartner(models.Model):
|
||||||
|
_name = 'res.partner'
|
||||||
|
_inherit = 'res.partner'
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _split_street_with_params(self, street_raw, street_format=False):
|
||||||
|
regex = '((\d+\w* ?(-|\/) ?\d*\w*)|(\d+\w*))'
|
||||||
|
|
||||||
|
street_name = street_raw
|
||||||
|
street_number = ''
|
||||||
|
street_number2 = ''
|
||||||
|
|
||||||
|
# Try to find number at beginning
|
||||||
|
start_regex = re.compile('^' + regex)
|
||||||
|
matches = re.search(start_regex, street_raw)
|
||||||
|
if matches and matches.group(0):
|
||||||
|
street_number = matches.group(0)
|
||||||
|
street_name = re.sub(start_regex, '', street_raw, 1)
|
||||||
|
else:
|
||||||
|
# Try to find number at end
|
||||||
|
end_regex = re.compile(regex + '$')
|
||||||
|
matches = re.search(end_regex, street_raw)
|
||||||
|
if matches and matches.group(0):
|
||||||
|
street_number = matches.group(0)
|
||||||
|
street_name = re.sub(end_regex, '', street_raw, 1)
|
||||||
|
|
||||||
|
if street_number:
|
||||||
|
street_number_split = street_number.split('/')
|
||||||
|
if len(street_number_split) > 1:
|
||||||
|
street_number2 = street_number_split.pop(-1)
|
||||||
|
street_number = '/'.join(street_number_split)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'street_name': street_name.strip(),
|
||||||
|
'street_number': street_number.strip(),
|
||||||
|
'street_number2': street_number2.strip(),
|
||||||
|
}
|
||||||
@@ -85,7 +85,7 @@ class PurchaseOrder(models.Model):
|
|||||||
date_order = fields.Datetime('Order Date', required=True, states=READONLY_STATES, index=True, copy=False, default=fields.Datetime.now,\
|
date_order = fields.Datetime('Order Date', required=True, states=READONLY_STATES, index=True, copy=False, default=fields.Datetime.now,\
|
||||||
help="Depicts the date where the Quotation should be validated and converted into a purchase order.")
|
help="Depicts the date where the Quotation should be validated and converted into a purchase order.")
|
||||||
date_approve = fields.Date('Approval Date', readonly=1, index=True, copy=False)
|
date_approve = fields.Date('Approval Date', readonly=1, index=True, copy=False)
|
||||||
partner_id = fields.Many2one('res.partner', string='Vendor', required=True, states=READONLY_STATES, change_default=True, track_visibility='always')
|
partner_id = fields.Many2one('res.partner', string='Vendor', required=True, states=READONLY_STATES, change_default=True, track_visibility='always', help="You can find a vendor by its Name, TIN, Email or Internal Reference.")
|
||||||
dest_address_id = fields.Many2one('res.partner', string='Drop Ship Address', states=READONLY_STATES,
|
dest_address_id = fields.Many2one('res.partner', string='Drop Ship Address', states=READONLY_STATES,
|
||||||
help="Put an address if you want to deliver directly from the vendor to the customer. "
|
help="Put an address if you want to deliver directly from the vendor to the customer. "
|
||||||
"Otherwise, keep empty to deliver to your own company.")
|
"Otherwise, keep empty to deliver to your own company.")
|
||||||
|
|||||||
@@ -165,7 +165,9 @@
|
|||||||
</div>
|
</div>
|
||||||
<group>
|
<group>
|
||||||
<group>
|
<group>
|
||||||
<field name="partner_id" context="{'search_default_supplier':1, 'default_supplier':1, 'default_customer':0}" domain="[('supplier','=',True)]"/>
|
<field name="partner_id" widget="res_partner_many2one" context="{'search_default_supplier':1, 'default_supplier':1, 'default_customer':0, 'show_vat': True}" domain="[('supplier','=',True)]"
|
||||||
|
placeholder="Name, TIN, Email, or Reference"
|
||||||
|
/>
|
||||||
<field name="partner_ref"/>
|
<field name="partner_ref"/>
|
||||||
<field name="currency_id" groups="base.group_multi_currency"/>
|
<field name="currency_id" groups="base.group_multi_currency"/>
|
||||||
</group>
|
</group>
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ class Repair(models.Model):
|
|||||||
partner_id = fields.Many2one(
|
partner_id = fields.Many2one(
|
||||||
'res.partner', 'Customer',
|
'res.partner', 'Customer',
|
||||||
index=True, states={'confirmed': [('readonly', True)]},
|
index=True, states={'confirmed': [('readonly', True)]},
|
||||||
help='Choose partner for whom the order will be invoiced and delivered.')
|
help='Choose partner for whom the order will be invoiced and delivered. You can find a partner by its Name, TIN, Email or Internal Reference.')
|
||||||
address_id = fields.Many2one(
|
address_id = fields.Many2one(
|
||||||
'res.partner', 'Delivery Address',
|
'res.partner', 'Delivery Address',
|
||||||
domain="[('parent_id','=',partner_id)]",
|
domain="[('parent_id','=',partner_id)]",
|
||||||
|
|||||||
@@ -59,7 +59,7 @@
|
|||||||
<field name="product_uom" groups="uom.group_uom"/>
|
<field name="product_uom" groups="uom.group_uom"/>
|
||||||
</div>
|
</div>
|
||||||
<field name="lot_id" domain="[('product_id', '=', product_id)]" context="{'default_product_id': product_id}" groups="stock.group_production_lot" attrs="{'required': [('tracking', '!=', 'none')], 'readonly': [('state', '=', 'done')]}"/>
|
<field name="lot_id" domain="[('product_id', '=', product_id)]" context="{'default_product_id': product_id}" groups="stock.group_production_lot" attrs="{'required': [('tracking', '!=', 'none')], 'readonly': [('state', '=', 'done')]}"/>
|
||||||
<field name="partner_id" attrs="{'required':[('invoice_method','!=','none')]}"/>
|
<field name="partner_id" widget="res_partner_many2one" attrs="{'required':[('invoice_method','!=','none')]}" context="{'show_vat': True}"/>
|
||||||
<field name="address_id" groups="sale.group_delivery_invoice_address"/>
|
<field name="address_id" groups="sale.group_delivery_invoice_address"/>
|
||||||
</group>
|
</group>
|
||||||
<group>
|
<group>
|
||||||
|
|||||||
@@ -154,7 +154,7 @@ class SaleOrder(models.Model):
|
|||||||
create_date = fields.Datetime(string='Creation Date', readonly=True, index=True, help="Date on which sales order is created.")
|
create_date = fields.Datetime(string='Creation Date', readonly=True, index=True, help="Date on which sales order is created.")
|
||||||
confirmation_date = fields.Datetime(string='Confirmation Date', readonly=True, index=True, help="Date on which the sales order is confirmed.", oldname="date_confirm", copy=False)
|
confirmation_date = fields.Datetime(string='Confirmation Date', readonly=True, index=True, help="Date on which the sales order is confirmed.", oldname="date_confirm", copy=False)
|
||||||
user_id = fields.Many2one('res.users', string='Salesperson', index=True, track_visibility='onchange', track_sequence=2, default=lambda self: self.env.user)
|
user_id = fields.Many2one('res.users', string='Salesperson', index=True, track_visibility='onchange', track_sequence=2, default=lambda self: self.env.user)
|
||||||
partner_id = fields.Many2one('res.partner', string='Customer', readonly=True, states={'draft': [('readonly', False)], 'sent': [('readonly', False)]}, required=True, change_default=True, index=True, track_visibility='always', track_sequence=1)
|
partner_id = fields.Many2one('res.partner', string='Customer', readonly=True, states={'draft': [('readonly', False)], 'sent': [('readonly', False)]}, required=True, change_default=True, index=True, track_visibility='always', track_sequence=1, help="You can find a customer by its Name, TIN, Email or Internal Reference.")
|
||||||
partner_invoice_id = fields.Many2one('res.partner', string='Invoice Address', readonly=True, required=True, states={'draft': [('readonly', False)], 'sent': [('readonly', False)], 'sale': [('readonly', False)]}, help="Invoice address for current sales order.")
|
partner_invoice_id = fields.Many2one('res.partner', string='Invoice Address', readonly=True, required=True, states={'draft': [('readonly', False)], 'sent': [('readonly', False)], 'sale': [('readonly', False)]}, help="Invoice address for current sales order.")
|
||||||
partner_shipping_id = fields.Many2one('res.partner', string='Delivery Address', readonly=True, required=True, states={'draft': [('readonly', False)], 'sent': [('readonly', False)], 'sale': [('readonly', False)]}, help="Delivery address for current sales order.")
|
partner_shipping_id = fields.Many2one('res.partner', string='Delivery Address', readonly=True, required=True, states={'draft': [('readonly', False)], 'sent': [('readonly', False)], 'sale': [('readonly', False)]}, help="Delivery address for current sales order.")
|
||||||
|
|
||||||
|
|||||||
@@ -264,7 +264,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<group>
|
<group>
|
||||||
<group>
|
<group>
|
||||||
<field name="partner_id" domain="[('customer','=',True)]" context="{'search_default_customer':1, 'show_address': 1}" options='{"always_reload": True}'/>
|
<field name="partner_id" widget="res_partner_many2one" domain="[('customer','=',True)]" context="{'search_default_customer':1, 'show_address': 1, 'show_vat': True}" options='{"always_reload": True}'/>
|
||||||
<field name="partner_invoice_id" groups="sale.group_delivery_invoice_address" context="{'default_type':'invoice'}" options='{"always_reload": True}'/>
|
<field name="partner_invoice_id" groups="sale.group_delivery_invoice_address" context="{'default_type':'invoice'}" options='{"always_reload": True}'/>
|
||||||
<field name="partner_shipping_id" groups="sale.group_delivery_invoice_address" context="{'default_type':'delivery'}" options='{"always_reload": True}'/>
|
<field name="partner_shipping_id" groups="sale.group_delivery_invoice_address" context="{'default_type':'delivery'}" options='{"always_reload": True}'/>
|
||||||
</group>
|
</group>
|
||||||
|
|||||||
@@ -138,6 +138,11 @@ var FieldMany2One = AbstractField.extend({
|
|||||||
this.isDirty = false;
|
this.isDirty = false;
|
||||||
this.lastChangeEvent = undefined;
|
this.lastChangeEvent = undefined;
|
||||||
|
|
||||||
|
// List of autocomplete sources
|
||||||
|
this._autocompleteSources = [];
|
||||||
|
// Add default search method for M20 (name_search)
|
||||||
|
this._addAutocompleteSource(this._search, {placeholder: _t('Loading...'), order: 1});
|
||||||
|
|
||||||
// use a DropPrevious to properly handle related record quick creations,
|
// use a DropPrevious to properly handle related record quick creations,
|
||||||
// and store a createDef to be able to notify the environment that there
|
// and store a createDef to be able to notify the environment that there
|
||||||
// is pending quick create operation
|
// is pending quick create operation
|
||||||
@@ -212,6 +217,24 @@ var FieldMany2One = AbstractField.extend({
|
|||||||
// Private
|
// Private
|
||||||
//--------------------------------------------------------------------------
|
//--------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a source to the autocomplete results
|
||||||
|
*
|
||||||
|
* @param {function} method : A function that returns a list of results. If async source, the function should return a promise
|
||||||
|
* @param {Object} params : Parameters containing placeholder/validation/order
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_addAutocompleteSource: function (method, params) {
|
||||||
|
this._autocompleteSources.push({
|
||||||
|
method: method,
|
||||||
|
placeholder: (params.placeholder ? _t(params.placeholder) : _t('Loading...')) + '<i class="fa fa-spinner fa-spin pull-right"></i>' ,
|
||||||
|
validation: params.validation,
|
||||||
|
loading: false,
|
||||||
|
order: params.order || 999
|
||||||
|
});
|
||||||
|
|
||||||
|
this._autocompleteSources = _.sortBy(this._autocompleteSources, 'order');
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
@@ -219,8 +242,22 @@ var FieldMany2One = AbstractField.extend({
|
|||||||
var self = this;
|
var self = this;
|
||||||
this.$input.autocomplete({
|
this.$input.autocomplete({
|
||||||
source: function (req, resp) {
|
source: function (req, resp) {
|
||||||
self._search(req.term).then(function (result) {
|
_.each(self._autocompleteSources, function (source) {
|
||||||
resp(result);
|
// Resets the results for this source
|
||||||
|
source.results = [];
|
||||||
|
|
||||||
|
// Check if this source should be used for the searched term
|
||||||
|
if (!source.validation || source.validation.call(self, req.term)) {
|
||||||
|
source.loading = true;
|
||||||
|
|
||||||
|
// Wrap the returned value of the source.method with $.when.
|
||||||
|
// So event if the returned value is not async, it will work
|
||||||
|
$.when(source.method.call(self, req.term)).then(function (results) {
|
||||||
|
source.results = results;
|
||||||
|
source.loading = false;
|
||||||
|
resp(self._concatenateAutocompleteResults());
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
select: function (event, ui) {
|
select: function (event, ui) {
|
||||||
@@ -256,6 +293,25 @@ var FieldMany2One = AbstractField.extend({
|
|||||||
this.$input.autocomplete("option", "position", { my : "left top", at: "left bottom" });
|
this.$input.autocomplete("option", "position", { my : "left top", at: "left bottom" });
|
||||||
this.autocomplete_bound = true;
|
this.autocomplete_bound = true;
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* Concatenate async results for autocomplete.
|
||||||
|
*
|
||||||
|
* @returns {Array}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_concatenateAutocompleteResults: function () {
|
||||||
|
var results = [];
|
||||||
|
_.each(this._autocompleteSources, function (source) {
|
||||||
|
if (source.results && source.results.length) {
|
||||||
|
results = results.concat(source.results);
|
||||||
|
} else if (source.loading) {
|
||||||
|
results.push({
|
||||||
|
label: source.placeholder
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return results;
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
* @param {string} [name]
|
* @param {string} [name]
|
||||||
@@ -389,6 +445,8 @@ var FieldMany2One = AbstractField.extend({
|
|||||||
this.m2o_value = this._formatValue(this.value);
|
this.m2o_value = this._formatValue(this.value);
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
|
* Executes a name_search and process its result.
|
||||||
|
*
|
||||||
* @private
|
* @private
|
||||||
* @param {string} search_val
|
* @param {string} search_val
|
||||||
* @returns {Deferred}
|
* @returns {Deferred}
|
||||||
|
|||||||
@@ -146,6 +146,7 @@ var UpgradeBoolean = FieldBoolean.extend(AbstractFieldUpgrade, {
|
|||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
_insertEnterpriseLabel: function ($enterpriseLabel) {
|
_insertEnterpriseLabel: function ($enterpriseLabel) {
|
||||||
|
if(this.name==='module_partner_autocomplete') debugger;
|
||||||
var $el = this.$label || this.$el;
|
var $el = this.$label || this.$el;
|
||||||
$el.append(' ').append($enterpriseLabel);
|
$el.append(' ').append($enterpriseLabel);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import datetime
|
|||||||
import hashlib
|
import hashlib
|
||||||
import pytz
|
import pytz
|
||||||
import threading
|
import threading
|
||||||
|
import re
|
||||||
|
|
||||||
from email.utils import formataddr
|
from email.utils import formataddr
|
||||||
|
|
||||||
@@ -239,6 +240,12 @@ class Partner(models.Model):
|
|||||||
('check_name', "CHECK( (type='contact' AND name IS NOT NULL) or (type!='contact') )", 'Contacts require a name.'),
|
('check_name', "CHECK( (type='contact' AND name IS NOT NULL) or (type!='contact') )", 'Contacts require a name.'),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@api.model_cr
|
||||||
|
def init(self):
|
||||||
|
self._cr.execute("""SELECT indexname FROM pg_indexes WHERE indexname = 'res_partner_vat_index'""")
|
||||||
|
if not self._cr.fetchone():
|
||||||
|
self._cr.execute("""CREATE INDEX res_partner_vat_index ON res_partner (regexp_replace(upper(vat), '[^A-Z0-9]+', '', 'g'))""")
|
||||||
|
|
||||||
@api.depends('is_company', 'name', 'parent_id.name', 'type', 'company_name')
|
@api.depends('is_company', 'name', 'parent_id.name', 'type', 'company_name')
|
||||||
def _compute_display_name(self):
|
def _compute_display_name(self):
|
||||||
diff = dict(show_address=None, show_address_only=None, show_email=None)
|
diff = dict(show_address=None, show_address_only=None, show_email=None)
|
||||||
@@ -610,6 +617,8 @@ class Partner(models.Model):
|
|||||||
name = "%s <%s>" % (name, partner.email)
|
name = "%s <%s>" % (name, partner.email)
|
||||||
if self._context.get('html_format'):
|
if self._context.get('html_format'):
|
||||||
name = name.replace('\n', '<br/>')
|
name = name.replace('\n', '<br/>')
|
||||||
|
if self._context.get('show_vat') and partner.vat:
|
||||||
|
name = "%s - %s" % (name, partner.vat)
|
||||||
return name
|
return name
|
||||||
|
|
||||||
@api.multi
|
@api.multi
|
||||||
@@ -697,7 +706,9 @@ class Partner(models.Model):
|
|||||||
percent=unaccent('%s'),
|
percent=unaccent('%s'),
|
||||||
vat=unaccent('vat'),)
|
vat=unaccent('vat'),)
|
||||||
|
|
||||||
where_clause_params += [search_name]*5
|
where_clause_params += [search_name]*3 # for email / display_name, reference
|
||||||
|
where_clause_params += [re.sub('[^a-zA-Z0-9]+', '', search_name)] # for vat
|
||||||
|
where_clause_params += [search_name] # for order by
|
||||||
if limit:
|
if limit:
|
||||||
query += ' limit %s'
|
query += ' limit %s'
|
||||||
where_clause_params.append(limit)
|
where_clause_params.append(limit)
|
||||||
|
|||||||
@@ -88,6 +88,7 @@
|
|||||||
<field name="bank_name"/>
|
<field name="bank_name"/>
|
||||||
<field name="company_id" groups="base.group_multi_company"/>
|
<field name="company_id" groups="base.group_multi_company"/>
|
||||||
<field name="partner_id"/>
|
<field name="partner_id"/>
|
||||||
|
<field name="acc_holder_name" invisible="1"/>
|
||||||
</tree>
|
</tree>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|||||||
@@ -70,8 +70,9 @@
|
|||||||
<field name="name" default_focus="1" placeholder="Name" attrs="{'required' : [('type', '=', 'contact')]}"/>
|
<field name="name" default_focus="1" placeholder="Name" attrs="{'required' : [('type', '=', 'contact')]}"/>
|
||||||
</h1>
|
</h1>
|
||||||
<field name="parent_id"
|
<field name="parent_id"
|
||||||
|
widget="res_partner_many2one"
|
||||||
placeholder="Company"
|
placeholder="Company"
|
||||||
domain="[('is_company', '=', True)]" context="{'default_is_company': True}"
|
domain="[('is_company', '=', True)]" context="{'default_is_company': True, 'show_vat': True}"
|
||||||
attrs="{'invisible': [('is_company','=', True)]}"/>
|
attrs="{'invisible': [('is_company','=', True)]}"/>
|
||||||
</div>
|
</div>
|
||||||
<group>
|
<group>
|
||||||
@@ -142,8 +143,9 @@
|
|||||||
</h1>
|
</h1>
|
||||||
<div class="o_row">
|
<div class="o_row">
|
||||||
<field name="parent_id"
|
<field name="parent_id"
|
||||||
|
widget="res_partner_many2one"
|
||||||
placeholder="Company"
|
placeholder="Company"
|
||||||
domain="[('is_company', '=', True)]" context="{'default_is_company': True, 'default_supplier': supplier, 'default_customer': customer}"
|
domain="[('is_company', '=', True)]" context="{'default_is_company': True, 'default_supplier': supplier, 'default_customer': customer, 'show_vat': True}"
|
||||||
attrs="{'invisible': ['|', '&', ('is_company','=', True),('parent_id', '=', False),('company_name', '!=', False),('company_name', '!=', '')]}"/>
|
attrs="{'invisible': ['|', '&', ('is_company','=', True),('parent_id', '=', False),('company_name', '!=', False),('company_name', '!=', '')]}"/>
|
||||||
<field name="company_name" attrs="{'invisible': ['|', '|', ('company_name', '=', False), ('company_name', '=', ''), ('is_company', '=', True)]}"/>
|
<field name="company_name" attrs="{'invisible': ['|', '|', ('company_name', '=', False), ('company_name', '=', ''), ('is_company', '=', True)]}"/>
|
||||||
<button name="create_company" type="object" class="oe_edit_only btn-link"
|
<button name="create_company" type="object" class="oe_edit_only btn-link"
|
||||||
@@ -215,8 +217,9 @@
|
|||||||
</h1>
|
</h1>
|
||||||
<div class="o_row">
|
<div class="o_row">
|
||||||
<field name="parent_id"
|
<field name="parent_id"
|
||||||
|
widget="res_partner_many2one"
|
||||||
placeholder="Company"
|
placeholder="Company"
|
||||||
domain="[('is_company', '=', True)]" context="{'default_is_company': True, 'default_supplier': supplier, 'default_customer': customer}"
|
domain="[('is_company', '=', True)]" context="{'default_is_company': True, 'default_supplier': supplier, 'default_customer': customer, 'show_vat': True}"
|
||||||
attrs="{'invisible': ['|', '&', ('is_company','=', True),('parent_id', '=', False),('company_name', '!=', False),('company_name', '!=', '')]}"/>
|
attrs="{'invisible': ['|', '&', ('is_company','=', True),('parent_id', '=', False),('company_name', '!=', False),('company_name', '!=', '')]}"/>
|
||||||
<field name="company_name" attrs="{'invisible': ['|', '|', ('company_name', '=', False), ('company_name', '=', ''), ('is_company', '=', True)]}"/>
|
<field name="company_name" attrs="{'invisible': ['|', '|', ('company_name', '=', False), ('company_name', '=', ''), ('is_company', '=', True)]}"/>
|
||||||
<button name="create_company" type="object" class="oe_edit_only btn-link"
|
<button name="create_company" type="object" class="oe_edit_only btn-link"
|
||||||
@@ -429,6 +432,7 @@
|
|||||||
<field name="bank_ids">
|
<field name="bank_ids">
|
||||||
<tree editable="bottom">
|
<tree editable="bottom">
|
||||||
<field name="acc_number"/>
|
<field name="acc_number"/>
|
||||||
|
<field name="acc_holder_name" invisible="1"/>
|
||||||
</tree>
|
</tree>
|
||||||
</field>
|
</field>
|
||||||
</group>
|
</group>
|
||||||
@@ -585,7 +589,7 @@
|
|||||||
<field name="view_type">form</field>
|
<field name="view_type">form</field>
|
||||||
<field name="view_mode">kanban,tree,form</field>
|
<field name="view_mode">kanban,tree,form</field>
|
||||||
<field name="domain">[]</field>
|
<field name="domain">[]</field>
|
||||||
<field name="context">{'default_customer':1, 'search_default_customer':1, 'default_company_type': 'company','default_is_company': 1}</field>
|
<field name="context">{'default_customer':1, 'search_default_customer':1, 'default_is_company': True}</field>
|
||||||
<field name="filter" eval="True"/>
|
<field name="filter" eval="True"/>
|
||||||
<field name="help" type="html">
|
<field name="help" type="html">
|
||||||
<p class="o_view_nocontent_smiling_face">
|
<p class="o_view_nocontent_smiling_face">
|
||||||
@@ -621,7 +625,7 @@
|
|||||||
<field name="view_type">form</field>
|
<field name="view_type">form</field>
|
||||||
<field name="domain">[]</field>
|
<field name="domain">[]</field>
|
||||||
<field name="view_mode">kanban,tree,form</field>
|
<field name="view_mode">kanban,tree,form</field>
|
||||||
<field name="context">{'search_default_supplier': 1,'default_customer': 0,'default_supplier': 1, 'default_company_type': 'company'}</field>
|
<field name="context">{'search_default_supplier': 1,'default_customer': 0,'default_supplier': 1, 'default_is_company': True}</field>
|
||||||
<field name="filter" eval="True"/>
|
<field name="filter" eval="True"/>
|
||||||
<field name="help" type="html">
|
<field name="help" type="html">
|
||||||
<p class="o_view_nocontent_smiling_face">
|
<p class="o_view_nocontent_smiling_face">
|
||||||
|
|||||||
Reference in New Issue
Block a user