moduleUtil->isModuleInstalled('Manufacturing')) { abort(403, 'Unauthorized action.'); } return $this->contextService->getBusinessId(request()); } protected function recipeQuery(int $business_id) { return MfgRecipe::join('variations as v', 'mfg_recipes.variation_id', '=', 'v.id') ->join('product_variations as pv', 'v.product_variation_id', '=', 'pv.id') ->join('products as p', 'v.product_id', '=', 'p.id') ->leftJoin('categories as c', 'p.category_id', '=', 'c.id') ->leftJoin('categories as sc', 'p.sub_category_id', '=', 'sc.id') ->join('units as u', 'p.unit_id', '=', 'u.id') ->where('p.business_id', $business_id) ->select( 'mfg_recipes.*', DB::raw('IF(p.type="variable", CONCAT(p.name, " - ", pv.name, " - ", v.name, " (", v.sub_sku, ")"), CONCAT(p.name, " (", v.sub_sku, ")")) as recipe_name'), 'u.short_name as unit_name', 'c.name as category', 'sc.name as sub_category' ); } public function recipes(Request $request) { $business_id = $this->ensureManufacturing(); $query = $this->recipeQuery($business_id)->with(['ingredients', 'sub_unit']); if ($request->filled('search')) { $keyword = $request->input('search'); $query->where(function ($q) use ($keyword) { $q->where('p.name', 'like', "%{$keyword}%") ->orWhere('v.sub_sku', 'like', "%{$keyword}%"); }); } $items = $query->orderByDesc('mfg_recipes.id')->paginate($request->input('per_page', 25)); $items->getCollection()->transform(function ($row) { $row->recipe_total = $this->mfgUtil->getRecipeTotal($row); $row->unit_cost = $row->total_quantity > 0 ? $row->recipe_total / $row->total_quantity : 0; return $row; }); return $this->paginated($items); } public function showRecipe(Request $request, $id) { $business_id = $this->ensureManufacturing(); $recipe = MfgRecipe::with(['variation', 'variation.product', 'variation.product_variation', 'sub_unit', 'variation.product.unit']) ->findOrFail($id); $variation = Variation::join('products as p', 'p.id', '=', 'variations.product_id') ->where('p.business_id', $business_id) ->where('variations.id', $recipe->variation_id) ->exists(); if (! $variation) { abort(404); } $ingredients = $this->mfgUtil->getIngredientDetails($recipe, $business_id); return $this->success([ 'recipe' => $recipe, 'ingredients' => $ingredients, 'recipe_total' => $this->mfgUtil->getRecipeTotal($recipe), 'production_cost' => $this->mfgUtil->getProductionCost($recipe), ]); } public function recipeByVariation(Request $request, $variation_id) { $business_id = $this->ensureManufacturing(); $variation = Variation::join('products as p', 'p.id', '=', 'variations.product_id') ->join('product_variations as pv', 'pv.id', '=', 'variations.product_variation_id') ->join('units as u', 'u.id', '=', 'p.unit_id') ->where('p.business_id', $business_id) ->select('p.name as product_name', 'p.type as product_type', 'variations.*', 'pv.name as product_variation_name', 'p.unit_id', 'u.short_name as unit_name') ->findOrFail($variation_id); $with = [ 'ingredients' => fn ($q) => $q->orderBy('sort_order', 'asc'), 'ingredients.variation', 'ingredients.variation.product', 'ingredients.variation.product.unit', 'ingredients.variation.product_variation', 'ingredients.sub_unit', 'ingredients.ingredient_group', ]; $recipe = MfgRecipe::where('variation_id', $variation_id)->with($with)->first(); $copy_recipe = null; if (empty($recipe) && $request->filled('copy_recipe_id')) { $copy_recipe = MfgRecipe::with($with)->find($request->input('copy_recipe_id')); } $ingredients = []; $total_production_cost = 0; if (! empty($recipe) || ! empty($copy_recipe)) { $ingredients_obj = ! empty($copy_recipe) ? $copy_recipe->ingredients : $recipe->ingredients; if (! empty($recipe)) { $total_production_cost = $this->mfgUtil->getProductionCost($recipe); } foreach ($ingredients_obj as $ingredient) { if (empty($ingredient->variation)) { continue; } $ingredient_sub_units = $this->transactionUtil->getSubUnits($business_id, $ingredient->variation->product->unit->id); $multiplier = ! empty($ingredient->sub_unit_id) ? ($ingredient->sub_unit->base_unit_multiplier ?? 1) : 1; if (empty($multiplier)) { $multiplier = 1; } $ingredients[] = [ 'ingredient_id' => $ingredient->variation->id, 'ingredient_line_id' => empty($copy_recipe) ? $ingredient->id : null, 'full_name' => $ingredient->variation->full_name, 'dpp_inc_tax' => $ingredient->variation->dpp_inc_tax, 'quantity' => $ingredient->quantity, 'waste_percent' => $ingredient->waste_percent ?? 0, 'sort_order' => $ingredient->sort_order, 'sub_unit_id' => $ingredient->sub_unit_id, 'unit' => $ingredient->variation->product->unit->short_name, 'sub_units' => $ingredient_sub_units, 'mfg_ingredient_group_id' => $ingredient->mfg_ingredient_group_id, 'ingredient_group_name' => $ingredient->ingredient_group->name ?? '', 'ig_description' => $ingredient->ingredient_group->description ?? '', ]; } } $sub_units = $this->moduleUtil->getSubUnits($business_id, $variation->unit_id); return $this->success([ 'variation' => $variation, 'recipe' => $recipe, 'ingredients' => $ingredients, 'sub_units' => $sub_units, 'total_production_cost' => $total_production_cost, ]); } public function storeRecipe(Request $request) { $business_id = $this->ensureManufacturing(); $request->validate([ 'variation_id' => 'required|integer', 'ingredients' => 'required|array|min:1', ]); try { $input = $request->only([ 'variation_id', 'ingredients', 'instructions', 'ingredients_cost', 'waste_percent', 'total_quantity', 'extra_cost', 'production_cost_type', 'sub_unit_id', ]); $input['total'] = $request->input('total', $request->input('final_price')); $variation = Variation::findOrFail($input['variation_id']); DB::beginTransaction(); $recipe = MfgRecipe::updateOrCreate( ['variation_id' => $input['variation_id']], [ 'product_id' => $variation->product_id, 'final_price' => $this->moduleUtil->num_uf($input['total'] ?? 0), 'ingredients_cost' => $input['ingredients_cost'] ?? 0, 'waste_percent' => $this->moduleUtil->num_uf($input['waste_percent'] ?? 0), 'total_quantity' => $this->moduleUtil->num_uf($input['total_quantity'] ?? 1), 'extra_cost' => $this->moduleUtil->num_uf($input['extra_cost'] ?? 0), 'production_cost_type' => $input['production_cost_type'] ?? 'fixed', 'instructions' => $input['instructions'] ?? null, 'sub_unit_id' => $request->input('sub_unit_id') ?: null, ] ); $edited_ingredients = []; $ingredient_groups = $request->input('ingredient_groups', []); $ingredient_group_descriptions = $request->input('ingredient_group_description', []); $created_ig_groups = []; $ingredients = []; foreach ($input['ingredients'] as $value) { $ingredient_variation = Variation::with(['product'])->findOrFail($value['ingredient_id']); if (! empty($value['ingredient_line_id'])) { $ingredient = MfgRecipeIngredient::find($value['ingredient_line_id']); $edited_ingredients[] = $ingredient->id; } else { $ingredient = new MfgRecipeIngredient(['variation_id' => $value['ingredient_id']]); } $ingredient->quantity = $this->moduleUtil->num_uf($value['quantity']); $ingredient->waste_percent = $this->moduleUtil->num_uf($value['waste_percent'] ?? 0); $ingredient->sort_order = $this->moduleUtil->num_uf($value['sort_order'] ?? 0); $ingredient->sub_unit_id = ! empty($value['sub_unit_id']) && $value['sub_unit_id'] != $ingredient_variation->product->unit_id ? $value['sub_unit_id'] : null; if (isset($value['ig_index'])) { $ig_name = $ingredient_groups[$value['ig_index']] ?? ''; $ig_description = $ingredient_group_descriptions[$value['ig_index']] ?? ''; if (! empty($created_ig_groups[$value['ig_index']])) { $ingredient_group = $created_ig_groups[$value['ig_index']]; } elseif (empty($value['mfg_ingredient_group_id'])) { $ingredient_group = MfgIngredientGroup::create([ 'name' => $ig_name, 'business_id' => $business_id, 'description' => $ig_description, ]); } else { $ingredient_group = MfgIngredientGroup::where('business_id', $business_id) ->findOrFail($value['mfg_ingredient_group_id']); if ($ingredient_group->name != $ig_name || $ingredient_group->description != $ig_description) { $ingredient_group->update(['name' => $ig_name, 'description' => $ig_description]); } } $created_ig_groups[$value['ig_index']] = $ingredient_group; $ingredient->mfg_ingredient_group_id = $ingredient_group->id; } $ingredients[] = $ingredient; } if (! empty($edited_ingredients)) { MfgRecipeIngredient::where('mfg_recipe_id', $recipe->id) ->whereNotIn('id', $edited_ingredients) ->delete(); } else { MfgRecipeIngredient::where('mfg_recipe_id', $recipe->id)->delete(); } $recipe->ingredients()->saveMany($ingredients); DB::commit(); return $this->success(['id' => $recipe->id], __('lang_v1.added_success')); } catch (\Exception $e) { DB::rollBack(); \Log::emergency('File:'.$e->getFile().'Line:'.$e->getLine().'Message:'.$e->getMessage()); return $this->error(__('messages.something_went_wrong'), 500); } } public function destroyRecipe(Request $request, $id) { $this->ensureManufacturing(); try { MfgRecipe::where('id', $id)->delete(); return $this->success(null, __('lang_v1.deleted_success')); } catch (\Exception $e) { return $this->error(__('messages.something_went_wrong'), 500); } } public function recipeDropdown(Request $request) { $business_id = $this->ensureManufacturing(); $dropdown = MfgRecipe::forDropdown($business_id, false); $items = []; foreach ($dropdown as $id => $name) { $items[] = ['id' => $id, 'name' => $name]; } return $this->success($items); } public function recipeDetails(Request $request) { $business_id = $this->ensureManufacturing(); $request->validate([ 'variation_id' => 'required|integer', 'location_id' => 'nullable|integer', ]); $variation_id = $request->input('variation_id'); $location_id = $request->input('location_id'); $recipe = MfgRecipe::where('variation_id', $variation_id) ->with(['variation', 'variation.product', 'variation.product.unit', 'sub_unit', 'ingredients' => fn ($q) => $q->orderBy('sort_order')]) ->first(); if (empty($recipe)) { return $this->error(__('manufacturing::lang.no_recipe_found'), 404); } $ingredients = $this->mfgUtil->getIngredientDetails($recipe, $business_id, $location_id); $sub_units = $this->moduleUtil->getSubUnits($business_id, $recipe->variation->product->unit->id); return $this->success([ 'recipe' => $recipe, 'ingredients' => $ingredients, 'sub_units' => $sub_units, 'production_cost' => $this->mfgUtil->getProductionCost($recipe), 'recipe_total' => $this->mfgUtil->getRecipeTotal($recipe), ]); } public function isRecipeExist(Request $request, $variation_id) { $this->ensureManufacturing(); return $this->success(['exists' => MfgRecipe::where('variation_id', $variation_id)->exists()]); } public function productions(Request $request) { $business_id = $this->ensureManufacturing(); $query = Transaction::join('business_locations AS bl', 'transactions.location_id', '=', 'bl.id') ->join('purchase_lines as pl', 'pl.transaction_id', '=', 'transactions.id') ->leftJoin('units as su', 'pl.sub_unit_id', '=', 'su.id') ->join('variations as v', 'v.id', '=', 'pl.variation_id') ->join('product_variations as pv', 'pv.id', '=', 'v.product_variation_id') ->join('products as p', 'p.id', '=', 'v.product_id') ->join('units as u', 'p.unit_id', '=', 'u.id') ->where('transactions.business_id', $business_id) ->where('transactions.type', 'production_purchase') ->select( 'transactions.id', 'transaction_date', 'ref_no', 'bl.name as location_name', DB::raw('IF(p.type="variable", CONCAT(p.name, " - ", pv.name, " - ", v.name, " (", v.sub_sku, ")"), CONCAT(p.name, " (", v.sub_sku, ")")) as product_name'), 'pl.quantity', 'final_total', 'su.short_name as sub_unit_name', 'su.base_unit_multiplier', 'u.short_name as unit_name', 'mfg_is_final', 'status' ) ->groupBy('transactions.id'); if ($request->filled('start_date') && $request->filled('end_date')) { $query->whereDate('transactions.transaction_date', '>=', $request->input('start_date')) ->whereDate('transactions.transaction_date', '<=', $request->input('end_date')); } if ($request->filled('location_id')) { $query->where('transactions.location_id', $request->input('location_id')); } if ($request->boolean('is_final')) { $query->where('transactions.mfg_is_final', 1); } return $this->paginated($query->orderByDesc('transactions.transaction_date')->paginate($request->input('per_page', 25))); } public function showProduction(Request $request, $id) { $business_id = $this->ensureManufacturing(); $production_purchase = Transaction::where('business_id', $business_id) ->where('type', 'production_purchase') ->with(['purchase_lines', 'purchase_lines.variations', 'purchase_lines.variations.product', 'purchase_lines.sub_unit', 'location']) ->findOrFail($id); $production_sell = Transaction::where('business_id', $business_id) ->where('type', 'production_sell') ->where('mfg_parent_production_purchase_id', $production_purchase->id) ->with(['sell_lines', 'sell_lines.variations', 'sell_lines.variations.product', 'sell_lines.sub_unit']) ->first(); $purchase_line = $production_purchase->purchase_lines->first(); $ingredients = []; if ($production_sell) { foreach ($production_sell->sell_lines as $sell_line) { $variation = $sell_line->variations; $sell_line_qty = empty($sell_line->sub_unit) ? $sell_line->quantity : $sell_line->quantity / $sell_line->sub_unit->base_unit_multiplier; $ingredients[] = [ 'sell_line_id' => $sell_line->id, 'full_name' => $variation->full_name ?? '', 'quantity' => $sell_line_qty, 'unit' => empty($sell_line->sub_unit) ? $variation->product->unit->short_name : $sell_line->sub_unit->short_name, 'waste_percent' => $sell_line->mfg_waste_percent ?? 0, ]; } } return $this->success([ 'production' => $production_purchase, 'purchase_line' => $purchase_line, 'ingredients' => $ingredients, ]); } public function editProduction(Request $request, $id) { $business_id = $this->ensureManufacturing(); $production_purchase = Transaction::where('business_id', $business_id) ->where('type', 'production_purchase') ->with(['purchase_lines', 'purchase_lines.variations', 'purchase_lines.variations.product', 'purchase_lines.sub_unit', 'location']) ->findOrFail($id); if ($production_purchase->mfg_is_final == 1) { return $this->error(__('messages.something_went_wrong'), 403); } $production_sell = Transaction::where('business_id', $business_id) ->where('type', 'production_sell') ->where('mfg_parent_production_purchase_id', $production_purchase->id) ->with(['sell_lines', 'sell_lines.variations', 'sell_lines.variations.product', 'sell_lines.sub_unit']) ->first(); $purchase_line = $production_purchase->purchase_lines->first(); $base_unit_multiplier = ! empty($purchase_line->sub_unit) ? $purchase_line->sub_unit->base_unit_multiplier : 1; $quantity = $purchase_line->quantity / $base_unit_multiplier; if (! empty($production_purchase->mfg_wasted_units)) { $quantity += $production_purchase->mfg_wasted_units; } $ingredients = []; if ($production_sell) { foreach ($production_sell->sell_lines as $sell_line) { $variation = $sell_line->variations; $multiplier = 1; if (! empty($sell_line->sub_unit)) { $multiplier = $sell_line->sub_unit->base_unit_multiplier ?: 1; } $ingredients[] = [ 'id' => $sell_line->id, 'variation_id' => $variation->id, 'full_name' => $variation->full_name, 'quantity' => $sell_line->quantity / $multiplier, 'waste_percent' => $sell_line->mfg_waste_percent ?? 0, 'sub_unit_id' => $sell_line->sub_unit_id, 'unit' => empty($sell_line->sub_unit) ? $variation->product->unit->short_name : $sell_line->sub_unit->short_name, ]; } } return $this->success([ 'production' => $production_purchase, 'purchase_line' => $purchase_line, 'ingredients' => $ingredients, 'form' => [ 'ref_no' => $production_purchase->ref_no, 'transaction_date' => $production_purchase->transaction_date, 'location_id' => $production_purchase->location_id, 'variation_id' => $purchase_line->variation_id, 'quantity' => $quantity, 'mfg_wasted_units' => $production_purchase->mfg_wasted_units ?? 0, 'production_cost' => $production_purchase->mfg_production_cost ?? 0, 'mfg_production_cost_type' => $production_purchase->mfg_production_cost_type ?? 'fixed', 'sub_unit_id' => $purchase_line->sub_unit_id, 'final_total' => $production_purchase->final_total, ], ]); } public function updateProduction(Request $request, $id) { $business_id = $this->ensureManufacturing(); $request->validate([ 'transaction_date' => 'required', 'location_id' => 'required', 'final_total' => 'required', ]); try { $transaction = Transaction::where('business_id', $business_id) ->where('type', 'production_purchase') ->findOrFail($id); if ($transaction->mfg_is_final == 1) { return $this->error(__('messages.something_went_wrong'), 403); } $manufacturing_settings = $this->mfgUtil->getSettings($business_id); $transaction_data = $request->only(['ref_no', 'transaction_date', 'location_id', 'final_total']); $is_final = $request->boolean('finalize') ? 1 : 0; $transaction_data['status'] = $is_final ? 'received' : 'pending'; $transaction_data['payment_status'] = 'due'; $transaction_data['transaction_date'] = $this->productUtil->uf_date($transaction_data['transaction_date'], true); $transaction_data['final_total'] = $this->productUtil->num_uf($transaction_data['final_total']); $variation_id = $request->input('variation_id'); $variation = Variation::with(['product'])->findOrFail($variation_id); $quantity = $request->input('quantity'); $waste_units = $this->productUtil->num_uf($request->input('mfg_wasted_units', 0)); $uf_qty = $this->productUtil->num_uf($quantity); if (! empty($waste_units)) { $uf_qty -= $waste_units; $quantity = $this->productUtil->num_f($uf_qty); } $unit_purchase_line_total_f = $this->productUtil->num_f($this->productUtil->num_uf($transaction_data['final_total']) / max($uf_qty, 1)); $transaction_data['mfg_wasted_units'] = $waste_units; $transaction_data['mfg_production_cost'] = $this->productUtil->num_uf($request->input('production_cost', 0)); $transaction_data['mfg_production_cost_type'] = $request->input('mfg_production_cost_type', 'fixed'); $transaction_data['mfg_is_final'] = $is_final; $purchase_line_data = [ 'variation_id' => $variation_id, 'quantity' => $quantity, 'product_id' => $variation->product_id, 'product_unit_id' => $variation->product->unit_id, 'pp_without_discount' => $unit_purchase_line_total_f, 'discount_percent' => 0, 'purchase_price' => $unit_purchase_line_total_f, 'purchase_price_inc_tax' => $unit_purchase_line_total_f, 'item_tax' => 0, 'purchase_line_tax_id' => null, 'mfg_date' => $this->transactionUtil->format_date($transaction_data['transaction_date']), ]; if ($request->filled('sub_unit_id')) { $purchase_line_data['sub_unit_id'] = $request->input('sub_unit_id'); } DB::beginTransaction(); $transaction->update($transaction_data); $currency_details = $this->transactionUtil->purchaseCurrencyDetails($business_id); $update_product_price = ! empty($manufacturing_settings['enable_updating_product_price']) && $is_final; $this->productUtil->createOrUpdatePurchaseLines($transaction, [$purchase_line_data], $currency_details, $update_product_price); $this->productUtil->adjustStockOverSelling($transaction); $production_sell = Transaction::where('business_id', $business_id) ->where('type', 'production_sell') ->with('sell_lines', 'sell_lines.variations', 'sell_lines.product') ->where('mfg_parent_production_purchase_id', $transaction->id) ->firstOrFail(); $production_sell->update([ 'transaction_date' => $transaction->transaction_date, 'status' => $is_final ? 'final' : 'draft', 'payment_status' => 'due', 'final_total' => $transaction->final_total, ]); $ingredient_quantities = $request->input('ingredients', []); $sell_lines = []; foreach ($production_sell->sell_lines as $sell_line) { $v = $sell_line->variations; $line_key = $sell_line->id; $line_sub_unit_id = $ingredient_quantities[$line_key]['sub_unit_id'] ?? null; $line_multiplier = 1; if (! empty($line_sub_unit_id)) { $sub_units = $this->productUtil->getSubUnits($business_id, $sell_line->product->unit_id); $line_multiplier = $sub_units[$line_sub_unit_id]['multiplier'] ?? 1; } $sell_lines[] = [ 'product_id' => $v->product_id, 'variation_id' => $v->id, 'quantity' => $this->productUtil->num_uf($ingredient_quantities[$line_key]['quantity'] ?? 0), 'item_tax' => 0, 'tax_id' => null, 'unit_price' => $v->dpp_inc_tax * $line_multiplier, 'unit_price_inc_tax' => $v->dpp_inc_tax * $line_multiplier, 'enable_stock' => $sell_line->product->enable_stock, 'product_unit_id' => $v->product->unit_id, 'sub_unit_id' => $line_sub_unit_id, 'base_unit_multiplier' => $line_multiplier, 'mfg_waste_percent' => $this->productUtil->num_uf($ingredient_quantities[$line_key]['mfg_waste_percent'] ?? 0), 'mfg_ingredient_group_id' => $ingredient_quantities[$line_key]['mfg_ingredient_group_id'] ?? null, ]; } if (! empty($sell_lines)) { $this->transactionUtil->createOrUpdateSellLines( $production_sell, $sell_lines, $transaction->location_id, false, 'draft', ['mfg_waste_percent' => 'mfg_waste_percent', 'mfg_ingredient_group_id' => 'mfg_ingredient_group_id'] ); } if ($is_final) { foreach ($sell_lines as $sell_line) { if ($sell_line['enable_stock']) { $line_qty = $sell_line['quantity'] * $sell_line['base_unit_multiplier']; $this->productUtil->decreaseProductQuantity( $sell_line['product_id'], $sell_line['variation_id'], $production_sell->location_id, $line_qty ); } } $business_details = $this->businessUtil->getDetails($business_id); $pos_settings = empty($business_details->pos_settings) ? $this->businessUtil->defaultPosSettings() : json_decode($business_details->pos_settings, true); $business = [ 'id' => $business_id, 'accounting_method' => request()->input('accounting_method', 'fifo'), 'location_id' => $production_sell->location_id, 'pos_settings' => $pos_settings, ]; $this->transactionUtil->mapPurchaseSell($business, $production_sell->sell_lines, 'production_purchase'); } DB::commit(); return $this->success(['id' => $transaction->id], __('lang_v1.updated_success')); } catch (\Exception $e) { DB::rollBack(); \Log::emergency('File:'.$e->getFile().'Line:'.$e->getLine().'Message:'.$e->getMessage()); return $this->error($e->getMessage() ?: __('messages.something_went_wrong'), 500); } } public function manufacturingReport(Request $request) { $business_id = $this->ensureManufacturing(); $start_date = $request->input('start_date'); $end_date = $request->input('end_date'); $location_id = $request->input('location_id'); $production_totals = $this->mfgUtil->getProductionTotals($business_id, $location_id, $start_date, $end_date); $total_sold = $this->mfgUtil->getTotalSold($business_id, $location_id, $start_date, $end_date); return $this->success([ 'total_production' => $production_totals['total_production'] ?? 0, 'total_production_cost' => $production_totals['total_production_cost'] ?? 0, 'total_sold' => $total_sold, ]); } public function updateRecipePrices(Request $request) { $business_id = $this->ensureManufacturing(); $request->validate([ 'recipe_ids' => 'required|array|min:1', 'unit_prices' => 'required|array', ]); try { $recipe_ids = $request->input('recipe_ids'); $unit_prices = $request->input('unit_prices'); $recipes = MfgRecipe::with(['variation', 'sub_unit', 'variation.product', 'variation.product.product_tax']) ->whereIn('id', $recipe_ids) ->get(); DB::beginTransaction(); foreach ($recipes as $recipe) { $variation = $recipe->variation; $unit_price = $this->moduleUtil->num_uf($unit_prices[$recipe->id] ?? 0); if (! empty($recipe->sub_unit->base_unit_multiplier)) { $unit_price = $unit_price / $recipe->sub_unit->base_unit_multiplier; } $unit_price_exc_tax = $unit_price; if (! empty($variation->product->product_tax)) { $tax_percent = $variation->product->product_tax->amount; $unit_price_exc_tax = $this->transactionUtil->calc_percentage_base($unit_price, $tax_percent); } $variation->default_purchase_price = $unit_price_exc_tax; $variation->dpp_inc_tax = $unit_price; $profit_margin = $this->transactionUtil->get_percent($unit_price, $variation->sell_price_inc_tax); $sell_price_excluding_tax = $this->transactionUtil->calc_percentage($unit_price_exc_tax, $profit_margin, $unit_price_exc_tax); $variation->default_sell_price = $sell_price_excluding_tax; $variation->profit_percent = $profit_margin; $variation->save(); } DB::commit(); return $this->success(null, __('lang_v1.updated_succesfully')); } catch (\Exception $e) { DB::rollBack(); return $this->error(__('messages.something_went_wrong'), 500); } } public function storeProduction(Request $request) { $business_id = $this->ensureManufacturing(); $request->validate([ 'transaction_date' => 'required', 'location_id' => 'required|integer', 'variation_id' => 'required|integer', 'quantity' => 'required', 'final_total' => 'required', 'ingredients' => 'required|array', ]); try { $manufacturing_settings = $this->mfgUtil->getSettings($business_id); $user_id = Auth::id(); $transaction_data = $request->only(['ref_no', 'transaction_date', 'location_id', 'final_total']); $is_final = $request->boolean('finalize') ? 1 : 0; $transaction_data['business_id'] = $business_id; $transaction_data['created_by'] = $user_id; $transaction_data['type'] = 'production_purchase'; $transaction_data['status'] = $is_final ? 'received' : 'pending'; $transaction_data['payment_status'] = 'due'; $transaction_data['transaction_date'] = $this->productUtil->uf_date($transaction_data['transaction_date'], true); $transaction_data['final_total'] = $this->productUtil->num_uf($transaction_data['final_total']); $ref_count = $this->productUtil->setAndGetReferenceCount($transaction_data['type']); if (empty($transaction_data['ref_no'])) { $prefix = $manufacturing_settings['ref_no_prefix'] ?? null; $transaction_data['ref_no'] = $this->productUtil->generateReferenceNumber($transaction_data['type'], $ref_count, null, $prefix); } $variation_id = $request->input('variation_id'); $variation = Variation::with(['product'])->findOrFail($variation_id); $quantity = $request->input('quantity'); $waste_units = $this->productUtil->num_uf($request->input('mfg_wasted_units', 0)); $uf_qty = $this->productUtil->num_uf($quantity); if (! empty($waste_units)) { $uf_qty -= $waste_units; $quantity = $this->productUtil->num_f($uf_qty); } $unit_purchase_line_total = $this->productUtil->num_uf($transaction_data['final_total']) / max($uf_qty, 1); $unit_purchase_line_total_f = $this->productUtil->num_f($unit_purchase_line_total); $transaction_data['mfg_wasted_units'] = $waste_units; $transaction_data['mfg_production_cost'] = $this->productUtil->num_uf($request->input('production_cost', 0)); $transaction_data['mfg_production_cost_type'] = $request->input('mfg_production_cost_type', 'fixed'); $transaction_data['mfg_is_final'] = $is_final; $purchase_line_data = [ 'variation_id' => $variation_id, 'quantity' => $quantity, 'product_id' => $variation->product_id, 'product_unit_id' => $variation->product->unit_id, 'pp_without_discount' => $unit_purchase_line_total_f, 'discount_percent' => 0, 'purchase_price' => $unit_purchase_line_total_f, 'purchase_price_inc_tax' => $unit_purchase_line_total_f, 'item_tax' => 0, 'purchase_line_tax_id' => null, 'mfg_date' => $this->transactionUtil->format_date($transaction_data['transaction_date']), ]; if ($request->filled('sub_unit_id')) { $purchase_line_data['sub_unit_id'] = $request->input('sub_unit_id'); } if ($request->filled('lot_number')) { $purchase_line_data['lot_number'] = $request->input('lot_number'); } if ($request->filled('exp_date')) { $purchase_line_data['exp_date'] = $request->input('exp_date'); } DB::beginTransaction(); $transaction = Transaction::create($transaction_data); $currency_details = $this->transactionUtil->purchaseCurrencyDetails($business_id); $update_product_price = ! empty($manufacturing_settings['enable_updating_product_price']) && $is_final; $this->productUtil->createOrUpdatePurchaseLines($transaction, [$purchase_line_data], $currency_details, $update_product_price); $this->productUtil->adjustStockOverSelling($transaction); $transaction_sell_data = [ 'business_id' => $business_id, 'location_id' => $transaction->location_id, 'transaction_date' => $transaction->transaction_date, 'created_by' => $transaction->created_by, 'status' => $is_final ? 'final' : 'draft', 'type' => 'production_sell', 'mfg_parent_production_purchase_id' => $transaction->id, 'payment_status' => 'due', 'final_total' => $transaction->final_total, ]; $sell_lines = []; $ingredient_quantities = $request->input('ingredients', []); $recipe = MfgRecipe::where('variation_id', $variation_id)->first(); $all_variation_details = $this->mfgUtil->getIngredientDetails($recipe, $business_id); foreach ($all_variation_details as $variation_details) { $v = $variation_details['variation']; $line_key = $variation_details['id']; $line_sub_unit_id = $ingredient_quantities[$line_key]['sub_unit_id'] ?? null; $line_multiplier = ! empty($line_sub_unit_id) ? ($variation_details['sub_units'][$line_sub_unit_id]['multiplier'] ?? 1) : 1; $mfg_waste_percent = $this->productUtil->num_uf($ingredient_quantities[$line_key]['mfg_waste_percent'] ?? 0); $sell_lines[] = [ 'product_id' => $v->product_id, 'variation_id' => $v->id, 'quantity' => $this->productUtil->num_uf($ingredient_quantities[$line_key]['quantity'] ?? 0), 'item_tax' => 0, 'tax_id' => null, 'unit_price' => $v->dpp_inc_tax * $line_multiplier, 'unit_price_inc_tax' => $v->dpp_inc_tax * $line_multiplier, 'enable_stock' => $variation_details['enable_stock'], 'product_unit_id' => $v->product->unit_id, 'sub_unit_id' => $line_sub_unit_id, 'base_unit_multiplier' => $line_multiplier, 'mfg_waste_percent' => $mfg_waste_percent, 'mfg_ingredient_group_id' => $ingredient_quantities[$line_key]['mfg_ingredient_group_id'] ?? null, ]; } $production_sell = Transaction::create($transaction_sell_data); if (! empty($sell_lines)) { $this->transactionUtil->createOrUpdateSellLines( $production_sell, $sell_lines, $transaction_sell_data['location_id'], null, null, ['mfg_waste_percent' => 'mfg_waste_percent', 'mfg_ingredient_group_id' => 'mfg_ingredient_group_id'] ); } if ($production_sell->status == 'final') { foreach ($sell_lines as $sell_line) { if ($sell_line['enable_stock']) { $line_qty = $sell_line['quantity'] * $sell_line['base_unit_multiplier']; $this->productUtil->decreaseProductQuantity( $sell_line['product_id'], $sell_line['variation_id'], $production_sell->location_id, $line_qty ); } } $business_details = $this->businessUtil->getDetails($business_id); $pos_settings = empty($business_details->pos_settings) ? $this->businessUtil->defaultPosSettings() : json_decode($business_details->pos_settings, true); $business = [ 'id' => $business_id, 'accounting_method' => request()->input('accounting_method', 'fifo'), 'location_id' => $production_sell->location_id, 'pos_settings' => $pos_settings, ]; $this->transactionUtil->mapPurchaseSell($business, $production_sell->sell_lines, 'production_purchase'); } DB::commit(); return $this->success(['id' => $transaction->id], __('lang_v1.added_success')); } catch (\Exception $e) { DB::rollBack(); \Log::emergency('File:'.$e->getFile().'Line:'.$e->getLine().'Message:'.$e->getMessage()); return $this->error($e->getMessage() ?: __('messages.something_went_wrong'), 500); } } public function destroyProduction(Request $request, $id) { $business_id = $this->ensureManufacturing(); try { $production = Transaction::where('business_id', $business_id) ->where('type', 'production_purchase') ->where('mfg_is_final', 0) ->findOrFail($id); DB::beginTransaction(); Transaction::where('business_id', $business_id) ->where('type', 'production_sell') ->where('mfg_parent_production_purchase_id', $production->id) ->delete(); $production->delete(); DB::commit(); return $this->success(null, __('lang_v1.deleted_success')); } catch (\Exception $e) { DB::rollBack(); return $this->error(__('messages.something_went_wrong'), 500); } } public function settings(Request $request) { $business_id = $this->ensureManufacturing(); return $this->success($this->mfgUtil->getSettings($business_id)); } public function updateSettings(Request $request) { $business_id = $this->ensureManufacturing(); try { $settings = $request->only(['ref_no_prefix']); $settings['disable_editing_ingredient_qty'] = $request->boolean('disable_editing_ingredient_qty'); $settings['enable_updating_product_price'] = $request->boolean('enable_updating_product_price'); Business::where('id', $business_id)->update(['manufacturing_settings' => json_encode($settings)]); return $this->success($settings, __('lang_v1.updated_success')); } catch (\Exception $e) { return $this->error(__('messages.something_went_wrong'), 500); } } public function resources(Request $request) { $business_id = $this->ensureManufacturing(); return $this->success([ 'locations' => BusinessLocation::forDropdown($business_id, true), 'recipe_dropdown' => MfgRecipe::forDropdown($business_id), ]); } }