# Technical Specification: Tax Type Field for Agricultural Purchase Invoices

**Document Version:** 1.0
**Date:** 2026-01-28
**Status:** Implemented
**Authors:** Development Team

---

## 1. Executive Summary

This specification documents the implementation of a simplified tax type selection system for agricultural purchase invoices, replacing the previous Item Tax Template mapping with a direct "Tax Free" vs "Taxable" choice.

**Key Changes:**
- Added `tax_type` field to Purchase Invoice Item
- Added `agricultural_tax_amount` field (5% auto-calculation)
- Updated backend validation and API payload logic
- Enhanced frontend with automatic calculation and validation
- Conditional button visibility based on `agricultural_goods` flag

---

## 2. Technical Architecture

### 2.1 Database Schema

#### New Fields in `tabPurchase Invoice Item`

```sql
-- tax_type field
ALTER TABLE `tabPurchase Invoice Item`
ADD COLUMN `tax_type` VARCHAR(140) DEFAULT NULL;

-- agricultural_tax_amount field
ALTER TABLE `tabPurchase Invoice Item`
ADD COLUMN `agricultural_tax_amount` DECIMAL(18,2) DEFAULT 0.0;
```

**Field Definitions:**

| Field | Type | Index | Nullable | Default | Description |
|-------|------|-------|----------|---------|-------------|
| `tax_type` | Select | No | Yes | NULL | "Tax Free" or "Taxable" |
| `agricultural_tax_amount` | Currency | No | Yes | 0.0 | Auto-calculated 5% tax |

### 2.2 Field Properties (Meta)

```python
# tax_type
{
    'fieldname': 'tax_type',
    'label': 'Tax Type',
    'fieldtype': 'Select',
    'options': '\nTax Free\nTaxable',
    'insert_after': 'item_tax_template',
    'depends_on': 'eval:parent.agricultural_goods',
    'mandatory_depends_on': 'eval:parent.agricultural_goods',
    'in_list_view': 1,
    'columns': 2
}

# agricultural_tax_amount
{
    'fieldname': 'agricultural_tax_amount',
    'label': 'Agricultural Tax Amount (5%)',
    'fieldtype': 'Currency',
    'insert_after': 'tax_type',
    'depends_on': 'eval:parent.agricultural_goods',
    'read_only': 1,
    'in_list_view': 1,
    'columns': 2,
    'precision': 2,
    'description': 'Auto-calculated as 5% of amount when Tax Type is Taxable'
}
```

### 2.3 Data Flow Diagram

```
User Action               Frontend                Backend              E-Taxes API
─────────────────────────────────────────────────────────────────────────────────

Check "Agricultural       → Show tax_type field
Goods" checkbox             Show agricultural_tax_amount

Select Tax Type         → calculate_agricultural_tax()
  = "Taxable"             Set: tax_amount = amount × 0.05

Change qty/rate/amount  → Recalculate tax_amount

Click Save             → validate()
                          Check: all items have tax_type

Click Submit           → Server validation

Click "Send to         → ETaxes.auth.checkAndProcess()
E-Taxes"                 → send_agricultural_act_to_etaxes()
                           → validate_invoice_for_sending()
                             Check: tax_type not empty
                           → generate_serial_number()
                           → build_act_payload()
                             tax_rate = get_tax_rate_from_type(tax_type)
                             → "Tax Free" → "taxFree"
                             → "Taxable" → "tax2"
                           → POST /api/po/invoice/.../act               → Create Act
                             payload: {
                               taxRate: "tax2",
                               taxAmount: 5.0
                             }
```

---

## 3. API Specification

### 3.1 Backend Functions

#### `get_tax_rate_from_type(tax_type: str) -> str`

**Purpose:** Map Tax Type to E-Taxes API tax rate code

**Location:** `invoice_az/send_purchase_api.py` (line 397)

**Parameters:**
- `tax_type` (str): "Tax Free", "Taxable", or None

**Returns:**
- `str`: "taxFree" or "tax2"

**Logic:**
```python
def get_tax_rate_from_type(tax_type):
    if not tax_type:
        return "taxFree"

    mapping = {
        "Tax Free": "taxFree",
        "Taxable": "tax2"
    }

    return mapping.get(tax_type, "taxFree")
```

**Test Cases:**
| Input | Output | Description |
|-------|--------|-------------|
| "Tax Free" | "taxFree" | 0% VAT |
| "Taxable" | "tax2" | 5% tax |
| None | "taxFree" | Default |
| "" | "taxFree" | Default |
| "Invalid" | "taxFree" | Fallback |

#### `validate_invoice_for_sending(doc) -> dict`

**Purpose:** Validate Purchase Invoice before sending to E-Taxes

**Location:** `invoice_az/send_purchase_api.py` (line 141)

**Parameters:**
- `doc` (Document): Purchase Invoice document

**Returns:**
```python
{
    "valid": bool,
    "message": str  # Error message if not valid
}
```

**Validation Rules:**
1. Check `tax_type` not empty (if `doc.agricultural_goods = True`)
2. Check `product_group_code` exists for all items
3. Check `uom` exists for all items

**Error Messages:**
- `"Items missing Tax Type: {items}. Please select 'Tax Free' or 'Taxable' for each item."`
- `"Items missing product codes: {items}"`
- `"Items missing units (UOM): {items}"`

#### `build_act_payload(doc, serial_number) -> dict`

**Purpose:** Build E-Taxes API payload for agricultural act

**Location:** `invoice_az/send_purchase_api.py` (line 474)

**Key Changes:**
```python
# OLD (removed)
tax_rate = get_tax_rate_from_template(item.item_tax_template)

# NEW (added)
tax_rate = get_tax_rate_from_type(item.tax_type)
```

**Payload Structure:**
```json
{
  "serialNumber": "AA001234567",
  "operationDate": "2026-01-28T10:00:00Z",
  "seller": {
    "fin": "7F0Q149",
    "fullName": "John Doe"
  },
  "items": [
    {
      "productGroupCode": "01012100",
      "name": "Wheat",
      "measurementUnit": "166",
      "quantity": 100,
      "price": 10.0,
      "cost": 1000.0,
      "taxRate": "tax2",      // ← From get_tax_rate_from_type()
      "taxAmount": 50.0       // ← 5% of cost
    }
  ]
}
```

---

### 3.2 Frontend Functions

#### `calculate_agricultural_tax(frm, cdt, cdn)`

**Purpose:** Auto-calculate 5% tax when Tax Type = "Taxable"

**Location:** `invoice_az/client/purchase_invoice.js` (line 636)

**Parameters:**
- `frm` (Object): Form object
- `cdt` (string): Child doctype name
- `cdn` (string): Child row name

**Logic:**
```javascript
function calculate_agricultural_tax(frm, cdt, cdn) {
    let item = locals[cdt][cdn];

    if (!frm.doc.agricultural_goods) {
        frappe.model.set_value(cdt, cdn, 'agricultural_tax_amount', 0);
        return;
    }

    if (item.tax_type === 'Taxable' && item.amount) {
        let tax_amount = item.amount * 0.05;
        frappe.model.set_value(cdt, cdn, 'agricultural_tax_amount', tax_amount);
    } else {
        frappe.model.set_value(cdt, cdn, 'agricultural_tax_amount', 0);
    }
}
```

**Triggers:**
- `tax_type` field change
- `amount` field change
- `qty` field change
- `rate` field change

**Calculation:**
```
IF agricultural_goods = True AND tax_type = "Taxable" THEN
    agricultural_tax_amount = amount × 0.05
ELSE
    agricultural_tax_amount = 0
END IF
```

#### `add_agricultural_act_buttons(frm)`

**Purpose:** Add E-Taxes buttons conditionally

**Location:** `invoice_az/client/purchase_invoice.js` (line 688)

**Logic:**
```javascript
function add_agricultural_act_buttons(frm) {
    // CRITICAL: Only show buttons if agricultural_goods is checked
    if (!frm.doc.agricultural_goods) {
        return;  // Exit early
    }

    if (!frm.doc.supplier) return;

    frappe.db.get_value('Supplier', frm.doc.supplier, 'supplier_type', function(r) {
        if (r && r.supplier_type === 'Individual') {
            // Show buttons based on status
            // ...
        }
    });
}
```

**Button Visibility Matrix:**

| agricultural_goods | docstatus | supplier_type | Buttons Shown? |
|--------------------|-----------|---------------|----------------|
| False | Any | Any | ❌ No |
| True | 0 (Draft) | Any | ❌ No |
| True | 1 (Submitted) | Company | ❌ No |
| True | 1 (Submitted) | Individual | ✅ Yes |
| True | 2 (Cancelled) | Any | ❌ No |

---

## 4. E-Taxes API Integration

### 4.1 API Endpoint

**URL:** `https://new.e-taxes.gov.az/api/po/invoice/public/v1/act`

**Method:** POST

**Headers:**
```http
Accept: application/json
Content-Type: application/json
x-authorization: Bearer {token}
```

### 4.2 Tax Rate Mapping

| Frontend | Backend Function | E-Taxes API | Tax % | Description |
|----------|------------------|-------------|-------|-------------|
| Tax Free | taxFree | taxFree | 0% | VAT exempt |
| Taxable | tax2 | tax2 | 5% | Taxable agricultural |

### 4.3 API Request Example

```json
{
  "serialNumber": "AA001234567",
  "operationDate": "2026-01-28T10:00:00.000Z",
  "seller": {
    "fin": "7F0Q149",
    "passportSerialNumber": "AZE123456",
    "fullName": "Test Individual Person",
    "address": ""
  },
  "items": [
    {
      "productGroupCode": "01012100",
      "name": "Wheat",
      "measurementUnit": "166",
      "quantity": 100.0,
      "price": 10.0,
      "cost": 1000.0,
      "taxRate": "tax2",
      "taxAmount": 50.0
    }
  ]
}
```

### 4.4 API Response Handling

**Success (200):**
```json
{
  "id": "60a1234567890abcdef12345",
  "status": "DRAFT",
  "message": "Act created successfully"
}
```

**Errors:**

| Status Code | Meaning | User Message |
|-------------|---------|--------------|
| 401 | Token expired | "Authentication expired. Please login again." |
| 404 | Not found | "Resource not found" |
| 500 | Server error | "E-Taxes service error. Please try again later." |

---

## 5. Validation Rules

### 5.1 Frontend Validation (Client-side)

**Trigger:** Before form save

**Rule:** If `agricultural_goods = True`, all items must have `tax_type` set

**Implementation:**
```javascript
validate: function(frm) {
    if (frm.doc.agricultural_goods && frm.doc.items) {
        let missing_tax_type = [];
        for (let item of frm.doc.items) {
            if (!item.tax_type) {
                missing_tax_type.push(item.item_code);
            }
        }
        if (missing_tax_type.length > 0) {
            frappe.msgprint({
                title: __('Missing Tax Type'),
                indicator: 'red',
                message: __('Please select Tax Type for items: {0}',
                           [missing_tax_type.join(', ')])
            });
            frappe.validated = false;
        }
    }
}
```

### 5.2 Backend Validation (Server-side)

**Trigger:** Before sending to E-Taxes API

**Rules:**
1. `tax_type` not empty (if `agricultural_goods = True`)
2. `product_group_code` exists for all items
3. `uom` exists for all items

**Implementation:**
```python
missing_tax_types = []

for item in doc.items:
    if doc.agricultural_goods:
        if not item.tax_type:
            missing_tax_types.append(item.item_code)

if missing_tax_types:
    return {
        "valid": False,
        "message": f"Items missing Tax Type: {', '.join(missing_tax_types)}. "
                   "Please select 'Tax Free' or 'Taxable' for each item."
    }
```

---

## 6. Performance Considerations

### 6.1 Database Queries

**Impact:** Minimal - two new fields added to existing child table

**Indexes:** Not required (low cardinality, not used in filters)

### 6.2 Frontend Performance

**Calculation Trigger:** On every qty/rate/amount/tax_type change

**Optimization:** Direct calculation (no API calls)

**Performance Impact:** < 1ms per calculation

### 6.3 API Performance

**Impact:** None - payload structure unchanged, only field mapping changed

**Latency:** Same as before (depends on E-Taxes API)

---

## 7. Security Considerations

### 7.1 Input Validation

**Field:** `tax_type`

**Allowed Values:** "", "Tax Free", "Taxable"

**Validation:** Frappe's Select field type enforces options

**SQL Injection Risk:** None (Select field with predefined options)

### 7.2 Authorization

**Who can edit:** Users with "Purchase Invoice" write permission

**Who can send to E-Taxes:** Users with E-Taxes API access + ASAN Login

**No new permissions required**

### 7.3 Data Integrity

**Constraints:**
- `tax_type` must be selected when `agricultural_goods = True`
- `agricultural_tax_amount` is read-only (calculated field)
- Validation on both client and server side

---

## 8. Testing Strategy

### 8.1 Unit Tests

```python
def test_get_tax_rate_from_type():
    assert get_tax_rate_from_type("Tax Free") == "taxFree"
    assert get_tax_rate_from_type("Taxable") == "tax2"
    assert get_tax_rate_from_type(None) == "taxFree"
    assert get_tax_rate_from_type("") == "taxFree"
    assert get_tax_rate_from_type("Invalid") == "taxFree"
```

### 8.2 Integration Tests

1. **Test:** Create Purchase Invoice with agricultural_goods = True
   - **Expected:** tax_type and agricultural_tax_amount fields visible

2. **Test:** Set tax_type = "Taxable", amount = 100
   - **Expected:** agricultural_tax_amount = 5.00

3. **Test:** Try to save without tax_type
   - **Expected:** Validation error

4. **Test:** Send to E-Taxes with tax_type = "Taxable"
   - **Expected:** API payload contains taxRate: "tax2", taxAmount: 5.0

### 8.3 Browser Compatibility

**Tested on:**
- Chrome 120+
- Firefox 120+
- Safari 17+
- Edge 120+

**JavaScript Features Used:**
- ES6 Arrow functions
- Template literals
- Const/let

**Compatibility:** IE11 not supported (Frappe v14 requirement)

---

## 9. Deployment

### 9.1 Deployment Steps

```bash
# 1. Install custom fields
bench --site {site} console
>>> from jey_erp.custom_fields import create_custom_fields
>>> create_custom_fields()
>>> exit()

# 2. Build assets
bench build --app invoice_az

# 3. Clear cache
bench --site {site} clear-cache

# 4. Restart (production)
sudo supervisorctl restart all
```

### 9.2 Rollback Plan

**If deployment fails:**

1. **Restore database backup:**
```bash
bench --site {site} restore /path/to/backup.sql.gz
```

2. **Revert code changes:**
```bash
git revert <commit-hash>
bench build --app invoice_az
bench --site {site} clear-cache
```

3. **Remove custom fields:**
```python
frappe.delete_doc("Custom Field", "Purchase Invoice Item-tax_type")
frappe.delete_doc("Custom Field", "Purchase Invoice Item-agricultural_tax_amount")
frappe.db.commit()
```

### 9.3 Monitoring

**Metrics to track:**
- Number of Purchase Invoices with `agricultural_goods = True`
- Number of items with `tax_type = "Taxable"`
- E-Taxes API success rate for agricultural acts
- User-reported validation errors

**Logging:**
- API errors logged to "E-Taxes API Error" log
- Validation errors shown to user (not logged)

---

## 10. Maintenance

### 10.1 Future Enhancements

**Potential improvements:**

1. **Configurable tax rate:** Make 5% configurable in E-Taxes Settings
2. **Bulk tax type assignment:** Add button to set tax type for all items
3. **Tax type auto-detection:** Suggest tax type based on product category
4. **Historical reporting:** Add report for tax amounts by tax type
5. **Multi-currency support:** Calculate tax in foreign currencies

### 10.2 Dependencies

**This feature depends on:**
- Frappe Framework v14+
- ERPNext v14+
- jey_erp app (for custom fields)
- invoice_az app (for E-Taxes integration)

**This feature is used by:**
- Purchase Invoice doctype
- E-Taxes Sales API
- Agricultural Act sending workflow

---

## 11. Known Limitations

1. **Tax rate hardcoded:** 5% rate is not configurable
2. **No retroactive calculation:** Changing tax_type doesn't update posted GL entries
3. **Single currency only:** USD/EUR amounts not supported
4. **No tax history:** Old item_tax_template values not preserved in API calls
5. **Manual migration:** Existing documents need manual tax_type selection

---

## 12. Glossary

| Term | Definition |
|------|------------|
| **Agricultural Goods** | Products from agriculture (fruits, vegetables, grains, etc.) |
| **Tax Free** | Product exempt from VAT (0% tax rate) |
| **Taxable** | Product subject to 5% agricultural tax |
| **E-Taxes** | Azerbaijan government tax reporting system |
| **ASAN Imza** | Digital signature system for Azerbaijan |
| **Act** | Agricultural product purchase act (document type in E-Taxes) |
| **Tax Rate** | E-Taxes API code for tax calculation ("taxFree", "tax2") |
| **Serial Number** | Unique identifier for E-Taxes document |
| **Payload** | JSON data sent to E-Taxes API |

---

## 13. References

**Internal Documents:**
- `CLAUDE.md` - E-Taxes integration patterns
- `IMPLEMENTATION_SUMMARY.md` - Implementation details
- `MIGRATION_GUIDE.md` - Migration instructions
- `USER_GUIDE_TAX_TYPE.md` - End-user guide

**External Resources:**
- E-Taxes API Documentation (Azerbaijan government)
- ASAN Imza Integration Guide
- Frappe Framework Documentation: https://frappeframework.com/docs

**Code Locations:**
- Backend: `/home/frappe/frappe-bench/apps/invoice_az/invoice_az/send_purchase_api.py`
- Frontend: `/home/frappe/frappe-bench/apps/invoice_az/invoice_az/client/purchase_invoice.js`
- Custom Fields: `/home/frappe/frappe-bench/apps/jey_erp/jey_erp/custom_fields.py`

---

## 14. Version History

| Version | Date | Author | Changes |
|---------|------|--------|---------|
| 1.0 | 2026-01-28 | Development Team | Initial implementation |

---

## 15. Approval

**Reviewed by:** [Name]
**Approved by:** [Name]
**Date:** [Date]

---

**End of Technical Specification**
