mirror of
https://github.com/mistergibson/gxg-server.git
synced 2026-10-19 06:59:44 -07:00
Initial Commit
This commit is contained in:
parent
293d1d2445
commit
c48d4c7ed0
803 changed files with 561605 additions and 1 deletions
884
Services/accounting/accounting.rb
Normal file
884
Services/accounting/accounting.rb
Normal file
|
|
@ -0,0 +1,884 @@
|
|||
# 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
|
||||
# ### <account_profile>,<currency_profile>
|
||||
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 : <the-string>.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 => <UUID>, :member_uuid => <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
|
||||
#
|
||||
1435
Services/core.rb
Executable file
1435
Services/core.rb
Executable file
File diff suppressed because it is too large
Load diff
1104
Services/exchange/exchange.rb
Normal file
1104
Services/exchange/exchange.rb
Normal file
File diff suppressed because it is too large
Load diff
12
Services/exchange/exchange/orders.rb
Normal file
12
Services/exchange/exchange/orders.rb
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
module GxG
|
||||
module Services
|
||||
module Exchange
|
||||
# ### Products
|
||||
# ### Services
|
||||
# ### Subscriptions
|
||||
# ### Packages
|
||||
# ### Orders
|
||||
# ### Order Items
|
||||
end
|
||||
end
|
||||
end
|
||||
12
Services/exchange/exchange/packages.rb
Normal file
12
Services/exchange/exchange/packages.rb
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
module GxG
|
||||
module Services
|
||||
module Exchange
|
||||
# ### Products
|
||||
# ### Services
|
||||
# ### Subscriptions
|
||||
# ### Packages
|
||||
# ### Orders
|
||||
# ### Order Items
|
||||
end
|
||||
end
|
||||
end
|
||||
12
Services/exchange/exchange/products.rb
Normal file
12
Services/exchange/exchange/products.rb
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
module GxG
|
||||
module Services
|
||||
module Exchange
|
||||
# ### Products
|
||||
# ### Services
|
||||
# ### Subscriptions
|
||||
# ### Packages
|
||||
# ### Orders
|
||||
# ### Order Items
|
||||
end
|
||||
end
|
||||
end
|
||||
12
Services/exchange/exchange/services.rb
Normal file
12
Services/exchange/exchange/services.rb
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
module GxG
|
||||
module Services
|
||||
module Exchange
|
||||
# ### Products
|
||||
# ### Services
|
||||
# ### Subscriptions
|
||||
# ### Packages
|
||||
# ### Orders
|
||||
# ### Order Items
|
||||
end
|
||||
end
|
||||
end
|
||||
12
Services/exchange/exchange/subscriptions.rb
Normal file
12
Services/exchange/exchange/subscriptions.rb
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
module GxG
|
||||
module Services
|
||||
module Exchange
|
||||
# ### Products
|
||||
# ### Services
|
||||
# ### Subscriptions
|
||||
# ### Packages
|
||||
# ### Orders
|
||||
# ### Order Items
|
||||
end
|
||||
end
|
||||
end
|
||||
305
Services/www/www.rb
Normal file
305
Services/www/www.rb
Normal file
|
|
@ -0,0 +1,305 @@
|
|||
#!/usr/bin/env ruby
|
||||
#
|
||||
# ### Standard Ruby is fine for small installations and development.
|
||||
# ### JRuby is best for scaled production as it uses *real* threads for each request.
|
||||
# ### WEBrick was chosen for its sheer simplicity. When combined with JRuby it performs suprisingly well.
|
||||
# ### DO NOT use with puma or rainbow or unicorn as this is a unitary NON-forking design. (feature != bug)
|
||||
#
|
||||
require "securerandom"
|
||||
require "logger"
|
||||
require "webrick"
|
||||
# module WEBrick
|
||||
# class Cookie
|
||||
# def to_s
|
||||
# ret = ""
|
||||
# ret << @name << "=" << @value
|
||||
# ret << "; " << "Version=" << @version.to_s if @version > 0
|
||||
# ret << "; " << "Domain=" << @domain if @domain
|
||||
# ret << "; " << "Expires=" << @expires if @expires
|
||||
# ret << "; " << "Max-Age=" << @max_age.to_s if @max_age
|
||||
# ret << "; " << "Comment=" << @comment if @comment
|
||||
# ret << "; " << "Path=" << @path if @path
|
||||
# ret << "; " << "Secure" if @secure
|
||||
# # ret << "; " << "SameSite=Lax"
|
||||
# ret
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
require "webrocket"
|
||||
# See: https://github.com/unak/WEBrocket
|
||||
require "sinatra/base"
|
||||
require "sinatra/webdav"
|
||||
require "sinatra/contrib/all"
|
||||
# See: http://sinatrarb.com/contrib/streaming.html
|
||||
#
|
||||
# GxG WWW Initialization
|
||||
www_service = ::GxG::Services::Service.new(:www)
|
||||
www_paths = {:www_public => "#{::GxG::SYSTEM_PATHS[:public]}/www"}
|
||||
www_paths[:www_themes] = "#{www_paths[:www_public]}/themes"
|
||||
www_paths[:www_javascript] = "#{www_paths[:www_public]}/javascript"
|
||||
www_paths[:www_images] = "#{www_paths[:www_public]}/images"
|
||||
www_paths[:www_audio] = "#{www_paths[:www_public]}/audio"
|
||||
www_paths[:www_video] = "#{www_paths[:www_public]}/video"
|
||||
www_paths.values.each do |a_path|
|
||||
unless Dir.exist?(a_path)
|
||||
begin
|
||||
FileUtils.mkpath(a_path, 0755)
|
||||
rescue Exception => error
|
||||
log_error({:error => error, :parameters => a_path})
|
||||
end
|
||||
end
|
||||
end
|
||||
www_paths.each_pair do |moniker, path|
|
||||
::GxG::SYSTEM_PATHS[(moniker)] = path
|
||||
end
|
||||
# ### Sinatra Bootstrap
|
||||
class Node0 < Sinatra::Application
|
||||
enable :sessions
|
||||
# use Rack::Session::Cookie, {:key => "rack.session", :expire_after => 318513600, :same_site => :strict, :secure => true}
|
||||
#
|
||||
helpers Sinatra::Streaming
|
||||
register Sinatra::WebDAV
|
||||
configure do
|
||||
set :backend, WEBrick
|
||||
set :environment, (::GxG::SERVICES[:www].configuration[:mode] || "production").to_sym
|
||||
set :bind, ((::GxG::SERVICES[:www].configuration[:listen] || [])[0] || {:address => "127.0.0.1"})[:address]
|
||||
set :port, ((::GxG::SERVICES[:www].configuration[:listen] || [])[0] || {:port => 32767})[:port].to_i
|
||||
set :sessions, true
|
||||
set :root, ::GxG::SYSTEM.gxg_root()
|
||||
set :public_folder, ::GxG::SYSTEM_PATHS[:www_public]
|
||||
set :logging, true
|
||||
set :logger, ::GxG::LOG
|
||||
secret = ::GxG::uuid_generate.to_s
|
||||
set :session_secret, secret
|
||||
use Rack::Session::Pool, {:expire_after => 318513600, :secure => false}
|
||||
use Rack::Session::Cookie, {:key => 'rack.session', :path => '/', :expire_after => 318513600, :secret => secret, :secure => false}
|
||||
end
|
||||
# ### Extend Sinatra Verbs
|
||||
# DAV Class 1 Supports
|
||||
# def self.mkcol(path, options = {}, &block)
|
||||
# route('MKCOL', path, options, &block)
|
||||
# end
|
||||
# #
|
||||
# def self.copy(path, options = {}, &block)
|
||||
# route('COPY', path, options, &block)
|
||||
# end
|
||||
# #
|
||||
# def self.move(path, options = {}, &block)
|
||||
# route('MOVE', path, options, &block)
|
||||
# end
|
||||
# #
|
||||
# def self.propfind(path, options = {}, &block)
|
||||
# route('PROPFIND', path, options, &block)
|
||||
# end
|
||||
# #
|
||||
# def self.proppatch(path, options = {}, &block)
|
||||
# route('PROPPATCH', path, options, &block)
|
||||
# end
|
||||
# # DAV Class 2 Supports
|
||||
# def self.enlock(path, options = {}, &block)
|
||||
# route('LOCK', path, options, &block)
|
||||
# end
|
||||
# #
|
||||
# def self.unlock(path, options = {}, &block)
|
||||
# route('UNLOCK', path, options, &block)
|
||||
# end
|
||||
# # DAV Class 3 Supports
|
||||
# # TODO: Versioning ??
|
||||
# # ### Set OPTIONS Header Values
|
||||
# route('OPTIONS', '*') do
|
||||
# # FIXME no GET on collections, so make this a resource method
|
||||
# headers({"DAV" => "1 2 3", "Ms-Author-Via" => "DAV", "Allow" => "OPTIONS, HEAD, GET, PUT, DELETE, MKCOL, COPY, MOVE, PROPFIND, PROPPATCH, LOCK, UNLOCK"})
|
||||
# ok
|
||||
# end
|
||||
# ### Implementation of the verbs:
|
||||
get '*' do
|
||||
GxG::SERVICES[:www][:receiver].handle_request(:get, request, session)
|
||||
end
|
||||
#
|
||||
put '*' do
|
||||
GxG::SERVICES[:www][:receiver].handle_request(:put, request, session)
|
||||
end
|
||||
#
|
||||
post '*' do
|
||||
GxG::SERVICES[:www][:receiver].handle_request(:post, request, session)
|
||||
end
|
||||
#
|
||||
delete '*' do
|
||||
GxG::SERVICES[:www][:receiver].handle_request(:delete, request, session)
|
||||
end
|
||||
#
|
||||
mkcol '*' do
|
||||
GxG::SERVICES[:www][:receiver].handle_request(:mkcol, request, session)
|
||||
end
|
||||
#
|
||||
copy '*' do
|
||||
GxG::SERVICES[:www][:receiver].handle_request(:copy, request, session)
|
||||
end
|
||||
#
|
||||
move '*' do
|
||||
GxG::SERVICES[:www][:receiver].handle_request(:move, request, session)
|
||||
end
|
||||
#
|
||||
propfind '*' do
|
||||
GxG::SERVICES[:www][:receiver].handle_request(:propfind, request, session)
|
||||
end
|
||||
#
|
||||
proppatch '*' do
|
||||
GxG::SERVICES[:www][:receiver].handle_request(:proppatch, request, session)
|
||||
end
|
||||
# lock method collision work-around:
|
||||
# lock '*' do
|
||||
# GxG::SERVICES[:www][:receiver].handle_request(:lock, request, session)
|
||||
# end
|
||||
route('LOCK', '*') do
|
||||
GxG::SERVICES[:www][:receiver].handle_request(:lock, request, session)
|
||||
end
|
||||
#
|
||||
unlock '*' do
|
||||
GxG::SERVICES[:www][:receiver].handle_request(:unlock, request, session)
|
||||
end
|
||||
# Note : options is pre-defined.
|
||||
end
|
||||
# ### WebSocket Listener
|
||||
module GxGwww
|
||||
SOCKETS = {}
|
||||
SOCKETS_SAFETY = Mutex.new
|
||||
end
|
||||
#
|
||||
class WebSocketListener
|
||||
def initialize()
|
||||
self
|
||||
end
|
||||
#
|
||||
def on_open(the_socket)
|
||||
the_uuid = GxG::uuid_generate.to_sym
|
||||
the_socket.instance_variable_set(:@uuid, the_uuid)
|
||||
the_socket.instance_variable_set(:@session, nil)
|
||||
the_socket.instance_variable_set(:@display, nil)
|
||||
the_socket.instance_variable_set(:@inbox, [])
|
||||
GxGwww::SOCKETS_SAFETY.synchronize {
|
||||
GxGwww::SOCKETS[(the_uuid)] = the_socket
|
||||
}
|
||||
#
|
||||
GxG::SERVICES[:www].dispatcher.post_event(:communications) do
|
||||
sleep 0.5
|
||||
the_socket.send({ :attach_socket => the_uuid.to_s }.to_json.encode64, :text)
|
||||
end
|
||||
#
|
||||
true
|
||||
end
|
||||
#
|
||||
def on_message(the_socket, the_data, the_type)
|
||||
message_queue = the_socket.instance_variable_get(:@inbox)
|
||||
GxGwww::SOCKETS_SAFETY.synchronize {
|
||||
if the_type == :text
|
||||
begin
|
||||
# Expects: json+base64 of a Hash
|
||||
message_queue << JSON.parse(the_data.to_s.decode64, {:symbolize_names => true})
|
||||
rescue Exception => the_error
|
||||
log_error({:error => the_error, :parameters => {:data => the_data}})
|
||||
end
|
||||
else
|
||||
# Takes in all other data as binary into inbox
|
||||
message_queue << ::GxG::ByteArray.new(the_data.to_s)
|
||||
end
|
||||
}
|
||||
true
|
||||
end
|
||||
#
|
||||
def on_recv(the_socket, the_data, the_type)
|
||||
self.on_message(the_socket, the_data, the_type)
|
||||
end
|
||||
#
|
||||
def on_close(the_socket)
|
||||
the_uuid = the_socket.instance_variable_get(:@uuid)
|
||||
the_session = the_socket.instance_variable_get(:@session)
|
||||
the_display = the_socket.instance_variable_get(:@display)
|
||||
#
|
||||
if the_display
|
||||
the_manifest = GxG::SERVICES[:www][:manifests][(the_session)]
|
||||
if the_manifest
|
||||
if the_manifest.display_exist?(the_display)
|
||||
the_manifest.display_set(the_display, :available)
|
||||
unless the_manifest.connector_get(the_display).remote_closed?
|
||||
the_manifest.connector_get(the_display).close_remote
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
#
|
||||
GxGwww::SOCKETS_SAFETY.synchronize {
|
||||
GxGwww::SOCKETS.delete(the_uuid)
|
||||
}
|
||||
puts "Closing Socket #{the_uuid} & Display #{the_display}"
|
||||
true
|
||||
end
|
||||
#
|
||||
def on_shutdown()
|
||||
puts "Shutdown Received ..."
|
||||
end
|
||||
#
|
||||
end
|
||||
# Load GxGwww:
|
||||
require (File.expand_path("./www/gxg-www.rb",File.dirname(__FILE__)))
|
||||
www_service[:manifests] = {}
|
||||
www_service[:receiver] = GxGwww::Responder.new
|
||||
# ### Define Public Command Interface:
|
||||
www_service.on(:start, {:description => "WWW Service Layer Start", :usage => "{ :start => nil }"}) do
|
||||
::GxG::SERVICES[:www].start
|
||||
end
|
||||
www_service.on(:stop, {:description => "WWW Service Layer Stop", :usage => "{ :stop => nil }"}) do
|
||||
::GxG::SERVICES[:www].stop
|
||||
end
|
||||
www_service.on(:restart, {:description => "WWW Service Layer Restart", :usage => "{ :restart => nil }"}) do
|
||||
::GxG::SERVICES[:www].restart
|
||||
end
|
||||
www_service.on(:pause, {:description => "WWW Service Pause", :usage => "{ :pause => nil }"}) do
|
||||
::GxG::SERVICES[:www].pause
|
||||
end
|
||||
www_service.on(:resume, {:description => "WWW Service Resume", :usage => "{ :resume => nil }"}) do
|
||||
::GxG::SERVICES[:www].resume
|
||||
end
|
||||
# ### Define Internal Service Control Events:
|
||||
www_service.on(:at_start, {:description => "WWW Startup", :usage => "{ :at_start => (service-object) }"}) do |service, credential|
|
||||
#
|
||||
# TODO : increment number of threads used in GxG::Engine
|
||||
Thread.new { Node0.run! }
|
||||
until Node0.running_server do
|
||||
sleep 1.0
|
||||
end
|
||||
(service.configuration[:listen] || []).each_with_index do |the_node, indexer|
|
||||
if indexer > 0
|
||||
Node0.running_server().listen(the_node[:address],(the_node[:port] || 32767))
|
||||
sleep 1.0
|
||||
log_info("Also listening on #{the_node[:address]}:#{(the_node[:port] || 32767)} ...")
|
||||
end
|
||||
end
|
||||
# Mount WebSocket Listener
|
||||
Node0::running_server.mount_websocket((GxG::SERVICES[:www].configuration[:relative_url].to_s + "/ws"), WebSocketListener.new, "gxg")
|
||||
# Load WebDAV Processing:
|
||||
# Access files and objects via: GxG::SERVICES[:core][:resources]
|
||||
require (File.expand_path("./www/protocols/dav/dav.rb",File.dirname(__FILE__)))
|
||||
#
|
||||
{:result => true}
|
||||
end
|
||||
www_service.on(:at_stop, {:description => "WWW Stop", :usage => "{ :at_stop => (service-object) }", :public => false}) do |service|
|
||||
#
|
||||
Node0.stop!
|
||||
#
|
||||
{:result => true}
|
||||
end
|
||||
# ### Service Installation
|
||||
unless ::GxG::Services::service_available?(:www)
|
||||
# Set Configuration Defaults
|
||||
www_service.configuration[:mode] = "production"
|
||||
www_service.configuration[:listen] = [{:address => "127.0.0.1", :port => 32767}]
|
||||
www_service.configuration[:relative_url] = ""
|
||||
www_service.configuration[:cache_quota] = 1073741824
|
||||
www_service.configuration[:cache_max_item_size] = 1073741824
|
||||
www_service.save_configuration
|
||||
::GxG::Services::install_service(:www)
|
||||
::GxG::Services::enable_service(:www)
|
||||
end
|
||||
#
|
||||
#
|
||||
2112
Services/www/www/gxg-www.rb
Normal file
2112
Services/www/www/gxg-www.rb
Normal file
File diff suppressed because it is too large
Load diff
4
Services/www/www/protocols/dav/dav.rb
Normal file
4
Services/www/www/protocols/dav/dav.rb
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
require 'nokogiri'
|
||||
# ### WebDAV Supports
|
||||
# TODO : DAV-verb based processing
|
||||
#
|
||||
Loading…
Reference in a new issue