# Jawid Tasal ERP Upgrade Guide
## System 2 Refactor and Implementation Blueprint

**Prepared for:** Afghan Cosmos  
**Target Client:**  Rahhed LTD .  
**Starting Codebase:** Second / Advanced Inventory System  
**Backend:** Django 5.0.7 + Django REST Framework 3.15.2  
**Frontend:** React 18.3.1 + Material UI + Redux Toolkit + TanStack React Query  
**Recommended Production Database:** PostgreSQL  
**Excluded Scope:** Corporate Website

---

# 1. Executive Direction

Use the second inventory system as the base.

Reuse the mature parts:
- Shop/branch scoping
- Multi-item purchases and sales
- Returns
- Partial payments
- Stock transfers
- Dynamic categories and units
- Exchange rates
- Generic financial accounts
- Reports and export UI

Refactor or replace the weak core:
- Inventory truth
- Batch/expiry/FEFO logic
- Stock movement history
- Payment allocations
- Posted transaction lifecycle
- Sarafi workflow
- Double-entry accounting
- General Ledger / Trial Balance / P&L
- Audit and action-level permissions

The system should evolve from a shop-management app into a controlled ERP transaction engine.

---

# 2. Final Confirmed Business Rules

## Organization

```text
 Rahhed LTD .
    -> Branch (existing Shop concept)
        -> Warehouse
```

One legal company only.

## Inventory

- Multiple branches
- Multiple warehouses
- Warehouse transfers
- No transfer approval
- Transfer statuses required
- Dynamic categories
- Dynamic UOM
- No unit conversions in V1
- Batch tracking
- Expiry tracking
- FEFO by default
- Manual batch override for authorized users
- Expired stock visible but never sellable
- Configurable expiry warning days
- Weighted Average Cost
- Negative inventory never allowed
- Controlled stock adjustments
- No IMEI/serial tracking

## Purchases

- Multi-item invoice
- Supplier
- Warehouse
- Batch / expiry
- Partial payments
- Supplier payable
- Purchase return
- Due dates
- Final landed cost entered directly by user
- No freight/customs allocation engine in V1

## Sales

- Multi-item invoice
- One currency per invoice
- Cash / credit / partial payment
- Customer receivable
- Invoice-level fixed or percentage discount
- No line-item discount
- Partial returns
- Returns must affect stock, receivable/refund, revenue and COGS

## Currency

- Fully dynamic currencies
- No hard-coded AFN/USD/PKR architecture
- Dynamic exchange rates
- Base/reporting currency selected at company level
- Exact historical rate snapshot per transaction
- Cross-currency operations supported

## Finance

Financial accounts include:

```text
CASH
BANK
SARAFI
OTHER
```

Each financial account has one currency.

Example:

```text
Kabul Cash - AFN
Kabul Cash - USD
Azizi Bank - USD
Ahmad Sarafi - USD
Ahmad Sarafi - AFN
```

## Sarafi

Support:
- Deposit
- Withdrawal
- Cash -> Sarafi
- Sarafi -> Cash
- Sarafi -> Sarafi
- Supplier payment
- Currency exchange
- Transfer/service charges

Example:

```text
Supplier receives USD 20,000
Sarafi fee       USD    200
Sarafi deduction USD 20,200
```

Supplier payable decreases only by 20,000.  
200 is an expense.

## Accounting

Formal double-entry accounting required:
- Chart of Accounts
- Journal Entry
- Journal Entry Lines
- General Ledger
- Trial Balance
- Profit & Loss
- Manual journal for authorized accountants
- Automatic journals from operational transactions

## Migration

Opening balances only. No full historical migration.

---

# 3. KEEP / REUSE

Keep and improve these existing concepts:

```text
Shop                 -> Branch concept
Category
Unit
Customer
Supplier

Purchase
PurchaseItem
PurchasePayment
PurchaseReturn
PurchaseReturnItem

Sale
SaleItem
SalePayment
SaleReturn
SaleReturnItem

Currency
ExchangeRate

Account
AccountTransaction
AccountTransfer

ExpenseCategory
Expense

DailyDiaryLog
Reports / printing / XLSX/PDF utilities
```

Do not rebuild these from zero unless implementation inspection proves a specific model unusable.

---

# 4. REPLACE AS SOURCE OF TRUTH

The following current architecture should not be the final source of truth:

```text
Legacy Stock
Direct stock quantity CRUD
PurchaseItem-based stock identity
Ad-hoc customer balance calculations
Ad-hoc supplier balance calculations
Operational profit calculation
Client-provided SaleItem buy_price
Hard-delete transaction behavior
Hard-coded AFG/USD paths
```

---

# 5. NEW CORE MODELS

Target additions:

