# Invoice and Payment Flow Implementation

## Current State Analysis

### What Exists:
1. ✅ Quotation creation and acceptance
2. ✅ Sales order creation from quotation
3. ✅ Pesepay payment gateway integration
4. ✅ Payment model with invoice_id field
5. ✅ Invoice model with basic fields
6. ✅ Odoo integration for invoice generation
7. ✅ File attachment capability in Odoo (ir.attachment)

### What's Missing:
1. ❌ Automatic invoice generation after quotation acceptance
2. ❌ Bank transfer payment option with POP upload
3. ❌ Unified payment table/view
4. ❌ POP attachment to Odoo invoice
5. ❌ Email notification with POP attachment

---

## Required Flow (Based on Your Requirements)

### Step 1: Quotation Accepted
**Current:** Quotation → Sales Order (in Odoo)
**Required:** Quotation → Sales Order → **Invoice** (in Odoo)

```
Customer accepts quotation
    ↓
Sales Order created in Odoo (state: 'sale')
    ↓
Invoice automatically generated in Odoo (account.move)
    ↓
Invoice details returned to portal
    ↓
Customer sees invoice with amount due
```

### Step 2: Payment Options Presented
Customer sees two payment methods:

#### Option A: Pesepay (Online Payment)
- Click "Pay Now"
- Redirect to Pesepay gateway
- Payment processed automatically
- Invoice marked as paid in Odoo
- Confirmation email sent

#### Option B: Bank Transfer
- Customer makes bank transfer offline
- Customer uploads Proof of Payment (POP)
- POP attached to invoice in Odoo (ir.attachment)
- POP sent to finance email (NOC@afinet.africa)
- Finance team verifies and marks invoice as paid manually in Odoo
- Confirmation email sent to customer

---

## Implementation Plan

### 1. Update Quotation Acceptance Flow
**File:** `afinet-portal-backend/app/Http/Controllers/API/QuotationController.php`

Add invoice generation after sales order creation:

```php
// After converting quotation to sales order in Odoo
if ($odooOrderId) {
    // Generate invoice immediately
    $invoiceId = $odooService->generateInvoice($odooOrderId);
    
    // Get invoice details
    $invoiceDetails = $odooService->getInvoice($invoiceId);
    
    // Create local invoice record
    $invoice = Invoice::create([
        'invoice_number' => $invoiceDetails['name'],
        'customer_id' => $customer->id,
        'sales_order_id' => $salesOrder->id,
        'invoice_type' => 'sales',
        'amount' => $invoiceDetails['amount_total'] - ($invoiceDetails['amount_tax'] ?? 0),
        'tax_amount' => $invoiceDetails['amount_tax'] ?? 0,
        'total_amount' => $invoiceDetails['amount_total'],
        'status' => 'sent', // Invoice is now sent to customer
        'invoice_date' => $invoiceDetails['invoice_date'] ?? now(),
        'due_date' => $invoiceDetails['invoice_date_due'] ?? now()->addDays(7),
        'odoo_invoice_id' => $invoiceId,
        'odoo_synced' => true,
    ]);
    
    // Update sales order with invoice reference
    $salesOrder->update([
        'invoice_id' => $invoice->id,
    ]);
}
```

### 2. Add Bank Transfer Payment Method
**File:** `afinet-portal-backend/app/Http/Controllers/API/PaymentController.php`

Add new endpoint for bank transfer with POP upload:

