from odoo import models, api, fields, _ from odoo.exceptions import UserError from datetime import date, datetime, timedelta # import datetime import logging _logger = logging.getLogger(__name__) from terbilang import Terbilang import pytz from pytz import timezone class DownPayment(models.Model): _name = 'down.payment' _description = 'Down Payment Management' _rec_name = 'number' _inherit = ['mail.thread'] user_id = fields.Many2one('res.users', string='Diajukan Oleh', default=lambda self: self.env.user, tracking=3) number = fields.Char(string='No. Dokumen', default='New Draft', tracking=3) applicant_name = fields.Char(string='Nama Pemohon', tracking=3, required=True) nominal = fields.Float(string='Nominal', tracking=3, required=True) bank_name = fields.Char(string='Bank', tracking=3, required=True) account_name = fields.Char(string='Nama Account', tracking=3, required=True) bank_account = fields.Char(string='No. Rekening', tracking=3, required=True) detail_note = fields.Text(string='Keterangan Penggunaan Rinci', tracking=3) estimated_return_date = fields.Date( string='Estimasi Batas Durasi Pengajuan' ) days_remaining = fields.Integer( string='Due Date', compute='_compute_days_remaining', ) status = fields.Selection([ ('draft', 'Draft'), ('pengajuan1', 'Menunggu Approval Departement'), ('pengajuan2', 'Menunggu Pengecekan AP'), ('pengajuan3', 'Menunggu Approval Pimpinan'), ('approved', 'Approved'), ('reject', 'Rejected') ], string='Status', default='draft', tracking=3, index=True, track_visibility='onchange') last_status = fields.Selection([ ('draft', 'Draft'), ('pengajuan1', 'Menunggu Approval Departement'), ('pengajuan2', 'Menunggu Pengecekan AP'), ('pengajuan3', 'Menunggu Approval Pimpinan'), ('approved', 'Approved'), ('reject', 'Rejected') ], string='Status') status_pay_down_payment = fields.Selection([ ('pending', 'Pending'), ('payment', 'Payment'), ], string='Status Pembayaran', default='pending', tracking=3) departement_type = fields.Selection([ ('sales', 'Sales'), ('merchandiser', 'Merchandiser'), ('marketing', 'Marketing'), ('logistic', 'Logistic'), ('procurement', 'Procurement'), ('fat', 'FAT'), ('hr_ga', 'HR & GA'), ], string='Departement Type', tracking=3, required=True) attachment_file_image = fields.Binary(string='Attachment Image', attachment_filename='attachment_filename_image') attachment_file_pdf = fields.Binary(string='Attachment PDF', attachment_filename='attachment_filename_pdf') attachment_filename_image = fields.Char(string='Filename Image') attachment_filename_pdf = fields.Char(string='Filename PDF') attachment_type = fields.Selection([ ('pdf', 'PDF'), ('image', 'Image'), ], string="Attachment Type", default='pdf') @api.onchange('attachment_type') def _onchange_attachment_type(self): self.attachment_file_image = False self.attachment_filename_image = False self.attachment_file_pdf = False self.attachment_filename_pdf = False # Sales & MD : Darren ID 19 # Marketing : Iwan ID 216 # Logistic & Procurement : Rafly H ID 21 # FAT : Stephan ID 28 # HR & GA : Akbar ID 7 / Pimpinan # --------------------------------------- # AP : Manzila (Finance) ID 23 def _get_departement_approver(self): mapping = { 'sales': 19, 'merchandiser': 19, 'marketing': 216, 'logistic': 21, 'procurement': 21, 'fat': 28, 'hr_ga': 7, } return mapping.get(self.departement_type) def action_realisasi_pum(self): self.ensure_one() realization = self.env['realization.down.payment'].search([('pum_id', '=', self.id)], limit=1) if realization: return { 'type': 'ir.actions.act_window', 'name': 'Realisasi PUM', 'res_model': 'realization.down.payment', 'view_mode': 'form', 'target': 'current', 'res_id': realization.id, } else: return { 'type': 'ir.actions.act_window', 'name': 'Realisasi PUM', 'res_model': 'realization.down.payment', 'view_mode': 'form', 'target': 'current', 'context': { 'default_pum_id': self.id, 'default_value_down_payment': self.nominal, 'default_name': f'Realisasi - {self.number or ""}', 'default_pemberian_line_ids': [ (0, 0, { 'date': self.create_date.date() if self.create_date else fields.Date.today(), 'description': 'Uang Muka', 'value': self.nominal }) ] } } def action_confirm_payment(self): for rec in self: if not rec.attachment_file_image and not rec.attachment_file_pdf: raise UserError('Tidak bisa konfirmasi pembayaran karena belum ada bukti attachment (PDF/Image).') rec.status_pay_down_payment = 'payment' def action_approval_check(self): for record in self: # user = record.user_id user = self.env['res.users'].browse(3401) roles = sorted(set( f"{group.name} (Category: {group.category_id.name})" for group in user.groups_id if group.category_id.name == 'Roles' )) _logger.info(f"[ROLE CHECK] User: {user.name} (Login: {user.login}) Roles: {roles}") return def action_reject(self): # Logic untuk konfirmasi pembayaran return def action_draft(self): for record in self: record.status = record.last_status if record.last_status else 'draft' return @api.depends('create_date') def _compute_days_remaining(self): today = date.today() for rec in self: if rec.create_date: due_date = rec.create_date.date() + timedelta(days=7) rec.days_remaining = (due_date - today).days else: rec.days_remaining = 0 @api.model def create(self, vals): if not vals.get('number') or vals['number'] == 'New Draft': vals['number'] = self.env['ir.sequence'].next_by_code('down.payment') or 'New Draft' if not vals.get('estimated_return_date'): vals['estimated_return_date'] = date.today() + timedelta(days=7) return super(DownPayment, self).create(vals) class RealizationDownPaymentLine(models.Model): _name = 'realization.down.payment.line' _description = 'Rincian Pemberian PUM' realization_id = fields.Many2one('realization.down.payment', string='Realization') date = fields.Date(string='Tanggal', required=True, default=fields.Date.today) description = fields.Char(string='Description', required=True) value = fields.Float(string='Nilai', required=True) class RealizationDownPaymentUseLine(models.Model): _name = 'realization.down.payment.use.line' _description = 'Rincian Penggunaan PUM' realization_id = fields.Many2one('realization.down.payment', string='Realization') date = fields.Date(string='Tanggal', required=True, default=fields.Date.today) description = fields.Char(string='Description', required=True) nominal = fields.Float(string='Nominal', required=True) done_attachment = fields.Boolean(string='Checked', default=False) attachment_type = fields.Selection([ ('pdf', 'PDF'), ('image', 'Image'), ], string="Attachment Type", default='pdf') attachment_file_image = fields.Binary(string='Attachment Image', attachment_filename='attachment_filename_image') attachment_file_pdf = fields.Binary(string='Attachment PDF', attachment_filename='attachment_filename_pdf') attachment_filename_image = fields.Char(string='Filename Image') attachment_filename_pdf = fields.Char(string='Filename PDF') @api.onchange('attachment_type') def _onchange_attachment_type(self): self.attachment_file_image = False self.attachment_filename_image = False self.attachment_file_pdf = False self.attachment_filename_pdf = False @api.onchange('done_attachment') def _onchange_done_attachment(self): if self.done_attachment and self.env.user.id != 23: self.done_attachment = False return { 'warning': { 'title': _('Tidak Diizinkan'), 'message': _('Hanya user AP yang bisa mencentang Done Attachment.') } } # def write(self, vals): # if 'done_attachment' in vals and vals['done_attachment']: # if self.env.user.id != 23: # Manzila (Finance) ID 23 # raise UserError('Hanya user AP yang dapat mencentang Checked.') # return super().write(vals) class RealizationDownPayment(models.Model): _name = 'realization.down.payment' _description = 'Realization Down Payment Management' _inherit = ['mail.thread'] pum_id = fields.Many2one('down.payment', string='No PUM') name = fields.Char(string='Nama', readonly=True, tracking=3) title = fields.Char(string='Judul', tracking=3) goals = fields.Text(string='Tujuan', tracking=3) related = fields.Char(string='Terkait', tracking=3,) pemberian_line_ids = fields.One2many( 'realization.down.payment.line', 'realization_id', string='Rincian Pemberian' ) penggunaan_line_ids = fields.One2many( 'realization.down.payment.use.line', 'realization_id', string='Rincian Penggunaan' ) grand_total = fields.Float(string='Grand Total Pemberian', tracking=3, compute='_compute_grand_total') grand_total_use = fields.Float(string='Grand Total Penggunaan', tracking=3, compute='_compute_grand_total_use') value_down_payment = fields.Float(string='PUM', tracking=3) remaining_value = fields.Float(string='Sisa Uang PUM', tracking=3, compute='_compute_remaining_value') note_approval = fields.Text(string='Note Persetujuan', tracking=3) done_status = fields.Selection([ ('remaining', 'Remaining'), ('done_not_realized', 'Done Not Realized'), ('done_realized', 'Done Realized') ], string='Status Realisasi', tracking=3, default='remaining') currency_id = fields.Many2one( 'res.currency', string='Currency', default=lambda self: self.env.company.currency_id ) attachment_file_image = fields.Binary(string='Attachment Image', attachment_filename='attachment_filename_image') attachment_file_pdf = fields.Binary(string='Attachment PDF', attachment_filename='attachment_filename_pdf') attachment_filename_image = fields.Char(string='Filename Image') attachment_filename_pdf = fields.Char(string='Filename PDF') attachment_type = fields.Selection([ ('pdf', 'PDF'), ('image', 'Image'), ], string="Attachment Type", default='pdf') @api.onchange('attachment_type') def _onchange_attachment_type(self): self.attachment_file_image = False self.attachment_filename_image = False self.attachment_file_pdf = False self.attachment_filename_pdf = False @api.depends('pemberian_line_ids.value') def _compute_grand_total(self): for rec in self: rec.grand_total = sum(line.value for line in rec.pemberian_line_ids) @api.depends('penggunaan_line_ids.nominal') def _compute_grand_total_use(self): for rec in self: rec.grand_total_use = sum(line.nominal for line in rec.penggunaan_line_ids) @api.depends('grand_total', 'grand_total_use') def _compute_remaining_value(self): for rec in self: rec.remaining_value = rec.value_down_payment - rec.grand_total_use def action_validation(self): self.ensure_one() # Validasi hanya AP yang bisa validasi if self.env.user.id != 23: raise UserError('Hanya AP yang dapat melakukan validasi realisasi.') if self.done_status == 'remaining': self.done_status = 'done_not_realized' elif self.done_status == 'done_not_realized': self.done_status = 'done_realized' else: raise UserError('Realisasi sudah berstatus Done Realized.') # Opsional: Tambah log di chatter self.message_post(body=f"Status realisasi diperbarui menjadi {dict(self._fields['done_status'].selection).get(self.done_status)} oleh {self.env.user.name}.")