# Buy Bill Controller Documentation

**File**: `/controllers/buyBillController.php`  
**Purpose**: Manages purchase orders, buy bills, purchase returns, and supplier transactions  
**Last Updated**: December 19, 2024  
**Total Functions**: 57  
**Lines of Code**: ~7000+

---

## 📋 Overview

The Buy Bill Controller is the core component for managing all purchase-related operations in the ERP system. It handles:
- Creating and editing purchase bills (buy bills)
- Managing purchase returns (return buy bills)
- Combined buy & return operations
- Purchase offers/quotations
- Inventory stock increases
- Supplier debt tracking
- Daily accounting entries for purchases
- Serial number tracking for purchased items
- Excel import for bulk purchases

### Primary Functions
- [x] Create new purchase bills
- [x] Edit existing purchase bills
- [x] Delete purchase bills
- [x] View purchase bill details
- [x] Print purchase bills
- [x] Manage purchase returns
- [x] Track serial numbers
- [x] Generate accounting entries
- [x] Update inventory quantities
- [x] Excel import/export

### Related Controllers
- [sellbillController.php](sellbillController.md) - Sales operations
- [supplierController.php](supplierController.md) - Supplier management
- [productController.php](productController.md) - Product management
- [storeController.php](storeController.md) - Warehouse/store management
- [returnBuyBillController.php](#) - Dedicated purchase returns
- [storedetailController.php](#) - Inventory management
- [dailyentryController.php](#) - Accounting entries

---

## 🗄️ Database Tables

### Primary Tables (Direct Operations)
| Table Name | Purpose | Key Columns |
|------------|---------|-------------|
| **buybill** | Main purchase bills | buybillid, buybillserial, buybilldate, buybilltotalbill, buybillsupplierid, buybillstoreid, buybillsaveid |
| **buybilldetail** | Purchase bill line items | buybilldetailid, buybillid, buybilldetailproductid, buybilldetailquantity, buybilldetailprice |
| **returnbuybill** | Purchase return bills | returnbuybillid, returnbuybillserial, returnbuybilltotalbill |
| **returnbuybilldetail** | Return bill line items | returnbuybilldetailid, returnbuybillid, returnbuybilldetailproductid |
| **buyandruternbill** | Combined buy & return | buybillid (combined operations) |
| **buyandruternbilldetail** | Combined details | Details for mixed operations |
| **buybilloffer** | Purchase quotations/offers | Same structure as buybill, conditions=1 |
| **buybilldetailoffer** | Offer line items | Details for offers |

### Inventory Tables
| Table Name | Purpose | Relationship |
|------------|---------|--------------|
| **storedetail** | Stock quantities by store | Updated on buy/return |
| **sizecolorstoredetail** | Stock by size/color | For variant products |
| **storereport** | Stock movement history | Audit trail |
| **buypriceshistorybook** | Purchase price history | Price tracking |
| **productserial** | Serial number tracking | For serialized items |

### Financial Tables
| Table Name | Purpose | Relationship |
|------------|---------|--------------|
| **supplier** | Supplier master data | buybill.buybillsupplierid |
| **save** | Cash registers/safes | buybill.buybillsaveid |
| **savedaily** | Daily cash movements | Financial reconciliation |
| **dailyentry** | Accounting journal entries | Auto-generated from bills |
| **buybillcurr** | Multi-currency support | Currency conversion |

### Reference Tables
| Table Name | Purpose | Relationship |
|------------|---------|--------------|
| **product** | Product master data | Foreign key in details |
| **productcat** | Product categories | product.productCatId |
| **productunit** | Units of measurement | buybilldetail.productunitid |
| **store** | Warehouses/stores | buybill.buybillstoreid |
| **billname** | Bill templates/types | buybill.billnameid |
| **billsettings** | Bill configuration | Per bill type settings |
| **currency** | Currency definitions | For multi-currency |
| **costcenter** | Cost centers | For cost allocation |

---

## 🔧 Key Functions

### 1. add()
**Purpose**: Create a new purchase bill (main entry point)  
**Called By**: Form submission with `?do=add`  
**Line**: 3952

**Parameters** (via $_POST):
- `billnameid` (int) - Bill template ID
- `saveid` (int) - Cash register/safe ID
- `supplier` (int) - Supplier ID
- `storeid` (int) - Store/warehouse ID
- `ordertype` (int) - 0=Bill, 1=Offer
- `buyItr` (int) - Number of line items
- `product{n}` - Product IDs (n=1 to buyItr)
- `pronum{n}` - Quantities
- `proprice{n}` - Unit prices
- `totalbill` (float) - Total before discount
- `gendis` (float) - General discount
- `totalafterdiscount` (float) - Total after discount
- `genpay` (float) - Payment amount

**Returns**: Array `[billType, returnBuyBill_Id, buyBill_Id, buyAndReturnBill_Id, ordertype, billnameId]`

**Database Operations**:
1. **BEGIN TRANSACTION**
2. **INSERT** into `buybill` - Main bill record
3. **INSERT** into `buybilldetail` - For each product line
4. **UPDATE** `storedetail` SET `productquantity = productquantity + quantity` - Increase stock
5. **INSERT** into `storereport` - Stock movement record
6. **INSERT** into `buypriceshistorybook` - Price history
7. **UPDATE** `supplier` SET `supplierdebt = supplierdebt + totalBill` - Update supplier debt
8. **INSERT** into `savedaily` - Cash movement (if payment made)
9. **INSERT** into `dailyentry` - Accounting journal entry
10. **COMMIT TRANSACTION**

**Business Logic Flow**:
```
1. Validate form data
2. Determine bill type (Buy, Return, or Both)
3. Create bill header record
4. Loop through products:
   a. Insert detail record
   b. Update stock quantity (call increaseProductQuantity)
   c. Record price history
   d. Handle serial numbers if applicable
5. Update supplier debt
6. Record cash payment
7. Create accounting entries (call doBillDailyEntry)
8. Commit or rollback on error
9. Redirect to success page with bill ID
```

**Example Flow**:
```php
POST: ?do=add
├── Validate supplier, store, products
├── INSERT buybill (ID: 123)
├── Loop products (3 items):
│   ├── INSERT buybilldetail (product A, qty 10, price 50)
│   ├── UPDATE storedetail (stock: 100 → 110)
│   ├── INSERT storereport (movement record)
│   ├── INSERT buybilldetail (product B, qty 5, price 100)
│   ├── UPDATE storedetail (stock: 50 → 55)
│   └── ...
├── UPDATE supplier debt (+total: 1000)
├── INSERT savedaily (cash out: -300)
├── doBillDailyEntry() → accounting entries
└── REDIRECT: ?do=editprint&id=123
```

---

### 2. increaseProductQuantity()
**Purpose**: Increase stock quantity when purchasing products  
**Line**: 6144  
**CRITICAL**: This function was fixed for PHP 8.2 compatibility

**Parameters**:
- `$storedetailId` (int) - Store detail record ID
- `$productquantityBefore` (float) - Current quantity
- `$productChangeAmount` (float) - Quantity to add
- `$lastBuyPrice` (float) - Purchase price
- `$productnumber` (float) - Unit conversion factor
- `$colName` (string) - Detail table column name
- `$detailId` (int) - Detail record ID
- `$productId` (int) - Product ID
- `$tableName` (string) - Detail table name
- `$sizeColorStoreDetailId` (int) - Size/color variant ID
- `$sizeId` (int) - Size ID
- `$colorId` (int) - Color ID
- ... (additional parameters for accounting)

**Returns**: `$productquantityAfter` (float) - New quantity

**Database Operations**:
- **UPDATE** `storedetail` SET `productquantity = Before + Change`
- **UPDATE** `sizecolorstoredetail` (if product has size/color)
- **INSERT/UPDATE** `buypriceshistorybook` - Price tracking

**Logic**:
```php
if (has size/color) {
    UPDATE sizecolorstoredetail.quantity
    UPDATE storedetail (sum all size/color quantities)
} else {
    UPDATE storedetail.productquantity directly
}
```

**PHP 8.2 Fix Applied**:
- Changed from `updateQuantityWithSumChild()` for all products
- Now uses `updateQuantityPlusEqualORMinusEqual()` for non-size/color products
- Prevents setting quantity to 0 when sizecolorstoredetail is empty

---

### 3. doBillDailyEntry()
**Purpose**: Create accounting journal entries for purchase bills  
**Line**: 6948

**Parameters**:
- `$billType` (int) - 1=Return, 2=Buy, 3=Both
- `$buyBill_Id` (int) - Buy bill ID
- `$returnBuyBill_Id` (int) - Return bill ID
- `$buyAndReturnBill_Id` (int) - Combined bill ID
- `$billTotalBeforeDiscount` (float) - Subtotal
- `$storeId` (int) - Warehouse ID
- `$taxVal` (float) - Tax amount
- `$totalpayed` (float) - Payment amount
- `$saveId` (int) - Cash register ID
- `$remin` (float) - Remaining balance
- `$supdata` (object) - Supplier data
- `$billDiscountVal` (float) - Discount amount
- `$taxOfDiscountVal` (float) - Tax on discount
- `$billAdditionVal` (float) - Additional charges

**Returns**: void

**Database Operations**:
- **INSERT** into `dailyentry` - Journal entry header
- **INSERT** into `dailyentrydetail` - Debit/credit lines

**Accounting Entries Created**:

**For Buy Bill (Type 2)**:
```
Debit:  Inventory (Store Account)        XXX
Debit:  VAT Receivable                   XXX
Credit: Supplier Payable                      XXX
Credit: Withholding Tax                       XXX
```

**For Return Bill (Type 1)**:
```
Debit:  Supplier Payable                 XXX
Credit: Inventory (Store Account)             XXX
Credit: VAT Receivable                        XXX
```

---

### 4. insertStoredetail()
**Purpose**: Create new stock record or update existing  
**Line**: 6295  
**CRITICAL**: Fixed for PHP 8.2 - now properly handles non-size/color products

**Parameters**:
- `$storeid` (int)
- `$productid` (int)
- `$productChangeAmount` (float)
- `$sizeColorStoreDetailId` (int)
- `$sizeId` (int)
- `$colorId` (int)

**Logic**:
```php
if (size/color product) {
    // Update sizecolorstoredetail
    // Update main storedetail with SUM
} else {
    if (storedetail exists) {
        // PHP 8.2 FIX: Use updateQuantityPlusEqualORMinusEqual
        // NOT updateQuantityWithSumChild (which returns 0)
    } else {
        // Insert new storedetail record
    }
}
```

---

### 5. delete()
**Purpose**: Soft delete purchase bill (sets conditions=1)  
**Line**: 5537

**Parameters** (via $_GET):
- `id` (int) - Bill ID to delete
- `ordertype` (int) - 0=Bill, 1=Offer

**Database Operations**:
- **UPDATE** `buybill` SET `conditions = 1` WHERE `buybillid = ?`
- Does NOT physically delete - preserves audit trail

---

### 6. showAll()
**Purpose**: Display list of all purchase bills  
**Line**: 5347

**Returns**: Assigns to Smarty template

**Database Operations**:
- **SELECT** from `buybill` JOIN `supplier` WHERE `conditions = 0`
- Calculates total bill amounts

---

### 7. showBillDetails()
**Purpose**: Load bill header and details for viewing/editing  
**Line**: 5298

**Parameters**:
- `$buyBill_Id` (int)
- `$ordertype` (int) - 0=Bill, 1=Offer

**Returns**: Array `[billData, billDetails, currencyData]`

**Database Operations**:
- **SELECT** from `buybill` JOIN related tables
- **SELECT** from `buybilldetail` WHERE `buybillid = ?`
- **SELECT** currency conversion data if multi-currency

---

## 📊 Main Workflows

### Workflow 1: Create Purchase Bill
```
User visits: ?do=bill&billnameid=X
   ↓
Display Form (bill.html)
   ↓
User fills:
   - Supplier
   - Store
   - Products (barcode scan or select)
   - Quantities
   - Prices
   ↓
Submit POST ?do=add
   ↓
add() Function:
   ├── Validate data
   ├── BEGIN TRANSACTION
   ├── INSERT buybill
   ├── INSERT buybilldetail (foreach product)
   ├── increaseProductQuantity() (foreach product)
   ├── UPDATE supplier debt
   ├── INSERT savedaily (if payment)
   ├── doBillDailyEntry() (accounting)
   ├── COMMIT
   └── Redirect to success page
   ↓
Display: notes2.html ("Success")
   ↓
Auto-redirect (1 sec): ?do=bill (new bill)
```

**Files Involved**:
- View: `/views/default/buyBillview/bill.html`
- Controller: `buyBillController.php::add()`
- Models: Buybill, Buybilldetail, Storedetail, Supplier, Save
- JavaScript: `/views/default/assets/js/buyBill.js`

---

### Workflow 2: Edit Existing Bill
```
?do=editbill&id=123
   ↓
Load bill data: showBillDetails(123)
   ↓
Display form pre-filled
   ↓
Submit POST ?do=update
   ↓
update() Function:
   ├── Load old bill data
   ├── Reverse old stock changes
   ├── Reverse old accounting entries
   ├── Apply new changes (like add())
   └── Redirect
```

---

### Workflow 3: Purchase Return
```
User scans "return" products
   ↓
Submit with negative quantities
   ↓
add() detects returns
   ↓
Creates returnbuybill record
   ↓
decreaseProductQuantity() (opposite of buy)
   ↓
Reverse accounting entries
```

---

## 🎯 URL Routes & Actions

| Action (`?do=`) | Method | Description | View Template | Line |
|-----------------|--------|-------------|---------------|------|
| **bill** | GET | Display new bill form | bill.html | 492 |
| **editbill** | GET | Edit existing bill form | edit.html | 756 |
| **add** | POST | Create new bill | - | 1245 |
| **update** | POST | Update existing bill | - | 1308 |
| **details** | GET | View bill details (read-only) | details.html | 1412 |
| **editprint** | GET | View/print bill | editprint.html | 1485 |
| **delete** | POST | Soft delete bill | - | 1846 |
| **show** | GET | List all bills | show.html | 1744 |
| **showoffers** | GET | List quotations/offers | showoffers.html | 2209 |
| **excelToBill** | GET/POST | Import from Excel | excelToBill.html | 1877 |
| **sucess** | GET | Success message | notes2.html | 1869 |

---

## ⚠️ Known Issues & Fixes

### Issue 1: Stock Quantity Set to 0 on Second Buy (FIXED)
**Problem**: When buying a product for the second time, stock quantity becomes 0 instead of increasing  
**Cause**: `updateQuantityWithSumChild()` was called for all products, which does `SUM(quantity) FROM sizecolorstoredetail`. For products without size/color, this table is empty, so SUM returns NULL → 0  
**Fix**: In `insertStoredetail()` line 6330-6340, added conditional:
```php
if (has size/color) {
    $storeDetailExt->updateQuantityWithSumChild(...);
} else {
    $storeDetailExt->updateQuantityPlusEqualORMinusEqual(...);
}
```
**Date Fixed**: December 19, 2024  
**File**: buyBillController.php lines 6330-6340

---

### Issue 2: Property Assignment on Null (FIXED)
**Problem**: `Fatal error: Attempt to assign property "entryComment" on null`  
**Cause**: PHP 8.2 doesn't allow assigning properties to uninitialized objects  
**Fix**: Added `$dailyEntry = new stdClass();` before property assignments  
**Line**: 6993  
**Date Fixed**: December 19, 2024

---

### Issue 3: abs() TypeError (FIXED)
**Problem**: `TypeError: abs(): Argument #1 must be of type int|float, string given`  
**Cause**: Empty string `$billAdditionVal = ''` passed to `abs()`  
**Fix**: Cast to float before abs(): `abs((float)$billAdditionVal)`  
**Line**: 7001  
**Date Fixed**: December 19, 2024

---

## 🔐 Permissions & Security

### Required Permissions
- User must be authenticated (`include_once("../public/authentication.php")`)
- Permission to access buy bills module
- Store access permissions (users can be restricted to specific stores)

### Security Checks
```php
// Line 1246
include_once("../public/authentication.php");

// Store restrictions
if ($userdata->viewbills == 0) {
    // Show only user's own bills
} elseif ($userdata->viewbills == 2) {
    // Show group's bills
}
```

### Input Validation
- Supplier ID must exist
- Store ID must exist  
- Product IDs must be valid
- Quantities must be > 0
- Prices must be numeric
- Transaction used for atomicity

---

## 📝 Notes

### Important Considerations
1. **Transactions**: All bill operations use database transactions for data integrity
2. **Stock Updates**: Stock is updated immediately upon bill creation
3. **Accounting**: Daily entries are auto-generated and linked to bills
4. **Multi-Currency**: System supports multiple currencies with conversion factors
5. **Serial Numbers**: Products can have serial number tracking
6. **Cost Centers**: Bills can be allocated to cost centers
7. **Taxes**: Multiple tax types supported (VAT, withholding tax)
8. **Bill Templates**: Different bill types via `billname` table

### Performance Optimizations
- OPcache enabled (see CLAUDE.md)
- Session caching for MAC address
- Lazy loading of product data
- Index on foreign keys

### Data Integrity
- Foreign key constraints ensure referential integrity
- Soft deletes preserve audit trail (`conditions=1`)
- Stock movements tracked in `storereport`
- Price history maintained in `buypriceshistorybook`

---

## 🔧 Configuration & Settings

### Bill Settings (via billsettings table)
- Enable/disable buy functionality
- Enable/disable return functionality
- Auto-numbering sequences
- Default accounts for accounting entries
- Tax calculation methods

### Program Settings (via programsettings table)
- Decimal places for quantities
- Decimal places for prices
- Default store
- Default currency
- Inventory valuation method (FIFO, LIFO, Average)

---

## 📚 Related Documentation
- [CLAUDE.md](../../CLAUDE.md) - PHP 8.2 Migration Guide
- [sellbillController.md](sellbillController.md) - Sales Operations
- [Database Schema](../database/schema.md) - Full database structure
- [API Documentation](../api/README.md) - Web API endpoints

---

## 🔍 Debugging Tips

### Enable Debug Mode
```php
// In buyBillController.php
error_reporting(E_ALL);
ini_set('display_errors', 1);
```

### Check Stock Updates
```sql
SELECT * FROM storereport 
WHERE storereportproductid = [PRODUCT_ID] 
ORDER BY storereportdate DESC 
LIMIT 10;
```

### Verify Accounting Entries
```sql
SELECT * FROM dailyentry 
WHERE dailyentrymodelid = [BILL_ID] 
AND tablename = 'buybill';
```

### Test Transaction Rollback
- Introduce intentional error in add() function
- Verify all changes are rolled back
- Check stock quantities remain unchanged

---

**Documented By**: AI Assistant  
**Review Status**: ✅ Complete  
**Next Review**: When major changes occur
