From fe9e230b500af316e6f80af453a4073d9dff24b7 Mon Sep 17 00:00:00 2001 From: "Adrien Guilliams (adgu)" Date: Fri, 2 Dec 2022 14:15:08 +0000 Subject: [PATCH] [IMP] point_of_sale: add date picker to ship later option In order to plan a certain Ship Later pos order, a user must go to the backend and find the correct picking linked to the order he made. The date picker can be useful to not have to go back to the backend and continue selling to customers. Now, when pushing on the button Ship Later, a date picker appears with a default date set as the current date and the user can chose another date after the default date. closes odoo/odoo#107115 Task-id: 3078526 Related: odoo/upgrade#4200 Signed-off-by: Trinh Jacky (trj) --- addons/point_of_sale/models/pos_order.py | 14 +++++--- addons/point_of_sale/models/pos_session.py | 2 +- .../static/src/js/Popups/DatePickerPopup.js | 33 +++++++++++++++++++ .../js/Screens/PaymentScreen/PaymentScreen.js | 19 +++++++---- .../js/Screens/ReceiptScreen/OrderReceipt.js | 3 ++ addons/point_of_sale/static/src/js/models.js | 29 +++++++++++----- .../static/src/xml/Popups/DatePickerPopup.xml | 23 +++++++++++++ .../Screens/PaymentScreen/PaymentScreen.xml | 7 ++-- .../Screens/ReceiptScreen/OrderReceipt.xml | 13 ++++++++ .../tests/test_point_of_sale_flow.py | 2 +- .../SaleOrderManagementScreen.js | 12 +++++++ addons/pos_sale/static/src/js/models.js | 3 ++ 12 files changed, 136 insertions(+), 24 deletions(-) create mode 100644 addons/point_of_sale/static/src/js/Popups/DatePickerPopup.js create mode 100644 addons/point_of_sale/static/src/xml/Popups/DatePickerPopup.xml diff --git a/addons/point_of_sale/models/pos_order.py b/addons/point_of_sale/models/pos_order.py index 028544a33be..6ae66174f50 100644 --- a/addons/point_of_sale/models/pos_order.py +++ b/addons/point_of_sale/models/pos_order.py @@ -52,7 +52,7 @@ class PosOrder(models.Model): 'amount_return': ui_order['amount_return'], 'company_id': self.env['pos.session'].browse(ui_order['pos_session_id']).company_id.id, 'to_invoice': ui_order['to_invoice'] if "to_invoice" in ui_order else False, - 'to_ship': ui_order['to_ship'] if "to_ship" in ui_order else False, + 'shipping_date': ui_order['shipping_date'] if "shipping_date" in ui_order else False, 'is_tipped': ui_order.get('is_tipped', False), 'tip_amount': ui_order.get('tip_amount', 0), 'access_token': ui_order.get('access_token', '') @@ -284,7 +284,7 @@ class PosOrder(models.Model): payment_ids = fields.One2many('pos.payment', 'pos_order_id', string='Payments', readonly=True) session_move_id = fields.Many2one('account.move', string='Session Journal Entry', related='session_id.move_id', readonly=True, copy=False) to_invoice = fields.Boolean('To invoice', copy=False) - to_ship = fields.Boolean('To ship') + shipping_date = fields.Date('Shipping Date') is_invoiced = fields.Boolean('Is Invoiced', compute='_compute_is_invoiced') is_tipped = fields.Boolean('Is this already tipped?', readonly=True) tip_amount = fields.Float(string='Tip Amount', digits=0, readonly=True) @@ -887,7 +887,7 @@ class PosOrder(models.Model): def _create_order_picking(self): self.ensure_one() - if self.to_ship: + if self.shipping_date: self.lines._launch_stock_rule_from_pos_order_lines() else: if self._should_create_picking_real_time(): @@ -1028,7 +1028,7 @@ class PosOrder(models.Model): 'creation_date': order.date_order.astimezone(timezone), 'fiscal_position_id': order.fiscal_position_id.id, 'to_invoice': order.to_invoice, - 'to_ship': order.to_ship, + 'shipping_date': order.shipping_date, 'state': order.state, 'account_move': order.account_move.id, 'id': order.id, @@ -1238,7 +1238,11 @@ class PosOrderLine(models.Model): """ self.ensure_one() # Use the delivery date if there is else use date_order and lead time - date_deadline = self.order_id.date_order + if self.order_id.shipping_date: + date_deadline = self.order_id.shipping_date + else: + date_deadline = self.order_id.date_order + values = { 'group_id': group_id, 'date_planned': date_deadline, diff --git a/addons/point_of_sale/models/pos_session.py b/addons/point_of_sale/models/pos_session.py index afe5bbf94d2..c8240e69846 100644 --- a/addons/point_of_sale/models/pos_session.py +++ b/addons/point_of_sale/models/pos_session.py @@ -587,7 +587,7 @@ class PosSession(models.Model): session_destination_id = picking_type.default_location_dest_id.id for order in self.order_ids: - if order.company_id.anglo_saxon_accounting and order.is_invoiced or order.to_ship: + if order.company_id.anglo_saxon_accounting and order.is_invoiced or order.shipping_date: continue destination_id = order.partner_id.property_stock_customer.id or session_destination_id if destination_id in lines_grouped_by_dest_location: diff --git a/addons/point_of_sale/static/src/js/Popups/DatePickerPopup.js b/addons/point_of_sale/static/src/js/Popups/DatePickerPopup.js new file mode 100644 index 00000000000..e7a7864a277 --- /dev/null +++ b/addons/point_of_sale/static/src/js/Popups/DatePickerPopup.js @@ -0,0 +1,33 @@ +/** @odoo-module */ + +import AbstractAwaitablePopup from "@point_of_sale/js/Popups/AbstractAwaitablePopup"; +import Registries from "@point_of_sale/js/Registries"; +import { _lt } from "@web/core/l10n/translation"; + +const { onMounted, useRef, useState } = owl; + +class DatePickerPopup extends AbstractAwaitablePopup { + setup() { + super.setup(); + this.state = useState({shippingDate: this._today()}); + this.inputRef = useRef('input'); + onMounted(() => this.inputRef.el.focus()); + } + getPayload() { + return this.state.shippingDate < this._today() ? this._today(): this.state.shippingDate; + } + _today() { + return new Date().toISOString().split('T')[0] + } + +} +DatePickerPopup.template = 'DatePickerPopup'; +DatePickerPopup.defaultProps = { + confirmText: _lt('Confirm'), + cancelText: _lt('Discard'), + title: _lt('DatePicker'), +}; + +Registries.Component.add(DatePickerPopup); + +export default DatePickerPopup; diff --git a/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreen.js b/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreen.js index e202fc38061..9eac2d4006c 100644 --- a/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreen.js +++ b/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreen.js @@ -138,10 +138,17 @@ class PaymentScreen extends PosComponent { this.currentOrder.set_tip(parse.float(payload)); } } - toggleIsToShip() { - // click_ship - this.currentOrder.set_to_ship(!this.currentOrder.is_to_ship()); - this.render(true); + async toggleShippingDatePicker() { + if (!this.currentOrder.getShippingDate()) { + const { confirmed, payload: shippingDate } = await this.showPopup("DatePickerPopup", { + title: this.env._t("Select the shipping date"), + }); + if (confirmed) { + this.currentOrder.setShippingDate(shippingDate); + } + } else { + this.currentOrder.setShippingDate(false); + } } deletePaymentLine(event) { var self = this; @@ -326,7 +333,7 @@ class PaymentScreen extends PosComponent { } if ( - (this.currentOrder.is_to_invoice() || this.currentOrder.is_to_ship()) && + (this.currentOrder.is_to_invoice() || this.currentOrder.getShippingDate()) && !this.currentOrder.get_partner() ) { const { confirmed } = await this.showPopup("ConfirmPopup", { @@ -343,7 +350,7 @@ class PaymentScreen extends PosComponent { const partner = this.currentOrder.get_partner(); if ( - this.currentOrder.is_to_ship() && + this.currentOrder.getShippingDate() && !(partner.name && partner.street && partner.city && partner.country_id) ) { this.showPopup("ErrorPopup", { diff --git a/addons/point_of_sale/static/src/js/Screens/ReceiptScreen/OrderReceipt.js b/addons/point_of_sale/static/src/js/Screens/ReceiptScreen/OrderReceipt.js index 4f7b5e29f55..8de7f185c5f 100644 --- a/addons/point_of_sale/static/src/js/Screens/ReceiptScreen/OrderReceipt.js +++ b/addons/point_of_sale/static/src/js/Screens/ReceiptScreen/OrderReceipt.js @@ -29,6 +29,9 @@ class OrderReceipt extends PosComponent { get receiptEnv() { return this._receiptEnv; } + get shippingDate() { + return this.receiptEnv.shippingDate + } isSimple(line) { return ( line.discount === 0 && diff --git a/addons/point_of_sale/static/src/js/models.js b/addons/point_of_sale/static/src/js/models.js index 7ca30c86f32..942bf4c4e37 100644 --- a/addons/point_of_sale/static/src/js/models.js +++ b/addons/point_of_sale/static/src/js/models.js @@ -2496,6 +2496,7 @@ export class Order extends PosModel { this.cashier = this.pos.get_cashier(); this.finalized = false; // if true, cannot be modified. this.set_pricelist(this.pos.default_pricelist); + this.shippingDate = null; this.partner = null; @@ -2591,9 +2592,9 @@ export class Order extends PosModel { } this.set_partner(partner); - this.temporary = false; // FIXME - this.to_invoice = false; // FIXME - this.to_ship = false; + this.temporary = false; // FIXME + this.to_invoice = false; // FIXME + this.shippingDate = json.shipping_date; var orderlines = json.lines; for (var i = 0; i < orderlines.length; i++) { @@ -2659,7 +2660,7 @@ export class Order extends PosModel { fiscal_position_id: this.fiscal_position ? this.fiscal_position.id : false, server_id: this.server_id ? this.server_id : false, to_invoice: this.to_invoice ? this.to_invoice : false, - to_ship: this.to_ship ? this.to_ship : false, + shipping_date: this.shippingDate ? this.shippingDate : false, is_tipped: this.is_tipped || false, tip_amount: this.tip_amount || 0, access_token: this.access_token || "", @@ -2669,6 +2670,16 @@ export class Order extends PosModel { } return json; } + _exportShippingDateForPrinting() { + const shippingDate = new Date(this.shippingDate); + const localeShippingDate = field_utils.format.date( + moment(shippingDate), {}, {timezone: false}); + const exportedDate = { + localestring: localeShippingDate, + validationDate: shippingDate, + }; + return exportedDate; + } export_for_printing() { var orderlines = []; var self = this; @@ -3536,6 +3547,7 @@ export class Order extends PosModel { receipt: this.export_for_printing(), orderlines: this.get_orderlines(), paymentlines: this.get_paymentlines(), + shippingDate: this.shippingDate ? this._exportShippingDateForPrinting() : false, }; } updatePricelist(newPartner) { @@ -3561,12 +3573,11 @@ export class Order extends PosModel { this.set_pricelist(newPartnerPricelist); } /* ---- Ship later --- */ - set_to_ship(to_ship) { - this.assert_editable(); - this.to_ship = to_ship; + setShippingDate(shippingDate) { + this.shippingDate = shippingDate } - is_to_ship() { - return this.to_ship; + getShippingDate() { + return this.shippingDate; } getHasRefundLines() { for (const line of this.get_orderlines()) { diff --git a/addons/point_of_sale/static/src/xml/Popups/DatePickerPopup.xml b/addons/point_of_sale/static/src/xml/Popups/DatePickerPopup.xml new file mode 100644 index 00000000000..121bf230d1a --- /dev/null +++ b/addons/point_of_sale/static/src/xml/Popups/DatePickerPopup.xml @@ -0,0 +1,23 @@ + + + + + + + + diff --git a/addons/point_of_sale/static/src/xml/Screens/PaymentScreen/PaymentScreen.xml b/addons/point_of_sale/static/src/xml/Screens/PaymentScreen/PaymentScreen.xml index 8411e587d3a..fcbe8512d0f 100644 --- a/addons/point_of_sale/static/src/xml/Screens/PaymentScreen/PaymentScreen.xml +++ b/addons/point_of_sale/static/src/xml/Screens/PaymentScreen/PaymentScreen.xml @@ -90,9 +90,12 @@ Open Cashbox
+ t-att-class="{ highlight: currentOrder.getShippingDate() }" + t-on-click="toggleShippingDatePicker"> Ship Later + + () +
diff --git a/addons/point_of_sale/static/src/xml/Screens/ReceiptScreen/OrderReceipt.xml b/addons/point_of_sale/static/src/xml/Screens/ReceiptScreen/OrderReceipt.xml index 1143af2db90..888da5fb2aa 100644 --- a/addons/point_of_sale/static/src/xml/Screens/ReceiptScreen/OrderReceipt.xml +++ b/addons/point_of_sale/static/src/xml/Screens/ReceiptScreen/OrderReceipt.xml @@ -153,6 +153,19 @@ +
+ +
+ Expected delivery: + +
+
+ +
+
+
+
+
diff --git a/addons/point_of_sale/tests/test_point_of_sale_flow.py b/addons/point_of_sale/tests/test_point_of_sale_flow.py index 28a86e2c71d..0730d3d8e76 100644 --- a/addons/point_of_sale/tests/test_point_of_sale_flow.py +++ b/addons/point_of_sale/tests/test_point_of_sale_flow.py @@ -1040,7 +1040,7 @@ class TestPointOfSaleFlow(TestPointOfSaleCommon): 'amount_total': untax+tax, 'amount_paid': 0, 'amount_return': 0, - 'to_ship': True, + 'shipping_date': fields.Date.today(), }) context_make_payment = { diff --git a/addons/pos_sale/static/src/js/OrderManagementScreen/SaleOrderManagementScreen.js b/addons/pos_sale/static/src/js/OrderManagementScreen/SaleOrderManagementScreen.js index 54f4e8fc8db..a207b0b254b 100644 --- a/addons/pos_sale/static/src/js/OrderManagementScreen/SaleOrderManagementScreen.js +++ b/addons/pos_sale/static/src/js/OrderManagementScreen/SaleOrderManagementScreen.js @@ -86,6 +86,7 @@ class SaleOrderManagementScreen extends ControlButtonsMixin(IndependentToOrderSc if (confirmed) { const currentPOSOrder = this.env.pos.get_order(); const sale_order = await this._getSaleOrder(clickedOrder.id); + clickedOrder.shipping_date = sale_order.shipping_date try { await this.env.pos.load_new_partners(); } catch { @@ -293,6 +294,7 @@ class SaleOrderManagementScreen extends ControlButtonsMixin(IndependentToOrderSc "fiscal_position_id", "amount_total", "amount_untaxed", + "picking_ids", ], ], context: this.env.session.user_context, @@ -300,6 +302,16 @@ class SaleOrderManagementScreen extends ControlButtonsMixin(IndependentToOrderSc const sale_lines = await this._getSOLines(sale_order[0].order_line); sale_order[0].order_line = sale_lines; + + const picking_id = await this.rpc({ + model: 'stock.picking', + method: 'read', + args: [ + [sale_order[0].picking_ids[0]], + ['scheduled_date'], + ], + }); + sale_order[0].shipping_date = picking_id[0].scheduled_date; return sale_order[0]; } diff --git a/addons/pos_sale/static/src/js/models.js b/addons/pos_sale/static/src/js/models.js index 0bec1f6ab73..f843ce530df 100644 --- a/addons/pos_sale/static/src/js/models.js +++ b/addons/pos_sale/static/src/js/models.js @@ -33,6 +33,9 @@ const PosSaleOrderline = (Orderline) => this.sale_order_line_id = this.sale_order_line_id || options.sale_order_line_id; this.down_payment_details = this.down_payment_details || options.down_payment_details; this.customerNote = this.customerNote || options.customer_note; + if (this.sale_order_origin_id && this.sale_order_origin_id.shipping_date) { + this.order.setShippingDate(this.sale_order_origin_id.shipping_date); + } } init_from_JSON(json) { super.init_from_JSON(...arguments);