Skip to main content

Business Logic Template

Template untuk mendokumentasikan business logic, rules, validations, dan decision flows secara sistematis.

📋 Business Rules Documentation

Rule Template

## Rule: [Rule Name]

**ID**: BR-[MODULE]-[NUMBER]  
**Priority**: High | Medium | Low  
**Status**: Active | Draft | Deprecated

### Description
[Penjelasan lengkap rule bisnis]

### Conditions
- Kondisi 1: [Detail kondisi]
- Kondisi 2: [Detail kondisi]

### Actions
- Action 1: [Apa yang terjadi]
- Action 2: [Apa yang terjadi]

### Exceptions
- Exception 1: [Kondisi exception]
- Exception 2: [Kondisi exception]

### Examples
**Scenario 1**: [Deskripsi]
- Input: [Data input]
- Expected Output: [Hasil yang diharapkan]

**Scenario 2**: [Deskripsi]
- Input: [Data input]
- Expected Output: [Hasil yang diharapkan]

### Related Rules
- BR-[MODULE]-[NUMBER]: [Rule name]

### Implementation
- Service: [Service name]
- Method: [Method name]
- File: [File path]

🎯 Complete Example: Order Processing

Rule: Order Minimum Amount

ID: BR-ORDER-001
Priority: High
Status: Active

Description

Setiap order harus memenuhi minimum purchase amount untuk dapat diproses. Minimum amount berbeda berdasarkan user tier dan payment method.

Conditions

  • User tier: Regular, Silver, Gold, Platinum
  • Payment method: Credit Card, Bank Transfer, E-Wallet, COD
  • Order subtotal (sebelum discount dan shipping)

Business Logic

IF user.tier == "Regular" THEN
    IF payment_method == "COD" THEN
        min_amount = 100000
    ELSE
        min_amount = 50000
    END IF
ELSE IF user.tier == "Silver" THEN
    min_amount = 25000
ELSE IF user.tier IN ["Gold", "Platinum"] THEN
    min_amount = 0  // No minimum
END IF

IF order.subtotal < min_amount THEN
    RETURN ERROR "Minimum order amount not met"
END IF

Decision Table

User TierPayment MethodMinimum Amount
RegularCredit CardRp 50,000
RegularBank TransferRp 50,000
RegularE-WalletRp 50,000
RegularCODRp 100,000
SilverAnyRp 25,000
GoldAnyRp 0
PlatinumAnyRp 0

Examples

Scenario 1: Regular user with COD
Input:
  user.tier = "Regular"
  payment_method = "COD"
  order.subtotal = 75000

Expected Output:
  ERROR: "Minimum order amount for COD is Rp 100,000"
Scenario 2: Gold user
Input:
  user.tier = "Gold"
  payment_method = "Credit Card"
  order.subtotal = 10000

Expected Output:
  SUCCESS: Order can be processed

Implementation

// internal/order/service/order_service.go

func (s *orderService) ValidateMinimumAmount(
    ctx context.Context,
    user *model.User,
    order *model.Order,
) error {
    minAmount := s.getMinimumAmount(user.Tier, order.PaymentMethod)
    
    if order.Subtotal < minAmount {
        return errors.New(fmt.Sprintf(
            "Minimum order amount for %s is Rp %s",
            order.PaymentMethod,
            formatCurrency(minAmount),
        ))
    }
    
    return nil
}

func (s *orderService) getMinimumAmount(
    tier string,
    paymentMethod string,
) decimal.Decimal {
    switch tier {
    case "Regular":
        if paymentMethod == "COD" {
            return decimal.NewFromInt(100000)
        }
        return decimal.NewFromInt(50000)
    case "Silver":
        return decimal.NewFromInt(25000)
    case "Gold", "Platinum":
        return decimal.Zero
    default:
        return decimal.NewFromInt(50000)
    }
}

Rule: Stock Reservation

ID: BR-ORDER-002
Priority: High
Status: Active

Description

Saat order dibuat, stock product harus di-reserve untuk mencegah overselling. Stock reservation berlaku selama 30 menit atau sampai payment confirmed.

State Machine

Business Logic

FUNCTION CreateOrder(order):
    BEGIN TRANSACTION
    
    FOR EACH item IN order.items:
        product = GET Product WHERE id = item.product_id
        
        // Check available stock
        available_stock = product.stock - product.reserved_stock
        
        IF available_stock < item.quantity THEN
            ROLLBACK TRANSACTION
            RETURN ERROR "Insufficient stock for " + product.name
        END IF
        
        // Reserve stock
        product.reserved_stock += item.quantity
        UPDATE product
    END FOR
    
    // Create order with status "pending"
    order.status = "pending"
    order.reservation_expires_at = NOW() + 30 MINUTES
    INSERT order
    
    // Schedule auto-cancel job
    SCHEDULE_JOB(CancelExpiredOrder, order.id, 30 MINUTES)
    
    COMMIT TRANSACTION
    RETURN order
END FUNCTION

FUNCTION ConfirmPayment(order_id):
    BEGIN TRANSACTION
    
    order = GET Order WHERE id = order_id
    
    IF order.status != "pending" THEN
        ROLLBACK TRANSACTION
        RETURN ERROR "Order cannot be confirmed"
    END IF
    
    FOR EACH item IN order.items:
        product = GET Product WHERE id = item.product_id
        
        // Move from reserved to sold
        product.reserved_stock -= item.quantity
        product.stock -= item.quantity
        UPDATE product
    END FOR
    
    order.status = "paid"
    order.paid_at = NOW()
    UPDATE order
    
    COMMIT TRANSACTION
    RETURN order
END FUNCTION

FUNCTION CancelExpiredOrder(order_id):
    BEGIN TRANSACTION
    
    order = GET Order WHERE id = order_id
    
    IF order.status == "pending" AND NOW() > order.reservation_expires_at THEN
        // Release reserved stock
        FOR EACH item IN order.items:
            product = GET Product WHERE id = item.product_id
            product.reserved_stock -= item.quantity
            UPDATE product
        END FOR
        
        order.status = "cancelled"
        order.cancel_reason = "Payment timeout"
        order.cancelled_at = NOW()
        UPDATE order
    END IF
    
    COMMIT TRANSACTION
END FUNCTION

Flow Diagram


🔄 Validation Rules

Input Validation Template

## Validation: [Field Name]

**Field**: [field_name]  
**Type**: [data type]  
**Required**: Yes | No

### Rules
- Rule 1: [Validation rule]
- Rule 2: [Validation rule]

### Error Messages
- Error 1: [Error message]
- Error 2: [Error message]

### Examples
**Valid**:
- `value1`
- `value2`

**Invalid**:
- `value1` → Error: [message]
- `value2` → Error: [message]

Example: Email Validation

Field: email
Type: string
Required: Yes

Rules

  • Must be valid email format (RFC 5322)
  • Maximum length: 255 characters
  • Must be unique in database
  • Must not contain spaces
  • Domain must have valid MX record (optional)

Error Messages

  • INVALID_EMAIL_FORMAT: “Email format is invalid”
  • EMAIL_TOO_LONG: “Email must not exceed 255 characters”
  • EMAIL_ALREADY_EXISTS: “Email already registered”
  • EMAIL_CONTAINS_SPACES: “Email must not contain spaces”

Implementation

// internal/shared/validator/email_validator.go

func ValidateEmail(email string) error {
    // Check empty
    if strings.TrimSpace(email) == "" {
        return errors.New("email is required")
    }
    
    // Check length
    if len(email) > 255 {
        return errors.New("email must not exceed 255 characters")
    }
    
    // Check spaces
    if strings.Contains(email, " ") {
        return errors.New("email must not contain spaces")
    }
    
    // Check format (regex)
    emailRegex := regexp.MustCompile(`^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`)
    if !emailRegex.MatchString(email) {
        return errors.New("email format is invalid")
    }
    
    return nil
}

🎲 Decision Tables

Template

Condition 1Condition 2Condition 3ActionResult
Value AValue XValue 1Action 1Result 1
Value AValue YValue 2Action 2Result 2
Value BValue XValue 1Action 3Result 3

Example: Shipping Cost Calculation

Weight (kg)Distance (km)User TierShipping CostFree Shipping
0-10-10AnyRp 10,000No
0-111-50AnyRp 15,000No
0-151-100AnyRp 25,000No
0-1>100AnyRp 50,000No
1-50-10RegularRp 15,000No
1-50-10GoldRp 15,000Yes (if order > 100k)
1-511-50RegularRp 25,000No
1-511-50GoldRp 25,000Yes (if order > 100k)
>5AnyAnyCalculateContact CS

🧮 Calculation Rules

Template

## Calculation: [Calculation Name]

**Formula**:
result = formula

