summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--fixco_custom/__manifest__.py3
-rw-r--r--fixco_custom/models/__init__.py2
-rw-r--r--fixco_custom/models/barcoding_product.py78
-rw-r--r--fixco_custom/models/detail_order.py183
-rw-r--r--fixco_custom/models/product_product.py41
-rw-r--r--fixco_custom/models/sale.py1
-rw-r--r--fixco_custom/models/stock_picking.py278
-rw-r--r--fixco_custom/security/ir.model.access.csv6
-rw-r--r--fixco_custom/views/barcoding_product.xml78
-rw-r--r--fixco_custom/views/product_product.xml17
-rw-r--r--fixco_custom/views/sale_order.xml3
-rw-r--r--fixco_custom/views/stock_picking.xml47
12 files changed, 645 insertions, 92 deletions
diff --git a/fixco_custom/__manifest__.py b/fixco_custom/__manifest__.py
index 232d2d9..85d9be3 100644
--- a/fixco_custom/__manifest__.py
+++ b/fixco_custom/__manifest__.py
@@ -15,6 +15,9 @@
'views/sale_order.xml',
'views/webhook_ginee.xml',
'views/detail_order.xml',
+ 'views/stock_picking.xml',
+ 'views/product_product.xml',
+ 'views/barcoding_product.xml',
],
'demo': [],
'css': [],
diff --git a/fixco_custom/models/__init__.py b/fixco_custom/models/__init__.py
index 9b3459e..6a4717c 100644
--- a/fixco_custom/models/__init__.py
+++ b/fixco_custom/models/__init__.py
@@ -3,3 +3,5 @@ from . import sale
from . import webhook_ginee
from . import detail_order
from . import stock_picking
+from . import product_product
+from . import barcoding_product
diff --git a/fixco_custom/models/barcoding_product.py b/fixco_custom/models/barcoding_product.py
new file mode 100644
index 0000000..335b481
--- /dev/null
+++ b/fixco_custom/models/barcoding_product.py
@@ -0,0 +1,78 @@
+from odoo import models, api, fields
+from odoo.exceptions import AccessError, UserError, ValidationError
+from datetime import timedelta, date, datetime
+import logging
+
+_logger = logging.getLogger(__name__)
+
+class BarcodingProduct(models.Model):
+ _name = "barcoding.product"
+ _description = "Barcoding Product"
+
+ barcoding_product_line = fields.One2many('barcoding.product.line', 'barcoding_product_id', string='Barcoding Product Lines', auto_join=True)
+ product_id = fields.Many2one('product.product', string="Product", tracking=3)
+ quantity = fields.Float(string="Quantity", tracking=3)
+ type = fields.Selection([('print', 'Print Barcode'), ('barcoding', 'Add Barcode To Product'), ('barcoding_box', 'Add Barcode Box To Product'), ('multiparts', 'Multiparts Product')], string='Type', default='print')
+ barcode = fields.Char(string="Barcode")
+ qty_pcs_box = fields.Char(string="Quantity Pcs Box")
+
+ def check_duplicate_barcode(self):
+ if self.type in ['barcoding_box', 'barcoding']:
+ barcode_product = self.env['product.product'].search([('barcode', '=', self.barcode)])
+
+ if barcode_product:
+ raise UserError('Barcode sudah digunakan {}'.format(barcode_product.display_name))
+
+ barcode_box = self.env['product.product'].search([('barcode_box', '=', self.barcode)])
+
+ if barcode_box:
+ raise UserError('Barcode box sudah digunakan {}'.format(barcode_box.display_name))
+
+ @api.constrains('barcode')
+ def _send_barcode_to_product(self):
+ for record in self:
+ record.check_duplicate_barcode()
+ if record.type == 'barcoding_box':
+ record.product_id.barcode_box = record.barcode
+ record.product_id.qty_pcs_box = record.qty_pcs_box
+ else:
+ record.product_id.barcode = record.barcode
+
+ @api.onchange('product_id', 'quantity')
+ def _onchange_product_or_quantity(self):
+ if self.product_id and self.quantity > 0:
+ self.barcoding_product_line = [(5, 0, 0)]
+
+ lines = []
+ for i in range(int(self.quantity)):
+ lines.append((0, 0, {
+ 'product_id': self.product_id.id,
+ 'barcoding_product_id': self.id,
+ 'sequence_with_total': f"{i+1}/{int(self.quantity)}"
+ }))
+ self.barcoding_product_line = lines
+
+ def write(self, vals):
+ res = super().write(vals)
+ if 'quantity' in vals and self.type == 'multiparts':
+ self._update_sequence_with_total()
+ return res
+
+ def _update_sequence_with_total(self):
+ for rec in self:
+ total = int(rec.quantity)
+ for index, line in enumerate(rec.barcoding_product_line, start=1):
+ line.sequence_with_total = f"{index}/{total}"
+
+
+class BarcodingProductLine(models.Model):
+ _name = 'barcoding.product.line'
+ _description = 'Barcoding Product Line'
+ _order = 'barcoding_product_id, id'
+
+ barcoding_product_id = fields.Many2one('barcoding.product', string='Barcoding Product Ref', required=True, ondelete='cascade', index=True, copy=False)
+ product_id = fields.Many2one('product.product', string="Product")
+ qr_code_variant = fields.Binary("QR Code Variant", related='product_id.qr_code_variant')
+ sequence_with_total = fields.Char(
+ string="Sequence"
+ ) \ No newline at end of file
diff --git a/fixco_custom/models/detail_order.py b/fixco_custom/models/detail_order.py
index 7bb3aec..466544f 100644
--- a/fixco_custom/models/detail_order.py
+++ b/fixco_custom/models/detail_order.py
@@ -23,6 +23,7 @@ class DetailOrder(models.Model):
('so_confirm', 'SO Confirm'),
('done', 'Done'),
('failed', 'Failed'),
+ ('already_so', 'SO Already Created'),
], 'Execute Status')
sale_id = fields.Many2one('sale.order', 'Sale Order')
picking_id = fields.Many2one('stock.picking', 'Picking')
@@ -83,8 +84,7 @@ class DetailOrder(models.Model):
self.execute_status = 'detail_order'
else:
self.write({
- 'execute_status': 'failed',
- 'json_ginee': json.dumps({
+ 'message_error': json.dumps({
'error': f"Request failed with status code {response.status_code}",
'response': response.text
})
@@ -92,8 +92,7 @@ class DetailOrder(models.Model):
except Exception as e:
self.write({
- 'execute_status': 'failed',
- 'json_ginee': json.dumps({
+ 'message_error': json.dumps({
'error': str(e)
})
})
@@ -143,7 +142,11 @@ class DetailOrder(models.Model):
limit=1
)
if not product:
- raise UserError(_("Product not found for SKU: %s") % item.get('sku'))
+ product = self.env['product.product'].search(
+ [('default_code', '=', 'PL-LN0760')],
+ limit=1
+ )
+ # raise UserError(_("Product not found for SKU: %s") % item.get('sku'))
line_data = {
'product_id': product.id,
@@ -161,35 +164,43 @@ class DetailOrder(models.Model):
order_lines = self.prepare_data_so_line(json_data)
order_id, order_status, print_info = self.get_order_id_detail()
- if order_status == 'READY_TO_SHIP' and print_info == 'NOT_PRINTED':
- # if order_status == 'SHIPPING' and print_info == 'PRINTED':
- data['order_line'] = order_lines
-
- sale_order = self.env['sale.order'].create(data)
-
- self.sale_id = sale_order.id
- sale_order.action_confirm()
+ if order_status != 'PENDING_PAYMENT':
+ if order_status == 'PARTIALLY_PAID' or order_status == 'PAID':
+ data['order_line'] = order_lines
+
+ sale_order = self.env['sale.order'].create(data)
+
+ self.sale_id = sale_order.id
+ sale_order.order_reference = order_id
+ sale_order.action_confirm()
+
- self.picking_id = sale_order.picking_ids[0].id
+ self.picking_id = sale_order.picking_ids[0].id
- self.execute_status = 'so_confirm'
- elif order_status == 'READY_TO_SHIP' and print_info == 'PRINTED':
- data['order_line'] = order_lines
-
- sale_order = self.env['sale.order'].create(data)
-
- self.sale_id = sale_order.id
- sale_order.action_confirm()
+ self.picking_id.order_reference = order_id
- self.picking_id = sale_order.picking_ids[0].id
- self.picking_id.sync_qty_reserved_qty_done()
+ self.execute_status = 'so_confirm'
+ else:
+ sale_orders = self.env['sale.order'].search([('order_reference', '=', order_id)])
+ if not sale_orders:
+ data['order_line'] = order_lines
+
+ sale_order = self.env['sale.order'].create(data)
+
+ self.sale_id = sale_order.id
+ sale_order.order_reference = order_id
+ sale_order.action_confirm()
- self.execute_status = 'so_confirm'
-
+ self.picking_id = sale_order.picking_ids[0].id
+ self.picking_id.order_reference = order_id
+
+ self.execute_status = 'so_confirm'
+ else:
+ self.sale_id = sale_orders.id
+ self.execute_status = 'already_so'
except Exception as e:
self.write({
- 'execute_status': 'failed',
'message_error': json.dumps({
'error': str(e)
})
@@ -206,72 +217,72 @@ class DetailOrder(models.Model):
# check print do section
- def get_order_id_check_print(self):
- try:
- if self.detail_order:
- json_data = json.loads(self.detail_order)
- order_id = json_data.get('data', {})[0].get('orderId')
- if not order_id:
- raise UserError(_("Order ID not found in JSON data"))
- return order_id
- raise UserError(_("No JSON data available"))
- except json.JSONDecodeError:
- raise UserError(_("Invalid JSON format in check_print field"))
- except Exception as e:
- raise UserError(_("Error extracting order ID: %s") % str(e))
+ # def get_order_id_check_print(self):
+ # try:
+ # if self.detail_order:
+ # json_data = json.loads(self.detail_order)
+ # order_id = json_data.get('data', {})[0].get('orderId')
+ # if not order_id:
+ # raise UserError(_("Order ID not found in JSON data"))
+ # return order_id
+ # raise UserError(_("No JSON data available"))
+ # except json.JSONDecodeError:
+ # raise UserError(_("Invalid JSON format in check_print field"))
+ # except Exception as e:
+ # raise UserError(_("Error extracting order ID: %s") % str(e))
- def process_queue_item_check_print(self, limit=100):
- domain = [('picking_id.state', 'not in', ['done', 'cancel'])]
- records = self.search(domain, order='create_date asc', limit=limit)
- for rec in records:
- rec.execute_queue_check_print()
+ # def process_queue_item_check_print(self, limit=100):
+ # domain = [('picking_id.state', 'not in', ['done', 'cancel'])]
+ # records = self.search(domain, order='create_date asc', limit=limit)
+ # for rec in records:
+ # rec.execute_queue_check_print()
- def execute_queue_check_print(self):
- try:
- order_id = self.get_order_id_check_print()
+ # def execute_queue_check_print(self):
+ # try:
+ # order_id = self.get_order_id_check_print()
- authorization = self.sign_request()
- headers = {
- 'Content-Type': 'application/json',
- 'X-Advai-Country': 'ID',
- 'Authorization': authorization
- }
+ # authorization = self.sign_request()
+ # headers = {
+ # 'Content-Type': 'application/json',
+ # 'X-Advai-Country': 'ID',
+ # 'Authorization': authorization
+ # }
- payload = {
- "orderIds": [order_id]
- }
+ # payload = {
+ # "orderIds": [order_id]
+ # }
- url = "https://api.ginee.com/openapi/order/v1/batch-get"
+ # url = "https://api.ginee.com/openapi/order/v1/batch-get"
- response = requests.post(
- url,
- headers=headers,
- data=json.dumps(payload)
- )
+ # response = requests.post(
+ # url,
+ # headers=headers,
+ # data=json.dumps(payload)
+ # )
- if response.status_code == 200:
- data = response.json()
- self.detail_order = json.dumps(data)
- detail_order = json.loads(self.detail_order)
- if detail_order['data'][0]['printInfo']['labelPrintStatus'] == 'PRINTED': #ubah ke printed lagi nanti
- self.picking_id.sync_qty_reserved_qty_done()
- # self.sale_id.api_create_invoices(self.sale_id.id)
- self.execute_status = 'done'
- else:
- self.write({
- 'execute_status': 'failed',
- 'json_ginee': json.dumps({
- 'error': f"Request failed with status code {response.status_code}",
- 'response': response.text
- })
- })
+ # if response.status_code == 200:
+ # data = response.json()
+ # self.detail_order = json.dumps(data)
+ # detail_order = json.loads(self.detail_order)
+ # if detail_order['data'][0]['printInfo']['labelPrintStatus'] == 'PRINTED': #ubah ke printed lagi nanti
+ # self.picking_id.sync_qty_reserved_qty_done()
+ # # self.sale_id.api_create_invoices(self.sale_id.id)
+ # self.execute_status = 'done'
+ # else:
+ # self.write({
+ # 'execute_status': 'failed',
+ # 'json_ginee': json.dumps({
+ # 'error': f"Request failed with status code {response.status_code}",
+ # 'response': response.text
+ # })
+ # })
- except Exception as e:
- self.write({
- 'execute_status': 'failed',
- 'json_ginee': json.dumps({
- 'error': str(e)
- })
- })
+ # except Exception as e:
+ # self.write({
+ # 'execute_status': 'failed',
+ # 'json_ginee': json.dumps({
+ # 'error': str(e)
+ # })
+ # })
\ No newline at end of file
diff --git a/fixco_custom/models/product_product.py b/fixco_custom/models/product_product.py
new file mode 100644
index 0000000..2e571a8
--- /dev/null
+++ b/fixco_custom/models/product_product.py
@@ -0,0 +1,41 @@
+from odoo import fields, models, api, tools, _
+from datetime import datetime, timedelta, date
+from odoo.exceptions import UserError
+import logging
+import requests
+import json
+import re
+import qrcode, base64
+from bs4 import BeautifulSoup
+from io import BytesIO
+
+
+class ProductProduct(models.Model):
+ _inherit = "product.product"
+
+ qty_pcs_box = fields.Float("Pcs Box")
+ barcode_box = fields.Char("Barcode Box")
+ qr_code_variant = fields.Binary("QR Code Variant", compute='_compute_qr_code_variant')
+
+ def _compute_qr_code_variant(self):
+ for rec in self:
+ # Skip inactive variants
+ if not rec.active:
+ rec.qr_code_variant = False # Clear the QR Code for archived variants
+ continue
+
+ qr = qrcode.QRCode(
+ version=1,
+ error_correction=qrcode.constants.ERROR_CORRECT_L,
+ box_size=5,
+ border=4,
+ )
+ qr.add_data(rec.barcode if rec.barcode else rec.default_code)
+ qr.make(fit=True)
+ img = qr.make_image(fill_color="black", back_color="white")
+
+ buffer = BytesIO()
+ img.save(buffer, format="PNG")
+ qr_code_img = base64.b64encode(buffer.getvalue()).decode()
+
+ rec.qr_code_variant = qr_code_img \ No newline at end of file
diff --git a/fixco_custom/models/sale.py b/fixco_custom/models/sale.py
index b266d6a..8764681 100644
--- a/fixco_custom/models/sale.py
+++ b/fixco_custom/models/sale.py
@@ -6,3 +6,4 @@ class SaleOrder(models.Model):
_inherit = "sale.order"
carrier_id = fields.Many2one('delivery.carrier', string='Shipping Method')
+ order_reference = fields.Char(string='Order Reference')
diff --git a/fixco_custom/models/stock_picking.py b/fixco_custom/models/stock_picking.py
index 4814ac5..9e2a5e6 100644
--- a/fixco_custom/models/stock_picking.py
+++ b/fixco_custom/models/stock_picking.py
@@ -15,18 +15,286 @@ import requests
import time
import logging
import re
+from hashlib import sha256
_logger = logging.getLogger(__name__)
+Request_URI = '/openapi/order/v1/print'
+ACCESS_KEY = '24bb6a1ec618ec6a'
+SECRET_KEY = '32e4a78ad05ee230'
class StockPicking(models.Model):
_inherit = 'stock.picking'
+ check_product_lines = fields.One2many('check.product', 'picking_id', string='Check Product', auto_join=True, copy=False)
- def sync_qty_reserved_qty_done(self):
+ 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')
+
+ def label_ginee(self):
+ try:
+ order_id = self.order_reference
+
+ authorization = self.sign_request()
+ headers = {
+ 'Content-Type': 'application/json',
+ 'X-Advai-Country': 'ID',
+ 'Authorization': authorization
+ }
+
+ payload = {
+ "orderId": order_id,
+ "documentType": "LABEL"
+ }
+ url = "https://api.ginee.com/openapi/order/v1/print"
+
+ response = requests.post(
+ url,
+ headers=headers,
+ data=json.dumps(payload)
+ )
+
+ if response.status_code == 200:
+ data = response.json()
+ if data.get('code') == 'SUCCESS' and data.get('message') == 'OK':
+ logistic_info_list = data.get('data', {}).get('logisticsInfos')
+
+ # Check if logistic_info exists and has at least one item
+ if not logistic_info_list:
+ raise UserError(_("No logistic information found in response"))
+
+ logistic_info = logistic_info_list[0]
+ self.pdf_label_url = data.get('data', {}).get('pdfUrl') or ''
+ self.tracking_number = logistic_info.get('logisticsTrackingNumber') or ''
+ self.provider_name = logistic_info.get('logisticsProviderName') or ''
+ self.invoice_number = logistic_info.get('invoiceNumber') or ''
+ else:
+ raise UserError(_("API Error: %s - %s") % (data.get('code', 'UNKNOWN'), data.get('message', 'No error message')))
+ else:
+ raise UserError(_("API request failed with status code: %s") % response.status_code)
+
+ except Exception as e:
+ raise UserError(_("Error: %s") % str(e))
+
+ def sign_request(self):
+ signData = '$'.join(['POST', Request_URI]) + '$'
+ authorization = ACCESS_KEY + ':' + base64.b64encode(
+ hmac.new(SECRET_KEY.encode('utf-8'), signData.encode('utf-8'), digestmod=sha256).digest()
+ ).decode('ascii')
+ return authorization
+
+
+
+ # def sync_qty_reserved_qty_done(self):
- for picking in self:
- for line in picking.move_line_ids_without_package:
- line.qty_done = line.product_uom_qty
+ # for picking in self:
+ # for line in picking.move_line_ids_without_package:
+ # line.qty_done = line.product_uom_qty
- picking.button_validate() \ No newline at end of file
+ # picking.button_validate()
+
+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')
+ quantity = fields.Float(string='Quantity')
+ status = fields.Char(string='Status', compute='_compute_status')
+ code_product = fields.Char(string='Code Product')
+
+ @api.onchange('code_product')
+ def _onchange_code_product(self):
+ if not self.code_product:
+ return
+
+ # Cari product berdasarkan default_code, barcode, atau barcode_box
+ product = self.env['product.product'].search([
+ '|',
+ ('default_code', '=', self.code_product),
+ '|',
+ ('barcode', '=', self.code_product),
+ ('barcode_box', '=', self.code_product)
+ ], limit=1)
+
+ if not product:
+ raise UserError("Product tidak ditemukan")
+
+ # Jika scan barcode_box, set quantity sesuai qty_pcs_box
+ if product.barcode_box == self.code_product:
+ self.product_id = product.id
+ self.quantity = product.qty_pcs_box
+ self.code_product = product.default_code or product.barcode
+ # return {
+ # 'warning': {
+ # 'title': 'Info',8994175025871
+
+ # 'message': f'Product box terdeteksi. Quantity di-set ke {product.qty_pcs_box}'
+ # }
+ # }
+ else:
+ # Jika scan biasa
+ self.product_id = product.id
+ self.code_product = product.default_code or product.barcode
+ self.quantity = 1
+
+ def unlink(self):
+ # Get all affected pickings before deletion
+ pickings = self.mapped('picking_id')
+
+ # Store product_ids that will be deleted
+ deleted_product_ids = self.mapped('product_id')
+
+ # Perform the deletion
+ result = super(CheckProduct, self).unlink()
+
+ # After deletion, update moves for affected pickings
+ for picking in pickings:
+ # For products that were completely removed (no remaining check.product lines)
+ remaining_product_ids = picking.check_product_lines.mapped('product_id')
+ removed_product_ids = deleted_product_ids - remaining_product_ids
+
+ # Set quantity_done to 0 for moves of completely removed products
+ moves_to_reset = picking.move_ids_without_package.filtered(
+ lambda move: move.product_id in removed_product_ids
+ )
+ for move in moves_to_reset:
+ move.quantity_done = 0.0
+
+ # Also sync remaining products in case their totals changed
+ self._sync_check_product_to_moves(picking)
+
+ return result
+
+ @api.depends('quantity')
+ def _compute_status(self):
+ for record in self:
+ moves = record.picking_id.move_ids_without_package.filtered(
+ lambda move: move.product_id.id == record.product_id.id
+ )
+ total_qty_in_moves = sum(moves.mapped('product_uom_qty'))
+
+ if record.quantity < total_qty_in_moves:
+ record.status = 'Pending'
+ else:
+ record.status = 'Done'
+
+ def create(self, vals):
+ # Create the record
+ record = super(CheckProduct, self).create(vals)
+ # Ensure uniqueness after creation
+ if not self.env.context.get('skip_consolidate'):
+ record.with_context(skip_consolidate=True)._consolidate_duplicate_lines()
+ return record
+
+ def write(self, vals):
+ # Write changes to the record
+ result = super(CheckProduct, self).write(vals)
+ # Ensure uniqueness after writing
+ if not self.env.context.get('skip_consolidate'):
+ self.with_context(skip_consolidate=True)._consolidate_duplicate_lines()
+ return result
+
+ def _sync_check_product_to_moves(self, picking):
+ """
+ Sinkronisasi quantity_done di move_ids_without_package
+ dengan total quantity dari check.product berdasarkan product_id.
+ """
+ for product_id in picking.check_product_lines.mapped('product_id'):
+ # Totalkan quantity dari semua baris check.product untuk product_id ini
+ total_quantity = sum(
+ line.quantity for line in
+ picking.check_product_lines.filtered(lambda line: line.product_id == product_id)
+ )
+ # Update quantity_done di move yang relevan
+ moves = picking.move_ids_without_package.filtered(lambda move: move.product_id == product_id)
+ for move in moves:
+ move.quantity_done = total_quantity
+
+ def _consolidate_duplicate_lines(self):
+ """
+ Consolidate duplicate lines with the same product_id under the same picking_id
+ and sync the total quantity to related moves.
+ """
+ for picking in self.mapped('picking_id'):
+ lines_to_remove = self.env['check.product'] # Recordset untuk menyimpan baris yang akan dihapus
+ product_lines = picking.check_product_lines.filtered(lambda line: line.product_id)
+
+ # Group lines by product_id
+ product_groups = {}
+ for line in product_lines:
+ product_groups.setdefault(line.product_id.id, []).append(line)
+
+ for product_id, lines in product_groups.items():
+ if len(lines) > 1:
+ # Consolidate duplicate lines
+ first_line = lines[0]
+ total_quantity = sum(line.quantity for line in lines)
+
+ # Update the first line's quantity
+ first_line.with_context(skip_consolidate=True).write({'quantity': total_quantity})
+
+ # Add the remaining lines to the lines_to_remove recordset
+ lines_to_remove |= self.env['check.product'].browse([line.id for line in lines[1:]])
+
+ # Perform unlink after consolidation
+ if lines_to_remove:
+ lines_to_remove.unlink()
+
+ # Sync total quantities to moves
+ self._sync_check_product_to_moves(picking)
+
+ @api.onchange('product_id', 'quantity')
+ def check_product_validity(self):
+ for record in self:
+ if not record.picking_id or not record.product_id:
+ continue
+
+ # Filter moves related to 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' tidak ada di operations. "
+ ) % record.product_id.display_name)
+
+ total_qty_in_moves = sum(moves.mapped('product_uom_qty'))
+
+ # Find existing lines for the same product, excluding the current line
+ existing_lines = record.picking_id.check_product_lines.filtered(
+ lambda line: line.product_id == record.product_id
+ )
+
+ if existing_lines:
+ # Get the first existing line
+ first_line = existing_lines[0]
+
+ # Calculate the total quantity after addition
+ total_quantity = sum(existing_lines.mapped('quantity'))
+
+ if total_quantity > total_qty_in_moves:
+ raise UserError((
+ "Quantity Product '%s' sudah melebihi quantity demand."
+ ) % (record.product_id.display_name))
+ else:
+ # Check if the quantity exceeds the allowed total
+ if record.quantity == total_qty_in_moves:
+ raise UserError((
+ "Quantity Product '%s' sudah melebihi quantity demand."
+ ) % (record.product_id.display_name))
+
+ # Set the quantity to the entered value
+ record.quantity = record.quantity \ No newline at end of file
diff --git a/fixco_custom/security/ir.model.access.csv b/fixco_custom/security/ir.model.access.csv
index a2d897a..260397e 100644
--- a/fixco_custom/security/ir.model.access.csv
+++ b/fixco_custom/security/ir.model.access.csv
@@ -1,3 +1,7 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_webhook_ginee,access.webhook.ginee,model_webhook_ginee,,1,1,1,1
-access_detail_order,access.detail.order,model_detail_order,,1,1,1,1 \ No newline at end of file
+access_detail_order,access.detail.order,model_detail_order,,1,1,1,1
+access_check_product,access.check.product,model_check_product,,1,1,1,1
+access_product_product,access.product.product,model_product_product,,1,1,1,1
+access_barcoding_product,access.barcoding.product,model_barcoding_product,,1,1,1,1
+access_barcoding_product_line,access.barcoding.product.line,model_barcoding_product_line,,1,1,1,1 \ No newline at end of file
diff --git a/fixco_custom/views/barcoding_product.xml b/fixco_custom/views/barcoding_product.xml
new file mode 100644
index 0000000..b259f1e
--- /dev/null
+++ b/fixco_custom/views/barcoding_product.xml
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<odoo>
+ <data>
+ <record id="barcoding_product_tree" model="ir.ui.view">
+ <field name="name">barcoding.product.tree</field>
+ <field name="model">barcoding.product</field>
+ <field name="arch" type="xml">
+ <tree default_order="create_date desc">
+ <field name="product_id"/>
+ <field name="quantity"/>
+ <field name="type"/>
+ </tree>
+ </field>
+ </record>
+
+ <record id="barcoding_product_line_tree" model="ir.ui.view">
+ <field name="name">barcoding.product.line.tree</field>
+ <field name="model">barcoding.product.line</field>
+ <field name="arch" type="xml">
+ <tree>
+ <field name="product_id"/>
+ <field name="qr_code_variant" widget="image"/>
+ <field name="sequence_with_total" attrs="{'invisible': [['parent.type', 'not in', ('multiparts')]]}"/>
+ </tree>
+ </field>
+ </record>
+
+ <record id="barcoding_product_form" model="ir.ui.view">
+ <field name="name">barcoding.product.form</field>
+ <field name="model">barcoding.product</field>
+ <field name="arch" type="xml">
+ <form >
+ <sheet>
+ <group>
+ <group>
+ <field name="product_id" required="1"/>
+ <field name="type" required="1"/>
+ <field name="quantity" attrs="{'invisible': [['type', 'in', ('barcoding','barcoding_box')]], 'required': [['type', 'not in', ('barcoding')]]}"/>
+ <field name="barcode" attrs="{'invisible': [['type', 'in', ('print','multiparts')]], 'required': [['type', 'not in', ('print','multiparts')]]}"/>
+ <field name="qty_pcs_box" attrs="{'invisible': [['type', 'in', ('print','barcoding','multiparts')]], 'required': [['type', 'not in', ('print','barcoding','multiparts')]]}"/>
+ </group>
+ </group>
+ <notebook>
+ <page string="Line" attrs="{'invisible': [['type', 'in', ('barcoding','barcoding_box')]]}">
+ <field name="barcoding_product_line"/>
+ </page>
+ </notebook>
+ </sheet>
+ </form>
+ </field>
+ </record>
+
+ <record id="barcoding_product_view_search" model="ir.ui.view">
+ <field name="name">barcoding.product.search.view</field>
+ <field name="model">barcoding.product</field>
+ <field name="arch" type="xml">
+ <search string="Search Barcoding Product">
+ <field name="product_id"/>
+ </search>
+ </field>
+ </record>
+
+ <record id="barcoding_product_action" model="ir.actions.act_window">
+ <field name="name">Barcoding Product</field>
+ <field name="type">ir.actions.act_window</field>
+ <field name="res_model">barcoding.product</field>
+ <field name="view_mode">tree,form</field>
+ </record>
+
+ <menuitem
+ id="menu_barcoding_product"
+ name="Barcoding Product"
+ parent="stock.menu_stock_warehouse_mgmt"
+ sequence="4"
+ action="barcoding_product_action"
+ />
+ </data>
+</odoo>
diff --git a/fixco_custom/views/product_product.xml b/fixco_custom/views/product_product.xml
new file mode 100644
index 0000000..2db7c31
--- /dev/null
+++ b/fixco_custom/views/product_product.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<odoo>
+ <data>
+ <record id="product_normal_form_view_inherit" model="ir.ui.view">
+ <field name="name">Product Template</field>
+ <field name="model">product.product</field>
+ <field name="inherit_id" ref="product.product_normal_form_view"/>
+ <field name="arch" type="xml">
+ <field name="categ_id" position="after">
+ <field name="barcode_box" />
+ <field name="qty_pcs_box" />
+ <field name="qr_code_variant" widget="image" readonly="True"/>
+ </field>
+ </field>
+ </record>
+ </data>
+</odoo>
diff --git a/fixco_custom/views/sale_order.xml b/fixco_custom/views/sale_order.xml
index 5a6bcbf..cbcbdc2 100644
--- a/fixco_custom/views/sale_order.xml
+++ b/fixco_custom/views/sale_order.xml
@@ -9,6 +9,9 @@
<field name="tag_ids" position="after">
<field name="carrier_id"/>
</field>
+ <field name="client_order_ref" position="after">
+ <field name="order_reference"/>
+ </field>
</field>
</record>
</data>
diff --git a/fixco_custom/views/stock_picking.xml b/fixco_custom/views/stock_picking.xml
new file mode 100644
index 0000000..17ed3e0
--- /dev/null
+++ b/fixco_custom/views/stock_picking.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<odoo>
+ <data>
+ <record id="stock_picking_form_view_inherit" model="ir.ui.view">
+ <field name="name">Stock Picking</field>
+ <field name="model">stock.picking</field>
+ <field name="inherit_id" ref="stock.view_picking_form"/>
+ <field name="arch" type="xml">
+ <button name="action_confirm" position="before">
+ <button name="label_ginee"
+ string="Print Label Ginee"
+ type="object"
+ />
+ </button>
+
+ <group name="other_infos" position="after">
+ <group string="Shipping Information" name="shipping_infos">
+ <field name="order_reference" readonly="1"/>
+ <field name="provider_name" readonly="1"/>
+ <field name="tracking_number" readonly="1"/>
+ <field name="invoice_number" readonly="1"/>
+ <field name="pdf_label_url" readonly="1" widget="url"/>
+ </group>
+ </group>
+
+ <page name="note" position="after">
+ <page string="Check Product" name="check_product">
+ <field name="check_product_lines"/>
+ </page>
+ </page>
+ </field>
+ </record>
+
+ <record id="check_product_tree" model="ir.ui.view">
+ <field name="name">check.product.tree</field>
+ <field name="model">check.product</field>
+ <field name="arch" type="xml">
+ <tree editable="bottom" decoration-warning="status == 'Pending'" decoration-success="status == 'Done'">
+ <field name="code_product"/>
+ <field name="product_id"/>
+ <field name="quantity"/>
+ <field name="status" readonly="1"/>
+ </tree>
+ </field>
+ </record>
+ </data>
+</odoo> \ No newline at end of file