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",
|
||||
"basePrice": "BasePriceWidget",
|
||||
"deductible": "DeductibleWidget",
|
||||
"mobileFee": "MobileServiceWidget",
|
||||
"recycleFee": "RecycleFeeWidget",
|
||||
"salesTax": "SalesTaxWidget",
|
||||
"subtotal": "SubtotalWidget",
|
||||
},
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import { getMountOptions } from '@/helpers/unit-test-helper.js';
|
|||
import { useMainStore } from '@/store';
|
||||
import coverageStatuses from '@/constants/coverage-statuses';
|
||||
import { formatAmountInDollars } from '@/helpers/text-helper.js';
|
||||
import partTypeStrings from '@/constants/part-type-strings';
|
||||
|
||||
const VERIFYING_COVERAGE = 'Verifying coverage';
|
||||
|
||||
|
|
@ -41,7 +42,13 @@ function getMountedComponent(mainInitialState = {}, initialData = {}, propsData
|
|||
describe('cart-dropdown component', () => {
|
||||
test('initial data rendered as expected', () => {
|
||||
// Arrange
|
||||
const { wrapper } = getMountedComponent();
|
||||
const { wrapper } = getMountedComponent({
|
||||
order: {
|
||||
lineItems: {
|
||||
supportingItems: []
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Assert
|
||||
expect(wrapper.vm.$data).toMatchSnapshot();
|
||||
|
|
@ -568,5 +575,85 @@ describe('cart-dropdown component', () => {
|
|||
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">
|
||||
<div
|
||||
id="cart-deductible-or-base-price"
|
||||
class="cart-deductible-or-base-price">
|
||||
class="cart-item cart-gray">
|
||||
<div
|
||||
v-if="showDeductibleLineItem"
|
||||
id="cart-deductible"
|
||||
|
|
@ -43,45 +43,69 @@
|
|||
<!-- Nonpackaged Cart Items -->
|
||||
<!-- This will be a loop when this is implemented -->
|
||||
<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
|
||||
id="cart-subtotal"
|
||||
class="col d-flex justify-content-between">
|
||||
<span id="subtotal-label">{{ subtotalLabel }}</span>
|
||||
<span id="subtotal-value">{{ getDisplayed(subTotal) }}</span>
|
||||
id="mobile-fee"
|
||||
class="cart-item">
|
||||
<div
|
||||
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
|
||||
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>
|
||||
id="recycle-fee"
|
||||
class="cart-item cart-gray">
|
||||
<div
|
||||
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
|
||||
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>
|
||||
id="cart-footer"
|
||||
class="cart-footer">
|
||||
<div
|
||||
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>
|
||||
<contentGroupModal
|
||||
ref="RecycleModal"
|
||||
cmsWidgetName="RecycleModal">
|
||||
<textBlock cmsWidgetName="RecycleTextBlock" />
|
||||
</contentGroupModal>
|
||||
</div>
|
||||
<contentGroupModal
|
||||
ref="RecycleModal"
|
||||
cmsWidgetName="RecycleModal">
|
||||
<textBlock cmsWidgetName="RecycleTextBlock" />
|
||||
</contentGroupModal>
|
||||
</div>
|
||||
</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 getPriceOfLineItems from '@/helpers/price-calculator.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';
|
||||
|
||||
const VERIFYING_COVERAGE = 'Verifying coverage';
|
||||
|
|
@ -105,6 +131,10 @@ export default {
|
|||
recyclingModalCmsWidgetName: String,
|
||||
showAsPaid: Boolean
|
||||
},
|
||||
setup() {
|
||||
const mainStore = useMainStore();
|
||||
return { mainStore };
|
||||
},
|
||||
data() {
|
||||
const { currentDeductible } = useMainStore().order;
|
||||
const { supportingItems, glassParts, otherParts } = useMainStore().lineItems;
|
||||
|
|
@ -122,7 +152,9 @@ export default {
|
|||
deductible: 'DeductibleWidget',
|
||||
basePrice: 'BasePriceWidget',
|
||||
subtotal: 'SubtotalWidget',
|
||||
salesTax: 'SalesTaxWidget'
|
||||
salesTax: 'SalesTaxWidget',
|
||||
recycleFee: 'RecycleFeeWidget',
|
||||
mobileFee: 'MobileServiceWidget'
|
||||
}
|
||||
};
|
||||
},
|
||||
|
|
@ -163,6 +195,18 @@ export default {
|
|||
},
|
||||
salesTaxLabel() {
|
||||
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: {
|
||||
|
|
@ -176,6 +220,19 @@ export default {
|
|||
return this.isUnverified && this.showDeductibleLineItem
|
||||
? VERIFYING_COVERAGE
|
||||
: 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;
|
||||
overflow: hidden;
|
||||
visibility: hidden;
|
||||
margin-top: 1rem;
|
||||
|
||||
.cart-deductible-or-base-price {
|
||||
background-color: $gray-100;
|
||||
margin-top: 1rem;
|
||||
.cart-item {
|
||||
padding: 0.25rem 1.5rem;
|
||||
}
|
||||
|
||||
.cart-gray {
|
||||
background-color: $gray-100;
|
||||
}
|
||||
|
||||
.cart-footer {
|
||||
border-top: 1px solid $green;
|
||||
}
|
||||
|
|
@ -269,6 +329,9 @@ export default {
|
|||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(#recycle-fee-label a){
|
||||
line-height: initial;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -340,8 +340,11 @@ export default {
|
|||
await this.$refs.shopQuestion.reloadShopData(zipCode);
|
||||
},
|
||||
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,
|
||||
address: {
|
||||
streetAddress: null,
|
||||
|
|
@ -350,10 +353,10 @@ export default {
|
|||
zipCode: null,
|
||||
zipCodeCtu: null
|
||||
}
|
||||
}
|
||||
: this.selectedProvider;
|
||||
};
|
||||
}
|
||||
|
||||
useMainStore().saveServiceLocation({
|
||||
this.mainStore.saveServiceLocation({
|
||||
address: this.streetAddress,
|
||||
address2: this.streetAddress2,
|
||||
city: this.city,
|
||||
|
|
@ -362,16 +365,7 @@ export default {
|
|||
zipCodeCtu: this.zipCodeCtu,
|
||||
appointmentType: this.selectedAppointmentType,
|
||||
isVehicleProtected: this.isVehicleProtected,
|
||||
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
|
||||
}
|
||||
}
|
||||
provider
|
||||
});
|
||||
|
||||
this.$router.navigate(this.navigationScenarios.CLICKED_FORWARD, this.$route);
|
||||
|
|
|
|||
|
|
@ -147,7 +147,8 @@ const getDefaultState = () => ({
|
|||
glassParts: null,
|
||||
otherParts: null,
|
||||
supportingItems: null,
|
||||
vaps: null
|
||||
vaps: null,
|
||||
mobileFee: null
|
||||
},
|
||||
payment: {
|
||||
insuranceCoverage: {
|
||||
|
|
@ -1537,6 +1538,10 @@ export const useMainStore = defineStore({
|
|||
this.order.lineItems.vaps = partsData;
|
||||
},
|
||||
|
||||
updateMobileFee(mobileFee) {
|
||||
this.order.lineItems.mobileFee = mobileFee;
|
||||
},
|
||||
|
||||
updateVehicle(vehicle) {
|
||||
// Assuming that the method caller pass all the properties.
|
||||
// otherwise need to check for undefined for every property.
|
||||
|
|
@ -2249,6 +2254,7 @@ export const useMainStore = defineStore({
|
|||
resetPartsAndDependencies() {
|
||||
this.resetGlassPartsState();
|
||||
this.updateVaps(null);
|
||||
this.updateMobileFee(null);
|
||||
|
||||
this.resetServiceLocationAndDependencies();
|
||||
},
|
||||
|
|
|
|||
Loading…
Reference in a new issue