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

basic inteface to retrieve payment schedules

This commit is contained in:
Sylvain 2021-01-25 17:42:30 +01:00
parent 01a0612a4b
commit def0778a4d
16 changed files with 177 additions and 7 deletions

View File

@ -23,7 +23,6 @@ class API::InvoicesController < API::ApiController
p = params.require(:query).permit(:number, :customer, :date, :order_by, :page, :size)
render json: { error: 'page must be an integer' }, status: :unprocessable_entity and return unless p[:page].is_a? Integer
render json: { error: 'size must be an integer' }, status: :unprocessable_entity and return unless p[:size].is_a? Integer
order = InvoicesService.parse_order(p[:order_by])

View File

@ -5,6 +5,21 @@ class API::PaymentSchedulesController < API::ApiController
before_action :authenticate_user!
before_action :set_payment_schedule, only: %i[download]
def list
authorize PaymentSchedule
p = params.require(:query).permit(:reference, :customer, :date, :page, :size)
render json: { error: 'page must be an integer' }, status: :unprocessable_entity and return unless p[:page].is_a? Integer
render json: { error: 'size must be an integer' }, status: :unprocessable_entity and return unless p[:size].is_a? Integer
@payment_schedules = PaymentScheduleService.list(
p[:page],
p[:size],
reference: p[:reference], customer: p[:customer], date: p[:date]
)
end
def download
authorize @payment_schedule
send_file File.join(Rails.root, @payment_schedule.file), type: 'application/pdf', disposition: 'attachment'

View File

@ -0,0 +1,17 @@
import apiClient from './api-client';
import { AxiosResponse } from 'axios';
import { PaymentSchedule, PaymentScheduleIndexRequest } from '../models/payment-schedule';
import wrapPromise, { IWrapPromise } from '../lib/wrap-promise';
export default class PaymentScheduleAPI {
async list (query: PaymentScheduleIndexRequest): Promise<Array<PaymentSchedule>> {
const res: AxiosResponse = await apiClient.post(`/api/payment_schedules/list`, query);
return res?.data;
}
static list (query: PaymentScheduleIndexRequest): IWrapPromise<Array<PaymentSchedule>> {
const api = new PaymentScheduleAPI();
return wrapPromise(api.list(query));
}
}

View File

@ -0,0 +1,39 @@
/**
* This component shows a list of all payment schedules with their associated deadlines (aka. PaymentScheduleItem) and invoices
*/
import React, { useState } from 'react';
import { IApplication } from '../models/application';
import { useTranslation } from 'react-i18next';
import { Loader } from './loader';
import { react2angular } from 'react2angular';
import PaymentScheduleAPI from '../api/payment-schedule';
declare var Application: IApplication;
const paymentSchedulesList = PaymentScheduleAPI.list({ query: { page: 1, size: 20 } });
const PaymentSchedulesList: React.FC = () => {
const { t } = useTranslation('admin');
const paymentSchedules = paymentSchedulesList.read();
return (
<div className="payment-schedules-list">
<ul>
{paymentSchedules.map(p => `<li>${p.reference}</li>`)}
</ul>
</div>
);
}
const PaymentSchedulesListWrapper: React.FC = () => {
return (
<Loader>
<PaymentSchedulesList />
</Loader>
);
}
Application.Components.component('paymentSchedulesList', react2angular(PaymentSchedulesListWrapper));

View File

