System Settings REST API で組織を廃止する

4 月の組織再編で、総務部の下の情報システム室を廃止することになったとします。この記事では、廃止が決まってから、あらかじめ決めた削除日に削除するまでを、REST API で行う方法を説明します。組織の新設・改称・親組織の付け替えと、組織ごとの所属メンバの入れ替えは「System Settings REST API で組織図の変更を反映する」(以下、組織図の記事)で説明しています。

この記事に進む前に、組織図の記事のスクリプトを実行しておいてください。 全体アナウンスと[削除予定]の設定は、そのスクリプトに続けて、同じ日に行います。

位置や組織名が変わる組織は、新しい組織を作って旧組織を廃止する形でも反映できます。 どちらにするかと、そのときの CSV の書き方は、組織図の記事の「既存の組織を直すか、新しい組織を作るか」にあります。この形を選んだ場合、この記事に進む時点では、新しい組織に所属メンバが入り、旧組織の所属メンバもそのまま残っています。新しい組織に旧組織と同じ名前を付けた場合は、旧組織は 営業部(旧) のような名前に改称されています。

組織の廃止は 2 段階です。 [削除予定]にして新しく指定されないようにし、削除日が来たら削除します。削除した組織は元に戻せません。

その組織を指しているアプリの設定は、各アプリのアプリ管理者に直してもらいます。 [削除予定]にすると、その組織を処理担当者に指定しているアプリがエラーになり、アプリ管理者にメールが届きます。アプリ管理者は、削除日までに自分のアプリを直します。処理担当者設定などは、システム管理権限を持つユーザが、全アプリの編集中バージョンをまとめて書き換える方法もあります。この記事の後半で紹介します。

組織の廃止は、[システム設定]>[組織一覧]でもできます。処理担当者設定の一括の付け替えも、[組織一覧]の[処理担当者の設定一括変更]で、全アプリを対象にまとめて実行できます。1 回かぎりであれば、そちらのほうが手軽です。

REST API を使うのは、廃止までを含めて毎年の手順にする場合です。

コード例は Python 3 で、HTTP リクエストの送信に Requests パッケージを使います。スクリプトは、[削除予定]・削除・処理担当者設定の一括の付け替えの 3 本に分けます。実行する時期が段階ごとに違い、削除は削除日が来てから実行するためです。3 本とも接続先の設定と組織一覧の取得から始まり、「コード全体」にそれぞれ単体で実行できる形で載せています。

用意する CSV

廃止する組織は、組織名だけを並べ、retire.csv として保存します。旧組織を 営業部(旧) のような名前に改称している場合は、改称した後の名前で書きます。

name
情報システム室

親子の関係にある組織をまとめて廃止する場合は、子組織の行を先に書きます。子組織を持つ組織は削除できず、API がエラーを返して、その行でスクリプトが止まるためです。子組織を残して親だけを廃止する場合は、組織図の記事の orgchart.csv で子組織の親組織を付け替えておきます。

この CSV は、[削除予定]にするときと削除するときの両方で使います。 処理担当者設定を一括で付け替える場合に使う swap.csv は、後半の「処理担当者設定の一括の付け替え」で説明します。

作業の順序

次の順序で進めます。組織図の記事のスクリプトは、実行を終えている前提です。

  1. 全体にアナウンスする。 組織変更を終えたことと、削除する組織、削除日を知らせます。
  2. 続けて、retire.csv で[削除予定]にする。 廃止する組織が、処理担当者の設定で新しく指定されないようにします。
  3. 削除日までに、各アプリのアプリ管理者が設定を直す。 2 のあと 1 分ほどで、その組織を処理担当者に指定しているアプリがエラーになり、アプリ管理者にメールが届きます。
  4. 一括で付け替える場合は、削除日の直前に swap.csv で付け替える。 まだ直っていないアプリの編集中バージョンの処理担当者設定などを、まとめて書き換えます。新バージョンのリリースは、各アプリのアプリ管理者にしてもらいます。
  5. 削除日が来たら、retire.csv で削除する。

API を呼び出す準備

