> ## Documentation Index
> Fetch the complete documentation index at: https://developers.scoutworks.app/llms.txt
> Use this file to discover all available pages before exploring further.

# Badges to order

> A shopping list of every badge your young people have earned but not yet been given, with a count for each badge.

Before you order badges, you need to know how many of each one to buy. This recipe builds that list in a Google Sheet. It counts every badge that is **completed** but not yet **awarded**, and names who gets each one.

It takes about five minutes.

## 1. Add the script

In your Google Sheet, choose **Extensions → Apps Script**. Delete what is there, paste the script below and choose **Save**.

```javascript Code.gs theme={null}
// Scoutworks → badges to order. https://developers.scoutworks.app/recipes/badges-to-order

const BASE = 'https://api-demo.scoutworks.app/api/v1';

function badgesToOrder() {
  const records = readAll('/badges?status=completed');

  // One row for each badge (and stage), with everyone who has earned it.
  const byBadge = {};
  records.forEach(function (r) {
    const key = r.badge.id + '|' + (r.level || '');
    if (!byBadge[key]) {
      byBadge[key] = { name: r.badge.name, level: r.level, category: r.badge.category, who: [] };
    }
    byBadge[key].who.push(r.member.firstName + ' ' + r.member.lastName);
  });

  const rows = Object.keys(byBadge)
    .map(function (key) { return byBadge[key]; })
    .sort(function (a, b) { return a.name.localeCompare(b.name) || (a.level || 0) - (b.level || 0); })
    .map(function (b) {
      return [
        safe(b.name),
        b.level ? 'Stage ' + b.level : '',
        CATEGORIES[b.category] || b.category,
        b.who.length,
        safe(b.who.sort().join(', ')),
      ];
    });

  writeTab('Badges to order', [['Badge', 'Stage', 'Kind', 'How many', 'For']].concat(rows));
  SpreadsheetApp.getActive().toast(records.length + ' badges to order.');
}

const CATEGORIES = {
  core_badges: 'Core badge',
  challenge_awards: 'Challenge award',
  activity_badges: 'Activity badge',
  staged_activity_badges: 'Staged activity badge',
};

// Replaces a tab's contents with these rows.
function writeTab(name, rows) {
  const book = SpreadsheetApp.getActive();
  const sheet = book.getSheetByName(name) || book.insertSheet(name);
  sheet.clearContents();
  sheet.getRange(1, 1, rows.length, rows[0].length).setValues(rows);
  sheet.setFrozenRows(1);
}

// Every item in a list, following nextCursor to the last page.
function readAll(path) {
  const items = [];
  let cursor = null;
  do {
    let url = BASE + path + (path.indexOf('?') === -1 ? '?' : '&') + 'limit=200';
    if (cursor) url += '&cursor=' + encodeURIComponent(cursor);
    const body = fetchScoutworks(url);
    items.push.apply(items, body.data);
    cursor = body.nextCursor;
  } while (cursor);
  return items;
}

// Calls the API. Waits and tries again if the rate limit is reached.
function fetchScoutworks(url) {
  const apiKey = PropertiesService.getScriptProperties().getProperty('SCOUTWORKS_API_KEY');
  if (!apiKey) throw new Error('Add SCOUTWORKS_API_KEY in Project Settings → Script Properties.');
  for (let attempt = 0; attempt < 3; attempt++) {
    const res = UrlFetchApp.fetch(url, {
      headers: { Authorization: 'Bearer ' + apiKey },
      muteHttpExceptions: true,
    });
    const body = JSON.parse(res.getContentText());
    if (res.getResponseCode() === 200) return body;
    if (res.getResponseCode() === 429) {
      Utilities.sleep((body.retryAfter || 30) * 1000);
      continue;
    }
    throw new Error(body.error.code + ': ' + body.error.message);
  }
  throw new Error('Scoutworks is still busy. Try again in a minute.');
}

// Stops a name that starts with = + - or @ from running as a formula.
function safe(value) {
  const text = String(value);
  return /^\s*[=+\-@]/.test(text) ? "'" + text : text;
}
```

## 2. Add your key

In the script editor, choose **Project Settings** (the cog), then **Script Properties → Add script property**. Property: `SCOUTWORKS_API_KEY`. Value: the [sandbox key](/guides/sandbox) to try it, or your own key.

## 3. Run it

Choose **badgesToOrder** in the toolbar and press **Run**. Open the **Badges to order** tab. Each row is one badge, with how many to order and who they are for.

Run it again each time you place an order. When you give a badge out, mark it as awarded in Scoutworks. It then leaves this list.

## 4. Switch to your group

Change `api-demo.scoutworks.app` to `api.scoutworks.app`, and replace the sandbox key with your own. Your key needs the `badges:read` scope.

## Variations

* **A button in the sheet.** Add this function, then reload the sheet. A **Scoutworks** menu appears with the command in it.

  ```javascript theme={null}
  function onOpen() {
    SpreadsheetApp.getUi().createMenu('Scoutworks').addItem('Badges to order', 'badgesToOrder').addToUi();
  }
  ```

* **Badges on the way.** Change `status=completed` to `status=in_progress` to see what young people are working on now. It helps you order early.

* **One young person.** Add `&memberId=` and a member’s `id` to see every badge record for one person. Get the `id` from the [members list](/recipes/google-sheets).
