Merge pull request #590 from Safelite/feature/digital/SSR-1134
SSR-1134 Add Fees to Cart
This commit is contained in:
commit
101c8767bc
5 changed files with 204 additions and 52 deletions
|
|
@ -9,6 +9,8 @@ Object {
|
||||||
"amountDue": "AmountDueTextWidget",
|
"amountDue": "AmountDueTextWidget",
|
||||||
"basePrice": "BasePriceWidget",
|
"basePrice": "BasePriceWidget",
|
||||||
"deductible": "DeductibleWidget",
|
"deductible": "DeductibleWidget",
|
||||||
|
"mobileFee": "MobileServiceWidget",
|
||||||
|
"recycleFee": "RecycleFeeWidget",
|
||||||
"salesTax": "SalesTaxWidget",
|
"salesTax": "SalesTaxWidget",
|
||||||
"subtotal": "SubtotalWidget",
|
"subtotal": "SubtotalWidget",
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import { getMountOptions } from '@/helpers/unit-test-helper.js';
|
||||||
import { useMainStore } from '@/store';
|
import { useMainStore } from '@/store';
|
||||||
import coverageStatuses from '@/constants/coverage-statuses';
|
import coverageStatuses from '@/constants/coverage-statuses';
|
||||||
import { formatAmountInDollars } from '@/helpers/text-helper.js';
|
import { formatAmountInDollars } from '@/helpers/text-helper.js';
|
||||||
|
import partTypeStrings from '@/constants/part-type-strings';
|
||||||
|
|
||||||
const VERIFYING_COVERAGE = 'Verifying coverage';
|
const VERIFYING_COVERAGE = 'Verifying coverage';
|
||||||
|
|
||||||
|
|
@ -41,7 +42,13 @@ function getMountedComponent(mainInitialState = {}, initialData = {}, propsData
|
||||||
describe('cart-dropdown component', () => {
|
describe('cart-dropdown component', () => {
|
||||||
test('initial data rendered as expected', () => {
|
test('initial data rendered as expected', () => {
|
||||||
// Arrange
|
// Arrange
|
||||||
const { wrapper } = getMountedComponent();
|
const { wrapper } = getMountedComponent({
|
||||||
|
order: {
|
||||||
|
lineItems: {
|
||||||
|
supportingItems: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
expect(wrapper.vm.$data).toMatchSnapshot();
|
expect(wrapper.vm.$data).toMatchSnapshot();
|
||||||
|
|
@ -568,5 +575,85 @@ describe('cart-dropdown component', () => {
|
||||||
expect(result).toBe(dollarAmount);
|
expect(result).toBe(dollarAmount);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
describe('Recyle fee', () => {
|
||||||
|
test('Shows recycle fee when preset in supported items', () => {
|
||||||
|
// Arrange
|
||||||
|
const reference = '#recycle-fee-item';
|
||||||
|
const {wrapper} = getMountedComponent({
|
||||||
|
order: {
|
||||||
|
lineItems: {
|
||||||
|
supportingItems: [{
|
||||||
|
partType: partTypeStrings.REPLACE_FEE
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const cartDeductible = wrapper.find(reference);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(wrapper.vm.recycleFeeCartItem).not.toBeNull();
|
||||||
|
expect(cartDeductible.exists()).toBeTruthy();
|
||||||
|
});
|
||||||
|
test('Hides recycle fee when not in supported items', () => {
|
||||||
|
// Arrange
|
||||||
|
const reference = '#recycle-fee-item';
|
||||||
|
const {wrapper} = getMountedComponent({
|
||||||
|
order: {
|
||||||
|
lineItems: {
|
||||||
|
supportingItems: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const cartDeductible = wrapper.find(reference);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(wrapper.vm.recycleFeeCartItem).toBeNull();
|
||||||
|
expect(cartDeductible.exists()).toBeFalsy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('Mobile Fee', () => {
|
||||||
|
test('Shows mobile fee when preset', () => {
|
||||||
|
// Arrange
|
||||||
|
const reference = '#mobile-fee-item';
|
||||||
|
const { wrapper } = getMountedComponent({
|
||||||
|
order: {
|
||||||
|
lineItems: {
|
||||||
|
supportingItems: [],
|
||||||
|
mobileFee: {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const cartDeductible = wrapper.find(reference);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(wrapper.vm.mobileFeeCartItem).not.toBeNull();
|
||||||
|
expect(cartDeductible.exists()).toBeTruthy();
|
||||||
|
});
|
||||||
|
test('Hides recycle fee when not in supported items', () => {
|
||||||
|
// Arrange
|
||||||
|
const reference = '#mobile-fee-item';
|
||||||
|
const { wrapper } = getMountedComponent({
|
||||||
|
order: {
|
||||||
|
lineItems: {
|
||||||
|
supportingItems: [],
|
||||||
|
mobileFee: null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const cartDeductible = wrapper.find(reference);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(wrapper.vm.mobileFeeCartItem).toBeNull();
|
||||||
|
expect(cartDeductible.exists()).toBeFalsy();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@
|
||||||
class="color-darker-gray">
|
class="color-darker-gray">
|
||||||
<div
|
<div
|
||||||
id="cart-deductible-or-base-price"
|
id="cart-deductible-or-base-price"
|
||||||
class="cart-deductible-or-base-price">
|
class="cart-item cart-gray">
|
||||||
<div
|
<div
|
||||||
v-if="showDeductibleLineItem"
|
v-if="showDeductibleLineItem"
|
||||||
id="cart-deductible"
|
id="cart-deductible"
|
||||||
|
|
@ -43,45 +43,69 @@
|
||||||
<!-- Nonpackaged Cart Items -->
|
<!-- Nonpackaged Cart Items -->
|
||||||
<!-- This will be a loop when this is implemented -->
|
<!-- This will be a loop when this is implemented -->
|
||||||
<div class="non-packaged-cart-item">
|
<div class="non-packaged-cart-item">
|
||||||
<textLink
|
|
||||||
linkType="text"
|
|
||||||
text="Recycling"
|
|
||||||
href="javascript:void(0)"
|
|
||||||
@clickEvent="openModal(recyclingModalCmsWidgetName)" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
id="cart-footer"
|
|
||||||
class="cart-footer">
|
|
||||||
<div
|
|
||||||
id="cart-subtotal-and-tax"
|
|
||||||
class="subtotal-and-tax">
|
|
||||||
<div
|
<div
|
||||||
id="cart-subtotal"
|
id="mobile-fee"
|
||||||
class="col d-flex justify-content-between">
|
class="cart-item">
|
||||||
<span id="subtotal-label">{{ subtotalLabel }}</span>
|
<div
|
||||||
<span id="subtotal-value">{{ getDisplayed(subTotal) }}</span>
|
v-if="mobileFeeCartItem"
|
||||||
|
id="mobile-fee-item"
|
||||||
|
class="d-flex justify-content-between align-items-center">
|
||||||
|
<span id="mobile-fee-label">{{ mobileFeeCartItem.name }}</span>
|
||||||
|
<span id="mobile-fee-value">{{ getDisplayed(mobileFeeCartItem.subTotal) }}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
id="cart-sales-tax"
|
id="recycle-fee"
|
||||||
class="pt-1 col d-flex justify-content-between">
|
class="cart-item cart-gray">
|
||||||
<span id="sales-tax-label">{{ salesTaxLabel }}</span>
|
<div
|
||||||
<span id="sales-tax-value">{{ getDisplayed(salesTax) }}</span>
|
v-if="recycleFeeCartItem"
|
||||||
|
id="recycle-fee-item"
|
||||||
|
class="d-flex justify-content-between align-items-center">
|
||||||
|
<span id="recycle-fee-label">
|
||||||
|
<textLink
|
||||||
|
linkType="text"
|
||||||
|
:text="recycleFeeCartItem.name"
|
||||||
|
href="javascript:void(0)"
|
||||||
|
@clickEvent="openModal(recyclingModalCmsWidgetName)" />
|
||||||
|
</span>
|
||||||
|
<span id="recycle-fee-value">{{ getDisplayed(recycleFeeCartItem.subTotal) }}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
id="bottom-amount-due"
|
id="cart-footer"
|
||||||
class="bottom-amount-due col d-flex justify-content-between">
|
class="cart-footer">
|
||||||
<span id="bottom-amount-due-label">{{ amountDueLabel }}</span>
|
<div
|
||||||
<span id="bottom-amount-due-value">{{ getDisplayed(amountDue) }}</span>
|
id="cart-subtotal-and-tax"
|
||||||
|
class="subtotal-and-tax">
|
||||||
|
<div
|
||||||
|
id="cart-subtotal"
|
||||||
|
class="col d-flex justify-content-between">
|
||||||
|
<span id="subtotal-label">{{ subtotalLabel }}</span>
|
||||||
|
<span id="subtotal-value">{{ getDisplayed(subTotal) }}</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
id="cart-sales-tax"
|
||||||
|
class="pt-1 col d-flex justify-content-between">
|
||||||
|
<span id="sales-tax-label">{{ salesTaxLabel }}</span>
|
||||||
|
<span id="sales-tax-value">{{ getDisplayed(salesTax) }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
id="bottom-amount-due"
|
||||||
|
class="bottom-amount-due col d-flex justify-content-between">
|
||||||
|
<span id="bottom-amount-due-label">{{ amountDueLabel }}</span>
|
||||||
|
<span id="bottom-amount-due-value">{{ getDisplayed(amountDue) }}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<contentGroupModal
|
||||||
|
ref="RecycleModal"
|
||||||
|
cmsWidgetName="RecycleModal">
|
||||||
|
<textBlock cmsWidgetName="RecycleTextBlock" />
|
||||||
|
</contentGroupModal>
|
||||||
</div>
|
</div>
|
||||||
<contentGroupModal
|
|
||||||
ref="RecycleModal"
|
|
||||||
cmsWidgetName="RecycleModal">
|
|
||||||
<textBlock cmsWidgetName="RecycleTextBlock" />
|
|
||||||
</contentGroupModal>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
@ -92,6 +116,8 @@ import textBlock from '@/digital-components/text-block/text-block.vue';
|
||||||
import textLink from '@/ux-components/text-link/text-link.vue';
|
import textLink from '@/ux-components/text-link/text-link.vue';
|
||||||
import getPriceOfLineItems from '@/helpers/price-calculator.js';
|
import getPriceOfLineItems from '@/helpers/price-calculator.js';
|
||||||
import { formatAmountInDollars } from '@/helpers/text-helper.js';
|
import { formatAmountInDollars } from '@/helpers/text-helper.js';
|
||||||
|
import baseMixin from '@/mixins/base-mixin';
|
||||||
|
import partTypeStrings from '@/constants/part-type-strings';
|
||||||
import widgetFields from '@/constants/cms-widget-fields.js';
|
import widgetFields from '@/constants/cms-widget-fields.js';
|
||||||
|
|
||||||
const VERIFYING_COVERAGE = 'Verifying coverage';
|
const VERIFYING_COVERAGE = 'Verifying coverage';
|
||||||
|
|
@ -105,6 +131,10 @@ export default {
|
||||||
recyclingModalCmsWidgetName: String,
|
recyclingModalCmsWidgetName: String,
|
||||||
showAsPaid: Boolean
|
showAsPaid: Boolean
|
||||||
},
|
},
|
||||||
|
setup() {
|
||||||
|
const mainStore = useMainStore();
|
||||||
|
return { mainStore };
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
const { currentDeductible } = useMainStore().order;
|
const { currentDeductible } = useMainStore().order;
|
||||||
const { supportingItems, glassParts, otherParts } = useMainStore().lineItems;
|
const { supportingItems, glassParts, otherParts } = useMainStore().lineItems;
|
||||||
|
|
@ -122,7 +152,9 @@ export default {
|
||||||
deductible: 'DeductibleWidget',
|
deductible: 'DeductibleWidget',
|
||||||
basePrice: 'BasePriceWidget',
|
basePrice: 'BasePriceWidget',
|
||||||
subtotal: 'SubtotalWidget',
|
subtotal: 'SubtotalWidget',
|
||||||
salesTax: 'SalesTaxWidget'
|
salesTax: 'SalesTaxWidget',
|
||||||
|
recycleFee: 'RecycleFeeWidget',
|
||||||
|
mobileFee: 'MobileServiceWidget'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
@ -163,6 +195,18 @@ export default {
|
||||||
},
|
},
|
||||||
salesTaxLabel() {
|
salesTaxLabel() {
|
||||||
return this.getCmsContent(this.widget.salesTax, widgetFields.TEXT_BLOCK_WIDGET.TEXT);
|
return this.getCmsContent(this.widget.salesTax, widgetFields.TEXT_BLOCK_WIDGET.TEXT);
|
||||||
|
},
|
||||||
|
recycleFeeCartItem() {
|
||||||
|
return this.getCartItem(
|
||||||
|
this.getCmsContent(this.widget.recycleFee, widgetFields.TEXT_BLOCK_WIDGET.TEXT),
|
||||||
|
this.mainStore.order.lineItems.supportingItems.find((lineItem) => lineItem.partType === partTypeStrings.REPLACE_FEE)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
mobileFeeCartItem() {
|
||||||
|
return this.getCartItem(
|
||||||
|
this.getCmsContent(this.widget.mobileFee, widgetFields.TEXT_BLOCK_WIDGET.TEXT),
|
||||||
|
this.mainStore.order.lineItems.mobileFee
|
||||||
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|
@ -176,6 +220,19 @@ export default {
|
||||||
return this.isUnverified && this.showDeductibleLineItem
|
return this.isUnverified && this.showDeductibleLineItem
|
||||||
? VERIFYING_COVERAGE
|
? VERIFYING_COVERAGE
|
||||||
: formatAmountInDollars(amount);
|
: formatAmountInDollars(amount);
|
||||||
|
},
|
||||||
|
getCartItem(label, lineItem) {
|
||||||
|
let cartItem = null;
|
||||||
|
|
||||||
|
if (lineItem) {
|
||||||
|
cartItem = {
|
||||||
|
name: label,
|
||||||
|
subTotal: baseMixin.methods.getTotalLineItemPrice(lineItem, false),
|
||||||
|
salesTax: lineItem.salesTax ?? 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return cartItem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -201,13 +258,16 @@ export default {
|
||||||
transition: all 350ms ease-in;
|
transition: all 350ms ease-in;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
|
margin-top: 1rem;
|
||||||
|
|
||||||
.cart-deductible-or-base-price {
|
.cart-item {
|
||||||
background-color: $gray-100;
|
|
||||||
margin-top: 1rem;
|
|
||||||
padding: 0.25rem 1.5rem;
|
padding: 0.25rem 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.cart-gray {
|
||||||
|
background-color: $gray-100;
|
||||||
|
}
|
||||||
|
|
||||||
.cart-footer {
|
.cart-footer {
|
||||||
border-top: 1px solid $green;
|
border-top: 1px solid $green;
|
||||||
}
|
}
|
||||||
|
|
@ -269,6 +329,9 @@ export default {
|
||||||
a {
|
a {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(#recycle-fee-label a){
|
||||||
|
line-height: initial;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -340,8 +340,11 @@ export default {
|
||||||
await this.$refs.shopQuestion.reloadShopData(zipCode);
|
await this.$refs.shopQuestion.reloadShopData(zipCode);
|
||||||
},
|
},
|
||||||
async forwardButtonAction() {
|
async forwardButtonAction() {
|
||||||
const providerToUse = this.isMobileLocationDisplayed
|
let provider = this.selectedProvider;
|
||||||
? {
|
this.mainStore.updateMobileFee(null);
|
||||||
|
if (this.isMobileLocationDisplayed) {
|
||||||
|
this.mainStore.updateMobileFee(this.mobileFeePart);
|
||||||
|
provider = {
|
||||||
providerNumber: this.mobileProviderNumber,
|
providerNumber: this.mobileProviderNumber,
|
||||||
address: {
|
address: {
|
||||||
streetAddress: null,
|
streetAddress: null,
|
||||||
|
|
@ -350,10 +353,10 @@ export default {
|
||||||
zipCode: null,
|
zipCode: null,
|
||||||
zipCodeCtu: null
|
zipCodeCtu: null
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
: this.selectedProvider;
|
}
|
||||||
|
|
||||||
useMainStore().saveServiceLocation({
|
this.mainStore.saveServiceLocation({
|
||||||
address: this.streetAddress,
|
address: this.streetAddress,
|
||||||
address2: this.streetAddress2,
|
address2: this.streetAddress2,
|
||||||
city: this.city,
|
city: this.city,
|
||||||
|
|
@ -362,16 +365,7 @@ export default {
|
||||||
zipCodeCtu: this.zipCodeCtu,
|
zipCodeCtu: this.zipCodeCtu,
|
||||||
appointmentType: this.selectedAppointmentType,
|
appointmentType: this.selectedAppointmentType,
|
||||||
isVehicleProtected: this.isVehicleProtected,
|
isVehicleProtected: this.isVehicleProtected,
|
||||||
provider: {
|
provider
|
||||||
providerNumber: providerToUse?.providerNumber,
|
|
||||||
address: {
|
|
||||||
streetAddress: providerToUse?.address?.streetAddress,
|
|
||||||
city: providerToUse?.address?.city,
|
|
||||||
state: providerToUse?.address?.state,
|
|
||||||
zipCode: providerToUse?.address?.zipCode,
|
|
||||||
zipCodeCtu: providerToUse?.address?.zipCodeCtu
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
this.$router.navigate(this.navigationScenarios.CLICKED_FORWARD, this.$route);
|
this.$router.navigate(this.navigationScenarios.CLICKED_FORWARD, this.$route);
|
||||||
|
|
|
||||||
|
|
@ -147,7 +147,8 @@ const getDefaultState = () => ({
|
||||||
glassParts: null,
|
glassParts: null,
|
||||||
otherParts: null,
|
otherParts: null,
|
||||||
supportingItems: null,
|
supportingItems: null,
|
||||||
vaps: null
|
vaps: null,
|
||||||
|
mobileFee: null
|
||||||
},
|
},
|
||||||
payment: {
|
payment: {
|
||||||
insuranceCoverage: {
|
insuranceCoverage: {
|
||||||
|
|
@ -1537,6 +1538,10 @@ export const useMainStore = defineStore({
|
||||||
this.order.lineItems.vaps = partsData;
|
this.order.lineItems.vaps = partsData;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
updateMobileFee(mobileFee) {
|
||||||
|
this.order.lineItems.mobileFee = mobileFee;
|
||||||
|
},
|
||||||
|
|
||||||
updateVehicle(vehicle) {
|
updateVehicle(vehicle) {
|
||||||
// Assuming that the method caller pass all the properties.
|
// Assuming that the method caller pass all the properties.
|
||||||
// otherwise need to check for undefined for every property.
|
// otherwise need to check for undefined for every property.
|
||||||
|
|
@ -2249,6 +2254,7 @@ export const useMainStore = defineStore({
|
||||||
resetPartsAndDependencies() {
|
resetPartsAndDependencies() {
|
||||||
this.resetGlassPartsState();
|
this.resetGlassPartsState();
|
||||||
this.updateVaps(null);
|
this.updateVaps(null);
|
||||||
|
this.updateMobileFee(null);
|
||||||
|
|
||||||
this.resetServiceLocationAndDependencies();
|
this.resetServiceLocationAndDependencies();
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue