summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--indoteknik_custom/models/account_move.py2
-rw-r--r--indoteknik_custom/models/account_move_due_extension.py4
-rw-r--r--indoteknik_custom/models/advance_payment_request.py32
-rw-r--r--indoteknik_custom/models/coretax_fatur.py14
-rw-r--r--indoteknik_custom/models/ged.py5
-rwxr-xr-xindoteknik_custom/models/purchase_order.py98
-rwxr-xr-xindoteknik_custom/models/sale_order.py3
-rw-r--r--indoteknik_custom/models/stock_inventory.py4
-rw-r--r--indoteknik_custom/models/stock_picking.py48
-rw-r--r--indoteknik_custom/models/stock_picking_return.py15
-rw-r--r--indoteknik_custom/models/tukar_guling.py95
-rw-r--r--indoteknik_custom/report/purchase_report.xml24
-rw-r--r--indoteknik_custom/views/account_move_line.xml3
-rw-r--r--indoteknik_custom/views/account_move_views.xml2
-rw-r--r--indoteknik_custom/views/advance_payment_request.xml9
-rw-r--r--indoteknik_custom/views/advance_payment_settlement.xml1
-rw-r--r--indoteknik_custom/views/dunning_run.xml6
-rw-r--r--indoteknik_custom/views/mail_template_po.xml3
-rwxr-xr-xindoteknik_custom/views/purchase_order.xml1
-rw-r--r--indoteknik_custom/views/unpaid_invoice_view.xml16
20 files changed, 258 insertions, 127 deletions
diff --git a/indoteknik_custom/models/account_move.py b/indoteknik_custom/models/account_move.py
index 6212664e..e36b9920 100644
--- a/indoteknik_custom/models/account_move.py
+++ b/indoteknik_custom/models/account_move.py
@@ -461,7 +461,7 @@ class AccountMove(models.Model):
'finance@indoteknik.co.id',
'akbar@indoteknik.co.id',
'stephan@indoteknik.co.id',
- 'darren@indoteknik.co.id'
+ # 'darren@indoteknik.co.id'
]
sales_email = invs[0].invoice_user_id.partner_id.email if invs[0].invoice_user_id else None
if sales_email and sales_email not in cc_list:
diff --git a/indoteknik_custom/models/account_move_due_extension.py b/indoteknik_custom/models/account_move_due_extension.py
index 7faad418..828be824 100644
--- a/indoteknik_custom/models/account_move_due_extension.py
+++ b/indoteknik_custom/models/account_move_due_extension.py
@@ -195,6 +195,7 @@ class DueExtensionLine(models.Model):
invoice_id = fields.Many2one('account.move', string='Invoice')
date_invoice = fields.Date(string='Invoice Date')
efaktur_id = fields.Many2one('vit.efaktur', string='Faktur Pajak')
+ user_id = fields.Many2one('res.users', string='Salesperson', related='invoice_id.invoice_user_id')
reference = fields.Char(string='Reference')
total_amt = fields.Float(string='Total Amount')
open_amt = fields.Float(string='Open Amount')
@@ -215,7 +216,8 @@ class DueExtensionLine(models.Model):
def _compute_day_to_due(self):
for line in self:
- line.day_to_due = line.invoice_id.invoice_day_to_due
+ # line.day_to_due = line.invoice_id.invoice_day_to_due
+ line.day_to_due = line.invoice_id.new_invoice_day_to_due
def _compute_due_date(self):
for line in self:
diff --git a/indoteknik_custom/models/advance_payment_request.py b/indoteknik_custom/models/advance_payment_request.py
index ec23de63..a50fabd7 100644
--- a/indoteknik_custom/models/advance_payment_request.py
+++ b/indoteknik_custom/models/advance_payment_request.py
@@ -205,6 +205,13 @@ class AdvancePaymentRequest(models.Model):
self.account_name = self.env.user.id
self.applicant_name = self.env.user.id
+ @api.constrains('is_represented', 'applicant_name', 'account_name', 'user_id')
+ def _check_applicant_consistency(self):
+ for rec in self:
+ if not rec.is_represented:
+ if rec.applicant_name != rec.user_id or rec.account_name != rec.user_id:
+ raise ValidationError("Nama Pemohon harus sesuai dengan User yang sedang Login, centang 'Nama Pemohon Berbeda?' jika ingin mewakilkan pemohon lain.")
+
@api.onchange('nominal')
def _onchange_nominal_no_minus(self):
if self.nominal and self.nominal < 0:
@@ -481,15 +488,7 @@ class AdvancePaymentRequest(models.Model):
'target': 'current',
'context': {
'default_pum_id': self.id,
- # 'default_value_down_payment': self.nominal,
'default_name': f'Realisasi - {self.number or ""}',
- # 'default_pemberian_line_ids': [
- # (0, 0, {
- # 'date': self.create_date.date() if self.create_date else fields.Date.today(),
- # 'description': 'Uang Muka',
- # 'value': self.nominal
- # })
- # ]
}
}
@@ -823,6 +822,9 @@ class AdvancePaymentUsageLine(models.Model):
_name = 'advance.payment.usage.line'
_description = 'Advance Payment Usage Line'
+ _order = 'sequence, id'
+ sequence = fields.Integer(string='Sequence', default=10)
+
realization_id = fields.Many2one('advance.payment.settlement', string='Realization')
date = fields.Date(string='Tanggal', required=True, default=fields.Date.today)
description = fields.Text(string='Description', required=True)
@@ -903,7 +905,10 @@ class AdvancePaymentUsageLine(models.Model):
class ReimburseLine(models.Model):
_name = 'reimburse.line'
- _description = 'Reimburse Line'
+ _description = 'Reimburse Line'
+
+ _order = 'sequence, id'
+ sequence = fields.Integer(string='Sequence', default=10)
request_id = fields.Many2one('advance.payment.request', string='Request')
date = fields.Date(string='Tanggal', required=True, default=fields.Date.today)
@@ -1325,7 +1330,10 @@ class AdvancePaymentSettlement(models.Model):
rec.approved_by = (rec.approved_by + ', ' if rec.approved_by else '') + rec.name_approval_ap
rec.date_approved_ap = now
rec.position_ap = 'Finance AP'
- rec.status = 'pengajuan3'
+ if rec.pum_id.position_type == 'pimpinan':
+ rec.status = 'approved'
+ else:
+ rec.status = 'pengajuan3'
rec.message_post(
body=f"Approval <b>AP</b> oleh <b>{self.env.user.name}</b> "
@@ -1372,7 +1380,7 @@ class AdvancePaymentSettlement(models.Model):
# now = fields.Datetime.now()
# --- BATAS PENYESUAIAN ---
- pum_id = vals.get('pum_id')
+ pum_id = vals.get('pum_id') or self._context.get('default_pum_id')
initial_status = ''
if pum_id:
pum_request = self.env['advance.payment.request'].browse(pum_id)
@@ -1516,7 +1524,7 @@ class CreateReimburseCabWizard(models.TransientModel):
lines.append((0, 0, {
'account_id': line.account_id.id,
'partner_id': partner_id,
- 'name': line.description,
+ 'name': f"{line.description} ({line.date})",
'debit': line.total,
'credit': 0,
}))
diff --git a/indoteknik_custom/models/coretax_fatur.py b/indoteknik_custom/models/coretax_fatur.py
index cabcd5d6..c098fd01 100644
--- a/indoteknik_custom/models/coretax_fatur.py
+++ b/indoteknik_custom/models/coretax_fatur.py
@@ -117,7 +117,7 @@ class CoretaxFaktur(models.Model):
)
# Calculate totals
- total_product_amount = sum(line.get('price_subtotal', 0) if isinstance(line, dict)
+ total_product_amount = sum(line.get('price_subtotal', 0) if isinstance(line, dict)
else line.price_subtotal for line in product_lines)
if total_product_amount == 0:
total_product_amount = 1 # Avoid division by zero
@@ -147,7 +147,15 @@ class CoretaxFaktur(models.Model):
subtotal = line_price_subtotal
quantity = line_quantity
total_discount = round(line_discount, 2)
- coretax_id = line.product_uom_id.coretax_id
+ if isinstance(line, dict):
+ product = line.get('product_id')
+ uom = product.uom_id if product else False
+ else:
+ uom = line.product_uom_id
+
+ coretax_id = uom.coretax_id if uom else 'UM.0018'
+
+ # coretax_id = line.product_uom_id.coretax_id
# Calculate other tax values
otherTaxBase = round(subtotal * (11 / 12), 2) if subtotal else 0
vat_amount = round(otherTaxBase * 0.12, 2)
@@ -159,7 +167,7 @@ class CoretaxFaktur(models.Model):
ET.SubElement(good_service, 'Opt').text = 'A'
ET.SubElement(good_service, 'Code').text = '000000'
ET.SubElement(good_service, 'Name').text = line_name
- ET.SubElement(good_service, 'Unit').text = coretax_id
+ ET.SubElement(good_service, 'Unit').text = coretax_id if invoice.partner_id.id != 80007 else 'UM.0018'
# ET.SubElement(good_service, 'Price').text = str(round(line_price_unit, 2)) if line_price_unit else '0'
ET.SubElement(good_service, 'Price').text = str(price_per_unit)
ET.SubElement(good_service, 'Qty').text = str(quantity)
diff --git a/indoteknik_custom/models/ged.py b/indoteknik_custom/models/ged.py
index d438a065..38627fec 100644
--- a/indoteknik_custom/models/ged.py
+++ b/indoteknik_custom/models/ged.py
@@ -309,8 +309,9 @@ class DunningRunGed(models.Model):
if ged_tracking:
dunning.last_status_awb = ged_tracking.last_status
if ged_tracking.last_status == 'POD Return':
- dunning.date_terima_tukar_faktur = ged_tracking.delivered_date
- dunning.copy_date_faktur()
+ if not dunning.date_terima_tukar_faktur:
+ dunning.date_terima_tukar_faktur = ged_tracking.delivered_date
+ dunning.copy_date_faktur()
return
def _get_tracking_history_log(self, awb_number='all_data'):
diff --git a/indoteknik_custom/models/purchase_order.py b/indoteknik_custom/models/purchase_order.py
index 3312e7fd..3d22b0f0 100755
--- a/indoteknik_custom/models/purchase_order.py
+++ b/indoteknik_custom/models/purchase_order.py
@@ -123,6 +123,8 @@ class PurchaseOrder(models.Model):
default=True
)
+ overseas_po = fields.Boolean(string='PO Luar Negeri?', tracking=3, help='Centang jika PO untuk pembelian luar negeri')
+
@staticmethod
def is_local_env():
hostname = socket.gethostname().lower()
@@ -1150,7 +1152,7 @@ class PurchaseOrder(models.Model):
if self.is_local_env():
_logger.warning("📪 Local environment detected — skip sending email reminders.")
return
- self._send_mail()
+ # self._send_mail()
if self.revisi_po:
delta_time = current_time - timedelta(days=1)
@@ -1227,58 +1229,58 @@ class PurchaseOrder(models.Model):
'order_line_id': line.id,
}])
- def _send_mail(self):
- output = io.BytesIO()
- workbook = xlsxwriter.Workbook(output, {'in_memory': True})
- worksheet = workbook.add_worksheet()
+ # def _send_mail(self):
+ # output = io.BytesIO()
+ # workbook = xlsxwriter.Workbook(output, {'in_memory': True})
+ # worksheet = workbook.add_worksheet()
- format6 = workbook.add_format({'font_size': 12, 'align': 'center', 'bg_color': '#D3D3D3', 'bold': True})
- format1 = workbook.add_format({'font_size': 11, 'align': 'center', 'valign': 'vcenter'})
+ # format6 = workbook.add_format({'font_size': 12, 'align': 'center', 'bg_color': '#D3D3D3', 'bold': True})
+ # format1 = workbook.add_format({'font_size': 11, 'align': 'center', 'valign': 'vcenter'})
- worksheet.set_column(0, 0, 10)
- worksheet.set_column(1, 1, 20)
- worksheet.set_column(2, 2, 20)
- worksheet.set_column(3, 3, 20)
- worksheet.set_column(4, 4, 15)
- worksheet.set_column(5, 5, 15)
+ # worksheet.set_column(0, 0, 10)
+ # worksheet.set_column(1, 1, 20)
+ # worksheet.set_column(2, 2, 20)
+ # worksheet.set_column(3, 3, 20)
+ # worksheet.set_column(4, 4, 15)
+ # worksheet.set_column(5, 5, 15)
- worksheet.write('A1', 'PO', format6)
- worksheet.write('B1', 'SKU', format6)
- worksheet.write('C1', 'Product', format6)
- worksheet.write('D1', 'Brand', format6)
- worksheet.write('E1', 'PO Price', format6)
- worksheet.write('F1', 'Purchase Pricelist', format6)
- worksheet.write('G1', 'Created On', format6)
+ # worksheet.write('A1', 'PO', format6)
+ # worksheet.write('B1', 'SKU', format6)
+ # worksheet.write('C1', 'Product', format6)
+ # worksheet.write('D1', 'Brand', format6)
+ # worksheet.write('E1', 'PO Price', format6)
+ # worksheet.write('F1', 'Purchase Pricelist', format6)
+ # worksheet.write('G1', 'Created On', format6)
- row_number = 1
- po_sync = self.env['po.sync.price'].search([], order='create_date desc')
- for po in po_sync:
- worksheet.write(row_number, 0, po.order_line_id.order_id.name, format1)
- worksheet.write(row_number, 1, po.order_line_id.product_id.default_code, format1)
- worksheet.write(row_number, 2, po.order_line_id.product_id.name, format1)
- worksheet.write(row_number, 3, po.order_line_id.product_id.x_manufacture.x_name, format1)
- worksheet.write(row_number, 4, po.order_line_id.price_unit, format1)
- worksheet.write(row_number, 5, po.order_line_id.price_vendor, format1)
- worksheet.write(row_number, 6, po.create_date.replace(tzinfo=utc).astimezone(timezone('Asia/Jakarta')).strftime('%Y-%m-%d %H:%M:%S'), format1)
- row_number += 1
-
- workbook.close()
+ # row_number = 1
+ # po_sync = self.env['po.sync.price'].search([], order='create_date desc')
+ # for po in po_sync:
+ # worksheet.write(row_number, 0, po.order_line_id.order_id.name, format1)
+ # worksheet.write(row_number, 1, po.order_line_id.product_id.default_code, format1)
+ # worksheet.write(row_number, 2, po.order_line_id.product_id.name, format1)
+ # worksheet.write(row_number, 3, po.order_line_id.product_id.x_manufacture.x_name, format1)
+ # worksheet.write(row_number, 4, po.order_line_id.price_unit, format1)
+ # worksheet.write(row_number, 5, po.order_line_id.price_vendor, format1)
+ # worksheet.write(row_number, 6, po.create_date.replace(tzinfo=utc).astimezone(timezone('Asia/Jakarta')).strftime('%Y-%m-%d %H:%M:%S'), format1)
+ # row_number += 1
+
+ # workbook.close()
- output.seek(0)
-
- template = self.env.ref('indoteknik_custom.mail_template_po_sync_price')
- template.attachment_ids.unlink()
- attachment_vals = {
- 'name': 'Purchase Order.xlsx',
- 'datas': base64.b64encode(output.read()),
- 'mimetype': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
- 'res_model': 'mail.template',
- 'res_id': template.id,
- }
- attachment_id = self.env['ir.attachment'].create(attachment_vals)
-
- template.attachment_ids = [(4, attachment_id.id)]
- template.send_mail(self.id, force_send=True)
+ # output.seek(0)
+
+ # template = self.env.ref('indoteknik_custom.mail_template_po_sync_price')
+ # template.attachment_ids.unlink()
+ # attachment_vals = {
+ # 'name': 'Purchase Order.xlsx',
+ # 'datas': base64.b64encode(output.read()),
+ # 'mimetype': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
+ # 'res_model': 'mail.template',
+ # 'res_id': template.id,
+ # }
+ # attachment_id = self.env['ir.attachment'].create(attachment_vals)
+
+ # template.attachment_ids = [(4, attachment_id.id)]
+ # template.send_mail(self.id, force_send=True)
def po_approve(self):
greater_than_plafon, message = self._get_msg_plafon_qty()
diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py
index 2ed4046f..b108f544 100755
--- a/indoteknik_custom/models/sale_order.py
+++ b/indoteknik_custom/models/sale_order.py
@@ -2780,8 +2780,9 @@ class SaleOrder(models.Model):
salesperson_id = self.user_id.id
approver_id = user.id
team_leader_id = self.team_id.user_id.id
+ team = self.env['crm.team'].search([('user_id', '=', approver_id)], limit=1)
- return salesperson_id == approver_id or approver_id == team_leader_id
+ return salesperson_id == approver_id or bool(team)
def _create_approval_notification(self, approval_role):
diff --git a/indoteknik_custom/models/stock_inventory.py b/indoteknik_custom/models/stock_inventory.py
index 69cca5bc..e34d9a8e 100644
--- a/indoteknik_custom/models/stock_inventory.py
+++ b/indoteknik_custom/models/stock_inventory.py
@@ -52,6 +52,10 @@ class StockInventory(models.Model):
return "00001" # Jika format tidak valid, mulai dari 00001
return "00001" # Jika belum ada data, mulai dari 00001
+ def action_start(self):
+ if self.env.user.id != 21:
+ raise UserError("Hanya Rafly H. yang bisa start inventory")
+
@api.model
def create(self, vals):
"""Pastikan nomor hanya dibuat saat penyimpanan."""
diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py
index e7686b75..900529b9 100644
--- a/indoteknik_custom/models/stock_picking.py
+++ b/indoteknik_custom/models/stock_picking.py
@@ -1406,6 +1406,47 @@ class StockPicking(models.Model):
else:
raise UserError('Hanya MD yang bisa Approve')
+ def validate_seq_vcm(self):
+ for picking in self:
+ tg_po = picking.tukar_guling_po_id
+ if not tg_po:
+ continue
+
+ return_type = tg_po.return_type
+ if return_type not in ['retur_po', 'tukar_guling']:
+ continue
+
+ picking_order = []
+ if return_type == 'retur_po':
+ picking_order = ['BU/INPUT', 'BU/PUT']
+ elif return_type == 'tukar_guling':
+ picking_order = ['BU/VRT', 'BU/PRT', 'BU/INPUT', 'BU/PUT']
+
+ related_pickings = self.env['stock.picking'].search([
+ ('tukar_guling_po_id', '=', tg_po.id)
+ ])
+
+ ordered_pickings = []
+ for prefix in picking_order:
+ match = next((p for p in related_pickings if p.name.startswith(prefix)), None)
+ if not match:
+ raise UserError(f"Picking dengan prefix {prefix} belum ada.")
+ ordered_pickings.append(match)
+
+ current_index = -1
+ for idx, p in enumerate(ordered_pickings):
+ if p.id == picking.id:
+ current_index = idx
+ break
+
+ if current_index == -1:
+ raise UserError("Picking ini tidak ditemukan dalam urutan picking yang sesuai.")
+
+ for prev_picking in ordered_pickings[:current_index]:
+ if prev_picking.state != 'done':
+ raise UserError(
+ f"Tidak bisa validasi {picking.name} sebelum {prev_picking.name} divalidasi."
+ )
def button_validate(self):
self.check_invoice_date()
_logger.info("Kode Picking: %s", self.picking_type_id.code)
@@ -1416,6 +1457,7 @@ class StockPicking(models.Model):
group_id = self.env.ref('indoteknik_custom.group_role_merchandiser').id
users_in_group = self.env['res.users'].search([('groups_id', 'in', [group_id])])
active_model = self.env.context.get('active_model')
+ self.validate_seq_vcm()
if self.is_so_fiktif == True:
raise UserError("SO Fiktif tidak bisa di validate")
if self.location_id.id == 47 and self.env.user.id not in users_in_group.mapped(
@@ -1453,10 +1495,10 @@ class StockPicking(models.Model):
if not self.linked_manual_bu_out and 'BU/PICK/' in self.name:
raise UserError(_("Isi BU Out terlebih dahulu!"))
- if len(self.check_product_lines) == 0 and 'BU/PICK/' in self.name:
+ if len(self.check_product_lines) == 0 and 'BU/PICK/' in self.name and not self.env.user.has_group('indoteknik_custom.group_role_it'):
raise UserError(_("Tidak ada Check Product! Harap periksa kembali."))
- if len(self.check_product_lines) == 0 and 'BU/INPUT/' in self.name:
+ if len(self.check_product_lines) == 0 and 'BU/INPUT/' in self.name and not self.env.user.has_group('indoteknik_custom.group_role_it'):
raise UserError(_("Tidak ada Check Product! Harap periksa kembali."))
if self.total_koli > self.total_so_koli:
@@ -2821,4 +2863,4 @@ class StockPickingSjDocument(models.Model):
picking_id = fields.Many2one('stock.picking', required=True, ondelete='cascade')
image = fields.Binary('Gambar', required=True, attachment=True)
- sequence = fields.Integer('Urutan', default=10) \ No newline at end of file
+ sequence = fields.Integer('Urutan', default=10)
diff --git a/indoteknik_custom/models/stock_picking_return.py b/indoteknik_custom/models/stock_picking_return.py
index 53a85f67..9cf017f9 100644
--- a/indoteknik_custom/models/stock_picking_return.py
+++ b/indoteknik_custom/models/stock_picking_return.py
@@ -120,8 +120,19 @@ class ReturnPicking(models.TransientModel):
'target': 'current',
'context': context,
}
- if picking.sale_id or 'SO' in (picking.origin or ''):
- _logger.info("This picking is NOT from a PO, fallback to SO.")
+ # if picking.sale_id or 'SO' in (picking.origin or ''):
+ # _logger.info("This picking is NOT from a PO, fallback to SO.")
+ # return {
+ # 'name': _('Tukar Guling SO'),
+ # 'type': 'ir.actions.act_window',
+ # 'res_model': 'tukar.guling',
+ # 'view_mode': 'form',
+ # 'target': 'current',
+ # 'context': context,
+ # }
+
+ if picking.name and any(k in picking.name.upper() for k in ('PICK', 'OUT')):
+ _logger.info("Redirect ke Tukar Guling SO via sale_id / origin")
return {
'name': _('Tukar Guling SO'),
'type': 'ir.actions.act_window',
diff --git a/indoteknik_custom/models/tukar_guling.py b/indoteknik_custom/models/tukar_guling.py
index aa116ce3..f31c68d0 100644
--- a/indoteknik_custom/models/tukar_guling.py
+++ b/indoteknik_custom/models/tukar_guling.py
@@ -2,6 +2,7 @@ from odoo import models, fields, api, _
from odoo.exceptions import UserError, ValidationError
import logging
from datetime import datetime
+from collections import defaultdict
_logger = logging.getLogger(__name__)
@@ -91,7 +92,7 @@ class TukarGuling(models.Model):
def _compute_origin_so(self):
for rec in self:
rec.origin_so = False
- origin_str = rec.origin or rec.operations.origin
+ origin_str = rec.operations.so_num
if origin_str:
so = self.env['sale.order'].search([('name', '=', origin_str)], limit=1)
rec.origin_so = so.id if so else False
@@ -425,12 +426,8 @@ class TukarGuling(models.Model):
if not self.return_type:
raise UserError("Return Type harus diisi!")
- # if operasi == 30 and self.operations.linked_manual_bu_out.state == 'done':
- # raise UserError("❌ Tidak bisa retur BU/PICK karena BU/OUT sudah done")
if operasi == 30 and pp == 'tukar_guling':
raise UserError("❌ BU/PICK tidak boleh di retur tukar guling")
- # else:
- # _logger.info("hehhe")
if 'operations' in vals and not vals.get('origin'):
picking = self.env['stock.picking'].browse(vals['operations'])
@@ -498,8 +495,6 @@ class TukarGuling(models.Model):
('state', '!=', 'cancel'),
], limit=1)
- # if existing_tukar_guling:
- # raise UserError("BU ini sudah pernah diretur oleh dokumen %s." % existing_tukar_guling.name)
picking = self.operations
if picking.picking_type_id.id == 30 and self.return_type == 'tukar_guling':
raise UserError("❌ BU/PICK tidak boleh di retur tukar guling")
@@ -510,8 +505,6 @@ class TukarGuling(models.Model):
linked_bu_out = picking.linked_manual_bu_out
if linked_bu_out and linked_bu_out.state == 'done':
raise UserError("❌ Tidak bisa retur BU/PICK karena BU/OUT suda Done!")
- # if self._is_already_returned(self.operations):
- # raise UserError("BU ini sudah pernah diretur oleh dokumen lain.")
if self.operations.picking_type_id.id == 29:
# Cek apakah ada BU/PICK di origin
@@ -521,16 +514,30 @@ class TukarGuling(models.Model):
('picking_type_id', '=', 30),
('state', '!=', 'cancel')
]) > 0
-
if has_bu_pick:
- for line in self.line_ids:
- mapping_lines = self.mapping_koli_ids.filtered(lambda x: x.product_id == line.product_id)
- total_qty = sum(l.qty_return for l in mapping_lines)
- if total_qty != line.product_uom_qty:
- raise UserError(
- _("Qty di Koli tidak sesuai dengan qty retur untuk produk %s") % line.product_id.display_name
- )
- # self._check_invoice_on_retur_so()
+ for record in self:
+ # Step 1: Hitung total qty per produk dari line_ids
+ product_qty_map = defaultdict(float)
+ for line in record.line_ids:
+ product_qty_map[line.product_id] += line.product_uom_qty
+
+ # Step 2: Cek mapping koli
+ for product, total_line_qty in product_qty_map.items():
+ mapping_lines = record.mapping_koli_ids.filtered(lambda x: x.product_id == product)
+ total_mapping_qty = sum(l.qty_return for l in mapping_lines)
+
+ if total_mapping_qty != total_line_qty:
+ raise UserError(
+ _("Qty di Koli tidak sesuai dengan qty retur untuk produk %s") % product.display_name
+ )
+ # if has_bu_pick:
+ # for line in self.line_ids:
+ # mapping_lines = self.mapping_koli_ids.filtered(lambda x: x.product_id == line.product_id)
+ # total_qty = sum(l.qty_return for l in mapping_lines)
+ # if total_qty != line.product_uom_qty:
+ # raise UserError(
+ # _("Qty di Koli tidak sesuai dengan qty retur untuk produk %s") % line.product_id.display_name
+ # )
self._validate_product_lines()
if self.state != 'draft':
@@ -621,13 +628,28 @@ class TukarGuling(models.Model):
]) > 0
if has_bu_pick:
- for line in self.line_ids:
- mapping_lines = self.mapping_koli_ids.filtered(lambda x: x.product_id == line.product_id)
- total_qty = sum(l.qty_return for l in mapping_lines)
- if total_qty != line.product_uom_qty:
- raise UserError(
- _("Qty di Koli tidak sesuai dengan qty retur untuk produk %s") % line.product_id.display_name
- )
+ for record in self:
+ # Step 1: Hitung total qty per produk dari line_ids
+ product_qty_map = defaultdict(float)
+ for line in record.line_ids:
+ product_qty_map[line.product_id] += line.product_uom_qty
+
+ # Step 2: Cek mapping koli
+ for product, total_line_qty in product_qty_map.items():
+ mapping_lines = record.mapping_koli_ids.filtered(lambda x: x.product_id == product)
+ total_mapping_qty = sum(l.qty_return for l in mapping_lines)
+
+ if total_mapping_qty != total_line_qty:
+ raise UserError(
+ _("Qty di Koli tidak sesuai dengan qty retur untuk produk %s") % product.display_name
+ )
+ # for line in self.line_ids:
+ # mapping_lines = self.mapping_koli_ids.filtered(lambda x: x.product_id == line.product_id)
+ # total_qty = sum(l.qty_return for l in mapping_lines)
+ # if total_qty != line.product_uom_qty:
+ # raise UserError(
+ # _("Qty di Koli tidak sesuai dengan qty retur untuk produk %s") % line.product_id.display_name
+ # )
if operasi == 30 and self.operations.linked_manual_bu_out.state == 'done':
raise UserError("❌ Tidak bisa retur BU/PICK karena BU/OUT sudah done")
@@ -737,17 +759,16 @@ class TukarGuling(models.Model):
if mapping_koli and record.operations.picking_type_id.id == 29:
for prod in mapping_koli.mapped('product_id'):
qty_total = sum(mk.qty_return for mk in mapping_koli.filtered(lambda m: m.product_id == prod))
-
- move_lines = bu_out.move_line_ids.filtered(
- lambda ml: ml.product_id == prod and ml.qty_done > 0 and not ml.package_id
- )
-
- for ml in move_lines:
- srt_return_lines.append((0, 0, {
- 'product_id': ml.product_id.id,
- 'quantity': ml.qty_done,
- 'move_id': ml.move_id.id,
- }))
+ if qty_total == 0:
+ continue
+ move = bu_out.move_lines.filtered(lambda m: m.product_id == prod)
+ if not move:
+ raise UserError(f"Move BU/OUT tidak ditemukan untuk produk {prod.display_name}")
+ srt_return_lines.append((0, 0, {
+ 'product_id': prod.id,
+ 'quantity': qty_total,
+ 'move_id': move[0].id,
+ }))
_logger.info(f"📟 SRT line: {prod.display_name} | qty={qty_total}")
@@ -1038,4 +1059,4 @@ class TukarGulingMappingKoli(models.Model):
for rec in self:
if rec.tukar_guling_id and rec.tukar_guling_id.state not in ['draft', 'cancel']:
raise UserError("Tidak bisa menghapus Mapping Koli karena status Tukar Guling bukan Draft atau Cancel.")
- return super(TukarGulingMappingKoli, self).unlink() \ No newline at end of file
+ return super(TukarGulingMappingKoli, self).unlink()
diff --git a/indoteknik_custom/report/purchase_report.xml b/indoteknik_custom/report/purchase_report.xml
index 208e6472..d3509720 100644
--- a/indoteknik_custom/report/purchase_report.xml
+++ b/indoteknik_custom/report/purchase_report.xml
@@ -61,9 +61,17 @@
<td style="width:50%; border:1px solid #ccc; border-radius:8px; padding:10px; background:#fcfcfc; vertical-align:top;">
<strong style="color:#d32f2f;">Alamat Pengiriman</strong><br/>
PT. Indoteknik Dotcom Gemilang<br/>
- Jl. Bandengan Utara Komp A 8 B
- RT. Penjaringan, Kec. Penjaringan, Jakarta
- (BELAKANG INDOMARET)<br/>
+ <t t-if="doc.overseas_po == True">
+ JALAN BANDENGAN UTARA 85A NO.8-9 RT. 003<br/>
+ RW. 016, PENJARINGAN, PENJARINGAN, KOTA<br/>
+ ADM. JAKARTA UTARA, DKI JAKARTA
+ </t>
+ <t t-else="">
+ Jl. Bandengan Utara Komp A 8 B RT.<br/>
+ Penjaringan, Kec. Penjaringan, Jakarta<br/>
+ (BELAKANG INDOMARET)
+ </t>
+ <br/>
Daerah Khusus Ibukota Jakarta 14440
</td>
<td style="width:50%; border:1px solid #ccc; border-radius:8px; padding:10px; background:#fcfcfc; vertical-align:top;">
@@ -97,10 +105,16 @@
<!-- TEKS -->
<div style="display:flex; flex-direction:column; flex:1;">
<span style="font-weight:bold; margin-bottom:2px;">
- <t t-esc="line_index + 1"/>. <t t-esc="line.name"/>
- <!-- <t t-esc="line_index + 1"/>. <t t-esc="line.product_id.display_name"/> -->
+ <t t-esc="line_index + 1"/>.
+ <t t-if="line.product_id.id == 114360">
+ <t t-esc="line.name"/>
+ </t>
+ <t t-else="">
+ <t t-esc="line.product_id.display_name"/>
+ </t>
</span>
</div>
+
</td>
<td style="border:1px solid #ccc; padding:6px; text-align:center;">
diff --git a/indoteknik_custom/views/account_move_line.xml b/indoteknik_custom/views/account_move_line.xml
index 3a20388e..cb24a0f0 100644
--- a/indoteknik_custom/views/account_move_line.xml
+++ b/indoteknik_custom/views/account_move_line.xml
@@ -16,6 +16,9 @@
<attribute name="groups"/>
<attribute name="options">{'no_create': True}</attribute>
</xpath>
+ <xpath expr="//field[@name='line_ids']/tree/field[@name='partner_id']" position="attributes">
+ <attribute name="options">{'no_create': True, 'no_open': True}</attribute>
+ </xpath>
</field>
</record>
</data>
diff --git a/indoteknik_custom/views/account_move_views.xml b/indoteknik_custom/views/account_move_views.xml
index 08b93f1f..b226460c 100644
--- a/indoteknik_custom/views/account_move_views.xml
+++ b/indoteknik_custom/views/account_move_views.xml
@@ -30,7 +30,7 @@
<field name="date_invoice"/>
<field name="due_date"/>
<field name="day_to_due"/>
- <field name="efaktur_id"/>
+ <field name="user_id" widget="many2one_avatar_user"/>
<field name="reference"/>
<field name="total_amt"/>
<field name="open_amt" sum="Grand Total Open Amount"/>
diff --git a/indoteknik_custom/views/advance_payment_request.xml b/indoteknik_custom/views/advance_payment_request.xml
index 4e73bb28..278deda5 100644
--- a/indoteknik_custom/views/advance_payment_request.xml
+++ b/indoteknik_custom/views/advance_payment_request.xml
@@ -86,12 +86,13 @@
<group string=" ">
<field name="type_request" attrs="{'readonly': [('status', '=', 'approved')]}"/>
<field name="is_represented" attrs="{'readonly': [('status', '=', 'approved')]}"/>
- <field name="applicant_name" colspan="2" attrs="{'readonly': [('status', '=', 'approved')]}"/>
+ <p style="font-size: 11px; color: grey; font-style: italic" >*Centang jika Anda membuat pengajuan ini untuk orang lain.</p>
+ <field name="applicant_name" colspan="2" attrs="{'readonly': ['|', ('status', '=', 'approved'), ('is_represented', '=', False)]}" force_save="1"/>
<field name="position_type" force_save="1" readonly="1"/>
<field name="nominal" colspan="2" attrs="{'readonly': ['|', ('status', '=', 'approved'), ('type_request', '=', 'reimburse')]}" force_save="1"/>
- <p style="font-size: 10px; color: grey; font-style: italic" attrs="{'invisible': [('type_request', '!=', 'reimburse')]}">*Nominal terisi otomatis sesuai grand total rincian reimburse</p>
+ <p style="font-size: 11px; color: grey; font-style: italic" attrs="{'invisible': [('type_request', '!=', 'reimburse')]}">*Nominal terisi otomatis sesuai grand total rincian reimburse</p>
<field name="bank_name" colspan="2" attrs="{'readonly': [('status', '=', 'approved')]}"/>
- <field name="account_name" colspan="2" attrs="{'readonly': [('status', '=', 'approved')]}"/>
+ <field name="account_name" colspan="2" attrs="{'readonly': ['|', ('status', '=', 'approved'), ('is_represented', '=', False)]}" force_save="1"/>
<field name="bank_account" colspan="2" attrs="{'readonly': [('status', '=', 'approved')]}"/>
<field name="detail_note" attrs="{'readonly': [('status', '=', 'approved')]}"/>
<br/>
@@ -130,6 +131,7 @@
<page string="Rincian Reimburse">
<field name="reimburse_line_ids">
<tree>
+ <field name="sequence" widget="handle"/>
<field name="date"/>
<field name="description"/>
<field name="account_id"/>
@@ -219,6 +221,7 @@
<field name="model">advance.payment.request</field>
<field name="arch" type="xml">
<search string="Search APR &amp; Reimburse">
+ <field name="number" string="No. Dokumen"/>
<filter string="My Requests" name="my_requests" domain="[('applicant_name','=',uid)]"/>
<separator/>
<filter string="PUM" name="filter_pum" domain="[('type_request','=','pum')]"/>
diff --git a/indoteknik_custom/views/advance_payment_settlement.xml b/indoteknik_custom/views/advance_payment_settlement.xml
index 050e3933..a77baffe 100644
--- a/indoteknik_custom/views/advance_payment_settlement.xml
+++ b/indoteknik_custom/views/advance_payment_settlement.xml
@@ -74,6 +74,7 @@
<page string="Rincian Penggunaan">
<field name="penggunaan_line_ids" nolabel="1">
<tree>
+ <field name="sequence" widget="handle"/>
<field name="date"/>
<field name="description"/>
<field name="nominal" sum="Total Penggunaan"/>
diff --git a/indoteknik_custom/views/dunning_run.xml b/indoteknik_custom/views/dunning_run.xml
index 911a372d..52a0c6ae 100644
--- a/indoteknik_custom/views/dunning_run.xml
+++ b/indoteknik_custom/views/dunning_run.xml
@@ -25,8 +25,8 @@
<field name="arch" type="xml">
<tree>
<field name="partner_id"/>
- <field name="information_line"/>
- <field name="reference"/>
+ <field name="information_line" optional="hide"/>
+ <field name="reference" optional="hide"/>
<field name="invoice_id"/>
<field name="date_invoice"/>
<field name="efaktur_id" optional="hide"/>
@@ -125,4 +125,4 @@
action="dunning_run_action"
parent="account.menu_finance_reports"
sequence="200"/>
-</odoo> \ No newline at end of file
+</odoo>
diff --git a/indoteknik_custom/views/mail_template_po.xml b/indoteknik_custom/views/mail_template_po.xml
index edbff609..9163d941 100644
--- a/indoteknik_custom/views/mail_template_po.xml
+++ b/indoteknik_custom/views/mail_template_po.xml
@@ -6,7 +6,8 @@
<field name="model_id" ref="model_purchase_order" />
<field name="subject">Your PO ${object.name}</field>
<field name="email_from"></field>
- <field name="email_to">darren@indoteknik.co.id, azkan4elll@gmail.com</field>
+ <!-- <field name="email_to">darren@indoteknik.co.id, azkan4elll@gmail.com</field> -->
+ <field name="email_to">azkan4elll@gmail.com</field>
<field name="body_html" type="html">
<table border="0" cellpadding="0" cellspacing="0"
style="padding-top: 16px; background-color: #F1F1F1; font-family:Verdana, Arial,sans-serif; color: #454748; width: 100%; border-collapse:separate;">
diff --git a/indoteknik_custom/views/purchase_order.xml b/indoteknik_custom/views/purchase_order.xml
index 09d901b9..16b8bd44 100755
--- a/indoteknik_custom/views/purchase_order.xml
+++ b/indoteknik_custom/views/purchase_order.xml
@@ -179,6 +179,7 @@
<field name="from_apo"/>
<field name="approval_edit_line"/>
<field name="logbook_bill_id"/>
+ <field name="overseas_po"/>
<field name="bills_dp_id" readonly="1"/>
<field name="bills_pelunasan_id" readonly="1"/>
</field>
diff --git a/indoteknik_custom/views/unpaid_invoice_view.xml b/indoteknik_custom/views/unpaid_invoice_view.xml
index ec6c749d..86b15a07 100644
--- a/indoteknik_custom/views/unpaid_invoice_view.xml
+++ b/indoteknik_custom/views/unpaid_invoice_view.xml
@@ -13,10 +13,10 @@
<field name="date_terima_tukar_faktur"/>
<field name="payment_term_id"/>
<field name="invoice_date_due" widget="badge" decoration-danger="invoice_day_to_due &lt; 0"/>
- <field name="invoice_day_to_due" readonly="1"/>
- <field name="new_invoice_day_to_due" readonly="1"/>
- <field name="amount_total"/>
- <field name="amount_residual"/>
+ <field name="invoice_day_to_due" widget="badge" decoration-danger="invoice_day_to_due &lt; 0" readonly="1"/>
+ <field name="new_invoice_day_to_due" widget="badge" decoration-danger="invoice_day_to_due &lt; 0" readonly="1"/>
+ <field name="amount_total" sum="Total Amount"/>
+ <field name="amount_residual" sum="Sisa Amount"/>
<field name="payment_state" widget="badge"
decoration-danger="payment_state == 'not_paid'"
decoration-warning="payment_state == 'partial'"/>
@@ -79,6 +79,14 @@
<search string="Search Unpaid Invoices">
<field name="partner_id"/>
<field name="invoice_number"/>
+ <separator/>
+ <filter name="overdue" string="Overdue" domain="[('new_invoice_day_to_due', '&lt;', 0)]"/>
+ <filter name="today" string="Today" domain="[('new_invoice_day_to_due', '=', 0)]"/>
+ <filter name="due" string="Due" domain="[('new_invoice_day_to_due', '&lt;', 0)]"/>
+ <separator/>
+ <group expand="0" string="Group By">
+ <filter string="Partner" name="group_partner" context="{'group_by': 'partner_id'}"/>
+ </group>
</search>
</field>
</record>