**Variables**:
- `var1`: [Description]
- `var2`: [Description]

**Constraints**:
- Constraint 1
- Constraint 2

**Examples**:
Input: var1 = X, var2 = Y Output: result = Z

Example: Order Total Calculation

Formula:
total = subtotal - discount + tax + shipping_cost

Where:
  subtotal = SUM(item.quantity * item.unit_price)
  discount = coupon_discount + member_discount
  tax = subtotal * tax_rate
  shipping_cost = calculated_shipping_cost
Variables:
  • subtotal: Total harga items sebelum discount
  • discount: Total discount dari coupon dan membership
  • tax: Pajak (PPN 11%)
  • shipping_cost: Biaya pengiriman
Constraints:
  • subtotal >= 0
  • discount <= subtotal
  • tax_rate = 0.11 (11% PPN)
  • total >= 0
Examples:
Example 1: Regular order
  Items:
    - Product A: 2 x Rp 50,000 = Rp 100,000
    - Product B: 1 x Rp 75,000 = Rp 75,000
  
  subtotal = Rp 175,000
  discount = Rp 0 (no coupon)
  tax = Rp 175,000 * 0.11 = Rp 19,250
  shipping_cost = Rp 15,000
  
  total = Rp 175,000 - Rp 0 + Rp 19,250 + Rp 15,000
  total = Rp 209,250
Example 2: With coupon
  Items:
    - Product A: 3 x Rp 100,000 = Rp 300,000
  
  subtotal = Rp 300,000
  coupon = 10% discount = Rp 30,000
  member_discount = Rp 0
  discount = Rp 30,000
  tax = (Rp 300,000 - Rp 30,000) * 0.11 = Rp 29,700
  shipping_cost = Rp 0 (free shipping)
  
  total = Rp 300,000 - Rp 30,000 + Rp 29,700 + Rp 0
  total = Rp 299,700
Implementation:
// internal/order/service/order_calculator.go

type OrderCalculator struct {
    taxRate decimal.Decimal
}

func NewOrderCalculator() *OrderCalculator {
    return &OrderCalculator{
        taxRate: decimal.NewFromFloat(0.11), // 11% PPN
    }
}

func (c *OrderCalculator) CalculateTotal(order *model.Order) error {
    // Calculate subtotal
    subtotal := decimal.Zero
    for _, item := range order.Items {
        itemTotal := item.UnitPrice.Mul(decimal.NewFromInt(int64(item.Quantity)))
        subtotal = subtotal.Add(itemTotal)
    }
    order.Subtotal = subtotal
    
    // Calculate discount
    discount := c.calculateDiscount(order)
    order.DiscountAmount = discount
    
    // Calculate tax (after discount)
    taxableAmount := subtotal.Sub(discount)
    tax := taxableAmount.Mul(c.taxRate)
    order.TaxAmount = tax
    
    // Get shipping cost
    shippingCost := c.calculateShipping(order)
    order.ShippingCost = shippingCost
    
    // Calculate total
    total := subtotal.Sub(discount).Add(tax).Add(shippingCost)
    order.TotalAmount = total
    
    return nil
}

func (c *OrderCalculator) calculateDiscount(order *model.Order) decimal.Decimal {
    discount := decimal.Zero
    
    // Apply coupon discount
    if order.Coupon != nil {
        if order.Coupon.Type == "percentage" {
            couponDiscount := order.Subtotal.Mul(order.Coupon.Value).Div(decimal.NewFromInt(100))
            if order.Coupon.MaxDiscount.GreaterThan(decimal.Zero) {
                couponDiscount = decimal.Min(couponDiscount, order.Coupon.MaxDiscount)
            }
            discount = discount.Add(couponDiscount)
        } else if order.Coupon.Type == "fixed" {
            discount = discount.Add(order.Coupon.Value)
        }
    }
    
    // Apply member discount
    memberDiscount := c.calculateMemberDiscount(order)
    discount = discount.Add(memberDiscount)
    
    // Discount cannot exceed subtotal
    if discount.GreaterThan(order.Subtotal) {
        discount = order.Subtotal
    }
    
    return discount
}


Template Usage: Copy template yang sesuai dan isi dengan business logic spesifik untuk fitur Anda. Pastikan semua rules, validations, dan calculations terdokumentasi dengan jelas dan disertai contoh.
Pro Tip: Dokumentasikan business logic sebelum implementasi untuk memastikan semua stakeholder aligned dengan requirements!