stock dashboard
This commit is contained in:
parent
bdea2c6c3e
commit
bc4672e695
|
|
@ -11,8 +11,12 @@ class SamashtiDashboard(models.AbstractModel):
|
||||||
_name = 'samashti.board'
|
_name = 'samashti.board'
|
||||||
_description = "Samashti Dashboard"
|
_description = "Samashti Dashboard"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
def get_stock_moves_data(self, from_date,to_date):
|
def get_stock_moves_data(self, from_date, to_date):
|
||||||
|
# Convert string dates to datetime objects
|
||||||
from_date_obj = datetime.strptime(from_date, '%Y-%m-%d').date()
|
from_date_obj = datetime.strptime(from_date, '%Y-%m-%d').date()
|
||||||
to_date_obj = datetime.strptime(to_date, '%Y-%m-%d').date()
|
to_date_obj = datetime.strptime(to_date, '%Y-%m-%d').date()
|
||||||
|
|
||||||
|
|
@ -30,14 +34,42 @@ class SamashtiDashboard(models.AbstractModel):
|
||||||
fromDate = "'" + fields.Datetime.to_string(from_local) + "'"
|
fromDate = "'" + fields.Datetime.to_string(from_local) + "'"
|
||||||
toDate = "'" + fields.Datetime.to_string(to_local) + "'"
|
toDate = "'" + fields.Datetime.to_string(to_local) + "'"
|
||||||
|
|
||||||
|
# Get opening stock at the beginning of the from_date
|
||||||
|
opening_context = {
|
||||||
|
'lang': 'en_US',
|
||||||
|
'tz': 'Asia/Kolkata',
|
||||||
|
'to_date': fields.Datetime.to_string(from_local)
|
||||||
|
}
|
||||||
|
opening_stock = self.env['product.product'].with_context(**opening_context).search_read(
|
||||||
|
[('is_storable', '=', True)],
|
||||||
|
['id', 'name', 'qty_available']
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create opening stock dictionary for easier lookup
|
||||||
|
opening_dict = {product['id']: product['qty_available'] for product in opening_stock}
|
||||||
|
|
||||||
|
# Get closing stock at the end of the to_date
|
||||||
|
closing_context = {
|
||||||
|
'lang': 'en_US',
|
||||||
|
'tz': 'Asia/Kolkata',
|
||||||
|
'to_date': fields.Datetime.to_string(to_local)
|
||||||
|
}
|
||||||
|
closing_stock = self.env['product.product'].with_context(**closing_context).search_read(
|
||||||
|
[('is_storable', '=', True)],
|
||||||
|
['id', 'name', 'qty_available']
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create closing stock dictionary for easier lookup
|
||||||
|
closing_dict = {product['id']: product['qty_available'] for product in closing_stock}
|
||||||
|
|
||||||
sql = f"""
|
sql = f"""
|
||||||
WITH stock_movements AS (
|
WITH stock_movements AS (
|
||||||
SELECT
|
SELECT
|
||||||
pp.id as product_id,
|
pp.id as product_id,
|
||||||
pp.default_code AS product_code,
|
pp.default_code AS product_code,
|
||||||
pt.name AS product_name,
|
pt.name->>'en_US' AS product_name,
|
||||||
pc.name AS category,
|
pc.name AS category,
|
||||||
uom.name AS uom,
|
uom.name->>'en_US' AS uom,
|
||||||
-- Current Cost (from Valuation Layer)
|
-- Current Cost (from Valuation Layer)
|
||||||
COALESCE((
|
COALESCE((
|
||||||
SELECT SUM(svl.value) / NULLIF(SUM(svl.quantity), 0)
|
SELECT SUM(svl.value) / NULLIF(SUM(svl.quantity), 0)
|
||||||
|
|
@ -45,17 +77,6 @@ class SamashtiDashboard(models.AbstractModel):
|
||||||
WHERE svl.product_id = pp.id
|
WHERE svl.product_id = pp.id
|
||||||
), 0) AS current_cost,
|
), 0) AS current_cost,
|
||||||
|
|
||||||
-- Opening Stock: includes inventory adjustments before fromDate
|
|
||||||
SUM(CASE
|
|
||||||
WHEN sm.date < {fromDate} AND sl_dest.usage = 'internal' AND sl_src.usage != 'internal'
|
|
||||||
THEN sm.product_uom_qty * (uom.factor / sm_uom.factor)
|
|
||||||
WHEN sm.date < {fromDate} AND sl_src.usage = 'internal' AND sl_dest.usage != 'internal'
|
|
||||||
THEN -sm.product_uom_qty * (uom.factor / sm_uom.factor)
|
|
||||||
WHEN sm.date < {fromDate} AND (sl_src.usage = 'inventory' OR sl_dest.usage = 'inventory')
|
|
||||||
THEN sm.product_uom_qty * (uom.factor / sm_uom.factor)
|
|
||||||
ELSE 0
|
|
||||||
END) AS opening_stock,
|
|
||||||
|
|
||||||
-- Receipts: from supplier and inventory adjustments in date range
|
-- Receipts: from supplier and inventory adjustments in date range
|
||||||
SUM(CASE
|
SUM(CASE
|
||||||
WHEN sm.date BETWEEN {fromDate} AND {toDate}
|
WHEN sm.date BETWEEN {fromDate} AND {toDate}
|
||||||
|
|
@ -78,7 +99,7 @@ class SamashtiDashboard(models.AbstractModel):
|
||||||
SUM(CASE
|
SUM(CASE
|
||||||
WHEN sm.date BETWEEN {fromDate} AND {toDate}
|
WHEN sm.date BETWEEN {fromDate} AND {toDate}
|
||||||
AND sl_src.usage = 'internal'
|
AND sl_src.usage = 'internal'
|
||||||
AND sl_dest.usage = 'production' AND sm.production_id IS NOT NULL
|
AND sl_dest.usage = 'production'
|
||||||
THEN sm.product_uom_qty * (uom.factor / sm_uom.factor)
|
THEN sm.product_uom_qty * (uom.factor / sm_uom.factor)
|
||||||
ELSE 0
|
ELSE 0
|
||||||
END) AS consumption,
|
END) AS consumption,
|
||||||
|
|
@ -90,15 +111,7 @@ class SamashtiDashboard(models.AbstractModel):
|
||||||
AND sl_dest.usage = 'customer'
|
AND sl_dest.usage = 'customer'
|
||||||
THEN sm.product_uom_qty * (uom.factor / sm_uom.factor)
|
THEN sm.product_uom_qty * (uom.factor / sm_uom.factor)
|
||||||
ELSE 0
|
ELSE 0
|
||||||
END) AS dispatch,
|
END) AS dispatch
|
||||||
|
|
||||||
-- Inventory Adjustments: inventory moves within date range
|
|
||||||
SUM(CASE
|
|
||||||
WHEN sm.date BETWEEN {fromDate} AND {toDate}
|
|
||||||
AND (sl_src.usage = 'inventory' OR sl_dest.usage = 'inventory')
|
|
||||||
THEN sm.product_uom_qty * (uom.factor / sm_uom.factor)
|
|
||||||
ELSE 0
|
|
||||||
END) AS inventory_adjustments
|
|
||||||
|
|
||||||
FROM
|
FROM
|
||||||
stock_move sm
|
stock_move sm
|
||||||
|
|
@ -121,48 +134,72 @@ class SamashtiDashboard(models.AbstractModel):
|
||||||
sl_src.usage IN ('internal', 'supplier', 'production', 'customer', 'inventory') AND
|
sl_src.usage IN ('internal', 'supplier', 'production', 'customer', 'inventory') AND
|
||||||
sl_dest.usage IN ('internal', 'supplier', 'production', 'customer', 'inventory') AND
|
sl_dest.usage IN ('internal', 'supplier', 'production', 'customer', 'inventory') AND
|
||||||
sm.state = 'done' AND
|
sm.state = 'done' AND
|
||||||
pt.type = 'consu' AND pp.default_code IS NOT NULL
|
pt.type = 'consu' AND
|
||||||
|
pp.default_code IS NOT NULL AND
|
||||||
|
pp.active = TRUE
|
||||||
|
|
||||||
GROUP BY
|
GROUP BY
|
||||||
pp.id, pp.default_code, pt.name, pc.name, uom.name
|
pp.id, pp.default_code, pt.name, pc.name, uom.name
|
||||||
)
|
)
|
||||||
|
|
||||||
SELECT
|
SELECT
|
||||||
|
product_id,
|
||||||
product_code,
|
product_code,
|
||||||
product_name,
|
product_name,
|
||||||
category,
|
category,
|
||||||
uom,
|
uom,
|
||||||
current_cost,
|
current_cost,
|
||||||
GREATEST(COALESCE(opening_stock, 0), 0) AS opening_stock,
|
|
||||||
COALESCE(receipts, 0) AS receipts,
|
COALESCE(receipts, 0) AS receipts,
|
||||||
COALESCE(production, 0) AS production,
|
COALESCE(production, 0) AS production,
|
||||||
COALESCE(consumption, 0) AS consumption,
|
COALESCE(consumption, 0) AS consumption,
|
||||||
COALESCE(dispatch, 0) AS dispatch,
|
COALESCE(dispatch, 0) AS dispatch
|
||||||
COALESCE(inventory_adjustments, 0) AS inventory_adjustments,
|
|
||||||
-- Closing Stock = Opening + Receipts + Production - Consumption - Dispatch + Inventory Adjustments
|
|
||||||
COALESCE(opening_stock, 0) +
|
|
||||||
COALESCE(receipts, 0) +
|
|
||||||
COALESCE(production, 0) -
|
|
||||||
COALESCE(consumption, 0) -
|
|
||||||
COALESCE(dispatch, 0) +
|
|
||||||
COALESCE(inventory_adjustments, 0) AS closing_stock
|
|
||||||
FROM
|
FROM
|
||||||
stock_movements
|
stock_movements
|
||||||
ORDER BY
|
ORDER BY
|
||||||
product_name;
|
product_name;
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.env.cr.execute(sql)
|
self.env.cr.execute(sql)
|
||||||
data = self.env.cr.dictfetchall()
|
data = self.env.cr.dictfetchall()
|
||||||
if data:
|
|
||||||
for row in data:
|
# Process the data
|
||||||
row['product_name'] = '[' + row['product_code'] + '] ' + list(row['product_name'].values())[0] if row[
|
processed_data = []
|
||||||
'product_name'] else ''
|
for row in data:
|
||||||
row['uom'] = list(row['uom'].values())[0] if row['uom'] else '-'
|
product_id = row['product_id']
|
||||||
row['value'] = row['closing_stock'] * row['current_cost']
|
opening_qty = opening_dict.get(product_id, 0)
|
||||||
return data
|
closing_qty = closing_dict.get(product_id, 0)
|
||||||
else:
|
|
||||||
return []
|
# Calculate net movement during the period
|
||||||
|
net_movement = (
|
||||||
|
row['receipts'] +
|
||||||
|
row['production'] -
|
||||||
|
row['consumption'] -
|
||||||
|
row['dispatch']
|
||||||
|
)
|
||||||
|
|
||||||
|
# Verify calculation matches with actual closing stock
|
||||||
|
calculated_closing = opening_qty + net_movement
|
||||||
|
actual_closing = closing_qty
|
||||||
|
|
||||||
|
processed_row = {
|
||||||
|
'product_id': product_id,
|
||||||
|
'product_code': row['product_code'] or '',
|
||||||
|
'product_name': f"[{row['product_code']}] {row['product_name']}" if row['product_code'] else row[
|
||||||
|
'product_name'],
|
||||||
|
'category': row['category'] or '',
|
||||||
|
'uom': row['uom'] or '-',
|
||||||
|
'current_cost': row['current_cost'],
|
||||||
|
'opening_stock': opening_qty,
|
||||||
|
'receipts': row['receipts'],
|
||||||
|
'production': row['production'],
|
||||||
|
'consumption': row['consumption'],
|
||||||
|
'dispatch': row['dispatch'],
|
||||||
|
'calculated_closing_stock': calculated_closing,
|
||||||
|
'closing_stock': actual_closing,
|
||||||
|
'variance': actual_closing - calculated_closing,
|
||||||
|
'value': actual_closing * row['current_cost']
|
||||||
|
}
|
||||||
|
processed_data.append(processed_row)
|
||||||
|
return processed_data
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
def get_dashboard_cards_data(self):
|
def get_dashboard_cards_data(self):
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue