rails_finance 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (190) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +20 -0
  3. data/README.md +20 -0
  4. data/Rakefile +32 -0
  5. data/app/controllers/finance/admin/assessments_controller.rb +55 -0
  6. data/app/controllers/finance/admin/base_controller.rb +4 -0
  7. data/app/controllers/finance/admin/budgets_controller.rb +83 -0
  8. data/app/controllers/finance/admin/expense_members_controller.rb +59 -0
  9. data/app/controllers/finance/admin/expenses_controller.rb +71 -0
  10. data/app/controllers/finance/admin/financial_taxons_controller.rb +67 -0
  11. data/app/controllers/finance/admin/fund_incomes_controller.rb +59 -0
  12. data/app/controllers/finance/admin/funds_controller.rb +58 -0
  13. data/app/controllers/finance/admin/home_controller.rb +8 -0
  14. data/app/controllers/finance/admin/stocks_controller.rb +60 -0
  15. data/app/controllers/finance/me/base_controller.rb +4 -0
  16. data/app/controllers/finance/me/budgets_controller.rb +111 -0
  17. data/app/controllers/finance/me/expense_members_controller.rb +73 -0
  18. data/app/controllers/finance/me/expenses_controller.rb +129 -0
  19. data/app/controllers/finance_controller/me.rb +14 -0
  20. data/app/models/budget/finance/fund_budget.rb +5 -0
  21. data/app/models/budget/finance/stock_budget.rb +5 -0
  22. data/app/models/expense/finance/payable_expense.rb +5 -0
  23. data/app/models/expense/finance/prepay_expense.rb +5 -0
  24. data/app/models/finance/application_record.rb +5 -0
  25. data/app/models/finance/assessment.rb +5 -0
  26. data/app/models/finance/budget.rb +7 -0
  27. data/app/models/finance/expense.rb +7 -0
  28. data/app/models/finance/expense_item.rb +5 -0
  29. data/app/models/finance/expense_member.rb +6 -0
  30. data/app/models/finance/ext/financial.rb +24 -0
  31. data/app/models/finance/ext/member.rb +13 -0
  32. data/app/models/finance/financial_taxon.rb +7 -0
  33. data/app/models/finance/fund.rb +6 -0
  34. data/app/models/finance/fund_income.rb +5 -0
  35. data/app/models/finance/model/assessment.rb +26 -0
  36. data/app/models/finance/model/budget/fund_budget.rb +5 -0
  37. data/app/models/finance/model/budget/stock_budget.rb +10 -0
  38. data/app/models/finance/model/budget.rb +152 -0
  39. data/app/models/finance/model/expense/payable_expense.rb +5 -0
  40. data/app/models/finance/model/expense/prepay_expense.rb +5 -0
  41. data/app/models/finance/model/expense.rb +168 -0
  42. data/app/models/finance/model/expense_item.rb +36 -0
  43. data/app/models/finance/model/expense_member.rb +130 -0
  44. data/app/models/finance/model/financial_taxon.rb +27 -0
  45. data/app/models/finance/model/fund.rb +36 -0
  46. data/app/models/finance/model/fund_income.rb +30 -0
  47. data/app/models/finance/model/stock.rb +33 -0
  48. data/app/models/finance/stock.rb +5 -0
  49. data/app/models/finance.rb +12 -0
  50. data/app/pdfs/borrow_expense_pdf.rb +70 -0
  51. data/app/pdfs/payout_expense_pdf.rb +70 -0
  52. data/app/pdfs/prepay_expense_pdf.rb +70 -0
  53. data/app/records/borrow_expense_record.rb +10 -0
  54. data/app/records/prepay_expense_record.rb +16 -0
  55. data/app/views/admin/_finance_nav.html.erb +14 -0
  56. data/app/views/admin/_nav_expense.html.erb +4 -0
  57. data/app/views/finance/admin/assessments/_edit_form.html.erb +4 -0
  58. data/app/views/finance/admin/assessments/_filter_form.html.erb +9 -0
  59. data/app/views/finance/admin/assessments/_form.html.erb +2 -0
  60. data/app/views/finance/admin/assessments/_index_tbody.html.erb +2 -0
  61. data/app/views/finance/admin/assessments/_index_thead.html.erb +3 -0
  62. data/app/views/finance/admin/assessments/_index_tr.html.erb +19 -0
  63. data/app/views/finance/admin/assessments/_new_form.html.erb +4 -0
  64. data/app/views/finance/admin/assessments/_show_table.html.erb +4 -0
  65. data/app/views/finance/admin/assessments/index.html.erb +5 -0
  66. data/app/views/finance/admin/base/_nav.html.erb +3 -0
  67. data/app/views/finance/admin/budgets/_breadcrumb.html.erb +9 -0
  68. data/app/views/finance/admin/budgets/_edit_form.html.erb +4 -0
  69. data/app/views/finance/admin/budgets/_filter_form.html.erb +10 -0
  70. data/app/views/finance/admin/budgets/_filter_table.html.erb +11 -0
  71. data/app/views/finance/admin/budgets/_form.html.erb +7 -0
  72. data/app/views/finance/admin/budgets/_index_tbody.html.erb +13 -0
  73. data/app/views/finance/admin/budgets/_index_thead.html.erb +6 -0
  74. data/app/views/finance/admin/budgets/_item_form.html.erb +17 -0
  75. data/app/views/finance/admin/budgets/_items.html.erb +16 -0
  76. data/app/views/finance/admin/budgets/_new_form.html.erb +4 -0
  77. data/app/views/finance/admin/budgets/_show/_breadcrumb.html.erb +6 -0
  78. data/app/views/finance/admin/budgets/_show_table.html.erb +62 -0
  79. data/app/views/finance/admin/budgets/index.html.erb +5 -0
  80. data/app/views/finance/admin/budgets/next.turbo_stream.erb +1 -0
  81. data/app/views/finance/admin/budgets/show.htmlx.erb +35 -0
  82. data/app/views/finance/admin/budgets/trigger.js.erb +1 -0
  83. data/app/views/finance/admin/expense_members/_edit_form.html.erb +3 -0
  84. data/app/views/finance/admin/expense_members/_form.html.erb +5 -0
  85. data/app/views/finance/admin/expense_members/_index_tbody.html.erb +53 -0
  86. data/app/views/finance/admin/expense_members/_index_thead.html.erb +12 -0
  87. data/app/views/finance/admin/expense_members/index.html.erb +8 -0
  88. data/app/views/finance/admin/expenses/_breadcrumb.html.erb +9 -0
  89. data/app/views/finance/admin/expenses/_edit_form.html.erb +4 -0
  90. data/app/views/finance/admin/expenses/_filter_form.html.erb +10 -0
  91. data/app/views/finance/admin/expenses/_filter_table.html.erb +11 -0
  92. data/app/views/finance/admin/expenses/_form.html.erb +12 -0
  93. data/app/views/finance/admin/expenses/_index_tbody.html.erb +13 -0
  94. data/app/views/finance/admin/expenses/_index_thead.html.erb +6 -0
  95. data/app/views/finance/admin/expenses/_item_form.html.erb +18 -0
  96. data/app/views/finance/admin/expenses/_items.html.erb +20 -0
  97. data/app/views/finance/admin/expenses/_member_form.html.erb +16 -0
  98. data/app/views/finance/admin/expenses/_new_form.html.erb +4 -0
  99. data/app/views/finance/admin/expenses/index.html.erb +5 -0
  100. data/app/views/finance/admin/expenses/next.turbo_stream.erb +1 -0
  101. data/app/views/finance/admin/expenses/show.html.erb +121 -0
  102. data/app/views/finance/admin/expenses/trigger.turbo_stream.erb +1 -0
  103. data/app/views/finance/admin/financial_taxons/_edit_form.html.erb +4 -0
  104. data/app/views/finance/admin/financial_taxons/_filter_form.html.erb +10 -0
  105. data/app/views/finance/admin/financial_taxons/_form.html.erb +5 -0
  106. data/app/views/finance/admin/financial_taxons/_index_tbody.html.erb +36 -0
  107. data/app/views/finance/admin/financial_taxons/_index_thead.html.erb +5 -0
  108. data/app/views/finance/admin/financial_taxons/_index_tr.html.erb +13 -0
  109. data/app/views/finance/admin/financial_taxons/_new_form.html.erb +4 -0
  110. data/app/views/finance/admin/financial_taxons/index.html.erb +6 -0
  111. data/app/views/finance/admin/fund_incomes/_breadcrumb.html.erb +7 -0
  112. data/app/views/finance/admin/fund_incomes/_edit_form.html.erb +4 -0
  113. data/app/views/finance/admin/fund_incomes/_filter_form.html.erb +9 -0
  114. data/app/views/finance/admin/fund_incomes/_form.html.erb +4 -0
  115. data/app/views/finance/admin/fund_incomes/_index_tbody.html.erb +5 -0
  116. data/app/views/finance/admin/fund_incomes/_index_thead.html.erb +4 -0
  117. data/app/views/finance/admin/fund_incomes/_new_form.html.erb +4 -0
  118. data/app/views/finance/admin/fund_incomes/_show_table.html.erb +12 -0
  119. data/app/views/finance/admin/fund_incomes/index.html.erb +5 -0
  120. data/app/views/finance/admin/funds/_edit_form.html.erb +3 -0
  121. data/app/views/finance/admin/funds/_filter_form.html.erb +9 -0
  122. data/app/views/finance/admin/funds/_form.html.erb +5 -0
  123. data/app/views/finance/admin/funds/_index_tbody.html.erb +14 -0
  124. data/app/views/finance/admin/funds/_index_thead.html.erb +8 -0
  125. data/app/views/finance/admin/funds/_new_form.html.erb +3 -0
  126. data/app/views/finance/admin/funds/_show_table.html.erb +16 -0
  127. data/app/views/finance/admin/funds/index.html.erb +5 -0
  128. data/app/views/finance/admin/home/index.html.erb +6 -0
  129. data/app/views/finance/admin/stocks/_breadcrumb.html.erb +7 -0
  130. data/app/views/finance/admin/stocks/_edit_form.html.erb +4 -0
  131. data/app/views/finance/admin/stocks/_filter_form.html.erb +9 -0
  132. data/app/views/finance/admin/stocks/_form.html.erb +4 -0
  133. data/app/views/finance/admin/stocks/_index_tbody.html.erb +6 -0
  134. data/app/views/finance/admin/stocks/_index_thead.html.erb +7 -0
  135. data/app/views/finance/admin/stocks/_new_form.html.erb +4 -0
  136. data/app/views/finance/admin/stocks/_show_table.html.erb +16 -0
  137. data/app/views/finance/admin/stocks/index.html.erb +5 -0
  138. data/app/views/finance/me/budgets/_breadcrumb.html.erb +6 -0
  139. data/app/views/finance/me/budgets/_filter_form.html.erb +8 -0
  140. data/app/views/finance/me/budgets/_filter_table.html.erb +11 -0
  141. data/app/views/finance/me/budgets/_index_tbody.html.erb +23 -0
  142. data/app/views/finance/me/budgets/_index_thead.html.erb +4 -0
  143. data/app/views/finance/me/budgets/_items.html.erb +22 -0
  144. data/app/views/finance/me/budgets/_show/_breadcrumb.html.erb +6 -0
  145. data/app/views/finance/me/budgets/admin.html.erb +5 -0
  146. data/app/views/finance/me/budgets/financial_taxons.js.erb +7 -0
  147. data/app/views/finance/me/budgets/index.html.erb +5 -0
  148. data/app/views/finance/me/budgets/index.js +45 -0
  149. data/app/views/finance/me/budgets/submit.turbo_stream.erb +1 -0
  150. data/app/views/finance/me/budgets/transfer.turbo_stream.erb +1 -0
  151. data/app/views/finance/me/expense_members/_breadcrumb.html.erb +6 -0
  152. data/app/views/finance/me/expense_members/_button.html.erb +0 -0
  153. data/app/views/finance/me/expense_members/_filter_form.html.erb +10 -0
  154. data/app/views/finance/me/expense_members/_form.html.erb +9 -0
  155. data/app/views/finance/me/expense_members/_index_tbody.html.erb +8 -0
  156. data/app/views/finance/me/expense_members/_index_thead.html.erb +7 -0
  157. data/app/views/finance/me/expense_members/_show_items.html.erb +1 -0
  158. data/app/views/finance/me/expense_members/admin.html.erb +3 -0
  159. data/app/views/finance/me/expense_members/index.html.erb +6 -0
  160. data/app/views/finance/me/expense_members/items.js.erb +1 -0
  161. data/app/views/finance/me/expense_members/show.html.erb +62 -0
  162. data/app/views/finance/me/expenses/_breadcrumb.html.erb +7 -0
  163. data/app/views/finance/me/expenses/_edit_form.html.erb +3 -0
  164. data/app/views/finance/me/expenses/_filter_form.html.erb +8 -0
  165. data/app/views/finance/me/expenses/_filter_table.html.erb +11 -0
  166. data/app/views/finance/me/expenses/_index_tbody.html.erb +16 -0
  167. data/app/views/finance/me/expenses/_index_thead.html.erb +6 -0
  168. data/app/views/finance/me/expenses/_index_tr.html.erb +14 -0
  169. data/app/views/finance/me/expenses/_new_form.html.erb +4 -0
  170. data/app/views/finance/me/expenses/_show/_breadcrumb.html.erb +6 -0
  171. data/app/views/finance/me/expenses/_show_table.html.erb +124 -0
  172. data/app/views/finance/me/expenses/admin.html.erb +5 -0
  173. data/app/views/finance/me/expenses/confirm.html.erb +27 -0
  174. data/app/views/finance/me/expenses/financial_taxons.js.erb +44 -0
  175. data/app/views/finance/me/expenses/index.html.erb +5 -0
  176. data/app/views/finance/me/expenses/index.js +42 -0
  177. data/app/views/finance/me/expenses/show.html.erb +3 -0
  178. data/app/views/me/_finance_nav.html.erb +11 -0
  179. data/config/locales/en.attributes.yml +6 -0
  180. data/config/locales/en.controller.yml +21 -0
  181. data/config/locales/en.enum.yml +18 -0
  182. data/config/locales/zh.attributes.yml +65 -0
  183. data/config/locales/zh.controller.yml +57 -0
  184. data/config/locales/zh.enum.yml +38 -0
  185. data/config/routes.rb +63 -0
  186. data/lib/rails_finance/config.rb +11 -0
  187. data/lib/rails_finance/engine.rb +26 -0
  188. data/lib/rails_finance/version.rb +3 -0
  189. data/lib/rails_finance.rb +2 -0
  190. metadata +259 -0
