From db98db3e34ac47eeea0fc53f215cb483d6c5d5f9 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Wed, 9 Jul 2025 11:32:19 +0700 Subject: (andri) scheduler reminder due inv --- indoteknik_custom/__manifest__.py | 1 + indoteknik_custom/models/account_move.py | 54 +++++++++++++++++++++ .../views/mail_template_invoice_reminder.xml | 55 ++++++++++++++++++++++ 3 files changed, 110 insertions(+) create mode 100644 indoteknik_custom/views/mail_template_invoice_reminder.xml diff --git a/indoteknik_custom/__manifest__.py b/indoteknik_custom/__manifest__.py index 17cec7b6..21afc26f 100755 --- a/indoteknik_custom/__manifest__.py +++ b/indoteknik_custom/__manifest__.py @@ -97,6 +97,7 @@ 'views/mail_template_po.xml', 'views/mail_template_efaktur.xml', 'views/mail_template_invoice_po.xml', + 'views/mail_template_invoice_reminder.xml', 'views/price_group.xml', 'views/mrp_production.xml', 'views/apache_solr.xml', diff --git a/indoteknik_custom/models/account_move.py b/indoteknik_custom/models/account_move.py index b6627867..df79b9f6 100644 --- a/indoteknik_custom/models/account_move.py +++ b/indoteknik_custom/models/account_move.py @@ -8,12 +8,14 @@ import PyPDF2 import os import re from terbilang import Terbilang +from collections import defaultdict _logger = logging.getLogger(__name__) class AccountMove(models.Model): _inherit = 'account.move' + _description = 'Account Move' invoice_day_to_due = fields.Integer(string="Day to Due", compute="_compute_invoice_day_to_due") bill_day_to_due = fields.Date(string="Day to Due", compute="_compute_bill_day_to_due") date_send_fp = fields.Datetime(string="Tanggal Kirim Faktur Pajak") @@ -72,6 +74,58 @@ class AccountMove(models.Model): bill_id = fields.Many2one('account.move', string='Vendor Bill', domain=[('move_type', '=', 'in_invoice')], help='Bill asal dari proses reklas ini') down_payment = fields.Boolean('Down Payments?') + def send_due_invoice_reminder(self): + today = fields.Date.today() + reminder_days = [-7, -3, 0, 3, 7] + target_dates = [today + timedelta(days=delta) for delta in reminder_days] + target_dates_str = [d.isoformat() for d in target_dates] + + # Ganti nama partner untuk test jika perlu + partner = self.env['res.partner'].search([('name', 'ilike', 'PRIMA SEJAHTERA MARITIM')], limit=1) + if not partner: + _logger.info("Partner tidak ditemukan.") + return + + invoices = self.env['account.move'].search([ + ('move_type', '=', 'out_invoice'), + ('state', '=', 'posted'), + ('payment_state', 'not in', ['paid','in_payment', 'reversed']), + ('invoice_date_due', '>=', today - timedelta(days=7)), + ('invoice_date_due', '>=', today - timedelta(days=3)), + ('invoice_date_due', '<=', today + timedelta(days=3)), + ('invoice_date_due', '<=', today + timedelta(days=7)), + ('partner_id', '=', partner.id), + ]) + + _logger.info(f"Invoices tahap 1: {invoices}") + + # Filter berdasarkan term mengandung "tempo" + 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}") + + if not invoices: + _logger.info(f"Tidak ada invoice yang due untuk partner: {partner.name}") + return + + # Pastikan field compute jalan + invoices._compute_invoice_day_to_due() + + # Ambil template + template = self.env.ref('indoteknik_custom.mail_template_invoice_due_reminder') + + for inv in invoices: + try: + # Untuk test: override ke email pribadi Anda + email_values = { + 'email_to': 'andrifebriyadiputra@gmail.com', + 'email_from': 'finance@indoteknik.co.id', + } + template.send_mail(inv.id, force_send=True, email_values=email_values) + _logger.info(f"Reminder terkirim: {inv.name} → {email_values['email_to']}") + except Exception as e: + _logger.error(f"Gagal kirim email untuk {inv.name}: {str(e)}") # def name_get(self): # result = [] diff --git a/indoteknik_custom/views/mail_template_invoice_reminder.xml b/indoteknik_custom/views/mail_template_invoice_reminder.xml new file mode 100644 index 00000000..b19171b2 --- /dev/null +++ b/indoteknik_custom/views/mail_template_invoice_reminder.xml @@ -0,0 +1,55 @@ + + + + + Invoice Reminder: Due Date Notification + + [Reminder] Invoice ${object.name} is Due Soon + finance@indoteknik.co.id + finance@indoteknik.co.id + andrifebriyadiputra@gmail.com + +
+

Dengan Hormat Bpk/Ibu ${object.partner_id.name},

+ +

Berikut adalah detail invoice Anda yang sudah mendekati atau telah jatuh tempo:

+ + + + + + + + + + + + + + + + + + + + + + +
Invoice NumberTanggal InvoiceJatuh TempoSisa HariTotalReferensi
${object.name}${format_date(object.invoice_date)}${format_date(object.invoice_date_due)}${object.invoice_day_to_due}${format_amount(object.amount_total, object.currency_id)}${object.ref or '-'}
+ +

Mohon segera melakukan proses pembayaran sebelum jatuh tempo.

+ +

Terima kasih atas perhatian dan kerjasamanya.

+ +

+ Hormat Kami,
+ PT. INDOTEKNIK DOTCOM GEMILANG
+ Jl. Bandengan Utara 85A No. 8-9, Penjaringan, Jakarta Utara
+ Telp: 021-2933 8828 / 29 | Email: finance@indoteknik.co.id +

+
+
+ +
+
+
-- cgit v1.2.3 From 0d43c8987d05543c20b1ea26e6645afcf153691b Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Wed, 9 Jul 2025 13:04:08 +0700 Subject: (andri) test --- indoteknik_custom/models/account_move.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indoteknik_custom/models/account_move.py b/indoteknik_custom/models/account_move.py index df79b9f6..059e8330 100644 --- a/indoteknik_custom/models/account_move.py +++ b/indoteknik_custom/models/account_move.py @@ -81,7 +81,7 @@ class AccountMove(models.Model): target_dates_str = [d.isoformat() for d in target_dates] # Ganti nama partner untuk test jika perlu - partner = self.env['res.partner'].search([('name', 'ilike', 'PRIMA SEJAHTERA MARITIM')], limit=1) + partner = self.env['res.partner'].search([('name', 'ilike', 'ROYALTAMA MULIA KONTRAKTORINDO')], limit=1) if not partner: _logger.info("Partner tidak ditemukan.") return -- cgit v1.2.3 From 5e905a9af7f6bb928c44cad2d47f8c6e69662bd2 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Wed, 9 Jul 2025 13:08:41 +0700 Subject: (andri) test --- indoteknik_custom/models/account_move.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indoteknik_custom/models/account_move.py b/indoteknik_custom/models/account_move.py index 059e8330..8ef3d273 100644 --- a/indoteknik_custom/models/account_move.py +++ b/indoteknik_custom/models/account_move.py @@ -81,7 +81,7 @@ class AccountMove(models.Model): target_dates_str = [d.isoformat() for d in target_dates] # Ganti nama partner untuk test jika perlu - partner = self.env['res.partner'].search([('name', 'ilike', 'ROYALTAMA MULIA KONTRAKTORINDO')], limit=1) + partner = self.env['res.partner'].search([('name', 'ilike', 'DAYA ANUGRAH MULYA')], limit=1) if not partner: _logger.info("Partner tidak ditemukan.") return -- cgit v1.2.3 From c72db0d0fa214e6691fa9a293020e7091a9c82c2 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Wed, 9 Jul 2025 13:42:33 +0700 Subject: (andri) fix invoices --- indoteknik_custom/models/account_move.py | 36 ++++++++++-------- .../views/mail_template_invoice_reminder.xml | 43 ++++++++++++---------- 2 files changed, 45 insertions(+), 34 deletions(-) diff --git a/indoteknik_custom/models/account_move.py b/indoteknik_custom/models/account_move.py index 8ef3d273..e63f4cb2 100644 --- a/indoteknik_custom/models/account_move.py +++ b/indoteknik_custom/models/account_move.py @@ -76,12 +76,9 @@ class AccountMove(models.Model): def send_due_invoice_reminder(self): today = fields.Date.today() - reminder_days = [-7, -3, 0, 3, 7] - target_dates = [today + timedelta(days=delta) for delta in reminder_days] - target_dates_str = [d.isoformat() for d in target_dates] # Ganti nama partner untuk test jika perlu - partner = self.env['res.partner'].search([('name', 'ilike', 'DAYA ANUGRAH MULYA')], limit=1) + partner = self.env['res.partner'].search([('name', 'ilike', 'TIRTA FRESINDO JAYA')], limit=1) if not partner: _logger.info("Partner tidak ditemukan.") return @@ -92,6 +89,7 @@ class AccountMove(models.Model): ('payment_state', 'not in', ['paid','in_payment', 'reversed']), ('invoice_date_due', '>=', today - timedelta(days=7)), ('invoice_date_due', '>=', today - timedelta(days=3)), + ('invoice_date_due', '>=', today - timedelta(days=0)), ('invoice_date_due', '<=', today + timedelta(days=3)), ('invoice_date_due', '<=', today + timedelta(days=7)), ('partner_id', '=', partner.id), @@ -115,17 +113,25 @@ class AccountMove(models.Model): # Ambil template template = self.env.ref('indoteknik_custom.mail_template_invoice_due_reminder') - for inv in invoices: - try: - # Untuk test: override ke email pribadi Anda - email_values = { - 'email_to': 'andrifebriyadiputra@gmail.com', - 'email_from': 'finance@indoteknik.co.id', - } - template.send_mail(inv.id, force_send=True, email_values=email_values) - _logger.info(f"Reminder terkirim: {inv.name} → {email_values['email_to']}") - except Exception as e: - _logger.error(f"Gagal kirim email untuk {inv.name}: {str(e)}") + try: + template.with_context(invoices=invoices).send_mail(partner.id, force_send=True, email_values={ + 'email_to': 'andrifebriyadiputra@gmail.com', # test override + }) + _logger.info(f"Reminder terkirim ke {partner.name} → {len(invoices)} invoice") + except Exception as e: + _logger.error(f"Gagal kirim email ke {partner.name}: {str(e)}") + + # for inv in invoices: + # try: + # # Untuk test: override ke email pribadi Anda + # email_values = { + # 'email_to': 'andrifebriyadiputra@gmail.com', + # 'email_from': 'finance@indoteknik.co.id', + # } + # template.send_mail(inv.id, force_send=True, email_values=email_values) + # _logger.info(f"Reminder terkirim: {inv.name} → {email_values['email_to']}") + # except Exception as e: + # _logger.error(f"Gagal kirim email untuk {inv.name}: {str(e)}") # def name_get(self): # result = [] diff --git a/indoteknik_custom/views/mail_template_invoice_reminder.xml b/indoteknik_custom/views/mail_template_invoice_reminder.xml index b19171b2..176a68ba 100644 --- a/indoteknik_custom/views/mail_template_invoice_reminder.xml +++ b/indoteknik_custom/views/mail_template_invoice_reminder.xml @@ -3,22 +3,23 @@ Invoice Reminder: Due Date Notification - - [Reminder] Invoice ${object.name} is Due Soon + + [Reminder] Invoice Due Summary for ${object.name} finance@indoteknik.co.id - finance@indoteknik.co.id andrifebriyadiputra@gmail.com +
-