```text
CompanySetting
Warehouse
UserWarehouseAccess

ProductPrice

InventoryBatch
StockBalance
StockMovement
StockAdjustment
StockAdjustmentItem
StockTransferItem

SaleItemBatchAllocation

CustomerPayment
CustomerPaymentAllocation
SupplierPayment
SupplierPaymentAllocation

LedgerAccount
JournalEntry
JournalEntryLine

AuditLog
Notification
```

---

# 6. Immediate Technical Work

Before feature development:

1. Create `feature/jawid-tasal-refactor`.
2. Backup database and media.
3. Rotate exposed secrets.
4. Move secrets to environment variables.
5. Set `DEBUG=False` in production.
6. Configure PostgreSQL for development/staging.
7. Run current tests against PostgreSQL.
8. Create a baseline Git tag.

Do not implement the new stock engine on SQLite.

---

# 7. Company and Warehouse

Keep `Shop` as the branch concept.

Create:

```python
class CompanySetting(models.Model):
    name = models.CharField(max_length=255)
    legal_name = models.CharField(max_length=255, blank=True)
    base_currency = models.ForeignKey("Currency", on_delete=models.PROTECT)
    expiry_warning_days = models.PositiveIntegerField(default=60)
    phone = models.CharField(max_length=100, blank=True)
    email = models.EmailField(blank=True)
    address = models.TextField(blank=True)
```

Create:

```python
class Warehouse(models.Model):
    shop = models.ForeignKey("Shop", on_delete=models.PROTECT, related_name="warehouses")
    code = models.CharField(max_length=50)
    name = models.CharField(max_length=255)
    address = models.TextField(blank=True)
    is_active = models.BooleanField(default=True)

    class Meta:
        constraints = [
            models.UniqueConstraint(
                fields=["shop", "code"],
                name="uniq_warehouse_code_per_shop",
            )
        ]
```

Migration:
- Create one default warehouse for each existing Shop.

---

# 8. Currency Refactor

Target:

```python
class Currency(models.Model):
    code = models.CharField(max_length=10, unique=True)
    name = models.CharField(max_length=100)
    symbol = models.CharField(max_length=20, blank=True)
    decimal_places = models.PositiveSmallIntegerField(default=2)
    is_active = models.BooleanField(default=True)
```

Remove Shop-specific duplicated currency masters over time.

Examples:

```text
AFN
USD
PKR
CNY
```

Never create new schema fields for new currencies.

---

# 9. Exchange Rates

Target:

```python
class ExchangeRate(models.Model):
    from_currency = models.ForeignKey(Currency, related_name="rates_from", on_delete=models.PROTECT)
    to_currency = models.ForeignKey(Currency, related_name="rates_to", on_delete=models.PROTECT)
    rate = models.DecimalField(max_digits=24, decimal_places=10)
    effective_date = models.DateField()
```

Constraint:

```text
unique(from_currency, to_currency, effective_date)
```

Lookup rule:

```text
latest effective_date <= transaction_date
```

Every posted cross-currency transaction stores its own `exchange_rate_used`.

---

# 10. Product Refactor

Extend existing Product:

```text
code / SKU
barcode
default_unit
minimum_stock_level
requires_batch
requires_expiry
is_active
```

Keep Category one-level.

Keep Unit dynamic.

No UOM conversion in V1.

---

# 11. ProductPrice

Add:

```python
class ProductPrice(models.Model):
    product = models.ForeignKey(Product, related_name="prices", on_delete=models.CASCADE)
    currency = models.ForeignKey(Currency, on_delete=models.PROTECT)
    price_type = models.CharField(
        max_length=20,
        choices=[("RETAIL", "Retail"), ("WHOLESALE", "Wholesale")],
    )
    amount = models.DecimalField(max_digits=18, decimal_places=4)
```

Constraint:

```text
unique(product, currency, price_type)
```

New sale pricing should use this, not purchase-row price columns.

---

# 12. Inventory Architecture

The new inventory truth:

```text
InventoryBatch
StockBalance
StockMovement
```

Legacy `Stock` remains temporarily for migration/read-only use.

---

# 13. InventoryBatch

```python
class InventoryBatch(models.Model):
    product = models.ForeignKey(Product, related_name="batches", on_delete=models.PROTECT)
    batch_number = models.CharField(max_length=100)
    manufacture_date = models.DateField(null=True, blank=True)
    expiry_date = models.DateField(null=True, blank=True)
    is_blocked = models.BooleanField(default=False)
    notes = models.TextField(blank=True)

    class Meta:
        constraints = [
            models.UniqueConstraint(
                fields=["product", "batch_number"],
                name="uniq_batch_per_product",
            )
        ]
```

A batch identity is product-specific, not warehouse-specific.

The same batch may move between warehouses.

---

# 14. StockBalance