@@ -0,0 +1,14 @@
1
+ module FinanceController::Me
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ layout 'me'
6
+ end
7
+
8
+ class_methods do
9
+ def local_prefixes
10
+ [controller_path, 'finance/me/base', 'me']
11
+ end
12
+ end
13
+
14
+ end
@@ -0,0 +1,5 @@
1
+ module Finance
2
+ class FundBudget < Budget
3
+ include Model::Budget::FundBudget
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module Finance
2
+ class StockBudget < Budget
3
+ include Model::Budget::StockBudget
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module Finance
2
+ class PayableExpense < Expense
3
+ include Model::Expense::PayableExpense
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module Finance
2
+ class PrepayExpense < Expense
3
+ include Model::Expense::PrepayExpense
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module Finance
2
+ class ApplicationRecord < ApplicationRecord
3
+ self.abstract_class = true
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module Finance
2
+ class Assessment < ApplicationRecord
3
+ include Model::Assessment
4
+ end
5
+ end
@@ -0,0 +1,7 @@
1
+ module Finance
2
+ class Budget < ApplicationRecord
3
+ include Model::Budget
4
+ include Auditor::Ext::Audited
5
+ include Auditor::Ext::Verified
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ module Finance
2
+ class Expense < ApplicationRecord
3
+ include Model::Expense
4
+ include Auditor::Ext::Audited
5
+ include Auditor::Ext::Verified
6
+ end
7
+ end
@@ -0,0 +1,5 @@
1
+ module Finance
2
+ class ExpenseItem < ApplicationRecord
3
+ include Model::ExpenseItem
4
+ end
5
+ end
@@ -0,0 +1,6 @@
1
+ module Finance
2
+ class ExpenseMember < ApplicationRecord
3
+ include Model::ExpenseMember
4
+ include Trade::Model::Payout if defined? RailsTrade
5
+ end
6
+ end
@@ -0,0 +1,24 @@
1
+ module Finance
2
+ module Ext::Financial
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ attribute :fund_budget, :decimal, default: 0
7
+ attribute :fund_expense, :decimal, default: 0
8
+ attribute :budget_amount, :decimal, default: 0
9
+ attribute :expense_amount, :decimal, default: 0
10
+
11
+ has_many :budgets, class_name: 'Finance::Budget', as: :financial
12
+ has_many :expenses, class_name: 'Finance::Expense', as: :financial
13
+ end
14
+
15
+ def compute_budget_amount
16
+ self.budget_amount = budgets.sum(:amount)
17
+ end
18
+
19
+ def compute_expense_amount
20
+ self.expense_amount = expenses.sum(:amount)
21
+ end
22
+
23
+ end
24
+ end
@@ -0,0 +1,13 @@
1
+ module Finance
2
+ module Ext::Member
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ has_many :expense_members, class_name: 'Finance::ExpenseMember', dependent: :nullify
7
+ has_many :expenses, class_name: 'Finance::Expense', through: :expense_members
8
+ has_many :created_expenses, class_name: 'Finance::Expense', foreign_key: :creator_id
9
+ has_many :budgets, class_name: 'Finance::Budget'
10
+ end
11
+
12
+ end
13
+ end
@@ -0,0 +1,7 @@
1
+ module Finance
2
+ class FinancialTaxon < ApplicationRecord
3
+ include Com::Ext::Taxon
4
+ include Model::FinancialTaxon
5
+ include Auditor::Ext::Verifiable if defined? RailsAudit
6
+ end
7
+ end
@@ -0,0 +1,6 @@
1
+ module Finance
2
+ class Fund < ApplicationRecord
3
+ include Model::Fund
4
+ include Trade::Model::Sell if defined? RailsTrade
5
+ end
6
+ end
@@ -0,0 +1,5 @@
1
+ module Finance
2
+ class FundIncome < ApplicationRecord
3
+ include Model::FundIncome
4
+ end
5
+ end
@@ -0,0 +1,26 @@
1
+ module Finance
2
+
3
+ # 估值
4
+ module Model::Assessment
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ attribute :amount, :decimal, default: 0
9
+ attribute :trustee_amount, :decimal
10
+
11
+ belongs_to :organ, class_name: 'Org::Organ'
12
+
13
+ has_many :stocks
14
+
15
+ after_save :sync_assess_amount, if: -> { saved_change_to_amount? }
16
+ end
17
+
18
+ def sync_assess_amount
19
+ stocks.each do |stock|
20
+ stock.update assess_amount: amount * stock.ratio
21
+ end
22
+ self.update trustee_amount: stocks.sum(:assess_amount)
23
+ end
24
+
25
+ end
26
+ end
@@ -0,0 +1,5 @@
1
+ module Finance
2
+ module Model::Budget::FundBudget
3
+
4
+ end
5
+ end
@@ -0,0 +1,10 @@
1
+ module Finance
2
+ module Model::Budget::StockBudget
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ attribute :amount, :integer
7
+ end
8
+
9
+ end
10
+ end
@@ -0,0 +1,152 @@
1
+ module Finance
2
+ module Model::Budget
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ attribute :type, :string, default: 'Finance::FundBudget'
7
+ attribute :subject, :string
8
+ attribute :amount, :decimal
9
+ attribute :note, :string
10
+
11
+ belongs_to :organ, class_name: 'Org::Organ', optional: true
12
+ belongs_to :member, class_name: 'Org::Member', optional: true
13
+ belongs_to :financial, polymorphic: true, optional: true
14
+ belongs_to :fund, optional: true
15
+ belongs_to :stock, optional: true
16
+
17
+ belongs_to :financial_taxon, optional: true
18
+ belongs_to :taxon, class_name: 'FinancialTaxon', foreign_key: :financial_taxon_id, optional: true
19
+
20
+ has_many :verifiers, -> { where(verifiable_type: 'FinancialTaxon').order(position: :asc) }, class_name: 'Auditor::Verifier', primary_key: :financial_taxon_id, foreign_key: :verifiable_id
21
+ has_many :expense_items, dependent: :destroy
22
+ has_many :expenses, dependent: :destroy
23
+ accepts_nested_attributes_for :expense_items, allow_destroy: true, reject_if: :all_blank
24
+
25
+ #validate :amount_sum
26
+ validates :subject, presence: true
27
+
28
+ has_one_attached :proof
29
+ has_one_attached :invoice
30
+
31
+ enum state: {
32
+ init: 'init',
33
+ verifying: 'verifying',
34
+ finished: 'finished',
35
+ rejected: 'rejected'
36
+ }, _default: 'init'
37
+
38
+ acts_as_notify(
39
+ :default,
40
+ only: [:subject, :amount, :type],
41
+ methods: [:creator_name, :state_i18n]
42
+ )
43
+ acts_as_notify(
44
+ :request,
45
+ only: [:subject, :amount, :type],
46
+ methods: [:creator_name]
47
+ )
48
+
49
+ after_save :sum_amount, if: -> { saved_change_to_amount? }
50
+ after_save :sum_fund_amount, if: -> { fund && saved_change_to_amount? }
51
+ after_save :sum_stock_amount, if: -> { stock && saved_change_to_amount? }
52
+ end
53
+
54
+ def can_operate?(member)
55
+ return true if member.admin?
56
+ self.next_operator == current_member && !['init', 'rejected', 'finished'].include?(self.state)
57
+ end
58
+
59
+ def next_state_state
60
+ if ['pending_om', 'pending_cfo'].include? state
61
+ next_state_states.without('pending_md').first
62
+ else
63
+ next_state_states.first
64
+ end
65
+ end
66
+
67
+ def next_state_states
68
+ next_states(:state) do |states|
69
+ unless self.financial_taxon.verifiers
70
+ states - ['pending_verifier']
71
+ end
72
+
73
+ if self.amount <= 5000
74
+ states - ['pending_cfo']
75
+ elsif self.amount > 5000 && self.amount <= 20000
76
+ states - ['pending_om']
77
+ elsif self.amount > 20000
78
+ states - ['pending_om', 'pending_cfo']
79
+ end
80
+ end
81
+ end
82
+
83
+ def do_trigger(params = {})
84
+ self.trigger_to(state: params[:state])
85
+
86
+ self.class.transaction do
87
+ self.save!
88
+ to_notification(
89
+ member: creator,
90
+ link: url_helpers.me_expenses_url(id: self.id),
91
+ verbose: true
92
+ )
93
+ if next_verifier
94
+ to_notification(
95
+ member: next_verifier.member,
96
+ link: url_helpers.admin_me_expenses_url(id: self.id),
97
+ code: :request,
98
+ verbose: true
99
+ )
100
+ end
101
+ end
102
+ end
103
+
104
+ def amount_cn
105
+ NumHelper.to_rmb amount&.to_f || 0
106
+ end
107
+
108
+ def details
109
+ expense_items.map do |item|
110
+ "#{item.financial_taxon&.name} #{item.note} #{item.amount}"
111
+ end.join("\n")
112
+ end
113
+
114
+ def amount_sum
115
+ unless self.amount == self.expense_members.sum(&:amount)
116
+ self.errors.add :amount, 'Amount must equal to amount sum'
117
+ end
118
+
119
+ unless self.amount == self.expense_items.sum(&:amount)
120
+ self.errors.add :amount, 'Amount must equal to cost sum'
121
+ end
122
+ end
123
+
124
+ def transfer(type)
125
+ expense = self.expenses.build(type: type)
126
+ expense.assign_attributes self.attributes.slice('financial_taxon_id', 'subject', 'amount')
127
+ expense.creator_id = member_id
128
+ expense.expendable_type = budgeting_type
129
+ expense.expendable_id = budgeting_id
130
+ expense.save!
131
+ end
132
+
133
+ def sum_amount
134
+ financial.compute_budget_amount
135
+ financial.save
136
+ end
137
+
138
+ def sum_fund_amount
139
+ fund.sum_budget_amount(financial_type)
140
+ fund.save
141
+ if financial
142
+ financial.compute_fund_budget
143
+ financial.save
144
+ end
145
+ end
146
+
147
+ def sum_stock_amount
148
+
149
+ end
150
+
151
+ end
152
+ end
@@ -0,0 +1,5 @@
1
+ module Finance
2
+ module Model::Expense::PayableExpense
3
+
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module Finance
2
+ module Model::Expense::PrepayExpense
3
+
4
+ end
5
+ end
@@ -0,0 +1,168 @@
1
+ module Finance
2
+ module Model::Expense
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ attribute :type, :string
7
+ attribute :state, :string
8
+ attribute :subject, :string
9
+ attribute :amount, :decimal
10
+ attribute :note, :string, limit: 4096
11
+ attribute :invoices_count, :integer
12
+
13
+ belongs_to :organ, class_name: 'Org::Organ', optional: true
14
+ belongs_to :creator, class_name: 'Org::Member'
15
+
16
+ belongs_to :financial, polymorphic: true, optional: true
17
+ belongs_to :budget, optional: true
18
+ belongs_to :fund, optional: true
19
+ belongs_to :stock, optional: true
20
+ belongs_to :financial_taxon
21
+ belongs_to :payout, optional: true
22
+ belongs_to :taxon, class_name: 'FinancialTaxon', foreign_key: :financial_taxon_id
23
+ belongs_to :payment_method, optional: true
24
+
25
+ has_many :verifiers, -> { where(verifiable_type: 'FinancialTaxon').order(position: :asc) }, class_name: 'Auditor::Verifier', primary_key: :financial_taxon_id, foreign_key: :verifiable_id
26
+ has_many :expense_members, dependent: :destroy
27
+ has_many :members, through: :expense_members
28
+ has_many :expense_items, dependent: :destroy
29
+
30
+ accepts_nested_attributes_for :expense_members, allow_destroy: true, reject_if: :all_blank
31
+ accepts_nested_attributes_for :expense_items, allow_destroy: true, reject_if: :all_blank
32
+
33
+ #validate :amount_sum
34
+ validates :subject, presence: true
35
+
36
+ has_one_attached :proof
37
+ has_one_attached :invoice
38
+
39
+ enum state: {
40
+ init: 'init',
41
+ verifying: 'verifying',
42
+ paying: 'paying',
43
+ upload_invoice: 'upload_invoice',
44
+ invoice_verifying: 'invoice_verifying',
45
+ finished: 'finished',
46
+ rejected: 'rejected'
47
+ }, _default: 'init'
48
+
49
+ acts_as_notify(
50
+ :default,
51
+ only: [:subject, :amount, :type],
52
+ methods: [:creator_name, :state_i18n]
53
+ )
54
+ acts_as_notify(
55
+ :request,
56
+ only: [:subject, :amount, :type],
57
+ methods: [:creator_name]
58
+ )
59
+
60
+ before_save :sync_amount
61
+ after_save :sum_amount, if: -> { saved_change_to_amount? }
62
+ after_save :sum_fund_amount, if: -> { fund && saved_change_to_amount? }
63
+ after_save :sync_items, if: -> { saved_change_to_budget_id? && budget }
64
+ end
65
+
66
+ def can_operate?(member)
67
+ return true if member.admin?
68
+ self.next_operator == current_member && !['init', 'rejected', 'finished'].include?(self.state)
69
+ end
70
+
71
+ def next_state_state
72
+ if ['pending_om', 'pending_cfo'].include? state
73
+ next_state_states.without('pending_md').first
74
+ else
75
+ next_state_states.first
76
+ end
77
+ end
78
+
79
+ def next_state_states
80
+ next_states(:state) do |states|
81
+ unless self.financial_taxon.verifiers
82
+ states - ['pending_verifier']
83
+ end
84
+
85
+ if self.amount <= 5000
86
+ states - ['pending_cfo']
87
+ elsif self.amount > 5000 && self.amount <= 20000
88
+ states - ['pending_om']
89
+ elsif self.amount > 20000
90
+ states - ['pending_om', 'pending_cfo']
91
+ end
92
+ end
93
+ end
94
+
95
+ def do_trigger(params = {})
96
+ self.trigger_to(state: params[:state])
97
+
98
+ self.class.transaction do
99
+ self.save!
100
+ to_notification(
101
+ member: creator,
102
+ link: url_helpers.me_expenses_url(id: self.id),
103
+ verbose: true
104
+ )
105
+ if next_verifier
106
+ to_notification(
107
+ member: next_verifier.member,
108
+ link: url_helpers.admin_me_expenses_url(id: self.id),
109
+ code: :request,
110
+ verbose: true
111
+ )
112
+ end
113
+ end
114
+ end
115
+
116
+ def amount_cn
117
+ NumHelper.to_rmb amount&.to_f || 0
118
+ end
119
+
120
+ def details
121
+ expense_items.map do |item|
122
+ "#{item.financial_taxon&.name} #{item.note} #{item.amount}"
123
+ end.join("\n")
124
+ end
125
+
126
+ def amount_sum
127
+ unless self.amount == self.expense_members.sum(&:amount)
128
+ self.errors.add :amount, 'Amount must equal to amount sum'
129
+ end
130
+
131
+ unless self.amount == self.expense_items.sum(&:amount)
132
+ self.errors.add :amount, 'Amount must equal to cost sum'
133
+ end
134
+ end
135
+
136
+ def sum_amount
137
+ return unless financial
138
+ financial.compute_expense_amount
139
+ financial.save
140
+ end
141
+
142
+ def sum_fund_amount
143
+ fund.sum_expense_amount(financial_type)
144
+ fund.save
145
+
146
+ if financial
147
+ financial.compute_fund_expense
148
+ financial.save
149
+ end
150
+ end
151
+
152
+ def sync_amount
153
+ self.amount = self.expense_items.sum(&:amount)
154
+ end
155
+
156
+ def sync_items
157
+ budget.expense_items.update_all expense_id: self.id
158
+ end
159
+
160
+ def sync_members
161
+ self.expense_items.pluck(:member_id).compact.each do |member_id|
162
+ amount = self.expense_items.sum(:amount)
163
+ self.expense_members.create(member_id: member_id, amount: amount)
164
+ end
165
+ end
166
+
167
+ end
168
+ end