俺、サービス売って家買うんだ

Swift, Kotlin, Vue.js, 統計, GCP / このペースで作ってればいつか2-3億で売れるのがポっと出来るんじゃなかろうか

1時間で終わる、圧倒的「確定申告」の方法

f:id:ie-kau:20170411113715p:plain


10代の頃からウェブサイトの広告収益をちまちま貰っては、確定申告で地獄を見てきたHayatoです。 今回は、最近よく聞かれる確定申告の流れをまとめておこうと思います。

サラリーマンとして普通に働きつつ副業で収入を得ている人が初めての確定申告をするところを想定して書いてます。

Index

  • 確定申告とは何か?
  • 対象者は?
  • 年間スケジュール
  • やっておくと便利な下準備
  • 経費の面倒じゃない集め方
  • 確定申告書を作って提出しよう
  • よく聞かれるQ&A

確定申告とは

ざっくりいうと、どれくらい収入があったかを国に伝えることです。 サラリーマンの場合は会社がやってくれるのですが、自分で稼いだ場合は自分で申告する必要があります。
稼ぐのに必要だった経費(交通費・交際費など)を稼いだ額(売上)から引くことで、収入を計算します。

確定申告の対象者は?

年間20万円以上の収益が、勤めている会社以外からある人は全員確定申告の義務があります。年間500万円を超えたあたりくらいからは税理士にお願いした方がいいような気もしますが、それより少なければ自分でやっても問題ないと思います。

確定申告の年間スケジュール

確定申告は2月15 - 3月15日の間に、税務署に収支を証明する確定申告書を提出することで完了します。期間は前年の1-12月の1年間です。 基本的なサイクルはわりかし簡単で僕の場合は、

  • 1~12月に5000円以上の領収書をとっておく(適宜)
  • 2月にマネーフォワードで帳簿を作成して提出する(1時間くらい)

と言った感じです。

確定申告前の下準備

確定申告が初めての方は、下準備から始めましょう。これをやっておけば2月の帳簿作成が1時間で終わりますが、やっておかないと100時間かかるか税理士にお願いするハメになります。

1.青色申告申請書を税務署に提出

税務署に事前に承認を受けないと青色申告はできません。
といっても5分で書き終わる書類を、自分が住んでいる地域の税務署に提出するだけです。国税庁のHPからPDFをダウンロードして、記入すればOKです。
半休を使ってなるべく早めに申請しておきましょう。

2. 専用の口座とクレジットカードを用意しておく

プライベートの口座・クレジットカードを使うのもよいのですが、分けておくと帳簿を作る時に便利です。
最近は会計ソフトの無料化&便利化しているので、半自動で帳簿や確定申告書を作成できます。が、プライベートの口座と混ざっていると仕分けが非常に面倒なので、出来れば専用の口座とクレジットカードを用意しておくと便利です。
僕はANAのSuicaオートチャージ付きクレジットカードと、SBI銀行を使っています。(後述する旅費交通費を仕分けする時に楽するため)

3. 無料で使えるMFクラウドにアカウント登録しておく

マネーフォワードクラウドを使えば自動で確定申告書と帳簿をつけてくれます。無料で提出書類まで作成できるのでおすすめです。 アカウント登録したら、上記で作成した口座とクレジットカードを紐付けておけば、あとは半自動で帳簿作成が完了します。1時間くらいで終わるので、放置しておいて申告期間中に帳簿を作成しましょう。 *ちなみにFreeeは個人事業主の場合あんまり使えない感じです。

経費の面倒じゃない集め方

最初は経費をどのように申請したり集めればいいか、どのように仕分けしてよいかわからないと思います。そんなときは、細かく全部の領収書をとっておくよりも、5000円以上の領収書だけ取っておく、とすると仕分け時の手間が激減します。 また、実際に経費として扱うのは

  1. 旅費交通費:電車賃とかタクシーとか飛行機とか
  2. 交際費:会食などから発生する飲食店での費用
  3. 図書新聞費:Amazonとかで買った本とか