```python
class StockBalance(models.Model):
    warehouse = models.ForeignKey(Warehouse, on_delete=models.PROTECT)
    product = models.ForeignKey(Product, on_delete=models.PROTECT)
    batch = models.ForeignKey(InventoryBatch, on_delete=models.PROTECT)

    quantity = models.DecimalField(max_digits=18, decimal_places=4, default=0)
    average_cost = models.DecimalField(max_digits=18, decimal_places=6, default=0)
    cost_currency = models.ForeignKey(Currency, on_delete=models.PROTECT)

    class Meta:
        constraints = [
            models.UniqueConstraint(
                fields=["warehouse", "product", "batch"],
                name="uniq_stock_balance",
            ),
            models.CheckConstraint(
                condition=models.Q(quantity__gte=0),
                name="stock_quantity_nonnegative",
            ),
        ]
```

Never expose generic quantity editing.

---

# 15. StockMovement

Every stock mutation creates one or more movements.

Types:

```text
OPENING
PURCHASE
PURCHASE_RETURN
SALE
SALE_RETURN
TRANSFER_OUT
TRANSFER_IN
ADJUSTMENT_IN
ADJUSTMENT_OUT
EXPIRY_WRITE_OFF
VOID_REVERSAL
```

Important fields:

```text
warehouse
product
batch
movement_type
quantity_in
quantity_out
unit_cost
currency
movement_date
reference_type
reference_id
remarks
created_by
```

StockMovement is immutable history.

---

# 16. Weighted Average Cost

Use Decimal only.

Formula:

```text
New Average =
(Current Qty * Current Average + Incoming Qty * Incoming Cost)
/
(Current Qty + Incoming Qty)
```

Recommended scope:

```text
Product + Warehouse + valuation currency context
```

FEFO decides physical batch issue.  
Weighted Average decides valuation/COGS.

---

# 17. FEFO

Create a backend service.

Rules:

1. Select warehouse.
2. Find positive stock balances for product.
3. Exclude blocked batches.
4. Exclude expired batches.
5. Sort earliest expiry first.
6. Allocate across multiple batches if needed.
7. Reject if available valid quantity is insufficient.

Pseudo:

```python
@transaction.atomic
def allocate_fefo(warehouse, product, quantity):
    balances = (
        StockBalance.objects
        .select_for_update()
        .filter(
            warehouse=warehouse,
            product=product,
            quantity__gt=0,
            batch__is_blocked=False,
            batch__expiry_date__gte=timezone.localdate(),
        )
        .select_related("batch")
        .order_by("batch__expiry_date", "id")
    )
```

---

# 18. Manual Batch Override

Permission:

```text
inventory.manual_batch_override
```

Validate:

```text
correct product
correct warehouse
not expired
not blocked
enough quantity
```

Store:

```text
allocation_method = FEFO / MANUAL
override_reason
```

Audit manual overrides.

---

# 19. SaleItemBatchAllocation

Add:

```python
class SaleItemBatchAllocation(models.Model):
    sale_item = models.ForeignKey(SaleItem, related_name="batch_allocations", on_delete=models.PROTECT)
    warehouse = models.ForeignKey(Warehouse, on_delete=models.PROTECT)
    batch = models.ForeignKey(InventoryBatch, on_delete=models.PROTECT)
    quantity = models.DecimalField(max_digits=18, decimal_places=4)
    unit_cost_snapshot = models.DecimalField(max_digits=18, decimal_places=6)
    total_cost = models.DecimalField(max_digits=18, decimal_places=6)
    allocation_method = models.CharField(max_length=20)
    override_reason = models.TextField(blank=True)
```

One SaleItem may consume several batches.

---

# 20. Expiry

Expired batches:
- visible in inventory
- visible in reports
- never selectable for sale
- can be adjusted/write-off
- can potentially be returned to supplier

Near-expiry:

```text
today <= expiry_date <= today + CompanySetting.expiry_warning_days
```

---

# 21. Stock Adjustment

Add controlled adjustment documents.

```text
StockAdjustment
StockAdjustmentItem
```

Statuses:

```text
DRAFT
POSTED
VOID
```

Examples:

```text
+5 physical count correction
-3 damaged
-20 expired
```

Posting creates StockMovement.

Remove ordinary direct stock edit from UI/API.

---

# 22. Stock Transfer

Reuse current transfer concept, but refactor to:

```text
DRAFT
DISPATCHED
RECEIVED
VOID
```

No approval required.

Transfer item fields:

```text
product
batch
quantity
unit_cost_snapshot
```

At dispatch:

```text
source stock decreases
TRANSFER_OUT movement
```

At receive:

```text
destination stock increases
TRANSFER_IN movement
```

Batch number, expiry, and cost remain intact.

---

# 23. Purchase Refactor

