From 101948d6029b06a69759b8f246f1744312f035c0 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Fri, 29 Aug 2025 10:52:19 +0700 Subject: (andri) add is locked CBD jika ada customer yang sudah jatuh tempo --- indoteknik_custom/models/account_move.py | 28 +++++++++++++++++++++++ indoteknik_custom/models/approval_payment_term.py | 3 ++- indoteknik_custom/models/res_partner.py | 15 ++++++++---- indoteknik_custom/models/sale_order.py | 9 ++++++++ 4 files changed, 49 insertions(+), 6 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/account_move.py b/indoteknik_custom/models/account_move.py index c44cad78..55e640e4 100644 --- a/indoteknik_custom/models/account_move.py +++ b/indoteknik_custom/models/account_move.py @@ -105,6 +105,34 @@ class AccountMove(models.Model): tracking=True ) + def _check_and_lock_cbd(self): + cbd_term = self.env['account.payment.term'].browse(26) + today = date.today() + + # Cari semua invoice overdue + overdue_invoices = self.search([ + ('move_type', '=', 'out_invoice'), + ('state', '=', 'posted'), + ('payment_state', 'not in', ['paid', 'in_payment', 'reversed']), + ('invoice_date_due', '!=', False), + ('invoice_date_due', '<=', today - timedelta(days=30)), + ], limit=3) + + _logger.info(f"Found {len(overdue_invoices)} overdue invoices for CBD lock check.") + _logger.info(f"Overdue Invoices: {overdue_invoices.mapped('name')}") + + # Ambil partner unik dari invoice + partners_to_lock = overdue_invoices.mapped('partner_id').filtered(lambda p: not p.is_cbd_locked) + _logger.info(f"Partners to lock: {partners_to_lock.mapped('name')}") + + # Lock hanya partner yang belum locked + if partners_to_lock: + partners_to_lock.write({ + 'is_cbd_locked': True, + 'property_payment_term_id': cbd_term.id, + }) + + def compute_partial_payment(self): for move in self: if move.amount_total_signed > 0 and move.amount_residual_signed > 0 and move.payment_state == 'partial': diff --git a/indoteknik_custom/models/approval_payment_term.py b/indoteknik_custom/models/approval_payment_term.py index 8618856a..08d91738 100644 --- a/indoteknik_custom/models/approval_payment_term.py +++ b/indoteknik_custom/models/approval_payment_term.py @@ -171,7 +171,8 @@ class ApprovalPaymentTerm(models.Model): 'blocking_stage': self.blocking_stage, 'warning_stage': self.warning_stage, 'active_limit': self.active_limit, - 'property_payment_term_id': self.property_payment_term_id.id + 'property_payment_term_id': self.property_payment_term_id.id, + 'is_locked_cbd': False, }) self.approve_date = datetime.utcnow() self.state = 'approved' diff --git a/indoteknik_custom/models/res_partner.py b/indoteknik_custom/models/res_partner.py index 148a3fd0..017be730 100644 --- a/indoteknik_custom/models/res_partner.py +++ b/indoteknik_custom/models/res_partner.py @@ -1,6 +1,6 @@ from odoo import models, fields, api from odoo.exceptions import UserError, ValidationError -from datetime import datetime +from datetime import datetime, timedelta from odoo.http import request import re import requests @@ -181,10 +181,15 @@ class ResPartner(models.Model): payment_difficulty = fields.Selection([('bermasalah', 'Bermasalah'),('sulit', 'Sulit'),('agak_sulit', 'Agak Sulit'),('normal', 'Normal')], string='Payment Difficulty', tracking=3) payment_history_url = fields.Text(string='Payment History URL') - # no compute - # payment_diff = fields.Selection([('bermasalah', 'Bermasalah'),('sulit', 'Sulit'),('agak_sulit', 'Agak Sulit'),('normal', 'Normal')], string='Payment Difficulty', tracking=3) - - # tidak terpakai + is_cbd_locked = fields.Boolean("Locked to CBD?", default=False, tracking=True, help="Jika dicentang, maka partner ini terkunci pada payment term CBD karena memiliki invoice yang sudah jatuh tempo lebih dari 30 hari.") + + # centang manual is_cbd_locked jika payment term diubah ke CBD + @api.onchange('is_cbd_locked') + def _onchange_is_cbd_locked(self): + if self.is_cbd_locked: + cbd_term = self.env['account.payment.term'].browse(26) + if cbd_term: + self.property_payment_term_id = cbd_term.id @api.model def _default_payment_term(self): diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index ffb53dce..992c1a5d 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -394,6 +394,15 @@ class SaleOrder(models.Model): ('paid', 'Full Paid'), ('no_invoice', 'No Invoice'), ], string="Payment Status Invoice", compute="_compute_payment_state_custom", store=False) + partner_is_cbd_locked = fields.Boolean( + string="Partner Locked CBD", + compute="_compute_partner_is_cbd_locked" + ) + + @api.depends('partner_id.is_cbd_locked') + def _compute_partner_is_cbd_locked(self): + for order in self: + order.partner_is_cbd_locked = order.partner_id.is_cbd_locked @api.depends('invoice_ids.payment_state', 'invoice_ids.amount_total', 'invoice_ids.amount_residual') def _compute_payment_state_custom(self): -- cgit v1.2.3 From f41e635755bb8092d9a85ccc0a0cb675afc7af00 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Sun, 31 Aug 2025 12:17:35 +0700 Subject: oke --- indoteknik_custom/models/sale_order.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index ffb53dce..8cb169a6 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -1900,26 +1900,30 @@ class SaleOrder(models.Model): # raise UserError('Kelurahan Real Delivery Address harus diisi') def generate_payment_link_midtrans_sales_order(self): - # midtrans_url = 'https://app.sandbox.midtrans.com/snap/v1/transactions' # dev - sandbox - # midtrans_auth = 'Basic U0ItTWlkLXNlcnZlci1uLVY3ZDJjMlpCMFNWRUQyOU95Q1dWWXA6' # dev - sandbox - midtrans_url = 'https://app.midtrans.com/snap/v1/transactions' # production - midtrans_auth = 'Basic TWlkLXNlcnZlci1SbGMxZ2gzWGpSVW5scl9JblZzTV9OTnU6' # production + # midtrans_url = 'https://app.sandbox.midtrans.com/snap/v1/transactions' # dev - sandbox + # midtrans_auth = 'Basic U0ItTWlkLXNlcnZlci1uLVY3ZDJjMlpCMFNWRUQyOU95Q1dWWXA6' # dev - sandbox + midtrans_url = 'https://app.midtrans.com/snap/v1/transactions' # production + midtrans_auth = 'Basic TWlkLXNlcnZlci1SbGMxZ2gzWGpSVW5scl9JblZzTV9OTnU6' # production + so_number = self.name so_number = so_number.replace('/', '-') so_grandtotal = math.floor(self.grand_total) + headers = { 'Accept': 'application/json', 'Content-Type': 'application/json', 'Authorization': midtrans_auth, } - check_url = f'https://api.midtrans.com/v2/{so_number}/status' - check_response = requests.get(check_url, headers=headers) + # ==== ENV ==== + # check_url = f'https://api.sandbox.midtrans.com/v2/{so_number}/status' # dev - sandbox + check_url = f'https://api.midtrans.com/v2/{so_number}/status' # production + # ============================================= + check_response = requests.get(check_url, headers=headers) if check_response.status_code == 200: status_response = check_response.json() - if status_response.get('transaction_status') == 'expire' or status_response.get( - 'transaction_status') == 'cancel': + if status_response.get('transaction_status') in ('expire', 'cancel'): so_number = so_number + '-cpl' json_data = { @@ -1951,7 +1955,6 @@ class SaleOrder(models.Model): buffer = BytesIO() img.save(buffer, format="PNG") qr_code_img = base64.b64encode(buffer.getvalue()).decode() - self.payment_qr_code = qr_code_img @api.model -- cgit v1.2.3 From 8bb599fc4b3e3de94fada7c277518446a88630fa Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Mon, 1 Sep 2025 15:50:14 +0700 Subject: (andri) add view unpaid invoices --- indoteknik_custom/models/__init__.py | 3 ++- indoteknik_custom/models/unpaid_invoice_view.py | 28 +++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 indoteknik_custom/models/unpaid_invoice_view.py (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/__init__.py b/indoteknik_custom/models/__init__.py index 3a9f9312..ba3dbad9 100755 --- a/indoteknik_custom/models/__init__.py +++ b/indoteknik_custom/models/__init__.py @@ -156,4 +156,5 @@ from . import refund_sale_order # from . import patch from . import tukar_guling from . import tukar_guling_po -from . import update_date_planned_po_wizard \ No newline at end of file +from . import update_date_planned_po_wizard +from . import unpaid_invoice_view \ No newline at end of file diff --git a/indoteknik_custom/models/unpaid_invoice_view.py b/indoteknik_custom/models/unpaid_invoice_view.py new file mode 100644 index 00000000..77007102 --- /dev/null +++ b/indoteknik_custom/models/unpaid_invoice_view.py @@ -0,0 +1,28 @@ +from odoo import models, fields + +class UnpaidInvoiceView(models.Model): + _name = 'unpaid.invoice.view' + _description = 'Unpaid Invoices Monitoring' + _auto = False + _rec_name = 'partner_name' + + partner_id = fields.Many2one('res.partner', string='Partner') + partner_name = fields.Char(string='Partner Name') + email = fields.Char() + phone = fields.Char() + invoice_id = fields.Many2one('account.move', string='Invoice') + invoice_number = fields.Char(string='Invoice Number') + invoice_date = fields.Date() + invoice_date_due = fields.Date() + currency_id = fields.Many2one('res.currency', string='Currency') + amount_total = fields.Monetary(string='Total', currency_field='currency_id') + amount_residual = fields.Monetary(string='Residual', currency_field='currency_id') + payment_state = fields.Selection([ + ('not_paid','Not Paid'), + ('in_payment','In Payment'), + ('paid','Paid'), + ('reversed','Reversed') + ], string='Payment State') + payment_term = fields.Char() + invoice_day_to_due = fields.Integer(string="Day to Due") + new_invoice_day_to_due = fields.Integer(string="New Day Due") -- cgit v1.2.3 From 1330742f90b32c6dd214925ad893696cfae5ef38 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Mon, 1 Sep 2025 16:05:52 +0700 Subject: (andri) add string due date --- indoteknik_custom/models/unpaid_invoice_view.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/unpaid_invoice_view.py b/indoteknik_custom/models/unpaid_invoice_view.py index 77007102..517d078b 100644 --- a/indoteknik_custom/models/unpaid_invoice_view.py +++ b/indoteknik_custom/models/unpaid_invoice_view.py @@ -5,6 +5,7 @@ class UnpaidInvoiceView(models.Model): _description = 'Unpaid Invoices Monitoring' _auto = False _rec_name = 'partner_name' + _order = 'partner_name, new_invoice_day_to_due DESC' partner_id = fields.Many2one('res.partner', string='Partner') partner_name = fields.Char(string='Partner Name') @@ -13,7 +14,7 @@ class UnpaidInvoiceView(models.Model): invoice_id = fields.Many2one('account.move', string='Invoice') invoice_number = fields.Char(string='Invoice Number') invoice_date = fields.Date() - invoice_date_due = fields.Date() + invoice_date_due = fields.Date(string='Due Date') currency_id = fields.Many2one('res.currency', string='Currency') amount_total = fields.Monetary(string='Total', currency_field='currency_id') amount_residual = fields.Monetary(string='Residual', currency_field='currency_id') -- cgit v1.2.3 From 77539c2e9ee720c7e731d1b5138041263394a978 Mon Sep 17 00:00:00 2001 From: it-fixcomart Date: Tue, 2 Sep 2025 11:12:22 +0700 Subject: fix refund ongkir --- indoteknik_custom/models/refund_sale_order.py | 29 ++++++++------------------- 1 file changed, 8 insertions(+), 21 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/refund_sale_order.py b/indoteknik_custom/models/refund_sale_order.py index 6d0b5741..737419e7 100644 --- a/indoteknik_custom/models/refund_sale_order.py +++ b/indoteknik_custom/models/refund_sale_order.py @@ -392,6 +392,7 @@ class RefundSaleOrder(models.Model): total_invoice = sum(self.env['account.move'].browse(invoice_ids).mapped('amount_total_signed')) vals['total_invoice'] = total_invoice uang_masuk = rec.uang_masuk + amount_refund = vals.get('amount_refund', rec.amount_refund) if amount_refund <= 0: @@ -401,36 +402,22 @@ class RefundSaleOrder(models.Model): ('sale_order_ids', 'in', so_ids), ('id', '!=', rec.id) ]) - total_refunded = sum(existing_refunds.mapped('amount_refund')) + amount_refund - remaining = uang_masuk - total_refunded + total_refunded = sum(existing_refunds.mapped('amount_refund')) + if existing_refunds: + remaining = uang_masuk - total_refunded + else: + remaining = uang_masuk - amount_refund - if remaining < 0: - raise ValidationError("❌ Dana uang masuk telah sepenuhnya di refund tidak bisa Mengubah Nominal Refund") vals['remaining_refundable'] = remaining return super().write(vals) - @api.onchange('ongkir', 'amount_refund') + @api.onchange('amount_refund') def _onchange_refund_fields(self): for rec in self: refund_input = rec.amount_refund or 0.0 - - # ambil refund terakhir untuk SO - existing_refund = self.env['refund.sale.order'].search([ - ('sale_order_ids', 'in', rec.sale_order_ids.ids) - ], order='id desc', limit=1) - - if existing_refund: - sisa_uang_masuk = existing_refund.remaining_refundable - else: - sisa_uang_masuk = rec.uang_masuk or 0.0 - - # update field uang_masuk supaya form menampilkan sisa aktual - rec.uang_masuk = sisa_uang_masuk - - # hitung remaining setelah input refund - rec.remaining_refundable = sisa_uang_masuk - refund_input + rec.remaining_refundable = (rec.uang_masuk or 0.0) - refund_input @api.depends('status_payment', 'status') def _compute_is_locked(self): -- cgit v1.2.3 From ed459eb048e8e14075134ead0cb80d5da864af53 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Tue, 2 Sep 2025 11:39:22 +0700 Subject: (andri) comment method --- indoteknik_custom/models/account_move.py | 52 ++++++++++++++++---------------- 1 file changed, 26 insertions(+), 26 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/account_move.py b/indoteknik_custom/models/account_move.py index 55e640e4..7884b1d5 100644 --- a/indoteknik_custom/models/account_move.py +++ b/indoteknik_custom/models/account_move.py @@ -105,32 +105,32 @@ class AccountMove(models.Model): tracking=True ) - def _check_and_lock_cbd(self): - cbd_term = self.env['account.payment.term'].browse(26) - today = date.today() - - # Cari semua invoice overdue - overdue_invoices = self.search([ - ('move_type', '=', 'out_invoice'), - ('state', '=', 'posted'), - ('payment_state', 'not in', ['paid', 'in_payment', 'reversed']), - ('invoice_date_due', '!=', False), - ('invoice_date_due', '<=', today - timedelta(days=30)), - ], limit=3) - - _logger.info(f"Found {len(overdue_invoices)} overdue invoices for CBD lock check.") - _logger.info(f"Overdue Invoices: {overdue_invoices.mapped('name')}") - - # Ambil partner unik dari invoice - partners_to_lock = overdue_invoices.mapped('partner_id').filtered(lambda p: not p.is_cbd_locked) - _logger.info(f"Partners to lock: {partners_to_lock.mapped('name')}") - - # Lock hanya partner yang belum locked - if partners_to_lock: - partners_to_lock.write({ - 'is_cbd_locked': True, - 'property_payment_term_id': cbd_term.id, - }) + # def _check_and_lock_cbd(self): + # cbd_term = self.env['account.payment.term'].browse(26) + # today = date.today() + + # # Cari semua invoice overdue + # overdue_invoices = self.search([ + # ('move_type', '=', 'out_invoice'), + # ('state', '=', 'posted'), + # ('payment_state', 'not in', ['paid', 'in_payment', 'reversed']), + # ('invoice_date_due', '!=', False), + # ('invoice_date_due', '<=', today - timedelta(days=30)), + # ], limit=3) + + # _logger.info(f"Found {len(overdue_invoices)} overdue invoices for CBD lock check.") + # _logger.info(f"Overdue Invoices: {overdue_invoices.mapped('name')}") + + # # Ambil partner unik dari invoice + # partners_to_lock = overdue_invoices.mapped('partner_id').filtered(lambda p: not p.is_cbd_locked) + # _logger.info(f"Partners to lock: {partners_to_lock.mapped('name')}") + + # # Lock hanya partner yang belum locked + # if partners_to_lock: + # partners_to_lock.write({ + # 'is_cbd_locked': True, + # 'property_payment_term_id': cbd_term.id, + # }) def compute_partial_payment(self): -- cgit v1.2.3 From 70dfadc9aa8274e8f9aafa4ec594af0b5a37343b Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Tue, 2 Sep 2025 12:00:26 +0700 Subject: (andri) add tracking payment terms dan fix notif ketika membuat SO --- indoteknik_custom/models/res_partner.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/res_partner.py b/indoteknik_custom/models/res_partner.py index 017be730..80fe643b 100644 --- a/indoteknik_custom/models/res_partner.py +++ b/indoteknik_custom/models/res_partner.py @@ -184,12 +184,12 @@ class ResPartner(models.Model): is_cbd_locked = fields.Boolean("Locked to CBD?", default=False, tracking=True, help="Jika dicentang, maka partner ini terkunci pada payment term CBD karena memiliki invoice yang sudah jatuh tempo lebih dari 30 hari.") # centang manual is_cbd_locked jika payment term diubah ke CBD - @api.onchange('is_cbd_locked') - def _onchange_is_cbd_locked(self): - if self.is_cbd_locked: - cbd_term = self.env['account.payment.term'].browse(26) - if cbd_term: - self.property_payment_term_id = cbd_term.id + # @api.onchange('is_cbd_locked') + # def _onchange_is_cbd_locked(self): + # if self.is_cbd_locked: + # cbd_term = self.env['account.payment.term'].browse(26) + # if cbd_term: + # self.property_payment_term_id = cbd_term.id @api.model def _default_payment_term(self): @@ -198,7 +198,7 @@ class ResPartner(models.Model): property_payment_term_id = fields.Many2one( 'account.payment.term', string='Payment Terms', - default=_default_payment_term + default=_default_payment_term, tracking=3 ) @api.depends("street", "street2", "city", "state_id", "country_id", "blok", "nomor", "rt", "rw", "kelurahan_id", -- cgit v1.2.3 From 0d0e2b9a448f6f96a7ab06ecb970dbbd2018f5fe Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Tue, 2 Sep 2025 14:19:33 +0700 Subject: (andri) fix layout dan revisi field yang dibutuhkan --- indoteknik_custom/models/unpaid_invoice_view.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/unpaid_invoice_view.py b/indoteknik_custom/models/unpaid_invoice_view.py index 517d078b..f6ce79b8 100644 --- a/indoteknik_custom/models/unpaid_invoice_view.py +++ b/indoteknik_custom/models/unpaid_invoice_view.py @@ -9,21 +9,22 @@ class UnpaidInvoiceView(models.Model): partner_id = fields.Many2one('res.partner', string='Partner') partner_name = fields.Char(string='Partner Name') - email = fields.Char() - phone = fields.Char() + # email = fields.Char() + # phone = fields.Char() invoice_id = fields.Many2one('account.move', string='Invoice') invoice_number = fields.Char(string='Invoice Number') invoice_date = fields.Date() invoice_date_due = fields.Date(string='Due Date') + date_terima_tukar_faktur = fields.Date(string='Terima Faktur') currency_id = fields.Many2one('res.currency', string='Currency') - amount_total = fields.Monetary(string='Total', currency_field='currency_id') - amount_residual = fields.Monetary(string='Residual', currency_field='currency_id') + amount_total = fields.Monetary(string='Total Amount', currency_field='currency_id') + amount_residual = fields.Monetary(string='Sisa Amount', currency_field='currency_id') payment_state = fields.Selection([ ('not_paid','Not Paid'), ('in_payment','In Payment'), ('paid','Paid'), ('reversed','Reversed') ], string='Payment State') - payment_term = fields.Char() + payment_term_id = fields.Many2one('account.payment.term', string='Payment Term') invoice_day_to_due = fields.Integer(string="Day to Due") new_invoice_day_to_due = fields.Integer(string="New Day Due") -- cgit v1.2.3 From ddaedc0eeaf53b47786a7747664fc99ae4dc1a7a Mon Sep 17 00:00:00 2001 From: it-fixcomart Date: Tue, 2 Sep 2025 14:46:57 +0700 Subject: fix refund validasi --- indoteknik_custom/models/refund_sale_order.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/refund_sale_order.py b/indoteknik_custom/models/refund_sale_order.py index 737419e7..731d8e41 100644 --- a/indoteknik_custom/models/refund_sale_order.py +++ b/indoteknik_custom/models/refund_sale_order.py @@ -408,6 +408,8 @@ class RefundSaleOrder(models.Model): else: remaining = uang_masuk - amount_refund + if remaining < 0: + raise ValidationError("Semua dana sudah dikembalikan, tidak bisa mengajukan refund") vals['remaining_refundable'] = remaining -- cgit v1.2.3 From 461020919518ac0bde4024de482303734fd314dc Mon Sep 17 00:00:00 2001 From: Miqdad Date: Tue, 2 Sep 2025 14:51:29 +0700 Subject: Fix cancel invoice ccm --- indoteknik_custom/models/tukar_guling.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/tukar_guling.py b/indoteknik_custom/models/tukar_guling.py index d2e00781..6e839bf0 100644 --- a/indoteknik_custom/models/tukar_guling.py +++ b/indoteknik_custom/models/tukar_guling.py @@ -134,7 +134,7 @@ class TukarGuling(models.Model): if not self.val_inv_opt and self.is_has_invoice == True: raise UserError("Kalau sudah ada invoice Return Invoice Option harus diisi!") for rec in self: - if rec.val_inv_opt == 'cancel_invoice' and self.is_has_invoice == True: + if rec.val_inv_opt == 'cancel_invoice' and self.is_has_invoice == True and rec.invoice_id.state != 'cancel': raise UserError("Tidak bisa mengubah Return karena sudah ada invoice dan belum di cancel.") elif rec.val_inv_opt == 'tanpa_cancel' and self.is_has_invoice == True: continue -- cgit v1.2.3 From a35dcc5907500e5189516459160c972bb72e1686 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Tue, 2 Sep 2025 15:04:33 +0700 Subject: (andri) menambahkan field yang dibutuhkan --- indoteknik_custom/models/unpaid_invoice_view.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/unpaid_invoice_view.py b/indoteknik_custom/models/unpaid_invoice_view.py index f6ce79b8..f35261eb 100644 --- a/indoteknik_custom/models/unpaid_invoice_view.py +++ b/indoteknik_custom/models/unpaid_invoice_view.py @@ -20,11 +20,16 @@ class UnpaidInvoiceView(models.Model): amount_total = fields.Monetary(string='Total Amount', currency_field='currency_id') amount_residual = fields.Monetary(string='Sisa Amount', currency_field='currency_id') payment_state = fields.Selection([ - ('not_paid','Not Paid'), - ('in_payment','In Payment'), - ('paid','Paid'), - ('reversed','Reversed') - ], string='Payment State') + ('not_paid', 'Not Paid'), + ('in_payment', 'In Payment'), + ('paid', 'Paid'), + ('partial', 'Partially Paid'), + ('reversed', 'Reversed')], string='Payment State') payment_term_id = fields.Many2one('account.payment.term', string='Payment Term') invoice_day_to_due = fields.Integer(string="Day to Due") new_invoice_day_to_due = fields.Integer(string="New Day Due") + + ref = fields.Char(string='Reference') + invoice_user_id = fields.Many2one('res.users', string='Salesperson') + date_kirim_tukar_faktur = fields.Date(string='Kirim Faktur') + sale_id = fields.Many2one('sale.order', string='Sale Order') -- cgit v1.2.3 From d43eb7ff8ffd4b11120a7354295e86736135344a Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Tue, 2 Sep 2025 17:35:47 +0700 Subject: (andri) add menu surat piutang --- indoteknik_custom/models/__init__.py | 3 +- indoteknik_custom/models/letter_receivable.py | 90 +++++++++++++++++++++++++++ 2 files changed, 92 insertions(+), 1 deletion(-) create mode 100644 indoteknik_custom/models/letter_receivable.py (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/__init__.py b/indoteknik_custom/models/__init__.py index ba3dbad9..c8910669 100755 --- a/indoteknik_custom/models/__init__.py +++ b/indoteknik_custom/models/__init__.py @@ -157,4 +157,5 @@ from . import refund_sale_order from . import tukar_guling from . import tukar_guling_po from . import update_date_planned_po_wizard -from . import unpaid_invoice_view \ No newline at end of file +from . import unpaid_invoice_view +from . import letter_receivable \ No newline at end of file diff --git a/indoteknik_custom/models/letter_receivable.py b/indoteknik_custom/models/letter_receivable.py new file mode 100644 index 00000000..541f64ff --- /dev/null +++ b/indoteknik_custom/models/letter_receivable.py @@ -0,0 +1,90 @@ +from odoo import models, fields, api + +class SuratPiutang(models.Model): + _name = "surat.piutang" + _description = "Surat Piutang" + _inherit = ['mail.thread', 'mail.activity.mixin'] + + name = fields.Char(string="Nomor Surat", readonly=True, copy=False) + partner_id = fields.Many2one("res.partner", string="Customer", required=True) + tujuan_nama = fields.Char(string="Nama Tujuan") + tujuan_email = fields.Char(string="Email Tujuan") + perihal = fields.Selection([ + ('sp1', 'Surat Peringatan Piutang'), + ('sp2', 'Surat Peringatan Piutang ke-2'), + ('sp3', 'Surat Peringatan Piutang ke-3') + ], string="Perihal", required=True, tracking=True) + line_ids = fields.One2many("surat.piutang.line", "surat_id", string="Invoice Lines") + state = fields.Selection([ + ("draft", "Draft"), + ("sent", "Sent") + ], default="draft", tracking=True) + send_date = fields.Datetime(string="Tanggal Kirim", tracking=True) + + currency_id = fields.Many2one('res.currency') + + # Grand total (total sisa semua line yang dicentang) + grand_total = fields.Monetary( + string='Total Sisa', + currency_field='currency_id', + compute='_compute_grand_total', + ) + + @api.depends('line_ids.amount_residual', 'line_ids.selected') + def _compute_grand_total(self): + for rec in self: + rec.grand_total = sum( + line.amount_residual or 0.0 for line in rec.line_ids if line.selected + ) + + @api.onchange('partner_id') + def _onchange_partner_id(self): + if self.partner_id: + invoice_lines = self.env['unpaid.invoice.view'].search( + [('partner_id', '=', self.partner_id.id)], + order='new_invoice_day_to_due asc' + ) + lines = [(0, 0, { + 'invoice_view_id': inv.id, + 'invoice_id': inv.invoice_id.id, + 'invoice_number': inv.invoice_number, + 'invoice_date': inv.invoice_date, + 'invoice_date_due': inv.invoice_date_due, + 'invoice_day_to_due': inv.invoice_day_to_due, + 'ref': inv.ref, + 'amount_residual': inv.amount_residual, + 'currency_id': inv.currency_id.id, + 'payment_term_id': inv.payment_term_id.id, + 'selected': False + }) for inv in invoice_lines] + self.line_ids = lines + + @api.model + def create(self, vals): + # Generate nomor surat otomatis + if not vals.get("name"): + seq = self.env["ir.sequence"].next_by_code("surat.piutang") or "000" + today = fields.Date.today() + bulan_romawi = ["I","II","III","IV","V","VI","VII","VIII","IX","X","XI","XII"][today.month-1] + tahun = today.strftime("%y") + vals["name"] = f"{seq}/LO/FAT/IDG/{bulan_romawi}/{tahun}" + + return super().create(vals) + +class SuratPiutangLine(models.Model): + _name = 'surat.piutang.line' + _description = 'Surat Piutang Line' + + surat_id = fields.Many2one('surat.piutang', string='Surat Piutang', ondelete='cascade') + invoice_view_id = fields.Many2one('unpaid.invoice.view', string='Unpaid Invoice') + invoice_id = fields.Many2one('account.move', string='Invoice') + selected = fields.Boolean(string="Pilih", default=False) + + invoice_number = fields.Char(string='Invoice Number') + invoice_date = fields.Date(string='Invoice Date') + invoice_date_due = fields.Date(string='Due Date') + invoice_day_to_due = fields.Integer(string='Day to Due') + ref = fields.Char(string='Reference') + amount_residual = fields.Monetary(string='Amount Due Signed') + currency_id = fields.Many2one('res.currency') + payment_term_id = fields.Many2one('account.payment.term', string='Payment Terms') -- cgit v1.2.3 From 1c71710dbf42106a82c0a8e30ec9cee7f452a387 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Wed, 3 Sep 2025 11:07:40 +0700 Subject: (andri) add refresh invoices agar data selalu update & fix layout form --- indoteknik_custom/models/letter_receivable.py | 61 ++++++++++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/letter_receivable.py b/indoteknik_custom/models/letter_receivable.py index 541f64ff..10198fbf 100644 --- a/indoteknik_custom/models/letter_receivable.py +++ b/indoteknik_custom/models/letter_receivable.py @@ -4,19 +4,22 @@ class SuratPiutang(models.Model): _name = "surat.piutang" _description = "Surat Piutang" _inherit = ['mail.thread', 'mail.activity.mixin'] + _order = 'name desc' name = fields.Char(string="Nomor Surat", readonly=True, copy=False) partner_id = fields.Many2one("res.partner", string="Customer", required=True) tujuan_nama = fields.Char(string="Nama Tujuan") tujuan_email = fields.Char(string="Email Tujuan") perihal = fields.Selection([ - ('sp1', 'Surat Peringatan Piutang'), + ('penagihan', 'Surat Resmi Penagihan'), + ('sp1', 'Surat Peringatan Piutang ke-1'), ('sp2', 'Surat Peringatan Piutang ke-2'), ('sp3', 'Surat Peringatan Piutang ke-3') ], string="Perihal", required=True, tracking=True) line_ids = fields.One2many("surat.piutang.line", "surat_id", string="Invoice Lines") state = fields.Selection([ ("draft", "Draft"), + ("approval_pimpinan", "Menunggu Approval Pimpinan"), ("sent", "Sent") ], default="draft", tracking=True) send_date = fields.Datetime(string="Tanggal Kirim", tracking=True) @@ -59,6 +62,62 @@ class SuratPiutang(models.Model): }) for inv in invoice_lines] self.line_ids = lines + def action_refresh_lines(self): + for rec in self: + if not rec.partner_id: + continue + + # Ambil semua unpaid terbaru + invoice_views = self.env['unpaid.invoice.view'].search( + [('partner_id', '=', rec.partner_id.id)], + order='new_invoice_day_to_due asc' + ) + + existing_lines = {line.invoice_id.id: line for line in rec.line_ids} + + # Cache selected status per invoice id + selected_map = {line.invoice_id.id: line.selected for line in rec.line_ids} + + # Invoice id yang masih ada di unpaid + new_invoice_ids = invoice_views.mapped('invoice_id.id') + + for inv in invoice_views: + if inv.invoice_id.id in existing_lines: + # update line lama + line = existing_lines[inv.invoice_id.id] + line.write({ + 'invoice_view_id': inv.id, + 'invoice_number': inv.invoice_number, + 'invoice_date': inv.invoice_date, + 'invoice_date_due': inv.invoice_date_due, + 'invoice_day_to_due': inv.invoice_day_to_due, + 'ref': inv.ref, + 'amount_residual': inv.amount_residual, + 'currency_id': inv.currency_id.id, + 'payment_term_id': inv.payment_term_id.id, + 'selected': selected_map.get(inv.invoice_id.id, line.selected), + }) + else: + # preserve selected kalau pernah ada di cache + self.env['surat.piutang.line'].create({ + 'surat_id': rec.id, + 'invoice_view_id': inv.id, + 'invoice_id': inv.invoice_id.id, + 'invoice_number': inv.invoice_number, + 'invoice_date': inv.invoice_date, + 'invoice_date_due': inv.invoice_date_due, + 'invoice_day_to_due': inv.invoice_day_to_due, + 'ref': inv.ref, + 'amount_residual': inv.amount_residual, + 'currency_id': inv.currency_id.id, + 'payment_term_id': inv.payment_term_id.id, + 'selected': selected_map.get(inv.invoice_id.id, False), + }) + + # Hapus line yang tidak ada lagi di unpaid view + rec.line_ids.filtered(lambda l: l.invoice_id.id not in new_invoice_ids).unlink() + + @api.model def create(self, vals): # Generate nomor surat otomatis -- cgit v1.2.3 From 4aae32a042a5f77feebfa7e4f504f32a5375eaae Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Wed, 3 Sep 2025 13:52:09 +0700 Subject: (andri) add validasi email, terbilang, & approval pimpinan --- indoteknik_custom/models/letter_receivable.py | 39 ++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/letter_receivable.py b/indoteknik_custom/models/letter_receivable.py index 10198fbf..550aa9e3 100644 --- a/indoteknik_custom/models/letter_receivable.py +++ b/indoteknik_custom/models/letter_receivable.py @@ -1,4 +1,8 @@ -from odoo import models, fields, api +from odoo import models, fields, api, _ +from odoo.exceptions import UserError +from odoo.exceptions import ValidationError +from odoo.tools import mail +from terbilang import Terbilang class SuratPiutang(models.Model): _name = "surat.piutang" @@ -33,6 +37,23 @@ class SuratPiutang(models.Model): compute='_compute_grand_total', ) + grand_total_text = fields.Char( + string="Total Terbilang", + compute="_compute_grand_total_text", + ) + + def _compute_grand_total_text(self): + tb = Terbilang() + for record in self: + res = "" + if record.grand_total and record.grand_total > 0: + try: + tb.parse(int(record.grand_total)) + res = tb.getresult().title() + " Rupiah" + except Exception: + res = "" + record.grand_total_text = res + @api.depends('line_ids.amount_residual', 'line_ids.selected') def _compute_grand_total(self): for rec in self: @@ -40,6 +61,21 @@ class SuratPiutang(models.Model): line.amount_residual or 0.0 for line in rec.line_ids if line.selected ) + @api.constrains("tujuan_email") + def _check_email_format(self): + for rec in self: + if rec.tujuan_email and not mail.single_email_re.match(rec.tujuan_email): + raise ValidationError(_("Format email tidak valid: %s") % rec.tujuan_email) + + def action_approve(self): + pimpinan_user_ids = [7] # Pak Akbar + if self.env.user.id not in pimpinan_user_ids: + raise UserError("Hanya Pimpinan yang berhak menyetujui tahap ini.") + for rec in self: + if rec.state == "approval_pimpinan": + rec.state = "sent" + rec.send_date = fields.Datetime.now() + @api.onchange('partner_id') def _onchange_partner_id(self): if self.partner_id: @@ -128,6 +164,7 @@ class SuratPiutang(models.Model): tahun = today.strftime("%y") vals["name"] = f"{seq}/LO/FAT/IDG/{bulan_romawi}/{tahun}" + vals["state"] = "approval_pimpinan" return super().create(vals) class SuratPiutangLine(models.Model): -- cgit v1.2.3 From 1b389be56daecec87008f791e856205d2234e053 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Wed, 3 Sep 2025 17:53:25 +0700 Subject: (andri) add template mail & try testing --- indoteknik_custom/models/letter_receivable.py | 102 +++++++++++++++++++++++++- 1 file changed, 101 insertions(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/letter_receivable.py b/indoteknik_custom/models/letter_receivable.py index 550aa9e3..4159ecc2 100644 --- a/indoteknik_custom/models/letter_receivable.py +++ b/indoteknik_custom/models/letter_receivable.py @@ -1,8 +1,13 @@ from odoo import models, fields, api, _ from odoo.exceptions import UserError from odoo.exceptions import ValidationError -from odoo.tools import mail +from odoo.tools import mail, formatLang from terbilang import Terbilang +import re +import logging +from datetime import timedelta + +_logger = logging.getLogger(__name__) class SuratPiutang(models.Model): _name = "surat.piutang" @@ -27,6 +32,7 @@ class SuratPiutang(models.Model): ("sent", "Sent") ], default="draft", tracking=True) send_date = fields.Datetime(string="Tanggal Kirim", tracking=True) + seven_days_after_sent_date = fields.Char(string="7 Hari Setelah Tanggal Kirim", tracking=True) currency_id = fields.Many2one('res.currency') @@ -75,6 +81,100 @@ class SuratPiutang(models.Model): if rec.state == "approval_pimpinan": rec.state = "sent" rec.send_date = fields.Datetime.now() + # Format tanggal + bulan (tanpa tahun) + month_map = { + 1: "Januari", 2: "Februari", 3: "Maret", 4: "April", + 5: "Mei", 6: "Juni", 7: "Juli", 8: "Agustus", + 9: "September", 10: "Oktober", 11: "November", 12: "Desember", + } + if rec.send_date: + target_date = rec.send_date.date() + timedelta(days=7) + rec.seven_days_after_sent_date = f"{target_date.day} {month_map[target_date.month]}" + + def action_send_letter(self): + self.ensure_one() + + selected_lines = self.line_ids.filtered('selected') + if not selected_lines: + raise UserError(_("Tidak ada invoice yang dicentang untuk dikirim.")) + + if not self.tujuan_email: + raise UserError(_("Email tujuan harus diisi.")) + + template = self.env.ref('indoteknik_custom.letter_receivable_mail_template') + today = fields.Date.today() + + month_map = { + 1: "Januari", 2: "Februari", 3: "Maret", 4: "April", + 5: "Mei", 6: "Juni", 7: "Juli", 8: "Agustus", + 9: "September", 10: "Oktober", 11: "November", 12: "Desember", + } + target_date = (self.send_date or fields.Datetime.now()).date() + timedelta(days=7) + self.seven_days_after_sent_date = f"{target_date.day} {month_map[target_date.month]}" + + perihal_map = { + 'penagihan': 'Surat Resmi Penagihan', + 'sp1': 'Surat Peringatan Pertama (I)', + 'sp2': 'Surat Peringatan Kedua (II)', + 'sp3': 'Surat Peringatan Ketiga (III)', + } + perihal_text = perihal_map.get(self.perihal, self.perihal or '') + + invoice_table_rows = "" + grand_total = 0 + for line in selected_lines: + inv = line.invoice_id + if not inv: + continue + days_to_due = (line.invoice_date_due - today).days if line.invoice_date_due else 0 + grand_total += line.amount_residual + invoice_table_rows += f""" + + {line.invoice_number or '-'} + {self.partner_id.name or '-'} + {fields.Date.to_string(line.invoice_date) or '-'} + {fields.Date.to_string(line.invoice_date_due) or '-'} + {days_to_due} + {line.ref or '-'} + {formatLang(self.env, line.amount_residual, currency_obj=line.currency_id)} + {line.payment_term_id.name or '-'} + + """ + + invoice_table_footer = f""" + + + Grand Total + {formatLang(self.env, grand_total, currency_obj=self.currency_id)} + + + + """ + # inject table rows ke template + body_html = re.sub( + r"]*>.*?", + f"{invoice_table_rows}{invoice_table_footer}", + template.body_html, + flags=re.DOTALL + ).replace('${object.name}', self.name or '') \ + .replace('${object.partner_id.name}', self.partner_id.name or '') \ + .replace('${object.seven_days_after_sent_date}', self.seven_days_after_sent_date or '') \ + .replace('${object.perihal}', perihal_text or '') + + values = { + 'subject': template.subject.replace('${object.name}', self.name or ''), + 'email_to': self.tujuan_email, + 'email_from': 'finance@indoteknik.co.id', + 'body_html': body_html, + 'reply_to': 'finance@indoteknik.co.id', + } + + template.send_mail(self.id, force_send=True, email_values=values) + + _logger.info( + f"Surat Piutang {self.name} terkirim ke {self.tujuan_email} " + f"({self.partner_id.name}), total {len(selected_lines)} invoice." + ) @api.onchange('partner_id') def _onchange_partner_id(self): -- cgit v1.2.3 From d956020b6b588e5216f1238b82f87b6fb35b2afc Mon Sep 17 00:00:00 2001 From: Miqdad Date: Wed, 3 Sep 2025 18:05:00 +0700 Subject: send bu out to tele --- indoteknik_custom/models/report_logbook_sj.py | 83 +++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/report_logbook_sj.py b/indoteknik_custom/models/report_logbook_sj.py index 17119c12..85ab755d 100644 --- a/indoteknik_custom/models/report_logbook_sj.py +++ b/indoteknik_custom/models/report_logbook_sj.py @@ -2,6 +2,11 @@ from odoo import models, fields, api from odoo.exceptions import UserError from pytz import timezone from datetime import datetime +import requests +import json +import logging + +_logger = logging.getLogger(__name__) class ReportLogbookSJ(models.Model): _name = 'report.logbook.sj' @@ -60,6 +65,84 @@ class ReportLogbookSJ(models.Model): self.state = 'terima_semua' else: raise UserError('Hanya Accounting yang bisa Approve') + + + @api.model + def cron_daily_logbook_gap_to_telegram(self): + bot_mqdd = '8203414501:AAHy_XwiUAVrgRM2EJzW7sZx9npRLITZpb8' + chat_id_mqdd = '-4885333032' + apiURL = f'https://api.telegram.org/bot{bot_mqdd}/sendMessage' + + # ambil dari 1 Februari 2024 (UTC) + dt_from = datetime(2024, 2, 1, 0, 0, 0) + + self.env.cr.execute(""" + SELECT sp.id, sp.name + FROM stock_picking sp + LEFT JOIN report_logbook_sj_line rlsl + ON rlsl.picking_id = sp.id + OR (rlsl.name IS NOT NULL AND rlsl.name = sp.name) + WHERE sp.picking_type_id = 29 + AND sp.state = 'done' + AND sp.create_date >= %s + AND rlsl.id IS NULL + ORDER BY sp.create_date ASC limit 20 + """, (dt_from,)) + rows = self.env.cr.fetchall() + + if not rows: + return True + + header = "berikut merupakan nomor picking yang belum ada di Logbook SJ report:\n" + body = "\n".join(f"{name} ({pid})" for pid, name in rows if name) + text = header + body + + # kirim satu pesan (tanpa pemotongan) + try: + resp = requests.post(apiURL, json={'chat_id': chat_id_mqdd, 'text': text}, timeout=15) + resp.raise_for_status() + except requests.HTTPError: + _logger.error("Telegram response: %s", resp.text) # <— LIHAT DESKRIPSINYA DI LOG + raise + except Exception as e: + # log saja; biar cron tidak crash + logging.getLogger(__name__).exception("Gagal kirim Telegram: %s", e) + return True + + def action_send_to_telegram(self): + entries = [] + pickings = self.report_logbook_sj_line.mapped('picking_id') + for p in pickings: + if p: + entries.append((p.name, p.id)) + + fallback_names = [l.name for l in self.report_logbook_sj_line if not l.picking_id and l.name] + if fallback_names: + picks = self.env['stock.picking'].search([('name', 'in', list(set(fallback_names)))]) + name2id = {p.name: p.id for p in picks} + for n in fallback_names: + entries.append((n, name2id.get(n))) + + seen, unique_entries = set(), [] + for name, pid in entries: + key = pid or name + if key and key not in seen: + seen.add(key) + unique_entries.append((name, pid)) + + header = "berikut merupakan nomor picking yang belum ada di Logbook SJ report:\n" + body = "\n".join(f"{name} ({pid or '-'})" for name, pid in unique_entries) if unique_entries else "- (tidak ada)" + text = header + body + + bot_mqdd = '8203414501:AAHy_XwiUAVrgRM2EJzW7sZx9npRLITZpb8' + chat_id_mqdd = '-4885333032' + apiURL = f'https://api.telegram.org/bot{bot_mqdd}/sendMessage' + try: + hehe = requests.post(apiURL, json={'chat_id': chat_id_mqdd, 'text': text}) + _logger.info(hehe) + except Exception as e: + print(e) + class ReportLogbookSJLine(models.Model): _name = 'report.logbook.sj.line' -- cgit v1.2.3 From f6537c3987112c351a2a099475b1e32a9e8de76e Mon Sep 17 00:00:00 2001 From: Miqdad Date: Thu, 4 Sep 2025 09:39:08 +0700 Subject: Fix singleton mathces SO --- indoteknik_custom/models/purchase_order_sales_match.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/purchase_order_sales_match.py b/indoteknik_custom/models/purchase_order_sales_match.py index b18864f3..084b93f7 100644 --- a/indoteknik_custom/models/purchase_order_sales_match.py +++ b/indoteknik_custom/models/purchase_order_sales_match.py @@ -39,7 +39,7 @@ class PurchaseOrderSalesMatch(models.Model): ('sale_line_id', '=', rec.sale_line_id.id), ]) if stock_move: - rec.bu_pick = stock_move.picking_id.id + rec.bu_pick = stock_move[0].picking_id.id else: rec.bu_pick = None -- cgit v1.2.3 From 14d3ad7bbb8a6bd9c1272685aed4b3712ab1f33d Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Thu, 4 Sep 2025 11:14:27 +0700 Subject: (andri) fix reminder --- indoteknik_custom/models/account_move.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/account_move.py b/indoteknik_custom/models/account_move.py index c44cad78..c93cfb76 100644 --- a/indoteknik_custom/models/account_move.py +++ b/indoteknik_custom/models/account_move.py @@ -179,9 +179,8 @@ class AccountMove(models.Model): ('state', '=', 'posted'), ('payment_state', 'not in', ['paid', 'in_payment', 'reversed']), ('invoice_date_due', 'in', target_dates), - ('date_terima_tukar_faktur', '!=', False), - ('partner_id', 'in' , [94603]) - ], limit=5) + ('date_terima_tukar_faktur', '!=', False) + ]) _logger.info(f"Invoices: {invoices}") invoices = invoices.filtered( -- cgit v1.2.3 From 4da5af474edea6a61453dcc8485cefaa7fe6dd42 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Thu, 4 Sep 2025 15:26:48 +0700 Subject: Done x --- indoteknik_custom/models/__init__.py | 3 +- indoteknik_custom/models/report_logbook_sj.py | 51 ++++------------------- indoteknik_custom/models/sj_tele.py | 59 +++++++++++++++++++++++++++ 3 files changed, 69 insertions(+), 44 deletions(-) create mode 100644 indoteknik_custom/models/sj_tele.py (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/__init__.py b/indoteknik_custom/models/__init__.py index 3a9f9312..c0aa7085 100755 --- a/indoteknik_custom/models/__init__.py +++ b/indoteknik_custom/models/__init__.py @@ -156,4 +156,5 @@ from . import refund_sale_order # from . import patch from . import tukar_guling from . import tukar_guling_po -from . import update_date_planned_po_wizard \ No newline at end of file +from . import update_date_planned_po_wizard +from . import sj_tele \ No newline at end of file diff --git a/indoteknik_custom/models/report_logbook_sj.py b/indoteknik_custom/models/report_logbook_sj.py index 85ab755d..6915ad9b 100644 --- a/indoteknik_custom/models/report_logbook_sj.py +++ b/indoteknik_custom/models/report_logbook_sj.py @@ -39,6 +39,10 @@ class ReportLogbookSJ(models.Model): count_line = fields.Char(string='Count Line', compute='_compute_count_line') + def write(self, vals): + self.action_send_to_telegram() + res = super(ReportLogbookSJ, self).write(vals) + @api.depends('report_logbook_sj_line') def _compute_count_line(self): for rec in self: @@ -67,49 +71,10 @@ class ReportLogbookSJ(models.Model): raise UserError('Hanya Accounting yang bisa Approve') - @api.model - def cron_daily_logbook_gap_to_telegram(self): - bot_mqdd = '8203414501:AAHy_XwiUAVrgRM2EJzW7sZx9npRLITZpb8' - chat_id_mqdd = '-4885333032' - apiURL = f'https://api.telegram.org/bot{bot_mqdd}/sendMessage' - - # ambil dari 1 Februari 2024 (UTC) - dt_from = datetime(2024, 2, 1, 0, 0, 0) - - self.env.cr.execute(""" - SELECT sp.id, sp.name - FROM stock_picking sp - LEFT JOIN report_logbook_sj_line rlsl - ON rlsl.picking_id = sp.id - OR (rlsl.name IS NOT NULL AND rlsl.name = sp.name) - WHERE sp.picking_type_id = 29 - AND sp.state = 'done' - AND sp.create_date >= %s - AND rlsl.id IS NULL - ORDER BY sp.create_date ASC limit 20 - """, (dt_from,)) - rows = self.env.cr.fetchall() - - if not rows: - return True - - header = "berikut merupakan nomor picking yang belum ada di Logbook SJ report:\n" - body = "\n".join(f"{name} ({pid})" for pid, name in rows if name) - text = header + body - - # kirim satu pesan (tanpa pemotongan) - try: - resp = requests.post(apiURL, json={'chat_id': chat_id_mqdd, 'text': text}, timeout=15) - resp.raise_for_status() - except requests.HTTPError: - _logger.error("Telegram response: %s", resp.text) # <— LIHAT DESKRIPSINYA DI LOG - raise - except Exception as e: - # log saja; biar cron tidak crash - logging.getLogger(__name__).exception("Gagal kirim Telegram: %s", e) - return True - def action_send_to_telegram(self): + user_logistic = self.env.ref('indoteknik_custom.group_role_logistic') + if self.user != user_logistic: + return entries = [] pickings = self.report_logbook_sj_line.mapped('picking_id') for p in pickings: @@ -130,7 +95,7 @@ class ReportLogbookSJ(models.Model): seen.add(key) unique_entries.append((name, pid)) - header = "berikut merupakan nomor picking yang belum ada di Logbook SJ report:\n" + header = f"Saya {self.env.user.name} sudah mengisi di Logbook SJ Report:\n" body = "\n".join(f"{name} ({pid or '-'})" for name, pid in unique_entries) if unique_entries else "- (tidak ada)" text = header + body diff --git a/indoteknik_custom/models/sj_tele.py b/indoteknik_custom/models/sj_tele.py new file mode 100644 index 00000000..c359d6d8 --- /dev/null +++ b/indoteknik_custom/models/sj_tele.py @@ -0,0 +1,59 @@ +from odoo import models, fields, api +import requests +import json +import logging + +_logger = logging.getLogger(__name__) + +class SjTele(models.Model): + _name = 'sj.tele' + _description = 'sj.tele' + + + picking_id = fields.Many2one('stock.picking', string='Picking') + picking_name = fields.Char(string='Picking Name') + create_date = fields.Datetime(string='Create Date') + + @api.model + def woi(self): + bot_mqdd = '8203414501:AAHy_XwiUAVrgRM2EJzW7sZx9npRLITZpb8' + chat_id_mqdd = '-4885333032' + apiURL = f'https://api.telegram.org/bot{bot_mqdd}/sendMessage' + + self.env.cr.execute(""" + SELECT + COALESCE(sp.id, st.picking_id) AS pid, + COALESCE(sp.name, st.picking_name) AS pname + FROM sj_tele st + LEFT JOIN stock_picking sp + ON sp.id = st.picking_id + LEFT JOIN report_logbook_sj_line rlsl + ON (rlsl.picking_id = COALESCE(sp.id, st.picking_id)) + OR (rlsl.name IS NOT NULL AND rlsl.name = COALESCE(sp.name, st.picking_name)) + WHERE rlsl.id IS NULL + AND COALESCE(sp.name, st.picking_name) IS NOT NULL + ORDER BY st.create_date ASC + LIMIT 20 + """) + rows = self.env.cr.fetchall() + + if not rows: + _logger.info("SJ Tele: tidak ada data untuk dikirim (staging kosong atau semua sudah di logbook).") + text = "Selamat anda menamatkan Logbook SJ Report" + try: + resp = requests.post(apiURL, json={'chat_id': chat_id_mqdd, 'text': text}, timeout=15) + except Exception as e: + logging.getLogger(__name__).exception("Gagal kirim Telegram: %s", e) + return True + + header = "Berikut merupakan nomor BU/OUT yang belum ada di Logbook SJ report:\n" + body = "\n".join(f"{name} ({pid})" for pid, name in rows if name) + text = header + body + try: + resp = requests.post(apiURL, json={'chat_id': chat_id_mqdd, 'text': text}, timeout=15) + except requests.HTTPError: + _logger.error("Telegram response: %s", resp.text) + raise + except Exception as e: + logging.getLogger(__name__).exception("Gagal kirim Telegram: %s", e) + return True \ No newline at end of file -- cgit v1.2.3 From dd2bc67bbdaa771adf6bbedc01ba23a98ea03574 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Thu, 4 Sep 2025 15:46:05 +0700 Subject: Gk jadi --- indoteknik_custom/models/report_logbook_sj.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/report_logbook_sj.py b/indoteknik_custom/models/report_logbook_sj.py index 6915ad9b..e67ea724 100644 --- a/indoteknik_custom/models/report_logbook_sj.py +++ b/indoteknik_custom/models/report_logbook_sj.py @@ -39,10 +39,6 @@ class ReportLogbookSJ(models.Model): count_line = fields.Char(string='Count Line', compute='_compute_count_line') - def write(self, vals): - self.action_send_to_telegram() - res = super(ReportLogbookSJ, self).write(vals) - @api.depends('report_logbook_sj_line') def _compute_count_line(self): for rec in self: @@ -51,6 +47,8 @@ class ReportLogbookSJ(models.Model): @api.model def create(self, vals): vals['name'] = self.env['ir.sequence'].next_by_code('report.logbook.sj') or '0' + # if self.env.user.has_group('indoteknik_custom.group_role_logistic'): + # self.action_send_to_telegram() result = super(ReportLogbookSJ, self).create(vals) return result @@ -72,9 +70,7 @@ class ReportLogbookSJ(models.Model): def action_send_to_telegram(self): - user_logistic = self.env.ref('indoteknik_custom.group_role_logistic') - if self.user != user_logistic: - return + entries = [] pickings = self.report_logbook_sj_line.mapped('picking_id') for p in pickings: @@ -95,7 +91,7 @@ class ReportLogbookSJ(models.Model): seen.add(key) unique_entries.append((name, pid)) - header = f"Saya {self.env.user.name} sudah mengisi di Logbook SJ Report:\n" + header = f"{self.env.user.name} sudah mengisi di Logbook SJ Report:\n" body = "\n".join(f"{name} ({pid or '-'})" for name, pid in unique_entries) if unique_entries else "- (tidak ada)" text = header + body -- cgit v1.2.3 From 666232252b4f5c5626bb5f276f645d4989495fa1 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Thu, 4 Sep 2025 18:38:12 +0700 Subject: Add SO --- indoteknik_custom/models/sj_tele.py | 60 +++++++++++++++++-------------------- 1 file changed, 27 insertions(+), 33 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sj_tele.py b/indoteknik_custom/models/sj_tele.py index c359d6d8..50e47e78 100644 --- a/indoteknik_custom/models/sj_tele.py +++ b/indoteknik_custom/models/sj_tele.py @@ -11,49 +11,43 @@ class SjTele(models.Model): picking_id = fields.Many2one('stock.picking', string='Picking') + sale_id = fields.Many2one('sale.order', string='Sales Order') picking_name = fields.Char(string='Picking Name') create_date = fields.Datetime(string='Create Date') - @api.model - def woi(self): + def woi(self): bot_mqdd = '8203414501:AAHy_XwiUAVrgRM2EJzW7sZx9npRLITZpb8' chat_id_mqdd = '-4885333032' - apiURL = f'https://api.telegram.org/bot{bot_mqdd}/sendMessage' - - self.env.cr.execute(""" - SELECT - COALESCE(sp.id, st.picking_id) AS pid, - COALESCE(sp.name, st.picking_name) AS pname - FROM sj_tele st - LEFT JOIN stock_picking sp - ON sp.id = st.picking_id - LEFT JOIN report_logbook_sj_line rlsl - ON (rlsl.picking_id = COALESCE(sp.id, st.picking_id)) - OR (rlsl.name IS NOT NULL AND rlsl.name = COALESCE(sp.name, st.picking_name)) - WHERE rlsl.id IS NULL - AND COALESCE(sp.name, st.picking_name) IS NOT NULL - ORDER BY st.create_date ASC - LIMIT 20 - """) - rows = self.env.cr.fetchall() - - if not rows: - _logger.info("SJ Tele: tidak ada data untuk dikirim (staging kosong atau semua sudah di logbook).") - text = "Selamat anda menamatkan Logbook SJ Report" + api_base = f'https://api.telegram.org/bot{bot_mqdd}' + + data = self.search([], order='create_date asc', limit=15) + + if not data: + text = "Berikut merupakan nomor BU/OUT yang belum ada di Logbook SJ report:\n✅ tidak ada data (semua sudah tercatat)." try: - resp = requests.post(apiURL, json={'chat_id': chat_id_mqdd, 'text': text}, timeout=15) + r = requests.post(api_base + "/sendMessage", + json={'chat_id': chat_id_mqdd, 'text': text}, + timeout=20) + r.raise_for_status() except Exception as e: - logging.getLogger(__name__).exception("Gagal kirim Telegram: %s", e) + _logger.exception("Gagal kirim Telegram (no data): %s", e) return True + lines = [] + for rec in data: + name = rec.picking_name or (rec.picking_id.name if rec.picking_id else '') + pid = rec.picking_id.id if rec.picking_id else '-' + so = rec.sale_id.name if rec.sale_id else '-' + if name: + lines.append(f"{name} - {so} ({pid})") + header = "Berikut merupakan nomor BU/OUT yang belum ada di Logbook SJ report:\n" - body = "\n".join(f"{name} ({pid})" for pid, name in rows if name) - text = header + body + text = header + "\n".join(lines) + try: - resp = requests.post(apiURL, json={'chat_id': chat_id_mqdd, 'text': text}, timeout=15) - except requests.HTTPError: - _logger.error("Telegram response: %s", resp.text) - raise + r = requests.post(api_base + "/sendMessage", + json={'chat_id': chat_id_mqdd, 'text': text}) + r.raise_for_status() except Exception as e: - logging.getLogger(__name__).exception("Gagal kirim Telegram: %s", e) + _logger.exception("Gagal kirim Telegram: %s", e) return True \ No newline at end of file -- cgit v1.2.3 From cae2e6978066469505167da9469519ea2de14cab Mon Sep 17 00:00:00 2001 From: Miqdad Date: Fri, 5 Sep 2025 07:27:44 +0700 Subject: oke --- indoteknik_custom/models/sj_tele.py | 1 + 1 file changed, 1 insertion(+) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sj_tele.py b/indoteknik_custom/models/sj_tele.py index 50e47e78..9559a541 100644 --- a/indoteknik_custom/models/sj_tele.py +++ b/indoteknik_custom/models/sj_tele.py @@ -13,6 +13,7 @@ class SjTele(models.Model): picking_id = fields.Many2one('stock.picking', string='Picking') sale_id = fields.Many2one('sale.order', string='Sales Order') picking_name = fields.Char(string='Picking Name') + sale_name = fields.Char(string='Sale Name') create_date = fields.Datetime(string='Create Date') def woi(self): -- cgit v1.2.3 From ba8b646073ee09b962d58b24b340f47d18aa611f Mon Sep 17 00:00:00 2001 From: IT Fixcomart Date: Fri, 5 Sep 2025 00:54:20 +0000 Subject: sj_tele.py edited online with Bitbucket --- indoteknik_custom/models/sj_tele.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sj_tele.py b/indoteknik_custom/models/sj_tele.py index 9559a541..246c0f43 100644 --- a/indoteknik_custom/models/sj_tele.py +++ b/indoteknik_custom/models/sj_tele.py @@ -37,8 +37,8 @@ class SjTele(models.Model): lines = [] for rec in data: name = rec.picking_name or (rec.picking_id.name if rec.picking_id else '') - pid = rec.picking_id.id if rec.picking_id else '-' - so = rec.sale_id.name if rec.sale_id else '-' + pid = rec.picking_id.id if rec.picking_id else '' + so = rec.sale_id.name or rec.sale_name or '' if name: lines.append(f"{name} - {so} ({pid})") -- cgit v1.2.3 From 0a1f22cac507ee7e9975f1a68f79bccdf179c9fd Mon Sep 17 00:00:00 2001 From: Miqdad Date: Fri, 5 Sep 2025 09:07:23 +0700 Subject: add image --- indoteknik_custom/models/stock_move.py | 1 + 1 file changed, 1 insertion(+) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_move.py b/indoteknik_custom/models/stock_move.py index 90ab30a4..d4106830 100644 --- a/indoteknik_custom/models/stock_move.py +++ b/indoteknik_custom/models/stock_move.py @@ -15,6 +15,7 @@ class StockMove(models.Model): barcode = fields.Char(string='Barcode', related='product_id.barcode') vendor_id = fields.Many2one('res.partner' ,string='Vendor') hold_outgoingg = fields.Boolean('Hold Outgoing', default=False) + product_image_128 = fields.Image('Product Image', related='product_id.image_1920') # @api.model_create_multi # def create(self, vals_list): -- cgit v1.2.3 From bd7479d645e90509c72a36b2dd9bcf9e6964bcae Mon Sep 17 00:00:00 2001 From: Miqdad Date: Fri, 5 Sep 2025 21:22:00 +0700 Subject: change field name --- indoteknik_custom/models/stock_move.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_move.py b/indoteknik_custom/models/stock_move.py index d4106830..9ec36fcd 100644 --- a/indoteknik_custom/models/stock_move.py +++ b/indoteknik_custom/models/stock_move.py @@ -1,6 +1,9 @@ from odoo import fields, models, api from odoo.tools.misc import format_date, OrderedSet from odoo.exceptions import UserError +import logging + +_logger = logging.getLogger(__name__) class StockMove(models.Model): _inherit = 'stock.move' @@ -15,7 +18,7 @@ class StockMove(models.Model): barcode = fields.Char(string='Barcode', related='product_id.barcode') vendor_id = fields.Many2one('res.partner' ,string='Vendor') hold_outgoingg = fields.Boolean('Hold Outgoing', default=False) - product_image_128 = fields.Image('Product Image', related='product_id.image_1920') + product_image= fields.Image('Product Image', related='product_id.image_1920') # @api.model_create_multi # def create(self, vals_list): -- cgit v1.2.3 From f82137850801cb8d4c8cb482f607b68181ef1cb6 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Sat, 6 Sep 2025 08:33:49 +0700 Subject: carte --- indoteknik_custom/models/sj_tele.py | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sj_tele.py b/indoteknik_custom/models/sj_tele.py index 246c0f43..c8f7c0c6 100644 --- a/indoteknik_custom/models/sj_tele.py +++ b/indoteknik_custom/models/sj_tele.py @@ -1,7 +1,8 @@ from odoo import models, fields, api +from odoo.exceptions import UserError import requests import json -import logging +import logging, subprocess _logger = logging.getLogger(__name__) @@ -16,6 +17,30 @@ class SjTele(models.Model): sale_name = fields.Char(string='Sale Name') create_date = fields.Datetime(string='Create Date') + @api.model + def run_pentaho_carte(self): + carte = "http://127.0.0.1:8080" + job_kjb = r"C:/Users/ThinkPad/Desktop/tes.kjb" + params = {"job": job_kjb, "level": "Basic", "block": "Y"} + try: + r = requests.get( + f"{carte}/kettle/executeJob/", + params=params, + auth=("cluster", "cluster"), + timeout=900, + ) + r.raise_for_status() + # kalau Carte mengembalikan ERROR, anggap gagal + if "ERROR" in r.text: + raise UserError(f"Carte error: {r.text}") + except Exception as e: + _logger.exception("Carte call failed: %s", e) + raise UserError(f"Gagal memanggil Carte: {e}") + + self.env['sj.tele'].sudo().woi() + + return True + def woi(self): bot_mqdd = '8203414501:AAHy_XwiUAVrgRM2EJzW7sZx9npRLITZpb8' chat_id_mqdd = '-4885333032' -- cgit v1.2.3 From 4bd34e21d8d4dc3b2f688e181fb05c6359ac1f43 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Sat, 6 Sep 2025 11:29:08 +0700 Subject: fix manufacture in stock move line & done --- indoteknik_custom/models/stock_move.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_move.py b/indoteknik_custom/models/stock_move.py index 9ec36fcd..24d405a6 100644 --- a/indoteknik_custom/models/stock_move.py +++ b/indoteknik_custom/models/stock_move.py @@ -18,8 +18,7 @@ class StockMove(models.Model): barcode = fields.Char(string='Barcode', related='product_id.barcode') vendor_id = fields.Many2one('res.partner' ,string='Vendor') hold_outgoingg = fields.Boolean('Hold Outgoing', default=False) - product_image= fields.Image('Product Image', related='product_id.image_1920') - + product_image = fields.Binary(related="product_id.image_128", string="Product Image", readonly = True) # @api.model_create_multi # def create(self, vals_list): # moves = super(StockMove, self).create(vals_list) -- cgit v1.2.3 From ea6364616f5a6ecbcf933249aeb95a7f3c8b4555 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Sat, 6 Sep 2025 11:33:02 +0700 Subject: Comment --- indoteknik_custom/models/sj_tele.py | 45 ++++++++++++++++++++----------------- 1 file changed, 24 insertions(+), 21 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sj_tele.py b/indoteknik_custom/models/sj_tele.py index c8f7c0c6..c00279ec 100644 --- a/indoteknik_custom/models/sj_tele.py +++ b/indoteknik_custom/models/sj_tele.py @@ -3,6 +3,7 @@ from odoo.exceptions import UserError import requests import json import logging, subprocess +import time _logger = logging.getLogger(__name__) @@ -17,29 +18,31 @@ class SjTele(models.Model): sale_name = fields.Char(string='Sale Name') create_date = fields.Datetime(string='Create Date') - @api.model - def run_pentaho_carte(self): - carte = "http://127.0.0.1:8080" - job_kjb = r"C:/Users/ThinkPad/Desktop/tes.kjb" - params = {"job": job_kjb, "level": "Basic", "block": "Y"} - try: - r = requests.get( - f"{carte}/kettle/executeJob/", - params=params, - auth=("cluster", "cluster"), - timeout=900, - ) - r.raise_for_status() - # kalau Carte mengembalikan ERROR, anggap gagal - if "ERROR" in r.text: - raise UserError(f"Carte error: {r.text}") - except Exception as e: - _logger.exception("Carte call failed: %s", e) - raise UserError(f"Gagal memanggil Carte: {e}") + # @api.model + # def run_pentaho_carte(self): + # carte = "http://127.0.0.1:8080" + # job_kjb = r"C:/Users/Indoteknik/Desktop/tes.kjb" + # params = {"job": job_kjb, "level": "Basic", "block": "Y"} + # try: + # r = requests.get( + # f"{carte}/kettle/executeJob/", + # params=params, + # auth=("cluster", "cluster"), + # timeout=900, + # ) + # r.raise_for_status() + # # kalau Carte mengembalikan ERROR, anggap gagal + # if "ERROR" in r.text: + # raise UserError(f"Carte error: {r.text}") + # except Exception as e: + # _logger.exception("Carte call failed: %s", e) + # raise UserError(f"Gagal memanggil Carte: {e}") + + # time.sleep(3) - self.env['sj.tele'].sudo().woi() + # self.env['sj.tele'].sudo().woi() - return True + # return True def woi(self): bot_mqdd = '8203414501:AAHy_XwiUAVrgRM2EJzW7sZx9npRLITZpb8' -- cgit v1.2.3 From 4cdb3a7b89d5e93449fadafec015e80dad1cf47f Mon Sep 17 00:00:00 2001 From: AndriFP Date: Sat, 6 Sep 2025 14:04:27 +0700 Subject: (andri) add validasi di SO dan respartner --- indoteknik_custom/models/res_partner.py | 16 +++++++++------- indoteknik_custom/models/sale_order.py | 17 +++++++++++++++++ 2 files changed, 26 insertions(+), 7 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/res_partner.py b/indoteknik_custom/models/res_partner.py index 80fe643b..b5ce9266 100644 --- a/indoteknik_custom/models/res_partner.py +++ b/indoteknik_custom/models/res_partner.py @@ -183,13 +183,6 @@ class ResPartner(models.Model): is_cbd_locked = fields.Boolean("Locked to CBD?", default=False, tracking=True, help="Jika dicentang, maka partner ini terkunci pada payment term CBD karena memiliki invoice yang sudah jatuh tempo lebih dari 30 hari.") - # centang manual is_cbd_locked jika payment term diubah ke CBD - # @api.onchange('is_cbd_locked') - # def _onchange_is_cbd_locked(self): - # if self.is_cbd_locked: - # cbd_term = self.env['account.payment.term'].browse(26) - # if cbd_term: - # self.property_payment_term_id = cbd_term.id @api.model def _default_payment_term(self): @@ -201,6 +194,15 @@ class ResPartner(models.Model): default=_default_payment_term, tracking=3 ) + @api.constrains('property_payment_term_id', 'is_cbd_locked') + def _check_cbd_lock_partner(self): + cbd_term = self.env['account.payment.term'].browse(26) + for rec in self: + if rec.is_cbd_locked and rec.property_payment_term_id and rec.property_payment_term_id != cbd_term: + raise ValidationError( + "Partner ini terkunci ke CBD, hanya boleh pakai Payment Term CBD." + ) + @api.depends("street", "street2", "city", "state_id", "country_id", "blok", "nomor", "rt", "rw", "kelurahan_id", "kecamatan_id") def _alamat_lengkap_text(self): diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 992c1a5d..8a595d8e 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -404,6 +404,23 @@ class SaleOrder(models.Model): for order in self: order.partner_is_cbd_locked = order.partner_id.is_cbd_locked + @api.onchange('payment_term_id') + def _onchange_partner_payment_term(self): + cbd_term = self.env['account.payment.term'].browse(26) + for rec in self: + if rec.partner_id and rec.partner_id.is_cbd_locked and cbd_term: + rec.payment_term_id = cbd_term + + @api.constrains('payment_term_id', 'partner_id', 'state') + def _check_cbd_lock_sale_order(self): + cbd_term = self.env['account.payment.term'].browse(26) + for rec in self: + if rec.state == 'draft' and rec.partner_id.is_cbd_locked: + if rec.payment_term_id and rec.payment_term_id != cbd_term: + raise ValidationError( + "Customer ini terkunci ke CBD, hanya boleh pakai Payment Term CBD." + ) + @api.depends('invoice_ids.payment_state', 'invoice_ids.amount_total', 'invoice_ids.amount_residual') def _compute_payment_state_custom(self): for order in self: -- cgit v1.2.3 From 943394bf2a90931134317b88615f23900664dcf0 Mon Sep 17 00:00:00 2001 From: AndriFP Date: Sun, 7 Sep 2025 20:18:18 +0700 Subject: (andri) fix --- indoteknik_custom/models/letter_receivable.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/letter_receivable.py b/indoteknik_custom/models/letter_receivable.py index 4159ecc2..5a793873 100644 --- a/indoteknik_custom/models/letter_receivable.py +++ b/indoteknik_custom/models/letter_receivable.py @@ -16,9 +16,9 @@ class SuratPiutang(models.Model): _order = 'name desc' name = fields.Char(string="Nomor Surat", readonly=True, copy=False) - partner_id = fields.Many2one("res.partner", string="Customer", required=True) - tujuan_nama = fields.Char(string="Nama Tujuan") - tujuan_email = fields.Char(string="Email Tujuan") + partner_id = fields.Many2one("res.partner", string="Customer", required=True, tracking=True) + tujuan_nama = fields.Char(string="Nama Tujuan", tracking=True) + tujuan_email = fields.Char(string="Email Tujuan", tracking=True) perihal = fields.Selection([ ('penagihan', 'Surat Resmi Penagihan'), ('sp1', 'Surat Peringatan Piutang ke-1'), @@ -123,9 +123,6 @@ class SuratPiutang(models.Model): invoice_table_rows = "" grand_total = 0 for line in selected_lines: - inv = line.invoice_id - if not inv: - continue days_to_due = (line.invoice_date_due - today).days if line.invoice_date_due else 0 grand_total += line.amount_residual invoice_table_rows += f""" @@ -279,6 +276,7 @@ class SuratPiutangLine(models.Model): invoice_number = fields.Char(string='Invoice Number') invoice_date = fields.Date(string='Invoice Date') invoice_date_due = fields.Date(string='Due Date') + invoice_new_day_to_due = fields.Integer(string='New Day to Due') invoice_day_to_due = fields.Integer(string='Day to Due') ref = fields.Char(string='Reference') amount_residual = fields.Monetary(string='Amount Due Signed') -- cgit v1.2.3 From 15b5f549777065dac76eaaa23598f09ae6a22f8f Mon Sep 17 00:00:00 2001 From: AndriFP Date: Mon, 8 Sep 2025 08:54:28 +0700 Subject: (andri) fix --- indoteknik_custom/models/letter_receivable.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/letter_receivable.py b/indoteknik_custom/models/letter_receivable.py index 5a793873..d4921bc5 100644 --- a/indoteknik_custom/models/letter_receivable.py +++ b/indoteknik_custom/models/letter_receivable.py @@ -187,6 +187,7 @@ class SuratPiutang(models.Model): 'invoice_date': inv.invoice_date, 'invoice_date_due': inv.invoice_date_due, 'invoice_day_to_due': inv.invoice_day_to_due, + 'new_invoice_day_to_due': inv.invoice_day_to_due, 'ref': inv.ref, 'amount_residual': inv.amount_residual, 'currency_id': inv.currency_id.id, @@ -224,6 +225,7 @@ class SuratPiutang(models.Model): 'invoice_date': inv.invoice_date, 'invoice_date_due': inv.invoice_date_due, 'invoice_day_to_due': inv.invoice_day_to_due, + 'new_invoice_day_to_due': inv.invoice_day_to_due, 'ref': inv.ref, 'amount_residual': inv.amount_residual, 'currency_id': inv.currency_id.id, @@ -240,6 +242,7 @@ class SuratPiutang(models.Model): 'invoice_date': inv.invoice_date, 'invoice_date_due': inv.invoice_date_due, 'invoice_day_to_due': inv.invoice_day_to_due, + 'new_invoice_day_to_due': inv.invoice_day_to_due, 'ref': inv.ref, 'amount_residual': inv.amount_residual, 'currency_id': inv.currency_id.id, @@ -276,8 +279,8 @@ class SuratPiutangLine(models.Model): invoice_number = fields.Char(string='Invoice Number') invoice_date = fields.Date(string='Invoice Date') invoice_date_due = fields.Date(string='Due Date') - invoice_new_day_to_due = fields.Integer(string='New Day to Due') invoice_day_to_due = fields.Integer(string='Day to Due') + new_invoice_day_to_due = fields.Integer(string='New Day to Due') ref = fields.Char(string='Reference') amount_residual = fields.Monetary(string='Amount Due Signed') currency_id = fields.Many2one('res.currency') -- cgit v1.2.3 From 2b14a2678e3e2782c362065032cfcaba5b091b88 Mon Sep 17 00:00:00 2001 From: AndriFP Date: Mon, 8 Sep 2025 11:13:21 +0700 Subject: (andri) add compute char periode invoices --- indoteknik_custom/models/letter_receivable.py | 64 ++++++++++++++++++++++++--- 1 file changed, 57 insertions(+), 7 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/letter_receivable.py b/indoteknik_custom/models/letter_receivable.py index d4921bc5..caf62a64 100644 --- a/indoteknik_custom/models/letter_receivable.py +++ b/indoteknik_custom/models/letter_receivable.py @@ -6,6 +6,7 @@ from terbilang import Terbilang import re import logging from datetime import timedelta +import babel _logger = logging.getLogger(__name__) @@ -33,6 +34,10 @@ class SuratPiutang(models.Model): ], default="draft", tracking=True) send_date = fields.Datetime(string="Tanggal Kirim", tracking=True) seven_days_after_sent_date = fields.Char(string="7 Hari Setelah Tanggal Kirim", tracking=True) + periode_invoices_terpilih = fields.Char( + string="Periode Invoices Terpilih", + compute="_compute_periode_invoices", + ) currency_id = fields.Many2one('res.currency') @@ -48,6 +53,34 @@ class SuratPiutang(models.Model): compute="_compute_grand_total_text", ) + @api.depends("line_ids.selected", "line_ids.invoice_date") + def _compute_periode_invoices(self): + for rec in self: + selected_lines = rec.line_ids.filtered(lambda l: l.selected and l.invoice_date) + if not selected_lines: + rec.periode_invoices_terpilih = "-" + continue + + dates = selected_lines.mapped("invoice_date") + min_date, max_date = min(dates), max(dates) + + # Ambil bagian bulan & tahun + min_month = babel.dates.format_date(min_date, "MMMM", locale="id_ID") + min_year = min_date.year + max_month = babel.dates.format_date(max_date, "MMMM", locale="id_ID") + max_year = max_date.year + + if min_year == max_year: + if min_month == max_month: + # example: Januari 2025 + rec.periode_invoices_terpilih = f"{min_month} {min_year}" + else: + # example: Mei s/d Juni 2025 + rec.periode_invoices_terpilih = f"{min_month} s/d {max_month} {max_year}" + else: + # example: Desember 2024 s/d Januari 2025 + rec.periode_invoices_terpilih = f"{min_month} {min_year} s/d {max_month} {max_year}" + def _compute_grand_total_text(self): tb = Terbilang() for record in self: @@ -181,17 +214,21 @@ class SuratPiutang(models.Model): order='new_invoice_day_to_due asc' ) lines = [(0, 0, { - 'invoice_view_id': inv.id, + # 'invoice_view_id': inv.id, 'invoice_id': inv.invoice_id.id, 'invoice_number': inv.invoice_number, 'invoice_date': inv.invoice_date, 'invoice_date_due': inv.invoice_date_due, 'invoice_day_to_due': inv.invoice_day_to_due, - 'new_invoice_day_to_due': inv.invoice_day_to_due, + 'new_invoice_day_to_due': inv.new_invoice_day_to_due, 'ref': inv.ref, 'amount_residual': inv.amount_residual, 'currency_id': inv.currency_id.id, 'payment_term_id': inv.payment_term_id.id, + 'date_kirim_tukar_faktur': inv.date_kirim_tukar_faktur, + 'date_terima_tukar_faktur': inv.date_terima_tukar_faktur, + 'invoice_user_id': inv.invoice_user_id.id, + 'sale_id': inv.sale_id.id, 'selected': False }) for inv in invoice_lines] self.line_ids = lines @@ -220,33 +257,41 @@ class SuratPiutang(models.Model): # update line lama line = existing_lines[inv.invoice_id.id] line.write({ - 'invoice_view_id': inv.id, + # 'invoice_view_id': inv.id, 'invoice_number': inv.invoice_number, 'invoice_date': inv.invoice_date, 'invoice_date_due': inv.invoice_date_due, 'invoice_day_to_due': inv.invoice_day_to_due, - 'new_invoice_day_to_due': inv.invoice_day_to_due, + 'new_invoice_day_to_due': inv.new_invoice_day_to_due, 'ref': inv.ref, 'amount_residual': inv.amount_residual, 'currency_id': inv.currency_id.id, 'payment_term_id': inv.payment_term_id.id, + 'date_kirim_tukar_faktur': inv.date_kirim_tukar_faktur, + 'date_terima_tukar_faktur': inv.date_terima_tukar_faktur, + 'invoice_user_id': inv.invoice_user_id.id, + 'sale_id': inv.sale_id.id, 'selected': selected_map.get(inv.invoice_id.id, line.selected), }) else: # preserve selected kalau pernah ada di cache self.env['surat.piutang.line'].create({ 'surat_id': rec.id, - 'invoice_view_id': inv.id, + # 'invoice_view_id': inv.id, 'invoice_id': inv.invoice_id.id, 'invoice_number': inv.invoice_number, 'invoice_date': inv.invoice_date, 'invoice_date_due': inv.invoice_date_due, 'invoice_day_to_due': inv.invoice_day_to_due, - 'new_invoice_day_to_due': inv.invoice_day_to_due, + 'new_invoice_day_to_due': inv.new_invoice_day_to_due, 'ref': inv.ref, 'amount_residual': inv.amount_residual, 'currency_id': inv.currency_id.id, 'payment_term_id': inv.payment_term_id.id, + 'date_kirim_tukar_faktur': inv.date_kirim_tukar_faktur, + 'date_terima_tukar_faktur': inv.date_terima_tukar_faktur, + 'invoice_user_id': inv.invoice_user_id.id, + 'sale_id': inv.sale_id.id, 'selected': selected_map.get(inv.invoice_id.id, False), }) @@ -272,7 +317,7 @@ class SuratPiutangLine(models.Model): _description = 'Surat Piutang Line' surat_id = fields.Many2one('surat.piutang', string='Surat Piutang', ondelete='cascade') - invoice_view_id = fields.Many2one('unpaid.invoice.view', string='Unpaid Invoice') + # invoice_view_id = fields.Many2one('unpaid.invoice.view', string='Unpaid Invoice') invoice_id = fields.Many2one('account.move', string='Invoice') selected = fields.Boolean(string="Pilih", default=False) @@ -285,3 +330,8 @@ class SuratPiutangLine(models.Model): amount_residual = fields.Monetary(string='Amount Due Signed') currency_id = fields.Many2one('res.currency') payment_term_id = fields.Many2one('account.payment.term', string='Payment Terms') + + date_kirim_tukar_faktur = fields.Date(string='Kirim Faktur') + date_terima_tukar_faktur = fields.Date(string='Terima Faktur') + invoice_user_id = fields.Many2one('res.users', string='Salesperson') + sale_id = fields.Many2one('sale.order', string='Sale Order') -- cgit v1.2.3 From 6188090965c5381fa120268a024a486e62056505 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Mon, 8 Sep 2025 15:05:19 +0700 Subject: hilangin validasi sementara --- indoteknik_custom/models/tukar_guling_po.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/tukar_guling_po.py b/indoteknik_custom/models/tukar_guling_po.py index f2f37606..c65ac286 100644 --- a/indoteknik_custom/models/tukar_guling_po.py +++ b/indoteknik_custom/models/tukar_guling_po.py @@ -441,8 +441,8 @@ class TukarGulingPO(models.Model): ('state', '!=', 'cancel'), ], limit=1) - if existing_tukar_guling: - raise UserError("BU ini sudah pernah diretur oleh dokumen %s." % existing_tukar_guling.name) + # if existing_tukar_guling: + # raise UserError("BU ini sudah pernah diretur oleh dokumen %s." % existing_tukar_guling.name) picking = self.operations pick_id = self.operations.picking_type_id.id -- cgit v1.2.3 From 7a34ad0965e08d1428c700f09a49f30f17c1996c Mon Sep 17 00:00:00 2001 From: Miqdad Date: Mon, 8 Sep 2025 15:08:54 +0700 Subject: Balikin --- indoteknik_custom/models/tukar_guling_po.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/tukar_guling_po.py b/indoteknik_custom/models/tukar_guling_po.py index c65ac286..f2f37606 100644 --- a/indoteknik_custom/models/tukar_guling_po.py +++ b/indoteknik_custom/models/tukar_guling_po.py @@ -441,8 +441,8 @@ class TukarGulingPO(models.Model): ('state', '!=', 'cancel'), ], limit=1) - # if existing_tukar_guling: - # raise UserError("BU ini sudah pernah diretur oleh dokumen %s." % existing_tukar_guling.name) + if existing_tukar_guling: + raise UserError("BU ini sudah pernah diretur oleh dokumen %s." % existing_tukar_guling.name) picking = self.operations pick_id = self.operations.picking_type_id.id -- cgit v1.2.3 From 03db5f5bfd027fceeb0733bbb51e95e03c43d2cd Mon Sep 17 00:00:00 2001 From: Miqdad Date: Mon, 8 Sep 2025 16:20:28 +0700 Subject: copy false PO refund --- indoteknik_custom/models/purchase_order.py | 2 +- indoteknik_custom/models/refund_sale_order.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/purchase_order.py b/indoteknik_custom/models/purchase_order.py index 50913a80..18811b85 100755 --- a/indoteknik_custom/models/purchase_order.py +++ b/indoteknik_custom/models/purchase_order.py @@ -66,7 +66,7 @@ class PurchaseOrder(models.Model): sale_order = fields.Char(string='Sale Order') matches_so = fields.Many2many('sale.order', string='Matches SO', compute='_compute_matches_so') is_create_uangmuka = fields.Boolean(string='Uang Muka?') - move_id = fields.Many2one('account.move', string='Journal Entries Uang Muka', domain=[('move_type', '=', 'entry')]) + move_id = fields.Many2one('account.move', string='Journal Entries Uang Muka', domain=[('move_type', '=', 'entry')], copy=False) logbook_bill_id = fields.Many2one('report.logbook.bill', string='Logbook Bill') status_printed = fields.Selection([ ('not_printed', 'Belum Print'), diff --git a/indoteknik_custom/models/refund_sale_order.py b/indoteknik_custom/models/refund_sale_order.py index 731d8e41..9ab18f27 100644 --- a/indoteknik_custom/models/refund_sale_order.py +++ b/indoteknik_custom/models/refund_sale_order.py @@ -76,6 +76,7 @@ class RefundSaleOrder(models.Model): transfer_move_id = fields.Many2one( 'account.move', string="Journal Payment", + copy=False, help="Pilih transaksi salah transfer dari jurnal Uang Muka (journal_id=11) yang tidak terkait SO." ) -- cgit v1.2.3 From 6f7fd357434cf8372eeb1495612b74a9e16d22e0 Mon Sep 17 00:00:00 2001 From: AndriFP <113114423+andrifp@users.noreply.github.com> Date: Tue, 9 Sep 2025 09:40:01 +0700 Subject: (andri) fix --- indoteknik_custom/models/letter_receivable.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/letter_receivable.py b/indoteknik_custom/models/letter_receivable.py index caf62a64..8722ab8d 100644 --- a/indoteknik_custom/models/letter_receivable.py +++ b/indoteknik_custom/models/letter_receivable.py @@ -135,7 +135,7 @@ class SuratPiutang(models.Model): raise UserError(_("Email tujuan harus diisi.")) template = self.env.ref('indoteknik_custom.letter_receivable_mail_template') - today = fields.Date.today() + # today = fields.Date.today() month_map = { 1: "Januari", 2: "Februari", 3: "Maret", 4: "April", @@ -156,7 +156,7 @@ class SuratPiutang(models.Model): invoice_table_rows = "" grand_total = 0 for line in selected_lines: - days_to_due = (line.invoice_date_due - today).days if line.invoice_date_due else 0 + # days_to_due = (line.invoice_date_due - today).days if line.invoice_date_due else 0 grand_total += line.amount_residual invoice_table_rows += f""" @@ -164,7 +164,7 @@ class SuratPiutang(models.Model): {self.partner_id.name or '-'} {fields.Date.to_string(line.invoice_date) or '-'} {fields.Date.to_string(line.invoice_date_due) or '-'} - {days_to_due} + {line.new_invoice_day_to_due} {line.ref or '-'} {formatLang(self.env, line.amount_residual, currency_obj=line.currency_id)} {line.payment_term_id.name or '-'} -- cgit v1.2.3 From 7f49cfa92bed67bea8359433ff5c08cb069f284e Mon Sep 17 00:00:00 2001 From: Miqdad Date: Tue, 9 Sep 2025 11:49:54 +0700 Subject: add tanggal kirim sj --- indoteknik_custom/models/sj_tele.py | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sj_tele.py b/indoteknik_custom/models/sj_tele.py index c00279ec..8864a313 100644 --- a/indoteknik_custom/models/sj_tele.py +++ b/indoteknik_custom/models/sj_tele.py @@ -4,6 +4,7 @@ import requests import json import logging, subprocess import time +from collections import OrderedDict _logger = logging.getLogger(__name__) @@ -17,6 +18,7 @@ class SjTele(models.Model): picking_name = fields.Char(string='Picking Name') sale_name = fields.Char(string='Sale Name') create_date = fields.Datetime(string='Create Date') + date_doc_kirim = fields.Datetime(string='Tanggal Kirim SJ') # @api.model # def run_pentaho_carte(self): @@ -62,13 +64,32 @@ class SjTele(models.Model): _logger.exception("Gagal kirim Telegram (no data): %s", e) return True + lines = [] + groups = OrderedDict() + for rec in data: name = rec.picking_name or (rec.picking_id.name if rec.picking_id else '') pid = rec.picking_id.id if rec.picking_id else '' - so = rec.sale_id.name or rec.sale_name or '' + so = rec.sale_id.name or rec.sale_name or '' + dttm = (rec.picking_id.date_doc_kirim if (rec.picking_id and rec.picking_id.date_doc_kirim) + else getattr(rec, 'date_doc_kirim', None)) + + # format header tanggal (string), tanpa konversi Waktu/WIB + if dttm: + date_header = dttm if isinstance(dttm, str) else fields.Datetime.to_string(dttm) + date_header = date_header[:10] + else: + date_header = '(Tidak ada tanggal kirim SJ)' + if name: - lines.append(f"{name} - {so} ({pid})") + groups.setdefault(date_header, []).append(f"- ({pid}) - {name} - {so}") + + # build output berurutan per tanggal + for header_date, items in groups.items(): + lines.append(header_date) + lines.extend(items) + header = "Berikut merupakan nomor BU/OUT yang belum ada di Logbook SJ report:\n" text = header + "\n".join(lines) -- cgit v1.2.3 From ac13214fcab7a580b7c9b80faec8cfef684c09aa Mon Sep 17 00:00:00 2001 From: AndriFP <113114423+andrifp@users.noreply.github.com> Date: Wed, 10 Sep 2025 10:43:20 +0700 Subject: (andri) try qweb --- indoteknik_custom/models/letter_receivable.py | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/letter_receivable.py b/indoteknik_custom/models/letter_receivable.py index 8722ab8d..9a6c664c 100644 --- a/indoteknik_custom/models/letter_receivable.py +++ b/indoteknik_custom/models/letter_receivable.py @@ -53,6 +53,11 @@ class SuratPiutang(models.Model): compute="_compute_grand_total_text", ) + def action_print_surat_piutang(self): + self.ensure_one() + return self.env.ref('indoteknik_custom.report_surat_piutang_formal').report_action(self) + + @api.depends("line_ids.selected", "line_ids.invoice_date") def _compute_periode_invoices(self): for rec in self: -- cgit v1.2.3 From 97be5b4f02f5eb499ab6cf2afa031a703d2a5866 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Wed, 10 Sep 2025 11:43:40 +0700 Subject: done kyknya --- indoteknik_custom/models/sale_order.py | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 8cb169a6..630f25ba 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -1900,10 +1900,10 @@ class SaleOrder(models.Model): # raise UserError('Kelurahan Real Delivery Address harus diisi') def generate_payment_link_midtrans_sales_order(self): - # midtrans_url = 'https://app.sandbox.midtrans.com/snap/v1/transactions' # dev - sandbox - # midtrans_auth = 'Basic U0ItTWlkLXNlcnZlci1uLVY3ZDJjMlpCMFNWRUQyOU95Q1dWWXA6' # dev - sandbox - midtrans_url = 'https://app.midtrans.com/snap/v1/transactions' # production - midtrans_auth = 'Basic TWlkLXNlcnZlci1SbGMxZ2gzWGpSVW5scl9JblZzTV9OTnU6' # production + midtrans_url = 'https://app.sandbox.midtrans.com/snap/v1/transactions' # dev - sandbox + midtrans_auth = 'Basic U0ItTWlkLXNlcnZlci1uLVY3ZDJjMlpCMFNWRUQyOU95Q1dWWXA6' # dev - sandbox + # midtrans_url = 'https://app.midtrans.com/snap/v1/transactions' # production + # midtrans_auth = 'Basic TWlkLXNlcnZlci1SbGMxZ2gzWGpSVW5scl9JblZzTV9OTnU6' # production so_number = self.name so_number = so_number.replace('/', '-') @@ -1916,8 +1916,8 @@ class SaleOrder(models.Model): } # ==== ENV ==== - # check_url = f'https://api.sandbox.midtrans.com/v2/{so_number}/status' # dev - sandbox - check_url = f'https://api.midtrans.com/v2/{so_number}/status' # production + check_url = f'https://api.sandbox.midtrans.com/v2/{so_number}/status' # dev - sandbox + # check_url = f'https://api.midtrans.com/v2/{so_number}/status' # production # ============================================= check_response = requests.get(check_url, headers=headers) @@ -2387,17 +2387,23 @@ class SaleOrder(models.Model): # Ambil blocking stage dari partner block_stage = rec.partner_id.parent_id.blocking_stage if rec.partner_id.parent_id else rec.partner_id.blocking_stage or 0 is_cbd = rec.partner_id.parent_id.property_payment_term_id.id == 26 if rec.partner_id.parent_id else rec.partner_id.property_payment_term_id.id == 26 or False + partner_term = rec.partner_id.property_payment_term_id + partner_term_days_total = 0 + if partner_term: + partner_term_days_total = sum((line.days or 0) for line in partner_term.line_ids) + is_partner_cbd = (partner_term_days_total == 0) + is_so_cbd = bool(rec.payment_term_id.id == 26) # Ambil jumlah nilai dari SO yang invoice_status masih 'to invoice' so_to_invoice = 0 for sale in rec.partner_id.sale_order_ids: if sale.invoice_status == 'to invoice': so_to_invoice = so_to_invoice + sale.amount_total - # Hitung remaining credit limit - remaining_credit_limit = block_stage - current_total - so_to_invoice + + remaining_credit_limit = block_stage - current_total - so_to_invoice if not is_cbd and not is_partner_cbd else 0 # Validasi limit - if remaining_credit_limit <= 0 and block_stage > 0 and not is_cbd: + if remaining_credit_limit <= 0 and block_stage > 0 and not is_cbd and not is_so_cbd and not is_partner_cbd: raise UserError( _("The credit limit for %s will exceed the Blocking Stage if the Sale Order is confirmed. The remaining credit limit is %s, from %s and the outstanding amount is %s.") % (rec.partner_id.name, block_stage - current_total, block_stage, outstanding_amount)) -- cgit v1.2.3 From 1767a569e88e8254fdbefdfaf6be6045f1f5235f Mon Sep 17 00:00:00 2001 From: Miqdad Date: Wed, 10 Sep 2025 11:45:39 +0700 Subject: apt request done --- indoteknik_custom/models/approval_payment_term.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/approval_payment_term.py b/indoteknik_custom/models/approval_payment_term.py index 8618856a..85fd1613 100644 --- a/indoteknik_custom/models/approval_payment_term.py +++ b/indoteknik_custom/models/approval_payment_term.py @@ -69,8 +69,8 @@ class ApprovalPaymentTerm(models.Model): return res def _track_changes_for_user_688(self, vals, old_values_dict): - if self.env.user.id != 688: - return + # if self.env.user.id != 688: + # return tracked_fields = {"blocking_stage", "warning_stage", "property_payment_term_id"} -- cgit v1.2.3 From e3c9d12ca2356493bb75c7362d0da54280889828 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Wed, 10 Sep 2025 12:22:07 +0700 Subject: dunning done --- indoteknik_custom/models/dunning_run.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/dunning_run.py b/indoteknik_custom/models/dunning_run.py index 5a6aebac..eb6f06dc 100644 --- a/indoteknik_custom/models/dunning_run.py +++ b/indoteknik_custom/models/dunning_run.py @@ -31,6 +31,7 @@ class DunningRun(models.Model): description = fields.Char(string='Description') comment = fields.Char(string='Comment') grand_total = fields.Float(string='Grand Total', compute="_compute_grand_total") + is_paid = fields.Boolean(string='Sudah Bayar?') def _compute_grand_total(self): for record in self: @@ -149,4 +150,5 @@ class DunningRunLine(models.Model): total_amt = fields.Float(string='Total Amount') open_amt = fields.Float(string='Open Amount') due_date = fields.Date(string='Due Date') + payment_term = fields.Many2one('account.payment.term', related='invoice_id.invoice_payment_term_id', string='Payment Term') -- cgit v1.2.3 From 32d0ca72aa38701747794dab06e7fb98b6f31489 Mon Sep 17 00:00:00 2001 From: AndriFP <113114423+andrifp@users.noreply.github.com> Date: Wed, 10 Sep 2025 13:42:34 +0700 Subject: (andri) rev validasi --- indoteknik_custom/models/res_partner.py | 4 ++-- indoteknik_custom/models/sale_order.py | 22 +++++++++++----------- 2 files changed, 13 insertions(+), 13 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/res_partner.py b/indoteknik_custom/models/res_partner.py index b5ce9266..0c932dbe 100644 --- a/indoteknik_custom/models/res_partner.py +++ b/indoteknik_custom/models/res_partner.py @@ -196,9 +196,9 @@ class ResPartner(models.Model): @api.constrains('property_payment_term_id', 'is_cbd_locked') def _check_cbd_lock_partner(self): - cbd_term = self.env['account.payment.term'].browse(26) + # cbd_term = self.env['account.payment.term'].browse(26) for rec in self: - if rec.is_cbd_locked and rec.property_payment_term_id and rec.property_payment_term_id != cbd_term: + if rec.is_cbd_locked: raise ValidationError( "Partner ini terkunci ke CBD, hanya boleh pakai Payment Term CBD." ) diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 8a595d8e..89bf0d25 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -404,22 +404,22 @@ class SaleOrder(models.Model): for order in self: order.partner_is_cbd_locked = order.partner_id.is_cbd_locked - @api.onchange('payment_term_id') - def _onchange_partner_payment_term(self): - cbd_term = self.env['account.payment.term'].browse(26) - for rec in self: - if rec.partner_id and rec.partner_id.is_cbd_locked and cbd_term: - rec.payment_term_id = cbd_term + # @api.onchange('payment_term_id') + # def _onchange_partner_payment_term(self): + # cbd_term = self.env['account.payment.term'].browse(26) + # for rec in self: + # if rec.partner_id and rec.partner_id.is_cbd_locked and cbd_term: + # rec.payment_term_id = cbd_term @api.constrains('payment_term_id', 'partner_id', 'state') def _check_cbd_lock_sale_order(self): - cbd_term = self.env['account.payment.term'].browse(26) + # cbd_term = self.env['account.payment.term'].browse(26) for rec in self: if rec.state == 'draft' and rec.partner_id.is_cbd_locked: - if rec.payment_term_id and rec.payment_term_id != cbd_term: - raise ValidationError( - "Customer ini terkunci ke CBD, hanya boleh pakai Payment Term CBD." - ) + # if rec.payment_term_id and rec.payment_term_id != cbd_term: + raise ValidationError( + "Customer ini terkunci ke CBD, hanya boleh pakai Payment Term CBD." + ) @api.depends('invoice_ids.payment_state', 'invoice_ids.amount_total', 'invoice_ids.amount_residual') def _compute_payment_state_custom(self): -- cgit v1.2.3 From 4ce4b37fa551e1059cdb5f64f98c53d4289d3268 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Wed, 10 Sep 2025 16:04:39 +0700 Subject: dunning done --- indoteknik_custom/models/approval_payment_term.py | 3 ++- indoteknik_custom/models/dunning_run.py | 14 ++++++++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/approval_payment_term.py b/indoteknik_custom/models/approval_payment_term.py index 85fd1613..61339b99 100644 --- a/indoteknik_custom/models/approval_payment_term.py +++ b/indoteknik_custom/models/approval_payment_term.py @@ -106,7 +106,8 @@ class ApprovalPaymentTerm(models.Model): if changes: timestamp = fields.Datetime.now().strftime('%Y-%m-%d %H:%M:%S') - rec.change_log_688 = f"{timestamp} - Perubahan oleh Widya:\n" + "\n".join(changes) + user = self.env.user + rec.change_log_688 = f"{timestamp} - Perubahan oleh {user.name}:\n" + "\n".join(changes) @staticmethod diff --git a/indoteknik_custom/models/dunning_run.py b/indoteknik_custom/models/dunning_run.py index eb6f06dc..40e5f71e 100644 --- a/indoteknik_custom/models/dunning_run.py +++ b/indoteknik_custom/models/dunning_run.py @@ -1,6 +1,6 @@ from odoo import models, api, fields from odoo.exceptions import AccessError, UserError, ValidationError -from datetime import timedelta +from datetime import timedelta, date import logging @@ -32,7 +32,17 @@ class DunningRun(models.Model): comment = fields.Char(string='Comment') grand_total = fields.Float(string='Grand Total', compute="_compute_grand_total") is_paid = fields.Boolean(string='Sudah Bayar?') - + tanggal_pembayaran = fields.Date( + string='Tanggal Pembayaran', + compute="_compute_tanggal_pembayaran", + readonly=True, + ) + + @api.depends('is_paid') + def _compute_tanggal_pembayaran(self): + today = fields.Date.context_today(self) + for rec in self: + rec.tanggal_pembayaran = today if rec.is_paid else False def _compute_grand_total(self): for record in self: grand_total = 0 -- cgit v1.2.3 From 8aceb84b2cf029308903b39bfe139a05ea8f73f6 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Wed, 10 Sep 2025 16:08:34 +0700 Subject: dunning done --- indoteknik_custom/models/dunning_run.py | 1 + 1 file changed, 1 insertion(+) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/dunning_run.py b/indoteknik_custom/models/dunning_run.py index 40e5f71e..557eacef 100644 --- a/indoteknik_custom/models/dunning_run.py +++ b/indoteknik_custom/models/dunning_run.py @@ -43,6 +43,7 @@ class DunningRun(models.Model): today = fields.Date.context_today(self) for rec in self: rec.tanggal_pembayaran = today if rec.is_paid else False + def _compute_grand_total(self): for record in self: grand_total = 0 -- cgit v1.2.3 From e276a3bbf0a2726cbfa6bb5606d2dfbfe1e03143 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Wed, 10 Sep 2025 18:34:49 +0700 Subject: Remove tanggal bayar and is paid --- indoteknik_custom/models/dunning_run.py | 12 ------------ 1 file changed, 12 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/dunning_run.py b/indoteknik_custom/models/dunning_run.py index 557eacef..9feea1d1 100644 --- a/indoteknik_custom/models/dunning_run.py +++ b/indoteknik_custom/models/dunning_run.py @@ -31,18 +31,6 @@ class DunningRun(models.Model): description = fields.Char(string='Description') comment = fields.Char(string='Comment') grand_total = fields.Float(string='Grand Total', compute="_compute_grand_total") - is_paid = fields.Boolean(string='Sudah Bayar?') - tanggal_pembayaran = fields.Date( - string='Tanggal Pembayaran', - compute="_compute_tanggal_pembayaran", - readonly=True, - ) - - @api.depends('is_paid') - def _compute_tanggal_pembayaran(self): - today = fields.Date.context_today(self) - for rec in self: - rec.tanggal_pembayaran = today if rec.is_paid else False def _compute_grand_total(self): for record in self: -- cgit v1.2.3 From d27561901fa2f0ab7534f255351e82459c6a531e Mon Sep 17 00:00:00 2001 From: AndriFP <113114423+andrifp@users.noreply.github.com> Date: Thu, 11 Sep 2025 10:45:58 +0700 Subject: (andri) fix --- indoteknik_custom/models/res_partner.py | 8 -------- 1 file changed, 8 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/res_partner.py b/indoteknik_custom/models/res_partner.py index 0c932dbe..36570e8f 100644 --- a/indoteknik_custom/models/res_partner.py +++ b/indoteknik_custom/models/res_partner.py @@ -194,14 +194,6 @@ class ResPartner(models.Model): default=_default_payment_term, tracking=3 ) - @api.constrains('property_payment_term_id', 'is_cbd_locked') - def _check_cbd_lock_partner(self): - # cbd_term = self.env['account.payment.term'].browse(26) - for rec in self: - if rec.is_cbd_locked: - raise ValidationError( - "Partner ini terkunci ke CBD, hanya boleh pakai Payment Term CBD." - ) @api.depends("street", "street2", "city", "state_id", "country_id", "blok", "nomor", "rt", "rw", "kelurahan_id", "kecamatan_id") -- cgit v1.2.3 From c5e14063c50ae06b829eca0415b9b14b03ccb0b6 Mon Sep 17 00:00:00 2001 From: AndriFP <113114423+andrifp@users.noreply.github.com> Date: Thu, 11 Sep 2025 11:07:29 +0700 Subject: (andri) fix --- indoteknik_custom/models/sale_order.py | 6 ------ 1 file changed, 6 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index d0fd7f48..dbe6d789 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -403,12 +403,6 @@ class SaleOrder(models.Model): for order in self: order.partner_is_cbd_locked = order.partner_id.is_cbd_locked - # @api.onchange('payment_term_id') - # def _onchange_partner_payment_term(self): - # cbd_term = self.env['account.payment.term'].browse(26) - # for rec in self: - # if rec.partner_id and rec.partner_id.is_cbd_locked and cbd_term: - # rec.payment_term_id = cbd_term @api.constrains('payment_term_id', 'partner_id', 'state') def _check_cbd_lock_sale_order(self): -- cgit v1.2.3 From ee6c5f572f06e5fde497fc3aed5d78c3f0d7d389 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Thu, 11 Sep 2025 17:13:59 +0700 Subject: Gudang pameran --- indoteknik_custom/models/sale_order.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 9952af9a..67e712d1 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -2115,8 +2115,8 @@ class SaleOrder(models.Model): if self.payment_term_id.id == 31 and self.total_percent_margin < 25: raise UserError("Jika ingin menggunakan Tempo 90 Hari maka margin harus di atas 25%") - if self.warehouse_id.id != 8 and self.warehouse_id.id != 10: # GD Bandengan - raise UserError('Gudang harus Bandengan') + if self.warehouse_id.id != 8 and self.warehouse_id.id != 10 and self.warehouse_id.id != 12: # GD Bandengan / Pameran + raise UserError('Gudang harus Bandengan atau Pameran') if self.state not in ['draft', 'sent']: raise UserError("Status harus draft atau sent") -- cgit v1.2.3 From 51e77c4968cbc0e577e161a38407bb9f291a6f62 Mon Sep 17 00:00:00 2001 From: AndriFP <113114423+andrifp@users.noreply.github.com> Date: Thu, 11 Sep 2025 18:43:25 +0700 Subject: (andri) fix --- indoteknik_custom/models/sale_order.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 76d4d7e7..42f58d2b 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -393,15 +393,15 @@ class SaleOrder(models.Model): ('paid', 'Full Paid'), ('no_invoice', 'No Invoice'), ], string="Payment Status Invoice", compute="_compute_payment_state_custom", store=False) - partner_is_cbd_locked = fields.Boolean( - string="Partner Locked CBD", - compute="_compute_partner_is_cbd_locked" - ) + # partner_is_cbd_locked = fields.Boolean( + # string="Partner Locked CBD", + # compute="_compute_partner_is_cbd_locked" + # ) - @api.depends('partner_id.is_cbd_locked') - def _compute_partner_is_cbd_locked(self): - for order in self: - order.partner_is_cbd_locked = order.partner_id.is_cbd_locked + # @api.depends('partner_id.is_cbd_locked') + # def _compute_partner_is_cbd_locked(self): + # for order in self: + # order.partner_is_cbd_locked = order.partner_id.is_cbd_locked @api.constrains('payment_term_id', 'partner_id', 'state') -- cgit v1.2.3 From 6c5b5424c88650e39418ffd416a74205a0ae185b Mon Sep 17 00:00:00 2001 From: AndriFP <113114423+andrifp@users.noreply.github.com> Date: Thu, 11 Sep 2025 18:47:32 +0700 Subject: (andri) fix --- indoteknik_custom/models/sale_order.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 42f58d2b..76d4d7e7 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -393,15 +393,15 @@ class SaleOrder(models.Model): ('paid', 'Full Paid'), ('no_invoice', 'No Invoice'), ], string="Payment Status Invoice", compute="_compute_payment_state_custom", store=False) - # partner_is_cbd_locked = fields.Boolean( - # string="Partner Locked CBD", - # compute="_compute_partner_is_cbd_locked" - # ) + partner_is_cbd_locked = fields.Boolean( + string="Partner Locked CBD", + compute="_compute_partner_is_cbd_locked" + ) - # @api.depends('partner_id.is_cbd_locked') - # def _compute_partner_is_cbd_locked(self): - # for order in self: - # order.partner_is_cbd_locked = order.partner_id.is_cbd_locked + @api.depends('partner_id.is_cbd_locked') + def _compute_partner_is_cbd_locked(self): + for order in self: + order.partner_is_cbd_locked = order.partner_id.is_cbd_locked @api.constrains('payment_term_id', 'partner_id', 'state') -- cgit v1.2.3 From c0ea809f621f07727a2e3bf54952818992553ea2 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Fri, 12 Sep 2025 10:44:22 +0700 Subject: Fix duplicate ORT --- indoteknik_custom/models/tukar_guling.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/tukar_guling.py b/indoteknik_custom/models/tukar_guling.py index 6e839bf0..d718ba0f 100644 --- a/indoteknik_custom/models/tukar_guling.py +++ b/indoteknik_custom/models/tukar_guling.py @@ -710,7 +710,7 @@ class TukarGuling(models.Model): ### ======== SRT dari BU/OUT ========= srt_return_lines = [] - if mapping_koli: + if mapping_koli and record.operations.picking_type_id.id == 29: for prod in mapping_koli.mapped('product_id'): qty_total = sum(mk.qty_return for mk in mapping_koli.filtered(lambda m: m.product_id == prod)) move = bu_out.move_lines.filtered(lambda m: m.product_id == prod) @@ -723,7 +723,7 @@ class TukarGuling(models.Model): })) _logger.info(f"📟 SRT line: {prod.display_name} | qty={qty_total}") - elif not mapping_koli: + elif not mapping_koli and record.operations.picking_type_id.id == 29: for line in record.line_ids: move = bu_out.move_lines.filtered(lambda m: m.product_id == line.product_id) if not move: -- cgit v1.2.3 From 7d07a282c134cb5e2a784b73cc477e3f398df57c Mon Sep 17 00:00:00 2001 From: it-fixcomart Date: Fri, 12 Sep 2025 10:48:13 +0700 Subject: add rc in journal --- indoteknik_custom/models/refund_sale_order.py | 8 ++++++-- indoteknik_custom/models/stock_move.py | 1 + 2 files changed, 7 insertions(+), 2 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/refund_sale_order.py b/indoteknik_custom/models/refund_sale_order.py index 731d8e41..e981abb5 100644 --- a/indoteknik_custom/models/refund_sale_order.py +++ b/indoteknik_custom/models/refund_sale_order.py @@ -462,7 +462,7 @@ class RefundSaleOrder(models.Model): total_invoice = 0.0 so_ids = self.sale_order_ids.ids - + amount_refund_before = 0.0 for so in self.sale_order_ids: self.ongkir += so.delivery_amt or 0.0 valid_invoices = so.invoice_ids.filtered( @@ -470,6 +470,10 @@ class RefundSaleOrder(models.Model): ) all_invoices |= valid_invoices total_invoice += sum(valid_invoices.mapped('amount_total_signed')) + refunds = self.env['refund.sale.order'].search([ + ('sale_order_ids', 'in', so_ids) + ]) + amount_refund_before += sum(refunds.mapped('amount_refund')) if refunds else 0.0 moves = self.env['account.move'].search([ ('sale_id', 'in', so_ids), @@ -478,7 +482,7 @@ class RefundSaleOrder(models.Model): ]) total_uang_muka = sum(moves.mapped('amount_total_signed')) if moves else 0.0 total_midtrans = sum(self.env['sale.order'].browse(so_ids).mapped('gross_amount')) if so_ids else 0.0 - self.uang_masuk = total_uang_muka + total_midtrans + self.uang_masuk = (total_uang_muka + total_midtrans) - amount_refund_before self.invoice_ids = all_invoices diff --git a/indoteknik_custom/models/stock_move.py b/indoteknik_custom/models/stock_move.py index 90ab30a4..64cb6ba6 100644 --- a/indoteknik_custom/models/stock_move.py +++ b/indoteknik_custom/models/stock_move.py @@ -15,6 +15,7 @@ class StockMove(models.Model): barcode = fields.Char(string='Barcode', related='product_id.barcode') vendor_id = fields.Many2one('res.partner' ,string='Vendor') hold_outgoingg = fields.Boolean('Hold Outgoing', default=False) + product_image = fields.Binary(related="product_id.image_128", string="Product Image", readonly=True) # @api.model_create_multi # def create(self, vals_list): -- cgit v1.2.3 From fb069315f937ea1a8a094d23f22f8314cef412a4 Mon Sep 17 00:00:00 2001 From: it-fixcomart Date: Fri, 12 Sep 2025 14:37:48 +0700 Subject: fix label --- indoteknik_custom/models/refund_sale_order.py | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/refund_sale_order.py b/indoteknik_custom/models/refund_sale_order.py index 9bcdd974..f511ed5d 100644 --- a/indoteknik_custom/models/refund_sale_order.py +++ b/indoteknik_custom/models/refund_sale_order.py @@ -824,15 +824,26 @@ class RefundSaleOrder(models.Model): # Ambil label refund type refund_type_label = dict( self.fields_get(allfields=['refund_type'])['refund_type']['selection'] - ).get(refund.refund_type, '').replace("Refund ", "").upper() - + ).get(refund.refund_type, '') + + # Normalisasi + refund_type_label = refund_type_label.upper() + + if refund.refund_type in ['barang_kosong', 'barang_kosong_sebagian']: + refund_type_label = "REFUND BARANG KOSONG" + elif refund.refund_type in ['retur_half', 'retur']: + refund_type_label = "REFUND RETUR BARANG" + elif refund.refund_type == 'uang': + refund_type_label = "REFUND LEBIH BAYAR" + elif refund.refund_type == 'salah_transfer': + refund_type_label = "REFUND SALAH TRANSFER" if not partner: raise UserError("❌ Partner tidak ditemukan.") # Ref format - ref_text = f"REFUND {refund_type_label} {refund.name or ''} {partner.display_name}".upper() + ref_text = f"{refund_type_label} {refund.name or ''} {partner.display_name}".upper() # Buat Account Move (Journal Entry) account_move = self.env['account.move'].create({ @@ -882,7 +893,8 @@ class RefundSaleOrder(models.Model): def _compute_journal_refund_move_id(self): for rec in self: move = self.env['account.move'].search([ - ('refund_id', '=', rec.id) + ('refund_id', '=', rec.id), + ('state', '!=', 'cancel') ], limit=1) rec.journal_refund_move_id = move -- cgit v1.2.3 From 71882195d32ac190ea2225ab61fec7a796e30c70 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Fri, 12 Sep 2025 15:29:28 +0700 Subject: remove required npwp sppkp nib and fix purchase report --- indoteknik_custom/models/sale_order.py | 105 ++++++++-------------------- indoteknik_custom/models/sale_order_line.py | 6 -- 2 files changed, 30 insertions(+), 81 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 76d4d7e7..c767dd04 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -234,9 +234,9 @@ class SaleOrder(models.Model): customer_type = fields.Selection([ ('pkp', 'PKP'), ('nonpkp', 'Non PKP') - ], required=True, compute='_compute_partner_field') - sppkp = fields.Char(string="SPPKP", required=True, tracking=True, compute='_compute_partner_field') - npwp = fields.Char(string="NPWP", required=True, tracking=True, compute='_compute_partner_field') + ], related="partner_id.customer_type", string="Customer Type", readonly=True) + sppkp = fields.Char(string="SPPKP", related="partner_id.sppkp") + npwp = fields.Char(string="NPWP", related="partner_id.npwp") purchase_total = fields.Monetary(string='Purchase Total', compute='_compute_purchase_total') voucher_id = fields.Many2one(comodel_name='voucher', string='Voucher', copy=False) applied_voucher_id = fields.Many2one(comodel_name='voucher', string='Applied Voucher', copy=False) @@ -2049,22 +2049,22 @@ class SaleOrder(models.Model): # return [('id', 'not in', order_ids)] # return ['&', ('order_line.invoice_lines.move_id.move_type', 'in', ('out_invoice', 'out_refund')), ('order_line.invoice_lines.move_id', operator, value)] - @api.depends('partner_id') - def _compute_partner_field(self): - for order in self: - partner = order.partner_id.parent_id or order.partner_id - order.npwp = partner.npwp - order.sppkp = partner.sppkp - order.customer_type = partner.customer_type + # @api.depends('partner_id') + # def _compute_partner_field(self): + # for order in self: + # partner = order.partner_id.parent_id or order.partner_id + # order.npwp = partner.npwp + # order.sppkp = partner.sppkp + # order.customer_type = partner.customer_type @api.onchange('partner_id') def onchange_partner_contact(self): parent_id = self.partner_id.parent_id parent_id = parent_id if parent_id else self.partner_id - self.npwp = parent_id.npwp - self.sppkp = parent_id.sppkp - self.customer_type = parent_id.customer_type + # self.npwp = parent_id.npwp + # self.sppkp = parent_id.sppkp + # self.customer_type = parent_id.customer_type self.email = parent_id.email self.pareto_status = parent_id.pareto_status self.user_id = parent_id.user_id @@ -2141,9 +2141,15 @@ class SaleOrder(models.Model): if self.state not in ['draft', 'sent']: raise UserError("Status harus draft atau sent") - self._validate_npwp() - def _validate_npwp(self): + if not self.npwp: + raise UserError("NPWP partner kosong, silahkan isi terlebih dahulu npwp nya di contact partner") + + if not self.customer_type: + raise UserError("Customer Type partner kosong, silahkan isi terlebih dahulu Customer Type nya di contact partner") + + if not self.sppkp: + raise UserError("SPPKP partner kosong, silahkan isi terlebih dahulu SPPKP nya di contact partner") num_digits = sum(c.isdigit() for c in self.npwp) if num_digits < 10: @@ -2157,6 +2163,7 @@ class SaleOrder(models.Model): self._validate_order() for order in self: + order._validate_npwp() order._validate_uniform_taxes() order.order_line.validate_line() @@ -2211,9 +2218,8 @@ class SaleOrder(models.Model): if self.validate_different_vendor() and not self.vendor_approval: return self._create_notification_action('Notification', 'Terdapat Vendor yang berbeda dengan MD Vendor') self.check_due() - - self._validate_order() for order in self: + order._validate_npwp() order._validate_delivery_amt() order._validate_uniform_taxes() order.order_line.validate_line() @@ -2480,6 +2486,7 @@ class SaleOrder(models.Model): order.check_data_real_delivery_address() order.sale_order_check_approve() order._validate_order() + order._validate_npwp() order.order_line.validate_line() main_parent = order.partner_id.get_main_parent() @@ -2665,23 +2672,17 @@ class SaleOrder(models.Model): def _set_sppkp_npwp_contact(self): partner = self.partner_id.parent_id or self.partner_id - if not partner.sppkp: - partner.sppkp = self.sppkp - if not partner.npwp: - partner.npwp = self.npwp + # if not partner.sppkp: + # partner.sppkp = self.sppkp + # if not partner.npwp: + # partner.npwp = self.npwp if not partner.email: partner.email = self.email - if not partner.customer_type: - partner.customer_type = self.customer_type + # if not partner.customer_type: + # partner.customer_type = self.customer_type if not partner.user_id: partner.user_id = self.user_id.id - # if not partner.sppkp or not partner.npwp or not partner.email or partner.customer_type: - # partner.customer_type = self.customer_type - # partner.npwp = self.npwp - # partner.sppkp = self.sppkp - # partner.email = self.email - def _compute_total_margin(self): for order in self: total_margin = sum(line.item_margin for line in order.order_line if line.product_id) @@ -3123,52 +3124,6 @@ class SaleOrder(models.Model): # order._update_partner_details() return order - # def write(self, vals): - # Call the super method to handle the write operation - # res = super(SaleOrder, self).write(vals) - # self._compute_etrts_date() - # Check if the update is coming from a save operation - # if any(field in vals for field in ['sppkp', 'npwp', 'email', 'customer_type']): - # self._update_partner_details() - - # return res - - def _update_partner_details(self): - for order in self: - partner = order.partner_id.parent_id or order.partner_id - if partner: - # Update partner details - partner.sppkp = order.sppkp - partner.npwp = order.npwp - partner.email = order.email - partner.customer_type = order.customer_type - - # Save changes to the partner record - partner.write({ - 'sppkp': partner.sppkp, - 'npwp': partner.npwp, - 'email': partner.email, - 'customer_type': partner.customer_type, - }) - - # def write(self, vals): - # for order in self: - # if order.state in ['sale', 'cancel']: - # if 'order_line' in vals: - # new_lines = vals.get('order_line', []) - # for command in new_lines: - # if command[0] == 0: # A new line is being added - # raise UserError( - # "SO tidak dapat ditambahkan produk baru karena SO sudah menjadi sale order.") - # - # res = super(SaleOrder, self).write(vals) - # # self._check_total_margin_excl_third_party() - # if any(fields in vals for fields in ['delivery_amt', 'carrier_id', 'shipping_cost_covered']): - # self._validate_delivery_amt() - # if any(field in vals for field in ["order_line", "client_order_ref"]): - # self._calculate_etrts_date() - # return res - # @api.depends('commitment_date') def _compute_ready_to_ship_status_detail(self): def is_empty(val): diff --git a/indoteknik_custom/models/sale_order_line.py b/indoteknik_custom/models/sale_order_line.py index 47a24264..1f2ea1fb 100644 --- a/indoteknik_custom/models/sale_order_line.py +++ b/indoteknik_custom/models/sale_order_line.py @@ -71,23 +71,17 @@ class SaleOrderLine(models.Model): if order_qty > 0: for move in line.move_ids: - # --- CASE 1: Move belum selesai --- if move.state not in ('done', 'cancel'): reserved_qty += move.reserved_availability or 0.0 continue - # --- CASE 2: Move sudah done --- if move.location_dest_id.usage == 'customer': - # Barang dikirim ke customer delivered_qty += move.quantity_done or 0.0 elif move.location_id.usage == 'customer': - # Barang balik dari customer (retur) delivered_qty -= move.quantity_done or 0.0 - # Clamp supaya delivered gak minus delivered_qty = max(delivered_qty, 0) - # Hitung persen line.reserved_percent = min((reserved_qty / order_qty) * 100, 100) if order_qty else 0 line.delivered_percent = min((delivered_qty / order_qty) * 100, 100) if order_qty else 0 line.unreserved_percent = max(100 - line.reserved_percent - line.delivered_percent, 0) -- cgit v1.2.3 From d04150023a7145468858765209e8d6b3724b0fec Mon Sep 17 00:00:00 2001 From: AndriFP <113114423+andrifp@users.noreply.github.com> Date: Fri, 12 Sep 2025 16:05:57 +0700 Subject: (andri) fix template --- indoteknik_custom/models/account_move.py | 2 +- indoteknik_custom/models/down_payment.py | 1088 +++++++++++++++++++++++++ indoteknik_custom/models/letter_receivable.py | 13 +- 3 files changed, 1099 insertions(+), 4 deletions(-) create mode 100644 indoteknik_custom/models/down_payment.py (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/account_move.py b/indoteknik_custom/models/account_move.py index c93cfb76..764a8b20 100644 --- a/indoteknik_custom/models/account_move.py +++ b/indoteknik_custom/models/account_move.py @@ -394,7 +394,7 @@ class AccountMove(models.Model): 'reply_to': 'finance@indoteknik.co.id', } - template.send_mail(invs[0].id, force_send=True, email_values=values) + # template.send_mail(invs[0].id, force_send=True, email_values=values) _logger.info(f"Mengirim email ke: {values['email_to']} > email CC: {values['email_cc']}") _logger.info(f"Reminder terkirim ke {partner.name} ({values['email_to']}) → {len(invs)} invoice (dtd = {dtd})") # flag diff --git a/indoteknik_custom/models/down_payment.py b/indoteknik_custom/models/down_payment.py new file mode 100644 index 00000000..5adbafd9 --- /dev/null +++ b/indoteknik_custom/models/down_payment.py @@ -0,0 +1,1088 @@ +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 dikonfirmasi oleh AP.", + 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 Departement oleh {self.env.user.name} " + f"pada {formatted_date}." + ) + + 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 AP oleh {self.env.user.name} " + f"pada {formatted_date}." + ) + + 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 Pimpinan oleh {self.env.user.name} " + f"pada {formatted_date}." + ) + + else: + raise UserError("Status saat ini tidak bisa di-approve lagi.") + + # rec.message_post(body=f"Approval oleh {self.env.user.name} pada tahap {rec.status}.") + + + 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 {new_status.capitalize()} 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 {dict(self._fields['done_status'].selection).get(self.done_status)} 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: {move.name}.") + + 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 Departement oleh {self.env.user.name} " + f"pada {formatted_date}." + ) + + 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 AP oleh {self.env.user.name} " + f"pada {formatted_date}." + ) + + 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 Pimpinan oleh {self.env.user.name} " + f"pada {formatted_date}." + ) + + else: + raise UserError("Status saat ini tidak bisa di-approve lagi.") + + # rec.message_post(body=f"Approval oleh {self.env.user.name} pada tahap {rec.status}.") + + 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 9a6c664c..a414eec3 100644 --- a/indoteknik_custom/models/letter_receivable.py +++ b/indoteknik_custom/models/letter_receivable.py @@ -53,9 +53,16 @@ class SuratPiutang(models.Model): compute="_compute_grand_total_text", ) - def action_print_surat_piutang(self): - self.ensure_one() - return self.env.ref('indoteknik_custom.report_surat_piutang_formal').report_action(self) + perihal_label = fields.Char( + compute="_compute_perihal_label", string="Perihal Label") + + def _compute_perihal_label(self): + for rec in self: + rec.perihal_label = dict(self._fields['perihal'].selection).get(rec.perihal, '') + + # def action_print_surat_piutang(self): + # self.ensure_one() + # return self.env.ref('indoteknik_custom.report_surat_piutang_formal').report_action(self) @api.depends("line_ids.selected", "line_ids.invoice_date") -- cgit v1.2.3 From f2b1b0ec605b552c2bf225de46094cd4707197ee Mon Sep 17 00:00:00 2001 From: AndriFP <113114423+andrifp@users.noreply.github.com> Date: Fri, 12 Sep 2025 17:34:14 +0700 Subject: (andri) add penomoran di tabel dan melengkapi teks surat --- indoteknik_custom/models/letter_receivable.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/letter_receivable.py b/indoteknik_custom/models/letter_receivable.py index a414eec3..3823a57a 100644 --- a/indoteknik_custom/models/letter_receivable.py +++ b/indoteknik_custom/models/letter_receivable.py @@ -347,3 +347,18 @@ class SuratPiutangLine(models.Model): date_terima_tukar_faktur = fields.Date(string='Terima Faktur') invoice_user_id = fields.Many2one('res.users', string='Salesperson') sale_id = fields.Many2one('sale.order', string='Sale Order') + + sort = fields.Integer(string='No Urut', compute='_compute_sort', store=False) + + @api.depends('surat_id.line_ids.selected') + def _compute_sort(self): + for line in self: + if line.surat_id: + # Ambil semua line yang selected + selected_lines = line.surat_id.line_ids.filtered(lambda l: l.selected) + try: + line.sort = selected_lines.ids.index(line.id) + 1 + except ValueError: + line.sort = 0 + else: + line.sort = 0 -- cgit v1.2.3 From b1ed808b9b21187afe89bf5a44ecf635132d51da Mon Sep 17 00:00:00 2001 From: AndriFP <113114423+andrifp@users.noreply.github.com> Date: Fri, 12 Sep 2025 19:40:07 +0700 Subject: (andri) fix typo --- indoteknik_custom/models/approval_payment_term.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/approval_payment_term.py b/indoteknik_custom/models/approval_payment_term.py index 7cab91f1..449bd90b 100644 --- a/indoteknik_custom/models/approval_payment_term.py +++ b/indoteknik_custom/models/approval_payment_term.py @@ -173,7 +173,7 @@ class ApprovalPaymentTerm(models.Model): 'warning_stage': self.warning_stage, 'active_limit': self.active_limit, 'property_payment_term_id': self.property_payment_term_id.id, - 'is_locked_cbd': False, + 'is_cbd_locked': False, }) self.approve_date = datetime.utcnow() self.state = 'approved' -- cgit v1.2.3 From fc83d226a0b49c1d0423f6e3ccadd353b1c45218 Mon Sep 17 00:00:00 2001 From: AndriFP <113114423+andrifp@users.noreply.github.com> Date: Sat, 13 Sep 2025 10:59:33 +0700 Subject: (andri) add attachment di email --- indoteknik_custom/models/letter_receivable.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/letter_receivable.py b/indoteknik_custom/models/letter_receivable.py index 3823a57a..4f1395fe 100644 --- a/indoteknik_custom/models/letter_receivable.py +++ b/indoteknik_custom/models/letter_receivable.py @@ -7,6 +7,7 @@ import re import logging from datetime import timedelta import babel +import base64 _logger = logging.getLogger(__name__) @@ -203,11 +204,25 @@ class SuratPiutang(models.Model): .replace('${object.seven_days_after_sent_date}', self.seven_days_after_sent_date or '') \ .replace('${object.perihal}', perihal_text or '') + report = self.env.ref('indoteknik_custom.action_report_surat_piutang') + pdf_content, _ = report._render_qweb_pdf([self.id]) + attachment_base64 = base64.b64encode(pdf_content) + + attachment = self.env['ir.attachment'].create({ + 'name': f"Surat Piutang {self.name}.pdf", + 'type': 'binary', + 'datas': attachment_base64, + 'res_model': 'surat.piutang', + 'res_id': self.id, + 'mimetype': 'application/pdf', + }) + values = { 'subject': template.subject.replace('${object.name}', self.name or ''), 'email_to': self.tujuan_email, 'email_from': 'finance@indoteknik.co.id', 'body_html': body_html, + 'attachments': [(attachment.name, attachment.datas)], 'reply_to': 'finance@indoteknik.co.id', } -- cgit v1.2.3 From a627a71d911dbfb54cac7c8151331f218292a9fd Mon Sep 17 00:00:00 2001 From: AndriFP <113114423+andrifp@users.noreply.github.com> Date: Sat, 13 Sep 2025 13:12:54 +0700 Subject: (andri) add chatter + fix approve --- indoteknik_custom/models/letter_receivable.py | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/letter_receivable.py b/indoteknik_custom/models/letter_receivable.py index 4f1395fe..2bac3754 100644 --- a/indoteknik_custom/models/letter_receivable.py +++ b/indoteknik_custom/models/letter_receivable.py @@ -5,9 +5,10 @@ from odoo.tools import mail, formatLang from terbilang import Terbilang import re import logging -from datetime import timedelta +from datetime import datetime, timedelta import babel import base64 +import pytz _logger = logging.getLogger(__name__) @@ -120,23 +121,25 @@ class SuratPiutang(models.Model): raise ValidationError(_("Format email tidak valid: %s") % rec.tujuan_email) def action_approve(self): + wib = pytz.timezone('Asia/Jakarta') + now_wib = datetime.now(wib) + pimpinan_user_ids = [7] # Pak Akbar if self.env.user.id not in pimpinan_user_ids: raise UserError("Hanya Pimpinan yang berhak menyetujui tahap ini.") for rec in self: if rec.state == "approval_pimpinan": rec.state = "sent" - rec.send_date = fields.Datetime.now() - # Format tanggal + bulan (tanpa tahun) - month_map = { - 1: "Januari", 2: "Februari", 3: "Maret", 4: "April", - 5: "Mei", 6: "Juni", 7: "Juli", 8: "Agustus", - 9: "September", 10: "Oktober", 11: "November", 12: "Desember", - } - if rec.send_date: - target_date = rec.send_date.date() + timedelta(days=7) - rec.seven_days_after_sent_date = f"{target_date.day} {month_map[target_date.month]}" - + now_utc = now_wib.astimezone(pytz.UTC).replace(tzinfo=None) + rec.send_date = now_utc + rec.action_send_letter() + + self.env.user.notify_info( + message=f"Surat piutang {rec.name} berhasil dikirim ke {rec.partner_id.name} ({rec.tujuan_email})", + title="Informasi", + sticky=False + ) + def action_send_letter(self): self.ensure_one() -- cgit v1.2.3 From 064cdf03526540e0cde6b6740fc97764c003adff Mon Sep 17 00:00:00 2001 From: Miqdad Date: Sun, 14 Sep 2025 00:42:04 +0700 Subject: MO kalau sudah jadi PO tidak perlu approval purchasing manager --- indoteknik_custom/models/manufacturing.py | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/manufacturing.py b/indoteknik_custom/models/manufacturing.py index aea01362..3e4f386d 100644 --- a/indoteknik_custom/models/manufacturing.py +++ b/indoteknik_custom/models/manufacturing.py @@ -7,22 +7,24 @@ _logger = logging.getLogger(__name__) class Manufacturing(models.Model): _inherit = 'mrp.production' unbuild_counter = fields.Integer(string='Unbuild Counter', default=0, help='For restrict unbuild more than once') - + def action_confirm(self): if self._name != 'mrp.production': return super(Manufacturing, self).action_confirm() - if not self.env.user.is_purchasing_manager: - raise UserError("Hanya bisa di confirm oleh Purchasing Manager") - - # if self.location_src_id.id != 75: - # raise UserError('Component Location hanya bisa di AS/Stock') - # elif self.location_dest_id.id != 75: - # raise UserError('Finished Product Location hanya bisa di AS/Stock') - - result = super(Manufacturing, self).action_confirm() - return result - + for mo in self: + has_po_flag = bool(getattr(mo, 'is_po', False)) + has_po_link = bool(self.env['purchase.order'].search([ + ('manufacturing_id', '=', mo.id), + ('state', '!=', 'cancel') + ], limit=1)) + mo_has_po = has_po_flag or has_po_link + + if not mo_has_po and not self.env.user.is_purchasing_manager: + raise UserError("Hanya bisa di confirm oleh Purchasing Manager") + + return super(Manufacturing, self).action_confirm() + def button_mark_done(self): if self._name != 'mrp.production': return super(Manufacturing, self).button_mark_done() -- cgit v1.2.3 From e12d3f75b57abc6231566c486a114cd0bfc01b8e Mon Sep 17 00:00:00 2001 From: AndriFP <113114423+andrifp@users.noreply.github.com> Date: Sun, 14 Sep 2025 20:24:18 +0700 Subject: (andri) add button create surat piutang di view form unpaid inv monitoring --- indoteknik_custom/models/letter_receivable.py | 11 ++++++----- indoteknik_custom/models/unpaid_invoice_view.py | 13 +++++++++++++ 2 files changed, 19 insertions(+), 5 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/letter_receivable.py b/indoteknik_custom/models/letter_receivable.py index 2bac3754..17963232 100644 --- a/indoteknik_custom/models/letter_receivable.py +++ b/indoteknik_custom/models/letter_receivable.py @@ -125,11 +125,11 @@ class SuratPiutang(models.Model): now_wib = datetime.now(wib) pimpinan_user_ids = [7] # Pak Akbar - if self.env.user.id not in pimpinan_user_ids: - raise UserError("Hanya Pimpinan yang berhak menyetujui tahap ini.") + # if self.env.user.id not in pimpinan_user_ids: + # raise UserError("Hanya Pimpinan yang berhak menyetujui tahap ini.") for rec in self: if rec.state == "approval_pimpinan": - rec.state = "sent" + # rec.state = "sent" now_utc = now_wib.astimezone(pytz.UTC).replace(tzinfo=None) rec.send_date = now_utc rec.action_send_letter() @@ -191,7 +191,7 @@ class SuratPiutang(models.Model): Grand Total - {formatLang(self.env, grand_total, currency_obj=self.currency_id)} + {formatLang(self.env, grand_total, currency_obj=self.currency_id, monetary=True)} @@ -243,6 +243,7 @@ class SuratPiutang(models.Model): [('partner_id', '=', self.partner_id.id)], 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, 'invoice_id': inv.invoice_id.id, @@ -259,7 +260,7 @@ class SuratPiutang(models.Model): 'date_terima_tukar_faktur': inv.date_terima_tukar_faktur, 'invoice_user_id': inv.invoice_user_id.id, 'sale_id': inv.sale_id.id, - 'selected': False + 'selected': True if inv.invoice_id.id == selected_invoice_id else False, }) for inv in invoice_lines] self.line_ids = lines diff --git a/indoteknik_custom/models/unpaid_invoice_view.py b/indoteknik_custom/models/unpaid_invoice_view.py index f35261eb..25e04968 100644 --- a/indoteknik_custom/models/unpaid_invoice_view.py +++ b/indoteknik_custom/models/unpaid_invoice_view.py @@ -33,3 +33,16 @@ class UnpaidInvoiceView(models.Model): invoice_user_id = fields.Many2one('res.users', string='Salesperson') date_kirim_tukar_faktur = fields.Date(string='Kirim Faktur') sale_id = fields.Many2one('sale.order', string='Sale Order') + + def action_create_surat_piutang(self): + self.ensure_one() + return { + 'type': 'ir.actions.act_window', + 'res_model': 'surat.piutang', + 'view_mode': 'form', + 'target': 'current', + 'context': { + 'default_partner_id': self.partner_id.id, + 'default_selected_invoice_id': self.invoice_id.id, + } + } -- cgit v1.2.3 From 7748c35d64c802ff5fc3d93aaf41c465b1f78e10 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Mon, 15 Sep 2025 09:18:54 +0700 Subject: Update add date doc kirim --- indoteknik_custom/models/sj_tele.py | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sj_tele.py b/indoteknik_custom/models/sj_tele.py index c00279ec..43709f45 100644 --- a/indoteknik_custom/models/sj_tele.py +++ b/indoteknik_custom/models/sj_tele.py @@ -4,19 +4,21 @@ import requests import json import logging, subprocess import time +from collections import OrderedDict + +logger = logging.getLogger(__name__) -_logger = logging.getLogger(__name__) class SjTele(models.Model): _name = 'sj.tele' _description = 'sj.tele' - picking_id = fields.Many2one('stock.picking', string='Picking') sale_id = fields.Many2one('sale.order', string='Sales Order') picking_name = fields.Char(string='Picking Name') sale_name = fields.Char(string='Sale Name') create_date = fields.Datetime(string='Create Date') + date_doc_kirim = fields.Datetime(string='Tanggal Kirim SJ') # @api.model # def run_pentaho_carte(self): @@ -37,7 +39,7 @@ class SjTele(models.Model): # except Exception as e: # _logger.exception("Carte call failed: %s", e) # raise UserError(f"Gagal memanggil Carte: {e}") - + # time.sleep(3) # self.env['sj.tele'].sudo().woi() @@ -63,12 +65,29 @@ class SjTele(models.Model): return True lines = [] + groups = OrderedDict() + for rec in data: name = rec.picking_name or (rec.picking_id.name if rec.picking_id else '') - pid = rec.picking_id.id if rec.picking_id else '' + pid = rec.picking_id.id if rec.picking_id else '' so = rec.sale_id.name or rec.sale_name or '' + dttm = (rec.picking_id.date_doc_kirim if (rec.picking_id and rec.picking_id.date_doc_kirim) + else getattr(rec, 'date_doc_kirim', None)) + + # format header tanggal (string), tanpa konversi Waktu/WIB + if dttm: + date_header = dttm if isinstance(dttm, str) else fields.Datetime.to_string(dttm) + date_header = date_header[:10] + else: + date_header = '(Tidak ada tanggal kirim SJ)' + if name: - lines.append(f"{name} - {so} ({pid})") + groups.setdefault(date_header, []).append(f"- ({pid}) - {name} - {so}") + + # build output berurutan per tanggal + for header_date, items in groups.items(): + lines.append(header_date) + lines.extend(items) header = "Berikut merupakan nomor BU/OUT yang belum ada di Logbook SJ report:\n" text = header + "\n".join(lines) @@ -78,5 +97,5 @@ class SjTele(models.Model): json={'chat_id': chat_id_mqdd, 'text': text}) r.raise_for_status() except Exception as e: - _logger.exception("Gagal kirim Telegram: %s", e) + logger.exception("Gagal kirim Telegram: %s", e) return True \ No newline at end of file -- cgit v1.2.3 From 3bc157731d03e9beb41dab993461b2bd6dcb1953 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Mon, 15 Sep 2025 10:12:37 +0700 Subject: balikin --- indoteknik_custom/models/manufacturing.py | 32 +++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/manufacturing.py b/indoteknik_custom/models/manufacturing.py index 3e4f386d..f986fd4f 100644 --- a/indoteknik_custom/models/manufacturing.py +++ b/indoteknik_custom/models/manufacturing.py @@ -4,6 +4,7 @@ import logging _logger = logging.getLogger(__name__) + class Manufacturing(models.Model): _inherit = 'mrp.production' unbuild_counter = fields.Integer(string='Unbuild Counter', default=0, help='For restrict unbuild more than once') @@ -12,18 +13,16 @@ class Manufacturing(models.Model): if self._name != 'mrp.production': return super(Manufacturing, self).action_confirm() - for mo in self: - has_po_flag = bool(getattr(mo, 'is_po', False)) - has_po_link = bool(self.env['purchase.order'].search([ - ('manufacturing_id', '=', mo.id), - ('state', '!=', 'cancel') - ], limit=1)) - mo_has_po = has_po_flag or has_po_link + if not self.env.user.is_purchasing_manager: + raise UserError("Hanya bisa di confirm oleh Purchasing Manager") - if not mo_has_po and not self.env.user.is_purchasing_manager: - raise UserError("Hanya bisa di confirm oleh Purchasing Manager") + # if self.location_src_id.id != 75: + # raise UserError('Component Location hanya bisa di AS/Stock') + # elif self.location_dest_id.id != 75: + # raise UserError('Finished Product Location hanya bisa di AS/Stock') - return super(Manufacturing, self).action_confirm() + result = super(Manufacturing, self).action_confirm() + return result def button_mark_done(self): if self._name != 'mrp.production': @@ -31,29 +30,30 @@ class Manufacturing(models.Model): # Check product category if self.product_id.categ_id.name != 'Finish Good': raise UserError('Tidak bisa di complete karna product category bukan Unit / Finish Good') - + if self.sale_order and self.sale_order.state != 'sale': raise UserError( ('Tidak bisa Mark as Done.\nSales Order "%s" (Nomor: %s) belum dikonfirmasi.') % (self.sale_order.partner_id.name, self.sale_order.name) ) - + for line in self.move_raw_ids: # if line.quantity_done > 0 and line.quantity_done != self.product_uom_qty: # raise UserError('Qty Consume per Line tidak sama dengan Qty to Produce') if line.forecast_availability != line.product_uom_qty: - raise UserError('Qty Reserved belum sesuai dengan yang seharusnya, product: %s' % line.product_id.display_name) + raise UserError( + 'Qty Reserved belum sesuai dengan yang seharusnya, product: %s' % line.product_id.display_name) result = super(Manufacturing, self).button_mark_done() return result - + def button_unbuild(self): if self._name != 'mrp.production': return super(Manufacturing, self).button_unbuild() - + if self.unbuild_counter >= 1: raise UserError('Tidak bisa unbuild lebih dari 1 kali') - + self.unbuild_counter = self.unbuild_counter + 1 result = super(Manufacturing, self).button_unbuild() -- cgit v1.2.3 From 248c42efcc30cb55d1827bb7b1e438753ffd0898 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Mon, 15 Sep 2025 11:07:40 +0700 Subject: done --- indoteknik_custom/models/purchase_order.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/purchase_order.py b/indoteknik_custom/models/purchase_order.py index 18811b85..98cf6ff1 100755 --- a/indoteknik_custom/models/purchase_order.py +++ b/indoteknik_custom/models/purchase_order.py @@ -1066,8 +1066,11 @@ class PurchaseOrder(models.Model): # sticky=True # ) + has_bom = self.product_bom_id.id + has_manufacturing = self.manufacturing_id.id + if not self.from_apo: - if not self.matches_so and not self.env.user.is_purchasing_manager and not self.env.user.is_leader: + if not self.matches_so and not self.env.user.is_purchasing_manager and not self.env.user.is_leader and not has_bom and not has_manufacturing: raise UserError("Tidak ada link dengan SO, harus di confirm oleh Purchasing Manager") send_email = False -- cgit v1.2.3 From 2b61b810f5b12f32bf837ab34c12d832d0be12eb Mon Sep 17 00:00:00 2001 From: AndriFP <113114423+andrifp@users.noreply.github.com> Date: Mon, 15 Sep 2025 11:48:33 +0700 Subject: (andri) edit status dan rev validasi --- indoteknik_custom/models/letter_receivable.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/letter_receivable.py b/indoteknik_custom/models/letter_receivable.py index 17963232..fe598e50 100644 --- a/indoteknik_custom/models/letter_receivable.py +++ b/indoteknik_custom/models/letter_receivable.py @@ -31,8 +31,8 @@ class SuratPiutang(models.Model): line_ids = fields.One2many("surat.piutang.line", "surat_id", string="Invoice Lines") state = fields.Selection([ ("draft", "Draft"), - ("approval_pimpinan", "Menunggu Approval Pimpinan"), - ("sent", "Sent") + ("waiting_approval", "Menunggu Approval"), + ("sent", "Approved & Sent") ], default="draft", tracking=True) send_date = fields.Datetime(string="Tanggal Kirim", tracking=True) seven_days_after_sent_date = fields.Char(string="7 Hari Setelah Tanggal Kirim", tracking=True) @@ -128,8 +128,11 @@ class SuratPiutang(models.Model): # if self.env.user.id not in pimpinan_user_ids: # raise UserError("Hanya Pimpinan yang berhak menyetujui tahap ini.") for rec in self: - if rec.state == "approval_pimpinan": - # rec.state = "sent" + if rec.state == "waiting_approval": + if rec.perihal in ("sp1", "sp2", "sp3"): + if self.env.user.id not in pimpinan_user_ids: + raise UserError("Hanya Pimpinan yang berhak menyetujui surat peringatan piutang (SP1, SP2, SP3).") + rec.state = "sent" now_utc = now_wib.astimezone(pytz.UTC).replace(tzinfo=None) rec.send_date = now_utc rec.action_send_letter() @@ -340,7 +343,7 @@ class SuratPiutang(models.Model): tahun = today.strftime("%y") vals["name"] = f"{seq}/LO/FAT/IDG/{bulan_romawi}/{tahun}" - vals["state"] = "approval_pimpinan" + vals["state"] = "waiting_approval" return super().create(vals) class SuratPiutangLine(models.Model): -- cgit v1.2.3 From 1afdf5c126bc41ece2e197610a340aab1d102c58 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Mon, 15 Sep 2025 12:37:32 +0700 Subject: add line no --- indoteknik_custom/models/logbook_sj.py | 5 ++- indoteknik_custom/models/report_logbook_sj.py | 58 ++++++++++++++++++++++++++- 2 files changed, 60 insertions(+), 3 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/logbook_sj.py b/indoteknik_custom/models/logbook_sj.py index 75b2622f..0cda9c8b 100644 --- a/indoteknik_custom/models/logbook_sj.py +++ b/indoteknik_custom/models/logbook_sj.py @@ -24,6 +24,7 @@ class LogbookSJ(models.TransientModel): } report_logbook = self.env['report.logbook.sj'].create([parameters_header]) + seq=1 for line in logbook_line: picking = self.env['stock.picking'].search([('picking_code', '=', line.name)], limit=1) if not picking: @@ -43,9 +44,11 @@ class LogbookSJ(models.TransientModel): 'tracking_no': stock.delivery_tracking_no, 'partner_id': parent_id, 'report_logbook_sj_id': report_logbook.id, - 'note': line.note + 'note': line.note, + 'line_num': seq } self.env['report.logbook.sj.line'].create([data]) + seq += 1 report_logbook_ids.append(report_logbook.id) line.unlink() diff --git a/indoteknik_custom/models/report_logbook_sj.py b/indoteknik_custom/models/report_logbook_sj.py index 17119c12..66edbf99 100644 --- a/indoteknik_custom/models/report_logbook_sj.py +++ b/indoteknik_custom/models/report_logbook_sj.py @@ -1,3 +1,5 @@ +from operator import index + from odoo import models, fields, api from odoo.exceptions import UserError from pytz import timezone @@ -60,9 +62,30 @@ class ReportLogbookSJ(models.Model): self.state = 'terima_semua' else: raise UserError('Hanya Accounting yang bisa Approve') - + + + def write(self, vals): + res = super(ReportLogbookSJ, self).write(vals) + if 'report_logbook_sj_line' in vals or any(f in vals for f in ()): + self._resequence_lines() + return res + + def _resequence_lines(self): + for rec in self: + lines = rec.report_logbook_sj_line.sorted(key=lambda l: (l.line_num or 0, l.id)) + for idx, line in enumerate(lines, start=1): + if line.line_num != idx: + line.line_num = idx + + @api.onchange('report_logbook_sj_line') + def _onchange_report_logbook_sj_line(self): + self._resequence_lines() + +from odoo import models, fields, api + class ReportLogbookSJLine(models.Model): _name = 'report.logbook.sj.line' + _order = 'sequence, id' # urut default di UI & ORM (drag pakai sequence) name = fields.Char(string='SJ Number') driver_id = fields.Many2one(comodel_name='res.users', string='Driver') @@ -70,10 +93,41 @@ class ReportLogbookSJLine(models.Model): arrival_date = fields.Char(string='Arrival Date') carrier_id = fields.Many2one('delivery.carrier', string='Shipping Method') tracking_no = fields.Char(string='Tracking No') - logbook_sj_id = fields.Many2one('report.logbook.sj', string='Logbook SJ') # Corrected model name + + # NOTE: field ini duplikat relasi; pakai salah satu saja. + # kamu boleh hapus logbook_sj_id kalau tidak dipakai di tempat lain. + logbook_sj_id = fields.Many2one('report.logbook.sj', string='Logbook SJ') + partner_id = fields.Many2one('res.partner', string='Customer') picking_id = fields.Many2one('stock.picking', string='Picking') sale_id = fields.Many2one('sale.order', string='Sale Order') + report_logbook_sj_id = fields.Many2one('report.logbook.sj', string='Logbook SJ') not_exist = fields.Boolean(string='Not Exist') note = fields.Char(string='Note') + + sequence = fields.Integer(string='Sequence', default=0, index=True) + + line_num = fields.Integer(string='No', compute='_compute_line_num', store=False) + + @api.depends( + 'report_logbook_sj_id.report_logbook_sj_line', + 'report_logbook_sj_id.report_logbook_sj_line.sequence' + ) + def _compute_line_num(self): + for parent in self.mapped('report_logbook_sj_id'): + lines = parent.report_logbook_sj_line.sorted(key=lambda l: (l.sequence or 0, l.id)) + for i, l in enumerate(lines, start=1): + l.line_num = i + for rec in self.filtered(lambda r: not r.report_logbook_sj_id): + rec.line_num = 0 + + @api.model + def create(self, vals): + if not vals.get('sequence') and vals.get('report_logbook_sj_id'): + last = self.search( + [('report_logbook_sj_id', '=', vals['report_logbook_sj_id'])], + order='sequence desc, id desc', limit=1 + ) + vals['sequence'] = (last.sequence or 0) + 1 + return super().create(vals) -- cgit v1.2.3 From a47bdc61945b8ab153d80590f06975210f8d2a80 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Mon, 15 Sep 2025 13:40:17 +0700 Subject: midtrans prod --- indoteknik_custom/models/sale_order.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 630f25ba..77fee068 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -1900,10 +1900,10 @@ class SaleOrder(models.Model): # raise UserError('Kelurahan Real Delivery Address harus diisi') def generate_payment_link_midtrans_sales_order(self): - midtrans_url = 'https://app.sandbox.midtrans.com/snap/v1/transactions' # dev - sandbox - midtrans_auth = 'Basic U0ItTWlkLXNlcnZlci1uLVY3ZDJjMlpCMFNWRUQyOU95Q1dWWXA6' # dev - sandbox - # midtrans_url = 'https://app.midtrans.com/snap/v1/transactions' # production - # midtrans_auth = 'Basic TWlkLXNlcnZlci1SbGMxZ2gzWGpSVW5scl9JblZzTV9OTnU6' # production + # midtrans_url = 'https://app.sandbox.midtrans.com/snap/v1/transactions' # dev - sandbox + # midtrans_auth = 'Basic U0ItTWlkLXNlcnZlci1uLVY3ZDJjMlpCMFNWRUQyOU95Q1dWWXA6' # dev - sandbox + midtrans_url = 'https://app.midtrans.com/snap/v1/transactions' # production + midtrans_auth = 'Basic TWlkLXNlcnZlci1SbGMxZ2gzWGpSVW5scl9JblZzTV9OTnU6' # production so_number = self.name so_number = so_number.replace('/', '-') -- cgit v1.2.3 From 1f63169f38675adf23a1e4a66b31711f90721f39 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Mon, 15 Sep 2025 13:41:11 +0700 Subject: midtrans prod --- indoteknik_custom/models/sale_order.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index e7dd582d..2ae2d647 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -1935,8 +1935,8 @@ class SaleOrder(models.Model): } # ==== ENV ==== - check_url = f'https://api.sandbox.midtrans.com/v2/{so_number}/status' # dev - sandbox - # check_url = f'https://api.midtrans.com/v2/{so_number}/status' # production + # check_url = f'https://api.sandbox.midtrans.com/v2/{so_number}/status' # dev - sandbox + check_url = f'https://api.midtrans.com/v2/{so_number}/status' # production # ============================================= check_response = requests.get(check_url, headers=headers) -- cgit v1.2.3 From 570087805ec46b8af7651187cfcf0ecef2733912 Mon Sep 17 00:00:00 2001 From: AndriFP <113114423+andrifp@users.noreply.github.com> Date: Mon, 15 Sep 2025 14:45:43 +0700 Subject: (andri) rev penamaan surat dan doc --- indoteknik_custom/models/letter_receivable.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/letter_receivable.py b/indoteknik_custom/models/letter_receivable.py index fe598e50..18485f01 100644 --- a/indoteknik_custom/models/letter_receivable.py +++ b/indoteknik_custom/models/letter_receivable.py @@ -215,7 +215,7 @@ class SuratPiutang(models.Model): attachment_base64 = base64.b64encode(pdf_content) attachment = self.env['ir.attachment'].create({ - 'name': f"Surat Piutang {self.name}.pdf", + 'name': f"{self.perihal_label} - {self.partner_id.name}.pdf", 'type': 'binary', 'datas': attachment_base64, 'res_model': 'surat.piutang', @@ -224,7 +224,8 @@ class SuratPiutang(models.Model): }) values = { - 'subject': template.subject.replace('${object.name}', self.name or ''), + # 'subject': template.subject.replace('${object.name}', self.name or ''), + 'subject': perihal_map.get(self.perihal, self.perihal or '') + " - " + (self.partner_id.name or ''), 'email_to': self.tujuan_email, 'email_from': 'finance@indoteknik.co.id', 'body_html': body_html, -- cgit v1.2.3 From 7b35f872704d12fc7ba60581d4f10ce4ded00763 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Mon, 15 Sep 2025 15:06:34 +0700 Subject: lepas validasi --- indoteknik_custom/models/tukar_guling.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/tukar_guling.py b/indoteknik_custom/models/tukar_guling.py index d718ba0f..f435dd9f 100644 --- a/indoteknik_custom/models/tukar_guling.py +++ b/indoteknik_custom/models/tukar_guling.py @@ -492,14 +492,14 @@ class TukarGuling(models.Model): self.ensure_one() self._check_not_allow_tukar_guling_on_bu_pick() - existing_tukar_guling = self.env['tukar.guling'].search([ - ('operations', '=', self.operations.id), - ('id', '!=', self.id), - ('state', '!=', 'cancel'), - ], limit=1) - - if existing_tukar_guling: - raise UserError("BU ini sudah pernah diretur oleh dokumen %s." % existing_tukar_guling.name) + # existing_tukar_guling = self.env['tukar.guling'].search([ + # ('operations', '=', self.operations.id), + # ('id', '!=', self.id), + # ('state', '!=', 'cancel'), + # ], limit=1) + # + # if existing_tukar_guling: + # raise UserError("BU ini sudah pernah diretur oleh dokumen %s." % existing_tukar_guling.name) picking = self.operations if picking.picking_type_id.id == 30 and self.return_type == 'tukar_guling': raise UserError("❌ BU/PICK tidak boleh di retur tukar guling") -- cgit v1.2.3 From 60ef4aa56889985028a75cc927db80c36bc21338 Mon Sep 17 00:00:00 2001 From: AndriFP <113114423+andrifp@users.noreply.github.com> Date: Mon, 15 Sep 2025 15:31:31 +0700 Subject: (andri) add message post after refresh inv --- indoteknik_custom/models/letter_receivable.py | 3 +++ 1 file changed, 3 insertions(+) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/letter_receivable.py b/indoteknik_custom/models/letter_receivable.py index 18485f01..79a4a3e0 100644 --- a/indoteknik_custom/models/letter_receivable.py +++ b/indoteknik_custom/models/letter_receivable.py @@ -333,6 +333,9 @@ class SuratPiutang(models.Model): # Hapus line yang tidak ada lagi di unpaid view rec.line_ids.filtered(lambda l: l.invoice_id.id not in new_invoice_ids).unlink() + rec.message_post( + body=f"Line Invoices diperbarui. Total line saat ini: {len(rec.line_ids)}" + ) @api.model def create(self, vals): -- cgit v1.2.3 From d7647af478bfe2c1b60f00ff2e13327c0ec09be2 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Mon, 15 Sep 2025 16:01:38 +0700 Subject: push --- indoteknik_custom/models/sale_order.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index c767dd04..8f49b579 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -2644,7 +2644,7 @@ class SaleOrder(models.Model): if user.is_leader or user.is_sales_manager: return True - if user.id in (3401, 20, 3988): # admin (fida, nabila, ninda) + if user.id in (3401, 20, 3988, 17340): # admin (fida, nabila, ninda) return True if self.env.context.get("ask_approval") and user.id in (3401, 20, 3988): -- cgit v1.2.3 From 1ca068669ba7e255e8a69d981aba513dcfeedd40 Mon Sep 17 00:00:00 2001 From: it-fixcomart Date: Mon, 15 Sep 2025 16:23:48 +0700 Subject: fix validasi refund --- indoteknik_custom/models/refund_sale_order.py | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/refund_sale_order.py b/indoteknik_custom/models/refund_sale_order.py index f511ed5d..9b14243b 100644 --- a/indoteknik_custom/models/refund_sale_order.py +++ b/indoteknik_custom/models/refund_sale_order.py @@ -56,6 +56,7 @@ class RefundSaleOrder(models.Model): refund_type = fields.Selection([ ('barang_kosong_sebagian', 'Refund Barang Kosong Sebagian'), + ('barang_kosong_indent', 'Refund Barang Kosong Jika ada Indent'), ('barang_kosong', 'Refund Barang Kosong Full'), ('uang', 'Refund Lebih Bayar'), ('retur_half', 'Refund Retur Sebagian'), @@ -77,7 +78,7 @@ class RefundSaleOrder(models.Model): 'account.move', string="Journal Payment", copy=False, - help="Pilih transaksi salah transfer dari jurnal Uang Muka (journal_id=11) yang tidak terkait SO." + help="Pilih transaksi salah transfer dari jurnal Uang Muka yang tidak terkait SO." ) tukar_guling_count = fields.Integer( @@ -297,6 +298,14 @@ class RefundSaleOrder(models.Model): total_invoice = sum(self.env['account.move'].browse(invoice_ids).mapped('amount_total_signed')) if invoice_ids else 0.0 vals['total_invoice'] = total_invoice amount_refund = vals.get('amount_refund', 0.0) + can_refund = sisa_uang_masuk - total_invoice + + if can_refund > amount_refund or can_refund == 0.0: + raise ValidationError( + _("Maksimal refund yang bisa dilakukan adalah sebesar %s. " + "Silakan sesuaikan jumlah refund.") % (can_refund) + ) + if amount_refund <= 0.00: raise ValidationError('Total Refund harus lebih dari 0 jika ingin mengajukan refund') @@ -393,9 +402,16 @@ class RefundSaleOrder(models.Model): total_invoice = sum(self.env['account.move'].browse(invoice_ids).mapped('amount_total_signed')) vals['total_invoice'] = total_invoice uang_masuk = rec.uang_masuk + can_refund = uang_masuk - total_invoice amount_refund = vals.get('amount_refund', rec.amount_refund) + if amount_refund > can_refund: + raise ValidationError( + _("Maksimal refund yang bisa dilakukan adalah sebesar %s. " + "Silakan sesuaikan jumlah refund.") % (can_refund) + ) + if amount_refund <= 0: raise ValidationError("Total Refund harus lebih dari 0.") @@ -829,7 +845,7 @@ class RefundSaleOrder(models.Model): # Normalisasi refund_type_label = refund_type_label.upper() - if refund.refund_type in ['barang_kosong', 'barang_kosong_sebagian']: + if refund.refund_type in ['barang_kosong', 'barang_kosong_sebagian', 'barang_kosong_indent']: refund_type_label = "REFUND BARANG KOSONG" elif refund.refund_type in ['retur_half', 'retur']: refund_type_label = "REFUND RETUR BARANG" -- cgit v1.2.3 From ada4b39703d079a92da5c6ad96ad715df13b67de Mon Sep 17 00:00:00 2001 From: it-fixcomart Date: Mon, 15 Sep 2025 17:08:09 +0700 Subject: fix validasi refund --- indoteknik_custom/models/refund_sale_order.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/refund_sale_order.py b/indoteknik_custom/models/refund_sale_order.py index 9b14243b..51d42514 100644 --- a/indoteknik_custom/models/refund_sale_order.py +++ b/indoteknik_custom/models/refund_sale_order.py @@ -300,7 +300,7 @@ class RefundSaleOrder(models.Model): amount_refund = vals.get('amount_refund', 0.0) can_refund = sisa_uang_masuk - total_invoice - if can_refund > amount_refund or can_refund == 0.0: + if amount_refund > can_refund or can_refund == 0.0: raise ValidationError( _("Maksimal refund yang bisa dilakukan adalah sebesar %s. " "Silakan sesuaikan jumlah refund.") % (can_refund) -- cgit v1.2.3 From 4c305c1ac192d5610dedbc90723281dd09bc0a97 Mon Sep 17 00:00:00 2001 From: it-fixcomart Date: Mon, 15 Sep 2025 17:14:22 +0700 Subject: fix validasi refund --- indoteknik_custom/models/refund_sale_order.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/refund_sale_order.py b/indoteknik_custom/models/refund_sale_order.py index 51d42514..55128080 100644 --- a/indoteknik_custom/models/refund_sale_order.py +++ b/indoteknik_custom/models/refund_sale_order.py @@ -56,7 +56,6 @@ class RefundSaleOrder(models.Model): refund_type = fields.Selection([ ('barang_kosong_sebagian', 'Refund Barang Kosong Sebagian'), - ('barang_kosong_indent', 'Refund Barang Kosong Jika ada Indent'), ('barang_kosong', 'Refund Barang Kosong Full'), ('uang', 'Refund Lebih Bayar'), ('retur_half', 'Refund Retur Sebagian'), @@ -845,7 +844,7 @@ class RefundSaleOrder(models.Model): # Normalisasi refund_type_label = refund_type_label.upper() - if refund.refund_type in ['barang_kosong', 'barang_kosong_sebagian', 'barang_kosong_indent']: + if refund.refund_type in ['barang_kosong', 'barang_kosong_sebagian']: refund_type_label = "REFUND BARANG KOSONG" elif refund.refund_type in ['retur_half', 'retur']: refund_type_label = "REFUND RETUR BARANG" -- cgit v1.2.3 From 78a4f924fa9ba38c58954b5840632806b04fff19 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Mon, 15 Sep 2025 18:08:20 +0700 Subject: kembaliinn validasi --- indoteknik_custom/models/tukar_guling.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/tukar_guling.py b/indoteknik_custom/models/tukar_guling.py index f435dd9f..d718ba0f 100644 --- a/indoteknik_custom/models/tukar_guling.py +++ b/indoteknik_custom/models/tukar_guling.py @@ -492,14 +492,14 @@ class TukarGuling(models.Model): self.ensure_one() self._check_not_allow_tukar_guling_on_bu_pick() - # existing_tukar_guling = self.env['tukar.guling'].search([ - # ('operations', '=', self.operations.id), - # ('id', '!=', self.id), - # ('state', '!=', 'cancel'), - # ], limit=1) - # - # if existing_tukar_guling: - # raise UserError("BU ini sudah pernah diretur oleh dokumen %s." % existing_tukar_guling.name) + existing_tukar_guling = self.env['tukar.guling'].search([ + ('operations', '=', self.operations.id), + ('id', '!=', self.id), + ('state', '!=', 'cancel'), + ], limit=1) + + if existing_tukar_guling: + raise UserError("BU ini sudah pernah diretur oleh dokumen %s." % existing_tukar_guling.name) picking = self.operations if picking.picking_type_id.id == 30 and self.return_type == 'tukar_guling': raise UserError("❌ BU/PICK tidak boleh di retur tukar guling") -- cgit v1.2.3 From 7fe2c169a448df17c9395dfae20c524e94aa8bfc Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Tue, 16 Sep 2025 09:27:32 +0700 Subject: push --- indoteknik_custom/models/purchase_order.py | 2 +- indoteknik_custom/models/purchase_order_line.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/purchase_order.py b/indoteknik_custom/models/purchase_order.py index 98cf6ff1..4636c243 100755 --- a/indoteknik_custom/models/purchase_order.py +++ b/indoteknik_custom/models/purchase_order.py @@ -137,7 +137,7 @@ class PurchaseOrder(models.Model): def _compute_date_planned(self): """ date_planned = the earliest date_planned across all order lines. """ for order in self: - order.date_planned = False + order.date_planned = order.date_planned @api.constrains('date_planned') def constrains_date_planned(self): diff --git a/indoteknik_custom/models/purchase_order_line.py b/indoteknik_custom/models/purchase_order_line.py index 315795d5..a3c3a33b 100755 --- a/indoteknik_custom/models/purchase_order_line.py +++ b/indoteknik_custom/models/purchase_order_line.py @@ -50,6 +50,7 @@ class PurchaseOrderLine(models.Model): cost_service_per_item = fields.Float(string='Biaya Jasa Per Item', compute='_compute_doc_delivery_amt') contribution_cost_service = fields.Float(string='Contribution Cost Service', compute='_compute_doc_delivery_amt') ending_price = fields.Float(string='Ending Price', compute='_compute_doc_delivery_amt') + show_description = fields.Boolean(string='Show Description', help="Show Description when print po", default=True) def _compute_doc_delivery_amt(self): for line in self: -- cgit v1.2.3 From f48a848147216d1cbf736d6926ab7aebffb0deba Mon Sep 17 00:00:00 2001 From: Miqdad Date: Tue, 16 Sep 2025 11:31:42 +0700 Subject: add help and change return type --- indoteknik_custom/models/tukar_guling.py | 24 ++++++++++++------------ indoteknik_custom/models/tukar_guling_po.py | 24 ++++++++++++------------ 2 files changed, 24 insertions(+), 24 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/tukar_guling.py b/indoteknik_custom/models/tukar_guling.py index d718ba0f..c683f75a 100644 --- a/indoteknik_custom/models/tukar_guling.py +++ b/indoteknik_custom/models/tukar_guling.py @@ -61,7 +61,7 @@ class TukarGuling(models.Model): notes = fields.Text('Notes') return_type = fields.Selection(String='Return Type', selection=[ ('tukar_guling', 'Tukar Guling'), # -> barang yang sama - ('revisi_so', 'Revisi SO')], required=True, tracking=3) + ('retur_so', 'Retur SO')], required=True, tracking=3, help='Retur SO (ORT-SRT),\n Tukar Guling (ORT-SRT-PICK-OUT)') state = fields.Selection(string='Status', selection=[ ('draft', 'Draft'), ('approval_sales', ' Approval Sales'), @@ -169,7 +169,7 @@ class TukarGuling(models.Model): raise UserError("❌ Picking type harus BU/OUT atau BU/PICK") for rec in self: if rec.operations and rec.operations.picking_type_id.id == 30: - rec.return_type = 'revisi_so' + rec.return_type = 'retur_so' if self.operations: from_return_picking = self.env.context.get('from_return_picking', False) or \ @@ -315,7 +315,7 @@ class TukarGuling(models.Model): @api.constrains('return_type', 'operations') def _check_required_bu_fields(self): for record in self: - if record.return_type in ['revisi_so', 'tukar_guling'] and not record.operations: + if record.return_type in ['retur_so', 'tukar_guling'] and not record.operations: raise ValidationError("Operations harus diisi") @api.constrains('line_ids', 'state') @@ -352,16 +352,16 @@ class TukarGuling(models.Model): # ('state', '!=', 'cancel') # ]) > 0 - # def _check_invoice_on_revisi_so(self): + # def _check_invoice_on_retur_so(self): # for record in self: - # if record.return_type == 'revisi_so' and record.origin: + # if record.return_type == 'retur_so' and record.origin: # invoices = self.env['account.move'].search([ # ('invoice_origin', 'ilike', record.origin), # ('state', 'not in', ['draft', 'cancel']) # ]) # if invoices: # raise ValidationError( - # _("Tidak bisa memilih Return Type 'Revisi SO' karena dokumen %s sudah dibuat invoice.") % record.origin + # _("Tidak bisa memilih Return Type 'Retur SO' karena dokumen %s sudah dibuat invoice.") % record.origin # ) @@ -414,7 +414,7 @@ class TukarGuling(models.Model): self.ensure_one() if self.operations.picking_type_id.id not in [29, 30]: raise UserError("❌ Picking type harus BU/OUT atau BU/PICK") - # self._check_invoice_on_revisi_so() + # self._check_invoice_on_retur_so() operasi = self.operations.picking_type_id.id tipe = self.return_type pp = vals.get('return_type', tipe) @@ -530,7 +530,7 @@ class TukarGuling(models.Model): raise UserError( _("Qty di Koli tidak sesuai dengan qty retur untuk produk %s") % line.product_id.display_name ) - # self._check_invoice_on_revisi_so() + # self._check_invoice_on_retur_so() self._validate_product_lines() if self.state != 'draft': @@ -553,7 +553,7 @@ class TukarGuling(models.Model): self.state = 'done' # OUT revisi SO - elif self.operations.picking_type_id.id == 29 and self.return_type == 'revisi_so': + elif self.operations.picking_type_id.id == 29 and self.return_type == 'retur_so': total_ort = self.env['stock.picking'].search_count([ ('tukar_guling_id', '=', self.id), ('picking_type_id', '=', 74), @@ -567,7 +567,7 @@ class TukarGuling(models.Model): self.state = 'done' # PICK revisi SO - elif self.operations.picking_type_id.id == 30 and self.return_type == 'revisi_so': + elif self.operations.picking_type_id.id == 30 and self.return_type == 'retur_so': done_ort = self.env['stock.picking'].search([ ('tukar_guling_id', '=', self.id), ('picking_type_id', '=', 74), @@ -581,7 +581,7 @@ class TukarGuling(models.Model): def action_approve(self): self.ensure_one() self._validate_product_lines() - # self._check_invoice_on_revisi_so() + # self._check_invoice_on_retur_so() self._check_not_allow_tukar_guling_on_bu_pick() operasi = self.operations.picking_type_id.id @@ -631,7 +631,7 @@ class TukarGuling(models.Model): elif rec.state == 'approval_finance': if not rec.env.user.has_group('indoteknik_custom.group_role_fat'): raise UserError("Hanya Finance Manager yang boleh approve tahap ini.") - # rec._check_invoice_on_revisi_so() + # rec._check_invoice_on_retur_so() rec.set_opt() rec.state = 'approval_logistic' rec.date_finance = now diff --git a/indoteknik_custom/models/tukar_guling_po.py b/indoteknik_custom/models/tukar_guling_po.py index f2f37606..f61b3828 100644 --- a/indoteknik_custom/models/tukar_guling_po.py +++ b/indoteknik_custom/models/tukar_guling_po.py @@ -38,9 +38,9 @@ class TukarGulingPO(models.Model): ) ba_num = fields.Char('Nomor BA', tracking=3) return_type = fields.Selection([ - ('revisi_po', 'Revisi PO'), + ('retur_po', 'Retur PO'), ('tukar_guling', 'Tukar Guling'), - ], string='Return Type', required=True, tracking=3) + ], string='Return Type', required=True, tracking=3, help='Retur PO (VRT-PRT),\n Tukar Guling (VRT-PRT-INPUT-PUT') notes = fields.Text('Notes', tracking=3) tukar_guling_po_id = fields.Many2one('tukar.guling.po', string='Tukar Guling PO', ondelete='cascade') line_ids = fields.One2many('tukar.guling.line.po', 'tukar_guling_po_id', string='Product Lines', tracking=3) @@ -143,9 +143,9 @@ class TukarGulingPO(models.Model): return res - # def _check_bill_on_revisi_po(self): + # def _check_bill_on_retur_po(self): # for record in self: - # if record.return_type == 'revisi_po' and record.origin: + # if record.return_type == 'retur_po' and record.origin: # bills = self.env['account.move'].search([ # ('invoice_origin', 'ilike', record.origin), # ('move_type', '=', 'in_invoice'), # hanya vendor bill @@ -153,7 +153,7 @@ class TukarGulingPO(models.Model): # ]) # if bills: # raise ValidationError( - # _("Tidak bisa memilih Return Type 'Revisi PO' karena PO %s sudah dibuat vendor bill. Harus Cancel Jika ingin melanjutkan") % record.origin + # _("Tidak bisa memilih Return Type 'Retur PO' karena PO %s sudah dibuat vendor bill. Harus Cancel Jika ingin melanjutkan") % record.origin # ) @api.onchange('operations') @@ -284,7 +284,7 @@ class TukarGulingPO(models.Model): @api.constrains('return_type', 'operations') def _check_required_bu_fields(self): for record in self: - if record.return_type in ['revisi_po', 'tukar_guling'] and not record.operations: + if record.return_type in ['retur_po', 'tukar_guling'] and not record.operations: raise ValidationError("Operations harus diisi") @api.constrains('line_ids', 'state') @@ -350,7 +350,7 @@ class TukarGulingPO(models.Model): def write(self, vals): if self.operations.picking_type_id.id not in [75, 28]: raise UserError("❌ Tidak bisa retur bukan BU/INPUT atau BU/PUT!") - # self._check_bill_on_revisi_po() + # self._check_bill_on_retur_po() tipe = vals.get('return_type', self.return_type) if self.operations and self.operations.picking_type_id.id == 28 and tipe == 'tukar_guling': @@ -418,7 +418,7 @@ class TukarGulingPO(models.Model): def action_submit(self): self.ensure_one() - # self._check_bill_on_revisi_po() + # self._check_bill_on_retur_po() self._validate_product_lines() self._check_not_allow_tukar_guling_on_bu_input() @@ -463,7 +463,7 @@ class TukarGulingPO(models.Model): def action_approve(self): self.ensure_one() self._validate_product_lines() - # self._check_bill_on_revisi_po() + # self._check_bill_on_retur_po() self._check_not_allow_tukar_guling_on_bu_input() if not self.operations: @@ -485,7 +485,7 @@ class TukarGulingPO(models.Model): elif rec.state == 'approval_finance': if not rec.env.user.has_group('indoteknik_custom.group_role_fat'): raise UserError("Hanya Finance yang boleh approve tahap ini.") - # rec._check_bill_on_revisi_po() + # rec._check_bill_on_retur_po() rec.set_opt() rec.state = 'approval_logistic' rec.date_finance = now @@ -501,7 +501,7 @@ class TukarGulingPO(models.Model): def update_doc_state(self): # bu input rev po - if self.operations.picking_type_id.id == 28 and self.return_type == 'revisi_po': + if self.operations.picking_type_id.id == 28 and self.return_type == 'retur_po': prt = self.env['stock.picking'].search([ ('tukar_guling_po_id', '=', self.id), ('state', '=', 'done'), @@ -510,7 +510,7 @@ class TukarGulingPO(models.Model): if self.state == 'approved' and prt: self.state = 'done' # bu put rev po - elif self.operations.picking_type_id.id == 75 and self.return_type == 'revisi_po': + elif self.operations.picking_type_id.id == 75 and self.return_type == 'retur_po': total_prt = self.env['stock.picking'].search_count([ ('tukar_guling_po_id', '=', self.id), ('picking_type_id.id', '=', 76) -- cgit v1.2.3 From bd6165ebb7c6df46117f7ec1cd50a27e7db5a39d Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Tue, 16 Sep 2025 13:09:18 +0700 Subject: command validation cbd on override create invoice --- indoteknik_custom/models/sale_order.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 8f49b579..136663ba 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -1674,7 +1674,7 @@ class SaleOrder(models.Model): rec.expected_ready_to_ship = eta_date @api.depends("order_line.product_id", "date_order") - def _compute_etrts_date(self): # Function to calculate Estimated Ready To Ship Date + def _compute_etrts_date(self): self._calculate_etrts_date() @@ -1844,9 +1844,9 @@ class SaleOrder(models.Model): def override_allow_create_invoice(self): if not self.env.user.is_accounting: raise UserError('Hanya Finance Accounting yang dapat klik tombol ini') - for term in self.payment_term_id.line_ids: - if term.days > 0: - raise UserError('Hanya dapat digunakan pada Cash Before Delivery') + # for term in self.payment_term_id.line_ids: + # if term.days > 0: + # raise UserError('Hanya dapat digunakan pada Cash Before Delivery') for line in self.order_line: line.qty_to_invoice = line.product_uom_qty -- cgit v1.2.3 From 19a286c49f2ae626d9d6c12829d7bc094ba563b2 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Tue, 16 Sep 2025 14:13:10 +0700 Subject: push --- indoteknik_custom/models/purchase_order.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/purchase_order.py b/indoteknik_custom/models/purchase_order.py index 4636c243..e7e5c382 100755 --- a/indoteknik_custom/models/purchase_order.py +++ b/indoteknik_custom/models/purchase_order.py @@ -6,6 +6,7 @@ import logging from pytz import timezone, utc import io import base64 +from odoo.tools import lazy_property try: from odoo.tools.misc import xlsxwriter except ImportError: @@ -115,6 +116,19 @@ class PurchaseOrder(models.Model): compute='_compute_complete_bu_in_count' ) + show_description = fields.Boolean( + string='Show Description' + ) + + @api.onchange('show_description') + def onchange_show_description(self): + if self.show_description == True: + for line in self.order_line: + line.show_description = True + else: + for line in self.order_line: + line.show_description = False + def _compute_complete_bu_in_count(self): for order in self: if order.state not in ['done', 'cancel']: -- cgit v1.2.3 From 540136f8096f090b481d139c58c3f36c28aa69bb Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 16 Sep 2025 15:01:02 +0700 Subject: (andri) add payment diff + fix onchange ketika pilih partner --- indoteknik_custom/models/down_payment.py | 1088 ----------------------- indoteknik_custom/models/letter_receivable.py | 12 +- indoteknik_custom/models/unpaid_invoice_view.py | 7 + 3 files changed, 14 insertions(+), 1093 deletions(-) delete mode 100644 indoteknik_custom/models/down_payment.py (limited to 'indoteknik_custom/models') 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 dikonfirmasi oleh AP.", - 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 Departement oleh {self.env.user.name} " - f"pada {formatted_date}." - ) - - 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 AP oleh {self.env.user.name} " - f"pada {formatted_date}." - ) - - 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 Pimpinan oleh {self.env.user.name} " - f"pada {formatted_date}." - ) - - else: - raise UserError("Status saat ini tidak bisa di-approve lagi.") - - # rec.message_post(body=f"Approval oleh {self.env.user.name} pada tahap {rec.status}.") - - - 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 {new_status.capitalize()} 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 {dict(self._fields['done_status'].selection).get(self.done_status)} 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: {move.name}.") - - 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 Departement oleh {self.env.user.name} " - f"pada {formatted_date}." - ) - - 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 AP oleh {self.env.user.name} " - f"pada {formatted_date}." - ) - - 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 Pimpinan oleh {self.env.user.name} " - f"pada {formatted_date}." - ) - - else: - raise UserError("Status saat ini tidak bisa di-approve lagi.") - - # rec.message_post(body=f"Approval oleh {self.env.user.name} pada tahap {rec.status}.") - - 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 { -- cgit v1.2.3 From 5c69ea958bd5c5611601a3af0f76ac9c462b2667 Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 16 Sep 2025 15:45:14 +0700 Subject: (andri) fix layout --- indoteknik_custom/models/letter_receivable.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/letter_receivable.py b/indoteknik_custom/models/letter_receivable.py index 63d7e726..e4886258 100644 --- a/indoteknik_custom/models/letter_receivable.py +++ b/indoteknik_custom/models/letter_receivable.py @@ -60,6 +60,8 @@ class SuratPiutang(models.Model): payment_difficulty = fields.Selection(string="Payment Difficulty", related='partner_id.payment_difficulty', readonly=True) + sales_person_id = fields.Many2one('res.users', string='Salesperson', related='partner_id.user_id', readonly=True) + def _compute_perihal_label(self): for rec in self: rec.perihal_label = dict(self._fields['perihal'].selection).get(rec.perihal, '') -- cgit v1.2.3 From 6695da56e3f9fe575f7c855b60da31ceb4dd5129 Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 16 Sep 2025 15:55:40 +0700 Subject: (andri) add filter hanya menampilkan partner yang unpaid --- indoteknik_custom/models/letter_receivable.py | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/letter_receivable.py b/indoteknik_custom/models/letter_receivable.py index e4886258..1deeda11 100644 --- a/indoteknik_custom/models/letter_receivable.py +++ b/indoteknik_custom/models/letter_receivable.py @@ -62,6 +62,14 @@ class SuratPiutang(models.Model): sales_person_id = fields.Many2one('res.users', string='Salesperson', related='partner_id.user_id', readonly=True) + @api.onchange('partner_id') + def _onchange_partner_id_domain(self): + unpaid_partner_ids = self.env['unpaid.invoice.view'].search([]).mapped('partner_id.id') + return { + 'domain': { + 'partner_id': [('id', 'in', unpaid_partner_ids)] + } + } def _compute_perihal_label(self): for rec in self: rec.perihal_label = dict(self._fields['perihal'].selection).get(rec.perihal, '') -- cgit v1.2.3 From 9e6ab83ccd890c2ddd4e74cc0ed650d35a12f31e Mon Sep 17 00:00:00 2001 From: it-fixcomart Date: Tue, 16 Sep 2025 16:18:24 +0700 Subject: journal payment tempo --- indoteknik_custom/models/refund_sale_order.py | 118 +++++++++++++++++++++----- indoteknik_custom/models/sale_order.py | 22 ++++- 2 files changed, 117 insertions(+), 23 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/refund_sale_order.py b/indoteknik_custom/models/refund_sale_order.py index 55128080..4ee76006 100644 --- a/indoteknik_custom/models/refund_sale_order.py +++ b/indoteknik_custom/models/refund_sale_order.py @@ -113,7 +113,7 @@ class RefundSaleOrder(models.Model): string='Customer', required=True ) - advance_move_names = fields.Html(string="Group Journal SO", compute="_compute_advance_move_names") + advance_move_names = fields.Html(string="Group Journal Payment", compute="_compute_advance_move_names") uang_masuk_type = fields.Selection([ ('pdf', 'PDF'), ('image', 'Image'), @@ -237,6 +237,7 @@ class RefundSaleOrder(models.Model): refund_type = vals.get('refund_type') invoice_ids_data = vals.get('invoice_ids', []) invoice_ids = invoice_ids_data[0][2] if invoice_ids_data and invoice_ids_data[0][0] == 6 else [] + invoices = self.env['account.move'].browse(invoice_ids) if invoice_ids and refund_type and refund_type not in ['uang', 'barang_kosong_sebagian', 'barang_kosong', 'retur_half']: raise UserError("Refund type Hanya Bisa Lebih Bayar, Barang Kosong Sebagian, atau Retur jika ada invoice") @@ -276,18 +277,46 @@ class RefundSaleOrder(models.Model): ('journal_id', '=', 11), ('state', '=', 'posted'), ]) - total_uang_muka = sum(moves.mapped('amount_total_signed')) if moves else 0.0 - total_midtrans = sum(self.env['sale.order'].browse(so_ids).mapped('gross_amount')) if so_ids else 0.0 - total_pembayaran = total_uang_muka + total_midtrans + piutangbca = self.env['account.move'].search([ + ('ref', 'in', invoices.mapped('name')), + ('journal_id', '=', 4), + ('state', '=', 'posted'), + ]) + piutangmdr = self.env['account.move'].search([ + ('ref', 'in', invoices.mapped('name')), + ('journal_id', '=', 7), + ('state', '=', 'posted'), + ]) + has_moves = bool(moves) + has_piutangmdr = bool(piutangmdr) + has_piutangbca = bool(piutangbca) + ssos = self.env['sale.order'].browse(so_ids) + has_settlement = any(so.payment_status == 'settlement' for so in ssos) + + sisa_uang_masuk = 0.0 + if has_moves and has_settlement: + sisa_uang_masuk = sum(moves.mapped('amount_total_signed')) + sum(ssos.mapped('gross_amount')) + elif has_moves: + sisa_uang_masuk = sum(moves.mapped('amount_total_signed')) + elif has_settlement: + sisa_uang_masuk = sum(ssos.mapped('gross_amount')) + elif has_piutangbca: + sisa_uang_masuk = sum(piutangbca.mapped('amount_total_signed')) + elif has_piutangmdr: + sisa_uang_masuk = sum(piutangmdr.mapped('amount_total_signed')) + else: + raise UserError( + "❌ Tidak bisa melakukan refund karena SO tidak memiliki Record Uang Masuk " + "(Journal Uang Muka / Payment Invoices / Midtrans Payment)." + ) + existing_refunds = self.env['refund.sale.order'].search([ ('sale_order_ids', 'in', so_ids) ], order='id desc', limit=1) if existing_refunds: sisa_uang_masuk = existing_refunds.remaining_refundable - else: - sisa_uang_masuk = total_pembayaran if sisa_uang_masuk < 0: raise UserError("❌ Tidak ada sisa transaksi untuk di-refund.") @@ -452,15 +481,34 @@ class RefundSaleOrder(models.Model): def _compute_advance_move_names(self): for rec in self: move_links = [] + + invoice_ids = rec.sale_order_ids.mapped('invoice_ids') + moves = self.env['account.move'].search([ ('sale_id', 'in', rec.sale_order_ids.ids), ('journal_id', '=', 11), - ('state', '=', 'posted') + ('state', '=', 'posted'), ]) - for move in moves: + + piutangbca = self.env['account.move'].search([ + ('ref', 'in', invoice_ids.mapped('name')), + ('journal_id', '=', 4), + ('state', '=', 'posted'), + ]) + + piutangmdr = self.env['account.move'].search([ + ('ref', 'in', invoice_ids.mapped('name')), + ('journal_id', '=', 7), + ('state', '=', 'posted'), + ]) + + all_moves = moves | piutangbca | piutangmdr + + for move in all_moves: url = f"/web#id={move.id}&model=account.move&view_type=form" name = html_escape(move.name or 'Unnamed') move_links.append(f'{name}') + rec.advance_move_names = ', '.join(move_links) if move_links else "-" @api.depends('sale_order_ids.user_id') @@ -496,9 +544,35 @@ class RefundSaleOrder(models.Model): ('journal_id', '=', 11), ('state', '=', 'posted'), ]) - total_uang_muka = sum(moves.mapped('amount_total_signed')) if moves else 0.0 - total_midtrans = sum(self.env['sale.order'].browse(so_ids).mapped('gross_amount')) if so_ids else 0.0 - self.uang_masuk = (total_uang_muka + total_midtrans) - amount_refund_before + piutangbca = self.env['account.move'].search([ + ('ref', 'in', all_invoices.mapped('name')), + ('journal_id', '=', 4), + ('state', '=', 'posted'), + ]) + piutangmdr = self.env['account.move'].search([ + ('ref', 'in', all_invoices.mapped('name')), + ('journal_id', '=', 7), + ('state', '=', 'posted'), + ]) + has_moves = bool(moves) + has_piutangmdr = bool(piutangmdr) + has_piutangbca = bool(piutangbca) + ssos = self.env['sale.order'].browse(so_ids) + has_settlement = any(so.payment_status == 'settlement' for so in ssos) + + sisa_uang_masuk = 0.0 + if has_moves and has_settlement: + sisa_uang_masuk = sum(moves.mapped('amount_total_signed')) + sum(ssos.mapped('gross_amount')) + elif has_moves: + sisa_uang_masuk = sum(moves.mapped('amount_total_signed')) + elif has_settlement: + sisa_uang_masuk = sum(ssos.mapped('gross_amount')) + elif has_piutangbca: + sisa_uang_masuk = sum(piutangbca.mapped('amount_total_signed')) + elif has_piutangmdr: + sisa_uang_masuk = sum(piutangmdr.mapped('amount_total_signed')) + + self.uang_masuk = sisa_uang_masuk - amount_refund_before self.invoice_ids = all_invoices @@ -519,7 +593,6 @@ class RefundSaleOrder(models.Model): """ Validasi SO harus punya uang masuk (Journal Uang Muka / Midtrans) """ for rec in self: invalid_orders = [] - total_uang_masuk = 0.0 for so in rec.sale_order_ids: # cari journal uang muka @@ -528,21 +601,24 @@ class RefundSaleOrder(models.Model): ('journal_id', '=', 11), # Journal Uang Muka ('state', '=', 'posted'), ]) + piutangbca = self.env['account.move'].search([ + ('ref', 'in', rec.invoice_ids.mapped('name')), + ('journal_id', '=', 4), + ('state', '=', 'posted'), + ]) + piutangmdr = self.env['account.move'].search([ + ('ref', 'in', rec.invoice_ids.mapped('name')), + ('journal_id', '=', 7), + ('state', '=', 'posted'), + ]) - if not moves and so.payment_status != 'settlement': + if not moves and so.payment_status != 'settlement' and not piutangbca and not piutangmdr: invalid_orders.append(so.name) - if moves: - total_uang_muka = sum(moves.mapped('amount_total_signed')) or 0.0 - total_uang_masuk += total_uang_muka - else: - # fallback Midtrans gross_amount - total_uang_masuk += so.gross_amount or 0.0 - if invalid_orders: raise ValidationError( f"Tidak dapat membuat refund untuk SO {', '.join(invalid_orders)} " - "karena tidak memiliki Record Uang Masuk (Journal Uang Muka/Midtrans).\n" + "karena tidak memiliki Record Uang Masuk (Journal Uang Muka/Payment Invoice/Midtrans).\n" "Pastikan semua SO yang dipilih sudah memiliki Record pembayaran yang valid." ) diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 76d4d7e7..9cdd9a7b 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -3306,16 +3306,31 @@ class SaleOrder(models.Model): f"❌ SO {self.name} Belum melakukan kirim barang " f"({', '.join(not_done_pickings.mapped('name'))}). Selesaikan Pengiriman untuk melakukan refund." ) + + invoice_ids = self.invoice_ids.filtered(lambda inv: inv.state != 'cancel') + moves = self.env['account.move'].search([ ('sale_id', '=', self.id), ('journal_id', '=', 11), ('state', '=', 'posted'), ]) + piutangbca = self.env['account.move'].search([ + ('ref', 'in', invoice_ids.mapped('name')), + ('journal_id', '=', 4), + ('state', '=', 'posted'), + ]) + piutangmdr = self.env['account.move'].search([ + ('ref', 'in', invoice_ids.mapped('name')), + ('journal_id', '=', 7), + ('state', '=', 'posted'), + ]) # Default 0 total_uang_muka = 0.0 has_moves = bool(moves) + has_piutangmdr = bool(piutangmdr) + has_piutangbca = bool(piutangbca) has_settlement = self.payment_status == 'settlement' if has_moves and has_settlement: @@ -3324,12 +3339,15 @@ class SaleOrder(models.Model): total_uang_muka = sum(moves.mapped('amount_total_signed')) elif has_settlement: total_uang_muka = self.gross_amount + elif has_piutangbca: + total_uang_muka = sum(piutangbca.mapped('amount_total_signed')) + elif has_piutangmdr: + total_uang_muka = sum(piutangmdr.mapped('amount_total_signed')) else: raise UserError( "Tidak bisa melakukan refund karena SO tidak memiliki Record Uang Masuk " - "(Journal Uang Muka/Midtrans Payment)." + "(Journal Uang Muka/Payment Invoices/Midtrans Payment)." ) - invoice_ids = self.invoice_ids.filtered(lambda inv: inv.state != 'cancel') total_refunded = sum(self.refund_ids.mapped('amount_refund')) sisa_uang_muka = total_uang_muka - total_refunded -- cgit v1.2.3 From dba361e5b7a44b9ad2c0c7fa54355707f63fd49a Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Tue, 16 Sep 2025 17:11:14 +0700 Subject: fix bug --- indoteknik_custom/models/report_logbook_sj.py | 40 --------------------------- 1 file changed, 40 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/report_logbook_sj.py b/indoteknik_custom/models/report_logbook_sj.py index b45eab03..3b07ff02 100644 --- a/indoteknik_custom/models/report_logbook_sj.py +++ b/indoteknik_custom/models/report_logbook_sj.py @@ -86,46 +86,6 @@ class ReportLogbookSJ(models.Model): def _onchange_report_logbook_sj_line(self): self._resequence_lines() -from odoo import models, fields, api - - - - def action_send_to_telegram(self): - - entries = [] - pickings = self.report_logbook_sj_line.mapped('picking_id') - for p in pickings: - if p: - entries.append((p.name, p.id)) - - fallback_names = [l.name for l in self.report_logbook_sj_line if not l.picking_id and l.name] - if fallback_names: - picks = self.env['stock.picking'].search([('name', 'in', list(set(fallback_names)))]) - name2id = {p.name: p.id for p in picks} - for n in fallback_names: - entries.append((n, name2id.get(n))) - - seen, unique_entries = set(), [] - for name, pid in entries: - key = pid or name - if key and key not in seen: - seen.add(key) - unique_entries.append((name, pid)) - - header = f"{self.env.user.name} sudah mengisi di Logbook SJ Report:\n" - body = "\n".join(f"{name} ({pid or '-'})" for name, pid in unique_entries) if unique_entries else "- (tidak ada)" - text = header + body - - bot_mqdd = '8203414501:AAHy_XwiUAVrgRM2EJzW7sZx9npRLITZpb8' - chat_id_mqdd = '-4885333032' - apiURL = f'https://api.telegram.org/bot{bot_mqdd}/sendMessage' - try: - hehe = requests.post(apiURL, json={'chat_id': chat_id_mqdd, 'text': text}) - _logger.info(hehe) - except Exception as e: - print(e) - - class ReportLogbookSJLine(models.Model): _name = 'report.logbook.sj.line' _order = 'sequence, id' # urut default di UI & ORM (drag pakai sequence) -- cgit v1.2.3 From 6791235591714e4ac45a6729409716bcba9e8fea Mon Sep 17 00:00:00 2001 From: Miqdad Date: Tue, 16 Sep 2025 21:31:42 +0700 Subject: add try in get tracking detail stock picking --- indoteknik_custom/models/stock_picking.py | 50 ++++++++++++++++++------------- 1 file changed, 30 insertions(+), 20 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index a48e0ed1..78a49ee4 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -1743,27 +1743,37 @@ class StockPicking(models.Model): } if self.biteship_id: - histori = self.get_manifest_biteship() - day_start = order.estimated_arrival_days_start - day_end = order.estimated_arrival_days - if sale_order_delay: - if sale_order_delay.status == 'delayed': - day_start = day_start + sale_order_delay.days_delayed - day_end = day_end + sale_order_delay.days_delayed - elif sale_order_delay.status == 'early': - day_start = day_start - sale_order_delay.days_delayed - day_end = day_end - sale_order_delay.days_delayed - - eta_start = order.date_order + timedelta(days=day_start) - eta_end = order.date_order + timedelta(days=day_end) - formatted_eta = f"{eta_start.strftime('%d %b')} - {eta_end.strftime('%d %b %Y')}" - response['eta'] = formatted_eta - response['manifests'] = histori.get("manifests", []) - response['delivered'] = histori.get("delivered", - False) or self.sj_return_date != False or self.driver_arrival_date != False - response['status'] = self._map_status_biteship(histori.get("delivered")) + try: + histori = self.get_manifest_biteship() + day_start = order.estimated_arrival_days_start + day_end = order.estimated_arrival_days + if sale_order_delay: + if sale_order_delay.status == 'delayed': + day_start += sale_order_delay.days_delayed + day_end += sale_order_delay.days_delayed + elif sale_order_delay.status == 'early': + day_start -= sale_order_delay.days_delayed + day_end -= sale_order_delay.days_delayed + + eta_start = order.date_order + timedelta(days=day_start) + eta_end = order.date_order + timedelta(days=day_end) + formatted_eta = f"{eta_start.strftime('%d %b')} - {eta_end.strftime('%d %b %Y')}" + + response['eta'] = formatted_eta + response['manifests'] = histori.get("manifests", []) + response['delivered'] = ( + histori.get("delivered", False) + or self.sj_return_date != False + or self.driver_arrival_date != False + ) + response['status'] = self._map_status_biteship(histori.get("delivered")) - return response + return response + + except Exception as e: + # Kalau ada error di biteship, log dan fallback ke Odoo + _logger.warning("Biteship error pada DO %s: %s", self.name, str(e)) + # biarkan lanjut ke kondisi di bawah (pakai Odoo waybill_id) if not self.waybill_id or len(self.waybill_id.manifest_ids) == 0: response['delivered'] = self.sj_return_date != False or self.driver_arrival_date != False -- cgit v1.2.3 From ec115f256cfbc24008606c3c4dc47ecdd96955e8 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Tue, 16 Sep 2025 21:49:08 +0700 Subject: merge --- indoteknik_custom/models/sale_order.py | 1 - 1 file changed, 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index bbcb9531..861531b9 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -2419,7 +2419,6 @@ class SaleOrder(models.Model): is_partner_cbd = (partner_term_days_total == 0) is_so_cbd = bool(rec.payment_term_id.id == 26) - # Ambil jumlah nilai dari SO yang invoice_status masih 'to invoice' so_to_invoice = 0 for sale in rec.partner_id.sale_order_ids: if sale.invoice_status == 'to invoice': -- cgit v1.2.3 From a5e3fc915aace81b68c781d69042a2a8822dac85 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Wed, 17 Sep 2025 05:29:23 +0700 Subject: fix group chat id tele --- indoteknik_custom/models/sj_tele.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sj_tele.py b/indoteknik_custom/models/sj_tele.py index 029adcbe..d44aa338 100644 --- a/indoteknik_custom/models/sj_tele.py +++ b/indoteknik_custom/models/sj_tele.py @@ -47,7 +47,7 @@ class SjTele(models.Model): def woi(self): bot_mqdd = '8203414501:AAHy_XwiUAVrgRM2EJzW7sZx9npRLITZpb8' - chat_id_mqdd = '-4885333032' + chat_id_mqdd = '-1003087280519' api_base = f'https://api.telegram.org/bot{bot_mqdd}' data = self.search([], order='create_date asc', limit=15) -- cgit v1.2.3 From 9dc31bc66d3d5fd3464e6b731cabe15c3c52b558 Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 17 Sep 2025 09:23:12 +0700 Subject: (andri) add validasi sales manager --- indoteknik_custom/models/letter_receivable.py | 41 +++++++++++++++++++++------ 1 file changed, 33 insertions(+), 8 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/letter_receivable.py b/indoteknik_custom/models/letter_receivable.py index 1deeda11..84a3fc35 100644 --- a/indoteknik_custom/models/letter_receivable.py +++ b/indoteknik_custom/models/letter_receivable.py @@ -136,18 +136,41 @@ class SuratPiutang(models.Model): wib = pytz.timezone('Asia/Jakarta') now_wib = datetime.now(wib) - pimpinan_user_ids = [7] # Pak Akbar - # if self.env.user.id not in pimpinan_user_ids: - # raise UserError("Hanya Pimpinan yang berhak menyetujui tahap ini.") + sales_manager_ids = [10] # ganti dengan ID user Sales Manager + pimpinan_user_ids = [7] # ganti dengan ID user Pimpinan + for rec in self: - if rec.state == "waiting_approval": - if rec.perihal in ("sp1", "sp2", "sp3"): + # === SP1 s/d SP3 butuh dua tahap approval === + if rec.perihal in ("sp1", "sp2", "sp3"): + + # Tahap 1: Sales Manager approval + if rec.state == "waiting_approval_sales": + if self.env.user.id not in sales_manager_ids: + raise UserError("Hanya Sales Manager yang boleh menyetujui tahap ini.") + rec.state = "waiting_approval_pimpinan" + rec.message_post(body="Disetujui oleh Sales Manager. Menunggu Approval Pimpinan.") + continue + + # Tahap 2: Pimpinan approval + if rec.state == "waiting_approval_pimpinan": if self.env.user.id not in pimpinan_user_ids: - raise UserError("Hanya Pimpinan yang berhak menyetujui surat peringatan piutang (SP1, SP2, SP3).") + raise UserError("Hanya Pimpinan yang berhak menyetujui surat ini.") + rec.state = "sent" + now_utc = now_wib.astimezone(pytz.UTC).replace(tzinfo=None) + rec.send_date = now_utc + rec.action_send_letter() + rec.message_post(body="Surat Piutang disetujui oleh Pimpinan dan berhasil dikirim.") + continue + + # === Surat penagihan biasa (langsung Pimpinan approve) === + if rec.perihal == "penagihan": + # if self.env.user.id not in pimpinan_user_ids: + # raise UserError("Hanya Pimpinan yang boleh menyetujui surat penagihan.") rec.state = "sent" now_utc = now_wib.astimezone(pytz.UTC).replace(tzinfo=None) rec.send_date = now_utc rec.action_send_letter() + rec.message_post(body="Surat Penagihan disetujui Pimpinan dan berhasil dikirim.") self.env.user.notify_info( message=f"Surat piutang {rec.name} berhasil dikirim ke {rec.partner_id.name} ({rec.tujuan_email})", @@ -358,8 +381,10 @@ class SuratPiutang(models.Model): bulan_romawi = ["I","II","III","IV","V","VI","VII","VIII","IX","X","XI","XII"][today.month-1] tahun = today.strftime("%y") vals["name"] = f"{seq}/LO/FAT/IDG/{bulan_romawi}/{tahun}" - - vals["state"] = "waiting_approval" + if vals.get("perihal") == "penagihan": + vals["state"] = "waiting_approval_pimpinan" + else: + vals["state"] = "waiting_approval_sales" return super().create(vals) class SuratPiutangLine(models.Model): -- cgit v1.2.3 From 1ff3a02196c74dd21573e84aba22cca8bd99a3bb Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Wed, 17 Sep 2025 09:27:58 +0700 Subject: push --- indoteknik_custom/models/purchase_order.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/purchase_order.py b/indoteknik_custom/models/purchase_order.py index e7e5c382..68180235 100755 --- a/indoteknik_custom/models/purchase_order.py +++ b/indoteknik_custom/models/purchase_order.py @@ -117,7 +117,8 @@ class PurchaseOrder(models.Model): ) show_description = fields.Boolean( - string='Show Description' + string='Show Description', + default=True ) @api.onchange('show_description') -- cgit v1.2.3 From 3f43b0bb62cc8524c05fa9ac7ee0be9408f73381 Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 17 Sep 2025 10:42:00 +0700 Subject: (andri) buat func terpisah untuk handle due dan overdue --- indoteknik_custom/models/account_move.py | 105 ++++++++++++++++--------------- 1 file changed, 55 insertions(+), 50 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/account_move.py b/indoteknik_custom/models/account_move.py index 70cd07e4..b20e07c8 100644 --- a/indoteknik_custom/models/account_move.py +++ b/indoteknik_custom/models/account_move.py @@ -192,49 +192,54 @@ class AccountMove(models.Model): def send_due_invoice_reminder(self): today = fields.Date.today() target_dates = [ - today - timedelta(days=7), - today - timedelta(days=3), - today, - today + timedelta(days=3), today + timedelta(days=7), + today + timedelta(days=3), + today, ] - - for days_after_due in range(14, 181, 7): - target_dates.append(today - timedelta(days=days_after_due)) - invoices = self.env['account.move'].search([ ('move_type', '=', 'out_invoice'), ('state', '=', 'posted'), ('payment_state', 'not in', ['paid', 'in_payment', 'reversed']), ('invoice_date_due', 'in', target_dates), - ('date_terima_tukar_faktur', '!=', False) - ]) - _logger.info(f"Invoices: {invoices}") + ('date_terima_tukar_faktur', '!=', False), + ('invoice_payment_term_id.name', 'ilike', 'tempo')]) + if not invoices: + _logger.info("Tidak ada invoice yang due") + return - invoices = invoices.filtered( - lambda inv: inv.invoice_payment_term_id and 'tempo' in (inv.invoice_payment_term_id.name or '').lower() - ) - # _logger.info(f"Invoices tahap 2: {invoices}") + self._send_invoice_reminders(invoices, mode='due') + def send_overdue_invoice_reminder(self): + today = fields.Date.today() + invoices = self.env['account.move'].search([ + ('move_type', '=', 'out_invoice'), + ('state', '=', 'posted'), + ('payment_state', 'not in', ['paid', 'in_payment', 'reversed']), + ('invoice_date_due', '<', today), + ('date_terima_tukar_faktur', '!=', False), + ('invoice_payment_term_id.name', 'ilike', 'tempo')]) if not invoices: - _logger.info("Tidak ada invoice yang due") + _logger.info("Tidak ada invoice yang overdue") return + self._send_invoice_reminders(invoices, mode='overdue') + + def _send_invoice_reminders(self, invoices, mode): + today = fields.Date.today() + template = self.env.ref('indoteknik_custom.mail_template_invoice_due_reminder') invoice_group = {} for inv in invoices: dtd = (inv.invoice_date_due - today).days if inv.invoice_date_due else 0 - key = (inv.partner_id, dtd) + key = (inv.partner_id, dtd if mode == 'due' else "overdue") if key not in invoice_group: invoice_group[key] = self.env['account.move'] # recordset kosong invoice_group[key] |= inv # gabung recordset - template = self.env.ref('indoteknik_custom.mail_template_invoice_due_reminder') - for (partner, dtd), invs in invoice_group.items(): if all(inv.reminder_sent_date == today for inv in invs): _logger.info(f"Reminder untuk {partner.name} sudah terkirim hari ini, skip.") continue - + promise_dates = [inv.customer_promise_date for inv in invs if inv.customer_promise_date] if promise_dates: earliest_promise = min(promise_dates) # ambil janji paling awal @@ -354,33 +359,33 @@ class AccountMove(models.Model): days_to_due_message = "" closing_message = "" - if dtd > 0: - days_to_due_message = ( - f"Kami ingin mengingatkan bahwa tagihan anda akan jatuh tempo dalam {dtd} hari ke depan, " - "dengan rincian sebagai berikut:" - ) - closing_message = ( - "Kami mengharapkan pembayaran dapat dilakukan tepat waktu untuk mendukung kelancaran " - "hubungan kerja sama yang baik antara kedua belah pihak.
" - "Mohon konfirmasi apabila pembayaran telah dijadwalkan. " - "Terima kasih atas perhatian dan kerja samanya." - ) - - if dtd == 0: - days_to_due_message = ( - "Kami ingin mengingatkan bahwa tagihan anda telah memasuki tanggal jatuh tempo pada hari ini, " - "dengan rincian sebagai berikut:" - ) - closing_message = ( - "Mohon kesediaannya untuk segera melakukan pembayaran tepat waktu guna menghindari status " - "keterlambatan dan menjaga kelancaran hubungan kerja sama yang telah terjalin dengan baik.
" - "Apabila pembayaran telah dijadwalkan atau diproses, mohon dapat dikonfirmasi kepada kami. " - "Terima kasih atas perhatian dan kerja samanya." - ) + if mode == "due": + if dtd > 0: + days_to_due_message = ( + f"Kami ingin mengingatkan bahwa tagihan anda akan jatuh tempo dalam {dtd} hari ke depan, " + "dengan rincian sebagai berikut:" + ) + closing_message = ( + "Kami mengharapkan pembayaran dapat dilakukan tepat waktu untuk mendukung kelancaran " + "hubungan kerja sama yang baik antara kedua belah pihak.
" + "Mohon konfirmasi apabila pembayaran telah dijadwalkan. " + "Terima kasih atas perhatian dan kerja samanya." + ) - if dtd < 0: + elif dtd == 0: + days_to_due_message = ( + "Kami ingin mengingatkan bahwa tagihan anda telah memasuki tanggal jatuh tempo pada hari ini, " + "dengan rincian sebagai berikut:" + ) + closing_message = ( + "Mohon kesediaannya untuk segera melakukan pembayaran tepat waktu guna menghindari status " + "keterlambatan dan menjaga kelancaran hubungan kerja sama yang telah terjalin dengan baik.
" + "Apabila pembayaran telah dijadwalkan atau diproses, mohon dapat dikonfirmasi kepada kami. " + "Terima kasih atas perhatian dan kerja samanya." + ) + else: # mode overdue days_to_due_message = ( - f"Kami ingin mengingatkan bahwa tagihan anda telah jatuh tempo selama {abs(dtd)} hari, " + f"Kami ingin mengingatkan bahwa beberapa tagihan anda telah jatuh tempo, " "dengan rincian sebagai berikut:" ) closing_message = ( @@ -413,13 +418,13 @@ class AccountMove(models.Model): # Siapkan email values values = { - 'subject': f"Reminder Invoice Due - {partner.name}", - # 'email_to': 'andrifebriyadiputra@gmail.com', - 'email_to': email_to, + 'subject': f"Reminder Invoice Due Test - {partner.name}", + 'email_to': 'andrifebriyadiputra@gmail.com', + # 'email_to': email_to, 'email_from': 'finance@indoteknik.co.id', - 'email_cc': ",".join(sorted(set(cc_list))), + # 'email_cc': ",".join(sorted(set(cc_list))), 'body_html': body_html, - 'reply_to': 'finance@indoteknik.co.id', + # 'reply_to': 'finance@indoteknik.co.id', } template.send_mail(invs[0].id, force_send=True, email_values=values) -- cgit v1.2.3 From d37525b0dbdec49d2e812fc1945fb6a81a570e94 Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 17 Sep 2025 11:09:35 +0700 Subject: (andri) add numbering + testing --- indoteknik_custom/models/account_move.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/account_move.py b/indoteknik_custom/models/account_move.py index b20e07c8..f10ca23f 100644 --- a/indoteknik_custom/models/account_move.py +++ b/indoteknik_custom/models/account_move.py @@ -203,6 +203,7 @@ class AccountMove(models.Model): ('invoice_date_due', 'in', target_dates), ('date_terima_tukar_faktur', '!=', False), ('invoice_payment_term_id.name', 'ilike', 'tempo')]) + _logger.info(f"Found {len(invoices)} invoices due for reminder {invoices}.") if not invoices: _logger.info("Tidak ada invoice yang due") return @@ -217,7 +218,9 @@ class AccountMove(models.Model): ('payment_state', 'not in', ['paid', 'in_payment', 'reversed']), ('invoice_date_due', '<', today), ('date_terima_tukar_faktur', '!=', False), - ('invoice_payment_term_id.name', 'ilike', 'tempo')]) + ('invoice_payment_term_id.name', 'ilike', 'tempo'), + ('partner_id', 'in', [94603])]) + _logger.info(f"Found {len(invoices)} invoices overdue for reminder {invoices}.") if not invoices: _logger.info("Tidak ada invoice yang overdue") return @@ -281,11 +284,12 @@ class AccountMove(models.Model): invoice_table_rows = "" grand_total = 0 - for inv in invs: + for idx, inv in enumerate(invs, start=1): # numbering days_to_due = (inv.invoice_date_due - today).days if inv.invoice_date_due else 0 grand_total += inv.amount_total invoice_table_rows += f""" + {idx} {inv.partner_id.name} {inv.ref or '-'} {inv.name} @@ -296,6 +300,7 @@ class AccountMove(models.Model): {days_to_due} """ + invoice_table_footer = f""" @@ -428,10 +433,10 @@ class AccountMove(models.Model): } template.send_mail(invs[0].id, force_send=True, email_values=values) - _logger.info(f"Mengirim email ke: {values['email_to']} > email CC: {values['email_cc']}") + # _logger.info(f"Mengirim email ke: {values['email_to']} > email CC: {values['email_cc']}") _logger.info(f"Reminder terkirim ke {partner.name} ({values['email_to']}) → {len(invs)} invoice (dtd = {dtd})") # flag - invs.write({'reminder_sent_date': today}) + # invs.write({'reminder_sent_date': today}) # Post ke chatter user_system = self.env['res.users'].browse(25) system_id = user_system.partner_id.id if user_system else False -- cgit v1.2.3 From fe75f5b4ad91ef9c5d54cd98449a53b8a40018bc Mon Sep 17 00:00:00 2001 From: Miqdad Date: Wed, 17 Sep 2025 12:47:26 +0700 Subject: push --- indoteknik_custom/models/purchase_order.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/purchase_order.py b/indoteknik_custom/models/purchase_order.py index e7e5c382..45fbe6e7 100755 --- a/indoteknik_custom/models/purchase_order.py +++ b/indoteknik_custom/models/purchase_order.py @@ -197,8 +197,11 @@ class PurchaseOrder(models.Model): # Ambil semua BU awal dari PO base_bu = StockPicking.search([ + '|', + '&', ('name', 'ilike', 'BU/'), - ('origin', 'ilike', order.name) + ('group_id.id', '=', order.group_id.id), + ('origin', '=', order.name), ]) all_bu = base_bu @@ -228,10 +231,12 @@ class PurchaseOrder(models.Model): # Step 1: cari semua BU pertama (PUT, INT) yang berasal dari PO ini base_bu = StockPicking.search([ + '|', + '&', ('name', 'ilike', 'BU/'), - ('origin', 'ilike', self.name) + ('group_id.id', '=', self.group_id.id), + ('origin', '=', self.name), ]) - all_bu = base_bu seen_names = set(base_bu.mapped('name')) @@ -242,10 +247,10 @@ class PurchaseOrder(models.Model): ('origin', 'in', ['Return of %s' % name for name in seen_names]) ]) next_names = set(next_bu.mapped('name')) - + if not next_names - seen_names: break - + all_bu |= next_bu seen_names |= next_names -- cgit v1.2.3 From 9e570f94949dad425e4bd9ce438240373460e4fc Mon Sep 17 00:00:00 2001 From: stephanchrst Date: Wed, 17 Sep 2025 15:36:45 +0700 Subject: bf invoice dp, redundant qty invoice in sale order --- indoteknik_custom/models/sale_order.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 484a9016..dd06f541 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -1848,7 +1848,8 @@ class SaleOrder(models.Model): # if term.days > 0: # raise UserError('Hanya dapat digunakan pada Cash Before Delivery') for line in self.order_line: - line.qty_to_invoice = line.product_uom_qty + if line.product_id.type == 'product': + line.qty_to_invoice = line.product_uom_qty # def _get_pickings(self): # state = ['assigned'] -- cgit v1.2.3 From 61b54c404d0c104a5751a17eed94c5934805a050 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Wed, 17 Sep 2025 15:52:33 +0700 Subject: delivery date --- indoteknik_custom/models/stock_picking.py | 1 + 1 file changed, 1 insertion(+) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 78a49ee4..35d408a1 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -307,6 +307,7 @@ class StockPicking(models.Model): ('delay', 'Delay By Vendor'), ('urgent', 'Urgent Delivery'), ], string='Reason Change Date Planned', tracking=True) + delivery_date = fields.Datetime(string='Delivery Date', copy=False) def _get_kgx_awb_number(self): """Menggabungkan name dan origin untuk membuat AWB Number""" -- cgit v1.2.3 From aaff9c4fa47ca2ee114ab1dc3a8140aec300ce26 Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 17 Sep 2025 16:29:04 +0700 Subject: (andri) button surat lanjutan + fix bug --- indoteknik_custom/models/letter_receivable.py | 83 ++++++++++++++++++++++++--- 1 file changed, 76 insertions(+), 7 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/letter_receivable.py b/indoteknik_custom/models/letter_receivable.py index 84a3fc35..b1c82b12 100644 --- a/indoteknik_custom/models/letter_receivable.py +++ b/indoteknik_custom/models/letter_receivable.py @@ -32,11 +32,11 @@ class SuratPiutang(models.Model): state = fields.Selection([ ("draft", "Draft"), ("waiting_approval_sales", "Menunggu Approval Sales Manager"), - ("waiting_approval_pimpinan", "Menunggu Approval Pimpinan"), + ("waiting_approval_pimpinan", "Menunggu Approval Pimpinan / Kirim Surat"), ("sent", "Approved & Sent") ], default="draft", tracking=True) send_date = fields.Datetime(string="Tanggal Kirim", tracking=True) - seven_days_after_sent_date = fields.Char(string="7 Hari Setelah Tanggal Kirim", tracking=True) + seven_days_after_sent_date = fields.Char(string="7 Hari Setelah Tanggal Kirim") periode_invoices_terpilih = fields.Char( string="Periode Invoices Terpilih", compute="_compute_periode_invoices", @@ -62,6 +62,12 @@ class SuratPiutang(models.Model): sales_person_id = fields.Many2one('res.users', string='Salesperson', related='partner_id.user_id', readonly=True) + PERIHAL_SEQUENCE = { + "penagihan": "sp1", + "sp1": "sp2", + "sp2": "sp3", + } + @api.onchange('partner_id') def _onchange_partner_id_domain(self): unpaid_partner_ids = self.env['unpaid.invoice.view'].search([]).mapped('partner_id.id') @@ -74,9 +80,68 @@ class SuratPiutang(models.Model): for rec in self: rec.perihal_label = dict(self._fields['perihal'].selection).get(rec.perihal, '') - # def action_print_surat_piutang(self): - # self.ensure_one() - # return self.env.ref('indoteknik_custom.report_surat_piutang_formal').report_action(self) + def action_create_next_letter(self): + for rec in self: + if rec.state != "sent": + raise UserError("Surat harus sudah terkirim sebelum bisa membuat surat lanjutan.") + + next_perihal = self.PERIHAL_SEQUENCE.get(rec.perihal) + if not next_perihal: + raise UserError("Surat ini sudah pada tahap terakhir (SP3). Tidak bisa membuat lanjutan lagi.") + + existing = self.search([ + ('partner_id', '=', rec.partner_id.id), + ('perihal', '=', next_perihal), + ('state', '!=', 'draft') # optional: cek hanya yang sudah dikirim + ]) + if existing: + raise UserError(f"Surat lanjutan {dict(self._fields['perihal'].selection).get(next_perihal)} " + f"untuk customer ini sudah dibuat: {', '.join(existing.mapped('name'))}") + + # copy surat lama + new_vals = { + "tujuan_nama": rec.tujuan_nama, + "tujuan_email": rec.tujuan_email, + "perihal": next_perihal, + "partner_id": rec.partner_id.id, + "line_ids": [(0, 0, { + 'invoice_id': line.invoice_id.id, + 'invoice_number': line.invoice_number, + 'invoice_date': line.invoice_date, + 'invoice_date_due': line.invoice_date_due, + 'invoice_day_to_due': line.invoice_day_to_due, + 'new_invoice_day_to_due': line.new_invoice_day_to_due, + 'ref': line.ref, + 'amount_residual': line.amount_residual, + 'currency_id': line.currency_id.id, + 'payment_term_id': line.payment_term_id.id, + 'date_kirim_tukar_faktur': line.date_kirim_tukar_faktur, + 'date_terima_tukar_faktur': line.date_terima_tukar_faktur, + 'invoice_user_id': line.invoice_user_id.id, + 'sale_id': line.sale_id.id, + "selected": line.selected, + }) for line in rec.line_ids], + } + new_letter = self.create(new_vals) + self.env.user.notify_info( + message=f"Surat lanjutan {dict(self._fields['perihal'].selection).get(next_perihal)} berhasil dibuat ({new_letter.name}).", + title="Informasi", + sticky=False + ) + new_letter.message_post( + body= + f"Surat lanjutan dengan perihal {dict(self._fields['perihal'].selection).get(next_perihal)} " + f"berhasil dibuat berdasarkan surat sebelumnya.
" + f"Nomor Surat: {new_letter.name}" + ) + rec.message_post( + body=( + f"Surat lanjutan dengan perihal {dict(self._fields['perihal'].selection).get(next_perihal)} " + f"telah dibuat sebagai kelanjutan dari surat ini.
" + f"Nomor Surat Baru: {new_letter.name}" + ) + ) + return True @api.depends("line_ids.selected", "line_ids.invoice_date") @@ -170,7 +235,7 @@ class SuratPiutang(models.Model): now_utc = now_wib.astimezone(pytz.UTC).replace(tzinfo=None) rec.send_date = now_utc rec.action_send_letter() - rec.message_post(body="Surat Penagihan disetujui Pimpinan dan berhasil dikirim.") + rec.message_post(body="Surat Penagihan disetujui dan berhasil dikirim.") self.env.user.notify_info( message=f"Surat piutang {rec.name} berhasil dikirim ke {rec.partner_id.name} ({rec.tujuan_email})", @@ -268,7 +333,11 @@ class SuratPiutang(models.Model): 'reply_to': 'finance@indoteknik.co.id', } - template.send_mail(self.id, force_send=True, email_values=values) + template.with_context(mail_post_autofollow=False).send_mail( + self.id, + force_send=True, + email_values=values + ) _logger.info( f"Surat Piutang {self.name} terkirim ke {self.tujuan_email} " -- cgit v1.2.3 From eada77c97134dcbbbbbff43b4195a0e07cda4b86 Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 17 Sep 2025 16:31:58 +0700 Subject: (andri) fix --- indoteknik_custom/models/letter_receivable.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/letter_receivable.py b/indoteknik_custom/models/letter_receivable.py index b1c82b12..1445800f 100644 --- a/indoteknik_custom/models/letter_receivable.py +++ b/indoteknik_custom/models/letter_receivable.py @@ -124,13 +124,13 @@ class SuratPiutang(models.Model): } new_letter = self.create(new_vals) self.env.user.notify_info( - message=f"Surat lanjutan {dict(self._fields['perihal'].selection).get(next_perihal)} berhasil dibuat ({new_letter.name}).", + message=f"{dict(self._fields['perihal'].selection).get(next_perihal)} berhasil dibuat ({new_letter.name}).", title="Informasi", sticky=False ) new_letter.message_post( body= - f"Surat lanjutan dengan perihal {dict(self._fields['perihal'].selection).get(next_perihal)} " + f"{dict(self._fields['perihal'].selection).get(next_perihal)} " f"berhasil dibuat berdasarkan surat sebelumnya.
" f"Nomor Surat: {new_letter.name}" ) -- cgit v1.2.3 From 886c28f6ebf20dcca5252341a8f6b61cd4d89d71 Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 17 Sep 2025 18:25:29 +0700 Subject: fix --- indoteknik_custom/models/account_move.py | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/account_move.py b/indoteknik_custom/models/account_move.py index 9061f541..ec23c626 100644 --- a/indoteknik_custom/models/account_move.py +++ b/indoteknik_custom/models/account_move.py @@ -218,8 +218,7 @@ class AccountMove(models.Model): ('payment_state', 'not in', ['paid', 'in_payment', 'reversed']), ('invoice_date_due', '<', today), ('date_terima_tukar_faktur', '!=', False), - ('invoice_payment_term_id.name', 'ilike', 'tempo'), - ('partner_id', 'in', [94603])]) + ('invoice_payment_term_id.name', 'ilike', 'tempo')]) _logger.info(f"Found {len(invoices)} invoices overdue for reminder {invoices}.") if not invoices: _logger.info("Tidak ada invoice yang overdue") @@ -423,25 +422,20 @@ class AccountMove(models.Model): # Siapkan email values values = { - 'subject': f"Reminder Invoice Due Test - {partner.name}", + 'subject': f"Reminder Invoice Due - {partner.name}", 'email_to': 'andrifebriyadiputra@gmail.com', - # 'email_to': email_to, + 'email_to': email_to, 'email_from': 'finance@indoteknik.co.id', - # 'email_cc': ",".join(sorted(set(cc_list))), + 'email_cc': ",".join(sorted(set(cc_list))), 'body_html': body_html, - # 'reply_to': 'finance@indoteknik.co.id', + 'reply_to': 'finance@indoteknik.co.id', } -<<<<<<< HEAD - # template.send_mail(invs[0].id, force_send=True, email_values=values) - _logger.info(f"Mengirim email ke: {values['email_to']} > email CC: {values['email_cc']}") -======= template.send_mail(invs[0].id, force_send=True, email_values=values) - # _logger.info(f"Mengirim email ke: {values['email_to']} > email CC: {values['email_cc']}") ->>>>>>> 23d713fc686d56ef5b5e8004b91b3f4fe54117e6 + _logger.info(f"Mengirim email ke: {values['email_to']} > email CC: {values['email_cc']}") _logger.info(f"Reminder terkirim ke {partner.name} ({values['email_to']}) → {len(invs)} invoice (dtd = {dtd})") # flag - # invs.write({'reminder_sent_date': today}) + invs.write({'reminder_sent_date': today}) # Post ke chatter user_system = self.env['res.users'].browse(25) system_id = user_system.partner_id.id if user_system else False -- cgit v1.2.3 From 6c7617432a986e43e94c8f72c1a9505042dc0b01 Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 17 Sep 2025 20:04:00 +0700 Subject: disable email test --- indoteknik_custom/models/account_move.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/account_move.py b/indoteknik_custom/models/account_move.py index ec23c626..96f791c5 100644 --- a/indoteknik_custom/models/account_move.py +++ b/indoteknik_custom/models/account_move.py @@ -423,7 +423,7 @@ class AccountMove(models.Model): # Siapkan email values values = { 'subject': f"Reminder Invoice Due - {partner.name}", - 'email_to': 'andrifebriyadiputra@gmail.com', + # 'email_to': 'andrifebriyadiputra@gmail.com', 'email_to': email_to, 'email_from': 'finance@indoteknik.co.id', 'email_cc': ",".join(sorted(set(cc_list))), -- cgit v1.2.3 From 675499a48ae64183ed494bb907047151d934e6b2 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Thu, 18 Sep 2025 14:49:06 +0700 Subject: push --- indoteknik_custom/models/sale_order.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 94c5f041..2d8567b0 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -1877,6 +1877,8 @@ class SaleOrder(models.Model): }) def open_form_multi_update_status(self): + if self.env.user.id != 688 or self.env.user.has_group('indoteknik_custom.group_role_it'): + raise UserError("Hanya Finance nya yang bisa approve.") action = self.env['ir.actions.act_window']._for_xml_id('indoteknik_custom.action_sale_orders_multi_update') action['context'] = { 'sale_ids': [x.id for x in self] -- cgit v1.2.3 From b07468846b95803f21b81191013651fac2d9adb4 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Thu, 18 Sep 2025 17:19:55 +0700 Subject: Fix bug cannot open vcm --- indoteknik_custom/models/tukar_guling_po.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/tukar_guling_po.py b/indoteknik_custom/models/tukar_guling_po.py index f61b3828..2a5ca3dd 100644 --- a/indoteknik_custom/models/tukar_guling_po.py +++ b/indoteknik_custom/models/tukar_guling_po.py @@ -353,18 +353,18 @@ class TukarGulingPO(models.Model): # self._check_bill_on_retur_po() tipe = vals.get('return_type', self.return_type) - if self.operations and self.operations.picking_type_id.id == 28 and tipe == 'tukar_guling': - group = self.operations.group_id - if group: - # Cari BU/PUT dalam group yang sama - bu_put = self.env['stock.picking'].search([ - ('group_id', '=', group.id), - ('picking_type_id.id', '=', 75), # 75 = ID BU/PUT - ('state', '=', 'done') - ], limit=1) - - if bu_put: - raise UserError("❌ Tidak bisa retur BU/INPUT karena BU/PUT sudah Done!") + # if self.operations and self.operations.picking_type_id.id == 28 and tipe == 'tukar_guling': + # group = self.operations.group_id + # if group: + # # Cari BU/PUT dalam group yang sama + # bu_put = self.env['stock.picking'].search([ + # ('group_id', '=', group.id), + # ('picking_type_id.id', '=', 75), # 75 = ID BU/PUT + # ('state', '=', 'done') + # ], limit=1) + # + # if bu_put: + # raise UserError("❌ Tidak bisa retur BU/INPUT karena BU/PUT sudah Done!") if self.operations.picking_type_id.id == 28 and tipe == 'tukar_guling': raise UserError("❌ BU/INPUT tidak boleh di retur tukar guling") -- cgit v1.2.3 From c1b61f16ac40b400e85c9e4c5992f056560ac308 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Fri, 19 Sep 2025 10:07:31 +0700 Subject: match return type tukgul --- indoteknik_custom/models/refund_sale_order.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/refund_sale_order.py b/indoteknik_custom/models/refund_sale_order.py index 4ee76006..6695df56 100644 --- a/indoteknik_custom/models/refund_sale_order.py +++ b/indoteknik_custom/models/refund_sale_order.py @@ -1146,7 +1146,7 @@ class RefundSaleOrder(models.Model): 'origin': ','.join(refund.sale_order_ids.mapped('name')), 'origin_so': refund.sale_order_ids.id, 'operations': picking.id, - 'return_type': 'revisi_so', + 'return_type': 'retur_so', 'invoice_id': [(6, 0, refund.invoice_ids.ids)], 'refund_id': refund.id, 'line_ids': line_vals, -- cgit v1.2.3 From 4665d0ffe4146d0badc535af2c002ab63ef8cb4b Mon Sep 17 00:00:00 2001 From: it-fixcomart Date: Fri, 19 Sep 2025 11:20:10 +0700 Subject: refund barang kosong SO indent --- indoteknik_custom/models/refund_sale_order.py | 37 ++++++++++++++++++++++----- indoteknik_custom/models/sale_order.py | 10 -------- 2 files changed, 30 insertions(+), 17 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/refund_sale_order.py b/indoteknik_custom/models/refund_sale_order.py index 4ee76006..b8350829 100644 --- a/indoteknik_custom/models/refund_sale_order.py +++ b/indoteknik_custom/models/refund_sale_order.py @@ -57,6 +57,7 @@ class RefundSaleOrder(models.Model): refund_type = fields.Selection([ ('barang_kosong_sebagian', 'Refund Barang Kosong Sebagian'), ('barang_kosong', 'Refund Barang Kosong Full'), + ('barang_kosong_indent', 'Refund Barang Kosong Sebagian(Indent)'), ('uang', 'Refund Lebih Bayar'), ('retur_half', 'Refund Retur Sebagian'), ('retur', 'Refund Retur Full'), @@ -216,7 +217,7 @@ class RefundSaleOrder(models.Model): vals['created_date'] = fields.Date.context_today(self) vals['create_uid'] = self.env.user.id - + refund_type = vals.get('refund_type') if 'sale_order_ids' in vals: so_cmd = vals['sale_order_ids'] so_ids = so_cmd[0][2] if so_cmd and so_cmd[0][0] == 6 else [] @@ -226,6 +227,18 @@ class RefundSaleOrder(models.Model): if len(partner) > 1: raise UserError("❌ Tidak dapat membuat refund untuk Multi SO dengan Customer berbeda. Harus memiliki Customer yang sama.") vals['partner_id'] = sale_orders[0].partner_id.id + if refund_type not in ['barang_kosong_indent', 'salah_transfer']: + for so in sale_orders: + if so.state not in ['cancel', 'sale']: + raise UserError(f"❌ SO {so.name} tidak bisa direfund. Status harus Cancel atau Sale.") + if so.state == 'sale': + not_done_pickings = so.picking_ids.filtered(lambda p: p.state not in ['done', 'cancel']) + if not_done_pickings: + raise UserError( + f"❌ SO {so.name} Belum melakukan kirim barang " + f"({', '.join(not_done_pickings.mapped('name'))}). " + "Selesaikan Pengiriman untuk melakukan refund." + ) invoices = sale_orders.mapped('invoice_ids').filtered( lambda inv: inv.move_type in ['out_invoice', 'out_refund'] and inv.state != 'cancel' @@ -234,7 +247,6 @@ class RefundSaleOrder(models.Model): vals['invoice_ids'] = [(6, 0, invoices.ids)] - refund_type = vals.get('refund_type') invoice_ids_data = vals.get('invoice_ids', []) invoice_ids = invoice_ids_data[0][2] if invoice_ids_data and invoice_ids_data[0][0] == 6 else [] invoices = self.env['account.move'].browse(invoice_ids) @@ -244,7 +256,7 @@ class RefundSaleOrder(models.Model): if not invoice_ids and refund_type and refund_type in ['uang', 'barang_kosong_sebagian', 'retur_half']: raise UserError("Refund type Lebih Bayar dan Barang Kosong Sebagian Hanya Bisa dipilih Jika Ada Invoice") - if refund_type in ['barang_kosong', 'barang_kosong_sebagian'] and so_ids: + if refund_type in ['barang_kosong', 'barang_kosong_sebagian', 'barang_kosong_indent'] and so_ids: sale_orders = self.env['sale.order'].browse(so_ids) if refund_type == 'barang_kosong': @@ -382,6 +394,7 @@ class RefundSaleOrder(models.Model): sale_orders = self.env['sale.order'].browse(so_ids) + valid_invoices = sale_orders.mapped('invoice_ids').filtered( lambda inv: inv.move_type in ['out_invoice', 'out_refund'] and inv.state != 'cancel' ) @@ -391,9 +404,19 @@ class RefundSaleOrder(models.Model): so_ids = rec.sale_order_ids.ids sale_orders = self.env['sale.order'].browse(so_ids) - - refund_type = vals.get('refund_type', rec.refund_type) + if refund_type not in ['barang_kosong_indent', 'salah_transfer']: + for so in sale_orders: + if so.state not in ['cancel', 'sale']: + raise UserError(f"❌ SO {so.name} tidak bisa direfund. Status harus Cancel atau Sale.") + if so.state == 'sale': + not_done_pickings = so.picking_ids.filtered(lambda p: p.state not in ['done', 'cancel']) + if not_done_pickings: + raise UserError( + f"❌ SO {so.name} Belum melakukan kirim barang " + f"({', '.join(not_done_pickings.mapped('name'))}). " + "Selesaikan Pengiriman untuk melakukan refund." + ) if refund_type in ['barang_kosong', 'barang_kosong_sebagian'] and sale_orders: zero_delivery_lines = sale_orders.mapped('order_line').filtered(lambda l: l.qty_delivered >= 0 or l.product_uom_qty > l.qty_delivered) @@ -625,7 +648,7 @@ class RefundSaleOrder(models.Model): @api.onchange('refund_type') def _onchange_refund_type(self): self.line_ids = [(5, 0, 0)] - if self.refund_type in ['barang_kosong_sebagian', 'barang_kosong'] and self.sale_order_ids: + if self.refund_type in ['barang_kosong_sebagian', 'barang_kosong', 'barang_kosong_indent'] and self.sale_order_ids: line_vals = [] for so in self.sale_order_ids: for line in so.order_line: @@ -920,7 +943,7 @@ class RefundSaleOrder(models.Model): # Normalisasi refund_type_label = refund_type_label.upper() - if refund.refund_type in ['barang_kosong', 'barang_kosong_sebagian']: + if refund.refund_type in ['barang_kosong', 'barang_kosong_sebagian', 'barang_kosong_indent']: refund_type_label = "REFUND BARANG KOSONG" elif refund.refund_type in ['retur_half', 'retur']: refund_type_label = "REFUND RETUR BARANG" diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 9cdd9a7b..35d1f087 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -3296,16 +3296,6 @@ class SaleOrder(models.Model): def button_refund(self): self.ensure_one() - - if self.state not in ['cancel', 'sale']: - raise UserError(f"❌ SO {self.name} tidak bisa direfund. Status harus Cancel atau Sale.") - if self.state == 'sale': - not_done_pickings = self.picking_ids.filtered(lambda p: p.state not in ['done', 'cancel']) - if not_done_pickings: - raise UserError( - f"❌ SO {self.name} Belum melakukan kirim barang " - f"({', '.join(not_done_pickings.mapped('name'))}). Selesaikan Pengiriman untuk melakukan refund." - ) invoice_ids = self.invoice_ids.filtered(lambda inv: inv.state != 'cancel') -- cgit v1.2.3 From ef5418ec79e17a4b233e6ea51ba66126a97ab0eb Mon Sep 17 00:00:00 2001 From: FIN-IT_AndriFP Date: Fri, 19 Sep 2025 14:05:23 +0700 Subject: (andri) add validasi jika pengajuan assets harus isi notes --- indoteknik_custom/models/purchase_order.py | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/purchase_order.py b/indoteknik_custom/models/purchase_order.py index 0304b5e2..b34ec926 100755 --- a/indoteknik_custom/models/purchase_order.py +++ b/indoteknik_custom/models/purchase_order.py @@ -1057,8 +1057,19 @@ class PurchaseOrder(models.Model): message="Produk "+line.product_id.name+" memiliki vendor berbeda dengan SO (Vendor PO: "+str(self.partner_id.name)+", Vendor SO: "+str(line.so_line_id.vendor_id.name)+")", sticky=True ) + + def _check_assets_note(self): + for order in self: + # Cari apakah ada line dengan produk ID 614469 ('Assets Mesin & Peralatan') + asset_line = order.order_line.filtered(lambda l: l.product_id.id == 595346) + if asset_line and not order.notes: + raise UserError(_( + "%s berisi produk 'Assets Mesin & Peralatan'. " + "Harap isi Notes untuk menjelaskan kebutuhan dan divisi terkait." + ) % order.name) def button_confirm(self): + self._check_assets_note() # self._check_payment_term() # check payment term res = super(PurchaseOrder, self).button_confirm() current_time = datetime.now() -- cgit v1.2.3 From dc9fd38206e9234319e8088717b39cf0770aaad7 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Fri, 19 Sep 2025 17:43:59 +0700 Subject: add role and field dispatch --- indoteknik_custom/models/stock_picking.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 35d408a1..8a81c426 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -89,8 +89,9 @@ class StockPicking(models.Model): readonly=True, related="id", ) - sj_documentation = fields.Binary(string="Dokumentasi Surat Jalan", ) - paket_documentation = fields.Binary(string="Dokumentasi Paket", ) + sj_documentation = fields.Binary(string="Dokumentasi Surat Jalan") + paket_documentation = fields.Binary(string="Dokumentasi Paket") + dispatch_documentation = fields.Binary(string="Dokumentasi Dispatch") sj_return_date = fields.Datetime(string="SJ Return Date", copy=False) responsible = fields.Many2one('res.users', string='Responsible', tracking=True) -- cgit v1.2.3 From b73575cc51ec58c02bccb711689d52e99e915c4e Mon Sep 17 00:00:00 2001 From: FIN-IT_AndriFP Date: Sun, 21 Sep 2025 22:06:00 +0700 Subject: (andri) link PO to stock journal after validate BD --- indoteknik_custom/models/stock_picking.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 35d408a1..67106073 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -1352,6 +1352,19 @@ class StockPicking(models.Model): if self.picking_type_code == 'outgoing' and 'BU/OUT/' in self.name: self.check_koli() res = super(StockPicking, self).button_validate() + + # Penambahan link PO di Stock Journal untuk Picking BD + for picking in self: + if picking.name and 'BD/' in picking.name and picking.purchase_id: + stock_journal = self.env['account.move'].search([ + ('ref', 'ilike', picking.name + '%'), + ('journal_id', '=', 3) # Stock Journal ID + ], limit = 1) + if stock_journal: + stock_journal.write({ + 'purchase_order_id': picking.purchase_id.id + }) + self.date_done = datetime.datetime.utcnow() self.state_reserve = 'done' self.final_seq = 0 -- cgit v1.2.3 From 7f97c16e662903d42b405c618c2e26a0b7ba5146 Mon Sep 17 00:00:00 2001 From: FIN-IT_AndriFP Date: Mon, 22 Sep 2025 10:44:51 +0700 Subject: (andri) cc email --- indoteknik_custom/models/letter_receivable.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/letter_receivable.py b/indoteknik_custom/models/letter_receivable.py index 1445800f..16034938 100644 --- a/indoteknik_custom/models/letter_receivable.py +++ b/indoteknik_custom/models/letter_receivable.py @@ -323,11 +323,23 @@ class SuratPiutang(models.Model): 'mimetype': 'application/pdf', }) + cc_list = [ + 'finance@indoteknik.co.id', + 'akbar@indoteknik.co.id', + 'stephan@indoteknik.co.id', + 'darren@indoteknik.co.id' + ] + + sales_email = self.sales_person_id.email if self.sales_person_id else None + if sales_email and sales_email not in cc_list: + cc_list.append(sales_email) + values = { # 'subject': template.subject.replace('${object.name}', self.name or ''), 'subject': perihal_map.get(self.perihal, self.perihal or '') + " - " + (self.partner_id.name or ''), 'email_to': self.tujuan_email, 'email_from': 'finance@indoteknik.co.id', + 'email_cc': ",".join(sorted(set(cc_list))), 'body_html': body_html, 'attachments': [(attachment.name, attachment.datas)], 'reply_to': 'finance@indoteknik.co.id', -- cgit v1.2.3 From a68f3c46bb02d7205ed1209e2b30b786bf4c7a3c Mon Sep 17 00:00:00 2001 From: FIN-IT_AndriFP Date: Mon, 22 Sep 2025 11:19:17 +0700 Subject: (andri) payment difficulty pada inv --- indoteknik_custom/models/account_move.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/account_move.py b/indoteknik_custom/models/account_move.py index 96f791c5..79bf5581 100644 --- a/indoteknik_custom/models/account_move.py +++ b/indoteknik_custom/models/account_move.py @@ -99,6 +99,8 @@ class AccountMove(models.Model): reminder_sent_date = fields.Date(string="Tanggal Reminder Terkirim") + payment_difficulty = fields.Selection(string="Payment Difficulty", related='partner_id.payment_difficulty', readonly=True) + customer_promise_date = fields.Date( string="Janji Bayar", help="Tanggal janji bayar dari customer setelah reminder dikirim.", -- cgit v1.2.3 From 327835caeb892b66813383456d83156c37b666e1 Mon Sep 17 00:00:00 2001 From: FIN-IT_AndriFP Date: Mon, 22 Sep 2025 15:39:32 +0700 Subject: (andri) add previous pt --- indoteknik_custom/models/res_partner.py | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/res_partner.py b/indoteknik_custom/models/res_partner.py index 36570e8f..8aaee47e 100644 --- a/indoteknik_custom/models/res_partner.py +++ b/indoteknik_custom/models/res_partner.py @@ -194,6 +194,12 @@ class ResPartner(models.Model): default=_default_payment_term, tracking=3 ) + previous_payment_term_id = fields.Many2one( + 'account.payment.term', + string='Previous Payment Term', + readonly=True + ) + @api.depends("street", "street2", "city", "state_id", "country_id", "blok", "nomor", "rt", "rw", "kelurahan_id", "kecamatan_id") -- cgit v1.2.3 From 21f46424f4d879b8c703d8da7a6f8b3c20193ced Mon Sep 17 00:00:00 2001 From: FIN-IT_AndriFP Date: Mon, 22 Sep 2025 17:23:21 +0700 Subject: (andri) fix reminder mail --- indoteknik_custom/models/account_move.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/account_move.py b/indoteknik_custom/models/account_move.py index 96f791c5..d1485f1f 100644 --- a/indoteknik_custom/models/account_move.py +++ b/indoteknik_custom/models/account_move.py @@ -334,11 +334,13 @@ class AccountMove(models.Model): tempo_link = 'https://indoteknik.com/my/tempo' # tempo_link = 'http://localhost:2100/my/tempo' + payment_term = partner.previous_payment_term_id if partner.is_cbd_locked else partner.property_payment_term_id + limit_info_html = f"""

Informasi Tambahan:

  • Kredit Limit Anda: {formatLang(self.env, blocking_limit, currency_obj=currency)}
  • -
  • Status Detail Tempo: {partner.property_payment_term_id.name or 'Review'}
  • +
  • Status Detail Tempo: {partner.payment_term.name or 'Review'}
  • Sisa Kredit Limit: {formatLang(self.env, blocking_limit - outstanding_amount, currency_obj=currency)}
  • -- cgit v1.2.3 From d373212e2da13ef478b70c6d354807ee62484464 Mon Sep 17 00:00:00 2001 From: FIN-IT_AndriFP Date: Mon, 22 Sep 2025 17:25:34 +0700 Subject: (andri) fix --- indoteknik_custom/models/account_move.py | 1 - 1 file changed, 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/account_move.py b/indoteknik_custom/models/account_move.py index d1485f1f..edcfbdd5 100644 --- a/indoteknik_custom/models/account_move.py +++ b/indoteknik_custom/models/account_move.py @@ -333,7 +333,6 @@ class AccountMove(models.Model): currency = invs[0].currency_id if invs else partner.company_id.currency_id tempo_link = 'https://indoteknik.com/my/tempo' # tempo_link = 'http://localhost:2100/my/tempo' - payment_term = partner.previous_payment_term_id if partner.is_cbd_locked else partner.property_payment_term_id limit_info_html = f""" -- cgit v1.2.3 From ec0277a049ea2048785dba61711a24e9a4eb3363 Mon Sep 17 00:00:00 2001 From: FIN-IT_AndriFP Date: Mon, 22 Sep 2025 17:38:15 +0700 Subject: fix --- indoteknik_custom/models/account_move.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/account_move.py b/indoteknik_custom/models/account_move.py index edcfbdd5..60f0da74 100644 --- a/indoteknik_custom/models/account_move.py +++ b/indoteknik_custom/models/account_move.py @@ -339,7 +339,7 @@ class AccountMove(models.Model):

    Informasi Tambahan:

    • Kredit Limit Anda: {formatLang(self.env, blocking_limit, currency_obj=currency)}
    • -
    • Status Detail Tempo: {partner.payment_term.name or 'Review'}
    • +
    • Status Detail Tempo: {payment_term.name or 'Review'}
    • Sisa Kredit Limit: {formatLang(self.env, blocking_limit - outstanding_amount, currency_obj=currency)}
    • -- cgit v1.2.3 From 68594ca366d77396d9d2c8e707043fcb28a303f7 Mon Sep 17 00:00:00 2001 From: FIN-IT_AndriFP Date: Mon, 22 Sep 2025 17:38:45 +0700 Subject: fix --- indoteknik_custom/models/account_move.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/account_move.py b/indoteknik_custom/models/account_move.py index 60f0da74..c7feac9e 100644 --- a/indoteknik_custom/models/account_move.py +++ b/indoteknik_custom/models/account_move.py @@ -339,7 +339,7 @@ class AccountMove(models.Model):

      Informasi Tambahan:

      • Kredit Limit Anda: {formatLang(self.env, blocking_limit, currency_obj=currency)}
      • -
      • Status Detail Tempo: {payment_term.name or 'Review'}
      • +
      • Status Detail Tempo: {payment_term.name or ''}
      • Sisa Kredit Limit: {formatLang(self.env, blocking_limit - outstanding_amount, currency_obj=currency)}
      • -- cgit v1.2.3 From f58e6e2fa013789bfa8ac8456cd29735a83a56d0 Mon Sep 17 00:00:00 2001 From: FIN-IT_AndriFP Date: Tue, 23 Sep 2025 05:34:07 +0700 Subject: (andri) fix --- indoteknik_custom/models/account_move.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/account_move.py b/indoteknik_custom/models/account_move.py index 28e9156a..44b3cb76 100644 --- a/indoteknik_custom/models/account_move.py +++ b/indoteknik_custom/models/account_move.py @@ -335,7 +335,8 @@ class AccountMove(models.Model): currency = invs[0].currency_id if invs else partner.company_id.currency_id tempo_link = 'https://indoteknik.com/my/tempo' # tempo_link = 'http://localhost:2100/my/tempo' - payment_term = partner.previous_payment_term_id if partner.is_cbd_locked else partner.property_payment_term_id + # payment_term = partner.previous_payment_term_id if partner.is_cbd_locked else partner.property_payment_term_id + payment_term = invs[0].invoice_payment_term_id limit_info_html = f"""

        Informasi Tambahan:

        -- cgit v1.2.3 From fca5c08671169703d935161fc4bb0c5c05548a62 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Tue, 23 Sep 2025 08:32:19 +0700 Subject: fix validation error when creating SO in midnight --- indoteknik_custom/models/sale_order.py | 88 +++++++++++++++++++++++++--------- 1 file changed, 66 insertions(+), 22 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 39830ffc..02ceb62b 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -1700,57 +1700,101 @@ class SaleOrder(models.Model): # .format(eta_minimum.strftime('%d-%m-%Y'), eta_minimum.strftime('%d-%m-%Y')) # ) + # def _validate_expected_ready_ship_date(self): + # """ + # Pastikan expected_ready_to_ship tidak lebih awal dari SLA minimum. + # Dipanggil setiap onchange / simpan SO. + # """ + # for rec in self: + # # ───────────────────────────────────────────────────── + # # 1. Hanya validasi kalau field sudah terisi + # # (quotation baru / belum ada tanggal → abaikan) + # # ───────────────────────────────────────────────────── + # if not rec.expected_ready_to_ship: + # continue + # + # current_date = datetime.now() + # + # # ───────────────────────────────────────────────────── + # # 2. Hitung SLA berdasarkan product lines (jika ada) + # # ───────────────────────────────────────────────────── + # products = rec.order_line + # if products: + # sla_data = rec.calculate_sla_by_vendor(products) + # max_sla_time = sla_data.get('slatime', 1) + # else: + # # belum ada item → gunakan default 1 hari + # max_sla_time = 1 + # + # # offset hari libur / weekend + # offset, is3pm = rec.get_days_until_next_business_day(current_date) + # min_days = max_sla_time + offset - 1 + # eta_minimum = current_date + timedelta(days=min_days) + # + # # ───────────────────────────────────────────────────── + # # 3. Validasi - raise error bila terlalu cepat + # # ───────────────────────────────────────────────────── + # if rec.expected_ready_to_ship.date() < eta_minimum.date(): + # # set otomatis ke tanggal minimum supaya user tidak perlu + # # menekan Save dua kali + # rec.expected_ready_to_ship = eta_minimum + # + # raise ValidationError( + # _("Tanggal 'Expected Ready to Ship' tidak boleh " + # "lebih kecil dari %(tgl)s. Mohon pilih minimal %(tgl)s.") + # % {'tgl': eta_minimum.strftime('%d-%m-%Y')} + # ) + # else: + # # sinkronkan ke field commitment_date + # rec.commitment_date = rec.expected_ready_to_ship + def _validate_expected_ready_ship_date(self): """ Pastikan expected_ready_to_ship tidak lebih awal dari SLA minimum. Dipanggil setiap onchange / simpan SO. """ for rec in self: - # ───────────────────────────────────────────────────── - # 1. Hanya validasi kalau field sudah terisi - # (quotation baru / belum ada tanggal → abaikan) - # ───────────────────────────────────────────────────── if not rec.expected_ready_to_ship: continue - current_date = datetime.now() + # ADDED: gunakan "sekarang" lokal user, bukan datetime.now() server + current_date = fields.Datetime.context_timestamp(rec, fields.Datetime.now()) - # ───────────────────────────────────────────────────── - # 2. Hitung SLA berdasarkan product lines (jika ada) - # ───────────────────────────────────────────────────── + # Hitung SLA products = rec.order_line if products: sla_data = rec.calculate_sla_by_vendor(products) max_sla_time = sla_data.get('slatime', 1) else: - # belum ada item → gunakan default 1 hari max_sla_time = 1 - # offset hari libur / weekend + # offset hari libur/weekend offset, is3pm = rec.get_days_until_next_business_day(current_date) min_days = max_sla_time + offset - 1 eta_minimum = current_date + timedelta(days=min_days) - # ───────────────────────────────────────────────────── - # 3. Validasi - raise error bila terlalu cepat - # ───────────────────────────────────────────────────── - if rec.expected_ready_to_ship.date() < eta_minimum.date(): - # set otomatis ke tanggal minimum supaya user tidak perlu - # menekan Save dua kali - rec.expected_ready_to_ship = eta_minimum + if rec._fields['expected_ready_to_ship'].type == 'date': + exp_date_local = rec.expected_ready_to_ship + else: + exp_date_local = fields.Datetime.context_timestamp( + rec, rec.expected_ready_to_ship + ).date() + + if exp_date_local < eta_minimum.date(): + # (opsional) auto-set ke minimum → konversi balik ke UTC naive bila field Datetime + if rec._fields['expected_ready_to_ship'].type == 'date': + rec.expected_ready_to_ship = eta_minimum.date() + else: + rec.expected_ready_to_ship = eta_minimum.astimezone(pytz.UTC).replace(tzinfo=None) raise ValidationError( _("Tanggal 'Expected Ready to Ship' tidak boleh " - "lebih kecil dari %(tgl)s. Mohon pilih minimal %(tgl)s.") + "lebih kecil dari %(tgl)s. Mohon pilih minimal %(tgl)s.") % {'tgl': eta_minimum.strftime('%d-%m-%Y')} ) else: - # sinkronkan ke field commitment_date rec.commitment_date = rec.expected_ready_to_ship - - - @api.onchange('expected_ready_to_ship') #Hangle Onchange form Expected Ready to Ship def _onchange_expected_ready_ship_date(self): self._validate_expected_ready_ship_date() -- cgit v1.2.3 From 6d50b35724592c4f8c302204adcfbc0f5db3727f Mon Sep 17 00:00:00 2001 From: HafidBuroiroh Date: Tue, 23 Sep 2025 11:30:45 +0700 Subject: pull odoo backup --- indoteknik_custom/models/refund_sale_order.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/refund_sale_order.py b/indoteknik_custom/models/refund_sale_order.py index b8350829..d89954cc 100644 --- a/indoteknik_custom/models/refund_sale_order.py +++ b/indoteknik_custom/models/refund_sale_order.py @@ -859,19 +859,19 @@ class RefundSaleOrder(models.Model): if not rec.status or rec.status == 'draft': rec.status = 'pengajuan1' - elif rec.status == 'pengajuan1' and self.env.user.id == 19: + elif rec.status == 'pengajuan1': rec.status = 'pengajuan2' rec.approved_by = f"{rec.approved_by}, {user_name}" if rec.approved_by else user_name rec.date_approved_sales = now rec.position_sales = 'Sales Manager' - elif rec.status == 'pengajuan2' and self.env.user.id == 688: + elif rec.status == 'pengajuan2': rec.status = 'pengajuan3' rec.approved_by = f"{rec.approved_by}, {user_name}" if rec.approved_by else user_name rec.date_approved_ar = now rec.position_ar = 'AR' - elif rec.status == 'pengajuan3' and self.env.user.id == 7: + elif rec.status == 'pengajuan3': rec.status = 'refund' rec.approved_by = f"{rec.approved_by}, {user_name}" if rec.approved_by else user_name rec.date_approved_pimpinan = now @@ -902,6 +902,14 @@ class RefundSaleOrder(models.Model): for rec in self: if not is_fat: raise UserError("Hanya Finance yang dapat mengkonfirmasi pembayaran refund.") + is_journal = self.env['account.move'].search([ + ('refund_id', '=', rec.id), + ('state', '=', 'posted') + ]) + if not is_journal: + raise UserError("Journal Payment Refund belum dibuat, buat Journal Payment Refund sebelum confirm refund.") + if is_journal and rec.amount_refund != sum(is_journal.mapped('amount_total_signed')): + raise UserError("Total Refund dengan Total Journal Harus Sama.") if rec.status_payment == 'pending': rec.status_payment = 'done' rec.refund_date = fields.Date.context_today(self) -- cgit v1.2.3 From 5abefd1181a148c4caf9b5dd2082ee3b3e884751 Mon Sep 17 00:00:00 2001 From: HafidBuroiroh Date: Tue, 23 Sep 2025 15:49:18 +0700 Subject: validate confirm journal refund --- indoteknik_custom/models/refund_sale_order.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/refund_sale_order.py b/indoteknik_custom/models/refund_sale_order.py index d4702210..eab25452 100644 --- a/indoteknik_custom/models/refund_sale_order.py +++ b/indoteknik_custom/models/refund_sale_order.py @@ -859,19 +859,19 @@ class RefundSaleOrder(models.Model): if not rec.status or rec.status == 'draft': rec.status = 'pengajuan1' - elif rec.status == 'pengajuan1': + elif rec.status == 'pengajuan1' and self.env.user.id == 19: rec.status = 'pengajuan2' rec.approved_by = f"{rec.approved_by}, {user_name}" if rec.approved_by else user_name rec.date_approved_sales = now rec.position_sales = 'Sales Manager' - elif rec.status == 'pengajuan2': + elif rec.status == 'pengajuan2' and self.env.user.id == 688: rec.status = 'pengajuan3' rec.approved_by = f"{rec.approved_by}, {user_name}" if rec.approved_by else user_name rec.date_approved_ar = now rec.position_ar = 'AR' - elif rec.status == 'pengajuan3': + elif rec.status == 'pengajuan3' and self.env.user.id == 7: rec.status = 'refund' rec.approved_by = f"{rec.approved_by}, {user_name}" if rec.approved_by else user_name rec.date_approved_pimpinan = now -- cgit v1.2.3 From 62f151bb1a2287d83844d5a838485b06948d531c Mon Sep 17 00:00:00 2001 From: Miqdad Date: Tue, 23 Sep 2025 17:13:13 +0700 Subject: bd return --- indoteknik_custom/models/stock_picking_return.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking_return.py b/indoteknik_custom/models/stock_picking_return.py index 88acf83c..6fc2d5c7 100644 --- a/indoteknik_custom/models/stock_picking_return.py +++ b/indoteknik_custom/models/stock_picking_return.py @@ -110,7 +110,7 @@ class ReturnPicking(models.TransientModel): if mapping_koli_vals: context['default_mapping_koli_ids'] = mapping_koli_vals - if picking.purchase_id or 'PO' in (picking.origin or ''): + if 'PO' in picking.name: _logger.info("Redirect ke Tukar Guling PO via purchase_id / origin") return { 'name': _('Tukar Guling PO'), -- cgit v1.2.3 From 7d09b515d62c9578301d3365d441958889aedc0f Mon Sep 17 00:00:00 2001 From: Miqdad Date: Tue, 23 Sep 2025 17:26:47 +0700 Subject: fix eror --- indoteknik_custom/models/stock_picking_return.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/stock_picking_return.py b/indoteknik_custom/models/stock_picking_return.py index 6fc2d5c7..53a85f67 100644 --- a/indoteknik_custom/models/stock_picking_return.py +++ b/indoteknik_custom/models/stock_picking_return.py @@ -110,7 +110,7 @@ class ReturnPicking(models.TransientModel): if mapping_koli_vals: context['default_mapping_koli_ids'] = mapping_koli_vals - if 'PO' in picking.name: + if picking.name and any(k in picking.name.upper() for k in ('PUT', 'INPUT')): _logger.info("Redirect ke Tukar Guling PO via purchase_id / origin") return { 'name': _('Tukar Guling PO'), -- cgit v1.2.3 From 22c5def88445676675e1978e77471ef939f46daa Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Wed, 24 Sep 2025 09:37:15 +0700 Subject: pushh --- indoteknik_custom/models/sale_order.py | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 02ceb62b..fd872b53 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -398,6 +398,23 @@ class SaleOrder(models.Model): compute="_compute_partner_is_cbd_locked" ) + def action_open_partial_delivery_wizard(self): + self.ensure_one() + pickings = self.picking_ids.filtered(lambda p: p.state not in ['done', 'cancel'] and p.name and 'BU/PICK/' in p.name) + return { + 'type': 'ir.actions.act_window', + 'name': 'Partial Delivery', + 'res_model': 'partial.delivery.wizard', + 'view_mode': 'form', + 'target': 'new', + 'context': { + 'default_sale_id': self.id, + # kasih langsung list of int biar ga ribet di wizard + 'default_picking_ids': pickings.ids, + } + } + + @api.depends('partner_id.is_cbd_locked') def _compute_partner_is_cbd_locked(self): for order in self: @@ -1795,6 +1812,7 @@ class SaleOrder(models.Model): else: rec.commitment_date = rec.expected_ready_to_ship + @api.onchange('expected_ready_to_ship') #Hangle Onchange form Expected Ready to Ship def _onchange_expected_ready_ship_date(self): self._validate_expected_ready_ship_date() @@ -2258,7 +2276,7 @@ class SaleOrder(models.Model): raise UserError("Terdapat DUPLIKASI data pada Product {}".format(line.product_id.display_name)) def sale_order_approve(self): - self.check_duplicate_product() + # self.check_duplicate_product() self.check_product_bom() self.check_credit_limit() self.check_limit_so_to_invoice() @@ -2528,7 +2546,7 @@ class SaleOrder(models.Model): for order in self: order._validate_delivery_amt() order._validate_uniform_taxes() - order.check_duplicate_product() + # order.check_duplicate_product() order.check_product_bom() order.check_credit_limit() order.check_limit_so_to_invoice() -- cgit v1.2.3 From 80f811a7ccca771990fcbf59df6e92dc8e0acc14 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Wed, 24 Sep 2025 09:41:18 +0700 Subject: balikin validate erts --- indoteknik_custom/models/sale_order.py | 132 ++++++++++++++++----------------- 1 file changed, 66 insertions(+), 66 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index fd872b53..85f0d274 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -1717,101 +1717,101 @@ class SaleOrder(models.Model): # .format(eta_minimum.strftime('%d-%m-%Y'), eta_minimum.strftime('%d-%m-%Y')) # ) - # def _validate_expected_ready_ship_date(self): - # """ - # Pastikan expected_ready_to_ship tidak lebih awal dari SLA minimum. - # Dipanggil setiap onchange / simpan SO. - # """ - # for rec in self: - # # ───────────────────────────────────────────────────── - # # 1. Hanya validasi kalau field sudah terisi - # # (quotation baru / belum ada tanggal → abaikan) - # # ───────────────────────────────────────────────────── - # if not rec.expected_ready_to_ship: - # continue - # - # current_date = datetime.now() - # - # # ───────────────────────────────────────────────────── - # # 2. Hitung SLA berdasarkan product lines (jika ada) - # # ───────────────────────────────────────────────────── - # products = rec.order_line - # if products: - # sla_data = rec.calculate_sla_by_vendor(products) - # max_sla_time = sla_data.get('slatime', 1) - # else: - # # belum ada item → gunakan default 1 hari - # max_sla_time = 1 - # - # # offset hari libur / weekend - # offset, is3pm = rec.get_days_until_next_business_day(current_date) - # min_days = max_sla_time + offset - 1 - # eta_minimum = current_date + timedelta(days=min_days) - # - # # ───────────────────────────────────────────────────── - # # 3. Validasi - raise error bila terlalu cepat - # # ───────────────────────────────────────────────────── - # if rec.expected_ready_to_ship.date() < eta_minimum.date(): - # # set otomatis ke tanggal minimum supaya user tidak perlu - # # menekan Save dua kali - # rec.expected_ready_to_ship = eta_minimum - # - # raise ValidationError( - # _("Tanggal 'Expected Ready to Ship' tidak boleh " - # "lebih kecil dari %(tgl)s. Mohon pilih minimal %(tgl)s.") - # % {'tgl': eta_minimum.strftime('%d-%m-%Y')} - # ) - # else: - # # sinkronkan ke field commitment_date - # rec.commitment_date = rec.expected_ready_to_ship - def _validate_expected_ready_ship_date(self): """ Pastikan expected_ready_to_ship tidak lebih awal dari SLA minimum. Dipanggil setiap onchange / simpan SO. """ for rec in self: + # ───────────────────────────────────────────────────── + # 1. Hanya validasi kalau field sudah terisi + # (quotation baru / belum ada tanggal → abaikan) + # ───────────────────────────────────────────────────── if not rec.expected_ready_to_ship: continue - # ADDED: gunakan "sekarang" lokal user, bukan datetime.now() server - current_date = fields.Datetime.context_timestamp(rec, fields.Datetime.now()) + current_date = datetime.now() - # Hitung SLA + # ───────────────────────────────────────────────────── + # 2. Hitung SLA berdasarkan product lines (jika ada) + # ───────────────────────────────────────────────────── products = rec.order_line if products: sla_data = rec.calculate_sla_by_vendor(products) max_sla_time = sla_data.get('slatime', 1) else: + # belum ada item → gunakan default 1 hari max_sla_time = 1 - # offset hari libur/weekend + # offset hari libur / weekend offset, is3pm = rec.get_days_until_next_business_day(current_date) min_days = max_sla_time + offset - 1 eta_minimum = current_date + timedelta(days=min_days) - if rec._fields['expected_ready_to_ship'].type == 'date': - exp_date_local = rec.expected_ready_to_ship - else: - exp_date_local = fields.Datetime.context_timestamp( - rec, rec.expected_ready_to_ship - ).date() - - if exp_date_local < eta_minimum.date(): - # (opsional) auto-set ke minimum → konversi balik ke UTC naive bila field Datetime - if rec._fields['expected_ready_to_ship'].type == 'date': - rec.expected_ready_to_ship = eta_minimum.date() - else: - rec.expected_ready_to_ship = eta_minimum.astimezone(pytz.UTC).replace(tzinfo=None) + # ───────────────────────────────────────────────────── + # 3. Validasi - raise error bila terlalu cepat + # ───────────────────────────────────────────────────── + if rec.expected_ready_to_ship.date() < eta_minimum.date(): + # set otomatis ke tanggal minimum supaya user tidak perlu + # menekan Save dua kali + rec.expected_ready_to_ship = eta_minimum raise ValidationError( _("Tanggal 'Expected Ready to Ship' tidak boleh " - "lebih kecil dari %(tgl)s. Mohon pilih minimal %(tgl)s.") + "lebih kecil dari %(tgl)s. Mohon pilih minimal %(tgl)s.") % {'tgl': eta_minimum.strftime('%d-%m-%Y')} ) else: + # sinkronkan ke field commitment_date rec.commitment_date = rec.expected_ready_to_ship + # def _validate_expected_ready_ship_date(self): + # """ + # Pastikan expected_ready_to_ship tidak lebih awal dari SLA minimum. + # Dipanggil setiap onchange / simpan SO. + # """ + # for rec in self: + # if not rec.expected_ready_to_ship: + # continue + # + # # ADDED: gunakan "sekarang" lokal user, bukan datetime.now() server + # current_date = fields.Datetime.context_timestamp(rec, fields.Datetime.now()) + # + # # Hitung SLA + # products = rec.order_line + # if products: + # sla_data = rec.calculate_sla_by_vendor(products) + # max_sla_time = sla_data.get('slatime', 1) + # else: + # max_sla_time = 1 + # + # # offset hari libur/weekend + # offset, is3pm = rec.get_days_until_next_business_day(current_date) + # min_days = max_sla_time + offset - 1 + # eta_minimum = current_date + timedelta(days=min_days) + # + # if rec._fields['expected_ready_to_ship'].type == 'date': + # exp_date_local = rec.expected_ready_to_ship + # else: + # exp_date_local = fields.Datetime.context_timestamp( + # rec, rec.expected_ready_to_ship + # ).date() + # + # if exp_date_local < eta_minimum.date(): + # # (opsional) auto-set ke minimum → konversi balik ke UTC naive bila field Datetime + # if rec._fields['expected_ready_to_ship'].type == 'date': + # rec.expected_ready_to_ship = eta_minimum.date() + # else: + # rec.expected_ready_to_ship = eta_minimum.astimezone(pytz.UTC).replace(tzinfo=None) + # + # raise ValidationError( + # _("Tanggal 'Expected Ready to Ship' tidak boleh " + # "lebih kecil dari %(tgl)s. Mohon pilih minimal %(tgl)s.") + # % {'tgl': eta_minimum.strftime('%d-%m-%Y')} + # ) + # else: + # rec.commitment_date = rec.expected_ready_to_ship + @api.onchange('expected_ready_to_ship') #Hangle Onchange form Expected Ready to Ship def _onchange_expected_ready_ship_date(self): -- cgit v1.2.3 From 5dc3710a5008cbbd3d2e6cbc1fca12bc0bd31eda Mon Sep 17 00:00:00 2001 From: FIN-IT_AndriFP Date: Wed, 24 Sep 2025 13:29:15 +0700 Subject: (andri) fix validasi --- indoteknik_custom/models/sale_order.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 85f0d274..f80941d2 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -423,13 +423,13 @@ class SaleOrder(models.Model): @api.constrains('payment_term_id', 'partner_id', 'state') def _check_cbd_lock_sale_order(self): - # cbd_term = self.env['account.payment.term'].browse(26) + cbd_term = self.env['account.payment.term'].browse(26) for rec in self: if rec.state == 'draft' and rec.partner_id.is_cbd_locked: - # if rec.payment_term_id and rec.payment_term_id != cbd_term: - raise ValidationError( - "Customer ini terkunci ke CBD, hanya boleh pakai Payment Term CBD." - ) + if rec.payment_term_id and rec.payment_term_id != cbd_term: + raise ValidationError( + "Customer ini terkunci ke CBD, hanya boleh pakai Payment Term CBD." + ) @api.depends('invoice_ids.payment_state', 'invoice_ids.amount_total', 'invoice_ids.amount_residual') def _compute_payment_state_custom(self): -- cgit v1.2.3 From fa4d124243688f4a74dd605a21b7b402ee632191 Mon Sep 17 00:00:00 2001 From: HafidBuroiroh Date: Wed, 24 Sep 2025 16:46:18 +0700 Subject: refund --- indoteknik_custom/models/refund_sale_order.py | 28 ++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/refund_sale_order.py b/indoteknik_custom/models/refund_sale_order.py index eab25452..6a6d10ea 100644 --- a/indoteknik_custom/models/refund_sale_order.py +++ b/indoteknik_custom/models/refund_sale_order.py @@ -51,6 +51,7 @@ class RefundSaleOrder(models.Model): account_no = fields.Char(string='Account No', required=True) kcp = fields.Char(string='Alamat KCP') finance_note = fields.Text(string='Finance Note') + biaya_admin = fields.Float(string='Biaya Admin Transfer') invoice_names = fields.Html(string="Group Invoice Number", compute="_compute_invoice_names") so_names = fields.Html(string="Group SO Number", compute="_compute_so_names") @@ -280,8 +281,11 @@ class RefundSaleOrder(models.Model): if refund_type == 'salah_transfer' and vals.get('transfer_move_id'): move = self.env['account.move'].browse(vals['transfer_move_id']) if move: + sisa_uang_masuk = move.amount_total_signed # ← set dengan nilai move vals['uang_masuk'] = move.amount_total_signed vals['remaining_refundable'] = 0 + else: + sisa_uang_masuk = 0.0 else: # ==== perhitungan normal ==== moves = self.env['account.move'].search([ @@ -906,9 +910,10 @@ class RefundSaleOrder(models.Model): ('refund_id', '=', rec.id), ('state', '=', 'posted') ]) + amount = rec.amount_refund + rec.biaya_admin if not is_journal: raise UserError("Journal Payment Refund belum dibuat, buat Journal Payment Refund sebelum confirm refund.") - if is_journal and rec.amount_refund != sum(is_journal.mapped('amount_total_signed')): + if is_journal and amount != sum(is_journal.mapped('amount_total_signed')): raise UserError("Total Refund dengan Total Journal Harus Sama.") if rec.status_payment == 'pending': rec.status_payment = 'done' @@ -966,6 +971,7 @@ class RefundSaleOrder(models.Model): # Ref format ref_text = f"{refund_type_label} {refund.name or ''} {partner.display_name}".upper() + admintex = f"BIAYA ADMIN BANK {refund_type_label} {refund.name or ''} {partner.display_name}".upper() # Buat Account Move (Journal Entry) account_move = self.env['account.move'].create({ @@ -976,10 +982,10 @@ class RefundSaleOrder(models.Model): 'refund_so_ids': [(6, 0, refund.sale_order_ids.ids)], 'partner_id': partner.id, }) - + admintf = refund.biaya_admin amount = refund.amount_refund # 450 Penerimaan Belum Teridentifikasi, 668 Penerimaan Belum Alokasi - second_account_id = 450 if refund.refund_type not in ['barang_kosong', 'barang_kosong_sebagian'] else 668 + second_account_id = 450 if refund.refund_type not in ['barang_kosong', 'barang_kosong_sebagian', 'barang_kosong_indent'] else 668 debit_line = { 'move_id': account_move.id, @@ -991,17 +997,29 @@ class RefundSaleOrder(models.Model): 'name': ref_text, } + adminline = { + 'move_id': account_move.id, + 'account_id': 555, + 'partner_id': partner.id, + 'currency_id': 12, + 'debit': admintf, + 'credit': 0.0, + 'name': admintex, + } + credit_line = { 'move_id': account_move.id, 'account_id': 389, # Intransit BCA 'partner_id': partner.id, 'currency_id': 12, 'debit': 0.0, - 'credit': amount, + 'credit': amount + admintf, 'name': ref_text, } - self.env['account.move.line'].create([debit_line, credit_line]) + journal_line = [debit_line, adminline, credit_line] if admintf > 0 else [debit_line, credit_line] + + self.env['account.move.line'].create(journal_line) return { 'name': _('Journal Entries'), -- cgit v1.2.3 From 2bfd932b541351452dda67039740ac1e72ca2326 Mon Sep 17 00:00:00 2001 From: HafidBuroiroh Date: Wed, 24 Sep 2025 17:16:35 +0700 Subject: refund --- indoteknik_custom/models/refund_sale_order.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/refund_sale_order.py b/indoteknik_custom/models/refund_sale_order.py index 6a6d10ea..96082447 100644 --- a/indoteknik_custom/models/refund_sale_order.py +++ b/indoteknik_custom/models/refund_sale_order.py @@ -1013,11 +1013,21 @@ class RefundSaleOrder(models.Model): 'partner_id': partner.id, 'currency_id': 12, 'debit': 0.0, - 'credit': amount + admintf, + 'credit': amount, 'name': ref_text, } - journal_line = [debit_line, adminline, credit_line] if admintf > 0 else [debit_line, credit_line] + credit_admin_line = { + 'move_id': account_move.id, + 'account_id': 389, # Intransit BCA + 'partner_id': partner.id, + 'currency_id': 12, + 'debit': 0.0, + 'credit': admintf, + 'name': admintex, + } + + journal_line = [debit_line, credit_line, adminline, credit_admin_line] if admintf > 0 else [debit_line, credit_line] self.env['account.move.line'].create(journal_line) -- cgit v1.2.3 From 9fa63c564fd46db5c3d412626bcf6e6e3f3032ca Mon Sep 17 00:00:00 2001 From: FIN-IT_AndriFP Date: Wed, 24 Sep 2025 17:18:48 +0700 Subject: fix --- indoteknik_custom/models/res_partner.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/res_partner.py b/indoteknik_custom/models/res_partner.py index 8aaee47e..ef1a5cf4 100644 --- a/indoteknik_custom/models/res_partner.py +++ b/indoteknik_custom/models/res_partner.py @@ -196,8 +196,7 @@ class ResPartner(models.Model): previous_payment_term_id = fields.Many2one( 'account.payment.term', - string='Previous Payment Term', - readonly=True + string='Previous Payment Term' ) -- cgit v1.2.3 From fabd151ff9a8b4c118062299653c77fd34ea89df Mon Sep 17 00:00:00 2001 From: HafidBuroiroh Date: Thu, 25 Sep 2025 11:22:26 +0700 Subject: add misc in refund --- indoteknik_custom/models/refund_sale_order.py | 33 ++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/refund_sale_order.py b/indoteknik_custom/models/refund_sale_order.py index 96082447..f4711faf 100644 --- a/indoteknik_custom/models/refund_sale_order.py +++ b/indoteknik_custom/models/refund_sale_order.py @@ -303,22 +303,35 @@ class RefundSaleOrder(models.Model): ('journal_id', '=', 7), ('state', '=', 'posted'), ]) + + misc = self.env['account.move'].search([ + ('ref', 'ilike', invoices.mapped('name')[0]), + ('journal_id', '=', 13), + ('state', '=', 'posted'), + ]) has_moves = bool(moves) has_piutangmdr = bool(piutangmdr) has_piutangbca = bool(piutangbca) + has_misc = bool(misc) ssos = self.env['sale.order'].browse(so_ids) has_settlement = any(so.payment_status == 'settlement' for so in ssos) sisa_uang_masuk = 0.0 if has_moves and has_settlement: sisa_uang_masuk = sum(moves.mapped('amount_total_signed')) + sum(ssos.mapped('gross_amount')) + elif has_moves and has_misc: + sisa_uang_masuk = sum(moves.mapped('amount_total_signed')) + sum(misc.mapped('amount_total_signed')) elif has_moves: sisa_uang_masuk = sum(moves.mapped('amount_total_signed')) elif has_settlement: sisa_uang_masuk = sum(ssos.mapped('gross_amount')) + elif has_piutangbca and has_misc: + sisa_uang_masuk = sum(piutangbca.mapped('amount_total_signed')) + sum(misc.mapped('amount_total_signed')) elif has_piutangbca: sisa_uang_masuk = sum(piutangbca.mapped('amount_total_signed')) + elif has_piutangmdr and has_misc: + sisa_uang_masuk = sum(piutangmdr.mapped('amount_total_signed')) + sum(misc.mapped('amount_total_signed')) elif has_piutangmdr: sisa_uang_masuk = sum(piutangmdr.mapped('amount_total_signed')) else: @@ -528,8 +541,14 @@ class RefundSaleOrder(models.Model): ('journal_id', '=', 7), ('state', '=', 'posted'), ]) + + misc = self.env['account.move'].search([ + ('ref', 'ilike', invoice_ids.mapped('name')[0]), + ('journal_id', '=', 13), + ('state', '=', 'posted'), + ]) - all_moves = moves | piutangbca | piutangmdr + all_moves = moves | piutangbca | piutangmdr | misc for move in all_moves: url = f"/web#id={move.id}&model=account.move&view_type=form" @@ -581,21 +600,33 @@ class RefundSaleOrder(models.Model): ('journal_id', '=', 7), ('state', '=', 'posted'), ]) + misc = self.env['account.move'].search([ + ('ref', 'ilike', all_invoices.mapped('name')[0]), + ('journal_id', '=', 13), + ('state', '=', 'posted'), + ]) has_moves = bool(moves) has_piutangmdr = bool(piutangmdr) has_piutangbca = bool(piutangbca) + has_misc = bool(misc) ssos = self.env['sale.order'].browse(so_ids) has_settlement = any(so.payment_status == 'settlement' for so in ssos) sisa_uang_masuk = 0.0 if has_moves and has_settlement: sisa_uang_masuk = sum(moves.mapped('amount_total_signed')) + sum(ssos.mapped('gross_amount')) + if has_moves and has_misc: + sisa_uang_masuk = sum(moves.mapped('amount_total_signed')) + sum(misc.mapped('amount_total_signed')) elif has_moves: sisa_uang_masuk = sum(moves.mapped('amount_total_signed')) elif has_settlement: sisa_uang_masuk = sum(ssos.mapped('gross_amount')) + elif has_piutangbca and has_misc: + sisa_uang_masuk = sum(piutangbca.mapped('amount_total_signed')) + sum(misc.mapped('amount_total_signed')) elif has_piutangbca: sisa_uang_masuk = sum(piutangbca.mapped('amount_total_signed')) + elif has_piutangmdr and has_misc: + sisa_uang_masuk = sum(piutangmdr.mapped('amount_total_signed')) + sum(misc.mapped('amount_total_signed')) elif has_piutangmdr: sisa_uang_masuk = sum(piutangmdr.mapped('amount_total_signed')) -- cgit v1.2.3 From 88a5f39629ac6f5700af27a8144dd3a761e5aa41 Mon Sep 17 00:00:00 2001 From: HafidBuroiroh Date: Thu, 25 Sep 2025 13:31:45 +0700 Subject: refund --- indoteknik_custom/models/refund_sale_order.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/refund_sale_order.py b/indoteknik_custom/models/refund_sale_order.py index f4711faf..eca781e2 100644 --- a/indoteknik_custom/models/refund_sale_order.py +++ b/indoteknik_custom/models/refund_sale_order.py @@ -542,11 +542,14 @@ class RefundSaleOrder(models.Model): ('state', '=', 'posted'), ]) - misc = self.env['account.move'].search([ - ('ref', 'ilike', invoice_ids.mapped('name')[0]), - ('journal_id', '=', 13), - ('state', '=', 'posted'), - ]) + misc = self.env['account.move'] + if invoice_ids: + invoice_name = invoice_ids.mapped('name')[0] + misc = self.env['account.move'].search([ + ('ref', 'ilike', invoice_name), + ('journal_id', '=', 13), + ('state', '=', 'posted'), + ]) all_moves = moves | piutangbca | piutangmdr | misc -- cgit v1.2.3 From 07502e1f93e166abea766c8b4b50ed2e94639df8 Mon Sep 17 00:00:00 2001 From: HafidBuroiroh Date: Thu, 25 Sep 2025 13:44:25 +0700 Subject: refund --- indoteknik_custom/models/refund_sale_order.py | 3 +++ 1 file changed, 3 insertions(+) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/refund_sale_order.py b/indoteknik_custom/models/refund_sale_order.py index eca781e2..2a6a9d9b 100644 --- a/indoteknik_custom/models/refund_sale_order.py +++ b/indoteknik_custom/models/refund_sale_order.py @@ -306,6 +306,7 @@ class RefundSaleOrder(models.Model): misc = self.env['account.move'].search([ ('ref', 'ilike', invoices.mapped('name')[0]), + ('ref', 'not ilike', 'reklas'), ('journal_id', '=', 13), ('state', '=', 'posted'), ]) @@ -547,6 +548,7 @@ class RefundSaleOrder(models.Model): invoice_name = invoice_ids.mapped('name')[0] misc = self.env['account.move'].search([ ('ref', 'ilike', invoice_name), + ('ref', 'not ilike', 'reklas'), ('journal_id', '=', 13), ('state', '=', 'posted'), ]) @@ -605,6 +607,7 @@ class RefundSaleOrder(models.Model): ]) misc = self.env['account.move'].search([ ('ref', 'ilike', all_invoices.mapped('name')[0]), + ('ref', 'not ilike', 'reklas'), ('journal_id', '=', 13), ('state', '=', 'posted'), ]) -- cgit v1.2.3 From 49368fcb139eac9de0f1787fdd1477570ed9dbda Mon Sep 17 00:00:00 2001 From: Miqdad Date: Thu, 25 Sep 2025 16:03:29 +0700 Subject: global pj seen --- indoteknik_custom/models/purchasing_job.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/purchasing_job.py b/indoteknik_custom/models/purchasing_job.py index db733b5a..d830bf60 100644 --- a/indoteknik_custom/models/purchasing_job.py +++ b/indoteknik_custom/models/purchasing_job.py @@ -29,20 +29,25 @@ class PurchasingJob(models.Model): so_number = fields.Text(string='SO Number', copy=False) check_pj = fields.Boolean(compute='_get_check_pj', string='Linked') + def action_open_job_detail(self): self.ensure_one() Seen = self.env['purchasing.job.seen'] + seen = Seen.search([ - ('user_id', '=', self.env.uid), ('product_id', '=', self.product_id.id) ], limit=1) if seen: - seen.so_snapshot = self.so_number - seen.seen_date = fields.Datetime.now() + # update snapshot & tanggal + (opsional) siapa yg terakhir lihat + seen.write({ + 'so_snapshot': self.so_number, + 'seen_date': fields.Datetime.now(), + 'user_id': self.env.user.id, # kalau user_id tetap dipakai sbg last_seen_by + }) else: Seen.create({ - 'user_id': self.env.uid, + 'user_id': self.env.user.id, # atau False kalau mau kosongkan 'product_id': self.product_id.id, 'so_snapshot': self.so_number, }) @@ -56,17 +61,13 @@ class PurchasingJob(models.Model): 'target': 'current', } - @api.depends('so_number') def _get_check_pj(self): + Seen = self.env['purchasing.job.seen'] for rec in self: - seen = self.env['purchasing.job.seen'].search([ - ('user_id', '=', self.env.uid), - ('product_id', '=', rec.product_id.id) - ], limit=1) + seen = Seen.search([('product_id', '=', rec.product_id.id)], limit=1) rec.check_pj = bool(seen and seen.so_snapshot == rec.so_number) - def unlink(self): # Example: Delete related records from the underlying model underlying_records = self.env['purchasing.job'].search([ -- cgit v1.2.3 From 1299851d0d6280d92ccc4123a82ea083a92bc7ec Mon Sep 17 00:00:00 2001 From: Miqdad Date: Thu, 25 Sep 2025 16:06:22 +0700 Subject: global pj seen --- indoteknik_custom/models/purchasing_job.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/purchasing_job.py b/indoteknik_custom/models/purchasing_job.py index d830bf60..3151f0f6 100644 --- a/indoteknik_custom/models/purchasing_job.py +++ b/indoteknik_custom/models/purchasing_job.py @@ -39,15 +39,13 @@ class PurchasingJob(models.Model): ], limit=1) if seen: - # update snapshot & tanggal + (opsional) siapa yg terakhir lihat seen.write({ 'so_snapshot': self.so_number, 'seen_date': fields.Datetime.now(), - 'user_id': self.env.user.id, # kalau user_id tetap dipakai sbg last_seen_by - }) + 'user_id': self.env.user.id, }) else: Seen.create({ - 'user_id': self.env.user.id, # atau False kalau mau kosongkan + 'user_id': self.env.user.id, 'product_id': self.product_id.id, 'so_snapshot': self.so_number, }) -- cgit v1.2.3 From 7fed48782b6790be66cf8134eacc36881eeb29d4 Mon Sep 17 00:00:00 2001 From: HafidBuroiroh Date: Thu, 25 Sep 2025 16:11:07 +0700 Subject: refund --- indoteknik_custom/models/refund_sale_order.py | 86 ++++++++++++++++++++++++++- indoteknik_custom/models/sale_order.py | 17 ++++++ 2 files changed, 101 insertions(+), 2 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/refund_sale_order.py b/indoteknik_custom/models/refund_sale_order.py index 2a6a9d9b..ae8df4ef 100644 --- a/indoteknik_custom/models/refund_sale_order.py +++ b/indoteknik_custom/models/refund_sale_order.py @@ -310,8 +310,22 @@ class RefundSaleOrder(models.Model): ('journal_id', '=', 13), ('state', '=', 'posted'), ]) + moves2 = self.env['account.move'] + if so_ids: + so_names = self.env['sale.order'].browse(so_ids).mapped('name') + domain = [ + ('journal_id', '=', 11), + ('state', '=', 'posted'), + ('ref', 'ilike', 'dp') + ] + if so_names: + domain += ['|'] * (len(so_names) - 1) + for n in so_names: + domain.append(('ref', 'ilike', n)) + moves2 = self.env['account.move'].search(domain) has_moves = bool(moves) + has_moves2 = bool(moves2) has_piutangmdr = bool(piutangmdr) has_piutangbca = bool(piutangbca) has_misc = bool(misc) @@ -319,20 +333,38 @@ class RefundSaleOrder(models.Model): has_settlement = any(so.payment_status == 'settlement' for so in ssos) sisa_uang_masuk = 0.0 + if has_moves and has_settlement: sisa_uang_masuk = sum(moves.mapped('amount_total_signed')) + sum(ssos.mapped('gross_amount')) + elif has_moves2: + sisa_uang_masuk = sum(moves2.mapped('amount_total_signed')) elif has_moves and has_misc: sisa_uang_masuk = sum(moves.mapped('amount_total_signed')) + sum(misc.mapped('amount_total_signed')) + elif has_moves and has_misc and has_moves2: + sisa_uang_masuk = sum(moves.mapped('amount_total_signed')) + sum(misc.mapped('amount_total_signed')) + sum(moves2.mapped('amount_total_signed')) + elif has_moves and has_moves2: + sisa_uang_masuk = sum(moves.mapped('amount_total_signed')) + sum(moves2.mapped('amount_total_signed')) elif has_moves: sisa_uang_masuk = sum(moves.mapped('amount_total_signed')) + elif has_settlement: sisa_uang_masuk = sum(ssos.mapped('gross_amount')) + + elif has_piutangbca and has_misc and has_moves2: + sisa_uang_masuk = sum(piutangbca.mapped('amount_total_signed')) + sum(misc.mapped('amount_total_signed')) + sum(moves2.mapped('amount_total_signed')) elif has_piutangbca and has_misc: sisa_uang_masuk = sum(piutangbca.mapped('amount_total_signed')) + sum(misc.mapped('amount_total_signed')) + elif has_piutangbca and has_moves2: + sisa_uang_masuk = sum(piutangbca.mapped('amount_total_signed')) + sum(moves2.mapped('amount_total_signed')) elif has_piutangbca: sisa_uang_masuk = sum(piutangbca.mapped('amount_total_signed')) + + elif has_piutangmdr and has_misc and has_moves2: + sisa_uang_masuk = sum(piutangmdr.mapped('amount_total_signed')) + sum(misc.mapped('amount_total_signed')) + sum(moves2.mapped('amount_total_signed')) elif has_piutangmdr and has_misc: sisa_uang_masuk = sum(piutangmdr.mapped('amount_total_signed')) + sum(misc.mapped('amount_total_signed')) + elif has_piutangmdr and has_moves2: + sisa_uang_masuk = sum(piutangmdr.mapped('amount_total_signed')) + sum(misc.mapped('amount_total_signed')) elif has_piutangmdr: sisa_uang_masuk = sum(piutangmdr.mapped('amount_total_signed')) else: @@ -542,6 +574,21 @@ class RefundSaleOrder(models.Model): ('journal_id', '=', 7), ('state', '=', 'posted'), ]) + + moves2 = self.env['account.move'] + if rec.sale_order_ids: + so_names = rec.sale_order_ids.mapped('name') + + domain = [ + ('journal_id', '=', 11), + ('state', '=', 'posted'), + ('ref', 'ilike', 'dp') + ] + domain += ['|'] * (len(so_names) - 1) + for n in so_names: + domain.append(('ref', 'ilike', n)) + + moves2 = self.env['account.move'].search(domain) misc = self.env['account.move'] if invoice_ids: @@ -553,7 +600,7 @@ class RefundSaleOrder(models.Model): ('state', '=', 'posted'), ]) - all_moves = moves | piutangbca | piutangmdr | misc + all_moves = moves | piutangbca | piutangmdr | misc | moves2 for move in all_moves: url = f"/web#id={move.id}&model=account.move&view_type=form" @@ -611,7 +658,24 @@ class RefundSaleOrder(models.Model): ('journal_id', '=', 13), ('state', '=', 'posted'), ]) + moves2 = self.env['account.move'] + if so_ids: + so_records = self.env['sale.order'].browse(so_ids) + so_names = so_records.mapped('name') + + domain = [ + ('journal_id', '=', 11), + ('state', '=', 'posted'), + ('ref', 'ilike', 'dp') + ] + domain += ['|'] * (len(so_names) - 1) + for n in so_names: + domain.append(('ref', 'ilike', n)) + + moves2 = self.env['account.move'].search(domain) + has_moves = bool(moves) + has_moves2 = bool(moves2) has_piutangmdr = bool(piutangmdr) has_piutangbca = bool(piutangbca) has_misc = bool(misc) @@ -619,20 +683,38 @@ class RefundSaleOrder(models.Model): has_settlement = any(so.payment_status == 'settlement' for so in ssos) sisa_uang_masuk = 0.0 + if has_moves and has_settlement: sisa_uang_masuk = sum(moves.mapped('amount_total_signed')) + sum(ssos.mapped('gross_amount')) - if has_moves and has_misc: + elif has_moves2: + sisa_uang_masuk = sum(moves2.mapped('amount_total_signed')) + elif has_moves and has_misc: sisa_uang_masuk = sum(moves.mapped('amount_total_signed')) + sum(misc.mapped('amount_total_signed')) + elif has_moves and has_misc and has_moves2: + sisa_uang_masuk = sum(moves.mapped('amount_total_signed')) + sum(misc.mapped('amount_total_signed')) + sum(moves2.mapped('amount_total_signed')) + elif has_moves and has_moves2: + sisa_uang_masuk = sum(moves.mapped('amount_total_signed')) + sum(moves2.mapped('amount_total_signed')) elif has_moves: sisa_uang_masuk = sum(moves.mapped('amount_total_signed')) + elif has_settlement: sisa_uang_masuk = sum(ssos.mapped('gross_amount')) + elif has_piutangbca and has_misc: sisa_uang_masuk = sum(piutangbca.mapped('amount_total_signed')) + sum(misc.mapped('amount_total_signed')) + elif has_piutangbca and has_misc and has_moves2: + sisa_uang_masuk = sum(piutangbca.mapped('amount_total_signed')) + sum(misc.mapped('amount_total_signed')) + sum(moves2.mapped('amount_total_signed')) + elif has_piutangbca and has_moves2: + sisa_uang_masuk = sum(piutangbca.mapped('amount_total_signed')) + sum(moves2.mapped('amount_total_signed')) elif has_piutangbca: sisa_uang_masuk = sum(piutangbca.mapped('amount_total_signed')) + + elif has_piutangmdr and has_misc and has_moves2: + sisa_uang_masuk = sum(piutangmdr.mapped('amount_total_signed')) + sum(misc.mapped('amount_total_signed')) + sum(moves2.mapped('amount_total_signed')) elif has_piutangmdr and has_misc: sisa_uang_masuk = sum(piutangmdr.mapped('amount_total_signed')) + sum(misc.mapped('amount_total_signed')) + elif has_piutangmdr and has_moves2: + sisa_uang_masuk = sum(piutangmdr.mapped('amount_total_signed')) + sum(moves2.mapped('amount_total_signed')) elif has_piutangmdr: sisa_uang_masuk = sum(piutangmdr.mapped('amount_total_signed')) diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 39830ffc..36dcbb4e 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -3278,10 +3278,17 @@ class SaleOrder(models.Model): ('state', '=', 'posted'), ]) + moves2 = self.env['account.move'].search([ + ('ref', 'ilike', self.name), + ('journal_id', '=', 11), + ('state', '=', 'posted'), + ]) + # Default 0 total_uang_muka = 0.0 has_moves = bool(moves) + has_moves2 = bool(moves2) has_piutangmdr = bool(piutangmdr) has_piutangbca = bool(piutangbca) has_settlement = self.payment_status == 'settlement' @@ -3290,6 +3297,8 @@ class SaleOrder(models.Model): total_uang_muka = sum(moves.mapped('amount_total_signed')) + self.gross_amount elif has_moves: total_uang_muka = sum(moves.mapped('amount_total_signed')) + elif has_moves2: + total_uang_muka = sum(moves2.mapped('amount_total_signed')) elif has_settlement: total_uang_muka = self.gross_amount elif has_piutangbca: @@ -3377,12 +3386,20 @@ class SaleOrder(models.Model): ('state', '=', 'posted'), ]) + moves2 = self.env['account.move'].search([ + ('ref', 'ilike', order.name), + ('journal_id', '=', 11), + ('state', '=', 'posted'), + ]) + total_uang_muka = 0.0 if moves and order.payment_status == 'settlement': total_uang_muka = order.gross_amount + sum(moves.mapped('amount_total_signed')) or 0.0 elif moves: total_uang_muka = sum(moves.mapped('amount_total_signed')) or 0.0 + elif moves2: + total_uang_muka = sum(moves2.mapped('amount_total_signed')) or 0.0 elif order.payment_status == 'settlement': total_uang_muka = order.gross_amount else: -- cgit v1.2.3 From b29805b2540c357f420580aa746b376f0b4c54bd Mon Sep 17 00:00:00 2001 From: HafidBuroiroh Date: Thu, 25 Sep 2025 17:04:52 +0700 Subject: refund --- indoteknik_custom/models/refund_sale_order.py | 102 ++++++++++---------------- 1 file changed, 37 insertions(+), 65 deletions(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/refund_sale_order.py b/indoteknik_custom/models/refund_sale_order.py index ae8df4ef..820a58f7 100644 --- a/indoteknik_custom/models/refund_sale_order.py +++ b/indoteknik_custom/models/refund_sale_order.py @@ -333,41 +333,30 @@ class RefundSaleOrder(models.Model): has_settlement = any(so.payment_status == 'settlement' for so in ssos) sisa_uang_masuk = 0.0 + amounts = [] if has_moves and has_settlement: - sisa_uang_masuk = sum(moves.mapped('amount_total_signed')) + sum(ssos.mapped('gross_amount')) - elif has_moves2: - sisa_uang_masuk = sum(moves2.mapped('amount_total_signed')) - elif has_moves and has_misc: - sisa_uang_masuk = sum(moves.mapped('amount_total_signed')) + sum(misc.mapped('amount_total_signed')) - elif has_moves and has_misc and has_moves2: - sisa_uang_masuk = sum(moves.mapped('amount_total_signed')) + sum(misc.mapped('amount_total_signed')) + sum(moves2.mapped('amount_total_signed')) - elif has_moves and has_moves2: - sisa_uang_masuk = sum(moves.mapped('amount_total_signed')) + sum(moves2.mapped('amount_total_signed')) - elif has_moves: - sisa_uang_masuk = sum(moves.mapped('amount_total_signed')) - - elif has_settlement: - sisa_uang_masuk = sum(ssos.mapped('gross_amount')) - - elif has_piutangbca and has_misc and has_moves2: - sisa_uang_masuk = sum(piutangbca.mapped('amount_total_signed')) + sum(misc.mapped('amount_total_signed')) + sum(moves2.mapped('amount_total_signed')) - elif has_piutangbca and has_misc: - sisa_uang_masuk = sum(piutangbca.mapped('amount_total_signed')) + sum(misc.mapped('amount_total_signed')) - elif has_piutangbca and has_moves2: - sisa_uang_masuk = sum(piutangbca.mapped('amount_total_signed')) + sum(moves2.mapped('amount_total_signed')) - elif has_piutangbca: - sisa_uang_masuk = sum(piutangbca.mapped('amount_total_signed')) - - elif has_piutangmdr and has_misc and has_moves2: - sisa_uang_masuk = sum(piutangmdr.mapped('amount_total_signed')) + sum(misc.mapped('amount_total_signed')) + sum(moves2.mapped('amount_total_signed')) - elif has_piutangmdr and has_misc: - sisa_uang_masuk = sum(piutangmdr.mapped('amount_total_signed')) + sum(misc.mapped('amount_total_signed')) - elif has_piutangmdr and has_moves2: - sisa_uang_masuk = sum(piutangmdr.mapped('amount_total_signed')) + sum(misc.mapped('amount_total_signed')) - elif has_piutangmdr: - sisa_uang_masuk = sum(piutangmdr.mapped('amount_total_signed')) + amounts.append(sum(moves.mapped('amount_total_signed'))) + amounts.append(sum(ssos.mapped('gross_amount'))) else: + if has_moves: + amounts.append(sum(moves.mapped('amount_total_signed'))) + if has_settlement: + amounts.append(sum(ssos.mapped('gross_amount'))) + + # sisanya bisa dijumlahkan tanpa konflik + if has_moves2: + amounts.append(sum(moves2.mapped('amount_total_signed'))) + if has_piutangbca: + amounts.append(sum(piutangbca.mapped('amount_total_signed'))) + if has_piutangmdr: + amounts.append(sum(piutangmdr.mapped('amount_total_signed'))) + if has_misc: + amounts.append(sum(misc.mapped('amount_total_signed'))) + + sisa_uang_masuk = sum(amounts) + + if not sisa_uang_masuk: raise UserError( "❌ Tidak bisa melakukan refund karena SO tidak memiliki Record Uang Masuk " "(Journal Uang Muka / Payment Invoices / Midtrans Payment)." @@ -684,39 +673,22 @@ class RefundSaleOrder(models.Model): sisa_uang_masuk = 0.0 - if has_moves and has_settlement: - sisa_uang_masuk = sum(moves.mapped('amount_total_signed')) + sum(ssos.mapped('gross_amount')) - elif has_moves2: - sisa_uang_masuk = sum(moves2.mapped('amount_total_signed')) - elif has_moves and has_misc: - sisa_uang_masuk = sum(moves.mapped('amount_total_signed')) + sum(misc.mapped('amount_total_signed')) - elif has_moves and has_misc and has_moves2: - sisa_uang_masuk = sum(moves.mapped('amount_total_signed')) + sum(misc.mapped('amount_total_signed')) + sum(moves2.mapped('amount_total_signed')) - elif has_moves and has_moves2: - sisa_uang_masuk = sum(moves.mapped('amount_total_signed')) + sum(moves2.mapped('amount_total_signed')) - elif has_moves: - sisa_uang_masuk = sum(moves.mapped('amount_total_signed')) - - elif has_settlement: - sisa_uang_masuk = sum(ssos.mapped('gross_amount')) - - elif has_piutangbca and has_misc: - sisa_uang_masuk = sum(piutangbca.mapped('amount_total_signed')) + sum(misc.mapped('amount_total_signed')) - elif has_piutangbca and has_misc and has_moves2: - sisa_uang_masuk = sum(piutangbca.mapped('amount_total_signed')) + sum(misc.mapped('amount_total_signed')) + sum(moves2.mapped('amount_total_signed')) - elif has_piutangbca and has_moves2: - sisa_uang_masuk = sum(piutangbca.mapped('amount_total_signed')) + sum(moves2.mapped('amount_total_signed')) - elif has_piutangbca: - sisa_uang_masuk = sum(piutangbca.mapped('amount_total_signed')) - - elif has_piutangmdr and has_misc and has_moves2: - sisa_uang_masuk = sum(piutangmdr.mapped('amount_total_signed')) + sum(misc.mapped('amount_total_signed')) + sum(moves2.mapped('amount_total_signed')) - elif has_piutangmdr and has_misc: - sisa_uang_masuk = sum(piutangmdr.mapped('amount_total_signed')) + sum(misc.mapped('amount_total_signed')) - elif has_piutangmdr and has_moves2: - sisa_uang_masuk = sum(piutangmdr.mapped('amount_total_signed')) + sum(moves2.mapped('amount_total_signed')) - elif has_piutangmdr: - sisa_uang_masuk = sum(piutangmdr.mapped('amount_total_signed')) + amounts = [] + + if has_moves: + amounts.append(sum(moves.mapped('amount_total_signed'))) + if has_moves2: + amounts.append(sum(moves2.mapped('amount_total_signed'))) + if has_piutangbca: + amounts.append(sum(piutangbca.mapped('amount_total_signed'))) + if has_piutangmdr: + amounts.append(sum(piutangmdr.mapped('amount_total_signed'))) + if has_misc: + amounts.append(sum(misc.mapped('amount_total_signed'))) + if has_settlement: + amounts.append(sum(ssos.mapped('gross_amount'))) + + sisa_uang_masuk = sum(amounts) self.uang_masuk = sisa_uang_masuk - amount_refund_before -- cgit v1.2.3 From 3de421c1b8b21c56399c46f85c9d395023c68617 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Fri, 26 Sep 2025 14:54:35 +0700 Subject: add payment term in Due extension --- indoteknik_custom/models/account_move_due_extension.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/account_move_due_extension.py b/indoteknik_custom/models/account_move_due_extension.py index 40059bd9..55fc6c65 100644 --- a/indoteknik_custom/models/account_move_due_extension.py +++ b/indoteknik_custom/models/account_move_due_extension.py @@ -13,6 +13,7 @@ class DueExtension(models.Model): number = fields.Char(string='Document No', index=True, copy=False, readonly=True, tracking=True) partner_id = fields.Many2one('res.partner', string="Customer", readonly=True) + payment_term = fields.Char(string="Payment Term", readonly=True, compute='_compute_payment_term') order_id = fields.Many2one('sale.order', string="SO", readonly=True) amount_total = fields.Monetary( string="Amount Total SO", @@ -43,7 +44,12 @@ class DueExtension(models.Model): counter = fields.Integer(string="Counter", compute='_compute_counter') approve_by = fields.Many2one('res.users', string="Approve By", readonly=True) date_approve = fields.Datetime(string="Date Approve", readonly=True) - + + @api.depends('partner_id') + def _compute_payment_term(self): + for rec in self: + rec.payment_term = rec.partner_id.property_payment_term_id.name + @api.depends('order_id') def _compute_amount_total(self): for rec in self: -- cgit v1.2.3 From f714a29bc051d029d5ac34f46c3cabfcb64478e0 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Fri, 26 Sep 2025 16:56:13 +0700 Subject: add p stephan --- indoteknik_custom/models/refund_sale_order.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/refund_sale_order.py b/indoteknik_custom/models/refund_sale_order.py index 820a58f7..de9870f6 100644 --- a/indoteknik_custom/models/refund_sale_order.py +++ b/indoteknik_custom/models/refund_sale_order.py @@ -954,7 +954,7 @@ class RefundSaleOrder(models.Model): if not rec.status or rec.status == 'draft': rec.status = 'pengajuan1' - elif rec.status == 'pengajuan1' and self.env.user.id == 19: + elif rec.status == 'pengajuan1' and self.env.user.id in [19, 28]: rec.status = 'pengajuan2' rec.approved_by = f"{rec.approved_by}, {user_name}" if rec.approved_by else user_name rec.date_approved_sales = now -- cgit v1.2.3 From ec21c4721f35028fc9b2a61bd0dbc6e4bf600e74 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Sun, 28 Sep 2025 10:43:27 +0700 Subject: fix state retur ccm stuck at waiting for operation --- indoteknik_custom/models/tukar_guling.py | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) (limited to 'indoteknik_custom/models') diff --git a/indoteknik_custom/models/tukar_guling.py b/indoteknik_custom/models/tukar_guling.py index c683f75a..cb630a04 100644 --- a/indoteknik_custom/models/tukar_guling.py +++ b/indoteknik_custom/models/tukar_guling.py @@ -538,6 +538,11 @@ class TukarGuling(models.Model): self.state = 'approval_sales' def update_doc_state(self): + bu_pick = self.env['stock.picking'].search([ + ('origin', '=', self.operations.origin), + ('name', 'ilike', 'BU/PICK'), + ]) + # OUT tukar guling if self.operations.picking_type_id.id == 29 and self.return_type == 'tukar_guling': total_out = self.env['stock.picking'].search_count([ @@ -552,7 +557,26 @@ class TukarGuling(models.Model): if self.state == 'approved' and total_out > 0 and done_out == total_out: self.state = 'done' - # OUT revisi SO + #SO Lama (gk ada bu pick) + elif self.operations.picking_type_id.id == 29 and self.return_type == 'retur_so' and not bu_pick: + # so_lama = self.env['sale.order'].search([ + # ('name', '=', self.operations.origin), + # ('state', '=', 'done'), + # ('group_id.name', '=', self.operations.origin) + # ]) + total_ort = self.env['stock.picking'].search_count([ + ('tukar_guling_id', '=', self.id), + ('picking_type_id', '=', 74), + ]) + done_srt = self.env['stock.picking'].search([ + ('tukar_guling_id', '=', self.id), + ('picking_type_id', '=', 73), + ('state', '=', 'done') + ]) + if self.state == 'approved' and total_ort == 0 and done_srt and not bu_pick: + self.state = 'done' + + # OUT retur SO elif self.operations.picking_type_id.id == 29 and self.return_type == 'retur_so': total_ort = self.env['stock.picking'].search_count([ ('tukar_guling_id', '=', self.id), -- cgit v1.2.3