first commit

This commit is contained in:
2024-11-05 12:22:50 +01:00
commit e5682a3912
19641 changed files with 2948548 additions and 0 deletions

View File

@@ -0,0 +1,210 @@
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/OSL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
* versions in the future. If you wish to customize PrestaShop for your
* needs please refer to https://devdocs.prestashop.com/ for more information.
*
* @author PrestaShop SA and Contributors <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
*/
/* eslint-disable max-len */
export default {
mainDiv: '#order-view-page',
orderPaymentDetailsBtn: '.js-payment-details-btn',
orderPaymentFormAmountInput: '#order_payment_amount',
orderPaymentInvoiceSelect: '#order_payment_id_invoice',
viewOrderPaymentsBlock: '#view_order_payments_block',
viewOrderPaymentsAlert: '.js-view-order-payments-alert',
privateNoteToggleBtn: '.js-private-note-toggle-btn',
privateNoteBlock: '.js-private-note-block',
privateNoteInput: '#private_note_note',
privateNoteSubmitBtn: '.js-private-note-btn',
addCartRuleModal: '#addOrderDiscountModal',
addCartRuleInvoiceIdSelect: '#add_order_cart_rule_invoice_id',
addCartRuleNameInput: '#add_order_cart_rule_name',
addCartRuleTypeSelect: '#add_order_cart_rule_type',
addCartRuleValueInput: '#add_order_cart_rule_value',
addCartRuleValueUnit: '#add_order_cart_rule_value_unit',
addCartRuleSubmit: '#add_order_cart_rule_submit',
cartRuleHelpText: '.js-cart-rule-value-help',
updateOrderStatusActionBtn: '#update_order_status_action_btn',
updateOrderStatusActionInput: '#update_order_status_action_input',
updateOrderStatusActionInputWrapper: '#update_order_status_action_input_wrapper',
updateOrderStatusActionForm: '#update_order_status_action_form',
showOrderShippingUpdateModalBtn: '.js-update-shipping-btn',
updateOrderShippingTrackingNumberInput: '#update_order_shipping_tracking_number',
updateOrderShippingCurrentOrderCarrierIdInput: '#update_order_shipping_current_order_carrier_id',
updateCustomerAddressModal: '#updateCustomerAddressModal',
openOrderAddressUpdateModalBtn: '.js-update-customer-address-modal-btn',
updateOrderAddressTypeInput: '#change_order_address_address_type',
deliveryAddressEditBtn: '#js-delivery-address-edit-btn',
invoiceAddressEditBtn: '#js-invoice-address-edit-btn',
orderMessageNameSelect: '#order_message_order_message',
orderMessagesContainer: '.js-order-messages-container',
orderMessage: '#order_message_message',
orderMessageChangeWarning: '.js-message-change-warning',
orderDocumentsTabCount: '#orderDocumentsTab .count',
orderDocumentsTabBody: '#orderDocumentsTabContent .card-body',
orderShippingTabCount: '#orderShippingTab .count',
orderShippingTabBody: '#orderShippingTabContent .card-body',
allMessagesModal: '#view_all_messages_modal',
allMessagesList: '#all-messages-list',
openAllMessagesBtn: '.js-open-all-messages-btn',
// Products table elements
productOriginalPosition: '#orderProductsOriginalPosition',
productModificationPosition: '#orderProductsModificationPosition',
productsPanel: '#orderProductsPanel',
productsCount: '#orderProductsPanelCount',
productDeleteBtn: '.js-order-product-delete-btn',
productsTable: '#orderProductsTable',
productsPagination: '.order-product-pagination',
productsNavPagination: '#orderProductsNavPagination',
productsTablePagination: '#orderProductsTablePagination',
productsTablePaginationNext: '#orderProductsTablePaginationNext',
productsTablePaginationPrev: '#orderProductsTablePaginationPrev',
productsTablePaginationLink: '.page-item:not(.d-none):not(#orderProductsTablePaginationNext):not(#orderProductsTablePaginationPrev) .page-link',
productsTablePaginationActive: '#orderProductsTablePagination .page-item.active span',
productsTablePaginationTemplate: '#orderProductsTablePagination .page-item.d-none',
productsTablePaginationNumberSelector: '#orderProductsTablePaginationNumberSelector',
productsTableRow: (productId) => `#orderProduct_${productId}`,
productsTableRowEdited: (productId) => `#editOrderProduct_${productId}`,
productsTableRows: 'tr.cellProduct',
productsCellLocation: 'tr .cellProductLocation',
productsCellRefunded: 'tr .cellProductRefunded',
productsCellLocationDisplayed: 'tr:not(.d-none) .cellProductLocation',
productsCellRefundedDisplayed: 'tr:not(.d-none) .cellProductRefunded',
productsTableCustomizationRows: '#orderProductsTable .order-product-customization',
productEditButtons: '.js-order-product-edit-btn',
productEditBtn: (productId) => `#orderProduct_${productId} .js-order-product-edit-btn`,
productAddBtn: '#addProductBtn',
productActionBtn: '.js-product-action-btn',
productAddActionBtn: '#add_product_row_add',
productCancelAddBtn: '#add_product_row_cancel',
productAddRow: '#addProductTableRow',
productSearchInput: '#add_product_row_search',
productSearchInputAutocomplete: '#addProductTableRow .dropdown',
productSearchInputAutocompleteMenu: '#addProductTableRow .dropdown .dropdown-menu',
productAddIdInput: '#add_product_row_product_id',
productAddTaxRateInput: '#add_product_row_tax_rate',
productAddCombinationsBlock: '#addProductCombinations',
productAddCombinationsSelect: '#addProductCombinationId',
productAddPriceTaxExclInput: '#add_product_row_price_tax_excluded',
productAddPriceTaxInclInput: '#add_product_row_price_tax_included',
productAddQuantityInput: '#add_product_row_quantity',
productAddAvailableText: '#addProductAvailable',
productAddLocationText: '#addProductLocation',
productAddTotalPriceText: '#addProductTotalPrice',
productAddInvoiceSelect: '#add_product_row_invoice',
productAddFreeShippingSelect: '#add_product_row_free_shipping',
productAddNewInvoiceInfo: '#addProductNewInvoiceInfo',
productEditSaveBtn: '.productEditSaveBtn',
productEditCancelBtn: '.productEditCancelBtn',
productEditRowTemplate: '#editProductTableRowTemplate',
productEditRow: '.editProductRow',
productEditImage: '.cellProductImg',
productEditName: '.cellProductName',
productEditUnitPrice: '.cellProductUnitPrice',
productEditQuantity: '.cellProductQuantity',
productEditAvailableQuantity: '.cellProductAvailableQuantity',
productEditTotalPrice: '.cellProductTotalPrice',
productEditPriceTaxExclInput: '.editProductPriceTaxExcl',
productEditPriceTaxInclInput: '.editProductPriceTaxIncl',
productEditInvoiceSelect: '.editProductInvoice',
productEditQuantityInput: '.editProductQuantity',
productEditLocationText: '.editProductLocation',
productEditAvailableText: '.editProductAvailable',
productEditTotalPriceText: '.editProductTotalPrice',
// Product Discount List
productDiscountList: {
list: '.table.discountList',
},
// Product Pack Modal
productPackModal: {
modal: '#product-pack-modal',
table: '#product-pack-modal-table tbody',
rows: '#product-pack-modal-table tbody tr:not(#template-pack-table-row)',
template: '#template-pack-table-row',
product: {
img: '.cell-product-img img',
link: '.cell-product-name a',
name: '.cell-product-name .product-name',
ref: '.cell-product-name .product-reference',
supplierRef: '.cell-product-name .product-supplier-reference',
quantity: '.cell-product-quantity',
availableQuantity: '.cell-product-available-quantity',
},
},
// Order price elements
orderProductsTotal: '#orderProductsTotal',
orderDiscountsTotalContainer: '#order-discounts-total-container',
orderDiscountsTotal: '#orderDiscountsTotal',
orderWrappingTotal: '#orderWrappingTotal',
orderShippingTotalContainer: '#order-shipping-total-container',
orderShippingTotal: '#orderShippingTotal',
orderTaxesTotal: '#orderTaxesTotal',
orderTotal: '#orderTotal',
orderHookTabsContainer: '#order_hook_tabs',
// Product cancel/refund elements
cancelProduct: {
form: 'form[name="cancel_product"]',
buttons: {
abort: 'button.cancel-product-element-abort',
save: '#cancel_product_save',
partialRefund: 'button.partial-refund-display',
standardRefund: 'button.standard-refund-display',
returnProduct: 'button.return-product-display',
cancelProducts: 'button.cancel-product-display',
},
inputs: {
quantity: '.cancel-product-quantity input',
amount: '.cancel-product-amount input',
selector: '.cancel-product-selector input',
},
table: {
cell: '.cancel-product-cell',
header: 'th.cancel-product-element p',
actions: 'td.cellProductActions, th.product_actions',
},
checkboxes: {
restock: '#cancel_product_restock',
creditSlip: '#cancel_product_credit_slip',
voucher: '#cancel_product_voucher',
},
radios: {
voucherRefundType: {
productPrices: 'input[voucher-refund-type="0"]',
productPricesVoucherExcluded: 'input[voucher-refund-type="1"]',
negativeErrorMessage: '.voucher-refund-type-negative-error',
},
},
toggle: {
partialRefund: '.cancel-product-element:not(.hidden):not(.shipping-refund), .cancel-product-amount',
standardRefund: '.cancel-product-element:not(.hidden):not(.shipping-refund-amount):not(.restock-products), .cancel-product-selector',
returnProduct: '.cancel-product-element:not(.hidden):not(.shipping-refund-amount), .cancel-product-selector',
cancelProducts: '.cancel-product-element:not(.hidden):not(.shipping-refund-amount):not(.shipping-refund):not(.restock-products):not(.refund-credit-slip):not(.refund-voucher):not(.voucher-refund-type), .cancel-product-selector',
},
},
printOrderViewPageButton: '.js-print-order-view-page',
orderNoteToggleBtn: '.js-order-notes-toggle-btn',
orderNoteBlock: '.js-order-notes-block',
orderNoteInput: '#internal_note_note',
orderNoteSubmitBtn: '.js-order-notes-btn',
refreshProductsListLoadingSpinner: '#orderProductsPanel .spinner-order-products-container#orderProductsLoading',
};

View File

@@ -0,0 +1,70 @@
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/OSL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
* versions in the future. If you wish to customize PrestaShop for your
* needs please refer to https://devdocs.prestashop.com/ for more information.
*
* @author PrestaShop SA and Contributors <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
*/
import CreateOrderPage from './create/create-order-page';
const {$} = window;
let orderPageManager = null;
/**
* proxy to allow other scripts within the page to trigger the search
* @param string
*/
function searchCustomerByString(string) {
if (orderPageManager !== null) {
orderPageManager.search(string);
} else {
console.log('Error: Could not search customer as orderPageManager is null');
}
}
/**
* proxy to allow other scripts within the page to refresh addresses list
*/
function refreshAddressesList(refreshCartAddresses) {
if (orderPageManager !== null) {
orderPageManager.refreshAddressesList(refreshCartAddresses);
} else {
console.log('Error: Could not refresh addresses list as orderPageManager is null');
}
}
/**
* proxy to allow other scripts within the Create Order page to refresh cart
*/
function refreshCart() {
if (orderPageManager === null) {
console.log('Error: Could not refresh addresses list as orderPageManager is null');
return;
}
orderPageManager.refreshCart();
}
$(document).ready(() => {
orderPageManager = new CreateOrderPage();
});
export {searchCustomerByString};
export {refreshAddressesList};
export {refreshCart};

View File

@@ -0,0 +1,52 @@
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/OSL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
* versions in the future. If you wish to customize PrestaShop for your
* needs please refer to https://devdocs.prestashop.com/ for more information.
*
* @author PrestaShop SA and Contributors <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
*/
/**
* Checks if correct addresses are selected.
* There is a case when options list cannot contain cart addresses 'selected' values
* because those are outdated in db (e.g. deleted after cart creation or country is disabled)
*
* @param {Array} addresses
*
* @returns {boolean}
*/
export const ValidateAddresses = (addresses) => {
let deliveryValid = false;
let invoiceValid = false;
addresses.forEach((address) => {
if (address.delivery) {
deliveryValid = true;
}
if (address.invoice) {
invoiceValid = true;
}
});
return deliveryValid && invoiceValid;
};
export default ValidateAddresses;

View File

@@ -0,0 +1,180 @@
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/OSL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
* versions in the future. If you wish to customize PrestaShop for your
* needs please refer to https://devdocs.prestashop.com/ for more information.
*
* @author PrestaShop SA and Contributors <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
*/
import createOrderMap from './create-order-map';
import Router from '../../../components/router';
const {$} = window;
/**
* Renders Delivery & Invoice addresses select
*/
export default class AddressesRenderer {
constructor() {
this.router = new Router();
}
/**
* @param {Array} addresses
* @param {int} cartId
*/
render(addresses, cartId) {
this.cleanAddresses();
if (addresses.length === 0) {
this.hideAddressesContent();
this.showEmptyAddressesWarning();
this.showAddressesBlock();
return;
}
this.showAddressesContent();
this.hideEmptyAddressesWarning();
Object.values(addresses).forEach((address) => {
this.renderDeliveryAddress(address, cartId);
this.renderInvoiceAddress(address, cartId);
});
this.showAddressesBlock();
}
/**
* Renders delivery address content
*
* @param address
* @param cartId
*
* @private
*/
renderDeliveryAddress(address, cartId) {
const deliveryAddressOption = {
value: address.addressId,
text: address.alias,
};
if (address.delivery) {
$(createOrderMap.deliveryAddressDetails).html(address.formattedAddress);
deliveryAddressOption.selected = 'selected';
$(createOrderMap.deliveryAddressEditBtn).prop('href', this.router.generate('admin_cart_addresses_edit', {
addressId: address.addressId,
cartId,
addressType: 'delivery',
liteDisplaying: 1,
submitFormAjax: 1,
}));
}
$(createOrderMap.deliveryAddressSelect).append($('<option>', deliveryAddressOption));
}
/**
* Renders invoice address content
*
* @param address
* @param cartId
*
* @private
*/
renderInvoiceAddress(address, cartId) {
const invoiceAddressOption = {
value: address.addressId,
text: address.alias,
};
if (address.invoice) {
$(createOrderMap.invoiceAddressDetails).html(address.formattedAddress);
invoiceAddressOption.selected = 'selected';
$(createOrderMap.invoiceAddressEditBtn).prop('href', this.router.generate('admin_cart_addresses_edit', {
addressId: address.addressId,
cartId,
addressType: 'invoice',
liteDisplaying: 1,
submitFormAjax: 1,
}));
}
$(createOrderMap.invoiceAddressSelect).append($('<option>', invoiceAddressOption));
}
/**
* Shows addresses block
*
* @private
*/
showAddressesBlock() {
$(createOrderMap.addressesBlock).removeClass('d-none');
}
/**
* Empties addresses content
*
* @private
*/
cleanAddresses() {
$(createOrderMap.deliveryAddressDetails).empty();
$(createOrderMap.deliveryAddressSelect).empty();
$(createOrderMap.invoiceAddressDetails).empty();
$(createOrderMap.invoiceAddressSelect).empty();
}
/**
* Shows addresses content and hides warning
*
* @private
*/
showAddressesContent() {
$(createOrderMap.addressesContent).removeClass('d-none');
$(createOrderMap.addressesWarning).addClass('d-none');
}
/**
* Hides addresses content and shows warning
*
* @private
*/
hideAddressesContent() {
$(createOrderMap.addressesContent).addClass('d-none');
$(createOrderMap.addressesWarning).removeClass('d-none');
}
/**
* Shows warning empty addresses warning
*
* @private
*/
showEmptyAddressesWarning() {
$(createOrderMap.addressesWarning).removeClass('d-none');
}
/**
* Hides empty addresses warning
*
* @private
*/
hideEmptyAddressesWarning() {
$(createOrderMap.addressesWarning).addClass('d-none');
}
}

View File

@@ -0,0 +1,219 @@
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/OSL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
* versions in the future. If you wish to customize PrestaShop for your
* needs please refer to https://devdocs.prestashop.com/ for more information.
*
* @author PrestaShop SA and Contributors <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
*/
import Router from '@components/router';
import {EventEmitter} from '@components/event-emitter';
import eventMap from '@pages/order/create/event-map';
import createOrderMap from './create-order-map';
const {$} = window;
/**
* Provides ajax calls for cart editing actions
* Each method emits an event with updated cart information after success.
*/
export default class CartEditor {
constructor() {
this.router = new Router();
}
/**
* Changes cart addresses
*
* @param {Number} cartId
* @param {Object} addresses
*/
changeCartAddresses(cartId, addresses) {
$.post(this.router.generate('admin_carts_edit_addresses', {cartId}), addresses)
.then((cartInfo) => EventEmitter.emit(eventMap.cartAddressesChanged, cartInfo))
.catch((response) => window.showErrorMessage(response.responseJSON.message));
}
/**
* Modifies cart delivery option
*
* @param {Number} cartId
* @param {Number} value
*/
changeDeliveryOption(cartId, value) {
$.post(this.router.generate('admin_carts_edit_carrier', {cartId}), {
carrierId: value,
}).then((cartInfo) => EventEmitter.emit(eventMap.cartDeliveryOptionChanged, cartInfo))
.catch((response) => window.showErrorMessage(response.responseJSON.message));
}
/**
* Changes cart free shipping value
*
* @param {Number} cartId
*/
updateDeliveryOptions(cartId) {
const freeShippingEnabled = $(createOrderMap.freeShippingSwitch)[1].checked;
const isAGiftEnabled = $(createOrderMap.isAGiftSwitchValue).val() === '1';
const useRecycledPackagingEnabled = $(createOrderMap.recycledPackagingSwitchValue).val() === '1';
const giftMessage = $(createOrderMap.giftMessageField).val();
$.post(this.router.generate('admin_carts_set_delivery_settings', {cartId}), {
freeShipping: freeShippingEnabled,
isAGift: isAGiftEnabled,
useRecycledPackaging: useRecycledPackagingEnabled,
giftMessage,
}).then((cartInfo) => EventEmitter.emit(eventMap.cartDeliverySettingChanged, cartInfo))
.catch((response) => window.showErrorMessage(response.responseJSON.message));
}
/**
* Adds cart rule to cart
*
* @param {Number} cartRuleId
* @param {Number} cartId
*/
addCartRuleToCart(cartRuleId, cartId) {
$.post(this.router.generate('admin_carts_add_cart_rule', {cartId}), {
cartRuleId,
}).then((cartInfo) => EventEmitter.emit(eventMap.cartRuleAdded, cartInfo))
.catch((response) => EventEmitter.emit(eventMap.cartRuleFailedToAdd, response.responseJSON.message));
}
/**
* Removes cart rule from cart
*
* @param {Number} cartRuleId
* @param {Number} cartId
*/
removeCartRuleFromCart(cartRuleId, cartId) {
$.post(this.router.generate('admin_carts_delete_cart_rule', {
cartId,
cartRuleId,
})).then((cartInfo) => EventEmitter.emit(eventMap.cartRuleRemoved, cartInfo))
.catch((response) => window.showErrorMessage(response.responseJSON.message));
}
/**
* Adds product to cart
*
* @param {Number} cartId
* @param {Object} data
*/
addProduct(cartId, data) {
let fileSizeHeader = '';
if (!$.isEmptyObject(data.fileSizes)) {
fileSizeHeader = JSON.stringify(data.fileSizes);
}
$.ajax(this.router.generate('admin_carts_add_product', {cartId}), {
headers: {
// Adds custom headers with submitted file sizes, to track if all files reached server side.
'file-sizes': fileSizeHeader,
},
method: 'POST',
data: data.product,
processData: false,
contentType: false,
}).then((cartInfo) => EventEmitter.emit(eventMap.productAddedToCart, cartInfo))
.catch((response) => EventEmitter.emit(eventMap.productAddToCartFailed, response.responseJSON.message));
}
/**
* Removes product from cart
*
* @param {Number} cartId
* @param {Object} product
*/
removeProductFromCart(cartId, product) {
$.post(this.router.generate('admin_carts_delete_product', {cartId}), {
productId: product.productId,
attributeId: product.attributeId,
customizationId: product.customizationId,
}).then((cartInfo) => EventEmitter.emit(eventMap.productRemovedFromCart, {cartInfo, product}))
.catch((response) => window.showErrorMessage(response.responseJSON.message));
}
/**
* Changes product price in cart
*
* @param {Number} cartId
* @param {Number} customerId
* @param {Object} product the updated product
*/
changeProductPrice(cartId, customerId, product) {
$.post(this.router.generate('admin_carts_edit_product_price', {
cartId,
productId: product.productId,
productAttributeId: product.attributeId,
}), {
newPrice: product.price,
customerId,
}).then((cartInfo) => EventEmitter.emit(eventMap.productPriceChanged, cartInfo))
.catch((response) => window.showErrorMessage(response.responseJSON.message));
}
/**
* Updates product quantity in cart
*
* @param cartId
* @param product
*/
changeProductQty(cartId, product) {
$.post(this.router.generate('admin_carts_edit_product_quantity', {
cartId,
productId: product.productId,
}), {
newQty: product.newQty,
attributeId: product.attributeId,
customizationId: product.customizationId,
}).then((cartInfo) => EventEmitter.emit(eventMap.productQtyChanged, {cartInfo, product}))
.catch((response) => EventEmitter.emit(eventMap.productQtyChangeFailed, response));
}
/**
* Changes cart currency
*
* @param {Number} cartId
* @param {Number} currencyId
*/
changeCartCurrency(cartId, currencyId) {
$(createOrderMap.cartCurrencySelect).data('selectedCurrencyId', currencyId);
$.post(this.router.generate('admin_carts_edit_currency', {cartId}), {
currencyId,
}).then((cartInfo) => EventEmitter.emit(eventMap.cartCurrencyChanged, cartInfo))
.catch((response) => EventEmitter.emit(eventMap.cartCurrencyChangeFailed, response));
}
/**
* Changes cart language
*
* @param {Number} cartId
* @param {Number} languageId
*/
changeCartLanguage(cartId, languageId) {
$.post(this.router.generate('admin_carts_edit_language', {cartId}), {
languageId,
}).then((cartInfo) => EventEmitter.emit(eventMap.cartLanguageChanged, cartInfo))
.catch((response) => window.showErrorMessage(response.responseJSON.message));
}
}

View File

@@ -0,0 +1,82 @@
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/OSL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
* versions in the future. If you wish to customize PrestaShop for your
* needs please refer to https://devdocs.prestashop.com/ for more information.
*
* @author PrestaShop SA and Contributors <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
*/
import createOrderPageMap from '@pages/order/create/create-order-map';
import Router from '@components/router';
import {EventEmitter} from '@components/event-emitter';
import eventMap from '@pages/order/create/event-map';
const {$} = window;
/**
* Provides ajax calls for getting cart information
*/
export default class CartProvider {
constructor() {
this.$container = $(createOrderPageMap.orderCreationContainer);
this.router = new Router();
}
/**
* Gets cart information
*
* @param cartId
*
* @returns {jqXHR}. Object with cart information in response.
*/
getCart(cartId) {
$.get(this.router.generate('admin_carts_info', {cartId})).then((cartInfo) => {
EventEmitter.emit(eventMap.cartLoaded, cartInfo);
});
}
/**
* Gets existing empty cart or creates new empty cart for customer.
*
* @param customerId
*
* @returns {jqXHR}. Object with cart information in response
*/
loadEmptyCart(customerId) {
$.post(this.router.generate('admin_carts_create'), {
customerId,
}).then((cartInfo) => {
EventEmitter.emit(eventMap.cartLoaded, cartInfo);
});
}
/**
* Duplicates cart from provided order
*
* @param orderId
*
* @returns {jqXHR}. Object with cart information in response
*/
duplicateOrderCart(orderId) {
$.post(this.router.generate('admin_orders_duplicate_cart', {orderId})).then((cartInfo) => {
EventEmitter.emit(eventMap.cartLoaded, cartInfo);
});
}
}

