Merge remote-tracking branch 'origin/master' into uk/spree-google-analytics

This commit is contained in:
Maikel Linke
2016-05-25 10:05:09 +10:00
223 changed files with 2634 additions and 1940 deletions

1
.gitignore vendored
View File

@@ -39,3 +39,4 @@ NERD_tree*
coverage
libpeerconnection.log
/config/application.yml
node_modules

View File

@@ -26,13 +26,16 @@ before_script:
- cp config/database.travis.yml config/database.yml
- cp config/application.yml.example config/application.yml
- RAILS_ENV=test bundle exec rake db:create db:schema:load
# Only install PhantomJS if it is not already present (ie. cached)
- npm list -g phantomjs-prebuilt@~2.1.7 --depth=0 || npm install -g phantomjs-prebuilt@~2.1.7
- export PATH=`npm bin -g`:$PATH
- >
if [ "$KARMA" = "true" ]; then
npm install karma@0.12.31
npm install karma-jasmine@0.1.5
npm install karma-phantomjs-launcher@0.1.4
npm install karma-coffee-preprocessor@0.2.1
npm install -g karma-cli@0.0.4
npm install -g npm@'3.8.8'
npm install
npm install -g karma-cli@0.1.2
fi
script:

View File

@@ -28,7 +28,7 @@ gem 'comfortable_mexican_sofa'
gem 'simple_form', :github => 'RohanM/simple_form'
gem 'unicorn'
gem 'angularjs-rails', '1.2.13'
gem 'angularjs-rails', '1.5.5'
gem 'bugsnag'
gem 'newrelic_rpm'
gem 'haml'

View File

@@ -153,7 +153,7 @@ GEM
sprockets (~> 2)
tilt
angularjs-file-upload-rails (1.1.0)
angularjs-rails (1.2.13)
angularjs-rails (1.5.5)
ansi (1.4.2)
arel (3.0.3)
atomic (1.1.99)
@@ -176,7 +176,8 @@ GEM
columnize (~> 0.3)
debugger-linecache (~> 1.2)
cancan (1.6.8)
capybara (2.5.0)
capybara (2.7.1)
addressable
mime-types (>= 1.16)
nokogiri (>= 1.3.3)
rack (>= 1.0.0)
@@ -455,7 +456,7 @@ GEM
railties (>= 3.1)
money (5.1.1)
i18n (~> 0.6.0)
multi_json (1.11.2)
multi_json (1.12.0)
multi_xml (0.5.5)
newrelic_rpm (3.12.0.288)
nokogiri (1.6.7.2)
@@ -479,7 +480,7 @@ GEM
paypal-sdk-merchant (1.106.1)
paypal-sdk-core (~> 0.2.3)
pg (0.13.2)
poltergeist (1.7.0)
poltergeist (1.9.0)
capybara (~> 2.1)
cliver (~> 0.3.1)
multi_json (~> 1.0)
@@ -626,7 +627,7 @@ GEM
webmock (1.13.0)
addressable (>= 2.2.7)
crack (>= 0.3.2)
websocket-driver (0.6.2)
websocket-driver (0.6.3)
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.2)
whenever (0.9.2)
@@ -650,7 +651,7 @@ DEPENDENCIES
andand
angular-rails-templates (~> 0.2.0)
angularjs-file-upload-rails (~> 1.1.0)
angularjs-rails (= 1.2.13)
angularjs-rails (= 1.5.5)
atomic
awesome_print
aws-sdk
@@ -734,4 +735,4 @@ DEPENDENCIES
wkhtmltopdf-binary
BUNDLED WITH
1.10.6
1.11.2

View File

@@ -43,9 +43,9 @@
//= require ./utils/utils
//= require ./users/users
//= require ./variant_overrides/variant_overrides
//= require textAngular.min.js
//= require textAngular-rangy.min.js
//= require textAngular-sanitize.min.js
//= require ../shared/bindonce.min.js
//= require textAngular.min.js
//= require darkswarm/i18n.js
//= require darkswarm/i18n.translate.js

View File

@@ -1,222 +0,0 @@
angular.module("ofn.admin").controller "AdminOrderMgmtCtrl", [
"$scope", "$http", "$filter", "dataFetcher", "blankOption", "pendingChanges", "VariantUnitManager", "OptionValueNamer", "SpreeApiKey", "Columns"
($scope, $http, $filter, dataFetcher, blankOption, pendingChanges, VariantUnitManager, OptionValueNamer, SpreeApiKey, Columns) ->
$scope.loading = true
$scope.initialiseVariables = ->
start = daysFromToday -7
end = daysFromToday 1
$scope.lineItems = []
$scope.filteredLineItems = []
$scope.confirmDelete = true
$scope.startDate = formatDate start
$scope.endDate = formatDate end
$scope.quickSearch = ""
$scope.bulkActions = [ { name: t("bom_actions_delete"), callback: $scope.deleteLineItems } ]
$scope.selectedBulkAction = $scope.bulkActions[0]
$scope.selectedUnitsProduct = {};
$scope.selectedUnitsVariant = {};
$scope.sharedResource = false
$scope.columns = Columns.setColumns
order_no: { name: t("bom_no"), visible: false }
full_name: { name: t("name"), visible: true }
email: { name: t("email"), visible: false }
phone: { name: t("phone"), visible: false }
order_date: { name: t("bom_date"), visible: true }
producer: { name: t("producer"), visible: true }
order_cycle: { name: t("bom_cycle"), visible: false }
hub: { name: t("bom_hub"), visible: false }
variant: { name: t("bom_variant"), visible: true }
quantity: { name: t("bom_quantity"), visible: true }
max: { name: t("bom_max"), visible: true }
final_weight_volume: { name: t("bom_final_weigth_volume"), visible: false }
price: { name: t("price"), visible: false }
$scope.initialise = ->
$scope.initialiseVariables()
authorise_api_reponse = ""
dataFetcher("/api/users/authorise_api?token=" + SpreeApiKey).then (data) ->
authorise_api_reponse = data
$scope.spree_api_key_ok = data.hasOwnProperty("success") and data["success"] == "Use of API Authorised"
if $scope.spree_api_key_ok
$http.defaults.headers.common["X-Spree-Token"] = SpreeApiKey
dataFetcher("/api/enterprises/accessible?template=bulk_index&q[is_primary_producer_eq]=true").then (data) ->
$scope.suppliers = $filter('orderBy')(data, 'name')
$scope.suppliers.unshift blankOption()
dataFetcher("/api/enterprises/accessible?template=bulk_index&q[sells_in][]=own&q[sells_in][]=any").then (data) ->
$scope.distributors = $filter('orderBy')(data, 'name')
$scope.distributors.unshift blankOption()
ocFetcher = dataFetcher("/api/order_cycles/accessible?as=distributor&q[orders_close_at_gt]=#{formatDate(daysFromToday(-90))}").then (data) ->
$scope.orderCycles = data
$scope.orderCyclesByID = []
$scope.orderCyclesByID[oc.id] = oc for oc in $scope.orderCycles
$scope.orderCycles.unshift blankOption()
$scope.fetchOrders()
ocFetcher.then ->
$scope.resetSelectFilters()
else if authorise_api_reponse.hasOwnProperty("error")
$scope.api_error_msg = authorise_api_reponse("error")
else
api_error_msg = "You don't have an API key yet. An attempt was made to generate one, but you are currently not authorised, please contact your site administrator for access."
$scope.fetchOrders = ->
$scope.loading = true
dataFetcher("/admin/orders/managed?template=bulk_index;page=1;per_page=500;q[state_not_eq]=canceled;q[completed_at_not_null]=true;q[completed_at_gt]=#{$scope.startDate};q[completed_at_lt]=#{$scope.endDate}").then (data) ->
$scope.resetOrders data
$scope.loading = false
$scope.resetOrders = (data) ->
$scope.orders = data
$scope.resetLineItems()
pendingChanges.removeAll()
$scope.resetLineItems = ->
$scope.lineItems = $scope.orders.reduce (lineItems,order) ->
orderWithoutLineItems = $scope.lineItemOrder order
for i,line_item of order.line_items
line_item.checked = false
line_item.supplier = $scope.matchObject $scope.suppliers, line_item.supplier, null
line_item.order = orderWithoutLineItems
line_item.original_final_weight_volume = line_item.final_weight_volume
line_item.original_quantity = line_item.quantity
line_item.original_price = line_item.price
lineItems.concat order.line_items
, []
$scope.lineItemOrder = (order) ->
lineItemOrder = angular.copy(order)
delete lineItemOrder.line_items
lineItemOrder.distributor = $scope.matchObject $scope.distributors, order.distributor, null
lineItemOrder.order_cycle = $scope.matchObject $scope.orderCycles, order.order_cycle, null
lineItemOrder
$scope.matchObject = (list, testObject, noMatch) ->
for i, object of list
if angular.equals(object, testObject)
return object
return noMatch
$scope.deleteLineItem = (lineItem) ->
if ($scope.confirmDelete && confirm("Are you sure?")) || !$scope.confirmDelete
$http(
method: "DELETE"
url: "/api/orders/" + lineItem.order.number + "/line_items/" + lineItem.id
).success (data) ->
$scope.lineItems.splice $scope.lineItems.indexOf(lineItem), 1
$scope.deleteLineItems = (lineItems) ->
existingState = $scope.confirmDelete
$scope.confirmDelete = false
$scope.deleteLineItem lineItem for lineItem in lineItems when lineItem.checked
$scope.confirmDelete = existingState
$scope.submit = ->
if $scope.bulk_order_form.$valid
pendingChanges.submitAll()
else
alert "Some errors must be resolved be before you can update orders.\nAny fields with red borders contain errors."
$scope.allBoxesChecked = ->
checkedCount = $scope.filteredLineItems.reduce (count,lineItem) ->
count + (if lineItem.checked then 1 else 0 )
, 0
checkedCount == $scope.filteredLineItems.length
$scope.toggleAllCheckboxes = ->
changeTo = !$scope.allBoxesChecked()
lineItem.checked = changeTo for lineItem in $scope.filteredLineItems
$scope.setSelectedUnitsVariant = (unitsProduct,unitsVariant) ->
$scope.selectedUnitsProduct = unitsProduct
$scope.selectedUnitsVariant = unitsVariant
$scope.sumUnitValues = ->
sum = $scope.filteredLineItems.reduce (sum,lineItem) ->
sum = sum + lineItem.final_weight_volume
, 0
$scope.sumMaxUnitValues = ->
sum = $scope.filteredLineItems.reduce (sum,lineItem) ->
sum = sum + Math.max(lineItem.max_quantity,lineItem.original_quantity) * lineItem.units_variant.unit_value
, 0
$scope.allFinalWeightVolumesPresent = ->
for i,lineItem of $scope.filteredLineItems
return false if !lineItem.hasOwnProperty('final_weight_volume') || !(lineItem.final_weight_volume > 0)
true
# How is this different to OptionValueNamer#name?
# Should it be extracted to that class or VariantUnitManager?
$scope.formattedValueWithUnitName = (value, unitsProduct, unitsVariant) ->
# A Units Variant is an API object which holds unit properies of a variant
if unitsProduct.hasOwnProperty("variant_unit") && (unitsProduct.variant_unit == "weight" || unitsProduct.variant_unit == "volume") && value > 0
scale = VariantUnitManager.getScale(value, unitsProduct.variant_unit)
Math.round(value/scale * 1000)/1000 + " " + VariantUnitManager.getUnitName(scale, unitsProduct.variant_unit)
else
''
$scope.fulfilled = (sumOfUnitValues) ->
# A Units Variant is an API object which holds unit properies of a variant
if $scope.selectedUnitsProduct.hasOwnProperty("group_buy_unit_size") && $scope.selectedUnitsProduct.group_buy_unit_size > 0 &&
$scope.selectedUnitsProduct.hasOwnProperty("variant_unit") &&
( $scope.selectedUnitsProduct.variant_unit == "weight" || $scope.selectedUnitsProduct.variant_unit == "volume" )
Math.round( sumOfUnitValues / $scope.selectedUnitsProduct.group_buy_unit_size * 1000)/1000
else
''
$scope.unitsVariantSelected = ->
!angular.equals($scope.selectedUnitsVariant,{})
$scope.resetSelectFilters = ->
$scope.distributorFilter = $scope.distributors[0].id
$scope.supplierFilter = $scope.suppliers[0].id
$scope.orderCycleFilter = $scope.orderCycles[0].id
$scope.quickSearch = ""
$scope.weightAdjustedPrice = (lineItem) ->
if lineItem.final_weight_volume > 0
unit_value = lineItem.final_weight_volume / lineItem.quantity
original_unit_value = lineItem.original_final_weight_volume / lineItem.original_quantity
lineItem.price = lineItem.original_price * (unit_value / original_unit_value)
$scope.unitValueLessThanZero = (lineItem) ->
if lineItem.units_variant.unit_value <= 0
true
else
false
$scope.updateOnQuantity = (lineItem) ->
if lineItem.quantity > 0
lineItem.final_weight_volume = lineItem.original_final_weight_volume * lineItem.quantity / lineItem.original_quantity
$scope.weightAdjustedPrice(lineItem)
$scope.$watch "orderCycleFilter", (newVal, oldVal) ->
unless $scope.orderCycleFilter == "0" || angular.equals(newVal, oldVal)
$scope.startDate = $scope.orderCyclesByID[$scope.orderCycleFilter].first_order
$scope.endDate = $scope.orderCyclesByID[$scope.orderCycleFilter].last_order
]
daysFromToday = (days) ->
now = new Date
now.setHours(0)
now.setMinutes(0)
now.setSeconds(0)
now.setDate( now.getDate() + days )
now
formatDate = (date) ->
year = date.getFullYear()
month = twoDigitNumber date.getMonth() + 1
day = twoDigitNumber date.getDate()
return year + "-" + month + "-" + day
formatTime = (date) ->
hours = twoDigitNumber date.getHours()
mins = twoDigitNumber date.getMinutes()
secs = twoDigitNumber date.getSeconds()
return hours + ":" + mins + ":" + secs
twoDigitNumber = (number) ->
twoDigits = "" + number
twoDigits = ("0" + number) if number < 10
twoDigits

