@@ -4,215 +4,171 @@ defmodule CraftplanWeb.InventoryLive.FormComponentMovement do
44
55 alias AshPhoenix.Form
66 alias Craftplan.Inventory
7+ alias Decimal , as: D
8+
9+ @ impl true
10+ def update ( assigns , socket ) do
11+ { :ok ,
12+ socket
13+ |> assign ( assigns )
14+ |> assign_new ( :mode , fn -> :add end )
15+ |> assign_new ( :calculated_new_total , fn -> nil end )
16+ |> assign_new ( :form , fn -> build_form ( assigns . current_user ) end ) }
17+ end
718
819 @ impl true
920 def render ( assigns ) do
1021 ~H"""
1122 < div >
1223 < . simple_form
1324 for = { @ form }
14- id = "movement-movment- form "
25+ id = "movement-form "
1526 phx-target = { @ myself }
1627 phx-change = "validate "
1728 phx-submit = "save "
1829 >
1930 < div class = "mb-4 " >
20- < div class = "mb-2 " >
21- < div class = "flex items-center " >
22- < button
23- type = "button "
24- phx-click = "toggle_adjustment_type "
25- phx-value-type = "set_total "
26- phx-target = { @ myself }
27- class = { [
28- @ adjustment_type == "set_total" && "bg-stone-200 text-stone-800" ,
29- @ adjustment_type != "set_total" && "bg-stone-50 text-stone-700 hover:bg-stone-100" ,
30- "flex cursor-pointer items-center rounded-l-md border-y border-l border-stone-300 px-3 py-1 text-xs font-medium disabled:cursor-default disabled:bg-stone-100 disabled:text-stone-400"
31- ] }
32- >
33- Set Total
34- </ button >
35- < button
36- type = "button "
37- phx-click = "toggle_adjustment_type "
38- phx-value-type = "add "
39- phx-target = { @ myself }
40- class = { [
41- @ adjustment_type == "add" && "bg-stone-200 text-stone-800" ,
42- @ adjustment_type != "add" && "bg-stone-50 text-stone-700 hover:bg-stone-100" ,
43- "flex cursor-pointer items-center border-y border-stone-300 px-3 py-1 text-xs font-medium disabled:cursor-default disabled:bg-stone-100 disabled:text-stone-400"
44- ] }
45- >
46- Add
47- </ button >
48- < button
49- type = "button "
50- phx-click = "toggle_adjustment_type "
51- phx-value-type = "subtract "
52- phx-target = { @ myself }
53- class = { [
54- @ adjustment_type == "subtract" && "bg-stone-200 text-stone-800" ,
55- @ adjustment_type != "subtract" && "bg-stone-50 text-stone-700 hover:bg-stone-100" ,
56- "flex cursor-pointer items-center rounded-r-md border-y border-r border-stone-300 px-3 py-1 text-xs font-medium disabled:cursor-default disabled:bg-stone-100 disabled:text-stone-400"
57- ] }
58- >
59- Subtract
60- </ button >
61- </ div >
31+ < div class = "inline-flex rounded-md border border-stone-300 " >
32+ < button
33+ type = "button "
34+ phx-click = "set_mode "
35+ phx-value-mode = "add "
36+ phx-target = { @ myself }
37+ class = { [
38+ "rounded-l-md px-4 py-1.5 text-xs font-medium transition-colors" ,
39+ @ mode == :add && "bg-stone-800 text-white" ,
40+ @ mode != :add && "bg-white text-stone-600 hover:bg-stone-50"
41+ ] }
42+ >
43+ Add
44+ </ button >
45+ < button
46+ type = "button "
47+ phx-click = "set_mode "
48+ phx-value-mode = "subtract "
49+ phx-target = { @ myself }
50+ class = { [
51+ "rounded-r-md border-l border-stone-300 px-4 py-1.5 text-xs font-medium transition-colors" ,
52+ @ mode == :subtract && "bg-stone-800 text-white" ,
53+ @ mode != :subtract && "bg-white text-stone-600 hover:bg-stone-50"
54+ ] }
55+ >
56+ Subtract
57+ </ button >
6258 </ div >
6359 </ div >
6460
65- < div :if = { @ adjustment_type == "add" || @ adjustment_type == "subtract" } >
66- < . focus_wrap id = "adjustment_quantity_focus_wrap " >
67- < . input
68- field = { @ form [ :quantity ] }
69- type = "number "
70- min = "0 "
71- label = "Quantity "
72- inline_label = { @ material . unit }
73- phx-change = "validate "
74- id = "adjustment_quantity "
75- />
76- </ . focus_wrap >
77- < . input field = { @ form [ :operation ] } type = "hidden " value = { @ adjustment_type } />
78- </ div >
79-
80- < div :if = { @ adjustment_type == "set_total" } >
81- < . input
82- field = { @ form [ :quantity ] }
83- type = "number "
84- min = "0 "
85- label = "New Total "
86- inline_label = { @ material . unit }
87- phx-change = "validate "
88- />
89- </ div >
61+ < . input
62+ field = { @ form [ :quantity ] }
63+ type = "number "
64+ min = "0 "
65+ step = "any "
66+ label = { if @ mode == :add , do: "Quantity to add" , else: "Quantity to subtract" }
67+ inline_label = { @ material . unit }
68+ id = "movement-quantity "
69+ />
9070
9171 < . input field = { @ form [ :reason ] } type = "textarea " label = "Notes " class = "mt-3 " />
9272 < . input field = { @ form [ :material_id ] } type = "hidden " value = { @ material . id } />
9373
94- < div class = "mt-4 rounded-md border border-stone-200 bg-stone-50 p-3 text-stone-700 " >
95- < div class = "text-sm " >
96- < span :if = { is_number ( @ calculated_new_total ) } >
97- < span class = "font-medium " > New stock will be:</ span >
98- < span class = "font-bold text-stone-900 " >
99- { format_amount ( @ material . unit , @ calculated_new_total ) }
100- </ span >
101- </ span >
102- < span :if = { ! is_number ( @ calculated_new_total ) } >
103- Enter a quantity to see the new stock level
74+ < div class = "mt-4 rounded-md border border-stone-200 bg-stone-50 p-3 text-sm text-stone-700 " >
75+ <%= if @ calculated_new_total do %>
76+ < span class = "font-medium " > New stock will be: </ span >
77+ < span class = { [
78+ "font-bold" ,
79+ negative? ( @ calculated_new_total ) && "text-red-600" ,
80+ ! negative? ( @ calculated_new_total ) && "text-stone-900"
81+ ] } >
82+ { format_preview ( @ material . unit , @ calculated_new_total ) }
10483 </ span >
105- </ div >
84+ <% else %>
85+ Enter a quantity to see the new stock level
86+ <% end %>
10687 </ div >
10788
10889 <: actions >
109- < . button variant = { :primary } phx-disable-with = "Saving... " >
110- Save
111- </ . button >
90+ < . button variant = { :primary } phx-disable-with = "Saving... " > Save</ . button >
11291 </: actions >
11392 </ . simple_form >
11493 </ div >
11594 """
11695 end
11796
11897 @ impl true
119- def update ( assigns , socket ) do
120- { :ok ,
121- socket
122- |> assign ( assigns )
123- |> assign ( adjustment_type: "set_total" )
124- |> assign ( :calculated_new_total , nil )
125- |> assign_form ( ) }
126- end
98+ def handle_event ( "set_mode" , % { "mode" => mode } , socket ) do
99+ mode = String . to_existing_atom ( mode )
100+ quantity = parse_quantity ( socket . assigns . form . params [ "quantity" ] )
101+ current_stock = socket . assigns . material . current_stock
127102
128- @ impl true
129- def handle_event ( "toggle_adjustment_type" , % { "type" => adjustment_type } , socket ) do
130- quantity = parse_integer ( socket . assigns . form . params [ "quantity" ] )
131- calculated_new_total = calculate_new_total ( socket , adjustment_type , quantity )
132-
133- { :noreply , assign ( socket , adjustment_type: adjustment_type , calculated_new_total: calculated_new_total ) }
103+ { :noreply ,
104+ socket
105+ |> assign ( :mode , mode )
106+ |> assign ( :calculated_new_total , new_total ( current_stock , mode , quantity ) ) }
134107 end
135108
136109 @ impl true
137- def handle_event ( "validate" , % { "movement" => movement_params } , socket ) do
138- quantity = parse_integer ( movement_params [ "quantity" ] )
139- calculated_new_total = calculate_new_total ( socket , socket . assigns . adjustment_type , quantity )
110+ def handle_event ( "validate" , % { "movement" => params } , socket ) do
111+ quantity = parse_quantity ( params [ "quantity" ] )
112+ current_stock = socket . assigns . material . current_stock
140113
141114 { :noreply ,
142115 socket
143- |> assign ( form: Form . validate ( socket . assigns . form , movement_params ) )
144- |> assign ( :calculated_new_total , calculated_new_total ) }
116+ |> assign ( :form , Form . validate ( socket . assigns . form , params ) )
117+ |> assign ( :calculated_new_total , new_total ( current_stock , socket . assigns . mode , quantity ) ) }
145118 end
146119
147- def handle_event ( "save" , % { "movement" => movement_params } , socket ) do
148- movement_params = prepare_movement_params ( movement_params , socket . assigns . adjustment_type )
120+ @ impl true
121+ def handle_event ( "save" , % { "movement" => params } , socket ) do
122+ params = sign_quantity ( params , socket . assigns . mode )
149123
150- case Form . submit ( socket . assigns . form , params: movement_params ) do
124+ case Form . submit ( socket . assigns . form , params: params ) do
151125 { :ok , movement } ->
152126 send ( self ( ) , { :saved , movement } )
153127
154128 { :noreply ,
155129 socket
156- |> put_flash ( :info , "Material #{ socket . assigns . form . source . type } d successfully " )
130+ |> put_flash ( :info , "Stock adjustment recorded " )
157131 |> push_patch ( to: socket . assigns . patch ) }
158132
159133 { :error , form } ->
160134 { :noreply , assign ( socket , :form , form ) }
161135 end
162136 end
163137
164- defp calculate_new_total ( socket , adjustment_type , quantity ) do
165- current_quantity = socket . assigns . material . current_stock
166-
167- case adjustment_type do
168- "add" ->
169- if is_integer ( quantity ) do
170- result = Decimal . add ( current_quantity , quantity )
171- Decimal . to_float ( result )
172- end
173-
174- "subtract" ->
175- if is_integer ( quantity ) do
176- result = Decimal . sub ( current_quantity , quantity )
177- result = if Decimal . compare ( result , 0 ) == :lt , do: Decimal . new ( 0 ) , else: result
178- Decimal . to_float ( result )
179- end
180-
181- "set_total" ->
182- if is_integer ( quantity ) , do: quantity * 1.0
138+ defp new_total ( _current_stock , _mode , nil ) , do: nil
139+ defp new_total ( current_stock , :add , quantity ) , do: D . add ( current_stock , quantity )
140+ defp new_total ( current_stock , :subtract , quantity ) , do: D . sub ( current_stock , quantity )
141+
142+ defp sign_quantity ( params , :subtract ) do
143+ case parse_quantity ( params [ "quantity" ] ) do
144+ nil -> params
145+ qty -> Map . put ( params , "quantity" , D . to_string ( D . negate ( qty ) ) )
183146 end
184147 end
185148
186- defp prepare_movement_params ( params , adjustment_type ) when adjustment_type in [ "add" , "subtract" ] do
187- quantity = String . to_integer ( params [ "quantity" ] || "0" )
188-
189- quantity =
190- case adjustment_type do
191- "subtract" -> - quantity
192- _ -> quantity
193- end
149+ defp sign_quantity ( params , :add ) , do: params
194150
195- Map . put ( params , "quantity" , to_string ( quantity ) )
151+ defp parse_quantity ( value ) when is_binary ( value ) and value != "" do
152+ case D . parse ( value ) do
153+ { d , "" } -> d
154+ _ -> nil
155+ end
196156 end
197157
198- defp prepare_movement_params ( params , _ ) , do: params
158+ defp parse_quantity ( _ ) , do: nil
199159
200- defp assign_form ( socket ) do
201- form =
202- Form . for_create ( Inventory.Movement , :adjust_stock ,
203- as: "movement" ,
204- actor: socket . assigns . current_user
205- )
160+ defp negative? ( value ) , do: D . compare ( value , D . new ( 0 ) ) == :lt
206161
207- assign ( socket , form: to_form ( form ) )
162+ defp format_preview ( unit , % D { } = value ) do
163+ format_amount ( unit , D . to_float ( value ) )
208164 end
209165
210- defp parse_integer ( value ) when is_binary ( value ) do
211- case Integer . parse ( value ) do
212- { int , _ } -> int
213- :error -> nil
214- end
166+ defp build_form ( current_user ) do
167+ Inventory.Movement
168+ |> Form . for_create ( :adjust_stock ,
169+ as: "movement" ,
170+ actor: current_user
171+ )
172+ |> to_form ( )
215173 end
216-
217- defp parse_integer ( _ ) , do: nil
218174end
0 commit comments