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.

399 lines
16 KiB

  1. /* Copyright 2018 GRAP - Sylvain LE GAL
  2. Copyright 2018 Tecnativa - David Vidal
  3. License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl). */
  4. odoo.define('pos_order_mgmt.widgets', function (require) {
  5. "use strict";
  6. var core = require('web.core');
  7. var _t = core._t;
  8. var PosBaseWidget = require('point_of_sale.BaseWidget');
  9. var screens = require('point_of_sale.screens');
  10. var gui = require('point_of_sale.gui');
  11. var chrome = require('point_of_sale.chrome');
  12. var pos = require('point_of_sale.models');
  13. var QWeb = core.qweb;
  14. var ScreenWidget = screens.ScreenWidget;
  15. var DomCache = screens.DomCache;
  16. screens.ReceiptScreenWidget.include({
  17. render_receipt: function () {
  18. if (!this.pos.reloaded_order) {
  19. return this._super();
  20. }
  21. var order = this.pos.reloaded_order;
  22. this.$('.pos-receipt-container').html(QWeb.render('PosTicket', {
  23. widget: this,
  24. pos: this.pos,
  25. order: order,
  26. receipt: order.export_for_printing(),
  27. orderlines: order.get_orderlines(),
  28. paymentlines: order.get_paymentlines(),
  29. }));
  30. this.pos.from_loaded_order = true;
  31. },
  32. click_next: function () {
  33. if (!this.pos.from_loaded_order) {
  34. return this._super();
  35. }
  36. this.pos.from_loaded_order = false;
  37. // When reprinting a loaded order we temporarily set it as the
  38. // active one. When we get out from the printing screen, we set
  39. // it back to the one that was active
  40. if (this.pos.current_order) {
  41. this.pos.set_order(this.pos.current_order);
  42. this.pos.current_order = false;
  43. }
  44. return this.gui.show_screen(this.gui.startup_screen);
  45. },
  46. });
  47. var OrderListScreenWidget = ScreenWidget.extend({
  48. template: 'OrderListScreenWidget',
  49. init: function (parent, options) {
  50. this._super(parent, options);
  51. this.order_cache = new DomCache();
  52. this.orders = [];
  53. this.unknown_products = [];
  54. this.search_done_orders();
  55. },
  56. auto_back: true,
  57. show: function () {
  58. var self = this;
  59. var previous_screen = this.pos.get_order().get_screen_data('previous-screen');
  60. if (previous_screen === 'receipt') {
  61. this.gui.screen_instances.receipt.click_next();
  62. this.gui.show_screen('orderlist');
  63. }
  64. this._super();
  65. this.renderElement();
  66. this.old_order = this.pos.get_order();
  67. this.$('.back').click(function () {
  68. return self.gui.show_screen(self.gui.startup_screen);
  69. });
  70. if (self.orders.length === 0) {
  71. this.search_done_orders();
  72. }
  73. this.render_list();
  74. var search_timeout = null;
  75. if (this.pos.config.iface_vkeyboard && this.chrome.widget.keyboard) {
  76. this.chrome.widget.keyboard.connect(this.$('.searchbox input'));
  77. }
  78. this.$('.searchbox input').on('keyup', function (event) {
  79. clearTimeout(search_timeout);
  80. var query = this.value;
  81. search_timeout = setTimeout(function () {
  82. self.perform_search(query, event.which === 13);
  83. }, 70);
  84. });
  85. this.$('.searchbox .search-clear').click(function () {
  86. self.clear_search();
  87. });
  88. },
  89. render_list: function () {
  90. var self = this;
  91. var orders = this.orders;
  92. var contents = this.$el[0].querySelector('.order-list-contents');
  93. contents.innerHTML = "";
  94. for (var i = 0, len = Math.min(orders.length, 1000); i < len; i++) {
  95. var order = orders[i];
  96. var orderline = this.order_cache.get_node(order.id || order.uid);
  97. if (!orderline) {
  98. var orderline_html = QWeb.render('OrderLine', {
  99. widget: this,
  100. order: order,
  101. });
  102. orderline = document.createElement('tbody');
  103. orderline.innerHTML = orderline_html;
  104. orderline = orderline.childNodes[1];
  105. this.order_cache.cache_node(order.id || order.uid, orderline);
  106. }
  107. if (order === this.old_order) {
  108. orderline.classList.add('highlight');
  109. } else {
  110. orderline.classList.remove('highlight');
  111. }
  112. contents.appendChild(orderline);
  113. }
  114. // FIXME: Everytime the list is rendered we need to reassing the
  115. // button events.
  116. this.$('.order-list-return').off('click');
  117. this.$('.order-list-reprint').off('click');
  118. this.$('.order-list-return').click(function (event) {
  119. self.order_list_actions(event, 'return');
  120. });
  121. this.$('.order-list-reprint').click(function (event) {
  122. self.order_list_actions(event, 'print');
  123. });
  124. },
  125. order_list_actions: function (event, action) {
  126. var dataset = event.target.parentNode.dataset;
  127. var self = this;
  128. if (dataset.orderId) {
  129. this.load_order(parseInt(dataset.orderId, 10), action);
  130. } else {
  131. var local_order = '';
  132. _.each(this.orders, function (order) {
  133. if (order.uid === dataset.uid) {
  134. order.return = action === 'return';
  135. local_order = self._prepare_order_from_order_data(order);
  136. }
  137. });
  138. if (local_order) {
  139. this['action_' + action](local_order);
  140. }
  141. }
  142. },
  143. action_print: function (order) {
  144. var receipt = order.export_for_printing();
  145. // We store temporarily the current order so we can safely compute
  146. // taxes based on fiscal position
  147. this.pos.current_order = this.pos.get_order()
  148. this.pos.set_order(order);
  149. if (this.pos.config.iface_print_via_proxy) {
  150. this.pos.proxy.print_receipt(QWeb.render(
  151. 'XmlReceipt', {
  152. receipt: receipt,
  153. widget: this,
  154. pos: this.pos,
  155. order: order,
  156. orderlines: order.get_orderlines(),
  157. paymentlines: order.get_paymentlines(),
  158. }));
  159. this.pos.set_order(this.pos.current_order);
  160. this.pos.current_order = false;
  161. } else {
  162. this.pos.reloaded_order = order;
  163. this.gui.show_screen('receipt');
  164. this.pos.reloaded_order = false;
  165. }
  166. },
  167. action_return: function (order) {
  168. this.pos.get('orders').add(order);
  169. this.pos.set('selectedOrder', order);
  170. return order;
  171. },
  172. _prepare_order_from_order_data: function (order_data) {
  173. var self = this;
  174. var order = new pos.Order({}, {
  175. pos: this.pos,
  176. temporary: !order_data.return,
  177. });
  178. // Set Generic Info
  179. if (!order_data.return) {
  180. order.name = order_data.pos_reference || order_data.name;
  181. }
  182. if (order_data.partner_id.length) {
  183. order_data.partner_id = order_data.partner_id[0];
  184. }
  185. order.set_client(this.pos.db.get_partner_by_id(order_data.partner_id));
  186. // Set fiscal position
  187. if (order_data.fiscal_position && this.pos.fiscal_positions) {
  188. var fiscal_positions = this.pos.fiscal_positions;
  189. order.fiscal_position = fiscal_positions.filter(function (p) {
  190. return p.id === order_data.fiscal_position;
  191. })[0];
  192. order.trigger('change');
  193. }
  194. // Set order lines
  195. var orderLines = order_data.line_ids || order_data.lines || [];
  196. _.each(orderLines, function (orderLine) {
  197. var line = orderLine;
  198. // In case of local data
  199. if (line.length === 3) {
  200. line = line[2];
  201. }
  202. var product = self.pos.db.get_product_by_id(line.product_id);
  203. // Check if product are available in pos
  204. if (_.isUndefined(product)) {
  205. self.unknown_products.push(String(line.product_id));
  206. } else {
  207. // Create a new order line
  208. order.add_product(product, {
  209. price: line.price_unit,
  210. quantity: order_data.return ? line.qty * -1 : line.qty,
  211. discount: line.discount,
  212. merge: false,
  213. });
  214. }
  215. });
  216. if (order_data.return) {
  217. order.return = true;
  218. // A credit note should be emited if there was an invoice
  219. order.set_to_invoice(order_data.origin_invoice_id);
  220. // We'll refunded orders once they are synced
  221. order.returned_order_id = order_data.id || order_data.name;
  222. order.origin_name = order_data.pos_reference || order.returned_order_id;
  223. return order;
  224. }
  225. if (order_data.returned_order_id) {
  226. order.origin_name = order_data.returned_order_id;
  227. }
  228. order.formatted_validation_date = moment(order_data.date_order).format('YYYY-MM-DD HH:mm:ss');
  229. // Set Payment lines
  230. var paymentLines = order_data.statement_ids || [];
  231. _.each(paymentLines, function (paymentLine) {
  232. var line = paymentLine;
  233. // In case of local data
  234. if (line.length === 3) {
  235. line = line[2];
  236. }
  237. _.each(self.pos.cashregisters, function (cashregister) {
  238. if (cashregister.id === line.statement_id) {
  239. if (line.amount > 0) {
  240. // If it is not change
  241. order.add_paymentline(cashregister);
  242. order.selected_paymentline.set_amount(line.amount);
  243. }
  244. }
  245. });
  246. });
  247. return order;
  248. },
  249. load_order: function (order_id, action) {
  250. this.unknown_products = [];
  251. var self = this;
  252. return this._rpc({
  253. model: 'pos.order',
  254. method: 'load_done_order_for_pos',
  255. args: [order_id],
  256. }).then(function (order_data) {
  257. self.gui.back();
  258. var correct_order_print = true;
  259. if (action === 'return') {
  260. order_data.return = true;
  261. }
  262. var order = self._prepare_order_from_order_data(order_data);
  263. // Forbid POS Order loading if some products are unknown
  264. if (self.unknown_products.length > 0) {
  265. self.gui.show_popup('error-traceback', {
  266. 'title': _t('Unknown Products'),
  267. 'body': _t('Unable to load some order lines because the ' +
  268. 'products are not available in the POS cache.\n\n' +
  269. 'Please check that lines :\n\n * ') + self.unknown_products.join("; \n *"),
  270. });
  271. correct_order_print = false;
  272. }
  273. if (correct_order_print && action === 'print') {
  274. self.action_print(order);
  275. }
  276. if (correct_order_print && action === 'return') {
  277. self.action_return(order);
  278. }
  279. }).fail(function (error, event) {
  280. if (parseInt(error.code, 10) === 200) {
  281. // Business Logic Error, not a connection problem
  282. self.gui.show_popup(
  283. 'error-traceback', {
  284. 'title': error.data.message,
  285. 'body': error.data.debug,
  286. });
  287. } else {
  288. self.gui.show_popup('error', {
  289. 'title': _t('Connection error'),
  290. 'body': _t('Can not execute this action because the POS is currently offline'),
  291. });
  292. }
  293. event.preventDefault();
  294. });
  295. },
  296. // Search Part
  297. search_done_orders: function (query) {
  298. var self = this;
  299. return this._rpc({
  300. model: 'pos.order',
  301. method: 'search_done_orders_for_pos',
  302. args: [query || '', this.pos.pos_session.id],
  303. }).then(function (result) {
  304. self.orders = result;
  305. }).fail(function (error, event) {
  306. if (parseInt(error.code, 10) === 200) {
  307. // Business Logic Error, not a connection problem
  308. self.gui.show_popup(
  309. 'error-traceback', {
  310. 'title': error.data.message,
  311. 'body': error.data.debug,
  312. }
  313. );
  314. } else {
  315. self.gui.show_popup('error', {
  316. 'title': _t('Connection error'),
  317. 'body': _t('Can not execute this action because the POS is currently offline'),
  318. });
  319. }
  320. event.preventDefault();
  321. });
  322. },
  323. perform_search: function (query) {
  324. var self = this;
  325. this.search_done_orders(query)
  326. .done(function () {
  327. self.render_list();
  328. });
  329. },
  330. clear_search: function () {
  331. var self = this;
  332. this.search_done_orders()
  333. .done(function () {
  334. self.$('.searchbox input')[0].value = '';
  335. self.$('.searchbox input').focus();
  336. self.render_list();
  337. });
  338. },
  339. });
  340. gui.define_screen({
  341. name: 'orderlist',
  342. widget: OrderListScreenWidget,
  343. });
  344. var ListOrderButtonWidget = PosBaseWidget.extend({
  345. template: 'ListOrderButtonWidget',
  346. init: function (parent, options) {
  347. var opts = options || {};
  348. this._super(parent, opts);
  349. this.action = opts.action;
  350. this.label = opts.label;
  351. },
  352. button_click: function () {
  353. this.gui.show_screen('orderlist');
  354. },
  355. renderElement: function () {
  356. var self = this;
  357. this._super();
  358. this.$el.click(function () {
  359. self.button_click();
  360. });
  361. },
  362. });
  363. var widgets = chrome.Chrome.prototype.widgets;
  364. widgets.push({
  365. 'name': 'list_orders',
  366. 'widget': ListOrderButtonWidget,
  367. 'prepend': '.pos-rightheader',
  368. 'args': {
  369. 'label': 'All Orders',
  370. },
  371. });
  372. return {
  373. ListOrderButtonWidget: ListOrderButtonWidget,
  374. OrderListScreenWidget: OrderListScreenWidget,
  375. };
  376. });