Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for arbitrary line item codes #1062

Merged
merged 8 commits into from
Apr 5, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions src/components/BookingBreakdown/BookingBreakdown.css
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,21 @@
justify-content: space-between;
}

.subTotalLineItem {
@apply --marketplaceH4FontStyles;
font-weight: var(--fontWeightBold);

margin: 4px 0 1px 0;

@media (--viewportMedium) {
margin: 6px 0 9px 0;
}

display: flex;
flex-direction: row;
justify-content: space-between;
}

.itemLabel {
flex: 1;
margin-top: 1px; /* align with baseline */
Expand Down
132 changes: 132 additions & 0 deletions src/components/BookingBreakdown/BookingBreakdown.example.js
Original file line number Diff line number Diff line change
Expand Up @@ -497,3 +497,135 @@ export const UnitsType = {
}),
},
};

export const CustomPricing = {
component: BookingBreakdown,
props: {
userRole: 'customer',
unitType: LINE_ITEM_NIGHT,
transaction: exampleTransaction({
payinTotal: new Money(12800, CURRENCY),
payoutTotal: new Money(12600, CURRENCY),
lineItems: [
{
code: 'line-item/night',
includeFor: ['customer', 'provider'],
quantity: new Decimal(2),
unitPrice: new Money(4500, CURRENCY),
lineTotal: new Money(9000, CURRENCY),
reversal: false,
},
{
code: 'line-item/car-cleaning',
includeFor: ['customer', 'provider'],
quantity: new Decimal(1),
unitPrice: new Money(5000, CURRENCY),
lineTotal: new Money(5000, CURRENCY),
reversal: false,
},
{
code: 'line-item/season-discount',
includeFor: ['customer', 'provider'],
percentage: new Decimal(-10),
unitPrice: new Money(14000, CURRENCY),
lineTotal: new Money(-1400, CURRENCY),
reversal: false,
},
{
code: 'line-item/customer-commission',
includeFor: ['customer'],
percentage: new Decimal(10),
unitPrice: new Money(2000, CURRENCY),
lineTotal: new Money(200, CURRENCY),
reversal: false,
},
],
}),
booking: exampleBooking({
start: new Date(Date.UTC(2017, 3, 14)),
end: new Date(Date.UTC(2017, 3, 16)),
}),
},
};

export const CustomPricingWithRefund = {
component: BookingBreakdown,
props: {
userRole: 'customer',
unitType: LINE_ITEM_NIGHT,
transaction: exampleTransaction({
payinTotal: new Money(0, CURRENCY),
payoutTotal: new Money(0, CURRENCY),
lineItems: [
{
code: 'line-item/night',
includeFor: ['customer', 'provider'],
quantity: new Decimal(2),
unitPrice: new Money(4500, CURRENCY),
lineTotal: new Money(9000, CURRENCY),
reversal: false,
},
{
code: 'line-item/night',
includeFor: ['customer', 'provider'],
quantity: new Decimal(-2),
unitPrice: new Money(4500, CURRENCY),
lineTotal: new Money(-9000, CURRENCY),
reversal: true,
},
{
code: 'line-item/car-cleaning',
includeFor: ['customer', 'provider'],
quantity: new Decimal(1),
unitPrice: new Money(5000, CURRENCY),
lineTotal: new Money(5000, CURRENCY),
reversal: false,
},
{
code: 'line-item/car-cleaning',
includeFor: ['customer', 'provider'],
quantity: new Decimal(-1),
unitPrice: new Money(5000, CURRENCY),
lineTotal: new Money(-5000, CURRENCY),
reversal: true,
},
{
code: 'line-item/season-discount',
includeFor: ['customer', 'provider'],
percentage: new Decimal(-10),
unitPrice: new Money(14000, CURRENCY),
lineTotal: new Money(-1400, CURRENCY),
reversal: false,
},
{
code: 'line-item/season-discount',
includeFor: ['customer', 'provider'],
percentage: new Decimal(10),
unitPrice: new Money(14000, CURRENCY),
lineTotal: new Money(1400, CURRENCY),
reversal: true,
},
{
code: 'line-item/customer-commission',
includeFor: ['customer'],
percentage: new Decimal(10),
unitPrice: new Money(2000, CURRENCY),
lineTotal: new Money(200, CURRENCY),
reversal: false,
},
{
code: 'line-item/customer-commission',
includeFor: ['customer'],
percentage: new Decimal(-10),
unitPrice: new Money(2000, CURRENCY),
lineTotal: new Money(-200, CURRENCY),
reversal: true,
},
],
}),
booking: exampleBooking({
start: new Date(Date.UTC(2017, 3, 14)),
end: new Date(Date.UTC(2017, 3, 16)),
}),
},
};
10 changes: 7 additions & 3 deletions src/components/BookingBreakdown/BookingBreakdown.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
LINE_ITEM_PROVIDER_COMMISSION,
} from '../../util/types';

