Donation payment management feature/193-donation_payments #194

Merged
niggl merged 14 commits from feature/193-donation_payments into dev 2021-04-14 16:56:24 +00:00
13 changed files with 138 additions and 5 deletions

View File

@ -33,6 +33,7 @@ export class CreateDistanceDonation extends CreateDonation {
let newDonation = new DistanceDonation; let newDonation = new DistanceDonation;
newDonation.amountPerDistance = this.amountPerDistance; newDonation.amountPerDistance = this.amountPerDistance;
newDonation.paidAmount = this.paidAmount;
newDonation.donor = await this.getDonor(); newDonation.donor = await this.getDonor();
newDonation.runner = await this.getRunner(); newDonation.runner = await this.getRunner();

View File

@ -1,4 +1,4 @@
import { IsInt, IsPositive } from 'class-validator'; import { IsInt, IsOptional, IsPositive } from 'class-validator';
import { getConnection } from 'typeorm'; import { getConnection } from 'typeorm';
import { DonorNotFoundError } from '../../../errors/DonorErrors'; import { DonorNotFoundError } from '../../../errors/DonorErrors';
import { Donation } from '../../entities/Donation'; import { Donation } from '../../entities/Donation';
@ -16,6 +16,13 @@ export abstract class CreateDonation {
@IsPositive() @IsPositive()
donor: number; donor: number;
/**
* The donation's paid amount in the smalles unit of your currency (default: euro cent).
*/
@IsInt()
@IsOptional()
paidAmount?: number;
/** /**
* Creates a new Donation entity from this. * Creates a new Donation entity from this.
*/ */

View File

@ -21,6 +21,7 @@ export class CreateFixedDonation extends CreateDonation {
let newDonation = new FixedDonation; let newDonation = new FixedDonation;
newDonation.amount = this.amount; newDonation.amount = this.amount;
newDonation.paidAmount = this.paidAmount;
newDonation.donor = await this.getDonor(); newDonation.donor = await this.getDonor();
return newDonation; return newDonation;

View File

@ -32,6 +32,7 @@ export class UpdateDistanceDonation extends UpdateDonation {
*/ */
public async update(donation: DistanceDonation): Promise<DistanceDonation> { public async update(donation: DistanceDonation): Promise<DistanceDonation> {
donation.amountPerDistance = this.amountPerDistance; donation.amountPerDistance = this.amountPerDistance;
donation.paidAmount = this.paidAmount;
donation.donor = await this.getDonor(); donation.donor = await this.getDonor();
donation.runner = await this.getRunner(); donation.runner = await this.getRunner();

View File

@ -1,4 +1,4 @@
import { IsInt, IsPositive } from 'class-validator'; import { IsInt, IsOptional, IsPositive } from 'class-validator';
import { getConnection } from 'typeorm'; import { getConnection } from 'typeorm';
import { DonorNotFoundError } from '../../../errors/DonorErrors'; import { DonorNotFoundError } from '../../../errors/DonorErrors';
import { Donation } from '../../entities/Donation'; import { Donation } from '../../entities/Donation';
@ -23,6 +23,13 @@ export abstract class UpdateDonation {
@IsPositive() @IsPositive()
donor: number; donor: number;
/**
* The donation's paid amount in the smalles unit of your currency (default: euro cent).
*/
@IsInt()
@IsOptional()
paidAmount?: number;
/** /**
* Creates a new Donation entity from this. * Creates a new Donation entity from this.
*/ */

View File

@ -20,6 +20,7 @@ export class UpdateFixedDonation extends UpdateDonation {
*/ */
public async update(donation: FixedDonation): Promise<FixedDonation> { public async update(donation: FixedDonation): Promise<FixedDonation> {
donation.amount = this.amount; donation.amount = this.amount;
donation.paidAmount = this.paidAmount;
donation.donor = await this.getDonor(); donation.donor = await this.getDonor();
return donation; return donation;

View File

@ -2,7 +2,7 @@ import {
IsInt, IsInt,
IsNotEmpty IsNotEmpty
} from "class-validator"; } from "class-validator";
import { Entity, ManyToOne, PrimaryGeneratedColumn, TableInheritance } from "typeorm"; import { Column, Entity, ManyToOne, PrimaryGeneratedColumn, TableInheritance } from "typeorm";
import { ResponseDonation } from '../responses/ResponseDonation'; import { ResponseDonation } from '../responses/ResponseDonation';
import { Donor } from './Donor'; import { Donor } from './Donor';
@ -34,6 +34,13 @@ export abstract class Donation {
*/ */
public abstract get amount(): number; public abstract get amount(): number;
/**
* The donation's paid amount in cents (or whatever your currency's smallest unit is.).
* Used to mark donations as paid.
*/
@Column({ nullable: true })
@IsInt()
paidAmount: number;
/** /**
* Turns this entity into it's response class. * Turns this entity into it's response class.

View File

@ -33,6 +33,15 @@ export class Donor extends Participant {
return this.donations.reduce((sum, current) => sum + current.amount, 0); return this.donations.reduce((sum, current) => sum + current.amount, 0);
} }
/**
* Returns the total paid donations of a donor based on his linked donations.
*/
@IsInt()
public get paidDonationAmount(): number {
if (!this.donations) { return 0; }
return this.donations.reduce((sum, current) => sum + current.paidAmount, 0);
}
/** /**
* Turns this entity into it's response class. * Turns this entity into it's response class.
*/ */

View File

@ -0,0 +1,7 @@
/**
* This enum contains all status a donation can inherit regarding it's payment status.
*/
export enum DonationStatus {
OPEN = 'OPEN',
PAID = 'PAID'
}

View File

@ -1,5 +1,6 @@
import { IsInt, IsNotEmpty, IsPositive } from "class-validator"; import { IsInt, IsNotEmpty, IsPositive } from "class-validator";
import { Donation } from '../entities/Donation'; import { Donation } from '../entities/Donation';
import { DonationStatus } from '../enums/DonationStatus';
import { ResponseObjectType } from '../enums/ResponseObjectType'; import { ResponseObjectType } from '../enums/ResponseObjectType';
import { IResponse } from './IResponse'; import { IResponse } from './IResponse';
import { ResponseDonor } from './ResponseDonor'; import { ResponseDonor } from './ResponseDonor';
@ -15,6 +16,12 @@ export class ResponseDonation implements IResponse {
*/ */
responseType: ResponseObjectType = ResponseObjectType.DONATION; responseType: ResponseObjectType = ResponseObjectType.DONATION;
/**
* The donation's payment status.
* Provides you with a quick indicator of it's payment status.
*/
status: DonationStatus;
/** /**
* The donation's id. * The donation's id.
*/ */
@ -34,6 +41,12 @@ export class ResponseDonation implements IResponse {
@IsInt() @IsInt()
amount: number; amount: number;
/**
* The donation's paid amount in the smalles unit of your currency (default: euro cent).
*/
@IsInt()
paidAmount: number;
/** /**
* Creates a ResponseDonation object from a scan. * Creates a ResponseDonation object from a scan.
* @param donation The donation the response shall be build for. * @param donation The donation the response shall be build for.
@ -42,5 +55,12 @@ export class ResponseDonation implements IResponse {
this.id = donation.id; this.id = donation.id;
this.donor = donation.donor.toResponse(); this.donor = donation.donor.toResponse();
this.amount = donation.amount; this.amount = donation.amount;
this.paidAmount = donation.paidAmount || 0;
if (this.paidAmount < this.amount) {
this.status = DonationStatus.OPEN;
}
else {
this.status = DonationStatus.PAID;
}
} }
} }

View File

@ -28,6 +28,12 @@ export class ResponseDonor extends ResponseParticipant implements IResponse {
@IsInt() @IsInt()
donationAmount: number; donationAmount: number;
/**
* Returns the total paid donations of a donor based on his linked donations.
*/
@IsInt()
paidDonationAmount: number;
/** /**
* Creates a ResponseRunner object from a runner. * Creates a ResponseRunner object from a runner.
* @param runner The user the response shall be build for. * @param runner The user the response shall be build for.
@ -36,5 +42,6 @@ export class ResponseDonor extends ResponseParticipant implements IResponse {
super(donor); super(donor);
this.receiptNeeded = donor.receiptNeeded; this.receiptNeeded = donor.receiptNeeded;
this.donationAmount = donor.donationAmount; this.donationAmount = donor.donationAmount;
this.paidDonationAmount = donor.paidDonationAmount;
} }
} }

View File

@ -170,7 +170,7 @@ describe('POST /api/donations/fixed successfully', () => {
expect(res.status).toEqual(200); expect(res.status).toEqual(200);
expect(res.headers['content-type']).toContain("application/json") expect(res.headers['content-type']).toContain("application/json")
}); });
it('creating a new fixed donation should return 200', async () => { it('creating a new fixed donation with more params should return 200', async () => {
const res = await axios.post(base + '/api/donations/fixed', { const res = await axios.post(base + '/api/donations/fixed', {
"donor": added_donor.id, "donor": added_donor.id,
"amount": 1000 "amount": 1000
@ -181,6 +181,25 @@ describe('POST /api/donations/fixed successfully', () => {
expect(res.data).toEqual({ expect(res.data).toEqual({
"donor": added_donor, "donor": added_donor,
"amount": 1000, "amount": 1000,
"paidAmount": 0,
"status": "OPEN",
"responseType": "DONATION"
});
});
it('creating a new fixed donation with all params should return 200', async () => {
const res = await axios.post(base + '/api/donations/fixed', {
"donor": added_donor.id,
"amount": 1000,
"paidAmount": 1000
}, axios_config);
delete res.data.id;
expect(res.status).toEqual(200);
expect(res.headers['content-type']).toContain("application/json");
expect(res.data).toEqual({
"donor": added_donor,
"amount": 1000,
"paidAmount": 1000,
"status": "PAID",
"responseType": "DONATION" "responseType": "DONATION"
}); });
}); });
@ -219,7 +238,7 @@ describe('POST /api/donations/distance successfully', () => {
expect(res.status).toEqual(200); expect(res.status).toEqual(200);
expect(res.headers['content-type']).toContain("application/json") expect(res.headers['content-type']).toContain("application/json")
}); });
it('creating a new fixed donation should return 200', async () => { it('creating a new distance donation with most params should return 200', async () => {
const res = await axios.post(base + '/api/donations/distance', { const res = await axios.post(base + '/api/donations/distance', {
"runner": added_runner.id, "runner": added_runner.id,
"amountPerDistance": 100, "amountPerDistance": 100,
@ -233,6 +252,28 @@ describe('POST /api/donations/distance successfully', () => {
"amountPerDistance": 100, "amountPerDistance": 100,
"runner": added_runner, "runner": added_runner,
"amount": 0, "amount": 0,
"paidAmount": 0,
"status": "PAID",
"responseType": "DISTANCEDONATION"
})
});
it('creating a new distance donation with all params should return 200', async () => {
const res = await axios.post(base + '/api/donations/distance', {
"runner": added_runner.id,
"amountPerDistance": 100,
"donor": added_donor.id,
"paidAmount": 1000
}, axios_config);
delete res.data.id;
expect(res.status).toEqual(200);
expect(res.headers['content-type']).toContain("application/json");
expect(res.data).toEqual({
"donor": added_donor,
"amountPerDistance": 100,
"runner": added_runner,
"amount": 0,
"paidAmount": 1000,
"status": "PAID",
"responseType": "DISTANCEDONATION" "responseType": "DISTANCEDONATION"
}) })
}); });

View File

@ -213,6 +213,17 @@ describe('adding + updating fixed donation valid', () => {
expect(res.headers['content-type']).toContain("application/json"); expect(res.headers['content-type']).toContain("application/json");
expect(res.data.amount).toEqual(42); expect(res.data.amount).toEqual(42);
}); });
it('updating paidAmount should return 200', async () => {
const res = await axios.put(base + '/api/donations/fixed/' + added_donation.id, {
"id": added_donation.id,
"donor": added_donor.id,
"amount": 42,
"paidAmount": 10
}, axios_config);
expect(res.status).toEqual(200);
expect(res.headers['content-type']).toContain("application/json");
expect(res.data.paidAmount).toEqual(10);
});
it('updating donor should return 200', async () => { it('updating donor should return 200', async () => {
const res = await axios.put(base + '/api/donations/fixed/' + added_donation.id, { const res = await axios.put(base + '/api/donations/fixed/' + added_donation.id, {
"id": added_donation.id, "id": added_donation.id,
@ -317,6 +328,19 @@ describe('adding + updating distance donation valid', () => {
expect(res.headers['content-type']).toContain("application/json"); expect(res.headers['content-type']).toContain("application/json");
expect(res.data.amountPerDistance).toEqual(69); expect(res.data.amountPerDistance).toEqual(69);
}); });
it('updating paidAmount should return 200', async () => {
const res = await axios.put(base + '/api/donations/distance/' + added_donation.id, {
"id": added_donation.id,
"runner": added_runner.id,
"amountPerDistance": 69,
"donor": added_donor.id,
"paidAmount": 10
}, axios_config);
delete res.data.donor.donationAmount;
expect(res.status).toEqual(200);
expect(res.headers['content-type']).toContain("application/json");
expect(res.data.paidAmount).toEqual(10);
});
it('updating runner should return 200', async () => { it('updating runner should return 200', async () => {
const res = await axios.put(base + '/api/donations/distance/' + added_donation.id, { const res = await axios.put(base + '/api/donations/distance/' + added_donation.id, {
"id": added_donation.id, "id": added_donation.id,