Skip to content

Commit

Permalink
Go powered seisan
Browse files Browse the repository at this point in the history
  • Loading branch information
snoozer05 committed Nov 6, 2015
1 parent 379a214 commit a8585ae
Show file tree
Hide file tree
Showing 7 changed files with 295 additions and 23 deletions.
29 changes: 6 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,14 @@ Seisan solution for small team.

## Installation

You need a few steps to setup seisan data repository. `example` directory in `enishitech/seisan` will be a good reference for you.

Put `Gemfile`:

```
source 'https://rubygems.org'
Compile from Source.

gem 'rake'
gem 'seisan'
```

Run bundler:

```shell
% bundle
% go get -u github.com/enishitech/seisan
```

Put `Rakefile`:
You need a few steps to setup seisan data repository. `example` directory in `enishitech/seisan` will be a good reference for you.

```
require 'seisan/task'
```
Create `data` directory to store data.

```shell
Expand All @@ -35,7 +21,7 @@ Create `data` directory to store data.
OK, now everything is set up. Run

```shell
% bundle exec rake seisan TARGET=2013/07
% seisan 2013/07
```

Then you will have an empty monthly report (because you have no record in seisan data) at `output/2013-07.xlsx`.
Expand Down Expand Up @@ -83,12 +69,10 @@ data
└── 08-shidara.yaml
```

Put `Rakefile` to your seisan data repository,

Then you can generate seisan report.

```shell
% bundle exec rake seisan TARGET=2013/07
% seisan 2013/07
```

## Contributing
Expand All @@ -101,8 +85,7 @@ Then you can generate seisan report.

-----

© 2013 Enishi Tech Inc.

© 2015 Enishi Tech Inc.

[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/enishitech/seisan/trend.png)](https://bitdeli.com/free "Bitdeli Badge")

39 changes: 39 additions & 0 deletions config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package main

import (
"io/ioutil"
"log"
"os"

"github.com/codegangsta/cli"

"gopkg.in/yaml.v2"
)

type Config struct {
Target string
Organization map[string]string
}

func (self *Config) mergeCliArgs(args cli.Args) {
self.Target = args.First()
}

func loadConfig(configPath string) Config {
var config Config

_, err := os.Stat(configPath)
if err != nil {
return config
}

buf, err := ioutil.ReadFile(configPath)
if err != nil {
log.Fatal("ERROR: ", err.Error())
}
err = yaml.Unmarshal(buf, &config)
if err != nil {
log.Fatal("ERROR: ", err.Error())
}
return config
}
8 changes: 8 additions & 0 deletions expense.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package main

type Expense struct {
Date string
Applicant string
Amount int
Remarks string
}
105 changes: 105 additions & 0 deletions expense_report.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package main

import (
"fmt"
"sort"

"github.com/tealeg/xlsx"
)

type ExpenseReport struct {
summary map[string]int
lines []Expense
}

func newExpenseReport(seisanRequests []SeisanRequest) *ExpenseReport {
report := &ExpenseReport{}
report.makeSummary(seisanRequests)
report.makeLines(seisanRequests)
return report
}

func (self *ExpenseReport) makeSummary(seisanRequests []SeisanRequest) {
self.summary = map[string]int{}
for _, seisanRequest := range seisanRequests {
for _, expense := range seisanRequest.Expenses {
if _, exists := self.summary[seisanRequest.Applicant]; exists {
self.summary[seisanRequest.Applicant] = expense.Amount
} else {
self.summary[seisanRequest.Applicant] += expense.Amount
}
}
}
}

type ByDate []Expense

func (a ByDate) Len() int { return len(a) }
func (a ByDate) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a ByDate) Less(i, j int) bool { return a[i].Date < a[j].Date }

func (self *ExpenseReport) makeLines(in []SeisanRequest) {
self.lines = []Expense{}
for _, seisanRequest := range in {
for _, expense := range seisanRequest.Expenses {
expense.Applicant = seisanRequest.Applicant
self.lines = append(self.lines, expense)
}
}
sort.Sort(ByDate(self.lines))
fmt.Printf("Processed %d expenses\n", len(self.lines))
}

func (self *ExpenseReport) renderSummary(sheet *xlsx.Sheet) {
var row *xlsx.Row
var cell *xlsx.Cell

row = sheet.AddRow()
cell = row.AddCell()
cell.SetValue("立替払サマリー")
row = sheet.AddRow()
for _, heading := range []string{"氏名", "金額"} {
cell = row.AddCell()
cell.SetValue(heading)
}
for key, value := range self.summary {
row = sheet.AddRow()
cell = row.AddCell()
cell.SetValue(key)
cell = row.AddCell()
cell.SetValue(value)
}
row = sheet.AddRow()
cell = row.AddCell()
cell.SetValue("")
}

