Skip to content

Commit 45bf6d4

Browse files
authored
Merge pull request #411 from Purple-Stock/staging
create a filter to tipo_estoque
2 parents 15b3e12 + 0432b79 commit 45bf6d4

File tree

3 files changed

+199
-28
lines changed

3 files changed

+199
-28
lines changed

app/controllers/stocks_controller.rb

Lines changed: 154 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,31 @@ def index
1010
@stocks_with_data = collection
1111
end
1212
format.csv do
13-
GenerateStocksCsvJob.perform_later(current_tenant, current_user.email)
14-
flash[:notice] = "CSV is being generated and will be emailed to you shortly."
15-
redirect_to stocks_path
13+
if params[:detailed]
14+
begin
15+
csv_data = generate_detailed_csv
16+
send_data csv_data,
17+
filename: "detailed_stock_calculations_#{Date.today}.csv",
18+
type: 'text/csv; charset=utf-8',
19+
disposition: 'attachment'
20+
rescue => e
21+
Rails.logger.error "Error generating CSV: #{e.message}"
22+
flash[:error] = "Erro ao gerar o CSV. Por favor, tente novamente."
23+
redirect_to stocks_path
24+
end
25+
else
26+
begin
27+
csv_data = generate_regular_csv
28+
send_data csv_data,
29+
filename: "stock_calculations_#{Date.today}.csv",
30+
type: 'text/csv; charset=utf-8',
31+
disposition: 'attachment'
32+
rescue => e
33+
Rails.logger.error "Error generating CSV: #{e.message}"
34+
flash[:error] = "Erro ao gerar o CSV. Por favor, tente novamente."
35+
redirect_to stocks_path
36+
end
37+
end
1638
end
1739
end
1840
end
@@ -66,13 +88,68 @@ def collection
6688
@default_situation_balance_filter = params['balance_situation']
6789
@default_sku_filter = params['sku']
6890
@default_period_filter = params['period'] || '30'
91+
@default_tipo_estoque = params['tipo_estoque'] || 'null'
6992

7093
stocks = Stock.where(account_id: current_tenant)
7194
.includes(:product, :balances)
7295
.only_positive_price(true)
7396
.filter_by_status(params['status'])
7497
.filter_by_total_balance_situation(params['balance_situation'])
7598
.filter_by_sku(params['sku'])
99+
.joins(:product)
100+
101+
# Calculate total balance for null tipo_estoque products
102+
default_warehouse_id = '9023657532' # ID for Estoque São Paulo Base
103+
null_tipo_stocks = Stock.where(account_id: current_tenant)
104+
.joins(:product)
105+
.includes(:product)
106+
.where(products: { tipo_estoque: nil })
107+
.includes(:balances)
108+
.to_a
109+
110+
Rails.logger.info "Starting balance calculations..."
111+
112+
@total_null_balance = null_tipo_stocks.sum do |stock|
113+
balance = stock.balances.find { |b| b.deposit_id.to_s == default_warehouse_id }
114+
next 0 unless balance
115+
116+
# Get the base physical balance
117+
physical_balance = balance.physical_balance
118+
119+
# Calculate the actual balance based on conditions
120+
actual_balance = if stock.discounted_warehouse_sku_id == "#{default_warehouse_id}_#{stock.product.sku}"
121+
# If discounted, subtract 1000 first
122+
discounted = physical_balance - 1000
123+
# If the discounted value is negative, don't count this stock
124+
discounted <= 0 ? 0 : discounted
125+
else
126+
# If not discounted, apply the regular rules
127+
if physical_balance >= 1000
128+
physical_balance - 1000
129+
elsif physical_balance <= 0
130+
0
131+
else
132+
physical_balance
133+
end
134+
end
135+
136+
Rails.logger.info "SKU: #{stock.product.sku} | Physical Balance: #{physical_balance} | Actual Balance: #{actual_balance} | Discounted?: #{stock.discounted_warehouse_sku_id.present?}"
137+
138+
actual_balance
139+
end
140+
141+
Rails.logger.info "Total balance calculated: #{@total_null_balance}"
142+
143+
stocks = case @default_tipo_estoque
144+
when 'null'
145+
stocks.where(products: { tipo_estoque: nil })
146+
when 'V'
147+
stocks.where(products: { tipo_estoque: 'V' })
148+
when 'P'
149+
stocks.where(products: { tipo_estoque: 'P' })
150+
else
151+
stocks
152+
end
76153

77154
@warehouses = Warehouse.where(account_id: current_tenant).pluck(:bling_id, :description).to_h
78155

@@ -86,8 +163,6 @@ def collection
86163
.group(:sku)
87164
.sum(:quantity)
88165

