diff options
Diffstat (limited to 'indoteknik_custom/models/advance_payment_request.py')
| -rw-r--r-- | indoteknik_custom/models/advance_payment_request.py | 1553 |
1 files changed, 1553 insertions, 0 deletions
diff --git a/indoteknik_custom/models/advance_payment_request.py b/indoteknik_custom/models/advance_payment_request.py new file mode 100644 index 00000000..ec23de63 --- /dev/null +++ b/indoteknik_custom/models/advance_payment_request.py @@ -0,0 +1,1553 @@ +from odoo import models, api, fields, _ +from odoo.exceptions import UserError, ValidationError +from datetime import date, datetime, timedelta +# import datetime +import logging +_logger = logging.getLogger(__name__) +from terbilang import Terbilang +import pytz +from pytz import timezone +import base64 + + +class AdvancePaymentRequest(models.Model): + _name = 'advance.payment.request' + _description = 'Advance Payment Request or Reimburse' + _rec_name = 'number' + _inherit = ['mail.thread', 'mail.activity.mixin'] + _order = 'create_date desc' + + user_id = fields.Many2one('res.users', string='Diajukan Oleh', default=lambda self: self.env.user, tracking=3) + partner_id = fields.Many2one('res.partner', string='Partner', related='user_id.partner_id', readonly=True) + + number = fields.Char(string='No. Dokumen', default='New Draft', tracking=3) + + applicant_name = fields.Many2one('res.users', string='Nama Pemohon', default=lambda self: self.env.user, required=True, tracking=3, domain="[('groups_id', 'in', [1])]") + # applicant_name = fields.One2many(string='Nama Pemohon', related='res.users') + nominal = fields.Float(string='Nominal', tracking=3, required=True) + + bank_name = fields.Char(string='Bank', tracking=3, required=True) + account_name = fields.Many2one('res.users', string='Nama Account', default=lambda self: self.env.user, required=True, tracking=3, domain="[('groups_id', 'in', [1])]") + # 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) + + date_back_to_office = fields.Date( + string='Tanggal Kembali ke Kantor', + tracking=3 + ) + + estimated_return_date = fields.Date( + string='Batas Pengajuan', + help='Tanggal batas maksimal durasi pengajuan realisasi' + ) + + days_remaining = fields.Integer( + string='Sisa Hari Realisasi', + compute='_compute_days_remaining', + help='Sisa hari batas maksimal pengajuan realisasi setelah kembali ke kantor. ' + '7 hari setelah tanggal kembali.' + ) + + status = fields.Selection([ + ('draft', 'Draft'), + ('pengajuan1', 'Menunggu Approval Departement'), + ('pengajuan2', 'Menunggu Approval AP'), + ('pengajuan3', 'Menunggu Approval Pimpinan'), + ('approved', 'Approved'), + ], string='Status', default='draft', tracking=3, index=True, track_visibility='onchange') + + + status_pay_down_payment = fields.Selection([ + ('pending', 'Pending'), + ('payment', 'Payment'), + ], string='Status Pembayaran', default='pending', tracking=3) + + name_approval_departement = fields.Char(string='Approval Departement') + name_approval_ap = fields.Char(string='Approval AP') + email_ap = fields.Char(string = 'Email AP') + name_approval_pimpinan = fields.Char(string='Approval Pimpinan') + + date_approved_department = fields.Datetime(string="Date Approved Department") + date_approved_ap = fields.Datetime(string="Date Approved AP") + date_approved_pimpinan = fields.Datetime(string="Date Approved Pimpinan") + + position_department = fields.Char(string='Position Departement') + position_ap = fields.Char(string='Position AP') + position_pimpinan = fields.Char(string='Position Pimpinan') + + approved_by = fields.Char(string='Approved By', tracking=True, track_visibility='always') + + departement_type = fields.Selection([ + ('sales', 'Sales'), + ('merchandiser', 'Merchandiser'), + ('marketing', 'Marketing'), + ('logistic', 'Logistic'), + ('procurement', 'Procurement'), + ('fat', 'FAT'), + ('it', 'IT'), + ('hr_ga', 'HR & GA'), + ('pimpinan', 'Pimpinan') + ], 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") + + move_id = fields.Many2one('account.move', string='Journal Entries', domain=[('move_type', '=', 'entry')]) + is_cab_visible = fields.Boolean(string='Is Journal Uang Muka Visible', compute='_compute_is_cab_visible') + + + currency_id = fields.Many2one( + 'res.currency', string='Currency', + default=lambda self: self.env.company.currency_id + ) + + type_request = fields.Selection([ + ('pum', 'PUM'), + ('reimburse', 'Reimburse')], string='Tipe Pengajuan', tracking=3) + + position_type = fields.Selection([ + ('staff', 'Staff'), + ('manager', 'Manager'), + ('pimpinan', 'Pimpinan')], string='Jabatan') + + settlement_type = fields.Selection([ + ('no_settlement', 'Belum Realisasi'), + ('settlement', 'Realisasi') + ]) + + is_represented = fields.Boolean(string='Nama Pemohon Berbeda?', default=False) + + apr_perjalanan = fields.Boolean(string = "PUM Perjalanan?", default = False) + reimburse_line_ids = fields.One2many('reimburse.line', 'request_id', string='Rincian Reimburse') + upload_attachment_date = fields.Datetime(string='Upload Attachment Date', tracking=3) + settlement_ids = fields.One2many( + 'advance.payment.settlement', + 'pum_id', + string='Realisasi' + ) + has_settlement = fields.Boolean( + string='Has Settlement', + compute='_compute_has_settlement' + ) + settlement_name = fields.Char( + string="Nama Realisasi", + compute='_compute_settlement_name' + ) + + grand_total_reimburse = fields.Monetary( + string='Total Reimburse', + compute='_compute_grand_total_reimburse', + currency_field='currency_id' + ) + + is_current_user_ap = fields.Boolean( + string="Is Current User AP", + compute='_compute_is_current_user_ap' + ) + + @api.onchange('grand_total_reimburse', 'type_request') + def _onchange_reimburse_line_update_nominal(self): + if self.type_request == 'reimburse': + self.nominal = self.grand_total_reimburse + + def _compute_is_current_user_ap(self): + ap_user_ids = [23, 9468, 16729] + is_ap = self.env.user.id in ap_user_ids + for line in self: + line.is_current_user_ap = is_ap + + @api.depends('reimburse_line_ids.total') + def _compute_grand_total_reimburse(self): + for request in self: + request.grand_total_reimburse = sum(request.reimburse_line_ids.mapped('total')) + + def action_open_create_reimburse_cab(self): + self.ensure_one() + return { + 'type': 'ir.actions.act_window', + 'name': 'Buat Jurnal Reimburse', + 'res_model': 'create.reimburse.cab.wizard', + 'view_mode': 'form', + 'target': 'new', + 'context': { + 'default_request_id': self.id, + 'default_total_reimburse': self.grand_total_reimburse, + } + } + + @api.depends('settlement_ids') + def _compute_has_settlement(self): + for rec in self: + rec.has_settlement = bool(rec.settlement_ids) + + @api.depends('settlement_ids', 'settlement_ids.name') + def _compute_settlement_name(self): + for request in self: + if request.settlement_ids: + request.settlement_name = request.settlement_ids[0].name + else: + request.settlement_name = False + + @api.onchange('is_represented') + def _onchange_is_represented(self): + if self.is_represented: + self.account_name = False + self.applicant_name = False + else: + self.account_name = self.env.user.id + self.applicant_name = self.env.user.id + + @api.onchange('nominal') + def _onchange_nominal_no_minus(self): + if self.nominal and self.nominal < 0: + self.nominal = 0 + return { + 'warning': { + 'title': _('Nominal Tidak Valid'), + 'message': _( + "Nominal tidak boleh diisi minus.\n" + "Nilai di set menjadi nol." + ) + } + } + + def _get_jasper_attachment(self): + self.ensure_one() + report = self.env['ir.actions.report'].browse(1134) # ID Downpayment Report + if not report: + raise UserError("Report Jasper tidak ditemukan.") + + data = report.render_jasper(self.ids, data={})[0] + filename = f"{self.number}.pdf" + return { + 'name': filename, + 'datas': base64.b64encode(data), + 'type': 'binary', + 'mimetype': 'application/pdf', + 'filename': filename, + } + + # def action_send_pum_reminder(self): + # """ + # Kirim email reminder PUM otomatis. + # - PUM Perjalanan: + # - Hari H kembali ke kantor = template 'mail_template_pum_reminder_today' + # - H-2 dari due date = template 'mail_template_pum_reminder_h_2' + # - PUM Non-Perjalanan: + # - H-2 dari due date = template 'mail_template_pum_reminder_h_2' + # """ + # today = date.today() + + # # Penyesuaian 1: Cari semua PUM yang sudah disetujui (bukan draft/reject) + # # Kita tidak filter 'date_back_to_office' di sini lagi. + # pum_ids = self.search([ + # ('status', 'not in', ['draft', 'reject']), + # ('type_request', '=', 'pum') + # ]) + + # template_today = self.env.ref('indoteknik_custom.mail_template_pum_reminder_today', raise_if_not_found=False) + # template_h2 = self.env.ref('indoteknik_custom.mail_template_pum_reminder_h_2', raise_if_not_found=False) + + # if not template_today or not template_h2: + # _logger.warning("Salah satu template email PUM (today/h2) tidak ditemukan.") + # return + + # for pum in pum_ids: + # _logger.info(f"[REMINDER] Memproses PUM {pum.number}") + + # # Penyesuaian 2: Logika ini sudah benar (sesuai update kita sebelumnya) + # # Jika realisasi sudah dibuat, PUM tidak aktif lagi, lewati. + # realization = self.env['advance.payment.settlement'].search([('pum_id', '=', pum.id)], limit=1) + # if realization: + # _logger.info(f"[REMINDER] Lewati PUM {pum.number}, realisasi sudah dibuat.") + # continue + + # if not pum.email_ap or not pum.user_id.partner_id.email: + # _logger.warning(f"[REMINDER] Lewati PUM {pum.number} karena email_ap atau email user kosong.") + # continue + + # # Penyesuaian 3: Logika penentuan Due Date (Wajib) + # due_date = False + # base_date_for_today_check = False # Khusus PUM Perjalanan + + # if pum.apr_perjalanan: + # if pum.date_back_to_office: + # due_date = pum.date_back_to_office + timedelta(days=7) + # base_date_for_today_check = pum.date_back_to_office + # else: + # _logger.warning(f"[REMINDER] Lewati PUM {pum.number} (perjalanan) karena tgl kembali kosong.") + # continue + # else: + # # Ini adalah PUM Non-Perjalanan + # if not pum.create_date: + # _logger.warning(f"[REMINDER] Lewati PUM {pum.number} (non-perjalanan) karena create_date kosong.") + # continue + # base_date = pum.create_date.date() + # due_date = base_date + timedelta(days=7) + + # # Hitung sisa hari + # days_remaining = (due_date - today).days + + # # Penyesuaian 4: Tentukan template berdasarkan sisa hari + # template = False + # if pum.apr_perjalanan and base_date_for_today_check == today: + # # Hari H kembali ke kantor (HANYA PUM Perjalanan) + # template = template_today + # elif days_remaining == 2: + # # H-2 due date (Untuk SEMUA jenis PUM) + # template = template_h2 + # else: + # _logger.info(f"[REMINDER] Lewati PUM {pum.number}, hari ini bukan tgl pengingat (Sisa hari: {days_remaining}).") + # continue + + # # --- Sisanya (Generate attachment & kirim email) sudah aman --- + + # # Generate attachment + # try: + # attachment_vals = pum._get_jasper_attachment() + # attachment = self.env['ir.attachment'].create({ + # 'name': attachment_vals['name'], + # 'type': 'binary', + # 'datas': attachment_vals['datas'], + # 'res_model': 'advance.payment.request', + # 'res_id': pum.id, + # 'mimetype': 'application/pdf', + # }) + # except Exception as e: + # _logger.error(f"[REMINDER] Gagal membuat attachment untuk PUM {pum.number}: {str(e)}") + # continue + + # email_values = { + # # 'email_to': pum.user_id.partner_id.email, + # 'email_to': 'andrifebriyadiputra@gmail.com', # Masih hardcode + # 'email_from': pum.email_ap, + # 'email_from': 'finance@indoteknik.co.id', + # 'attachment_ids': [(6, 0, [attachment.id])] + # } + + # _logger.info(f"[REMINDER] Mengirim email PUM {pum.number} ke {email_values['email_to']} dari {email_values['email_from']}") + + # try: + # body_html = template._render_field('body_html', [pum.id])[pum.id] + # template.send_mail(pum.id, force_send=True, email_values=email_values) + # _logger.info(f"[REMINDER] Email berhasil dikirim untuk PUM {pum.number}") + + # # Post info sederhana + # pum.message_post( + # body="Email Reminder Berhasil dikirimkan", + # message_type="comment", + # subtype_xmlid="mail.mt_note", + # ) + + # user_system = self.env['res.users'].browse(25) + # system_id = user_system.partner_id.id if user_system else False + + # # Post isi email ke chatter + # pum.message_post( + # body=body_html, + # message_type="comment", + # subtype_xmlid="mail.mt_note", + # author_id=system_id, + # ) + # except Exception as e: + # _logger.error(f"[REMINDER] Gagal mengirim email untuk PUM {pum.number}: {str(e)}") + + # return True + + + @api.depends('move_id.state') + def _compute_is_cab_visible(self): + for rec in self: + move = rec.move_id + rec.is_cab_visible = bool(move and move.state == 'posted') + + def action_view_journal_uangmuka(self): + self.ensure_one() + + ap_user_ids = [23, 9468, 16729] + is_it = self.env.user.has_group('indoteknik_custom.group_role_it') + if self.env.user.id not in ap_user_ids and not is_it: + raise UserError('Hanya User AP yang dapat menggunakan fitur ini.') + + if not self.move_id: + raise UserError("Journal Uang Muka belum tersedia.") + + return { + 'name': 'Journal Entry', + 'view_mode': 'form', + 'res_model': 'account.move', + 'type': 'ir.actions.act_window', + 'res_id': self.move_id.id, + 'target': 'current', + } + + @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 & IT : Stephan ID 28 + # HR & GA : Akbar ID 7 / Pimpinan + # --------------------------------------- + # AP : Manzila (Finance) ID 23 + + def _get_approver_mapping(self): + return { + 'sales': 19, + 'merchandiser': 19, + 'marketing': 216, + 'logistic': 21, + 'procurement': 21, + 'fat': 28, + 'it': 28, + 'hr_ga': 7, + } + + def _get_departement_approver(self): + mapping = self._get_approver_mapping() + return mapping.get(self.departement_type) + + @api.constrains('apr_perjalanan', 'date_back_to_office') + def _check_date_back_to_office(self): + if self.apr_perjalanan and not self.date_back_to_office: + raise ValidationError("Tanggal Kembali ke Kantor wajib diisi jika PUM Perjalanan dicentang.") + + @api.onchange('applicant_name') + def _onchange_applicant_name_set_position(self): + if self.applicant_name: + user_id = self.applicant_name.id + mapping = self._get_approver_mapping() + manager_ids = set(mapping.values()) + pimpinan_id = 7 + if user_id == pimpinan_id: + self.position_type = 'pimpinan' + elif user_id in manager_ids: + self.position_type = 'manager' + else: + self.position_type = 'staff' + else: + self.position_type = False + + # @api.model + # def default_get(self, fields_list): + # defaults = super(AdvancePaymentRequest, self).default_get(fields_list) + # user_id = defaults.get('user_id', self.env.uid) + # mapping = self._get_approver_mapping() + # manager_ids = set(mapping.values()) + # pimpinan_id = 7 + + # position = 'staff' + # if user_id == pimpinan_id: + # position = 'pimpinan' + # elif user_id in manager_ids: + # position = 'manager' + + # defaults['position_type'] = position + # return defaults + + def action_realisasi_pum(self): + self.ensure_one() + + realization = self.env['advance.payment.settlement'].search([('pum_id', '=', self.id)], limit=1) + + if realization: + return { + 'type': 'ir.actions.act_window', + 'name': 'Realisasi PUM', + 'res_model': 'advance.payment.settlement', + 'view_mode': 'form', + 'target': 'current', + 'res_id': realization.id, + } + else: + return { + 'type': 'ir.actions.act_window', + 'name': 'Realisasi PUM', + 'res_model': 'advance.payment.settlement', + '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): + # jakarta_tz = pytz.timezone('Asia/Jakarta') + # now = datetime.now(jakarta_tz).replace(tzinfo=None) + + ap_user_ids = [23, 9468, 16729] + if self.env.user.id not in ap_user_ids: + raise UserError('Hanya User AP yang dapat menggunakan fitur ini.') + + for rec in self: + if not rec.attachment_file_image and not rec.attachment_file_pdf: + raise UserError( + f'Tidak bisa konfirmasi pembayaran {rec.number or ""} ' + f'karena belum ada bukti attachment (PDF/Image).' + ) + + rec.status_pay_down_payment = 'payment' + rec.upload_attachment_date = datetime.utcnow() + + rec.message_post( + body="Bukti transfer telah di upload oleh <b>Finance AP</b>.", + message_type="comment", + subtype_xmlid="mail.mt_note", + ) + + + + # 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_approval_check(self): + jakarta_tz = pytz.timezone('Asia/Jakarta') + now = datetime.now(jakarta_tz).replace(tzinfo=None) + formatted_date = now.strftime('%d %B %Y %H:%M') + + for rec in self: + if not rec.departement_type: + raise UserError("Field 'departement_type' wajib diisi sebelum approval.") + + approver_id = rec._get_departement_approver() + + if rec.status == 'pengajuan1': + if self.env.user.id != approver_id: + raise UserError("Hanya approver departement yang berhak menyetujui tahap ini.") + rec.name_approval_departement = self.env.user.name + rec.approved_by = (rec.approved_by + ', ' if rec.approved_by else '') + rec.name_approval_departement + rec.date_approved_department = now + + # Mapping posisi berdasarkan departement_type + department_titles = { + 'sales': 'Sales Manager', + 'merchandiser': 'Merchandiser Manager', + 'marketing': 'Marketing Manager', + 'logistic': 'Logistic Manager', + 'procurement': 'Procurement Manager', + 'fat': 'Finance & Accounting Manager', + 'it': 'IT Manager', + 'hr_ga': 'HR & GA Manager', + 'pimpinan': 'Pimpinan', + } + rec.position_department = department_titles.get(rec.departement_type, 'Departement Manager') + + rec.status = 'pengajuan2' + + rec.message_post( + body=f"Approval <b>Departement</b> oleh <b>{self.env.user.name}</b> " + f"pada <i>{formatted_date}</i>." + ) + + elif rec.status == 'pengajuan2': + ap_user_ids = [23, 9468, 16729] # List user ID yang boleh approve sebagai Finance AP + if self.env.user.id not in ap_user_ids: + raise UserError("Hanya AP yang berhak menyetujui tahap ini.") + rec.name_approval_ap = self.env.user.name + rec.approved_by = (rec.approved_by + ', ' if rec.approved_by else '') + rec.name_approval_ap + rec.email_ap = self.env.user.email + rec.date_approved_ap = now + rec.position_ap = 'Finance AP' + if rec.position_type == 'pimpinan': + rec.status = 'approved' + else: + rec.status = 'pengajuan3' + + rec.message_post( + body=f"Approval <b>AP</b> oleh <b>{self.env.user.name}</b> " + f"pada <i>{formatted_date}</i>." + ) + + elif rec.status == 'pengajuan3': + if self.env.user.id != 7: # ID user Pimpinan + raise UserError("Hanya Pimpinan yang berhak menyetujui tahap ini.") + rec.name_approval_pimpinan = self.env.user.name + rec.approved_by = (rec.approved_by + ', ' if rec.approved_by else '') + rec.name_approval_pimpinan + rec.date_approved_pimpinan = now + rec.position_pimpinan = 'Pimpinan' + rec.status = 'approved' + + rec.message_post( + body=f"Approval <b>Pimpinan</b> oleh <b>{self.env.user.name}</b> " + f"pada <i>{formatted_date}</i>." + ) + + else: + raise UserError("Status saat ini tidak bisa di-approve lagi.") + + # rec.message_post(body=f"Approval oleh {self.env.user.name} pada tahap <b>{rec.status}</b>.") + + + + def action_ap_only(self): + self.ensure_one() + + ap_user_ids = [23, 9468, 16729] # Ganti sesuai kebutuhan + if self.env.user.id not in ap_user_ids: + raise UserError('Hanya User AP yang dapat menggunakan fitur ini.') + + if self.move_id: + raise UserError('CAB / Jurnal sudah pernah dibuat untuk PUM ini.') + + return { + 'name': 'Create CAB AP Only', + 'type': 'ir.actions.act_window', + 'res_model': 'advance.payment.create.bill', + 'view_mode': 'form', + 'target': 'new', + 'context': { + 'default_nominal': self.nominal, + 'default_apr_id': self.id, + } + } + + + @api.depends('date_back_to_office', 'status', 'apr_perjalanan', 'create_date', 'settlement_ids.status', 'type_request') + def _compute_days_remaining(self): + today = date.today() + for rec in self: + + current_days = rec.days_remaining or 0 + current_due_date = rec.estimated_return_date or False + if rec.type_request == 'pum': + is_settlement_approved = any(s.status == 'approved' for s in rec.settlement_ids) + if not is_settlement_approved: + due_date = False + + if rec.apr_perjalanan: + # Alur PUM Perjalanan + if rec.date_back_to_office: + due_date = rec.date_back_to_office + timedelta(days=7) + effective_today = max(today, rec.date_back_to_office) + + current_due_date = due_date + current_days = (due_date - effective_today).days + else: + + current_due_date = False + current_days = 0 + else: + # Alur PUM Non-Perjalanan + if rec.create_date: + base_date = rec.create_date.date() + due_date = base_date + timedelta(days=7) + + current_due_date = due_date + current_days = (due_date - today).days + else: + current_due_date = False + current_days = 0 + else: + current_due_date = False + current_days = 0 + + rec.days_remaining = current_days + rec.estimated_return_date = current_due_date + + @api.onchange('date_back_to_office') + def _onchange_date_back_to_office(self): + if self.date_back_to_office and self.date_back_to_office < date.today(): + return { + 'warning': { + 'title': _('Tanggal Tidak Valid'), + 'message': _('Tanggal kembali ke kantor tidak boleh lebih awal dari hari ini.') + } + } + + @api.onchange('applicant_name') + def _onchange_applicant_name(self): + if self.applicant_name: + self.account_name = self.applicant_name.id + + @api.onchange('account_name') + def _onchange_account_name(self): + if self.account_name: + self.applicant_name = self.account_name.id + + @api.onchange('user_id') + def _onchange_user_id_limit_check(self): + if self.type_request != 'pum': + return + if not self.user_id: + return + + pum_ids = self.search([ + ('user_id', '=', self.user_id.id), + ('status', '!=', 'reject'), + ('type_request', '=', 'pum') + ]) + + active_pum_count = 0 + for pum in pum_ids: + realization = self.env['advance.payment.settlement'].search([('pum_id', '=', pum.id)], limit=1) + if not realization: + active_pum_count += 1 + + if active_pum_count >= 2: + return { + 'warning': { + 'title': 'Batas Pengajuan Tercapai', + 'message': 'User ini sudah memiliki 2 PUM aktif. Tidak dapat mengajukan lagi sampai salah satu direalisasi.', + } + } + + def _get_department_titles_mapping(self): + return { + 'sales': 'Sales Manager', + 'merchandiser': 'Merchandiser Manager', + 'marketing': 'Marketing Manager', + 'logistic': 'Logistic Manager', + 'procurement': 'Procurement Manager', + 'fat': 'Finance & Accounting Manager', + 'it': 'IT Manager', + 'hr_ga': 'HR & GA Manager', + } + + @api.model + def create(self, vals): + jakarta_tz = pytz.timezone('Asia/Jakarta') + now = datetime.now(jakarta_tz).replace(tzinfo=None) + user = self.env.user + + pum_ids = self.search([ + ('user_id', '=', user.id), + ('status', '!=', 'reject'), + ('type_request', '=', 'pum') + ]) + + active_pum_count = 0 + for pum in pum_ids: + realization = self.env['advance.payment.settlement'].search([('pum_id', '=', pum.id)], limit=1) + if not realization: + active_pum_count += 1 + + if active_pum_count >= 2 and vals.get('type_request') == 'pum': + raise UserError("Anda hanya dapat mengajukan maksimal 2 PUM aktif. Silakan realisasikan salah satunya terlebih dahulu.") + + if not vals.get('apr_perjalanan'): + if 'estimated_return_date' not in vals: + today = date.today() + due_date = today + timedelta(days=7) + vals['estimated_return_date'] = due_date + + initial_status = '' + position = vals.get('position_type') + department = vals.get('departement_type') + if department == 'hr_ga' or position in ('manager', 'pimpinan'): + initial_status = 'pengajuan2' + else: + initial_status = 'pengajuan1' + + vals['status'] = initial_status + + if initial_status == 'pengajuan2' and department != 'hr_ga': + applicant_name = vals.get('applicant_name') + vals['name_approval_departement'] = self.env['res.users'].browse(applicant_name).name or '' + vals['date_approved_department'] = now + department_type = vals.get('departement_type') + department_titles = self._get_department_titles_mapping() + vals['position_department'] = department_titles.get(department_type, 'Departement Manager') + + if position == 'pimpinan' and department != 'hr_ga': + vals['name_approval_pimpinan'] = self.env['res.users'].browse(vals.get('applicant_name')).name or '' + vals['position_pimpinan'] = 'Pimpinan' + vals['date_approved_pimpinan'] = now + # if position == 'staff': + # initial_status = 'pengajuan1' + # elif position == 'manager': + # initial_status = 'pengajuan2' + # applicant_name = vals.get('applicant_name') + # vals['name_approval_departement'] = self.env['res.users'].browse(applicant_name).name or '' + # vals['date_approved_department'] = now + # department_type = vals.get('departement_type') + # department_titles = self._get_department_titles_mapping() + # vals['position_department'] = department_titles.get(department_type, 'Departement Manager') + # elif position == 'pimpinan': + # initial_status = 'pengajuan2' + # applicant_name = vals.get('applicant_name') + # vals['name_approval_pimpinan'] = self.env['res.users'].browse(applicant_name).name or '' + # vals['position_pimpinan'] = 'Pimpinan' + # vals['date_approved_pimpinan'] = now + + # vals['status'] = initial_status + + if not vals.get('number') or vals['number'] == 'New Draft': + if vals.get('type_request') == 'reimburse': + vals['number'] = self.env['ir.sequence'].next_by_code('reimburse.request') or 'New Draft' + else: + vals['number'] = self.env['ir.sequence'].next_by_code('advance.payment.request') or 'New Draft' + + # vals['status'] = 'pengajuan1' + # return super(AdvancePaymentRequest, self).create(vals) + rec = super(AdvancePaymentRequest, self).create(vals) + if rec.type_request == 'reimburse': + rec._compute_grand_total_reimburse() + rec.nominal = rec.grand_total_reimburse + return rec + + +class AdvancePaymentUsageLine(models.Model): + _name = 'advance.payment.usage.line' + _description = 'Advance Payment Usage Line' + + realization_id = fields.Many2one('advance.payment.settlement', string='Realization') + date = fields.Date(string='Tanggal', required=True, default=fields.Date.today) + description = fields.Text(string='Description', required=True) + nominal = fields.Float(string='Nominal', required=True) + done_attachment = fields.Boolean(string='Checked', default=False) + + lot_of_attachment = fields.Selection( + related='realization_id.lot_of_attachment', + string='Lot of Attachment (Related)', + store=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') + + account_id = fields.Many2one( + 'account.account', string='Jenis Biaya', tracking=3 # ID Jenis Biaya yang dibutuhkan + ) + # domain="[('id', 'in', [484, 486, 488, 506, 507, 625, 471, 519, 527, 528, 529, 530, 565])]" # ID Jenis Biaya yang dibutuhkan + + is_current_user_ap = fields.Boolean( + string="Is Current User AP", + compute='_compute_is_current_user_ap' + ) + + def _compute_is_current_user_ap(self): + ap_user_ids = [23, 9468, 16729] + is_ap = self.env.user.id in ap_user_ids + for line in self: + line.is_current_user_ap = is_ap + + # @api.onchange('account_id') + # def _onchange_account_id(self): + # for rec in self: + # if rec.account_id: + # rec.description = rec.account_id.name + " - " + + @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): + ap_user_ids = [23, 9468, 16729] # List user ID yang boleh approve sebagai Finance AP + + if self.done_attachment and self.env.user.id not in ap_user_ids: + self.done_attachment = False + return { + 'warning': { + 'title': _('Tidak Diizinkan'), + 'message': _('Hanya user AP yang bisa mencentang Done Attachment.') + } + } + + @api.onchange('nominal') + def _onchange_nominal_no_minus(self): + if self.nominal and self.nominal < 0: + self.nominal = 0 + return { + 'warning': { + 'title': _('Nominal Tidak Valid'), + 'message': _( + "Nominal penggunaan PUM tidak boleh diisi minus.\n" + "Nilai di Set menjadi nol." + ) + } + } + +class ReimburseLine(models.Model): + _name = 'reimburse.line' + _description = 'Reimburse Line' + + request_id = fields.Many2one('advance.payment.request', string='Request') + date = fields.Date(string='Tanggal', required=True, default=fields.Date.today) + account_id = fields.Many2one( + 'account.account', + string='Jenis Biaya', tracking=3 + ) + description = fields.Text(string='Description', required=True, tracking=3) + distance_departure = fields.Float(string='Pergi (Km)', tracking=3) + distance_return = fields.Float(string='Pulang (Km)', tracking=3) + quantity = fields.Float(string='Quantity', tracking=3, default=1) + price_unit = fields.Float(string='Price', tracking=3) + total = fields.Float(string='Total', tracking=3, compute='_compute_total') + # total = fields.Float(string='Total', tracking=3) + currency_id = fields.Many2one(related='request_id.currency_id') + + is_vehicle = fields.Boolean(string='Berkendara?') + vehicle_type = fields.Selection([ + ('motor', 'Motor'), + ('car', 'Mobil'), + ], string='Tipe Kendaraan', tracking=3) + + attachment_image = fields.Binary(string='Image', attachment_filename='attachment_name_image') + attachment_pdf = fields.Binary(string='PDF', attachment_filename='attachment_name_pdf') + attachment_name_image = fields.Char(string='Filename Image') + attachment_name_pdf = fields.Char(string='Filename PDF') + + attachment_type = fields.Selection([ + ('pdf', 'PDF'), + ('image', 'Image'), + ], string="Attachment Type") + + is_checked = fields.Boolean(string='Checked', default=False) + + is_current_user_ap = fields.Boolean( + string="Is Current User AP", + compute='_compute_is_current_user_ap' + ) + + def _compute_is_current_user_ap(self): + ap_user_ids = [23, 9468, 16729] + is_ap = self.env.user.id in ap_user_ids + for line in self: + line.is_current_user_ap = is_ap + + @api.depends('quantity', 'price_unit', 'is_vehicle') + def _compute_total(self): + for line in self: + line.total = line.quantity * line.price_unit + + @api.onchange('is_vehicle', 'vehicle_type', 'distance_departure', 'distance_return') + def _onchange_vehicle_data(self): + if not self.is_vehicle: + self.vehicle_type = False + self.distance_departure = 0 + self.distance_return = 0 + self.price_unit = 0 + return + + total_distance = self.distance_departure + self.distance_return + + if self.vehicle_type and total_distance > 0: + biaya_per_km = 0 + if self.vehicle_type == 'car': + biaya_per_km = 1000 # Rp 10.000 / 10 km + self.price_unit = biaya_per_km + elif self.vehicle_type == 'motor': + biaya_per_km = 500 # Rp 10.000 / 20 km + self.price_unit = biaya_per_km + self.total = total_distance * biaya_per_km + self.quantity = total_distance + else: + self.total = 0 + +class AdvancePaymentSettlement(models.Model): + _name = 'advance.payment.settlement' + _description = 'Advance Payment Settlement' + _inherit = ['mail.thread'] + _rec_name = 'name' + _order = 'create_date desc' + + pum_id = fields.Many2one('advance.payment.request', string='No PUM', ondelete='cascade') + 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='Dok. Terkait', tracking=3) + + # pemberian_line_ids = fields.One2many( + # 'advance.payment.settlement.line', 'realization_id', string='Rincian Pemberian' + # ) + penggunaan_line_ids = fields.One2many( + 'advance.payment.usage.line', 'realization_id', string='Rincian Penggunaan' + ) + + nominal_pum = fields.Float( + string='Nominal Pemberian PUM', + related='pum_id.nominal', + readonly=True ) + + # 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') + + def _get_default_note_approval(self): + template = ( + "Demikian dokumen Realisasi Uang Muka ini saya buat, dengan ini saya meminta persetujuan dibawah atas hasil penggunaan uang muka yang saya gunakan untuk kebutuhan realisasi " + ) + return template + + note_approval = fields.Text(string='Note Persetujuan', tracking=3, default=_get_default_note_approval) + + name_approval_departement = fields.Char(string='Approval Departement') + name_approval_ap = fields.Char(string='Approval AP') + name_approval_pimpinan = fields.Char(string='Approval Pimpinan') + + date_approved_department = fields.Datetime(string="Date Approved Department") + date_approved_ap = fields.Datetime(string="Date Approved AP") + date_approved_pimpinan = fields.Datetime(string="Date Approved Pimpinan") + + position_department = fields.Char(string='Position Departement') + position_ap = fields.Char(string='Position AP') + position_pimpinan = fields.Char(string='Position Pimpinan') + + approved_by = fields.Char(string='Approved By', track_visibility='always') + + status = fields.Selection([ + ('pengajuan1', 'Menunggu Approval Departement'), + ('pengajuan2', 'Menunggu Approval AP'), + ('pengajuan3', 'Menunggu Approval Pimpinan'), + ('approved', 'Approved'), + ], string='Status', default='pengajuan1', tracking=3, index=True, track_visibility='onchange') + + # --- DIHAPUS --- + # done_status = fields.Selection([ + # ('remaining', 'Remaining'), + # ('done_not_realized', 'Done Not Realized'), + # ('done_realized', 'Done Realized') + # ], string='Status Realisasi', tracking=3, default='remaining') + # date_done_not_realized = fields.Date(string='Tanggal Done Not Realized', tracking=3) + # --- BATAS DIHAPUS --- + + 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') + + lot_of_attachment = fields.Selection([ + ('one_for_all_line', '1 Attachment Untuk Semua Line Penggunaan PUM'), + ('one_for_one_line', '1 Attachment per 1 Line Penggunaan PUM'), + ], string = "Banyaknya Attachment", default='one_for_one_line') + + move_id = fields.Many2one('account.move', string='Journal Entries', domain=[('move_type', '=', 'entry')]) + is_cab_visible = fields.Boolean(string='Is Journal Uang Muka Visible', compute='_compute_is_cab_visible') + + user_id = fields.Many2one( + 'res.users', + string='Diajukan Oleh', + related='pum_id.user_id', + readonly=True + ) + applicant_name = fields.Many2one( + 'res.users', + string='Nama Pemohon', + related='pum_id.applicant_name', + readonly=True + ) + is_current_user_ap = fields.Boolean( + string="Is Current User AP", + compute='_compute_is_current_user_ap' + ) + + def _compute_is_current_user_ap(self): + ap_user_ids = [23, 9468, 16729] + is_ap = self.env.user.id in ap_user_ids + for line in self: + line.is_current_user_ap = is_ap + + def action_toggle_check_attachment(self): + ap_user_ids = [23, 9468, 16729] + if self.env.user.id not in ap_user_ids: + raise UserError('Hanya User AP yang dapat menggunakan tombol ini.') + + for rec in self: + if not rec.penggunaan_line_ids: + continue + + if all(line.done_attachment for line in rec.penggunaan_line_ids): + for line in rec.penggunaan_line_ids: + line.done_attachment = False + else: + for line in rec.penggunaan_line_ids: + line.done_attachment = True + + @api.onchange('lot_of_attachment') + def _onchange_lot_of_attachment(self): + if self.lot_of_attachment == 'one_for_all_line': + for line in self.penggunaan_line_ids: + line.attachment_file_pdf = False + line.attachment_file_image = False + line.attachment_filename_pdf = False + line.attachment_filename_image = False + + + @api.depends('move_id.state') + def _compute_is_cab_visible(self): + for rec in self: + move = rec.move_id + rec.is_cab_visible = bool(move and move.state == 'posted') + + def action_view_journal_uangmuka(self): + self.ensure_one() + + ap_user_ids = [23, 9468, 16729] + is_it = self.env.user.has_group('indoteknik_custom.group_role_it') + if self.env.user.id not in ap_user_ids and not is_it: + raise UserError('Hanya User AP yang dapat menggunakan fitur ini.') + + if not self.move_id: + raise UserError("Journal Uang Muka belum tersedia.") + + return { + 'name': 'Journal Entry', + 'view_mode': 'form', + 'res_model': 'account.move', + 'type': 'ir.actions.act_window', + 'res_id': self.move_id.id, + 'target': 'current', + } + + + @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('nominal_pum', 'grand_total_use') + def _compute_remaining_value(self): + for rec in self: + rec.remaining_value = rec.nominal_pum - rec.grand_total_use + return + + # --- DIHAPUS --- + # def action_validation(self): + # self.ensure_one() + + # # Validasi hanya AP yang bisa validasi + # ap_user_ids = [23, 9468] # List user ID yang boleh approve sebagai Finance AP + # if self.env.user.id not in ap_user_ids: + # raise UserError('Hanya AP yang dapat melakukan validasi realisasi.') + + # if self.done_status == 'remaining': + # self.done_status = 'done_not_realized' + # self.date_done_not_realized = fields.Date.today() + # 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 <b>{dict(self._fields['done_status'].selection).get(self.done_status)}</b> oleh {self.env.user.name}.") + # --- BATAS DIHAPUS --- + + def action_cab(self): + self.ensure_one() + + ap_user_ids = [23, 9468, 16729] # List user ID yang boleh approve sebagai Finance AP + if self.env.user.id not in ap_user_ids: + raise UserError('Hanya User AP yang dapat menggunakan ini.') + if self.move_id: + raise UserError("CAB / Jurnal sudah pernah dibuat untuk Realisasi ini.") + + if not self.pum_id or not self.pum_id.move_id: + raise UserError("PUM terkait atau CAB belum tersedia.") + + # partner_id = self.pum_id.user_id.partner_id.id + partner_id = self.pum_id.applicant_name.partner_id.id + cab_move = self.pum_id.move_id + + # Account Bank Intransit dari CAB: + bank_intransit_line = cab_move.line_ids.filtered(lambda l: l.account_id.id in [573, 389, 392]) + if not bank_intransit_line: + raise UserError("Account Bank Intransit dengan tidak ditemukan di CAB terkait.") + account_sisa_pum = bank_intransit_line[0].account_id.id + + # Account Uang Muka Operasional + account_uang_muka = 403 + + # --- PENYESUAIAN LOGIKA --- + # Tanggal pakai create_date atau hari ini + account_date = fields.Date.context_today(self) + # --- BATAS PENYESUAIAN --- + + ref_label = f"Realisasi {self.pum_id.number} {self.pum_id.detail_note} ({cab_move.name})" + + label_sisa_pum = f"Sisa PUM {self.pum_id.detail_note} {self.pum_id.number} ({cab_move.name})" + + lines = [] + + # Sisa PUM (Debit) + if self.remaining_value > 0: + lines.append((0, 0, { + 'account_id': account_sisa_pum, + 'partner_id': partner_id, + 'name': label_sisa_pum, + 'debit': self.remaining_value, + 'credit': 0, + })) + + # Biaya Penggunaan (Debit) + total_biaya = 0 + for line in self.penggunaan_line_ids: + lines.append((0, 0, { + 'account_id': line.account_id.id, + 'partner_id': partner_id, + 'name': f"{line.description} ({line.date})", + 'debit': line.nominal, + 'credit': 0, + })) + total_biaya += line.nominal + + # Uang Muka Operasional (Credit) + total_credit = self.remaining_value + total_biaya + if total_credit > 0: + lines.append((0, 0, { + 'account_id': account_uang_muka, + 'partner_id': partner_id, + 'name': ref_label, + 'debit': 0, + 'credit': total_credit, + })) + + move = self.env['account.move'].create({ + 'ref': ref_label, + 'date': account_date, + 'journal_id': 11, # MISC + 'line_ids': lines, + }) + + # self.message_post(body=f"Jurnal CAB telah dibuat dengan nomor: <b>{move.name}</b>.") + + self.move_id = move.id + + return { + 'name': _('Journal Entry'), + 'view_mode': 'form', + 'res_model': 'account.move', + 'type': 'ir.actions.act_window', + 'res_id': move.id, + 'target': 'current', + } + + def action_approval_check(self): + jakarta_tz = pytz.timezone('Asia/Jakarta') + now = datetime.now(jakarta_tz).replace(tzinfo=None) + formatted_date = now.strftime('%d %B %Y %H:%M') + + for rec in self: + if not rec.pum_id.departement_type: + raise UserError("Field 'departement_type' wajib diisi sebelum approval.") + + approver_id = rec.pum_id._get_departement_approver() + + if rec.status == 'pengajuan1': + if self.env.user.id != approver_id: + raise UserError("Hanya approver departement yang berhak menyetujui tahap ini.") + rec.name_approval_departement = self.env.user.name + rec.approved_by = (rec.approved_by + ', ' if rec.approved_by else '') + rec.name_approval_departement + rec.date_approved_department = now + + # Mapping posisi berdasarkan departement_type + department_titles = { + 'sales': 'Sales Manager', + 'merchandiser': 'Merchandiser Manager', + 'marketing': 'Marketing Manager', + 'logistic': 'Logistic Manager', + 'procurement': 'Procurement Manager', + 'fat': 'Finance & Accounting Manager', + 'it': 'IT Manager', + 'hr_ga': 'HR & GA Manager', + 'pimpinan': 'Pimpinan', + } + rec.position_department = department_titles.get(rec.pum_id.departement_type, 'Departement Manager') + + rec.status = 'pengajuan2' + + rec.message_post( + body=f"Approval <b>Departement</b> oleh <b>{self.env.user.name}</b> " + f"pada <i>{formatted_date}</i>." + ) + + elif rec.status == 'pengajuan2': + ap_user_ids = [23, 9468, 16729] # List user ID yang boleh approve sebagai Finance AP + if self.env.user.id not in ap_user_ids: + raise UserError("Hanya AP yang berhak menyetujui tahap ini.") + rec.name_approval_ap = self.env.user.name + rec.approved_by = (rec.approved_by + ', ' if rec.approved_by else '') + rec.name_approval_ap + rec.date_approved_ap = now + rec.position_ap = 'Finance AP' + rec.status = 'pengajuan3' + + rec.message_post( + body=f"Approval <b>AP</b> oleh <b>{self.env.user.name}</b> " + f"pada <i>{formatted_date}</i>." + ) + + elif rec.status == 'pengajuan3': + if self.env.user.id != 7: # ID user Pimpinan + raise UserError("Hanya Pimpinan yang berhak menyetujui tahap ini.") + rec.name_approval_pimpinan = self.env.user.name + rec.approved_by = (rec.approved_by + ', ' if rec.approved_by else '') + rec.name_approval_pimpinan + rec.date_approved_pimpinan = now + rec.position_pimpinan = 'Pimpinan' + rec.status = 'approved' + # --- DIHAPUS --- + # rec.done_status = 'done_not_realized' # Set status done untuk realisasi + # --- BATAS DIHAPUS --- + + rec.message_post( + body=f"Approval <b>Pimpinan</b> oleh <b>{self.env.user.name}</b> " + f"pada <i>{formatted_date}</i>." + ) + + else: + raise UserError("Status saat ini tidak bisa di-approve lagi.") + + # rec.message_post(body=f"Approval oleh {self.env.user.name} pada tahap <b>{rec.status}</b>.") + + def _check_remaining_value(self): + for rec in self: + # Cek sisa PUM + if rec.remaining_value < 0: + raise ValidationError( + "Sisa uang PUM tidak boleh kurang dari Rp 0.\n" + "Jika ada penggunaan uang pribadi, maka ajukan dengan sistem reimburse." + ) + + @api.model + def create(self, vals): + jakarta_tz = pytz.timezone('Asia/Jakarta') + # --- PENYESUAIAN LOGIKA WAKTU --- + now = datetime.now(jakarta_tz).replace(tzinfo=None) + # Gunakan fields.Datetime.now() agar konsisten + # now = fields.Datetime.now() + # --- BATAS PENYESUAIAN --- + + pum_id = vals.get('pum_id') + initial_status = '' + if pum_id: + pum_request = self.env['advance.payment.request'].browse(pum_id) + if pum_request: + position_dari_pum = pum_request.position_type + department_dari_pum = pum_request.departement_type + if position_dari_pum == 'staff': + if department_dari_pum == 'hr_ga': + initial_status = 'pengajuan2' + else: + initial_status = 'pengajuan1' + elif position_dari_pum == 'manager': + initial_status = 'pengajuan2' + applicant_name_str = pum_request.applicant_name.name or '' + department_type = pum_request.departement_type + department_titles = pum_request._get_department_titles_mapping() + dept_position = department_titles.get(department_type, 'Departement Manager') + vals['date_approved_department'] = now + vals['name_approval_departement'] = applicant_name_str + vals['position_department'] = dept_position + elif position_dari_pum == 'pimpinan': + initial_status = 'pengajuan2' + applicant_name_str = pum_request.applicant_name.name or '' + vals['date_approved_pimpinan'] = now + vals['name_approval_pimpinan'] = applicant_name_str + vals['position_pimpinan'] = 'Pimpinan' + + # --- PENYESUAIAN LOGIKA: SET DEFAULT JIKA KOSONG --- + vals['status'] = initial_status or 'pengajuan1' + # --- BATAS PENYESUAIAN --- + + rec = super().create(vals) + rec._check_remaining_value() + return rec + + def write(self, vals): + res = super().write(vals) + self._check_remaining_value() + return res + + +class AdvancePaymentCreateBill(models.TransientModel): + _name = 'advance.payment.create.bill' + _description = 'Create Bill from Advance Payment' + + apr_id = fields.Many2one('advance.payment.request', string='Advance Payment Request', required=True) + account_id = fields.Many2one( + 'account.account', string='Bank Intransit', required=True, + domain="[('id', 'in', [573, 389, 392, 683, 380])]" # ID Bank Intransit + ) + nominal = fields.Float(string='Nominal', related='apr_id.nominal') + + def action_create_cab(self): + self.ensure_one() + + # if self.env.user.id != 23: + # raise UserError('Hanya AP yang dapat menggunakan ini.') + + apr = self.apr_id + # partner_id = apr.user_id.partner_id.id + partner_id = apr.applicant_name.partner_id.id + + ref_label = f'{apr.number} - {apr.detail_note or "-"}' + + move = self.env['account.move'].create({ + 'ref': ref_label, + 'date': fields.Date.context_today(self), + 'journal_id': 11, # Cash & Bank + 'line_ids': [ + (0, 0, { + 'account_id': 403, # Uang Muka Operasional + 'partner_id': partner_id, + 'name': ref_label, + 'debit': apr.nominal, + 'credit': 0, + }), + (0, 0, { + 'account_id': self.account_id.id, # Bank Intransit yang dipilih + 'partner_id': partner_id, + 'name': ref_label, + 'debit': 0, + 'credit': apr.nominal, + }) + ] + }) + + apr.move_id = move.id # jika ada field untuk menampung move_id + + return { + 'name': _('Journal Entry'), + 'view_mode': 'form', + 'res_model': 'account.move', + 'type': 'ir.actions.act_window', + 'res_id': move.id, + 'target': 'current', + } + +class CreateReimburseCabWizard(models.TransientModel): + _name = 'create.reimburse.cab.wizard' + _description = 'Wizard untuk Membuat Jurnal Reimburse' + + # Field untuk menampung ID request yang sedang diproses + request_id = fields.Many2one('advance.payment.request', string='Pengajuan', readonly=True) + + # Field untuk memilih salah satu dari dua bank Anda + account_id = fields.Many2one( + 'account.account', + string='Bank Intransit (Credit)', + required=True, + # Domain untuk membatasi pilihan hanya pada ID 573 dan 389 + domain="[('id', 'in', [573, 389])]" + ) + + # Field untuk menampilkan total agar pengguna bisa konfirmasi + total_reimburse = fields.Monetary( + string='Total Reimburse', + related='request_id.grand_total_reimburse', + ) + currency_id = fields.Many2one(related='request_id.currency_id', readonly=True) + + def action_create_reimburse_cab(self): + """Metode ini yang akan membuat Journal Entry (CAB).""" + self.ensure_one() + request = self.request_id + + # --- Validasi --- + if request.move_id: + raise UserError("Jurnal sudah pernah dibuat untuk pengajuan ini.") + if not request.reimburse_line_ids: + raise UserError("Tidak ada rincian reimburse yang bisa dijurnalkan.") + + lines = [] + # partner_id = request.user_id.partner_id.id + partner_id = request.applicant_name.partner_id.id + ref_label = f'{request.number} - {request.detail_note or "-"}' + # 1. Buat Jurnal DEBIT dari setiap baris reimburse + for line in request.reimburse_line_ids: + if not line.account_id: + raise UserError(f"Jenis Biaya pada baris '{line.description}' belum diisi oleh AP.") + + lines.append((0, 0, { + 'account_id': line.account_id.id, + 'partner_id': partner_id, + 'name': line.description, + 'debit': line.total, + 'credit': 0, + })) + + # 2. Buat satu Jurnal CREDIT ke bank yang dipilih di wizard + lines.append((0, 0, { + 'account_id': self.account_id.id, + 'partner_id': partner_id, + 'name': ref_label, + 'debit': 0, + 'credit': request.grand_total_reimburse, + })) + + + # 3. Buat Journal Entry + move = self.env['account.move'].create({ + 'ref': ref_label, + 'date': fields.Date.context_today(self), + 'journal_id': 11, # PENTING: Ganti 11 dengan ID Journal "Miscellaneous" Anda + 'line_ids': lines, + }) + + # 4. Tautkan journal yang baru dibuat ke request + request.move_id = move.id + + # 5. Buka tampilan form journal yang baru dibuat + return { + 'name': _('Journal Entry'), + 'view_mode': 'form', + 'res_model': 'account.move', + 'type': 'ir.actions.act_window', + 'res_id': move.id, + 'target': 'current', + }
\ No newline at end of file |
