Files
saat-app/addons/saat_invest/models/transaksi.py
Alex Ardhian d657a0a8f0 Aplikasi catat jual-beli saham: Odoo 17 + Postgres 16 + addon saat_invest
- docker-compose.yml: odoo:17.0 (port 8069) + postgres:16 (internal, no publish)
- Addon saat_invest: model saat.saham / saat.transaksi / saat.portofolio
  - P/L computed (realized FIFO, unrealized dari harga_sekarang)
  - action_recalculate() endpoint untuk Flutter
  - seed 5 saham IDX (BBRI, TLKM, BBCA, ASII, PTBA)
- JSON-RPC end-to-end terverifikasi: create beli/jual -> recalculate -> portfolio
  (avg_cost 4525, realized 65000, unrealized 220000, nilai 3840000)
- PLAN.md: rancangan disetujui Bozz
- Login Odoo admin/admin (ganti password master di .env, jangan commit)
2026-09-02 11:23:39 +07:00

52 lines
2.2 KiB
Python

# -*- coding: utf-8 -*-
from odoo import api, fields, models
class SaatTransaksi(models.Model):
"""Catat setiap pembelian / penjualan saham."""
_name = 'saat.transaksi'
_description = "Transaksi Jual/Beli Saham"
_order = 'tanggal desc, id desc'
TIPE = [('buy', 'Beli'), ('sell', 'Jual')]
name = fields.Char(string="Referensi", compute='_compute_name', store=True)
tanggal = fields.Date(string="Tanggal", required=True, default=fields.Date.context_today)
tipe = fields.Selection(TIPE, string="Tipe", required=True, default='buy')
saham_id = fields.Many2one('saat.saham', string="Saham", required=True, ondelete='restrict')
volume = fields.Integer(string="Jumlah Lembar", required=True)
harga = fields.Monetary(string="Harga/Lembar", currency_field='currency_id', required=True)
fee = fields.Monetary(string="Biaya (Komisi/PPN)", currency_field='currency_id', default=0.0)
total = fields.Monetary(
string="Total Biaya", currency_field='currency_id',
compute='_compute_total', store=True)
catatan = fields.Text(string="Catatan")
user_id = fields.Many2one('res.users', string="Pencatat", default=lambda self: self.env.user)
currency_id = fields.Many2one(
'res.currency', string="Mata Uang", default=lambda self: self._default_currency())
@api.model
def _default_currency(self):
# IDR
return self.env.ref('base.IDR', raise_if_not_found=False) or self.env.company.currency_id
@api.depends('tanggal', 'tipe', 'saham_id')
def _compute_name(self):
for r in self:
ticker = r.saham_id.name or ''
tipe_label = dict(self.TIPE).get(r.tipe, '')
r.name = f"{r.tanggal or ''} {tipe_label} {ticker}"
@api.depends('volume', 'harga', 'fee', 'tipe')
def _compute_total(self):
for r in self:
r.total = (r.volume or 0) * (r.harga or 0) + (r.fee or 0)
@api.constrains('volume', 'harga')
def _check_positive(self):
for r in self:
if r.volume <= 0:
raise models.ValidationError("Volume harus lebih dari 0.")
if r.harga < 0:
raise models.ValidationError("Harga tidak boleh negatif.")