stockor 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (148) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +2 -4
  3. data/Gemfile.lock +104 -87
  4. data/client/skr/api.js +3 -1
  5. data/client/skr/api/Components/AddressForm.cjsx +1 -1
  6. data/client/skr/api/Components/SingleItemCart.cjsx +1 -1
  7. data/client/skr/api/SingleItemCheckout.cjsx +3 -3
  8. data/client/skr/components/BankAccountFinder.cjsx +1 -1
  9. data/client/skr/components/CreditCardForm.cjsx +1 -1
  10. data/client/skr/components/Currency.cjsx +2 -1
  11. data/client/skr/components/CustomerFinder.cjsx +1 -1
  12. data/client/skr/components/CustomerProjectFinder.cjsx +1 -1
  13. data/client/skr/components/ExpenseCategoryFinder.cjsx +34 -0
  14. data/client/skr/components/GlAccountChooser.cjsx +2 -1
  15. data/client/skr/components/InvoiceFinder.cjsx +1 -1
  16. data/client/skr/components/LatexSnippets.cjsx +3 -0
  17. data/client/skr/components/LocationChooser.cjsx +3 -3
  18. data/client/skr/components/PaymentCategoryFinder.cjsx +1 -1
  19. data/client/skr/components/SalesOrderFinder.cjsx +1 -2
  20. data/client/skr/components/SkuFinder.cjsx +1 -1
  21. data/client/skr/components/SystemSettings.cjsx +4 -1
  22. data/client/skr/components/TermsChooser.cjsx +2 -2
  23. data/client/skr/components/ToolbarButton.cjsx +7 -3
  24. data/client/skr/components/TotalsLine.cjsx +5 -1
  25. data/client/skr/components/UserPreferences.cjsx +1 -1
  26. data/client/skr/components/VendorFinder.cjsx +1 -1
  27. data/client/skr/components/address/Address.cjsx +14 -1
  28. data/client/skr/components/styles.scss +6 -0
  29. data/client/skr/index.js +1 -0
  30. data/client/skr/lib/Remote.coffee +32 -0
  31. data/client/skr/lib/index.js +2 -0
  32. data/client/skr/lib/namespace.coffee +1 -0
  33. data/client/skr/models/CreditCardGateway.coffee +1 -1
  34. data/client/skr/models/ExpenseCategory.coffee +11 -0
  35. data/client/skr/models/ExpenseEntry.coffee +64 -0
  36. data/client/skr/models/ExpenseEntryCategory.coffee +11 -0
  37. data/client/skr/models/Invoice.coffee +4 -1
  38. data/client/skr/models/Payment.coffee +2 -2
  39. data/client/skr/models/SalesOrder.coffee +3 -2
  40. data/client/skr/models/SequentialId.coffee +1 -1
  41. data/client/skr/models/Vendor.coffee +1 -1
  42. data/client/skr/models/mixins/PrintSupport.coffee +1 -1
  43. data/client/skr/screens/bank-maint/BankMaint.cjsx +1 -1
  44. data/client/skr/screens/chart-of-accounts/ChartOfAccounts.cjsx +1 -1
  45. data/client/skr/screens/customer-maint/CustomerMaint.cjsx +1 -1
  46. data/client/skr/screens/customer-projects/CustomerProjects.cjsx +1 -1
  47. data/client/skr/screens/expense-categories/ExpenseCategories.cjsx +31 -0
  48. data/client/skr/screens/expense-categories/index.js +5 -0
  49. data/client/skr/screens/expense-categories/index.scss +9 -0
  50. data/client/skr/screens/expense-entry/ApprovalRequest.coffee +14 -0
  51. data/client/skr/screens/expense-entry/ApproveDialog.cjsx +30 -0
  52. data/client/skr/screens/expense-entry/Categories.cjsx +112 -0
  53. data/client/skr/screens/expense-entry/ExpenseEntry.cjsx +193 -0
  54. data/client/skr/screens/expense-entry/Form.cjsx +54 -0
  55. data/client/skr/screens/expense-entry/Grid.cjsx +51 -0
  56. data/client/skr/screens/expense-entry/ScreenState.coffee +1 -0
  57. data/client/skr/screens/expense-entry/index.js +1 -0
  58. data/client/skr/screens/expense-entry/index.scss +106 -0
  59. data/client/skr/screens/fresh-books-import/ApiInfo.cjsx +3 -3
  60. data/client/skr/screens/fresh-books-import/ChooseRecords.cjsx +6 -4
  61. data/client/skr/screens/fresh-books-import/FreshBooksImport.cjsx +1 -1
  62. data/client/skr/screens/fresh-books-import/ViewRecords.cjsx +2 -2
  63. data/client/skr/screens/fresh-books-import/index.scss +5 -4
  64. data/client/skr/screens/gl-accounts/GlAccounts.cjsx +1 -1
  65. data/client/skr/screens/gl-transactions/GlTransactions.cjsx +1 -1
  66. data/client/skr/screens/invoice/Invoice.cjsx +6 -2
  67. data/client/skr/screens/invoice/Payment.cjsx +1 -1
  68. data/client/skr/screens/invoice/TotalExtraInfo.cjsx +32 -0
  69. data/client/skr/screens/invoice/index.scss +4 -2
  70. data/client/skr/screens/locations/Locations.cjsx +1 -1
  71. data/client/skr/screens/payment-category/PaymentCategory.cjsx +1 -1
  72. data/client/skr/screens/payment-terms/PaymentTerms.cjsx +1 -1
  73. data/client/skr/screens/payments/Payments.cjsx +13 -6
  74. data/client/skr/screens/sale-report/SaleReport.cjsx +1 -1
  75. data/client/skr/screens/sales-order/SalesOrder.cjsx +2 -1
  76. data/client/skr/screens/sku-maint/SkuMaint.cjsx +1 -1
  77. data/client/skr/screens/sku-maint/SkuUomList.cjsx +2 -2
  78. data/client/skr/screens/time-invoicing/TimeInvoicing.cjsx +4 -4
  79. data/client/skr/screens/time-tracking/EditEntry.cjsx +2 -2
  80. data/client/skr/screens/time-tracking/Entries.coffee +6 -4
  81. data/client/skr/screens/time-tracking/Popover.cjsx +11 -6
  82. data/client/skr/screens/time-tracking/TimeTracking.cjsx +12 -5
  83. data/client/skr/screens/vendor-maint/VendorMaint.cjsx +2 -2
  84. data/config/database.yml +2 -0
  85. data/config/puma.rb +0 -4
  86. data/config/routes.rb +4 -0
  87. data/config/screens.rb +17 -0
  88. data/db/migrate/20140323001446_create_so_details_view.rb +2 -2
  89. data/db/migrate/20151121211323_create_customer_project_details_views.rb +1 -1
  90. data/db/migrate/20160216142845_create_gl_account_balances_view.rb +1 -1
  91. data/db/migrate/20160726004411_string_check_numbers.rb +5 -0
  92. data/db/migrate/20160805002717_create_expenses.rb +65 -0
  93. data/db/schema.sql +394 -229
  94. data/db/seed.rb +12 -12
  95. data/lib/skr/access_roles.rb +9 -4
  96. data/lib/skr/concerns/has_gl_transaction.rb +1 -1
  97. data/lib/skr/configuration.rb +3 -1
  98. data/lib/skr/db/migration_helpers.rb +2 -2
  99. data/lib/skr/handlers/approve_expense_entries.rb +16 -0
  100. data/lib/skr/handlers/invoice_from_time_entries.rb +1 -1
  101. data/lib/skr/handlers/sequential_ids.rb +2 -2
  102. data/lib/skr/jobs/fresh_books/import.rb +18 -17
  103. data/lib/skr/model.rb +3 -0
  104. data/lib/skr/models/bank_account.rb +9 -0
  105. data/lib/skr/models/expense_category.rb +27 -0
  106. data/lib/skr/models/expense_entry.rb +76 -0
  107. data/lib/skr/models/expense_entry_category.rb +15 -0
  108. data/lib/skr/models/gl_transaction.rb +2 -2
  109. data/lib/skr/models/inv_line.rb +3 -1
  110. data/lib/skr/models/invoice.rb +9 -5
  111. data/lib/skr/models/payment.rb +1 -1
  112. data/lib/skr/models/purchase_order.rb +4 -4
  113. data/lib/skr/models/uom.rb +2 -2
  114. data/lib/skr/models/voucher.rb +4 -4
  115. data/lib/skr/print/form.rb +17 -14
  116. data/lib/skr/version.rb +1 -1
  117. data/spec/fixtures/skr/expense_category.yml +17 -0
  118. data/spec/fixtures/skr/expense_entries.yml +20 -0
  119. data/spec/fixtures/skr/expense_entry_category.yml +19 -0
  120. data/spec/fixtures/skr/gl_account.yml +10 -0
  121. data/spec/server/handlers/invoice_from_time_entries_spec.rb +2 -2
  122. data/spec/server/handlers/sequential_ids_spec.rb +1 -1
  123. data/spec/server/models/expense_category_spec.rb +17 -0
  124. data/spec/server/models/expense_entry_spec.rb +44 -0
  125. data/spec/server/models/invoice_spec.rb +0 -1
  126. data/spec/server/models/spec_helper_spec.rb +5 -10
  127. data/spec/server/spec_helper.rb +12 -11
  128. data/spec/skr/components/SkuLinesSpec.coffee +11 -9
  129. data/spec/skr/models/CustomerSpec.coffee +1 -1
  130. data/spec/skr/models/ExpenseAccountSpec.coffee +5 -0
  131. data/spec/skr/screens/expense-categories/ExpenseCategoriesSpec.coffee +5 -0
  132. data/spec/skr/screens/expense-entry/ExpenseEntrySpec.coffee +5 -0
  133. data/spec/skr/screens/locations/LocationsSpec.coffee +4 -4
  134. data/spec/skr/screens/time-tracking/TimeTrackingSpec.coffee +4 -4
  135. data/stockor.gemspec +2 -3
  136. data/templates/print/partials/header.tex.erb +1 -1
  137. data/templates/print/partials/skus_table_invoice_footer.tex.erb +22 -19
  138. data/templates/print/partials/skus_table_invoice_info_line.tex.erb +5 -1
  139. data/templates/print/types/payment/default.tex.erb +10 -10
  140. metadata +40 -14
  141. data/client/skr/api/onReady.coffee +0 -23
  142. data/spec/skr/api/SingleItemCheckoutSpec.cjsx +0 -10
  143. data/spec/skr/screens/bank-maint/BankMaintSpec.coffee +0 -5
  144. data/spec/skr/screens/gl-accounts/GlAccountsSpec.coffee +0 -5
  145. data/spec/skr/screens/invoice/InvoiceSpec.coffee +0 -5
  146. data/spec/skr/screens/payment-category/PaymentCategorySpec.coffee +0 -5
  147. data/spec/skr/screens/payments/PaymentsSpec.coffee +0 -5
  148. data/spec/skr/screens/sale-report/SaleReportSpec.coffee +0 -5