89-
default_warehouse_id = '9023657532' # ID for Estoque São Paulo Base
90-
91166
total_in_production = Stock.total_in_production_for_all
92167

93168
stocks_with_forecasts = stocks.map do |stock|
@@ -98,16 +173,20 @@ def collection
98173
virtual_balance = default_balance ? default_balance.virtual_balance : 0
99174
in_production = stock.total_in_production
100175

101-
# Calculate discounted balances
102-
discounted_physical_balance = stock.discounted_balance(default_balance) if default_balance
103-
discounted_virtual_balance = stock.discounted_virtual_balance(default_balance) if default_balance
176+
# Calculate discounted balances if default_balance exists
177+
discounted_physical_balance = default_balance ? stock.discounted_balance(default_balance) : 0
178+
discounted_virtual_balance = default_balance ? stock.discounted_virtual_balance(default_balance) : 0
104179

105-
# Calculate adjusted balances
106-
adjusted_physical_balance = stock.adjusted_balance(default_balance) if default_balance
107-
adjusted_virtual_balance = stock.adjusted_virtual_balance(default_balance) if default_balance
180+
# Calculate adjusted balances if default_balance exists
181+
adjusted_physical_balance = default_balance ? stock.adjusted_balance(default_balance) : 0
182+
adjusted_virtual_balance = default_balance ? stock.adjusted_virtual_balance(default_balance) : 0
108183

109-
# Calculate forecast: total_sold - adjusted_physical_balance
110-
total_forecast = [total_sold - adjusted_physical_balance, 0].max if adjusted_physical_balance
184+
# Calculate forecast only if we have adjusted_physical_balance
185+
total_forecast = if adjusted_physical_balance && !stock.product.composed?
186+
[total_sold - adjusted_physical_balance, 0].max
187+
else
188+
0
189+
end
111190

112191
[stock, {
113192
total_sold: total_sold,
@@ -123,7 +202,8 @@ def collection
123202
}]
124203
end
125204

126-
sorted_stocks = stocks_with_forecasts.sort_by { |_, data| -data[:total_sold] }
205+
# Sort by total_sold, but handle nil values
206+
sorted_stocks = stocks_with_forecasts.sort_by { |_, data| -(data[:total_sold] || 0) }
127207

128208
@pagy, @stocks_with_data = pagy_array(sorted_stocks, items: 20)
129209

@@ -140,4 +220,64 @@ def set_stock
140220
flash[:alert] = "Stock not found"
141221
redirect_to stocks_path
142222
end
223+
224+
def generate_detailed_csv
225+
require 'csv'
226+
227+
default_warehouse_id = '9023657532'
228+
stocks = Stock.where(account_id: current_tenant)
229+
.includes(:product, :balances)
230+
.joins(:product)
231+
.where(products: { tipo_estoque: nil })
232+
233+
CSV.generate(headers: true, col_sep: ';', encoding: 'UTF-8') do |csv|
234+
csv << [
235+
'SKU', 'Saldo Físico'
236+
]
237+
238+
stocks.each do |stock|
239+
begin
240+
balance = stock.balances.find { |b| b.deposit_id.to_s == default_warehouse_id }
241+
next unless balance
242+
243+
physical_balance = if stock.discounted_warehouse_sku_id == "#{default_warehouse_id}_#{stock.product.sku}"
244+
stock.discounted_balance(balance)
245+
else
246+
balance.physical_balance
247+
end
248+
249+
csv << [
250+
stock.product.sku,
251+
physical_balance || 0
252+
]
253+
rescue => e
254+
Rails.logger.error "Error processing stock #{stock.id}: #{e.message}"
255+
next
256+
end
257+
end
258+
end
259+
end
260+
261+
def generate_regular_csv
262+
CSV.generate(headers: true, col_sep: ';', encoding: 'UTF-8') do |csv|
263+
csv << ['id', 'SKU', 'Saldo Total', 'Saldo Virtual Total', 'Quantidade Vendida dos Últimos 30 dias',
264+
'Previsão para os Próximos 30 dias', 'Produto']
265+
266+
stocks = Stock.where(account_id: current_tenant)
267+
.includes(:product, :balances)
268+
.joins(:product)
269+
.where(products: { tipo_estoque: nil })
270+
.sort_by(&:calculate_basic_forecast)
271+
.reverse!
272+
273+
stocks.each do |stock|
274+
next if stock.total_balance.zero? && stock.count_sold.zero?
275+
276+
row = [stock.id, stock.sku, stock.total_balance, stock.total_virtual_balance, stock.count_sold,
277+
stock.calculate_basic_forecast,
278+
stock.product.name]
279+
csv << row
280+
end
281+
end
282+
end
143283
end

