graycoreio/daffodil

View on GitHub
libs/order/state/src/selectors/order-entities.selector.ts

Summary

Maintainability
C
1 day
Test Coverage
import { Dictionary } from '@ngrx/entity';
import {
  createSelector,
  defaultMemoize,
  MemoizedSelector,
} from '@ngrx/store';

import {
  getDaffCartSelectors,
  DaffCartStateRootSlice,
} from '@daffodil/cart/state';
import {
  DaffOrder,
  DaffOrderItem,
  DaffOrderTotal,
  DaffOrderTotalTypeEnum,
} from '@daffodil/order';

import { getDaffOrderReducersStateSelector } from './order-feature.selector';
import {
  DaffOrderStateRootSlice,
  daffGetOrderAdapter,
  DaffOrderEntityState,
} from '../reducers/public_api';

export interface DaffOrderEntitySelectors<T extends DaffOrder = DaffOrder> {
  selectOrderEntitiesState: MemoizedSelector<DaffOrderStateRootSlice<T>, DaffOrderEntityState<T>>;
  /**
   * Selector for order IDs.
   */
  selectOrderIds: MemoizedSelector<DaffOrderStateRootSlice<T>, T['id'][]>;
  /**
   * Selector for order entities.
   */
  selectOrderEntities: MemoizedSelector<DaffOrderStateRootSlice<T>, Dictionary<T>>;
  /**
   * Selector for all orders.
   */
  selectAllOrders: MemoizedSelector<DaffOrderStateRootSlice<T>, T[]>;
  /**
   * Selector for total number of orders.
   */
  selectOrderTotal: MemoizedSelector<DaffOrderStateRootSlice<T>, number>;
  selectOrder: (orderId: T['id']) => MemoizedSelector<DaffOrderStateRootSlice<T>, T>;

  /**
   * Selector for the most recently placed order (if any).
   *
   * @deprecated in favor of {@link DaffCheckoutPlacedOrderSelectors#selectPlacedOrder}
   */
  selectPlacedOrder: MemoizedSelector<DaffOrderStateRootSlice<T>, T>;
  /**
   * Selector for the existence of the most recently placed order.
   *
   * @deprecated in favor of {@link DaffCheckoutPlacedOrderSelectors#selectHasPlacedOrder}
   */
  selectHasPlacedOrder: MemoizedSelector<DaffOrderStateRootSlice<T>, boolean>;

  /**
   * Selects the specified order's totals.
   */
  selectOrderTotals: (orderId: T['id']) => MemoizedSelector<DaffOrderStateRootSlice<T>, T['totals']>;
  /**
   * Selects the specified order's applied coupon codes.
   */
  selectOrderAppliedCodes: (orderId: T['id']) => MemoizedSelector<DaffOrderStateRootSlice<T>, T['applied_codes']>;
  /**
   * Selects the specified order's items.
   */
  selectOrderItems: (orderId: T['id']) => MemoizedSelector<DaffOrderStateRootSlice<T>, T['items']>;
  /**
   * Selects the specified order's billing addresses.
   */
  selectOrderBillingAddresses: (orderId: T['id']) => MemoizedSelector<DaffOrderStateRootSlice<T>, T['billing_addresses']>;
  /**
   * Selects the specified order's shipping addresses.
   */
  selectOrderShippingTotalAddresses: (orderId: T['id']) => MemoizedSelector<DaffOrderStateRootSlice<T>, T['shipping_addresses']>;
  /**
   * Selects the specified order's shipments.
   */
  selectOrderShipments: (orderId: T['id']) => MemoizedSelector<DaffOrderStateRootSlice<T>, T['shipments']>;
  /**
   * Selects the specified order's payment.
   */
  selectOrderPayment: (orderId: T['id']) => MemoizedSelector<DaffOrderStateRootSlice<T>, T['payment']>;
  /**
   * Selects the specified order's invoices.
   */
  selectOrderInvoices: (orderId: T['id']) => MemoizedSelector<DaffOrderStateRootSlice<T>, T['invoices']>;
  /**
   * Selects the specified order's credits.
   */
  selectOrderCredits: (orderId: T['id']) => MemoizedSelector<DaffOrderStateRootSlice<T>, T['credits']>;

