Merge branch 'master' into 2-0-stable

* master: (206 commits)
  embedded groups layout changes
  embedded groups initial test
  Allow html requests for OrderCycleController#update
  Move applicator calls to OrderCycleForm
  Refactor OrderCycleForm to make logic clearer
  Extract schedule syncing logic into OrderCycleForm
  Add basic specs for OrderCyclesController#create
  Add basic OrderCycleForm to handle create/update logic
  Remove unnecessary respond_to blocks from OrderCyclesController
  Include admin users as managers on new enterprises
  Remove obsolete goWithoutHashFragments
  Simplify Navigation.go, not preserving hash fragments
  Only show change warning for open order cycles
  Use a SubscriptionsCount query object to provide counts to IndexOrderCycleSerializer
  Preload subscription counts for serialization in order cycle collection actions
  Request the subscription count for change warning each time, don't cache
  Ask user to confirm oc date change for open order cycles with subsciptions
  Fix ordering of Gemfile.lock
  Add rack-rewrite to handle redirects
  Renames product bulk edit action to index
  ...
This commit is contained in:
Pau Perez
2018-06-22 13:23:18 +02:00
307 changed files with 10996 additions and 4157 deletions

View File

@@ -36,6 +36,7 @@
//= require ./orders/orders
//= require ./order_cycles/order_cycles
//= require ./payment_methods/payment_methods
//= require ./product_import/product_import
//= require ./products/products
//= require ./resources/resources
//= require ./shipping_methods/shipping_methods

View File

@@ -28,6 +28,7 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout
$scope.filterTaxons = [{id: "0", name: ""}].concat $scope.taxons
$scope.producerFilter = "0"
$scope.categoryFilter = "0"
$scope.importDateFilter = "0"
$scope.products = BulkProducts.products
$scope.filteredProducts = []
$scope.currentFilters = []
@@ -43,7 +44,7 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout
.catch (message) ->
$scope.api_error_msg = message
$scope.$watchCollection '[query, producerFilter, categoryFilter]', ->
$scope.$watchCollection '[query, producerFilter, categoryFilter, importDateFilter]', ->
$scope.limit = 15 # Reset limit whenever searching
$scope.fetchProducts = ->
@@ -52,6 +53,9 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout
$scope.resetProducts()
$scope.loading = false
$timeout ->
if $scope.showLatestImport
$scope.importDateFilter = $scope.importDates[1].id
$scope.resetProducts = ->
DirtyProducts.clear()
@@ -91,6 +95,7 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout
$scope.query = ""
$scope.producerFilter = "0"
$scope.categoryFilter = "0"
$scope.importDateFilter = "0"
$scope.editWarn = (product, variant) ->
if (DirtyProducts.count() > 0 and confirm(t("unsaved_changes_confirmation"))) or (DirtyProducts.count() == 0)

View File

@@ -1,2 +0,0 @@
angular.module("ofn.admin").controller "enterprisesDashboardCtrl", ($scope) ->
$scope.activeTab = "hubs"

View File

@@ -0,0 +1,6 @@
angular.module("ofn.admin").directive "select2NoSearch", ($timeout) ->
restrict: 'CA'
link: (scope, element, attrs) ->
$timeout ->
element.select2
minimumResultsForSearch: Infinity

View File

@@ -0,0 +1,18 @@
# Used in enterprise new and edit forms to reset the state when the country is changed
angular.module("admin.enterprises").controller 'countryCtrl', ($scope, availableCountries) ->
$scope.countries = availableCountries
$scope.countriesById = $scope.countries.reduce (obj, country) ->
obj[country.id] = country
obj
, {}
$scope.$watch 'Enterprise.address.country_id', (newID, oldID) ->
$scope.clearState() unless $scope.addressStateMatchesCountry()
$scope.clearState = ->
$scope.Enterprise.address.state_id = null
$scope.addressStateMatchesCountry = ->
$scope.countriesById[$scope.Enterprise.address.country_id].states.some (state) ->
state.id == $scope.Enterprise.address.state_id

View File

@@ -11,6 +11,9 @@ angular.module("admin.enterprises")
$scope.$watch 'enterprise_form.$dirty', (newValue) ->
StatusMessage.display 'notice', t('admin.unsaved_changes') if newValue
$scope.$watch 'newManager', (newValue) ->
$scope.addManager($scope.newManager) if newValue
$scope.setFormDirty = ->
$scope.$apply ->
$scope.enterprise_form.$setDirty()
@@ -47,7 +50,7 @@ angular.module("admin.enterprises")
email: manager.email
confirmed: manager.confirmed
if (user for user in $scope.Enterprise.users when user.id == manager.id).length == 0
$scope.Enterprise.users.push manager
$scope.Enterprise.users.unshift(manager)
$scope.enterprise_form?.$setDirty()
else
alert ("#{manager.email}" + " " + t("is_already_manager"))

