viehlieb
21a9121c7f
use filetypes for manual uploading bnn, odin, foodsoft file use opts in .parse adapt specs to include file format add specs for odin, bnn, foodsoft files adapt localize input to remove ',' separator and replace with '.' remove depr foodsoftfile.rb and spreadsheet.rb remove todo
161 lines
6.4 KiB
Ruby
161 lines
6.4 KiB
Ruby
require 'foodsoft_article_import'
|
|
class Supplier < ApplicationRecord
|
|
include MarkAsDeletedWithName
|
|
include CustomFields
|
|
|
|
has_many :articles, -> { where(:type => nil).includes(:article_category).order('article_categories.name', 'articles.name') }
|
|
has_many :stock_articles, -> { includes(:article_category).order('article_categories.name', 'articles.name') }
|
|
has_many :orders
|
|
has_many :deliveries
|
|
has_many :invoices
|
|
belongs_to :supplier_category
|
|
belongs_to :shared_supplier, optional: true # for the sharedLists-App
|
|
|
|
validates :name, :presence => true, :length => { :in => 4..30 }
|
|
validates :phone, :presence => true, :length => { :in => 8..25 }
|
|
validates :address, :presence => true, :length => { :in => 8..50 }
|
|
validates_format_of :iban, :with => /\A[A-Z]{2}[0-9]{2}[0-9A-Z]{,30}\z/, :allow_blank => true
|
|
validates_uniqueness_of :iban, :case_sensitive => false, :allow_blank => true
|
|
validates_length_of :order_howto, :note, maximum: 250
|
|
validate :valid_shared_sync_method
|
|
validate :uniqueness_of_name
|
|
|
|
scope :undeleted, -> { where(deleted_at: nil) }
|
|
scope :having_articles, -> { where(id: Article.undeleted.select(:supplier_id).distinct) }
|
|
|
|
def self.ransackable_attributes(auth_object = nil)
|
|
%w(id name)
|
|
end
|
|
|
|
def self.ransackable_associations(auth_object = nil)
|
|
%w(articles stock_articles orders)
|
|
end
|
|
|
|
# sync all articles with the external database
|
|
# 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 new articles, which should be added (depending on shared_sync_method)
|
|
def sync_all
|
|
updated_article_pairs, outlisted_articles, new_articles = [], [], []
|
|
existing_articles = Set.new
|
|
for article in articles.undeleted
|
|
# try to find the associated shared_article
|
|
shared_article = article.shared_article(self)
|
|
|
|
if shared_article # article will be updated
|
|
existing_articles.add(shared_article.id)
|
|
unequal_attributes = article.shared_article_changed?(self)
|
|
unless unequal_attributes.blank? # skip if shared_article has not been changed
|
|
article.attributes = unequal_attributes
|
|
updated_article_pairs << [article, unequal_attributes]
|
|
end
|
|
# Articles with no order number can be used to put non-shared articles
|
|
# in a shared supplier, with sync keeping them.
|
|
elsif not article.order_number.blank?
|
|
# article isn't in external database anymore
|
|
outlisted_articles << article
|
|
end
|
|
end
|
|
# Find any new articles, unless the import is manual
|
|
if ['all_available', 'all_unavailable'].include?(shared_sync_method)
|
|
# build new articles
|
|
shared_supplier
|
|
.shared_articles
|
|
.where.not(id: existing_articles.to_a)
|
|
.find_each { |new_shared_article| new_articles << new_shared_article.build_new_article(self) }
|
|
# make them unavailable when desired
|
|
if shared_sync_method == 'all_unavailable'
|
|
new_articles.each { |new_article| new_article.availability = false }
|
|
end
|
|
end
|
|
return [updated_article_pairs, outlisted_articles, new_articles]
|
|
end
|
|
|
|
# Synchronise articles with spreadsheet.
|
|
#
|
|
# @param file [File] Spreadsheet file to parse
|
|
# @param options [Hash] Options passed to {FoodsoftArticleImport#parse} except when listed here.
|
|
# @option options [Boolean] :outlist_absent Set to +true+ to remove articles not in spreadsheet.
|
|
# @option options [Boolean] :convert_units Omit or set to +true+ to keep current units, recomputing unit quantity and price.
|
|
def sync_from_file(file, type, options = {})
|
|
all_order_numbers = []
|
|
updated_article_pairs, outlisted_articles, new_articles = [], [], []
|
|
custom_codes_path = File.join(Rails.root, "config", "custom_codes.yml")
|
|
opts = options.except(:convert_units, :outlist_absent)
|
|
custom_codes_file_path = custom_codes_path if File.exist?(custom_codes_path)
|
|
FoodsoftArticleImport.parse(file, custom_file_path: custom_codes_file_path, type: type, **opts) do |new_attrs, status, line|
|
|
article = articles.undeleted.where(order_number: new_attrs[:order_number]).first
|
|
new_attrs[:article_category] = ArticleCategory.find_match(new_attrs[:article_category])
|
|
new_attrs[:tax] ||= FoodsoftConfig[:tax_default]
|
|
new_article = articles.build(new_attrs)
|
|
|
|
if status.nil?
|
|
if article.nil?
|
|
new_articles << new_article
|
|
else
|
|
unequal_attributes = article.unequal_attributes(new_article, options.slice(:convert_units))
|
|
unless unequal_attributes.empty?
|
|
article.attributes = unequal_attributes
|
|
updated_article_pairs << [article, unequal_attributes]
|
|
end
|
|
end
|
|
elsif status == :outlisted && article.present?
|
|
outlisted_articles << article
|
|
|
|
# stop when there is a parsing error
|
|
elsif status.is_a? String
|
|
# @todo move I18n key to model
|
|
raise I18n.t('articles.model.error_parse', :msg => status, :line => line.to_s)
|
|
end
|
|
|
|
all_order_numbers << article.order_number if article
|
|
end
|
|
if options[:outlist_absent]
|
|
outlisted_articles += articles.undeleted.where.not(order_number: all_order_numbers + [nil])
|
|
end
|
|
return [updated_article_pairs, outlisted_articles, new_articles]
|
|
end
|
|
|
|
# default value
|
|
def shared_sync_method
|
|
return unless shared_supplier
|
|
|
|
self[:shared_sync_method] || 'import'
|
|
end
|
|
|
|
def deleted?
|
|
deleted_at.present?
|
|
end
|
|
|
|
def mark_as_deleted
|
|
transaction do
|
|
super
|
|
update_column :iban, nil
|
|
articles.each(&:mark_as_deleted)
|
|
end
|
|
end
|
|
|
|
# @return [Boolean] Whether there are articles that would use tolerance (unit_quantity > 1)
|
|
def has_tolerance?
|
|
articles.where('articles.unit_quantity > 1').any?
|
|
end
|
|
|
|
protected
|
|
|
|
# make sure the shared_sync_method is allowed for the shared supplier
|
|
def valid_shared_sync_method
|
|
if shared_supplier && !shared_supplier.shared_sync_methods.include?(shared_sync_method)
|
|
errors.add :shared_sync_method, :included
|
|
end
|
|
end
|
|
|
|
# Make sure, the name is uniq, add usefull message if uniq group is already deleted
|
|
def uniqueness_of_name
|
|
supplier = Supplier.where(name: name)
|
|
supplier = supplier.where.not(id: self.id) unless new_record?
|
|
if supplier.exists?
|
|
message = supplier.first.deleted? ? :taken_with_deleted : :taken
|
|
errors.add :name, message
|
|
end
|
|
end
|
|
end
|