  /**
   * Selects the specified order's specified item.
   */
  selectOrderItem: (orderId: T['id'], itemId: T['items'][0]['id']) => MemoizedSelector<DaffOrderStateRootSlice<T>, DaffOrderItem>;

  /**
   * Selects the specified order's grand total.
   */
  selectOrderGrandTotal: (orderId: T['id']) => MemoizedSelector<DaffOrderStateRootSlice<T>, DaffOrderTotal>;
  /**
   * Selects the specified order's subtotal.
   */
  selectOrderSubtotal: (orderId: T['id']) => MemoizedSelector<DaffOrderStateRootSlice<T>, DaffOrderTotal>;
  /**
   * Selects the specified order's shipping total.
   */
  selectOrderShippingTotal: (orderId: T['id']) => MemoizedSelector<DaffOrderStateRootSlice<T>, DaffOrderTotal>;
  /**
   * Selects the specified order's discount total.
   */
  selectOrderDiscountTotal: (orderId: T['id']) => MemoizedSelector<DaffOrderStateRootSlice<T>, DaffOrderTotal>;
  /**
   * Selects whether the specified order has a discount.
   */
  selectOrderHasDiscount: (orderId: T['id']) => MemoizedSelector<DaffOrderStateRootSlice<T>, boolean>;
  /**
   * Selects the specified order's tax total.
   */
  selectOrderTaxTotal: (orderId: T['id']) => MemoizedSelector<DaffOrderStateRootSlice<T>, DaffOrderTotal>;
}

