summaryrefslogtreecommitdiff
path: root/fixco_custom/models/purchase_order.py
diff options
context:
space:
mode:
authorAzka Nathan <darizkyfaz@gmail.com>2025-11-26 10:08:16 +0700
committerAzka Nathan <darizkyfaz@gmail.com>2025-11-26 10:08:16 +0700
commit25edffb8ebf51e4b133132f4fbd49363b1426664 (patch)
tree903a73bef338d0ddd1bed8577b276b318f37cc56 /fixco_custom/models/purchase_order.py
parent29d10b8de8422a7c2ced1816d7cc7df41c20b73c (diff)
api altama
Diffstat (limited to 'fixco_custom/models/purchase_order.py')
-rw-r--r--fixco_custom/models/purchase_order.py317
1 files changed, 317 insertions, 0 deletions
diff --git a/fixco_custom/models/purchase_order.py b/fixco_custom/models/purchase_order.py
index 8c84215..2393618 100644
--- a/fixco_custom/models/purchase_order.py
+++ b/fixco_custom/models/purchase_order.py
@@ -5,6 +5,8 @@ from datetime import datetime, timedelta
import logging
from pytz import timezone, utc
import io
+import requests
+import json
import base64
try:
from odoo.tools.misc import xlsxwriter
@@ -13,6 +15,7 @@ except ImportError:
_logger = logging.getLogger(__name__)
+
class PurchaseOrder(models.Model):
_inherit = 'purchase.order'
@@ -46,6 +49,320 @@ class PurchaseOrder(models.Model):
('manual', 'Manual')
], string='Source', default='manual', copy=False)
count_journals = fields.Integer('Count Payment', compute='_compute_count_journals')
+ shipping_cost = fields.Float(string='Shipping Cost', help='Total shipping cost for this PO')
+ order_altama_id = fields.Integer('Req Order Altama', copy=False)
+ soo_number = fields.Char('SOO Number', copy=False)
+ soo_price = fields.Float('SOO Price', copy=False)
+ soo_discount = fields.Float('SOO Discount', copy=False)
+ soo_tax = fields.Float('SOO Tax', copy=False)
+
+ def _get_fixco_token(self, source='auto'):
+ ICP = self.env['ir.config_parameter'].sudo()
+ TokenLog = self.env['token.log'].sudo()
+
+ token_url = ICP.get_param('token.adempiere.altama')
+ client_id = ICP.get_param('client.adempiere.altama')
+ client_secret = ICP.get_param('secret_key.adempiere.altama')
+
+ active_token = TokenLog.search([
+ ('is_active', '=', True),
+ ('token_from', '=', 'Adempiere Altama'),
+ ('expires_at', '>', datetime.utcnow()),
+ ], limit=1, order='id desc')
+
+ if active_token:
+ return active_token.token
+
+ headers = {
+ "Authorization": "Basic " + base64.b64encode(f"{client_id}:{client_secret}".encode()).decode(),
+ "Content-Type": "application/x-www-form-urlencoded",
+ }
+ data = {"grant_type": "client_credentials"}
+
+ response = requests.post(token_url, data=data, headers=headers, timeout=15)
+ if response.status_code == 200:
+ result = response.json()
+ token = result.get("access_token")
+ expires_in = result.get("expires_in", 3600)
+ expiry_time = datetime.utcnow() + timedelta(seconds=expires_in - 60)
+
+ TokenLog.search([
+ ('token_from', '=', 'Adempiere Altama'),
+ ('is_active', '=', True),
+ ]).write({'is_active': False})
+
+ TokenLog.create({
+ 'token': token,
+ 'expires_at': expiry_time,
+ 'is_active': True,
+ 'created_by': self.env.user.id if self.env.user else None,
+ 'source': source,
+ 'token_from': 'Adempiere Altama',
+ })
+
+ return token
+
+ else:
+ raise Exception(f"Gagal ambil token: {response.status_code} - {response.text}")
+
+ def action_create_order_altama(self):
+ ICP = self.env['ir.config_parameter'].sudo()
+ for order in self:
+ try:
+ token = self._get_fixco_token(source='manual')
+ url = ICP.get_param('endpoint.create.order.adempiere.altama')
+ headers = {
+ "Authorization": f"Bearer {token}",
+ "Content-Type": "application/json",
+ }
+
+ payload = {
+ "date_po": order.date_approve.strftime("%Y%m%d%H%M%S"),
+ "no_po": order.name,
+ "details": [
+ {
+ "item_code": line.product_id.default_code or "",
+ "price": line.price_unit,
+ "qty": line.product_qty,
+ }
+ for line in order.order_line
+ ],
+ }
+
+ response = requests.post(url, json=payload, headers=headers, timeout=20)
+
+ try:
+ result = response.json()
+ except json.JSONDecodeError:
+ raise Exception(f"Response bukan JSON valid: {response.text}")
+
+ if response.status_code == 200 and result.get("code") == "00":
+ contents = result.get("contents", {})
+ if isinstance(contents, dict):
+ order.order_altama_id = contents.get("req_id")
+ else:
+ order.order_altama_id = contents.get("req_id")
+
+ elif response.status_code == 404:
+ raise Exception("URL endpoint gak ditemukan (404). Pastikan path-nya benar di Altama API.")
+ elif response.status_code == 401:
+ token = self._get_fixco_token(source='auto')
+ headers["Authorization"] = f"Bearer {token}"
+ response = requests.post(url, json=payload, headers=headers, timeout=20)
+ elif response.status_code not in (200, 201):
+ raise Exception(f"Gagal kirim ke Altama: {response.status_code} - {response.text}")
+
+ self.message_post(body=f"✅ PO berhasil dikirim ke Altama!\nResponse: {json.dumps(result, indent=2)}")
+
+ except Exception as e:
+ self.message_post(body=f"❌ Gagal kirim ke Altama:<br/><pre>{str(e)}</pre>")
+
+
+ def action_get_order_altama(self):
+ ICP = self.env['ir.config_parameter'].sudo()
+
+ for order in self:
+ try:
+ # ============================
+ # Get Token
+ # ============================
+ token = self._get_fixco_token(source='manual')
+
+ url = ICP.get_param('endpoint.get.order.adempiere.altama')
+ if not url:
+ raise Exception("Parameter 'endpoint.adempiere.altama' belum diset di System Parameters.")
+
+ headers = {
+ "Authorization": f"Bearer {token}",
+ "Content-Type": "application/json",
+ }
+
+ params = {
+ "orderreq_id": order.order_altama_id or 0,
+ }
+
+ # ============================
+ # Request ke API
+ # ============================
+ response = requests.get(url, headers=headers, params=params, timeout=20)
+
+ if response.status_code == 401:
+ token = self._get_fixco_token(source='auto')
+ headers["Authorization"] = f"Bearer {token}"
+ response = requests.get(url, headers=headers, params=params, timeout=20)
+
+ if response.status_code not in (200, 201):
+ raise Exception(f"Gagal ambil data dari Altama: {response.status_code} - {response.text}")
+
+ data = response.json()
+
+ # ============================
+ # Extract Data
+ # ============================
+ contents_root = data.get("contents", {})
+ contents_list = contents_root.get("contents", [])
+
+ if not isinstance(contents_list, list):
+ raise Exception("Format data contents dari Altama tidak sesuai (expected list).")
+
+ order.message_post(
+ body=f"✅ Berhasil ambil data dari Altama. Ditemukan {len(contents_list)} record."
+ )
+
+ # =====================================================
+ # LOOP MAIN DATA
+ # =====================================================
+ for item in contents_list:
+
+ req_id = item.get("req_id")
+ no_po = item.get("no_po")
+ list_item_po = item.get("list_Item_po", [])
+ list_soo = item.get("list_soo", [])
+
+ # ============================
+ # 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")
+
+ order.order_altama_id = req_id
+
+ # ============================
+ # Update Order Lines
+ # ============================
+ for item_line in list_item_po:
+
+ line = order.order_line.filtered(
+ lambda l: l.product_id.default_code == item_line.get("item_code")
+ )
+
+ if line:
+ line.write({
+ "description": item_line.get("description", ""),
+ "altama_ordered": item_line.get("qtyordered", 0),
+ "altama_delivered": item_line.get("qtydelivered", 0),
+ "altama_invoiced": item_line.get("qtyinvoiced", 0),
+ "docstatus_altama": item_line.get("docstatus", ""),
+ })
+
+ # =====================================================
+ # BUILD HTML TABLES FOR CHATTER
+ # =====================================================
+
+ # ---- SOO TABLE ----
+ soo_rows = ""
+ for s in list_soo:
+ soo_rows += f"""
+ <tr>
+ <td>{s.get('no_soo')}</td>
+ <td>{s.get('totalprice')}</td>
+ <td>{s.get('diskon')}</td>
+ <td>{s.get('ppn')}</td>
+ </tr>
+ """
+
+ soo_table = f"""
+ <table style="width:100%; border-collapse: collapse; margin-top: 10px;">
+ <thead>
+ <tr style="background:#f1f1f1;">
+ <th style="border:1px solid #ccc; padding:6px;">SOO Number</th>
+ <th style="border:1px solid #ccc; padding:6px;">Total Price</th>
+ <th style="border:1px solid #ccc; padding:6px;">Diskon</th>
+ <th style="border:1px solid #ccc; padding:6px;">PPN</th>
+ </tr>
+ </thead>
+ <tbody>
+ {soo_rows or '<tr><td colspan="4">Tidak ada data SOO</td></tr>'}
+ </tbody>
+ </table>
+ """
+
+ # ---- ITEM PO TABLE ----
+ po_rows = ""
+ for l in list_item_po:
+
+ desc = l.get("description") or ""
+
+ # Flag: row error kalau description tidak mulai dengan SOO/
+ is_error = not desc.startswith("SOO/")
+
+ # Style row merah
+ row_style = "color:red; font-weight:bold;" if is_error else ""
+
+ po_rows += f"""
+ <tr style="{row_style}">
+ <td>{l.get('item_code')}</td>
+ <td>{desc}</td>
+ <td>{l.get('qtyordered')}</td>
+ <td>{l.get('qtydelivered')}</td>
+ <td>{l.get('qtyinvoiced')}</td>
+ <td>{l.get('docstatus')}</td>
+ </tr>
+ """
+
+
+ po_table = f"""
+ <table style="width:100%; border-collapse: collapse; margin-top: 10px;">
+ <thead>
+ <tr style="background:#f1f1f1;">
+ <th style="border:1px solid #ccc; padding:6px;">Item Code</th>
+ <th style="border:1px solid #ccc; padding:6px;">Description</th>
+ <th style="border:1px solid #ccc; padding:6px;">Ordered</th>
+ <th style="border:1px solid #ccc; padding:6px;">Delivered</th>
+ <th style="border:1px solid #ccc; padding:6px;">Invoiced</th>
+ <th style="border:1px solid #ccc; padding:6px;">Status</th>
+ </tr>
+ </thead>
+ <tbody>
+ {po_rows or '<tr><td colspan="6">Tidak ada item PO</td></tr>'}
+ </tbody>
+ </table>
+ """
+
+ # ---- POST TO CHATTER ----
+ order.message_post(
+ body=f"""
+ <b>📦 Data SOO</b><br/>{soo_table}
+ <br/><br/>
+ <b>📦 Data Item PO</b><br/>{po_table}
+ """
+ )
+
+ except Exception as e:
+ order.message_post(
+ body=f"❌ Gagal ambil data dari Altama:<br/><pre>{str(e)}</pre>"
+ )
+
+
+ def button_confirm(self):
+ res = super(PurchaseOrder, self).button_confirm()
+ self.action_create_order_altama()
+ return res
+
+ @api.onchange('shipping_cost')
+ def _onchange_shipping_cost(self):
+ for order in self:
+ if not order.order_line:
+ continue
+
+ total_subtotal = sum(order.order_line.mapped('original_price_unit'))
+ if total_subtotal == 0:
+ continue
+
+ if order.shipping_cost and order.shipping_cost > 0:
+ # Distribusi ongkos kirim prorata
+ for line in order.order_line:
+ proportion = (line.original_price_subtotal / total_subtotal)
+ allocated_cost = order.shipping_cost * proportion
+ extra_cost_per_unit = allocated_cost / (line.product_qty or 1)
+ line.price_unit = line.original_price_unit + extra_cost_per_unit
+ else:
+ # Balikin harga ke semula kalau shipping_cost = 0
+ for line in order.order_line:
+ line.price_unit = line.original_price_unit
def _compute_count_journals(self):
for order in self: