backlog4jで課題を登録してみる

今度は、backlogへの課題登録を自動化したいという話があり、前回のredmineと似通った要件なので再利用箇所も多く、redmine java api部分をヌーラボ公認のbacklog4jに置き換えて実装してみました。
redmineとの違いはそれなりにありますが、backlog4jの方はKeyとIDを混同しやすい感じがします。例えば、課題のKeyはURLを見ればすぐ解りますが、IDの方は内部的なユニークな数値です。これに注意しながらbacklog用に前回のラッパークラスを置き換えてみました。

import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.lang3.StringUtils;

import com.nulabinc.backlog4j.BacklogClient;
import com.nulabinc.backlog4j.BacklogClientFactory;
import com.nulabinc.backlog4j.Category;
import com.nulabinc.backlog4j.Issue;
import com.nulabinc.backlog4j.IssueComment;
import com.nulabinc.backlog4j.IssueType;
import com.nulabinc.backlog4j.Project;
import com.nulabinc.backlog4j.Status;
import com.nulabinc.backlog4j.api.option.AddIssueCommentParams;
import com.nulabinc.backlog4j.api.option.CreateIssueParams;
import com.nulabinc.backlog4j.api.option.GetIssuesParams;
import com.nulabinc.backlog4j.api.option.UpdateIssueParams;
import com.nulabinc.backlog4j.conf.BacklogConfigure;
import com.nulabinc.backlog4j.conf.BacklogJpConfigure;
import com.nulabinc.backlog4j.conf.BacklogPackageConfigure;

public class BacklogRegist {
	
	private BacklogClient backlog;
		
	public BacklogRegist(String url, String apikey) throws MalformedURLException  {
		
		BacklogConfigure conf = null;
		//オンプレ環境
		if(url.startsWith("http")){
			conf = new BacklogPackageConfigure(url);
		}
		//クラウド
		else{
			conf = new BacklogJpConfigure(url);
		}
		conf.apiKey(apikey);
 		backlog = new BacklogClientFactory(conf).newClient();
				
		
	}
	/**
	 * 課題を取得する
	 * @param issueKey
	 * */
	public Issue getIssue(String issueKey){
		
		if(issueKey == null){
			return null;
		}
		return getIssue = backlog.getIssue(issueKey);
	}
	/**
	 * 課題を作成する
	 * @param projectKey
	 * @param  issueTypeName 種別名称
	 * @param categoryName カテゴリ名称
	 * @param summary 件名
	 * @param description 詳細
	 * @return ticketID 0は登録失敗
	 * */
	public long regist(String projectKey, String issueTypeName, String categoryName, 
						String summary, String description){
		
		CreateIssueParams param = new CreateIssueParams(
				getProjectID(projectKey), summary,
				getIssueTypeId(projectKey, issueTypeName), Issue.PriorityType.Normal);
		
		param.description(description);
		
		if(StringUtils.isNotEmpty(categoryName)){
			List<String> categoryIds = new ArrayList<String>();
			categoryIds.add(getCategoryId(projectKey, categoryName));		
			param.categoryIds(categoryIds);
		}
		
		Issue issue = backlog.createIssue(param);
		if(issue != null){
			return issue.getId();
		}
		return 0;
	}
	
	/**
	 * 課題を更新する(コメントの追加も可能)
	 * @param projectKey プロジェクトKey
	 * @param  issueIdorKey 課題IDまたはKey 
	 * @param status ステータス名称(更新しなければセットしない)
	 * @param addComment  更新なければセットしない
	 * @return commentID 0は登録失敗
	 * */
	public long update(String issueIdorKey, String status, String addComment){
		long ret = 0;
		Issue issue = null;
		
		if(StringUtils.isNotEmpty(status)){
			UpdateIssueParams param = new UpdateIssueParams(issueIdorKey);
			for( Status Entity: backlog.getStatuses()){
				if( Entity.getName().equals(status)){
					param.status(Entity.getStatusType());
				}
			}
			issue = backlog.updateIssue(param);
		}
		else{
			issue = backlog.getIssue(issueIdorKey);
		}
		if(StringUtils.isNotEmpty(addComment)){
			AddIssueCommentParams params = new AddIssueCommentParams(issue.getIssueKey(), addComment);

			IssueComment cmt = backlog.addIssueComment(params);
			ret = cmt.getId();
		}
		else{
			ret = issue.getId();
		}
		return ret;
	}

	/**
	 * 課題にコメントを追加する
	 * @param projectKey
	 * @param  issueKey
	 * @param addComment
	 * @return commentID 0は登録失敗
	 * */
	public long addComment(String issueKey, String addComment){
		Issue issue = backlog.getIssue(issueKey);
		
		if( issue != null){
			AddIssueCommentParams params = new AddIssueCommentParams(issue.getIssueKey(), addComment);

			IssueComment cmt = backlog.addIssueComment(params);
			return cmt.getId();
			
		}
		return 0;
	}

	/**
	 * 課題のタイプ(種別)を取得する
	 * @param prjKey プロジェクトキー
	 * @param name 種別名称
	 * */
	public String getIssueTypeId(String projectKey, String name){
		for( IssueType type: backlog.getIssueTypes(projectKey)){
			if( type.getName().equals(name)){
				return type.getIdAsString();
			}
		}
		return null;
	}
	
	/**
	 * カテゴリを取得する
	 * @param prjKey プロジェクトキー
	 * @param name 種別名称
	 * */
	public String getCategoryId(String projectKey, String name){
		for( Category type: backlog.getCategories(projectKey)){
			if( type.getName().equals(name)){
				return type.getIdAsString();
			}
		}
		return null;
	}
	/**
	 * プロジェクトIDを取得する
	 * @param prjKey プロジェクトキー
	 * @return Projectid
	 * */
	public String getProjectID(String projectKey){
		for( Project type: backlog.getProjects()){
			if( type.getProjectKey().equals(projectKey)){
				return type.getIdAsString();
			}
		}
		return null;
	}
}

上記を呼び出すのはこんな感じ

BacklogRegist backlog = new BacklogRegist("{URLまたはスペースID}","{apiKey}");

//チケット取得
Issue issue = backlog.getIssue("PROJECT-3");

//チケット登録
long issueId = backlog.regist("PROJECT", "要望", "設定変更",  
	"ユーザーの追加について", "○○さんを追加してください");

後日談
GetIssieで3ヶ月経って問題発生・・・たまに課題の取得に失敗するようになりました・・・
下記のようにしましたが、最初からそうしろ!って事ですね。でも、3ヶ月は何も問題なかったのですが・・
public Issue getIssue(String issueKey)

Issue getIssue = backlog.getIssue(issueKey);

redmine java api でチケットを取得してみる

 前回の続きで、今度はチケットを取得してみます。単純にチケットIDを指定して一つのチケットを取得するのでは無く、条件を指定し一括してチケットを取得して何かをするような要件への対応です。

 下記サンプルでは、クエリーを使用せずに、
ステータスID=2 かつ トラッカーID=10または11 かつ 題名に「テスト」を含む
という条件に一致するチケットを全て取得する内容です。
100件ずつチケットを取得して、100件以上のチケットがあればページ番号を変えて全チケットを取得します。

String apikey = "APIキー値";
String uri = "redmineサイトURI";

String TICKET_LIMIT = "100";

Map<String,String> params = new HashMap<String,String>();

// リミット
params.put("limit",TICKET_LIMIT);

params.put("status_id","2");

params.put("tracker_id","10|11");
//題名に「テスト」を含む
params.put("subject", "~テスト");

RedmineManager mgr = RedmineManagerFactory.createWithApiKey(uri, apikey);

boolean iscontinue = true;
int page = 1;
while(iscontinue){
	
	List<Issue> issues = mgr.getIssueManager().getIssues(params);
	
	for(Issue issue : issues){
		System.out.println(issue.getSubject());
	}
	if(issues.size() < Integer.valueOf(TICKET_LIMIT)){
		iscontinue = false;
	}
	else{
		page++;
		params.put("page",String.valueOf(page));
	}
}

redmine java api でチケットを登録してみる

 redmineへのチケット登録を自動化したいという話があり、内容的には常時トリガーを拾って登録という感じだったので、javaの常駐プロセスでトリガーを拾う事を前提として、こちらを使用して実装してみました。
https://github.com/taskadapter/redmine-java-api
なお、登録対象のredmineは2.5系でしたが、それほどバージョンを意識しなくても大丈夫そうです。

事前準備として、対象のredmineへapi登録用のユーザーを用意し、登録対象のプロジェクトへの権限を付与、個人設定画面からapiキーを取得します。

その他、プロジェクトidやトラッカーid等のredmine内部で持っているidの値はブラウザからapiで下記のようにidの値を確認してセットとなります。ただ、redmine java apiには、様々なマネージャーがあるので、id指定でなくても都度問い合わせしてidを取得できそうです。

ソース的には下記のようなラッパークラスを作って、登録や更新を呼び出し側で簡略化出来るようにしてます。

package jp.eosoro.redmine;

import java.util.ArrayList;
import java.util.List;

import com.taskadapter.redmineapi.Include;
import com.taskadapter.redmineapi.RedmineException;
import com.taskadapter.redmineapi.RedmineManager;
import com.taskadapter.redmineapi.RedmineManagerFactory;
import com.taskadapter.redmineapi.bean.CustomField;
import com.taskadapter.redmineapi.bean.Issue;
import com.taskadapter.redmineapi.bean.ProjectFactory;
import com.taskadapter.redmineapi.bean.TrackerFactory;
import com.taskadapter.redmineapi.bean.User;
import com.taskadapter.redmineapi.bean.UserFactory;

/**
 * Redmine登録クラス
 * */
public class RedmineRegist {

	RedmineManager mgr;
	
	private Issue issue;
	private List<CustomField> customeField;
	
	/**
	 * コンストラクタ
	 * @param String RedmineサイトURI
	 * @param String APIキー
	 */
	public RedmineRegist(String uri, String apiAccessKey) {
		mgr = RedmineManagerFactory.createWithApiKey(uri, apiAccessKey);

		issue = new Issue();
		customeField = new ArrayList<CustomField>();		
	}
	/**
	 * チケット登録
	 * @param subject タイトル
	 * @param description 詳細
	 * @param assigned_toId 担当者を指定する時はユーザーid 指定しない場合は0
	 * @param assigned_toGroupId グループを指定するときはグループid 指定しない場合は0
	 * @param parentid 親チケットを指定する時はチケットid 指定しない場合は0
	 * @param projectId
	 * @param trackerId 
	 * @param priorityId
	 * @param statusId
	 * @return int 登録チケットID 登録失敗時はゼロ
	 * */ 
	public int regist(String subject, String description, int assigned_toId, int assigned_toGroupId, int parentid, int projectId, int trackerId, int priorityId, int statusId){
		
		getIssue().setSubject(subject);
		
		getIssue().setDescription(description);

		//プロジェクト指定
		getIssue().setProject(ProjectFactory.create(projectId));

		//トラッカー指定
		getIssue().setTracker(TrackerFactory.create(trackerId));
		
		//ステータス/優先度指定
		getIssue().setPriorityId(priorityId);
		getIssue().setStatusId(statusId);
		
		// カスタムフィールド設定
		getIssue().addCustomFields(getCustomeField());
		
		try {
			//担当者指定
			if(assigned_toId > 0){
				getIssue().setAssignee(getMgr().getUserManager().getUserById(assigned_toId));
				
			}
			//グループ
			else if(assigned_toGroupId > 0){
				User u = UserFactory.create(assigned_toGroupId);
				getIssue().setAssignee(u);
			}
			//親チケット
			if(parentid > 0){
				getIssue().setParentId(parentid);
			}
			return getMgr().getIssueManager().createIssue(getIssue()).getId();

		} catch (RedmineException e) {
			return 0;
		}
	}
	/**
	 * 関連するチケットをセットする
	 * @param int チケットID
	 * @throws RedmineException 
	 * */
	public void setRelation(int id, int relateid) throws RedmineException{
		getMgr().getIssueManager().createRelation(id, relateid, "relates");
		
	}
	/**
	 * ジャーナル追加
	 * @param id チケットID
	 * @param Notes コメント
	 * @throws RedmineException 
	 * */
	public int addJournal(int id, String Notes) throws RedmineException{
		Issue issue =  getMgr().getIssueManager().getIssueById(id, Include.journals);
		
		issue.setNotes(Notes);

		getMgr().getIssueManager().update(issue);

		return issue.getId();
	}

	public RedmineManager getMgr() {
		return mgr;
	}
	private Issue getIssue() {
		return issue;
	}
	public List<CustomField> getCustomeField() {
		return customeField;
	}
	public void setCustomeField(List<CustomField> customeField) {
		this.customeField = customeField;
	}
}

下記の呼び出し側では、親チケットを作成してジャーナルを追加、子チケットを2つ作成し関連付けてます。

       RedmineRegist  mine = new RedmineRegist (url, apikey);
       
       int parentId = mine.regist("親チケット", "親チケット本文", 0, 0, 0, 4, 4, 2, 1);
       
       mine.addJournal(parentId, "ジャーナル");

       int childId = mine.regist("子チケット", "子チケット本文",  0, 0, parentId, 4, 4, 2, 1);
       
       int relationId = mine.regist("関連チケット", "関連チケット本文",  0, 0, parentId, 4, 4, 2, 1);
       
       
       mine.setRelation(childId, relationId);

いろいろやってみましたが、redmineに対して殆どの事は出来そうです。

e-Taxで法人税の中間申告&納税してみた。

中間申告の通知が来ていたので、e-Taxで納税してみました。

e-Taxは何回も使ってますが、中間申告は今回初めて。
確定申告の期間なので、e-Taxが土日もやっており助かります。

e-Taxにログインし、申告・申請・納税を押して、新規作成
で次はどこ押すの?と立ち止まる。

正解は、「納税情報を登録する」なのだが、
中間申告の通知を送ってきてるんだから、既に税務署にデータは登録されているんじゃないの?
と疑問を感じつつ先へ進む。税務署は初期表示されているので、次へ
作成方法の選択と来て、「新規作成」を選択し、次へ
税目を「法人税」と選択し、次へ
課税期間を送られてきた通知の通り入力し、「中間申告」を選択し、次へ
通知に書かれていた金額を「本税」に入力し、次へ
ここで入力内容の確認画面となり、確認後、次へ
受付システムへの送信となり、送信
「送信が完了しました」と出て、受信通知の確認を押す。
送信されたデータを受け付けたと表示され、画面下の方に、電子納税とあるので、今すぐ納付でやると今日は土曜でダメ、納付日を指定される方というボタンを押すと、登録済の口座情報が表示され、納付ボタンを押して終了です。

これって、画面4つ程度で事足りるし、その方が解りやすい気がしますが、どうなんでしょう?
昔、画面1ついくらみたいな開発費用の見積もりをした事もありましたが、今もそうなのかなあ?

なお、このオペレーションを法人税と地方法人税で2回やって、中間申告&納税手続きは終了です。

コンピューターは人がやりたがらない仕事をすればいい

 基本的に筆者の仕事の大半は、企業内で人手がかかるオフィス業務を自動化するためのプログラム作りと言っていいのかも知れません。オフィス業務といってもいろいろあって、バックオフィス業務の自動化であったり、システム運用作業の自動化だったり、要件はそれぞれ違いますが、どちらかというとシステム運用作業の自動化の方が簡単というか、結構ワンパターンなので、完全自動化はやりやすいです。逆に言うと、開発を経てシステム運用フェーズに至る前にちゃんと運用設計されていれば、運用をフォローしやすくする為の設計とか運用ツールとかがある程度用意されているので、それらに沿って運用作業を自動化するだけという事で簡単に感じるのかも知れません。早い話、コマンドを自動的に実行出来ればいいみたいな。逆にバックオフィス業務はそういう観点から構築されていないので、業務分析という整理をするフェーズが無いと何がベストか解りにくいですね。

 10年以上前の事ですが、某地銀の仕事をやってた時にその銀行の部長が、システムを導入するのは人を減らすのが目的ではなく、今居る人で今後増えていく業務量をこなしていく為にシステムを導入するんだというような事を言っていたのを今でも覚えています。システム開発の仕事をしていると、自分の仕事は誰かの仕事を奪っているのではないか?という風に感じた事もありましたが、その部長の言葉は、とても腑に落ちました。

 今の時代、AIとかで人の仕事が無くなるのでは?みたいな話題が多いですが、結局の所、その仕事はどこからどこまでが範囲で、何をしてよくて何をしてはならないといったルールを定義するのはAIではなく人です。そんなケースごとに決める必要が無い範囲では既にコンピューターやロボットで自動化されていると思います。その先を実現しているのは、逆説的にコンピューターの世界で出来る範囲だけがその仕事の範囲であると定義付けしたところだけでは?
 オフィス業務はイレギュラーなケースを考えると無限に近いパターンがありますが、90%程度はワンパターンかも知れません。でもそこから少しでも外れたことがあれば人にしかできないし、そこまでフォロー出来る仕組みを作る方が却って人を雇うよりコスト高になると思います。判断基準は変わっていきますし、昔の判断が今でも正しいと決めつけてたら間違いますよ(例えば、昔はタバコをどこでも吸って、どこでも灰を落としてるけど、それは今時であれば限られた場所でしか出来ないみたいな)

今の所、コンピューターが人の仕事を奪っているのは、人があまりやりたがらないルーチンワークが殆どです。大量生産をひたすら24時間続けるなんて誰もやりたがらないし出来ないですよね。いつか、AIだけで会社を経営する事が出来る時代になったら、その時は人の仕事が殆ど無くなるんだろうと思いますが、そんな時代は当面無いですよね?
 
 例えばタクシーの運転手も配達員もただクルマを運転している訳では無く、依頼を聞くとか、請求するとか、荷物を車に載せるとか、伝票を処理するとか、不在だったらどうする?とか、自動運転が実現しても、他の仕事範囲はまた別の話です。その先にあるであろう、経営自体をAIでってのは全く聞いたことがありません。経営をAIに任せようという時代が来た時点で、その先の時代はターミネーターだったりマトリックスのようなストーリーが本当になる可能性が出てくるんでしょうね。

JAX-RSでWeb画面にドラッグアンドドロップされたファイルを読み込む

テキストファイルをサーバへアップロードしたいという要件があり、Web画面からファイルをドラッグアンドドロップできるようにして、それをJAX-RSで処理する事にしてみました。

下記の参考にさせて頂き、それらを組合わせただけと言えばだけですが。。

Jersey(JAX-RS)でファイルアップロード
HTML5 の File API でドラッグ&ドロップする

まず、JAX-RSのルートパスを指定します

import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;

@ApplicationPath("/api")
public class RestApplication extends Application {
//何も書く事はありません・・・
}

次にファイルを処理するJAX-RS部分を作ります

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.stream.Stream;

import javax.enterprise.context.RequestScoped;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

import com.sun.jersey.core.header.FormDataContentDisposition;
import com.sun.jersey.multipart.FormDataParam;

@RequestScoped
@Path("/upload")
public class FileUploader {
	
	@POST
	@Consumes(MediaType.MULTIPART_FORM_DATA)
	public Response post(@FormDataParam("file") InputStream fileStream,
					@FormDataParam("file") FormDataContentDisposition fileDisposition) {

		int statusCode = 200;
		String out = "";

		//テキストファイルの読み込み
		try (BufferedReader br = new BufferedReader(new InputStreamReader(fileStream))) {
			try(Stream<String> lines = br.lines()){
				//1行毎の処理は省略
			}
        } catch (IOException e) {
			statusCode = 400;
			out = e.getMessage();
		}
		return Response.status(statusCode).type("text/html;charset=Shift-JIS").
			entity(out).
			build();
	}
}

これでファイルアップローダーのJAX-RSパスは、{コンテンツルート}/api/uploadになりました。

続いてJavaScriptの部分(殆ど上記参考から持ってきただけです。。)

$(function() {
   var droppable = $("#droppable");

    // イベントをキャンセルするハンドラです.
    var cancelEvent = function(event) {
        event.preventDefault();
        event.stopPropagation();
        return false;
    }

    // dragenter, dragover イベントのデフォルト処理をキャンセルします.
    droppable.bind("dragenter", cancelEvent);
    droppable.bind("dragover", cancelEvent);

    // ドロップ時のイベントハンドラを設定します.
    var handleDroppedFile = function(event) {

	    var dropfile = event.originalEvent.dataTransfer.files[0];
	    
	    var formData = new FormData();
	    formData.append( 'file', dropfile );
	      
	    var hostUrl= 'api/upload';
	    $.ajax({
	       url: hostUrl,
	       method: 'post',
	       dataType: 'json',
	       data: formData,
	       processData: false,
	       contentType: false,
	       timeout:100000
	    }).done(function(data) {
        	alert( '正常に終了しました');
	    }).fail(function( jqXHR, textStatus, errorThrown ) {
	         alert( 'エラーが発生しました\n'+ jqXHR.responseText);
	    });
	
	    // デフォルトの処理をキャンセルします.
	    cancelEvent(event);
	    return false;
    }
    // ドロップ時のイベントハンドラを設定します.
    droppable.bind("drop", handleDroppedFile);
});

最後にHTML部分のドラッグアンドドロップ部分です。
ここにドロップされたファイルがJAX-RSの処理箇所のInputStream に繋がります。

<div class="droppable" id="droppable" style="border:gray solid 1em; width:100px; padding:2em;">アップロードするファイルはココにドロップしてください。</div>      

これでとりあえずはJAX-RSでファイル処理が出来ることが確認出来ました。

最後にPOM。

<dependency>
    <groupId>javax.ws.rs</groupId>
    <artifactId>javax.ws.rs-api</artifactId>
    <version>2.0.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.sun.jersey.contribs/jersey-multipart -->
<dependency>
    <groupId>com.sun.jersey.contribs</groupId>
    <artifactId>jersey-multipart</artifactId>
    <version>1.19.3</version>
</dependency>

eLTaxのPCdeskで税務申告してみた

 昨年の10月くらいだったか「給与所得の源泉徴収表等の法定調書の提出について」と題した書類が届いていて、1月末までに提出しなければならないものがあるとの事なので、夏休みの宿題のように追われて対応していました。
 ネットで調べていくと、どうやら「給与所得の源泉徴収票等の法定調書」は税務署へ提出し、殆ど同じ内容の「給与支払報告書」を市町村に提出しなければならない模様。
 で、さらに調べるとラッキーなことに今年から一括提出が出来るようになったようで、一括提出にはPCdeskというソフトが必要との事。PCdeskはeTaxの地方税版であるeLTaxのクライアントソフトという位置づけのようで、PCにインストールする必要があります。
 eLTaxの利用申請は既にやっていたので、eLTaxのサイトからダウンロードしてインストール後すんなり使用可能になり、住基カードを使って何とか国税と地方税双方に申告することが出来ました。

が、このソフト、筆者にとってはかなり使い勝手が悪く、インポートとエクスポートとしかボタンに書いてないので、ボタンを押すことで何が起きるのかわからず、誤って入力したデータを何回か消しました。。
 通信で法人データを取得することをファイルのダウンロード処理とメッセージに表示されるのも、メッセージとしてとても分かりずらい。。
 どうもこのソフトは税理士等代理人が複数の企業を担当するのを意識して作られているようなので、その道のプロフェッショナルが対象という事なんでしょう。筆者のような素人は、このソフトがターゲットとしているユーザーではないのかもしれません。でも、それって公的機関が作るべきソフトとしてどうなんでしょうか?それを使わなければならない素人にもわかってもらうような対処がなければ、それは従来の業務を簡素化するだけで、その業務をやっていた人達が代理人だとすれば、このソフトは代理人の業務簡素化を目的に作ったのか、それとも作ったソフトを受入れ評価する人達がそのような立場の人たちしか集まらなかったのか??
 ソフトウェアの評価を求める時はいろんなタイプのユーザーを入れて評価してもらう、それが難しければ、せめて画面のメッセージぐらいは、いろんなタイプのユーザーの存在をちゃんと意識して文章にしようと思う今日この頃です。

OTRSを試してみた

 ヘルプデスク業務で使えそうなオープンソースを探していたのですがOTRSがよさげでしたので、下記を参考にさせて頂き構築してみました。
日本OTRSユーザ会 インストールマニュアル

 インストールでちょっと面倒なのは、otrs.CheckModules.plで不足モジュールを確認しながらひたすらyumしていくとこですが、確認時の出力にyumコマンドを表示してくれるので簡単です。

 とりあえず、動く状態になったのでブラウザからアクセスし、DB設定等の初期設定後ログインできる状態になったので、管理画面をみて、ざっくりどんな機能があるかを確認します。

いや、結構な機能数ですね。。

 ざっくり言うと、メールと電話がインバウンドとなり、チケットを作成してヘルプデスク業務を行うという内容です。チケットの登録画面とかも含めて殆どメールでやり取りする感覚です。今回はメールの設定まではしなかったので確認してませんが、恐らく受信メールをキューから取得し、チケット管理するという感じと思われます。

 ちょっとよくわからないのが、顧客と顧客ユーザーの関係。基本的に顧客に対して顧客ユーザーを複数登録できますが、なぜここが分離されているんだろう?法人が顧客で、その担当者が顧客ユーザーという事でしょうか?その顧客ユーザーとのやり取りをチケットとして管理するという内容ですので、顧客ユーザーの管理を実業務にどうマッチさせるかがキモになりそうです。
 結局、この規模のシステムになると、実際に使うところの業務内容や体制等をちゃんとフィットアンドギャップしてみないと使えるものか、やっぱりすぐには解りませんね。。
 その前に、OTRSの機能を把握しなければなりませんが、システム設定値が1400項目あります。。Webサービスとかプロセス管理とか意味不明です。。細かい管理設定周りが完全に日本語表記になってないので、試す以上先に進むのはきついなあ。。

でも、新規にヘルプデスクを立ち上げるような時であれば、オープンソースだしSLA管理とかもあるので、かなり有力な候補にはなるかと思います。

bootstrap2 datetimepickerを1分単位にする

ちょっと前に作ったJavaEEアプリでbootstrap2のdatatimepickerを使ったのだが、
?p=698
5分刻みは使いにくいという話が出てきて、ググッてみたところ下記を発見。
http://www.malot.fr/bootstrap-datetimepicker/
オプションのminuteStepで値を変えられるみたいなのでやってみました。
1分刻みにするとこうなります。

xhtml側は下記minuteStepのオプションを追加するだけ

<h:outputScript name="bootstrap/js/bootstrap-datetimepicker.min.js" />
<h:outputScript name="bootstrap/js/bootstrap-datetimepicker.ja.js" />
<h:outputStylesheet name="bootstrap/css/bootstrap-datetimepicker.min.css" />

$(function() {
  $('.datetimepicker').datetimepicker({
    format: 'yyyy/mm/dd hh:ii:ss',
    autoclose: true,
    todayBtn: true,
    pickerPosition: "bottom-left",
    minuteStep: 1,  
    language: 'ja'
   });
});

デフォルトだと5分刻みなのでこんな感じ

ダイアログが長くなって画面ギリギリだけど、この方が便利ということでこちらでリリース
次作るものはbootstrap3にしないとなあ。。

法人宛に送られてきたもの

法人成りして暫くは近所の会計事務所や税務署の書類くらいしかポストに入っていませんでしたが、2年目に入りこんな書類が来ていたなあという整理です。

・企業情報調査票
 初年の決算終了後に東京商工リサーチから送られてきたもの。財務諸表とかかなり細かい情報を記入するようになっている。特に信用情報を確認するような相手との取引は無いので、未返信。

・厚生年金保険・健康保険の加入状況にかかる調査
 日本年金機構から来たアンケート。まだ国民健康保険のままですが、正直に書いて返信

・事業所・企業照会票
 総務省統計局から来た事業状況を確認する書類。こちらはWebから記入出来たのでそちらで対処。しかし、FireFoxだと政府のSSL証明書が安全でないとでるんですが何とかしないのでしょうか?

・消費税の転嫁拒否等に関する調査
 こちらは、中小企業庁から法人取引でちゃんと消費税を転嫁できているかの調査票。してくれない取引先も記入可だが、継続して取引しているところは書きにくいよね。筆者としてはまったく問題無いので返信してません。個人事業主のころから毎年来ている書類ですが、最初のうちは返信してました。

・償却資産申告書
 ちょっと前に市から来ていた償却資産所有の申告書。来年1月末までに届けるものらしいが、要領を見る限り、該当する資産が無ければ何もしなくて良さそう。

→後日談
償却資産申告書は何も無いから何も送らなかったのですが、3月になって、「ちゃんと提出してね」的な書面が届いてました。
無いものは無いので、電話で役所に連絡したところ、電話レベルで「無い」という事が確認できたのでOKです、と回答が得られました。