Life is Really Short, Have Your Life!!

ござ先輩の主に技術的なメモ

IntegerFieldで「0」が入力エラーになる場合

validatorにDataRequiredを使っているせいです。InputRequiredに変更しましょう。

Dockerコンテナでリバースプロキシ

github.com

nginx-proxyを使った構成

nginxはリバースプロキシの1つだけにして、後はサービスのコンテナを起動してリバースプロキシ側で色々頑張ろうと思ったのですが、nginx-proxyのDockerイメージも各々のWebアプリのNginx設定に対応しきれていないようなので...

こういう感じで、nginx-proxyには単純にバーチャルホストベースで割り振ってもらうだけにして、「nginx-proxy」⇔「nginx⇔各アプリのサービス」という仕組みにすると簡単にできました。この方式ならば、各々のWebアプリコンテナの稼働が確認できれば、後はnetworkとvirtualhostに追加するだけなので、結構柔軟です。

各コンテナごとに専用のnginxを立てるのは無駄な気もしなくてはないですが、取りあえずはこれで。はい。

本家のnginx-proxyのfastsgi設定が怪しいかも

github.com

VIRTUAL_PROTO=fastcgi を設定するとfastcgi用の設定ファイルがnginx-proxyコンテナに書かれるんですが、上記のコメントにあるように「include fastcgi.conf」で指定しているfastcgi.confがありません。多分fastcgi_paramsのtypoだと思われます。

売上を顧客の締め日で集計するSQLを簡単に書く方法

detail.chiebukuro.yahoo.co.jp

天才的なソリューションだった。驚いた。日付から締め日を引き算すれば、何月締めであるかを判定できるとは。シカクいアタマをマルくする必要性を痛感した。

締め日集計のめんどいところは、締め日がN件あること。5日、10日、15日、20日、25日、月末の6パターンある場合、2017年12月の締め日別の合計は以下の期間を元に算出する必要がある。

2017年12月の締日別集計期間

締め日 集計期間
5日 2017/11/06〜2017/12/05
10日 2017/11/11〜2017/12/10
15日 2017/11/16〜2017/12/15
20日 2017/11/21〜2017/12/20
25日 2017/11/26〜2017/12/25
月末 2017/12/01〜2017/12/31

当たり前だが、月末の末日は変化する。28、29、30、31の4通りある。締日別で売上を見たいというのは、上記の集計期間別に売上総額を見て、入金予定の算段を立てる目安として使われる事が多い。

この締日別の集計期間をどう作るべきか悩んでいた。最低最悪のソリューションだと締日別にSQLを投げてUNIONするのはありえへんし、CASE-WHENで締日を見てクロス集計するのも無駄に複雑でだるい。そこに彗星の如く現れたのが「日付から締め日を引き算」するというSQLだ。実際にやってみた。

伝票日付から締日を引くSQL(サンプル)

select
	customers.duedate,
	earnings.billdate,
	(earnings.billdate - interval customers.duedate DAY) as monthly
from
	earnings
	JOIN customers ON customers.id = earnings.customer_id
where
	year(earnings.billdate) = 2017
group by
	earnings.billdate,customers.duedate

MySQLの場合、日付型のデータにおいてはINTERVALを与えると引き算(足し算)してくれる。実際に引いてみたら、たしかにこれで集計できることがわかった。あとは簡単。DATE_FORMATで年月を割り出し、そいつでGroupByするだけだ。

締日別売上集計SQL

select
	duedate,
	DATE_FORMAT((billdate - interval duedate DAY),'%Y年%m月') as monthly,
	sum(subtotal)
from
	earnings
	JOIN customers ON customers.id = earnings.customer_id
group by
	monthly,duedate
order by
	monthly,duedate

手元のMacで8万行の売上データが507msで集計できた。

追記(2017.12.26 17:30)

duedate(締日)に31が登録されている場合(うちのシステムは、末締めの場合31という数値を入力して表現することになっている)月末が31日じゃないものについてはバグになってしまう。11/30から31を引いたら、10/30になってしまい11月伝票が10月伝票になってしまうことに気がついたので、このSQLはクソ。駄目です。

というか、末締めの場合は締日の引き算が不要。その日付の年月が締め対象になる。というわけで、そこをCASE-WHENで場合分けすればOKだった。以下のSQLなら大丈夫なはず。

select
	duedate,
	case 
	  when duedate = 31 then DATE_FORMAT(billdate,'%Y年%m月')
	  else DATE_FORMAT((billdate - interval duedate DAY),'%Y年%m月') 
	end as monthly,
	sum(subtotal)
from
	earnings
	JOIN customers ON customers.id = earnings.customer_id
group by
	monthly,duedate
order by
	monthly,duedate

追記(2018.03.23 14:40)

バグが有ることが判明しました。

20日締で5/25付の顧客は6月付にならなければなりません。5/21〜6/20 が期間なので。でも、5/25 で 締日が20日の場合、単純に日付から締日を引き算してしまうと、5/5になり、5月付になってしまいます。5/25は6月付やん。アカンわ。

「日付の日にち」>「締日」だったら日付の年月を1ヶ月加算、「日付の日にち」<=「締日」はその日付の年月を請求月とする。これが正しいようです。末締の場合は何も加算する必要が無いので注意が必要です。

締日5日で検証
締め日 日付 請求月
5日(2017/10/06〜2017/11/5) 2017/10/25 11月締
5日(2017/10/06〜2017/11/5) 2017/10/4 10月締
締日10日で検証
締め日 日付 請求月
10日(2017/10/11〜2017/11/10) 2017/10/9 10月締
10日(2017/10/11〜2017/11/10) 2017/10/31 11月締
締日15日で検証
締め日 日付 請求月
15日(2017/10/16〜2017/11/15) 2017/10/9 10月締
15日(2017/10/16〜2017/11/15) 2017/10/31 11月締
締日20日で検証
締め日 日付 請求月
20日(2017/10/21〜2017/11/20) 2017/10/9 10月締
20日(2017/10/21〜2017/11/20) 2017/10/24 11月締
締日25日で検証
締め日 日付 請求月
25日(2017/10/26〜2017/11/25) 2017/11/10 11月締
25日(2017/10/26〜2017/11/20) 2017/11/28 12月締

良さそうですね。締日よりも先の日付(締日は含まない)だったら、1ヶ月先が請求月。正しい気がします。

というわけで、締日集計のSQLの改訂版です。

SELECT
	CASE 
	  WHEN duedate = 31 THEN DATE_FORMAT(billdate,'%Y年%m月')
	  WHEN duedate < 31 AND DAY(billdate) <= duedate THEN DATE_FORMAT(billdate,'%Y年%m月')
	  WHEN duedate < 31 AND DAY(billdate) > duedate THEN DATE_FORMAT(DATE_ADD(billdate, INTERVAL 1 MONTH),'%Y年%m月')
	END AS monthly,
	duedate,
	sum(subtotal)
FROM
	earnings
	JOIN customers ON customers.id = earnings.customer_id
WHERE
	duedate > 0
GROUP BY
	monthly,duedate
ORDER BY
	monthly,duedate

念のため、締日での請求月の算出動作確認用SQLも貼り付けておきます。

SELECT
	billdate, -- 伝票日付
	duedate, -- 顧客の締日(1〜31)
	CASE 
	  WHEN duedate = 31 THEN DATE_FORMAT(billdate,'%Y年%m月') -- 末締は伝票月=請求月
	  WHEN duedate < 31 AND DAY(billdate) <= duedate THEN DATE_FORMAT(billdate,'%Y年%m月') -- 末締以外で、伝票日 <= 締日なら、伝票月=請求月
	  WHEN duedate < 31 AND DAY(billdate) > duedate THEN DATE_FORMAT(DATE_ADD(billdate, INTERVAL 1 MONTH),'%Y年%m月')  -- 末締以外で、伝票日 > 締日なら、伝票月=請求月の翌月
	END AS monthly
FROM
	earnings
	JOIN customers ON customers.id = earnings.customer_id
WHERE
	duedate > 0
GROUP BY
	billdate,duedate

2017年度の振り返り

自分一人が食っていく分には困らないけど、事業を作っていくのは本当に大変だなと改めて実感。サービスを販売して軌道に乗せるためには、年商1000万は無いとだめ。年間100万で売るなら10本、年間1万円にするなら100本売らないと販管費や広告宣伝費等を含めるとペイしない。ここまで育て上げるのに使うお金は、1000万では効かない。自分の人件費等を含めたら。

業務委託で自分のスキルを売るのが、手っ取り早く売上になって定期的にお金が入ってくるので、すごく楽です。僕自身も(かなり自由にやらせて頂いてますが)お世話になっている。スキルがあれば買ってくれる人は、どこかしらにいる。スキルの切り売り感は個人的には全く無いし、何かしら新しい発見があるよね、問題意識さえ持っていれば。いつまでもこのままお世話になるのは嫌だけど、生命維持装置のようなもので、なかなか外すことが困難。

サービスを販売するための原資や時間を捻出する為に、自分のリソースを売りながらチャンスを掴もうとしている。ニーズがない商品を作ってもしょうがないので、ニーズを探すために色んな所へ顔を出した。

チャンスの女神の前髪が見えてきたのが、2017年度のハイライト。SIビジネスを変えうるお話を頂いたし、昔から諦めきれない販売管理でも多くの引き合いを頂いた。2018年はシーズを育ててソリューションへと結実させて新しいステージへと進む。

前途洋々、頑張れおれ。

Androidでバーコードリーダーを作る

Andoridでは、だいたい2年ぐらい前からGoogle公式でバーコードをカメラで解析するAPIが搭載されています。

Barcode Detection with the Mobile Vision API

このAPIがクセがあって「カメラに写っているバーコード全てを認識できる」というものになっています。それはそれですごいのだけれども、「ここが読取り範囲だよ」という範囲を指定して単一のバーコードしか読まないようにする事が出来ない。

そこを制御してくれるのが、下記のGitHubのライブラリでした。導入すごい簡単でした。
github.com

Android6.0以上から、実行時にカメラへのアクセス許可が必要になった。そのハンドリングをするコードを書かないと動かないので、ご注意を。

Prismに慣れてきた。これがMS標準だったらなぁ・・・

WPFアプリ構築の敷居もグッと下がったと思う。このライブラリがMicrosoft標準でないというのが、WPFの悲劇なのでは。

Prismを使ってWindowsのデスクトップアプリを作っているのですが、だいぶ慣れてきた。かずきさんのレポジトリを見て写経すればまずOK。MVVMのやり方と画面遷移のあり方が書いてあるので、その2つがあれば生きていける。Prismはよく出来ているライブラリですわ。

github.com

XAMLが好きになってきた

最初はなんだよこのハイパー拡張HTMLはと思いましたが、XAMLに慣れてしまうとWindowsフォームで画面を作るのは無茶苦茶だるくなりました。テキストで全部が表現されているのは正義だ。似たような画面作るのすごい楽。ViewModelのインスタンスはDIしてくれるのも楽なんだよな〜

画面遷移が便利すぎて感動

Region機能を使わないでPrismでアプリを使うことはありえない。今回、こいつがすごく便利でびっくりしている。

HTMLのコーディングと一緒で、MainWindowにヘッダーとフッターだけは共通化して定義。あとは「Contents」みたいな感じにして、そこだけPrismからユーザーコントロールを当て込む。これだけで生きていけるようだ。

かずきさんのレポジトリにあるように、PrismのUnityBootstraperの機能で画面とViewModelが予め全て初期化された状態でアプリが起動してくれるので、画面遷移した時は遷移前の状態が残る。残ったほうが便利なんだよね、間違いないんだよね。

ダイアログを起動してコールバックをするというのは、IntercationRequestなるものを使えばいける。YES/Noも取れるし、画面を起動する時にその都度パラメーターを与えてバインドさせることもできる。任意の型のパラメーターを受け渡すことができる。

ダイアログではなく、Contentsページの切り替えも簡単にできる。当該画面が画面遷移によって表示される時と、当該画面が遷移によって消える時の両方にコールバック関数を定義することができるし、任意の型のパラメーターの受け渡しもいける。

RegionManagerクラスのインスタンスは、任意のViewModelのプロパティにDIすることができる。親画面のメニューをクリックして画面遷移、データグリッドをEnterひっぱたいて画面遷移の2パターン等が多いと思いますが、この2つは別のViewModelですよね。でも、PrismがDIしてくれることで、同じインスタンスがSingletonで引き回せるため、どのVMからも画面操作が可能。これは便利。

5年ぐらい前のWindowsフォームで作ったシステムのように、PanelにAddControl/RemoveControlしていた原始的なやり方に比べると、近代的で大変すばらしい。

コードビハインドからのVM

DataContextプロパティをDIされたViewModelにキャストすれば、どうとでもなる。

ただ、ほとんど使わない。Commandプロパティにバインドできるし、KeyイベントもInputBindingsを使えばどうとでもバインドできる。コードビハインドを余儀なくされたのは、任意の画面のコンポーネントにフォーカスを移す時。それ以外は殆どCommandをバインドすればいけるんじゃないかな。ViewModelを継承すると、更に便利。イベントは一緒だけど処理が違う(コマンドの中身が違う)という差分だけ実装すれば、生きていける。

DataGridの編集可能カラムで発生するキーイベントについては、コードビハインドで書いた。ViewModelとは関係なく任意のロジックを持っているクラスに処理を投げたかったので。

引き続き頑張ります

まだ5画面ぐらいしか作ってない。本番では60画面ぐらいになる予定なので、重くないかどうかはとても気になっている。でも、Prism+WPFの組み合わせは最高に便利だし、生産性も高い。引き続き頑張るぞい。

デスクトップアプリは、一周回ってWPFでいいじゃんと思った話

aroundthedistance.hatenadiary.jp

これは、2016年になってから前職の内製したWindowsフォームの密結合過ぎるデスクトップアプリを、WPFで刷新している時に書いた記事です。画面がレスポンシブじゃないのを除けば別にWPFやる必要なくねって書いていました。できること同じじゃん、っていう。

が、この時から1年半。もう1回WPF(with Prism)に向き合ってみると、PrismのようなMVVMライブラリを活用できなかった自分がアホやっただけなのでは、という気持ちがドンドン大きくなってきました。

Windowsフォーム→WPFに焼き直すメリットがあまり無いのも事実でしょうが、新規アプリでWindowsフォームを採用するメリットも特に無いわけです。

レイアウトを作るのはWPFの圧勝

改めて思いました。XAMLで画面デザインできることに慣れると、Windowsフォームはだるくてしょうがない。

Windowsフォームは絶対配置が基本ですが、TableLayout的なコントロールを使えば相対配置が可能です。でも、カラムに置くことができるコントロールが1個しか無い。この時点でかなりきつい。Dock/Anchorを活用すれば画面サイズの変更に伴って要素のサイズも変動できるけど、ちまちますべての要素にそれを設定するのはだるすぎた。

XAMLやリソース定義したスタイル設定、強力なGridLayoutやPanelコントロールになれてしまうと、戻れないですね... UI回りのカスタマイズが本当にやりやすくなっている。

MVVM、やっぱりイイ...

1年半前はPrismよくわかんないで素のMVVMやってたけれど、挙動が安定しなかった。Prismを勉強(GitHubのサンプルレポジトリの写経)してみると、思いの外簡単だった。DIでViewModelをインジェクションしてくれるの、すげー楽。ライフサイクルの管理もしなくて良いので、大変助かる。

MVVMで業務系アプリ作っていて、問題となるのはこの辺だと思う。

  • キーイベントとCommand対応
  • 親画面⇔子画面連携
  • 画面遷移
  • ViewModelの共存

KeyBindingsって機構があればキーイベントに対してコマンドが実行できる事を知った。これが上手くできれば、コードビハインドを書く必要が全くといいほど無い。残りの3つは、Prismがサポートしてくれる。ViewModelにViewの状態を集約できれば、似たような画面が多い業務系のアプリでは再利用性が高くなるので、MVVMを活用しない手は全く無い。

WindowsフォームもMVVMできるんだろうけど、データバインディングWPFに軍配が上がる。

フォーカス制御だけはどうしようもない

これはWPF/Windowsフォームどっちも一緒。フォーカスが正しく制御されるUIに慣れてしまった人は、もうマウスクリックには戻りたくはない。そら、そうやね。入力はマウスレスが理想。検索・集計・出力系は、右クリックメニューが活躍するので、マウスレスはそこまで必要とされていない感触。

フォーカス制御する場合は、画面のコンポーネントの名前解決ができないといけないので、Region/Moduleと言った疎結合を実現するPrismの恩恵に預かることは困難だけど、それ以外はすごく楽。画面遷移や遷移時の値の引き継ぎも簡単にできる。Prism、いいっすね~。はじめから使っていればと悔やまれる。

結論: WPF悪くない

WPFはMVVMを簡素化してくれるライブラリを活用すれば、Windowsフォームにはない快適さがあることも事実でした。年内一杯、WPFでデスクトップアプリ作りまーす。