Merging master branch into require_standard_variant

This commit is contained in:
Rob Harrington
2015-04-29 14:30:49 +10:00
33 changed files with 280 additions and 119 deletions

View File

@@ -4,13 +4,15 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout
$scope.StatusMessage = StatusMessage
$scope.columns =
producer: {name: "Producer", visible: true}
name: {name: "Name", visible: true}
unit: {name: "Unit", visible: true}
price: {name: "Price", visible: true}
on_hand: {name: "On Hand", visible: true}
category: {name: "Category", visible: false}
available_on: {name: "Available On", visible: false}
producer: {name: "Producer", visible: true}
sku: {name: "SKU", visible: false}
name: {name: "Name", visible: true}
unit: {name: "Unit", visible: true}
price: {name: "Price", visible: true}
on_hand: {name: "On Hand", visible: true}
category: {name: "Category", visible: false}
inherits_properties: {name: "Inherits Properties?", visible: false}
available_on: {name: "Available On", visible: false}
$scope.variant_unit_options = VariantUnitManager.variantUnitOptions()
@@ -288,6 +290,9 @@ filterSubmitProducts = (productsToFilter) ->
filteredMaster ?= { id: product.master.id }
filteredMaster.display_as = product.master.display_as
if product.hasOwnProperty("sku")
filteredProduct.sku = product.sku
hasUpdatableProperty = true
if product.hasOwnProperty("name")
filteredProduct.name = product.name
hasUpdatableProperty = true
@@ -310,6 +315,9 @@ filterSubmitProducts = (productsToFilter) ->
if product.hasOwnProperty("category_id")
filteredProduct.primary_taxon_id = product.category_id
hasUpdatableProperty = true
if product.hasOwnProperty("inherits_properties")
filteredProduct.inherits_properties = product.inherits_properties
hasUpdatableProperty = true
if product.hasOwnProperty("available_on")
filteredProduct.available_on = product.available_on
hasUpdatableProperty = true

View File

@@ -6,10 +6,10 @@ Darkswarm.directive "activeSelector", ->
replace: true
templateUrl: 'active_selector.html'
link: (scope, elem, attr)->
scope.selector.emit = scope.emit
elem.bind "click", ->
scope.$apply ->
scope.selector.active = !scope.selector.active
# This function is a convention, e.g. a callback on the scope applied when active changes
scope.emit() if scope.emit
unless scope.readOnly && scope.readOnly()
scope.selector.emit = scope.emit
elem.bind "click", ->
scope.$apply ->
scope.selector.active = !scope.selector.active
# This function is a convention, e.g. a callback on the scope applied when active changes
scope.emit() if scope.emit

View File

@@ -5,7 +5,7 @@ Darkswarm.directive "filterSelector", (FilterSelectorsService)->
replace: true
scope:
objects: "&"
activeSelectors: "="
activeSelectors: "=?"
allSelectors: "=?" # Optional
templateUrl: "filter_selector.html"
@@ -13,6 +13,9 @@ Darkswarm.directive "filterSelector", (FilterSelectorsService)->
selectors_by_id = {}
selectors = null # To get scoping/closure right
scope.readOnly = ->
!attr.activeSelectors?
scope.emit = ->
scope.activeSelectors = selectors.filter (selector)->
selector.active
@@ -23,10 +26,10 @@ Darkswarm.directive "filterSelector", (FilterSelectorsService)->
# when data has been loaded, in order to pass
# selectors up
scope.$on 'loadFilterSelectors', ->
scope.allSelectors = scope.selectors()
scope.allSelectors = scope.selectors() if attr.allSelectors?
scope.$watchCollection "selectors()", (newValue, oldValue) ->
scope.allSelectors = scope.selectors()
scope.allSelectors = scope.selectors() if attr.allSelectors?
# Build a list of selectors
scope.selectors = ->

View File

@@ -4,6 +4,7 @@ Darkswarm.directive 'singleLineSelectors', ($timeout, $filter) ->
scope:
objects: "&"
activeSelectors: "="
selectorName: "@activeSelectors"
link: (scope,element,attrs) ->
scope.fitting = false

View File

@@ -0,0 +1,7 @@
Darkswarm.filter 'propertiesWithValuesOf', ->
(objects)->
propertiesWithValues = {}
for object in objects
for property in object.properties_with_values
propertiesWithValues[property.id] = property
propertiesWithValues

View File

@@ -21,6 +21,8 @@ Darkswarm.factory 'Products', ($resource, Enterprises, Dereferencer, Taxons, Pro
for product in @products
product.supplier = Enterprises.enterprises_by_id[product.supplier.id]
Dereferencer.dereference product.taxons, Taxons.taxons_by_id
product.properties = angular.copy(product.properties_with_values)
Dereferencer.dereference product.properties, Properties.properties_by_id
# May return different objects! If the variant has already been registered

View File

@@ -1,2 +1,3 @@
%li{ ng: { class: "{active: selector.active}" } }
%a{ ng: { transclude: true, class: "{active: selector.active}" } }
%a{ "tooltip" => "{{selector.object.value}}", "tooltip-placement" => "bottom",
ng: { transclude: true, class: "{active: selector.active, 'has-tip': selector.object.value}" } }

View File

@@ -1,3 +1,4 @@
%active-selector{ ng: { repeat: "selector in selectors()", show: "ifDefined(selector.fits, true)" } }
%render-svg{path: "{{selector.object.icon}}", ng: { if: "selector.object.icon"} }
%span {{ selector.object.name }}
%div{ style: "display: inline-block" }
%active-selector{ ng: { repeat: "selector in selectors()", show: "ifDefined(selector.fits, true)" } }
%render-svg{path: "{{selector.object.icon}}", ng: { if: "selector.object.icon"} }
%span {{ selector.object.name }}

View File

@@ -6,24 +6,15 @@
%em from
%span.avenir {{ enterprise.name }}
%br
-# TODO: Add product taxons and properties here
-# / TODO: Rob - add in taxons and properties and property pop-overs
-# / %render-svg{path: "{{product.primary_taxon.icon}}"}
-# .pad-top
-# %span.filter-shopfront.taxon-selectors
-# %ul.inline-block
-# %li
-# %a.button.tiny.disabled Grains
-# %li
-# %a.button.tiny.disabled Dairy
-#
-# %span.filter-shopfront.property-selectors.pad-top
-# %ul.inline-block
-# %li
-# %a.button.tiny Organic certified
-# / TODO: Rob - need popover, use will's directive or this? http://pineconellc.github.io/angular-foundation/
.filter-shopfront.taxon-selectors.inline-block
%ul
%filter-selector{ objects: "[product] | taxonsOf" }
.filter-shopfront.property-selectors.inline-block
%ul
%filter-selector{ objects: "[product] | propertiesWithValuesOf" }
%div{"ng-if" => "product.description"}
%hr

View File

@@ -3,12 +3,12 @@
%filter-selector{objects: "objects()", "active-selectors" => "activeSelectors", "all-selectors" => "allSelectors" }
%li.more{ ng: { show: "overFlowSelectors().length > 0 || fitting" } }
%a.dropdown{ data: { dropdown: "show-more" }, ng: { class: "{active: selectedOverFlowSelectors().length > 0}" } }
%a.dropdown{ data: { dropdown: "{{ 'show-more-' + selectorName }}" }, ng: { class: "{active: selectedOverFlowSelectors().length > 0}" } }
%span
+ {{ overFlowSelectors().length }} more
%i.ofn-i_052-point-down
.f-dropdown.text-right.content#show-more
.f-dropdown.text-right.content{ ng: { attr: { id: "{{ 'show-more-' + selectorName }}" } } }
%ul
%active-selector{ ng: { repeat: "selector in overFlowSelectors()", hide: "selector.fits" } }
%render-svg{path: "{{selector.object.icon}}"}
%render-svg{path: "{{selector.object.icon}}", ng: { if: "selector.object.icon"}}
%span {{ selector.object.name }}

View File

@@ -4,7 +4,7 @@
@import animations
@mixin filter-selector($base-clr, $border-clr, $hover-clr)
ul.inline-block
&.inline-block, ul.inline-block
display: inline-block
li
@@ -14,7 +14,7 @@
margin: 0 0 0.25rem 0.25rem
&:hover, &:focus
background: transparent
&.active
&.active
box-shadow: none
a, a.button
@@ -48,10 +48,10 @@
svg
path
fill: $hover-clr
&.disabled
opacity: 0.6
&:hover, &:focus
border-color: $border-clr
color: $base-clr
@@ -118,5 +118,3 @@
// Shopfront properties
&.property-selectors
@include filter-selector(#666, #ccc, #777)

View File

@@ -65,7 +65,7 @@ Spree::Api::ProductsController.class_eval do
end
def render_paged_products(products)
render text: { products: ActiveModel::ArraySerializer.new(products, each_serializer: Spree::Api::ProductSerializer), pages: products.num_pages }.to_json
render text: { products: ActiveModel::ArraySerializer.new(products, each_serializer: Api::Admin::ProductSerializer), pages: products.num_pages }.to_json
end
end

View File

@@ -47,7 +47,7 @@ module Admin
end
def admin_inject_products
admin_inject_json_ams_array "ofn.admin", "products", @products, Spree::Api::ProductSerializer
admin_inject_json_ams_array "ofn.admin", "products", @products, Api::Admin::ProductSerializer
end
def admin_inject_taxons

View File

@@ -18,8 +18,10 @@ Spree::Product.class_eval do
delegate_belongs_to :master, :unit_value, :unit_description
delegate :images_attributes=, :display_as=, to: :master
attr_accessible :supplier_id, :primary_taxon_id, :distributor_ids, :product_distributions_attributes, :group_buy, :group_buy_unit_size
attr_accessible :variant_unit, :variant_unit_scale, :variant_unit_name, :unit_value, :unit_description, :notes, :images_attributes, :display_as
attr_accessible :supplier_id, :primary_taxon_id, :distributor_ids, :product_distributions_attributes
attr_accessible :group_buy, :group_buy_unit_size, :unit_description, :notes, :images_attributes, :display_as
attr_accessible :variant_unit, :variant_unit_scale, :variant_unit_name, :unit_value
attr_accessible :inherits_properties, :sku
# validates_presence_of :variants, unless: :new_record?, message: "Product must have at least one variant"
validates_presence_of :supplier
@@ -107,19 +109,21 @@ Spree::Product.class_eval do
# -- Methods
def properties_h
def properties_including_inherited
# Product properties override producer properties
ps = supplier.producer_properties.inject(product_properties) do |properties, property|
if properties.find { |p| p.property.presentation == property.property.presentation }
properties
else
properties + [property]
ps = product_properties.all
if inherits_properties
supplier.producer_properties.each do |producer_property|
unless ps.find { |product_property| product_property.property.presentation == producer_property.property.presentation }
ps << producer_property
end
end
end
ps.
sort_by { |pp| pp.position }.
map { |pp| {presentation: pp.property.presentation, value: pp.value} }
map { |pp| {id: pp.property.id, name: pp.property.presentation, value: pp.value} }
end
def in_distributor?(distributor)

View File

@@ -0,0 +1,26 @@
/ insert_after 'table.index.sortable'
=f.check_box :inherits_properties
=f.label :inherits_properties, "Inherit properties from #{@product.supplier.name}? (unless overridden above)"
%br
%br
#inherited_properties
%table.index
%thead
%tr{"data-hook" => "producer_properties_header"}
%th= t(:inherited_property)
%th= t(:value)
%th.actions
%tbody#producer_properties{"data-hook" => ""}
- @product.supplier.producer_properties.each do |producer_property|
%tr
%td= producer_property.property.presentation
%td= producer_property.value
%td.actions
:coffee
$(document).ready ->
$("#inherited_properties").toggle $("input#product_inherits_properties").is(':checked')
$("input#product_inherits_properties").change ->
$("#inherited_properties").toggle $(this).is(':checked')

View File

@@ -1,12 +1,12 @@
class Spree::Api::ProductSerializer < ActiveModel::Serializer
attributes :id, :name, :variant_unit, :variant_unit_scale, :variant_unit_name, :on_demand
class Api::Admin::ProductSerializer < ActiveModel::Serializer
attributes :id, :name, :sku, :variant_unit, :variant_unit_scale, :variant_unit_name, :on_demand, :inherits_properties
attributes :on_hand, :price, :available_on, :permalink_live
has_one :supplier, key: :producer_id, embed: :id
has_one :primary_taxon, key: :category_id, embed: :id
has_many :variants, key: :variants, serializer: Spree::Api::VariantSerializer # embed: ids
has_one :master, serializer: Spree::Api::VariantSerializer
has_many :variants, key: :variants, serializer: Api::Admin::VariantSerializer # embed: ids
has_one :master, serializer: Api::Admin::VariantSerializer
def on_hand
object.on_hand.nil? ? 0 : object.on_hand.to_f.finite? ? object.on_hand : "On demand"

View File

@@ -1,4 +1,4 @@
class Spree::Api::VariantSerializer < ActiveModel::Serializer
class Api::Admin::VariantSerializer < ActiveModel::Serializer
attributes :id, :options_text, :unit_value, :unit_description, :unit_to_display, :on_demand, :display_as, :display_name, :name_to_display
attributes :on_hand, :price

View File

@@ -31,17 +31,20 @@ class Api::CachedProductSerializer < ActiveModel::Serializer
#delegate :cache_key, to: :object
attributes :id, :name, :permalink, :count_on_hand, :on_demand, :group_buy,
:notes, :description
:notes, :description, :properties_with_values
has_many :variants, serializer: Api::VariantSerializer
has_many :taxons, serializer: Api::IdSerializer
has_many :properties, serializer: Api::IdSerializer
has_many :images, serializer: Api::ImageSerializer
has_one :supplier, serializer: Api::IdSerializer
has_one :primary_taxon, serializer: Api::TaxonSerializer
has_one :master, serializer: Api::VariantSerializer
def properties_with_values
object.properties_including_inherited
end
def variants
# 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

View File

@@ -1,21 +1,22 @@
.row
= render partial: 'shared/components/filter_controls'
-# = render partial: 'shared/components/filter_controls'
.small-12.medium-6.columns &nbsp;
= render partial: 'shared/components/show_profiles'
.row.animate-show{"ng-show" => "filtersActive"}
.small-12.columns
.row.filter-box
.small-12.large-9.columns
%h5.tdhead
.light Filter by
Type
%ul.small-block-grid-2.medium-block-grid-4.large-block-grid-5
%filter-selector{objects: "Enterprises.hubs | searchEnterprises:query | taxonsOf", "active-selectors" => "activeTaxons"}
.small-12.large-3.columns
%h5.tdhead
.light Filter by
Delivery
%ul.small-block-grid-2.medium-block-grid-4.large-block-grid-2
%shipping-type-selector{results: "shippingTypes"}
= render partial: 'shared/components/filter_box'
-# .row.animate-show{"ng-show" => "filtersActive"}
-# .small-12.columns
-# .row.filter-box
-# .small-12.large-9.columns
-# %h5.tdhead
-# .light Filter by
-# Type
-# %ul.small-block-grid-2.medium-block-grid-4.large-block-grid-5
-# %filter-selector{objects: "Enterprises.hubs | searchEnterprises:query | taxonsOf", "active-selectors" => "activeTaxons"}
-# .small-12.large-3.columns
-# %h5.tdhead
-# .light Filter by
-# Delivery
-# %ul.small-block-grid-2.medium-block-grid-4.large-block-grid-2
-# %shipping-type-selector{results: "shippingTypes"}
-#
-# = render partial: 'shared/components/filter_box'

View File

@@ -1,15 +1,15 @@
.row
= render partial: 'shared/components/filter_controls'
.small-12.medium-6.columns.text-right
&nbsp;
.row.animate-show{"ng-show" => "filtersActive"}
.small-12.columns
.row.filter-box
.small-12.columns
%h5.tdhead
.light Filter by
Type
%ul.small-block-grid-2.medium-block-grid-4.large-block-grid-6
%filter-selector{objects: "Enterprises.producers | searchEnterprises:query | taxonsOf", "active-selectors" => "activeTaxons"}
= render partial: 'shared/components/filter_box'
-# .row
-# = render partial: 'shared/components/filter_controls'
-# .small-12.medium-6.columns.text-right
-# &nbsp;
-#
-# .row.animate-show{"ng-show" => "filtersActive"}
-# .small-12.columns
-# .row.filter-box
-# .small-12.columns
-# %h5.tdhead
-# .light Filter by
-# Type
-# %ul.small-block-grid-2.medium-block-grid-4.large-block-grid-6
-# %filter-selector{objects: "Enterprises.producers | searchEnterprises:query | taxonsOf", "active-selectors" => "activeTaxons"}
-# = render partial: 'shared/components/filter_box'

View File

@@ -1,12 +1,14 @@
%colgroup
%col.actions
%col.producer{ ng: { show: 'columns.producer.visible' } }
%col.sku{ ng: { show: 'columns.sku.visible' } }
%col.name{ ng: { show: 'columns.name.visible' } }
%col.unit{ ng: { show: 'columns.unit.visible' } }
%col.display_as{ ng: { show: 'columns.unit.visible' } }
%col.price{ ng: { show: 'columns.price.visible'} }
%col.on_hand{ ng: { show: 'columns.on_hand.visible' } }
%col.category{ ng: { show: 'columns.category.visible' } }
%col.inherits_properties{ ng: { show: 'columns.inherits_properties.visible' } }
%col.available_on{ ng: { show: 'columns.available_on.visible' } }
%col.actions
%col.actions
@@ -16,12 +18,14 @@
%tr
%th.left-actions
%th.producer{ 'ng-show' => 'columns.producer.visible' } Producer
%th.sku{ 'ng-show' => 'columns.sku.visible' } SKU
%th.name{ 'ng-show' => 'columns.name.visible' } Name
%th.unit{ 'ng-show' => 'columns.unit.visible' } Unit / Value
%th.display_as{ 'ng-show' => 'columns.unit.visible' } Display As
%th.price{ 'ng-show' => 'columns.price.visible' } Price
%th.on_hand{ 'ng-show' => 'columns.on_hand.visible' } On Hand
%th.category{ 'ng-show' => 'columns.category.visible' } Category
%th.inherits_properties{ 'ng-show' => 'columns.inherits_properties.visible' } Inherits Properties?
%th.available_on{ 'ng-show' => 'columns.available_on.visible' } Av. On
%th.actions
%th.actions

View File

@@ -4,6 +4,8 @@
%a{ :class => "add-variant icon-plus-sign", 'ng-click' => "addVariant(product)", 'ng-show' => "!hasVariants(product) && hasUnit(product)" }
%td.producer{ 'ng-show' => 'columns.producer.visible' }
%select.select2.fullwidth{ 'ng-model' => 'product.producer_id', :name => 'producer_id', 'ofn-track-product' => 'producer_id', 'ng-options' => 'producer.id as producer.name for producer in producers' }
%td.sku{ 'ng-show' => 'columns.sku.visible' }
%input{ 'ng-model' => "product.sku", :name => 'product_sku', 'ofn-track-product' => 'sku', :type => 'text' }
%td.name{ 'ng-show' => 'columns.name.visible' }
%input{ 'ng-model' => "product.name", :name => 'product_name', 'ofn-track-product' => 'name', :type => 'text' }
%td.unit{ 'ng-show' => 'columns.unit.visible' }
@@ -20,6 +22,8 @@
%input.field{ 'ng-model' => 'product.on_hand', :name => 'on_hand', 'ofn-track-product' => 'on_hand', 'ng-hide' => 'hasVariants(product) || product.on_demand', :type => 'number' }
%td.category{ 'ng-if' => 'columns.category.visible' }
%input.fullwidth{ :type => 'text', id: "p{{product.id}}_category_id", 'ng-model' => 'product.category_id', 'ofn-taxon-autocomplete' => '', 'ofn-track-product' => 'category_id', 'multiple-selection' => 'false', placeholder: 'Category' }
%td.inherits_properties{ 'ng-show' => 'columns.inherits_properties.visible' }
%input{ 'ng-model' => 'product.inherits_properties', :name => 'inherits_properties', 'ofn-track-product' => 'inherits_properties', type: "checkbox" }
%td.available_on{ 'ng-show' => 'columns.available_on.visible' }
%input{ 'ng-model' => 'product.available_on', :name => 'available_on', 'ofn-track-product' => 'available_on', 'datetimepicker' => 'product.available_on', type: "text" }
%td.actions

View File

@@ -3,6 +3,7 @@
%a{ :class => "variant-item icon-caret-right", 'ng-hide' => "$last" }
%a{ :class => "add-variant icon-plus-sign", 'ng-click' => "addVariant(product)", 'ng-show' => "$last" }
%td{ 'ng-show' => 'columns.producer.visible' }
%td{ 'ng-show' => 'columns.sku.visible' }
%td{ 'ng-show' => 'columns.name.visible' }
%input{ 'ng-model' => 'variant.display_name', :name => 'variant_display_name', 'ofn-track-variant' => 'display_name', :type => 'text', placeholder: "{{ product.name }}" }
%td.unit_value{ 'ng-show' => 'columns.unit.visible' }
@@ -15,6 +16,7 @@
%input.field{ 'ng-model' => 'variant.on_hand', 'ng-change' => 'updateOnHand(product)', :name => 'variant_on_hand', 'ng-hide' => 'variant.on_demand', 'ofn-track-variant' => 'on_hand', :type => 'number' }
%span{ 'ng-bind' => 'variant.on_hand', :name => 'variant_on_hand', 'ng-show' => 'variant.on_demand' }
%td{ 'ng-show' => 'columns.category.visible' }
%td{ 'ng-show' => 'columns.inherits_properties.visible' }
%td{ 'ng-show' => 'columns.available_on.visible' }
%td.actions
%a{ 'ng-click' => 'editWarn(product,variant)', :class => "edit-variant icon-edit no-text", 'ng-show' => "variantSaved(variant)" }