2016-03-23 18:39:41 +01:00
|
|
|
class Invoice < ActiveRecord::Base
|
|
|
|
include NotifyWith::NotificationAttachedObject
|
|
|
|
require 'fileutils'
|
|
|
|
scope :only_invoice, -> { where(type: nil) }
|
|
|
|
belongs_to :invoiced, polymorphic: true
|
|
|
|
|
|
|
|
has_many :invoice_items, dependent: :destroy
|
|
|
|
accepts_nested_attributes_for :invoice_items
|
|
|
|
belongs_to :user
|
2016-07-20 15:07:43 +02:00
|
|
|
belongs_to :wallet_transaction
|
2016-08-03 17:25:00 +02:00
|
|
|
belongs_to :coupon
|
2016-03-23 18:39:41 +01:00
|
|
|
|
|
|
|
has_one :avoir, class_name: 'Invoice', foreign_key: :invoice_id, dependent: :destroy
|
|
|
|
|
|
|
|
after_create :update_reference
|
|
|
|
after_commit :generate_and_send_invoice, on: [:create]
|
|
|
|
|
|
|
|
def file
|
|
|
|
dir = "invoices/#{user.id}"
|
|
|
|
|
|
|
|
# create directories if they doesn't exists (invoice & user_id)
|
|
|
|
FileUtils::mkdir_p dir
|
2016-03-29 18:02:40 +02:00
|
|
|
"#{dir}/#{self.filename}"
|
|
|
|
end
|
|
|
|
|
|
|
|
def filename
|
|
|
|
"#{ENV['INVOICE_PREFIX']}-#{self.id}_#{self.created_at.strftime('%d%m%Y')}.pdf"
|
2016-03-23 18:39:41 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
def generate_reference
|
|
|
|
pattern = Setting.find_by({name: 'invoice_reference'}).value
|
|
|
|
|
|
|
|
# invoice number per day (dd..dd)
|
|
|
|
reference = pattern.gsub(/d+(?![^\[]*\])/) do |match|
|
|
|
|
pad_and_truncate(number_of_invoices('day'), match.to_s.length)
|
|
|
|
end
|
|
|
|
# invoice number per month (mm..mm)
|
|
|
|
reference.gsub!(/m+(?![^\[]*\])/) do |match|
|
|
|
|
pad_and_truncate(number_of_invoices('month'), match.to_s.length)
|
|
|
|
end
|
|
|
|
# invoice number per year (yy..yy)
|
|
|
|
reference.gsub!(/y+(?![^\[]*\])/) do |match|
|
|
|
|
pad_and_truncate(number_of_invoices('year'), match.to_s.length)
|
|
|
|
end
|
|
|
|
|
|
|
|
# full year (YYYY)
|
|
|
|
reference.gsub!(/YYYY(?![^\[]*\])/, Time.now.strftime('%Y'))
|
|
|
|
# year without century (YY)
|
|
|
|
reference.gsub!(/YY(?![^\[]*\])/, Time.now.strftime('%y'))
|
|
|
|
|
|
|
|
# abreviated month name (MMM)
|
|
|
|
reference.gsub!(/MMM(?![^\[]*\])/, Time.now.strftime('%^b'))
|
|
|
|
# month of the year, zero-padded (MM)
|
|
|
|
reference.gsub!(/MM(?![^\[]*\])/, Time.now.strftime('%m'))
|
|
|
|
# month of the year, non zero-padded (M)
|
|
|
|
reference.gsub!(/M(?![^\[]*\])/, Time.now.strftime('%-m'))
|
|
|
|
|
|
|
|
# day of the month, zero-padded (DD)
|
|
|
|
reference.gsub!(/DD(?![^\[]*\])/, Time.now.strftime('%d'))
|
|
|
|
# day of the month, non zero-padded (DD)
|
|
|
|
reference.gsub!(/DD(?![^\[]*\])/, Time.now.strftime('%-d'))
|
|
|
|
|
|
|
|
# information about online selling (X[text])
|
|
|
|
if self.stp_invoice_id
|
|
|
|
reference.gsub!(/X\[([^\]]+)\]/, '\1')
|
|
|
|
else
|
|
|
|
reference.gsub!(/X\[([^\]]+)\]/, ''.to_s)
|
|
|
|
end
|
|
|
|
|
2016-07-12 12:29:51 +02:00
|
|
|
# information about wallet (W[text])
|
|
|
|
reference.gsub!(/W\[([^\]]+)\]/, ''.to_s)
|
|
|
|
|
2016-03-23 18:39:41 +01:00
|
|
|
# remove information about refunds (R[text])
|
|
|
|
reference.gsub!(/R\[([^\]]+)\]/, ''.to_s)
|
|
|
|
|
|
|
|
self.reference = reference
|
|
|
|
end
|
|
|
|
|
|
|
|
def update_reference
|
|
|
|
generate_reference
|
|
|
|
save
|
|
|
|
end
|
|
|
|
|
|
|
|
def order_number
|
|
|
|
pattern = Setting.find_by({name: 'invoice_order-nb'}).value
|
|
|
|
|
|
|
|
# global invoice number (nn..nn)
|
|
|
|
reference = pattern.gsub(/n+(?![^\[]*\])/) do |match|
|
|
|
|
pad_and_truncate(number_of_invoices('global'), match.to_s.length)
|
|
|
|
end
|
|
|
|
# invoice number per year (yy..yy)
|
|
|
|
reference.gsub!(/y+(?![^\[]*\])/) do |match|
|
|
|
|
pad_and_truncate(number_of_invoices('year'), match.to_s.length)
|
|
|
|
end
|
|
|
|
# invoice number per month (mm..mm)
|
|
|
|
reference.gsub!(/m+(?![^\[]*\])/) do |match|
|
|
|
|
pad_and_truncate(number_of_invoices('month'), match.to_s.length)
|
|
|
|
end
|
|
|
|
# invoice number per day (dd..dd)
|
|
|
|
reference.gsub!(/d+(?![^\[]*\])/) do |match|
|
|
|
|
pad_and_truncate(number_of_invoices('day'), match.to_s.length)
|
|
|
|
end
|
|
|
|
|
|
|
|
# full year (YYYY)
|
|
|
|
reference.gsub!(/YYYY(?![^\[]*\])/, self.created_at.strftime('%Y'))
|
|
|
|
# year without century (YY)
|
|
|
|
reference.gsub!(/YY(?![^\[]*\])/, self.created_at.strftime('%y'))
|
|
|
|
|
|
|
|
# abreviated month name (MMM)
|
|
|
|
reference.gsub!(/MMM(?![^\[]*\])/, self.created_at.strftime('%^b'))
|
|
|
|
# month of the year, zero-padded (MM)
|
|
|
|
reference.gsub!(/MM(?![^\[]*\])/, self.created_at.strftime('%m'))
|
|
|
|
# month of the year, non zero-padded (M)
|
|
|
|
reference.gsub!(/M(?![^\[]*\])/, self.created_at.strftime('%-m'))
|
|
|
|
|
|
|
|
# day of the month, zero-padded (DD)
|
|
|
|
reference.gsub!(/DD(?![^\[]*\])/, self.created_at.strftime('%d'))
|
|
|
|
# day of the month, non zero-padded (DD)
|
|
|
|
reference.gsub!(/DD(?![^\[]*\])/, self.created_at.strftime('%-d'))
|
|
|
|
|
|
|
|
reference
|
|
|
|
end
|
|
|
|
|
2016-04-06 17:46:24 +02:00
|
|
|
# for debug & used by rake task "fablab:regenerate_invoices"
|
2016-03-23 18:39:41 +01:00
|
|
|
def regenerate_invoice_pdf
|
|
|
|
pdf = ::PDF::Invoice.new(self).render
|
|
|
|
File.binwrite(file, pdf)
|
|
|
|
end
|
|
|
|
|
|
|
|
def build_avoir(attrs = {})
|
|
|
|
raise Exception if has_avoir === true or prevent_refund?
|
|
|
|
avoir = Avoir.new(self.dup.attributes)
|
|
|
|
avoir.type = 'Avoir'
|
|
|
|
avoir.attributes = attrs
|
|
|
|
avoir.reference = nil
|
|
|
|
avoir.invoice_id = id
|
|
|
|
# override created_at to compute CA in stats
|
|
|
|
avoir.created_at = avoir.avoir_date
|
|
|
|
avoir.total = 0
|
|
|
|
invoice_items.each do |ii|
|
|
|
|
if attrs[:invoice_items_ids].include? ii.id
|
|
|
|
raise Exception if ii.invoice_item
|
|
|
|
avoir_ii = avoir.invoice_items.build(ii.dup.attributes)
|
|
|
|
avoir_ii.created_at = avoir.avoir_date
|
|
|
|
avoir_ii.invoice_item_id = ii.id
|
|
|
|
avoir.total += avoir_ii.amount
|
|
|
|
end
|
|
|
|
end
|
2016-08-11 13:44:42 +02:00
|
|
|
# handle coupon
|
|
|
|
unless avoir.coupon_id.nil?
|
2016-08-16 10:29:08 +02:00
|
|
|
discount = avoir.total * avoir.coupon.percent_off / 100.0
|
2016-08-11 13:44:42 +02:00
|
|
|
avoir.total -= discount
|
|
|
|
end
|
2016-03-23 18:39:41 +01:00
|
|
|
avoir
|
|
|
|
end
|
|
|
|
|
|
|
|
def is_subscription_invoice?
|
|
|
|
invoice_items.each do |ii|
|
|
|
|
return true if ii.subscription and !ii.subscription.is_expired?
|
|
|
|
end
|
|
|
|
false
|
|
|
|
end
|
|
|
|
|
|
|
|
##
|
|
|
|
# Test if the current invoice has been refund, totally or partially.
|
|
|
|
# @return {Boolean|'partial'}, true means fully refund, false means not refunded
|
|
|
|
##
|
|
|
|
def has_avoir
|
|
|
|
if avoir
|
|
|
|
invoice_items.each do |item|
|
|
|
|
return 'partial' unless item.invoice_item
|
|
|
|
end
|
|
|
|
true
|
|
|
|
else
|
|
|
|
false
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
##
|
|
|
|
# Check if the current invoice is about a training that was previously validated for the concerned user.
|
2016-07-27 11:28:54 +02:00
|
|
|
# In that case refunding the invoice shouldn't be allowed.
|
2016-03-23 18:39:41 +01:00
|
|
|
# @return {Boolean}
|
|
|
|
##
|
|
|
|
def prevent_refund?
|
|
|
|
if invoiced_type == 'Reservation' and invoiced.reservable_type == 'Training'
|
|
|
|
user.trainings.include?(invoiced.reservable_id)
|
|
|
|
else
|
|
|
|
false
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
private
|
|
|
|
def generate_and_send_invoice
|
|
|
|
InvoiceWorker.perform_async(id)
|
|
|
|
end
|
|
|
|
|
|
|
|
##
|
|
|
|
# Output the given integer with leading zeros. If the given value is longer than the given
|
|
|
|
# length, it will be truncated.
|
|
|
|
# @param value {Integer} the integer to pad
|
|
|
|
# @param length {Integer} the length of the resulting string.
|
|
|
|
##
|
|
|
|
def pad_and_truncate (value, length)
|
|
|
|
value.to_s.rjust(length, '0').gsub(/^.*(.{#{length},}?)$/m,'\1')
|
|
|
|
end
|
|
|
|
|
|
|
|
##
|
|
|
|
# Returns the number of current invoices in the given range around the current date.
|
|
|
|
# If range is invalid or not specified, the total number of invoices is returned.
|
|
|
|
# @param range {String} 'day', 'month', 'year'
|
|
|
|
# @return {Integer}
|
|
|
|
##
|
|
|
|
def number_of_invoices(range)
|
|
|
|
case range.to_s
|
|
|
|
when 'day'
|
|
|
|
start = DateTime.current.beginning_of_day
|
|
|
|
ending = DateTime.current.end_of_day
|
|
|
|
when 'month'
|
|
|
|
start = DateTime.current.beginning_of_month
|
|
|
|
ending = DateTime.current.end_of_month
|
|
|
|
when 'year'
|
|
|
|
start = DateTime.current.beginning_of_year
|
|
|
|
ending = DateTime.current.end_of_year
|
|
|
|
else
|
|
|
|
return self.id
|
|
|
|
end
|
|
|
|
if defined? start and defined? ending
|
|
|
|
Invoice.where('created_at >= :start_date AND created_at < :end_date', {start_date: start, end_date: ending}).length
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
end
|