Skip to content

Commit

Permalink
Add abacus abo magazin invoice class
Browse files Browse the repository at this point in the history
  • Loading branch information
njaeggi committed Feb 12, 2025
1 parent 0589cef commit e9268af
Show file tree
Hide file tree
Showing 6 changed files with 167 additions and 108 deletions.
65 changes: 49 additions & 16 deletions app/domain/invoices/abacus/abo_magazin_invoice.rb
Original file line number Diff line number Diff line change
@@ -1,29 +1,25 @@
# frozen_string_literal: true

# Copyright (c) 2024, Schweizer Alpen-Club. This file is part of
# Copyright (c) 2025, Schweizer Alpen-Club. This file is part of
# hitobito_sac_cas and licensed under the Affero General Public License version 3
# or later. See the COPYING file at the top-level directory or at
# https://github.com/hitobito/hitobito_sac_cas.

module Invoices
module Abacus
class AboMagazinInvoice
attr_reader :abonnent
attr_reader :abonnent_role

def initialize(abonnent)
@abonnent = abonnent
delegate :person, to: :abonnent_role

ARTICLE_NUMBER = "APG"

def initialize(abonnent_role)
@abonnent_role = abonnent_role
end

def positions
@positions ||= [Invoices::Abacus::InvoicePosition.new(
name: name,
grouping: name,
amount: ,
count: 1,
article_number: article_number,
cost_center: event.kind.cost_center.code,
cost_unit: event.kind.cost_unit.code
)]
@positions ||= build_positions
end

def total
Expand All @@ -32,9 +28,46 @@ def total

private

def position_description_and_amount
description = participation.price_category? ? Event::Course.human_attribute_name(participation.price_category) : nil
[description, participation.price]
def build_positions
positions = [main_fee_position]
positions << abroad_fee_position if person.country != "CH" # NJTODO: living_abroad?
positions
end

def main_fee_position
Invoices::Abacus::InvoicePosition.new(
name: fee_position_name,
grouping: fee_position_name,
article_number: ARTICLE_NUMBER,
amount: 60, # NJTODO: model call
count: 1
)
end

def abroad_fee_position
Invoices::Abacus::InvoicePosition.new(
name: abroad_fee_position_name,
amount: 16 # NJTODO: model call
)
end

def fee_position_name
I18n.t("invoices.abo_magazin.positions.abo_fee",
group: abonnent_role.group,
time_period: new_role_period,
locale: person.language)
end

def abroad_fee_position_name
I18n.t("invoices.abo_magazin.positions.abroad_fee",
group: abonnent_role.group,
locale: person.language)
end

def new_role_period
start_date = abonnent_role.end_on + 1.day
end_date = abonnent_role.end_on + 1.year
"#{I18n.l(start_date)} - #{I18n.l(end_date)}"
end
end
end
Expand Down
122 changes: 31 additions & 91 deletions app/jobs/invoices/abacus/create_yearly_abo_alpen_invoices_job.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,10 @@
# https://github.com/hitobito/hitobito_sac_cas.

class Invoices::Abacus::CreateYearlyAboAlpenInvoicesJob < BaseJob

SLICE_SIZE = 25 # number of people/invoices transmitted per abacus batch request

self.max_run_time = 24.hours

def perform
#log_progress(0)
clear_spurious_draft_invoices!
process_invoices
# log_progress(100) if @current_logged_percent < 100
end

def enqueue
assert_no_other_job_running!
end

def error(job, exception)
Expand All @@ -32,13 +22,14 @@ def failure(job)
create_error_log_entry("MV-Jahresinkassolauf abgebrochen", nil)
end

def active_abonnenten
Person.joins(:roles_unscoped)
.left_joins(:external_invoices)
.where.not(abacus_subject_key: nil)
.where(roles: { type: Group::AboMagazin::Abonnent.sti_name, terminated: false, end_on: Time.zone.today..62.days.from_now })
.where("external_invoices.id IS NULL OR external_invoices.year != EXTRACT(YEAR FROM roles.end_on + INTERVAL '1 day')")
.distinct
def active_abonnenten_roles
Role.joins(:person)
.left_joins(:external_invoices)
.where(type: Group::AboMagazin::Abonnent.sti_name, terminated: false)
.where(end_on: Time.zone.today..62.days.from_now)
.where.not(people: {abacus_subject_key: nil})
.where("external_invoices.id IS NULL OR external_invoices.year != EXTRACT(YEAR FROM roles.end_on + INTERVAL '1 day')")
.distinct
end

def self.job_running?
Expand All @@ -49,25 +40,27 @@ def self.job_running?
private

def process_invoices
# TODO: start_progress
active_abonnenten do |person|
check_terminated!
create_invoice(person)
sales_orders = []

active_abonnenten_roles do |abonnent_role|
sales_orders.concat(create_invoice(abonnent_role))
end
submit_sales_orders(sales_orders) unless sales_orders.empty?
end

def create_invoice(person)
membership_invoice = membership_invoice(person)
sales_orders = create_sales_orders(membership_invoices)
parts = submit_sales_orders(sales_orders)
log_error_parts(parts)
def create_invoice(abonnent_role)
abo_magazin_invoice = abo_magazin_invoice(abonnent_role)
invoice = create_external_invoice(abo_magazin_invoice)
create_sales_order(invoice, abo_magazin_invoice)
end

def submit_sales_orders(sales_orders)
sales_order_interface.create_batch(sales_orders)
rescue RestClient::Exception => e
clear_external_invoices(sales_orders)
raise e
sales_orders.each_slice(SLICE_SIZE) do |batch|
sales_order_interface.create_batch(batch)
rescue RestClient::Exception => e
clear_external_invoices(batch)
raise e
end
end

def clear_external_invoices(sales_orders)
Expand All @@ -76,66 +69,19 @@ def clear_external_invoices(sales_orders)
end
end

def membership_invoice(person)
member = Invoices::SacMemberships::Member.new(person, context)
invoice = Invoices::Abacus::MembershipInvoice.new(member, member.active_memberships)
invoice if invoice.invoice?
end
def abo_magazin_invoice(abonnent_role) = Invoices::Abacus::AboMagazinInvoice.new(abonnent_role)

def create_sales_orders(membership_invoices)
membership_invoices.map do |mi|
invoice = create_external_invoice(mi)
Invoices::Abacus::SalesOrder.new(invoice, mi.positions, mi.additional_user_fields)
end
end
def create_sales_order(invoice, abo_magazin_invoice) = Invoices::Abacus::SalesOrder.new(invoice, abo_magazin_invoice.positions)

def create_external_invoice(membership_invoice)
ExternalInvoice::SacMembership.create!(
person: membership_invoice.member.person,
def create_external_invoice(abo_magazin_invoice)
ExternalInvoice::AboMagazin.create!(
person: abo_magazin_invoice.abonnent,
year: @invoice_year,
state: :draft,
total: membership_invoice.total,
total: abo_magazin_invoice.total,
issued_at: @invoice_date,
sent_at: @send_date,
# also see comment in ExternalInvoice::SacMembership
link: membership_invoice.member.stammsektion,
invoice_kind: :sac_membership_yearly
)
end

def assert_no_other_job_running!
raise "There is already a job running" if self.class.job_running?
end

# clears invoice models from previously failed job runs
def clear_spurious_draft_invoices!
ExternalInvoice::AboMagazin.where(state: :draft, year: @invoice_year).destroy_all
end

def start_progress
@current_logged_percent = 0
@members_count = active_members.count
@processed_members = 0
end

def update_progress(people_count)
@processed_members += people_count
@progress_percent = @processed_members * 100 / @members_count
if @progress_percent >= (@current_logged_percent + 10)
@current_logged_percent = @progress_percent / 10 * 10
log_progress(@current_logged_percent)
end
end

def load_people(ids)
context.people_with_membership_years.where(id: ids).order(:id)
end

def log_progress(percent)
HitobitoLogEntry.create!(
category: "stapelverarbeitung",
level: :info,
message: "MV-Jahresinkassolauf: Fortschritt #{percent}%"
link: nil # NJTODO,
)
end

Expand Down Expand Up @@ -169,11 +115,5 @@ def reference_date
@reference_date ||= Date.new(@invoice_year)
end

def context
@context ||= Invoices::SacMemberships::Context.new(reference_date)
end

def sales_order_interface
@sales_order_interface ||= Invoices::Abacus::SalesOrderInterface.new
end
def sales_order_interface = @sales_order_interface ||= Invoices::Abacus::SalesOrderInterface.new
end
5 changes: 4 additions & 1 deletion config/locales/wagon.de.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1434,7 +1434,10 @@ de:

invoices:
abo_magazin:
title: Rechnung Die Alpen %{year}
title: NJTODO
positions:
abo_fee: Abonnement %{group} %{time_period}
abroad_fee: Porto %{group}
errors:
data_quality_error: "Die Person hat Datenqualitätsprobleme, daher wurde keine Rechnung erstellt."
create_subject_failed: "Probleme beim Übermitteln der Personendaten"
Expand Down
5 changes: 5 additions & 0 deletions config/locales/wagon.fr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1446,6 +1446,11 @@ fr:
cannot_set_main_person: Contacte le secrétariat pour modifier le destinataire des factures familiales

invoices:
abo_magazin:
title: NJTODO
positions:
abo_fee: Abonnement %{group} %{time_period}
abroad_fee: Porto %{group}
errors:
data_quality_error: "La personne a des problèmes de qualité des données, c’est pourquoi aucune facture n’a été créée."
create_subject_failed: "Problèmes lors de la transmission des données personnelles"
Expand Down
5 changes: 5 additions & 0 deletions config/locales/wagon.it.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1440,6 +1440,11 @@ it:
cannot_set_main_person: Contatta il Segretariato centrale per far cambiare il destinatario di fatturazione per la famiglia.

invoices:
abo_magazin:
title: NJTODO
positions:
abo_fee: Abbonamento %{group} %{time_period}
abroad_fee: Porto %{group}
errors:
data_quality_error: "La persona ha dei problemi nella qualità dei dati, pertanto non è stata creata alcuna fattura."
create_subject_failed: "Problemi durante il trasferimento dei dati personali"
Expand Down
73 changes: 73 additions & 0 deletions spec/domain/invoices/abacus/abo_magazin_invoice_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# frozen_string_literal: true

# Copyright (c) 2025, Schweizer Alpen-Club. This file is part of
# hitobito_sac_cas and licensed under the Affero General Public License version 3
# or later. See the COPYING file at the top-level directory or at
# https://github.com/hitobito/hitobito_sac_cas.

require "spec_helper"

describe Invoices::Abacus::AboMagazinInvoice do
let(:abonnent_alpen) { roles(:abonnent_alpen) }
let(:abonnent) { people(:abonnent) }

subject { described_class.new(abonnent_alpen) }

describe "#positions" do
context "for swiss person" do
before do
abonnent.update_column(:country, "CH")
end

it "creates abo fee position with correct values" do
position = subject.positions.first
expect(subject.positions.count).to eq 1
expect(position.name).to eq "Abonnement Die Alpen DE 01.01.2026 - 31.12.2026"
expect(position.grouping).to eq "Abonnement Die Alpen DE 01.01.2026 - 31.12.2026"
expect(position.amount).to eq 60
expect(position.count).to eq 1
expect(position.article_number).to eq "APG"
end

it "position uses language of person as locale" do
abonnent.update_column(:language, "it")
expect(subject.positions.first.name).to eq "Abbonamento Die Alpen DE 01.01.2026 - 31.12.2026"
end
end

context "for person living abroad" do
before do
abonnent.update_column(:country, "BO")
end

it "has second position for abroad costs" do
position = subject.positions.second
expect(subject.positions.count).to eq 2
expect(position.name).to eq "Porto Die Alpen DE"
expect(position.amount).to eq 16
end
end
end

describe "#total" do
context "for swiss person" do
before do
abonnent.update_column(:country, "CH")
end

it "total does not include porto cost" do
expect(subject.total).to eq 60
end
end

context "for person living abroad" do
before do
abonnent.update_column(:country, "BO")
end

it "total does include porto cost" do
expect(subject.total).to eq 76
end
end
end
end

0 comments on commit e9268af

Please sign in to comment.