Item availability - part 3

The item-availability project is now starting to take shape.

Item-Availability-iPad

As before it’s available on Github and forked on Stackblitz.

In addition to few minor adjustments to what was created in part 2, part 3 includes:

If you compare this to part 2 inclusion, you might notice an evolving design pattern.

ww-item-availability

This is the request class and response interfaces for W41202A with slight request difference from item-ss which we created in part 2:

export class WWItemAvailabilityRequest extends FormRequest {
    constructor(item: string) {
        super();
        this.formName = 'P41202_W41202A';
        this.formServiceAction = 'R';
        this.maxPageSize = '1000';
        this.returnControlIDs = '17|19|162|30|319[12,62,9,13,48,47,49,32,276,11]';
        this.formActions = [
            {
                controlID: '30',
                command: 'SetCheckboxValue',
                value: 'on'
            },
            {
                controlID: '17',
                command: 'SetControlValue',
                value: item
            },
            {
                controlID: '14',
                command: 'DoAction'
            }
        ];

Instead of creating an ad-hoc query, we tick the ‘Summary Only’ check-box, or controlID ‘18’; populate ‘Item Number’, or controlID ‘17’ and finally press the find button, or controlID ‘14’.

store update

The new app-state variables are available with the items we selected and quantities with the response from W41201A:

export interface IQuantity {
  item: string;
  branch: string;
  uom: string;
  available: number;
  commited: number;
  onReceipt: number;
}
export interface IAppState {
  items: IItem[];
  available: IItem[];
  quantities: IQuantity[];
}

We have new actions to update the app-state:

  export class AvailableAction implements Action {
    readonly type = ActionTypes.AVAILABLE;
    constructor(public quantities: IQuantity[]) { }
  }
  export class UpdateAvailableAction implements Action {
    readonly type = ActionTypes.UPDATE_AVAILABLE;
    constructor(public available: IItem[]) { }
  }
  export class AddAvailableAction implements Action {
    readonly type = ActionTypes.ADD_AVAILABLE;
    constructor(public items: IItem[]) { }
  }

And finally updates for the reducer:

    case ActionTypes.AVAILABLE:
      return {
        ...state, ...{
          quantities: [...action.quantities]
        }
      };
    case ActionTypes.ADD_AVAILABLE:
      return {
        ...state, ...{
          available: [...state.available, ...action.items]
        }
      };

The syntax is slightly changed from part 2 and is now using the new spread syntax.
Also notice that UpdateAvailableAction action doesn’t change the app-state but is used by e1/effects (see below).

e1/effects

We’ve added three new @Effect() Observables. Lets first look at addAvailable$ and updateAvailable$.

    @Effect()
    addAvailable$ = this.actions$.ofType<AppActions.AddAvailableAction>(ActionTypes.ADD_AVAILABLE)
        .pipe(
            map(action => action.items),
            switchMap(items => OfObservable(new AppActions.UpdateAvailableAction(items)))
        );
    @Effect({ dispatch: false })
    updateAvailable$ = this.actions$.ofType<AppActions.UpdateAvailableAction>(ActionTypes.UPDATE_AVAILABLE)
        .pipe(
            map(action => action.available),
            tap(available => {
                if (available.length > 0) {
                    const request = new BatchformRequest();
                    request.formRequests = available.map(r => new WWItemAvailabilityRequest(r.item));
                    this.batch.request = request;
                    this.e1.call(this.batch);
                }
            })
        );

The purpose of the first one, addAvailable$ is to notify updateAvailable$ that we have selected new items. Whenever updateAvailble$ is observed it will use a Batch Form Service to call WWItemAvailabilityRequest for all the items that were passed in.

The third new observable, itemAvailability$ then maps the response to the app-state:

    @Effect()
    itemAvailability$ = this.actions$.ofType<E1Actions.BatchformResponseAction>(E1ActionTypes.BATCHFORM_RESPONSE)
        .pipe(
            map(action => action.payload.batchformResponse),
            withLatestFrom(this.store),
            switchMap(([bf, store]) => {
                const qt: IQuantity[] = [];
                for (let i = 0; bf[`fs_${i}_${W41202A}`]; i++) {
                    const form: IWWItemAvailabilityForm = bf[`fs_${i}_${W41202A}`];
                    const total:IQuantity = {
                        item: form.data.txtItemNumber_17.value,
                        branch: 'TOTAL',
                        uom: form.data.txtUnitOfMeasure_19.value,
                        available: 0,
                        commited: 0,
                        onReceipt: 0
                    };
                    qt.push(...form.data.gridData.rowset.map<IQuantity>(r => {
                        total.available += r.mnAvailable_47.internalValue;
                        total.commited += r.mnCommitted_48.internalValue;
                        total.onReceipt += r.mnOnReceipt_49.internalValue;
                        return {
                            item: form.data.txtItemNumber_17.value,
                            branch: r.sBranchPlant_9.value,
                            uom: form.data.txtUnitOfMeasure_19.value,
                            available: r.mnAvailable_47.internalValue,
                            commited: r.mnCommitted_48.internalValue,
                            onReceipt: r.mnOnReceipt_49.internalValue
                        }
                    }));
                    qt.push(total);
                };
                const existing = qt.map(ar => ar.item);
                qt.push(...store.app.quantities.filter(sr => !existing.includes(sr.item)));
                return qt.length > 0 ? OfObservable(new AppActions.AvailableAction(qt)) : EmptyObservable();
            })
        );

There are three things that happen in this code, first we need to extract individual form responses into the form variable, map the response to the quantites app-state and total it up.

The last line submits the AvailableAction for non-blank response.

item-availability.component

The page component uses Angular Material’s Card Component for layout. The benefit of the card component is the ease of how it adapts to different displays. The top image shows how it would arrange it-self on a vertical iPad. The same page would arrange the cards vertically on iPhone7 (the title bar text needs adjustment).

Item-Availability-iPhone7

The component’s code is quite simple. It needs Observable for the app-state variables of available and quantities:

export class ItemAvailabilityComponent implements OnInit {
  available: Observable<IItem[]>;
  quantaties: Observable<IQuantity[]>;
  add() {
    this.router.navigate(['item-search']);
  }
  ngOnInit() {
  }
  constructor(
    private router: Router,
    private store: Store<IState>
  ) {
    this.available = store.select<IItem[]>(s => s.app.available);
    this.quantaties = store.select<IQuantity[]>(s => s.app.quantities);
  }
}

And then subscribe to them on the html template with the *ngFor statement:

  <mat-card *ngFor="let each of available | async">
    <mat-card-title>Item {{ each.item }}</mat-card-title>
    <mat-card-subtitle>
      <div>{{ each.desc }}</div>
      <div>{{ each.desc2 }}</div>
    </mat-card-subtitle>
    <mat-card-content>
      <table>
        <tr>
          <th>Branch</th>
          <th>Available</th>
          <th>Committed</th>
          <th>On-Receipt</th>
        </tr>
        <tr *ngFor="let q of quantaties | async | itemFilter:each.item">
          <td></td>
          <td class="qt">{{ q.available }}</td>
          <td class="qt">{{ q.commited }}</td>
          <td class="qt">{{ q.onReceipt }}</td>
          <td></td>
        </tr>
      </table>
    </mat-card-content>
  </mat-card>

We create a card for each item in the available app-state variable and then for each card we create a table with the Branch, Available, Committed and On-Receipt values by using itemFilter.

app-routing.module update

And finally we need add the new component to the routing.

const routes: Routes = [
  { path: 'item-search', component: ItemSearchComponent },
  { path: 'item-availability', component: ItemAvailabilityComponent },
  { path: '**', component: ItemSearchComponent }
];