@ -1,7 +1,10 @@
export interface PaymentScheduleItem {
id: number,
amount: number,
due_date: Date
due_date: Date,
state: string,
invoice_id: number,
payment_method: string,
details: {
recurring: number,
adjustment: number,
@ -18,5 +21,25 @@ export interface PaymentSchedule {
reference: string,
payment_method: string,
wallet_amount: number,
items: Array<PaymentScheduleItem>
items: Array<PaymentScheduleItem>,
created_at: Date,
chained_footprint: boolean,
user: {
name: string
},
operator: {
id: number,
first_name: string,
last_name: string,
}
}
export interface PaymentScheduleIndexRequest {
query: {
reference?: string,
customer?: string,
date?: Date,
page: number,
size: number
}
}

View File

@ -34,6 +34,10 @@
<ng-include src="'/admin/invoices/list.html'"></ng-include>
</uib-tab>
<uib-tab heading="{{ 'app.admin.invoices.payment_schedules_list' | translate }}" ng-show="$root.modules.invoicing" index="4">
<payment-schedules-list />
</uib-tab>
<uib-tab heading="{{ 'app.admin.invoices.invoicing_settings' | translate }}" index="1" class="invoices-settings">
<ng-include src="'/admin/invoices/settings.html'"></ng-include>
</uib-tab>

View File

@ -0,0 +1,12 @@
# frozen_string_literal: true
# Check the access policies for API::PaymentSchedulesController
class PaymentSchedulePolicy < ApplicationPolicy
def list?
user.admin? || user.manager?
end
def download?
user.admin? || user.manager? || (record.invoicing_profile.user_id == user.id)
end
end

View File

@ -98,7 +98,43 @@ class PaymentScheduleService
# save the results
invoice.save
payment_schedule_item.update_attributes(invoice_id: invoice.id, stp_invoice_id: stp_invoice.id)
payment_schedule_item.update_attributes(invoice_id: invoice.id, stp_invoice_id: stp_invoice&.id)
end
##
# return a paginated list of PaymentSchedule, optionally filtered, with their associated PaymentScheduleItem
# @param page {number} page number, used to paginate results
# @param size {number} number of items per page
# @param filters {Hash} allowed filters: reference, customer, date.
##
def self.list(page, size, filters = {})
ps = PaymentSchedule.includes(:invoicing_profile, :payment_schedule_items, :subscription)
.joins(:invoicing_profile)
.page(page)
.per(size)
unless filters[:reference].nil?
ps = ps.where(
'payment_schedules.reference LIKE :search',
search: "#{filters[:reference]}%"
)
end
unless filters[:customer].nil?
# ILIKE => PostgreSQL case-insensitive LIKE
ps = ps.where(
'invoicing_profiles.first_name ILIKE :search OR invoicing_profiles.last_name ILIKE :search',
search: "%#{filters[:customer]}%"
)
end
unless filters[:date].nil?
ps = ps.where(
"date_trunc('day', payment_schedules.created_at) = :search",
search: "%#{DateTime.iso8601(filters[:date]).to_time.to_date}%"
)
end
ps
end
private

View File

@ -7,7 +7,7 @@ json.array!(@invoices) do |invoice|
json.extract! invoice, :id, :created_at, :reference, :invoiced_type, :avoir_date
json.user_id invoice.invoicing_profile.user_id
json.total invoice.total / 100.00
json.name invoice.invoicing_profile.full_name
json.has_avoir invoice.refunded?
json.is_avoir invoice.is_a?(Avoir)

View File

@ -0,0 +1,20 @@
# frozen_string_literal: true
json.array! @payment_schedules do |ps|
json.extract! ps, :id, :reference, :created_at, :payment_method
json.total ps.total / 100.00
json.chained_footprint ps.check_footprint
json.user do
json.name ps.invoicing_profile.full_name
end
if ps.operator_profile
json.operator do
json.id ps.operator_profile.user_id
json.extract! ps.operator_profile, :first_name, :last_name
end
end
json.items ps.payment_schedule_items do |item|
json.extract! item, :id, :due_date, :state, :invoice_id, :payment_method
json.amount item.amount / 100.00
end
end

View File

@ -16,7 +16,7 @@ class PaymentScheduleItemWorker
if stp_invoice.status == 'paid'
##### Stripe / Successfully paid
PaymentScheduleService.new.generate_invoice(psi, stp_invoice)
psi.update_attributes(state: 'paid')
psi.update_attributes(state: 'paid', payment_method: 'stripe')
else
##### Stripe / Payment error
NotificationCenter.call type: 'notify_admin_payment_schedule_failed',

View File

@ -414,6 +414,7 @@ en:
credit_note: "Credit note"
display_more_invoices: "Display more invoices..."
no_invoices_for_now: "No invoices for now."
payment_schedules_list: "Payment schedules"
invoicing_settings: "Invoicing settings"
warning_invoices_disabled: "Warning : invoices are not enabled. No invoices will be generated by Fab-manager. Nevertheless, you must correctly fill the information below, especially VAT."
change_logo: "Change logo"

View File

@ -414,6 +414,7 @@ fr:
credit_note: "Avoir"
display_more_invoices: "Afficher plus de factures..."
no_invoices_for_now: "Aucune facture pour le moment."
payment_schedules_list: "Échéanciers de paiement"
invoicing_settings: "Paramètres de facturation"
warning_invoices_disabled: "Attention : les factures ne sont pas activées. Aucune facture ne sera générée par Fab-manager. Vous devez néanmoins remplir correctement les informations ci-dessous, particulièrement la TVA."
change_logo: "Changer le logo"

View File

@ -111,7 +111,8 @@ Rails.application.routes.draw do
get 'first', action: 'first', on: :collection
end
resources :payment_schedules, only: %i[index show] do
resources :payment_schedules, only: %i[show] do
post 'list', action: 'list', on: :collection
get 'download', on: :member
end

View File

@ -9,6 +9,7 @@ class CreatePaymentScheduleItems < ActiveRecord::Migration[5.2]
t.string :state, default: 'new'
t.jsonb :details, default: '{}'
t.string :stp_invoice_id
t.string :payment_method
t.belongs_to :payment_schedule, foreign_key: true
t.belongs_to :invoice, foreign_key: true
t.string :footprint

View File

@ -1473,6 +1473,7 @@ CREATE TABLE public.payment_schedule_items (
state character varying DEFAULT 'new'::character varying,
details jsonb DEFAULT '"{}"'::jsonb,
stp_invoice_id character varying,
payment_method character varying,
payment_schedule_id bigint,
invoice_id bigint,
footprint character varying,