caboose-cms 0.6.11 → 0.6.12
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +8 -8
- data/app/assets/javascripts/caboose/model/index_table.js +123 -37
- data/app/controllers/caboose/media_controller.rb +3 -2
- data/app/controllers/caboose/variants_controller.rb +29 -20
- data/app/models/caboose/#Untitled-1# +56 -0
- data/app/models/caboose/schema.rb +37 -32
- data/app/models/caboose/tax_calculator.rb +2 -1
- data/app/models/caboose/variant.rb +11 -2
- data/app/views/caboose/orders_mailer/customer_status_updated.html.erb +1 -0
- data/app/views/caboose/variants/admin_edit.html.erb +39 -25
- data/app/views/caboose/variants/admin_index.html.erb +33 -22
- data/lib/caboose.rb +3 -0
- data/lib/caboose/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
MmZmZjJjMjE2ZTgyZjc3ZjEwYjVkODMxNzA2ODY2NjBmY2M1NmU5ZA==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
YmUxZjQ5ZTc3YmJlZWRiZGE2MjY2OTFhY2I4Y2IxYzJhNzNiMjVlMw==
|
7
7
|
SHA512:
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
MTU4NTM4YTY2MDVkOWU1MTMzYzIwYWY1YzNmMDZmZDc2YzVhODdmMmM0Njgx
|
10
|
+
ZTAyZDg0Yzg4YmUxMmQ4MTVlZDI1YzQxNzM0NmRjMTQ2ODE0YzE3ZGIyMjJi
|
11
|
+
NjUyMzBhZjZkYTNhMTNiZTI4NDY1NzBjMDQ2MzkzMzVlZjUwMmY=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
OTEwNjAzN2YyMDgwNGI4YTkyZDk2M2RjYmVlZmQyOGUwYmExOTQ2ODk3MmRl
|
14
|
+
OGIwNjMxZTBiYzJjZDQzYzFmNTEyNTE3YjQzYzY4MDVlNmU4Y2RjYWU4YTE3
|
15
|
+
MTgxNDBhODQyNjQ0N2M5ZTM5ZDVkMWYwZGVmMjBlNTE0MGUxZWY=
|
@@ -40,7 +40,7 @@ IndexTable.prototype = {
|
|
40
40
|
// fixed_placeholder: false,
|
41
41
|
// width: 200,
|
42
42
|
// }]
|
43
|
-
fields: [],
|
43
|
+
fields: [],
|
44
44
|
|
45
45
|
//============================================================================
|
46
46
|
// Additional parameters
|
@@ -84,7 +84,9 @@ IndexTable.prototype = {
|
|
84
84
|
bulk_import_fields: false,
|
85
85
|
no_models_text: "There are no models right now.",
|
86
86
|
new_model_text: 'New',
|
87
|
-
new_model_fields: [{ name: 'name', nice_name: 'Name', type: 'text', width: 400 }],
|
87
|
+
new_model_fields: [{ name: 'name', nice_name: 'Name', type: 'text', width: 400 }],
|
88
|
+
search_fields: false,
|
89
|
+
custom_row_controls: false,
|
88
90
|
|
89
91
|
//============================================================================
|
90
92
|
// End of parameters
|
@@ -113,7 +115,7 @@ IndexTable.prototype = {
|
|
113
115
|
if (f.editable == null) f.editable = true;
|
114
116
|
});
|
115
117
|
this.init_local_storage();
|
116
|
-
this.get_visible_columns();
|
118
|
+
this.get_visible_columns();
|
117
119
|
|
118
120
|
$(window).on('hashchange', function() { that.refresh(); });
|
119
121
|
this.refresh();
|
@@ -123,8 +125,8 @@ IndexTable.prototype = {
|
|
123
125
|
{
|
124
126
|
var b = {};
|
125
127
|
|
126
|
-
// Get the
|
127
|
-
|
128
|
+
// Get the querystring values
|
129
|
+
a = window.location.search;
|
128
130
|
if (a.length > 0)
|
129
131
|
{
|
130
132
|
a = a.substr(1).split('&');
|
@@ -134,10 +136,21 @@ IndexTable.prototype = {
|
|
134
136
|
if (p.length == 1) b[p[0]] = "";
|
135
137
|
else b[p[0]] = decodeURIComponent(p[1].replace(/\+/g, " "));
|
136
138
|
}
|
137
|
-
}
|
139
|
+
}
|
138
140
|
|
139
|
-
//
|
140
|
-
|
141
|
+
// Redirect to the hash page if we have a querystring
|
142
|
+
if (!$.isEmptyObject(b))
|
143
|
+
{
|
144
|
+
var qs = [];
|
145
|
+
for (var i in b)
|
146
|
+
qs[qs.length] = '' + i + '=' + b[i];
|
147
|
+
var uri = window.location.pathname + '#' + qs.join('&');
|
148
|
+
window.location = uri;
|
149
|
+
return;
|
150
|
+
};
|
151
|
+
|
152
|
+
// Get the hash values
|
153
|
+
var a = window.location.hash;
|
141
154
|
if (a.length > 0)
|
142
155
|
{
|
143
156
|
a = a.substr(1).split('&');
|
@@ -147,7 +160,7 @@ IndexTable.prototype = {
|
|
147
160
|
if (p.length == 1) b[p[0]] = "";
|
148
161
|
else b[p[0]] = decodeURIComponent(p[1].replace(/\+/g, " "));
|
149
162
|
}
|
150
|
-
}
|
163
|
+
}
|
151
164
|
|
152
165
|
// Set both hash and querystring values in the pager
|
153
166
|
for (var i in b)
|
@@ -166,13 +179,17 @@ IndexTable.prototype = {
|
|
166
179
|
//}
|
167
180
|
},
|
168
181
|
|
169
|
-
refresh: function()
|
182
|
+
refresh: function(skip_parse_querystring)
|
170
183
|
{
|
171
|
-
this.pager = { options: { page: 1 }, params: {}};
|
172
|
-
this.parse_querystring();
|
173
|
-
|
174
184
|
var that = this;
|
175
|
-
|
185
|
+
|
186
|
+
if (!skip_parse_querystring)
|
187
|
+
{
|
188
|
+
that.pager = { options: { page: 1 }, params: {}};
|
189
|
+
that.parse_querystring();
|
190
|
+
}
|
191
|
+
|
192
|
+
var $el = $('#' + that.container + '_table_container').length > 0 ? $('#' + that.container + '_table_container') : $('#' + that.container);
|
176
193
|
$el.html("<p class='loading'>Refreshing...</p>");
|
177
194
|
$.ajax({
|
178
195
|
url: that.refresh_url,
|
@@ -187,8 +204,7 @@ IndexTable.prototype = {
|
|
187
204
|
var m = that.models[i];
|
188
205
|
m.id = parseInt(m.id);
|
189
206
|
}
|
190
|
-
that.print();
|
191
|
-
that.populate_search_form();
|
207
|
+
that.print();
|
192
208
|
},
|
193
209
|
error: function() { $('#' + this.container).html("<p class='note error'>Error retrieving data.</p>"); }
|
194
210
|
});
|
@@ -252,19 +268,22 @@ IndexTable.prototype = {
|
|
252
268
|
else
|
253
269
|
{
|
254
270
|
var controls = $('<p/>');
|
255
|
-
if (this.allow_add ) controls.append($('<input/>').attr('type', 'button').attr('id', this.container + '_new' ).val(that.new_model_text
|
256
|
-
controls.append($('<input/>').attr('type', 'button').attr('id', this.container + '_toggle_columns' ).val('Show/Hide Columns'
|
257
|
-
if (this.
|
258
|
-
if (this.
|
259
|
-
if (this.
|
260
|
-
if (this.
|
271
|
+
if (this.allow_add ) controls.append($('<input/>').attr('type', 'button').attr('id', this.container + '_new' ).val(that.new_model_text ).click(function(e) { that.new_form(); })).append(' ');
|
272
|
+
controls.append($('<input/>').attr('type', 'button').attr('id', this.container + '_toggle_columns' ).val('Show/Hide Columns' ).click(function(e) { that.toggle_columns(); })).append(' ');
|
273
|
+
if (this.search_fields ) controls.append($('<input/>').attr('type', 'button').attr('id', this.container + '_toggle_search' ).val('Show/Hide Search Form' ).click(function(e) { that.toggle_search_form(); })).append(' ');
|
274
|
+
if (this.allow_bulk_edit ) controls.append($('<input/>').attr('type', 'button').attr('id', this.container + '_bulk_edit' ).val('Bulk Edit' ).click(function(e) { that.bulk_edit(); })).append(' ');
|
275
|
+
if (this.allow_bulk_import ) controls.append($('<input/>').attr('type', 'button').attr('id', this.container + '_bulk_import' ).val('Import' ).click(function(e) { that.bulk_import(); })).append(' ');
|
276
|
+
if (this.allow_duplicate ) controls.append($('<input/>').attr('type', 'button').attr('id', this.container + '_duplicate' ).val('Duplicate' ).click(function(e) { that.duplicate(); })).append(' ');
|
277
|
+
if (this.allow_bulk_delete ) controls.append($('<input/>').attr('type', 'button').attr('id', this.container + '_bulk_delete' ).val('Delete' ).click(function(e) { that.bulk_delete(); })).append(' ');
|
261
278
|
|
262
279
|
var c = $('#' + that.container);
|
263
|
-
c.empty()
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
280
|
+
c.empty();
|
281
|
+
c.append($('<div/>').attr('id', that.container + '_controls').append(controls));
|
282
|
+
//if (this.search_fields)
|
283
|
+
// c.append(that.search_form());
|
284
|
+
c.append($('<div/>').attr('id', that.container + '_message'));
|
285
|
+
c.append($('<div/>').attr('id', that.container + '_table_container').append(table));
|
286
|
+
c.append($('<div/>').attr('id', that.container + '_pager').append(pager_div));
|
268
287
|
|
269
288
|
if (model_count == 0)
|
270
289
|
{
|
@@ -298,14 +317,6 @@ IndexTable.prototype = {
|
|
298
317
|
}
|
299
318
|
},
|
300
319
|
|
301
|
-
populate_search_form: function()
|
302
|
-
{
|
303
|
-
var pp = this.pager_params();
|
304
|
-
$.each(this.search_form_fields, function(i, f) {
|
305
|
-
$('#' + f).val(pp[f]);
|
306
|
-
});
|
307
|
-
},
|
308
|
-
|
309
320
|
all_models_selected: function()
|
310
321
|
{
|
311
322
|
var that = this;
|
@@ -492,7 +503,7 @@ IndexTable.prototype = {
|
|
492
503
|
cols[col] = checked;
|
493
504
|
localStorage.setItem(this.container + '_cols', JSON.stringify(cols));
|
494
505
|
},
|
495
|
-
|
506
|
+
|
496
507
|
toggle_columns: function()
|
497
508
|
{
|
498
509
|
var that = this;
|
@@ -762,7 +773,9 @@ IndexTable.prototype = {
|
|
762
773
|
{
|
763
774
|
var p = this.pager_params(h);
|
764
775
|
var qs = [];
|
765
|
-
$.each(p, function(k,v) {
|
776
|
+
$.each(p, function(k,v) {
|
777
|
+
if (k != '[object Object]') qs.push('' + k + '=' + encodeURIComponent(v));
|
778
|
+
});
|
766
779
|
return '#' + qs.join('&');
|
767
780
|
},
|
768
781
|
|
@@ -842,5 +855,78 @@ IndexTable.prototype = {
|
|
842
855
|
});
|
843
856
|
},
|
844
857
|
|
858
|
+
//============================================================================
|
859
|
+
// Seach Form
|
860
|
+
//============================================================================
|
861
|
+
|
862
|
+
toggle_search_form: function()
|
863
|
+
{
|
864
|
+
var that = this;
|
865
|
+
var form = that.search_form();
|
866
|
+
that.show_message(form, 'toggle_search_form');
|
867
|
+
},
|
868
|
+
|
869
|
+
search_form: function()
|
870
|
+
{
|
871
|
+
var that = this;
|
872
|
+
|
873
|
+
var pp = that.pager_params();
|
874
|
+
var tbody = $('<tbody/>');
|
875
|
+
$.each(that.search_fields, function(i, f) {
|
876
|
+
var tr = $('<tr/>').append($('<td/>').attr('align', 'right').html(f.nice_name));
|
877
|
+
var td = $('<td/>');
|
878
|
+
if (f.type == 'text')
|
879
|
+
{
|
880
|
+
td.append($('<input/>').attr('name', f.name).attr('id', f.name).val(pp[f.name]));
|
881
|
+
}
|
882
|
+
else if (f.type == 'select')
|
883
|
+
{
|
884
|
+
var options = false;
|
885
|
+
if (!f.options && f.options_url)
|
886
|
+
{
|
887
|
+
$.ajax({
|
888
|
+
url: f.options_url,
|
889
|
+
type: 'get',
|
890
|
+
success: function(resp) { f.options = resp; },
|
891
|
+
async: false
|
892
|
+
});
|
893
|
+
}
|
894
|
+
var select = $('<select/>').attr('name', f.name).attr('id', f.name);
|
895
|
+
if (f.empty_option_text)
|
896
|
+
select.append($('<option/>').val('').html(f.empty_option_text));
|
897
|
+
$.each(f.options, function(j, option) {
|
898
|
+
var opt = $('<option/>').val(option.value).html(option.text);
|
899
|
+
if (pp[f.name] == option.value)
|
900
|
+
opt.attr('selected', true);
|
901
|
+
select.append(opt);
|
902
|
+
});
|
903
|
+
td.append(select);
|
904
|
+
}
|
905
|
+
tr.append(td);
|
906
|
+
tbody.append(tr);
|
907
|
+
});
|
908
|
+
var form = $('<form/>')
|
909
|
+
.attr('id', 'search_form')
|
910
|
+
.append($('<table/>').append(tbody))
|
911
|
+
.append($('<p/>').append($('<input/>').attr('type', 'submit').val('Search').click(function(e) { e.preventDefault(); that.search(); })));
|
912
|
+
return form;
|
913
|
+
},
|
914
|
+
|
915
|
+
populate_search_form: function()
|
916
|
+
{
|
917
|
+
var pp = this.pager_params();
|
918
|
+
$.each(this.search_form_fields, function(i, f) {
|
919
|
+
$('#' + f).val(pp[f]);
|
920
|
+
});
|
921
|
+
},
|
845
922
|
|
923
|
+
search: function()
|
924
|
+
{
|
925
|
+
var that = this;
|
926
|
+
$.each(that.search_fields, function(i, f) {
|
927
|
+
that.pager.params[f.name] = $('#'+f.name).val();
|
928
|
+
});
|
929
|
+
window.location.hash = that.pager_hash();
|
930
|
+
that.refresh(true);
|
931
|
+
}
|
846
932
|
};
|
@@ -16,11 +16,12 @@ module Caboose
|
|
16
16
|
config = YAML.load(File.read(Rails.root.join('config', 'aws.yml')))[Rails.env]
|
17
17
|
access_key = config['access_key_id']
|
18
18
|
secret_key = config['secret_access_key']
|
19
|
-
bucket = config['bucket']
|
19
|
+
bucket = config['bucket']
|
20
|
+
bucket = Caboose::uploads_bucket && Caboose::uploads_bucket.strip.length > 0 ? Caboose::uploads_bucket : "#{bucket}-uploads"
|
20
21
|
policy = {
|
21
22
|
"expiration" => 1.hour.from_now.utc.xmlschema,
|
22
23
|
"conditions" => [
|
23
|
-
{ "bucket" =>
|
24
|
+
{ "bucket" => bucket },
|
24
25
|
{ "acl" => "public-read" },
|
25
26
|
[ "starts-with", "$key", '' ],
|
26
27
|
#[ "starts-with", "$Content-Type", 'image/' ],
|
@@ -65,7 +65,7 @@ module Caboose
|
|
65
65
|
})
|
66
66
|
render :json => {
|
67
67
|
:pager => pager,
|
68
|
-
:models => pager.items
|
68
|
+
:models => pager.items.as_json(:include => [:flat_rate_shipping_package, :flat_rate_shipping_method])
|
69
69
|
}
|
70
70
|
end
|
71
71
|
|
@@ -116,25 +116,34 @@ module Caboose
|
|
116
116
|
save = true
|
117
117
|
params.each do |name,value|
|
118
118
|
case name
|
119
|
-
when 'alternate_id'
|
120
|
-
when 'sku'
|
121
|
-
when 'barcode'
|
122
|
-
when 'price'
|
123
|
-
when 'quantity_in_stock'
|
124
|
-
when 'ignore_quantity'
|
125
|
-
when 'allow_backorder'
|
126
|
-
when 'status'
|
127
|
-
when 'weight'
|
128
|
-
when 'length'
|
129
|
-
when 'width'
|
130
|
-
when 'height'
|
131
|
-
when 'option1'
|
132
|
-
when 'option2'
|
133
|
-
when 'option3'
|
134
|
-
when 'requires_shipping'
|
135
|
-
when 'taxable'
|
136
|
-
when '
|
137
|
-
when '
|
119
|
+
when 'alternate_id' then v.alternate_id = value
|
120
|
+
when 'sku' then v.sku = value
|
121
|
+
when 'barcode' then v.barcode = value
|
122
|
+
when 'price' then v.price = value
|
123
|
+
when 'quantity_in_stock' then v.quantity_in_stock = value
|
124
|
+
when 'ignore_quantity' then v.ignore_quantity = value
|
125
|
+
when 'allow_backorder' then v.allow_backorder = value
|
126
|
+
when 'status' then v.status = value
|
127
|
+
when 'weight' then v.weight = value
|
128
|
+
when 'length' then v.length = value
|
129
|
+
when 'width' then v.width = value
|
130
|
+
when 'height' then v.height = value
|
131
|
+
when 'option1' then v.option1 = value
|
132
|
+
when 'option2' then v.option2 = value
|
133
|
+
when 'option3' then v.option3 = value
|
134
|
+
when 'requires_shipping' then v.requires_shipping = value
|
135
|
+
when 'taxable' then v.taxable = value
|
136
|
+
when 'flat_rate_shipping' then v.flat_rate_shipping = value
|
137
|
+
when 'flat_rate_shipping_single' then v.flat_rate_shipping_single = value
|
138
|
+
when 'flat_rate_shipping_combined' then v.flat_rate_shipping_combined = value
|
139
|
+
when 'flat_rate_shipping_package_id' then v.flat_rate_shipping_package_id = value
|
140
|
+
when 'flat_rate_shipping_method_id' then v.flat_rate_shipping_method_id = value
|
141
|
+
when 'flat_rate_shipping_package_method_id' then
|
142
|
+
arr = value.split('_')
|
143
|
+
v.flat_rate_shipping_package_id = arr[0].to_i
|
144
|
+
v.flat_rate_shipping_method_id = arr[1].to_i
|
145
|
+
when 'downloadable' then v.downloadable = value
|
146
|
+
when 'download_path' then v.download_path = value
|
138
147
|
|
139
148
|
when 'sale_price'
|
140
149
|
v.sale_price = value
|
@@ -0,0 +1,56 @@
|
|
1
|
+
================================================================================
|
2
|
+
Order packages
|
3
|
+
================================================================================
|
4
|
+
|
5
|
+
# Create a single order package for the order
|
6
|
+
sp = ShippingPackage.where(:site_id => order.site_id).first
|
7
|
+
op = OrderPackage.create(:order_id => order.id, :shipping_package_id => sp.id)
|
8
|
+
|
9
|
+
# Assign the line items to the order package
|
10
|
+
order.line_items.each do |li|
|
11
|
+
li.order_package_id = op.id
|
12
|
+
li.save
|
13
|
+
end
|
14
|
+
|
15
|
+
================================================================================
|
16
|
+
Shipping
|
17
|
+
================================================================================
|
18
|
+
|
19
|
+
sc = store_config
|
20
|
+
sa = order.shipping_address
|
21
|
+
origin = ActiveShipping::Location.new(:country => sc.origin_country, :state => sc.origin_state, :city => sc.origin_city, :zip => sc.origin_zip)
|
22
|
+
destination = ActiveShipping::Location.new(:country => sc.origin_country, :state => sa.state, :city => sa.city, :postal_code => sa.zip)
|
23
|
+
carriers = {}
|
24
|
+
carriers['UPS'] = ActiveShipping::UPS.new(:login => sc.ups_username, :password => sc.ups_password, :key => sc.ups_key, :origin_account => sc.ups_origin_account)
|
25
|
+
carriers['USPS'] = ActiveShipping::USPS.new( :login => sc.usps_username) if order.total < 150
|
26
|
+
|
27
|
+
all_rates = []
|
28
|
+
order.order_packages.all.each do |op|
|
29
|
+
|
30
|
+
flat_rate = true
|
31
|
+
op.line_items do |li|
|
32
|
+
flat_rate = false if !li.variant.flat_rate_shipping
|
33
|
+
end
|
34
|
+
if flat_rate
|
35
|
+
v = op.line_items.first.variant
|
36
|
+
all_rates << { :order_package => op, :rates => [{ :shipping_method => v.flat_rate_shipping_method, :total_price => (v.flat_rate_shipping_single/100) }] }
|
37
|
+
else
|
38
|
+
sp = op.shipping_package
|
39
|
+
package = op.activemerchant_package
|
40
|
+
rates = []
|
41
|
+
carriers.each do |name, carrier|
|
42
|
+
resp = carrier.find_rates(origin, destination, package)
|
43
|
+
resp.rates.sort_by(&:price).each do |rate|
|
44
|
+
sm = ShippingMethod.where( :carrier => name, :service_code => rate.service_code).first
|
45
|
+
sm = ShippingMethod.create(:carrier => name, :service_code => rate.service_code, :service_name => rate.service_name) if sm.nil?
|
46
|
+
next if !sp.uses_shipping_method(sm)
|
47
|
+
price = rate.total_price
|
48
|
+
price = rate.package_rates[0].rate if price.nil? && rate.package_rates && rate.package_rates.count > 0
|
49
|
+
rates << { :shipping_method => sm, :total_price => (price.to_d/100) }
|
50
|
+
end
|
51
|
+
end
|
52
|
+
all_rates << { :order_package => op, :rates => rates }
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
return all_rates
|
@@ -734,38 +734,43 @@ class Caboose::Schema < Caboose::Utilities::Schema
|
|
734
734
|
[ :payment_profile_id , :string ]
|
735
735
|
],
|
736
736
|
Caboose::Variant => [
|
737
|
-
[ :product_id
|
738
|
-
[ :alternate_id
|
739
|
-
[ :sku
|
740
|
-
[ :barcode
|
741
|
-
[ :price
|
742
|
-
[ :sale_price
|
743
|
-
[ :date_sale_starts
|
744
|
-
[ :date_sale_ends
|
745
|
-
[ :date_sale_end
|
746
|
-
[ :available
|
747
|
-
[ :quantity_in_stock
|
748
|
-
[ :ignore_quantity
|
749
|
-
[ :allow_backorder
|
750
|
-
[ :weight
|
751
|
-
[ :length
|
752
|
-
[ :width
|
753
|
-
[ :height
|
754
|
-
[ :volume
|
755
|
-
[ :cylinder
|
756
|
-
[ :option1
|
757
|
-
[ :option2
|
758
|
-
[ :option3
|
759
|
-
[ :requires_shipping
|
760
|
-
[ :taxable
|
761
|
-
[ :shipping_unit_value
|
762
|
-
[ :
|
763
|
-
[ :
|
764
|
-
[ :
|
765
|
-
[ :
|
766
|
-
[ :
|
767
|
-
[ :
|
768
|
-
[ :
|
737
|
+
[ :product_id , :integer ],
|
738
|
+
[ :alternate_id , :string ],
|
739
|
+
[ :sku , :string ],
|
740
|
+
[ :barcode , :string ],
|
741
|
+
[ :price , :decimal , { :precision => 8, :scale => 2 }],
|
742
|
+
[ :sale_price , :decimal , { :precision => 8, :scale => 2 }],
|
743
|
+
[ :date_sale_starts , :datetime ],
|
744
|
+
[ :date_sale_ends , :datetime ],
|
745
|
+
[ :date_sale_end , :datetime ],
|
746
|
+
[ :available , :boolean ],
|
747
|
+
[ :quantity_in_stock , :integer , :default => 0 ],
|
748
|
+
[ :ignore_quantity , :boolean ],
|
749
|
+
[ :allow_backorder , :boolean ],
|
750
|
+
[ :weight , :decimal ],
|
751
|
+
[ :length , :decimal ],
|
752
|
+
[ :width , :decimal ],
|
753
|
+
[ :height , :decimal ],
|
754
|
+
[ :volume , :decimal ],
|
755
|
+
[ :cylinder , :boolean ],
|
756
|
+
[ :option1 , :string ],
|
757
|
+
[ :option2 , :string ],
|
758
|
+
[ :option3 , :string ],
|
759
|
+
[ :requires_shipping , :boolean ],
|
760
|
+
[ :taxable , :boolean ],
|
761
|
+
[ :shipping_unit_value , :numeric ],
|
762
|
+
[ :flat_rate_shipping , :boolean , { :default => false }],
|
763
|
+
[ :flat_rate_shipping_package_id , :integer ],
|
764
|
+
[ :flat_rate_shipping_method_id , :integer ],
|
765
|
+
[ :flat_rate_shipping_single , :decimal , { :precision => 8, :scale => 2, :default => 0.0 }],
|
766
|
+
[ :flat_rate_shipping_combined , :decimal , { :precision => 8, :scale => 2, :default => 0.0 }],
|
767
|
+
[ :status , :string ],
|
768
|
+
[ :option1_sort_order , :integer , { :default => 0 }],
|
769
|
+
[ :option2_sort_order , :integer , { :default => 0 }],
|
770
|
+
[ :option3_sort_order , :integer , { :default => 0 }],
|
771
|
+
[ :sort_order , :integer , { :default => 0 }],
|
772
|
+
[ :downloadable , :boolean , { :default => false }],
|
773
|
+
[ :download_path , :string ]
|
769
774
|
],
|
770
775
|
Caboose::Vendor => [
|
771
776
|
[ :site_id , :integer ],
|
@@ -54,7 +54,8 @@ module Caboose
|
|
54
54
|
:origin => origin,
|
55
55
|
:destination => destination
|
56
56
|
)
|
57
|
-
order.line_items.each_with_index do |li, i|
|
57
|
+
order.line_items.each_with_index do |li, i|
|
58
|
+
next if !li.variant.taxable # Skip any non-taxable items
|
58
59
|
transaction.cart_items << TaxCloud::CartItem.new(
|
59
60
|
:index => i,
|
60
61
|
:item_id => li.variant.id,
|
@@ -11,6 +11,8 @@ module Caboose
|
|
11
11
|
belongs_to :product
|
12
12
|
has_many :product_image_variants
|
13
13
|
has_many :product_images, :through => :product_image_variants
|
14
|
+
belongs_to :flat_rate_shipping_method, :class_name => 'Caboose::ShippingMethod'
|
15
|
+
belongs_to :flat_rate_shipping_package, :class_name => 'Caboose::ShippingPackage'
|
14
16
|
|
15
17
|
attr_accessible :id,
|
16
18
|
:alternate_id,
|
@@ -37,7 +39,12 @@ module Caboose
|
|
37
39
|
:sku,
|
38
40
|
:available,
|
39
41
|
:cylinder,
|
40
|
-
:shipping_unit_value
|
42
|
+
:shipping_unit_value,
|
43
|
+
:flat_rate_shipping,
|
44
|
+
:flat_rate_shipping_single,
|
45
|
+
:flat_rate_shipping_combined,
|
46
|
+
:flat_rate_shipping_method_id,
|
47
|
+
:flat_rate_shipping_package_id
|
41
48
|
|
42
49
|
after_initialize do |v|
|
43
50
|
v.price = 0.00 if v.price.nil?
|
@@ -85,7 +92,9 @@ module Caboose
|
|
85
92
|
def as_json(options={})
|
86
93
|
self.attributes.merge({
|
87
94
|
:images => self.product_images.any? ? self.product_images : [self.product.product_images.first],
|
88
|
-
:title => "#{self.product.title} (#{self.options.join(', ')})"
|
95
|
+
:title => "#{self.product.title} (#{self.options.join(', ')})",
|
96
|
+
:flat_rate_shipping_package => self.flat_rate_shipping_package,
|
97
|
+
:flat_rate_shipping_method => self.flat_rate_shipping_method
|
89
98
|
})
|
90
99
|
end
|
91
100
|
|
@@ -12,10 +12,10 @@ v = @variant
|
|
12
12
|
<% if p.option2 %><div id='variant_<%= v.id %>_option2'></div><% end %>
|
13
13
|
<% if p.option3 %><div id='variant_<%= v.id %>_option3'></div><% end %>
|
14
14
|
<div id='variant_<%= v.id %>_status' ></div>
|
15
|
-
<div id='variant_<%= v.id %>_requires_shipping' ></div>
|
16
15
|
<div id='variant_<%= v.id %>_allow_backorder' ></div>
|
17
16
|
<div id='variant_<%= v.id %>_taxable' ></div>
|
18
17
|
<div id='variant_<%= v.id %>_downloadable' ></div>
|
18
|
+
<div id='variant_<%= v.id %>_requires_shipping' ></div>
|
19
19
|
</td><td valign='top'>
|
20
20
|
<h2>Inventory</h2>
|
21
21
|
<div id='variant_<%= v.id %>_alternate_id' ></div>
|
@@ -37,7 +37,14 @@ v = @variant
|
|
37
37
|
<div id='variant_<%= v.id %>_date_sale_starts' ></div>
|
38
38
|
<div id='variant_<%= v.id %>_time_sale_starts' ></div>
|
39
39
|
<div id='variant_<%= v.id %>_date_sale_ends' ></div>
|
40
|
-
<div id='variant_<%= v.id %>_time_sale_ends' ></div>
|
40
|
+
<div id='variant_<%= v.id %>_time_sale_ends' ></div>
|
41
|
+
</td>
|
42
|
+
<td valign='top' colspan='4'4>
|
43
|
+
<h2>Flat Rate Shipping</h2>
|
44
|
+
<div id='variant_<%= v.id %>_flat_rate_shipping' ></div>
|
45
|
+
<div id='variant_<%= v.id %>_flat_rate_shipping_single' ></div>
|
46
|
+
<div id='variant_<%= v.id %>_flat_rate_shipping_combined' ></div>
|
47
|
+
<div id='variant_<%= v.id %>_flat_rate_shipping_package_method_id'></div>
|
41
48
|
</td>
|
42
49
|
</tr>
|
43
50
|
</table>
|
@@ -63,29 +70,36 @@ $(document).ready(function() {
|
|
63
70
|
update_url: '/admin/products/<%= v.product_id %>/variants/<%= v.id %>',
|
64
71
|
authenticity_token: '<%= form_authenticity_token %>',
|
65
72
|
attributes: [
|
66
|
-
<% if p.option1 %>{ name: 'option1'
|
67
|
-
<% if p.option2 %>{ name: 'option2'
|
68
|
-
<% if p.option3 %>{ name: 'option3'
|
69
|
-
{ name: 'alternate_id'
|
70
|
-
{ name: 'sku'
|
71
|
-
{ name: 'barcode'
|
72
|
-
{ name: 'price'
|
73
|
-
{ name: 'sale_price'
|
74
|
-
{ name: 'date_sale_starts'
|
75
|
-
{ name: 'date_sale_ends'
|
76
|
-
{ name: 'quantity_in_stock'
|
77
|
-
{ name: 'ignore_quantity'
|
78
|
-
{ name: 'weight'
|
79
|
-
{ name: 'length'
|
80
|
-
{ name: 'width'
|
81
|
-
{ name: 'height'
|
82
|
-
{ name: 'cylinder'
|
83
|
-
{ name: 'requires_shipping'
|
84
|
-
{ name: 'taxable'
|
85
|
-
{ name: 'allow_backorder'
|
86
|
-
{ name: 'status'
|
87
|
-
{ name: 'downloadable'
|
88
|
-
{ name: 'download_path'
|
73
|
+
<% if p.option1 %>{ name: 'option1' , nice_name: <%= raw Caboose.json(p.option1) %> , type: 'text' , align: 'right' , width: 250, value: <%= raw Caboose.json(v.option1 ) %> },<% end %>
|
74
|
+
<% if p.option2 %>{ name: 'option2' , nice_name: <%= raw Caboose.json(p.option2) %> , type: 'text' , align: 'right' , width: 250, value: <%= raw Caboose.json(v.option2 ) %> },<% end %>
|
75
|
+
<% if p.option3 %>{ name: 'option3' , nice_name: <%= raw Caboose.json(p.option3) %> , type: 'text' , align: 'right' , width: 250, value: <%= raw Caboose.json(v.option3 ) %> },<% end %>
|
76
|
+
{ name: 'alternate_id' , nice_name: 'Alternate ID' , type: 'text' , align: 'right' , width: 250, value: <%= raw Caboose.json(v.alternate_id ) %> },
|
77
|
+
{ name: 'sku' , nice_name: 'SKU' , type: 'text' , align: 'right' , width: 250, value: <%= raw Caboose.json(v.sku ) %> },
|
78
|
+
{ name: 'barcode' , nice_name: 'Barcode' , type: 'text' , align: 'right' , width: 250, value: <%= raw Caboose.json(v.barcode ) %> },
|
79
|
+
{ name: 'price' , nice_name: 'Price' , type: 'text' , align: 'right' , width: 250, value: <%= raw Caboose.json(sprintf("%.2f", v.price) ) %> },
|
80
|
+
{ name: 'sale_price' , nice_name: 'Sale price' , type: 'text' , align: 'right' , width: 175, value: <%= raw Caboose.json(v.sale_price ) %> },
|
81
|
+
{ name: 'date_sale_starts' , nice_name: 'Sale starts' , type: 'datetime' , align: 'right' , width: 175, value: <%= raw Caboose.json(v.date_sale_starts ? v.date_sale_starts.in_time_zone(@logged_in_user.timezone).strftime('%m/%d/%Y %I:%M %P') : '') %> },
|
82
|
+
{ name: 'date_sale_ends' , nice_name: 'Sale ends' , type: 'datetime' , align: 'right' , width: 175, value: <%= raw Caboose.json(v.date_sale_ends ? v.date_sale_ends.in_time_zone(@logged_in_user.timezone).strftime('%m/%d/%Y %I:%M %P') : '') %> },
|
83
|
+
{ name: 'quantity_in_stock' , nice_name: 'Quantity' , type: 'text' , align: 'right' , width: 250, value: <%= raw Caboose.json(v.quantity_in_stock ) %> },
|
84
|
+
{ name: 'ignore_quantity' , nice_name: 'Ignore Quantity' , type: 'checkbox' , align: 'right' , width: 250, value: <%= raw Caboose.json(v.ignore_quantity ) %> },
|
85
|
+
{ name: 'weight' , nice_name: 'Weight (<%= sc.weight_unit %>)' , type: 'text' , align: 'right' , width: 175, value: <%= raw Caboose.json(v.weight ) %> },
|
86
|
+
{ name: 'length' , nice_name: 'Length (<%= sc.length_unit %>)' , type: 'text' , align: 'right' , width: 175, value: <%= raw Caboose.json(v.length ) %> },
|
87
|
+
{ name: 'width' , nice_name: 'Width (<%= sc.length_unit %>)' , type: 'text' , align: 'right' , width: 175, value: <%= raw Caboose.json(v.width ) %> },
|
88
|
+
{ name: 'height' , nice_name: 'Height (<%= sc.length_unit %>)' , type: 'text' , align: 'right' , width: 175, value: <%= raw Caboose.json(v.height ) %> },
|
89
|
+
{ name: 'cylinder' , nice_name: 'Cylinder' , type: 'checkbox' , align: 'right' , width: 175, value: <%= raw Caboose.json(v.cylinder ) %> },
|
90
|
+
{ name: 'requires_shipping' , nice_name: 'Requires shipping' , type: 'checkbox' , align: 'right' , width: 250, value: <%= raw Caboose.json(v.requires_shipping ) %> },
|
91
|
+
{ name: 'taxable' , nice_name: 'Taxable' , type: 'checkbox' , align: 'right' , width: 250, value: <%= raw Caboose.json(v.taxable ) %> },
|
92
|
+
{ name: 'allow_backorder' , nice_name: 'Allow backorder' , type: 'checkbox' , align: 'right' , width: 250, value: <%= raw Caboose.json(v.allow_backorder ) %> },
|
93
|
+
{ name: 'status' , nice_name: 'Status' , type: 'select' , align: 'right' , width: 250, value: <%= raw Caboose.json(v.status ) %>, options_url: '/admin/variants/status-options' },
|
94
|
+
{ name: 'downloadable' , nice_name: 'Downloadable' , type: 'checkbox' , align: 'right' , width: 250, value: <%= raw Caboose.json(v.downloadable ) %> },
|
95
|
+
{ name: 'download_path' , nice_name: 'Download path' , type: 'text' , align: 'right' , width: 800, value: <%= raw Caboose.json(v.download_path ) %> },
|
96
|
+
{ name: 'flat_rate_shipping' , nice_name: 'Use flat rate shipping' , type: 'checkbox' , align: 'right' , width: 250, value: <%= raw Caboose.json(v.flat_rate_shipping ) %> },
|
97
|
+
{ name: 'flat_rate_shipping_single' , nice_name: 'Amount (single)' , type: 'text' , align: 'right' , width: 250, value: <%= raw Caboose.json(v.flat_rate_shipping_single ) %> },
|
98
|
+
{ name: 'flat_rate_shipping_combined' , nice_name: 'Amount (combined)' , type: 'text' , align: 'right' , width: 250, value: <%= raw Caboose.json(v.flat_rate_shipping_combined ) %> },
|
99
|
+
{ name: 'flat_rate_shipping_package_method_id' , nice_name: 'Package method' , type: 'select' , align: 'right' , width: 250, options_url: '/admin/shipping-packages/package-method-options',
|
100
|
+
value: <%= raw Caboose.json("#{v.flat_rate_shipping_package_id}_#{v.flat_rate_shipping_method_id}") %>,
|
101
|
+
text: <%= raw Caboose.json(v.flat_rate_shipping_package && v.flat_rate_shipping_method ? "#{v.flat_rate_shipping_package.name} - #{v.flat_rate_shipping_method.service_name}" : '') %>
|
102
|
+
}
|
89
103
|
]
|
90
104
|
});
|
91
105
|
});
|
@@ -66,28 +66,39 @@ $(document).ready(function() {
|
|
66
66
|
allow_duplicate: false,
|
67
67
|
allow_advanced_edit: true,
|
68
68
|
fields: [
|
69
|
-
<% if p.option1 %>{ show: true , name: 'option1'
|
70
|
-
<% if p.option2 %>{ show: true , name: 'option2'
|
71
|
-
<% if p.option3 %>{ show: true , name: 'option3'
|
72
|
-
{ show: true , name: 'status'
|
73
|
-
{ show: true , name: 'alternate_id'
|
74
|
-
{ show: true , name: 'sku'
|
75
|
-
{ show: false , name: 'barcode'
|
76
|
-
{ show: true , name: 'price'
|
77
|
-
{ show: true , name: 'sale_price'
|
78
|
-
{ show: false , name: 'date_sale_starts'
|
79
|
-
{ show: false , name: 'date_sale_ends'
|
80
|
-
{ show: true , name: 'quantity_in_stock'
|
81
|
-
{ show: false , name: 'weight'
|
82
|
-
{ show: false , name: 'length'
|
83
|
-
{ show: false , name: 'width'
|
84
|
-
{ show: false , name: 'height'
|
85
|
-
{ show: false , name: 'cylinder'
|
86
|
-
{ show: false , name: 'requires_shipping'
|
87
|
-
{ show: false , name: 'taxable'
|
88
|
-
{ show: false , name: 'allow_backorder'
|
89
|
-
{ show: false , name: 'downloadable'
|
90
|
-
{ show: false , name: 'download_path'
|
69
|
+
<% if p.option1 %>{ show: true , name: 'option1' , nice_name: <%= raw Caboose.json(p.option1) %> , sort: 'option1' , type: 'text' , value: function(v) { return v.option1 }, width: 75, align: 'left' , bulk_edit: true },<% end %>
|
70
|
+
<% if p.option2 %>{ show: true , name: 'option2' , nice_name: <%= raw Caboose.json(p.option2) %> , sort: 'option2' , type: 'text' , value: function(v) { return v.option2 }, width: 75, align: 'left' , bulk_edit: true },<% end %>
|
71
|
+
<% if p.option3 %>{ show: true , name: 'option3' , nice_name: <%= raw Caboose.json(p.option3) %> , sort: 'option3' , type: 'text' , value: function(v) { return v.option3 }, width: 75, align: 'left' , bulk_edit: true },<% end %>
|
72
|
+
{ show: true , name: 'status' , nice_name: 'Status' , sort: 'status' , type: 'text' , value: function(v) { return v.status }, width: 75, align: 'left' , bulk_edit: true },
|
73
|
+
{ show: true , name: 'alternate_id' , nice_name: 'Alternate ID' , sort: 'alternate_id' , type: 'text' , value: function(v) { return v.alternate_id }, width: 75, align: 'left' , bulk_edit: true },
|
74
|
+
{ show: true , name: 'sku' , nice_name: 'SKU' , sort: 'sku' , type: 'text' , value: function(v) { return v.sku }, width: 75, align: 'left' , bulk_edit: true },
|
75
|
+
{ show: false , name: 'barcode' , nice_name: 'Barcode' , sort: 'barcode' , type: 'text' , value: function(v) { return v.barcode }, width: 75, align: 'left' , bulk_edit: true },
|
76
|
+
{ show: true , name: 'price' , nice_name: 'Price' , sort: 'price' , type: 'text' , value: function(v) { return v.price }, width: 75, align: 'right' , bulk_edit: true },
|
77
|
+
{ show: true , name: 'sale_price' , nice_name: 'Sale price' , sort: 'sale_price' , type: 'text' , value: function(v) { return v.sale_price }, width: 75, align: 'right' , bulk_edit: true },
|
78
|
+
{ show: false , name: 'date_sale_starts' , nice_name: 'Sale starts' , sort: 'date_sale_starts' , type: 'datetime' , value: function(v) { return v.date_sale_starts }, width: 75, align: 'left' , bulk_edit: true },
|
79
|
+
{ show: false , name: 'date_sale_ends' , nice_name: 'Sale ends' , sort: 'date_sale_ends' , type: 'datetime' , value: function(v) { return v.date_sale_ends }, width: 75, align: 'left' , bulk_edit: true },
|
80
|
+
{ show: true , name: 'quantity_in_stock' , nice_name: 'Quantity' , sort: 'quantity_in_stock' , type: 'text' , value: function(v) { return v.quantity_in_stock }, width: 50, align: 'right' , bulk_edit: true },
|
81
|
+
{ show: false , name: 'weight' , nice_name: 'Weight (<%= sc.weight_unit %>)' , sort: 'weight' , type: 'text' , value: function(v) { return v.weight }, width: 50, align: 'right' , bulk_edit: true },
|
82
|
+
{ show: false , name: 'length' , nice_name: 'Length (<%= sc.length_unit %>)' , sort: 'length' , type: 'text' , value: function(v) { return v.length }, width: 50, align: 'right' , bulk_edit: true },
|
83
|
+
{ show: false , name: 'width' , nice_name: 'Width (<%= sc.length_unit %>)' , sort: 'width' , type: 'text' , value: function(v) { return v.width }, width: 50, align: 'right' , bulk_edit: true },
|
84
|
+
{ show: false , name: 'height' , nice_name: 'Height (<%= sc.length_unit %>)' , sort: 'height' , type: 'text' , value: function(v) { return v.height }, width: 50, align: 'right' , bulk_edit: true },
|
85
|
+
{ show: false , name: 'cylinder' , nice_name: 'Cylinder' , sort: 'cylinder' , type: 'checkbox' , value: function(v) { return v.cylinder }, text: function(v) { return v.cylinder ? 'Yes' : 'No' }, width: 50, align: 'center' , bulk_edit: true },
|
86
|
+
{ show: false , name: 'requires_shipping' , nice_name: 'Requires shipping' , sort: 'requires_shipping' , type: 'checkbox' , value: function(v) { return v.requires_shipping }, text: function(v) { return v.requires_shipping ? 'Yes' : 'No' }, width: 50, align: 'center' , bulk_edit: true },
|
87
|
+
{ show: false , name: 'taxable' , nice_name: 'Taxable' , sort: 'taxable' , type: 'checkbox' , value: function(v) { return v.taxable }, text: function(v) { return v.taxable ? 'Yes' : 'No' }, width: 50, align: 'center' , bulk_edit: true },
|
88
|
+
{ show: false , name: 'allow_backorder' , nice_name: 'Allow backorder' , sort: 'allow_backorder' , type: 'checkbox' , value: function(v) { return v.allow_backorder }, text: function(v) { return v.allow_backorder ? 'Yes' : 'No' }, width: 50, align: 'center' , bulk_edit: true },
|
89
|
+
{ show: false , name: 'downloadable' , nice_name: 'Downloadable' , sort: 'downloadable' , type: 'checkbox' , value: function(v) { return v.downloadable }, text: function(v) { return v.downloadable ? 'Yes' : 'No' }, width: 50, align: 'center' , bulk_edit: true },
|
90
|
+
{ show: false , name: 'download_path' , nice_name: 'Download path' , sort: 'download_path' , type: 'text' , value: function(v) { return v.download_path }, width: 50, align: 'left' , bulk_edit: true },
|
91
|
+
{ show: false , name: 'flat_rate_shipping' , nice_name: 'Flat rate shipping' , sort: 'flat_rate_shipping' , type: 'checkbox' , value: function(v) { return v.flat_rate_shipping }, text: function(v) { return v.flat_rate_shipping ? 'Yes' : 'No' }, width: 50, align: 'center' , bulk_edit: true },
|
92
|
+
{ show: false , name: 'flat_rate_shipping_single' , nice_name: 'Amount (single)' , sort: 'flat_rate_shipping_single' , type: 'text' , value: function(v) { return v.flat_rate_shipping_single }, width: 50, align: 'right' , bulk_edit: true },
|
93
|
+
{ show: false , name: 'flat_rate_shipping_combined' , nice_name: 'Amount (combined)' , sort: 'flat_rate_shipping_combined' , type: 'text' , value: function(v) { return v.flat_rate_shipping_combined }, width: 50, align: 'right' , bulk_edit: true },
|
94
|
+
{ show: false , name: 'flat_rate_shipping_package_method_id' , nice_name: 'Package and method' , sort: 'flat_rate_shipping_package_id' , type: 'select' ,
|
95
|
+
value: function(v) { return v.flat_rate_shipping_package_id + '_' + v.flat_rate_shipping_method_id },
|
96
|
+
text: function(v) { return v.flat_rate_shipping_package && v.flat_rate_shipping_method ? v.flat_rate_shipping_package.name + ' - ' + v.flat_rate_shipping_method.service_name : '' },
|
97
|
+
width: 150,
|
98
|
+
align: 'right' ,
|
99
|
+
bulk_edit: true ,
|
100
|
+
options_url: '/admin/shipping-packages/package-method-options'
|
101
|
+
}
|
91
102
|
],
|
92
103
|
<% if @highlight_variant_id %>highlight_id: <%= @highlight_variant_id %>,<% end %>
|
93
104
|
new_model_text: 'New Variant',
|
data/lib/caboose.rb
CHANGED
@@ -124,6 +124,9 @@ module Caboose
|
|
124
124
|
mattr_accessor :from_address
|
125
125
|
@@from_address = ''
|
126
126
|
|
127
|
+
mattr_accessor :uploads_bucket
|
128
|
+
@@uploads_bucket = ''
|
129
|
+
|
127
130
|
end
|
128
131
|
|
129
132
|
# These are used so that both local filestorage and S3 can work without having to change paperclip paths in models
|
data/lib/caboose/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: caboose-cms
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.6.
|
4
|
+
version: 0.6.12
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- William Barry
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-07-
|
11
|
+
date: 2015-07-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: pg
|
@@ -660,6 +660,7 @@ files:
|
|
660
660
|
- app/mailers/caboose/caboose_mailer.rb
|
661
661
|
- app/mailers/caboose/login_mailer.rb
|
662
662
|
- app/mailers/caboose/orders_mailer.rb
|
663
|
+
- app/models/caboose/#Untitled-1#
|
663
664
|
- app/models/caboose/ab_option.rb
|
664
665
|
- app/models/caboose/ab_testing.rb
|
665
666
|
- app/models/caboose/ab_value.rb
|