ppt.js 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566
  1. /******************************************************************************
  2. Point Of Sale - Product Template module for Odoo
  3. Copyright (C) 2014-Today Akretion (http://www.akretion.com)
  4. @author Sylvain LE GAL (https://twitter.com/legalsylvain)
  5. This program is free software: you can redistribute it and/or modify
  6. it under the terms of the GNU Affero General Public License as
  7. published by the Free Software Foundation, either version 3 of the
  8. License, or (at your option) any later version.
  9. This program is distributed in the hope that it will be useful,
  10. but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. GNU Affero General Public License for more details.
  13. You should have received a copy of the GNU Affero General Public License
  14. along with this program. If not, see <http://www.gnu.org/licenses/>.
  15. ******************************************************************************/
  16. openerp.pos_product_template = function (instance) {
  17. module = instance.point_of_sale;
  18. var QWeb = instance.web.qweb;
  19. var _t = instance.web._t;
  20. /* ********************************************************
  21. Overload: point_of_sale.ProductListWidget
  22. - The overload will:
  23. - display only product template;
  24. - Add an extra behaviour on click on a template, if template has many
  25. variant, displaying an extra scren to select the variant;
  26. *********************************************************** */
  27. module.ProductListWidget = module.ProductListWidget.extend({
  28. init: function(parent, options) {
  29. this._super(parent,options);
  30. var self = this;
  31. // OVERWRITE 'click_product_handler' function to do
  32. // a different behaviour if template with one or many variants
  33. // are selected.
  34. this.click_product_handler = function(event){
  35. var product = self.pos.db.get_product_by_id(this.dataset['productId']);
  36. if (product.product_variant_count == 1) {
  37. // Normal behaviour, The template has only one variant
  38. options.click_product_action(product);
  39. }
  40. else{
  41. // Display for selection all the variants of a template
  42. self.pos.pos_widget.screen_selector.show_popup('select-variant-popup', product.product_tmpl_id);
  43. }
  44. };
  45. },
  46. /* ************************************************
  47. Overload: 'set_product_list'
  48. 'set_product_list' is a function called before displaying Products.
  49. (at the beginning, after a category selection, after a research, etc.
  50. we just splice all products that are not the 'primary variant'
  51. */
  52. set_product_list: function(product_list){
  53. for (var i = product_list.length - 1; i >= 0; i--){
  54. if (!product_list[i].is_primary_variant){
  55. product_list.splice(i, 1);
  56. }
  57. }
  58. this._super(product_list);
  59. },
  60. });
  61. var _render_product_ = module.ProductListWidget.prototype.render_product;
  62. module.ProductListWidget.prototype.render_product = function(product){
  63. self = this;
  64. if (product.product_variant_count == 1){
  65. // Normal Display
  66. return _render_product_.call(this, product);
  67. }
  68. else{
  69. var cached = this.product_cache.get_node(product.id);
  70. if(!cached){
  71. var image_url = this.get_product_image_url(product);
  72. var product_html = QWeb.render('Template',{
  73. widget: this,
  74. product: product,
  75. image_url: this.get_product_image_url(product),
  76. });
  77. var product_node = document.createElement('div');
  78. product_node.innerHTML = product_html;
  79. product_node = product_node.childNodes[1];
  80. this.product_cache.cache_node(product.id,product_node);
  81. return product_node;
  82. }
  83. return cached;
  84. }
  85. };
  86. /* ********************************************************
  87. Overload: point_of_sale.PosWidget
  88. - Add a new PopUp 'SelectVariantPopupWidget';
  89. *********************************************************** */
  90. module.PosWidget = module.PosWidget.extend({
  91. /* Overload Section */
  92. build_widgets: function(){
  93. this._super();
  94. this.select_variant_popup = new module.SelectVariantPopupWidget(this, {});
  95. this.select_variant_popup.appendTo($(this.$el));
  96. this.screen_selector.popup_set['select-variant-popup'] = this.select_variant_popup;
  97. // Hide the popup because all pop up are displayed at the
  98. // beginning by default
  99. this.select_variant_popup.hide();
  100. },
  101. });
  102. /* ********************************************************
  103. Define : pos_product_template.SelectVariantPopupWidget
  104. - This widget that display a pop up to select a variant of a Template;
  105. *********************************************************** */
  106. module.SelectVariantPopupWidget = module.PopUpWidget.extend({
  107. template:'SelectVariantPopupWidget',
  108. start: function(){
  109. var self = this;
  110. // Define Variant Widget
  111. this.variant_list_widget = new module.VariantListWidget(this,{});
  112. this.variant_list_widget.replace(this.$('.placeholder-VariantListWidget'));
  113. // Define Attribute Widget
  114. this.attribute_list_widget = new module.AttributeListWidget(this,{});
  115. this.attribute_list_widget.replace(this.$('.placeholder-AttributeListWidget'));
  116. // Add behaviour on Cancel Button
  117. this.$('#variant-popup-cancel').off('click').click(function(){
  118. self.hide();
  119. });
  120. },
  121. show: function(product_tmpl_id){
  122. var self = this;
  123. var template = this.pos.db.template_by_id[product_tmpl_id];
  124. // Display Name of Template
  125. this.$('#variant-title-name').html(template.name);
  126. // Render Variants
  127. var variant_ids = this.pos.db.template_by_id[product_tmpl_id].product_variant_ids;
  128. var variant_list = [];
  129. for (var i = 0, len = variant_ids.length; i < len; i++) {
  130. variant_list.push(this.pos.db.get_product_by_id(variant_ids[i]));
  131. }
  132. this.variant_list_widget.filters = {}
  133. this.variant_list_widget.set_variant_list(variant_list);
  134. // Render Attributes
  135. var attribute_ids = this.pos.db.attribute_by_template_id(template.id);
  136. var attribute_list = [];
  137. for (var i = 0, len = attribute_ids.length; i < len; i++) {
  138. attribute_list.push(this.pos.db.get_product_attribute_by_id(attribute_ids[i]));
  139. }
  140. this.attribute_list_widget.set_attribute_list(attribute_list, template);
  141. this._super();
  142. },
  143. });
  144. /* ********************************************************
  145. Define: pos_product_template.VariantListWidget
  146. - This widget will display a list of Variants;
  147. - This widget has some part of code that come from point_of_sale.ProductListWidget;
  148. *********************************************************** */
  149. module.VariantListWidget = module.PosBaseWidget.extend({
  150. template:'VariantListWidget',
  151. init: function(parent, options) {
  152. var self = this;
  153. this._super(parent, options);
  154. this.variant_list = [];
  155. this.filter_variant_list = [];
  156. this.filters = {};
  157. this.click_variant_handler = function(event){
  158. var variant = self.pos.db.get_product_by_id(this.dataset['variantId']);
  159. if(variant.to_weight && self.pos.config.iface_electronic_scale){
  160. self.__parentedParent.hide();
  161. self.pos_widget.screen_selector.set_current_screen('scale',{product: variant});
  162. }else{
  163. self.__parentedParent.hide();
  164. self.pos.get('selectedOrder').addProduct(variant);
  165. }
  166. };
  167. },
  168. replace: function($target){
  169. this.renderElement();
  170. var target = $target[0];
  171. target.parentNode.replaceChild(this.el,target);
  172. },
  173. set_filter: function(attribute_id, value_id){
  174. this.filters[attribute_id] = value_id;
  175. this.filter_variant();
  176. },
  177. reset_filter: function(attribute_id){
  178. if (attribute_id in this.filters){
  179. delete this.filters[attribute_id];
  180. }
  181. this.filter_variant();
  182. },
  183. filter_variant: function(){
  184. value_list = []
  185. for (var item in this.filters){
  186. value_list.push(parseInt(this.filters[item]));
  187. }
  188. this.filter_variant_list = [];
  189. for (index in this.variant_list){
  190. variant = this.variant_list[index];
  191. found = true;
  192. for (var i = 0; i < value_list.length; i++){
  193. found = found && (variant.attribute_value_ids.indexOf(value_list[i]) != -1);
  194. }
  195. if (found){
  196. this.filter_variant_list.push(variant);
  197. }
  198. }
  199. this.renderElement();
  200. },
  201. set_variant_list: function(variant_list){
  202. this.variant_list = variant_list;
  203. this.filter_variant_list = variant_list;
  204. this.renderElement();
  205. },
  206. get_product_image_url: function(variant){
  207. return window.location.origin + '/web/binary/image?model=product.product&field=image_medium&id='+variant.id;
  208. },
  209. render_variant: function(variant){
  210. var self = this;
  211. var variant_html = QWeb.render('VariantWidget', {
  212. widget: self,
  213. variant: variant,
  214. image_url: self.get_product_image_url(variant),
  215. });
  216. var variant_node = document.createElement('div');
  217. variant_node.innerHTML = variant_html;
  218. variant_node = variant_node.childNodes[1];
  219. return variant_node;
  220. },
  221. renderElement: function() {
  222. var self = this;
  223. var el_html = openerp.qweb.render(this.template, {widget: this});
  224. var el_node = document.createElement('div');
  225. el_node.innerHTML = el_html;
  226. el_node = el_node.childNodes[1];
  227. if(this.el && this.el.parentNode){
  228. this.el.parentNode.replaceChild(el_node,this.el);
  229. }
  230. this.el = el_node;
  231. var list_container = el_node.querySelector('.variant-list');
  232. for(var i = 0, len = this.filter_variant_list.length; i < len; i++){
  233. var variant_node = this.render_variant(this.filter_variant_list[i]);
  234. variant_node.addEventListener('click',this.click_variant_handler);
  235. list_container.appendChild(variant_node);
  236. }
  237. },
  238. });
  239. /* ********************************************************
  240. Define: pos_product_template.AttributeListWidget
  241. - This widget will display a list of Attribute;
  242. *********************************************************** */
  243. module.AttributeListWidget = module.PosBaseWidget.extend({
  244. template:'AttributeListWidget',
  245. init: function(parent, options) {
  246. var self = this;
  247. this.attribute_list = [];
  248. this.product_template = null;
  249. this.click_set_attribute_handler = function(event){
  250. /*TODO: Refactor this function with elegant DOM manipulation */
  251. // remove selected item
  252. parent = this.parentElement.parentElement.parentElement;
  253. parent.children[0].classList.remove('selected');
  254. for (var i = 0 ; i < parent.children[1].children[0].children.length; i ++){
  255. elem = parent.children[1].children[0].children[i];
  256. elem.children[0].classList.remove('selected');
  257. }
  258. // add selected item
  259. this.children[0].classList.add('selected');
  260. self.__parentedParent.variant_list_widget.set_filter(this.dataset['attributeId'], this.dataset['attributeValueId']);
  261. };
  262. this.click_reset_attribute_handler = function(event){
  263. /*TODO: Refactor this function with elegant DOM manipulation */
  264. // remove selected item
  265. parent = this.parentElement;
  266. parent.children[0].classList.remove('selected');
  267. for (var i = 0 ; i < parent.children[1].children[0].children.length; i ++){
  268. elem = parent.children[1].children[0].children[i];
  269. elem.children[0].classList.remove('selected');
  270. }
  271. // add selected item
  272. this.classList.add('selected');
  273. self.__parentedParent.variant_list_widget.reset_filter(this.dataset['attributeId']);
  274. };
  275. this._super(parent, options);
  276. },
  277. replace: function($target){
  278. this.renderElement();
  279. var target = $target[0];
  280. target.parentNode.replaceChild(this.el,target);
  281. },
  282. set_attribute_list: function(attribute_list, product_template){
  283. this.attribute_list = attribute_list;
  284. this.product_template = product_template;
  285. this.renderElement();
  286. },
  287. render_attribute: function(attribute){
  288. var attribute_html = QWeb.render('AttributeWidget',{
  289. widget: this,
  290. attribute: attribute,
  291. });
  292. var attribute_node = document.createElement('div');
  293. attribute_node.innerHTML = attribute_html;
  294. attribute_node = attribute_node.childNodes[1];
  295. var list_container = attribute_node.querySelector('.value-list');
  296. for(var i = 0, len = attribute.value_ids.length; i < len; i++){
  297. var value = this.pos.db.get_product_attribute_value_by_id(attribute.value_ids[i]);
  298. var product_list = this.pos.db.get_product_by_ids(this.product_template.product_variant_ids);
  299. var subproduct_list = this.pos.db.get_product_by_value_and_products(value.id, product_list);
  300. var variant_qty = subproduct_list.length;
  301. // Hide product attribute value if there is no product associated to it
  302. if (variant_qty != 0) {
  303. var value_node = this.render_value(value, variant_qty);
  304. value_node.addEventListener('click', this.click_set_attribute_handler);
  305. list_container.appendChild(value_node);
  306. }
  307. };
  308. return attribute_node;
  309. },
  310. render_value: function(value, variant_qty){
  311. var value_html = QWeb.render('AttributeValueWidget',{
  312. widget: this,
  313. value: value,
  314. variant_qty: variant_qty,
  315. });
  316. var value_node = document.createElement('div');
  317. value_node.innerHTML = value_html;
  318. value_node = value_node.childNodes[1];
  319. return value_node;
  320. },
  321. renderElement: function() {
  322. var self = this;
  323. var el_html = openerp.qweb.render(this.template, {widget: this});
  324. var el_node = document.createElement('div');
  325. el_node.innerHTML = el_html;
  326. el_node = el_node.childNodes[1];
  327. if(this.el && this.el.parentNode){
  328. this.el.parentNode.replaceChild(el_node,this.el);
  329. }
  330. this.el = el_node;
  331. var list_container = el_node.querySelector('.attribute-list');
  332. for(var i = 0, len = this.attribute_list.length; i < len; i++){
  333. var attribute_node = this.render_attribute(this.attribute_list[i]);
  334. attribute_node.querySelector('.attribute-name').addEventListener('click', this.click_reset_attribute_handler);
  335. // attribute_node.addEventListener('click', this.click_reset_attribute_handler);
  336. list_container.appendChild(attribute_node);
  337. };
  338. },
  339. });
  340. /* ********************************************************
  341. Overload: point_of_sale.PosDB
  342. - Add to local storage Product Templates Data.
  343. - Link Product Variants to Product Templates.
  344. - Add an extra field 'is_primary_variant' on product object. the product
  345. will be display on product list, only if it is the primary variant;
  346. Otherwise, the product will be displayed only on Template Screen.
  347. - Add an extra field 'product_variant_count' on product object that
  348. indicates the number of variant of the template of the product.
  349. *********************************************************** */
  350. module.PosDB = module.PosDB.extend({
  351. init: function(options){
  352. this.template_by_id = {};
  353. this.product_attribute_by_id = {};
  354. this.product_attribute_value_by_id = {};
  355. this._super(options);
  356. },
  357. get_product_by_value_and_products: function(value_id, products){
  358. var list = [];
  359. for (var i = 0, len = products.length; i < len; i++) {
  360. if (products[i].attribute_value_ids.indexOf(value_id) != -1){
  361. list.push(products[i]);
  362. }
  363. }
  364. return list;
  365. },
  366. get_product_attribute_by_id: function(attribute_id){
  367. return this.product_attribute_by_id[attribute_id];
  368. },
  369. get_product_attribute_value_by_id: function(attribute_value_id){
  370. return this.product_attribute_value_by_id[attribute_value_id];
  371. },
  372. get_product_by_ids: function(product_ids){
  373. var list = [];
  374. for (var i = 0, len = product_ids.length; i < len; i++) {
  375. list.push(this.product_by_id[product_ids[i]]);
  376. }
  377. return list;
  378. },
  379. attribute_by_template_id: function(template_id){
  380. template = this.template_by_id[template_id];
  381. return this.attribute_by_attribute_value_ids(template.attribute_value_ids);
  382. },
  383. attribute_by_attribute_value_ids: function(value_ids){
  384. attribute_ids = [];
  385. for (var i = 0; i < value_ids.length; i++){
  386. var value = this.product_attribute_value_by_id[value_ids[i]];
  387. if (attribute_ids.indexOf(value.attribute_id[0])==-1){
  388. attribute_ids.push(value.attribute_id[0]);
  389. }
  390. }
  391. return attribute_ids;
  392. },
  393. add_templates: function(templates){
  394. for(var i=0 ; i < templates.length; i++){
  395. var attribute_value_ids = [];
  396. // store Templates
  397. this.template_by_id[templates[i].id] = templates[i];
  398. // Update Product information
  399. for (var j = 0; j <templates[i].product_variant_ids.length; j++){
  400. var product = this.product_by_id[templates[i].product_variant_ids[j]]
  401. for (var k = 0; k < product.attribute_value_ids.length; k++){
  402. if (attribute_value_ids.indexOf(product.attribute_value_ids[k])==-1){
  403. attribute_value_ids.push(product.attribute_value_ids[k]);
  404. }
  405. }
  406. product.product_variant_count = templates[i].product_variant_count;
  407. product.is_primary_variant = (j==0);
  408. }
  409. this.template_by_id[templates[i].id].attribute_value_ids = attribute_value_ids;
  410. }
  411. },
  412. add_product_attributes: function(product_attributes){
  413. for(var i=0 ; i < product_attributes.length; i++){
  414. // store Product Attributes
  415. this.product_attribute_by_id[product_attributes[i].id] = product_attributes[i];
  416. }
  417. },
  418. add_product_attribute_values: function(product_attribute_values){
  419. for(var i=0 ; i < product_attribute_values.length; i++){
  420. // store Product Attribute Values
  421. this.product_attribute_value_by_id[product_attribute_values[i].id] = product_attribute_values[i];
  422. }
  423. },
  424. });
  425. /* ********************************************************
  426. Overload: point_of_sale.PosModel
  427. - Overload module.PosModel.initialize function to load extra-data
  428. - Load 'name' field of model product.product;
  429. - Load product.template model;
  430. *********************************************************** */
  431. var _initialize_ = module.PosModel.prototype.initialize;
  432. module.PosModel.prototype.initialize = function(session, attributes){
  433. self = this;
  434. // Add the load of the field product_product.name
  435. // that is the name of the template
  436. // Add the load of attribute values
  437. for (var i = 0 ; i < this.models.length; i++){
  438. if (this.models[i].model == 'product.product'){
  439. if (this.models[i].fields.indexOf('name') == -1) {
  440. this.models[i].fields.push('name');
  441. }
  442. if (this.models[i].fields.indexOf('attribute_value_ids') == -1) {
  443. this.models[i].fields.push('attribute_value_ids');
  444. }
  445. }
  446. }
  447. // Load Product Template
  448. model = {
  449. model: 'product.template',
  450. fields: [
  451. 'name',
  452. 'display_name',
  453. 'product_variant_ids',
  454. 'product_variant_count',
  455. ],
  456. domain: function(self){
  457. return [
  458. ['sale_ok','=',true],
  459. ['available_in_pos','=',true],
  460. ];},
  461. context: function(self){
  462. return {
  463. pricelist: self.pricelist.id,
  464. display_default_code: false,
  465. };},
  466. loaded: function(self, templates){
  467. self.db.add_templates(templates);
  468. },
  469. }
  470. this.models.push(model);
  471. // Load Product Attribute
  472. model = {
  473. model: 'product.attribute',
  474. fields: [
  475. 'name',
  476. 'value_ids',
  477. ],
  478. loaded: function(self, attributes){
  479. self.db.add_product_attributes(attributes);
  480. },
  481. }
  482. this.models.push(model);
  483. // Load Product Attribute Value
  484. model = {
  485. model: 'product.attribute.value',
  486. fields: [
  487. 'name',
  488. 'attribute_id',
  489. ],
  490. loaded: function(self, values){
  491. self.db.add_product_attribute_values(values);
  492. },
  493. }
  494. this.models.push(model);
  495. return _initialize_.call(this, session, attributes);
  496. };
  497. };