All files / src/barang barang.service.ts

93.33% Statements 56/60
76.92% Branches 10/13
86.66% Functions 13/15
92.59% Lines 50/54

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 1962x         2x 2x 2x       2x 2x     2x     22x       2x     2x 1x 1x               4x 4x     4x 1x             4x 1x           4x       4x       11x 11x 7x       4x 2x   2x 1x 1x       2x 1x 1x                 5x 4x 2x 2x 2x       1x                                         1x                           1x                     1x     1x 1x                         1x                                                   1x 1x 1x 121x 1x 1x 1x        
import {
  Injectable,
  NotFoundException,
  BadRequestException,
} from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Barang } from '../entities/barang.entity';
import { Repository, FindOptionsWhere, ILike, LessThanOrEqual } from 'typeorm';
import { CreateBarangDto } from './dto/create-barang.dto';
import { UpdateBarangDto } from './dto/update-barang.dto';
import { AddStokDto } from './dto/add-stok.dto';
import * as PdfPrinter from 'pdfmake';
import * as path from 'path';
 
@Injectable()
export class BarangService {
  constructor(
    @InjectRepository(Barang)
    private barangRepo: Repository<Barang>,
  ) {}
 
  async create(dto: CreateBarangDto): Promise<Barang> {
    const exist = await this.barangRepo.findOne({
      where: { kode_barang: dto.kode_barang },
    });
    if (exist) throw new BadRequestException('Kode barang sudah terdaftar');
    const barang = this.barangRepo.create({ ...dto, status_aktif: true });
    return this.barangRepo.save(barang);
  }
 
  async findAll(query?: {
    q?: string;
    status_aktif?: boolean;
    stok_kritis?: boolean;
  }): Promise<Barang[]> {
    const repo = this.barangRepo;
    const qb = repo.createQueryBuilder('barang');
 
    // Pencarian nama/kode
    if (query?.q) {
      qb.andWhere(
        '(barang.nama_barang ILIKE :q OR barang.kode_barang ILIKE :q)',
        { q: `%${query.q}%` },
      );
    }
 
    // Filter status aktif
    if (typeof query?.status_aktif === 'boolean') {
      qb.andWhere('barang.status_aktif = :status', {
        status: query.status_aktif,
      });
    }
 
    // Filter stok kritis
    Iif (query?.stok_kritis) {
      qb.andWhere('barang.stok <= barang.ambang_batas_kritis');
    }
 
    return qb.getMany();
  }
 
  async findOne(id: number): Promise<Barang> {
    const barang = await this.barangRepo.findOne({ where: { id } });
    if (!barang) throw new NotFoundException('Barang tidak ditemukan');
    return barang;
  }
 
  async update(id: number, dto: UpdateBarangDto): Promise<Barang> {
    if (dto.stok !== undefined && dto.stok < 0) {
      throw new BadRequestException('Stok tidak boleh negatif');
    }
    const barang = await this.findOne(id);
    Object.assign(barang, dto);
    return this.barangRepo.save(barang);
  }
 
  async softDelete(id: number): Promise<Barang> {
    const barang = await this.findOne(id);
    barang.status_aktif = false;
    return this.barangRepo.save(barang);
  }
 
  async remove(id: number): Promise<void> {
    const barang = await this.findOne(id);
    await this.barangRepo.remove(barang);
  }
 
  async addStok(id: number, dto: AddStokDto): Promise<Barang> {
    const barang = await this.findOne(id);
    if (!barang.status_aktif)
      throw new BadRequestException('Barang tidak aktif');
    barang.stok = (barang.stok ?? 0) + dto.jumlah;
    return this.barangRepo.save(barang);
  }
 
  async getStokKritis(): Promise<Barang[]> {
    return this.barangRepo
      .createQueryBuilder('barang')
      .where('barang.stok <= barang.ambang_batas_kritis')
      .andWhere('barang.status_aktif = :aktif', { aktif: true })
      .getMany();
  }
 
  async getBarangKritis() {
    return this.barangRepo
      .createQueryBuilder('barang')
      .where('barang.stok <= barang.ambang_batas_kritis')
      .andWhere('barang.status_aktif = :aktif', { aktif: true })
      .orderBy('barang.stok', 'ASC')
      .getMany();
  }
 
  async generateLaporanPenggunaanPDF(
    start: string,
    end: string,
  ): Promise<Buffer> {
    // Query data penggunaan barang dari tabel detail_permintaan + permintaan
    const penggunaan = await this.barangRepo.query(
      `
      SELECT b.nama_barang, b.satuan, SUM(d.jumlah_disetujui) as total_digunakan
      FROM detail_permintaan d
      JOIN barang b ON d.id_barang = b.id
      JOIN permintaan p ON d.id_permintaan = p.id
      WHERE p.status IN ('Disetujui', 'Disetujui Sebagian')
        AND p.tanggal_permintaan BETWEEN $1 AND $2
      GROUP BY b.nama_barang, b.satuan
      ORDER BY b.nama_barang
    `,
      [start, end],
    );
 
    const fonts = {
      Roboto: {
        normal: path.join(__dirname, '../assets/fonts/Roboto-Regular.ttf'),
        bold: path.join(__dirname, '../assets/fonts/Roboto-Bold.ttf'),
        italics: path.join(__dirname, '../assets/fonts/Roboto-Italic.ttf'),
        bolditalics: path.join(
          __dirname,
          '../assets/fonts/Roboto-BoldItalic.ttf',
        ),
      },
    };
    const printer = new PdfPrinter(fonts);
 
    const bodyRows =
      penggunaan.length > 0
        ? penggunaan.map((row) => [
            row.nama_barang,
            row.total_digunakan,
            row.satuan,
          ])
        : [
            [
              { text: 'Tidak ada data', colSpan: 3, alignment: 'center' },
              {},
              {},
            ],
          ];
 
    const docDefinition = {
      content: [
        { text: 'Laporan Penggunaan Barang', style: 'header' },
        { text: `Periode: ${start} s/d ${end}`, margin: [0, 10, 0, 10] },
        {
          table: {
            headerRows: 1,
            widths: ['*', 60, 60],
            body: [
              [
                { text: 'Nama Barang', style: 'tableHeader' },
                { text: 'Total Digunakan', style: 'tableHeader' },
                { text: 'Satuan', style: 'tableHeader' },
              ],
              ...bodyRows,
            ],
          },
          layout: 'lightHorizontalLines',
        },
      ],
      styles: {
        header: { fontSize: 16, bold: true, alignment: 'center' },
        tableHeader: { bold: true, fillColor: '#eeeeee' },
      },
    };
 
    const pdfDoc = printer.createPdfKitDocument(docDefinition);
    const chunks: Buffer[] = [];
    return new Promise<Buffer>((resolve, reject) => {
      pdfDoc.on('data', (chunk) => chunks.push(chunk));
      pdfDoc.on('end', () => resolve(Buffer.concat(chunks)));
      pdfDoc.on('error', reject);
      pdfDoc.end();
    });
  }
}