DEV Community

Nadim Chowdhury
Nadim Chowdhury

Posted on • Updated on

Detail implementation of Financial Management

Here's an implementation for a Financial Management system focusing on fee management, billing, payments, and financial report generation using Next.js, NestJS, and GraphQL.

Backend (NestJS)

1. Entities

Fee Entity:

// fee.entity.ts
import { Entity, Column, PrimaryGeneratedColumn, ManyToOne } from 'typeorm';
import { User } from './user.entity';

@Entity()
export class Fee {
  @PrimaryGeneratedColumn()
  id: number;

  @ManyToOne(() => User, (user) => user.fees)
  user: User;

  @Column()
  amount: number;

  @Column()
  dueDate: Date;

  @Column()
  status: string;  // Pending, Paid, Overdue
}
Enter fullscreen mode Exit fullscreen mode

Payment Entity:

// payment.entity.ts
import { Entity, Column, PrimaryGeneratedColumn, ManyToOne, CreateDateColumn } from 'typeorm';
import { Fee } from './fee.entity';

@Entity()
export class Payment {
  @PrimaryGeneratedColumn()
  id: number;

  @ManyToOne(() => Fee, (fee) => fee.payments)
  fee: Fee;

  @Column()
  amount: number;

  @CreateDateColumn()
  paymentDate: Date;

  @Column()
  method: string;  // e.g., Credit Card, Bank Transfer
}
Enter fullscreen mode Exit fullscreen mode

2. Services

Fee Service:

// fee.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Fee } from './fee.entity';

@Injectable()
export class FeeService {
  constructor(
    @InjectRepository(Fee)
    private feeRepository: Repository<Fee>,
  ) {}

  findAll(): Promise<Fee[]> {
    return this.feeRepository.find({ relations: ['user'] });
  }

  findOne(id: number): Promise<Fee> {
    return this.feeRepository.findOne(id, { relations: ['user'] });
  }

  create(userId: number, amount: number, dueDate: Date): Promise<Fee> {
    const newFee = this.feeRepository.create({ user: { id: userId }, amount, dueDate, status: 'Pending' });
    return this.feeRepository.save(newFee);
  }

  updateStatus(id: number, status: string): Promise<Fee> {
    return this.feeRepository.save({ id, status });
  }
}
Enter fullscreen mode Exit fullscreen mode

Payment Service:

// payment.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Payment } from './payment.entity';

@Injectable()
export class PaymentService {
  constructor(
    @InjectRepository(Payment)
    private paymentRepository: Repository<Payment>,
  ) {}

  findAll(): Promise<Payment[]> {
    return this.paymentRepository.find({ relations: ['fee'] });
  }

  create(feeId: number, amount: number, method: string): Promise<Payment> {
    const newPayment = this.paymentRepository.create({ fee: { id: feeId }, amount, method });
    return this.paymentRepository.save(newPayment);
  }
}
Enter fullscreen mode Exit fullscreen mode

3. Resolvers

Fee Resolver:

// fee.resolver.ts
import { Resolver, Query, Mutation, Args } from '@nestjs/graphql';
import { FeeService } from './fee.service';
import { Fee } from './fee.entity';

@Resolver(() => Fee)
export class FeeResolver {
  constructor(private feeService: FeeService) {}

  @Query(() => [Fee])
  async fees() {
    return this.feeService.findAll();
  }

  @Mutation(() => Fee)
  async createFee(
    @Args('userId') userId: number,
    @Args('amount') amount: number,
    @Args('dueDate') dueDate: string,
  ) {
    return this.feeService.create(userId, amount, new Date(dueDate));
  }

  @Mutation(() => Fee)
  async updateFeeStatus(
    @Args('id') id: number,
    @Args('status') status: string,
  ) {
    return this.feeService.updateStatus(id, status);
  }
}
Enter fullscreen mode Exit fullscreen mode

Payment Resolver:

// payment.resolver.ts
import { Resolver, Query, Mutation, Args } from '@nestjs/graphql';
import { PaymentService } from './payment.service';
import { Payment } from './payment.entity';

@Resolver(() => Payment)
export class PaymentResolver {
  constructor(private paymentService: PaymentService) {}

  @Query(() => [Payment])
  async payments() {
    return this.paymentService.findAll();
  }

  @Mutation(() => Payment)
  async createPayment(
    @Args('feeId') feeId: number,
    @Args('amount') amount: number,
    @Args('method') method: string,
  ) {
    return this.paymentService.create(feeId, amount, method);
  }
}
Enter fullscreen mode Exit fullscreen mode

Frontend (Next.js)

1. Apollo Client Setup

// apollo-client.js
import { ApolloClient, InMemoryCache } from '@apollo/client';

const client = new ApolloClient({
  uri: 'http://localhost:3000/graphql',
  cache: new InMemoryCache(),
});

export default client;
Enter fullscreen mode Exit fullscreen mode

2. Fee Management Page

// pages/fees.js
import { useState } from 'react';
import { useQuery, useMutation, gql } from '@apollo/client';

const GET_FEES = gql`
  query GetFees {
    fees {
      id
      amount
      dueDate
      status
      user {
        username
      }
    }
  }
`;

const CREATE_FEE = gql`
  mutation CreateFee($userId: Int!, $amount: Float!, $dueDate: String!) {
    createFee(userId: $userId, amount: $amount, dueDate: $dueDate) {
      id
      amount
      dueDate
      status
    }
  }
`;

const UPDATE_FEE_STATUS = gql`
  mutation UpdateFeeStatus($id: Int!, $status: String!) {
    updateFeeStatus(id: $id, status: $status) {
      id
      status
    }
  }
`;

