1
0
mirror of https://github.com/LaCasemate/fab-manager.git synced 2025-01-29 18:52:22 +01:00

refactored Invoice & PaymentSchedule to use inheritance

This commit is contained in:
Sylvain 2020-12-22 14:43:08 +01:00
parent 6fa9780ad5
commit bbf88846dd
9 changed files with 112 additions and 60 deletions

View File

@ -0,0 +1,18 @@
# frozen_string_literal: true
# API Controller for resources of PaymentSchedule
class API::PaymentSchedulesController < API::ApiController
before_action :authenticate_user!
before_action :set_payment_schedule, only: %i[download]
def download
authorize @payment_schedule
# TODO, send_file File.join(Rails.root, @payment_schedule.file), type: 'application/pdf', disposition: 'attachment'
end
private
def set_payment_schedule
@payment_schedule = PaymentSchedule.find(params[:id])
end
end

View File

@ -0,0 +1,6 @@
# frozen_string_literal: true
# Raised when the Avoir cannot be generated from an existing Invoice
class CannotRefundError < StandardError
end

View File

@ -28,6 +28,7 @@ function extractHumanReadableMessage(error: any): string {
if (typeof error === 'string') return error; if (typeof error === 'string') return error;
// parse Rails errors (as JSON)
let message = ''; let message = '';
if (error instanceof Object) { if (error instanceof Object) {
// iterate through all the keys to build the message // iterate through all the keys to build the message

View File

@ -12,7 +12,7 @@ class Avoir < Invoice
attr_accessor :invoice_items_ids attr_accessor :invoice_items_ids
def generate_reference def generate_reference
self.reference = InvoiceReferenceService.generate_reference(self, date: created_at, avoir: true) super(created_at)
end end
def expire_subscription def expire_subscription

View File

@ -4,7 +4,7 @@ require 'checksum'
# Invoice correspond to a single purchase made by an user. This purchase may # Invoice correspond to a single purchase made by an user. This purchase may
# include reservation(s) and/or a subscription # include reservation(s) and/or a subscription
class Invoice < Footprintable class Invoice < PaymentDocument
include NotifyWith::NotificationAttachedObject include NotifyWith::NotificationAttachedObject
require 'fileutils' require 'fileutils'
scope :only_invoice, -> { where(type: nil) } scope :only_invoice, -> { where(type: nil) }
@ -41,30 +41,21 @@ class Invoice < Footprintable
end end
def filename def filename
prefix = Setting.find_by(name: 'invoice_prefix').history_values.order(created_at: :desc).where('created_at <= ?', created_at).limit(1).first prefix = Setting.find_by(name: 'invoice_prefix').value_at(created_at)
prefix ||= if created_at < Setting.find_by(name: 'invoice_prefix').history_values.order(created_at: :asc).limit(1).first.created_at prefix ||= if created_at < Setting.find_by(name: 'invoice_prefix').first_update
Setting.find_by(name: 'invoice_prefix').history_values.order(created_at: :asc).limit(1).first Setting.find_by(name: 'invoice_prefix').first_value
else else
Setting.find_by(name: 'invoice_prefix')..history_values.order(created_at: :desc).limit(1).first Setting.get('invoice_prefix')
end end
"#{prefix.value}-#{id}_#{created_at.strftime('%d%m%Y')}.pdf" "#{prefix}-#{id}_#{created_at.strftime('%d%m%Y')}.pdf"
end end
def user def user
invoicing_profile.user invoicing_profile.user
end end
def generate_reference
self.reference = InvoiceReferenceService.generate_reference(self)
end
def update_reference
generate_reference
save
end
def order_number def order_number
InvoiceReferenceService.generate_order_number(self) PaymentDocumentService.generate_order_number(self)
end end
# for debug & used by rake task "fablab:maintenance:regenerate_invoices" # for debug & used by rake task "fablab:maintenance:regenerate_invoices"
@ -74,7 +65,7 @@ class Invoice < Footprintable
end end
def build_avoir(attrs = {}) def build_avoir(attrs = {})
raise Exception if refunded? == true || prevent_refund? raise CannotRefundError if refunded? == true || prevent_refund?
avoir = Avoir.new(dup.attributes) avoir = Avoir.new(dup.attributes)
avoir.type = 'Avoir' avoir.type = 'Avoir'
@ -168,21 +159,10 @@ class Invoice < Footprintable
res res
end end
def add_environment
self.environment = Rails.env
end
def check_footprint def check_footprint
invoice_items.map(&:check_footprint).all? && footprint == compute_footprint invoice_items.map(&:check_footprint).all? && footprint == compute_footprint
end end
def set_wallet_transaction(amount, transaction_id)
raise InvalidFootprintError unless check_footprint
update_columns(wallet_amount: amount, wallet_transaction_id: transaction_id)
chain_record
end
def paid_with_stripe? def paid_with_stripe?
stp_payment_intent_id? || stp_invoice_id? || payment_method == 'stripe' stp_payment_intent_id? || stp_invoice_id? || payment_method == 'stripe'
end end

View File

@ -0,0 +1,26 @@
# frozen_string_literal: true
# SuperClass for models that provides legal PDF documents concerning sales
class PaymentDocument < Footprintable
self.abstract_class = true
def generate_reference(date = DateTime.current)
self.reference = PaymentDocumentService.generate_reference(self, date: date)
end
def update_reference
generate_reference
save
end
def add_environment
self.environment = Rails.env
end
def set_wallet_transaction(amount, transaction_id)
raise InvalidFootprintError unless check_footprint
update_columns(wallet_amount: amount, wallet_transaction_id: transaction_id)
chain_record
end
end

View File

@ -2,7 +2,9 @@
# PaymentSchedule is a way for members to pay something (especially a Subscription) with multiple payment, # PaymentSchedule is a way for members to pay something (especially a Subscription) with multiple payment,
# staged on a long period rather than with a single payment # staged on a long period rather than with a single payment
class PaymentSchedule < Footprintable class PaymentSchedule < PaymentDocument
require 'fileutils'
belongs_to :scheduled, polymorphic: true belongs_to :scheduled, polymorphic: true
belongs_to :wallet_transaction belongs_to :wallet_transaction
belongs_to :coupon belongs_to :coupon
@ -17,6 +19,24 @@ class PaymentSchedule < Footprintable
before_create :add_environment before_create :add_environment
after_create :update_reference, :chain_record after_create :update_reference, :chain_record
def file
dir = "payment_schedules/#{invoicing_profile.id}"
# create directories if they doesn't exists (payment_schedules & invoicing_profile_id)
FileUtils.mkdir_p dir
"#{dir}/#{filename}"
end
def filename
prefix = Setting.find_by(name: 'invoice_prefix').value_at(created_at)
prefix ||= if created_at < Setting.find_by(name: 'invoice_prefix').history_values.order(created_at: :asc).limit(1).first.created_at
Setting.find_by(name: 'invoice_prefix').history_values.order(created_at: :asc).limit(1).first
else
Setting.find_by(name: 'invoice_prefix')..history_values.order(created_at: :desc).limit(1).first
end
"#{prefix.value}-#{id}_#{created_at.strftime('%d%m%Y')}.pdf"
end
## ##
# This is useful to check the first item because its amount may be different from the others # This is useful to check the first item because its amount may be different from the others
## ##
@ -24,22 +44,6 @@ class PaymentSchedule < Footprintable
payment_schedule_items.order(due_date: :asc) payment_schedule_items.order(due_date: :asc)
end end
def add_environment
self.environment = Rails.env
end
def update_reference
self.reference = InvoiceReferenceService.generate_reference(self, payment_schedule: true)
save
end
def set_wallet_transaction(amount, transaction_id)
raise InvalidFootprintError unless check_footprint
update_columns(wallet_amount: amount, wallet_transaction_id: transaction_id)
chain_record
end
def check_footprint def check_footprint
payment_schedule_items.map(&:check_footprint).all? && footprint == compute_footprint payment_schedule_items.map(&:check_footprint).all? && footprint == compute_footprint
end end

View File

@ -110,12 +110,27 @@ class Setting < ApplicationRecord
# WARNING: when adding a new key, you may also want to add it in app/policies/setting_policy.rb#public_whitelist # WARNING: when adding a new key, you may also want to add it in app/policies/setting_policy.rb#public_whitelist
def value def value
last_value = history_values.order(HistoryValue.arel_table['created_at'].desc).first last_value = history_values.order(HistoryValue.arel_table['created_at'].desc).limit(1).first
last_value&.value last_value&.value
end end
def value_at(date)
val = history_values.order(HistoryValue.arel_table['created_at'].desc).where('created_at <= ?', date).limit(1).first
val&.value
end
def first_update
first_value = history_values.order(HistoryValue.arel_table['created_at'].asc).limit(1).first
first_value&.created_at
end
def first_value
first_value = history_values.order(HistoryValue.arel_table['created_at'].asc).limit(1).first
first_value&.value
end
def last_update def last_update
last_value = history_values.order(HistoryValue.arel_table['created_at'].desc).first last_value = history_values.order(HistoryValue.arel_table['created_at'].desc).limit(1).first
last_value&.created_at last_value&.created_at
end end

View File

@ -1,15 +1,15 @@
# frozen_string_literal: true # frozen_string_literal: true
# Provides methods to generate Invoice or PaymentSchedule references # Provides methods to generate Invoice, Avoir or PaymentSchedule references
class InvoiceReferenceService class PaymentDocumentService
class << self class << self
def generate_reference(invoice, date: DateTime.current, avoir: false, payment_schedule: false) def generate_reference(document, date: DateTime.current)
pattern = Setting.get('invoice_reference') pattern = Setting.get('invoice_reference')
reference = replace_invoice_number_pattern(pattern) reference = replace_invoice_number_pattern(pattern)
reference = replace_date_pattern(reference, date) reference = replace_date_pattern(reference, date)
if avoir if document.class == Avoir
# information about refund/avoir (R[text]) # information about refund/avoir (R[text])
reference.gsub!(/R\[([^\]]+)\]/, '\1') reference.gsub!(/R\[([^\]]+)\]/, '\1')
@ -17,16 +17,16 @@ class InvoiceReferenceService
reference.gsub!(/X\[([^\]]+)\]/, ''.to_s) reference.gsub!(/X\[([^\]]+)\]/, ''.to_s)
# remove information about payment schedule (S[text]) # remove information about payment schedule (S[text])
reference.gsub!(/S\[([^\]]+)\]/, ''.to_s) reference.gsub!(/S\[([^\]]+)\]/, ''.to_s)
elsif payment_schedule elsif document.class == PaymentSchedule
# information about payment schedule # information about payment schedule
reference.gsub!(/S\[([^\]]+)\]/, '\1') reference.gsub!(/S\[([^\]]+)\]/, '\1')
# remove information about online selling (X[text]) # remove information about online selling (X[text])
reference.gsub!(/X\[([^\]]+)\]/, ''.to_s) reference.gsub!(/X\[([^\]]+)\]/, ''.to_s)
# remove information about refunds (R[text]) # remove information about refunds (R[text])
reference.gsub!(/R\[([^\]]+)\]/, ''.to_s) reference.gsub!(/R\[([^\]]+)\]/, ''.to_s)
else elsif document.class == Invoice
# information about online selling (X[text]) # information about online selling (X[text])
if invoice.paid_with_stripe? if document.paid_with_stripe?
reference.gsub!(/X\[([^\]]+)\]/, '\1') reference.gsub!(/X\[([^\]]+)\]/, '\1')
else else
reference.gsub!(/X\[([^\]]+)\]/, ''.to_s) reference.gsub!(/X\[([^\]]+)\]/, ''.to_s)
@ -36,6 +36,8 @@ class InvoiceReferenceService
reference.gsub!(/R\[([^\]]+)\]/, ''.to_s) reference.gsub!(/R\[([^\]]+)\]/, ''.to_s)
# remove information about payment schedule (S[text]) # remove information about payment schedule (S[text])
reference.gsub!(/S\[([^\]]+)\]/, ''.to_s) reference.gsub!(/S\[([^\]]+)\]/, ''.to_s)
else
raise TypeError
end end
reference reference
@ -44,7 +46,7 @@ class InvoiceReferenceService
def generate_order_number(invoice) def generate_order_number(invoice)
pattern = Setting.get('invoice_order-nb') pattern = Setting.get('invoice_order-nb')
# global invoice number (nn..nn) # global document number (nn..nn)
reference = pattern.gsub(/n+(?![^\[]*\])/) do |match| reference = pattern.gsub(/n+(?![^\[]*\])/) do |match|
pad_and_truncate(number_of_invoices('global'), match.to_s.length) pad_and_truncate(number_of_invoices('global'), match.to_s.length)
end end
@ -119,21 +121,21 @@ class InvoiceReferenceService
end end
## ##
# Replace the invoice number elements in the provided pattern with counts from the database # Replace the document number elements in the provided pattern with counts from the database
# @param reference {string} # @param reference {string}
## ##
def replace_invoice_number_pattern(reference) def replace_invoice_number_pattern(reference)
copy = reference.dup copy = reference.dup
# invoice number per year (yy..yy) # document number per year (yy..yy)
copy.gsub!(/y+(?![^\[]*\])/) do |match| copy.gsub!(/y+(?![^\[]*\])/) do |match|
pad_and_truncate(number_of_invoices('year'), match.to_s.length) pad_and_truncate(number_of_invoices('year'), match.to_s.length)
end end
# invoice number per month (mm..mm) # document number per month (mm..mm)
copy.gsub!(/m+(?![^\[]*\])/) do |match| copy.gsub!(/m+(?![^\[]*\])/) do |match|
pad_and_truncate(number_of_invoices('month'), match.to_s.length) pad_and_truncate(number_of_invoices('month'), match.to_s.length)
end end
# invoice number per day (dd..dd) # document number per day (dd..dd)
copy.gsub!(/d+(?![^\[]*\])/) do |match| copy.gsub!(/d+(?![^\[]*\])/) do |match|
pad_and_truncate(number_of_invoices('day'), match.to_s.length) pad_and_truncate(number_of_invoices('day'), match.to_s.length)
end end