@@ -14,7 +14,7 @@ class Skr.Models.Payment extends Skr.Models.Base
14
14
  amount: {type:"bigdec", required: true, default: ->
15
15
  _.bigDecimal('0')
16
16
  }
17
- check_number: {type:"integer"}
17
+ check_number: {type:"string"}
18
18
  date: {type:"date", default: ->
19
19
  new Date
20
20
  }
@@ -51,6 +51,6 @@ class Skr.Models.Payment extends Skr.Models.Base
51
51
 
52
52
  onSetVendor: ->
53
53
  return unless @vendor and @vendor.isPersistent()
54
- @name = @vendor.name
54
+ @name = @vendor.billing_address.name
55
55
  @vendor.withAssociations(['billing_address']).then (v) =>
56
56
  @address = v.billing_address.toString()
@@ -27,7 +27,10 @@ class Skr.Models.SalesOrder extends Skr.Models.Base
27
27
  # optional attributes from details view
28
28
  session:
29
29
  customer_code: {type:"string"}
30
+ customer_name: {type:"string"}
31
+ bill_addr_name: {type:"string"}
30
32
  order_total: {type:"bigdec"}
33
+ num_lines: {type:"integer"}
31
34
 
32
35
  derived:
33
36
  total: deps: ['order_total'], fn: ->
