1
0
mirror of https://github.com/LaCasemate/fab-manager.git synced 2025-02-19 13:54:25 +01:00

(feat) save the accounting data in DB

Previously, the accounting data were built on demand,
extracting the data from the invoices on-the-fly.
This is intended to be used only once in a while, so there was
no performance issue with that.
Now, we want those data to be accessed from the OpenAPI,
so building them on-the-fly would be very much
intensive and resouces heavy. So we build them each nights
using a scheduled worker and save them in the database
This commit is contained in:
Sylvain 2022-11-18 16:42:11 +01:00
parent 514a797b64
commit af3def0e2e
20 changed files with 842 additions and 158 deletions

View File

@ -1,13 +1,14 @@
# frozen_string_literal: true
# API Controller for resources of type ProfileCustomField
# ProfileCustomFields are used to provide admin config user profile custom fields
# ProfileCustomFields are fields configured by an admin, added to the user's profile
class API::ProfileCustomFieldsController < API::ApiController
before_action :authenticate_user!, except: :index
before_action :set_profile_custom_field, only: %i[show update destroy]
def index
@profile_custom_fields = ProfileCustomField.all.order('id ASC')
@profile_custom_fields = @profile_custom_fields.where(actived: params[:actived]) if params[:actived].present?
end
def show; end

View File

@ -1,10 +1,11 @@
import apiClient from './clients/api-client';
import { AxiosResponse } from 'axios';
import { ProfileCustomField } from '../models/profile-custom-field';
import { ProfileCustomField, ProfileCustomFieldIndexFilters } from '../models/profile-custom-field';
import ApiLib from '../lib/api';
export default class ProfileCustomFieldAPI {
static async index (): Promise<Array<ProfileCustomField>> {
const res: AxiosResponse<Array<ProfileCustomField>> = await apiClient.get('/api/profile_custom_fields');
static async index (filters?: ProfileCustomFieldIndexFilters): Promise<Array<ProfileCustomField>> {
const res: AxiosResponse<Array<ProfileCustomField>> = await apiClient.get(`/api/profile_custom_fields${ApiLib.filtersToQuery(filters)}`);
return res?.data;
}

View File

@ -82,10 +82,9 @@ export const UserProfileForm: React.FC<UserProfileFormProps> = ({ action, size,
if (cgu?.custom_asset_file_attributes) setTermsAndConditions(cgu);
}).catch(error => onError(error));
}
ProfileCustomFieldAPI.index().then(data => {
const fData = data.filter(f => f.actived);
setProfileCustomFields(fData);
const userProfileCustomFields = fData.map(f => {
ProfileCustomFieldAPI.index({ actived: true }).then(data => {
setProfileCustomFields(data);
const userProfileCustomFields = data.map(f => {
const upcf = user?.invoicing_profile_attributes?.user_profile_custom_fields_attributes?.find(uf => uf.profile_custom_field_id === f.id);
return upcf || {
value: '',

View File

@ -1,6 +1,12 @@
import { ApiFilter } from './api';
export interface ProfileCustomField {
id: number,
label: string,
required: boolean,
actived: boolean
}
export interface ProfileCustomFieldIndexFilters extends ApiFilter {
actived?: boolean
}

View File

@ -0,0 +1,8 @@
# frozen_string_literal: false
# Stores an accounting datum related to an invoice, matching the French accounting system (PCG).
# Accounting data are configured by settings starting with accounting_* and by AdvancedAccounting
class AccountingLine < ApplicationRecord
belongs_to :invoice
belongs_to :invoicing_profile
end

View File

@ -18,6 +18,8 @@ class Invoice < PaymentDocument
has_one :payment_gateway_object, as: :item, dependent: :destroy
belongs_to :operator_profile, class_name: 'InvoicingProfile'
has_many :accounting_lines, dependent: :destroy
delegate :user, to: :invoicing_profile
before_create :add_environment

View File

@ -17,17 +17,20 @@ class InvoicingProfile < ApplicationRecord
has_many :history_values, dependent: :nullify
has_many :operated_invoices, foreign_key: :operator_profile_id, class_name: 'Invoice', dependent: :nullify
has_many :operated_payment_schedules, foreign_key: :operator_profile_id, class_name: 'PaymentSchedule', dependent: :nullify
has_many :operated_invoices, foreign_key: :operator_profile_id, class_name: 'Invoice', dependent: :nullify, inverse_of: :operator_profile
has_many :operated_payment_schedules, foreign_key: :operator_profile_id, class_name: 'PaymentSchedule',
dependent: :nullify, inverse_of: :operator_profile
has_many :user_profile_custom_fields
has_many :user_profile_custom_fields, dependent: :destroy
has_many :profile_custom_fields, through: :user_profile_custom_fields
accepts_nested_attributes_for :user_profile_custom_fields, allow_destroy: true
has_many :accounting_lines, dependent: :destroy
validates :address, presence: true, if: -> { Setting.get('address_required') }
def full_name
# if first_name or last_name is nil, the empty string will be used as a temporary replacement
(first_name || '').humanize.titleize + ' ' + (last_name || '').humanize.titleize
"#{(first_name || '').humanize.titleize} #{(last_name || '').humanize.titleize}"
end
end

View File

@ -1,4 +1,7 @@
# frozen_string_literal: true
# ProfileCustomFields are customer fields, configured by an admin, added to the user's profile
class ProfileCustomField < ApplicationRecord
has_many :user_profile_custom_fields
has_many :user_profile_custom_fields, dependent: :destroy
has_many :invoicing_profiles, through: :user_profile_custom_fields
end

View File

@ -1,3 +1,6 @@
# frozen_string_literal: true
# UserProfileCustomField store values for custom fields per user's profile
class UserProfileCustomField < ApplicationRecord
belongs_to :invoicing_profile
belongs_to :profile_custom_field

View File

@ -57,7 +57,7 @@ class Accounting::AccountingCodeService
raise ArgumentError('invalid section') unless %i[code analytical_section].include?(section)
if type == :code
item_code = Setting.get('advanced_accounting') ? invoice_item.object.plan.advanced_accounting.send(section) : nil
item_code = Setting.get('advanced_accounting') ? invoice_item.object.plan.advanced_accounting&.send(section) : nil
return Setting.get('accounting_subscription_code') if item_code.nil? && section == :code
item_code
@ -71,7 +71,7 @@ class Accounting::AccountingCodeService
raise ArgumentError('invalid section') unless %i[code analytical_section].include?(section)
if type == :code
item_code = Setting.get('advanced_accounting') ? invoice_item.object.orderable.advanced_accounting.send(section) : nil
item_code = Setting.get('advanced_accounting') ? invoice_item.object.orderable.advanced_accounting&.send(section) : nil
return Setting.get('accounting_Product_code') if item_code.nil? && section == :code
item_code

View File

@ -7,8 +7,7 @@ module Accounting; end
class Accounting::AccountingExportService
include ActionView::Helpers::NumberHelper
attr_reader :encoding, :format, :separator, :journal_code, :date_format, :columns, :decimal_separator, :label_max_length,
:export_zeros
attr_reader :encoding, :format, :separator, :date_format, :columns, :decimal_separator, :label_max_length, :export_zeros
def initialize(columns, encoding: 'UTF-8', format: 'CSV', separator: ';')
@encoding = encoding
@ -18,7 +17,6 @@ class Accounting::AccountingExportService
@date_format = '%d/%m/%Y'
@label_max_length = 50
@export_zeros = false
@journal_code = Setting.get('accounting_journal_code') || ''
@columns = columns
end
@ -32,11 +30,12 @@ class Accounting::AccountingExportService
def export(start_date, end_date, file)
# build CSV content
content = header_row
invoices = Invoice.where('created_at >= ? AND created_at <= ?', start_date, end_date).order('created_at ASC')
invoices = invoices.where('total > 0') unless export_zeros
invoices.each do |i|
Rails.logger.debug { "processing invoice #{i.id}..." } unless Rails.env.test?
content << generate_rows(i)
lines = AccountingLine.where('date >= ? AND date <= ?', start_date, end_date)
.order('date ASC')
lines = lines.joins(:invoice).where('invoices.total > 0') unless export_zeros
lines.each do |l|
Rails.logger.debug { "processing invoice #{l.invoice_id}..." } unless Rails.env.test?
content << "#{row(l)}\n"
end
# write content to file
@ -53,138 +52,46 @@ class Accounting::AccountingExportService
"#{row}\n"
end
def generate_rows(invoice)
rows = client_rows(invoice) + items_rows(invoice)
vat = vat_row(invoice)
rows += "#{vat}\n" unless vat.nil?
rows
end
# Generate the "subscription" and "reservation" rows associated with the provided invoice
def items_rows(invoice)
rows = ''
%w[Subscription Reservation WalletTransaction StatisticProfilePrepaidPack OrderItem Error].each do |object_type|
items = invoice.invoice_items.filter { |ii| ii.object_type == object_type }
items.each do |item|
rows << "#{row(
invoice,
Accounting::AccountingCodeService.sales_account(item),
Accounting::AccountingCodeService.sales_account(item, type: :label),
item.net_amount / 100.00,
line_label: label(invoice)
)}\n"
end
end
rows
end
# Generate the "client" rows, which contains the debit to the client account, all taxes included
def client_rows(invoice)
rows = ''
invoice.payment_means.each do |details|
rows << row(
invoice,
Accounting::AccountingCodeService.client_account(details[:means]),
Accounting::AccountingCodeService.client_account(details[:means], type: :label),
details[:amount] / 100.00,
line_label: label(invoice),
debit_method: :debit_client,
credit_method: :credit_client
)
rows << "\n"
end
rows
end
# Generate the "VAT" row, which contains the credit to the VAT account, with VAT amount only
def vat_row(invoice)
total = invoice.invoice_items.map(&:net_amount).sum
# we do not render the VAT row if it was disabled for this invoice
return nil if total == invoice.total
row(
invoice,
Accounting::AccountingCodeService.vat_account,
Accounting::AccountingCodeService.vat_account(type: :label),
invoice.invoice_items.map(&:vat).map(&:to_i).reduce(:+) / 100.00,
line_label: label(invoice)
)
end
# Generate a row of the export, filling the configured columns with the provided values
def row(invoice, account_code, account_label, amount, line_label: '', debit_method: :debit, credit_method: :credit)
def row(line)
row = ''
columns.each do |column|
case column
when 'journal_code'
row << journal_code.to_s
row << line.journal_code.to_s
when 'date'
row << invoice.created_at&.strftime(date_format)
row << line.date&.strftime(date_format)
when 'account_code'
row << account_code
row << line.account_code
when 'account_label'
row << account_label
row << line.account_label
when 'piece'
row << invoice.reference
row << line.invoice.reference
when 'line_label'
row << line_label
row << label(line)
when 'debit_origin', 'debit_euro'
row << method(debit_method).call(invoice, amount)
row << format_number(line.debit / 100.00)
when 'credit_origin', 'credit_euro'
row << method(credit_method).call(invoice, amount)
row << format_number(line.credit / 100.00)
when 'lettering'
row << ''
else
Rails.logger.debug { "Unsupported column: #{column}" }
Rails.logger.warn { "Unsupported column: #{column}" }
end
row << separator
end
row
end
# Fill the value of the "debit" column: if the invoice is a refund, returns the given amount, returns 0 otherwise
def debit(invoice, amount)
avoir = invoice.is_a? Avoir
avoir ? format_number(amount) : '0'
end
# Fill the value of the "credit" column: if the invoice is a refund, returns 0, otherwise, returns the given amount
def credit(invoice, amount)
avoir = invoice.is_a? Avoir
avoir ? '0' : format_number(amount)
end
# Fill the value of the "debit" column for the client row: if the invoice is a refund, returns 0, otherwise, returns the given amount
def debit_client(invoice, amount)
credit(invoice, amount)
end
# Fill the value of the "credit" column, for the client row: if the invoice is a refund, returns the given amount, returns 0 otherwise
def credit_client(invoice, amount)
debit(invoice, amount)
end
# Format the given number as a string, using the configured separator
def format_number(num)
number_to_currency(num, unit: '', separator: decimal_separator, delimiter: '', precision: 2)
end
# Create a text from the given invoice, matching the accounting software rules for the labels
def label(invoice)
name = "#{invoice.invoicing_profile.last_name} #{invoice.invoicing_profile.first_name}".tr separator, ''
reference = invoice.reference
items = invoice.subscription_invoice? ? [I18n.t('accounting_export.subscription')] : []
if invoice.main_item.object_type == 'Reservation'
items.push I18n.t("accounting_export.#{invoice.main_item.object.reservable_type}_reservation")
end
items.push I18n.t('accounting_export.wallet') if invoice.main_item.object_type == 'WalletTransaction'
items.push I18n.t('accounting_export.shop_order') if invoice.main_item.object_type == 'OrderItem'
summary = items.join(' + ')
res = "#{reference}, #{summary}"
"#{name.truncate(label_max_length - res.length)}, #{res}"
def label(line)
name = "#{line.invoicing_profile.last_name} #{line.invoicing_profile.first_name}".tr separator, ''
summary = line.summary
"#{name.truncate(label_max_length - summary.length)}, #{summary}"
end
end

View File

@ -0,0 +1,140 @@
# frozen_string_literal: false
# module definition
module Accounting; end
# Provides the routine to build the accounting data and save them in DB
class Accounting::AccountingService
attr_reader :currency, :journal_code
def initialize
@currency = ENV.fetch('INTL_CURRENCY') { '' }
@journal_code = Setting.get('accounting_journal_code') || ''
end
def build(start_date, end_date)
# build accounting lines
lines = []
invoices = Invoice.where('created_at >= ? AND created_at <= ?', start_date, end_date).order('created_at ASC')
invoices.each do |i|
Rails.logger.debug { "processing invoice #{i.id}..." } unless Rails.env.test?
lines << generate_lines(i)
end
AccountingLine.create!(lines)
end
private
def generate_lines(invoice)
lines = client_lines(invoice) + items_lines(invoice)
vat = vat_line(invoice)
lines << vat unless vat.nil?
lines
end
# Generate the lines associated with the provided invoice, for the sales accounts
def items_lines(invoice)
lines = []
%w[Subscription Reservation WalletTransaction StatisticProfilePrepaidPack OrderItem Error].each do |object_type|
items = invoice.invoice_items.filter { |ii| ii.object_type == object_type }
items.each do |item|
lines << line(
invoice,
'item',
Accounting::AccountingCodeService.sales_account(item),
Accounting::AccountingCodeService.sales_account(item, type: :label),
item.net_amount,
analytical_code: Accounting::AccountingCodeService.sales_account(item, section: :analytical_section)
)
end
end
lines
end
# Generate the "client" lines, which contains the debit to the client account, all taxes included
def client_lines(invoice)
lines = []
invoice.payment_means.each do |details|
lines << line(
invoice,
'client',
Accounting::AccountingCodeService.client_account(details[:means]),
Accounting::AccountingCodeService.client_account(details[:means], type: :label),
details[:amount],
debit_method: :debit_client,
credit_method: :credit_client
)
end
lines
end
# Generate the "VAT" line, which contains the credit to the VAT account, with total VAT amount only
def vat_line(invoice)
vat_rate_groups = VatHistoryService.new.invoice_vat(invoice)
total_vat = vat_rate_groups.values.pluck(:total_vat).sum
# we do not render the VAT row if it was disabled for this invoice
return nil if total_vat.zero?
line(
invoice,
'vat',
Accounting::AccountingCodeService.vat_account,
Accounting::AccountingCodeService.vat_account(type: :label),
total_vat
)
end
# Generate a row of the export, filling the configured columns with the provided values
def line(invoice, line_type, account_code, account_label, amount, analytical_code: '', debit_method: :debit, credit_method: :credit)
{
line_type: line_type,
journal_code: journal_code,
date: invoice.created_at,
account_code: account_code,
account_label: account_label,
analytical_code: analytical_code,
invoice_id: invoice.id,
invoicing_profile_id: invoice.invoicing_profile_id,
debit: method(debit_method).call(invoice, amount),
credit: method(credit_method).call(invoice, amount),
currency: currency,
summary: summary(invoice)
}
end
# Fill the value of the "debit" column: if the invoice is a refund, returns the given amount, returns 0 otherwise
def debit(invoice, amount)
invoice.is_a?(Avoir) ? amount : 0
end
# Fill the value of the "credit" column: if the invoice is a refund, returns 0, otherwise, returns the given amount
def credit(invoice, amount)
invoice.is_a?(Avoir) ? 0 : amount
end
# Fill the value of the "debit" column for the client row: if the invoice is a refund, returns 0, otherwise, returns the given amount
def debit_client(invoice, amount)
credit(invoice, amount)
end
# Fill the value of the "credit" column, for the client row: if the invoice is a refund, returns the given amount, returns 0 otherwise
def credit_client(invoice, amount)
debit(invoice, amount)
end
# Create a text from the given invoice, matching the accounting software rules for the labels
def summary(invoice)
reference = invoice.reference
items = invoice.subscription_invoice? ? [I18n.t('accounting_summary.subscription_abbreviation')] : []
if invoice.main_item.object_type == 'Reservation'
items.push I18n.t("accounting_summary.#{invoice.main_item.object.reservable_type}_reservation_abbreviation")
end
items.push I18n.t('accounting_summary.wallet_abbreviation') if invoice.main_item.object_type == 'WalletTransaction'
items.push I18n.t('accounting_summary.shop_order_abbreviation') if invoice.main_item.object_type == 'OrderItem'
"#{reference}, #{items.join(' + ')}"
end
end

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true
# Asynchronously export the accounting data (Invoices & Avoirs) to an external accounting software
# Asynchronously export the accounting data (AccountingLines) to an external accounting software
class AccountingExportWorker
include Sidekiq::Worker

View File

@ -0,0 +1,11 @@
# frozen_string_literal: true
# Periodically build the accounting data (AccountingLine) from the Invoices & Avoirs
class AccountingWorker
include Sidekiq::Worker
def perform
service = Accounting::AccountingService.new
service.build(DateTime.current.beginning_of_day, DateTime.current.end_of_day)
end
end

View File

@ -158,13 +158,14 @@ en:
credit_euro: "Euro credit"
lettering: "Lettering"
VAT: 'VAT'
subscription: "subscr."
Machine_reservation: "machine reserv."
Training_reservation: "training reserv."
Event_reservation: "event reserv."
Space_reservation: "space reserv."
wallet: "wallet"
shop_order: "shop order"
accounting_summary:
subscription_abbreviation: "subscr."
Machine_reservation_abbreviation: "machine reserv."
Training_reservation_abbreviation: "training reserv."
Event_reservation_abbreviation: "event reserv."
Space_reservation_abbreviation: "space reserv."
wallet_abbreviation: "wallet"
shop_order_abbreviation: "shop order"
vat_export:
start_date: "Start date"
end_date: "End date"

View File

@ -1,27 +1,27 @@
subscription_expire_in_7_days:
cron: "0 0 * * *"
cron: "0 0 * * *" # every day, at midnight
class: SubscriptionExpireWorker
queue: default
args: [7]
subscription_is_expired:
cron: "0 23 * * *"
cron: "0 23 * * *" # every day, at 11pm
class: SubscriptionExpireWorker
queue: default
args: [0]
generate_statistic:
cron: "0 1 * * *"
cron: "0 1 * * *" # every day, at 1am
class: StatisticWorker
queue: default
i_calendar_import:
cron: "0 * * * *"
cron: "0 * * * *" # every day, every hour
class: ICalendarImportWorker
queue: default
reservation_reminder:
cron: "1 * * * *"
cron: "1 * * * *" # every day, every hour + 1 minute
class: ReservationReminderWorker
queue: default
@ -35,11 +35,10 @@ free_disk_space:
class: FreeDiskSpaceWorker
queue: system
# schedule a version check, every week at the current day+time
# this will prevent that all the instances query the hub simultaneously
<% h = DateTime.current - 1.minute %>
version_check:
cron: <%="#{h.strftime('%M %H')} * * #{h.cwday}" %>
cron: <%="#{h.strftime('%M %H')} * * #{h.cwday}" %> # every week, at current day+time
class: VersionCheckWorker
queue: system
@ -48,4 +47,9 @@ payment_schedule_item:
class: PaymentScheduleItemWorker
queue: default
accounting_data:
cron: "0 0 * * *" # every day, at midnight
class: AccountingWorker
queue: default
<%= PluginRegistry.insert_code('yml.schedule') %>

View File

@ -0,0 +1,24 @@
# frozen_string_literal: true
# From this migration we save the accounting lines in database rather than building them on-the-fly.
# This will improve performance for API based requests
class CreateAccountingLines < ActiveRecord::Migration[5.2]
def change
create_table :accounting_lines do |t|
t.string :line_type
t.string :journal_code
t.datetime :date
t.string :account_code
t.string :account_label
t.string :analytical_code
t.references :invoice, foreign_key: true, index: true
t.references :invoicing_profile, foreign_key: true, index: true
t.integer :debit
t.integer :credit
t.string :currency
t.string :summary
t.timestamps
end
end
end

View File

@ -19,8 +19,8 @@ ActiveRecord::Schema.define(version: 2022_11_22_123605) do
enable_extension "unaccent"
create_table "abuses", id: :serial, force: :cascade do |t|
t.integer "signaled_id"
t.string "signaled_type"
t.integer "signaled_id"
t.string "first_name"
t.string "last_name"
t.string "email"
@ -30,6 +30,25 @@ ActiveRecord::Schema.define(version: 2022_11_22_123605) do
t.index ["signaled_type", "signaled_id"], name: "index_abuses_on_signaled_type_and_signaled_id"
end
create_table "accounting_lines", force: :cascade do |t|
t.string "line_type"
t.string "journal_code"
t.datetime "date"
t.string "account_code"
t.string "account_label"
t.string "analytical_code"
t.bigint "invoice_id"
t.bigint "invoicing_profile_id"
t.integer "debit"
t.integer "credit"
t.string "currency"
t.string "summary"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["invoice_id"], name: "index_accounting_lines_on_invoice_id"
t.index ["invoicing_profile_id"], name: "index_accounting_lines_on_invoicing_profile_id"
end
create_table "accounting_periods", id: :serial, force: :cascade do |t|
t.date "start_at"
t.date "end_at"
@ -49,8 +68,8 @@ ActiveRecord::Schema.define(version: 2022_11_22_123605) do
t.string "locality"
t.string "country"
t.string "postal_code"
t.integer "placeable_id"
t.string "placeable_type"
t.integer "placeable_id"
t.datetime "created_at"
t.datetime "updated_at"
end
@ -74,8 +93,8 @@ ActiveRecord::Schema.define(version: 2022_11_22_123605) do
end
create_table "assets", id: :serial, force: :cascade do |t|
t.integer "viewable_id"
t.string "viewable_type"
t.integer "viewable_id"
t.string "attachment"
t.string "type"
t.datetime "created_at"
@ -157,8 +176,8 @@ ActiveRecord::Schema.define(version: 2022_11_22_123605) do
end
create_table "credits", id: :serial, force: :cascade do |t|
t.integer "creditable_id"
t.string "creditable_type"
t.integer "creditable_id"
t.integer "plan_id"
t.integer "hours"
t.datetime "created_at"
@ -387,15 +406,15 @@ ActiveRecord::Schema.define(version: 2022_11_22_123605) do
create_table "notifications", id: :serial, force: :cascade do |t|
t.integer "receiver_id"
t.integer "attached_object_id"
t.string "attached_object_type"
t.integer "attached_object_id"
t.integer "notification_type_id"
t.boolean "is_read", default: false
t.datetime "created_at"
t.datetime "updated_at"
t.string "receiver_type"
t.boolean "is_send", default: false
t.jsonb "meta_data", default: {}
t.jsonb "meta_data", default: "{}"
t.index ["notification_type_id"], name: "index_notifications_on_notification_type_id"
t.index ["receiver_id"], name: "index_notifications_on_receiver_id"
end
@ -635,8 +654,8 @@ ActiveRecord::Schema.define(version: 2022_11_22_123605) do
create_table "prices", id: :serial, force: :cascade do |t|
t.integer "group_id"
t.integer "plan_id"
t.integer "priceable_id"
t.string "priceable_type"
t.integer "priceable_id"
t.integer "amount"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
@ -836,8 +855,8 @@ ActiveRecord::Schema.define(version: 2022_11_22_123605) do
t.text "message"
t.datetime "created_at"
t.datetime "updated_at"
t.integer "reservable_id"
t.string "reservable_type"
t.integer "reservable_id"
t.integer "nb_reserve_places"
t.integer "statistic_profile_id"
t.index ["reservable_type", "reservable_id"], name: "index_reservations_on_reservable_type_and_reservable_id"
@ -846,8 +865,8 @@ ActiveRecord::Schema.define(version: 2022_11_22_123605) do
create_table "roles", id: :serial, force: :cascade do |t|
t.string "name"
t.integer "resource_id"
t.string "resource_type"
t.integer "resource_id"
t.datetime "created_at"
t.datetime "updated_at"
t.index ["name", "resource_type", "resource_id"], name: "index_roles_on_name_and_resource_type_and_resource_id"
@ -1178,6 +1197,8 @@ ActiveRecord::Schema.define(version: 2022_11_22_123605) do
t.index ["invoicing_profile_id"], name: "index_wallets_on_invoicing_profile_id"
end
add_foreign_key "accounting_lines", "invoices"
add_foreign_key "accounting_lines", "invoicing_profiles"
add_foreign_key "accounting_periods", "users", column: "closed_by"
add_foreign_key "auth_provider_mappings", "auth_providers"
add_foreign_key "availability_tags", "availabilities"

544
test/fixtures/accounting_lines.yml vendored Normal file
View File

@ -0,0 +1,544 @@
accounting_line_1:
id: 1
line_type: client
journal_code: '530'
date: '2012-03-12 11:03:31.651441'
account_code: '5801'
account_label: Client card
analytical_code: ''
invoice_id: 1
invoicing_profile_id: 3
debit: 10000
credit: 0
currency: EUR
summary: 1604001/VL, subscr.
created_at: '2022-11-18 15:04:08.437029'
updated_at: '2022-11-18 15:04:08.437029'
accounting_line_2:
id: 2
line_type: item
journal_code: '530'
date: '2012-03-12 11:03:31.651441'
account_code: '7061'
account_label: Subscription
analytical_code:
invoice_id: 1
invoicing_profile_id: 3
debit: 0
credit: 10000
currency: EUR
summary: 1604001/VL, subscr.
created_at: '2022-11-18 15:04:08.455753'
updated_at: '2022-11-18 15:04:08.455753'
accounting_line_3:
id: 3
line_type: client
journal_code: '530'
date: '2012-03-12 13:40:22.342717'
account_code: '5803'
account_label: Client other
analytical_code: ''
invoice_id: 2
invoicing_profile_id: 4
debit: 2000
credit: 0
currency: EUR
summary: 1604002, subscr.
created_at: '2022-11-18 15:04:08.463802'
updated_at: '2022-11-18 15:04:08.463802'
accounting_line_4:
id: 4
line_type: item
journal_code: '530'
date: '2012-03-12 13:40:22.342717'
account_code: '7061'
account_label: Subscription
analytical_code:
invoice_id: 2
invoicing_profile_id: 4
debit: 0
credit: 2000
currency: EUR
summary: 1604002, subscr.
created_at: '2022-11-18 15:04:08.471904'
updated_at: '2022-11-18 15:04:08.471904'
accounting_line_5:
id: 5
line_type: client
journal_code: '530'
date: '2015-06-10 11:20:01.341130'
account_code: '5803'
account_label: Client other
analytical_code: ''
invoice_id: 3
invoicing_profile_id: 7
debit: 3000
credit: 0
currency: EUR
summary: 1203001, subscr.
created_at: '2022-11-18 15:04:08.480362'
updated_at: '2022-11-18 15:04:08.480362'
accounting_line_6:
id: 6
line_type: item
journal_code: '530'
date: '2015-06-10 11:20:01.341130'
account_code: '7061'
account_label: Subscription
analytical_code:
invoice_id: 3
invoicing_profile_id: 7
debit: 0
credit: 3000
currency: EUR
summary: 1203001, subscr.
created_at: '2022-11-18 15:04:08.488755'
updated_at: '2022-11-18 15:04:08.488755'
accounting_line_7:
id: 7
line_type: client
journal_code: '530'
date: '2016-04-05 08:35:52.931187'
account_code: '5803'
account_label: Client other
analytical_code: ''
invoice_id: 4
invoicing_profile_id: 7
debit: 0
credit: 0
currency: EUR
summary: 1203002, training reserv.
created_at: '2022-11-18 15:04:08.497148'
updated_at: '2022-11-18 15:04:08.497148'
accounting_line_8:
id: 8
line_type: item
journal_code: '530'
date: '2016-04-05 08:35:52.931187'
account_code: '7062'
account_label: Training reservation
analytical_code:
invoice_id: 4
invoicing_profile_id: 7
debit: 0
credit: 0
currency: EUR
summary: 1203002, training reserv.
created_at: '2022-11-18 15:04:08.505540'
updated_at: '2022-11-18 15:04:08.505540'
accounting_line_9:
id: 9
line_type: client
journal_code: '530'
date: '2016-04-05 08:36:46.853368'
account_code: '5803'
account_label: Client other
analytical_code: ''
invoice_id: 5
invoicing_profile_id: 3
debit: 1500
credit: 0
currency: EUR
summary: 1506031, machine reserv.
created_at: '2022-11-18 15:04:08.513708'
updated_at: '2022-11-18 15:04:08.513708'
accounting_line_10:
id: 10
line_type: item
journal_code: '530'
date: '2016-04-05 08:36:46.853368'
account_code: '7065'
account_label: Machine reservation
analytical_code:
invoice_id: 5
invoicing_profile_id: 3
debit: 0
credit: 1500
currency: EUR
summary: 1506031, machine reserv.
created_at: '2022-11-18 15:04:08.522222'
updated_at: '2022-11-18 15:04:08.522222'
accounting_line_11:
id: 11
line_type: client
journal_code: '530'
date: '2021-01-04 14:51:21.616153'
account_code: '5803'
account_label: Client other
analytical_code: ''
invoice_id: 6
invoicing_profile_id: 8
debit: 3000
credit: 0
currency: EUR
summary: 2101041, subscr.
created_at: '2022-11-18 15:04:08.530494'
updated_at: '2022-11-18 15:04:08.530494'
accounting_line_12:
id: 12
line_type: item
journal_code: '530'
date: '2021-01-04 14:51:21.616153'
account_code: '7061'
account_label: Subscription
analytical_code:
invoice_id: 6
invoicing_profile_id: 8
debit: 0
credit: 3000
currency: EUR
summary: 2101041, subscr.
created_at: '2022-11-18 15:04:08.538721'
updated_at: '2022-11-18 15:04:08.538721'
accounting_line_13:
id: 13
line_type: client
journal_code: '530'
date: '2022-09-20 15:14:22.873707'
account_code: '5803'
account_label: Client other
analytical_code: ''
invoice_id: 5811
invoicing_profile_id: 3
debit: 4500
credit: 0
currency: EUR
summary: 2209002, shop order
created_at: '2022-11-18 15:04:08.547966'
updated_at: '2022-11-18 15:04:08.547966'
accounting_line_14:
id: 14
line_type: item
journal_code: '530'
date: '2022-09-20 15:14:22.873707'
account_code: '7067'
account_label: Shop order
analytical_code:
invoice_id: 5811
invoicing_profile_id: 3
debit: 0
credit: 4000
currency: EUR
summary: 2209002, shop order
created_at: '2022-11-18 15:04:08.556504'
updated_at: '2022-11-18 15:04:08.556504'
accounting_line_15:
id: 15
line_type: item
journal_code: '530'
date: '2022-09-20 15:14:22.873707'
account_code: '7067'
account_label: Shop order
analytical_code:
invoice_id: 5811
invoicing_profile_id: 3
debit: 0
credit: 500
currency: EUR
summary: 2209002, shop order
created_at: '2022-11-18 15:04:08.563733'
updated_at: '2022-11-18 15:04:08.563733'
accounting_line_16:
id: 16
line_type: client
journal_code: '530'
date: '2022-09-20 15:14:48.345927'
account_code: '5803'
account_label: Client other
analytical_code: ''
invoice_id: 5812
invoicing_profile_id: 7
debit: 6000
credit: 0
currency: EUR
summary: 2209004, shop order
created_at: '2022-11-18 15:04:08.571992'
updated_at: '2022-11-18 15:04:08.571992'
accounting_line_17:
id: 17
line_type: item
journal_code: '530'
date: '2022-09-20 15:14:48.345927'
account_code: '7067'
account_label: Shop order
analytical_code:
invoice_id: 5812
invoicing_profile_id: 7
debit: 0
credit: 6000
currency: EUR
summary: 2209004, shop order
created_at: '2022-11-18 15:04:08.580452'
updated_at: '2022-11-18 15:04:08.580452'
accounting_line_18:
id: 18
line_type: client
journal_code: '530'
date: '2022-10-04 12:36:03.060832'
account_code: '5801'
account_label: Client card
analytical_code: ''
invoice_id: 5816
invoicing_profile_id: 4
debit: 319
credit: 0
currency: EUR
summary: 2210002/VL, shop order
created_at: '2022-11-18 15:04:08.589664'
updated_at: '2022-11-18 15:04:08.589664'
accounting_line_19:
id: 19
line_type: item
journal_code: '530'
date: '2022-10-04 12:36:03.060832'
account_code: '7067'
account_label: Shop order
analytical_code:
invoice_id: 5816
invoicing_profile_id: 4
debit: 0
credit: 119
currency: EUR
summary: 2210002/VL, shop order
created_at: '2022-11-18 15:04:08.598371'
updated_at: '2022-11-18 15:04:08.598371'
accounting_line_20:
id: 20
line_type: item
journal_code: '530'
date: '2022-10-04 12:36:03.060832'
account_code: '7067'
account_label: Shop order
analytical_code:
invoice_id: 5816
invoicing_profile_id: 4
debit: 0
credit: 200
currency: EUR
summary: 2210002/VL, shop order
created_at: '2022-11-18 15:04:08.613961'
updated_at: '2022-11-18 15:04:08.613961'
accounting_line_21:
id: 21
line_type: client
journal_code: '530'
date: '2022-10-04 13:54:42.975196'
account_code: '5801'
account_label: Client card
analytical_code: ''
invoice_id: 5817
invoicing_profile_id: 4
debit: 1295
credit: 0
currency: EUR
summary: 2210004/VL, shop order
created_at: '2022-11-18 15:04:08.622056'
updated_at: '2022-11-18 15:04:08.622056'
accounting_line_22:
id: 22
line_type: item
journal_code: '530'
date: '2022-10-04 13:54:42.975196'
account_code: '7067'
account_label: Shop order
analytical_code:
invoice_id: 5817
invoicing_profile_id: 4
debit: 0
credit: 95
currency: EUR
summary: 2210004/VL, shop order
created_at: '2022-11-18 15:04:08.630519'
updated_at: '2022-11-18 15:04:08.630519'
accounting_line_23:
id: 23
line_type: item
journal_code: '530'
date: '2022-10-04 13:54:42.975196'
account_code: '7067'
account_label: Shop order
analytical_code:
invoice_id: 5817
invoicing_profile_id: 4
debit: 0
credit: 1200
currency: EUR
summary: 2210004/VL, shop order
created_at: '2022-11-18 15:04:08.640333'
updated_at: '2022-11-18 15:04:08.640333'
accounting_line_24:
id: 24
line_type: client
journal_code: '530'
date: '2022-10-04 14:04:12.742685'
account_code: '5801'
account_label: Client card
analytical_code: ''
invoice_id: 5818
invoicing_profile_id: 4
debit: 1000
credit: 0
currency: EUR
summary: 2210006/VL, shop order
created_at: '2022-11-18 15:04:08.656104'
updated_at: '2022-11-18 15:04:08.656104'
accounting_line_25:
id: 25
line_type: item
journal_code: '530'
date: '2022-10-04 14:04:12.742685'
account_code: '7067'
account_label: Shop order
analytical_code:
invoice_id: 5818
invoicing_profile_id: 4
debit: 0
credit: 1000
currency: EUR
summary: 2210006/VL, shop order
created_at: '2022-11-18 15:04:08.663862'
updated_at: '2022-11-18 15:04:08.663862'
accounting_line_26:
id: 26
line_type: client
journal_code: '530'
date: '2022-10-04 14:17:52.854636'
account_code: '5801'
account_label: Client card
analytical_code: ''
invoice_id: 5819
invoicing_profile_id: 4
debit: 4002
credit: 0
currency: EUR
summary: 2210008/VL, shop order
created_at: '2022-11-18 15:04:08.672150'
updated_at: '2022-11-18 15:04:08.672150'
accounting_line_27:
id: 27
line_type: item
journal_code: '530'
date: '2022-10-04 14:17:52.854636'
account_code: '7067'
account_label: Shop order
analytical_code:
invoice_id: 5819
invoicing_profile_id: 4
debit: 0
credit: 2
currency: EUR
summary: 2210008/VL, shop order
created_at: '2022-11-18 15:04:08.680577'
updated_at: '2022-11-18 15:04:08.680577'
accounting_line_28:
id: 28
line_type: item
journal_code: '530'
date: '2022-10-04 14:17:52.854636'
account_code: '7067'
account_label: Shop order
analytical_code:
invoice_id: 5819
invoicing_profile_id: 4
debit: 0
credit: 4000
currency: EUR
summary: 2210008/VL, shop order
created_at: '2022-11-18 15:04:08.688864'
updated_at: '2022-11-18 15:04:08.688864'
accounting_line_29:
id: 29
line_type: client
journal_code: '530'
date: '2022-10-04 14:25:37.291945'
account_code: '5803'
account_label: Client other
analytical_code: ''
invoice_id: 5820
invoicing_profile_id: 3
debit: 12000
credit: 0
currency: EUR
summary: 2210010, shop order
created_at: '2022-11-18 15:04:08.697635'
updated_at: '2022-11-18 15:04:08.697635'
accounting_line_30:
id: 30
line_type: item
journal_code: '530'
date: '2022-10-04 14:25:37.291945'
account_code: '7067'
account_label: Shop order
analytical_code:
invoice_id: 5820
invoicing_profile_id: 3
debit: 0
credit: 12000
currency: EUR
summary: 2210010, shop order
created_at: '2022-11-18 15:04:08.705822'
updated_at: '2022-11-18 15:04:08.705822'
accounting_line_31:
id: 31
line_type: client
journal_code: '530'
date: '2022-10-04 14:32:28.204985'
account_code: '5803'
account_label: Client other
analytical_code: ''
invoice_id: 5821
invoicing_profile_id: 2
debit: 12000
credit: 0
currency: EUR
summary: 2210012, shop order
created_at: '2022-11-18 15:04:08.713849'
updated_at: '2022-11-18 15:04:08.713849'
accounting_line_32:
id: 32
line_type: item
journal_code: '530'
date: '2022-10-04 14:32:28.204985'
account_code: '7067'
account_label: Shop order
analytical_code:
invoice_id: 5821
invoicing_profile_id: 2
debit: 0
credit: 12000
currency: EUR
summary: 2210012, shop order
created_at: '2022-11-18 15:04:08.722579'
updated_at: '2022-11-18 15:04:08.722579'
accounting_line_33:
id: 33
line_type: client
journal_code: '530'
date: '2022-10-04 14:35:40.584472'
account_code: '5803'
account_label: Client other
analytical_code: ''
invoice_id: 5822
invoicing_profile_id: 2
debit: 3000
credit: 0
currency: EUR
summary: 2210014, shop order
created_at: '2022-11-18 15:04:08.731248'
updated_at: '2022-11-18 15:04:08.731248'
accounting_line_34:
id: 34
line_type: item
journal_code: '530'
date: '2022-10-04 14:35:40.584472'
account_code: '7067'
account_label: Shop order
analytical_code:
invoice_id: 5822
invoicing_profile_id: 2
debit: 0
credit: 3000
currency: EUR
summary: 2210014, shop order
created_at: '2022-11-18 15:04:08.739474'
updated_at: '2022-11-18 15:04:08.739474'

View File

@ -168,19 +168,25 @@ class Exports::AccountingExportTest < ActionDispatch::IntegrationTest
def check_entry_label(invoice, line)
if invoice.subscription_invoice?
assert_match I18n.t('accounting_export.subscription'),
assert_match I18n.t('accounting_summary.subscription_abbreviation'),
line[I18n.t('accounting_export.line_label')],
'Entry label does not contains the reference to the subscription'
end
if invoice.main_item.object_type == 'Reservation'
assert_match I18n.t("accounting_export.#{invoice.main_item.object.reservable_type}_reservation"),
assert_match I18n.t("accounting_summary.#{invoice.main_item.object.reservable_type}_reservation_abbreviation"),
line[I18n.t('accounting_export.line_label')],
'Entry label does not contains the reference to the reservation'
end
return unless invoice.main_item.object_type == 'WalletTransaction'
if invoice.main_item.object_type == 'WalletTransaction'
assert_match I18n.t('accounting_summary.wallet_abbreviation'),
line[I18n.t('accounting_export.line_label')],
'Entry label does not contains the reference to the wallet'
end
assert_match I18n.t('accounting_export.wallet'),
return unless invoice.main_item.object_type == 'OrderItem'
assert_match I18n.t('accounting_summary.shop_order_abbreviation'),
line[I18n.t('accounting_export.line_label')],
'Entry label does not contains the reference to the wallet'
'Entry label does not contains the reference to the order'
end
end