実行するユーザには、ユーザ管理権限と、Basic 認証による API アクセスの許可が必要です。 その設定の手順、API パスワードの調べ方、接続先と認証情報の用意、呼び出しをまとめる check と call の 2 つの関数は、「System Settings REST API で入社と異動を反映する」の「API を呼び出す準備」と同じです。

処理担当者設定を一括で付け替える場合は、システム管理権限も必要です。 持っていなければ、システム管理権限を持つユーザに、[システム設定]>[権限管理]>[システム全体]で付与してもらってください。

組織一覧の取得

API が使うのは組織の ID で、CSV にあるのは組織名です。 先に組織の一覧を取得して、組織名から組織を引く辞書を作ります。組織名はワークフロー基盤の中で一意なので、組織名で引けます。/API/User/Qgroup/list を limit と start でくり返し呼ぶ書き方は、「System Settings REST API で入社と異動を反映する」の「組織一覧の取得」と同じです。

辞書には、応答の 1 件をまるごと入れます。 groups['情報システム室']['id'] が組織 ID、groups['情報システム室']['deletedInFuture'] が[削除予定]かどうかです。deletedInFuture は、「削除」で[削除予定]になっていない組織を飛ばすために使います。

[削除予定]の設定

実行する前に、削除日を決め、どの組織をいつ削除するかを全体にアナウンスしてください。 アナウンスは「作業の順序」の 1 で、組織図の記事のスクリプトを実行したあとに 1 回だけ出します。組織変更を終えたことと削除日をまとめて知らせれば、アプリ管理者に個別に伝える必要はありません。削除日を先に決めるのは、削除してよいかどうかを削除する時点で判断するのが困難なためです。進行中のケースが、廃止する組織を指す工程にこれから到達するかどうかは、各アプリのアプリ管理者に確認するほかありません。

アナウンスには、組織図の記事の「アプリ設定の見直し」の内容に加えて、次のことを書きます。

  • 削除する組織と、削除日。
  • 削除日までに、その組織を指しているアプリの設定を、各アプリのアプリ管理者が直すこと。処理担当者設定のほか、組織型のデータ項目の初期値、条件式などでの組織の参照、アプリ権限(アプリ管理権限・コントロール権限・データ閲覧権限)の組織指定も対象です。
  • 削除日の直前に一括で付け替える場合は、そのことと、付け替え先の組織。一括の付け替えで書き換わるのは、アプリ権限の組織指定を除く設定です。アプリ管理者は、一括の付け替えに任せるか、自分で直すかを選べます。任せる場合も、書き換わるのは編集中バージョンだけなので、新バージョンのリリースはアプリ管理者が行います。

[削除予定]は、/API/UGA/Qgroup/update の deletedInFuture で設定します。

with open('retire.csv', encoding='utf-8') as f:
    for row in csv.DictReader(f):
        group = groups.get(row['name'])
        if not group:
            print(row['name'], '組織一覧にありません')
            continue
        call('POST', '/API/UGA/Qgroup/update',
             data={'id': group['id'], 'deletedInFuture': 'true'})
        print(row['name'], '削除予定にしました')

組織図の記事とは違い、見つからない行は飛ばして先へ進みます。 組織 1 つごとに独立した操作で、途中で止まっても中途半端な形は残らないためです。次の「削除」も同じです。後半の「処理担当者設定の一括の付け替え」だけは、先に全行を照合します。

[削除予定]にした組織は、処理担当者の設定で新しく指定できなくなります。 所属しているユーザはそのまま残り、その組織に割り当てられている進行中のケースも、そのまま処理できます。この設定は外せるので、廃止が取りやめになった場合は deletedInFuture に false を渡して戻せます。

設定してから 1 分ほどで、その組織を処理担当者に指定しているアプリがエラーになり、アプリ管理者にメールが届きます。 スクリプトを実行したユーザには、アプリ管理者を兼ねていないかぎり届きません。アプリ管理者は、このメールで直す対象のアプリを知ることができます。