func (self *ExpenseReport) renderLines(sheet *xlsx.Sheet) {
var row *xlsx.Row
var cell *xlsx.Cell

row = sheet.AddRow()
cell = row.AddCell()
cell.SetValue("立替払明細")
row = sheet.AddRow()
for _, heading := range []string{"日付", "立替者", "金額", "摘要", "備考"} {
cell = row.AddCell()
cell.SetValue(heading)
}
for _, detail := range self.lines {
row = sheet.AddRow()
cell = row.AddCell()
cell.SetValue(detail.Date)
cell = row.AddCell()
cell.SetValue(detail.Applicant)
cell = row.AddCell()
cell.SetValue(detail.Amount)
cell = row.AddCell()
cell.SetValue(detail.Remarks)
}
}

func (self *ExpenseReport) render(sheet *xlsx.Sheet) {
self.renderSummary(sheet)
self.renderLines(sheet)
}
29 changes: 29 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package main

import (
"fmt"
"os"
"path/filepath"

"github.com/codegangsta/cli"
)

func main() {
app := cli.NewApp()
app.Name = "seisan"
app.Usage = "Generate seisan report"
app.Action = func(c *cli.Context) {
if args := c.Args(); args.Present() {
config := loadConfig("config.yaml")
config.mergeCliArgs(args)

fmt.Printf("Processing %s ...\n", config.Target)
seisanRequests := loadSeisanRequests(filepath.Join("data", config.Target))
seisanReport := newSeisanReport(seisanRequests, config)
seisanReport.export()
} else {
fmt.Println("You must specify the 'TARGET'.\nExample:\n % seisan 2015/10")
}
}
app.Run(os.Args)
}
68 changes: 68 additions & 0 deletions seisan_report.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package main

import (
"fmt"
"log"
"os"
"path/filepath"
"strings"
"time"

"github.com/tealeg/xlsx"
)

type SeisanReport struct {
config Config
expenseReport *ExpenseReport
}

func newSeisanReport(seisanRequests []SeisanRequest, config Config) *SeisanReport {
report := &SeisanReport{}
report.config = config
report.expenseReport = newExpenseReport(seisanRequests)
return report
}

func (self *SeisanReport) renderReportHeader(sheet *xlsx.Sheet, name string) {
var row *xlsx.Row
var cell *xlsx.Cell

row = sheet.AddRow()
cell = row.AddCell()
orgName := fmt.Sprint(self.config.Organization["name"])
cell.SetValue(orgName + " 精算シート " + name)
row = sheet.AddRow()
cell = row.AddCell()
cell.SetValue("作成時刻")
cell = row.AddCell()
cell.SetValue(time.Now())
row = sheet.AddRow()
cell = row.AddCell()
}

func (self *SeisanReport) export() {
targetName := strings.Replace(self.config.Target, "/", "-", -1)

xlsx.SetDefaultFont(11, "MS Pゴシック")

file := xlsx.NewFile()
sheet, err := file.AddSheet("精算シート")
if err != nil {
log.Fatal("ERROR: ", err.Error())
}

self.renderReportHeader(sheet, targetName)
self.expenseReport.render(sheet)

destPath := filepath.Join("output", targetName+".xlsx")
if _, err := os.Stat("output"); os.IsNotExist(err) {
if err := os.Mkdir("output", 0777); err != nil {
log.Fatal("ERROR: ", err.Error())
}
}
err = file.Save(destPath)
if err != nil {
log.Fatal("ERROR: ", err.Error())
}
fmt.Printf("Wrote to %s\n", destPath)
}
40 changes: 40 additions & 0 deletions seisan_request.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package main

import (
"fmt"
"io/ioutil"
"log"
"path/filepath"

"gopkg.in/yaml.v2"
)

type SeisanRequest struct {
Applicant string
Expenses []Expense `yaml:"expense"`
}

func loadSeisanRequests(targetPath string) []SeisanRequest {
entries, err := ioutil.ReadDir(targetPath)
if err != nil {
log.Fatal("ERROR: ", err.Error())
}
requests := []SeisanRequest{}

for _, entry := range entries {
entryPath := filepath.Join(targetPath, entry.Name())
buf, err := ioutil.ReadFile(entryPath)
if err != nil {
log.Fatal("ERROR: ", err.Error())
}
var req SeisanRequest
err = yaml.Unmarshal(buf, &req)
if err != nil {
log.Fatal("ERROR: ", err.Error())
}
requests = append(requests, req)
}
fmt.Printf("Loaded %d files\n", len(entries))

return requests
}

0 comments on commit a8585ae

Please sign in to comment.