mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-02-27 01:43:22 +00:00
Merge remote-tracking branch 'origin/master' into discourse-sso
This commit is contained in:
13
.codeclimate.yml
Normal file
13
.codeclimate.yml
Normal file
@@ -0,0 +1,13 @@
|
||||
engines:
|
||||
rubocop:
|
||||
enabled: true
|
||||
scss-lint:
|
||||
enabled: true
|
||||
ratings:
|
||||
paths:
|
||||
- app/**
|
||||
- lib/**
|
||||
- "**.rb"
|
||||
exclude_paths:
|
||||
- spec/**/*
|
||||
- vendor/**/*
|
||||
29
.rubocop.yml
Normal file
29
.rubocop.yml
Normal file
@@ -0,0 +1,29 @@
|
||||
AllCops:
|
||||
Include:
|
||||
- '**/Rakefile'
|
||||
- '**/config.ru'
|
||||
Exclude:
|
||||
- 'db/**/*'
|
||||
- 'config/**/*'
|
||||
- 'script/**/*'
|
||||
- 'spec/**/*'
|
||||
- !ruby/regexp /old_and_unused\.rb$/
|
||||
|
||||
Documentation:
|
||||
Enabled: false
|
||||
|
||||
Style/EmptyLinesAroundClassBody:
|
||||
Enabled: false
|
||||
|
||||
Style/BracesAroundHashParameters:
|
||||
Enabled: false
|
||||
|
||||
Metrics/LineLength:
|
||||
Enabled: false
|
||||
Max: 120
|
||||
|
||||
MethodLength:
|
||||
Enabled: false
|
||||
|
||||
StringLiterals:
|
||||
Enabled: false
|
||||
3
.scss-lint.yml
Normal file
3
.scss-lint.yml
Normal file
@@ -0,0 +1,3 @@
|
||||
scss_files: 'app/assets/stylesheets/**/*.css.scss'
|
||||
|
||||
exclude: 'app/assets/stylesheets/shared/**'
|
||||
16
.travis.yml
16
.travis.yml
@@ -14,12 +14,13 @@ env:
|
||||
global:
|
||||
- TZ="Australia/Melbourne"
|
||||
- TIMEZONE="Australia/Melbourne"
|
||||
- CI_NODE_TOTAL=5
|
||||
matrix:
|
||||
- TEST_CASES="./spec/features/admin" GITHUB_DEPLOY="true"
|
||||
- TEST_CASES="./spec/features/consumer ./spec/serializers ./spec/performance"
|
||||
- TEST_CASES="./spec/models"
|
||||
- TEST_CASES="./spec/controllers ./spec/views ./spec/jobs"
|
||||
- TEST_CASES="./spec/requests ./spec/helpers ./spec/mailers ./spec/lib" KARMA="true"
|
||||
- CI_NODE_INDEX=0
|
||||
- CI_NODE_INDEX=1
|
||||
- CI_NODE_INDEX=2
|
||||
- CI_NODE_INDEX=3
|
||||
- CI_NODE_INDEX=4 KARMA="true" GITHUB_DEPLOY="true"
|
||||
|
||||
before_script:
|
||||
- cp config/database.travis.yml config/database.yml
|
||||
@@ -35,8 +36,9 @@ before_script:
|
||||
fi
|
||||
|
||||
script:
|
||||
- '[ "$KARMA" = "true" ] && bundle exec rake karma:run || echo "Skipping karma run"'
|
||||
- "bundle exec rspec $TEST_CASES"
|
||||
- 'if [ "$KARMA" = "true" ]; then bundle exec rake karma:run; else echo "Skipping karma run"; fi'
|
||||
#- "KNAPSACK_GENERATE_REPORT=true bundle exec rspec spec"
|
||||
- "bundle exec rake knapsack:rspec"
|
||||
|
||||
after_success:
|
||||
- >
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Contributing
|
||||
|
||||
We love pull requests from everyone. Here are some instructions for
|
||||
contributing code to Open Food Network.
|
||||
contributing code to Open Food Network. See the [developer wiki](https://github.com/openfoodfoundation/openfoodnetwork/wiki) for more information.
|
||||
|
||||
Fork, then clone the repo:
|
||||
|
||||
|
||||
1
Gemfile
1
Gemfile
@@ -105,6 +105,7 @@ group :test, :development do
|
||||
gem 'json_spec'
|
||||
gem 'unicorn-rails'
|
||||
gem 'atomic'
|
||||
gem 'knapsack'
|
||||
end
|
||||
|
||||
group :test do
|
||||
|
||||
@@ -122,7 +122,7 @@ GEM
|
||||
rack-test (~> 0.6.1)
|
||||
sprockets (~> 2.2.1)
|
||||
active_link_to (1.0.0)
|
||||
active_model_serializers (0.8.1)
|
||||
active_model_serializers (0.8.3)
|
||||
activemodel (>= 3.0)
|
||||
activemerchant (1.48.0)
|
||||
activesupport (>= 3.2.14, < 5.0.0)
|
||||
@@ -431,6 +431,9 @@ GEM
|
||||
actionpack (>= 3.0.0)
|
||||
activesupport (>= 3.0.0)
|
||||
kgio (2.9.3)
|
||||
knapsack (1.5.1)
|
||||
rake
|
||||
timecop (>= 0.1.0)
|
||||
launchy (2.1.2)
|
||||
addressable (~> 2.3)
|
||||
letter_opener (1.0.0)
|
||||
@@ -684,6 +687,7 @@ DEPENDENCIES
|
||||
immigrant
|
||||
jquery-rails
|
||||
json_spec
|
||||
knapsack
|
||||
letter_opener
|
||||
momentjs-rails
|
||||
newrelic_rpm
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
[](https://travis-ci.org/openfoodfoundation/openfoodnetwork)
|
||||
[](https://codeclimate.com/github/openfoodfoundation/openfoodnetwork)
|
||||
|
||||
# Open Food Network
|
||||
@@ -14,10 +15,12 @@ We're part of global movement - get involved!
|
||||
|
||||
## Getting started
|
||||
|
||||
Below are instructions for setting up a development environment for Open Food Network. If you're interested in provisioning a server, see [the project's Ansible playbooks](https://github.com/openfoodfoundation/ofn_deployment).
|
||||
Below are instructions for setting up a development environment for Open Food Network. More information is in the [developer wiki](https://github.com/openfoodfoundation/openfoodnetwork/wiki).
|
||||
|
||||
If you're interested in provisioning a server, see [the project's Ansible playbooks](https://github.com/openfoodfoundation/ofn_deployment).
|
||||
|
||||
|
||||
## Dependencies
|
||||
### Dependencies
|
||||
|
||||
* Rails 3.2.x
|
||||
* Ruby 2.1.5
|
||||
@@ -26,7 +29,7 @@ Below are instructions for setting up a development environment for Open Food Ne
|
||||
* See Gemfile for a list of gems required
|
||||
|
||||
|
||||
## Get it
|
||||
### Get it
|
||||
|
||||
The source code is managed with Git (a version control system) and
|
||||
hosted at GitHub.
|
||||
@@ -40,7 +43,7 @@ You can download the source with the command:
|
||||
git clone https://github.com/openfoodfoundation/openfoodnetwork.git
|
||||
|
||||
|
||||
## Get it running
|
||||
### Get it running
|
||||
|
||||
For those new to Rails, the following tutorial will help get you up to speed with configuring a Rails environment: http://guides.rubyonrails.org/getting_started.html .
|
||||
|
||||
@@ -58,6 +61,15 @@ Configure the site:
|
||||
cp config/application.yml.example config/application.yml
|
||||
edit config/application.yml
|
||||
|
||||
Create a PostgreSQL user:
|
||||
|
||||
* Login as your system postrgresql priviledged user: `sudo -i -u postgres` (this may vary on your OS). Now your prompt looks like: `[postgres@your_host ~]$`
|
||||
* Create the `ofn` database superuser and give it the password `f00d`:
|
||||
|
||||
```
|
||||
createuser -s -P ofn
|
||||
```
|
||||
|
||||
Create the development and test databases, using the settings specified in `config/database.yml`, and populate them with a schema and seed data:
|
||||
|
||||
rake db:setup
|
||||
@@ -71,7 +83,7 @@ At long last, your dreams of spinning up a development server can be realised:
|
||||
rails server
|
||||
|
||||
|
||||
## Testing
|
||||
### Testing
|
||||
|
||||
Tests, both unit and integration, are based on RSpec. To run the test suite, first prepare the test database:
|
||||
|
||||
@@ -97,6 +109,10 @@ usage instructions.
|
||||
* Will Marshall (http://soundcloud.com/willmarshall)
|
||||
* Laura Summers (https://github.com/summerscope)
|
||||
* Maikel Linke (https://github.com/mkllnk)
|
||||
* Lynne Davis (https://github.com/lin-d-hop)
|
||||
* Paul Mackay (https://github.com/pmackay)
|
||||
* Steve Petitt (https://github.com/stveep)
|
||||
|
||||
|
||||
## Licence
|
||||
|
||||
2
Rakefile
2
Rakefile
@@ -5,3 +5,5 @@
|
||||
require File.expand_path('../config/application', __FILE__)
|
||||
|
||||
Openfoodnetwork::Application.load_tasks
|
||||
|
||||
Knapsack.load_tasks if defined?(Knapsack)
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
//= require ./taxons/taxons
|
||||
//= require ./utils/utils
|
||||
//= require ./users/users
|
||||
//= require ./variant_overrides/variant_overrides
|
||||
//= require textAngular.min.js
|
||||
//= require textAngular-sanitize.min.js
|
||||
//= require ../shared/bindonce.min.js
|
||||
|
||||
@@ -352,6 +352,9 @@ filterSubmitVariant = (variant) ->
|
||||
filteredVariant = {}
|
||||
if not variant.deleted_at? and variant.hasOwnProperty("id")
|
||||
filteredVariant.id = variant.id unless variant.id <= 0
|
||||
if variant.hasOwnProperty("sku")
|
||||
filteredVariant.sku = variant.sku
|
||||
hasUpdatableProperty = true
|
||||
if variant.hasOwnProperty("on_hand")
|
||||
filteredVariant.on_hand = variant.on_hand
|
||||
hasUpdatableProperty = true
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
angular.module("ofn.admin").controller "AdminVariantOverridesCtrl", ($scope, $timeout, Indexer, SpreeApiAuth, PagedFetcher, StatusMessage, hubs, producers, hubPermissions, VariantOverrides, DirtyVariantOverrides) ->
|
||||
$scope.hubs = hubs
|
||||
$scope.hub = null
|
||||
$scope.products = []
|
||||
$scope.producers = Indexer.index producers
|
||||
$scope.hubPermissions = hubPermissions
|
||||
$scope.variantOverrides = VariantOverrides.variantOverrides
|
||||
$scope.StatusMessage = StatusMessage
|
||||
|
||||
$scope.initialise = ->
|
||||
SpreeApiAuth.authorise()
|
||||
.then ->
|
||||
$scope.spree_api_key_ok = true
|
||||
$scope.fetchProducts()
|
||||
.catch (message) ->
|
||||
$scope.api_error_msg = message
|
||||
|
||||
|
||||
$scope.fetchProducts = ->
|
||||
url = "/api/products/overridable?page=::page::;per_page=100"
|
||||
PagedFetcher.fetch url, (data) => $scope.addProducts data.products
|
||||
|
||||
|
||||
$scope.addProducts = (products) ->
|
||||
$scope.products = $scope.products.concat products
|
||||
VariantOverrides.ensureDataFor hubs, products
|
||||
|
||||
|
||||
$scope.selectHub = ->
|
||||
$scope.hub = (hub for hub in hubs when hub.id == $scope.hub_id)[0]
|
||||
|
||||
|
||||
$scope.displayDirty = ->
|
||||
if DirtyVariantOverrides.count() > 0
|
||||
num = if DirtyVariantOverrides.count() == 1 then "one override" else "#{DirtyVariantOverrides.count()} overrides"
|
||||
StatusMessage.display 'notice', "Changes to #{num} remain unsaved."
|
||||
else
|
||||
StatusMessage.clear()
|
||||
|
||||
|
||||
$scope.update = ->
|
||||
if DirtyVariantOverrides.count() == 0
|
||||
StatusMessage.display 'alert', 'No changes to save.'
|
||||
else
|
||||
StatusMessage.display 'progress', 'Saving...'
|
||||
DirtyVariantOverrides.save()
|
||||
.success (updatedVos) ->
|
||||
DirtyVariantOverrides.clear()
|
||||
VariantOverrides.updateIds updatedVos
|
||||
$timeout -> StatusMessage.display 'success', 'Changes saved.'
|
||||
.error (data, status) ->
|
||||
$timeout -> StatusMessage.display 'failure', $scope.updateError(data, status)
|
||||
|
||||
|
||||
$scope.updateError = (data, status) ->
|
||||
if status == 401
|
||||
"I couldn't get authorisation to save those changes, so they remain unsaved."
|
||||
|
||||
else if status == 400 && data.errors?
|
||||
errors = []
|
||||
for field, field_errors of data.errors
|
||||
errors = errors.concat field_errors
|
||||
errors = errors.join ', '
|
||||
"I had some trouble saving: #{errors}"
|
||||
|
||||
else
|
||||
"Oh no! I was unable to save your changes."
|
||||
@@ -1,8 +1,9 @@
|
||||
angular.module("admin.dropdown").directive "ofnDropDown", ($document) ->
|
||||
restrict: 'C'
|
||||
link: (scope, element, attrs) ->
|
||||
outsideClickListener = (event) ->
|
||||
unless $(event.target).is("div.ofn_drop_down##{attrs.id} div.menu") ||
|
||||
$(event.target).parents("div.ofn_drop_down##{attrs.id} div.menu").length > 0
|
||||
unless $(event.target).is("div.ofn-drop-down##{attrs.id} div.menu") ||
|
||||
$(event.target).parents("div.ofn-drop-down##{attrs.id} div.menu").length > 0
|
||||
scope.$emit "offClick"
|
||||
|
||||
element.click (event) ->
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
angular.module("admin.indexUtils").directive "ofnSelect2", ($timeout, blankOption) ->
|
||||
require: 'ngModel'
|
||||
restrict: 'C'
|
||||
scope:
|
||||
data: "="
|
||||
minSearch: "@?"
|
||||
text: "@?"
|
||||
blank: "=?"
|
||||
link: (scope, element, attrs, ngModel) ->
|
||||
$timeout ->
|
||||
scope.text ||= 'name'
|
||||
scope.data.unshift(scope.blank) if scope.blank? && typeof scope.blank is "object"
|
||||
element.select2
|
||||
minimumResultsForSearch: scope.minSearch || 0
|
||||
data: { results: scope.data, text: scope.text }
|
||||
initSelection: (element, callback) ->
|
||||
callback scope.data[0]
|
||||
formatSelection: (item) ->
|
||||
item[scope.text]
|
||||
formatResult: (item) ->
|
||||
item[scope.text]
|
||||
|
||||
attrs.$observe 'disabled', (value) ->
|
||||
element.select2('enable', !value)
|
||||
|
||||
ngModel.$formatters.push (value) ->
|
||||
element.select2('val', value)
|
||||
value
|
||||
@@ -1,7 +0,0 @@
|
||||
angular.module("admin.indexUtils").directive "saveBar", ->
|
||||
restrict: "E"
|
||||
scope:
|
||||
save: "&"
|
||||
saving: "&"
|
||||
dirty: "&"
|
||||
templateUrl: "admin/save_bar.html"
|
||||
@@ -0,0 +1,12 @@
|
||||
# Used like a regular angular filter where an object is passed
|
||||
# Adds the additional special case that a value of 0 for the filter
|
||||
# acts as a bypass for that particular attribute
|
||||
angular.module("admin.indexUtils").filter "attrFilter", ($filter) ->
|
||||
return (objects, filters) ->
|
||||
Object.keys(filters).reduce (filtered, attr) ->
|
||||
filter = filters[attr]
|
||||
return filtered if !filter? || filter == 0
|
||||
return $filter('filter')(filtered, (object) ->
|
||||
object[attr] == filter
|
||||
)
|
||||
, objects
|
||||
@@ -1,4 +1,4 @@
|
||||
angular.module("ofn.admin").factory "dataFetcher", [
|
||||
angular.module("admin.indexUtils").factory "dataFetcher", [
|
||||
"$http", "$q"
|
||||
($http, $q) ->
|
||||
return (dataLocation) ->
|
||||
@@ -9,4 +9,4 @@ angular.module("ofn.admin").factory "dataFetcher", [
|
||||
deferred.reject()
|
||||
|
||||
deferred.promise
|
||||
]
|
||||
]
|
||||
@@ -4,7 +4,7 @@
|
||||
# Indexer.index producers
|
||||
# -> {1: {id: 1, name: 'one'}, 2: {id: 2, name: 'two'}}
|
||||
|
||||
angular.module("ofn.admin").factory 'Indexer', ->
|
||||
angular.module("admin.indexUtils").factory 'Indexer', ->
|
||||
new class Indexer
|
||||
index: (data, key='id') ->
|
||||
index = {}
|
||||
@@ -1,4 +1,4 @@
|
||||
angular.module("ofn.admin").factory "PagedFetcher", (dataFetcher) ->
|
||||
angular.module("admin.indexUtils").factory "PagedFetcher", (dataFetcher) ->
|
||||
new class PagedFetcher
|
||||
# Given a URL like http://example.com/foo?page=::page::&per_page=20
|
||||
# And the response includes an attribute pages with the number of pages to fetch
|
||||
@@ -13,4 +13,4 @@ angular.module("ofn.admin").factory "PagedFetcher", (dataFetcher) ->
|
||||
processData data
|
||||
|
||||
urlForPage: (url, page) ->
|
||||
url.replace("::page::", page)
|
||||
url.replace("::page::", page)
|
||||
@@ -1,4 +1,4 @@
|
||||
angular.module("ofn.admin").factory "SpreeApiAuth", ($q, $http, SpreeApiKey) ->
|
||||
angular.module("admin.indexUtils").factory "SpreeApiAuth", ($q, $http, SpreeApiKey) ->
|
||||
new class SpreeApiAuth
|
||||
authorise: ->
|
||||
deferred = $q.defer()
|
||||
@@ -1,7 +1,6 @@
|
||||
angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout, $http, $q, Columns, Dereferencer, Orders, LineItems, Enterprises, OrderCycles, blankOption, VariantUnitManager, RequestMonitor) ->
|
||||
angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout, $http, $q, StatusMessage, Columns, Dereferencer, Orders, LineItems, Enterprises, OrderCycles, blankOption, VariantUnitManager, RequestMonitor) ->
|
||||
$scope.initialized = false
|
||||
$scope.RequestMonitor = RequestMonitor
|
||||
$scope.saving = false
|
||||
$scope.filteredLineItems = []
|
||||
$scope.confirmDelete = true
|
||||
$scope.startDate = formatDate daysFromToday -7
|
||||
@@ -55,6 +54,7 @@ angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout,
|
||||
Dereferencer.dereferenceAttr $scope.lineItems, "supplier", Enterprises.enterprisesByID
|
||||
Dereferencer.dereferenceAttr $scope.lineItems, "order", Orders.ordersByID
|
||||
$scope.bulk_order_form.$setPristine()
|
||||
StatusMessage.clear()
|
||||
unless $scope.initialized
|
||||
$scope.initialized = true
|
||||
$timeout ->
|
||||
@@ -62,16 +62,20 @@ angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout,
|
||||
|
||||
$scope.refreshData()
|
||||
|
||||
$scope.submit = =>
|
||||
$scope.$watch 'bulk_order_form.$dirty', (newVal, oldVal) ->
|
||||
if newVal == true
|
||||
StatusMessage.display 'notice', "You have unsaved changes"
|
||||
|
||||
$scope.submit = ->
|
||||
if $scope.bulk_order_form.$valid
|
||||
$scope.saving = true
|
||||
StatusMessage.display 'progress', "Saving..."
|
||||
$q.all(LineItems.saveAll()).then(->
|
||||
StatusMessage.display 'success', "All changes saved"
|
||||
$scope.bulk_order_form.$setPristine()
|
||||
$scope.saving = false
|
||||
).catch ->
|
||||
alert "Some errors must be resolved be before you can update orders.\nAny fields with red borders contain errors."
|
||||
StatusMessage.display 'failure', "Fields with red borders contain errors."
|
||||
else
|
||||
alert "Some errors must be resolved be before you can update orders.\nAny fields with red borders contain errors."
|
||||
StatusMessage.display 'failure', "Fields with red borders contain errors."
|
||||
|
||||
$scope.deleteLineItem = (lineItem) ->
|
||||
if ($scope.confirmDelete && confirm("Are you sure?")) || !$scope.confirmDelete
|
||||
|
||||
@@ -1 +1 @@
|
||||
angular.module("admin.lineItems", ["admin.indexUtils", "admin.products", "admin.orders", "admin.enterprises", "admin.orderCycles"])
|
||||
angular.module("admin.lineItems", ["admin.indexUtils", "admin.utils", "admin.products", "admin.orders", "admin.enterprises", "admin.orderCycles"])
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
angular.module('admin.orderCycles')
|
||||
.controller 'AdminCreateOrderCycleCtrl', ($scope, $filter, OrderCycle, Enterprise, EnterpriseFee, ocInstance, StatusMessage) ->
|
||||
$scope.enterprises = Enterprise.index(coordinator_id: ocInstance.coordinator_id)
|
||||
$scope.supplier_enterprises = Enterprise.producer_enterprises
|
||||
$scope.distributor_enterprises = Enterprise.hub_enterprises
|
||||
$scope.supplied_products = Enterprise.supplied_products
|
||||
$scope.enterprise_fees = EnterpriseFee.index(coordinator_id: ocInstance.coordinator_id)
|
||||
|
||||
$scope.OrderCycle = OrderCycle
|
||||
$scope.order_cycle = OrderCycle.new({ coordinator_id: ocInstance.coordinator_id})
|
||||
|
||||
$scope.StatusMessage = StatusMessage
|
||||
|
||||
$scope.loaded = ->
|
||||
Enterprise.loaded && EnterpriseFee.loaded
|
||||
|
||||
$scope.suppliedVariants = (enterprise_id) ->
|
||||
Enterprise.suppliedVariants(enterprise_id)
|
||||
|
||||
$scope.exchangeSelectedVariants = (exchange) ->
|
||||
OrderCycle.exchangeSelectedVariants(exchange)
|
||||
|
||||
$scope.setExchangeVariants = (exchange, variants, selected) ->
|
||||
OrderCycle.setExchangeVariants(exchange, variants, selected)
|
||||
|
||||
$scope.enterpriseTotalVariants = (enterprise) ->
|
||||
Enterprise.totalVariants(enterprise)
|
||||
|
||||
$scope.productSuppliedToOrderCycle = (product) ->
|
||||
OrderCycle.productSuppliedToOrderCycle(product)
|
||||
|
||||
$scope.variantSuppliedToOrderCycle = (variant) ->
|
||||
OrderCycle.variantSuppliedToOrderCycle(variant)
|
||||
|
||||
$scope.incomingExchangeVariantsFor = (enterprise_id) ->
|
||||
$filter('filterExchangeVariants')(OrderCycle.incomingExchangesVariants(), $scope.order_cycle.visible_variants_for_outgoing_exchanges[enterprise_id])
|
||||
|
||||
$scope.exchangeDirection = (exchange) ->
|
||||
OrderCycle.exchangeDirection(exchange)
|
||||
|
||||
$scope.enterprisesWithFees = ->
|
||||
$scope.enterprises[id] for id in OrderCycle.participatingEnterpriseIds() when $scope.enterpriseFeesForEnterprise(id).length > 0
|
||||
|
||||
$scope.toggleProducts = ($event, exchange) ->
|
||||
$event.preventDefault()
|
||||
OrderCycle.toggleProducts(exchange)
|
||||
|
||||
$scope.enterpriseFeesForEnterprise = (enterprise_id) ->
|
||||
EnterpriseFee.forEnterprise(parseInt(enterprise_id))
|
||||
|
||||
$scope.addSupplier = ($event) ->
|
||||
$event.preventDefault()
|
||||
OrderCycle.addSupplier($scope.new_supplier_id)
|
||||
|
||||
$scope.addDistributor = ($event) ->
|
||||
$event.preventDefault()
|
||||
OrderCycle.addDistributor($scope.new_distributor_id)
|
||||
|
||||
$scope.removeExchange = ($event, exchange) ->
|
||||
$event.preventDefault()
|
||||
OrderCycle.removeExchange(exchange)
|
||||
|
||||
$scope.addCoordinatorFee = ($event) ->
|
||||
$event.preventDefault()
|
||||
OrderCycle.addCoordinatorFee()
|
||||
|
||||
$scope.removeCoordinatorFee = ($event, index) ->
|
||||
$event.preventDefault()
|
||||
OrderCycle.removeCoordinatorFee(index)
|
||||
|
||||
$scope.addExchangeFee = ($event, exchange) ->
|
||||
$event.preventDefault()
|
||||
OrderCycle.addExchangeFee(exchange)
|
||||
|
||||
$scope.removeExchangeFee = ($event, exchange, index) ->
|
||||
$event.preventDefault()
|
||||
OrderCycle.removeExchangeFee(exchange, index)
|
||||
|
||||
$scope.removeDistributionOfVariant = (variant_id) ->
|
||||
OrderCycle.removeDistributionOfVariant(variant_id)
|
||||
|
||||
$scope.submit = (destination) ->
|
||||
OrderCycle.create(destination)
|
||||
@@ -0,0 +1,84 @@
|
||||
angular.module('admin.orderCycles')
|
||||
.controller 'AdminEditOrderCycleCtrl', ($scope, $filter, $location, OrderCycle, Enterprise, EnterpriseFee, StatusMessage) ->
|
||||
order_cycle_id = $location.absUrl().match(/\/admin\/order_cycles\/(\d+)/)[1]
|
||||
$scope.enterprises = Enterprise.index(order_cycle_id: order_cycle_id)
|
||||
$scope.supplier_enterprises = Enterprise.producer_enterprises
|
||||
$scope.distributor_enterprises = Enterprise.hub_enterprises
|
||||
$scope.supplied_products = Enterprise.supplied_products
|
||||
$scope.enterprise_fees = EnterpriseFee.index(order_cycle_id: order_cycle_id)
|
||||
|
||||
$scope.OrderCycle = OrderCycle
|
||||
$scope.order_cycle = OrderCycle.load(order_cycle_id)
|
||||
|
||||
$scope.StatusMessage = StatusMessage
|
||||
|
||||
$scope.loaded = ->
|
||||
Enterprise.loaded && EnterpriseFee.loaded && OrderCycle.loaded
|
||||
|
||||
$scope.suppliedVariants = (enterprise_id) ->
|
||||
Enterprise.suppliedVariants(enterprise_id)
|
||||
|
||||
$scope.exchangeSelectedVariants = (exchange) ->
|
||||
OrderCycle.exchangeSelectedVariants(exchange)
|
||||
|
||||
$scope.setExchangeVariants = (exchange, variants, selected) ->
|
||||
OrderCycle.setExchangeVariants(exchange, variants, selected)
|
||||
|
||||
$scope.enterpriseTotalVariants = (enterprise) ->
|
||||
Enterprise.totalVariants(enterprise)
|
||||
|
||||
$scope.productSuppliedToOrderCycle = (product) ->
|
||||
OrderCycle.productSuppliedToOrderCycle(product)
|
||||
|
||||
$scope.variantSuppliedToOrderCycle = (variant) ->
|
||||
OrderCycle.variantSuppliedToOrderCycle(variant)
|
||||
|
||||
$scope.incomingExchangeVariantsFor = (enterprise_id) ->
|
||||
$filter('filterExchangeVariants')(OrderCycle.incomingExchangesVariants(), $scope.order_cycle.visible_variants_for_outgoing_exchanges[enterprise_id])
|
||||
|
||||
$scope.exchangeDirection = (exchange) ->
|
||||
OrderCycle.exchangeDirection(exchange)
|
||||
|
||||
$scope.enterprisesWithFees = ->
|
||||
$scope.enterprises[id] for id in OrderCycle.participatingEnterpriseIds() when $scope.enterpriseFeesForEnterprise(id).length > 0
|
||||
|
||||
$scope.toggleProducts = ($event, exchange) ->
|
||||
$event.preventDefault()
|
||||
OrderCycle.toggleProducts(exchange)
|
||||
|
||||
$scope.enterpriseFeesForEnterprise = (enterprise_id) ->
|
||||
EnterpriseFee.forEnterprise(parseInt(enterprise_id))
|
||||
|
||||
$scope.addSupplier = ($event) ->
|
||||
$event.preventDefault()
|
||||
OrderCycle.addSupplier($scope.new_supplier_id)
|
||||
|
||||
$scope.addDistributor = ($event) ->
|
||||
$event.preventDefault()
|
||||
OrderCycle.addDistributor($scope.new_distributor_id)
|
||||
|
||||
$scope.removeExchange = ($event, exchange) ->
|
||||
$event.preventDefault()
|
||||
OrderCycle.removeExchange(exchange)
|
||||
|
||||
$scope.addCoordinatorFee = ($event) ->
|
||||
$event.preventDefault()
|
||||
OrderCycle.addCoordinatorFee()
|
||||
|
||||
$scope.removeCoordinatorFee = ($event, index) ->
|
||||
$event.preventDefault()
|
||||
OrderCycle.removeCoordinatorFee(index)
|
||||
|
||||
$scope.addExchangeFee = ($event, exchange) ->
|
||||
$event.preventDefault()
|
||||
OrderCycle.addExchangeFee(exchange)
|
||||
|
||||
$scope.removeExchangeFee = ($event, exchange, index) ->
|
||||
$event.preventDefault()
|
||||
OrderCycle.removeExchangeFee(exchange, index)
|
||||
|
||||
$scope.removeDistributionOfVariant = (variant_id) ->
|
||||
OrderCycle.removeDistributionOfVariant(variant_id)
|
||||
|
||||
$scope.submit = (destination) ->
|
||||
OrderCycle.update(destination)
|
||||
@@ -1,204 +0,0 @@
|
||||
angular.module('admin.orderCycles', ['ngResource', 'admin.utils'])
|
||||
.controller('AdminCreateOrderCycleCtrl', ['$scope', '$filter', 'OrderCycle', 'Enterprise', 'EnterpriseFee', 'ocInstance', 'StatusMessage', ($scope, $filter, OrderCycle, Enterprise, EnterpriseFee, ocInstance, StatusMessage) ->
|
||||
$scope.enterprises = Enterprise.index(coordinator_id: ocInstance.coordinator_id)
|
||||
$scope.supplier_enterprises = Enterprise.producer_enterprises
|
||||
$scope.distributor_enterprises = Enterprise.hub_enterprises
|
||||
$scope.supplied_products = Enterprise.supplied_products
|
||||
$scope.enterprise_fees = EnterpriseFee.index(coordinator_id: ocInstance.coordinator_id)
|
||||
|
||||
$scope.OrderCycle = OrderCycle
|
||||
$scope.order_cycle = OrderCycle.new({ coordinator_id: ocInstance.coordinator_id})
|
||||
|
||||
$scope.StatusMessage = StatusMessage
|
||||
|
||||
$scope.loaded = ->
|
||||
Enterprise.loaded && EnterpriseFee.loaded
|
||||
|
||||
$scope.suppliedVariants = (enterprise_id) ->
|
||||
Enterprise.suppliedVariants(enterprise_id)
|
||||
|
||||
$scope.exchangeSelectedVariants = (exchange) ->
|
||||
OrderCycle.exchangeSelectedVariants(exchange)
|
||||
|
||||
$scope.setExchangeVariants = (exchange, variants, selected) ->
|
||||
OrderCycle.setExchangeVariants(exchange, variants, selected)
|
||||
|
||||
$scope.enterpriseTotalVariants = (enterprise) ->
|
||||
Enterprise.totalVariants(enterprise)
|
||||
|
||||
$scope.productSuppliedToOrderCycle = (product) ->
|
||||
OrderCycle.productSuppliedToOrderCycle(product)
|
||||
|
||||
$scope.variantSuppliedToOrderCycle = (variant) ->
|
||||
OrderCycle.variantSuppliedToOrderCycle(variant)
|
||||
|
||||
$scope.incomingExchangeVariantsFor = (enterprise_id) ->
|
||||
$filter('filterExchangeVariants')(OrderCycle.incomingExchangesVariants(), $scope.order_cycle.visible_variants_for_outgoing_exchanges[enterprise_id])
|
||||
|
||||
$scope.exchangeDirection = (exchange) ->
|
||||
OrderCycle.exchangeDirection(exchange)
|
||||
|
||||
$scope.enterprisesWithFees = ->
|
||||
$scope.enterprises[id] for id in OrderCycle.participatingEnterpriseIds() when $scope.enterpriseFeesForEnterprise(id).length > 0
|
||||
|
||||
$scope.toggleProducts = ($event, exchange) ->
|
||||
$event.preventDefault()
|
||||
OrderCycle.toggleProducts(exchange)
|
||||
|
||||
$scope.enterpriseFeesForEnterprise = (enterprise_id) ->
|
||||
EnterpriseFee.forEnterprise(parseInt(enterprise_id))
|
||||
|
||||
$scope.addSupplier = ($event) ->
|
||||
$event.preventDefault()
|
||||
OrderCycle.addSupplier($scope.new_supplier_id)
|
||||
|
||||
$scope.addDistributor = ($event) ->
|
||||
$event.preventDefault()
|
||||
OrderCycle.addDistributor($scope.new_distributor_id)
|
||||
|
||||
$scope.removeExchange = ($event, exchange) ->
|
||||
$event.preventDefault()
|
||||
OrderCycle.removeExchange(exchange)
|
||||
|
||||
$scope.addCoordinatorFee = ($event) ->
|
||||
$event.preventDefault()
|
||||
OrderCycle.addCoordinatorFee()
|
||||
|
||||
$scope.removeCoordinatorFee = ($event, index) ->
|
||||
$event.preventDefault()
|
||||
OrderCycle.removeCoordinatorFee(index)
|
||||
|
||||
$scope.addExchangeFee = ($event, exchange) ->
|
||||
$event.preventDefault()
|
||||
OrderCycle.addExchangeFee(exchange)
|
||||
|
||||
$scope.removeExchangeFee = ($event, exchange, index) ->
|
||||
$event.preventDefault()
|
||||
OrderCycle.removeExchangeFee(exchange, index)
|
||||
|
||||
$scope.removeDistributionOfVariant = (variant_id) ->
|
||||
OrderCycle.removeDistributionOfVariant(variant_id)
|
||||
|
||||
$scope.submit = (destination) ->
|
||||
OrderCycle.create(destination)
|
||||
])
|
||||
|
||||
.controller('AdminEditOrderCycleCtrl', ['$scope', '$filter', '$location', 'OrderCycle', 'Enterprise', 'EnterpriseFee', 'StatusMessage', ($scope, $filter, $location, OrderCycle, Enterprise, EnterpriseFee, StatusMessage) ->
|
||||
order_cycle_id = $location.absUrl().match(/\/admin\/order_cycles\/(\d+)/)[1]
|
||||
$scope.enterprises = Enterprise.index(order_cycle_id: order_cycle_id)
|
||||
$scope.supplier_enterprises = Enterprise.producer_enterprises
|
||||
$scope.distributor_enterprises = Enterprise.hub_enterprises
|
||||
$scope.supplied_products = Enterprise.supplied_products
|
||||
$scope.enterprise_fees = EnterpriseFee.index(order_cycle_id: order_cycle_id)
|
||||
|
||||
$scope.OrderCycle = OrderCycle
|
||||
$scope.order_cycle = OrderCycle.load(order_cycle_id)
|
||||
|
||||
$scope.StatusMessage = StatusMessage
|
||||
|
||||
$scope.loaded = ->
|
||||
Enterprise.loaded && EnterpriseFee.loaded && OrderCycle.loaded
|
||||
|
||||
$scope.suppliedVariants = (enterprise_id) ->
|
||||
Enterprise.suppliedVariants(enterprise_id)
|
||||
|
||||
$scope.exchangeSelectedVariants = (exchange) ->
|
||||
OrderCycle.exchangeSelectedVariants(exchange)
|
||||
|
||||
$scope.setExchangeVariants = (exchange, variants, selected) ->
|
||||
OrderCycle.setExchangeVariants(exchange, variants, selected)
|
||||
|
||||
$scope.enterpriseTotalVariants = (enterprise) ->
|
||||
Enterprise.totalVariants(enterprise)
|
||||
|
||||
$scope.productSuppliedToOrderCycle = (product) ->
|
||||
OrderCycle.productSuppliedToOrderCycle(product)
|
||||
|
||||
$scope.variantSuppliedToOrderCycle = (variant) ->
|
||||
OrderCycle.variantSuppliedToOrderCycle(variant)
|
||||
|
||||
$scope.incomingExchangeVariantsFor = (enterprise_id) ->
|
||||
$filter('filterExchangeVariants')(OrderCycle.incomingExchangesVariants(), $scope.order_cycle.visible_variants_for_outgoing_exchanges[enterprise_id])
|
||||
|
||||
$scope.exchangeDirection = (exchange) ->
|
||||
OrderCycle.exchangeDirection(exchange)
|
||||
|
||||
$scope.enterprisesWithFees = ->
|
||||
$scope.enterprises[id] for id in OrderCycle.participatingEnterpriseIds() when $scope.enterpriseFeesForEnterprise(id).length > 0
|
||||
|
||||
$scope.toggleProducts = ($event, exchange) ->
|
||||
$event.preventDefault()
|
||||
OrderCycle.toggleProducts(exchange)
|
||||
|
||||
$scope.enterpriseFeesForEnterprise = (enterprise_id) ->
|
||||
EnterpriseFee.forEnterprise(parseInt(enterprise_id))
|
||||
|
||||
$scope.addSupplier = ($event) ->
|
||||
$event.preventDefault()
|
||||
OrderCycle.addSupplier($scope.new_supplier_id)
|
||||
|
||||
$scope.addDistributor = ($event) ->
|
||||
$event.preventDefault()
|
||||
OrderCycle.addDistributor($scope.new_distributor_id)
|
||||
|
||||
$scope.removeExchange = ($event, exchange) ->
|
||||
$event.preventDefault()
|
||||
OrderCycle.removeExchange(exchange)
|
||||
|
||||
$scope.addCoordinatorFee = ($event) ->
|
||||
$event.preventDefault()
|
||||
OrderCycle.addCoordinatorFee()
|
||||
|
||||
$scope.removeCoordinatorFee = ($event, index) ->
|
||||
$event.preventDefault()
|
||||
OrderCycle.removeCoordinatorFee(index)
|
||||
|
||||
$scope.addExchangeFee = ($event, exchange) ->
|
||||
$event.preventDefault()
|
||||
OrderCycle.addExchangeFee(exchange)
|
||||
|
||||
$scope.removeExchangeFee = ($event, exchange, index) ->
|
||||
$event.preventDefault()
|
||||
OrderCycle.removeExchangeFee(exchange, index)
|
||||
|
||||
$scope.removeDistributionOfVariant = (variant_id) ->
|
||||
OrderCycle.removeDistributionOfVariant(variant_id)
|
||||
|
||||
$scope.submit = (destination) ->
|
||||
OrderCycle.update(destination)
|
||||
])
|
||||
|
||||
.config(['$httpProvider', ($httpProvider) ->
|
||||
$httpProvider.defaults.headers.common['X-CSRF-Token'] = $('meta[name=csrf-token]').attr('content')
|
||||
])
|
||||
|
||||
.directive('datetimepicker', ['$parse', ($parse) ->
|
||||
(scope, element, attrs) ->
|
||||
# using $parse instead of scope[attrs.datetimepicker] for cases
|
||||
# where attrs.datetimepicker is 'foo.bar.lol'
|
||||
$(element).datetimepicker
|
||||
dateFormat: 'yy-mm-dd'
|
||||
timeFormat: 'HH:mm:ss'
|
||||
showOn: "button"
|
||||
buttonImage: "<%= asset_path 'datepicker/cal.gif' %>"
|
||||
buttonImageOnly: true
|
||||
stepMinute: 15
|
||||
onSelect: (dateText, inst) ->
|
||||
scope.$apply ->
|
||||
parsed = $parse(attrs.datetimepicker)
|
||||
parsed.assign(scope, dateText)
|
||||
])
|
||||
|
||||
.directive('ofnOnChange', ->
|
||||
(scope, element, attrs) ->
|
||||
element.bind 'change', ->
|
||||
scope.$apply(attrs.ofnOnChange)
|
||||
)
|
||||
|
||||
.directive('ofnSyncDistributions', ->
|
||||
(scope, element, attrs) ->
|
||||
element.bind 'change', ->
|
||||
if !$(this).is(':checked')
|
||||
scope.$apply ->
|
||||
scope.removeDistributionOfVariant(attrs.ofnSyncDistributions)
|
||||
)
|
||||
@@ -1 +1,32 @@
|
||||
angular.module('admin.orderCycles', ['ngResource', 'admin.indexUtils'])
|
||||
angular.module('admin.orderCycles', ['ngResource', 'admin.utils', 'admin.indexUtils'])
|
||||
|
||||
.config ($httpProvider) ->
|
||||
$httpProvider.defaults.headers.common['X-CSRF-Token'] = $('meta[name=csrf-token]').attr('content')
|
||||
|
||||
.directive 'datetimepicker', ($parse) ->
|
||||
(scope, element, attrs) ->
|
||||
# using $parse instead of scope[attrs.datetimepicker] for cases
|
||||
# where attrs.datetimepicker is 'foo.bar.lol'
|
||||
$(element).datetimepicker
|
||||
dateFormat: 'yy-mm-dd'
|
||||
timeFormat: 'HH:mm:ss'
|
||||
showOn: "button"
|
||||
buttonImage: "<%= asset_path 'datepicker/cal.gif' %>"
|
||||
buttonImageOnly: true
|
||||
stepMinute: 15
|
||||
onSelect: (dateText, inst) ->
|
||||
scope.$apply ->
|
||||
parsed = $parse(attrs.datetimepicker)
|
||||
parsed.assign(scope, dateText)
|
||||
|
||||
.directive 'ofnOnChange', ->
|
||||
(scope, element, attrs) ->
|
||||
element.bind 'change', ->
|
||||
scope.$apply(attrs.ofnOnChange)
|
||||
|
||||
.directive 'ofnSyncDistributions', ->
|
||||
(scope, element, attrs) ->
|
||||
element.bind 'change', ->
|
||||
if !$(this).is(':checked')
|
||||
scope.$apply ->
|
||||
scope.removeDistributionOfVariant(attrs.ofnSyncDistributions)
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
angular.module("ofn.admin").factory "StatusMessage", ($timeout) ->
|
||||
new class StatusMessage
|
||||
types:
|
||||
progress: {timeout: false, style: {color: '#ff9906'}}
|
||||
alert: {timeout: 5000, style: {color: 'grey'}}
|
||||
notice: {timeout: false, style: {color: 'grey'}}
|
||||
success: {timeout: 5000, style: {color: '#9fc820'}}
|
||||
failure: {timeout: false, style: {color: '#da5354'}}
|
||||
|
||||
statusMessage:
|
||||
text: ""
|
||||
style: {}
|
||||
|
||||
display: (type, text) ->
|
||||
@statusMessage.text = text
|
||||
@statusMessage.style = @types[type].style
|
||||
$timeout.cancel @statusMessage.timeout if @statusMessage.timeout
|
||||
timeout = @types[type].timeout
|
||||
if timeout
|
||||
@statusMessage.timeout = $timeout =>
|
||||
@clear()
|
||||
, timeout, true
|
||||
|
||||
clear: ->
|
||||
@statusMessage.text = ''
|
||||
@statusMessage.style = {}
|
||||
@@ -1,23 +0,0 @@
|
||||
angular.module("ofn.admin").factory "VariantOverrides", (variantOverrides, Indexer) ->
|
||||
new class VariantOverrides
|
||||
variantOverrides: {}
|
||||
|
||||
constructor: ->
|
||||
for vo in variantOverrides
|
||||
@variantOverrides[vo.hub_id] ||= {}
|
||||
@variantOverrides[vo.hub_id][vo.variant_id] = vo
|
||||
|
||||
ensureDataFor: (hubs, products) ->
|
||||
for hub in hubs
|
||||
@variantOverrides[hub.id] ||= {}
|
||||
for product in products
|
||||
for variant in product.variants
|
||||
@variantOverrides[hub.id][variant.id] ||=
|
||||
variant_id: variant.id
|
||||
hub_id: hub.id
|
||||
price: ''
|
||||
count_on_hand: ''
|
||||
|
||||
updateIds: (updatedVos) ->
|
||||
for vo in updatedVos
|
||||
@variantOverrides[vo.hub_id][vo.variant_id].id = vo.id
|
||||
@@ -0,0 +1,8 @@
|
||||
angular.module("admin.utils").directive "saveBar", (StatusMessage) ->
|
||||
restrict: "E"
|
||||
scope:
|
||||
save: "&"
|
||||
form: "="
|
||||
templateUrl: "admin/save_bar.html"
|
||||
link: (scope, element, attrs) ->
|
||||
scope.StatusMessage = StatusMessage
|
||||
@@ -11,6 +11,9 @@ angular.module("admin.utils").factory "StatusMessage", ($timeout) ->
|
||||
text: ""
|
||||
style: {}
|
||||
|
||||
active: ->
|
||||
@statusMessage.text != ''
|
||||
|
||||
display: (type, text) ->
|
||||
@statusMessage.text = text
|
||||
@statusMessage.style = @types[type].style
|
||||
@@ -20,6 +23,7 @@ angular.module("admin.utils").factory "StatusMessage", ($timeout) ->
|
||||
@statusMessage.timeout = $timeout =>
|
||||
@clear()
|
||||
, timeout, true
|
||||
null # So we don't return weird timeouts
|
||||
|
||||
clear: ->
|
||||
@statusMessage.text = ''
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
angular.module("admin.variantOverrides").controller "AdminVariantOverridesCtrl", ($scope, $http, $timeout, Indexer, Columns, SpreeApiAuth, PagedFetcher, StatusMessage, hubs, producers, hubPermissions, VariantOverrides, DirtyVariantOverrides) ->
|
||||
$scope.hubs = Indexer.index hubs
|
||||
$scope.hub = null
|
||||
$scope.products = []
|
||||
$scope.producers = producers
|
||||
$scope.producersByID = Indexer.index producers
|
||||
$scope.hubPermissions = hubPermissions
|
||||
$scope.variantOverrides = VariantOverrides.variantOverrides
|
||||
$scope.StatusMessage = StatusMessage
|
||||
|
||||
$scope.columns = Columns.setColumns
|
||||
producer: { name: "Producer", visible: true }
|
||||
product: { name: "Product", visible: true }
|
||||
sku: { name: "SKU", visible: false }
|
||||
price: { name: "Price", visible: true }
|
||||
on_hand: { name: "On Hand", visible: true }
|
||||
on_demand: { name: "On Demand", visible: false }
|
||||
reset: { name: "Reset Stock Level", visible: false }
|
||||
inheritance: { name: "Inheritance", visible: false }
|
||||
|
||||
$scope.resetSelectFilters = ->
|
||||
$scope.producerFilter = 0
|
||||
$scope.query = ''
|
||||
|
||||
$scope.resetSelectFilters()
|
||||
|
||||
$scope.initialise = ->
|
||||
SpreeApiAuth.authorise()
|
||||
.then ->
|
||||
$scope.spree_api_key_ok = true
|
||||
$scope.fetchProducts()
|
||||
.catch (message) ->
|
||||
$scope.api_error_msg = message
|
||||
|
||||
|
||||
$scope.fetchProducts = ->
|
||||
url = "/api/products/overridable?page=::page::;per_page=100"
|
||||
PagedFetcher.fetch url, (data) => $scope.addProducts data.products
|
||||
|
||||
|
||||
$scope.addProducts = (products) ->
|
||||
$scope.products = $scope.products.concat products
|
||||
VariantOverrides.ensureDataFor hubs, products
|
||||
|
||||
|
||||
$scope.selectHub = ->
|
||||
$scope.hub = $scope.hubs[$scope.hub_id]
|
||||
|
||||
$scope.displayDirty = ->
|
||||
if DirtyVariantOverrides.count() > 0
|
||||
num = if DirtyVariantOverrides.count() == 1 then "one override" else "#{DirtyVariantOverrides.count()} overrides"
|
||||
StatusMessage.display 'notice', "Changes to #{num} remain unsaved."
|
||||
else
|
||||
StatusMessage.clear()
|
||||
|
||||
$scope.update = ->
|
||||
if DirtyVariantOverrides.count() == 0
|
||||
StatusMessage.display 'alert', 'No changes to save.'
|
||||
else
|
||||
StatusMessage.display 'progress', 'Saving...'
|
||||
DirtyVariantOverrides.save()
|
||||
.success (updatedVos) ->
|
||||
DirtyVariantOverrides.clear()
|
||||
VariantOverrides.updateIds updatedVos
|
||||
$scope.variant_overrides_form.$setPristine()
|
||||
StatusMessage.display 'success', 'Changes saved.'
|
||||
VariantOverrides.updateData updatedVos # Refresh page data
|
||||
.error (data, status) ->
|
||||
StatusMessage.display 'failure', $scope.updateError(data, status)
|
||||
|
||||
|
||||
$scope.updateError = (data, status) ->
|
||||
if status == 401
|
||||
"I couldn't get authorisation to save those changes, so they remain unsaved."
|
||||
|
||||
else if status == 400 && data.errors?
|
||||
errors = []
|
||||
for field, field_errors of data.errors
|
||||
errors = errors.concat field_errors
|
||||
errors = errors.join ', '
|
||||
"I had some trouble saving: #{errors}"
|
||||
else
|
||||
"Oh no! I was unable to save your changes."
|
||||
|
||||
$scope.resetStock = ->
|
||||
if DirtyVariantOverrides.count() > 0
|
||||
StatusMessage.display 'alert', 'Save changes first.'
|
||||
$timeout ->
|
||||
$scope.displayDirty()
|
||||
, 3000 # 3 second delay
|
||||
else
|
||||
return unless $scope.hub_id?
|
||||
StatusMessage.display 'progress', 'Changing on hand stock levels...'
|
||||
$http
|
||||
method: "POST"
|
||||
url: "/admin/variant_overrides/bulk_reset"
|
||||
data: { hub_id: $scope.hub_id }
|
||||
.success (updatedVos) ->
|
||||
VariantOverrides.updateData updatedVos
|
||||
StatusMessage.display 'success', 'Stocks reset to defaults.'
|
||||
.error (data, status) ->
|
||||
$timeout -> StatusMessage.display 'failure', $scope.updateError(data, status)
|
||||
@@ -0,0 +1,12 @@
|
||||
angular.module("admin.variantOverrides").directive "trackInheritance", (VariantOverrides, DirtyVariantOverrides) ->
|
||||
require: "ngModel"
|
||||
link: (scope, element, attrs, ngModel) ->
|
||||
# This is a bit hacky, but it allows us to load the inherit property on the VO, but then not submit it
|
||||
scope.inherit = angular.equals scope.variantOverrides[scope.hub.id][scope.variant.id], VariantOverrides.newFor scope.hub.id, scope.variant.id
|
||||
|
||||
ngModel.$parsers.push (viewValue) ->
|
||||
if ngModel.$dirty && viewValue
|
||||
variantOverride = VariantOverrides.inherit(scope.hub.id, scope.variant.id)
|
||||
DirtyVariantOverrides.add variantOverride
|
||||
scope.displayDirty()
|
||||
viewValue
|
||||
@@ -1,9 +1,10 @@
|
||||
angular.module("ofn.admin").directive "ofnTrackVariantOverride", (DirtyVariantOverrides) ->
|
||||
angular.module("admin.variantOverrides").directive "ofnTrackVariantOverride", (DirtyVariantOverrides) ->
|
||||
require: "ngModel"
|
||||
link: (scope, element, attrs, ngModel) ->
|
||||
ngModel.$parsers.push (viewValue) ->
|
||||
if ngModel.$dirty
|
||||
variantOverride = scope.variantOverrides[scope.hub.id][scope.variant.id]
|
||||
scope.inherit = false
|
||||
DirtyVariantOverrides.add variantOverride
|
||||
scope.displayDirty()
|
||||
viewValue
|
||||
@@ -1,4 +1,4 @@
|
||||
angular.module("ofn.admin").filter "hubPermissions", ($filter) ->
|
||||
angular.module("admin.variantOverrides").filter "hubPermissions", ($filter) ->
|
||||
return (products, hubPermissions, hub_id) ->
|
||||
return [] if !hub_id
|
||||
return $filter('filter')(products, ((product) -> hubPermissions[hub_id].indexOf(product.producer_id) > -1), true)
|
||||
@@ -1,4 +1,4 @@
|
||||
angular.module("ofn.admin").factory "DirtyVariantOverrides", ($http) ->
|
||||
angular.module("admin.variantOverrides").factory "DirtyVariantOverrides", ($http) ->
|
||||
new class DirtyVariantOverrides
|
||||
dirtyVariantOverrides: {}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
|
||||
angular.module("admin.variantOverrides").factory "VariantOverrides", (variantOverrides) ->
|
||||
new class VariantOverrides
|
||||
variantOverrides: {}
|
||||
|
||||
constructor: ->
|
||||
for vo in variantOverrides
|
||||
@variantOverrides[vo.hub_id] ||= {}
|
||||
@variantOverrides[vo.hub_id][vo.variant_id] = vo
|
||||
|
||||
ensureDataFor: (hubs, products) ->
|
||||
for hub_id, hub of hubs
|
||||
@variantOverrides[hub.id] ||= {}
|
||||
for product in products
|
||||
for variant in product.variants
|
||||
@inherit(hub.id, variant.id) unless @variantOverrides[hub.id][variant.id]
|
||||
|
||||
inherit: (hub_id, variant_id) ->
|
||||
# This method is called from the trackInheritance directive, to reinstate inheritance
|
||||
@variantOverrides[hub_id][variant_id] ||= {}
|
||||
angular.extend @variantOverrides[hub_id][variant_id], @newFor hub_id, variant_id
|
||||
|
||||
newFor: (hub_id, variant_id) ->
|
||||
# These properties need to match those checked in VariantOverrideSet.deletable?
|
||||
hub_id: hub_id
|
||||
variant_id: variant_id
|
||||
sku: null
|
||||
price: null
|
||||
count_on_hand: null
|
||||
on_demand: null
|
||||
default_stock: null
|
||||
resettable: false
|
||||
|
||||
updateIds: (updatedVos) ->
|
||||
for vo in updatedVos
|
||||
@variantOverrides[vo.hub_id][vo.variant_id].id = vo.id
|
||||
|
||||
updateData: (updatedVos) ->
|
||||
for vo in updatedVos
|
||||
@variantOverrides[vo.hub_id][vo.variant_id] = vo
|
||||
@@ -0,0 +1 @@
|
||||
angular.module("admin.variantOverrides", ["pasvaz.bindonce", "admin.indexUtils", "admin.utils", "admin.dropdown"])
|
||||
@@ -1,9 +1,7 @@
|
||||
Darkswarm.controller "EnterprisesCtrl", ($scope, $rootScope, $timeout, Enterprises, Search, $document, HashNavigation, FilterSelectorsService, EnterpriseModal, enterpriseMatchesNameQueryFilter, distanceWithinKmFilter) ->
|
||||
$scope.Enterprises = Enterprises
|
||||
$scope.totalActive = FilterSelectorsService.totalActive
|
||||
$scope.clearAll = FilterSelectorsService.clearAll
|
||||
$scope.filterText = FilterSelectorsService.filterText
|
||||
$scope.FilterSelectorsService = FilterSelectorsService
|
||||
$scope.producers_to_filter = Enterprises.producers
|
||||
$scope.filterSelectors = FilterSelectorsService.createSelectors()
|
||||
$scope.query = Search.search()
|
||||
$scope.openModal = EnterpriseModal.open
|
||||
$scope.activeTaxons = []
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
Darkswarm.controller "GroupEnterprisesCtrl", ($scope, Search, FilterSelectorsService, EnterpriseModal) ->
|
||||
$scope.totalActive = FilterSelectorsService.totalActive
|
||||
$scope.clearAll = FilterSelectorsService.clearAll
|
||||
$scope.filterText = FilterSelectorsService.filterText
|
||||
$scope.FilterSelectorsService = FilterSelectorsService
|
||||
$scope.filterSelectors = FilterSelectorsService.createSelectors()
|
||||
$scope.query = Search.search()
|
||||
$scope.openModal = EnterpriseModal.open
|
||||
$scope.activeTaxons = []
|
||||
|
||||
@@ -15,6 +15,8 @@ Darkswarm.controller "GroupPageCtrl", ($scope, group_enterprises, Enterprises, M
|
||||
$scope.group_hubs = visible_enterprises.filter (enterprise) ->
|
||||
enterprise.category in ["hub", "hub_profile", "producer_hub", "producer_shop"]
|
||||
|
||||
$scope.producers_to_filter = $scope.group_producers
|
||||
|
||||
$scope.map = angular.copy MapConfiguration.options
|
||||
$scope.mapMarkers = OfnMap.enterprise_markers visible_enterprises
|
||||
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
Darkswarm.controller "ProductsCtrl", ($scope, $rootScope, Products, OrderCycle, FilterSelectorsService, Cart, Taxons, Properties) ->
|
||||
$scope.Products = Products
|
||||
$scope.Cart = Cart
|
||||
$scope.totalActive = FilterSelectorsService.totalActive
|
||||
$scope.clearAll = FilterSelectorsService.clearAll
|
||||
$scope.filterText = FilterSelectorsService.filterText
|
||||
$scope.FilterSelectorsService = FilterSelectorsService
|
||||
$scope.taxonSelectors = FilterSelectorsService.createSelectors()
|
||||
$scope.propertySelectors = FilterSelectorsService.createSelectors()
|
||||
$scope.filtersActive = true
|
||||
$scope.limit = 3
|
||||
$scope.order_cycle = OrderCycle.order_cycle
|
||||
@@ -33,4 +31,5 @@ Darkswarm.controller "ProductsCtrl", ($scope, $rootScope, Products, OrderCycle,
|
||||
|
||||
$scope.clearAll = ->
|
||||
$scope.query = ""
|
||||
FilterSelectorsService.clearAll()
|
||||
$scope.taxonSelectors.clearAll()
|
||||
$scope.propertySelectors.clearAll()
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
Darkswarm.directive "filterSelector", (FilterSelectorsService)->
|
||||
Darkswarm.directive "filterSelector", ->
|
||||
# Automatically builds activeSelectors for taxons
|
||||
# Lots of magic here
|
||||
restrict: 'E'
|
||||
replace: true
|
||||
scope:
|
||||
selectorSet: '='
|
||||
objects: "&"
|
||||
activeSelectors: "=?"
|
||||
allSelectors: "=?" # Optional
|
||||
@@ -36,7 +37,7 @@ Darkswarm.directive "filterSelector", (FilterSelectorsService)->
|
||||
if selector = selectors_by_id[id]
|
||||
selectors.push selector
|
||||
else
|
||||
selector = selectors_by_id[id] = FilterSelectorsService.new
|
||||
selector = selectors_by_id[id] = scope.selectorSet.new
|
||||
object: object
|
||||
selectors.push selector
|
||||
selectors
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Darkswarm.directive "shippingTypeSelector", (FilterSelectorsService)->
|
||||
Darkswarm.directive "shippingTypeSelector", ->
|
||||
# Builds selector for shipping types
|
||||
restrict: 'E'
|
||||
replace: true
|
||||
@@ -8,10 +8,10 @@ Darkswarm.directive "shippingTypeSelector", (FilterSelectorsService)->
|
||||
pickup: false
|
||||
delivery: false
|
||||
|
||||
scope.selectors =
|
||||
delivery: FilterSelectorsService.new
|
||||
scope.selectors =
|
||||
delivery: scope.filterSelectors.new
|
||||
icon: "ofn-i_039-delivery"
|
||||
pickup: FilterSelectorsService.new
|
||||
pickup: scope.filterSelectors.new
|
||||
icon: "ofn-i_038-takeaway"
|
||||
|
||||
scope.emit = ->
|
||||
|
||||
@@ -2,6 +2,7 @@ Darkswarm.directive 'singleLineSelectors', ($timeout, $filter) ->
|
||||
restrict: 'E'
|
||||
templateUrl: "single_line_selectors.html"
|
||||
scope:
|
||||
selectors: "="
|
||||
objects: "&"
|
||||
activeSelectors: "="
|
||||
selectorName: "@activeSelectors"
|
||||
|
||||
@@ -2,8 +2,11 @@ Darkswarm.factory 'Cart', (CurrentOrder, Variants, $timeout, $http, storage)->
|
||||
# Handles syncing of current cart/order state to server
|
||||
new class Cart
|
||||
dirty: false
|
||||
update_running: false
|
||||
update_enqueued: false
|
||||
order: CurrentOrder.order
|
||||
line_items: CurrentOrder.order?.line_items || []
|
||||
|
||||
constructor: ->
|
||||
for line_item in @line_items
|
||||
line_item.variant.line_item = line_item
|
||||
@@ -12,15 +15,31 @@ Darkswarm.factory 'Cart', (CurrentOrder, Variants, $timeout, $http, storage)->
|
||||
|
||||
orderChanged: =>
|
||||
@unsaved()
|
||||
|
||||
if !@update_running
|
||||
@scheduleUpdate()
|
||||
else
|
||||
@update_enqueued = true
|
||||
|
||||
scheduleUpdate: =>
|
||||
if @promise
|
||||
$timeout.cancel(@promise)
|
||||
@promise = $timeout @update, 1000
|
||||
|
||||
update: =>
|
||||
@update_running = true
|
||||
$http.post('/orders/populate', @data()).success (data, status)=>
|
||||
@saved()
|
||||
@update_running = false
|
||||
@popQueue() if @update_enqueued
|
||||
|
||||
.error (response, status)=>
|
||||
@scheduleRetry()
|
||||
@scheduleRetry(status)
|
||||
@update_running = false
|
||||
|
||||
popQueue: =>
|
||||
@update_enqueued = false
|
||||
@scheduleUpdate()
|
||||
|
||||
data: =>
|
||||
variants = {}
|
||||
@@ -30,7 +49,7 @@ Darkswarm.factory 'Cart', (CurrentOrder, Variants, $timeout, $http, storage)->
|
||||
max_quantity: li.max_quantity
|
||||
{variants: variants}
|
||||
|
||||
scheduleRetry: =>
|
||||
scheduleRetry: (status) =>
|
||||
console.log "Error updating cart: #{status}. Retrying in 3 seconds..."
|
||||
$timeout =>
|
||||
console.log "Retrying cart update"
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
# Returns a factory with the only function `createSelectors()`.
|
||||
# That function creates objects managing a list of filter selectors.
|
||||
Darkswarm.factory "FilterSelectorsService", ->
|
||||
# This stores all filters so we can access in-use counts etc
|
||||
# Accessed via activeSelector Directive
|
||||
new class FilterSelectorsService
|
||||
selectors: []
|
||||
class FilterSelectors
|
||||
constructor: ->
|
||||
@selectors = []
|
||||
|
||||
new: (obj = {})->
|
||||
obj.active = false
|
||||
@selectors.push obj
|
||||
@@ -26,3 +29,8 @@ Darkswarm.factory "FilterSelectorsService", ->
|
||||
for selector in @selectors
|
||||
selector.active = false
|
||||
selector.emit()
|
||||
|
||||
# Creates instances of `FilterSelectors`
|
||||
new class FilterSelectorsService
|
||||
createSelectors: ->
|
||||
new FilterSelectors
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
.ofn_drop_down{ "ofn-drop-down" => true }
|
||||
.ofn-drop-down
|
||||
%span
|
||||
%i.icon-check
|
||||
Actions
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
#save-bar.animate-show{ ng: { show: 'dirty()' } }
|
||||
#save-bar.animate-show{ ng: { show: 'form.$dirty || StatusMessage.active()' } }
|
||||
.twelve.columns.alpha
|
||||
%h5{ ng: { show: "dirty() && !saving()" } }
|
||||
You have unsaved changes
|
||||
%h5{ ng: { hide: "dirty() || saving()" } }
|
||||
All changes saved
|
||||
%h5{ ng: { show: "saving()" } }
|
||||
Saving...
|
||||
%h5#status-message{ ng: { style: 'StatusMessage.statusMessage.style' } }
|
||||
{{ StatusMessage.statusMessage.text || " " }}
|
||||
.four.columns.omega.text-right
|
||||
%input.red{type: "button", value: "Save Changes", ng: { click: "save()" } }
|
||||
%input.red{type: "button", value: "Save Changes", ng: { disabled: '!form.$dirty', click: "save()" } }
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
%active-selector{"ng-repeat" => "(name, selector) in selectors"}
|
||||
%i{"ng-class" => "selector.icon"}
|
||||
{{ name | capitalize }}
|
||||
%ul.small-block-grid-2.medium-block-grid-4.large-block-grid-2
|
||||
%active-selector{"ng-repeat" => "(name, selector) in selectors"}
|
||||
%i{"ng-class" => "selector.icon"}
|
||||
{{ name | capitalize }}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
-# In order for the single-line-selector scope to have access to the available selectors,
|
||||
%filter-selector{objects: "objects()", "active-selectors" => "activeSelectors", "all-selectors" => "allSelectors" }
|
||||
%filter-selector{"selector-set" => "selectors", objects: "objects()", "active-selectors" => "activeSelectors", "all-selectors" => "allSelectors" }
|
||||
|
||||
%ul{ ng: { if: "overFlowSelectors().length > 0 || fitting" } }
|
||||
%li.more
|
||||
|
||||
13
app/assets/stylesheets/admin/disabled.css.scss
Normal file
13
app/assets/stylesheets/admin/disabled.css.scss
Normal file
@@ -0,0 +1,13 @@
|
||||
label.disabled {
|
||||
color: #c3c3c3;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
input[type='button']:disabled {
|
||||
background-color: #c3c3c3;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.select2-container-disabled {
|
||||
pointer-events: none;
|
||||
}
|
||||
70
app/assets/stylesheets/admin/dropdown.css.scss
Normal file
70
app/assets/stylesheets/admin/dropdown.css.scss
Normal file
@@ -0,0 +1,70 @@
|
||||
#content-header .ofn-drop-down {
|
||||
border: none;
|
||||
background-color: #5498da;
|
||||
color: #fff;
|
||||
float: none;
|
||||
margin-left: 3px;
|
||||
}
|
||||
|
||||
.ofn-drop-down:hover, .ofn-drop-down.expanded {
|
||||
border: 1px solid #adadad;
|
||||
color: #575757;
|
||||
}
|
||||
|
||||
.ofn-drop-down {
|
||||
padding: 7px 15px;
|
||||
border-radius: 3px;
|
||||
border: 1px solid #d4d4d4;
|
||||
background-color: #f5f5f5;
|
||||
position: relative;
|
||||
display: block;
|
||||
float: left;
|
||||
color: #828282;
|
||||
cursor: pointer;
|
||||
-moz-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
text-align: center;
|
||||
|
||||
&.right {
|
||||
float: right;
|
||||
}
|
||||
|
||||
&:hover, &.expanded {
|
||||
border: 1px solid #adadad;
|
||||
color: #575757;
|
||||
}
|
||||
|
||||
> span {
|
||||
width: auto;
|
||||
text-transform: uppercase;
|
||||
font-size: 85%;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.menu {
|
||||
margin-top: 1px;
|
||||
position: absolute;
|
||||
float: none;
|
||||
top:100%;
|
||||
left: 0px;
|
||||
padding: 5px 0px;
|
||||
border: 1px solid #adadad;
|
||||
background-color: #ffffff;
|
||||
box-shadow: 1px 3px 10px #888888;
|
||||
z-index: 100;
|
||||
|
||||
.menu_item {
|
||||
margin: 0px;
|
||||
padding: 2px 0px;
|
||||
color: #454545;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.menu_item:hover {
|
||||
background-color: #ededed;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
.filters, .controls, .divider {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
@@ -184,68 +184,6 @@ table#listing_enterprise_groups {
|
||||
}
|
||||
}
|
||||
|
||||
#content-header .ofn_drop_down {
|
||||
border: none;
|
||||
background-color: #5498da;
|
||||
color: #fff;
|
||||
float: none;
|
||||
margin-left: 3px;
|
||||
}
|
||||
|
||||
.ofn_drop_down {
|
||||
padding: 6px 15px;
|
||||
border-radius: 3px;
|
||||
border: 1px solid #d4d4d4;
|
||||
background-color: #f5f5f5;
|
||||
position: relative;
|
||||
display: block;
|
||||
float: left;
|
||||
color: #828282;
|
||||
cursor: pointer;
|
||||
-moz-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
text-align: center;
|
||||
|
||||
> span {
|
||||
width: auto;
|
||||
text-transform: uppercase;
|
||||
font-size: 85%;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.menu {
|
||||
margin-top: 1px;
|
||||
position: absolute;
|
||||
float: none;
|
||||
top:100%;
|
||||
left: 0px;
|
||||
padding: 5px 0px;
|
||||
border: 1px solid #adadad;
|
||||
background-color: #ffffff;
|
||||
box-shadow: 1px 3px 10px #888888;
|
||||
z-index: 100;
|
||||
|
||||
.menu_item {
|
||||
margin: 0px;
|
||||
padding: 2px 0px;
|
||||
color: #454545;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.menu_item:hover {
|
||||
background-color: #ededed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ofn_drop_down:hover, .ofn_drop_down.expanded {
|
||||
border: 1px solid #adadad;
|
||||
color: #575757;
|
||||
}
|
||||
|
||||
.field_with_errors > input {
|
||||
border-color: red;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
.filter_select, .date_filter {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
input, div {
|
||||
&.update-pending {
|
||||
border: solid 1px orange;
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
height: 7rem
|
||||
float: left
|
||||
display: block
|
||||
z-index: 999999
|
||||
z-index: 1
|
||||
background-color: white
|
||||
overflow: hidden
|
||||
i
|
||||
@@ -56,4 +56,4 @@
|
||||
width: 0rem
|
||||
height: 0rem
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -11,6 +11,10 @@
|
||||
background-size: 922px 922px
|
||||
@include sidepaddingSm
|
||||
@include panepadding
|
||||
h1, p.text
|
||||
font-weight: 300
|
||||
h1
|
||||
font-size: 350%
|
||||
a > .group-name
|
||||
&:hover, &:focus, &:active
|
||||
text-decoration: underline
|
||||
@@ -97,11 +101,13 @@
|
||||
// Producers tab
|
||||
.producers
|
||||
background-image: none
|
||||
background-color: initial
|
||||
.active_table .active_table_node a.is_distributor, .active_table .active_table_node a.is_distributor i.ofn-i_059-producer
|
||||
color: $clr-turquoise
|
||||
padding: 0
|
||||
// Hubs tab
|
||||
.hubs
|
||||
background-image: none
|
||||
padding-top: 0
|
||||
padding-bottom: 0
|
||||
|
||||
|
||||
|
||||
@@ -48,3 +48,7 @@ dialog .close-reveal-modal, .reveal-modal .close-reveal-modal
|
||||
&:hover, &:active, &:focus
|
||||
background-color: rgba(205,205,205,1)
|
||||
color: #333
|
||||
|
||||
// Prevent body from scrolling when a modal is open
|
||||
body.modal-open
|
||||
overflow: hidden
|
||||
|
||||
@@ -13,6 +13,11 @@
|
||||
right: 10px
|
||||
top: 55px
|
||||
width: 480px
|
||||
|
||||
@media screen and (min-width: 641px)
|
||||
overflow-y: auto
|
||||
max-height: calc(95vh - 55px)
|
||||
|
||||
@media screen and (max-width: 640px)
|
||||
width: 96%
|
||||
|
||||
@@ -48,7 +53,7 @@
|
||||
.cart-item-delete
|
||||
a.delete
|
||||
font-size: 1.125em
|
||||
|
||||
|
||||
.item-thumb-image
|
||||
display: none
|
||||
@media screen and (min-width: 640px)
|
||||
|
||||
@@ -89,7 +89,7 @@ module Admin
|
||||
end
|
||||
|
||||
def collection_actions
|
||||
[:index, :for_order_cycle]
|
||||
[:index, :for_order_cycle, :bulk_update]
|
||||
end
|
||||
|
||||
def current_enterprise
|
||||
|
||||
@@ -175,5 +175,9 @@ module Admin
|
||||
def ams_prefix_whitelist
|
||||
[:basic]
|
||||
end
|
||||
|
||||
def collection_actions
|
||||
[:index, :bulk_update]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -4,32 +4,43 @@ module Admin
|
||||
class VariantOverridesController < ResourceController
|
||||
include OpenFoodNetwork::SpreeApiKeyLoader
|
||||
|
||||
prepend_before_filter :load_data
|
||||
before_filter :load_collection, only: [:bulk_update]
|
||||
before_filter :load_spree_api_key, only: :index
|
||||
before_filter :load_data
|
||||
|
||||
|
||||
def index
|
||||
end
|
||||
|
||||
|
||||
def bulk_update
|
||||
collection_hash = Hash[params[:variant_overrides].each_with_index.map { |vo, i| [i, vo] }]
|
||||
vo_set = VariantOverrideSet.new @variant_overrides, collection_attributes: collection_hash
|
||||
|
||||
# Ensure we're authorised to update all variant overrides
|
||||
vo_set.collection.each { |vo| authorize! :update, vo }
|
||||
@vo_set.collection.each { |vo| authorize! :update, vo }
|
||||
|
||||
if vo_set.save
|
||||
if @vo_set.save
|
||||
# Return saved VOs with IDs
|
||||
render json: vo_set.collection, each_serializer: Api::Admin::VariantOverrideSerializer
|
||||
render json: @vo_set.collection, each_serializer: Api::Admin::VariantOverrideSerializer
|
||||
else
|
||||
if vo_set.errors.present?
|
||||
render json: { errors: vo_set.errors }, status: 400
|
||||
if @vo_set.errors.present?
|
||||
render json: { errors: @vo_set.errors }, status: 400
|
||||
else
|
||||
render nothing: true, status: 500
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def bulk_reset
|
||||
# Ensure we're authorised to update all variant overrides.
|
||||
@collection.each { |vo| authorize! :bulk_reset, vo }
|
||||
@collection.each(&:reset_stock!)
|
||||
|
||||
if collection_errors.present?
|
||||
render json: { errors: collection_errors }, status: 400
|
||||
else
|
||||
render json: @collection, each_serializer: Api::Admin::VariantOverrideSerializer
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
@@ -43,10 +54,28 @@ module Admin
|
||||
|
||||
@hub_permissions = OpenFoodNetwork::Permissions.new(spree_current_user).
|
||||
variant_override_enterprises_per_hub
|
||||
@variant_overrides = VariantOverride.for_hubs(@hubs)
|
||||
end
|
||||
|
||||
def load_collection
|
||||
collection_hash = Hash[params[:variant_overrides].each_with_index.map { |vo, i| [i, vo] }]
|
||||
@vo_set = VariantOverrideSet.new @variant_overrides, collection_attributes: collection_hash
|
||||
end
|
||||
|
||||
def collection
|
||||
@variant_overrides = VariantOverride.for_hubs(params[:hub_id] || @hubs)
|
||||
end
|
||||
|
||||
def collection_actions
|
||||
[:index, :bulk_update, :bulk_reset]
|
||||
end
|
||||
|
||||
# This has been pulled from ModelSet as it is useful for compiling a list of errors on any generic collection (not necessarily a ModelSet)
|
||||
# Could be pulled down into a lower level controller if it is useful in other high level controllers
|
||||
def collection_errors
|
||||
errors = ActiveModel::Errors.new self
|
||||
full_messages = @collection.map { |element| element.errors.full_messages }.flatten
|
||||
full_messages.each { |fm| errors.add(:base, fm) }
|
||||
errors
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -23,13 +23,23 @@ Spree::OrdersController.class_eval do
|
||||
end
|
||||
|
||||
def populate
|
||||
populator = Spree::OrderPopulator.new(current_order(true), current_currency)
|
||||
if populator.populate(params.slice(:products, :variants, :quantity), true)
|
||||
fire_event('spree.cart.add')
|
||||
fire_event('spree.order.contents_changed')
|
||||
render json: true, status: 200
|
||||
else
|
||||
render json: false, status: 402
|
||||
# Without intervention, the Spree::Adjustment#update_adjustable callback is called many times
|
||||
# during cart population, for both taxation and enterprise fees. This operation triggers a
|
||||
# costly Spree::Order#update!, which only needs to be run once. We avoid this by disabling
|
||||
# callbacks on Spree::Adjustment and then manually invoke Spree::Order#update! on success.
|
||||
|
||||
Spree::Adjustment.without_callbacks do
|
||||
populator = Spree::OrderPopulator.new(current_order(true), current_currency)
|
||||
if populator.populate(params.slice(:products, :variants, :quantity), true)
|
||||
fire_event('spree.cart.add')
|
||||
fire_event('spree.order.contents_changed')
|
||||
|
||||
current_order.update!
|
||||
|
||||
render json: true, status: 200
|
||||
else
|
||||
render json: false, status: 402
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -31,12 +31,12 @@ module Admin
|
||||
admin_inject_json_ams_array ngModule, "shops", @shops, Api::Admin::IdNameSerializer
|
||||
end
|
||||
|
||||
def admin_inject_hubs
|
||||
admin_inject_json_ams_array "ofn.admin", "hubs", @hubs, Api::Admin::IdNameSerializer
|
||||
def admin_inject_hubs(opts={module: 'ofn.admin'})
|
||||
admin_inject_json_ams_array opts[:module], "hubs", @hubs, Api::Admin::IdNameSerializer
|
||||
end
|
||||
|
||||
def admin_inject_producers
|
||||
admin_inject_json_ams_array "ofn.admin", "producers", @producers, Api::Admin::IdNameSerializer
|
||||
def admin_inject_producers(opts={module: 'ofn.admin'})
|
||||
admin_inject_json_ams_array opts[:module], "producers", @producers, Api::Admin::IdNameSerializer
|
||||
end
|
||||
|
||||
def admin_inject_enterprise_permissions
|
||||
@@ -49,7 +49,7 @@ module Admin
|
||||
end
|
||||
|
||||
def admin_inject_hub_permissions
|
||||
render partial: "admin/json/injection_ams", locals: {ngModule: "ofn.admin", name: "hubPermissions", json: @hub_permissions.to_json}
|
||||
render partial: "admin/json/injection_ams", locals: {ngModule: "admin.variantOverrides", name: "hubPermissions", json: @hub_permissions.to_json}
|
||||
end
|
||||
|
||||
def admin_inject_products
|
||||
@@ -69,7 +69,7 @@ module Admin
|
||||
end
|
||||
|
||||
def admin_inject_variant_overrides
|
||||
admin_inject_json_ams_array "ofn.admin", "variantOverrides", @variant_overrides, Api::Admin::VariantOverrideSerializer
|
||||
admin_inject_json_ams_array "admin.variantOverrides", "variantOverrides", @variant_overrides, Api::Admin::VariantOverrideSerializer
|
||||
end
|
||||
|
||||
def admin_inject_order_cycle_instance
|
||||
@@ -85,7 +85,7 @@ module Admin
|
||||
end
|
||||
|
||||
def admin_inject_spree_api_key
|
||||
render partial: "admin/json/injection_ams", locals: {ngModule: 'ofn.admin', name: 'SpreeApiKey', json: "'#{@spree_api_key.to_s}'"}
|
||||
render partial: "admin/json/injection_ams", locals: {ngModule: 'admin.indexUtils', name: 'SpreeApiKey', json: "'#{@spree_api_key.to_s}'"}
|
||||
end
|
||||
|
||||
def admin_inject_json_ams(ngModule, name, data, serializer, opts = {})
|
||||
|
||||
@@ -6,7 +6,7 @@ module InjectionHelper
|
||||
end
|
||||
|
||||
def inject_group_enterprises
|
||||
inject_json_ams "group_enterprises", @group.enterprises, Api::EnterpriseSerializer, enterprise_injection_data
|
||||
inject_json_ams "group_enterprises", @group.enterprises.activated.all, Api::EnterpriseSerializer, enterprise_injection_data
|
||||
end
|
||||
|
||||
def inject_current_hub
|
||||
|
||||
@@ -10,4 +10,4 @@ Spree::BaseMailer.class_eval do
|
||||
# This lets us specify assets using relative paths in email templates
|
||||
super.merge(url_options: {host: URI(spree.root_url).host })
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -16,8 +16,8 @@ class ModelSet
|
||||
end
|
||||
end
|
||||
|
||||
def collection_attributes=(attributes)
|
||||
attributes.each do |k, attributes|
|
||||
def collection_attributes=(collection_attributes)
|
||||
collection_attributes.each do |k, attributes|
|
||||
# attributes == {:id => 123, :next_collection_at => '...'}
|
||||
e = @collection.detect { |e| e.id.to_s == attributes[:id].to_s && !e.id.nil? }
|
||||
if e.nil?
|
||||
@@ -41,7 +41,11 @@ class ModelSet
|
||||
end
|
||||
|
||||
def collection_to_delete
|
||||
collection.select { |e| @delete_if.andand.call(e.attributes) }
|
||||
# Remove all elements to be deleted from collection and return them
|
||||
# Allows us to render @model_set.collection without deleted elements
|
||||
deleted = []
|
||||
collection.delete_if { |e| deleted << e if @delete_if.andand.call(e.attributes) }
|
||||
deleted
|
||||
end
|
||||
|
||||
def collection_to_keep
|
||||
@@ -51,5 +55,4 @@ class ModelSet
|
||||
def persisted?
|
||||
false
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -16,7 +16,7 @@ class OrderCycle < ActiveRecord::Base
|
||||
scope :inactive, lambda { where('order_cycles.orders_open_at > ? OR order_cycles.orders_close_at < ?', Time.zone.now, Time.zone.now) }
|
||||
scope :upcoming, lambda { where('order_cycles.orders_open_at > ?', Time.zone.now) }
|
||||
scope :closed, lambda { where('order_cycles.orders_close_at < ?', Time.zone.now).order("order_cycles.orders_close_at DESC") }
|
||||
scope :undated, where(orders_open_at: nil, orders_close_at: nil)
|
||||
scope :undated, where('order_cycles.orders_open_at IS NULL OR orders_close_at IS NULL')
|
||||
|
||||
scope :soonest_closing, lambda { active.order('order_cycles.orders_close_at ASC') }
|
||||
# TODO This method returns all the closed orders. So maybe we can replace it with :recently_closed.
|
||||
@@ -182,7 +182,7 @@ class OrderCycle < ActiveRecord::Base
|
||||
end
|
||||
|
||||
def undated?
|
||||
self.orders_open_at.nil? && self.orders_close_at.nil?
|
||||
self.orders_open_at.nil? || self.orders_close_at.nil?
|
||||
end
|
||||
|
||||
def upcoming?
|
||||
|
||||
@@ -66,7 +66,7 @@ class AbilityDecorator
|
||||
def add_enterprise_management_abilities(user)
|
||||
# Spree performs authorize! on (:create, nil) when creating a new order from admin, and also (:search, nil)
|
||||
# when searching for variants to add to the order
|
||||
can [:create, :search, :bulk_update], nil
|
||||
can [:create, :search], nil
|
||||
|
||||
can [:admin, :index], :overview
|
||||
|
||||
@@ -111,7 +111,9 @@ class AbilityDecorator
|
||||
OpenFoodNetwork::Permissions.new(user).managed_product_enterprises.include? variant.product.supplier
|
||||
end
|
||||
|
||||
can [:admin, :index, :read, :update, :bulk_update], VariantOverride do |vo|
|
||||
can [:admin, :index, :read, :update, :bulk_update, :bulk_reset], VariantOverride do |vo|
|
||||
next false unless vo.hub.present? && vo.variant.andand.product.andand.supplier.present?
|
||||
|
||||
hub_auth = OpenFoodNetwork::Permissions.new(user).
|
||||
variant_override_hubs.
|
||||
include? vo.hub
|
||||
|
||||
@@ -35,5 +35,19 @@ module Spree
|
||||
def display_included_tax
|
||||
Spree::Money.new(included_tax, { :currency => currency })
|
||||
end
|
||||
|
||||
def self.without_callbacks
|
||||
skip_callback :save, :after, :update_adjustable
|
||||
skip_callback :destroy, :after, :update_adjustable
|
||||
|
||||
result = yield
|
||||
|
||||
ensure
|
||||
set_callback :save, :after, :update_adjustable
|
||||
set_callback :destroy, :after, :update_adjustable
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3,6 +3,8 @@ class VariantOverride < ActiveRecord::Base
|
||||
belongs_to :variant, class_name: 'Spree::Variant'
|
||||
|
||||
validates_presence_of :hub_id, :variant_id
|
||||
# Default stock can be nil, indicating stock should not be reset or zero, meaning reset to zero. Need to ensure this can be set by the user.
|
||||
validates :default_stock, numericality: { greater_than_or_equal_to: 0 }, allow_nil: true
|
||||
|
||||
scope :for_hubs, lambda { |hubs|
|
||||
where(hub_id: hubs)
|
||||
@@ -49,6 +51,21 @@ class VariantOverride < ActiveRecord::Base
|
||||
end
|
||||
end
|
||||
|
||||
def default_stock?
|
||||
default_stock.present?
|
||||
end
|
||||
|
||||
def reset_stock!
|
||||
if resettable
|
||||
if default_stock?
|
||||
self.attributes = { count_on_hand: default_stock }
|
||||
self.save
|
||||
else
|
||||
Bugsnag.notify RuntimeError.new "Attempting to reset stock level for a variant with no default stock level."
|
||||
end
|
||||
end
|
||||
self
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
|
||||
@@ -1,6 +1,16 @@
|
||||
class VariantOverrideSet < ModelSet
|
||||
def initialize(collection, attributes={})
|
||||
super(VariantOverride, collection, attributes, nil,
|
||||
proc { |attrs| attrs['price'].blank? && attrs['count_on_hand'].blank? } )
|
||||
super(VariantOverride, collection, attributes, nil, proc { |attrs| deletable?(attrs) } )
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def deletable?(attrs)
|
||||
attrs['price'].blank? &&
|
||||
attrs['count_on_hand'].blank? &&
|
||||
attrs['default_stock'].blank? &&
|
||||
attrs['resettable'].blank? &&
|
||||
attrs['sku'].nil? &&
|
||||
attrs['on_demand'].nil?
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
class Api::Admin::ExchangeSerializer < ActiveModel::Serializer
|
||||
attributes :id, :sender_id, :receiver_id, :incoming, :variants, :pickup_time, :pickup_instructions
|
||||
attributes :id, :sender_id, :receiver_id, :incoming, :variants, :receival_instructions, :pickup_time, :pickup_instructions
|
||||
|
||||
has_many :enterprise_fees, serializer: Api::Admin::BasicEnterpriseFeeSerializer
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
class Api::Admin::VariantOverrideSerializer < ActiveModel::Serializer
|
||||
attributes :id, :hub_id, :variant_id, :price, :count_on_hand
|
||||
attributes :id, :hub_id, :variant_id, :sku, :price, :count_on_hand, :on_demand, :default_stock, :resettable
|
||||
end
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
class Api::Admin::VariantSerializer < ActiveModel::Serializer
|
||||
attributes :id, :options_text, :unit_value, :unit_description, :unit_to_display, :on_demand, :display_as, :display_name, :name_to_display
|
||||
attributes :id, :options_text, :unit_value, :unit_description, :unit_to_display, :on_demand, :display_as, :display_name, :name_to_display, :sku
|
||||
attributes :on_hand, :price
|
||||
has_many :variant_overrides
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
class Api::LineItemSerializer < ActiveModel::Serializer
|
||||
attributes :id, :quantity, :price
|
||||
attributes :id, :quantity, :max_quantity, :price
|
||||
|
||||
has_one :variant, serializer: Api::VariantSerializer
|
||||
end
|
||||
|
||||
@@ -12,25 +12,13 @@
|
||||
.seven.columns.omega
|
||||
|
||||
.row{ 'ng-hide' => '!loaded() || filteredCustomers.length == 0' }
|
||||
.controls{ :class => "sixteen columns alpha", :style => "margin-bottom: 15px;" }
|
||||
.controls.sixteen.columns.alpha.omega
|
||||
.five.columns.alpha
|
||||
%input{ :class => "fullwidth", :type => "text", :id => 'quick_search', 'ng-model' => 'quickSearch', :placeholder => 'Quick Search' }
|
||||
%input.fullwidth{ :type => "text", :id => 'quick_search', 'ng-model' => 'quickSearch', :placeholder => 'Quick Search' }
|
||||
.five.columns
|
||||
-# %div.ofn_drop_down{ 'ng-controller' => "DropDownCtrl", :id => "bulk_actions_dropdown", 'ofn-drop-down' => true }
|
||||
-# %span{ :class => 'icon-check' } Actions
|
||||
-# %span{ 'ng-class' => "expanded && 'icon-caret-up' || !expanded && 'icon-caret-down'" }
|
||||
-# %div.menu{ 'ng-show' => "expanded" }
|
||||
-# %div.menu_item{ :class => "three columns alpha", 'ng-repeat' => "action in bulkActions", 'ng-click' => "selectedBulkAction.callback(filteredCustomers)", 'ofn-close-on-click' => true }
|
||||
-# %span{ :class => 'three columns omega' } {{action.name }}
|
||||
-# =render 'admin/shared/bulk_actions_dropdown'
|
||||
.three.columns
|
||||
.three.columns.omega
|
||||
%div.ofn_drop_down{ 'ng-controller' => "DropDownCtrl", :id => "columns_dropdown", 'ofn-drop-down' => true, :style => 'float:right;' }
|
||||
%span{ :class => 'icon-reorder' } Columns
|
||||
%span{ 'ng-class' => "expanded && 'icon-caret-up' || !expanded && 'icon-caret-down'" }
|
||||
%div.menu{ 'ng-show' => "expanded" }
|
||||
%div.menu_item{ :class => "three columns alpha", 'ng-repeat' => "column in columns", 'ofn-toggle-column' => true }
|
||||
%span{ :class => 'one column alpha', :style => 'text-align: center'} {{ column.visible && "✓" || !column.visible && " " }}
|
||||
%span{ :class => 'two columns omega' } {{column.name }}
|
||||
= render 'admin/shared/columns_dropdown'
|
||||
.row{ 'ng-if' => 'shop && !loaded()' }
|
||||
.sixteen.columns.alpha#loading
|
||||
%img.spinner{ src: "/assets/spinning-circles.svg" }
|
||||
|
||||
@@ -4,21 +4,9 @@
|
||||
.four.columns.alpha
|
||||
%input{ :class => "fullwidth", :type => "text", :id => 'quick_search', 'ng-model' => 'quickSearch', :placeholder => 'Search By Name' }
|
||||
.six.columns
|
||||
-# %div.ofn_drop_down{ 'ng-controller' => "DropDownCtrl", :id => "bulk_actions_dropdown", 'ofn-drop-down' => true }
|
||||
-# %span{ :class => 'icon-check' } Actions
|
||||
-# %span{ 'ng-class' => "expanded && 'icon-caret-up' || !expanded && 'icon-caret-down'" }
|
||||
-# %div.menu{ 'ng-show' => "expanded" }
|
||||
-# %div.menu_item{ :class => "three columns alpha", 'ng-repeat' => "action in bulkActions", 'ng-click' => "selectedBulkAction.callback(filteredEnterprises)", 'ofn-close-on-click' => true }
|
||||
-# %span{ :class => 'three columns omega' } {{action.name }}
|
||||
-# = render 'admin/shared/bulk_actions_dropdown'
|
||||
.three.columns
|
||||
.three.columns.omega
|
||||
%div.ofn_drop_down{ 'ng-controller' => "DropDownCtrl", :id => "columns_dropdown", 'ofn-drop-down' => true, :style => 'float:right;' }
|
||||
%span{ :class => 'icon-reorder' } Columns
|
||||
%span{ 'ng-class' => "expanded && 'icon-caret-up' || !expanded && 'icon-caret-down'" }
|
||||
%div.menu{ 'ng-show' => "expanded" }
|
||||
%div.menu_item{ :class => "three columns alpha", 'ng-repeat' => "column in columns", 'ofn-toggle-column' => true }
|
||||
%span{ :class => 'one column alpha', :style => 'text-align: center'} {{ column.visible && "✓" || !column.visible && " " }}
|
||||
%span{ :class => 'two columns omega' } {{column.name }}
|
||||
= render 'admin/shared/columns_dropdown'
|
||||
.row{ 'ng-if' => '!loaded' }
|
||||
.sixteen.columns.alpha#loading
|
||||
%img.spinner{ src: "/assets/spinning-circles.svg" }
|
||||
|
||||
7
app/views/admin/shared/_bulk_actions_dropdown.html.haml
Normal file
7
app/views/admin/shared/_bulk_actions_dropdown.html.haml
Normal file
@@ -0,0 +1,7 @@
|
||||
.three.columns
|
||||
.ofn-drop-down#bulk-actions-dropdown{ 'ng-controller' => "DropDownCtrl" }
|
||||
%span.icon-check Actions
|
||||
%span{ 'ng-class' => "expanded && 'icon-caret-up' || !expanded && 'icon-caret-down'" }
|
||||
%div.menu{ 'ng-show' => "expanded" }
|
||||
.three.columns.alpha.menu_item{ 'ng-repeat' => "action in bulkActions", 'ng-click' => "$eval(action.callback)(filteredLineItems)", 'ofn-close-on-click' => true }
|
||||
%span.three.columns.omega {{action.name }}
|
||||
8
app/views/admin/shared/_columns_dropdown.html.haml
Normal file
8
app/views/admin/shared/_columns_dropdown.html.haml
Normal file
@@ -0,0 +1,8 @@
|
||||
%div.three.columns.omega
|
||||
%div.ofn-drop-down.right#columns-dropdown{ 'ng-controller' => "DropDownCtrl" }
|
||||
%span{ :class => 'icon-reorder' } Columns
|
||||
%span{ 'ng-class' => "expanded && 'icon-caret-up' || !expanded && 'icon-caret-down'" }
|
||||
%div.menu{ 'ng-show' => "expanded" }
|
||||
%div.menu_item.three.columns.alpha.omega{ 'ng-repeat' => "column in columns", 'ofn-toggle-column' => true }
|
||||
%span.one.column.alpha.text-center {{ column.visible && "✓" || !column.visible && " " }}
|
||||
%span.two.columns.omega {{column.name }}
|
||||
@@ -1,4 +0,0 @@
|
||||
.row
|
||||
%input.four.columns.alpha{type: 'button', value: 'Save Changes', 'ng-click' => 'update()'}
|
||||
.twelve.columns.omega
|
||||
= render 'spree/admin/shared/status_message'
|
||||
@@ -1,5 +1,5 @@
|
||||
= admin_inject_spree_api_key
|
||||
= admin_inject_hubs
|
||||
= admin_inject_hubs module: 'admin.variantOverrides'
|
||||
= admin_inject_hub_permissions
|
||||
= admin_inject_producers
|
||||
= admin_inject_producers module: 'admin.variantOverrides'
|
||||
= admin_inject_variant_overrides
|
||||
|
||||
26
app/views/admin/variant_overrides/_filters.html.haml
Normal file
26
app/views/admin/variant_overrides/_filters.html.haml
Normal file
@@ -0,0 +1,26 @@
|
||||
.filters.sixteen.columns.alpha
|
||||
.filter.four.columns.alpha
|
||||
%label{ :for => 'query', ng: {class: '{disabled: !hub.id}'} }Quick Search
|
||||
%br
|
||||
%input.fullwidth{ :type => "text", :id => 'query', ng: { model: 'query', disabled: '!hub.id'} }
|
||||
.two.columns
|
||||
.filter_select.four.columns
|
||||
%label{ :for => 'hub_id', ng: { bind: 'hub_id ? "Shop" : "Select a shop"' } }
|
||||
%br
|
||||
%select.select2.fullwidth#hub_id{ 'ng-model' => 'hub_id', name: 'hub_id', ng: { options: 'hub.id as hub.name for (id, hub) in hubs', change: 'selectHub()' } }
|
||||
.filter_select.four.columns
|
||||
%label{ :for => 'producer_filter', ng: {class: '{disabled: !hub.id}'} }Producer
|
||||
%br
|
||||
%input.ofn-select2.fullwidth{ :id => 'producer_filter', type: 'number', style: 'display:none', data: 'producers', blank: "{id: 0, name: 'All'}", ng: { model: 'producerFilter', disabled: '!hub.id' } }
|
||||
-# .filter_select{ :class => "three columns" }
|
||||
-# %label{ :for => 'distributor_filter' }Hub
|
||||
-# %br
|
||||
-# %select{ :class => "three columns alpha", :id => 'distributor_filter', 'select2-min-search' => 5, 'ng-model' => 'distributorFilter', 'ng-options' => 'd.id as d.name for d in distributors'}
|
||||
-# .filter_select{ :class => "three columns" }
|
||||
-# %label{ :for => 'order_cycle_filter' }Order Cycle
|
||||
-# %br
|
||||
-# %select{ :class => "three columns alpha", :id => 'order_cycle_filter', 'select2-min-search' => 5, 'ng-model' => 'orderCycleFilter', 'ng-options' => 'oc.id as oc.name for oc in orderCycles', 'confirm-change' => "confirmRefresh()", 'ng-change' => 'refreshData()'}
|
||||
.filter_clear.two.columns.omega
|
||||
%label{ :for => 'clear_all_filters' }
|
||||
%br
|
||||
%input.red.fullwidth{ :type => 'button', :id => 'clear_all_filters', :value => "Clear All", ng: { click: "resetSelectFilters()", disabled: '!hub.id'} }
|
||||
@@ -1,7 +0,0 @@
|
||||
.row
|
||||
.two.columns.alpha
|
||||
Hub
|
||||
.four.columns
|
||||
%select.select2.fullwidth#hub_id{ 'ng-model' => 'hub_id', name: 'hub_id', 'ng-options' => 'hub.id as hub.name for hub in hubs' }
|
||||
.ten.columns.omega
|
||||
%input{ type: 'button', value: 'Go', 'ng-click' => 'selectHub()' }
|
||||
@@ -1,10 +1,23 @@
|
||||
%table.index.bulk{ng: {show: 'hub'}}
|
||||
%table.index.bulk{ ng: {show: 'hub'}}
|
||||
%col.producer{ width: "20%", ng: { show: 'columns.producer.visible' } }
|
||||
%col.product{ width: "20%", ng: { show: 'columns.product.visible' } }
|
||||
%col.sku{ width: "20%", ng: { show: 'columns.sku.visible' } }
|
||||
%col.price{ width: "10%", ng: { show: 'columns.price.visible' } }
|
||||
%col.on_hand{ width: "10%", ng: { show: 'columns.on_hand.visible' } }
|
||||
%col.on_demand{ width: "10%", ng: { show: 'columns.on_demand.visible' } }
|
||||
%col.reset{ width: "1%", ng: { show: 'columns.reset.visible' } }
|
||||
%col.reset{ width: "15%", ng: { show: 'columns.reset.visible' } }
|
||||
%col.inheritance{ width: "5%", ng: { show: 'columns.inheritance.visible' } }
|
||||
%thead
|
||||
%tr
|
||||
%th Producer
|
||||
%th Product
|
||||
%th Price
|
||||
%th On hand
|
||||
%tbody{ng: {repeat: 'product in products | hubPermissions:hubPermissions:hub.id'}}
|
||||
%tr{ ng: { controller: "ColumnsCtrl" } }
|
||||
%th.producer{ ng: { show: 'columns.producer.visible' } } Producer
|
||||
%th.product{ ng: { show: 'columns.product.visible' } } Product
|
||||
%th.sku{ ng: { show: 'columns.sku.visible' } } SKU
|
||||
%th.price{ ng: { show: 'columns.price.visible' } } Price
|
||||
%th.on_hand{ ng: { show: 'columns.on_hand.visible' } } On hand
|
||||
%th.on_demand{ ng: { show: 'columns.on_demand.visible' } } On Demand?
|
||||
%th.reset{ colspan: 2, ng: { show: 'columns.reset.visible' } } Enable Stock Level Reset?
|
||||
%th.inheritance{ ng: { show: 'columns.inheritance.visible' } } Inherit?
|
||||
%tbody{bindonce: true, ng: {repeat: 'product in products | hubPermissions:hubPermissions:hub.id | attrFilter:{producer_id:producerFilter} | filter:query' } }
|
||||
= render 'admin/variant_overrides/products_product'
|
||||
= render 'admin/variant_overrides/products_variants'
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
%tr.product.even
|
||||
%td {{ producers[product.producer_id].name }}
|
||||
%td {{ product.name }}
|
||||
%td
|
||||
%td
|
||||
%td.producer{ ng: { show: 'columns.producer.visible' }, bo: { bind: 'producersByID[product.producer_id].name'} }
|
||||
%td.product{ ng: { show: 'columns.product.visible' }, bo: { bind: 'product.name'} }
|
||||
%td.sku{ ng: { show: 'columns.sku.visible' } }
|
||||
%td.price{ ng: { show: 'columns.price.visible' } }
|
||||
%td.on_hand{ ng: { show: 'columns.on_hand.visible' } }
|
||||
%td.on_demand{ ng: { show: 'columns.on_demand.visible' } }
|
||||
%td.reset{ colspan: 2, ng: { show: 'columns.reset.visible' } }
|
||||
%td.inheritance{ ng: { show: 'columns.inheritance.visible' } }
|
||||
|
||||
@@ -1,10 +1,19 @@
|
||||
%tr.variant{ng: {repeat: 'variant in product.variants'}}
|
||||
%td
|
||||
%td
|
||||
{{ variant.display_name }}
|
||||
.variant-override-unit {{ variant.unit_to_display }}
|
||||
%td
|
||||
%tr.variant{ id: "v_{{variant.id}}", ng: {repeat: 'variant in product.variants'}}
|
||||
%td.producer{ ng: { show: 'columns.producer.visible' } }
|
||||
%td.product{ ng: { show: 'columns.product.visible' } }
|
||||
%span{ bo: { bind: 'variant.display_name || ""'} }
|
||||
.variant-override-unit{ bo: { bind: 'variant.unit_to_display'} }
|
||||
%td.sku{ ng: { show: 'columns.sku.visible' } }
|
||||
%input{name: 'variant-overrides-{{ variant.id }}-sku', type: 'text', ng: {model: 'variantOverrides[hub.id][variant.id].sku'}, placeholder: '{{ variant.sku }}', 'ofn-track-variant-override' => 'sku'}
|
||||
%td.price{ ng: { show: 'columns.price.visible' } }
|
||||
%input{name: 'variant-overrides-{{ variant.id }}-price', type: 'text', ng: {model: 'variantOverrides[hub.id][variant.id].price'}, placeholder: '{{ variant.price }}', 'ofn-track-variant-override' => 'price'}
|
||||
|
||||
%td
|
||||
%input{name: 'variant-overrides-{{ variant.id }}-count-on-hand', type: 'text', ng: {model: 'variantOverrides[hub.id][variant.id].count_on_hand'}, placeholder: '{{ variant.on_hand }}', 'ofn-track-variant-override' => 'price'}
|
||||
%td.on_hand{ ng: { show: 'columns.on_hand.visible' } }
|
||||
%input{name: 'variant-overrides-{{ variant.id }}-count_on_hand', type: 'text', ng: {model: 'variantOverrides[hub.id][variant.id].count_on_hand'}, placeholder: '{{ variant.on_hand }}', 'ofn-track-variant-override' => 'count_on_hand'}
|
||||
%td.on_demand{ ng: { show: 'columns.on_demand.visible' } }
|
||||
%input.field{ :type => 'checkbox', name: 'variant-overrides-{{ variant.id }}-on_demand', ng: { model: 'variantOverrides[hub.id][variant.id].on_demand' }, 'ofn-track-variant-override' => 'on_demand' }
|
||||
%td.reset{ ng: { show: 'columns.reset.visible' } }
|
||||
%input{name: 'variant-overrides-{{ variant.id }}-resettable', type: 'checkbox', ng: {model: 'variantOverrides[hub.id][variant.id].resettable'}, placeholder: '{{ variant.resettable }}', 'ofn-track-variant-override' => 'resettable'}
|
||||
%td.reset{ ng: { show: 'columns.reset.visible' } }
|
||||
%input{name: 'variant-overrides-{{ variant.id }}-default_stock', type: 'text', ng: {model: 'variantOverrides[hub.id][variant.id].default_stock'}, placeholder: '{{ variant.default_stock ? variant.default_stock : "Default stock"}}', 'ofn-track-variant-override' => 'default_stock'}
|
||||
%td.inheritance{ ng: { show: 'columns.inheritance.visible' } }
|
||||
%input.field{ :type => 'checkbox', name: 'variant-overrides-{{ variant.id }}-inherit', ng: { model: 'inherit' }, 'track-inheritance' => true }
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
= render 'admin/variant_overrides/header'
|
||||
= render 'admin/variant_overrides/data'
|
||||
|
||||
%div{ ng: { app: 'ofn.admin', controller: 'AdminVariantOverridesCtrl', init: 'initialise()' } }
|
||||
= render 'admin/variant_overrides/hub_choice'
|
||||
%div{ ng: { app: 'admin.variantOverrides', controller: 'AdminVariantOverridesCtrl', init: 'initialise()' } }
|
||||
= render 'admin/variant_overrides/filters'
|
||||
%hr.divider.sixteen.columns.alpha.omega{ ng: { show: 'hub' } }
|
||||
.controls.sixteen.columns.alpha.omega{ ng: { show: 'hub' } }
|
||||
%input.four.columns.alpha{ type: 'button', value: 'Reset Stock to Defaults', 'ng-click' => 'resetStock()' }
|
||||
%div.nine.columns.alpha
|
||||
= render 'admin/shared/columns_dropdown'
|
||||
|
||||
%div{ng: {show: 'hub'}}
|
||||
%h2 {{ hub.name }}
|
||||
= render 'admin/variant_overrides/actions'
|
||||
|
||||
= render 'admin/variant_overrides/products'
|
||||
%form{ name: 'variant_overrides_form' }
|
||||
%save-bar{ save: "update()", form: "variant_overrides_form" }
|
||||
= render 'admin/variant_overrides/products'
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
- content_for(:title) do
|
||||
= current_distributor.name
|
||||
- content_for(:description) do
|
||||
= current_distributor.description
|
||||
- content_for(:image) do
|
||||
= current_distributor.logo.url
|
||||
|
||||
= inject_enterprises
|
||||
|
||||
|
||||
21
app/views/groups/_hub_filters.html.haml
Normal file
21
app/views/groups/_hub_filters.html.haml
Normal file
@@ -0,0 +1,21 @@
|
||||
.row
|
||||
= render partial: 'shared/components/filter_controls'
|
||||
= render partial: 'shared/components/show_profiles'
|
||||
|
||||
.row.animate-show{"ng-show" => "filtersActive"}
|
||||
.small-12.columns
|
||||
.row.filter-box
|
||||
.small-12.large-9.columns
|
||||
%h5.tdhead
|
||||
.light
|
||||
= t :hubs_filter_by
|
||||
= t :hubs_filter_type
|
||||
%filter-selector.small-block-grid-2.medium-block-grid-4.large-block-grid-5{"selector-set" => "filterSelectors", objects: "group_hubs | searchEnterprises:query | shipping:shippingTypes | showHubProfiles:show_profiles | taxonsOf", "active-selectors" => "activeTaxons"}
|
||||
.small-12.large-3.columns
|
||||
%h5.tdhead
|
||||
.light
|
||||
= t :hubs_filter_by
|
||||
= t :hubs_filter_delivery
|
||||
%shipping-type-selector
|
||||
|
||||
= render partial: 'shared/components/filter_box'
|
||||
@@ -7,10 +7,14 @@
|
||||
angular.module('Darkswarm').value('groups', #{render partial: "json/groups", object: @groups})
|
||||
|
||||
#groups.pad-top.footer-pad{"ng-controller" => "GroupsCtrl"}
|
||||
#active-table-search.row.pad-top
|
||||
.small-12.columns
|
||||
.row
|
||||
.small-12.medium-6.medium-offset-3.columns.text-center
|
||||
%h1
|
||||
= t :groups_headline
|
||||
%p.text
|
||||
= t :groups_text
|
||||
#active-table-search.row.pad-top
|
||||
.small-12.columns
|
||||
%p
|
||||
%input{type: :text,
|
||||
"ng-model" => "query",
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
- content_for(:title) do
|
||||
= @group.name
|
||||
- content_for(:description) do
|
||||
= @group.description
|
||||
- content_for(:image) do
|
||||
= @group.logo.url
|
||||
|
||||
-# inject all enterprises as "enterprises"
|
||||
-# it could be more efficient to inject only the enterprises that are related to the group
|
||||
@@ -55,14 +59,13 @@
|
||||
%h1
|
||||
= t :groups_producers
|
||||
= render partial: "shared/components/enterprise_search"
|
||||
-# TODO: find out why this is not working
|
||||
-#= render partial: "producers/filters"
|
||||
= render partial: "producers/filters"
|
||||
|
||||
.row{bindonce: true}
|
||||
.small-12.columns
|
||||
.active_table
|
||||
%producer.active_table_node.row.animate-repeat{id: "{{producer.path}}",
|
||||
"ng-repeat" => "producer in filteredEnterprises = (group_producers | visible | searchEnterprises:query | taxons:activeTaxons)",
|
||||
"ng-repeat" => "producer in filteredEnterprises = (group_producers | searchEnterprises:query | taxons:activeTaxons)",
|
||||
"ng-controller" => "GroupEnterpriseNodeCtrl",
|
||||
"ng-class" => "{'closed' : !open(), 'open' : open(), 'inactive' : !producer.active}",
|
||||
id: "{{producer.hash}}"}
|
||||
@@ -83,17 +86,13 @@
|
||||
= t :groups_hubs
|
||||
|
||||
= render partial: "shared/components/enterprise_search"
|
||||
-# TODO: find out why this is not working
|
||||
-#= render partial: "home/filters"
|
||||
.small-12.medium-6.columns
|
||||
%span
|
||||
= render partial: 'shared/components/show_profiles'
|
||||
= render partial: "hub_filters"
|
||||
|
||||
.row{bindonce: true}
|
||||
.small-12.columns
|
||||
.active_table
|
||||
%hub.active_table_node.row.animate-repeat{id: "{{hub.hash}}",
|
||||
"ng-repeat" => "hub in filteredEnterprises = (group_hubs | visible | searchEnterprises:query | taxons:activeTaxons | shipping:shippingTypes | showHubProfiles:show_profiles | orderBy:['-active', '+orders_close_at'])",
|
||||
"ng-repeat" => "hub in filteredEnterprises = (group_hubs | searchEnterprises:query | taxons:activeTaxons | shipping:shippingTypes | showHubProfiles:show_profiles | orderBy:['-active', '+orders_close_at'])",
|
||||
"ng-class" => "{'is_profile' : hub.category == 'hub_profile', 'closed' : !open(), 'open' : open(), 'inactive' : !hub.active, 'current' : current()}",
|
||||
"ng-controller" => "GroupEnterpriseNodeCtrl"}
|
||||
.small-12.columns
|
||||
|
||||
@@ -11,13 +11,12 @@
|
||||
.light
|
||||
= t :hubs_filter_by
|
||||
= t :hubs_filter_type
|
||||
%filter-selector.small-block-grid-2.medium-block-grid-4.large-block-grid-5{ objects: "visibleMatches | visible | taxonsOf", "active-selectors" => "activeTaxons" }
|
||||
%filter-selector.small-block-grid-2.medium-block-grid-4.large-block-grid-5{ "selector-set" => "filterSelectors", objects: "visibleMatches | visible | taxonsOf", "active-selectors" => "activeTaxons" }
|
||||
.small-12.large-3.columns
|
||||
%h5.tdhead
|
||||
.light
|
||||
= t :hubs_filter_by
|
||||
= t :hubs_filter_delivery
|
||||
%ul.small-block-grid-2.medium-block-grid-4.large-block-grid-2
|
||||
%shipping-type-selector{results: "shippingTypes"}
|
||||
%shipping-type-selector
|
||||
|
||||
= render partial: 'shared/components/filter_box'
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
%head
|
||||
%meta{charset: 'utf-8'}/
|
||||
%meta{name: 'viewport', content: "width=device-width,initial-scale=1.0"}/
|
||||
|
||||
%meta{property: "og:title", content: content_for?(:title) ? yield(:title) : t(:title)}
|
||||
%meta{property: "og:description", content: content_for?(:description) ? yield(:description) : t(:site_meta_description)}
|
||||
%meta{property: "og:image", content: content_for?(:image) ? yield(:image) : ContentConfig.logo.url}
|
||||
%title= content_for?(:title) ? "#{yield(:title)} - #{t(:title)}".html_safe : "#{t(:welcome_to)} #{t(:title)}"
|
||||
- if Rails.env.production?
|
||||
= favicon_link_tag
|
||||
|
||||
@@ -11,5 +11,5 @@
|
||||
.light
|
||||
= t :producers_filter
|
||||
= t :producers_filter_type
|
||||
%filter-selector.small-block-grid-2.medium-block-grid-4.large-block-grid-6{objects: "Enterprises.producers | searchEnterprises:query | taxonsOf", "active-selectors" => "activeTaxons"}
|
||||
%filter-selector.small-block-grid-2.medium-block-grid-4.large-block-grid-6{"selector-set" => "filterSelectors", objects: "producers_to_filter | searchEnterprises:query | taxonsOf", "active-selectors" => "activeTaxons"}
|
||||
= render partial: 'shared/components/filter_box'
|
||||
|
||||
@@ -150,7 +150,7 @@
|
||||
= t :footer_legal_tos
|
||||
|
|
||||
= t :footer_legal_visit
|
||||
%a{href:"https://github.com/openfoodfoundation/openfoodnetwork", target: "_blank"} Github
|
||||
%a{href:"https://github.com/openfoodfoundation/openfoodnetwork", target: "_blank"} GitHub
|
||||
%p.text-small
|
||||
= t :footer_legal_text_html, {content_license: link_to('CC BY-SA 3.0', 'https://creativecommons.org/licenses/by-sa/3.0/'), code_license: link_to('AGPL 3', 'https://tldrlegal.com/license/gnu-affero-general-public-license-v3-(agpl-3.0)')}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
.row.filter-box.clear-filters.animate-show{"ng-show" => "filtersActive && totalActive() > 0"}
|
||||
.row.filter-box.clear-filters.animate-show{"ng-show" => "filtersActive && filterSelectors.totalActive() > 0"}
|
||||
.small-12.columns
|
||||
%a.button.secondary.small.expand{"ng-click" => "clearAll()"}
|
||||
%a.button.secondary.small.expand{"ng-click" => "filterSelectors.clearAll()"}
|
||||
%i.ofn-i_009-close
|
||||
= t :components_filters_clearfilters
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
%span.animate-show{"ng-show" => "filtersActive && totalActive() > 0"}
|
||||
%a.button.secondary.tiny{"ng-click" => "clearAll()"}
|
||||
%i.ofn-i_009-close
|
||||
= t :components_filters_clearfilters
|
||||
@@ -1,9 +1,9 @@
|
||||
.small-12.medium-6.columns
|
||||
%a.button.success.tiny.filterbtn{"ng-click" => "filtersActive = !filtersActive",
|
||||
"ng-show" => "FilterSelectorsService.selectors.length > 0"}
|
||||
{{ filterText(filtersActive) }}
|
||||
"ng-show" => "filterSelectors.selectors.length > 0"}
|
||||
{{ filterSelectors.filterText(filtersActive) }}
|
||||
%i.ofn-i_005-caret-down{"ng-show" => "!filtersActive"}
|
||||
%i.ofn-i_006-caret-up{"ng-show" => "filtersActive"}
|
||||
|
||||
%a.button.secondary.tiny.filterbtn.disabled{"ng-show" => "FilterSelectorsService.selectors.length == 0"}
|
||||
%a.button.secondary.tiny.filterbtn.disabled{"ng-show" => "filterSelectors.selectors.length == 0"}
|
||||
= t :components_filters_nofilters
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
%a.button.success.tiny.filterbtn{"ng-click" => "filtersActive = !filtersActive",
|
||||
"ng-show" => "FilterSelectorsService.selectors.length > 0"}
|
||||
{{ filterText(filtersActive) }}
|
||||
%i.ofn-i_005-caret-down{"ng-show" => "!filtersActive"}
|
||||
%i.ofn-i_006-caret-up{"ng-show" => "filtersActive"}
|
||||
|
||||
%a.button.secondary.tiny.filterbtn.disabled{"ng-show" => "FilterSelectorsService.selectors.length == 0"}
|
||||
= t :components_filters_nofilters
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user