GAミント至上主義

Web Monomaniacal Developer.

このブログについて(先頭固定)

【お約束】 投稿内容は個人の見解であり、所属する(していた)組織の公式見解ではありません。

名前:uyamazak(昔いた会社で上司が開発用Linuxサーバーのユーザー名に「yuyamazaki」が長いので勝手に作ってくれた。読み方わからないけどウヤマザク)

高校あたりからWEBサイトをやったり趣味の延長でWEB系を仕事にした感じの人間。

2020年1月から株式会社シニアジョブにジョイン。
まだ開発者の社員は2人、これからの会社なのでインフラからきれいな設計をしたい

アズールレーン@竹敷でモバイルのUI、UX研究中(初嫁ジャベリン)

GitHub: https://github.com/uyamazak/

これまでの主なプロジェクト

続きを読む

Firebase deploy時のエラー Error: HTTP Error: 404, Requested entity was not found.

久しぶりにFireabse(WEB)のアプリをデプロイしようとしたら

このエラーが出た。詳細がなく分かりづらい。

Error: HTTP Error: 404, Requested entity was not found.

アップロードするファイルがないとか、変化がないとか疑ったけどそんなとこはなく
そういえば、複数サイト機能をつかっていたことを思い出した。

firebase.google.com


このtarget設定コマンドが必要だった。

firebase target:apply hosting target-name resource-name

target-nameは任意に決められる値、 resource-nameはFireabse Hostingで表示されているサイトのID?を使った。
変えてしまうとわかりにくので同じledeightにした。

firebase target:apply hosting ledeight ledeight

firebase.json側も同じ値を指定する必要がある。キー的にtarget-nameを入れるんだろうけど、同じにしたので間違いがない。

