Skip to content

実績工数を定期的に削除する

概要

TimeTracker NXの各種データは、年々蓄積されていきます。運用状況によっては、管理が不要になったデータを削除することもあるかもしれません。 そこで、参照する必要がなくなった古い実績工数を削除して、管理対象のデータを絞り込む際にWeb APIを利用すれば、漏れなく対象のデータを削除できます。

利用場面

  • 集計や参照に利用しなくなった古い実績工数を削除する

使用するAPI

以下のAPIを使用します。

  • ユーザーの取得
  • 実績工数の取得
  • 実績工数の削除
//======================================================================
//  実績工数を削除する
//======================================================================
//======================================================================
//  外部ファイル(ライブラリ、他ファイル)のインクルード
//======================================================================

const https = require('https'); // HTTPS通信を利用する場合に設定する
const http = require('http'); // HTTP通信を利用する場合に設定する
const axios = require('axios');
const fs = require('fs');

//======================================================================
//  共通部の定義
//======================================================================

// APIを呼び出すURLを設定する
const baseURLString = 'http://yourserver/TimeTrackerNX/api';

// サーバースペックに合わせてウェイト時間を設定する
// 1分間に1500件程度の処理に収まるようするため、
// 目安として推奨スペックでは100ms程度とする 
const requestWaitTime = 100;    //単位:ms

//----------------------------------------------------------------------
//  Web API間の処理待ち関数
//----------------------------------------------------------------------
async function wait(time){
    const d1 = new Date();
    while (true) {
        const d2 = new Date();
        if (d2 - d1 > time) {
            break;
        }
    }
}

//======================================================================
//  メイン処理
//======================================================================
main();

async function main(){

    // 引数チェック    
    if (process.argv.length < 3){
        return 'enter username and password'; 
    }

    const userName = process.argv[2];
    var pass;
    var authString; //ブロック内スコープから外に出すためvarで宣言(固定値)

    // 引数でパスワードが未入力の場合は空白とみなす
    if (process.argv.length > 3){
        pass = process.argv[3];
    } else {
        pass = '';        
    }

    // 認証情報を取得する
    var sendUrl = baseURLString + '/auth/token';

    try{
        const { data } = await axios.post(
            sendUrl,
            {
                loginName : userName,
                password : pass
            },
/*
        // https通信時に使用する
        httpsAgent  : new https.Agent({
            rejectUnauthorized: false
                    })
*/    
        );
        authString = 'Bearer ' + data.token;

    } catch (err) {
        console.log(err);
    }

    // 以降のAPI通信で利用する送信ヘッダを作成する
    const request = axios.create({
        baseURL     : baseURLString,
        headers     :{
                        'Authorization'   : authString
                    },
/*
        // https通信時に使用する
        httpsAgent  : new https.Agent({
            rejectUnauthorized: false
                    })
*/    
    });

    // JSON形式のデータ(例:後述のdeleteActualTime.json)を入力とする
    const filepath = './deleteActualTime.json';

    var updateData = JSON.parse(fs.readFileSync(filepath, {encoding: "utf-8"}));

    // JSON形式の実績工数データから1つ分のデータを取り出す
    for (key in updateData){
        var sendUrl = '/system/users';
        var userId;

        // 実績を削除する対象ユーザのID取得
        // ユーザーを一意に特定できる情報(コード)をもとに、実績を削除する対象ユーザーのIDを取得
        try {
            const { data } = await request.get(
                sendUrl,
                {
                    params: {
                        code                : updateData[key].code
                    }
                }
            );
            userId = data.data[0].id;
        } catch (e) {
            console.log(e);
        }

        // 削除対象の実績工数のIDを取得する
        sendUrl = '/system/users/' + userId + '/timeEntries?startDate=[削除対象日]';

        // 実績の取得 (リクエストに検索用の実績工数情報を設定して送信)
        try {
            const { data } = await request.get(
                sendUrl,
                {
                    params: {
                        startDate   : updateData[key].startDate,
                        finishDate  : updateData[key].finishDate,
                        workItemId  : updateData[key].workItemId
                    }
                }                
            ); 

            // 検索で見つかった実績データを一つずつ削除する
            for ( timeEntriesData in data.data ){
                // 実績の削除
                sendUrl = '/system/users/'+userId+'/timeEntries/'+ data.data[timeEntriesData].id;
                try {
                    const { data } = await request.delete(
                        sendUrl
                    ); 
                } catch (e) {
                    console.log(e);
                }; 
                await wait(requestWaitTime); 
            }
        } catch (e) {
            console.log(e);
        }
        await wait(requestWaitTime);
    }
}

入力情報

