summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorunknown <it@fixcomart.co.id>2025-09-16 15:01:02 +0700
committerunknown <it@fixcomart.co.id>2025-09-16 15:01:02 +0700
commit540136f8096f090b481d139c58c3f36c28aa69bb (patch)
tree564e27b879251c961afe6550058e9f47a1744bd0
parent462fb823c79d8652a6a08e267b00b13c7bbe5df0 (diff)
(andri) add payment diff + fix onchange ketika pilih partner
-rw-r--r--indoteknik_custom/models/down_payment.py1088
-rw-r--r--indoteknik_custom/models/letter_receivable.py12
-rw-r--r--indoteknik_custom/models/unpaid_invoice_view.py7
-rw-r--r--indoteknik_custom/views/letter_receivable.xml5
-rw-r--r--indoteknik_custom/views/unpaid_invoice_view.xml4
5 files changed, 21 insertions, 1095 deletions
diff --git a/indoteknik_custom/models/down_payment.py b/indoteknik_custom/models/down_payment.py
deleted file mode 100644
index 5adbafd9..00000000
--- a/indoteknik_custom/models/down_payment.py
+++ /dev/null
@@ -1,1088 +0,0 @@
-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 DownPayment(models.Model):
- _name = 'down.payment'
- _description = 'Down Payment Management'
- _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['realization.down.payment'].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': 'down.payment',
- '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['realization.down.payment'].search([('pum_id', '=', self.id)], limit=1)
-
- if realization:
- return {
- 'type': 'ir.actions.act_window',
- 'name': 'Realisasi PUM',
- 'res_model': 'realization.down.payment',
- 'view_mode': 'form',
- 'target': 'current',
- 'res_id': realization.id,
- }
- else:
- return {
- 'type': 'ir.actions.act_window',
- 'name': 'Realisasi PUM',
- 'res_model': 'realization.down.payment',
- 'view_mode': 'form',
- 'target': 'current',
- 'context': {
- 'default_pum_id': self.id,
- 'default_value_down_payment': self.nominal,
- 'default_name': f'Realisasi - {self.number or ""}',
- 'default_pemberian_line_ids': [
- (0, 0, {
- 'date': self.create_date.date() if self.create_date else fields.Date.today(),
- 'description': 'Uang Muka',
- 'value': self.nominal
- })
- ]
- }
- }
-
-
- def action_confirm_payment(self):
- 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': 'reject.reason.downpayment',
- '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': 'down.payment.ap.only',
- '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['realization.down.payment'].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['realization.down.payment'].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('down.payment') or 'New Draft'
-
- vals['status'] = 'pengajuan1'
- return super(DownPayment, self).create(vals)
-
-
-class RealizationDownPaymentLine(models.Model):
- _name = 'realization.down.payment.line'
- _description = 'Rincian Pemberian PUM'
-
- realization_id = fields.Many2one('realization.down.payment', string='Realization')
- date = fields.Date(string='Tanggal', required=True, default=fields.Date.today)
- description = fields.Char(string='Description', required=True)
- value = fields.Float(string='Nilai', required=True)
-
-
-class RealizationDownPaymentUseLine(models.Model):
- _name = 'realization.down.payment.use.line'
- _description = 'Rincian Penggunaan PUM'
-
- realization_id = fields.Many2one('realization.down.payment', string='Realization')
- date = fields.Date(string='Tanggal', required=True, default=fields.Date.today)
- description = fields.Char(string='Description', required=True)
- nominal = fields.Float(string='Nominal', required=True)
- done_attachment = fields.Boolean(string='Checked', default=False)
-
- 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 RealizationDownPayment(models.Model):
- _name = 'realization.down.payment'
- _description = 'Realization Down Payment Management'
- _inherit = ['mail.thread']
-
- pum_id = fields.Many2one('down.payment', string='No PUM')
- name = fields.Char(string='Nama', readonly=True, tracking=3)
- title = fields.Char(string='Judul', tracking=3)
- goals = fields.Text(string='Tujuan', tracking=3)
- related = fields.Char(string='Terkait', tracking=3)
-
- pemberian_line_ids = fields.One2many(
- 'realization.down.payment.line', 'realization_id', string='Rincian Pemberian'
- )
- penggunaan_line_ids = fields.One2many(
- 'realization.down.payment.use.line', 'realization_id', string='Rincian Penggunaan'
- )
-
- grand_total = fields.Float(string='Grand Total Pemberian', tracking=3, compute='_compute_grand_total')
- grand_total_use = fields.Float(string='Grand Total Penggunaan', tracking=3, compute='_compute_grand_total_use')
- value_down_payment = fields.Float(string='PUM', tracking=3)
- remaining_value = fields.Float(string='Sisa Uang PUM', tracking=3, compute='_compute_remaining_value')
-
- note_approval = fields.Text(string='Note Persetujuan', tracking=3)
-
- 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 RejectReasonDownPayment(models.TransientModel):
- _name = 'reject.reason.downpayment'
- _description = 'Wizard for Reject Reason Down Payment'
-
- request_id = fields.Many2one('down.payment', 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 DownPaymentApOnly(models.TransientModel):
- _name = 'down.payment.ap.only'
- _description = 'Create CAB from Down Payment for AP Only'
-
- down_payment_id = fields.Many2one('down.payment', 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',
- }
-
diff --git a/indoteknik_custom/models/letter_receivable.py b/indoteknik_custom/models/letter_receivable.py
index 79a4a3e0..63d7e726 100644
--- a/indoteknik_custom/models/letter_receivable.py
+++ b/indoteknik_custom/models/letter_receivable.py
@@ -31,7 +31,8 @@ class SuratPiutang(models.Model):
line_ids = fields.One2many("surat.piutang.line", "surat_id", string="Invoice Lines")
state = fields.Selection([
("draft", "Draft"),
- ("waiting_approval", "Menunggu Approval"),
+ ("waiting_approval_sales", "Menunggu Approval Sales Manager"),
+ ("waiting_approval_pimpinan", "Menunggu Approval Pimpinan"),
("sent", "Approved & Sent")
], default="draft", tracking=True)
send_date = fields.Datetime(string="Tanggal Kirim", tracking=True)
@@ -55,8 +56,9 @@ class SuratPiutang(models.Model):
compute="_compute_grand_total_text",
)
- perihal_label = fields.Char(
- compute="_compute_perihal_label", string="Perihal Label")
+ perihal_label = fields.Char(compute="_compute_perihal_label", string="Perihal Label")
+
+ payment_difficulty = fields.Selection(string="Payment Difficulty", related='partner_id.payment_difficulty', readonly=True)
def _compute_perihal_label(self):
for rec in self:
@@ -248,8 +250,8 @@ class SuratPiutang(models.Model):
order='new_invoice_day_to_due asc'
)
selected_invoice_id = self.env.context.get('default_selected_invoice_id')
- lines = [(0, 0, {
- # 'invoice_view_id': inv.id,
+ lines = [(5, 0, 0)] # hapus semua line lama
+ lines += [(0, 0, {
'invoice_id': inv.invoice_id.id,
'invoice_number': inv.invoice_number,
'invoice_date': inv.invoice_date,
diff --git a/indoteknik_custom/models/unpaid_invoice_view.py b/indoteknik_custom/models/unpaid_invoice_view.py
index 25e04968..3eb6efc7 100644
--- a/indoteknik_custom/models/unpaid_invoice_view.py
+++ b/indoteknik_custom/models/unpaid_invoice_view.py
@@ -34,6 +34,13 @@ class UnpaidInvoiceView(models.Model):
date_kirim_tukar_faktur = fields.Date(string='Kirim Faktur')
sale_id = fields.Many2one('sale.order', string='Sale Order')
+ payment_difficulty = fields.Selection([
+ ('bermasalah', 'Bermasalah'),
+ ('sulit', 'Sulit'),
+ ('agak_sulit', 'Agak Sulit'),
+ ('normal', 'Normal'),
+ ], string="Payment Difficulty")
+
def action_create_surat_piutang(self):
self.ensure_one()
return {
diff --git a/indoteknik_custom/views/letter_receivable.xml b/indoteknik_custom/views/letter_receivable.xml
index 3a7c2c65..f55d84e8 100644
--- a/indoteknik_custom/views/letter_receivable.xml
+++ b/indoteknik_custom/views/letter_receivable.xml
@@ -10,7 +10,7 @@
<field name="perihal"/>
<field name="state" widget="badge"
decoration-danger="state == 'draft'"
- decoration-warning="state == 'waiting_approval'"
+ decoration-warning="state in ('waiting_approval_sales', 'waiting_approval_pimpinan')"
decoration-success="state == 'sent'"/>
<field name="send_date"/>
<!-- <field name="line_ids" widget="one2many_list"/> -->
@@ -25,7 +25,7 @@
<field name="arch" type="xml">
<form string="Surat Piutang">
<header>
- <field name="state" widget="statusbar" statusbar_visible="draft,waiting_approval,sent"/>
+ <field name="state" widget="statusbar" statusbar_visible="draft,waiting_approval_sales,waiting_approval_pimpinan,sent"/>
<button name="action_approve" type="object" string="Approve &amp; Send" class="btn-primary"/>
<!-- <button name="action_send_letter" type="object" string="Email Send" class="btn-primary"/> -->
</header>
@@ -58,6 +58,7 @@
<field name="tujuan_email"/>
<field name="perihal"/>
<field name="partner_id" options="{'no_create': True}"/>
+ <field name="payment_difficulty"/>
<field name="send_date" readonly="1"/>
</group>
<group>
diff --git a/indoteknik_custom/views/unpaid_invoice_view.xml b/indoteknik_custom/views/unpaid_invoice_view.xml
index 20145a46..e56bbee7 100644
--- a/indoteknik_custom/views/unpaid_invoice_view.xml
+++ b/indoteknik_custom/views/unpaid_invoice_view.xml
@@ -21,6 +21,10 @@
decoration-danger="payment_state == 'not_paid'"
decoration-warning="payment_state == 'partial'"/>
<field name="invoice_user_id"/>
+ <field name="payment_difficulty" widget="badge"
+ decoration-info="payment_difficulty == 'normal'"
+ decoration-warning="payment_difficulty in ('agak_sulit', 'sulit')"
+ decoration-danger="payment_difficulty == 'bermasalah'"/>
</tree>
</field>
</record>