from odoo import api, fields, models, _ from odoo.exceptions import UserError import time import requests import json import hmac import base64 from hashlib import sha256 import logging _logger = logging.getLogger(__name__) Request_URI = '/openapi/order/v1/batch-get' ACCESS_KEY = '24bb6a1ec618ec6a' SECRET_KEY = '32e4a78ad05ee230' class DetailOrder(models.Model): _name = "detail.order" _inherit = ['mail.thread'] json_ginee = fields.Text('JSON Ginee') detail_order = fields.Text() execute_status = fields.Selection([ ('from_webhook', 'From Webhook'), ('detail_order', 'Detail Order'), ('so_confirm', 'SO Confirm'), ('so_draft', 'SO Draft'), ('done', 'Done'), ('failed', 'Failed'), ('already_so', 'SO Already Created'), ('processing_get_so', 'Processing Get SO'), ], 'Execute Status') source = fields.Selection([ ('webhook', 'From Webhook'), ('manual', 'Manual'), ], 'source') sale_id = fields.Many2one('sale.order', 'Sale Order') picking_id = fields.Many2one('stock.picking', 'Picking') invoice_id = fields.Many2one('account.move', 'Invoice') message_error = fields.Text('Message Error') is_grouped_order = fields.Boolean('Is Grouped Order', default=False) original_order_ids = fields.Char('Original Order IDs') # get detail order section def get_order_id(self): try: if self.json_ginee: json_data = json.loads(self.json_ginee) order_id = json_data.get('payload', {}).get('orderId') if not order_id: raise UserError(_("Order ID not found in JSON data")) return order_id raise UserError(_("No JSON data available")) except json.JSONDecodeError: raise UserError(_("Invalid JSON format in json_ginee field")) except Exception as e: raise UserError(_("Error extracting order ID: %s") % str(e)) def process_queue_item(self, limit=100): domain = [('execute_status', '=', False)] records = self.search(domain, order='create_date asc', limit=limit) for rec in records: rec.execute_queue() def execute_queue(self): try: order_id = self.get_order_id() authorization = self.sign_request() headers = { 'Content-Type': 'application/json', 'X-Advai-Country': 'ID', 'Authorization': authorization } payload = { "orderIds": [order_id] } # URL endpoint Ginee url = "https://api.ginee.com/openapi/order/v1/batch-get" # Melakukan POST request response = requests.post( url, headers=headers, data=json.dumps(payload) ) # Cek status response if response.status_code == 200: data = response.json() self.detail_order = json.dumps(data, indent=4, sort_keys=True) self.execute_status = 'detail_order' else: self.write({ 'message_error': json.dumps({ 'error': f"Request failed with status code {response.status_code}", 'response': response.text }) }) except Exception as e: self.write({ 'message_error': json.dumps({ 'error': str(e) }) }) # detail order to so section def get_order_id_detail(self): try: if self.detail_order: json_data = json.loads(self.detail_order) order_id = json_data.get('data', {})[0].get('orderId') order_status = json_data.get('data', {})[0].get('orderStatus') print_info = json_data.get('data', {})[0].get('printInfo', {}).get('labelPrintStatus') if not order_id: raise UserError(_("Order ID not found in JSON data")) return order_id, order_status, print_info raise UserError(_("No JSON data available")) except json.JSONDecodeError: raise UserError(_("Invalid JSON format in detail_order field")) except Exception as e: raise UserError(_("Error extracting order ID: %s") % str(e)) def process_queue_item_detail(self, limit=100): domain = [ ('execute_status', '=', 'detail_order'), ('detail_order', 'not like', '"orderStatus": "PENDING_PAYMENT"'), ('json_ginee', 'not like', '"orderStatus": "PENDING_PAYMENT"') ] records = self.search(domain, order='create_date desc', limit=limit) for i, rec in enumerate(records, 1): try: rec.execute_queue_detail() if i % 10 == 0: self.env.cr.commit() except Exception as e: _logger.error("Failed to process record %s: %s", rec.id, str(e)) self.env.cr.rollback() self.env.cr.commit() def get_partner(self, shop_id): partner = self.env['res.partner'].search([('ginee_shop_id', '=', shop_id)], limit=1) if not partner: raise UserError(_("Partner not found for Shop ID: %s") % shop_id) return partner.id def prepare_data_so(self, json_data): data = { 'partner_id': self.get_partner(json_data.get('data', {})[0].get('shopId')), 'client_order_ref': json_data.get('data', {})[0].get('orderId'), 'warehouse_id': 4, 'picking_policy': 'direct', 'carrier': json_data.get('data', {})[0].get('logisticsInfos')[0].get('logisticsProviderName'), 'invoice_mp': json_data.get('data', {})[0].get('externalOrderId'), } return data def _combine_order_items(self, items): """Combine quantities of the same products from multiple orders""" product_quantities = {} for item in items: key = item.get('masterSku') if key in product_quantities: product_quantities[key]['quantity'] += item.get('quantity', 0) product_quantities[key]['actualPrice'] += item.get('actualPrice', 0) else: product_quantities[key] = { 'quantity': item.get('quantity', 0), 'actualPrice': item.get('actualPrice', 0), 'productName': item.get('productName'), 'masterSkuType': item.get('masterSkuType'), 'item_data': item # Keep original item data } return product_quantities def prepare_data_so_line(self, json_data): order_lines = [] product_not_found = False # Get all items (already combined if grouped) items = json_data.get('data', [{}])[0].get('items', []) # Combine quantities of the same products product_quantities = self._combine_order_items(items) # Process the combined items for sku, combined_item in product_quantities.items(): item = combined_item['item_data'] product = self.env['product.product'].search( [('default_code', '=', sku)], limit=1 ) if product and item.get('masterSkuType') == 'BUNDLE': order_lines.append((0, 0, { 'display_type': 'line_note', 'name': f"Bundle: {item.get('productName')}, Qty: {combined_item['quantity']}, Master SKU: {sku}", 'product_uom_qty': 0, 'price_unit': 0, })) bundling_lines = self.env['bundling.line'].search([('product_id', '=', product.id)]) bundling_variant_ids = bundling_lines.mapped('variant_id').ids sale_pricelist = self.env['product.pricelist.item'].search([ ('product_id', 'in', bundling_variant_ids), ('pricelist_id', '=', 17) ]) price_bundling_bottom = sum(item.fixed_price for item in sale_pricelist) for bline in bundling_lines: bottom_price = self.env['product.pricelist.item'].search([ ('product_id', '=', bline.variant_id.id), ('pricelist_id', '=', 17) ], limit=1) price = bottom_price.fixed_price price_unit = self.prorate_price_bundling( bline.variant_id, price_bundling_bottom, price, actual_price=combined_item['actualPrice'] ) order_lines.append((0, 0, { 'product_id': bline.variant_id.id if bline.variant_id else product.id, 'product_uom_qty': bline.product_uom_qty * combined_item['quantity'], 'price_unit': price_unit, 'name': f"{bline.variant_id.display_name} (Bundle Component)" if bline.variant_id.display_name else product.name, })) order_lines.append((0, 0, { 'display_type': 'line_note', 'name': f"End Of Bundling Product", 'product_uom_qty': 0, 'price_unit': 0, })) continue # Regular product line line_data = { 'product_id': product.id if product else 5792, 'product_uom_qty': combined_item['quantity'], 'price_unit': combined_item['actualPrice'], } if not product: line_data['name'] = f"{sku} ({combined_item['productName']})" product_not_found = True order_lines.append((0, 0, line_data)) return order_lines, product_not_found def execute_queue_detail(self): try: json_data = json.loads(self.detail_order) data = self.prepare_data_so(json_data) order_lines, product_not_found = self.prepare_data_so_line(json_data) order_id, order_status, print_info = self.get_order_id_detail() # First check if a sale order with this reference already exists existing_order = self.env['sale.order'].search([('order_reference', '=', order_id)], limit=1) if existing_order: # If order already exists, just update the references self.sale_id = existing_order.id self.execute_status = 'already_so' return # Exit early since we don't need to create anything # Only proceed with creation if order doesn't exist and status is appropriate if order_status != 'PENDING_PAYMENT': if order_status in ('PARTIALLY_PAID', 'PAID'): data['order_line'] = order_lines sale_order = self.env['sale.order'].create(data) self.sale_id = sale_order.id sale_order.order_reference = order_id sale_order.address = json_data.get('data', [{}])[0].get('shippingAddressInfo', []).get('fullAddress', []) sale_order.note_by_buyer = json_data.get('data', [{}])[0].get('extraInfo', []).get('noteByBuyer', []) if not product_not_found: sale_order.action_confirm() # self.picking_id = sale_order.picking_ids[0].id # self.picking_id.order_reference = order_id # self.picking_id.invoice_mp = sale_order.invoice_mp # self.picking_id.carrier = sale_order.carrier # self.picking_id.address = json_data.get('data', [{}])[0].get('shippingAddressInfo', []).get('fullAddress', []) # self.picking_id.note_by_buyer = json_data.get('data', [{}])[0].get('extraInfo', []).get('noteByBuyer', []) self.execute_status = 'so_confirm' else: self.execute_status = 'so_draft' else: # For other statuses, create new order only if it doesn't exist data['order_line'] = order_lines sale_order = self.env['sale.order'].create(data) self.sale_id = sale_order.id sale_order.order_reference = order_id sale_order.address = json_data.get('data', [{}])[0].get('shippingAddressInfo', []).get('fullAddress', []) sale_order.note_by_buyer = json_data.get('data', [{}])[0].get('extraInfo', []).get('noteByBuyer', []) if not product_not_found: sale_order.action_confirm() # self.picking_id = sale_order.picking_ids[0].id # self.picking_id.order_reference = order_id # self.picking_id.invoice_mp = sale_order.invoice_mp # self.picking_id.carrier = sale_order.carrier # self.picking_id.address = json_data.get('data', [{}])[0].get('shippingAddressInfo', []).get('fullAddress', []) # self.picking_id.note_by_buyer = json_data.get('data', [{}])[0].get('extraInfo', []).get('noteByBuyer', []) self.execute_status = 'so_confirm' else: self.execute_status = 'so_draft' except Exception as e: self.write({ 'message_error': json.dumps({ 'error': 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 prorate_price_bundling(self, product, sum_bottom, price_bottom,actual_price): percent = price_bottom / sum_bottom real_price = percent * actual_price return real_price # check print do section # def get_order_id_check_print(self): # try: # if self.detail_order: # json_data = json.loads(self.detail_order) # order_id = json_data.get('data', {})[0].get('orderId') # if not order_id: # raise UserError(_("Order ID not found in JSON data")) # return order_id # raise UserError(_("No JSON data available")) # except json.JSONDecodeError: # raise UserError(_("Invalid JSON format in check_print field")) # except Exception as e: # raise UserError(_("Error extracting order ID: %s") % str(e)) # def process_queue_item_check_print(self, limit=100): # domain = [('picking_id.state', 'not in', ['done', 'cancel'])] # records = self.search(domain, order='create_date asc', limit=limit) # for rec in records: # rec.execute_queue_check_print() # def execute_queue_check_print(self): # try: # order_id = self.get_order_id_check_print() # authorization = self.sign_request() # headers = { # 'Content-Type': 'application/json', # 'X-Advai-Country': 'ID', # 'Authorization': authorization # } # payload = { # "orderIds": [order_id] # } # url = "https://api.ginee.com/openapi/order/v1/batch-get" # response = requests.post( # url, # headers=headers, # data=json.dumps(payload) # ) # if response.status_code == 200: # data = response.json() # self.detail_order = json.dumps(data) # detail_order = json.loads(self.detail_order) # if detail_order['data'][0]['printInfo']['labelPrintStatus'] == 'PRINTED': #ubah ke printed lagi nanti # self.picking_id.sync_qty_reserved_qty_done() # # self.sale_id.api_create_invoices(self.sale_id.id) # self.execute_status = 'done' # else: # self.write({ # 'execute_status': 'failed', # 'json_ginee': json.dumps({ # 'error': f"Request failed with status code {response.status_code}", # 'response': response.text # }) # }) # except Exception as e: # self.write({ # 'execute_status': 'failed', # 'json_ginee': json.dumps({ # 'error': str(e) # }) # })