summaryrefslogtreecommitdiff
path: root/indoteknik_custom/models/advance_payment_request.py
diff options
context:
space:
mode:
authorAzka Nathan <darizkyfaz@gmail.com>2025-11-19 14:49:01 +0700
committerAzka Nathan <darizkyfaz@gmail.com>2025-11-19 14:49:01 +0700
commitbb2be920076aabc49b4f9fdd896d14e096e633eb (patch)
tree588196ba53fb7e41d96a61272bdb74b4821fe661 /indoteknik_custom/models/advance_payment_request.py
parent9c4f131ffaf37ca47a78b320a68f7de4e846ecfb (diff)
parent58623e9509789381dbe334969de647b4ad0302a4 (diff)
Merge branch 'odoo-backup' into locatorlocator
# Conflicts: # indoteknik_custom/models/__init__.py # indoteknik_custom/models/stock_move.py # indoteknik_custom/security/ir.model.access.csv # indoteknik_custom/views/stock_picking.xml
Diffstat (limited to 'indoteknik_custom/models/advance_payment_request.py')
-rw-r--r--indoteknik_custom/models/advance_payment_request.py1553
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