diff options
| author | Azka Nathan <darizkyfaz@gmail.com> | 2025-07-09 10:58:07 +0700 |
|---|---|---|
| committer | Azka Nathan <darizkyfaz@gmail.com> | 2025-07-09 10:58:07 +0700 |
| commit | f12beff2f1e4da1244e7a8e014e73e5e5023aa9d (patch) | |
| tree | d8787db1de0b4c717f2e73eb0087ceac5d1861f8 | |
| parent | e46be164dc1e419cdbfd0c0cf587fadc63beef3e (diff) | |
multiple qty
| -rwxr-xr-x | fixco_custom/__manifest__.py | 1 | ||||
| -rwxr-xr-x | fixco_custom/models/__init__.py | 1 | ||||
| -rwxr-xr-x | fixco_custom/models/product_product.py | 9 | ||||
| -rw-r--r-- | fixco_custom/models/purchase_order_line.py | 10 | ||||
| -rw-r--r-- | fixco_custom/models/requisition.py | 388 | ||||
| -rwxr-xr-x | fixco_custom/security/ir.model.access.csv | 4 | ||||
| -rwxr-xr-x | fixco_custom/views/product_product.xml | 4 | ||||
| -rw-r--r-- | fixco_custom/views/requisition.xml | 124 |
8 files changed, 22 insertions, 519 deletions
diff --git a/fixco_custom/__manifest__.py b/fixco_custom/__manifest__.py index 5697dc2..44ec3be 100755 --- a/fixco_custom/__manifest__.py +++ b/fixco_custom/__manifest__.py @@ -32,7 +32,6 @@ 'views/upload_ginee.xml', 'views/report_picking_list.xml', 'views/purchase_order.xml', - 'views/requisition.xml', 'views/shipment_line.xml', 'views/manage_stock.xml', 'views/automatic_purchase.xml', diff --git a/fixco_custom/models/__init__.py b/fixco_custom/models/__init__.py index df56efb..150e15f 100755 --- a/fixco_custom/models/__init__.py +++ b/fixco_custom/models/__init__.py @@ -18,7 +18,6 @@ from . import uangmuka_penjualan from . import upload_ginee from . import purchase_order_line from . import purchase_order -from . import requisition from . import account_move_line from . import manage_stock from . import automatic_purchase diff --git a/fixco_custom/models/product_product.py b/fixco_custom/models/product_product.py index a6bec4d..5117104 100755 --- a/fixco_custom/models/product_product.py +++ b/fixco_custom/models/product_product.py @@ -17,7 +17,16 @@ class ProductProduct(models.Model): 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') + qty_multiple = fields.Float('Multiple') + def check_multiple_qty(self, other_qty): + if self.qty_multiple > 0 and other_qty > 0: + multiple = self.qty_multiple + if other_qty % multiple != 0: + return True + else: + return False + @api.constrains('name', 'default_code') def constrains_product_type(self): self.type = 'product' diff --git a/fixco_custom/models/purchase_order_line.py b/fixco_custom/models/purchase_order_line.py index eee5a7a..c06ed4f 100644 --- a/fixco_custom/models/purchase_order_line.py +++ b/fixco_custom/models/purchase_order_line.py @@ -1,4 +1,5 @@ -from odoo import models, fields, api +from odoo import models, fields, api, _ +from odoo.exceptions import UserError class PurchaseOrderLine(models.Model): _inherit = 'purchase.order.line' @@ -19,6 +20,13 @@ class PurchaseOrderLine(models.Model): compute='_compute_discount_amount' ) + @api.constrains('product_qty', 'product_id') + def constrains_product_qty(self): + for line in self: + if line.product_id.check_multiple_qty(line.product_qty) == True: + raise UserError(f'Qty Product {line.product_id.display_name} tidak sesuai dengan kelipatan {line.product_id.qty_multiple}') + + @api.depends('price_unit', 'product_qty', 'discount') def _compute_discount_amount(self): for line in self: diff --git a/fixco_custom/models/requisition.py b/fixco_custom/models/requisition.py deleted file mode 100644 index b0800ba..0000000 --- a/fixco_custom/models/requisition.py +++ /dev/null @@ -1,388 +0,0 @@ -from odoo import models, fields, api, tools, _ -from odoo.exceptions import UserError -from datetime import datetime -import math -import logging - -_logger = logging.getLogger(__name__) - - -class RequisitionMatchPO(models.Model): - _name = 'v.requisition.match.po' - _auto = False - _rec_name = 'product_id' - - id = fields.Integer(string='ID') - product_id = fields.Many2one('product.product', string='Product') - qty_rpo = fields.Float(string='Qty RPO', help='Qty RPO yang sudah di PO namun SO masih Draft') - - # def init(self): - # tools.drop_view_if_exists(self.env.cr, self._table) - # self.env.cr.execute(""" - # create or replace view %s as - # select rl.product_id as id, rl.product_id, sum(rl.qty_purchase) as qty_rpo - # from requisition_line rl - # join requisition r on r.id = rl.requisition_id - # join requisition_purchase_match rpm on rpm.requisition_id = r.id - # join purchase_order po on po.id = rpm.order_id - # join sale_order so on so.id = r.sale_order_id - # where 1=1 - # and r.date_doc >= '2024-11-11' - # and po.state in ('done', 'purchase') - # and so.state in ('draft', 'sent') - # group by rl.product_id - # """ % self._table) - - -class Requisition(models.Model): - _name = 'requisition' - _order = 'id desc' - _inherit = ['mail.thread'] - _rec_name = 'number' - - number = fields.Char(string='Document No', index=True, copy=False, readonly=True) - date_doc = fields.Date(string='Date', help='isi tanggal hari ini') - description = fields.Char(string='Description', help='bebas isinya apa aja') - requisition_lines = fields.One2many('requisition.line', 'requisition_id', string='Lines', auto_join=True) - notification = fields.Char(string='Notification') - is_po = fields.Boolean(string='Is PO') - requisition_match = fields.One2many('requisition.purchase.match', 'requisition_id', string='Matches', auto_join=True) - sale_order_id = fields.Many2one('sale.order', string='SO', help='harus diisi nomor SO yang ingin digenerate') - sales_approve = fields.Boolean(string='Approval Status', tracking=3, copy=False) - merchandise_approve = fields.Boolean(string='Approval Status', tracking=3, copy=False) - - def get_price(self, product): - purchase_pricelist = self.env['purchase.pricelist'].search([ - ('product_id', '=', product) - ], limit=1) - if purchase_pricelist: - return purchase_pricelist.price, purchase_pricelist.vendor_id.id - - def generate_requisition_from_so(self): - state = ['done', 'sale'] - # if not self.sale_order_id: - # raise UserError('Sale Order Wajib Diisi dan Harus Draft') - # if self.sale_order_id.state in state: - # raise UserError('SO sudah Confirm, akan berakibat double Purchase melalui PJ') - # if not self.sale_order_id.order_line: - # raise UserError('Line SO masih kosong, harus diisi dulu') - for order_line in self.sale_order_id.order_line: - price, vendor = self.get_price(order_line.product_id.id) - param = { - 'requisition_id': self.id, - 'product_id': order_line.product_id.id, - 'qty_purchase': order_line.product_uom_qty, - 'partner_id': vendor, - 'price_unit': price, - 'taxes_id': 14, - 'subtotal': price * order_line.product_uom_qty - } - self.env['requisition.line'].create([param]) - - @api.model - def create(self, vals): - vals['number'] = self.env['ir.sequence'].next_by_code('requisition') or '0' - result = super(Requisition, self).create(vals) - return result - - def button_approve(self): - state = ['done', 'sale'] - if self.sale_order_id.state in state: - raise UserError('SO sudah Confirm, akan berakibat double Purchase melalui PJ') - if self.env.user.id not in [21, 19, 28]: - raise UserError('Hanya Rafly dan Darren Yang Bisa Approve') - if self.env.user.id == 19 or self.env.user.id == 28: - self.sales_approve = True - elif self.env.user.id == 21 or self.env.user.id == 28: - self.merchandise_approve = True - - def create_po_from_requisition(self): - # if not self.sales_approve and not self.merchandise_approve: - # raise UserError('Harus Di Approve oleh Darren atau Rafly') - if not self.requisition_lines: - raise UserError('Tidak ada Lines, belum bisa create PO') - if self.is_po: - raise UserError('Sudah pernah di create PO') - if not self.sale_order_id: - raise UserError('Tidak ada link dengan Sales Order, tidak bisa dihitung sebagai Plafon Qty di PO') - - vendor_ids = self.env['requisition.line'].read_group([ - ('requisition_id', '=', self.id), - ('partner_id', '!=', False) - ], fields=['partner_id'], groupby=['partner_id']) - - po_ids = [] - for vendor in vendor_ids: - result_po = self.create_po_by_vendor(vendor['partner_id'][0]) - po_ids += result_po - return { - 'name': _('Purchase Order'), - 'view_mode': 'tree,form', - 'res_model': 'purchase.order', - 'target': 'current', - 'type': 'ir.actions.act_window', - 'domain': [('id', 'in', po_ids)], - } - - def create_po_by_vendor(self, vendor_id): - current_time = datetime.now() - - PRODUCT_PER_PO = 20 - - requisition_line = self.env['requisition.line'] - - param_header = { - 'partner_id': vendor_id, - # 'partner_ref': self.sale_order_id.name, - 'currency_id': 12, - 'user_id': self.env.user.id, - 'company_id': 1, # indoteknik dotcom gemilang - 'picking_type_id': 28, # indoteknik bandengan receipts - 'date_order': current_time, - 'sale_order_id': self.sale_order_id.id, - # 'source': 'requisition', - # 'note_description': 'from Purchase Requisition' - } - - domain = [ - ('requisition_id', '=', self.id), - ('partner_id', '=', vendor_id), - ('qty_purchase', '>', 0) - ] - - products_len = requisition_line.search_count(domain) - page = math.ceil(products_len / PRODUCT_PER_PO) - po_ids = [] - # i start from zero (0) - for i in range(page): - new_po = self.env['purchase.order'].create([param_header]) - new_po.source = 'requisition' - po_ids.append(new_po.id) - lines = requisition_line.search( - domain, - offset=i * PRODUCT_PER_PO, - limit=PRODUCT_PER_PO - ) - tax = [22] - - for line in lines: - product = line.product_id - param_line = { - 'order_id' : new_po.id, - 'product_id': product.id, - 'product_qty': line.qty_purchase, - 'product_uom_qty': line.qty_purchase, - 'name': product.display_name, - 'price_unit': line.price_unit, - 'taxes_id': tax, - } - new_po_line = self.env['purchase.order.line'].create([param_line]) - # line.current_po_id = new_po.id - # line.current_po_line_id = new_po_line.id - - self.env['requisition.purchase.match'].create([{ - 'requisition_id': self.id, - 'order_id': new_po.id - }]) - self.is_po = True - - return po_ids - - # def create_po_from_requisition(self): - # if not self.requisition_lines: - # raise UserError('Tidak ada Lines, belum bisa create PO') - # if self.is_po: - # raise UserError('Sudah pernah di create PO') - # current_time = datetime.now() - # vendor_ids = self.env['requisition.line'].read_group([('requisition_id', '=', self.id), ('partner_id', '!=', False)], fields=['partner_id'], groupby=['partner_id']) - - # counter_po_number = 0 - # po_ids = [] - # for vendor in vendor_ids: - # param_header = { - # 'partner_id': vendor['partner_id'][0], - # # 'partner_ref': self.sale_order_id.name, - # 'currency_id': 12, - # 'user_id': self.env.user.id, - # 'company_id': 1, # indoteknik dotcom gemilang - # 'picking_type_id': 28, # indoteknik bandengan receipts - # 'date_order': current_time, - # 'sale_order_id': self.sale_order_id.id, - # 'note_description': 'from Purchase Requisition' - # } - # param_requisition_line = [ - # ('requisition_id', '=', self.id), - # ('partner_id', '=', vendor['partner_id'][0]), - # ('qty_purchase', '>', 0) - # ] - # # new_po = self.env['purchase.order'].create([param_header]) - # products_vendors = self.env['requisition.line'].search(, order='brand_id') - # count = brand_id = 0 - - # for product in products_vendors: - # if count > 200 or brand_id != product.brand_id.id: - # continue - - # count = 0 - # counter_po_number += 1 - # new_po = self.env['purchase.order'].create([param_header]) - # new_po.name = new_po.name + "/R/"+str(counter_po_number) - # self.env['requisition.purchase.match'].create([{ - # 'requisition_id': self.id, - # 'order_id': new_po.id - # }]) - # po_ids.append(new_po.id) - # self.env.cr.commit() - # # else: - # # new_po = self.env['purchase.order'].create([param_header]) - # brand_id = product.brand_id.id - # count += 10 - - # # qty_available = product.product_id.qty_onhand_bandengan + product.product_id.qty_incoming_bandengan - product.product_id.outgoing_qty - # # suggest = 'harus beli' - # # if qty_available > product.qty_purchase: - # # suggest = 'masih cukup' - - # tax = [22] - - # param_line = { - - # 'sequence': count, - # 'product_id': product.product_id.id, - # 'product_qty': product.qty_purchase, - # 'product_uom_qty': product.qty_purchase, - # 'price_unit': product.price_unit, - # 'taxes_id': tax, - # # 'qty_available_store': qty_available, - # # 'suggest': suggest, - # } - # new_line = self.env['purchase.order.line'].create([param_line]) - # if new_po: - # new_line.write({ - # 'order_id': new_po.id, - # }) - # product.current_po_id = new_po.id - # product.current_po_line_id = new_line.id - # _logger.info('Create PO Line %s' % product.product_id.name) - # # self.notification = self.notification + ' %s' % new_po.name - # self.is_po = True - # if po_ids: - # return { - # 'name': _('Purchase Order'), - # 'view_mode': 'tree,form', - # 'res_model': 'purchase.order', - # 'target': 'current', - # 'type': 'ir.actions.act_window', - # 'domain': [('id', 'in', po_ids)], - # } - -class RequisitionLine(models.Model): - _name = 'requisition.line' - _description = 'Requisition Line' - _order = 'requisition_id, id' - _inherit = ['mail.thread'] - - requisition_id = fields.Many2one('requisition', string='Ref', required=True, ondelete='cascade', index=True, copy=False) - product_id = fields.Many2one('product.product', string='Product', tracking=3,) - partner_id = fields.Many2one('res.partner', string='Vendor') - qty_purchase = fields.Float(string='Qty Purchase') - price_unit = fields.Float(string='Price') - tax_id = fields.Many2one('account.tax', help='isi tax pembelian include atau exclude', domain="[('type_tax_use', '=', 'purchase')]") - subtotal = fields.Float(string='Subtotal') - last_price = fields.Float(string='Last Price') - last_order_id = fields.Many2one('purchase.order', string='Last Order') - last_orderline_id = fields.Many2one('purchase.order.line', string='Last Order Line') - taxes_id = fields.Many2one('account.tax', string='Tax') - is_po = fields.Boolean(String='Is PO') - current_po_id = fields.Many2one('purchase.order', string='Current') - current_po_line_id = fields.Many2one('purchase.order.line', string='Current Line') - source = fields.Char(string='Source', help='data harga diambil darimana') - qty_available_store = fields.Float(string='Available') - suggest = fields.Char(string='Suggest') - - def _get_valid_purchase_price(self, purchase_price): - price = 0 - taxes = 24 - human_last_update = purchase_price.human_last_update or datetime.min - system_last_update = purchase_price.system_last_update or datetime.min - - #if purchase_price.taxes_product_id.type_tax_use == 'purchase': - price = purchase_price.product_price - taxes = purchase_price.taxes_product_id.id or 24 - - if system_last_update > human_last_update: - #if purchase_price.taxes_system_id.type_tax_use == 'purchase': - price = purchase_price.system_price - taxes = purchase_price.taxes_system_id.id or 24 - - return price, taxes - - @api.onchange('price_unit') - def _onchange_price_unit(self): - for line in self: - line.subtotal = line.price_unit * line.qty_purchase - - @api.onchange('product_id') - def _onchange_product(self): - for line in self: - purchase_pricelist = self.env['purchase.pricelist'].search([ - ('product_id', '=', line.product_id.id) - ], limit=1) - - # price, taxes = line._get_valid_purchase_price(purchase_pricelist) - line.price_unit = purchase_pricelist.price - line.partner_id = purchase_pricelist.vendor_id.id - - @api.model - def create(self, vals): - record = super(RequisitionLine, self).create(vals) - record._track_changes('Tambah') - return record - - def write(self, vals): - for record in self: - old_values = {field: record[field] for field in vals if field in record} - - result = super(RequisitionLine, self).write(vals) - - for record in self: - record._track_changes('Updated', old_values) - - return result - - def unlink(self): - for record in self: - record._track_changes('Hapus') - return super(RequisitionLine, self).unlink() - - def _track_changes(self, action, old_values=None): - message = f"Produk telah di-{action} : <br/>" - if action == 'Tambah': - # message += f"<br/> Product: {self.product_id.name}" - message += f"Product: {self.product_id.name} <br/> Vendor: {self.partner_id.name} <br/> Qty: {self.qty_purchase} <br/> Price: {self.price_unit} <br/> Tax: {self.tax_id.name} <br/> Subtotal: {self.subtotal} <br/>" - elif action == 'Hapus': - # message += f"<br/> Deleted Product: {self.product_id.name}" - message += f"<br/> Deleted Product: {self.product_id.name} <br/> Vendor: {self.partner_id.name} Qty: {self.qty_purchase} <br/> Price: {self.price_unit} <br/> Tax: {self.tax_id.name} <br/> Subtotal: {self.subtotal} <br/>" - else: # Updated - for field, old_value in old_values.items(): - new_value = self[field] - if old_value != new_value: - field_label = self._fields[field].string # Ambil nama label field - message += f"{field_label}: {old_value} -> {new_value}<br/>" - - if self.requisition_id: - self.requisition_id.message_post(body=message) - -class RequisitionPurchaseMatch(models.Model): - _name = 'requisition.purchase.match' - _order = 'requisition_id, id' - - requisition_id = fields.Many2one('requisition', string='Ref', required=True, ondelete='cascade', index=True, copy=False) - order_id = fields.Many2one('purchase.order', string='Purchase Order') - vendor = fields.Char(string='Vendor', compute='_compute_info_po') - total = fields.Float(string='Total', compute='_compute_info_po') - - def _compute_info_po(self): - for match in self: - match.vendor = match.order_id.partner_id.name - match.total = match.order_id.amount_total diff --git a/fixco_custom/security/ir.model.access.csv b/fixco_custom/security/ir.model.access.csv index 4f20dfe..cdfaa49 100755 --- a/fixco_custom/security/ir.model.access.csv +++ b/fixco_custom/security/ir.model.access.csv @@ -22,10 +22,6 @@ access_uangmuka_penjualan,access.uangmuka.penjualan,model_uangmuka_penjualan,,1, access_picking_line,access.picking.line,model_picking_line,,1,1,1,1 access_upload_ginee,access.upload.ginee,model_upload_ginee,,1,1,1,1 access_upload_ginee_line,access.upload.ginee.line,model_upload_ginee_line,,1,1,1,1 -access_requisition,access.requisition,model_requisition,,1,1,1,1 -access_requisition_line,access.requisition.line,model_requisition_line,,1,1,1,1 -access_requisition_purchase_match,access.requisition.purchase.match,model_requisition_purchase_match,,1,1,1,1 -access_v_requisition_match_po,access.v.requisition.match.po,model_v_requisition_match_po,,1,1,1,1 access_product_shipment_line,access.product.shipment.line,model_product_shipment_line,,1,1,1,1 access_manage_stock,access.manage.stock,model_manage_stock,,1,1,1,1 access_automatic_purchase_line,access.automatic.purchase.line,model_automatic_purchase_line,,1,1,1,1 diff --git a/fixco_custom/views/product_product.xml b/fixco_custom/views/product_product.xml index 7842909..6c468ba 100755 --- a/fixco_custom/views/product_product.xml +++ b/fixco_custom/views/product_product.xml @@ -7,10 +7,14 @@ <field name="inherit_id" ref="product.product_normal_form_view"/> <field name="arch" type="xml"> <field name="categ_id" position="after"> + <field name="qty_multiple" /> <field name="barcode_box" /> <field name="qty_pcs_box" /> <field name="qr_code_variant" widget="image" readonly="True"/> </field> + <field name="uom_po_id" position="after"> + <field name="qty_multiple" /> + </field> <notebook position="inside"> <page string="Bundling"> <field name="bundling_line_ids"/> diff --git a/fixco_custom/views/requisition.xml b/fixco_custom/views/requisition.xml deleted file mode 100644 index 3648401..0000000 --- a/fixco_custom/views/requisition.xml +++ /dev/null @@ -1,124 +0,0 @@ -<?xml version="1.0" encoding="UTF-8" ?> -<odoo> - <record id="requisition_tree" model="ir.ui.view"> - <field name="name">requisition.tree</field> - <field name="model">requisition</field> - <field name="arch" type="xml"> - <tree> - <field name="date_doc"/> - <field name="number"/> - <field name="description"/> - <field name="notification" readonly="1"/> - <field name="is_po" readonly="1"/> - </tree> - </field> - </record> - - <record id="requisition_line_tree" model="ir.ui.view"> - <field name="name">requisition.line.tree</field> - <field name="model">requisition.line</field> - <field name="arch" type="xml"> - <tree> - <field name="product_id"/> - <field name="partner_id"/> - <field name="qty_purchase"/> - <field name="price_unit"/> - <field name="tax_id"/> - <field name="subtotal"/> - <field name="source"/> - <field name="qty_available_store"/> - <field name="suggest"/> - </tree> - </field> - </record> - - <record id="requisition_purchase_match_tree" model="ir.ui.view"> - <field name="name">requisition.purchase.match.tree</field> - <field name="model">requisition.purchase.match</field> - <field name="arch" type="xml"> - <tree> - <field name="order_id" readonly="1"/> - <field name="vendor" readonly="1"/> - <field name="total" readonly="1"/> - </tree> - </field> - </record> - - <record id="requisition_form" model="ir.ui.view"> - <field name="name">requisition.form</field> - <field name="model">requisition</field> - <field name="arch" type="xml"> - <form> - <header> - <button name="button_approve" - string="Approve" - type="object" - class="mr-2 oe_highlight" - /> - </header> - <sheet string="Requisition"> - <div class="oe_button_box" name="button_box"/> - <group> - <group> - <field name="number"/> - <field name="date_doc"/> - <field name="sale_order_id"/> - <field name="description"/> - <field name="notification" readonly="1"/> - </group> - <group> - <div> - <button name="generate_requisition_from_so" - string="Create Line from SO" - type="object" - class="mr-2 oe_highlight" - /> - </div> - <div> - <button name="create_po_from_requisition" - string="Create PO" - type="object" - class="mr-2 oe_highlight" - /> - </div> - </group> - </group> - <notebook> - <page string="Lines"> - <field name="requisition_lines"> - <tree editable="line"> - <field name="product_id" required="1"/> - <field name="partner_id" required="1" /> - <field name="qty_purchase" required="1" /> - <field name="price_unit" required="1" /> - <field name="taxes_id" readonly="1" /> - <field name="subtotal" readonly="1" /> - </tree> - </field> - </page> - <page string="Matches"> - <field name="requisition_match"/> - </page> - </notebook> - </sheet> - <div class="oe_chatter"> - <field name="message_follower_ids" widget="mail_followers"/> - <field name="message_ids" widget="mail_thread"/> - </div> - </form> - </field> - </record> - - <record id="requisition_action" model="ir.actions.act_window"> - <field name="name">Requisition</field> - <field name="type">ir.actions.act_window</field> - <field name="res_model">requisition</field> - <field name="view_mode">tree,form</field> - </record> - - <menuitem id="menu_requisition" - name="Requisition" - action="requisition_action" - parent="purchase.menu_procurement_management" - sequence="300"/> -</odoo>
\ No newline at end of file |