@@ -65,11 +68,9 @@ class Skr.Models.SalesOrder extends Skr.Models.Base
65
68
 
66
69
  onCustomerChange: ->
67
70
  return if @customer.isProxy or not @isNew()
68
-
69
71
  associations = ['billing_address', 'shipping_address']
70
72
  for attr in ['terms_id']
71
73
  @set(attr, @customer[attr])
72
-
73
74
  @customer.withAssociations(associations).then =>
74
75
  for name in associations
75
76
  @[name].set(
@@ -5,7 +5,7 @@ class Skr.Models.SequentialId extends Skr.Models.Base
5
5
  ids:
6
6
  type: 'array', required: true, default: -> []
7
7
 
8
- registerforPubSub: false
8
+ registerForPubSub: false
9
9
 
10
10
  modelTypeIdentifier: ->
11
11
  'sequential-ids'
@@ -22,7 +22,7 @@ class Skr.Models.Vendor extends Skr.Models.Base
22
22
  shipping_address: { model: "Address" }
23
23
  terms: { model: "PaymentTerm", required: true }
24
24
  gl_payables_account: { model: "GlAccount", required: true }
25
- gl_freight_account: { model: "GlAccount", required: true }
25
+ gl_freight_account: { model: "GlAccount" }
26
26
  purchase_orders: { collection: "PurchaseOrder" }
27
27
  vouchers: { collection: "Voucher" }
28
28
  vendor_skus: { collection: "SkuVendor" }
@@ -4,6 +4,6 @@ Skr.Models.Mixins.PrintSupport = {
4
4
  if @printFormIdentifier then @printFormIdentifier() else @modelTypeIdentifier()
5
5
 
6
6
  pdfDownloadUrl: ->
7
- "#{Lanes.config.api_path}/skr/print/#{@_mixinPrintFormIdentifier()}/#{@hash_code}.pdf"
7
+ "//#{Lanes.config.api_host}#{Lanes.config.api_path}/skr/print/#{@_mixinPrintFormIdentifier()}/#{@hash_code}.pdf"
8
8
 
9
9
  }
@@ -2,7 +2,7 @@ class Skr.Screens.BankMaint extends Skr.Screens.Base
2
2
  syncOptions:
3
3
  include: [ 'address' ]
4
4
 
5
- dataObjects:
5
+ modelBindings:
6
6
  bank: ->
7
7
  @loadOrCreateModel({
8
8
  syncOptions: @syncOptions, klass: Skr.Models.BankAccount
@@ -1,7 +1,7 @@
1
1
  class Skr.Screens.ChartOfAccounts extends Skr.Screens.Base
2
2
 
3
3
  modelForAccess: 'gl-transaction'
4
- dataObjects:
4
+ modelBindings:
5
5
  query: ->
6
6
  new Lanes.Models.Query
7
7
  title: 'Lines', src: Skr.Models.GlAccount
@@ -1,7 +1,7 @@
1
1
  class Skr.Screens.CustomerMaint extends Skr.Screens.Base
2
2
 
3
3
  syncOptions: {include: ['billing_address', 'shipping_address']}
4
- dataObjects:
4
+ modelBindings:
5
5
  customer: ->
6
6
  @loadOrCreateModel({
7
7
  syncOptions: @syncOptions, klass: Skr.Models.Customer,
@@ -3,7 +3,7 @@ class Skr.Screens.CustomerProjects extends Skr.Screens.Base
3
3
  getInitialState: ->
4
4
  commands: new Skr.Screens.Commands(this, modelName: 'project')
5
5
 
6
- dataObjects:
6
+ modelBindings:
7
7
  project: ->
8
8
  @loadOrCreateModel({
9
9
  klass: Skr.Models.CustomerProject,
@@ -0,0 +1,31 @@
1
+ class Skr.Screens.ExpenseCategories extends Skr.Screens.Base
2
+
3
+ modelBindings:
4
+ category: ->
5
+ @loadOrCreateModel({
6
+ klass: Skr.Models.ExpenseCategory
7
+ prop: 'category', attribute: 'code'
8
+ })
9
+
10
+ getInitialState: ->
11
+ commands: new Skr.Screens.Commands(this, modelName: 'category')
12
+
13
+ render: ->
14
+ <LC.ScreenWrapper identifier="expense-categories">
15
+ <SC.ScreenControls commands={@state.commands} />
16
+
17
+ <BS.Row>
18
+ <SC.ExpenseCategoryFinder sm=2 autoFocus editOnly
19
+ commands={@state.commands} model={@category}
20
+ />
21
+
22
+ <LC.Input sm=9 name='name' model={@category} />
23
+ </BS.Row>
24
+ <BS.Row>
25
+ <SC.GlAccountChooser sm=3 label="GL Account"
26
+ name="gl_account" model={@category} />
27
+
28
+ <LC.ToggleField model={@category} name='is_active' sm=2 />
29
+ </BS.Row>
30
+
31
+ </LC.ScreenWrapper>
@@ -0,0 +1,5 @@
1
+ //= require_tree .
2
+
3
+ /*
4
+ Add additional requirement statements for other files that the screen uses
5
+ */
@@ -0,0 +1,9 @@
1
+ /*
2
+ You should remove this file if your screen does not use any custom styles
3
+
4
+ Otherwise Lanes will continue to fetch an empty file whenever
5
+ the screen is displayed
6
+ */
7
+ .expense-categories {
8
+
9
+ }
@@ -0,0 +1,14 @@
1
+ class Skr.Screens.ExpenseEntry.ApprovalRequest extends Lanes.Models.Base
2
+
3
+ props:
4
+ bank_account_id:{"type":"integer"}
5
+ expense_ids: { type: 'array', default: -> [] }
6
+
7
+ api_path: ->
8
+ '/skr/expense-entries/approve'
9
+
10
+ associations:
11
+ bank_account: { model: "BankAccount" }
12
+
13
+
14
+ isValid: -> !!@bank_account_id
@@ -0,0 +1,30 @@
1
+ class Skr.Screens.ExpenseEntry.Approve extends Lanes.React.Component
2
+
3
+ modelBindings:
4
+ approvalRequest: ->
5
+ new Skr.Screens.ExpenseEntry.ApprovalRequest
6
+
7
+ query: 'props'
8
+
9
+ approveSelected: ->
10
+ @approvalRequest.expense_ids = []
11
+ @query.results.eachRow (row, xd) =>
12
+ if LC.Grid.Selections.isSelected(row, xd)
13
+ @approvalRequest.expense_ids.push(@query.results.idForRow(row))
14
+ @approvalRequest.save().then @props.onApproved
15
+
16
+ render: ->
17
+ props = _.omit(@props, 'query', 'onApproved')
18
+
19
+ <BS.Popover
20
+ {...props}
21
+ id="choose-bank-account" title="Choose bank account">
22
+ <SC.BankAccountFinder
23
+ selectField fieldOnly editOnly
24
+ model={@approvalRequest} name="bank_account"
25
+ />
26
+ <BS.Button
27
+ disabled={not @approvalRequest.isValid()}
28
+ bsStyle="success" onClick={@approveSelected}
29
+ >Approve</BS.Button>
30
+ </BS.Popover>
@@ -0,0 +1,112 @@
1
+ class TextCategory extends Lanes.React.Component
2
+ modelBindings:
3
+ category: 'props'
4
+
5
+ render: ->
6
+ code = @props.categories.get(@category.category_id).code
7
+ <span className='pair'>
8
+ {Lanes.u.format.currency(@category.amount)} {code}
9
+ </span>
10
+
11
+
12
+ class Category extends Lanes.React.Component
13
+
14
+ modelBindings:
15
+ category: 'props'
16
+
17
+ delete: ->
18
+ @props.onEntryDestroy(@props.category)
19
+
20
+ render: ->
21
+ <div className="category">
22
+ <SC.ExpenseCategoryFinder selectField
23
+ choices={@props.categories.models} fetchOnSelect={false}
24
+ model={@category} name='category' />
25
+ <LC.NumberInput name='amount' model={@category} align="right" />
26
+ <BS.Button className="delete"
27
+ onClick={@delete}
28
+ >
29
+ <LC.Icon type="trash-o" lg />
30
+ </BS.Button>
31
+
32
+ </div>
33
+
34
+ class Skr.Screens.ExpenseEntry.Categories extends Lanes.React.Component
35
+
36
+ modelBindings:
37
+ entry: 'props'
38
+ categories: 'props'
39
+ entryCategories: ->
40
+ if @props.entry.categories.isEmpty()
41
+ @props.entry.categories.add({})
42
+ @props.entry.categories
43
+
44
+ addCategory: ->
45
+ @entry.categories.add({})
46
+ @setState(overlayShown: true)
47
+ _.defer =>
48
+ _.dom(@, '.popover-content .category:last-child .select input').focus()
49
+
50
+ onMultiClick: ->
51
+ @setState(overlayShown: true)
52
+
53
+ getPopOverTarget: ->
54
+ _.dom(@refs.overlayTarget).el
55
+
56
+ onEntryDestroy: (cat) ->
57
+ @setState(overlayShown: false)
58
+ @props.entry.categories.remove(cat)
59
+
60
+ renderMultiple: ->
61
+
62
+ entries = @entry.categories.map (category) =>
63
+ <Category key={category.cid} category={category}
64
+ onEntryDestroy={@onEntryDestroy}
65
+ categories={@props.categories} />
66
+ msg =
67
+ @entry.categories.map (expcat) =>
68
+ category = @categories.get( expcat.category_id )
69
+ return null unless category
70
+ <TextCategory key={expcat.cid} category={expcat} categories={@categories} />
71
+
72
+ <BS.Col sm=12 className="multiple">
73
+ <BS.FormGroup>
74
+ <BS.ControlLabel>Categories</BS.ControlLabel>
75
+ <div className='controls'>
76
+ <BS.FormControl.Static onClick={@onMultiClick}>{msg}</BS.FormControl.Static>
77
+ <LC.IconButton icon="edit" className="view" ref="overlayTarget"
78
+ onClick={=> @setState(overlayShown: true)}
79
+ />
80
+ <BS.Overlay
81
+ show={@state.overlayShown}
82
+ rootClose=true
83
+ onHide={=> @setState(overlayShown: false)}
84
+ placement="bottom"
85
+ container={@}
86
+ target={@getPopOverTarget}
87
+ >
88
+ <BS.Popover id="popover-positioned-bottom" title="Entries">
89
+ {entries}
90
+ </BS.Popover>
91
+ </BS.Overlay>
92
+ </div>
93
+ </BS.FormGroup>
94
+ </BS.Col>
95
+
96
+ render: ->
97
+
98
+ body = switch @entry.categories.length
99
+ when 1
100
+ <Category category={@entry.categories.first()}
101
+ categories={@props.categories} />
102
+ else
103
+ @renderMultiple()
104
+
105
+ props = _.without( @props, _.keys(@constructor.propTypes) )
106
+
107
+ <BS.Col {...props} className="categories">
108
+ {body}
109
+ <BS.FormGroup className='add'>
110
+ <LC.IconButton icon="plus" onClick={@addCategory} />
111
+ </BS.FormGroup>
112
+ </BS.Col>
@@ -0,0 +1,193 @@
1
+ ##= require_self
2
+ ##= require ./Form
3
+ ##= require ./Grid
4
+ ##= require ./Categories
5
+ ##= require ./ApprovalRequest
6
+ ##= require ./ApproveDialog
7
+
8
+ isApproved = (row) ->
9
+ !!row[2]
10
+
11
+ getCategoryVal = (type, fields, q) ->
12
+ list = fields[1]
13
+ if _.isArray(q.syncOptions.with)
14
+ _.reduce(list, (amt, cat) ->
15
+ amt + cat[type]
16
+ , 0)
17
+ else
18
+ category_id = q.syncOptions.with.with_category_details
19
+ cat = _.find(list , (f) ->
20
+ f.category_id = category_id
21
+ )
22
+ cat?[type] || 0.0
23
+
24
+ BALANCE_FIELD = {
25
+ id: 'balance', title: 'Balance',
26
+ fixedWidth: 100, textAlign: 'right', query: false,
27
+ format: (e, fields, q) ->
28
+ Lanes.u.format.currency getCategoryVal('balance', fields, q)
29
+ }
30
+
31
+ class Skr.Screens.ExpenseEntry extends Skr.Screens.Base
32
+
33
+ getInitialState: ->
34
+ isReviewing: false
35
+
36
+ syncOptions:
37
+ include: ['categories', 'attachments' ]
38
+
39
+ modelBindings:
40
+ categories: ->
41
+ Skr.Models.ExpenseCategory.Collection.fetch()
42
+
43
+ entry: ->
44
+ @loadOrCreateModel({
45
+ syncOptions: @syncOptions, klass: Skr.Models.ExpenseEntry
46
+ prop: 'entry', attribute: 'id'
47
+ })
48
+
49
+ query: ->
50
+ new Lanes.Models.Query({
51
+ autoRetrieve: true, sortAscending: false,
52
+ src: Skr.Models.ExpenseEntry,
53
+ syncOptions: _.extend({}, @syncOptions,
54
+ with: ['with_category_details']
55
+ ),
56
+ fields: [
57
+ { id: 'id', visible: false }
58
+ { id: 'category_list', visible: false }
59
+ { id: 'gl_transaction_ids', visible: false }
60
+ { id: 'occured', fixedWidth: 200, textAlign: 'center', format: Lanes.u.format.dateTime }
61
+ { id: 'name', flex: 1 }
62
+ {
63
+ id: 'amount', fixedWidth: 100, textAlign: 'right', query: false,
64
+ format: (e, fields, q) ->
65
+ Lanes.u.format.currency getCategoryVal('amount', fields, q)
66
+ }
67
+ ]
68
+ })
69
+
70
+ getSelectionAttributes: ({row}) ->
71
+ if isApproved(row) then {checked: true, disabled: true} else {}
72
+
73
+ setReviewState: (@isReviewing) ->
74
+ @setState({@isReviewing})
75
+ @query.syncOptions.with = ['with_category_details']
76
+ if @isReviewing
77
+ @query.syncOptions.queryParams = {review: true}
78
+ @query.syncOptions.with.push('with_user_logins')
79
+ @query.fields.add({ id: 'created_by_user.login', title: 'User', fixedWidth: 120 }, at: 3)
80
+ @gridSelections = new LC.Grid.Selections(
81
+ onChange: @rowSelectChanged, attributesGetter: @getSelectionAttributes
82
+ )
83
+ @query.fields.add(@gridSelections, at: 2)
84
+ else
85
+ delete @query.syncOptions.queryParams
86
+ delete @gridSelections
87
+ @query.fields.remove(['selected', 'created_by_user.login'])
88
+
89
+ @query.results.reload()
90
+
91
+ rowSelectChanged: ->
92
+ @forceUpdate()
93
+
94
+ onEntrySelect: (entry, index) ->
95
+ entry.fetch(@syncOptions).then (e) =>
96
+ @modelBindings.reset({entry})
97
+ @query.markModified()
98
+ @refs.form.focus()
99
+
100
+ onEntryReset: ->
101
+ entry = new Skr.Models.ExpenseEntry
102
+ entry.categories.add({})
103
+ @modelBindings.reset({entry})
104
+ @refs.form.focus()
105
+
106
+ onCategoryChange: ->
107
+ @query.results.reload()
108
+
109
+ onEntrySave: ->
110
+ isNew = @entry.isNew()
111
+ isCategoriesChanged = @entry.categories.isDirty()
112
+ @entry.save().then =>
113
+ return if @entry.hasErrors
114
+ if isNew or isCategoriesChanged
115
+ @query.results.reload()
116
+ else
117
+ @query.results.saveModelChanges(@entry)
118
+ @onEntryReset()
119
+
120
+ onConfirmationToggle: (selected) ->
121
+ @query.results.eachRow (row, xd) ->
122
+ LC.Grid.Selections.setRow(row, xd, selected)
123
+ @query.markModified()
124
+
125
+ onApproved: ->
126
+ @refs.overlay.hide()
127
+ @query.results.reload()
128
+
129
+ selectedState: ->
130
+ none = all = true
131
+ @query.results.eachRow (row, xd) ->
132
+ if LC.Grid.Selections.isSelected(row, xd) and not isApproved(row)
133
+ none = false
134
+ else
135
+ all = false
136
+ if none is false and all is false
137
+ return 'break'
138
+ if all then true else if none then false else null
139
+
140
+ onCategorySelect: (category) ->
141
+ if category
142
+ @query.syncOptions.with = { with_category_details: category.id }
143
+ @query.fields.add(BALANCE_FIELD)
144
+ else
145
+ @query.fields.remove(BALANCE_FIELD.id)
146
+ category = new Skr.Models.ExpenseCategory
147
+ @query.syncOptions.with = [ 'with_category_details' ]
148
+ @setState({category})
149
+ @query.results.reload()
150
+
151
+ isRowEditing: (row) ->
152
+ @entry.id is row[0]
153
+
154
+ render: ->
155
+ selectionState = @selectedState()
156
+ <LC.ScreenWrapper flexVertical
157
+ identifier="expense-entry"
158
+ className={_.classnames('is-reviewing': @isReviewing)}
159
+ >
160
+
161
+ <Skr.Screens.ExpenseEntry.Form
162
+ ref="form"
163
+ categories={@categories}
164
+ entry={@entry}
165
+ onEntryReset={@onEntryReset}
166
+ onEntrySave={@onEntrySave}
167
+ />
168
+
169
+ <Skr.Screens.ExpenseEntry.Grid
170
+ onCategorySelect={@onCategorySelect}
171
+ setReviewState={@setReviewState}
172
+ isReviewing={@isReviewing}
173
+ isRowFocused={@isRowEditing}
174
+ isConfirmationChecked={selectionState}
175
+ categories={@categories}
176
+ onRecordSelect={@onEntrySelect}
177
+ onConfirmationToggle={@onConfirmationToggle}
178
+ query={@query}
179
+ />
180
+
181
+ <BS.Row className='footer'>
182
+ <BS.Col sm=12>
183
+ <BS.OverlayTrigger
184
+ rootClose={true} container={@} trigger="click" ref="overlay" placement="top"
185
+ overlay={<Skr.Screens.ExpenseEntry.Approve
186
+ query={@query} onApproved={@onApproved}/>}
187
+ >
188
+ <BS.Button bsStyle="primary" disabled={selectionState is false}>Approve</BS.Button>
189
+ </BS.OverlayTrigger>
190
+ </BS.Col>
191
+ </BS.Row>
192
+
193
+ </LC.ScreenWrapper>