Auto-merged master into uk/trial-length on deployment.

This commit is contained in:
Maikel
2016-03-16 22:42:50 +11:00
163 changed files with 2707 additions and 914 deletions

View File

@@ -730,6 +730,3 @@ DEPENDENCIES
whenever
wicked_pdf
wkhtmltopdf-binary
BUNDLED WITH
1.10.6

View File

@@ -27,8 +27,10 @@
//= require ./customers/customers
//= require ./dropdown/dropdown
//= require ./enterprises/enterprises
//= require ./enterprise_fees/enterprise_fees
//= require ./enterprise_groups/enterprise_groups
//= require ./index_utils/index_utils
//= require ./inventory_items/inventory_items
//= require ./line_items/line_items
//= require ./orders/orders
//= require ./order_cycles/order_cycles

View File

@@ -1,2 +0,0 @@
angular.module("admin.dropdown").controller "DropDownCtrl", ($scope) ->
$scope.expanded = false

View File

@@ -1,4 +1,4 @@
angular.module("admin.dropdown").directive "ofnCloseOnClick", ($document) ->
angular.module("admin.dropdown").directive "closeOnClick", () ->
link: (scope, element, attrs) ->
element.click (event) ->
event.stopPropagation()

View File

@@ -1,6 +1,9 @@
angular.module("admin.dropdown").directive "ofnDropDown", ($document) ->
restrict: 'C'
scope: true
link: (scope, element, attrs) ->
scope.expanded = false
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

View File

@@ -1,69 +0,0 @@
angular.module('enterprise_fees', [])
.controller('AdminEnterpriseFeesCtrl', ['$scope', '$http', '$window', function($scope, $http, $window) {
$scope.enterpriseFeesUrl = function() {
var url = '/admin/enterprise_fees.json?include_calculators=1';
var match = $window.location.search.match(/enterprise_id=(\d+)/);
if(match) {
url += "&"+match[0];
}
return url;
};
$http.get($scope.enterpriseFeesUrl()).success(function(data) {
$scope.enterprise_fees = data;
// TODO: Angular 1.1.0 will have a means to reset a form to its pristine state, which
// would avoid the need to save off original calculator types for comparison.
for(i in $scope.enterprise_fees) {
$scope.enterprise_fees[i].orig_calculator_type = $scope.enterprise_fees[i].calculator_type;
}
});
}])
.directive('ngBindHtmlUnsafeCompiled', ['$compile', function($compile) {
return function(scope, element, attrs) {
scope.$watch(attrs.ngBindHtmlUnsafeCompiled, function(value) {
element.html($compile(value)(scope));
});
}
}])
.directive('spreeDeleteResource', function() {
return function(scope, element, attrs) {
if(scope.enterprise_fee.id) {
var url = "/admin/enterprise_fees/" + scope.enterprise_fee.id
var html = '<a href="'+url+'" class="delete-resource icon_link icon-trash no-text" data-action="remove" data-confirm="Are you sure?" url="'+url+'"></a>';
//var html = '<a href="'+url+'" class="delete-resource" data-confirm="Are you sure?"><img alt="Delete" src="/assets/admin/icons/delete.png" /> Delete</a>';
element.append(html);
}
}
})
.directive('spreeEnsureCalculatorPreferencesMatchType', function() {
// Hide calculator preference fields when calculator type changed
// Fixes 'Enterprise fee is not found' error when changing calculator type
// See spree/core/app/assets/javascripts/admin/calculator.js
// Note: For some reason, DOM --> model bindings aren't working here, so
// we use element.val() instead of querying the model itself.
return function(scope, element, attrs) {
scope.$watch(function(scope) {
//return scope.enterprise_fee.calculator_type;
return element.val();
}, function(value) {
var settings = element.parent().parent().find("div.calculator-settings");
// scope.enterprise_fee.calculator_type == scope.enterprise_fee.orig_calculator_type
if(element.val() == scope.enterprise_fee.orig_calculator_type) {
settings.show();
settings.find("input").prop("disabled", false);
} else {
settings.hide();
settings.find("input").prop("disabled", true);
}
});
}
});

View File

@@ -0,0 +1,14 @@
angular.module('admin.enterpriseFees').controller 'enterpriseFeesCtrl', ($scope, $http, $window, enterprises, tax_categories, calculators) ->
$scope.enterprises = enterprises
$scope.tax_categories = [{id: -1, name: "Inherit From Product"}].concat tax_categories
$scope.calculators = calculators
$scope.enterpriseFeesUrl = ->
url = '/admin/enterprise_fees.json?include_calculators=1'
match = $window.location.search.match(/enterprise_id=(\d+)/)
if match
url += '&' + match[0]
url
$http.get($scope.enterpriseFeesUrl()).success (data) ->
$scope.enterprise_fees = data

View File

@@ -0,0 +1,6 @@
angular.module("admin.enterpriseFees").directive 'ngBindHtmlUnsafeCompiled', ($compile) ->
(scope, element, attrs) ->
scope.$watch attrs.ngBindHtmlUnsafeCompiled, (value) ->
element.html $compile(value)(scope)
return
return

View File

@@ -0,0 +1,8 @@
angular.module('admin.enterpriseFees').directive 'spreeDeleteResource', ->
(scope, element, attrs) ->
if scope.enterprise_fee.id
url = '/admin/enterprise_fees/' + scope.enterprise_fee.id
html = '<a href="' + url + '" class="delete-resource icon_link icon-trash no-text" data-action="remove" data-confirm="Are you sure?" url="' + url + '"></a>'
#var html = '<a href="'+url+'" class="delete-resource" data-confirm="Are you sure?"><img alt="Delete" src="/assets/admin/icons/delete.png" /> Delete</a>';
element.append html
return

View File

@@ -0,0 +1,17 @@
angular.module("admin.enterpriseFees").directive 'spreeEnsureCalculatorPreferencesMatchType', ->
# Hide calculator preference fields when calculator type changed
# Fixes 'Enterprise fee is not found' error when changing calculator type
# See spree/core/app/assets/javascripts/admin/calculator.js
(scope, element, attrs) ->
orig_calculator_type = scope.enterprise_fee.calculator_type
scope.$watch "enterprise_fee.calculator_type", (value) ->
settings = element.parent().parent().find('div.calculator-settings')
if value == orig_calculator_type
settings.show()
settings.find('input').prop 'disabled', false
else
settings.hide()
settings.find('input').prop 'disabled', true
return
return

View File

@@ -0,0 +1,15 @@
angular.module("admin.enterpriseFees").directive 'watchTaxCategory', ->
# In order to have a nice user experience on this page, we're modelling tax_category
# inheritance using tax_category_id = -1.
# This directive acts as a parser for tax_category_id, storing the value the form as "" when
# tax_category is to be inherited and setting inherits_tax_category as appropriate.
(scope, element, attrs) ->
scope.$watch 'enterprise_fee.tax_category_id', (value) ->
if value == -1
scope.enterprise_fee.inherits_tax_category = true
element.val("")
else
scope.enterprise_fee.inherits_tax_category = false
element.val(value)
scope.enterprise_fee.tax_category_id = -1 if scope.enterprise_fee.inherits_tax_category

View File

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

View File

@@ -17,6 +17,7 @@ angular.module("admin.enterprises")
{ name: "Shipping Methods", icon_class: "icon-truck", show: "showShippingMethods()" }
{ name: "Payment Methods", icon_class: "icon-money", show: "showPaymentMethods()" }
{ name: "Enterprise Fees", icon_class: "icon-tasks", show: "showEnterpriseFees()" }
{ name: "Inventory Settings", icon_class: "icon-list-ol", show: "showInventorySettings()" }
{ name: "Shop Preferences", icon_class: "icon-shopping-cart", show: "showShopPreferences()" }
]
@@ -41,5 +42,8 @@ angular.module("admin.enterprises")
$scope.showEnterpriseFees = ->
enterprisePermissions.can_manage_enterprise_fees && ($scope.Enterprise.sells != "none" || $scope.Enterprise.is_primary_producer)
$scope.showInventorySettings = ->
$scope.Enterprise.sells != "none"
$scope.showShopPreferences = ->
$scope.Enterprise.sells != "none"

View File

@@ -0,0 +1,13 @@
# Mainly useful for adding a blank option that works with AngularJS
# Angular doesn't seem to understand the blank option generated by rails
# using the include_blank flag on select helper.
angular.module("admin.indexUtils").directive "ofnSelect", ->
restrict: 'E'
scope:
data: "="
replace: true
template: (element, attrs) ->
valueAttr = attrs.valueAttr || 'id'
textAttr = attrs.textAttr || 'name'
blank = if attrs.includeBlank? then "<option value=''>#{attrs.includeBlank}</option>" else ""
return "<select ng-options='e.#{valueAttr} as e.#{textAttr} for e in data'>#{blank}</select>"

View File

@@ -1,4 +1,4 @@
angular.module("admin.indexUtils").directive "ofnToggleColumn", (Columns) ->
angular.module("admin.indexUtils").directive "toggleColumn", (Columns) ->
link: (scope, element, attrs) ->
element.addClass "selected" if scope.column.visible

View File

@@ -0,0 +1,11 @@
angular.module("admin.indexUtils").directive "toggleView", (Views) ->
link: (scope, element, attrs) ->
Views.register
element.addClass "selected" if scope.view.visible
element.click "click", ->
scope.$apply ->
Views.selectView(scope.viewKey)
scope.$watch "view.visible", (newValue, oldValue) ->
element.toggleClass "selected", scope.view.visible

View File

@@ -1,12 +1,9 @@
angular.module("admin.indexUtils").factory "dataFetcher", [
"$http", "$q"
($http, $q) ->
return (dataLocation) ->
deferred = $q.defer()
$http.get(dataLocation).success((data) ->
deferred.resolve data
).error ->
deferred.reject()
angular.module("admin.indexUtils").factory "dataFetcher", ($http, $q, RequestMonitor) ->
return (dataLocation) ->
deferred = $q.defer()
RequestMonitor.load $http.get(dataLocation).success((data) ->
deferred.resolve data
).error ->
deferred.reject()
deferred.promise
]
deferred.promise