View File

@@ -0,0 +1,149 @@
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/OSL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
* versions in the future. If you wish to customize PrestaShop for your
* needs please refer to https://devdocs.prestashop.com/ for more information.
*
* @author PrestaShop SA and Contributors <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
*/
import CartEditor from '@pages/order/create/cart-editor';
import CartRulesRenderer from '@pages/order/create/cart-rules-renderer';
import {EventEmitter} from '@components/event-emitter';
import eventMap from '@pages/order/create/event-map';
import Router from '@components/router';
import SummaryRenderer from '@pages/order/create/summary-renderer';
import ShippingRenderer from '@pages/order/create/shipping-renderer';
import ProductRenderer from '@pages/order/create/product-renderer';
const {$} = window;
/**
* Responsible for searching cart rules and managing cart rules search block
*/
export default class CartRuleManager {
constructor() {
this.activeSearchRequest = null;
this.router = new Router();
this.cartRulesRenderer = new CartRulesRenderer();
this.cartEditor = new CartEditor();
this.summaryRenderer = new SummaryRenderer();
this.shippingRenderer = new ShippingRenderer();
this.productRenderer = new ProductRenderer();
this.initListeners();
return {
search: (searchPhrase) => this.search(searchPhrase),
stopSearching: () => this.cartRulesRenderer.hideResultsDropdown(),
addCartRuleToCart: (cartRuleId, cartId) => this.cartEditor.addCartRuleToCart(cartRuleId, cartId),
removeCartRuleFromCart: (cartRuleId, cartId) => this.cartEditor.removeCartRuleFromCart(cartRuleId, cartId),
};
}
/**
* Initiates event listeners for cart rule actions
*
* @private
*/
initListeners() {
this.onCartRuleSearch();
this.onAddCartRuleToCart();
this.onAddCartRuleToCartFailure();
this.onRemoveCartRuleFromCart();
}
/**
* Listens for cart rule search action
*
* @private
*/
onCartRuleSearch() {
EventEmitter.on(eventMap.cartRuleSearched, (cartRules) => {
this.cartRulesRenderer.renderSearchResults(cartRules);
});
}
/**
* Listens event of add cart rule to cart action
*
* @private
*/
onAddCartRuleToCart() {
EventEmitter.on(eventMap.cartRuleAdded, (cartInfo) => {
const cartIsEmpty = cartInfo.products.length === 0;
this.cartRulesRenderer.renderCartRulesBlock(cartInfo.cartRules, cartIsEmpty);
this.productRenderer.renderList(cartInfo.products);
this.shippingRenderer.render(cartInfo.shipping, cartIsEmpty);
this.summaryRenderer.render(cartInfo);
});
}
/**
* Listens event when add cart rule to cart fails
*
* @private
*/
onAddCartRuleToCartFailure() {
EventEmitter.on(eventMap.cartRuleFailedToAdd, (message) => {
this.cartRulesRenderer.displayErrorMessage(message);
});
}
/**
* Listens event for remove cart rule from cart action
*
* @private
*/
onRemoveCartRuleFromCart() {
EventEmitter.on(eventMap.cartRuleRemoved, (cartInfo) => {
const cartIsEmpty = cartInfo.products.length === 0;
this.shippingRenderer.render(cartInfo.shipping, cartIsEmpty);
this.cartRulesRenderer.renderCartRulesBlock(cartInfo.cartRules, cartIsEmpty);
this.summaryRenderer.render(cartInfo);
this.productRenderer.renderList(cartInfo.products);
});
}
/**
* Searches for cart rules by search phrase
*
* @private
*/
search(searchPhrase) {
if (this.activeSearchRequest !== null) {
this.activeSearchRequest.abort();
}
this.activeSearchRequest = $.get(this.router.generate('admin_cart_rules_search'), {
search_phrase: searchPhrase,
});
this.activeSearchRequest.then((cartRules) => {
EventEmitter.emit(eventMap.cartRuleSearched, cartRules);
}).catch((e) => {
if (e.statusText === 'abort') {
return;
}
window.showErrorMessage(e.responseJSON.message);
});
}
}

View File

@@ -0,0 +1,238 @@
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/OSL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
* versions in the future. If you wish to customize PrestaShop for your
* needs please refer to https://devdocs.prestashop.com/ for more information.
*
* @author PrestaShop SA and Contributors <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
*/
import createOrderMap from './create-order-map';
const {$} = window;
/**
* Renders cart rules (cartRules) block
*/
export default class CartRulesRenderer {
constructor() {
this.$cartRulesBlock = $(createOrderMap.cartRulesBlock);
this.$cartRulesTable = $(createOrderMap.cartRulesTable);
this.$searchResultBox = $(createOrderMap.cartRulesSearchResultBox);
}
/**
* Responsible for rendering cartRules (a.k.a cart rules/discounts) block
*
* @param {Array} cartRules
* @param {Boolean} emptyCart
*/
renderCartRulesBlock(cartRules, emptyCart) {
this.hideErrorBlock();
// do not render cart rules block at all if cart has no products
if (emptyCart) {
this.hideCartRulesBlock();
return;
}
this.showCartRulesBlock();
// do not render cart rules list when there are no cart rules
if (cartRules.length === 0) {
this.hideCartRulesList();
return;
}
this.renderList(cartRules);
}
/**
* Responsible for rendering search results dropdown
*
* @param searchResults
*/
renderSearchResults(searchResults) {
this.clearSearchResults();
if (searchResults.cart_rules.length === 0) {
this.renderNotFound();
} else {
this.renderFoundCartRules(searchResults.cart_rules);
}
this.showResultsDropdown();
}
/**
* Displays error message bellow search input
*
* @param message
*/
displayErrorMessage(message) {
$(createOrderMap.cartRuleErrorText).text(message);
this.showErrorBlock();
}
/**
* Hides cart rules search result dropdown
*/
hideResultsDropdown() {
this.$searchResultBox.addClass('d-none');
}
/**
* Displays cart rules search result dropdown
*
* @private
*/
showResultsDropdown() {
this.$searchResultBox.removeClass('d-none');
}
/**
* Renders warning that no cart rule was found
*
* @private
*/
renderNotFound() {
const $template = $($(createOrderMap.cartRulesNotFoundTemplate).html()).clone();
this.$searchResultBox.html($template);
}
/**
* Empties cart rule search results block
*
* @private
*/
clearSearchResults() {
this.$searchResultBox.empty();
}
/**
* Renders found cart rules after search
*
* @param cartRules
*
* @private
*/
renderFoundCartRules(cartRules) {
const $cartRuleTemplate = $($(createOrderMap.foundCartRuleTemplate).html());
Object.values(cartRules).forEach((cartRule) => {
const $template = $cartRuleTemplate.clone();
let cartRuleName = cartRule.name;
if (cartRule.code !== '') {
cartRuleName = `${cartRule.name} - ${cartRule.code}`;
}
$template.text(cartRuleName);
$template.data('cart-rule-id', cartRule.cartRuleId);
this.$searchResultBox.append($template);
});
}
/**
* Responsible for rendering the list of cart rules
*
* @param {Array} cartRules
*
* @private
*/
renderList(cartRules) {
this.cleanCartRulesList();
const $cartRulesTableRowTemplate = $($(createOrderMap.cartRulesTableRowTemplate).html());
Object.values(cartRules).forEach((cartRule) => {
const $template = $cartRulesTableRowTemplate.clone();
$template.find(createOrderMap.cartRuleNameField).text(cartRule.name);
$template.find(createOrderMap.cartRuleDescriptionField).text(cartRule.description);
$template.find(createOrderMap.cartRuleValueField).text(cartRule.value);
$template.find(createOrderMap.cartRuleDeleteBtn).data('cart-rule-id', cartRule.cartRuleId);
this.$cartRulesTable.find('tbody').append($template);
});
this.showCartRulesList();
}
/**
* Shows error block
*
* @private
*/
showErrorBlock() {
$(createOrderMap.cartRuleErrorBlock).removeClass('d-none');
}
/**
* Hides error block
*
* @private
*/
hideErrorBlock() {
$(createOrderMap.cartRuleErrorBlock).addClass('d-none');
}
/**
* Shows cartRules block
*
* @private
*/
showCartRulesBlock() {
this.$cartRulesBlock.removeClass('d-none');
}
/**
* hide cartRules block
*
* @private
*/
hideCartRulesBlock() {
this.$cartRulesBlock.addClass('d-none');
}
/**
* Display the list block of cart rules
*
* @private
*/
showCartRulesList() {
this.$cartRulesTable.removeClass('d-none');
}
/**
* Hide list block of cart rules
*
* @private
*/
hideCartRulesList() {
this.$cartRulesTable.addClass('d-none');
}
/**
* remove items in cart rules list
*
* @private
*/
cleanCartRulesList() {
this.$cartRulesTable.find('tbody').empty();
}
}

View File

@@ -0,0 +1,184 @@
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/OSL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
* versions in the future. If you wish to customize PrestaShop for your
* needs please refer to https://devdocs.prestashop.com/ for more information.
*
* @author PrestaShop SA and Contributors <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
*/
/**
* Encapsulates selectors for "Create order" page
*/
export default {
productCustomizationFieldTypeFile: 0,
productCustomizationFieldTypeText: 1,
orderCreationContainer: '#order-creation-container',
requiredFieldMark: '.js-required-field-mark',
cartInfoWrapper: '#js-cart-info-wrapper',
// selectors related to customer block
customerSearchInput: '#customer-search-input',
customerSearchResultsBlock: '.js-customer-search-results',
customerSearchResultTemplate: '#customer-search-result-template',
customerSearchEmptyResultWarning: '#customer-search-empty-result-warn',
customerSearchLoadingNotice: '#customer-search-loading-notice',
customerAddBtn: '#customer-add-btn',
changeCustomerBtn: '.js-change-customer-btn',
customerSearchRow: '.js-search-customer-row',
chooseCustomerBtn: '.js-choose-customer-btn',
notSelectedCustomerSearchResults: '.js-customer-search-result:not(.border-success)',
customerSearchResultName: '.js-customer-name',
customerSearchResultEmail: '.js-customer-email',
customerSearchResultId: '.js-customer-id',
customerSearchResultBirthday: '.js-customer-birthday',
customerDetailsBtn: '.js-details-customer-btn',
customerSearchResultColumn: '.js-customer-search-result-col',
customerSearchBlock: '#customer-search-block',
customerCartsTab: '.js-customer-carts-tab',
customerOrdersTab: '.js-customer-orders-tab',
customerCartsTable: '#customer-carts-table',
customerCartsTableRowTemplate: '#customer-carts-table-row-template',
customerCheckoutHistory: '#customer-checkout-history',
customerOrdersTable: '#customer-orders-table',
customerOrdersTableRowTemplate: '#customer-orders-table-row-template',
cartRulesTable: '#cart-rules-table',
cartRulesTableRowTemplate: '#cart-rules-table-row-template',
useCartBtn: '.js-use-cart-btn',
cartDetailsBtn: '.js-cart-details-btn',
cartIdField: '.js-cart-id',
cartDateField: '.js-cart-date',
cartTotalField: '.js-cart-total',
useOrderBtn: '.js-use-order-btn',
orderDetailsBtn: '.js-order-details-btn',
orderIdField: '.js-order-id',
orderDateField: '.js-order-date',
orderProductsField: '.js-order-products',
orderTotalField: '.js-order-total-paid',
orderPaymentMethod: '.js-order-payment-method',
orderStatusField: '.js-order-status',
emptyListRowTemplate: '#js-empty-list-row',
loadingListRowTemplate: '#js-loading-list-row',
emptyListRow: '.js-empty-row',
// selectors related to cartRules block
cartRulesBlock: '#cart-rules-block',
cartRuleSearchInput: '#search-cart-rules-input',
cartRulesSearchResultBox: '#search-cart-rules-result-box',
cartRulesNotFoundTemplate: '#cart-rules-not-found-template',
foundCartRuleTemplate: '#found-cart-rule-template',
foundCartRuleListItem: '.js-found-cart-rule',
cartRuleNameField: '.js-cart-rule-name',
cartRuleDescriptionField: '.js-cart-rule-description',
cartRuleValueField: '.js-cart-rule-value',
cartRuleDeleteBtn: '.js-cart-rule-delete-btn',
cartRuleErrorBlock: '#js-cart-rule-error-block',
cartRuleErrorText: '#js-cart-rule-error-text',
// selectors related to addresses block
addressesBlock: '#addresses-block',
deliveryAddressDetails: '#delivery-address-details',
invoiceAddressDetails: '#invoice-address-details',
deliveryAddressSelect: '#delivery-address-select',
invoiceAddressSelect: '#invoice-address-select',
addressSelect: '.js-address-select',
addressesContent: '#addresses-content',
addressesWarning: '#addresses-warning',
deliveryAddressEditBtn: '#js-delivery-address-edit-btn',
invoiceAddressEditBtn: '#js-invoice-address-edit-btn',
addressAddBtn: '#js-add-address-btn',
// selectors related to summary block
summaryBlock: '#summary-block',
summaryTotalProducts: '.js-total-products',
summaryTotalDiscount: '.js-total-discounts',
summaryTotalShipping: '.js-total-shipping',
summaryTotalTaxes: '.js-total-taxes',
summaryTotalWithoutTax: '.js-total-without-tax',
summaryTotalWithTax: '.js-total-with-tax',
placeOrderCartIdField: '.js-place-order-cart-id',
processOrderLinkTag: '#js-process-order-link',
orderMessageField: '#js-order-message-wrap textarea',
sendProcessOrderEmailBtn: '#js-send-process-order-email-btn',
summarySuccessAlertBlock: '#js-summary-success-block',
summaryErrorAlertBlock: '#js-summary-error-block',
summarySuccessAlertText: '#js-summary-success-block .alert-text',
summaryErrorAlertText: '#js-summary-error-block .alert-text',
// selectors related to shipping block
shippingBlock: '#shipping-block',
shippingForm: '.js-shipping-form',
noCarrierBlock: '.js-no-carrier-block',
deliveryOptionSelect: '#delivery-option-select',
totalShippingField: '.js-total-shipping-tax-inc',
freeShippingSwitch: '.js-free-shipping-switch',
recycledPackagingSwitch: '.js-recycled-packaging-switch',
recycledPackagingSwitchValue: '.js-recycled-packaging-switch:checked',
isAGiftSwitch: '.js-is-gift-switch',
isAGiftSwitchValue: '.js-is-gift-switch:checked',
giftMessageField: '#cart_gift_message',
// selectors related to cart block
cartBlock: '#cart-block',
cartCurrencySelect: '#js-cart-currency-select',
cartLanguageSelect: '#js-cart-language-select',
productSearch: '#product-search',
combinationsSelect: '#combination-select',
productResultBlock: '#product-search-results',
productSelect: '#product-select',
quantityInput: '#quantity-input',
inStockCounter: '.js-in-stock-counter',
combinationsTemplate: '#combinations-template',
combinationsRow: '.js-combinations-row',
productSelectRow: '.js-product-select-row',
productCustomFieldsContainer: '#js-custom-fields-container',
productCustomizationContainer: '#js-customization-container',
productCustomFileTemplate: '#js-product-custom-file-template',
productCustomTextTemplate: '#js-product-custom-text-template',
productCustomInputLabel: '.js-product-custom-input-label',
productCustomInput: '.js-product-custom-input',
quantityRow: '.js-quantity-row',
addToCartButton: '#add-product-to-cart-btn',
productsTable: '#products-table',
productsTableRowTemplate: '#products-table-row-template',
productsTableGiftRowTemplate: '#products-table-gift-row-template',
listedProductImageField: '.js-product-image',
listedProductNameField: '.js-product-name',
listedProductAttrField: '.js-product-attr',
listedProductReferenceField: '.js-product-ref',
listedProductUnitPriceInput: '.js-product-unit-input',
listedProductQtyInput: '.js-product-qty-input',
listedProductQtyStock: '.js-product-qty-stock',
listedProductGiftQty: '.js-product-gift-qty',
productTotalPriceField: '.js-product-total-price',
listedProductCustomizedTextTemplate: '#js-table-product-customized-text-template',
listedProductCustomizedFileTemplate: '#js-table-product-customized-file-template',
listedProductCustomizationName: '.js-customization-name',
listedProductCustomizationValue: '.js-customization-value',
listedProductDefinition: '.js-product-definition-td',
productRemoveBtn: '.js-product-remove-btn',
productTaxWarning: '.js-tax-warning',
noProductsFoundWarning: '.js-no-products-found',
searchingProductsNotice: '.js-searching-products',
productAddForm: '#js-add-product-form',
cartErrorAlertBlock: '#js-cart-error-block',
cartErrorAlertText: '#js-cart-error-block .alert-text',
createOrderButton: '#create-order-button',
};

View File

