summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMiqdad <ahmadmiqdad27@gmail.com>2025-08-29 08:07:48 +0700
committerMiqdad <ahmadmiqdad27@gmail.com>2025-08-29 08:07:48 +0700
commit82ee99f089a1388becfea21ed664096240c21575 (patch)
treecae78474f513c339a81e8d9acbfd50ead21a58a6
parentd672d8745f5157d4cf3ff17907a3ca0881b68901 (diff)
parent0112ac064a7484685119cf9371ffbea32de6fd59 (diff)
Merge branch 'odoo-backup' of https://bitbucket.org/altafixco/indoteknik-addons into regenerate_midtrans_web
-rw-r--r--indoteknik_custom/models/account_move.py72
-rw-r--r--indoteknik_custom/models/solr/promotion_program_line.py112
-rw-r--r--indoteknik_custom/views/mail_template_invoice_reminder.xml55
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"/>