Keep Purchase + PurchaseItem.

Add Purchase fields:

```text
purchase_number
supplier_invoice_number
warehouse
invoice_date
due_date
currency
exchange_rate_used
subtotal
discount_type
discount_value
discount_amount
total_amount
payment_status
document_status
notes
```

Statuses:

```text
DRAFT
POSTED
VOID
```

Draft purchases do not affect stock.

---

# 24. PurchaseItem

Target:

```text
purchase
product
unit
quantity
unit_price
line_total
batch_number
manufacture_date
expiry_date
```

For Jawid Tasal:

```text
unit_price = final landed unit cost
```

No import-cost allocation engine required.

---

# 25. Purchase Posting Service

Create:

```text
services/purchases/post_purchase.py
```

Inside `transaction.atomic()`:

1. Lock purchase.
2. Require DRAFT.
3. Validate supplier.
4. Validate Shop/branch.
5. Validate warehouse belongs to Shop.
6. Validate currency/rate.
7. Validate items.
8. Validate batch/expiry where required.
9. Create/find InventoryBatch.
10. Lock StockBalance.
11. Update weighted-average cost.
12. Increase stock.
13. Create PURCHASE StockMovement.
14. Post supplier payable journal.
15. Record immediate payment if provided.
16. Mark POSTED.
17. Audit.

Do not use Purchase creation itself as posting.

---

# 26. Supplier Payment

Refactor current purchase/general balance payment model into:

```text
SupplierPayment
SupplierPaymentAllocation
```

SupplierPayment fields:

```text
supplier
shop
financial_account
payment_date
currency
amount
exchange_rate_used
base_amount
reference
notes
status
```

Allocation:

```text
payment
purchase
allocated_amount
```

One payment may settle several invoices.

Unallocated/on-account payment is allowed.

---

# 27. Purchase Return

Reuse PurchaseReturn/PurchaseReturnItem.

Posting must:

```text
validate remaining returnable quantity
reduce correct batch stock
create PURCHASE_RETURN movement
reduce supplier payable
create supplier credit/refund if needed
post accounting reversal
preserve original purchase
```

Never mutate original PurchaseItem quantity.

---

# 28. Sale Refactor

Keep Sale + SaleItem.

Add:

```text
sale_number
warehouse
invoice_date
due_date
sales_type
currency
exchange_rate_used
subtotal
discount_type
discount_value
discount_amount
total_amount
payment_status
document_status
```

Generate invoice number server-side.

Example:

```text
KBL-SAL-2026-000001
```

---

# 29. Sale Discounts

Invoice-level only.

Types:

```text
FIXED
PERCENTAGE
```

Backend calculates:

```text
subtotal
discount_amount
total_amount
```

Frontend totals are advisory only.

---

# 30. Sale Posting Service

Create:

```text
services/sales/post_sale.py
```

Atomic flow:

1. Require DRAFT.
2. Validate Shop.
3. Validate Warehouse.
4. Validate customer for credit sale.
5. Validate currency/rate.
6. Recalculate totals.
7. Lock StockBalance rows.
8. Run FEFO or manual override.
9. Block expired stock.
10. Prevent negative stock.
11. Deduct stock.
12. Snapshot COGS.
13. Create SALE StockMovements.
14. Create revenue journal.
15. Create COGS journal.
16. Create customer receivable.
17. Record immediate receipt if entered.
18. Mark POSTED.
19. Audit.

Remove trusted frontend/client control over `buy_price`.

---

# 31. Customer Payment

Replace limitations in current SalePayment architecture.

Create:

```text
CustomerPayment
CustomerPaymentAllocation
```

Fields:

```text
customer
shop
financial_account
payment_date
currency
amount
exchange_rate_used
base_amount
reference
notes
status
```

Allocation:

```text
payment
sale
allocated_amount
```

One receipt can pay several invoices.

Unallocated customer credit is allowed.

---

# 32. Receivables

Customer outstanding:

```text
Posted Sales
- Payment Allocations
- Sales Returns/Credits
= Accounts Receivable
```

Required reports:

```text
Customer Statement
Outstanding Invoices
Receivable Aging
Payment History
```

Do not maintain one independently editable customer balance field.

---

# 33. Sale Return

Reuse SaleReturn + SaleReturnItem.

Requirements:

```text
linked to original SaleItem
partial return
multiple returns allowed while quantity remains
correct original batch restoration
revenue reversal
COGS reversal
receivable reduction
customer credit/refund if already paid
```

Original sale remains untouched.

---

# 34. FinancialAccount

Evolve current Account model.

Add:

```text
account_type
bank_name
account_number
sarafi_name
is_active
```

Types:

```text
CASH
BANK
SARAFI
OTHER
```

Each account has exactly one currency.

---

# 35. Financial Account Balance

Do not manually edit running balance.

Balance:

```text
opening_balance
+ inflows
- outflows
```

If a cached balance is added later, it must be maintained by controlled transactions.

---

# 36. AccountTransfer

Keep/refactor current account transfer.

Fields:

```text
transfer_number
source_account
destination_account
source_amount
source_currency
destination_amount
destination_currency
exchange_rate_used
service_charge
service_charge_currency
service_charge_account
date
status
```

---

# 37. Sarafi

No dedicated Sarafi model required.

Use:

```text
FinancialAccount.account_type = SARAFI
```

Each currency = separate account.

Example:

```text
Ahmad Sarafi USD
Ahmad Sarafi AFN
```

---

# 38. Sarafi Supplier Payment

Create one atomic workflow.

Example:

```text
Supplier receives 20,000 USD
Transfer fee       200 USD
Sarafi outflow   20,200 USD
```

Journal:

```text
DR Accounts Payable        20,000
DR Transfer Charge Expense    200
    CR Sarafi Account              20,200
```

Supplier payment allocation = 20,000 only.

---

# 39. Expense Refactor

Keep ExpenseCategory.

Expense target fields:

```text
expense_number
shop
category
financial_account
currency
amount
exchange_rate_used
base_amount
expense_date
description
attachment
status
```

Posting:

```text
DR Expense Ledger
CR Financial Account
```

---

# 40. Chart of Accounts

Add:

```text
LedgerAccount
```

Account types:

```text
ASSET
LIABILITY
EQUITY
INCOME
EXPENSE
```

Suggested initial chart:

```text
1000 Assets
  1100 Cash
  1200 Bank
  1300 Sarafi
  1400 Accounts Receivable
  1500 Inventory

2000 Liabilities
  2100 Accounts Payable

3000 Equity
  3100 Opening Balance Equity
  3200 Retained Earnings

4000 Income
  4100 Sales Revenue
  4200 Other Income

5000 Cost of Sales
  5100 Cost of Goods Sold

6000 Expenses
  6100 Salary Expense
  6200 Rent Expense
  6300 Utilities Expense
  6400 Transport Expense
  6500 Transfer Charge Expense
  6900 Other Expense
```

---

# 41. JournalEntry

Fields:

```text
journal_number
shop
entry_date
description
source_type
source_id
status
posted_by
posted_at
voided_by
voided_at
void_reason
```

---

# 42. JournalEntryLine

Fields:

```text
journal_entry
ledger_account
customer
supplier
financial_account
transaction_currency
debit
credit
exchange_rate_used
base_debit
base_credit
description
```

Before posting:

```text
sum(debit) == sum(credit)
sum(base_debit) == sum(base_credit)
```

---

# 43. Posting Rules

## Credit Sale

```text
DR Accounts Receivable
    CR Sales Revenue

DR Cost of Goods Sold
    CR Inventory
```

## Cash Sale

```text
DR Cash/Bank/Sarafi
    CR Sales Revenue

DR Cost of Goods Sold
    CR Inventory
```

## Credit Purchase

```text
DR Inventory
    CR Accounts Payable
```

## Supplier Payment

```text
DR Accounts Payable
    CR Financial Account
```

## Customer Receipt

```text
DR Financial Account
    CR Accounts Receivable
```

## Expense

```text
DR Expense
    CR Financial Account
```

## Sale Return

```text
DR Sales Return / Revenue Reversal
    CR AR / Customer Credit / Cash

DR Inventory
    CR Cost of Goods Sold
```

## Purchase Return

```text
DR Accounts Payable / Supplier Credit
    CR Inventory
```

---

# 44. Accounting Reports

Build from JournalEntryLine:

```text
General Ledger
Trial Balance
Profit & Loss
Journal Report
Account Activity
```

Do not calculate accounting P&L independently from sales/purchase report formulas.

---

# 45. P&L

```text
Income
- Cost of Goods Sold
= Gross Profit

Gross Profit
- Operating Expenses
= Net Profit
```

Base/reporting currency used for consolidated reporting.

---

# 46. AR/AP Aging

Invoices have due dates.

Buckets:

```text
Current
1-30
31-60
61-90
90+
```

Reports:

```text
Customer Receivable Aging
Supplier Payable Aging
```

---

# 47. Document Lifecycle

For all important documents:

```text
DRAFT
POSTED
VOID
```

Transfers additionally:

```text
DISPATCHED
RECEIVED
```

Posted records:
- cannot be physically deleted
- cannot freely edit financial/stock meaning

Corrections occur through void/reversal.

---

# 48. Service Layer

Move business side effects out of large ViewSets.

Recommended:

