solidus_br_common 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (113) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +14 -0
  3. data/.rspec +1 -0
  4. data/.ruby-gemset +1 -0
  5. data/.ruby-version +1 -0
  6. data/.travis.yml +14 -0
  7. data/Gemfile +19 -0
  8. data/LICENSE +26 -0
  9. data/README.md +55 -0
  10. data/Rakefile +21 -0
  11. data/app/assets/javascripts/spree/backend/solidus_br_common.js +2 -0
  12. data/app/assets/javascripts/spree/frontend/address.js +119 -0
  13. data/app/assets/javascripts/spree/frontend/mask.js +21 -0
  14. data/app/assets/javascripts/spree/frontend/plugins/jquery.mask.extension.js +4 -0
  15. data/app/assets/javascripts/spree/frontend/plugins/jquery.mask.js +395 -0
  16. data/app/assets/javascripts/spree/frontend/routes.js.erb +4 -0
  17. data/app/assets/javascripts/spree/frontend/solidus_br_common.js +1 -0
  18. data/app/assets/stylesheets/spree/backend/solidus_br_common.css +4 -0
  19. data/app/assets/stylesheets/spree/frontend/solidus_br_common.css +4 -0
  20. data/app/controllers/spree/addresses_controller.rb +24 -0
  21. data/app/controllers/spree/api/cities_controller.rb +33 -0
  22. data/app/controllers/spree/user_registrations_controller_decorator.rb +13 -0
  23. data/app/helpers/spree/api/api_helpers_decorator.rb +9 -0
  24. data/app/helpers/spree/base_helper_decorator.rb +5 -0
  25. data/app/models/spree/ability_decorator.rb +21 -0
  26. data/app/models/spree/address_decorator.rb +6 -0
  27. data/app/models/spree/city.rb +7 -0
  28. data/app/models/spree/permitted_attributes_decorator.rb +8 -0
  29. data/app/models/spree/state_decorator.rb +3 -0
  30. data/app/models/spree/user_decorator.rb +52 -0
  31. data/app/overrides/spree/address/_form/add_district.html.erb.deface +5 -0
  32. data/app/overrides/spree/address/_form/add_number.html.erb.deface +5 -0
  33. data/app/overrides/spree/address/_form/hide_country.html.erb.deface +4 -0
  34. data/app/overrides/spree/address/_form/move_city_to_after_state.deface +2 -0
  35. data/app/overrides/spree/address/_form/move_zipcode_after_last_name.deface +2 -0
  36. data/app/overrides/spree/address/_form/replace_city_input_text_by_select.html.erb.deface +18 -0
  37. data/app/overrides/spree/address/_form/set_data_uf_to_state.html.erb.deface +15 -0
  38. data/app/overrides/spree/admin/users/_form/add_extras_fields.html.erb.deface +39 -0
  39. data/app/overrides/spree/shared/_user_form/add_extra_fields.html.erb.deface +19 -0
  40. data/app/overrides/spree/users/show/add_details.html.erb.deface +13 -0
  41. data/app/views/spree/api/cities/index.v1.rabl +11 -0
  42. data/app/views/spree/api/cities/show.v1.rabl +2 -0
  43. data/bin/rails +7 -0
  44. data/config/locales/en.yml +11 -0
  45. data/config/locales/pt-BR.yml +14 -0
  46. data/config/routes.rb +7 -0
  47. data/db/default/country.rb +3 -0
  48. data/db/default/shipping_categories.rb +1 -0
  49. data/db/default/shipping_zones.rb +3 -0
  50. data/db/default/states.rb +31 -0
  51. data/db/default/states_cities_of_acre.rb +26 -0
  52. data/db/default/states_cities_of_alagoas.rb +107 -0
  53. data/db/default/states_cities_of_amapa.rb +20 -0
  54. data/db/default/states_cities_of_amazonas.rb +67 -0
  55. data/db/default/states_cities_of_bahia.rb +421 -0
  56. data/db/default/states_cities_of_ceara.rb +188 -0
  57. data/db/default/states_cities_of_distrito_federal.rb +5 -0
  58. data/db/default/states_cities_of_espirito_santo.rb +82 -0
  59. data/db/default/states_cities_of_goias.rb +250 -0
  60. data/db/default/states_cities_of_maranhao.rb +221 -0
  61. data/db/default/states_cities_of_mato_grosso.rb +145 -0
  62. data/db/default/states_cities_of_mato_grosso_do_sul.rb +82 -0
  63. data/db/default/states_cities_of_minas_gerais.rb +857 -0
  64. data/db/default/states_cities_of_para.rb +147 -0
  65. data/db/default/states_cities_of_paraiba.rb +227 -0
  66. data/db/default/states_cities_of_parana.rb +403 -0
  67. data/db/default/states_cities_of_pernambuco.rb +189 -0
  68. data/db/default/states_cities_of_piaui.rb +227 -0
  69. data/db/default/states_cities_of_rio_de_janeiro.rb +96 -0
  70. data/db/default/states_cities_of_rio_grande_do_norte.rb +171 -0
  71. data/db/default/states_cities_of_rio_grande_do_sul.rb +500 -0
  72. data/db/default/states_cities_of_rondonia.rb +56 -0
  73. data/db/default/states_cities_of_roraima.rb +19 -0
  74. data/db/default/states_cities_of_santa_catarina.rb +297 -0
  75. data/db/default/states_cities_of_sao_paulo.rb +649 -0
  76. data/db/default/states_cities_of_sergipe.rb +79 -0
  77. data/db/default/states_cities_of_tocantins.rb +144 -0
  78. data/db/default/stock.rb +1 -0
  79. data/db/default/tax_categories.rb +1 -0
  80. data/db/default/user.rb +100 -0
  81. data/db/migrate/20150503011715_create_spree_cities.rb +13 -0
  82. data/db/migrate/20150504004509_remove_city_from_spree_address.rb +5 -0
  83. data/db/migrate/20150504004648_add_city_to_spree_address.rb +5 -0
  84. data/db/migrate/20150505020218_add_number_and_district_to_spree_address.rb +6 -0
  85. data/db/migrate/20150505031019_add_details_to_spree_user.rb +10 -0
  86. data/db/seeds.rb +5 -0
  87. data/lib/generators/solidus_br_common/install/install_generator.rb +31 -0
  88. data/lib/solidus_br_common/engine.rb +20 -0
  89. data/lib/solidus_br_common/factories/address_factory.rb +7 -0
  90. data/lib/solidus_br_common/factories/city_factory.rb +8 -0
  91. data/lib/solidus_br_common/factories/user_factory.rb +12 -0
  92. data/lib/solidus_br_common/factories.rb +3 -0
  93. data/lib/solidus_br_common.rb +2 -0
  94. data/solidus_br_common.gemspec +33 -0
  95. data/spec/controllers/spree/addresses_controller_spec.rb +27 -0
  96. data/spec/controllers/spree/api/cities_controller_spec.rb +91 -0
  97. data/spec/features/address_checkout_spec.rb +89 -0
  98. data/spec/features/user_account_spec.rb +46 -0
  99. data/spec/features/user_signup_spec.rb +24 -0
  100. data/spec/fixtures/vcr_cassettes/Checkout/visitor_makes_checkout_as_guest_without_registration/full_checkout/does_not_break_the_per-item_shipping_method_calculator.yml +49 -0
  101. data/spec/fixtures/vcr_cassettes/Checkout/visitor_makes_checkout_as_guest_without_registration/full_checkout/in_Brazil/should_autocomplete_zipcode.yml +46 -0
  102. data/spec/fixtures/vcr_cassettes/Spree_AddressesController/_show/does_not_find_an_incorrect_cep.yml +38 -0
  103. data/spec/fixtures/vcr_cassettes/Spree_AddressesController/_show/shows_the_correct_address.yml +46 -0
  104. data/spec/models/spree/address_spec.rb +32 -0
  105. data/spec/models/spree/city_spec.rb +7 -0
  106. data/spec/models/spree/user_spec.rb +88 -0
  107. data/spec/spec_helper.rb +61 -0
  108. data/spec/support/controller_hacks.rb +33 -0
  109. data/spec/support/database_cleaner.rb +24 -0
  110. data/spec/support/shared_context/checkout_setup.rb +9 -0
  111. data/spec/support/shoulda_matchers.rb +6 -0
  112. data/spec/support/vcr.rb +8 -0
  113. metadata +385 -0