以下のサンプルスクリプトで使用する削除用の実績情報ファイルを以下のリンク先から参照できます。

    private async void DeleteActualTime_Click(object sender, EventArgs e)
    {
        //======================================================//
        //  必要な変数等の定義                                    //
        //======================================================//
        JavaScriptSerializer serializar = new JavaScriptSerializer();
        var httpClient = new HttpClient();

        string serverUrl = "http://WIN-Q1OR68LM6RG.mshome.net/TimeTrackerNX/";
        string loginname = "okamoto";
        string password = "";

        // トークンの取得
        var tokenRequest = new Dictionary<string, string>
        {
            {"loginName",loginname },
            {"password",password }
        };
        var jsonData = serializar.Serialize(tokenRequest);
        var content = new StringContent(jsonData, Encoding.UTF8, "application/json");
        string requestUrl = serverUrl + "/api/auth/token";
        var response = await httpClient.PostAsync(requestUrl, content);
        string responseData = await response.Content.ReadAsStringAsync();
        Token token;

        try
        {
            token = serializar.Deserialize<Token>(responseData);
        }
        catch
        {
            MessageBox.Show("トークン取得に失敗しました。\n接続先のサーバーURL、ログイン名、パスワードを確認してください。");
            return;
        }
        // ヘッダ設定
        httpClient.DefaultRequestHeaders.Add("contentType", "application/json");
        httpClient.DefaultRequestHeaders.Add("Authorization", "Bearer " + token.token);

        //==========================================//
        // CSVを入力にデータを取得                    //
        //==========================================//
        String[] actualTimeData = null;
        List<string> lists = new List<string>();
        const int ActualDataMemberNum = 5;
        string userId = null;

        StreamReader sr = new StreamReader(@"deleteActualTime.csv", System.Text.Encoding.GetEncoding("shift_jis"));
        {
            //タイトル行の回避(1行目はデータにしない)
            string line = sr.ReadLine();

            while (!sr.EndOfStream)
            {
                line = sr.ReadLine();

                actualTimeData = line.Split(',');
                lists.AddRange(actualTimeData);
            }
        }

        for (int i = 0; i < lists.Count; i = i + ActualDataMemberNum)
        {

            var Member = new Dictionary<string, string>
            {
                {"user",lists[i]},
                {"code ",lists[i+1]},
                {"workItemId",lists[i+2]},
                {"startTime",lists[i+3] },
                {"finishTime",lists[i+4]},
            };

            //------------------------------------------//
            // ユーザー情報の取得                    //
            //------------------------------------------//
            requestUrl = serverUrl + "/api/system/users?code=" + lists[i + 1];
            response = await httpClient.GetAsync(requestUrl);
            responseData = await response.Content.ReadAsStringAsync();


            var user = serializar.Deserialize<ResponseUsers>(responseData);
            userId = user.data[0].id;

            //------------------------------------------//
            // 実績データの取得                         //
            //------------------------------------------//
            requestUrl = serverUrl + "/api/system/users/" + userId+ "/timeEntries?startDate=[削除対象日]";

            response = await httpClient.GetAsync(requestUrl);
            responseData = await response.Content.ReadAsStringAsync();

            var actualData = serializar.Deserialize<ActualTimeEntries>(responseData);
            int actualDataNum = int.Parse(actualData.totalCount);

            //------------------------------------------//
            // 実績工数の削除                          //
            //------------------------------------------//

            for (int delCnt = 0; delCnt < actualDataNum; delCnt++)
            {
                requestUrl = serverUrl + "/api/system/users/" + userId + "/timeEntries/" + actualData.data[delCnt].id;
                response = await httpClient.DeleteAsync(requestUrl);
                responseData = await response.Content.ReadAsStringAsync();

            }

        }
    }

    public class Token
    {
        public string token { get; set; }
    }


    public class ResponseUsers
    {
        public string totalCount;
        public ResponseUser[] data;
    }

    public class ResponseUser
    {
        public string isDeleted;
        public string createdAt;
        public string updatedAt;
        public string createdBy;
        public string updatedBy;
        public string id;

        public ResponseUser()
        {
            this.id = null;
        }
    }


    public class ActualTimeEntries
    {
        public string totalCount;
        public ActualTimeEntry[] data;
    }

    public class ActualTimeEntry
    {
        public string id;
        public DateTime workDate;
        public int time;
        public DateTime startTime;
        public DateTime finishTime;
        public string memo;
        public string projectId;
        public string projectName;
        public string projectCode;
        public string userId;
        public string userName;
        public string workItemId;
        public string workItemName;
        public string workItemNumber;
        public string workItemTypeId;
        public string timeEntryCategoryId;
        public string timeEntryCategory;
        public string processCategoryId;
        public string processCategory;
        public bool isLocked;
        public bool isDeleted;
        public DateTime createdAt;
        public string createdBy;
        public DateTime updatedAt;
        public string updatedBy;


        public ActualTimeEntry()
        {
            this.workItemId = null;
            this.id = null;
            this.workDate = DateTime.MinValue;
            this.time = 0;
            this.startTime = DateTime.MinValue;
            this.finishTime = DateTime.MaxValue;
            this.memo = null;
            this.projectId = null;
            this.projectName = null;
            this.projectCode = null;
            this.userId = null;
            this.userName = null;
            this.workItemId = null;
            this.workItemName = null;
            this.workItemNumber = null;
            this.workItemTypeId = null;
            this.timeEntryCategoryId = null;
            this.timeEntryCategory = null;
            this.processCategoryId = null;
            this.processCategory = null;
            this.isLocked = false;
            this.isDeleted = false;
            this.createdAt = DateTime.MinValue;
            this.createdBy = null;
            this.updatedAt = DateTime.MinValue;
            this.updatedBy = null;

        }

    }

入力情報

以下のサンプルスクリプトで使用する削除用の実績情報ファイルを以下のリンク先から参照できます。