View File

@@ -0,0 +1,16 @@
angular.module("admin.indexUtils").factory 'Views', ($rootScope) ->
new class Views
views: {}
currentView: null
setViews: (views) =>
@views = {}
for key, view of views
@views[key] = view
@selectView(key) if view.visible
@views
selectView: (selectedKey) =>
@currentView = @views[selectedKey]
for key, view of @views
view.visible = (key == selectedKey)

View File

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

View File

@@ -0,0 +1,5 @@
angular.module("admin.inventoryItems").factory 'InventoryItemResource', ($resource) ->
$resource('/admin/inventory_items/:id/:action.json', {}, {
'update':
method: 'PUT'
})

View File

@@ -0,0 +1,25 @@
angular.module("admin.inventoryItems").factory "InventoryItems", (inventoryItems, InventoryItemResource) ->
new class InventoryItems
inventoryItems: {}
errors: {}
constructor: ->
for ii in inventoryItems
@inventoryItems[ii.enterprise_id] ||= {}
@inventoryItems[ii.enterprise_id][ii.variant_id] = new InventoryItemResource(ii)
setVisibility: (hub_id, variant_id, visible) ->
if @inventoryItems[hub_id] && @inventoryItems[hub_id][variant_id]
inventory_item = angular.extend(angular.copy(@inventoryItems[hub_id][variant_id]), {visible: visible})
InventoryItemResource.update {id: inventory_item.id}, inventory_item, (data) =>
@inventoryItems[hub_id][variant_id] = data
, (response) =>
@errors[hub_id] ||= {}
@errors[hub_id][variant_id] = response.data.errors
else
InventoryItemResource.save {enterprise_id: hub_id, variant_id: variant_id, visible: visible}, (data) =>
@inventoryItems[hub_id] ||= {}
@inventoryItems[hub_id][variant_id] = data
, (response) =>
@errors[hub_id] ||= {}
@errors[hub_id][variant_id] = response.data.errors

View File

@@ -1,4 +0,0 @@
angular.module("admin.orderCycles").filter "visibleProductVariants", ->
return (product, exchange, rules) ->
variants = product.variants.concat( [{ "id": product.master_id}] )
return (variant for variant in variants when variant.id in rules[exchange.enterprise_id])

View File

@@ -1,3 +1,3 @@
angular.module("admin.orderCycles").filter "visibleProducts", ($filter) ->
return (products, exchange, rules) ->
return (product for product in products when $filter('visibleProductVariants')(product, exchange, rules).length > 0)
return (product for product in products when $filter('visibleVariants')(product.variants, exchange, rules).length > 0)

View File

@@ -0,0 +1,3 @@
angular.module("admin.orderCycles").filter "visibleVariants", ->
return (variants, exchange, rules) ->
return (variant for variant in variants when variant.id in rules[exchange.enterprise_id])

View File

@@ -29,4 +29,4 @@ angular.module("ofn.admin").factory 'EnterpriseRelationships', ($http, enterpris
when "add_to_order_cycle" then "add to order cycle"
when "manage_products" then "manage products"
when "edit_profile" then "edit profile"
when "create_variant_overrides" then "override variant details"
when "create_variant_overrides" then "add products to inventory"

View File

@@ -0,0 +1,18 @@
angular.module("admin.utils").directive "alertRow", ->
restrict: "E"
replace: true
scope:
message: '@'
buttonText: '@?'
buttonAction: '&?'
dismissed: '=?'
close: "&?"
transclude: true
templateUrl: "admin/alert_row.html"
link: (scope, element, attrs) ->
scope.dismissed = false
scope.dismiss = ->
scope.dismissed = true
scope.close() if scope.close?
return false

View File

@@ -1,12 +1,23 @@
angular.module("admin.variantOverrides").controller "AdminVariantOverridesCtrl", ($scope, $http, $timeout, Indexer, Columns, SpreeApiAuth, PagedFetcher, StatusMessage, hubs, producers, hubPermissions, VariantOverrides, DirtyVariantOverrides) ->
angular.module("admin.variantOverrides").controller "AdminVariantOverridesCtrl", ($scope, $http, $timeout, Indexer, Columns, Views, SpreeApiAuth, PagedFetcher, StatusMessage, RequestMonitor, hubs, producers, hubPermissions, InventoryItems, VariantOverrides, DirtyVariantOverrides) ->
$scope.hubs = Indexer.index hubs
$scope.hub = null
$scope.hub_id = if hubs.length == 1 then hubs[0].id else null
$scope.products = []
$scope.producers = producers
$scope.producersByID = Indexer.index producers
$scope.hubPermissions = hubPermissions
$scope.productLimit = 10
$scope.variantOverrides = VariantOverrides.variantOverrides
$scope.inventoryItems = InventoryItems.inventoryItems
$scope.setVisibility = InventoryItems.setVisibility
$scope.StatusMessage = StatusMessage
$scope.RequestMonitor = RequestMonitor
$scope.selectView = Views.selectView
$scope.currentView = -> Views.currentView
$scope.views = Views.setViews
inventory: { name: "Inventory Products", visible: true }
hidden: { name: "Hidden Products", visible: false }
new: { name: "New Products", visible: false }
$scope.columns = Columns.setColumns
producer: { name: "Producer", visible: true }
@@ -17,6 +28,9 @@ angular.module("admin.variantOverrides").controller "AdminVariantOverridesCtrl",
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.resetSelectFilters = ->
$scope.producerFilter = 0
@@ -24,6 +38,9 @@ angular.module("admin.variantOverrides").controller "AdminVariantOverridesCtrl",
$scope.resetSelectFilters()
$scope.filtersApplied = ->
$scope.producerFilter != 0 || $scope.query != ''
$scope.initialise = ->
SpreeApiAuth.authorise()
.then ->
@@ -42,10 +59,6 @@ angular.module("admin.variantOverrides").controller "AdminVariantOverridesCtrl",
$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"

View File

@@ -2,11 +2,11 @@ angular.module("admin.variantOverrides").directive "trackInheritance", (VariantO
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
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)
variantOverride = VariantOverrides.inherit(scope.hub_id, scope.variant.id)
DirtyVariantOverrides.add variantOverride
scope.displayDirty()
viewValue

View File

@@ -3,7 +3,7 @@ angular.module("admin.variantOverrides").directive "ofnTrackVariantOverride", (D
link: (scope, element, attrs, ngModel) ->
ngModel.$parsers.push (viewValue) ->
if ngModel.$dirty
variantOverride = scope.variantOverrides[scope.hub.id][scope.variant.id]
variantOverride = scope.variantOverrides[scope.hub_id][scope.variant.id]
scope.inherit = false
DirtyVariantOverrides.add variantOverride
scope.displayDirty()

View File

@@ -0,0 +1,17 @@
angular.module("admin.variantOverrides").filter "inventoryProducts", ($filter, InventoryItems) ->
return (products, hub_id, views) ->
return [] if !hub_id
return $filter('filter')(products, (product) ->
for variant in product.variants
if InventoryItems.inventoryItems.hasOwnProperty(hub_id) && InventoryItems.inventoryItems[hub_id].hasOwnProperty(variant.id)
if InventoryItems.inventoryItems[hub_id][variant.id].visible
# Important to only return if true, as other variants for this product might be visible
return true if views.inventory.visible
else
# Important to only return if true, as other variants for this product might be visible
return true if views.hidden.visible
else
# Important to only return if true, as other variants for this product might be visible
return true if views.new.visible
false
, true)

View File

@@ -0,0 +1,12 @@
angular.module("admin.variantOverrides").filter "inventoryVariants", ($filter, InventoryItems) ->
return (variants, hub_id, views) ->
return [] if !hub_id
return $filter('filter')(variants, (variant) ->
if InventoryItems.inventoryItems.hasOwnProperty(hub_id) && InventoryItems.inventoryItems[hub_id].hasOwnProperty(variant.id)
if InventoryItems.inventoryItems[hub_id][variant.id].visible
return views.inventory.visible
else
return views.hidden.visible
else
return views.new.visible
, true)

View File

@@ -0,0 +1,9 @@
angular.module("admin.variantOverrides").filter "newInventoryProducts", ($filter, InventoryItems) ->
return (products, hub_id) ->
return [] if !hub_id
return products unless InventoryItems.inventoryItems.hasOwnProperty(hub_id)
return $filter('filter')(products, (product) ->
for variant in product.variants
return true if !InventoryItems.inventoryItems[hub_id].hasOwnProperty(variant.id)
false
, true)

View File

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

View File

@@ -2,7 +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

@@ -0,0 +1,8 @@
.sixteen.columns.alpha.omega.alert-row{ ng: { show: '!dismissed' } }
.fifteen.columns.pad.alpha
%span.message.text-big{ ng: { bind: 'message'} }
&nbsp;&nbsp;&nbsp;
%input{ type: 'button', ng: { value: "buttonText", show: 'buttonText && buttonAction', click: "buttonAction()" } }
.one.column.omega.pad.text-center
%a.close{ href: "#", ng: { click: "dismiss()" } }
&times;

View File

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

View File

@@ -17,28 +17,13 @@
{{'enterprise_contact_required' | t}}
.row
.small-12.columns.field
%label{ for: 'enterprise_email' } {{'enterprise_email' | t}}:
%input.chunky.small-12.columns{ id: 'enterprise_email', name: 'email', type: 'email', required: true, placeholder: "eg. charlie@thefarm.com", ng: { model: 'enterprise.email' } }
%span.error.small-12.columns{ ng: { show: "(contact.email.$error.email || contact.email.$error.required) && submitted" } }
{{'enterprise_email_required' | t}}
%label{ for: 'enterprise_email_address' } {{'enterprise_email_address' | t}}:
%input.chunky.small-12.columns{ id: 'enterprise_email_address', name: 'email_address', type: 'email', placeholder: "eg. charlie@thefarm.com", ng: { model: 'enterprise.email_address' } }
.row
.small-12.columns.field
%label{ for: 'enterprise_phone' } {{'enterprise_phone' | t}}:
%input.chunky.small-12.columns{ id: 'enterprise_phone', name: 'phone', placeholder: "eg. (03) 1234 5678", ng: { model: 'enterprise.phone' } }
.small-12.medium-12.large-5.hide-for-small-only
/ %h6
/ Contact display
/ %i.ofn-i_013-help.has-tip{ 'data-tooltip' => true, title: "Choose how you want to display your contact details on the Open Food Network."}
/ .row
/ .small-12.columns
/ %label.indent-checkbox
/ %input{ type: 'checkbox', id: 'contact_name_profile', ng: { model: 'enterprise.name_in_profile' } } &nbsp; Display name in profile
/ .small-12.columns
/ %label.indent-checkbox
/ %input{ type: 'checkbox', id: 'contact_email_profile', ng: { model: 'enterprise.email_in_profile' } } &nbsp; Display email in profile
/ .small-12.columns
/ %label.indent-checkbox
/ %input{ type: 'checkbox', id: 'contact_phone_profile', ng: { model: 'enterprise.phone_in_profile' } } &nbsp; Display phone in profile
.row.buttons
.small-12.columns

View File

@@ -0,0 +1,19 @@
#advanced_settings {
background-color: #eff5fc;
border: 1px solid #cee1f4;
margin-bottom: 20px;
.row{
margin: 0px -4px;
padding: 20px 0px;
.column.alpha, .columns.alpha {
padding-left: 20px;
}
.column.omega, .columns.omega {
padding-right: 20px;
}
}
}

View File

@@ -0,0 +1,21 @@
.alert-row{
margin-bottom: 10px;
font-weight: bold;
background-color: #eff5fc;
.column, .columns {
padding-top: 8px;
padding-bottom: 8px;
&.alpha { padding-left: 10px; }
&.omega { padding-right: 10px; }
}
span {
line-height: 3rem;
}
a.close {
line-height: 3rem;
font-size: 2.0rem;
}
}

View File

@@ -27,9 +27,12 @@
-ms-user-select: none;
user-select: none;
text-align: center;
margin-right: 10px;
&.right {
float: right;
margin-right: 0px;
margin-left: 10px;
}
&:hover, &.expanded {
@@ -55,12 +58,35 @@
background-color: #ffffff;
box-shadow: 1px 3px 10px #888888;
z-index: 100;
white-space: nowrap;
.menu_item {
margin: 0px;
padding: 2px 0px;
padding: 2px 10px;
color: #454545;
text-align: left;
display: block;
.check {
display: inline-block;
text-align: center;
width: 40px;
&:before {
content: "\00a0";
}
}
.name {
display: inline-block;
padding: 0px 15px 0px 0px;
}
&.selected{
.check:before {
content: "\2713";
}
}
}
.menu_item:hover {

View File

@@ -0,0 +1,7 @@
.margin-bottom-20 {
margin-bottom: 20px;
}
.margin-bottom-50 {
margin-bottom: 50px;
}

View File

@@ -166,11 +166,16 @@ table#listing_enterprise_groups {
}
}
// TODO: remove this, use class below
#no_results {
font-weight:bold;
color: #DA5354;
}
.no-results {
font-weight:bold;
color: #DA5354;
}
#loading {
text-align: center;

