Merge pull request #287 from wvengen/feature-sync_all
Allow to synchronise all articles + category matching
This commit is contained in:
commit
9343b965a0
23 changed files with 242 additions and 99 deletions
|
@ -148,7 +148,7 @@ class ArticlesController < ApplicationController
|
||||||
no_category = ArticleCategory.new
|
no_category = ArticleCategory.new
|
||||||
articles.each do |row|
|
articles.each do |row|
|
||||||
# fallback to Others category
|
# fallback to Others category
|
||||||
category = (ArticleCategory.find_by_name(row[:category]) or no_category)
|
category = (ArticleCategory.find_match(row[:category]) or no_category)
|
||||||
# creates a new article and price
|
# creates a new article and price
|
||||||
article = Article.new( :name => row[:name],
|
article = Article.new( :name => row[:name],
|
||||||
:note => row[:note],
|
:note => row[:note],
|
||||||
|
@ -227,10 +227,10 @@ class ArticlesController < ApplicationController
|
||||||
redirect_to supplier_articles_url(@supplier), :alert => I18n.t('articles.controller.sync.shared_alert', :supplier => @supplier.name)
|
redirect_to supplier_articles_url(@supplier), :alert => I18n.t('articles.controller.sync.shared_alert', :supplier => @supplier.name)
|
||||||
end
|
end
|
||||||
# sync articles against external database
|
# sync articles against external database
|
||||||
@updated_articles, @outlisted_articles = @supplier.sync_all
|
@updated_articles, @outlisted_articles, @new_articles = @supplier.sync_all
|
||||||
# convert to db-compatible-string
|
# convert to db-compatible-string
|
||||||
@updated_articles.each {|a, b| a.shared_updated_on = a.shared_updated_on.to_formatted_s(:db)}
|
@updated_articles.each {|a, b| a.shared_updated_on = a.shared_updated_on.to_formatted_s(:db)}
|
||||||
if @updated_articles.empty? && @outlisted_articles.empty?
|
if @updated_articles.empty? && @outlisted_articles.empty? && @new_articles.empty?
|
||||||
redirect_to supplier_articles_path(@supplier), :notice => I18n.t('articles.controller.sync.notice')
|
redirect_to supplier_articles_path(@supplier), :notice => I18n.t('articles.controller.sync.notice')
|
||||||
end
|
end
|
||||||
@ignored_article_count = @supplier.articles.where(order_number: [nil, '']).count
|
@ignored_article_count = @supplier.articles.where(order_number: [nil, '']).count
|
||||||
|
@ -246,8 +246,21 @@ class ArticlesController < ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
# Update articles
|
# Update articles
|
||||||
params[:articles].each do |id, attrs|
|
if params[:articles]
|
||||||
Article.find(id).update_attributes! attrs
|
params[:articles].each do |id, attrs|
|
||||||
|
Article.find(id).update_attributes! attrs
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Add new articles
|
||||||
|
if params[:new_articles]
|
||||||
|
params[:new_articles].each do |attrs|
|
||||||
|
article = Article.new attrs
|
||||||
|
article.supplier = @supplier
|
||||||
|
article.availability = true if @supplier.shared_sync_method == 'all_available'
|
||||||
|
article.availability = false if @supplier.shared_sync_method == 'all_unavailable'
|
||||||
|
article.save!
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ module ArticlesHelper
|
||||||
|
|
||||||
# useful for highlighting attributes, when synchronizing articles
|
# useful for highlighting attributes, when synchronizing articles
|
||||||
def highlight_new(unequal_attributes, attribute)
|
def highlight_new(unequal_attributes, attribute)
|
||||||
|
return unless unequal_attributes
|
||||||
unequal_attributes.detect {|a| a == attribute} ? "background-color: yellow" : ""
|
unequal_attributes.detect {|a| a == attribute} ? "background-color: yellow" : ""
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -14,6 +15,7 @@ module ArticlesHelper
|
||||||
|
|
||||||
# Flatten search params, used in import from external database
|
# Flatten search params, used in import from external database
|
||||||
def search_params
|
def search_params
|
||||||
|
return {} unless params[:q]
|
||||||
Hash[params[:q].map { |k,v| [k, (v.is_a?(Array) ? v.join(" ") : v)] }]
|
Hash[params[:q].map { |k,v| [k, (v.is_a?(Array) ? v.join(" ") : v)] }]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,4 +3,10 @@ module SuppliersHelper
|
||||||
def associated_supplier_names(shared_supplier)
|
def associated_supplier_names(shared_supplier)
|
||||||
"(#{shared_supplier.suppliers.map(&:name).join(', ')})"
|
"(#{shared_supplier.suppliers.map(&:name).join(', ')})"
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
def shared_sync_method_collection(shared_supplier)
|
||||||
|
shared_supplier.shared_sync_methods.map do |m|
|
||||||
|
[t("suppliers.shared_supplier_methods.#{m}"), m]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
|
@ -59,7 +59,9 @@ class Article < ActiveRecord::Base
|
||||||
validates_numericality_of :price, :greater_than_or_equal_to => 0
|
validates_numericality_of :price, :greater_than_or_equal_to => 0
|
||||||
validates_numericality_of :unit_quantity, :greater_than => 0
|
validates_numericality_of :unit_quantity, :greater_than => 0
|
||||||
validates_numericality_of :deposit, :tax
|
validates_numericality_of :deposit, :tax
|
||||||
validates_uniqueness_of :name, :scope => [:supplier_id, :deleted_at, :type]
|
#validates_uniqueness_of :name, :scope => [:supplier_id, :deleted_at, :type], if: Proc.new {|a| a.supplier.shared_sync_method.blank? or a.supplier.shared_sync_method == 'import' }
|
||||||
|
#validates_uniqueness_of :name, :scope => [:supplier_id, :deleted_at, :type, :unit, :unit_quantity]
|
||||||
|
validate :uniqueness_of_name
|
||||||
|
|
||||||
# Callbacks
|
# Callbacks
|
||||||
before_save :update_price_history
|
before_save :update_price_history
|
||||||
|
@ -98,9 +100,10 @@ class Article < ActiveRecord::Base
|
||||||
# unequal attributes will returned in array
|
# unequal attributes will returned in array
|
||||||
# if only the timestamps differ and the attributes are equal,
|
# if only the timestamps differ and the attributes are equal,
|
||||||
# false will returned and self.shared_updated_on will be updated
|
# false will returned and self.shared_updated_on will be updated
|
||||||
def shared_article_changed?
|
def shared_article_changed?(supplier = self.supplier)
|
||||||
# skip early if the timestamp hasn't changed
|
# skip early if the timestamp hasn't changed
|
||||||
unless self.shared_article.nil? or self.shared_updated_on == self.shared_article.updated_on
|
shared_article = self.shared_article(supplier)
|
||||||
|
unless shared_article.nil? or self.shared_updated_on == shared_article.updated_on
|
||||||
|
|
||||||
# try to convert units
|
# try to convert units
|
||||||
# convert supplier's price and unit_quantity into fc-size
|
# convert supplier's price and unit_quantity into fc-size
|
||||||
|
@ -108,27 +111,27 @@ class Article < ActiveRecord::Base
|
||||||
new_unit = self.unit
|
new_unit = self.unit
|
||||||
unless new_price and new_unit_quantity
|
unless new_price and new_unit_quantity
|
||||||
# if convertion isn't possible, take shared_article-price/unit_quantity
|
# if convertion isn't possible, take shared_article-price/unit_quantity
|
||||||
new_price, new_unit_quantity, new_unit = self.shared_article.price, self.shared_article.unit_quantity, self.shared_article.unit
|
new_price, new_unit_quantity, new_unit = shared_article.price, shared_article.unit_quantity, shared_article.unit
|
||||||
end
|
end
|
||||||
|
|
||||||
# check if all attributes differ
|
# check if all attributes differ
|
||||||
unequal_attributes = Article.compare_attributes(
|
unequal_attributes = Article.compare_attributes(
|
||||||
{
|
{
|
||||||
:name => [self.name, self.shared_article.name],
|
:name => [self.name, shared_article.name],
|
||||||
:manufacturer => [self.manufacturer, self.shared_article.manufacturer.to_s],
|
:manufacturer => [self.manufacturer, shared_article.manufacturer.to_s],
|
||||||
:origin => [self.origin, self.shared_article.origin],
|
:origin => [self.origin, shared_article.origin],
|
||||||
:unit => [self.unit, new_unit],
|
:unit => [self.unit, new_unit],
|
||||||
:price => [self.price, new_price],
|
:price => [self.price.to_f.round(2), new_price.to_f.round(2)],
|
||||||
:tax => [self.tax, self.shared_article.tax],
|
:tax => [self.tax, shared_article.tax],
|
||||||
:deposit => [self.deposit, self.shared_article.deposit],
|
:deposit => [self.deposit.to_f.round(2), shared_article.deposit.to_f.round(2)],
|
||||||
# take care of different num-objects.
|
# take care of different num-objects.
|
||||||
:unit_quantity => [self.unit_quantity.to_s.to_f, new_unit_quantity.to_s.to_f],
|
:unit_quantity => [self.unit_quantity.to_s.to_f, new_unit_quantity.to_s.to_f],
|
||||||
:note => [self.note.to_s, self.shared_article.note.to_s]
|
:note => [self.note.to_s, shared_article.note.to_s]
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
if unequal_attributes.empty?
|
if unequal_attributes.empty?
|
||||||
# when attributes not changed, update timestamp of article
|
# when attributes not changed, update timestamp of article
|
||||||
self.update_attribute(:shared_updated_on, self.shared_article.updated_on)
|
self.update_attribute(:shared_updated_on, shared_article.updated_on)
|
||||||
false
|
false
|
||||||
else
|
else
|
||||||
unequal_attributes
|
unequal_attributes
|
||||||
|
@ -144,9 +147,9 @@ class Article < ActiveRecord::Base
|
||||||
end
|
end
|
||||||
|
|
||||||
# to get the correspondent shared article
|
# to get the correspondent shared article
|
||||||
def shared_article
|
def shared_article(supplier = self.supplier)
|
||||||
self.order_number.blank? and return nil
|
self.order_number.blank? and return nil
|
||||||
@shared_article ||= self.supplier.shared_supplier.shared_articles.find_by_number(self.order_number) rescue nil
|
@shared_article ||= supplier.shared_supplier.shared_articles.find_by_number(self.order_number) rescue nil
|
||||||
end
|
end
|
||||||
|
|
||||||
# convert units in foodcoop-size
|
# convert units in foodcoop-size
|
||||||
|
@ -214,4 +217,18 @@ class Article < ActiveRecord::Base
|
||||||
def price_changed?
|
def price_changed?
|
||||||
changed.detect { |attr| attr == 'price' || 'tax' || 'deposit' || 'unit_quantity' } ? true : false
|
changed.detect { |attr| attr == 'price' || 'tax' || 'deposit' || 'unit_quantity' } ? true : false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# We used have the name unique per supplier+deleted_at+type. With the addition of shared_sync_method all,
|
||||||
|
# this came in the way, and we now allow duplicate names for the 'all' methods - expecting foodcoops to
|
||||||
|
# make their own choice among products with different units by making articles available/unavailable.
|
||||||
|
def uniqueness_of_name
|
||||||
|
matches = Article.where(name: name, supplier_id: supplier_id, deleted_at: deleted_at, type: type)
|
||||||
|
matches = matches.where.not(id: id) unless new_record?
|
||||||
|
if supplier.shared_sync_method.blank? or supplier.shared_sync_method == 'import'
|
||||||
|
errors.add :name, :taken if matches.any?
|
||||||
|
else
|
||||||
|
errors.add :name, :taken_with_unit if matches.where(unit: unit, unit_quantity: unit_quantity).any?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -12,10 +12,26 @@ class ArticleCategory < ActiveRecord::Base
|
||||||
|
|
||||||
normalize_attributes :name, :description
|
normalize_attributes :name, :description
|
||||||
|
|
||||||
validates :name, :presence => true, :uniqueness => true, :length => { :in => 2..20 }
|
validates :name, :presence => true, :uniqueness => true, :length => { :minimum => 2 }
|
||||||
|
|
||||||
before_destroy :check_for_associated_articles
|
before_destroy :check_for_associated_articles
|
||||||
|
|
||||||
|
# Find a category that matches a category name; may return nil.
|
||||||
|
# TODO more intelligence like remembering earlier associations (global and/or per-supplier)
|
||||||
|
def self.find_match(category)
|
||||||
|
return if category.blank? or category.length < 3
|
||||||
|
c = nil
|
||||||
|
## exact match - not needed, will be returned by next query as well
|
||||||
|
#c ||= ArticleCategory.where(name: category).first
|
||||||
|
# case-insensitive substring match (take the closest match = shortest)
|
||||||
|
c = ArticleCategory.where('name LIKE ?', "%#{category}%") unless c and c.any?
|
||||||
|
# case-insensitive phrase present in category description
|
||||||
|
c = ArticleCategory.where('description LIKE ?', "%#{category}%").select {|s| s.description.match /(^|,)\s*#{category}\s*(,|$)/i} unless c and c.any?
|
||||||
|
# return closest match if there are multiple
|
||||||
|
c = c.sort_by {|s| s.name.length}.first if c.respond_to? :sort_by
|
||||||
|
c
|
||||||
|
end
|
||||||
|
|
||||||
protected
|
protected
|
||||||
|
|
||||||
# Deny deleting the category when there are associated articles.
|
# Deny deleting the category when there are associated articles.
|
||||||
|
|
|
@ -19,6 +19,7 @@ class SharedArticle < ActiveRecord::Base
|
||||||
:deposit => deposit,
|
:deposit => deposit,
|
||||||
:unit_quantity => unit_quantity,
|
:unit_quantity => unit_quantity,
|
||||||
:order_number => number,
|
:order_number => number,
|
||||||
|
:article_category => ArticleCategory.find_match(category),
|
||||||
# convert to db-compatible-string
|
# convert to db-compatible-string
|
||||||
:shared_updated_on => updated_on.to_formatted_s(:db)
|
:shared_updated_on => updated_on.to_formatted_s(:db)
|
||||||
)
|
)
|
||||||
|
|
|
@ -14,5 +14,13 @@ class SharedSupplier < ActiveRecord::Base
|
||||||
whitelist = %w(name address phone fax email url delivery_days note)
|
whitelist = %w(name address phone fax email url delivery_days note)
|
||||||
attributes.select { |k,_v| whitelist.include?(k) }
|
attributes.select { |k,_v| whitelist.include?(k) }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# return list of synchronisation methods available for this supplier
|
||||||
|
def shared_sync_methods
|
||||||
|
methods = []
|
||||||
|
methods += %w(all_available all_unavailable) if shared_articles.count < 200
|
||||||
|
methods += %w(import) # perhaps, in the future: if shared_articles.count > 20
|
||||||
|
methods
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -9,12 +9,13 @@ class Supplier < ActiveRecord::Base
|
||||||
|
|
||||||
include ActiveModel::MassAssignmentSecurity
|
include ActiveModel::MassAssignmentSecurity
|
||||||
attr_accessible :name, :address, :phone, :phone2, :fax, :email, :url, :contact_person, :customer_number,
|
attr_accessible :name, :address, :phone, :phone2, :fax, :email, :url, :contact_person, :customer_number,
|
||||||
:delivery_days, :order_howto, :note, :shared_supplier_id, :min_order_quantity
|
:delivery_days, :order_howto, :note, :shared_supplier_id, :min_order_quantity, :shared_sync_method
|
||||||
|
|
||||||
validates :name, :presence => true, :length => { :in => 4..30 }
|
validates :name, :presence => true, :length => { :in => 4..30 }
|
||||||
validates :phone, :presence => true, :length => { :in => 8..25 }
|
validates :phone, :presence => true, :length => { :in => 8..25 }
|
||||||
validates :address, :presence => true, :length => { :in => 8..50 }
|
validates :address, :presence => true, :length => { :in => 8..50 }
|
||||||
validates_length_of :order_howto, :note, maximum: 250
|
validates_length_of :order_howto, :note, maximum: 250
|
||||||
|
validate :valid_shared_sync_method
|
||||||
validate :uniqueness_of_name
|
validate :uniqueness_of_name
|
||||||
|
|
||||||
scope :undeleted, -> { where(deleted_at: nil) }
|
scope :undeleted, -> { where(deleted_at: nil) }
|
||||||
|
@ -22,16 +23,18 @@ class Supplier < ActiveRecord::Base
|
||||||
# sync all articles with the external database
|
# sync all articles with the external database
|
||||||
# returns an array with articles(and prices), which should be updated (to use in a form)
|
# returns an array with articles(and prices), which should be updated (to use in a form)
|
||||||
# also returns an array with outlisted_articles, which should be deleted
|
# also returns an array with outlisted_articles, which should be deleted
|
||||||
|
# also returns an array with new articles, which should be added (depending on shared_sync_method)
|
||||||
def sync_all
|
def sync_all
|
||||||
updated_articles = Array.new
|
updated_articles = Array.new
|
||||||
outlisted_articles = Array.new
|
outlisted_articles = Array.new
|
||||||
|
new_articles = Array.new
|
||||||
for article in articles.undeleted
|
for article in articles.undeleted
|
||||||
# try to find the associated shared_article
|
# try to find the associated shared_article
|
||||||
shared_article = article.shared_article
|
shared_article = article.shared_article(self)
|
||||||
|
|
||||||
if shared_article # article will be updated
|
if shared_article # article will be updated
|
||||||
|
|
||||||
unequal_attributes = article.shared_article_changed?
|
unequal_attributes = article.shared_article_changed?(self)
|
||||||
unless unequal_attributes.blank? # skip if shared_article has not been changed
|
unless unequal_attributes.blank? # skip if shared_article has not been changed
|
||||||
|
|
||||||
# try to convert different units
|
# try to convert different units
|
||||||
|
@ -63,7 +66,21 @@ class Supplier < ActiveRecord::Base
|
||||||
outlisted_articles << article
|
outlisted_articles << article
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return [updated_articles, outlisted_articles]
|
# Find any new articles, unless the import is manual
|
||||||
|
unless shared_sync_method == 'import'
|
||||||
|
for shared_article in shared_supplier.shared_articles
|
||||||
|
unless articles.undeleted.find_by_order_number(shared_article.number)
|
||||||
|
new_articles << shared_article.build_new_article(self)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return [updated_articles, outlisted_articles, new_articles]
|
||||||
|
end
|
||||||
|
|
||||||
|
# default value
|
||||||
|
def shared_sync_method
|
||||||
|
return unless shared_supplier
|
||||||
|
self[:shared_sync_method] || 'import'
|
||||||
end
|
end
|
||||||
|
|
||||||
def deleted?
|
def deleted?
|
||||||
|
@ -79,6 +96,13 @@ class Supplier < ActiveRecord::Base
|
||||||
|
|
||||||
protected
|
protected
|
||||||
|
|
||||||
|
# make sure the shared_sync_method is allowed for the shared supplier
|
||||||
|
def valid_shared_sync_method
|
||||||
|
if shared_supplier and !shared_supplier.shared_sync_methods.include?(shared_sync_method)
|
||||||
|
errors.add :name, :included
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# Make sure, the name is uniq, add usefull message if uniq group is already deleted
|
# Make sure, the name is uniq, add usefull message if uniq group is already deleted
|
||||||
def uniqueness_of_name
|
def uniqueness_of_name
|
||||||
supplier = Supplier.where(name: name)
|
supplier = Supplier.where(name: name)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
= simple_form_for @article_category do |f|
|
= simple_form_for @article_category do |f|
|
||||||
= f.input :name
|
= f.input :name
|
||||||
= f.input :description
|
= f.input :description, as: :text, input_html: {class: 'input-xxlarge'}
|
||||||
.form-actions
|
.form-actions
|
||||||
= f.submit class: 'btn'
|
= f.submit class: 'btn'
|
||||||
= link_to t('ui.or_cancel'), article_categories_path
|
= link_to t('ui.or_cancel'), article_categories_path
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
- @article_categories.each do |article_category|
|
- @article_categories.each do |article_category|
|
||||||
%tr
|
%tr
|
||||||
%td= article_category.name
|
%td= article_category.name
|
||||||
%td= article_category.description
|
%td= truncate article_category.description, length: 50
|
||||||
%td
|
%td
|
||||||
= link_to t('ui.edit'), edit_article_category_path(article_category), class: 'btn btn-mini'
|
= link_to t('ui.edit'), edit_article_category_path(article_category), class: 'btn btn-mini'
|
||||||
= link_to t('ui.delete'), article_category, :method => :delete, :data => {:confirm => t('.confirm_delete')},
|
= link_to t('ui.delete'), article_category, :method => :delete, :data => {:confirm => t('.confirm_delete')},
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
%tbody
|
%tbody
|
||||||
- for article in @articles
|
- for article in @articles
|
||||||
%tr{id: "shared_article_#{article.id}"}
|
%tr{id: "shared_article_#{article.id}"}
|
||||||
%td= highlight article.name, params[:q][:name_cont_all]
|
%td= highlight article.name, search_params[:name_cont_all]
|
||||||
%td= article.origin
|
%td= article.origin
|
||||||
%td= article.manufacturer
|
%td= article.manufacturer
|
||||||
%td{title: article.note}= truncate(article.note, length: 11)
|
%td{title: article.note}= truncate(article.note, length: 11)
|
||||||
|
|
53
app/views/articles/_sync_table.html.haml
Normal file
53
app/views/articles/_sync_table.html.haml
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
%table.table
|
||||||
|
%thead
|
||||||
|
%tr
|
||||||
|
%th= heading_helper Article, :name
|
||||||
|
%th= heading_helper Article, :note
|
||||||
|
%th= heading_helper Article, :manufacturer
|
||||||
|
%th= heading_helper Article, :origin
|
||||||
|
%th= heading_helper Article, :unit
|
||||||
|
%th= heading_helper Article, :unit_quantity, short: true
|
||||||
|
%th= heading_helper Article, :price
|
||||||
|
%th= heading_helper Article, :tax
|
||||||
|
%th= heading_helper Article, :deposit
|
||||||
|
%th= heading_helper Article, :article_category
|
||||||
|
%tbody
|
||||||
|
- articles.each do |changed_article, attrs|
|
||||||
|
- unless changed_article.new_record?
|
||||||
|
- article = Article.find(changed_article.id)
|
||||||
|
%tr{:style => 'color:grey'}
|
||||||
|
%td= article.name
|
||||||
|
%td= article.note
|
||||||
|
%td= article.manufacturer
|
||||||
|
%td= article.origin
|
||||||
|
%td= article.unit
|
||||||
|
%td= article.unit_quantity
|
||||||
|
%td= number_to_currency article.price
|
||||||
|
%td= number_to_percentage article.tax
|
||||||
|
%td= number_to_currency article.deposit
|
||||||
|
%td= article.article_category.name if article.article_category
|
||||||
|
%tr
|
||||||
|
= fields_for "#{field}[]", changed_article do |form|
|
||||||
|
%td{:style => highlight_new(attrs, :name)}
|
||||||
|
= form.text_field 'name', :size => 0
|
||||||
|
- hidden_fields.each do |field|
|
||||||
|
= form.hidden_field field
|
||||||
|
%td{:style => highlight_new(attrs, :note)}= form.text_field 'note', class: 'input-small'
|
||||||
|
%td{:style => highlight_new(attrs, :manufacturer)}= form.text_field 'manufacturer', class: 'input-small'
|
||||||
|
%td{:style => highlight_new(attrs, :origin)}= form.text_field 'origin', class: 'input-mini'
|
||||||
|
%td{:style => highlight_new(attrs, :unit)}= form.text_field 'unit', class: 'input-mini'
|
||||||
|
%td{:style => highlight_new(attrs, :unit_quantity)}= form.text_field 'unit_quantity', class: 'input-mini'
|
||||||
|
%td{:style => highlight_new(attrs, :price)}
|
||||||
|
.input-prepend
|
||||||
|
%span.add-on= t 'number.currency.format.unit'
|
||||||
|
= form.text_field 'price', class: 'input-mini', style: 'width: 45px'
|
||||||
|
%td{:style => highlight_new(attrs, :tax)}
|
||||||
|
.input-append
|
||||||
|
= form.text_field 'tax', class: 'input-mini', style: 'width: 45px'
|
||||||
|
%span.add-on %
|
||||||
|
%td{:style => highlight_new(attrs, :deposit)}
|
||||||
|
.input-prepend
|
||||||
|
%span.add-on= t 'number.currency.format.unit'
|
||||||
|
= form.text_field 'deposit', class: 'input-mini', style: 'width: 45px'
|
||||||
|
%td= form.select :article_category_id, ArticleCategory.all.map {|a| [ a.name, a.id ] },
|
||||||
|
{include_blank: true}, class: 'input-small'
|
|
@ -18,7 +18,8 @@
|
||||||
|
|
||||||
- if @supplier.shared_supplier
|
- if @supplier.shared_supplier
|
||||||
.btn-group
|
.btn-group
|
||||||
= link_to t('.ext_db.import'), "#import", 'data-toggle-this' => '#import', class: 'btn btn-primary'
|
- if @supplier.shared_sync_method == 'import'
|
||||||
|
= link_to t('.ext_db.import'), "#import", 'data-toggle-this' => '#import', class: 'btn btn-primary'
|
||||||
= link_to t('.ext_db.sync'), sync_supplier_articles_path(@supplier), method: :post, class: 'btn btn-success'
|
= link_to t('.ext_db.sync'), sync_supplier_articles_path(@supplier), method: :post, class: 'btn btn-success'
|
||||||
|
|
||||||
.btn-group
|
.btn-group
|
||||||
|
|
|
@ -1,72 +1,39 @@
|
||||||
- title t('.title')
|
- title t('.title')
|
||||||
|
|
||||||
= form_tag update_synchronized_supplier_articles_path(@supplier) do
|
= form_tag update_synchronized_supplier_articles_path(@supplier) do
|
||||||
%h2= t '.outlist.title'
|
- if @outlisted_articles.any?
|
||||||
%p
|
%h2= t '.outlist.title'
|
||||||
- unless @outlisted_articles.empty?
|
%p
|
||||||
= t('.outlist.body').html_safe
|
= t('.outlist.body').html_safe
|
||||||
%ul
|
%ul
|
||||||
- for article in @outlisted_articles
|
- for article in @outlisted_articles
|
||||||
%li
|
%li
|
||||||
= hidden_field_tag "outlisted_articles[#{article.id}]", '1'
|
= hidden_field_tag "outlisted_articles[#{article.id}]", '1'
|
||||||
= article.name
|
= article.name
|
||||||
- if article.in_open_order
|
- if article.in_open_order
|
||||||
.alert= t '.outlist.alert_used', article: article.name
|
.alert= t '.outlist.alert_used', article: article.name
|
||||||
- else
|
%hr/
|
||||||
%i= t '.outlist.body_skip'
|
|
||||||
|
- if @updated_articles.any?
|
||||||
|
%h2= t '.update.title'
|
||||||
|
%p
|
||||||
|
%i
|
||||||
|
= t '.update.update_msg', count: @updated_articles.size
|
||||||
|
= t '.update.body'
|
||||||
|
= render 'sync_table', articles: @updated_articles, field: 'articles', hidden_fields: %w(shared_updated_on)
|
||||||
|
%hr/
|
||||||
|
|
||||||
|
- if @new_articles.any?
|
||||||
|
%h2= t '.upnew.title'
|
||||||
|
%p
|
||||||
|
%i= t '.upnew.body_count', count: @new_articles.length
|
||||||
|
= render 'sync_table', articles: @new_articles, field: 'new_articles', hidden_fields: %w(shared_updated_on order_number)
|
||||||
|
%hr/
|
||||||
|
|
||||||
- if @ignored_article_count > 0
|
- if @ignored_article_count > 0
|
||||||
%i= t '.outlist.body_ignored', count: @ignored_article_count
|
%p
|
||||||
%hr/
|
%i= t '.outlist.body_ignored', count: @ignored_article_count
|
||||||
%h2= t '.update.title'
|
|
||||||
%p
|
|
||||||
%i
|
|
||||||
= t '.update.update_msg', count: @updated_articles.size
|
|
||||||
= t '.update.body'
|
|
||||||
%table.table
|
|
||||||
%thead
|
|
||||||
%tr
|
|
||||||
%th= heading_helper Article, :name
|
|
||||||
%th= heading_helper Article, :note
|
|
||||||
%th= heading_helper Article, :manufacturer
|
|
||||||
%th= heading_helper Article, :origin
|
|
||||||
%th= heading_helper Article, :unit
|
|
||||||
%th= heading_helper Article, :unit_quantity, short: true
|
|
||||||
%th= heading_helper Article, :price
|
|
||||||
%th= heading_helper Article, :tax
|
|
||||||
%th= heading_helper Article, :deposit
|
|
||||||
%th= heading_helper Article, :article_category
|
|
||||||
%tbody
|
|
||||||
- @updated_articles.each do |updated_article, attrs|
|
|
||||||
- article = Article.find(updated_article.id)
|
|
||||||
%tr{:style => 'color:grey'}
|
|
||||||
%td= article.name
|
|
||||||
%td= article.note
|
|
||||||
%td= article.manufacturer
|
|
||||||
%td= article.origin
|
|
||||||
%td= article.unit
|
|
||||||
%td= article.unit_quantity
|
|
||||||
%td= article.price
|
|
||||||
%td= article.tax
|
|
||||||
%td= article.deposit
|
|
||||||
%td= article.article_category.name if article.article_category
|
|
||||||
%tr
|
|
||||||
= fields_for 'articles[]', updated_article do |form|
|
|
||||||
%td{:style => highlight_new(attrs, :name)}
|
|
||||||
= form.text_field 'name', :size => 0
|
|
||||||
= form.hidden_field 'shared_updated_on'
|
|
||||||
%td{:style => highlight_new(attrs, :note)}= form.text_field 'note', class: 'input-small'
|
|
||||||
%td{:style => highlight_new(attrs, :manufacturer)}= form.text_field 'manufacturer', class: 'input-small'
|
|
||||||
%td{:style => highlight_new(attrs, :origin)}= form.text_field 'origin', class: 'input-mini'
|
|
||||||
%td{:style => highlight_new(attrs, :unit)}= form.text_field 'unit', class: 'input-mini'
|
|
||||||
%td{:style => highlight_new(attrs, :unit_quantity)}= form.text_field 'unit_quantity', class: 'input-mini'
|
|
||||||
%td{:style => highlight_new(attrs, :price)}= form.text_field 'price', class: 'input-mini'
|
|
||||||
%td{:style => highlight_new(attrs, :tax), class: 'input-append'}
|
|
||||||
= form.text_field 'tax', class: 'input-mini'
|
|
||||||
%span.add-on %
|
|
||||||
%td{:style => highlight_new(attrs, :deposit)}= form.text_field 'deposit', class: 'input-mini'
|
|
||||||
%td= form.select :article_category_id, ArticleCategory.all.map {|a| [ a.name, a.id ] },
|
|
||||||
{include_blank: true}, class: 'input-small'
|
|
||||||
%hr/
|
|
||||||
= hidden_field 'supplier', 'id'
|
= hidden_field 'supplier', 'id'
|
||||||
= submit_tag t('.submit'), class: 'btn btn-primary'
|
= submit_tag t('.submit'), class: 'btn btn-primary'
|
||||||
= link_to t('ui.or_cancel'), supplier_articles_path(@supplier)
|
= link_to t('ui.or_cancel'), supplier_articles_path(@supplier)
|
||||||
|
|
|
@ -16,6 +16,8 @@
|
||||||
= f.input :order_howto, as: :text, input_html: {rows: 5}
|
= f.input :order_howto, as: :text, input_html: {rows: 5}
|
||||||
= f.input :note, as: :text, input_html: {rows: 5}
|
= f.input :note, as: :text, input_html: {rows: 5}
|
||||||
= f.input :min_order_quantity
|
= f.input :min_order_quantity
|
||||||
|
- if @supplier.shared_supplier
|
||||||
|
= f.input :shared_sync_method, collection: shared_sync_method_collection(@supplier.shared_supplier), input_html: {class: 'input-xlarge'}, include_blank: false, disabled: @supplier.shared_supplier.shared_sync_methods.count < 2
|
||||||
.form-actions
|
.form-actions
|
||||||
= f.submit class: 'btn'
|
= f.submit class: 'btn'
|
||||||
= link_to t('ui.or_cancel'), suppliers_path
|
= link_to t('ui.or_cancel'), suppliers_path
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
- if shared_supplier = @supplier.shared_supplier
|
- if shared_supplier = @supplier.shared_supplier
|
||||||
.alert.alert-info
|
.alert.alert-info
|
||||||
= t 'suppliers.shared_supplier_note'
|
= t 'suppliers.shared_supplier_note'
|
||||||
|
= t "suppliers.shared_supplier_methods.#{@supplier.shared_sync_method}"
|
||||||
|
|
||||||
%dl.dl-horizontal
|
%dl.dl-horizontal
|
||||||
%dt= heading_helper(Supplier, :address) + ':'
|
%dt= heading_helper(Supplier, :address) + ':'
|
||||||
|
|
|
@ -26,7 +26,7 @@ en:
|
||||||
unit_quantity_short: U.Q.
|
unit_quantity_short: U.Q.
|
||||||
units: Units
|
units: Units
|
||||||
article_category:
|
article_category:
|
||||||
description: Description
|
description: Import names
|
||||||
name: Name
|
name: Name
|
||||||
article_price:
|
article_price:
|
||||||
deposit: Deposit
|
deposit: Deposit
|
||||||
|
@ -135,6 +135,7 @@ en:
|
||||||
order_howto: How to order
|
order_howto: How to order
|
||||||
phone: Phone
|
phone: Phone
|
||||||
phone2: Phone 2
|
phone2: Phone 2
|
||||||
|
shared_sync_method: How to synchronize
|
||||||
url: Homepage
|
url: Homepage
|
||||||
task:
|
task:
|
||||||
description: Description
|
description: Description
|
||||||
|
@ -172,6 +173,15 @@ en:
|
||||||
errors:
|
errors:
|
||||||
has_many_left: is still associated with a %{collection}!
|
has_many_left: is still associated with a %{collection}!
|
||||||
models:
|
models:
|
||||||
|
article:
|
||||||
|
attributes:
|
||||||
|
name:
|
||||||
|
taken: name is already taken
|
||||||
|
taken_with_unit: name and unit are already taken
|
||||||
|
supplier:
|
||||||
|
attributes:
|
||||||
|
shared_sync_method:
|
||||||
|
included: is not a valid option for this supplier
|
||||||
task:
|
task:
|
||||||
attributes:
|
attributes:
|
||||||
done:
|
done:
|
||||||
|
@ -396,6 +406,11 @@ en:
|
||||||
update_msg:
|
update_msg:
|
||||||
one: One article needs to be updated.
|
one: One article needs to be updated.
|
||||||
other: '%{count} articles need to be updated.'
|
other: '%{count} articles need to be updated.'
|
||||||
|
upnew:
|
||||||
|
body_count:
|
||||||
|
one: There is one new article to add.
|
||||||
|
other: 'There are %{count} articles to add.'
|
||||||
|
title: Add new ...
|
||||||
upload:
|
upload:
|
||||||
body: <p>The file has to be a text file with the ending '.csv' The first line will be ignored when imported</p> <p>The fields have to be separated with semicolons (';') and the text enclosed by double quotation marks ("text...").</p> <p>As character set UTF-8 is demanded. Correct order of the column:</p>
|
body: <p>The file has to be a text file with the ending '.csv' The first line will be ignored when imported</p> <p>The fields have to be separated with semicolons (';') and the text enclosed by double quotation marks ("text...").</p> <p>As character set UTF-8 is demanded. Correct order of the column:</p>
|
||||||
fields:
|
fields:
|
||||||
|
@ -1299,6 +1314,8 @@ en:
|
||||||
hints:
|
hints:
|
||||||
article:
|
article:
|
||||||
unit: e.g. KG or 1L or 500g
|
unit: e.g. KG or 1L or 500g
|
||||||
|
article_category:
|
||||||
|
description: comma-separated list of category names recognised at import/synchronisation
|
||||||
message:
|
message:
|
||||||
private: Message doesn’t show in Foodsoft mail inbox
|
private: Message doesn’t show in Foodsoft mail inbox
|
||||||
order_article:
|
order_article:
|
||||||
|
@ -1435,6 +1452,10 @@ en:
|
||||||
new:
|
new:
|
||||||
title: New supplier
|
title: New supplier
|
||||||
shared_supplier_note: Supplier is connected to the external database.
|
shared_supplier_note: Supplier is connected to the external database.
|
||||||
|
shared_supplier_methods:
|
||||||
|
import: Articles are imported manually.
|
||||||
|
all_available: New articles are available by default.
|
||||||
|
all_unavailable: New articles are not available by default.
|
||||||
shared_suppliers:
|
shared_suppliers:
|
||||||
body: <p>Suppliers of the external database are displayed here.</p> <p>You can import external suppliers by subscribing (see below).</p> <p>A new supplier will be created and connected to the external database.</p>
|
body: <p>Suppliers of the external database are displayed here.</p> <p>You can import external suppliers by subscribing (see below).</p> <p>A new supplier will be created and connected to the external database.</p>
|
||||||
subscribe: Subscribe
|
subscribe: Subscribe
|
||||||
|
|
|
@ -26,7 +26,7 @@ nl:
|
||||||
unit_quantity_short: Colli
|
unit_quantity_short: Colli
|
||||||
units: Eenheden
|
units: Eenheden
|
||||||
article_category:
|
article_category:
|
||||||
description: Omschrijving
|
description: Import namen
|
||||||
name: Naam
|
name: Naam
|
||||||
article_price:
|
article_price:
|
||||||
deposit: Statiegeld
|
deposit: Statiegeld
|
||||||
|
@ -1294,6 +1294,8 @@ nl:
|
||||||
hints:
|
hints:
|
||||||
article:
|
article:
|
||||||
unit: 'Bijvoorbeeld: KG of 1L of 500g'
|
unit: 'Bijvoorbeeld: KG of 1L of 500g'
|
||||||
|
article_category:
|
||||||
|
description: komma-gescheiden lijst van namen die herkend worden bij uploaden/synchroniseren
|
||||||
message:
|
message:
|
||||||
private: Bericht wordt niet getoond in de Foodsoft inbox.
|
private: Bericht wordt niet getoond in de Foodsoft inbox.
|
||||||
order_article:
|
order_article:
|
||||||
|
|
5
db/migrate/20140521142651_add_sync_method_to_supplier.rb
Normal file
5
db/migrate/20140521142651_add_sync_method_to_supplier.rb
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
class AddSyncMethodToSupplier < ActiveRecord::Migration
|
||||||
|
def change
|
||||||
|
add_column :suppliers, :shared_sync_method, :string
|
||||||
|
end
|
||||||
|
end
|
|
@ -11,7 +11,7 @@
|
||||||
#
|
#
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
# It's strongly recommended that you check this file into your version control system.
|
||||||
|
|
||||||
ActiveRecord::Schema.define(version: 20140318173000) do
|
ActiveRecord::Schema.define(version: 20140521142651) do
|
||||||
|
|
||||||
create_table "article_categories", force: true do |t|
|
create_table "article_categories", force: true do |t|
|
||||||
t.string "name", default: "", null: false
|
t.string "name", default: "", null: false
|
||||||
|
@ -317,6 +317,7 @@ ActiveRecord::Schema.define(version: 20140318173000) do
|
||||||
t.integer "shared_supplier_id"
|
t.integer "shared_supplier_id"
|
||||||
t.string "min_order_quantity"
|
t.string "min_order_quantity"
|
||||||
t.datetime "deleted_at"
|
t.datetime "deleted_at"
|
||||||
|
t.string "shared_sync_method"
|
||||||
end
|
end
|
||||||
|
|
||||||
add_index "suppliers", ["name"], name: "index_suppliers_on_name", unique: true, using: :btree
|
add_index "suppliers", ["name"], name: "index_suppliers_on_name", unique: true, using: :btree
|
||||||
|
|
|
@ -9,7 +9,7 @@ FactoryGirl.define do
|
||||||
tax { [6, 21].sample }
|
tax { [6, 21].sample }
|
||||||
deposit { rand(10) < 8 ? 0 : [0.0, 0.80, 1.20, 12.00].sample }
|
deposit { rand(10) < 8 ? 0 : [0.0, 0.80, 1.20, 12.00].sample }
|
||||||
unit_quantity { rand(5) < 3 ? 1 : rand(1..20) }
|
unit_quantity { rand(5) < 3 ? 1 : rand(1..20) }
|
||||||
#supplier_id
|
supplier { create :supplier }
|
||||||
article_category { create :article_category }
|
article_category { create :article_category }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -108,7 +108,7 @@ describe Article do
|
||||||
|
|
||||||
it 'does not synchronise when it has no order number' do
|
it 'does not synchronise when it has no order number' do
|
||||||
article.update_attributes :order_number => nil
|
article.update_attributes :order_number => nil
|
||||||
expect(supplier.sync_all).to eq [[], []]
|
expect(supplier.sync_all).to eq [[], [], []]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -22,6 +22,9 @@ ActiveSupport.on_load(:after_initialize) do
|
||||||
SharedArticle.class_eval do
|
SharedArticle.class_eval do
|
||||||
alias_attribute :number, :order_number
|
alias_attribute :number, :order_number
|
||||||
alias_attribute :updated_on, :updated_at
|
alias_attribute :updated_on, :updated_at
|
||||||
|
def category
|
||||||
|
ArticleCategory.find(article_category_id).name
|
||||||
|
end
|
||||||
def self.find_by_number(n)
|
def self.find_by_number(n)
|
||||||
find_by_order_number(n)
|
find_by_order_number(n)
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue