Python(Django) と AnyTree で 勘定科目の集計

## はじめに ##
　技術的なというより、実務的な使い方として、よく会計システム等にみられる様な、勘定の集計を行います。改良すべきところは多々ありそうですが、アイデアとしての備忘禄です。

## 発想と方針 ##
　製造業では、製造用の勘定を材料費、労務費、その他経費に分類して集計しますが、必要に応じて全体を集計・表示するときに、各科目の関係がTree構造になっていると便利です。
Pythonには、AnyTreeというライブラリがあったのでこれを使って集計・表示ができるようにします。
貸借対照表や損益計算書も同様のやり方でできると思いますが、生産管理システムにみられる様な製品構成をあらわす部品表などへの活用の方が使い勝手が良いかもしれません。
ここでは総製造費用を簡単なモデルとして集計します。因みに、総製造費用に期首仕掛品をプラスして期末仕掛品をマイナスすると、当期製品製造原価となります。当期製品製造原価は、売上原価の構成要素です。

## 完成したモノのイメージ ##
![キャプチャ.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/440325/4575c49a-bc39-469b-edd0-fc5dc00d07ac.png)

## 手順 ##
１．Pythonが使える状態で、djangoとAnyTreeをインストールする。
２．Djangoのプロジェクトとアプリケーションを作る。
３．DjangoのModel.py でデータ設計してmigrateする。
４．AnyTreeを使って勘定の構成と集計をするclassを作る。
５．views.pyから４の手順で作ったclassを使い、Html表示できるようにする。

## ライブラリのインストール（手順１） ##

```
>> pip install django
>> pip install anytree
```

## プロジェクトとアプリケーションを作る（手順２） ##

```
>> Django-admin startproject mysite
>> cd mysite
>> python manage.py startapp app
```

## コーディング（手順３～５） ##
#### models.py ####
費目構成と費目別の実績を記録するデータのひな型を設計します。
モデルが作成できたら、migrateします。

```python:models.py
from django.db import models

class KmkKosei(models.Model):
    cd= models.IntegerField(primary_key=True)
    mei= models.CharField(max_length=20, null=True, blank=True)
    oya= models.IntegerField()
    tai= models.CharField(max_length=1)
    jun= models.IntegerField(null=True, blank=True)

    def __str__(self):
        return self.mei

class BaseManager(models.Manager):
   def GetTkJsk(self, **kwargs):
       """
       検索にヒットすれば実績値のリストを返す
       """
       try:
           j= self.get_queryset().get(**kwargs)
           return j.get_tk_jsk()
       except self.model.DoesNotExist:
           return None

class KmkJsk(models.Model):
    bmn= models.IntegerField()
    cd= models.IntegerField()
    mei= models.CharField(max_length=20, null=True, blank=True)
    t1= models.IntegerField()
    t2= models.IntegerField()
    t3= models.IntegerField()
    t4= models.IntegerField()
    t5= models.IntegerField()
    t6= models.IntegerField()
    t7= models.IntegerField()
    t8= models.IntegerField()
    t9= models.IntegerField()
    t10= models.IntegerField()
    t11= models.IntegerField()
    t12= models.IntegerField()

    objects = BaseManager()

    def __str__(self):
        return self.mei

    def get_tk_jsk(self) -> list:
        res = []
        res.append(self.t1)
        res.append(self.t2)
        res.append(self.t3)
        res.append(self.t4)
        res.append(self.t5)
        res.append(self.t6)
        res.append(self.t7)
        res.append(self.t8)
        res.append(self.t9)
        res.append(self.t10)
        res.append(self.t11)
        res.append(self.t12)
        return res
```
　【KmkKosei】クラスは費目構成のモデルです。各フィールドの意味は下記の通り。
>cd：勘定科目コード
>mei：勘定科目名称
>oya：Tree構造における親のコードを指す
>tai：勘定科目の貸借属性でAの場合は、借方属性とし、Bの場合は貸方属性とする
>　　（勘定の残高を借方、貸方のどちらに持つかという属性）
>jun：勘定科目を表示する際の表示順を示す

　
　【KmkJsk】クラスは、科目別の実績を月別に記録するモデルです。各フィールドの意味は下記の通り。
>bmn：原価部門コードのつもりですが、今回は使用しない
>cd：勘定科目コード
>mei：勘定科目名称ですが、冗長になるので特になくても良い
>t1 ～ t12：月別の実績（発生額）

