from odoo import models, api, fields, _ from odoo.exceptions import UserError from datetime import datetime # import datetime import logging from terbilang import Terbilang import pytz _logger = logging.getLogger(__name__) class CustomerRebate(models.Model): _name = 'customer.rebate' _order = 'id desc' _inherit = ['mail.thread'] partner_id = fields.Many2one('res.partner', string='Customer', required=True) date_from = fields.Date(string='Date From', required=True, help="Pastikan tanggal 1 januari, jika tidak, code akan break") date_to = fields.Date(string='Date To', required=True, help="Pastikan tanggal 31 desember, jika tidak, code akan break") description = fields.Char(string='Description') target_1st = fields.Float(string='Target/Quarter 1st') target_2nd = fields.Float(string='Target/Quarter 2nd') achieve_1 = fields.Float(string='Achieve 1 %') achieve_2 = fields.Float(string='Achieve 2 %') dpp_q1 = fields.Float(string='DPP Q1', compute='_compute_current_dpp') dpp_q2 = fields.Float(string='DPP Q2', compute='_compute_current_dpp') dpp_q3 = fields.Float(string='DPP Q3', compute='_compute_current_dpp') dpp_q4 = fields.Float(string='DPP Q4', compute='_compute_current_dpp') status_q1 = fields.Char(string='Status Q1', compute='_compute_achievement') status_q2 = fields.Char(string='Status Q2', compute='_compute_achievement') status_q3 = fields.Char(string='Status Q3', compute='_compute_achievement') status_q4 = fields.Char(string='Status Q4', compute='_compute_achievement') # all code class CustomerRebate deprecated, cause lack of performance def _compute_current_dpp(self): for line in self: line.dpp_q1 = line._get_current_dpp_q1(line) line.dpp_q2 = line._get_current_dpp_q2(line) line.dpp_q3 = line._get_current_dpp_q3(line) line.dpp_q4 = line._get_current_dpp_q4(line) def _compute_achievement(self): for line in self: line.status_q1 = line._check_achievement(line.target_1st, line.target_2nd, line.dpp_q1) line.status_q2 = line._check_achievement(line.target_1st, line.target_2nd, line.dpp_q2) line.status_q3 = line._check_achievement(line.target_1st, line.target_2nd, line.dpp_q3) line.status_q4 = line._check_achievement(line.target_1st, line.target_2nd, line.dpp_q4) def _check_achievement(self, target_1st, target_2nd, dpp): status = 'not achieve' if dpp >= target_1st: status = '1st' elif dpp >= target_2nd: status = '2nd' else: status = 'not achieve' return status def _get_current_dpp_q1(self, line): sum_dpp = 0 brand = [10, 89, 122] where = [ ('move_id.move_type', '=', 'out_invoice'), ('move_id.state', '=', 'posted'), ('move_id.is_customer_commision', '=', False), ('move_id.partner_id.id', '=', line.partner_id.id), ('move_id.invoice_date', '>=', line.date_from), ('move_id.invoice_date', '<=', '2023-03-31'), ('product_id.x_manufacture', 'in', brand), ] invoice_lines = self.env['account.move.line'].search(where, order='id') for invoice_line in invoice_lines: sum_dpp += invoice_line.price_subtotal return sum_dpp def _get_current_dpp_q2(self, line): sum_dpp = 0 brand = [10, 89, 122] where = [ ('move_id.move_type', '=', 'out_invoice'), ('move_id.state', '=', 'posted'), ('move_id.is_customer_commision', '=', False), ('move_id.partner_id.id', '=', line.partner_id.id), ('move_id.invoice_date', '>=', '2023-04-01'), ('move_id.invoice_date', '<=', '2023-06-30'), ('product_id.x_manufacture', 'in', brand), ] invoices = self.env['account.move.line'].search(where, order='id') for invoice in invoices: sum_dpp += invoice.price_subtotal return sum_dpp def _get_current_dpp_q3(self, line): sum_dpp = 0 brand = [10, 89, 122] where = [ ('move_id.move_type', '=', 'out_invoice'), ('move_id.state', '=', 'posted'), ('move_id.is_customer_commision', '=', False), ('move_id.partner_id.id', '=', line.partner_id.id), ('move_id.invoice_date', '>=', '2023-07-01'), ('move_id.invoice_date', '<=', '2023-09-30'), ('product_id.x_manufacture', 'in', brand), ] invoices = self.env['account.move.line'].search(where, order='id') for invoice in invoices: sum_dpp += invoice.price_subtotal return sum_dpp def _get_current_dpp_q4(self, line): sum_dpp = 0 brand = [10, 89, 122] where = [ ('move_id.move_type', '=', 'out_invoice'), ('move_id.state', '=', 'posted'), ('move_id.is_customer_commision', '=', False), ('move_id.partner_id.id', '=', line.partner_id.id), ('move_id.invoice_date', '>=', '2023-10-01'), ('move_id.invoice_date', '<=', line.date_to), ('product_id.x_manufacture', 'in', brand), ] invoices = self.env['account.move.line'].search(where, order='id') for invoice in invoices: sum_dpp += invoice.price_subtotal return sum_dpp class RejectReasonCommision(models.TransientModel): _name = 'reject.reason.commision' _description = 'Wizard for Reject Reason Customer Commision' request_id = fields.Many2one('customer.commision', string='Request') reason_reject = fields.Text(string='Reason for Rejection', required=True, tracking=True) def confirm_reject(self): commision = self.request_id if commision: commision.last_status = commision.status commision.write({'reason_reject': self.reason_reject}) commision.status = 'reject' return {'type': 'ir.actions.act_window_close'} class CustomerCommision(models.Model): _name = 'customer.commision' _order = 'id desc' _inherit = ['mail.thread'] _rec_name = 'number' _description = 'Customer Benefits' number = fields.Char(string='Document No', index=True, copy=False, readonly=True) date_from = fields.Date(string='Date From', required=True) date_to = fields.Date(string='Date To', required=True) partner_ids = fields.Many2many('res.partner', String='Customer', required=True) description = fields.Char(string='Description') notification = fields.Char(string='Notification') commision_lines = fields.One2many('customer.commision.line', 'customer_commision_id', string='Lines', auto_join=True) status = fields.Selection([ ('draft', 'Draft'), ('pengajuan1', 'Menunggu Approval Manager Sales'), ('pengajuan2', 'Menunggu Approval Marketing'), ('pengajuan3', 'Menunggu Approval Pimpinan'), ('pengajuan4', 'Menunggu Approval Accounting'), ('approved', 'Approved'), ('reject', 'Rejected'), ], string='Status', copy=False, readonly=True, tracking=3, index=True, track_visibility='onchange', default='draft') last_status = fields.Selection([ ('draft', 'Draft'), ('pengajuan1', 'Menunggu Approval Manager Sales'), ('pengajuan2', 'Menunggu Approval Marketing'), ('pengajuan3', 'Menunggu Approval Pimpinan'), ('pengajuan4', 'Menunggu Approval Accounting'), ('approved', 'Approved'), ('reject', 'Rejected'), ], string='Status') # commision_percent = fields.Float(string='Commision %', tracking=3) commision_percent = fields.Float(string='Persentase (%)', tracking=3) # commision_amt = fields.Float(string='Commision Amount', tracking=3) commision_amt = fields.Float(string='Amount', tracking=3) # cashback = fields.Float(string='Cashback', compute="compute_cashback") cashback = fields.Float(string='PPh Cashback', compute="compute_cashback") # total_commision = fields.Float(string='Total Commision', compute="compute_cashback") total_commision = fields.Float(string='Cashback yang dibayarkan', compute="compute_cashback") total_cashback = fields.Float(string='Total Cashback') commision_amt_text = fields.Char(string='Amount Text', compute='compute_delivery_amt_text') total_cashback_text = fields.Char(string='Cashback Text', compute='compute_total_cashback_text') total_dpp = fields.Float(string='Total DPP', compute='_compute_total_dpp') biaya_lain_lain = fields.Float(string='Biaya Lain-lain') commision_type = fields.Selection([ ('fee', 'Fee'), ('cashback', 'Cashback'), ('rebate', 'Rebate'), ], string='Commision Type', required=True) bank_name = fields.Char(string='Bank', tracking=3, required=True) account_name = fields.Char(string='Account Name', tracking=3, required=True) bank_account = fields.Char(string='Account No', tracking=3, required=True) note_transfer = fields.Char(string='Keterangan') brand_ids = fields.Many2many('x_manufactures', string='Brands') payment_status = fields.Selection([ ('pending', 'Pending'), ('payment', 'Payment'), ], string='Payment Status', copy=False, readonly=True, tracking=3, default='pending') note_finnance = fields.Text('Notes Finance') reason_reject = fields.Char(string='Reason Reaject', tracking=True, track_visibility='onchange') approved_by = fields.Char(string='Approved By', tracking=True, track_visibility='always') grouped_so_number = fields.Char(string='Group SO Number', compute='_compute_grouped_numbers') grouped_invoice_number = fields.Char(string='Group Invoice Number', compute='_compute_grouped_numbers') sales_id = fields.Many2one('res.users', string="Sales", tracking=True, required=True, domain=[('groups_id', 'in', [94]),('id', '!=', 15710)]) date_approved_sales = fields.Datetime(string="Date Approved Sales", tracking=True) date_approved_marketing = fields.Datetime(string="Date Approved Marketing", tracking=True) date_approved_pimpinan = fields.Datetime(string="Date Approved Pimpinan", tracking=True) date_approved_accounting = fields.Datetime(string="Date Approved Accounting", tracking=True) position_sales = fields.Char(string="Position Sales", tracking=True) position_marketing = fields.Char(string="Position Marketing", tracking=True) position_pimpinan = fields.Char(string="Position Pimpinan", tracking=True) position_accounting = fields.Char(string="Position Accounting", tracking=True) # get partner ids so it can be grouped by @api.model def read_group(self, domain, fields, groupby, offset=0, limit=None, orderby=False, lazy=True): if 'partner_ids' in groupby: # Get all records matching the domain records = self.search(domain) # Create groups for each partner groups = {} for record in records: for partner in record.partner_ids: if partner.id not in groups: groups[partner.id] = { 'partner_ids': partner, 'records': self.env['customer.commision'] } groups[partner.id]['records'] |= record # Format the result result = [] for partner_id, group_data in groups.items(): partner = group_data['partner_ids'] record_ids = group_data['records'].ids # Create the domain group_domain = [('id', 'in', record_ids)] if domain: group_domain = ['&'] + domain + group_domain result.append({ 'partner_ids': (partner.id, partner.display_name), 'partner_ids_count': len(record_ids), '__domain': group_domain, '__count': len(record_ids), }) return result return super(CustomerCommision, self).read_group(domain, fields, groupby, offset, limit, orderby, lazy) def compute_delivery_amt_text(self): tb = Terbilang() for record in self: res = '' try: if record.commision_amt > 0: tb.parse(int(record.commision_amt)) res = tb.getresult().title() record.commision_amt_text = res + ' Rupiah' except: record.commision_amt_text = res def compute_total_cashback_text(self): tb = Terbilang() for record in self: res = '' try: if record.total_commision > 0: tb.parse(int(record.total_commision)) res = tb.getresult().title() record.total_cashback_text = f"{res} Rupiah" if res else "" except Exception as e: record.total_cashback_text = "" _logger.error("Error computing cashback text: %s", str(e)) def _compute_grouped_numbers(self): for rec in self: so_numbers = set() invoice_numbers = set() for line in rec.commision_lines: if line.invoice_id: if line.invoice_id.sale_id: so_numbers.add(line.invoice_id.sale_id.name) invoice_numbers.add(line.invoice_id.name) rec.grouped_so_number = ', '.join(sorted(so_numbers)) rec.grouped_invoice_number = ', '.join(sorted(invoice_numbers)) # add status for type of commision, fee, rebate / cashback # include child or not? # @api.constrains('partner_ids') def _onchange_partner_ids(self): commision = self.env['cust.commision'].search([ ('partner_id', 'in', [rec.id for rec in self.partner_ids]), ('commision_type', '=', self.commision_type) ], limit=1) if commision: if self.commision_type == 'fee': max_commision = max(commision.mapped('commision_percent')) self.commision_percent = max_commision else: target_1st, target_2nd = commision.target_1st, commision.target_2nd achieve_1st, achieve_2nd = commision.achieve_1st, commision.achieve_2nd if self.total_dpp >= target_1st: self.commision_percent = achieve_1st elif target_2nd <= self.total_dpp < target_1st: self.commision_percent = achieve_2nd else: self.commision_percent = 0 self._onchange_commision_amt() @api.constrains('commision_percent') def _onchange_commision_percent(self): if not self.env.context.get('_onchange_commision_percent', True): return if self.commision_amt == 0: self.commision_amt = self.commision_percent * self.total_dpp // 100 @api.constrains('commision_amt') def _onchange_commision_amt(self): """ Constrain to update commision percent from commision amount """ if not self.env.context.get('_onchange_commision_amt', True): return if self.total_dpp > 0 and self.commision_percent == 0: self.commision_percent = (self.commision_amt / self.total_dpp) * 100 def compute_cashback(self): if self.commision_type == 'cashback' and self.commision_amt > 0: self.cashback = self.commision_amt * 0.15 self.total_commision = self.commision_amt * 0.85 else: self.cashback = 0 self.total_commision = 0 def _compute_total_dpp(self): for data in self: total_dpp = 0 for line in data.commision_lines: total_dpp = total_dpp + line.dpp data.total_dpp = total_dpp - data.biaya_lain_lain @api.model def create(self, vals): commision_type = vals.get('commision_type') if commision_type == 'cashback': sequence_code = 'customer.commision.cashback' elif commision_type == 'fee': sequence_code = 'customer.commision.fee' elif commision_type == 'rebate': sequence_code = 'customer.commision.rebate' else: raise UserError('Tipe komisi tidak dikenal!') vals['number'] = self.env['ir.sequence'].next_by_code(sequence_code) or '0' return super(CustomerCommision, self).create(vals) # @api.model # def create(self, vals): # vals['number'] = self.env['ir.sequence'].next_by_code('customer.commision') or '0' # # if vals['commision_amt'] > 0: # # commision_amt = vals['commision_amt'] # # total_dpp = vals['total_dpp'] # # commision_percent = commision_amt / total_dpp * 100 # # vals['commision_percent'] = commision_percent # result = super(CustomerCommision, self).create(vals) # return result def _fill_note_finance(self): for rec in self: fee_percent = rec.commision_percent or 0.0 dpp = rec.total_dpp or 0.0 fee = dpp * fee_percent / 100 pph21 = 0.5 * fee * 0.05 fee_net = fee - pph21 rec.note_finnance = ( "Kelengkapan data penerima fee sudah lengkap (NPWP dan KTP)\n" f"Perhitungan Fee ({fee_percent:.0f}%) dari nilai DPP pada Invoice terlampir sudah\n" f"sesuai yaitu Rp {fee:,.0f}\n" "Sesuai PMK No. 168 tahun 2023, komisi fee dikenakan PPH 21\n" "sebesar :\n" f"= 50% x Penghasilan Bruto x 5%\n" f"= 50% x Rp {fee:,.0f} x 5%\n" f"= Rp {pph21:,.0f}\n" "Sehingga fee bersih sebesar\n" f"= Rp {fee:,.0f} - Rp {pph21:,.0f}\n" f"= Rp {fee_net:,.0f}" ) def action_confirm_customer_commision(self): jakarta_tz = pytz.timezone('Asia/Jakarta') now = datetime.now(jakarta_tz) now_naive = now.replace(tzinfo=None) if not self.status or self.status == 'draft': self.status = 'pengajuan1' elif self.status == 'pengajuan1' and (self.env.user.id == 19 or self.env.user.has_group('indoteknik_custom.group_role_it')): self.status = 'pengajuan2' self.approved_by = (self.approved_by + ', ' if self.approved_by else '') + self.env.user.name self.date_approved_sales = now_naive self.position_sales = 'Sales Manager' elif self.status == 'pengajuan2' and (self.env.user.id == 216 or self.env.user.has_group('indoteknik_custom.group_role_it')): self.status = 'pengajuan3' self.approved_by = (self.approved_by + ', ' if self.approved_by else '') + self.env.user.name self.date_approved_marketing = now_naive self.position_marketing = 'Marketing Manager' elif self.status == 'pengajuan3' and (self.env.user.is_leader or self.env.user.has_group('indoteknik_custom.group_role_it')): self.status = 'pengajuan4' self.approved_by = (self.approved_by + ', ' if self.approved_by else '') + self.env.user.name self.date_approved_pimpinan = now_naive self.position_pimpinan = 'Pimpinan' elif self.status == 'pengajuan4' and (self.env.user.id == 1272 or self.env.user.has_group('indoteknik_custom.group_role_it')): for line in self.commision_lines: line.invoice_id.is_customer_commision = True if self.commision_type == 'fee': self._fill_note_finance() self.status = 'approved' self.approved_by = (self.approved_by + ', ' if self.approved_by else '') + self.env.user.name self.date_approved_accounting = now_naive self.position_accounting = 'Accounting' else: raise UserError('Harus di approved oleh yang bersangkutan') return def action_reject(self): # add 2 step approval return { 'type': 'ir.actions.act_window', 'name': _('Reject Reason'), 'res_model': 'reject.reason.commision', 'view_mode': 'form', 'target': 'new', 'context': {'default_request_id': self.id}, } def button_draft(self): for commision in self: commision.status = commision.last_status if commision.last_status else 'draft' def action_confirm_customer_payment(self): if self.status != 'approved': raise UserError('Commision harus di approve terlebih dahulu sebelum di konfirmasi pembayarannya') if self.payment_status == 'payment': raise UserError('Customer Commision sudah berstatus Payment') group_id = self.env.ref('indoteknik_custom.group_role_fat').id users_in_group = self.env['res.users'].search([('groups_id', 'in', [group_id])]) if self.env.user.id not in users_in_group.mapped('id'): raise UserError('Hanya bisa dikonfirmasi oleh FAT') else: self.payment_status = 'payment' return def generate_customer_commision(self): if self.commision_lines: raise UserError('Line sudah ada, tidak bisa di generate ulang') if self.commision_type == 'fee': self._generate_customer_commision_fee() else: self._generate_customer_commision_rebate() context = self.env.context.copy() context.update({'_onchange_commision_amt': False}) self.env.context = context self._onchange_partner_ids() # self._onchange_commision_percent() # self._onchange_commision_amt() def _generate_customer_commision_rebate(self): for rec in self: # partners = rec.partner_ids.child_ids + rec.partner_ids partners = rec.partner_ids for partner in partners: brand = [int(brand) for brand in rec.brand_ids] where = [ ('move_id.move_type', '=', 'out_invoice'), ('move_id.state', '=', 'posted'), ('move_id.is_customer_commision', '=', False), ('move_id.amount_residual_signed', '=', 0), ('move_id.partner_id.id', '=', partner.id), ('move_id.invoice_date', '>=', self.date_from), ('move_id.invoice_date', '<=', self.date_to), ('product_id.x_manufacture', 'in', brand), ('exclude_from_invoice_tab', '=', False), ] invoice_lines = self.env['account.move.line'].search(where, order='id') for invoice_line in invoice_lines: tax = invoice_line.price_total - invoice_line.price_subtotal self.env['customer.commision.line'].create([{ 'customer_commision_id': self.id, 'partner_id': invoice_line.move_id.partner_id.id, 'invoice_id': invoice_line.move_id.id, 'state': invoice_line.move_id.state, 'product_id': invoice_line.product_id.id, 'dpp': invoice_line.price_subtotal, 'tax': tax, 'total': invoice_line.price_total }]) return def _generate_customer_commision_fee(self): for rec in self: # partners = rec.partner_ids.child_ids + rec.partner_ids partners = rec.partner_ids for partner in partners: where = [ ('move_type', '=', 'out_invoice'), ('state', '=', 'posted'), ('is_customer_commision', '=', False), ('amount_residual_signed', '=', 0), ('partner_id.id', '=', partner.id), ('invoice_date', '>=', self.date_from), ('invoice_date', '<=', self.date_to), ] invoices = self.env['account.move'].search(where, order='id') for invoice in invoices: self.env['customer.commision.line'].create([{ 'customer_commision_id': self.id, 'partner_id': invoice.partner_id.id, 'invoice_id': invoice.id, 'state': invoice.state, 'dpp': invoice.amount_untaxed_signed, 'tax': invoice.amount_tax_signed, 'total': invoice.amount_total_signed }]) return class CustomerCommisionLine(models.Model): _name = 'customer.commision.line' _order = 'id' customer_commision_id = fields.Many2one('customer.commision', string='Ref', required=True, ondelete='cascade', copy=False) invoice_id = fields.Many2one('account.move', string='Invoice') partner_id = fields.Many2one('res.partner', string='Customer') state = fields.Char(string='InvStatus') dpp = fields.Float(string='DPP') tax = fields.Float(string='TaxAmt') total = fields.Float(string='Total') total_percent_margin = fields.Float('Total Margin', related='invoice_id.sale_id.total_percent_margin') total_margin_excl_third_party = fields.Float('Before Margin', related='invoice_id.sale_id.total_margin_excl_third_party') product_id = fields.Many2one('product.product', string='Product') sale_order_id = fields.Many2one('sale.order', string='Sale Order', related='invoice_id.sale_id') class AccountMove(models.Model): _inherit = 'account.move' is_customer_commision = fields.Boolean(string='Customer Commision Used')