stockor 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (142) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -0
  3. data/.rubocop.yml +15 -0
  4. data/Gemfile +3 -1
  5. data/Gemfile.lock +97 -193
  6. data/client/skr/Extension.coffee +8 -4
  7. data/client/skr/api.js +21 -0
  8. data/client/skr/api.scss +148 -0
  9. data/client/skr/api/Components/AddressForm.cjsx +47 -0
  10. data/client/skr/api/Components/Base.coffee +7 -0
  11. data/client/skr/api/Components/SaleHistory.cjsx +32 -0
  12. data/client/skr/api/Components/SingleItemCart.cjsx +30 -0
  13. data/client/skr/api/Models/Base.coffee +10 -0
  14. data/client/skr/api/Models/Cart.coffee +40 -0
  15. data/client/skr/api/Models/Sale.coffee +43 -0
  16. data/client/skr/api/Models/SalesHistory.coffee +17 -0
  17. data/client/skr/api/Models/Sku.coffee +16 -0
  18. data/client/skr/api/SingleItemCheckout.cjsx +99 -0
  19. data/client/skr/api/namespace.js +6 -0
  20. data/client/skr/api/onReady.coffee +23 -0
  21. data/client/skr/components/CreditCardForm.cjsx +35 -0
  22. data/client/skr/components/CustomerFinder.cjsx +0 -3
  23. data/client/skr/components/InvoiceFinder.cjsx +1 -0
  24. data/client/skr/components/LatexSnippets.cjsx +65 -0
  25. data/client/skr/components/LocationChooser.cjsx +5 -1
  26. data/client/skr/components/PrintButton.cjsx +33 -0
  27. data/client/skr/components/PrintFormChooser.cjsx +10 -10
  28. data/client/skr/components/SalesOrderFinder.cjsx +2 -1
  29. data/client/skr/components/SystemSettings.cjsx +69 -9
  30. data/client/skr/components/address/index.js +1 -0
  31. data/client/skr/models/Address.coffee +3 -0
  32. data/client/skr/models/CreditCard.coffee +49 -0
  33. data/client/skr/models/CreditCardGateway.coffee +27 -0
  34. data/client/skr/models/Invoice.coffee +13 -5
  35. data/client/skr/models/Location.coffee +6 -4
  36. data/client/skr/models/Payment.coffee +33 -13
  37. data/client/skr/models/SalesOrder.coffee +7 -3
  38. data/client/skr/models/SequentialId.coffee +17 -0
  39. data/client/skr/models/Sku.coffee +1 -1
  40. data/client/skr/models/TimeEntry.coffee +3 -4
  41. data/client/skr/models/mixins/PrintSupport.coffee +4 -1
  42. data/client/skr/models/mixins/SkuLine.coffee +1 -1
  43. data/client/skr/screens/customer-maint/CustomerMaint.cjsx +1 -2
  44. data/client/skr/screens/customer-projects/CustomerProjects.cjsx +7 -3
  45. data/client/skr/screens/customer-projects/index.scss +1 -0
  46. data/client/skr/screens/fresh-books-import/ApiInfo.cjsx +1 -1
  47. data/client/skr/screens/gl-accounts/GlAccounts.cjsx +1 -1
  48. data/client/skr/screens/gl-transactions/GlTransactions.cjsx +1 -1
  49. data/client/skr/screens/invoice/Invoice.cjsx +9 -17
  50. data/client/skr/screens/invoice/Payment.cjsx +94 -13
  51. data/client/skr/screens/invoice/index.scss +32 -0
  52. data/client/skr/screens/locations/Locations.cjsx +3 -2
  53. data/client/skr/screens/payment-category/PaymentCategory.cjsx +1 -1
  54. data/client/skr/screens/payments/Payments.cjsx +5 -4
  55. data/client/skr/screens/sale-report/SaleReport.cjsx +73 -0
  56. data/client/skr/screens/sale-report/index.js +5 -0
  57. data/client/skr/screens/sale-report/index.scss +9 -0
  58. data/client/skr/screens/sales-order/SalesOrder.cjsx +4 -4
  59. data/client/skr/screens/sku-maint/SkuMaint.cjsx +5 -0
  60. data/client/skr/screens/sku-maint/SkuUomList.cjsx +45 -56
  61. data/client/skr/screens/sku-maint/index.scss +33 -13
  62. data/client/skr/screens/time-invoicing/TimeInvoicing.cjsx +2 -2
  63. data/client/skr/screens/time-tracking/EditEntry.cjsx +2 -4
  64. data/client/skr/screens/time-tracking/TimeTracking.cjsx +1 -1
  65. data/client/skr/screens/vendor-maint/VendorMaint.cjsx +1 -1
  66. data/client/skr/vendor/index.js +2438 -0
  67. data/config/lanes.rb +10 -3
  68. data/config/routes.rb +9 -7
  69. data/config/screens.rb +39 -13
  70. data/db/migrate/20160517032350_create_remove_location_logos.rb +11 -0
  71. data/db/migrate/20160531014306_create_public_skus.rb +7 -0
  72. data/db/migrate/20160604195848_create_null_addresses.rb +9 -0
  73. data/db/migrate/20160605024432_create_sku_associations_views.rb +48 -0
  74. data/db/migrate/20160608023553_create_invoice_payments.rb +9 -0
  75. data/db/migrate/20160620010455_add_payments_to_inv_details.rb +54 -0
  76. data/db/schema.sql +206 -38
  77. data/db/seed.rb +6 -0
  78. data/lib/skr.rb +1 -1
  79. data/lib/skr/access_roles.rb +6 -1
  80. data/lib/skr/concerns/has_gl_transaction.rb +8 -0
  81. data/lib/skr/concerns/inv_extensions.rb +11 -0
  82. data/lib/skr/concerns/is_order_like.rb +3 -3
  83. data/lib/skr/concerns/visible_id_identifier.rb +3 -8
  84. data/lib/skr/configuration.rb +2 -0
  85. data/lib/skr/extension.rb +6 -0
  86. data/lib/skr/handlers/credit_card_gateway.rb +31 -0
  87. data/lib/skr/handlers/invoice_from_time_entries.rb +12 -26
  88. data/lib/skr/handlers/sales.rb +44 -0
  89. data/lib/skr/handlers/sequential_ids.rb +46 -0
  90. data/lib/skr/handlers/skus.rb +36 -0
  91. data/lib/skr/jobs/fresh_books/import.rb +3 -1
  92. data/lib/skr/merchant_gateway.rb +37 -0
  93. data/lib/skr/models/address.rb +7 -5
  94. data/lib/skr/models/bank_account.rb +12 -0
  95. data/lib/skr/models/business_entity.rb +1 -1
  96. data/lib/skr/models/gl_transaction.rb +3 -0
  97. data/lib/skr/models/inv_line.rb +7 -2
  98. data/lib/skr/models/inventory_adjustment.rb +0 -5
  99. data/lib/skr/models/invoice.rb +25 -36
  100. data/lib/skr/models/location.rb +1 -1
  101. data/lib/skr/models/payment.rb +56 -10
  102. data/lib/skr/models/po_receipt.rb +0 -8
  103. data/lib/skr/models/sales_order.rb +7 -2
  104. data/lib/skr/models/sequential_id.rb +6 -1
  105. data/lib/skr/models/sku.rb +3 -0
  106. data/lib/skr/print.rb +1 -0
  107. data/lib/skr/print/context.rb +19 -0
  108. data/lib/skr/print/form.rb +7 -1
  109. data/lib/skr/version.rb +1 -1
  110. data/npm-build/package.json +26 -0
  111. data/npm-build/vendor.js +10 -0
  112. data/npm-build/webpack.config.js +25 -0
  113. data/spec/fixtures/skr/bank_account.yml +9 -0
  114. data/spec/fixtures/skr/customer.yml +10 -0
  115. data/spec/fixtures/skr/inv_line.yml +12 -0
  116. data/spec/fixtures/skr/invoice.yml +17 -0
  117. data/spec/fixtures/skr/payment_category.yml +6 -0
  118. data/spec/fixtures/skr/sales_order.yml +1 -1
  119. data/spec/fixtures/skr/sku.yml +10 -0
  120. data/spec/fixtures/skr/sku_loc.yml +4 -0
  121. data/spec/fixtures/skr/uom.yml +6 -0
  122. data/spec/server/handlers/invoice_from_time_entries_spec.rb +13 -6
  123. data/spec/server/handlers/sequential_ids_spec.rb +22 -0
  124. data/spec/server/models/invoice_spec.rb +36 -0
  125. data/spec/server/models/payment_spec.rb +50 -22
  126. data/spec/server/models/sales_order_spec.rb +12 -0
  127. data/spec/server/print/form_spec.rb +20 -4
  128. data/spec/skr/api/SingleItemCheckoutSpec.cjsx +10 -0
  129. data/spec/skr/screens/sale-report/SaleReportSpec.coffee +5 -0
  130. data/stockor.gemspec +5 -4
  131. data/templates/print/layout.tex.erb +1 -1
  132. data/templates/print/packages/INSTALL +7 -0
  133. data/templates/print/packages/qrcode.sty +3051 -0
  134. data/templates/print/packages/svgnam.def +186 -0
  135. data/templates/print/packages/x11nam.def +352 -0
  136. data/templates/print/packages/xcolor.pro +58 -0
  137. data/templates/print/packages/xcolor.sty +1460 -0
  138. data/templates/print/types/invoice/default.tex.erb +2 -0
  139. data/templates/print/types/invoice/labor.tex.erb +2 -1
  140. data/templates/print/types/invoice/ticket.tex.erb +41 -0
  141. data/templates/print/types/payment/default.tex.erb +13 -12
  142. metadata +78 -12