の3つくらいに絞るのが良いかと思います。ウェブサイトを運営しているときのサーバー代やドメイン代は、少額で面倒なので経費申請してないです。 また、 専用のクレジットカードで支払うことを徹底すると、後の作業がめちゃめちゃ楽になります。Suicaのオートチャージを使えばもっと楽になります。 ちなみに一つ一つの経費に理由を書いたりとかはしなくて大丈夫ですw(なぜかこれ結構聞かれる)

毎年2月に確定申告しよう

さあ、1年間ためてきた領収書(レシート)の出番ですね。マネーフォワードで確定申告書と帳簿を作成しましょう。

1. 源泉徴収票を用意する

確定申告時は会社からの給与+副収入を足して、合計収入を税務署に報告します。「会社からの給与」を証明するのに、毎年12月にもらう源泉徴収票を用意しましょう。なくしたら会社に再発行してもらえます。

2. クレカと口座の履歴を仕分けする

口座の履歴は主に収入かと思います。こちらを「事業所得」に振り分けましょう。 クレカの方は主に支出可と思います。こちらを「旅費交通費・交際費・図書新聞」に振り分けていきましょう。 この「振り分け」はやる前にはピンとこないですが、ラベルを付ける程度なので非常に簡単です。マネーフォワードに感謝しましょう。

3. 税務署に行って提出する

仕分けが終わったら、マネーフォワードから確定申告書類をPDFでダウンロードしましょう。そちらを印刷して、源泉徴収票と一緒に税務署に持っていけば完了です。半休の申請を忘れずに。

以上です。割りと雑に確定申告をすること、出来るだけ帳簿作成を自動化すること、が楽するコツかと思います。
売上をごまかすのは犯罪ですが、微妙に経費かどうかわからないものや、経費なんだけど金額が大したことないものを自腹にしちゃうことでだいぶ楽になります。
あとは、マネーフォワード&専用クレカ&口座は割りと完璧なコンボだと自画自賛ですね。

よく聞かれるQ&A

青色申告が良いの?白色申告が良いの?

今や手間はあんま変わらないので青色申告しましょう。

家賃とか光熱費やインターネット費用を経費にできる?

できます!しかし100%ってのはなかなかイメージわかないので20%くらいで申請するのが良いのではないでしょうか。(例:家賃10万円だったら、2万円*12ヶ月)。しかし「旅費交通費・交際費・図書新聞」で賄えるのであればそれに越したことはないかと思います。

確定申告したらお金を払わなきゃいけないの?

場合によります。極端な話、売上 - 経費がマイナスだった場合は払う必要はないですし、割と個人事業主の人はそういう努力をしているイメージがあります(あくまでイメージ)

会社にバレない確定申告方法

残念ながら絶対バレない、というのは無いです。が、確定申告時に住民税の徴収方法の選択という項目があり、「給与から天引き」または「自分で納付」を選択できます。「自分を納付」を選択することで、少なくとも住民税の増減による会社バレを防ぐことはできます。確定申告時に忘れずに「自分で納付」を選択しましょう。

マネーフォワードの回し者ですか?

いえ、違います。

オラッ!オラオラオラ!

Sirenを使って iOSアプリに強制アップデート実装するよ

f:id:ie-kau:20151120074851p:plain


ハロー、こんにちは @hazumu です。 iOS App作ってますか?
アプリをの開発に置いてサーバー側のAPIとアプリのバージョンを合わせるのって結構だるいですよね。
そんなときはAPIのバージョンをたくさん用意するのも面倒なのでアプリ側に強制アップデートを導入するケースって多いのではないでしょうか??

とはいえ、中小規模のアプリで「強制アップデートの実装なんかに時間はかけたくない。。」って気もします。
今回はそんなニーズにお答えするライブラリを紹介します。

Siren

いま開発しているアプリではSirenというライブラリを導入しています。
語源は、救急車の上についているサイレンなんですかね?それともギリシャ神話のセイレーン??そこはよくわかりません。

github.com

何ができるか?

Sirenは数行記述するだけで、アプリ起動時にiTunes StoreのAPIをlookupして現在のbundleにかかれているバージョンがiTunsストアで配信されているバージョンより古い場合にアップデートのアラートを出してくれます。

