From 2c8689ec1c3e4298d202bcbf6637da580cb22cc3 Mon Sep 17 00:00:00 2001 From: Juan Carlos Date: Fri, 25 Oct 2013 13:11:15 -0500 Subject: [PATCH 01/19] created query for the first report --- app/controllers/reports_controller.rb | 17 +++++++++++++++++ app/views/layouts/_navigation.haml | 3 ++- app/views/reports/income_v_expense.html.haml | 13 +++++++++++++ app/views/reports/net_worth.html.haml | 0 app/views/reports/show.html.haml | 8 ++++++++ .../reports/spending_by_category.html.haml | 0 app/views/reports/spending_by_payee.html.haml | 0 config/routes.rb | 6 ++++++ 8 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 app/controllers/reports_controller.rb create mode 100644 app/views/reports/income_v_expense.html.haml create mode 100644 app/views/reports/net_worth.html.haml create mode 100644 app/views/reports/show.html.haml create mode 100644 app/views/reports/spending_by_category.html.haml create mode 100644 app/views/reports/spending_by_payee.html.haml diff --git a/app/controllers/reports_controller.rb b/app/controllers/reports_controller.rb new file mode 100644 index 0000000..b3775e3 --- /dev/null +++ b/app/controllers/reports_controller.rb @@ -0,0 +1,17 @@ +class ReportsController < ApplicationController + + def income_v_expense + @report = Transaction.all.group(:pm_type).sum(:amount) + end + + def spending_by_payee + end + + def net_worth + @report = Transaction.where("account_id = 1 and date between '2013-01-01' and '2014-01-01'").sum(:amount).to_f + end + + def spending_by_category + end + +end diff --git a/app/views/layouts/_navigation.haml b/app/views/layouts/_navigation.haml index 2006527..5003c0f 100644 --- a/app/views/layouts/_navigation.haml +++ b/app/views/layouts/_navigation.haml @@ -2,12 +2,13 @@ %nav.navbar-inner .container - = link_to 'Bernard', root_path, class:'brand' + = link_to 'Bernard', root_path, class: 'brand' .nav-collapse.collapse %ul.nav %li= link_to 'All transactions', transactions_path = render '/accounts/menu' + %li= link_to 'Reports', report_path %li= link_to 'Import', import_index_path diff --git a/app/views/reports/income_v_expense.html.haml b/app/views/reports/income_v_expense.html.haml new file mode 100644 index 0000000..48462fa --- /dev/null +++ b/app/views/reports/income_v_expense.html.haml @@ -0,0 +1,13 @@ +%h1 Income vs Expense Report +%table.table-bordered + %thead + %tr + %th Pm Type + %th Amount + %tbody + - @report.each do |key, value| + %tr + %th= key + %th= value + +%canvas#canvas{height: '450', width: '600'} diff --git a/app/views/reports/net_worth.html.haml b/app/views/reports/net_worth.html.haml new file mode 100644 index 0000000..e69de29 diff --git a/app/views/reports/show.html.haml b/app/views/reports/show.html.haml new file mode 100644 index 0000000..1a9b8c5 --- /dev/null +++ b/app/views/reports/show.html.haml @@ -0,0 +1,8 @@ +%h2 Reports +=link_to 'Income vs Expense', income_v_expense_report_path +| +=link_to 'Spending by Payee', spending_by_payee_report_path +| +=link_to 'Net Worth', net_worth_report_path +| +=link_to 'Speding by Category', spending_by_category_report_path diff --git a/app/views/reports/spending_by_category.html.haml b/app/views/reports/spending_by_category.html.haml new file mode 100644 index 0000000..e69de29 diff --git a/app/views/reports/spending_by_payee.html.haml b/app/views/reports/spending_by_payee.html.haml new file mode 100644 index 0000000..e69de29 diff --git a/config/routes.rb b/config/routes.rb index 51066a7..fbd3b08 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -15,6 +15,12 @@ # You can have the root of your site routed with "root" # root 'welcome#index' root 'transactions#index' + resource :report, only: :show do + get 'income_v_expense' => 'reports#income_v_expense' + get 'spending_by_payee' => 'reports#spending_by_payee' + get 'net_worth' => 'reports#net_worth' + get 'spending_by_category' => 'reports#spending_by_category' + end # Example of regular route: # get 'products/:id' => 'catalog#view' From 18a30d63b93a6786572093d5c4f01a0b277988bc Mon Sep 17 00:00:00 2001 From: Juan Carlos Date: Fri, 25 Oct 2013 15:37:52 -0500 Subject: [PATCH 02/19] created queries for get the correct values --- app/controllers/reports_controller.rb | 63 ++++++++++++++++++++++++++- 1 file changed, 61 insertions(+), 2 deletions(-) diff --git a/app/controllers/reports_controller.rb b/app/controllers/reports_controller.rb index b3775e3..0b4c539 100644 --- a/app/controllers/reports_controller.rb +++ b/app/controllers/reports_controller.rb @@ -2,16 +2,75 @@ class ReportsController < ApplicationController def income_v_expense @report = Transaction.all.group(:pm_type).sum(:amount) + respond_to do |format| + format.html + format.json { render json: income_v_expense_json } + end end def spending_by_payee + @report = Transaction.all.group(:payee_id).sum(:amount) + respond_to do |format| + format.html + format.json { render json: spending_by_payee_json } + end end def net_worth - @report = Transaction.where("account_id = 1 and date between '2013-01-01' and '2014-01-01'").sum(:amount).to_f + @report = Transaction.select(:account_id, :amount).group(:account_id).sum(:amount) + end + + def spending_by_category + @report = Transaction.all.group(:category_id).sum(:amount) + respond_to do |format| + format.html + format.json { render json: spending_by_category_json } + end end - def spending_by_category + def income_v_expense_json + { + labels: @report.keys, + datasets: [ + { + fillColor: "rgba(220,220,220,0.5)", + strokeColor: "rgba(220,220,220,1)", + fillColor: "rgba(225, 0, 0, 0.5)", + strokeColor: "rgba(220, 220, 220, 1)", + data: @report.values + } + ] + } + end + + def spending_by_payee_json + { + labels: @report.keys, + datasets: [ + { + fillColor: "rgba(220,220,220,0.5)", + strokeColor: "rgba(220,220,220,1)", + fillColor: "rgba(225, 0, 0, 0.5)", + strokeColor: "rgba(220, 220, 220, 1)", + data: @report.values + } + ] + } end + def spending_by_category_json + { + labels: @report.keys, + datasets: [ + { + fillColor: "rgba(220,220,220,0.5)", + strokeColor: "rgba(220,220,220,1)", + fillColor: "rgba(225, 0, 0, 0.5)", + strokeColor: "rgba(220, 220, 220, 1)", + data: @report.values + } + ] + } + + end end From 95831c95806d94248e64650a6c603f45fd4dcef1 Mon Sep 17 00:00:00 2001 From: Juan Carlos Date: Fri, 25 Oct 2013 15:38:20 -0500 Subject: [PATCH 03/19] created html for the new views --- app/views/reports/income_v_expense.html.haml | 1 + app/views/reports/spending_by_category.html.haml | 14 ++++++++++++++ app/views/reports/spending_by_payee.html.haml | 14 ++++++++++++++ 3 files changed, 29 insertions(+) diff --git a/app/views/reports/income_v_expense.html.haml b/app/views/reports/income_v_expense.html.haml index 48462fa..565a481 100644 --- a/app/views/reports/income_v_expense.html.haml +++ b/app/views/reports/income_v_expense.html.haml @@ -11,3 +11,4 @@ %th= value %canvas#canvas{height: '450', width: '600'} += javascript_include_tag 'reports/income_v_expense' diff --git a/app/views/reports/spending_by_category.html.haml b/app/views/reports/spending_by_category.html.haml index e69de29..ecb2e8a 100644 --- a/app/views/reports/spending_by_category.html.haml +++ b/app/views/reports/spending_by_category.html.haml @@ -0,0 +1,14 @@ +%h1 Spending by Category +%table.table-bordered + %thead + %tr + %th Id Category + %th Amount + %tbody + - @report.each do |key, value| + %tr + %th= key + %th= value + +%canvas#canvas{height: '450', width: '600'} += javascript_include_tag 'reports/spending_by_category' diff --git a/app/views/reports/spending_by_payee.html.haml b/app/views/reports/spending_by_payee.html.haml index e69de29..b94e000 100644 --- a/app/views/reports/spending_by_payee.html.haml +++ b/app/views/reports/spending_by_payee.html.haml @@ -0,0 +1,14 @@ +%h1 Income vs Expense Report +%table.table-bordered + %thead + %tr + %th Pm Type + %th Amount + %tbody + - @report.each do |key, value| + %tr + %th= key + %th= value + +%canvas#canvas{height: '450', width: '600'} += javascript_include_tag 'reports/spending_by_payee' From b3e60f2738fee00d939a864f04736a79b00658e3 Mon Sep 17 00:00:00 2001 From: Juan Carlos Date: Fri, 25 Oct 2013 15:39:01 -0500 Subject: [PATCH 04/19] created javascript for use the jsons in the graph --- app/assets/javascripts/Chart.js | 1424 +++++++++++++++++ app/assets/javascripts/application.js | 2 +- .../javascripts/reports/income_v_expense.js | 3 + .../reports/spending_by_category.js | 3 + .../javascripts/reports/spending_by_payee.js | 3 + 5 files changed, 1434 insertions(+), 1 deletion(-) create mode 100644 app/assets/javascripts/Chart.js create mode 100644 app/assets/javascripts/reports/income_v_expense.js create mode 100644 app/assets/javascripts/reports/spending_by_category.js create mode 100644 app/assets/javascripts/reports/spending_by_payee.js diff --git a/app/assets/javascripts/Chart.js b/app/assets/javascripts/Chart.js new file mode 100644 index 0000000..58e9647 --- /dev/null +++ b/app/assets/javascripts/Chart.js @@ -0,0 +1,1424 @@ +/*! + * Chart.js + * http://chartjs.org/ + * + * Copyright 2013 Nick Downie + * Released under the MIT license + * https://github.com/nnnick/Chart.js/blob/master/LICENSE.md + */ + +//Define the global Chart Variable as a class. +window.Chart = function(context){ + + var chart = this; + + + //Easing functions adapted from Robert Penner's easing equations + //http://www.robertpenner.com/easing/ + + var animationOptions = { + linear : function (t){ + return t; + }, + easeInQuad: function (t) { + return t*t; + }, + easeOutQuad: function (t) { + return -1 *t*(t-2); + }, + easeInOutQuad: function (t) { + if ((t/=1/2) < 1) return 1/2*t*t; + return -1/2 * ((--t)*(t-2) - 1); + }, + easeInCubic: function (t) { + return t*t*t; + }, + easeOutCubic: function (t) { + return 1*((t=t/1-1)*t*t + 1); + }, + easeInOutCubic: function (t) { + if ((t/=1/2) < 1) return 1/2*t*t*t; + return 1/2*((t-=2)*t*t + 2); + }, + easeInQuart: function (t) { + return t*t*t*t; + }, + easeOutQuart: function (t) { + return -1 * ((t=t/1-1)*t*t*t - 1); + }, + easeInOutQuart: function (t) { + if ((t/=1/2) < 1) return 1/2*t*t*t*t; + return -1/2 * ((t-=2)*t*t*t - 2); + }, + easeInQuint: function (t) { + return 1*(t/=1)*t*t*t*t; + }, + easeOutQuint: function (t) { + return 1*((t=t/1-1)*t*t*t*t + 1); + }, + easeInOutQuint: function (t) { + if ((t/=1/2) < 1) return 1/2*t*t*t*t*t; + return 1/2*((t-=2)*t*t*t*t + 2); + }, + easeInSine: function (t) { + return -1 * Math.cos(t/1 * (Math.PI/2)) + 1; + }, + easeOutSine: function (t) { + return 1 * Math.sin(t/1 * (Math.PI/2)); + }, + easeInOutSine: function (t) { + return -1/2 * (Math.cos(Math.PI*t/1) - 1); + }, + easeInExpo: function (t) { + return (t==0) ? 1 : 1 * Math.pow(2, 10 * (t/1 - 1)); + }, + easeOutExpo: function (t) { + return (t==1) ? 1 : 1 * (-Math.pow(2, -10 * t/1) + 1); + }, + easeInOutExpo: function (t) { + if (t==0) return 0; + if (t==1) return 1; + if ((t/=1/2) < 1) return 1/2 * Math.pow(2, 10 * (t - 1)); + return 1/2 * (-Math.pow(2, -10 * --t) + 2); + }, + easeInCirc: function (t) { + if (t>=1) return t; + return -1 * (Math.sqrt(1 - (t/=1)*t) - 1); + }, + easeOutCirc: function (t) { + return 1 * Math.sqrt(1 - (t=t/1-1)*t); + }, + easeInOutCirc: function (t) { + if ((t/=1/2) < 1) return -1/2 * (Math.sqrt(1 - t*t) - 1); + return 1/2 * (Math.sqrt(1 - (t-=2)*t) + 1); + }, + easeInElastic: function (t) { + var s=1.70158;var p=0;var a=1; + if (t==0) return 0; if ((t/=1)==1) return 1; if (!p) p=1*.3; + if (a < Math.abs(1)) { a=1; var s=p/4; } + else var s = p/(2*Math.PI) * Math.asin (1/a); + return -(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*1-s)*(2*Math.PI)/p )); + }, + easeOutElastic: function (t) { + var s=1.70158;var p=0;var a=1; + if (t==0) return 0; if ((t/=1)==1) return 1; if (!p) p=1*.3; + if (a < Math.abs(1)) { a=1; var s=p/4; } + else var s = p/(2*Math.PI) * Math.asin (1/a); + return a*Math.pow(2,-10*t) * Math.sin( (t*1-s)*(2*Math.PI)/p ) + 1; + }, + easeInOutElastic: function (t) { + var s=1.70158;var p=0;var a=1; + if (t==0) return 0; if ((t/=1/2)==2) return 1; if (!p) p=1*(.3*1.5); + if (a < Math.abs(1)) { a=1; var s=p/4; } + else var s = p/(2*Math.PI) * Math.asin (1/a); + if (t < 1) return -.5*(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*1-s)*(2*Math.PI)/p )); + return a*Math.pow(2,-10*(t-=1)) * Math.sin( (t*1-s)*(2*Math.PI)/p )*.5 + 1; + }, + easeInBack: function (t) { + var s = 1.70158; + return 1*(t/=1)*t*((s+1)*t - s); + }, + easeOutBack: function (t) { + var s = 1.70158; + return 1*((t=t/1-1)*t*((s+1)*t + s) + 1); + }, + easeInOutBack: function (t) { + var s = 1.70158; + if ((t/=1/2) < 1) return 1/2*(t*t*(((s*=(1.525))+1)*t - s)); + return 1/2*((t-=2)*t*(((s*=(1.525))+1)*t + s) + 2); + }, + easeInBounce: function (t) { + return 1 - animationOptions.easeOutBounce (1-t); + }, + easeOutBounce: function (t) { + if ((t/=1) < (1/2.75)) { + return 1*(7.5625*t*t); + } else if (t < (2/2.75)) { + return 1*(7.5625*(t-=(1.5/2.75))*t + .75); + } else if (t < (2.5/2.75)) { + return 1*(7.5625*(t-=(2.25/2.75))*t + .9375); + } else { + return 1*(7.5625*(t-=(2.625/2.75))*t + .984375); + } + }, + easeInOutBounce: function (t) { + if (t < 1/2) return animationOptions.easeInBounce (t*2) * .5; + return animationOptions.easeOutBounce (t*2-1) * .5 + 1*.5; + } + }; + + //Variables global to the chart + var width = context.canvas.width; + var height = context.canvas.height; + + + //High pixel density displays - multiply the size of the canvas height/width by the device pixel ratio, then scale. + if (window.devicePixelRatio) { + context.canvas.style.width = width + "px"; + context.canvas.style.height = height + "px"; + context.canvas.height = height * window.devicePixelRatio; + context.canvas.width = width * window.devicePixelRatio; + context.scale(window.devicePixelRatio, window.devicePixelRatio); + } + + this.PolarArea = function(data,options){ + + chart.PolarArea.defaults = { + scaleOverlay : true, + scaleOverride : false, + scaleSteps : null, + scaleStepWidth : null, + scaleStartValue : null, + scaleShowLine : true, + scaleLineColor : "rgba(0,0,0,.1)", + scaleLineWidth : 1, + scaleShowLabels : true, + scaleLabel : "<%=value%>", + scaleFontFamily : "'Arial'", + scaleFontSize : 12, + scaleFontStyle : "normal", + scaleFontColor : "#666", + scaleShowLabelBackdrop : true, + scaleBackdropColor : "rgba(255,255,255,0.75)", + scaleBackdropPaddingY : 2, + scaleBackdropPaddingX : 2, + segmentShowStroke : true, + segmentStrokeColor : "#fff", + segmentStrokeWidth : 2, + animation : true, + animationSteps : 100, + animationEasing : "easeOutBounce", + animateRotate : true, + animateScale : false, + onAnimationComplete : null + }; + + var config = (options)? mergeChartConfig(chart.PolarArea.defaults,options) : chart.PolarArea.defaults; + + return new PolarArea(data,config,context); + }; + + this.Radar = function(data,options){ + + chart.Radar.defaults = { + scaleOverlay : false, + scaleOverride : false, + scaleSteps : null, + scaleStepWidth : null, + scaleStartValue : null, + scaleShowLine : true, + scaleLineColor : "rgba(0,0,0,.1)", + scaleLineWidth : 1, + scaleShowLabels : false, + scaleLabel : "<%=value%>", + scaleFontFamily : "'Arial'", + scaleFontSize : 12, + scaleFontStyle : "normal", + scaleFontColor : "#666", + scaleShowLabelBackdrop : true, + scaleBackdropColor : "rgba(255,255,255,0.75)", + scaleBackdropPaddingY : 2, + scaleBackdropPaddingX : 2, + angleShowLineOut : true, + angleLineColor : "rgba(0,0,0,.1)", + angleLineWidth : 1, + pointLabelFontFamily : "'Arial'", + pointLabelFontStyle : "normal", + pointLabelFontSize : 12, + pointLabelFontColor : "#666", + pointDot : true, + pointDotRadius : 3, + pointDotStrokeWidth : 1, + datasetStroke : true, + datasetStrokeWidth : 2, + datasetFill : true, + animation : true, + animationSteps : 60, + animationEasing : "easeOutQuart", + onAnimationComplete : null + }; + + var config = (options)? mergeChartConfig(chart.Radar.defaults,options) : chart.Radar.defaults; + + return new Radar(data,config,context); + }; + + this.Pie = function(data,options){ + chart.Pie.defaults = { + segmentShowStroke : true, + segmentStrokeColor : "#fff", + segmentStrokeWidth : 2, + animation : true, + animationSteps : 100, + animationEasing : "easeOutBounce", + animateRotate : true, + animateScale : false, + onAnimationComplete : null + }; + + var config = (options)? mergeChartConfig(chart.Pie.defaults,options) : chart.Pie.defaults; + + return new Pie(data,config,context); + }; + + this.Doughnut = function(data,options){ + + chart.Doughnut.defaults = { + segmentShowStroke : true, + segmentStrokeColor : "#fff", + segmentStrokeWidth : 2, + percentageInnerCutout : 50, + animation : true, + animationSteps : 100, + animationEasing : "easeOutBounce", + animateRotate : true, + animateScale : false, + onAnimationComplete : null + }; + + var config = (options)? mergeChartConfig(chart.Doughnut.defaults,options) : chart.Doughnut.defaults; + + return new Doughnut(data,config,context); + + }; + + this.Line = function(data,options){ + + chart.Line.defaults = { + scaleOverlay : false, + scaleOverride : false, + scaleSteps : null, + scaleStepWidth : null, + scaleStartValue : null, + scaleLineColor : "rgba(0,0,0,.1)", + scaleLineWidth : 1, + scaleShowLabels : true, + scaleLabel : "<%=value%>", + scaleFontFamily : "'Arial'", + scaleFontSize : 12, + scaleFontStyle : "normal", + scaleFontColor : "#666", + scaleShowGridLines : true, + scaleGridLineColor : "rgba(0,0,0,.05)", + scaleGridLineWidth : 1, + bezierCurve : true, + pointDot : true, + pointDotRadius : 4, + pointDotStrokeWidth : 2, + datasetStroke : true, + datasetStrokeWidth : 2, + datasetFill : true, + animation : true, + animationSteps : 60, + animationEasing : "easeOutQuart", + onAnimationComplete : null + }; + var config = (options) ? mergeChartConfig(chart.Line.defaults,options) : chart.Line.defaults; + + return new Line(data,config,context); + } + + this.Bar = function(data,options){ + chart.Bar.defaults = { + scaleOverlay : false, + scaleOverride : false, + scaleSteps : null, + scaleStepWidth : null, + scaleStartValue : null, + scaleLineColor : "rgba(0,0,0,.1)", + scaleLineWidth : 1, + scaleShowLabels : true, + scaleLabel : "<%=value%>", + scaleFontFamily : "'Arial'", + scaleFontSize : 12, + scaleFontStyle : "normal", + scaleFontColor : "#666", + scaleShowGridLines : true, + scaleGridLineColor : "rgba(0,0,0,.05)", + scaleGridLineWidth : 1, + barShowStroke : true, + barStrokeWidth : 2, + barValueSpacing : 5, + barDatasetSpacing : 1, + animation : true, + animationSteps : 60, + animationEasing : "easeOutQuart", + onAnimationComplete : null + }; + var config = (options) ? mergeChartConfig(chart.Bar.defaults,options) : chart.Bar.defaults; + + return new Bar(data,config,context); + } + + var clear = function(c){ + c.clearRect(0, 0, width, height); + }; + + var PolarArea = function(data,config,ctx){ + var maxSize, scaleHop, calculatedScale, labelHeight, scaleHeight, valueBounds, labelTemplateString; + + + calculateDrawingSizes(); + + valueBounds = getValueBounds(); + + labelTemplateString = (config.scaleShowLabels)? config.scaleLabel : null; + + //Check and set the scale + if (!config.scaleOverride){ + + calculatedScale = calculateScale(scaleHeight,valueBounds.maxSteps,valueBounds.minSteps,valueBounds.maxValue,valueBounds.minValue,labelTemplateString); + } + else { + calculatedScale = { + steps : config.scaleSteps, + stepValue : config.scaleStepWidth, + graphMin : config.scaleStartValue, + labels : [] + } + populateLabels(labelTemplateString, calculatedScale.labels,calculatedScale.steps,config.scaleStartValue,config.scaleStepWidth); + } + + scaleHop = maxSize/(calculatedScale.steps); + + //Wrap in an animation loop wrapper + animationLoop(config,drawScale,drawAllSegments,ctx); + + function calculateDrawingSizes(){ + maxSize = (Min([width,height])/2); + //Remove whatever is larger - the font size or line width. + + maxSize -= Max([config.scaleFontSize*0.5,config.scaleLineWidth*0.5]); + + labelHeight = config.scaleFontSize*2; + //If we're drawing the backdrop - add the Y padding to the label height and remove from drawing region. + if (config.scaleShowLabelBackdrop){ + labelHeight += (2 * config.scaleBackdropPaddingY); + maxSize -= config.scaleBackdropPaddingY*1.5; + } + + scaleHeight = maxSize; + //If the label height is less than 5, set it to 5 so we don't have lines on top of each other. + labelHeight = Default(labelHeight,5); + } + function drawScale(){ + for (var i=0; i upperValue) {upperValue = data[i].value;} + if (data[i].value < lowerValue) {lowerValue = data[i].value;} + }; + + var maxSteps = Math.floor((scaleHeight / (labelHeight*0.66))); + var minSteps = Math.floor((scaleHeight / labelHeight*0.5)); + + return { + maxValue : upperValue, + minValue : lowerValue, + maxSteps : maxSteps, + minSteps : minSteps + }; + + + } + } + + var Radar = function (data,config,ctx) { + var maxSize, scaleHop, calculatedScale, labelHeight, scaleHeight, valueBounds, labelTemplateString; + + //If no labels are defined set to an empty array, so referencing length for looping doesn't blow up. + if (!data.labels) data.labels = []; + + calculateDrawingSizes(); + + var valueBounds = getValueBounds(); + + labelTemplateString = (config.scaleShowLabels)? config.scaleLabel : null; + + //Check and set the scale + if (!config.scaleOverride){ + + calculatedScale = calculateScale(scaleHeight,valueBounds.maxSteps,valueBounds.minSteps,valueBounds.maxValue,valueBounds.minValue,labelTemplateString); + } + else { + calculatedScale = { + steps : config.scaleSteps, + stepValue : config.scaleStepWidth, + graphMin : config.scaleStartValue, + labels : [] + } + populateLabels(labelTemplateString, calculatedScale.labels,calculatedScale.steps,config.scaleStartValue,config.scaleStepWidth); + } + + scaleHop = maxSize/(calculatedScale.steps); + + animationLoop(config,drawScale,drawAllDataPoints,ctx); + + //Radar specific functions. + function drawAllDataPoints(animationDecimal){ + var rotationDegree = (2*Math.PI)/data.datasets[0].data.length; + + ctx.save(); + //translate to the centre of the canvas. + ctx.translate(width/2,height/2); + + //We accept multiple data sets for radar charts, so show loop through each set + for (var i=0; i Math.PI){ + ctx.textAlign = "right"; + } + else{ + ctx.textAlign = "left"; + } + + ctx.textBaseline = "middle"; + + ctx.fillText(data.labels[k],opposite,-adjacent); + + } + ctx.restore(); + }; + function calculateDrawingSizes(){ + maxSize = (Min([width,height])/2); + + labelHeight = config.scaleFontSize*2; + + var labelLength = 0; + for (var i=0; ilabelLength) labelLength = textMeasurement; + } + + //Figure out whats the largest - the height of the text or the width of what's there, and minus it from the maximum usable size. + maxSize -= Max([labelLength,((config.pointLabelFontSize/2)*1.5)]); + + maxSize -= config.pointLabelFontSize; + maxSize = CapValue(maxSize, null, 0); + scaleHeight = maxSize; + //If the label height is less than 5, set it to 5 so we don't have lines on top of each other. + labelHeight = Default(labelHeight,5); + }; + function getValueBounds() { + var upperValue = Number.MIN_VALUE; + var lowerValue = Number.MAX_VALUE; + + for (var i=0; i upperValue){upperValue = data.datasets[i].data[j]} + if (data.datasets[i].data[j] < lowerValue){lowerValue = data.datasets[i].data[j]} + } + } + + var maxSteps = Math.floor((scaleHeight / (labelHeight*0.66))); + var minSteps = Math.floor((scaleHeight / labelHeight*0.5)); + + return { + maxValue : upperValue, + minValue : lowerValue, + maxSteps : maxSteps, + minSteps : minSteps + }; + + + } + } + + var Pie = function(data,config,ctx){ + var segmentTotal = 0; + + //In case we have a canvas that is not a square. Minus 5 pixels as padding round the edge. + var pieRadius = Min([height/2,width/2]) - 5; + + for (var i=0; i 0){ + ctx.save(); + ctx.textAlign = "right"; + } + else{ + ctx.textAlign = "center"; + } + ctx.fillStyle = config.scaleFontColor; + for (var i=0; i 0){ + ctx.translate(yAxisPosX + i*valueHop,xAxisPosY + config.scaleFontSize); + ctx.rotate(-(rotateLabels * (Math.PI/180))); + ctx.fillText(data.labels[i], 0,0); + ctx.restore(); + } + + else{ + ctx.fillText(data.labels[i], yAxisPosX + i*valueHop,xAxisPosY + config.scaleFontSize+3); + } + + ctx.beginPath(); + ctx.moveTo(yAxisPosX + i * valueHop, xAxisPosY+3); + + //Check i isnt 0, so we dont go over the Y axis twice. + if(config.scaleShowGridLines && i>0){ + ctx.lineWidth = config.scaleGridLineWidth; + ctx.strokeStyle = config.scaleGridLineColor; + ctx.lineTo(yAxisPosX + i * valueHop, 5); + } + else{ + ctx.lineTo(yAxisPosX + i * valueHop, xAxisPosY+3); + } + ctx.stroke(); + } + + //Y axis + ctx.lineWidth = config.scaleLineWidth; + ctx.strokeStyle = config.scaleLineColor; + ctx.beginPath(); + ctx.moveTo(yAxisPosX,xAxisPosY+5); + ctx.lineTo(yAxisPosX,5); + ctx.stroke(); + + ctx.textAlign = "right"; + ctx.textBaseline = "middle"; + for (var j=0; j longestText)? measuredText : longestText; + } + //Add a little extra padding from the y axis + longestText +=10; + } + xAxisLength = width - longestText - widestXLabel; + valueHop = Math.floor(xAxisLength/(data.labels.length-1)); + + yAxisPosX = width-widestXLabel/2-xAxisLength; + xAxisPosY = scaleHeight + config.scaleFontSize/2; + } + function calculateDrawingSizes(){ + maxSize = height; + + //Need to check the X axis first - measure the length of each text metric, and figure out if we need to rotate by 45 degrees. + ctx.font = config.scaleFontStyle + " " + config.scaleFontSize+"px " + config.scaleFontFamily; + widestXLabel = 1; + for (var i=0; i widestXLabel)? textLength : widestXLabel; + } + if (width/data.labels.length < widestXLabel){ + rotateLabels = 45; + if (width/data.labels.length < Math.cos(rotateLabels) * widestXLabel){ + rotateLabels = 90; + maxSize -= widestXLabel; + } + else{ + maxSize -= Math.sin(rotateLabels) * widestXLabel; + } + } + else{ + maxSize -= config.scaleFontSize; + } + + //Add a little padding between the x line and the text + maxSize -= 5; + + + labelHeight = config.scaleFontSize; + + maxSize -= labelHeight; + //Set 5 pixels greater than the font size to allow for a little padding from the X axis. + + scaleHeight = maxSize; + + //Then get the area above we can safely draw on. + + } + function getValueBounds() { + var upperValue = Number.MIN_VALUE; + var lowerValue = Number.MAX_VALUE; + for (var i=0; i upperValue) { upperValue = data.datasets[i].data[j] }; + if ( data.datasets[i].data[j] < lowerValue) { lowerValue = data.datasets[i].data[j] }; + } + }; + + var maxSteps = Math.floor((scaleHeight / (labelHeight*0.66))); + var minSteps = Math.floor((scaleHeight / labelHeight*0.5)); + + return { + maxValue : upperValue, + minValue : lowerValue, + maxSteps : maxSteps, + minSteps : minSteps + }; + + + } + + + } + + var Bar = function(data,config,ctx){ + var maxSize, scaleHop, calculatedScale, labelHeight, scaleHeight, valueBounds, labelTemplateString, valueHop,widestXLabel, xAxisLength,yAxisPosX,xAxisPosY,barWidth, rotateLabels = 0; + + calculateDrawingSizes(); + + valueBounds = getValueBounds(); + //Check and set the scale + labelTemplateString = (config.scaleShowLabels)? config.scaleLabel : ""; + if (!config.scaleOverride){ + + calculatedScale = calculateScale(scaleHeight,valueBounds.maxSteps,valueBounds.minSteps,valueBounds.maxValue,valueBounds.minValue,labelTemplateString); + } + else { + calculatedScale = { + steps : config.scaleSteps, + stepValue : config.scaleStepWidth, + graphMin : config.scaleStartValue, + labels : [] + } + populateLabels(labelTemplateString, calculatedScale.labels,calculatedScale.steps,config.scaleStartValue,config.scaleStepWidth); + } + + scaleHop = Math.floor(scaleHeight/calculatedScale.steps); + calculateXAxisSize(); + animationLoop(config,drawScale,drawBars,ctx); + + function drawBars(animPc){ + ctx.lineWidth = config.barStrokeWidth; + for (var i=0; i 0){ + ctx.save(); + ctx.textAlign = "right"; + } + else{ + ctx.textAlign = "center"; + } + ctx.fillStyle = config.scaleFontColor; + for (var i=0; i 0){ + ctx.translate(yAxisPosX + i*valueHop,xAxisPosY + config.scaleFontSize); + ctx.rotate(-(rotateLabels * (Math.PI/180))); + ctx.fillText(data.labels[i], 0,0); + ctx.restore(); + } + + else{ + ctx.fillText(data.labels[i], yAxisPosX + i*valueHop + valueHop/2,xAxisPosY + config.scaleFontSize+3); + } + + ctx.beginPath(); + ctx.moveTo(yAxisPosX + (i+1) * valueHop, xAxisPosY+3); + + //Check i isnt 0, so we dont go over the Y axis twice. + ctx.lineWidth = config.scaleGridLineWidth; + ctx.strokeStyle = config.scaleGridLineColor; + ctx.lineTo(yAxisPosX + (i+1) * valueHop, 5); + ctx.stroke(); + } + + //Y axis + ctx.lineWidth = config.scaleLineWidth; + ctx.strokeStyle = config.scaleLineColor; + ctx.beginPath(); + ctx.moveTo(yAxisPosX,xAxisPosY+5); + ctx.lineTo(yAxisPosX,5); + ctx.stroke(); + + ctx.textAlign = "right"; + ctx.textBaseline = "middle"; + for (var j=0; j longestText)? measuredText : longestText; + } + //Add a little extra padding from the y axis + longestText +=10; + } + xAxisLength = width - longestText - widestXLabel; + valueHop = Math.floor(xAxisLength/(data.labels.length)); + + barWidth = (valueHop - config.scaleGridLineWidth*2 - (config.barValueSpacing*2) - (config.barDatasetSpacing*data.datasets.length-1) - ((config.barStrokeWidth/2)*data.datasets.length-1))/data.datasets.length; + + yAxisPosX = width-widestXLabel/2-xAxisLength; + xAxisPosY = scaleHeight + config.scaleFontSize/2; + } + function calculateDrawingSizes(){ + maxSize = height; + + //Need to check the X axis first - measure the length of each text metric, and figure out if we need to rotate by 45 degrees. + ctx.font = config.scaleFontStyle + " " + config.scaleFontSize+"px " + config.scaleFontFamily; + widestXLabel = 1; + for (var i=0; i widestXLabel)? textLength : widestXLabel; + } + if (width/data.labels.length < widestXLabel){ + rotateLabels = 45; + if (width/data.labels.length < Math.cos(rotateLabels) * widestXLabel){ + rotateLabels = 90; + maxSize -= widestXLabel; + } + else{ + maxSize -= Math.sin(rotateLabels) * widestXLabel; + } + } + else{ + maxSize -= config.scaleFontSize; + } + + //Add a little padding between the x line and the text + maxSize -= 5; + + + labelHeight = config.scaleFontSize; + + maxSize -= labelHeight; + //Set 5 pixels greater than the font size to allow for a little padding from the X axis. + + scaleHeight = maxSize; + + //Then get the area above we can safely draw on. + + } + function getValueBounds() { + var upperValue = Number.MIN_VALUE; + var lowerValue = Number.MAX_VALUE; + for (var i=0; i upperValue) { upperValue = data.datasets[i].data[j] }; + if ( data.datasets[i].data[j] < lowerValue) { lowerValue = data.datasets[i].data[j] }; + } + }; + + var maxSteps = Math.floor((scaleHeight / (labelHeight*0.66))); + var minSteps = Math.floor((scaleHeight / labelHeight*0.5)); + + return { + maxValue : upperValue, + minValue : lowerValue, + maxSteps : maxSteps, + minSteps : minSteps + }; + + + } + } + + function calculateOffset(val,calculatedScale,scaleHop){ + var outerValue = calculatedScale.steps * calculatedScale.stepValue; + var adjustedValue = val - calculatedScale.graphMin; + var scalingFactor = CapValue(adjustedValue/outerValue,1,0); + return (scaleHop*calculatedScale.steps) * scalingFactor; + } + + function animationLoop(config,drawScale,drawData,ctx){ + var animFrameAmount = (config.animation)? 1/CapValue(config.animationSteps,Number.MAX_VALUE,1) : 1, + easingFunction = animationOptions[config.animationEasing], + percentAnimComplete =(config.animation)? 0 : 1; + + + + if (typeof drawScale !== "function") drawScale = function(){}; + + requestAnimFrame(animLoop); + + function animateFrame(){ + var easeAdjustedAnimationPercent =(config.animation)? CapValue(easingFunction(percentAnimComplete),null,0) : 1; + clear(ctx); + if(config.scaleOverlay){ + drawData(easeAdjustedAnimationPercent); + drawScale(); + } else { + drawScale(); + drawData(easeAdjustedAnimationPercent); + } + } + function animLoop(){ + //We need to check if the animation is incomplete (less than 1), or complete (1). + percentAnimComplete += animFrameAmount; + animateFrame(); + //Stop the loop continuing forever + if (percentAnimComplete <= 1){ + requestAnimFrame(animLoop); + } + else{ + if (typeof config.onAnimationComplete == "function") config.onAnimationComplete(); + } + + } + + } + + //Declare global functions to be called within this namespace here. + + + // shim layer with setTimeout fallback + var requestAnimFrame = (function(){ + return window.requestAnimationFrame || + window.webkitRequestAnimationFrame || + window.mozRequestAnimationFrame || + window.oRequestAnimationFrame || + window.msRequestAnimationFrame || + function(callback) { + window.setTimeout(callback, 1000 / 60); + }; + })(); + + function calculateScale(drawingHeight,maxSteps,minSteps,maxValue,minValue,labelTemplateString){ + var graphMin,graphMax,graphRange,stepValue,numberOfSteps,valueRange,rangeOrderOfMagnitude,decimalNum; + + valueRange = maxValue - minValue; + + rangeOrderOfMagnitude = calculateOrderOfMagnitude(valueRange); + + graphMin = Math.floor(minValue / (1 * Math.pow(10, rangeOrderOfMagnitude))) * Math.pow(10, rangeOrderOfMagnitude); + + graphMax = Math.ceil(maxValue / (1 * Math.pow(10, rangeOrderOfMagnitude))) * Math.pow(10, rangeOrderOfMagnitude); + + graphRange = graphMax - graphMin; + + stepValue = Math.pow(10, rangeOrderOfMagnitude); + + numberOfSteps = Math.round(graphRange / stepValue); + + //Compare number of steps to the max and min for that size graph, and add in half steps if need be. + while(numberOfSteps < minSteps || numberOfSteps > maxSteps) { + if (numberOfSteps < minSteps){ + stepValue /= 2; + numberOfSteps = Math.round(graphRange/stepValue); + } + else{ + stepValue *=2; + numberOfSteps = Math.round(graphRange/stepValue); + } + }; + + var labels = []; + populateLabels(labelTemplateString, labels, numberOfSteps, graphMin, stepValue); + + return { + steps : numberOfSteps, + stepValue : stepValue, + graphMin : graphMin, + labels : labels + + } + + function calculateOrderOfMagnitude(val){ + return Math.floor(Math.log(val) / Math.LN10); + } + + + } + + //Populate an array of all the labels by interpolating the string. + function populateLabels(labelTemplateString, labels, numberOfSteps, graphMin, stepValue) { + if (labelTemplateString) { + //Fix floating point errors by setting to fixed the on the same decimal as the stepValue. + for (var i = 1; i < numberOfSteps + 1; i++) { + labels.push(tmpl(labelTemplateString, {value: (graphMin + (stepValue * i)).toFixed(getDecimalPlaces(stepValue))})); + } + } + } + + //Max value from array + function Max( array ){ + return Math.max.apply( Math, array ); + }; + //Min value from array + function Min( array ){ + return Math.min.apply( Math, array ); + }; + //Default if undefined + function Default(userDeclared,valueIfFalse){ + if(!userDeclared){ + return valueIfFalse; + } else { + return userDeclared; + } + }; + //Is a number function + function isNumber(n) { + return !isNaN(parseFloat(n)) && isFinite(n); + } + //Apply cap a value at a high or low number + function CapValue(valueToCap, maxValue, minValue){ + if(isNumber(maxValue)) { + if( valueToCap > maxValue ) { + return maxValue; + } + } + if(isNumber(minValue)){ + if ( valueToCap < minValue ){ + return minValue; + } + } + return valueToCap; + } + function getDecimalPlaces (num){ + var numberOfDecimalPlaces; + if (num%1!=0){ + return num.toString().split(".")[1].length + } + else{ + return 0; + } + + } + + function mergeChartConfig(defaults,userDefined){ + var returnObj = {}; + for (var attrname in defaults) { returnObj[attrname] = defaults[attrname]; } + for (var attrname in userDefined) { returnObj[attrname] = userDefined[attrname]; } + return returnObj; + } + + //Javascript micro templating by John Resig - source at http://ejohn.org/blog/javascript-micro-templating/ + var cache = {}; + + function tmpl(str, data){ + // Figure out if we're getting a template, or if we need to + // load the template - and be sure to cache the result. + var fn = !/\W/.test(str) ? + cache[str] = cache[str] || + tmpl(document.getElementById(str).innerHTML) : + + // Generate a reusable function that will serve as a template + // generator (and which will be cached). + new Function("obj", + "var p=[],print=function(){p.push.apply(p,arguments);};" + + + // Introduce the data as local variables using with(){} + "with(obj){p.push('" + + + // Convert the template into pure JavaScript + str + .replace(/[\r\t\n]/g, " ") + .split("<%").join("\t") + .replace(/((^|%>)[^\t]*)'/g, "$1\r") + .replace(/\t=(.*?)%>/g, "',$1,'") + .split("\t").join("');") + .split("%>").join("p.push('") + .split("\r").join("\\'") + + "');}return p.join('');"); + + // Provide some basic currying to the user + return data ? fn( data ) : fn; + }; +} diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 90b9689..51ef637 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -14,4 +14,4 @@ //= require jquery_ujs //= require twitter/bootstrap //= require turbolinks -//= require_tree . +//= require Chart diff --git a/app/assets/javascripts/reports/income_v_expense.js b/app/assets/javascripts/reports/income_v_expense.js new file mode 100644 index 0000000..f7a8c69 --- /dev/null +++ b/app/assets/javascripts/reports/income_v_expense.js @@ -0,0 +1,3 @@ +$.get("/report/income_v_expense.json",function(barChartData){ + var myLine = new Chart(document.getElementById("canvas").getContext("2d")).Bar(barChartData); +}); diff --git a/app/assets/javascripts/reports/spending_by_category.js b/app/assets/javascripts/reports/spending_by_category.js new file mode 100644 index 0000000..92118f9 --- /dev/null +++ b/app/assets/javascripts/reports/spending_by_category.js @@ -0,0 +1,3 @@ +$.get("/report/spending_by_category.json",function(barChartData){ + var myLine = new Chart(document.getElementById("canvas").getContext("2d")).Bar(barChartData); +}); diff --git a/app/assets/javascripts/reports/spending_by_payee.js b/app/assets/javascripts/reports/spending_by_payee.js new file mode 100644 index 0000000..e752481 --- /dev/null +++ b/app/assets/javascripts/reports/spending_by_payee.js @@ -0,0 +1,3 @@ +$.get("/report/spending_by_payee.json",function(barChartData){ + var myLine = new Chart(document.getElementById("canvas").getContext("2d")).Bar(barChartData); +}); From 297c82298a1266bba08e4f58a4f76a489477e4b8 Mon Sep 17 00:00:00 2001 From: Juan Carlos Date: Fri, 25 Oct 2013 17:23:49 -0500 Subject: [PATCH 05/19] changed methods in the controller --- app/controllers/reports_controller.rb | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/app/controllers/reports_controller.rb b/app/controllers/reports_controller.rb index 0b4c539..c74d016 100644 --- a/app/controllers/reports_controller.rb +++ b/app/controllers/reports_controller.rb @@ -17,7 +17,14 @@ def spending_by_payee end def net_worth - @report = Transaction.select(:account_id, :amount).group(:account_id).sum(:amount) + @report = {} + Transaction.net_worth.each do |e| + @report[e.month] = e.amount.to_f + end + respond_to do |format| + format.html + format.json { render json: net_worth_json } + end end def spending_by_category @@ -73,4 +80,19 @@ def spending_by_category_json } end + + def net_worth_json + { + labels: @report.keys, + datasets: [ + { + fillColor: "rgba(220,220,220,0.5)", + strokeColor: "rgba(220,220,220,1)", + fillColor: "rgba(225, 0, 0, 0.5)", + strokeColor: "rgba(220, 220, 220, 1)", + data: @report.values + } + ] + } + end end From 1e095f7d91038a3854ee0a9452f7b3cddff0cb75 Mon Sep 17 00:00:00 2001 From: Juan Carlos Date: Fri, 25 Oct 2013 17:24:23 -0500 Subject: [PATCH 06/19] solved scope for net worth --- app/models/transaction.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/models/transaction.rb b/app/models/transaction.rb index ee2cc2b..808bac4 100644 --- a/app/models/transaction.rb +++ b/app/models/transaction.rb @@ -20,6 +20,7 @@ class Transaction < ActiveRecord::Base scope :interval, ->(from, to) { where("transactions.date >= ? AND transactions.date <= ?", from, to) } scope :total_amount, -> { select('count(transactions.amount) as total_count', 'sum(transactions.amount) as total_amount') } + scope :net_worth, -> { select("date_part('month', date) as month, sum(amount) as amount").where("date between '2013-01-01' and '2014-01-01'").group("month") } belongs_to :account has_many :splits From 5004570467905e927011e5f29cee3f6b16cdc570 Mon Sep 17 00:00:00 2001 From: Juan Carlos Date: Fri, 25 Oct 2013 17:25:19 -0500 Subject: [PATCH 07/19] created javascript functions for each view --- app/assets/javascripts/reports/net_worth.js | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 app/assets/javascripts/reports/net_worth.js diff --git a/app/assets/javascripts/reports/net_worth.js b/app/assets/javascripts/reports/net_worth.js new file mode 100644 index 0000000..b3e3f58 --- /dev/null +++ b/app/assets/javascripts/reports/net_worth.js @@ -0,0 +1,3 @@ +$.get("/report/net_worth.json",function(barChartData){ + var myLine = new Chart(document.getElementById("canvas").getContext("2d")).Bar(barChartData); +}); From a1f16688f441b76d984fd983edc55ece3d624e76 Mon Sep 17 00:00:00 2001 From: Juan Carlos Date: Fri, 25 Oct 2013 17:25:52 -0500 Subject: [PATCH 08/19] changed haml for the views --- app/views/reports/_menu.html.haml | 7 +++++++ app/views/reports/income_v_expense.html.haml | 1 + app/views/reports/net_worth.html.haml | 15 +++++++++++++++ app/views/reports/show.html.haml | 8 +------- app/views/reports/spending_by_category.html.haml | 5 +++-- app/views/reports/spending_by_payee.html.haml | 1 + 6 files changed, 28 insertions(+), 9 deletions(-) create mode 100644 app/views/reports/_menu.html.haml diff --git a/app/views/reports/_menu.html.haml b/app/views/reports/_menu.html.haml new file mode 100644 index 0000000..4abad6d --- /dev/null +++ b/app/views/reports/_menu.html.haml @@ -0,0 +1,7 @@ +=link_to 'Income vs Expense', income_v_expense_report_path +| +=link_to 'Spending by Payee', spending_by_payee_report_path +| +=link_to 'Net Worth', net_worth_report_path +| +=link_to 'Speding by Category', spending_by_category_report_path diff --git a/app/views/reports/income_v_expense.html.haml b/app/views/reports/income_v_expense.html.haml index 565a481..21c2ba4 100644 --- a/app/views/reports/income_v_expense.html.haml +++ b/app/views/reports/income_v_expense.html.haml @@ -1,3 +1,4 @@ += render 'menu' %h1 Income vs Expense Report %table.table-bordered %thead diff --git a/app/views/reports/net_worth.html.haml b/app/views/reports/net_worth.html.haml index e69de29..d054560 100644 --- a/app/views/reports/net_worth.html.haml +++ b/app/views/reports/net_worth.html.haml @@ -0,0 +1,15 @@ += render 'menu' +%h1 Net Worth +%table.table-bordered + %thead + %tr + %th Month + %th Amount + %tbody + - @report.each do |key, value| + %tr + %td= key + %td= value + +%canvas#canvas{height: '450', width: '600'} += javascript_include_tag 'reports/net_worth' diff --git a/app/views/reports/show.html.haml b/app/views/reports/show.html.haml index 1a9b8c5..331e6ef 100644 --- a/app/views/reports/show.html.haml +++ b/app/views/reports/show.html.haml @@ -1,8 +1,2 @@ += render 'menu' %h2 Reports -=link_to 'Income vs Expense', income_v_expense_report_path -| -=link_to 'Spending by Payee', spending_by_payee_report_path -| -=link_to 'Net Worth', net_worth_report_path -| -=link_to 'Speding by Category', spending_by_category_report_path diff --git a/app/views/reports/spending_by_category.html.haml b/app/views/reports/spending_by_category.html.haml index ecb2e8a..eb7aec0 100644 --- a/app/views/reports/spending_by_category.html.haml +++ b/app/views/reports/spending_by_category.html.haml @@ -1,3 +1,4 @@ += render 'menu' %h1 Spending by Category %table.table-bordered %thead @@ -7,8 +8,8 @@ %tbody - @report.each do |key, value| %tr - %th= key - %th= value + %td= key + %td= value %canvas#canvas{height: '450', width: '600'} = javascript_include_tag 'reports/spending_by_category' diff --git a/app/views/reports/spending_by_payee.html.haml b/app/views/reports/spending_by_payee.html.haml index b94e000..5d9a621 100644 --- a/app/views/reports/spending_by_payee.html.haml +++ b/app/views/reports/spending_by_payee.html.haml @@ -1,3 +1,4 @@ += render 'menu' %h1 Income vs Expense Report %table.table-bordered %thead From ebfc2559c1dcb6081e4992dd711b9a52438c8e3b Mon Sep 17 00:00:00 2001 From: Juan Carlos Date: Fri, 25 Oct 2013 17:36:39 -0500 Subject: [PATCH 09/19] refactor controller --- app/controllers/reports_controller.rb | 75 +++++---------------------- 1 file changed, 12 insertions(+), 63 deletions(-) diff --git a/app/controllers/reports_controller.rb b/app/controllers/reports_controller.rb index c74d016..b999498 100644 --- a/app/controllers/reports_controller.rb +++ b/app/controllers/reports_controller.rb @@ -1,19 +1,13 @@ class ReportsController < ApplicationController def income_v_expense - @report = Transaction.all.group(:pm_type).sum(:amount) - respond_to do |format| - format.html - format.json { render json: income_v_expense_json } - end + @report = Transaction.group(:pm_type).sum(:amount) + format_respond end def spending_by_payee - @report = Transaction.all.group(:payee_id).sum(:amount) - respond_to do |format| - format.html - format.json { render json: spending_by_payee_json } - end + @report = Transaction.group(:payee_id).sum(:amount) + format_respond end def net_worth @@ -21,67 +15,22 @@ def net_worth Transaction.net_worth.each do |e| @report[e.month] = e.amount.to_f end - respond_to do |format| - format.html - format.json { render json: net_worth_json } - end + format_respond end def spending_by_category - @report = Transaction.all.group(:category_id).sum(:amount) + @report = Transaction.group(:category_id).sum(:amount) + format_respond + end + + def format_respond respond_to do |format| format.html - format.json { render json: spending_by_category_json } + format.json { render json: json_for_chart } end end - def income_v_expense_json - { - labels: @report.keys, - datasets: [ - { - fillColor: "rgba(220,220,220,0.5)", - strokeColor: "rgba(220,220,220,1)", - fillColor: "rgba(225, 0, 0, 0.5)", - strokeColor: "rgba(220, 220, 220, 1)", - data: @report.values - } - ] - } - end - - def spending_by_payee_json - { - labels: @report.keys, - datasets: [ - { - fillColor: "rgba(220,220,220,0.5)", - strokeColor: "rgba(220,220,220,1)", - fillColor: "rgba(225, 0, 0, 0.5)", - strokeColor: "rgba(220, 220, 220, 1)", - data: @report.values - } - ] - } - end - - def spending_by_category_json - { - labels: @report.keys, - datasets: [ - { - fillColor: "rgba(220,220,220,0.5)", - strokeColor: "rgba(220,220,220,1)", - fillColor: "rgba(225, 0, 0, 0.5)", - strokeColor: "rgba(220, 220, 220, 1)", - data: @report.values - } - ] - } - - end - - def net_worth_json + def json_for_chart { labels: @report.keys, datasets: [ From deea7df909fe9c996ee8d284486c5b3bd0150c7b Mon Sep 17 00:00:00 2001 From: Juan Carlos Date: Mon, 28 Oct 2013 13:34:11 -0600 Subject: [PATCH 10/19] refactored table to show values of reports --- app/views/reports/_table_body.html.haml | 4 ++++ app/views/reports/income_v_expense.html.haml | 5 +---- app/views/reports/net_worth.html.haml | 5 +---- app/views/reports/spending_by_category.html.haml | 5 +---- app/views/reports/spending_by_payee.html.haml | 5 +---- 5 files changed, 8 insertions(+), 16 deletions(-) create mode 100644 app/views/reports/_table_body.html.haml diff --git a/app/views/reports/_table_body.html.haml b/app/views/reports/_table_body.html.haml new file mode 100644 index 0000000..3f98f2c --- /dev/null +++ b/app/views/reports/_table_body.html.haml @@ -0,0 +1,4 @@ +- report.each do |key, value| + %tr + %th= key + %th= value diff --git a/app/views/reports/income_v_expense.html.haml b/app/views/reports/income_v_expense.html.haml index 21c2ba4..57c568d 100644 --- a/app/views/reports/income_v_expense.html.haml +++ b/app/views/reports/income_v_expense.html.haml @@ -6,10 +6,7 @@ %th Pm Type %th Amount %tbody - - @report.each do |key, value| - %tr - %th= key - %th= value + = render partial: "table_body", locals: {report: @report} %canvas#canvas{height: '450', width: '600'} = javascript_include_tag 'reports/income_v_expense' diff --git a/app/views/reports/net_worth.html.haml b/app/views/reports/net_worth.html.haml index d054560..f0603ad 100644 --- a/app/views/reports/net_worth.html.haml +++ b/app/views/reports/net_worth.html.haml @@ -6,10 +6,7 @@ %th Month %th Amount %tbody - - @report.each do |key, value| - %tr - %td= key - %td= value + = render partial: "table_body", locals: {report: @report} %canvas#canvas{height: '450', width: '600'} = javascript_include_tag 'reports/net_worth' diff --git a/app/views/reports/spending_by_category.html.haml b/app/views/reports/spending_by_category.html.haml index eb7aec0..9aa9e50 100644 --- a/app/views/reports/spending_by_category.html.haml +++ b/app/views/reports/spending_by_category.html.haml @@ -6,10 +6,7 @@ %th Id Category %th Amount %tbody - - @report.each do |key, value| - %tr - %td= key - %td= value + = render partial: "table_body", locals: {report: @report} %canvas#canvas{height: '450', width: '600'} = javascript_include_tag 'reports/spending_by_category' diff --git a/app/views/reports/spending_by_payee.html.haml b/app/views/reports/spending_by_payee.html.haml index 5d9a621..e14ab44 100644 --- a/app/views/reports/spending_by_payee.html.haml +++ b/app/views/reports/spending_by_payee.html.haml @@ -6,10 +6,7 @@ %th Pm Type %th Amount %tbody - - @report.each do |key, value| - %tr - %th= key - %th= value + = render partial: "table_body", locals: {report: @report} %canvas#canvas{height: '450', width: '600'} = javascript_include_tag 'reports/spending_by_payee' From 16b516f526c2c083c54397aa6baf238527ccfae3 Mon Sep 17 00:00:00 2001 From: Juan Carlos Date: Mon, 28 Oct 2013 15:10:12 -0600 Subject: [PATCH 11/19] added gems for testing purposes --- Gemfile | 11 +++++++++++ Gemfile.lock | 45 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+) diff --git a/Gemfile b/Gemfile index f58c20f..edcf492 100644 --- a/Gemfile +++ b/Gemfile @@ -33,6 +33,7 @@ gem 'haml' gem 'twitter-bootstrap-rails', :git => 'git://github.com/seyhunak/twitter-bootstrap-rails.git' gem 'less-rails' gem "therubyracer" +gem 'faker' group :doc do # bundle exec rake doc:rails generates the API under doc/api. @@ -46,6 +47,16 @@ gem 'pry-rails' gem 'ruby-progressbar' +group :test do + gem 'database_cleaner' + gem 'rspec' + gem 'rspec-rails' + gem 'capybara' + gem 'factory_girl' + gem 'factory_girl_rails' + gem 'shoulda-matchers' +end + # Use ActiveModel has_secure_password # gem 'bcrypt-ruby', '~> 3.0.0' diff --git a/Gemfile.lock b/Gemfile.lock index cb0d74c..5f15c1c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -38,6 +38,12 @@ GEM arel (4.0.0) atomic (1.1.9) builder (3.1.4) + capybara (2.1.0) + mime-types (>= 1.16) + nokogiri (>= 1.3.3) + rack (>= 1.0.0) + rack-test (>= 0.5.4) + xpath (~> 2.0) coderay (1.0.9) coffee-rails (4.0.0) coffee-script (>= 2.2.0) @@ -47,9 +53,18 @@ GEM execjs coffee-script-source (1.6.2) commonjs (0.2.6) + database_cleaner (1.2.0) + diff-lcs (1.2.4) erubis (2.7.0) execjs (1.4.0) multi_json (~> 1.0) + factory_girl (4.2.0) + activesupport (>= 3.0.0) + factory_girl_rails (4.2.1) + factory_girl (~> 4.2.0) + railties (>= 3.0.0) + faker (1.2.0) + i18n (~> 0.5) haml (4.0.3) tilt hike (1.2.2) @@ -71,8 +86,11 @@ GEM treetop (~> 1.4.8) method_source (0.8.1) mime-types (1.23) + mini_portile (0.5.2) minitest (4.7.4) multi_json (1.7.4) + nokogiri (1.6.0) + mini_portile (~> 0.5.0) pg (0.15.1) polyglot (0.3.3) pry (0.9.12.2) @@ -104,6 +122,21 @@ GEM rdoc (3.12.2) json (~> 1.4) ref (1.0.5) + rspec (2.14.1) + rspec-core (~> 2.14.0) + rspec-expectations (~> 2.14.0) + rspec-mocks (~> 2.14.0) + rspec-core (2.14.6) + rspec-expectations (2.14.3) + diff-lcs (>= 1.1.3, < 2.0) + rspec-mocks (2.14.4) + rspec-rails (2.14.0) + actionpack (>= 3.0) + activesupport (>= 3.0) + railties (>= 3.0) + rspec-core (~> 2.14.0) + rspec-expectations (~> 2.14.0) + rspec-mocks (~> 2.14.0) ruby-progressbar (1.1.1) sass (3.2.9) sass-rails (4.0.0.rc1) @@ -114,6 +147,8 @@ GEM sdoc (0.3.20) json (>= 1.1.3) rdoc (~> 3.10) + shoulda-matchers (2.4.0) + activesupport (>= 3.0.0) slop (3.4.5) sprockets (2.10.0) hike (~> 1.2) @@ -141,13 +176,20 @@ GEM uglifier (2.1.1) execjs (>= 0.3.0) multi_json (~> 1.0, >= 1.0.2) + xpath (2.0.0) + nokogiri (~> 1.3) yard (0.8.7) PLATFORMS ruby DEPENDENCIES + capybara coffee-rails (~> 4.0.0) + database_cleaner + factory_girl + factory_girl_rails + faker haml jbuilder (~> 1.0.1) jquery-rails @@ -157,9 +199,12 @@ DEPENDENCIES pry-doc pry-rails rails (= 4.0.0.rc1) + rspec + rspec-rails ruby-progressbar sass-rails (~> 4.0.0.rc1) sdoc + shoulda-matchers sqlite3 therubyracer turbolinks From 44c3e7ec1c33b5aeeb08113daaf974a8bdb74db3 Mon Sep 17 00:00:00 2001 From: Juan Carlos Date: Mon, 28 Oct 2013 15:10:37 -0600 Subject: [PATCH 12/19] created new folder for specs --- spec/factories/account.rb | 39 ++++++++++++++++++++++++++++ spec/factories/transaction.rb | 49 +++++++++++++++++++++++++++++++++++ spec/spec_helper.rb | 21 +++++++++++++++ 3 files changed, 109 insertions(+) create mode 100644 spec/factories/account.rb create mode 100644 spec/factories/transaction.rb create mode 100644 spec/spec_helper.rb diff --git a/spec/factories/account.rb b/spec/factories/account.rb new file mode 100644 index 0000000..f52af00 --- /dev/null +++ b/spec/factories/account.rb @@ -0,0 +1,39 @@ +require 'faker' + +boolean = [true, false] +currency = ["AED", "ALL", "CAD", "CNY", "MXN", "IRR", "JPY", "USD", "UYU"] +FactoryGirl.define do + factory :account do + deleted false + pm_id (0..2).to_a.sample + pm_account_type (0..8).to_a.sample + display_order (1..9).to_a.sample + name Faker::Company.name + balance_overall (1..9).to_a.sample + balance_cleared (1..9).to_a.sample + number Faker::Number.number(2) + institution Faker::Company.suffix + phone Faker::PhoneNumber.cell_phone + expiration_date Faker::Business.credit_card_expiry_date.strftime("%d%m%Y") + check_number (1..9).to_a.sample + notes Faker::Lorem.paragraph + pm_icon "image" + url Faker::Internet.url + of_x_id "dummy" + of_x_url Faker::Internet.domain_word + password Faker::Internet.password + fee (1..9).to_a.sample + fixed_percent (1..9).to_a.sample + limit_amount (1..9).to_a.sample + limit boolean.sample + total_worth boolean.sample + exchange_rate (1..9).to_a.sample + currency_code currency.sample + last_sync_time Time.now + routing_number (1..9).to_a.sample + overdraft_account_id (1..9).to_a.sample + keep_the_change_account_id (1..9).to_a.sample + heek_change_round_to (1..9).to_a.sample + uuid Faker::Code.isbn(64) + end +end diff --git a/spec/factories/transaction.rb b/spec/factories/transaction.rb new file mode 100644 index 0000000..558d34a --- /dev/null +++ b/spec/factories/transaction.rb @@ -0,0 +1,49 @@ +require 'faker' + +currency = ["AED", "ALL", "CAD", "CNY", "MXN", "IRR", "JPY", "USD", "UYU"] +account = FactoryGirl.create(:account) +payee = FactoryGirl.create(:payee) +department = FactoryGirl.create(:department) +category = FactoryGirl.create(:category) + + +FactoryGirl.define do + factory :transaction do + pm_type (0..2).to_a.sample + pm_id (0..8).to_a.sample + account_id account.id + pm_account_id account.pm_id + pm_payee payee.name + pm_sub_total (10..10_000).to_a.sample + pm_of_x_id "dummy" + pm_image "dummy" + pm_overdraft_id (1..30).to_a.sample.to_s + date Time.now + deleted false + check_number (1..10).to_a.sample + payee_name payee.name + payee_id payee.pm_id + category_id category.id + department_id department.id + amount Faker::Number.number(2) + cleared true + uuid Faker::Code.isbn + end +end + +transaction = FactoryGirl.create(:transaction) + +FactoryGirl.define do + factory :split do + pm_id transaction.pm_id + transaction_id transaction.id + amount transaction.amount + xrate (1..10).to_a.sample + category_id transaction.category_id + class_id (0..10).to_a.sample + memo Faker::Lorem.paragraph + transfer_to_account_id (1..100).to_a.sample + currency_code currency.sample + of_x_id "dummy" + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb new file mode 100644 index 0000000..1754b20 --- /dev/null +++ b/spec/spec_helper.rb @@ -0,0 +1,21 @@ +# This file is copied to spec/ when you run 'rails generate rspec:install' +ENV["RAILS_ENV"] ||= 'test' +require File.expand_path("../../config/environment", __FILE__) +require 'rspec/rails' +require 'rspec/autorun' +require 'factory_girl_rails' + +Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f } + +ActiveRecord::Migration.check_pending! if defined?(ActiveRecord::Migration) + +RSpec.configure do |config| + config.include FactoryGirl::Syntax::Methods + config.fixture_path = "#{::Rails.root}/spec/fixtures" + config.use_transactional_fixtures = true + + config.infer_base_class_for_anonymous_controllers = false + + config.order = "random" + +end From 8fbbdbb5efec8f5307eee4e39c0332e641b4a2f5 Mon Sep 17 00:00:00 2001 From: Juan Carlos Date: Mon, 28 Oct 2013 17:45:25 -0600 Subject: [PATCH 13/19] fixed issues with the views --- app/views/reports/_menu.html.haml | 2 +- app/views/reports/net_worth.html.haml | 2 +- app/views/reports/spending_by_category.html.haml | 2 +- app/views/reports/spending_by_payee.html.haml | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/views/reports/_menu.html.haml b/app/views/reports/_menu.html.haml index 4abad6d..b50dd76 100644 --- a/app/views/reports/_menu.html.haml +++ b/app/views/reports/_menu.html.haml @@ -4,4 +4,4 @@ | =link_to 'Net Worth', net_worth_report_path | -=link_to 'Speding by Category', spending_by_category_report_path +=link_to 'Spending by Category', spending_by_category_report_path diff --git a/app/views/reports/net_worth.html.haml b/app/views/reports/net_worth.html.haml index f0603ad..195a2f3 100644 --- a/app/views/reports/net_worth.html.haml +++ b/app/views/reports/net_worth.html.haml @@ -1,5 +1,5 @@ = render 'menu' -%h1 Net Worth +%h1 Net Worth Report %table.table-bordered %thead %tr diff --git a/app/views/reports/spending_by_category.html.haml b/app/views/reports/spending_by_category.html.haml index 9aa9e50..91ac649 100644 --- a/app/views/reports/spending_by_category.html.haml +++ b/app/views/reports/spending_by_category.html.haml @@ -1,5 +1,5 @@ = render 'menu' -%h1 Spending by Category +%h1 Spending by Category Report %table.table-bordered %thead %tr diff --git a/app/views/reports/spending_by_payee.html.haml b/app/views/reports/spending_by_payee.html.haml index e14ab44..30fc553 100644 --- a/app/views/reports/spending_by_payee.html.haml +++ b/app/views/reports/spending_by_payee.html.haml @@ -1,9 +1,9 @@ = render 'menu' -%h1 Income vs Expense Report +%h1 Spending by Payee Report %table.table-bordered %thead %tr - %th Pm Type + %th Payee Id %th Amount %tbody = render partial: "table_body", locals: {report: @report} From 4694476c466f8cba83930640f79bb461764e3569 Mon Sep 17 00:00:00 2001 From: Juan Carlos Date: Mon, 28 Oct 2013 17:46:11 -0600 Subject: [PATCH 14/19] added some gems for testing purposes --- Gemfile | 3 +++ Gemfile.lock | 19 +++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/Gemfile b/Gemfile index edcf492..60b82d6 100644 --- a/Gemfile +++ b/Gemfile @@ -52,6 +52,9 @@ group :test do gem 'rspec' gem 'rspec-rails' gem 'capybara' + gem 'capybara-webkit' + gem 'capybara-firebug' + gem "selenium-webdriver" gem 'factory_girl' gem 'factory_girl_rails' gem 'shoulda-matchers' diff --git a/Gemfile.lock b/Gemfile.lock index 5f15c1c..f9cea64 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -44,6 +44,14 @@ GEM rack (>= 1.0.0) rack-test (>= 0.5.4) xpath (~> 2.0) + capybara-firebug (2.0.0) + capybara (>= 1.0, < 3.0) + selenium-webdriver + capybara-webkit (1.0.0) + capybara (~> 2.0, >= 2.0.2) + json + childprocess (0.3.9) + ffi (~> 1.0, >= 1.0.11) coderay (1.0.9) coffee-rails (4.0.0) coffee-script (>= 2.2.0) @@ -65,6 +73,7 @@ GEM railties (>= 3.0.0) faker (1.2.0) i18n (~> 0.5) + ffi (1.9.0) haml (4.0.3) tilt hike (1.2.2) @@ -138,6 +147,7 @@ GEM rspec-expectations (~> 2.14.0) rspec-mocks (~> 2.14.0) ruby-progressbar (1.1.1) + rubyzip (1.0.0) sass (3.2.9) sass-rails (4.0.0.rc1) railties (>= 4.0.0.beta, < 5.0) @@ -147,6 +157,11 @@ GEM sdoc (0.3.20) json (>= 1.1.3) rdoc (~> 3.10) + selenium-webdriver (2.37.0) + childprocess (>= 0.2.5) + multi_json (~> 1.0) + rubyzip (~> 1.0.0) + websocket (~> 1.0.4) shoulda-matchers (2.4.0) activesupport (>= 3.0.0) slop (3.4.5) @@ -176,6 +191,7 @@ GEM uglifier (2.1.1) execjs (>= 0.3.0) multi_json (~> 1.0, >= 1.0.2) + websocket (1.0.7) xpath (2.0.0) nokogiri (~> 1.3) yard (0.8.7) @@ -185,6 +201,8 @@ PLATFORMS DEPENDENCIES capybara + capybara-firebug + capybara-webkit coffee-rails (~> 4.0.0) database_cleaner factory_girl @@ -204,6 +222,7 @@ DEPENDENCIES ruby-progressbar sass-rails (~> 4.0.0.rc1) sdoc + selenium-webdriver shoulda-matchers sqlite3 therubyracer From 44827722a71de226de3b6d084a0edc5cb3f89aad Mon Sep 17 00:00:00 2001 From: Juan Carlos Date: Mon, 28 Oct 2013 17:48:05 -0600 Subject: [PATCH 15/19] making some conigurations over the tests --- spec/spec_helper.rb | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 1754b20..c07ea6f 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -4,6 +4,8 @@ require 'rspec/rails' require 'rspec/autorun' require 'factory_girl_rails' +require 'capybara/rspec' +require 'capybara/firebug' Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f } @@ -15,7 +17,19 @@ config.use_transactional_fixtures = true config.infer_base_class_for_anonymous_controllers = false + config.treat_symbols_as_metadata_keys_with_true_values = true config.order = "random" + config.before(:suite) do + DatabaseCleaner.strategy = :transaction + end + + config.before(:each) do + DatabaseCleaner.start + end + + config.after(:each) do + DatabaseCleaner.clean + end end From 05ca4ee1ba2a0dfbffd7c7ff01da7ad49d32dca2 Mon Sep 17 00:00:00 2001 From: Juan Carlos Date: Mon, 28 Oct 2013 17:48:35 -0600 Subject: [PATCH 16/19] added necesary factories --- spec/factories/category.rb | 15 +++++++++++++++ spec/factories/department.rb | 10 ++++++++++ spec/factories/payee.rb | 13 +++++++++++++ spec/factories/transaction.rb | 17 ----------------- 4 files changed, 38 insertions(+), 17 deletions(-) create mode 100644 spec/factories/category.rb create mode 100644 spec/factories/department.rb create mode 100644 spec/factories/payee.rb diff --git a/spec/factories/category.rb b/spec/factories/category.rb new file mode 100644 index 0000000..018c146 --- /dev/null +++ b/spec/factories/category.rb @@ -0,0 +1,15 @@ +require 'faker' + +FactoryGirl.define do + factory :category do + name Faker::Commerce.department + deleted false + pm_id (0..8).to_a.sample + pm_type (0..2).to_a.sample + budget_period (100..1000).to_a.sample + budget_limit (50..500).to_a.sample + include_subcategories false + rollover false + uuid Faker::Code.isbn + end +end diff --git a/spec/factories/department.rb b/spec/factories/department.rb new file mode 100644 index 0000000..dade4bf --- /dev/null +++ b/spec/factories/department.rb @@ -0,0 +1,10 @@ +require 'faker' + +FactoryGirl.define do + factory :department do + name Faker::Commerce.product_name + pm_id (0..8).to_a.sample + uuid Faker::Code.isbn + deleted false + end +end diff --git a/spec/factories/payee.rb b/spec/factories/payee.rb new file mode 100644 index 0000000..e967919 --- /dev/null +++ b/spec/factories/payee.rb @@ -0,0 +1,13 @@ +require 'faker' + +FactoryGirl.define do + factory :payee do + name Faker::Name.name + deleted false + pm_id (0..8).to_a.sample + latitude Faker::Address.latitude + longitude Faker::Address.longitude + uuid Faker::Code.isbn + + end +end diff --git a/spec/factories/transaction.rb b/spec/factories/transaction.rb index 558d34a..1ab3e5f 100644 --- a/spec/factories/transaction.rb +++ b/spec/factories/transaction.rb @@ -30,20 +30,3 @@ uuid Faker::Code.isbn end end - -transaction = FactoryGirl.create(:transaction) - -FactoryGirl.define do - factory :split do - pm_id transaction.pm_id - transaction_id transaction.id - amount transaction.amount - xrate (1..10).to_a.sample - category_id transaction.category_id - class_id (0..10).to_a.sample - memo Faker::Lorem.paragraph - transfer_to_account_id (1..100).to_a.sample - currency_code currency.sample - of_x_id "dummy" - end -end From 21b121517ea99e51b6ab36668affdda38af3541a Mon Sep 17 00:00:00 2001 From: Juan Carlos Date: Mon, 28 Oct 2013 17:49:01 -0600 Subject: [PATCH 17/19] created tests for reports --- spec/features/reports_spec.rb | 66 +++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 spec/features/reports_spec.rb diff --git a/spec/features/reports_spec.rb b/spec/features/reports_spec.rb new file mode 100644 index 0000000..28965ac --- /dev/null +++ b/spec/features/reports_spec.rb @@ -0,0 +1,66 @@ +require 'spec_helper' + +feature 'Checking Reports' do + let!(:transaction1) {create(:transaction, + amount: 300, date: "20131005", payee_id: 2, + category_id: 1, pm_type: 1)} + let!(:transaction2) {create(:transaction, + amount: 350, date: "20131015", payee_id: 1, + category_id: 2, pm_type: 1)} + let!(:transaction3) {create(:transaction, + amount: 100, date: "20131025", payee_id: 3, + category_id: 2, pm_type: 1)} + let!(:transaction4) {create(:transaction, + amount: -250, date: "20131016", payee_id: 2, + category_id: 3, pm_type: 2)} + let!(:transaction5) {create(:transaction, + amount: 450, date: "20131021", payee_id: 3, + category_id: 1, pm_type: 2)} + + scenario 'Income vs Expense' do + visit report_path + click_link "Income vs Expense" + page.should have_content('Income vs Expense Report') + page.has_selector?('.table-bordered').should be_true + page.should have_content("1") + page.should have_content("2") + page.should have_content("750") + page.should have_content("200") + end + + scenario 'Spending by payee' do + visit report_path + click_link "Spending by Payee" + page.should have_content('Spending by Payee Report') + page.has_selector?('.table-bordered').should be_true + page.should have_content("1") + page.should have_content("2") + page.should have_content("3") + page.should have_content("350") + page.should have_content("50") + page.should have_content("550") + end + + scenario 'Net Worth' do + visit report_path + click_link "Net Worth" + page.should have_content('Net Worth Report') + page.has_selector?('.table-bordered').should be_true + page.should have_content("10") + page.should have_content("950") + end + + scenario "Spending by Category" do + visit report_path + click_link "Spending by Category" + page.should have_content("Spending by Category Report") + page.has_selector?('.table-bordered').should be_true + page.should have_content("1") + page.should have_content("750") + page.should have_content("2") + page.should have_content("450") + page.should have_content("3") + page.should have_content("-250") + end + +end From 412db08b183519fac2d0992ee2c772a95c4f43de Mon Sep 17 00:00:00 2001 From: Juan Carlos Date: Tue, 29 Oct 2013 18:08:22 -0600 Subject: [PATCH 18/19] dont works u.u --- app/controllers/reports_controller.rb | 7 ++++--- app/models/transaction.rb | 5 +++++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/app/controllers/reports_controller.rb b/app/controllers/reports_controller.rb index b999498..f698611 100644 --- a/app/controllers/reports_controller.rb +++ b/app/controllers/reports_controller.rb @@ -1,12 +1,12 @@ class ReportsController < ApplicationController def income_v_expense - @report = Transaction.group(:pm_type).sum(:amount) + @report = Transaction.active.group(:pm_type).sum(:amount)#.income_v_expense format_respond end def spending_by_payee - @report = Transaction.group(:payee_id).sum(:amount) + @report = Transaction.active.group(:payee_id).sum(:amount) format_respond end @@ -19,7 +19,7 @@ def net_worth end def spending_by_category - @report = Transaction.group(:category_id).sum(:amount) + @report = Transaction.active.group(:category_id).sum(:amount) format_respond end @@ -31,6 +31,7 @@ def format_respond end def json_for_chart + { labels: @report.keys, datasets: [ diff --git a/app/models/transaction.rb b/app/models/transaction.rb index 808bac4..5838271 100644 --- a/app/models/transaction.rb +++ b/app/models/transaction.rb @@ -11,6 +11,7 @@ class Transaction < ActiveRecord::Base scope :search, ->(q) { where "payee_name like ? OR uuid = ?", "%#{q}%", q } scope :uuid, ->(uuid) { where(uuid:uuid).first } scope :active, -> { where(deleted:false).where('transactions.pm_type <> 5') } + scope :active, -> { where('deleted=false AND pm_type <> 5') } scope :order_date, -> { order('date desc') } scope :cleared, -> { where(cleared:true) } scope :before_today, -> { where('date < ?', Time.now) } @@ -21,6 +22,10 @@ class Transaction < ActiveRecord::Base scope :interval, ->(from, to) { where("transactions.date >= ? AND transactions.date <= ?", from, to) } scope :total_amount, -> { select('count(transactions.amount) as total_count', 'sum(transactions.amount) as total_amount') } scope :net_worth, -> { select("date_part('month', date) as month, sum(amount) as amount").where("date between '2013-01-01' and '2014-01-01'").group("month") } + scope :income_v_expense, -> { select("SUM(amount) AS amount, pm_type case AS pm_type") + .where(deleted: false).where('transactions.pm_type <> 5') + .group("pm_type") } + #{ active.group(:pm_type).sum(:amount) } belongs_to :account has_many :splits From e7d4ee2f59abd858dac4eb682002ecc0f017e5a1 Mon Sep 17 00:00:00 2001 From: Juan Carlos Date: Wed, 30 Oct 2013 18:35:10 -0600 Subject: [PATCH 19/19] weird scopes --- app/controllers/reports_controller.rb | 2 +- app/models/transaction.rb | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/app/controllers/reports_controller.rb b/app/controllers/reports_controller.rb index f698611..3909b27 100644 --- a/app/controllers/reports_controller.rb +++ b/app/controllers/reports_controller.rb @@ -1,7 +1,7 @@ class ReportsController < ApplicationController def income_v_expense - @report = Transaction.active.group(:pm_type).sum(:amount)#.income_v_expense + @report = Transaction.active.group(:pm_type).sum(:amount) format_respond end diff --git a/app/models/transaction.rb b/app/models/transaction.rb index 5838271..c1db9df 100644 --- a/app/models/transaction.rb +++ b/app/models/transaction.rb @@ -11,7 +11,6 @@ class Transaction < ActiveRecord::Base scope :search, ->(q) { where "payee_name like ? OR uuid = ?", "%#{q}%", q } scope :uuid, ->(uuid) { where(uuid:uuid).first } scope :active, -> { where(deleted:false).where('transactions.pm_type <> 5') } - scope :active, -> { where('deleted=false AND pm_type <> 5') } scope :order_date, -> { order('date desc') } scope :cleared, -> { where(cleared:true) } scope :before_today, -> { where('date < ?', Time.now) } @@ -22,7 +21,7 @@ class Transaction < ActiveRecord::Base scope :interval, ->(from, to) { where("transactions.date >= ? AND transactions.date <= ?", from, to) } scope :total_amount, -> { select('count(transactions.amount) as total_count', 'sum(transactions.amount) as total_amount') } scope :net_worth, -> { select("date_part('month', date) as month, sum(amount) as amount").where("date between '2013-01-01' and '2014-01-01'").group("month") } - scope :income_v_expense, -> { select("SUM(amount) AS amount, pm_type case AS pm_type") + scope :income_v_expense, -> { select('SUM("transactions"."amount") AS sum_amount, pm_type as pm_types') .where(deleted: false).where('transactions.pm_type <> 5') .group("pm_type") } #{ active.group(:pm_type).sum(:amount) }