const createOrderEntitySelectors = <T extends DaffOrder = DaffOrder>() => {
  const { selectOrderFeatureState } = getDaffOrderReducersStateSelector<T>();
  const selectOrderEntitiesState = createSelector(
    selectOrderFeatureState,
    state => state.orders,
  );
  const { selectIds, selectEntities, selectAll, selectTotal } = daffGetOrderAdapter<T>().getSelectors(selectOrderEntitiesState);
  const { selectCartOrderId } = getDaffCartSelectors();

  const selectOrder: (orderId: T['id']) => MemoizedSelector<DaffOrderStateRootSlice<T>, T> =
    defaultMemoize((orderId: T['id']) => createSelector(
      selectEntities,
      (orders: Dictionary<T>) => orders[orderId] || null,
    )).memoized;

  const selectPlacedOrder = createSelector(
    selectEntities,
    selectCartOrderId,
    (orders, orderId) => orderId ? selectOrder(orderId).projector(orders) : null,
  );

  const selectHasPlacedOrder = createSelector(
    selectPlacedOrder,
    placedOrder => !!placedOrder,
  );

  const selectOrderTotals: (orderId: T['id']) => MemoizedSelector<DaffOrderStateRootSlice<T>, T['totals']> =
    defaultMemoize((orderId: T['id']) => createSelector(
      selectOrder(orderId),
      (order) => (order && order.totals) || [],
    )).memoized;

  const selectOrderAppliedCodes = defaultMemoize((orderId: T['id']) => createSelector(
    selectOrder(orderId),
    (order) => (order && order.applied_codes) || [],
  )).memoized;

  const selectOrderItems: (orderId: T['id']) => MemoizedSelector<DaffOrderStateRootSlice<T>, T['items']> =
    defaultMemoize((orderId: T['id']) => createSelector(
      selectOrder(orderId),
      (order) => (order && order.items) || [],
    )).memoized;

  const selectOrderBillingAddresses = defaultMemoize((orderId: T['id']) => createSelector(
    selectOrder(orderId),
    (order) => (order && order.billing_addresses) || [],
  )).memoized;

  const selectOrderShippingTotalAddresses = defaultMemoize((orderId: T['id']) => createSelector(
    selectOrder(orderId),
    (order) => (order && order.shipping_addresses) || [],
  )).memoized;

  const selectOrderShipments = defaultMemoize((orderId: T['id']) => createSelector(
    selectOrder(orderId),
    (order) => (order && order.shipments) || [],
  )).memoized;

  const selectOrderPayment = defaultMemoize((orderId: T['id']) => createSelector(
    selectOrder(orderId),
    (order) => (order && order.payment) || null,
  )).memoized;

  const selectOrderInvoices = defaultMemoize((orderId: T['id']) => createSelector(
    selectOrder(orderId),
    (order) => (order && order.invoices) || [],
  )).memoized;

  const selectOrderCredits = defaultMemoize((orderId: T['id']) => createSelector(
    selectOrder(orderId),
    (order) => (order && order.credits) || [],
  )).memoized;

  const selectOrderGrandTotal = defaultMemoize((orderId: T['id']) => createSelector(
    selectOrderTotals(orderId),
    totals => {
      const index = totals.findIndex(total => total.type === DaffOrderTotalTypeEnum.GrandTotal);

      return index > -1 ? totals[index] : null;
    },
  )).memoized;

  const selectOrderSubtotal = defaultMemoize((orderId: T['id']) => createSelector(
    selectOrderTotals(orderId),
    totals => {
      const index = totals.findIndex(total => total.type === DaffOrderTotalTypeEnum.Subtotal);

      return index > -1 ? totals[index] : null;
    },
  )).memoized;

  const selectOrderShippingTotal = defaultMemoize((orderId: T['id']) => createSelector(
    selectOrderTotals(orderId),
    totals => {
      const index = totals.findIndex(total => total.type === DaffOrderTotalTypeEnum.Shipping);

      return index > -1 ? totals[index] : null;
    },
  )).memoized;

  const selectOrderDiscountTotal: (orderId: T['id']) => MemoizedSelector<DaffOrderStateRootSlice<T>, DaffOrderTotal> =
    defaultMemoize((orderId: T['id']) => createSelector(
      selectOrderTotals(orderId),
      totals => {
        const index = totals.findIndex(total => total.type === DaffOrderTotalTypeEnum.Discount);

        return index > -1 ? totals[index] : null;
      },
    )).memoized;

  const selectOrderHasDiscount = defaultMemoize((orderId: T['id']) => createSelector(
    selectOrderDiscountTotal(orderId),
    discountTotal => discountTotal?.value > 0,
  )).memoized;

  const selectOrderTaxTotal = defaultMemoize((orderId: T['id']) => createSelector(
    selectOrderTotals(orderId),
    totals => {
      const index = totals.findIndex(total => total.type === DaffOrderTotalTypeEnum.Tax);

      return index > -1 ? totals[index] : null;
    },
  )).memoized;

  const selectOrderItem = defaultMemoize((orderId: T['id'], itemId: T['items'][0]['id']) => createSelector(
    selectOrderItems(orderId),
    items => items.find(item => item.id === itemId) || null,
  )).memoized;

  return {
    selectOrderEntitiesState,
    selectOrderIds: selectIds,
    selectOrderEntities: selectEntities,
    selectAllOrders: selectAll,
    selectOrderTotal: selectTotal,

    selectPlacedOrder,
    selectHasPlacedOrder,

    selectOrder,
    selectOrderTotals,
    selectOrderAppliedCodes,
    selectOrderItems,
    selectOrderBillingAddresses,
    selectOrderShippingTotalAddresses,
    selectOrderShipments,
    selectOrderPayment,
    selectOrderInvoices,
    selectOrderCredits,
    selectOrderItem,

    selectOrderGrandTotal,
    selectOrderSubtotal,
    selectOrderShippingTotal,
    selectOrderDiscountTotal,
    selectOrderHasDiscount,
    selectOrderTaxTotal,
  };
};

export const getDaffOrderEntitySelectors = (() => {
  let cache;
  return <T extends DaffOrder = DaffOrder>(): DaffOrderEntitySelectors<T> =>
    cache = cache || createOrderEntitySelectors<T>();
})();