firebase.json

  "hosting": {
    "target": "ledeight",
    "public": "led8vue/dist",
    "ignore": [

デプロイターゲットの概念、設定完全に理解できてない。複数サイト機能はいいと思うけど、もうちょっとシンプルにできないものか。
デプロイ ターゲット  |  Firebase

Firebaseの機能でGitHubと連携するだけでNetfilyレベルで簡単にCIできるものがあればいいなぁと思った。Cloud Buildもあるんだし。

実践Firestore (技術の泉シリーズ(NextPublishing))

実践Firestore (技術の泉シリーズ(NextPublishing))

  • 作者:福田 雄貴
  • 発売日: 2020/02/28
  • メディア: オンデマンド (ペーパーバック)

PHPの公式Docker alpine環境でphpredisをインストールする(公式ドキュメント通り)

結論

公式ドキュメント通りpcelで入る。

FROM php:7.2-fpm-alpine

RUN pecl install redis && \
         docker-php-ext-enable  redis

git cloneとかいらない。

概要

Laravel 5.6から6にアップデートした際(自分はやってない)、推奨のRedisのクライアントがこれまで使ってたpredisから、phpredisに変更された。

「phpredis docker」でググるとdocker-php-ext-installで入れられないから、git cloneで取ってくるという内容の、ちょっと古い記事ばかり出てきた。

f:id:uyamazak:20200312192055p:plain

PHPオフィシャル Dockerコンテナに redis exension を追加するメモ - Qiita

PHPのオフィシャルDockerイメージにredis exensionを追加する方法 - Qiita

alpine版のPHP公式Dockerfileを使ってphpredisを入れる - Qiita


今の時代そんなわけなかろう、せっかくalpineでイメージサイズ小さくしてるにこれだけのためにgit入れたり消したりしたくないわ、と思いながらphpredis公式のドキュメントを読んでみた。

github.com

一番上に書いてあるインストール方法がこれ。

pecl install redis

冒頭のようにalpineのPHP公式イメージを元にしたDockerfileに書いたら問題なく動いた。

Laravelのドキュメント(公式の翻訳?)にもPECLで入れろって出てきてる。
Redis 6.x Laravel

LaravelでRedis使用するには、PECLを使用してPhpRedis PHP拡張をインストールすることを推奨します。

当時の状況は分からないけど、明らかによろしくない方法が書いてある記事ばっかり、しかもQiitaばっかり上位に出る状況はよろしくない。

同時期のstack overflowの記事は大丈夫だった。これがqiitaスパムか

How to install php-redis extension using the official PHP Docker image approach? - Stack Overflow


まず第一に公式ドキュメント。次にstack overflow。大切。

PHPフレームワークLaravel入門 第2版

PHPフレームワークLaravel入門 第2版

速習 Laravel 6 速習シリーズ

速習 Laravel 6 速習シリーズ

Vue.jsとPHPで給与計算処理を書いた

シニアジョブでは、派遣従業員の勤怠や給与計算まで自社のシステムでまかなっていますが、いろいろと足りない機能や問題があり、これまで多くが手作業で行われてました。
その作業をできるだけ無くすために、既存の機能と併存する形で新しく開発しました。

機能は大きく分けて、2つあります。
1、Vue.jsで総支給額や勤務時間から、残業代、深夜手当などの金額を算出する部分
2、PHP(Laravel)で、1で算出した金額と勤怠情報を組み合わせて月の給料を計算する部分

ただでさえ計算に必要な時間、金額など項目数が多い上に、月給、日給、時給でそれぞれ算出が微妙に異なったり、さらに「休日手当と残業代は重複しない」、「小数点以下の端数処理は労働者の不利にならないようにする」などのビジネスロジックドメイン知識が満載なので、開発者の設計力が試されるいいお題だと思います。

変数名などはfreee人事労務APIを参考にさせていただきました。
developer.freee.co.jp


今回は時間も限られ、根本的な設計し直しは今後控えている全面リニューアル時にするとして、既存の実装の上に追加という形で納得行っていない部分もありますが、覚えておきたいポイントをメモ。

1、契約金額の算出(Vue.js)

f:id:uyamazak:20200312102116p:plain

こんな感じの画面で、総支給額や労働時間等を入れるとそれぞれの値をいわば逆算し、DBに保存することで後述のLaravel側と連携します。
開発中のもので数字は適当です。
派遣会社特有の機能として、従業員に支払う給料と、派遣先への請求金額がそれぞれ分かれています。

単一のコンポーネントで十分そうだったのでVue CLIなどは使用せず、scriptタグでVue本体を読み込んで使ってます。
部分的な改修なのでLaravel Mixも使ってません。

公式ドキュメントでいうとCDNのやつ。
https://jp.vuejs.org/v2/guide/installation.html

なにげに業務でこの使い方するの初めてだったけどすぐ作れて便利だった。

Laravel→Vueへの連携 @jsonPHPの値をJSON

すでにDBに保存されているデータはLaravel側でView (Bladeを使用)に渡して@jsonでJSのオブジェクトにして使います。

<script>
const metaObject = @json($meta);
// いろいろ
</script>

ユーザー入力値以外はひたすらcomputedを使う

総支給額と、労働時間、固定残業時間などを入れたらあとは全部計算で決まるのでcomputedの出番です。
実際のコードはこんな感じ。

  computed: {
    // 基本給:給与総額 - 固定残業代 円/月
    // 固定残業代と加算した場合、総支給額の端数が出ないように調整する
    basicPaymentAmount: function () {
        return this.calcAmountsBypayCalcType({
            'monthly': type => this.totalPaymentAmount[type] - Math.ceil(this.fixedOvertimePaymentAmount[type]),
            'daily': type => this.totalPaymentAmount[type] - Math.ceil(this.fixedOvertimePaymentAmount[type]),
            'hourly': null
        });
    },
    // 固定残業代:固定残業時間 × 法定内残業代 × 1.25 円/月
    fixedOvertimePaymentAmount: function () {
        return this.calcAmountsBypayCalcType({
            'monthly': type => this.fixedOvertimeHours * this.excessStatutoryWorkPaymentAmount[type] * 1.25,
            'daily': type => this.fixedOvertimeHours * this.excessStatutoryWorkPaymentAmount[type] * 1.25,
            'hourly': null
        });
    },
    // 法定内残業代:給与総額 ÷ (所定労働時間 + 固定残業時間 × 1.25) 円/時
    excessStatutoryWorkPaymentAmount: function () {
        return this.calcAmountsBypayCalcType({
            'monthly': type => this.totalPaymentAmount[type] / (this.monthlyNormalWorkHours + this.fixedOvertimeHours * 1.25),
            'daily': type => this.totalPaymentAmount[type] / (this.dailyNormalWorkHours + this.fixedOvertimeHours * 1.25),
            'hourly': null
        });
    },
    // 法定外残業代:法定内残業代 × 1.25 円/時
    overtimeExceptNormalWorkPaymentAmount: function () {
        return this.calcAmountsBypayCalcType({
            'monthly': type => this.excessStatutoryWorkPaymentAmount[type] * 1.25,
            'daily': type => this.excessStatutoryWorkPaymentAmount[type] * 1.25,
            'hourly': type => this.totalPaymentAmount[type] * 1.25
        });
    },
    // 休日手当:法定内残業代 × 1.35 円/時
    holidayWorkAllowancePaymentAmount: function () {
        return this.calcAmountsBypayCalcType({
            'monthly': type => this.excessStatutoryWorkPaymentAmount[type] * 1.35,
            'daily': type => this.excessStatutoryWorkPaymentAmount[type] * 1.35,
            'hourly': type => this.totalPaymentAmount[type] * 1.35
        });
    },
    // 深夜手当:法定内残業代 × 1.5 円/時
    lateNightWorkAllowancePaymentAmount: function () {
        return this.calcAmountsBypayCalcType({
            'monthly': type => this.excessStatutoryWorkPaymentAmount[type] * 1.5,
            'daily': type => this.excessStatutoryWorkPaymentAmount[type] * 1.5,
            'hourly': type => this.totalPaymentAmount[type] * 1.5
        });
    },

1.25とかの倍率は普通は定数化するところかも知れないけど、ぱっと見で倍率が分かるこの方がいいかなとあえてこのままにしてる。

可能な限りDRYを目指す。

月給(monthly)、日給(daily)、時給(hourly)で算出方法が異なったり、不要な場合があるのでここはそれぞれ書くしか無い。
でも給料(pay)と、請求金額 (invoice)は元になる金額は違うものの、計算式は同じ。

普通に書くとそれぞれ3給与制 × 2(給与・請求)* 9項目、計54の式 を書いて保守していくのはつらい。

ということで専用のメソッドをつくり、計算式をコールバックで渡すことで、共通化しました。
ここはreduceを使ったりして可読性が落ちてるけど、computedの方はシンプルになるのでいいかなと。

  methods: {
    /**
     * 請求、給料ごとに給与タイプ別のcallbackを計算してObjectでまとめて返す
     * @return Object {'invoice': number , 'pay': number}
     */
    calcAmountsBypayCalcType: function (callbacks) {
        if (callbacks[this.payCalcType] === null) {
            return {};
        }
        return this.calcAmounts(callbacks[this.payCalcType]);
    },
    /**
     * 請求、給料ごとに計算してObjectでまとめて返す
     * @return Object {'invoice': number , 'pay': number}
     */
    calcAmounts: function (callback) {
        return amountTypes.reduce((result, type) => {
            result[type] = callback(type);
            return result;
        }, {});
    },

2、月ごとの給与額の計算

勤怠情報と上記で設定した金額を元に月ごとの金額を算出します。
勤怠情報には日別で、出勤時間、退勤時間、休憩時間、出勤タイプ(休日出勤とか、欠勤とか)が入ってる感じ。
ここは今回は手をつけず既存のものをそのまま利用。

処理の流れとしてはこんな感じ。

  1. 日ごとに、いろんな時間を算出
  2. 月間合計の時間を算出
  3. 各設定金額と掛け算して残業代とかを算出

ちゃんと時間集計があっているか、デバッグ用、営業さんが確認できる用にこんな表を見れるようにしました。
f:id:uyamazak:20200312114653p:plain

min(), max()を活用

時間系の計算では、今までたまにしか使わなかったmin(), max()を大量に使うことになりました。

簡単な例で、早退&遅刻時間です。

例1として所定労働時間が8時間なのに、
総労働時間が7時間だったら
1時間遅刻か早退したなってことですね。

この例だったらこの式で問題ありません。

所定労働時間 - 総労働時間 = 早退&遅刻時間
8 -7 = 1時間

でも例2として、残業したとき
所定労働時間が8時間なのに、
総労働時間が9時間だったら

8 -9 = -1時間
  • 1が返ってしまいます。早退・遅刻はしてないんだから0が返ってほしいところ。

これをif分で書くといくつかパターンがありますが、一つはこんな感じになると思います。

早退&遅刻時間 = 所定労働時間 - 総労働時間 
if ( 早退&遅刻時間 < 0) {
  早退&遅刻時間 = 0;
}

でもここでmax関数を使うと

早退&遅刻時間 = max( 所定労働時間 - 総労働時間, 0); 

この1行で済みます。

例2のときは−1となり、max関数は大きい方を返すので0を返してくれます。

max( -1, 0); 
=> 0

簡単な例だとたいして恩恵はないんですが、深夜勤務時間の算出では下記ページで見つけた式に大変お世話になりました。

Excel for iPad:深夜勤務時間を求めるには

= "5:00" - MIN("5:00", 開始時刻) + MIN("29:00", 終了時刻) - MIN(MAX("22:00", 開始時刻), 終了時刻)

勤務時間のうち0時 - 5時か、22-29時(翌日の朝5時)の時間をこれだけの式で出してくれます。
これをif分で書こうとすると、出勤時間が5時より前か後か、退勤時間が22時以降か、29時前か・・・と考え始めて大変なことになりました。

今回の開発で改めて自分の数学・・・というか算数レベルの能力の低さを実感できました。

これからはじめるVue.js実践入門

これからはじめるVue.js実践入門