View File

@@ -3,18 +3,7 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout
$scope.StatusMessage = StatusMessage
$scope.columns = Columns.setColumns
producer: {name: t("products_producer"), visible: true}
sku: {name: t("products_sku"), visible: false}
name: {name: t("products_name"), visible: true}
unit: {name: t("products_unit"), visible: true}
price: {name: t("products_price"), visible: true}
on_hand: {name: t("products_on_hand"), visible: true}
on_demand: {name: t("products_on_demand"), visible: false}
category: {name: t("products_category"), visible: false}
tax_category: {name: t("products_tax_category"), visible: false}
inherits_properties: {name: t("products_inherits_properties"), visible: false}
available_on: {name: t("products_available_on"), visible: false}
$scope.columns = Columns.columns
$scope.variant_unit_options = VariantUnitManager.variantUnitOptions()

View File

@@ -9,6 +9,10 @@ angular.module("admin.businessModelConfiguration").controller "BusinessModelConf
return $scope.bill() if !$scope.cap? || Number($scope.cap) == 0
Math.min($scope.bill(), Number($scope.cap))
$scope.finalBill = ->
return 0 if Number($scope.turnover) < Number($scope.minBillableTurnover)
$scope.cappedBill()
$scope.capReached = ->
return "No" if !$scope.cap? || Number($scope.cap) == 0
if $scope.bill() >= Number($scope.cap) then "Yes" else "No"
@@ -18,4 +22,4 @@ angular.module("admin.businessModelConfiguration").controller "BusinessModelConf
($scope.cappedBill() * Number($scope.taxRate))
$scope.total = ->
$scope.cappedBill() + $scope.includedTax()
$scope.finalBill() + $scope.includedTax()

View File

@@ -1,43 +1,24 @@
angular.module("admin.customers").controller "customersCtrl", ($scope, CustomerResource, TagsResource, $q, Columns, pendingChanges, shops) ->
$scope.shop = {}
angular.module("admin.customers").controller "customersCtrl", ($scope, $q, Customers, TagRuleResource, CurrentShop, RequestMonitor, Columns, pendingChanges, shops) ->
$scope.shops = shops
$scope.CurrentShop = CurrentShop
$scope.RequestMonitor = RequestMonitor
$scope.submitAll = pendingChanges.submitAll
$scope.add = Customers.add
$scope.deleteCustomer = Customers.remove
$scope.customerLimit = 20
$scope.columns = Columns.columns
$scope.columns = Columns.setColumns
email: { name: "Email", visible: true }
code: { name: "Code", visible: true }
tags: { name: "Tags", visible: true }
$scope.$watch "shop.id", ->
if $scope.shop.id?
$scope.customers = index {enterprise_id: $scope.shop.id}
$scope.$watch "CurrentShop.shop", ->
if $scope.CurrentShop.shop.id?
Customers.index({enterprise_id: $scope.CurrentShop.shop.id}).then (data) ->
$scope.customers = data
$scope.findTags = (query) ->
defer = $q.defer()
params =
enterprise_id: $scope.shop.id
TagsResource.index params, (data) =>
enterprise_id: $scope.CurrentShop.shop.id
TagRuleResource.mapByTag params, (data) =>
filtered = data.filter (tag) ->
tag.text.toLowerCase().indexOf(query.toLowerCase()) != -1
defer.resolve filtered
defer.promise
$scope.add = (email) ->
params =
enterprise_id: $scope.shop.id
email: email
CustomerResource.create params, (customer) =>
if customer.id
$scope.customers.push customer
$scope.quickSearch = customer.email
$scope.deleteCustomer = (customer) ->
params = id: customer.id
CustomerResource.destroy params, ->
i = $scope.customers.indexOf customer
$scope.customers.splice i, 1 unless i < 0
index = (params) ->
$scope.loaded = false
CustomerResource.index params, =>
$scope.loaded = true

View File

@@ -1 +1 @@
angular.module("admin.customers", ['ngResource', 'ngTagsInput', 'admin.indexUtils', 'admin.utils', 'admin.dropdown'])
angular.module("admin.customers", ['ngResource', 'admin.tagRules', 'admin.indexUtils', 'admin.utils', 'admin.dropdown'])

View File

@@ -0,0 +1,46 @@
angular.module("admin.customers").directive 'newCustomerDialog', ($compile, $injector, $templateCache, $window, CurrentShop, Customers) ->
restrict: 'A'
scope: true
link: (scope, element, attr) ->
scope.CurrentShop = CurrentShop
scope.submitted = null
scope.email = ""
scope.errors = []
scope.addCustomer = (valid) ->
scope.submitted = scope.email
scope.errors = []
if valid
Customers.add(scope.email).$promise.then (data) ->
if data.id
scope.email = ""
scope.submitted = null
template.dialog('close')
, (response) ->
if response.data.errors
scope.errors.push(error) for error in response.data.errors
else
scope.errors.push("Sorry! Could not create '#{scope.email}'")
return
# Compile modal template
template = $compile($templateCache.get('admin/new_customer_dialog.html'))(scope)
# Set Dialog options
template.dialog
show: { effect: "fade", duration: 400 }
hide: { effect: "fade", duration: 300 }
autoOpen: false
resizable: false
width: $window.innerWidth * 0.4;
modal: true
open: (event, ui) ->
$('.ui-widget-overlay').bind 'click', ->
$(this).siblings('.ui-dialog').find('.ui-dialog-content').dialog('close')
# Link opening of dialog to click event on element
element.bind 'click', (e) ->
if CurrentShop.shop.id
template.dialog('open')
else
alert('Please select a shop first')

View File

@@ -0,0 +1,3 @@
angular.module("admin.customers").factory "CurrentShop", ->
new class CurrentShop
shop: {}

View File

@@ -0,0 +1,21 @@
angular.module("admin.customers").factory "Customers", ($q, RequestMonitor, CustomerResource, CurrentShop) ->
new class Customers
customers: []
add: (email) ->
params =
enterprise_id: CurrentShop.shop.id
email: email
CustomerResource.create params, (customer) =>
@customers.unshift customer if customer.id
remove: (customer) ->
params = id: customer.id
CustomerResource.destroy params, =>
i = @customers.indexOf customer
@customers.splice i, 1 unless i < 0
index: (params) ->
request = CustomerResource.index(params, (data) => @customers = data)
RequestMonitor.load(request.$promise)
request.$promise

View File

@@ -1,9 +0,0 @@
angular.module("admin.customers").factory 'TagsResource', ($resource) ->
$resource('/admin/tags.json', {}, {
'index':
method: 'GET'
isArray: true
cache: true
params:
enterprise_id: '@enterprise_id'
})

View File

@@ -0,0 +1,10 @@
angular.module("admin.dropdown").controller "ColumnsDropdownCtrl", ($scope, Columns) ->
$scope.columns = Columns.columns
$scope.toggle = Columns.toggleColumn
$scope.saved = Columns.preferencesSaved
$scope.saving = false
$scope.saveColumnPreferences = (action_name) ->
$scope.saving = true
Columns.savePreferences(action_name).then ->
$scope.saving = false

View File

@@ -0,0 +1,6 @@
angular.module("admin.dropdown").directive 'columnsDropdown', ->
restrict: 'E'
templateUrl: 'admin/columns_dropdown.html'
controller: 'ColumnsDropdownCtrl'
scope:
action: '@'

View File

