diff options
| author | Miqdad <ahmadmiqdad27@gmail.com> | 2025-08-29 08:07:48 +0700 |
|---|---|---|
| committer | Miqdad <ahmadmiqdad27@gmail.com> | 2025-08-29 08:07:48 +0700 |
| commit | 82ee99f089a1388becfea21ed664096240c21575 (patch) | |
| tree | cae78474f513c339a81e8d9acbfd50ead21a58a6 | |
| parent | d672d8745f5157d4cf3ff17907a3ca0881b68901 (diff) | |
| parent | 0112ac064a7484685119cf9371ffbea32de6fd59 (diff) | |
Merge branch 'odoo-backup' of https://bitbucket.org/altafixco/indoteknik-addons into regenerate_midtrans_web
| -rw-r--r-- | indoteknik_custom/models/account_move.py | 72 | ||||
| -rw-r--r-- | indoteknik_custom/models/solr/promotion_program_line.py | 112 | ||||
| -rw-r--r-- | indoteknik_custom/views/mail_template_invoice_reminder.xml | 55 |
3 files changed, 159 insertions, 80 deletions
diff --git a/indoteknik_custom/models/account_move.py b/indoteknik_custom/models/account_move.py index 905855c6..c44cad78 100644 --- a/indoteknik_custom/models/account_move.py +++ b/indoteknik_custom/models/account_move.py @@ -179,8 +179,9 @@ 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) - ]) + ('date_terima_tukar_faktur', '!=', False), + ('partner_id', 'in' , [94603]) + ], limit=5) _logger.info(f"Invoices: {invoices}") invoices = invoices.filtered( @@ -247,8 +248,10 @@ class AccountMove(models.Model): _logger.info(f"Email tujuan: {email_to}") invoice_table_rows = "" + grand_total = 0 for inv in invs: 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""" <tr> <td>{inv.partner_id.name}</td> @@ -261,6 +264,66 @@ class AccountMove(models.Model): <td>{days_to_due}</td> </tr> """ + invoice_table_footer = f""" + <tfoot> + <tr style="font-weight:bold; background-color:#f9f9f9;"> + <td colspan="5" align="right">Grand Total</td> + <td>{formatLang(self.env, grand_total, currency_obj=invs[0].currency_id)}</td> + <td colspan="2"></td> + </tr> + </tfoot> + """ + + blocking_limit = partner.blocking_stage or 0.0 + + # semua invoice tempo yang masih open + outstanding_invoices = self.env['account.move'].search([ + ('move_type', '=', 'out_invoice'), + ('state', '=', 'posted'), + ('payment_state', 'not in', ['paid', 'in_payment', 'reversed']), + ('partner_id', '=', partner.id), + ('invoice_payment_term_id.name', 'ilike', 'tempo') + ]) + + outstanding_amount = sum(outstanding_invoices.mapped('amount_total')) + + # invoice tempo yang sudah jatuh tempo + overdue_invoices = outstanding_invoices.filtered( + lambda inv: inv.invoice_date_due and inv.invoice_date_due < fields.Date.today() + ) + + overdue_amount = sum(overdue_invoices.mapped('amount_total')) + + 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' + + limit_info_html = f""" + <p><b>Informasi Tambahan:</b></p> + <ul style="font-size:12px; color:#333; line-height:1.4; margin:0; padding-left:0; list-style-position:inside;"> + <li>Kredit Limit Anda: {formatLang(self.env, blocking_limit, currency_obj=currency)}</li> + <li>Status Detail Tempo: {partner.property_payment_term_id.name or 'Review'}</li> + <li style="color:{'red' if (blocking_limit - outstanding_amount) < 0 else 'green'};"> + Sisa Kredit Limit: {formatLang(self.env, blocking_limit - outstanding_amount, currency_obj=currency)} + </li> + <li style="color:red;"> + Kredit Limit Terpakai: {formatLang(self.env, outstanding_amount, currency_obj=currency)} + <span style="font-size:12px; color:#666;">({len(outstanding_invoices)} Transaksi)</span> + </li> + <li style="color:red;"> + Jatuh Tempo: {formatLang(self.env, overdue_amount, currency_obj=currency)} + <span style="font-size:12px; color:#666;">({len(overdue_invoices)} Invoice)</span> + </li> + </ul> + <p style="margin-top:10px;"> + <a href="{tempo_link or '#'}" + style="display:inline-block; padding:8px 16px; + background-color:#007bff; color:#fff; text-decoration:none; + border-radius:4px; font-size:12px;"> + Cek Selengkapnya + </a> + </p> + """ days_to_due_message = "" closing_message = "" @@ -302,13 +365,14 @@ class AccountMove(models.Model): body_html = re.sub( r"<tbody[^>]*>.*?</tbody>", - f"<tbody>{invoice_table_rows}</tbody>", + f"<tbody>{invoice_table_rows}</tbody>{invoice_table_footer}", template.body_html, flags=re.DOTALL ).replace('${object.name}', partner.name) \ .replace('${object.partner_id.name}', partner.name) \ .replace('${days_to_due_message}', days_to_due_message) \ - .replace('${closing_message}', closing_message) + .replace('${closing_message}', closing_message) \ + .replace('${limit_info_html}', limit_info_html) cc_list = [ 'finance@indoteknik.co.id', diff --git a/indoteknik_custom/models/solr/promotion_program_line.py b/indoteknik_custom/models/solr/promotion_program_line.py index 64ad4209..b1b2f88e 100644 --- a/indoteknik_custom/models/solr/promotion_program_line.py +++ b/indoteknik_custom/models/solr/promotion_program_line.py @@ -2,6 +2,10 @@ from odoo import models, api from typing import Type import pysolr import json +import logging +from odoo.exceptions import UserError + +_logger = logging.getLogger(__name__) class PromotionProgramLine(models.Model): _inherit = 'promotion.program.line' @@ -20,58 +24,64 @@ class PromotionProgramLine(models.Model): def _sync_to_solr(self): solr_model = self.env['apache.solr'] - for rec in self: - document = solr_model.get_doc(self._solr_schema, rec.id) - - products = [{ - 'product_id': x.product_id.id, - 'qty': x.qty, - 'qty_sold': x.product_id.qty_sold - } for x in rec.product_ids] - - free_products = [{ - 'product_id': x.product_id.id, - 'qty': x.qty - } for x in rec.free_product_ids] - - promotion_type = rec._res_promotion_type() - - # Gathering all categories - category_names = [category.name for category in rec.product_ids.product_id.public_categ_ids] - - # Set sequence_i to None if rec.sequence is 0 - sequence_value = None if rec.sequence == 0 else rec.sequence - - document.update({ - 'id': rec.id, - 'program_id_i': rec.program_id.id or 0, - 'name_s': rec.name, - 'type_value_s': promotion_type['value'], - 'type_label_s': promotion_type['label'], - 'package_limit_i': rec.package_limit, - 'package_limit_user_i': rec.package_limit_user, - 'package_limit_trx_i': rec.package_limit_trx, - 'price_f': rec.price, - 'price_tier_1_f': rec.price_tier_1, - 'price_tier_2_f': rec.price_tier_2, - 'price_tier_3_f': rec.price_tier_3, - 'price_tier_4_f': rec.price_tier_4, - 'price_tier_5_f': rec.price_tier_5, - 'sequence_i': sequence_value, - 'product_ids': [x.product_id.id for x in rec.product_ids], - 'products_s': json.dumps(products), - 'free_product_ids': [x.product_id.id for x in rec.free_product_ids], - 'free_products_s': json.dumps(free_products), - 'total_qty_i': sum([x.qty for x in rec.product_ids] + [x.qty for x in rec.free_product_ids]), - 'total_qty_sold_f': [x.product_id.qty_sold for x in rec.product_ids], - 'active_b': rec.active, - "manufacture_name_s": rec.product_ids.product_id.x_manufacture.x_name or '', - "category_name": category_names, - }) - - self.solr().add([document]) - self.solr().commit() + try: + document = solr_model.get_doc(self._solr_schema, rec.id) + + products = [{ + 'product_id': x.product_id.id, + 'qty': x.qty, + 'qty_sold': x.product_id.qty_sold + } for x in rec.product_ids] + + free_products = [{ + 'product_id': x.product_id.id, + 'qty': x.qty + } for x in rec.free_product_ids] + + promotion_type = rec._res_promotion_type() + + category_names = [category.name for category in rec.product_ids.product_id.public_categ_ids] + sequence_value = None if rec.sequence == 0 else rec.sequence + + document.update({ + 'id': rec.id, + 'program_id_i': rec.program_id.id or 0, + 'name_s': rec.name, + 'type_value_s': promotion_type['value'], + 'type_label_s': promotion_type['label'], + 'package_limit_i': rec.package_limit, + 'package_limit_user_i': rec.package_limit_user, + 'package_limit_trx_i': rec.package_limit_trx, + 'price_f': rec.price, + 'price_tier_1_f': rec.price_tier_1, + 'price_tier_2_f': rec.price_tier_2, + 'price_tier_3_f': rec.price_tier_3, + 'price_tier_4_f': rec.price_tier_4, + 'price_tier_5_f': rec.price_tier_5, + 'sequence_i': sequence_value, + 'product_ids': [x.product_id.id for x in rec.product_ids], + 'products_s': json.dumps(products), + 'free_product_ids': [x.product_id.id for x in rec.free_product_ids], + 'free_products_s': json.dumps(free_products), + 'total_qty_i': sum([x.qty for x in rec.product_ids] + [x.qty for x in rec.free_product_ids]), + 'total_qty_sold_f': sum([x.product_id.qty_sold for x in rec.product_ids]), + 'active_b': rec.active, + "manufacture_name_s": rec.product_ids[0].product_id.x_manufacture.x_name or '', + "category_name": category_names, + }) + + self.solr().add([document]) + self.solr().commit() + + except Exception as e: + _logger.error( + "Failed to sync record %s (ID: %s) to Solr. Error: %s", + rec._name, rec.id, str(e), + exc_info=True # biar stack trace keluar + ) + # opsional -> kalau mau hard fail: + raise UserError(_("Sync to Solr failed for record %s: %s") % (rec.name, str(e))) @api.model def create(self, vals): diff --git a/indoteknik_custom/views/mail_template_invoice_reminder.xml b/indoteknik_custom/views/mail_template_invoice_reminder.xml index 8450be28..13c02a08 100644 --- a/indoteknik_custom/views/mail_template_invoice_reminder.xml +++ b/indoteknik_custom/views/mail_template_invoice_reminder.xml @@ -8,45 +8,50 @@ <field name="email_from">finance@indoteknik.co.id</field> <field name="email_to"></field> <field name="body_html" type="html"> - <div> + <div style="font-family:Arial, sans-serif; font-size:13px; color:#333;"> <p><b>Dear ${object.name},</b></p> <p>${days_to_due_message}</p> - <table border="1" cellpadding="4" cellspacing="0" style="border-collapse: collapse; font-size: 12px"> + <table cellpadding="6" cellspacing="0" width="100%" + style="border-collapse:collapse; font-size:12px; border:1px solid #ddd;"> <thead> - <tr style="background-color: #f2f2f2;" align="left"> - <th>Customer</th> - <th>No. PO</th> - <th>Invoice Number</th> - <th>Invoice Date</th> - <th>Due Date</th> - <th>Amount</th> - <th>Term</th> - <th>Days To Due</th> + <tr style="background-color:#f2f2f2; text-align:left;"> + <th style="border:1px solid #ddd;">Customer</th> + <th style="border:1px solid #ddd;">No. PO</th> + <th style="border:1px solid #ddd;">Invoice Number</th> + <th style="border:1px solid #ddd;">Invoice Date</th> + <th style="border:1px solid #ddd;">Due Date</th> + <th style="border:1px solid #ddd;">Amount</th> + <th style="border:1px solid #ddd;">Term</th> + <th style="border:1px solid #ddd;">Days To Due</th> </tr> </thead> <tbody> + <!-- baris invoice akan diinject dari Python --> </tbody> </table> <p>${closing_message}</p> + ${limit_info_html} <br/> <p>Terima Kasih.</p> - <br/> - <br/> - <p><b>Best Regards, - <br/> - <br/> - Widya R.<br/> - Dept. Finance<br/> - PT. INDOTEKNIK DOTCOM GEMILANG<br/> - <img src="https://erp.indoteknik.com/api/image/ir.attachment/datas/2135765" alt="Indoteknik" style="max-width: 18%; height: auto;"></img><br/> - <a href="https://wa.me/6285716970374" target="_blank">+62-857-1697-0374</a> | - <a href="mailto:finance@indoteknik.co.id">finance@indoteknik.co.id</a> - </b></p> - <p><i>Email ini dikirim secara otomatis. Abaikan jika pembayaran telah dilakukan.</i></p> - + <br/><br/> + <p> + <b> + Best Regards,<br/><br/> + Widya R.<br/> + Dept. Finance<br/> + PT. INDOTEKNIK DOTCOM GEMILANG<br/> + <img src="https://erp.indoteknik.com/api/image/ir.attachment/datas/2135765" + alt="Indoteknik" style="max-width:18%; height:auto;"/><br/> + <a href="https://wa.me/6285716970374" target="_blank">+62-857-1697-0374</a> | + <a href="mailto:finance@indoteknik.co.id">finance@indoteknik.co.id</a> + </b> + </p> + <p style="font-size:11px; color:#777;"> + <i>Email ini dikirim secara otomatis. Abaikan jika pembayaran telah dilakukan.</i> + </p> </div> </field> <field name="auto_delete" eval="True"/> |