@@ -0,0 +1,644 @@
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/OSL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
* versions in the future. If you wish to customize PrestaShop for your
* needs please refer to https://devdocs.prestashop.com/ for more information.
*
* @author PrestaShop SA and Contributors <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
*/
import Router from '@components/router';
import {EventEmitter} from '@components/event-emitter';
import _ from 'lodash';
import createOrderMap from './create-order-map';
import CustomerManager from './customer-manager';
import ShippingRenderer from './shipping-renderer';
import CartProvider from './cart-provider';
import AddressesRenderer from './addresses-renderer';
import CartRulesRenderer from './cart-rules-renderer';
import CartEditor from './cart-editor';
import eventMap from './event-map';
import CartRuleManager from './cart-rule-manager';
import ProductManager from './product-manager';
import ProductRenderer from './product-renderer';
import SummaryRenderer from './summary-renderer';
import SummaryManager from './summary-manager';
import {ValidateAddresses} from './address-validator';
const {$} = window;
/**
* Page Object for "Create order" page
*/
export default class CreateOrderPage {
constructor() {
this.cartId = null;
this.customerId = null;
this.$container = $(createOrderMap.orderCreationContainer);
this.cartProvider = new CartProvider();
this.customerManager = new CustomerManager();
this.shippingRenderer = new ShippingRenderer();
this.addressesRenderer = new AddressesRenderer();
this.cartRulesRenderer = new CartRulesRenderer();
this.router = new Router();
this.cartEditor = new CartEditor();
this.cartRuleManager = new CartRuleManager();
this.productManager = new ProductManager();
this.productRenderer = new ProductRenderer();
this.summaryRenderer = new SummaryRenderer();
this.summaryManager = new SummaryManager();
this.initListeners();
this.loadCartFromUrlParams();
return {
refreshAddressesList: (refreshCartAddresses) => this.refreshAddressesList(refreshCartAddresses),
refreshCart: (refreshCart) => this.refreshCart(refreshCart),
search: (string) => this.customerManager.search(string),
};
}
/**
* Checks if correct addresses are selected.
* There is a case when options list cannot contain cart addresses 'selected' values
* because those are outdated in db (e.g. deleted after cart creation or country is disabled)
*
* @param {Array} addresses
*
* @returns {boolean}
*/
static validateSelectedAddresses(addresses) {
let deliveryValid = false;
let invoiceValid = false;
const keys = Object.keys(addresses);
for (let i = 0; i < keys.length; i += 1) {
const address = addresses[keys[i]];
if (address.delivery) {
deliveryValid = true;
}
if (address.invoice) {
invoiceValid = true;
}
if (deliveryValid && invoiceValid) {
return true;
}
}
return false;
}
/**
* Hides whole cart information wrapper
*/
hideCartInfo() {
$(createOrderMap.cartInfoWrapper).addClass('d-none');
}
/**
* Shows whole cart information wrapper
*/
showCartInfo() {
$(createOrderMap.cartInfoWrapper).removeClass('d-none');
}
/**
* Loads cart if query params contains valid cartId
*
* @private
*/
loadCartFromUrlParams() {
const urlParams = new URLSearchParams(window.location.search);
const cartId = Number(urlParams.get('cartId'));
if (!Number.isNaN(cartId) && cartId !== 0) {
this.cartProvider.getCart(cartId);
}
}
/**
* Initializes event listeners
*
* @private
*/
initListeners() {
this.$container.on('input', createOrderMap.customerSearchInput, (e) => this.initCustomerSearch(e));
this.$container.on('click', createOrderMap.chooseCustomerBtn, (e) => this.initCustomerSelect(e));
this.$container.on('click', createOrderMap.useCartBtn, (e) => this.initCartSelect(e));
this.$container.on('click', createOrderMap.useOrderBtn, (e) => this.initDuplicateOrderCart(e));
this.$container.on('input', createOrderMap.productSearch, (e) => this.initProductSearch(e));
this.$container.on('input', createOrderMap.cartRuleSearchInput, (e) => this.initCartRuleSearch(e));
this.$container.on('blur', createOrderMap.cartRuleSearchInput, () => this.cartRuleManager.stopSearching());
this.listenForCartEdit();
this.onCartLoaded();
this.onCustomersNotFound();
this.onCustomerSelected();
this.initAddressButtonsIframe();
this.initCartRuleButtonsIframe();
}
/**
* @private
*/
initAddressButtonsIframe() {
$(createOrderMap.addressAddBtn).fancybox({
type: 'iframe',
width: '90%',
height: '90%',
});
$(createOrderMap.invoiceAddressEditBtn).fancybox({
type: 'iframe',
width: '90%',
height: '90%',
});
$(createOrderMap.deliveryAddressEditBtn).fancybox({
type: 'iframe',
width: '90%',
height: '90%',
});
}
initCartRuleButtonsIframe() {
$('#js-add-cart-rule-btn').fancybox({
type: 'iframe',
width: '90%',
height: '90%',
});
}
/**
* Delegates actions to events associated with cart update (e.g. change cart address)
*
* @private
*/
listenForCartEdit() {
this.onCartAddressesChanged();
this.onDeliveryOptionChanged();
this.onDeliverySettingChanged();
this.addCartRuleToCart();
this.removeCartRuleFromCart();
this.onCartCurrencyChanged();
this.onCartLanguageChanged();
this.$container.on(
'change',
createOrderMap.deliveryOptionSelect,
(e) => this.cartEditor.changeDeliveryOption(this.cartId, e.currentTarget.value),
);
this.$container.on(
'change',
createOrderMap.freeShippingSwitch,
() => this.cartEditor.updateDeliveryOptions(this.cartId),
);
this.$container.on(
'change',
createOrderMap.recycledPackagingSwitch,
() => this.cartEditor.updateDeliveryOptions(this.cartId),
);
this.$container.on(
'change',
createOrderMap.isAGiftSwitch,
() => this.cartEditor.updateDeliveryOptions(this.cartId),
);
this.$container.on(
'blur',
createOrderMap.giftMessageField,
() => this.cartEditor.updateDeliveryOptions(this.cartId),
);
this.$container.on(
'click',
createOrderMap.addToCartButton,
() => this.productManager.addProductToCart(this.cartId),
);
this.$container.on(
'change',
createOrderMap.cartCurrencySelect,
(e) => this.cartEditor.changeCartCurrency(this.cartId, e.currentTarget.value),
);
this.$container.on(
'change',
createOrderMap.cartLanguageSelect,
(e) => this.cartEditor.changeCartLanguage(this.cartId, e.currentTarget.value),
);
this.$container.on(
'click',
createOrderMap.sendProcessOrderEmailBtn,
() => this.summaryManager.sendProcessOrderEmail(this.cartId),
);
this.$container.on('change', createOrderMap.listedProductUnitPriceInput, (e) => this.initProductChangePrice(e));
this.$container.on(
'change',
createOrderMap.listedProductQtyInput,
_.debounce((e) => {
const inputsQty = document.querySelectorAll(createOrderMap.listedProductQtyInput);
inputsQty.forEach((inputQty) => {
inputQty.setAttribute('disabled', true);
});
this.initProductChangeQty(e);
}, 500),
);
this.$container.on('change', createOrderMap.addressSelect, () => this.changeCartAddresses());
this.$container.on('click', createOrderMap.productRemoveBtn, (e) => this.initProductRemoveFromCart(e));
}
/**
* Listens for event when cart is loaded
*
* @private
*/
onCartLoaded() {
EventEmitter.on(eventMap.cartLoaded, (cartInfo) => {
this.cartId = cartInfo.cartId;
this.renderCartInfo(cartInfo);
if (cartInfo.addresses.length !== 0 && !ValidateAddresses(cartInfo.addresses)) {
this.changeCartAddresses();
}
this.customerManager.loadCustomerCarts(this.cartId);
this.customerManager.loadCustomerOrders();
});
}
/**
* Listens for event when no customers were found by search
*
* @private
*/
onCustomersNotFound() {
EventEmitter.on(eventMap.customersNotFound, () => {
this.hideCartInfo();
});
}
/**
* Listens for event when customer is selected
*
* @private
*/
onCustomerSelected() {
EventEmitter.on(eventMap.customerSelected, () => {
this.showCartInfo();
});
}
/**
* Listens for cart addresses update event
*
* @private
*/
onCartAddressesChanged() {
EventEmitter.on(eventMap.cartAddressesChanged, (cartInfo) => {
this.addressesRenderer.render(cartInfo.addresses, cartInfo.cartId);
this.cartRulesRenderer.renderCartRulesBlock(cartInfo.cartRules, cartInfo.products.length === 0);
this.shippingRenderer.render(cartInfo.shipping, cartInfo.products.length === 0);
this.productRenderer.renderList(cartInfo.products);
this.summaryRenderer.render(cartInfo);
});
}
/**
* Listens for cart delivery option update event
*
* @private
*/
onDeliveryOptionChanged() {
EventEmitter.on(eventMap.cartDeliveryOptionChanged, (cartInfo) => {
this.cartRulesRenderer.renderCartRulesBlock(cartInfo.cartRules, cartInfo.products.length === 0);
this.shippingRenderer.render(cartInfo.shipping, cartInfo.products.length === 0);
this.summaryRenderer.render(cartInfo);
this.productRenderer.renderList(cartInfo.products);
});
}
/**
* Listens for cart delivery setting update event
*
* @private
*/
onDeliverySettingChanged() {
EventEmitter.on(eventMap.cartDeliverySettingChanged, (cartInfo) => {
this.cartRulesRenderer.renderCartRulesBlock(cartInfo.cartRules, cartInfo.products.length === 0);
this.shippingRenderer.render(cartInfo.shipping, cartInfo.products.length === 0);
this.summaryRenderer.render(cartInfo);
});
}
/**
* Listens for cart language update event
*
* @private
*/
onCartLanguageChanged() {
EventEmitter.on(eventMap.cartLanguageChanged, (cartInfo) => {
this.preselectCartLanguage(cartInfo.langId);
this.renderCartInfo(cartInfo);
});
}
/**
* Listens for cart currency update event
*
* @private
*/
onCartCurrencyChanged() {
// on success
EventEmitter.on(eventMap.cartCurrencyChanged, (cartInfo) => {
this.renderCartInfo(cartInfo);
this.productRenderer.reset();
});
// on failure
EventEmitter.on(eventMap.cartCurrencyChangeFailed, (response) => {
this.productRenderer.renderCartBlockErrorAlert(response.responseJSON.message);
});
}
/**
* Init customer searching
*
* @param event
*
* @private
*/
initCustomerSearch(event) {
clearTimeout(this.timeoutId);
this.timeoutId = setTimeout(() => this.customerManager.search($(event.currentTarget).val()), 300);
}
/**
* Init selecting customer for which order is being created
*
* @param event
*
* @private
*/
initCustomerSelect(event) {
const customerId = this.customerManager.selectCustomer(event);
this.customerId = customerId;
this.cartProvider.loadEmptyCart(customerId);
}
/**
* Inits selecting cart to load
*
* @param event
*
* @private
*/
initCartSelect(event) {
const cartId = $(event.currentTarget).data('cart-id');
this.cartProvider.getCart(cartId);
}
/**
* Inits duplicating order cart
*
* @private
*/
initDuplicateOrderCart(event) {
const orderId = $(event.currentTarget).data('order-id');
this.cartProvider.duplicateOrderCart(orderId);
}
/**
* Triggers cart rule searching
*
* @private
*/
initCartRuleSearch(event) {
const searchPhrase = event.currentTarget.value;
clearTimeout(this.timeoutId);
this.timeoutId = setTimeout(() => this.cartRuleManager.search(searchPhrase), 300);
}
/**
* Triggers cart rule select
*
* @private
*/
addCartRuleToCart() {
this.$container.on('mousedown', createOrderMap.foundCartRuleListItem, (event) => {
// prevent blur event to allow selecting cart rule
event.preventDefault();
const cartRuleId = $(event.currentTarget).data('cart-rule-id');
this.cartRuleManager.addCartRuleToCart(cartRuleId, this.cartId);
// manually fire blur event after cart rule is selected.
})
.on('click', createOrderMap.foundCartRuleListItem, () => {
$(createOrderMap.cartRuleSearchInput).blur();
});
}
/**
* Triggers cart rule removal from cart
*
* @private
*/
removeCartRuleFromCart() {
this.$container.on('click', createOrderMap.cartRuleDeleteBtn, (event) => {
this.cartRuleManager.removeCartRuleFromCart($(event.currentTarget).data('cart-rule-id'), this.cartId);
});
}
/**
* Inits product searching
*
* @param event
*
* @private
*/
initProductSearch(event) {
const $productSearchInput = $(event.currentTarget);
const searchPhrase = $productSearchInput.val();
clearTimeout(this.timeoutId);
this.timeoutId = setTimeout(() => this.productManager.search(searchPhrase), 300);
}
/**
* Inits product removing from cart
*
* @param event
*
* @private
*/
initProductRemoveFromCart(event) {
const productQty = Number($(event.currentTarget).parents().find(createOrderMap.listedProductQtyInput).val());
const product = {
productId: $(event.currentTarget).data('product-id'),
attributeId: $(event.currentTarget).data('attribute-id'),
customizationId: $(event.currentTarget).data('customization-id'),
qtyToRemove: productQty,
};
this.productManager.removeProductFromCart(this.cartId, product);
}
/**
* Inits product in cart price change
*
* @param event
*
* @private
*/
initProductChangePrice(event) {
const product = {
productId: $(event.currentTarget).data('product-id'),
attributeId: $(event.currentTarget).data('attribute-id'),
customizationId: $(event.currentTarget).data('customization-id'),
price: $(event.currentTarget).val(),
};
this.productManager.changeProductPrice(this.cartId, this.customerId, product);
}
/**
* Inits product in cart quantity update
*
* @param event
*
* @private
*/
initProductChangeQty(event) {
const product = {
productId: $(event.currentTarget).data('product-id'),
attributeId: $(event.currentTarget).data('attribute-id'),
customizationId: $(event.currentTarget).data('customization-id'),
newQty: $(event.currentTarget).val(),
prevQty: $(event.currentTarget).data('prev-qty'),
};
if (
typeof product.productId !== 'undefined'
&& product.productId !== null
&& typeof product.attributeId !== 'undefined'
&& product.attributeId !== null
) {
this.productManager.changeProductQty(this.cartId, product);
} else {
const inputsQty = document.querySelectorAll(createOrderMap.listedProductQtyInput);
inputsQty.forEach((inputQty) => {
inputQty.disabled = false;
});
}
}
/**
* Renders cart summary on the page
*
* @param {Object} cartInfo
*
* @private
*/
renderCartInfo(cartInfo) {
this.addressesRenderer.render(cartInfo.addresses, cartInfo.cartId);
this.cartRulesRenderer.renderCartRulesBlock(cartInfo.cartRules, cartInfo.products.length === 0);
this.shippingRenderer.render(cartInfo.shipping, cartInfo.products.length === 0);
this.productRenderer.cleanCartBlockAlerts();
this.productRenderer.renderList(cartInfo.products);
this.summaryRenderer.render(cartInfo);
this.preselectCartCurrency(cartInfo.currencyId);
this.preselectCartLanguage(cartInfo.langId);
$(createOrderMap.cartBlock).removeClass('d-none');
$(createOrderMap.cartBlock).data('cartId', cartInfo.cartId);
}
/**
* Sets cart currency selection value
*
* @param currencyId
*
* @private
*/
preselectCartCurrency(currencyId) {
$(createOrderMap.cartCurrencySelect).val(currencyId);
}
/**
* Sets cart language selection value
*
* @param langId
*
* @private
*/
preselectCartLanguage(langId) {
$(createOrderMap.cartLanguageSelect).val(langId);
}
/**
* Changes cart addresses
*
* @private
*/
changeCartAddresses() {
const addresses = {
deliveryAddressId: $(createOrderMap.deliveryAddressSelect).val(),
invoiceAddressId: $(createOrderMap.invoiceAddressSelect).val(),
};
this.cartEditor.changeCartAddresses(this.cartId, addresses);
}
/**
* Refresh addresses list
*
* @param {boolean} refreshCartAddresses optional
*
* @private
*/
refreshAddressesList(refreshCartAddresses) {
const cartId = $(createOrderMap.cartBlock).data('cartId');
$.get(this.router.generate('admin_carts_info', {cartId})).then((cartInfo) => {
this.addressesRenderer.render(cartInfo.addresses, cartInfo.cartId);
if (refreshCartAddresses) {
this.changeCartAddresses();
}
}).catch((e) => {
window.showErrorMessage(e.responseJSON.message);
});
}
/**
* proxy to allow other scripts within the page to refresh addresses list
*/
refreshCart() {
const cartId = $(createOrderMap.cartBlock).data('cartId');
this.cartProvider.getCart(cartId);
}
}

View File

@@ -0,0 +1,220 @@
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/OSL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
* versions in the future. If you wish to customize PrestaShop for your
* needs please refer to https://devdocs.prestashop.com/ for more information.
*
* @author PrestaShop SA and Contributors <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
*/
import createOrderMap from '@pages/order/create/create-order-map';
import CustomerRenderer from '@pages/order/create/customer-renderer';
import {EventEmitter} from '@components/event-emitter';
import eventMap from '@pages/order/create/event-map';
import Router from '@components/router';
const {$} = window;
/**
* Responsible for customers managing. (search, select, get customer info etc.)
*/
export default class CustomerManager {
constructor() {
this.customerId = null;
this.activeSearchRequest = null;
this.router = new Router();
this.$container = $(createOrderMap.customerSearchBlock);
this.$searchInput = $(createOrderMap.customerSearchInput);
this.$customerSearchResultBlock = $(createOrderMap.customerSearchResultsBlock);
this.customerRenderer = new CustomerRenderer();
this.initListeners();
this.initAddCustomerIframe();
return {
search: (searchPhrase) => this.search(searchPhrase),
selectCustomer: (event) => this.selectCustomer(event),
loadCustomerCarts: (currentCartId) => this.loadCustomerCarts(currentCartId),
loadCustomerOrders: () => this.loadCustomerOrders(),
};
}
/**
* Initializes event listeners
*
* @private
*/
initListeners() {
this.$container.on('click', createOrderMap.changeCustomerBtn, () => this.changeCustomer());
this.onCustomerSearch();
this.onCustomerSelect();
this.onCustomersNotFound();
}
/**
* @private
*/
initAddCustomerIframe() {
$(createOrderMap.customerAddBtn).fancybox({
type: 'iframe',
width: '90%',
height: '90%',
});
}
/**
* Listens for customer search event
*
* @private
*/
onCustomerSearch() {
EventEmitter.on(eventMap.customerSearched, (response) => {
this.activeSearchRequest = null;
this.customerRenderer.hideSearchingCustomers();
if (response.customers.length === 0) {
EventEmitter.emit(eventMap.customersNotFound);
return;
}
this.customerRenderer.renderSearchResults(response.customers);
});
}
/**
* Listens for event of when no customers were found by search
*
* @private
*/
onCustomersNotFound() {
EventEmitter.on(eventMap.customersNotFound, () => {
this.customerRenderer.showNotFoundCustomers();
this.customerRenderer.hideCheckoutHistoryBlock();
});
}
/**
* Listens for customer select event
*
* @private
*/
onCustomerSelect() {
EventEmitter.on(eventMap.customerSelected, (event) => {
const $chooseBtn = $(event.currentTarget);
this.customerId = $chooseBtn.data('customer-id');
const createAddressUrl = this.router.generate(
'admin_addresses_create',
{
liteDisplaying: 1,
submitFormAjax: 1,
id_customer: this.customerId,
},
);
$(createOrderMap.addressAddBtn).attr('href', createAddressUrl);
this.customerRenderer.displaySelectedCustomerBlock($chooseBtn);
});
}
/**
* Handles use case when customer is changed
*
* @private
*/
changeCustomer() {
this.customerRenderer.showCustomerSearch();
}
/**
* Loads customer carts list
*
* @param currentCartId
*/
loadCustomerCarts(currentCartId) {
const {customerId} = this;
this.customerRenderer.showLoadingCarts();
$.get(this.router.generate('admin_customers_carts', {customerId})).then((response) => {
this.customerRenderer.renderCarts(response.carts, currentCartId);
}).catch((e) => {
window.showErrorMessage(e.responseJSON.message);
});
}
/**
* Loads customer orders list
*/
loadCustomerOrders() {
const {customerId} = this;
this.customerRenderer.showLoadingOrders();
$.get(this.router.generate('admin_customers_orders', {customerId})).then((response) => {
this.customerRenderer.renderOrders(response.orders);
}).catch((e) => {
window.showErrorMessage(e.responseJSON.message);
});
}
/**
* @param {Event} chooseCustomerEvent
*
* @return {Number}
*/
selectCustomer(chooseCustomerEvent) {
EventEmitter.emit(eventMap.customerSelected, chooseCustomerEvent);
return this.customerId;
}
/**
* Searches for customers
*
* @private
*/
search(searchPhrase) {
if (searchPhrase.length === 0) {
return;
}
if (this.activeSearchRequest !== null) {
this.activeSearchRequest.abort();
}
this.customerRenderer.clearShownCustomers();
this.customerRenderer.hideNotFoundCustomers();
this.customerRenderer.showSearchingCustomers();
const $searchRequest = $.get(this.router.generate('admin_customers_search'), {
customer_search: searchPhrase,
});
this.activeSearchRequest = $searchRequest;
$searchRequest.then((response) => {
EventEmitter.emit(eventMap.customerSearched, response);
}).catch((response) => {
if (response.statusText === 'abort') {
return;
}
window.showErrorMessage(response.responseJSON.message);
});
}
}

View File

@@ -0,0 +1,341 @@
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/OSL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
* versions in the future. If you wish to customize PrestaShop for your
* needs please refer to https://devdocs.prestashop.com/ for more information.
*
* @author PrestaShop SA and Contributors <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
*/
import createOrderMap from '@pages/order/create/create-order-map';
import Router from '@components/router';
import eventMap from '@pages/order/create/event-map';
import {EventEmitter} from '@components/event-emitter';
const {$} = window;
/**
* Responsible for customer information rendering
*/
export default class CustomerRenderer {
constructor() {
this.$container = $(createOrderMap.customerSearchBlock);
this.$customerSearchResultBlock = $(createOrderMap.customerSearchResultsBlock);
this.router = new Router();
}
/**
* Renders customer search results
*
* @param foundCustomers
*/
renderSearchResults(foundCustomers) {
if (foundCustomers.length === 0) {
EventEmitter.emit(eventMap.customersNotFound);
return;
}
Object.entries(foundCustomers).forEach(([customerId, customerResult]) => {
const customer = {
id: customerId,
firstName: customerResult.firstname,
lastName: customerResult.lastname,
email: customerResult.email,
birthday: customerResult.birthday !== '0000-00-00' ? customerResult.birthday : ' ',
};
this.renderFoundCustomer(customer);
});
// Show customer details in fancy box
$(createOrderMap.customerDetailsBtn).fancybox({
type: 'iframe',
width: '90%',
height: '90%',
});
}
/**
* Responsible for displaying customer block after customer select
*
* @param $targetedBtn
*/
displaySelectedCustomerBlock($targetedBtn) {
this.showCheckoutHistoryBlock();
$targetedBtn.addClass('d-none');
const $customerCard = $targetedBtn.closest('.card');
$customerCard.addClass('border-success');
$customerCard.find(createOrderMap.changeCustomerBtn).removeClass('d-none');
this.$container.find(createOrderMap.customerSearchRow).addClass('d-none');
this.$container.find(createOrderMap.notSelectedCustomerSearchResults)
.closest(createOrderMap.customerSearchResultColumn)
.remove();
// Initial display of the customer, the cart is gonna be created then customer's carts
// and orders are going to be fetched, but we can display the loading messages right now
this.showLoadingCarts();
this.showLoadingOrders();
}
/**
* Shows customer search block
*/
showCustomerSearch() {
this.$container.find(createOrderMap.customerSearchRow).removeClass('d-none');
}
/**
* Empty the cart list and display a loading message.
*/
showLoadingCarts() {
const $cartsTable = $(createOrderMap.customerCartsTable);
$cartsTable.find('tbody').empty();
this.renderLoading($cartsTable);
}
/**
* Renders customer carts list
*
* @param {Array} carts
* @param {Int} currentCartId
*/
renderCarts(carts, currentCartId) {
const $cartsTable = $(createOrderMap.customerCartsTable);
const $cartsTableRowTemplate = $($(createOrderMap.customerCartsTableRowTemplate).html());
$cartsTable.find('tbody').empty();
this.showCheckoutHistoryBlock();
this.removeEmptyListRowFromTable($cartsTable);
Object.values(carts).forEach((cart) => {
// do not render current cart
if (cart.cartId === currentCartId) {
// render 'No records found' warn if carts only contain current cart
if (carts.length === 1) {
this.renderEmptyList($cartsTable);
}
return;
}
const $cartsTableRow = $cartsTableRowTemplate.clone();
$cartsTableRow.find(createOrderMap.cartIdField).text(cart.cartId);
$cartsTableRow.find(createOrderMap.cartDateField).text(cart.creationDate);
$cartsTableRow.find(createOrderMap.cartTotalField).text(cart.totalPrice);
$cartsTableRow.find(createOrderMap.cartDetailsBtn).prop(
'href',
this.router.generate('admin_carts_view', {
cartId: cart.cartId,
liteDisplaying: 1,
}),
);
$cartsTableRow.find(createOrderMap.useCartBtn).data('cart-id', cart.cartId);
$cartsTable.find('thead').removeClass('d-none');
$cartsTable.find('tbody').append($cartsTableRow);
});
// Show cart details in fancy box
$(createOrderMap.cartDetailsBtn).fancybox({
type: 'iframe',
width: '90%',
height: '90%',
});
}
/**
* Empty the order list and display a loading message.
*/
showLoadingOrders() {
const $ordersTable = $(createOrderMap.customerOrdersTable);
$ordersTable.find('tbody').empty();
this.renderLoading($ordersTable);
}
/**
* Renders customer orders list
*
* @param {Array} orders
*/
renderOrders(orders) {
const $ordersTable = $(createOrderMap.customerOrdersTable);
const $rowTemplate = $($(createOrderMap.customerOrdersTableRowTemplate).html());
$ordersTable.find('tbody').empty();
this.showCheckoutHistoryBlock();
this.removeEmptyListRowFromTable($ordersTable);
// render 'No records found' when list is empty
if (orders.length === 0) {
this.renderEmptyList($ordersTable);
return;
}
Object.values(orders).forEach((order) => {
const $template = $rowTemplate.clone();
$template.find(createOrderMap.orderIdField).text(order.orderId);
$template.find(createOrderMap.orderDateField).text(order.orderPlacedDate);
$template.find(createOrderMap.orderProductsField).text(order.orderProductsCount);
$template.find(createOrderMap.orderTotalField).text(order.totalPaid);
$template.find(createOrderMap.orderPaymentMethod).text(order.paymentMethodName);
$template.find(createOrderMap.orderStatusField).text(order.orderStatus);
$template.find(createOrderMap.orderDetailsBtn).prop(
'href',
this.router.generate('admin_orders_view', {
orderId: order.orderId,
liteDisplaying: 1,
}),
);
$template.find(createOrderMap.useOrderBtn).data('order-id', order.orderId);
$ordersTable.find('thead').removeClass('d-none');
$ordersTable.find('tbody').append($template);
});
// Show order details in fancy box
$(createOrderMap.orderDetailsBtn).fancybox({
type: 'iframe',
width: '90%',
height: '90%',
});
}
/**
* Shows empty result when customer is not found
*/
showNotFoundCustomers() {
$(createOrderMap.customerSearchEmptyResultWarning).removeClass('d-none');
}
/**
* Hides not found customers warning
*/
hideNotFoundCustomers() {
$(createOrderMap.customerSearchEmptyResultWarning).addClass('d-none');
}
/**
* Hides checkout history block where carts and orders are rendered
*/
hideCheckoutHistoryBlock() {
$(createOrderMap.customerCheckoutHistory).addClass('d-none');
}
/**
* Shows searching customers notice during request
*/
showSearchingCustomers() {
$(createOrderMap.customerSearchLoadingNotice).removeClass('d-none');
}
/**
* Hide searching notice
*/
hideSearchingCustomers() {
$(createOrderMap.customerSearchLoadingNotice).addClass('d-none');
}
/**
* Renders 'No records' warning in list
*
* @param $table
*
* @private
*/
renderEmptyList($table) {
const $emptyTableRow = $($(createOrderMap.emptyListRowTemplate).html()).clone();
$table.find('tbody').append($emptyTableRow);
}
/**
* Renders 'Loading' message in list
*
* @param $table
*
* @private
*/
renderLoading($table) {
const $emptyTableRow = $($(createOrderMap.loadingListRowTemplate).html()).clone();
$table.find('tbody').append($emptyTableRow);
}
/**
* Removes empty list row in case it was rendered
*/
removeEmptyListRowFromTable($table) {
$table.find(createOrderMap.emptyListRow).remove();
}
/**
* Renders customer information after search action
*
* @param {Object} customer
*
* @return {jQuery}
*
* @private
*/
renderFoundCustomer(customer) {
this.hideNotFoundCustomers();
const $customerSearchResultTemplate = $($(createOrderMap.customerSearchResultTemplate).html());
const $template = $customerSearchResultTemplate.clone();
$template.find(createOrderMap.customerSearchResultName).text(`${customer.firstName} ${customer.lastName}`);
$template.find(createOrderMap.customerSearchResultEmail).text(customer.email);
$template.find(createOrderMap.customerSearchResultId).text(customer.id);
$template.find(createOrderMap.customerSearchResultBirthday).text(customer.birthday);
$template.find(createOrderMap.chooseCustomerBtn).data('customer-id', customer.id);
$template.find(createOrderMap.customerDetailsBtn).prop(
'href',
this.router.generate('admin_customers_view', {
customerId: customer.id,
liteDisplaying: 1,
}),
);
return this.$customerSearchResultBlock.append($template);
}
/**
* Shows checkout history block where carts and orders are rendered
*
* @private
*/
showCheckoutHistoryBlock() {
$(createOrderMap.customerCheckoutHistory).removeClass('d-none');
}
/**
* Clears shown customers
*/
clearShownCustomers() {
this.$customerSearchResultBlock.empty();
}
}

