added watchlist and position transfer feature
[smr.git] / gui / app / controllers / position_controller.rb
blob8238a959879f07b4098ab0d31d9b04d262568528
2 # This file is part of SMR.
4 # SMR is free software: you can redistribute it and/or modify it under the
5 # terms of the GNU General Public License as published by the Free Software
6 # Foundation, either version 3 of the License, or (at your option) any later
7 # version.
9 # SMR is distributed in the hope that it will be useful, but WITHOUT ANY
10 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
11 # A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License along with
14 # SMR.  If not, see <http://www.gnu.org/licenses/>.
16 require 'uploaded_file'
19 # Controller for AssetPosition
21 # This also cares about orders and other things since an AssetPosition
22 # is more than just the Position model.
23 class PositionController < ApplicationController
25     ##
26     # Show details about a SmrPosition.
27     def show
28         @position = Smr::AssetPosition.new(params[:id].to_i, current_user.id, smr_browse_date)
29         redirect_to root_path if @position.is_cash_position?
31         if @position.is_new? then
32             smr_menu_addsubitem('assets', {
33                 '+ order'=>new_position_path + '?id_position=%i'%params[:id],
34                 '- sell off'=>'/close?id=%i' % params[:id]
35             })
36         elsif not @position.is_closed?
37             smr_menu_addsubitem('assets', {
38                 '+ order'=>new_position_path + '?id_position=%i' % params[:id],
39                 '+ dividend'=>position_path + '?add_dividend=true',
40                 '- sell off'=>'/close?id=%i' % params[:id],
41                 '+ document'=>position_path + '?add_document=true',
42             })
43         end
45         @page = smr_page
46         @documents, @total_pages = smr_paginate(@page, Smr::UploadedFiles.new(current_user.id, @position.id))
48         if params[:add_dividend] then
49             @dividend = Dividend.new(:exdate=>smr_browse_date - 2.days, :id_position=>@position.id)
50         end
51     end
53     ##
54     # conditionally make a new position
55     #
56     # Most of the time this will not create a new position but rather add an
57     # order to an existing position. It can perform one of these things
58     # actually:
59     #
60     # 1) make a new order on existing position
61     # 2) make new position in given portfolio with new order
62     # 3) make new position in selectable portfolio holding selectable stock
63     #    order
64     #    - OR -
65     #    make new order on selected existing position
66     #
67     # In any case the +session+ is checked for :position_prefilled_order which
68     # will be used as basis for the new order, if present.
69     def new
70         if session[:position_prefilled_order] and session[:position_prefilled_order].is_a?(Order) then
71             @order = session[:position_prefilled_order]
72         else
73             @order = Order.new(:issued => smr_browse_date, :expire => (smr_browse_date+1.month).end_of_day)
74         end
76         if params[:id_position] then
77             # 1)
78             @position = Smr::AssetPosition.new(params[:id_position], current_user.id, smr_browse_date)
79         elsif params[:id_portfolio] then
80             # 2)
81             @portfolio = Portfolio.find_by_id(params[:id_portfolio])
82             @stocks = Stock.select(:id, :name).where.not(:id=>Smr::ID_CASH_SECURITY).all
83         else
84             # 3)
85             @portfolios = Portfolio.select(:id, :name).where(:id_user=>current_user.id).order(order: :desc)
86             @open_positions = Smr::Asset.new(current_user.id, smr_browse_date, :skip_cash_positions=>true).open_positions
87             @stocks = Stock.select(:id, :name).where.not(:id=>Smr::ID_CASH_SECURITY).all
88         end
89     end
91     ##
92     # creates an position/order by taking POST
93     #
94     # FIXME: Gummicode! Test carefully, ie issue on closed position?
95     def create
96         redirect = :back
98         order = Order.new(order_params)
99         order.issued = Time.parse(params[:order_issued]).to_i
100         order.expire = Time.parse(params[:order_expire]).to_i
102         if not params[:id_position].empty? then
103             # 1)
104             order.id_position = params[:id_position].to_i
105             redirect = position_path(order.id_position)
106         elsif params[:id_portfolio].empty? or params[:id_stock].empty?  then
107             redirect_to :back, :notice=>'Please select some Security and Portfolio or use an existing Position to order.'
108             return
109         else
110             # 2) and 3)
111             position = Position.new(
112                 :id_portfolio=>params[:id_portfolio].to_i,
113                 :id_stock=>params[:id_stock].to_i,
114                 :comment=>''
115             )
116             position.save!
118             order.id_position=position.id
120             redirect = position_path(position.id)
121         end
123         order.save!
125         # handle execute
126         if params[:execute_now].to_i == 1 and params[:execute_quote].to_f > 0.0 then
127             t = Smr::Transaction.new(
128                     Position.find(order.id_position),
129                     current_user,
130                     :order=>order
131             )
132             t.execute(params[:execute_quote].to_f, Time.parse(params[:execute_time]))
133             n = t.to_s
135             if params[:close_position].to_i == 1 then
136                 pstat = Smr::AssetPosition.new(order.id_position, current_user.id, smr_browse_date).close
137                 n += ' Closed position because all shares were sold.' if pstat.is_a?(TrueClass)
138             end
139         else
140             n = 'Issued new Order to %s %f shares.' % [order.type, order.shares]
141         end
143         # handle document
144         if params[:order][:document] then
145             doc = Smr::UploadedFile.store(current_user.id, params[:order][:document])
146             doc.assign(:order=>order.id)
147         end
149         redirect_to redirect, :notice=>n
150     end
152     ##
153     # update Position details
154     # - that is things that wont change the positions` status
155     def patch
156         if params[:id] and params[:comment]
157             p = Position.find(params[:id])
158             p.comment = params[:comment]
159             p.save
160         end
161         redirect_to :back
162     end
164     ##
165     # close this position at smr_browse_date
166     def close
167         p = Smr::AssetPosition.new(params[:id].to_i, current_user.id, smr_browse_date)
168         stat = p.close(smr_browse_date)
169         if stat.is_a?(TrueClass) then
170             n='Closed position #%i as of %s.' % [p.id, p.time_closed]
171             r=:back
172         elsif stat.is_a?(Order) then
173             session[:position_prefilled_order] = stat
174             n='Position holds shares. Can not close it right away. A new '\
175              +'order to sell them off has been pre-filled, please edit and '\
176              +'execute.'
177             r=new_position_path + '?id_position=%i' % p.id
178         else
179             n='Can not close position #%i because it is closed already.' % p.id
180             r=:back
181         end
182         redirect_to r, :notice=>n
183     end
185     def edit
186     end
188     def update
189     end
191     ##
192     # apply Order to Position at #smr_browse_date
193     def execute_order
194         p = Smr::AssetPosition.new(params[:id], current_user.id)
195         t = Smr::Transaction.new(p, current_user, :order=>Order.find(params[:id_order]))
196         t.execute(params[:execute_quote].to_f, Time.parse(params[:execute_time]))
198         # try to close the position, it may remain open unless empty
199         p.close if t.order.is_sale?
201         redirect_to :back, :notice=>t.to_s
202     end
204     ##
205     # cancel pending order by setting the canceled flag
206     def cancel_order
207         o = Order.find_by_id(params[:id_order])
208         o.is_canceled = 1
209         o.save!
210         redirect_to :back, :notice=>'Order #%i has been canceled' % o.id
211     end
213     protected
215     ##
216     # internal helper defining which parameters to accept for an order
217     def order_params
218       params.require(:order).permit(
219         :exchange, :shares, :limit, :addon, :provision, :courtage, :type,
220         :comment, :expense, :accrued_interest
221       )
222     end