View File

@@ -0,0 +1,9 @@
.text-normal {
font-size: 1.0rem;
font-weight: 300;
}
.text-big {
font-size: 1.2rem;
font-weight: 300;
}

View File

@@ -1,3 +1,6 @@
.variant-override-unit
float: right
font-style: italic
button.hide:hover
background-color: #DA5354

View File

@@ -62,10 +62,8 @@
// ABOUT Enterprise
.about-container
max-height: 200px
min-height: 20px
margin-bottom: 0.5rem
overflow-y: scroll
overflow-x: hidden
border-bottom: 1px solid $light-grey
@include box-shadow(0 2px 2px -2px $light-grey)

View File

@@ -16,18 +16,15 @@ module Admin
respond_to do |format|
format.html
format.json { @presented_collection = @collection.each_with_index.map { |ef, i| EnterpriseFeePresenter.new(self, ef, i) } }
format.json { render_as_json @collection, controller: self, include_calculators: @include_calculators }
# format.json { @presented_collection = @collection.each_with_index.map { |ef, i| EnterpriseFeePresenter.new(self, ef, i) } }
end
end
def for_order_cycle
respond_to do |format|
format.html
format.json do
render json: ActiveModel::ArraySerializer.new( @collection,
each_serializer: Api::Admin::EnterpriseFeeSerializer, controller: self
).to_json
end
format.json { render_as_json @collection, controller: self }
end
end

View File

@@ -96,7 +96,7 @@ module Admin
def for_order_cycle
respond_to do |format|
format.json do
render json: @collection, each_serializer: Api::Admin::ForOrderCycle::EnterpriseSerializer, spree_current_user: spree_current_user
render json: @collection, each_serializer: Api::Admin::ForOrderCycle::EnterpriseSerializer, order_cycle: @order_cycle, spree_current_user: spree_current_user
end
end
end
@@ -138,10 +138,10 @@ module Admin
def collection
case action
when :for_order_cycle
order_cycle = OrderCycle.find_by_id(params[:order_cycle_id]) if params[:order_cycle_id]
@order_cycle = OrderCycle.find_by_id(params[:order_cycle_id]) if params[:order_cycle_id]
coordinator = Enterprise.find_by_id(params[:coordinator_id]) if params[:coordinator_id]
order_cycle = OrderCycle.new(coordinator: coordinator) if order_cycle.nil? && coordinator.present?
return OpenFoodNetwork::OrderCyclePermissions.new(spree_current_user, order_cycle).visible_enterprises
@order_cycle = OrderCycle.new(coordinator: coordinator) if @order_cycle.nil? && coordinator.present?
return OpenFoodNetwork::OrderCyclePermissions.new(spree_current_user, @order_cycle).visible_enterprises
when :index
if spree_current_user.admin?
OpenFoodNetwork::Permissions.new(spree_current_user).

View File

@@ -0,0 +1,28 @@
module Admin
class InventoryItemsController < ResourceController
respond_to :json
respond_override update: { json: {
success: lambda { render_as_json @inventory_item },
failure: lambda { render json: { errors: @inventory_item.errors.full_messages }, status: :unprocessable_entity }
} }
respond_override create: { json: {
success: lambda { render_as_json @inventory_item },
failure: lambda { render json: { errors: @inventory_item.errors.full_messages }, status: :unprocessable_entity }
} }
private
# Overriding Spree method to load data from params here so that
# we can authorise #create using an object with required attributes
def build_resource
if parent_data.present?
parent.send(controller_name).build
else
model_class.new(params[object_name]) # This line changed
end
end
end
end

View File

@@ -60,8 +60,12 @@ module Admin
respond_to do |format|
if @order_cycle.update_attributes(params[:order_cycle])
OpenFoodNetwork::OrderCycleFormApplicator.new(@order_cycle, spree_current_user).go!
unless params[:order_cycle][:incoming_exchanges].nil? && params[:order_cycle][:outgoing_exchanges].nil?
# Only update apply exchange information if it is actually submmitted
OpenFoodNetwork::OrderCycleFormApplicator.new(@order_cycle, spree_current_user).go!
end
flash[:notice] = 'Your order cycle has been updated.' if params[:reloading] == '1'
format.html { redirect_to main_app.edit_admin_order_cycle_path(@order_cycle) }
format.json { render :json => {:success => true} }
else
format.json { render :json => {:success => false} }

View File

@@ -54,6 +54,8 @@ module Admin
@hub_permissions = OpenFoodNetwork::Permissions.new(spree_current_user).
variant_override_enterprises_per_hub
@inventory_items = InventoryItem.where(enterprise_id: @hubs)
end
def load_collection

View File

@@ -1,4 +1,4 @@
require 'open_food_network/scope_product_to_hub'
require 'open_food_network/products_renderer'
class ShopController < BaseController
layout "darkswarm"
@@ -10,22 +10,13 @@ class ShopController < BaseController
end
def products
if @products = products_for_shop
begin
products_json = OpenFoodNetwork::ProductsRenderer.new(current_distributor, current_order_cycle).products
enterprise_fee_calculator = OpenFoodNetwork::EnterpriseFeeCalculator.new current_distributor, current_order_cycle
render json: products_json
render status: 200,
json: ActiveModel::ArraySerializer.new(@products,
each_serializer: Api::ProductSerializer,
current_order_cycle: current_order_cycle,
current_distributor: current_distributor,
variants: variants_for_shop_by_id,
master_variants: master_variants_for_shop_by_id,
enterprise_fee_calculator: enterprise_fee_calculator,
).to_json
else
render json: "", status: 404
rescue OpenFoodNetwork::ProductsRenderer::NoProducts
render status: 404, json: ''
end
end
@@ -42,55 +33,4 @@ class ShopController < BaseController
end
end
private
def products_for_shop
if current_order_cycle
scoper = OpenFoodNetwork::ScopeProductToHub.new(current_distributor)
current_order_cycle.
valid_products_distributed_by(current_distributor).
order(taxon_order).
each { |p| scoper.scope(p) }.
select { |p| !p.deleted? && p.has_stock_for_distribution?(current_order_cycle, current_distributor) }
end
end
def taxon_order
if current_distributor.preferred_shopfront_taxon_order.present?
current_distributor
.preferred_shopfront_taxon_order
.split(",").map { |id| "primary_taxon_id=#{id} DESC" }
.join(",") + ", name ASC"
else
"name ASC"
end
end
def all_variants_for_shop
# We use the in_stock? method here instead of the in_stock scope because we need to
# look up the stock as overridden by VariantOverrides, and the scope method is not affected
# by them.
scoper = OpenFoodNetwork::ScopeVariantToHub.new(current_distributor)
Spree::Variant.
for_distribution(current_order_cycle, current_distributor).
each { |v| scoper.scope(v) }.
select(&:in_stock?)
end
def variants_for_shop_by_id
index_by_product_id all_variants_for_shop.reject(&:is_master)
end
def master_variants_for_shop_by_id
index_by_product_id all_variants_for_shop.select(&:is_master)
end
def index_by_product_id(variants)
variants.inject({}) do |vs, v|
vs[v.product_id] ||= []
vs[v.product_id] << v
vs
end
end
end

