installed plugin Easy Digital Downloads
version 3.1.0.3
This commit is contained in:
@ -0,0 +1,130 @@
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import OrderOverview from './order-overview';
|
||||
import './order-details';
|
||||
import { jQueryReady } from 'utils/jquery.js';
|
||||
|
||||
jQueryReady( () => {
|
||||
// Order Overview.
|
||||
if ( window.eddAdminOrderOverview ) {
|
||||
OrderOverview.render();
|
||||
|
||||
/**
|
||||
* Add validation to Add/Edit Order form.
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
( () => {
|
||||
const overview = OrderOverview.options.state;
|
||||
const orderItems = overview.get( 'items' );
|
||||
|
||||
const noItemErrorEl = document.getElementById( 'edd-add-order-no-items-error' );
|
||||
const noCustomerErrorEl = document.getElementById( 'edd-add-order-customer-error' );
|
||||
|
||||
const assignCustomerEl = document.getElementById( 'customer_id' );
|
||||
const newCustomerEmailEl = document.getElementById( 'edd_new_customer_email' );
|
||||
|
||||
[
|
||||
'edd-add-order-form',
|
||||
'edd-edit-order-form',
|
||||
].forEach( ( form ) => {
|
||||
const formEl = document.getElementById( form );
|
||||
|
||||
if ( ! formEl ) {
|
||||
return;
|
||||
}
|
||||
|
||||
formEl.addEventListener( 'submit', submitForm );
|
||||
} );
|
||||
|
||||
/**
|
||||
* Submits an Order form.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @param {Object} event Submit event.
|
||||
*/
|
||||
function submitForm( event ) {
|
||||
let hasError = false;
|
||||
|
||||
// Ensure `OrderItem`s.
|
||||
if ( noItemErrorEl ) {
|
||||
if ( 0 === orderItems.length ) {
|
||||
noItemErrorEl.style.display = 'block';
|
||||
hasError = true;
|
||||
} else {
|
||||
noItemErrorEl.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure Customer.
|
||||
if ( noCustomerErrorEl ) {
|
||||
if ( '0' === assignCustomerEl.value && '' === newCustomerEmailEl.value ) {
|
||||
noCustomerErrorEl.style.display = 'block';
|
||||
hasError = true;
|
||||
} else {
|
||||
noCustomerErrorEl.style.display = 'none';
|
||||
}
|
||||
|
||||
if ( true === hasError ) {
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove `OrderItem` notice when an `OrderItem` is added.
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
orderItems.on( 'add', function() {
|
||||
noItemErrorEl.style.display = 'none';
|
||||
} );
|
||||
|
||||
/**
|
||||
* Remove Customer notice when a Customer is changed.
|
||||
*
|
||||
* Uses a jQuery binding for Chosen support.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @param {Object} event Change event.
|
||||
*/
|
||||
$( assignCustomerEl ).on( 'change', ( event ) => {
|
||||
const val = event.target.value;
|
||||
|
||||
if ( '0' !== val ) {
|
||||
noCustomerErrorEl.style.display = 'none';
|
||||
}
|
||||
} )
|
||||
|
||||
if ( newCustomerEmailEl ) {
|
||||
/**
|
||||
* Remove Customer notice when a Customer is set.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @param {Object} event Input event.
|
||||
*/
|
||||
newCustomerEmailEl.addEventListener( 'input', ( event ) => {
|
||||
const val = event.target.value;
|
||||
|
||||
if ( '' !== val ) {
|
||||
noCustomerErrorEl.style.display = 'none';
|
||||
}
|
||||
} );
|
||||
}
|
||||
} )();
|
||||
}
|
||||
|
||||
// Move `.update-nag` items below the top header.
|
||||
// `#update-nag` is legacy styling, which core still supports.
|
||||
//
|
||||
// `.notice` items are properly moved, but WordPress core
|
||||
// does not move `.update-nag`.
|
||||
if ( 0 !== $( '.edit-post-editor-regions__header' ).length ) {
|
||||
$( 'div.update-nag, div#update-nag' ).insertAfter( $( '.edit-post-editor-regions__header' ) );
|
||||
}
|
||||
|
||||
} );
|
@ -0,0 +1,17 @@
|
||||
/* global $, ajaxurl */
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { jQueryReady } from 'utils/jquery.js';
|
||||
|
||||
jQueryReady( () => {
|
||||
|
||||
$( '.download_page_edd-payment-history .row-actions .delete a' ).on( 'click', function() {
|
||||
if( confirm( edd_vars.delete_payment ) ) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
} );
|
@ -0,0 +1,256 @@
|
||||
/* global $, ajaxurl, _ */
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import OrderOverview from './../order-overview';
|
||||
import { getChosenVars } from 'utils/chosen.js';
|
||||
import { jQueryReady } from 'utils/jquery.js';
|
||||
|
||||
// Store customer search results to help prefill address data.
|
||||
let CUSTOMER_SEARCH_RESULTS = {
|
||||
addresses: {
|
||||
'0': {
|
||||
address: '',
|
||||
address2: '',
|
||||
city: '',
|
||||
region: '',
|
||||
postal_code: '',
|
||||
country: '',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
jQueryReady( () => {
|
||||
|
||||
/**
|
||||
* Adjusts Overview tax configuration when the Customer's address changes.
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
( () => {
|
||||
const { state: overviewState } = OrderOverview.options;
|
||||
|
||||
// No tax, do nothing.
|
||||
if ( false === overviewState.get( 'hasTax' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Editing, do nothing.
|
||||
if ( false === overviewState.get( 'isAdding' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const countryInput = document.getElementById(
|
||||
'edd_order_address_country'
|
||||
);
|
||||
const regionInput = document.getElementById(
|
||||
'edd_order_address_region'
|
||||
);
|
||||
|
||||
if ( ! ( countryInput && regionInput ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a tax rate based on the currently selected Address.
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
function getTaxRate() {
|
||||
const country = $( '#edd_order_address_country' ).val();
|
||||
const region = $( '#edd_order_address_region' ).val();
|
||||
|
||||
const nonce = document.getElementById( 'edd_get_tax_rate_nonce' )
|
||||
.value;
|
||||
|
||||
wp.ajax.send( 'edd_get_tax_rate', {
|
||||
data: {
|
||||
nonce,
|
||||
country,
|
||||
region,
|
||||
},
|
||||
/**
|
||||
* Updates the Overview's tax configuration on successful retrieval.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @param {Object} response AJAX response.
|
||||
*/
|
||||
success( response ) {
|
||||
let { tax_rate: rate } = response;
|
||||
|
||||
// Make a percentage.
|
||||
rate = rate * 100;
|
||||
|
||||
overviewState.set( 'hasTax', {
|
||||
...overviewState.get( 'hasTax' ),
|
||||
country,
|
||||
region,
|
||||
rate,
|
||||
} );
|
||||
},
|
||||
/*
|
||||
* Updates the Overview's tax configuration on failed retrieval.
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
error() {
|
||||
overviewState.set( 'hasTax', 'none' );
|
||||
},
|
||||
} );
|
||||
}
|
||||
|
||||
// Update rate on Address change.
|
||||
//
|
||||
// Wait for Region field to be replaced when Country changes.
|
||||
// Wait for typing when Regino field changes.
|
||||
// jQuery listeners for Chosen compatibility.
|
||||
$( '#edd_order_address_country' ).on( 'change', _.debounce( getTaxRate, 250 ) );
|
||||
|
||||
$( '#edd-order-address' ).on( 'change', '#edd_order_address_region', getTaxRate );
|
||||
$( '#edd-order-address' ).on( 'keyup', '#edd_order_address_region', _.debounce( getTaxRate, 250 ) );
|
||||
} )();
|
||||
|
||||
$( '.edd-payment-change-customer-input' ).on( 'change', function() {
|
||||
const $this = $( this ),
|
||||
data = {
|
||||
action: 'edd_customer_addresses',
|
||||
customer_id: $this.val(),
|
||||
nonce: $( '#edd_add_order_nonce' ).val(),
|
||||
};
|
||||
|
||||
$.post( ajaxurl, data, function( response ) {
|
||||
const { success, data } = response;
|
||||
|
||||
if ( ! success ) {
|
||||
$( '.customer-address-select-wrap' ).hide();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Store response for later use.
|
||||
CUSTOMER_SEARCH_RESULTS = {
|
||||
...CUSTOMER_SEARCH_RESULTS,
|
||||
...data,
|
||||
addresses: {
|
||||
...CUSTOMER_SEARCH_RESULTS.addresses,
|
||||
...data.addresses,
|
||||
},
|
||||
};
|
||||
|
||||
if ( data.html ) {
|
||||
$( '.customer-address-select-wrap' ).show();
|
||||
$( '.customer-address-select-wrap .edd-form-group__control' ).html( data.html );
|
||||
} else {
|
||||
$( '.customer-address-select-wrap' ).hide();
|
||||
}
|
||||
}, 'json' );
|
||||
|
||||
return false;
|
||||
} );
|
||||
|
||||
/**
|
||||
* Retrieves a list of states based on a Country HTML <select>.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @param {HTMLElement} countryEl Element containing country information.
|
||||
* @param {string} fieldName the name of the field to use in response.
|
||||
* @return {$.promise} Region data response.
|
||||
*/
|
||||
function getStates( countryEl, fieldName, fieldId ) {
|
||||
const data = {
|
||||
action: 'edd_get_shop_states',
|
||||
country: countryEl.val(),
|
||||
nonce: countryEl.data( 'nonce' ),
|
||||
field_name: fieldName,
|
||||
field_id: fieldId,
|
||||
};
|
||||
|
||||
return $.post( ajaxurl, data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces the Region area with the appropriate field type.
|
||||
*
|
||||
* @todo This is hacky and blindly picks elements from the DOM.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @param {string} regions Regions response.
|
||||
*/
|
||||
function replaceRegionField( regions ) {
|
||||
const state_wrapper = $( '#edd_order_address_region' );
|
||||
|
||||
$( '#edd_order_address_region_chosen' ).remove();
|
||||
|
||||
if ( 'nostates' === regions ) {
|
||||
state_wrapper
|
||||
.replaceWith( '<input type="text" name="edd_order_address[region]" id="edd_order_address_region" value="" class="wide-fat" style="max-width: none; width: 100%;" />' );
|
||||
} else {
|
||||
state_wrapper
|
||||
.replaceWith( regions );
|
||||
|
||||
$( '#edd_order_address_region' ).chosen( getChosenVars( $( '#edd_order_address_region' ) ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles replacing a Region field when a Country field changes.
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
function updateRegionFieldOnChange() {
|
||||
getStates(
|
||||
$( this ),
|
||||
'edd_order_address[region]',
|
||||
'edd_order_address_region'
|
||||
)
|
||||
.done( replaceRegionField );
|
||||
}
|
||||
|
||||
$( document.body ).on( 'change', '.customer-address-select-wrap .add-order-customer-address-select', function() {
|
||||
const $this = $( this ),
|
||||
val = $this.val(),
|
||||
address = CUSTOMER_SEARCH_RESULTS.addresses[ val ];
|
||||
|
||||
$( '#edd-add-order-form input[name="edd_order_address[address]"]' ).val( address.address );
|
||||
$( '#edd-add-order-form input[name="edd_order_address[address2]"]' ).val( address.address2 );
|
||||
$( '#edd-add-order-form input[name="edd_order_address[postal_code]"]' ).val( address.postal_code );
|
||||
$( '#edd-add-order-form input[name="edd_order_address[city]"]' ).val( address.city );
|
||||
$( '#edd-add-order-form input[name="edd_order_address[address_id]"]' ).val( val );
|
||||
|
||||
// Remove global `change` event handling to prevent loop.
|
||||
$( '#edd_order_address_country' ).off( 'change', updateRegionFieldOnChange );
|
||||
|
||||
// Set Country.
|
||||
$( '#edd_order_address_country' )
|
||||
.val( address.country )
|
||||
.trigger( 'change' )
|
||||
.trigger( 'chosen:updated' );
|
||||
|
||||
// Set Region.
|
||||
getStates(
|
||||
$( '#edd_order_address_country' ),
|
||||
'edd_order_address[region]',
|
||||
'edd_order_address_region'
|
||||
)
|
||||
.done( replaceRegionField )
|
||||
.done( ( response ) => {
|
||||
$( '#edd_order_address_region' )
|
||||
.val( address.region )
|
||||
.trigger( 'change' )
|
||||
.trigger( 'chosen:updated' );
|
||||
} );
|
||||
|
||||
// Add back global `change` event handling.
|
||||
$( '#edd_order_address_country' ).on( 'change', updateRegionFieldOnChange );
|
||||
|
||||
return false;
|
||||
} );
|
||||
|
||||
// Country change.
|
||||
$( '#edd_order_address_country' ).on( 'change', updateRegionFieldOnChange );
|
||||
|
||||
} );
|
@ -0,0 +1,69 @@
|
||||
/* global $ */
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { jQueryReady } from 'utils/jquery.js';
|
||||
|
||||
jQueryReady( () => {
|
||||
|
||||
// Change Customer.
|
||||
$( '.edd-payment-change-customer-input' ).on( 'change', function() {
|
||||
const $this = $( this ),
|
||||
data = {
|
||||
action: 'edd_customer_details',
|
||||
customer_id: $this.val(),
|
||||
nonce: $( '#edd_customer_details_nonce' ).val(),
|
||||
};
|
||||
|
||||
if ( '' === data.customer_id ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$( '.customer-details' ).css( 'display', 'none' );
|
||||
$( '#customer-avatar' ).html( '<span class="spinner is-active"></span>' );
|
||||
|
||||
$.post( ajaxurl, data, function( response ) {
|
||||
const { success, data } = response;
|
||||
|
||||
if ( success ) {
|
||||
$( '.customer-details' ).css( 'display', 'flex' );
|
||||
$( '.customer-details-wrap' ).css( 'display', 'flex' );
|
||||
|
||||
$( '#customer-avatar' ).html( data.avatar );
|
||||
$( '.customer-name' ).html( data.name );
|
||||
$( '.customer-since span' ).html( data.date_created_i18n );
|
||||
$( '.customer-record a' ).prop( 'href', data._links.self );
|
||||
} else {
|
||||
$( '.customer-details-wrap' ).css( 'display', 'none' );
|
||||
}
|
||||
}, 'json' );
|
||||
} );
|
||||
|
||||
$( '.edd-payment-change-customer-input' ).trigger( 'change' );
|
||||
|
||||
// New Customer.
|
||||
$( '#edd-customer-details' ).on( 'click', '.edd-payment-new-customer, .edd-payment-new-customer-cancel', function( e ) {
|
||||
e.preventDefault();
|
||||
|
||||
var new_customer = $( this ).hasClass( 'edd-payment-new-customer' ),
|
||||
cancel = $( this ).hasClass( 'edd-payment-new-customer-cancel' );
|
||||
|
||||
if ( new_customer ) {
|
||||
$( '.order-customer-info' ).hide();
|
||||
$( '.new-customer' ).show();
|
||||
} else if ( cancel ) {
|
||||
$( '.order-customer-info' ).show();
|
||||
$( '.new-customer' ).hide();
|
||||
}
|
||||
|
||||
var new_customer = $( '#edd-new-customer' );
|
||||
|
||||
if ( $( '.new-customer' ).is( ':visible' ) ) {
|
||||
new_customer.val( 1 );
|
||||
} else {
|
||||
new_customer.val( 0 );
|
||||
}
|
||||
} );
|
||||
|
||||
} );
|
@ -0,0 +1,3 @@
|
||||
import './address.js';
|
||||
import './customer.js';
|
||||
import './receipt.js';
|
@ -0,0 +1,32 @@
|
||||
/* global $, ajaxurl */
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { jQueryReady } from 'utils/jquery.js';
|
||||
|
||||
jQueryReady( () => {
|
||||
const emails_wrap = $( '.edd-order-resend-receipt-addresses' );
|
||||
|
||||
$( document.body ).on( 'click', '#edd-select-receipt-email', function( e ) {
|
||||
e.preventDefault();
|
||||
emails_wrap.slideDown();
|
||||
} );
|
||||
|
||||
$( document.body ).on( 'change', '.edd-order-resend-receipt-email', function() {
|
||||
const selected = $('input:radio.edd-order-resend-receipt-email:checked').val();
|
||||
|
||||
$( '#edd-select-receipt-email').data( 'email', selected );
|
||||
} );
|
||||
|
||||
$( document.body).on( 'click', '#edd-select-receipt-email', function () {
|
||||
if ( confirm( edd_vars.resend_receipt ) ) {
|
||||
const href = $( this ).prop( 'href' ) + '&email=' + $( this ).data( 'email' );
|
||||
window.location = href;
|
||||
}
|
||||
} );
|
||||
|
||||
$( document.body ).on( 'click', '#edd-resend-receipt', function() {
|
||||
return confirm( edd_vars.resend_receipt );
|
||||
} );
|
||||
} );
|
@ -0,0 +1,279 @@
|
||||
import { NumberFormat } from '@easy-digital-downloads/currency';
|
||||
|
||||
const number = new NumberFormat();
|
||||
|
||||
/* global eddAdminOrderOverview */
|
||||
|
||||
// Loads the modal when the refund button is clicked.
|
||||
$(document.body).on('click', '.edd-refund-order', function (e) {
|
||||
e.preventDefault();
|
||||
var link = $(this),
|
||||
postData = {
|
||||
action : 'edd_generate_refund_form',
|
||||
order_id: $('input[name="edd_payment_id"]').val(),
|
||||
};
|
||||
|
||||
$.ajax({
|
||||
type : 'POST',
|
||||
data : postData,
|
||||
url : ajaxurl,
|
||||
success: function success(data) {
|
||||
let modal_content = '';
|
||||
if (data.success) {
|
||||
modal_content = data.html;
|
||||
} else {
|
||||
modal_content = data.message;
|
||||
}
|
||||
|
||||
$('#edd-refund-order-dialog').dialog({
|
||||
position: { my: 'top center', at: 'center center-25%' },
|
||||
width : '75%',
|
||||
modal : true,
|
||||
resizable: false,
|
||||
draggable: false,
|
||||
classes: {
|
||||
'ui-dialog': 'edd-dialog',
|
||||
},
|
||||
closeText: eddAdminOrderOverview.i18n.closeText,
|
||||
open: function( event, ui ) {
|
||||
$(this).html( modal_content );
|
||||
},
|
||||
close: function( event, ui ) {
|
||||
$( this ).html( '' );
|
||||
if ( $( this ).hasClass( 'did-refund' ) ) {
|
||||
location.reload();
|
||||
}
|
||||
}
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}).fail(function (data) {
|
||||
$('#edd-refund-order-dialog').dialog({
|
||||
position: { my: 'top center', at: 'center center-25%' },
|
||||
width : '75%',
|
||||
modal : true,
|
||||
resizable: false,
|
||||
draggable: false
|
||||
}).html(data.message);
|
||||
return false;
|
||||
});
|
||||
});
|
||||
|
||||
$( document.body ).on( 'click', '.ui-widget-overlay', function ( e ) {
|
||||
$( '#edd-refund-order-dialog' ).dialog( 'close' );
|
||||
} );
|
||||
|
||||
/**
|
||||
* Listen for the bulk actions checkbox, since WP doesn't trigger a change on sub-items.
|
||||
*/
|
||||
$( document.body ).on( 'change', '#edd-refund-order-dialog #cb-select-all-1', function () {
|
||||
const itemCheckboxes = $( '.edd-order-item-refund-checkbox' );
|
||||
const isChecked = $( this ).prop( 'checked' );
|
||||
|
||||
itemCheckboxes.each( function() {
|
||||
$( this ).prop( 'checked', isChecked ).trigger( 'change' );
|
||||
} );
|
||||
} );
|
||||
|
||||
/**
|
||||
* Listen for individual checkbox changes.
|
||||
* When it does, trigger a quantity change.
|
||||
*/
|
||||
$( document.body ).on( 'change', '.edd-order-item-refund-checkbox', function () {
|
||||
const parent = $( this ).parent().parent();
|
||||
const quantityField = parent.find( '.edd-order-item-refund-quantity' );
|
||||
|
||||
if ( quantityField.length ) {
|
||||
if ( $( this ).prop( 'checked' ) ) {
|
||||
// Triggering a change on the quantity field handles enabling the inputs.
|
||||
quantityField.trigger( 'change' );
|
||||
} else {
|
||||
// Disable inputs and recalculate total.
|
||||
parent.find( '.edd-order-item-refund-input' ).prop( 'disabled', true );
|
||||
recalculateRefundTotal();
|
||||
}
|
||||
}
|
||||
} );
|
||||
|
||||
/**
|
||||
* Handles quantity changes, which includes items in the refund.
|
||||
*/
|
||||
$( document.body ).on( 'change', '#edd-refund-order-dialog .edd-order-item-refund-input', function () {
|
||||
let parent = $( this ).closest( '.refunditem' ),
|
||||
quantityField = parent.find( '.edd-order-item-refund-quantity' ),
|
||||
quantity = parseInt( quantityField.val() );
|
||||
|
||||
if ( quantity > 0 ) {
|
||||
parent.addClass( 'refunded' );
|
||||
} else {
|
||||
parent.removeClass( 'refunded' );
|
||||
}
|
||||
|
||||
// Only auto calculate subtotal / tax if we've adjusted the quantity.
|
||||
if ( $( this ).hasClass( 'edd-order-item-refund-quantity' ) ) {
|
||||
// Enable/disable amount fields.
|
||||
parent.find( '.edd-order-item-refund-input:not(.edd-order-item-refund-quantity)' ).prop( 'disabled', quantity === 0 );
|
||||
if ( quantity > 0 ) {
|
||||
quantityField.prop( 'disabled', false );
|
||||
}
|
||||
|
||||
let subtotalField = parent.find( '.edd-order-item-refund-subtotal' ),
|
||||
taxField = parent.find( '.edd-order-item-refund-tax' ),
|
||||
originalSubtotal = number.unformat( subtotalField.data( 'original' ) ),
|
||||
originalTax = taxField.length ? number.unformat( taxField.data( 'original' ) ) : 0.00,
|
||||
originalQuantity = parseInt( quantityField.data( 'max' ) ),
|
||||
calculatedSubtotal = ( originalSubtotal / originalQuantity ) * quantity,
|
||||
calculatedTax = taxField.length ? ( originalTax / originalQuantity ) * quantity : 0.00;
|
||||
|
||||
// Make sure totals don't go over maximums.
|
||||
if ( calculatedSubtotal > parseFloat( subtotalField.data( 'max' ) ) ) {
|
||||
calculatedSubtotal = subtotalField.data( 'max' );
|
||||
}
|
||||
if ( taxField.length && calculatedTax > parseFloat( taxField.data( 'max' ) ) ) {
|
||||
calculatedTax = taxField.data( 'max' );
|
||||
}
|
||||
|
||||
// Guess the subtotal and tax for the selected quantity.
|
||||
subtotalField.val( number.format( calculatedSubtotal ) );
|
||||
if ( taxField.length ) {
|
||||
taxField.val( number.format( calculatedTax ) );
|
||||
}
|
||||
}
|
||||
|
||||
recalculateRefundTotal();
|
||||
} );
|
||||
|
||||
/**
|
||||
* Calculates all the final refund values.
|
||||
*/
|
||||
function recalculateRefundTotal() {
|
||||
let newSubtotal = 0,
|
||||
newTax = 0,
|
||||
newTotal = 0,
|
||||
canRefund = false,
|
||||
allInputBoxes = $( '#edd-refund-order-dialog .edd-order-item-refund-input' ),
|
||||
allReadOnly = $( '#edd-refund-order-dialog .edd-order-item-refund-input.readonly' );
|
||||
|
||||
// Set a readonly while we recalculate, to avoid race conditions in the browser.
|
||||
allInputBoxes.prop( 'readonly', true );
|
||||
|
||||
// Loop over all order items.
|
||||
$( '#edd-refund-order-dialog .edd-order-item-refund-quantity' ).each( function() {
|
||||
const thisItemQuantity = parseInt( $( this ).val() );
|
||||
|
||||
if ( ! thisItemQuantity ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const thisItemParent = $( this ).closest( '.refunditem' );
|
||||
const thisItemSelected = thisItemParent.find( '.edd-order-item-refund-checkbox' ).prop( 'checked' );
|
||||
|
||||
if ( ! thisItemSelected ) {
|
||||
thisItemParent.removeClass( 'refunded' );
|
||||
return;
|
||||
}
|
||||
|
||||
// Values for this item.
|
||||
let thisItemTax = 0.00;
|
||||
|
||||
let thisItemSubtotal = number.unformat( thisItemParent.find( '.edd-order-item-refund-subtotal' ).val() );
|
||||
|
||||
if ( thisItemParent.find( '.edd-order-item-refund-tax' ).length ) {
|
||||
thisItemTax = number.unformat( thisItemParent.find( '.edd-order-item-refund-tax' ).val() );
|
||||
}
|
||||
|
||||
let thisItemTotal = thisItemSubtotal + thisItemTax;
|
||||
|
||||
thisItemParent.find( '.column-total span' ).text( number.format( thisItemTotal ) );
|
||||
|
||||
// Negate amounts if working with credit.
|
||||
if ( thisItemParent.data( 'credit' ) ) {
|
||||
thisItemSubtotal = thisItemSubtotal * -1;
|
||||
thisItemTax = thisItemTax * -1;
|
||||
thisItemTotal = thisItemTotal * -1;
|
||||
}
|
||||
|
||||
// Only include order items in the subtotal.
|
||||
if ( thisItemParent.data( 'orderItem' ) ) {
|
||||
newSubtotal += thisItemSubtotal;
|
||||
}
|
||||
|
||||
newTax += thisItemTax;
|
||||
newTotal += thisItemTotal;
|
||||
} );
|
||||
|
||||
if ( parseFloat( newTotal ) > 0 ) {
|
||||
canRefund = true;
|
||||
}
|
||||
|
||||
$( '#edd-refund-submit-subtotal-amount' ).text( number.format( newSubtotal ) );
|
||||
$( '#edd-refund-submit-tax-amount' ).text( number.format( newTax ) );
|
||||
$( '#edd-refund-submit-total-amount' ).text( number.format( newTotal ) );
|
||||
|
||||
$( '#edd-submit-refund-submit' ).attr( 'disabled', ! canRefund );
|
||||
|
||||
// Remove the readonly.
|
||||
allInputBoxes.prop( 'readonly', false );
|
||||
allReadOnly.prop( 'readonly', true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the refund form after the button is clicked.
|
||||
*/
|
||||
$(document.body).on( 'click', '#edd-submit-refund-submit', function(e) {
|
||||
e.preventDefault();
|
||||
$('.edd-submit-refund-message').removeClass('success').removeClass('fail');
|
||||
$( this ).removeClass( 'button-primary' ).attr( 'disabled', true ).addClass( 'updating-message' );
|
||||
$('#edd-submit-refund-status').hide();
|
||||
|
||||
const refundForm = $( '#edd-submit-refund-form' );
|
||||
const refundData = refundForm.serialize();
|
||||
|
||||
var postData = {
|
||||
action: 'edd_process_refund_form',
|
||||
data: refundData,
|
||||
order_id: $('input[name="edd_payment_id"]').val()
|
||||
};
|
||||
|
||||
$.ajax({
|
||||
type : 'POST',
|
||||
data : postData,
|
||||
url : ajaxurl,
|
||||
success: function success(response) {
|
||||
const message_target = $('.edd-submit-refund-message'),
|
||||
url_target = $('.edd-submit-refund-url');
|
||||
|
||||
if ( response.success ) {
|
||||
message_target.text(response.data.message).addClass('success');
|
||||
url_target.attr( 'href', response.data.refund_url ).show();
|
||||
|
||||
$( '#edd-submit-refund-status' ).show();
|
||||
url_target.focus();
|
||||
$( '#edd-refund-order-dialog' ).addClass( 'did-refund' );
|
||||
} else {
|
||||
message_target.html(response.data).addClass('fail');
|
||||
url_target.hide();
|
||||
|
||||
$('#edd-submit-refund-status').show();
|
||||
$( '#edd-submit-refund-submit' ).attr( 'disabled', false ).removeClass( 'updating-message' ).addClass( 'button-primary' );
|
||||
}
|
||||
}
|
||||
} ).fail( function ( data ) {
|
||||
const message_target = $('.edd-submit-refund-message'),
|
||||
url_target = $('.edd-submit-refund-url'),
|
||||
json = data.responseJSON;
|
||||
|
||||
|
||||
message_target.text( json.data ).addClass( 'fail' );
|
||||
url_target.hide();
|
||||
|
||||
$( '#edd-submit-refund-status' ).show();
|
||||
$( '#edd-submit-refund-submit' ).attr( 'disabled', false ).removeClass( 'updating-message' ).addClass( 'button-primary' );
|
||||
return false;
|
||||
});
|
||||
});
|
||||
|
||||
// Initialize WP toggle behavior for the modal.
|
||||
$( document.body ).on( 'click', '.refund-items .toggle-row', function () {
|
||||
$( this ).closest( 'tr' ).toggleClass( 'is-expanded' );
|
||||
} );
|
@ -0,0 +1,101 @@
|
||||
/* global Backbone */
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { OrderAdjustment } from './../models/order-adjustment.js';
|
||||
import { OrderAdjustmentDiscount } from './../models/order-adjustment-discount.js';
|
||||
|
||||
/**
|
||||
* Collection of `OrderAdjustment`s.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @class Adjustments
|
||||
* @augments Backbone.Collection
|
||||
*/
|
||||
export const OrderAdjustments = Backbone.Collection.extend( {
|
||||
/**
|
||||
* @since 3.0
|
||||
*/
|
||||
comparator: 'type',
|
||||
|
||||
/**
|
||||
* Initializes the `OrderAdjustments` collection.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @constructs OrderAdjustments
|
||||
* @augments Backbone.Collection
|
||||
*/
|
||||
initialize() {
|
||||
this.getByType = this.getByType.bind( this );
|
||||
},
|
||||
|
||||
/**
|
||||
* Determines which Model to use and instantiates it.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @param {Object} attributes Model attributes.
|
||||
* @param {Object} options Model options.
|
||||
*/
|
||||
model( attributes, options ) {
|
||||
let model;
|
||||
|
||||
switch ( attributes.type ) {
|
||||
case 'discount':
|
||||
model = new OrderAdjustmentDiscount( attributes, options );
|
||||
break;
|
||||
default:
|
||||
model = new OrderAdjustment( attributes, options );
|
||||
}
|
||||
|
||||
return model;
|
||||
},
|
||||
|
||||
/**
|
||||
* Defines the model's attribute that defines it's ID.
|
||||
*
|
||||
* Uses the `OrderAdjustment`'s Type ID.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @param {Object} attributes Model attributes.
|
||||
* @return {number}
|
||||
*/
|
||||
modelId( attributes ) {
|
||||
return `${ attributes.type }-${ attributes.typeId }-${ attributes.description }`;
|
||||
},
|
||||
|
||||
/**
|
||||
* Determines if `OrderAdjustments` contains a specific `OrderAdjustment`.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @param {OrderAdjustment} model Model to look for.
|
||||
* @return {bool} True if the Collection contains the Model.
|
||||
*/
|
||||
has( model ) {
|
||||
return (
|
||||
undefined !==
|
||||
this.findWhere( {
|
||||
typeId: model.get( 'typeId' ),
|
||||
} )
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns a list of `OrderAdjustment`s by type.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @param {string} type Type of adjustment to retrieve. `fee`, `credit`, or `discount`.
|
||||
* @return {Array} List of type-specific adjustments.
|
||||
*/
|
||||
getByType( type ) {
|
||||
return this.where( {
|
||||
type,
|
||||
} );
|
||||
},
|
||||
} );
|
@ -0,0 +1,137 @@
|
||||
/* global Backbone, $, _ */
|
||||
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import uuid from 'uuid-random';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { OrderAdjustments } from './../collections/order-adjustments.js';
|
||||
import { OrderAdjustmentDiscount } from './../models/order-adjustment-discount.js';
|
||||
import { OrderItem } from './../models/order-item.js';
|
||||
|
||||
/**
|
||||
* Collection of `OrderItem`s.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @class OrderItems
|
||||
* @augments Backbone.Collection
|
||||
*/
|
||||
export const OrderItems = Backbone.Collection.extend( {
|
||||
/**
|
||||
* @since 3.0
|
||||
*
|
||||
* @type {OrderItem}
|
||||
*/
|
||||
model: OrderItem,
|
||||
|
||||
/**
|
||||
* Ensures `OrderItems` has access to the current state through a similar
|
||||
* interface as Views. BackBone.Collection does not automatically set
|
||||
* passed options as a property.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @param {null|Array} models List of Models.
|
||||
* @param {Object} options Collection options.
|
||||
*/
|
||||
preinitialize( models, options ) {
|
||||
this.options = options;
|
||||
},
|
||||
|
||||
/**
|
||||
* Determines if `OrderItems` contains a specific `OrderItem`.
|
||||
*
|
||||
* Uses the `OrderItem`s Product ID and Price ID to create a unique
|
||||
* value to check against.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @param {OrderItem} model Model to look for.
|
||||
* @return {bool} True if the Collection contains the Model.
|
||||
*/
|
||||
has( model ) {
|
||||
const duplicates = this.filter( ( item ) => {
|
||||
const itemId =
|
||||
item.get( 'productId' ) + '_' + item.get( 'priceId' );
|
||||
const modelId =
|
||||
model.get( 'productId' ) + '_' + model.get( 'priceId' );
|
||||
|
||||
return itemId === modelId;
|
||||
} );
|
||||
|
||||
return duplicates.length > 0;
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates the amounts for all current `OrderItem`s.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @return {$.promise} A jQuery promise representing zero or more requests.
|
||||
*/
|
||||
updateAmounts() {
|
||||
const { options } = this;
|
||||
const { state } = options;
|
||||
|
||||
const items = state.get( 'items' );
|
||||
const discounts = new Backbone.Collection(
|
||||
state.get( 'adjustments' ).getByType( 'discount' )
|
||||
);
|
||||
|
||||
const args = {
|
||||
country: state.getTaxCountry(),
|
||||
region: state.getTaxRegion(),
|
||||
products: items.map( ( item ) => ( {
|
||||
id: item.get( 'productId' ),
|
||||
quantity: item.get( 'quantity' ),
|
||||
options: {
|
||||
price_id: item.get( 'priceId' ),
|
||||
}
|
||||
} ) ),
|
||||
discountIds: discounts.pluck( 'typeId' ),
|
||||
};
|
||||
|
||||
// Keep track of all jQuery Promises.
|
||||
const promises = [];
|
||||
|
||||
// Find each `OrderItem`'s amounts.
|
||||
items.models.forEach( ( item ) => {
|
||||
const getItemAmounts = item.getAmounts( args );
|
||||
|
||||
getItemAmounts
|
||||
// Update `OrderItem`-level Adjustments.
|
||||
.done( ( { adjustments } ) => {
|
||||
// Map returned Discounts to `OrderAdjustmentDiscount`.
|
||||
const orderItemDiscounts = adjustments.map( ( adjustment ) => {
|
||||
return new OrderAdjustmentDiscount( {
|
||||
...adjustment,
|
||||
id: uuid(),
|
||||
objectId: item.get( 'id' ),
|
||||
} );
|
||||
} );
|
||||
|
||||
// Gather existing `fee` and `credit` `OrderItem`-level Adjustments.
|
||||
const orderItemAdjustments = item.get( 'adjustments' ).filter( ( adjustment ) => {
|
||||
return [ 'fee', 'credit' ].includes( adjustment.type );
|
||||
} );
|
||||
|
||||
// Reset `OrderAdjustments` collection with new data.
|
||||
item.set( 'adjustments', new OrderAdjustments( [
|
||||
...orderItemDiscounts,
|
||||
...orderItemAdjustments,
|
||||
] ) );
|
||||
} )
|
||||
// Update individual `OrderItem`s and `OrderAdjustment`s with new amounts.
|
||||
.done( ( response ) => item.setAmounts( response ) );
|
||||
|
||||
// Track jQuery Promise.
|
||||
promises.push( getItemAmounts );
|
||||
} );
|
||||
|
||||
return $.when.apply( $, promises );
|
||||
},
|
||||
} );
|
@ -0,0 +1,21 @@
|
||||
/* global Backbone */
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { OrderRefund } from './../models/order-refund.js';
|
||||
|
||||
/**
|
||||
* Collection of `OrderRefund`s.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @class OrderRefunds
|
||||
* @augments Backbone.Collection
|
||||
*/
|
||||
export const OrderRefunds = Backbone.Collection.extend( {
|
||||
/**
|
||||
* @since 3.0
|
||||
*/
|
||||
model: OrderRefund,
|
||||
} );
|
@ -0,0 +1,106 @@
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { Currency, NumberFormat } from '@easy-digital-downloads/currency';
|
||||
import { Overview } from './views/overview.js';
|
||||
import { OrderItems } from './collections/order-items.js';
|
||||
import { OrderItem } from './models/order-item.js';
|
||||
import { OrderAdjustments } from './collections/order-adjustments.js';
|
||||
import { OrderRefunds } from './collections/order-refunds.js';
|
||||
import { State } from './models/state.js';
|
||||
|
||||
// Temporarily include old Refund flow.
|
||||
import './_refund.js';
|
||||
|
||||
let overview;
|
||||
|
||||
( () => {
|
||||
if ( ! window.eddAdminOrderOverview ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const {
|
||||
isAdding,
|
||||
isRefund,
|
||||
hasTax,
|
||||
hasQuantity,
|
||||
hasDiscounts,
|
||||
order,
|
||||
items,
|
||||
adjustments,
|
||||
refunds,
|
||||
} = window.eddAdminOrderOverview;
|
||||
|
||||
const currencyFormatter = new Currency( {
|
||||
currency: order.currency,
|
||||
currencySymbol: order.currencySymbol,
|
||||
} );
|
||||
|
||||
// Create and hydrate state.
|
||||
const state = new State( {
|
||||
isAdding: '1' === isAdding,
|
||||
isRefund: '1' === isRefund,
|
||||
hasTax: '0' === hasTax ? false : hasTax,
|
||||
hasQuantity: '1' === hasQuantity,
|
||||
hasDiscounts: '1' === hasDiscounts,
|
||||
formatters: {
|
||||
currency: currencyFormatter,
|
||||
// Backbone doesn't merge nested defaults.
|
||||
number: new NumberFormat(),
|
||||
},
|
||||
order,
|
||||
} );
|
||||
|
||||
// Create collections and add to state.
|
||||
state.set( {
|
||||
items: new OrderItems( null, {
|
||||
state,
|
||||
} ),
|
||||
adjustments: new OrderAdjustments( null, {
|
||||
state,
|
||||
} ),
|
||||
refunds: new OrderRefunds( null, {
|
||||
state,
|
||||
} ),
|
||||
} );
|
||||
|
||||
// Create Overview.
|
||||
overview = new Overview( {
|
||||
state,
|
||||
} );
|
||||
|
||||
// Hydrate collections.
|
||||
|
||||
// Hydrate `OrderItem`s.
|
||||
//
|
||||
// Models are created manually before being added to the collection to
|
||||
// ensure attributes maintain schema with deep model attributes.
|
||||
items.forEach( ( item ) => {
|
||||
const orderItemAdjustments = new OrderAdjustments( item.adjustments );
|
||||
const orderItem = new OrderItem( {
|
||||
...item,
|
||||
adjustments: orderItemAdjustments,
|
||||
state,
|
||||
} );
|
||||
|
||||
state.get( 'items' ).add( orderItem );
|
||||
} );
|
||||
|
||||
// Hyrdate `Order`-level `Adjustments`.
|
||||
adjustments.forEach( ( adjustment ) => {
|
||||
state.get( 'adjustments' ).add( {
|
||||
state,
|
||||
...adjustment,
|
||||
} )
|
||||
} );
|
||||
|
||||
// Hydrate `OrderRefund`s.
|
||||
refunds.forEach( ( refund ) => {
|
||||
state.get( 'refunds' ).add( {
|
||||
state,
|
||||
...refund,
|
||||
} );
|
||||
} );
|
||||
} ) ();
|
||||
|
||||
export default overview;
|
@ -0,0 +1,76 @@
|
||||
/* global _ */
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { OrderAdjustment } from './order-adjustment.js';
|
||||
|
||||
/**
|
||||
* OrderAdjustmentDiscount
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @class OrderAdjustmentDiscount
|
||||
* @augments Backbone.Model
|
||||
*/
|
||||
export const OrderAdjustmentDiscount = OrderAdjustment.extend( {
|
||||
/**
|
||||
* @since 3.0
|
||||
*
|
||||
* @typedef {Object} OrderAdjustmentDiscount
|
||||
*/
|
||||
defaults: {
|
||||
...OrderAdjustment.prototype.defaults,
|
||||
type: 'discount',
|
||||
},
|
||||
|
||||
/**
|
||||
* @since 3.0
|
||||
*/
|
||||
idAttribute: 'typeId',
|
||||
|
||||
/**
|
||||
* Returns the `OrderAdjustmentDiscount`'s amount based on the current values
|
||||
* of all `OrderItems` discounts.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @return {number} `OrderAdjustmentDiscount` amount.
|
||||
*/
|
||||
getAmount() {
|
||||
let amount = 0;
|
||||
|
||||
const state = this.get( 'state' );
|
||||
|
||||
// Return stored amount if viewing an existing Order.
|
||||
if ( false === state.get( 'isAdding' ) ) {
|
||||
return OrderAdjustment.prototype.getAmount.apply( this, arguments );
|
||||
}
|
||||
|
||||
const { models: items } = state.get( 'items' );
|
||||
const { number } = state.get( 'formatters' );
|
||||
|
||||
items.forEach( ( item ) => {
|
||||
const discount = item.get( 'adjustments' ).findWhere( {
|
||||
typeId: this.get( 'typeId' ),
|
||||
} );
|
||||
|
||||
if ( undefined !== discount ) {
|
||||
amount += number.unformat(
|
||||
number.format( discount.get( 'subtotal' ) )
|
||||
);
|
||||
}
|
||||
} );
|
||||
|
||||
return amount;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the `OrderAdjustment` total.
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
getTotal() {
|
||||
return this.getAmount();
|
||||
},
|
||||
} );
|
@ -0,0 +1,101 @@
|
||||
/* global Backbone */
|
||||
|
||||
/**
|
||||
* OrderAdjustment
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @class OrderAdjustment
|
||||
* @augments Backbone.Model
|
||||
*/
|
||||
export const OrderAdjustment = Backbone.Model.extend( {
|
||||
/**
|
||||
* @since 3.0
|
||||
*
|
||||
* @typedef {Object} OrderAdjustment
|
||||
*/
|
||||
defaults: {
|
||||
id: 0,
|
||||
objectId: 0,
|
||||
objectType: '',
|
||||
typeId: 0,
|
||||
type: '',
|
||||
description: '',
|
||||
subtotal: 0,
|
||||
tax: 0,
|
||||
total: 0,
|
||||
dateCreated: '',
|
||||
dateModified: '',
|
||||
uuid: '',
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the `OrderAdjustment` amount.
|
||||
*
|
||||
* Separate from subtotal or total calculation so `OrderAdjustmentDiscount`
|
||||
* can be calculated independently.
|
||||
*
|
||||
* @see OrderAdjustmentDiscount.prototype.getAmount()
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
getAmount() {
|
||||
return this.get( 'subtotal' );
|
||||
},
|
||||
|
||||
/**
|
||||
* Retrieves the `OrderAdjustment` tax.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @return {number} Total amount.
|
||||
*/
|
||||
getTax() {
|
||||
return this.get( 'tax' );
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the `OrderAdjustment` total.
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
getTotal() {
|
||||
// Fees always have tax added exclusively.
|
||||
// @link https://github.com/easydigitaldownloads/easy-digital-downloads/issues/2445#issuecomment-53215087
|
||||
// @link https://github.com/easydigitaldownloads/easy-digital-downloads/blob/f97f4f6f5454921a2014dc1fa8f4caa5f550108c/includes/cart/class-edd-cart.php#L1306-L1311
|
||||
return this.get( 'subtotal' ) + this.get( 'tax' );
|
||||
},
|
||||
|
||||
/**
|
||||
* Recalculates the tax amount based on the current tax rate.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*/
|
||||
updateTax() {
|
||||
const state = this.get( 'state' );
|
||||
const hasTax = state.get( 'hasTax' );
|
||||
|
||||
if (
|
||||
'none' === hasTax ||
|
||||
'' === hasTax.country ||
|
||||
'' === hasTax.rate
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { number } = state.get( 'formatters' );
|
||||
const taxRate = hasTax.rate / 100;
|
||||
const adjustments = state.get( 'adjustments' ).getByType( 'fee' );
|
||||
|
||||
adjustments.forEach( ( adjustment ) => {
|
||||
if ( false === adjustment.get( 'isTaxable' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const taxableAmount = adjustment.getAmount();
|
||||
const taxAmount = number.unformat( taxableAmount * taxRate );
|
||||
|
||||
adjustment.set( 'tax', taxAmount );
|
||||
} );
|
||||
}
|
||||
} );
|
@ -0,0 +1,250 @@
|
||||
/* global Backbone, _, $ */
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { OrderAdjustments } from './../collections/order-adjustments.js';
|
||||
|
||||
/**
|
||||
* OrderItem
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @class OrderItem
|
||||
* @augments Backbone.Model
|
||||
*/
|
||||
export const OrderItem = Backbone.Model.extend( {
|
||||
/**
|
||||
* @since 3.0
|
||||
*
|
||||
* @typedef {Object} OrderItem
|
||||
*/
|
||||
defaults: {
|
||||
id: 0,
|
||||
orderId: 0,
|
||||
productId: 0,
|
||||
productName: '',
|
||||
priceId: null,
|
||||
cartIndex: 0,
|
||||
type: 'download',
|
||||
status: '',
|
||||
statusLabel: '',
|
||||
quantity: 1,
|
||||
amount: 0,
|
||||
subtotal: 0,
|
||||
discount: 0,
|
||||
tax: 0,
|
||||
total: 0,
|
||||
dateCreated: '',
|
||||
dateModified: '',
|
||||
uuid: '',
|
||||
|
||||
// Track manually set amounts.
|
||||
amountManual: 0,
|
||||
taxManual: 0,
|
||||
subtotalManual: 0,
|
||||
|
||||
// Track if the amounts have been adjusted manually on addition.
|
||||
_isAdjustingManually: false,
|
||||
|
||||
// Track `OrderItem`-level adjustments.
|
||||
//
|
||||
// The handling of Adjustments in the API is currently somewhat
|
||||
// fragmented with certain extensions creating Adjustments at the
|
||||
// `Order` level, some at a duplicate `OrderItem` level, and some both.
|
||||
adjustments: new OrderAdjustments(),
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the `OrderItem` subtotal amount.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @param {bool} includeTax If taxes should be included when retrieving the subtotal.
|
||||
* This is needed in some scenarios with inclusive taxes.
|
||||
* @return {number} Subtotal amount.
|
||||
*/
|
||||
getSubtotal( includeTax = false ) {
|
||||
const state = this.get( 'state' );
|
||||
const subtotal = this.get( 'subtotal' );
|
||||
|
||||
// Use stored value if the record has already been created.
|
||||
if ( false === state.get( 'isAdding' ) ) {
|
||||
return subtotal;
|
||||
}
|
||||
|
||||
// Calculate subtotal.
|
||||
if ( true === state.hasInclusiveTax() && false === includeTax ) {
|
||||
return subtotal - this.getTax();
|
||||
}
|
||||
|
||||
return subtotal;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the Discount amount.
|
||||
*
|
||||
* If an Order is being added the amount is calculated based
|
||||
* on the total of `OrderItem`-level Adjustments that are
|
||||
* currently applied.
|
||||
*
|
||||
* If an Order has already been added use the amount stored
|
||||
* directly in the database.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @return {number} Discount amount.
|
||||
*/
|
||||
getDiscountAmount() {
|
||||
let amount = 0;
|
||||
|
||||
const discounts = this.get( 'adjustments' ).getByType( 'discount' );
|
||||
|
||||
if ( 0 === discounts.length ) {
|
||||
return this.get( 'discount' );
|
||||
}
|
||||
|
||||
discounts.forEach( ( discount ) => {
|
||||
amount += +discount.get( 'subtotal' );
|
||||
} );
|
||||
|
||||
return amount;
|
||||
},
|
||||
|
||||
/**
|
||||
* Retrieves the rounded Tax for the order item.
|
||||
*
|
||||
* Rounded to match storefront checkout.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @return {number} Total amount.
|
||||
*/
|
||||
getTax() {
|
||||
const state = this.get( 'state' );
|
||||
const tax = this.get( 'tax' );
|
||||
|
||||
// Use stored value if the record has already been created.
|
||||
if ( false === state.get( 'isAdding' ) ) {
|
||||
return tax;
|
||||
}
|
||||
|
||||
// Calculate tax.
|
||||
const { number } = state.get( 'formatters' );
|
||||
|
||||
return number.unformat( number.format( tax ) );
|
||||
},
|
||||
|
||||
/**
|
||||
* Retrieves the Total for the order item.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*
|
||||
* @return {number} Total amount.
|
||||
*/
|
||||
getTotal() {
|
||||
const state = this.get( 'state' );
|
||||
|
||||
// Use stored value if the record has already been created.
|
||||
if ( false === state.get( 'isAdding' ) ) {
|
||||
return this.get( 'total' );
|
||||
}
|
||||
|
||||
// Calculate total.
|
||||
if ( true === state.hasInclusiveTax() ) {
|
||||
return this.get( 'subtotal' ) - this.getDiscountAmount();
|
||||
}
|
||||
|
||||
return ( this.get( 'subtotal' ) - this.getDiscountAmount() ) + this.getTax();
|
||||
},
|
||||
|
||||
/**
|
||||
* Retrieves amounts for the `OrderItem` based on other `OrderItem`s and `OrderAdjustment`s.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @param {Object} args Arguments to pass as data in the XHR request.
|
||||
* @param {string} args.country Country code to determine tax rate.
|
||||
* @param {string} args.region Region to determine tax rate.
|
||||
* @param {Array} args.products List of current products added to the order.
|
||||
* @param {Array} args.discountIds List of `OrderAdjustmentDiscount`s to calculate amounts against.
|
||||
* @return {$.promise} A jQuery promise that represents the request.
|
||||
*/
|
||||
getAmounts( {
|
||||
country = '',
|
||||
region = '',
|
||||
products = [],
|
||||
discountIds = [],
|
||||
} ) {
|
||||
const {
|
||||
nonces: { edd_admin_order_get_item_amounts: nonce },
|
||||
} = window.eddAdminOrderOverview;
|
||||
|
||||
const { productId, priceId, quantity, amount, tax, subtotal } = _.clone(
|
||||
this.attributes
|
||||
);
|
||||
|
||||
return wp.ajax.send( 'edd-admin-order-get-item-amounts', {
|
||||
data: {
|
||||
nonce,
|
||||
productId,
|
||||
priceId,
|
||||
quantity,
|
||||
amount,
|
||||
tax,
|
||||
subtotal,
|
||||
country,
|
||||
region,
|
||||
products: _.uniq( [
|
||||
...products,
|
||||
{
|
||||
id: productId,
|
||||
quantity,
|
||||
options: {
|
||||
price_id: priceId,
|
||||
},
|
||||
},
|
||||
], function( { id, options: { price_id } } ) {
|
||||
return `${ id }_${ price_id }`
|
||||
} ),
|
||||
discounts: _.uniq( discountIds ),
|
||||
},
|
||||
} );
|
||||
},
|
||||
|
||||
/**
|
||||
* Bulk sets amounts.
|
||||
*
|
||||
* Only adjusts the Discount amount if adjusting manually.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @param {Object} amounts Amounts to set.
|
||||
* @param {number} amounts.amount `OrderItem` unit price.
|
||||
* @param {number} amounts.discount `OrderItem` discount amount.
|
||||
* @param {number} amounts.tax `OrderItem` tax amount.
|
||||
* @param {number} amounts.subtotal `OrderItem` subtotal amount.
|
||||
* @param {number} amounts.total `OrderItem` total amount.
|
||||
*/
|
||||
setAmounts( {
|
||||
amount = 0,
|
||||
discount = 0,
|
||||
tax = 0,
|
||||
subtotal = 0,
|
||||
total = 0,
|
||||
} ) {
|
||||
if ( true === this.get( '_isAdjustingManually' ) ) {
|
||||
this.set( {
|
||||
discount,
|
||||
} );
|
||||
} else {
|
||||
this.set( {
|
||||
amount,
|
||||
discount,
|
||||
tax,
|
||||
subtotal,
|
||||
total,
|
||||
} );
|
||||
}
|
||||
},
|
||||
} );
|
@ -0,0 +1,24 @@
|
||||
/* global Backbone */
|
||||
|
||||
/**
|
||||
* OrderRefund
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @class OrderRefund
|
||||
* @augments Backbone.Model
|
||||
*/
|
||||
export const OrderRefund = Backbone.Model.extend( {
|
||||
/**
|
||||
* @since 3.0
|
||||
*
|
||||
* @typedef {Object} OrderAdjustment
|
||||
*/
|
||||
defaults: {
|
||||
id: 0,
|
||||
number: '',
|
||||
total: 0,
|
||||
dateCreated: '',
|
||||
dateCreatedi18n: '',
|
||||
},
|
||||
} );
|
@ -0,0 +1,233 @@
|
||||
/* global Backbone, _ */
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { Currency, NumberFormat } from '@easy-digital-downloads/currency';
|
||||
|
||||
/**
|
||||
* State
|
||||
*
|
||||
* Leverages `Backbone.Model` and subsequently `Backbone.Events`
|
||||
* to easily track changes to top level state changes.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @class State
|
||||
* @augments Backbone.Model
|
||||
*/
|
||||
export const State = Backbone.Model.extend(
|
||||
/** Lends State.prototype */ {
|
||||
/**
|
||||
* @since 3.0
|
||||
*
|
||||
* @typedef {Object} State
|
||||
*/
|
||||
defaults: {
|
||||
isAdding: false,
|
||||
isFetching: false,
|
||||
hasQuantity: false,
|
||||
hasTax: false,
|
||||
items: [],
|
||||
adjustments: [],
|
||||
refunds: [],
|
||||
formatters: {
|
||||
currency: new Currency(),
|
||||
number: new NumberFormat(),
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the current tax rate's country code.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @return {string} Tax rate country code.
|
||||
*/
|
||||
getTaxCountry() {
|
||||
return false !== this.get( 'hasTax' )
|
||||
? this.get( 'hasTax' ).country
|
||||
: '';
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the current tax rate's region.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @return {string} Tax rate region.
|
||||
*/
|
||||
getTaxRegion() {
|
||||
return false !== this.get( 'hasTax' )
|
||||
? this.get( 'hasTax' ).region
|
||||
: '';
|
||||
},
|
||||
|
||||
/**
|
||||
* Retrieves the Order subtotal.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @param {bool} includeTax If taxes should be included when retrieving the subtotal.
|
||||
* This is needed in some scenarios with inclusive taxes.
|
||||
* @return {number} Order subtotal.
|
||||
*/
|
||||
getSubtotal( includeTax = false ) {
|
||||
// Use stored value if the record has already been created.
|
||||
if ( false === this.get( 'isAdding' ) ) {
|
||||
return this.get( 'order' ).subtotal;
|
||||
}
|
||||
|
||||
const { models: items } = this.get( 'items' );
|
||||
|
||||
return items.reduce(
|
||||
( amount, item ) => {
|
||||
return amount += +item.getSubtotal( includeTax );
|
||||
},
|
||||
0
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Retrieves the Order discount.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @return {number} Order discount.
|
||||
*/
|
||||
getDiscount() {
|
||||
// Use stored value if the record has already been created.
|
||||
if ( false === this.get( 'isAdding' ) ) {
|
||||
return this.get( 'order' ).discount;
|
||||
}
|
||||
|
||||
const adjustments = this.get( 'adjustments' ).getByType( 'discount' );
|
||||
|
||||
return adjustments.reduce(
|
||||
( amount, adjustment ) => {
|
||||
return amount += +adjustment.getAmount();
|
||||
},
|
||||
0
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Retrieves the Order tax.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @return {number} Order tax.
|
||||
*/
|
||||
getTax() {
|
||||
// Use stored value if the record has already been created.
|
||||
if ( false === this.get( 'isAdding' ) ) {
|
||||
return this.get( 'order' ).tax;
|
||||
}
|
||||
|
||||
const items = this.get( 'items' ).models;
|
||||
const feesTax = this.getFeesTax();
|
||||
|
||||
return items.reduce(
|
||||
( amount, item ) => {
|
||||
return amount += +item.getTax();
|
||||
},
|
||||
feesTax
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Retrieves the Order tax amount for fees.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @return {number} Order tax amount for fees.
|
||||
*/
|
||||
getFeesTax() {
|
||||
// Use stored value if the record has already been created.
|
||||
if ( false === this.get( 'isAdding' ) ) {
|
||||
return this.get( 'order' ).tax;
|
||||
}
|
||||
|
||||
const adjustments = this.get( 'adjustments' ).getByType( 'fee' );
|
||||
|
||||
return adjustments.reduce(
|
||||
( amount, item ) => {
|
||||
return amount += +item.getTax();
|
||||
},
|
||||
0
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Retrieves the Order total.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @return {number} Order total.
|
||||
*/
|
||||
getTotal() {
|
||||
// Use stored value if the record has already been created.
|
||||
if ( false === this.get( 'isAdding' ) ) {
|
||||
return this.get( 'order' ).total;
|
||||
}
|
||||
|
||||
// Calculate all adjustments that affect the total.
|
||||
const { models: adjustments } = this.get( 'adjustments' );
|
||||
const includeTaxInSubtotal = true;
|
||||
|
||||
const adjustedSubtotal = adjustments.reduce(
|
||||
( amount, adjustment ) => {
|
||||
if (
|
||||
[ 'discount', 'credit' ].includes(
|
||||
adjustment.get( 'type' )
|
||||
)
|
||||
) {
|
||||
return amount -= +adjustment.getAmount();
|
||||
} else {
|
||||
return amount += +adjustment.get( 'subtotal' );
|
||||
}
|
||||
},
|
||||
this.getSubtotal( includeTaxInSubtotal )
|
||||
);
|
||||
|
||||
if ( true === this.hasInclusiveTax() ) {
|
||||
// Fees always have tax added exclusively.
|
||||
// @link https://github.com/easydigitaldownloads/easy-digital-downloads/issues/2445#issuecomment-53215087
|
||||
// @link https://github.com/easydigitaldownloads/easy-digital-downloads/blob/f97f4f6f5454921a2014dc1fa8f4caa5f550108c/includes/cart/class-edd-cart.php#L1306-L1311
|
||||
return adjustedSubtotal + this.getFeesTax();
|
||||
}
|
||||
|
||||
return adjustedSubtotal + this.getTax();
|
||||
},
|
||||
|
||||
/**
|
||||
* Determines if the state has a new, valid, tax rate.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @return {bool} True if the rate has changed.
|
||||
*/
|
||||
hasNewTaxRate() {
|
||||
const hasTax = this.get( 'hasTax' );
|
||||
|
||||
if ( false === hasTax ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const prevHasTax = this.previous( 'hasTax' );
|
||||
|
||||
return ! _.isEqual( hasTax, prevHasTax );
|
||||
},
|
||||
|
||||
/**
|
||||
* Determines if the state has prices entered inclusive of tax.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @returns {bool} True if prices are entered inclusive of tax.
|
||||
*/
|
||||
hasInclusiveTax() {
|
||||
return this.get( 'hasTax' ).inclusive;
|
||||
}
|
||||
}
|
||||
);
|
@ -0,0 +1,91 @@
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { edd_attach_tooltips as setupTooltips } from 'admin/components/tooltips';
|
||||
import { FormAddOrderItem } from './form-add-order-item.js';
|
||||
import { FormAddOrderDiscount } from './form-add-order-discount.js';
|
||||
import { FormAddOrderAdjustment } from './form-add-order-adjustment.js';
|
||||
|
||||
/**
|
||||
* Actions
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @class Actions
|
||||
* @augments wp.Backbone.View
|
||||
*/
|
||||
export const Actions = wp.Backbone.View.extend( {
|
||||
/**
|
||||
* @since 3.0
|
||||
*/
|
||||
el: '#edd-order-overview-actions',
|
||||
|
||||
/**
|
||||
* @since 3.0
|
||||
*/
|
||||
template: wp.template( 'edd-admin-order-actions' ),
|
||||
|
||||
/**
|
||||
* @since 3.0
|
||||
*/
|
||||
events: {
|
||||
'click #add-item': 'onAddOrderItem',
|
||||
'click #add-discount': 'onAddOrderDiscount',
|
||||
'click #add-adjustment': 'onAddOrderAdjustment',
|
||||
},
|
||||
|
||||
/**
|
||||
* Ensures tooltips can be used after render.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @return {Object}
|
||||
*/
|
||||
render() {
|
||||
wp.Backbone.View.prototype.render.apply( this, arguments );
|
||||
|
||||
// Setup Tooltips after render.
|
||||
setupTooltips( $( '.edd-help-tip' ) );
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Renders the "Add Item" flow.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @param {Object} e Click event.
|
||||
*/
|
||||
onAddOrderItem( e ) {
|
||||
e.preventDefault();
|
||||
|
||||
new FormAddOrderItem( this.options ).openDialog().render();
|
||||
},
|
||||
|
||||
/**
|
||||
* Renders the "Add Discount" flow.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @param {Object} e Click event.
|
||||
*/
|
||||
onAddOrderDiscount( e ) {
|
||||
e.preventDefault();
|
||||
|
||||
new FormAddOrderDiscount( this.options ).openDialog().render();
|
||||
},
|
||||
|
||||
/**
|
||||
* Renders the "Add Adjustment" flow.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @param {Object} e Click event.
|
||||
*/
|
||||
onAddOrderAdjustment( e ) {
|
||||
e.preventDefault();
|
||||
|
||||
new FormAddOrderAdjustment( this.options ).openDialog().render();
|
||||
},
|
||||
} );
|
@ -0,0 +1,255 @@
|
||||
/* global _, $ */
|
||||
|
||||
/**
|
||||
* WordPress dependencies
|
||||
*/
|
||||
import { focus } from '@wordpress/dom';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { getChosenVars } from 'utils/chosen.js';
|
||||
|
||||
// Set noconflict when using Lodash (@wordpress packages) and Underscores.
|
||||
// @todo Find a better place to set this up. Webpack?
|
||||
window.lodash = _.noConflict();
|
||||
|
||||
/**
|
||||
* Base
|
||||
*
|
||||
* Supplies additional functionality and helpers beyond
|
||||
* what is provided by `wp.Backbone.View`.
|
||||
*
|
||||
* - Maintains focus and caret positioning on rendering.
|
||||
* - Extends events via `addEvents()`.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @class Base
|
||||
* @augments wp.Backbone.View
|
||||
*/
|
||||
export const Base = wp.Backbone.View.extend( {
|
||||
/**
|
||||
* Defines base events to help maintain focus and caret position.
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
events: {
|
||||
'keydown input': 'handleTabBehavior',
|
||||
'keydown textarea': 'handleTabBehavior',
|
||||
|
||||
'focus input': 'onFocus',
|
||||
'focus textarea': 'onFocus',
|
||||
'focus select': 'onFocus',
|
||||
|
||||
'change input': 'onChange',
|
||||
'change textarea': 'onChange',
|
||||
'change select': 'onChange',
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets up additional properties.
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
preinitialize() {
|
||||
this.focusedEl = null;
|
||||
this.focusedElCaretPos = 0;
|
||||
|
||||
wp.Backbone.View.prototype.preinitialize.apply( this, arguments );
|
||||
},
|
||||
|
||||
/**
|
||||
* Merges additional events with existing events.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @param {Object} events Hash of events to add.
|
||||
*/
|
||||
addEvents( events ) {
|
||||
this.delegateEvents( {
|
||||
...this.events,
|
||||
...events,
|
||||
} );
|
||||
},
|
||||
|
||||
/**
|
||||
* Moves the focus when dealing with tabbing.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @param {Object} e Keydown event.
|
||||
*/
|
||||
handleTabBehavior( e ) {
|
||||
const { keyCode, shiftKey, target } = e;
|
||||
|
||||
// 9 = TAB
|
||||
if ( 9 !== keyCode ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const tabbables = focus.tabbable.find( this.el );
|
||||
|
||||
if ( ! tabbables.length ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const firstTabbable = tabbables[ 0 ];
|
||||
const lastTabbable = tabbables[ tabbables.length - 1 ];
|
||||
let toFocus;
|
||||
|
||||
if ( shiftKey && target === firstTabbable ) {
|
||||
toFocus = lastTabbable;
|
||||
} else if ( ! shiftKey && target === lastTabbable ) {
|
||||
toFocus = firstTabbable;
|
||||
} else if ( shiftKey ) {
|
||||
toFocus = focus.tabbable.findPrevious( target );
|
||||
} else {
|
||||
toFocus = focus.tabbable.findNext( target );
|
||||
}
|
||||
|
||||
if ( 'undefined' !== typeof toFocus ) {
|
||||
this.focusedEl = toFocus;
|
||||
this.focusedElCartetPos = toFocus.value.length;
|
||||
} else {
|
||||
this.focusedEl = null;
|
||||
this.focusedElCartetPos = 0;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Tracks the current element when focusing.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @param {Object} e Change event.
|
||||
*/
|
||||
onFocus( e ) {
|
||||
this.focusedEl = e.target;
|
||||
},
|
||||
|
||||
/**
|
||||
* Tracks the current cursor position when editing.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @param {Object} e Change event.
|
||||
*/
|
||||
onChange( e ) {
|
||||
const { target, keyCode } = e;
|
||||
|
||||
// 9 = TAB
|
||||
if ( undefined !== typeof keyCode && 9 === keyCode ) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if ( target.selectionStart ) {
|
||||
this.focusedElCaretPos = target.selectionStart;
|
||||
}
|
||||
} catch ( error ) {
|
||||
this.focusedElCaretPos = target.value.length;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Prepares data to be used in `render` method.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @see wp.Backbone.View
|
||||
* @see https://github.com/WordPress/WordPress/blob/master/wp-includes/js/wp-backbone.js
|
||||
*
|
||||
* @return {Object} The data for this view.
|
||||
*/
|
||||
prepare() {
|
||||
return this.model
|
||||
? {
|
||||
...this.model.toJSON(),
|
||||
state: this.model.get( 'state' ).toJSON(),
|
||||
}
|
||||
: {};
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds additional handling after initial render.
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
render() {
|
||||
wp.Backbone.View.prototype.render.apply( this, arguments );
|
||||
|
||||
this.initializeSelects();
|
||||
this.setFocus();
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Reinitializes special <select> fields.
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
initializeSelects() {
|
||||
const selects = this.el.querySelectorAll( '.edd-select-chosen' );
|
||||
|
||||
// Reinialize Chosen.js
|
||||
_.each( selects, ( el ) => {
|
||||
$( el ).chosen( {
|
||||
...getChosenVars( $( el ) ),
|
||||
width: '100%',
|
||||
} );
|
||||
} );
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the focus and caret position.
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
setFocus() {
|
||||
const { el, focusedEl, focusedElCaretPos } = this;
|
||||
|
||||
// Do nothing extra if nothing is focused.
|
||||
if ( null === focusedEl || 'undefined' === typeof focusedEl ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert full element in to a usable selector.
|
||||
// We can't search for the actual HTMLElement because
|
||||
// the DOM has since changed.
|
||||
let selector = null;
|
||||
|
||||
if ( '' !== focusedEl.id ) {
|
||||
selector = `#${ focusedEl.id }`;
|
||||
} else if ( '' !== focusedEl.name ) {
|
||||
selector = `[name="${ focusedEl.name }"]`;
|
||||
} else if ( focusedEl.classList.length > 0 ) {
|
||||
selector = `.${ [ ...focusedEl.classList ].join( '.' ) }`;
|
||||
}
|
||||
|
||||
// Do nothing if we can't generate a selector.
|
||||
if ( null === selector ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Focus element.
|
||||
const elToFocus = el.querySelector( selector );
|
||||
|
||||
if ( ! elToFocus ) {
|
||||
return;
|
||||
}
|
||||
|
||||
elToFocus.focus();
|
||||
|
||||
// Attempt to set the caret position.
|
||||
try {
|
||||
if ( elToFocus.setSelectionRange ) {
|
||||
elToFocus.setSelectionRange(
|
||||
focusedElCaretPos,
|
||||
focusedElCaretPos
|
||||
);
|
||||
}
|
||||
} catch ( error ) {}
|
||||
},
|
||||
} );
|
@ -0,0 +1,113 @@
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { Base } from './base.js';
|
||||
import { Dialog } from './dialog.js';
|
||||
|
||||
/**
|
||||
* "Copy Download Link" view
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @class FormAddOrderItem
|
||||
* @augments Dialog
|
||||
*/
|
||||
export const CopyDownloadLink = Dialog.extend( {
|
||||
/**
|
||||
* @since 3.0
|
||||
*/
|
||||
el: '#edd-admin-order-copy-download-link-dialog',
|
||||
|
||||
/**
|
||||
* @since 3.0
|
||||
*/
|
||||
template: wp.template( 'edd-admin-order-copy-download-link' ),
|
||||
|
||||
/**
|
||||
* "Copy Download Link" view.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @constructs CopyDownloadLink
|
||||
* @augments Base
|
||||
*/
|
||||
initialize() {
|
||||
Dialog.prototype.initialize.apply( this, arguments );
|
||||
|
||||
this.link = false;
|
||||
|
||||
this.addEvents( {
|
||||
'click #close': 'closeDialog',
|
||||
} );
|
||||
|
||||
this.fetchLink.call( this );
|
||||
},
|
||||
|
||||
/**
|
||||
* Prepares data to be used in `render` method.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @see wp.Backbone.View
|
||||
* @see https://github.com/WordPress/WordPress/blob/master/wp-includes/js/wp-backbone.js
|
||||
*
|
||||
* @return {Object} The data for this view.
|
||||
*/
|
||||
prepare() {
|
||||
const { link } = this;
|
||||
|
||||
return {
|
||||
link,
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Renders the view.
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
render() {
|
||||
Base.prototype.render.apply( this, arguments );
|
||||
|
||||
const { el, link } = this;
|
||||
|
||||
// Select the contents if a link is available.
|
||||
if ( false !== link && '' !== link ) {
|
||||
el.querySelector( '#link' ).select();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Fetches the Download's file URLs.
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
fetchLink() {
|
||||
const { orderId, productId, priceId } = this.options;
|
||||
|
||||
// Retrieve and set link.
|
||||
//
|
||||
// We can't use wp.ajax.send because the `edd_ajax_generate_file_download_link()`
|
||||
// does not send back JSON responses.
|
||||
$.ajax( {
|
||||
type: 'POST',
|
||||
url: ajaxurl,
|
||||
data: {
|
||||
action: 'edd_get_file_download_link',
|
||||
payment_id: orderId,
|
||||
download_id: productId,
|
||||
price_id: priceId,
|
||||
},
|
||||
} )
|
||||
.done( ( link ) => {
|
||||
link = link.trim();
|
||||
|
||||
if ( [ '-1', '-2', '-3', '-4', '' ].includes( link ) ) {
|
||||
this.link = '';
|
||||
} else {
|
||||
this.link = link.trim();
|
||||
}
|
||||
} )
|
||||
.done( () => this.render() );
|
||||
},
|
||||
} );
|
@ -0,0 +1,79 @@
|
||||
/* global eddAdminOrderOverview */
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { Base } from './base.js';
|
||||
|
||||
/**
|
||||
* "Dialog" view
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @class Dialog
|
||||
* @augments Base
|
||||
*/
|
||||
export const Dialog = Base.extend( {
|
||||
/**
|
||||
* "Dialog" view.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @constructs Dialog
|
||||
* @augments wp.Backbone.View
|
||||
*/
|
||||
initialize() {
|
||||
this.$el.dialog( {
|
||||
position: {
|
||||
my: 'top center',
|
||||
at: 'center center-25%',
|
||||
},
|
||||
classes: {
|
||||
'ui-dialog': 'edd-dialog',
|
||||
},
|
||||
closeText: eddAdminOrderOverview.i18n.closeText,
|
||||
width: '350px',
|
||||
modal: true,
|
||||
resizable: false,
|
||||
draggable: false,
|
||||
autoOpen: false,
|
||||
create: function() {
|
||||
$( this ).css( 'maxWidth', '90vw' );
|
||||
},
|
||||
} );
|
||||
},
|
||||
|
||||
/**
|
||||
* Opens the jQuery UI Dialog containing this view.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @return {Dialog} Current view.
|
||||
*/
|
||||
openDialog() {
|
||||
this.$el.dialog( 'open' );
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Closes the jQuery UI Dialog containing this view.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @param {Object=} e Event that triggered the close.
|
||||
* @return {Dialog} Current view.
|
||||
*/
|
||||
closeDialog( e ) {
|
||||
if ( e && e.preventDefault ) {
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
this.$el.dialog( 'close' );
|
||||
|
||||
// Prevent events from stacking.
|
||||
this.undelegateEvents();
|
||||
|
||||
return this;
|
||||
},
|
||||
} );
|
@ -0,0 +1,289 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import uuid from 'uuid-random';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { Base } from './base.js';
|
||||
import { Dialog } from './dialog.js';
|
||||
import { OrderAdjustment } from './../models/order-adjustment.js';
|
||||
|
||||
/**
|
||||
* FormAddOrderAdjustment
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @class FormAddOrderAdjustment
|
||||
* @augments Dialog
|
||||
*/
|
||||
export const FormAddOrderAdjustment = Dialog.extend( {
|
||||
/**
|
||||
* @since 3.0
|
||||
*/
|
||||
el: '#edd-admin-order-add-adjustment-dialog',
|
||||
|
||||
/**
|
||||
* @since 3.0
|
||||
*/
|
||||
template: wp.template( 'edd-admin-order-form-add-order-adjustment' ),
|
||||
|
||||
/**
|
||||
* "Add Adjustment" view.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @constructs FormAddOrderAdjustment
|
||||
* @augments Dialog
|
||||
*/
|
||||
initialize() {
|
||||
Dialog.prototype.initialize.apply( this, arguments );
|
||||
|
||||
// Delegate additional events.
|
||||
this.addEvents( {
|
||||
'change #object_type': 'onChangeObjectType',
|
||||
'change [name="type"]': 'onChangeType',
|
||||
|
||||
'keyup #amount': 'onChangeAmount',
|
||||
'change #no-tax': 'onHasTaxToggle',
|
||||
'click #set-address': 'onSetAddress',
|
||||
|
||||
'keyup #description': 'onChangeDescription',
|
||||
|
||||
'submit form': 'onAdd',
|
||||
} );
|
||||
|
||||
const { state } = this.options;
|
||||
|
||||
// Create a model `OrderAdjustment` to be added.
|
||||
this.model = new OrderAdjustment( {
|
||||
id: uuid(),
|
||||
objectId: uuid(),
|
||||
typeId: uuid(),
|
||||
objectType: 'order',
|
||||
type: 'fee',
|
||||
amountManual: '',
|
||||
isTaxed: true,
|
||||
|
||||
state,
|
||||
} );
|
||||
|
||||
// Listen for events.
|
||||
this.listenTo( this.model, 'change', this.render );
|
||||
this.listenTo( state.get( 'adjustments' ), 'add', this.closeDialog );
|
||||
},
|
||||
|
||||
/**
|
||||
* Prepares data to be used in `render` method.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @see wp.Backbone.View
|
||||
* @see https://github.com/WordPress/WordPress/blob/master/wp-includes/js/wp-backbone.js
|
||||
*
|
||||
* @return {Object} The data for this view.
|
||||
*/
|
||||
prepare() {
|
||||
const { model, options } = this;
|
||||
const { state } = options;
|
||||
|
||||
return {
|
||||
...Base.prototype.prepare.apply( this, arguments ),
|
||||
|
||||
// Pass existing OrderItems so we can apply a fee at OrderItem level.
|
||||
orderItems: state.get( 'items' ).models.map( ( item ) => ( {
|
||||
id: item.get( 'id' ),
|
||||
productName: item.get( 'productName' ),
|
||||
} ) ),
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates the `OrderAdjustment` when the Object Type changes.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @param {Object} e Change event
|
||||
*/
|
||||
onChangeObjectType( e ) {
|
||||
const {
|
||||
target: { options, selectedIndex },
|
||||
} = e;
|
||||
|
||||
const selected = options[ selectedIndex ];
|
||||
|
||||
const objectType = selected.value;
|
||||
let objectId = this.model.get( 'objectId' );
|
||||
|
||||
// Apply to a specific `OrderItem`.
|
||||
if ( 'order_item' === objectType ) {
|
||||
objectId = selected.dataset.orderItemId;
|
||||
|
||||
this.model.set( {
|
||||
objectId,
|
||||
objectType,
|
||||
} );
|
||||
|
||||
// Apply to the whole order.
|
||||
} else {
|
||||
this.model.set( {
|
||||
objectType,
|
||||
objectId,
|
||||
} );
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates the `OrderAdjustment` when the Type changes.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @param {Object} e Change event
|
||||
*/
|
||||
onChangeType( e ) {
|
||||
const type = e.target.value;
|
||||
|
||||
this.model.set( 'type', type );
|
||||
|
||||
if ( 'credit' === type ) {
|
||||
this.model.set( 'objectId', 0 );
|
||||
this.model.set( 'objectType', 'order' );
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates the `OrderAdjustment` when the Amount changes.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @param {Object} e Change event
|
||||
*/
|
||||
onChangeAmount( e ) {
|
||||
const { target } = e;
|
||||
|
||||
e.preventDefault();
|
||||
|
||||
const { state } = this.options;
|
||||
const { number } = state.get( 'formatters' );
|
||||
|
||||
const amountManual = target.value;
|
||||
const amountNumber = number.unformat( amountManual );
|
||||
|
||||
let taxNumber = 0;
|
||||
|
||||
const hasTax = state.get( 'hasTax' );
|
||||
|
||||
if (
|
||||
true === this.model.get( 'isTaxed' ) &&
|
||||
'fee' === this.model.get( 'type' ) &&
|
||||
'none' !== hasTax &&
|
||||
'' !== hasTax.country &&
|
||||
'' !== hasTax.rate
|
||||
) {
|
||||
taxNumber = amountNumber * ( hasTax.rate / 100 );
|
||||
}
|
||||
|
||||
this.model.set( {
|
||||
amountManual,
|
||||
subtotal: amountNumber,
|
||||
total: amountNumber,
|
||||
tax: number.unformat( number.format( taxNumber ) ),
|
||||
} );
|
||||
},
|
||||
|
||||
/**
|
||||
* Toggles if the fee should be taxed.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @param {Object} e Change event.
|
||||
*/
|
||||
onHasTaxToggle( e ) {
|
||||
e.preventDefault();
|
||||
|
||||
const checked = e.target.checked;
|
||||
const args = {
|
||||
isTaxed: checked,
|
||||
}
|
||||
|
||||
// Reset tax amount if it should not be taxed.
|
||||
if ( false === checked ) {
|
||||
args.tax = 0;
|
||||
}
|
||||
|
||||
this.model.set( args );
|
||||
},
|
||||
|
||||
/**
|
||||
* Closes dialog and opens "Order Details - Address" section.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @param {Object} e Click event.
|
||||
*/
|
||||
onSetAddress( e ) {
|
||||
e.preventDefault();
|
||||
|
||||
this.closeDialog();
|
||||
|
||||
const button = $( '[href="#edd_general_address"]' );
|
||||
|
||||
if ( ! button ) {
|
||||
return;
|
||||
}
|
||||
|
||||
button.trigger( 'click' );
|
||||
|
||||
$( '#edd_order_address_country' ).trigger( 'focus' );
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates the `OrderAdjustment` when the Description changes.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @param {Object} e Change event
|
||||
*/
|
||||
onChangeDescription( e ) {
|
||||
this.model.set( 'description', e.target.value );
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds an `OrderAdjustment` to `OrderAdjustments`.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @param {Object} e Submit event.
|
||||
*/
|
||||
onAdd( e ) {
|
||||
e.preventDefault();
|
||||
|
||||
const { model, options } = this;
|
||||
const { state } = options;
|
||||
|
||||
const adjustments = state.get( 'adjustments' );
|
||||
const items = state.get( 'items' );
|
||||
|
||||
// Add at `OrderItem` level if necessary.
|
||||
if ( 'order_item' === model.get( 'objectType' ) ) {
|
||||
const orderItem = items.findWhere( {
|
||||
id: model.get( 'objectId' ),
|
||||
} );
|
||||
|
||||
orderItem.get( 'adjustments' ).add( model );
|
||||
// Adding to the Collection doesn't bubble up a change event.
|
||||
orderItem.trigger( 'change' );
|
||||
model.set( 'objectType', 'order_item' );
|
||||
} else {
|
||||
|
||||
// Add to `Order` level.
|
||||
model.set( 'objectType', 'order' );
|
||||
}
|
||||
|
||||
adjustments.add( model );
|
||||
|
||||
// Stop listening to the model in this view.
|
||||
this.stopListening( model );
|
||||
},
|
||||
} );
|
@ -0,0 +1,155 @@
|
||||
/* global _ */
|
||||
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import uuid from 'uuid-random';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { Dialog } from './dialog.js';
|
||||
import { Base } from './base.js';
|
||||
import { OrderAdjustmentDiscount } from './../models/order-adjustment-discount.js';
|
||||
|
||||
/**
|
||||
* "Add Discount" view
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @class FormAddOrderDiscount
|
||||
* @augments wp.Backbone.View
|
||||
*/
|
||||
export const FormAddOrderDiscount = Dialog.extend( {
|
||||
/**
|
||||
* @since 3.0
|
||||
*/
|
||||
el: '#edd-admin-order-add-discount-dialog',
|
||||
|
||||
/**
|
||||
* @since 3.0
|
||||
*/
|
||||
template: wp.template( 'edd-admin-order-form-add-order-discount' ),
|
||||
|
||||
/**
|
||||
* @since 3.0
|
||||
*/
|
||||
events: {
|
||||
'submit form': 'onAdd',
|
||||
|
||||
'change #discount': 'onChangeDiscount',
|
||||
},
|
||||
|
||||
/**
|
||||
* "Add Discount" view.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @constructs FormAddOrderAdjustment
|
||||
* @augments wp.Backbone.View
|
||||
*/
|
||||
initialize() {
|
||||
Dialog.prototype.initialize.apply( this, arguments );
|
||||
|
||||
const { state } = this.options;
|
||||
|
||||
// Create a fresh `OrderAdjustmentDiscount` to be added.
|
||||
this.model = new OrderAdjustmentDiscount( {
|
||||
id: uuid(),
|
||||
typeId: uuid(),
|
||||
|
||||
state,
|
||||
} );
|
||||
|
||||
// Listen for events.
|
||||
this.listenTo( this.model, 'change', this.render );
|
||||
this.listenTo( state.get( 'adjustments' ), 'add', this.closeDialog );
|
||||
},
|
||||
|
||||
/**
|
||||
* Prepares data to be used in `render` method.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @see wp.Backbone.View
|
||||
* @see https://github.com/WordPress/WordPress/blob/master/wp-includes/js/wp-backbone.js
|
||||
*
|
||||
* @return {Object} The data for this view.
|
||||
*/
|
||||
prepare() {
|
||||
const { model, options } = this;
|
||||
const { state } = options;
|
||||
|
||||
const _isDuplicate = state.get( 'adjustments' ).has( model );
|
||||
|
||||
return {
|
||||
...Base.prototype.prepare.apply( this, arguments ),
|
||||
|
||||
_isDuplicate,
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates the `OrderDiscounts` when the Discount changes.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @param {Object} e Change event.
|
||||
*/
|
||||
onChangeDiscount( e ) {
|
||||
const { target: { selectedIndex, options } } = e;
|
||||
const { model } = this;
|
||||
|
||||
e.preventDefault();
|
||||
|
||||
const discount = options[ selectedIndex ];
|
||||
const adjustment = discount.dataset;
|
||||
|
||||
if ( '' === discount.value ) {
|
||||
return model.set( OrderAdjustmentDiscount.prototype.defaults );
|
||||
}
|
||||
|
||||
model.set( {
|
||||
typeId: parseInt( discount.value ),
|
||||
description: adjustment.code,
|
||||
} );
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds an `OrderAdjustmentDiscount` to `OrderAdjustments`.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @param {Object} e Submit event.
|
||||
*/
|
||||
onAdd( e ) {
|
||||
e.preventDefault();
|
||||
|
||||
const { model, options } = this;
|
||||
const { state } = options;
|
||||
|
||||
state.set( 'isFetching', true );
|
||||
|
||||
const items = state.get( 'items' );
|
||||
const adjustments = state.get( 'adjustments' );
|
||||
|
||||
// Add to collection but do not alert.
|
||||
adjustments.add( model, {
|
||||
silent: true,
|
||||
} );
|
||||
|
||||
// Update all amounts with new item and alert when done.
|
||||
items
|
||||
.updateAmounts()
|
||||
.done( () => {
|
||||
// Stop listening to the model in this view.
|
||||
this.stopListening( model );
|
||||
|
||||
// Alert of succesful addition.
|
||||
adjustments.trigger( 'add', model );
|
||||
|
||||
// Clear fetching.
|
||||
state.set( 'isFetching', false ) ;
|
||||
} );
|
||||
},
|
||||
} );
|
@ -0,0 +1,364 @@
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import uuid from 'uuid-random';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { Base } from './base.js';
|
||||
import { Dialog } from './dialog.js';
|
||||
import { OrderItem } from './../models/order-item.js';
|
||||
|
||||
/**
|
||||
* "Add Item" view
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @class FormAddOrderItem
|
||||
* @augments Dialog
|
||||
*/
|
||||
export const FormAddOrderItem = Dialog.extend( {
|
||||
/**
|
||||
* @since 3.0
|
||||
*/
|
||||
el: '#edd-admin-order-add-item-dialog',
|
||||
|
||||
/**
|
||||
* @since 3.0
|
||||
*/
|
||||
template: wp.template( 'edd-admin-order-form-add-order-item' ),
|
||||
|
||||
/**
|
||||
* "Add Item" view.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @constructs FormAddOrderItem
|
||||
* @augments Base
|
||||
*/
|
||||
initialize() {
|
||||
Dialog.prototype.initialize.apply( this, arguments );
|
||||
|
||||
// Delegate additional events.
|
||||
this.addEvents( {
|
||||
'change #download': 'onChangeDownload',
|
||||
'change #quantity': 'onChangeQuantity',
|
||||
'change #auto-calculate': 'onAutoCalculateToggle',
|
||||
|
||||
'keyup #amount': 'onChangeAmount',
|
||||
'keyup #tax': 'onChangeTax',
|
||||
'keyup #subtotal': 'onChangeSubtotal',
|
||||
|
||||
'click #set-address': 'onSetAddress',
|
||||
|
||||
'submit form': 'onAdd',
|
||||
} );
|
||||
|
||||
const { state } = this.options;
|
||||
const id = uuid();
|
||||
|
||||
// Create a fresh `OrderItem` to be added.
|
||||
this.model = new OrderItem( {
|
||||
id,
|
||||
orderId: id,
|
||||
|
||||
state,
|
||||
|
||||
error: false,
|
||||
} );
|
||||
|
||||
// Listen for events.
|
||||
this.listenTo( this.model, 'change', this.render );
|
||||
this.listenTo( state, 'change:isFetching', this.render );
|
||||
this.listenTo( state.get( 'items' ), 'add', this.closeDialog );
|
||||
},
|
||||
|
||||
/**
|
||||
* Prepares data to be used in `render` method.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @see wp.Backbone.View
|
||||
* @see https://github.com/WordPress/WordPress/blob/master/wp-includes/js/wp-backbone.js
|
||||
*
|
||||
* @return {Object} The data for this view.
|
||||
*/
|
||||
prepare() {
|
||||
const { model, options } = this;
|
||||
const { state } = options;
|
||||
const { number } = state.get( 'formatters' );
|
||||
|
||||
const quantity = model.get( 'quantity' );
|
||||
|
||||
let amount = number.format( model.get( 'amount' ) * quantity );
|
||||
let tax = number.format( model.get( 'tax' ) * quantity );
|
||||
let subtotal = number.format( model.get( 'subtotal' ) * quantity );
|
||||
|
||||
if ( true === model.get( '_isAdjustingManually' ) ) {
|
||||
amount = model.get( 'amountManual' );
|
||||
tax = model.get( 'taxManual' );
|
||||
subtotal = model.get( 'subtotalManual' );
|
||||
}
|
||||
|
||||
const isDuplicate = false === state.get( 'isFetching' ) && true === state.get( 'items' ).has( model );
|
||||
const isAdjustingManually = model.get( '_isAdjustingManually' );
|
||||
const error = model.get( 'error' );
|
||||
|
||||
const defaults = Base.prototype.prepare.apply( this, arguments );
|
||||
|
||||
return {
|
||||
...defaults,
|
||||
|
||||
amountManual: amount,
|
||||
taxManual: tax,
|
||||
subtotalManual: subtotal,
|
||||
|
||||
state: {
|
||||
...defaults.state,
|
||||
isAdjustingManually,
|
||||
isDuplicate,
|
||||
error,
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates the OrderItem when the Download changes.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @param {Object} e Change event for Download selector.
|
||||
*/
|
||||
onChangeDownload( e ) {
|
||||
const {
|
||||
target: { options: selectOptions, selectedIndex },
|
||||
} = e;
|
||||
|
||||
const { model, options } = this;
|
||||
const { state } = options;
|
||||
const { number } = state.get( 'formatters' );
|
||||
|
||||
// Find the selected Download.
|
||||
const selected = selectOptions[ selectedIndex ];
|
||||
|
||||
// Set ID and Price ID.
|
||||
let productId = selected.value;
|
||||
let priceId = null;
|
||||
|
||||
const parts = productId.split( '_' );
|
||||
|
||||
productId = parseInt( parts[ 0 ] );
|
||||
|
||||
if ( parts[ 1 ] ) {
|
||||
priceId = parseInt( parts[ 1 ] );
|
||||
}
|
||||
|
||||
state.set( 'isFetching', true );
|
||||
|
||||
// Update basic attributes.
|
||||
model.set( {
|
||||
productId,
|
||||
priceId,
|
||||
productName: selected.text,
|
||||
error: false,
|
||||
} );
|
||||
|
||||
// Update amount attributes.
|
||||
model
|
||||
.getAmounts( {
|
||||
country: state.getTaxCountry(),
|
||||
region: state.getTaxRegion(),
|
||||
products: state.get( 'items' ).map( ( item ) => ( {
|
||||
id: item.get( 'productId' ),
|
||||
quantity: item.get( 'quantity' ),
|
||||
options: {
|
||||
price_id: item.get( 'priceId' ),
|
||||
}
|
||||
} ) ),
|
||||
discountIds: state.get( 'adjustments' ).pluck( 'typeId' ),
|
||||
} )
|
||||
.fail( ( { message: error } ) => {
|
||||
// Clear fetching.
|
||||
state.set( 'isFetching', false );
|
||||
|
||||
// Set error and reset model.
|
||||
model.set( {
|
||||
error,
|
||||
productId: 0,
|
||||
priceId: null,
|
||||
productName: '',
|
||||
} );
|
||||
} )
|
||||
.then( ( response ) => {
|
||||
const { amount, tax, subtotal, total } = response;
|
||||
|
||||
model.set( {
|
||||
amount,
|
||||
tax,
|
||||
subtotal,
|
||||
total,
|
||||
|
||||
amountManual: number.format( amount ),
|
||||
taxManual: number.format( tax ),
|
||||
subtotalManual: number.format( subtotal ),
|
||||
} );
|
||||
|
||||
// Clear fetching.
|
||||
state.set( 'isFetching', false );
|
||||
} );
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates the `OrderItem`'s when the Quantity changes.
|
||||
*
|
||||
* @since 3.0
|
||||
* @todo Validate.
|
||||
*
|
||||
* @param {Object} e Change event.
|
||||
*/
|
||||
onChangeQuantity( e ) {
|
||||
this.model.set( 'quantity', parseInt( e.target.value ) );
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates the `OrderItem`'s when the manually managed Amount changes.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @param {Object} e Change event.
|
||||
*/
|
||||
onChangeAmount( e ) {
|
||||
this.model.set( 'amountManual', e.target.value );
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates the `OrderItem`'s when the manually managed Tax changes.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @param {Object} e Change event.
|
||||
*/
|
||||
onChangeTax( e ) {
|
||||
this.model.set( 'taxManual', e.target.value );
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates the `OrderItem`'s when the manually managed Subtotal changes.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @param {Object} e Change event.
|
||||
*/
|
||||
onChangeSubtotal( e ) {
|
||||
this.model.set( 'subtotalManual', e.target.value );
|
||||
},
|
||||
|
||||
/**
|
||||
* Toggles manual amount adjustments.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @param {Object} e Change event.
|
||||
*/
|
||||
onAutoCalculateToggle( e ) {
|
||||
e.preventDefault();
|
||||
|
||||
this.model.set( {
|
||||
_isAdjustingManually: ! e.target.checked,
|
||||
} );
|
||||
},
|
||||
|
||||
/**
|
||||
* Closes dialog and opens "Order Details - Address" section.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @param {Object} e Click event.
|
||||
*/
|
||||
onSetAddress( e ) {
|
||||
e.preventDefault();
|
||||
|
||||
this.closeDialog();
|
||||
|
||||
const button = $( '[href="#edd_general_address"]' );
|
||||
|
||||
if ( ! button ) {
|
||||
return;
|
||||
}
|
||||
|
||||
button.trigger( 'click' );
|
||||
|
||||
$( '#edd_order_address_country' ).trigger( 'focus' );
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds an `OrderItem` to `OrderItems`.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @param {Object} e Submit event.
|
||||
*/
|
||||
onAdd( e ) {
|
||||
e.preventDefault();
|
||||
|
||||
const { model, options } = this;
|
||||
const { state } = options;
|
||||
const { number } = state.get( 'formatters' );
|
||||
|
||||
state.set( 'isFetching', true );
|
||||
|
||||
// Use manual amounts if adjusting manually.
|
||||
if ( true === model.get( '_isAdjustingManually' ) ) {
|
||||
model.set( {
|
||||
amount: number.unformat( model.get( 'amountManual' ) ),
|
||||
tax: number.unformat( model.get( 'taxManual' ) ),
|
||||
subtotal: number.unformat( model.get( 'subtotalManual' ) ),
|
||||
} );
|
||||
|
||||
// Duplicate base amounts by the quantity set.
|
||||
} else {
|
||||
const quantity = model.get( 'quantity' );
|
||||
|
||||
model.set( {
|
||||
tax: model.get( 'tax' ) * quantity,
|
||||
subtotal: model.get( 'subtotal' ) * quantity,
|
||||
} );
|
||||
}
|
||||
|
||||
const items = state.get( 'items' );
|
||||
|
||||
// Add to collection but do not alert.
|
||||
items.add( model, {
|
||||
silent: true,
|
||||
} );
|
||||
|
||||
// Update all amounts with new item and alert when done.
|
||||
items
|
||||
.updateAmounts()
|
||||
.fail( ( { message: error } ) => {
|
||||
// Remove added model on failure.
|
||||
// It is is added previously to calculate Discounts
|
||||
// as if adding would be successful.
|
||||
items.remove( model, {
|
||||
silent: true,
|
||||
} );
|
||||
|
||||
// Clear fetching.
|
||||
state.set( 'isFetching', false );
|
||||
|
||||
// Set error.
|
||||
model.set( 'error', error );
|
||||
} )
|
||||
.done( () => {
|
||||
// Stop listening to the model in this view.
|
||||
this.stopListening( model );
|
||||
|
||||
// Alert of succesful addition.
|
||||
items.trigger( 'add', model );
|
||||
|
||||
// Clear fetching.
|
||||
state.set( 'isFetching', false );
|
||||
} );
|
||||
},
|
||||
} );
|
@ -0,0 +1,50 @@
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { Base } from './base.js';
|
||||
|
||||
/**
|
||||
* NoOrderItems
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @class NoOrderItems
|
||||
* @augments wp.Backbone.View
|
||||
*/
|
||||
export const NoOrderItems = wp.Backbone.View.extend( {
|
||||
/**
|
||||
* @since 3.0
|
||||
*/
|
||||
tagName: 'tr',
|
||||
|
||||
/**
|
||||
* @since 3.0
|
||||
*/
|
||||
template: wp.template( 'edd-admin-order-no-items' ),
|
||||
|
||||
/**
|
||||
* Prepares data to be used in `render` method.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @see wp.Backbone.View
|
||||
* @see https://github.com/WordPress/WordPress/blob/master/wp-includes/js/wp-backbone.js
|
||||
*
|
||||
* @return {Object} The data for this view.
|
||||
*/
|
||||
prepare() {
|
||||
const { model, options } = this;
|
||||
const { state } = this.options;
|
||||
|
||||
// Determine column offset -- using cart quantities requires an extra column.
|
||||
const colspan = true === state.get( 'hasQuantity' ) ? 4 : 3;
|
||||
|
||||
return {
|
||||
...Base.prototype.prepare.apply( this, arguments ),
|
||||
|
||||
config: {
|
||||
colspan,
|
||||
},
|
||||
};
|
||||
},
|
||||
} );
|
@ -0,0 +1,118 @@
|
||||
/* global _ */
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { Base } from './base.js';
|
||||
|
||||
/**
|
||||
* OrderAdjustment
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @class OrderAdjustment
|
||||
* @augments wp.Backbone.View
|
||||
*/
|
||||
export const OrderAdjustment = Base.extend( {
|
||||
/**
|
||||
* @since 3.0
|
||||
*/
|
||||
tagName: 'tr',
|
||||
|
||||
/**
|
||||
* @since 3.0
|
||||
*/
|
||||
className: 'is-expanded',
|
||||
|
||||
/**
|
||||
* @since 3.0
|
||||
*/
|
||||
events: {
|
||||
'click .delete': 'onDelete',
|
||||
},
|
||||
|
||||
initialize() {
|
||||
Base.prototype.initialize.apply( this );
|
||||
|
||||
// Set template depending on type.
|
||||
switch ( this.model.get( 'type' ) ) {
|
||||
case 'credit':
|
||||
case 'fee':
|
||||
this.template = wp.template( 'edd-admin-order-adjustment' );
|
||||
break;
|
||||
default:
|
||||
this.template = wp.template(
|
||||
'edd-admin-order-adjustment-discount'
|
||||
);
|
||||
}
|
||||
|
||||
// Listen for events.
|
||||
this.listenTo( this.model, 'change', this.render );
|
||||
},
|
||||
|
||||
/**
|
||||
* Prepares data to be used in `render` method.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @see wp.Backbone.View
|
||||
* @see https://github.com/WordPress/WordPress/blob/master/wp-includes/js/wp-backbone.js
|
||||
*
|
||||
* @return {Object} The data for this view.
|
||||
*/
|
||||
prepare() {
|
||||
const { model, options } = this;
|
||||
const { state } = this.options;
|
||||
|
||||
const { currency, number } = state.get( 'formatters' );
|
||||
|
||||
// Determine column offset -- using cart quantities requires an extra column.
|
||||
const colspan = true === state.get( 'hasQuantity' ) ? 2 : 1;
|
||||
|
||||
let orderItem;
|
||||
|
||||
if ( 'order_item' === model.get( 'objectType' ) ) {
|
||||
orderItem = _.first( state.get( 'items' ).filter( ( item ) => {
|
||||
return undefined !== item.get( 'adjustments' ).findWhere( {
|
||||
objectId: item.get( 'id' ),
|
||||
} );
|
||||
} ) );
|
||||
}
|
||||
|
||||
const subtotal = model.getAmount();
|
||||
const total = model.getTotal();
|
||||
|
||||
return {
|
||||
...Base.prototype.prepare.apply( this, arguments ),
|
||||
|
||||
config: {
|
||||
colspan,
|
||||
},
|
||||
|
||||
total,
|
||||
subtotal,
|
||||
orderItem: orderItem ? orderItem.toJSON() : false,
|
||||
totalCurrency: currency.format( total ),
|
||||
subtotalCurrency: currency.format( subtotal )
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Removes the current Adjustment from Adjustments.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @param {Object} e Click event.
|
||||
*/
|
||||
onDelete( e ) {
|
||||
e.preventDefault();
|
||||
|
||||
const { state } = this.options;
|
||||
|
||||
// Remove `OrderAdjustment`.
|
||||
state.get( 'adjustments' ).remove( this.model );
|
||||
|
||||
// Update `OrderItem` amounts.
|
||||
state.get( 'items' ).updateAmounts();
|
||||
},
|
||||
} );
|
@ -0,0 +1,101 @@
|
||||
/* global _ */
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { OrderAdjustment } from './order-adjustment.js';
|
||||
|
||||
/**
|
||||
* OrderAdjustments
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @class OrderAdjustments
|
||||
* @augments wp.Backbone.View
|
||||
*/
|
||||
export const OrderAdjustments = wp.Backbone.View.extend( {
|
||||
/**
|
||||
* @since 3.0
|
||||
*/
|
||||
tagName: 'tbody',
|
||||
|
||||
/**
|
||||
* @since 3.0
|
||||
*/
|
||||
className: 'edd-order-overview-summary__adjustments',
|
||||
|
||||
/**
|
||||
* @since 3.0
|
||||
*/
|
||||
initialize() {
|
||||
const { state } = this.options;
|
||||
|
||||
const items = state.get( 'items' );
|
||||
const adjustments = state.get( 'adjustments' );
|
||||
|
||||
// Listen for events.
|
||||
this.listenTo( state, 'change:hasTax', this.render );
|
||||
this.listenTo( items, 'change', this.render );
|
||||
this.listenTo( adjustments, 'add', this.render );
|
||||
this.listenTo( adjustments, 'remove', this.remove );
|
||||
},
|
||||
|
||||
/**
|
||||
* Renders initial view.
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
render() {
|
||||
const adjustments = this.getAdjustments();
|
||||
|
||||
this.views.remove();
|
||||
|
||||
_.each( adjustments, ( adjustment ) => this.add( adjustment ) );
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds an `OrderAdjustment` subview.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @param {OrderAdjustment} model OrderAdjustment to add to view.
|
||||
*/
|
||||
add( model ) {
|
||||
this.views.add(
|
||||
new OrderAdjustment( {
|
||||
...this.options,
|
||||
model,
|
||||
} )
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Removes an `OrderAdjustment` subview.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @param {OrderAdjustment} model OrderAdjustment to remove from view.
|
||||
*/
|
||||
remove( model ) {
|
||||
let subview = null;
|
||||
const views = this.views.get();
|
||||
|
||||
if ( ! views ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Find the Subview containing the model.
|
||||
views.forEach( ( view ) => {
|
||||
const { model: viewModel } = view;
|
||||
|
||||
if ( viewModel.id === model.id ) {
|
||||
subview = view;
|
||||
}
|
||||
} );
|
||||
|
||||
// Remove Subview if found.
|
||||
if ( null !== subview ) {
|
||||
subview.remove();
|
||||
}
|
||||
},
|
||||
} );
|
@ -0,0 +1,27 @@
|
||||
/* global _ */
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { OrderAdjustments } from './order-adjustments.js';
|
||||
|
||||
/**
|
||||
* OrderCredits
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @class OrderCredits
|
||||
* @augments wp.Backbone.View
|
||||
*/
|
||||
export const OrderCredits = OrderAdjustments.extend( {
|
||||
/**
|
||||
* Returns Credit adjustments.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*/
|
||||
getAdjustments() {
|
||||
const { state } = this.options;
|
||||
|
||||
return state.get( 'adjustments' ).getByType( 'credit' );
|
||||
},
|
||||
} );
|
@ -0,0 +1,31 @@
|
||||
/* global _ */
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { OrderAdjustments } from './order-adjustments.js';
|
||||
|
||||
/**
|
||||
* OrderDiscountsFees
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @class OrderDiscountsFees
|
||||
* @augments wp.Backbone.View
|
||||
*/
|
||||
export const OrderDiscountsFees = OrderAdjustments.extend( {
|
||||
/**
|
||||
* Returns Discount and Fee adjustments.
|
||||
*
|
||||
* @since 3.0.0
|
||||
*/
|
||||
getAdjustments() {
|
||||
const { state } = this.options;
|
||||
|
||||
return state.get( 'adjustments' ).filter(
|
||||
( adjustment ) => {
|
||||
return [ 'discount', 'fee' ].includes( adjustment.get( 'type' ) );
|
||||
}
|
||||
);
|
||||
},
|
||||
} );
|
@ -0,0 +1,127 @@
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { Base } from './base.js';
|
||||
import { CopyDownloadLink } from './copy-download-link.js';
|
||||
|
||||
/**
|
||||
* OrderItem
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @class OrderItem
|
||||
* @augments wp.Backbone.View
|
||||
*/
|
||||
export const OrderItem = Base.extend( {
|
||||
/**
|
||||
* @since 3.0
|
||||
*/
|
||||
tagName: 'tr',
|
||||
|
||||
/**
|
||||
* @since 3.0
|
||||
*/
|
||||
template: wp.template( 'edd-admin-order-item' ),
|
||||
|
||||
/**
|
||||
* @since 3.0
|
||||
*/
|
||||
events: {
|
||||
'click .delete': 'onDelete',
|
||||
'click .copy-download-link': 'onCopyDownloadLink',
|
||||
},
|
||||
|
||||
/**
|
||||
* "Order Item" view.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @constructs OrderItem
|
||||
* @augments Base
|
||||
*/
|
||||
initialize() {
|
||||
// Listen for events.
|
||||
this.listenTo( this.model, 'change', this.render );
|
||||
},
|
||||
|
||||
/**
|
||||
* Prepares data to be used in `render` method.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @see wp.Backbone.View
|
||||
* @see https://github.com/WordPress/WordPress/blob/master/wp-includes/js/wp-backbone.js
|
||||
*
|
||||
* @return {Object} The data for this view.
|
||||
*/
|
||||
prepare() {
|
||||
const { model, options } = this;
|
||||
const { state } = options;
|
||||
|
||||
const { currency } = state.get( 'formatters' );
|
||||
|
||||
const subtotal = model.getSubtotal();
|
||||
const discountAmount = model.getDiscountAmount();
|
||||
const isAdjustingManually = model.get( '_isAdjustingManually' );
|
||||
const tax = model.getTax();
|
||||
|
||||
return {
|
||||
...Base.prototype.prepare.apply( this, arguments ),
|
||||
|
||||
discount: discountAmount,
|
||||
amountCurrency: currency.format( model.get( 'amount' ) ),
|
||||
subtotal,
|
||||
subtotalCurrency: currency.format( subtotal ),
|
||||
tax,
|
||||
taxCurrency: currency.format( tax ),
|
||||
total: model.getTotal(),
|
||||
|
||||
config: {
|
||||
isAdjustingManually,
|
||||
},
|
||||
|
||||
adjustments: model.get( 'adjustments' ).toJSON(),
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Removes the current Item from Items.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @param {Object} e Click event.
|
||||
*/
|
||||
onDelete( e ) {
|
||||
e.preventDefault();
|
||||
|
||||
const { model, options } = this;
|
||||
const { state } = options;
|
||||
|
||||
// Remove OrderItem.
|
||||
state.get( 'items' ).remove( model );
|
||||
|
||||
// Update remaining OrderItem amounts.
|
||||
state.get( 'items' ).updateAmounts();
|
||||
},
|
||||
|
||||
/**
|
||||
* Opens a Dialog that fetches Download File URLs.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @param {Object} e Click event.
|
||||
*/
|
||||
onCopyDownloadLink( e ) {
|
||||
e.preventDefault();
|
||||
|
||||
const { options, model } = this;
|
||||
|
||||
new CopyDownloadLink( {
|
||||
orderId: model.get( 'orderId' ),
|
||||
productId: model.get( 'productId' ),
|
||||
priceId: model.get( 'priceId' ),
|
||||
} )
|
||||
.openDialog()
|
||||
.render();
|
||||
},
|
||||
} );
|
@ -0,0 +1,120 @@
|
||||
/* global _ */
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { OrderItem } from './order-item.js';
|
||||
import { NoOrderItems } from './no-order-items.js';
|
||||
|
||||
/**
|
||||
* OrderItems
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @class OrderItems
|
||||
* @augments wp.Backbone.View
|
||||
*/
|
||||
export const OrderItems = wp.Backbone.View.extend( {
|
||||
/**
|
||||
* @since 3.0
|
||||
*/
|
||||
tagName: 'tbody',
|
||||
|
||||
/**
|
||||
* @since 3.0
|
||||
*/
|
||||
className: 'edd-order-overview-summary__items',
|
||||
|
||||
/**
|
||||
* "Order Items" view.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @constructs OrderItem
|
||||
* @augments Base
|
||||
*/
|
||||
initialize() {
|
||||
const { state } = this.options;
|
||||
|
||||
const items = state.get( 'items' );
|
||||
const adjustments = state.get( 'adjustments' );
|
||||
|
||||
// Listen for events.
|
||||
this.listenTo( items, 'add', this.render );
|
||||
this.listenTo( items, 'remove', this.remove );
|
||||
},
|
||||
|
||||
/**
|
||||
* Renders initial view.
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
render() {
|
||||
const { state } = this.options;
|
||||
const items = state.get( 'items' );
|
||||
|
||||
this.views.remove();
|
||||
|
||||
// Nothing available.
|
||||
if ( 0 === items.length ) {
|
||||
this.views.set(
|
||||
new NoOrderItems( {
|
||||
...this.options,
|
||||
} )
|
||||
);
|
||||
// Render each item.
|
||||
} else {
|
||||
_.each( items.models, ( model ) => this.add( model ) );
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds an `OrderItem` subview.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @param {OrderItem} model OrderItem
|
||||
*/
|
||||
add( model ) {
|
||||
this.views.add(
|
||||
new OrderItem( {
|
||||
...this.options,
|
||||
model,
|
||||
} )
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Removes an `OrderItem` subview.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @param {OrderItem} model OrderItem
|
||||
*/
|
||||
remove( model ) {
|
||||
let subview = null;
|
||||
|
||||
// Find the subview containing the model.
|
||||
this.views.get().forEach( ( view ) => {
|
||||
const { model: viewModel } = view;
|
||||
|
||||
if ( viewModel.get( 'id' ) === model.id ) {
|
||||
subview = view;
|
||||
}
|
||||
} );
|
||||
|
||||
// Remove subview if found.
|
||||
if ( null !== subview ) {
|
||||
subview.remove();
|
||||
}
|
||||
|
||||
// Last item was removed, show "No items".
|
||||
if ( 0 === this.views.get().length ) {
|
||||
this.views.set(
|
||||
new NoOrderItems( {
|
||||
...this.options,
|
||||
} )
|
||||
);
|
||||
}
|
||||
},
|
||||
} );
|
@ -0,0 +1,57 @@
|
||||
/* global wp */
|
||||
|
||||
/**
|
||||
* OrderRefund
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @class OrderRefund
|
||||
* @augments wp.Backbone.View
|
||||
*/
|
||||
export const OrderRefund = wp.Backbone.View.extend( {
|
||||
/**
|
||||
* @since 3.0
|
||||
*/
|
||||
template: wp.template( 'edd-admin-order-refund' ),
|
||||
|
||||
/**
|
||||
* @since 3.0
|
||||
*/
|
||||
tagName: 'tr',
|
||||
|
||||
/**
|
||||
* @since 3.0
|
||||
*/
|
||||
className: 'is-expanded',
|
||||
|
||||
/**
|
||||
* Prepares data to be used in `render` method.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @see wp.Backbone.View
|
||||
* @see https://github.com/WordPress/WordPress/blob/master/wp-includes/js/wp-backbone.js
|
||||
*
|
||||
* @return {Object} The data for this view.
|
||||
*/
|
||||
prepare() {
|
||||
const { model, options } = this;
|
||||
const { state } = options;
|
||||
|
||||
const { currency } = state.get( 'formatters' );
|
||||
|
||||
// Determine column offset -- using cart quantities requires an extra column.
|
||||
const colspan = true === state.get( 'hasQuantity' ) ? 2 : 1;
|
||||
|
||||
return {
|
||||
config: {
|
||||
colspan,
|
||||
},
|
||||
|
||||
id: model.get( 'id' ),
|
||||
number: model.get( 'number' ),
|
||||
dateCreated: model.get( 'dateCreatedi18n' ),
|
||||
totalCurrency: currency.format( model.get( 'total' ) ),
|
||||
};
|
||||
},
|
||||
} );
|
@ -0,0 +1,48 @@
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { OrderRefund } from './order-refund.js';
|
||||
|
||||
/**
|
||||
* Order refunds
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @class OrderRefunds
|
||||
* @augments wp.Backbone.View
|
||||
*/
|
||||
export const OrderRefunds = wp.Backbone.View.extend( {
|
||||
/**
|
||||
* @since 3.0
|
||||
*/
|
||||
tagName: 'tbody',
|
||||
|
||||
/**
|
||||
* @since 3.0
|
||||
*/
|
||||
className: 'edd-order-overview-summary__refunds',
|
||||
|
||||
/**
|
||||
* @since 3.0
|
||||
*/
|
||||
template: wp.template( 'edd-admin-order-refunds' ),
|
||||
|
||||
/**
|
||||
* Renders initial view.
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
render() {
|
||||
const { state } = this.options;
|
||||
const { models: refunds } = state.get( 'refunds' );
|
||||
|
||||
_.each( refunds, ( model ) => (
|
||||
this.views.add(
|
||||
new OrderRefund( {
|
||||
...this.options,
|
||||
model,
|
||||
} )
|
||||
)
|
||||
) );
|
||||
},
|
||||
} );
|
@ -0,0 +1,68 @@
|
||||
/**
|
||||
* Order subtotal
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @class Subtotal
|
||||
* @augments wp.Backbone.View
|
||||
*/
|
||||
export const OrderSubtotal = wp.Backbone.View.extend( {
|
||||
/**
|
||||
* @since 3.0
|
||||
*/
|
||||
tagName: 'tbody',
|
||||
|
||||
/**
|
||||
* @since 3.0
|
||||
*/
|
||||
className: 'edd-order-overview-summary__subtotal',
|
||||
|
||||
/**
|
||||
* @since 3.0
|
||||
*/
|
||||
template: wp.template( 'edd-admin-order-subtotal' ),
|
||||
|
||||
/**
|
||||
* Order subtotal view.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @constructs OrderSubtotal
|
||||
* @augments wp.Backbone.View
|
||||
*/
|
||||
initialize() {
|
||||
const { state } = this.options;
|
||||
|
||||
// Listen for events.
|
||||
this.listenTo( state.get( 'items' ), 'add remove change', this.render );
|
||||
this.listenTo( state.get( 'adjustments' ), 'add remove', this.render );
|
||||
},
|
||||
|
||||
/**
|
||||
* Prepares data to be used in `render` method.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @see wp.Backbone.View
|
||||
* @see https://github.com/WordPress/WordPress/blob/master/wp-includes/js/wp-backbone.js
|
||||
*
|
||||
* @return {Object} The data for this view.
|
||||
*/
|
||||
prepare() {
|
||||
const { state } = this.options;
|
||||
const { currency, number } = state.get( 'formatters' );
|
||||
const colspan = true === state.get( 'hasQuantity' ) ? 2 : 1;
|
||||
|
||||
const subtotal = state.getSubtotal();
|
||||
|
||||
return {
|
||||
state: state.toJSON(),
|
||||
config: {
|
||||
colspan,
|
||||
},
|
||||
|
||||
subtotal,
|
||||
subtotalCurrency: currency.format( subtotal ),
|
||||
};
|
||||
},
|
||||
} );
|
@ -0,0 +1,130 @@
|
||||
/**
|
||||
* Order tax
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @class OrderTax
|
||||
* @augments wp.Backbone.View
|
||||
*/
|
||||
export const OrderTax = wp.Backbone.View.extend( {
|
||||
/**
|
||||
* @since 3.0
|
||||
*/
|
||||
tagName: 'tbody',
|
||||
|
||||
/**
|
||||
* @since 3.0
|
||||
*/
|
||||
className: 'edd-order-overview-summary__tax',
|
||||
|
||||
/**
|
||||
* @since 3.0
|
||||
*/
|
||||
template: wp.template( 'edd-admin-order-tax' ),
|
||||
|
||||
/**
|
||||
* @since 3.0
|
||||
*/
|
||||
events: {
|
||||
'click #notice-tax-change .notice-dismiss': 'onDismissTaxRateChange',
|
||||
'click #notice-tax-change .update-amounts': 'onUpdateAmounts',
|
||||
},
|
||||
|
||||
/**
|
||||
* Order total view.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @constructs OrderTotal
|
||||
* @augments wp.Backbone.View
|
||||
*/
|
||||
initialize() {
|
||||
const { state } = this.options;
|
||||
|
||||
// Listen for events.
|
||||
this.listenTo( state, 'change:hasTax', this.render );
|
||||
this.listenTo( state.get( 'items' ), 'add remove change', this.render );
|
||||
this.listenTo( state.get( 'adjustments' ), 'add remove', this.render );
|
||||
},
|
||||
|
||||
/**
|
||||
* Prepares data to be used in `render` method.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @see wp.Backbone.View
|
||||
* @see https://github.com/WordPress/WordPress/blob/master/wp-includes/js/wp-backbone.js
|
||||
*
|
||||
* @return {Object} The data for this view.
|
||||
*/
|
||||
prepare() {
|
||||
const { state } = this.options;
|
||||
const { currency, number } = state.get( 'formatters' );
|
||||
|
||||
// Determine column offset -- using cart quantities requires an extra column.
|
||||
const colspan = true === state.get( 'hasQuantity' ) ? 2 : 1;
|
||||
|
||||
const tax = state.getTax();
|
||||
const hasNewTaxRate = state.hasNewTaxRate();
|
||||
|
||||
const taxableItems = [
|
||||
...state.get( 'items' ).models,
|
||||
...state.get( 'adjustments' ).getByType( 'fee' ),
|
||||
];
|
||||
|
||||
return {
|
||||
state: {
|
||||
...state.toJSON(),
|
||||
hasNewTaxRate,
|
||||
},
|
||||
config: {
|
||||
colspan,
|
||||
},
|
||||
|
||||
tax,
|
||||
taxCurrency: currency.format( tax ),
|
||||
|
||||
hasTaxableItems: taxableItems.length > 0,
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Dismisses Tax Rate change notice.
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
onDismissTaxRateChange() {
|
||||
const { state } = this.options;
|
||||
// Reset amount
|
||||
state.set( 'hasTax', state.get( 'hasTax' ) );
|
||||
|
||||
// Manually trigger change because new and previous attributes
|
||||
// are the same so Backbone will not.
|
||||
state.trigger( 'change:hasTax' );
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates amounts for existing Order Items.
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
onUpdateAmounts( e ) {
|
||||
e.preventDefault();
|
||||
|
||||
const { state } = this.options;
|
||||
|
||||
// Manually recalculate taxed fees.
|
||||
state.get( 'adjustments' ).getByType( 'fee' ).forEach(
|
||||
( fee ) => {
|
||||
fee.updateTax();
|
||||
}
|
||||
);
|
||||
|
||||
// Request updated tax amounts for orders from the server.
|
||||
state.get( 'items' )
|
||||
.updateAmounts()
|
||||
.done( () => {
|
||||
this.onDismissTaxRateChange();
|
||||
} );
|
||||
},
|
||||
} );
|
@ -0,0 +1,81 @@
|
||||
/**
|
||||
* Order total
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @class OrderTotal
|
||||
* @augments wp.Backbone.View
|
||||
*/
|
||||
export const OrderTotal = wp.Backbone.View.extend( {
|
||||
/**
|
||||
* @since 3.0
|
||||
*/
|
||||
tagName: 'tbody',
|
||||
|
||||
/**
|
||||
* @since 3.0
|
||||
*/
|
||||
className: 'edd-order-overview-summary__total',
|
||||
|
||||
/**
|
||||
* @since 3.0
|
||||
*/
|
||||
template: wp.template( 'edd-admin-order-total' ),
|
||||
|
||||
/**
|
||||
* Order tax view.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @constructs OrderTax
|
||||
* @augments wp.Backbone.View
|
||||
*/
|
||||
initialize() {
|
||||
const { state } = this.options;
|
||||
|
||||
// Listen for events.
|
||||
this.listenTo( state, 'change:hasTax', this.render );
|
||||
this.listenTo( state.get( 'items' ), 'add remove change', this.render );
|
||||
this.listenTo( state.get( 'adjustments' ), 'add remove', this.render );
|
||||
},
|
||||
|
||||
/**
|
||||
* Prepares data to be used in `render` method.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @see wp.Backbone.View
|
||||
* @see https://github.com/WordPress/WordPress/blob/master/wp-includes/js/wp-backbone.js
|
||||
*
|
||||
* @return {Object} The data for this view.
|
||||
*/
|
||||
prepare() {
|
||||
const { state } = this.options;
|
||||
const { currency, number } = state.get( 'formatters' );
|
||||
|
||||
// Determine column offset -- using cart quantities requires an extra column.
|
||||
const colspan = true === state.get( 'hasQuantity' ) ? 2 : 1;
|
||||
|
||||
const total = state.getTotal();
|
||||
const discount = state.getDiscount();
|
||||
const hasManualAdjustment = undefined !== state.get( 'items' ).findWhere( {
|
||||
_isAdjustingManually: true,
|
||||
} );
|
||||
|
||||
return {
|
||||
state: {
|
||||
...state.toJSON(),
|
||||
hasManualAdjustment,
|
||||
},
|
||||
config: {
|
||||
colspan,
|
||||
},
|
||||
|
||||
total,
|
||||
discount,
|
||||
|
||||
discountCurrency: currency.format( discount ),
|
||||
totalCurrency: currency.format( total ),
|
||||
};
|
||||
},
|
||||
} );
|
@ -0,0 +1,66 @@
|
||||
/** global wp */
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { Summary } from './summary.js';
|
||||
import { Actions } from './actions.js';
|
||||
|
||||
/**
|
||||
* Overview
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @class Overview
|
||||
* @augments wp.Backbone.View
|
||||
*/
|
||||
export const Overview = wp.Backbone.View.extend( {
|
||||
/**
|
||||
* @since 3.0
|
||||
*/
|
||||
el: '#edd-order-overview',
|
||||
|
||||
/**
|
||||
* @since 3.0
|
||||
*/
|
||||
events: {
|
||||
'click .toggle-row': 'onToggleRow',
|
||||
},
|
||||
|
||||
/**
|
||||
* Renders the view.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @return {Overview} Current view.
|
||||
*/
|
||||
render() {
|
||||
// Add "Summary".
|
||||
//
|
||||
// Contains `OrderItems`, `OrderAdjustments`, and `Totals` subviews.
|
||||
this.views.add( new Summary( this.options ) );
|
||||
|
||||
// "Actions".
|
||||
if ( document.getElementById( 'edd-order-overview-actions' ) ) {
|
||||
this.views.add( new Actions( this.options ) );
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Toggles a row's other columns.
|
||||
*
|
||||
* Core does not support the dynamically added items.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @see https://github.com/WordPress/WordPress/blob/001ffe81fbec4438a9f594f330e18103d21fbcd7/wp-admin/js/common.js#L908
|
||||
*
|
||||
* @param {Object} e Click event.
|
||||
*/
|
||||
onToggleRow( e ) {
|
||||
e.preventDefault();
|
||||
$( e.target ).closest( 'tr' ).toggleClass( 'is-expanded' );
|
||||
},
|
||||
} );
|
@ -0,0 +1,46 @@
|
||||
/** global wp */
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { OrderItems } from './order-items.js';
|
||||
import { OrderSubtotal } from './order-subtotal.js';
|
||||
import { OrderDiscountsFees } from './order-discounts-fees.js';
|
||||
import { OrderTax } from './order-tax.js';
|
||||
import { OrderCredits } from './order-credits.js';
|
||||
import { OrderTotal } from './order-total.js';
|
||||
import { OrderRefunds } from './order-refunds.js';
|
||||
|
||||
/**
|
||||
* Overview summary
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @class Summary
|
||||
* @augments wp.Backbone.view
|
||||
*/
|
||||
export const Summary = wp.Backbone.View.extend( {
|
||||
/**
|
||||
* @since 3.0
|
||||
*/
|
||||
el: '#edd-order-overview-summary',
|
||||
|
||||
/**
|
||||
* Renders the view.
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
* @return {Summary} Current view.
|
||||
*/
|
||||
render() {
|
||||
this.views.add( new OrderItems( this.options ) );
|
||||
this.views.add( new OrderSubtotal( this.options ) );
|
||||
this.views.add( new OrderDiscountsFees( this.options ) );
|
||||
this.views.add( new OrderTax( this.options ) );
|
||||
this.views.add( new OrderCredits( this.options ) );
|
||||
this.views.add( new OrderTotal( this.options ) );
|
||||
this.views.add( new OrderRefunds( this.options ) );
|
||||
|
||||
return this;
|
||||
},
|
||||
} );
|
Reference in New Issue
Block a user