```text
services/
  inventory/
    fefo.py
    receive_stock.py
    issue_stock.py
    adjust_stock.py
    transfer_stock.py

  purchases/
    post_purchase.py
    void_purchase.py
    return_purchase.py
    supplier_payment.py

  sales/
    post_sale.py
    void_sale.py
    return_sale.py
    customer_payment.py

  finance/
    account_transfer.py
    sarafi_payment.py
    currency_exchange.py

  accounting/
    post_journal.py
    posting_rules.py
    reverse_journal.py
```

---

# 49. Transaction Safety

Every posting/reversal operation must use:

```python
@transaction.atomic
```

Use `select_for_update()` for:
- StockBalance
- invoices being paid
- affected financial projections
- documents being posted

---

# 50. Permissions

Initial roles:

```text
Head User
Branch Manager
Accountant
Cashier
Sales User
Purchase User
Warehouse User
Report Viewer
```

Keep current tab permissions for UI navigation, but add backend action-level permissions.

Examples:

```text
sales.create_sale
sales.post_sale
sales.void_sale
sales.return_sale

inventory.adjust_stock
inventory.transfer_stock
inventory.manual_batch_override

finance.receive_customer_payment
finance.make_supplier_payment
finance.transfer_accounts

accounting.create_manual_journal
accounting.view_general_ledger
```

---

# 51. Branch/Warehouse Isolation

Centralize access helpers:

```python
accessible_shops(user)
assert_shop_access(user, shop)
accessible_warehouses(user)
assert_warehouse_access(user, warehouse)
```

Do not implement different access logic independently in each ViewSet.

---

# 52. AuditLog

Add:

```text
user
action
model_name
object_id
reference
timestamp
before_data
after_data
ip_address
```

Audit:

```text
LOGIN
CREATE
UPDATE
POST
VOID
PAYMENT
RETURN
TRANSFER
ADJUSTMENT
MANUAL_BATCH_OVERRIDE
PERMISSION_CHANGE
EXCHANGE_RATE_CHANGE
```

Keep DailyDiary as human-readable activity history, but not as security/accounting audit truth.

---

# 53. Notifications

Version 1:

```text
LOW_STOCK
NEAR_EXPIRY
EXPIRED_STOCK
CUSTOMER_DUE
SUPPLIER_DUE
```

In-system dashboard alerts first.

---

# 54. Required Reports

## Inventory

```text
Current Stock
Stock by Branch
Stock by Warehouse
Stock by Product
Batch Stock
Near Expiry
Expired Stock
Low Stock
Stock Movement
Stock Adjustment
Stock Transfer
Inventory Valuation
```

## Sales

```text
Sales Summary
Sales by Branch
Sales by Product
Sales by Customer
Sales by User
Sales by Currency
Sales Returns
Outstanding Sales
Customer Statement
Receivable Aging
Gross Profit
```

## Purchases

```text
Purchase Summary
Purchase by Branch
Purchase by Product
Purchase by Supplier
Purchase Returns
Outstanding Purchases
Supplier Statement
Payable Aging
```

## Finance

```text
Cash Ledger
Bank Ledger
Sarafi Ledger
Financial Account Ledger
Account Balances
Transfers
Service Charges
Currency Exchange
Receipts
Payments
```

## Accounting

```text
General Ledger
Trial Balance
Profit & Loss
Journal Report
```

---

# 55. Dashboard

Reuse current dashboard, add:

```text
Today's Sales
Today's Purchases
Receivables
Payables
Cash Balance
Bank Balance
Sarafi Balance
Low Stock Count
Near Expiry Count
Expired Stock Count
Top Products
Branch Comparison
Expense Breakdown
```

---

# 56. Opening Balances

No historical migration.

Create controlled opening workflows.

## Opening Inventory

Input:

```text
branch
warehouse
product
batch
expiry
quantity
unit_cost
currency
```

Creates:

```text
StockBalance
StockMovement OPENING
```

Accounting:

```text
DR Inventory
CR Opening Balance Equity
```

## Customer Opening Receivable

```text
DR Accounts Receivable
CR Opening Balance Equity
```

## Supplier Opening Payable

```text
DR Opening Balance Equity
CR Accounts Payable
```

## Cash/Bank/Sarafi

```text
DR Financial Account Control
CR Opening Balance Equity
```

---

# 57. Migration Strategy

Preferred go-live:

1. Preserve legacy DB.
2. Build new structures.
3. Freeze legacy transaction entry.
4. Perform physical stock count.
5. Collect batch and expiry data.
6. Import opening inventory.
7. Reconcile customer balances.
8. Reconcile supplier balances.
9. Reconcile Cash/Bank/Sarafi.
10. Create balanced opening journal.
11. Go live.
12. Keep legacy data read-only if needed.

Do not invent historical batches from unreliable legacy rows.

---

# 58. API Plan

Recommended:

```text
/api/v1/
```

Organization:

```text
/shops/
/warehouses/
/company-settings/
```

Products:

```text
/products/
/categories/
/units/
/product-prices/
```

Inventory:

```text
/inventory/balances/
/inventory/batches/
/inventory/movements/
/inventory/adjustments/
/inventory/transfers/
```

Actions:

```text
/transfers/{id}/dispatch/
/transfers/{id}/receive/
/adjustments/{id}/post/
/adjustments/{id}/void/
```

Purchases:

```text
/purchases/
/purchases/{id}/post/
/purchases/{id}/void/
/purchase-returns/
/purchase-returns/{id}/post/
/supplier-payments/
```

Sales:

```text
/sales/
/sales/{id}/post/
/sales/{id}/void/
/sale-returns/
/sale-returns/{id}/post/
/customer-payments/
```

Finance:

```text
/financial-accounts/
/financial-transactions/
/account-transfers/
/sarafi/pay-supplier/
```

Accounting:

```text
/ledger-accounts/
/journal-entries/
/journal-entries/{id}/post/
/journal-entries/{id}/void/
/reports/general-ledger/
/reports/trial-balance/
/reports/profit-loss/
```

---

# 59. Serializer Pattern

For transaction documents use:

```text
SaleCreateSerializer
SaleDraftUpdateSerializer
SaleDetailSerializer
SalePostSerializer
SaleVoidSerializer
```

Same approach for Purchase, Returns, Transfers, Payments, Journals.

Serializers validate data.  
Services perform business side effects.

---

# 60. Frontend Changes

Keep React stack.

Add modules/screens:

Inventory:
```text
Warehouses
Batch Inventory
Expiry
Stock Movements
Adjustments
Transfers
Dispatch
Receive
```

Sales:
```text
Warehouse
Due Date
Currency/Rate
Invoice Discount
Payment Account
FEFO Allocation
Manual Batch Override
Customer Outstanding
Return
```

Purchases:
```text
Warehouse
Supplier Invoice
Due Date
Batch
Expiry
Final Landed Unit Cost
Supplier Payment
Return
```

Finance:
```text
Financial Accounts
Cash
Bank
Sarafi
Customer Receipt
Supplier Payment
Account Transfer
Currency Exchange
Sarafi Supplier Payment
```

Accounting:
```text
Chart of Accounts
Manual Journal
Journal List
General Ledger
Trial Balance
P&L
```

Settings:
```text
Company
Branches
Warehouses
Currencies
Exchange Rates
Categories
Units
Product Prices
Expense Categories
Users
Roles
Permissions
Expiry Warning
Numbering
```

---

# 61. Development Phases

## PHASE 0 - Safety and PostgreSQL

- Git branch
- backup
- credential rotation
- env config
- PostgreSQL
- baseline tests

## PHASE 1 - Company / Branch / Warehouse / Currency

- CompanySetting
- Currency refactor
- base currency
- ExchangeRate fix
- Warehouse
- default warehouses
- access helpers

## PHASE 2 - Product Master

- Product extensions
- default UOM
- barcode
- stock minimum
- batch/expiry flags
- ProductPrice

## PHASE 3 - Inventory Core

- InventoryBatch
- StockBalance
- StockMovement
- weighted average
- FEFO
- expiry blocking
- manual override
- adjustments
- transfer refactor
- concurrency tests

**Do not proceed to major sale/purchase refactoring until Phase 3 is stable.**

## PHASE 4 - Purchase

- new fields
- document lifecycle
- posting service
- supplier payment allocation
- purchase returns
- inventory integration

## PHASE 5 - Sales

- new fields
- document lifecycle
- FEFO
- COGS snapshot
- customer payment allocation
- sale return
- refunds/credits

## PHASE 6 - Finance / Sarafi

- financial account types
- account transfers
- cross-currency
- Sarafi supplier payment
- transfer charges

## PHASE 7 - Accounting

- Chart of Accounts
- JournalEntry
- JournalEntryLine
- automatic posting
- manual journal
- reversals
- GL
- Trial Balance
- P&L

## PHASE 8 - Expenses

- accounting integration
- attachments
- document lifecycle

## PHASE 9 - Permissions and Audit

- action permissions
- warehouse scope
- AuditLog
- security audit

## PHASE 10 - Reports / Dashboard / Alerts

- new reports
- aging
- accounting reports
- dashboard
- notifications

## PHASE 11 - Opening Balance / Go-Live

- opening imports
- reconciliation
- training
- production cutover

---

# 62. Critical Tests

## Inventory

```text
weighted average
FEFO earliest expiry
FEFO split batches
expired stock rejected
manual override
negative stock rejected
concurrent last-stock sale
adjustment
transfer dispatch
transfer receipt
batch preserved
expiry preserved
sale return
purchase return
```