View File

@@ -39,6 +39,10 @@ module Admin
admin_inject_json_ams_array opts[:module], "producers", @producers, Api::Admin::IdNameSerializer
end
def admin_inject_inventory_items(opts={module: 'ofn.admin'})
admin_inject_json_ams_array opts[:module], "inventoryItems", @inventory_items, Api::Admin::InventoryItemSerializer
end
def admin_inject_enterprise_permissions
permissions =
{can_manage_shipping_methods: can?(:manage_shipping_methods, @enterprise),
@@ -56,8 +60,8 @@ module Admin
admin_inject_json_ams_array "ofn.admin", "products", @products, Api::Admin::ProductSerializer
end
def admin_inject_tax_categories
admin_inject_json_ams_array "ofn.admin", "tax_categories", @tax_categories, Api::Admin::TaxCategorySerializer
def admin_inject_tax_categories(opts={module: 'ofn.admin'})
admin_inject_json_ams_array opts[:module], "tax_categories", @tax_categories, Api::Admin::TaxCategorySerializer
end
def admin_inject_taxons

View File

@@ -11,26 +11,26 @@ class AngularFormBuilder < ActionView::Helpers::FormBuilder
# @fields_for_record_name --> :collection
# @object.send(@fields_for_record_name).first.class.to_s.underscore --> enterprise_fee
value = "{{ #{@object.send(@fields_for_record_name).first.class.to_s.underscore}.#{method} }}"
value = "{{ #{angular_model(method)} }}"
options.reverse_merge!({'id' => angular_id(method)})
@template.text_field_tag angular_name(method), value, options
end
def ng_hidden_field(method, options = {})
value = "{{ #{@object.send(@fields_for_record_name).first.class.to_s.underscore}.#{method} }}"
value = "{{ #{angular_model(method)} }}"
@template.hidden_field_tag angular_name(method), value, :id => angular_id(method)
end
def ng_select(method, choices, angular_field, options = {})
options.reverse_merge!({'id' => angular_id(method)})
options.reverse_merge!({'id' => angular_id(method), 'ng-model' => "#{angular_model(method)}"})
@template.select_tag angular_name(method), @template.ng_options_for_select(choices, angular_field), options
end
def ng_collection_select(method, collection, value_method, text_method, angular_field, options = {})
options.reverse_merge!({'id' => angular_id(method)})
options.reverse_merge!({'id' => angular_id(method), 'ng-model' => "#{angular_model(method)}"})
@template.select_tag angular_name(method), @template.ng_options_from_collection_for_select(collection, value_method, text_method, angular_field), options
end
@@ -43,4 +43,8 @@ class AngularFormBuilder < ActionView::Helpers::FormBuilder
def angular_id(method)
"#{@object_name}_#{@fields_for_record_name}_attributes_{{ $index }}_#{method}"
end
def angular_model(method)
"#{@object.send(@fields_for_record_name).first.class.to_s.underscore}.#{method}"
end
end

View File

@@ -5,8 +5,7 @@ module AngularFormHelper
container.map do |element|
html_attributes = option_html_attributes(element)
text, value = option_text_and_value(element).map { |item| item.to_s }
selected_attribute = %Q( ng-selected="#{angular_field} == '#{value}'") if angular_field
%(<option value="#{ERB::Util.html_escape(value)}"#{selected_attribute}#{html_attributes}>#{ERB::Util.html_escape(text)}</option>)
%(<option value="#{ERB::Util.html_escape(value)}"#{html_attributes}>#{ERB::Util.html_escape(text)}</option>)
end.join("\n").html_safe
end

View File

@@ -1,4 +1,12 @@
module EnterpriseFeesHelper
def angular_name(method)
"enterprise_fee_set[collection_attributes][{{ $index }}][#{method}]"
end
def angular_id(method)
"enterprise_fee_set_collection_attributes_{{ $index }}_#{method}"
end
def enterprise_fee_type_options
EnterpriseFee::FEE_TYPES.map { |f| [f.capitalize, f] }
end

View File

@@ -7,6 +7,11 @@ class Enterprise < ActiveRecord::Base
preference :shopfront_taxon_order, :string, default: ""
preference :shopfront_order_cycle_order, :string, default: "orders_close_at"
# This is hopefully a temporary measure, pending the arrival of multiple named inventories
# for shops. We need this here to allow hubs to restrict visible variants to only those in
# their inventory if they so choose
preference :product_selection_from_inventory_only, :boolean, default: false
devise :confirmable, reconfirmable: true, confirmation_keys: [ :id, :email ]
handle_asynchronously :send_confirmation_instructions
handle_asynchronously :send_on_create_confirmation_instructions
@@ -35,6 +40,7 @@ class Enterprise < ActiveRecord::Base
has_many :shipping_methods, through: :distributor_shipping_methods
has_many :customers
has_many :billable_periods
has_many :inventory_items
delegate :latitude, :longitude, :city, :state_name, :to => :address
@@ -74,6 +80,7 @@ class Enterprise < ActiveRecord::Base
before_validation :initialize_permalink, if: lambda { permalink.nil? }
before_validation :ensure_owner_is_manager, if: lambda { owner_id_changed? && !owner_id.nil? }
before_validation :ensure_email_set
before_validation :set_unused_address_fields
after_validation :geocode_address
@@ -164,7 +171,7 @@ class Enterprise < ActiveRecord::Base
if user.has_spree_role?('admin')
scoped
else
joins(:enterprise_roles).where('enterprise_roles.user_id = ?', user.id).select("DISTINCT enterprises.*")
joins(:enterprise_roles).where('enterprise_roles.user_id = ?', user.id)
end
}
@@ -246,6 +253,14 @@ class Enterprise < ActiveRecord::Base
strip_url read_attribute(:linkedin)
end
def inventory_variants
if prefers_product_selection_from_inventory_only?
Spree::Variant.visible_for(self)
else
Spree::Variant.not_hidden_for(self)
end
end
def distributed_variants
Spree::Variant.joins(:product).merge(Spree::Product.in_distributor(self)).select('spree_variants.*')
end
@@ -394,6 +409,10 @@ class Enterprise < ActiveRecord::Base
users << owner unless users.include?(owner) || owner.admin?
end
def ensure_email_set
self.email = owner.email if email.blank? && owner.present?
end
def enforce_ownership_limit
unless owner.can_own_more_enterprises?
errors.add(:owner, "^#{owner.email} is not permitted to own any more enterprises (limit is #{owner.enterprise_limit}).")

View File

@@ -9,7 +9,7 @@ class EnterpriseFee < ActiveRecord::Base
calculated_adjustments
attr_accessible :enterprise_id, :fee_type, :name, :tax_category_id, :calculator_type
attr_accessible :enterprise_id, :fee_type, :name, :tax_category_id, :calculator_type, :inherits_tax_category
FEE_TYPES = %w(packing transport admin sales fundraising)
PER_ORDER_CALCULATORS = ['Spree::Calculator::FlatRate', 'Spree::Calculator::FlexiRate']
@@ -18,6 +18,7 @@ class EnterpriseFee < ActiveRecord::Base
validates_inclusion_of :fee_type, :in => FEE_TYPES
validates_presence_of :name
before_save :ensure_valid_tax_category_settings
scope :for_enterprise, lambda { |enterprise| where(enterprise_id: enterprise) }
scope :for_enterprises, lambda { |enterprises| where(enterprise_id: enterprises) }
@@ -57,4 +58,18 @@ class EnterpriseFee < ActiveRecord::Base
:mandatory => mandatory,
:locked => true}, :without_protection => true)
end
private
def ensure_valid_tax_category_settings
# Setting an explicit tax_category removes any inheritance behaviour
# In the absence of any current changes to tax_category, setting
# inherits_tax_category to true will clear the tax_category
if tax_category_id_changed?
self.inherits_tax_category = false if tax_category.present?
elsif inherits_tax_category_changed?
self.tax_category_id = nil if inherits_tax_category?
end
return true
end
end

View File

@@ -6,6 +6,8 @@ class EnterpriseRelationship < ActiveRecord::Base
validates_presence_of :parent_id, :child_id
validates_uniqueness_of :child_id, scope: :parent_id, message: "^That relationship is already established."
after_save :apply_variant_override_permissions
scope :with_enterprises,
joins('LEFT JOIN enterprises AS parent_enterprises ON parent_enterprises.id = enterprise_relationships.parent_id').
joins('LEFT JOIN enterprises AS child_enterprises ON child_enterprises.id = enterprise_relationships.child_id')
@@ -61,10 +63,28 @@ class EnterpriseRelationship < ActiveRecord::Base
def permissions_list=(perms)
perms.andand.each { |name| permissions.build name: name }
if perms.nil?
permissions.destroy_all
else
permissions.where('name NOT IN (?)', perms).destroy_all
perms.map { |name| permissions.find_or_initialize_by_name name }
end
end
def has_permission?(name)
permissions.map(&:name).map(&:to_sym).include? name.to_sym
permissions(:reload).map(&:name).map(&:to_sym).include? name.to_sym
end
private
def apply_variant_override_permissions
variant_overrides = VariantOverride.unscoped.for_hubs(child)
.joins(variant: :product).where("spree_products.supplier_id IN (?)", parent)
if has_permission?(:create_variant_overrides)
variant_overrides.update_all(permission_revoked_at: nil)
else
variant_overrides.update_all(permission_revoked_at: Time.now)
end
end
end