　【BaseManager】クラスは、プログラムの中で、Kmk.objects.メソッド()みたいに使用できることを実現するためのものです。これは、KmkJskクラスに対して使用します。

```
objects = BaseManager()
```

#### モデルをmigrateする ####

```
>> python manage.py makemigrations
>> python manage.py migrate

  その後で管理者アカウントを作成する・・
>> python manage.py createsuperuser
``` 

#### データ登録をする ####
データベースが利用できるようになったら、Djangoの管理者画面から適当にデータを登録します。
管理者画面のカスタマイズは、アプリのadmin.pyを編集します。
#### admin.py ####

```python:admin.py
from django.contrib import admin
from .models import *

class KmkKoseiAdmin(admin.ModelAdmin):
    list_display = ('cd', 'mei', 'tai' , 'oya', 'jun')
admin.site.register(KmkKosei, KmkKoseiAdmin)

class KmkJskAdmin(admin.ModelAdmin):
    list_display = ("bmn", 'cd', 'mei', 't1', 't2', 't3', 't4', 't5', 't6', 't7', 't8', 't9', 't10', 't11', 't12')
admin.site.register(KmkJsk, KmkJskAdmin)
```
#### 科目構成のデータ ####
![キャプチャ1.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/440325/53ff7bc0-fe51-86a6-6c58-e4f8533e431b.png)

### 科目実績のデータ ###
![キャプチャ2.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/440325/f346dab7-8dfb-fa38-4429-fa0959fdf17d.png)

#### myclass.py ####
ここでは、データベースから科目構成と実績を取得するクラスを定義します。
myclass.py は、アプリの直下に配置します。

```python:myclass.py
from anytree import Node
from .models import *

#------------------------------------------------------------
# 科目構成集計クラス
#------------------------------------------------------------
class Nd():
    TK=[0,0,0,0,0,0,0,0,0,0,0,0]

    #-----------------------------------
    # initializer
    #-----------------------------------
    def __init__(self):
        self.root = None

    #-----------------------------------
    # 引数で渡されたノードに子供のノードを繋げる処理（再帰）
    #-----------------------------------
    def SearchChildren(self, p_node):
        children = KmkKosei.objects.filter(oya= p_node.cd).order_by("jun") # -> QuerySet
        for child in children:
            nd = Node(child.mei, parent=p_node, cd=child.cd, tai= child.tai, oya=child.oya, jun= child.jun, tk=Nd.TK) # -> Node
            # 再帰
            self.SearchChildren(nd)

    #-----------------------------------
    # ルートにノードをつなげる処理
    #-----------------------------------
    def SetTreeByCD(self, cd):
        roots = KmkKosei.objects.filter(cd= cd).order_by("jun") # -> QuerySet
        for kmk in roots:
            self.root = Node(kmk.mei , parent=None, cd=kmk.cd, tai= kmk.tai, oya=kmk.oya, jun= kmk.jun, tk=Nd.TK) # -> Node
            #
            self.SearchChildren(self.root)

    #-----------------------------------
    # 戻り値：子供たちの値の合計
    #-----------------------------------
    def SumAllChildren(self, p_node=None):
        """ 引数で渡されたノードが持つ末端の子供（子供を持たないノード）を取得しながら、それを合計する """

        tk_sum=[0,0,0,0,0,0,0,0,0,0,0,0]

        if p_node:
            for ch in p_node.leaves:
                s2= KmkJsk.objects.GetTkJsk(cd=ch.cd)
                if s2:
                    if p_node.tai == "A":
                        if ch.tai == "A":
                            tk_sum = [x + y for (x, y) in zip(tk_sum, s2)]
                        else:
                            tk_sum = [x - y for (x, y) in zip(tk_sum, s2)]
                    else:
                        if ch.tai == "B":
                            tk_sum = [x + y for (x, y) in zip(tk_sum, s2)]
                        else:
                            tk_sum = [x - y for (x, y) in zip(tk_sum, s2)]
                    ch.tk= s2

        return tk_sum 

    #-----------------------------------
    # 引数で渡されたノードが持つすべてのノードを取得する
    #-----------------------------------
    def SumNodeParents(self, p_node=None):
        """ 引数で渡されたノードが持つすべての親ノードを取得しながら、それを合計する """

        res={} # 辞書のネストを戻り値とする。
        tk_sum=[0,0,0,0,0,0,0,0,0,0,0,0]

        if p_node:
            for ch in p_node.descendants:
                if ch.children:
                    s2= self.SumAllChildren(ch)
                    res[ch.cd]=dict(tk=s2)

                    ch.tk=s2

                    if p_node.tai == "A":
                        if ch.tai == "A":
                            tk_sum = [x + y for (x, y) in zip(tk_sum, s2)]
                        else:
                            tk_sum = [x - y for (x, y) in zip(tk_sum, s2)]
                    else:
                        if ch.tai == "B":
                            tk_sum = [x + y for (x, y) in zip(tk_sum, s2)]
                        else:
                            tk_sum = [x - y for (x, y) in zip(tk_sum, s2)]

            res[p_node.cd]=dict(tk=tk_sum)
            p_node.tk= tk_sum
        return res 
```