View File

@@ -0,0 +1,77 @@
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/OSL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
* versions in the future. If you wish to customize PrestaShop for your
* needs please refer to https://devdocs.prestashop.com/ for more information.
*
* @author PrestaShop SA and Contributors <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
*/
/**
* Encapsulates js events used in create order page
*/
export default {
// when customer search action is done
customerSearched: 'OrderCreateCustomerSearched',
// when new customer is selected
customerSelected: 'OrderCreateCustomerSelected',
// when no customers found by search
customersNotFound: 'OrderCreateSearchCustomerNotFound',
// when new cart is loaded,
// no matter if its empty, selected from carts list or duplicated by order.
cartLoaded: 'OrderCreateCartLoaded',
// when cart currency has been changed
cartCurrencyChanged: 'OrderCreateCartCurrencyChanged',
// when cart currency changing fails
cartCurrencyChangeFailed: 'OrderCreateCartCurrencyChangeFailed',
// when cart language has been changed
cartLanguageChanged: 'OrderCreateCartLanguageChanged',
// when cart addresses information has been changed
cartAddressesChanged: 'OrderCreateCartAddressesChanged',
// when cart delivery option has been changed
cartDeliveryOptionChanged: 'OrderCreateCartDeliveryOptionChanged',
// when cart delivery setting has been changed
cartDeliverySettingChanged: 'OrderCreateCartDeliverySettingChangedSet',
// when cart rules search action is done
cartRuleSearched: 'OrderCreateCartRuleSearched',
// when cart rule is removed from cart
cartRuleRemoved: 'OrderCreateCartRuleRemoved',
// when cart rule is added to cart
cartRuleAdded: 'OrderCreateCartRuleAdded',
// when cart rule cannot be added to cart
cartRuleFailedToAdd: 'OrderCreateCartRuleFailedToAdd',
// when product search action is done
productSearched: 'OrderCreateProductSearched',
// when product is added to cart
productAddedToCart: 'OrderCreateProductAddedToCart',
// when adding product to cart fails
productAddToCartFailed: 'OrderCreateProductAddToCartFailed',
// when product is removed from cart
productRemovedFromCart: 'OrderCreateProductRemovedFromCart',
// when product in cart price has been changed
productPriceChanged: 'OrderCreateProductPriceChanged',
// when product quantity in cart has been changed
productQtyChanged: 'OrderCreateProductQtyChanged',
// when changing product quantity in cart failed
productQtyChangeFailed: 'OrderCreateProductQtyChangeFailed',
// when order process email has been sent to customer
processOrderEmailSent: 'OrderCreateProcessOrderEmailSent',
// when order process email sending failed
processOrderEmailFailed: 'OrderCreateProcessOrderEmailFailed',
};

View File

@@ -0,0 +1,422 @@
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/OSL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
* versions in the future. If you wish to customize PrestaShop for your
* needs please refer to https://devdocs.prestashop.com/ for more information.
*
* @author PrestaShop SA and Contributors <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
*/
import CartEditor from '@pages/order/create/cart-editor';
import createOrderMap from '@pages/order/create/create-order-map';
import eventMap from '@pages/order/create/event-map';
import {EventEmitter} from '@components/event-emitter';
import ProductRenderer from '@pages/order/create/product-renderer';
import Router from '@components/router';
const {$} = window;
/**
* Product component Object for "Create order" page
*/
export default class ProductManager {
constructor() {
this.products = [];
this.selectedProduct = null;
this.selectedCombinationId = null;
this.activeSearchRequest = null;
this.productRenderer = new ProductRenderer();
this.router = new Router();
this.cartEditor = new CartEditor();
this.initListeners();
return {
search: (searchPhrase) => this.search(searchPhrase),
addProductToCart: (cartId) => this.cartEditor.addProduct(cartId, this.getProductData()),
removeProductFromCart: (cartId, product) => this.cartEditor.removeProductFromCart(cartId, product),
/* eslint-disable-next-line max-len */
changeProductPrice: (cartId, customerId, updatedProduct) => this.cartEditor.changeProductPrice(cartId, customerId, updatedProduct),
changeProductQty: (cartId, updatedProduct) => this.cartEditor.changeProductQty(cartId, updatedProduct),
};
}
/**
* Initializes event listeners
*
* @private
*/
initListeners() {
$(createOrderMap.productSelect).on('change', (e) => this.initProductSelect(e));
$(createOrderMap.combinationsSelect).on('change', (e) => this.initCombinationSelect(e));
this.onProductSearch();
this.onAddProductToCart();
this.onRemoveProductFromCart();
this.onProductPriceChange();
this.onProductQtyChange();
}
/**
* Listens for product search event
*
* @private
*/
onProductSearch() {
EventEmitter.on(eventMap.productSearched, (response) => {
this.products = response.products;
this.productRenderer.renderSearchResults(this.products);
this.selectFirstResult();
});
}
/**
* Listens for add product to cart event
*
* @private
*/
onAddProductToCart() {
// on success
EventEmitter.on(eventMap.productAddedToCart, (cartInfo) => {
this.productRenderer.cleanCartBlockAlerts();
this.updateStockOnProductAdd();
EventEmitter.emit(eventMap.cartLoaded, cartInfo);
});
// on failure
EventEmitter.on(eventMap.productAddToCartFailed, (errorMessage) => {
this.productRenderer.renderCartBlockErrorAlert(errorMessage);
});
}
/**
* Listens for remove product from cart event
*
* @private
*/
onRemoveProductFromCart() {
EventEmitter.on(eventMap.productRemovedFromCart, (data) => {
this.updateStockOnProductRemove(data.product);
EventEmitter.emit(eventMap.cartLoaded, data.cartInfo);
});
}
/**
* Listens for product price change in cart event
*
* @private
*/
onProductPriceChange() {
EventEmitter.on(eventMap.productPriceChanged, (cartInfo) => {
this.productRenderer.cleanCartBlockAlerts();
EventEmitter.emit(eventMap.cartLoaded, cartInfo);
});
}
/**
* Listens for product quantity change in cart success/failure event
*
* @private
*/
onProductQtyChange() {
const enableQtyInputs = () => {
const inputsQty = document.querySelectorAll(createOrderMap.listedProductQtyInput);
inputsQty.forEach((inputQty) => {
inputQty.disabled = false;
});
};
// on success
EventEmitter.on(eventMap.productQtyChanged, (data) => {
this.productRenderer.cleanCartBlockAlerts();
this.updateStockOnQtyChange(data.product);
$(createOrderMap.createOrderButton).prop('disabled', false);
EventEmitter.emit(eventMap.cartLoaded, data.cartInfo);
enableQtyInputs();
});
// on failure
EventEmitter.on(eventMap.productQtyChangeFailed, (e) => {
this.productRenderer.renderCartBlockErrorAlert(e.responseJSON.message);
$(createOrderMap.createOrderButton).prop('disabled', true);
enableQtyInputs();
});
}
/**
* Initializes product select
*
* @param event
*
* @private
*/
initProductSelect(event) {
const productId = Number(
$(event.currentTarget)
.find(':selected')
.val(),
);
this.selectProduct(productId);
}
/**
* Initializes combination select
*
* @param event
*
* @private
*/
initCombinationSelect(event) {
const combinationId = Number(
$(event.currentTarget)
.find(':selected')
.val(),
);
this.selectCombination(combinationId);
}
/**
* Searches for product
*
* @private
*/
search(searchPhrase) {
// Search only if the search phrase length is greater than 2 characters
if (searchPhrase.length < 2) {
return;
}
this.productRenderer.renderSearching();
if (this.activeSearchRequest !== null) {
this.activeSearchRequest.abort();
}
const params = {
search_phrase: searchPhrase,
};
if ($(createOrderMap.cartCurrencySelect).data('selectedCurrencyId') !== undefined) {
params.currency_id = $(createOrderMap.cartCurrencySelect).data('selectedCurrencyId');
}
const $searchRequest = $.get(this.router.generate('admin_orders_products_search'), params);
this.activeSearchRequest = $searchRequest;
$searchRequest
.then((response) => {
EventEmitter.emit(eventMap.productSearched, response);
})
.catch((response) => {
if (response.statusText === 'abort') {
return;
}
window.showErrorMessage(response.responseJSON.message);
});
}
/**
* Initiate first result dataset after search
*
* @private
*/
selectFirstResult() {
this.unsetProduct();
if (this.products.length !== 0) {
this.selectProduct(this.products[0].productId);
}
}
/**
* Handles use case when product is selected from search results
*
* @private
*
* @param {Number} productId
*/
selectProduct(productId) {
this.unsetCombination();
const selectedProduct = Object.values(this.products).find((product) => product.productId === productId);
if (selectedProduct) {
this.selectedProduct = selectedProduct;
}
this.productRenderer.renderProductMetadata(this.selectedProduct);
// if product has combinations select the first else leave it null
if (this.selectedProduct.combinations.length !== 0) {
this.selectCombination(Object.keys(this.selectedProduct.combinations)[0]);
}
return this.selectedProduct;
}
/**
* Handles use case when new combination is selected
*
* @param combinationId
*
* @private
*/
selectCombination(combinationId) {
const combination = this.selectedProduct.combinations[combinationId];
this.selectedCombinationId = combinationId;
this.productRenderer.renderStock(
$(createOrderMap.inStockCounter),
$(createOrderMap.quantityInput),
combination.stock,
this.selectedProduct.availableOutOfStock || combination.stock <= 0,
);
return combination;
}
/**
* Sets the selected combination id to null
*
* @private
*/
unsetCombination() {
this.selectedCombinationId = null;
}
/**
* Sets the selected product to null
*
* @private
*/
unsetProduct() {
this.selectedProduct = null;
}
/**
* Retrieves product data from product search result block fields
*
* @returns {Object}
*
* @private
*/
getProductData() {
const $fileInputs = $(createOrderMap.productCustomizationContainer).find('input[type="file"]');
const formData = new FormData(document.querySelector(createOrderMap.productAddForm));
const fileSizes = {};
// adds key value pairs {input name: file size} of each file in separate object
// in case formData size exceeds server settings.
$.each($fileInputs, (key, input) => {
if (input.files.length !== 0) {
fileSizes[$(input).data('customization-field-id')] = input.files[0].size;
}
});
return {
product: formData,
fileSizes,
};
}
/**
* Updates the stock when the product is added to cart in "create new order" page
*
* @private
*/
updateStockOnProductAdd() {
const {productId} = this.selectedProduct;
const attributeId = this.selectedCombinationId;
const qty = -Number($(createOrderMap.quantityInput).val());
this.updateStock(productId, attributeId, qty);
}
/**
* Updates the stock when the product is removed from cart in Orders/"create new order page"
*
* @private
*/
updateStockOnProductRemove(product) {
const {productId, attributeId, qtyToRemove} = product;
const qty = qtyToRemove;
this.updateStock(productId, attributeId, qty);
}
/**
* Updates the stock when the quantity of product is changed from cart in Orders/"create new order page"
*
* @private
*/
updateStockOnQtyChange(product) {
const {
productId, attributeId, prevQty, newQty,
} = product;
const qty = prevQty - newQty;
this.updateStock(productId, attributeId, qty);
}
/**
* Updates the stock in products object and renders the new stock
*
* @private
*/
updateStock(productId, attributeId, qty) {
const productKeys = Object.keys(this.products);
const productValues = Object.values(this.products);
for (let i = 0; i < productKeys.length; i += 1) {
if (productValues[i].productId === productId) {
const $template = this.productRenderer.cloneProductTemplate(productValues[i]);
// Update the stock value in products object
productValues[i].stock += qty;
// Update the stock also for combination */
if (attributeId && attributeId > 0) {
productValues[i].combinations[attributeId].stock += qty;
}
// Render the new stock value
if (this.selectedProduct.productId === productId) {
if (this.selectedProduct.combinations.length === 0) {
this.productRenderer.renderStock(
$template.find(createOrderMap.listedProductQtyStock),
$template.find(createOrderMap.listedProductQtyInput),
productValues[i].stock,
productValues[i].availableOutOfStock || productValues[i].availableStock <= 0,
);
} else if (attributeId && Number(this.selectedCombinationId) === Number(attributeId)) {
this.productRenderer.renderStock(
$template.find(createOrderMap.listedProductQtyStock),
$template.find(createOrderMap.listedProductQtyInput),
productValues[i].combinations[attributeId].stock,
productValues[i].availableOutOfStock || productValues[i].availableStock <= 0,
);
}
}
break;
}
}
}
}

View File

@@ -0,0 +1,500 @@
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/OSL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
* versions in the future. If you wish to customize PrestaShop for your
* needs please refer to https://devdocs.prestashop.com/ for more information.
*
* @author PrestaShop SA and Contributors <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
*/
import createOrderMap from './create-order-map';
const {$} = window;
export default class ProductRenderer {
constructor() {
this.$productsTable = $(createOrderMap.productsTable);
}
/**
* Renders cart products list
*
* @param products
*/
renderList(products) {
this.cleanProductsList();
if (products.length === 0) {
this.hideProductsList();
return;
}
Object.values(products).forEach((product) => {
const $template = this.cloneProductTemplate(product);
let customizationId = 0;
if (product.customization) {
({customizationId} = product.customization);
this.renderListedProductCustomization(product.customization, $template);
}
$template.find(createOrderMap.listedProductImageField).prop('src', product.imageLink);
$template.find(createOrderMap.listedProductNameField).text(product.name);
$template.find(createOrderMap.listedProductAttrField).text(product.attribute);
$template.find(createOrderMap.listedProductReferenceField).text(product.reference);
if (product.gift !== true) {
$template.find(createOrderMap.listedProductUnitPriceInput).val(product.unitPrice);
$template.find(createOrderMap.listedProductUnitPriceInput).data('product-id', product.productId);
$template.find(createOrderMap.listedProductUnitPriceInput).data('attribute-id', product.attributeId);
$template.find(createOrderMap.listedProductUnitPriceInput).data('customization-id', customizationId);
$template.find(createOrderMap.listedProductQtyInput).val(product.quantity);
$template.find(createOrderMap.listedProductQtyInput).data('product-id', product.productId);
$template.find(createOrderMap.listedProductQtyInput).data('attribute-id', product.attributeId);
$template.find(createOrderMap.listedProductQtyInput).data('customization-id', customizationId);
$template.find(createOrderMap.listedProductQtyInput).data('prev-qty', product.quantity);
this.renderStock(
$template.find(createOrderMap.listedProductQtyStock),
$template.find(createOrderMap.listedProductQtyInput),
product.availableStock,
product.availableOutOfStock || (product.availableStock <= 0),
);
$template.find(createOrderMap.productTotalPriceField).text(product.price);
$template.find(createOrderMap.productRemoveBtn).data('product-id', product.productId);
$template.find(createOrderMap.productRemoveBtn).data('attribute-id', product.attributeId);
$template.find(createOrderMap.productRemoveBtn).data('customization-id', customizationId);
} else {
$template.find(createOrderMap.listedProductGiftQty).text(product.quantity);
}
this.$productsTable.find('tbody').append($template);
});
this.showTaxWarning();
this.showProductsList();
}
/**
* Renders customization data for listed product
*
* @param customization
* @param $productRowTemplate
*
* @private
*/
renderListedProductCustomization(customization, $productRowTemplate) {
const $customizedTextTemplate = $($(createOrderMap.listedProductCustomizedTextTemplate).html());
const $customizedFileTemplate = $($(createOrderMap.listedProductCustomizedFileTemplate).html());
Object.values(customization.customizationFieldsData).forEach((customizedData) => {
let $customizationTemplate = $customizedTextTemplate.clone();
if (customizedData.type === createOrderMap.productCustomizationFieldTypeFile) {
$customizationTemplate = $customizedFileTemplate.clone();
$customizationTemplate.find(createOrderMap.listedProductCustomizationName).text(customizedData.name);
$customizationTemplate
.find(`${createOrderMap.listedProductCustomizationValue} img`)
.prop('src', customizedData.value);
} else {
$customizationTemplate.find(createOrderMap.listedProductCustomizationName).text(customizedData.name);
$customizationTemplate.find(createOrderMap.listedProductCustomizationValue).text(customizedData.value);
}
$productRowTemplate.find(createOrderMap.listedProductDefinition).append($customizationTemplate);
});
}
renderSearching() {
this.reset();
this.toggleSearchingNotice(true);
}
/**
* Renders cart products search results block
*
* @param foundProducts
*/
renderSearchResults(foundProducts) {
this.cleanSearchResults();
this.toggleSearchingNotice(false);
if (foundProducts.length === 0) {
this.showNotFound();
this.hideTaxWarning();
return;
}
this.renderFoundProducts(foundProducts);
this.hideNotFound();
this.showTaxWarning();
this.showResultBlock();
}
reset() {
this.cleanSearchResults();
this.hideTaxWarning();
this.hideResultBlock();
this.toggleSearchingNotice(false);
}
/**
* Renders available fields related to selected product
*
* @param {object} product
*/
renderProductMetadata(product) {
this.renderStock(
$(createOrderMap.inStockCounter),
$(createOrderMap.quantityInput),
product.stock,
product.availableOutOfStock || (product.stock <= 0),
);
this.renderCombinations(product.combinations);
this.renderCustomizations(product.customizationFields);
}
/**
* Updates stock text helper value
*
* @param {object} inputStockCounter Text Help with the stock counter
* @param {object} inputQuantity Input for the stock
* @param {number} stock Available stock for the product
* @param {boolean} infiniteMax If the product order has no limits
*/
renderStock(inputStockCounter, inputQuantity, stock, infiniteMax) {
inputStockCounter.text(stock);
if (!infiniteMax) {
inputQuantity.attr('max', stock);
} else {
inputQuantity.removeAttr('max');
}
}
/**
* @param product
*
* @private
*/
cloneProductTemplate(product) {
return product.gift === true
? $($(createOrderMap.productsTableGiftRowTemplate).html()).clone()
: $($(createOrderMap.productsTableRowTemplate).html()).clone();
}
/**
* Renders found products select
*
* @param foundProducts
*
* @private
*/
renderFoundProducts(foundProducts) {
Object.values(foundProducts).forEach((product) => {
let {name} = product;
if (product.combinations.length === 0) {
name += ` - ${product.formattedPrice}`;
}
$(createOrderMap.productSelect).append(`<option value="${product.productId}">${name}</option>`);
});
}
/**
* Cleans product search result fields
*
* @private
*/
cleanSearchResults() {
$(createOrderMap.productSelect).empty();
$(createOrderMap.combinationsSelect).empty();
$(createOrderMap.quantityInput).empty();
}
/**
* Renders combinations row with select options
*
* @param {Array} combinations
*
* @private
*/
renderCombinations(combinations) {
this.cleanCombinations();
if (combinations.length === 0) {
this.hideCombinations();
return;
}
Object.values(combinations).forEach((combination) => {
$(createOrderMap.combinationsSelect).append(
`<option
value="${combination.attributeCombinationId}">
${combination.attribute} - ${combination.formattedPrice}
</option>`,
);
});
this.showCombinations();
}
/**
* Resolves weather to add customization fields to result block and adds them if needed
*
* @param customizationFields
*
* @private
*/
renderCustomizations(customizationFields) {
// represents customization field type "file".
const fieldTypeFile = createOrderMap.productCustomizationFieldTypeFile;
// represents customization field type "text".
const fieldTypeText = createOrderMap.productCustomizationFieldTypeText;
this.cleanCustomizations();
if (customizationFields.length === 0) {
this.hideCustomizations();
return;
}
const $customFieldsContainer = $(createOrderMap.productCustomFieldsContainer);
const $fileInputTemplate = $($(createOrderMap.productCustomFileTemplate).html());
const $textInputTemplate = $($(createOrderMap.productCustomTextTemplate).html());
const templateTypeMap = {
[fieldTypeFile]: $fileInputTemplate,
[fieldTypeText]: $textInputTemplate,
};
Object.values(customizationFields).forEach((customField) => {
const $template = templateTypeMap[customField.type].clone();
if (customField.type === fieldTypeFile) {
$template.on('change', (e) => {
const fileName = e.target.files[0].name;
$(e.target).next('.custom-file-label').html(fileName);
});
}
$template
.find(createOrderMap.productCustomInput)
.attr('name', `customizations[${customField.customizationFieldId}]`)
.data('customization-field-id', customField.customizationFieldId);
$template
.find(createOrderMap.productCustomInputLabel)
.attr('for', `customizations[${customField.customizationFieldId}]`)
.text(customField.name);
if (customField.required === true) {
$template.find(createOrderMap.requiredFieldMark).removeClass('d-none');
}
$customFieldsContainer.append($template);
});
this.showCustomizations();
}
/**
* Renders error alert for cart block
*
* @param message
*/
renderCartBlockErrorAlert(message) {
$(createOrderMap.cartErrorAlertText).text(message);
this.showCartBlockError();
}
/**
* Cleans cart block alerts content and hides them
*/
cleanCartBlockAlerts() {
$(createOrderMap.cartErrorAlertText).text('');
this.hideCartBlockError();
}
/**
* Shows error alert block of cart block
*
* @private
*/
showCartBlockError() {
$(createOrderMap.cartErrorAlertBlock).removeClass('d-none');
}
/**
* Hides error alert block of cart block
*
* @private
*/
hideCartBlockError() {
$(createOrderMap.cartErrorAlertBlock).addClass('d-none');
}
/**
* Shows product customization container
*
* @private
*/
showCustomizations() {
$(createOrderMap.productCustomizationContainer).removeClass('d-none');
}
/**
* Hides product customization container
*
* @private
*/
hideCustomizations() {
$(createOrderMap.productCustomizationContainer).addClass('d-none');
}
/**
* Empties customization fields container
*
* @private
*/
cleanCustomizations() {
$(createOrderMap.productCustomFieldsContainer).empty();
}
/**
* Shows result block
*
* @private
*/
showResultBlock() {
$(createOrderMap.productResultBlock).removeClass('d-none');
}
/**
* Hides result block
*
* @private
*/
hideResultBlock() {
$(createOrderMap.productResultBlock).addClass('d-none');
}
/**
* Shows products list
*
* @private
*/
showProductsList() {
this.$productsTable.removeClass('d-none');
}
/**
* Hides products list
*
* @private
*/
hideProductsList() {
this.$productsTable.addClass('d-none');
}
/**
* Empties products list
*
* @private
*/
cleanProductsList() {
this.$productsTable.find('tbody').empty();
}
/**
* Empties combinations select
*
* @private
*/
cleanCombinations() {
$(createOrderMap.combinationsSelect).empty();
}
/**
* Shows combinations row
*
* @private
*/
showCombinations() {
$(createOrderMap.combinationsRow).removeClass('d-none');
}
/**
* Hides combinations row
*
* @private
*/
hideCombinations() {
$(createOrderMap.combinationsRow).addClass('d-none');
}
/**
* Shows warning of tax included/excluded
*
* @private
*/
showTaxWarning() {
$(createOrderMap.productTaxWarning).removeClass('d-none');
}
/**
* Hides warning of tax included/excluded
*
* @private
*/
hideTaxWarning() {
$(createOrderMap.productTaxWarning).addClass('d-none');
}
/**
* Shows product not found warning
*
* @private
*/
showNotFound() {
$(createOrderMap.noProductsFoundWarning).removeClass('d-none');
}
/**
* Hides product not found warning
*
* @private
*/
hideNotFound() {
$(createOrderMap.noProductsFoundWarning).addClass('d-none');
}
/**
* Toggles searching product notice
*
* @private
*/
toggleSearchingNotice(visible) {
$(createOrderMap.searchingProductsNotice).toggleClass('d-none', !visible);
}
}

