|
1 | | -import { Injectable, NotFoundException } from '@nestjs/common'; |
| 1 | +import { |
| 2 | + Injectable, |
| 3 | + NotFoundException, |
| 4 | + BadRequestException, |
| 5 | +} from '@nestjs/common'; |
2 | 6 | import { InjectRepository } from '@nestjs/typeorm'; |
3 | 7 | import { Repository } from 'typeorm'; |
4 | | -import { PurchaseOrder, POStatus } from './entities/purchase-order.entity'; |
| 8 | +import { |
| 9 | + PurchaseOrder, |
| 10 | + PurchaseOrderLineItem, |
| 11 | + POStatus, |
| 12 | +} from './entities/purchase-order.entity'; |
5 | 13 |
|
6 | 14 | @Injectable() |
7 | 15 | export class PurchaseOrdersService { |
8 | 16 | constructor( |
9 | 17 | @InjectRepository(PurchaseOrder) |
10 | 18 | private readonly poRepo: Repository<PurchaseOrder>, |
| 19 | + @InjectRepository(PurchaseOrderLineItem) |
| 20 | + private readonly lineItemRepo: Repository<PurchaseOrderLineItem>, |
11 | 21 | ) {} |
12 | 22 |
|
13 | 23 | async findAll() { |
14 | | - return this.poRepo.find(); |
| 24 | + return this.poRepo.find({ relations: ['lineItems'] }); |
15 | 25 | } |
16 | 26 |
|
17 | 27 | async findById(id: string) { |
18 | | - const po = await this.poRepo.findOne({ where: { id } }); |
| 28 | + const po = await this.poRepo.findOne({ |
| 29 | + where: { id }, |
| 30 | + relations: ['lineItems'], |
| 31 | + }); |
19 | 32 | if (!po) throw new NotFoundException(`PO ${id} not found`); |
20 | 33 | return po; |
21 | 34 | } |
22 | 35 |
|
23 | | - async create(dto: Partial<PurchaseOrder>) { |
| 36 | + async create(dto: Partial<PurchaseOrder> & { lineItems?: Partial<PurchaseOrderLineItem>[] }) { |
24 | 37 | const count = await this.poRepo.count(); |
25 | 38 | const poNumber = dto.poNumber || `PO-${String(count + 1).padStart(5, '0')}`; |
26 | | - const po = this.poRepo.create({ ...dto, poNumber }); |
| 39 | + const { lineItems, ...poData } = dto; |
| 40 | + const po = this.poRepo.create({ ...poData, poNumber, status: POStatus.DRAFT }); |
| 41 | + const saved = await this.poRepo.save(po); |
| 42 | + |
| 43 | + if (lineItems && lineItems.length > 0) { |
| 44 | + const items = lineItems.map((item) => |
| 45 | + this.lineItemRepo.create({ ...item, purchaseOrderId: saved.id }), |
| 46 | + ); |
| 47 | + await this.lineItemRepo.save(items); |
| 48 | + saved.totalAmount = items.reduce( |
| 49 | + (sum, item) => sum + item.quantity * item.unitCost, |
| 50 | + 0, |
| 51 | + ); |
| 52 | + await this.poRepo.save(saved); |
| 53 | + } |
| 54 | + |
| 55 | + return this.findById(saved.id); |
| 56 | + } |
| 57 | + |
| 58 | + async update( |
| 59 | + id: string, |
| 60 | + dto: Partial<PurchaseOrder> & { lineItems?: Partial<PurchaseOrderLineItem>[] }, |
| 61 | + ) { |
| 62 | + const po = await this.findById(id); |
| 63 | + if (po.status !== POStatus.DRAFT) { |
| 64 | + throw new BadRequestException('Only draft purchase orders can be edited'); |
| 65 | + } |
| 66 | + |
| 67 | + const { lineItems, ...poData } = dto; |
| 68 | + Object.assign(po, poData); |
| 69 | + |
| 70 | + if (lineItems) { |
| 71 | + await this.lineItemRepo.delete({ purchaseOrderId: id }); |
| 72 | + const items = lineItems.map((item) => |
| 73 | + this.lineItemRepo.create({ ...item, purchaseOrderId: id }), |
| 74 | + ); |
| 75 | + await this.lineItemRepo.save(items); |
| 76 | + po.totalAmount = items.reduce( |
| 77 | + (sum, item) => sum + item.quantity * item.unitCost, |
| 78 | + 0, |
| 79 | + ); |
| 80 | + } |
| 81 | + |
| 82 | + await this.poRepo.save(po); |
| 83 | + return this.findById(id); |
| 84 | + } |
| 85 | + |
| 86 | + async submit(id: string) { |
| 87 | + const po = await this.findById(id); |
| 88 | + if (po.status !== POStatus.DRAFT) { |
| 89 | + throw new BadRequestException('Only draft purchase orders can be submitted'); |
| 90 | + } |
| 91 | + po.status = POStatus.SUBMITTED; |
| 92 | + return this.poRepo.save(po); |
| 93 | + } |
| 94 | + |
| 95 | + async approve(id: string, approverUserId: string) { |
| 96 | + const po = await this.findById(id); |
| 97 | + if (po.status !== POStatus.SUBMITTED) { |
| 98 | + throw new BadRequestException( |
| 99 | + 'Only submitted purchase orders can be approved', |
| 100 | + ); |
| 101 | + } |
| 102 | + po.status = POStatus.APPROVED; |
| 103 | + po.createdByUserId = approverUserId; |
27 | 104 | return this.poRepo.save(po); |
28 | 105 | } |
29 | 106 |
|
30 | 107 | async receive(id: string) { |
31 | 108 | const po = await this.findById(id); |
| 109 | + if (po.status !== POStatus.APPROVED) { |
| 110 | + throw new BadRequestException( |
| 111 | + 'Only approved purchase orders can be received', |
| 112 | + ); |
| 113 | + } |
32 | 114 | po.status = POStatus.RECEIVED; |
33 | 115 | return this.poRepo.save(po); |
34 | 116 | } |
| 117 | + |
| 118 | + async cancel(id: string) { |
| 119 | + const po = await this.findById(id); |
| 120 | + if ( |
| 121 | + po.status !== POStatus.DRAFT && |
| 122 | + po.status !== POStatus.SUBMITTED |
| 123 | + ) { |
| 124 | + throw new BadRequestException( |
| 125 | + 'Only draft or submitted purchase orders can be cancelled', |
| 126 | + ); |
| 127 | + } |
| 128 | + po.status = POStatus.CANCELLED; |
| 129 | + return this.poRepo.save(po); |
| 130 | + } |
35 | 131 | } |
0 commit comments