# Accounting Support Toolbox module GxG module Services module Accounting # ### Accounts class Account # private # def post_entry(amount=0, description="", is_debit=false, reference=nil) result = false new_entry = ::GxG::DB[:roles][:data].new_structure_from_format(GxG::DB[:administrator],{:ufs => "org.gxg.accounting.account.entry"}) if new_entry.is_a?(::GxG::Database::PersistedHash) new_entry.get_reservation if new_entry.write_reserved? new_entry[:account_uuid] = @account.uuid.to_s new_entry[:occurred_at] = DateTime.now new_entry[:reference_uuid] = reference.to_s new_entry[:description] = description.to_s new_entry[:is_debit] = is_debit new_entry[:amount] = amount new_entry.save new_entry.release_reservation new_entry.deactivate result = true else # Review : raise Exception here instead?? log_warning("Unable to secure a write-reservation on Account Entry #{new_entry.uuid.inspect} .") end end result end # public # ### , def initialize(account_profile=nil, currency_profile=nil) unless account_profile.is_a?(::GxG::Database::PersistedHash) raise ArgumentError, "Account Profile MUST be a GxG::Database::PersistedHash." end unless currency_profile.is_a?(::GxG::Database::PersistedHash) raise ArgumentError, "Currency Profile MUST be a GxG::Database::PersistedHash." end # @account = account_profile @currency = currency_profile # self end # def uuid() @account.uuid end # def post_credit(amount=0, description="", reference=nil) result = false unless amount.is_a?(::Integer) raise ArgumentError, "You MUST provide the amount as an Integer." end unless description.is_a?(::String) raise ArgumentError, "You MUST provide a description as a String." end @account.get_reservation if @account.write_reserved? if post_entry(amount, description, false, reference) @account[:balance] += amount @account[:balance_updated_at] = DateTime.now @account.save @account.release_reservation result = true else # Review : raise Exception here instead?? log_warning("Unable to post Entry for Account #{@account.uuid.inspect} .") end else # Review : raise Exception here instead?? log_warning("Unable to secure a write-reservation on Account #{@account.uuid.inspect} .") end result end # def post_debit(amount=0, description="", reference=nil) result = false unless amount.is_a?(::Integer) raise ArgumentError, "You MUST provide the amount as an Integer." end unless description.is_a?(::String) raise ArgumentError, "You MUST provide a description as a String." end @account.get_reservation if @account.write_reserved? if post_entry(amount, description, true, reference) @account[:balance] -= amount @account[:balance_updated_at] = DateTime.now @account.save @account.release_reservation result = true else # Review : raise Exception here instead?? log_warning("Unable to post Entry for Account #{@account.uuid.inspect} .") end else # Review : raise Exception here instead?? log_warning("Unable to secure a write-reservation on Account #{@account.uuid.inspect} .") end result end # def search_entries(criteria=nil, sort_with=:occurred_at) # Example Criteria : {:occurred_at => {:between => (DateTIme.now..DateTime.now)}} # Returns : unpersisted data (a copy) disconnected from Database records (Account Entries are largely immutable) result = [] presorted = [] unless [:occurred_at, :amount].include?(sort_with) sort_with = :occurred_at end properties = [{:account_uuid => {:equals => @account.uuid.to_s}}] if criteria.is_a?(::Hash) properties << criteria else if criteria.is_a?(::Array) criteria.each do |the_property| properties << the_property end end end ::GxG::DB[:roles][:data].search_database(GxG::DB[:administrator], {:properties => properties}).each do |entry| presorted << entry.unpersist end result = presorted.sort_by {|entry| entry[(sort_with)]} # result end # end # ### Financial Books (Chart of Accounts) class Book # # ### Chart of Accounts Supports # Note : Busines Logic - for each Service or Product create a :revenue and :cost_of_goods_sold account. (assigned in each member's service/product/subscription record) def self.default_accounts_reference() # Standard Accounts accounts = { :asset => { :asset_operating_account => "Operating Account", :asset_debitors => "Debitors", :asset_petty_cash => "Petty Cash" }, :receivable => { :receivable_trade => "Trade", :receivable_trade_notes => "Trade Notes", :receivable_installment => "Installment Receivables", :receivable_retainage_withheld => "Retainage Withheld", :receivable_allowance_for_uncollectable => "Allowance for Uncollectable Accounts" }, :inventory => { :inventory_reserved => "Reserved", :inventory_work_in_progress => "Work In Progress", :inventory_finished_goods => "Finished Goods", :inventory_unbilled_costs => "Unbilled Costs and Fees", :inventory_obsolescence_reserve => "Reserve for Obsolescence" }, :prepaid_expense => { :prepaid_insurance => "insurance", :prepaid_real_estate_taxes => "Real Estate Taxes", :prepaid_repairs_and_maintenance => "Repairs and Maintenance", :prepaid_rent => "Rent", :prepaid_deposits => "Deposits" }, :equipment => { :equipment_buildings => "Buildings", :equipment_machinery => "Machinery and Equipment", :equipment_vehicles => "Vehicles", :equipment_computers => "Computer Equipment", :equipment_furniture => "Furniture and Fixtures", :equipment_improvements => "Leasehold Improvements" }, :depreciation => { :depreciation_building => "Building Depreciation", :depreciation_equipment => "Machinery and Equipment Depreciation", :depreciation_vehicle => "Vehicle Depreciation", :depreciation_computer_equipment => "Computer Equipment Depreciation", :depreciation_furniture => "Furniture and Fixture Depreciation", :depreciation_improvement => "Leasehold Improvement Depreciation" }, :non_current_receivable => { :ncr_notes_receivable => "Notes Receivable", :ncr_installment_receivable => "Installment Receivables", :ncr_retainage_withheld => "Retainage Withheld" }, :intercompany_receivable => { :intercompany_receivables => "Intercompany Receivables" }, :non_current_asset => { :nca_organization_costs => "Organization Costs", :nca_patents_and_licenses => "Patents and Licenses", :nca_intangible_assets => "Intangible Assets - Capitalized Software Costs" }, :liability => { :liability_general => "General Liabilities" }, :payable => { :payable_trade => "Trade Payable", :payable_accrued => "Accrued Accounts Payable", :payable_retained_withheld => "Payable Retained Withheld", :payable_maturities_long_term_debt => "Current Maturities of Long-Term Debt", :payable_bank_notes => "Bank Notes Payable", :payable_construction_loans => "Construction Loans Payable" }, :accrued_compensation => { :accrued_payroll => "Payroll", :accrued_commissions => "Commissions", :accrued_fica => "FICA", :accrued_unemployment_taxes => "Unemployment Taxes", :accrued_workmen_compensation => "Workmen's Compensation", :accrued_medical_benefits => "Medical Benefits", :accrued_retirement_company_match => "401 K Company Match", :accrued_esop_company_match => "ESOP Company Match", :accrued_eot_company_match => "EOT Company Match", :accrued_withheld_fica => "Withheld FICA", :accrued_withheld_medical_benefits => "Withheld Medical Benefits", :accrued_withheld_retirement_contributions => "401 K Employee Contributions", :accrued_withheld_esop_contributions => "ESOP Employee Contributions", :accrued_withheld_eot_contributions => "EOT Employee Contributions" }, :accrued_expense => { :accrued_expense_rent => "Rent", :accrued_expense_interest => "Interest", :accrued_expense_property_taxes => "Property Taxes", :accrued_expense_warranty => "Warranty Expense" }, :accrued_tax => { :accrued_federal_income_taxes => "Federal Income Taxes", :accrued_state_income_taxes => "State Income Taxes", :accrued_county_income_taxes => "County Income Taxes", :accrued_city_income_taxes => "Municipal Income Taxes", :accrued_franchise_taxes => "Franchise Taxes", :accrued_sales_taxes => "Sales Taxes", :accrued_deferred_fit_current => "Deferred Federal Income Taxes Current", :accrued_deferred_sit_current => "Deferred State Income Taxes Current", :accrued_deferred_sales_taxes_current => "Deferred State Sales Taxes Current", :accrued_deferred_cit_current => "Deferred County Income Taxes Current", :accrued_deferred_mit_current => "Deferred Municipal Income Taxes Current" }, :deferred_tax => { :deferred_fit_non_current => "Deferred Federal Income Taxes Noncurrent", :deferred_sit_non_current => "Deferred State Income Taxes Noncurrent", :deferred_sales_tax_non_current => "Deferred State Sales Taxes Noncurrent", :deferred_cit_non_current => "Deferred County Income Taxes Noncurrent", :deferred_mit_non_current => "Deferred Municipal Income Taxes Noncurrent" }, :long_term_debt => { :long_term_notes_payable => "Long Term Debt Notes Payable", :long_term_mortgages_payable => "Long Term Mortgages Payable", :long_term_installment_notes_payable => "Long Term Installment Notes Payable" }, :intercompany_payable => { :intercompany_payables => "Intercompany Payables" }, :non_current_liability => { :non_current_liabilities => "Non Current Liabilities" }, :owner_equity => { :owner_common_stock => "Owner's Common Stock", :owner_preferred_stock => "Owner's Preferred Stock", :owner_paid_capital => "Owner Paid in Capital", :owner_member_contributions => "Owner Member Contributions", :owner_retained_earnings => "Owner's Retained Earnings" }, :revenue => { :revenue_interest_income => "Interest Income", :revenue_other_income => "Other Income", :revenue_finance_charge_income => "Finance Charge Income", :revenue_sales_returns_allowances => "Sales Returns and Allowances", :revenue_sales_discounts => "Sales Discounts" }, :cost_of_goods_sold => { :cogs_freight => "Freight", :cogs_inventory_adjustment => "Inventory Adjustment", :cogs_purchase_returns => "Purchase Returns and Allowances", :cogs_reserved => "Reserved" }, :operating_expense => { :expense_advertising => "Advertising Expense", :expense_amortization => "Amortization Expense", :expense_vehicle => "Vehicle Expense", :expense_bad_debt => "Bad Debt Expense", :expense_bank_charges => "Bank Charges", :expense_payment_clearing_charges => "Payment Clearing Charges", :expense_cash_over_short => "Cash Over and Short", :expense_commissions => "Commissions Expense", :expense_depreciation => "Depreciation Expense", :expense_employee_benefit_program => "Employee Benefit Program", :expense_freight => "Freight Expense", :expense_gifts => "Gifts Expense", :expense_insurance_general => "Insurance - General", :expense_interest => "Interest Expense", :expense_professional_fees => "Professional Fees", :expense_licenses => "License Expense", :expense_maintenance => "Maintenance Expense", :expense_meals_entertainment => "Meals and Entertainment Expense", :expense_office => "Office Expense", :expense_payroll => "Payroll Expense", :expense_printing => "Printing Expense", :expense_postage => "Postage Expense", :expense_rent => "Rent", :expense_repairs => "Repairs", :expense_salaries => "Salaries", :expense_supplies => "Supplies", :expense_federal_income_tax => "Federal Income Tax", :expense_state_income_tax => "State Income Tax", :expense_county_income_tax => "County Income Tax", :expense_city_income_tax => "Municipal Income Tax", :expense_utilities => "Utilities Expense", :expense_gain_loss_asset_sale => "Gain/Loss on Sale of Assets", :expense_currency_conversion => "Currency Conversion Costs" } } # Assets: 10000-19999 # Receivables: 20000-29999 # Inventories: 30000-39999 # PREPAID EXPENSES & OTHER CURRENT ASSETS: 40000-49999 # PROPERTY PLANT & EQUIPMENT: 50000-59999 # ACCUMULATED DEPRECIATION & AMORTIZATION: 60000-69999 # NON – CURRENT RECEIVABLES: 70000-79999 # INTERCOMPANY RECEIVABLES: 80000-89999 # OTHER NON-CURRENT ASSETS: 90000-99999 # LIABILITIES: 100000-109999 # PAYABLES: 110000-119999 # ACCRUED COMPENSATION & RELATED ITEMS: 120000-129999 # OTHER ACCRUED EXPENSES: 130000-139999 # ACCRUED TAXES: 140000-149999 # DEFERRED TAXES: 150000-159999 # LONG-TERM DEBT: 160000-169999 # INTERCOMPANY PAYABLES: 170000-179999 # OTHER NON CURRENT LIABILITIES: 180000-189999 # OWNERS EQUITIES: 190000-199999 # REVENUE: 200000-209999 # COST OF GOODS SOLD: 210000-219999 # OPERATING EXPENSES: 220000-229999 type_codes = { :asset => (10000..19999), :receivable => (20000..29999), :inventory => (30000..39999), :prepaid_expense => (40000..49999), :equipment => (50000..59999), :depreciation => (60000..69999), :non_current_receivable => (70000..79999), :intercompany_receivable => (80000..89999), :non_current_asset => (90000..99999), :liability => (100000..109999), :payable => (110000..119999), :accrued_compensation => (120000..129999), :accrued_expense => (130000..139999), :accrued_tax => (140000..149999), :deferred_tax => (150000..159999), :long_term_debt => (160000..169999), :intercompany_payable => (170000..179999), :non_current_liability => (180000..189999), :owner_equity => (190000..199999), :revenue => (200000..209999), :cost_of_goods_sold => (210000..219999), :operating_expense => (220000..229999) } # type_mnemonics = { :asset => "CASH", :receivable => "AREC", :inventory => "INV", :prepaid_expense => "PREPAID", :equipment => "PPE", :depreciation => "ACCUM DEPR", :non_current_receivable => "NCR", :intercompany_receivable => "IREC", :non_current_asset => "OTHER NCA", :liability => "LIAB", :payable => "APAY", :accrued_compensation => "ACCRU COMP", :accrued_expense => "ACCRU EXP", :accrued_tax => "ACCRU TAX", :deferred_tax => "DEFR TAX", :long_term_debt => "LONG DEBT", :intercompany_payable => "IPAY", :non_current_liability => "NONCUR LIAB", :owner_equity => "OWNER EQTY", :revenue => "REV", :cost_of_goods_sold => "COGS", :operating_expense => "OEXP" } # {:categories => type_codes, :mnemonics => type_mnemonics, :accounts => accounts} end # def self.next_account_code(exchange_uuid=nil, member_uuid=nil, category=nil, category_increment=10) result = nil if ::GxG::valid_uuid?(exchange_uuid) && ::GxG::valid_uuid?(member_uuid) && category.is_any?(::String, ::Symbol) # determine new code number valid_range = ::GxG::Services::Accounting::default_accounts_reference()[:categories][(category.to_s.to_sym)] if valid_range codes_in_use = [] accounts = [] ::GxG::DB[:roles][:data].search_database(::GxG::DB[:administrator], {:ufs => "org.gxg.accounting.account", :properties => [{:exchange_uuid => {:equals => exchange_uuid.to_s}},{:member_uuid => {:equals => member_uuid.to_s}}]}).each do |header| if header record = ::GxG::DB[:roles][:data].retrieve_by_uuid(header[:uuid], ::GxG::DB[:administrator]) if record accounts << record end end end accounts.each do |the_account| if valid_range.include?(the_account[:account_code]) codes_in_use << the_account[:account_code] end end unless category_increment.is_a?(::Integer) category_increment = 10 end if codes_in_use.size > 0 account_code = codes_in_use.max + category_increment else account_code = valid_range.first + category_increment end if account_code > valid_range.last codes_in_use.each do |the_code| unless codes_in_use.include?(the_code + 1) account_code = the_code + 1 break end end end result = account_code end end result end # def self.valid_account_moniker(account_code=0, account_name=nil) # Review : Experimental : .downcase.match(/\A[a-z0-9\s]+\Z/i).to_s.gsub(" ","_") new_moniker = "" mnemonic = "CASH" reference_data = ::GxG::Services::Accounting::Book::default_accounts_reference() reference_data[:categories].each_pair do |the_category, the_code_range| if the_code_range.include?(account_code) mnemonic = (reference_data[:mnemonics][(the_category)] || "CASH") break end end (mnemonic + "_" + account_name.to_s).downcase.gsub(" ","_").chars.each do |the_character| the_valid_char = the_character.match(/^[a-z0-9\_]$/) if the_valid_char new_moniker << the_valid_char.to_s end end new_moniker[0..255].to_sym end # def self.account_uuid_by_moniker(exchange_uuid=nil, member_uuid=nil, moniker=nil) result = nil if ::GxG::valid_uuid?(exchange_uuid) && ::GxG::valid_uuid?(member_uuid) && moniker.is_any(::String, ::Symbol) found = ::GxG::DB[:roles][:data].search_database(::GxG::DB[:administrator], {:ufs => "org.gxg.accounting.account", :properties => [{:exchange_uuid => {:equals => exchange_uuid.to_s}},{:member_uuid => {:equals => member_uuid.to_s}},{:moniker => {:equals => moniker.to_s}}]}).first # if found result = found[:uuid].to_s.to_sym end end result end # ### Initialize def initialize(exchange_uuid=nil, member_uuid=nil) unless ::GxG::valid_uuid?(exchange_uuid) raise ArgumentError, "Exchange MUST be specified with a valid UUID." end unless ::GxG::valid_uuid?(member_uuid) raise ArgumentError, "Exchange Member MUST be specified with a valid UUID." end # @exchange = ::GxG::DB[:roles][:data].retrieve_by_uuid(exchange_uuid, GxG::DB[:administrator]) unless @exchange.is_a?(::GxG::Database::PersistedHash) raise ArgumentError, "Exchange #{exchange_uuid.inspect} is missing: Could not retrieve its profile." end @member = ::GxG::DB[:roles][:data].retrieve_by_uuid(member_uuid, GxG::DB[:administrator]) unless @member.is_a?(::GxG::Database::PersistedHash) raise ArgumentError, "Exchange Member #{member_uuid.inspect} is missing: Could not retrieve thier profile." end # @accounts = {} @thread_safety = ::Mutex.new # self end # def mount_account(the_account=nil) if the_account.is_a?(::GxG::Services::Accounting::Account) @thread_safety.synchronize { @accounts[(the_account[:moniker].to_s.to_sym)] = the_account @accounts = @accounts.sort_by {|the_moniker, account| account[:account_code]}.to_h } end end # def accounts_available() @thread_safety.synchronize { @accounts.keys } end # def [](the_account_moniker=nil) @thread_safety.synchronize { @accounts[(the_account_moniker.to_s.to_sym)] } end # def create_account(category=nil, moniker=nil, account_name=nil, reference_uuid=nil, category_increment=10, alternate_currency=nil) result = false # determine new code number valid_range = ::GxG::Services::Accounting::default_accounts_reference()[:categories][(category.to_s.to_sym)] if valid_range codes_in_use = [] @thread_safety.synchronize { @accounts.each_pair do |the_moniker, the_account| if valid_range.include?(the_account[:account_code]) codes_in_use << the_account[:account_code] end end } unless category_increment.is_a?(::Integer) category_increment = 10 end if codes_in_use.size > 0 account_code = codes_in_use.max + category_increment else account_code = valid_range.first + category_increment end if account_code > valid_range.last codes_in_use.each do |the_code| unless codes_in_use.include?(the_code + 1) account_code = the_code + 1 break end end end if ::GxG::valid_uuid?(alternate_currency) currency = alternate_currency.to_s.to_sym else currency = @exchange[:currency_uuid].to_s.to_sym end # Review : auto-set account monker based upon account_code and name: unless moniker moniker = ::GxG::Accounting::Book::valid_account_moniker(account_code, account_name).to_s.to_sym end new_account = ::GxG::Services::Accounting::create_account(moniker, @exchange.uuid, @member.uuid, currency, account_code, account_name, reference_uuid) if new_account new_account.post_credit(0,"Open Account") @thread_safety.synchronize { @accounts[(new_account[:moniker].to_s.to_sym)] = new_account @accounts = @accounts.sort_by {|the_moniker, account| account[:account_code]}.to_h } result = true else log_warning("Could not create Account #{moniker.inspect} .") end else log_warning("Category #{category.inspect} not found.") end # result end end # def self.create_book(exchange_uuid=nil, member_uuid=nil) unless ::GxG::valid_uuid?(exchange_uuid) raise ArgumentError, "Exchange MUST be specified with a valid UUID." end unless ::GxG::valid_uuid?(member_uuid) raise ArgumentError, "Exchange Member MUST be specified with a valid UUID." end the_exchange = ::GxG::DB[:roles][:data].retrieve_by_uuid(exchange_uuid, GxG::DB[:administrator]) unless the_exchange.is_a?(::GxG::Database::PersistedHash) raise ArgumentError, "Exchange #{exchange_uuid.inspect} is missing: Could not retrieve its profile." end currency_uuid = the_exchange[:currency_uuid] the_member = ::GxG::DB[:roles][:data].retrieve_by_uuid(member_uuid, GxG::DB[:administrator]) unless the_member.is_a?(::GxG::Database::PersistedHash) raise ArgumentError, "Exchange Member #{member_uuid.inspect} is missing: Could not retrieve thier profile." end # Note : {:categories => type_codes, :mnemonics => type_mnemonics, :accounts => accounts} defaults = ::GxG::Services::Accounting::Book::default_accounts_reference() # Build Accounts cleanup_list = [] prior_code = 0 begin defaults[:accounts].keys.each do |the_category| prior_code = defaults[:categories][(the_category)].first defaults[:accounts][(the_category)].each_pair do |the_account_moniker, the_account_title| prior_code += 10 new_account = ::GxG::Services::Accounting::create_account(the_account_moniker, exchange_uuid, member_uuid, currency_uuid, prior_code, the_account_title) if new_account cleanup_list << new_account end end end rescue Exception => the_error # delete orphaned accounts while cleanup_list.size > 0 do ::GxG::Services::Accounting::delete_account(cleanup_list.shift.uuid) end ::GxG::DB[:roles][:data].empty_trash() raise the_error end # cleanup_list.each do |the_account| the_account.post_credit(0, "Open Account") end true end # def self.load_book(exchange_uuid=nil, member_uuid=nil) result = nil unless ::GxG::valid_uuid?(exchange_uuid) raise ArgumentError, "Exchange MUST be specified with a valid UUID." end unless ::GxG::valid_uuid?(member_uuid) raise ArgumentError, "Exchange Member MUST be specified with a valid UUID." end # create chart of accounts if missing, otherwise load manifest of accounts. search_criteria = {:ufs => "org.gxg.accounting.account", :properties => []} search_criteria[:properties] << {:exchange_uuid => {:equals => exchange_uuid.to_s}} search_criteria[:properties] << {:member_uuid => {:equals => member_uuid.to_s}} accounts_manifest = ::GxG::DB[:roles][:data].search_database(GxG::DB[:administrator], search_criteria) unless accounts_manifest.size > 0 if GxG::Services::Accounting::Book::create_book(exchange_uuid, member_uuid) accounts_manifest = ::GxG::DB[:roles][:data].search_database(GxG::DB[:administrator], search_criteria) else accounts_manifest = [] end end if accounts_manifest.size > 0 # mount accounts in book begin the_book = ::GxG::Services::Accounting::Book.new(exchange_uuid, member_uuid) if the_book # accounts = [] accounts_manifest.each do |the_header| the_account = ::GxG::Services::Accounting::load_account(the_header[:uuid]) if the_account accounts << the_account end end accounts = accounts.sort_by {|entry| entry[:account_code]} accounts.each do |the_account| the_book.mount_account(the_account) end # if accounts.size > 0 result = the_book end end rescue Exception => the_error log_error({:error => the_error, :parameters => {:exchange => exchange_uuid, :member => member_uuid}}) end else log_warning("Unable to load or create Accounts while opening a Chart of Accounts for Member #{member_uuid.inspect} of Exchange #{exchange_uuid.inspect} .") end result end # def self.delete_book(exchange_uuid=nil, member_uuid=nil) search_criteria = {:ufs => "org.gxg.accounting.account", :properties => []} search_criteria[:properties] << {:exchange_uuid => {:equals => exchange_uuid.to_s}} search_criteria[:properties] << {:member_uuid => {:equals => member_uuid.to_s}} accounts_manifest = ::GxG::DB[:roles][:data].search_database(GxG::DB[:administrator], search_criteria) accounts_manifest.each do |the_header| entries_manifest = ::GxG::DB[:roles][:data].search_database(GxG::DB[:administrator], {:ufs => "org.gxg.accounting.account.entry", :properties => [{:account_uuid => {:equals => the_header[:uuid].to_s}}]}) entries_manifest.each do |the_entry_header| ::GxG::DB[:roles][:data].destroy_by_uuid(GxG::DB[:administrator], the_entry_header[:uuid]) end ::GxG::DB[:roles][:data].destroy_by_uuid(GxG::DB[:administrator], the_header[:uuid]) end true end # def self.create_account(moniker=nil, exchange_uuid=nil, member_uuid=nil, currency_uuid=nil, account_code=nil, account_name=nil, reference_uuid=nil) result = nil unless moniker.is_any?(::String, ::Symbol) raise ArgumentError, "Account Moniker is invalid, please provide a String or Symbol - not: #{moniker.inspect} ." end unless ::GxG::valid_uuid?(exchange_uuid) raise ArgumentError, "Exchange UUID is invalid: #{exchange_uuid.inspect} ." end unless ::GxG::valid_uuid?(member_uuid) raise ArgumentError, "Exchange Member UUID is invalid: #{member_uuid.inspect} ." end unless ::GxG::valid_uuid?(currency_uuid) raise ArgumentError, "Currenty UUID is invalid: #{currency_uuid.inspect} ." end currency_profile = ::GxG::DB[:roles][:reference].retrieve_by_uuid(currency_uuid, GxG::DB[:administrator]) unless currency_profile.is_a?(::GxG::Database::PersistedHash) raise ArgumentError, "Currenty #{currency_uuid.inspect} is missing: Could not retrieve its profile." end unless account_code.is_a?(::Numeric) raise ArgumentError, "Account Code #{account_code.inspect} is invalid: Needs to be a Numeric." end unless account_name.is_a?(::String) raise ArgumentError, "Account Name #{account_name.inspect} is invalid: Needs to be a String." end # ### ensure account moniker is unique for this exchange and member unique_criteria = {:ufs => "org.gxg.accounting.account", :properties => []} unique_criteria[:properties] << {:moniker => {:equals => moniker.to_s}} unique_criteria[:properties] << {:exchange_uuid => {:equals => exchange_uuid.to_s}} unique_criteria[:properties] << {:member_uuid => {:equals => member_uuid.to_s}} if ::GxG::DB[:roles][:data].search_database(GxG::DB[:administrator], unique_criteria).size > 0 raise ArgumentError, "Account Moniker is NOT UNIQUE for this Exchange & Member, please provide a UNIQUE one - not: #{moniker.inspect} ." end # ### create db record new_account = ::GxG::DB[:roles][:data].new_structure_from_format(GxG::DB[:administrator],{:ufs => "org.gxg.accounting.account"}) if new_account.is_a?(::GxG::Database::PersistedHash) new_account.get_reservation new_account[:moniker] = moniker.to_s new_account[:exchange_uuid] = exchange_uuid.to_s new_account[:member_uuid] = member_uuid.to_s if ::GxG::valid_uuid?(reference_uuid) new_account[:reference_uuid] = reference_uuid.to_s end new_account[:currency_uuid] = currency_uuid.to_s new_account[:account_code] = account_code.to_i new_account[:account_name] = account_name.to_s new_account[:balance_updated_at] = DateTime.now new_account.title = account_name.to_s new_account.save new_account.release_reservation # ### mount db record result = ::GxG::Services::Accounting::Account.new(new_account, currency_profile) else raise Exception, "Could not create the account: #{account_name} ." end # result end # def self.load_account(account_uuid=nil) result = nil if ::GxG::valid_uuid?(account_uuid) account = ::GxG::DB[:roles][:data].retrieve_by_uuid(account_uuid, GxG::DB[:administrator]) if account.is_a?(::GxG::Database::PersistedHash) currency_uuid = account[:currency_uuid].to_s.to_sym currency_profile = ::GxG::DB[:roles][:reference].retrieve_by_uuid(currency_uuid, GxG::DB[:administrator]) if currency_profile.is_a?(::GxG::Database::PersistedHash) result = ::GxG::Services::Accounting::Account.new(account, currency_profile) end end end result end # def self.delete_account(account_uuid=nil) result = false if ::GxG::valid_uuid?(account_uuid) account = ::GxG::DB[:roles][:data].retrieve_by_uuid(account_uuid, GxG::DB[:administrator]) if account.is_a?(::GxG::Database::PersistedHash) account.get_reservation # Review : allow for branch if cannot secure write reservation if account.write_reserved?() remove_list = [] available_list = ::GxG::DB[:roles][:data].search_database(GxG::DB[:administrator], {:properties => [{:account_uuid => {:equals => account.uuid.to_s}}]}) available_list.each do |header| account_entry = ::GxG::DB[:roles][:data].retrieve_by_uuid(header[:uuid], GxG::DB[:administrator]) if account_entry account_entry.get_reservation if account_entry.write_reserved? remove_list << account_entry else remove_list = [] break end end end # if available_list.size > 0 if remove_list.size > 0 remove_list.each do |entry_object| entry_object.destroy end # account.destroy result = true else raise Exception, "Cannot secure a write-reservation on an Account Entry (aborting)." end else account.destroy result = true end else raise Exception, "Cannot secure a write-reservation on Account #{account.uuid.to_s}." end end end result end # end end end # ### Reference # # Chart of Accounts - Account Profile # format_manifest[:account] = { # :uuid => :"b69640db-5b14-453b-87b5-42279a8eba10", # :version => 0.0, # :ufs => "org.gxg.accounting.account", # :title => "Account", # :mime_types => [], # :content => { # :exchange_uuid => "", # :member_uuid => "", # :currency_uuid => "", # :account_code => 0, # :account_name => "", # :balance_updated_at => nil, # :balance => 0 # } # } # # Account Entry # format_manifest[:account_entry] = { # :uuid => :"95d7ef37-cfbf-49d6-8f42-07c956d02518", # :version => 0.0, # :ufs => "org.gxg.accounting.account.entry", # :title => "Account Entry", # :mime_types => [], # :content => { # :account_uuid => "", # :occurred_at => nil, # :description => "", # :is_debit => false, # :amount => 0 # } # } # ### Setup accounting_service = ::GxG::Services::Service.new(:accounting) # ### Define Public Command Interface: accounting_service.on(:start, {:description => "Accounting Service Start", :usage => "{ :start => nil }"}) do ::GxG::SERVICES[:accounting].start end accounting_service.on(:stop, {:description => "Accounting Service Stop", :usage => "{ :stop => nil }"}) do ::GxG::SERVICES[:accounting].stop end accounting_service.on(:restart, {:description => "Accounting Service Restart", :usage => "{ :restart => nil }"}) do ::GxG::SERVICES[:accounting].restart end accounting_service.on(:pause, {:description => "Accounting Service Pause", :usage => "{ :pause => nil }"}) do ::GxG::SERVICES[:accounting].pause end accounting_service.on(:resume, {:description => "Accounting Service Resume", :usage => "{ :resume => nil }"}) do ::GxG::SERVICES[:accounting].resume end # ### Define Internal Service Control Events: accounting_service.on(:at_start, {:description => "Accounting Startup", :usage => "{ :at_start => (service-object) }"}) do |service, credential| {:result => true} end accounting_service.on(:open_book, {:description => "Open Account Book", :usage => "{ :open_book => {:exchange_uuid => , :member_uuid => } }"}) do |details, credential| begin unless ::GxG::valid_uuid?(details[:exchange_uuid]) raise ArgumentError, "You MUST provide a valid UUID specifying the Exchange in question." end unless ::GxG::valid_uuid?(details[:member_uuid]) raise ArgumentError, "You MUST provide a valid UUID specifying the Exchange Member in question." end unless ::GxG::SERVICES[:accounting] raise Exception, "Accounting Service not yet started." end # the_book = ::GxG::Services::Accounting::load_book(details[:exchange_uuid].to_s.to_sym, details[:member_uuid].to_s.to_sym) if the_book ::GxG::SERVICES[:accounting][(details[:member_uuid].to_s.to_sym)] = the_book {:result => true} else {:result => nil, :error => "Could not load the book for Exchange Member #{details[:member_uuid].to_s.to_sym.inspect}."} end # rescue Exception => the_error {:result => nil, :error => the_error.to_s} end end # ### Service Installation unless ::GxG::Services::service_available?(:accounting) ::GxG::Services::install_service(:accounting) ::GxG::Services::enable_service(:accounting) end #