summaryrefslogtreecommitdiff
path: root/fixco_custom/models/stock_picking.py
diff options
context:
space:
mode:
authorAzka Nathan <darizkyfaz@gmail.com>2025-05-23 14:20:57 +0700
committerAzka Nathan <darizkyfaz@gmail.com>2025-05-23 14:20:57 +0700
commitca3455a3054e2953fb8b274ba5a4e65abcf3d257 (patch)
treec3dca3015cfbb4efaf4de4e2463db8fe8bd12d6e /fixco_custom/models/stock_picking.py
parente38dcc63819f47ce32d52494a6b7277441c6c66a (diff)
Note Development
Diffstat (limited to 'fixco_custom/models/stock_picking.py')
-rw-r--r--fixco_custom/models/stock_picking.py278
1 files changed, 273 insertions, 5 deletions
diff --git a/fixco_custom/models/stock_picking.py b/fixco_custom/models/stock_picking.py
index 4814ac5..9e2a5e6 100644
--- a/fixco_custom/models/stock_picking.py
+++ b/fixco_custom/models/stock_picking.py
@@ -15,18 +15,286 @@ import requests
import time
import logging
import re
+from hashlib import sha256
_logger = logging.getLogger(__name__)
+Request_URI = '/openapi/order/v1/print'
+ACCESS_KEY = '24bb6a1ec618ec6a'
+SECRET_KEY = '32e4a78ad05ee230'
class StockPicking(models.Model):
_inherit = 'stock.picking'
+ check_product_lines = fields.One2many('check.product', 'picking_id', string='Check Product', auto_join=True, copy=False)
- def sync_qty_reserved_qty_done(self):
+ order_reference = fields.Char('Order Reference')
+ provider_name = fields.Char('Provider Name')
+ tracking_number = fields.Char('Tracking Number')
+ invoice_number = fields.Char('Invoice Number')
+ pdf_label_url = fields.Char('PDF Label URL')
+
+ def label_ginee(self):
+ try:
+ order_id = self.order_reference
+
+ authorization = self.sign_request()
+ headers = {
+ 'Content-Type': 'application/json',
+ 'X-Advai-Country': 'ID',
+ 'Authorization': authorization
+ }
+
+ payload = {
+ "orderId": order_id,
+ "documentType": "LABEL"
+ }
+ url = "https://api.ginee.com/openapi/order/v1/print"
+
+ response = requests.post(
+ url,
+ headers=headers,
+ data=json.dumps(payload)
+ )
+
+ if response.status_code == 200:
+ data = response.json()
+ if data.get('code') == 'SUCCESS' and data.get('message') == 'OK':
+ logistic_info_list = data.get('data', {}).get('logisticsInfos')
+
+ # Check if logistic_info exists and has at least one item
+ if not logistic_info_list:
+ raise UserError(_("No logistic information found in response"))
+
+ logistic_info = logistic_info_list[0]
+ self.pdf_label_url = data.get('data', {}).get('pdfUrl') or ''
+ self.tracking_number = logistic_info.get('logisticsTrackingNumber') or ''
+ self.provider_name = logistic_info.get('logisticsProviderName') or ''
+ self.invoice_number = logistic_info.get('invoiceNumber') or ''
+ else:
+ raise UserError(_("API Error: %s - %s") % (data.get('code', 'UNKNOWN'), data.get('message', 'No error message')))
+ else:
+ raise UserError(_("API request failed with status code: %s") % response.status_code)
+
+ except Exception as e:
+ raise UserError(_("Error: %s") % str(e))
+
+ def sign_request(self):
+ signData = '$'.join(['POST', Request_URI]) + '$'
+ authorization = ACCESS_KEY + ':' + base64.b64encode(
+ hmac.new(SECRET_KEY.encode('utf-8'), signData.encode('utf-8'), digestmod=sha256).digest()
+ ).decode('ascii')
+ return authorization
+
+
+
+ # def sync_qty_reserved_qty_done(self):
- for picking in self:
- for line in picking.move_line_ids_without_package:
- line.qty_done = line.product_uom_qty
+ # for picking in self:
+ # for line in picking.move_line_ids_without_package:
+ # line.qty_done = line.product_uom_qty
- picking.button_validate() \ No newline at end of file
+ # picking.button_validate()
+
+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')
+ quantity = fields.Float(string='Quantity')
+ status = fields.Char(string='Status', compute='_compute_status')
+ code_product = fields.Char(string='Code Product')
+
+ @api.onchange('code_product')
+ def _onchange_code_product(self):
+ if not self.code_product:
+ return
+
+ # Cari product berdasarkan default_code, barcode, atau barcode_box
+ product = self.env['product.product'].search([
+ '|',
+ ('default_code', '=', self.code_product),
+ '|',
+ ('barcode', '=', self.code_product),
+ ('barcode_box', '=', self.code_product)
+ ], limit=1)
+
+ if not product:
+ raise UserError("Product tidak ditemukan")
+
+ # Jika scan barcode_box, set quantity sesuai qty_pcs_box
+ if product.barcode_box == self.code_product:
+ self.product_id = product.id
+ self.quantity = product.qty_pcs_box
+ self.code_product = product.default_code or product.barcode
+ # return {
+ # 'warning': {
+ # 'title': 'Info',8994175025871
+
+ # 'message': f'Product box terdeteksi. Quantity di-set ke {product.qty_pcs_box}'
+ # }
+ # }
+ else:
+ # Jika scan biasa
+ self.product_id = product.id
+ self.code_product = product.default_code or product.barcode
+ self.quantity = 1
+
+ def unlink(self):
+ # Get all affected pickings before deletion
+ pickings = self.mapped('picking_id')
+
+ # Store product_ids that will be deleted
+ deleted_product_ids = self.mapped('product_id')
+
+ # Perform the deletion
+ result = super(CheckProduct, self).unlink()
+
+ # After deletion, update moves for affected pickings
+ for picking in pickings:
+ # For products that were completely removed (no remaining check.product lines)
+ remaining_product_ids = picking.check_product_lines.mapped('product_id')
+ removed_product_ids = deleted_product_ids - remaining_product_ids
+
+ # Set quantity_done to 0 for moves of completely removed products
+ moves_to_reset = picking.move_ids_without_package.filtered(
+ lambda move: move.product_id in removed_product_ids
+ )
+ for move in moves_to_reset:
+ move.quantity_done = 0.0
+
+ # Also sync remaining products in case their totals changed
+ self._sync_check_product_to_moves(picking)
+
+ return result
+
+ @api.depends('quantity')
+ def _compute_status(self):
+ for record in self:
+ moves = record.picking_id.move_ids_without_package.filtered(
+ lambda move: move.product_id.id == record.product_id.id
+ )
+ total_qty_in_moves = sum(moves.mapped('product_uom_qty'))
+
+ if record.quantity < total_qty_in_moves:
+ record.status = 'Pending'
+ else:
+ record.status = 'Done'
+
+ def create(self, vals):
+ # Create the record
+ record = super(CheckProduct, self).create(vals)
+ # Ensure uniqueness after creation
+ if not self.env.context.get('skip_consolidate'):
+ record.with_context(skip_consolidate=True)._consolidate_duplicate_lines()
+ return record
+
+ def write(self, vals):
+ # Write changes to the record
+ result = super(CheckProduct, self).write(vals)
+ # Ensure uniqueness after writing
+ if not self.env.context.get('skip_consolidate'):
+ self.with_context(skip_consolidate=True)._consolidate_duplicate_lines()
+ return result
+
+ def _sync_check_product_to_moves(self, picking):
+ """
+ Sinkronisasi quantity_done di move_ids_without_package
+ dengan total quantity dari check.product berdasarkan product_id.
+ """
+ for product_id in picking.check_product_lines.mapped('product_id'):
+ # Totalkan quantity dari semua baris check.product untuk product_id ini
+ total_quantity = sum(
+ line.quantity for line in
+ picking.check_product_lines.filtered(lambda line: line.product_id == product_id)
+ )
+ # Update quantity_done di move yang relevan
+ moves = picking.move_ids_without_package.filtered(lambda move: move.product_id == product_id)
+ for move in moves:
+ move.quantity_done = total_quantity
+
+ def _consolidate_duplicate_lines(self):
+ """
+ Consolidate duplicate lines with the same product_id under the same picking_id
+ and sync the total quantity to related moves.
+ """
+ for picking in self.mapped('picking_id'):
+ lines_to_remove = self.env['check.product'] # Recordset untuk menyimpan baris yang akan dihapus
+ product_lines = picking.check_product_lines.filtered(lambda line: line.product_id)
+
+ # Group lines by product_id
+ product_groups = {}
+ for line in product_lines:
+ product_groups.setdefault(line.product_id.id, []).append(line)
+
+ for product_id, lines in product_groups.items():
+ if len(lines) > 1:
+ # Consolidate duplicate lines
+ first_line = lines[0]
+ total_quantity = sum(line.quantity for line in lines)
+
+ # Update the first line's quantity
+ first_line.with_context(skip_consolidate=True).write({'quantity': total_quantity})
+
+ # Add the remaining lines to the lines_to_remove recordset
+ lines_to_remove |= self.env['check.product'].browse([line.id for line in lines[1:]])
+
+ # Perform unlink after consolidation
+ if lines_to_remove:
+ lines_to_remove.unlink()
+
+ # Sync total quantities to moves
+ self._sync_check_product_to_moves(picking)
+
+ @api.onchange('product_id', 'quantity')
+ def check_product_validity(self):
+ for record in self:
+ if not record.picking_id or not record.product_id:
+ continue
+
+ # Filter moves related to 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' tidak ada di operations. "
+ ) % record.product_id.display_name)
+
+ total_qty_in_moves = sum(moves.mapped('product_uom_qty'))
+
+ # Find existing lines for the same product, excluding the current line
+ existing_lines = record.picking_id.check_product_lines.filtered(
+ lambda line: line.product_id == record.product_id
+ )
+
+ if existing_lines:
+ # Get the first existing line
+ first_line = existing_lines[0]
+
+ # Calculate the total quantity after addition
+ total_quantity = sum(existing_lines.mapped('quantity'))
+
+ if total_quantity > total_qty_in_moves:
+ raise UserError((
+ "Quantity Product '%s' sudah melebihi quantity demand."
+ ) % (record.product_id.display_name))
+ else:
+ # Check if the quantity exceeds the allowed total
+ if record.quantity == total_qty_in_moves:
+ raise UserError((
+ "Quantity Product '%s' sudah melebihi quantity demand."
+ ) % (record.product_id.display_name))
+
+ # Set the quantity to the entered value
+ record.quantity = record.quantity \ No newline at end of file