@@ -23,10 +23,14 @@ class Skr.Extension extends Lanes.Extensions.Base
23
23
  else
24
24
  console.log "Unable to find model for #{type}"
25
25
 
26
+ title: ->
27
+ "Stockor"
28
+
26
29
  rootComponent: (viewport) ->
27
30
  Lanes.Workspace.Layout
28
31
 
29
- preferenceElement: (props) ->
30
- React.createElement(SC.UserPreferences, key: 'skr')
31
- settingsElement: (props) ->
32
- React.createElement(SC.SystemSettings, key: 'skr')
32
+ getPreferenceElement: (props) ->
33
+ React.createElement(SC.UserPreferences, props)
34
+
35
+ getSettingsElement: (props) ->
36
+ React.createElement(SC.SystemSettings, props)
data/client/skr/api.js ADDED
@@ -0,0 +1,21 @@
1
+ //=require lanes/remote/api
2
+ //=require ./api/namespace
3
+ //=require skr/vendor
4
+ //=require ./api/Models/Base
5
+ //=require_tree ./models/mixins
6
+ //=require ../api/Components/Base
7
+ //=require ./api/Components/Base
8
+ //=require_tree ./api
9
+ //=require_self
10
+
11
+
12
+ var previousSkr = window.Skr;
13
+ window.Skr = Lanes.Skr;
14
+
15
+ window.Skr._lanes = window.Lanes.noConflict();
16
+
17
+ Skr.noConflict = function(){
18
+ var Skr = window.Skr;
19
+ window.Skr = previousSkr;
20
+ return Skr;
21
+ };
@@ -0,0 +1,148 @@
1
+ .skr-simple-checkout {
2
+ width: 100%;
3
+ max-width: 350px;
4
+ margin: 50px auto;
5
+ .section {
6
+ margin-top: 2rem;
7
+ padding-top: 2rem;
8
+ border-top: 2px solid grey;
9
+ }
10
+ form { }
11
+
12
+ input {
13
+ font-size: 18px;
14
+ padding: 5px;
15
+ }
16
+ .errors {
17
+ color: #D8000C;
18
+ background-color: #FFBABA;
19
+ border: 1px solid darken(#FFBABA, 50%);
20
+ }
21
+ .address {
22
+ label {
23
+ display: flex;
24
+ justify-content: flex-end;
25
+ span { margin-right: 1rem; }
26
+ input { width: 200px; }
27
+ }
28
+ }
29
+ .cart {
30
+ width: 100%;
31
+ td, th {
32
+ &:last-child { text-align: right; }
33
+ }
34
+ input {
35
+ width: 4rem;
36
+ text-align: center;
37
+ }
38
+ }
39
+ .credit-card {
40
+ .row {
41
+ display: flex;
42
+ align-items: stretch;
43
+ height: 30px;
44
+ margin: 10px auto;
45
+ }
46
+ input {
47
+ flex: 1;
48
+ width: 100%;
49
+ &[name='expiry'], &[name='cvc'] {
50
+ flex: 0.5;
51
+ }
52
+ &.jp-card-invalid {
53
+ border: 1px solid red;
54
+ }
55
+ }
56
+ }
57
+ button {
58
+ border: 0;
59
+ background: #3498db;
60
+ background-image: -webkit-linear-gradient(top, #3498db, #2980b9);
61
+ background-image: -moz-linear-gradient(top, #3498db, #2980b9);
62
+ background-image: -ms-linear-gradient(top, #3498db, #2980b9);
63
+ background-image: -o-linear-gradient(top, #3498db, #2980b9);
64
+ background-image: linear-gradient(to bottom, #3498db, #2980b9);
65
+ font-family: Arial;
66
+ color: #f0edf0;
67
+ font-size: 22px;
68
+ padding: 10px 20px 10px 20px;
69
+ text-decoration: none;
70
+ &:hover {
71
+ background: #3cb0fd;
72
+ background-image: -webkit-linear-gradient(top, #3cb0fd, #3498db);
73
+ background-image: -moz-linear-gradient(top, #3cb0fd, #3498db);
74
+ background-image: -ms-linear-gradient(top, #3cb0fd, #3498db);
75
+ background-image: -o-linear-gradient(top, #3cb0fd, #3498db);
76
+ background-image: linear-gradient(to bottom, #3cb0fd, #3498db);
77
+ text-decoration: none;
78
+ }
79
+ }
80
+ @mixin spinner($opacity, $size){
81
+ box-shadow: rgba(0, 0, 0, $opacity) $size*1.5 0 0 0,
82
+ rgba(0, 0, 0, $opacity) ($size*1.1) ($size*1.1) 0 0,
83
+ rgba(0, 0, 0, $opacity) 0 ($size*1.5) 0 0,
84
+ rgba(0, 0, 0, $opacity) (-$size*1.1) ($size*1.1) 0 0,
85
+ rgba(0, 0, 0, $opacity) (-$size*1.5) 0 0 0,
86
+ rgba(0, 0, 0, $opacity) (-$size*1.1) (-$size*1.1) 0 0,
87
+ rgba(0, 0, 0, $opacity) 0 (-$size*1.5) 0 0,
88
+ rgba(0, 0, 0, $opacity) $size*1.1 (-$size*1.1) 0 0;
89
+ }
90
+
91
+ $spinner-size: 10px;
92
+ .mask {
93
+ background-color: transparent;
94
+ display: none;
95
+ }
96
+ .order {
97
+ &.is-saving {
98
+ position: relative;
99
+ .mask {
100
+ position: absolute;
101
+ display: flex;
102
+ justify-content: center;
103
+ align-items: center;
104
+ top: 0;
105
+ left: 0;
106
+ bottom: 0;
107
+ right: 0;
108
+ font-size: 2.8rem;
109
+ text-shadow: none;
110
+ border: 0;
111
+ transition: all 0.8s ease-in-out;
112
+ background-color: rgba(221, 221, 221, 0.7);
113
+ z-index: 2;
114
+ .msg {
115
+ transition: all 1s ease-in-out;
116
+ color: rgba(0, 0, 0, 0.8);
117
+ span { margin-left: $spinner-size*4; }
118
+ i {
119
+ transition: all 1s ease-in-out;
120
+ display: block;
121
+ position: relative;
122
+ width: $spinner-size;
123
+ height: $spinner-size;
124
+ top: $spinner-size*4;
125
+ left: $spinner-size*1.5;
126
+ animation: spinner 3000ms infinite linear;
127
+ border-radius: $spinner-size / 2;
128
+ @include spinner(0.75, $spinner-size);
129
+ }
130
+ }
131
+ }
132
+ &.is-complete {
133
+ .mask {
134
+ background-color: rgba(221, 221, 221, 0);
135
+ .msg {
136
+ color: rgba(0, 0, 0, 0);
137
+ i { @include spinner(0.0, $spinner-size); }
138
+ }
139
+ }
140
+ }
141
+ }
142
+ @keyframes spinner {
143
+ 0% { transform: rotate(0deg); }
144
+ 100% { transform: rotate(360deg); }
145
+ }
146
+
147
+ }
148
+ }
@@ -0,0 +1,47 @@
1
+ class Skr.Api.Components.AddressForm extends Skr.Api.Components.Base
2
+
3
+ dataObjects:
4
+ address: 'props'
5
+
6
+ setField: (ev) ->
7
+ @address[ev.target.name] = ev.target.value
8
+
9
+ render: ->
10
+ fieldProps =
11
+ onChange: @setField
12
+ type: 'text'
13
+
14
+ <div className="address">
15
+ <label>
16
+ <span>Name:</span>
17
+ <input {...fieldProps} name="name" />
18
+ </label>
19
+ <label>
20
+ <span>Email:</span>
21
+ <input {...fieldProps} name="email" />
22
+ </label>
23
+ <label>
24
+ <span>Phone:</span>
25
+ <input {...fieldProps} name="phone" />
26
+ </label>
27
+ <label>
28
+ <span>Address line 1:</span>
29
+ <input {...fieldProps} name="line1" />
30
+ </label>
31
+ <label>
32
+ <span>Address line 2:</span>
33
+ <input {...fieldProps} name="line2" />
34
+ </label>
35
+ <label>
36
+ <span>City:</span>
37
+ <input {...fieldProps} name="city" />
38
+ </label>
39
+ <label>
40
+ <span>State:</span>
41
+ <input {...fieldProps} name="state" />
42
+ </label>
43
+ <label>
44
+ <span>Postal Code:</span>
45
+ <input {...fieldProps} name="postal_code" />
46
+ </label>
47
+ </div>
@@ -0,0 +1,7 @@
1
+ Skr.Api.Components.Base =
2
+
3
+ # Pass coponent onthrough to Lanes default
4
+ # Skr.Api.Components.Base is only here to provide
5
+ # an extension point that may be needed in the future
6
+ extend: (klass) ->
7
+ Lanes.React.Component.extend(klass)
@@ -0,0 +1,32 @@
1
+ class Skr.Api.Components.SaleHistory extends Skr.Api.Components.Base
2
+
3
+ componentWillMount: ->
4
+ @setState(orders: Skr.Api.Models.SalesHistory.get())
5
+
6
+ render: ->
7
+ return null if _.isEmpty( @state.orders )
8
+
9
+ <table className="cart">
10
+ <thead>
11
+ <tr>
12
+ <th colSpan=3>Previous Purchases</th>
13
+ </tr>
14
+ <tr>
15
+ <th>Sale Number</th>
16
+ <th>Date</th>
17
+ <th>Amount</th>
18
+ </tr>
19
+ </thead>
20
+ <tbody>
21
+ {for order in @state.orders
22
+ <tr>
23
+ <td>
24
+ <a target="_blank" href={order.pdfDownloadUrl()}>
25
+ {order.visible_id}
26
+ </a>
27
+ </td>
28
+ <td>{Lanes.u.format.shortDate(order.date)}</td>
29
+ <td>{Lanes.u.format.currency(order.total)}</td>
30
+ </tr>}
31
+ </tbody>
32
+ </table>
@@ -0,0 +1,30 @@
1
+ class Skr.Api.Components.SingleItemCart extends Skr.Api.Components.Base
2
+
3
+ dataObjects:
4
+ cart: 'props'
5
+
6
+ item: -> @cart.skus.first()
7
+
8
+ setQty: (ev) ->
9
+ @item().qty = ev.target.value
10
+
11
+ render: ->
12
+ item = @item()
13
+ return null unless item
14
+
15
+ <table className="cart">
16
+ <tbody>
17
+ <tr>
18
+ <td colSpan="4">{item.sku.description}</td>
19
+ <td>{item.sku.display_price}</td>
20
+ </tr>
21
+ <tr>
22
+ <th>Qty:</th>
23
+ <td>
24
+ <input type="number" value={item.qty} onChange={@setQty} />
25
+ </td>
26
+ <th>Total:</th>
27
+ <th colSpan="2">{item.display_total}</th>
28
+ </tr>
29
+ </tbody>
30
+ </table>
@@ -0,0 +1,10 @@
1
+ ##= require 'skr/models/Base'
2
+
3
+ class Skr.Api.Models.Base extends Skr.Models.Base
4
+
5
+ constructor: ->
6
+ super
7
+
8
+
9
+ api_path: ->
10
+ '/skr/public/' + _.pluralize(@modelTypeIdentifier())
@@ -0,0 +1,40 @@
1
+ class CartSku extends Lanes.Models.State
2
+
3
+ session:
4
+ sku: 'state'
5
+ qty: type: 'integer', default: 0, required: true
6
+
7
+ derived:
8
+ total: deps: ['qty', 'sku.price'], fn: ->
9
+ @sku.price.times(@qty)
10
+ display_total:
11
+ deps: ['total'], fn: ->
12
+ Lanes.u.format.currency @total
13
+
14
+ class Skr.Api.Models.Cart extends Skr.Api.Models.Base
15
+
16
+ session:
17
+ total:
18
+ type: 'bigdec', required: true, default: -> _.bigDecimal('0')
19
+
20
+ derived:
21
+ display_total:
22
+ deps: ['total'], fn: ->
23
+ Lanes.u.format.currency @total
24
+
25
+ associations:
26
+ skus: { collection: CartSku }
27
+
28
+ addSku: (sku, qty = 1) ->
29
+ ci = new CartSku({sku, qty})
30
+ @skus.add(ci)
31
+
32
+ initialize: ->
33
+ @listenToAndRun(@skus, 'add change', @onSkusChange)
34
+
35
+ onSkusChange: ->
36
+ @total = _.sumBy(@skus.models, 'total') unless @skus.isEmpty()
37
+
38
+ addBySkuCode: (code) ->
39
+ Skr.Api.Models.Sku.findByCode(code).then (sku) =>
40
+ @addSku(sku) unless sku.isNew()
@@ -0,0 +1,43 @@
1
+ ##= require 'skr/models/Address'
2
+ ##= require 'skr/models/CreditCard'
3
+
4
+ class SaleSku extends Lanes.Models.State
5
+
6
+ props:
7
+ sku_id: 'integer'
8
+ qty: 'integer'
9
+
10
+
11
+ class Skr.Api.Models.Sale extends Skr.Api.Models.Base
12
+
13
+
14
+ props:
15
+ address_id: 'integer'
16
+ id: 'integer'
17
+ visible_id: 'visible_id'
18
+ terms_id: 'integer'
19
+ customer_id: 'integer'
20
+ hash_code: 'string'
21
+ total: 'bigdec'
22
+ form: 'string'
23
+ options: 'object'
24
+
25
+ mixins: [ Lanes.Skr.Models.Mixins.PrintSupport ]
26
+
27
+ associations:
28
+ skus: { collection: SaleSku }
29
+ billing_address: { model: 'Address', autoCreate: true }
30
+ credit_card:
31
+ model: 'Skr.Models.CreditCard', autoCreate: true
32
+ options: ->
33
+ linkToAddress: @billing_address
34
+
35
+ printFormIdentifier: ->
36
+ 'invoice'
37
+
38
+ copySkusFromCart: (cart) ->
39
+ cart.skus.each (ci) =>
40
+ @skus.add(sku_id: ci.sku.id, qty: ci.qty)
41
+
42
+ save: ->
43
+ super(with: 'details')
@@ -0,0 +1,17 @@
1
+ KEY = 'SALES-HISTORY'
2
+
3
+ Skr.Api.Models.SalesHistory = {
4
+
5
+ _encode: (sale) ->
6
+ _.pick(sale, 'visible_id', 'hash_code', 'invoice_date', 'total')
7
+
8
+ record: (sale) ->
9
+ sales = @get()
10
+ sales.push(@_encode(sale))
11
+ localStorage.setItem(KEY, JSON.stringify({sales}) )
12
+
13
+ get: ->
14
+ sales = JSON.parse(localStorage.getItem(KEY))?.sales
15
+ _.map sales, (sale) -> new Skr.Api.Models.Sale(sale)
16
+
17
+ }
@@ -0,0 +1,16 @@
1
+ class Skr.Api.Models.Sku extends Skr.Api.Models.Base
2
+
3
+ props:
4
+ id: {type:"integer"}
5
+ code: {type:"code" }
6
+ description: {type:"string" }
7
+ price: {type:"bigdec" }
8
+ default_uom_code: {type:"string" }
9
+
10
+ derived:
11
+ display_price: deps: ['price'], fn: ->
12
+ Lanes.u.format.currency @price
13
+
14
+ @findByCode: (code) ->
15
+ sku = new Skr.Api.Models.Sku({code})
16
+ sku.fetch(query: {code})