@@ -5,9 +5,4 @@ angular.module("admin.enterprises").controller 'enterprisesCtrl', ($scope, $q, E
$q.all(requests).then ->
$scope.loaded = true
$scope.columns = Columns.setColumns
name: { name: "Name", visible: true }
producer: { name: "Producer", visible: true }
package: { name: "Package", visible: true }
status: { name: "Status", visible: true }
manage: { name: "Manage", visible: true }
$scope.columns = Columns.columns

View File

@@ -1 +1 @@
angular.module("admin.enterprises", [
angular.module("admin.enterprises", [

View File

@@ -1,4 +1,4 @@
angular.module("admin.indexUtils").directive "ofnSelect2", ($sanitize, $timeout) ->
angular.module("admin.indexUtils").directive "ofnSelect2", ($sanitize, $timeout, $filter) ->
require: 'ngModel'
restrict: 'C'
scope:
@@ -6,15 +6,19 @@ angular.module("admin.indexUtils").directive "ofnSelect2", ($sanitize, $timeout)
minSearch: "@?"
text: "@?"
blank: "=?"
filter: "=?"
link: (scope, element, attrs, ngModel) ->
$timeout ->
scope.text ||= 'name'
scope.filter ||= -> true
scope.data.unshift(scope.blank) if scope.blank? && typeof scope.blank is "object"
item.name = $sanitize(item.name) for item in scope.data
element.select2
minimumResultsForSearch: scope.minSearch || 0
data: { results: scope.data, text: scope.text }
data: ->
filtered = $filter('filter')(scope.data,scope.filter)
{ results: filtered, text: scope.text }
formatSelection: (item) ->
item[scope.text]
formatResult: (item) ->

View File

@@ -0,0 +1,12 @@
angular.module("admin.indexUtils").component 'showMore',
templateUrl: 'admin/show_more.html'
bindings:
data: "="
limit: "="
increment: "="
# For now, this component is not being used.
# Something about binding "data" to a variable on the parent scope that is continually refreshed by
# being assigned within an ng-repeat means that we get $digest iteration errors. Seems to be solved
# by using the new "as" syntax for ng-repeat to assign and alias the outcome of the filters, but this
# has the limitation of not being able to be limited AFTER the assignment has been made, which we need

View File

@@ -1,8 +0,0 @@
angular.module("admin.indexUtils").directive "toggleColumn", (Columns) ->
link: (scope, element, attrs) ->
element.addClass "selected" if scope.column.visible
element.click "click", ->
scope.$apply ->
Columns.toggleColumn(scope.column)
element.toggleClass "selected"

View File

@@ -1 +1 @@
angular.module("admin.indexUtils", ['ngResource', 'ngSanitize', 'templates']).config ($httpProvider) ->
angular.module("admin.indexUtils", ['ngResource', 'ngSanitize', 'templates', 'admin.utils']).config ($httpProvider) ->

View File

@@ -1,13 +1,15 @@
angular.module("admin.indexUtils").factory 'Columns', ($rootScope) ->
angular.module("admin.indexUtils").factory 'Columns', ($rootScope, $http, columns) ->
new class Columns
savedColumns: {}
columns: {}
visibleCount: 0
setColumns: (columns) =>
constructor: ->
@columns = {}
@columns[name] = column for name, column of columns
for column in columns
@columns[column.column_name] = column
@savedColumns[column.column_name] = angular.copy(column)
@calculateVisibleCount()
@columns
toggleColumn: (column) =>
column.visible = !column.visible
@@ -16,3 +18,18 @@ angular.module("admin.indexUtils").factory 'Columns', ($rootScope) ->
calculateVisibleCount: =>
@visibleCount = (column for name, column of @columns when column.visible).length
$rootScope.$broadcast "columnCount:changed", @visibleCount
preferencesSaved: =>
angular.equals(@columns, @savedColumns)
savePreferences: (action_name) =>
$http
method: "PUT"
url: "/admin/column_preferences/bulk_update"
data:
action_name: action_name
column_preferences: (preference for column_name, preference of @columns)
.success (data) =>
for column in data
angular.extend(@columns[column.column_name], column)
angular.extend(@savedColumns[column.column_name], column)

View File

@@ -1,10 +1,12 @@
angular.module("admin.indexUtils").factory "pendingChanges", (resources) ->
angular.module("admin.indexUtils").factory "pendingChanges", ($q, resources, StatusMessage) ->
new class pendingChanges
pendingChanges: {}
errors: []
add: (id, attr, change) =>
@pendingChanges["#{id}"] = {} unless @pendingChanges.hasOwnProperty("#{id}")
@pendingChanges["#{id}"]["#{attr}"] = change
StatusMessage.display('notice', "You have made #{@changeCount(@pendingChanges)} unsaved changes")
removeAll: =>
@pendingChanges = {}
@@ -14,11 +16,19 @@ angular.module("admin.indexUtils").factory "pendingChanges", (resources) ->
delete @pendingChanges["#{id}"]["#{attr}"]
delete @pendingChanges["#{id}"] if @changeCount( @pendingChanges["#{id}"] ) < 1
submitAll: =>
submitAll: (form=null) =>
all = []
@errors = []
StatusMessage.display('progress', "Saving...")
for id, objectChanges of @pendingChanges
for attrName, change of objectChanges
all.push @submit(change)
$q.all(all).then =>
if @errors.length == 0
StatusMessage.display('success', "All changes saved successfully")
form.$setPristine() if form?
else
StatusMessage.display('failure', "Oh no! I was unable to save your changes")
all
submit: (change) ->
@@ -26,7 +36,8 @@ angular.module("admin.indexUtils").factory "pendingChanges", (resources) ->
@remove change.object.id, change.attr
change.scope.reset( data["#{change.attr}"] )
change.scope.success()
, (error) ->
, (error) =>
@errors.push error
change.scope.error()
changeCount: (objectChanges) ->

View File

@@ -5,27 +5,14 @@ angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout,
$scope.confirmDelete = true
$scope.startDate = formatDate daysFromToday -7
$scope.endDate = formatDate daysFromToday 1
$scope.bulkActions = [ { name: t("bom_actions_delete"), callback: 'deleteLineItems' } ]
$scope.bulkActions = [ { name: t("admin.orders.bulk_management.actions_delete"), callback: 'deleteLineItems' } ]
$scope.selectedUnitsProduct = {}
$scope.selectedUnitsVariant = {}
$scope.sharedResource = false
$scope.columns = Columns.setColumns
order_no: { name: t("bom_no"), visible: false }
full_name: { name: t("name"), visible: true }
email: { name: t("email"), visible: false }
phone: { name: t("phone"), visible: false }
order_date: { name: t("bom_date"), visible: true }
producer: { name: t("producer"), visible: true }
order_cycle: { name: t("bom_cycle"), visible: false }
hub: { name: t("bom_hub"), visible: false }
variant: { name: t("bom_variant"), visible: true }
quantity: { name: t("bom_quantity"), visible: true }
max: { name: t("bom_max"), visible: true }
final_weight_volume: { name: t("bom_final_weigth_volume"), visible: false }
price: { name: t("price"), visible: false }
$scope.columns = Columns.columns
$scope.confirmRefresh = ->
LineItems.allSaved() || confirm(t "unsaved_changes_warning")
LineItems.allSaved() || confirm(t("unsaved_changes_warning"))
$scope.resetSelectFilters = ->
$scope.distributorFilter = blankOption().id

View File

@@ -12,7 +12,7 @@ angular.module('admin.orderCycles')
$scope.StatusMessage = StatusMessage
$scope.loaded = ->
Enterprise.loaded && EnterpriseFee.loaded
Enterprise.loaded && EnterpriseFee.loaded && OrderCycle.loaded
$scope.suppliedVariants = (enterprise_id) ->
Enterprise.suppliedVariants(enterprise_id)
@@ -79,5 +79,6 @@ angular.module('admin.orderCycles')
$scope.removeDistributionOfVariant = (variant_id) ->
OrderCycle.removeDistributionOfVariant(variant_id)
$scope.submit = (destination) ->
$scope.submit = ($event, destination) ->
$event.preventDefault()
OrderCycle.create(destination)

View File

@@ -1,5 +1,5 @@
angular.module('admin.orderCycles')
.controller 'AdminEditOrderCycleCtrl', ($scope, $filter, $location, OrderCycle, Enterprise, EnterpriseFee, StatusMessage) ->
.controller 'AdminEditOrderCycleCtrl', ($scope, $filter, $location, $window, 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
@@ -12,6 +12,9 @@ angular.module('admin.orderCycles')
$scope.StatusMessage = StatusMessage
$scope.$watch 'order_cycle_form.$dirty', (newValue) ->
StatusMessage.display 'notice', 'You have unsaved changes' if newValue
$scope.loaded = ->
Enterprise.loaded && EnterpriseFee.loaded && OrderCycle.loaded
@@ -60,6 +63,7 @@ angular.module('admin.orderCycles')
$scope.removeExchange = ($event, exchange) ->
$event.preventDefault()
OrderCycle.removeExchange(exchange)
$scope.order_cycle_form.$dirty = true
$scope.addCoordinatorFee = ($event) ->
$event.preventDefault()
@@ -81,4 +85,13 @@ angular.module('admin.orderCycles')
OrderCycle.removeDistributionOfVariant(variant_id)
$scope.submit = (destination) ->
OrderCycle.update(destination)
$event.preventDefault()
StatusMessage.display 'progress', "Saving..."
$scope.submit = ($event, destination) ->
$event.preventDefault()
StatusMessage.display 'progress', "Saving..."
OrderCycle.update(destination, $scope.order_cycle_form)
$scope.cancel = (destination) ->
$window.location = destination

View File

@@ -20,7 +20,7 @@ angular.module('admin.orderCycles').controller "AdminSimpleCreateOrderCycleCtrl"
OrderCycle.order_cycle.coordinator_id = enterprise.id
$scope.loaded = ->
Enterprise.loaded && EnterpriseFee.loaded
Enterprise.loaded && EnterpriseFee.loaded && OrderCycle.loaded
$scope.removeDistributionOfVariant = angular.noop
@@ -41,6 +41,7 @@ angular.module('admin.orderCycles').controller "AdminSimpleCreateOrderCycleCtrl"
$scope.enterpriseFeesForEnterprise = (enterprise_id) ->
EnterpriseFee.forEnterprise(parseInt(enterprise_id))
$scope.submit = (destination) ->
$scope.submit = ($event, destination) ->
$event.preventDefault()
OrderCycle.mirrorIncomingToOutgoingProducts()
OrderCycle.create(destination)

View File

@@ -9,6 +9,9 @@ angular.module('admin.orderCycles').controller "AdminSimpleEditOrderCycleCtrl",
$scope.order_cycle = OrderCycle.load $scope.orderCycleId(), (order_cycle) =>
$scope.init()
$scope.$watch 'order_cycle_form.$dirty', (newValue) ->
StatusMessage.display 'notice', 'You have unsaved changes' if newValue
$scope.loaded = ->
Enterprise.loaded && EnterpriseFee.loaded && OrderCycle.loaded
@@ -34,6 +37,8 @@ angular.module('admin.orderCycles').controller "AdminSimpleEditOrderCycleCtrl",
$event.preventDefault()
OrderCycle.removeCoordinatorFee(index)
$scope.submit = (destination) ->
$scope.submit = ($event, destination) ->
$event.preventDefault()
StatusMessage.display 'progress', "Saving..."
OrderCycle.mirrorIncomingToOutgoingProducts()
OrderCycle.update(destination)
OrderCycle.update(destination, $scope.order_cycle_form)

View File

@@ -154,11 +154,12 @@ angular.module('admin.orderCycles').factory 'OrderCycle', ($resource, $window, S
else
console.log('Failed to create order cycle')
update: (destination) ->
update: (destination, form) ->
return unless @confirmNoDistributors()
oc = new OrderCycleResource({order_cycle: this.dataForSubmit()})
oc.$update {order_cycle_id: this.order_cycle.id, reloading: (if destination? then 1 else 0)}, (data) =>
if data['success']
form.$setPristine() if form
if destination?
$window.location = destination
else

View File

@@ -2,13 +2,11 @@ angular.module("admin.orders").controller "ordersCtrl", ($scope, $compile, $attr
$scope.$compile = $compile
$scope.shops = shops
$scope.orderCycles = orderCycles
for oc in $scope.orderCycles
oc.name_and_status = "#{oc.name} (#{oc.status})"
$scope.distributor_id = $attrs.ofnDistributorId
$scope.order_cycle_id = $attrs.ofnOrderCycleId
$scope.distributor_id = parseInt($attrs.ofnDistributorId)
$scope.order_cycle_id = parseInt($attrs.ofnOrderCycleId)
$scope.validOrderCycle = (oc, index, array) ->
$scope.validOrderCycle = (oc) ->
$scope.orderCycleHasDistributor oc, parseInt($scope.distributor_id)
$scope.distributorHasOrderCycles = (distributor) ->
@@ -20,3 +18,9 @@ angular.module("admin.orders").controller "ordersCtrl", ($scope, $compile, $attr
$scope.distributionChosen = ->
$scope.distributor_id && $scope.order_cycle_id
for oc in $scope.orderCycles
oc.name_and_status = "#{oc.name} (#{oc.status})"
for shop in $scope.shops
shop.disabled = !$scope.distributorHasOrderCycles(shop)

View File

@@ -1 +1 @@
angular.module("admin.orders", ['ngResource'])
angular.module("admin.orders", ['admin.indexUtils', 'ngResource'])

View File

@@ -1,2 +1,10 @@
angular.module("admin.shippingMethods").controller "shippingMethodCtrl", ($scope, shippingMethod) ->
angular.module("admin.shippingMethods").controller "shippingMethodCtrl", ($scope, shippingMethod, TagRuleResource, $q) ->
$scope.shippingMethod = shippingMethod
$scope.findTags = (query) ->
defer = $q.defer()
TagRuleResource.mapByTag (data) =>
filtered = data.filter (tag) ->
tag.text.toLowerCase().indexOf(query.toLowerCase()) != -1
defer.resolve filtered
defer.promise

View File

@@ -1 +1 @@
angular.module("admin.shippingMethods", ["ngTagsInput", 'admin.utils', 'templates'])
angular.module("admin.shippingMethods", ["ngTagsInput", 'admin.utils', 'admin.tagRules', 'templates'])

View File

@@ -0,0 +1,10 @@
angular.module("admin.tagRules").factory 'TagRuleResource', ($resource) ->
$resource('/admin/tag_rules/:action.json', {}, {
'mapByTag':
method: 'GET'
isArray: true
cache: true
params:
action: 'map_by_tag'
enterprise_id: '@enterprise_id'
})

View File

@@ -1 +1 @@
angular.module("admin.tagRules", ['ngTagsInput'])
angular.module("admin.tagRules", ['ngResource', 'ngTagsInput'])

View File

@@ -1,8 +1,9 @@
angular.module("admin.utils").directive "saveBar", (StatusMessage) ->
restrict: "E"
transclude: true
scope:
save: "&"
form: "="
dirty: "="
persist: "=?"
templateUrl: "admin/save_bar.html"
link: (scope, element, attrs) ->
scope.StatusMessage = StatusMessage

View File

@@ -19,19 +19,10 @@ angular.module("admin.variantOverrides").controller "AdminVariantOverridesCtrl",
hidden: { name: "Hidden Products", visible: false }
new: { name: "New Products", visible: false }
$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 }
visibility: { name: "Hide", visible: false }
$scope.bulkActions = [ name: "Reset Stock Levels To Defaults", callback: 'resetStock' ]
$scope.columns = Columns.columns
$scope.resetSelectFilters = ->
$scope.producerFilter = 0
$scope.query = ''

View File

@@ -1 +1 @@
angular.module("admin.variantOverrides", ["pasvaz.bindonce", "admin.indexUtils", "admin.utils", "admin.dropdown", "admin.inventoryItems"])
angular.module("admin.variantOverrides", ["admin.indexUtils", "admin.utils", "admin.dropdown", "admin.inventoryItems"])

View File

@@ -11,8 +11,7 @@
#= require lodash.underscore.js
#= require angular-scroll.min.js
#= require angular-google-maps.min.js
#= require ../shared/mm-foundation-tpls-0.2.2.min.js
#= require ../shared/bindonce.min.js
#= require ../shared/mm-foundation-tpls-0.8.0.min.js
#= require ../shared/ng-infinite-scroll.min.js
#= require ../shared/angular-local-storage.js
#= require ../shared/angular-slideables.js

View File

@@ -1,5 +1,6 @@
Darkswarm.controller "SignupCtrl", ($scope, $http, $window, $location, Redirections, AuthenticationService) ->
$scope.path = "/signup"
$scope.errors =
email: null
password: null

View File

@@ -3,5 +3,10 @@ Darkswarm.controller "AuthenticationCtrl", ($scope, AuthenticationService, Spree
$scope.toggle = AuthenticationService.toggle
$scope.spree_user = SpreeUser.spree_user
$scope.active = AuthenticationService.active
$scope.isActive = AuthenticationService.isActive
$scope.select = AuthenticationService.select
$scope.tabs =
login: { active: $scope.isActive('/login') }
signup: { active: $scope.isActive('/signup') }
forgot: { active: $scope.isActive('/forgot') }

View File

@@ -1,4 +1,4 @@
Darkswarm.controller "GroupPageCtrl", ($scope, group_enterprises, Enterprises, MapConfiguration, OfnMap, visibleFilter) ->
Darkswarm.controller "GroupPageCtrl", ($scope, group_enterprises, Enterprises, MapConfiguration, OfnMap, visibleFilter, Navigation) ->
$scope.Enterprises = Enterprises
all_enterprises_by_id = Enterprises.enterprises_by_id
@@ -19,4 +19,3 @@ Darkswarm.controller "GroupPageCtrl", ($scope, group_enterprises, Enterprises, M
$scope.map = angular.copy MapConfiguration.options
$scope.mapMarkers = OfnMap.enterprise_markers visible_enterprises

View File

@@ -0,0 +1,8 @@
Darkswarm.controller "GroupTabsCtrl", ($scope, $controller, Navigation) ->
angular.extend this, $controller('TabsCtrl', {$scope: $scope})
$scope.tabs =
map: { active: Navigation.isActive('/map') }
about: { active: Navigation.isActive('/about') }
producers: { active: Navigation.isActive('/producers') }
hubs: { active: Navigation.isActive('/hubs') }

View File

@@ -1,3 +1,3 @@
Darkswarm.controller "GroupsCtrl", ($scope, Groups, $anchorScroll, $rootScope) ->
Darkswarm.controller "GroupsCtrl", ($scope, Groups) ->
$scope.Groups = Groups
$scope.order = 'position'

View File

@@ -1,6 +1,7 @@
Darkswarm.controller "ProductNodeCtrl", ($scope, $modal) ->
Darkswarm.controller "ProductNodeCtrl", ($scope, $modal, FilterSelectorsService) ->
$scope.enterprise = $scope.product.supplier # For the modal, so it's consistent
$scope.triggerProductModal = ->
$scope.productTaxonSelectors = FilterSelectorsService.createSelectors()
$scope.productPropertySelectors = FilterSelectorsService.createSelectors()
$modal.open(templateUrl: "product_modal.html", scope: $scope)

View File

@@ -0,0 +1,8 @@
Darkswarm.controller "ShoppingTabsCtrl", ($scope, $controller, Navigation) ->
angular.extend this, $controller('TabsCtrl', {$scope: $scope})
$scope.tabs =
about: { active: Navigation.isActive('/about') }
producers: { active: Navigation.isActive('/producers') }
contact: { active: Navigation.isActive('/contact') }
groups: { active: Navigation.isActive('/groups') }

View File

@@ -1,15 +1,6 @@
Darkswarm.controller "TabsCtrl", ($scope, $rootScope, $location) ->
# Return active if supplied path matches url hash path.
$scope.active = (path)->
$location.hash() == path
Darkswarm.controller "TabsCtrl", ($scope, Navigation) ->
$scope.isActive = Navigation.isActive
# Select tab by setting the url hash path.
$scope.select = (path)->
$location.hash path
# Toggle tab selected status by setting the url hash path.
$scope.toggle = (path)->
if $scope.active(path)
$location.hash ""
else
$location.hash path
$scope.select = (path) ->
Navigation.navigate path

View File

@@ -1,7 +1,6 @@
window.Darkswarm = angular.module("Darkswarm", ["ngResource",
'mm.foundation',
'angularLocalStorage',
'pasvaz.bindonce',
'infinite-scroll',
'angular-flash.service',
'templates',

View File

@@ -22,7 +22,7 @@ Darkswarm.factory "AuthenticationService", (Navigation, $modal, $location, Redir
@selectedPath = path
Navigation.navigate @selectedPath
active: Navigation.active
isActive: Navigation.isActive
close: ->
if location.pathname in ["/", "/checkout"]

View File

@@ -2,6 +2,7 @@ Darkswarm.factory "EnterpriseRegistrationService", ($http, RegistrationService,
new class EnterpriseRegistrationService
enterprise:
user_ids: [CurrentUser.id]
email: CurrentUser.email
email_address: CurrentUser.email
address: {}
country: availableCountries[0]

View File

@@ -1,9 +1,9 @@
Darkswarm.factory 'Navigation', ($location, $window) ->
new class Navigation
path: null
path: null
active: (path)->
$location.path() == path
isActive: (path)->
$location.path() == path
navigate: (path)=>
@path = path

View File

@@ -1 +0,0 @@
!function(){"use strict";var e=angular.module("pasvaz.bindonce",[]);e.directive("bindonce",function(){var e=function(e){if(e&&0!==e.length){var t=angular.lowercase(""+e);e=!("f"===t||"0"===t||"false"===t||"no"===t||"n"===t||"[]"===t)}else e=!1;return e},t=parseInt((/msie (\d+)/.exec(angular.lowercase(navigator.userAgent))||[])[1],10);isNaN(t)&&(t=parseInt((/trident\/.*; rv:(\d+)/.exec(angular.lowercase(navigator.userAgent))||[])[1],10));var r={restrict:"AM",controller:["$scope","$element","$attrs","$interpolate",function(r,a,i,n){var c=function(t,r,a){var i="show"===r?"":"none",n="hide"===r?"":"none";t.css("display",e(a)?i:n)},o=function(e,t){if(angular.isObject(t)&&!angular.isArray(t)){var r=[];angular.forEach(t,function(e,t){e&&r.push(t)}),t=r}t&&e.addClass(angular.isArray(t)?t.join(" "):t)},s=function(e,t){e.transclude(t,function(t){var r=e.element.parent(),a=e.element&&e.element[e.element.length-1],i=r&&r[0]||a&&a.parentNode,n=a&&a.nextSibling||null;angular.forEach(t,function(e){i.insertBefore(e,n)})})},l={watcherRemover:void 0,binders:[],group:i.boName,element:a,ran:!1,addBinder:function(e){this.binders.push(e),this.ran&&this.runBinders()},setupWatcher:function(e){var t=this;this.watcherRemover=r.$watch(e,function(e){void 0!==e&&(t.removeWatcher(),t.checkBindonce(e))},!0)},checkBindonce:function(e){var t=this,r=e.$promise?e.$promise.then:e.then;"function"==typeof r?r(function(){t.runBinders()}):t.runBinders()},removeWatcher:function(){void 0!==this.watcherRemover&&(this.watcherRemover(),this.watcherRemover=void 0)},runBinders:function(){for(;this.binders.length>0;){var r=this.binders.shift();if(!this.group||this.group==r.group){var a=r.scope.$eval(r.interpolate?n(r.value):r.value);switch(r.attr){case"boIf":e(a)&&s(r,r.scope.$new());break;case"boSwitch":var i,l=r.controller[0];(i=l.cases["!"+a]||l.cases["?"])&&(r.scope.$eval(r.attrs.change),angular.forEach(i,function(e){s(e,r.scope.$new())}));break;case"boSwitchWhen":var u=r.controller[0];u.cases["!"+r.attrs.boSwitchWhen]=u.cases["!"+r.attrs.boSwitchWhen]||[],u.cases["!"+r.attrs.boSwitchWhen].push({transclude:r.transclude,element:r.element});break;case"boSwitchDefault":var u=r.controller[0];u.cases["?"]=u.cases["?"]||[],u.cases["?"].push({transclude:r.transclude,element:r.element});break;case"hide":case"show":c(r.element,r.attr,a);break;case"class":o(r.element,a);break;case"text":r.element.text(a);break;case"html":r.element.html(a);break;case"style":r.element.css(a);break;case"src":r.element.attr(r.attr,a),t&&r.element.prop("src",a);break;case"attr":angular.forEach(r.attrs,function(e,t){var a,i;t.match(/^boAttr./)&&r.attrs[t]&&(a=t.replace(/^boAttr/,"").replace(/([a-z])([A-Z])/g,"$1-$2").toLowerCase(),i=r.scope.$eval(r.attrs[t]),r.element.attr(a,i))});break;case"href":case"alt":case"title":case"id":case"value":r.element.attr(r.attr,a)}}}this.ran=!0}};return l}],link:function(e,t,r,a){var i=r.bindonce&&e.$eval(r.bindonce);void 0!==i?a.checkBindonce(i):(a.setupWatcher(r.bindonce),t.bind("$destroy",a.removeWatcher))}};return r}),angular.forEach([{directiveName:"boShow",attribute:"show"},{directiveName:"boHide",attribute:"hide"},{directiveName:"boClass",attribute:"class"},{directiveName:"boText",attribute:"text"},{directiveName:"boBind",attribute:"text"},{directiveName:"boHtml",attribute:"html"},{directiveName:"boSrcI",attribute:"src",interpolate:!0},{directiveName:"boSrc",attribute:"src"},{directiveName:"boHrefI",attribute:"href",interpolate:!0},{directiveName:"boHref",attribute:"href"},{directiveName:"boAlt",attribute:"alt"},{directiveName:"boTitle",attribute:"title"},{directiveName:"boId",attribute:"id"},{directiveName:"boStyle",attribute:"style"},{directiveName:"boValue",attribute:"value"},{directiveName:"boAttr",attribute:"attr"},{directiveName:"boIf",transclude:"element",terminal:!0,priority:1e3},{directiveName:"boSwitch",require:"boSwitch",controller:function(){this.cases={}}},{directiveName:"boSwitchWhen",transclude:"element",priority:800,require:"^boSwitch"},{directiveName:"boSwitchDefault",transclude:"element",priority:800,require:"^boSwitch"}],function(t){var r=200;return e.directive(t.directiveName,function(){var e={priority:t.priority||r,transclude:t.transclude||!1,terminal:t.terminal||!1,require:["^bindonce"].concat(t.require||[]),controller:t.controller,compile:function(e,r,a){return function(e,r,i,n){var c=n[0],o=i.boParent;if(o&&c.group!==o){var s=c.element.parent();c=void 0;for(var l;9!==s[0].nodeType&&s.length;){if((l=s.data("$bindonceController"))&&l.group===o){c=l;break}s=s.parent()}if(!c)throw new Error("No bindonce controller: "+o)}c.addBinder({element:r,attr:t.attribute||t.directiveName,attrs:i,value:i[t.directiveName],interpolate:t.interpolate,group:o,transclude:a,controller:n.slice(1),scope:e})}}};return e})})}();

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,11 @@
.ofn-drop-down.right#columns-dropdown{ ng: { controller: 'ColumnsDropdownCtrl' } }
%span{ :class => 'icon-reorder' }= "&nbsp; #{t('admin.columns')}".html_safe
%span{ 'ng-class' => "expanded && 'icon-caret-up' || !expanded && 'icon-caret-down'" }
%div.menu{ 'ng-show' => "expanded" }
%div.menu_item{ ng: { repeat: "column in columns", click: "toggle(column)", class: "{selected: column.visible}" } }
%span.check
%span.name {{column.name }}
%hr
%div.menu_item.text-center
%input.fullwidth.orange{ type: "button", ng: { value: "saved() ? 'Saved': 'Saving'", show: "saved() || saving", disabled: "saved()" } }
%input.fullwidth.red{ type: "button", value: 'Save As Default', ng: { show: "!saved() && !saving", click: "saveColumnPreferences(action)"} }

View File

@@ -0,0 +1,15 @@
#new-customer-dialog
.text-normal.margin-bottom-30.text-center
= t('admin.customers.index.add_a_new_customer_for', shop_name: "{{ CurrentShop.shop.name }}:")
%form{ name: 'new_customer_form', novalidate: true }
.text-center.margin-bottom-30
%input.fullwidth{ type: 'email', name: 'email', required: true, placeholder: t('admin.customers.index.customer_placeholder'), ng: { model: "email" } }
%div{ ng: { show: "email == submitted" } }
.error{ ng: { show: "(new_customer_form.email.$error.email || new_customer_form.email.$error.required)" } }
= t('admin.customers.index.valid_email_error')
.error{ ng: { repeat: "error in errors", bind: "error" } }
.text-center
%input.button.red.icon-plus{ type: 'submit', value: t('admin.customers.index.add_customer'), ng: { click: 'addCustomer(new_customer_form.email.$valid)' } }

View File

@@ -17,13 +17,13 @@
%td.severity
%i.icon-warning-sign.issue
%td.description
%span{ bo: { bind: "issue.description" } }
%span{ ng: { bind: "::issue.description" } }
%td.resolve
%div{ ng: { bind: { html: "issue.link" } } }
%tr{ ng: { repeat: "warning in warnings"} }
%td.severity
%i.icon-warning-sign.warning
%td.description
%span{ bo: { bind: "warning.description" } }
%span{ ng: { bind: "::warning.description" } }
%td.resolve
%div{ ng: { bind: { html: "warning.link" } } }

View File

@@ -1,6 +1,6 @@
#save-bar.animate-show{ ng: { show: 'form.$dirty || StatusMessage.active()' } }
.twelve.columns.alpha
%h5#status-message{ ng: { style: 'StatusMessage.statusMessage.style' } }
{{ StatusMessage.statusMessage.text || "&nbsp;" }}
.four.columns.omega.text-right
%input.red{type: "button", value: "Save Changes", ng: { disabled: '!form.$dirty', click: "save()" } }
#save-bar.animate-show{ ng: { show: 'dirty || persist || StatusMessage.active()' } }
.container
.eight.columns.alpha
%h5#status-message{ ng: { style: 'StatusMessage.statusMessage.style' } }
{{ StatusMessage.statusMessage.text || "&nbsp;" }}
.eight.columns.omega.text-right{ ng: { transclude: true } }

View File

@@ -0,0 +1,4 @@
%div{ ng: { show: "data.length > limit" } }
%input{ type: 'button', value: 'Show More', ng: { click: 'limit = limit + increment' } }
or
%input{ type: 'button', value: "Show All ({{ data.length - limit }} More)", ng: { click: 'limit = data.length' } }

View File

@@ -1,4 +1,4 @@
%ul{ bindonce: true }
%ul
%active-selector{ ng: { repeat: "selector in allSelectors", show: "ifDefined(selector.fits, true)" } }
%render-svg{path: "{{selector.object.icon}}", ng: { if: "selector.object.icon"} }
%span{"bo-text" => "selector.object.name"}
%span{"ng-bind" => "::selector.object.name"}

View File

@@ -1,10 +1,5 @@
%tab#forgot{"ng-controller" => "ForgotCtrl",
heading: "{{'forgot_password' | t}}",
active: "active(path)",
select: "select(path)"}
%form{"ng-submit" => "submit()"}
%tab#forgot{ heading: "{{'forgot_password' | t}}", active: "tabs.forgot.active", select: "select(path)"}
%form{ ng: { controller: "ForgotCtrl", submit: "submit()" } }
.row
.large-12.columns
.alert-box.success.radius{"ng-show" => "sent"}
@@ -13,18 +8,18 @@
%div{"ng-show" => "!sent"}
.alert-box.alert{"ng-show" => "errors != null"}
{{ errors }}
.row
.large-12.columns
%label{for: "email"} {{'signup_email' | t}}
%input.title.input-text{name: "email",
%input.title.input-text{name: "email",
type: "email",
id: "email",
tabindex: 1,
"ng-model" => "spree_user.email"}
.row
.large-12.columns
%input.button.primary{name: "commit",
tabindex: "3",
type: "submit",
%input.button.primary{name: "commit",
tabindex: "3",
type: "submit",
value: "{{'reset_password' | t}}"}

View File

@@ -1,8 +1,5 @@
%tab#login-content{"ng-controller" => "LoginCtrl",
heading: "{{'label_login' | t}}",
active: "active(path)",
select: "select(path)"}
%form{"ng-submit" => "submit()"}
%tab#login-content{ heading: "{{'label_login' | t}}", active: "tabs.login.active", select: "select(path)"}
%form{ ng: { controller: "LoginCtrl", submit: "submit()" } }
.row
.large-12.columns
.alert-box.alert{"ng-show" => "errors != null"}
@@ -10,7 +7,7 @@
.row
.large-12.columns
%label{for: "email"} {{'email' | t}}
%input.title.input-text{name: "email",
%input.title.input-text{name: "email",
type: "email",
id: "email",
tabindex: 1,
@@ -18,7 +15,7 @@
.row
.large-12.columns
%label{for: "password"} {{'password' | t}}
%input.title.input-text{name: "password",
%input.title.input-text{name: "password",
type: "password",
id: "password",
autocomplete: "off",
@@ -26,15 +23,15 @@
"ng-model" => "spree_user.password"}
.row
.large-12.columns
%input{name: "remember_me",
type: "checkbox",
id: "remember_me",
%input{name: "remember_me",
type: "checkbox",
id: "remember_me",
value: "1",
"ng-model" => "spree_user.remember_me"}
%label{for: "remember_me"} {{'remember_me' | t}}
.row
.large-12.columns
%input.button.primary{name: "commit",
tabindex: "3",
type: "submit",
%input.button.primary{name: "commit",
tabindex: "3",
type: "submit",
value: "{{'label_login' | t}}"}

View File

@@ -1,11 +1,11 @@
%div.contact-container{bindonce: true}
%div.modal-centered{"bo-if" => "enterprise.email_address || enterprise.website || enterprise.phone"}
%div.contact-container
%div.modal-centered{"ng-if" => "::enterprise.email_address || enterprise.website || enterprise.phone"}
%p.modal-header {{'contact' | t}}
%p{"bo-if" => "enterprise.phone", "bo-text" => "enterprise.phone"}
%p{"ng-if" => "::enterprise.phone", "ng-bind" => "::enterprise.phone"}
%p.word-wrap{"ng-if" => "enterprise.email_address"}
%a{"bo-href" => "enterprise.email_address | stripUrl", target: "_blank", mailto: true}
%span.email{"bo-bind" => "enterprise.email_address | stripUrl"}
%p.word-wrap{"ng-if" => "::enterprise.email_address"}
%a{"ng-href" => "{{::enterprise.email_address | stripUrl}}", target: "_blank", mailto: true}
%span.email{"ng-bind" => "::enterprise.email_address | stripUrl"}
%p.word-wrap{"ng-if" => "enterprise.website"}
%a{"bo-href-i" => "http://{{enterprise.website | stripUrl}}", target: "_blank", "bo-bind" => "enterprise.website | stripUrl"}
%a{"ng-href" => "http://{{::enterprise.website | stripUrl}}", target: "_blank", "ng-bind" => "::enterprise.website | stripUrl"}

View File

@@ -1,4 +1,4 @@
.row{bindonce: true}
.row
.small-12.large-8.columns
/ TODO: Rob add logic for taxons and properties too:
/ %div{"ng-if" => "enterprise.long_description.length > 0 || enterprise.logo"}
@@ -22,8 +22,8 @@
-# / TODO: Rob - need popover, use will's directive or this? http://pineconellc.github.io/angular-foundation/
-#
.about-container.pad-top
%img.enterprise-logo{"bo-src" => "enterprise.logo", "bo-if" => "enterprise.logo"}
%p.text-small{"ng-bind-html" => "enterprise.long_description"}
%img.enterprise-logo{"ng-src" => "{{::enterprise.logo}}", "ng-if" => "::enterprise.logo"}
%p.text-small{"ng-bind-html" => "::enterprise.long_description"}
.small-12.large-4.columns
%ng-include{src: "'partials/contact.html'"}
%ng-include{src: "'partials/follow.html'"}

View File

@@ -1,13 +1,13 @@
.highlight{bindonce: true, "ng-class" => "{'is_distributor' : enterprise.is_distributor}"}
.highlight{"ng-class" => "::{'is_distributor' : enterprise.is_distributor}"}
.highlight-top.row
.small-12.medium-7.large-8.columns
%h3{"ng-if" => "enterprise.is_distributor"}
%a{"bo-href" => "enterprise.path", "ofn-change-hub" => "enterprise"}
%i{"ng-class" => "enterprise.icon_font"}
%span{"bo-text" => "enterprise.name"}
%h3{"ng-if" => "!enterprise.is_distributor", "ng-class" => "{'is_producer' : enterprise.is_primary_producer}"}
%i{"ng-class" => "enterprise.icon_font"}
%span{"bo-text" => "enterprise.name"}
%h3{"ng-if" => "::enterprise.is_distributor"}
%a{"ng-href" => "{{::enterprise.path}}", "ofn-change-hub" => "enterprise"}
%i{"ng-class" => "::enterprise.icon_font"}
%span{"ng-bind" => "::enterprise.name"}
%h3{"ng-if" => "::!enterprise.is_distributor", "ng-class" => "::{'is_producer' : enterprise.is_primary_producer}"}
%i{"ng-class" => "::enterprise.icon_font"}
%span{"ng-bind" => "::enterprise.name"}
.small-12.medium-5.large-4.columns.text-right.small-only-text-left
%p{"bo-bind" => "[enterprise.address.city, enterprise.address.state_name] | printArray"}
%img.hero-img{"bo-src" => "enterprise.promo_image"}
%p{"ng-bind" => "::[enterprise.address.city, enterprise.address.state_name] | printArray"}
%img.hero-img{"ng-src" => "{{::enterprise.promo_image}}"}

View File

@@ -1,19 +1,18 @@
%div.modal-centered{bindonce: true, "bo-if" => "enterprise.twitter || enterprise.facebook || enterprise.linkedin || enterprise.instagram"}
%div.modal-centered{ "ng-if" => "::enterprise.twitter || enterprise.facebook || enterprise.linkedin || enterprise.instagram"}
%p.modal-header {{'follow' | t}}
.follow-icons
%span{"bo-if" => "enterprise.twitter"}
%a{"bo-href-i" => "http://twitter.com/{{enterprise.twitter}}", target: "_blank"}
%span{"ng-if" => "::enterprise.twitter"}
%a{"ng-href" => "http://twitter.com/{{::enterprise.twitter}}", target: "_blank"}
%i.ofn-i_041-twitter
%span{"bo-if" => "enterprise.facebook"}
%a{"bo-href-i" => "http://{{enterprise.facebook | stripUrl}}", target: "_blank"}
%i.ofn-i_044-facebook
%span{"bo-if" => "enterprise.linkedin"}
%a{"bo-href-i" => "http://{{enterprise.linkedin | stripUrl}}", target: "_blank"}
%i.ofn-i_042-linkedin
%span{"bo-if" => "enterprise.instagram"}
%a{"bo-href-i" => "http://instagram.com/{{enterprise.instagram}}", target: "_blank"}
%i.ofn-i_043-instagram
%span{"ng-if" => "::enterprise.facebook"}
%a{"ng-href" => "http://{{::enterprise.facebook | stripUrl}}", target: "_blank"}
%i.ofn-i_044-facebook
%span{"ng-if" => "::enterprise.linkedin"}
%a{"ng-href" => "http://{{::enterprise.linkedin | stripUrl}}", target: "_blank"}
%i.ofn-i_042-linkedin
%span{"ng-if" => "::enterprise.instagram"}
%a{"ng-href" => "http://instagram.com/{{::enterprise.instagram}}", target: "_blank"}
%i.ofn-i_043-instagram

View File

@@ -1,24 +1,24 @@
.row.pad-top{bindonce: true, ng: { if: 'enterprise.is_distributor' } }
.row.pad-top{ng: { if: 'enterprise.is_distributor' } }
.cta-container.small-12.columns
.row
.small-4.columns
%label{"active-table-hub-link" => "enterprise", change: "{{'change_shop' | t}}", shop: "{{'shop_at' | t}}"}
.small-8.columns.right
%label.right{"bo-if" => "enterprise.pickup || enterprise.delivery"}
%label.right{"ng-if" => "::enterprise.pickup || enterprise.delivery"}
{{'hubs_delivery_options' | t}}:
%span{"bo-if" => "enterprise.pickup"}
%span{"ng-if" => "::enterprise.pickup"}
%i.ofn-i_038-takeaway
{{'hubs_pickup' | t}}
%span{"bo-if" => "enterprise.delivery"}
%span{"ng-if" => "::enterprise.delivery"}
%i.ofn-i_039-delivery
{{'hubs_delivery' | t}}
.row
.columns.small-12
%a.cta-hub{"bo-href" => "enterprise.path",
%a.cta-hub{"ng-href" => "{{::enterprise.path}}",
"ng-class" => "{primary: enterprise.active, secondary: !enterprise.active}",
"ofn-change-hub" => "enterprise"}
%i.ofn-i_033-open-sign{"bo-if" => "enterprise.active"}
%i.ofn-i_032-closed-sign{"bo-if" => "!enterprise.active"}
.hub-name{"bo-text" => "enterprise.name"}
.button-address{"bo-bind" => "[enterprise.address.city, enterprise.address.state_name] | printArray"}
%i.ofn-i_033-open-sign{"ng-if" => "::enterprise.active"}
%i.ofn-i_032-closed-sign{"ng-if" => "::!enterprise.active"}
.hub-name{"ng-bind" => "::enterprise.name"}
.button-address{"ng-bind" => "::[enterprise.address.city, enterprise.address.state_name] | printArray"}
/ %i.ofn-i_007-caret-right

View File

@@ -1,20 +1,20 @@
-# Show places to buy products from this producer, when there are any
-# Do not show this for producer shops selling only their own produce,
-# Since a shopping link will already have been displayed in hub_details.html.haml
.row.active_table_row.pad-top{bindonce: true, "ng-if" => "enterprise.is_primary_producer && enterprise.hubs.length > 0 && !(enterprise.hubs.length == 1 && enterprise.hubs[0] == enterprise)"}
.row.active_table_row.pad-top{ "ng-if" => "enterprise.is_primary_producer && enterprise.hubs.length > 0 && !(enterprise.hubs.length == 1 && enterprise.hubs[0] == enterprise)"}
.columns.small-12
.row
.columns.small-12.fat
%div{"bo-if" => "enterprise.name"}
%label{"bo-html" => "'shop_for_products_html' | t:{enterprise: enterprise.name}"}
%div.show-for-medium-up{"bo-if" => "!enterprise.name"}
%div{"ng-if" => "::enterprise.name"}
%label{"ng-html" => "::'shop_for_products_html' | t:{enterprise: enterprise.name}"}
%div.show-for-medium-up{"ng-if" => "::!enterprise.name"}
&nbsp;
.row.cta-container
.columns.small-12
%a.cta-hub{"ng-repeat" => "hub in enterprise.hubs | filter:{id: '!'+enterprise.id} | orderBy:'-active'",
"bo-href" => "hub.path", "ofn-empties-cart" => "hub",
"bo-class" => "{primary: hub.active, secondary: !hub.active}"}
%i.ofn-i_033-open-sign{"bo-if" => "hub.active"}
%i.ofn-i_032-closed-sign{"bo-if" => "!hub.active"}
.hub-name{"bo-text" => "hub.name"}
.button-address{"bo-bind" => "[hub.address.city, hub.address.state_name] | printArray"}
"ng-href" => "{{::hub.path}}", "ofn-empties-cart" => "hub",
"ng-class" => "::{primary: hub.active, secondary: !hub.active}"}
%i.ofn-i_033-open-sign{"ng-if" => "::hub.active"}
%i.ofn-i_032-closed-sign{"ng-if" => "::!hub.active"}
.hub-name{"ng-bind" => "::hub.name"}
.button-address{"ng-bind" => "::[hub.address.city, hub.address.state_name] | printArray"}

View File

@@ -1,4 +1,4 @@
.small-5.medium-3.large-3.columns.text-right{"bo-if" => "!variant.product.group_buy"}
.small-5.medium-3.large-3.columns.text-right{"ng-if" => "::!variant.product.group_buy"}
%input{type: :number,
integer: true,

View File

@@ -1,4 +1,4 @@
.small-5.medium-3.large-3.columns.text-right{"bo-if" => "variant.product.group_buy"}
.small-5.medium-3.large-3.columns.text-right{"ng-if" => "::variant.product.group_buy"}
%span.bulk-input-container
%span.bulk-input
%input.bulk.first{type: :number,

View File

@@ -1,38 +1,37 @@
.joyride-tip-guide.price_breakdown{bindonce: true, "ng-class" => "{ in: tt_isOpen, fade: tt_animation }"}
.joyride-tip-guide.price_breakdown{"ng-class" => "{ in: tt_isOpen, fade: tt_animation }"}
%span.joyride-nub.right
.joyride-content-wrapper
.collapsed{"ng-show" => "!expanded"}
%price-percentage{percentage: 'variant.basePricePercentage'}
%a{"ng-click" => "expanded = !expanded"}
%span{"bo-text" => "'price_breakdown' | t"}
%a{"ng-click" => "expanded = !expanded"}
%span{"ng-bind" => "::'price_breakdown' | t"}
%i.ofn-i_005-caret-down
.expanded{"ng-show" => "expanded"}
%ul
%li.cost
.right {{ variant.price | localizeCurrency }}
%span{"bo-text" => "'item_cost' | t"}
%li.admin-fee{"bo-if" => "variant.fees.admin"}
%span{"ng-bind" => "::'item_cost' | t"}
%li.admin-fee{"ng-if" => "::variant.fees.admin"}
.right {{ variant.fees.admin | localizeCurrency }}
%span{"bo-text" => "'admin_fee' | t"}
%li.sales-fee{"bo-if" => "variant.fees.sales"}
%span{"ng-bind" => "::'admin_fee' | t"}
%li.sales-fee{"ng-if" => "::variant.fees.sales"}
.right {{ variant.fees.sales | localizeCurrency }}
%span{"bo-text" => "'sales_fee' | t"}
%li.packing-fee{"bo-if" => "variant.fees.packing"}
%span{"ng-bind" => "::'sales_fee' | t"}
%li.packing-fee{"ng-if" => "::variant.fees.packing"}
.right {{ variant.fees.packing | localizeCurrency }}
%span{"bo-text" => "'packing_fee' | t"}
%li.transport-fee{"bo-if" => "variant.fees.transport"}
%span{"ng-bind" => "::'packing_fee' | t"}
%li.transport-fee{"ng-if" => "::variant.fees.transport"}
.right {{ variant.fees.transport | localizeCurrency }}
%span{"bo-text" => "'transport_fee' | t"}
%li.fundraising-fee{"bo-if" => "variant.fees.fundraising"}
%span{"ng-bind" => "::'transport_fee' | t"}
%li.fundraising-fee{"ng-if" => "::variant.fees.fundraising"}
.right {{ variant.fees.fundraising | localizeCurrency }}
%span{"bo-text" => "'fundraising_fee' | t"}
%span{"ng-bind" => "::'fundraising_fee' | t"}
%li.total
%strong
.right = {{ variant.price_with_fees | localizeCurrency }}
&nbsp;
%a{"ng-click" => "expanded = !expanded"}
%span{"bo-text" => "'price_graph' | t"}
%a{"ng-click" => "expanded = !expanded"}
%span{"ng-bind" => "::'price_graph' | t"}
%i.ofn-i_006-caret-up

View File

@@ -1,26 +1,26 @@
.row{bindonce: true}
.row
.columns.small-12.large-6.product-header
%h3{"bo-text" => "product.name"}
%h3{"ng-bind" => "::product.name"}
%span
%em {{'products_from' | t}}
%span{"bo-text" => "enterprise.name"}
%span{"ng-bind" => "::enterprise.name"}
%br
.filter-shopfront.taxon-selectors.inline-block
%filter-selector{ objects: "[product] | taxonsOf" }
%filter-selector{ 'selector-set' => "productTaxonSelectors", objects: "[product] | taxonsOf" }
.filter-shopfront.property-selectors.inline-block
%filter-selector{ objects: "[product] | propertiesWithValuesOf" }
%filter-selector{ 'selector-set' => "productPropertySelectors", objects: "[product] | propertiesWithValuesOf" }
%div{"ng-if" => "product.description"}
%hr
%p.text-small{"bo-text" => "product.description"}
%p.text-small{"ng-bind" => "::product.description"}
%hr
.columns.small-12.large-6
%img.product-img{"bo-src" => "product.largeImage", "bo-if" => "product.largeImage"}
%img.product-img.placeholder{"bo-src" => "'/assets/noimage/large.png'", "bo-if" => "!product.largeImage"}
%img.product-img{"ng-src" => "{{::product.largeImage}}", "ng-if" => "::product.largeImage"}
%img.product-img.placeholder{ src: "/assets/noimage/large.png", "ng-if" => "::!product.largeImage"}
%ng-include{src: "'partials/close.html'"}

View File

@@ -1,19 +1,19 @@
.container#registration-details{bindonce: true}
.container#registration-details
%ng-include{ src: "'registration/steps.html'" }
.row
.small-12.columns
%header
%h2 {{'registration_detail_headline' | t}}
%h5{ bo: { if: "enterprise.type != 'own'" } } {{'registration_detail_enterprise' | t}}
%h5{ bo: { if: "enterprise.type == 'own'" } } {{'registration_detail_producer' | t}}
%h5{ ng: { if: "::enterprise.type != 'own'" } } {{'registration_detail_enterprise' | t}}
%h5{ ng: { if: "::enterprise.type == 'own'" } } {{'registration_detail_producer' | t}}
%form{ name: 'details', novalidate: true, ng: { controller: "RegistrationFormCtrl", submit: "selectIfValid('contact',details)" } }
.row
.small-12.medium-9.large-12.columns.end
.field
%label{ for: 'enterprise_name', bo: { if: "enterprise.type != 'own'" } } {{'registration_detail_name_enterprise' | t}}
%label{ for: 'enterprise_name', bo: { if: "enterprise.type == 'own'" } } {{'registration_detail_name_producer' | t}}
%label{ for: 'enterprise_name', ng: { if: "::enterprise.type != 'own'" } } {{'registration_detail_name_enterprise' | t}}
%label{ for: 'enterprise_name', ng: { if: "::enterprise.type == 'own'" } } {{'registration_detail_name_producer' | t}}
%input.chunky{ id: 'enterprise_name', name: 'name', placeholder: "{{'registration_detail_name_placeholder' | t}}", required: true, ng: { model: 'enterprise.name' } }
%span.error{ ng: { show: "details.name.$error.required && submitted" } }
{{'registration_detail_name_error' | t}}

View File

@@ -10,6 +10,6 @@
.small-12.columns.text-center
%h4{ "ng-bind" => "'registration_finished_activate' | t:{enterprise: enterprise.name}" }
%p{ "ng-bind-html" => "t('registration_finished_activate_instruction_html', {email: enterprise.email})"}
%p{ "ng-bind-html" => "'registration_finished_activate_instruction_html' | t:{email: enterprise.email}"}
%a.button.primary{ type: "button", href: "/" } {{'registration_finished_action' | t}} &gt;

View File

@@ -1,4 +1,4 @@
.container#registration-type{bindonce: true}
.container#registration-type
%ng-include{ src: "'registration/steps.html'" }
@@ -10,7 +10,7 @@
{{'registration_type_question' | t}}
%form{ name: 'type', novalidate: true, ng: { controller: "RegistrationFormCtrl", submit: "create(type)" } }
.row#enterprise-types{ 'data-equalizer' => true, bo: { if: "enterprise.type != 'own'" } }
.row#enterprise-types{ 'data-equalizer' => true, ng: { if: "::enterprise.type != 'own'" } }
.small-12.columns.field
.row
.small-12.medium-6.large-6.columns{ 'data-equalizer-watch' => true }

View File

@@ -2,16 +2,14 @@
.small-12.medium-4.large-4.columns.variant-name
.table-cell
.inline {{ variant.name_to_display }}
.bulk-buy.inline{"bo-if" => "variant.product.group_buy"}
.bulk-buy.inline{"ng-if" => "::variant.product.group_buy"}
%i.ofn-i_056-bulk><
%em><
\ {{'bulk' | t}}
%ng-include{src: "'partials/shop_variant_no_group_buy.html'"}
%ng-include{src: "'partials/shop_variant_with_group_buy.html'"}
.small-3.medium-1.large-1.columns.variant-unit
.table-cell
%em {{ variant.unit_to_display }}

View File

@@ -1,8 +1,5 @@
%tab#sign-up-content{"ng-controller" => "SignupCtrl",
heading: "{{'label_signup' | t}}",
active: "active(path)",
select: "select(path)"}
%form{"ng-submit" => "submit()"}
%tab#sign-up-content{ heading: "{{'label_signup' | t}}", active: 'tabs.signup.active', select: "select(path)"}
%form{ ng: { controller: "SignupCtrl", submit: "submit()" } }
.row
.large-12.columns
%label{for: "email"} {{'signup_email' | t}}

View File

@@ -9,7 +9,7 @@
*= require admin/spree_promo
*= require shared/jquery-ui-timepicker-addon
*= require shared/textAngular.min
*= require shared/textAngular
*= require shared/ng-tags-input.min
*= require_self

View File

@@ -1,9 +1,14 @@
#save-bar
position: fixed
width: 100%
z-index: 100
bottom: 0px
padding: 8px 10px
left: 0
padding: 8px 8px
font-weight: bold
background-color: #fff
background-color: #eff5fc
color: #5498da
h5
color: #5498da
input
margin-right: 5px

View File

@@ -28,6 +28,9 @@ text-angular .ta-editor {
left: 275px;
}
span.error, div.error {
color: #DA5354;
}
/* Fix conflict between Spree and elRTE's styles */
.el-rte .toolbar {
@@ -37,6 +40,12 @@ text-angular .ta-editor {
input.red {
background-color: #DA5354;
margin-right: 5px;
}
input.orange {
background-color: #FF9848;
margin-right: 5px;
}
input.search {

View File

@@ -13,10 +13,6 @@ input.show-dirty {
}
}
span.error {
color: #DA5354;
}
input, div {
&.update-error {
border: solid 1px #DA5354;

View File

@@ -84,16 +84,25 @@
padding-right: 0rem
font-size: 0.8rem
.shopfront_message, .shopfront_closed_message, .shopfront_hidden_message
.alert-box.shopfront-message
border: 2px solid $clr-turquoise
border-radius: 5px
background-color: $clr-turquoise-light
color: $clr-turquoise
a
color: #0096ad
&:hover, &:focus, &:active
text-decoration: none
color: #4aadbd
.shopfront_closed_message, .shopfront_hidden_message
padding: 15px
border-radius: 5px
.shopfront_message, .shopfront_closed_message
.shopfront_closed_message
border: 2px solid #eb4c46
.shopfront_message
margin-top: 2em
.shopfront_closed_message
margin: 2em 0em

View File

@@ -74,6 +74,9 @@ table.order-summary
padding-left: 5px
padding-right: 5px
.text-right
text-align: right
.social .soc-btn
padding: 3px 7px
font-size: 12px

View File

@@ -1 +1 @@
tags-input{display:block}tags-input *,tags-input :after,tags-input :before{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}tags-input .host{position:relative;margin-top:5px;margin-bottom:5px;height:100%}tags-input .host:active{outline:0}tags-input .tags{-moz-appearance:textfield;-webkit-appearance:textfield;padding:1px;overflow:hidden;word-wrap:break-word;cursor:text;background-color:#fff;border:1px solid #a9a9a9;box-shadow:1px 1px 1px 0 #d3d3d3 inset;height:100%}tags-input .tags.focused{outline:0;-webkit-box-shadow:0 0 3px 1px rgba(5,139,242,.6);-moz-box-shadow:0 0 3px 1px rgba(5,139,242,.6);box-shadow:0 0 3px 1px rgba(5,139,242,.6)}tags-input .tags .tag-list{margin:0;padding:0;list-style-type:none}tags-input .tags .tag-item{margin:2px;padding:0 5px;display:inline-block;float:left;font:14px "Helvetica Neue",Helvetica,Arial,sans-serif;height:26px;line-height:25px;border:1px solid #acacac;border-radius:3px;background:-webkit-linear-gradient(top,#f0f9ff 0,#cbebff 47%,#a1dbff 100%);background:linear-gradient(to bottom,#f0f9ff 0,#cbebff 47%,#a1dbff 100%)}tags-input .tags .tag-item.selected{background:-webkit-linear-gradient(top,#febbbb 0,#fe9090 45%,#ff5c5c 100%);background:linear-gradient(to bottom,#febbbb 0,#fe9090 45%,#ff5c5c 100%)}tags-input .tags .tag-item .remove-button{margin:0 0 0 5px;padding:0;border:none;background:0 0;cursor:pointer;vertical-align:middle;font:700 16px Arial,sans-serif;color:#585858}tags-input .tags .tag-item .remove-button:active{color:red}tags-input .tags .input{border:0;outline:0;margin:2px;padding:0;padding-left:5px;float:left;height:26px;font:14px "Helvetica Neue",Helvetica,Arial,sans-serif}tags-input .tags .input.invalid-tag{color:red}tags-input .tags .input::-ms-clear{display:none}tags-input.ng-invalid .tags{-webkit-box-shadow:0 0 3px 1px rgba(255,0,0,.6);-moz-box-shadow:0 0 3px 1px rgba(255,0,0,.6);box-shadow:0 0 3px 1px rgba(255,0,0,.6)}tags-input[disabled] .host:focus{outline:0}tags-input[disabled] .tags{background-color:#eee;cursor:default}tags-input[disabled] .tags .tag-item{opacity:.65;background:-webkit-linear-gradient(top,#f0f9ff 0,rgba(203,235,255,.75)47%,rgba(161,219,255,.62)100%);background:linear-gradient(to bottom,#f0f9ff 0,rgba(203,235,255,.75)47%,rgba(161,219,255,.62)100%)}tags-input[disabled] .tags .tag-item .remove-button{cursor:default}tags-input[disabled] .tags .tag-item .remove-button:active{color:#585858}tags-input[disabled] .tags .input{background-color:#eee;cursor:default}tags-input .autocomplete{margin-top:5px;position:absolute;padding:5px 0;z-index:999;width:100%;background-color:#fff;border:1px solid rgba(0,0,0,.2);-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);-moz-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2)}tags-input .autocomplete .suggestion-list{margin:0;padding:0;list-style-type:none;max-height:280px;overflow-y:auto;position:relative}tags-input .autocomplete .suggestion-item{padding:5px 10px;cursor:pointer;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font:16px "Helvetica Neue",Helvetica,Arial,sans-serif;color:#000;background-color:#fff}tags-input .autocomplete .suggestion-item.selected,tags-input .autocomplete .suggestion-item.selected em{color:#fff;background-color:#0097cf}tags-input .autocomplete .suggestion-item em{font:normal bold 16px "Helvetica Neue",Helvetica,Arial,sans-serif;color:#000;background-color:#fff}
tags-input{display:block}tags-input *,tags-input :after,tags-input :before{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}tags-input .host{position:relative;margin-top:5px;margin-bottom:5px;height:100%}tags-input .host:active{outline:0}tags-input .tags{-moz-appearance:textfield;-webkit-appearance:textfield;padding:1px;overflow:hidden;word-wrap:break-word;cursor:text;background-color:#fff;border:1px solid #a9a9a9;box-shadow:1px 1px 1px 0 #d3d3d3 inset;height:100%}tags-input .tags.focused{outline:0;-webkit-box-shadow:0 0 3px 1px rgba(5,139,242,.6);-moz-box-shadow:0 0 3px 1px rgba(5,139,242,.6);box-shadow:0 0 3px 1px rgba(5,139,242,.6)}tags-input .tags .tag-list{margin:0;padding:0;list-style-type:none}tags-input .tags .tag-item{margin:2px;padding:0 5px;display:inline-block;float:left;font:14px "Helvetica Neue",Helvetica,Arial,sans-serif;height:26px;line-height:25px;border:1px solid #acacac;border-radius:3px;background:-webkit-linear-gradient(top,#f0f9ff 0,#cbebff 47%,#a1dbff 100%);background:linear-gradient(to bottom,#f0f9ff 0,#cbebff 47%,#a1dbff 100%)}tags-input .tags .tag-item.selected{background:-webkit-linear-gradient(top,#febbbb 0,#fe9090 45%,#ff5c5c 100%);background:linear-gradient(to bottom,#febbbb 0,#fe9090 45%,#ff5c5c 100%)}tags-input .tags .tag-item .remove-button{margin:0 0 0 5px;padding:0;border:none;background:0 0;cursor:pointer;vertical-align:middle;font:700 16px Arial,sans-serif;color:#585858}tags-input .tags .input.invalid-tag,tags-input .tags .tag-item .remove-button:active{color:red}tags-input .tags .input{border:0;outline:0;margin:2px;padding:0 0 0 5px;float:left;height:26px;font:14px "Helvetica Neue",Helvetica,Arial,sans-serif}tags-input .tags .input::-ms-clear{display:none}tags-input.ng-invalid .tags{-webkit-box-shadow:0 0 3px 1px rgba(255,0,0,.6);-moz-box-shadow:0 0 3px 1px rgba(255,0,0,.6);box-shadow:0 0 3px 1px rgba(255,0,0,.6)}tags-input[disabled] .host:focus{outline:0}tags-input[disabled] .tags{background-color:#eee;cursor:default}tags-input[disabled] .tags .tag-item{opacity:.65;background:-webkit-linear-gradient(top,#f0f9ff 0,rgba(203,235,255,.75)47%,rgba(161,219,255,.62)100%);background:linear-gradient(to bottom,#f0f9ff 0,rgba(203,235,255,.75)47%,rgba(161,219,255,.62)100%)}tags-input[disabled] .tags .tag-item .remove-button{cursor:default}tags-input[disabled] .tags .tag-item .remove-button:active{color:#585858}tags-input[disabled] .tags .input{background-color:#eee;cursor:default}tags-input .autocomplete{margin-top:5px;position:absolute;padding:5px 0;z-index:999;width:100%;background-color:#fff;border:1px solid rgba(0,0,0,.2);-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);-moz-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2)}tags-input .autocomplete .suggestion-list{margin:0;padding:0;list-style-type:none;max-height:280px;overflow-y:auto;position:relative}tags-input .autocomplete .suggestion-item{padding:5px 10px;cursor:pointer;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font:16px "Helvetica Neue",Helvetica,Arial,sans-serif;color:#000;background-color:#fff}tags-input .autocomplete .suggestion-item.selected,tags-input .autocomplete .suggestion-item.selected em{color:#fff;background-color:#0097cf}tags-input .autocomplete .suggestion-item em{font:normal bold 16px "Helvetica Neue",Helvetica,Arial,sans-serif;color:#000;background-color:#fff}

View File

@@ -0,0 +1,193 @@
.ta-hidden-input {
width: 1px;
height: 1px;
border: none;
margin: 0;
padding: 0;
position: absolute;
top: -10000px;
left: -10000px;
opacity: 0;
overflow: hidden;
}
/* add generic styling for the editor */
.ta-root.focussed > .ta-scroll-window.form-control {
border-color: #66afe9;
outline: 0;
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(102, 175, 233, 0.6);
-moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(102, 175, 233, 0.6);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(102, 175, 233, 0.6);
}
.ta-editor.ta-html, .ta-scroll-window.form-control {
min-height: 300px;
height: auto;
overflow: auto;
font-family: inherit;
font-size: 100%;
}
.ta-scroll-window.form-control {
position: relative;
padding: 0;
}
.ta-scroll-window > .ta-bind {
height: auto;
min-height: 300px;
padding: 6px 12px;
}
.ta-editor:focus {
user-select: text;
}
/* add the styling for the awesomness of the resizer */
.ta-resizer-handle-overlay {
z-index: 100;
position: absolute;
display: none;
}
.ta-resizer-handle-overlay > .ta-resizer-handle-info {
position: absolute;
bottom: 16px;
right: 16px;
border: 1px solid black;
background-color: #FFF;
padding: 0 4px;
opacity: 0.7;
}
.ta-resizer-handle-overlay > .ta-resizer-handle-background {
position: absolute;
bottom: 5px;
right: 5px;
left: 5px;
top: 5px;
border: 1px solid black;
background-color: rgba(0, 0, 0, 0.2);
}
.ta-resizer-handle-overlay > .ta-resizer-handle-corner {
width: 10px;
height: 10px;
position: absolute;
}
.ta-resizer-handle-overlay > .ta-resizer-handle-corner-tl{
top: 0;
left: 0;
border-left: 1px solid black;
border-top: 1px solid black;
}
.ta-resizer-handle-overlay > .ta-resizer-handle-corner-tr{
top: 0;
right: 0;
border-right: 1px solid black;
border-top: 1px solid black;
}
.ta-resizer-handle-overlay > .ta-resizer-handle-corner-bl{
bottom: 0;
left: 0;
border-left: 1px solid black;
border-bottom: 1px solid black;
}
.ta-resizer-handle-overlay > .ta-resizer-handle-corner-br{
bottom: 0;
right: 0;
border: 1px solid black;
cursor: se-resize;
background-color: white;
}
/* copy the popover code from bootstrap so this will work even without it */
.popover {
position: absolute;
top: 0;
left: 0;
z-index: 1060;
display: none;
max-width: 276px;
padding: 1px;
font-size: 14px;
font-weight: normal;
line-height: 1.42857143;
text-align: left;
white-space: normal;
background-color: #fff;
-webkit-background-clip: padding-box;
background-clip: padding-box;
border: 1px solid #ccc;
border: 1px solid rgba(0, 0, 0, .2);
border-radius: 6px;
-webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, .2);
box-shadow: 0 5px 10px rgba(0, 0, 0, .2);
}
.popover.top {
margin-top: -10px;
}
.popover.bottom {
margin-top: 10px;
}
.popover-title {
padding: 8px 14px;
margin: 0;
font-size: 14px;
background-color: #f7f7f7;
border-bottom: 1px solid #ebebeb;
border-radius: 5px 5px 0 0;
}
.popover-content {
padding: 9px 14px;
}
.popover > .arrow,
.popover > .arrow:after {
position: absolute;
display: block;
width: 0;
height: 0;
border-color: transparent;
border-style: solid;
}
.popover > .arrow {
border-width: 11px;
}
.popover > .arrow:after {
content: "";
border-width: 10px;
}
.popover.top > .arrow {
bottom: -11px;
left: 50%;
margin-left: -11px;
border-top-color: #999;
border-top-color: rgba(0, 0, 0, .25);
border-bottom-width: 0;
}
.popover.top > .arrow:after {
bottom: 1px;
margin-left: -10px;
content: " ";
border-top-color: #fff;
border-bottom-width: 0;
}
.popover.bottom > .arrow {
top: -11px;
left: 50%;
margin-left: -11px;
border-top-width: 0;
border-bottom-color: #999;
border-bottom-color: rgba(0, 0, 0, .25);
}
.popover.bottom > .arrow:after {
top: 1px;
margin-left: -10px;
content: " ";
border-top-width: 0;
border-bottom-color: #fff;
}

View File

@@ -1 +0,0 @@
.ta-scroll-window.form-control{height:auto;min-height:300px;overflow:auto;font-family:inherit;font-size:100%;position:relative;padding:0}.ta-root.focussed .ta-scroll-window.form-control{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)}.ta-editor.ta-html{min-height:300px;height:auto;overflow:auto;font-family:inherit;font-size:100%}.ta-scroll-window>.ta-bind{height:auto;min-height:300px;padding:6px 12px}.ta-root .ta-resizer-handle-overlay{z-index:100;position:absolute;display:none}.ta-root .ta-resizer-handle-overlay>.ta-resizer-handle-info{position:absolute;bottom:16px;right:16px;border:1px solid #000;background-color:#FFF;padding:0 4px;opacity:.7}.ta-root .ta-resizer-handle-overlay>.ta-resizer-handle-background{position:absolute;bottom:5px;right:5px;left:5px;top:5px;border:1px solid #000;background-color:rgba(0,0,0,.2)}.ta-root .ta-resizer-handle-overlay>.ta-resizer-handle-corner{width:10px;height:10px;position:absolute}.ta-root .ta-resizer-handle-overlay>.ta-resizer-handle-corner-tl{top:0;left:0;border-left:1px solid #000;border-top:1px solid #000}.ta-root .ta-resizer-handle-overlay>.ta-resizer-handle-corner-tr{top:0;right:0;border-right:1px solid #000;border-top:1px solid #000}.ta-root .ta-resizer-handle-overlay>.ta-resizer-handle-corner-bl{bottom:0;left:0;border-left:1px solid #000;border-bottom:1px solid #000}.ta-root .ta-resizer-handle-overlay>.ta-resizer-handle-corner-br{bottom:0;right:0;border:1px solid #000;cursor:se-resize;background-color:#fff}

View File

@@ -18,10 +18,13 @@ class Admin::BusinessModelConfigurationController < Spree::Admin::BaseController
def load_settings
@settings = OpenFoodNetwork::BusinessModelConfigurationValidator.new(params[:settings] || {
shop_trial_length_days: Spree::Config[:shop_trial_length_days],
account_invoices_monthly_fixed: Spree::Config[:account_invoices_monthly_fixed],
account_invoices_monthly_rate: Spree::Config[:account_invoices_monthly_rate],
account_invoices_monthly_cap: Spree::Config[:account_invoices_monthly_cap],
account_invoices_tax_rate: Spree::Config[:account_invoices_tax_rate]
account_invoices_tax_rate: Spree::Config[:account_invoices_tax_rate],
minimum_billable_turnover: Spree::Config[:minimum_billable_turnover]
})
end

View File

@@ -0,0 +1,38 @@
module Admin
class ColumnPreferencesController < ResourceController
before_filter :load_collection, only: [:bulk_update]
respond_to :json
def bulk_update
@cp_set.collection.each { |cp| authorize! :bulk_update, cp }
if @cp_set.save
# Return saved VOs with IDs
render json: @cp_set.collection, each_serializer: Api::Admin::ColumnPreferenceSerializer
else
if @cp_set.errors.present?
render json: { errors: @cp_set.errors }, status: 400
else
render nothing: true, status: 500
end
end
end
private
def load_collection
collection_hash = Hash[params[:column_preferences].each_with_index.map { |cp, i| [i, cp] }]
collection_hash.reject!{ |i, cp| cp[:action_name] != params[:action_name] }
@cp_set = ColumnPreferenceSet.new @column_preferences, collection_attributes: collection_hash
end
def collection
ColumnPreference.where(user_id: spree_current_user, action_name: params[:action_name])
end
def collection_actions
[:bulk_update]
end
end
end

View File

@@ -7,11 +7,8 @@ module Admin
respond_to do |format|
format.html
format.json do
serialised = ActiveModel::ArraySerializer.new(
@collection,
each_serializer: Api::Admin::CustomerSerializer,
spree_current_user: spree_current_user)
render json: serialised.to_json
tag_rule_mapping = TagRule.mapping_for(Enterprise.where(id: params[:enterprise_id]))
render_as_json @collection, tag_rule_mapping: tag_rule_mapping
end
end
end
@@ -19,8 +16,12 @@ module Admin
def create
@customer = Customer.new(params[:customer])
if user_can_create_customer?
@customer.save
render json: Api::Admin::CustomerSerializer.new(@customer).to_json
if @customer.save
tag_rule_mapping = TagRule.mapping_for(Enterprise.where(id: @customer.enterprise))
render_as_json @customer, tag_rule_mapping: tag_rule_mapping
else
render json: { errors: @customer.errors.full_messages }, status: 400
end
else
redirect_to '/unauthorized'
end

View File

@@ -6,5 +6,38 @@ module Admin
respond_override destroy: { json: {
success: lambda { render nothing: true, :status => 204 }
} }
def map_by_tag
respond_to do |format|
format.json do
serialiser = ActiveModel::ArraySerializer.new(collection)
render json: serialiser.to_json
end
end
end
private
def collection_actions
[:index, :map_by_tag]
end
def collection
case action
when :map_by_tag
TagRule.mapping_for(enterprises).values
else
TagRule.for(enterprises.pluck(&:id))
end
end
def enterprises
if params[:enterprise_id]
Enterprise.managed_by(spree_current_user).where(id: params[:enterprise_id])
else
Enterprise.managed_by(spree_current_user)
end
end
end
end

View File

@@ -1,28 +0,0 @@
module Admin
class TagsController < Spree::Admin::BaseController
respond_to :json
def index
respond_to do |format|
format.json do
serialiser = ActiveModel::ArraySerializer.new(tags_of_enterprise)
render json: serialiser.to_json
end
end
end
private
def enterprise
Enterprise.managed_by(spree_current_user).find_by_id(params[:enterprise_id])
end
def tags_of_enterprise
return [] unless enterprise
tag_rule_map = enterprise.rules_per_tag
tag_rule_map.keys.map do |tag|
{ text: tag, rules: tag_rule_map[tag] }
end
end
end
end

View File

@@ -47,13 +47,19 @@ module Admin
admin_inject_json_ams_array opts[:module], "inventoryItems", @inventory_items, Api::Admin::InventoryItemSerializer
end
def admin_inject_column_preferences(opts={})
opts.reverse_merge!(module: 'ofn.admin', action: "#{controller_name}_#{action_name}")
column_preferences = ColumnPreference.for(spree_current_user, opts[:action])
admin_inject_json_ams_array opts[:module], "columns", column_preferences, Api::Admin::ColumnPreferenceSerializer
end
def admin_inject_enterprise_permissions
permissions =
{can_manage_shipping_methods: can?(:manage_shipping_methods, @enterprise),
can_manage_payment_methods: can?(:manage_payment_methods, @enterprise),
can_manage_enterprise_fees: can?(:manage_enterprise_fees, @enterprise)}
render partial: "admin/json/injection_ams", locals: {ngModule: "admin.enterprises", name: "enterprisePermissions", json: permissions.to_json}
admin_inject_json "admin.enterprises", "enterprisePermissions", permissions
end
def admin_inject_hub_permissions
@@ -96,6 +102,11 @@ module Admin
render partial: "admin/json/injection_ams", locals: {ngModule: 'admin.indexUtils', name: 'SpreeApiKey', json: "'#{@spree_api_key.to_s}'"}
end
def admin_inject_json(ngModule, name, data)
json = data.to_json
render partial: "admin/json/injection_ams", locals: {ngModule: ngModule, name: name, json: json}
end
def admin_inject_json_ams(ngModule, name, data, serializer, opts = {})
json = serializer.new(data, scope: spree_current_user).to_json
render partial: "admin/json/injection_ams", locals: {ngModule: ngModule, name: name, json: json}

View File

@@ -52,17 +52,17 @@ module EnterprisesHelper
def shop_trial_in_progress?(enterprise)
!!enterprise.shop_trial_start_date &&
(enterprise.shop_trial_start_date + Enterprise::SHOP_TRIAL_LENGTH.days > Time.zone.now) &&
(enterprise.shop_trial_start_date + Spree::Config[:shop_trial_length_days].days > Time.zone.now) &&
%w(own any).include?(enterprise.sells)
end
def shop_trial_expired?(enterprise)
!!enterprise.shop_trial_start_date &&
(enterprise.shop_trial_start_date + Enterprise::SHOP_TRIAL_LENGTH.days <= Time.zone.now) &&
(enterprise.shop_trial_start_date + Spree::Config[:shop_trial_length_days].days <= Time.zone.now) &&
%w(own any).include?(enterprise.sells)
end
def remaining_trial_days(enterprise)
distance_of_time_in_words(Time.zone.now, enterprise.shop_trial_start_date + Enterprise::SHOP_TRIAL_LENGTH.days)
distance_of_time_in_words(Time.zone.now, enterprise.shop_trial_start_date + Spree::Config[:shop_trial_length_days].days)
end
end

View File

@@ -4,8 +4,11 @@ class ProducerMailer < Spree::BaseMailer
@producer = producer
@coordinator = order_cycle.coordinator
@order_cycle = order_cycle
@line_items = aggregated_line_items_from(@order_cycle, @producer)
line_items = line_items_from(@order_cycle, @producer)
@grouped_line_items = line_items.group_by(&:product_and_full_name)
@receival_instructions = @order_cycle.receival_instructions_for @producer
@total = total_from_line_items(line_items)
@tax_total = tax_total_from_line_items(line_items)
subject = "[#{Spree::Config.site_name}] Order cycle report for #{producer.name}"
@@ -25,10 +28,6 @@ class ProducerMailer < Spree::BaseMailer
line_items_from(order_cycle, producer).any?
end
def aggregated_line_items_from(order_cycle, producer)
aggregate_line_items line_items_from(order_cycle, producer)
end
def line_items_from(order_cycle, producer)
Spree::LineItem.
joins(:order => :order_cycle, :variant => :product).
@@ -37,16 +36,11 @@ class ProducerMailer < Spree::BaseMailer
merge(Spree::Order.complete)
end
def aggregate_line_items(line_items)
# Arrange the items in a hash to group quantities
line_items.inject({}) do |lis, li|
if lis.key? li.variant
lis[li.variant].quantity += li.quantity
else
lis[li.variant] = li
end
def total_from_line_items(line_items)
Spree::Money.new line_items.sum(&:total)
end
lis
end
def tax_total_from_line_items(line_items)
Spree::Money.new line_items.sum(&:included_tax)
end
end

View File

@@ -0,0 +1,46 @@
require 'open_food_network/column_preference_defaults'
class ColumnPreference < ActiveRecord::Base
extend OpenFoodNetwork::ColumnPreferenceDefaults
# These are the attributes used to identify a preference
attr_accessible :user_id, :action_name, :column_name
# These are attributes that need to be mass assignable
attr_accessible :name, :visible
# Non-persisted attributes that only have one
# setting (ie. the default) for a given column
attr_accessor :name
belongs_to :user, class_name: "Spree::User"
validates :action_name, presence: true, inclusion: { in: proc { known_actions } }
validates :column_name, presence: true, inclusion: { in: proc { |p| valid_columns_for(p.action_name) } }
def self.for(user, action_name)
stored_preferences = where(user_id: user.id, action_name: action_name)
default_preferences = send("#{action_name}_columns")
default_preferences.each_with_object([]) do |(column_name, default_attributes), preferences|
stored_preference = stored_preferences.find_by_column_name(column_name)
if stored_preference
stored_preference.assign_attributes(default_attributes.select{ |k,v| stored_preference[k].nil? })
preferences << stored_preference
else
attributes = default_attributes.merge(user_id: user.id, action_name: action_name, column_name: column_name)
preferences << ColumnPreference.new(attributes)
end
end
end
private
def self.valid_columns_for(action_name)
send("#{action_name}_columns").keys.map(&:to_s)
end
def self.known_actions
OpenFoodNetwork::ColumnPreferenceDefaults.private_instance_methods
.select{|m| m.to_s.end_with?("_columns")}.map{ |m| m.to_s.sub /_columns$/, ''}
end
end

View File

@@ -0,0 +1,5 @@
class ColumnPreferenceSet < ModelSet
def initialize(collection, attributes={})
super(ColumnPreference, collection, attributes, nil, nil )
end
end

View File

@@ -1,6 +1,5 @@
class Enterprise < ActiveRecord::Base
SELLS = %w(unspecified none own any)
SHOP_TRIAL_LENGTH = 30
ENTERPRISE_SEARCH_RADIUS = 100
preference :shopfront_message, :text, default: ""
@@ -338,7 +337,7 @@ class Enterprise < ActiveRecord::Base
end
def shop_trial_expiry
shop_trial_start_date.andand + Enterprise::SHOP_TRIAL_LENGTH.days
shop_trial_start_date.andand + Spree::Config[:shop_trial_length_days].days
end
def can_invoice?
@@ -352,20 +351,6 @@ class Enterprise < ActiveRecord::Base
end
end
def rules_per_tag
tag_rule_map = {}
tag_rules.each do |rule|
rule.preferred_customer_tags.split(",").each do |tag|
if tag_rule_map[tag]
tag_rule_map[tag] += 1
else
tag_rule_map[tag] = 1
end
end
end
tag_rule_map
end
protected
def devise_mailer

View File

@@ -72,7 +72,7 @@ class AbilityDecorator
can [:admin, :index, :read, :create, :edit, :update_positions, :destroy], ProducerProperty
can [:admin, :destroy], TagRule do |tag_rule|
can [:admin, :map_by_tag, :destroy], TagRule do |tag_rule|
user.enterprises.include? tag_rule.enterprise
end
@@ -101,6 +101,10 @@ class AbilityDecorator
can [:print], Spree::Order do |order|
order.user == user
end
can [:admin, :bulk_update], ColumnPreference do |column_preference|
column_preference.user == user
end
end
def add_product_management_abilities(user)
@@ -218,7 +222,6 @@ class AbilityDecorator
can [:create], Customer
can [:admin, :index, :update, :destroy], Customer, enterprise_id: Enterprise.managed_by(user).pluck(:id)
can [:admin, :index], :tag
end

Some files were not shown because too many files have changed in this diff Show More