アラートのパターンは3パターンで。

  • 強制アップデート
  • 強制アップデート or 次に開いたときにする
  • 強制アップデート or 次に開いたときにする or スキップする

というのが選べます。

強制アップデートを選んだ場合に表示されるのはこんなメッセージ。

f:id:ie-kau:20170409020909p:plain

多言語化対応もしてありもちろん日本語でアラートを表示できます。
そして、更新ボンタンをタップするとiTuns Store に遷移します。

実装方法

実装は簡単で、AppDelegateに以下のコードを記述するだけ。 これだけであら不思議強制アップデートの実装が終わっちゃいます。

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        
        // 強制アップデート
        let siren = Siren.shared
        siren.forceLanguageLocalization = SirenLanguageType.Japanese  // 日本語
        siren.alertType = .force  // 強制アップデートしか選択肢が無いオプション
        siren.checkVersion(checkType: .immediately)  // .Daily or .Weekly とかチェック頻度を設定できる
        
        return true
    }

余談

アプリのバージョンってどこから引いてきてるんだろうって思い、Sirenの内部実装を読んでわかったのですがiTunes StoreってbundleIdを元にアプリのバージョンを引いてくるURLを提供してるのですね。そっからスクリーンキャプチャとかバージョンとか説明を引いてこれるっぽいです。

https://itunes.apple.com/lookup?bundleId=

まとめ

強制アップデートとかはさくっと入れてユーザーの利用価値を向上できるところに時間をさいていきまっしょい!
季節も暖かくなってきましたし、夜は短し歩けよ乙女!!!(๑˃̵ᴗ˂̵)وオラッオラッ!!

Homebrewでインストールした MySQL の my.cnf の置き場所と一般ログの設定方法

f:id:ie-kau:20170407132034p:plain


my.cnf の置き場所

手持ちのMacにHomebrewでMySQLををインストールした場合にどこにmy.cnfを置けばよいか調べたのでメモ。 ただインストールするだけでは、ここ↓には設置されない。

/etc/my.cnf

以下のコマンドを叩いて設定を見る。

mysql --help
Default options are read from the following files in the given order:
/etc/my.cnf /etc/mysql/my.cnf /usr/local/etc/my.cnf ~/.my.cnf

とのことらしいので、素直に /etc/my.cnf に設置してやればOK!

一般ログの設定

実行されているクエリなどを見るために一般ログを流す。
ログファイルのパーミッションに気をつけつつ、my.cnfにこんな感じで記述しておけば良い。

[mysqld]
general_log=1
general_log_file=/var/log/mysql/general-query.log

Xcode8.3, Swift 3.1にアップデートしたメモ

f:id:ie-kau:20151120074851p:plain


概要

先程Xcode のバージョンをアップデートしたのでその時に対応したことのメモです。

いつも通りXVimが起動しない

既にissueが上がっており対応してくれてた人がいるのでその方のリポジトリにあるコードで一旦対応。 本家にマージされたらまた乗り換えよう。

issue

github.com

これを使わせていただく

github.com

# 昔のものを削除
cd ~/XVim
make uninstall
rm -r ~/XVim

# いつも通りcondesign
sudo codesign -f -s XcodeSigner /Applications/Xcode.app  

# 入れる
cd ~
git clone https://github.com/keith/XVim.git
git fetch origin xcode-8.3-release
git checkout xcode-8.3-release
make

Swiftの修正

2系→3系のときと違い破壊的変更は無いのでほとんど何も変わらない。 自分がいじっているプロダクトでいうと以下だけ変更

-   var delegate: Reportable  & Linkable!
+   var delegate: (Reportable & Linkable)!

複数のプロトコルを継承する際にカッコで囲む必要がでた。

Swiftで左から右にグラデーションがかかるViewを作る

f:id:ie-kau:20151120074851p:plain


連日のSwiftメモです。

作りたい物

こんな感じで左から右にグラデーションがかかるボタンを作ります。

f:id:ie-kau:20170323192947p:plain

ソースコード

buttonViewがグラデーションをかける対象のViewとなります。

 //グラデーションの設定
