summaryrefslogtreecommitdiff
path: root/indoteknik_custom/models
diff options
context:
space:
mode:
authorAzka Nathan <darizkyfaz@gmail.com>2025-08-14 16:28:15 +0700
committerAzka Nathan <darizkyfaz@gmail.com>2025-08-14 16:28:15 +0700
commitbfdaf12dc3e7abace11502fcf1cb6c87522f1a51 (patch)
tree1b7d44f2b6a5ad2539f181effb66e9e3a59c9e9a /indoteknik_custom/models
parentad6ccce331dc8c83f2cacb82b0e3c94467ff9d85 (diff)
parentdb75081a2afcd369e8a0169f3fe2f080dbad0c4a (diff)
Merge branch 'odoo-backup' of bitbucket.org:altafixco/indoteknik-addons into odoo-backup
Diffstat (limited to 'indoteknik_custom/models')
-rw-r--r--indoteknik_custom/models/account_move.py73
-rw-r--r--indoteknik_custom/models/res_partner.py4
-rwxr-xr-xindoteknik_custom/models/sale_order.py30
-rw-r--r--indoteknik_custom/models/stock_picking.py8
-rw-r--r--indoteknik_custom/models/tukar_guling.py60
-rw-r--r--indoteknik_custom/models/tukar_guling_po.py60
6 files changed, 157 insertions, 78 deletions
diff --git a/indoteknik_custom/models/account_move.py b/indoteknik_custom/models/account_move.py
index fd08ed60..b0ffd8b9 100644
--- a/indoteknik_custom/models/account_move.py
+++ b/indoteknik_custom/models/account_move.py
@@ -97,6 +97,8 @@ class AccountMove(models.Model):
payment_date = fields.Date(string="Payment Date", compute='_compute_payment_date')
partial_payment = fields.Float(string="Partial Payment", compute='compute_partial_payment')
+ reminder_sent_date = fields.Date(string="Tanggal Reminder Terkirim")
+
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':
@@ -119,20 +121,6 @@ class AccountMove(models.Model):
else:
move.payment_date = False
- # 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
def send_due_invoice_reminder(self):
today = fields.Date.today()
@@ -144,31 +132,22 @@ class AccountMove(models.Model):
today + timedelta(days=7),
]
- # --- TESTING ---
- # partner = self.env['res.partner'].search([('name', 'ilike', 'DIRGANTARA YUDHA ARTHA')], 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', 'in', target_dates),
- # ('partner_id', '=', partner.id),
- # ])
+ 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 tahap 1: {invoices}")
+ _logger.info(f"Invoices: {invoices}")
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}")
+ # _logger.info(f"Invoices tahap 2: {invoices}")
if not invoices:
_logger.info("Tidak ada invoice yang due")
@@ -177,11 +156,18 @@ class AccountMove(models.Model):
invoice_group = {}
for inv in invoices:
dtd = (inv.invoice_date_due - today).days if inv.invoice_date_due else 0
- invoice_group.setdefault((inv.partner_id, dtd), []).append(inv)
+ key = (inv.partner_id, dtd)
+ 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
+
# Ambil child contact yang di-checklist reminder_invoices
reminder_contacts = self.env['res.partner'].search([
('parent_id', '=', partner.id),
@@ -190,11 +176,16 @@ class AccountMove(models.Model):
])
_logger.info(f"Email Reminder Child {reminder_contacts}")
- if not reminder_contacts:
- _logger.info(f"Partner {partner.name} tidak memiliki email yang sudah ceklis reminder")
- continue
+ # if not reminder_contacts:
+ # _logger.info(f"Partner {partner.name} tidak memiliki email yang sudah ceklis reminder")
+ # continue
emails = list(filter(None, [partner.email])) + reminder_contacts.mapped('email')
+ if reminder_contacts:
+ _logger.info(f"Email Reminder Child {reminder_contacts}")
+ else:
+ _logger.info(f"Tidak ada child contact reminder, gunakan email utama partner")
+
if not emails:
_logger.info(f"Partner {partner.name} tidak memiliki email yang bisa dikirimi")
continue
@@ -208,7 +199,7 @@ class AccountMove(models.Model):
invoice_table_rows += f"""
<tr>
<td>{inv.partner_id.name}</td>
- <td>{inv.purchase_order_id.name or '-'}</td>
+ <td>{inv.ref or '-'}</td>
<td>{inv.name}</td>
<td>{fields.Date.to_string(inv.invoice_date) or '-'}</td>
<td>{fields.Date.to_string(inv.invoice_date_due) or '-'}</td>
@@ -220,9 +211,9 @@ class AccountMove(models.Model):
days_to_due_message = ""
closing_message = ""
- if dtd < 0:
+ if dtd > 0:
days_to_due_message = (
- f"Kami ingin mengingatkan bahwa tagihan anda akan jatuh tempo dalam {abs(dtd)} hari ke depan, "
+ f"Kami ingin mengingatkan bahwa tagihan anda akan jatuh tempo dalam {dtd} hari ke depan, "
"dengan rincian sebagai berikut:"
)
closing_message = (
@@ -244,9 +235,9 @@ class AccountMove(models.Model):
"Terima kasih atas perhatian dan kerja samanya."
)
- if dtd > 0:
+ if dtd < 0:
days_to_due_message = (
- f"Kami ingin mengingatkan bahwa tagihan anda telah jatuh tempo selama {dtd} hari, "
+ f"Kami ingin mengingatkan bahwa tagihan anda telah jatuh tempo selama {abs(dtd)} hari, "
"dengan rincian sebagai berikut:"
)
closing_message = (
@@ -282,18 +273,18 @@ class AccountMove(models.Model):
# 'email_to': 'andrifebriyadiputra@gmail.com',
'email_to': email_to,
'email_from': 'finance@indoteknik.co.id',
- 'email_cc': ",".join(cc_list),
+ 'email_cc': ",".join(sorted(set(cc_list))),
'body_html': body_html,
'reply_to': 'finance@indoteknik.co.id',
}
- _logger.info(f"Mengirim email ke: {values['email_to']} CC: {values['email_cc']}")
+ _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)
-
+ # flag
+ 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
-
for inv in invs:
inv.message_post(
subject=values['subject'],
diff --git a/indoteknik_custom/models/res_partner.py b/indoteknik_custom/models/res_partner.py
index f260f58e..cf9fbea4 100644
--- a/indoteknik_custom/models/res_partner.py
+++ b/indoteknik_custom/models/res_partner.py
@@ -231,7 +231,7 @@ class ResPartner(models.Model):
rec.payment_difficulty = rec.parent_id.payment_difficulty
return records
- @api.constrains('name')
+ @api.constrains('email')
def _check_duplicate_name(self):
for record in self:
if record.name:
@@ -242,7 +242,7 @@ class ResPartner(models.Model):
], limit=1)
if existing_partner:
- raise ValidationError(f"Nama '{record.name}' sudah digunakan oleh partner lain!")
+ raise ValidationError(f"Nama '{record.name}' dengan email '{record.email}' sudah digunakan oleh partner lain!")
@api.constrains('npwp')
def _check_npwp(self):
diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py
index e71e3830..0acfa0b0 100755
--- a/indoteknik_custom/models/sale_order.py
+++ b/indoteknik_custom/models/sale_order.py
@@ -1116,8 +1116,9 @@ class SaleOrder(models.Model):
break
if not selected_option and shipping_options:
+ if not self.env.context.get('from_website_checkout'):
+ _logger.info(f"[DEFAULT] Tidak ada yang cocok, pakai opsi pertama: {shipping_options[0].name}")
selected_option = shipping_options[0]
- _logger.info(f"[DEFAULT] Tidak ada yang cocok, pakai opsi pertama: {selected_option.name}")
# ❗ Ganti carrier_id hanya jika BELUM terisi sama sekali (contoh: user dari backend)
if not self.carrier_id:
@@ -1137,9 +1138,18 @@ class SaleOrder(models.Model):
# Set shipping option dan nilai ongkir
if selected_option:
- self.shipping_option_id = selected_option.id
- self.delivery_amt = selected_option.price
- self.delivery_service_type = selected_option.courier_service_code
+ if self.env.context.get('from_website_checkout'):
+ # Simpan di context sebagai nilai sementara
+ self = self.with_context(
+ _temp_delivery_amt=selected_option.price,
+ _temp_delivery_service=selected_option.courier_service_code,
+ _temp_shipping_option=selected_option.id
+ )
+ else:
+ self.shipping_option_id = selected_option.id
+ self.delivery_amt = selected_option.price
+ self.delivery_service_type = selected_option.courier_service_code
+
message_lines = [f"<b>Estimasi Ongkos Kirim Biteship:</b><br/>"]
for courier, options in courier_options.items():
@@ -2178,7 +2188,7 @@ class SaleOrder(models.Model):
self.check_product_bom()
self.check_credit_limit()
self.check_limit_so_to_invoice()
- order.approval_status = 'approved'
+ order.approval_status = 'pengajuan1'
return self._create_approval_notification('Team Sales')
raise UserError("Bisa langsung Confirm")
@@ -2408,7 +2418,7 @@ class SaleOrder(models.Model):
order.approval_status = 'pengajuan1'
return self._create_approval_notification('Sales Manager')
elif order._requires_approval_team_sales():
- order.approval_status = 'approved'
+ order.approval_status = 'pengajuan1'
return self._create_approval_notification('Team Sales')
order.approval_status = 'approved'
@@ -2950,6 +2960,14 @@ class SaleOrder(models.Model):
order.select_shipping_option = 'biteship'
order.action_estimate_shipping()
+ temp_price = self.env.context.get('_temp_delivery_amt')
+ temp_service = self.env.context.get('_temp_delivery_service')
+ temp_option_id = self.env.context.get('_temp_shipping_option')
+ if temp_price and temp_option_id:
+ order.shipping_option_id = temp_option_id
+ order.delivery_amt = temp_price
+ order.delivery_service_type = temp_service
+
# Restore pilihan user setelah estimasi
if user_carrier_id and user_service:
# Dapatkan provider
diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py
index 0354587c..bf6834d0 100644
--- a/indoteknik_custom/models/stock_picking.py
+++ b/indoteknik_custom/models/stock_picking.py
@@ -19,10 +19,10 @@ import re
_logger = logging.getLogger(__name__)
_biteship_url = "https://api.biteship.com/v1"
-biteship_api_key = "biteship_live.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiaW5kb3Rla25payIsInVzZXJJZCI6IjY3MTViYTJkYzVkMjdkMDAxMjRjODk2MiIsImlhdCI6MTc0MTE1NTU4M30.pbFCai9QJv8iWhgdosf8ScVmEeP3e5blrn33CHe7Hgo"
+# biteship_api_key = "biteship_live.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiaW5kb3Rla25payIsInVzZXJJZCI6IjY3MTViYTJkYzVkMjdkMDAxMjRjODk2MiIsImlhdCI6MTc0MTE1NTU4M30.pbFCai9QJv8iWhgdosf8ScVmEeP3e5blrn33CHe7Hgo"
-# biteship_api_key = "biteship_test.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSW5kb3Rla25payIsInVzZXJJZCI6IjY3MTViYTJkYzVkMjdkMDAxMjRjODk2MiIsImlhdCI6MTcyOTQ5ODAwMX0.L6C73couP4-cgVEfhKI2g7eMCMo3YOFSRZhS-KSuHNA"
+biteship_api_key = "biteship_test.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSW5kb3Rla25payIsInVzZXJJZCI6IjY3MTViYTJkYzVkMjdkMDAxMjRjODk2MiIsImlhdCI6MTcyOTQ5ODAwMX0.L6C73couP4-cgVEfhKI2g7eMCMo3YOFSRZhS-KSuHNA"
class StockPicking(models.Model):
@@ -1714,7 +1714,9 @@ class StockPicking(models.Model):
if move_line.qty_done > 0:
product_shipped.append({
'name': move_line.product_id.name,
- 'qty': move_line.qty_done
+ 'code': move_line.product_id.default_code,
+ 'qty': move_line.qty_done,
+ 'image': self.env['ir.attachment'].api_image('product.template', 'image_128', move_line.product_id.product_tmpl_id.id),
})
response = {
diff --git a/indoteknik_custom/models/tukar_guling.py b/indoteknik_custom/models/tukar_guling.py
index 4c7722d5..4b03d4b0 100644
--- a/indoteknik_custom/models/tukar_guling.py
+++ b/indoteknik_custom/models/tukar_guling.py
@@ -32,7 +32,7 @@ class TukarGuling(models.Model):
'tukar_guling_id',
string='Transfers'
)
- # origin_so = fields.Many2one('sale.order', string='Origin SO')
+ origin_so = fields.Many2one('sale.order', string='Origin SO', compute='_compute_origin_so')
name = fields.Char('Number', required=True, copy=False, readonly=True, default='New')
date = fields.Datetime('Date', default=fields.Datetime.now, required=True)
operations = fields.Many2one(
@@ -83,19 +83,47 @@ class TukarGuling(models.Model):
invoice_id = fields.Many2many('account.move', string='Invoice Ref', readonly=True)
- @api.depends('origin')
+ @api.depends('origin', 'operations')
+ def _compute_origin_so(self):
+ for rec in self:
+ rec.origin_so = False
+ origin_str = rec.origin or rec.operations.origin
+ if origin_str:
+ so = self.env['sale.order'].search([('name', '=', origin_str)], limit=1)
+ rec.origin_so = so.id if so else False
+
+ @api.depends('origin', 'origin_so', 'partner_id', 'line_ids.product_id')
def _compute_is_has_invoice(self):
+ Move = self.env['account.move']
for rec in self:
- invoices = self.env['account.move'].search([
- ('invoice_origin', 'ilike', rec.origin),
- ('move_type', '=', 'out_invoice'), # hanya invoice
- ('state', 'not in', ['draft', 'cancel'])
- ])
+ rec.is_has_invoice = False
+ rec.invoice_id = [(5, 0, 0)]
+
+ product_ids = rec.line_ids.mapped('product_id').ids
+ if not product_ids:
+ continue
+
+ domain = [
+ ('move_type', 'in', ['out_invoice', 'in_invoice']),
+ ('state', 'not in', ['draft', 'cancel']),
+ ('invoice_line_ids.product_id', 'in', product_ids),
+ ]
+
+ if rec.partner_id:
+ domain.append(('partner_id', '=', rec.partner_id.id))
+
+ extra = []
+ if rec.origin:
+ extra.append(('invoice_origin', 'ilike', rec.origin))
+ if rec.origin_so:
+ extra.append(('invoice_line_ids.sale_line_ids.order_id', '=', rec.origin_so.id))
+ if extra:
+ domain = domain + ['|'] * (len(extra) - 1) + extra
+
+ invoices = Move.search(domain).with_context(active_test=False)
if invoices:
+ rec.invoice_id = [(6, 0, invoices.ids)]
rec.is_has_invoice = True
- rec.invoice_id = invoices
- else:
- rec.is_has_invoice = False
def set_opt(self):
if not self.val_inv_opt and self.is_has_invoice == True:
@@ -144,8 +172,6 @@ class TukarGuling(models.Model):
if self.line_ids and from_return_picking:
# Hanya update origin, jangan ubah lines
- if self.operations.origin:
- self.origin = self.operations.origin
_logger.info("📌 Menggunakan product lines dari return wizard, tidak populate ulang.")
# 🚀 Tapi tetap populate mapping koli jika BU/OUT
@@ -177,6 +203,7 @@ class TukarGuling(models.Model):
# Set origin dari operations
if self.operations.origin:
self.origin = self.operations.origin
+ self.origin_so = self.operations.group_id.id
# Auto-populate lines dari move_ids operations
lines_data = []
@@ -332,17 +359,20 @@ class TukarGuling(models.Model):
# _("Tidak bisa memilih Return Type 'Revisi SO' karena dokumen %s sudah dibuat invoice.") % record.origin
# )
+
@api.model
def create(self, vals):
- # Generate sequence number
if not vals.get('name') or vals['name'] == 'New':
vals['name'] = self.env['ir.sequence'].next_by_code('tukar.guling')
- # Auto-fill origin from operations
- if not vals.get('origin') and vals.get('operations'):
+ if vals.get('operations'):
picking = self.env['stock.picking'].browse(vals['operations'])
if picking.origin:
vals['origin'] = picking.origin
+ # Find matching SO
+ so = self.env['sale.order'].search([('name', '=', picking.origin)], limit=1)
+ if so:
+ vals['origin_so'] = so.id
if picking.partner_id:
vals['partner_id'] = picking.partner_id.id
diff --git a/indoteknik_custom/models/tukar_guling_po.py b/indoteknik_custom/models/tukar_guling_po.py
index 0badc117..cc1c79c0 100644
--- a/indoteknik_custom/models/tukar_guling_po.py
+++ b/indoteknik_custom/models/tukar_guling_po.py
@@ -62,20 +62,57 @@ class TukarGulingPO(models.Model):
is_has_bill = fields.Boolean('Has Bill?', compute='_compute_is_has_bill', readonly=True, default=False)
bill_id = fields.Many2many('account.move', string='Bill Ref', readonly=True)
+ origin_po = fields.Many2one('purchase.order', string='Origin PO', compute='_compute_origin_po')
- @api.depends('origin')
+ @api.depends('origin', 'operations')
+ def _compute_origin_po(self):
+ for rec in self:
+ rec.origin_po = False
+ origin_str = rec.origin or rec.operations.origin
+ if origin_str:
+ so = self.env['purchase.order'].search([('name', '=', origin_str)], limit=1)
+ rec.origin_po = so.id if so else False
+
+ @api.depends('origin', 'origin_po', 'vendor_id', 'line_ids.product_id')
def _compute_is_has_bill(self):
+ Move = self.env['account.move']
for rec in self:
- bills = self.env['account.move'].search([
- ('invoice_origin', 'ilike', rec.origin),
- ('move_type', '=', 'in_invoice'), # hanya vendor bill
- ('state', 'not in', ['draft', 'cancel'])
- ])
- if bills:
- rec.is_has_bill = True
- rec.bill_id = bills
- else:
- rec.is_has_bill = False
+ # reset
+ rec.is_has_bill = False
+ rec.bill_id = [(5, 0, 0)]
+
+ product_ids = rec.line_ids.mapped('product_id').ids
+ if not product_ids:
+ continue
+
+ # dasar: bill atau vendor credit note yang linennya mengandung produk TG
+ domain = [
+ ('move_type', 'in', ['in_invoice', 'in_refund']),
+ ('state', 'not in', ['draft', 'cancel']),
+ ('invoice_line_ids.product_id', 'in', product_ids),
+ ]
+
+ # batasi ke vendor sama (kalau ada)
+ if rec.vendor_id:
+ domain.append(('partner_id', '=', rec.vendor_id.id))
+
+ # bantu pembatasan ke asal dokumen
+ extra = []
+ if rec.origin:
+ extra.append(('invoice_origin', 'ilike', rec.origin))
+ if rec.origin_po:
+ # di Odoo 14, invoice line biasanya link ke purchase.line lewat purchase_line_id
+ extra.append(('invoice_line_ids.purchase_line_id.order_id', '=', rec.origin_po.id))
+
+ # OR-kan semua extra filter jika ada
+ if extra:
+ domain = domain + ['|'] * (len(extra) - 1) + extra
+
+ bills = Move.search(domain).with_context(active_test=False)
+
+ # --- Opsi 1: minimal salah satu produk TG muncul di bill (default) ---
+ rec.bill_id = [(6, 0, bills.ids)]
+ rec.is_has_bill = bool(bills)
def set_opt(self):
if not self.val_bil_opt and self.is_has_bill == True:
@@ -133,6 +170,7 @@ class TukarGulingPO(models.Model):
# Hanya update origin, jangan ubah lines
if self.operations.origin:
self.origin = self.operations.origin
+ self.origin_po = self.operations.group_id.id
return
if from_return_picking: