From 2a77c2a565bc6f8139af830853e1c06625593f44 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Mon, 8 Dec 2025 16:14:00 +0700 Subject: push --- fixco_custom/models/__init__.py | 3 +- fixco_custom/models/detail_order.py | 1 + fixco_custom/models/purchase_order.py | 20 +- fixco_custom/models/sale.py | 1 + fixco_custom/models/shipment_group.py | 7 + fixco_custom/models/stock_picking.py | 344 ++++++++++++++++++++++++++-- fixco_custom/models/stock_picking_return.py | 27 +++ fixco_custom/security/ir.model.access.csv | 4 +- fixco_custom/views/purchase_order.xml | 2 +- fixco_custom/views/report_picking_list.xml | 24 +- fixco_custom/views/sale_order.xml | 19 ++ fixco_custom/views/stock_picking.xml | 51 ++++- fixco_custom/views/webhook_ginee.xml | 1 + 13 files changed, 469 insertions(+), 35 deletions(-) create mode 100644 fixco_custom/models/stock_picking_return.py 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) + ) diff --git a/fixco_custom/security/ir.model.access.csv b/fixco_custom/security/ir.model.access.csv index 4ed6857..f51c8ad 100755 --- a/fixco_custom/security/ir.model.access.csv +++ b/fixco_custom/security/ir.model.access.csv @@ -39,4 +39,6 @@ access_uangmuka_pembelian,access.uangmuka.pembelian,model_uangmuka_pembelian,,1, access_coretax_faktur,access.coretax.faktur,model_coretax_faktur,,1,1,1,1 access_report.fixco_custom.report_picking_list_custom,access.report.fixco_custom.report_picking_list_custom,model_report_fixco_custom_report_picking_list_custom,,1,1,1,1 access_token_log,access.token.log,model_token_log,,1,1,1,1 -access_purchase_pricelist_wizard,access.token.log,model_purchase_pricelist_wizard,,1,1,1,1 +access_purchase_pricelist_wizard,purchase.pricelist.wizard,model_purchase_pricelist_wizard,,1,1,1,1 +access_stock_return_picking,stock.return.picking,model_stock_return_picking,,1,1,1,1 +access_stock_return_picking_line,stock.return.picking.line,model_stock_return_picking_line,,1,1,1,1 diff --git a/fixco_custom/views/purchase_order.xml b/fixco_custom/views/purchase_order.xml index dc863bc..54b28f9 100644 --- a/fixco_custom/views/purchase_order.xml +++ b/fixco_custom/views/purchase_order.xml @@ -31,7 +31,7 @@ icon="fa-cloud-download"/> - + diff --git a/fixco_custom/views/report_picking_list.xml b/fixco_custom/views/report_picking_list.xml index 109a780..bff1fc6 100644 --- a/fixco_custom/views/report_picking_list.xml +++ b/fixco_custom/views/report_picking_list.xml @@ -17,14 +17,36 @@
+ + data:image/jpeg;base64,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 + + +
- QR Code
+ +
+ +
+

Web Service diff --git a/fixco_custom/views/sale_order.xml b/fixco_custom/views/sale_order.xml index 00234d9..5d702e5 100755 --- a/fixco_custom/views/sale_order.xml +++ b/fixco_custom/views/sale_order.xml @@ -36,6 +36,7 @@ + @@ -53,6 +54,24 @@ + + + + + + + + + + Quotations + sale.order + + + + + + + diff --git a/fixco_custom/views/stock_picking.xml b/fixco_custom/views/stock_picking.xml index f8d0934..21ced7c 100755 --- a/fixco_custom/views/stock_picking.xml +++ b/fixco_custom/views/stock_picking.xml @@ -9,6 +9,8 @@ + + @@ -24,6 +26,16 @@ type="object" attrs="{'invisible': [('picking_type_code', '!=', 'outgoing')]}" /> +