From ca3455a3054e2953fb8b274ba5a4e65abcf3d257 Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Fri, 23 May 2025 14:20:57 +0700 Subject: Note Development --- fixco_custom/__manifest__.py | 3 + fixco_custom/models/__init__.py | 2 + fixco_custom/models/barcoding_product.py | 78 +++++++++ fixco_custom/models/detail_order.py | 183 +++++++++++--------- fixco_custom/models/product_product.py | 41 +++++ fixco_custom/models/sale.py | 1 + fixco_custom/models/stock_picking.py | 278 +++++++++++++++++++++++++++++- fixco_custom/security/ir.model.access.csv | 6 +- fixco_custom/views/barcoding_product.xml | 78 +++++++++ fixco_custom/views/product_product.xml | 17 ++ fixco_custom/views/sale_order.xml | 3 + fixco_custom/views/stock_picking.xml | 47 +++++ 12 files changed, 645 insertions(+), 92 deletions(-) create mode 100644 fixco_custom/models/barcoding_product.py create mode 100644 fixco_custom/models/product_product.py create mode 100644 fixco_custom/views/barcoding_product.xml create mode 100644 fixco_custom/views/product_product.xml create mode 100644 fixco_custom/views/stock_picking.xml 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 @@ + + + + + barcoding.product.tree + barcoding.product + + + + + + + + + + + barcoding.product.line.tree + barcoding.product.line + + + + + + + + + + + barcoding.product.form + barcoding.product + +
+ + + + + + + + + + + + + + + + +
+
+
+ + + barcoding.product.search.view + barcoding.product + + + + + + + + + Barcoding Product + ir.actions.act_window + barcoding.product + tree,form + + + +
+
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 @@ + + + + + Product Template + product.product + + + + + + + + + + + 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 @@ + + + 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 @@ + + + + + Stock Picking + stock.picking + + + + + + + + + + + + + + + + + + + + + + + + check.product.tree + check.product + + + + + + + + + + + \ No newline at end of file -- cgit v1.2.3