1
0
mirror of https://github.com/LaCasemate/fab-manager.git synced 2025-01-17 06:52:27 +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;
// parse Rails errors (as JSON)
let message = '';
if (error instanceof Object) {
// iterate through all the keys to build the message

View File

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

View File

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

View File

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