```php
/**
 * Upload proof of payment for bank transfer
 * POST /api/payments/upload-proof
 */
public function uploadProofOfPayment(Request $request)
{
    $validator = Validator::make($request->all(), [
        'invoice_id' => 'required|exists:invoices,id',
        'amount' => 'required|numeric|min:0.01',
        'payment_reference' => 'required|string|max:255',
        'payment_date' => 'required|date',
        'proof_file' => 'required|file|mimes:pdf,jpg,jpeg,png|max:5120', // 5MB max
        'notes' => 'nullable|string',
    ]);

    if ($validator->fails()) {
        return response()->json([
            'success' => false,
            'errors' => $validator->errors(),
        ], 422);
    }

    $customer = Auth::user();

    try {
        // Get invoice
        $invoice = Invoice::where('id', $request->invoice_id)
            ->where('customer_id', $customer->id)
            ->first();

        if (!$invoice) {
            return response()->json([
                'success' => false,
                'message' => 'Invoice not found',
            ], 404);
        }

        // Check if invoice is already paid
        if ($invoice->status === 'paid') {
            return response()->json([
                'success' => false,
                'message' => 'This invoice has already been paid',
            ], 400);
        }

        // Store file locally
        $file = $request->file('proof_file');
        $filename = 'pop_' . $invoice->invoice_number . '_' . time() . '.' . $file->getClientOriginalExtension();
        $path = $file->storeAs('proof_of_payments', $filename, 'private');

        // Create payment record
        $payment = Payment::create([
            'payment_reference' => $request->payment_reference,
            'customer_id' => $customer->id,
            'invoice_id' => $invoice->id,
            'sales_order_id' => $invoice->sales_order_id,
            'amount' => $request->amount,
            'payment_method' => 'bank_transfer',
            'status' => 'pending_verification',
            'notes' => $request->notes,
            'gateway_response' => [
                'proof_file_path' => $path,
                'proof_file_name' => $filename,
                'payment_date' => $request->payment_date,
            ],
        ]);

        // Upload to Odoo if invoice has Odoo ID
        if ($invoice->odoo_invoice_id) {
            $odooService = app(OdooService::class);
            
            // Read file content
            $fileContent = file_get_contents($file->getRealPath());
            
            // Upload attachment to Odoo
            $attachmentId = $odooService->uploadAttachment(
                $filename,
                $fileContent,
                'account.move',
                $invoice->odoo_invoice_id
            );

            // Update payment with Odoo attachment ID
            $payment->update([
                'gateway_response' => array_merge($payment->gateway_response, [
                    'odoo_attachment_id' => $attachmentId,
                ]),
            ]);

            Log::info('Proof of payment uploaded to Odoo', [
                'payment_id' => $payment->id,
                'invoice_id' => $invoice->id,
                'odoo_attachment_id' => $attachmentId,
            ]);
        }

        // Send email to finance team with POP attachment
        Mail::to('NOC@afinet.africa')
            ->send(new ProofOfPaymentReceived($payment, $invoice, $customer, $path));

        // Send confirmation to customer
        Mail::to($customer->email)
            ->send(new ProofOfPaymentConfirmation($payment, $invoice));

        return response()->json([
            'success' => true,
            'message' => 'Proof of payment uploaded successfully. Your payment will be verified within 24 hours.',
            'data' => [
                'payment_id' => $payment->id,
                'payment_reference' => $payment->payment_reference,
                'status' => $payment->status,
                'invoice' => [
                    'id' => $invoice->id,
                    'invoice_number' => $invoice->invoice_number,
                    'amount' => $invoice->total_amount,
                ],
            ],
        ]);

    } catch (\Exception $e) {
        Log::error('Proof of payment upload failed', [
            'customer_id' => $customer->id,
            'invoice_id' => $request->invoice_id,
            'error' => $e->getMessage(),
        ]);

        return response()->json([
            'success' => false,
            'message' => 'Failed to upload proof of payment: ' . $e->getMessage(),
        ], 500);
    }
}
```

### 3. Add Upload Attachment Method to OdooService
**File:** `afinet-portal-backend/app/Services/OdooService.php`

```php
/**
 * Upload file attachment to Odoo
 * 
 * @param string $fileName Original filename
 * @param string $fileContent Raw file content (not base64 yet)
 * @param string $resModel Model to attach to (e.g., 'account.move', 'sale.order')
 * @param int $resId Record ID to attach to
 * @param string|null $description Optional description
 * @return int Attachment ID in Odoo
 */
public function uploadAttachment($fileName, $fileContent, $resModel, $resId, $description = null)
{
    try {
        $attachmentData = [
            'name' => $fileName,
            'datas' => base64_encode($fileContent),
            'res_model' => $resModel,
            'res_id' => $resId,
            'type' => 'binary',
        ];

        if ($description) {
            $attachmentData['description'] = $description;
        }

        $attachmentId = $this->call('ir.attachment', 'create', $attachmentData);

        Log::info('File uploaded to Odoo', [
            'attachment_id' => $attachmentId,
            'file_name' => $fileName,
            'res_model' => $resModel,
            'res_id' => $resId,
        ]);

        return $attachmentId;

    } catch (Exception $e) {
        Log::error('Failed to upload attachment to Odoo: ' . $e->getMessage());
        throw $e;
    }
}
```

### 4. Update Payment Model
**File:** `afinet-portal-backend/app/Models/Payment.php`

Add bank_transfer to payment_method enum:

```php
// In migration file: database/migrations/*_create_payments_table.php
$table->enum('payment_method', ['pesepay', 'bank_transfer', 'cash', 'other'])->default('pesepay');
$table->enum('status', ['pending', 'pending_verification', 'completed', 'failed', 'refunded', 'cancelled'])->default('pending');
```

### 5. Create Email Templates

**File:** `afinet-portal-backend/app/Mail/ProofOfPaymentReceived.php`
```php
<?php

namespace App\Mail;

use App\Models\Payment;
use App\Models\Invoice;
use App\Models\Customer;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Storage;

class ProofOfPaymentReceived extends Mailable
{
    use Queueable, SerializesModels;

    public $payment;
    public $invoice;
    public $customer;
    public $popPath;

    public function __construct(Payment $payment, Invoice $invoice, Customer $customer, $popPath)
    {
        $this->payment = $payment;
        $this->invoice = $invoice;
        $this->customer = $customer;
        $this->popPath = $popPath;
    }

    public function build()
    {
        $email = $this->subject('New Proof of Payment Received - ' . $this->invoice->invoice_number)
            ->view('emails.proof-of-payment-received')
            ->with([
                'customerName' => $this->customer->name,
                'invoiceNumber' => $this->invoice->invoice_number,
                'amount' => $this->payment->amount,
                'paymentReference' => $this->payment->payment_reference,
                'paymentDate' => $this->payment->gateway_response['payment_date'] ?? now(),
                'notes' => $this->payment->notes,
            ]);

        // Attach proof of payment file
        if (Storage::disk('private')->exists($this->popPath)) {
            $email->attach(Storage::disk('private')->path($this->popPath), [
                'as' => basename($this->popPath),
                'mime' => Storage::disk('private')->mimeType($this->popPath),
            ]);
        }

        return $email;
    }
}
```

### 6. Update API Routes
**File:** `afinet-portal-backend/routes/api.php`

```php
// Payment routes
Route::middleware(['auth:sanctum'])->prefix('payments')->group(function () {
    Route::post('/initiate', [PaymentController::class, 'initiatePayment']);
    Route::post('/upload-proof', [PaymentController::class, 'uploadProofOfPayment']); // NEW
    Route::get('/{id}/status', [PaymentController::class, 'getPaymentStatus']);
    Route::get('/', [PaymentController::class, 'getPayments']);
    Route::get('/{id}', [PaymentController::class, 'getPaymentDetails']);
});

// Invoice routes
Route::middleware(['auth:sanctum'])->prefix('invoices')->group(function () {
    Route::get('/', [PaymentController::class, 'getInvoices']);
    Route::get('/{id}', [PaymentController::class, 'getInvoice']);
    Route::post('/{id}/pay', [PaymentController::class, 'processInvoicePayment']);
});
```

---

## Frontend Changes Required

### 1. Invoice Display After Quotation Acceptance
Show invoice details immediately after quotation is accepted:

```javascript
// After quotation acceptance
const response = await api.quotations.accept(quotationId);

if (response.success) {
  const invoice = response.data.invoice;
  
  // Show invoice details
  showInvoiceModal({
    invoiceNumber: invoice.invoice_number,
    amount: invoice.total_amount,
    dueDate: invoice.due_date,
    paymentOptions: ['pesepay', 'bank_transfer']
  });
}
```

### 2. Unified Payment Page
Create a single payment page that shows both options:

```jsx
<PaymentOptions invoice={invoice}>
  <PesepayOption 
    onPay={() => initiatePesepayPayment(invoice.id)}
  />
  
  <BankTransferOption 
    onUploadPOP={(file, details) => uploadProofOfPayment(invoice.id, file, details)}
    bankDetails={companyBankDetails}
  />
</PaymentOptions>
```

### 3. Proof of Payment Upload Component
```jsx
<ProofOfPaymentUpload>
  <FileUpload 
    accept=".pdf,.jpg,.jpeg,.png"
    maxSize={5 * 1024 * 1024} // 5MB
  />
  
  <PaymentDetails>
    <Input name="payment_reference" label="Payment Reference" required />
    <Input name="payment_date" type="date" label="Payment Date" required />
    <Input name="amount" type="number" label="Amount Paid" required />
    <Textarea name="notes" label="Additional Notes" />
  </PaymentDetails>
  
  <Button onClick={handleSubmit}>Upload Proof of Payment</Button>
</ProofOfPaymentUpload>
```

---

## Database Migration Required

```php
// Add to payments table
Schema::table('payments', function (Blueprint $table) {
    $table->enum('payment_method', ['pesepay', 'bank_transfer', 'cash', 'other'])->change();
    $table->enum('status', ['pending', 'pending_verification', 'completed', 'failed', 'refunded', 'cancelled'])->change();
});

// Add to invoices table
Schema::table('invoices', function (Blueprint $table) {
    $table->decimal('amount_paid', 10, 2)->default(0)->after('total_amount');
    $table->decimal('amount_due', 10, 2)->virtualAs('total_amount - amount_paid');
});

// Add to sales_orders table
Schema::table('sales_orders', function (Blueprint $table) {
    $table->foreignId('invoice_id')->nullable()->after('quotation_id')->constrained('invoices');
});
```

---

## Testing Checklist

- [ ] Quotation acceptance generates invoice in Odoo
- [ ] Invoice details returned to portal
- [ ] Pesepay payment works for invoices
- [ ] Bank transfer option displays correctly
- [ ] POP file upload validates file type and size
- [ ] POP uploads to Odoo ir.attachment
- [ ] POP email sent to finance team with attachment
- [ ] Customer receives confirmation email
- [ ] Payment status shows "pending_verification" for bank transfers
- [ ] Unified payments table shows both Pesepay and bank transfer payments
- [ ] Invoice status updates after payment verification

---

## Summary

This implementation adds:
1. ✅ Automatic invoice generation after quotation acceptance
2. ✅ Bank transfer payment option
3. ✅ Proof of payment upload to Odoo
4. ✅ Email notifications with POP attachment
5. ✅ Unified payment tracking
6. ✅ Support for both online (Pesepay) and offline (bank transfer) payments