@@ -0,0 +1,395 @@
1
+ /**
2
+ * jquery.mask.js
3
+ * @version: v1.6.5
4
+ * @author: Igor Escobar
5
+ *
6
+ * Created by Igor Escobar on 2012-03-10. Please report any bug at http://blog.igorescobar.com
7
+ *
8
+ * Copyright (c) 2012 Igor Escobar http://blog.igorescobar.com
9
+ *
10
+ * The MIT License (http://www.opensource.org/licenses/mit-license.php)
11
+ *
12
+ * Permission is hereby granted, free of charge, to any person
13
+ * obtaining a copy of this software and associated documentation
14
+ * files (the "Software"), to deal in the Software without
15
+ * restriction, including without limitation the rights to use,
16
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
17
+ * copies of the Software, and to permit persons to whom the
18
+ * Software is furnished to do so, subject to the following
19
+ * conditions:
20
+ *
21
+ * The above copyright notice and this permission notice shall be
22
+ * included in all copies or substantial portions of the Software.
23
+ *
24
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
25
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
26
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
27
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
28
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
29
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
30
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
31
+ * OTHER DEALINGS IN THE SOFTWARE.
32
+ */
33
+ /*jshint laxbreak: true */
34
+ /* global define */
35
+
36
+ // UMD (Universal Module Definition) patterns for JavaScript modules that work everywhere.
37
+ // https://github.com/umdjs/umd/blob/master/jqueryPlugin.js
38
+ (function (factory) {
39
+ if (typeof define === "function" && define.amd) {
40
+ // AMD. Register as an anonymous module.
41
+ define(["jquery"], factory);
42
+ } else {
43
+ // Browser globals
44
+ factory(window.jQuery || window.Zepto);
45
+ }
46
+ }(function ($) {
47
+ "use strict";
48
+ var Mask = function (el, mask, options) {
49
+ var jMask = this, old_value, regexMask;
50
+ el = $(el);
51
+
52
+ mask = typeof mask === "function" ? mask(el.val(), undefined, el, options) : mask;
53
+
54
+ jMask.init = function() {
55
+ options = options || {};
56
+
57
+ jMask.byPassKeys = [9, 16, 17, 18, 36, 37, 38, 39, 40, 91];
58
+ jMask.translation = {
59
+ '0': {pattern: /\d/},
60
+ '9': {pattern: /\d/, optional: true},
61
+ '#': {pattern: /\d/, recursive: true},
62
+ 'A': {pattern: /[a-zA-Z0-9]/},
63
+ 'S': {pattern: /[a-zA-Z]/}
64
+ };
65
+
66
+ jMask.translation = $.extend({}, jMask.translation, options.translation);
67
+ jMask = $.extend(true, {}, jMask, options);
68
+
69
+ regexMask = p.getRegexMask();
70
+
71
+ el.each(function() {
72
+ if (options.maxlength !== false) {
73
+ el.attr('maxlength', mask.length);
74
+ }
75
+
76
+ if (options.placeholder) {
77
+ el.attr('placeholder' , options.placeholder);
78
+ }
79
+
80
+ el.attr('autocomplete', 'off');
81
+ p.destroyEvents();
82
+ p.events();
83
+
84
+ var caret = p.getCaret();
85
+
86
+ p.val(p.getMasked());
87
+ p.setCaret(caret + p.getMaskCharactersBeforeCount(caret, true));
88
+ });
89
+
90
+
91
+ };
92
+
93
+ var p = {
94
+ getCaret: function () {
95
+ var sel,
96
+ pos = 0,
97
+ ctrl = el.get(0),
98
+ dSel = document.selection,
99
+ cSelStart = ctrl.selectionStart;
100
+
101
+ // IE Support
102
+ if (dSel && !~navigator.appVersion.indexOf("MSIE 10")) {
103
+ sel = dSel.createRange();
104
+ sel.moveStart('character', el.is("input") ? -el.val().length : -el.text().length);
105
+ pos = sel.text.length;
106
+ }
107
+ // Firefox support
108
+ else if (cSelStart || cSelStart === '0') {
109
+ pos = cSelStart;
110
+ }
111
+
112
+ return pos;
113
+ },
114
+ setCaret: function(pos) {
115
+ if (el.is(":focus")) {
116
+ var range, ctrl = el.get(0);
117
+
118
+ if (ctrl.setSelectionRange) {
119
+ ctrl.setSelectionRange(pos,pos);
120
+ } else if (ctrl.createTextRange) {
121
+ range = ctrl.createTextRange();
122
+ range.collapse(true);
123
+ range.moveEnd('character', pos);
124
+ range.moveStart('character', pos);
125
+ range.select();
126
+ }
127
+ }
128
+ },
129
+ events: function() {
130
+ el.on('keydown.mask', function() {
131
+ old_value = p.val();
132
+ });
133
+ el.on('keyup.mask', p.behaviour);
134
+ el.on("paste.mask drop.mask", function() {
135
+ setTimeout(function() {
136
+ el.keydown().keyup();
137
+ }, 100);
138
+ });
139
+ el.on("change.mask", function() {
140
+ el.data("changeCalled", true);
141
+ });
142
+ el.on("blur.mask", function(e){
143
+ var el = $(e.target);
144
+ if (el.prop("defaultValue") !== el.val()) {
145
+ el.prop("defaultValue", el.val());
146
+ if (!el.data("changeCalled")) {
147
+ el.trigger("change");
148
+ }
149
+ }
150
+ el.data("changeCalled", false);
151
+ });
152
+
153
+ // clear the value if it not complete the mask
154
+ el.on("focusout.mask", function() {
155
+ if (options.clearIfNotMatch && !regexMask.test(p.val())) {
156
+ p.val('');
157
+ }
158
+ });
159
+ },
160
+ getRegexMask: function() {
161
+ var maskChunks = [], translation, pattern, optional, recursive, oRecursive, r;
162
+
163
+ for (var i = 0; i < mask.length; i++) {
164
+ translation = jMask.translation[mask[i]];
165
+
166
+ if (translation) {
167
+
168
+ pattern = translation.pattern.toString().replace(/.{1}$|^.{1}/g, "");
169
+ optional = translation.optional;
170
+ recursive = translation.recursive;
171
+
172
+ if (recursive) {
173
+ maskChunks.push(mask[i]);
174
+ oRecursive = {digit: mask[i], pattern: pattern};
175
+ } else {
176
+ maskChunks.push((!optional && !recursive) ? pattern : (pattern + "?"));
177
+ }
178
+
179
+ } else {
180
+ maskChunks.push("\\" + mask[i]);
181
+ }
182
+ }
183
+
184
+ r = maskChunks.join("");
185
+
186
+ if (oRecursive) {
187
+ r = r.replace(new RegExp("(" + oRecursive.digit + "(.*" + oRecursive.digit + ")?)"), "($1)?")
188
+ .replace(new RegExp(oRecursive.digit, "g"), oRecursive.pattern);
189
+ }
190
+
191
+ return new RegExp(r);
192
+ },
193
+ destroyEvents: function() {
194
+ el.off('keydown.mask keyup.mask paste.mask drop.mask change.mask blur.mask focusout.mask').removeData("changeCalled");
195
+ },
196
+ val: function(v) {
197
+ var isInput = el.is('input');
198
+ return arguments.length > 0
199
+ ? (isInput ? el.val(v) : el.text(v))
200
+ : (isInput ? el.val() : el.text());
201
+ },
202
+ getMaskCharactersBeforeCount: function(index, onCleanVal) {
203
+ for (var count = 0, i = 0, maskL = mask.length; i < maskL && i < index; i++) {
204
+ if (!jMask.translation[mask.charAt(i)]) {
205
+ index = onCleanVal ? index + 1 : index;
206
+ count++;
207
+ }
208
+ }
209
+ return count;
210
+ },
211
+ determineCaretPos: function (originalCaretPos, oldLength, newLength, maskDif) {
212
+ var translation = jMask.translation[mask.charAt(Math.min(originalCaretPos - 1, mask.length - 1))];
213
+
214
+ return !translation ? p.determineCaretPos(originalCaretPos + 1, oldLength, newLength, maskDif)
215
+ : Math.min(originalCaretPos + newLength - oldLength - maskDif, newLength);
216
+ },
217
+ behaviour: function(e) {
218
+ e = e || window.event;
219
+ var keyCode = e.keyCode || e.which;
220
+
221
+ if ($.inArray(keyCode, jMask.byPassKeys) === -1) {
222
+
223
+ var caretPos = p.getCaret(),
224
+ currVal = p.val(),
225
+ currValL = currVal.length,
226
+ changeCaret = caretPos < currValL,
227
+ newVal = p.getMasked(),
228
+ newValL = newVal.length,
229
+ maskDif = p.getMaskCharactersBeforeCount(newValL - 1) - p.getMaskCharactersBeforeCount(currValL - 1);
230
+
231
+ if (newVal !== currVal) {
232
+ p.val(newVal);
233
+ }
234
+
235
+ // change caret but avoid CTRL+A
236
+ if (changeCaret && !(keyCode === 65 && e.ctrlKey)) {
237
+ // Avoid adjusting caret on backspace or delete
238
+ if (!(keyCode === 8 || keyCode === 46)) {
239
+ caretPos = p.determineCaretPos(caretPos, currValL, newValL, maskDif);
240
+ }
241
+ p.setCaret(caretPos);
242
+ }
243
+
244
+ return p.callbacks(e);
245
+ }
246
+ },
247
+ getMasked: function (skipMaskChars) {
248
+ var buf = [],
249
+ value = p.val(),
250
+ m = 0, maskLen = mask.length,
251
+ v = 0, valLen = value.length,
252
+ offset = 1, addMethod = "push",
253
+ resetPos = -1,
254
+ lastMaskChar,
255
+ check;
256
+
257
+ if (options.reverse) {
258
+ addMethod = "unshift";
259
+ offset = -1;
260
+ lastMaskChar = 0;
261
+ m = maskLen - 1;
262
+ v = valLen - 1;
263
+ check = function () {
264
+ return m > -1 && v > -1;
265
+ };
266
+ } else {
267
+ lastMaskChar = maskLen - 1;
268
+ check = function () {
269
+ return m < maskLen && v < valLen;
270
+ };
271
+ }
272
+
273
+ while (check()) {
274
+ var maskDigit = mask.charAt(m),
275
+ valDigit = value.charAt(v),
276
+ translation = jMask.translation[maskDigit];
277
+
278
+ if (translation) {
279
+ if (valDigit.match(translation.pattern)) {
280
+ buf[addMethod](valDigit);
281
+ if (translation.recursive) {
282
+ if (resetPos === -1) {
283
+ resetPos = m;
284
+ } else if (m === lastMaskChar) {
285
+ m = resetPos - offset;
286
+ }
287
+
288
+ if (lastMaskChar === resetPos) {
289
+ m -= offset;
290
+ }
291
+ }
292
+ m += offset;
293
+ } else if (translation.optional) {
294
+ m += offset;
295
+ v -= offset;
296
+ }
297
+ v += offset;
298
+ } else {
299
+ if (!skipMaskChars) {
300
+ buf[addMethod](maskDigit);
301
+ }
302
+
303
+ if (valDigit === maskDigit) {
304
+ v += offset;
305
+ }
306
+
307
+ m += offset;
308
+ }
309
+ }
310
+
311
+ var lastMaskCharDigit = mask.charAt(lastMaskChar);
312
+ if (maskLen === valLen + 1 && !jMask.translation[lastMaskCharDigit]) {
313
+ buf.push(lastMaskCharDigit);
314
+ }
315
+
316
+ return buf.join("");
317
+ },
318
+ callbacks: function (e) {
319
+ var val = p.val(),
320
+ changed = p.val() !== old_value;
321
+ if (changed === true) {
322
+ if (typeof options.onChange === "function") {
323
+ options.onChange(val, e, el, options);
324
+ }
325
+ }
326
+
327
+ if (changed === true && typeof options.onKeyPress === "function") {
328
+ options.onKeyPress(val, e, el, options);
329
+ }
330
+
331
+ if (typeof options.onComplete === "function" && val.length === mask.length) {
332
+ options.onComplete(val, e, el, options);
333
+ }
334
+ }
335
+ };
336
+
337
+ // public methods
338
+ jMask.remove = function() {
339
+ var caret = p.getCaret(),
340
+ maskedCharacterCountBefore = p.getMaskCharactersBeforeCount(caret);
341
+
342
+ p.destroyEvents();
343
+ p.val(jMask.getCleanVal()).removeAttr('maxlength');
344
+ p.setCaret(caret - maskedCharacterCountBefore);
345
+ };
346
+
347
+ // get value without mask
348
+ jMask.getCleanVal = function() {
349
+ return p.getMasked(true);
350
+ };
351
+
352
+ jMask.init();
353
+ };
354
+
355
+ $.fn.mask = function(mask, options) {
356
+ this.unmask();
357
+ return this.each(function() {
358
+ $(this).data('mask', new Mask(this, mask, options));
359
+ });
360
+ };
361
+
362
+ $.fn.unmask = function() {
363
+ return this.each(function() {
364
+ try {
365
+ $(this).data('mask').remove();
366
+ } catch (e) {}
367
+ });
368
+ };
369
+
370
+ $.fn.cleanVal = function() {
371
+ return $(this).data('mask').getCleanVal();
372
+ };
373
+
374
+ // looking for inputs with data-mask attribute
375
+ $('*[data-mask]').each(function() {
376
+ var input = $(this),
377
+ options = {},
378
+ prefix = "data-mask-";
379
+
380
+ if (input.attr(prefix + 'reverse') === 'true') {
381
+ options.reverse = true;
382
+ }
383
+
384
+ if (input.attr(prefix + 'maxlength') === 'false') {
385
+ options.maxlength = false;
386
+ }
387
+
388
+ if (input.attr(prefix + 'clearifnotmatch') === 'true') {
389
+ options.clearIfNotMatch = true;
390
+ }
391
+
392
+ input.mask(input.attr('data-mask'), options);
393
+ });
394
+
395
+ }));
@@ -0,0 +1,4 @@
1
+ Spree.routes.cities_search = Spree.pathFor('api/cities');
2
+ Spree.routes.address_show = function (cep) {
3
+ return Spree.pathFor('addresses/' + cep);
4
+ };
@@ -0,0 +1 @@
1
+ //= require_tree .
@@ -0,0 +1,4 @@
1
+ /*
2
+ Placeholder manifest file.
3
+ the installer will append this file to the app vendored assets here: 'vendor/assets/stylesheets/spree/backend/all.css'
4
+ */
@@ -0,0 +1,4 @@
1
+ /*
2
+ Placeholder manifest file.
3
+ the installer will append this file to the app vendored assets here: 'vendor/assets/stylesheets/spree/frontend/all.css'
4
+ */
@@ -0,0 +1,24 @@
1
+ require 'rest-client'
2
+
3
+ module Spree
4
+ class AddressesController < Spree::BaseController
5
+ def show
6
+ respond_to do |format|
7
+ format.json { render json: JSON.parse(find_address) }
8
+ end
9
+ rescue ::RestClient::ResourceNotFound
10
+ respond_to do |format|
11
+ format.json { render json: nil, status: :not_found }
12
+ end
13
+ end
14
+
15
+ private
16
+ def find_address
17
+ ::RestClient.get "http://api.postmon.com.br/v1/cep/#{permitted_params}"
18
+ end
19
+
20
+ def permitted_params
21
+ params.require(:cep)
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,33 @@
1
+ module Spree
2
+ module Api
3
+ class CitiesController < Spree::Api::BaseController
4
+ skip_before_action :authenticate_user
5
+
6
+ def index
7
+ @cities = scope.ransack(params[:q]).result.
8
+ includes(:state).order(name: :asc).
9
+ page(params[:page]).per(params[:per_page])
10
+
11
+ city = City.order(updated_at: :asc).last
12
+ if stale?(city)
13
+ respond_with(@cities)
14
+ end
15
+ end
16
+
17
+ def show
18
+ @city = scope.find(params[:id])
19
+ respond_with(@city)
20
+ end
21
+
22
+ private
23
+ def scope
24
+ if params[:state_id]
25
+ @state = State.accessible_by(current_ability, :read).find(params[:state_id])
26
+ return @state.cities.accessible_by(current_ability, :read)
27
+ else
28
+ return City.accessible_by(current_ability, :read)
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,13 @@
1
+ Spree::UserRegistrationsController.class_eval do
2
+ include Spree::BaseHelper
3
+ before_action :parse_date_of_birth, :only => [:create, :update]
4
+
5
+ protected
6
+
7
+ def parse_date_of_birth
8
+ params[:spree_user][:date_of_birth] = DateTime.strptime(
9
+ params[:spree_user][:date_of_birth],
10
+ Spree.t('date_picker.format', default: '%Y/%m/%d')
11
+ ) rescue ''
12
+ end
13
+ end
@@ -0,0 +1,9 @@
1
+ module Spree
2
+ module Api
3
+ module ApiHelpers
4
+ mattr_reader :city_attributes
5
+
6
+ @@city_attributes = [:id, :name, :ibge_code, :state_id]
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,5 @@
1
+ Spree::BaseHelper.class_eval do
2
+ def datepicker_value(model, field)
3
+ l(model[field], :format => Spree.t('date_picker.format', default: '%Y/%m/%d')) if model && model.respond_to?(field) && model[field]
4
+ end
5
+ end
@@ -0,0 +1,21 @@
1
+ require 'cancan'
2
+
3
+ module Spree
4
+ class AbilityDecorator
5
+ include CanCan::Ability
6
+
7
+ def initialize(user)
8
+ user ||= Spree.user_class.new
9
+
10
+ if user.respond_to?(:has_spree_role?) && user.has_spree_role?('admin')
11
+ can :manage, :all
12
+ else
13
+ can [:index, :read], City
14
+ end
15
+
16
+ end
17
+
18
+ end
19
+ end
20
+
21
+ Spree::Ability.register_ability(Spree::AbilityDecorator)
@@ -0,0 +1,6 @@
1
+ Spree::Address.class_eval do
2
+ belongs_to :city, class_name: 'Spree::City'
3
+
4
+ validates :number, :numericality => {greater_than: 0}, :presence => true
5
+ validates :district, length: { maximum: 150}, presence: true
6
+ end
@@ -0,0 +1,7 @@
1
+ module Spree
2
+ class City < ActiveRecord::Base
3
+ belongs_to :state, class_name: 'Spree::State'
4
+
5
+ validates_presence_of :state, :ibge_code, :name
6
+ end
7
+ end
@@ -0,0 +1,8 @@
1
+ module Spree
2
+ module PermittedAttributes
3
+ class << self
4
+ @@address_attributes.push :city_id, :district, :number
5
+ @@user_attributes.push :first_name, :last_name, :cpf, :phone, :alternative_phone, :date_of_birth
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,3 @@
1
+ Spree::State.class_eval do
2
+ has_many :cities, -> { order('name ASC') }, dependent: :destroy
3
+ end
@@ -0,0 +1,52 @@
1
+ require 'cpf_cnpj'
2
+ require 'validates_timeliness'
3
+
4
+ Spree::User.class_eval do
5
+
6
+ validates_presence_of :phone
7
+ validates :date_of_birth,
8
+ presence: true,
9
+ timeliness: {
10
+ on_or_before: lambda { 18.years.ago.at_end_of_day },
11
+ on_or_after: lambda { 100.years.ago.at_midnight },
12
+ type: :datetime
13
+ }
14
+ validates :cpf, presence: true
15
+ validates :first_name, :last_name, presence: true, length: {maximum: 100}
16
+ validates :phone, :alternative_phone, length: {maximum: 11}
17
+ validate :valid_cpf
18
+
19
+ before_validation :sanitize_fields
20
+
21
+ def full_name
22
+ "#{first_name} #{last_name}"
23
+ end
24
+
25
+ def cpf_formatted
26
+ CPF::Formatter.format(cpf)
27
+ end
28
+
29
+ def phone_area_code
30
+ phone[0..1] if phone
31
+ end
32
+
33
+ def phone_number
34
+ phone[2..-1] if phone
35
+ end
36
+
37
+ private
38
+
39
+ def sanitize(*fields)
40
+ fields.each do |field|
41
+ self[field] = self[field].to_s.gsub(/[^\d]/, '') if self.respond_to?(field)
42
+ end
43
+ end
44
+
45
+ def sanitize_fields
46
+ sanitize(:cpf, :phone, :alternative_phone)
47
+ end
48
+
49
+ def valid_cpf
50
+ errors.add(:cpf, Spree.t(:invalid)) unless CPF.valid?(cpf)
51
+ end
52
+ end
@@ -0,0 +1,5 @@
1
+ <!-- insert_after '[data-erb-id*="address2"]' -->
2
+ <p class="form-group" id=<%="#{address_id}district" %>>
3
+ <%= form.label :district, Spree.t(:district) %><span class="required">*</span><br />
4
+ <%= form.text_field :district, class: 'form-control required ajax-loading' %>
5
+ </p>
@@ -0,0 +1,5 @@
1
+ <!-- insert_before '[data-erb-id*="address2"]' -->
2
+ <p class="form-group" id=<%="#{address_id}number" %>>
3
+ <%= form.label :number, Spree.t(:number) %><span class="required">*</span><br />
4
+ <%= form.number_field :number, class: 'form-control required ajax-loading' %>
5
+ </p>
@@ -0,0 +1,4 @@
1
+ <!-- replace 'p[data-erb-id*="country"]' -->
2
+ <p class="form-group" id=<%="#{address_id}country" %>>
3
+ <%= form.hidden_field :country_id %>
4
+ </p>
@@ -0,0 +1,2 @@
1
+ cut 'p[data-erb-id*="city"]'
2
+ insert_after 'p[data-erb-id*="state"]'
@@ -0,0 +1,2 @@
1
+ cut 'p[data-erb-id*="zipcode"]'
2
+ insert_after 'p[data-erb-id*="lastname"]'
@@ -0,0 +1,18 @@
1
+ <!-- replace 'p[data-erb-id*="city"]' -->
2
+ <p class="form-group" id=<%="#{address_id}city" %>>
3
+ <%= form.label :city, Spree.t(:city) %><span class="required">*</span><br />
4
+ <%= form.collection_select(
5
+ :city_id, address.state ? address.state.cities : [],
6
+ :id, :name,
7
+ {:include_blank => false},
8
+ {:class => 'form-control required'}
9
+ ) %>
10
+
11
+ <% unless address.state %>
12
+ <script>
13
+ $(function() {
14
+ Spree.updateCity('b');
15
+ });
16
+ </script>
17
+ <% end %>
18
+ </p>
@@ -0,0 +1,15 @@
1
+ <!-- replace 'erb:contains("state_elements = [")' -->
2
+ <% states = address.country.states.map{ |s| [s.name, s.id, {'data-uf'=>s.abbr}]} %>
3
+ <% state_elements = [
4
+ form.select(
5
+ :state_id,
6
+ options_for_select(states, address.state ? address.state.id: nil),
7
+ {:include_blank => false},
8
+ {:class => 'ajax-loading form-control ' + (have_states ? 'required' : 'hidden'),
9
+ :disabled => !have_states}
10
+ ) +
11
+ form.text_field(:state_name,
12
+ :class => !have_states ? 'form-control required' : 'form-control hidden',
13
+ :disabled => have_states)
14
+ ].join.gsub('"', "'").gsub("\n", "")
15
+ %>