summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorit-fixcomart <it@fixcomart.co.id>2025-01-13 17:26:30 +0700
committerit-fixcomart <it@fixcomart.co.id>2025-01-13 17:26:30 +0700
commit1c9308c1a18bd89612dc7fca5726cbf96c28deed (patch)
treeacc36cfa86d66b4e230aa5cf6830e2c6724d77a9
parent9f994de3f13f6be24d17233bf6890e6e88dd959b (diff)
parent46e968a3b28c00aa74e6f09b451d5a87e8523043 (diff)
Merge branch 'odoo-production' into iman/pengajuan-tempo
# Conflicts: # indoteknik_custom/security/ir.model.access.csv
-rwxr-xr-xindoteknik_custom/__manifest__.py3
-rwxr-xr-xindoteknik_custom/models/__init__.py2
-rw-r--r--indoteknik_custom/models/account_move.py32
-rw-r--r--indoteknik_custom/models/coretax_fatur.py119
-rwxr-xr-xindoteknik_custom/models/product_template.py2
-rwxr-xr-xindoteknik_custom/models/purchase_order.py44
-rwxr-xr-xindoteknik_custom/models/sale_order.py19
-rw-r--r--indoteknik_custom/models/sale_order_line.py24
-rw-r--r--indoteknik_custom/models/stock_immediate_transfer.py36
-rw-r--r--indoteknik_custom/models/stock_picking.py205
-rw-r--r--indoteknik_custom/models/user_pengajuan_tempo_request.py14
-rwxr-xr-xindoteknik_custom/security/ir.model.access.csv2
-rw-r--r--indoteknik_custom/views/account_move.xml8
-rw-r--r--indoteknik_custom/views/coretax_faktur.xml49
-rwxr-xr-xindoteknik_custom/views/sale_order.xml8
-rw-r--r--indoteknik_custom/views/stock_picking.xml25
-rw-r--r--indoteknik_custom/views/user_pengajuan_tempo.xml8
-rw-r--r--indoteknik_custom/views/user_pengajuan_tempo_request.xml4
18 files changed, 564 insertions, 40 deletions
diff --git a/indoteknik_custom/__manifest__.py b/indoteknik_custom/__manifest__.py
index 07af1802..d44f85db 100755
--- a/indoteknik_custom/__manifest__.py
+++ b/indoteknik_custom/__manifest__.py
@@ -8,7 +8,7 @@
'author': 'Rafi Zadanly',
'website': '',
'images': ['assets/favicon.ico'],
- 'depends': ['base', 'coupon', 'delivery', 'sale', 'sale_management', 'vit_kelurahan'],
+ 'depends': ['base', 'coupon', 'delivery', 'sale', 'sale_management', 'vit_kelurahan', 'vit_efaktur' ],
'data': [
'security/ir.model.access.csv',
'views/group_partner.xml',
@@ -157,6 +157,7 @@
'report/report_invoice.xml',
'report/report_picking.xml',
'report/report_sale_order.xml',
+ 'views/coretax_faktur.xml',
],
'demo': [],
'css': [],
diff --git a/indoteknik_custom/models/__init__.py b/indoteknik_custom/models/__init__.py
index c0b32e18..3990e81c 100755
--- a/indoteknik_custom/models/__init__.py
+++ b/indoteknik_custom/models/__init__.py
@@ -137,3 +137,5 @@ from . import user_pengajuan_tempo
from . import approval_retur_picking
from . import va_multi_approve
from . import va_multi_reject
+from . import stock_immediate_transfer
+from . import coretax_fatur
diff --git a/indoteknik_custom/models/account_move.py b/indoteknik_custom/models/account_move.py
index 725b3c2d..42678847 100644
--- a/indoteknik_custom/models/account_move.py
+++ b/indoteknik_custom/models/account_move.py
@@ -325,3 +325,35 @@ class AccountMove(models.Model):
# if rec.statement_line_id and not rec.statement_line_id.statement_id.is_edit and rec.statement_line_id.statement_id.state == 'confirm':
# raise UserError('Bank Statement di Lock, Minta admin reconcile untuk unlock')
# return res
+
+ def validate_faktur_for_export(self):
+ invoices = self.filtered(lambda inv: not inv.is_efaktur_exported and
+ inv.state == 'posted' and
+ inv.move_type == 'out_invoice')
+
+ invalid_invoices = self - invoices
+ if invalid_invoices:
+ invalid_ids = ", ".join(str(inv.id) for inv in invalid_invoices)
+ raise UserError(_(
+ "Faktur dengan ID berikut tidak valid untuk diekspor: {}.\n"
+ "Pastikan faktur dalam status 'posted', belum diekspor, dan merupakan 'out_invoice'.".format(invalid_ids)
+ ))
+
+ return invoices
+
+ def export_faktur_to_xml(self):
+
+ valid_invoices = self.validate_faktur_for_export()
+
+ # Panggil model coretax.faktur untuk menghasilkan XML
+ coretax_faktur = self.env['coretax.faktur'].create({})
+ response = coretax_faktur.export_to_download(invoices=valid_invoices)
+
+ current_time = datetime.utcnow()
+ # Tandai faktur sebagai sudah diekspor
+ valid_invoices.write({
+ 'is_efaktur_exported': True,
+ 'date_efaktur_exported': current_time, # Set tanggal ekspor
+ })
+
+ return response
diff --git a/indoteknik_custom/models/coretax_fatur.py b/indoteknik_custom/models/coretax_fatur.py
new file mode 100644
index 00000000..ff8606b1
--- /dev/null
+++ b/indoteknik_custom/models/coretax_fatur.py
@@ -0,0 +1,119 @@
+from odoo import models, fields
+import xml.etree.ElementTree as ET
+from xml.dom import minidom
+import base64
+import re
+
+class CoretaxFaktur(models.Model):
+ _name = 'coretax.faktur'
+ _description = 'Export Faktur ke XML'
+
+ export_file = fields.Binary(string="Export File", )
+ export_filename = fields.Char(string="Export File", )
+
+ def validate_and_format_number(slef, input_number):
+ # Hapus semua karakter non-digit
+ cleaned_number = re.sub(r'\D', '', input_number)
+
+ total_sum = sum(int(char) for char in cleaned_number)
+ if total_sum == 0 :
+ return '0000000000000000'
+
+ # Hitung jumlah digit
+ digit_count = len(cleaned_number)
+
+ # Jika jumlah digit kurang dari 15, tambahkan nol di depan
+ if digit_count < 16:
+ cleaned_number = cleaned_number.zfill(16)
+
+ return cleaned_number
+
+ def generate_xml(self, invoices=None):
+ # Buat root XML
+ root = ET.Element('TaxInvoiceBulk', {
+ 'xmlns:xsi': "http://www.w3.org/2001/XMLSchema-instance",
+ 'xsi:noNamespaceSchemaLocation': "TaxInvoice.xsd"
+ })
+ ET.SubElement(root, 'TIN').text = '0742260227086000'
+
+ # Tambahkan elemen ListOfTaxInvoice
+ list_of_tax_invoice = ET.SubElement(root, 'ListOfTaxInvoice')
+
+ # Dapatkan data faktur
+ # inv_obj = self.env['account.move']
+ # invoices = inv_obj.search([('is_efaktur_exported','=',True),
+ # ('state','=','posted'),
+ # ('efaktur_id','!=', False),
+ # ('move_type','=','out_invoice')], limit = 5)
+
+ for invoice in invoices:
+ tax_invoice = ET.SubElement(list_of_tax_invoice, 'TaxInvoice')
+ buyerTIN = self.validate_and_format_number(invoice.partner_id.npwp)
+ buyerIDTKU = buyerTIN.ljust(len(buyerTIN) + 6, '0') if sum(int(char) for char in buyerTIN) > 0 else '000000'
+
+ # Tambahkan elemen faktur
+ ET.SubElement(tax_invoice, 'TaxInvoiceDate').text = invoice.invoice_date.strftime('%Y-%m-%d') if invoice.invoice_date else ''
+ ET.SubElement(tax_invoice, 'TaxInvoiceOpt').text = 'Normal'
+ ET.SubElement(tax_invoice, 'TrxCode').text = '04'
+ ET.SubElement(tax_invoice, 'AddInfo')
+ ET.SubElement(tax_invoice, 'CustomDoc')
+ ET.SubElement(tax_invoice, 'RefDesc').text = invoice.name
+ ET.SubElement(tax_invoice, 'FacilityStamp')
+ ET.SubElement(tax_invoice, 'SellerIDTKU').text = '0742260227086000000000'
+ ET.SubElement(tax_invoice, 'BuyerTin').text = buyerTIN
+ ET.SubElement(tax_invoice, 'BuyerDocument').text = 'TIN' if sum(int(char) for char in buyerTIN) > 0 else 'Other ID'
+ ET.SubElement(tax_invoice, 'BuyerCountry').text = 'IDN'
+ ET.SubElement(tax_invoice, 'BuyerDocumentNumber').text = str(invoice.partner_id.id)
+ ET.SubElement(tax_invoice, 'BuyerName').text = invoice.partner_id.nama_wajib_pajak or ''
+ ET.SubElement(tax_invoice, 'BuyerAdress').text = invoice.partner_id.alamat_lengkap_text or ''
+ ET.SubElement(tax_invoice, 'BuyerEmail').text = invoice.partner_id.email or ''
+ ET.SubElement(tax_invoice, 'BuyerIDTKU').text = buyerIDTKU
+
+ # Tambahkan elemen ListOfGoodService
+ list_of_good_service = ET.SubElement(tax_invoice, 'ListOfGoodService')
+ for line in invoice.invoice_line_ids:
+ otherTaxBase = round(line.price_subtotal * (11/12)) if line.price_subtotal else 0
+ good_service = ET.SubElement(list_of_good_service, 'GoodService')
+ ET.SubElement(good_service, 'Opt').text = 'A'
+ ET.SubElement(good_service, 'Code')
+ ET.SubElement(good_service, 'Name').text = line.name
+ ET.SubElement(good_service, 'Unit').text = 'UM.0018'
+ ET.SubElement(good_service, 'Price').text = str(round(line.price_subtotal/line.quantity, 2)) if line.price_subtotal else '0'
+ ET.SubElement(good_service, 'Qty').text = str(line.quantity)
+ ET.SubElement(good_service, 'TotalDiscount').text = '0'
+ ET.SubElement(good_service, 'TaxBase').text = str(round(line.price_subtotal)) if line.price_subtotal else '0'
+ ET.SubElement(good_service, 'OtherTaxBase').text = str(otherTaxBase)
+ ET.SubElement(good_service, 'VATRate').text = '12'
+ ET.SubElement(good_service, 'VAT').text = str(round(otherTaxBase * 0.12, 2))
+ ET.SubElement(good_service, 'STLGRate').text = '0'
+ ET.SubElement(good_service, 'STLG').text = '0'
+
+ # Pretty print XML
+ xml_str = ET.tostring(root, encoding='utf-8')
+ xml_pretty = minidom.parseString(xml_str).toprettyxml(indent=" ")
+ return xml_pretty
+
+ def export_to_download(self, invoices):
+ # Generate XML content
+ xml_content = self.generate_xml(invoices)
+
+ # Encode content to Base64
+ xml_encoded = base64.b64encode(xml_content.encode('utf-8'))
+
+ # Buat attachment untuk XML
+ attachment = self.env['ir.attachment'].create({
+ 'name': 'Faktur_XML.xml',
+ 'type': 'binary',
+ 'datas': xml_encoded,
+ 'mimetype': 'application/xml',
+ 'store_fname': 'faktur_xml.xml',
+ })
+
+ # Kembalikan URL untuk download dengan header 'Content-Disposition'
+ response = {
+ 'type': 'ir.actions.act_url',
+ 'url': '/web/content/{}?download=true'.format(attachment.id),
+ 'target': 'self',
+ }
+
+ return response
diff --git a/indoteknik_custom/models/product_template.py b/indoteknik_custom/models/product_template.py
index 9007dd71..5bedae13 100755
--- a/indoteknik_custom/models/product_template.py
+++ b/indoteknik_custom/models/product_template.py
@@ -416,7 +416,7 @@ class ProductProduct(models.Model):
box_size=5,
border=4,
)
- qr.add_data(rec.display_name)
+ qr.add_data(rec.default_code)
qr.make(fit=True)
img = qr.make_image(fill_color="black", back_color="white")
diff --git a/indoteknik_custom/models/purchase_order.py b/indoteknik_custom/models/purchase_order.py
index 0e39d12a..799c4db0 100755
--- a/indoteknik_custom/models/purchase_order.py
+++ b/indoteknik_custom/models/purchase_order.py
@@ -75,6 +75,28 @@ class PurchaseOrder(models.Model):
exclude_incoming = fields.Boolean(string='Exclude Incoming', default=False,
help='Centang jika tidak mau masuk perhitungan Incoming Qty')
not_update_purchasepricelist = fields.Boolean(string='Not Update Purchase Pricelist?')
+ # total_cost_service = fields.Float(string='Total Cost Service')
+ # total_delivery_amt = fields.Float(string='Total Delivery Amt')
+
+ # @api.onchange('total_cost_service')
+ # def _onchange_total_cost_service(self):
+ # for order in self:
+ # lines = order.order_line
+ # if lines:
+ # # Hitung nilai rata-rata cost_service
+ # per_line_cost_service = order.total_cost_service / len(lines)
+ # for line in lines:
+ # line.cost_service = per_line_cost_service
+
+ # @api.onchange('total_delivery_amt')
+ # def _onchange_total_delivery_amt(self):
+ # for order in self:
+ # lines = order.order_line
+ # if lines:
+ # # Hitung nilai rata-rata delivery_amt
+ # per_line_delivery_amt = order.total_delivery_amt / len(lines)
+ # for line in lines:
+ # line.delivery_amt = per_line_delivery_amt
def _compute_total_margin_match(self):
for purchase in self:
@@ -115,6 +137,7 @@ class PurchaseOrder(models.Model):
'ref': self.name,
'invoice_date': current_date,
'date': current_date,
+ 'invoice_origin': self.name,
'move_type': 'in_invoice'
}
@@ -165,6 +188,11 @@ class PurchaseOrder(models.Model):
self.bills_pelunasan_id = bills.id
+ lognote_message = (
+ f"Vendor bill created from: {self.name} ({self.partner_ref})"
+ )
+ bills.message_post(body=lognote_message)
+
return {
'name': _('Account Move'),
'view_mode': 'tree,form',
@@ -174,12 +202,10 @@ class PurchaseOrder(models.Model):
'domain': [('id', '=', bills.id)]
}
-
-
def create_bill_dp(self):
if not self.env.user.is_accounting:
raise UserError('Hanya Accounting yang bisa bikin bill dp')
-
+
current_date = datetime.utcnow()
data_bills = {
'partner_id': self.partner_id.id,
@@ -187,8 +213,8 @@ class PurchaseOrder(models.Model):
'ref': self.name,
'invoice_date': current_date,
'date': current_date,
+ 'invoice_origin': self.name,
'move_type': 'in_invoice'
-
}
bills = self.env['account.move'].create([data_bills])
@@ -197,14 +223,13 @@ class PurchaseOrder(models.Model):
data_line_bills = {
'move_id': bills.id,
- 'product_id': product_dp.id, #product down payment
- 'account_id': 401, #Uang Muka persediaan barang dagang
+ 'product_id': product_dp.id, # product down payment
+ 'account_id': 401, # Uang Muka persediaan barang dagang
'quantity': 1,
'product_uom_id': 1,
'tax_ids': [line[0].taxes_id.id for line in self.order_line],
}
-
bills_line = self.env['account.move.line'].create([data_line_bills])
self.bills_dp_id = bills.id
@@ -213,6 +238,11 @@ class PurchaseOrder(models.Model):
move_line.name = '[IT.121456] Down Payment'
move_line.partner_id = self.partner_id.id
+ lognote_message = (
+ f"Vendor bill created from: {self.name} ({self.partner_ref})"
+ )
+ bills.message_post(body=lognote_message)
+
return {
'name': _('Account Move'),
'view_mode': 'tree,form',
diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py
index 2b448874..2ef1f43d 100755
--- a/indoteknik_custom/models/sale_order.py
+++ b/indoteknik_custom/models/sale_order.py
@@ -1031,8 +1031,9 @@ class SaleOrder(models.Model):
if self.have_outstanding_invoice:
raise UserError("Invoice harus di Cancel dahulu")
- elif self.have_outstanding_picking:
- raise UserError("DO harus di Cancel dahulu")
+ for line in self.order_line:
+ if line.qty_delivered > 0:
+ raise UserError("DO harus di-cancel terlebih dahulu.")
if not self.web_approval:
self.web_approval = 'company'
@@ -1410,4 +1411,16 @@ class SaleOrder(models.Model):
'npwp': partner.npwp,
'email': partner.email,
'customer_type': partner.customer_type,
- }) \ No newline at end of file
+ })
+
+ def write(self, vals):
+ for order in self:
+ if order.state in ['sale', 'cancel']:
+ if 'order_line' in vals:
+ new_lines = vals.get('order_line', [])
+ for command in new_lines:
+ if command[0] == 0: # A new line is being added
+ raise UserError(
+ "SO tidak dapat ditambahkan produk baru karena SO sudah menjadi sale order.")
+ res = super(SaleOrder, self).write(vals)
+ return res \ No newline at end of file
diff --git a/indoteknik_custom/models/sale_order_line.py b/indoteknik_custom/models/sale_order_line.py
index a82bcc8a..5b990ed0 100644
--- a/indoteknik_custom/models/sale_order_line.py
+++ b/indoteknik_custom/models/sale_order_line.py
@@ -38,6 +38,14 @@ class SaleOrderLine(models.Model):
md_vendor_id = fields.Many2one('res.partner', string='MD Vendor', readonly=True)
margin_md = fields.Float(string='Margin MD')
qty_free_bu = fields.Float(string='Free BU', compute='_get_qty_free_bandengan')
+ desc_updatable = fields.Boolean(string='desc boolean', default=False, compute='_get_desc_updatable')
+
+ def _get_desc_updatable(self):
+ for line in self:
+ if line.product_id.id != 417724:
+ line.desc_updatable = False
+ else:
+ line.desc_updatable = True
def _get_qty_free_bandengan(self):
for line in self:
@@ -272,6 +280,10 @@ class SaleOrderLine(models.Model):
(line.product_id.short_spesification if line.product_id.short_spesification else '')
line.name = line_name
line.weight = line.product_id.weight
+ if line.product_id.id != 417724:
+ line.desc_updatable = False
+ else:
+ line.desc_updatable = True
@api.constrains('vendor_id')
def _check_vendor_id(self):
@@ -378,4 +390,14 @@ class SaleOrderLine(models.Model):
if not line.product_id.product_tmpl_id.sale_ok:
raise UserError('Product %s belum bisa dijual, harap hubungi finance' % line.product_id.display_name)
if not line.vendor_id or not line.purchase_price and not line.display_type == 'line_note':
- raise UserError(_('Isi Vendor dan Harga Beli sebelum Request Approval')) \ No newline at end of file
+ raise UserError(_('Isi Vendor dan Harga Beli sebelum Request Approval'))
+
+ @api.depends('state')
+ def _compute_product_updatable(self):
+ for line in self:
+ if line.state == 'draft':
+ line.product_updatable = True
+ # line.desc_updatable = True
+ else:
+ line.product_updatable = False
+ # line.desc_updatable = False
diff --git a/indoteknik_custom/models/stock_immediate_transfer.py b/indoteknik_custom/models/stock_immediate_transfer.py
new file mode 100644
index 00000000..4be0dff2
--- /dev/null
+++ b/indoteknik_custom/models/stock_immediate_transfer.py
@@ -0,0 +1,36 @@
+from odoo import models, api, _
+from odoo.exceptions import UserError
+
+class StockImmediateTransfer(models.TransientModel):
+ _inherit = 'stock.immediate.transfer'
+
+ def process(self):
+ """Override process method to add send_mail_bills logic."""
+ pickings_to_do = self.env['stock.picking']
+ pickings_not_to_do = self.env['stock.picking']
+
+ for line in self.immediate_transfer_line_ids:
+ if line.to_immediate is True:
+ pickings_to_do |= line.picking_id
+ else:
+ pickings_not_to_do |= line.picking_id
+
+ for picking in pickings_to_do:
+ picking.send_mail_bills()
+ # If still in draft => confirm and assign
+ if picking.state == 'draft':
+ picking.action_confirm()
+ if picking.state != 'assigned':
+ picking.action_assign()
+ if picking.state != 'assigned':
+ raise UserError(_("Could not reserve all requested products. Please use the 'Mark as Todo' button to handle the reservation manually."))
+ for move in picking.move_lines.filtered(lambda m: m.state not in ['done', 'cancel']):
+ for move_line in move.move_line_ids:
+ move_line.qty_done = move_line.product_uom_qty
+
+ pickings_to_validate = self.env.context.get('button_validate_picking_ids')
+ if pickings_to_validate:
+ pickings_to_validate = self.env['stock.picking'].browse(pickings_to_validate)
+ pickings_to_validate = pickings_to_validate - pickings_not_to_do
+ return pickings_to_validate.with_context(skip_immediate=True).button_validate()
+ return True
diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py
index e25704f4..cd330aeb 100644
--- a/indoteknik_custom/models/stock_picking.py
+++ b/indoteknik_custom/models/stock_picking.py
@@ -6,10 +6,17 @@ from itertools import groupby
import pytz, requests, json, requests
from dateutil import parser
import datetime
-
+import hmac
+import hashlib
+import base64
+import requests
+import time
+import logging
+_logger = logging.getLogger(__name__)
class StockPicking(models.Model):
_inherit = 'stock.picking'
+ # check_product_lines = fields.One2many('check.product', 'picking_id', string='Check Product', auto_join=True)
is_internal_use = fields.Boolean('Internal Use', help='flag which is internal use or not')
account_id = fields.Many2one('account.account', string='Account')
efaktur_id = fields.Many2one('vit.efaktur', string='Faktur Pajak')
@@ -147,6 +154,86 @@ class StockPicking(models.Model):
envio_latest_longitude = fields.Float(string="Log Longitude", readonly=True)
tracking_by = fields.Many2one('res.users', string='Tracking By', readonly=True, tracking=True)
+ # Lalamove Section
+ lalamove_order_id = fields.Char(string="Lalamove Order ID", copy=False)
+ lalamove_address = fields.Char(string="Lalamove Address")
+ lalamove_name = fields.Char(string="Lalamove Name")
+ lalamove_phone = fields.Char(string="Lalamove Phone")
+ lalamove_status = fields.Char(string="Lalamove Status")
+ lalamove_delivered_at = fields.Datetime(string="Lalamove Delivered At")
+ lalamove_data = fields.Text(string="Lalamove Data", readonly=True)
+ lalamove_image_url = fields.Char(string="Lalamove Image URL")
+ lalamove_image_html = fields.Html(string="Lalamove Image", compute="_compute_lalamove_image_html")
+
+ def _compute_lalamove_image_html(self):
+ for record in self:
+ if record.lalamove_image_url:
+ record.lalamove_image_html = f'<img src="{record.lalamove_image_url}" width="300" height="300"/>'
+ else:
+ record.lalamove_image_html = "No image available."
+
+ def action_fetch_lalamove_order(self):
+ pickings = self.env['stock.picking'].search([
+ ('picking_type_code', '=', 'outgoing'),
+ ('state', '=', 'done'),
+ ('carrier_id', '=', 9)
+ ])
+ for picking in pickings:
+ try:
+ order_id = picking.lalamove_order_id
+ apikey = self.env['ir.config_parameter'].sudo().get_param('lalamove.apikey')
+ secret = self.env['ir.config_parameter'].sudo().get_param('lalamove.secret')
+ market = self.env['ir.config_parameter'].sudo().get_param('lalamove.market', default='ID')
+
+ order_data = picking.get_lalamove_order(order_id, apikey, secret, market)
+ picking.lalamove_data = order_data
+ except Exception as e:
+ _logger.error(f"Error fetching Lalamove order for picking {picking.id}: {str(e)}")
+ continue
+
+ def get_lalamove_order(self, order_id, apikey, secret, market):
+ timestamp = str(int(time.time() * 1000))
+ message = f"{timestamp}\r\nGET\r\n/v3/orders/{order_id}\r\n\r\n"
+ signature = hmac.new(secret.encode('utf-8'), message.encode('utf-8'), hashlib.sha256).hexdigest()
+
+ headers = {
+ "Content-Type": "application/json",
+ "Authorization": f"hmac {apikey}:{timestamp}:{signature}",
+ "Market": market
+ }
+
+ url = f"https://rest.lalamove.com/v3/orders/{order_id}"
+ response = requests.get(url, headers=headers)
+
+ if response.status_code == 200:
+ data = response.json()
+ stops = data.get("data", {}).get("stops", [])
+
+ for stop in stops:
+ pod = stop.get("POD", {})
+ if pod.get("status") == "DELIVERED":
+ image_url = pod.get("image") # Sesuaikan jika key berbeda
+ self.lalamove_image_url = image_url
+
+ address = stop.get("address")
+ name = stop.get("name")
+ phone = stop.get("phone")
+ delivered_at = pod.get("deliveredAt")
+
+ delivered_at_dt = self._convert_to_datetime(delivered_at)
+
+ self.lalamove_address = address
+ self.lalamove_name = name
+ self.lalamove_phone = phone
+ self.lalamove_status = pod.get("status")
+ self.lalamove_delivered_at = delivered_at_dt
+ return data
+
+ raise UserError("No delivered data found in Lalamove response.")
+ else:
+ raise UserError(f"Error {response.status_code}: {response.text}")
+
+
def _convert_to_wib(self, date_str):
"""
Mengonversi string waktu ISO 8601 ke format waktu Indonesia (WIB)
@@ -674,13 +761,13 @@ class StockPicking(models.Model):
if not self.env.user.is_logistic_approver and self.env.context.get('active_model') == 'stock.picking':
if self.origin and 'Return of' in self.origin:
raise UserError("Button ini hanya untuk Logistik")
-
+
if self.picking_type_code == 'internal':
self.check_qty_done_stock()
if self._name != 'stock.picking':
return super(StockPicking, self).button_validate()
-
+
if not self.picking_code:
self.picking_code = self.env['ir.sequence'].next_by_code('stock.picking.code') or '0'
@@ -694,15 +781,10 @@ class StockPicking(models.Model):
raise UserError("Harus di Approve oleh Accounting")
if self.picking_type_id.id == 28 and not self.env.user.is_logistic_approver:
- raise UserError("Harus di Approve oleh Logistik")
+ raise UserError("Harus di Approve oleh Logistik")
if self.location_dest_id.id == 47 and not self.env.user.is_purchasing_manager:
- raise UserError("Transfer ke gudang selisih harus di approve Rafly Hanggara")
-
- # if self.group_id.sale_id:
- # if self.group_id.sale_id.payment_link_midtrans:
- # if self.group_id.sale_id.payment_status != 'settlement' and self.group_id.sale_id.state == 'draft':
- # raise UserError('Uang belum masuk (settlement), mohon konfirmasi ke sales atau finance')
+ raise UserError("Transfer ke gudang selisih harus di approve Rafly Hanggara")
if self.is_internal_use:
self.approval_status = 'approved'
@@ -720,7 +802,7 @@ class StockPicking(models.Model):
if not self.date_reserved:
current_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
self.date_reserved = current_time
-
+
self.validation_minus_onhand_quantity()
self.responsible = self.env.user.id
res = super(StockPicking, self).button_validate()
@@ -728,6 +810,59 @@ class StockPicking(models.Model):
self.date_done = datetime.datetime.utcnow()
self.state_reserve = 'done'
return res
+
+
+ def send_mail_bills(self):
+ if self.picking_type_code == 'incoming' and self.purchase_id:
+ template = self.env.ref('indoteknik_custom.mail_template_invoice_po_document')
+ if template and self.purchase_id:
+ # Render email body
+ email_values = template.sudo().generate_email(
+ res_ids=[self.purchase_id.id],
+ fields=['body_html']
+ )
+ rendered_body = email_values.get(self.purchase_id.id, {}).get('body_html', '')
+
+ # Render report dengan XML ID
+ report = self.env.ref('purchase.action_report_purchase_order') # Gunakan XML ID laporan
+ if not report:
+ raise UserError("Laporan dengan XML ID 'purchase.action_report_purchase_order' tidak ditemukan.")
+
+ # Render laporan ke PDF
+ pdf_content, _ = report._render_qweb_pdf([self.purchase_id.id])
+ report_content = base64.b64encode(pdf_content).decode('utf-8')
+
+ # Kirim email menggunakan template
+ email_sent = template.sudo().send_mail(self.purchase_id.id, force_send=True)
+
+ if email_sent:
+ # Buat attachment untuk laporan
+ attachment = self.env['ir.attachment'].create({
+ 'name': self.purchase_id.name or "Laporan Invoice.pdf",
+ 'type': 'binary',
+ 'datas': report_content,
+ 'res_model': 'purchase.order',
+ 'res_id': self.purchase_id.id,
+ 'mimetype': 'application/pdf',
+ })
+
+ # Siapkan data untuk mail.compose.message
+ compose_values = {
+ 'subject': "Pengiriman Email Invoice",
+ 'body': rendered_body,
+ 'attachment_ids': [(4, attachment.id)],
+ 'res_id': self.purchase_id.id,
+ 'model': 'purchase.order',
+ }
+
+ # Buat mail.compose.message
+ compose_message = self.env['mail.compose.message'].create(compose_values)
+
+ # Kirim pesan melalui wizard
+ compose_message.action_send_mail()
+
+ return True
+
def action_cancel(self):
if not self.env.user.is_logistic_approver and self.env.context.get('active_model') == 'stock.picking':
if self.origin and 'Return of' in self.origin:
@@ -871,4 +1006,50 @@ class StockPicking(models.Model):
formatted_fastest_eta = fastest_eta.strftime(format_time_fastest)
formatted_longest_eta = longest_eta.strftime(format_time)
- return f'{formatted_fastest_eta} - {formatted_longest_eta}' \ No newline at end of file
+ return f'{formatted_fastest_eta} - {formatted_longest_eta}'
+
+# class CheckProduct(models.Model):
+# _name = 'check.product'
+# _description = 'Check Product'
+# _order = 'picking_id, id'
+
+# picking_id = fields.Many2one('stock.picking', string='Picking Reference', required=True, ondelete='cascade', index=True, copy=False)
+# product_id = fields.Many2one('product.product', string='Product')
+
+
+# @api.constrains('product_id')
+# def check_product_validity(self):
+# """
+# Validate if the product exists in the related stock.picking's move_ids_without_package
+# and ensure that the product's quantity does not exceed the available product_uom_qty.
+# """
+# for record in self:
+# if not record.picking_id or not record.product_id:
+# continue
+
+# # Filter move lines in the related picking for the selected product
+# moves = record.picking_id.move_ids_without_package.filtered(
+# lambda move: move.product_id.id == record.product_id.id
+# )
+
+# if not moves:
+# raise UserError((
+# "The product '%s' is not available in the related stock picking's moves. "
+# "Please check and try again."
+# ) % record.product_id.display_name)
+
+# # Calculate the total entries for the product in check.product for the same picking
+# product_entries_count = self.search_count([
+# ('picking_id', '=', record.picking_id.id),
+# ('product_id', '=', record.product_id.id)
+# ])
+
+# # Sum the product_uom_qty for all relevant moves
+# total_qty_in_moves = sum(moves.mapped('product_uom_qty'))
+
+# # Compare the count of entries against the available quantity
+# if product_entries_count > total_qty_in_moves:
+# raise UserError((
+# "The product '%s' exceeds the allowable quantity (%s) in the related stock picking's moves. "
+# "You can only add it %s times."
+# ) % (record.product_id.display_name, total_qty_in_moves, total_qty_in_moves))
diff --git a/indoteknik_custom/models/user_pengajuan_tempo_request.py b/indoteknik_custom/models/user_pengajuan_tempo_request.py
index c237f417..f5261cd4 100644
--- a/indoteknik_custom/models/user_pengajuan_tempo_request.py
+++ b/indoteknik_custom/models/user_pengajuan_tempo_request.py
@@ -334,7 +334,7 @@ class UserPengajuanTempoRequest(models.Model):
@api.onchange('tempo_duration')
def _tempo_duration_change(self, vals):
for tempo in self:
- if tempo.env.user.id not in (7, 377):
+ if tempo.env.user.id not in (7, 377, 12182):
raise UserError("Durasi tempo hanya bisa di ubah oleh Sales Manager atau Direktur")
@api.onchange('tempo_limit')
@@ -350,8 +350,8 @@ class UserPengajuanTempoRequest(models.Model):
if tempo.env.user.id in (688, 28, 7):
raise UserError("Pengajuan tempo harus di approve oleh sales manager terlebih dahulu")
else:
- # if tempo.env.user.id != 377:
- if tempo.env.user.id != 12182:
+ if tempo.env.user.id not in (377, 12182):
+ # if tempo.env.user.id != 12182:
raise UserError("Pengajuan tempo hanya bisa di approve oleh sales manager")
else:
return {
@@ -369,8 +369,8 @@ class UserPengajuanTempoRequest(models.Model):
if tempo.env.user.id == 7:
raise UserError("Pengajuan tempo harus di approve oleh Finence terlebih dahulu")
else:
- # if tempo.env.user.id not in (688, 28):
- if tempo.env.user.id not in (101,288,28,12182):
+ if tempo.env.user.id not in (688, 28, 12182):
+ # if tempo.env.user.id not in (288,28,12182):
raise UserError("Pengajuan tempo hanya bisa di approve oleh Finence")
else:
return {
@@ -385,8 +385,8 @@ class UserPengajuanTempoRequest(models.Model):
}
elif tempo.state_tempo == 'approval_finance':
- # if tempo.env.user.id != 7:
- if tempo.env.user.id != 12182:
+ if tempo.env.user.id != 7:
+ # if tempo.env.user.id != 12182:
raise UserError("Pengajuan tempo hanya bisa di approve oleh Direktur")
else:
return {
diff --git a/indoteknik_custom/security/ir.model.access.csv b/indoteknik_custom/security/ir.model.access.csv
index d4a1ea90..f53a360f 100755
--- a/indoteknik_custom/security/ir.model.access.csv
+++ b/indoteknik_custom/security/ir.model.access.csv
@@ -149,6 +149,8 @@ access_sales_order_fulfillment_v2,access.sales.order.fulfillment.v2,model_sales_
access_v_move_outstanding,access.v.move.outstanding,model_v_move_outstanding,,1,1,1,1
access_va_multi_approve,access.va.multi.approve,model_va_multi_approve,,1,1,1,1
access_va_multi_reject,access.va.multi.reject,model_va_multi_reject,,1,1,1,1
+access_stock_immediate_transfer,access.stock.immediate.transfer,model_stock_immediate_transfer,,1,1,1,1
+access_coretax_faktur,access.coretax.faktur,model_coretax_faktur,,1,1,1,1
access_User_pengajuan_tempo_line,access.user.pengajuan.tempo.line,model_user_pengajuan_tempo_line,,1,1,1,1
access_user_pengajuan_tempo,access.user.pengajuan.tempo,model_user_pengajuan_tempo,,1,1,1,1
diff --git a/indoteknik_custom/views/account_move.xml b/indoteknik_custom/views/account_move.xml
index 2863af57..4cc35b6d 100644
--- a/indoteknik_custom/views/account_move.xml
+++ b/indoteknik_custom/views/account_move.xml
@@ -163,5 +163,13 @@
<field name="state">code</field>
<field name="code">action = records.open_form_multi_create_reklas_penjualan()</field>
</record>
+
+ <record id="action_export_faktur" model="ir.actions.server">
+ <field name="name">Export Faktur ke XML</field>
+ <field name="model_id" ref="account.model_account_move" />
+ <field name="binding_model_id" ref="account.model_account_move" />
+ <field name="state">code</field>
+ <field name="code">action = records.export_faktur_to_xml()</field>
+ </record>
</data>
</odoo> \ No newline at end of file
diff --git a/indoteknik_custom/views/coretax_faktur.xml b/indoteknik_custom/views/coretax_faktur.xml
new file mode 100644
index 00000000..45eea23f
--- /dev/null
+++ b/indoteknik_custom/views/coretax_faktur.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="utf-8"?>
+<odoo>
+ <data>
+ <record id="act_coretax_faktur" model="ir.actions.act_window">
+ <field name="name">Export Faktur Pajak Keluaran XML Version</field>
+ <field name="type">ir.actions.act_window</field>
+ <field name="res_model">coretax.faktur</field>
+ <field name="view_mode">form</field>
+ <field name="target">new</field>
+ </record>
+
+ <record id="view_coretax_faktur" model="ir.ui.view">
+ <field name="name">view coretax faktur</field>
+ <field name="model">coretax.faktur</field>
+ <field name="type">form</field>
+ <field name="arch" type="xml">
+ <form string="Export Pajak Keluaran">
+ <p>
+ Klik tombol Export di bawah untuk mulai export Faktur Pajak Keluaran.
+ Data yang diexport adalah Customer Invoice yang berstatus Open dan belum diexport ke E-Faktur.
+ </p>
+
+ <p>
+ Setelah proses export Faktur Pajak Keluaran selesai dilakukan,
+ download file ini:
+ </p>
+ <group>
+ <field name="export_file" filename="export_filename" readonly="1"/>
+ </group>
+
+ <p>
+ Lalu import ke program E-Faktur DJP melalui menu <b>Referensi - Pajak Keluaran - Import</b>
+ </p>
+
+ <footer>
+ <button string="Export" name="export_to_download" type="object" class="btn-primary"/>
+ <button string="Cancel" class="btn-default" special="cancel"/>
+ </footer>
+ </form>
+ </field>
+ </record>
+
+ <menuitem id="coretax_faktur_export"
+ parent="vit_efaktur.menu_vit_efaktur_keluaran"
+ sequence="80"
+ name="Export FP. Keluaran XML"
+ action="act_coretax_faktur" />
+ </data>
+</odoo> \ No newline at end of file
diff --git a/indoteknik_custom/views/sale_order.xml b/indoteknik_custom/views/sale_order.xml
index 703b4d49..3539dbf3 100755
--- a/indoteknik_custom/views/sale_order.xml
+++ b/indoteknik_custom/views/sale_order.xml
@@ -107,6 +107,14 @@
{'readonly': [('state', 'in', ('done','cancel'))]}
</attribute>
</xpath>
+ <xpath expr="//form/sheet/notebook/page/field[@name='order_line']/tree" position="inside">
+ <field name="desc_updatable" invisible="1"/>
+ </xpath>
+ <xpath expr="//form/sheet/notebook/page/field[@name='order_line']/tree/field[@name='name']" position="attributes">
+ <attribute name="attrs">
+ {'readonly': [('desc_updatable', '=', False)]}
+ </attribute>
+ </xpath>
<xpath expr="//form/sheet/notebook/page/field[@name='order_line']/tree/field[@name='price_unit']" position="attributes">
<attribute name="attrs">
{
diff --git a/indoteknik_custom/views/stock_picking.xml b/indoteknik_custom/views/stock_picking.xml
index 26fe7c1e..8acba608 100644
--- a/indoteknik_custom/views/stock_picking.xml
+++ b/indoteknik_custom/views/stock_picking.xml
@@ -61,6 +61,11 @@
type="object"
attrs="{'invisible': [('carrier_id', '!=', 151)]}"
/>
+ <button name="action_fetch_lalamove_order"
+ string="Tracking Lalamove"
+ type="object"
+ attrs="{'invisible': [('carrier_id', '!=', 9)]}"
+ />
</button>
<field name="backorder_id" position="after">
<field name="summary_qty_detail"/>
@@ -81,6 +86,9 @@
<field name="partner_id" position="after">
<field name="real_shipping_id"/>
</field>
+ <field name="product_uom_qty" position="attributes">
+ <attribute name="attrs">{'readonly': [('parent.picking_type_code', '=', 'incoming')]}</attribute>
+ </field>
<field name="date_done" position="after">
<field name="arrival_time"/>
</field>
@@ -152,7 +160,7 @@
<field name="sj_documentation" widget="image" />
<field name="paket_documentation" widget="image" />
</group>
- <group>
+ <group attrs="{'invisible': [('carrier_id', '!=', 151)]}">
<field name="envio_id" invisible="1"/>
<field name="envio_code"/>
<field name="envio_ref_code"/>
@@ -170,6 +178,17 @@
<field name="envio_latest_longitude" invisible="1"/>
<field name="tracking_by" invisible="1"/>
</group>
+ <group attrs="{'invisible': [('carrier_id', '!=', 9)]}">
+ <field name="lalamove_data" invisible="1"/>
+ <field name="lalamove_order_id"/>
+ <field name="lalamove_address"/>
+ <field name="lalamove_name"/>
+ <field name="lalamove_phone"/>
+ <field name="lalamove_status"/>
+ <field name="lalamove_delivered_at"/>
+ <field name="lalamove_image_url" invisible="1"/>
+ <field name="lalamove_image_html"/>
+ </group>
</group>
</page>
<!-- <page string="Check Product" name="check_product">
@@ -184,8 +203,10 @@
<field name="name">check.product.tree</field>
<field name="model">check.product</field>
<field name="arch" type="xml">
- <tree editable="bottom">
+ <tree editable="bottom" decoration-warning="status == 'Pending'" decoration-success="status == 'Done'">
<field name="product_id"/>
+ <field name="quantity"/>
+ <field name="status"/>
</tree>
</field>
</record> -->
diff --git a/indoteknik_custom/views/user_pengajuan_tempo.xml b/indoteknik_custom/views/user_pengajuan_tempo.xml
index 5b9e51cc..f9e747b3 100644
--- a/indoteknik_custom/views/user_pengajuan_tempo.xml
+++ b/indoteknik_custom/views/user_pengajuan_tempo.xml
@@ -202,8 +202,8 @@
<field name="subject">Pengajuan Tempo Harus di Periksa!</field>
<field name="email_from">"Indoteknik.com" &lt;noreply@indoteknik.com&gt;</field>
<field name="reply_to">sales@indoteknik.com</field>
-<!-- <field name="email_to">vita@indoteknik.co.id</field>-->
- <field name="email_to">sapiabon768@gmail.com</field>
+ <field name="email_to">vita@indoteknik.co.id</field>
+<!-- <field name="email_to">sapiabon768@gmail.com</field>-->
<field name="body_html" type="html">
<table border="0" cellpadding="0" cellspacing="0" style="padding-top: 16px; background-color: #F1F1F1; font-family:Inter, Helvetica, Verdana, Arial,sans-serif; line-height: 24px; color: #454748; width: 100%; border-collapse:separate;">
<tr><td align="center">
@@ -269,8 +269,8 @@
<field name="subject">Pengajuan Tempo Harus di Periksa!</field>
<field name="email_from">"Indoteknik.com" &lt;noreply@indoteknik.com&gt;</field>
<field name="reply_to">sales@indoteknik.com</field>
-<!-- <field name="email_to">widyariyanti97@gmail.com, stephan@indoteknik.co.id</field>-->
- <field name="email_to">sapiabon768@gmail.com</field>
+ <field name="email_to">widyariyanti97@gmail.com, stephan@indoteknik.co.id</field>
+<!-- <field name="email_to">sapiabon768@gmail.com</field>-->
<field name="body_html" type="html">
<table border="0" cellpadding="0" cellspacing="0" style="padding-top: 16px; background-color: #F1F1F1; font-family:Inter, Helvetica, Verdana, Arial,sans-serif; line-height: 24px; color: #454748; width: 100%; border-collapse:separate;">
<tr><td align="center">
diff --git a/indoteknik_custom/views/user_pengajuan_tempo_request.xml b/indoteknik_custom/views/user_pengajuan_tempo_request.xml
index 7ba87184..bb8262c9 100644
--- a/indoteknik_custom/views/user_pengajuan_tempo_request.xml
+++ b/indoteknik_custom/views/user_pengajuan_tempo_request.xml
@@ -200,8 +200,8 @@
<field name="subject">Pengajuan Tempo Harus di Periksa!</field>
<field name="email_from">"Indoteknik.com" &lt;noreply@indoteknik.com&gt;</field>
<field name="reply_to">sales@indoteknik.com</field>
-<!-- <field name="email_to">akbar@fixcomart.co.id</field>-->
- <field name="email_to">sapiabon768@gmail.com</field>
+ <field name="email_to">akbar@fixcomart.co.id</field>
+<!-- <field name="email_to">sapiabon768@gmail.com</field>-->
<field name="body_html" type="html">
<table border="0" cellpadding="0" cellspacing="0" style="padding-top: 16px; background-color: #F1F1F1; font-family:Inter, Helvetica, Verdana, Arial,sans-serif; line-height: 24px; color: #454748; width: 100%; border-collapse:separate;">
<tr><td align="center">