View File

@@ -0,0 +1,233 @@
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/OSL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
* versions in the future. If you wish to customize PrestaShop for your
* needs please refer to https://devdocs.prestashop.com/ for more information.
*
* @author PrestaShop SA and Contributors <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
*/
import createOrderMap from './create-order-map';
const {$} = window;
/**
* Manipulates UI of Shipping block in Order creation page
*/
export default class ShippingRenderer {
constructor() {
this.$container = $(createOrderMap.shippingBlock);
this.$form = $(createOrderMap.shippingForm);
this.$noCarrierBlock = $(createOrderMap.noCarrierBlock);
}
/**
* @param {Object} shipping
* @param {Boolean} emptyCart
*/
render(shipping, emptyCart) {
if (emptyCart) {
this.hideContainer();
} else if (shipping !== null) {
this.displayForm(shipping);
} else {
this.displayNoCarriersWarning();
}
}
/**
* Show form block with rendered delivery options instead of warning message
*
* @param shipping
*
* @private
*/
displayForm(shipping) {
this.hideNoCarrierBlock();
this.renderDeliveryOptions(shipping.deliveryOptions, shipping.selectedCarrierId);
this.renderTotalShipping(shipping.shippingPrice);
this.renderFreeShippingSwitch(shipping.freeShipping);
this.renderRecycledPackagingSwitch(shipping.recycledPackaging);
this.renderGiftMessageField(shipping.giftMessage);
this.renderGiftSwitch(shipping.gift);
this.showForm();
this.showContainer();
}
/**
* Renders free shipping switch depending on free shipping value
*
* @param isFreeShipping
*
* @private
*/
renderFreeShippingSwitch(isFreeShipping) {
$(createOrderMap.freeShippingSwitch).each((key, input) => {
if (input.value === '1') {
input.checked = isFreeShipping;
} else {
input.checked = !isFreeShipping;
}
});
}
/**
* @param useRecycledPackaging
*
* @private
*/
renderRecycledPackagingSwitch(useRecycledPackaging) {
$(createOrderMap.recycledPackagingSwitch).each((key, input) => {
if (input.value === '1') {
input.checked = useRecycledPackaging;
} else {
input.checked = !useRecycledPackaging;
}
});
}
/**
* @param isAGift
*
* @private
*/
renderGiftSwitch(isAGift) {
$(createOrderMap.isAGiftSwitch).each((key, input) => {
if (input.value === '1') {
input.checked = isAGift;
} else {
input.checked = !isAGift;
}
});
}
/**
* @param giftMessage
*
* @private
*/
renderGiftMessageField(giftMessage) {
$(createOrderMap.giftMessageField).val(giftMessage);
}
/**
* Show warning message that no carriers are available and hide form block
*
* @private
*/
displayNoCarriersWarning() {
this.showContainer();
this.hideForm();
this.showNoCarrierBlock();
}
/**
* Renders delivery options selection block
*
* @param deliveryOptions
* @param selectedVal
*
* @private
*/
renderDeliveryOptions(deliveryOptions, selectedVal) {
const $deliveryOptionSelect = $(createOrderMap.deliveryOptionSelect);
$deliveryOptionSelect.empty();
Object.values(deliveryOptions).forEach((option) => {
const deliveryOption = {
value: option.carrierId,
text: `${option.carrierName} - ${option.carrierDelay}`,
};
if (selectedVal === deliveryOption.value) {
deliveryOption.selected = 'selected';
}
$deliveryOptionSelect.append($('<option>', deliveryOption));
});
}
/**
* Renders dynamic value of shipping price
*
* @param shippingPrice
*
* @private
*/
renderTotalShipping(shippingPrice) {
const $totalShippingField = $(createOrderMap.totalShippingField);
$totalShippingField.empty();
$totalShippingField.append(shippingPrice);
}
/**
* Show whole shipping container
*
* @private
*/
showContainer() {
this.$container.removeClass('d-none');
}
/**
* Hide whole shipping container
*
* @private
*/
hideContainer() {
this.$container.addClass('d-none');
}
/**
* Show form block
*
* @private
*/
showForm() {
this.$form.removeClass('d-none');
}
/**
* Hide form block
*
* @private
*/
hideForm() {
this.$form.addClass('d-none');
}
/**
* Show warning message block which warns that no carriers are available
*
* @private
*/
showNoCarrierBlock() {
this.$noCarrierBlock.removeClass('d-none');
}
/**
* Hide warning message block which warns that no carriers are available
*
* @private
*/
hideNoCarrierBlock() {
this.$noCarrierBlock.addClass('d-none');
}
}

View File

@@ -0,0 +1,93 @@
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/OSL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
* versions in the future. If you wish to customize PrestaShop for your
* needs please refer to https://devdocs.prestashop.com/ for more information.
*
* @author PrestaShop SA and Contributors <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
*/
import {EventEmitter} from '@components/event-emitter';
import Router from '@components/router';
import eventMap from './event-map';
import SummaryRenderer from './summary-renderer';
const {$} = window;
/**
* Manages summary block
*/
export default class SummaryManager {
constructor() {
this.router = new Router();
this.summaryRenderer = new SummaryRenderer();
this.initListeners();
return {
sendProcessOrderEmail: (cartId) => this.sendProcessOrderEmail(cartId),
};
}
/**
* Inits event listeners
*
* @private
*/
initListeners() {
this.onProcessOrderEmailError();
this.onProcessOrderEmailSuccess();
}
/**
* Listens for process order email sending success event
*
* @private
*/
onProcessOrderEmailSuccess() {
EventEmitter.on(eventMap.processOrderEmailSent, (response) => {
this.summaryRenderer.cleanAlerts();
this.summaryRenderer.renderSuccessMessage(response.message);
});
}
/**
* Listens for process order email failed event
*
* @private
*/
onProcessOrderEmailError() {
EventEmitter.on(eventMap.processOrderEmailFailed, (response) => {
this.summaryRenderer.cleanAlerts();
this.summaryRenderer.renderErrorMessage(response.responseJSON.message);
});
}
/**
* Sends email to customer with link of order processing
*
* @param {Number} cartId
*/
sendProcessOrderEmail(cartId) {
$.post(this.router.generate('admin_orders_send_process_order_email'), {
cartId,
}).then((response) => EventEmitter.emit(eventMap.processOrderEmailSent, response)).catch((e) => {
EventEmitter.emit(eventMap.processOrderEmailFailed, e);
});
}
}

View File

@@ -0,0 +1,177 @@
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/OSL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
* versions in the future. If you wish to customize PrestaShop for your
* needs please refer to https://devdocs.prestashop.com/ for more information.
*
* @author PrestaShop SA and Contributors <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
*/
import createOrderMap from './create-order-map';
import {ValidateAddresses} from './address-validator';
const {$} = window;
/**
* Responsible for summary block rendering
*/
export default class SummaryRenderer {
constructor() {
this.$totalProducts = $(createOrderMap.summaryTotalProducts);
this.$totalDiscount = $(createOrderMap.summaryTotalDiscount);
this.$totalShipping = $(createOrderMap.totalShippingField);
this.$summaryTotalShipping = $(createOrderMap.summaryTotalShipping);
this.$totalTaxes = $(createOrderMap.summaryTotalTaxes);
this.$totalWithoutTax = $(createOrderMap.summaryTotalWithoutTax);
this.$totalWithTax = $(createOrderMap.summaryTotalWithTax);
this.$placeOrderCartIdField = $(createOrderMap.placeOrderCartIdField);
this.$orderMessageField = $(createOrderMap.orderMessageField);
this.$processOrderLink = $(createOrderMap.processOrderLinkTag);
}
/**
* Renders summary block
*
* @param {Object} cartInfo
*/
render(cartInfo) {
this.cleanSummary();
const noProducts = cartInfo.products.length === 0;
const noShippingOptions = cartInfo.shipping === null;
const addressesAreValid = ValidateAddresses(cartInfo.addresses);
if (noProducts || noShippingOptions || !addressesAreValid) {
this.hideSummaryBlock();
return;
}
const cartSummary = cartInfo.summary;
this.$totalProducts.text(cartSummary.totalProductsPrice);
this.$totalDiscount.text(cartSummary.totalDiscount);
this.$summaryTotalShipping.text(cartSummary.totalShippingWithoutTaxes);
this.$totalShipping.text(cartSummary.totalShippingPrice);
this.$totalTaxes.text(cartSummary.totalTaxes);
this.$totalWithoutTax.text(cartSummary.totalPriceWithoutTaxes);
this.$totalWithTax.text(cartSummary.totalPriceWithTaxes);
this.$processOrderLink.prop('href', cartSummary.processOrderLink);
this.$orderMessageField.text(cartSummary.orderMessage);
this.$placeOrderCartIdField.val(cartInfo.cartId);
this.showSummaryBlock();
}
/**
* Renders summary success message
*
* @param message
*/
renderSuccessMessage(message) {
$(createOrderMap.summarySuccessAlertText).text(message);
this.showSummarySuccessAlertBlock();
}
/**
* Renders summary error message
*
* @param message
*/
renderErrorMessage(message) {
$(createOrderMap.summaryErrorAlertText).text(message);
this.showSummaryErrorAlertBlock();
}
/**
* Cleans content of success/error summary alerts and hides them
*/
cleanAlerts() {
$(createOrderMap.summarySuccessAlertText).text('');
$(createOrderMap.summaryErrorAlertText).text('');
this.hideSummarySuccessAlertBlock();
this.hideSummaryErrorAlertBlock();
}
/**
* Shows summary block
*
* @private
*/
showSummaryBlock() {
$(createOrderMap.summaryBlock).removeClass('d-none');
}
/**
* Hides summary block
*
* @private
*/
hideSummaryBlock() {
$(createOrderMap.summaryBlock).addClass('d-none');
}
/**
* Shows error alert of summary block
*
* @private
*/
showSummaryErrorAlertBlock() {
$(createOrderMap.summaryErrorAlertBlock).removeClass('d-none');
}
/**
* Hides error alert of summary block
*
* @private
*/
hideSummaryErrorAlertBlock() {
$(createOrderMap.summaryErrorAlertBlock).addClass('d-none');
}
/**
* Shows success alert of summary block
*
* @private
*/
showSummarySuccessAlertBlock() {
$(createOrderMap.summarySuccessAlertBlock).removeClass('d-none');
}
/**
* Hides success alert of summary block
*
* @private
*/
hideSummarySuccessAlertBlock() {
$(createOrderMap.summarySuccessAlertBlock).addClass('d-none');
}
/**
* Empties cart summary fields
*/
cleanSummary() {
this.$totalProducts.empty();
this.$totalDiscount.empty();
this.$totalShipping.empty();
this.$totalTaxes.empty();
this.$totalWithoutTax.empty();
this.$totalWithTax.empty();
this.$processOrderLink.prop('href', '');
this.$orderMessageField.text('');
this.cleanAlerts();
}
}

View File

@@ -0,0 +1,32 @@
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/OSL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
* versions in the future. If you wish to customize PrestaShop for your
* needs please refer to https://devdocs.prestashop.com/ for more information.
*
* @author PrestaShop SA and Contributors <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
*/
import TranslatableInput from '@components/translatable-input';
const {$} = window;
$(() => {
new TranslatableInput();
});

View File

@@ -0,0 +1,60 @@
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/OSL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
* versions in the future. If you wish to customize PrestaShop for your
* needs please refer to https://devdocs.prestashop.com/ for more information.
*
* @author PrestaShop SA and Contributors <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
*/
import Grid from '@components/grid/grid';
import FiltersResetExtension from '@components/grid/extension/filters-reset-extension';
import ReloadListActionExtension from '@components/grid/extension/reload-list-extension';
import ExportToSqlManagerExtension from '@components/grid/extension/export-to-sql-manager-extension';
import SortingExtension from '@components/grid/extension/sorting-extension';
import LinkRowActionExtension from '@components/grid/extension/link-row-action-extension';
import SubmitGridExtension from '@components/grid/extension/submit-grid-action-extension';
import SubmitBulkExtension from '@components/grid/extension/submit-bulk-action-extension';
import BulkActionCheckboxExtension from '@components/grid/extension/bulk-action-checkbox-extension';
import FiltersSubmitButtonEnablerExtension
from '@components/grid/extension/filters-submit-button-enabler-extension';
import ChoiceExtension from '@components/grid/extension/choice-extension';
import ModalFormSubmitExtension from '@components/grid/extension/modal-form-submit-extension';
import PreviewExtension from '@components/grid/extension/preview-extension';
import previewProductsToggler from '@pages/order/preview-products-toggler';
import BulkOpenTabsExtension from '@components/grid/extension/bulk-open-tabs-extension';
const {$} = window;
$(() => {
const orderGrid = new Grid('order');
orderGrid.addExtension(new ReloadListActionExtension());
orderGrid.addExtension(new ExportToSqlManagerExtension());
orderGrid.addExtension(new FiltersResetExtension());
orderGrid.addExtension(new SortingExtension());
orderGrid.addExtension(new LinkRowActionExtension());
orderGrid.addExtension(new SubmitGridExtension());
orderGrid.addExtension(new SubmitBulkExtension());
orderGrid.addExtension(new BulkActionCheckboxExtension());
orderGrid.addExtension(new FiltersSubmitButtonEnablerExtension());
orderGrid.addExtension(new ModalFormSubmitExtension());
orderGrid.addExtension(new ChoiceExtension());
orderGrid.addExtension(new PreviewExtension(previewProductsToggler));
orderGrid.addExtension(new BulkOpenTabsExtension());
});

View File

@@ -0,0 +1,68 @@
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/OSL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
* versions in the future. If you wish to customize PrestaShop for your
* needs please refer to https://devdocs.prestashop.com/ for more information.
*
* @author PrestaShop SA and Contributors <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
*/
import OrderViewPageMap from './OrderViewPageMap';
const {$} = window;
/**
* Manages adding/editing note for invoice documents.
*/
export default class InvoiceNoteManager {
constructor() {
this.setupListeners();
}
setupListeners() {
this.initShowNoteFormEventHandler();
this.initCloseNoteFormEventHandler();
this.initEnterPaymentEventHandler();
}
initShowNoteFormEventHandler() {
$('.js-open-invoice-note-btn').on('click', (event) => {
event.preventDefault();
const $btn = $(event.currentTarget);
const $noteRow = $btn.closest('tr').next();
$noteRow.removeClass('d-none');
});
}
initCloseNoteFormEventHandler() {
$('.js-cancel-invoice-note-btn').on('click', (event) => {
$(event.currentTarget).closest('tr').addClass('d-none');
});
}
initEnterPaymentEventHandler() {
$('.js-enter-payment-btn').on('click', (event) => {
const $btn = $(event.currentTarget);
const paymentAmount = $btn.data('payment-amount');
$(OrderViewPageMap.viewOrderPaymentsBlock).get(0).scrollIntoView({behavior: 'smooth'});
$(OrderViewPageMap.orderPaymentFormAmountInput).val(paymentAmount);
});
}
}

View File

@@ -0,0 +1,100 @@
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/OSL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
* versions in the future. If you wish to customize PrestaShop for your
* needs please refer to https://devdocs.prestashop.com/ for more information.
*
* @author PrestaShop SA and Contributors <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
*/
import OrderViewPageMap from '../OrderViewPageMap';
const {$} = window;
/**
* All actions for order view page messages are registered in this class.
*/
export default class OrderViewPageMessagesHandler {
constructor() {
this.$orderMessageChangeWarning = $(OrderViewPageMap.orderMessageChangeWarning);
this.$messagesContainer = $(OrderViewPageMap.orderMessagesContainer);
return {
listenForPredefinedMessageSelection: () => this.handlePredefinedMessageSelection(),
listenForFullMessagesOpen: () => this.onFullMessagesOpen(),
};
}
/**
* Handles predefined order message selection.
*
* @private
*/
handlePredefinedMessageSelection() {
$(document).on('change', OrderViewPageMap.orderMessageNameSelect, (e) => {
const $currentItem = $(e.currentTarget);
const valueId = $currentItem.val();
if (!valueId) {
return;
}
const message = this.$messagesContainer.find(`div[data-id=${valueId}]`).text().trim();
const $orderMessage = $(OrderViewPageMap.orderMessage);
const isSameMessage = $orderMessage.val().trim() === message;
if (isSameMessage) {
return;
}
if ($orderMessage.val() && !window.confirm(this.$orderMessageChangeWarning.text())) {
return;
}
$orderMessage.val(message);
$orderMessage.trigger('input');
});
}
/**
* Listens for event when all messages modal is being opened
*
* @private
*/
onFullMessagesOpen() {
$(document).on('click', OrderViewPageMap.openAllMessagesBtn, () => this.scrollToMsgListBottom());
}
/**
* Scrolls down to the bottom of all messages list
*
* @private
*/
scrollToMsgListBottom() {
const $msgModal = $(OrderViewPageMap.allMessagesModal);
const msgList = document.querySelector(OrderViewPageMap.allMessagesList);
const classCheckInterval = window.setInterval(() => {
if ($msgModal.hasClass('show')) {
msgList.scrollTop = msgList.scrollHeight;
clearInterval(classCheckInterval);
}
}, 10);
}
}

View File

@@ -0,0 +1,42 @@
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/OSL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
* versions in the future. If you wish to customize PrestaShop for your
* needs please refer to https://devdocs.prestashop.com/ for more information.
*
* @author PrestaShop SA and Contributors <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
*/
import OrderViewPageMap from './OrderViewPageMap';
const {$} = window;
export default class OrderShippingManager {
constructor() {
this.initOrderShippingUpdateEventHandler();
}
initOrderShippingUpdateEventHandler() {
$(OrderViewPageMap.mainDiv).on('click', OrderViewPageMap.showOrderShippingUpdateModalBtn, (event) => {
const $btn = $(event.currentTarget);
$(OrderViewPageMap.updateOrderShippingTrackingNumberInput).val($btn.data('order-tracking-number'));
$(OrderViewPageMap.updateOrderShippingCurrentOrderCarrierIdInput).val($btn.data('order-carrier-id'));
});
}
}

View File

@@ -0,0 +1,57 @@
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/OSL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
* versions in the future. If you wish to customize PrestaShop for your
* needs please refer to https://devdocs.prestashop.com/ for more information.
*
* @author PrestaShop SA and Contributors <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
*/
const {$} = window;
/**
* Toggles hidden products in order preview block.
*
* @param {jQuery} $gridContainer
*/
export default function previewProductsToggler($row) {
toggleStockLocationColumn($row);
$row.on('click', '.js-preview-more-products-btn', (event) => {
event.preventDefault();
const $btn = $(event.currentTarget);
const $hiddenProducts = $btn.closest('tbody').find('.js-product-preview-more');
$hiddenProducts.removeClass('d-none');
$btn.closest('tr').remove();
toggleStockLocationColumn($row);
});
}
function toggleStockLocationColumn($container) {
let showColumn = false;
$('.js-cell-product-stock-location', $container.find('tr:not(.d-none)')).filter('td').each((index, element) => {
if ($(element).html().trim() !== '') {
showColumn = true;
return false;
}
return true;
});
$('.js-cell-product-stock-location', $container).toggle(showColumn);
}

View File