import LineItemUnitPrice from './LineItemUnitPrice';
import LineItemUnitPriceMaybe from './LineItemUnitPriceMaybe';
import LineItemBookingPeriod from './LineItemBookingPeriod';
import LineItemUnitsMaybe from './LineItemUnitsMaybe';
import LineItemSubTotalMaybe from './LineItemSubTotalMaybe';
Expand All @@ -22,6 +22,8 @@ import LineItemProviderCommissionMaybe from './LineItemProviderCommissionMaybe';
import LineItemProviderCommissionRefundMaybe from './LineItemProviderCommissionRefundMaybe';
import LineItemRefundMaybe from './LineItemRefundMaybe';
import LineItemTotalPrice from './LineItemTotalPrice';
import LineItemUnknownItemsMaybe from './LineItemUnknownItemsMaybe';

import css from './BookingBreakdown.css';

export const BookingBreakdownComponent = props => {
Expand All @@ -40,17 +42,19 @@ export const BookingBreakdownComponent = props => {

return (
<div className={classes}>
<LineItemUnitPrice transaction={transaction} unitType={unitType} intl={intl} />
<LineItemUnitPriceMaybe transaction={transaction} unitType={unitType} intl={intl} />
<LineItemBookingPeriod transaction={transaction} booking={booking} unitType={unitType} />
<LineItemUnitsMaybe transaction={transaction} unitType={unitType} />

<LineItemUnknownItemsMaybe transaction={transaction} intl={intl} />

<LineItemSubTotalMaybe
transaction={transaction}
unitType={unitType}
userRole={userRole}
intl={intl}
/>
<LineItemRefundMaybe transaction={transaction} unitType={unitType} intl={intl} />
<LineItemRefundMaybe transaction={transaction} intl={intl} />

<LineItemCustomerCommissionMaybe
transaction={transaction}
Expand Down
8 changes: 4 additions & 4 deletions src/components/BookingBreakdown/BookingBreakdown.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -159,17 +159,17 @@ describe('BookingBreakdown', () => {
{
code: 'line-item/provider-commission',
includeFor: ['provider'],
quantity: new Decimal(1),
percentage: new Decimal(-10),
lineTotal: new Money(-200, 'USD'),
unitPrice: new Money(-200, 'USD'),
unitPrice: new Money(2000, 'USD'),
reversal: false,
},
{
code: 'line-item/provider-commission',
includeFor: ['provider'],
quantity: new Decimal(-1),
percentage: new Decimal(10),
lineTotal: new Money(200, 'USD'),
unitPrice: new Money(-200, 'USD'),
unitPrice: new Money(2000, 'USD'),
reversal: true,
},
],
Expand Down
6 changes: 1 addition & 5 deletions src/components/BookingBreakdown/LineItemBookingPeriod.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,12 +59,8 @@ const LineItemBookingPeriod = props => {
item => item.code === unitType && !item.reversal
);

if (!unitPurchase) {
throw new Error(`LineItemBookingPeriod: lineItem (${unitType}) missing`);
}

const useQuantityForDayCount = isNightly || isDaily;
const count = useQuantityForDayCount ? unitPurchase.quantity.toFixed() : dayCount;
const count = useQuantityForDayCount && unitPurchase ? unitPurchase.quantity.toFixed() : dayCount;

const unitCountMessage = (
<FormattedHTMLMessage
Expand Down
53 changes: 46 additions & 7 deletions src/components/BookingBreakdown/LineItemRefundMaybe.js
Original file line number Diff line number Diff line change
@@ -1,23 +1,62 @@
import React from 'react';
import { FormattedMessage, intlShape } from 'react-intl';
import Decimal from 'decimal.js';
import { formatMoney } from '../../util/currency';
import { propTypes } from '../../util/types';
import { types as sdkTypes } from '../../util/sdkLoader';
import config from '../../config';
import {
propTypes,
LINE_ITEM_CUSTOMER_COMMISSION,
LINE_ITEM_PROVIDER_COMMISSION,
} from '../../util/types';

import css from './BookingBreakdown.css';

const LineItemRefundMaybe = props => {
const { transaction, unitType, intl } = props;
const { Money } = sdkTypes;

/**
* Calculates the total price in sub units for multiple line items.
*/
const lineItemsTotal = lineItems => {
const amount = lineItems.reduce((total, item) => {
return total.plus(item.lineTotal.amount);
}, new Decimal(0));
return new Money(amount, config.currency);
};

const refund = transaction.attributes.lineItems.find(
item => item.code === unitType && item.reversal
/**
* Checks if line item represents commission
*/
const isCommission = lineItem => {
return (
lineItem.code === LINE_ITEM_PROVIDER_COMMISSION ||
lineItem.code === LINE_ITEM_CUSTOMER_COMMISSION
);
};

/**
* Returns non-commission, reversal line items
*/
const nonCommissionReversalLineItems = transaction => {
return transaction.attributes.lineItems.filter(item => !isCommission(item) && item.reversal);
};

const LineItemRefundMaybe = props => {
const { transaction, intl } = props;

// all non-commission, reversal line items
const refundLineItems = nonCommissionReversalLineItems(transaction);

const refund = lineItemsTotal(refundLineItems);

const formattedRefund = refundLineItems.length > 0 ? formatMoney(intl, refund) : null;

return refund ? (
return formattedRefund ? (
<div className={css.lineItem}>
<span className={css.itemLabel}>
<FormattedMessage id="BookingBreakdown.refund" />
</span>
<span className={css.itemValue}>{formatMoney(intl, refund.lineTotal)}</span>
<span className={css.itemValue}>{formattedRefund}</span>
</div>
) : null;
};
Expand Down
48 changes: 39 additions & 9 deletions src/components/BookingBreakdown/LineItemSubTotalMaybe.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import React from 'react';
import { string } from 'prop-types';
import { FormattedMessage, intlShape } from 'react-intl';
import Decimal from 'decimal.js';
import { formatMoney } from '../../util/currency';
import config from '../../config';
import { types as sdkTypes } from '../../util/sdkLoader';
import {
propTypes,
LINE_ITEM_CUSTOMER_COMMISSION,
Expand All @@ -10,6 +13,35 @@ import {

import css from './BookingBreakdown.css';

const { Money } = sdkTypes;

/**
* Calculates the total price in sub units for multiple line items.
*/
const lineItemsTotal = lineItems => {
const amount = lineItems.reduce((total, item) => {
return total.plus(item.lineTotal.amount);
}, new Decimal(0));
return new Money(amount, config.currency);
};

/**
* Checks if line item represents commission
*/
const isCommission = lineItem => {
return (
lineItem.code === LINE_ITEM_PROVIDER_COMMISSION ||
lineItem.code === LINE_ITEM_CUSTOMER_COMMISSION
);
};

/**
* Returns non-commission, non-reversal line items
*/
const nonCommissionNonReversalLineItems = transaction => {
return transaction.attributes.lineItems.filter(item => !isCommission(item) && !item.reversal);
};

/**
* Checks if a transaction has a commission line-item for
* a given user role.
Expand Down Expand Up @@ -40,18 +72,16 @@ const LineItemSubTotalMaybe = props => {
// PLEASE NOTE that this assumes that the transaction doesn't have other
// line item types than the defined unit type (e.g. week, month, year).
const showSubTotal = txHasCommission(transaction, userRole) || refund;
const unitPurchase = transaction.attributes.lineItems.find(
item => item.code === unitType && !item.reversal
);

if (!unitPurchase) {
throw new Error(`LineItemSubTotalMaybe: lineItem (${unitType}) missing`);
}
// all non-reversal, non-commission line items
const subTotalLineItems = nonCommissionNonReversalLineItems(transaction);
// line totals of those line items combined
const subTotal = lineItemsTotal(subTotalLineItems);

const formattedSubTotal = formatMoney(intl, unitPurchase.lineTotal);
const formattedSubTotal = subTotalLineItems.length > 0 ? formatMoney(intl, subTotal) : null;

return showSubTotal ? (
<div className={css.lineItem}>
return formattedSubTotal && showSubTotal ? (
<div className={css.subTotalLineItem}>
<span className={css.itemLabel}>
<FormattedMessage id="BookingBreakdown.subTotal" />
</span>
Expand Down
Loading