diff options
| author | Azka Nathan <darizkyfaz@gmail.com> | 2025-05-23 14:20:57 +0700 |
|---|---|---|
| committer | Azka Nathan <darizkyfaz@gmail.com> | 2025-05-23 14:20:57 +0700 |
| commit | ca3455a3054e2953fb8b274ba5a4e65abcf3d257 (patch) | |
| tree | c3dca3015cfbb4efaf4de4e2463db8fe8bd12d6e /fixco_custom/models/stock_picking.py | |
| parent | e38dcc63819f47ce32d52494a6b7277441c6c66a (diff) | |
Note Development
Diffstat (limited to 'fixco_custom/models/stock_picking.py')
| -rw-r--r-- | fixco_custom/models/stock_picking.py | 278 |
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 |
