---
id: 69a5f35669099ed52f8563b1
title: Build a Smart Pantry Restocker
challengeType: 26
dashedName: lab-smart-pantry-restocker
---

# --description--

In this lab, you will build a small pantry management program using basic JavaScript concepts like arrays, objects, loops, and conditionals.

You will simulate receiving a shipment of pantry items, deciding what to do with each item, and organizing the results for storage.

**Objective:** Fulfill the user stories below and get all the tests to pass to complete the lab.

The `rawData` array contains pipe-separated strings with the format `sku|name|qty|expires|zone`, where `zone` is optional.

**User Stories:**

1. You should implement a `parseShipment(rawData)` function that takes an array of strings and returns an array of objects with `{ sku, name, qty, expires, zone }` properties.

   - Duplicate `sku` values in the shipment should be ignored.
   - When the `zone` segment is not provided, it should default to `"general"`.
   - The `qty` value should be converted to a number.

2. You should implement a `planRestock(pantry, shipment)` function that compares the current pantry with the incoming shipment and returns an array of actions in the form `{ type, item }`, where `type` is one of `"restock"`, `"discard"`, or `"donate"`, and `item` is the parsed shipment object.

   The `pantry` parameter is an array of objects with the same shape as a parsed shipment item (`{ sku, name, qty, expires, zone }`).

   - If a shipment item has a `qty` of `0` or less, the action `type` should be `"discard"`, regardless of whether the item exists in the pantry.
   - Otherwise, if the shipment item's `sku` already exists in the pantry, the action `type` should be `"restock"`.
   - Otherwise (the shipment item's `sku` does not exist in the pantry), the action `type` should be `"donate"`.

3. You should implement a `groupByZone(actions)` function that groups the actions into storage zones based on each item's `zone` property. The function should return an object where each key is a zone name and the value is an array of actions belonging to that zone. For example, if actions contain items with zones `"fridge"` and `"pantry"`, the result should be `{ fridge: [...], pantry: [...] }`.

4. You should implement a `clonePantry(pantry)` function that returns a deep copy of the pantry so planning changes do not affect the original list. A deep copy means creating a new array with new objects, so modifying the copy does not change the original pantry.

5. You should use all of the functions together to process a shipment and log the final grouped result object to the console.

# --before-each--

```js
const spy = __helpers.spyOn(console, "log");
const getLogs = () => spy.calls.map(call => call?.[0]);
```

# --hints--

You should define a function named `parseShipment` that accepts one array of strings called `rawData` as parameter.

``` js
assert.isFunction(parseShipment);
assert.lengthOf(parseShipment, 1);
```

Your `parseShipment` function should convert shipment strings in the array into objects with the properties: `sku`, `name`, `qty`, `expires`, and `zone`.

``` js
const rawData = ["A10|Tomatoes|5|2027-01-01|fridge"];
const result = parseShipment(rawData);

assert.isArray(result);
assert.isObject(result[0]);
assert.deepEqual(result[0], {
  sku: "A10",
  name: "Tomatoes",
  qty: 5,
  expires: "2027-01-01",
  zone: "fridge"
});
```

Duplicate SKUs in the shipment should be ignored.

``` js
const rawData = [
  "C32|Eggs|3|2027-01-01",
  "C32|Eggs|3|2027-01-01"
];

const result = parseShipment(rawData);

assert.lengthOf(result, 1);
assert.strictEqual(result[0].sku, "C32");
```

When `zone` is not present in a shipment string, you should assign it to `general`.

``` js
const rawData = ["B21|Bananas|10|2027-01-01"];
const result = parseShipment(rawData);
assert.strictEqual(result[0].zone, "general");
```

The `qty` value should be converted into a number.

``` js
const rawData = ["A10|Tomatoes|5|2027-01-01|fridge"];
const result = parseShipment(rawData);
assert.strictEqual(result[0].qty, 5);
assert.isNumber(result[0].qty);
```

You should define a function named `planRestock` that accepts two parameters: `pantry` and `shipment`.

``` js
assert.isFunction(planRestock);
assert.lengthOf(planRestock, 2);
```

Your `planRestock` function should return an array of actions with the properties of `type` and `item`.

``` js
const pantry = [{ sku: "A10", name: "Tomatoes", qty: 4, expires: "2027-01-01", zone: "fridge" }];
const shipment = [{ sku: "A10", name: "Tomatoes", qty: 6, expires: "2027-01-01", zone: "fridge" }];

const result = planRestock(pantry, shipment);

assert.isArray(result);
assert.isObject(result[0]);
assert.property(result[0], "type");
assert.property(result[0], "item");
```

Items with a quantity of `0` or less in shipment should create an action with type `discard`, regardless of whether the item is in the pantry.

``` js
const pantry = [{ sku: "D43", name: "Pineapples", qty: 2, expires: "2027-01-01", zone: "general" }];
const shipment = [{ sku: "D43", name: "Pineapples", qty: 0, expires: "2027-01-01", zone: "general" },
                  { sku: "E54", name: "Peppers", qty: -1, expires: "2027-01-01", zone: "fridge" }
                ];

const result = planRestock(pantry, shipment);

assert.strictEqual(result[0].type, "discard");
assert.strictEqual(result[1].type, "discard");
```

When a shipment item already exists in the pantry (same `sku`), the action should be `restock`.

``` js
const pantry = [{ sku: "A10", name: "Tomatoes", qty: 4, expires: "2027-01-01", zone: "fridge" }];
const shipment = [{ sku: "A10", name: "Tomatoes", qty: 4, expires: "2027-01-01", zone: "fridge" }];

const result = planRestock(pantry, shipment);

assert.strictEqual(result[0].type, "restock");
```

If a shipment item does not exist in the pantry, the action should be `"donate"`.

``` js
const pantry = [];
const shipment = [{ sku: "C32", name: "Eggs", qty: 3, expires: "2027-01-01", zone: "fridge" }];

const result = planRestock(pantry, shipment);

assert.strictEqual(result[0].type, "donate");
```

You should define a function named `groupByZone` that accepts one parameter called `actions`.

``` js
assert.isFunction(groupByZone);
assert.lengthOf(groupByZone, 1);
```

Your function `groupByZone` should return the actions grouped by the `zone` property of each object.

``` js

const actions = [
  { type: "restock", item: { sku: "A1", zone: "fridge" } },
  { type: "restock", item: { sku: "B1", zone: "pantry" } }, 
];

const result = groupByZone(actions);

assert.isObject(result);
assert.isArray(result.fridge);
assert.isArray(result.pantry);
```

Your `groupByZone` function should correctly group actions with the right content and count.

``` js
const actions = [
  { type: "restock", item: { sku: "A1", zone: "fridge" } },
  { type: "donate",  item: { sku: "B1", zone: "fridge" } },
  { type: "discard", item: { sku: "C1", zone: "pantry" } },
];

const result = groupByZone(actions);

assert.lengthOf(result.fridge, 2);
assert.lengthOf(result.pantry, 1);
assert.strictEqual(result.fridge[0].item.sku, "A1");
assert.strictEqual(result.pantry[0].type, "discard");
```

You should define a function named `clonePantry` that accepts one parameter called `pantry`.

``` js
assert.isFunction(clonePantry);
assert.lengthOf(clonePantry, 1);
```

`clonePantry` should return a new array instead of the original pantry array.

``` js
const pantry = [{ sku: "A10", name: "Tomatoes", qty: 4, expires: "2027-01-01", zone: "fridge" }];
const pantryClone = clonePantry(pantry);

assert.isArray(pantryClone);
assert.notStrictEqual(pantryClone, pantry);
```

The objects inside the cloned pantry should also be new objects (deep copy).

``` js
const pantry = [{ sku: "A10", name: "Tomatoes", qty: 4, expires: "2027-01-01", zone: "fridge" }];
const pantryClone = clonePantry(pantry);

assert.notStrictEqual(pantryClone[0], pantry[0]);
assert.deepEqual(pantryClone[0], pantry[0]);

pantryClone[0].qty = 100;
assert.strictEqual(pantry[0].qty, 4);
```

The functions should work together to process a shipment and group the resulting actions.

``` js
const pantry = [{ sku: "A10", name: "Tomatoes", qty: 4, expires: "2027-01-01", zone: "fridge" }];
const rawData = ["A10|Tomatoes|5|2027-01-01|fridge"];

const shipment = parseShipment(rawData);
const pantryClone = clonePantry(pantry);
const actions = planRestock(pantryClone, shipment);
const grouped = groupByZone(actions);

assert.isObject(grouped);
assert.strictEqual(grouped.fridge[0].item.qty, 5);
```

You should log the resulting actions grouped by zones.

``` js
const groupedLog = getLogs().find(log => {
  if (!log || typeof log !== "object" || Array.isArray(log)) return false;

  return Object.values(log).some(zoneActions => {
    if (!Array.isArray(zoneActions)) return false;

    return zoneActions.some(action =>
      action &&
      ["restock", "discard", "donate"].includes(action.type) &&
      action.item &&
      typeof action.item.sku === "string"
    );
  });
});

assert.isOk(groupedLog, "Log the grouped actions object to the console.");
```

# --seed--

## --seed-contents--

```js
const pantry = [
  { sku: "A10", name: "Tomatoes", qty: 4, expires: "2027-01-01", zone: "fridge" },
  { sku: "D43", name: "Pineapples", qty: 2, expires: "2020-01-01", zone: "general" }
];

const rawData = [
  "A10|Tomatoes|5|2027-01-01",
  "B21|Bananas|10|2027-01-01",
  "C32|Eggs|3|2027-01-01|fridge",
  "C32|Eggs|3|2027-01-01",
  "D43|Pineapples|0|2027-01-01",
  "E54|Peppers|-1|2027-01-01|fridge"
];
```

# --solutions--

```js

function parseShipment(rawData) {

  // Early return if rawData is not an Array
  if (!Array.isArray(rawData)) return [];

  const result = [];
  const seenSKUs = [];

  for (let line of rawData) {

    const parts = line.split("|");

    const sku = parts[0].trim();

    if (seenSKUs.includes(sku)) continue; // Avoid duplicates

    const name = parts[1].trim();
    const qty = Number(parts[2].trim()); // Parse qty into a number
    const expires = parts[3].trim();
    const zone = ( parts[4] ) ? parts[4].trim() : "general";

    result.push({
      sku: sku,
      name: name,
      qty: qty,
      expires: expires,
      zone: zone
    });

    seenSKUs.push(sku);

  }

  return result;
}

function planRestock(pantry, shipment) {
  const actions = [];

  for (let item of shipment) {

    var exists = false;

    for (let stock of pantry)
    {
      if (stock.sku === item.sku)
      {
        exists = true;
      }
    }

    if (item.qty > 0) {
      if (exists) {
        actions.push({ type: "restock", item: item });
      } else{
        actions.push({ type: "donate", item: item });
      }
    } else {
      actions.push({ type: "discard", item: item });
    }
  }

  return actions;
}

function groupByZone(actions) {

  const byZone = {};

  for (const action of actions) {

    const zone = action.item.zone;

    if (!byZone[zone]) {
      byZone[zone] = [];
    }

    byZone[zone].push(action);
  }

  return byZone;

}

function clonePantry(pantry) {
  const pantryClone = [];

  for (let item of pantry) {
    pantryClone.push({
      sku: item.sku,
      name: item.name,
      qty: item.qty,
      expires: item.expires,
      zone: item.zone
    });
  }

  return pantryClone;
}

const pantry = [
  { sku: "A10", name: "Tomatoes", qty: 4, expires: "2027-01-01", zone: "fridge" },
  { sku: "D43", name: "Pineapples", qty: 2, expires: "2020-01-01", zone: "general" }
];

const rawData = [
  "A10|Tomatoes|5|2027-01-01", // Restock existing item
  "B21|Bananas|10|2027-01-01", // Donate new item without zone
  "C32|Eggs|3|2027-01-01|fridge", // Donate to a defined zone
  "C32|Eggs|3|2027-01-01", // Duplicated SKU in shipment
  "D43|Pineapples|0|2027-01-01", // Discard with quantity of 0
  "E54|Peppers|-1|2027-01-01|fridge" // Discard even if it's not in pantry
];

const shipment = parseShipment(rawData);
const pantryCopy = clonePantry(pantry);
const actions = planRestock(pantryCopy, shipment);
const grouped = groupByZone(actions);

// Show items expanded in zones
console.log(grouped);

```
