summaryrefslogtreecommitdiff
path: root/indoteknik_custom/models/stock_picking.py
diff options
context:
space:
mode:
Diffstat (limited to 'indoteknik_custom/models/stock_picking.py')
-rw-r--r--indoteknik_custom/models/stock_picking.py223
1 files changed, 195 insertions, 28 deletions
diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py
index c5b112a3..5a793382 100644
--- a/indoteknik_custom/models/stock_picking.py
+++ b/indoteknik_custom/models/stock_picking.py
@@ -5,16 +5,16 @@ from collections import defaultdict
from datetime import timedelta, datetime
from datetime import timedelta, datetime as waktu
from itertools import groupby
-import pytz, requests, json, requests
+import pytz, json
from dateutil import parser
import datetime
import hmac
import hashlib
-import base64
import requests
import time
import logging
import re
+import base64
_logger = logging.getLogger(__name__)
@@ -89,6 +89,7 @@ class StockPicking(models.Model):
readonly=True,
related="id",
)
+ sj_documentations = fields.One2many('stock.picking.sj.document','picking_id', string='Dokumentasi SJ (Multi)')
sj_documentation = fields.Binary(string="Dokumentasi Surat Jalan")
paket_documentation = fields.Binary(string="Dokumentasi Paket")
dispatch_documentation = fields.Binary(string="Dokumentasi Dispatch")
@@ -177,6 +178,113 @@ class StockPicking(models.Model):
area_name = fields.Char(string="Area", compute="_compute_area_name")
is_bu_iu = fields.Boolean('Is BU/IU', compute='_compute_is_bu_iu', default=False, copy=False, readonl=True)
+ qty_yang_mau_dikirim = fields.Float(
+ string='Qty yang Mau Dikirim',
+ compute='_compute_delivery_status_detail',
+ store=False
+ )
+ qty_terkirim = fields.Float(
+ string='Qty Terkirim',
+ compute='_compute_delivery_status_detail',
+ store=False
+ )
+ qty_gantung = fields.Float(
+ string='Qty Gantung',
+ compute='_compute_delivery_status_detail',
+ store=False
+ )
+ delivery_status = fields.Selection([
+ ('none', 'No Movement'),
+ ('partial', 'Partial'),
+ ('partial_final', 'Partial Final'),
+ ('full', 'Full'),
+ ], string='Delivery Status', compute='_compute_delivery_status_detail', store=False)
+ so_num = fields.Char('SO Number', compute='_get_so_num')
+ is_so_fiktif = fields.Boolean('SO Fiktif?', compute='_compute_is_so_fiktif', tracking=3)
+
+ @api.depends('sale_id.is_so_fiktif')
+ def _compute_is_so_fiktif(self):
+ for picking in self:
+ picking.is_so_fiktif = picking.sale_id.is_so_fiktif if picking.sale_id else False
+
+
+ @api.depends('group_id')
+ def _get_so_num(self):
+ for record in self:
+ record.so_num = record.group_id.name
+
+ @api.depends('move_line_ids_without_package.qty_done', 'move_line_ids_without_package.product_uom_qty', 'state')
+ def _compute_delivery_status_detail(self):
+ for picking in self:
+ # Default values
+ picking.qty_yang_mau_dikirim = 0.0
+ picking.qty_terkirim = 0.0
+ picking.qty_gantung = 0.0
+ picking.delivery_status = 'none'
+
+ # Hanya berlaku untuk pengiriman (BU/OUT)
+ if picking.picking_type_id.code != 'outgoing':
+ continue
+
+ if picking.name not in ['BU/OUT']:
+ continue
+
+ move_lines = picking.move_line_ids_without_package
+ if not move_lines:
+ continue
+
+ # ======================
+ # HITUNG QTY
+ # ======================
+ total_qty = sum(line.product_uom_qty for line in move_lines)
+
+ done_qty_total = sum(line.sale_line_id.qty_delivered for line in picking.move_ids_without_package)
+ order_qty_total = sum(line.sale_line_id.product_uom_qty for line in picking.move_ids_without_package)
+ gantung_qty_total = order_qty_total - done_qty_total - total_qty
+
+ picking.qty_yang_mau_dikirim = total_qty
+ picking.qty_terkirim = done_qty_total
+ picking.qty_gantung = gantung_qty_total
+
+ # if total_qty == 0:
+ # picking.delivery_status = 'none'
+ # continue
+
+ # if done_qty_total == 0:
+ # picking.delivery_status = 'none'
+ # continue
+
+ # ======================
+ # CEK BU/OUT LAIN (BACKORDER)
+ # ======================
+ # has_other_out = self.env['stock.picking'].search_count([
+ # ('group_id', '=', picking.group_id.id),
+ # ('name', 'ilike', 'BU/OUT'),
+ # ('id', '!=', picking.id),
+ # ('state', 'in', ['assigned', 'waiting', 'confirmed', 'done']),
+ # ])
+
+ # ======================
+ # LOGIKA STATUS
+ # ======================
+ if gantung_qty_total == 0 and done_qty_total == 0:
+ # Semua barang udah terkirim, ga ada picking lain
+ picking.delivery_status = 'full'
+
+ elif gantung_qty_total > 0 and total_qty > 0 and done_qty_total == 0:
+ # Masih ada picking lain dan sisa gantung → proses masih jalan
+ picking.delivery_status = 'partial'
+
+ # elif gantung_qty_total > 0:
+ # # Ini picking terakhir, tapi qty belum full
+ # picking.delivery_status = 'partial_final'
+
+ elif gantung_qty_total == 0 and done_qty_total > 0 and total_qty > 0:
+ # Udah kirim semua tapi masih ada picking lain (rare case)
+ picking.delivery_status = 'partial_final'
+
+ else:
+ picking.delivery_status = 'none'
@api.depends('name')
def _compute_is_bu_iu(self):
@@ -366,7 +474,6 @@ class StockPicking(models.Model):
except ValueError:
return False
-
def action_get_kgx_pod(self, shipment=False):
self.ensure_one()
@@ -464,15 +571,15 @@ class StockPicking(models.Model):
rec.last_update_date_doc_kirim = datetime.datetime.utcnow()
- @api.constrains('scan_koli_lines')
- def _constrains_scan_koli_lines(self):
- now = datetime.datetime.utcnow()
- for picking in self:
- if len(picking.scan_koli_lines) > 0:
- if len(picking.scan_koli_lines) != picking.total_mapping_koli:
- raise UserError("Scan Koli Tidak Sesuai Dengan Total Mapping Koli")
+ # @api.constrains('scan_koli_lines')
+ # def _constrains_scan_koli_lines(self):
+ # now = datetime.datetime.utcnow()
+ # for picking in self:
+ # if len(picking.scan_koli_lines) > 0:
+ # if len(picking.scan_koli_lines) != picking.total_mapping_koli:
+ # raise UserError("Scan Koli Tidak Sesuai Dengan Total Mapping Koli")
- picking.driver_departure_date = now
+ # picking.driver_departure_date = now
@api.depends('total_so_koli')
def _compute_total_so_koli(self):
@@ -716,6 +823,37 @@ class StockPicking(models.Model):
picking.envio_cod_value = data.get("cod_value", 0.0)
picking.envio_cod_status = data.get("cod_status")
+ images_data = data.get('images', [])
+ for img in images_data:
+ image_url = img.get('image')
+ if image_url:
+ try:
+ # Download image from URL
+ img_response = requests.get(image_url)
+ img_response.raise_for_status()
+
+ # Encode image to base64
+ image_base64 = base64.b64encode(img_response.content)
+
+ # Create attachment in Odoo
+ attachment = self.env['ir.attachment'].create({
+ 'name': 'Envio Image',
+ 'type': 'binary',
+ 'datas': image_base64,
+ 'res_model': picking._name,
+ 'res_id': picking.id,
+ 'mimetype': 'image/png',
+ })
+
+ # Post log note with attachment
+ picking.message_post(
+ body="Image Envio",
+ attachment_ids=[attachment.id]
+ )
+
+ except Exception as e:
+ picking.message_post(body=f"Gagal ambil image Envio: {str(e)}")
+
# Menyimpan log terbaru
logs = data.get("logs", [])
if logs and isinstance(logs, list) and logs[0]:
@@ -1087,7 +1225,8 @@ class StockPicking(models.Model):
self.sale_id.date_doc_kirim = self.date_doc_kirim
def action_assign(self):
- if self.env.context.get('default_picking_type_id') and ('BU/INPUT' not in self.name or 'BU/PUT' not in self.name):
+ if self.env.context.get('default_picking_type_id') and (
+ 'BU/INPUT' not in self.name or 'BU/PUT' not in self.name):
pickings_to_assign = self.filtered(
lambda p: not (p.sale_id and p.sale_id.hold_outgoing)
)
@@ -1103,18 +1242,15 @@ class StockPicking(models.Model):
return res
-
def ask_approval(self):
# if self.env.user.is_accounting:
# if self.env.user.is_accounting and self.location_id.id == 57 or self.location_id == 57 and self.approval_status in ['pengajuan1', ''] and 'BU/IU' in self.name and self.approval_status == 'pengajuan1':
# raise UserError("Bisa langsung set ke approval logistik")
if self.env.user.is_accounting and self.approval_status == "pengajuan2" and 'BU/IU' in self.name:
raise UserError("Tidak perlu ask approval sudah approval logistik")
- if self.env.user.is_logistic_approver and self.location_id.id == 57 or self.location_id== 57 and self.approval_status == 'pengajuan2' and 'BU/IU' in self.name:
+ if self.env.user.is_logistic_approver and self.location_id.id == 57 or self.location_id == 57 and self.approval_status == 'pengajuan2' and 'BU/IU' in self.name:
raise UserError("Bisa langsung Validate")
-
-
# for calendar distribute only
# if self.is_internal_use:
# stock_move_lines = self.env['stock.move.line'].search([
@@ -1137,7 +1273,6 @@ class StockPicking(models.Model):
raise UserError("Qty tidak boleh 0")
pick.approval_status = 'pengajuan1'
-
def ask_receipt_approval(self):
if self.env.user.is_logistic_approver:
raise UserError('Bisa langsung validate tanpa Ask Receipt')
@@ -1281,6 +1416,8 @@ 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')
+ 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(
'id') and self.state_approve_md != 'done':
self.state_approve_md = 'waiting' if self.state_approve_md != 'pending' else 'pending'
@@ -1304,6 +1441,9 @@ class StockPicking(models.Model):
and not self.so_lama):
raise UserError(_("Tidak ada scan koli! Harap periksa kembali."))
+ if 'BU/OUT/' in self.name:
+ self.driver_departure_date = datetime.datetime.utcnow()
+
# if self.driver_departure_date == False and 'BU/OUT/' in self.name and self.picking_type_code == 'outgoing':
# raise UserError(_("Isi Driver Departure Date dulu sebelum validate"))
@@ -1316,6 +1456,9 @@ class StockPicking(models.Model):
if len(self.check_product_lines) == 0 and 'BU/PICK/' in self.name:
raise UserError(_("Tidak ada Check Product! Harap periksa kembali."))
+ if len(self.check_product_lines) == 0 and 'BU/INPUT/' in self.name:
+ raise UserError(_("Tidak ada Check Product! Harap periksa kembali."))
+
if self.total_koli > self.total_so_koli:
raise UserError(_("Total Koli (%s) dan Total SO Koli (%s) tidak sama! Harap periksa kembali.")
% (self.total_koli, self.t1otal_so_koli))
@@ -1342,13 +1485,16 @@ class StockPicking(models.Model):
# if self.is_internal_use and not self.env.user.is_logistic_approver and self.location_id.id == 57 and self.approval_status == 'pengajuan2':
# raise UserError("Harus di Approve oleh Logistik")
- if self.is_internal_use and self.approval_status in ['pengajuan1', '', False] and 'BU/IU' in self.name and self.is_bu_iu == True:
+ if self.is_internal_use and self.approval_status in ['pengajuan1', '',
+ False] and 'BU/IU' in self.name and self.is_bu_iu == True:
raise UserError("Tidak Bisa Validate, set approval status ke approval logistik terlebih dahhulu")
- if self.is_internal_use and not self.env.user.is_logistic_approver and self.approval_status in ['pengajuan2'] and self.is_bu_iu == True and 'BU/IU' in self.name:
+ if self.is_internal_use and not self.env.user.is_logistic_approver and self.approval_status in [
+ 'pengajuan2'] and self.is_bu_iu == True and 'BU/IU' in self.name:
raise UserError("Harus di Approve oleh Logistik")
- if self.is_internal_use and not self.env.user.is_accounting and self.approval_status in ['pengajuan1', '', False] and self.is_bu_iu == False:
+ if self.is_internal_use and not self.env.user.is_accounting and self.approval_status in ['pengajuan1', '',
+ False] and self.is_bu_iu == False:
raise UserError("Harus di Approve oleh Accounting")
if self.picking_type_id.id == 28 and not self.env.user.is_logistic_approver:
@@ -1374,7 +1520,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:
@@ -1396,9 +1541,9 @@ class StockPicking(models.Model):
self.check_koli()
res = super(StockPicking, self).button_validate()
- # Penambahan link PO di Stock Journal untuk Picking BD
+ # Penambahan link PO di Stock Journal
for picking in self:
- if picking.name and 'BD/' in picking.name and picking.purchase_id:
+ if picking.name and picking.purchase_id:
stock_journal = self.env['account.move'].search([
('ref', 'ilike', picking.name + '%'),
('journal_id', '=', 3) # Stock Journal ID
@@ -1457,6 +1602,10 @@ class StockPicking(models.Model):
elif self.tukar_guling_po_id:
self.tukar_guling_po_id.update_doc_state()
+ user = self.env.user
+ if not user.has_group('indoteknik_custom.group_role_logistic') and 'BU/IU' in self.name:
+ raise UserWarning('Validate hnaya bisa di lakukan oleh logistik')
+
return res
def automatic_reserve_product(self):
@@ -1627,6 +1776,15 @@ class StockPicking(models.Model):
'indoteknik_custom.group_role_logistic') and self.picking_type_code == 'outgoing':
raise UserError("Button ini hanya untuk Logistik")
+ if len(self.check_product_lines) >= 1:
+ raise UserError("Tidak Bisa cancel karena sudah di check product")
+
+ if not self.env.user.is_logistic_approver and not self.env.user.has_group('indoteknik_custom.group_role_logistic'):
+ for picking in self:
+ if picking.name and ('BU/PICK' in picking.name or 'BU/OUT' in picking.name or 'BU/ORT' in picking.name or 'BU/SRT' in picking.name):
+ if picking.state not in ['cancel']:
+ raise UserError("Button ini hanya untuk Logistik")
+
res = super(StockPicking, self).action_cancel()
return res
@@ -1775,7 +1933,8 @@ class StockPicking(models.Model):
'name': move_line.product_id.name,
'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),
+ 'image': self.env['ir.attachment'].api_image('product.template', 'image_128',
+ move_line.product_id.product_tmpl_id.id),
})
response = {
@@ -2141,7 +2300,6 @@ class CheckProduct(models.Model):
_order = 'picking_id, id'
_inherit = ['barcodes.barcode_events_mixin']
-
picking_id = fields.Many2one(
'stock.picking',
string='Picking Reference',
@@ -2627,8 +2785,6 @@ class ScanKoli(models.Model):
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)])
@@ -2687,4 +2843,15 @@ class WarningModalWizard(models.TransientModel):
def action_continue(self):
if self.picking_id:
return self.picking_id.with_context(skip_koli_check=True).button_validate()
- return {'type': 'ir.actions.act_window_close'} \ No newline at end of file
+ return {'type': 'ir.actions.act_window_close'}
+
+
+class StockPickingSjDocument(models.Model):
+ _name = 'stock.picking.sj.document'
+ _description = 'Dokumentasi Surat Jalan (Multi)'
+ _order = 'sequence, id'
+ _rec_name = 'id'
+
+ 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