From b858358ffbdd14c9b56ac96f035bddccae4d872d Mon Sep 17 00:00:00 2001 From: Azka Nathan Date: Tue, 8 Jul 2025 08:58:46 +0700 Subject: skema bills and requisition --- fixco_custom/models/__init__.py | 1 + fixco_custom/models/account_move.py | 60 +++++++++++++++++++++++++++++++- fixco_custom/models/account_move_line.py | 19 ++++++++++ fixco_custom/models/purchase_order.py | 8 +++++ fixco_custom/models/requisition.py | 55 ++++++++++++++++------------- fixco_custom/views/account_move.xml | 52 +++++++++++++++++++++++++-- fixco_custom/views/purchase_order.xml | 2 ++ fixco_custom/views/requisition.xml | 2 -- 8 files changed, 168 insertions(+), 31 deletions(-) create mode 100644 fixco_custom/models/account_move_line.py diff --git a/fixco_custom/models/__init__.py b/fixco_custom/models/__init__.py index 4bc9e44..c12e9a7 100755 --- a/fixco_custom/models/__init__.py +++ b/fixco_custom/models/__init__.py @@ -19,3 +19,4 @@ from . import upload_ginee from . import purchase_order_line from . import purchase_order from . import requisition +from . import account_move_line diff --git a/fixco_custom/models/account_move.py b/fixco_custom/models/account_move.py index 0cdc22d..0a417cd 100644 --- a/fixco_custom/models/account_move.py +++ b/fixco_custom/models/account_move.py @@ -23,6 +23,14 @@ class AccountMove(models.Model): ('difaktur', 'Faktur Pajak')], string='Transaction Type' ) + purchase_vendor_bill_ids = fields.Many2many( + 'purchase.bill.union', + string='Auto-complete', + store=False, + readonly=True, + states={'draft': [('readonly', False)]}, + help="Auto-complete from multiple past bills / purchase orders.", + ) def action_post(self): @@ -30,4 +38,54 @@ class AccountMove(models.Model): for entry in self: entry.invoice_date = entry.picking_id.date_done - return res \ No newline at end of file + return res + + @api.onchange('purchase_vendor_bill_ids', 'purchase_id') + def _onchange_purchase_auto_complete(self): + """ Load from either multiple old purchase orders or vendor bills. """ + + vendor_bills = self.purchase_vendor_bill_ids.mapped('vendor_bill_id') + purchase_orders = self.purchase_vendor_bill_ids.mapped('purchase_order_id') + + for bill in vendor_bills: + self.invoice_vendor_bill_id = bill + self._onchange_invoice_vendor_bill() + + for po in purchase_orders: + self.purchase_id = po + + invoice_vals = po.with_company(po.company_id)._prepare_invoice() + invoice_vals['currency_id'] = self.line_ids and self.currency_id or invoice_vals.get('currency_id') + invoice_vals.pop('ref', None) + self.update(invoice_vals) + + po_lines = po.order_line - self.line_ids.mapped('purchase_line_id') + new_lines = self.env['account.move.line'] + sequence = max(self.line_ids.mapped('sequence')) + 1 if self.line_ids else 10 + + for line in po_lines.filtered(lambda l: not l.display_type): + line_vals = line._prepare_account_move_line(self) + line_vals.update({'sequence': sequence}) + new_line = new_lines.new(line_vals) + sequence += 1 + new_line.account_id = new_line._get_computed_account() + new_line._onchange_price_subtotal() + new_lines += new_line + + new_lines._onchange_mark_recompute_taxes() + + # Compute invoice_origin + origins = set(self.line_ids.mapped('purchase_line_id.order_id.name')) + self.invoice_origin = ', '.join(origins) + + # Compute ref + refs = self._get_invoice_reference() + self.ref = ', '.join(refs) + + # Compute payment_reference + if len(refs) == 1: + self.payment_reference = refs[0] + + self.purchase_id = False + self.purchase_vendor_bill_ids = [(5, 0, 0)] # clear after use + self._onchange_currency() diff --git a/fixco_custom/models/account_move_line.py b/fixco_custom/models/account_move_line.py new file mode 100644 index 0000000..cf4d5d7 --- /dev/null +++ b/fixco_custom/models/account_move_line.py @@ -0,0 +1,19 @@ +from odoo import models, api, fields, _ +from odoo.exceptions import AccessError, UserError, ValidationError + +class AccountMoveLine(models.Model): + _inherit = "account.move.line" + + qty_outstanding = fields.Float(string='Qty Outstanding', compute='_compute_qty_outstanding') + + def _compute_qty_outstanding(self): + for line in self: + qty_received = line.purchase_line_id.qty_received + qty_billed = line.purchase_line_id.qty_invoiced + line.qty_outstanding = qty_received - qty_billed + + @api.onchange('quantity') + def _onchange_quantity(self): + for line in self: + if line.quantity > line.qty_outstanding: + raise UserError(_("Quantity Tidak Boleh Melebihi Qty Outstanding")) diff --git a/fixco_custom/models/purchase_order.py b/fixco_custom/models/purchase_order.py index a623ae4..06f4ef1 100644 --- a/fixco_custom/models/purchase_order.py +++ b/fixco_custom/models/purchase_order.py @@ -17,6 +17,7 @@ _logger = logging.getLogger(__name__) class PurchaseOrder(models.Model): _inherit = 'purchase.order' + sale_order_id = fields.Many2one('sale.order', string='Sales Order') amount_discount = fields.Monetary( string='Total Discount', compute='_compute_amount_discount', @@ -28,6 +29,13 @@ class PurchaseOrder(models.Model): default=0.0, tracking=True ) + + source = fields.Selection([ + ('requisition', 'Requisition'), + ('reordering', 'Reordering'), + ('purchasing_job', 'Purchasing Job'), + ('manual', 'Manual') + ], string='Source', default='manual') @api.depends('order_line.price_total', 'biaya_lain_lain') def _amount_all(self): diff --git a/fixco_custom/models/requisition.py b/fixco_custom/models/requisition.py index 1734abd..b0800ba 100644 --- a/fixco_custom/models/requisition.py +++ b/fixco_custom/models/requisition.py @@ -51,24 +51,31 @@ class Requisition(models.Model): 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') + # 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, - 'partner_id': order_line.vendor_id.id, 'qty_purchase': order_line.product_uom_qty, - 'price_unit': order_line.purchase_price, - 'taxes_id': order_line.purchase_tax_id.id, - 'subtotal': order_line.purchase_price * order_line.product_uom_qty, - 'brand_id': order_line.product_id.x_manufacture.id + 'partner_id': vendor, + 'price_unit': price, + 'taxes_id': 14, + 'subtotal': price * order_line.product_uom_qty } self.env['requisition.line'].create([param]) @@ -90,8 +97,8 @@ class Requisition(models.Model): 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.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: @@ -133,7 +140,8 @@ class Requisition(models.Model): 'picking_type_id': 28, # indoteknik bandengan receipts 'date_order': current_time, 'sale_order_id': self.sale_order_id.id, - 'note_description': 'from Purchase Requisition' + # 'source': 'requisition', + # 'note_description': 'from Purchase Requisition' } domain = [ @@ -148,7 +156,7 @@ class Requisition(models.Model): # i start from zero (0) for i in range(page): new_po = self.env['purchase.order'].create([param_header]) - new_po.name = new_po.name + "/R/" + str(i + 1) + new_po.source = 'requisition' po_ids.append(new_po.id) lines = requisition_line.search( domain, @@ -169,8 +177,8 @@ class Requisition(models.Model): '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 + # 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, @@ -275,7 +283,6 @@ class RequisitionLine(models.Model): _inherit = ['mail.thread'] requisition_id = fields.Many2one('requisition', string='Ref', required=True, ondelete='cascade', index=True, copy=False) - brand_id = fields.Many2one('x_manufactures', string='Brand') 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') @@ -318,14 +325,12 @@ class RequisitionLine(models.Model): @api.onchange('product_id') def _onchange_product(self): for line in self: - line.brand_id = line.product_id.product_tmpl_id.x_manufacture.id purchase_pricelist = self.env['purchase.pricelist'].search([ ('product_id', '=', line.product_id.id) - ],order='count_trx_po desc, count_trx_po_vendor desc', limit=1) + ], limit=1) - price, taxes = line._get_valid_purchase_price(purchase_pricelist) - line.price_unit = price - line.taxes_id = taxes + # 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 @@ -354,10 +359,10 @@ class RequisitionLine(models.Model): message = f"Produk telah di-{action} :
" if action == 'Tambah': # message += f"
Product: {self.product_id.name}" - message += f"Product: {self.product_id.name}
Vendor: {self.partner_id.name}
Qty: {self.qty_purchase}
Price: {self.price_unit}
Tax: {self.tax_id.name}
Subtotal: {self.subtotal}
Brand: {self.brand_id.x_name}" + message += f"Product: {self.product_id.name}
Vendor: {self.partner_id.name}
Qty: {self.qty_purchase}
Price: {self.price_unit}
Tax: {self.tax_id.name}
Subtotal: {self.subtotal}
" elif action == 'Hapus': # message += f"
Deleted Product: {self.product_id.name}" - message += f"
Deleted Product: {self.product_id.name}
Vendor: {self.partner_id.name} Qty: {self.qty_purchase}
Price: {self.price_unit}
Tax: {self.tax_id.name}
Subtotal: {self.subtotal}
Brand: {self.brand_id.x_name}" + message += f"
Deleted Product: {self.product_id.name}
Vendor: {self.partner_id.name} Qty: {self.qty_purchase}
Price: {self.price_unit}
Tax: {self.tax_id.name}
Subtotal: {self.subtotal}
" else: # Updated for field, old_value in old_values.items(): new_value = self[field] diff --git a/fixco_custom/views/account_move.xml b/fixco_custom/views/account_move.xml index c77d046..1c55831 100644 --- a/fixco_custom/views/account_move.xml +++ b/fixco_custom/views/account_move.xml @@ -6,20 +6,66 @@ account.move - + + + + + + + + + + + + + Account Move + account.move + + + + 0 + hide + + {'column_invisible': [('parent.move_type', '!=', 'in_invoice')]} + + + + + diff --git a/fixco_custom/views/purchase_order.xml b/fixco_custom/views/purchase_order.xml index 1aa438c..de8c7bb 100644 --- a/fixco_custom/views/purchase_order.xml +++ b/fixco_custom/views/purchase_order.xml @@ -7,7 +7,9 @@ + + diff --git a/fixco_custom/views/requisition.xml b/fixco_custom/views/requisition.xml index 957113a..3648401 100644 --- a/fixco_custom/views/requisition.xml +++ b/fixco_custom/views/requisition.xml @@ -19,7 +19,6 @@ requisition.line - @@ -94,7 +93,6 @@ - -- cgit v1.2.3