@@ -0,0 +1,220 @@
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/OSL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
* versions in the future. If you wish to customize PrestaShop for your
* needs please refer to https://devdocs.prestashop.com/ for more information.
*
* @author PrestaShop SA and Contributors <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
*/
import OrderViewPageMap from '@pages/order/OrderViewPageMap';
import OrderShippingManager from '@pages/order/order-shipping-manager';
import InvoiceNoteManager from '@pages/order/invoice-note-manager';
import OrderViewPage from '@pages/order/view/order-view-page';
import OrderProductAutocomplete from '@pages/order/view/order-product-add-autocomplete';
import OrderProductAdd from '@pages/order/view/order-product-add';
import TextWithLengthCounter from '@components/form/text-with-length-counter';
import OrderViewPageMessagesHandler from './message/order-view-page-messages-handler';
const {$} = window;
$(() => {
const DISCOUNT_TYPE_AMOUNT = 'amount';
const DISCOUNT_TYPE_PERCENT = 'percent';
const DISCOUNT_TYPE_FREE_SHIPPING = 'free_shipping';
new OrderShippingManager();
new TextWithLengthCounter();
const orderViewPage = new OrderViewPage();
const orderAddAutocomplete = new OrderProductAutocomplete($(OrderViewPageMap.productSearchInput));
const orderAdd = new OrderProductAdd();
orderViewPage.listenForProductPack();
orderViewPage.listenForProductDelete();
orderViewPage.listenForProductEdit();
orderViewPage.listenForProductAdd();
orderViewPage.listenForProductPagination();
orderViewPage.listenForRefund();
orderViewPage.listenForCancelProduct();
orderAddAutocomplete.listenForSearch();
orderAddAutocomplete.onItemClickedCallback = (product) => orderAdd.setProduct(product);
handlePaymentDetailsToggle();
handlePrivateNoteChange();
handleOrderNoteChange();
handleUpdateOrderStatusButton();
new InvoiceNoteManager();
const orderViewPageMessageHandler = new OrderViewPageMessagesHandler();
orderViewPageMessageHandler.listenForPredefinedMessageSelection();
orderViewPageMessageHandler.listenForFullMessagesOpen();
$(OrderViewPageMap.privateNoteToggleBtn).on('click', (event) => {
event.preventDefault();
togglePrivateNoteBlock();
});
$(OrderViewPageMap.orderNoteToggleBtn).on('click', (event) => {
event.preventDefault();
toggleOrderNoteBlock();
});
$(OrderViewPageMap.printOrderViewPageButton).on('click', () => {
const tempTitle = document.title;
document.title = $(OrderViewPageMap.mainDiv).data('orderTitle');
window.print();
document.title = tempTitle;
});
initAddCartRuleFormHandler();
initChangeAddressFormHandler();
initHookTabs();
function initHookTabs() {
$(OrderViewPageMap.orderHookTabsContainer)
.find('.nav-tabs li:first-child a')
.tab('show');
}
function handlePaymentDetailsToggle() {
$(OrderViewPageMap.orderPaymentDetailsBtn).on('click', (event) => {
const $paymentDetailRow = $(event.currentTarget)
.closest('tr')
.next(':first');
$paymentDetailRow.toggleClass('d-none');
});
}
function togglePrivateNoteBlock() {
const $block = $(OrderViewPageMap.privateNoteBlock);
const $btn = $(OrderViewPageMap.privateNoteToggleBtn);
const isPrivateNoteOpened = $btn.hasClass('is-opened');
if (isPrivateNoteOpened) {
$btn.removeClass('is-opened');
$block.addClass('d-none');
} else {
$btn.addClass('is-opened');
$block.removeClass('d-none');
}
const $icon = $btn.find('.material-icons');
$icon.text(isPrivateNoteOpened ? 'add' : 'remove');
}
function handlePrivateNoteChange() {
const $submitBtn = $(OrderViewPageMap.privateNoteSubmitBtn);
$(OrderViewPageMap.privateNoteInput).on('input', () => {
$submitBtn.prop('disabled', false);
});
}
function toggleOrderNoteBlock() {
const $block = $(OrderViewPageMap.orderNoteBlock);
const $btn = $(OrderViewPageMap.orderNoteToggleBtn);
const isNoteOpened = $btn.hasClass('is-opened');
$btn.toggleClass('is-opened', !isNoteOpened);
$block.toggleClass('d-none', isNoteOpened);
const $icon = $btn.find('.material-icons');
$icon.text(isNoteOpened ? 'add' : 'remove');
}
function handleOrderNoteChange() {
const $submitBtn = $(OrderViewPageMap.orderNoteSubmitBtn);
$(OrderViewPageMap.orderNoteInput).on('input', () => {
$submitBtn.prop('disabled', false);
});
}
function initAddCartRuleFormHandler() {
const $modal = $(OrderViewPageMap.addCartRuleModal);
const $form = $modal.find('form');
const $invoiceSelect = $modal.find(OrderViewPageMap.addCartRuleInvoiceIdSelect);
const $valueHelp = $modal.find(OrderViewPageMap.cartRuleHelpText);
const $valueInput = $form.find(OrderViewPageMap.addCartRuleValueInput);
const $valueFormGroup = $valueInput.closest('.form-group');
$modal.on('shown.bs.modal', () => {
$(OrderViewPageMap.addCartRuleSubmit).attr('disabled', true);
});
$form.find(OrderViewPageMap.addCartRuleNameInput).on('keyup', (event) => {
const cartRuleName = $(event.currentTarget).val();
$(OrderViewPageMap.addCartRuleSubmit).attr('disabled', cartRuleName.trim().length === 0);
});
$form.find(OrderViewPageMap.addCartRuleApplyOnAllInvoicesCheckbox).on('change', (event) => {
const isChecked = $(event.currentTarget).is(':checked');
$invoiceSelect.attr('disabled', isChecked);
});
$form.find(OrderViewPageMap.addCartRuleTypeSelect).on('change', (event) => {
const selectedCartRuleType = $(event.currentTarget).val();
const $valueUnit = $form.find(OrderViewPageMap.addCartRuleValueUnit);
if (selectedCartRuleType === DISCOUNT_TYPE_AMOUNT) {
$valueHelp.removeClass('d-none');
$valueUnit.html($valueUnit.data('currencySymbol'));
} else {
$valueHelp.addClass('d-none');
}
if (selectedCartRuleType === DISCOUNT_TYPE_PERCENT) {
$valueUnit.html('%');
}
if (selectedCartRuleType === DISCOUNT_TYPE_FREE_SHIPPING) {
$valueFormGroup.addClass('d-none');
$valueInput.attr('disabled', true);
} else {
$valueFormGroup.removeClass('d-none');
$valueInput.attr('disabled', false);
}
});
}
function handleUpdateOrderStatusButton() {
const $btn = $(OrderViewPageMap.updateOrderStatusActionBtn);
const $wrapper = $(OrderViewPageMap.updateOrderStatusActionInputWrapper);
$(OrderViewPageMap.updateOrderStatusActionInput).on('change', (event) => {
const $element = $(event.currentTarget);
const $option = $('option:selected', $element);
const selectedOrderStatusId = $element.val();
$wrapper.css('background-color', $option.data('background-color'));
$wrapper.toggleClass('is-bright', $option.data('is-bright') !== undefined);
$btn.prop('disabled', parseInt(selectedOrderStatusId, 10) === $btn.data('orderStatusId'));
});
}
function initChangeAddressFormHandler() {
const $modal = $(OrderViewPageMap.updateCustomerAddressModal);
$(OrderViewPageMap.openOrderAddressUpdateModalBtn).on('click', (event) => {
$modal.find(OrderViewPageMap.updateOrderAddressTypeInput).val($(event.currentTarget).data('addressType'));
});
}
});

View File

@@ -0,0 +1,42 @@
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/OSL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
* versions in the future. If you wish to customize PrestaShop for your
* needs please refer to https://devdocs.prestashop.com/ for more information.
*
* @author PrestaShop SA and Contributors <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
*/
import Router from '@components/router';
import OrderViewPageMap from '@pages/order/OrderViewPageMap';
const {$} = window;
export default class OrderDiscountsRefresher {
constructor() {
this.router = new Router();
}
refresh(orderId) {
$.ajax(this.router.generate('admin_orders_get_discounts', {orderId}))
.then((response) => {
$(OrderViewPageMap.productDiscountList.list).replaceWith(response);
});
}
}

View File

@@ -0,0 +1,46 @@
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/OSL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
* versions in the future. If you wish to customize PrestaShop for your
* needs please refer to https://devdocs.prestashop.com/ for more information.
*
* @author PrestaShop SA and Contributors <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
*/
import Router from '@components/router';
import OrderViewPageMap from '@pages/order/OrderViewPageMap';
import InvoiceNoteManager from '../invoice-note-manager';
const {$} = window;
export default class OrderDocumentsRefresher {
constructor() {
this.router = new Router();
this.invoiceNoteManager = new InvoiceNoteManager();
}
refresh(orderId) {
$.getJSON(this.router.generate('admin_orders_get_documents', {orderId}))
.then((response) => {
$(OrderViewPageMap.orderDocumentsTabCount).text(response.total);
$(OrderViewPageMap.orderDocumentsTabBody).html(response.html);
this.invoiceNoteManager.setupListeners();
});
}
}

View File

@@ -0,0 +1,66 @@
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/OSL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
* versions in the future. If you wish to customize PrestaShop for your
* needs please refer to https://devdocs.prestashop.com/ for more information.
*
* @author PrestaShop SA and Contributors <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
*/
import Router from '@components/router';
import OrderViewPageMap from '@pages/order/OrderViewPageMap';
const {$} = window;
export default class OrderInvoicesRefresher {
constructor() {
this.router = new Router();
}
refresh(orderId) {
$.getJSON(this.router.generate('admin_orders_get_invoices', {orderId}))
.then((response) => {
if (!response || !response.invoices || Object.keys(response.invoices).length <= 0) {
return;
}
const $paymentInvoiceSelect = $(OrderViewPageMap.orderPaymentInvoiceSelect);
const $addProductInvoiceSelect = $(OrderViewPageMap.productAddInvoiceSelect);
const $existingInvoicesGroup = $addProductInvoiceSelect.find('optgroup:first');
const $productEditInvoiceSelect = $(OrderViewPageMap.productEditInvoiceSelect);
const $addDiscountInvoiceSelect = $(OrderViewPageMap.addCartRuleInvoiceIdSelect);
$existingInvoicesGroup.empty();
$paymentInvoiceSelect.empty();
$productEditInvoiceSelect.empty();
$addDiscountInvoiceSelect.empty();
Object.keys(response.invoices).forEach((invoiceName) => {
const invoiceId = response.invoices[invoiceName];
const invoiceNameWithoutPrice = invoiceName.split(' - ')[0];
$existingInvoicesGroup.append(`<option value="${invoiceId}">${invoiceNameWithoutPrice}</option>`);
$paymentInvoiceSelect.append(`<option value="${invoiceId}">${invoiceNameWithoutPrice}</option>`);
$productEditInvoiceSelect.append(`<option value="${invoiceId}">${invoiceNameWithoutPrice}</option>`);
$addDiscountInvoiceSelect.append(`<option value="${invoiceId}">${invoiceName}</option>`);
});
document.querySelector(OrderViewPageMap.productAddInvoiceSelect).selectedIndex = 0;
});
}
}

View File

@@ -0,0 +1,50 @@
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/OSL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
* versions in the future. If you wish to customize PrestaShop for your
* needs please refer to https://devdocs.prestashop.com/ for more information.
*
* @author PrestaShop SA and Contributors <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
*/
import Router from '@components/router';
import OrderViewPageMap from '@pages/order/OrderViewPageMap';
const {$} = window;
export default class OrderPaymentsRefresher {
constructor() {
this.router = new Router();
}
refresh(orderId) {
$.ajax(this.router.generate('admin_orders_get_payments', {orderId}))
.then(
(response) => {
$(OrderViewPageMap.viewOrderPaymentsAlert).remove();
$(`${OrderViewPageMap.viewOrderPaymentsBlock} .card-body`).prepend(response);
},
(response) => {
if (response.responseJSON && response.responseJSON.message) {
$.growl.error({message: response.responseJSON.message});
}
},
);
}
}

View File

@@ -0,0 +1,126 @@
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/OSL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
* versions in the future. If you wish to customize PrestaShop for your
* needs please refer to https://devdocs.prestashop.com/ for more information.
*
* @author PrestaShop SA and Contributors <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
*/
import Router from '@components/router';
import OrderViewPageMap from '@pages/order/OrderViewPageMap';
const {$} = window;
export default class OrderPricesRefresher {
constructor() {
this.router = new Router();
}
refresh(orderId) {
$.getJSON(this.router.generate('admin_orders_get_prices', {orderId})).then((response) => {
$(OrderViewPageMap.orderTotal).text(response.orderTotalFormatted);
$(OrderViewPageMap.orderDiscountsTotal).text(`-${response.discountsAmountFormatted}`);
$(OrderViewPageMap.orderDiscountsTotalContainer).toggleClass('d-none', !response.discountsAmountDisplayed);
$(OrderViewPageMap.orderProductsTotal).text(response.productsTotalFormatted);
$(OrderViewPageMap.orderShippingTotal).text(response.shippingTotalFormatted);
$(OrderViewPageMap.orderShippingTotalContainer).toggleClass('d-none', !response.shippingTotalDisplayed);
$(OrderViewPageMap.orderTaxesTotal).text(response.taxesTotalFormatted);
});
}
refreshProductPrices(orderId) {
$.getJSON(this.router.generate('admin_orders_product_prices', {orderId})).then((productPricesList) => {
productPricesList.forEach((productPrices) => {
const orderProductTrId = OrderViewPageMap.productsTableRow(productPrices.orderDetailId);
let $quantity = $(productPrices.quantity);
if (productPrices.quantity > 1) {
$quantity = $quantity.wrap('<span class="badge badge-secondary rounded-circle"></span>');
}
$(`${orderProductTrId} ${OrderViewPageMap.productEditUnitPrice}`).text(productPrices.unitPrice);
$(`${orderProductTrId} ${OrderViewPageMap.productEditQuantity}`).html($quantity.html());
$(`${orderProductTrId} ${OrderViewPageMap.productEditAvailableQuantity}`).text(productPrices.availableQuantity);
$(`${orderProductTrId} ${OrderViewPageMap.productEditTotalPrice}`).text(productPrices.totalPrice);
// update order row price values
const productEditButton = $(OrderViewPageMap.productEditBtn(productPrices.orderDetailId));
productEditButton.data('product-price-tax-incl', productPrices.unitPriceTaxInclRaw);
productEditButton.data('product-price-tax-excl', productPrices.unitPriceTaxExclRaw);
productEditButton.data('product-quantity', productPrices.quantity);
});
});
}
/**
* This method will check if the same product is already present in the order
* and if so and if the price of the 2 products doesn't match will return either
* 'invoice' if the 2 products are in 2 different invoices or 'product' if the 2 products
* are in the same invoice (or no invoice yet). Only products that have different customizations
* can be twice in a same invoice.
* Will return null if no matching products are found.
*/
checkOtherProductPricesMatch(givenPrice, productId, combinationId, invoiceId, orderDetailId) {
const productRows = document.querySelectorAll('tr.cellProduct');
// We convert the expected values into int/float to avoid a type mismatch that would be wrongly interpreted
const expectedProductId = Number(productId);
const expectedCombinationId = Number(combinationId);
const expectedGivenPrice = Number(givenPrice);
let unmatchingInvoicePriceExists = false;
let unmatchingProductPriceExists = false;
productRows.forEach((productRow) => {
const productRowId = $(productRow).attr('id');
// No need to check edited row (especially if it's the only one for this product)
if (orderDetailId && productRowId === `orderProduct_${orderDetailId}`) {
return;
}
const productEditBtn = $(`#${productRowId} ${OrderViewPageMap.productEditButtons}`);
const currentOrderInvoiceId = Number(productEditBtn.data('order-invoice-id'));
const currentProductId = Number(productEditBtn.data('product-id'));
const currentCombinationId = Number(productEditBtn.data('combination-id'));
if (currentProductId !== expectedProductId || currentCombinationId !== expectedCombinationId) {
return;
}
if (expectedGivenPrice !== Number(productEditBtn.data('product-price-tax-incl'))) {
if (invoiceId === '' || (invoiceId && currentOrderInvoiceId && invoiceId === currentOrderInvoiceId)) {
unmatchingProductPriceExists = true;
} else {
unmatchingInvoicePriceExists = true;
}
}
});
if (unmatchingInvoicePriceExists) {
return 'invoice';
}
if (unmatchingProductPriceExists) {
return 'product';
}
return null;
}
}

View File

@@ -0,0 +1,52 @@
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/OSL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
* versions in the future. If you wish to customize PrestaShop for your
* needs please refer to https://devdocs.prestashop.com/ for more information.
*
* @author PrestaShop SA and Contributors <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
*/
export default class OrderPrices {
calculateTaxExcluded(taxIncluded, taxRatePerCent, currencyPrecision) {
let priceTaxIncl = parseFloat(taxIncluded);
if (priceTaxIncl < 0 || Number.isNaN(priceTaxIncl)) {
priceTaxIncl = 0;
}
const taxRate = taxRatePerCent / 100 + 1;
return window.ps_round(priceTaxIncl / taxRate, currencyPrecision);
}
calculateTaxIncluded(taxExcluded, taxRatePerCent, currencyPrecision) {
let priceTaxExcl = parseFloat(taxExcluded);
if (priceTaxExcl < 0 || Number.isNaN(priceTaxExcl)) {
priceTaxExcl = 0;
}
const taxRate = taxRatePerCent / 100 + 1;
return window.ps_round(priceTaxExcl * taxRate, currencyPrecision);
}
calculateTotalPrice(quantity, unitPrice, currencyPrecision) {
return window.ps_round(unitPrice * quantity, currencyPrecision);
}
}

View File

@@ -0,0 +1,121 @@
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/OSL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
* versions in the future. If you wish to customize PrestaShop for your
* needs please refer to https://devdocs.prestashop.com/ for more information.
*
* @author PrestaShop SA and Contributors <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
*/
import Router from '@components/router';
import OrderViewPageMap from '@pages/order/OrderViewPageMap';
const {$} = window;
export default class OrderProductAutocomplete {
constructor(input) {
this.activeSearchRequest = null;
this.router = new Router();
this.input = input;
this.results = [];
this.dropdownMenu = $(OrderViewPageMap.productSearchInputAutocompleteMenu);
/**
* Permit to link to each value of dropdown a callback after item is clicked
*/
this.onItemClickedCallback = () => {};
}
listenForSearch() {
this.input.on('click', (event) => {
event.stopImmediatePropagation();
this.updateResults(this.results);
});
this.input.on('keyup', (event) => this.delaySearch(event.currentTarget));
$(document).on('click', () => this.dropdownMenu.hide());
}
delaySearch(input) {
clearTimeout(this.searchTimeoutId);
// Search only if the search phrase length is greater than 2 characters
if (input.value.length < 2) {
return;
}
this.searchTimeoutId = setTimeout(() => {
this.search(input.value, $(input).data('currency'), $(input).data('order'));
}, 300);
}
search(search, currency, orderId) {
const params = {search_phrase: search};
if (currency) {
params.currency_id = currency;
}
if (orderId) {
params.order_id = orderId;
}
if (this.activeSearchRequest !== null) {
this.activeSearchRequest.abort();
}
this.activeSearchRequest = $.get(this.router.generate('admin_orders_products_search', params));
this.activeSearchRequest
.then((response) => this.updateResults(response))
.always(() => {
this.activeSearchRequest = null;
});
}
updateResults(results) {
this.dropdownMenu.empty();
if (!results || !results.products || Object.keys(results.products).length <= 0) {
this.dropdownMenu.hide();
return;
}
this.results = results.products;
Object.values(this.results).forEach((val) => {
const link = $(`<a class="dropdown-item" data-id="${val.productId}" href="#">${val.name}</a>`);
link.on('click', (event) => {
event.preventDefault();
this.onItemClicked($(event.target).data('id'));
});
this.dropdownMenu.append(link);
});
this.dropdownMenu.show();
}
onItemClicked(id) {
const selectedProduct = this.results.filter((product) => product.productId === id);
if (selectedProduct.length !== 0) {
this.input.val(selectedProduct[0].name);
this.onItemClickedCallback(selectedProduct[0]);
}
}
}

View File

@@ -0,0 +1,299 @@
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/OSL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
* versions in the future. If you wish to customize PrestaShop for your
* needs please refer to https://devdocs.prestashop.com/ for more information.
*
* @author PrestaShop SA and Contributors <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
*/
import Router from '@components/router';
import OrderViewPageMap from '@pages/order/OrderViewPageMap';
import {EventEmitter} from '@components/event-emitter';
import OrderViewEventMap from '@pages/order/view/order-view-event-map';
import OrderPrices from '@pages/order/view/order-prices';
import OrderProductRenderer from '@pages/order/view/order-product-renderer';
import ConfirmModal from '@components/modal';
import OrderPricesRefresher from '@pages/order/view/order-prices-refresher';
const {$} = window;
export default class OrderProductAdd {
constructor() {
this.router = new Router();
this.productAddActionBtn = $(OrderViewPageMap.productAddActionBtn);
this.productIdInput = $(OrderViewPageMap.productAddIdInput);
this.combinationsBlock = $(OrderViewPageMap.productAddCombinationsBlock);
this.combinationsSelect = $(OrderViewPageMap.productAddCombinationsSelect);
this.priceTaxIncludedInput = $(OrderViewPageMap.productAddPriceTaxInclInput);
this.priceTaxExcludedInput = $(OrderViewPageMap.productAddPriceTaxExclInput);
this.taxRateInput = $(OrderViewPageMap.productAddTaxRateInput);
this.quantityInput = $(OrderViewPageMap.productAddQuantityInput);
this.availableText = $(OrderViewPageMap.productAddAvailableText);
this.locationText = $(OrderViewPageMap.productAddLocationText);
this.totalPriceText = $(OrderViewPageMap.productAddTotalPriceText);
this.invoiceSelect = $(OrderViewPageMap.productAddInvoiceSelect);
this.freeShippingSelect = $(OrderViewPageMap.productAddFreeShippingSelect);
this.productAddMenuBtn = $(OrderViewPageMap.productAddBtn);
this.available = null;
this.setupListener();
this.product = {};
this.currencyPrecision = $(OrderViewPageMap.productsTable).data('currencyPrecision');
this.priceTaxCalculator = new OrderPrices();
this.orderProductRenderer = new OrderProductRenderer();
this.orderPricesRefresher = new OrderPricesRefresher();
this.isOrderTaxIncluded = $(OrderViewPageMap.productAddRow).data('isOrderTaxIncluded');
this.taxExcluded = null;
this.taxIncluded = null;
}
setupListener() {
this.combinationsSelect.on('change', (event) => {
const taxExcluded = window.ps_round(
$(event.currentTarget)
.find(':selected')
.data('priceTaxExcluded'),
this.currencyPrecision,
);
this.priceTaxExcludedInput.val(taxExcluded);
this.taxExcluded = parseFloat(taxExcluded);
const taxIncluded = window.ps_round(
$(event.currentTarget)
.find(':selected')
.data('priceTaxIncluded'),
this.currencyPrecision,
);
this.priceTaxIncludedInput.val(taxIncluded);
this.taxIncluded = parseFloat(taxIncluded);
this.locationText.html(
$(event.currentTarget)
.find(':selected')
.data('location'),
);
this.available = $(event.currentTarget)
.find(':selected')
.data('stock');
this.quantityInput.trigger('change');
this.orderProductRenderer.toggleColumn(OrderViewPageMap.productsCellLocation);
});
this.quantityInput.on('change keyup', (event) => {
if (this.available !== null) {
const newQuantity = Number(event.target.value);
const remainingAvailable = this.available - newQuantity;
const availableOutOfStock = this.availableText.data('availableOutOfStock');
this.availableText.text(remainingAvailable);
this.availableText.toggleClass('text-danger font-weight-bold', remainingAvailable < 0);
const disableAddActionBtn = newQuantity <= 0 || (remainingAvailable < 0 && !availableOutOfStock);
this.productAddActionBtn.prop('disabled', disableAddActionBtn);
this.invoiceSelect.prop('disabled', !availableOutOfStock && remainingAvailable < 0);
this.taxIncluded = parseFloat(this.priceTaxIncludedInput.val());
this.totalPriceText.html(
this.priceTaxCalculator.calculateTotalPrice(
newQuantity,
this.isOrderTaxIncluded ? this.taxIncluded : this.taxExcluded,
this.currencyPrecision,
),
);
}
});
this.productIdInput.on('change', () => {
this.productAddActionBtn.removeAttr('disabled');
this.invoiceSelect.removeAttr('disabled');
});
this.priceTaxIncludedInput.on('change keyup', (event) => {
this.taxIncluded = parseFloat(event.target.value);
this.taxExcluded = this.priceTaxCalculator.calculateTaxExcluded(
this.taxIncluded,
this.taxRateInput.val(),
this.currencyPrecision,
);
const quantity = parseInt(this.quantityInput.val(), 10);
this.priceTaxExcludedInput.val(this.taxExcluded);
this.totalPriceText.html(
this.priceTaxCalculator.calculateTotalPrice(
quantity,
this.isOrderTaxIncluded ? this.taxIncluded : this.taxExcluded,
this.currencyPrecision,
),
);
});
this.priceTaxExcludedInput.on('change keyup', (event) => {
this.taxExcluded = parseFloat(event.target.value);
this.taxIncluded = this.priceTaxCalculator.calculateTaxIncluded(
this.taxExcluded,
this.taxRateInput.val(),
this.currencyPrecision,
);
const quantity = parseInt(this.quantityInput.val(), 10);
this.priceTaxIncludedInput.val(this.taxIncluded);
this.totalPriceText.html(
this.priceTaxCalculator.calculateTotalPrice(
quantity,
this.isOrderTaxIncluded ? this.taxIncluded : this.taxExcluded,
this.currencyPrecision,
),
);
});
this.productAddActionBtn.on('click', (event) => this.confirmNewInvoice(event));
this.invoiceSelect.on('change', () => this.orderProductRenderer.toggleProductAddNewInvoiceInfo());
}
setProduct(product) {
this.productIdInput.val(product.productId).trigger('change');
const taxExcluded = window.ps_round(product.priceTaxExcl, this.currencyPrecision);
this.priceTaxExcludedInput.val(taxExcluded);
this.taxExcluded = parseFloat(taxExcluded);
const taxIncluded = window.ps_round(product.priceTaxIncl, this.currencyPrecision);
this.priceTaxIncludedInput.val(taxIncluded);
this.taxIncluded = parseFloat(taxIncluded);
this.taxRateInput.val(product.taxRate);
this.locationText.html(product.location);
this.available = product.stock;
this.availableText.data('availableOutOfStock', product.availableOutOfStock);
this.quantityInput.val(1);
this.quantityInput.trigger('change');
this.setCombinations(product.combinations);
this.orderProductRenderer.toggleColumn(OrderViewPageMap.productsCellLocation);
}
setCombinations(combinations) {
this.combinationsSelect.empty();
Object.values(combinations).forEach((val) => {
this.combinationsSelect.append(
/* eslint-disable-next-line max-len */
`<option value="${val.attributeCombinationId}" data-price-tax-excluded="${val.priceTaxExcluded}" data-price-tax-included="${val.priceTaxIncluded}" data-stock="${val.stock}" data-location="${val.location}">${val.attribute}</option>`,
);
});
this.combinationsBlock.toggleClass('d-none', Object.keys(combinations).length === 0);
if (Object.keys(combinations).length > 0) {
this.combinationsSelect.trigger('change');
}
}
addProduct(orderId) {
this.productAddActionBtn.prop('disabled', true);
this.invoiceSelect.prop('disabled', true);
this.combinationsSelect.prop('disabled', true);
const params = {
product_id: this.productIdInput.val(),
combination_id: $(':selected', this.combinationsSelect).val(),
price_tax_incl: this.priceTaxIncludedInput.val(),
price_tax_excl: this.priceTaxExcludedInput.val(),
quantity: this.quantityInput.val(),
invoice_id: this.invoiceSelect.val(),
free_shipping: this.freeShippingSelect.prop('checked'),
};
$.ajax({
url: this.router.generate('admin_orders_add_product', {orderId}),
method: 'POST',
data: params,
}).then(
(response) => {
EventEmitter.emit(OrderViewEventMap.productAddedToOrder, {
orderId,
orderProductId: params.product_id,
newRow: response,
});
},
(response) => {
this.productAddActionBtn.prop('disabled', false);
this.invoiceSelect.prop('disabled', false);
this.combinationsSelect.prop('disabled', false);
if (response.responseJSON && response.responseJSON.message) {
$.growl.error({message: response.responseJSON.message});
}
},
);
}
confirmNewInvoice(event) {
const invoiceId = parseInt(this.invoiceSelect.val(), 10);
const orderId = $(event.currentTarget).data('orderId');
// Explicit 0 value is used when we the user selected New Invoice
if (invoiceId === 0) {
const modal = new ConfirmModal(
{
id: 'modal-confirm-new-invoice',
confirmTitle: this.invoiceSelect.data('modal-title'),
confirmMessage: this.invoiceSelect.data('modal-body'),
confirmButtonLabel: this.invoiceSelect.data('modal-apply'),
closeButtonLabel: this.invoiceSelect.data('modal-cancel'),
},
() => {
this.confirmNewPrice(orderId, invoiceId);
},
);
modal.show();
} else {
// Last case is Nan, the selector is not even present, we simply add product and let the BO handle it
this.addProduct(orderId);
}
}
confirmNewPrice(orderId, invoiceId) {
const combinationValue = $(':selected', this.combinationsSelect).val();
const combinationId = typeof combinationValue === 'undefined' ? 0 : combinationValue;
const productPriceMatch = this.orderPricesRefresher.checkOtherProductPricesMatch(
this.priceTaxIncludedInput.val(),
this.productIdInput.val(),
combinationId,
invoiceId,
);
if (productPriceMatch === 'invoice') {
const modalEditPrice = new ConfirmModal(
{
id: 'modal-confirm-new-price',
confirmTitle: this.invoiceSelect.data('modal-edit-price-title'),
confirmMessage: this.invoiceSelect.data('modal-edit-price-body'),
confirmButtonLabel: this.invoiceSelect.data('modal-edit-price-apply'),
closeButtonLabel: this.invoiceSelect.data('modal-edit-price-cancel'),
},
() => {
this.addProduct(orderId);
},
);
modalEditPrice.show();
} else {
this.addProduct(orderId);
}
}
}

View File

@@ -0,0 +1,266 @@
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/OSL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
* versions in the future. If you wish to customize PrestaShop for your
* needs please refer to https://devdocs.prestashop.com/ for more information.
*
* @author PrestaShop SA and Contributors <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
*/
import Router from '@components/router';
import OrderViewPageMap from '@pages/order/OrderViewPageMap';
import {NumberFormatter} from '@app/cldr';
const {$} = window;
/**
* manages all product cancel actions, that includes all refund operations
*/
export default class OrderProductCancel {
constructor() {
this.router = new Router();
this.cancelProductForm = $(OrderViewPageMap.cancelProduct.form);
this.orderId = this.cancelProductForm.data('orderId');
this.orderDelivered = parseInt(this.cancelProductForm.data('isDelivered'), 10) === 1;
this.isTaxIncluded = parseInt(this.cancelProductForm.data('isTaxIncluded'), 10) === 1;
this.discountsAmount = parseFloat(this.cancelProductForm.data('discountsAmount'));
this.currencyFormatter = NumberFormatter.build(
this.cancelProductForm.data('priceSpecification'),
);
this.useAmountInputs = true;
this.listenForInputs();
}
showPartialRefund() {
// Always start by hiding elements then show the others, since some elements are common
this.hideCancelElements();
$(OrderViewPageMap.cancelProduct.toggle.partialRefund).show();
this.useAmountInputs = true;
this.initForm(
$(OrderViewPageMap.cancelProduct.buttons.save).data('partialRefundLabel'),
this.router.generate('admin_orders_partial_refund', {
orderId: this.orderId,
}),
'partial-refund',
);
}
showStandardRefund() {
// Always start by hiding elements then show the others, since some elements are common
this.hideCancelElements();
$(OrderViewPageMap.cancelProduct.toggle.standardRefund).show();
this.useAmountInputs = false;
this.initForm(
$(OrderViewPageMap.cancelProduct.buttons.save).data('standardRefundLabel'),
this.router.generate('admin_orders_standard_refund', {
orderId: this.orderId,
}),
'standard-refund',
);
}
showReturnProduct() {
// Always start by hiding elements then show the others, since some elements are common
this.hideCancelElements();
$(OrderViewPageMap.cancelProduct.toggle.returnProduct).show();
this.useAmountInputs = false;
this.initForm(
$(OrderViewPageMap.cancelProduct.buttons.save).data('returnProductLabel'),
this.router.generate('admin_orders_return_product', {
orderId: this.orderId,
}),
'return-product',
);
}
hideRefund() {
this.hideCancelElements();
$(OrderViewPageMap.cancelProduct.table.actions).show();
}
hideCancelElements() {
$(OrderViewPageMap.cancelProduct.toggle.standardRefund).hide();
$(OrderViewPageMap.cancelProduct.toggle.partialRefund).hide();
$(OrderViewPageMap.cancelProduct.toggle.returnProduct).hide();
$(OrderViewPageMap.cancelProduct.table.actions).hide();
}
initForm(actionName, formAction, formClass) {
this.updateVoucherRefund();
this.cancelProductForm.prop('action', formAction);
this.cancelProductForm
.removeClass('standard-refund partial-refund return-product cancel-product')
.addClass(formClass);
$(OrderViewPageMap.cancelProduct.buttons.save).html(actionName);
$(OrderViewPageMap.cancelProduct.table.header).html(actionName);
$(OrderViewPageMap.cancelProduct.checkboxes.restock).prop('checked', this.orderDelivered);
$(OrderViewPageMap.cancelProduct.checkboxes.creditSlip).prop('checked', true);
$(OrderViewPageMap.cancelProduct.checkboxes.voucher).prop('checked', false);
}
listenForInputs() {
$(document).on('change', OrderViewPageMap.cancelProduct.inputs.quantity, (event) => {
const $productQuantityInput = $(event.target);
const $parentCell = $productQuantityInput.parents(OrderViewPageMap.cancelProduct.table.cell);
const $productAmount = $parentCell.find(OrderViewPageMap.cancelProduct.inputs.amount);
const productQuantity = parseInt($productQuantityInput.val(), 10);
if (productQuantity <= 0) {
$productAmount.val(0);
this.updateVoucherRefund();
return;
}
const priceFieldName = this.isTaxIncluded ? 'productPriceTaxIncl' : 'productPriceTaxExcl';
const productUnitPrice = parseFloat($productQuantityInput.data(priceFieldName));
const amountRefundable = parseFloat($productQuantityInput.data('amountRefundable'));
const guessedAmount = productUnitPrice * productQuantity < amountRefundable
? productUnitPrice * productQuantity
: amountRefundable;
const amountValue = parseFloat($productAmount.val());
if (this.useAmountInputs) {
this.updateAmountInput($productQuantityInput);
}
if ($productAmount.val() === '' || amountValue === 0 || amountValue > guessedAmount) {
$productAmount.val(guessedAmount);
this.updateVoucherRefund();
}
});
$(document).on('change', OrderViewPageMap.cancelProduct.inputs.amount, () => {
this.updateVoucherRefund();
});
$(document).on('change', OrderViewPageMap.cancelProduct.inputs.selector, (event) => {
const $productCheckbox = $(event.target);
const $parentCell = $productCheckbox.parents(OrderViewPageMap.cancelProduct.table.cell);
const productQuantityInput = $parentCell.find(OrderViewPageMap.cancelProduct.inputs.quantity);
const refundableQuantity = parseInt(productQuantityInput.data('quantityRefundable'), 10);
const productQuantity = parseInt(productQuantityInput.val(), 10);
if (!$productCheckbox.is(':checked')) {
productQuantityInput.val(0);
} else if (Number.isNaN(productQuantity) || productQuantity === 0) {
productQuantityInput.val(refundableQuantity);
}
this.updateVoucherRefund();
});
}
updateAmountInput($productQuantityInput) {
const $parentCell = $productQuantityInput.parents(OrderViewPageMap.cancelProduct.table.cell);
const $productAmount = $parentCell.find(OrderViewPageMap.cancelProduct.inputs.amount);
const productQuantity = parseInt($productQuantityInput.val(), 10);
if (productQuantity <= 0) {
$productAmount.val(0);
return;
}
const priceFieldName = this.isTaxIncluded ? 'productPriceTaxIncl' : 'productPriceTaxExcl';
const productUnitPrice = parseFloat($productQuantityInput.data(priceFieldName));
const amountRefundable = parseFloat($productQuantityInput.data('amountRefundable'));
const guessedAmount = productUnitPrice * productQuantity < amountRefundable
? productUnitPrice * productQuantity
: amountRefundable;
const amountValue = parseFloat($productAmount.val());
if ($productAmount.val() === '' || amountValue === 0 || amountValue > guessedAmount) {
$productAmount.val(guessedAmount);
}
}
getRefundAmount() {
let totalAmount = 0;
if (this.useAmountInputs) {
$(OrderViewPageMap.cancelProduct.inputs.amount).each((index, amount) => {
const floatValue = parseFloat(amount.value);
totalAmount += !Number.isNaN(floatValue) ? floatValue : 0;
});
} else {
$(OrderViewPageMap.cancelProduct.inputs.quantity).each((index, quantity) => {
const $quantityInput = $(quantity);
const priceFieldName = this.isTaxIncluded ? 'productPriceTaxIncl' : 'productPriceTaxExcl';
const productUnitPrice = parseFloat($quantityInput.data(priceFieldName));
const productQuantity = parseInt($quantityInput.val(), 10);
totalAmount += productQuantity * productUnitPrice;
});
}
return totalAmount;
}
updateVoucherRefund() {
const refundAmount = this.getRefundAmount();
this.updateVoucherRefundTypeLabel(
$(OrderViewPageMap.cancelProduct.radios.voucherRefundType.productPrices),
refundAmount,
);
const refundVoucherExcluded = refundAmount - this.discountsAmount;
this.updateVoucherRefundTypeLabel(
$(OrderViewPageMap.cancelProduct.radios.voucherRefundType.productPricesVoucherExcluded),
refundVoucherExcluded,
);
// Disable voucher excluded option when the voucher amount is too high
if (refundVoucherExcluded < 0) {
$(OrderViewPageMap.cancelProduct.radios.voucherRefundType.productPricesVoucherExcluded)
.prop('checked', false)
.prop('disabled', true);
$(OrderViewPageMap.cancelProduct.radios.voucherRefundType.productPrices).prop(
'checked',
true,
);
$(OrderViewPageMap.cancelProduct.radios.voucherRefundType.negativeErrorMessage).show();
} else {
$(OrderViewPageMap.cancelProduct.radios.voucherRefundType.productPricesVoucherExcluded).prop(
'disabled',
false,
);
$(OrderViewPageMap.cancelProduct.radios.voucherRefundType.negativeErrorMessage).hide();
}
}
updateVoucherRefundTypeLabel($input, refundAmount) {
const defaultLabel = $input.data('defaultLabel');
const $label = $input.parents('label');
const formattedAmount = this.currencyFormatter.format(refundAmount);
// Change the ending text part only to avoid removing the input (the EOL is on purpose for better display)
$label.get(0).lastChild.nodeValue = `
${defaultLabel} ${formattedAmount}`;
}
showCancelProductForm() {
const cancelProductRoute = this.router.generate('admin_orders_cancellation', {orderId: this.orderId});
this.initForm(
$(OrderViewPageMap.cancelProduct.buttons.save).data('cancelLabel'),
cancelProductRoute,
'cancel-product',
);
this.hideCancelElements();
$(OrderViewPageMap.cancelProduct.toggle.cancelProducts).show();
}
}

View File

@@ -0,0 +1,249 @@
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/OSL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
* versions in the future. If you wish to customize PrestaShop for your
* needs please refer to https://devdocs.prestashop.com/ for more information.
*
* @author PrestaShop SA and Contributors <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
*/
import Router from '@components/router';
import OrderViewPageMap from '@pages/order/OrderViewPageMap';
import {EventEmitter} from '@components/event-emitter';
import OrderViewEventMap from '@pages/order/view/order-view-event-map';
import OrderPrices from '@pages/order/view/order-prices';
import ConfirmModal from '@components/modal';
import OrderPricesRefresher from '@pages/order/view/order-prices-refresher';
const {$} = window;
export default class OrderProductEdit {
constructor(orderDetailId) {
this.router = new Router();
this.orderDetailId = orderDetailId;
this.productRow = $(`#orderProduct_${this.orderDetailId}`);
this.product = {};
this.currencyPrecision = $(OrderViewPageMap.productsTable).data('currencyPrecision');
this.priceTaxCalculator = new OrderPrices();
this.productEditSaveBtn = $(OrderViewPageMap.productEditSaveBtn);
this.quantityInput = $(OrderViewPageMap.productEditQuantityInput);
this.orderPricesRefresher = new OrderPricesRefresher();
}
setupListener() {
this.quantityInput.on('change keyup', (event) => {
const newQuantity = Number(event.target.value);
const availableQuantity = parseInt($(event.currentTarget).data('availableQuantity'), 10);
const previousQuantity = parseInt(this.quantityInput.data('previousQuantity'), 10);
const remainingAvailable = availableQuantity - (newQuantity - previousQuantity);
const availableOutOfStock = this.availableText.data('availableOutOfStock');
this.quantity = newQuantity;
this.availableText.text(remainingAvailable);
this.availableText.toggleClass('text-danger font-weight-bold', remainingAvailable < 0);
this.updateTotal();
const disableEditActionBtn = newQuantity <= 0 || (remainingAvailable < 0 && !availableOutOfStock);
this.productEditSaveBtn.prop('disabled', disableEditActionBtn);
});
this.productEditInvoiceSelect.on('change', () => {
this.productEditSaveBtn.prop('disabled', false);
});
this.priceTaxIncludedInput.on('change keyup', (event) => {
this.taxIncluded = parseFloat(event.target.value);
this.taxExcluded = this.priceTaxCalculator.calculateTaxExcluded(
this.taxIncluded,
this.taxRate,
this.currencyPrecision,
);
this.priceTaxExcludedInput.val(this.taxExcluded);
this.updateTotal();
});
this.priceTaxExcludedInput.on('change keyup', (event) => {
this.taxExcluded = parseFloat(event.target.value);
this.taxIncluded = this.priceTaxCalculator.calculateTaxIncluded(
this.taxExcluded,
this.taxRate,
this.currencyPrecision,
);
this.priceTaxIncludedInput.val(this.taxIncluded);
this.updateTotal();
});
this.productEditSaveBtn.on('click', (event) => {
const $btn = $(event.currentTarget);
const confirmed = window.confirm($btn.data('updateMessage'));
if (!confirmed) {
return;
}
$btn.prop('disabled', true);
this.handleEditProductWithConfirmationModal(event);
});
this.productEditCancelBtn.on('click', () => {
EventEmitter.emit(OrderViewEventMap.productEditionCanceled, {
orderDetailId: this.orderDetailId,
});
});
}
updateTotal() {
const updatedTotal = this.priceTaxCalculator.calculateTotalPrice(
this.quantity,
this.isOrderTaxIncluded ? this.taxIncluded : this.taxExcluded,
this.currencyPrecision,
);
this.priceTotalText.html(updatedTotal);
this.productEditSaveBtn.prop('disabled', updatedTotal === this.initialTotal);
}
displayProduct(product) {
this.productRowEdit = $(OrderViewPageMap.productEditRowTemplate).clone(true);
this.productRowEdit.attr('id', `editOrderProduct_${this.orderDetailId}`);
this.productRowEdit.find('*[id]').each(function removeAllIds() {
$(this).removeAttr('id');
});
// Find controls
this.productEditSaveBtn = this.productRowEdit.find(OrderViewPageMap.productEditSaveBtn);
this.productEditCancelBtn = this.productRowEdit.find(OrderViewPageMap.productEditCancelBtn);
this.productEditInvoiceSelect = this.productRowEdit.find(OrderViewPageMap.productEditInvoiceSelect);
this.productEditImage = this.productRowEdit.find(OrderViewPageMap.productEditImage);
this.productEditName = this.productRowEdit.find(OrderViewPageMap.productEditName);
this.priceTaxIncludedInput = this.productRowEdit.find(OrderViewPageMap.productEditPriceTaxInclInput);
this.priceTaxExcludedInput = this.productRowEdit.find(OrderViewPageMap.productEditPriceTaxExclInput);
this.quantityInput = this.productRowEdit.find(OrderViewPageMap.productEditQuantityInput);
this.locationText = this.productRowEdit.find(OrderViewPageMap.productEditLocationText);
this.availableText = this.productRowEdit.find(OrderViewPageMap.productEditAvailableText);
this.priceTotalText = this.productRowEdit.find(OrderViewPageMap.productEditTotalPriceText);
// Init input values
this.priceTaxExcludedInput.val(
window.ps_round(product.price_tax_excl, this.currencyPrecision),
);
this.priceTaxIncludedInput.val(
window.ps_round(product.price_tax_incl, this.currencyPrecision),
);
this.quantityInput.val(product.quantity)
.data('availableQuantity', product.availableQuantity)
.data('previousQuantity', product.quantity);
this.availableText.data('availableOutOfStock', product.availableOutOfStock);
// set this product's orderInvoiceId as selected
if (product.orderInvoiceId) {
this.productEditInvoiceSelect.val(product.orderInvoiceId);
}
// Init editor data
this.taxRate = product.tax_rate;
this.initialTotal = this.priceTaxCalculator.calculateTotalPrice(
product.quantity,
product.isOrderTaxIncluded ? product.price_tax_incl : product.price_tax_excl,
this.currencyPrecision,
);
this.isOrderTaxIncluded = product.isOrderTaxIncluded;
this.quantity = product.quantity;
this.taxIncluded = product.price_tax_incl;
this.taxExcluded = product.price_tax_excl;
// Copy product content in cells
this.productEditImage.html(
this.productRow.find(OrderViewPageMap.productEditImage).html(),
);
this.productEditName.html(
this.productRow.find(OrderViewPageMap.productEditName).html(),
);
this.locationText.html(product.location);
this.availableText.html(product.availableQuantity);
this.priceTotalText.html(this.initialTotal);
this.productRow.addClass('d-none').after(this.productRowEdit.removeClass('d-none'));
this.setupListener();
}
handleEditProductWithConfirmationModal(event) {
const productEditBtn = $(`#orderProduct_${this.orderDetailId} ${OrderViewPageMap.productEditButtons}`);
const productId = productEditBtn.data('product-id');
const combinationId = productEditBtn.data('combination-id');
const orderInvoiceId = productEditBtn.data('order-invoice-id');
const productPriceMatch = this.orderPricesRefresher.checkOtherProductPricesMatch(
this.priceTaxIncludedInput.val(),
productId,
combinationId,
orderInvoiceId,
this.orderDetailId,
);
if (productPriceMatch === null) {
this.editProduct($(event.currentTarget).data('orderId'), this.orderDetailId);
return;
}
const dataSelector = productPriceMatch === 'product' ? this.priceTaxExcludedInput : this.productEditInvoiceSelect;
const modalEditPrice = new ConfirmModal(
{
id: 'modal-confirm-new-price',
confirmTitle: dataSelector.data('modal-edit-price-title'),
confirmMessage: dataSelector.data('modal-edit-price-body'),
confirmButtonLabel: dataSelector.data('modal-edit-price-apply'),
closeButtonLabel: dataSelector.data('modal-edit-price-cancel'),
},
() => {
this.editProduct($(event.currentTarget).data('orderId'), this.orderDetailId);
},
);
modalEditPrice.show();
}
editProduct(orderId, orderDetailId) {
const params = {
price_tax_incl: this.priceTaxIncludedInput.val(),
price_tax_excl: this.priceTaxExcludedInput.val(),
quantity: this.quantityInput.val(),
invoice: this.productEditInvoiceSelect.val(),
};
$.ajax({
url: this.router.generate('admin_orders_update_product', {
orderId,
orderDetailId,
}),
method: 'POST',
data: params,
}).then(
() => {
EventEmitter.emit(OrderViewEventMap.productUpdated, {
orderId,
orderDetailId,
});
},
(response) => {
if (response.responseJSON && response.responseJSON.message) {
$.growl.error({message: response.responseJSON.message});
}
},
);
}
}

View File

@@ -0,0 +1,66 @@
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/OSL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
* versions in the future. If you wish to customize PrestaShop for your
* needs please refer to https://devdocs.prestashop.com/ for more information.
*
* @author PrestaShop SA and Contributors <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
*/
import Router from '@components/router';
import {EventEmitter} from '@components/event-emitter';
import OrderViewEventMap from '@pages/order/view/order-view-event-map';
const {$} = window;
export default class OrderProductManager {
constructor() {
this.router = new Router();
}
handleDeleteProductEvent(event) {
event.preventDefault();
const $btn = $(event.currentTarget);
const confirmed = window.confirm($btn.data('deleteMessage'));
if (!confirmed) {
return;
}
$btn.pstooltip('dispose');
$btn.prop('disabled', true);
this.deleteProduct($btn.data('orderId'), $btn.data('orderDetailId'));
}
deleteProduct(orderId, orderDetailId) {
$.ajax(this.router.generate('admin_orders_delete_product', {orderId, orderDetailId}), {
method: 'POST',
}).then(() => {
EventEmitter.emit(OrderViewEventMap.productDeletedFromOrder, {
oldOrderDetailId: orderDetailId,
orderId,
});
}, (response) => {
if (response.responseJSON && response.responseJSON.message) {
$.growl.error({message: response.responseJSON.message});
}
});
}
}

View File

@@ -0,0 +1,307 @@
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/OSL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
* versions in the future. If you wish to customize PrestaShop for your
* needs please refer to https://devdocs.prestashop.com/ for more information.
*
* @author PrestaShop SA and Contributors <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
*/
import OrderViewPageMap from '@pages/order/OrderViewPageMap';
import OrderProductEdit from '@pages/order/view/order-product-edit';
import Router from '@components/router';
const {$} = window;
export default class OrderProductRenderer {
constructor() {
this.router = new Router();
}
addOrUpdateProductToList($productRow, newRow) {
if ($productRow.length > 0) {
$productRow.html($(newRow).html());
} else {
$(OrderViewPageMap.productAddRow).before(
$(newRow)
.hide()
.fadeIn(),
);
}
}
updateNumProducts(numProducts) {
$(OrderViewPageMap.productsCount).html(numProducts);
}
editProductFromList(
orderDetailId,
quantity,
priceTaxIncl,
priceTaxExcl,
taxRate,
location,
availableQuantity,
availableOutOfStock,
orderInvoiceId,
isOrderTaxIncluded,
) {
const $orderEdit = new OrderProductEdit(orderDetailId);
$orderEdit.displayProduct({
price_tax_excl: priceTaxExcl,
price_tax_incl: priceTaxIncl,
tax_rate: taxRate,
quantity,
location,
availableQuantity,
availableOutOfStock,
orderInvoiceId,
isOrderTaxIncluded,
});
$(OrderViewPageMap.productAddActionBtn).addClass('d-none');
$(OrderViewPageMap.productAddRow).addClass('d-none');
}
moveProductsPanelToModificationPosition(scrollTarget = 'body') {
$(OrderViewPageMap.productActionBtn).addClass('d-none');
$(
`${OrderViewPageMap.productAddActionBtn}, ${OrderViewPageMap.productAddRow}`,
).removeClass('d-none');
this.moveProductPanelToTop(scrollTarget);
}
moveProductsPanelToRefundPosition() {
this.resetAllEditRows();
$(
/* eslint-disable-next-line max-len */
`${OrderViewPageMap.productAddActionBtn}, ${OrderViewPageMap.productAddRow}, ${OrderViewPageMap.productActionBtn}`,
).addClass('d-none');
this.moveProductPanelToTop();
}
moveProductPanelToTop(scrollTarget = 'body') {
const $modificationPosition = $(
OrderViewPageMap.productModificationPosition,
);
if ($modificationPosition.find(OrderViewPageMap.productsPanel).length > 0) {
return;
}
$(OrderViewPageMap.productsPanel)
.detach()
.appendTo($modificationPosition);
$modificationPosition.closest('.row').removeClass('d-none');
// Show column location & refunded
this.toggleColumn(OrderViewPageMap.productsCellLocation);
this.toggleColumn(OrderViewPageMap.productsCellRefunded);
// Show all rows, hide pagination controls
const $rows = $(OrderViewPageMap.productsTable).find(
'tr[id^="orderProduct_"]',
);
$rows.removeClass('d-none');
$(OrderViewPageMap.productsPagination).addClass('d-none');
const scrollValue = $(scrollTarget).offset().top - $('.header-toolbar').height() - 100;
$('html,body').animate({scrollTop: scrollValue}, 'slow');
}
moveProductPanelToOriginalPosition() {
$(OrderViewPageMap.productAddNewInvoiceInfo).addClass('d-none');
$(OrderViewPageMap.productModificationPosition)
.closest('.row')
.addClass('d-none');
$(OrderViewPageMap.productsPanel)
.detach()
.appendTo(OrderViewPageMap.productOriginalPosition);
$(OrderViewPageMap.productsPagination).removeClass('d-none');
$(OrderViewPageMap.productActionBtn).removeClass('d-none');
$(
`${OrderViewPageMap.productAddActionBtn}, ${OrderViewPageMap.productAddRow}`,
).addClass('d-none');
// Restore pagination
this.paginate(1);
}
resetAddRow() {
$(OrderViewPageMap.productAddIdInput).val('');
$(OrderViewPageMap.productSearchInput).val('');
$(OrderViewPageMap.productAddCombinationsBlock).addClass('d-none');
$(OrderViewPageMap.productAddCombinationsSelect).val('');
$(OrderViewPageMap.productAddCombinationsSelect).prop('disabled', false);
$(OrderViewPageMap.productAddPriceTaxExclInput).val('');
$(OrderViewPageMap.productAddPriceTaxInclInput).val('');
$(OrderViewPageMap.productAddQuantityInput).val('');
$(OrderViewPageMap.productAddAvailableText).html('');
$(OrderViewPageMap.productAddLocationText).html('');
$(OrderViewPageMap.productAddNewInvoiceInfo).addClass('d-none');
$(OrderViewPageMap.productAddActionBtn).prop('disabled', true);
}
resetAllEditRows() {
$(OrderViewPageMap.productEditButtons).each((key, editButton) => {
this.resetEditRow($(editButton).data('orderDetailId'));
});
}
resetEditRow(orderProductId) {
const $productRow = $(OrderViewPageMap.productsTableRow(orderProductId));
const $productEditRow = $(
OrderViewPageMap.productsTableRowEdited(orderProductId),
);
$productEditRow.remove();
$productRow.removeClass('d-none');
}
paginate(originalNumPage) {
const $rows = $(OrderViewPageMap.productsTable).find(
'tr[id^="orderProduct_"]',
);
const $customizationRows = $(
OrderViewPageMap.productsTableCustomizationRows,
);
const $tablePagination = $(OrderViewPageMap.productsTablePagination);
const numRowsPerPage = parseInt($tablePagination.data('numPerPage'), 10);
const maxPage = Math.ceil($rows.length / numRowsPerPage);
const numPage = Math.max(1, Math.min(originalNumPage, maxPage));
this.paginateUpdateControls(numPage);
// Hide all rows...
$rows.addClass('d-none');
$customizationRows.addClass('d-none');
// ... and display good ones
const startRow = (numPage - 1) * numRowsPerPage + 1;
const endRow = numPage * numRowsPerPage;
for (let i = startRow - 1; i < Math.min(endRow, $rows.length); i += 1) {
$($rows[i]).removeClass('d-none');
}
$customizationRows.each(function () {
if (
!$(this)
.prev()
.hasClass('d-none')
) {
$(this).removeClass('d-none');
}
});
// Remove all edition rows (careful not to remove the template)
$(OrderViewPageMap.productEditRow)
.not(OrderViewPageMap.productEditRowTemplate)
.remove();
// Toggle Column Location & Refunded
this.toggleColumn(OrderViewPageMap.productsCellLocationDisplayed);
this.toggleColumn(OrderViewPageMap.productsCellRefundedDisplayed);
}
paginateUpdateControls(numPage) {
// Why 3 ? Next & Prev & Template
const totalPage = $(OrderViewPageMap.productsTablePagination).find('li.page-item').length
- 3;
$(OrderViewPageMap.productsTablePagination)
.find('.active')
.removeClass('active');
$(OrderViewPageMap.productsTablePagination)
.find(`li:has(> [data-page="${numPage}"])`)
.addClass('active');
$(OrderViewPageMap.productsTablePaginationPrev).removeClass('disabled');
if (numPage === 1) {
$(OrderViewPageMap.productsTablePaginationPrev).addClass('disabled');
}
$(OrderViewPageMap.productsTablePaginationNext).removeClass('disabled');
if (numPage === totalPage) {
$(OrderViewPageMap.productsTablePaginationNext).addClass('disabled');
}
this.togglePaginationControls();
}
updateNumPerPage(numPerPage) {
$(OrderViewPageMap.productsTablePagination).data('numPerPage', numPerPage);
this.updatePaginationControls();
}
togglePaginationControls() {
// Why 3 ? Next & Prev & Template
const totalPage = $(OrderViewPageMap.productsTablePagination).find('li.page-item').length
- 3;
$(OrderViewPageMap.productsNavPagination).toggleClass(
'd-none',
totalPage <= 1,
);
}
toggleProductAddNewInvoiceInfo() {
$(OrderViewPageMap.productAddNewInvoiceInfo).toggleClass(
'd-none',
parseInt($(OrderViewPageMap.productAddInvoiceSelect).val(), 10) !== 0,
);
}
toggleColumn(target, forceDisplay = null) {
let isColumnDisplayed = false;
if (forceDisplay === null) {
$(target)
.filter('td')
.each(function () {
if ($(this).html() !== '') {
isColumnDisplayed = true;
return false;
}
return true;
});
} else {
isColumnDisplayed = forceDisplay;
}
$(target).toggleClass('d-none', !isColumnDisplayed);
}
updatePaginationControls() {
const $tablePagination = $(OrderViewPageMap.productsTablePagination);
const numPerPage = $tablePagination.data('numPerPage');
const $rows = $(OrderViewPageMap.productsTable).find('tr[id^="orderProduct_"]');
const numPages = Math.ceil($rows.length / numPerPage);
// Update table data fields
$tablePagination.data('numPages', numPages);
// Clean all page links, reinsert the removed template
const $linkPaginationTemplate = $(OrderViewPageMap.productsTablePaginationTemplate);
$(OrderViewPageMap.productsTablePagination).find('li:has(> [data-page])').remove();
$(OrderViewPageMap.productsTablePaginationNext).before($linkPaginationTemplate);
// Add appropriate pages
for (let i = 1; i <= numPages; i += 1) {
const $linkPagination = $linkPaginationTemplate.clone();
$linkPagination.find('span').attr('data-page', i);
$linkPagination.find('span').html(i);
$linkPaginationTemplate.before($linkPagination.removeClass('d-none'));
}
this.togglePaginationControls();
}
}

View File

@@ -0,0 +1,43 @@
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/OSL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
* versions in the future. If you wish to customize PrestaShop for your
* needs please refer to https://devdocs.prestashop.com/ for more information.
*
* @author PrestaShop SA and Contributors <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
*/
import Router from '@components/router';
import OrderViewPageMap from '@pages/order/OrderViewPageMap';
const {$} = window;
export default class OrderShippingRefresher {
constructor() {
this.router = new Router();
}
refresh(orderId) {
$.getJSON(this.router.generate('admin_orders_get_shipping', {orderId}))
.then((response) => {
$(OrderViewPageMap.orderShippingTabCount).text(response.total);
$(OrderViewPageMap.orderShippingTabBody).html(response.html);
});
}
}

View File

@@ -0,0 +1,33 @@
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/OSL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
* versions in the future. If you wish to customize PrestaShop for your
* needs please refer to https://devdocs.prestashop.com/ for more information.
*
* @author PrestaShop SA and Contributors <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
*/
export default {
productDeletedFromOrder: 'productDeletedFromOrder',
productAddedToOrder: 'productAddedToOrder',
productUpdated: 'productUpdated',
productEditionCanceled: 'productEditionCanceled',
productListPaginated: 'productListPaginated',
productListNumberPerPage: 'productListNumberPerPage',
};

View File

@@ -0,0 +1,369 @@
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/OSL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
* versions in the future. If you wish to customize PrestaShop for your
* needs please refer to https://devdocs.prestashop.com/ for more information.
*
* @author PrestaShop SA and Contributors <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
*/
import OrderProductManager from '@pages/order/view/order-product-manager';
import OrderViewPageMap from '@pages/order/OrderViewPageMap';
import OrderViewEventMap from '@pages/order/view/order-view-event-map';
import {EventEmitter} from '@components/event-emitter';
import OrderDiscountsRefresher from '@pages/order/view/order-discounts-refresher';
import OrderProductRenderer from '@pages/order/view/order-product-renderer';
import OrderPricesRefresher from '@pages/order/view/order-prices-refresher';
import OrderPaymentsRefresher from '@pages/order/view/order-payments-refresher';
import OrderShippingRefresher from '@pages/order/view/order-shipping-refresher';
import Router from '@components/router';
import OrderInvoicesRefresher from './order-invoices-refresher';
import OrderProductCancel from './order-product-cancel';
import OrderDocumentsRefresher from './order-documents-refresher';
const {$} = window;
export default class OrderViewPage {
constructor() {
this.orderDiscountsRefresher = new OrderDiscountsRefresher();
this.orderProductManager = new OrderProductManager();
this.orderProductRenderer = new OrderProductRenderer();
this.orderPricesRefresher = new OrderPricesRefresher();
this.orderPaymentsRefresher = new OrderPaymentsRefresher();
this.orderShippingRefresher = new OrderShippingRefresher();
this.orderDocumentsRefresher = new OrderDocumentsRefresher();
this.orderInvoicesRefresher = new OrderInvoicesRefresher();
this.orderProductCancel = new OrderProductCancel();
this.router = new Router();
this.listenToEvents();
}
listenToEvents() {
$(OrderViewPageMap.invoiceAddressEditBtn).fancybox({
type: 'iframe',
width: '90%',
height: '90%',
});
$(OrderViewPageMap.deliveryAddressEditBtn).fancybox({
type: 'iframe',
width: '90%',
height: '90%',
});
EventEmitter.on(OrderViewEventMap.productDeletedFromOrder, (event) => {
this.orderPricesRefresher.refresh(event.orderId);
this.orderPaymentsRefresher.refresh(event.orderId);
this.refreshProductsList(event.orderId);
this.orderDiscountsRefresher.refresh(event.orderId);
this.orderDocumentsRefresher.refresh(event.orderId);
this.orderShippingRefresher.refresh(event.orderId);
});
EventEmitter.on(OrderViewEventMap.productEditionCanceled, (event) => {
this.orderProductRenderer.resetEditRow(event.orderDetailId);
const editRowsLeft = $(OrderViewPageMap.productEditRow).not(OrderViewPageMap.productEditRowTemplate).length;
if (editRowsLeft > 0) {
return;
}
this.orderProductRenderer.moveProductPanelToOriginalPosition();
});
EventEmitter.on(OrderViewEventMap.productUpdated, (event) => {
this.orderProductRenderer.resetEditRow(event.orderDetailId);
this.orderPricesRefresher.refresh(event.orderId);
this.orderPricesRefresher.refreshProductPrices(event.orderId);
this.refreshProductsList(event.orderId);
this.orderPaymentsRefresher.refresh(event.orderId);
this.orderDiscountsRefresher.refresh(event.orderId);
this.orderInvoicesRefresher.refresh(event.orderId);
this.orderDocumentsRefresher.refresh(event.orderId);
this.orderShippingRefresher.refresh(event.orderId);
this.listenForProductDelete();
this.listenForProductEdit();
this.resetToolTips();
const editRowsLeft = $(OrderViewPageMap.productEditRow).not(OrderViewPageMap.productEditRowTemplate).length;
if (editRowsLeft > 0) {
return;
}
this.orderProductRenderer.moveProductPanelToOriginalPosition();
});
EventEmitter.on(OrderViewEventMap.productAddedToOrder, (event) => {
this.orderProductRenderer.resetAddRow();
this.orderPricesRefresher.refreshProductPrices(event.orderId);
this.orderPricesRefresher.refresh(event.orderId);
this.refreshProductsList(event.orderId);
this.orderPaymentsRefresher.refresh(event.orderId);
this.orderDiscountsRefresher.refresh(event.orderId);
this.orderInvoicesRefresher.refresh(event.orderId);
this.orderDocumentsRefresher.refresh(event.orderId);
this.orderShippingRefresher.refresh(event.orderId);
this.orderProductRenderer.moveProductPanelToOriginalPosition();
});
}
listenForProductDelete() {
$(OrderViewPageMap.productDeleteBtn)
.off('click')
.on('click', (event) => this.orderProductManager.handleDeleteProductEvent(event));
}
resetToolTips() {
$(OrderViewPageMap.productEditButtons).pstooltip();
$(OrderViewPageMap.productDeleteBtn).pstooltip();
}
listenForProductEdit() {
$(OrderViewPageMap.productEditButtons).off('click').on('click', (event) => {
const $btn = $(event.currentTarget);
this.orderProductRenderer.moveProductsPanelToModificationPosition();
this.orderProductRenderer.editProductFromList(
$btn.data('orderDetailId'),
$btn.data('productQuantity'),
$btn.data('productPriceTaxIncl'),
$btn.data('productPriceTaxExcl'),
$btn.data('taxRate'),
$btn.data('location'),
$btn.data('availableQuantity'),
$btn.data('availableOutOfStock'),
$btn.data('orderInvoiceId'),
$btn.data('isOrderTaxIncluded'),
);
});
}
listenForProductPack() {
$(OrderViewPageMap.productPackModal.modal).on('show.bs.modal', (event) => {
const button = $(event.relatedTarget);
const packItems = button.data('packItems');
$(OrderViewPageMap.productPackModal.rows).remove();
packItems.forEach((item) => {
const $item = $(OrderViewPageMap.productPackModal.template).clone();
$item.attr('id', `productpack_${item.id}`).removeClass('d-none');
$item.find(OrderViewPageMap.productPackModal.product.img).attr('src', item.imagePath);
$item.find(OrderViewPageMap.productPackModal.product.name).html(item.name);
$item.find(OrderViewPageMap.productPackModal.product.link).attr(
'href',
this.router.generate('admin_product_form', {id: item.id}),
);
if (item.reference !== '') {
$item.find(OrderViewPageMap.productPackModal.product.ref).append(item.reference);
} else {
$item.find(OrderViewPageMap.productPackModal.product.ref).remove();
}
if (item.supplierReference !== '') {
$item.find(OrderViewPageMap.productPackModal.product.supplierRef).append(item.supplierReference);
} else {
$item.find(OrderViewPageMap.productPackModal.product.supplierRef).remove();
}
if (item.quantity > 1) {
$item.find(`${OrderViewPageMap.productPackModal.product.quantity} span`).html(item.quantity);
} else {
$item.find(OrderViewPageMap.productPackModal.product.quantity).html(item.quantity);
}
$item.find(OrderViewPageMap.productPackModal.product.availableQuantity).html(item.availableQuantity);
$(OrderViewPageMap.productPackModal.template).before($item);
});
});
}
listenForProductAdd() {
$(OrderViewPageMap.productAddBtn).on(
'click',
() => {
this.orderProductRenderer.toggleProductAddNewInvoiceInfo();
this.orderProductRenderer.moveProductsPanelToModificationPosition(OrderViewPageMap.productSearchInput);
},
);
$(OrderViewPageMap.productCancelAddBtn).on(
'click', () => this.orderProductRenderer.moveProductPanelToOriginalPosition(),
);
}
listenForProductPagination() {
$(OrderViewPageMap.productsTablePagination).on('click', OrderViewPageMap.productsTablePaginationLink, (event) => {
event.preventDefault();
const $btn = $(event.currentTarget);
EventEmitter.emit(OrderViewEventMap.productListPaginated, {
numPage: $btn.data('page'),
});
});
$(OrderViewPageMap.productsTablePaginationNext).on('click', (event) => {
event.preventDefault();
const $btn = $(event.currentTarget);
if ($btn.hasClass('disabled')) {
return;
}
const activePage = this.getActivePage();
EventEmitter.emit(OrderViewEventMap.productListPaginated, {
numPage: parseInt($(activePage).html(), 10) + 1,
});
});
$(OrderViewPageMap.productsTablePaginationPrev).on('click', (event) => {
event.preventDefault();
const $btn = $(event.currentTarget);
if ($btn.hasClass('disabled')) {
return;
}
const activePage = this.getActivePage();
EventEmitter.emit(OrderViewEventMap.productListPaginated, {
numPage: parseInt($(activePage).html(), 10) - 1,
});
});
$(OrderViewPageMap.productsTablePaginationNumberSelector).on('change', (event) => {
event.preventDefault();
const $select = $(event.currentTarget);
const numPerPage = parseInt($select.val(), 10);
EventEmitter.emit(OrderViewEventMap.productListNumberPerPage, {
numPerPage,
});
});
EventEmitter.on(OrderViewEventMap.productListPaginated, (event) => {
this.orderProductRenderer.paginate(event.numPage);
this.listenForProductDelete();
this.listenForProductEdit();
this.resetToolTips();
});
EventEmitter.on(OrderViewEventMap.productListNumberPerPage, (event) => {
// Update pagination num per page (page links are regenerated)
this.orderProductRenderer.updateNumPerPage(event.numPerPage);
// Paginate to page 1
EventEmitter.emit(OrderViewEventMap.productListPaginated, {
numPage: 1,
});
// Save new config
$.ajax({
url: this.router.generate('admin_orders_configure_product_pagination'),
method: 'POST',
data: {numPerPage: event.numPerPage},
});
});
}
listenForRefund() {
$(OrderViewPageMap.cancelProduct.buttons.partialRefund).on('click', () => {
this.orderProductRenderer.moveProductsPanelToRefundPosition();
this.orderProductCancel.showPartialRefund();
});
$(OrderViewPageMap.cancelProduct.buttons.standardRefund).on('click', () => {
this.orderProductRenderer.moveProductsPanelToRefundPosition();
this.orderProductCancel.showStandardRefund();
});
$(OrderViewPageMap.cancelProduct.buttons.returnProduct).on('click', () => {
this.orderProductRenderer.moveProductsPanelToRefundPosition();
this.orderProductCancel.showReturnProduct();
});
$(OrderViewPageMap.cancelProduct.buttons.abort).on('click', () => {
this.orderProductRenderer.moveProductPanelToOriginalPosition();
this.orderProductCancel.hideRefund();
});
}
listenForCancelProduct() {
$(OrderViewPageMap.cancelProduct.buttons.cancelProducts).on('click', () => {
this.orderProductRenderer.moveProductsPanelToRefundPosition();
this.orderProductCancel.showCancelProductForm();
});
}
getActivePage() {
return $(OrderViewPageMap.productsTablePagination).find('.active span').get(0);
}
refreshProductsList(orderId) {
$(OrderViewPageMap.refreshProductsListLoadingSpinner).show();
const $tablePagination = $(OrderViewPageMap.productsTablePagination);
const numRowsPerPage = $tablePagination.data('numPerPage');
const initialNumProducts = $(OrderViewPageMap.productsTableRows).length;
const currentPage = parseInt($(OrderViewPageMap.productsTablePaginationActive).html(), 10);
$.ajax(this.router.generate('admin_orders_get_products', {orderId}))
.done((response) => {
// Delete previous product lines
$(OrderViewPageMap.productsTable).find(OrderViewPageMap.productsTableRows).remove();
$(OrderViewPageMap.productsTableCustomizationRows).remove();
$(`${OrderViewPageMap.productsTable} tbody`).prepend(response);
$(OrderViewPageMap.refreshProductsListLoadingSpinner).hide();
const newNumProducts = $(OrderViewPageMap.productsTableRows).length;
const newPagesNum = Math.ceil(newNumProducts / numRowsPerPage);
this.orderProductRenderer.updateNumProducts(newNumProducts);
this.orderProductRenderer.updatePaginationControls();
let numPage = 1;
let message = '';
// Display alert
if (initialNumProducts > newNumProducts) { // product deleted
message = (initialNumProducts - newNumProducts === 1)
? window.translate_javascripts['The product was successfully removed.']
: window.translate_javascripts['[1] products were successfully removed.']
.replace('[1]', (initialNumProducts - newNumProducts));
// Set target page to the page of the deleted item
numPage = (newPagesNum === 1) ? 1 : currentPage;
} else if (initialNumProducts < newNumProducts) { // product added
message = (newNumProducts - initialNumProducts === 1)
? window.translate_javascripts['The product was successfully added.']
: window.translate_javascripts['[1] products were successfully added.']
.replace('[1]', (newNumProducts - initialNumProducts));
// Move to first page to see the added product
numPage = 1;
}
if (message !== '') {
$.growl.notice({
title: '',
message,
});
}
// Move to page of the modified item
EventEmitter.emit(OrderViewEventMap.productListPaginated, {
numPage,
});
// Bind hover on product rows buttons
this.resetToolTips();
})
.fail(() => {
$.growl.error({
title: '',
message: 'Failed to reload the products list. Please reload the page',
});
});
}
}