let gradientLayer = CAGradientLayer()
gradientLayer.frame = buttonView.bounds
let color1 = UIColor.hexStr(hexStr: "#a6a1ff", alpha: 1.0).cgColor as CGColor
let color2 = UIColor.hexStr(hexStr: "#f871d0", alpha: 1.0).cgColor as CGColor
gradientLayer.colors = [color1, color2]
gradientLayer.startPoint = CGPoint(x: 0.0, y: 1.0)
gradientLayer.endPoint = CGPoint(x: 1.0, y: 1.0)
buttonView.layer.insertSublayer(gradientLayer,at:0)
buttonView.layer.cornerRadius = 5
buttonView.clipsToBounds = true

これでOK! 16進数のカラーコードを扱うextensionを用意していますが、色はどう指定しても構いません。

Swift で○○秒前みたいにするエクステンション

f:id:ie-kau:20151120074851p:plain


こんにちは、はやとです。最近はSwiftばっかりやってて楽しいです。 Swift3.0で、SNSでよくある「○○秒前」みたいに表示するエクステンションを書いたので置いておきます。

環境

  • Swift3.0
  • extensionで実装 (Dateクラスを拡張)

コード

Date.swift

import Foundation
import UIKit

extension Date {
    func timeAgoSinceDate(numericDates:Bool) -> String {
        let calendar = NSCalendar.current
        let now = NSDate()
        let earliest = now.earlierDate(self as Date)
        let latest = (earliest == now as Date) ? self : now as Date
        let components = calendar.dateComponents([.minute , .hour , .day , .weekOfYear , .month , .year , .second], from: earliest, to: latest as Date)
        
        if (components.year! >= 2) {
            return "\(components.year!) 年前"
        } else if (components.year! >= 1){
            if (numericDates){
                return "1 年前"
            } else {
                return "去年"
            }
        } else if (components.month! >= 2) {
            return "\(components.month!) ヶ月前"
        } else if (components.month! >= 1){
            if (numericDates){
                return "1 ヶ月前"
            } else {
                return "先月"
            }
        } else if (components.weekOfYear! >= 2) {
            return "\(components.weekOfYear!) 週間前"
        } else if (components.weekOfYear! >= 1){
            if (numericDates){
                return "1 週間前"
            } else {
                return "先週"
            }
        } else if (components.day! >= 2) {
            return "\(components.day!) 日前"
        } else if (components.day! >= 1){
            if (numericDates){
                return "1 日前"
            } else {
                return "昨日"
            }
        } else if (components.hour! >= 2) {
            return "\(components.hour!) 時間前"
        } else if (components.hour! >= 1){
            if (numericDates){
                return "1 時間前"
            } else {
                return "1 時間前"
            }
        } else if (components.minute! >= 2) {
            return "\(components.minute!) 分前"
        } else if (components.minute! >= 1){
            if (numericDates){
                return "1 分前"
            } else {
                return "1 分前"
            }
        } else if (components.second! >= 3) {
            return "\(components.second!) 秒前"
        } else {
            return "ついさっき"
        }
        
    }
    
}

で、ViewControllerとかで

let dateString = Date() //現在時刻
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ" //フォーマット合わせる
let posTimeText:String = formatter.date(from: dateString!)!.timeAgoSinceDate(numericDates: true)

ExpressでGoogle Cloud Storageに画像をアップロードする

f:id:ie-kau:20170222235628j:plain

やりたきこと

  • Expressで稼働しているWebサービスで画像をGoogle Cloud Storage(以下GCS)にアップロードする
  • サブドメインで画像を閲覧できるようにする

各種バージョン

Node 7.x
Express 4.x

事前準備

以下は完了している

  • ドメインは取得
  • Google Cloud DNSの設定

1. image.[ドメイン].comというサブドメインを設定する

Google Cloud DNSでCNAMEレコードに該当サブドメインを追加します。

f:id:ie-kau:20170222235440p:plain

2. ドメインの所有権の認証をする

ドメイン名を持つバケットを作成するには、GCPにログインしている管理者が対象のドメインの所有者であることを確認しておかなければなりません。

ドメインを持つバケットを作成できるユーザー