View File

@@ -0,0 +1,5 @@
angular.module("admin.enterprises").controller 'NewEnterpriseController', ($scope, defaultCountryID) ->
$scope.Enterprise =
address:
country_id: defaultCountryID
state_id: null

View File

@@ -0,0 +1,4 @@
angular.module("ofn.admin").filter "importDate", ($filter) ->
return (products, importDate) ->
return products if importDate == "0"
$filter('filter')( products, { import_date: importDate } )

View File

@@ -0,0 +1,23 @@
angular.module("admin.orderCycles").directive "changeWarning", (ConfirmDialog) ->
restrict: "A"
scope:
orderCycle: '=changeWarning'
link: (scope, element, attrs) ->
acknowledged = false
cancel = 'admin.order_cycles.date_warning.cancel'
proceed = 'admin.order_cycles.date_warning.proceed'
msg = 'admin.order_cycles.date_warning.msg'
options = { cancel: t(cancel), confirm: t(proceed) }
isOpen = (orderCycle) ->
moment(orderCycle.orders_open_at, "YYYY-MM-DD HH:mm:SS Z").isBefore() &&
moment(orderCycle.orders_close_at, "YYYY-MM-DD HH:mm:SS Z").isAfter()
element.focus ->
count = scope.orderCycle.subscriptions_count
return if acknowledged
return unless isOpen(scope.orderCycle)
return if count < 1
ConfirmDialog.open('info', t(msg, n: count), options).then ->
acknowledged = true
element.siblings('img').trigger('click')

View File