Dengan Hormat Bpk/Ibu ${object.partner_id.name},

+

Dengan Hormat Bpk/Ibu ${object.name},

-

Berikut adalah detail invoice Anda yang sudah mendekati atau telah jatuh tempo:

+

Berikut adalah daftar invoice Anda yang mendekati atau telah jatuh tempo:

+ - + @@ -26,30 +27,34 @@ - - - - - - - - + + + + + + + + + + + + +
No Invoice NumberTanggal InvoiceTanggal Jatuh Tempo Sisa Hari Total
${object.name}${format_date(object.invoice_date)}${format_date(object.invoice_date_due)}${object.invoice_day_to_due}${format_amount(object.amount_total, object.currency_id)}${object.ref or '-'}
-

Mohon segera melakukan proses pembayaran sebelum jatuh tempo.

- -

Terima kasih atas perhatian dan kerjasamanya.

+

Mohon segera melakukan proses pembayaran. Terima kasih.

Hormat Kami,
PT. INDOTEKNIK DOTCOM GEMILANG
- Jl. Bandengan Utara 85A No. 8-9, Penjaringan, Jakarta Utara
- Telp: 021-2933 8828 / 29 | Email: finance@indoteknik.co.id + Telp: 021-2933 8828 / 29
+ Email: finance@indoteknik.co.id

+
-- cgit v1.2.3 From 028480352e86ac8cbfef5ea4834caf111ebfb3d4 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Wed, 9 Jul 2025 14:04:27 +0700 Subject: (andri) try --- indoteknik_custom/models/account_move.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/indoteknik_custom/models/account_move.py b/indoteknik_custom/models/account_move.py index e63f4cb2..436dfcaa 100644 --- a/indoteknik_custom/models/account_move.py +++ b/indoteknik_custom/models/account_move.py @@ -114,12 +114,15 @@ class AccountMove(models.Model): template = self.env.ref('indoteknik_custom.mail_template_invoice_due_reminder') try: - template.with_context(invoices=invoices).send_mail(partner.id, force_send=True, email_values={ - 'email_to': 'andrifebriyadiputra@gmail.com', # test override - }) - _logger.info(f"Reminder terkirim ke {partner.name} → {len(invoices)} invoice") + email_values = template.with_context(invoices=invoices).generate_email(partner.id) + email_values['email_to'] = 'andrifebriyadiputra@gmail.com' # override untuk test + email_values['email_from'] = 'finance@indoteknik.co.id' + + self.env['mail.mail'].create(email_values).send() + _logger.info(f"[Reminder Terkirim] ke {partner.name} → {len(invoices)} invoice") except Exception as e: - _logger.error(f"Gagal kirim email ke {partner.name}: {str(e)}") + _logger.error(f"[Reminder Gagal] ke {partner.name} → {str(e)}") + # for inv in invoices: # try: -- cgit v1.2.3 From 00b357fa35ff809c153a5aeaf67f97a00715e463 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Wed, 9 Jul 2025 14:33:52 +0700 Subject: (andr) try2 --- indoteknik_custom/models/account_move.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/indoteknik_custom/models/account_move.py b/indoteknik_custom/models/account_move.py index 436dfcaa..c3908daf 100644 --- a/indoteknik_custom/models/account_move.py +++ b/indoteknik_custom/models/account_move.py @@ -114,7 +114,9 @@ class AccountMove(models.Model): template = self.env.ref('indoteknik_custom.mail_template_invoice_due_reminder') try: - email_values = template.with_context(invoices=invoices).generate_email(partner.id) + email_values = template.with_context(invoices=invoices).generate_email( + partner.id, + ['subject', 'body_html', 'email_to', 'email_from']) email_values['email_to'] = 'andrifebriyadiputra@gmail.com' # override untuk test email_values['email_from'] = 'finance@indoteknik.co.id' -- cgit v1.2.3 From c7781579662dbf2b66dcaff5d1b3737e9e32c3c5 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Thu, 10 Jul 2025 14:08:38 +0700 Subject: sort dunning run based on invoice num --- indoteknik_custom/models/dunning_run.py | 4 +++- indoteknik_custom/models/sale_order_line.py | 3 +++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/indoteknik_custom/models/dunning_run.py b/indoteknik_custom/models/dunning_run.py index bb53fc0c..682c7b0b 100644 --- a/indoteknik_custom/models/dunning_run.py +++ b/indoteknik_custom/models/dunning_run.py @@ -1,3 +1,4 @@ +from Tools.scripts.dutree import store from odoo import models, api, fields from odoo.exceptions import AccessError, UserError, ValidationError from datetime import timedelta @@ -123,8 +124,9 @@ class DunningRunLine(models.Model): _name = 'dunning.run.line' _description = 'Dunning Run Line' # _order = 'dunning_id, id' - _order = 'invoice_id desc, id' + _order = 'invoice_number asc, id' + invoice_number = fields.Char('Invoice Number', related='invoice_id.name', store=True) dunning_id = fields.Many2one('dunning.run', string='Dunning Ref', required=True, ondelete='cascade', index=True, copy=False) partner_id = fields.Many2one('res.partner', string='Customer') invoice_id = fields.Many2one('account.move', string='Invoice') diff --git a/indoteknik_custom/models/sale_order_line.py b/indoteknik_custom/models/sale_order_line.py index 291940ed..c4b5381a 100644 --- a/indoteknik_custom/models/sale_order_line.py +++ b/indoteknik_custom/models/sale_order_line.py @@ -5,6 +5,9 @@ from datetime import datetime, timedelta class SaleOrderLine(models.Model): _inherit = 'sale.order.line' + + hold_item = fields.Boolean('Hold?', default=False) + item_margin = fields.Float('Margin', compute='compute_item_margin', help="Total Margin in Sales Order Header") item_before_margin = fields.Float('Before Margin', compute='compute_item_before_margin', help="Total Margin in Sales Order Header") -- cgit v1.2.3 From d5a9aa70794de3604a1db9fdcb5f6952afa4a52b Mon Sep 17 00:00:00 2001 From: Miqdad Date: Thu, 10 Jul 2025 14:12:52 +0700 Subject: sort dunning run based on invoice num --- indoteknik_custom/models/dunning_run.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indoteknik_custom/models/dunning_run.py b/indoteknik_custom/models/dunning_run.py index 682c7b0b..fdc730de 100644 --- a/indoteknik_custom/models/dunning_run.py +++ b/indoteknik_custom/models/dunning_run.py @@ -126,7 +126,7 @@ class DunningRunLine(models.Model): # _order = 'dunning_id, id' _order = 'invoice_number asc, id' - invoice_number = fields.Char('Invoice Number', related='invoice_id.name', store=True) + invoice_number = fields.Char('Invoice Number', related='invoice_id.name') dunning_id = fields.Many2one('dunning.run', string='Dunning Ref', required=True, ondelete='cascade', index=True, copy=False) partner_id = fields.Many2one('res.partner', string='Customer') invoice_id = fields.Many2one('account.move', string='Invoice') -- cgit v1.2.3 From d42597543c17a72173d50aa66939c0f3ab776363 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Thu, 10 Jul 2025 14:14:06 +0700 Subject: sort dunning run based on invoice num --- indoteknik_custom/models/sale_order_line.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/indoteknik_custom/models/sale_order_line.py b/indoteknik_custom/models/sale_order_line.py index c4b5381a..2a0160e8 100644 --- a/indoteknik_custom/models/sale_order_line.py +++ b/indoteknik_custom/models/sale_order_line.py @@ -6,8 +6,6 @@ from datetime import datetime, timedelta class SaleOrderLine(models.Model): _inherit = 'sale.order.line' - hold_item = fields.Boolean('Hold?', default=False) - item_margin = fields.Float('Margin', compute='compute_item_margin', help="Total Margin in Sales Order Header") item_before_margin = fields.Float('Before Margin', compute='compute_item_before_margin', help="Total Margin in Sales Order Header") -- cgit v1.2.3 From 6ab4b390aec6ae68e7c3a43fae6bfd730ce54230 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Thu, 10 Jul 2025 14:18:37 +0700 Subject: sort dunning run based on invoice num --- indoteknik_custom/models/dunning_run.py | 1 - 1 file changed, 1 deletion(-) diff --git a/indoteknik_custom/models/dunning_run.py b/indoteknik_custom/models/dunning_run.py index fdc730de..d7178cb4 100644 --- a/indoteknik_custom/models/dunning_run.py +++ b/indoteknik_custom/models/dunning_run.py @@ -1,4 +1,3 @@ -from Tools.scripts.dutree import store from odoo import models, api, fields from odoo.exceptions import AccessError, UserError, ValidationError from datetime import timedelta -- cgit v1.2.3 From 710c5100c5ba4f0a02210418e96a14b66ca03698 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Thu, 10 Jul 2025 14:56:54 +0700 Subject: sort dunning run based on invoice num --- indoteknik_custom/models/dunning_run.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/indoteknik_custom/models/dunning_run.py b/indoteknik_custom/models/dunning_run.py index d7178cb4..341b206d 100644 --- a/indoteknik_custom/models/dunning_run.py +++ b/indoteknik_custom/models/dunning_run.py @@ -92,10 +92,19 @@ class DunningRun(models.Model): ('move_type', '=', 'out_invoice'), ('state', '=', 'posted'), ('partner_id', '=', partner.id), - # ('amount_residual_signed', '>', 0), ('date_kirim_tukar_faktur', '=', False), ] - invoices = self.env['account.move'].search(query, order='invoice_date') + invoices = self.env['account.move'].search(query) + + # sort by last number in invoice name + try: + invoices = sorted( + invoices, + key=lambda x: int((x.name or '0').split('/')[-1]) + ) + except Exception as e: + _logger.error('Gagal sort invoice number: %s', e) + count = 0 for invoice in invoices: self.env['dunning.run.line'].create([{ -- cgit v1.2.3 From 8e80bb240aa74c8b2942d983e73ff501f5b8defc Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Thu, 10 Jul 2025 15:12:36 +0700 Subject: (andri) rev mutiple invoices reminder --- indoteknik_custom/models/account_move.py | 78 ++++++++++++++++------ .../views/mail_template_invoice_reminder.xml | 25 ++----- 2 files changed, 65 insertions(+), 38 deletions(-) diff --git a/indoteknik_custom/models/account_move.py b/indoteknik_custom/models/account_move.py index df79b9f6..fd72d566 100644 --- a/indoteknik_custom/models/account_move.py +++ b/indoteknik_custom/models/account_move.py @@ -9,6 +9,7 @@ import os import re from terbilang import Terbilang from collections import defaultdict +from odoo.tools.misc import formatLang _logger = logging.getLogger(__name__) @@ -76,12 +77,8 @@ class AccountMove(models.Model): def send_due_invoice_reminder(self): today = fields.Date.today() - reminder_days = [-7, -3, 0, 3, 7] - target_dates = [today + timedelta(days=delta) for delta in reminder_days] - target_dates_str = [d.isoformat() for d in target_dates] - # Ganti nama partner untuk test jika perlu - partner = self.env['res.partner'].search([('name', 'ilike', 'PRIMA SEJAHTERA MARITIM')], limit=1) + partner = self.env['res.partner'].search([('name', 'ilike', 'TIRTA FRESINDO JAYA')], limit=1) if not partner: _logger.info("Partner tidak ditemukan.") return @@ -92,6 +89,7 @@ class AccountMove(models.Model): ('payment_state', 'not in', ['paid','in_payment', 'reversed']), ('invoice_date_due', '>=', today - timedelta(days=7)), ('invoice_date_due', '>=', today - timedelta(days=3)), + ('invoice_date_due', '=', today), ('invoice_date_due', '<=', today + timedelta(days=3)), ('invoice_date_due', '<=', today + timedelta(days=7)), ('partner_id', '=', partner.id), @@ -99,7 +97,6 @@ class AccountMove(models.Model): _logger.info(f"Invoices tahap 1: {invoices}") - # Filter berdasarkan term mengandung "tempo" invoices = invoices.filtered( lambda inv: inv.invoice_payment_term_id and 'tempo' in (inv.invoice_payment_term_id.name or '').lower() ) @@ -109,23 +106,64 @@ class AccountMove(models.Model): _logger.info(f"Tidak ada invoice yang due untuk partner: {partner.name}") return - # Pastikan field compute jalan - invoices._compute_invoice_day_to_due() + grouped = {} + for inv in invoices: + grouped.setdefault(inv.partner_id, []).append(inv) - # Ambil template template = self.env.ref('indoteknik_custom.mail_template_invoice_due_reminder') - for inv in invoices: - try: - # Untuk test: override ke email pribadi Anda - email_values = { - 'email_to': 'andrifebriyadiputra@gmail.com', - 'email_from': 'finance@indoteknik.co.id', - } - template.send_mail(inv.id, force_send=True, email_values=email_values) - _logger.info(f"Reminder terkirim: {inv.name} → {email_values['email_to']}") - except Exception as e: - _logger.error(f"Gagal kirim email untuk {inv.name}: {str(e)}") + for partner, invs in grouped.items(): + if not partner.email: + _logger.info(f"Partner {partner.name} tidak memiliki email") + continue + + invoice_table_rows = "" + for inv in invs: + days_to_due = (inv.invoice_date_due - today).days if inv.invoice_date_due else 0 + invoice_table_rows += f""" + + {inv.name} + {fields.Date.to_string(inv.invoice_date) or '-'} + {fields.Date.to_string(inv.invoice_date_due) or '-'} + {days_to_due} + {formatLang(self.env, inv.amount_total, currency_obj=inv.currency_id)} + {inv.ref or '-'} + + """ + + subject = f"Reminder Invoice Due - {partner.name}" + body_html = re.sub( + r"]*>.*?", + f"{invoice_table_rows}", + template.body_html, + flags=re.DOTALL + ).replace('${object.name}', partner.name) \ + .replace('${object.partner_id.name}', partner.name) \ + .replace('${object.email}', partner.email or '') + + values = { + 'subject': subject, + 'email_to': 'andrifebriyadiputra@gmail.com', # Ubah ke partner.email untuk produksi + 'email_from': 'finance@indoteknik.co.id', + 'body_html': body_html, + } + + _logger.info(f"VALUES: {values}") + + # self.env['mail.mail'].create(values).send() + # _logger.info(f"Reminder terkirim ke {partner.name} ({values['email_to']}) → {len(invs)} invoice") + + # for inv in invoices: + # try: + # # Untuk test: override ke email pribadi Anda + # email_values = { + # 'email_to': 'andrifebriyadiputra@gmail.com', + # 'email_from': 'finance@indoteknik.co.id', + # } + # template.send_mail(inv.id, force_send=True, email_values=email_values) + # _logger.info(f"Reminder terkirim: {inv.name} → {email_values['email_to']}") + # except Exception as e: + # _logger.error(f"Gagal kirim email untuk {inv.name}: {str(e)}") # def name_get(self): # result = [] diff --git a/indoteknik_custom/views/mail_template_invoice_reminder.xml b/indoteknik_custom/views/mail_template_invoice_reminder.xml index b19171b2..b39730b6 100644 --- a/indoteknik_custom/views/mail_template_invoice_reminder.xml +++ b/indoteknik_custom/views/mail_template_invoice_reminder.xml @@ -2,17 +2,16 @@ - Invoice Reminder: Due Date Notification - - [Reminder] Invoice ${object.name} is Due Soon + Invoice Reminder: Due Date Notification (Manual) + + [Reminder] Invoice Manual finance@indoteknik.co.id - finance@indoteknik.co.id - andrifebriyadiputra@gmail.com + test@example.com
-

Dengan Hormat Bpk/Ibu ${object.partner_id.name},

+

Dengan Hormat Bpk/Ibu,

-

Berikut adalah detail invoice Anda yang sudah mendekati atau telah jatuh tempo:

+

Berikut adalah daftar invoice Anda yang mendekati atau telah jatuh tempo:

@@ -26,20 +25,10 @@ - - - - - - - -
${object.name}${format_date(object.invoice_date)}${format_date(object.invoice_date_due)}${object.invoice_day_to_due}${format_amount(object.amount_total, object.currency_id)}${object.ref or '-'}
-

Mohon segera melakukan proses pembayaran sebelum jatuh tempo.

- -

Terima kasih atas perhatian dan kerjasamanya.

+

Mohon segera melakukan proses pembayaran untuk invoice-invoice tersebut.

Hormat Kami,
-- cgit v1.2.3 From b2c6b57b7c621379aea029d2c716282cc65db6e0 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Thu, 10 Jul 2025 15:12:56 +0700 Subject: sort dunning run based on invoice num --- indoteknik_custom/models/dunning_run.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/indoteknik_custom/models/dunning_run.py b/indoteknik_custom/models/dunning_run.py index 341b206d..5a6aebac 100644 --- a/indoteknik_custom/models/dunning_run.py +++ b/indoteknik_custom/models/dunning_run.py @@ -96,14 +96,18 @@ class DunningRun(models.Model): ] invoices = self.env['account.move'].search(query) - # sort by last number in invoice name - try: - invoices = sorted( - invoices, - key=lambda x: int((x.name or '0').split('/')[-1]) - ) - except Exception as e: - _logger.error('Gagal sort invoice number: %s', e) + # sort full berdasarkan tahun, bulan, nomor + def invoice_key(x): + try: + parts = x.name.split('/') + tahun = int(parts[1]) + bulan = int(parts[2]) + nomor = int(parts[3]) + return (tahun, bulan, nomor) + except Exception: + return (0, 0, 0) + + invoices = sorted(invoices, key=invoice_key) count = 0 for invoice in invoices: -- cgit v1.2.3 From 6aee89eff0e1511c257c60fac9fa84172729063c Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Thu, 10 Jul 2025 15:13:18 +0700 Subject: (andri) apus bagian yang tak perlu --- indoteknik_custom/models/account_move.py | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/indoteknik_custom/models/account_move.py b/indoteknik_custom/models/account_move.py index fd72d566..eb39a1ac 100644 --- a/indoteknik_custom/models/account_move.py +++ b/indoteknik_custom/models/account_move.py @@ -153,32 +153,6 @@ class AccountMove(models.Model): # self.env['mail.mail'].create(values).send() # _logger.info(f"Reminder terkirim ke {partner.name} ({values['email_to']}) → {len(invs)} invoice") - # for inv in invoices: - # try: - # # Untuk test: override ke email pribadi Anda - # email_values = { - # 'email_to': 'andrifebriyadiputra@gmail.com', - # 'email_from': 'finance@indoteknik.co.id', - # } - # template.send_mail(inv.id, force_send=True, email_values=email_values) - # _logger.info(f"Reminder terkirim: {inv.name} → {email_values['email_to']}") - # except Exception as e: - # _logger.error(f"Gagal kirim email untuk {inv.name}: {str(e)}") - - # def name_get(self): - # result = [] - # for move in self: - # if move.move_type == 'entry': - # # Jika masih draft, tampilkan 'Draft CAB' - # if move.state == 'draft': - # label = 'Draft CAB' - # else: - # label = move.name - # result.append((move.id, label)) - # else: - # # Untuk invoice dan lainnya, pakai default - # result.append((move.id, move.display_name)) - # return result @api.onchange('invoice_date') def _onchange_invoice_date(self): -- cgit v1.2.3 From 839474c5f411b8c6c2476d8dcda9a6068d9848e5 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Thu, 10 Jul 2025 15:28:10 +0700 Subject: (andri) try test --- indoteknik_custom/models/account_move.py | 4 ++-- indoteknik_custom/views/mail_template_invoice_reminder.xml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/indoteknik_custom/models/account_move.py b/indoteknik_custom/models/account_move.py index 822c54f7..6c4eb14b 100644 --- a/indoteknik_custom/models/account_move.py +++ b/indoteknik_custom/models/account_move.py @@ -150,8 +150,8 @@ class AccountMove(models.Model): _logger.info(f"VALUES: {values}") - # self.env['mail.mail'].create(values).send() - # _logger.info(f"Reminder terkirim ke {partner.name} ({values['email_to']}) → {len(invs)} invoice") + self.env['mail.mail'].create(values).send() + _logger.info(f"Reminder terkirim ke {partner.name} ({values['email_to']}) → {len(invs)} invoice") @api.onchange('invoice_date') diff --git a/indoteknik_custom/views/mail_template_invoice_reminder.xml b/indoteknik_custom/views/mail_template_invoice_reminder.xml index b39730b6..92362284 100644 --- a/indoteknik_custom/views/mail_template_invoice_reminder.xml +++ b/indoteknik_custom/views/mail_template_invoice_reminder.xml @@ -6,7 +6,7 @@ [Reminder] Invoice Manual finance@indoteknik.co.id - test@example.com + andrifebriyadiputra@gmail.com

Dengan Hormat Bpk/Ibu,

-- cgit v1.2.3 From b3003dfcffa29390ec078ed206c9b013e683d1c8 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Thu, 10 Jul 2025 15:41:26 +0700 Subject: (andri) try --- indoteknik_custom/models/account_move.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indoteknik_custom/models/account_move.py b/indoteknik_custom/models/account_move.py index 6c4eb14b..33149cb0 100644 --- a/indoteknik_custom/models/account_move.py +++ b/indoteknik_custom/models/account_move.py @@ -78,7 +78,7 @@ class AccountMove(models.Model): def send_due_invoice_reminder(self): today = fields.Date.today() - partner = self.env['res.partner'].search([('name', 'ilike', 'TIRTA FRESINDO JAYA')], limit=1) + partner = self.env['res.partner'].search([('name', 'ilike', 'GEMILANG TUJUH BERSAUDARA')], limit=1) if not partner: _logger.info("Partner tidak ditemukan.") return -- cgit v1.2.3 From 9aa1682f36cad78e04d3367c1d30867c7706a5d1 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Thu, 10 Jul 2025 17:15:02 +0700 Subject: (andri) fix invoices date due --- indoteknik_custom/models/account_move.py | 23 ++++++++++++---------- .../views/mail_template_invoice_reminder.xml | 9 +++++---- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/indoteknik_custom/models/account_move.py b/indoteknik_custom/models/account_move.py index 33149cb0..ddd2f7d9 100644 --- a/indoteknik_custom/models/account_move.py +++ b/indoteknik_custom/models/account_move.py @@ -77,8 +77,15 @@ class AccountMove(models.Model): def send_due_invoice_reminder(self): today = fields.Date.today() - - partner = self.env['res.partner'].search([('name', 'ilike', 'GEMILANG TUJUH BERSAUDARA')], limit=1) + target_dates = [ + today - timedelta(days=7), + today - timedelta(days=3), + today, + today + timedelta(days=3), + today + timedelta(days=7), + ] + + partner = self.env['res.partner'].search([('name', 'ilike', 'PROBAN OSTBURG TRISAKTI')], limit=1) if not partner: _logger.info("Partner tidak ditemukan.") return @@ -87,11 +94,7 @@ class AccountMove(models.Model): ('move_type', '=', 'out_invoice'), ('state', '=', 'posted'), ('payment_state', 'not in', ['paid','in_payment', 'reversed']), - ('invoice_date_due', '>=', today - timedelta(days=7)), - ('invoice_date_due', '>=', today - timedelta(days=3)), - ('invoice_date_due', '>=', today), - ('invoice_date_due', '<=', today + timedelta(days=3)), - ('invoice_date_due', '<=', today + timedelta(days=7)), + ('invoice_date_due', 'in', target_dates), ('partner_id', '=', partner.id), ]) @@ -138,8 +141,8 @@ class AccountMove(models.Model): template.body_html, flags=re.DOTALL ).replace('${object.name}', partner.name) \ - .replace('${object.partner_id.name}', partner.name) \ - .replace('${object.email}', partner.email or '') + .replace('${object.partner_id.name}', partner.name) + # .replace('${object.email}', partner.email or '') values = { 'subject': subject, @@ -150,7 +153,7 @@ class AccountMove(models.Model): _logger.info(f"VALUES: {values}") - self.env['mail.mail'].create(values).send() + template.send_mail(invs[0].id, force_send=True, email_values=values) _logger.info(f"Reminder terkirim ke {partner.name} ({values['email_to']}) → {len(invs)} invoice") diff --git a/indoteknik_custom/views/mail_template_invoice_reminder.xml b/indoteknik_custom/views/mail_template_invoice_reminder.xml index 92362284..4ed91c75 100644 --- a/indoteknik_custom/views/mail_template_invoice_reminder.xml +++ b/indoteknik_custom/views/mail_template_invoice_reminder.xml @@ -1,15 +1,15 @@ - + Invoice Reminder: Due Date Notification (Manual) - + [Reminder] Invoice Manual finance@indoteknik.co.id andrifebriyadiputra@gmail.com
-

Dengan Hormat Bpk/Ibu,

+

Dear ${object.name},

Berikut adalah daftar invoice Anda yang mendekati atau telah jatuh tempo:

@@ -28,7 +28,8 @@ -

Mohon segera melakukan proses pembayaran untuk invoice-invoice tersebut.

+

Mohon bantuan dan kerjasamanya agar tetap bisa bekerjasama dengan baik

+

Terima Kasih

Hormat Kami,
-- cgit v1.2.3 From 575a7a506382487a625914a7bde9a18b20173cc6 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Fri, 11 Jul 2025 10:45:26 +0700 Subject: (andri) rev template email & fix sequence approval --- indoteknik_custom/models/account_move.py | 2 +- indoteknik_custom/views/ir_sequence.xml | 2 +- .../views/mail_template_invoice_reminder.xml | 26 +++++++++++++--------- 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/indoteknik_custom/models/account_move.py b/indoteknik_custom/models/account_move.py index ddd2f7d9..5ac1c6e5 100644 --- a/indoteknik_custom/models/account_move.py +++ b/indoteknik_custom/models/account_move.py @@ -85,7 +85,7 @@ class AccountMove(models.Model): today + timedelta(days=7), ] - partner = self.env['res.partner'].search([('name', 'ilike', 'PROBAN OSTBURG TRISAKTI')], limit=1) + partner = self.env['res.partner'].search([('name', 'ilike', 'FLYNINDO MEGA PERSADA')], limit=1) if not partner: _logger.info("Partner tidak ditemukan.") return diff --git a/indoteknik_custom/views/ir_sequence.xml b/indoteknik_custom/views/ir_sequence.xml index a0f5fc6b..d9b93ff3 100644 --- a/indoteknik_custom/views/ir_sequence.xml +++ b/indoteknik_custom/views/ir_sequence.xml @@ -154,7 +154,7 @@ Approval Payment Term approval.payment.term - APP/%(year)s/ + APT/%(year)s/ 5 1 1 diff --git a/indoteknik_custom/views/mail_template_invoice_reminder.xml b/indoteknik_custom/views/mail_template_invoice_reminder.xml index 4ed91c75..8b3b9880 100644 --- a/indoteknik_custom/views/mail_template_invoice_reminder.xml +++ b/indoteknik_custom/views/mail_template_invoice_reminder.xml @@ -2,20 +2,20 @@ - Invoice Reminder: Due Date Notification (Manual) + Invoice Reminder: Due Date Notification - [Reminder] Invoice Manual + Reminder Invoice Due - ${object.name} finance@indoteknik.co.id andrifebriyadiputra@gmail.com -

+

Dear ${object.name},

Berikut adalah daftar invoice Anda yang mendekati atau telah jatuh tempo:

- + @@ -29,14 +29,18 @@
Invoice Number Tanggal Invoice Jatuh Tempo

Mohon bantuan dan kerjasamanya agar tetap bisa bekerjasama dengan baik

-

Terima Kasih

+

Terima Kasih.

+
+
+

Best Regards, +
+
+ Widya R.
+ Dept. Finance
+ PT. INDOTEKNIK DOTCOM GEMILANG

+ Indoteknik
+ +62-857-1697-0374 | finance@indoteknik.co.id

-

- Hormat Kami,
- PT. INDOTEKNIK DOTCOM GEMILANG
- Jl. Bandengan Utara 85A No. 8-9, Penjaringan, Jakarta Utara
- Telp: 021-2933 8828 / 29 | Email: finance@indoteknik.co.id -

-- cgit v1.2.3