summaryrefslogtreecommitdiff
path: root/fixco_custom/models/stock_picking.py
diff options
context:
space:
mode:
authorAzka Nathan <darizkyfaz@gmail.com>2025-12-08 16:14:00 +0700
committerAzka Nathan <darizkyfaz@gmail.com>2025-12-08 16:14:00 +0700
commit2a77c2a565bc6f8139af830853e1c06625593f44 (patch)
treeb7d475aaea7fdcd54afbf4e53697061517976509 /fixco_custom/models/stock_picking.py
parent6b255061a752a5d8cc9af65b98f39cf0abe1824d (diff)
push
Diffstat (limited to 'fixco_custom/models/stock_picking.py')
-rwxr-xr-xfixco_custom/models/stock_picking.py344
1 files changed, 319 insertions, 25 deletions
diff --git a/fixco_custom/models/stock_picking.py b/fixco_custom/models/stock_picking.py
index 916ac9f..7c9f0d9 100755
--- a/fixco_custom/models/stock_picking.py
+++ b/fixco_custom/models/stock_picking.py
@@ -19,7 +19,7 @@ from hashlib import sha256
_logger = logging.getLogger(__name__)
-Request_URI = '/openapi/order/v1/print'
+Request_URI = ['/openapi/order/v1/print', '/openapi/logistics/v1/get-shipping-parameter', '/openapi/order/v3/batchShipping']
ACCESS_KEY = '24bb6a1ec618ec6a'
SECRET_KEY = '32e4a78ad05ee230'
@@ -29,32 +29,48 @@ class StockPicking(models.Model):
check_product_lines = fields.One2many('check.product', 'picking_id', string='Check Product', auto_join=True, copy=False)
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')
- invoice_mp = fields.Char(string='Invoice Marketplace')
- address = fields.Char('Address')
- note_by_buyer = fields.Char('Note By Buyer')
- carrier = fields.Char(string='Shipping Method')
+ provider_name = fields.Char('Provider Name', tracking=True)
+ tracking_number = fields.Char('Tracking Number', tracking=True)
+ invoice_number = fields.Char('Invoice Number', tracking=True)
+ pdf_label_url = fields.Char('PDF Label URL', tracking=True)
+ invoice_mp = fields.Char(string='Invoice Marketplace', tracking=True)
+ address = fields.Char('Address', tracking=True)
+ note_by_buyer = fields.Char('Note By Buyer', tracking=True)
+ carrier = fields.Char(string='Shipping Method', tracking=True)
shipment_group_id = fields.Many2one('shipment.group', string='Shipment Group', copy=False)
pdf_label_preview = fields.Binary(
string="PDF Preview",
compute="_compute_pdf_binary",
- store=False
+ store=False, tracking=True
)
- is_printed = fields.Boolean(string="Sudah Dicetak", default=False)
+ is_printed = fields.Boolean(string="Sudah Dicetak", default=False, tracking=True)
is_return = fields.Boolean(
string="Is Return",
compute="_compute_is_return",
- store=True
+ store=True, tracking=True
)
+ channel = fields.Char('Channel')
+ ginee_delivery_type = fields.Char("Delivery Type", tracking=True)
+ ginee_tracking_no = fields.Char("Tracking Number", tracking=True)
+ ginee_invoice_no = fields.Char("Invoice Number", tracking=True)
+ ginee_shipping_task_id = fields.Char("Shipping Task ID", tracking=True)
+
+ ginee_provider_id = fields.Char("Provider ID", tracking=True)
+ ginee_provider_name = fields.Char("Provider Name", tracking=True)
+ ginee_address_id = fields.Char("Pickup Address ID", tracking=True)
+ ginee_address = fields.Char("Pickup Address", tracking=True)
+ ginee_pickup_time_id = fields.Char("Pickup Time ID", tracking=True)
+ ginee_task_id = fields.Char("Ginee Task ID", tracking=True)
+
+ soo_number = fields.Char(string='SOO Altama Number', related='purchase_id.soo_number')
+ type_sku = fields.Selection([('single', 'Single SKU'), ('multi', 'Multi SKU')], string='Type SKU')
+ list_product = fields.Char(string='List Product')
def button_validate(self):
- if len(self.check_product_lines) == 0:
- raise UserError(_(
- "Belum ada check product, gabisa validate"
- ))
+ origin = self.origin or ''
+ if any(prefix in origin for prefix in ['PO/', 'SO/']) and not self.check_product_lines:
+ raise UserError(_("Belum ada check product, gabisa validate"))
+
return super(StockPicking, self).button_validate()
@@ -203,9 +219,23 @@ class StockPicking(models.Model):
picking.order_reference = picking.sale_id.name
self.order_reference = picking.sale_id.order_reference
self.invoice_mp = picking.sale_id.invoice_mp
+ self.channel = picking.sale_id.channel
self.carrier = picking.sale_id.carrier
self.address = picking.sale_id.address
self.note_by_buyer = picking.sale_id.note_by_buyer
+ self.schema_multi_single_sku()
+
+ def schema_multi_single_sku(self):
+ for picking in self:
+ if len(picking.move_ids_without_package) > 1:
+ picking.type_sku = 'multi'
+ else:
+ picking.type_sku = 'single'
+
+ names = picking.move_ids_without_package.mapped('product_id.display_name')
+ picking.list_product = ", ".join(names)
+
+
def open_form_shipment_group(self):
return {
@@ -235,7 +265,7 @@ class StockPicking(models.Model):
try:
order_id = self.order_reference
- authorization = self.sign_request()
+ authorization = self.sign_request(0)
headers = {
'Content-Type': 'application/json',
'X-Advai-Country': 'ID',
@@ -275,14 +305,281 @@ class StockPicking(models.Model):
except Exception as e:
raise UserError(_("Error: %s") % str(e))
+
+ def get_shipping_parameter(self):
+ try:
+ order_id = self.order_reference
+
+ authorization = self.sign_request(1)
+ headers = {
+ 'Content-Type': 'application/json',
+ 'X-Advai-Country': 'ID',
+ 'Authorization': authorization
+ }
+
+ payload = {"orderId": order_id}
+ url = "https://api.ginee.com/openapi/logistics/v1/get-shipping-parameter"
+
+ response = requests.post(url, headers=headers, data=json.dumps(payload))
+ res = response.json()
+
+ if res.get("code") != "SUCCESS":
+ raise UserError("Ginee Error: %s" % res.get("message"))
+
+ data = res.get("data", {})
+
+ # ==============================
+ # Basic fields
+ # ==============================
+ self.ginee_delivery_type = data.get("deliveryType")
+ self.ginee_tracking_no = data.get("logisticsTrackingNumber")
+ self.ginee_invoice_no = data.get("invoiceNumber")
+
+ # ==============================
+ # FIND CORRECT PROVIDER
+ # ==============================
+ provider_id = None
+ provider_name = None
+ logistics_type = None
+
+ for lg in data.get("logistics", []):
+ details = lg.get("logisticDetailList") or []
+ if details:
+ # ambil pertama yang terisi
+ d = details[0]
+ provider_id = d.get("logisticsProviderId")
+ provider_name = d.get("logisticsProviderName")
+ logistics_type = lg.get("logisticsDeliveryType")
+ break # STOP di yang pertama terisi
+
+ # Simpan hasil ke field Odoo
+ self.ginee_provider_id = provider_id
+ self.ginee_provider_name = provider_name
+ self.ginee_delivery_type = logistics_type or self.ginee_delivery_type
+
+ # ==============================
+ # PICKUP ADDRESS (kalau ada)
+ # ==============================
+ addr = None
+ pickup_time = None
+
+ addresses = data.get("addresses") or []
+ if addresses:
+ # ambil address pertama paling relevan
+ first = addresses[0]
+ addr = first.get("address")
+ self.ginee_address = addr
+ self.ginee_address_id = first.get("addressId")
+
+ times = first.get("pickupTimeList") or []
+ if times:
+ pt = times[0]
+ self.ginee_pickup_time_id = pt.get("pickupTimeId")
+
+ except Exception as e:
+ raise UserError(_("Error: %s") % str(e))
+
- def sign_request(self):
- signData = '$'.join(['POST', Request_URI]) + '$'
+ def ship_order(self):
+ try:
+ order_id = self.order_reference
+
+ authorization = self.sign_request(2) # index 2 -> ship-order
+ headers = {
+ 'Content-Type': 'application/json',
+ 'X-Advai-Country': 'ID',
+ 'Authorization': authorization
+ }
+
+ # ==========================================================
+ # Ambil field dari GET SHIPPING PARAMETER
+ # ==========================================================
+ delivery_type = self.ginee_delivery_type
+ provider_name = self.ginee_provider_name
+ provider_id = self.ginee_provider_id
+ tracking_no = self.ginee_tracking_no
+ invoice_no = self.ginee_invoice_no
+ address_id = self.ginee_address_id
+ address = self.ginee_address
+ pickup_time_id = self.ginee_pickup_time_id
+
+ # ==========================================================
+ # Ambil platform dari channel (SHOPEE, LAZADA, dll)
+ # ==========================================================
+ platform = self._get_platform_from_channel() or ""
+
+ # ==========================================================
+ # Build order-level payload
+ # ==========================================================
+ order_data = {
+ "orderId": order_id,
+ "deliveryType": delivery_type
+ }
+
+ # ==========================================================
+ # PLATFORM RULES
+ # ==========================================================
+
+ # ----------------------------- SHOPEE -----------------------------
+ if platform == "SHOPEE":
+ if delivery_type == "PICK_UP":
+ order_data.update({
+ "shippingProvider": provider_name,
+ "pickupTimeId": pickup_time_id,
+ "addressId": address_id,
+ "address": address,
+ })
+ elif delivery_type == "DROP_OFF":
+ order_data.update({"shippingProvider": provider_name})
+ elif delivery_type == "MANUAL_SHIP":
+ order_data.update({
+ "shippingProvider": provider_name,
+ "trackingNo": tracking_no,
+ })
+
+ # ----------------------------- TOKOPEDIA -----------------------------
+ elif platform == "TOKOPEDIA":
+ if delivery_type == "PICK_UP":
+ order_data.update({
+ "shippingProvider": provider_name,
+ "trackingNo": tracking_no,
+ })
+ elif delivery_type == "DROP_OFF":
+ order_data.update({"shippingProvider": provider_name})
+ elif delivery_type == "MANUAL_SHIP":
+ order_data.update({
+ "shippingProvider": provider_name,
+ "trackingNo": tracking_no
+ })
+
+ # ----------------------------- LAZADA -----------------------------
+ elif platform == "LAZADA":
+ if delivery_type == "PICK_UP":
+ order_data.update({
+ "shippingProvider": provider_name,
+ "address": address,
+ "addressId": address_id,
+ "pickupTimeId": pickup_time_id
+ })
+ elif delivery_type == "DROP_OFF":
+ order_data.update({
+ "shippingProvider": provider_name,
+ "invoiceNumber": invoice_no
+ })
+
+ # ----------------------------- TIKTOK -----------------------------
+ elif platform == "TIKTOK":
+ if delivery_type == "PICK_UP":
+ order_data.update({
+ "shippingProvider": provider_name,
+ "pickupStartTime": pickup_time_id,
+ "pickupEndTime": pickup_time_id
+ })
+ elif delivery_type == "DROP_OFF":
+ order_data.update({"shippingProvider": provider_name})
+
+ # ----------------------------- ZALORA -----------------------------
+ elif platform == "ZALORA":
+ if delivery_type == "DROP_OFF":
+ order_data.update({
+ "trackingNo": tracking_no,
+ "invoiceNumber": invoice_no
+ })
+
+ # ----------------------------- AKULAKU -----------------------------
+ elif platform == "AKULAKU":
+ if delivery_type in ["PICK_UP", "DROP_OFF"]:
+ order_data.update({
+ "shippingProvider": provider_name,
+ "shippingProviderId": provider_id,
+ "addressId": address_id,
+ "address": address
+ })
+ elif delivery_type == "MANUAL_SHIP":
+ order_data.update({
+ "shippingProvider": provider_name,
+ "shippingProviderId": provider_id,
+ "trackingNo": tracking_no
+ })
+
+ # ----------------------------- BUKALAPAK -----------------------------
+ elif platform == "BUKALAPAK":
+ if delivery_type in ["PICK_UP", "DROP_OFF"]:
+ order_data.update({"shippingProvider": provider_name})
+ elif delivery_type == "MANUAL_SHIP":
+ order_data.update({
+ "shippingProvider": provider_name,
+ "trackingNo": tracking_no
+ })
+
+ # ----------------------------- BLIBLI -----------------------------
+ elif platform == "BLIBLI":
+ if delivery_type == "PICK_UP":
+ order_data.update({"shippingProvider": provider_name})
+ elif delivery_type == "DROP_OFF":
+ order_data.update({
+ "shippingProvider": provider_name,
+ "trackingNo": tracking_no
+ })
+
+ # ----------------------------- DEFAULT FALLBACK -----------------------------
+ else:
+ if delivery_type == "PICK_UP":
+ order_data.update({
+ "shippingProvider": provider_name,
+ "pickupTimeId": pickup_time_id,
+ "addressId": address_id,
+ "address": address,
+ })
+ elif delivery_type == "DROP_OFF":
+ order_data.update({"shippingProvider": provider_name})
+ elif delivery_type == "MANUAL_SHIP":
+ order_data.update({
+ "shippingProvider": provider_name,
+ "trackingNo": tracking_no,
+ })
+
+ # ==========================================================
+ # FINAL PAYLOAD (dibungkus orderShips)
+ # ==========================================================
+ payload = {
+ "orderShips": [order_data]
+ }
+
+ # ==========================================================
+ # CALL API
+ # ==========================================================
+ url = "https://api.ginee.com/openapi/order/v3/batchShipping"
+
+ response = requests.post(url, headers=headers, data=json.dumps(payload))
+ res = response.json()
+
+ if res.get("code") != "SUCCESS":
+ raise UserError("Ship Order Error: %s" % res.get("message"))
+
+ self.ginee_task_id = res.get("data") or False
+
+ return True
+
+ except Exception as e:
+ raise UserError(_("Error: %s") % str(e))
+
+ def sign_request(self, array_num):
+ signData = '$'.join(['POST', Request_URI[array_num]]) + '$'
authorization = ACCESS_KEY + ':' + base64.b64encode(
hmac.new(SECRET_KEY.encode('utf-8'), signData.encode('utf-8'), digestmod=sha256).digest()
).decode('ascii')
return authorization
+ def _get_platform_from_channel(self):
+ if not self.channel:
+ return None
+ ch = (self.channel or "").upper().strip()
+ # remove suffix _ID / _CHOICE / etc
+ ch = ch.replace("_ID", "").replace("_CHOICE", "")
+ return ch
+
+
# def sync_qty_reserved_qty_done(self):
@@ -502,13 +799,8 @@ class PickingReportCustom(models.AbstractModel):
def _get_report_values(self, docids, data=None):
pickings = self.env['stock.picking'].browse(docids)
- already_printed = pickings.filtered(lambda p: p.is_printed)
- if already_printed:
- names = "\n- ".join(already_printed.mapped("name"))
- raise UserError(
- "Dokumen berikut sudah pernah di-print sebelumnya:\n\n- %s" % names
- )
+ was_printed_map = {p.id: p.is_printed for p in pickings}
pickings.write({'is_printed': True})
@@ -516,4 +808,6 @@ class PickingReportCustom(models.AbstractModel):
'doc_ids': docids,
'doc_model': 'stock.picking',
'docs': pickings,
+ 'was_printed_map': was_printed_map,
}
+