Skip to content

Commit

Permalink
feat: seller payouts pdf (#279)
Browse files Browse the repository at this point in the history
  • Loading branch information
JustSamuel authored Aug 30, 2024
1 parent e7f5a6a commit 4cdecce
Show file tree
Hide file tree
Showing 45 changed files with 1,069 additions and 762 deletions.
3 changes: 3 additions & 0 deletions init_scripts/00_make_sudosos_data_dirs.sh
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,6 @@ chown node /app/out/data/invoices

mkdir -p /app/out/data/payout_requests
chown node /app/out/data/payout_requests

mkdir -p /app/out/data/seller_payouts
chown node /app/out/data/seller_payouts
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
"mysql2": "^3.2.0",
"node-cron": "^3.0.2",
"nodemailer": "^6.9.1",
"pdf-generator-client": "github:GEWIS/pdf-generator#4b7c455",
"pdf-generator-client": "github:GEWIS/pdf-generator#04e040b",
"reflect-metadata": "^0.1.13",
"sqlite3": "^5.1.5",
"stripe": "^16.1.0",
Expand Down
9 changes: 4 additions & 5 deletions src/controller/debtor-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ import { asArrayOfDates, asArrayOfUserTypes, asDate, asFromAndTillDate, asReturn
import { In } from 'typeorm';
import { HandoutFinesRequest } from './request/debtor-request';
import Fine from '../entity/fine/fine';
import ReportPdfService from '../service/report-pdf-service';
import { ReturnFileType } from 'pdf-generator-client';

export default class DebtorController extends BaseController {
Expand Down Expand Up @@ -327,7 +326,7 @@ export default class DebtorController extends BaseController {

try {
const report = await DebtorService.getFineReport(fromDate, toDate);
res.json(DebtorService.fineReportToResponse(report));
res.json(report.toResponse());
} catch (error) {
this.logger.error('Could not get fine report:', error);
res.status(500).json('Internal server error.');
Expand Down Expand Up @@ -365,14 +364,14 @@ export default class DebtorController extends BaseController {
try {
const report = await DebtorService.getFineReport(fromDate, toDate);

const pdf = await ReportPdfService.getFineReportPdf(report, fileType);
const buffer = fileType === 'PDF' ? await report.createPdf() : await report.createTex();
const from = `${fromDate.getFullYear()}${fromDate.getMonth() + 1}${fromDate.getDate()}`;
const to = `${toDate.getFullYear()}${toDate.getMonth() + 1}${toDate.getDate()}`;
const fileName = `fine-report-${from}-${to}.${fileType}`;

res.setHeader('Content-Type', 'application/pdf');
res.setHeader('Content-Type', 'application/pdf+tex');
res.setHeader('Content-Disposition', `attachment; filename="${fileName}"`);
res.send(pdf);
res.send(buffer);
} catch (error) {
this.logger.error('Could not get fine report pdf:', error);
res.status(500).json('Internal server error.');
Expand Down
3 changes: 1 addition & 2 deletions src/controller/invoice-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ import User, { UserType } from '../entity/user/user';
import { UpdateInvoiceUserRequest } from './request/user-request';
import InvoiceUser from '../entity/user/invoice-user';
import { parseInvoiceUserToResponse } from '../helpers/revision-to-response';
import FileService from '../service/file-service';
import { AppDataSource } from '../database/database';
import { NotImplementedError } from '../errors';

Expand Down Expand Up @@ -358,7 +357,7 @@ export default class InvoiceController extends BaseController {
return;
}

const pdf = await FileService.getOrCreatePDF(invoice);
const pdf = await invoice.getOrCreatePdf();

res.status(200).json({ pdf: pdf.downloadName });
} catch (error) {
Expand Down
3 changes: 1 addition & 2 deletions src/controller/payout-request-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ import { PayoutRequestState } from '../entity/transactions/payout/payout-request
import PayoutRequestRequest from './request/payout-request-request';
import User from '../entity/user/user';
import BalanceService from '../service/balance-service';
import FileService from '../service/file-service';
import { PdfUrlResponse } from './response/simple-file-response';

export default class PayoutRequestController extends BaseController {
Expand Down Expand Up @@ -290,7 +289,7 @@ export default class PayoutRequestController extends BaseController {
return;
}

const pdf = await FileService.getOrCreatePDF(payoutRequest);
const pdf = await payoutRequest.getOrCreatePdf();

res.status(200).json({ pdf: pdf.downloadName } as PdfUrlResponse);
} catch (error) {
Expand Down
10 changes: 0 additions & 10 deletions src/controller/response/debtor-response.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import BaseResponse from './base-response';
import { PaginationResult } from '../../helpers/pagination';
import { BaseUserResponse } from './user-response';
import BalanceResponse from './balance-response';
import { Dinero } from 'dinero.js';

/**
* @typedef {object} UserToFineResponse
Expand All @@ -35,15 +34,6 @@ export interface UserToFineResponse {
balances: BalanceResponse[]
}

export interface FineReport {
fromDate: Date;
toDate: Date;
count: number;
handedOut: Dinero;
waivedCount: number;
waivedAmount: Dinero;
}

/**
* @typedef {object} FineReportResponse
* @property {string} fromDate.required - From date of the report
Expand Down
8 changes: 5 additions & 3 deletions src/controller/seller-payout-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,8 @@ export default class SellerPayoutController extends BaseController {
* @operationId getSellerPayoutReportPdf
* @tags sellerPayouts - Operations of the seller payout controller
* @param {integer} id.path.required - ID of the seller payout that should be returned
* @return {string} 200 - The requested report - application/pdf
* @param {boolean} force.query - Force the generation of the PDF
* @return {PdfUrlResponse} 200 - The requested report
* @return {string} 404 - SellerPayout not found.
* @return {string} 500 - Internal server error.
*/
Expand All @@ -203,15 +204,16 @@ export default class SellerPayoutController extends BaseController {

try {
const sellerPayoutId = Number(req.params.id);
const force = req.query.force === 'true';
const service = new SellerPayoutService();
const [[sellerPayout]] = await service.getSellerPayouts({ sellerPayoutId });
if (!sellerPayout) {
res.status(404).json('Seller Payout not found.');
return;
}

// TODO: create the pdf and actually send it
res.status(501).json(null);
const pdf = await sellerPayout.getOrCreatePdf(force);
res.status(200).json({ pdf: pdf.downloadName });
} catch (error) {
this.logger.error('Could not get sales report for seller payout:', error);
res.status(500).json('Internal server error.');
Expand Down
2 changes: 2 additions & 0 deletions src/database/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ import { NestedProductCategories1722517212441 } from '../migrations/172251721244
import SellerPayout from '../entity/transactions/payout/seller-payout';
import { InvoiceAsTopups1724506999318 } from '../migrations/1724506999318-invoice-as-topups';
import { SellerPayouts1724855153990 } from '../migrations/1724855153990-seller-payouts';
import SellerPayoutPdf from '../entity/file/seller-payout-pdf';

// We need to load the dotenv to prevent the env from being undefined.
dotenv.config();
Expand Down Expand Up @@ -149,6 +150,7 @@ const options: DataSourceOptions = {
PayoutRequestPdf,
PayoutRequestStatus,
SellerPayout,
SellerPayoutPdf,
Fine,
FineHandoutEvent,
UserFineGroup,
Expand Down
135 changes: 135 additions & 0 deletions src/entity/file/pdf-able.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
/**
* SudoSOS back-end API service.
* Copyright (C) 2024 Study association GEWIS
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import Pdf from './pdf-file';
import BaseEntity from '../base-entity';
import User from '../user/user';
import { PdfService, RouteParams, UnstoredPdfService } from '../../service/pdf/pdf-service';
import { hashJSON } from '../../helpers/hash';

type Constructor<T = {}> = new (...args: any[]) => T;

export interface IPdfAble<S extends Pdf = Pdf> extends BaseEntity {
pdf?: S,

getPdfParamHash(): Promise<string>,

createPdf(): Promise<S>

getOwner(): Promise<User>
}

/**
* PdfAble is a Mixin that allows entities to be converted to Pdf.
* @param Base
* @constructor
*/
export function PdfAble<TBase extends Constructor<BaseEntity>>(Base: TBase) {
abstract class PdfAbleClass extends Base {
/**
* The id of the Pdf file.
*/
abstract pdfId?: number;

/**
* The Pdf file.
*/
abstract pdf?: Pdf;

/**
* The service that creates the Pdf file.
*/
abstract pdfService: PdfService<Pdf, this, RouteParams>;

/**
* Get the owner of the Pdf file.
* Needed for the File.createdBy field.
*/
abstract getOwner(): Promise<User>;

/**
* Create the Pdf file.
*/
async createPdf(): Promise<Pdf> {
return this.pdfService.createPdf(this as any);
}

/**
* Get the hash of the parameters of the Pdf file.
*/
async getPdfParamHash(): Promise<string> {
return hashJSON(await this.pdfService.getParameters(this as any));
}

/**
* Get the Pdf file.
* If the Pdf file is not current, create it.
* @param force If true, always create the Pdf file.
*/
public async getOrCreatePdf(force: boolean = false): Promise<Pdf> {
if (this.pdf && !force) {
// check if pdf is current.
if (await this.validatePdfHash()) return this.pdf;
}

return Promise.resolve(await this.createPdf());
}

/**
* Validates if the Pdf matches the stored hash.
*/
async validatePdfHash(): Promise<boolean> {
if (!this.pdf) return false;
const hash = await this.getPdfParamHash();

return hash === this.pdf.hash;
}
}

return PdfAbleClass;
}

export interface IUnstoredPdfAble {
createPdf(): Promise<Buffer>;
}

export function UnstoredPdfAble<TBase extends Constructor>(Base: TBase) {
abstract class UnstoredPdfAbleClass extends Base implements IUnstoredPdfAble {
/**
* The service that creates the Pdf buffer.
*/
abstract pdfService: UnstoredPdfService<UnstoredPdfAbleClass, RouteParams>;

/**
* Create the Pdf buffer.
* This method generates the PDF and returns it as a Buffer.
*/
async createPdf(): Promise<Buffer> {
return this.pdfService.createPdf(this as UnstoredPdfAbleClass);
}

async createTex(): Promise<Buffer> {
return this.pdfService.createTex(this as UnstoredPdfAbleClass);
}

async getPdfParamHash(): Promise<string> {
return hashJSON(await this.pdfService.getParameters(this as UnstoredPdfAbleClass));
}
}

return UnstoredPdfAbleClass;
}
13 changes: 0 additions & 13 deletions src/entity/file/pdf-file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,6 @@

import { Column, Entity } from 'typeorm';
import BaseFile from './base-file';
import BaseEntity from '../base-entity';
import { Client } from 'pdf-generator-client';
import FileService from '../../service/file-service';

/**
* @typedef {BaseFile} Pdf
Expand All @@ -33,13 +30,3 @@ export default class Pdf extends BaseFile {
public hash: string;
}

export interface Pdfable<S extends Pdf = Pdf> extends BaseEntity {
pdf?: S,
getPdfParamHash: () => Promise<string>,
createPDF: () => Promise<S>
}

export interface PdfGenerator {
client: Client,
fileService: FileService
}
26 changes: 26 additions & 0 deletions src/entity/file/seller-payout-pdf.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/**
* SudoSOS back-end API service.
* Copyright (C) 2024 Study association GEWIS
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

import { Entity } from 'typeorm';
import Pdf from './pdf-file';

/**
* @typedef {Pdf} SellerPayoutPdf
*/
@Entity()
export default class SellerPayoutPdf extends Pdf {}
15 changes: 7 additions & 8 deletions src/entity/invoices/invoice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,14 @@ import InvoiceEntry from './invoice-entry';
// eslint-disable-next-line import/no-cycle
import InvoiceStatus from './invoice-status';
import InvoicePdf from '../file/invoice-pdf';
import { hashJSON } from '../../helpers/hash';
import InvoicePdfService from '../../service/invoice-pdf-service';
import SubTransactionRow from '../transactions/sub-transaction-row';
import { INVOICE_PDF_LOCATION } from '../../files/storage';
import { PdfAble } from '../file/pdf-able';
import InvoicePdfService from '../../service/pdf/invoice-pdf-service';


@Entity()
export default class Invoice extends BaseEntity {
export default class Invoice extends PdfAble(BaseEntity) {

/**
* The ID of the account for whom the invoice is
Expand Down Expand Up @@ -159,11 +160,9 @@ export default class Invoice extends BaseEntity {
@OneToMany(() => SubTransactionRow, (row) => row.invoice, { cascade: false })
public subTransactionRows: SubTransactionRow[];

async getPdfParamHash(): Promise<string> {
return hashJSON(InvoicePdfService.getParameters(this));
}
pdfService = new InvoicePdfService(INVOICE_PDF_LOCATION);

createPDF(): Promise<InvoicePdf> {
return InvoicePdfService.createPdf(this.id);
async getOwner(): Promise<User> {
return this.to;
}
}
Loading

0 comments on commit 4cdecce

Please sign in to comment.