Skip to content

Purchase Returns / Debit Notes (مردودات المشتريات)

Purchase returns (debit notes) allow returning goods to suppliers and reversing the associated accounting entries. Returns can be created against a posted purchase bill (bill-linked) or independently without a bill (standalone mode).

Entity Attributes

PurchaseReturn

FieldTypeRequiredDescription
idintegerautoPrimary key
company_idFKautoCompany
branch_idFKauto/conditionalInherited from bill; required if standalone
return_numberstringautoAuto-generated PDN-YYYY-NNNNN
datedateyesReturn date
supplier_idFKauto/conditionalInherited from bill; required if standalone
supplier_namestringautoCached supplier name (auto-resolved from BusinessPartner when standalone)
bill_idFKnoSource purchase bill (must be posted); null for standalone returns
cost_center_idFKnoOptional cost center
currency_codestringauto/conditionalInherited from bill; required if standalone (default: KWD)
exchange_ratedecimal(12,6)autoInherited from bill (1.0 for standalone)
reasontextnoReturn reason (EN)
reason_artextnoReturn reason (AR)
notestextnoNotes (EN)
notes_artextnoNotes (AR)
subtotaldecimal(15,3)autoSum of line_total
discount_amountdecimal(15,3)autoSum of item discounts
tax_amountdecimal(15,3)autoSum of item taxes
totaldecimal(15,3)autosubtotal + tax_amount
statusenumautoCurrent workflow status
journal_entry_idFKautoLinked JE (after posting)

PurchaseReturnItem

FieldTypeRequiredDescription
idintegerautoPrimary key
purchase_return_idFKautoParent return
bill_item_idFKconditionalSource bill item; null for standalone items
product_idFKauto/conditionalFrom bill item; required if standalone item
product_variant_idFKnoFrom bill item; optional for standalone
unit_idFKauto/conditionalFrom bill item; required if standalone item
warehouse_idFKnoReturn warehouse (defaults to bill item)
quantitydecimal(15,3)yesQuantity to return
unit_costdecimal(15,3)auto/conditionalFrom bill item; required if standalone item
discount_amountdecimal(15,3)auto/noProportional from bill item; optional for standalone
tax_rate_idFKauto/noFrom bill item; optional for standalone
tax_amountdecimal(15,3)autoCalculated from line_total
line_totaldecimal(15,3)auto(qty * unit_cost) - discount
total_costdecimal(15,3)autoqty * unit_cost

ER Diagram

Workflow / Status Diagram

API Endpoints

MethodEndpointPermissionDescription
GET/api/purchases/returnspurchases.returns.viewList returns
POST/api/purchases/returnspurchases.returns.createCreate return
GET/api/purchases/returns/{id}purchases.returns.viewShow return
PUT/api/purchases/returns/{id}purchases.returns.updateUpdate draft
DELETE/api/purchases/returns/{id}purchases.returns.deleteDelete draft
POST/api/purchases/returns/{id}/submit-approvalpurchases.returns.approveSubmit for approval
POST/api/purchases/returns/{id}/approvepurchases.returns.approveApprove return
POST/api/purchases/returns/{id}/rejectpurchases.returns.approveReject (back to draft)
POST/api/purchases/returns/{id}/postpurchases.returns.postPost return
POST/api/purchases/returns/{id}/cancelpurchases.returns.cancelCancel return

Query Parameters (Index)

ParameterTypeDescription
statusstringFilter by status: draft, pending_approval, approved, posted, cancelled
supplier_idintegerFilter by supplier
bill_idintegerFilter by source bill
branch_idintegerFilter by branch
date_fromdateStart date filter
date_todateEnd date filter
searchstringSearch return_number, reason, supplier_name
standaloneboolean1 = only standalone (no bill), 0 = only bill-linked

Create Request (Bill-Linked)

bash
curl -X POST /api/purchases/returns \
  -H "Authorization: Bearer {token}" \
  -H "Content-Type: application/json" \
  -d '{
    "bill_id": 1,
    "date": "2026-02-25",
    "reason": "Defective goods received",
    "items": [
      {
        "bill_item_id": 1,
        "quantity": 3,
        "warehouse_id": 1,
        "notes": "Damaged packaging"
      }
    ]
  }'
dart
final response = await dio.post(
  '/api/purchases/returns',
  data: {
    'bill_id': 1,
    'date': '2026-02-25',
    'reason': 'Defective goods received',
    'items': [
      {
        'bill_item_id': 1,
        'quantity': 3,
        'warehouse_id': 1,
        'notes': 'Damaged packaging',
      },
    ],
  },
);

Create Request (Standalone — No Bill)

bash
curl -X POST /api/purchases/returns \
  -H "Authorization: Bearer {token}" \
  -H "Content-Type: application/json" \
  -d '{
    "supplier_id": 5,
    "branch_id": 1,
    "currency_code": "KWD",
    "date": "2026-02-28",
    "reason": "Defective goods returned directly",
    "items": [
      {
        "product_id": 12,
        "unit_id": 1,
        "unit_cost": 25.500,
        "quantity": 2,
        "discount_amount": 0,
        "tax_rate": 5,
        "warehouse_id": 1,
        "notes": "Returned without original bill"
      }
    ]
  }'
dart
final response = await dio.post(
  '/api/purchases/returns',
  data: {
    'supplier_id': 5,
    'branch_id': 1,
    'currency_code': 'KWD',
    'date': '2026-02-28',
    'reason': 'Defective goods returned directly',
    'items': [
      {
        'product_id': 12,
        'unit_id': 1,
        'unit_cost': 25.500,
        'quantity': 2,
        'discount_amount': 0,
        'tax_rate': 5,
        'warehouse_id': 1,
        'notes': 'Returned without original bill',
      },
    ],
  },
);

Business Rules

Bill-Linked Mode (with bill_id)

  1. Source Bill: Returns can only be created against posted bills (status = posted)
  2. Returnable Quantity: Each bill item's return quantity is validated against original_qty - already_returned_qty (excluding cancelled returns)
  3. Pricing: Uses original bill item pricing (unit_cost, discount, tax rate) — prices are not manually set on returns
  4. Auto-Derived Fields: supplier_id, supplier_name, branch_id, currency_code, exchange_rate are inherited from the bill

Standalone Mode (without bill_id)

  1. Required Fields: supplier_id, branch_id, currency_code are required when no bill_id is provided
  2. Supplier Name: Auto-resolved from the BusinessPartner record
  3. Item Fields: Each item requires product_id, unit_id, unit_cost (instead of bill_item_id)
  4. Pricing: unit_cost, discount_amount, tax_rate are set directly per item
  5. Mixed Items: A single return can contain both bill-linked items (with bill_item_id) and standalone items (with product_id/unit_id/unit_cost)

Common Rules

  1. Posting creates reverse AP JE:
    • DR: Accounts Payable (reduces AP balance)
    • CR: Inventory Account (for tracked products)
    • CR: Expense Account (for services/non-inventory)
    • CR: Tax Receivable (reverses input tax)
    • DR: Discount Account (reverses purchase discount)
  2. Stock Decrease: On posting, tracked inventory products have stock decreased via StockService::decreaseStock() with movement_type=issue and reference_type=purchase_return
  3. Cancellation: Reverses the JE (via ReverseJournalEntry) and restores stock (via StockService::increaseStock()) if return was posted

Sequence

Returns use auto-generated numbers with prefix PDN (Purchase Debit Note):

  • Format: PDN-{YYYY}-{NNNNN} (e.g., PDN-2026-00001)
  • Reset frequency: Yearly

Moon ERP API Documentation