ゲーム化!tomo_manaのブログ

ゲーム化!tomo-manaのブログ

Unityでゲームを作る方法について紹介しています

Unity×会計#10 価格変動を考慮した仕入、売上、在庫(決算仕訳)(Unity 2019.4.4f1)

前回の続きで、価格変動を考慮した場合の決算処理を実装します。


今は、情報を最小限にするため、商品は1種類だけ、原価計算は総平均法で計算しています。

また、原価計算として総平均法を採用している場合、仕入帳と売上帳があれば原価計算ができるため、商品有高帳は一旦無視します。

仕入・売上(商品有高帳の省略)

商品が1種類の場合、集計作業が単純なため試算表を省略できます。

決算処理

(本来は(1)棚卸と(2)損益計算の前に、全帳簿を参照して試算を行います)

決算処理

棚卸

期首繰越商品を含む仕入個数と、当期の売上個数をまとめ、次期の繰越商品数を求めます。

個数計算

上記をまとめると、以下の表になります。

棚卸

2枚目の表では、期首繰越商品と当期の仕入個数を分けていますが、1枚目の仕入仕入帳)には期首繰越商品が含まれています。そのため、仕入帳と売上帳の差が繰越商品数になります。

// 期首繰越個数と仕入個数を分けて管理している場合は、第三引数を指定する
void 棚卸(仕入個数, 売上個数, [期首繰越個数 = 0]){
    this.仕入個数 = 仕入個数;
    this.売上個数 = 売上個数;
    this.期首繰越個数 = 期首繰越個数;

    this.期末繰越個数 = this.期首繰越個数 + this.仕入個数 - this.売上個数;
}

棚卸( 仕入帳.個数, 売上帳.個数 );
//期首繰越個数と当期仕入数を分けて考える場合は以下
//if( 仕入帳.レコード数 != 0 ){
//    棚卸( 仕入帳.個数 - 仕入帳.レコード[0].個数, 売上帳.個数, 仕入帳.レコード[0].個数 );
//}

現実には、帳簿上の数と実際の在庫が合わないことがあるため、欠損処理が入りますが、ここでは省略しています。

原価計算(総平均法)

次に、商品原価を計算します。今回は総平均法で計算します。

総平均法では、全ての仕入個数と、全ての仕入額とを足し合わせ、仕入総額を仕入総数で割って求めるのでした。この計算対象となる仕入は全ての仕入です。当期にどれだけ売り上げたかに関わらず、期首繰越商品を含むすべての仕入個数で計算します。

原価計算
// 総平均法
int 商品原価;
if( 仕入個数 != 0 ){
    商品原価 = 仕入総額 / 仕入個数;
} else {
    商品原価 = 0;    // ゼロ割防止
}

商品原価は整数型にしましたが、商品原価を小数点まで保持したい場合は、原価を10倍(小数点1位)、100倍(2位)、1000倍(3位)しておく(ゲタを履かせる)方法もあります。また、思い切ってfloatやdoubleなどの浮動小数点を使う方法もあります(ただし、浮動小数点は、思いがけない誤差が出る可能性があります)。


(端数処理について)
簿記の試験ではともかく、実際の会計では、原価が割り切れないことはよくあると思います(端数処理が必要)。

今回は、端数を来期に持ち越すといろいろ面倒なので、当期中に処理することにします。後述しますが、商品原価は損益計算書の「費用」と貸借対照表の「流動資産」で按分されます。当期中に処理する場合、損益計算書の「費用」に端数が反映されます。

試算表の作成(省略)

試算表は、取扱商品が2つ以上ある場合に必要になります。今回は省略します。

損益計算

棚卸と原価計算が終わると、費用が算出できるので、以下のように算出します。

void 損益計算( 仕入総額, 売上総額 )
{
    費用 = 仕入総額;
    収益 = 売上総額;
    利益損失 = 収益 - 費用;
}

// 損益計算( 売上帳.個数 * 商品原価, 売上帳.総額 );    // 端数を来期に回す場合
損益計算( 仕入帳.総額 - 棚卸.期末繰越個数 * 商品原価, 売上帳.総額 );    // 端数を当期で回収する場合

貸借対照表の作成

売れ残った商品は繰越商品(流動資産)に計上します。箱型バランスシート※と呼ばれる形式を使用しているため、現金は流動資産に含めない形で計算しています。(通常は現金は流動資産に含まれます)

void 貸借対照表計算( 現金, 流動資産, 固定資産, 流動負債, 固定負債 )
{
    this.現金 = 現金;
    this.流動資産 = 流動資産;
    this.固定資産 = 固定資産;
    this.流動負債 = 流動負債;
    this.固定負債 = 固定負債 ;
    this.純利益損失 = 現金 + 流動資産 + 固定資産 - 流動負債 - 固定負債;
}

貸借対照表計算( 現金出納帳.残高, 棚卸.期末繰越個数 * 商品原価, 0, 0, 0 );
棚卸、損益計算書貸借対照表

繰り越しと初期化

(繰り越し)
現金は昨年の残高を期首に振り替え、繰越商品は期首仕入に振り替えます。売上はまたゼロからスタートです。

void 繰越()
{
    現金出納帳.初期化( 現金出納帳.残高 );
    仕入帳.初期化( 棚卸.期末繰越個数, 仕入帳.総額 - 棚卸.期末繰越個数 * 商品原価 );
    売上帳.初期化();
}

(初期化)
尚、創業直後(初期化)は以下のようになります。補助簿をまっさらにして、初期の手持ち現金で初期化します。

void 初期化()
{
    現金出納帳.初期化( 初期現金 );
    仕入帳.初期化();
    売上帳.初期化();
}

初期化処理と繰越処理は似ているので共通化できそうです。ただ、今は処理を分けておくことにします(共通化することで余計な検討が増えそうな予感がするため)

入力フォーム(ボタン)の追加

決算処理のテストのため、第6回で追加したものと同じボタンをこちらの入力フォームにも追加します。

入力フォーム(#10への追加分)

決算と初期化の入力部をSettleInputという名前でグループ化し(いつものEmpty Object)、決算ボタンをButtonSettle、初期化ボタンをButtonResetとします。

Hierarchy - 決算、RESETボタン

後述する入力フォーム用のコード(SettleInput.cs)を、ButtonSettleとButtonResetに取り付けて、各ボタンに処理(ButtonName)をアサインします。

尚、SettleInput は 貸借対照表損益計算書、ログ出力、データアクセスAPIへの参照を持ちます。少し複雑になってきたため、画面の各オブジェクトとクラスの関係をまとめます。

Inspector - SettleInput.cs
チャートとクラス

貸借対照表(BalanceSheet)、損益計算書(ProfitAndLoss)、ログ出力(JournalLog) は過去の記事で作ってきたものです。今回修正を入れるデータ処理部との関係は以下の構成になっています。

階層構造とクラス

コード

(決算・リセット入力フォーム:SettleInput.cs)

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class SettleInput : MonoBehaviour
{
    enum BUTTON_NAME {
        BUTTON_SETTLE = 0,
        BUTTON_RESET,
    };
    
    [SerializeField]
    BUTTON_NAME buttonName = BUTTON_NAME.BUTTON_SETTLE;
    
    [SerializeField]
    BalanceSheet bs = default;
    [SerializeField]
    ProfitAndLoss pl = default;
    
    [SerializeField]
    JournalLog log = default;
    [SerializeField]
    SalesTransaction2 ts = default;
    
    void UpdateLog(int error)
    {
    	log.UpdateLog( error );
    }
    
    void ExecuteSettle()
    {
        int e = -1;

        if( !ReferenceEquals(ts, null) ){
            e = ts.ExecuteSettle();
        }
        if( e == 0 ){
            e = 3;
            BalanceSheetDB bsdb = ts.GetBalanceSheet();
            ProfitAndLossDB pldb = ts.GetProfitAndLoss();
            bs.ChangeAreaValues( bsdb.cash, bsdb.flexAssets, bsdb.fixedAssets, bsdb.flexLiability, bsdb.fixedLiability );
            pl.ChangeAreaValues( pldb.expenses, pldb.revenue );
	    UpdateLog( e );
        }
    }
	
    void ExecuteReset()
    {
        int e = -1;

        if( !ReferenceEquals(ts, null) ){
            e = ts.ExecuteReset();
        }
        if( e == 0 ){
            e = 3;
            BalanceSheetDB bsdb = ts.GetBalanceSheet();
            ProfitAndLossDB pldb = ts.GetProfitAndLoss();
            bs.ChangeAreaValues( bsdb.cash, bsdb.flexAssets, bsdb.fixedAssets, bsdb.flexLiability, bsdb.fixedLiability );
            pl.ChangeAreaValues( pldb.expenses, pldb.revenue );
	    UpdateLog( e );
        }
    }
    
    void ButtonClicked(int buttonNo)
    {
        switch( buttonNo ){
        case (int)BUTTON_NAME.BUTTON_SETTLE:
            Debug.Log("Button Clicked = " + buttonNo + "Settle!");  // OK
            ExecuteSettle();
            break;
        case (int)BUTTON_NAME.BUTTON_RESET:
            Debug.Log("Button Clicked = " + buttonNo + "Reset!");  // OK
            ExecuteReset();
            break;
        default:
            break;
        }
    }
    
    // Start is called before the first frame update
    void Start()
    {
        int i = 0;
        Button button;
        // InputFieldゲームオブジェクトを取得
        button = gameObject.GetComponent<Button>();
        if( !ReferenceEquals(button, null) ){
            int ii = (int)buttonName;
            button.onClick.AddListener(() => ButtonClicked(ii));
            i++;
        }
    }
}

(データ処理部:SalesTransaction2.cs)

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;

// 台帳(増減あり)
public class SinglePoint {
    public List<Tuple<int, int>> l;
    int rsum;
    public void Add( int a )
    {
        rsum += a;
        l.Add( new Tuple<int, int>(a, rsum) );
    }
    public void Clear()
    {
        l.Clear();
        rsum = 0;
    }
    public SinglePoint()
    {
        l = new List<Tuple<int, int>>();
        rsum = 0;
    }
    public int Get()
    {
        return rsum;
    }
}

// 台帳(増加のみ)
public class DoublePoint {
    public List<Tuple<int, int>> l;
    public void Add( int a, int b )
    {
        rsum1 += a;
        rsum2 += b;
        l.Add( new Tuple<int, int>(a, b) );
    }
    public void Clear()
    {
        l.Clear();
        rsum1 = 0;
        rsum2 = 0;
    }
    int rsum1;  // 個数
    int rsum2;  // 金額
    public DoublePoint()
    {
        rsum1 = rsum2 = 0;
        l = new List<Tuple<int, int>>();
    }
    public int Get(int id)
    {
        if( id == 0 ){
            return rsum1;   // 個数
        } else if( id == 1 ){
            return rsum2;   // 金額
        } else {
            return 0;
        }
    }
}

public class SalesTransaction2 : MonoBehaviour
{
    // 試算表
    TrialBalance tb = new TrialBalance();
    // 棚卸
    InventoryDB iv = new InventoryDB();
    // 貸借対照表
    BalanceSheetDB bs = new BalanceSheetDB();
    // 損益計算書
    ProfitAndLossDB pl = new ProfitAndLossDB();
    
    // 資本
    int firstCash = 100000;
    // 販売物の仕入単価(現在は固定)
    int unitPrice = 1000;
    
    // 現金出納帳
    SinglePoint cash = new SinglePoint();
    // 仕入帳
    DoublePoint purchase = new DoublePoint();
    // 売上帳
    DoublePoint sales = new DoublePoint();
    
    public BalanceSheetDB GetBalanceSheet()
    {
        return bs;
    }
    
    public ProfitAndLossDB GetProfitAndLoss()
    {
        return pl;
    }
    
    public void InitializeJournal()
    {
    	// 現金出納帳
        cash.Clear();
        cash.Add(firstCash);
        // 仕入帳
        purchase.Clear();
        // 売上帳
        sales.Clear();
    }
    
    /* 略 */
    
    // 繰り越し
    void CarryOver()
    {
        int[] tmp = new int[2];
        
        // 現金の繰り越し
        tmp[0] = cash.Get();
        cash.Clear();
        cash.Add(tmp[0]);
        
        // 仕入の繰り越し
        tmp[0] = iv.q_carryover;
        tmp[1] = tmp[0] * unitPrice;
        purchase.Clear();
        if( tmp[0] != 0 ){
            purchase.Add(tmp[0], tmp[1]);
        }
        
        // 売上の繰り越し
        sales.Clear();
    }
    
    // 決算処理
    public int ExecuteSettle()
    {
        // 棚卸表の作成(今のところ仕入れてないものを売るは想定しない)
        iv.Set( purchase.Get(0), sales.Get(0) );
        
        // 原価計算(端数は当期中に処理すること)
        if( purchase.Get(0) != 0 ){
            unitPrice = purchase.Get(1) / purchase.Get(0);
        } else {
            unitPrice = 0;
        }
        
        // 試算表の作成(省略)

        // 損益計算
//      pl.Set( iv.q_sales * unitPrice, sales.Get(1) )  // 端数を加味しない
        pl.Set( purchase.Get(1) - iv.q_carryover * unitPrice, sales.Get(1) );   
        
        // 貸借対照表
        bs.Set( cash.Get(), iv.q_carryover * unitPrice, 0, 0, 0 );
        
        // 繰り越し
        CarryOver();

        return 0;
    }
    
    // 初期化処理
    public int ExecuteReset()
    {
        InitializeJournal();
        return 0;
    }
    
    /* 略 */
}

(データ構造:Transaction.cs)

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

// 試算表
public class TrialBalance {
    public int cash;        // 現金
    public int purchase;    // 仕入
    public int sales;       // 売上
    public int q_remain;    // 在庫残
    public int q_total;     // 在庫総購入量
    public int unit;        // 仕入単価
    
    public void Initialize(int c, int p, int s, int qr, int qt){
        cash = c;
        purchase = p;
        sales = s;
        q_remain = qr;
        q_total = qt;
    }
}

// 棚卸
public class InventoryDB {
    public int q_initial;   // 期首繰越商品数
    public int q_purchase;  // 仕入数
    public int q_sales;     // 売上数
    public int q_carryover; // 期末繰越商品数
    
    public void Initialize(int p, int s, int i)
    {
        q_purchase = p;
        q_sales = s;
        q_initial = i;
        q_carryover = 0;
    }
    
    void CalcCarryOver()
    {
        q_carryover = q_initial + q_purchase - q_sales;
    }
    
    public void Set(int q_purchase, int q_sales, int q_initial)
    {
        this.q_purchase = q_purchase;
        this.q_sales = q_sales;
        this.q_initial = q_initial;
        
        CalcCarryOver();
    }
    
    public void Set(int q_purchase, int q_sales)
    {
        Set(q_purchase, q_sales, 0);
    }
}

// 貸借対照表
public class BalanceSheetDB {
    public int cash;            // 現金
    public int flexAssets;      // 流動資産
    public int fixedAssets;     // 固定資産
    public int flexLiability;   // 流動負債
    public int fixedLiability;  // 固定負債
    public int netAssets;       // 純資産
    
    void CalcNetAssets()
    {
        netAssets = cash + flexAssets + fixedAssets - flexLiability - fixedLiability;
    }
    
    public void Set( int cash, int flexAssets, int fixedAssets, int flexLiability, int fixedLiability )
    {
        this.cash = cash;
        this.flexAssets = flexAssets;
        this.fixedAssets = fixedAssets;
        this.flexLiability = flexLiability;
        this.fixedLiability = fixedLiability;
        
        CalcNetAssets();
    }
}

// 損益計算書
public class ProfitAndLossDB {
    public int expenses;    // 費用
    public int revenue;     // 収益
    public int netIncome;   // 利益
    
    void CalcNetIncome()
    {
        netIncome = revenue - expenses;
    }
    
    public void Set( int purchase, int sales )
    {
        expenses = purchase;
        revenue = sales;
        
        CalcNetIncome();
    }
}

動作テスト

ゲームのURL(前回までと同じURLです)
tomo-mana.hatenablog.com


次回からは以下に取り組みます。
●5年分のデータを保持する(決算資料の配列化)
●複数の商品を扱えるようにする(仕入帳、売上帳の配列化)