Files
puaros/packages/guardian/examples/good-architecture/domain/aggregates/Order.ts
imfozilbek 03705b5264 feat(guardian): add guardian package - code quality analyzer
Add @puaros/guardian package v0.1.0 - code quality guardian for vibe coders and enterprise teams.

Features:
- Hardcode detection (magic numbers, magic strings)
- Circular dependency detection
- Naming convention enforcement (Clean Architecture)
- Architecture violation detection
- CLI tool with comprehensive reporting
- 159 tests with 80%+ coverage
- Smart suggestions for fixes
- Built for AI-assisted development

Built with Clean Architecture and DDD principles.
Works with Claude, GPT, Copilot, Cursor, and any AI coding assistant.
2025-11-24 02:54:39 +05:00

264 lines
6.7 KiB
TypeScript

import { BaseEntity } from "../../../../src/domain/entities/BaseEntity"
import { OrderId } from "../value-objects/OrderId"
import { UserId } from "../value-objects/UserId"
import { OrderStatus } from "../value-objects/OrderStatus"
import { Money } from "../value-objects/Money"
import { OrderItem } from "../entities/OrderItem"
/**
* Order Aggregate Root
*
* DDD Patterns:
* - Aggregate Root: controls access to OrderItems
* - Consistency Boundary: all changes through Order
* - Rich Domain Model: contains business logic
*
* SOLID Principles:
* - SRP: manages order lifecycle
* - OCP: extensible through status transitions
* - ISP: focused interface for order operations
*
* Business Rules (Invariants):
* - Order must have at least one item
* - Cannot modify confirmed/paid/shipped orders
* - Status transitions must be valid
* - Total = sum of all items
* - Cannot cancel delivered orders
*
* Clean Code:
* - No magic numbers: MIN_ITEMS constant
* - Meaningful names: addItem, removeItem, confirm
* - Small methods: each does one thing
* - No hardcoded strings: OrderStatus enum
*/
export class Order extends BaseEntity {
private static readonly MIN_ITEMS = 1
private readonly _orderId: OrderId
private readonly _userId: UserId
private readonly _items: Map<string, OrderItem>
private _status: OrderStatus
private readonly _createdAt: Date
private _confirmedAt?: Date
private _deliveredAt?: Date
private constructor(
orderId: OrderId,
userId: UserId,
items: OrderItem[],
status: OrderStatus,
createdAt: Date,
confirmedAt?: Date,
deliveredAt?: Date,
) {
super(orderId.value)
this._orderId = orderId
this._userId = userId
this._items = new Map(items.map((item) => [item.id, item]))
this._status = status
this._createdAt = createdAt
this._confirmedAt = confirmedAt
this._deliveredAt = deliveredAt
this.validateInvariants()
}
/**
* Factory: Create new order
*/
public static create(userId: UserId): Order {
const orderId = OrderId.create()
const now = new Date()
return new Order(orderId, userId, [], OrderStatus.PENDING, now)
}
/**
* Factory: Reconstitute from persistence
*/
public static reconstitute(
orderId: OrderId,
userId: UserId,
items: OrderItem[],
status: OrderStatus,
createdAt: Date,
confirmedAt?: Date,
deliveredAt?: Date,
): Order {
return new Order(orderId, userId, items, status, createdAt, confirmedAt, deliveredAt)
}
/**
* Business Operation: Add item to order
*
* DDD: Only Aggregate Root can modify its entities
*/
public addItem(productId: string, productName: string, price: Money, quantity: number): void {
this.ensureCanModify()
const existingItem = Array.from(this._items.values()).find(
(item) => item.productId === productId,
)
if (existingItem) {
existingItem.updateQuantity(existingItem.quantity + quantity)
} else {
const newItem = OrderItem.create(productId, productName, price, quantity)
this._items.set(newItem.id, newItem)
}
this.touch()
}
/**
* Business Operation: Remove item from order
*/
public removeItem(itemId: string): void {
this.ensureCanModify()
if (!this._items.has(itemId)) {
throw new Error(`Item not found: ${itemId}`)
}
this._items.delete(itemId)
this.touch()
}
/**
* Business Operation: Update item quantity
*/
public updateItemQuantity(itemId: string, newQuantity: number): void {
this.ensureCanModify()
const item = this._items.get(itemId)
if (!item) {
throw new Error(`Item not found: ${itemId}`)
}
item.updateQuantity(newQuantity)
this.touch()
}
/**
* Business Operation: Confirm order
*/
public confirm(): void {
this.transitionTo(OrderStatus.CONFIRMED)
this._confirmedAt = new Date()
}
/**
* Business Operation: Mark as paid
*/
public markAsPaid(): void {
this.transitionTo(OrderStatus.PAID)
}
/**
* Business Operation: Ship order
*/
public ship(): void {
this.transitionTo(OrderStatus.SHIPPED)
}
/**
* Business Operation: Deliver order
*/
public deliver(): void {
this.transitionTo(OrderStatus.DELIVERED)
this._deliveredAt = new Date()
}
/**
* Business Operation: Cancel order
*/
public cancel(): void {
if (this._status.isDelivered()) {
throw new Error("Cannot cancel delivered order")
}
this.transitionTo(OrderStatus.CANCELLED)
}
/**
* Business Query: Calculate total
*/
public calculateTotal(): Money {
const items = Array.from(this._items.values())
if (items.length === 0) {
return Money.zero("USD")
}
return items.reduce((total, item) => total.add(item.calculateTotal()), Money.zero("USD"))
}
/**
* Business Query: Check if order can be modified
*/
public canModify(): boolean {
return this._status.isPending()
}
/**
* Getters
*/
public get orderId(): OrderId {
return this._orderId
}
public get userId(): UserId {
return this._userId
}
public get items(): readonly OrderItem[] {
return Array.from(this._items.values())
}
public get status(): OrderStatus {
return this._status
}
public get createdAt(): Date {
return this._createdAt
}
public get confirmedAt(): Date | undefined {
return this._confirmedAt
}
public get deliveredAt(): Date | undefined {
return this._deliveredAt
}
/**
* Private helpers
*/
private ensureCanModify(): void {
if (!this.canModify()) {
throw new Error(`Cannot modify order in ${this._status.value} status`)
}
}
private transitionTo(newStatus: OrderStatus): void {
if (!this._status.canTransitionTo(newStatus)) {
throw new Error(
`Invalid status transition: ${this._status.value} -> ${newStatus.value}`,
)
}
this._status = newStatus
this.touch()
}
/**
* Invariant validation
*/
private validateInvariants(): void {
if (!this._status.isPending() && this._items.size < Order.MIN_ITEMS) {
throw new Error(`Order must have at least ${Order.MIN_ITEMS} item(s)`)
}
}
}