エラーになるのは、処理担当者設定に指定している場合だけです。 組織型のデータ項目の初期値、条件式などでの組織の参照、アプリ権限の組織指定はエラーにならず、知らせも届きません。アナウンスで、あわせて見直しを依頼するのはそのためです。

削除

組織を削除する API は /API/UGA/Qgroup/delete で、id を渡します。削除した組織は元に戻せません。

削除は、削除日が来たら実行します。 アプリの設定が直っているかどうかは、確かめません。削除日までに直すのは、各アプリのアプリ管理者の役割です。

with open('retire.csv', encoding='utf-8') as f:
    for row in csv.DictReader(f):
        group = groups.get(row['name'])
        if not group:
            print(row['name'], '組織一覧にありません')
            continue
        if not group['deletedInFuture']:
            print(row['name'], '削除予定になっていません')
            continue
        call('POST', '/API/UGA/Qgroup/delete', data={'id': group['id']})
        print(row['name'], '削除しました')

[削除予定]になっていない組織は、削除せずに表示して次へ進みます。 [削除予定]にする手順を飛ばした組織や、廃止がいったん取りやめになった組織が、CSV に残っていることがあるためです。見つからない組織も同じです。子組織を持つ組織だけは飛ばされず、API のエラーでスクリプトが止まります。

すでに担当者にオファーされているワークアイテムは、組織を削除しても消えません。 その担当者から見えたまま残ります。

その組織に所属しているユーザは、削除と同時に所属から外れます。 その組織にしか所属していないユーザは、どの組織にも所属しない状態になります。移動先は、組織図の記事の roster.csv で反映しておきます。

主として属する組織にしていたユーザは、別の組織に変わります。 残っている所属組織のうち、いちばん先に作られた組織が、自動で主として属する組織になります。意図した組織になるとはかぎらないので、削除したあとで確認してください。

アプリ権限の組織指定は、一覧から消えます。 消えたことの知らせは届きません。ただし、その組織にだけアプリ管理権限を与えていたアプリでは、アプリ管理者がいなくなるので、システム管理権限を持つユーザにアプリ設定エラーの知らせが届きます。その段階で、システム管理権限を持つユーザが直します。

データ項目に保存された値として組織が入っていて、その値を処理担当者の設定やスクリプトタスクが参照している場合は、事前に防げません。 設定で組織を直接指定していないので、[削除予定]にしてもアプリはエラーにならず、一括の付け替えでも書き換わりません。

削除したあとで問題が起きた場合は、コントロール権限を持つユーザが対処します。 削除した組織を指す工程にケースが進んだ場合などです。その先どうなるかは、アプリの設定によります。

処理担当者設定の一括の付け替え

処理担当者設定などは、システム管理権限を持つユーザが、全アプリをまとめて付け替えることもできます。 削除日の直前に、まだ直っていないアプリの編集中バージョンをまとめて書き換える使い方です。新バージョンのリリースは、各アプリのアプリ管理者にしてもらいます。組織を削除したあとでは使えません。

API の名前と、画面の[処理担当者の設定一括変更]のラベルは処理担当者を指していますが、書き換えの対象は処理担当者設定にかぎりません。 組織型のデータ項目の初期値や、条件式などでの組織の参照も書き換わります。アプリ権限の組織指定は対象外です。

アプリの設定で参照している組織を置き換える API は /API/Admin/ProcessModel/{processModelInfoId}/AllocationRule/update です。version、fromQgroupIds、toQgroupIds を渡します。fromQgroupIds と toQgroupIds は同じ数だけ並べ、同じ位置どうしが対応します。

廃止する組織と、その代わりに指定する組織を並べ、swap.csv として保存します。

from_name,to_name
情報システム室,総務部

from_name も retire.csv と同じで、改称している旧組織は改称した後の名前で書きます。

to_name には、必ず組織を書きます。 廃止するだけで代わりの組織を決めていない場合、この付け替えは使えません。その組織を指しているアプリは、各アプリのアプリ管理者に直してもらいます。

新しい組織を作って旧組織を廃止する場合は、to_name に新しい組織を書きます。 新しい組織は、組織図の記事の orgchart.csv で作られています。

