2023-01-31 15:25:36 +01:00
|
|
|
require 'foodsoft_article_import'
|
2019-01-13 07:05:54 +01:00
|
|
|
class Supplier < ApplicationRecord
|
2015-04-17 20:05:46 +02:00
|
|
|
include MarkAsDeletedWithName
|
2017-10-12 20:50:40 +02:00
|
|
|
include CustomFields
|
2015-04-17 20:05:46 +02:00
|
|
|
|
2014-02-20 15:04:53 +01:00
|
|
|
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') }
|
2009-01-06 11:49:19 +01:00
|
|
|
has_many :orders
|
2009-01-08 16:33:27 +01:00
|
|
|
has_many :deliveries
|
|
|
|
has_many :invoices
|
2019-11-17 11:41:34 +01:00
|
|
|
belongs_to :supplier_category
|
2021-03-01 15:27:26 +01:00
|
|
|
belongs_to :shared_supplier, optional: true # for the sharedLists-App
|
2009-01-08 16:33:27 +01:00
|
|
|
|
2013-02-24 23:26:16 +01:00
|
|
|
validates :name, :presence => true, :length => { :in => 4..30 }
|
2013-07-15 17:57:00 +02:00
|
|
|
validates :phone, :presence => true, :length => { :in => 8..25 }
|
2011-05-18 15:52:06 +02:00
|
|
|
validates :address, :presence => true, :length => { :in => 8..50 }
|
2017-01-18 11:12:56 +01:00
|
|
|
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
|
2012-10-17 20:45:52 +02:00
|
|
|
validates_length_of :order_howto, :note, maximum: 250
|
2014-05-21 21:24:03 +02:00
|
|
|
validate :valid_shared_sync_method
|
2013-02-24 23:26:16 +01:00
|
|
|
validate :uniqueness_of_name
|
2009-02-11 15:23:59 +01:00
|
|
|
|
2013-03-16 17:53:24 +01:00
|
|
|
scope :undeleted, -> { where(deleted_at: nil) }
|
2015-02-18 23:46:50 +01:00
|
|
|
scope :having_articles, -> { where(id: Article.undeleted.select(:supplier_id).distinct) }
|
2013-03-16 17:53:24 +01:00
|
|
|
|
2018-10-13 16:16:44 +02:00
|
|
|
def self.ransackable_attributes(auth_object = nil)
|
|
|
|
%w(id name)
|
|
|
|
end
|
|
|
|
|
|
|
|
def self.ransackable_associations(auth_object = nil)
|
|
|
|
%w(articles stock_articles orders)
|
|
|
|
end
|
|
|
|
|
2009-01-06 11:49:19 +01:00
|
|
|
# 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
|
2014-05-21 21:24:03 +02:00
|
|
|
# also returns an array with new articles, which should be added (depending on shared_sync_method)
|
2009-01-06 11:49:19 +01:00
|
|
|
def sync_all
|
2015-03-27 19:03:21 +01:00
|
|
|
updated_article_pairs, outlisted_articles, new_articles = [], [], []
|
2019-01-15 02:56:21 +01:00
|
|
|
existing_articles = Set.new
|
2013-03-16 17:53:24 +01:00
|
|
|
for article in articles.undeleted
|
2009-01-06 11:49:19 +01:00
|
|
|
# try to find the associated shared_article
|
2014-05-21 21:44:35 +02:00
|
|
|
shared_article = article.shared_article(self)
|
2009-02-10 13:26:10 +01:00
|
|
|
|
|
|
|
if shared_article # article will be updated
|
2019-01-15 02:56:21 +01:00
|
|
|
existing_articles.add(shared_article.id)
|
2014-05-21 21:44:35 +02:00
|
|
|
unequal_attributes = article.shared_article_changed?(self)
|
2009-02-10 13:26:10 +01:00
|
|
|
unless unequal_attributes.blank? # skip if shared_article has not been changed
|
2015-01-18 02:04:57 +01:00
|
|
|
article.attributes = unequal_attributes
|
2015-03-27 19:03:21 +01:00
|
|
|
updated_article_pairs << [article, unequal_attributes]
|
2009-01-06 11:49:19 +01:00
|
|
|
end
|
2014-01-24 22:10:00 +01:00
|
|
|
# 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?
|
2009-01-06 11:49:19 +01:00
|
|
|
# article isn't in external database anymore
|
|
|
|
outlisted_articles << article
|
|
|
|
end
|
|
|
|
end
|
2014-05-21 21:24:03 +02:00
|
|
|
# Find any new articles, unless the import is manual
|
2019-03-24 22:14:10 +01:00
|
|
|
if ['all_available', 'all_unavailable'].include?(shared_sync_method)
|
|
|
|
# build new articles
|
2019-01-15 02:56:21 +01:00
|
|
|
shared_supplier
|
|
|
|
.shared_articles
|
2019-03-23 18:31:22 +01:00
|
|
|
.where.not(id: existing_articles.to_a)
|
2019-03-24 22:02:53 +01:00
|
|
|
.find_each { |new_shared_article| new_articles << new_shared_article.build_new_article(self) }
|
2019-03-24 22:14:10 +01:00
|
|
|
# make them unavailable when desired
|
|
|
|
if shared_sync_method == 'all_unavailable'
|
2021-03-01 15:27:26 +01:00
|
|
|
new_articles.each { |new_article| new_article.availability = false }
|
2019-03-24 22:14:10 +01:00
|
|
|
end
|
2014-05-21 21:24:03 +02:00
|
|
|
end
|
2015-03-27 19:03:21 +01:00
|
|
|
return [updated_article_pairs, outlisted_articles, new_articles]
|
|
|
|
end
|
|
|
|
|
|
|
|
# Synchronise articles with spreadsheet.
|
|
|
|
#
|
|
|
|
# @param file [File] Spreadsheet file to parse
|
2023-01-31 15:25:36 +01:00
|
|
|
# @param options [Hash] Options passed to {FoodsoftArticleImport#parse} except when listed here.
|
2015-03-27 19:03:21 +01:00
|
|
|
# @option options [Boolean] :outlist_absent Set to +true+ to remove articles not in spreadsheet.
|
2015-04-10 20:05:42 +02:00
|
|
|
# @option options [Boolean] :convert_units Omit or set to +true+ to keep current units, recomputing unit quantity and price.
|
2023-01-31 15:25:36 +01:00
|
|
|
def sync_from_file(file, type, options = {})
|
2015-04-10 19:41:08 +02:00
|
|
|
all_order_numbers = []
|
2015-03-27 19:03:21 +01:00
|
|
|
updated_article_pairs, outlisted_articles, new_articles = [], [], []
|
2023-01-31 15:25:36 +01:00
|
|
|
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|
|
2015-03-27 19:03:21 +01:00
|
|
|
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
|
2015-04-10 20:05:42 +02:00
|
|
|
unequal_attributes = article.unequal_attributes(new_article, options.slice(:convert_units))
|
2015-03-27 19:03:21 +01:00
|
|
|
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
|
2015-04-10 19:41:08 +02:00
|
|
|
|
|
|
|
all_order_numbers << article.order_number if article
|
|
|
|
end
|
|
|
|
if options[:outlist_absent]
|
2021-03-01 15:27:26 +01:00
|
|
|
outlisted_articles += articles.undeleted.where.not(order_number: all_order_numbers + [nil])
|
2015-03-27 19:03:21 +01:00
|
|
|
end
|
|
|
|
return [updated_article_pairs, outlisted_articles, new_articles]
|
2014-05-21 21:24:03 +02:00
|
|
|
end
|
|
|
|
|
|
|
|
# default value
|
|
|
|
def shared_sync_method
|
|
|
|
return unless shared_supplier
|
2021-03-01 15:27:26 +01:00
|
|
|
|
2014-05-21 21:24:03 +02:00
|
|
|
self[:shared_sync_method] || 'import'
|
2009-01-06 11:49:19 +01:00
|
|
|
end
|
2013-02-24 23:26:16 +01:00
|
|
|
|
2013-03-16 17:53:24 +01:00
|
|
|
def deleted?
|
|
|
|
deleted_at.present?
|
|
|
|
end
|
|
|
|
|
|
|
|
def mark_as_deleted
|
|
|
|
transaction do
|
2015-04-17 20:05:46 +02:00
|
|
|
super
|
2020-06-22 16:31:13 +02:00
|
|
|
update_column :iban, nil
|
2013-03-16 17:53:24 +01:00
|
|
|
articles.each(&:mark_as_deleted)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2015-09-23 22:38:20 +02:00
|
|
|
# @return [Boolean] Whether there are articles that would use tolerance (unit_quantity > 1)
|
|
|
|
def has_tolerance?
|
|
|
|
articles.where('articles.unit_quantity > 1').any?
|
|
|
|
end
|
|
|
|
|
2013-02-24 23:26:16 +01:00
|
|
|
protected
|
|
|
|
|
2014-05-21 21:24:03 +02:00
|
|
|
# make sure the shared_sync_method is allowed for the shared supplier
|
|
|
|
def valid_shared_sync_method
|
2015-01-14 21:15:08 +01:00
|
|
|
if shared_supplier && !shared_supplier.shared_sync_methods.include?(shared_sync_method)
|
2019-01-14 19:35:01 +01:00
|
|
|
errors.add :shared_sync_method, :included
|
2014-05-21 21:24:03 +02:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2013-02-24 23:26:16 +01:00
|
|
|
# Make sure, the name is uniq, add usefull message if uniq group is already deleted
|
|
|
|
def uniqueness_of_name
|
2014-02-20 15:04:53 +01:00
|
|
|
supplier = Supplier.where(name: name)
|
|
|
|
supplier = supplier.where.not(id: self.id) unless new_record?
|
2013-07-15 18:03:02 +02:00
|
|
|
if supplier.exists?
|
|
|
|
message = supplier.first.deleted? ? :taken_with_deleted : :taken
|
2013-02-24 23:26:16 +01:00
|
|
|
errors.add :name, message
|
|
|
|
end
|
|
|
|
end
|
2009-01-06 11:49:19 +01:00
|
|
|
end
|