1849 lines
58 KiB
PHP
1849 lines
58 KiB
PHP
<?php
|
|
defined('BASEPATH') OR exit('No direct script access allowed');
|
|
|
|
class Invoices extends CI_Controller {
|
|
|
|
|
|
// =========================
|
|
// 🔥 CONFIG AKUN
|
|
// =========================
|
|
private $akun_persediaan = 21;
|
|
private $akun_hpp = 63;
|
|
private $akun_piutang = 19;
|
|
private $pendapatan_dimuka = 78; // Pendapatan Diterima Dimuka
|
|
private $persediaan_dijual = 80; // Persediaan Dalam Penjualan
|
|
private $pendapatan_barang = 62;
|
|
|
|
|
|
public function __construct()
|
|
{
|
|
parent::__construct();
|
|
$this->load->database();
|
|
|
|
$this->load->model('DynamicModel', 'dm');
|
|
|
|
if (!$this->session->userdata('logged_in')) {
|
|
redirect('auth');
|
|
}
|
|
}
|
|
|
|
// =========================
|
|
// VIEW
|
|
// =========================
|
|
|
|
public function paid()
|
|
{
|
|
$data = ["active_menu" => "invoice_paid"];
|
|
$this->load->view('partials/header', $data);
|
|
$this->load->view('invoices/layout');
|
|
$this->load->view('partials/footer');
|
|
}
|
|
|
|
public function draft()
|
|
{
|
|
$data = ["active_menu" => "invoice_draft"];
|
|
$this->load->view('partials/header', $data);
|
|
$this->load->view('invoices/layout');
|
|
$this->load->view('partials/footer');
|
|
}
|
|
|
|
// =========================
|
|
// GET DATA (FILTER STATUS)
|
|
// =========================
|
|
|
|
public function get_data($status = null)
|
|
{
|
|
$this->db->select('
|
|
invoices.id,
|
|
invoices.customer_id,
|
|
invoices.no_invoice,
|
|
invoices.tanggal,
|
|
invoices.jatuh_tempo,
|
|
invoices.total,
|
|
invoices.total_bayar,
|
|
invoices.sisa_piutang,
|
|
invoices.status,
|
|
customers.nama
|
|
');
|
|
$this->db->from('invoices');
|
|
$this->db->join('customers','customers.id = invoices.customer_id');
|
|
|
|
// =========================
|
|
// FILTER STATUS
|
|
// =========================
|
|
if ($status) {
|
|
if($status == 'draft'){
|
|
$this->db->where_in('invoices.status', ['unpaid','partial','draft']);
|
|
} else {
|
|
$this->db->where('invoices.status', $status);
|
|
}
|
|
}
|
|
|
|
$this->db->where('invoices.deleted_at IS NULL', null, false);
|
|
$this->db->order_by('invoices.tanggal','DESC');
|
|
$this->db->order_by('customers.nama','ASC');
|
|
|
|
$data = $this->db->get()->result();
|
|
|
|
$result = [];
|
|
$no = 1;
|
|
|
|
foreach ($data as $row) {
|
|
|
|
// =========================
|
|
// CEK TUNGGAKAN SEBELUMNYA
|
|
// =========================
|
|
$this->db->select_sum('sisa_piutang');
|
|
$this->db->where('customer_id', $row->customer_id);
|
|
$this->db->where('status !=', 'paid');
|
|
$this->db->where('id <', $row->id);
|
|
|
|
$tunggakan = $this->db->get('invoices')->row()->sisa_piutang ?? 0;
|
|
|
|
// =========================
|
|
// STATUS BADGE
|
|
// =========================
|
|
switch ($row->status) {
|
|
|
|
case 'paid':
|
|
$status_badge = '<span class="badge bg-success px-3 py-1">PAID</span>';
|
|
break;
|
|
|
|
case 'partial':
|
|
$status_badge = '<span class="badge bg-warning px-3 py-1">PARTIAL</span>';
|
|
break;
|
|
|
|
case 'unpaid':
|
|
$status_badge = '<span class="badge bg-danger px-3 py-1">UNPAID</span>';
|
|
break;
|
|
|
|
default:
|
|
$status_badge = '<span class="badge bg-secondary px-3 py-1">DRAFT</span>';
|
|
}
|
|
|
|
// =========================
|
|
// AKSI
|
|
// =========================
|
|
$aksi = '';
|
|
|
|
// tombol detail
|
|
$aksi .= '<button class="btn btn-sm btn-secondary btn-detail mt-1 ml-1"
|
|
data-id="'.$row->id.'"
|
|
data-status="'.$row->status.'">
|
|
Detail
|
|
</button> ';
|
|
|
|
// =========================
|
|
// LOGIC BAYAR
|
|
// =========================
|
|
$bolehBayar = ($tunggakan <= 0);
|
|
|
|
// DRAFT
|
|
if($row->status == 'draft'){
|
|
|
|
if ($row->total > 0 && $bolehBayar) {
|
|
$aksi .= '<button class="btn btn-sm btn-success btn-bayar mt-1 ml-1"
|
|
data-id="'.$row->id.'"
|
|
data-total="'.$row->total.'"
|
|
data-terbayar="'.$row->total_bayar.'"
|
|
data-sisa="'.$row->sisa_piutang.'"
|
|
data-customer="'.$row->nama.'"
|
|
data-invoice="'.$row->no_invoice.'">
|
|
Bayar
|
|
</button> ';
|
|
}
|
|
|
|
$aksi .= '<button class="btn btn-sm btn-danger btn-delete mt-1 ml-1" data-id="'.$row->id.'">
|
|
Delete
|
|
</button>';
|
|
}
|
|
|
|
// UNPAID + PARTIAL
|
|
if(in_array($row->status, ['unpaid','partial'])){
|
|
|
|
if($bolehBayar){
|
|
$aksi .= '<button class="btn btn-sm btn-success btn-bayar mt-1 ml-1"
|
|
data-id="'.$row->id.'"
|
|
data-total="'.$row->total.'"
|
|
data-terbayar="'.$row->total_bayar.'"
|
|
data-sisa="'.$row->sisa_piutang.'"
|
|
data-customer="'.$row->nama.'"
|
|
data-invoice="'.$row->no_invoice.'">
|
|
Bayar
|
|
</button> ';
|
|
} else {
|
|
// indikator tunggakan
|
|
$aksi .= '<span class="badge bg-danger mt-1 ml-1">
|
|
Ada tunggakan
|
|
</span>';
|
|
}
|
|
}
|
|
|
|
// =========================
|
|
// FORMAT OUTPUT
|
|
// =========================
|
|
$result[] = [
|
|
$no++,
|
|
'<b>'.$row->no_invoice.'</b>',
|
|
tanggal_indo($row->tanggal),
|
|
tanggal_indo($row->jatuh_tempo),
|
|
$row->nama,
|
|
'<span class="text-dark fw-bold">Rp '.number_format($row->total,0,',','.').'</span>',
|
|
'<span class="text-success">Rp '.number_format($row->total_bayar,0,',','.').'</span>',
|
|
'<span class="text-danger fw-bold">Rp '.number_format($row->sisa_piutang,0,',','.').'</span>',
|
|
$status_badge,
|
|
$aksi
|
|
];
|
|
}
|
|
|
|
echo json_encode(["data"=>$result]);
|
|
}
|
|
|
|
// =========================
|
|
// GET CUSTOMER
|
|
// =========================
|
|
public function get_customers()
|
|
{
|
|
echo json_encode($this->db->get('customers')->result());
|
|
}
|
|
|
|
// =========================
|
|
// SAVE INVOICE
|
|
// =========================
|
|
public function save()
|
|
{
|
|
$this->output->set_content_type('application/json');
|
|
|
|
$customer_id = $this->input->post('customer_id');
|
|
$tanggal = $this->input->post('tanggal');
|
|
$jatuh_tempo = $this->input->post('jatuh_tempo');
|
|
|
|
// =========================
|
|
// VALIDASI WAJIB FIELD
|
|
// =========================
|
|
if (empty($customer_id) || empty($tanggal) || empty($jatuh_tempo)) {
|
|
echo json_encode([
|
|
'status' => false,
|
|
'message' => 'Customer, tanggal, dan jatuh tempo wajib diisi'
|
|
]);
|
|
return;
|
|
}
|
|
|
|
// =========================
|
|
// NORMALISASI TANGGAL
|
|
// =========================
|
|
$today = date('Y-m-d');
|
|
$tanggal = date('Y-m-d', strtotime($tanggal));
|
|
$jatuh_tempo = date('Y-m-d', strtotime($jatuh_tempo));
|
|
|
|
|
|
// ❌ jatuh tempo tidak boleh kurang dari tanggal invoice
|
|
if ($jatuh_tempo < $tanggal) {
|
|
echo json_encode([
|
|
'status' => false,
|
|
'message' => 'Jatuh tempo tidak boleh lebih kecil dari tanggal invoice'
|
|
]);
|
|
return;
|
|
}
|
|
|
|
// =========================
|
|
// GENERATE NO INVOICE
|
|
// =========================
|
|
$tanggal_now = date('Ymd');
|
|
|
|
$this->db->like('no_invoice', 'INV-'.$tanggal_now, 'after');
|
|
$this->db->from('invoices');
|
|
$count = $this->db->count_all_results();
|
|
|
|
$urutan = str_pad($count + 1, 4, '0', STR_PAD_LEFT);
|
|
|
|
$no_invoice = 'INV-' . $tanggal_now . '-' . $urutan;
|
|
|
|
// =========================
|
|
// HANDEL A<BIL CICILAN JIKA ADA
|
|
// =========================
|
|
$cicilan_raw = $this->db->query("
|
|
SELECT *
|
|
FROM invoice_installments
|
|
WHERE customer_id = ?
|
|
AND status IN ('unpaid','partial')
|
|
AND (nominal - paid_amount) > 0
|
|
AND is_billed = 0
|
|
ORDER BY invoice_detail_id, angsuran_ke ASC
|
|
", [$customer_id])->result();
|
|
|
|
$cicilan = [];
|
|
$group = [];
|
|
|
|
foreach ($cicilan_raw as $c) {
|
|
$group[$c->invoice_detail_id][] = $c;
|
|
}
|
|
|
|
foreach ($group as $detail_id => $rows) {
|
|
|
|
$picked_status = [];
|
|
$min_row = null;
|
|
|
|
foreach ($rows as $r) {
|
|
|
|
// ambil angsuran terkecil
|
|
if ($min_row === null) {
|
|
$min_row = $r;
|
|
$picked_status[$r->status] = true;
|
|
$cicilan[] = $r;
|
|
continue;
|
|
}
|
|
|
|
// ambil jika status beda dan belum pernah diambil
|
|
if (!isset($picked_status[$r->status])) {
|
|
$picked_status[$r->status] = true;
|
|
$cicilan[] = $r;
|
|
}
|
|
}
|
|
}
|
|
|
|
// =========================
|
|
// TRANSACTION
|
|
// =========================
|
|
$this->db->trans_start();
|
|
|
|
$this->db->insert('invoices', [
|
|
'no_invoice' => $no_invoice,
|
|
'customer_id' => $customer_id,
|
|
'tanggal' => $tanggal,
|
|
'jatuh_tempo' => $jatuh_tempo,
|
|
'total' => 0,
|
|
'status' => 'draft'
|
|
]);
|
|
|
|
$invoice_id = $this->db->insert_id();
|
|
|
|
$total_invoice = 0;
|
|
|
|
foreach ($cicilan as $c) {
|
|
|
|
// ambil data item asli
|
|
$inv_items = $this->db
|
|
->select('account_id, items_id, nama_item, keterangan, subtotal_asli, is_cicilan')
|
|
->where('id', $c->invoice_detail_id)
|
|
->get('invoice_details')
|
|
->row();
|
|
|
|
if(!$inv_items) continue;
|
|
|
|
// =========================
|
|
// FORMAT NAMA
|
|
// =========================
|
|
if($c->paid_amount > 0){
|
|
$priodecicilan = '( Sisa Cicilan ke ' . $c->angsuran_ke . '/' . $c->total_angsuran . ' )';
|
|
}else{
|
|
$priodecicilan = '( Cicilan ke ' . $c->angsuran_ke . '/' . $c->total_angsuran . ' )';
|
|
}
|
|
|
|
$nama_item = preg_replace(
|
|
'/\s*\(\s*Cicilan.*$/i',
|
|
'',
|
|
$inv_items->nama_item
|
|
) . ' ' . $priodecicilan;
|
|
|
|
// =========================
|
|
// NOMINAL
|
|
// =========================
|
|
$harga_cicilan = max(0, $c->nominal - $c->paid_amount);
|
|
if($harga_cicilan <= 0) continue;
|
|
|
|
// =========================
|
|
// INSERT DETAIL
|
|
// =========================
|
|
$insert_data = [
|
|
'invoice_id' => $invoice_id,
|
|
'tanggal' => $tanggal,
|
|
'account_id' => $inv_items->account_id,
|
|
'items_id' => $inv_items->items_id ?: null,
|
|
'nama_item' => $nama_item,
|
|
'qty' => 1,
|
|
'harga' => $harga_cicilan,
|
|
'subtotal' => $harga_cicilan,
|
|
'warehouse_id' => null,
|
|
'keterangan' => $inv_items->keterangan,
|
|
'subtotal_asli' => $inv_items->subtotal_asli,
|
|
'is_cicilan' => $inv_items->is_cicilan
|
|
];
|
|
|
|
$this->db->insert('invoice_details', $insert_data);
|
|
|
|
// =========================
|
|
// AKUMULASI TOTAL
|
|
// =========================
|
|
$total_invoice += $harga_cicilan;
|
|
|
|
// =========================
|
|
// MARK SUDAH DITAGIHKAN
|
|
// =========================
|
|
$this->db->where('id', $c->id)->update('invoice_installments', [
|
|
'is_billed' => 1
|
|
]);
|
|
}
|
|
|
|
// =========================
|
|
// UPDATE TOTAL INVOICE
|
|
// =========================
|
|
$this->update_total($invoice_id);
|
|
|
|
$this->db->trans_complete();
|
|
|
|
// =========================
|
|
// RESPONSE
|
|
// =========================
|
|
if (!$this->db->trans_status()) {
|
|
echo json_encode([
|
|
'status' => false,
|
|
'message' => 'Gagal membuat invoice'
|
|
]);
|
|
return;
|
|
}
|
|
|
|
log_activity(
|
|
'invoice',
|
|
'create',
|
|
'Membuat invoice ' . $no_invoice . ' untuk customer ID ' . $customer_id,
|
|
'success'
|
|
);
|
|
|
|
echo json_encode([
|
|
'status' => true,
|
|
'invoice_id' => $invoice_id,
|
|
'no_invoice' => $no_invoice
|
|
]);
|
|
}
|
|
|
|
// =========================
|
|
// DELETE
|
|
// =========================
|
|
public function delete($id)
|
|
{
|
|
// Ambil invoice
|
|
$invoice = $this->db->get_where('invoices', ['id' => $id])->row();
|
|
|
|
if (!$invoice) {
|
|
echo json_encode(['status'=>false,'message'=>'Invoice tidak ditemukan']);
|
|
return;
|
|
}
|
|
|
|
// ❌ Cek detail
|
|
$cekDetail = $this->db->where('invoice_id', $id)
|
|
->count_all_results('invoice_details');
|
|
|
|
if ($cekDetail > 0) {
|
|
echo json_encode([
|
|
'status'=>false,
|
|
'message'=>'Invoice tidak bisa dihapus karena masih memiliki detail'
|
|
]);
|
|
return;
|
|
}
|
|
|
|
if ($invoice->posted == 1) {
|
|
echo json_encode([
|
|
'status'=>false,
|
|
'message'=>'Invoice sudah diposting, tidak bisa dihapus'
|
|
]);
|
|
return;
|
|
}
|
|
|
|
// ❌ Cek sudah dibayar
|
|
if (in_array($invoice->status, ['paid','partial'])) {
|
|
echo json_encode([
|
|
'status'=>false,
|
|
'message'=>'Invoice sudah ada pembayaran, tidak bisa dihapus'
|
|
]);
|
|
return;
|
|
}
|
|
|
|
// ✅ Hapus
|
|
$this->db->delete('invoices', ['id'=>$id]);
|
|
|
|
log_activity(
|
|
'invoice',
|
|
'delete',
|
|
'Menghapus invoice ID ' . $id . ' - ' . $invoice->no_invoice,
|
|
'success'
|
|
);
|
|
|
|
echo json_encode(['status'=>true]);
|
|
}
|
|
|
|
|
|
public function detail($id)
|
|
{
|
|
// GET customer_id
|
|
$row = $this->db->select('customer_id')
|
|
->where('id',$id)
|
|
->get('invoices')
|
|
->row();
|
|
|
|
$data['active_menu'] = "invoice_draft";
|
|
$data['invoice_id'] = $id;
|
|
$data['customer_id'] = $row ? $row->customer_id : null;
|
|
|
|
$this->load->view('partials/header', $data);
|
|
$this->load->view('invoices/detail', $data);
|
|
$this->load->view('partials/footer');
|
|
}
|
|
|
|
|
|
public function get_invoice($id)
|
|
{
|
|
// ================= AMBIL INVOICE =================
|
|
$this->db->select('invoices.*, customers.nama, customers.alamat, customers.telp, customers.email');
|
|
$this->db->from('invoices');
|
|
$this->db->join('customers', 'customers.id = invoices.customer_id');
|
|
$this->db->where('invoices.id', $id);
|
|
|
|
$data = $this->db->get()->row();
|
|
|
|
if(!$data){
|
|
echo json_encode(null);
|
|
return;
|
|
}
|
|
|
|
// ================= AMBIL TUNGGAKAN =================
|
|
$this->db->select_sum('sisa_piutang');
|
|
$this->db->where('customer_id', $data->customer_id);
|
|
$this->db->where('status !=', 'paid');
|
|
$this->db->where('id !=', $id); // exclude invoice sekarang
|
|
|
|
$tunggakan = $this->db->get('invoices')->row()->sisa_piutang ?? 0;
|
|
|
|
// ================= MASUKKAN KE DATA =================
|
|
$data->tunggakan = (int)$tunggakan;
|
|
|
|
// ================= OUTPUT =================
|
|
echo json_encode($data);
|
|
}
|
|
|
|
// =========================
|
|
// GET ITEMS
|
|
// =========================
|
|
public function get_items($id)
|
|
{
|
|
$this->db->select('invoice_details.*, accounts.kode_akun, accounts.nama_akun, invoices.status');
|
|
$this->db->from('invoice_details');
|
|
$this->db->join('accounts', 'accounts.id = invoice_details.account_id', 'left');
|
|
$this->db->join('invoices', 'invoices.id = invoice_details.invoice_id', 'left');
|
|
$this->db->where('invoice_details.invoice_id', $id);
|
|
$this->db->order_by('tanggal','ASC');
|
|
$this->db->order_by('id','ASC');
|
|
|
|
$data = $this->db->get()->result();
|
|
|
|
echo json_encode($data);
|
|
}
|
|
|
|
// =========================
|
|
// ADD ITEM - FIXED RESPONSE
|
|
// =========================
|
|
public function add_item()
|
|
{
|
|
$this->db->trans_begin();
|
|
|
|
$invoice_id = $this->input->post('invoice_id');
|
|
$tanggal = $this->input->post('tanggal');
|
|
$item_id = $this->input->post('item_id');
|
|
$nama_item = $this->input->post('nama_item');
|
|
$qty = (float)$this->input->post('qty');
|
|
$harga = (float)$this->input->post('harga');
|
|
$account_id = $this->input->post('account_id');
|
|
$warehouse_id = $this->input->post('warehouse_id');
|
|
|
|
$total_harga = $qty * $harga;
|
|
$total_hpp = 0;
|
|
|
|
// jika di bayar cicilan
|
|
$is_cicilan = $this->input->post('is_cicilan') ?? null;
|
|
$tenor = (int)$this->input->post('tenor') ?? 0;
|
|
|
|
// =========================
|
|
// VALIDASI & AMBIL ITEM
|
|
// =========================
|
|
if($item_id){
|
|
$item = $this->db->get_where('items', ['id'=>$item_id])->row();
|
|
if(!$item){
|
|
echo json_encode(['status'=>false, 'message'=>'Item tidak ditemukan']);
|
|
$this->db->trans_rollback();
|
|
return;
|
|
}
|
|
|
|
$nama_item = $item->nama_barang;
|
|
|
|
// cek stok
|
|
$stok = $this->get_stok($item_id);
|
|
if($stok < $qty){
|
|
echo json_encode(['status'=>false, 'message'=>'Stok tidak cukup. Sisa: '.$stok]);
|
|
$this->db->trans_rollback();
|
|
return;
|
|
}
|
|
|
|
$total_hpp = $qty * $item->harga_beli;
|
|
}
|
|
|
|
// =========================
|
|
// HANDLE CICILAN
|
|
// =========================
|
|
$harga_simpan = $harga;
|
|
$subtotal_simpan= $total_harga;
|
|
$harga_asli = $total_harga;
|
|
|
|
$ket_cicilan_inv = '';
|
|
$ket_cicilan_jr = '';
|
|
|
|
if ($is_cicilan && $tenor > 0) {
|
|
|
|
// harga per cicilan
|
|
$harga_cicilan = round($total_harga / $tenor);
|
|
|
|
$harga_simpan = $harga_cicilan;
|
|
$subtotal_simpan = $harga_cicilan;
|
|
|
|
$ket_cicilan_inv = ' ( Cicilan ke 1/' . $tenor . ' )';
|
|
$ket_cicilan_jr = ' ( Cicilan ' . $tenor . ' Periode )';
|
|
}
|
|
|
|
// =========================
|
|
// INSERT DETAIL
|
|
// =========================
|
|
$insert_data = [
|
|
'invoice_id' => $invoice_id,
|
|
'tanggal' => $tanggal,
|
|
'account_id' => $account_id,
|
|
'items_id' => $item_id ?: null,
|
|
'nama_item' => $nama_item . $ket_cicilan_inv,
|
|
'qty' => $qty,
|
|
'harga' => $harga_simpan,
|
|
'subtotal' => $subtotal_simpan,
|
|
'warehouse_id' => $warehouse_id,
|
|
'keterangan' => $this->input->post('keterangan'),
|
|
'subtotal_asli' => $harga_asli,
|
|
'is_cicilan' => $is_cicilan ? 1 : 0,
|
|
'total_hpp_barang' => $total_hpp
|
|
];
|
|
|
|
$this->db->insert('invoice_details', $insert_data);
|
|
$detail_id = $this->db->insert_id();
|
|
|
|
// =========================
|
|
// JURNAL + STOK
|
|
// =========================
|
|
if($this->input->post('account_id') != 70) {
|
|
$this->generate_jurnal_item($detail_id, $ket_cicilan_jr);
|
|
}
|
|
|
|
$this->update_total($invoice_id);
|
|
|
|
// ambil customer dari invoice
|
|
$invoice = $this->db->get_where('invoices', ['id' => $invoice_id])->row();
|
|
|
|
if ($is_cicilan && $tenor > 0) {
|
|
|
|
// generate cicilan
|
|
$this->generate_installments(
|
|
$invoice_id,
|
|
$detail_id,
|
|
$invoice->customer_id,
|
|
$total_harga,
|
|
$tenor,
|
|
$tanggal,
|
|
$account_id,
|
|
$item_id ?: null
|
|
);
|
|
}
|
|
|
|
// ✅ RESPONSE KONSISTEN DENGAN JS
|
|
if ($this->db->trans_status() === FALSE){
|
|
$this->db->trans_rollback();
|
|
echo json_encode(['status'=>false, 'message'=>'Transaksi gagal']);
|
|
} else {
|
|
$this->db->trans_commit();
|
|
|
|
log_activity(
|
|
'invoice',
|
|
'add_item',
|
|
'Tambah item invoice ID ' . $invoice_id . ' - ' . $nama_item,
|
|
'success'
|
|
);
|
|
|
|
echo json_encode(['status'=>true, 'message'=>'Item berhasil ditambahkan']);
|
|
}
|
|
}
|
|
|
|
// =========================
|
|
// UPDATE ITEM - RESPONSE KONSISTEN
|
|
// =========================
|
|
public function update_item()
|
|
{
|
|
$this->db->trans_begin();
|
|
|
|
$id = $this->input->post('id');
|
|
$old = $this->db->get_where('invoice_details', ['id'=>$id])->row();
|
|
|
|
if(!$old){
|
|
echo json_encode(['status'=>false, 'message'=>'Item tidak ditemukan']);
|
|
return;
|
|
}
|
|
|
|
$cek = $this->db->get_where('invoice_installments', [
|
|
'invoice_detail_id' => $id
|
|
])->num_rows();
|
|
|
|
if ($cek > 0) {
|
|
echo json_encode([
|
|
'status' => false,
|
|
'message' => 'Item cicilan tidak bisa diubah. Hapus dan buat ulang.'
|
|
]);
|
|
return;
|
|
}
|
|
|
|
// 🔥 REVERSE DATA LAMA
|
|
$this->delete_jurnal_item($id);
|
|
$this->delete_stok_item($id);
|
|
|
|
// DATA BARU
|
|
$item_id = $this->input->post('item_id');
|
|
$nama_item = $this->input->post('nama_item');
|
|
$tanggal = $this->input->post('tanggal');
|
|
|
|
$total_hpp = 0;
|
|
|
|
if($item_id){
|
|
$item = $this->db->get_where('items', ['id'=>$item_id])->row();
|
|
if($item){
|
|
$nama_item = $item->nama_barang;
|
|
$stok = $this->get_stok($item_id);
|
|
$qty = (float)$this->input->post('qty');
|
|
$total_hpp = $this->input->post('qty') * $item->harga_beli;
|
|
if($stok < $qty){
|
|
echo json_encode(['status'=>false, 'message'=>'Stok tidak cukup']);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
$qty = (float)$this->input->post('qty');
|
|
$harga = (float)$this->input->post('harga');
|
|
|
|
// UPDATE
|
|
$update_data = [
|
|
'tanggal' => $tanggal,
|
|
'account_id' => $this->input->post('account_id'),
|
|
'items_id' => $item_id ?: null,
|
|
'nama_item' => $nama_item,
|
|
'qty' => $qty,
|
|
'harga' => $harga,
|
|
'subtotal' => $qty * $harga,
|
|
'warehouse_id' => $this->input->post('warehouse_id'),
|
|
'keterangan' => $this->input->post('keterangan'),
|
|
'total_hpp_barang' => $total_hpp
|
|
];
|
|
|
|
$this->db->where('id', $id)->update('invoice_details', $update_data);
|
|
|
|
// GENERATE ULANG
|
|
if($this->input->post('account_id') != 70) {
|
|
$this->generate_jurnal_item($id);
|
|
}
|
|
$this->update_total($old->invoice_id);
|
|
|
|
// ✅ RESPONSE KONSISTEN DENGAN JS
|
|
if ($this->db->trans_status() === FALSE){
|
|
$this->db->trans_rollback();
|
|
echo json_encode(['status'=>false, 'message'=>'Update gagal']);
|
|
} else {
|
|
$this->db->trans_commit();
|
|
|
|
log_activity(
|
|
'invoice',
|
|
'update_item',
|
|
'Update item invoice detail ID ' . $id,
|
|
'success'
|
|
);
|
|
|
|
echo json_encode(['status'=>true, 'message'=>'Item berhasil diupdate']);
|
|
}
|
|
}
|
|
|
|
// =========================
|
|
// DELETE ITEM - RESPONSE KONSISTEN
|
|
// =========================
|
|
public function delete_item($id)
|
|
{
|
|
$this->db->trans_begin();
|
|
|
|
$d = $this->db->get_where('invoice_details', ['id'=>$id])->row();
|
|
|
|
if(!$d){
|
|
echo json_encode(['status'=>false, 'message'=>'Item tidak ditemukan']);
|
|
return;
|
|
}
|
|
|
|
if($d->is_cicilan == 1) {
|
|
$this->delete_invoice_installments($id, $d->source_cicilan_id);
|
|
}
|
|
// reverse stok & hapus
|
|
$this->delete_jurnal_item($id);
|
|
$this->delete_stok_item($id);
|
|
$this->db->delete('invoice_details', ['id'=>$id]);
|
|
$this->update_total($d->invoice_id);
|
|
|
|
// ✅ RESPONSE KONSISTEN DENGAN JS
|
|
if ($this->db->trans_status() === FALSE){
|
|
$this->db->trans_rollback();
|
|
echo json_encode(['status'=>false, 'message'=>'Hapus gagal']);
|
|
} else {
|
|
$this->db->trans_commit();
|
|
|
|
log_activity(
|
|
'invoice',
|
|
'delete_item',
|
|
'Hapus item invoice detail ID ' . $id,
|
|
'success'
|
|
);
|
|
|
|
echo json_encode(['status'=>true, 'message'=>'Item berhasil dihapus']);
|
|
}
|
|
}
|
|
|
|
// =========================
|
|
// AMBIL DATA CICILAN
|
|
// =========================
|
|
public function get_cicilan($id)
|
|
{
|
|
$cicilan_raw = $this->db->query("
|
|
SELECT
|
|
ii.*,
|
|
it.nama_barang
|
|
FROM invoice_installments ii
|
|
LEFT JOIN items it ON ii.item_id = it.id
|
|
WHERE ii.customer_id = ?
|
|
AND ii.status IN ('unpaid','partial')
|
|
AND (ii.nominal - ii.paid_amount) > 0
|
|
AND ii.is_billed = 0
|
|
ORDER BY ii.invoice_detail_id, ii.angsuran_ke ASC
|
|
", [$id])->result();
|
|
|
|
echo json_encode($cicilan_raw);
|
|
}
|
|
|
|
private function generate_jurnal_item($id, $ket_cicilan_jr = null)
|
|
{
|
|
// $d = $this->db->get_where('invoice_details', ['id'=>$id])->row();
|
|
|
|
// if(!$d) return;
|
|
|
|
$d = $this->db
|
|
->select('
|
|
invoice_details.*,
|
|
customers.nama,
|
|
invoices.no_invoice
|
|
')
|
|
->from('invoice_details')
|
|
->join('invoices', 'invoices.id = invoice_details.invoice_id')
|
|
->join('customers', 'customers.id = invoices.customer_id')
|
|
->where('invoice_details.id', $id)
|
|
->get()
|
|
->row();
|
|
|
|
if(!$d) return;
|
|
|
|
$total_hpp = 0;
|
|
|
|
// =========================
|
|
// 🔥 TENTUKAN NILAI JURNAL
|
|
// =========================
|
|
$nilai_jurnal = $d->subtotal;
|
|
|
|
if (isset($d->is_cicilan) && $d->is_cicilan == 1) {
|
|
$nilai_jurnal = $d->subtotal_asli ?? $d->subtotal;
|
|
}
|
|
|
|
// =========================
|
|
// 🔥 STOK & HPP
|
|
// =========================
|
|
if($d->items_id){
|
|
|
|
$item = $this->db->get_where('items',['id'=>$d->items_id])->row();
|
|
|
|
if($item){
|
|
$total_hpp = $item->harga_beli * $d->qty;
|
|
}
|
|
|
|
// stok keluar
|
|
$this->db->insert('stock_logs', [
|
|
'item_id' => $d->items_id,
|
|
'qty' => $d->qty,
|
|
'tipe' => 'keluar',
|
|
'ref_type' => 'invoice_details',
|
|
'ref_id' => $id,
|
|
'warehouse_id' => $d->warehouse_id,
|
|
'keterangan' => 'Penjualan ' . $item->nama_barang . ' kepada ' . $d->nama . ' - Invoice ' . $d->no_invoice . ' - Tgl ' . $d->tanggal
|
|
]);
|
|
|
|
$this->update_item_stok($d->items_id);
|
|
}
|
|
|
|
// =========================
|
|
// 🔥 generate no_ref
|
|
// =========================
|
|
$no_ref = $this->generate_no_ref();
|
|
|
|
// =========================
|
|
// 🔥 JOURNAL HEADER
|
|
// =========================
|
|
$nama_item_jr = preg_replace('/\s*\(\s*Cicilan.*$/i', '', $d->nama_item) . $ket_cicilan_jr;
|
|
$this->db->insert('journals', [
|
|
'tanggal' => $d->tanggal,
|
|
'no_ref' => $no_ref,
|
|
'keterangan' => 'Penjualan '.$nama_item_jr,
|
|
'ref_type' => 'invoice_details',
|
|
'ref_id' => $id,
|
|
'created_by' => $this->session->userdata('user_id')
|
|
]);
|
|
|
|
$jid = $this->db->insert_id();
|
|
|
|
// =========================
|
|
// 🔥 PIUTANG (DEBIT)
|
|
// =========================
|
|
$this->db->insert('journal_details', [
|
|
'journal_id' => $jid,
|
|
'account_id' => $this->akun_piutang,
|
|
'debit' => $nilai_jurnal,
|
|
'kredit' => 0
|
|
]);
|
|
|
|
// =========================
|
|
// 🔥 PENDAPATAN (KREDIT) Pendapatan Diterima Dimuka
|
|
// =========================
|
|
$this->db->insert('journal_details', [
|
|
'journal_id' => $jid,
|
|
'account_id' => $this->pendapatan_dimuka,
|
|
'debit' => 0,
|
|
'kredit' => $nilai_jurnal
|
|
]);
|
|
|
|
// =========================
|
|
// 🔥 HPP & PERSEDIAAN
|
|
// =========================
|
|
if($total_hpp > 0){
|
|
|
|
// HPP (debit)
|
|
$this->db->insert('journal_details', [
|
|
'journal_id' => $jid,
|
|
'account_id' => $this->persediaan_dijual,
|
|
'debit' => $total_hpp,
|
|
'kredit' => 0
|
|
]);
|
|
|
|
// Persediaan (kredit)
|
|
$this->db->insert('journal_details', [
|
|
'journal_id' => $jid,
|
|
'account_id' => $this->akun_persediaan,
|
|
'debit' => 0,
|
|
'kredit' => $total_hpp
|
|
]);
|
|
}
|
|
}
|
|
|
|
// =========================
|
|
// 🔥 DELETE JURNAL
|
|
// =========================
|
|
private function delete_jurnal_item($id)
|
|
{
|
|
$d = $this->db->get_where('invoice_details', ['id'=>$id])->row();
|
|
|
|
$j = $this->db->get_where('journals', [
|
|
'ref_type'=>'invoice_details',
|
|
'ref_id'=>$id
|
|
])->result();
|
|
|
|
foreach($j as $row){
|
|
$this->db->delete('journal_details', ['journal_id'=>$row->id]);
|
|
$this->db->delete('journals', ['id'=>$row->id]);
|
|
}
|
|
|
|
if($d && $d->items_id){
|
|
$this->update_item_stok($d->items_id); // ✅ BENAR
|
|
}
|
|
}
|
|
|
|
// =========================
|
|
// 🔥 DELETE STOK
|
|
// =========================
|
|
private function delete_stok_item($id)
|
|
{
|
|
$d = $this->db->get_where('invoice_details', ['id'=>$id])->row();
|
|
|
|
$this->db->delete('stock_logs', [
|
|
'ref_type'=>'invoice_details',
|
|
'ref_id'=>$id
|
|
]);
|
|
|
|
if($d && $d->items_id){
|
|
$this->update_item_stok($d->items_id); // ✅ BENAR
|
|
}
|
|
}
|
|
|
|
// =========================
|
|
// 🔥 DELETE INVOICES INSTALLMENT
|
|
// =========================
|
|
private function delete_invoice_installments($invoice_detail_id,$source_cicilan_id)
|
|
{
|
|
$cek = $this->db
|
|
->where_in('status', ['paid', 'partial'])
|
|
->where('invoice_detail_id', $invoice_detail_id)
|
|
->count_all_results('invoice_installments');
|
|
|
|
// kalau TIDAK ADA paid/partial → hapus
|
|
if ($cek === 0) {
|
|
$this->db->delete('invoice_installments', [
|
|
'invoice_detail_id' => $invoice_detail_id
|
|
]);
|
|
}
|
|
|
|
if ($source_cicilan_id) {
|
|
$this->db->update('invoice_installments', [
|
|
'is_billed' => 0
|
|
], ['id' => $source_cicilan_id]);
|
|
}
|
|
}
|
|
|
|
// =========================
|
|
// 🔥 GET STOK
|
|
// =========================
|
|
private function get_stok($item_id)
|
|
{
|
|
return $this->db->query("
|
|
SELECT COALESCE(SUM(qty * IF(tipe='masuk',1,-1)),0) as stok
|
|
FROM stock_logs
|
|
WHERE item_id = ?
|
|
", [$item_id])->row()->stok;
|
|
}
|
|
|
|
// =========================
|
|
// ADD ITEM - FIXED HELPER
|
|
// =========================
|
|
private function commit()
|
|
{
|
|
if ($this->db->trans_status() === FALSE){
|
|
$this->db->trans_rollback();
|
|
echo json_encode(['status'=>false, 'message'=>'Transaksi gagal']);
|
|
} else {
|
|
$this->db->trans_commit();
|
|
echo json_encode(['status'=>true, 'message'=>'Berhasil']);
|
|
}
|
|
}
|
|
|
|
private function rollback($message)
|
|
{
|
|
$this->db->trans_rollback();
|
|
echo json_encode(['status'=>false, 'message'=>$message]);
|
|
}
|
|
|
|
// =========================
|
|
// UPDATE TOTAL
|
|
// =========================
|
|
private function update_total($invoice_id)
|
|
{
|
|
$this->db->select_sum('subtotal', 'total');
|
|
$this->db->where('invoice_id', $invoice_id);
|
|
$total = $this->db->get('invoice_details')->row()->total ?? 0;
|
|
|
|
$this->db->where('id', $invoice_id)
|
|
->update('invoices', ['total' => $total]);
|
|
}
|
|
|
|
// =========================
|
|
// UPDATE TOTAL
|
|
// =========================
|
|
private function generate_no_ref()
|
|
{
|
|
$tanggal = date('Y-m-d');
|
|
$prefix = 'INV-'.date('Ymd').'-';
|
|
|
|
// ambil nomor terakhir hari ini
|
|
$last = $this->db->query("
|
|
SELECT no_ref
|
|
FROM journals
|
|
WHERE DATE(tanggal) = ?
|
|
AND no_ref LIKE ?
|
|
ORDER BY no_ref DESC
|
|
LIMIT 1
|
|
", [$tanggal, $prefix.'%'])->row();
|
|
|
|
if($last){
|
|
$last_number = (int) substr($last->no_ref, -4);
|
|
$next = $last_number + 1;
|
|
} else {
|
|
$next = 1;
|
|
}
|
|
|
|
return $prefix . str_pad($next, 4, '0', STR_PAD_LEFT);
|
|
}
|
|
|
|
// =========================
|
|
// GET ACCOUNTS
|
|
// =========================
|
|
public function get_accounts()
|
|
{
|
|
$data = $this->db
|
|
->group_start()
|
|
->where('tipe', 'revenue')
|
|
->or_where('id', 70)
|
|
->group_end()
|
|
->order_by('kode_akun', 'ASC')
|
|
->get('accounts')
|
|
->result();
|
|
|
|
echo json_encode($data);
|
|
}
|
|
|
|
public function get_accounts_assets()
|
|
{
|
|
$data = $this->db
|
|
->where_in('kode_akun', [101,102,207,201,202,203,204,205,209])
|
|
->order_by('kode_akun', 'ASC')
|
|
->get('accounts')
|
|
->result();
|
|
|
|
echo json_encode($data);
|
|
}
|
|
|
|
public function check_items_by_account($account_id)
|
|
{
|
|
$exists = $this->db
|
|
->where('account_id', $account_id)
|
|
->limit(1)
|
|
->get('items')
|
|
->num_rows() > 0;
|
|
|
|
echo json_encode([
|
|
'has_item' => $exists
|
|
]);
|
|
}
|
|
|
|
public function get_items_by_wh_account($warehouse_id, $account_id)
|
|
{
|
|
$this->db->select('
|
|
items.id,
|
|
items.nama_barang,
|
|
items.harga_jual,
|
|
items.kode_detail,
|
|
COALESCE(SUM(
|
|
stock_logs.qty * IF(stock_logs.tipe="masuk",1,-1)
|
|
),0) as stok
|
|
');
|
|
|
|
$this->db->where('status', 'active');
|
|
$this->db->from('items');
|
|
|
|
$this->db->join('stock_logs',
|
|
'stock_logs.item_id = items.id
|
|
AND stock_logs.warehouse_id = '.$this->db->escape($warehouse_id),
|
|
'left');
|
|
|
|
$this->db->where('items.account_id', $account_id);
|
|
|
|
$this->db->group_by('items.id');
|
|
|
|
// ✅ INI KUNCINYA
|
|
$this->db->having('stok >', 0);
|
|
|
|
|
|
$data = $this->db->get()->result();
|
|
|
|
echo json_encode($data);
|
|
}
|
|
|
|
public function gudang_list()
|
|
{
|
|
echo json_encode(
|
|
$this->db->get('warehouses')->result()
|
|
);
|
|
}
|
|
|
|
public function get_payment($invoice_id)
|
|
{
|
|
$this->db->select('
|
|
id,
|
|
tanggal,
|
|
jumlah,
|
|
metode,
|
|
created_at
|
|
');
|
|
$this->db->from('payments');
|
|
$this->db->where('invoice_id', $invoice_id);
|
|
$this->db->order_by('id','DESC');
|
|
|
|
$data = $this->db->get()->result();
|
|
|
|
$result = [];
|
|
|
|
$this->db->from('invoices');
|
|
$this->db->where('invoices.id', $invoice_id);
|
|
|
|
$invoice = $this->db->get()->row();
|
|
|
|
foreach ($data as $row) {
|
|
$result[] = [
|
|
'id' => $row->id,
|
|
'tanggal' => date('d-m-Y', strtotime($row->tanggal)),
|
|
'jumlah' => 'Rp '.number_format($row->jumlah,0,',','.'),
|
|
'metode' => $row->metode,
|
|
'created_at' => date('d-m-Y H:i', strtotime($row->created_at))
|
|
];
|
|
}
|
|
|
|
echo json_encode([
|
|
'data' => $result,
|
|
'invoice' => $invoice
|
|
]);
|
|
}
|
|
|
|
// Update Stok Cache
|
|
private function update_item_stok($item_id)
|
|
{
|
|
$stok = $this->db->query("
|
|
SELECT COALESCE(SUM(qty * IF(tipe='masuk',1,-1)),0) as stok
|
|
FROM stock_logs
|
|
WHERE item_id = ?
|
|
", [$item_id])->row()->stok;
|
|
|
|
$this->db->where('id', $item_id)
|
|
->update('items', [
|
|
'stok' => $stok
|
|
]);
|
|
}
|
|
|
|
|
|
private function generate_installments($invoice_id, $detail_id, $customer_id, $total, $tenor, $start_date, $account_id, $item_id = null)
|
|
{
|
|
$nominal = round($total / $tenor);
|
|
$sisa = $total - ($nominal * $tenor);
|
|
|
|
for ($i = 1; $i <= $tenor; $i++) {
|
|
|
|
$tgl = date('Y-m-d', strtotime("+".($i-1)." month", strtotime($start_date)));
|
|
|
|
$nilai = $nominal;
|
|
|
|
if ($i == $tenor) {
|
|
$nilai += $sisa;
|
|
}
|
|
|
|
$is_billed = ($i == 1) ? 1 : 0;
|
|
|
|
$this->db->insert('invoice_installments', [
|
|
'invoice_id' => $invoice_id,
|
|
'invoice_detail_id' => $detail_id, // 🔥 relasi ke item
|
|
'customer_id' => $customer_id,
|
|
'angsuran_ke' => $i,
|
|
'total_angsuran' => $tenor,
|
|
'jatuh_tempo' => $tgl,
|
|
'nominal' => $nilai,
|
|
'account_id' => $account_id,
|
|
'item_id' => $item_id ?: null,
|
|
'status' => 'unpaid',
|
|
'is_billed' => $is_billed
|
|
]);
|
|
}
|
|
}
|
|
|
|
public function save_cicilan()
|
|
{
|
|
$cicilan_ids = $this->input->post('cicilan_id');
|
|
$invoice_id = $this->input->post('invoice_id');
|
|
|
|
if (empty($cicilan_ids)) {
|
|
echo json_encode([
|
|
'status' => false,
|
|
'message' => 'Tidak ada data dipilih'
|
|
]);
|
|
return;
|
|
}
|
|
|
|
$this->db->trans_start();
|
|
|
|
foreach ($cicilan_ids as $id) {
|
|
|
|
// =========================
|
|
// AMBIL CICILAN
|
|
// =========================
|
|
$cicilan = $this->db->get_where('invoice_installments', [
|
|
'id' => $id,
|
|
'is_billed' => 0 // penting!
|
|
])->row();
|
|
|
|
if (!$cicilan) continue;
|
|
|
|
// =========================
|
|
// CEK DUPLIKAT
|
|
// =========================
|
|
$cek = $this->db->get_where('invoice_details', [
|
|
'source_cicilan_id' => $id
|
|
])->row();
|
|
|
|
if ($cek) continue;
|
|
|
|
$sisa = $cicilan->nominal - $cicilan->paid_amount;
|
|
|
|
// =========================
|
|
// AMBIL REFERENSI ITEM
|
|
// =========================
|
|
$inv_items = $this->db
|
|
->select('account_id, items_id, nama_item, keterangan, subtotal_asli, is_cicilan')
|
|
->where('id', $cicilan->invoice_detail_id)
|
|
->get('invoice_details')
|
|
->row();
|
|
|
|
if(!$inv_items) continue;
|
|
|
|
// =========================
|
|
// FORMAT NAMA
|
|
// =========================
|
|
if($cicilan->paid_amount > 0){
|
|
$priodecicilan = '( Sisa Cicilan ke ' . $cicilan->angsuran_ke . '/' . $cicilan->total_angsuran . ' )';
|
|
} else {
|
|
$priodecicilan = '( Cicilan ke ' . $cicilan->angsuran_ke . '/' . $cicilan->total_angsuran . ' )';
|
|
}
|
|
|
|
$nama_item = preg_replace(
|
|
'/\s*\(\s*Cicilan.*$/i',
|
|
'',
|
|
$inv_items->nama_item
|
|
) . ' ' . $priodecicilan;
|
|
|
|
// =========================
|
|
// INSERT
|
|
// =========================
|
|
$this->db->insert('invoice_details', [
|
|
'invoice_id' => $invoice_id,
|
|
'tanggal' => $cicilan->jatuh_tempo,
|
|
'account_id' => $inv_items->account_id,
|
|
'items_id' => $inv_items->items_id ?: null,
|
|
'nama_item' => $nama_item,
|
|
'qty' => 1,
|
|
'harga' => $sisa,
|
|
'subtotal' => $sisa,
|
|
'warehouse_id' => null,
|
|
'keterangan' => $inv_items->keterangan,
|
|
'subtotal_asli' => $inv_items->subtotal_asli,
|
|
'is_cicilan' => $inv_items->is_cicilan,
|
|
'source_cicilan_id' => $cicilan->id
|
|
]);
|
|
|
|
// =========================
|
|
// UPDATE STATUS
|
|
// =========================
|
|
$this->db->update('invoice_installments', [
|
|
'is_billed' => 1
|
|
], ['id' => $id]);
|
|
}
|
|
|
|
$this->update_total($invoice_id);
|
|
|
|
$this->db->trans_complete();
|
|
|
|
echo json_encode([
|
|
'status' => $this->db->trans_status()
|
|
]);
|
|
}
|
|
|
|
public function bayar()
|
|
{
|
|
$this->output->set_content_type('application/json');
|
|
|
|
$invoice_id = $this->input->post('invoice_id');
|
|
$tanggal_bayar = $this->input->post('tanggal_bayar');
|
|
$detail = $this->input->post('detail');
|
|
|
|
if (!$invoice_id || empty($detail)) {
|
|
echo json_encode(['status' => false, 'message' => 'Data tidak valid']);
|
|
return;
|
|
}
|
|
|
|
$invoice = $this->db->get_where('invoices', ['id' => $invoice_id])->row();
|
|
if (!$invoice) {
|
|
echo json_encode(['status' => false, 'message' => 'Invoice tidak ditemukan']);
|
|
return;
|
|
}
|
|
|
|
$customer = $this->db->get_where('customers', ['id' => $invoice->customer_id])->row();
|
|
if (!$customer) {
|
|
echo json_encode(['status' => false, 'message' => 'Customer tidak ditemukan']);
|
|
return;
|
|
}
|
|
|
|
// =========================
|
|
// HITUNG TOTAL BAYAR
|
|
// =========================
|
|
$total_bayar_input = 0;
|
|
foreach ($detail as $d) {
|
|
$total_bayar_input += (float)$d['amount'];
|
|
}
|
|
|
|
if ($total_bayar_input <= 0) {
|
|
echo json_encode(['status' => false, 'message' => 'Total bayar harus > 0']);
|
|
return;
|
|
}
|
|
|
|
// =========================
|
|
// DATA INVOICE
|
|
// =========================
|
|
$paid_before = (float)$invoice->total_bayar;
|
|
$total_invoice = (float)$invoice->total;
|
|
$new_total_bayar = $paid_before + $total_bayar_input;
|
|
$sisa_setelah = $total_invoice - $new_total_bayar;
|
|
|
|
if ($new_total_bayar > $total_invoice) {
|
|
echo json_encode([
|
|
'status' => false,
|
|
'message' => 'Pembayaran melebihi total invoice'
|
|
]);
|
|
return;
|
|
}
|
|
|
|
// =========================
|
|
// KONFIGURASI AKUN
|
|
// =========================
|
|
$akun_tabungan = 70;
|
|
$akun_barang = 62;
|
|
$akun_piutang = 19;
|
|
$akun_pendapatan_ditahan = 78;
|
|
$akun_hpp = isset($this->akun_hpp) ? $this->akun_hpp : 81;
|
|
$persediaan_dijual = isset($this->persediaan_dijual) ? $this->persediaan_dijual : 50;
|
|
|
|
// =========================
|
|
// HITUNG CICILAN
|
|
// =========================
|
|
$cicilan = $this->db->query("
|
|
SELECT ii.*
|
|
FROM invoice_installments ii
|
|
JOIN (
|
|
SELECT invoice_detail_id, MIN(angsuran_ke) as angsuran_ke
|
|
FROM invoice_installments
|
|
WHERE customer_id = ?
|
|
AND status != 'paid'
|
|
GROUP BY invoice_detail_id
|
|
) x
|
|
ON x.invoice_detail_id = ii.invoice_detail_id
|
|
AND x.angsuran_ke = ii.angsuran_ke
|
|
", [$customer->id])->result();
|
|
|
|
$total_cicilan = 0;
|
|
foreach ($cicilan as $c) {
|
|
$total_cicilan += ($c->nominal - $c->paid_amount);
|
|
}
|
|
|
|
// =========================
|
|
// TOTAL PER KATEGORI
|
|
// =========================
|
|
$total_tabungan = (float)$this->db
|
|
->select('SUM(subtotal) as total')
|
|
->where('invoice_id', $invoice_id)
|
|
->where('account_id', $akun_tabungan)
|
|
->get('invoice_details')->row()->total ?? 0;
|
|
|
|
$total_barang = (float)$this->db
|
|
->select('SUM(subtotal) as total')
|
|
->where('invoice_id', $invoice_id)
|
|
->where('account_id', $akun_barang)
|
|
->get('invoice_details')->row()->total ?? 0;
|
|
|
|
$total_other = (float)$this->db
|
|
->select('SUM(subtotal) as total')
|
|
->where('invoice_id', $invoice_id)
|
|
->where_not_in('account_id', [$akun_tabungan, $akun_barang])
|
|
->get('invoice_details')->row()->total ?? 0;
|
|
|
|
// =========================
|
|
// SISA PER KATEGORI
|
|
// =========================
|
|
$sisa_other = max(0, $total_other - $paid_before);
|
|
$sisa_tabungan = max(0, $total_tabungan - max(0, $paid_before - $total_other));
|
|
$sisa_barang = max(0, $total_barang - max(0, $paid_before - $total_other - $total_tabungan));
|
|
|
|
// =========================
|
|
// ALOKASI PEMBAYARAN
|
|
// =========================
|
|
$sisa_bayar = $total_bayar_input;
|
|
|
|
$bayar_cicilan = min($sisa_bayar, $total_cicilan);
|
|
$sisa_bayar -= $bayar_cicilan;
|
|
|
|
$bayar_other = min($sisa_bayar, $sisa_other);
|
|
$sisa_bayar -= $bayar_other;
|
|
|
|
$bayar_tabungan = min($sisa_bayar, $sisa_tabungan);
|
|
$sisa_bayar -= $bayar_tabungan;
|
|
|
|
$bayar_barang = min($sisa_bayar, $sisa_barang);
|
|
$sisa_bayar -= $bayar_barang;
|
|
|
|
// =========================
|
|
// AMBIL INVOICE DETAIL
|
|
// =========================
|
|
$inv_detail_items = $this->db
|
|
->select('
|
|
invoice_details.id,
|
|
invoice_details.account_id,
|
|
invoice_details.subtotal,
|
|
invoice_details.recognized_amount,
|
|
invoice_details.total_hpp_barang,
|
|
invoice_details.recognized_hpp,
|
|
accounts.priority
|
|
')
|
|
->from('invoice_details')
|
|
->join('accounts', 'accounts.id = invoice_details.account_id', 'left')
|
|
->where('invoice_details.invoice_id', $invoice_id)
|
|
->where('invoice_details.account_id !=', $akun_tabungan)
|
|
->order_by('accounts.priority', 'ASC')
|
|
->order_by('invoice_details.id', 'ASC')
|
|
->get()
|
|
->result();
|
|
|
|
// =========================
|
|
// DEBIT PENDAPATAN DITAHAN
|
|
// =========================
|
|
// Tidak termasuk tabungan!
|
|
$debit_pendapatan_ditahan = $bayar_cicilan + $bayar_other + $bayar_barang;
|
|
|
|
// =========================
|
|
// MULAI TRANSAKSI
|
|
// =========================
|
|
$this->db->trans_start();
|
|
|
|
// UPDATE cicilan
|
|
if ($bayar_cicilan > 0) {
|
|
$sisa = $bayar_cicilan;
|
|
foreach ($cicilan as $c) {
|
|
if ($sisa <= 0) break;
|
|
$kurang = $c->nominal - $c->paid_amount;
|
|
$bayar = min($sisa, $kurang);
|
|
$sisa -= $bayar;
|
|
$new_paid = $c->paid_amount + $bayar;
|
|
$status = ($new_paid >= $c->nominal) ? 'paid' : 'partial';
|
|
|
|
$this->db->where('id', $c->id)->update('invoice_installments', [
|
|
'paid_amount' => $new_paid,
|
|
'status' => $status
|
|
]);
|
|
}
|
|
}
|
|
|
|
$no_ref = 'PAY-' . date('YmdHis');
|
|
|
|
$this->db->insert('journals', [
|
|
'tanggal' => $tanggal_bayar,
|
|
'no_ref' => $no_ref,
|
|
'keterangan'=> 'Pembayaran Invoice ' . $invoice->no_invoice . ' (' . $customer->nama . ')',
|
|
'ref_type' => 'invoice_payment',
|
|
'ref_id' => $invoice_id,
|
|
'created_by'=> $this->session->userdata('user_id')
|
|
]);
|
|
|
|
$journal_id = $this->db->insert_id();
|
|
|
|
// =========================
|
|
// DEBIT SIDE
|
|
// =========================
|
|
$total_debit = 0;
|
|
$jurnal_debit = [];
|
|
|
|
// 1. Kas/Bank (dari detail)
|
|
foreach ($detail as $d) {
|
|
if (empty($d['account_id']) || $d['amount'] <= 0) continue;
|
|
|
|
$this->db->insert('journal_details', [
|
|
'journal_id' => $journal_id,
|
|
'account_id' => $d['account_id'],
|
|
'debit' => $d['amount'],
|
|
'kredit' => 0
|
|
]);
|
|
|
|
$total_debit += (float)$d['amount'];
|
|
$jurnal_debit[] = [
|
|
'account_id' => $d['account_id'],
|
|
'debit' => (float)$d['amount'],
|
|
'jenis' => 'kas'
|
|
];
|
|
}
|
|
|
|
// =========================
|
|
// KREDIT SIDE
|
|
// =========================
|
|
$total_kredit = 0;
|
|
$jurnal_kredit = [];
|
|
|
|
// 1. Piutang (KREDIT)
|
|
$kredit_piutang = $total_bayar_input - $bayar_tabungan;
|
|
if ($kredit_piutang > 0) {
|
|
$this->db->insert('journal_details', [
|
|
'journal_id' => $journal_id,
|
|
'account_id' => $akun_piutang,
|
|
'debit' => 0,
|
|
'kredit' => $kredit_piutang
|
|
]);
|
|
$total_kredit += $kredit_piutang;
|
|
$jurnal_kredit[] = [
|
|
'account_id' => $akun_piutang,
|
|
'kredit' => $kredit_piutang,
|
|
'jenis' => 'piutang'
|
|
];
|
|
}
|
|
|
|
// 2. Tabungan (KREDIT)
|
|
if ($bayar_tabungan > 0) {
|
|
$this->db->insert('journal_details', [
|
|
'journal_id' => $journal_id,
|
|
'account_id' => $akun_tabungan,
|
|
'debit' => 0,
|
|
'kredit' => $bayar_tabungan
|
|
]);
|
|
$total_kredit += $bayar_tabungan;
|
|
$jurnal_kredit[] = [
|
|
'account_id' => $akun_tabungan,
|
|
'kredit' => $bayar_tabungan,
|
|
'jenis' => 'tabungan'
|
|
];
|
|
}
|
|
|
|
// =========================
|
|
// PENGAKUAN PENDAPATAN & HPP
|
|
// =========================
|
|
$sisa_pengakuan = $bayar_cicilan + $bayar_other + $bayar_barang;
|
|
$account_recognition = [];
|
|
$total_hpp = 0;
|
|
|
|
foreach ($inv_detail_items as $i) {
|
|
if ($sisa_pengakuan <= 0) break;
|
|
|
|
$belum_diakui = (float)$i->subtotal - (float)$i->recognized_amount;
|
|
if ($belum_diakui <= 0) continue;
|
|
|
|
$nominal = min($sisa_pengakuan, $belum_diakui);
|
|
|
|
if (!isset($account_recognition[$i->account_id])) {
|
|
$account_recognition[$i->account_id] = 0;
|
|
}
|
|
$account_recognition[$i->account_id] += $nominal;
|
|
|
|
// Update recognized amount
|
|
$this->db->query("
|
|
UPDATE invoice_details
|
|
SET recognized_amount = recognized_amount + ?
|
|
WHERE id = ?
|
|
", [$nominal, $i->id]);
|
|
|
|
if ($i->account_id == $akun_barang ) {
|
|
|
|
$persentase = $nominal / (float)$i->subtotal;
|
|
$hpp_item = (float)$i->total_hpp_barang * $persentase;
|
|
$hpp_item = round($hpp_item, 2);
|
|
|
|
$sisa_hpp = max(0, (float)$i->total_hpp_barang - (float)$i->recognized_hpp);
|
|
$hpp_item = min($hpp_item, $sisa_hpp);
|
|
|
|
if ($hpp_item > 0) {
|
|
// Cara 1: Pakai update() method
|
|
$this->db->where('id', $i->id);
|
|
$this->db->update('invoice_details', [
|
|
'recognized_hpp' => $i->recognized_hpp + $hpp_item
|
|
]);
|
|
|
|
$total_hpp += $hpp_item;
|
|
}
|
|
}
|
|
|
|
$sisa_pengakuan -= $nominal;
|
|
}
|
|
|
|
// =========================
|
|
// ALOKASI SISA KE AKUN LAIN
|
|
// =========================
|
|
$total_diaku = array_sum($account_recognition);
|
|
$sisa_yg_harus_diaku = $bayar_cicilan + $bayar_other + $bayar_barang;
|
|
$selisih_alokasi = $sisa_yg_harus_diaku - $total_diaku;
|
|
|
|
if ($selisih_alokasi > 0) {
|
|
$akun_other_list = $this->db
|
|
->select('account_id, SUM(subtotal) as total')
|
|
->from('invoice_details')
|
|
->where('invoice_id', $invoice_id)
|
|
->where_not_in('account_id', [$akun_tabungan, $akun_barang])
|
|
->group_by('account_id')
|
|
->get()
|
|
->result();
|
|
|
|
foreach ($akun_other_list as $ao) {
|
|
if ($selisih_alokasi <= 0) break;
|
|
|
|
$belum_diaku_akun = (float)$ao->total;
|
|
$alokasi = min($selisih_alokasi, $belum_diaku_akun);
|
|
|
|
if (!isset($account_recognition[$ao->account_id])) {
|
|
$account_recognition[$ao->account_id] = 0;
|
|
}
|
|
$account_recognition[$ao->account_id] += $alokasi;
|
|
|
|
$selisih_alokasi -= $alokasi;
|
|
}
|
|
}
|
|
|
|
// =========================
|
|
// Insert DEBIT pendapatan
|
|
// =========================
|
|
if ($debit_pendapatan_ditahan > 0) {
|
|
$this->db->insert('journal_details', [
|
|
'journal_id' => $journal_id,
|
|
'account_id' => $akun_pendapatan_ditahan,
|
|
'debit' => $debit_pendapatan_ditahan,
|
|
'kredit' => 0
|
|
]);
|
|
|
|
$total_debit += $debit_pendapatan_ditahan;
|
|
$jurnal_debit[] = [
|
|
'account_id' => $akun_pendapatan_ditahan,
|
|
'debit' => $debit_pendapatan_ditahan,
|
|
'jenis' => 'pendapatan_ditahan'
|
|
];
|
|
}
|
|
|
|
// =========================
|
|
// Insert KREDIT pendapatan
|
|
// =========================
|
|
foreach ($account_recognition as $account_id => $nominal) {
|
|
if ($nominal <= 0) continue;
|
|
|
|
$this->db->insert('journal_details', [
|
|
'journal_id' => $journal_id,
|
|
'account_id' => $account_id,
|
|
'debit' => 0,
|
|
'kredit' => $nominal
|
|
]);
|
|
|
|
$total_kredit += $nominal;
|
|
$jurnal_kredit[] = [
|
|
'account_id' => $account_id,
|
|
'kredit' => $nominal,
|
|
'jenis' => 'pendapatan'
|
|
];
|
|
}
|
|
|
|
// =========================
|
|
// JURNAL HPP
|
|
// =========================
|
|
if ($total_hpp > 0) {
|
|
$this->db->insert('journal_details', [
|
|
'journal_id' => $journal_id,
|
|
'account_id' => $akun_hpp,
|
|
'debit' => $total_hpp,
|
|
'kredit' => 0
|
|
]);
|
|
$total_debit += $total_hpp;
|
|
|
|
$jurnal_debit[] = [
|
|
'account_id' => $akun_hpp,
|
|
'debit' => $total_hpp,
|
|
'jenis' => 'hpp_barang'
|
|
];
|
|
|
|
$this->db->insert('journal_details', [
|
|
'journal_id' => $journal_id,
|
|
'account_id' => $persediaan_dijual,
|
|
'debit' => 0,
|
|
'kredit' => $total_hpp
|
|
]);
|
|
$total_kredit += $total_hpp;
|
|
|
|
|
|
$jurnal_kredit[] = [
|
|
'account_id' => $persediaan_dijual,
|
|
'kredit' => $total_hpp,
|
|
'jenis' => 'persediaan_dijual'
|
|
];
|
|
}
|
|
|
|
// =========================
|
|
// VALIDASI JURNAL
|
|
// =========================
|
|
if (round($total_debit, 2) !== round($total_kredit, 2)) {
|
|
$this->db->trans_rollback();
|
|
|
|
echo json_encode([
|
|
'status' => false,
|
|
'message' => 'Jurnal tidak balance!'
|
|
]);
|
|
return;
|
|
}
|
|
|
|
// =========================
|
|
// INSERT PAYMENTS
|
|
// =========================
|
|
$this->db->insert('payments', [
|
|
'invoice_id' => $invoice_id,
|
|
'tanggal' => $tanggal_bayar,
|
|
'jumlah' => $total_bayar_input,
|
|
'metode' => 'manual',
|
|
'journal_id'=> $journal_id
|
|
]);
|
|
|
|
// =========================
|
|
// UPDATE STATUS INVOICE
|
|
// =========================
|
|
if ($new_total_bayar >= $total_invoice) {
|
|
$status = 'paid';
|
|
$sisa_setelah = 0;
|
|
} elseif ($new_total_bayar > 0) {
|
|
$status = 'partial';
|
|
} else {
|
|
$status = 'unpaid';
|
|
}
|
|
|
|
$this->db->where('id', $invoice_id)->update('invoices', [
|
|
'total_bayar' => $new_total_bayar,
|
|
'sisa_piutang' => $sisa_setelah,
|
|
'status' => $status,
|
|
'updated_at' => date('Y-m-d H:i:s'),
|
|
'updated_by' => $this->session->userdata('user_id')
|
|
]);
|
|
|
|
// =========================
|
|
// SELESAIKAN TRANSAKSI
|
|
// =========================
|
|
$this->db->trans_complete();
|
|
|
|
// =========================
|
|
// RESPONSE
|
|
// =========================
|
|
if (!$this->db->trans_status()) {
|
|
echo json_encode([
|
|
'status' => false,
|
|
'message' => 'Gagal menyelesaikan pembayaran'
|
|
]);
|
|
return;
|
|
}
|
|
|
|
// Logging
|
|
log_activity(
|
|
'invoice',
|
|
'create',
|
|
'Pembayaran invoice ' . $invoice->no_invoice . ' - ' . $customer->nama,
|
|
'success'
|
|
);
|
|
|
|
echo json_encode([
|
|
'status' => true,
|
|
'message' => 'Pembayaran berhasil'
|
|
]);
|
|
}
|
|
|
|
|
|
} |