#### views.py ####
```python:views.py
from django.views import generic
from anytree import RenderTree
from .myclass import *

class IndexView(generic.TemplateView):
    template_name= "app/index.html"

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context["mess"] = "総製造費用"

        #------------------------------
        #
        #------------------------------
        n= Nd()
        n.SetTreeByCD(4000)
        n.SumNodeParents(n.root)  #<-- 集計します（記載漏れ）

        if n.root:
            context["ts"] = RenderTree(n.root)

        return context

```
　総製造費用の集計において、ルートノードとする科目は、「4000：総製造費用」です。

#### index.html ####
アプリで使用するHTMLテンプレートを作ります。
templates/app/index.html

```html:index.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <!-- UIkit CSS -->
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/uikit@3.9.4/dist/css/uikit.min.css" />

    <!-- UIkit JS -->
    <script src="https://cdn.jsdelivr.net/npm/uikit@3.9.4/dist/js/uikit.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/uikit@3.9.4/dist/js/uikit-icons.min.js"></script>

</head>
<body>
    <div class="uk-section">
        <div class="uk-container">

            <div class="uk-h3 uk-heading-bullet">
                {{mess}}
            </div>

            <div class="uk-margin">
                <table class="" border="1">
                    <tr style="background-color: lightgreen">
                        <th class="uk-text-center" rowspan="2">科目CD</th>
                        <th class="uk-text-center" rowspan="2">科目名称</th>
                        <th class="uk-text-center" rowspan="2">貸借</th>
                        <th class="uk-text-center" colspan="12">当期</th>
                    </tr>
                    <tr style="background-color: lightgreen">
                        <th class="uk-text-center">04</th>
                        <th class="uk-text-center">05</th>
                        <th class="uk-text-center">06</th>
                        <th class="uk-text-center">07</th>
                        <th class="uk-text-center">08</th>
                        <th class="uk-text-center">09</th>
                        <th class="uk-text-center">10</th>
                        <th class="uk-text-center">11</th>
                        <th class="uk-text-center">12</th>
                        <th class="uk-text-center">01</th>
                        <th class="uk-text-center">02</th>
                        <th class="uk-text-center">03</th>

                    </tr>
                    {% for n in ts %}
                        {#{% if n.node.parent %}#}
                            {% if n.node.children %}
                            <tr style="background-color: lavender">
                            {% else %}
                            <tr>
                            {% endif %}
                                <td class="uk-text-right">{{n.node.cd}}{#{{n.node.depth}}#}</td>
                                <td>{#{{n.pre}}#}{% for _ in ''|center:n.node.depth %}+{% endfor %}
                                    <a href="{{n.node.cd}}"> {{n.node.name}}{#{{n.node.cd}}{{n.node.tai}}{{n.node.jun}}#}</a>
                                </td>
                                <td class="uk-text-center">
                                    {{n.node.tai}}
                                </td>
                                {% for d in n.node.tk %}
                                <td class="uk-text-right">
                                    {{d}}
                                </td>
                                {% endfor %}
                            </tr>
                        {#{% endif %}#}
                    {% endfor %}

                </table>
            </div>
        </div>
    </div>
</body>
</html>
```
ここでは、私の好みで、CSSにUIKITフレームワークを使用しています。（テーブルデザインはいまいち気に入りません）

## まとめ ##
一応目的は達成できましたが、科目構成集計（Nd）クラスは、集計科目は上ではなく下に表示したいなど、改良の余地がありそうです。
ルートノードを変えることで、他の集計科目で集計することも可能です。

この国の製造業の競争力アップの為に、共通化された原価管理システムがあればいいのにと、常々に思います。