diff options
| author | Azka Nathan <darizkyfaz@gmail.com> | 2025-12-08 16:14:00 +0700 |
|---|---|---|
| committer | Azka Nathan <darizkyfaz@gmail.com> | 2025-12-08 16:14:00 +0700 |
| commit | 2a77c2a565bc6f8139af830853e1c06625593f44 (patch) | |
| tree | b7d475aaea7fdcd54afbf4e53697061517976509 /fixco_custom/models | |
| parent | 6b255061a752a5d8cc9af65b98f39cf0abe1824d (diff) | |
push
Diffstat (limited to 'fixco_custom/models')
| -rwxr-xr-x | fixco_custom/models/__init__.py | 3 | ||||
| -rwxr-xr-x | fixco_custom/models/detail_order.py | 1 | ||||
| -rw-r--r-- | fixco_custom/models/purchase_order.py | 20 | ||||
| -rwxr-xr-x | fixco_custom/models/sale.py | 1 | ||||
| -rw-r--r-- | fixco_custom/models/shipment_group.py | 7 | ||||
| -rwxr-xr-x | fixco_custom/models/stock_picking.py | 344 | ||||
| -rw-r--r-- | fixco_custom/models/stock_picking_return.py | 27 |
7 files changed, 372 insertions, 31 deletions
diff --git a/fixco_custom/models/__init__.py b/fixco_custom/models/__init__.py index 8316e53..f20c116 100755 --- a/fixco_custom/models/__init__.py +++ b/fixco_custom/models/__init__.py @@ -32,4 +32,5 @@ from . import invoice_reklas from . import uangmuka_pembelian from . import coretax_faktur from . import token_log -from . import purchase_pricelist_wizard
\ No newline at end of file +from . import purchase_pricelist_wizard +from . import stock_picking_return
\ No newline at end of file diff --git a/fixco_custom/models/detail_order.py b/fixco_custom/models/detail_order.py index 1211aad..6bb5429 100755 --- a/fixco_custom/models/detail_order.py +++ b/fixco_custom/models/detail_order.py @@ -167,6 +167,7 @@ class DetailOrder(models.Model): 'picking_policy': 'direct', 'carrier': json_data.get('data', {})[0].get('logisticsInfos')[0].get('logisticsProviderName'), 'invoice_mp': json_data.get('data', {})[0].get('externalOrderId'), + 'channel': json_data.get('data', {})[0].get('channel'), } return data diff --git a/fixco_custom/models/purchase_order.py b/fixco_custom/models/purchase_order.py index 61c4ef8..7646656 100644 --- a/fixco_custom/models/purchase_order.py +++ b/fixco_custom/models/purchase_order.py @@ -222,11 +222,21 @@ class PurchaseOrder(models.Model): # ============================ # Isi Data SOO Ke Order # ============================ - for soo in list_soo: - order.soo_number = soo.get("no_soo") - order.soo_price = soo.get("totalprice") - order.soo_discount = soo.get("diskon") - order.soo_tax = soo.get("ppn") + soo_numbers = [s.get("no_soo") for s in list_soo if s.get("no_soo")] + unique_soo = list(set(soo_numbers)) + if len(unique_soo) == 1: + order.soo_number = unique_soo[0] + elif len(unique_soo) > 1: + order.soo_number = ", ".join(unique_soo) + + else: + order.soo_number = False + + if list_soo: + first_soo = list_soo[0] + order.soo_price = first_soo.get("totalprice") + order.soo_discount = first_soo.get("diskon") + order.soo_tax = first_soo.get("ppn") order.order_altama_id = req_id diff --git a/fixco_custom/models/sale.py b/fixco_custom/models/sale.py index 11340eb..4a1b79e 100755 --- a/fixco_custom/models/sale.py +++ b/fixco_custom/models/sale.py @@ -9,6 +9,7 @@ class SaleOrder(models.Model): invoice_mp = fields.Char(string='Invoice Marketplace', required=True) order_reference = fields.Char(string='Order Reference', required=True) address = fields.Char('Address') + channel = fields.Char('Channel') note_by_buyer = fields.Char('Note By Buyer') count_payment = fields.Integer('Count Payment', compute='_compute_count_payment') diff --git a/fixco_custom/models/shipment_group.py b/fixco_custom/models/shipment_group.py index ca3ef76..546251a 100644 --- a/fixco_custom/models/shipment_group.py +++ b/fixco_custom/models/shipment_group.py @@ -38,6 +38,13 @@ class ShipmentGroup(models.Model): def sync_product_to_picking_line(self): for shipment in self: + cancelled_picking = shipment.picking_lines.filtered( + lambda picking: picking.status == 'CANCELLED' + ) + if cancelled_picking: + cancelled_names = "\n - " + "\n - ".join(cancelled_picking.mapped("name")) + raise UserError(f"Pickings berikut telah dibatalkan:{cancelled_names}") + for picking_line in shipment.picking_lines: shipment_lines = self.env['shipment.group.line'].search([ ('shipment_id', '=', shipment.id), 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, } + diff --git a/fixco_custom/models/stock_picking_return.py b/fixco_custom/models/stock_picking_return.py new file mode 100644 index 0000000..69cc6ab --- /dev/null +++ b/fixco_custom/models/stock_picking_return.py @@ -0,0 +1,27 @@ +from odoo.exceptions import UserError +from odoo.tools.float_utils import float_round +from odoo import models, fields, api, _ +import logging +_logger = logging.getLogger(__name__) + + +class ReturnPicking(models.TransientModel): + _inherit = 'stock.return.picking' + +class ReturnPickingLine(models.TransientModel): + _inherit = 'stock.return.picking.line' + + @api.onchange('quantity') + def _onchange_quantity(self): + for rec in self: + if rec.move_id and rec.quantity > 0: + qty_done = rec.move_id.quantity_done + + if qty_done == 0: + qty_done = rec.move_id.product_uom_qty + + if rec.quantity > qty_done: + raise UserError( + _("Quantity yang Anda masukkan (%.2f) tidak boleh melebihi quantity done yaitu: %.2f untuk produk %s") + % (rec.quantity, qty_done, rec.product_id.name) + ) |
