-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement YearlyInvoiceJob for Abo Magazin Roles (#1561)
- Loading branch information
Showing
3 changed files
with
265 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
# frozen_string_literal: true | ||
|
||
# Copyright (c) 2024, 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 | ||
|
||
def initialize(abonnent) | ||
@abonnent = abonnent | ||
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 | ||
)] | ||
end | ||
|
||
def total | ||
positions.sum(&:amount) | ||
end | ||
|
||
private | ||
|
||
def position_description_and_amount | ||
description = participation.price_category? ? Event::Course.human_attribute_name(participation.price_category) : nil | ||
[description, participation.price] | ||
end | ||
end | ||
end | ||
end |
179 changes: 179 additions & 0 deletions
179
app/jobs/invoices/abacus/create_yearly_abo_alpen_invoices_job.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,179 @@ | ||
# frozen_string_literal: true | ||
|
||
# Copyright (c) 2024, 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. | ||
|
||
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) | ||
super | ||
create_error_log_entry("Mitgliedschaftsrechnungen konnten nicht an Abacus übermittelt werden. " \ | ||
"Es erfolgt ein weiterer Versuch.", exception.message) | ||
end | ||
|
||
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 | ||
end | ||
|
||
def self.job_running? | ||
Delayed::Job.where("handler LIKE ?", "%#{name}%") | ||
.where(failed_at: nil).exists? | ||
end | ||
|
||
private | ||
|
||
def process_invoices | ||
# TODO: start_progress | ||
active_abonnenten do |person| | ||
check_terminated! | ||
create_invoice(person) | ||
end | ||
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) | ||
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 | ||
end | ||
|
||
def clear_external_invoices(sales_orders) | ||
sales_orders.each do |so| | ||
so.entity.destroy | ||
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 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_external_invoice(membership_invoice) | ||
ExternalInvoice::SacMembership.create!( | ||
person: membership_invoice.member.person, | ||
year: @invoice_year, | ||
state: :draft, | ||
total: membership_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}%" | ||
) | ||
end | ||
|
||
def create_error_log_entry(message, payload) | ||
HitobitoLogEntry.create!( | ||
category: "stapelverarbeitung", | ||
level: :error, | ||
message: message, | ||
payload: payload | ||
) | ||
end | ||
|
||
def log_error_parts(parts) | ||
parts.reject(&:success?).each do |part| | ||
create_log_entry(part) | ||
end | ||
end | ||
|
||
def create_log_entry(part) | ||
part.context_object.entity.update!(state: :error) | ||
HitobitoLogEntry.create!( | ||
subject: part.context_object.entity, | ||
category: "rechnungen", | ||
level: :error, | ||
message: "Mitgliedschaftsrechnung konnte nicht in Abacus erstellt werden", | ||
payload: part.error_payload | ||
) | ||
end | ||
|
||
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 | ||
end |
45 changes: 45 additions & 0 deletions
45
spec/jobs/invoices/abacus/create_yearly_abo_alpen_invoices_job_spec.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
# frozen_string_literal: true | ||
|
||
# Copyright (c) 2024, 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::CreateYearlyAboAlpenInvoicesJob do | ||
let(:subject) { described_class.new } | ||
let(:abonnent) { people(:abonnent) } | ||
let(:abonnent_role) { roles(:abonnent_alpen) } | ||
|
||
before do | ||
abonnent.update_column(:abacus_subject_key, "123") | ||
abonnent_role.update_column(:end_on, 50.days.from_now) | ||
end | ||
|
||
describe "#active_abonnenten" do | ||
it "includes every abonnent role where role end in next 62 days" do | ||
expect(subject.active_abonnenten).to eq [abonnent] | ||
end | ||
|
||
it "does no unclude abonnent where role ends in more than 62 days" do | ||
abonnent_role.update_column(:end_on, 70.days.from_now) | ||
expect(subject.active_abonnenten).to be_empty | ||
end | ||
|
||
it "does not include people where abonnent role is terminated" do | ||
abonnent_role.update_column(:terminated, true) | ||
expect(subject.active_abonnenten).to be_empty | ||
end | ||
|
||
it "does not include people who already have external invoice abo magazin in this year" do | ||
ExternalInvoice::AboMagazin.create!(person: abonnent, year: (abonnent_role.end_on + 1.day).year) | ||
expect(subject.active_abonnenten).to be_empty | ||
end | ||
|
||
it "does include people who already have external invoice but not in this year" do | ||
ExternalInvoice::AboMagazin.create!(person: abonnent, year: 3.years.ago.year) | ||
expect(subject.active_abonnenten).to eq [abonnent] | ||
end | ||
end | ||
end |