Merge branch 'main' of bitbucket.org:jubilian/nhance_partner_be
This commit is contained in:
commit
c8135a06cd
@ -213,6 +213,7 @@ $routes->group('api', ['filter' => ["jwtAuth:1,2,3,4,agent","appSignature"] ], f
|
||||
//invoice
|
||||
$routes->get('invoice/list', 'InvoiceController::invoiceList');
|
||||
$routes->get('invoice/details', 'InvoiceController::findInvoiceWithItems');
|
||||
$routes->get('invoice/view', 'InvoiceController::invoiceView');
|
||||
$routes->post('invoice/create-or-update', 'InvoiceController::createOrUpdateInvoice');
|
||||
$routes->get('invoice/utrDetails', 'InvoiceController::utrDetails');
|
||||
$routes->post('invoice/add-payment', 'InvoiceController::addInvoicePayment'); // tempo
|
||||
|
||||
@ -38,8 +38,36 @@ class InvoiceController extends ResourceController
|
||||
public function invoiceList()
|
||||
{
|
||||
try {
|
||||
$fromDateRaw = $this->request->getGet('from_date');
|
||||
$toDateRaw = $this->request->getGet('to_date');
|
||||
|
||||
$data = $this->InvoiceModel
|
||||
$fromParsed = $this->parseOptionalDmYDate($fromDateRaw);
|
||||
$toParsed = $this->parseOptionalDmYDate($toDateRaw);
|
||||
|
||||
if ($fromParsed['error'] !== null) {
|
||||
return $this->respond([
|
||||
'status' => 'failed',
|
||||
'code' => 400,
|
||||
'data' => $fromParsed['error'],
|
||||
], 400);
|
||||
}
|
||||
if ($toParsed['error'] !== null) {
|
||||
return $this->respond([
|
||||
'status' => 'failed',
|
||||
'code' => 400,
|
||||
'data' => $toParsed['error'],
|
||||
], 400);
|
||||
}
|
||||
|
||||
if ($fromParsed['ymd'] !== null && $toParsed['ymd'] !== null && $fromParsed['ymd'] > $toParsed['ymd']) {
|
||||
return $this->respond([
|
||||
'status' => 'failed',
|
||||
'code' => 400,
|
||||
'data' => 'from_date must be on or before to_date',
|
||||
], 400);
|
||||
}
|
||||
|
||||
$query = $this->InvoiceModel
|
||||
->select('partner_invoice.*,
|
||||
PB.name as broker_name,
|
||||
pos.name as pos_name,
|
||||
@ -78,9 +106,16 @@ class InvoiceController extends ResourceController
|
||||
false)
|
||||
->join('partner_brokers PB', 'PB.id = partner_invoice.broker_id', 'left')
|
||||
->join('partner_pos pos', 'pos.id = partner_invoice.pos_id', 'left')
|
||||
->where('partner_invoice.is_active', 1)
|
||||
->orderBy('partner_invoice.id', 'DESC')
|
||||
->findAll();
|
||||
->where('partner_invoice.is_active', 1);
|
||||
|
||||
if ($fromParsed['ymd'] !== null) {
|
||||
$query->where('DATE(partner_invoice.invoice_date) >=', $fromParsed['ymd']);
|
||||
}
|
||||
if ($toParsed['ymd'] !== null) {
|
||||
$query->where('DATE(partner_invoice.invoice_date) <=', $toParsed['ymd']);
|
||||
}
|
||||
|
||||
$data = $query->orderBy('partner_invoice.id', 'DESC')->findAll();
|
||||
|
||||
return $this->respond(['status' => 'success', 'code' => 200,'data' => $data ], 200);
|
||||
|
||||
@ -89,6 +124,32 @@ class InvoiceController extends ResourceController
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional invoice list date filter: format d-m-Y (e.g. 10-03-2026). Empty / null = no filter.
|
||||
*
|
||||
* @return array{ymd: ?string, error: ?string}
|
||||
*/
|
||||
private function parseOptionalDmYDate($value): array
|
||||
{
|
||||
if ($value === null) {
|
||||
return ['ymd' => null, 'error' => null];
|
||||
}
|
||||
$v = trim((string) $value);
|
||||
if ($v === '') {
|
||||
return ['ymd' => null, 'error' => null];
|
||||
}
|
||||
|
||||
$dt = \DateTime::createFromFormat('d-m-Y', $v);
|
||||
if ($dt === false || $dt->format('d-m-Y') !== $v) {
|
||||
return [
|
||||
'ymd' => null,
|
||||
'error' => 'Invalid date format; use d-m-Y (e.g. 10-03-2026)',
|
||||
];
|
||||
}
|
||||
|
||||
return ['ymd' => $dt->format('Y-m-d'), 'error' => null];
|
||||
}
|
||||
|
||||
public function findInvoiceWithItems()
|
||||
{
|
||||
try {
|
||||
@ -150,6 +211,84 @@ class InvoiceController extends ResourceController
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* GET invoice_id, manager_id — invoice header + active line items (policies under this manager only).
|
||||
*/
|
||||
public function invoiceView()
|
||||
{
|
||||
try {
|
||||
$invoiceId = (int) $this->request->getGet('invoice_id');
|
||||
$managerId = (int) $this->request->getGet('manager_id');
|
||||
|
||||
if ($invoiceId <= 0 || $managerId <= 0) {
|
||||
return $this->respond([
|
||||
'status' => 'failed',
|
||||
'code' => 400,
|
||||
'data' => 'invoice_id and manager_id are required',
|
||||
], 400);
|
||||
}
|
||||
|
||||
$invoice = $this->InvoiceModel
|
||||
->where('id', $invoiceId)
|
||||
->where('is_active', 1)
|
||||
->first();
|
||||
|
||||
if (! $invoice) {
|
||||
return $this->respond([
|
||||
'status' => 'failed',
|
||||
'code' => 404,
|
||||
'data' => 'Invoice not found',
|
||||
], 404);
|
||||
}
|
||||
|
||||
$forbidden = $this->db->query(
|
||||
'SELECT 1 AS x FROM partner_invoice_items pii '
|
||||
. 'INNER JOIN partner_policy pp ON pp.id = pii.policy_id '
|
||||
. 'WHERE pii.invoice_id = ? AND pii.is_active = 1 '
|
||||
. 'AND (pp.manager_id IS NULL OR pp.manager_id != ?) '
|
||||
. 'LIMIT 1',
|
||||
[$invoiceId, $managerId]
|
||||
)->getRow();
|
||||
|
||||
if ($forbidden !== null) {
|
||||
return $this->respond([
|
||||
'status' => 'failed',
|
||||
'code' => 403,
|
||||
'data' => 'You do not have access to this invoice',
|
||||
], 403);
|
||||
}
|
||||
|
||||
$items = $this->InvoiceItemModel
|
||||
->select(
|
||||
'partner_invoice_items.*, pp.issued_date, pe.name AS customer_name, pp.premium_amount, '
|
||||
. 'pa.agent_code, pa.name AS agent_name, CONCAT_WS(" - ", pa.agent_code, pa.name) AS agent_code_name',
|
||||
false
|
||||
)
|
||||
->join('partner_policy pp', 'pp.id = partner_invoice_items.policy_id', 'left')
|
||||
->join('partner_enquiry pe', 'pe.id = pp.enquiry_id', 'left')
|
||||
->join('partner_quotation pq', 'pq.id = pp.quotation_id', 'left')
|
||||
->join('partner_agent pa', 'pa.id = pp.agent_id', 'left')
|
||||
->where('partner_invoice_items.invoice_id', $invoiceId)
|
||||
->where('partner_invoice_items.is_active', 1)
|
||||
->findAll();
|
||||
|
||||
return $this->respond([
|
||||
'status' => 'success',
|
||||
'code' => 200,
|
||||
'data' => [
|
||||
'invoice' => $invoice,
|
||||
'items' => $items,
|
||||
],
|
||||
], 200);
|
||||
} catch (\Exception $e) {
|
||||
return $this->respond([
|
||||
'status' => 'failed',
|
||||
'code' => 500,
|
||||
'data' => $e->getMessage(),
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
public function createOrUpdateInvoice()
|
||||
{
|
||||
$this->db->transBegin();
|
||||
@ -388,138 +527,177 @@ class InvoiceController extends ResourceController
|
||||
public function getCommissionRateList()
|
||||
{
|
||||
try {
|
||||
|
||||
$input = $this->request->getJSON(true);
|
||||
|
||||
// if (empty($input['broker_id'])) {
|
||||
// return $this->respond([
|
||||
// 'status' => 'failed',
|
||||
// 'code' => 400,
|
||||
// 'message'=> 'broker_id is required'
|
||||
// ], 400);
|
||||
// }
|
||||
|
||||
$invoice_id = isset($input['invoice_id']) ? $input['invoice_id'] : '';
|
||||
|
||||
// Build the main query
|
||||
$query = $this->PolicyModel
|
||||
->select('partner_policy.policy_number as policy_no, partner_policy.id as policy_id, partner_policy.issued_date, partner_policy.commission_amount, partner_policy.insured_name as customer_name, pe.agent_id ,pa.name as agent_name,pa.agent_code, partner_policy.premium_amount, pi.id as invoice_id, pi.invoice_no,
|
||||
COALESCE((
|
||||
SELECT GROUP_CONCAT(piu.utr_no ORDER BY piu.id SEPARATOR ", ")
|
||||
FROM partner_invoice_utr piu
|
||||
WHERE piu.invoice_id = pi.id
|
||||
AND piu.is_active = 1
|
||||
), "") as utr_no', false)
|
||||
// ->join(
|
||||
// 'partner_enquiry pe',
|
||||
// 'pe.id = partner_policy.enquiry_id AND pe.broker_id = ' . (int)$input['broker_id'],
|
||||
// 'left'
|
||||
// )
|
||||
->join('partner_enquiry pe','pe.id = partner_policy.enquiry_id','left')
|
||||
->join('partner_quotation pq', 'pq.id = partner_policy.quotation_id', 'left')
|
||||
->join('partner_invoice_items pii', 'pii.policy_id = partner_policy.id', 'left')
|
||||
->join('partner_invoice pi', 'pi.id = pii.invoice_id AND pi.is_active = 1', 'left')
|
||||
->join('partner_agent pa', 'pa.id = pe.agent_id', 'left')
|
||||
->where('partner_policy.is_active', 1)
|
||||
->where('partner_policy.commission_amount > 0')
|
||||
->where('partner_policy.is_data_accuracy_checked', 1)
|
||||
->where('partner_policy.manager_id', $input['manager_id']);
|
||||
|
||||
// Apply agent_id filter (supports multiple agents)
|
||||
if (!empty($input['agent_id'])) {
|
||||
if (is_array($input['agent_id'])) {
|
||||
$query->whereIn('pe.agent_id', $input['agent_id']);
|
||||
} else {
|
||||
$query->where('pe.agent_id', $input['agent_id']);
|
||||
}
|
||||
if (! is_array($input)) {
|
||||
return $this->respond([
|
||||
'status' => 'failed',
|
||||
'code' => 400,
|
||||
'message'=> 'JSON body is required',
|
||||
], 400);
|
||||
}
|
||||
|
||||
// Apply date range filters
|
||||
if (!empty($input['from_date'])) {
|
||||
$query->where('partner_policy.issued_date >=', date('Y-m-d', strtotime($input['from_date'])));
|
||||
if (empty($input['manager_id'])) {
|
||||
return $this->respond([
|
||||
'status' => 'failed',
|
||||
'code' => 400,
|
||||
'message'=> 'manager_id is required',
|
||||
], 400);
|
||||
}
|
||||
|
||||
if (!empty($input['to_date'])) {
|
||||
$query->where('partner_policy.issued_date <=', date('Y-m-d', strtotime($input['to_date'])));
|
||||
}
|
||||
$managerId = (int) $input['manager_id'];
|
||||
$invoiceIdRaw = $input['invoice_id'] ?? null;
|
||||
$invoiceId = ($invoiceIdRaw !== null && $invoiceIdRaw !== '' && (int) $invoiceIdRaw > 0)
|
||||
? (int) $invoiceIdRaw
|
||||
: 0;
|
||||
|
||||
// Apply POS filter if provided
|
||||
if (!empty($input['pos_id'])) {
|
||||
$query->where('partner_policy.pos_id', $input['pos_id']);
|
||||
}
|
||||
|
||||
// Filter based on invoice_id or policies not yet invoiced
|
||||
if (empty($invoice_id)) {
|
||||
$query->where('pii.policy_id IS NULL');
|
||||
// Unused policies: no active invoice line on an active invoice (same as getAgentUnusedCommissionList).
|
||||
// Do not LEFT JOIN all invoice_items — inactive rows duplicate policies and break "unused" detection.
|
||||
if ($invoiceId === 0) {
|
||||
$query = $this->PolicyModel
|
||||
->select(
|
||||
'partner_policy.policy_number as policy_no, partner_policy.id as policy_id, partner_policy.issued_date, '
|
||||
. 'partner_policy.commission_amount, partner_policy.insured_name as customer_name, pe.agent_id, '
|
||||
. 'pa.name as agent_name, pa.agent_code, partner_policy.premium_amount, '
|
||||
. "NULL AS invoice_id, NULL AS invoice_no, '' AS utr_no",
|
||||
false
|
||||
)
|
||||
->join('partner_enquiry pe', 'pe.id = partner_policy.enquiry_id', 'left')
|
||||
->join('partner_quotation pq', 'pq.id = partner_policy.quotation_id', 'left')
|
||||
->join('partner_agent pa', 'pa.id = pe.agent_id', 'left')
|
||||
->where('partner_policy.is_active', 1)
|
||||
// ->where('partner_policy.commission_amount >', 0)
|
||||
// ->where('partner_policy.is_data_accuracy_checked', 1)
|
||||
->where('partner_policy.manager_id', $managerId)
|
||||
->where('partner_policy.policy_number IS NOT NULL', null, false)
|
||||
->where(
|
||||
'NOT EXISTS (SELECT 1 FROM partner_invoice_items pii2 '
|
||||
. 'INNER JOIN partner_invoice pi2 ON pi2.id = pii2.invoice_id AND pi2.is_active = 1 '
|
||||
. 'WHERE pii2.policy_id = partner_policy.id AND pii2.is_active = 1)',
|
||||
null,
|
||||
false
|
||||
);
|
||||
} else {
|
||||
$query->where('pi.id', $invoice_id);
|
||||
$query = $this->PolicyModel
|
||||
->select(
|
||||
'partner_policy.policy_number as policy_no, partner_policy.id as policy_id, partner_policy.issued_date, '
|
||||
. 'partner_policy.commission_amount, partner_policy.insured_name as customer_name, pe.agent_id, '
|
||||
. 'pa.name as agent_name, pa.agent_code, partner_policy.premium_amount, pi.id as invoice_id, pi.invoice_no, '
|
||||
. 'COALESCE(('
|
||||
. 'SELECT GROUP_CONCAT(piu.utr_no ORDER BY piu.id SEPARATOR ", ") '
|
||||
. 'FROM partner_invoice_utr piu '
|
||||
. 'WHERE piu.invoice_id = pi.id AND piu.is_active = 1'
|
||||
. "), '') as utr_no",
|
||||
false
|
||||
)
|
||||
->join('partner_enquiry pe', 'pe.id = partner_policy.enquiry_id', 'left')
|
||||
->join('partner_quotation pq', 'pq.id = partner_policy.quotation_id', 'left')
|
||||
->join('partner_agent pa', 'pa.id = pe.agent_id', 'left')
|
||||
->join(
|
||||
'partner_invoice_items pii',
|
||||
'pii.policy_id = partner_policy.id AND pii.is_active = 1 AND pii.invoice_id = ' . $invoiceId,
|
||||
'inner'
|
||||
)
|
||||
->join('partner_invoice pi', 'pi.id = pii.invoice_id AND pi.is_active = 1', 'inner')
|
||||
->where('partner_policy.is_active', 1)
|
||||
->where('partner_policy.commission_amount >', 0)
|
||||
->where('partner_policy.is_data_accuracy_checked', 1)
|
||||
->where('partner_policy.manager_id', $managerId)
|
||||
->where('partner_policy.policy_number IS NOT NULL', null, false)
|
||||
->where('pi.id', $invoiceId)
|
||||
->distinct();
|
||||
}
|
||||
|
||||
$this->applyCommissionRateListPayloadFilters($query, $input);
|
||||
|
||||
$query->orderBy('partner_policy.issued_date', 'DESC');
|
||||
$query->orderBy('partner_policy.id', 'DESC');
|
||||
|
||||
$data = $query->findAll();
|
||||
|
||||
// Calculate total commission
|
||||
$total_commission = 0;
|
||||
foreach ($data as $policy) {
|
||||
$total_commission += $policy['commission_amount'];
|
||||
$total_commission += (float) ($policy['commission_amount'] ?? 0);
|
||||
}
|
||||
|
||||
return $this->respond([
|
||||
'status' => 'success',
|
||||
'code' => 200,
|
||||
'data' => $data,
|
||||
'status' => 'success',
|
||||
'code' => 200,
|
||||
'data' => $data,
|
||||
'total_commission' => $total_commission,
|
||||
'total_policies' => count($data)
|
||||
'total_policies' => count($data),
|
||||
], 200);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return $this->respond([
|
||||
'status' => 'failed',
|
||||
'code' => 500,
|
||||
'message'=> $e->getMessage()
|
||||
'message'=> $e->getMessage(),
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional filters for getCommissionRateList JSON: agent_id, from_date, to_date, pos_id.
|
||||
*/
|
||||
private function applyCommissionRateListPayloadFilters($query, array $input): void
|
||||
{
|
||||
if (! empty($input['agent_id'])) {
|
||||
if (is_array($input['agent_id'])) {
|
||||
$query->whereIn('pe.agent_id', $input['agent_id']);
|
||||
} else {
|
||||
$query->where('pe.agent_id', $input['agent_id']);
|
||||
}
|
||||
}
|
||||
if (! empty($input['from_date'])) {
|
||||
$query->where('partner_policy.issued_date >=', date('Y-m-d', strtotime((string) $input['from_date'])));
|
||||
}
|
||||
if (! empty($input['to_date'])) {
|
||||
$query->where('partner_policy.issued_date <=', date('Y-m-d', strtotime((string) $input['to_date'])));
|
||||
}
|
||||
if (! empty($input['pos_id'])) {
|
||||
$query->where('partner_policy.pos_id', $input['pos_id']);
|
||||
}
|
||||
}
|
||||
|
||||
public function getAgentUnusedCommissionList()
|
||||
{
|
||||
try {
|
||||
|
||||
$manager_id = $this->request->getGet('manager_id');
|
||||
// $broker_id = $this->request->getGet('broker_id');
|
||||
|
||||
// if (empty($broker_id)) {
|
||||
// return $this->respond([
|
||||
// 'status' => 'failed',
|
||||
// 'code' => 400,
|
||||
// 'message'=> 'broker_id is required'
|
||||
// ], 400);
|
||||
// }
|
||||
if ($manager_id === null || $manager_id === '') {
|
||||
return $this->respond([
|
||||
'status' => 'failed',
|
||||
'code' => 400,
|
||||
'message'=> 'manager_id is required',
|
||||
], 400);
|
||||
}
|
||||
|
||||
// Unused = policy has no active invoice line on an active invoice (same idea as commissionPayoutReport pending).
|
||||
// NOT EXISTS avoids false "invoiced" when only inactive items/invoices exist, and avoids join fan-out.
|
||||
$query = $this->PolicyModel
|
||||
->select([
|
||||
'pe.agent_id',
|
||||
'pa.name as agent_name',
|
||||
'MAX(pa.name) as agent_name',
|
||||
'MAX(pa.agent_code) as agent_code',
|
||||
'SUM(partner_policy.commission_amount) as unused_commission_amount',
|
||||
'COUNT(partner_policy.id) as total_policies'
|
||||
'COUNT(partner_policy.id) as total_policies',
|
||||
])
|
||||
// ->join(
|
||||
// 'partner_enquiry pe',
|
||||
// 'pe.id = partner_policy.enquiry_id AND pe.broker_id = ' . (int)$broker_id,
|
||||
// 'left'
|
||||
// )
|
||||
->join('partner_enquiry pe','pe.id = partner_policy.enquiry_id','left')
|
||||
->join('partner_enquiry pe', 'pe.id = partner_policy.enquiry_id', 'left')
|
||||
->join('partner_agent pa', 'pa.id = pe.agent_id', 'left')
|
||||
->join('partner_invoice_items pii', 'pii.policy_id = partner_policy.id', 'left')
|
||||
->join('partner_invoice pi', 'pi.id = pii.invoice_id AND pi.is_active = 1', 'left')
|
||||
->where('partner_policy.is_active', 1)
|
||||
->where('partner_policy.commission_amount >=', 0)
|
||||
// ->where('partner_policy.commission_amount >', 0)
|
||||
// ->where('partner_policy.is_data_accuracy_checked', 1)
|
||||
->where('partner_policy.manager_id', $manager_id)
|
||||
->where('pii.policy_id IS NULL') // ❗ unused commission
|
||||
->where('partner_policy.manager_id', (int) $manager_id)
|
||||
->where('partner_policy.policy_number IS NOT NULL', null, false)
|
||||
->where('pe.agent_id IS NOT NULL', null, false)
|
||||
->where(
|
||||
'NOT EXISTS (SELECT 1 FROM partner_invoice_items pii2 '
|
||||
. 'INNER JOIN partner_invoice pi2 ON pi2.id = pii2.invoice_id AND pi2.is_active = 1 '
|
||||
. 'WHERE pii2.policy_id = partner_policy.id AND pii2.is_active = 1)',
|
||||
null,
|
||||
false
|
||||
)
|
||||
->groupBy('pe.agent_id');
|
||||
|
||||
|
||||
|
||||
$data = $query->findAll();
|
||||
|
||||
// Grand total (optional but useful for FE)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user