summaryrefslogtreecommitdiff
path: root/indoteknik_custom/models/stock_picking.py
diff options
context:
space:
mode:
authorit-fixcomart <it@fixcomart.co.id>2025-01-15 15:43:42 +0700
committerit-fixcomart <it@fixcomart.co.id>2025-01-15 15:43:42 +0700
commitbe1ee2092fef86e79932206cc48d5a146107ac32 (patch)
treee54a8c6ffda996f212ab1c2d03b27a32bab8ba89 /indoteknik_custom/models/stock_picking.py
parent8b2897d9c72eb67382221d320d488543aea08323 (diff)
parente4ec9406cd0903db59cfed34781da55a2dba4ca3 (diff)
Merge branch 'odoo-production' into iman/switch-account
Diffstat (limited to 'indoteknik_custom/models/stock_picking.py')
-rw-r--r--indoteknik_custom/models/stock_picking.py338
1 files changed, 324 insertions, 14 deletions
diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py
index 17dd5766..cd330aeb 100644
--- a/indoteknik_custom/models/stock_picking.py
+++ b/indoteknik_custom/models/stock_picking.py
@@ -1,13 +1,22 @@
from odoo import fields, models, api, _
from odoo.exceptions import AccessError, UserError, ValidationError
from odoo.tools.float_utils import float_is_zero
-from datetime import datetime
+from datetime import timedelta, datetime
from itertools import groupby
-import pytz, datetime, requests, json
-
+import pytz, requests, json, requests
+from dateutil import parser
+import datetime
+import hmac
+import hashlib
+import base64
+import requests
+import time
+import logging
+_logger = logging.getLogger(__name__)
class StockPicking(models.Model):
_inherit = 'stock.picking'
+ # check_product_lines = fields.One2many('check.product', 'picking_id', string='Check Product', auto_join=True)
is_internal_use = fields.Boolean('Internal Use', help='flag which is internal use or not')
account_id = fields.Many2one('account.account', string='Account')
efaktur_id = fields.Many2one('vit.efaktur', string='Faktur Pajak')
@@ -113,6 +122,213 @@ class StockPicking(models.Model):
], string='Status Reserve', readonly=True, tracking=True, help="The current state of the stock picking.")
notee = fields.Text(string="Note")
+ @api.model
+ def _compute_dokumen_tanda_terima(self):
+ for picking in self:
+ picking.dokumen_tanda_terima = picking.partner_id.dokumen_pengiriman
+
+ @api.model
+ def _compute_dokumen_pengiriman(self):
+ for picking in self:
+ picking.dokumen_pengiriman = picking.partner_id.dokumen_pengiriman_input
+
+ dokumen_tanda_terima = fields.Char(string='Dokumen Tanda Terima yang Diberikan Pada Saat Pengiriman Barang', readonly=True, compute=_compute_dokumen_tanda_terima)
+ dokumen_pengiriman = fields.Char(string='Dokumen yang Dibawa Saat Pengiriman Barang', readonly=True, compute=_compute_dokumen_pengiriman)
+
+ # Envio Tracking Section
+ envio_id = fields.Char(string="Envio ID", readonly=True)
+ envio_code = fields.Char(string="Envio Code", readonly=True)
+ envio_ref_code = fields.Char(string="Envio Reference Code", readonly=True)
+ envio_eta_at = fields.Datetime(string="Estimated Time of Arrival (ETA)", readonly=True)
+ envio_ata_at = fields.Datetime(string="Actual Time of Arrival (ATA)", readonly=True)
+ envio_etd_at = fields.Datetime(string="Estimated Time of Departure (ETD)", readonly=True)
+ envio_atd_at = fields.Datetime(string="Actual Time of Departure (ATD)", readonly=True)
+ envio_received_by = fields.Char(string="Received By", readonly=True)
+ envio_status = fields.Char(string="Status", readonly=True)
+ envio_cod_value = fields.Float(string="COD Value", readonly=True)
+ envio_cod_status = fields.Char(string="COD Status", readonly=True)
+ envio_logs = fields.Text(string="Logs", readonly=True)
+ envio_latest_message = fields.Text(string="Latest Log Message", readonly=True)
+ envio_latest_recorded_at = fields.Datetime(string="Log Recorded At", readonly=True)
+ envio_latest_latitude = fields.Float(string="Log Latitude", readonly=True)
+ envio_latest_longitude = fields.Float(string="Log Longitude", readonly=True)
+ tracking_by = fields.Many2one('res.users', string='Tracking By', readonly=True, tracking=True)
+
+ # Lalamove Section
+ lalamove_order_id = fields.Char(string="Lalamove Order ID", copy=False)
+ lalamove_address = fields.Char(string="Lalamove Address")
+ lalamove_name = fields.Char(string="Lalamove Name")
+ lalamove_phone = fields.Char(string="Lalamove Phone")
+ lalamove_status = fields.Char(string="Lalamove Status")
+ lalamove_delivered_at = fields.Datetime(string="Lalamove Delivered At")
+ lalamove_data = fields.Text(string="Lalamove Data", readonly=True)
+ lalamove_image_url = fields.Char(string="Lalamove Image URL")
+ lalamove_image_html = fields.Html(string="Lalamove Image", compute="_compute_lalamove_image_html")
+
+ def _compute_lalamove_image_html(self):
+ for record in self:
+ if record.lalamove_image_url:
+ record.lalamove_image_html = f'<img src="{record.lalamove_image_url}" width="300" height="300"/>'
+ else:
+ record.lalamove_image_html = "No image available."
+
+ def action_fetch_lalamove_order(self):
+ pickings = self.env['stock.picking'].search([
+ ('picking_type_code', '=', 'outgoing'),
+ ('state', '=', 'done'),
+ ('carrier_id', '=', 9)
+ ])
+ for picking in pickings:
+ try:
+ order_id = picking.lalamove_order_id
+ apikey = self.env['ir.config_parameter'].sudo().get_param('lalamove.apikey')
+ secret = self.env['ir.config_parameter'].sudo().get_param('lalamove.secret')
+ market = self.env['ir.config_parameter'].sudo().get_param('lalamove.market', default='ID')
+
+ order_data = picking.get_lalamove_order(order_id, apikey, secret, market)
+ picking.lalamove_data = order_data
+ except Exception as e:
+ _logger.error(f"Error fetching Lalamove order for picking {picking.id}: {str(e)}")
+ continue
+
+ def get_lalamove_order(self, order_id, apikey, secret, market):
+ timestamp = str(int(time.time() * 1000))
+ message = f"{timestamp}\r\nGET\r\n/v3/orders/{order_id}\r\n\r\n"
+ signature = hmac.new(secret.encode('utf-8'), message.encode('utf-8'), hashlib.sha256).hexdigest()
+
+ headers = {
+ "Content-Type": "application/json",
+ "Authorization": f"hmac {apikey}:{timestamp}:{signature}",
+ "Market": market
+ }
+
+ url = f"https://rest.lalamove.com/v3/orders/{order_id}"
+ response = requests.get(url, headers=headers)
+
+ if response.status_code == 200:
+ data = response.json()
+ stops = data.get("data", {}).get("stops", [])
+
+ for stop in stops:
+ pod = stop.get("POD", {})
+ if pod.get("status") == "DELIVERED":
+ image_url = pod.get("image") # Sesuaikan jika key berbeda
+ self.lalamove_image_url = image_url
+
+ address = stop.get("address")
+ name = stop.get("name")
+ phone = stop.get("phone")
+ delivered_at = pod.get("deliveredAt")
+
+ delivered_at_dt = self._convert_to_datetime(delivered_at)
+
+ self.lalamove_address = address
+ self.lalamove_name = name
+ self.lalamove_phone = phone
+ self.lalamove_status = pod.get("status")
+ self.lalamove_delivered_at = delivered_at_dt
+ return data
+
+ raise UserError("No delivered data found in Lalamove response.")
+ else:
+ raise UserError(f"Error {response.status_code}: {response.text}")
+
+
+ def _convert_to_wib(self, date_str):
+ """
+ Mengonversi string waktu ISO 8601 ke format waktu Indonesia (WIB)
+ """
+ if not date_str:
+ return False
+ try:
+ utc_time = datetime.datetime.strptime(date_str, '%Y-%m-%dT%H:%M:%SZ')
+ wib_time = utc_time + timedelta(hours=7)
+ return wib_time.strftime('%d-%m-%Y %H:%M:%S')
+ except ValueError:
+ raise UserError(f"Format waktu tidak sesuai: {date_str}")
+
+ def _convert_to_datetime(self, date_str):
+ """Mengonversi string waktu dari API ke datetime."""
+ if not date_str:
+ return False
+ try:
+ # Format waktu dengan milidetik
+ date = datetime.datetime.strptime(date_str, '%Y-%m-%dT%H:%M:%S.%fZ')
+ return date
+ except ValueError:
+ try:
+ # Format waktu tanpa milidetik
+ date = datetime.datetime.strptime(date_str, '%Y-%m-%dT%H:%M:%SZ')
+ return date
+ except ValueError:
+ raise UserError(f"Format waktu tidak sesuai: {date_str}")
+
+ def track_envio_shipment(self):
+ pickings = self.env['stock.picking'].search([
+ ('picking_type_code', '=', 'outgoing'),
+ ('state', '=', 'done'),
+ ('carrier_id', '=', 151)
+ ])
+ for picking in pickings:
+ if not picking.name:
+ raise UserError("Name pada stock.picking tidak ditemukan.")
+
+ # API URL dan headers
+ url = f"https://api.envio.co.id/v1/tracking/distribution?code={picking.name}"
+ headers = {
+ 'Authorization': 'Bearer JZ0Seh6qpYJAC3CJHdhF7sPqv8B/uSSfZe1VX5BL?vPYdo',
+ 'Content-Type': 'application/json',
+ }
+
+ try:
+ # Request ke API
+ response = requests.get(url, headers=headers, timeout=10)
+ response.raise_for_status() # Raise error jika status code bukan 200
+ response_data = response.json()
+
+ # Validasi jika respons tidak sesuai format yang diharapkan
+ if not response_data or "data" not in response_data:
+ raise UserError("Respons API tidak sesuai format yang diharapkan.")
+
+ data = response_data.get("data")
+ if not data:
+ continue
+
+ # Menyimpan data ke field masing-masing
+ picking.envio_id = data.get("id")
+ picking.envio_code = data.get("code")
+ picking.envio_ref_code = data.get("ref_code")
+ picking.envio_eta_at = self._convert_to_datetime(data.get("eta_at"))
+ picking.envio_ata_at = self._convert_to_datetime(data.get("ata_at"))
+ picking.envio_etd_at = self._convert_to_datetime(data.get("etd_at"))
+ picking.envio_atd_at = self._convert_to_datetime(data.get("atd_at"))
+ picking.envio_received_by = data.get("received_by")
+ picking.envio_status = data.get("status")
+ picking.envio_cod_value = data.get("cod_value", 0.0)
+ picking.envio_cod_status = data.get("cod_status")
+
+ # Menyimpan log terbaru
+ logs = data.get("logs", [])
+ if logs and isinstance(logs, list) and logs[0]:
+ latest_log = logs[0]
+ picking.envio_latest_message = latest_log.get("message", "Log kosong.")
+ picking.envio_latest_recorded_at = self._convert_to_datetime(latest_log.get("recorded_at"))
+ picking.envio_latest_latitude = latest_log.get("latitude", 0.0)
+ picking.envio_latest_longitude = latest_log.get("longitude", 0.0)
+
+ picking.tracking_by = self.env.user.id
+ ata_at_str = data.get("ata_at")
+ envio_ata = self._convert_to_datetime(data.get("ata_at"))
+
+ picking.driver_arrival_date = envio_ata
+ if data.get("status") != 'delivered':
+ picking.driver_arrival_date = False
+ picking.envio_ata_at = False
+ except requests.exceptions.RequestException as e:
+ raise UserError(f"Terjadi kesalahan saat menghubungi API Envio: {str(e)}")
+ except Exception as e:
+ raise UserError(f"Kesalahan tidak terduga: {str(e)}")
+
def action_send_to_biteship(self):
url = "https://api.biteship.com/v1/orders"
api_key = "biteship_test.eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSW5kb3Rla25payIsInVzZXJJZCI6IjY3MTViYTJkYzVkMjdkMDAxMjRjODk2MiIsImlhdCI6MTcyOTQ5ODAwMX0.L6C73couP4-cgVEfhKI2g7eMCMo3YOFSRZhS-KSuHNA"
@@ -545,13 +761,13 @@ class StockPicking(models.Model):
if not self.env.user.is_logistic_approver and self.env.context.get('active_model') == 'stock.picking':
if self.origin and 'Return of' in self.origin:
raise UserError("Button ini hanya untuk Logistik")
-
+
if self.picking_type_code == 'internal':
self.check_qty_done_stock()
if self._name != 'stock.picking':
return super(StockPicking, self).button_validate()
-
+
if not self.picking_code:
self.picking_code = self.env['ir.sequence'].next_by_code('stock.picking.code') or '0'
@@ -565,15 +781,10 @@ class StockPicking(models.Model):
raise UserError("Harus di Approve oleh Accounting")
if self.picking_type_id.id == 28 and not self.env.user.is_logistic_approver:
- raise UserError("Harus di Approve oleh Logistik")
+ raise UserError("Harus di Approve oleh Logistik")
if self.location_dest_id.id == 47 and not self.env.user.is_purchasing_manager:
- raise UserError("Transfer ke gudang selisih harus di approve Rafly Hanggara")
-
- # if self.group_id.sale_id:
- # if self.group_id.sale_id.payment_link_midtrans:
- # if self.group_id.sale_id.payment_status != 'settlement' and self.group_id.sale_id.state == 'draft':
- # raise UserError('Uang belum masuk (settlement), mohon konfirmasi ke sales atau finance')
+ raise UserError("Transfer ke gudang selisih harus di approve Rafly Hanggara")
if self.is_internal_use:
self.approval_status = 'approved'
@@ -591,7 +802,7 @@ class StockPicking(models.Model):
if not self.date_reserved:
current_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
self.date_reserved = current_time
-
+
self.validation_minus_onhand_quantity()
self.responsible = self.env.user.id
res = super(StockPicking, self).button_validate()
@@ -599,6 +810,59 @@ class StockPicking(models.Model):
self.date_done = datetime.datetime.utcnow()
self.state_reserve = 'done'
return res
+
+
+ def send_mail_bills(self):
+ if self.picking_type_code == 'incoming' and self.purchase_id:
+ template = self.env.ref('indoteknik_custom.mail_template_invoice_po_document')
+ if template and self.purchase_id:
+ # Render email body
+ email_values = template.sudo().generate_email(
+ res_ids=[self.purchase_id.id],
+ fields=['body_html']
+ )
+ rendered_body = email_values.get(self.purchase_id.id, {}).get('body_html', '')
+
+ # Render report dengan XML ID
+ report = self.env.ref('purchase.action_report_purchase_order') # Gunakan XML ID laporan
+ if not report:
+ raise UserError("Laporan dengan XML ID 'purchase.action_report_purchase_order' tidak ditemukan.")
+
+ # Render laporan ke PDF
+ pdf_content, _ = report._render_qweb_pdf([self.purchase_id.id])
+ report_content = base64.b64encode(pdf_content).decode('utf-8')
+
+ # Kirim email menggunakan template
+ email_sent = template.sudo().send_mail(self.purchase_id.id, force_send=True)
+
+ if email_sent:
+ # Buat attachment untuk laporan
+ attachment = self.env['ir.attachment'].create({
+ 'name': self.purchase_id.name or "Laporan Invoice.pdf",
+ 'type': 'binary',
+ 'datas': report_content,
+ 'res_model': 'purchase.order',
+ 'res_id': self.purchase_id.id,
+ 'mimetype': 'application/pdf',
+ })
+
+ # Siapkan data untuk mail.compose.message
+ compose_values = {
+ 'subject': "Pengiriman Email Invoice",
+ 'body': rendered_body,
+ 'attachment_ids': [(4, attachment.id)],
+ 'res_id': self.purchase_id.id,
+ 'model': 'purchase.order',
+ }
+
+ # Buat mail.compose.message
+ compose_message = self.env['mail.compose.message'].create(compose_values)
+
+ # Kirim pesan melalui wizard
+ compose_message.action_send_mail()
+
+ return True
+
def action_cancel(self):
if not self.env.user.is_logistic_approver and self.env.context.get('active_model') == 'stock.picking':
if self.origin and 'Return of' in self.origin:
@@ -742,4 +1006,50 @@ class StockPicking(models.Model):
formatted_fastest_eta = fastest_eta.strftime(format_time_fastest)
formatted_longest_eta = longest_eta.strftime(format_time)
- return f'{formatted_fastest_eta} - {formatted_longest_eta}' \ No newline at end of file
+ return f'{formatted_fastest_eta} - {formatted_longest_eta}'
+
+# class CheckProduct(models.Model):
+# _name = 'check.product'
+# _description = 'Check Product'
+# _order = 'picking_id, id'
+
+# picking_id = fields.Many2one('stock.picking', string='Picking Reference', required=True, ondelete='cascade', index=True, copy=False)
+# product_id = fields.Many2one('product.product', string='Product')
+
+
+# @api.constrains('product_id')
+# def check_product_validity(self):
+# """
+# Validate if the product exists in the related stock.picking's move_ids_without_package
+# and ensure that the product's quantity does not exceed the available product_uom_qty.
+# """
+# for record in self:
+# if not record.picking_id or not record.product_id:
+# continue
+
+# # Filter move lines in the related picking for the selected product
+# moves = record.picking_id.move_ids_without_package.filtered(
+# lambda move: move.product_id.id == record.product_id.id
+# )
+
+# if not moves:
+# raise UserError((
+# "The product '%s' is not available in the related stock picking's moves. "
+# "Please check and try again."
+# ) % record.product_id.display_name)
+
+# # Calculate the total entries for the product in check.product for the same picking
+# product_entries_count = self.search_count([
+# ('picking_id', '=', record.picking_id.id),
+# ('product_id', '=', record.product_id.id)
+# ])
+
+# # Sum the product_uom_qty for all relevant moves
+# total_qty_in_moves = sum(moves.mapped('product_uom_qty'))
+
+# # Compare the count of entries against the available quantity
+# if product_entries_count > total_qty_in_moves:
+# raise UserError((
+# "The product '%s' exceeds the allowable quantity (%s) in the related stock picking's moves. "
+# "You can only add it %s times."
+# ) % (record.product_id.display_name, total_qty_in_moves, total_qty_in_moves))