書き換えるのは、編集中バージョンだけです。 version には、編集中バージョンを表す Creating を渡します。アプリには、編集中バージョンと、稼働中の最新バージョン、そして過去のバージョンがあります。ケースは開始したときのバージョンで最後まで進むので、稼働中のバージョンを書き換えると、いま動いているケースの担当者などを途中で変えることになります。編集中バージョンを直しても、新バージョンをリリースさせるまでは使われません。リリースは、各アプリのアプリ管理者にしてもらいます。リリースする前に開始したケースは、直す前の設定のまま進みます。

組織を操作するスクリプトと同時に実行しないでください。 組織図の記事のスクリプトと、この記事の[削除予定]と削除のスクリプトが、組織を操作するスクリプトです。組織の操作とアプリ設定の変更は同時に行えず、どちらかがエラーになります。

swap.csv は、先に全行を照合します。 全行の組み合わせをアプリごとに 1 回の呼び出しでまとめて渡すためです。見つからない行が 1 行でもあれば、付け替えを実行せずに終了します。CSV を直してから実行し直してください。

アプリの一覧は /API/Admin/ProcessModel/list で取得します。付け替えは、アプリ 1 つにつき 1 回の呼び出しになります。アプリの多いワークフロー基盤では、終わるまでに時間がかかります。

途中で止まったときは、スクリプトを最初から実行し直します。 すでに付け替えたアプリにもう一度実行しても、結果は変わりません。参照していないアプリに実行しても、応答の updated が false になるだけで、何も変わりません。

with open('swap.csv', encoding='utf-8') as f:
    rows = list(csv.DictReader(f))

missing = [row for row in rows
           if row['from_name'] not in groups or row['to_name'] not in groups]
if missing:
    for row in missing:
        print(row['from_name'], row['to_name'], '組織一覧にありません')
    raise SystemExit('付け替えを実行しませんでした')

from_ids = [groups[row['from_name']]['id'] for row in rows]
to_ids = [groups[row['to_name']]['id'] for row in rows]

apps = []
start = 0
while True:
    r = call('GET', '/API/Admin/ProcessModel/list',
             params={'start': start, 'limit': LIMIT})
    page = r.json()['processModelInfos']
    apps.extend(page)
    if len(page) < LIMIT:
        break
    start += LIMIT

for app in apps:
    r = call('POST',
             f"/API/Admin/ProcessModel/{app['processModelInfoId']}/AllocationRule/update",
             data={'version': 'Creating',
                   'fromQgroupIds': from_ids,
                   'toQgroupIds': to_ids})
    if r.json()['updated']:
        print(app['processModelInfoName'], '付け替えました')

付け替えた結果は、スクリプトの出力で確かめます。 付け替えたアプリごとに「付け替えました」と表示されます。その組織を参照していないアプリは、表示されません。

付け替えた設定の見直しは、各アプリのアプリ管理者に任せます。 実行するユーザが、アプリを開いて設定を確かめられるとはかぎらないためです。

コード全体

[削除予定]にするスクリプトです。実行する前に、次の「実行の進め方」を読んでください。

import csv
import requests
from requests.auth import HTTPBasicAuth

BASE = 'https://example.questetra.net'
AUTH = HTTPBasicAuth('admin@example.com', '{API パスワード}')
LIMIT = 10


def check(r):
    if not r.ok:
        print(r.status_code, r.text)
        r.raise_for_status()
    return r


def call(method, path, **kwargs):
    return check(requests.request(method, f'{BASE}{path}', auth=AUTH, **kwargs))


groups = {}
start = 0
while True:
    r = call('GET', '/API/User/Qgroup/list',
             params={'start': start, 'limit': LIMIT})
    qgroups = r.json()['qgroups']
    groups.update({g['name']: g for g in qgroups})
    if len(qgroups) < LIMIT:
        break
    start += LIMIT

with open('retire.csv', encoding='utf-8') as f:
    for row in csv.DictReader(f):
        group = groups.get(row['name'])
        if not group:
            print(row['name'], '組織一覧にありません')
            continue
        call('POST', '/API/UGA/Qgroup/update',
             data={'id': group['id'], 'deletedInFuture': 'true'})
        print(row['name'], '削除予定にしました')

削除するスクリプトです。

import csv
import requests
from requests.auth import HTTPBasicAuth

BASE = 'https://example.questetra.net'
AUTH = HTTPBasicAuth('admin@example.com', '{API パスワード}')
LIMIT = 10


def check(r):
    if not r.ok:
        print(r.status_code, r.text)
        r.raise_for_status()
    return r


def call(method, path, **kwargs):
    return check(requests.request(method, f'{BASE}{path}', auth=AUTH, **kwargs))


groups = {}
start = 0
while True:
    r = call('GET', '/API/User/Qgroup/list',
             params={'start': start, 'limit': LIMIT})
    qgroups = r.json()['qgroups']
    groups.update({g['name']: g for g in qgroups})
    if len(qgroups) < LIMIT:
        break
    start += LIMIT

with open('retire.csv', encoding='utf-8') as f:
    for row in csv.DictReader(f):
        group = groups.get(row['name'])
        if not group:
            print(row['name'], '組織一覧にありません')
            continue
        if not group['deletedInFuture']:
            print(row['name'], '削除予定になっていません')
            continue
        call('POST', '/API/UGA/Qgroup/delete', data={'id': group['id']})
        print(row['name'], '削除しました')

処理担当者設定を一括で付け替えるスクリプトです。システム管理権限が必要です。

import csv
import requests
from requests.auth import HTTPBasicAuth

BASE = 'https://example.questetra.net'
AUTH = HTTPBasicAuth('admin@example.com', '{API パスワード}')
LIMIT = 10


def check(r):
    if not r.ok:
        print(r.status_code, r.text)
        r.raise_for_status()
    return r


def call(method, path, **kwargs):
    return check(requests.request(method, f'{BASE}{path}', auth=AUTH, **kwargs))


groups = {}
start = 0
while True:
    r = call('GET', '/API/User/Qgroup/list',
             params={'start': start, 'limit': LIMIT})
    qgroups = r.json()['qgroups']
    groups.update({g['name']: g for g in qgroups})
    if len(qgroups) < LIMIT:
        break
    start += LIMIT

with open('swap.csv', encoding='utf-8') as f:
    rows = list(csv.DictReader(f))

missing = [row for row in rows
           if row['from_name'] not in groups or row['to_name'] not in groups]
if missing:
    for row in missing:
        print(row['from_name'], row['to_name'], '組織一覧にありません')
    raise SystemExit('付け替えを実行しませんでした')

from_ids = [groups[row['from_name']]['id'] for row in rows]
to_ids = [groups[row['to_name']]['id'] for row in rows]

apps = []
start = 0
while True:
    r = call('GET', '/API/Admin/ProcessModel/list',
             params={'start': start, 'limit': LIMIT})
    page = r.json()['processModelInfos']
    apps.extend(page)
    if len(page) < LIMIT:
        break
    start += LIMIT

for app in apps:
    r = call('POST',
             f"/API/Admin/ProcessModel/{app['processModelInfoId']}/AllocationRule/update",
             data={'version': 'Creating',
                   'fromQgroupIds': from_ids,
                   'toQgroupIds': to_ids})
    if r.json()['updated']:
        print(app['processModelInfoName'], '付け替えました')

実行の進め方

[削除予定]にするスクリプトは、行を絞らずに実行して構いません。 設定は取り消せます。

削除するスクリプトは、まず retire.csv を 1 行だけにして実行します。 削除は取り消せません。削除日が来ていることを確かめてから実行し、1 行目の結果を確かめてから、残りの行を実行します。

一括で付け替えるスクリプトは、行を絞らずに実行して構いません。 書き換えるのは編集中バージョンだけで、実行し直しても結果は変わりません。

関連ページ

Questetra Supportをもっと見る

今すぐ購読し、続きを読んで、すべてのアーカイブにアクセスしましょう。

続きを読む