質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

88.78%

顧客別、月次別の売上高集計表の作り方

解決済

回答 1

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 3,492

mulberryfields

score 802

Djangoで作っているアプリの中で、顧客別、月次別の売上高集計表を作成しようと考えています。
表示したい画面は、下記のようなものです。
sample
顧客と売上を扱うモデルは、下記のとおりです。(これ自体はうまく動いています。)
#models.py

class Customer(models.Model): #顧客を扱うmodel
    id = models.IntegerField('顧客番号', blank = True, primary_key = True, unique = True)
    name = models.CharField(u'顧客名称', blank = True, max_length = 100)
    def __unicode__(self):
        return u"%s" % self.id

class Sales(models.Model): #売上を扱うmodel
    date = models.DateField(u'売上日')
    customer_id = models.ForeignKey(Customer) #顧客ID
    ammount = models.IntegerField(u'金額')
    def __unicode__(self):
        return u"%s" % self.customer_id
views.pyをどうしたらよいか分からないのですが、現状、下記のようにしています。
#views.py
def month_total(request):
    customer = Customer.objects.all() 
    customerlist = [] #空のリストを作成
    for customer in customer:
    customerlist.append(customer.id) #顧客IDのリストを作成

    start_date = date(2012,10,1) #2012年10月1日からのデータを取りまとめます。
    today = date.today()
    end_date = date.fromtimestamp(time.mktime((today.year,today.month + 1,1,0,0,0,0,0,0))) #今日を含む月の月末までを集計の対象とします。

    # 各月の初日のリストを取得するためのコード
    months = [start_date]
    first_date_of_each_month = start_date 
    while first_date_of_each_month < end_date:
        first_date_of_each_month =  date.fromtimestamp(time.mktime((first_date_of_each_month.year,first_date_of_each_month.month + 1,1,0,0,0,0,0,0)))
        months.append(first_date_of_each_month) #ここまでで、 [datetime.date(2012, 10, 1), datetime.date(2012, 11, 1), ・・・,, datetime.date(2015, 10, 1)]というリストができます(これ自体はうまくいっています)。
 
    sales_for_each_customer= []  #顧客毎の売上を入れるための空のリスト

    for customer in customerlist:
    sales = Sales.objects.filter(customer_id = customer) #各顧客への売上をピックアップ
    sales_in_each_month = [] #各顧客の各月毎の売上を集計するための空のリスト
    for m in months :
        last_date_of_each_month = date.fromtimestamp(time.mktime((m.year,m.month + 1,1,0,0,0,0,0,0))) - timedelta(days=1) #各月の月末の日
        sales_in_month = 0 #各顧客の各月売上集計
            for s in sales : 各顧客への売上が、どの月のものかを判定するコード
        if s.date >= first_date_of_each_month and s.date <= last_date_of_each_month : #もし、当該売上日が当月初日以上当月月末日以下であった場合
            sales_in_month = sales_in_month + s.ammount #当該売上を当月売上高に集計
        sales_in_each_month.append(sales_in_month) 
        first_date_of_each_month = last_date_of_each_month + timedelta(days = 1) #次の月の初日
    sales_for_each_customer.append(sales_in_each_month) #[・・・、(顧客AのH27.1月売上)、(顧客AのH27.2月売上)、・・・]というリストができる。(これ自体はうまくいきます。)
    first_date_of_each_month = start_date #次の顧客に移る前に、2012年10月1日に戻す。

    return render_to_response('cls/sales_list.html',
        {''months' : months, 'sales_in_each_month' : sales_in_each_month, 'customerlist' : customerlist, 'sales_for_each_customer' : sales_for_each_customer},
        context_instance = RequestContext(request)
    )
テンプレートは、下記のとおりです。
<!--sales_list.html-->
{% extends "base.html" %}
{% load humanize %}
・・・
{% block content %}
<table>
<tr>
    <th>顧客id</th>
    {% for months in months %}
    <th>{{months | date:"Y-m"}}</th>
    {% endfor %}
</tr>
{% for customer in customerlist %}
<tr>
    <td>{{customer}}</td>
    {% for sales in sales_for_each_site %}
    <td style = "text-align:right">{{sales | intcomma}}</td>
    {% endfor %}
</tr>
{% endfor %}
</table>
{% endblock %}

上記の結果、sales_for_each_customerには、  [[顧客Aの2012.10月売上, 顧客Aの2012.11月売上,・・・,顧客Aの2015.9月売上],[顧客Bの2012.10月売上, 顧客Bの2012.11月売上,・・・,顧客Bの2015.9月売上],・・・]が入っていることは確認できています。
しかしながら、実際に表示されるのは、顧客Aの2012.10月のところに顧客Aの各月の売上のリストが表示され、顧客Aの2012.11月のところに顧客Bの各月の売上のリストが表示されるようなことになってしまっています。
どうしたら良いのでしょうか。

そもそも、この方法は、足りない知識を使って、何とか無理矢理作ったコードです。しかし、このような処理自体はよくありがちなものではないかと思います。一般的に、こういう時はこういう考え方でやるという定番的な方法があるのであれば、それを知りたいです。

分かりづらい内容の質問を長々と書いて申し訳ありません。
お分かりの方、ご教示頂ければ幸いです。
よろしくお願いいたします。
  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

checkベストアンサー

+1

HTMLにある"sales_for_each_site"がPythonから引き渡された"sales_for_each_customer"であっていますか?
sales_for_each_customerの構造は[[A社の月別売上リスト],[B社の月別売上リスト]...]ですね。
今のソースコードではまず表作成部分のネストが足りていません。
そして顧客IDと売上高が別データになっているので、1つのテーブルにまとめるのがとても大変です。
どうして大変になるのか、以下修正例で示してみます。

◆オリジナルのコード◆
{% for customer in customerlist %}
<tr>
    <td>{{customer}}</td>
    {% for sales in sales_for_each_site %}←ここで各社の売上リストを取り出しています。
    ##sales = [A社の売上リスト]が取り出される。

    <td style = "text-align:right">{{sales | intcomma}}</td>
    ##salesを表示するので、A社の売上リストが指定されたセルに表示される。

    {% endfor %}
</tr>
{% endfor %}
---------------------------------------------------------------------------------------
◆ネストを増やしただけのコード◆
{% for customer in customerlist %}
<tr>
    <td>{{customer}}</td>
     {% for sales in sales_for_each_site %}
  ##sales =[A社の売上リスト]

       {% for each_month in sales %}}
   ##each_month=[各月の売上]

               <td style = "text-align:right">{{each_month | intcomma}}</td>
         {% endfor %}
    {% endfor %}