ドメインに確認済みのオーナーが複数いる場合、これらのオーナーのみが名前にドメインを使用したバケットを作成することができます。ドメインに確認済のオーナーがいない場合、確認済のウェブサイト オーナーが名前にドメインを使用したバケットを作成することができます。少なくとも 1 人の確認済のオーナーがドメインまたはウェブサイトに含まれていないと、名前にドメインを使用したバケットを作成することはできません。ドメイン所有権はウェブサイト所有権よりも制御レベルが高いため、ドメイン名を持つバケットを作成できるユーザーをサイトで厳しく制御する必要がある場合に役立ちます。

たとえば、「example.com」というサイトの管理を担当する IT スタッフ メンバーが 2 人いるとします。必要な確認(以下を参照)を完了すると、「example.com」、「reports.example.com」、「downloads.example.com」など、ドメイン名を持つバケットを作成できるユーザーはその 2 人のみになります。

確認済のウェブサイトまたはドメイン オーナーは、Search Console を使用してウェブサイトまたはドメイン オーナーを追加することができます。Search Console のダッシュボードで、管理するウェブサイトを検索し、[プロパティの管理] > [ユーザーを追加/削除] を選択します。ドメイン所有者を追加するには、[プロパティ所有者の管理] リンクを選択します。ドメイン所有者は、他のドメイン所有者を追加することができます。

※引用元
Domain-Named Bucket Verification  |  Cloud Storage Documentation  |  Google Cloud Platform

これは引用にあるようにSearch Consoleから対応できます。

3. Google Cloud Platform API に利用する鍵の作成

「IAMと管理」の「サービス アカウント」でGCPのAPIを利用するサービスアカウントと鍵を作成します。

f:id:ie-kau:20170222235332p:plain

ここで作成したjsonの鍵はバケットにファイルをアップロードする時に利用します。

4. GCSのバケットを作る

利用したいサブドメインと同じバケット名でバケットを作ります。 ドメインの所有権が確認されてない場合はここでエラーになります。

f:id:ie-kau:20170222235225p:plain

Expressの設定

利用するモジュール

ソースコード

const express = require('express')
const router = express.Router()
// -- その他色々 -- 
const multer  = require('multer')
const storage = require('@google-cloud/storage')
const gcs = storage({
  projectId: '[projectId]',
  keyFilename: '[/path/to/key.json]' // 事前準備3で作成した鍵
})

const bucket = gcs.bucket('image.[ドメイン].jp')
bucket.acl.default.add({
  entity: "allUsers",  // 外部アクセスを可能にする
  role: storage.acl.READER_ROLE
}, err => {})

const imageUploader = multer({
  storage: multer.memoryStorage(),
  limits: {
    fileSize: 10 * 1024 * 1024  // ファイルサイズ上限
  }
}).single('image')

router.post('/upload', (req, res, next) => {
  imageUploader(req, res, err => {
    if (err) {
      var error = null
      switch(err.code) {
        case 'LIMIT_FILE_SIZE':
          error = {code: 1111, message: 'ファイルが大きすぎます'}
          break
        default:
          error = err
          break
      }
      next(error)
      return
    }

    const mimetype = req.file.mimetype
    const type = [
      { ext: 'gif', mime: 'image/gif' },
      { ext: 'jpg', mime: 'image/jpeg' },
      { ext: 'png', mime: 'image/png' }
    ].find(ext => {
      return ext.mime === mimetype
    })

    if (!type) {
      next({code: 1111, message: "不正なファイル形式です"})
      return
    }

    const blob = bucket.file(`uploads/${req.file.originalname}`)
    const opts = { 
      metadata: { 
        contentType: mimetype,
        cacheControl: "public, max-age=300"
      } 
    }
    const blobStream = blob.createWriteStream(opts)

    blobStream.on('error', err => {
      next(err)
      return
    })

    blobStream.on('finish', () => {
      const publicUrl = `http://${bucket.name}/${blob.name}`
      res.json({ok: 1, imageUrl: publicUrl})
    })

    blobStream.end(req.file.buffer)
  })
})

コードは端折りましたがこれで image.[ドメイン].jp のバケットの uploadsというフォルダにファイルをアップロードすることができました。

課題

  • GCSでサーブした場合のSSL対応はどうすればいいのか?