You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

575 lines
23 KiB

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