From 226b35479f9969089f164dffbaa9f39e009beac0 Mon Sep 17 00:00:00 2001 From: Ilham Date: Sun, 7 Dec 2025 23:13:53 +0700 Subject: [PATCH] tipis --- .env | 4 +- __pycache__/sibal.cpython-314.pyc | Bin 261552 -> 321023 bytes sibal.py | 913 ++++++++++++++++++++++++++++-- text | 2 +- text copy | 8 + 5 files changed, 878 insertions(+), 49 deletions(-) create mode 100644 text copy diff --git a/.env b/.env index 3155e20..b36dc95 100644 --- a/.env +++ b/.env @@ -7,8 +7,8 @@ # Optional: override 'From' email SMTP_HOST=smtp.gmail.com SMTP_PORT=587 -SMTP_USER=maulanaryan2004@gmail.com -SMTP_PASS=kfebdpeoaxfabzpm +SMTP_USER=appsibal@gmail.com +SMTP_PASS=yogsorikxfqymakh SMTP_SENDER_NAME=SIBAL OTP_DEBUG=false diff --git a/__pycache__/sibal.cpython-314.pyc b/__pycache__/sibal.cpython-314.pyc index f548f757f5ab24b36f8b375dfef1f12ddc35e272..56d1b8558150600fb6221cbc2a2b5dd1364991b2 100644 GIT binary patch delta 86094 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
^c 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 M*Gww>FGXPdH*F3DrvLx| diff --git a/sibal.py b/sibal.py index 419bc4b..8c0542b 100644 --- a/sibal.py +++ b/sibal.py @@ -11,6 +11,7 @@ import glob from pathlib import Path import os, smtplib, ssl from email.message import EmailMessage +import re BASE_DIR = Path(__file__).resolve().parent ENV_PATH = BASE_DIR / ".env" @@ -76,6 +77,76 @@ supabase_url = "https://shltrwcweexbdcuogscs.supabase.co" supabase_key = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InNobHRyd2N3ZWV4YmRjdW9nc2NzIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NjQwNjk4MjUsImV4cCI6MjA3OTY0NTgyNX0.tVwzjiqORJ0dAZhW_f0PneVqJyummvmPOi-WFC5Wd1I" supabase_client = create_client(supabase_url, supabase_key) + +def delete_old_transactions(table_name: str, older_than_days: int | None = None) -> dict: + """Delete historical rows from a Supabase table. + + - If `older_than_days` is an int, delete rows where `tanggal` < (today - days). + - If `older_than_days` is None, delete ALL rows in the table (best-effort). + + Returns a dict with result information. + """ + try: + if older_than_days is None: + # Best-effort: fetch ids then delete by ids to avoid delete-without-filter issues + resp = supabase_client.table(table_name).select('id').execute() + ids = [r.get('id') for r in (resp.data or []) if r.get('id') is not None] + if not ids: + return {'deleted': 0, 'ids_deleted': []} + del_resp = supabase_client.table(table_name).delete().in_('id', ids).execute() + return {'deleted': len(ids), 'ids_deleted': ids, 'raw': del_resp} + else: + cutoff = (datetime.now().date() - timedelta(days=older_than_days)).isoformat() + # Try deleting by tanggal < cutoff + del_resp = supabase_client.table(table_name).delete().lt('tanggal', cutoff).execute() + # supabase returns deleted rows in del_resp.data when succesful + deleted = len(del_resp.data) if getattr(del_resp, 'data', None) else 0 + return {'deleted': deleted, 'cutoff': cutoff, 'raw': del_resp} + except Exception as e: + print(f"[CLEANUP] Error deleting from {table_name}: {e}") + return {'error': str(e)} + + +@server.route('/admin/cleanup-transactions') +def admin_cleanup_transactions(): + """HTTP helper to cleanup transactions. + + Query params: + - table: 'pemasukan'|'pengeluaran'|'both' (default: both) + - days: integer number of days to keep (delete older than days), or 'all' to delete everything + + Example: /admin/cleanup-transactions?table=both&days=365 + """ + try: + table_param = request.args.get('table', 'both') + days_param = request.args.get('days', None) + + if days_param is None: + return ("Usage: provide ?table={pemasukan|pengeluaran|both}&days={N|all}", 400) + + if days_param == 'all': + days = None + else: + try: + days = int(days_param) + except Exception: + return ("Invalid days parameter", 400) + + results = {} + targets = [] + if table_param in ('pemasukan', 'both'): + targets.append('transaksi_pemasukan') + if table_param in ('pengeluaran', 'both'): + targets.append('transaksi_pengeluaran') + + for t in targets: + results[t] = delete_old_transactions(t, days) + + return results + except Exception as e: + return {'error': str(e)} + + # ===== SIMPLE AUTH SYSTEM ===== import requests import secrets @@ -1403,17 +1474,34 @@ class SIBALData: if not self.client: print(f"❌ Client not available for insert to {table_name}") return None - try: - response = self.client.table(table_name).insert(data).execute() - if response.data: - print(f"✅ Inserted data to {table_name}") - return response.data[0] - else: - print(f"❌ No data returned from insert to {table_name}") + # Make a shallow copy so we can mutate for retry attempts + payload = dict(data) + attempts = 0 + while attempts < 3: + attempts += 1 + try: + response = self.client.table(table_name).insert(payload).execute() + if response.data: + print(f"✅ Inserted data to {table_name}") + return response.data[0] + else: + print(f"❌ No data returned from insert to {table_name}") + return None + except Exception as e: + err = str(e) + print(f"⚠️ Insert attempt {attempts} failed for {table_name}: {err}") + # Handle PostgREST schema cache error where a column is missing + # message example: "Could not find the 'user_id' column of 'transaksi_pemasukan' in the schema cache" + m = re.search(r"Could not find the '([a-zA-Z0-9_]+)' column", err) + if m: + missing_col = m.group(1) + if missing_col in payload: + print(f"ℹ️ Removing unknown column '{missing_col}' and retrying insert to {table_name}") + payload.pop(missing_col, None) + continue + # If not a schema-missing issue or column not in payload, stop retrying + print(f"❌ Error inserting to {table_name}: {err}") return None - except Exception as e: - print(f"❌ Error inserting to {table_name}: {e}") - return None def load_all_data(self): """Memuat semua data dari Supabase""" @@ -2037,10 +2125,22 @@ def create_laporan_sub_nav(): html.I(className="fas fa-sync-alt"), " Jurnal Penyesuaian" ], href="/jurnal-penyesuaian", className="sub-nav-link"), + dcc.Link([ + html.I(className="fas fa-balance-scale"), + " Neraca Saldo Setelah Penyesuaian" + ], href="/neraca-saldo-penyesuaian", className="sub-nav-link"), dcc.Link([ html.I(className="fas fa-chart-line"), - " Neraca Setelah Penyesuaian" + " Laporan Posisi Keuangan" ], href="/neraca-setelah-penyesuaian", className="sub-nav-link"), + dcc.Link([ + html.I(className="fas fa-clipboard-list"), + " Jurnal Penutup" + ], href="/jurnal-penutup", className="sub-nav-link"), + dcc.Link([ + html.I(className="fas fa-file-invoice"), + " Neraca Setelah Penutupan" + ], href="/neraca-setelah-penutupan", className="sub-nav-link"), dcc.Link([ html.I(className="fas fa-money-bill-wave"), " Laporan Laba Rugi" @@ -2188,16 +2288,6 @@ def transaksi_layout(): ) ], className="form-group"), - html.Div([ - html.Label("Quantity", className="form-label"), - dcc.Input( - id='pemasukan-qty', - type='number', - placeholder='0', - min=0, - className="form-input" - ) - ], className="form-group"), html.Div([ html.Label("Quantity (kg) - untuk penjualan ikan", className="form-label"), @@ -2219,6 +2309,7 @@ def transaksi_layout(): className="form-input" ) ], className="form-group"), + html.Div([ html.Label("Keterangan Transaksi", className="form-label"), dcc.Input( @@ -2308,16 +2399,6 @@ def transaksi_layout(): ) ], className="form-group"), - html.Div([ - html.Label("Quantity", className="form-label"), - dcc.Input( - id='pengeluaran-qty', - type='number', - placeholder='0', - min=1, - className="form-input" - ) - ], className="form-group"), html.Div([ html.Label("Quantity (kg) - hanya untuk pembelian persediaan", className="form-label"), dcc.Input( @@ -2436,6 +2517,37 @@ def kartu_persediaan_layout(): className="btn btn-primary", style={'marginBottom': '20px'} ), + # Single-row input form (masuk / keluar) - user requested + html.Div([ + html.Div([ + html.Div([ + html.Label('Tanggal', className='form-label'), + dcc.DatePickerSingle(id='tanggal-persediaan', date=datetime.now().date(), display_format='YYYY-MM-DD', className='form-control') + ], className='form-group', style={'width': '18%', 'display': 'inline-block', 'marginRight': '10px'}), + html.Div([ + html.Label('Barang', className='form-label'), + dcc.Dropdown(id='dropdown-kode-barang', options=[{'label': f"{b['kode']} - {b['nama']}", 'value': b['kode']} for b in sibal_data.master_persediaan], placeholder='Pilih Barang', className='form-control') + ], className='form-group', style={'width': '30%', 'display': 'inline-block', 'marginRight': '10px'}), + html.Div([ + html.Label('Masuk (Qty)', className='form-label'), + dcc.Input(id='input-masuk-qty', type='number', value=0, min=0, className='form-control') + ], className='form-group', style={'width': '12%', 'display': 'inline-block', 'marginRight': '10px'}), + html.Div([ + html.Label('Masuk (Harga)', className='form-label'), + dcc.Input(id='input-masuk-harga', type='number', value=0, min=0, className='form-control') + ], className='form-group', style={'width': '15%', 'display': 'inline-block', 'marginRight': '10px'}), + html.Div([ + html.Label('Keluar (Qty)', className='form-label'), + dcc.Input(id='input-keluar-qty', type='number', value=0, min=0, className='form-control') + ], className='form-group', style={'width': '12%', 'display': 'inline-block', 'marginRight': '10px'}), + html.Div([ + html.Label('Keterangan', className='form-label'), + dcc.Input(id='input-keterangan-persediaan', type='text', value='', className='form-control') + ], className='form-group', style={'width': '100%', 'display': 'block', 'marginTop': '10px'}), + html.Button('💾 Simpan Transaksi', id='btn-simpan-kartu-persediaan', className='btn btn-success', style={'marginTop': '10px'}) + ], className='form-row') + ], style={'marginBottom': '18px'}), + html.Div(id='tabel-kartu-persediaan', className="table-container") ], className="card-content") ], className="glass-card") @@ -2559,7 +2671,7 @@ def neraca_setelah_penyesuaian_layout(): html.Div([ html.I(className="fas fa-chart-line") ], className="card-icon"), - html.H1("Neraca Setelah Penyesuaian", className="card-title") + html.H1("Laporan posisi keuangan", className="card-title") ], className="card-header"), html.Div([ html.Div([ @@ -2572,7 +2684,7 @@ def neraca_setelah_penyesuaian_layout(): ) ], className="form-group"), html.Button( - "📊 Generate Neraca Setelah Penyesuaian", + "📊 Generate Laporan Posisi Keuangan", id='btn-generate-neraca-penyesuaian', className="btn btn-primary" ), @@ -2581,6 +2693,60 @@ def neraca_setelah_penyesuaian_layout(): ], className="glass-card") ], className="main-container") +def neraca_saldo_penyesuaian_layout(): + return html.Div([ + html.Div([ + html.Div([ + html.Div([html.I(className='fas fa-balance-scale')], className='card-icon'), + html.H1('Neraca Saldo Setelah Penyesuaian', className='card-title') + ], className='card-header'), + html.Div([ + html.Div([html.Label('Pilih Tanggal Neraca:', className='form-label'), + dcc.DatePickerSingle(id='tanggal-neraca-saldo-penyesuaian', date=datetime.now().date(), display_format='YYYY-MM-DD', className='form-control')], className='form-group'), + html.Div([html.Button('📊 Generate Neraca Saldo Setelah Penyesuaian', id='btn-generate-neraca-saldo-penyesuaian', className='btn btn-primary'), html.Button('💾 Simpan', id='btn-save-neraca-saldo-penyesuaian', className='btn btn-secondary', style={'marginLeft':'10px'})], style={'marginBottom':'12px'}), + html.Div(id='tabel-neraca-saldo-penyesuaian', className='table-container'), + html.Div(id='save-neraca-saldo-penyesuaian-status', style={'marginTop':'12px'}) + ], className='card-content') + ], className='glass-card') + ], className='main-container') + +def jurnal_penutup_layout(): + return html.Div([ + html.Div([ + html.Div([ + html.Div([html.I(className="fas fa-clipboard-list")], className="card-icon"), + html.H1("Jurnal Penutup", className="card-title") + ], className="card-header"), + html.Div([ + html.Div([ + html.Label("Tanggal Jurnal Penutup", className="form-label"), + dcc.DatePickerSingle( + id='tanggal-jurnal-penutup', + date=datetime.now().date(), + display_format='YYYY-MM-DD', + className='form-control' + ) + ], className='form-group'), + html.Button("📌 Generate Jurnal Penutup", id='btn-generate-jurnal-penutup', className='btn btn-primary'), + html.Div(id='tabel-jurnal-penutup', className='table-container') + ], className='card-content') + ], className='glass-card') + ], className='main-container') + +def neraca_setelah_penutupan_layout(): + return html.Div([ + html.Div([ + html.Div([html.Div([html.I(className='fas fa-file-invoice')], className='card-icon'), html.H1('Neraca Setelah Penutupan', className='card-title')], className='card-header'), + html.Div([ + html.Div([html.Label('Pilih Tanggal Neraca Setelah Penutupan:', className='form-label'), + dcc.DatePickerSingle(id='tanggal-neraca-penutupan', date=datetime.now().date(), display_format='YYYY-MM-DD', className='form-control')], className='form-group'), + html.Div([html.Button('📊 Generate Neraca Setelah Penutupan', id='btn-generate-neraca-penutupan', className='btn btn-primary'), html.Button('💾 Simpan ke Supabase', id='btn-save-neraca-penutupan', className='btn btn-secondary', style={'marginLeft':'10px'})], style={'marginBottom':'12px'}), + html.Div(id='tabel-neraca-penutupan', className='table-container'), + html.Div(id='save-neraca-penutupan-status', style={'marginTop':'12px'}) + ], className='card-content') + ], className='glass-card') + ], className='main-container') + def laporan_laba_rugi_layout(): return html.Div([ html.Div([ @@ -3228,7 +3394,7 @@ def display_page(pathname): laporan_pages = [ '/laporan-analisis', '/jurnal-penyesuaian', '/neraca-setelah-penyesuaian', - '/laporan-laba-rugi', '/laporan-keuangan' + '/laporan-laba-rugi', '/laporan-keuangan', '/jurnal-penutup', '/neraca-setelah-penutupan', '/neraca-saldo-penyesuaian' ] if pathname in akuntansi_pages: @@ -3265,6 +3431,12 @@ def display_page(pathname): content = jurnal_penyesuaian_layout() elif pathname == '/neraca-setelah-penyesuaian': content = neraca_setelah_penyesuaian_layout() + elif pathname == '/jurnal-penutup': + content = jurnal_penutup_layout() + elif pathname == '/neraca-setelah-penutupan': + content = neraca_setelah_penutupan_layout() + elif pathname == '/neraca-saldo-penyesuaian': + content = neraca_saldo_penyesuaian_layout() elif pathname == '/laporan-laba-rugi': content = laporan_laba_rugi_layout() elif pathname == '/laporan-keuangan': @@ -3816,10 +3988,24 @@ def hapus_pemasukan(n_clicks, tanggal, checklists): if n_clicks and n_clicks > 0: transaksi_hari_ini = [t for t in sibal_data.transaksi_pemasukan if t['tanggal'] == tanggal] indices_to_delete = [] - + # Each checklist returns a list of selected values. We encode option values as + # 'id:' when item has been saved to DB, or 'idx:' for in-memory items. for i, checked in enumerate(checklists): - if checked and 'selected' in checked and i < len(transaksi_hari_ini): - indices_to_delete.append(i) + if checked: + val = checked[0] + if isinstance(val, str) and val.startswith('id:'): + tid = val.split(':', 1)[1] + for idx_t, t in enumerate(transaksi_hari_ini): + if str(t.get('id')) == str(tid): + indices_to_delete.append(idx_t) + break + elif isinstance(val, str) and val.startswith('idx:'): + try: + idx_val = int(val.split(':', 1)[1]) + if idx_val < len(transaksi_hari_ini): + indices_to_delete.append(idx_val) + except Exception: + pass if indices_to_delete: # Hapus dari belakang untuk menghindari index error @@ -3827,9 +4013,16 @@ def hapus_pemasukan(n_clicks, tanggal, checklists): for i in sorted(indices_to_delete, reverse=True): if i < len(transaksi_hari_ini): transaksi_to_delete = transaksi_hari_ini[i] + # If saved to DB, attempt DB delete + try: + if 'id' in transaksi_to_delete and transaksi_to_delete.get('id') and supabase_client: + supabase_client.table('transaksi_pemasukan').delete().eq('id', transaksi_to_delete.get('id')).execute() + except Exception as e: + print(f"[DELETE] Failed to delete pemasukan id {transaksi_to_delete.get('id')}: {e}") + sibal_data.transaksi_pemasukan.remove(transaksi_to_delete) deleted_count += 1 - + sibal_data.save_all_data() return update_daftar_transaksi(tanggal, None, None, None, None)[0] @@ -3846,10 +4039,23 @@ def hapus_pengeluaran(n_clicks, tanggal, checklists): if n_clicks and n_clicks > 0: transaksi_hari_ini = [t for t in sibal_data.transaksi_pengeluaran if t['tanggal'] == tanggal] indices_to_delete = [] - + # Parse checklist selected values encoded as 'id:' or 'idx:' for i, checked in enumerate(checklists): - if checked and 'selected' in checked and i < len(transaksi_hari_ini): - indices_to_delete.append(i) + if checked: + val = checked[0] + if isinstance(val, str) and val.startswith('id:'): + tid = val.split(':', 1)[1] + for idx_t, t in enumerate(transaksi_hari_ini): + if str(t.get('id')) == str(tid): + indices_to_delete.append(idx_t) + break + elif isinstance(val, str) and val.startswith('idx:'): + try: + idx_val = int(val.split(':', 1)[1]) + if idx_val < len(transaksi_hari_ini): + indices_to_delete.append(idx_val) + except Exception: + pass if indices_to_delete: # Hapus dari belakang untuk menghindari index error @@ -3857,9 +4063,16 @@ def hapus_pengeluaran(n_clicks, tanggal, checklists): for i in sorted(indices_to_delete, reverse=True): if i < len(transaksi_hari_ini): transaksi_to_delete = transaksi_hari_ini[i] + # If saved to DB, attempt DB delete + try: + if 'id' in transaksi_to_delete and transaksi_to_delete.get('id') and supabase_client: + supabase_client.table('transaksi_pengeluaran').delete().eq('id', transaksi_to_delete.get('id')).execute() + except Exception as e: + print(f"[DELETE] Failed to delete pengeluaran id {transaksi_to_delete.get('id')}: {e}") + sibal_data.transaksi_pengeluaran.remove(transaksi_to_delete) deleted_count += 1 - + sibal_data.save_all_data() return update_daftar_transaksi(tanggal, None, None, None, None)[1] @@ -3887,10 +4100,16 @@ def update_daftar_transaksi(tanggal, n_pemasukan, n_pengeluaran, n_hapus_pemasuk quantity = trans.get('quantity', 0) jumlah = trans.get('jumlah', 0) + # encode option value to indicate DB id or index + if trans.get('id'): + opt_value = f"id:{trans.get('id')}" + else: + opt_value = f"idx:{i}" + items_pemasukan.append(html.Div([ dcc.Checklist( id={'type': 'pemasukan-check', 'index': i}, - options=[{'label': '', 'value': 'selected'}], + options=[{'label': '', 'value': opt_value}], value=[], style={'display': 'inline-block', 'marginRight': '10px'} ), @@ -3917,10 +4136,16 @@ def update_daftar_transaksi(tanggal, n_pemasukan, n_pengeluaran, n_hapus_pemasuk jenis_label = trans['jenis'].replace('_', ' ').title() metode = trans.get('metode_bayar', 'tunai') + # encode option value to indicate DB id or index + if trans.get('id'): + opt_value = f"id:{trans.get('id')}" + else: + opt_value = f"idx:{i}" + items_pengeluaran.append(html.Div([ dcc.Checklist( id={'type': 'pengeluaran-check', 'index': i}, - options=[{'label': '', 'value': 'selected'}], + options=[{'label': '', 'value': opt_value}], value=[], style={'display': 'inline-block', 'marginRight': '10px'} ), @@ -4623,6 +4848,74 @@ def update_kartu_persediaan(n_clicks): html.P("💡 Buat transaksi pembelian persediaan terlebih dahulu", style={'textAlign': 'center', 'color': COLORS['gray_500'], 'fontSize': '14px'}) ]) + + +@app.callback( + Output('tabel-kartu-persediaan', 'children'), + Input('btn-simpan-kartu-persediaan', 'n_clicks'), + [State('dropdown-kode-barang', 'value'), + State('tanggal-persediaan', 'date'), + State('input-masuk-qty', 'value'), + State('input-masuk-harga', 'value'), + State('input-keluar-qty', 'value'), + State('input-keterangan-persediaan', 'value')], + prevent_initial_call=True, + allow_duplicate=True +) +def save_kartu_persediaan_row(n_clicks, kode_barang, tanggal, masuk_qty, masuk_harga, keluar_qty, keterangan): + """Simpan satu baris transaksi kartu persediaan (masuk atau keluar).""" + ctx = dash.callback_context + if not ctx.triggered: + return dash.no_update + + # Normalize inputs + try: + masuk_qty = int(masuk_qty) if masuk_qty else 0 + except Exception: + masuk_qty = 0 + try: + masuk_harga = float(masuk_harga) if masuk_harga else 0 + except Exception: + masuk_harga = 0 + try: + keluar_qty = int(keluar_qty) if keluar_qty else 0 + except Exception: + keluar_qty = 0 + + if not kode_barang: + return html.Div([html.P('⚠️ Pilih barang terlebih dahulu', style={'color': '#d9534f'})]) + + if masuk_qty <= 0 and keluar_qty <= 0: + return html.Div([html.P('⚠️ Masukkan qty masuk atau keluar (minimal salah satu > 0)', style={'color': '#d9534f'})]) + + # Use existing helpers to update in-memory kartu persediaan + result_msg = None + if masuk_qty > 0: + success = update_persediaan_pembelian(kode_barang, masuk_qty, masuk_harga, tanggal, keterangan) + if success: + result_msg = html.Div([html.P('✅ Transaksi pembelian disimpan', style={'color': '#28a745'})]) + else: + result_msg = html.Div([html.P('❌ Gagal menyimpan pembelian', style={'color': '#d9534f'})]) + + if keluar_qty > 0: + success, hpp = update_persediaan_penjualan(kode_barang, keluar_qty, tanggal, keterangan) + if success: + result_msg = html.Div([html.P('✅ Transaksi penjualan disimpan', style={'color': '#28a745'})]) + else: + result_msg = html.Div([html.P('❌ Gagal menyimpan penjualan (cek stok)', style={'color': '#d9534f'})]) + + # Persist data to Supabase + try: + sibal_data.save_all_data() + except Exception as e: + print(f"❌ Error saat menyimpan ke Supabase: {e}") + + # Return refreshed table by calling the update function directly + try: + return update_kartu_persediaan(None) + except Exception as e: + print(f"❌ Error saat merefresh tampilan kartu persediaan: {e}") + return result_msg if result_msg is not None else html.Div([html.P('Operasi selesai')]) @app.callback( Output('detail-buku-besar', 'children'), @@ -5366,7 +5659,7 @@ def generate_neraca_setelah_penyesuaian(n_clicks, tanggal): # Buat tabel neraca return html.Div([ - html.H4(f"Neraca Setelah Penyesuaian per {tanggal}", + html.H4(f"Laporan Posisi Keuangan per {tanggal}", style={'textAlign': 'center', 'marginBottom': '30px', 'color': COLORS['primary']}), html.Div([ @@ -5420,7 +5713,7 @@ def generate_neraca_setelah_penyesuaian(n_clicks, tanggal): ]) ]) - return html.P("Klik 'Generate Neraca Setelah Penyesuaian' untuk melihat neraca", style={'color': '#6c757d'}) + return html.P("Klik 'Generate Laporan Posisi Keuangan' untuk melihat neraca", style={'color': '#6c757d'}) @app.callback( Output('tabel-laba-rugi', 'children'), @@ -5854,6 +6147,534 @@ def calculate_saldo_akun_periode(jurnal_data, start_date, end_date): return saldo_akun + +@app.callback( + Output('tabel-jurnal-penutup', 'children'), + Input('btn-generate-jurnal-penutup', 'n_clicks'), + State('tanggal-jurnal-penutup', 'date'), + prevent_initial_call=True +) +def generate_jurnal_penutup(n_clicks, tanggal): + if not (n_clicks and n_clicks > 0): + return html.P("Klik 'Generate Jurnal Penutup' untuk membuat jurnal penutup", style={'color': '#6c757d'}) + + # Hitung saldo akun dari jurnal umum + jurnal penyesuaian + saldo_akun = {} + semua_jurnal = sibal_data.jurnal_umum + sibal_data.jurnal_penyesuaian + for jurnal in semua_jurnal: + kode_debit = jurnal['kode_akun_debit'] + if kode_debit not in saldo_akun: + saldo_akun[kode_debit] = {'debit': 0, 'kredit': 0, 'nama': jurnal['akun_debit']} + saldo_akun[kode_debit]['debit'] += jurnal['jumlah_debit'] + + kode_kredit = jurnal['kode_akun_kredit'] + if kode_kredit not in saldo_akun: + saldo_akun[kode_kredit] = {'debit': 0, 'kredit': 0, 'nama': jurnal['akun_kredit']} + saldo_akun[kode_kredit]['kredit'] += jurnal['jumlah_kredit'] + + # Identifikasi akun nominal (pendapatan & beban) + jurnal_penutup_entries = [] + total_pendapatan = 0 + total_beban = 0 + + for kode, data in sorted(saldo_akun.items()): + tipe = ('pendapatan' if kode.startswith('4') else 'beban' if kode.startswith('5') or kode.startswith('6') else None) + saldo = (data['kredit'] - data['debit']) if tipe == 'pendapatan' else (data['debit'] - data['kredit']) if tipe == 'beban' else 0 + if tipe == 'pendapatan' and saldo != 0: + # Tutup pendapatan: Debit akun pendapatan, Kredit Ikhtisar Laba Rugi + jurnal_penutup_entries.append({ + 'tanggal': tanggal, + 'keterangan': f"Penutupan akun pendapatan {data['nama']}", + 'akun_debit': data['nama'], + 'debit': saldo, + 'akun_kredit': 'Ikhtisar Laba Rugi', + 'kredit': saldo + }) + total_pendapatan += saldo + elif tipe == 'beban' and saldo != 0: + # Tutup beban: Debit Ikhtisar Laba Rugi, Kredit akun beban + jurnal_penutup_entries.append({ + 'tanggal': tanggal, + 'keterangan': f"Penutupan akun beban {data['nama']}", + 'akun_debit': 'Ikhtisar Laba Rugi', + 'debit': saldo, + 'akun_kredit': data['nama'], + 'kredit': saldo + }) + total_beban += saldo + + laba_rugi = total_pendapatan - total_beban + # Tutup Ikhtisar Laba Rugi ke Modal + if laba_rugi != 0: + if laba_rugi > 0: + # Laba: Debit Ikhtisar, Kredit Modal + jurnal_penutup_entries.append({ + 'tanggal': tanggal, + 'keterangan': 'Penutupan Ikhtisar Laba Rugi (Laba)', + 'akun_debit': 'Ikhtisar Laba Rugi', + 'debit': laba_rugi, + 'akun_kredit': KODE_AKUN['modal']['nama'], + 'kredit': laba_rugi + }) + else: + # Rugi: Debit Modal, Kredit Ikhtisar + jurnal_penutup_entries.append({ + 'tanggal': tanggal, + 'keterangan': 'Penutupan Ikhtisar Laba Rugi (Rugi)', + 'akun_debit': KODE_AKUN['modal']['nama'], + 'debit': abs(laba_rugi), + 'akun_kredit': 'Ikhtisar Laba Rugi', + 'kredit': abs(laba_rugi) + }) + + # Render tabel jurnal penutup + if jurnal_penutup_entries: + header = html.Tr([html.Th('Tanggal'), html.Th('Keterangan'), html.Th('Akun Debit'), html.Th('Debit'), html.Th('Akun Kredit'), html.Th('Kredit')]) + rows = [] + for j in jurnal_penutup_entries: + rows.append(html.Tr([html.Td(j['tanggal']), html.Td(j['keterangan']), html.Td(j['akun_debit']), html.Td(format_rupiah(j['debit'])), html.Td(j['akun_kredit']), html.Td(format_rupiah(j['kredit']))])) + + # Also persist jurnal penutup entries to Supabase table 'jurnal_penutup' (delete existing for same tanggal+user) + insert_count = 0 + insert_errors = [] + try: + if sibal_data and getattr(sibal_data, 'active_user_id', None) is not None: + try: + supabase_client.table('jurnal_penutup').delete().eq('tanggal', str(tanggal)).eq('user_id', sibal_data.active_user_id).execute() + except Exception: + # ignore delete errors but continue to attempt inserts + pass + + # Build name -> code map from existing data to supply kode_akun_* fields + code_map = {} + try: + all_journals = sibal_data.jurnal_umum + sibal_data.jurnal_penyesuaian + for jj in all_journals: + name_debit = jj.get('akun_debit') + code_debit = jj.get('kode_akun_debit') + if name_debit and code_debit: + code_map.setdefault(name_debit, code_debit) + name_kredit = jj.get('akun_kredit') + code_kredit = jj.get('kode_akun_kredit') + if name_kredit and code_kredit: + code_map.setdefault(name_kredit, code_kredit) + except Exception: + pass + + for j in jurnal_penutup_entries: + akun_debet_name = j.get('akun_debit','') + akun_kredit_name = j.get('akun_kredit','') + kode_debet = code_map.get(akun_debet_name) + kode_kredit = code_map.get(akun_kredit_name) + # try known KODE_AKUN mapping for modal or ikhtisar if available + try: + if not kode_debet and akun_debet_name and 'modal' in globals() or 'KODE_AKUN' in globals(): + kode_debet = KODE_AKUN.get(akun_debet_name, {}).get('kode') if isinstance(KODE_AKUN, dict) else None + except Exception: + pass + + try: + if not kode_kredit and akun_kredit_name and 'KODE_AKUN' in globals(): + kode_kredit = KODE_AKUN.get(akun_kredit_name, {}).get('kode') if isinstance(KODE_AKUN, dict) else None + except Exception: + pass + + # fallback to using the account name as code (to satisfy NOT NULL constraints) + if not kode_debet: + kode_debet = str(akun_debet_name) + if not kode_kredit: + kode_kredit = str(akun_kredit_name) + + payload = { + 'tanggal': str(j.get('tanggal')), + 'keterangan': j.get('keterangan',''), + 'akun_debit': akun_debet_name, + 'kode_akun_debit': kode_debet, + 'jumlah_debit': float(j.get('debit',0) or 0), + 'akun_kredit': akun_kredit_name, + 'kode_akun_kredit': kode_kredit, + 'jumlah_kredit': float(j.get('kredit',0) or 0), + 'user_id': sibal_data.active_user_id + } + try: + resp = supabase_client.table('jurnal_penutup').insert(payload).execute() + if getattr(resp, 'status_code', 0) in (200,201) or getattr(resp, 'data', None): + insert_count += 1 + else: + insert_errors.append(str(payload.get('akun_debit'))) + except Exception as e: + insert_errors.append(f"{payload.get('akun_debit')}:{e}") + except Exception: + insert_errors.append('unknown') + + status_el = None + if insert_count > 0: + status_msg = f"✅ Tersimpan {insert_count} baris jurnal penutup ke tabel 'jurnal_penutup'" + if insert_errors: + status_msg += f" — gagal: {', '.join(insert_errors)}" + status_el = html.Div(status_msg, style={'color': COLORS['success'], 'marginTop': '10px'}) + elif insert_errors: + status_el = html.Div(f"⚠️ Gagal menyimpan jurnal penutup: {', '.join(insert_errors)}", style={'color': COLORS['warning'], 'marginTop': '10px'}) + + content = [html.Table([header] + rows, className='modern-table')] + if status_el: + content.append(status_el) + return html.Div(content) + + return html.P('Tidak ada akun nominal yang perlu ditutup', style={'color': '#6c757d'}) + + +@app.callback( + Output('tabel-neraca-penutupan', 'children'), + Input('btn-generate-neraca-penutupan', 'n_clicks'), + State('tanggal-neraca-penutupan', 'date'), + prevent_initial_call=True +) +def generate_neraca_setelah_penutupan(n_clicks, tanggal): + if not (n_clicks and n_clicks > 0): + return html.P("Klik 'Generate Neraca Setelah Penutupan' untuk melihat neraca", style={'color': '#6c757d'}) + # Ambil saldo dari jurnal umum + penyesuaian + saldo_akun = {} + semua_jurnal = sibal_data.jurnal_umum + sibal_data.jurnal_penyesuaian + for jurnal in semua_jurnal: + kode_debit = jurnal.get('kode_akun_debit') + kode_kredit = jurnal.get('kode_akun_kredit') + + if kode_debit: + if kode_debit not in saldo_akun: + saldo_akun[kode_debit] = {'debit': 0.0, 'kredit': 0.0, 'nama': jurnal.get('akun_debit','')} + saldo_akun[kode_debit]['debit'] += float(jurnal.get('jumlah_debit', 0) or 0) + + if kode_kredit: + if kode_kredit not in saldo_akun: + saldo_akun[kode_kredit] = {'debit': 0.0, 'kredit': 0.0, 'nama': jurnal.get('akun_kredit','')} + saldo_akun[kode_kredit]['kredit'] += float(jurnal.get('jumlah_kredit', 0) or 0) + + # Hitung laba rugi dari akun pendapatan/beban + pendapatan = {k: v for k, v in saldo_akun.items() if str(k).startswith('4')} + beban = {k: v for k, v in saldo_akun.items() if str(k).startswith('5') or str(k).startswith('6')} + total_pendapatan = sum((v['kredit'] - v['debit']) for v in pendapatan.values()) + total_beban = sum((v['debit'] - v['kredit']) for v in beban.values()) + laba_rugi = total_pendapatan - total_beban + + # Siapkan baris neraca setelah penutupan dalam format Debit/Kredit + rows = [] + total_debit = 0.0 + total_kredit = 0.0 + + # Masukkan semua akun non-nominal (exclude pendapatan & beban) + for kode, data in sorted(saldo_akun.items()): + kode_str = str(kode) + tipe = 'aktiva' if kode_str.startswith('1') else 'pasiva' if kode_str.startswith('2') else 'modal' if kode_str.startswith('3') else 'pendapatan' if kode_str.startswith('4') else 'beban' if kode_str.startswith('5') or kode_str.startswith('6') else 'lainnya' + if tipe in ('pendapatan', 'beban'): + continue + + # aktiva shown as debit = debit - kredit ; pasiva/modal shown as kredit = kredit - debit + debit_amt = 0.0 + kredit_amt = 0.0 + if tipe == 'aktiva': + saldo = data['debit'] - data['kredit'] + if abs(saldo) < 0.01: + continue + debit_amt = round(saldo,2) if saldo > 0 else 0.0 + kredit_amt = round(abs(saldo),2) if saldo < 0 else 0.0 + else: + saldo = data['kredit'] - data['debit'] + if abs(saldo) < 0.01: + continue + kredit_amt = round(saldo,2) if saldo > 0 else 0.0 + debit_amt = round(abs(saldo),2) if saldo < 0 else 0.0 + + rows.append(html.Tr([html.Td(kode), html.Td(data.get('nama','')), html.Td(format_rupiah(debit_amt) if debit_amt else ''), html.Td(format_rupiah(kredit_amt) if kredit_amt else '')])) + total_debit += debit_amt + total_kredit += kredit_amt + + # Tambahkan laba/rugi ke modal (jika ada) + if abs(laba_rugi) >= 0.01: + # find modal kode if available + modal_kode = KODE_AKUN.get('modal', {}).get('kode') + modal_nama = KODE_AKUN.get('modal', {}).get('nama') + if modal_kode: + # laba (positif) increases modal kredit, rugi increases debit + if laba_rugi > 0: + rows.append(html.Tr([html.Td(modal_kode), html.Td(modal_nama or 'Modal'), html.Td(''), html.Td(format_rupiah(round(laba_rugi,2)))])) + total_kredit += round(laba_rugi,2) + else: + rows.append(html.Tr([html.Td(modal_kode), html.Td(modal_nama or 'Modal'), html.Td(format_rupiah(round(abs(laba_rugi),2))), html.Td('')])) + total_debit += round(abs(laba_rugi),2) + + header = html.Tr([html.Th('Kode'), html.Th('Nama Akun'), html.Th('Debit'), html.Th('Kredit')]) + if rows: + rows.append(html.Tr([html.Td('', colSpan=2, style={'fontWeight': 'bold', 'borderTop': '2px solid #000'}), html.Td(format_rupiah(total_debit), style={'fontWeight': 'bold', 'borderTop': '2px solid #000'}), html.Td(format_rupiah(total_kredit), style={'fontWeight': 'bold', 'borderTop': '2px solid #000'})])) + balans = abs(total_debit - total_kredit) < 0.01 + status = html.P('✅ Neraca Setelah Penutupan Seimbang' if balans else '❌ Neraca Setelah Penutupan Tidak Seimbang', style={'textAlign': 'center', 'fontWeight': 'bold', 'color': COLORS['success'] if balans else COLORS['error']}) + return html.Div([html.H4(f'Neraca Setelah Penutupan per {tanggal}', style={'textAlign': 'center', 'marginBottom': '20px', 'color': COLORS['primary']}), html.Table([header] + rows, className='modern-table', style={'width': '100%'}), status]) + + return html.P('Tidak ada akun non-nominal untuk ditampilkan setelah penutupan', style={'color': '#6c757d'}) + + +@app.callback( + Output('save-neraca-penutupan-status', 'children'), + Input('btn-save-neraca-penutupan', 'n_clicks'), + State('tanggal-neraca-penutupan', 'date'), + prevent_initial_call=True +) +def save_neraca_setelah_penutupan(n_clicks, tanggal): + if not (n_clicks and n_clicks > 0): + return '' + + # Recompute balances (same logic as generate_neraca_setelah_penutupan) + saldo_akun = {} + semua_jurnal = sibal_data.jurnal_umum + sibal_data.jurnal_penyesuaian + for jurnal in semua_jurnal: + kode_debit = jurnal['kode_akun_debit'] + if kode_debit not in saldo_akun: + saldo_akun[kode_debit] = {'debit': 0.0, 'kredit': 0.0, 'nama': jurnal['akun_debit']} + saldo_akun[kode_debit]['debit'] += float(jurnal.get('jumlah_debit', 0) or 0) + + kode_kredit = jurnal['kode_akun_kredit'] + if kode_kredit not in saldo_akun: + saldo_akun[kode_kredit] = {'debit': 0.0, 'kredit': 0.0, 'nama': jurnal['akun_kredit']} + saldo_akun[kode_kredit]['kredit'] += float(jurnal.get('jumlah_kredit', 0) or 0) + + # Compute laba/rugi + pendapatan = {k: v for k, v in saldo_akun.items() if str(k).startswith('4')} + beban = {k: v for k, v in saldo_akun.items() if str(k).startswith('5') or str(k).startswith('6')} + total_pendapatan = sum((v['kredit'] - v['debit']) for v in pendapatan.values()) + total_beban = sum((v['debit'] - v['kredit']) for v in beban.values()) + laba_rugi = total_pendapatan - total_beban + + # Build adjusted balances (exclude nominal accounts) + rows_to_save = [] + total_aktiva = 0.0 + total_pasiva_modal = 0.0 + + for kode, data in sorted(saldo_akun.items()): + kode_str = str(kode) + tipe = 'aktiva' if kode_str.startswith('1') else 'pasiva' if kode_str.startswith('2') else 'modal' if kode_str.startswith('3') else 'pendapatan' if kode_str.startswith('4') else 'beban' if kode_str.startswith('5') or kode_str.startswith('6') else 'lainnya' + if tipe in ('pendapatan', 'beban'): + continue + + if tipe == 'aktiva': + saldo = data['debit'] - data['kredit'] + if abs(saldo) < 0.01: + continue + # aktiva -> saldo_debit + rows_to_save.append({'kode_akun': kode, 'nama_akun': data.get('nama',''), 'saldo_debit': round(saldo,2), 'saldo_kredit': 0.0, 'jenis_akun': tipe}) + total_aktiva += saldo + else: + # pasiva or modal -> saldo_kredit + saldo = data['kredit'] - data['debit'] + if abs(saldo) < 0.01: + continue + rows_to_save.append({'kode_akun': kode, 'nama_akun': data.get('nama',''), 'saldo_debit': 0.0, 'saldo_kredit': round(saldo,2), 'jenis_akun': tipe}) + total_pasiva_modal += saldo + + # Add laba_rugi to modal account + if abs(laba_rugi) >= 0.01: + modal_kode = KODE_AKUN.get('modal', {}).get('kode') + modal_nama = KODE_AKUN.get('modal', {}).get('nama') + if modal_kode: + # find existing modal row + found = next((r for r in rows_to_save if r['kode_akun'] == modal_kode), None) + if found: + if laba_rugi > 0: + found['saldo_kredit'] = round(found.get('saldo_kredit',0) + laba_rugi,2) + else: + found['saldo_debit'] = round(found.get('saldo_debit',0) + abs(laba_rugi),2) + else: + if laba_rugi > 0: + rows_to_save.append({'kode_akun': modal_kode, 'nama_akun': modal_nama or '', 'saldo_debit': 0.0, 'saldo_kredit': round(laba_rugi,2), 'jenis_akun': 'modal'}) + else: + rows_to_save.append({'kode_akun': modal_kode, 'nama_akun': modal_nama or '', 'saldo_debit': round(abs(laba_rugi),2), 'saldo_kredit': 0.0, 'jenis_akun': 'modal'}) + + # Prepare payloads in same format as neraca_setelah_penyesuaian + payloads = [] + for r in rows_to_save: + payloads.append({ + 'tanggal': str(tanggal) if tanggal is not None else None, + 'kode_akun': str(r['kode_akun']), + 'nama_akun': r.get('nama_akun',''), + 'saldo_debit': float(r.get('saldo_debit',0) or 0), + 'saldo_kredit': float(r.get('saldo_kredit',0) or 0), + 'jenis_akun': r.get('jenis_akun',''), + 'keterangan': f'Neraca setelah penutupan per {tanggal}', + 'user_id': sibal_data.active_user_id + }) + + if not payloads: + return html.Div([html.Span('Tidak ada baris disimpan (saldo kosong atau error).', style={'color': COLORS['warning']})]) + + # Optional: delete existing rows for same tanggal + user to avoid duplicates + try: + if sibal_data and getattr(sibal_data, 'active_user_id', None) is not None: + del_resp = supabase_client.table('neraca_setelah_penutup').delete().eq('tanggal', str(tanggal)).eq('user_id', sibal_data.active_user_id).execute() + else: + del_resp = None + except Exception as e: + # proceed but capture warning + del_resp = {'error': str(e)} + + # Insert payloads in batch (use supabase client directly to get feedback) + inserted = 0 + errors = [] + for p in payloads: + try: + resp = supabase_client.table('neraca_setelah_penutup').insert(p).execute() + # supabase-python returns object with .status_code or .data + if getattr(resp, 'status_code', 0) in (200, 201) or getattr(resp, 'data', None): + inserted += 1 + else: + errors.append(str(p.get('kode_akun'))) + except Exception as e: + errors.append(f"{p.get('kode_akun')}:{e}") + + if inserted == 0: + err_msg = 'Tidak ada baris disimpan (saldo kosong atau error).' + if errors: + err_msg += ' Error: ' + '; '.join(errors) + return html.Div([html.Span(err_msg, style={'color': COLORS['warning']})]) + + msg = f"✅ Disimpan {inserted} baris ke tabel 'neraca_setelah_penutup'" + if errors: + msg += f" — gagal menyimpan kode: {', '.join(errors)}" + + return html.Div([html.Span(msg, style={'color': COLORS['success']} )]) + + +@app.callback( + Output('tabel-neraca-saldo-penyesuaian', 'children'), + Input('btn-generate-neraca-saldo-penyesuaian', 'n_clicks'), + State('tanggal-neraca-saldo-penyesuaian', 'date'), + prevent_initial_call=True +) +def generate_neraca_saldo_penyesuaian(n_clicks, tanggal): + if not (n_clicks and n_clicks > 0): + return html.P("Klik 'Generate Neraca Saldo Setelah Penyesuaian' untuk melihat neraca saldo", style={'color': '#6c757d'}) + + # Kumpulkan saldo dari jurnal umum + penyesuaian + saldo_akun = {} + semua_jurnal = sibal_data.jurnal_umum + sibal_data.jurnal_penyesuaian + for jurnal in semua_jurnal: + kode_debit = jurnal['kode_akun_debit'] + if kode_debit not in saldo_akun: + saldo_akun[kode_debit] = {'debit': 0, 'kredit': 0, 'nama': jurnal['akun_debit']} + saldo_akun[kode_debit]['debit'] += jurnal['jumlah_debit'] + + kode_kredit = jurnal['kode_akun_kredit'] + if kode_kredit not in saldo_akun: + saldo_akun[kode_kredit] = {'debit': 0, 'kredit': 0, 'nama': jurnal['akun_kredit']} + saldo_akun[kode_kredit]['kredit'] += jurnal['jumlah_kredit'] + + # Buat baris neraca saldo: tampilkan debit atau kredit sesuai saldo + rows = [] + total_debit = 0 + total_kredit = 0 + + for kode, data in sorted(saldo_akun.items()): + saldo = data['debit'] - data['kredit'] + if saldo > 0: + debit_amt = saldo + kredit_amt = 0 + elif saldo < 0: + debit_amt = 0 + kredit_amt = abs(saldo) + else: + debit_amt = 0 + kredit_amt = 0 + + if debit_amt != 0 or kredit_amt != 0: + rows.append(html.Tr([html.Td(kode), html.Td(data['nama']), html.Td(format_rupiah(debit_amt) if debit_amt else ''), html.Td(format_rupiah(kredit_amt) if kredit_amt else '')])) + total_debit += debit_amt + total_kredit += kredit_amt + + header = html.Tr([html.Th('Kode'), html.Th('Nama Akun'), html.Th('Debit'), html.Th('Kredit')]) + # Tambahkan baris total + if rows: + rows.append(html.Tr([html.Td('', colSpan=2, style={'fontWeight': 'bold', 'borderTop': '2px solid #000'}), html.Td(format_rupiah(total_debit), style={'fontWeight': 'bold', 'borderTop': '2px solid #000'}), html.Td(format_rupiah(total_kredit), style={'fontWeight': 'bold', 'borderTop': '2px solid #000'})])) + balans = abs(total_debit - total_kredit) < 0.01 + status = html.P('✅ Neraca Saldo Seimbang' if balans else '❌ Neraca Saldo Tidak Seimbang', style={'textAlign': 'center', 'fontWeight': 'bold', 'color': COLORS['success'] if balans else COLORS['error']}) + return html.Div([html.H4(f'Neraca Saldo Setelah Penyesuaian per {tanggal}', style={'textAlign': 'center', 'marginBottom': '20px', 'color': COLORS['primary']}), html.Table([header] + rows, className='modern-table', style={'width': '100%'}), status]) + + return html.P('Tidak ada saldo akun untuk ditampilkan', style={'color': '#6c757d'}) + + +@app.callback( + Output('save-neraca-saldo-penyesuaian-status', 'children'), + Input('btn-save-neraca-saldo-penyesuaian', 'n_clicks'), + State('tanggal-neraca-saldo-penyesuaian', 'date'), + prevent_initial_call=True +) +def save_neraca_saldo_penyesuaian(n_clicks, tanggal): + if not (n_clicks and n_clicks > 0): + return '' + + # Recompute saldo per akun (gabungkan jurnal umum + penyesuaian) + saldo_akun = {} + semua_jurnal = sibal_data.jurnal_umum + sibal_data.jurnal_penyesuaian + for jurnal in semua_jurnal: + kode_debit = jurnal.get('kode_akun_debit') + kode_kredit = jurnal.get('kode_akun_kredit') + + if kode_debit: + if kode_debit not in saldo_akun: + saldo_akun[kode_debit] = {'kode': kode_debit, 'nama': jurnal.get('akun_debit'), 'debit': 0.0, 'kredit': 0.0} + saldo_akun[kode_debit]['debit'] += float(jurnal.get('jumlah_debit', 0) or 0) + + if kode_kredit: + if kode_kredit not in saldo_akun: + saldo_akun[kode_kredit] = {'kode': kode_kredit, 'nama': jurnal.get('akun_kredit'), 'debit': 0.0, 'kredit': 0.0} + saldo_akun[kode_kredit]['kredit'] += float(jurnal.get('jumlah_kredit', 0) or 0) + + # Prepare and insert rows into Supabase table 'neraca_setelah_penyesuaian' + inserted = 0 + errors = [] + for kode, data in sorted(saldo_akun.items()): + debit_amt = data['debit'] - 0 + kredit_amt = data['kredit'] - 0 + + # skip zero balances + if abs(debit_amt) < 0.01 and abs(kredit_amt) < 0.01: + continue + + # Compute final balance: if debit > credit => saldo_debit = debit-credit; else saldo_kredit = credit-debit + if debit_amt >= kredit_amt: + saldo_debit = round(debit_amt - kredit_amt, 2) + saldo_kredit = 0.0 + else: + saldo_debit = 0.0 + saldo_kredit = round(kredit_amt - debit_amt, 2) + + # Determine jenis akun from kode (best-effort) + kode_str = str(kode) + jenis = 'aktiva' if kode_str.startswith('1') else 'pasiva' if kode_str.startswith('2') else 'modal' if kode_str.startswith('3') else 'pendapatan' if kode_str.startswith('4') else 'beban' if kode_str.startswith('5') or kode_str.startswith('6') else 'lainnya' + + payload = { + 'tanggal': tanggal, + 'kode_akun': kode, + 'nama_akun': data.get('nama') or '', + 'saldo_debit': float(saldo_debit), + 'saldo_kredit': float(saldo_kredit), + 'jenis_akun': jenis, + 'keterangan': f'Neraca saldo setelah penyesuaian per {tanggal}', + 'user_id': sibal_data.active_user_id + } + + saved = sibal_data._insert_table_data('neraca_setelah_penyesuaian', payload) + if saved: + inserted += 1 + else: + errors.append(str(kode)) + + if inserted == 0: + return html.Div([html.Span('Tidak ada baris disimpan (saldo kosong atau error).', style={'color': COLORS['warning']})]) + + msg = f"✅ Disimpan {inserted} baris ke tabel 'neraca_setelah_penyesuaian'" + if errors: + msg += f" — gagal menyimpan kode: {', '.join(errors)}" + + return html.Div([html.Span(msg, style={'color': COLORS['success']} )]) + def format_currency(amount): """Format currency dengan pemisah ribuan""" if amount == 0: diff --git a/text b/text index d1edaa4..7970d3b 100644 --- a/text +++ b/text @@ -1,4 +1,4 @@ -cd 'C:\Users\ilham\Downloads\Projek-SIBAL-main\Projek-SIBAL-main' +cd 'C:\Users\ilham\Downloads\Projek-SIBA\Projek-SIBAL-main' python -m venv .venv Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope Process -Force .\.venv\Scripts\Activate.ps1 diff --git a/text copy b/text copy new file mode 100644 index 0000000..d1edaa4 --- /dev/null +++ b/text copy @@ -0,0 +1,8 @@ +cd 'C:\Users\ilham\Downloads\Projek-SIBAL-main\Projek-SIBAL-main' +python -m venv .venv +Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope Process -Force +.\.venv\Scripts\Activate.ps1 +pip install -r requirements.txt +pip install authlib +pip freeze > requirements.txt +python sibal.py \ No newline at end of file