rails_finance 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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