Merge pull request #590 from Safelite/feature/digital/SSR-1134

SSR-1134 Add Fees to Cart
This commit is contained in:
Josh Dassinger 2024-03-19 08:02:45 -05:00 committed by GitHub
commit 101c8767bc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 204 additions and 52 deletions

View file

@ -9,6 +9,8 @@ Object {
"amountDue": "AmountDueTextWidget",
"basePrice": "BasePriceWidget",
"deductible": "DeductibleWidget",
"mobileFee": "MobileServiceWidget",
"recycleFee": "RecycleFeeWidget",
"salesTax": "SalesTaxWidget",
"subtotal": "SubtotalWidget",
},

View file

@ -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();
});
});
});
});

View file

@ -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>

View file

@ -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);

View file

@ -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();
},