mirror of
https://github.com/kingomarnajjar/flex-template-web.git
synced 2026-07-29 05:00:43 +10:00
Merge pull request #1062 from sharetribe/custom-pricing-support
Add support for arbitrary line item codes
This commit is contained in:
commit
b929493f01
12 changed files with 350 additions and 41 deletions
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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)),
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
@ -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 => {
|
||||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
],
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
const refund = transaction.attributes.lineItems.find(
|
||||
item => item.code === unitType && item.reversal
|
||||
/**
|
||||
* 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
|
||||
);
|
||||
};
|
||||
|
||||
return refund ? (
|
||||
/**
|
||||
* 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 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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
@ -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.
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { LINE_ITEM_NIGHT, LINE_ITEM_DAY, propTypes } from '../../util/types';
|
|||
|
||||
import css from './BookingBreakdown.css';
|
||||
|
||||
const LineItemUnitPrice = props => {
|
||||
const LineItemUnitPriceMaybe = props => {
|
||||
const { transaction, unitType, intl } = props;
|
||||
const isNightly = unitType === LINE_ITEM_NIGHT;
|
||||
const isDaily = unitType === LINE_ITEM_DAY;
|
||||
|
|
@ -19,26 +19,22 @@ const LineItemUnitPrice = props => {
|
|||
item => item.code === unitType && !item.reversal
|
||||
);
|
||||
|
||||
if (!unitPurchase) {
|
||||
throw new Error(`LineItemUnitPrice: lineItem (${unitType}) missing`);
|
||||
}
|
||||
const formattedUnitPrice = unitPurchase ? formatMoney(intl, unitPurchase.unitPrice) : null;
|
||||
|
||||
const formattedUnitPrice = formatMoney(intl, unitPurchase.unitPrice);
|
||||
|
||||
return (
|
||||
return formattedUnitPrice ? (
|
||||
<div className={css.lineItem}>
|
||||
<span className={css.itemLabel}>
|
||||
<FormattedMessage id={translationKey} />
|
||||
</span>
|
||||
<span className={css.itemValue}>{formattedUnitPrice}</span>
|
||||
</div>
|
||||
);
|
||||
) : null;
|
||||
};
|
||||
|
||||
LineItemUnitPrice.propTypes = {
|
||||
LineItemUnitPriceMaybe.propTypes = {
|
||||
transaction: propTypes.transaction.isRequired,
|
||||
unitType: propTypes.bookingUnitType.isRequired,
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
export default LineItemUnitPrice;
|
||||
export default LineItemUnitPriceMaybe;
|
||||
49
src/components/BookingBreakdown/LineItemUnknownItemsMaybe.js
Normal file
49
src/components/BookingBreakdown/LineItemUnknownItemsMaybe.js
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
/**
|
||||
* Renders non-reversal line items that are not listed in the
|
||||
* `LINE_ITEMS` array in util/types.js
|
||||
*
|
||||
* The line items are rendered so that the line item code is formatted to human
|
||||
* readable form and the line total is printed as price.
|
||||
*
|
||||
* If you require another kind of presentation for your line items, add them to
|
||||
* the `LINE_ITEMS` array in util/types.js and create a specific line item
|
||||
* component for them that can be used in the `BookingBreakdown` component.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { intlShape } from 'react-intl';
|
||||
import { formatMoney } from '../../util/currency';
|
||||
import { humanizeLineItemCode } from '../../util/data';
|
||||
import { LINE_ITEMS, propTypes } from '../../util/types';
|
||||
|
||||
import css from './BookingBreakdown.css';
|
||||
|
||||
const LineItemUnknownItemsMaybe = props => {
|
||||
const { transaction, intl } = props;
|
||||
|
||||
// resolve unknown non-reversal line items
|
||||
const items = transaction.attributes.lineItems.filter(
|
||||
item => LINE_ITEMS.indexOf(item.code) === -1 && !item.reversal
|
||||
);
|
||||
|
||||
return items.length > 0 ? (
|
||||
<React.Fragment>
|
||||
{items.map((item, i) => {
|
||||
const label = humanizeLineItemCode(item.code);
|
||||
const formattedTotal = formatMoney(intl, item.lineTotal);
|
||||
return (
|
||||
<div key={`${i}-item.code`} className={css.lineItem}>
|
||||
<span className={css.itemLabel}>{label}</span>
|
||||
<span className={css.itemValue}>{formattedTotal}</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</React.Fragment>
|
||||
) : null;
|
||||
};
|
||||
|
||||
LineItemUnknownItemsMaybe.propTypes = {
|
||||
transaction: propTypes.transaction.isRequired,
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
export default LineItemUnknownItemsMaybe;
|
||||
|
|
@ -277,7 +277,7 @@ export const userDisplayNameAsString = (user, defaultUserDisplayName) => {
|
|||
*/
|
||||
export const userDisplayName = (user, bannedUserDisplayName) => {
|
||||
console.warn(
|
||||
`Function userDisplayName is deprecated!
|
||||
`Function userDisplayName is deprecated!
|
||||
User function userDisplayNameAsString or component UserDisplayName instead.`
|
||||
);
|
||||
|
||||
|
|
@ -333,3 +333,21 @@ export const overrideArrays = (objValue, srcValue, key, object, source, stack) =
|
|||
return srcValue;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Humanizes a line item code. Strips the "line-item/" namespace
|
||||
* definition from the beginnign, replaces dashes with spaces and
|
||||
* capitalizes the first character.
|
||||
*
|
||||
* @param {string} code a line item code
|
||||
*
|
||||
* @return {string} returns the line item code humanized
|
||||
*/
|
||||
export const humanizeLineItemCode = code => {
|
||||
if (!/^line-item\/.+/.test(code)) {
|
||||
throw new Error(`Invalid line item code: ${code}`);
|
||||
}
|
||||
const lowercase = code.replace(/^line-item\//, '').replace(/-/g, ' ');
|
||||
|
||||
return lowercase.charAt(0).toUpperCase() + lowercase.slice(1);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import {
|
|||
denormalisedEntities,
|
||||
arrayToFormValues,
|
||||
formValuesToArray,
|
||||
humanizeLineItemCode,
|
||||
} from './data';
|
||||
|
||||
const { UUID } = sdkTypes;
|
||||
|
|
@ -329,3 +330,21 @@ describe('data utils', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('humanizeLineItemCode', () => {
|
||||
it('should humanize a line item code', () => {
|
||||
expect(humanizeLineItemCode('line-item/new-line-item')).toEqual('New line item');
|
||||
});
|
||||
|
||||
it('should capitalize a one word code', () => {
|
||||
expect(humanizeLineItemCode('line-item/booking')).toEqual('Booking');
|
||||
});
|
||||
|
||||
it('should reject a code with missing namespace', () => {
|
||||
expect(() => humanizeLineItemCode('new-line-item')).toThrowError(Error);
|
||||
});
|
||||
|
||||
it('should reject a code with missing code value', () => {
|
||||
expect(() => humanizeLineItemCode('line-item/')).toThrowError(Error);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -297,7 +297,7 @@ export const LINE_ITEM_UNITS = 'line-item/units';
|
|||
export const LINE_ITEM_CUSTOMER_COMMISSION = 'line-item/customer-commission';
|
||||
export const LINE_ITEM_PROVIDER_COMMISSION = 'line-item/provider-commission';
|
||||
|
||||
const LINE_ITEMS = [
|
||||
export const LINE_ITEMS = [
|
||||
LINE_ITEM_NIGHT,
|
||||
LINE_ITEM_DAY,
|
||||
LINE_ITEM_UNITS,
|
||||
|
|
@ -307,6 +307,17 @@ const LINE_ITEMS = [
|
|||
|
||||
propTypes.bookingUnitType = oneOf([LINE_ITEM_NIGHT, LINE_ITEM_DAY, LINE_ITEM_UNITS]);
|
||||
|
||||
const requiredLineItemPropType = (props, propName, componentName) => {
|
||||
const prop = props[propName];
|
||||
|
||||
if (!prop || prop === '') {
|
||||
return new Error(`Missing line item code prop from ${componentName}.`);
|
||||
}
|
||||
if (!/^line-item\/.+/.test(prop)) {
|
||||
return new Error(`Invalid line item code value ${prop} passed to ${componentName}.`);
|
||||
}
|
||||
};
|
||||
|
||||
// Denormalised transaction object
|
||||
propTypes.transaction = shape({
|
||||
id: propTypes.uuid.isRequired,
|
||||
|
|
@ -323,7 +334,7 @@ propTypes.transaction = shape({
|
|||
|
||||
lineItems: arrayOf(
|
||||
shape({
|
||||
code: oneOf(LINE_ITEMS).isRequired,
|
||||
code: requiredLineItemPropType,
|
||||
includeFor: arrayOf(oneOf(['customer', 'provider'])).isRequired,
|
||||
quantity: instanceOf(Decimal),
|
||||
unitPrice: propTypes.money.isRequired,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue