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.

491 lines
18 KiB

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