## Sales

```text
cash sale
credit sale
partial payment
multi-invoice receipt
fixed discount
percentage discount
partial return
multiple returns
paid-sale return
unpaid-sale return
void
```

## Purchases

```text
multi-item purchase
partial supplier payment
multi-invoice allocation
on-account payment
purchase return
paid purchase return
void
```

## Currency

```text
new dynamic currency
base currency
effective-date rate
cross-currency
historical snapshot
```

## Sarafi

```text
deposit
withdrawal
Sarafi-to-Sarafi
currency exchange
supplier payment
service charge
correct total deduction
```

## Accounting

For every journal:

```text
debit == credit
base debit == base credit
```

Test:
```text
sale
COGS
purchase
customer receipt
supplier payment
expense
sale return
purchase return
Sarafi fee
account transfer
manual journal
void
opening balances
```

---

# 63. First 20 Tasks

Start here:

```text
1. Create feature/jawid-tasal-refactor
2. Backup database
3. Rotate exposed secrets
4. Move secrets to .env
5. Configure PostgreSQL
6. Run current system on PostgreSQL
7. Add CompanySetting
8. Refactor Currency fields
9. Add base currency
10. Fix effective-date ExchangeRate lookup
11. Add Warehouse
12. Create default warehouse for each Shop
13. Centralize Shop/Warehouse access validation
14. Extend Product
15. Add ProductPrice
16. Add InventoryBatch
17. Add StockBalance constraints
18. Add StockMovement
19. Write weighted-average tests
20. Write FEFO tests
```

Only after Tasks 16-20 are stable should the developer refactor purchase/sale posting.

---

# 64. Do NOT Do These

Do not:
- keep expanding legacy Stock forever
- allow stock quantity direct editing
- accept sale COGS from frontend
- physically delete posted transactions
- hard-code currencies
- rely on SQLite row locking
- calculate final accounting P&L from unrelated report formulas
- put all side effects in model `.save()`
- let frontend role/tab hiding replace backend permissions

---

# 65. Target Transaction Flow

```text
User Action
    ↓
Serializer Validation
    ↓
Domain Service
    ↓
transaction.atomic()
    ↓
Permission + Branch/Warehouse Validation
    ↓
select_for_update()
    ↓
Operational Document
    ↓
Stock Engine
    ↓
Financial Account Engine
    ↓
AR/AP Allocation
    ↓
Accounting Journal
    ↓
Audit Log
    ↓
Commit
```

Reports should read from these controlled sources.

---

# 66. Target Model List

Organization:
```text
CompanySetting
Shop
Warehouse
UserWarehouseAccess
```

Master Data:
```text
Product
Category
Unit
ProductPrice
Currency
ExchangeRate
```

Inventory:
```text
InventoryBatch
StockBalance
StockMovement
StockAdjustment
StockAdjustmentItem
StockTransfer
StockTransferItem
```

Parties:
```text
Customer
Supplier
```

Purchases:
```text
Purchase
PurchaseItem
SupplierPayment
SupplierPaymentAllocation
PurchaseReturn
PurchaseReturnItem
```

Sales:
```text
Sale
SaleItem
SaleItemBatchAllocation
CustomerPayment
CustomerPaymentAllocation
SaleReturn
SaleReturnItem
```

Finance:
```text
FinancialAccount
AccountTransaction
AccountTransfer
```

Expenses:
```text
ExpenseCategory
Expense
```

Accounting:
```text
LedgerAccount
JournalEntry
JournalEntryLine
```

Controls:
```text
AuditLog
Notification
```

---

# 67. Definition of Done

The architecture is ready when:

```text
Every stock change has a StockMovement.
Every stock quantity belongs to a Warehouse.
Expiry-sensitive stock belongs to a Batch.
Every sale is expiry-safe.
Every sale uses FEFO or audited override.
Every posted financial event creates balanced journals.
Every customer receipt reaches a FinancialAccount.
Every supplier payment reaches a FinancialAccount.
Sarafi charges are separate from supplier payment value.
Posted documents are reversible but not silently deleted.
Branch users cannot access unauthorized data.
Important actions are auditable.
Reports read from consistent transaction sources.
```

---

# 68. Final Direction

Use the second system as the base.

**Reuse:**
- branch-aware application structure
- existing purchase/sale header-item models
- returns
- payment concepts
- account screens
- exchange-rate concepts
- reporting/export UI

**Rebuild/refactor deeply:**
- inventory source of truth
- warehouse/batch/FEFO layer
- posting lifecycle
- customer/supplier payment allocations
- Sarafi workflow
- accounting engine
- immutable audit/reversal behavior

Priority:

```text
Data Integrity
-> Transaction Safety
-> Accounting Correctness
-> Permissions
-> Reporting
-> UI Polish
```