</tr>
{% endfor %}
上記コードでは1行に全顧客の各月売上がずらっと並び、それが顧客ID分繰り返されます。
なぜこうなるのかと言うと、表の行送りをcustomerのループで制御しているからです。
customerのループは売上データのループと無関係に動きますから、正しいタイミングで
次の顧客ID分のデータ表示を始めることが出来ません。

つまり、顧客IDと顧客ごとの各月売上とで1セットのデータとなっている必要があります。
なので顧客IDと売上で以下のようなリストを作るとよいでしょう。
[['顧客A',[1月の売上,2月の売上,3月の売上...]],['顧客B',[1月の売上,2月の売上,3月の売上...]]]
仮に上記データをsales_for_each_customerとすると、以下HTMLで表を作れます。

{% for each_customer in sales_for_each_customer %}
<tr>
   <td>{{each_customer.0}}<td>
   {% for each_month in each_customer.1 %}
      <td>{{each_month}}</td>
   {% endfor %}
</tr>
{% endfor %}
コード

コード全体へのアドバイスは差し控えますが、考え方としては顧客IDとその顧客のデータをきちんと
紐づけておくのが大事だと思います。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2015/09/11 22:28

    Fuekemさん、詳しくご説明頂き、ありがとうございました。
    無事、解決することができました。

    [[顧客A,[1月売上,2月売上,・・・]],[顧客B,[1月売上,2月売上,・・・]],・・・]
    という感じでリストを作っていけば良いのですね。
    forループの内側でもう一回forループを動かすことで、更に深いリストの要素を回すことができるということも分かりました。
    リストで処理する場合のコツがよく分かりました。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 88.78%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る