@@ -209,6 +209,7 @@ angular.module('admin.orderCycles').factory 'OrderCycle', ($resource, $window, S
delete order_cycle.editable_variants_for_incoming_exchanges
delete order_cycle.editable_variants_for_outgoing_exchanges
delete order_cycle.visible_variants_for_outgoing_exchanges
delete order_cycle.subscriptions_count
order_cycle
removeInactiveExchanges: (order_cycle) ->

View File

@@ -56,7 +56,4 @@ angular.module("admin.orders").directive 'customerSearchOverride', ->
return
$('#order_email').val customer.email
$('#user_id').val customer.user_id # modified
$('#guest_checkout_true').prop 'checked', false
$('#guest_checkout_false').prop 'checked', true
$('#guest_checkout_false').prop 'disabled', false
customer.email

View File

@@ -1,4 +1,4 @@
angular.module("ofn.admin").controller "DropdownPanelsCtrl", ($scope) ->
angular.module("admin.productImport").controller "DropdownPanelsCtrl", ($scope) ->
$scope.active = false
$scope.togglePanel = ->

View File

@@ -0,0 +1,12 @@
angular.module("admin.productImport").controller "ImportFeedbackCtrl", ($scope) ->
$scope.count = (items) ->
total = 0
angular.forEach items, (item) ->
total++
total
$scope.attribute_invalid = (attribute, line_number) ->
$scope.entries[line_number]['errors'][attribute] != undefined
$scope.ignore_fields = ['variant_unit', 'variant_unit_scale', 'unit_description']

View File

@@ -0,0 +1,161 @@
angular.module("admin.productImport").controller "ImportFormCtrl", ($scope, $http, $filter, ProductImportService, $timeout) ->
$scope.entries = {}
$scope.update_counts = {}
$scope.reset_counts = {}
$scope.updates = {}
$scope.updated_total = 0
$scope.updated_ids = []
$scope.update_errors = []
$scope.chunks = 0
$scope.completed = 0
$scope.percentage = "0%"
$scope.started = false
$scope.finished = false
$scope.countResettable = () ->
angular.forEach $scope.supplier_product_counts, (value, key) ->
$scope.reset_counts[key] = value
if $scope.update_counts[key]
$scope.reset_counts[key] -= $scope.update_counts[key]
$scope.resetProgress = () ->
$scope.chunks = 0
$scope.completed = 0
$scope.percentage = "0%"
$scope.started = false
$scope.finished = false
$scope.step = 'settings'
$scope.confirmSettings = () ->
$scope.step = 'import'
$scope.viewResults = () ->
$scope.countResettable()
$scope.step = 'results'
$scope.resetProgress()
$scope.acceptResults = () ->
$scope.step = 'save'
$scope.finalResults = () ->
$scope.step = 'complete'
$scope.start = () ->
$scope.started = true
$scope.percentage = "1%"
total = $scope.item_count
size = 100
$scope.chunks = Math.ceil(total / size)
i = 0
while i < $scope.chunks
start = (i*size)+1
end = (i+1)*size
if $scope.step == 'import'
$scope.processImport(start, end)
if $scope.step == 'save'
$scope.processSave(start, end)
i++
$scope.processImport = (start, end) ->
$scope.getSettings() if $scope.importSettings == null
$http(
url: $scope.import_url
method: 'POST'
data:
'start': start
'end': end
'filepath': $scope.filepath
'settings': $scope.importSettings
).success((data, status, headers, config) ->
angular.merge($scope.entries, angular.fromJson(data['entries']))
$scope.sortUpdates(data['reset_counts'])
$scope.updateProgress()
).error((data, status, headers, config) ->
$scope.exception = data
console.error(data)
)
$scope.importSettings = null
$scope.getSettings = () ->
$scope.importSettings = ProductImportService.getSettings()
$scope.sortUpdates = (data) ->
angular.forEach data, (value, key) ->
if (key in $scope.update_counts)
$scope.update_counts[key] += value['updates_count']
else
$scope.update_counts[key] = value['updates_count']
$scope.processSave = (start, end) ->
$scope.getSettings() if $scope.importSettings == null
$http(
url: $scope.save_url
method: 'POST'
data:
'start': start
'end': end
'filepath': $scope.filepath
'settings': $scope.importSettings
).success((data, status, headers, config) ->
$scope.sortResults(data['results'])
angular.forEach data['updated_ids'], (id) ->
$scope.updated_ids.push(id)
angular.forEach data['errors'], (error) ->
$scope.update_errors.push(error)
$scope.updateProgress()
).error((data, status, headers, config) ->
$scope.exception = data
console.error(data)
)
$scope.sortResults = (results) ->
angular.forEach results, (value, key) ->
if ($scope.updates[key] != undefined)
$scope.updates[key] += value
else
$scope.updates[key] = value
$scope.updated_total += value
$scope.resetAbsent = () ->
enterprises_to_reset = []
angular.forEach $scope.importSettings, (settings, enterprise) ->
if settings['reset_all_absent']
enterprises_to_reset.push(enterprise)
if enterprises_to_reset.length && $scope.updated_ids.length
$http(
url: $scope.reset_url
method: 'POST'
data:
'filepath': $scope.filepath
'settings': $scope.importSettings
'reset_absent': true,
'updated_ids': $scope.updated_ids,
'enterprises_to_reset': enterprises_to_reset
).success((data, status, headers, config) ->
console.log(data)
$scope.updates.products_reset = data
).error((data, status, headers, config) ->
console.error(data)
)
$scope.updateProgress = () ->
$scope.completed++
$scope.percentage = String(Math.round(($scope.completed / $scope.chunks) * 100)) + '%'
if $scope.completed == $scope.chunks
$scope.finished = true
$scope.resetAbsent() if $scope.step == 'save'

View File

@@ -1,12 +1,38 @@
angular.module("ofn.admin").controller "ImportOptionsFormCtrl", ($scope, $rootScope, ProductImportService) ->
angular.module("admin.productImport").controller "ImportOptionsFormCtrl", ($scope, $rootScope, ProductImportService) ->
$scope.toggleResetAbsent = () ->
confirmed = confirm t('js.product_import.confirmation') if $scope.resetAbsent
$scope.initForm = () ->
$scope.settings = {} if $scope.settings == undefined
$scope.settings[$scope.supplierId] = {
import_into: 'product_list'
defaults:
count_on_hand:
mode: 'overwrite_all'
on_hand:
mode: 'overwrite_all'
tax_category_id:
mode: 'overwrite_all'
shipping_category_id:
mode: 'overwrite_all'
available_on:
mode: 'overwrite_all'
}
$scope.import_into = 'product_list'
if confirmed or !$scope.resetAbsent
ProductImportService.updateResetAbsent($scope.supplierId, $scope.resetCount, $scope.resetAbsent)
$scope.updateImportInto = () ->
$scope.import_into = $scope.settings[$scope.supplierId]['import_into']
$scope.$watch 'settings', (updated) ->
ProductImportService.updateSettings(updated)
, true
$scope.toggleResetAbsent = (id) ->
checked = $scope.settings[id]['reset_all_absent']
confirmed = confirm t('js.product_import.confirmation') if checked
if confirmed or !checked
ProductImportService.updateResetAbsent($scope.supplierId, $scope.reset_counts[$scope.supplierId], checked)
else
$scope.resetAbsent = false
$scope.settings[id]['reset_all_absent'] = false
$scope.resetTotal = ProductImportService.resetTotal

View File

@@ -0,0 +1,32 @@
angular.module("admin.productImport").filter 'entriesFilterValid', ->
(entries, type) ->
if type == 'all'
return entries
filtered = {}
angular.forEach entries, (entry, line_number) ->
validates_as = entry.validates_as
if type == 'valid' and validates_as != '' \
or type == 'invalid' and validates_as == '' \
or type == 'create_product' and validates_as == 'new_product' or validates_as == 'new_variant' \
or type == 'update_product' and validates_as == 'existing_variant' \
or type == 'create_inventory' and validates_as == 'new_inventory_item' \
or type == 'update_inventory' and validates_as == 'existing_inventory_item'
filtered[line_number] = entry
filtered
angular.module("admin.productImport").filter 'entriesFilterSupplier', ->
(entries, supplier) ->
if supplier == 'all'
return entries
filtered = {}
angular.forEach entries, (entry, line_number) ->
if supplier == entry.attributes['supplier']
filtered[line_number] = entry
filtered

View File

@@ -0,0 +1,3 @@
angular.module("admin.productImport", ["ngResource"]).config ($httpProvider) ->
$httpProvider.defaults.headers.common["X-CSRF-Token"] = $("meta[name=csrf-token]").attr("content")
$httpProvider.defaults.headers.common["Accept"] = "application/json, text/javascript, */*"

View File

@@ -1,7 +1,8 @@
angular.module("ofn.admin").factory "ProductImportService", ($rootScope) ->
angular.module("admin.productImport").factory "ProductImportService", ($rootScope) ->
new class ProductImportService
suppliers: {}
resetTotal: 0
settings: {}
updateResetAbsent: (supplierId, resetCount, resetAbsent) ->
if resetAbsent
@@ -13,3 +14,8 @@ angular.module("ofn.admin").factory "ProductImportService", ($rootScope) ->
$rootScope.resetTotal = @resetTotal
updateSettings: (updated) ->
angular.merge(@settings, updated)
getSettings: () ->
@settings

View File

@@ -2,5 +2,7 @@ angular.module("ofn.admin").controller "ProductImageCtrl", ($scope, ProductImage
$scope.imageUploader = ProductImageService.imageUploader
$scope.imagePreview = ProductImageService.imagePreview
$scope.$watch 'product.image_url', (newValue) ->
$scope.imagePreview = newValue if newValue
$scope.$watch 'product.image_url', (newValue, oldValue) ->
if newValue != oldValue
$scope.imagePreview = newValue
$scope.uploadModal.close()

View File

@@ -0,0 +1,9 @@
angular.module("admin.utils").directive "datepicker", ->
require: "ngModel"
link: (scope, element, attrs, ngModel) ->
element.datepicker
dateFormat: "yy-mm-dd"
onSelect: (dateText, inst) ->
scope.$apply (scope) ->
# Fires ngModel.$parsers
ngModel.$setViewValue dateText

View File

@@ -25,7 +25,8 @@ angular.module("admin.utils").directive "tagsWithTranslation", ($timeout) ->
scope.object[scope.tagsAttr] ||= []
compileTagList()
scope.tagAdded = ->
scope.tagAdded = (tag)->
tag.text = tag.text.toLowerCase()
scope.onTagAdded()
compileTagList()

View File

@@ -25,6 +25,7 @@ angular.module("admin.variantOverrides").controller "AdminVariantOverridesCtrl",
$scope.resetSelectFilters = ->
$scope.producerFilter = 0
$scope.importDateFilter = '0'
$scope.query = ''
$scope.resetSelectFilters()

View File

@@ -0,0 +1,12 @@
angular.module("admin.variantOverrides").filter "importDate", ($filter, variantOverrides) ->
return (products, hub_id, date) ->
return [] if !hub_id
return $filter('filter')(products, (product) ->
return true if date == 0 or date == undefined or date == '0' or date == ''
angular.forEach product.variants (variant) ->
angular.forEach variantOverrides (vo) ->
if vo.variant_id == variant.id and vo.import_date == date
return true
false
, true)

View File

@@ -1 +1 @@
angular.module("admin.variantOverrides", ["admin.indexUtils", "admin.utils", "admin.dropdown", "admin.inventoryItems", 'ngTagsInput'])
angular.module("admin.variantOverrides", ["ofn.admin", "admin.indexUtils", "admin.utils", "admin.dropdown", "admin.inventoryItems", 'ngTagsInput'])