View File

@@ -0,0 +1,14 @@
class InventoryItem < ActiveRecord::Base
attr_accessible :enterprise, :enterprise_id, :variant, :variant_id, :visible
belongs_to :enterprise
belongs_to :variant, class_name: "Spree::Variant"
validates :variant_id, uniqueness: { scope: :enterprise_id }
validates :enterprise_id, presence: true
validates :variant_id, presence: true
validates :visible, inclusion: { in: [true, false], message: "must be true or false" }
scope :visible, where(visible: true)
scope :hidden, where(visible: false)
end

View File

@@ -11,6 +11,8 @@ class OrderCycle < ActiveRecord::Base
validates_presence_of :name, :coordinator_id
preference :product_selection_from_coordinator_inventory_only, :boolean, default: false
scope :active, lambda { where('order_cycles.orders_open_at <= ? AND order_cycles.orders_close_at >= ?', Time.zone.now, Time.zone.now) }
scope :active_or_complete, lambda { where('order_cycles.orders_open_at <= ?', Time.zone.now) }
scope :inactive, lambda { where('order_cycles.orders_open_at > ? OR order_cycles.orders_close_at < ?', Time.zone.now, Time.zone.now) }
@@ -113,6 +115,7 @@ class OrderCycle < ActiveRecord::Base
oc.name = "COPY OF #{oc.name}"
oc.orders_open_at = oc.orders_close_at = nil
oc.coordinator_fee_ids = self.coordinator_fee_ids
oc.preferred_product_selection_from_coordinator_inventory_only = self.preferred_product_selection_from_coordinator_inventory_only
oc.save!
self.exchanges.each { |e| e.clone!(oc) }
oc.reload
@@ -142,12 +145,15 @@ class OrderCycle < ActiveRecord::Base
end
def distributed_variants
# TODO: only used in DistributionChangeValidator, can we remove?
self.exchanges.outgoing.map(&:variants).flatten.uniq.reject(&:deleted?)
end
def variants_distributed_by(distributor)
return Spree::Variant.where("1=0") unless distributor.present?
Spree::Variant.
not_deleted.
merge(distributor.inventory_variants).
joins(:exchanges).
merge(Exchange.in_order_cycle(self)).
merge(Exchange.outgoing).

View File

@@ -125,6 +125,20 @@ class AbilityDecorator
hub_auth && producer_auth
end
can [:admin, :create, :update], InventoryItem do |ii|
next false unless ii.enterprise.present? && ii.variant.andand.product.andand.supplier.present?
hub_auth = OpenFoodNetwork::Permissions.new(user).
variant_override_hubs.
include? ii.enterprise
producer_auth = OpenFoodNetwork::Permissions.new(user).
variant_override_producers.
include? ii.variant.product.supplier
hub_auth && producer_auth
end
can [:admin, :index, :read, :create, :edit, :update_positions, :destroy], Spree::ProductProperty
can [:admin, :index, :read, :create, :edit, :update, :destroy], Spree::Image

View File

@@ -0,0 +1,40 @@
require 'open_food_network/enterprise_fee_calculator'
Spree::Calculator::DefaultTax.class_eval do
private
# Override this method to enable calculation of tax for
# enterprise fees with tax rates where included_in_price = false
def compute_order(order)
matched_line_items = order.line_items.select do |line_item|
line_item.product.tax_category == rate.tax_category
end
line_items_total = matched_line_items.sum(&:total)
# Added this line
calculator = OpenFoodNetwork::EnterpriseFeeCalculator.new(order.distributor, order.order_cycle)
# Added this block, finds relevant fees for each line_item, calculates the tax on them, and returns the total tax
per_item_fees_total = order.line_items.sum do |line_item|
calculator.send(:per_item_enterprise_fee_applicators_for, line_item.variant)
.select { |applicator| (!applicator.enterprise_fee.inherits_tax_category && applicator.enterprise_fee.tax_category == rate.tax_category) ||
(applicator.enterprise_fee.inherits_tax_category && line_item.product.tax_category == rate.tax_category) }
.sum { |applicator| applicator.enterprise_fee.compute_amount(line_item) }
end
# Added this block, finds relevant fees for whole order, calculates the tax on them, and returns the total tax
per_order_fees_total = calculator.send(:per_order_enterprise_fee_applicators_for, order)
.select { |applicator| applicator.enterprise_fee.tax_category == rate.tax_category }
.sum { |applicator| applicator.enterprise_fee.compute_amount(order) }
# round_to_two_places(line_items_total * rate.amount) # Removed this line
# Added this block
[line_items_total, per_item_fees_total, per_order_fees_total].sum do |total|
round_to_two_places(total * rate.amount)
end
end
end

View File

@@ -52,6 +52,13 @@ Spree::Product.class_eval do
scope :with_order_cycles_inner, joins(:variants_including_master => {:exchanges => :order_cycle})
scope :visible_for, lambda { |enterprise|
joins('LEFT OUTER JOIN spree_variants AS o_spree_variants ON (o_spree_variants.product_id = spree_products.id)').
joins('LEFT OUTER JOIN inventory_items AS o_inventory_items ON (o_spree_variants.id = o_inventory_items.variant_id)').
where('o_inventory_items.enterprise_id = (?) AND visible = (?)', enterprise, true).
select('DISTINCT spree_products.*')
}
# -- Scopes
scope :in_supplier, lambda { |supplier| where(:supplier_id => supplier) }

View File

@@ -12,6 +12,7 @@ Spree::Variant.class_eval do
has_many :exchange_variants, dependent: :destroy
has_many :exchanges, through: :exchange_variants
has_many :variant_overrides
has_many :inventory_items
attr_accessible :unit_value, :unit_description, :images_attributes, :display_as, :display_name
accepts_nested_attributes_for :images
@@ -40,6 +41,16 @@ Spree::Variant.class_eval do
where('spree_variants.id IN (?)', order_cycle.variants_distributed_by(distributor))
}
scope :visible_for, lambda { |enterprise|
joins(:inventory_items).where('inventory_items.enterprise_id = (?) AND inventory_items.visible = (?)', enterprise, true)
}
scope :not_hidden_for, lambda { |enterprise|
return where("1=0") unless enterprise.present?
joins("LEFT OUTER JOIN (SELECT * from inventory_items WHERE enterprise_id = #{sanitize enterprise.andand.id}) AS o_inventory_items ON o_inventory_items.variant_id = spree_variants.id")
.where("o_inventory_items.id IS NULL OR o_inventory_items.visible = (?)", true)
}
# Define sope as class method to allow chaining with other scopes filtering id.
# In Rails 3, merging two scopes on the same column will consider only the last scope.
def self.in_distributor(distributor)

View File

@@ -6,6 +6,8 @@ class VariantOverride < ActiveRecord::Base
# 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
default_scope where(permission_revoked_at: nil)
scope :for_hubs, lambda { |hubs|
where(hub_id: hubs)
}

View File

@@ -1,4 +1,7 @@
/ replace_contents "title"
= t(controller.controller_name, :default => controller.controller_name.titleize)
= " - OFN #{t(:administration)}"
- if content_for? :html_title
= yield :html_title
- else
= t(controller.controller_name, :default => controller.controller_name.titleize)
= " - OFN #{t(:administration)}"

View File

@@ -1,3 +1,3 @@
/ insert_bottom "[data-hook='admin_product_sub_tabs']"
= tab :variant_overrides, label: "Overrides", url: main_app.admin_variant_overrides_path, match_path: '/variant_overrides'
= tab :variant_overrides, label: "Inventory", url: main_app.admin_inventory_path, match_path: '/inventory'

View File

@@ -1,31 +0,0 @@
class EnterpriseFeePresenter
def initialize(controller, enterprise_fee, index)
@controller, @enterprise_fee, @index = controller, enterprise_fee, index
end
delegate :id, :enterprise_id, :fee_type, :name, :tax_category_id, :calculator_type, :to => :enterprise_fee
def enterprise_fee
@enterprise_fee
end
def enterprise_name
@enterprise_fee.enterprise.andand.name
end
def calculator_description
@enterprise_fee.calculator.andand.description
end
def calculator_settings
result = nil
@controller.send(:with_format, :html) do
result = @controller.render_to_string :partial => 'admin/enterprise_fees/calculator_settings', :locals => {:enterprise_fee => @enterprise_fee, :index => @index}
end
result.gsub('[0]', '[{{ $index }}]').gsub('_0_', '_{{ $index }}_')
end
end

View File

@@ -0,0 +1,11 @@
class Api::Admin::CalculatorSerializer < ActiveModel::Serializer
attributes :name, :description
def name
object.name
end
def description
object.description
end
end

View File

@@ -1,5 +1,5 @@
class Api::Admin::EnterpriseFeeSerializer < ActiveModel::Serializer
attributes :id, :enterprise_id, :fee_type, :name, :tax_category_id, :calculator_type
attributes :id, :enterprise_id, :fee_type, :name, :tax_category_id, :inherits_tax_category, :calculator_type
attributes :enterprise_name, :calculator_description, :calculator_settings
def enterprise_name
@@ -11,6 +11,8 @@ class Api::Admin::EnterpriseFeeSerializer < ActiveModel::Serializer
end
def calculator_settings
return nil unless options[:include_calculators]
result = nil
options[:controller].send(:with_format, :html) do

View File

@@ -2,6 +2,7 @@ class Api::Admin::EnterpriseSerializer < ActiveModel::Serializer
attributes :name, :id, :is_primary_producer, :is_distributor, :sells, :category, :payment_method_ids, :shipping_method_ids
attributes :producer_profile_only, :email, :long_description, :permalink
attributes :preferred_shopfront_message, :preferred_shopfront_closed_message, :preferred_shopfront_taxon_order, :preferred_shopfront_order_cycle_order
attributes :preferred_product_selection_from_inventory_only
attributes :owner, :users
has_one :owner, serializer: Api::Admin::UserSerializer

View File

@@ -4,14 +4,35 @@ class Api::Admin::ExchangeSerializer < ActiveModel::Serializer
has_many :enterprise_fees, serializer: Api::Admin::BasicEnterpriseFeeSerializer
def variants
permitted = Spree::Variant.where("1=0")
if object.incoming
permitted = OpenFoodNetwork::OrderCyclePermissions.new(options[:current_user], object.order_cycle).
visible_variants_for_incoming_exchanges_from(object.sender)
variants = object.incoming? ? visible_incoming_variants : visible_outgoing_variants
Hash[ object.variants.merge(variants).map { |v| [v.id, true] } ]
end
private
def visible_incoming_variants
if object.order_cycle.prefers_product_selection_from_coordinator_inventory_only?
permitted_incoming_variants.visible_for(object.order_cycle.coordinator)
else
permitted = OpenFoodNetwork::OrderCyclePermissions.new(options[:current_user], object.order_cycle).
visible_variants_for_outgoing_exchanges_to(object.receiver)
permitted_incoming_variants
end
Hash[ object.variants.merge(permitted).map { |v| [v.id, true] } ]
end
def visible_outgoing_variants
if object.receiver.prefers_product_selection_from_inventory_only?
permitted_outgoing_variants.visible_for(object.receiver)
else
permitted_outgoing_variants.not_hidden_for(object.receiver)
end
end
def permitted_incoming_variants
OpenFoodNetwork::OrderCyclePermissions.new(options[:current_user], object.order_cycle).
visible_variants_for_incoming_exchanges_from(object.sender)
end
def permitted_outgoing_variants
OpenFoodNetwork::OrderCyclePermissions.new(options[:current_user], object.order_cycle)
.visible_variants_for_outgoing_exchanges_to(object.receiver)
end
end

View File

@@ -6,7 +6,11 @@ class Api::Admin::ForOrderCycle::EnterpriseSerializer < ActiveModel::Serializer
attributes :is_primary_producer, :is_distributor, :sells
def issues_summary_supplier
OpenFoodNetwork::EnterpriseIssueValidator.new(object).issues_summary confirmation_only: true
issues = OpenFoodNetwork::EnterpriseIssueValidator.new(object).issues_summary confirmation_only: true
if issues.nil? && products.empty?
issues = "no products in inventory"
end
issues
end
def issues_summary_distributor
@@ -18,8 +22,22 @@ class Api::Admin::ForOrderCycle::EnterpriseSerializer < ActiveModel::Serializer
end
def supplied_products
objects = object.supplied_products.not_deleted
serializer = Api::Admin::ForOrderCycle::SuppliedProductSerializer
ActiveModel::ArraySerializer.new(objects, each_serializer: serializer)
ActiveModel::ArraySerializer.new(products, each_serializer: serializer, order_cycle: order_cycle)
end
private
def products
return @products unless @products.nil?
@products = if order_cycle.prefers_product_selection_from_coordinator_inventory_only?
object.supplied_products.not_deleted.visible_for(order_cycle.coordinator)
else
object.supplied_products.not_deleted
end
end
def order_cycle
options[:order_cycle]
end
end

View File

@@ -14,8 +14,17 @@ class Api::Admin::ForOrderCycle::SuppliedProductSerializer < ActiveModel::Serial
end
def variants
object.variants.map do |variant|
{ id: variant.id, label: variant.full_name }
variants = if order_cycle.prefers_product_selection_from_coordinator_inventory_only?
object.variants.visible_for(order_cycle.coordinator)
else
object.variants
end
variants.map { |variant| { id: variant.id, label: variant.full_name } }
end
private
def order_cycle
options[:order_cycle]
end
end

View File

@@ -0,0 +1,3 @@
class Api::Admin::InventoryItemSerializer < ActiveModel::Serializer
attributes :id, :enterprise_id, :variant_id, :visible
end

View File

@@ -58,7 +58,14 @@ class Api::Admin::OrderCycleSerializer < ActiveModel::Serializer
permissions = OpenFoodNetwork::OrderCyclePermissions.new(options[:current_user], object)
enterprises = permissions.visible_enterprises
enterprises.each do |enterprise|
variants = permissions.visible_variants_for_outgoing_exchanges_to(enterprise).pluck(:id)
# This is hopefully a temporary measure, pending the arrival of multiple named inventories
# for shops. We need this here to allow hubs to restrict visible variants to only those in
# their inventory if they so choose
variants = if enterprise.prefers_product_selection_from_inventory_only?
permissions.visible_variants_for_outgoing_exchanges_to(enterprise).visible_for(enterprise)
else
permissions.visible_variants_for_outgoing_exchanges_to(enterprise).not_hidden_for(enterprise)
end.pluck(:id)
visible[enterprise.id] = variants if variants.any?
end
visible

View File

@@ -41,7 +41,7 @@ class Api::CachedEnterpriseSerializer < ActiveModel::Serializer
attributes :name, :id, :description, :latitude, :longitude,
:long_description, :website, :instagram, :linkedin, :twitter,
:facebook, :is_primary_producer, :is_distributor, :phone, :visible,
:email, :hash, :logo, :promo_image, :path, :pickup, :delivery,
:email_address, :hash, :logo, :promo_image, :path, :pickup, :delivery,
:icon, :icon_font, :producer_icon_font, :category, :producers, :hubs
attributes :taxons, :supplied_taxons
@@ -67,8 +67,8 @@ class Api::CachedEnterpriseSerializer < ActiveModel::Serializer
services ? services[:delivery] : false
end
def email
object.email.to_s.reverse
def email_address
object.email_address.to_s.reverse
end
def hash

View File

@@ -34,6 +34,7 @@ end
class Api::CachedProductSerializer < ActiveModel::Serializer
#cached
#delegate :cache_key, to: :object
include ActionView::Helpers::SanitizeHelper
attributes :id, :name, :permalink, :count_on_hand
attributes :on_demand, :group_buy, :notes, :description
@@ -48,6 +49,10 @@ class Api::CachedProductSerializer < ActiveModel::Serializer
has_many :images, serializer: Api::ImageSerializer
has_one :supplier, serializer: Api::IdSerializer
def description
strip_tags object.description
end
def properties_with_values
object.properties_including_inherited
end

View File

@@ -0,0 +1,3 @@
= admin_inject_json_ams_array "admin.enterpriseFees", "enterprises", @enterprises, Api::Admin::IdNameSerializer
= admin_inject_tax_categories(module: "admin.enterpriseFees")
= admin_inject_json_ams_array "admin.enterpriseFees", "calculators", @calculators, Api::Admin::CalculatorSerializer

View File

@@ -1,8 +1,9 @@
= content_for :page_title do
Enterprise Fees
= ng_form_for @enterprise_fee_set, :url => main_app.bulk_update_admin_enterprise_fees_path, :html => {'ng-app' => 'enterprise_fees', 'ng-controller' => 'AdminEnterpriseFeesCtrl'} do |enterprise_fee_set_form|
= ng_form_for @enterprise_fee_set, :url => main_app.bulk_update_admin_enterprise_fees_path, :html => {'ng-app' => 'admin.enterpriseFees', 'ng-controller' => 'enterpriseFeesCtrl'} do |enterprise_fee_set_form|
= hidden_field_tag 'enterprise_id', @enterprise.id if @enterprise
= render "admin/enterprise_fees/data"
= render :partial => 'spree/shared/error_messages', :locals => { :target => @enterprise_fee_set }
%input.search{'ng-model' => 'query', 'placeholder' => 'Search'}
@@ -19,14 +20,20 @@
%th.actions
%tbody
= enterprise_fee_set_form.ng_fields_for :collection do |f|
%tr{'ng-repeat' => 'enterprise_fee in enterprise_fees | filter:query'}
%tr{ ng: { repeat: 'enterprise_fee in enterprise_fees | filter:query' } }
%td
= f.ng_hidden_field :id
= f.ng_collection_select :enterprise_id, @enterprises, :id, :name, 'enterprise_fee.enterprise_id', include_blank: true
%ofn-select{ :id => angular_id(:enterprise_id), data: 'enterprises', include_blank: true, ng: { model: 'enterprise_fee.enterprise_id' } }
%input{ type: "hidden", name: angular_name(:enterprise_id), ng: { value: "enterprise_fee.enterprise_id" } }
%td= f.ng_select :fee_type, enterprise_fee_type_options, 'enterprise_fee.fee_type'
%td= f.ng_text_field :name, { placeholder: 'e.g. packing fee' }
%td= f.ng_collection_select :tax_category_id, @tax_categories, :id, :name, 'enterprise_fee.tax_category_id', include_blank: true
%td= f.ng_collection_select :calculator_type, @calculators, :name, :description, 'enterprise_fee.calculator_type', {'class' => 'calculator_type', 'ng-model' => 'calculatorType', 'spree-ensure-calculator-preferences-match-type' => "1"}
%td
%ofn-select{ :id => angular_id(:tax_category_id), data: 'tax_categories', include_blank: true, ng: { model: 'enterprise_fee.tax_category_id' } }
%input{ type: "hidden", name: angular_name(:tax_category_id), 'watch-tax-category' => true }
%input{ type: "hidden", name: angular_name(:inherits_tax_category), ng: { value: "enterprise_fee.inherits_tax_category" } }
%td
%ofn-select.calculator_type{ :id => angular_id(:calculator_type), ng: { model: 'enterprise_fee.calculator_type' }, value_attr: 'name', text_attr: 'description', data: 'calculators', 'spree-ensure-calculator-preferences-match-type' => true }
%input{ type: "hidden", name: angular_name(:calculator_type), ng: { value: "enterprise_fee.calculator_type" } }
%td{'ng-bind-html-unsafe-compiled' => 'enterprise_fee.calculator_settings'}
%td.actions{'spree-delete-resource' => "1"}

View File

@@ -1,11 +0,0 @@
r.list_of :enterprise_fees, @presented_collection do
r.element :id
r.element :enterprise_id
r.element :enterprise_name
r.element :fee_type
r.element :name
r.element :tax_category_id
r.element :calculator_type
r.element :calculator_description
r.element :calculator_settings if @include_calculators
end

