from odoo import models, fields, api from odoo.exceptions import UserError, ValidationError from odoo.osv import expression class UpahHarian(models.Model): _name = 'upah.harian' _description = 'Upah Harian' _inherit = ['mail.thread', 'mail.activity.mixin'] name = fields.Char(readonly=True) pemohon = fields.Many2one( 'res.users', string='Pemohon', required=True, domain=[('active', '=', True), ('share', '=', False)], default=lambda self: self.env.user ) tanggal = fields.Date( 'Tanggal Pengajuan', required=True, default=fields.Date.today() ) upah_harian = fields.Float('Upah Harian') notes = fields.Text('Notes') state = fields.Selection([ ('draft', 'Draft'), ('pic', 'Approval PIC'), ('dept', 'Approval Kepala Departemen'), ('waiting_payment', 'Menunggu Pembayaran'), ('paid', 'Lunas'), ('cancel', 'Canceled') ], default='draft', tracking=True) cancel_reason = fields.Text('Alasan Cancel') approved_by_pic = fields.Char('Approved By PIC', readonly=True) approved_by_dept = fields.Char('Approved By Dept', readonly=True) approved_by_finance = fields.Char('Approved By Finance', readonly=True) date_approved_pic = fields.Datetime('Date Approved PIC', readonly=True) date_approved_dept = fields.Datetime('Date Approved Dept', readonly=True) date_approved_finance = fields.Datetime('Date Approved Finance', readonly=True) departement_type = fields.Selection([ ('sales', 'Sales'), ('merchandiser', 'Merchandiser'), ('marketing', 'Marketing'), ('logistic', 'Logistic'), ('procurement', 'Procurement'), ('fat', 'FAT'), ('it', 'IT'), ('hr_ga', 'HR & GA'), ], string='Departement Type', required=True, tracking=True) upah_harian_line = fields.One2many( 'upah.harian.line', 'upah_harian_id', string='Activity Line' ) journal_upah_id = fields.Many2one('account.move') total_upah = fields.Float( 'Total Upah Harian', compute='_compute_total_upah', store=True ) sisa_jam_mingguan = fields.Float( string='Sisa Jam Mingguan', compute='_compute_sisa_jam_mingguan' ) attachment_type = fields.Selection([ ('pdf', 'PDF'), ('image', 'Image') ]) 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() attachment_filename_pdf = fields.Char() # ========================================================= # COMPUTE # ========================================================= @api.depends('upah_harian_line.upah_harian_compute') def _compute_total_upah(self): for rec in self: rec.total_upah = sum(rec.upah_harian_line.mapped('upah_harian_compute')) @api.depends('upah_harian_line.jam_masuk', 'upah_harian_line.jam_keluar') def _compute_sisa_jam_mingguan(self): MAX = 7.5 for rec in self: total_kurang = 0 for line in rec.upah_harian_line: if not line.jam_masuk or not line.jam_keluar: continue delta = line.jam_keluar - line.jam_masuk jam = max(delta.total_seconds() / 3600, 0) if jam < MAX: total_kurang += (MAX - jam) rec.sisa_jam_mingguan = total_kurang # ========================================================= # ONCHANGE # ========================================================= @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 # ========================================================= # CREATE # ========================================================= @api.model def create(self, vals): dept = vals.get('departement_type') if dept == 'logistic': vals['name'] = self.env['ir.sequence'].next_by_code('upah.harian.logistic') else: vals['name'] = self.env['ir.sequence'].next_by_code('upah.harian.office') return super().create(vals) # ========================================================= # ACTION # ========================================================= def action_approve(self): self.cancel_reason = False or "" dept_head_ids = [25] if not self.upah_harian_line.mapped('kegiatan'): raise UserError("Harus Isi Kegiatan yang dilakukan") approver = self.env.user.name if self.state == 'draft': self.state = 'pic' return { 'effect': { 'fadeout': 'slow', 'type': 'rainbow_man', 'message': 'Berhasil Submit', } } elif self.state == 'pic': if not self.env.user.pic: raise UserError("Only PIC user can approve this document.") self.state = 'dept' self.approved_by_pic = approver self.date_approved_pic = fields.Datetime.now() return { 'effect': { 'fadeout': 'slow', 'type': 'rainbow_man', 'message': 'Berhasil Approve', } } elif self.state == 'dept': if self.env.user.id not in dept_head_ids: raise UserError("Harus di Approve kepala departemen") self.state = 'waiting_payment' self.approved_by_dept = approver self.date_approved_dept = fields.Datetime.now() return { 'effect': { 'fadeout': 'slow', 'type': 'rainbow_man', 'message': 'Berhasil approve', } } def action_reset_to_draft(self): if self.state != 'cancel': raise UserError("Cancel Pengajuan Dahulu Sebelum set to Draft") self.state ='draft' self.approved_by_pic = False or "" self.approved_by_dept = False or "" self.approved_by_finance = False or "" def action_cancel(self): if not self.cancel_reason: raise UserError('Harus Isi Alasan Cancel') if self.state in ['paid', 'dept']: raise UserError('Tidak Bisa Cancel Karena pengajuan sudah diapprove') self.state = 'cancel' self.approved_by_pic = False or "" self.approved_by_dept = False or "" self.approved_by_finance = False or "" self.date_approved_finance = False or "" self.date_approved_dept= False or "" self.date_approved_pic = False or "" def write(self, vals): res = super().write(vals) if 'attachment_file_image' in vals or 'attachment_file_pdf' in vals: for rec in self: if rec.attachment_file_image or rec.attachment_file_pdf: rec.state = 'paid' return res def action_create_journal_entries(self): if not self.env.user.has_group('indoteknik_custom.group_role_fat'): raise UserError("Hanya Finance yang bisa create journal") move = self.env['account.move'].create({ 'date': fields.Date.today(), 'journal_id': 11, }) self.journal_upah_id = move.id return { 'type': 'ir.actions.act_window', 'name': 'Journal Entry', 'res_model': 'account.move', 'view_mode': 'form', 'res_id': move.id, 'target': 'current', } def action_open_journal(self): return { 'name': 'Journal Entry', 'view_mode': 'form', 'res_model': 'account.move', 'type': 'ir.actions.act_window', 'res_id': self.journal_upah_id.id, 'target': 'current' } # Constrains @api.constrains('attachment_file_image', 'attachment_file_pdf', 'attachment_type') def _check_finance_only_attachment(self): if not self.env.user.has_group('indoteknik_custom.group_role_fat'): for rec in self: if rec.attachment_file_image or rec.attachment_file_pdf: raise ValidationError("Only Finance can upload attachment.") # Restrictions def _search(self, domain, offset=0, limit=None, order=None, count=False, access_rights_uid=None): if self.env.user.harian: domain = expression.AND([ domain, [('pemohon', '=', self.env.user.id)] ]) return super()._search( domain, offset=offset, limit=limit, order=order, count=count, access_rights_uid=access_rights_uid ) class UpahHarianLine(models.Model): _name = 'upah.harian.line' _description = 'Upah Harian Line' _order = 'tanggal_line asc' MAX_WORKING_HOUR = 7.5 upah_harian_id = fields.Many2one( 'upah.harian', ondelete='cascade' ) tanggal_line = fields.Date( 'Tanggal', required=True ) hari = fields.Char('Hari', compute='_compute_hari') jam_masuk = fields.Datetime( 'Jam Masuk', required=True, ) jam_keluar = fields.Datetime( 'Jam Keluar', required=True, ) kegiatan = fields.Text( 'Kegiatan', required=True ) is_ganti_jam = fields.Boolean( 'Ganti Jam Kerja', default=False ) total_jam_kerja = fields.Float( 'Total Jam Kerja', compute='_compute_total_jam_kerja', store=True ) upah_harian_compute = fields.Float( 'Upah Harian', compute='_compute_upah_harian', store=True ) # ========================================================= # COMPUTE # ========================================================= @api.depends('tanggal_line') def _compute_hari(self): mapping = { 'Monday': 'Senin', 'Tuesday': 'Selasa', 'Wednesday': 'Rabu', 'Thursday': 'Kamis', 'Friday': 'Jumat', 'Saturday': 'Sabtu', 'Sunday': 'Minggu', } for rec in self: if rec.tanggal_line: day = rec.tanggal_line.strftime('%A') rec.hari = mapping.get(day) else: rec.hari = False @api.depends('jam_masuk', 'jam_keluar', 'is_ganti_jam') def _compute_total_jam_kerja(self): MAX = 7.5 for doc in self.mapped('upah_harian_id'): lines = doc.upah_harian_line.filtered( lambda l: l.tanggal_line ).sorted('tanggal_line') excess_pool = 0 for line in lines: if not line.jam_masuk or not line.jam_keluar: line.total_jam_kerja = 0 continue delta = line.jam_keluar - line.jam_masuk jam = max(delta.total_seconds() / 3600, 0) if line.is_ganti_jam and jam > MAX: line.total_jam_kerja = MAX excess_pool += jam - MAX else: line.total_jam_kerja = min(jam, MAX) if excess_pool > 0: for line in lines: if excess_pool <= 0: break if line.total_jam_kerja < MAX: need = MAX - line.total_jam_kerja tambahan = min(need, excess_pool) line.total_jam_kerja += tambahan excess_pool -= tambahan @api.depends('total_jam_kerja', 'upah_harian_id.upah_harian') def _compute_upah_harian(self): MAX = self.MAX_WORKING_HOUR for line in self: if not line.upah_harian_id.upah_harian: line.upah_harian_compute = 0 continue rate = line.upah_harian_id.upah_harian / MAX line.upah_harian_compute = line.total_jam_kerja * rate # ========================================================= # VALIDATION # ========================================================= @api.constrains('jam_masuk', 'jam_keluar') def _check_jam_valid(self): for line in self: if not line.jam_masuk or not line.jam_keluar: continue delta = line.jam_keluar - line.jam_masuk if delta.total_seconds() <= 0: raise ValidationError( "Jam keluar harus lebih besar dari jam masuk." )