summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAzka Nathan <darizkyfaz@gmail.com>2025-07-28 11:22:49 +0700
committerAzka Nathan <darizkyfaz@gmail.com>2025-07-28 11:22:49 +0700
commitece8f2950b6c8fc8b65def8bc70d347fe7157f96 (patch)
treefdd85bcac414aa2ee968ee6f640b010f5d70289d
parentb48199f0ffefe2ccd43799c06f679f1db96c5334 (diff)
skema buffer stock, cr purchasing job, api cancel so
-rwxr-xr-xfixco_custom/__manifest__.py1
-rwxr-xr-xfixco_custom/models/__init__.py3
-rw-r--r--fixco_custom/models/automatic_purchase.py27
-rwxr-xr-xfixco_custom/models/detail_order.py17
-rw-r--r--fixco_custom/models/purchasing_job.py169
-rw-r--r--fixco_custom/models/reordering_rule.py94
-rwxr-xr-xfixco_custom/security/ir.model.access.csv6
-rwxr-xr-xfixco_custom/views/detail_order.xml2
-rw-r--r--fixco_custom/views/purchasing_job.xml20
-rw-r--r--fixco_custom/views/reordering_rule.xml75
-rwxr-xr-xfixco_custom/views/sale_order.xml13
11 files changed, 348 insertions, 79 deletions
diff --git a/fixco_custom/__manifest__.py b/fixco_custom/__manifest__.py
index 0bcdfaa..0624df9 100755
--- a/fixco_custom/__manifest__.py
+++ b/fixco_custom/__manifest__.py
@@ -39,6 +39,7 @@
'views/upload_bills.xml',
'views/brands_views.xml',
'views/product_public_category.xml',
+ 'views/reordering_rule.xml',
],
'demo': [],
'css': [],
diff --git a/fixco_custom/models/__init__.py b/fixco_custom/models/__init__.py
index e80acd8..297002e 100755
--- a/fixco_custom/models/__init__.py
+++ b/fixco_custom/models/__init__.py
@@ -25,4 +25,5 @@ from . import purchasing_job
from . import stock_move
from . import upload_bills
from . import brands
-from . import product_public_category \ No newline at end of file
+from . import product_public_category
+from . import reordering_rule \ No newline at end of file
diff --git a/fixco_custom/models/automatic_purchase.py b/fixco_custom/models/automatic_purchase.py
index d2d6830..037d65a 100644
--- a/fixco_custom/models/automatic_purchase.py
+++ b/fixco_custom/models/automatic_purchase.py
@@ -108,12 +108,19 @@ class AutomaticPurchase(models.Model):
action['views'] = [(self.env.ref('purchase.purchase_order_form').id, 'form')]
action['res_id'] = self.purchase_order_ids[0].id
return action
+
+ def delete_note_pj(self):
+ self.env['purchasing.job.note'].search([
+ ('product_id', 'in', self.automatic_purchase_lines.mapped('product_id').ids)
+ ]).unlink()
def create_purchase_orders(self):
self.ensure_one()
if not self.automatic_purchase_lines:
raise UserError(_("No purchase lines to process!"))
+ self.delete_note_pj()
+
if self.is_po:
raise UserError(_("Purchase order already created!"))
@@ -162,23 +169,8 @@ class AutomaticPurchase(models.Model):
self.is_po = True
return self.action_view_purchase_orders()
-
-
def _create_purchase_order(self, vendor, sequence, total_chunks, brand_id=None, category_id=None):
- order_name = f"{self.number} - {vendor.name}"
-
- if brand_id:
- brand = self.env['brands'].browse(brand_id)
- order_name += f" - {brand.name}"
-
- if category_id:
- cat = self.env['product.public.category'].browse(category_id)
- order_name += f" - {cat.name}"
-
- if total_chunks > 1:
- order_name += f" ({sequence}/{total_chunks})"
-
return self.env['purchase.order'].create({
'partner_id': vendor.id,
'origin': self.number,
@@ -187,7 +179,6 @@ class AutomaticPurchase(models.Model):
'currency_id': vendor.property_purchase_currency_id.id or self.env.company.currency_id.id,
'automatic_purchase_id': self.id,
'source': self.apo_type,
- 'name': order_name,
})
@@ -377,9 +368,9 @@ class AutomaticPurchaseLine(models.Model):
for line in self:
if line.product_id:
stock_move = self.env['stock.move'].search([
- ('product_id', '=', line.product_id.id),
+ ('product_id', '=', line.product_id.id),
('location_dest_id', '=', 5),
('location_id', '=', 55),
('state', 'in', ['waiting', 'confirmed', 'assigned', 'partially_available'])
- ], limit=1)
+ ])
line.qty_outgoing = sum(move.product_uom_qty for move in stock_move) \ No newline at end of file
diff --git a/fixco_custom/models/detail_order.py b/fixco_custom/models/detail_order.py
index 188424a..e8e87aa 100755
--- a/fixco_custom/models/detail_order.py
+++ b/fixco_custom/models/detail_order.py
@@ -276,13 +276,6 @@ class DetailOrder(models.Model):
# First check if a sale order with this reference already exists
existing_order = self.env['sale.order'].search([('order_reference', '=', order_id)], limit=1)
- if existing_order:
- # If order already exists, just update the references
- self.sale_id = existing_order.id
- self.execute_status = 'already_so'
- return # Exit early since we don't need to create anything
-
- # Handle cancelled orders
if order_status == 'CANCELLED':
external_order_id = json_data.get('data', [{}])[0].get('externalOrderId')
order_id = json_data.get('data', [{}])[0].get('orderId')
@@ -302,10 +295,12 @@ class DetailOrder(models.Model):
picking.action_cancel()
self.sale_id = existing_order.id
self.execute_status = 'cancelled_so_picking'
+ existing_order.action_cancel()
else:
existing_order.action_cancel()
self.sale_id = existing_order.id
self.execute_status = 'cancelled_so'
+
else:
# If no existing SO, create one, then cancel
data = self.prepare_data_so(json_data)
@@ -320,6 +315,14 @@ class DetailOrder(models.Model):
sale_order.action_cancel()
self.execute_status = 'cancelled_so_created'
+
+ return
+
+ if existing_order:
+ # If order already exists, just update the references
+ self.sale_id = existing_order.id
+ self.execute_status = 'already_so'
+ return # Exit early since we don't need to create anything
if order_status != 'PENDING_PAYMENT':
if order_status in ('PARTIALLY_PAID', 'PAID'):
diff --git a/fixco_custom/models/purchasing_job.py b/fixco_custom/models/purchasing_job.py
index 2c7138a..5d37c8c 100644
--- a/fixco_custom/models/purchasing_job.py
+++ b/fixco_custom/models/purchasing_job.py
@@ -6,11 +6,10 @@ class PurchasingJob(models.Model):
_name = 'purchasing.job'
_description = 'Procurement Monitoring by Product'
_auto = False
- _rec_name = 'product'
+ _rec_name = 'product_id'
id = fields.Integer(string='ID', readonly=True)
item_code = fields.Char(string='Item Code')
- product = fields.Char(string='Product')
onhand = fields.Float(string='On Hand')
incoming = fields.Float(string='Incoming')
outgoing = fields.Float(string='Outgoing')
@@ -18,6 +17,30 @@ class PurchasingJob(models.Model):
product_id = fields.Many2one('product.product', string='Product')
vendor_id = fields.Many2one('res.partner', string='Vendor')
brand_id = fields.Many2one('brands', string='Brand')
+ note = fields.Text(string='Note', compute='_compute_note')
+
+ def action_open_note_wizard(self):
+ self.ensure_one()
+ return {
+ 'name': 'Add/Edit Note',
+ 'type': 'ir.actions.act_window',
+ 'res_model': 'purchasing.job.note.wizard',
+ 'view_mode': 'form',
+ 'target': 'new',
+ 'context': {
+ 'default_product_id': self.product_id.id,
+ 'default_note': self.note,
+ }
+ }
+
+
+ def _compute_note(self):
+ for rec in self:
+ note_obj = self.env['purchasing.job.note'].search([
+ ('product_id', '=', rec.product_id.id)
+ ], limit=1)
+ rec.note = note_obj.note if note_obj else ''
+
def create_automatic_purchase(self):
if not self:
@@ -72,54 +95,102 @@ class PurchasingJob(models.Model):
def init(self):
tools.drop_view_if_exists(self.env.cr, self._table)
self.env.cr.execute("""
- CREATE OR REPLACE VIEW %s AS
- SELECT * FROM (
- SELECT
- row_number() OVER () AS id,
- a.item_code,
- a.product,
- a.onhand,
- a.incoming,
- a.outgoing,
- CASE
- WHEN (a.incoming + a.onhand) < a.outgoing THEN 'kurang'
- ELSE 'cukup'
- END AS action,
- a.product_id,
- pp2.vendor_id,
- b.id AS brand_id
- FROM (
- SELECT
- COALESCE(pp.default_code, pt.default_code) AS item_code,
- pt.name AS product,
- get_qty_onhand(pp.id::numeric) AS onhand,
- get_qty_incoming(pp.id::numeric) AS incoming,
- get_qty_outgoing(pp.id::numeric) AS outgoing,
- pp.id AS product_id,
- pp.brand_id
- FROM stock_move sm
- JOIN stock_picking sp ON sp.id = sm.picking_id
- JOIN product_product pp ON pp.id = sm.product_id
- JOIN product_template pt ON pt.id = pp.product_tmpl_id
- WHERE sp.state IN ('draft', 'waiting', 'confirmed', 'assigned')
- AND sp.name LIKE '%%OUT%%'
- AND sm.location_id = 55
- GROUP BY pp.id, pp.default_code, pt.default_code, pt.name, pp.brand_id
- ) a
- LEFT JOIN brands b ON b.id = a.brand_id
- LEFT JOIN LATERAL (
- SELECT vendor_id
- FROM purchase_pricelist
- WHERE product_id = a.product_id
- ORDER BY id ASC
- LIMIT 1
- ) pp2 ON true
- ) final
- WHERE final.action = 'kurang'
+ CREATE OR REPLACE VIEW %s
+ AS SELECT
+ row_number() OVER ()::integer AS id,
+ pmp.item_code,
+ max(pmp.onhand) AS onhand,
+ max(pmp.incoming) AS incoming,
+ max(pmp.outgoing) AS outgoing,
+ pmp.action,
+ pmp.product_id,
+ pmp.brand_id,
+ pmp.vendor_id
+ FROM v_procurement_monitoring_by_product pmp
+ WHERE pmp.action = 'kurang'::text
+ GROUP BY pmp.product_id, pmp.item_code, pmp.action, pmp.brand_id, pmp.vendor_id;
""" % self._table)
- super(PurchasingJob, self).init()
-
-
+ # def init(self):
+ # tools.drop_view_if_exists(self.env.cr, self._table)
+ # self.env.cr.execute("""
+ # CREATE OR REPLACE VIEW %s AS
+ # SELECT * FROM (
+ # SELECT
+ # row_number() OVER () AS id,
+ # a.item_code,
+ # a.product,
+ # a.onhand,
+ # a.incoming,
+ # a.outgoing,
+ # CASE
+ # WHEN (a.incoming + a.onhand) < a.outgoing THEN 'kurang'
+ # ELSE 'cukup'
+ # END AS action,
+ # a.product_id,
+ # pp2.vendor_id,
+ # b.id AS brand_id
+ # FROM (
+ # SELECT
+ # COALESCE(pp.default_code, pt.default_code) AS item_code,
+ # pt.name AS product,
+ # get_qty_onhand(pp.id::numeric) AS onhand,
+ # get_qty_incoming(pp.id::numeric) AS incoming,
+ # get_qty_outgoing(pp.id::numeric) AS outgoing,
+ # pp.id AS product_id,
+ # pp.brand_id
+ # FROM stock_move sm
+ # JOIN stock_picking sp ON sp.id = sm.picking_id
+ # JOIN product_product pp ON pp.id = sm.product_id
+ # JOIN product_template pt ON pt.id = pp.product_tmpl_id
+ # WHERE sp.state IN ('draft', 'waiting', 'confirmed', 'assigned')
+ # AND sp.name LIKE '%%OUT%%'
+ # AND sm.location_id = 55
+ # GROUP BY pp.id, pp.default_code, pt.default_code, pt.name, pp.brand_id
+ # ) a
+ # LEFT JOIN brands b ON b.id = a.brand_id
+ # LEFT JOIN LATERAL (
+ # SELECT vendor_id
+ # FROM purchase_pricelist
+ # WHERE product_id = a.product_id
+ # ORDER BY id ASC
+ # LIMIT 1
+ # ) pp2 ON true
+ # ) final
+ # WHERE final.action = 'kurang'
+ # """ % self._table)
+
+ # super(PurchasingJob, self).init()
+
+class PurchasingJobNote(models.Model):
+ _name = 'purchasing.job.note'
+ _description = 'Note for Product in Purchasing Job'
+
+ product_id = fields.Many2one('product.product', string='Product', required=True, ondelete='cascade', index=True)
+ note = fields.Text(string='Note')
+ user_id = fields.Many2one('res.users', string='Created by', default=lambda self: self.env.user)
+
+
+
+class PurchasingJobNoteWizard(models.TransientModel):
+ _name = 'purchasing.job.note.wizard'
+ _description = 'Add/Edit Note for Product'
+
+ product_id = fields.Many2one('product.product', string='Product', required=True)
+ note = fields.Text(string='Note')
+
+ def action_confirm(self):
+ self.ensure_one()
+ existing = self.env['purchasing.job.note'].search([
+ ('product_id', '=', self.product_id.id)
+ ], limit=1)
+ if existing:
+ existing.note = self.note
+ else:
+ self.env['purchasing.job.note'].create({
+ 'product_id': self.product_id.id,
+ 'note': self.note,
+ })
+ return {'type': 'ir.actions.act_window_close'}
diff --git a/fixco_custom/models/reordering_rule.py b/fixco_custom/models/reordering_rule.py
new file mode 100644
index 0000000..dad07b0
--- /dev/null
+++ b/fixco_custom/models/reordering_rule.py
@@ -0,0 +1,94 @@
+from odoo import models, fields, api
+from odoo import tools
+from odoo.exceptions import UserError
+
+class ReorderingRule(models.Model):
+ _name = 'reordering.rule'
+ _description = 'Buffer Stock'
+ _auto = False # Karena ini dari SQL view
+
+ product_id = fields.Many2one('product.product', string='Product', readonly=True)
+ min_stock = fields.Float(string='Min Stock', readonly=True)
+ buffer_stock = fields.Float(string='Buffer Stock', readonly=True)
+ vendor_id = fields.Many2one('res.partner', string='Vendor', readonly=True)
+ qty_onhand = fields.Float(string='Qty Onhand', readonly=True)
+ qty_incoming = fields.Float(string='Qty Incoming')
+
+ def create_automatic_purchase(self):
+ if not self:
+ raise UserError("No stock records selected.")
+
+ automatic_purchase = self.env['automatic.purchase'].create({
+ 'apo_type': 'reordering',
+ })
+
+ lines_to_create = []
+
+ for stock in self:
+ location_id = 55
+
+ quant_records = self.env['stock.quant'].search([
+ ('product_id', '=', stock.product_id.id),
+ ('location_id', '=', location_id)
+ ])
+
+ total_available = quant_records.quantity or 0.0
+
+ qty_incoming = stock.product_id.incoming_qty or 0.0
+
+ qty_purchase = stock.buffer_stock - (total_available + qty_incoming)
+
+ qty_purchase = max(qty_purchase, 0.0)
+
+ pricelist = self.env['purchase.pricelist'].search([
+ ('product_id', '=', stock.product_id.id),
+ ('vendor_id', '=', stock.vendor_id.id)
+ ], limit=1)
+
+ price = pricelist.price if pricelist else 0.0
+ subtotal = qty_purchase * price
+
+ lines_to_create.append({
+ 'automatic_purchase_id': automatic_purchase.id,
+ 'product_id': stock.product_id.id,
+ 'qty_purchase': qty_purchase,
+ 'qty_min': stock.min_stock,
+ 'qty_buffer': stock.buffer_stock,
+ 'partner_id': stock.vendor_id.id,
+ 'taxes_id': stock.vendor_id.tax_id.id if stock.vendor_id.tax_id else False,
+ 'price': price,
+ 'product_public_category_id': stock.product_id.product_public_category_id.id,
+ 'brand_id': stock.product_id.brand_id.id,
+ })
+
+ self.env['automatic.purchase.line'].create(lines_to_create)
+
+ return {
+ 'type': 'ir.actions.act_window',
+ 'res_model': 'automatic.purchase',
+ 'view_mode': 'form',
+ 'res_id': automatic_purchase.id,
+ 'target': 'current',
+ }
+
+ def init(self):
+ tools.drop_view_if_exists(self.env.cr, self._table)
+ self.env.cr.execute(f"""
+ CREATE OR REPLACE VIEW {self._table} AS (
+ SELECT
+ vmsbm.id AS id,
+ vmsbm.product_id,
+ vmsbm.min_stock,
+ vmsbm.buffer_stock,
+ vmsbm.vendor_id,
+ COALESCE(SUM(sq.quantity), 0.0) AS qty_onhand,
+ vmsbm.incoming_qty AS qty_incoming
+ FROM
+ view_manage_stock_below_min vmsbm
+ LEFT JOIN
+ stock_quant sq ON sq.product_id = vmsbm.product_id AND sq.location_id = 55
+
+ GROUP BY
+ vmsbm.id, vmsbm.product_id, vmsbm.min_stock, vmsbm.buffer_stock, vmsbm.vendor_id, vmsbm.incoming_qty
+ )
+ """)
diff --git a/fixco_custom/security/ir.model.access.csv b/fixco_custom/security/ir.model.access.csv
index 4b22abd..66ecf82 100755
--- a/fixco_custom/security/ir.model.access.csv
+++ b/fixco_custom/security/ir.model.access.csv
@@ -29,4 +29,8 @@ access_automatic_purchase,access.automatic.purchase,model_automatic_purchase,,1,
access_purchasing_job,access.purchasing.job,model_purchasing_job,,1,1,1,1
access_upload_bills,access.upload.bills,model_upload_bills,,1,1,1,1
access_upload_bills_line,access.upload.bills.line,model_upload_bills_line,,1,1,1,1
-access_brands,access.brands,model_brands,,1,1,1,1 \ No newline at end of file
+access_brands,access.brands,model_brands,,1,1,1,1
+access_reordering_rule,access.reordering.rule,model_reordering_rule,,1,1,1,1
+access_purchasing_job_note,access.purchasing.job.note,model_purchasing_job_note,,1,1,1,1
+access_purchasing_job_note_wizard,access.purchasing.job.note.wizard,model_purchasing_job_note_wizard,,1,1,1,1
+
diff --git a/fixco_custom/views/detail_order.xml b/fixco_custom/views/detail_order.xml
index 228f9e4..90e2e6b 100755
--- a/fixco_custom/views/detail_order.xml
+++ b/fixco_custom/views/detail_order.xml
@@ -27,12 +27,10 @@
<button name="execute_queue"
string="Create Detail Order"
type="object"
- attrs="{'invisible': [('detail_order', '!=', True)]}"
/>
<button name="execute_queue_detail"
string="Create SO"
type="object"
- attrs="{'invisible': [('sale_id', '!=', True), ('detail_order', '!=', False)]}"
/>
</header>
<sheet>
diff --git a/fixco_custom/views/purchasing_job.xml b/fixco_custom/views/purchasing_job.xml
index 7a15160..cb5126a 100644
--- a/fixco_custom/views/purchasing_job.xml
+++ b/fixco_custom/views/purchasing_job.xml
@@ -7,13 +7,15 @@
<field name="arch" type="xml">
<tree string="Procurement Monitoring by Product" create="false" delete="false">
<field name="item_code"/>
- <field name="product"/>
+ <field name="product_id"/>
<field name="brand_id"/>
<field name="vendor_id"/>
<field name="onhand"/>
<field name="incoming"/>
<field name="outgoing"/>
<field name="action"/>
+ <field name="note" widget="text"/>
+ <button name="action_open_note_wizard" string="Add Note" type="object" class="btn-primary"/>
</tree>
</field>
</record>
@@ -29,6 +31,22 @@
</field>
</record>
+ <record id="view_purchasing_job_note_wizard_form" model="ir.ui.view">
+ <field name="name">purchasing.job.note.wizard.form</field>
+ <field name="model">purchasing.job.note.wizard</field>
+ <field name="arch" type="xml">
+ <form string="Note for Product">
+ <group>
+ <field name="note" placeholder="Masukkan catatan kamu..."/>
+ </group>
+ <footer>
+ <button string="Confirm" type="object" name="action_confirm" class="btn-primary"/>
+ <button string="Cancel" class="btn-secondary" special="cancel"/>
+ </footer>
+ </form>
+ </field>
+ </record>
+
<record id="action_create_automatic_purchase_purchasing_job" model="ir.actions.server">
<field name="name">Create Automatic Purchase</field>
<field name="model_id" ref="model_purchasing_job"/>
diff --git a/fixco_custom/views/reordering_rule.xml b/fixco_custom/views/reordering_rule.xml
new file mode 100644
index 0000000..dbc3e13
--- /dev/null
+++ b/fixco_custom/views/reordering_rule.xml
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<odoo>
+ <record id="reordering_rule_tree" model="ir.ui.view">
+ <field name="name">reordering.rule.tree</field>
+ <field name="model">reordering.rule</field>
+ <field name="arch" type="xml">
+ <tree>
+ <field name="product_id"/>
+ <field name="min_stock"/>
+ <field name="buffer_stock"/>
+ <field name="qty_onhand"/>
+ <field name="qty_incoming"/>
+ <field name="vendor_id"/>
+ </tree>
+ </field>
+ </record>
+
+ <record id="reordering_rule_form" model="ir.ui.view">
+ <field name="name">reordering.rule.form</field>
+ <field name="model">reordering.rule</field>
+ <field name="arch" type="xml">
+ <form>
+ <sheet>
+ <group>
+ <group>
+ <field name="product_id"/>
+ <field name="min_stock"/>
+ <field name="buffer_stock"/>
+ <field name="qty_onhand"/>
+ <field name="qty_incoming"/>
+ <field name="vendor_id"/>
+ </group>
+ </group>
+ </sheet>
+ </form>
+ </field>
+ </record>
+
+ <record id="view_reordering_rule_filter" model="ir.ui.view">
+ <field name="name">reordering.rule.list.select</field>
+ <field name="model">reordering.rule</field>
+ <field name="priority" eval="15"/>
+ <field name="arch" type="xml">
+ <search string="Search">
+ <field name="product_id"/>
+ </search>
+ </field>
+ </record>
+
+ <record id="action_create_automatic_purchase_reordering_rule" model="ir.actions.server">
+ <field name="name">Create Automatic Purchase</field>
+ <field name="model_id" ref="model_reordering_rule"/>
+ <field name="binding_model_id" ref="model_reordering_rule"/>
+ <field name="state">code</field>
+ <field name="code">
+ action = records.create_automatic_purchase()
+ </field>
+ </record>
+
+ <record id="reordering_rule_action" model="ir.actions.act_window">
+ <field name="name">Buffer Stock</field>
+ <field name="type">ir.actions.act_window</field>
+ <field name="res_model">reordering.rule</field>
+ <field name="search_view_id" ref="view_reordering_rule_filter"/>
+ <field name="view_mode">tree,form</field>
+ </record>
+
+ <menuitem
+ action="reordering_rule_action"
+ id="reordering_rule"
+ parent="stock.menu_stock_warehouse_mgmt"
+ name="Buffer Stock"
+ sequence="1"
+ />
+</odoo>
diff --git a/fixco_custom/views/sale_order.xml b/fixco_custom/views/sale_order.xml
index eb609e9..137ded1 100755
--- a/fixco_custom/views/sale_order.xml
+++ b/fixco_custom/views/sale_order.xml
@@ -27,6 +27,19 @@
</data>
<data>
+ <record id="view_sales_order_filter_inherit" model="ir.ui.view">
+ <field name="name">sale.order.list.select.inherit.invoice_mp</field>
+ <field name="model">sale.order</field>
+ <field name="inherit_id" ref="sale.view_sales_order_filter"/>
+ <field name="arch" type="xml">
+ <xpath expr="//search" position="inside">
+ <field name="invoice_mp" string="Invoice Marketplace" filter_domain="[('invoice_mp', 'ilike', self)]"/>
+ </xpath>
+ </field>
+ </record>
+ </data>
+
+ <data>
<record id="sale_order_multi_create_invoices_ir_actions_server" model="ir.actions.server">
<field name="name">Multi Invoices</field>
<field name="model_id" ref="sale.model_sale_order"/>