View File

@@ -1,9 +1,9 @@
= content_for :page_title do
Enterprise Groups
= content_for :page_actions do
%li#new_enterprise_group_link
= button_link_to "New Enterprise Group", main_app.new_admin_enterprise_group_path, :icon => 'add', :id => 'admin_new_enterprise_group_link'
- if admin_user?
= content_for :page_actions do
%li= button_link_to "New Enterprise Group", main_app.new_admin_enterprise_group_path
%table.index#listing_enterprise_groups
%thead

View File

@@ -1,11 +1,11 @@
%tr
%td
%select{name: "enterprise_relationship_parent_id", "ng-model" => "parent_id", "ng-options" => "e.id as e.name for e in Enterprises.my_enterprises"}
%select.select2.fullwidth{id: "enterprise_relationship_parent_id", "ng-model" => "parent_id", "ng-options" => "e.id as e.name for e in Enterprises.my_enterprises"}
%td
permits
%td
%select{name: "enterprise_relationship_child_id", "ng-model" => "child_id", "ng-options" => "e.id as e.name for e in Enterprises.all_enterprises"}
%select.select2.fullwidth{id: "enterprise_relationship_child_id", "ng-model" => "child_id", "ng-options" => "e.id as e.name for e in Enterprises.all_enterprises"}
%td
%label
%input{type: "checkbox", ng: {checked: "allPermissionsChecked()", click: "checkAllPermissions()"}}

View File

@@ -9,6 +9,12 @@
= render 'search_input'
%table#enterprise-relationships
%colgroup
%col{ style: "width: 30%" }
%col{ style: "width: 5%" }
%col{ style: "width: 30%" }
%col{ style: "width: 30%" }
%col{ style: "width: 5%" }
%tbody
= render 'form'
= render 'enterprise_relationship'

View File

@@ -47,6 +47,10 @@
%legend Enterprise Fees
= render 'admin/enterprises/form/enterprise_fees', f: f
%fieldset.alpha.no-border-bottom{ ng: { show: "menu.selected.name=='Inventory Settings'" } }
%legend Inventory Settings
= render 'admin/enterprises/form/inventory_settings', f: f
%fieldset.alpha.no-border-bottom{ ng: { show: "menu.selected.name=='Shop Preferences'" } }
%legend Shop Preferences
= render 'admin/enterprises/form/shop_preferences', f: f

View File

@@ -1,6 +1,7 @@
.row
.three.columns.alpha
= f.label :name
%span.required *
.nine.columns.omega
= f.text_field :name, { placeholder: "eg. Professor Plum's Biodynamic Truffles", class: "fullwidth" }
@@ -8,6 +9,7 @@
.row
.three.columns.alpha
=f.label :owner_id, 'Owner'
%span.required *
%div{'ofn-with-tip' => "The primary user responsible for this enterprise."}
%a What's this?
.nine.columns.omega
@@ -50,13 +52,9 @@
= f.text_field :contact, { placeholder: "eg. Gustav Plum"}
.row
.alpha.three.columns
= f.label :email
= f.label :email_address
.omega.nine.columns
= f.text_field :email, { placeholder: "eg. gustav@truffles.com", "ng-model" => "Enterprise.email" }
.alert-box
%i.icon-info-sign
If we don't recognise this email address we'll send you a confirmation email to make sure it belongs to you. You'll need to use the link in the email we send to fully activate your new enterprise.
%a.close{ href: "" } ×
= f.text_field :email_address, { placeholder: "eg. gustav@truffles.com", "ng-model" => "Enterprise.email_address" }
.row
.alpha.three.columns
= f.label :phone
@@ -72,6 +70,7 @@
.row
.three.columns.alpha
= af.label :address1
%span.required *
.nine.columns.omega
= af.text_field :address1, { placeholder: "eg. 123 High Street"}
.row
@@ -84,6 +83,7 @@
= af.label :city, 'Suburb'
\/
= af.label :zipcode, 'Postcode'
%span.required *
.four.columns
= af.text_field :city, { placeholder: "eg. Northcote"}
.five.columns.omega

View File

@@ -1,11 +1,3 @@
-if @enterprise.pending_any_confirmation?
.alert-box
- email = @enterprise.confirmed? ? @enterprise.unconfirmed_email : @enterprise.email
Email confirmation is pending.
We've sent a confirmation email to
%strong= "#{email}."
= link_to('Resend', main_app.enterprise_confirmation_path(enterprise: { id: @enterprise.id, email: email } ), method: :post)
%a.close{ href: "#" } ×
.row
.alpha.three.columns
= f.label :contact, 'Name'
@@ -13,15 +5,9 @@
= f.text_field :contact, { placeholder: "eg. Gustav Plum"}
.row
.alpha.three.columns
= f.label :email
%span.required *
= f.label :email_address
.omega.eight.columns
= f.text_field :email, { placeholder: "eg. gustav@truffles.com", "ng-model" => "Enterprise.email" }
.row{ ng: { hide: "pristineEmail == null || pristineEmail == Enterprise.email"} }
.alpha.three.columns
&nbsp;
.omega.eight.columns
Note: A new email address may need to be confirmed prior to use
= f.text_field :email_address, { placeholder: "eg. gustav@truffles.com" }
.row
.alpha.three.columns
= f.label :phone

View File

@@ -0,0 +1,18 @@
-# This is hopefully a temporary measure, pending the arrival of multiple named inventories
-# for shops. We need this here to allow hubs to restrict visible variants to only those in
-# their inventory if they so choose
.row
.alpha.eleven.columns
You may opt to manage stock levels and prices in via your
= succeed "." do
%strong
%a{href: main_app.admin_inventory_path } inventory
If you are using the inventory tool, you can select whether new products added by your suppliers need to be added to your inventory before they can be stocked. If you are not using your inventory to manage your products you should select the 'recommended' option below:
.row
.alpha.eleven.columns
.five.columns.alpha
= radio_button :enterprise, :preferred_product_selection_from_inventory_only, "0", { 'ng-model' => 'Enterprise.preferred_product_selection_from_inventory_only' }
= label :enterprise, :preferred_product_selection_from_inventory_only, "New products can be put into my shopfront (recommended)"
.six.columns.omega
= radio_button :enterprise, :preferred_product_selection_from_inventory_only, "1", { 'ng-model' => 'Enterprise.preferred_product_selection_from_inventory_only' }
= label :enterprise, :preferred_product_selection_from_inventory_only, "New products must be added to my inventory before they can be put into my shopfront"

View File

@@ -1,6 +1,15 @@
- owner_email = @enterprise.andand.owner.andand.email || ""
- full_permissions = (spree_current_user.admin? || spree_current_user == @enterprise.andand.owner)
-if @enterprise.pending_any_confirmation?
.alert-box
- email = @enterprise.confirmed? ? @enterprise.unconfirmed_email : @enterprise.email
Email confirmation is pending.
We've sent a confirmation email to
%strong= "#{email}."
= link_to('Resend', main_app.enterprise_confirmation_path(enterprise: { id: @enterprise.id, email: email } ), method: :post)
%a.close{ href: "#" } ×
.row
.three.columns.alpha
=f.label :owner_id, 'Owner'
@@ -14,6 +23,24 @@
- else
= owner_email
.row
.three.columns.alpha
= f.label :email, 'Notifications'
- if full_permissions
%span.required *
.with-tip{'data-powertip' => "Notifications about orders will be send to this email address."}
%a What's this?
.eight.columns.omega
- if full_permissions
= f.text_field :email, { placeholder: "eg. gustav@truffles.com", "ng-model" => "Enterprise.email" }
- else
= @enterprise.email
.row{ ng: { hide: "pristineEmail == null || pristineEmail == Enterprise.email"} }
.alpha.three.columns
&nbsp;
.omega.eight.columns
Note: A new email address may need to be confirmed prior to use
.row
.three.columns.alpha
=f.label :user_ids, 'Managers'

View File

@@ -0,0 +1,22 @@
.row
.alpha.omega.sixteen.columns
%h3 Advanced Settings
= form_for [main_app, :admin, @order_cycle] do |f|
.row
.six.columns.alpha
= f.label "enterprise_preferred_product_selection_from_coordinator_inventory_only", t('admin.order_cycle.choose_products_from')
.with-tip{'data-powertip' => "You can opt to restrict all available products (both incoming and outgoing), to only those in #{@order_cycle.coordinator.name}'s inventory."}
%a What's this?
.four.columns
= f.radio_button :preferred_product_selection_from_coordinator_inventory_only, true
= f.label :preferred_product_selection_from_coordinator_inventory_only, "Coordinator's Inventory Only"
.six.columns.omega
= f.radio_button :preferred_product_selection_from_coordinator_inventory_only, false
= f.label :preferred_product_selection_from_coordinator_inventory_only, "All Available Products"
.row
.sixteen.columns.alpha.omega.text-center
%input{ type: 'submit', value: 'Save and Reload Page' }
or
%a{ href: "#", onClick: "toggleSettings()" } Close

View File

