summaryrefslogtreecommitdiff
path: root/indoteknik_custom/models
diff options
context:
space:
mode:
Diffstat (limited to 'indoteknik_custom/models')
-rw-r--r--indoteknik_custom/models/approval_payment_term.py1
-rw-r--r--indoteknik_custom/models/letter_receivable.py167
-rw-r--r--indoteknik_custom/models/mrp_production.py16
-rw-r--r--indoteknik_custom/models/refund_sale_order.py4
-rwxr-xr-xindoteknik_custom/models/sale_order.py65
-rw-r--r--indoteknik_custom/models/stock_picking.py31
-rw-r--r--indoteknik_custom/models/user_pengajuan_tempo_request.py3
7 files changed, 198 insertions, 89 deletions
diff --git a/indoteknik_custom/models/approval_payment_term.py b/indoteknik_custom/models/approval_payment_term.py
index 449bd90b..e45305db 100644
--- a/indoteknik_custom/models/approval_payment_term.py
+++ b/indoteknik_custom/models/approval_payment_term.py
@@ -56,6 +56,7 @@ class ApprovalPaymentTerm(models.Model):
change_log_688 = fields.Text(string="Change Log", readonly=True, copy=False)
+
def write(self, vals):
# Ambil nilai lama sebelum perubahan
old_values_dict = {
diff --git a/indoteknik_custom/models/letter_receivable.py b/indoteknik_custom/models/letter_receivable.py
index 16034938..ffe14491 100644
--- a/indoteknik_custom/models/letter_receivable.py
+++ b/indoteknik_custom/models/letter_receivable.py
@@ -23,6 +23,7 @@ class SuratPiutang(models.Model):
tujuan_nama = fields.Char(string="Nama Tujuan", tracking=True)
tujuan_email = fields.Char(string="Email Tujuan", tracking=True)
perihal = fields.Selection([
+ ('tutup_tempo', 'Surat Penutupan Pembayaran Tempo'),
('penagihan', 'Surat Resmi Penagihan'),
('sp1', 'Surat Peringatan Piutang ke-1'),
('sp2', 'Surat Peringatan Piutang ke-2'),
@@ -36,6 +37,7 @@ class SuratPiutang(models.Model):
("sent", "Approved & Sent")
], default="draft", tracking=True)
send_date = fields.Datetime(string="Tanggal Kirim", tracking=True)
+ due_date = fields.Date(string="Tanggal Jatuh Tempo", tracking=True, default= fields.Date.today)
seven_days_after_sent_date = fields.Char(string="7 Hari Setelah Tanggal Kirim")
periode_invoices_terpilih = fields.Char(
string="Periode Invoices Terpilih",
@@ -228,20 +230,27 @@ class SuratPiutang(models.Model):
continue
# === Surat penagihan biasa (langsung Pimpinan approve) ===
- if rec.perihal == "penagihan":
+ if rec.perihal in ("tutup_tempo", "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 dan berhasil dikirim.")
+ rec.message_post(body=f"{rec.perihal_label} 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})",
title="Informasi",
sticky=False
)
+
+ def action_print(self):
+ self.ensure_one()
+ if self.perihal == 'tutup_tempo':
+ return self.env.ref('indoteknik_custom.action_report_surat_tutup_tempo').report_action(self)
+ else:
+ return self.env.ref('indoteknik_custom.action_report_surat_piutang').report_action(self)
def action_send_letter(self):
self.ensure_one()
@@ -253,64 +262,79 @@ class SuratPiutang(models.Model):
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:
- # 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"""
- <tr>
- <td>{line.invoice_number or '-'}</td>
- <td>{self.partner_id.name or '-'}</td>
- <td>{fields.Date.to_string(line.invoice_date) or '-'}</td>
- <td>{fields.Date.to_string(line.invoice_date_due) or '-'}</td>
- <td>{line.new_invoice_day_to_due}</td>
- <td>{line.ref or '-'}</td>
- <td>{formatLang(self.env, line.amount_residual, currency_obj=line.currency_id)}</td>
- <td>{line.payment_term_id.name or '-'}</td>
- </tr>
- """
-
- invoice_table_footer = f"""
- <tfoot>
- <tr style="font-weight:bold; background-color:#f9f9f9;">
- <td colspan="6" align="right">Grand Total</td>
- <td>{formatLang(self.env, grand_total, currency_obj=self.currency_id, monetary=True)}</td>
- <td colspan="2"></td>
+ template = None
+ report = None
+ body_html = None
+ subject = None
+
+ # Logika untuk memilih template dan report berdasarkan 'perihal'
+ if self.perihal == 'tutup_tempo':
+ template = self.env.ref('indoteknik_custom.close_tempo_mail_template')
+ report = self.env.ref('indoteknik_custom.action_report_surat_tutup_tempo')
+ due_date_str = self.due_date.strftime('%d %B %Y') if self.due_date else 'yang telah ditentukan'
+ body_html = template.body_html \
+ .replace('${object.partner_id.name}', self.partner_id.name or '') \
+ .replace('${object.due_date}', due_date_str or '')
+ subject = f"Pemberitahuan Penutupan Pembayaran Tempo – {self.partner_id.name}"
+ else:
+ template = self.env.ref('indoteknik_custom.letter_receivable_mail_template')
+
+ 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:
+ grand_total += line.amount_residual
+ invoice_table_rows += f"""
+ <tr>
+ <td>{line.invoice_number or '-'}</td>
+ <td>{self.partner_id.name or '-'}</td>
+ <td>{fields.Date.to_string(line.invoice_date) or '-'}</td>
+ <td>{fields.Date.to_string(line.invoice_date_due) or '-'}</td>
+ <td>{line.new_invoice_day_to_due}</td>
+ <td>{line.ref or '-'}</td>
+ <td>{formatLang(self.env, line.amount_residual, currency_obj=line.currency_id)}</td>
+ <td>{line.payment_term_id.name or '-'}</td>
</tr>
- </tfoot>
- """
- # inject table rows ke template
- body_html = re.sub(
- r"<tbody[^>]*>.*?</tbody>",
- f"<tbody>{invoice_table_rows}</tbody>{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 '')
-
- report = self.env.ref('indoteknik_custom.action_report_surat_piutang')
+ """
+
+ invoice_table_footer = f"""
+ <tfoot>
+ <tr style="font-weight:bold; background-color:#f9f9f9;">
+ <td colspan="6" align="right">Grand Total</td>
+ <td>{formatLang(self.env, grand_total, currency_obj=self.currency_id, monetary=True)}</td>
+ <td colspan="2"></td>
+ </tr>
+ </tfoot>
+ """
+
+ body_html = re.sub(
+ r"<tbody[^>]*>.*?</tbody>",
+ f"<tbody>{invoice_table_rows}</tbody>{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 '')
+
+ report = self.env.ref('indoteknik_custom.action_report_surat_piutang')
+ subject = perihal_map.get(self.perihal, self.perihal or '') + " - " + (self.partner_id.name or '')
+
pdf_content, _ = report._render_qweb_pdf([self.id])
attachment_base64 = base64.b64encode(pdf_content)
@@ -335,14 +359,13 @@ class SuratPiutang(models.Model):
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 ''),
+ 'subject': subject, # Menggunakan subject yang sudah ditentukan di atas
'email_to': self.tujuan_email,
'email_from': 'finance@indoteknik.co.id',
- 'email_cc': ",".join(sorted(set(cc_list))),
- 'body_html': body_html,
+ # 'email_cc': ",".join(sorted(set(cc_list))),
+ 'body_html': body_html, # Menggunakan body_html yang sudah ditentukan di atas
'attachments': [(attachment.name, attachment.datas)],
- 'reply_to': 'finance@indoteknik.co.id',
+ # 'reply_to': 'finance@indoteknik.co.id',
}
template.with_context(mail_post_autofollow=False).send_mail(
@@ -352,7 +375,7 @@ class SuratPiutang(models.Model):
)
_logger.info(
- f"Surat Piutang {self.name} terkirim ke {self.tujuan_email} "
+ f"{self.name} terkirim ke {self.tujuan_email} "
f"({self.partner_id.name}), total {len(selected_lines)} invoice."
)
@@ -453,6 +476,18 @@ class SuratPiutang(models.Model):
body=f"Line Invoices diperbarui. Total line saat ini: {len(rec.line_ids)}"
)
+ @api.onchange('perihal', 'partner_id')
+ def _onchange_perihal_tutup_tempo(self):
+ if self.perihal == 'tutup_tempo':
+ for line in self.line_ids:
+ if line.new_invoice_day_to_due < -30:
+ line.selected = True
+ else:
+ line.selected = False
+ else:
+ for line in self.line_ids:
+ line.selected = False
+
@api.model
def create(self, vals):
# Generate nomor surat otomatis
@@ -462,7 +497,7 @@ 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}"
- if vals.get("perihal") == "penagihan":
+ if vals.get("perihal") in ("tutup_tempo", "penagihan"):
vals["state"] = "waiting_approval_pimpinan"
else:
vals["state"] = "waiting_approval_sales"
diff --git a/indoteknik_custom/models/mrp_production.py b/indoteknik_custom/models/mrp_production.py
index b39995b5..30956082 100644
--- a/indoteknik_custom/models/mrp_production.py
+++ b/indoteknik_custom/models/mrp_production.py
@@ -21,6 +21,22 @@ class MrpProduction(models.Model):
], string='Status Reserve', tracking=True, copy=False, help="The current state of the stock picking.")
date_reserved = fields.Datetime(string="Date Reserved", help='Tanggal ter-reserved semua barang nya', copy=False)
+ def action_cancel(self):
+ for production in self:
+ moves_with_forecast = production.move_raw_ids.filtered(
+ lambda m: m.reserved_availability > 0
+ )
+
+ if moves_with_forecast:
+ # bikin list produk per baris
+ product_list = "\n".join(
+ "- %s" % p.display_name for p in moves_with_forecast.mapped('product_id')
+ )
+ raise UserError(_(
+ "You cannot cancel this Manufacturing Order because the following raw materials "
+ "still have forecast availability:\n\n%s" % product_list
+ ))
+ return super(MrpProduction, self).action_cancel()
@api.constrains('check_bom_product_lines')
def constrains_check_bom_product_lines(self):
diff --git a/indoteknik_custom/models/refund_sale_order.py b/indoteknik_custom/models/refund_sale_order.py
index de9870f6..a866af6e 100644
--- a/indoteknik_custom/models/refund_sale_order.py
+++ b/indoteknik_custom/models/refund_sale_order.py
@@ -571,7 +571,9 @@ class RefundSaleOrder(models.Model):
domain = [
('journal_id', '=', 11),
('state', '=', 'posted'),
- ('ref', 'ilike', 'dp')
+ '|',
+ ('ref', 'ilike', 'dp'),
+ ('ref', 'ilike', 'payment'),
]
domain += ['|'] * (len(so_names) - 1)
for n in so_names:
diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py
index 663cba58..3aaae12d 100755
--- a/indoteknik_custom/models/sale_order.py
+++ b/indoteknik_custom/models/sale_order.py
@@ -2871,6 +2871,7 @@ class SaleOrder(models.Model):
def action_apply_voucher(self):
for line in self.order_line:
if line.order_promotion_id:
+ _logger.warning(f"[CHECKOUT FAILED] Produk promo ditemukan: {line.product_id.display_name}")
raise UserError('Voucher tidak dapat digabung dengan promotion program')
voucher = self.voucher_id
@@ -2913,42 +2914,82 @@ class SaleOrder(models.Model):
self.apply_voucher_shipping()
def apply_voucher(self):
+ def _is_promo_line(line):
+ # TRUE jika baris tidak boleh kena voucher
+ if getattr(line, 'order_promotion_id', False):
+ return True # baris dari program promo
+ if (line.price_unit or 0.0) == 0.0:
+ return True # free item
+ if getattr(line, 'is_has_disc', False):
+ return True # sudah promo/flashsale/berdiskon
+ if (line.discount or 0.0) >= 100.0:
+ return True # safety
+ return False
+
+ # --- LOOP 1: susun input untuk voucher.apply() ---
order_line = []
for line in self.order_line:
+ if _is_promo_line(line):
+ continue
order_line.append({
'product_id': line.product_id,
'price': line.price_unit,
'discount': line.discount,
'qty': line.product_uom_qty,
- 'subtotal': line.price_subtotal
+ 'subtotal': line.price_subtotal,
})
+
+ if not order_line:
+ return
+
voucher = self.voucher_id.apply(order_line)
+ # --- LOOP 2: tulis hasilnya HANYA ke non-promo ---
for line in self.order_line:
+ if _is_promo_line(line):
+ continue
+
line.initial_discount = line.discount
voucher_type = voucher['type']
- used_total = voucher['total'][voucher_type]
- used_discount = voucher['discount'][voucher_type]
+ total_map = voucher['total'][voucher_type]
+ discount_map = voucher['discount'][voucher_type]
- manufacture_id = line.product_id.x_manufacture.id
if voucher_type == 'brand':
- used_total = used_total.get(manufacture_id)
- used_discount = used_discount.get(manufacture_id)
+ m_id = line.product_id.x_manufacture.id
+ used_total = (total_map or {}).get(m_id)
+ used_discount = (discount_map or {}).get(m_id)
+ else:
+ used_total = total_map
+ used_discount = discount_map
- if not used_total or not used_discount:
+ if not used_total or not used_discount or (line.product_uom_qty or 0.0) == 0.0:
continue
line_contribution = line.price_subtotal / used_total
line_voucher = used_discount * line_contribution
- line_voucher_item = line_voucher / line.product_uom_qty
+ per_item_voucher = line_voucher / line.product_uom_qty
- line_price_unit = line.price_unit / 1.11 if any(tax.id == 23 for tax in line.tax_id) else line.price_unit
- line_discount_item = line_price_unit * line.discount / 100 + line_voucher_item
- line_voucher_item = line_discount_item / line_price_unit * 100
+ has_ppn_11 = any(tax.id == 23 for tax in line.tax_id)
+ base_unit = line.price_unit / 1.11 if has_ppn_11 else line.price_unit
+
+ new_disc_value = base_unit * line.discount / 100 + per_item_voucher
+ new_disc_pct = (new_disc_value / base_unit) * 100
line.amount_voucher_disc = line_voucher
- line.discount = line_voucher_item
+ line.discount = new_disc_pct
+
+ _logger.info(
+ "[VOUCHER_APPLIED] SO=%s voucher=%s type=%s line_id=%s product=%s qty=%s discount_pct=%.2f amount_voucher=%s",
+ self.name,
+ getattr(self.voucher_id, "code", None),
+ voucher.get("type"),
+ line.id,
+ line.product_id.display_name,
+ line.product_uom_qty,
+ line.discount,
+ line.amount_voucher_disc,
+ )
self.amount_voucher_disc = voucher['discount']['all']
self.applied_voucher_id = self.voucher_id
diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py
index b27e6b5d..0b91e79d 100644
--- a/indoteknik_custom/models/stock_picking.py
+++ b/indoteknik_custom/models/stock_picking.py
@@ -97,6 +97,7 @@ class StockPicking(models.Model):
approval_status = fields.Selection([
('pengajuan1', 'Approval Accounting'),
+ ('pengajuan2', 'Approval Logistic'),
('approved', 'Approved'),
], string='Approval Status', readonly=True, copy=False, index=True, tracking=3,
help="Approval Status untuk Internal Use")
@@ -152,6 +153,7 @@ class StockPicking(models.Model):
state_reserve = fields.Selection([
('waiting', 'Waiting For Fullfilment'),
('ready', 'Ready to Ship'),
+ ('partial', 'Ready to Ship Partial'),
('done', 'Done'),
('cancel', 'Cancelled'),
], string='Status Reserve', tracking=True, copy=False, help="The current state of the stock picking.")
@@ -393,7 +395,7 @@ class StockPicking(models.Model):
deadline = kirim_date + timedelta(days=1)
deadline = deadline.replace(hour=10, minute=0, second=0)
- if now > deadline:
+ if now > deadline and not self.so_lama:
raise ValidationError(
_("Anda tidak dapat mengubah Tanggal Kirim setelah jam 10:00 pada hari berikutnya!")
)
@@ -1078,7 +1080,6 @@ class StockPicking(models.Model):
return res
-
def ask_approval(self):
if self.env.user.is_accounting:
raise UserError("Bisa langsung Validate")
@@ -1332,7 +1333,6 @@ class StockPicking(models.Model):
current_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
self.date_reserved = current_time
-
# Validate Qty Demand Can't higher than Qty Product
if self.location_dest_id.id == 58 and 'BU/INPUT/' in self.name:
for move in self.move_ids_without_package:
@@ -1353,14 +1353,14 @@ 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)
+ ('journal_id', '=', 3) # Stock Journal ID
+ ], limit=1)
if stock_journal:
stock_journal.write({
'purchase_order_id': picking.purchase_id.id
@@ -2537,9 +2537,22 @@ class ScanKoli(models.Model):
out_moves = self.env['stock.move.line'].search([('picking_id', '=', picking.linked_out_picking_id.id)])
for pick_move in pick_moves:
- corresponding_out_move = out_moves.filtered(lambda m: m.product_id == pick_move.product_id)
- if corresponding_out_move:
- corresponding_out_move.qty_done += pick_move.qty_done
+ corresponding_out_moves = out_moves.filtered(lambda m: m.product_id == pick_move.product_id)
+
+ if len(corresponding_out_moves) == 1:
+ corresponding_out_moves.qty_done += pick_move.qty_done
+
+ elif len(corresponding_out_moves) > 1:
+ qty_koli = pick_move.qty_done
+ for out_move in corresponding_out_moves:
+ if qty_koli <= 0:
+ break
+ # ambil sesuai kebutuhan atau sisa qty
+ qty_to_assign = min(qty_koli, out_move.product_uom_qty)
+ out_move.qty_done += qty_to_assign
+ qty_koli -= qty_to_assign
+
+
def _reset_qty_done_if_no_scan(self, picking_id):
product_bu_pick = self.env['stock.move.line'].search([('picking_id', '=', picking_id)])
diff --git a/indoteknik_custom/models/user_pengajuan_tempo_request.py b/indoteknik_custom/models/user_pengajuan_tempo_request.py
index 600381c0..8ed92fc8 100644
--- a/indoteknik_custom/models/user_pengajuan_tempo_request.py
+++ b/indoteknik_custom/models/user_pengajuan_tempo_request.py
@@ -381,7 +381,8 @@ class UserPengajuanTempoRequest(models.Model):
if tempo.env.user.id in (688, 28, 7):
raise UserError("Pengajuan tempo harus di approve oleh sales manager terlebih dahulu")
else:
- if tempo.env.user.id not in (375, 19):
+ # if tempo.env.user.id not in (375, 19):
+ if tempo.env.user.id != 19:
# if tempo.env.user.id != 12182:
raise UserError("Pengajuan tempo hanya bisa di approve oleh sales manager")
else: