From ad450980c3f477f65580ac4fb51403a41e902301 Mon Sep 17 00:00:00 2001 From: Ilham Date: Sun, 7 Dec 2025 23:45:35 +0700 Subject: [PATCH] Kartu Perdana --- __pycache__/sibal.cpython-314.pyc | Bin 321023 -> 332240 bytes sibal.py | 197 +++++++++++++++--------------- 2 files changed, 96 insertions(+), 101 deletions(-) diff --git a/__pycache__/sibal.cpython-314.pyc b/__pycache__/sibal.cpython-314.pyc index 56d1b8558150600fb6221cbc2a2b5dd1364991b2..793c84740fdeeb9e3a47756127c8eb97897e90e2 100644 GIT binary patch delta 15835 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 delta 7146 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 fFg4%8e=QoX=&2c!2Iv0*W2Coz diff --git a/sibal.py b/sibal.py index 8c0542b..ad1b026 100644 --- a/sibal.py +++ b/sibal.py @@ -2517,37 +2517,6 @@ 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") @@ -4340,6 +4309,100 @@ def hitung_ulang_semua_saldo(): print("✅ SELESAI menghitung ulang semua saldo") + +def regenerate_kartu_persediaan_from_transactions(): + """Rebuild in-memory `kartu_persediaan` from transaksi_pemasukan and transaksi_pengeluaran. + This does NOT persist results to the database; it only reconstructs the view used by the UI. + """ + print("🔁 Membangun ulang kartu persediaan dari transaksi...") + + # Reset in-memory kartu persediaan for active user + uid = str(sibal_data.active_user_id) if sibal_data.active_user_id else None + # We'll rebuild _all_kartu_persediaan entries only for the active user + # Remove existing entries for this user + sibal_data._all_kartu_persediaan = [item for item in sibal_data._all_kartu_persediaan if str(item.get('user_id')) != str(uid)] + + # Collect relevant transactions + pemasukan_list = [t for t in sibal_data.transaksi_pemasukan if t.get('tipe') == 'pemasukan' and t.get('jenis') == 'penjualan_ikan'] + pengeluaran_list = [t for t in sibal_data.transaksi_pengeluaran if t.get('tipe') == 'pengeluaran' and t.get('jenis') == 'pembelian_persediaan'] + + # Build events list per kode_barang + events = [] + for p in pengeluaran_list: + kode = p.get('kode_barang') or 'IK001' + tanggal = p.get('tanggal') + qty = int(p.get('quantity', p.get('qty', 0)) or 0) + hpp = float(p.get('hpp', p.get('harga_beli', 0)) or 0) + events.append({'tanggal': tanggal, 'kode_barang': kode, 'masuk_qty': qty, 'masuk_harga': hpp, 'keterangan': p.get('keterangan', 'Pembelian')}) + + for s in pemasukan_list: + # For penjualan_ikan we treat as keluar + kode = 'IK001' + tanggal = s.get('tanggal') + qty = int(s.get('quantity', 0) or 0) + events.append({'tanggal': tanggal, 'kode_barang': kode, 'keluar_qty': qty, 'keluar_harga': None, 'keterangan': s.get('keterangan', 'Penjualan')}) + + # Sort events by tanggal (string 'YYYY-MM-DD' expected); stable sort preserves insertion order + events.sort(key=lambda x: x.get('tanggal') or '') + + # Process per kode_barang, compute running saldo + saldo_map = {} + for ev in events: + kode = ev['kode_barang'] + if kode not in saldo_map: + saldo_map[kode] = {'saldo_qty': 0, 'saldo_total': 0.0} + + current = saldo_map[kode] + masuk_qty = int(ev.get('masuk_qty', 0) or 0) + masuk_harga = float(ev.get('masuk_harga', 0) or 0) + keluar_qty = int(ev.get('keluar_qty', 0) or 0) + + masuk_total = masuk_qty * masuk_harga + keluar_total = 0.0 + + # If keluar, compute HPP using current saldo average + if keluar_qty > 0: + if current['saldo_qty'] > 0: + unit_hpp = current['saldo_total'] / current['saldo_qty'] + else: + unit_hpp = 0.0 + keluar_total = keluar_qty * unit_hpp + + # Update saldo + new_saldo_qty = current['saldo_qty'] + masuk_qty - keluar_qty + new_saldo_total = current['saldo_total'] + masuk_total - keluar_total + new_saldo_harga = (new_saldo_total / new_saldo_qty) if new_saldo_qty > 0 else 0.0 + + # Lookup nama barang + barang = next((b for b in sibal_data.master_persediaan if b['kode'] == kode), None) + nama_barang = barang['nama'] if barang else kode + + entry = { + 'tanggal': ev.get('tanggal'), + 'kode_barang': kode, + 'nama_barang': nama_barang, + 'masuk_qty': masuk_qty, + 'masuk_harga': masuk_harga, + 'masuk_total': masuk_total, + 'keluar_qty': keluar_qty, + 'keluar_harga': unit_hpp if keluar_qty > 0 else 0.0, + 'keluar_total': keluar_total, + 'saldo_qty': new_saldo_qty, + 'saldo_harga': new_saldo_harga, + 'saldo_total': new_saldo_total, + 'keterangan': ev.get('keterangan', '') + } + entry['user_id'] = sibal_data.active_user_id + + # Append to in-memory kartu persediaan + sibal_data._all_kartu_persediaan.append(entry) + + # Update map + saldo_map[kode]['saldo_qty'] = new_saldo_qty + saldo_map[kode]['saldo_total'] = new_saldo_total + + print(f"✅ Selesai membangun kartu persediaan ({len(events)} events processed)") + # Callback untuk menampilkan jurnal umum dengan format yang rapi @app.callback( [Output('tabel-jurnal-umum', 'children'), @@ -4728,8 +4791,8 @@ def update_kartu_persediaan(n_clicks): """Update kartu persediaan dengan perhitungan real-time""" print(f"🔄 Memperbarui kartu persediaan...") - # HITUNG ULANG SALDO SEBELUM MENAMPILKAN - hitung_ulang_semua_saldo() + # Bangun ulang kartu persediaan dari transaksi sebelum menampilkan + regenerate_kartu_persediaan_from_transactions() if sibal_data.kartu_persediaan: # Kelompokkan berdasarkan kode barang @@ -4848,74 +4911,6 @@ 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'),