diff options
| author | FIN-IT_AndriFP <it@fixcomart.co.id> | 2025-10-02 15:08:54 +0700 |
|---|---|---|
| committer | FIN-IT_AndriFP <it@fixcomart.co.id> | 2025-10-02 15:08:54 +0700 |
| commit | 96cb47f78e7847bb7d0ceec20fc8d275e9a916d8 (patch) | |
| tree | 77f43d18ffa72e6593f5bca7c857a639ac813452 | |
| parent | 7d89516ce05d0ea67733c04436cc2de544189efe (diff) | |
(andri) rename downpayment to advance payment request
| -rwxr-xr-x | indoteknik_custom/__manifest__.py | 4 | ||||
| -rwxr-xr-x | indoteknik_custom/models/__init__.py | 2 | ||||
| -rw-r--r-- | indoteknik_custom/models/advance_payment_request.py | 1087 | ||||
| -rwxr-xr-x | indoteknik_custom/security/ir.model.access.csv | 12 | ||||
| -rw-r--r-- | indoteknik_custom/views/advance_payment_request.xml | 207 | ||||
| -rw-r--r-- | indoteknik_custom/views/advance_payment_settlement.xml | 168 |
6 files changed, 1471 insertions, 9 deletions
diff --git a/indoteknik_custom/__manifest__.py b/indoteknik_custom/__manifest__.py index c0f65cf5..94e67c1c 100755 --- a/indoteknik_custom/__manifest__.py +++ b/indoteknik_custom/__manifest__.py @@ -175,8 +175,8 @@ 'views/stock_inventory.xml', 'views/sale_order_delay.xml', 'views/refund_sale_order.xml', - 'views/down_payment.xml', - 'views/down_payment_realization.xml', + 'views/advance_payment_request.xml', + 'views/advance_payment_settlement.xml', # 'views/refund_sale_order.xml', 'views/tukar_guling.xml', # 'views/tukar_guling_return_views.xml' diff --git a/indoteknik_custom/models/__init__.py b/indoteknik_custom/models/__init__.py index f3501f7b..71e5b4b3 100755 --- a/indoteknik_custom/models/__init__.py +++ b/indoteknik_custom/models/__init__.py @@ -154,7 +154,7 @@ from . import approval_invoice_date from . import approval_payment_term from . import refund_sale_order # from . import patch -from . import down_payment +from . import advance_payment_request from . import tukar_guling from . import tukar_guling_po from . import update_date_planned_po_wizard diff --git a/indoteknik_custom/models/advance_payment_request.py b/indoteknik_custom/models/advance_payment_request.py new file mode 100644 index 00000000..ccbf8988 --- /dev/null +++ b/indoteknik_custom/models/advance_payment_request.py @@ -0,0 +1,1087 @@ +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' + _rec_name = 'number' + _inherit = ['mail.thread', 'mail.activity.mixin'] + + 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.Char(string='Nama Pemohon', tracking=3, required=True) + nominal = fields.Float(string='Nominal', tracking=3, required=True) + + bank_name = fields.Char(string='Bank', tracking=3, required=True) + account_name = fields.Char(string='Nama Account', tracking=3, required=True) + bank_account = fields.Char(string='No. Rekening', tracking=3, required=True) + detail_note = fields.Text(string='Keterangan Penggunaan Rinci', tracking=3) + + date_back_to_office = fields.Date( + string='Tanggal Kembali ke Kantor', + tracking=3, + required=True + ) + + estimated_return_date = fields.Date( + string='Batas Pengajuan', + help='Tanggal batas maksimal pengajuan realisasi setelah kembali ke kantor. ' + '7 hari setelah tanggal kembali.' + ) + + days_remaining = fields.Integer( + string='Sisa Hari Pengajuan', + 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 Pengecekan AP'), + ('pengajuan3', 'Menunggu Approval Pimpinan'), + ('approved', 'Approved'), + ('reject', 'Rejected') + ], string='Status', default='draft', tracking=3, index=True, track_visibility='onchange') + + last_status = fields.Selection([ + ('draft', 'Draft'), + ('pengajuan1', 'Menunggu Approval Departement'), + ('pengajuan2', 'Menunggu Pengecekan AP'), + ('pengajuan3', 'Menunggu Approval Pimpinan'), + ('approved', 'Approved'), + ('reject', 'Rejected') + ], string='Status') + + status_pay_down_payment = fields.Selection([ + ('pending', 'Pending'), + ('payment', 'Payment'), + ], string='Status Pembayaran', default='pending', tracking=3) + + name_approval_departement = fields.Char(string='Approval Departement', tracking=True) + name_approval_ap = fields.Char(string='Approval AP', tracking=True) + email_ap = fields.Char(string = 'Email AP') + name_approval_pimpinan = fields.Char(string='Approval Pimpinan', tracking=True) + + 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', tracking=True) + position_ap = fields.Char(string='Position AP', tracking=True) + position_pimpinan = fields.Char(string='Position Pimpinan', tracking=True) + + 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'), + ('hr_ga', 'HR & GA'), + ], string='Departement Type', tracking=3, required=True) + + attachment_file_image = fields.Binary(string='Attachment Image', attachment_filename='attachment_filename_image') + attachment_file_pdf = fields.Binary(string='Attachment PDF', attachment_filename='attachment_filename_pdf') + attachment_filename_image = fields.Char(string='Filename Image') + attachment_filename_pdf = fields.Char(string='Filename PDF') + + attachment_type = fields.Selection([ + ('pdf', 'PDF'), + ('image', 'Image'), + ], string="Attachment Type", default='pdf') + + 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') + + reason_reject = fields.Text(string='Alasan Penolakan') + + currency_id = fields.Many2one( + 'res.currency', string='Currency', + default=lambda self: self.env.company.currency_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. + - Hari ini = kirim dengan template 'mail_template_pum_reminder_today' + - H-2 dari due date = kirim dengan template 'mail_template_pum_reminder_h_2' + """ + today = date.today() + pum_ids = self.search([ + ('date_back_to_office', '!=', False), + ('status', 'not in', ['draft', 'reject']), + ]) + + 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 tidak ditemukan.") + return + + for pum in pum_ids: + _logger.info(f"[REMINDER] Memproses PUM {pum.number}") + + 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 + + due_date = pum.date_back_to_office + timedelta(days=7) + days_remaining = (due_date - today).days + + realization = self.env['advance.payment.settlement'].search([('pum_id', '=', pum.id)], limit=1) + if not realization or realization.done_status != 'remaining': + _logger.info(f"[REMINDER] Lewati PUM {pum.number}, status realisasi bukan 'remaining'.") + continue + + # Tentukan template + if pum.date_back_to_office == today: + template = template_today + elif days_remaining == 2: + template = template_h2 + else: + _logger.info(f"[REMINDER] Lewati PUM {pum.number}, hari ini bukan tanggal pengingat.") + continue + + # 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', + 'email_from': pum.email_ap, + '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] + if self.env.user.id not in ap_user_ids: + 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 : Stephan ID 28 + # HR & GA : Akbar ID 7 / Pimpinan + # --------------------------------------- + # AP : Manzila (Finance) ID 23 + + def _get_departement_approver(self): + mapping = { + 'sales': 19, + 'merchandiser': 19, + 'marketing': 216, + 'logistic': 21, + 'procurement': 21, + 'fat': 28, + 'hr_ga': 7, + } + return mapping.get(self.departement_type) + + def action_realisasi_pum(self): + self.ensure_one() + + realization = self.env['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): + ap_user_ids = [23, 9468] + 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 PUM {rec.name or ""} ' + f'karena belum ada bukti attachment (PDF/Image).' + ) + + rec.status_pay_down_payment = 'payment' + + rec.message_post( + body="Status pembayaran telah <b>dikonfirmasi</b> oleh <b>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', + 'hr_ga': 'HR & GA Manager', + } + 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] # 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' + 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_reject(self): + return { + 'type': 'ir.actions.act_window', + 'name': 'Alasan Penolakan', + 'res_model': 'advance.payment.request.reject', + 'view_mode': 'form', + 'target': 'new', + 'context': {'default_request_id': self.id}, + } + + def action_draft(self): + for record in self: + # Pastikan hanya yang statusnya 'reject' yang bisa di-reset + if record.status != 'reject': + raise UserError("Hanya data dengan status 'Reject' yang bisa dikembalikan ke Draft atau status sebelumnya.") + + # Jika ada last_status, gunakan itu; jika tidak, fallback ke 'draft' + new_status = 'pengajuan1' + + # Reset field-field approval & alasan reject + record.write({ + 'status': new_status, + 'reason_reject': False, + 'last_status': False, + 'name_approval_departement': False, + 'name_approval_ap': False, + 'name_approval_pimpinan': False, + 'date_approved_department': False, + 'date_approved_ap': False, + 'date_approved_pimpinan': False, + 'position_department': False, + 'position_ap': False, + 'position_pimpinan': False, + }) + + record.message_post(body=f"Status dikembalikan ke <b>{new_status.capitalize()}</b> oleh {self.env.user.name}.") + + + def action_ap_only(self): + self.ensure_one() + + ap_user_ids = [23, 9468] # 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_down_payment_id': self.id, + } + } + + + @api.depends('date_back_to_office', 'status') + def _compute_days_remaining(self): + today = date.today() + for rec in self: + if rec.status in ['approved', 'reject'] and rec.days_remaining: + continue + + if rec.date_back_to_office: + due_date = rec.date_back_to_office + timedelta(days=7) + rec.estimated_return_date = due_date + + # Jika hari ini sebelum tanggal kembali, maka anggap belum mulai dihitung + effective_today = max(today, rec.date_back_to_office) + rec.days_remaining = (due_date - effective_today).days + else: + rec.estimated_return_date = False + rec.days_remaining = 0 + + @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 + + @api.onchange('account_name') + def _onchange_account_name(self): + if self.account_name: + self.applicant_name = self.account_name + + @api.onchange('user_id') + def _onchange_user_id_limit_check(self): + if not self.user_id: + return + + pum_ids = self.search([ + ('user_id', '=', self.user_id.id), + ('status', '!=', 'reject') + ]) + + 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 or realization.done_status != 'done_not_realized': + 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.', + } + } + + @api.model + def create(self, vals): + user = self.env.user + + pum_ids = self.search([ + ('user_id', '=', user.id), + ('status', '!=', 'reject') + ]) + + 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 or realization.done_status != 'done_not_realized': + active_pum_count += 1 + + if active_pum_count >= 2: + raise UserError("Anda hanya dapat mengajukan maksimal 2 PUM aktif. Silakan realisasikan salah satunya terlebih dahulu.") + + if not vals.get('number') or vals['number'] == 'New Draft': + vals['number'] = self.env['ir.sequence'].next_by_code('advance.payment.request') or 'New Draft' + + vals['status'] = 'pengajuan1' + return super(AdvancePaymentRequest, self).create(vals) + + +class AdvancePaymentSettlementLine(models.Model): + _name = 'advance.payment.settlement.line' + _description = 'Advance Payment Settlement Line' + + realization_id = fields.Many2one('advance.payment.settlement', string='Realization') + date = fields.Date(string='Tanggal', required=True, default=fields.Date.today) + description = fields.Char(string='Description', required=True) + value = fields.Float(string='Nilai', required=True) + + +class 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.Char(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', required=True, + domain="[('id', 'in', [484, 486, 488, 506, 507, 625, 471, 519, 527, 528, 529, 530, 565])]" # ID Jenis Biaya yang dibutuhkan + ) + + @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] # 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 AdvancePaymentSettlement(models.Model): + _name = 'advance.payment.settlement' + _description = 'Advance Payment Settlement' + _inherit = ['mail.thread'] + + pum_id = fields.Many2one('advance.payment.request', string='No PUM') + name = fields.Char(string='Nama', readonly=True, tracking=3) + title = fields.Char(string='Judul', tracking=3) + goals = fields.Text(string='Tujuan', tracking=3) + related = fields.Char(string='Terkait', tracking=3) + + pemberian_line_ids = fields.One2many( + 'advance.payment.settlement.line', 'realization_id', string='Rincian Pemberian' + ) + penggunaan_line_ids = fields.One2many( + 'advance.payment.usage.line', 'realization_id', string='Rincian Penggunaan' + ) + + grand_total = fields.Float(string='Grand Total Pemberian', tracking=3, compute='_compute_grand_total') + grand_total_use = fields.Float(string='Grand Total Penggunaan', tracking=3, compute='_compute_grand_total_use') + value_down_payment = fields.Float(string='PUM', tracking=3) + remaining_value = fields.Float(string='Sisa Uang PUM', tracking=3, compute='_compute_remaining_value') + + note_approval = fields.Text(string='Note Persetujuan', tracking=3) + + name_approval_departement = fields.Char(string='Approval Departement', tracking=True) + name_approval_ap = fields.Char(string='Approval AP', tracking=True) + name_approval_pimpinan = fields.Char(string='Approval Pimpinan', tracking=True) + + 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', tracking=True) + position_ap = fields.Char(string='Position AP', tracking=True) + position_pimpinan = fields.Char(string='Position Pimpinan', tracking=True) + + approved_by = fields.Char(string='Approved By', tracking=True, track_visibility='always') + + status = fields.Selection([ + ('pengajuan1', 'Menunggu Approval Departement'), + ('pengajuan2', 'Menunggu Pengecekan AP'), + ('pengajuan3', 'Menunggu Approval Pimpinan'), + ('approved', 'Approved'), + ], string='Status', default='pengajuan1', tracking=3, index=True, track_visibility='onchange') + + 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) + + 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') + + def action_toggle_check_attachment(self): + ap_user_ids = [23, 9468] + 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] + if self.env.user.id not in ap_user_ids: + 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('grand_total', 'grand_total_use') + def _compute_remaining_value(self): + for rec in self: + rec.remaining_value = rec.value_down_payment - rec.grand_total_use + + def action_validation(self): + self.ensure_one() + + # Validasi hanya AP yang bisa validasi + 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}.") + + def action_cab(self): + self.ensure_one() + + 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 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 + 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 + + # Tanggal pakai create_date atau hari ini + account_date = self.date_done_not_realized or fields.Date.today() + + ref_label = f"Realisasi {self.pum_id.number} Biaya {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', + 'hr_ga': 'HR & GA Manager', + } + 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] # 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' + rec.done_status = 'done_not_realized' # Set status done untuk realisasi + + 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 0.\n" + "Jika ada penggunaan uang pribadi, maka ajukan dengan sistem reimburse." + ) + + @api.model + def create(self, vals): + 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 AdvancePaymentRequestReject(models.TransientModel): + _name = 'advance.payment.request.reject' + _description = 'Wizard for Reject Reason APR' + + request_id = fields.Many2one('advance.payment.request', string='Pengajuan PUM') + reason_reject = fields.Text(string='Alasan Penolakan', required=True) + + def confirm_reject(self): + if self.request_id: + self.request_id.write({ + 'status': 'reject', + 'last_status': self.request_id.status, + 'reason_reject': self.reason_reject, + }) + return {'type': 'ir.actions.act_window_close'} + +class AdvancePaymentCreateBill(models.TransientModel): + _name = 'advance.payment.create.bill' + _description = 'Create Bill from Advance Payment' + + down_payment_id = fields.Many2one('advance.payment.request', string='Down Payment', required=True) + account_id = fields.Many2one( + 'account.account', string='Bank Intransit', required=True, + domain="[('id', 'in', [573, 389, 392])]" # ID Bank Intransit + ) + nominal = fields.Float(string='Nominal', related='down_payment_id.nominal') + + def action_create_cab(self): + self.ensure_one() + + # if self.env.user.id != 23: + # raise UserError('Hanya AP yang dapat menggunakan ini.') + + dp = self.down_payment_id + partner_id = dp.user_id.partner_id.id + + ref_label = f'{dp.number} - Biaya {dp.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': dp.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': dp.nominal, + }) + ] + }) + + dp.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', + }
\ No newline at end of file diff --git a/indoteknik_custom/security/ir.model.access.csv b/indoteknik_custom/security/ir.model.access.csv index 12cc3098..fa519232 100755 --- a/indoteknik_custom/security/ir.model.access.csv +++ b/indoteknik_custom/security/ir.model.access.csv @@ -187,12 +187,12 @@ access_purchase_order_update_date_wizard,access.purchase.order.update.date.wizar access_change_date_planned_wizard,access.change.date.planned.wizard,model_change_date_planned_wizard,,1,1,1,1 access_refund_sale_order,access.refund.sale.order,model_refund_sale_order,base.group_user,1,1,1,1 access_refund_sale_order_line,access.refund.sale.order.line,model_refund_sale_order_line,base.group_user,1,1,1,1 -access_down_payment,access.down.payment,model_down_payment,,1,1,1,1 -access_realization_down_payment,access.realization.down.payment,model_realization_down_payment,,1,1,1,1 -access_realization_down_payment_line,access.realization.down.payment.line,model_realization_down_payment_line,,1,1,1,1 -access_realization_down_payment_use_line,access.realization.down.payment.use.line,model_realization_down_payment_use_line,,1,1,1,1 -access_down_payment_ap_only,access.down.payment.ap.only,model_down_payment_ap_only,,1,1,1,1 -access_reject_reason_downpayment,access.reject.reason.downpayment,model_reject_reason_downpayment,,1,1,1,1 +access_advance_payment_request,access.advance.payment.request,model_advance_payment_request,,1,1,1,1 +access_advance_payment_settlement,access.advance.payment.settlement,model_advance_payment_settlement,,1,1,1,1 +access_advance_payment_settlement_line,access.advance.payment.settlement.line,model_advance_payment_settlement_line,,1,1,1,1 +access_advance_payment_usage_line,access.advance.payment.usage.line,model_advance_payment_usage_line,,1,1,1,1 +access_advance_payment_create_bill,access.advance.payment.create.bill,model_advance_payment_create_bill,,1,1,1,1 +access_advance_payment_request_reject,access.advance.payment.request.reject,model_advance_payment_request_reject,,1,1,1,1 access_purchasing_job_seen,purchasing.job.seen,model_purchasing_job_seen,,1,1,1,1 diff --git a/indoteknik_custom/views/advance_payment_request.xml b/indoteknik_custom/views/advance_payment_request.xml new file mode 100644 index 00000000..1610293f --- /dev/null +++ b/indoteknik_custom/views/advance_payment_request.xml @@ -0,0 +1,207 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<odoo> + <record id="view_form_advance_payment_request" model="ir.ui.view"> + <field name="name">advance.payment.request.form</field> + <field name="model">advance.payment.request</field> + <field name="arch" type="xml"> + <form string="Advance Payment Request (APR)"> + <header> + <button name="action_realisasi_pum" + type="object" + string="Settlement" + class="btn-primary" + attrs="{'invisible': [('status', 'not in', 'approved')]}"/> + <button name="action_draft" + string="Reset to Draft" + attrs="{'invisible': [('status', '!=', 'reject')]}" + type="object"/> + <button name="action_reject" + type="object" + string="Reject" + class="btn-danger" + attrs="{'invisible': [('status', 'in', ['approved','reject','draft'])]}"/> + <button name="action_approval_check" + type="object" + string="Checking/Approval" + class="btn-success" + attrs="{'invisible': [('status', 'in', ['approved','reject','draft'])]}"/> + <button name="action_confirm_payment" + type="object" + string="Konfirmasi Pembayaran" + class="btn-info" + attrs="{'invisible': ['|', ('status', '=', 'reject'), ('status_pay_down_payment', '=', 'payment')]}"/> + <button name="action_ap_only" + type="object" + string="AP Only" + class="btn-info" + attrs="{'invisible': ['|', ('status', '=', 'reject'), ('is_cab_visible', '=', True)]}"/> + <field name="status" widget="statusbar" + statusbar_visible="draft,pengajuan1,pengajuan2,pengajuan3,approved" + statusbar_colors='{"reject":"red"}' + readonly="1"/> + </header> + <sheet> + <div class="oe_button_box" name="button_box"> + <field name="is_cab_visible" invisible="1"/> + <button type="object" + name="action_view_journal_uangmuka" + class="oe_stat_button" + icon="fa-book" + attrs="{'invisible': [('is_cab_visible', '=', False)]}" + style="width: 200px;"> + <field name="move_id" widget="statinfo" string="Journal Uang Muka"/> + <span class="o_stat_text"> + <t t-esc="record.move_id.name"/> + </span> + </button> + </div> + <div class="oe_title"> + <h1> + <field name="number" readonly="1"/> + </h1> + </div> + <group col="2"> + <group string=""> + <field name="applicant_name" colspan="2" attrs="{'readonly': [('status', '=', 'approved')]}"/> + <field name="nominal" colspan="2" attrs="{'readonly': [('status', '=', 'approved')]}"/> + <field name="bank_name" colspan="2" attrs="{'readonly': [('status', '=', 'approved')]}"/> + <field name="account_name" colspan="2" attrs="{'readonly': [('status', '=', 'approved')]}"/> + <field name="bank_account" colspan="2" attrs="{'readonly': [('status', '=', 'approved')]}"/> + <field name="detail_note" attrs="{'readonly': [('status', '=', 'approved')]}"/> + <hr/> + <field name="user_id" readonly="1"/> + <field name="partner_id" readonly="1"/> + <field name="departement_type"/> + <field name="status_pay_down_payment" + readonly="1" + decoration-success="status_pay_down_payment == 'payment'" + decoration-danger="status_pay_down_payment == 'pending'" + widget="badge"/> + <field name="create_date" readonly="1"/> + <field name="date_back_to_office"/> + <field name="estimated_return_date" readonly="1" widget="badge"/> + <field name="days_remaining" readonly="1" widget="badge"/> + <field name="approved_by" readonly="1"/> + <field name="reason_reject" attrs="{'invisible': [('status', '!=', 'reject')], 'readonly': True}"/> + </group> + + <group> + <field name="attachment_type" attrs="{'readonly': [('status', '=', 'approved')]}"/> + + <field name="attachment_file_pdf" filename="attachment_filename" + widget="pdf_viewer" + attrs="{'invisible': [('attachment_type', '!=', 'pdf')], 'readonly': [('status', '=', 'approved')]}"/> + + <field name="attachment_file_image" filename="attachment_filename" + widget="image" + attrs="{'invisible': [('attachment_type', '!=', 'image')], 'readonly': [('status', '=', 'approved')]}" + style="max-width:250px; max-height:250px; object-fit:contain;"/> + <br/> + </group> + + </group> + </sheet> + <div class="oe_chatter"> + <field name="message_follower_ids" widget="mail_followers"/> + <field name="message_ids" widget="mail_thread"/> + </div> + </form> + </field> + </record> + + <record id="view_tree_advance_payment_request" model="ir.ui.view"> + <field name="name">advance.payment.request.tree</field> + <field name="model">advance.payment.request</field> + <field name="arch" type="xml"> + <tree> + <field name="number"/> + <field name="user_id" optional='hide'/> + <field name="applicant_name"/> + <field name="nominal"/> + <field name="departement_type" optional='hide'/> + <field name="status" + readonly="1" + decoration-success="status == 'approved'" + decoration-danger="status == 'reject'" + widget="badge" optional="show"/> + <field name="status_pay_down_payment" + readonly="1" + decoration-success="status_pay_down_payment == 'payment'" + decoration-danger="status_pay_down_payment == 'pending'" + widget="badge"/> + <field name="days_remaining" readonly="1" widget="badge" optional="hide"/> + <field name="estimated_return_date" widget="badge" /> + </tree> + </field> + </record> + + <record id="action_advance_payment_request" model="ir.actions.act_window"> + <field name="name">Advance Payment Request</field> + <field name="type">ir.actions.act_window</field> + <field name="res_model">advance.payment.request</field> + <field name="view_mode">tree,form</field> + </record> + + <menuitem id="menu_advance_payment_request_acct" + name="Advance Payment Request" + parent="account.menu_finance_entries" + sequence="114" + action="action_advance_payment_request" + /> + + <menuitem id="menu_advance_payment_request_sales" + name="Advance Payment Request" + parent="sale.product_menu_catalog" + sequence="101" + action="action_advance_payment_request" + /> + + <record id="view_advance_payment_create_bill_form" model="ir.ui.view"> + <field name="name">advance.payment.create.bill.form</field> + <field name="model">advance.payment.create.bill</field> + <field name="arch" type="xml"> + <form string="Create CAB AP Only"> + <group> + <field name="nominal"/> + <field name="account_id"/> + </group> + <footer> + <button name="action_create_cab" type="object" string="Create CAB" class="btn-primary"/> + <button string="Cancel" class="btn-secondary" special="cancel"/> + </footer> + </form> + </field> + </record> + + <record id="action_advance_payment_create_bill" model="ir.actions.act_window"> + <field name="name">Create CAB AP Only</field> + <field name="res_model">advance.payment.create.bill</field> + <field name="view_mode">form</field> + <field name="view_id" ref="view_advance_payment_create_bill_form"/> + <field name="target">new</field> + </record> + + <record id="view_advance_payment_request_reject_form" model="ir.ui.view"> + <field name="name">advance.payment.request.reject.form</field> + <field name="model">advance.payment.request.reject</field> + <field name="arch" type="xml"> + <form string="Alasan Penolakan APR"> + <group> + <field name="reason_reject"/> + </group> + <footer> + <button name="confirm_reject" type="object" string="Tolak" class="btn-danger"/> + <button string="Batal" class="btn-secondary" special="cancel"/> + </footer> + </form> + </field> + </record> + + <record id="action_advance_payment_request_reject" model="ir.actions.act_window"> + <field name="name">Tolak Pengajuan APR</field> + <field name="res_model">advance.payment.request.reject</field> + <field name="view_mode">form</field> + <field name="target">new</field> + </record> + +</odoo>
\ No newline at end of file diff --git a/indoteknik_custom/views/advance_payment_settlement.xml b/indoteknik_custom/views/advance_payment_settlement.xml new file mode 100644 index 00000000..a2bb31b2 --- /dev/null +++ b/indoteknik_custom/views/advance_payment_settlement.xml @@ -0,0 +1,168 @@ +<odoo> + <record id="view_form_advance_payment_settlement" model="ir.ui.view"> + <field name="name">advance.payment.settlement.form</field> + <field name="model">advance.payment.settlement</field> + <field name="arch" type="xml"> + <form string="Advance Payment Settlement"> + <header> + <button name="action_validation" + type="object" + string="Validasi" + class="btn-info" + attrs="{'invisible': [('done_status', '=', 'done_realized')]}"/> + <button name="action_cab" + type="object" + class="btn-info" + attrs="{'invisible': [('is_cab_visible', '=', True)]}" + string="AP Only"/> + <button name="action_approval_check" + type="object" + string="Checking/Approval" + class="btn-success" + attrs="{'invisible': [('status', '=', 'approved')]}"/> + <field name="status" widget="statusbar" + statusbar_visible="pengajuan1,pengajuan2,pengajuan3,approved" + statusbar_colors='{"reject":"red"}' + readonly="1"/> + </header> + <sheet> + <div class="oe_button_box" name="button_box"> + <field name="is_cab_visible" invisible="1"/> + <button type="object" + name="action_view_journal_uangmuka" + class="oe_stat_button" + icon="fa-book" + attrs="{'invisible': [('is_cab_visible', '=', False)]}" + style="width: 200px;"> + <field name="move_id" widget="statinfo" string="Journal Entries"/> + <span class="o_stat_text"> + <t t-esc="record.move_misc_id.name"/> + </span> + </button> + </div> + <div class="oe_title"> + <h1> + <field name="name" readonly="1"/> + </h1> + </div> + <group col="2"> + <group> + <field name="pum_id" readonly="1"/> + <field name="title" required="1"/> + <field name="goals" required="1"/> + <field name="related" required="1"/> + <field name="note_approval" required="1"/> + <field name="lot_of_attachment"/> + <field name="approved_by" readonly="1"/> + <field name="done_status" + decoration-success="done_status == 'done_realized'" + decoration-danger="done_status == 'remaining'" + widget="badge" readonly="1"/> + </group> + <group attrs="{'invisible': [('lot_of_attachment', '!=', 'one_for_all_line')]}"> + <field name="attachment_type" attrs="{'readonly': [('status', '=', 'approved')]}"/> + + <field name="attachment_file_pdf" filename="attachment_filename" + widget="pdf_viewer" + attrs="{'invisible': [('attachment_type', '!=', 'pdf')], 'readonly': [('status', '=', 'approved')]}"/> + + <field name="attachment_file_image" filename="attachment_filename" + widget="image" + attrs="{'invisible': [('attachment_type', '!=', 'image')], 'readonly': [('status', '=', 'approved')]}" + style="max-width:250px; max-height:250px; object-fit:contain;"/> + <br/> + </group> + </group> + + <notebook> + <page string="Rincian Pemberian"> + <field name="pemberian_line_ids" nolabel="1"> + <tree editable="false" create="false" delete="false"> + <field name="date" readonly="1"/> + <field name="description" readonly="1"/> + <field name="value" sum="Total Pemberian" readonly="1"/> + </tree> + </field> + </page> + + <page string="Rincian Penggunaan"> + <field name="penggunaan_line_ids" nolabel="1"> + <tree> + <field name="date"/> + <field name="description"/> + <field name="nominal" sum="Total Penggunaan"/> + <field name="done_attachment"/> + </tree> + + <form> + <group> + <field name="lot_of_attachment" invisible="1"/> + <field name="date"/> + <field name="account_id"/> + <field name="description"/> + <field name="nominal"/> + <field name="attachment_type" + attrs="{ + 'invisible': [('lot_of_attachment', '=', 'one_for_all_line')] + }"/> + <field name="attachment_file_pdf" + filename="attachment_filename_pdf" + widget="pdf_viewer" + attrs="{ + 'invisible': [ + '|', + ('lot_of_attachment', '=', 'one_for_all_line'), + ('attachment_type', '!=', 'pdf') + ] + }"/> + <field name="attachment_file_image" + filename="attachment_filename_image" + widget="image" + attrs="{ + 'invisible': [ + '|', + ('lot_of_attachment', '=', 'one_for_all_line'), + ('attachment_type', '!=', 'image') + ] + }" + style="max-width:250px; max-height:250px; object-fit:contain;"/> + + <field name="done_attachment"/> + </group> + </form> + </field> + </page> + </notebook> + + <div style="text-align:right;"> + <button name="action_toggle_check_attachment" + type="object" + string="Check/Uncheck All Line Use PUM" + class="btn-secondary"/> + </div> + + <group col="2"> + <group class="oe_subtotal_footer oe_right"> + <field name="currency_id" invisible="1"/> + <field name="grand_total" readonly="1" widget="monetary" options="{'currency_field': 'currency_id'}"/> + <field name="grand_total_use" readonly="1" widget="monetary" options="{'currency_field': 'currency_id'}"/> + <field name="value_down_payment" readonly="1" widget="monetary" options="{'currency_field': 'currency_id'}" style="font-weight: bold;"/> + <field name="remaining_value" readonly="1" widget="monetary" options="{'currency_field': 'currency_id'}"/> + </group> + </group> + </sheet> + + <div class="oe_chatter"> + <field name="message_follower_ids" widget="mail_followers"/> + <field name="message_ids" widget="mail_thread"/> + </div> + </form> + </field> + </record> + + <record id="action_advance_payment_settlement" model="ir.actions.act_window"> + <field name="name">Advance Payment Settlement</field> + <field name="res_model">advance.payment.settlement</field> + <field name="view_mode">tree,form</field> + </record> +</odoo>
\ No newline at end of file |