@@ -9,14 +9,14 @@
.exchange-product{'ng-repeat' => 'product in supplied_products | filter:productSuppliedToOrderCycle | visibleProducts:exchange:order_cycle.visible_variants_for_outgoing_exchanges | orderBy:"name"' }
.exchange-product-details
%label
= check_box_tag 'order_cycle_outgoing_exchange_{{ $parent.$index }}_variants_{{ product.master_id }}', 1, 1, 'ng-hide' => 'product.variants.length > 0', 'ng-model' => 'exchange.variants[product.master_id]', 'id' => 'order_cycle_outgoing_exchange_{{ $parent.$index }}_variants_{{ product.master_id }}',
'ng-disabled' => 'product.variants.length > 0 || !order_cycle.editable_variants_for_outgoing_exchanges.hasOwnProperty(exchange.enterprise_id) || order_cycle.editable_variants_for_outgoing_exchanges[exchange.enterprise_id].indexOf(product.master_id) < 0'
-# MASTER_VARIANTS: No longer required
-# = check_box_tag 'order_cycle_outgoing_exchange_{{ $parent.$index }}_variants_{{ product.master_id }}', 1, 1, 'ng-hide' => 'product.variants.length > 0', 'ng-model' => 'exchange.variants[product.master_id]', 'id' => 'order_cycle_outgoing_exchange_{{ $parent.$index }}_variants_{{ product.master_id }}',
-# 'ng-disabled' => 'product.variants.length > 0 || !order_cycle.editable_variants_for_outgoing_exchanges.hasOwnProperty(exchange.enterprise_id) || order_cycle.editable_variants_for_outgoing_exchanges[exchange.enterprise_id].indexOf(product.master_id) < 0'
%img{'ng-src' => '{{ product.image_url }}'}
.name {{ product.name }}
.supplier {{ product.supplier_name }}
-# if we ever need to filter variants within a product using visibility permissions, we can use this filter: visibleVariants:exchange:order_cycle.visible_variants_for_outgoing_exchanges
.exchange-product-variant{'ng-repeat' => 'variant in product.variants | filter:variantSuppliedToOrderCycle'}
.exchange-product-variant{'ng-repeat' => 'variant in product.variants | visibleVariants:exchange:order_cycle.visible_variants_for_outgoing_exchanges | filter:variantSuppliedToOrderCycle'}
%label
= check_box_tag 'order_cycle_outgoing_exchange_{{ $parent.$parent.$index }}_variants_{{ variant.id }}', 1, 1, 'ng-model' => 'exchange.variants[variant.id]', 'id' => 'order_cycle_outgoing_exchange_{{ $parent.$parent.$index }}_variants_{{ variant.id }}',
'ng-disabled' => '!order_cycle.editable_variants_for_outgoing_exchanges.hasOwnProperty(exchange.enterprise_id) || order_cycle.editable_variants_for_outgoing_exchanges[exchange.enterprise_id].indexOf(variant.id) < 0'

View File

@@ -1,9 +1,27 @@
- if can? :notify_producers, @order_cycle
= content_for :page_actions do
- content_for :page_actions do
:javascript
function toggleSettings(){
if( $('#advanced_settings').is(":visible") ){
$('button#toggle_settings i').switchClass("icon-chevron-up","icon-chevron-down")
}
else {
$('button#toggle_settings i').switchClass("icon-chevron-down","icon-chevron-up")
}
$("#advanced_settings").slideToggle()
}
- if can? :notify_producers, @order_cycle
%li
= button_to "Notify producers", main_app.notify_producers_admin_order_cycle_path, :id => 'admin_notify_producers', :confirm => 'Are you sure?'
%li
%button#toggle_settings{ onClick: 'toggleSettings()' }
Advanced Settings
%i.icon-chevron-down
#advanced_settings{ hidden: true }
= render partial: "/admin/order_cycles/advanced_settings"
%h1 Edit Order Cycle

View File

@@ -1,7 +1,6 @@
.three.columns
.ofn-drop-down#bulk-actions-dropdown{ 'ng-controller' => "DropDownCtrl" }
%span.icon-check &nbsp; 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 }}
.ofn-drop-down#bulk-actions-dropdown
%span.icon-check= "&nbsp; #{t('admin.actions')}".html_safe
%span{ 'ng-class' => "expanded && 'icon-caret-up' || !expanded && 'icon-caret-down'" }
.menu{ 'ng-show' => "expanded" }
.menu_item{ 'ng-repeat' => "action in bulkActions", 'ng-click' => "$eval(action.callback)(filteredLineItems)", 'close-on-click' => true }
%span.name {{ action.name }}

View File

@@ -1,8 +1,7 @@
%div.three.columns.omega
%div.ofn-drop-down.right#columns-dropdown{ 'ng-controller' => "DropDownCtrl" }
%span{ :class => 'icon-reorder' } &nbsp; 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 && "&#10003;" || !column.visible && "&nbsp;" }}
%span.two.columns.omega {{column.name }}
.ofn-drop-down.right#columns-dropdown
%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" }, toggle: { column: true } }
%span.check
%span.name {{column.name }}

View File

@@ -0,0 +1,7 @@
.ofn-drop-down#views-dropdown
%span{ :class => 'icon-eye-open' }= "&nbsp; #{t('admin.viewing', current_view_name: '{{ currentView().name }}')}".html_safe
%span{ 'ng-class' => "expanded && 'icon-caret-up' || !expanded && 'icon-caret-down'" }
%div.menu{ 'ng-show' => "expanded" }
%div.menu_item{ ng: { repeat: "(viewKey, view) in views" }, toggle: { view: true }, 'close-on-click' => true }
%span.check
%span.name {{ view.name }}

View File

@@ -0,0 +1,15 @@
%hr.divider.sixteen.columns.alpha.omega{ ng: { show: 'hub_id && products.length > 0' } }
.controls.sixteen.columns.alpha.omega{ ng: { show: 'hub_id && products.length > 0' } }
.eight.columns.alpha
= render 'admin/shared/bulk_actions_dropdown'
= render 'admin/shared/views_dropdown'
%span.text-big.with-tip.icon-question-sign{ ng: { show: 'views.inventory.visible' } , data: { powertip: "#{t('admin.inventory.inventory_powertip')}" } }
%span.text-big.with-tip.icon-question-sign{ ng: { show: 'views.hidden.visible' } , data: { powertip: "#{t('admin.inventory.hidden_powertip')}" } }
%span.text-big.with-tip.icon-question-sign{ ng: { show: 'views.new.visible' } , data: { powertip: "#{t('admin.inventory.new_powertip')}" } }
.four.columns &nbsp;
.four.columns.omega{ ng: { show: 'views.new.visible' } }
%button.fullwidth{ type: 'button', ng: { click: "selectView('inventory')" } }
%i.icon-chevron-left
Back to my inventory
.four.columns.omega{ng: { show: 'views.inventory.visible' } }
= render 'admin/shared/columns_dropdown'

View File

@@ -3,3 +3,4 @@
= admin_inject_hub_permissions
= admin_inject_producers module: 'admin.variantOverrides'
= admin_inject_variant_overrides
= admin_inject_inventory_items module: 'admin.variantOverrides'

View File

@@ -1,17 +1,17 @@
.filters.sixteen.columns.alpha
.filter.four.columns.alpha
%label{ :for => 'query', ng: {class: '{disabled: !hub.id}'} }Quick Search
%label{ :for => 'query', ng: {class: '{disabled: !hub_id}'} }=t('admin.quick_search')
%br
%input.fullwidth{ :type => "text", :id => 'query', ng: { model: 'query', disabled: '!hub.id'} }
%input.fullwidth{ :type => "text", :id => 'query', ng: { model: 'query', disabled: '!hub_id'} }
.two.columns &nbsp;
.filter_select.four.columns
%label{ :for => 'hub_id', ng: { bind: 'hub_id ? "Shop" : "Select a shop"' } }
%label{ :for => 'hub_id', ng: { bind: "hub_id ? '#{t('admin.shop')}' : '#{t('admin.inventory.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()' } }
%select.select2.fullwidth#hub_id{ 'ng-model' => 'hub_id', name: 'hub_id', ng: { options: 'hub.id as hub.name for (id, hub) in hubs' } }
.filter_select.four.columns
%label{ :for => 'producer_filter', ng: {class: '{disabled: !hub.id}'} }Producer
%label{ :for => 'producer_filter', ng: {class: '{disabled: !hub_id}'} }=t('admin.producer')
%br
%input.ofn-select2.fullwidth{ :id => 'producer_filter', type: 'number', data: 'producers', blank: "{id: 0, name: 'All'}", ng: { model: 'producerFilter', disabled: '!hub.id' } }
%input.ofn-select2.fullwidth{ :id => 'producer_filter', type: 'number', data: 'producers', blank: "{id: 0, name: 'All'}", ng: { model: 'producerFilter', disabled: '!hub_id' } }
-# .filter_select{ :class => "three columns" }
-# %label{ :for => 'distributor_filter' }Hub
-# %br
@@ -23,4 +23,4 @@
.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'} }
%input.red.fullwidth{ :type => 'button', :id => 'clear_all_filters', :value => "#{t('admin.clear_all')}", ng: { click: "resetSelectFilters()", disabled: '!hub_id'} }

View File

@@ -1,4 +1,8 @@
- content_for :html_title do
= t("admin.inventory.title")
- content_for :page_title do
Override Product Details
%h1.page-title= t("admin.inventory.title")
%a.with-tip{ 'data-powertip' => "#{t("admin.inventory.description")}" }=t('admin.whats_this')
= render :partial => 'spree/admin/shared/product_sub_menu'

View File

@@ -0,0 +1,22 @@
%div{ ng: { show: 'views.hidden.visible' } }
%table#hidden-products{ ng: { show: 'filteredProducts.length > 0' } }
%col.producer{ width: "20%" }
%col.product{ width: "20%" }
%col.variant{ width: "30%" }
%col.add{ width: "15%" }
%thead
%tr
%th.producer=t('admin.producer')
%th.product=t('admin.product')
%th.variant=t('(admin.variant')
%th.add=t('admin.inventory.add')
%tbody{ bindonce: true, ng: { repeat: 'product in filteredProducts | limitTo:productLimit' } }
%tr{ id: "v_{{variant.id}}", ng: { repeat: 'variant in product.variants | inventoryVariants:hub_id:views' } }
%td.producer{ bo: { bind: 'producersByID[product.producer_id].name'} }
%td.product{ bo: { bind: 'product.name'} }
%td.variant
%span{ bo: { bind: 'variant.display_name || ""'} }
.variant-override-unit{ bo: { bind: 'variant.unit_to_display'} }
%td.add
%button.fullwidth.icon-plus{ ng: { click: "setVisibility(hub_id,variant.id,true)" } }
= t('admin.inventory.add')

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