diff options
| author | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
|---|---|---|
| committer | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
| commit | 3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch) | |
| tree | a44932296ef4a9b71d5f010906253d8c53727726 /addons/partner_autocomplete/static | |
| parent | 0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff) | |
initial commit 2
Diffstat (limited to 'addons/partner_autocomplete/static')
| -rw-r--r-- | addons/partner_autocomplete/static/img/crunchbase.ico | bin | 0 -> 8348 bytes | |||
| -rw-r--r-- | addons/partner_autocomplete/static/img/facebook.ico | bin | 0 -> 5430 bytes | |||
| -rw-r--r-- | addons/partner_autocomplete/static/img/linkedin.ico | bin | 0 -> 4286 bytes | |||
| -rw-r--r-- | addons/partner_autocomplete/static/img/twitter.ico | bin | 0 -> 4286 bytes | |||
| -rw-r--r-- | addons/partner_autocomplete/static/lib/jsvat.js | 1338 | ||||
| -rw-r--r-- | addons/partner_autocomplete/static/src/js/partner_autocomplete_core.js | 366 | ||||
| -rw-r--r-- | addons/partner_autocomplete/static/src/js/partner_autocomplete_fieldchar.js | 337 | ||||
| -rw-r--r-- | addons/partner_autocomplete/static/src/js/partner_autocomplete_many2one.js | 155 | ||||
| -rw-r--r-- | addons/partner_autocomplete/static/src/scss/partner_autocomplete.scss | 66 | ||||
| -rw-r--r-- | addons/partner_autocomplete/static/src/xml/partner_autocomplete.xml | 27 | ||||
| -rw-r--r-- | addons/partner_autocomplete/static/tests/partner_autocomplete_tests.js | 421 |
11 files changed, 2710 insertions, 0 deletions
diff --git a/addons/partner_autocomplete/static/img/crunchbase.ico b/addons/partner_autocomplete/static/img/crunchbase.ico Binary files differnew file mode 100644 index 00000000..9cf19b93 --- /dev/null +++ b/addons/partner_autocomplete/static/img/crunchbase.ico diff --git a/addons/partner_autocomplete/static/img/facebook.ico b/addons/partner_autocomplete/static/img/facebook.ico Binary files differnew file mode 100644 index 00000000..8ce319b8 --- /dev/null +++ b/addons/partner_autocomplete/static/img/facebook.ico diff --git a/addons/partner_autocomplete/static/img/linkedin.ico b/addons/partner_autocomplete/static/img/linkedin.ico Binary files differnew file mode 100644 index 00000000..3988a3d7 --- /dev/null +++ b/addons/partner_autocomplete/static/img/linkedin.ico diff --git a/addons/partner_autocomplete/static/img/twitter.ico b/addons/partner_autocomplete/static/img/twitter.ico Binary files differnew file mode 100644 index 00000000..eeae0a0c --- /dev/null +++ b/addons/partner_autocomplete/static/img/twitter.ico diff --git a/addons/partner_autocomplete/static/lib/jsvat.js b/addons/partner_autocomplete/static/lib/jsvat.js new file mode 100644 index 00000000..07dae2d3 --- /dev/null +++ b/addons/partner_autocomplete/static/lib/jsvat.js @@ -0,0 +1,1338 @@ +/*================================================================== + +Application: Utility Function +Author: John Gardner +Website: http://www.braemoor.co.uk/software/vat.shtml + +Version: V1.0 +Date: 30th July 2005 +Description: Used to check the validity of an EU country VAT number + +Version: V1.1 +Date: 3rd August 2005 +Description: Lithuanian legal entities & Maltese check digit checks added. + +Version: V1.2 +Date: 20th October 2005 +Description: Italian checks refined (thanks Matteo Mike Peluso). + +Version: V1.3 +Date: 16th November 2005 +Description: Error in GB numbers ending in 00 fixed (thanks Guy Dawson). + +Version: V1.4 +Date: 28th September 2006 +Description: EU-type numbers added. + +Version: V1.5 +Date: 1st January 2007 +Description: Romanian and Bulgarian numbers added. + +Version: V1.6 +Date: 7th January 2007 +Description: Error with Slovenian numbers (thanks to Ales Hotko). + +Version: V1.7 +Date: 10th February 2007 +Description: Romanian check digits added. + Thanks to Dragu Costel for the test suite. + +Version: V1.8 +Date: 3rd August 2007 +Description: IE code modified to allow + and * in old format numbers. + Thanks to Antonin Moy of Sphere Solutions for pointing out the error. + +Version: V1.9 +Date: 6th August 2007 +Description: BE code modified to make a specific check that the leading character of 10 digit + numbers is 0 (belts and braces). + +Version: V1.10 +Date: 10th August 2007 +Description: Cypriot check digit support added. + Check digit validation support for non-standard UK numbers + +Version: V1.11 +Date: 25th September 2007 +Description: Spain check digit support for personal numbers. + Author: David Perez Carmona + +Version: V1.12 +Date: 23rd November 2009 +Description: GB code modified to take into account new style check digits. + Thanks to Guy Dawson of Crossflight Ltd for pointing out the necessity. + +Version: V1.13 +Date: 7th July 2012 +Description: EL, GB, SE and BE formats updated - thanks to Joost Van Biervliet of VAT Applications + +Version: V.14 +Date: 8th April 2013 +Description: BE Pattern match refined + BG Add check digit checks for all four types of VAT number + CY Pattern match improved + CZ Personal pattern match checking improved + CZ Personal check digits incorporated + EE improved pattern match + ES Physical person number checking refined + GB Check digit support provided for 12 digit VAT codes and range checks included + IT Bug removed to allow 999 and 888 issuing office codes + LT temporarily registered taxpayers check digit support added + LV Natural persons checks added + RO improved pattern match + SK improved pattern match and added check digit support + + Thanks to Theo Vroom for his help in this latest release. + +Version: V1.15 +Date: 15th April 2013 + Swedish algorithm re-implemented. + +Version: V1.16 +Date: 25th July 2013 + Support for Croatian numbers added + +Version V1.17 + 10th September 2013 + Support for Norwegian MVA numbers added (yes, I know that Norway is not in the EU!) + +Version V1.18 + 29th October 2013 + Partial support for new style Irish numbers. + See http://www.revenue.ie/en/practitioner/ebrief/2013/no-032013.html + Thanks to Simon Leigh for drawing the author's attention to this. + +Version V1.19 + 31st October 2013 + Support for Serbian PBI numbers added (yes, I know that Serbia is not in the EU!) + +Version V1.20 + 1st November 2013 + Support for Swiss MWST numbers added (yes, I know that Switzerland is not in the EU!) + +Version V1.21 + 16th December 2014 + Non-critical code tidies to French and Danish regular expressions. + Thanks to Bill Seddon of Lyquidity Solutions + +Version V1.22 + 14th January 2014 + Non-critical code tidy to regular expression for new format Irish numbers. + Thanks to Olivier Reubens of UNIT4 C-Logic N.V. + +Version V1.23 + 10th April 2014 + Support for Russian INN numbers added (yes, I know that Russia is not in the EU!). + Thanks to Marco Cesaratto of Arki Tech, Italy + +Version V1.24 + 4th June 2014 + Check digit validation supported for Irish Type 3 numbers + Thanks to Olivier Reubens of UNIT4 C-Logic N.V. + +Version V1.25 + 29th July 2014 + Code improvements + Thanks to Sébastien Boelpaep and Nate Kerkhofs + +Version V1.26 + 4th May 2015 + Code improvements to regular expressions + Thanks to Robert Gust-Bardon of webcraft.ch + +Version V1.27 + 3rd December 2015 + Extend Swiss optional suffix to allow TVA and ITA + Thanks to Oskars Petermanis + +Version V1.28 + 30th August 2016 + Correct Swiss optional suffix to allow TVA and IVA + Thanks to Jan Verhaegen + +Version V1.29 + 29th July 2017 + Correct Czeck Republic checking of Individual type 2 - Special Cases + Thanks to Andreas Wuermser of Auer Packaging UK + +Parameters: toCheck - VAT number be checked. + +This function checks the value of the parameter for a valid European VAT number. + +If the number is found to be invalid format, the function returns a value of false. Otherwise it +returns the VAT number re-formatted. + +Example call: + + if (checkVATNumber (myVATNumber)) + alert ("VAT number has a valid format") + else + alert ("VAT number has invalid format"); + +---------------------------------------------------------------------------------------------------*/ + +var checkVATNumber = (function (){ + // Array holds the regular expressions for the valid VAT number + var vatexp = new Array(); + + // To change the default country (e.g. from the UK to Germany - DE): + // 1. Change the country code in the defCCode variable below to "DE". + // 2. Remove the question mark from the regular expressions associated with the UK VAT number: + // i.e. "(GB)?" -> "(GB)" + // 3. Add a question mark into the regular expression associated with Germany's number + // following the country code: i.e. "(DE)" -> "(DE)?" + + var defCCode = "GB"; + + // Note - VAT codes without the "**" in the comment do not have check digit checking. + + vatexp.push(/^(AT)U(\d{8})$/); //** Austria + vatexp.push(/^(BE)(0?\d{9})$/); //** Belgium + vatexp.push(/^(BG)(\d{9,10})$/); //** Bulgaria + vatexp.push(/^(CHE)(\d{9})(MWST|TVA|IVA)?$/); //** Switzerland + vatexp.push(/^(CY)([0-59]\d{7}[A-Z])$/); //** Cyprus + vatexp.push(/^(CZ)(\d{8,10})(\d{3})?$/); //** Czech Republic + vatexp.push(/^(DE)([1-9]\d{8})$/); //** Germany + vatexp.push(/^(DK)(\d{8})$/); //** Denmark + vatexp.push(/^(EE)(10\d{7})$/); //** Estonia + vatexp.push(/^(EL)(\d{9})$/); //** Greece + vatexp.push(/^(ES)([A-Z]\d{8})$/); //** Spain (National juridical entities) + vatexp.push(/^(ES)([A-HN-SW]\d{7}[A-J])$/); //** Spain (Other juridical entities) + vatexp.push(/^(ES)([0-9YZ]\d{7}[A-Z])$/); //** Spain (Personal entities type 1) + vatexp.push(/^(ES)([KLMX]\d{7}[A-Z])$/); //** Spain (Personal entities type 2) + vatexp.push(/^(EU)(\d{9})$/); //** EU-type + vatexp.push(/^(FI)(\d{8})$/); //** Finland + vatexp.push(/^(FR)(\d{11})$/); //** France (1) + vatexp.push(/^(FR)([A-HJ-NP-Z]\d{10})$/); // France (2) + vatexp.push(/^(FR)(\d[A-HJ-NP-Z]\d{9})$/); // France (3) + vatexp.push(/^(FR)([A-HJ-NP-Z]{2}\d{9})$/); // France (4) + vatexp.push(/^(GB)?(\d{9})$/); //** UK (Standard) + vatexp.push(/^(GB)?(\d{12})$/); //** UK (Branches) + vatexp.push(/^(GB)?(GD\d{3})$/); //** UK (Government) + vatexp.push(/^(GB)?(HA\d{3})$/); //** UK (Health authority) + vatexp.push(/^(HR)(\d{11})$/); //** Croatia + vatexp.push(/^(HU)(\d{8})$/); //** Hungary + vatexp.push(/^(IE)(\d{7}[A-W])$/); //** Ireland (1) + vatexp.push(/^(IE)([7-9][A-Z\*\+)]\d{5}[A-W])$/); //** Ireland (2) + vatexp.push(/^(IE)(\d{7}[A-W][AH])$/); //** Ireland (3) + vatexp.push(/^(IT)(\d{11})$/); //** Italy + vatexp.push(/^(LV)(\d{11})$/); //** Latvia + vatexp.push(/^(LT)(\d{9}|\d{12})$/); //** Lithunia + vatexp.push(/^(LU)(\d{8})$/); //** Luxembourg + vatexp.push(/^(MT)([1-9]\d{7})$/); //** Malta + vatexp.push(/^(NL)(\d{9})B\d{2}$/); //** Netherlands + vatexp.push(/^(NO)(\d{9})$/); //** Norway (not EU) + vatexp.push(/^(PL)(\d{10})$/); //** Poland + vatexp.push(/^(PT)(\d{9})$/); //** Portugal + vatexp.push(/^(RO)([1-9]\d{1,9})$/); //** Romania + vatexp.push(/^(RU)(\d{10}|\d{12})$/); //** Russia + vatexp.push(/^(RS)(\d{9})$/); //** Serbia + vatexp.push(/^(SI)([1-9]\d{7})$/); //** Slovenia + vatexp.push(/^(SK)([1-9]\d[2346-9]\d{7})$/); //** Slovakia Republic + vatexp.push(/^(SE)(\d{10}01)$/); //** Sweden + + function checkVATNumber(toCheck) { + // Load up the string to check + var VATNumber = toCheck.toUpperCase(); + + // Remove spaces etc. from the VAT number to help validation + VATNumber = VATNumber.replace(/(\s|-|\.)+/g, ''); + + // Assume we're not going to find a valid VAT number + var valid = false; + + // Check the string against the regular expressions for all types of VAT numbers + for (var i = 0; i < vatexp.length; i++) { + + // Have we recognised the VAT number? + if (vatexp[i].test(VATNumber)) { + + // Yes - we have + var cCode = RegExp.$1; // Isolate country code + var cNumber = RegExp.$2; // Isolate the number + if (cCode.length == 0) cCode = defCCode; // Set up default country code + + // Call the appropriate country VAT validation routine depending on the country code + if (eval(cCode + "VATCheckDigit ('" + cNumber + "')")) valid = VATNumber; + + // Having processed the number, we break from the loop + break; + } + } + + // Return with either an error or the reformatted VAT number + return valid; + } + + function ATVATCheckDigit(vatnumber) { + + // Checks the check digits of an Austrian VAT number. + + var total = 0; + var multipliers = [1, 2, 1, 2, 1, 2, 1]; + var temp = 0; + + // Extract the next digit and multiply by the appropriate multiplier. + for (var i = 0; i < 7; i++) { + temp = Number(vatnumber.charAt(i)) * multipliers[i]; + if (temp > 9) + total += Math.floor(temp / 10) + temp % 10; + else + total += temp; + } + + // Establish check digit. + total = 10 - (total + 4) % 10; + if (total == 10) total = 0; + + // Compare it with the last character of the VAT number. If it's the same, then it's valid. + if (total == vatnumber.slice(7, 8)) + return true; + else + return false; + } + + function BEVATCheckDigit(vatnumber) { + + // Checks the check digits of a Belgium VAT number. + + // Nine digit numbers have a 0 inserted at the front. + if (vatnumber.length == 9) vatnumber = "0" + vatnumber; + + if (vatnumber.slice(1, 2) == 0) return false; + + // Modulus 97 check on last nine digits + if (97 - vatnumber.slice(0, 8) % 97 == vatnumber.slice(8, 10)) + return true; + else + return false; + } + + function BGVATCheckDigit(vatnumber) { + var temp, total, multipliers, i; + + // Checks the check digits of a Bulgarian VAT number. + + if (vatnumber.length == 9) { + // Check the check digit of 9 digit Bulgarian VAT numbers. + total = 0; + + // First try to calculate the check digit using the first multipliers + temp = 0; + for (i = 0; i < 8; i++) temp += Number(vatnumber.charAt(i)) * (i + 1); + + // See if we have a check digit yet + total = temp % 11; + if (total != 10) { + if (total == vatnumber.slice(8)) + return true; + else + return false; + } + + // We got a modulus of 10 before so we have to keep going. Calculate the new check digit using + // the different multipliers + temp = 0; + for (i = 0; i < 8; i++) temp += Number(vatnumber.charAt(i)) * (i + 3); + + // See if we have a check digit yet. If we still have a modulus of 10, set it to 0. + total = temp % 11; + if (total == 10) total = 0; + if (total == vatnumber.slice(8)) + return true; + else + return false; + } + + // 10 digit VAT code - see if it relates to a standard physical person + if ((/^\d\d[0-5]\d[0-3]\d\d{4}$/).test(vatnumber)) { + + // Check month + var month = Number(vatnumber.slice(2, 4)); + if ((month > 0 && month < 13) || (month > 20 && month < 33) || (month > 40 && month < 53)) { + + // Extract the next digit and multiply by the counter. + multipliers = [2, 4, 8, 5, 10, 9, 7, 3, 6]; + total = 0; + for (i = 0; i < 9; i++) total += Number(vatnumber.charAt(i)) * multipliers[i]; + + // Establish check digit. + total = total % 11; + if (total == 10) total = 0; + + // Check to see if the check digit given is correct, If not, try next type of person + if (total == vatnumber.substr(9, 1)) return true; + } + } + + // It doesn't relate to a standard physical person - see if it relates to a foreigner. + + // Extract the next digit and multiply by the counter. + multipliers = [21, 19, 17, 13, 11, 9, 7, 3, 1]; + total = 0; + for (i = 0; i < 9; i++) total += Number(vatnumber.charAt(i)) * multipliers[i]; + + // Check to see if the check digit given is correct, If not, try next type of person + if (total % 10 == vatnumber.substr(9, 1)) return true; + + // Finally, if not yet identified, see if it conforms to a miscellaneous VAT number + + // Extract the next digit and multiply by the counter. + multipliers = [4, 3, 2, 7, 6, 5, 4, 3, 2]; + total = 0; + for (i = 0; i < 9; i++) total += Number(vatnumber.charAt(i)) * multipliers[i]; + + // Establish check digit. + total = 11 - total % 11; + if (total == 10) return false; + if (total == 11) total = 0; + + // Check to see if the check digit given is correct, If not, we have an error with the VAT number + if (total == vatnumber.substr(9, 1)) + return true; + else + return false; + } + + function CHEVATCheckDigit(vatnumber) { + + // Checks the check digits of a Swiss VAT number. + + // Extract the next digit and multiply by the counter. + var multipliers = [5, 4, 3, 2, 7, 6, 5, 4]; + var total = 0; + for (var i = 0; i < 8; i++) total += Number(vatnumber.charAt(i)) * multipliers[i]; + + // Establish check digit. + total = 11 - total % 11; + if (total == 10) return false; + if (total == 11) total = 0; + + // Check to see if the check digit given is correct, If not, we have an error with the VAT number + if (total == vatnumber.substr(8, 1)) + return true; + else + return false; + } + + function CYVATCheckDigit(vatnumber) { + + // Checks the check digits of a Cypriot VAT number. + + // Not allowed to start with '12' + if (Number(vatnumber.slice(0, 2) == 12)) return false; + + // Extract the next digit and multiply by the counter. + var total = 0; + for (var i = 0; i < 8; i++) { + var temp = Number(vatnumber.charAt(i)); + if (i % 2 == 0) { + switch (temp) { + case 0: + temp = 1; + break; + case 1: + temp = 0; + break; + case 2: + temp = 5; + break; + case 3: + temp = 7; + break; + case 4: + temp = 9; + break; + default: + temp = temp * 2 + 3; + } + } + total += temp; + } + + // Establish check digit using modulus 26, and translate to char. equivalent. + total = total % 26; + total = String.fromCharCode(total + 65); + + // Check to see if the check digit given is correct + if (total == vatnumber.substr(8, 1)) + return true; + else + return false; + } + + function CZVATCheckDigit(vatnumber) { + + // Checks the check digits of a Czech Republic VAT number. + + var total = 0; + var multipliers = [8, 7, 6, 5, 4, 3, 2]; + + var czexp = new Array(); + czexp[0] = (/^\d{8}$/); // 8 digit legal entities + // Note - my specification says that that the following should have a range of 0-3 in the fourth + // digit, but the valid number CZ395601439 did not confrm, so a range of 0-9 has been allowed. + czexp[1] = (/^[0-5][0-9][0|1|5|6][0-9][0-3][0-9]\d{3}$/); // 9 digit individuals + czexp[2] = (/^6\d{8}$/); // 9 digit individuals (Special cases) + czexp[3] = (/^\d{2}[0-3|5-8][0-9][0-3][0-9]\d{4}$/); // 10 digit individuals + var i = 0; + var a; + + // Legal entities + if (czexp[0].test(vatnumber)) { + + // Extract the next digit and multiply by the counter. + for (i = 0; i < 7; i++) total += Number(vatnumber.charAt(i)) * multipliers[i]; + + // Establish check digit. + total = 11 - total % 11; + if (total == 10) total = 0; + if (total == 11) total = 1; + + // Compare it with the last character of the VAT number. If it's the same, then it's valid. + if (total == vatnumber.slice(7, 8)) + return true; + else + return false; + } + + // Individuals type 1 (Standard) - 9 digits without check digit + else if (czexp[1].test(vatnumber)) { + if (Number(vatnumber.slice(0, 2)) > 62) return false; + return true; + } + + // Individuals type 2 (Special Cases) - 9 digits including check digit + else if (czexp[2].test(vatnumber)) { + + // Extract the next digit and multiply by the counter. + for (i = 0; i < 7; i++) total += Number(vatnumber.charAt(i + 1)) * multipliers[i]; + + // Establish check digit pointer into lookup table + if (total % 11 == 0) + a = total + 11; + else + a = Math.ceil(total / 11) * 11; + var pointer = a - total; + + // Convert calculated check digit according to a lookup table; + var lookup = [8, 7, 6, 5, 4, 3, 2, 1, 0, 9, 8]; + if (lookup[pointer - 1] == vatnumber.slice(8, 9)) + return true; + else + return false; + } + + // Individuals type 3 - 10 digits + else if (czexp[3].test(vatnumber)) { + var temp = Number(vatnumber.slice(0, 2)) + Number(vatnumber.slice(2, 4)) + Number(vatnumber.slice(4, 6)) + Number(vatnumber.slice(6, 8)) + Number(vatnumber.slice(8)); + if (temp % 11 == 0 && Number(vatnumber) % 11 == 0) + return true; + else + return false; + } + + // else error + return false; + } + + function DEVATCheckDigit(vatnumber) { + + // Checks the check digits of a German VAT number. + + var product = 10; + var sum = 0; + var checkdigit = 0; + for (var i = 0; i < 8; i++) { + + // Extract the next digit and implement peculiar algorithm!. + sum = (Number(vatnumber.charAt(i)) + product) % 10; + if (sum == 0) { + sum = 10; + } + product = (2 * sum) % 11; + } + + // Establish check digit. + if (11 - product == 10) { + checkdigit = 0; + } else { + checkdigit = 11 - product; + } + + // Compare it with the last two characters of the VAT number. If the same, then it is a valid + // check digit. + if (checkdigit == vatnumber.slice(8, 9)) + return true; + else + return false; + } + + function DKVATCheckDigit(vatnumber) { + + // Checks the check digits of a Danish VAT number. + + var total = 0; + var multipliers = [2, 7, 6, 5, 4, 3, 2, 1]; + + // Extract the next digit and multiply by the counter. + for (var i = 0; i < 8; i++) total += Number(vatnumber.charAt(i)) * multipliers[i]; + + // Establish check digit. + total = total % 11; + + // The remainder should be 0 for it to be valid.. + if (total == 0) + return true; + else + return false; + } + + function EEVATCheckDigit(vatnumber) { + + // Checks the check digits of an Estonian VAT number. + + var total = 0; + var multipliers = [3, 7, 1, 3, 7, 1, 3, 7]; + + // Extract the next digit and multiply by the counter. + for (var i = 0; i < 8; i++) total += Number(vatnumber.charAt(i)) * multipliers[i]; + + // Establish check digits using modulus 10. + total = 10 - total % 10; + if (total == 10) total = 0; + + // Compare it with the last character of the VAT number. If it's the same, then it's valid. + if (total == vatnumber.slice(8, 9)) + return true; + else + return false; + } + + function ELVATCheckDigit(vatnumber) { + + // Checks the check digits of a Greek VAT number. + + var total = 0; + var multipliers = [256, 128, 64, 32, 16, 8, 4, 2]; + + //eight character numbers should be prefixed with an 0. + if (vatnumber.length == 8) { + vatnumber = "0" + vatnumber; + } + + // Extract the next digit and multiply by the counter. + for (var i = 0; i < 8; i++) total += Number(vatnumber.charAt(i)) * multipliers[i]; + + // Establish check digit. + total = total % 11; + if (total > 9) { + total = 0; + } + + // Compare it with the last character of the VAT number. If it's the same, then it's valid. + if (total == vatnumber.slice(8, 9)) + return true; + else + return false; + } + + function ESVATCheckDigit(vatnumber) { + + // Checks the check digits of a Spanish VAT number. + + var total = 0; + var temp = 0; + var multipliers = [2, 1, 2, 1, 2, 1, 2]; + var esexp = new Array(); + esexp[0] = (/^[A-H|J|U|V]\d{8}$/); + esexp[1] = (/^[A-H|N-S|W]\d{7}[A-J]$/); + esexp[2] = (/^[0-9|Y|Z]\d{7}[A-Z]$/); + esexp[3] = (/^[K|L|M|X]\d{7}[A-Z]$/); + var i = 0; + + // National juridical entities + if (esexp[0].test(vatnumber)) { + + // Extract the next digit and multiply by the counter. + for (i = 0; i < 7; i++) { + temp = Number(vatnumber.charAt(i + 1)) * multipliers[i]; + if (temp > 9) + total += Math.floor(temp / 10) + temp % 10; + else + total += temp; + } + // Now calculate the check digit itself. + total = 10 - total % 10; + if (total == 10) { + total = 0; + } + + // Compare it with the last character of the VAT number. If it's the same, then it's valid. + if (total == vatnumber.slice(8, 9)) + return true; + else + return false; + } + + // Juridical entities other than national ones + else if (esexp[1].test(vatnumber)) { + + // Extract the next digit and multiply by the counter. + for (i = 0; i < 7; i++) { + temp = Number(vatnumber.charAt(i + 1)) * multipliers[i]; + if (temp > 9) + total += Math.floor(temp / 10) + temp % 10; + else + total += temp; + } + + // Now calculate the check digit itself. + total = 10 - total % 10; + total = String.fromCharCode(total + 64); + + // Compare it with the last character of the VAT number. If it's the same, then it's valid. + if (total == vatnumber.slice(8, 9)) + return true; + else + return false; + } + + // Personal number (NIF) (starting with numeric of Y or Z) + else if (esexp[2].test(vatnumber)) { + var tempnumber = vatnumber; + if (tempnumber.substring(0, 1) == 'Y') tempnumber = tempnumber.replace(/Y/, "1"); + if (tempnumber.substring(0, 1) == 'Z') tempnumber = tempnumber.replace(/Z/, "2"); + return tempnumber.charAt(8) == 'TRWAGMYFPDXBNJZSQVHLCKE'.charAt(Number(tempnumber.substring(0, 8)) % 23); + } + + // Personal number (NIF) (starting with K, L, M, or X) + else if (esexp[3].test(vatnumber)) { + return vatnumber.charAt(8) == 'TRWAGMYFPDXBNJZSQVHLCKE'.charAt(Number(vatnumber.substring(1, 8)) % 23); + } + + else return false; + } + + function EUVATCheckDigit(vatnumber) { + + // We know little about EU numbers apart from the fact that the first 3 digits represent the + // country, and that there are nine digits in total. + return true; + } + + function FIVATCheckDigit(vatnumber) { + + // Checks the check digits of a Finnish VAT number. + + var total = 0; + var multipliers = [7, 9, 10, 5, 8, 4, 2]; + + // Extract the next digit and multiply by the counter. + for (var i = 0; i < 7; i++) total += Number(vatnumber.charAt(i)) * multipliers[i]; + + // Establish check digit. + total = 11 - total % 11; + if (total > 9) { + total = 0; + } + + // Compare it with the last character of the VAT number. If it's the same, then it's valid. + if (total == vatnumber.slice(7, 8)) + return true; + else + return false; + } + + function FRVATCheckDigit(vatnumber) { + + // Checks the check digits of a French VAT number. + + if (!(/^\d{11}$/).test(vatnumber)) return true; + + // Extract the last nine digits as an integer. + var total = vatnumber.substring(2); + + // Establish check digit. + total = (total * 100 + 12) % 97; + + // Compare it with the last character of the VAT number. If it's the same, then it's valid. + if (total == vatnumber.slice(0, 2)) + return true; + else + return false; + } + + function GBVATCheckDigit(vatnumber) { + + // Checks the check digits of a UK VAT number. + + var multipliers = [8, 7, 6, 5, 4, 3, 2]; + + // Government departments + if (vatnumber.substr(0, 2) == 'GD') { + if (vatnumber.substr(2, 3) < 500) + return true; + else + return false; + } + + // Health authorities + if (vatnumber.substr(0, 2) == 'HA') { + if (vatnumber.substr(2, 3) > 499) + return true; + else + return false; + } + + // Standard and commercial numbers + var total = 0; + + // 0 VAT numbers disallowed! + if (Number(vatnumber.slice(0)) == 0) return false; + + // Check range is OK for modulus 97 calculation + var no = Number(vatnumber.slice(0, 7)); + + // Extract the next digit and multiply by the counter. + for (var i = 0; i < 7; i++) total += Number(vatnumber.charAt(i)) * multipliers[i]; + + // Old numbers use a simple 97 modulus, but new numbers use an adaptation of that (less 55). Our + // VAT number could use either system, so we check it against both. + + // Establish check digits by subtracting 97 from total until negative. + var cd = total; + while (cd > 0) { + cd = cd - 97; + } + + // Get the absolute value and compare it with the last two characters of the VAT number. If the + // same, then it is a valid traditional check digit. However, even then the number must fit within + // certain specified ranges. + cd = Math.abs(cd); + if (cd == vatnumber.slice(7, 9) && no < 9990001 && (no < 100000 || no > 999999) && (no < 9490001 || no > 9700000)) return true; + + // Now try the new method by subtracting 55 from the check digit if we can - else add 42 + if (cd >= 55) + cd = cd - 55; + else + cd = cd + 42; + if (cd == vatnumber.slice(7, 9) && no > 1000000) + return true; + else + return false; + } + + function HRVATCheckDigit(vatnumber) { + + // Checks the check digits of a Croatian VAT number using ISO 7064, MOD 11-10 for check digit. + + var product = 10; + var sum = 0; + var checkdigit = 0; + + for (var i = 0; i < 10; i++) { + + // Extract the next digit and implement the algorithm + sum = (Number(vatnumber.charAt(i)) + product) % 10; + if (sum == 0) { + sum = 10; + } + product = (2 * sum) % 11; + } + + // Now check that we have the right check digit + if ((product + vatnumber.slice(10, 11) * 1) % 10 == 1) + return true; + else + return false; + } + + function HUVATCheckDigit(vatnumber) { + + // Checks the check digits of a Hungarian VAT number. + + var total = 0; + var multipliers = [9, 7, 3, 1, 9, 7, 3]; + + // Extract the next digit and multiply by the counter. + for (var i = 0; i < 7; i++) total += Number(vatnumber.charAt(i)) * multipliers[i]; + + // Establish check digit. + total = 10 - total % 10; + if (total == 10) total = 0; + + // Compare it with the last character of the VAT number. If it's the same, then it's valid. + if (total == vatnumber.slice(7, 8)) + return true; + else + return false; + } + + function IEVATCheckDigit(vatnumber) { + + // Checks the check digits of an Irish VAT number. + + var total = 0; + var multipliers = [8, 7, 6, 5, 4, 3, 2]; + + // If the code is type 1 format, we need to convert it to the new before performing the validation. + if (/^\d[A-Z\*\+]/.test(vatnumber)) vatnumber = "0" + vatnumber.substring(2, 7) + vatnumber.substring(0, 1) + vatnumber.substring(7, 8); + + // Extract the next digit and multiply by the counter. + for (var i = 0; i < 7; i++) total += Number(vatnumber.charAt(i)) * multipliers[i]; + + // If the number is type 3 then we need to include the trailing A or H in the calculation + if (/^\d{7}[A-Z][AH]$/.test(vatnumber)) { + + // Add in a multiplier for the character A (1*9=9) or H (8*9=72) + if (vatnumber.charAt(8) == 'H') + total += 72; + else + total += 9; + } + + // Establish check digit using modulus 23, and translate to char. equivalent. + total = total % 23; + if (total == 0) + total = "W"; + else + total = String.fromCharCode(total + 64); + + // Compare it with the eighth character of the VAT number. If it's the same, then it's valid. + if (total == vatnumber.slice(7, 8)) + return true; + else + return false; + } + + function ITVATCheckDigit(vatnumber) { + + // Checks the check digits of an Italian VAT number. + + var total = 0; + var multipliers = [1, 2, 1, 2, 1, 2, 1, 2, 1, 2]; + var temp; + + // The last three digits are the issuing office, and cannot exceed more 201, unless 999 or 888 + if (Number(vatnumber.slice(0, 7)) == 0) return false; + temp = Number(vatnumber.slice(7, 10)); + if ((temp < 1) || (temp > 201) && temp != 999 && temp != 888) return false; + + // Extract the next digit and multiply by the appropriate + for (var i = 0; i < 10; i++) { + temp = Number(vatnumber.charAt(i)) * multipliers[i]; + if (temp > 9) + total += Math.floor(temp / 10) + temp % 10; + else + total += temp; + } + + // Establish check digit. + total = 10 - total % 10; + if (total > 9) { + total = 0; + } + + // Compare it with the last character of the VAT number. If it's the same, then it's valid. + if (total == vatnumber.slice(10, 11)) + return true; + else + return false; + } + + function LTVATCheckDigit(vatnumber) { + + // Checks the check digits of a Lithuanian VAT number. + var total, multipliers, i; + + // 9 character VAT numbers are for legal persons + if (vatnumber.length == 9) { + + // 8th character must be one + if (!(/^\d{7}1/).test(vatnumber)) return false; + + // Extract the next digit and multiply by the counter+1. + total = 0; + for (i = 0; i < 8; i++) total += Number(vatnumber.charAt(i)) * (i + 1); + + // Can have a double check digit calculation! + if (total % 11 == 10) { + multipliers = [3, 4, 5, 6, 7, 8, 9, 1]; + total = 0; + for (i = 0; i < 8; i++) total += Number(vatnumber.charAt(i)) * multipliers[i]; + } + + // Establish check digit. + total = total % 11; + if (total == 10) { + total = 0; + } + + // Compare it with the last character of the VAT number. If it's the same, then it's valid. + if (total == vatnumber.slice(8, 9)) + return true; + else + return false; + } + + // 12 character VAT numbers are for temporarily registered taxpayers + else { + + // 11th character must be one + if (!(/^\d{10}1/).test(vatnumber)) return false; + + // Extract the next digit and multiply by the counter+1. + total = 0; + multipliers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2]; + for (i = 0; i < 11; i++) total += Number(vatnumber.charAt(i)) * multipliers[i]; + + // Can have a double check digit calculation! + if (total % 11 == 10) { + multipliers = [3, 4, 5, 6, 7, 8, 9, 1, 2, 3, 4]; + total = 0; + for (i = 0; i < 11; i++) total += Number(vatnumber.charAt(i)) * multipliers[i]; + } + + // Establish check digit. + total = total % 11; + if (total == 10) { + total = 0; + } + + // Compare it with the last character of the VAT number. If it's the same, then it's valid. + if (total == vatnumber.slice(11, 12)) + return true; + else + return false; + } + } + + function LUVATCheckDigit(vatnumber) { + + // Checks the check digits of a Luxembourg VAT number. + + if (vatnumber.slice(0, 6) % 89 == vatnumber.slice(6, 8)) + return true; + else + return false; + } + + function LVVATCheckDigit(vatnumber) { + + // Checks the check digits of a Latvian VAT number. + + // Differentiate between legal entities and natural bodies. For the latter we simply check that + // the first six digits correspond to valid DDMMYY dates. + if ((/^[0-3]/).test(vatnumber)) { + if ((/^[0-3][0-9][0-1][0-9]/).test(vatnumber)) + return true; + else + return false; + } + + else { + + var total = 0; + var multipliers = [9, 1, 4, 8, 3, 10, 2, 5, 7, 6]; + + // Extract the next digit and multiply by the counter. + for (var i = 0; i < 10; i++) total += Number(vatnumber.charAt(i)) * multipliers[i]; + + // Establish check digits by getting modulus 11. + if (total % 11 == 4 && vatnumber[0] == 9) total = total - 45; + if (total % 11 == 4) + total = 4 - total % 11; + else if (total % 11 > 4) + total = 14 - total % 11; + else if (total % 11 < 4) + total = 3 - total % 11; + + // Compare it with the last character of the VAT number. If it's the same, then it's valid. + if (total == vatnumber.slice(10, 11)) + return true; + else + return false; + } + } + + function MTVATCheckDigit(vatnumber) { + + // Checks the check digits of a Maltese VAT number. + + var total = 0; + var multipliers = [3, 4, 6, 7, 8, 9]; + + // Extract the next digit and multiply by the counter. + for (var i = 0; i < 6; i++) total += Number(vatnumber.charAt(i)) * multipliers[i]; + + // Establish check digits by getting modulus 37. + total = 37 - total % 37; + + // Compare it with the last character of the VAT number. If it's the same, then it's valid. + if (total == vatnumber.slice(6, 8) * 1) + return true; + else + return false; + } + + function NLVATCheckDigit(vatnumber) { + + // Checks the check digits of a Dutch VAT number. + + var total = 0; + var multipliers = [9, 8, 7, 6, 5, 4, 3, 2]; + + // Extract the next digit and multiply by the counter. + for (var i = 0; i < 8; i++) total += Number(vatnumber.charAt(i)) * multipliers[i]; + + // Establish check digits by getting modulus 11. + total = total % 11; + if (total > 9) { + total = 0; + } + + // Compare it with the last character of the VAT number. If it's the same, then it's valid. + if (total == vatnumber.slice(8, 9)) + return true; + else + return false; + } + + function NOVATCheckDigit(vatnumber) { + + // Checks the check digits of a Norwegian VAT number. + // See http://www.brreg.no/english/coordination/number.html + + var total = 0; + var multipliers = [3, 2, 7, 6, 5, 4, 3, 2]; + + // Extract the next digit and multiply by the counter. + for (var i = 0; i < 8; i++) total += Number(vatnumber.charAt(i)) * multipliers[i]; + + // Establish check digits by getting modulus 11. Check digits > 9 are invalid + total = 11 - total % 11; + if (total == 11) { + total = 0; + } + if (total < 10) { + + // Compare it with the last character of the VAT number. If it's the same, then it's valid. + if (total == vatnumber.slice(8, 9)) + return true; + else + return false; + } + } + + function PLVATCheckDigit(vatnumber) { + + // Checks the check digits of a Polish VAT number. + + var total = 0; + var multipliers = [6, 5, 7, 2, 3, 4, 5, 6, 7]; + + // Extract the next digit and multiply by the counter. + for (var i = 0; i < 9; i++) total += Number(vatnumber.charAt(i)) * multipliers[i]; + + // Establish check digits subtracting modulus 11 from 11. + total = total % 11; + if (total > 9) { + total = 0; + } + + // Compare it with the last character of the VAT number. If it's the same, then it's valid. + if (total == vatnumber.slice(9, 10)) + return true; + else + return false; + } + + function PTVATCheckDigit(vatnumber) { + + // Checks the check digits of a Portugese VAT number. + + var total = 0; + var multipliers = [9, 8, 7, 6, 5, 4, 3, 2]; + + // Extract the next digit and multiply by the counter. + for (var i = 0; i < 8; i++) total += Number(vatnumber.charAt(i)) * multipliers[i]; + + // Establish check digits subtracting modulus 11 from 11. + total = 11 - total % 11; + if (total > 9) { + total = 0; + } + + // Compare it with the last character of the VAT number. If it's the same, then it's valid. + if (total == vatnumber.slice(8, 9)) + return true; + else + return false; + } + + function ROVATCheckDigit(vatnumber) { + + // Checks the check digits of a Romanian VAT number. + + var multipliers = [7, 5, 3, 2, 1, 7, 5, 3, 2]; + + // Extract the next digit and multiply by the counter. + var VATlen = vatnumber.length; + multipliers = multipliers.slice(10 - VATlen); + var total = 0; + for (var i = 0; i < vatnumber.length - 1; i++) { + total += Number(vatnumber.charAt(i)) * multipliers[i]; + } + + // Establish check digits by getting modulus 11. + total = (10 * total) % 11; + if (total == 10) total = 0; + + // Compare it with the last character of the VAT number. If it's the same, then it's valid. + if (total == vatnumber.slice(vatnumber.length - 1, vatnumber.length)) + return true; + else + return false; + } + + function RSVATCheckDigit(vatnumber) { + + // Checks the check digits of a Serbian VAT number using ISO 7064, MOD 11-10 for check digit. + + var product = 10; + var sum = 0; + var checkdigit = 0; + + for (var i = 0; i < 8; i++) { + + // Extract the next digit and implement the algorithm + sum = (Number(vatnumber.charAt(i)) + product) % 10; + if (sum == 0) { + sum = 10; + } + product = (2 * sum) % 11; + } + + // Now check that we have the right check digit + if ((product + vatnumber.slice(8, 9) * 1) % 10 == 1) + return true; + else + return false; + } + + function RUVATCheckDigit(vatnumber) { + + // Checks the check digits of a Russian INN number + // See http://russianpartner.biz/test_inn.html for algorithm + + var i; + + // 10 digit INN numbers + if (vatnumber.length == 10) { + var total = 0; + var multipliers = [2, 4, 10, 3, 5, 9, 4, 6, 8, 0]; + for (i = 0; i < 10; i++) { + total += Number(vatnumber.charAt(i)) * multipliers[i]; + } + total = total % 11; + if (total > 9) { + total = total % 10; + } + + // Compare it with the last character of the VAT number. If it is the same, then it's valid + if (total == vatnumber.slice(9, 10)) + return true; + else + return false; + + // 12 digit INN numbers + } else if (vatnumber.length == 12) { + var total1 = 0; + var multipliers1 = [7, 2, 4, 10, 3, 5, 9, 4, 6, 8, 0]; + var total2 = 0; + var multipliers2 = [3, 7, 2, 4, 10, 3, 5, 9, 4, 6, 8, 0]; + + for (i = 0; i < 11; i++) total1 += Number(vatnumber.charAt(i)) * multipliers1[i]; + total1 = total1 % 11; + if (total1 > 9) { + total1 = total1 % 10; + } + + for (i = 0; i < 11; i++) total2 += Number(vatnumber.charAt(i)) * multipliers2[i]; + total2 = total2 % 11; + if (total2 > 9) { + total2 = total2 % 10; + } + + // Compare the first check with the 11th character and the second check with the 12th and last + // character of the VAT number. If they're both the same, then it's valid + if ((total1 == vatnumber.slice(10, 11)) && (total2 == vatnumber.slice(11, 12))) + return true; + else + return false; + } + } + + function SEVATCheckDigit(vatnumber) { + var i; + + // Calculate R where R = R1 + R3 + R5 + R7 + R9, and Ri = INT(Ci/5) + (Ci*2) modulo 10 + var R = 0; + var digit; + for (i = 0; i < 9; i = i + 2) { + digit = Number(vatnumber.charAt(i)); + R += Math.floor(digit / 5) + ((digit * 2) % 10); + } + + // Calculate S where S = C2 + C4 + C6 + C8 + var S = 0; + for (i = 1; i < 9; i = i + 2) S += Number(vatnumber.charAt(i)); + + // Calculate the Check Digit + var cd = (10 - (R + S) % 10) % 10; + + // Compare it with the last character of the VAT number. If it's the same, then it's valid. + if (cd == vatnumber.slice(9, 10)) + return true; + else + return false; + } + + function SIVATCheckDigit(vatnumber) { + + // Checks the check digits of a Slovenian VAT number. + + var total = 0; + var multipliers = [8, 7, 6, 5, 4, 3, 2]; + + // Extract the next digit and multiply by the counter. + for (var i = 0; i < 7; i++) total += Number(vatnumber.charAt(i)) * multipliers[i]; + + // Establish check digits using modulus 11 + total = 11 - total % 11; + if (total == 10) { + total = 0; + } + + // Compare the number with the last character of the VAT number. If it is the + // same, then it's a valid check digit. + if (total != 11 && total == vatnumber.slice(7, 8)) + return true; + else + return false; + } + + function SKVATCheckDigit(vatnumber) { + + // Checks the check digits of a Slovakian VAT number. + + // Check that the modulus of the whole VAT number is 0 - else error + if (Number(vatnumber % 11) == 0) + return true; + else + return false; + } + + return checkVATNumber; +})();
\ No newline at end of file diff --git a/addons/partner_autocomplete/static/src/js/partner_autocomplete_core.js b/addons/partner_autocomplete/static/src/js/partner_autocomplete_core.js new file mode 100644 index 00000000..06002dd5 --- /dev/null +++ b/addons/partner_autocomplete/static/src/js/partner_autocomplete_core.js @@ -0,0 +1,366 @@ +odoo.define('partner.autocomplete.Mixin', function (require) { +'use strict'; + +var concurrency = require('web.concurrency'); + +var core = require('web.core'); +var Qweb = core.qweb; +var utils = require('web.utils'); +var _t = core._t; + +/** + * This mixin only works with classes having EventDispatcherMixin in 'web.mixins' + */ +var PartnerAutocompleteMixin = { + _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 {Promise} + * @private + */ + _autocomplete: function (value) { + var self = this; + value = value.trim(); + var isVAT = this._isVAT(value); + var odooSuggestions = []; + var clearbitSuggestions = []; + return new Promise(function (resolve, reject) { + var odooPromise = self._getOdooSuggestions(value, isVAT).then(function (suggestions){ + odooSuggestions = suggestions; + }); + + // Only get Clearbit suggestions if not a VAT number + var clearbitPromise = isVAT ? false : self._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.domain) < 0) { + websites.push(suggestion.domain); + odooSuggestions.push(suggestion); + } + }); + } + + odooSuggestions = _.filter(odooSuggestions, function (suggestion) { + return !suggestion.ignored; + }); + _.each(odooSuggestions, function(suggestion){ + delete suggestion.ignored; + }); + return resolve(odooSuggestions); + }; + + self._whenAll([odooPromise, clearbitPromise]).then(concatResults, concatResults); + }); + + }, + + /** + * Get enrichment data + * + * @param {Object} company + * @param {string} company.website + * @param {string} company.partner_gid + * @param {string} company.vat + * @returns {Promise} + * @private + */ + _enrichCompany: function (company) { + return this._rpc({ + 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 {Promise} + * @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; + }).catch(function () { + return false; + }); + }, + + /** + * Get enriched data + logo before populating partner form + * + * @param {Object} company + * @returns {Promise} + */ + _getCreateData: function (company) { + var self = this; + + var removeUselessFields = function (company) { + var fields = 'label,description,domain,logo,legal_name,ignored,email'.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]; + }); + }; + + return new Promise(function (resolve) { + // Fetch additional company info via Autocomplete Enrichment API + var enrichPromise = self._enrichCompany(company); + + // Get logo + var logoPromise = company.logo ? self._getCompanyLogo(company.logo) : false; + self._whenAll([enrichPromise, logoPromise]).then(function (result) { + var company_data = result[0]; + var logo_data = result[1]; + + // The vat should be returned for free. This is the reason why + // we add it into the data of 'company' even if an error such as + // an insufficient credit error is raised. + if (company_data.error && company_data.vat) { + company.vat = company_data.vat; + } + + if (company_data.error) { + if (company_data.error_message === 'Insufficient Credit') { + self._notifyNoCredits(); + } else if (company_data.error_message === 'No Account Token') { + self._notifyAccountToken(); + } else { + self.do_notify(false, company_data.error_message); + } + company_data = company; + } + + 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; + } + resolve({ + company: company_data, + logo: logo_data + }); + }); + }); + }, + + /** + * 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 promise 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 {Promise} + */ + _getBase64Image: function (url) { + return new Promise(function (resolve, reject) { + var xhr = new XMLHttpRequest(); + xhr.onload = function () { + utils.getDataURLFromFile(xhr.response).then(resolve); + }; + xhr.open('GET', url); + xhr.responseType = 'blob'; + xhr.onerror = reject; + xhr.send(); + }); + }, + + /** + * Use Clearbit Autocomplete API to return suggestions + * + * @param {string} value + * @returns {Promise} + * @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 {Promise} + * @private + */ + _getOdooSuggestions: function (value, isVAT) { + var method = isVAT ? 'read_by_vat' : 'autocomplete'; + + var def = this._rpc({ + 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 + * Promise.all will reject all promises whenever a promise is rejected + * This utility will continue + * + * @param {Promise[]} promises + * @returns {Promise} + * @private + */ + _whenAll: function (promises) { + return Promise.all(promises.map(function (p) { + return Promise.resolve(p); + })); + }, + + /** + * @private + * @returns {Promise} + */ + _notifyNoCredits: function () { + var self = this; + return this._rpc({ + model: 'iap.account', + method: 'get_credits_url', + args: ['partner_autocomplete'], + }).then(function (url) { + var title = _t('Not enough credits for Partner Autocomplete'); + var content = Qweb.render('partner_autocomplete.insufficient_credit_notification', { + credits_url: url + }); + self.do_notify(title, content, false, 'o_partner_autocomplete_no_credits_notify'); + }); + }, + + _notifyAccountToken: function () { + var self = this; + return this._rpc({ + model: 'iap.account', + method: 'get_config_account_url', + args: [] + }).then(function (url) { + var title = _t('IAP Account Token missing'); + if (url){ + var content = Qweb.render('partner_autocomplete.account_token', { + account_url: url + }); + self.do_notify(title, content, false, 'o_partner_autocomplete_no_credits_notify'); + } + else { + self.do_notify(title); + } + }); + }, +}; + +return PartnerAutocompleteMixin; + +}); diff --git a/addons/partner_autocomplete/static/src/js/partner_autocomplete_fieldchar.js b/addons/partner_autocomplete/static/src/js/partner_autocomplete_fieldchar.js new file mode 100644 index 00000000..37a2b19c --- /dev/null +++ b/addons/partner_autocomplete/static/src/js/partner_autocomplete_fieldchar.js @@ -0,0 +1,337 @@ +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 AutocompleteMixin = require('partner.autocomplete.Mixin'); + +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(AutocompleteMixin, { + 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; + this._getCreateData(company).then(function (data) { + if (data.logo) { + var logoField = self.model === 'res.partner' ? 'image_1920' : '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('bank_ids', data.company.bank_ids); + delete data.company.bank_ids; + + self.trigger_up('field_changed', { + dataPointID: self.dataPointID, + changes: data.company, + onSuccess: function () { + // update the input's value directly + if (self.onlyVAT) + self.$input.val(self._formatValue(company.vat)); + else + self.$input.val(self._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 (this._validateSearchTerm(value, this.onlyVAT) && this._isOnline()) { + return this._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; +}); diff --git a/addons/partner_autocomplete/static/src/js/partner_autocomplete_many2one.js b/addons/partner_autocomplete/static/src/js/partner_autocomplete_many2one.js new file mode 100644 index 00000000..856bcf65 --- /dev/null +++ b/addons/partner_autocomplete/static/src/js/partner_autocomplete_many2one.js @@ -0,0 +1,155 @@ +odoo.define('partner.autocomplete.many2one', function (require) { +'use strict'; + +var FieldMany2One = require('web.relational_fields').FieldMany2One; +var core = require('web.core'); +var AutocompleteMixin = require('partner.autocomplete.Mixin'); +var field_registry = require('web.field_registry'); + +var _t = core._t; + +var PartnerField = FieldMany2One.extend(AutocompleteMixin, { + 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: this._validateSearchTerm, + }); + + this.additionalContext['show_vat'] = true; + }, + + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + + /** + * Action : create popup form with pre-filled values from Autocomplete + * + * @param {Object} company + * @returns {Promise} + * @private + */ + _createPartner: function (company) { + var self = this; + self.$('input').val(''); + + return self._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_1920 = data.logo; + + return self._searchCreatePopup("form", false, context); + }); + }, + + /** + * Returns the display_name from a string which contains it but was altered + * as a result of the show_vat option. + * Note that the split is done on a 'figuredash', not a standard dash. + * + * @private + * @param {string} value + * @returns {string} display_name without TaxID + */ + _getDisplayNameWithoutVAT: function (value) { + return value.split(' ‒ ')[0]; + }, + + /** + * Modify autocomplete results rendering + * Add logo in the autocomplete results if logo is provided + * + * @private + */ + _modifyAutompleteRendering: function (){ + var api = this.$input.data('ui-autocomplete'); + // FIXME: bugfix to prevent traceback in mobile apps due to override + // of Many2one widget with native implementation. + if (!api) { + return; + } + api._renderItem = function(ul, item){ + ul.addClass('o_partner_autocomplete_dropdown'); + var $a = $('<a/>')["html"](item.label); + if (item.logo){ + var $img = $('<img/>').attr('src', item.logo); + $a.append($img); + } + + return $("<li></li>") + .data("item.autocomplete",item) + .append($a) + .appendTo(ul) + .addClass(item.classname); + }; + }, + + /** + * @override + * @private + */ + _renderEdit: function (){ + this.m2o_value = this._getDisplayNameWithoutVAT(this.m2o_value); + this._super.apply(this, arguments); + this._modifyAutompleteRendering(); + }, + + /** + * Query Autocomplete and add results to the popup + * + * @override + * @param search_val {string} + * @returns {Promise} + * @private + */ + _searchSuggestions: function (search_val) { + var self = this; + return new Promise(function (resolve, reject) { + if (self._isOnline()) { + + self._autocomplete(search_val).then(function (suggestions) { + var choices = []; + if (suggestions && suggestions.length) { + _.each(suggestions, function (suggestion) { + var label = '<i class="fa fa-magic text-muted"/> '; + label += _.str.sprintf('%s, <span class="text-muted">%s</span>', suggestion.label, suggestion.description); + + choices.push({ + label: label, + action: function () { + self._createPartner(suggestion); + }, + logo: suggestion.logo, + classname: 'o_partner_autocomplete_dropdown_item', + }); + }); + } + + resolve(choices); + }); + } else { + resolve([]); + } + }); + }, +}); + +field_registry.add('res_partner_many2one', PartnerField); + +return PartnerField; +}); diff --git a/addons/partner_autocomplete/static/src/scss/partner_autocomplete.scss b/addons/partner_autocomplete/static/src/scss/partner_autocomplete.scss new file mode 100644 index 00000000..cb337d4b --- /dev/null +++ b/addons/partner_autocomplete/static/src/scss/partner_autocomplete.scss @@ -0,0 +1,66 @@ +.o_field_partner_autocomplete.dropdown { + > .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.o_partner_autocomplete_dropdown { + > .ui-menu-item:nth-of-type(1n+16) { + display: none; + } + > .o_partner_autocomplete_dropdown_item { + > a { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + position: relative; + padding-right: 50px; + padding-left: 40px; + max-width: 400px; + > img { + position: absolute; + width: 20px; + height: 20px; + right: 20px; + top: 3px; + } + } + } + > .ui-menu-item > a.ui-state-active .text-muted { + color: white !important; + } +} + +@media (max-height: 700px) { + .ui-autocomplete.o_partner_autocomplete_dropdown { + > .ui-menu-item:nth-of-type(1n+13) { + display: none; + } + } +} +@media (max-height: 620px) { + .ui-autocomplete.o_partner_autocomplete_dropdown { + > .ui-menu-item:nth-of-type(1n+12) { + display: none; + } + } +} +@media (max-height: 570px) { + .ui-autocomplete.o_partner_autocomplete_dropdown { + > .ui-menu-item:nth-of-type(1n+11) { + display: none; + } + } +} diff --git a/addons/partner_autocomplete/static/src/xml/partner_autocomplete.xml b/addons/partner_autocomplete/static/src/xml/partner_autocomplete.xml new file mode 100644 index 00000000..6a1edfd8 --- /dev/null +++ b/addons/partner_autocomplete/static/src/xml/partner_autocomplete.xml @@ -0,0 +1,27 @@ +<?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> + + <!-- + @param {string} credits_url + --> + <div t-name="partner_autocomplete.insufficient_credit_notification" class="o-hidden-ios"> + <a class="btn btn-link" t-att-href="credits_url"><i class="fa fa-arrow-right"/> Buy more credits</a> + </div> + + <div t-name="partner_autocomplete.account_token" class=""> + <a class="btn btn-link" t-att-href="account_url" ><i class="fa fa-arrow-right"/> Set Your Account Token</a> + </div> +</templates> diff --git a/addons/partner_autocomplete/static/tests/partner_autocomplete_tests.js b/addons/partner_autocomplete/static/tests/partner_autocomplete_tests.js new file mode 100644 index 00000000..7b07bd99 --- /dev/null +++ b/addons/partner_autocomplete/static/tests/partner_autocomplete_tests.js @@ -0,0 +1,421 @@ +odoo.define('partner_autocomplete.tests', function (require) { + "use strict"; + + var FormView = require('web.FormView'); + var concurrency = require('web.concurrency'); + var testUtils = require("web.test_utils"); + var AutocompleteField = require('partner.autocomplete.fieldchar'); + var PartnerField = require('partner.autocomplete.many2one'); + var NotificationService = require('web.NotificationService'); + + var createView = testUtils.createView; + + function _compareResultFields(assert, form, fields, createData) { + var type, formatted, $fieldInput; + + _.each(createData, function (val, key) { + if (fields[key]) { + if (key === 'image_1920') { + if (val) val = 'data:image/png;base64,' + val; + assert.hasAttrValue(form.$(".o_field_image img"), "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 + '"' + ); + } + + } + } + }); + } + + var suggestions = [{ + name: "Odoo", + website: "odoo.com", + domain: "odoo.com", + logo: "odoo.com/logo.png", + vat: "BE0477472701" + }]; + + var enrichData = {}; + + var createData = {}; + + QUnit.module('partner_autocomplete', { + before: function () { + var fieldsToPatch = [PartnerField, AutocompleteField]; + _.each(fieldsToPatch, function (fieldToPatch) { + testUtils.mock.patch(fieldToPatch, { + _getBase64Image: function (url) { + return Promise.resolve(url === "odoo.com/logo.png" ? "odoobase64" : ""); + }, + _isOnline: function () { + return true; + }, + _getCreateData: function (company) { + var def = this._super.apply(this, arguments); + def.then(function (data) { + createData = data.company; + }); + return def; + }, + _enrichCompany: function (company) { + return Promise.resolve(enrichData); + }, + _getOdooSuggestions: function (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 Promise.resolve(results); + }, + _getClearbitSuggestions: function (value) { + return this._getOdooSuggestions(value); + }, + do_notify: function (title, message, sticky, className) { + return this.displayNotification({ + type: 'warning', + title: title, + message: message, + sticky: sticky, + className: 'o_partner_autocomplete_test_notify' + }); + }, + }); + }); + + testUtils.mock.patch(AutocompleteField, { + debounceSuggestions: 0, + }); + }, + beforeEach: function () { + enrichData = { + 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", + vat: "BE0477472701", + }; + + 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}, + parent_id: {string: "Company", type: "many2one", relation: "res.partner"}, + website: {string: "Website", type: "char", searchable: true}, + image_1920: {string: "Image", type: "binary", 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.mock.unpatch(AutocompleteField); + testUtils.mock.unpatch(PartnerField); + }, + }); + + 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_1920" widget="image"/>' + + '</form>', + }).then(function (form){ + // Set company type to Individual + var $company_type = form.$("select[name='company_type']"); + testUtils.fields.editSelect($company_type, '"individual"'); + + // 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 + testUtils.fields.editInput($input, "odoo") + 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", async function (assert) { + assert.expect(17); + var fields = this.data['res.partner'].fields; + var form = await 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_1920" widget="image"/>' + + '<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) { + 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 Promise.resolve(); + } + return this._super.apply(this, arguments); + }, + }); + // Set company type to Company + var $company_type = form.$("select[name='company_type']"); + await testUtils.fields.editSelect($company_type, '"company"'); + + // 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 + await testUtils.fields.editInput($input, "odoo"); + await testUtils.nextTick(); + 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 ne proposition"); + + await testUtils.dom.click($dropdown.find("a").first()); + $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, createData); + + // Try suggestion with bullshit query + await testUtils.fields.editInput($input, "ZZZZZZZZZZZZZZZZZZZZZZ"); + $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 + await testUtils.fields.editInput($input, "odoo"); + await testUtils.nextTick(); + $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"); + await testUtils.nextTick(); + $dropdown = form.$(".o_field_partner_autocomplete .dropdown-menu:visible"); + assert.strictEqual($dropdown.length, 0, "unfocusing the input should close the dropdown"); + + form.destroy(); + }); + + QUnit.test("Partner autocomplete : Company type = Company / VAT search", async function (assert) { + assert.expect(27); + var fields = this.data['res.partner'].fields; + var form = await 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_1920" widget="image"/>' + + '<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) { + 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 Promise.resolve(); + } + return this._super.apply(this, arguments); + }, + }); + // Set company type to Company + var $company_type = form.$("select[name='company_type']"); + await testUtils.fields.editSelect($company_type, '"company"'); + + + // 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 + await testUtils.fields.editInput($input, "BE047747270") + + 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) + await testUtils.fields.editInput($input, "BE0477472701") + $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"); + + await testUtils.dom.click($dropdown.find("a").first()); + + $input = form.$(".o_field_partner_autocomplete > input"); + assert.strictEqual($input.val(), "Odoo", "Input value should have been updated to \"Odoo\""); + + _compareResultFields(assert, form, fields, createData); + await testUtils.nextTick(); + // Set complete VAT and assert changes + // Second suggestion (only vat + clearbit result) + await testUtils.fields.editInput($input, "BE0477472701") + $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"); + + await testUtils.dom.click($dropdown.find("a").first()); + + $input = form.$(".o_field_partner_autocomplete > input"); + assert.strictEqual($input.val(), "Odoo", "Input value should have been updated to \"Odoo\""); + + _compareResultFields(assert, form, fields, 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(); + + }); + + QUnit.test("Partner autocomplete : render Many2one", function (assert) { + var done = assert.async(); + assert.expect(3); + + var M2O_DELAY = PartnerField.prototype.AUTOCOMPLETE_DELAY; + PartnerField.prototype.AUTOCOMPLETE_DELAY = 0; + + createView({ + View: FormView, + model: 'res.partner', + data: this.data, + arch: + '<form>' + + '<field name="name"/>' + + '<field name="parent_id" widget="res_partner_many2one"/>' + + '</form>', + }).then(async function (form) { + var $input = form.$('.o_field_many2one[name="parent_id"] input:visible'); + assert.strictEqual($input.length, 1, "there should be an <input/> for the Many2one"); + + await testUtils.fields.editInput($input, 'odoo'); + + concurrency.delay(0).then(function () { + var $dropdown = $input.autocomplete('widget'); + assert.strictEqual($dropdown.length, 1, "there should be an opened dropdown"); + assert.ok($dropdown.is('.o_partner_autocomplete_dropdown'), + "there should be a partner_autocomplete"); + + PartnerField.prototype.AUTOCOMPLETE_DELAY = M2O_DELAY; + form.destroy(); + + done(); + }); + }); + }); + + QUnit.test("Partner autocomplete : Notify not enough credits", async function (assert) { + assert.expect(1); + + enrichData = { + error: true, + error_message: 'Insufficient Credit', + }; + + var form = await createView({ + View: FormView, + model: 'res.partner', + data: this.data, + arch: + '<form>' + + '<field name="company_type"/>' + + '<field name="name" widget="field_partner_autocomplete"/>' + + '</form>', + services: { + notification: NotificationService, + }, + mockRPC: function (route, args) { + if (args.method === "get_credits_url"){ + return Promise.resolve('credits_url'); + } + return this._super.apply(this, arguments); + }, + }); + // Set company type to Company + var $company_type = form.$("select[name='company_type']"); + await testUtils.fields.editSelect($company_type, '"company"'); + + var $input = form.$(".o_field_partner_autocomplete > input:visible"); + await testUtils.fields.editInput($input, "BE0477472701"); + + var $dropdown = form.$(".o_field_partner_autocomplete .dropdown-menu:visible"); + await testUtils.dom.click($dropdown.find("a").first()); + + var $notify = $(".o_partner_autocomplete_test_notify"); + assert.isVisible($notify, "there should be an 'Insufficient Credit' notification"); + + form.destroy(); + }); +}); |