app/models/product.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,12 @@
77
# id :bigint not null, primary key
88
# active :boolean
99
# bar_code :string
10-
# componentes :jsonb
1110
# extra_sku :string
1211
# highlight :boolean
13-
# lancamento_estoque :string
1412
# name :string
1513
# number_of_pieces_per_fabric_roll :integer
1614
# price :float
1715
# sku :string
18-
# tipo_estoque :string
1916
# created_at :datetime not null
2017
# updated_at :datetime not null
2118
# account_id :integer
@@ -180,6 +177,9 @@ def component_quantities
180177

181178
# Check if product is composed of other products
182179
def composed?
180+
# Return false if tipo_estoque is nil
181+
return false if tipo_estoque.nil?
182+
183183
tipo_estoque == 'V' && componentes.present?
184184
end
185185

app/views/stocks/index.html.erb

Lines changed: 42 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,25 @@
11
<%- model_class = Stock -%>
2-
<div class="section-header row-cols-2">
3-
<div class="page-header">
4-
<h1><%= t('stocks.two') %></h1>
5-
</div>
6-
<div class="d-flex justify-content-end">
7-
<%= button_to "Gerar Previsão de Venda", stocks_path(format: "csv"), class: "btn btn-success", method: :get %>
2+
<div class="section-header">
3+
<div class="d-flex justify-content-between align-items-center w-100">
4+
<div class="d-flex align-items-center">
5+
<h1 class="me-3"><%= t('stocks.two') %></h1>
6+
<div class="total-balance-counter">
7+
<h4>Total de Estoque (Tipo Null): <span class="badge bg-primary"><%= number_with_delimiter(@total_null_balance) %></span></h4>
8+
</div>
9+
</div>
10+
<div class="d-flex gap-2">
11+
<%= button_to stocks_path(format: "csv", detailed: true),
12+
class: "btn btn-info",
13+
method: :get,
14+
form: { target: '_blank' } do %>
15+
<i class="fas fa-download"></i> Cálculo Detalhado
16+
<% end %>
17+
<%= button_to stocks_path(format: "csv"),
18+
class: "btn btn-success",
19+
method: :get do %>
20+
Gerar Previsão de Venda
21+
<% end %>
22+
</div>
823
</div>
924
</div>
1025

@@ -27,8 +42,19 @@
2742
options_for_select([['30 dias', '30'], ['15 dias', '15'], ['7 dias', '7']], @default_period_filter),
2843
class: "form-select" %>
2944
</div>
45+
46+
<div class="col-md-2">
47+
<%= select_tag "tipo_estoque",
48+
options_for_select([
49+
['Tipo Null', 'null'],
50+
['Tipo V', 'V'],
51+
['Tipo P', 'P'],
52+
['Todos', 'all']
53+
], @default_tipo_estoque),
54+
class: "form-select" %>
55+
</div>
3056

31-
<div class="col-md-3">
57+
<div class="col-md-2">
3258
<%= text_field_tag "sku", @default_sku_filter,
3359
placeholder: "Filtrar por SKU",
3460
class: "form-control" %>
@@ -65,7 +91,7 @@
6591
<% sao_paulo_base = stock.balances.find { |b| b.deposit_id.to_s == '9023657532' } %>
6692
<tr>
6793
<td>
68-
<% if sao_paulo_base %>
94+
<% if sao_paulo_base && !stock.product.composed? %>
6995
<%= check_box_tag "discount_#{stock.id}_#{sao_paulo_base.deposit_id}", 1,
7096
stock.discounted_warehouse_sku_id == "#{sao_paulo_base.deposit_id}_#{stock.product.sku}",
7197
class: 'discount-checkbox',
@@ -84,11 +110,16 @@
84110
</td>
85111
<td class="in-production"><%= data[:total_in_production] %></td>
86112
<td><%= data[:total_sold] %></td>
87-
<td class="forecast"><%= data[:total_forecast] %></td>
113+
<td class="forecast"><%= stock.product.composed? ? 'N/A' : data[:total_forecast] %></td>
88114
<td class="number-of-rolls">
89-
<%= (data[:total_forecast].to_f / (stock.product.number_of_pieces_per_fabric_roll || 1)).ceil %>
115+
<%= stock.product.composed? ? 'N/A' : (data[:total_forecast].to_f / (stock.product.number_of_pieces_per_fabric_roll || 1)).ceil %>
116+
</td>
117+
<td>
118+
<%= stock.product.name %>
119+
<% if stock.product.composed? %>
120+
<span class="badge bg-info">Composto</span>
121+
<% end %>
90122
</td>
91-
<td><%= stock.product.name %></td>
92123
<td>
93124
<%= link_to icon('fas fa-eye'), stock, title: t('show'), class: 'btn btn-info', data: { toggle: 'tooltip', turbo: false } %>
94125
</td>

0 commit comments

Comments
 (0)