export default function Fees() {
  const { loading, error, data } = useQuery(GET_FEES);
  const [createFee] = useMutation(CREATE_FEE);
  const [updateFeeStatus] = useMutation(UPDATE_FEE_STATUS);
  const [userId, setUserId] = useState('');
  const [amount, setAmount] = useState('');
  const [dueDate, setDueDate] = useState('');
  const [feeId, setFeeId] = useState('');
  const [status, setStatus] = useState('');

  const handleCreateFee = async (e) => {
    e.preventDefault();
    await createFee({ variables: { userId: parseInt(userId), amount: parseFloat(amount), dueDate } });
    setUserId('');
    setAmount('');
    setDueDate('');
  };

  const handleUpdateFeeStatus = async (e) => {
    e.preventDefault();
    await updateFeeStatus({ variables: { id: parseInt(feeId), status } });
    setFeeId('');
    setStatus('');
  };

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error.message}</p>;

  return (
    <div>
      <h1>Fees</h1>
      <form onSubmit={handleCreateFee}>
        <input
          type="number"
          placeholder="User ID"
          value={userId}
          onChange={(e) => setUserId(e.target.value)}
        />
        <input
          type="number"
          placeholder="Amount"
          value={amount}
          onChange={(e) => setAmount(e.target.value)}
        />
        <input
          type="date"
          placeholder="Due Date"
          value={dueDate}
          onChange={(e) => setDueDate(e.target.value)}
        />
        <button type="submit">Create Fee</button>
      </form>
      <form onSubmit={handleUpdateFeeStatus}>
        <input
          type="number"
          placeholder="Fee ID"
          value={feeId}
          onChange={(e) => setFeeId(e.target.value)}
        />
        <input
          type="text"
          placeholder="Status"
          value={status}
          onChange={(e) => setStatus(e.target.value)}
        />
        <button type="submit">Update Fee Status</button>
      </form>
      <ul>
        {data.fees.map((fee) => (
          <li key={fee.id}>
            User: {fee.user.username}, Amount: {fee.amount}, Due: {fee.dueDate}, Status: {fee.status}
          </li>
        ))}
      </ul>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

3. Payment Management Page

// pages/payments.js
import { useState } from 'react';
import { useQuery, useMutation, gql } from '@apollo/client';

const GET_PAYMENTS = gql`
  query GetPayments {
    payments {
      id
      amount
      paymentDate
      method
      fee {
        amount
        user {
          username
        }
      }
    }
  }
`;

const CREATE_PAYMENT = gql`
  mutation CreatePayment($feeId: Int!, $amount: Float!, $method: String!) {
    createPayment(feeId: $feeId, amount

: $amount, method: $method) {
      id
      amount
      paymentDate
      method
    }
  }
`;

export default function Payments() {
  const { loading, error, data } = useQuery(GET_PAYMENTS);
  const [createPayment] = useMutation(CREATE_PAYMENT);
  const [feeId, setFeeId] = useState('');
  const [amount, setAmount] = useState('');
  const [method, setMethod] = useState('');

  const handleCreatePayment = async (e) => {
    e.preventDefault();
    await createPayment({ variables: { feeId: parseInt(feeId), amount: parseFloat(amount), method } });
    setFeeId('');
    setAmount('');
    setMethod('');
  };

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error.message}</p>;

  return (
    <div>
      <h1>Payments</h1>
      <form onSubmit={handleCreatePayment}>
        <input
          type="number"
          placeholder="Fee ID"
          value={feeId}
          onChange={(e) => setFeeId(e.target.value)}
        />
        <input
          type="number"
          placeholder="Amount"
          value={amount}
          onChange={(e) => setAmount(e.target.value)}
        />
        <input
          type="text"
          placeholder="Method"
          value={method}
          onChange={(e) => setMethod(e.target.value)}
        />
        <button type="submit">Create Payment</button>
      </form>
      <ul>
        {data.payments.map((payment) => (
          <li key={payment.id}>
            Fee: {payment.fee.amount}, User: {payment.fee.user.username}, Amount: {payment.amount}, Date: {payment.paymentDate}, Method: {payment.method}
          </li>
        ))}
      </ul>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Financial Reports (Next.js)

To generate financial reports, we can create a new page that aggregates data from fees and payments.

Financial Reports Page

// pages/financial-reports.js
import { useQuery, gql } from '@apollo/client';

const GET_FEES_AND_PAYMENTS = gql`
  query GetFeesAndPayments {
    fees {
      id
      amount
      dueDate
      status
      user {
        username
      }
    }
    payments {
      id
      amount
      paymentDate
      method
      fee {
        amount
        user {
          username
        }
      }
    }
  }
`;

export default function FinancialReports() {
  const { loading, error, data } = useQuery(GET_FEES_AND_PAYMENTS);

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error.message}</p>;

  const totalFees = data.fees.reduce((sum, fee) => sum + fee.amount, 0);
  const totalPayments = data.payments.reduce((sum, payment) => sum + payment.amount, 0);

  return (
    <div>
      <h1>Financial Reports</h1>
      <h2>Total Fees: ${totalFees}</h2>
      <h2>Total Payments: ${totalPayments}</h2>
      <h3>Fees</h3>
      <ul>
        {data.fees.map((fee) => (
          <li key={fee.id}>
            User: {fee.user.username}, Amount: {fee.amount}, Due: {fee.dueDate}, Status: {fee.status}
          </li>
        ))}
      </ul>
      <h3>Payments</h3>
      <ul>
        {data.payments.map((payment) => (
          <li key={payment.id}>
            Fee: {payment.fee.amount}, User: {payment.fee.user.username}, Amount: {payment.amount}, Date: {payment.paymentDate}, Method: {payment.method}
          </li>
        ))}
      </ul>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

GraphQL Schema

Define your GraphQL schema to match the resolver functions:

type User {
  id: ID!
  username: String!
}

type Fee {
  id: ID!
  amount: Float!
  dueDate: String!
  status: String!
  user: User!
}

type Payment {
  id: ID!
  amount: Float!
  paymentDate: String!
  method: String!
  fee: Fee!
}

type Query {
  fees: [Fee!]!
  payments: [Payment!]!
}

type Mutation {
  createFee(userId: Int!, amount: Float!, dueDate: String!): Fee!
  updateFeeStatus(id: Int!, status: String!): Fee!
  createPayment(feeId: Int!, amount: Float!, method: String!): Payment!
}
Enter fullscreen mode Exit fullscreen mode

This setup covers the backend and frontend code for developing a fee management system with billing, payments, and financial report generation. You can expand on this by adding more features, such as detailed payment history, invoice generation, and more comprehensive financial reports.

Sure! Let's expand the existing system to include detailed payment history and invoice generation. This involves updating the backend to handle invoices and modifying the frontend to display detailed payment history and generate invoices.

Backend (NestJS)

1. Entities

Add a new Invoice entity:

Invoice Entity:

// invoice.entity.ts
import { Entity, Column, PrimaryGeneratedColumn, ManyToOne, CreateDateColumn } from 'typeorm';
import { User } from './user.entity';
import { Payment } from './payment.entity';

@Entity()
export class Invoice {
  @PrimaryGeneratedColumn()
  id: number;

  @ManyToOne(() => User, (user) => user.invoices)
  user: User;

  @Column()
  amount: number;

  @CreateDateColumn()
  generatedAt: Date;

  @ManyToOne(() => Payment, (payment) => payment.invoice)
  payment: Payment;
}
Enter fullscreen mode Exit fullscreen mode

2. Services

Invoice Service:

// invoice.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Invoice } from './invoice.entity';
import { Payment } from './payment.entity';
import { User } from './user.entity';

@Injectable()
export class InvoiceService {
  constructor(
    @InjectRepository(Invoice)
    private invoiceRepository: Repository<Invoice>,
  ) {}

  findAll(): Promise<Invoice[]> {
    return this.invoiceRepository.find({ relations: ['user', 'payment'] });
  }

  create(userId: number, paymentId: number, amount: number): Promise<Invoice> {
    const newInvoice = this.invoiceRepository.create({
      user: { id: userId },
      payment: { id: paymentId },
      amount,
    });
    return this.invoiceRepository.save(newInvoice);
  }
}
Enter fullscreen mode Exit fullscreen mode

3. Resolvers

Invoice Resolver:

// invoice.resolver.ts
import { Resolver, Query, Mutation, Args } from '@nestjs/graphql';
import { InvoiceService } from './invoice.service';
import { Invoice } from './invoice.entity';

@Resolver(() => Invoice)
export class InvoiceResolver {
  constructor(private invoiceService: InvoiceService) {}

  @Query(() => [Invoice])
  async invoices() {
    return this.invoiceService.findAll();
  }

  @Mutation(() => Invoice)
  async createInvoice(
    @Args('userId') userId: number,
    @Args('paymentId') paymentId: number,
    @Args('amount') amount: number,
  ) {
    return this.invoiceService.create(userId, paymentId, amount);
  }
}
Enter fullscreen mode Exit fullscreen mode

4. Update GraphQL Schema

Update your GraphQL schema to include the new Invoice type and related queries and mutations:

type User {
  id: ID!
  username: String!
  invoices: [Invoice!]!
}

type Fee {
  id: ID!
  amount: Float!
  dueDate: String!
  status: String!
  user: User!
}

type Payment {
  id: ID!
  amount: Float!
  paymentDate: String!
  method: String!
  fee: Fee!
  invoice: Invoice
}

type Invoice {
  id: ID!
  amount: Float!
  generatedAt: String!
  user: User!
  payment: Payment!
}

type Query {
  fees: [Fee!]!
  payments: [Payment!]!
  invoices: [Invoice!]!
}

type Mutation {
  createFee(userId: Int!, amount: Float!, dueDate: String!): Fee!
  updateFeeStatus(id: Int!, status: String!): Fee!
  createPayment(feeId: Int!, amount: Float!, method: String!): Payment!
  createInvoice(userId: Int!, paymentId: Int!, amount: Float!): Invoice!
}
Enter fullscreen mode Exit fullscreen mode

Frontend (Next.js)

1. Invoice Management Page

pages/invoices.js

import { useState } from 'react';
import { useQuery, useMutation, gql } from '@apollo/client';

const GET_INVOICES = gql`
  query GetInvoices {
    invoices {
      id
      amount
      generatedAt
      user {
        username
      }
      payment {
        id
        amount
      }
    }
  }
`;

const CREATE_INVOICE = gql`
  mutation CreateInvoice($userId: Int!, $paymentId: Int!, $amount: Float!) {
    createInvoice(userId: $userId, paymentId: $paymentId, amount: $amount) {
      id
      amount
      generatedAt
    }
  }
`;

export default function Invoices() {
  const { loading, error, data } = useQuery(GET_INVOICES);
  const [createInvoice] = useMutation(CREATE_INVOICE);
  const [userId, setUserId] = useState('');
  const [paymentId, setPaymentId] = useState('');
  const [amount, setAmount] = useState('');

  const handleSubmit = async (e) => {
    e.preventDefault();
    await createInvoice({ variables: { userId: parseInt(userId), paymentId: parseInt(paymentId), amount: parseFloat(amount) } });
    setUserId('');
    setPaymentId('');
    setAmount('');
  };

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error.message}</p>;

  return (
    <div>
      <h1>Invoices</h1>
      <form onSubmit={handleSubmit}>
        <input
          type="number"
          placeholder="User ID"
          value={userId}
          onChange={(e) => setUserId(e.target.value)}
        />
        <input
          type="number"
          placeholder="Payment ID"
          value={paymentId}
          onChange={(e) => setPaymentId(e.target.value)}
        />
        <input
          type="number"
          placeholder="Amount"
          value={amount}
          onChange={(e) => setAmount(e.target.value)}
        />
        <button type="submit">Create Invoice</button>
      </form>
      <ul>
        {data.invoices.map((invoice) => (
          <li key={invoice.id}>
            User: {invoice.user.username}, Amount: {invoice.amount}, Generated At: {invoice.generatedAt}, Payment ID: {invoice.payment.id}
          </li>
        ))}
      </ul>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

2. Detailed Payment History Page

pages/payment-history.js

import { useQuery, gql } from '@apollo/client';

const GET_PAYMENTS = gql`
  query GetPayments {
    payments {
      id
      amount
      paymentDate
      method
      fee {
        id
        amount
        user {
          username
        }
      }
    }
  }
`;

export default function PaymentHistory() {
  const { loading, error, data } = useQuery(GET_PAYMENTS);

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error.message}</p>;

  return (
    <div>
      <h1>Payment History</h1>
      <ul>
        {data.payments.map((payment) => (
          <li key={payment.id}>
            User: {payment.fee.user.username}, Fee Amount: {payment.fee.amount}, Payment Amount: {payment.amount}, Date: {payment.paymentDate}, Method: {payment.method}
          </li>
        ))}
      </ul>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Integrating Payment Creation with Invoice Generation

To automatically generate an invoice when a payment is created, we can modify the PaymentService and PaymentResolver to include invoice generation.

payment.service.ts

import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Payment } from './payment.entity';
import { InvoiceService } from './invoice.service';

@Injectable()
export class PaymentService {
  constructor(
    @InjectRepository(Payment)
    private paymentRepository: Repository<Payment>,
    private invoiceService: InvoiceService,
  ) {}

  async findAll(): Promise<Payment[]> {
    return this.paymentRepository.find({ relations: ['fee', 'fee.user'] });
  }

  async create(feeId: number, amount: number, method: string): Promise<Payment> {
    const newPayment = this.paymentRepository.create({ fee: { id: feeId }, amount, method });
    const payment = await this.paymentRepository.save(newPayment);
    await this.invoiceService.create(payment.fee.user.id, payment.id, amount);
    return payment;
  }
}
Enter fullscreen mode Exit fullscreen mode

payment.resolver.ts

import { Resolver, Query, Mutation, Args } from '@nestjs/graphql';
import { PaymentService } from './payment.service';
import { Payment } from './payment.entity';

@Resolver(() => Payment)
export class PaymentResolver {
  constructor(private paymentService: PaymentService) {}

  @Query(() => [Payment])
  async payments() {
    return this.paymentService.findAll();
  }

  @Mutation(() => Payment)
  async createPayment(
    @Args('feeId') feeId: number,
    @Args('amount') amount: number,
    @Args('method') method: string,
  ) {
    return this.paymentService.create(feeId, amount, method);
  }
}
Enter fullscreen mode Exit fullscreen mode

This completes the implementation of detailed payment history and invoice generation within the financial management system. You can further enhance the system by adding features such as exporting invoices to PDF, sending invoice notifications via email, and more.

If you enjoy my content and would like to support my work, you can buy me a coffee. Your support is greatly appreciated!

Disclaimer: This content in generated by AI.

Top comments (0)