refactor: remove quick-create raw material functionality and associated request, streamlining the CuttingDraftItemController and related components
This commit is contained in:
parent
b8e9aaa128
commit
b93a200284
@ -7,7 +7,6 @@
|
|||||||
use App\Http\Requests\Admin\Manage\CuttingDraftMaterialRequest;
|
use App\Http\Requests\Admin\Manage\CuttingDraftMaterialRequest;
|
||||||
use App\Http\Requests\Admin\Manage\CuttingDraftResultRequest;
|
use App\Http\Requests\Admin\Manage\CuttingDraftResultRequest;
|
||||||
use App\Http\Requests\Admin\Manage\CuttingQuickCreateProductRequest;
|
use App\Http\Requests\Admin\Manage\CuttingQuickCreateProductRequest;
|
||||||
use App\Http\Requests\Admin\Manage\CuttingQuickCreateRawMaterialRequest;
|
|
||||||
use App\Models\ProductVariant;
|
use App\Models\ProductVariant;
|
||||||
use App\Models\RawMaterialPrice;
|
use App\Models\RawMaterialPrice;
|
||||||
use App\Services\Manage\CuttingService;
|
use App\Services\Manage\CuttingService;
|
||||||
@ -62,13 +61,6 @@ public function destroyCombination(Request $request, int $combinationId): JsonRe
|
|||||||
return response()->json(['ok' => true]);
|
return response()->json(['ok' => true]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function quickCreateRawMaterial(CuttingQuickCreateRawMaterialRequest $request): JsonResponse
|
|
||||||
{
|
|
||||||
$rawMaterial = $this->cuttingService->quickCreateRawMaterial($request->validated());
|
|
||||||
|
|
||||||
return response()->json(['raw_material' => $rawMaterial]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function quickCreateProduct(CuttingQuickCreateProductRequest $request): JsonResponse
|
public function quickCreateProduct(CuttingQuickCreateProductRequest $request): JsonResponse
|
||||||
{
|
{
|
||||||
$product = $this->cuttingService->quickCreateProduct($request->validated());
|
$product = $this->cuttingService->quickCreateProduct($request->validated());
|
||||||
|
|||||||
@ -1,44 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Http\Requests\Admin\Manage;
|
|
||||||
|
|
||||||
use App\Enums\Permission;
|
|
||||||
use App\Enums\RawMaterialUnit;
|
|
||||||
use App\Http\Requests\Concerns\HasRawMaterialPriceRules;
|
|
||||||
use Illuminate\Foundation\Http\FormRequest;
|
|
||||||
use Illuminate\Validation\Rule;
|
|
||||||
|
|
||||||
class CuttingQuickCreateRawMaterialRequest extends FormRequest
|
|
||||||
{
|
|
||||||
use HasRawMaterialPriceRules;
|
|
||||||
|
|
||||||
public function authorize(): bool
|
|
||||||
{
|
|
||||||
return $this->user()?->can(Permission::CUTTINGS_CREATE->value) ?? false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return array<string, mixed>
|
|
||||||
*/
|
|
||||||
public function rules(): array
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'name' => ['required', 'string', 'max:200'],
|
|
||||||
'unit' => ['required', Rule::enum(RawMaterialUnit::class)],
|
|
||||||
|
|
||||||
...$this->rawMaterialPriceRules(),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return array<string, string>
|
|
||||||
*/
|
|
||||||
public function attributes(): array
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'name' => 'Nama Bahan Baku',
|
|
||||||
'unit' => 'Satuan',
|
|
||||||
...$this->rawMaterialPriceAttributes(),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1182,65 +1182,6 @@ private function storeResultPrices(Cutting $cutting, array $resultPrices): void
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Quick-create a raw material with variants, bypassing owner verification.
|
|
||||||
*/
|
|
||||||
public function quickCreateRawMaterial(array $validated): array
|
|
||||||
{
|
|
||||||
$rawMaterial = RawMaterial::create([
|
|
||||||
'name' => $validated['name'],
|
|
||||||
'unit' => $validated['unit'],
|
|
||||||
'is_active' => true,
|
|
||||||
]);
|
|
||||||
|
|
||||||
$createdPrices = [];
|
|
||||||
$maxVariantImages = 5;
|
|
||||||
|
|
||||||
foreach ($validated['prices'] as $index => $priceData) {
|
|
||||||
$price = $rawMaterial->prices()->create([
|
|
||||||
'variant' => $priceData['variant'],
|
|
||||||
'price' => $priceData['price'],
|
|
||||||
'stock' => $priceData['stock'],
|
|
||||||
]);
|
|
||||||
|
|
||||||
$this->mediaService->syncCollection(
|
|
||||||
$price,
|
|
||||||
'images',
|
|
||||||
$priceData['images'] ?? null,
|
|
||||||
null,
|
|
||||||
$maxVariantImages,
|
|
||||||
required: true,
|
|
||||||
errorKey: "prices.{$index}.images",
|
|
||||||
s3Keys: $priceData['s3_keys'] ?? null,
|
|
||||||
);
|
|
||||||
|
|
||||||
$price->refresh();
|
|
||||||
|
|
||||||
$createdPrices[] = [
|
|
||||||
'id' => $price->id,
|
|
||||||
'variant' => $price->variant,
|
|
||||||
'price' => $price->price,
|
|
||||||
'price_formatted' => 'Rp '.number_format($price->price, 0, ',', '.'),
|
|
||||||
'stock' => $price->stock,
|
|
||||||
'stock_formatted' => $this->formatStockForUnit((float) $price->stock, $rawMaterial->unit),
|
|
||||||
'stock_input' => $this->formatQuantityInput((float) $price->stock),
|
|
||||||
'images' => MediaPresenter::collection($price, 'images'),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->cacheForgetByPattern('manage:cuttings:*');
|
|
||||||
|
|
||||||
return [
|
|
||||||
'id' => $rawMaterial->id,
|
|
||||||
'name' => $rawMaterial->name,
|
|
||||||
'unit' => $rawMaterial->unit->value,
|
|
||||||
'unit_label' => $rawMaterial->unit->label(),
|
|
||||||
'unit_abbreviation' => $rawMaterial->unit->abbreviation(),
|
|
||||||
'is_active' => true,
|
|
||||||
'prices' => $createdPrices,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Quick-create a product with variants and prices, bypassing owner verification.
|
* Quick-create a product with variants and prices, bypassing owner verification.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -263,7 +263,7 @@ function () use ($validated, $user): array {
|
|||||||
if ($rawMaterial->trashed()) {
|
if ($rawMaterial->trashed()) {
|
||||||
$rawMaterial->restore();
|
$rawMaterial->restore();
|
||||||
}
|
}
|
||||||
if ($isOwner && !$rawMaterial->is_active) {
|
if ($isOwner && ! $rawMaterial->is_active) {
|
||||||
$rawMaterial->update(['is_active' => true]);
|
$rawMaterial->update(['is_active' => true]);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -847,7 +847,7 @@ private function processRequestItems(array $items, bool $isOwner): array
|
|||||||
if ($rawMaterial->trashed()) {
|
if ($rawMaterial->trashed()) {
|
||||||
$rawMaterial->restore();
|
$rawMaterial->restore();
|
||||||
}
|
}
|
||||||
if ($isOwner && !$rawMaterial->is_active) {
|
if ($isOwner && ! $rawMaterial->is_active) {
|
||||||
$rawMaterial->update(['is_active' => true]);
|
$rawMaterial->update(['is_active' => true]);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -881,7 +881,7 @@ private function processRequestItems(array $items, bool $isOwner): array
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Sync variant photo if provided
|
// Sync variant photo if provided
|
||||||
if (!empty($itemData['photos']) || !empty($itemData['s3_keys']) || !empty($itemData['remove_media_ids'])) {
|
if (! empty($itemData['photos']) || ! empty($itemData['s3_keys']) || ! empty($itemData['remove_media_ids'])) {
|
||||||
$this->mediaService->syncCollection(
|
$this->mediaService->syncCollection(
|
||||||
$price,
|
$price,
|
||||||
'images',
|
'images',
|
||||||
|
|||||||
@ -292,17 +292,7 @@ function submit() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function onRawMaterialCreated(rawMaterial: CuttingRawMaterialCatalogItem) {
|
|
||||||
// Add to catalog
|
|
||||||
rawMaterialCatalogState.value.push(rawMaterial);
|
|
||||||
|
|
||||||
// Auto-add the first price variant to cart
|
|
||||||
const firstPrice = rawMaterial.prices[0];
|
|
||||||
|
|
||||||
if (firstPrice) {
|
|
||||||
addMaterial(rawMaterial, firstPrice);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function onCombinationCreated(items: CuttingMaterialCartItem[]) {
|
function onCombinationCreated(items: CuttingMaterialCartItem[]) {
|
||||||
for (const item of items) {
|
for (const item of items) {
|
||||||
@ -333,7 +323,7 @@ function onProductCreated(product: CuttingProductCatalogItem) {
|
|||||||
:filtered-raw-materials="filteredRawMaterials" :raw-material-catalog="rawMaterialCatalogState"
|
:filtered-raw-materials="filteredRawMaterials" :raw-material-catalog="rawMaterialCatalogState"
|
||||||
:material-cart="materialCart" :get-material-cart-item="getMaterialCartItem" :units="units"
|
:material-cart="materialCart" :get-material-cart-item="getMaterialCartItem" :units="units"
|
||||||
@add-material="addMaterial" @decrease-material-qty="decreaseMaterialQty"
|
@add-material="addMaterial" @decrease-material-qty="decreaseMaterialQty"
|
||||||
@raw-material-created="onRawMaterialCreated" @combination-created="onCombinationCreated" />
|
@combination-created="onCombinationCreated" />
|
||||||
|
|
||||||
<CuttingPosResultCatalogPanel v-model:product-search="productSearch" :filtered-products="filteredProducts"
|
<CuttingPosResultCatalogPanel v-model:product-search="productSearch" :filtered-products="filteredProducts"
|
||||||
:get-result-cart-item="getResultCartItem" @add-result="addResult" :is-create-mode="isCreateMode"
|
:get-result-cart-item="getResultCartItem" @add-result="addResult" :is-create-mode="isCreateMode"
|
||||||
|
|||||||
@ -25,7 +25,6 @@ import { getFirstCoverImage } from '@/lib/catalog-cover';
|
|||||||
import type { CuttingMaterialCartItem, CuttingRawMaterialCatalogItem } from '@/types/cutting';
|
import type { CuttingMaterialCartItem, CuttingRawMaterialCatalogItem } from '@/types/cutting';
|
||||||
import type { EnumOption } from '@/types/raw-material';
|
import type { EnumOption } from '@/types/raw-material';
|
||||||
import CuttingPosCombinationDialog from './CuttingPosCombinationDialog.vue';
|
import CuttingPosCombinationDialog from './CuttingPosCombinationDialog.vue';
|
||||||
import QuickCreateRawMaterialModal from './QuickCreateRawMaterialModal.vue';
|
|
||||||
import type { CuttingCatalogPrice } from './useCuttingPosCart';
|
import type { CuttingCatalogPrice } from './useCuttingPosCart';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
@ -83,11 +82,9 @@ const selectedRawMaterialId = defineModel<string>('selectedRawMaterialId', { req
|
|||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
'add-material': [rawMaterial: CuttingRawMaterialCatalogItem, price: CuttingCatalogPrice];
|
'add-material': [rawMaterial: CuttingRawMaterialCatalogItem, price: CuttingCatalogPrice];
|
||||||
'decrease-material-qty': [priceId: number];
|
'decrease-material-qty': [priceId: number];
|
||||||
'raw-material-created': [rawMaterial: CuttingRawMaterialCatalogItem];
|
|
||||||
'combination-created': [items: CuttingMaterialCartItem[]];
|
'combination-created': [items: CuttingMaterialCartItem[]];
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const quickCreateOpen = ref(false);
|
|
||||||
const combinationOpen = ref(false);
|
const combinationOpen = ref(false);
|
||||||
const combinationInitialVariant = ref<{
|
const combinationInitialVariant = ref<{
|
||||||
rawMaterial: CuttingRawMaterialCatalogItem;
|
rawMaterial: CuttingRawMaterialCatalogItem;
|
||||||
@ -104,10 +101,6 @@ function openCombination(rawMaterial: CuttingRawMaterialCatalogItem, price: Cutt
|
|||||||
<Card class="min-w-0">
|
<Card class="min-w-0">
|
||||||
<CardHeader class="flex flex-row items-center justify-between pb-3 space-y-0">
|
<CardHeader class="flex flex-row items-center justify-between pb-3 space-y-0">
|
||||||
<CardTitle class="text-base">Pilih Bahan Baku</CardTitle>
|
<CardTitle class="text-base">Pilih Bahan Baku</CardTitle>
|
||||||
<Button type="button" variant="outline" size="sm" @click="quickCreateOpen = true">
|
|
||||||
<Plus class="size-3.5 mr-1" />
|
|
||||||
Baru
|
|
||||||
</Button>
|
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
@ -119,10 +112,10 @@ function openCombination(rawMaterial: CuttingRawMaterialCatalogItem, price: Cutt
|
|||||||
<div class="w-full sm:w-60">
|
<div class="w-full sm:w-60">
|
||||||
<Select v-model="selectedRawMaterialId">
|
<Select v-model="selectedRawMaterialId">
|
||||||
<SelectTrigger class="w-full">
|
<SelectTrigger class="w-full">
|
||||||
<SelectValue placeholder="Semua Bahan Baku" />
|
<SelectValue placeholder="FIlter Bahan Baku" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem value="all">Semua Bahan Baku</SelectItem>
|
<SelectItem value="all">Filter Bahan Baku</SelectItem>
|
||||||
<SelectItem v-for="item in rawMaterialCatalog" :key="item.id" :value="String(item.id)">
|
<SelectItem v-for="item in rawMaterialCatalog" :key="item.id" :value="String(item.id)">
|
||||||
{{ item.name }}
|
{{ item.name }}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
@ -207,8 +200,7 @@ function openCombination(rawMaterial: CuttingRawMaterialCatalogItem, price: Cutt
|
|||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<QuickCreateRawMaterialModal v-model:open="quickCreateOpen" :units="units"
|
|
||||||
@created="emit('raw-material-created', $event)" />
|
|
||||||
|
|
||||||
<CuttingPosCombinationDialog v-model:open="combinationOpen" :raw-material-catalog="rawMaterialCatalog"
|
<CuttingPosCombinationDialog v-model:open="combinationOpen" :raw-material-catalog="rawMaterialCatalog"
|
||||||
:existing-cart-items="materialCart" :initial-variant="combinationInitialVariant"
|
:existing-cart-items="materialCart" :initial-variant="combinationInitialVariant"
|
||||||
|
|||||||
@ -1,228 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { Plus, Save } from '@lucide/vue';
|
|
||||||
import { computed, ref } from 'vue';
|
|
||||||
import { toast } from 'vue-sonner';
|
|
||||||
import { Button } from '@/components/ui/button';
|
|
||||||
import {
|
|
||||||
Dialog,
|
|
||||||
DialogContent,
|
|
||||||
DialogHeader,
|
|
||||||
DialogTitle,
|
|
||||||
} from '@/components/ui/dialog';
|
|
||||||
import { FieldError } from '@/components/ui/field';
|
|
||||||
import { useVariantList } from '@/composables/useVariantList';
|
|
||||||
import { apiFetch } from '@/lib/api';
|
|
||||||
import { formErrors } from '@/lib/form';
|
|
||||||
import { parseRupiah } from '@/lib/rupiah';
|
|
||||||
import RawMaterialInfoSection from '@/pages/admin/master/raw-materials/form/RawMaterialInfoSection.vue';
|
|
||||||
import RawMaterialSharedPriceSection from '@/pages/admin/master/raw-materials/form/RawMaterialSharedPriceSection.vue';
|
|
||||||
import RawMaterialVariantSection from '@/pages/admin/master/raw-materials/form/RawMaterialVariantSection.vue';
|
|
||||||
import type { CuttingRawMaterialCatalogItem } from '@/types/cutting';
|
|
||||||
import { appendMediaToFormData, createMediaUploadState } from '@/types/media';
|
|
||||||
import type { EnumOption, RawMaterialPriceFormItem } from '@/types/raw-material';
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
units: EnumOption[];
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const open = defineModel<boolean>('open', { required: true });
|
|
||||||
|
|
||||||
const emit = defineEmits<{
|
|
||||||
'created': [rawMaterial: CuttingRawMaterialCatalogItem];
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const loading = ref(false);
|
|
||||||
const selectPortalTarget = ref<HTMLElement>();
|
|
||||||
|
|
||||||
const isUploading = computed(() =>
|
|
||||||
prices.value.some((p) => p.media.pendingUploads > 0)
|
|
||||||
);
|
|
||||||
|
|
||||||
function createClientId(): string {
|
|
||||||
return `price-${crypto.randomUUID()}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const form = ref({
|
|
||||||
name: '',
|
|
||||||
unit: '',
|
|
||||||
errors: {} as Record<string, string>,
|
|
||||||
});
|
|
||||||
|
|
||||||
const {
|
|
||||||
items: prices,
|
|
||||||
removeItem: removePrice,
|
|
||||||
setField: setPriceField,
|
|
||||||
appendToFormData,
|
|
||||||
itemErrors: priceErrors,
|
|
||||||
} = useVariantList<RawMaterialPriceFormItem>(
|
|
||||||
'prices',
|
|
||||||
() => ({
|
|
||||||
client_id: createClientId(),
|
|
||||||
variant: '',
|
|
||||||
price: '',
|
|
||||||
stock: '0',
|
|
||||||
media: createMediaUploadState(),
|
|
||||||
}),
|
|
||||||
() => [{
|
|
||||||
client_id: createClientId(),
|
|
||||||
variant: '',
|
|
||||||
price: '',
|
|
||||||
stock: '0',
|
|
||||||
media: createMediaUploadState(),
|
|
||||||
}],
|
|
||||||
);
|
|
||||||
|
|
||||||
const useSamePrice = ref(true);
|
|
||||||
|
|
||||||
function addPrice() {
|
|
||||||
const newPrice: RawMaterialPriceFormItem = {
|
|
||||||
client_id: createClientId(),
|
|
||||||
variant: '',
|
|
||||||
price: '',
|
|
||||||
stock: '0',
|
|
||||||
media: createMediaUploadState(),
|
|
||||||
};
|
|
||||||
|
|
||||||
if (useSamePrice.value && prices.value[0]) {
|
|
||||||
newPrice.price = prices.value[0].price;
|
|
||||||
}
|
|
||||||
|
|
||||||
prices.value = [...prices.value, newPrice];
|
|
||||||
}
|
|
||||||
|
|
||||||
function setPriceValue(clientId: string, value: string) {
|
|
||||||
setPriceField(clientId, 'price', value);
|
|
||||||
}
|
|
||||||
|
|
||||||
function setSharedPrice(value: string) {
|
|
||||||
prices.value = prices.value.map((price) => ({ ...price, price: value }));
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleUseSamePrice(checked: boolean) {
|
|
||||||
useSamePrice.value = checked;
|
|
||||||
|
|
||||||
if (!checked || !prices.value[0]) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const sourcePrice = prices.value[0].price;
|
|
||||||
prices.value = prices.value.map((price) => ({ ...price, price: sourcePrice }));
|
|
||||||
}
|
|
||||||
|
|
||||||
function applyPriceToAllVariants(sourceClientId: string) {
|
|
||||||
const source = prices.value.find((price) => price.client_id === sourceClientId);
|
|
||||||
|
|
||||||
if (!source) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
prices.value = prices.value.map((price) => ({ ...price, price: source.price }));
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseStockValue(value: string): number {
|
|
||||||
const parsed = Number.parseFloat(value.replace(',', '.'));
|
|
||||||
|
|
||||||
return Number.isNaN(parsed) ? 0 : parsed;
|
|
||||||
}
|
|
||||||
|
|
||||||
function resetForm() {
|
|
||||||
form.value = { name: '', unit: '', errors: {} };
|
|
||||||
prices.value = [{
|
|
||||||
client_id: createClientId(),
|
|
||||||
variant: '',
|
|
||||||
price: '',
|
|
||||||
stock: '0',
|
|
||||||
media: createMediaUploadState(),
|
|
||||||
}];
|
|
||||||
useSamePrice.value = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildFormData(): FormData {
|
|
||||||
const formData = new FormData();
|
|
||||||
|
|
||||||
formData.append('name', form.value.name.trim());
|
|
||||||
formData.append('unit', form.value.unit);
|
|
||||||
|
|
||||||
appendToFormData(formData, (formData, index, price) => {
|
|
||||||
formData.append(`prices[${index}][variant]`, price.variant.trim());
|
|
||||||
formData.append(`prices[${index}][price]`, String(Number.parseInt(parseRupiah(price.price), 10) || 0));
|
|
||||||
formData.append(`prices[${index}][stock]`, String(parseStockValue(price.stock)));
|
|
||||||
appendMediaToFormData(formData, `prices[${index}]`, price.media);
|
|
||||||
}, 'post');
|
|
||||||
|
|
||||||
return formData;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function submit() {
|
|
||||||
loading.value = true;
|
|
||||||
form.value.errors = {};
|
|
||||||
|
|
||||||
try {
|
|
||||||
const payload = buildFormData();
|
|
||||||
const { raw_material } = await apiFetch<{ raw_material: CuttingRawMaterialCatalogItem }>(
|
|
||||||
'/admin/manage/cuttings/quick-create-raw-material',
|
|
||||||
{
|
|
||||||
method: 'POST',
|
|
||||||
body: payload,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
toast.success(`Bahan baku "${raw_material.name}" berhasil ditambahkan.`);
|
|
||||||
emit('created', raw_material);
|
|
||||||
open.value = false;
|
|
||||||
resetForm();
|
|
||||||
} catch (error) {
|
|
||||||
if (error instanceof Error) {
|
|
||||||
toast.error(error.message);
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
loading.value = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Dialog v-model:open="open">
|
|
||||||
<DialogContent class="flex max-h-[90vh] flex-col sm:max-w-5xl">
|
|
||||||
<div class="scrollbar-thin flex-1 overflow-y-auto pr-1">
|
|
||||||
<form id="quick-create-raw-material-form" @submit.prevent="submit">
|
|
||||||
<RawMaterialInfoSection :form="form" :units="units" method="post"
|
|
||||||
:select-portal-target="selectPortalTarget" />
|
|
||||||
|
|
||||||
<RawMaterialSharedPriceSection :form="form" :prices="prices" :use-same-price="useSamePrice"
|
|
||||||
@toggle-use-same-price="toggleUseSamePrice" @set-shared-price="setSharedPrice" />
|
|
||||||
|
|
||||||
<RawMaterialVariantSection v-for="(price, index) in prices" :key="price.client_id" :form="form"
|
|
||||||
:price="price" :index="index" :total-prices="prices.length" :use-same-price="useSamePrice"
|
|
||||||
:price-errors="(clientId, field) => priceErrors(form, clientId, field)"
|
|
||||||
@remove="removePrice(price.client_id)"
|
|
||||||
@apply-price-to-all="applyPriceToAllVariants(price.client_id)"
|
|
||||||
@update:variant="setPriceField(price.client_id, 'variant', $event)"
|
|
||||||
@update:stock="setPriceField(price.client_id, 'stock', $event)"
|
|
||||||
@update:price="setPriceValue(price.client_id, $event)" />
|
|
||||||
|
|
||||||
<FieldError :errors="formErrors(form, 'prices')" />
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex items-center justify-between gap-2 border-t pt-4">
|
|
||||||
<Button type="button" variant="outline" @click="addPrice">
|
|
||||||
<Plus class="size-4" />
|
|
||||||
Tambah Varian
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<div class="flex gap-2">
|
|
||||||
<Button type="button" variant="outline" :disabled="loading" @click="open = false">
|
|
||||||
Batal
|
|
||||||
</Button>
|
|
||||||
<Button type="submit" form="quick-create-raw-material-form" :disabled="loading || isUploading">
|
|
||||||
<Save class="size-4" />
|
|
||||||
{{ isUploading ? 'Mengunggah...' : loading ? 'Menyimpan...' : 'Simpan' }}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div ref="selectPortalTarget" class="contents"></div>
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
</template>
|
|
||||||
@ -370,10 +370,6 @@
|
|||||||
->middleware('permission:'.Permission::CUTTINGS_CREATE->value)
|
->middleware('permission:'.Permission::CUTTINGS_CREATE->value)
|
||||||
->name('draft_combinations.destroy');
|
->name('draft_combinations.destroy');
|
||||||
|
|
||||||
Route::post('quick-create-raw-material', [CuttingDraftItemController::class, 'quickCreateRawMaterial'])
|
|
||||||
->middleware('permission:'.Permission::CUTTINGS_CREATE->value)
|
|
||||||
->name('quick_create_raw_material');
|
|
||||||
|
|
||||||
Route::post('quick-create-product', [CuttingDraftItemController::class, 'quickCreateProduct'])
|
Route::post('quick-create-product', [CuttingDraftItemController::class, 'quickCreateProduct'])
|
||||||
->middleware('permission:'.Permission::CUTTINGS_CREATE->value)
|
->middleware('permission:'.Permission::CUTTINGS_CREATE->value)
|
||||||
->name('quick_create_product');
|
->name('quick_create_product');
|
||||||
|
|||||||
@ -6,11 +6,15 @@
|
|||||||
use App\Models\OwnerVerificationRequest;
|
use App\Models\OwnerVerificationRequest;
|
||||||
use App\Models\Purchase;
|
use App\Models\Purchase;
|
||||||
use App\Models\PurchaseItem;
|
use App\Models\PurchaseItem;
|
||||||
|
use App\Models\RawMaterial;
|
||||||
use App\Models\RawMaterialPrice;
|
use App\Models\RawMaterialPrice;
|
||||||
use App\Models\Supplier;
|
use App\Models\Supplier;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use Database\Seeders\RolePermissionSeeder;
|
use Database\Seeders\RolePermissionSeeder;
|
||||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
|
use Illuminate\Http\UploadedFile;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
uses(RefreshDatabase::class);
|
uses(RefreshDatabase::class);
|
||||||
|
|
||||||
@ -64,7 +68,7 @@ function approveLatestPurchaseVerificationRequest(User $verifier): OwnerVerifica
|
|||||||
function getPurchaseItemsPayload(RawMaterialPrice $price, float $quantity = 2): array
|
function getPurchaseItemsPayload(RawMaterialPrice $price, float $quantity = 2): array
|
||||||
{
|
{
|
||||||
$unit = $price->rawMaterial->unit;
|
$unit = $price->rawMaterial->unit;
|
||||||
$unitVal = $unit instanceof \BackedEnum ? $unit->value : $unit;
|
$unitVal = $unit instanceof BackedEnum ? $unit->value : $unit;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
[
|
[
|
||||||
@ -74,7 +78,7 @@ function getPurchaseItemsPayload(RawMaterialPrice $price, float $quantity = 2):
|
|||||||
'variant' => $price->variant,
|
'variant' => $price->variant,
|
||||||
'quantity' => $quantity,
|
'quantity' => $quantity,
|
||||||
'price' => $price->price,
|
'price' => $price->price,
|
||||||
]
|
],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -791,9 +795,9 @@ function getPurchaseItemsPayload(RawMaterialPrice $price, float $quantity = 2):
|
|||||||
);
|
);
|
||||||
|
|
||||||
$disk = config('filesystems.default') === 's3' ? 's3' : config('filesystems.default', 'public');
|
$disk = config('filesystems.default') === 's3' ? 's3' : config('filesystems.default', 'public');
|
||||||
$key = 'raw-materials/'.\Illuminate\Support\Str::uuid().'.jpg';
|
$key = 'raw-materials/'.Str::uuid().'.jpg';
|
||||||
$imageContent = \Illuminate\Http\UploadedFile::fake()->image("Standard.jpg", 100, 100)->get();
|
$imageContent = UploadedFile::fake()->image('Standard.jpg', 100, 100)->get();
|
||||||
\Illuminate\Support\Facades\Storage::disk($disk)->put($key, $imageContent);
|
Storage::disk($disk)->put($key, $imageContent);
|
||||||
|
|
||||||
$this->actingAs($user)
|
$this->actingAs($user)
|
||||||
->postJson(route('admin.manage.purchases.draft_items.store_new_raw_material'), [
|
->postJson(route('admin.manage.purchases.draft_items.store_new_raw_material'), [
|
||||||
@ -846,11 +850,11 @@ function getPurchaseItemsPayload(RawMaterialPrice $price, float $quantity = 2):
|
|||||||
);
|
);
|
||||||
|
|
||||||
$disk = config('filesystems.default') === 's3' ? 's3' : config('filesystems.default', 'public');
|
$disk = config('filesystems.default') === 's3' ? 's3' : config('filesystems.default', 'public');
|
||||||
$key1 = 'raw-materials/'.\Illuminate\Support\Str::uuid().'.jpg';
|
$key1 = 'raw-materials/'.Str::uuid().'.jpg';
|
||||||
$key2 = 'raw-materials/'.\Illuminate\Support\Str::uuid().'.jpg';
|
$key2 = 'raw-materials/'.Str::uuid().'.jpg';
|
||||||
$imageContent = \Illuminate\Http\UploadedFile::fake()->image("Standard.jpg", 100, 100)->get();
|
$imageContent = UploadedFile::fake()->image('Standard.jpg', 100, 100)->get();
|
||||||
\Illuminate\Support\Facades\Storage::disk($disk)->put($key1, $imageContent);
|
Storage::disk($disk)->put($key1, $imageContent);
|
||||||
\Illuminate\Support\Facades\Storage::disk($disk)->put($key2, $imageContent);
|
Storage::disk($disk)->put($key2, $imageContent);
|
||||||
|
|
||||||
// Add first variant
|
// Add first variant
|
||||||
$this->actingAs($user)
|
$this->actingAs($user)
|
||||||
@ -877,11 +881,11 @@ function getPurchaseItemsPayload(RawMaterialPrice $price, float $quantity = 2):
|
|||||||
->assertOk();
|
->assertOk();
|
||||||
|
|
||||||
// Check that raw_materials table only has 1 record for this name
|
// Check that raw_materials table only has 1 record for this name
|
||||||
$rawMaterialsCount = \App\Models\RawMaterial::where('name', 'Kain Toyobo Unik')->count();
|
$rawMaterialsCount = RawMaterial::where('name', 'Kain Toyobo Unik')->count();
|
||||||
expect($rawMaterialsCount)->toBe(1);
|
expect($rawMaterialsCount)->toBe(1);
|
||||||
|
|
||||||
// Check that raw_material_prices has both variants for the same raw_material_id
|
// Check that raw_material_prices has both variants for the same raw_material_id
|
||||||
$rawMaterial = \App\Models\RawMaterial::where('name', 'Kain Toyobo Unik')->first();
|
$rawMaterial = RawMaterial::where('name', 'Kain Toyobo Unik')->first();
|
||||||
$this->assertDatabaseHas('raw_material_prices', [
|
$this->assertDatabaseHas('raw_material_prices', [
|
||||||
'raw_material_id' => $rawMaterial->id,
|
'raw_material_id' => $rawMaterial->id,
|
||||||
'variant' => 'Standard',
|
'variant' => 'Standard',
|
||||||
@ -901,9 +905,9 @@ function getPurchaseItemsPayload(RawMaterialPrice $price, float $quantity = 2):
|
|||||||
);
|
);
|
||||||
|
|
||||||
$disk = config('filesystems.default') === 's3' ? 's3' : config('filesystems.default', 'public');
|
$disk = config('filesystems.default') === 's3' ? 's3' : config('filesystems.default', 'public');
|
||||||
$key = 'raw-materials/'.\Illuminate\Support\Str::uuid().'.jpg';
|
$key = 'raw-materials/'.Str::uuid().'.jpg';
|
||||||
$imageContent = \Illuminate\Http\UploadedFile::fake()->image("Standard.jpg", 100, 100)->get();
|
$imageContent = UploadedFile::fake()->image('Standard.jpg', 100, 100)->get();
|
||||||
\Illuminate\Support\Facades\Storage::disk($disk)->put($key, $imageContent);
|
Storage::disk($disk)->put($key, $imageContent);
|
||||||
|
|
||||||
// Add standard variant first time (qty = 10)
|
// Add standard variant first time (qty = 10)
|
||||||
$this->actingAs($user)
|
$this->actingAs($user)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user