mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-01-14 18:56:49 +00:00
Compare commits
131 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
be19d50639 | ||
|
|
0ceb8ab6c4 | ||
|
|
e387c7db83 | ||
|
|
d5df48f3c0 | ||
|
|
c9abdac2e0 | ||
|
|
ff08d9f210 | ||
|
|
9313a57d19 | ||
|
|
b0f2e01c70 | ||
|
|
747be81aec | ||
|
|
fd124daf50 | ||
|
|
ef33d27e6c | ||
|
|
3a01e00d7b | ||
|
|
81103f3f71 | ||
|
|
a8b48a561f | ||
|
|
c7038f6ac9 | ||
|
|
ea75714561 | ||
|
|
b7d19dd34c | ||
|
|
44d1b3f00c | ||
|
|
f8376c0aeb | ||
|
|
88464d58c2 | ||
|
|
962779bba1 | ||
|
|
1ae73dd6ae | ||
|
|
f48ab57782 | ||
|
|
8b6ebfb351 | ||
|
|
9c0788c3e0 | ||
|
|
f61258d0b4 | ||
|
|
289f62f115 | ||
|
|
c3a98d5a4f | ||
|
|
d8374e9caa | ||
|
|
0648f23e94 | ||
|
|
e37cb2d63f | ||
|
|
ee64238bdd | ||
|
|
d3130e111f | ||
|
|
93f4eee887 | ||
|
|
408ada9097 | ||
|
|
e14ebd9503 | ||
|
|
289b99c30e | ||
|
|
f85c36a17e | ||
|
|
4715df3258 | ||
|
|
b20be101cc | ||
|
|
d424987587 | ||
|
|
a0575430dd | ||
|
|
c1e6344b18 | ||
|
|
ed0198382f | ||
|
|
577fb88843 | ||
|
|
2b879221b2 | ||
|
|
22db2e99f0 | ||
|
|
ef4aa488c3 | ||
|
|
57775f49bc | ||
|
|
e8a12d9897 | ||
|
|
f3b8d5b868 | ||
|
|
246235b921 | ||
|
|
0ded41afee | ||
|
|
d32e106bf0 | ||
|
|
bbb3748d3c | ||
|
|
08dd992344 | ||
|
|
914244a1ee | ||
|
|
e5f089610c | ||
|
|
6e26841817 | ||
|
|
ab60c4a9dd | ||
|
|
7009cd89e0 | ||
|
|
b93af37ea9 | ||
|
|
76b6a85509 | ||
|
|
a1d4b4ee98 | ||
|
|
51bca7ce2f | ||
|
|
70147f908a | ||
|
|
c4bf4f001f | ||
|
|
7d71f21753 | ||
|
|
285c78a5e4 | ||
|
|
9a2ad16926 | ||
|
|
afec21eb3d | ||
|
|
651ee720c6 | ||
|
|
2e6c5e1fad | ||
|
|
87366ae7fc | ||
|
|
27aea0b277 | ||
|
|
d4edc9f20d | ||
|
|
6c7991be75 | ||
|
|
3f81352df5 | ||
|
|
9d0e26ae28 | ||
|
|
d80554a14a | ||
|
|
a5fe5fb448 | ||
|
|
4c51d60bfd | ||
|
|
9218008530 | ||
|
|
f36c5b8938 | ||
|
|
67199fd2d6 | ||
|
|
18d17ec674 | ||
|
|
3981ee7ec1 | ||
|
|
a0475ee8a4 | ||
|
|
4cdc604f45 | ||
|
|
dbf44c41b2 | ||
|
|
25c4aed368 | ||
|
|
c5a6ef673c | ||
|
|
79ba15fe9a | ||
|
|
e192207f4e | ||
|
|
baf1ecb436 | ||
|
|
947914724a | ||
|
|
b5004f1cbf | ||
|
|
4596399bc2 | ||
|
|
685abccb61 | ||
|
|
9254928656 | ||
|
|
228997c35b | ||
|
|
667f44336d | ||
|
|
0a136ff2fb | ||
|
|
3f3577e73c | ||
|
|
cf1664bed3 | ||
|
|
b8aee4e857 | ||
|
|
cfe3435851 | ||
|
|
53e342ba1a | ||
|
|
6bdb14248c | ||
|
|
8e27291b15 | ||
|
|
ec67736dff | ||
|
|
766303b332 | ||
|
|
74226fbdf8 | ||
|
|
dc5374e284 | ||
|
|
f6ecf57737 | ||
|
|
940953b043 | ||
|
|
fbc5887fa6 | ||
|
|
61ce849546 | ||
|
|
afddaed9fc | ||
|
|
12158d73fa | ||
|
|
5f3abbf00e | ||
|
|
a02c58e231 | ||
|
|
59ebfb9bd4 | ||
|
|
57ca1d54bb | ||
|
|
138248e1c9 | ||
|
|
5fccd5fe58 | ||
|
|
4658b7a533 | ||
|
|
d0f33e7c8a | ||
|
|
55bb328d48 | ||
|
|
a8a6fce385 | ||
|
|
60677a2414 |
@@ -2,7 +2,7 @@ version: "2"
|
||||
plugins:
|
||||
rubocop:
|
||||
enabled: true
|
||||
channel: "rubocop-0-57"
|
||||
channel: "rubocop-0-76"
|
||||
config:
|
||||
file: ".rubocop_styleguide.yml"
|
||||
scss-lint:
|
||||
|
||||
7
.github/ISSUE_TEMPLATE/release.md
vendored
7
.github/ISSUE_TEMPLATE/release.md
vendored
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: Release task
|
||||
about: Track the process of a new release
|
||||
title: ''
|
||||
title: 'Release v'
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
@@ -10,10 +10,13 @@ assignees: ''
|
||||
Steps:
|
||||
|
||||
- [ ] Include translations
|
||||
- [ ] Draft: https://github.com/openfoodfoundation/openfoodnetwork/releases/new <!-- replace the URL -->
|
||||
- [ ] [Draft new release]
|
||||
- [ ] Notify #instance-managers of user-facing changes.
|
||||
- [ ] Test: https://semaphoreci.com/openfoodfoundation/openfoodnetwork-2/branches/master <!-- replace the URL -->
|
||||
- [ ] Publish and notify #global-community
|
||||
- [ ] Deploy and notify #instance-managers
|
||||
- [ ] Nudge next release manager
|
||||
|
||||
The full process is described at https://github.com/openfoodfoundation/openfoodnetwork/wiki/Releasing.
|
||||
|
||||
[Draft new release]: https://github.com/openfoodfoundation/openfoodnetwork/releases/new?tag=v&title=v+Code+Name&body=Congrats%0A%0ADescription%0A%0A%23%23+User+facing+changes+:eyes:%0A%0A%0A%0A%23%23+Technical+changes+:wrench:%0A%0A
|
||||
|
||||
@@ -39,11 +39,9 @@ Layout/LineLength:
|
||||
- app/controllers/admin/variant_overrides_controller.rb
|
||||
- app/controllers/api/enterprise_attachment_controller.rb
|
||||
- app/controllers/api/product_images_controller.rb
|
||||
- app/controllers/application_controller.rb
|
||||
- app/controllers/checkout_controller.rb
|
||||
- app/controllers/spree/admin/adjustments_controller_decorator.rb
|
||||
- app/controllers/spree/admin/orders_controller_decorator.rb
|
||||
- app/controllers/spree/admin/payments_controller_decorator.rb
|
||||
- app/controllers/spree/credit_cards_controller.rb
|
||||
- app/controllers/spree/paypal_controller_decorator.rb
|
||||
- app/controllers/stripe/callbacks_controller.rb
|
||||
@@ -361,15 +359,15 @@ Metrics/AbcSize:
|
||||
- app/controllers/enterprises_controller.rb
|
||||
- app/controllers/spree/admin/adjustments_controller_decorator.rb
|
||||
- app/controllers/spree/admin/image_settings_controller.rb
|
||||
- app/controllers/spree/admin/orders/customer_details_controller_decorator.rb
|
||||
- app/controllers/spree/admin/orders_controller_decorator.rb
|
||||
- app/controllers/spree/admin/orders/customer_details_controller.rb
|
||||
- app/controllers/spree/admin/orders_controller.rb
|
||||
- app/controllers/spree/admin/overview_controller.rb
|
||||
- app/controllers/spree/admin/payment_methods_controller.rb
|
||||
- app/controllers/spree/admin/payments_controller_decorator.rb
|
||||
- app/controllers/spree/admin/payments_controller.rb
|
||||
- app/controllers/spree/admin/products_controller.rb
|
||||
- app/controllers/spree/admin/reports_controller.rb
|
||||
- app/controllers/spree/admin/resource_controller.rb
|
||||
- app/controllers/spree/admin/products_controller.rb
|
||||
- app/controllers/spree/admin/search_controller_decorator.rb
|
||||
- app/controllers/spree/admin/search_controller.rb
|
||||
- app/controllers/spree/admin/taxons_controller.rb
|
||||
- app/controllers/spree/admin/users_controller.rb
|
||||
- app/controllers/spree/admin/variants_controller.rb
|
||||
@@ -496,7 +494,6 @@ Metrics/CyclomaticComplexity:
|
||||
- app/controllers/admin/enterprise_fees_controller.rb
|
||||
- app/controllers/admin/enterprises_controller.rb
|
||||
- app/controllers/checkout_controller.rb
|
||||
- app/controllers/spree/admin/payments_controller_decorator.rb
|
||||
- app/controllers/spree/admin/taxons_controller.rb
|
||||
- app/controllers/spree/orders_controller.rb
|
||||
- app/helpers/checkout_helper.rb
|
||||
@@ -527,7 +524,6 @@ Metrics/PerceivedComplexity:
|
||||
- app/controllers/admin/enterprises_controller.rb
|
||||
- app/controllers/api/variants_controller.rb
|
||||
- app/controllers/checkout_controller.rb
|
||||
- app/controllers/spree/admin/payments_controller_decorator.rb
|
||||
- app/controllers/spree/admin/taxons_controller.rb
|
||||
- app/controllers/spree/orders_controller.rb
|
||||
- app/helpers/checkout_helper.rb
|
||||
@@ -567,13 +563,13 @@ Metrics/MethodLength:
|
||||
- app/controllers/checkout_controller.rb
|
||||
- app/controllers/shop_controller.rb
|
||||
- app/controllers/spree/admin/image_settings_controller.rb
|
||||
- app/controllers/spree/admin/orders/customer_details_controller_decorator.rb
|
||||
- app/controllers/spree/admin/orders/customer_details_controller.rb
|
||||
- app/controllers/spree/admin/orders_controller.rb
|
||||
- app/controllers/spree/admin/payment_methods_controller.rb
|
||||
- app/controllers/spree/admin/payments_controller_decorator.rb
|
||||
- app/controllers/spree/admin/payments_controller.rb
|
||||
- app/controllers/spree/admin/products_controller.rb
|
||||
- app/controllers/spree/admin/reports_controller.rb
|
||||
- app/controllers/spree/admin/resource_controller.rb
|
||||
- app/controllers/spree/admin/products_controller.rb
|
||||
- app/controllers/spree/admin/search_controller_decorator.rb
|
||||
- app/controllers/spree/admin/tax_categories_controller.rb
|
||||
- app/controllers/spree/admin/taxons_controller.rb
|
||||
- app/controllers/spree/admin/users_controller.rb
|
||||
@@ -652,8 +648,10 @@ Metrics/ClassLength:
|
||||
- app/controllers/admin/order_cycles_controller.rb
|
||||
- app/controllers/admin/subscriptions_controller.rb
|
||||
- app/controllers/api/products_controller.rb
|
||||
- app/controllers/application_controller.rb
|
||||
- app/controllers/checkout_controller.rb
|
||||
- app/controllers/spree/admin/base_controller.rb
|
||||
- app/controllers/spree/admin/orders_controller.rb
|
||||
- app/controllers/spree/admin/payment_methods_controller.rb
|
||||
- app/controllers/spree/admin/reports_controller.rb
|
||||
- app/controllers/spree/admin/resource_controller.rb
|
||||
|
||||
@@ -335,6 +335,7 @@ Rails/OutputSafety:
|
||||
- 'app/helpers/spree/admin/zones_helper.rb'
|
||||
- 'app/helpers/spree/reports_helper.rb'
|
||||
- 'app/helpers/spree/admin/navigation_helper.rb'
|
||||
- 'app/helpers/spree/admin/orders_helper.rb'
|
||||
- 'app/serializers/api/product_serializer.rb'
|
||||
- 'lib/spree/money_decorator.rb'
|
||||
- 'spec/features/admin/orders_spec.rb'
|
||||
|
||||
1
Gemfile
1
Gemfile
@@ -94,7 +94,6 @@ gem 'wkhtmltopdf-binary'
|
||||
gem 'foreigner'
|
||||
gem 'immigrant'
|
||||
gem 'roo', '~> 2.8.2'
|
||||
gem 'roo-xls', '~> 1.1.0'
|
||||
|
||||
gem 'whenever', require: false
|
||||
|
||||
|
||||
14
Gemfile.lock
14
Gemfile.lock
@@ -166,7 +166,7 @@ GEM
|
||||
bcrypt-ruby (3.1.5)
|
||||
bcrypt (>= 3.1.3)
|
||||
blockenspiel (0.5.0)
|
||||
bugsnag (6.12.2)
|
||||
bugsnag (6.13.0)
|
||||
concurrent-ruby (~> 1.0)
|
||||
builder (3.0.4)
|
||||
byebug (9.0.6)
|
||||
@@ -492,7 +492,7 @@ GEM
|
||||
multi_json (~> 1.3)
|
||||
multi_xml (~> 0.5)
|
||||
rack (>= 1.2, < 3)
|
||||
oj (3.10.1)
|
||||
oj (3.10.2)
|
||||
orm_adapter (0.5.0)
|
||||
paper_trail (5.2.3)
|
||||
activerecord (>= 3.0, < 6.0)
|
||||
@@ -530,7 +530,7 @@ GEM
|
||||
rack (1.4.7)
|
||||
rack-cache (1.9.0)
|
||||
rack (>= 0.4)
|
||||
rack-mini-profiler (1.1.4)
|
||||
rack-mini-profiler (1.1.6)
|
||||
rack (>= 1.2.0)
|
||||
rack-protection (1.5.5)
|
||||
rack
|
||||
@@ -587,10 +587,6 @@ GEM
|
||||
roo (2.8.2)
|
||||
nokogiri (~> 1)
|
||||
rubyzip (>= 1.2.1, < 2.0.0)
|
||||
roo-xls (1.1.0)
|
||||
nokogiri
|
||||
roo (>= 2.0.0beta1, < 3)
|
||||
spreadsheet (> 0.9.0)
|
||||
rspec (3.9.0)
|
||||
rspec-core (~> 3.9.0)
|
||||
rspec-expectations (~> 3.9.0)
|
||||
@@ -624,7 +620,6 @@ GEM
|
||||
rubocop-rails (2.4.2)
|
||||
rack (>= 1.1)
|
||||
rubocop (>= 0.72.0)
|
||||
ruby-ole (1.2.12.1)
|
||||
ruby-progressbar (1.10.1)
|
||||
ruby-rc4 (0.1.5)
|
||||
rubyzip (1.3.0)
|
||||
@@ -653,8 +648,6 @@ GEM
|
||||
tilt (>= 1.3, < 3)
|
||||
spinjs-rails (1.4)
|
||||
rails (>= 3.1)
|
||||
spreadsheet (1.1.7)
|
||||
ruby-ole (>= 1.0)
|
||||
spring (1.7.2)
|
||||
spring-commands-rspec (1.0.4)
|
||||
spring (>= 0.9.1)
|
||||
@@ -791,7 +784,6 @@ DEPENDENCIES
|
||||
redcarpet
|
||||
roadie-rails (~> 1.3.0)
|
||||
roo (~> 2.8.2)
|
||||
roo-xls (~> 1.1.0)
|
||||
rspec-rails (>= 3.5.2)
|
||||
rspec-retry
|
||||
rubocop
|
||||
|
||||
@@ -12,6 +12,10 @@
|
||||
//= require jquery.ui.all
|
||||
//= require jquery-ui-timepicker-addon
|
||||
//= require jquery.powertip
|
||||
//= require jquery.cookie
|
||||
//= require jquery.jstree/jquery.jstree
|
||||
//= require jquery.vAlign
|
||||
//= require jquery.horizontalNav
|
||||
//= require angular
|
||||
//= require angular-resource
|
||||
//= require angular-animate
|
||||
@@ -20,13 +24,20 @@
|
||||
//= require ../shared/ng-infinite-scroll.min.js
|
||||
//= require ../shared/ng-tags-input.min.js
|
||||
//= require angular-rails-templates
|
||||
//= require lodash.underscore.js
|
||||
|
||||
// spree
|
||||
//= require spree
|
||||
//= require admin/spree-select2
|
||||
//= require admin/spree_backend
|
||||
//= require modernizr
|
||||
//= require spin
|
||||
//= require jquery.adaptivemenu
|
||||
//= require equalize
|
||||
//= require css_browser_selector_dev
|
||||
//= require responsive-tables
|
||||
//= require admin/spree_paypal_express
|
||||
//= require admin/handlebar_extensions
|
||||
|
||||
// OFN specific
|
||||
//= require_tree ../templates/admin
|
||||
|
||||
@@ -8,7 +8,7 @@ angular.module('admin.orderCycles').factory('ExchangeProduct', ($resource) ->
|
||||
|
||||
index: (params={}, callback=null) ->
|
||||
ExchangeProductResource.index params, (data) =>
|
||||
(callback || angular.noop)(data.products, data.pagination.pages, data.pagination.results)
|
||||
(callback || angular.noop)(data.products, data.pagination?.pages, data.pagination?.results)
|
||||
|
||||
countVariants: (params={}, callback=null) ->
|
||||
ExchangeProductResource.variant_count params, (data) =>
|
||||
|
||||
228
app/assets/javascripts/admin/spree/base.js.erb
Normal file
228
app/assets/javascripts/admin/spree/base.js.erb
Normal file
@@ -0,0 +1,228 @@
|
||||
//= require_self
|
||||
//= require admin/handlebar_extensions
|
||||
//= require admin/variant_autocomplete
|
||||
|
||||
/**
|
||||
This is a collection of javascript functions and whatnot
|
||||
under the spree namespace that do stuff we find helpful.
|
||||
Hopefully, this will evolve into a propper class.
|
||||
**/
|
||||
|
||||
jQuery(function($) {
|
||||
// Make main menu use full width
|
||||
mainMenu = $('.fullwidth-menu')
|
||||
if (typeof mainMenu.horizontalNav === 'function' )
|
||||
mainMenu.horizontalNav({
|
||||
tableDisplay: false,
|
||||
responsiveDelay: 0
|
||||
});
|
||||
|
||||
// Vertical align of checkbox fields
|
||||
if (typeof $('.field.checkbox label').vAlign === 'function' )
|
||||
$('.field.checkbox label').vAlign()
|
||||
|
||||
// if (typeof Spree !== 'undefined') {
|
||||
// $('.main-menu-wrapper ul').AdaptiveMenu({
|
||||
// text: "<a href='#'><i class='icon-chevron-down'></i> " + Spree.translations.more + "</a>",
|
||||
// klass: "dropdown"
|
||||
// });
|
||||
// }
|
||||
|
||||
// Add some tips
|
||||
if (typeof $('.with-tip').powerTip === 'function' ) {
|
||||
$('.with-tip').powerTip({
|
||||
smartPlacement: true,
|
||||
fadeInTime: 50,
|
||||
fadeOutTime: 50,
|
||||
intentPollInterval: 300
|
||||
});
|
||||
|
||||
$('.with-tip').on({
|
||||
powerTipPreRender: function(){
|
||||
$('#powerTip').addClass($(this).attr("data-action"));
|
||||
$('#powerTip').addClass($(this).attr("data-tip-color"));
|
||||
},
|
||||
powerTipClose: function(){
|
||||
$('#powerTip').removeClass($(this).attr("data-action"))
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Make flash messages dissapear
|
||||
setTimeout('$(".flash").fadeOut()', 5000);
|
||||
|
||||
// Highlight hovered table column
|
||||
$('table tbody tr td.actions a').hover(function(){
|
||||
var tr = $(this).closest('tr');
|
||||
var klass = 'highlight action-' + $(this).attr('data-action')
|
||||
tr.addClass(klass)
|
||||
tr.prev().addClass('before-' + klass);
|
||||
}, function(){
|
||||
var tr = $(this).closest('tr');
|
||||
var klass = 'highlight action-' + $(this).attr('data-action')
|
||||
tr.removeClass(klass)
|
||||
tr.prev().removeClass('before-' + klass);
|
||||
});
|
||||
|
||||
// Trunkate text in page_title that didn't fit
|
||||
var truncate_elements = $('.truncate');
|
||||
|
||||
truncate_elements.each(function(){
|
||||
$(this).trunk8();
|
||||
});
|
||||
$(window).resize(function (event) {
|
||||
truncate_elements.each(function(){
|
||||
$(this).trunk8();
|
||||
})
|
||||
});
|
||||
|
||||
// Make height of dt/dd elements the same
|
||||
if (typeof $("dl").equalize === 'function' )
|
||||
$("dl").equalize('outerHeight');
|
||||
});
|
||||
|
||||
|
||||
$.fn.visible = function(cond) { this[cond ? 'show' : 'hide' ]() };
|
||||
|
||||
// Overriding a broken function in Spree. Bug report at
|
||||
// https://github.com/spree/spree/issues/4032
|
||||
show_flash_error = function(message) {
|
||||
error_div = $('.flash.error');
|
||||
if (error_div.length > 0) {
|
||||
error_div.html(message);
|
||||
error_div.show();
|
||||
} else {
|
||||
if ($("#content .toolbar").length > 0) {
|
||||
$("#content .toolbar").before('<div class="flash error">' + message + '</div>');
|
||||
} else {
|
||||
$("#progress").before('<div class="flash error">' + message + '</div>');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Apply to individual radio button that makes another element visible when checked
|
||||
$.fn.radioControlsVisibilityOfElement = function(dependentElementSelector){
|
||||
if(!this.get(0)){ return }
|
||||
showValue = this.get(0).value;
|
||||
radioGroup = $("input[name='" + this.get(0).name + "']");
|
||||
radioGroup.each(function(){
|
||||
$(this).click(function(){
|
||||
$(dependentElementSelector).visible(this.checked && this.value == showValue)
|
||||
});
|
||||
if(this.checked){ this.click() }
|
||||
});
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
if (typeof Spree !== 'undefined') {
|
||||
handle_date_picker_fields = function(){
|
||||
$('.datepicker').datepicker({
|
||||
dateFormat: Spree.translations.date_picker,
|
||||
dayNames: Spree.translations.abbr_day_names,
|
||||
dayNamesMin: Spree.translations.abbr_day_names,
|
||||
monthNames: Spree.translations.month_names,
|
||||
prevText: Spree.translations.previous,
|
||||
nextText: Spree.translations.next,
|
||||
showOn: "focus"
|
||||
});
|
||||
|
||||
// Correctly display range dates
|
||||
$('.date-range-filter .datepicker-from').datepicker('option', 'onSelect', function(selectedDate) {
|
||||
$(".date-range-filter .datepicker-to" ).datepicker( "option", "minDate", selectedDate );
|
||||
});
|
||||
$('.date-range-filter .datepicker-to').datepicker('option', 'onSelect', function(selectedDate) {
|
||||
$(".date-range-filter .datepicker-from" ).datepicker( "option", "maxDate", selectedDate );
|
||||
});
|
||||
}
|
||||
|
||||
handle_date_picker_fields();
|
||||
}
|
||||
|
||||
$(".observe_field").on('change', function() {
|
||||
target = $(this).attr("data-update");
|
||||
ajax_indicator = $(this).attr("data-ajax-indicator") || '#busy_indicator';
|
||||
$(target).hide();
|
||||
$(ajax_indicator).show();
|
||||
$.ajax({ dataType: 'html',
|
||||
url: $(this).attr("data-base-url")+encodeURIComponent($(this).val()),
|
||||
type: 'get',
|
||||
success: function(data){
|
||||
$(target).html(data);
|
||||
$(ajax_indicator).hide();
|
||||
$(target).show();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$('.spree_add_fields').click(function() {
|
||||
var target = $(this).data("target");
|
||||
var new_table_row = $(target + ' tr:visible:last').clone();
|
||||
var new_id = new Date().getTime();
|
||||
new_table_row.find("input, select").each(function () {
|
||||
var el = $(this);
|
||||
el.val("");
|
||||
if (typeof el.attr("id") !== 'undefined') el.attr("id", el.attr("id").replace(/\d+/, new_id))
|
||||
if (typeof el.attr("name") !== 'undefined') el.attr("name", el.attr("name").replace(/\d+/, new_id))
|
||||
})
|
||||
// When cloning a new row, set the href of all icons to be an empty "#"
|
||||
// This is so that clicking on them does not perform the actions for the
|
||||
// duplicated row
|
||||
new_table_row.find("a").each(function () {
|
||||
var el = $(this);
|
||||
el.attr('href', '#');
|
||||
})
|
||||
$(target).prepend(new_table_row);
|
||||
})
|
||||
|
||||
// Fix sortable helper
|
||||
var fixHelper = function(e, ui) {
|
||||
ui.children().each(function() {
|
||||
$(this).width($(this).width());
|
||||
});
|
||||
return ui;
|
||||
};
|
||||
|
||||
$('table.sortable').ready(function(){
|
||||
var td_count = $(this).find('tbody tr:first-child td').length
|
||||
|
||||
if (typeof $('table.sortable tbody').sortable !== 'function' )
|
||||
return
|
||||
|
||||
$('table.sortable tbody').sortable(
|
||||
{
|
||||
handle: '.handle',
|
||||
helper: fixHelper,
|
||||
placeholder: 'ui-sortable-placeholder',
|
||||
update: function(event, ui) {
|
||||
$("#progress").show();
|
||||
positions = {};
|
||||
$.each($('table.sortable tbody tr'), function(position, obj){
|
||||
reg = /spree_(\w+_?)+_(\d+)/;
|
||||
parts = reg.exec($(obj).attr('id'));
|
||||
if (parts) {
|
||||
positions['positions['+parts[2]+']'] = position;
|
||||
}
|
||||
});
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
dataType: 'script',
|
||||
url: $(ui.item).closest("table.sortable").data("sortable-link"),
|
||||
data: positions,
|
||||
success: function(data){ $("#progress").hide(); }
|
||||
});
|
||||
},
|
||||
start: function (event, ui) {
|
||||
// Set correct height for placehoder (from dragged tr)
|
||||
ui.placeholder.height(ui.item.height())
|
||||
// Fix placeholder content to make it correct width
|
||||
ui.placeholder.html("<td colspan='"+(td_count-1)+"'></td><td class='actions'></td>")
|
||||
},
|
||||
stop: function (event, ui) {
|
||||
// Fix odd/even classes after reorder
|
||||
$("table.sortable tr:even").removeClass("odd even").addClass("even");
|
||||
$("table.sortable tr:odd").removeClass("odd even").addClass("odd");
|
||||
}
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
27
app/assets/javascripts/admin/spree/orders/address_states.js
Normal file
27
app/assets/javascripts/admin/spree/orders/address_states.js
Normal file
@@ -0,0 +1,27 @@
|
||||
var update_state = function(region) {
|
||||
var country = $('span#' + region + 'country .select2').select2('val');
|
||||
|
||||
var state_select = $('span#' + region + 'state select.select2');
|
||||
var state_input = $('span#' + region + 'state input.state_name');
|
||||
|
||||
$.get(Spree.routes.states_search + "?country_id=" + country, function(data) {
|
||||
var states = data["states"]
|
||||
if (states.length > 0) {
|
||||
state_select.html('');
|
||||
var states_with_blank = [{name: '', id: ''}].concat(states);
|
||||
$.each(states_with_blank, function(pos,state) {
|
||||
var opt = $(document.createElement('option'))
|
||||
.attr('value', state.id)
|
||||
.html(state.name);
|
||||
state_select.append(opt);
|
||||
});
|
||||
state_select.prop("disabled", false).show();
|
||||
state_select.select2();
|
||||
state_input.hide().prop("disabled", true);
|
||||
|
||||
} else {
|
||||
state_input.prop("disabled", false).show();
|
||||
state_select.select2('destroy').hide();
|
||||
}
|
||||
})
|
||||
};
|
||||
64
app/assets/javascripts/admin/spree/orders/shipments.js.erb
Normal file
64
app/assets/javascripts/admin/spree/orders/shipments.js.erb
Normal file
@@ -0,0 +1,64 @@
|
||||
// Shipments AJAX API
|
||||
|
||||
$(document).ready(function() {
|
||||
|
||||
handle_ship_click = function(){
|
||||
var link = $(this);
|
||||
var shipment_number = link.data('shipment-number');
|
||||
var url = Spree.url( Spree.routes.orders_api + "/" + order_number + "/shipments/" + shipment_number + "/ship.json");
|
||||
$.ajax({
|
||||
type: "PUT",
|
||||
url: url
|
||||
}).done(function( msg ) {
|
||||
window.location.reload();
|
||||
}).error(function( msg ) {
|
||||
console.log(msg);
|
||||
});
|
||||
}
|
||||
// $('[data-hook=admin_order_edit_form] a.ship').click(handle_ship_click);
|
||||
|
||||
//handle shipping method edit click
|
||||
// $('a.edit-method').click(toggleMethodEdit);
|
||||
// $('a.cancel-method').click(toggleMethodEdit);
|
||||
|
||||
handle_shipping_method_save = function(){
|
||||
var link = $(this);
|
||||
var shipment_number = link.data('shipment-number');
|
||||
var selected_shipping_rate_id = link.parents('tbody').find("select#selected_shipping_rate_id[data-shipment-number='" + shipment_number + "']").val();
|
||||
var unlock = link.parents('tbody').find("input[name='open_adjustment'][data-shipment-number='" + shipment_number + "']:checked").val();
|
||||
var url = Spree.url( Spree.routes.orders_api + "/" + order_number + "/shipments/" + shipment_number + ".json");
|
||||
|
||||
$.ajax({
|
||||
type: "PUT",
|
||||
url: url,
|
||||
data: { shipment: { selected_shipping_rate_id: selected_shipping_rate_id, unlock: unlock } }
|
||||
}).done(function( msg ) {
|
||||
window.location.reload();
|
||||
}).error(function( msg ) {
|
||||
console.log(msg);
|
||||
});
|
||||
}
|
||||
// $('[data-hook=admin_order_edit_form] a.save-method').click(handle_shipping_method_save);
|
||||
|
||||
//handle tracking edit click
|
||||
// $('a.edit-tracking').click(toggleTrackingEdit);
|
||||
// $('a.cancel-tracking').click(toggleTrackingEdit);
|
||||
|
||||
handle_tracking_save = function(){
|
||||
var link = $(this);
|
||||
var shipment_number = link.data('shipment-number');
|
||||
var tracking = link.parents('tbody').find('input#tracking').val();
|
||||
var url = Spree.url( Spree.routes.orders_api + "/" + order_number + "/shipments/" + shipment_number + ".json");
|
||||
|
||||
$.ajax({
|
||||
type: "PUT",
|
||||
url: url,
|
||||
data: { shipment: { tracking: tracking } }
|
||||
}).done(function( msg ) {
|
||||
window.location.reload();
|
||||
}).error(function( msg ) {
|
||||
console.log(msg);
|
||||
});
|
||||
}
|
||||
$('[data-hook=admin_order_edit_form] a.save-tracking').click(handle_tracking_save);
|
||||
});
|
||||
@@ -0,0 +1,177 @@
|
||||
// variant autocompletion
|
||||
|
||||
$(document).ready(function() {
|
||||
|
||||
if ($('#variant_autocomplete_template').length > 0) {
|
||||
window.variantTemplate = Handlebars.compile($('#variant_autocomplete_template').text());
|
||||
window.variantStockTemplate = Handlebars.compile($('#variant_autocomplete_stock_template').text());
|
||||
|
||||
// handle variant selection, show stock level.
|
||||
$('#add_variant_id').change(function(){
|
||||
var variant_id = $(this).val();
|
||||
|
||||
var variant = _.find(window.variants, function(variant){
|
||||
return variant.id == variant_id
|
||||
})
|
||||
$('#stock_details').html(variantStockTemplate({variant: variant}));
|
||||
$('#stock_details').show();
|
||||
|
||||
$('button.add_variant').click(addVariantFromStockLocation);
|
||||
|
||||
// Add some tips
|
||||
$('.with-tip').powerTip({
|
||||
smartPlacement: true,
|
||||
fadeInTime: 50,
|
||||
fadeOutTime: 50,
|
||||
intentPollInterval: 300
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
//handle edit click
|
||||
// $('a.edit-item').click(toggleItemEdit);
|
||||
|
||||
//handle cancel click
|
||||
// $('a.cancel-item').click(toggleItemEdit);
|
||||
|
||||
handle_save_click = function(){
|
||||
var save = $(this);
|
||||
var shipment_number = save.data('shipment-number');
|
||||
var variant_id = save.data('variant-id');
|
||||
|
||||
var quantity = parseInt(save.parents('tr').find('input.line_item_quantity').val());
|
||||
|
||||
toggleItemEdit();
|
||||
|
||||
adjustItems(shipment_number, variant_id, quantity);
|
||||
return false;
|
||||
}
|
||||
// $('a.save-item').click(handle_save_click);
|
||||
|
||||
handle_delete_click = function(){
|
||||
var del = $(this);
|
||||
var shipment_number = del.data('shipment-number');
|
||||
var variant_id = del.data('variant-id');
|
||||
|
||||
toggleItemEdit();
|
||||
|
||||
adjustItems(shipment_number, variant_id, 0);
|
||||
}
|
||||
// $('a.delete-item').click(handle_delete_click);
|
||||
}
|
||||
});
|
||||
|
||||
adjustItems = function(shipment_number, variant_id, quantity){
|
||||
var shipment = _.findWhere(shipments, {number: shipment_number + ''});
|
||||
var inventory_units = _.where(shipment.inventory_units, {variant_id: variant_id});
|
||||
|
||||
var url = Spree.routes.orders_api + "/" + order_number + "/shipments/" + shipment_number;
|
||||
|
||||
var new_quantity = 0;
|
||||
if(inventory_units.length<quantity){
|
||||
url += "/add"
|
||||
new_quantity = (quantity - inventory_units.length);
|
||||
}else if(inventory_units.length>quantity){
|
||||
url += "/remove"
|
||||
new_quantity = (inventory_units.length - quantity);
|
||||
}
|
||||
url += '.json';
|
||||
|
||||
if(new_quantity!=0){
|
||||
$.ajax({
|
||||
type: "PUT",
|
||||
url: Spree.url(url),
|
||||
data: { variant_id: variant_id, quantity: new_quantity }
|
||||
}).done(function( msg ) {
|
||||
window.location.reload();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
toggleTrackingEdit = function(){
|
||||
var link = $(this);
|
||||
link.parents('tbody').find('tr.edit-tracking').toggle();
|
||||
link.parents('tbody').find('tr.show-tracking').toggle();
|
||||
}
|
||||
|
||||
toggleMethodEdit = function(){
|
||||
var link = $(this);
|
||||
link.parents('tbody').find('tr.edit-method').toggle();
|
||||
link.parents('tbody').find('tr.show-method').toggle();
|
||||
}
|
||||
|
||||
toggleItemEdit = function(){
|
||||
var link = $(this);
|
||||
link.parent().find('a.edit-item').toggle();
|
||||
link.parent().find('a.cancel-item').toggle();
|
||||
link.parent().find('a.save-item').toggle();
|
||||
link.parent().find('a.delete-item').toggle();
|
||||
link.parents('tr').find('td.item-qty-show').toggle();
|
||||
link.parents('tr').find('td.item-qty-edit').toggle();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
addVariantFromStockLocation = function() {
|
||||
$('#stock_details').hide();
|
||||
|
||||
var variant_id = $('input.variant_autocomplete').val();
|
||||
var stock_location_id = $(this).data('stock-location-id');
|
||||
var quantity = $("input.quantity[data-stock-location-id='" + stock_location_id + "']").val();
|
||||
|
||||
var shipment = _.find(shipments, function(shipment){
|
||||
return shipment.stock_location_id == stock_location_id && (shipment.state == 'ready' || shipment.state == 'pending');
|
||||
});
|
||||
|
||||
if(shipment==undefined){
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: Spree.url(Spree.routes.orders_api + "/" + order_number + "/shipments.json"),
|
||||
data: { variant_id: variant_id, quantity: quantity, stock_location_id: stock_location_id }
|
||||
}).done(function( msg ) {
|
||||
window.location.reload();
|
||||
}).error(function( msg ) {
|
||||
console.log(msg);
|
||||
});
|
||||
}else{
|
||||
//add to existing shipment
|
||||
adjustItems(shipment.number, variant_id, quantity);
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
formatVariantResult = function(variant) {
|
||||
if (variant["images"][0] != undefined && variant["images"][0].urls != undefined) {
|
||||
variant.image = variant.images[0].urls.mini
|
||||
}
|
||||
return variantTemplate({ variant: variant })
|
||||
}
|
||||
|
||||
$.fn.variantAutocomplete = function() {
|
||||
this.parent().children(".options_placeholder").attr('id', this.parent().data('index'))
|
||||
this.select2({
|
||||
placeholder: Spree.translations.variant_placeholder,
|
||||
minimumInputLength: 3,
|
||||
ajax: {
|
||||
url: Spree.url(Spree.routes.variants_search),
|
||||
datatype: 'json',
|
||||
data: function(term, page) {
|
||||
return {
|
||||
q: {
|
||||
"product_name_or_sku_cont": term
|
||||
}
|
||||
}
|
||||
},
|
||||
results: function (data, page) {
|
||||
window.variants = data['variants'];
|
||||
|
||||
return { results: data['variants'] }
|
||||
}
|
||||
},
|
||||
formatResult: formatVariantResult,
|
||||
formatSelection: function (variant) {
|
||||
$(this.element).parent().children('.options_placeholder').html(variant.options_text)
|
||||
return variant.name;
|
||||
}
|
||||
})
|
||||
}
|
||||
27
app/assets/javascripts/admin/spree/progress.coffee
Normal file
27
app/assets/javascripts/admin/spree/progress.coffee
Normal file
@@ -0,0 +1,27 @@
|
||||
$(document).ready ->
|
||||
opts =
|
||||
lines: 11
|
||||
length: 2
|
||||
width: 3
|
||||
radius: 9
|
||||
corners: 1
|
||||
rotate: 0
|
||||
color: '#fff'
|
||||
speed: 0.8
|
||||
trail: 48
|
||||
shadow: false
|
||||
hwaccel: true
|
||||
className: 'spinner'
|
||||
zIndex: 2e9
|
||||
top: 'auto'
|
||||
left: 'auto'
|
||||
|
||||
target = document.getElementById("spinner")
|
||||
|
||||
$(document).ajaxStart ->
|
||||
$("#progress").fadeIn()
|
||||
spinner = new Spinner(opts).spin(target)
|
||||
|
||||
$(document).ajaxStop ->
|
||||
$("#progress").fadeOut()
|
||||
|
||||
8
app/assets/javascripts/admin/spree/spree-select2.js.erb
Normal file
8
app/assets/javascripts/admin/spree/spree-select2.js.erb
Normal file
@@ -0,0 +1,8 @@
|
||||
//= require select2
|
||||
jQuery(function($) {
|
||||
// Make select beautiful
|
||||
if (typeof $('select.select2').select2 === 'function' )
|
||||
$('select.select2').select2({
|
||||
allowClear: true
|
||||
});
|
||||
})
|
||||
@@ -0,0 +1,22 @@
|
||||
root = exports ? this
|
||||
|
||||
root.taxon_tree_menu = (obj, context) ->
|
||||
|
||||
base_url = Spree.url(Spree.routes.taxonomy_taxons_path)
|
||||
admin_base_url = Spree.url(Spree.routes.admin_taxonomy_taxons_path)
|
||||
edit_url = admin_base_url.clone()
|
||||
edit_url.setPath(edit_url.path() + '/' + obj.attr("id") + "/edit");
|
||||
|
||||
create:
|
||||
label: "<i class='icon-plus'></i> " + Spree.translations.add,
|
||||
action: (obj) -> context.create(obj)
|
||||
rename:
|
||||
label: "<i class='icon-pencil'></i> " + Spree.translations.rename,
|
||||
action: (obj) -> context.rename(obj)
|
||||
remove:
|
||||
label: "<i class='icon-trash'></i> " + Spree.translations.remove,
|
||||
action: (obj) -> context.remove(obj)
|
||||
edit:
|
||||
separator_before: true,
|
||||
label: "<i class='icon-edit'></i> " + Spree.translations.edit,
|
||||
action: (obj) -> window.location = edit_url.toString()
|
||||
127
app/assets/javascripts/admin/spree/taxons/taxonomy.js.coffee
Normal file
127
app/assets/javascripts/admin/spree/taxons/taxonomy.js.coffee
Normal file
@@ -0,0 +1,127 @@
|
||||
handle_ajax_error = (XMLHttpRequest, textStatus, errorThrown) ->
|
||||
$.jstree.rollback(last_rollback)
|
||||
$("#ajax_error").show().html("<strong>" + server_error + "</strong><br />" + taxonomy_tree_error)
|
||||
|
||||
handle_move = (e, data) ->
|
||||
last_rollback = data.rlbk
|
||||
position = data.rslt.cp
|
||||
node = data.rslt.o
|
||||
new_parent = data.rslt.np
|
||||
|
||||
url = Spree.url(base_url).clone()
|
||||
url.setPath url.path() + '/' + node.attr("id")
|
||||
$.ajax
|
||||
type: "POST",
|
||||
dataType: "json",
|
||||
url: url.toString(),
|
||||
data: ({_method: "put", "taxon[parent_id]": new_parent.attr("id"), "taxon[position]": position }),
|
||||
error: handle_ajax_error
|
||||
|
||||
true
|
||||
|
||||
handle_create = (e, data) ->
|
||||
last_rollback = data.rlbk
|
||||
node = data.rslt.obj
|
||||
name = data.rslt.name
|
||||
position = data.rslt.position
|
||||
new_parent = data.rslt.parent
|
||||
|
||||
$.ajax
|
||||
type: "POST",
|
||||
dataType: "json",
|
||||
url: base_url.toString(),
|
||||
data: ({"taxon[name]": name, "taxon[parent_id]": new_parent.attr("id"), "taxon[position]": position }),
|
||||
error: handle_ajax_error,
|
||||
success: (data,result) ->
|
||||
node.attr('id', data.id)
|
||||
|
||||
handle_rename = (e, data) ->
|
||||
last_rollback = data.rlbk
|
||||
node = data.rslt.obj
|
||||
name = data.rslt.new_name
|
||||
|
||||
url = Spree.url(base_url).clone()
|
||||
url.setPath(url.path() + '/' + node.attr("id"))
|
||||
|
||||
$.ajax
|
||||
type: "POST",
|
||||
dataType: "json",
|
||||
url: url.toString(),
|
||||
data: {_method: "put", "taxon[name]": name },
|
||||
error: handle_ajax_error
|
||||
|
||||
handle_delete = (e, data) ->
|
||||
last_rollback = data.rlbk
|
||||
node = data.rslt.obj
|
||||
delete_url = base_url.clone()
|
||||
delete_url.setPath delete_url.path() + '/' + node.attr("id")
|
||||
if confirm(Spree.translations.are_you_sure_delete)
|
||||
$.ajax
|
||||
type: "POST",
|
||||
dataType: "json",
|
||||
url: delete_url.toString(),
|
||||
data: {_method: "delete"},
|
||||
error: handle_ajax_error
|
||||
else
|
||||
$.jstree.rollback(last_rollback)
|
||||
last_rollback = null
|
||||
|
||||
root = exports ? this
|
||||
root.setup_taxonomy_tree = (taxonomy_id) ->
|
||||
if taxonomy_id != undefined
|
||||
# this is defined within admin/taxonomies/edit
|
||||
root.base_url = Spree.url(Spree.routes.taxonomy_taxons_path)
|
||||
|
||||
$.ajax
|
||||
url: base_url.path().replace("/taxons", "/jstree"),
|
||||
success: (taxonomy) ->
|
||||
last_rollback = null
|
||||
|
||||
conf =
|
||||
json_data:
|
||||
data: taxonomy,
|
||||
ajax:
|
||||
url: (e) ->
|
||||
base_url.path() + '/' + e.attr('id') + '/jstree'
|
||||
themes:
|
||||
theme: "apple",
|
||||
url: "/assets/jquery.jstree/themes/apple/style.css"
|
||||
strings:
|
||||
new_node: new_taxon,
|
||||
loading: Spree.translations.loading + "..."
|
||||
crrm:
|
||||
move:
|
||||
check_move: (m) ->
|
||||
position = m.cp
|
||||
node = m.o
|
||||
new_parent = m.np
|
||||
|
||||
# no parent or cant drag and drop
|
||||
if !new_parent || node.attr("rel") == "root"
|
||||
return false
|
||||
|
||||
# can't drop before root
|
||||
if new_parent.attr("id") == "taxonomy_tree" && position == 0
|
||||
return false
|
||||
|
||||
true
|
||||
contextmenu:
|
||||
items: (obj) ->
|
||||
taxon_tree_menu(obj, this)
|
||||
plugins: ["themes", "json_data", "dnd", "crrm", "contextmenu"]
|
||||
|
||||
$("#taxonomy_tree").jstree(conf)
|
||||
.bind("move_node.jstree", handle_move)
|
||||
.bind("remove.jstree", handle_delete)
|
||||
.bind("create.jstree", handle_create)
|
||||
.bind("rename.jstree", handle_rename)
|
||||
.bind "loaded.jstree", ->
|
||||
$(this).jstree("core").toggle_node($('.jstree-icon').first())
|
||||
|
||||
$("#taxonomy_tree a").on "dblclick", (e) ->
|
||||
$("#taxonomy_tree").jstree("rename", this)
|
||||
|
||||
# surpress form submit on enter/return
|
||||
$(document).keypress (e) ->
|
||||
if e.keyCode == 13
|
||||
e.preventDefault()
|
||||
@@ -16,22 +16,6 @@ $(document).ready(function() {
|
||||
});
|
||||
});
|
||||
|
||||
// Overriding a broken function in Spree. Bug report at
|
||||
// https://github.com/spree/spree/issues/4032
|
||||
show_flash_error = function(message) {
|
||||
error_div = $('.flash.error');
|
||||
if (error_div.length > 0) {
|
||||
error_div.html(message);
|
||||
error_div.show();
|
||||
} else {
|
||||
if ($("#content .toolbar").length > 0) {
|
||||
$("#content .toolbar").before('<div class="flash error">' + message + '</div>');
|
||||
} else {
|
||||
$("#progress").before('<div class="flash error">' + message + '</div>');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$(document).ready(function(){
|
||||
$('a.close').click(function(event){
|
||||
event.preventDefault();
|
||||
|
||||
@@ -7,6 +7,9 @@
|
||||
*= require admin/spree_backend
|
||||
*= require jquery.powertip
|
||||
*= require responsive-tables
|
||||
*= require normalize
|
||||
*= require skeleton
|
||||
*= require jquery.ui.datepicker
|
||||
*= require jquery-ui-timepicker-addon
|
||||
*= require shared/textAngular
|
||||
*= require shared/ng-tags-input.min
|
||||
@@ -14,15 +17,27 @@
|
||||
*= require_self
|
||||
*/
|
||||
|
||||
//************************************************************************//
|
||||
//************************************************************************//
|
||||
@import 'globals/functions';
|
||||
@import 'globals/variables';
|
||||
@import 'variables';
|
||||
@import 'globals/mixins';
|
||||
|
||||
@import 'shared/typography';
|
||||
@import 'shared/tables';
|
||||
@import 'shared/icons';
|
||||
@import 'shared/forms';
|
||||
@import 'shared/layout';
|
||||
|
||||
@import 'plugins/powertip';
|
||||
@import 'plugins/jstree';
|
||||
@import 'plugins/font-awesome';
|
||||
|
||||
@import 'hacks/mozilla';
|
||||
@import 'hacks/opera';
|
||||
@import 'hacks/ie';
|
||||
|
||||
@import 'variables';
|
||||
@import 'components/*';
|
||||
@import 'plugins/font-awesome';
|
||||
@import 'pages/*';
|
||||
|
||||
@import '*';
|
||||
|
||||
33
app/assets/stylesheets/admin/components/actions.scss
Normal file
33
app/assets/stylesheets/admin/components/actions.scss
Normal file
@@ -0,0 +1,33 @@
|
||||
@import 'admin/globals/variables';
|
||||
|
||||
table tbody tr {
|
||||
&.highlight {
|
||||
|
||||
@each $action in $actions {
|
||||
&.action-#{$action} td {
|
||||
background-color: get-value($actions, $actions-bg-colors, $action);
|
||||
border-color: get-value($actions, $actions-brd-colors, $action);
|
||||
}
|
||||
}
|
||||
|
||||
&.action-remove td, &.action-void td {
|
||||
text-decoration: line-through;
|
||||
|
||||
&.actions {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.before-highlight {
|
||||
@each $action in $actions {
|
||||
&.action-#{$action} td {
|
||||
border-bottom-color: get-value($actions, $actions-brd-colors, $action);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
td.actions {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
#admin-menu {
|
||||
li {
|
||||
a {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
a::before {
|
||||
font-weight: normal;
|
||||
padding-top: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
158
app/assets/stylesheets/admin/components/date-picker.scss
Normal file
158
app/assets/stylesheets/admin/components/date-picker.scss
Normal file
@@ -0,0 +1,158 @@
|
||||
@import 'admin/globals/variables';
|
||||
@import 'admin/globals/mixins';
|
||||
@import 'admin/plugins/font-awesome';
|
||||
|
||||
.date-range-filter {
|
||||
.range-divider {
|
||||
padding: 0;
|
||||
}
|
||||
input.datepicker {
|
||||
width: 96px !important;
|
||||
}
|
||||
}
|
||||
|
||||
#ui-datepicker-div {
|
||||
@include border-radius($border-radius);
|
||||
border-color: $color-3;
|
||||
padding: 0;
|
||||
margin-top: 10px;
|
||||
|
||||
&:before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
border-left: 10px solid transparent;
|
||||
border-right: 10px solid transparent;
|
||||
border-bottom: 10px solid $color-3;
|
||||
top: 0px;
|
||||
margin-top: -10px;
|
||||
left: 25px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.ui-datepicker-header {
|
||||
padding: 0;
|
||||
background-image: none;
|
||||
background-color: $color-3;
|
||||
border: none;
|
||||
border-bottom: none;
|
||||
border-radius: 0;
|
||||
height: 32px;
|
||||
|
||||
.ui-datepicker-prev, .ui-datepicker-next {
|
||||
border-radius: 0;
|
||||
top: 0;
|
||||
height: 32px;
|
||||
|
||||
&:hover {
|
||||
border: none;
|
||||
background-image: none;
|
||||
background-color: $color-3;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.ui-icon {
|
||||
background-image: none;
|
||||
text-indent: 0;
|
||||
color: $color-1;
|
||||
width: 10px;
|
||||
margin-left: -5px;
|
||||
@extend [class^="icon-"]:before;
|
||||
|
||||
&:hover {
|
||||
color: very-light($color-2, 25);
|
||||
}
|
||||
}
|
||||
}
|
||||
.ui-datepicker-prev {
|
||||
left: 0;
|
||||
|
||||
.ui-icon {
|
||||
@extend .icon-arrow-left;
|
||||
}
|
||||
}
|
||||
.ui-datepicker-next {
|
||||
right: 0;
|
||||
|
||||
.ui-icon {
|
||||
@extend .icon-arrow-right;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.ui-icon {
|
||||
margin-left: -5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ui-datepicker-title {
|
||||
color: $color-1;
|
||||
text-transform: uppercase;
|
||||
font-size: 85% !important;
|
||||
padding: 6px 10px;
|
||||
}
|
||||
}
|
||||
|
||||
table.ui-datepicker-calendar {
|
||||
border: none;
|
||||
|
||||
thead {
|
||||
th {
|
||||
border-bottom: 1px solid $color-border;
|
||||
border-right: 1px solid $color-border;
|
||||
color: $color-body-text;
|
||||
width: 33px;
|
||||
|
||||
&:last-child {
|
||||
border-right: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
tbody {
|
||||
tr:hover {
|
||||
td {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
}
|
||||
&:last-child tr:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
td {
|
||||
a {
|
||||
border: 1px solid transparent;
|
||||
background-color: $color-1;
|
||||
background-image: none;
|
||||
font-size: 85%;
|
||||
color: $color-body-text;
|
||||
|
||||
&.ui-state-active {
|
||||
background-color: $color-2;
|
||||
color: $color-1;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: $color-2;
|
||||
color: $color-1;
|
||||
border-color: darken($color-2, 5);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
&.ui-state-disabled {
|
||||
.ui-state-default {
|
||||
border: none;
|
||||
background-image: none;
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
&.ui-datepicker-today {
|
||||
a {
|
||||
background-color: $color-6;
|
||||
color: $color-1;
|
||||
border: 1px solid darken($color-6, 5);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
45
app/assets/stylesheets/admin/components/messages.scss
Normal file
45
app/assets/stylesheets/admin/components/messages.scss
Normal file
@@ -0,0 +1,45 @@
|
||||
@import 'admin/globals/variables';
|
||||
|
||||
.errorExplanation {
|
||||
padding: 5px;
|
||||
border: 1px solid very-light($color-error, 12);
|
||||
background-color: very-light($color-error, 6);
|
||||
border-radius: 3px;
|
||||
color: very-light($color-error, 30);
|
||||
margin-bottom: 15px;
|
||||
|
||||
h2 {
|
||||
font-size: 140%;
|
||||
color: very-light($color-error, 30);
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
p {
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style-position: inside;
|
||||
|
||||
li {
|
||||
font-weight: $font-weight-bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.flash {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
padding: 16px;
|
||||
text-align: center;
|
||||
z-index: 1000;
|
||||
font-size: 120%;
|
||||
color: $color-1;
|
||||
font-weight: 600;
|
||||
|
||||
&.notice { background-color: rgba($color-notice, 0.8) }
|
||||
&.success { background-color: rgba($color-success, 0.8) }
|
||||
&.error { background-color: rgba($color-error, 0.8) }
|
||||
}
|
||||
162
app/assets/stylesheets/admin/components/navigation.scss
Normal file
162
app/assets/stylesheets/admin/components/navigation.scss
Normal file
@@ -0,0 +1,162 @@
|
||||
@import 'admin/globals/variables';
|
||||
|
||||
// Navigation
|
||||
//---------------------------------------------------
|
||||
.inline-menu {
|
||||
margin: 0;
|
||||
-webkit-margin-before: 0;
|
||||
-webkit-padding-start: 0;
|
||||
}
|
||||
|
||||
nav.menu {
|
||||
ul {
|
||||
list-style: none;
|
||||
|
||||
li {
|
||||
a {
|
||||
padding: 10px 0;
|
||||
display: block;
|
||||
position: relative;
|
||||
text-align: left;
|
||||
border: 1px solid transparent;
|
||||
text-transform: uppercase;
|
||||
font-weight: 600;
|
||||
font-size: 90%;
|
||||
}
|
||||
|
||||
&.active a {
|
||||
color: $color-2;
|
||||
border-left-width: 0;
|
||||
border-bottom-color: $color-2;
|
||||
}
|
||||
|
||||
&:hover a {
|
||||
color: $color-2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[data-hook="admin_login_navigation_bar"] {
|
||||
ul {
|
||||
text-align: right;
|
||||
|
||||
li {
|
||||
padding: 5px 0 5px 10px;
|
||||
text-align: right;
|
||||
font-size: 90%;
|
||||
color: $color-link;
|
||||
margin-top: 8px;
|
||||
|
||||
&[data-hook="user-logged-in-as"] {
|
||||
width: 50%;
|
||||
color: $color-body-text;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
i {
|
||||
color: $color-2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#admin-menu {
|
||||
background-color: $color-3;
|
||||
|
||||
li {
|
||||
min-width: 90px;
|
||||
|
||||
a {
|
||||
display: block;
|
||||
padding: 25px 20px;
|
||||
color: $color-1 !important;
|
||||
text-transform: uppercase;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
font-weight: 600;
|
||||
|
||||
i {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: $color-2;
|
||||
|
||||
&:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
border-left: 10px solid transparent;
|
||||
border-right: 10px solid transparent;
|
||||
border-top: 5px solid $color-2;
|
||||
bottom: 0px;
|
||||
margin-bottom: -5px;
|
||||
left: 50%;
|
||||
margin-left: -10px;
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
|
||||
span.text {
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
a::before {
|
||||
font-weight: normal;
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
width: 300px;
|
||||
background-color: $color-3;
|
||||
width: 200px;
|
||||
z-index: 100000;
|
||||
|
||||
> li {
|
||||
width: 200px !important;
|
||||
|
||||
a:after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.selected a {
|
||||
@extend a:hover;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#sub-menu {
|
||||
background-color: $color-2;
|
||||
padding-bottom: 0;
|
||||
|
||||
li {
|
||||
a {
|
||||
display: block;
|
||||
padding: 12px 20px;
|
||||
color: $color-1;
|
||||
text-align: center;
|
||||
text-transform: uppercase;
|
||||
position: relative;
|
||||
font-size: 85%;
|
||||
}
|
||||
|
||||
&.selected a, a:hover {
|
||||
&:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
border-left: 10px solid transparent;
|
||||
border-right: 10px solid transparent;
|
||||
border-top: 5px solid $color-2;
|
||||
bottom: 0px;
|
||||
margin-bottom: -5px;
|
||||
left: 50%;
|
||||
margin-left: -10px;
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,23 @@
|
||||
@import 'admin/globals/variables';
|
||||
@import "admin/variables";
|
||||
|
||||
.pagination {
|
||||
text-align: center;
|
||||
margin: 2em 0 1em;
|
||||
padding: 10px 0;
|
||||
|
||||
.page {
|
||||
padding: 5px 8px;
|
||||
text-align: center;
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
|
||||
&.current {
|
||||
background-color: $color-2;
|
||||
border-radius: 3px;
|
||||
color: $color-1;
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
margin: 0 0.35em;
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
.select2-result-label {
|
||||
.variant-autocomplete-item {
|
||||
.variant-details {
|
||||
padding: 0 10px;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.variant-image {
|
||||
margin-top: 5px;
|
||||
background-color: white;
|
||||
float: left;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
ul.variant-details {
|
||||
li {
|
||||
display: inline-block;
|
||||
|
||||
&:after {
|
||||
content: ' / ';
|
||||
}
|
||||
|
||||
&:last-child:after {
|
||||
content: '';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
38
app/assets/stylesheets/admin/components/progress.scss
Normal file
38
app/assets/stylesheets/admin/components/progress.scss
Normal file
@@ -0,0 +1,38 @@
|
||||
@import 'admin/globals/variables';
|
||||
@import 'admin/globals/mixins';
|
||||
|
||||
#progress {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
z-index: 1000;
|
||||
opacity: 0.8;
|
||||
width: 100%;
|
||||
|
||||
.wrapper {
|
||||
@include border-radius(10px);
|
||||
top: -10px;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
width: 200px;
|
||||
margin-left: -100px;
|
||||
padding: 11px 0;
|
||||
background-color: $color-3;
|
||||
color: $color-1;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#spinner {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 50%;
|
||||
margin-left: -5px;
|
||||
}
|
||||
|
||||
.progress-message {
|
||||
font-size: 120%;
|
||||
font-weight: $font-weight-bold;
|
||||
margin-top: 20px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
}
|
||||
28
app/assets/stylesheets/admin/components/sidebar.scss
Normal file
28
app/assets/stylesheets/admin/components/sidebar.scss
Normal file
@@ -0,0 +1,28 @@
|
||||
@import 'admin/globals/variables';
|
||||
|
||||
// Sidebar
|
||||
//---------------------------------------------------
|
||||
#sidebar {
|
||||
overflow: visible;
|
||||
border-top: 1px solid $color-border;
|
||||
margin-top: 17px;
|
||||
|
||||
.sidebar-title {
|
||||
color: $color-2;
|
||||
text-transform: uppercase;
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
|
||||
> span {
|
||||
display: inline;
|
||||
background: #fff;
|
||||
padding: 5px 10px;
|
||||
position: relative;
|
||||
top: -14px;
|
||||
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
}
|
||||
}
|
||||
36
app/assets/stylesheets/admin/components/states.scss
Normal file
36
app/assets/stylesheets/admin/components/states.scss
Normal file
@@ -0,0 +1,36 @@
|
||||
@import 'admin/globals/variables';
|
||||
|
||||
.state {
|
||||
text-transform: uppercase;
|
||||
font-size: 80%;
|
||||
font-weight: 600;
|
||||
|
||||
&:before {
|
||||
content: '';
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
margin-right: 3px;
|
||||
border-radius: $body-font-size/2;
|
||||
width: $body-font-size - 4px;
|
||||
height: $body-font-size - 4px;
|
||||
}
|
||||
|
||||
@each $state in $states {
|
||||
&.#{$state}:before {
|
||||
background-color: get-value($states, $states-bg-colors, $state);
|
||||
|
||||
// &, a {
|
||||
// color: get-value($states, $states-text-colors, $state);
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
table tbody tr {
|
||||
&[class*="state"] td:first-child {
|
||||
border-left-width: 3px;
|
||||
}
|
||||
&.state-complete td:first-child { border-left-color: $color-success }
|
||||
&.state-cart td:first-child { border-left-color: very-light($color-notice, 6) }
|
||||
&.state-canceled td:first-child { border-left-color: $color-error }
|
||||
}
|
||||
14
app/assets/stylesheets/admin/components/table-filter.scss
Normal file
14
app/assets/stylesheets/admin/components/table-filter.scss
Normal file
@@ -0,0 +1,14 @@
|
||||
#table-filter {
|
||||
|
||||
.field {
|
||||
input[type="text"], input[type="phone"],
|
||||
input[type="email"], input[type="number"],
|
||||
input[type="url"] {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.actions {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
25
app/assets/stylesheets/admin/globals/functions.scss
Normal file
25
app/assets/stylesheets/admin/globals/functions.scss
Normal file
@@ -0,0 +1,25 @@
|
||||
// Make color very close to white
|
||||
@function very-light($color, $adjust: 3){
|
||||
@if type-of($adjust) == 'number' and $adjust > 0 {
|
||||
@for $i from 0 through 100 {
|
||||
@if lighten($color, $i) == white and ($i - $adjust) > $adjust {
|
||||
@return lighten($color, $i - $adjust);
|
||||
}
|
||||
}
|
||||
}
|
||||
@else {
|
||||
@debug "Please correct $adjust value. It should be number and larger then 0. Currently it is '#{type-of($adjust)}' with value '#{$adjust}'"
|
||||
}
|
||||
};
|
||||
|
||||
// Quick fix for dynamic variables missing in SASS
|
||||
@function get-value($prop, $val, $search) {
|
||||
$n1: index($prop, $search);
|
||||
$n2: index($val, $search);
|
||||
|
||||
@if($n1) {
|
||||
@return nth($val, $n1);
|
||||
} @else {
|
||||
@return nth($prop, $n2);
|
||||
}
|
||||
}
|
||||
135
app/assets/stylesheets/admin/plugins/jstree.scss
Normal file
135
app/assets/stylesheets/admin/plugins/jstree.scss
Normal file
@@ -0,0 +1,135 @@
|
||||
@import 'admin/globals/variables';
|
||||
@import 'admin/globals/mixins';
|
||||
@import 'admin/plugins/font-awesome';
|
||||
|
||||
#taxonomy_tree {
|
||||
> ul, .jstree-icon {
|
||||
background-image: none;
|
||||
}
|
||||
|
||||
.jstree-icon {
|
||||
@extend [class^="icon-"]:before;
|
||||
}
|
||||
|
||||
.jstree-open > .jstree-icon {
|
||||
@extend .icon-caret-down;
|
||||
}
|
||||
.jstree-closed > .jstree-icon {
|
||||
@extend .icon-caret-right;
|
||||
}
|
||||
|
||||
li {
|
||||
background-image: none;
|
||||
|
||||
a {
|
||||
background-color: very-light($color-3);
|
||||
border: 1px solid $color-border;
|
||||
color: $color-body-text;
|
||||
font-weight: $font-weight-bold;
|
||||
text-shadow: none;
|
||||
width: 90%;
|
||||
height: auto;
|
||||
line-height: inherit;
|
||||
padding: 5px 0 5px 10px;
|
||||
margin-bottom: 10px;
|
||||
|
||||
.jstree-icon {
|
||||
padding-left: 0px;
|
||||
@extend .icon-move;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#vakata-dragged.jstree-apple .jstree-invalid,
|
||||
#vakata-dragged.jstree-apple .jstree-ok,
|
||||
#jstree-marker {
|
||||
background-image: none !important;
|
||||
background-color: transparent !important;
|
||||
@extend [class^="icon-"]:before;
|
||||
}
|
||||
#vakata-dragged.jstree-apple .jstree-invalid {
|
||||
@extend .icon-remove;
|
||||
color: $color-5;
|
||||
}
|
||||
#vakata-dragged.jstree-apple .jstree-ok {
|
||||
@extend .icon-ok;
|
||||
color: $color-2;
|
||||
}
|
||||
|
||||
#jstree-marker {
|
||||
@extend .icon-caret-right;
|
||||
color: $color-body-text !important;
|
||||
width: 4px !important;
|
||||
}
|
||||
|
||||
#jstree-marker-line {
|
||||
@include border-radius($border-radius !important);
|
||||
height: 0px !important;
|
||||
margin-left: 5px !important;
|
||||
margin-top: -2px !important;
|
||||
border: none !important;
|
||||
border-bottom: 1px solid $color-body-text !important;
|
||||
background-color: very-light($color-3) !important;
|
||||
|
||||
-webkit-box-shadow: none !important;
|
||||
-moz-box-shadow: none !important;
|
||||
box-shadow: none !important;
|
||||
|
||||
}
|
||||
|
||||
#vakata-contextmenu {
|
||||
background-color: $color-3 !important;
|
||||
-moz-box-shadow: none !important;
|
||||
-webkit-box-shadow: none !important;
|
||||
box-shadow: none !important;
|
||||
border: none !important;
|
||||
@include border-radius($border-radius !important);
|
||||
|
||||
&:before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
border-left: 10px solid transparent;
|
||||
border-right: 10px solid transparent;
|
||||
border-bottom: 10px solid $color-3;
|
||||
top: 0px;
|
||||
margin-top: -10px;
|
||||
left: 25px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
a {
|
||||
color: $color-1 !important;
|
||||
line-height: inherit !important;
|
||||
padding: 5px 10px !important;
|
||||
margin: 0 !important;
|
||||
font-size: 90% !important;
|
||||
|
||||
&:hover {
|
||||
@include border-radius($border-radius !important);
|
||||
background-color: $color-2 !important;
|
||||
border: none !important;
|
||||
-moz-box-shadow: none !important;
|
||||
-webkit-box-shadow: none !important;
|
||||
line-height: inherit !important;
|
||||
padding: 5px 10px !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
li:first-child a:hover:before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
border-left: 10px solid transparent;
|
||||
border-right: 10px solid transparent;
|
||||
border-bottom: 10px solid $color-2;
|
||||
top: 0px;
|
||||
margin-top: -10px;
|
||||
left: 25px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
li.vakata-separator {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
193
app/assets/stylesheets/admin/plugins/select2.scss
Normal file
193
app/assets/stylesheets/admin/plugins/select2.scss
Normal file
@@ -0,0 +1,193 @@
|
||||
@import 'admin/globals/functions';
|
||||
@import 'admin/globals/variables';
|
||||
@import 'admin/globals/mixins';
|
||||
|
||||
@import 'admin/shared/forms';
|
||||
|
||||
@import 'admin/plugins/font-awesome';
|
||||
|
||||
.select2-container {
|
||||
&:hover .select2-choice, &.select2-container-active .select2-choice {
|
||||
background-color: $color-sel-hover-bg !important;
|
||||
border-color: $color-sel-hover-bg !important;
|
||||
}
|
||||
.select2-choice {
|
||||
background-image: none !important;
|
||||
background-color: $color-sel-bg;
|
||||
border: none !important;
|
||||
box-shadow: none !important;
|
||||
@include border-radius($border-radius);
|
||||
color: $color-1 !important;
|
||||
font-size: 90%;
|
||||
height: 31px;
|
||||
line-height: inherit !important;
|
||||
padding: 5px 15px 7px;
|
||||
|
||||
span {
|
||||
display: block;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.select2-search-choice-close {
|
||||
background-image: none !important;
|
||||
font-size: 100% !important;
|
||||
@extend .icon-remove;
|
||||
@extend [class^="icon-"]:before;
|
||||
margin-top: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
&.select2-container-active {
|
||||
.select2-choice {
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
&.select2-dropdown-open .select2-choice div b {
|
||||
@extend .icon-caret-up
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.select2-drop {
|
||||
border-color: $color-sel-hover-bg;
|
||||
box-shadow: none !important;
|
||||
z-index: 1000000;
|
||||
|
||||
&.select2-drop-above {
|
||||
border-color: $color-sel-hover-bg;
|
||||
}
|
||||
}
|
||||
|
||||
.select2-search {
|
||||
@extend .icon-search;
|
||||
|
||||
font-size: 100%;
|
||||
color: darken($color-border, 15);
|
||||
padding: 0 9px 0 0;
|
||||
|
||||
&:before {
|
||||
@extend [class^="icon-"]:before;
|
||||
|
||||
position: absolute;
|
||||
top: 13px;
|
||||
left: 13px;
|
||||
}
|
||||
|
||||
input {
|
||||
@extend input[type="text"];
|
||||
|
||||
padding: 6px 0 6px 25px;
|
||||
margin: 5px 0 0 5px;
|
||||
font-family: $base-font-family;
|
||||
font-size: 90%;
|
||||
box-shadow: none;
|
||||
background-image: none;
|
||||
}
|
||||
}
|
||||
|
||||
.select2-container .select2-choice .select2-arrow {
|
||||
background-image: none;
|
||||
background: transparent;
|
||||
border: 0;
|
||||
|
||||
b {
|
||||
padding-top: 7px;
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: none;
|
||||
font-family: FontAwesome;
|
||||
font-weight: 200 !important;
|
||||
|
||||
&:before {
|
||||
content: "\f0d7";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.select2-results {
|
||||
padding-left: 0 !important;
|
||||
|
||||
li {
|
||||
font-size: 85% !important;
|
||||
|
||||
&.select2-highlighted {
|
||||
.select2-result-label {
|
||||
&, h6 {
|
||||
color: $color-1 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.select2-result-label {
|
||||
color: $color-body-text;
|
||||
min-height: 22px;
|
||||
clear: both;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
&.select2-no-results, &.select2-searching {
|
||||
padding: 5px;
|
||||
background-color: transparent;
|
||||
color: $color-body-text;
|
||||
text-align: center;
|
||||
font-weight: $font-weight-bold;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
}
|
||||
|
||||
.select2-highlighted {
|
||||
background-color: $color-sel-bg;
|
||||
}
|
||||
}
|
||||
|
||||
.select2-container-multi {
|
||||
&.select2-container-active, &.select2-dropdown-open {
|
||||
.select2-choices {
|
||||
border-color: $color-sel-hover-bg !important;
|
||||
box-shadow: none;
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
}
|
||||
.select2-choices {
|
||||
@extend input[type="text"];
|
||||
padding: 6px 3px 3px 3px;
|
||||
box-shadow: none;
|
||||
background-image: none !important;
|
||||
|
||||
.select2-search-choice {
|
||||
@include border-radius($border-radius);
|
||||
margin: 0 0 3px 3px;
|
||||
background-image: none;
|
||||
background-color: $color-sel-bg;
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
color: $color-1 !important;
|
||||
font-size: 85%;
|
||||
|
||||
&:hover {
|
||||
background-color: $color-sel-hover-bg;
|
||||
}
|
||||
|
||||
.select2-search-choice-close {
|
||||
background-image: none !important;
|
||||
font-size: 85% !important;
|
||||
@extend .icon-remove;
|
||||
@extend [class^="icon-"]:before;
|
||||
margin-left: 2px;
|
||||
color: $color-1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
label .select2-container {
|
||||
margin-top: -6px;
|
||||
.select2-choice {
|
||||
span {
|
||||
text-transform: none;
|
||||
font-weight: normal;
|
||||
}
|
||||
}
|
||||
}
|
||||
270
app/assets/stylesheets/admin/shared/forms.scss
Normal file
270
app/assets/stylesheets/admin/shared/forms.scss
Normal file
@@ -0,0 +1,270 @@
|
||||
@import 'admin/globals/variables';
|
||||
@import 'admin/globals/mixins';
|
||||
|
||||
input[type="text"],
|
||||
input[type="password"],
|
||||
input[type="email"],
|
||||
input[type="date"],
|
||||
input[type="datetime"],
|
||||
input[type="time"],
|
||||
input[type="url"],
|
||||
input[type="number"],
|
||||
input[type="tel"],
|
||||
textarea, fieldset {
|
||||
@include border-radius($border-radius);
|
||||
padding: 7px 10px;
|
||||
border: 1px solid $color-txt-brd;
|
||||
color: $color-txt-text;
|
||||
font-size: 90%;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
border-color: $color-txt-hover-brd;
|
||||
}
|
||||
|
||||
&[disabled] {
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
|
||||
textarea {
|
||||
line-height: 19px;
|
||||
}
|
||||
|
||||
.fullwidth {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
label {
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
font-size: 85%;
|
||||
display: inline;
|
||||
margin-bottom: 5px;
|
||||
color: $color-4;
|
||||
|
||||
&.inline {
|
||||
display: inline-block !important;
|
||||
}
|
||||
|
||||
&.block {
|
||||
display: block !important;
|
||||
}
|
||||
}
|
||||
|
||||
.label-block label { display: block }
|
||||
|
||||
input[type="submit"],
|
||||
input[type="button"],
|
||||
button, .button {
|
||||
@include border-radius($border-radius);
|
||||
display: inline-block;
|
||||
padding: 8px 15px;
|
||||
border: none;
|
||||
background-color: $color-btn-bg;
|
||||
color: $color-btn-text;
|
||||
text-transform: uppercase;
|
||||
font-weight: 600 !important;
|
||||
|
||||
&:before {
|
||||
font-weight: normal !important;
|
||||
}
|
||||
|
||||
&:visited, &:active, &:focus { color: $color-btn-text }
|
||||
|
||||
&:hover {
|
||||
background-color: $color-btn-hover-bg;
|
||||
color: $color-btn-hover-text;
|
||||
}
|
||||
|
||||
&:active:focus {
|
||||
box-shadow: 0 0 8px 0 darken($color-btn-hover-bg, 5) inset;
|
||||
}
|
||||
|
||||
&.fullwidth {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
span.info {
|
||||
font-style: italic;
|
||||
font-size: 85%;
|
||||
color: lighten($color-body-text, 15);
|
||||
display: block;
|
||||
line-height: 20px;
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.field {
|
||||
padding: 10px 0;
|
||||
|
||||
&.checkbox {
|
||||
min-height: 73px;
|
||||
|
||||
input[type="checkbox"] {
|
||||
display: inline-block;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
label {
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
ul {
|
||||
border-top: 1px solid $color-border;
|
||||
list-style: none;
|
||||
padding-top: 5px;
|
||||
|
||||
li {
|
||||
display: inline-block;
|
||||
padding-right: 10px;
|
||||
|
||||
|
||||
label {
|
||||
font-weight: normal;
|
||||
text-transform: none;
|
||||
}
|
||||
&.white-space-nowrap {
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.withError {
|
||||
.field_with_errors {
|
||||
label {
|
||||
color: very-light($color-error, 30);
|
||||
}
|
||||
|
||||
input {
|
||||
border-color: very-light($color-error, 15);
|
||||
}
|
||||
}
|
||||
.formError {
|
||||
color: very-light($color-error, 30);
|
||||
font-style: italic;
|
||||
font-size: 85%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fieldset {
|
||||
box-shadow: none;
|
||||
box-sizing: border-box;
|
||||
border-color: $color-border;
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
position: relative;
|
||||
margin-bottom: 35px;
|
||||
padding: 10px 0 15px 0;
|
||||
background-color: transparent;
|
||||
border-left: none;
|
||||
border-right: none;
|
||||
border-radius: 0;
|
||||
|
||||
&.no-border-bottom {
|
||||
border-bottom: none;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
&.no-border-top {
|
||||
border-top: none;
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
legend {
|
||||
background-color: $color-1;
|
||||
color: $color-2;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
text-align: center;
|
||||
padding: 8px 15px;
|
||||
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
|
||||
i {
|
||||
color: $color-link;
|
||||
}
|
||||
}
|
||||
|
||||
label {
|
||||
color: lighten($color-body-text, 8);
|
||||
}
|
||||
|
||||
.filter-actions {
|
||||
margin-bottom: -32px;
|
||||
margin-top: 15px;
|
||||
text-align: center;
|
||||
|
||||
form {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
button, .button, input[type="submit"], input[type="button"], span.or {
|
||||
@include border-radius($border-radius);
|
||||
|
||||
-webkit-box-shadow: 0 0 0 15px $color-1;
|
||||
-moz-box-shadow: 0 0 0 15px $color-1;
|
||||
-ms-box-shadow: 0 0 0 15px $color-1;
|
||||
-o-box-shadow: 0 0 0 15px $color-1;
|
||||
box-shadow: 0 0 0 15px $color-1;
|
||||
|
||||
&:hover {
|
||||
border-color: $color-1;
|
||||
}
|
||||
}
|
||||
|
||||
span.or {
|
||||
background-color: $color-1;
|
||||
border-width: 5px;
|
||||
margin-left: 5px;
|
||||
margin-right: 5px;
|
||||
position: relative;
|
||||
|
||||
-webkit-box-shadow: 0 0 0 5px $color-1;
|
||||
-moz-box-shadow: 0 0 0 5px $color-1;
|
||||
-ms-box-shadow: 0 0 0 5px $color-1;
|
||||
-o-box-shadow: 0 0 0 5px $color-1;
|
||||
box-shadow: 0 0 0 5px $color-1;
|
||||
}
|
||||
}
|
||||
|
||||
&.labels-inline {
|
||||
.field {
|
||||
margin-bottom: 0;
|
||||
display: table;
|
||||
width: 100%;
|
||||
|
||||
label, input {
|
||||
display: table-cell !important;
|
||||
}
|
||||
input {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&.checkbox {
|
||||
input {
|
||||
width: auto !important
|
||||
}
|
||||
}
|
||||
}
|
||||
.actions {
|
||||
padding: 0;
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
margin-top: 18px;
|
||||
}
|
||||
.form-buttons {
|
||||
text-align: center;
|
||||
}
|
||||
25
app/assets/stylesheets/admin/shared/icons.scss
Normal file
25
app/assets/stylesheets/admin/shared/icons.scss
Normal file
@@ -0,0 +1,25 @@
|
||||
@import 'admin/plugins/font-awesome';
|
||||
|
||||
// Some fixes for fontwesome stylesheets
|
||||
[class^="icon-"], [class*=" icon-"] {
|
||||
&:before {
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
&.button, &.icon_link {
|
||||
width: auto;
|
||||
|
||||
&:before {
|
||||
padding-top: 3px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.icon-email:before { @extend .icon-envelope:before }
|
||||
.icon-resume:before { @extend .icon-refresh:before }
|
||||
|
||||
.icon-cancel:before,
|
||||
.icon-void:before { @extend .icon-remove:before }
|
||||
|
||||
.icon-capture:before { @extend .icon-ok:before }
|
||||
.icon-credit:before { @extend .icon-ok:before }
|
||||
92
app/assets/stylesheets/admin/shared/layout.scss
Normal file
92
app/assets/stylesheets/admin/shared/layout.scss
Normal file
@@ -0,0 +1,92 @@
|
||||
@import 'admin/globals/variables';
|
||||
|
||||
// Basics
|
||||
//---------------------------------------------------
|
||||
* {
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
// Helpers
|
||||
.block-table {
|
||||
display: table;
|
||||
width: 100%;
|
||||
|
||||
.table-cell {
|
||||
display: table-cell;
|
||||
vertical-align: middle;
|
||||
padding: 0 10px;
|
||||
|
||||
&:first-child {
|
||||
padding-left: 0;
|
||||
}
|
||||
&:last-child {
|
||||
padding-right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
// For block grids
|
||||
.frameless {
|
||||
margin-left: -10px;
|
||||
margin-right: -10px;
|
||||
}
|
||||
|
||||
// Header
|
||||
//---------------------------------------------------
|
||||
#header {
|
||||
background-color: $color-1;
|
||||
padding: 5px 0;
|
||||
}
|
||||
|
||||
#logo { height: 40px }
|
||||
|
||||
[data-hook="admin-title"] { font-size: 14px }
|
||||
|
||||
.page-title {
|
||||
i {
|
||||
color: $color-2;
|
||||
}
|
||||
}
|
||||
|
||||
// Content
|
||||
//---------------------------------------------------
|
||||
#content {
|
||||
background-color: $color-1;
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
padding: 0;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
#content-header {
|
||||
padding: 15px 0;
|
||||
background-color: very-light($color-3, 4);
|
||||
border-bottom: 1px solid $color-border;
|
||||
|
||||
.page-title {
|
||||
font-size: 20px;
|
||||
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
.page-actions {
|
||||
text-align: right;
|
||||
form {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Footer
|
||||
//---------------------------------------------------
|
||||
#footer {
|
||||
margin-top: 15px;
|
||||
border-top: 1px solid $color-border;
|
||||
padding: 10px 0;
|
||||
}
|
||||
208
app/assets/stylesheets/admin/shared/tables.scss
Normal file
208
app/assets/stylesheets/admin/shared/tables.scss
Normal file
@@ -0,0 +1,208 @@
|
||||
@import 'admin/globals/variables';
|
||||
@import 'admin/plugins/font-awesome';
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
margin-bottom: 15px;
|
||||
border-collapse: separate;
|
||||
|
||||
th, td {
|
||||
padding: 7px 5px;
|
||||
border-right: 1px solid $color-border;
|
||||
border-bottom: 1px solid $color-border;
|
||||
vertical-align: middle;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
img {
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
border-left: 1px solid $color-border;
|
||||
}
|
||||
|
||||
a {
|
||||
border-bottom: 1px dotted lighten($color-link, 10);
|
||||
|
||||
&:hover {
|
||||
border-color: lighten($color-link-hover, 10);
|
||||
}
|
||||
}
|
||||
|
||||
.handle {
|
||||
display: block !important;
|
||||
text-align: center;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
&.actions {
|
||||
background-color: transparent;
|
||||
border: none !important;
|
||||
text-align: center;
|
||||
|
||||
span.text {
|
||||
font-size: $body-font-size;
|
||||
}
|
||||
|
||||
[class*='icon-'].no-text {
|
||||
font-size: 120%;
|
||||
background-color: very-light($color-3);
|
||||
border: 1px solid $color-border;
|
||||
border-radius: 15px;
|
||||
width: 29px;
|
||||
height: 29px;
|
||||
display: inline-block;
|
||||
padding-top: 2px;
|
||||
|
||||
&:before {
|
||||
text-align: center !important;
|
||||
width: 27px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
border-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
button[class*='icon-'] {
|
||||
color: $color-link;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.icon-envelope-alt, .icon-eye-open {
|
||||
color: $color-link;
|
||||
padding-left: 0px;
|
||||
|
||||
&:hover {
|
||||
background-color: $color-3;
|
||||
color: $color-1;
|
||||
}
|
||||
}
|
||||
.icon-trash:hover, .icon-void:hover {
|
||||
background-color: $color-error;
|
||||
color: $color-1;
|
||||
}
|
||||
.icon-cancel:hover {
|
||||
background-color: $color-notice;
|
||||
color: $color-1;
|
||||
}
|
||||
.icon-edit:hover, .icon-capture:hover, .icon-ok:hover, .icon-plus:hover {
|
||||
background-color: $color-success;
|
||||
color: $color-1;
|
||||
}
|
||||
.icon-copy:hover {
|
||||
background-color: $color-notice;
|
||||
color: $color-1;
|
||||
}
|
||||
}
|
||||
|
||||
input[type="number"],
|
||||
input[type="text"] {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&.no-border {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
.handle {
|
||||
@extend [class^="icon-"]:before;
|
||||
@extend .icon-reorder;
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
&.no-borders {
|
||||
td, th {
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
thead {
|
||||
th {
|
||||
padding: 10px;
|
||||
border-top: 1px solid $color-border;
|
||||
border-bottom: none;
|
||||
background-color: $color-tbl-thead;
|
||||
text-transform: uppercase;
|
||||
font-size: 85%;
|
||||
font-weight: $font-weight-bold;
|
||||
}
|
||||
}
|
||||
|
||||
tbody {
|
||||
tr {
|
||||
&:first-child th,
|
||||
&:first-child td {
|
||||
border-top: 1px solid $color-border;
|
||||
}
|
||||
&.even td {
|
||||
background-color: $color-tbl-even;
|
||||
|
||||
img {
|
||||
border: 1px solid very-light($color-3, 6);
|
||||
}
|
||||
}
|
||||
|
||||
&:hover td {
|
||||
background-color: very-light($color-3, 5);
|
||||
|
||||
img {
|
||||
border: 1px solid $color-border;
|
||||
}
|
||||
}
|
||||
|
||||
&.deleted td {
|
||||
background-color: very-light($color-error, 6);
|
||||
border-color: very-light($color-error, 15);
|
||||
}
|
||||
|
||||
&.ui-sortable-placeholder td {
|
||||
border: 1px solid $color-2 !important;
|
||||
visibility: visible !important;
|
||||
|
||||
&.actions {
|
||||
background-color: transparent;
|
||||
border-right: none !important;
|
||||
border-top: none !important;
|
||||
border-bottom: none !important;
|
||||
border-left: 1px solid $color-2 !important;
|
||||
}
|
||||
}
|
||||
|
||||
&.ui-sortable-helper {
|
||||
width: 100%;
|
||||
|
||||
td {
|
||||
background-color: lighten($color-3, 33);
|
||||
border-bottom: 1px solid $color-border;
|
||||
|
||||
&.actions {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.no-border-top tr:first-child td {
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
&.grand-total {
|
||||
td {
|
||||
border-color: $color-2 !important;
|
||||
text-transform: uppercase;
|
||||
font-size: 110%;
|
||||
font-weight: 600;
|
||||
background-color: lighten($color-2, 50);
|
||||
}
|
||||
.total {
|
||||
background-color: $color-2;
|
||||
color: $color-1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
134
app/assets/stylesheets/admin/shared/typography.scss
Normal file
134
app/assets/stylesheets/admin/shared/typography.scss
Normal file
@@ -0,0 +1,134 @@
|
||||
@import 'admin/globals/variables';
|
||||
|
||||
// Base
|
||||
//--------------------------------------------------------------
|
||||
body, div, dl, dt, dd, ul, ol, li, h1, h2, h3, h4, h5, h6, pre, form, p, blockquote, th, td { margin: 0; padding: 0; font-size: 13px; }
|
||||
|
||||
body {
|
||||
font-family: $base-font-family;
|
||||
font-size: $body-font-size;
|
||||
font-weight: 400;
|
||||
color: $color-body-text;
|
||||
text-rendering: optimizeLegibility;
|
||||
}
|
||||
|
||||
hr {
|
||||
border-top: 1px solid $color-border;
|
||||
border-bottom: 1px solid white;
|
||||
border-left: none;
|
||||
}
|
||||
|
||||
strong, b {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
// links
|
||||
//--------------------------------------------------------------
|
||||
a {
|
||||
color: $color-link;
|
||||
text-decoration: none;
|
||||
line-height: inherit;
|
||||
|
||||
&, &:hover, &:active, &:visited, &:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&:visited {
|
||||
color: $color-link-visited;
|
||||
}
|
||||
&:focus {
|
||||
color: $color-link-focus;
|
||||
}
|
||||
&:active {
|
||||
color: $color-link-active;
|
||||
}
|
||||
&:hover {
|
||||
color: $color-link-hover;
|
||||
}
|
||||
}
|
||||
|
||||
// Headings
|
||||
//--------------------------------------------------------------
|
||||
|
||||
h1,h2,h3,h4,h5,h6 {
|
||||
font-weight: 600;
|
||||
color: $color-headers;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
h1 { font-size: $h1-size; line-height: $h1-size + 6 }
|
||||
h2 { font-size: $h2-size; line-height: $h1-size + 4 }
|
||||
h3 { font-size: $h3-size; line-height: $h1-size + 2 }
|
||||
h4 { font-size: $h4-size; line-height: $h1-size }
|
||||
h5 { font-size: $h5-size; line-height: $h1-size }
|
||||
h6 { font-size: $h6-size; line-height: $h1-size }
|
||||
|
||||
|
||||
// Lists
|
||||
//--------------------------------------------------------------
|
||||
ul {
|
||||
&.inline-menu {
|
||||
li {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
&.fields {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
dl {
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
margin: 5px 0;
|
||||
color: lighten($color-body-text, 15);
|
||||
|
||||
dt, dd {
|
||||
float: left;
|
||||
line-height: 16px;
|
||||
padding: 5px;
|
||||
text-align: justify;
|
||||
}
|
||||
|
||||
dt {
|
||||
width: 40%;
|
||||
font-weight: 600;
|
||||
padding-left: 0;
|
||||
text-transform: uppercase;
|
||||
font-size: 85%;
|
||||
}
|
||||
|
||||
dd {
|
||||
width: 60%;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
dd:after {
|
||||
content: '';
|
||||
clear: both;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Helpers
|
||||
.align-center { text-align: center }
|
||||
.align-right { text-align: right }
|
||||
.align-left { text-align: left }
|
||||
.align-justify { text-align: justify }
|
||||
|
||||
.uppercase { text-transform: uppercase }
|
||||
|
||||
.green { color: $color-2 }
|
||||
.blue { color: $color-3 }
|
||||
.red { color: $color-5 }
|
||||
.yellow { color: $color-6 }
|
||||
|
||||
.no-objects-found {
|
||||
text-align: center;
|
||||
font-size: 120%;
|
||||
text-transform: uppercase;
|
||||
padding: 40px 0px;
|
||||
color: lighten($color-body-text, 15);
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
require 'open_food_network/referer_parser'
|
||||
require 'open_food_network/permissions'
|
||||
require 'open_food_network/order_cycle_permissions'
|
||||
|
||||
module Admin
|
||||
class EnterprisesController < ResourceController
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# This controller lists products that can be added to an exchange
|
||||
#
|
||||
# Pagination is optional and can be required by using param[:page]
|
||||
module Api
|
||||
class ExchangeProductsController < Api::BaseController
|
||||
DEFAULT_PAGE = 1
|
||||
DEFAULT_PER_PAGE = 100
|
||||
|
||||
skip_authorization_check only: [:index]
|
||||
@@ -29,22 +32,28 @@ module Api
|
||||
|
||||
def render_variant_count
|
||||
render text: {
|
||||
count: Spree::Variant.
|
||||
not_master.
|
||||
where(product_id: products).
|
||||
count
|
||||
count: variants.count
|
||||
}.to_json
|
||||
end
|
||||
|
||||
def variants
|
||||
renderer.exchange_variants(@incoming, @enterprise)
|
||||
end
|
||||
|
||||
def products
|
||||
ExchangeProductsRenderer.
|
||||
new(@order_cycle, spree_current_user).
|
||||
exchange_products(@incoming, @enterprise)
|
||||
renderer.exchange_products(@incoming, @enterprise)
|
||||
end
|
||||
|
||||
def renderer
|
||||
@renderer ||= ExchangeProductsRenderer.
|
||||
new(@order_cycle, spree_current_user)
|
||||
end
|
||||
|
||||
def paginated_products
|
||||
return products unless pagination_required?
|
||||
|
||||
products.
|
||||
page(params[:page] || DEFAULT_PAGE).
|
||||
page(params[:page]).
|
||||
per(params[:per_page] || DEFAULT_PER_PAGE)
|
||||
end
|
||||
|
||||
@@ -74,19 +83,23 @@ module Api
|
||||
order_cycle: @order_cycle
|
||||
)
|
||||
|
||||
render text: {
|
||||
products: serializer,
|
||||
pagination: pagination_data(paginated_products)
|
||||
}.to_json
|
||||
result = { products: serializer }
|
||||
result = result.merge(pagination: pagination_data(paginated_products)) if pagination_required?
|
||||
|
||||
render text: result.to_json
|
||||
end
|
||||
|
||||
def pagination_data(paginated_products)
|
||||
{
|
||||
results: paginated_products.total_count,
|
||||
pages: paginated_products.num_pages,
|
||||
page: (params[:page] || DEFAULT_PAGE).to_i,
|
||||
page: params[:page].to_i,
|
||||
per_page: (params[:per_page] || DEFAULT_PER_PAGE).to_i
|
||||
}
|
||||
end
|
||||
|
||||
def pagination_required?
|
||||
params[:page].present?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -23,7 +23,11 @@ class ApplicationController < ActionController::Base
|
||||
referer_path = OpenFoodNetwork::RefererParser.path(request.referer)
|
||||
if referer_path
|
||||
is_checkout_path_the_referer = [main_app.checkout_path].include?(referer_path)
|
||||
session["spree_user_return_to"] = is_checkout_path_the_referer ? referer_path : root_path
|
||||
session["spree_user_return_to"] = if is_checkout_path_the_referer
|
||||
referer_path
|
||||
else
|
||||
main_app.root_path
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -48,11 +52,11 @@ class ApplicationController < ActionController::Base
|
||||
def after_sign_in_path_for(resource_or_scope)
|
||||
return session[:shopfront_redirect] if session[:shopfront_redirect]
|
||||
|
||||
stored_location_for(resource_or_scope) || signed_in_root_path(resource_or_scope)
|
||||
stored_location_for(resource_or_scope) || main_app.root_path
|
||||
end
|
||||
|
||||
def after_sign_out_path_for(_resource_or_scope)
|
||||
session[:shopfront_redirect] || root_path
|
||||
session[:shopfront_redirect] || main_app.root_path
|
||||
end
|
||||
|
||||
private
|
||||
@@ -74,7 +78,7 @@ class ApplicationController < ActionController::Base
|
||||
|
||||
def require_distributor_chosen
|
||||
unless @distributor = current_distributor
|
||||
redirect_to spree.root_path
|
||||
redirect_to main_app.root_path
|
||||
false
|
||||
end
|
||||
end
|
||||
@@ -86,26 +90,29 @@ class ApplicationController < ActionController::Base
|
||||
end
|
||||
|
||||
def check_hub_ready_for_checkout
|
||||
# This condition is more rigourous than required by development to avoid coupling this
|
||||
# condition to every controller spec
|
||||
if current_distributor && current_order &&
|
||||
current_distributor.respond_to?(:ready_for_checkout?) &&
|
||||
!current_distributor.ready_for_checkout?
|
||||
|
||||
if current_distributor_closed?
|
||||
current_order.empty!
|
||||
current_order.set_distribution! nil, nil
|
||||
flash[:info] = "The hub you have selected is temporarily closed for orders. Please try again later."
|
||||
redirect_to root_url
|
||||
flash[:info] = "The hub you have selected is temporarily closed for orders. "\
|
||||
"Please try again later."
|
||||
redirect_to main_app.root_url
|
||||
end
|
||||
end
|
||||
|
||||
def current_distributor_closed?
|
||||
current_distributor &&
|
||||
current_order &&
|
||||
current_distributor.respond_to?(:ready_for_checkout?) &&
|
||||
!current_distributor.ready_for_checkout?
|
||||
end
|
||||
|
||||
def check_order_cycle_expiry
|
||||
if current_order_cycle.andand.closed?
|
||||
session[:expired_order_cycle_id] = current_order_cycle.id
|
||||
current_order.empty!
|
||||
current_order.set_order_cycle! nil
|
||||
flash[:info] = "The order cycle you've selected has just closed. Please try again!"
|
||||
redirect_to root_url
|
||||
redirect_to main_app.root_url
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -15,11 +15,10 @@ module Spree
|
||||
# Warn the user when they have an active order cycle with hubs that are not ready
|
||||
# for checkout (ie. does not have valid shipping and payment methods).
|
||||
def warn_invalid_order_cycles
|
||||
distributors = active_distributors_not_ready_for_checkout
|
||||
return if flash[:notice].present?
|
||||
|
||||
return if distributors.empty? || flash[:notice].present?
|
||||
|
||||
flash[:notice] = active_distributors_not_ready_for_checkout_message(distributors)
|
||||
warning = OrderCycleWarning.new(spree_current_user).call
|
||||
flash[:notice] = warning if warning.present?
|
||||
end
|
||||
|
||||
# This is in Spree::Core::ControllerHelpers::Auth
|
||||
@@ -30,7 +29,7 @@ module Spree
|
||||
redirect_to '/unauthorized'
|
||||
else
|
||||
store_location
|
||||
redirect_to root_path(anchor: "login?after_login=#{request.env['PATH_INFO']}")
|
||||
redirect_to main_app.root_path(anchor: "login?after_login=#{request.env['PATH_INFO']}")
|
||||
end
|
||||
end
|
||||
|
||||
@@ -93,24 +92,6 @@ module Spree
|
||||
|
||||
private
|
||||
|
||||
def active_distributors_not_ready_for_checkout
|
||||
ocs = OrderCycle.managed_by(spree_current_user).active
|
||||
distributors = ocs.includes(:distributors).map(&:distributors).flatten.uniq
|
||||
Enterprise.where('enterprises.id IN (?)', distributors).not_ready_for_checkout
|
||||
end
|
||||
|
||||
def active_distributors_not_ready_for_checkout_message(distributors)
|
||||
distributor_names = distributors.map(&:name).join ', '
|
||||
|
||||
if distributors.count > 1
|
||||
I18n.t(:active_distributors_not_ready_for_checkout_message_plural,
|
||||
distributor_names: distributor_names)
|
||||
else
|
||||
I18n.t(:active_distributors_not_ready_for_checkout_message_singular,
|
||||
distributor_names: distributor_names)
|
||||
end
|
||||
end
|
||||
|
||||
def html_request?
|
||||
request.format.html?
|
||||
end
|
||||
@@ -121,13 +102,17 @@ module Spree
|
||||
|
||||
def render_as_json(data, options = {})
|
||||
ams_prefix = options.delete :ams_prefix
|
||||
if [Array, ActiveRecord::Relation].include? data.class
|
||||
if each_serializer_required?(data)
|
||||
render options.merge(json: data, each_serializer: serializer(ams_prefix))
|
||||
else
|
||||
render options.merge(json: data, serializer: serializer(ams_prefix))
|
||||
end
|
||||
end
|
||||
|
||||
def each_serializer_required?(data)
|
||||
['Array', 'ActiveRecord::Relation'].include?(data.class.name)
|
||||
end
|
||||
|
||||
def serializer(ams_prefix)
|
||||
unless ams_prefix.nil? || ams_prefix_whitelist.include?(ams_prefix.to_sym)
|
||||
raise "Suffix '#{ams_prefix}' not found in ams_prefix_whitelist for #{self.class.name}."
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
module Spree
|
||||
module Admin
|
||||
module Orders
|
||||
class CustomerDetailsController < Spree::Admin::BaseController
|
||||
before_filter :load_order
|
||||
before_filter :check_authorization
|
||||
before_filter :set_guest_checkout_status, only: :update
|
||||
|
||||
def show
|
||||
edit
|
||||
render action: :edit
|
||||
end
|
||||
|
||||
def edit
|
||||
country_id = Address.default.country.id
|
||||
@order.build_bill_address(country_id: country_id) if @order.bill_address.nil?
|
||||
@order.build_ship_address(country_id: country_id) if @order.ship_address.nil?
|
||||
end
|
||||
|
||||
def update
|
||||
if @order.update_attributes(params[:order])
|
||||
if params[:guest_checkout] == "false"
|
||||
@order.associate_user!(Spree.user_class.find_by_email(@order.email))
|
||||
end
|
||||
|
||||
AdvanceOrderService.new(@order).call
|
||||
|
||||
@order.shipments.map(&:refresh_rates)
|
||||
flash[:success] = Spree.t('customer_details_updated')
|
||||
redirect_to admin_order_customer_path(@order)
|
||||
else
|
||||
render action: :edit
|
||||
end
|
||||
end
|
||||
|
||||
# Inherit CanCan permissions for the current order
|
||||
def model_class
|
||||
load_order unless @order
|
||||
@order
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def load_order
|
||||
@order = Order.find_by_number!(params[:order_id], include: :adjustments)
|
||||
end
|
||||
|
||||
def check_authorization
|
||||
load_order
|
||||
session[:access_token] ||= params[:token]
|
||||
|
||||
resource = @order
|
||||
action = params[:action].to_sym
|
||||
action = :edit if action == :show # show route renders :edit for this controller
|
||||
|
||||
authorize! action, resource, session[:access_token]
|
||||
end
|
||||
|
||||
def set_guest_checkout_status
|
||||
registered_user = Spree::User.find_by_email(params[:order][:email])
|
||||
|
||||
params[:order][:guest_checkout] = registered_user.nil?
|
||||
|
||||
return unless registered_user
|
||||
|
||||
@order.user_id = registered_user.id
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,49 +0,0 @@
|
||||
Spree::Admin::Orders::CustomerDetailsController.class_eval do
|
||||
before_filter :check_authorization
|
||||
before_filter :set_guest_checkout_status, only: :update
|
||||
|
||||
def update
|
||||
if @order.update_attributes(params[:order])
|
||||
if params[:guest_checkout] == "false"
|
||||
@order.associate_user!(Spree.user_class.find_by_email(@order.email))
|
||||
end
|
||||
|
||||
AdvanceOrderService.new(@order).call
|
||||
|
||||
@order.shipments.map &:refresh_rates
|
||||
flash[:success] = Spree.t('customer_details_updated')
|
||||
redirect_to admin_order_customer_path(@order)
|
||||
else
|
||||
render action: :edit
|
||||
end
|
||||
end
|
||||
|
||||
# Inherit CanCan permissions for the current order
|
||||
def model_class
|
||||
load_order unless @order
|
||||
@order
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def check_authorization
|
||||
load_order
|
||||
session[:access_token] ||= params[:token]
|
||||
|
||||
resource = @order
|
||||
action = params[:action].to_sym
|
||||
action = :edit if action == :show # show route renders :edit for this controller
|
||||
|
||||
authorize! action, resource, session[:access_token]
|
||||
end
|
||||
|
||||
def set_guest_checkout_status
|
||||
registered_user = Spree::User.find_by_email(params[:order][:email])
|
||||
|
||||
params[:order][:guest_checkout] = registered_user.nil?
|
||||
|
||||
return unless registered_user
|
||||
|
||||
@order.user_id = registered_user.id
|
||||
end
|
||||
end
|
||||
150
app/controllers/spree/admin/orders_controller.rb
Normal file
150
app/controllers/spree/admin/orders_controller.rb
Normal file
@@ -0,0 +1,150 @@
|
||||
require 'open_food_network/spree_api_key_loader'
|
||||
|
||||
module Spree
|
||||
module Admin
|
||||
class OrdersController < Spree::Admin::BaseController
|
||||
require 'spree/core/gateway_error'
|
||||
include OpenFoodNetwork::SpreeApiKeyLoader
|
||||
helper CheckoutHelper
|
||||
|
||||
before_filter :load_order, only: [:edit, :update, :fire, :resend,
|
||||
:invoice, :print, :print_ticket]
|
||||
before_filter :load_distribution_choices, only: [:new, :edit, :update]
|
||||
|
||||
# Ensure that the distributor is set for an order when
|
||||
before_filter :ensure_distribution, only: :new
|
||||
|
||||
# After updating an order, the fees should be updated as well
|
||||
# Currently, adding or deleting line items does not trigger updating the
|
||||
# fees! This is a quick fix for that.
|
||||
# TODO: update fees when adding/removing line items
|
||||
# instead of the update_distribution_charge method.
|
||||
after_filter :update_distribution_charge, only: :update
|
||||
|
||||
before_filter :require_distributor_abn, only: :invoice
|
||||
|
||||
respond_to :html, :json
|
||||
|
||||
def new
|
||||
@order = Order.create
|
||||
@order.created_by = try_spree_current_user
|
||||
@order.save
|
||||
redirect_to edit_admin_order_url(@order)
|
||||
end
|
||||
|
||||
def edit
|
||||
@order.shipments.map(&:refresh_rates)
|
||||
|
||||
AdvanceOrderService.new(@order).call
|
||||
|
||||
# The payment step shows an error of 'No pending payments'
|
||||
# Clearing the errors from the order object will stop this error
|
||||
# appearing on the edit page where we don't want it to.
|
||||
@order.errors.clear
|
||||
end
|
||||
|
||||
def update
|
||||
unless @order.update_attributes(params[:order]) && @order.line_items.present?
|
||||
if @order.line_items.empty?
|
||||
@order.errors.add(:line_items, Spree.t('errors.messages.blank'))
|
||||
end
|
||||
return redirect_to(edit_admin_order_path(@order),
|
||||
flash: { error: @order.errors.full_messages.join(', ') })
|
||||
end
|
||||
|
||||
@order.update!
|
||||
if @order.complete?
|
||||
redirect_to edit_admin_order_path(@order)
|
||||
else
|
||||
# Jump to next step if order is not complete
|
||||
redirect_to admin_order_customer_path(@order)
|
||||
end
|
||||
end
|
||||
|
||||
def bulk_management
|
||||
load_spree_api_key
|
||||
end
|
||||
|
||||
def fire
|
||||
event = params[:e]
|
||||
if @order.public_send(event.to_s)
|
||||
flash[:success] = Spree.t(:order_updated)
|
||||
else
|
||||
flash[:error] = Spree.t(:cannot_perform_operation)
|
||||
end
|
||||
rescue Spree::Core::GatewayError => e
|
||||
flash[:error] = e.message.to_s
|
||||
ensure
|
||||
redirect_to :back
|
||||
end
|
||||
|
||||
def resend
|
||||
Spree::OrderMailer.confirm_email_for_customer(@order.id, true).deliver
|
||||
flash[:success] = t(:order_email_resent)
|
||||
|
||||
respond_with(@order) { |format| format.html { redirect_to :back } }
|
||||
end
|
||||
|
||||
def invoice
|
||||
pdf = InvoiceRenderer.new.render_to_string(@order)
|
||||
|
||||
Spree::OrderMailer.invoice_email(@order.id, pdf).deliver
|
||||
flash[:success] = t('admin.orders.invoice_email_sent')
|
||||
|
||||
respond_with(@order) { |format| format.html { redirect_to edit_admin_order_path(@order) } }
|
||||
end
|
||||
|
||||
def print
|
||||
render InvoiceRenderer.new.args(@order)
|
||||
end
|
||||
|
||||
def print_ticket
|
||||
render template: "spree/admin/orders/ticket", layout: false
|
||||
end
|
||||
|
||||
def update_distribution_charge
|
||||
@order.update_distribution_charge!
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def load_order
|
||||
@order = Order.find_by_number!(params[:id], include: :adjustments) if params[:id]
|
||||
authorize! action, @order
|
||||
end
|
||||
|
||||
def model_class
|
||||
Spree::Order
|
||||
end
|
||||
|
||||
def require_distributor_abn
|
||||
return if @order.distributor.abn.present?
|
||||
|
||||
flash[:error] = t(:must_have_valid_business_number,
|
||||
enterprise_name: @order.distributor.name)
|
||||
respond_with(@order) { |format| format.html { redirect_to edit_admin_order_path(@order) } }
|
||||
end
|
||||
|
||||
def load_distribution_choices
|
||||
@shops = Enterprise.is_distributor.managed_by(spree_current_user).by_name
|
||||
|
||||
ocs = OrderCycle.managed_by(spree_current_user)
|
||||
@order_cycles = ocs.soonest_closing +
|
||||
ocs.soonest_opening +
|
||||
ocs.closed +
|
||||
ocs.undated
|
||||
end
|
||||
|
||||
def ensure_distribution
|
||||
unless @order
|
||||
@order = Spree::Order.new
|
||||
@order.generate_order_number
|
||||
@order.save!
|
||||
end
|
||||
return if @order.distribution_set?
|
||||
|
||||
render 'set_distribution', locals: { order: @order }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,120 +0,0 @@
|
||||
require 'open_food_network/spree_api_key_loader'
|
||||
|
||||
Spree::Admin::OrdersController.class_eval do
|
||||
include OpenFoodNetwork::SpreeApiKeyLoader
|
||||
helper CheckoutHelper
|
||||
before_filter :load_order, only: %i[show edit update fire resend invoice print print_ticket]
|
||||
|
||||
before_filter :load_distribution_choices, only: [:new, :edit, :update]
|
||||
|
||||
# Ensure that the distributor is set for an order when
|
||||
before_filter :ensure_distribution, only: :new
|
||||
|
||||
# After updating an order, the fees should be updated as well
|
||||
# Currently, adding or deleting line items does not trigger updating the
|
||||
# fees! This is a quick fix for that.
|
||||
# TODO: update fees when adding/removing line items
|
||||
# instead of the update_distribution_charge method.
|
||||
after_filter :update_distribution_charge, only: :update
|
||||
|
||||
before_filter :require_distributor_abn, only: :invoice
|
||||
|
||||
respond_to :html, :json
|
||||
|
||||
def index
|
||||
# Overriding the action so we only render the page template. An angular request
|
||||
# within the page then fetches the data it needs from Api::OrdersController
|
||||
end
|
||||
|
||||
def bulk_management
|
||||
load_spree_api_key
|
||||
end
|
||||
|
||||
def edit
|
||||
@order.shipments.map &:refresh_rates
|
||||
|
||||
AdvanceOrderService.new(@order).call
|
||||
|
||||
# The payment step shows an error of 'No pending payments'
|
||||
# Clearing the errors from the order object will stop this error
|
||||
# appearing on the edit page where we don't want it to.
|
||||
@order.errors.clear
|
||||
end
|
||||
|
||||
# Re-implement spree method so that it redirects to edit instead of rendering edit
|
||||
# This allows page reloads while adding variants to the order (/edit), without being redirected to customer details page (/update)
|
||||
def update
|
||||
unless @order.update_attributes(params[:order]) && @order.line_items.present?
|
||||
@order.errors.add(:line_items, Spree.t('errors.messages.blank')) if @order.line_items.empty?
|
||||
return redirect_to edit_admin_order_path(@order), flash: { error: @order.errors.full_messages.join(', ') }
|
||||
end
|
||||
|
||||
@order.update!
|
||||
if @order.complete?
|
||||
redirect_to edit_admin_order_path(@order)
|
||||
else
|
||||
# Jump to next step if order is not complete
|
||||
redirect_to admin_order_customer_path(@order)
|
||||
end
|
||||
end
|
||||
|
||||
# Overwrite to use confirm_email_for_customer instead of confirm_email.
|
||||
# This uses a new template. See mailers/spree/order_mailer_decorator.rb.
|
||||
def resend
|
||||
Spree::OrderMailer.confirm_email_for_customer(@order.id, true).deliver
|
||||
flash[:success] = t(:order_email_resent)
|
||||
|
||||
respond_with(@order) { |format| format.html { redirect_to :back } }
|
||||
end
|
||||
|
||||
def invoice
|
||||
pdf = InvoiceRenderer.new.render_to_string(@order)
|
||||
|
||||
Spree::OrderMailer.invoice_email(@order.id, pdf).deliver
|
||||
flash[:success] = t('admin.orders.invoice_email_sent')
|
||||
|
||||
respond_with(@order) { |format| format.html { redirect_to edit_admin_order_path(@order) } }
|
||||
end
|
||||
|
||||
def print
|
||||
render InvoiceRenderer.new.args(@order)
|
||||
end
|
||||
|
||||
def print_ticket
|
||||
render template: "spree/admin/orders/ticket", layout: false
|
||||
end
|
||||
|
||||
def update_distribution_charge
|
||||
@order.update_distribution_charge!
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def require_distributor_abn
|
||||
if @order.distributor.abn.blank?
|
||||
flash[:error] = t(:must_have_valid_business_number, enterprise_name: @order.distributor.name)
|
||||
respond_with(@order) { |format| format.html { redirect_to edit_admin_order_path(@order) } }
|
||||
end
|
||||
end
|
||||
|
||||
def load_distribution_choices
|
||||
@shops = Enterprise.is_distributor.managed_by(spree_current_user).by_name
|
||||
|
||||
ocs = OrderCycle.managed_by(spree_current_user)
|
||||
@order_cycles = ocs.soonest_closing +
|
||||
ocs.soonest_opening +
|
||||
ocs.closed +
|
||||
ocs.undated
|
||||
end
|
||||
|
||||
def ensure_distribution
|
||||
unless @order
|
||||
@order = Spree::Order.new
|
||||
@order.generate_order_number
|
||||
@order.save!
|
||||
end
|
||||
unless @order.distribution_set?
|
||||
render 'set_distribution', locals: { order: @order }
|
||||
end
|
||||
end
|
||||
end
|
||||
129
app/controllers/spree/admin/payments_controller.rb
Normal file
129
app/controllers/spree/admin/payments_controller.rb
Normal file
@@ -0,0 +1,129 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Spree
|
||||
module Admin
|
||||
class PaymentsController < Spree::Admin::BaseController
|
||||
before_filter :load_order, except: [:show]
|
||||
before_filter :load_payment, only: [:fire, :show]
|
||||
before_filter :load_data
|
||||
before_filter :can_transition_to_payment
|
||||
|
||||
respond_to :html
|
||||
|
||||
def index
|
||||
@payments = @order.payments
|
||||
redirect_to new_admin_order_payment_url(@order) if @payments.empty?
|
||||
end
|
||||
|
||||
def new
|
||||
@payment = @order.payments.build
|
||||
end
|
||||
|
||||
def create
|
||||
@payment = @order.payments.build(object_params)
|
||||
load_payment_source
|
||||
|
||||
begin
|
||||
unless @payment.save
|
||||
redirect_to admin_order_payments_path(@order)
|
||||
return
|
||||
end
|
||||
|
||||
if @order.completed?
|
||||
@payment.process!
|
||||
flash[:success] = flash_message_for(@payment, :successfully_created)
|
||||
|
||||
redirect_to admin_order_payments_path(@order)
|
||||
else
|
||||
AdvanceOrderService.new(@order).call!
|
||||
|
||||
flash[:success] = Spree.t(:new_order_completed)
|
||||
redirect_to edit_admin_order_url(@order)
|
||||
end
|
||||
rescue Spree::Core::GatewayError => e
|
||||
flash[:error] = e.message.to_s
|
||||
redirect_to new_admin_order_payment_path(@order)
|
||||
end
|
||||
end
|
||||
|
||||
# When a user fires an event, take them back to where they came from
|
||||
# (we can't use respond_override because Spree no longer uses respond_with)
|
||||
def fire
|
||||
event = params[:e]
|
||||
return unless event && @payment.payment_source
|
||||
|
||||
# Because we have a transition method also called void, we do this to avoid conflicts.
|
||||
event = "void_transaction" if event == "void"
|
||||
if @payment.public_send("#{event}!")
|
||||
flash[:success] = t(:payment_updated)
|
||||
else
|
||||
flash[:error] = t(:cannot_perform_operation)
|
||||
end
|
||||
rescue Spree::Core::GatewayError => e
|
||||
flash[:error] = e.message
|
||||
ensure
|
||||
redirect_to request.referer
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def load_payment_source
|
||||
if @payment.payment_method.is_a?(Spree::Gateway) &&
|
||||
@payment.payment_method.payment_profiles_supported? &&
|
||||
params[:card].present? &&
|
||||
(params[:card] != 'new')
|
||||
@payment.source = CreditCard.find_by_id(params[:card])
|
||||
end
|
||||
end
|
||||
|
||||
def object_params
|
||||
if params[:payment] &&
|
||||
params[:payment_source] &&
|
||||
source_params = params.delete(:payment_source)[params[:payment][:payment_method_id]]
|
||||
params[:payment][:source_attributes] = source_params
|
||||
end
|
||||
params[:payment]
|
||||
end
|
||||
|
||||
def load_data
|
||||
@amount = params[:amount] || load_order.total
|
||||
|
||||
# Only show payments for the order's distributor
|
||||
@payment_methods = PaymentMethod.
|
||||
available(:back_end).
|
||||
select{ |pm| pm.has_distributor? @order.distributor }
|
||||
|
||||
@payment_method = if @payment && @payment.payment_method
|
||||
@payment.payment_method
|
||||
else
|
||||
@payment_methods.first
|
||||
end
|
||||
|
||||
@previous_cards = @order.credit_cards.with_payment_profile
|
||||
end
|
||||
|
||||
# At this point admin should have passed through Customer Details step
|
||||
# where order.next is called which leaves the order in payment step
|
||||
#
|
||||
# Orders in complete step also allows to access this controller
|
||||
#
|
||||
# Otherwise redirect user to that step
|
||||
def can_transition_to_payment
|
||||
return if @order.payment? || @order.complete?
|
||||
|
||||
flash[:notice] = Spree.t(:fill_in_customer_info)
|
||||
redirect_to edit_admin_order_customer_url(@order)
|
||||
end
|
||||
|
||||
def load_order
|
||||
@order = Order.find_by_number!(params[:order_id])
|
||||
authorize! action, @order
|
||||
@order
|
||||
end
|
||||
|
||||
def load_payment
|
||||
@payment = Payment.find(params[:id])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,59 +0,0 @@
|
||||
Spree::Admin::PaymentsController.class_eval do
|
||||
append_before_filter :filter_payment_methods
|
||||
|
||||
def create
|
||||
@payment = @order.payments.build(object_params)
|
||||
if @payment.payment_method.is_a?(Spree::Gateway) && @payment.payment_method.payment_profiles_supported? && params[:card].present? && (params[:card] != 'new')
|
||||
@payment.source = CreditCard.find_by_id(params[:card])
|
||||
end
|
||||
|
||||
begin
|
||||
unless @payment.save
|
||||
redirect_to admin_order_payments_path(@order)
|
||||
return
|
||||
end
|
||||
|
||||
if @order.completed?
|
||||
@payment.process!
|
||||
flash[:success] = flash_message_for(@payment, :successfully_created)
|
||||
|
||||
redirect_to admin_order_payments_path(@order)
|
||||
else
|
||||
AdvanceOrderService.new(@order).call!
|
||||
|
||||
flash[:success] = Spree.t(:new_order_completed)
|
||||
redirect_to edit_admin_order_url(@order)
|
||||
end
|
||||
rescue Spree::Core::GatewayError => e
|
||||
flash[:error] = e.message.to_s
|
||||
redirect_to new_admin_order_payment_path(@order)
|
||||
end
|
||||
end
|
||||
|
||||
# When a user fires an event, take them back to where they came from
|
||||
# (we can't use respond_override because Spree no longer uses respond_with)
|
||||
def fire
|
||||
event = params[:e]
|
||||
return unless event && @payment.payment_source
|
||||
|
||||
# Because we have a transition method also called void, we do this to avoid conflicts.
|
||||
event = "void_transaction" if event == "void"
|
||||
if @payment.public_send("#{event}!")
|
||||
flash[:success] = t(:payment_updated)
|
||||
else
|
||||
flash[:error] = t(:cannot_perform_operation)
|
||||
end
|
||||
rescue Spree::Core::GatewayError => e
|
||||
flash[:error] = e.message
|
||||
ensure
|
||||
redirect_to request.referer
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Only show payments for the order's distributor
|
||||
def filter_payment_methods
|
||||
@payment_methods = @payment_methods.select{ |pm| pm.has_distributor? @order.distributor }
|
||||
@payment_method ||= @payment_methods.first
|
||||
end
|
||||
end
|
||||
43
app/controllers/spree/admin/search_controller.rb
Normal file
43
app/controllers/spree/admin/search_controller.rb
Normal file
@@ -0,0 +1,43 @@
|
||||
module Spree
|
||||
module Admin
|
||||
class SearchController < Spree::Admin::BaseController
|
||||
# http://spreecommerce.com/blog/2010/11/02/json-hijacking-vulnerability/
|
||||
before_filter :check_json_authenticity, only: :index
|
||||
respond_to :json
|
||||
|
||||
def known_users
|
||||
@users = if exact_match = Spree.user_class.find_by_email(params[:q])
|
||||
[exact_match]
|
||||
else
|
||||
spree_current_user.known_users.ransack(ransack_hash).result.limit(10)
|
||||
end
|
||||
|
||||
render json: @users, each_serializer: ::Api::Admin::UserSerializer
|
||||
end
|
||||
|
||||
def customers
|
||||
@customers = []
|
||||
if spree_current_user.enterprises.pluck(:id).include? params[:distributor_id].to_i
|
||||
@customers = Customer.
|
||||
ransack(m: 'or', email_start: params[:q], name_start: params[:q]).
|
||||
result.
|
||||
where(enterprise_id: params[:distributor_id])
|
||||
end
|
||||
render json: @customers, each_serializer: ::Api::Admin::CustomerSerializer
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def ransack_hash
|
||||
{
|
||||
m: 'or',
|
||||
email_start: params[:q],
|
||||
ship_address_firstname_start: params[:q],
|
||||
ship_address_lastname_start: params[:q],
|
||||
bill_address_firstname_start: params[:q],
|
||||
bill_address_lastname_start: params[:q]
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,36 +0,0 @@
|
||||
Spree::Admin::SearchController.class_eval do
|
||||
def known_users
|
||||
@users = if exact_match = Spree.user_class.find_by_email(params[:q])
|
||||
[exact_match]
|
||||
else
|
||||
spree_current_user.known_users.ransack(
|
||||
m: 'or',
|
||||
email_start: params[:q],
|
||||
ship_address_firstname_start: params[:q],
|
||||
ship_address_lastname_start: params[:q],
|
||||
bill_address_firstname_start: params[:q],
|
||||
bill_address_lastname_start: params[:q]
|
||||
).result.limit(10)
|
||||
end
|
||||
|
||||
render json: @users, each_serializer: Api::Admin::UserSerializer
|
||||
end
|
||||
|
||||
def customers
|
||||
@customers = if spree_current_user.enterprises.pluck(:id).include? params[:distributor_id].to_i
|
||||
Customer.ransack(m: 'or', email_start: params[:q], name_start: params[:q])
|
||||
.result.where(enterprise_id: params[:distributor_id])
|
||||
else
|
||||
[]
|
||||
end
|
||||
|
||||
render json: @customers, each_serializer: Api::Admin::CustomerSerializer
|
||||
end
|
||||
|
||||
def users_with_ams
|
||||
users_without_ams
|
||||
render json: @users, each_serializer: Api::Admin::UserSerializer
|
||||
end
|
||||
|
||||
alias_method_chain :users, :ams
|
||||
end
|
||||
@@ -71,7 +71,7 @@ module Spree
|
||||
@order = order_to_update
|
||||
unless @order
|
||||
flash[:error] = t(:order_not_found)
|
||||
redirect_to(root_path) && return
|
||||
redirect_to(main_app.root_path) && return
|
||||
end
|
||||
|
||||
if @order.update_attributes(params[:order])
|
||||
|
||||
@@ -32,7 +32,7 @@ module Spree
|
||||
session[:guest_token] = nil
|
||||
end
|
||||
|
||||
redirect_back_or_default(root_url)
|
||||
redirect_back_or_default(main_app.root_url)
|
||||
else
|
||||
render :new
|
||||
end
|
||||
|
||||
@@ -1,6 +1,17 @@
|
||||
module Spree
|
||||
module Admin
|
||||
module OrdersHelper
|
||||
def event_links
|
||||
links = []
|
||||
links << event_link("cancel") if @order.can_cancel?
|
||||
links << event_link("resume") if @order.can_resume?
|
||||
links.join(' ').html_safe
|
||||
end
|
||||
|
||||
def line_item_shipment_price(line_item, quantity)
|
||||
Spree::Money.new(line_item.price * quantity, currency: line_item.currency)
|
||||
end
|
||||
|
||||
def order_links(order)
|
||||
@order ||= order
|
||||
links = []
|
||||
@@ -100,6 +111,14 @@ module Spree
|
||||
icon: 'icon-trash',
|
||||
confirm: t(:are_you_sure) }
|
||||
end
|
||||
|
||||
def event_link(event)
|
||||
button_link_to(Spree.t(event),
|
||||
fire_admin_order_url(@order, e: event),
|
||||
method: :put,
|
||||
icon: "icon-#{event}",
|
||||
data: { confirm: Spree.t(:order_sure_want_to, event: Spree.t(event)) })
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
11
app/helpers/spree/admin/payments_helper.rb
Normal file
11
app/helpers/spree/admin/payments_helper.rb
Normal file
@@ -0,0 +1,11 @@
|
||||
module Spree
|
||||
module Admin
|
||||
module PaymentsHelper
|
||||
def payment_method_name(payment)
|
||||
# hack to allow us to retrieve the name of a "deleted" payment method
|
||||
id = payment.payment_method_id
|
||||
Spree::PaymentMethod.find_with_destroyed(id).name
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -9,6 +9,6 @@ Spree::BaseMailer.class_eval do
|
||||
|
||||
def roadie_options
|
||||
# This lets us specify assets using relative paths in email templates
|
||||
super.merge(url_options: { host: URI(spree.root_url).host })
|
||||
super.merge(url_options: { host: URI(main_app.root_url).host })
|
||||
end
|
||||
end
|
||||
|
||||
@@ -25,7 +25,8 @@ class Enterprise < ActiveRecord::Base
|
||||
has_many :relationships_as_child, class_name: 'EnterpriseRelationship',
|
||||
foreign_key: 'child_id',
|
||||
dependent: :destroy
|
||||
has_and_belongs_to_many :groups, class_name: 'EnterpriseGroup'
|
||||
has_and_belongs_to_many :groups, join_table: 'enterprise_groups_enterprises',
|
||||
class_name: 'EnterpriseGroup'
|
||||
has_many :producer_properties, foreign_key: 'producer_id'
|
||||
has_many :properties, through: :producer_properties
|
||||
has_many :supplied_products, class_name: 'Spree::Product',
|
||||
@@ -115,7 +116,10 @@ class Enterprise < ActiveRecord::Base
|
||||
scope :not_ready_for_checkout, lambda {
|
||||
# When ready_for_checkout is empty, return all rows when there are no enterprises ready for
|
||||
# checkout.
|
||||
ready_enterprises = Enterprise.ready_for_checkout.select('enterprises.id')
|
||||
ready_enterprises = Enterprise.ready_for_checkout.
|
||||
except(:select).
|
||||
select('DISTINCT enterprises.id')
|
||||
|
||||
if ready_enterprises.present?
|
||||
where("enterprises.id NOT IN (?)", ready_enterprises)
|
||||
else
|
||||
|
||||
@@ -5,7 +5,7 @@ class EnterpriseGroup < ActiveRecord::Base
|
||||
include PermalinkGenerator
|
||||
acts_as_list
|
||||
|
||||
has_and_belongs_to_many :enterprises
|
||||
has_and_belongs_to_many :enterprises, join_table: 'enterprise_groups_enterprises'
|
||||
belongs_to :owner, class_name: 'Spree::User', foreign_key: :owner_id, inverse_of: :owned_groups
|
||||
belongs_to :address, class_name: 'Spree::Address'
|
||||
accepts_nested_attributes_for :address
|
||||
|
||||
@@ -63,7 +63,7 @@ class OrderCycle < ActiveRecord::Base
|
||||
if user.has_spree_role?('admin')
|
||||
scoped
|
||||
else
|
||||
where('coordinator_id IN (?)', user.enterprises.map(&:id))
|
||||
where(coordinator_id: user.enterprises)
|
||||
end
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'open_food_network/order_cycle_permissions'
|
||||
|
||||
class ExchangeProductsRenderer
|
||||
def initialize(order_cycle, user)
|
||||
@order_cycle = order_cycle
|
||||
@@ -12,6 +16,14 @@ class ExchangeProductsRenderer
|
||||
end
|
||||
end
|
||||
|
||||
def exchange_variants(incoming, enterprise)
|
||||
variants_relation = Spree::Variant.
|
||||
not_master.
|
||||
where(product_id: exchange_products(incoming, enterprise).select(&:id))
|
||||
|
||||
filter_visible(variants_relation)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def products_for_incoming_exchange(enterprise)
|
||||
@@ -21,12 +33,16 @@ class ExchangeProductsRenderer
|
||||
def supplied_products(enterprises_query_matcher)
|
||||
products_relation = Spree::Product.where(supplier_id: enterprises_query_matcher)
|
||||
|
||||
filter_visible(products_relation)
|
||||
end
|
||||
|
||||
def filter_visible(relation)
|
||||
if @order_cycle.present? &&
|
||||
@order_cycle.prefers_product_selection_from_coordinator_inventory_only?
|
||||
products_relation = products_relation.visible_for(@order_cycle.coordinator)
|
||||
relation = relation.visible_for(@order_cycle.coordinator)
|
||||
end
|
||||
|
||||
products_relation
|
||||
relation
|
||||
end
|
||||
|
||||
def products_for_outgoing_exchange
|
||||
|
||||
37
app/services/order_cycle_warning.rb
Normal file
37
app/services/order_cycle_warning.rb
Normal file
@@ -0,0 +1,37 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class OrderCycleWarning
|
||||
def initialize(current_user)
|
||||
@current_user = current_user
|
||||
end
|
||||
|
||||
def call
|
||||
distributors = active_distributors_not_ready_for_checkout
|
||||
|
||||
return if distributors.empty?
|
||||
|
||||
active_distributors_not_ready_for_checkout_message(distributors)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :current_user
|
||||
|
||||
def active_distributors_not_ready_for_checkout
|
||||
ocs = OrderCycle.managed_by(current_user).active
|
||||
distributors = ocs.includes(:distributors).map(&:distributors).flatten.uniq
|
||||
Enterprise.where(id: distributors.map(&:id)).not_ready_for_checkout
|
||||
end
|
||||
|
||||
def active_distributors_not_ready_for_checkout_message(distributors)
|
||||
distributor_names = distributors.map(&:name).join ', '
|
||||
|
||||
if distributors.count > 1
|
||||
I18n.t(:active_distributors_not_ready_for_checkout_message_plural,
|
||||
distributor_names: distributor_names)
|
||||
else
|
||||
I18n.t(:active_distributors_not_ready_for_checkout_message_singular,
|
||||
distributor_names: distributor_names)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -54,7 +54,7 @@
|
||||
.row{ ng: { show: "Enterprise.sells == 'own' || Enterprise.sells == 'any'" } }
|
||||
.three.columns.alpha
|
||||
= f.label :permalink, t('.permalink')
|
||||
%div{'ofn-with-tip' => t('.permalink_tip', link: spree.root_url)}
|
||||
%div{'ofn-with-tip' => t('.permalink_tip', link: main_app.root_url)}
|
||||
%a= t('admin.whats_this')
|
||||
.six.columns
|
||||
= f.text_field :permalink, { 'ng-model' => "Enterprise.permalink", placeholder: "eg. your-shop-name", 'ng-model-options' => "{ updateOn: 'default blur', debounce: {'default': 300, 'blur': 0} }" }
|
||||
@@ -69,7 +69,7 @@
|
||||
%div{'ofn-with-tip' => t('.link_to_front_tip')}
|
||||
%a= t('admin.whats_this')
|
||||
.eight.columns.omega
|
||||
= surround spree.root_url, "/shop" do
|
||||
= surround main_app.root_url, "/shop" do
|
||||
{{Enterprise.permalink}}
|
||||
.row
|
||||
.three.columns.alpha
|
||||
|
||||
@@ -42,10 +42,10 @@
|
||||
%tr
|
||||
%td{:align => "center"}
|
||||
%p
|
||||
%a{:href => "#{ URI.join(spree.root_url, Spree::Config.footer_tos_url).to_s }", :target => "_blank"}
|
||||
%a{:href => "#{ URI.join(main_app.root_url, Spree::Config.footer_tos_url).to_s }", :target => "_blank"}
|
||||
= t :terms_of_service
|
||||
|
|
||||
%a{:href => "#{ spree.root_url }"}
|
||||
%a{:href => "#{ main_app.root_url }"}
|
||||
= Spree::Config[:site_name]
|
||||
/ | <a href="#"><unsubscribe>Unsubscribe</unsubscribe></a>
|
||||
%td
|
||||
|
||||
@@ -94,7 +94,7 @@
|
||||
|
||||
.row
|
||||
.small-12.medium-3.medium-offset-2.columns.text-left
|
||||
%a{href: root_path}
|
||||
%a{href: main_app.root_path}
|
||||
%img{src: ContentConfig.footer_logo.url, width: "220"}
|
||||
.small-12.medium-5.columns.text-left
|
||||
%p.text-small
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
%section.top-bar-section
|
||||
%ul.left
|
||||
%li.ofn-logo
|
||||
%a{href: root_path}
|
||||
%a{href: main_app.root_path}
|
||||
%img{src: ContentConfig.logo.url}
|
||||
%li.powered-by
|
||||
%img{src: '/favicon.ico'}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
%section.left
|
||||
.ofn-logo
|
||||
%a{href: root_path}
|
||||
%a{href: main_app.root_path}
|
||||
%img{src: ContentConfig.logo_mobile.url, srcset: ContentConfig.logo_mobile_svg.url, width: "75", height: "26"}
|
||||
|
||||
%section.right{"ng-cloak" => true}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
%aside.left-off-canvas-menu.show-for-medium-down{ ng: { controller: "OffcanvasCtrl" } }
|
||||
%ul.off-canvas-list
|
||||
%li.ofn-logo
|
||||
%a{href: root_path}
|
||||
%a{href: main_app.root_path}
|
||||
%img{src: ContentConfig.logo_mobile.url, srcset: ContentConfig.logo_mobile_svg.url, width: "75", height: "26"}
|
||||
- [*1..7].each do |menu_number|
|
||||
- menu_name = "menu_#{menu_number}"
|
||||
|
||||
@@ -9,6 +9,6 @@
|
||||
.alert-box.blocked-cookies.hidden
|
||||
%p= t('blocked_cookies_alert')
|
||||
|
||||
%a.button.allow{target: '_blank', href: "#{spree.root_url}embedded_shopfront/shopfront_session/"}
|
||||
%a.button.allow{target: '_blank', href: "#{main_app.root_url}embedded_shopfront/shopfront_session/"}
|
||||
= t('allow_cookies')
|
||||
|
||||
@@ -25,4 +25,4 @@
|
||||
= button Spree.t('actions.update'), 'icon-refresh'
|
||||
|
||||
- content_for :head do
|
||||
= javascript_include_tag 'admin/address_states.js'
|
||||
= javascript_include_tag 'admin/spree/orders/address_states.js'
|
||||
|
||||
72
app/views/spree/admin/shared/_address_form.html.erb
Normal file
72
app/views/spree/admin/shared/_address_form.html.erb
Normal file
@@ -0,0 +1,72 @@
|
||||
<% if use_billing %>
|
||||
<div class="field" style="position: absolute;margin-top: -15px;right: 0;">
|
||||
<span data-hook="use_billing">
|
||||
<%= check_box_tag 'order[use_billing]', '1', (!(@order.bill_address.empty? && @order.ship_address.empty?) && @order.bill_address.same_as?(@order.ship_address)) %>
|
||||
<%= label_tag 'order[use_billing]', Spree.t(:use_billing_address) %>
|
||||
</span>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<% is_shipping_address = name == Spree.t(:shipping_address) %>
|
||||
<% shipping_or_billing = is_shipping_address ? 'shipping' : 'billing' %>
|
||||
<% s_or_b = is_shipping_address ? 's' : 'b' %>
|
||||
|
||||
<div id="<%= is_shipping_address ? 'shipping' : 'billing' %>" style="display: <%= (use_billing && (!(@order.bill_address.empty? && @order.ship_address.empty?) && @order.bill_address.eql?(@order.ship_address))) ? 'none' : 'block' %>" data-hook="address_fields">
|
||||
<div class="field <%= "#{shipping_or_billing}-row" %>">
|
||||
<%= f.label :firstname, Spree.t(:first_name) + ':' %>
|
||||
<%= f.text_field :firstname, :class => 'fullwidth' %>
|
||||
</div>
|
||||
<div class="field <%= "#{shipping_or_billing}-row" %>">
|
||||
<%= f.label :lastname, Spree.t(:last_name) + ':' %>
|
||||
<%= f.text_field :lastname, :class => 'fullwidth' %>
|
||||
</div>
|
||||
<% if Spree::Config[:company] %>
|
||||
<div class="field <%= "#{shipping_or_billing}-row" %>">
|
||||
<%= f.label :company, Spree.t(:company) + ':' %>
|
||||
<%= f.text_field :company, :class => 'fullwidth' %>
|
||||
</div>
|
||||
<% end %>
|
||||
<div class="field <%= "#{shipping_or_billing}-row" %>">
|
||||
<%= f.label :address1, Spree.t(:street_address) + ':' %>
|
||||
<%= f.text_field :address1, :class => 'fullwidth' %>
|
||||
</div>
|
||||
<div class="field <%= "#{shipping_or_billing}-row" %>">
|
||||
<%= f.label :address2, Spree.t(:street_address_2) + ':' %>
|
||||
<%= f.text_field :address2, :class => 'fullwidth' %>
|
||||
</div>
|
||||
<div class="field <%= "#{shipping_or_billing}-row" %>">
|
||||
<%= f.label :city, Spree.t(:city) + ':' %>
|
||||
<%= f.text_field :city, :class => 'fullwidth' %>
|
||||
</div>
|
||||
<div class="field <%= "#{shipping_or_billing}-row" %>">
|
||||
<%= f.label :zipcode, Spree.t(:zip) + ':' %>
|
||||
<%= f.text_field :zipcode, :class => 'fullwidth' %>
|
||||
</div>
|
||||
<div class="field <%= "#{shipping_or_billing}-row" %>">
|
||||
<%= f.label :country_id, Spree.t(:country) + ':' %>
|
||||
<span id="<%= s_or_b %>country">
|
||||
<%= f.collection_select :country_id, available_countries, :id, :name, {}, {:class => 'select2 fullwidth'} %>
|
||||
</span>
|
||||
</div>
|
||||
<div class="field <%= "#{shipping_or_billing}-row" %>">
|
||||
<%= f.label :state_id, Spree.t(:state) + ':' %>
|
||||
<span id="<%= s_or_b %>state">
|
||||
<%= f.text_field :state_name,
|
||||
:style => "display: #{f.object.country.states.empty? ? 'block' : 'none' };",
|
||||
:disabled => !f.object.country.states.empty?, :class => 'fullwidth state_name' %>
|
||||
<%= f.collection_select :state_id, f.object.country.states.sort, :id, :name, {:include_blank => true}, {:class => 'select2 fullwidth', :style => "display: #{f.object.country.states.empty? ? 'none' : 'block' };", :disabled => f.object.country.states.empty?} %>
|
||||
</span>
|
||||
</div>
|
||||
<div class="field <%= "#{shipping_or_billing}-row" %>">
|
||||
<%= f.label :phone, Spree.t(:phone) + ':' %>
|
||||
<%= f.phone_field :phone, :class => 'fullwidth' %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<% content_for :head do %>
|
||||
<%= javascript_tag do -%>
|
||||
$(document).ready(function(){
|
||||
$('span#<%= s_or_b %>country .select2').on('change', function() { update_state('<%= s_or_b %>'); });
|
||||
});
|
||||
<% end -%>
|
||||
<% end %>
|
||||
@@ -1,5 +0,0 @@
|
||||
<div class="form-buttons filter-actions actions" data-hook="buttons">
|
||||
<%= button Spree.t('actions.create'), 'icon-ok' %>
|
||||
<span class="or"><%= Spree.t(:or) %></span>
|
||||
<%= link_to_with_icon 'icon-remove', Spree.t('actions.cancel'), collection_url, :class => 'button' %>
|
||||
</div>
|
||||
@@ -0,0 +1,4 @@
|
||||
.form-buttons.filter-actions.actions
|
||||
= button t('actions.create'), 'icon-ok'
|
||||
%span.or= t(:or)
|
||||
= button_link_to t('actions.cancel'), collection_url, icon: 'icon-remove'
|
||||
@@ -2,7 +2,6 @@
|
||||
Spree.routes = <%== {
|
||||
:variants_search => spree.admin_search_variants_path(:format => 'json'),
|
||||
:taxons_search => main_app.api_taxons_path(:format => 'json'),
|
||||
:user_search => spree.admin_search_users_path(:format => 'json'),
|
||||
:orders_api => main_app.api_orders_path
|
||||
}.to_json %>;
|
||||
</script>
|
||||
|
||||
7
app/views/spree/admin/shared/_update_order_state.js
Normal file
7
app/views/spree/admin/shared/_update_order_state.js
Normal file
@@ -0,0 +1,7 @@
|
||||
$('#order_tab_summary h5#order_status').html('<%= j Spree.t(:status) %>: <%= j Spree.t(@order.state, :scope => :order_state) %>');
|
||||
$('#order_tab_summary h5#order_total').html('<%= j Spree.t(:total) %>: <%= j @order.display_total.to_html %>');
|
||||
|
||||
<% if @order.completed? %>
|
||||
$('#order_tab_summary h5#payment_status').html('<%= j Spree.t(:payment) %>: <%= j Spree.t(@order.payment_state, :scope => :payment_states, :default => [:missing, "none"]) %>');
|
||||
$('#order_tab_summary h5#shipment_status').html('<%= j Spree.t(:shipment) %>: <%= j Spree.t(@order.shipment_state, :scope => :shipment_state, :default => [:missing, "none"]) %>');
|
||||
<% end %>
|
||||
@@ -8,7 +8,7 @@
|
||||
= link_to t(:account), account_path
|
||||
%li{"data-hook" => "user-logout-link"}
|
||||
%i.icon-signout
|
||||
= link_to t(:logout), spree.logout_path
|
||||
= link_to t(:logout), logout_path
|
||||
%li{"data-hook" => "store-frontend-link"}
|
||||
%i.icon-external-link
|
||||
= link_to t(".header.store"), spree.root_path, target: "_blank"
|
||||
= link_to t(".header.store"), main_app.root_path, target: "_blank"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
%html{ lang: "en" }
|
||||
%head{"data-hook" => "admin_inside_head"}= render :partial => 'spree/admin/shared/head'
|
||||
%body.admin{"data-ajax-root-path" => spree.root_path}
|
||||
%body.admin{"data-ajax-root-path" => main_app.root_path}
|
||||
#wrapper{"data-hook" => ""}
|
||||
- if flash[:error]
|
||||
.flash.error= flash[:error]
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
= t :email_signup_confirmed_email
|
||||
%p
|
||||
-# Remove http:// and trailing slashes from root url if they exist
|
||||
= t :email_signup_shop_html, link: link_to(spree.root_url.sub(/http:\/\//,"").sub(/\/$/,""), spree.root_url, target: '_blank')
|
||||
= t :email_signup_shop_html, link: link_to(main_app.root_url.sub(/http:\/\//,"").sub(/\/$/,""), main_app.root_url, target: '_blank')
|
||||
%p
|
||||
%hr/
|
||||
%p
|
||||
|
||||
@@ -50,6 +50,8 @@ ca:
|
||||
shipping_method_ids: "Mètodes d'enviament"
|
||||
payment_method_ids: "Mètodes de Pagament"
|
||||
errors:
|
||||
messages:
|
||||
inclusion: "no està inclòs a la llista"
|
||||
models:
|
||||
subscription_validator:
|
||||
attributes:
|
||||
@@ -700,6 +702,11 @@ ca:
|
||||
enable_subscriptions_false: "Deshabilitat"
|
||||
enable_subscriptions_true: "Habilitat"
|
||||
shopfront_message: "Missatge de la botiga"
|
||||
shopfront_message_placeholder: >
|
||||
Un missatge opcional per donar la benvinguda als clients i explicar
|
||||
com comprar amb vosaltres. Si s'introdueix text aquí, es mostrarà a
|
||||
la pestanya d'inici quan els clients arribin per primera vegada a la
|
||||
vostra botiga.
|
||||
shopfront_message_link_tooltip: "Inserir / editar enllaç"
|
||||
shopfront_message_link_prompt: "Introduïu un URL per inserir"
|
||||
shopfront_closed_message: "Missatge de tancament de la botiga"
|
||||
@@ -1101,10 +1108,13 @@ ca:
|
||||
destroy_attachment_does_not_exist: "El logotip no existeix"
|
||||
enterprise_promo_image:
|
||||
destroy_attachment_does_not_exist: "La imatge promocional no existeix"
|
||||
orders:
|
||||
failed_to_update: "No s'ha pogut actualitzar la comanda"
|
||||
checkout:
|
||||
already_ordered:
|
||||
cart: "cistella"
|
||||
message_html: "Ja teniu una comanda per a aquest cicle de comanda. Consulteu %{cart} per veure els articles que heu demanat anteriorment. També podeu cancel·lar articles sempre que el cicle de comanda estigui obert."
|
||||
failed: "La comanda ha fallat. Informeu-nos perquè puguem processar la vostra comanda."
|
||||
shops:
|
||||
hubs:
|
||||
show_closed_shops: "Mostra les botigues tancades"
|
||||
@@ -1935,6 +1945,7 @@ ca:
|
||||
tax_category: "Categoria d'impostos"
|
||||
calculator: "Calculadora"
|
||||
calculator_values: "Valors de la calculadora"
|
||||
calculator_settings_warning: "Si canvieu el tipus de calculadora, primer heu de desar abans de poder editar la configuració de la calculadora"
|
||||
flat_percent_per_item: "Percentatge fixe (per article)"
|
||||
flat_rate_per_item: "Tarifa fixa (per article)"
|
||||
flat_rate_per_order: "Tarifa fixa (per comanda)"
|
||||
@@ -2419,6 +2430,12 @@ ca:
|
||||
severity: Severitat
|
||||
description: Descripció
|
||||
resolve: Resoldre
|
||||
exchange_products:
|
||||
load_more_products: "Carrega més productes"
|
||||
load_all_products: "Carrega tots els productes"
|
||||
select_all_products: "Seleccioneu tots els productes %{total_number_of_products}"
|
||||
products_loaded: "%{num_of_products_loaded} de %{total_number_of_products} productes carregats"
|
||||
loading_products: "Carregant productes"
|
||||
tag_rules:
|
||||
shipping_method_tagged_top: "Els mètodes d'enviament etiquetats"
|
||||
shipping_method_tagged_bottom: "son:"
|
||||
@@ -2501,6 +2518,7 @@ ca:
|
||||
customer_placeholder: "consumidora@example.org"
|
||||
valid_email_error: "siusplau, introduïu una adreça de correu electrònic vàlida"
|
||||
subscriptions:
|
||||
error_saving: "S'ha produït un error en desar la subscripció"
|
||||
new:
|
||||
please_select_a_shop: "Si us plau, seleccioneu una botiga"
|
||||
insufficient_stock: "No hi ha prou estoc disponible, només queda %{on_hand}"
|
||||
@@ -2818,6 +2836,8 @@ ca:
|
||||
zipcode: Codi postal
|
||||
weight: Pes (per kg)
|
||||
error_user_destroy_with_orders: "No es poden esborrar usuaris amb comandes completades"
|
||||
cannot_create_payment_without_payment_methods: "No podeu crear un pagament per a una comanda sense que hi hagi definit cap mètode de pagament."
|
||||
please_define_payment_methods: "Primer definiu alguns mètodes de pagament."
|
||||
options: "Opcions"
|
||||
actions:
|
||||
update: "Actualitzar"
|
||||
@@ -2904,6 +2924,7 @@ ca:
|
||||
capture: "Captura"
|
||||
ship: "Enviament"
|
||||
edit: "Editar"
|
||||
order_not_updated: "La comanda no s'ha pogut actualitzar"
|
||||
note: "Nota"
|
||||
first: "Primer"
|
||||
last: "Últim"
|
||||
|
||||
@@ -2988,6 +2988,8 @@ See the %{link} to find out more about %{sitename}'s features and to start using
|
||||
zipcode: Postcode
|
||||
weight: Weight (per kg)
|
||||
error_user_destroy_with_orders: "Users with completed orders may not be deleted"
|
||||
cannot_create_payment_without_payment_methods: "You cannot create a payment for an order without any payment methods defined."
|
||||
please_define_payment_methods: "Please define some payment methods first."
|
||||
options: "Options"
|
||||
|
||||
actions:
|
||||
|
||||
@@ -701,6 +701,10 @@ en_CA:
|
||||
enable_subscriptions_false: "Disabled"
|
||||
enable_subscriptions_true: "Enabled"
|
||||
shopfront_message: "Shopfront Message"
|
||||
shopfront_message_placeholder: >
|
||||
An optional message to welcome customers and explain how to shop with
|
||||
you. If text is entered here it will be displayed in a home tab when
|
||||
customers first arrive at your shopfront.
|
||||
shopfront_message_link_tooltip: "Insert/edit link"
|
||||
shopfront_message_link_prompt: "Please enter a URL to insert"
|
||||
shopfront_closed_message: "Shopfront Closed Message"
|
||||
@@ -1107,6 +1111,7 @@ en_CA:
|
||||
already_ordered:
|
||||
cart: "cart"
|
||||
message_html: "You have an order for this order cycle already. Check the %{cart} to see the items you ordered before. You can also cancel items as long as the order cycle is open."
|
||||
failed: "The checkout failed. Please let us know so that we can process your order."
|
||||
shops:
|
||||
hubs:
|
||||
show_closed_shops: "Show closed shops"
|
||||
@@ -1937,6 +1942,7 @@ en_CA:
|
||||
tax_category: "Tax Category"
|
||||
calculator: "Calculator"
|
||||
calculator_values: "Calculator values"
|
||||
calculator_settings_warning: "If you are changing the calculator type, you must save first before you can edit the calculator settings"
|
||||
flat_percent_per_item: "Flat Percent (per item)"
|
||||
flat_rate_per_item: "Flat Rate (per item)"
|
||||
flat_rate_per_order: "Flat Rate (per order)"
|
||||
|
||||
@@ -2830,6 +2830,8 @@ en_FR:
|
||||
zipcode: Postcode
|
||||
weight: Weight (per kg)
|
||||
error_user_destroy_with_orders: "Users with completed orders may not be deleted"
|
||||
cannot_create_payment_without_payment_methods: "You cannot create a payment for an order without any payment methods defined."
|
||||
please_define_payment_methods: "Please define some payment methods first."
|
||||
options: "Options"
|
||||
actions:
|
||||
update: "Update"
|
||||
|
||||
@@ -701,6 +701,10 @@ en_GB:
|
||||
enable_subscriptions_false: "Disabled"
|
||||
enable_subscriptions_true: "Enabled"
|
||||
shopfront_message: "Shopfront Message"
|
||||
shopfront_message_placeholder: >
|
||||
An optional message to welcome customers and explain how to shop with
|
||||
you. If text is entered here it will be displayed in a home tab when
|
||||
customers first arrive at your shopfront.
|
||||
shopfront_message_link_tooltip: "Insert / edit link"
|
||||
shopfront_message_link_prompt: "Please enter a URL to insert"
|
||||
shopfront_closed_message: "Shopfront Closed Message"
|
||||
@@ -1107,6 +1111,7 @@ en_GB:
|
||||
already_ordered:
|
||||
cart: "cart"
|
||||
message_html: "You have an order for this order cycle already. Check the %{cart} to see the items you ordered before. You can also cancel items as long as the order cycle is open."
|
||||
failed: "The checkout failed. Please let us know so that we can process your order."
|
||||
shops:
|
||||
hubs:
|
||||
show_closed_shops: "Show closed shops"
|
||||
|
||||
@@ -2860,6 +2860,8 @@ fr:
|
||||
zipcode: Code postal
|
||||
weight: Poids (au kg)
|
||||
error_user_destroy_with_orders: "Les utilisateurs avec des commandes finalisées pourraient ne pas être supprimés"
|
||||
cannot_create_payment_without_payment_methods: "Vous ne pouvez pas ajouter un paiement pour une commande sans avoir défini de méthode de paiement au préalable."
|
||||
please_define_payment_methods: "Il faut que vous définissiez une méthode de paiement avant tout !"
|
||||
options: "Options"
|
||||
actions:
|
||||
update: "Mettre à jour"
|
||||
|
||||
@@ -703,6 +703,11 @@ fr_CA:
|
||||
enable_subscriptions_false: "Désactivée"
|
||||
enable_subscriptions_true: "Activée"
|
||||
shopfront_message: "Message d'accueil de la boutique ouverte"
|
||||
shopfront_message_placeholder: >
|
||||
Vous pouvez indiquer ici un message de bienvenue ou un message expliquant
|
||||
les particularités de votre boutique. Ce message s'affiche dans l'onglet
|
||||
Accueil de votre boutique. Si aucun message n'est affiché, l'onglet
|
||||
Accueil n'apparaitra pas.
|
||||
shopfront_message_link_tooltip: "Insérer / modifier un lien"
|
||||
shopfront_message_link_prompt: "Veuillez entrer l'URL à insérer"
|
||||
shopfront_closed_message: "Message d'accueil de la boutique fermée"
|
||||
@@ -1102,10 +1107,13 @@ fr_CA:
|
||||
destroy_attachment_does_not_exist: "Aucun logo trouvé "
|
||||
enterprise_promo_image:
|
||||
destroy_attachment_does_not_exist: "Aucune bannière trouvée "
|
||||
orders:
|
||||
failed_to_update: "La mise à jour de la commande échoué"
|
||||
checkout:
|
||||
already_ordered:
|
||||
cart: "panier"
|
||||
message_html: "Vous avez déjà passé une commande pour ce cycle de vente. Vérifiez votre %{cart} pour voir les produits commandés. Vous pouvez annuler ou modifier votre commande jusqu'à la fermeture du cycle de vente."
|
||||
failed: "La validation du paiement a échoué. Contactez-nous afin de finaliser votre commande."
|
||||
shops:
|
||||
hubs:
|
||||
show_closed_shops: "Afficher les boutiques fermées"
|
||||
@@ -1936,6 +1944,7 @@ fr_CA:
|
||||
tax_category: "Type de taxe "
|
||||
calculator: "Calculateur"
|
||||
calculator_values: "Valeurs applicables"
|
||||
calculator_settings_warning: "Si vous modifiez le type de calculateur, vous devez sauvegarder avant de modifier les paramètres du calculateur."
|
||||
flat_percent_per_item: "Pourcentage net"
|
||||
flat_rate_per_item: "Taux forfaitaire par article"
|
||||
flat_rate_per_order: "Montant fixe par commande"
|
||||
@@ -2920,6 +2929,7 @@ fr_CA:
|
||||
capture: "Payée"
|
||||
ship: "Expédier"
|
||||
edit: "Modifier"
|
||||
order_not_updated: "La commande n'a pas pu être mise à jour"
|
||||
note: "Note"
|
||||
first: "Début"
|
||||
last: "Fin"
|
||||
|
||||
@@ -701,6 +701,10 @@ nb:
|
||||
enable_subscriptions_false: "Deaktivert"
|
||||
enable_subscriptions_true: "Aktivert"
|
||||
shopfront_message: "Melding Butikk"
|
||||
shopfront_message_placeholder: >
|
||||
En valgfri melding for å ønske kunder velkommen og forklare hvordan
|
||||
de handler hos deg. Hvis tekst legges inn her, vil den vises i en hjemmefane
|
||||
når kundene først ankommer butikken din.
|
||||
shopfront_message_link_tooltip: "Sett inn / rediger lenke"
|
||||
shopfront_message_link_prompt: "Vennligst skriv inn en URL for å sette inn"
|
||||
shopfront_closed_message: "Melding Butikk Stengt"
|
||||
@@ -1107,6 +1111,7 @@ nb:
|
||||
already_ordered:
|
||||
cart: "handlekurv"
|
||||
message_html: "Du har allerede en bestilling for denne bestillingsrunden. Sjekk %{cart}en for å se varene du bestilte før. Du kan også avbryte varer så lenge bestillingsrunden er åpen."
|
||||
failed: "Utsjekk fra kassen mislyktes. Gi oss beskjed slik at vi kan behandle bestillingen din."
|
||||
shops:
|
||||
hubs:
|
||||
show_closed_shops: "Vis stengte butikker"
|
||||
@@ -2824,6 +2829,8 @@ nb:
|
||||
zipcode: Postnummer
|
||||
weight: Vekt (per kg)
|
||||
error_user_destroy_with_orders: "Brukere med fullførte ordrer kan ikke slettes"
|
||||
cannot_create_payment_without_payment_methods: "Du kan ikke opprette en betaling for en bestilling uten at det er definert noen betalingsmetoder."
|
||||
please_define_payment_methods: "Vennligst angi noen betalingsmetoder først."
|
||||
options: "Valg"
|
||||
actions:
|
||||
update: "Oppdater"
|
||||
|
||||
@@ -50,7 +50,6 @@ Spree::Core::Engine.routes.draw do
|
||||
namespace :admin do
|
||||
get '/search/known_users' => "search#known_users", :as => :search_known_users
|
||||
get '/search/customers' => 'search#customers', :as => :search_customers
|
||||
get '/search/customer_addresses' => 'search#customer_addresses', :as => :search_customer_addresses
|
||||
|
||||
resources :users
|
||||
|
||||
@@ -85,18 +84,32 @@ Spree::Core::Engine.routes.draw do
|
||||
get '/variants/search', :to => "variants#search", :as => :search_variants
|
||||
|
||||
resources :orders do
|
||||
get :invoice, on: :member
|
||||
get :print, on: :member
|
||||
get :print_ticket, on: :member
|
||||
get :managed, on: :collection
|
||||
member do
|
||||
put :fire
|
||||
get :fire
|
||||
post :resend
|
||||
get :invoice
|
||||
get :print
|
||||
get :print_ticket
|
||||
end
|
||||
|
||||
collection do
|
||||
get :managed
|
||||
|
||||
resources :invoices, only: [:create, :show] do
|
||||
get :poll
|
||||
end
|
||||
end
|
||||
|
||||
resources :adjustments
|
||||
|
||||
resources :payments do
|
||||
member do
|
||||
put :fire
|
||||
end
|
||||
end
|
||||
|
||||
resource :customer, :controller => "orders/customer_details"
|
||||
end
|
||||
|
||||
resources :users do
|
||||
|
||||
@@ -77,7 +77,7 @@ module OpenFoodNetwork
|
||||
ba.phone,
|
||||
order.shipping_method.andand.name,
|
||||
order.payments.first.andand.payment_method.andand.name,
|
||||
order.payments.first.amount,
|
||||
order.payment_total,
|
||||
OpenFoodNetwork::UserBalanceCalculator.new(order.email, order.distributor).balance]
|
||||
end
|
||||
|
||||
@@ -92,7 +92,7 @@ module OpenFoodNetwork
|
||||
sa.phone,
|
||||
order.shipping_method.andand.name,
|
||||
order.payments.first.andand.payment_method.andand.name,
|
||||
order.payments.first.amount,
|
||||
order.payment_total,
|
||||
OpenFoodNetwork::UserBalanceCalculator.new(order.email, order.distributor).balance,
|
||||
has_temperature_controlled_items?(order),
|
||||
order.special_instructions]
|
||||
|
||||
@@ -6,25 +6,13 @@ module OpenFoodNetwork
|
||||
end
|
||||
|
||||
def balance
|
||||
payment_total - completed_order_total
|
||||
-completed_orders.sum(&:outstanding_balance)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def completed_order_total
|
||||
completed_orders.sum(&:total)
|
||||
end
|
||||
|
||||
def payment_total
|
||||
payments.sum(&:amount)
|
||||
end
|
||||
|
||||
def completed_orders
|
||||
Spree::Order.where(distributor_id: @distributor, email: @email).complete.not_state(:canceled)
|
||||
end
|
||||
|
||||
def payments
|
||||
Spree::Payment.where(order_id: completed_orders, state: "completed")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
Spree::Core::ControllerHelpers::Auth.class_eval do
|
||||
def require_login_then_redirect_to(url)
|
||||
redirect_to root_path(anchor: "login?after_login=#{url}")
|
||||
redirect_to main_app.root_path(anchor: "login?after_login=#{url}")
|
||||
end
|
||||
end
|
||||
|
||||
@@ -9,6 +9,9 @@ namespace :ofn do
|
||||
|
||||
sql_delete_from "
|
||||
spree_inventory_units #{where_order_id_in_orders_to_delete}"
|
||||
sql_delete_from "
|
||||
spree_inventory_units
|
||||
where shipment_id in (select id from spree_shipments #{where_order_id_in_orders_to_delete})"
|
||||
|
||||
truncate_adjustments
|
||||
|
||||
|
||||
@@ -51,12 +51,27 @@ module Api
|
||||
let(:exchange) { order_cycle.exchanges.outgoing.first }
|
||||
let(:products_relation) { Spree::Product.includes(:variants).where("spree_variants.id": exchange.variants.map(&:id)) }
|
||||
|
||||
it "paginates results" do
|
||||
spree_get :index, exchange_id: exchange.id, page: 1, per_page: 1
|
||||
before do
|
||||
stub_const("Api::ExchangeProductsController::DEFAULT_PER_PAGE", 1)
|
||||
end
|
||||
|
||||
expect(json_response["products"].size).to eq 1
|
||||
expect(json_response["pagination"]["results"]).to eq 2
|
||||
expect(json_response["pagination"]["pages"]).to eq 2
|
||||
describe "when a specific page is requested" do
|
||||
it "returns the requested page with paginated data" do
|
||||
spree_get :index, exchange_id: exchange.id, page: 1
|
||||
|
||||
expect(json_response["products"].size).to eq 1
|
||||
expect(json_response["pagination"]["results"]).to eq 2
|
||||
expect(json_response["pagination"]["pages"]).to eq 2
|
||||
end
|
||||
end
|
||||
|
||||
describe "when no specific page is requested" do
|
||||
it "returns all results without paginating" do
|
||||
spree_get :index, exchange_id: exchange.id
|
||||
|
||||
expect(json_response["products"].size).to eq 2
|
||||
expect(json_response["pagination"]).to be nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -13,31 +13,6 @@ describe Spree::Admin::BaseController, type: :controller do
|
||||
expect(response).to redirect_to root_path(anchor: "login?after_login=/spree/admin/base")
|
||||
end
|
||||
|
||||
describe "displaying error messages for active distributors not ready for checkout" do
|
||||
it "generates an error message when there is one distributor" do
|
||||
distributor = double(:distributor, name: 'My Hub')
|
||||
expect(controller.
|
||||
send(:active_distributors_not_ready_for_checkout_message, [distributor])).
|
||||
to eq(
|
||||
"The hub My Hub is listed in an active order cycle, " \
|
||||
"but does not have valid shipping and payment methods. " \
|
||||
"Until you set these up, customers will not be able to shop at this hub."
|
||||
)
|
||||
end
|
||||
|
||||
it "generates an error message when there are several distributors" do
|
||||
d1 = double(:distributor, name: 'Hub One')
|
||||
d2 = double(:distributor, name: 'Hub Two')
|
||||
expect(controller.
|
||||
send(:active_distributors_not_ready_for_checkout_message, [d1, d2])).
|
||||
to eq(
|
||||
"The hubs Hub One, Hub Two are listed in an active order cycle, " \
|
||||
"but do not have valid shipping and payment methods. " \
|
||||
"Until you set these up, customers will not be able to shop at these hubs."
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
describe "rendering as json ActiveModelSerializer" do
|
||||
context "when data is an object" do
|
||||
let(:data) { { attr: 'value' } }
|
||||
|
||||
@@ -369,7 +369,12 @@ feature '
|
||||
find(:css, "tags-input .tags input").set "wholesale\n"
|
||||
end
|
||||
|
||||
page.all("table.exchanges tr.distributor td.products").each(&:click)
|
||||
exchange_rows = page.all("table.exchanges tbody")
|
||||
exchange_rows.each do |exchange_row|
|
||||
exchange_row.find("td.products").click
|
||||
# Wait for the products panel to be visible.
|
||||
expect(exchange_row).to have_selector "tr", count: 2
|
||||
end
|
||||
|
||||
uncheck "order_cycle_outgoing_exchange_2_variants_#{v1.id}"
|
||||
check "order_cycle_outgoing_exchange_2_variants_#{v2.id}"
|
||||
|
||||
@@ -69,7 +69,7 @@ module OpenFoodNetwork
|
||||
create(:order_with_totals_and_distribution,
|
||||
user: user1, distributor: hub1,
|
||||
completed_at: 1.day.ago, state: "canceled")
|
||||
} # total=10
|
||||
} # total=13 (10 + 3 shipping fee)
|
||||
let!(:p4) {
|
||||
create(:payment, order: o4, amount: 20.00,
|
||||
state: "completed")
|
||||
@@ -79,6 +79,62 @@ module OpenFoodNetwork
|
||||
expect(UserBalanceCalculator.new(o4.email, hub1).balance).to eq(-9) # = 15 + 2 - 13 - 13
|
||||
end
|
||||
end
|
||||
|
||||
context "with void payments" do
|
||||
let!(:o4) {
|
||||
create(:order_with_totals_and_distribution,
|
||||
user: user1, distributor: hub1,
|
||||
completed_at: 1.day.ago)
|
||||
} # total=13 (10 + 3 shipping fee)
|
||||
let!(:p4) {
|
||||
create(:payment, order: o4, amount: 20.00,
|
||||
state: "void")
|
||||
}
|
||||
|
||||
it "does not include void in the balance" do
|
||||
expect(UserBalanceCalculator.new(o4.email, hub1).balance).to eq(-22) # = 15 + 2 - 13 - 13 - 10
|
||||
end
|
||||
end
|
||||
|
||||
context "with invalid payments" do
|
||||
let!(:o4) {
|
||||
create(:order_with_totals_and_distribution,
|
||||
user: user1, distributor: hub1,
|
||||
completed_at: 1.day.ago)
|
||||
} # total=13 (10 + 3 shipping fee)
|
||||
let!(:p4) {
|
||||
create(:payment, order: o4, amount: 20.00,
|
||||
state: "invalid")
|
||||
}
|
||||
|
||||
it "does not include invalid payments in the balance" do
|
||||
expect(UserBalanceCalculator.new(o4.email, hub1).balance).to eq(-22) # = 15 + 2 - 13 - 13 - 10
|
||||
end
|
||||
end
|
||||
|
||||
context "with multiple payments on single order" do
|
||||
let!(:o4) {
|
||||
create(:order_with_totals_and_distribution,
|
||||
user: user1, distributor: hub1,
|
||||
completed_at: 1.day.ago)
|
||||
} # total=13 (10 + 3 shipping fee)
|
||||
let!(:p4) {
|
||||
create(:payment, order: o4, amount: 4.00,
|
||||
state: "completed")
|
||||
}
|
||||
let!(:p5) {
|
||||
create(:payment, order: o4, amount: 5.00,
|
||||
state: "completed")
|
||||
}
|
||||
let!(:p6) {
|
||||
create(:payment, order: o4, amount: 6.00,
|
||||
state: "completed")
|
||||
}
|
||||
|
||||
it "includes orders with multiple payments in the balance" do
|
||||
expect(UserBalanceCalculator.new(o4.email, hub1).balance).to eq(-7) # = 15 + 2 + 4 + 5 + 6 - 13 - 13 - 10
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -26,4 +26,38 @@ describe ExchangeProductsRenderer do
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#exchange_variants" do
|
||||
describe "for an incoming exchange" do
|
||||
it "loads variants" do
|
||||
exchange = order_cycle.exchanges.incoming.first
|
||||
variants = renderer.exchange_variants(true, exchange.sender)
|
||||
|
||||
expect(variants.first.product.supplier.name).to eq exchange.variants.first.product.supplier.name
|
||||
end
|
||||
|
||||
describe "when OC is showing only the coordinators inventory" do
|
||||
let(:exchange_with_visible_variant) { order_cycle.exchanges.incoming.second }
|
||||
let(:exchange_with_hidden_variant) { order_cycle.exchanges.incoming.first }
|
||||
let!(:visible_inventory_item) { create(:inventory_item, enterprise: order_cycle.coordinator, variant: exchange_with_visible_variant.variants.first, visible: true) }
|
||||
let!(:hidden_inventory_item) { create(:inventory_item, enterprise: order_cycle.coordinator, variant: exchange_with_hidden_variant.variants.first, visible: false) }
|
||||
|
||||
before do
|
||||
order_cycle.prefers_product_selection_from_coordinator_inventory_only = true
|
||||
end
|
||||
|
||||
it "renders visible inventory variants" do
|
||||
variants = renderer.exchange_variants(true, exchange_with_visible_variant.sender)
|
||||
|
||||
expect(variants.size).to eq 1
|
||||
end
|
||||
|
||||
it "does not render hidden inventory variants" do
|
||||
variants = renderer.exchange_variants(true, exchange_with_hidden_variant.sender)
|
||||
|
||||
expect(variants.size).to eq 0
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
31
spec/services/order_cycle_warning_spec.rb
Normal file
31
spec/services/order_cycle_warning_spec.rb
Normal file
@@ -0,0 +1,31 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe OrderCycleWarning do
|
||||
let(:user) { create(:user) }
|
||||
let(:subject) { OrderCycleWarning }
|
||||
let!(:distributor) { create(:enterprise, owner: user) }
|
||||
let!(:order_cycle) { create(:simple_order_cycle, distributors: [distributor]) }
|
||||
|
||||
describe "checking if user's managed order cycles have distributors not ready for checkout" do
|
||||
context "with an invalid distributor" do
|
||||
it "returns a warning message" do
|
||||
expect(subject.new(user).call).to eq(
|
||||
I18n.t(:active_distributors_not_ready_for_checkout_message_singular,
|
||||
distributor_names: distributor.name)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context "with a valid distributor" do
|
||||
let!(:distributor) {
|
||||
create(:distributor_enterprise,
|
||||
shipping_methods: [create(:shipping_method)],
|
||||
payment_methods: [create(:payment_method)])
|
||||
}
|
||||
|
||||
it "returns nil" do
|
||||
expect(subject.new(user).call).to eq nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
41
vendor/assets/javascripts/equalize.js
vendored
Normal file
41
vendor/assets/javascripts/equalize.js
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* equalize.js
|
||||
* Author & copyright (c) 2012: Tim Svensen
|
||||
* Dual MIT & GPL license
|
||||
*
|
||||
* Page: http://tsvensen.github.com/equalize.js
|
||||
* Repo: https://github.com/tsvensen/equalize.js/
|
||||
*
|
||||
* The jQuery plugin for equalizing the height or width of elements.
|
||||
*
|
||||
* Equalize will accept any of the jQuery Dimension methods:
|
||||
* height, outerHeight, innerHeight,
|
||||
* width, outerWidth, innerWidth.
|
||||
*
|
||||
* EXAMPLE
|
||||
* $('.parent').equalize(); // defaults to 'height'
|
||||
* $('.parent').equalize('width'); // equalize the widths
|
||||
*/
|
||||
(function($, window, document, undefined) {
|
||||
|
||||
$.fn.equalize = function(equalize) {
|
||||
var $containers = this, // this is the jQuery object
|
||||
equalize = equalize || 'height',
|
||||
type = (equalize.indexOf('eight') > 0) ? 'height' : 'width';
|
||||
|
||||
if (!$.isFunction($.fn[equalize])) { return false; }
|
||||
|
||||
return $containers.each(function() {
|
||||
var $children = $(this).children(),
|
||||
max = 0; // reset for each container
|
||||
|
||||
$children.each(function() {
|
||||
var value = $(this)[equalize](); // call height(), outerHeight(), etc.
|
||||
if (value > max) { max = value; } // update max
|
||||
});
|
||||
|
||||
$children.css(type, max +'px'); // add CSS to children
|
||||
});
|
||||
};
|
||||
|
||||
}(jQuery, window, document));
|
||||
1920
vendor/assets/javascripts/handlebars.js
vendored
Normal file
1920
vendor/assets/javascripts/handlebars.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
41
vendor/assets/javascripts/jquery.cookie.js
vendored
Executable file
41
vendor/assets/javascripts/jquery.cookie.js
vendored
Executable file
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* jQuery Cookie plugin
|
||||
*
|
||||
* Copyright (c) 2010 Klaus Hartl (stilbuero.de)
|
||||
* Dual licensed under the MIT and GPL licenses:
|
||||
* http://www.opensource.org/licenses/mit-license.php
|
||||
* http://www.gnu.org/licenses/gpl.html
|
||||
*
|
||||
*/
|
||||
jQuery.cookie = function (key, value, options) {
|
||||
|
||||
// key and at least value given, set cookie...
|
||||
if (arguments.length > 1 && String(value) !== "[object Object]") {
|
||||
options = jQuery.extend({}, options);
|
||||
|
||||
if (value === null || value === undefined) {
|
||||
options.expires = -1;
|
||||
}
|
||||
|
||||
if (typeof options.expires === 'number') {
|
||||
var days = options.expires, t = options.expires = new Date();
|
||||
t.setDate(t.getDate() + days);
|
||||
}
|
||||
|
||||
value = String(value);
|
||||
|
||||
return (document.cookie = [
|
||||
encodeURIComponent(key), '=',
|
||||
options.raw ? value : encodeURIComponent(value),
|
||||
options.expires ? '; expires=' + options.expires.toUTCString() : '', // use expires attribute, max-age is not supported by IE
|
||||
options.path ? '; path=' + options.path : '',
|
||||
options.domain ? '; domain=' + options.domain : '',
|
||||
options.secure ? '; secure' : ''
|
||||
].join(''));
|
||||
}
|
||||
|
||||
// key and possibly options given, get cookie...
|
||||
options = value || {};
|
||||
var result, decode = options.raw ? function (s) { return s; } : decodeURIComponent;
|
||||
return (result = new RegExp('(?:^|; )' + encodeURIComponent(key) + '=([^;]*)').exec(document.cookie)) ? decode(result[1]) : null;
|
||||
};
|
||||
141
vendor/assets/javascripts/jquery.horizontalNav.js
vendored
Executable file
141
vendor/assets/javascripts/jquery.horizontalNav.js
vendored
Executable file
@@ -0,0 +1,141 @@
|
||||
/**
|
||||
* jQuery Horizontal Navigation 1.0
|
||||
* https://github.com/sebnitu/horizontalNav
|
||||
*
|
||||
* By Sebastian Nitu - Copyright 2012 - All rights reserved
|
||||
* Author URL: http://sebnitu.com
|
||||
*/
|
||||
(function($) {
|
||||
|
||||
$.fn.horizontalNav = function(options) {
|
||||
|
||||
// Extend our default options with those provided.
|
||||
var opts = $.extend({}, $.fn.horizontalNav.defaults, options);
|
||||
|
||||
return this.each(function () {
|
||||
|
||||
// Save our object
|
||||
var $this = $(this);
|
||||
|
||||
// Build element specific options
|
||||
// This lets me access options with this syntax: o.optionName
|
||||
var o = $.meta ? $.extend({}, opts, $this.data()) : opts;
|
||||
|
||||
// Save the wrapper. The wrapper is the element that
|
||||
// we figure out what the full width should be
|
||||
if ($this.is('ul')) {
|
||||
var ul_wrap = $this.parent();
|
||||
} else {
|
||||
var ul_wrap = $this;
|
||||
}
|
||||
|
||||
// let's append a clearfixing element to the ul wrapper
|
||||
ul_wrap.css({ 'zoom' : '1' }).append('<div class="clearHorizontalNav">');
|
||||
$('.clearHorizontalNav').css({
|
||||
'display' : 'block',
|
||||
'overflow' : 'hidden',
|
||||
'visibility' : 'hidden',
|
||||
'width' : 0,
|
||||
'height' : 0,
|
||||
'clear' : 'both'
|
||||
});
|
||||
|
||||
// Grab elements we'll need and add some default styles
|
||||
var ul = $this.is('ul') ? $this : ul_wrap.find('> ul'), // The unordered list element
|
||||
li = ul.find('> li'), // All list items
|
||||
li_last = li.last(), // Last list item
|
||||
li_count = li.size(), // The number of navigation elements
|
||||
li_a = li.find('> a'); // Remove padding from the links
|
||||
|
||||
// If set to responsive, re-construct after every browser resize
|
||||
if ( o.responsive === true ) {
|
||||
// Only need to do this for IE7 and below
|
||||
// or if we set tableDisplay to false
|
||||
if ( (o.tableDisplay != true) || ($.browser.msie && parseInt($.browser.version, 10) <= 7) ) {
|
||||
resizeTrigger( _construct, o.responsiveDelay );
|
||||
}
|
||||
}
|
||||
|
||||
// Initiate the plugin
|
||||
_construct();
|
||||
|
||||
// Returns the true inner width of an element
|
||||
// Essentially it's the inner width without padding.
|
||||
function trueInnerWidth(element) {
|
||||
return element.innerWidth() - (
|
||||
parseInt(element.css('padding-left')) + parseInt(element.css('padding-right'))
|
||||
);
|
||||
}
|
||||
|
||||
// Call funcion on browser resize
|
||||
function resizeTrigger(callback, delay) {
|
||||
// Delay before function is called
|
||||
delay = delay || 100;
|
||||
// Call function on resize
|
||||
var resizeTimer;
|
||||
$(window).resize(function() {
|
||||
clearTimeout(resizeTimer);
|
||||
resizeTimer = setTimeout(function() {
|
||||
callback();
|
||||
}, delay);
|
||||
});
|
||||
}
|
||||
|
||||
// The heavy lifting of this plugin. This is where we
|
||||
// find and set the appropriate widths for list items
|
||||
function _construct() {
|
||||
|
||||
if ( (o.tableDisplay != true) || ($.browser.msie && parseInt($.browser.version, 10) <= 7) ) {
|
||||
|
||||
// IE7 doesn't support the "display: table" method
|
||||
// so we need to do it the hard way.
|
||||
|
||||
// Add some styles
|
||||
ul.css({ 'float' : 'left' });
|
||||
li.css({ 'float' : 'left', 'width' : 'auto' });
|
||||
li_a.css({ 'padding-left' : 0, 'padding-right' : 0 });
|
||||
|
||||
// Grabbing widths and doing some math
|
||||
var ul_width = trueInnerWidth(ul),
|
||||
ul_width_outer = ul.outerWidth(true),
|
||||
ul_width_extra = ul_width_outer - ul_width,
|
||||
|
||||
full_width = trueInnerWidth(ul_wrap),
|
||||
extra_width = (full_width - ul_width_extra) - ul_width,
|
||||
li_padding = Math.floor( extra_width / li_count );
|
||||
|
||||
// Cycle through the list items and give them widths
|
||||
li.each(function(index) {
|
||||
var li_width = trueInnerWidth( $(this) );
|
||||
$(this).css({ 'width' : (li_width + li_padding) + 'px' });
|
||||
});
|
||||
|
||||
// Get the leftover pixels after we set every itms width
|
||||
var li_last_width = trueInnerWidth(li_last) + ( (full_width - ul_width_extra) - trueInnerWidth(ul) );
|
||||
// I hate to do this but for some reason Firefox (v13.0) and IE are always
|
||||
// one pixel off when rendering. So this is a quick fix for that.
|
||||
if ($.browser.mozilla || $.browser.msie) {
|
||||
li_last_width = li_last_width - 1;
|
||||
}
|
||||
// Add the leftovers to the last navigation item
|
||||
li_last.css({ 'width' : li_last_width + 'px' });
|
||||
|
||||
} else {
|
||||
// Every modern browser supports the "display: table" method
|
||||
// so this is the best way to do it for them.
|
||||
ul.css({ 'display' : 'table', 'float' : 'none', 'width' : '100%' });
|
||||
li.css({ 'display' : 'table-cell', 'float' : 'none' });
|
||||
}
|
||||
}
|
||||
|
||||
}); // @end of return this.each()
|
||||
|
||||
};
|
||||
|
||||
$.fn.horizontalNav.defaults = {
|
||||
responsive : true,
|
||||
responsiveDelay : 100,
|
||||
tableDisplay : true
|
||||
};
|
||||
|
||||
})(jQuery);
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user