Compare commits

..

43 Commits

Author SHA1 Message Date
5e98841eea Update app/Models/Ecommerce/Product/Category/Category.php 2026-03-04 23:28:31 +05:00
2b8f9aa314 wip 2026-03-02 07:30:59 +05:00
cb63c54c39 wip 2026-03-02 07:08:57 +05:00
b7bc192e6f wip 2026-03-02 07:01:14 +05:00
06617a0005 wip 2026-02-25 19:24:49 +05:00
da2645fae9 add provinces seeder 2026-02-25 19:07:35 +05:00
066f4d6a53 ignore data dir in db 2026-02-25 19:04:00 +05:00
Mekan1206
1e84ceab3c Refactor code for consistency and clarity; update seeder comments, enhance error handling, and improve API routes. Added 'original' field to ProductMediaResource and adjusted various formatting issues across multiple files. 2026-02-20 15:33:29 +05:00
Mekan1206
a8599d9c79 WIP 2026-02-19 18:11:13 +05:00
Mekan1206
78ef14b99b WIP 2026-02-19 17:57:01 +05:00
Mekan1206
1a63c52106 WIP 2026-02-19 17:55:45 +05:00
Mekan1206
75a6af68eb WIP 2026-02-19 17:49:32 +05:00
Mekan1206
58c1915413 image 2026-02-19 06:22:37 +05:00
Mekan1206
ba537b7868 WIP 2026-02-19 01:08:52 +05:00
Mekan1206
bf69d6733b WIP 2026-02-19 00:47:22 +05:00
Mekan1206
9107d3118d WIP 2026-02-19 00:38:32 +05:00
Mekan1206
fac624b2a5 WIP 2026-02-19 00:35:01 +05:00
Mekan1206
9071c9a4c6 WIP 2026-02-18 23:38:01 +05:00
Mekan1206
18e510674a WIP 2026-02-18 23:37:02 +05:00
Mekan1206
073bbd497b only 400, 800, 1200 image resolutions remained 2026-02-18 23:32:00 +05:00
Mekan1206
ab3e69e831 WIP 2026-02-18 04:23:36 +05:00
Mekan1206
18401b908f WIP 2026-02-18 04:20:05 +05:00
Mekan1206
c712fa07ba WIP 2026-02-18 04:06:40 +05:00
Mekan1206
f011da72d9 WIP 2026-02-18 04:01:28 +05:00
Mekan1206
4a0e24683a WIP 2026-02-15 00:31:16 +05:00
Mekan1206
49ef491e80 WIP 2026-02-14 23:09:52 +05:00
Mekan1206
1880a167b3 Update DatabaseSeeder to include all seeders and add inventory creation in UsersTableSeeder
- Enabled all previously commented-out seeders in DatabaseSeeder for comprehensive data seeding.
- Added inventory creation logic in UsersTableSeeder to initialize a new inventory record for 'Tmpost'.
2026-02-14 00:38:15 +05:00
Mekan1206
ec33006984 Update DatabaseSeeder to include MediaSeeder and enhance 404 error page localization
- Added MediaSeeder to DatabaseSeeder for improved data seeding.
- Updated 404 error page to use localized string for "Page not found" and added links for navigation.
- Added new translation for "Page not found" in tk.json.
2026-02-13 22:51:43 +05:00
Mekan1206
147e7b9516 Update DatabaseSeeder to include MediaSeeder and enhance 404 error page localization
- Added MediaSeeder to DatabaseSeeder for improved data seeding.
- Updated lang/tk.json to include a new translation for "Page not found".
- Refactored 404 error page to utilize localization for the title and home link.
2026-02-11 04:30:32 +05:00
Mekan1206
e70ec773f9 Comment out ProductStocksSeeder in DatabaseSeeder and implement stock updates in ProductStocksSeeder
- Commented out the ProductStocksSeeder in DatabaseSeeder for future adjustments.
- Implemented stock updates in ProductStocksSeeder to update product stock and inventory based on data from stocks.json.
- Enhanced transaction handling and ensured proper sequence setting for inventory_product table.
2026-02-11 03:42:54 +05:00
Mekan1206
e2adf5e9da write me a warner 2026-02-11 03:15:43 +05:00
Mekan1206
d3ac6ff8d9 Comment out ProductPropertyValuesSeeder in DatabaseSeeder for future adjustments 2026-02-11 02:50:49 +05:00
Mekan1206
b0c6a4236c Refactor seeders for improved clarity and functionality
- Reorganized imports in DatabaseSeeder for better structure.
- Enabled ProductCategoryRelationshipsSeeder and ProductBarcodesSeeder in DatabaseSeeder.
- Updated ProductPropertyValuesSeeder to enhance property value handling and added a new method for filling product property values.
- Removed unused imports from ProductBarcodesSeeder, ProductCategoryRelationshipsSeeder, and ProductPropertiesSeeder.
- Ensured consistent formatting by adding missing newlines at the end of files.
2026-02-11 00:59:13 +05:00
Mekan1206
9fac84a882 Enhance product handling and seeding logic
- Added a call to sync properties JSON in UpdateProductRelations job if the method exists.
- Updated ProductFieldsForDetail to provide a default value of '-' for missing property values.
- Simplified the insertion logic in ProductPropertyValuesSeeder by replacing 'tm' with 'tk' in the value field and removed commented-out code for clarity.
2026-02-09 23:15:56 +05:00
Mekan1206
8f49fe0124 404 add animation 2026-02-09 22:41:53 +05:00
Mekan1206
43272bb1b1 nice 404 page 2026-02-09 22:28:57 +05:00
Mekan1206
c48ad83548 Update DatabaseSeeder to include additional seeders and comment out FavoritesSeeder
- Added ProductCategoryRelationshipsSeeder, ProductBarcodesSeeder, SectionsSeeder, ProductPropertiesSeeder, and ProductPropertyValuesSeeder to DatabaseSeeder.
- Commented out FavoritesSeeder and SectionsSeeder for future adjustments.
2026-02-09 03:29:21 +05:00
Mekan1206
b4a05e3f8c Add FavoritesSeeder to DatabaseSeeder for data population 2026-02-09 02:32:52 +05:00
Mekan1206
bac1579285 Remove unused MorphToMany relationships from models and add CategoriesTableSeeder for category data population 2026-02-09 02:26:59 +05:00
Mekan1206
41d6ddc346 Update DatabaseSeeder to include CategoriesTableSeeder and adjust product seeders
- Added CategoriesTableSeeder to the run method in DatabaseSeeder.
- Commented out ProductsTableSeeder and ProductPricesSeeder for future adjustments.
2026-02-09 00:59:04 +05:00
Mekan1206
522ebdae34 Refactor product relations to use new category_product and channel_product tables
- Updated queries and relationships in various controllers and models to replace `product_has_relations` with `category_product` and `channel_product`.
- Adjusted methods in `ProductFilterer`, `FilterParamsController`, and `FilterController` to reflect the new database structure.
- Modified seeder to insert data into the new `channel_product` table.
- Updated tests to ensure compatibility with the new product relation structure.
2026-02-09 00:48:22 +05:00
Mekan1206
683fa5e0d9 Refactor helper functions and update seeders
- Simplified `tmpostChannel` and `tmpostDefaultInventory` functions by removing caching logic.
- Changed return type of `namesWithTaxes` method in `CategoryRepository` to `mixed`.
- Enabled `ProductsTableSeeder` and added user association logic for product relations.
- Updated `BrandsSeeder` to include the product ID in the slug.
- Added `old_customer_id` to `options` in `SellersTableSeeder` for user tracking.
2026-02-08 23:12:40 +05:00
Mekan1206
d83bc03258 WIP 2026-02-08 22:08:53 +05:00
89 changed files with 2426 additions and 188 deletions

View File

@@ -7,6 +7,8 @@ APP_URL=http://localhost
API_TOKEN=123
NOVA_LICENSE_KEY=UEkhFwqhhYw2UPqcoVNWWhKrZOOWXujgj7pLPdDzMqflYX4Pwl
DEBUGBAR_ENABLED=false
LOG_CHANNEL=stack
LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=debug

View File

@@ -5,7 +5,7 @@
# Install posgres and configure
sudo su - postgres
psql
CREATE USER postshop WITH PASSWORD 'ACvL2(F@H^F)D7gs';
CREATE USER postshop WITH PASSWORD 'HRbuBNRBaTPeUfbcFf9XHWay';
CREATE DATABASE postshopdb WITH OWNER = postshop LC_COLLATE = 'en_US.UTF-8' TEMPLATE template0;
# Clone

View File

@@ -18,6 +18,9 @@ class Kernel extends ConsoleKernel
// Remove non saved attachments...
$schedule->call(new PruneStaleAttachments)->daily();
// IF any warnings unresolved warnings exists, send notify me
$schedule->call(new WarnDev)->dailyAt('16:00');
}
/**

25
app/Console/WarnDev.php Normal file
View File

@@ -0,0 +1,25 @@
<?php
namespace App\Console;
use App\Models\System\Warning;
use Illuminate\Console\Command;
class WarnDev extends Command
{
/**
* Notify me if any unresolved warnings exists
*
* @return void
*/
public function __invoke()
{
$warnings = Warning::whereNull('resolved_at')->get();
if ($warnings->isEmpty()) {
return;
}
sendSMS('61929248', 'Warnings: '.$warnings->count());
}
}

View File

@@ -3,7 +3,9 @@
namespace App\Exceptions;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Throwable;
class Handler extends ExceptionHandler
{
@@ -29,5 +31,25 @@ class Handler extends ExceptionHandler
return response()->noContent(404);
}
});
$this->reportable(function (Throwable $e) {
$statusCode = $e instanceof HttpException ? $e->getStatusCode() : 500;
// only real server errors
if ($statusCode < 500) {
return;
}
try {
warn(
message: get_class($e),
content: $e->getMessage(),
where: $e->getFile().':'.$e->getLine(),
notes: substr($e->getTraceAsString(), 0, 65000),
);
} catch (Throwable $ignored) {
// logging must never crash the app
}
});
}
}

View File

@@ -81,11 +81,10 @@ class ProductFilterer
$this->queryBuilder->whereIn(
column: 'id',
values: (
fn ($query) => $query->from('product_has_relations')
fn ($query) => $query->from('category_product')
->select('product_id')
->distinct('product_id')
->where('productable_type', '=', 'category')
->whereIn('productable_id', explode(',', $this->request->categories))
->whereIn('category_id', explode(',', $this->request->categories))
)
);
}

View File

@@ -3,6 +3,7 @@
use App\Models\Auth\Verification;
use App\Models\Ecommerce\Channel\Channel;
use App\Models\Ecommerce\Product\Inventory\Inventory;
use App\Models\System\Warning;
use App\Repositories\Ecommerce\Product\Barcode\BarcodeRepository;
use Illuminate\Http\Client\PendingRequest;
use Illuminate\Support\Collection;
@@ -176,9 +177,7 @@ if (! function_exists('tmpostChannel')) {
*/
function tmpostChannel(): Channel
{
Cache::rememberForever('tmpostChannel', fn () => Channel::tmpostDefault());
return Cache::get('tmpostChannel');
return Channel::tmpostDefault();
}
}
@@ -188,9 +187,7 @@ if (! function_exists('tmpostDefaultInventory')) {
*/
function tmpostDefaultInventory(): mixed
{
Cache::rememberForever('tmpostDefaultInventory', fn () => Inventory::tmpostDefault());
return Cache::get('tmpostDefaultInventory');
return Inventory::tmpostDefault();
}
}
@@ -392,3 +389,16 @@ function createHalkbankOrder($price = 123): array
'url' => $paymentResponse['formUrl'],
];
}
/**
* Warn brother
*/
function warn(string $message, string $content = '', string $where = '', string $notes = ''): void
{
Warning::forceCreate([
'name' => $message,
'content' => $content,
'where' => $where,
'notes' => $notes,
]);
}

View File

@@ -15,9 +15,8 @@ class BrandMediaResource extends JsonResource
public function toArray(Request $request): array
{
return [
'thumbnail' => $this->getUrl('thumb200x200'),
'thumbnail' => $this->getUrl('thumb400x400'),
'images_400x400' => $this->getUrl('thumb400x400'),
'images_720x720' => $this->getUrl('thumb720x720'),
'images_800x800' => $this->getUrl('thumb800x800'),
'images_1200x1200' => $this->getUrl('thumb1200x1200'),
];

View File

@@ -55,7 +55,7 @@ class CategoryController extends Controller
}
/**
* Brands (products)
* Categories (products)
*/
public function products(BasicProductIndexRequest $request, Category $category): JsonResponse
{

View File

@@ -17,7 +17,6 @@ class CategoryMediaResource extends JsonResource
return [
'thumbnail' => $this->getUrl(),
'images_400x400' => $this->getUrl('thumb400x400'),
'images_720x720' => $this->getUrl('thumb720x720'),
'images_800x800' => $this->getUrl('thumb800x800'),
'images_1200x1200' => $this->getUrl('thumb1200x1200'),
];

View File

@@ -0,0 +1,25 @@
<?php
namespace App\Http\Controllers\Api\V1\Category\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class SelectedCategoryResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'name' => $this->name,
'description' => $this->description,
'is_visible' => $this->is_visible,
'categories' => CategoryResource::collection($this->whenLoaded('categories')),
];
}
}

View File

@@ -0,0 +1,37 @@
<?php
namespace App\Http\Controllers\Api\V1\Category;
use App\Http\Controllers\Api\V1\Category\Resources\SelectedCategoryResource;
use App\Http\Controllers\Controller;
use App\Models\Ecommerce\Product\Category\SelectedCategory;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class SelectedCategoryController extends Controller
{
/**
* Selected Categories (index)
*/
public function index(Request $request): JsonResponse
{
$selectedCategories = SelectedCategory::query()
->where('is_visible', true)
->with(['categories' => function ($query) {
$query->where('is_visible', true)->ordered()->with(['media']);
}])
->get();
return response()->json(SelectedCategoryResource::collection($selectedCategories));
}
/**
* Selected Categories (show)
*/
public function show(SelectedCategory $selectedCategory): JsonResponse
{
$selectedCategory->load('categories');
return response()->json(new SelectedCategoryResource($selectedCategory));
}
}

View File

@@ -38,10 +38,9 @@ class FilterParamsController extends Controller
$category_id = (int) $request->category_id;
$brand_ids = DB::table('products')
->join('product_has_relations', function ($join) use ($category_id) {
$join->on('products.id', '=', 'product_has_relations.product_id')
->where('product_has_relations.productable_id', '=', $category_id)
->where('product_has_relations.productable_type', '=', 'category');
->join('category_product', function ($join) use ($category_id) {
$join->on('products.id', '=', 'category_product.product_id')
->where('category_product.category_id', '=', $category_id);
})
->select(['id', 'brand_id'])
->pluck('brand_id');

View File

@@ -97,9 +97,8 @@ class FilterController extends Controller
->distinct('products.id')
->pluck('products.id');
return Category::where('is_visible', true)->ordered()->join('product_has_relations', 'categories.id', '=', 'product_has_relations.productable_id')
->where('product_has_relations.productable_type', '=', 'category')
->whereIntegerInRaw('product_has_relations.product_id', $products)
return Category::where('is_visible', true)->ordered()->join('category_product', 'categories.id', '=', 'category_product.category_id')
->whereIntegerInRaw('category_product.product_id', $products)
->get(['id', 'parent_id', 'name'])
->unique('categories.id');
}

View File

@@ -87,9 +87,8 @@ class CategoriesFilter
->distinct('products.id')
->pluck('products.id');
return $this->queryBuilder->join('product_has_relations', 'categories.id', '=', 'product_has_relations.productable_id')
->where('product_has_relations.productable_type', '=', 'category')
->whereIntegerInRaw('product_has_relations.product_id', $products)
return $this->queryBuilder->join('category_product', 'categories.id', '=', 'category_product.category_id')
->whereIntegerInRaw('category_product.product_id', $products)
->get($this->columns)
->unique('categories.id');
}

View File

@@ -14,7 +14,7 @@ class OrderPaymentController extends Controller
public function index(): JsonResponse
{
return response()->rest(
PaymentType::all(['id', 'name'])
PaymentType::query()->where('is_enabled', true)->get(['id', 'name'])
->map(fn ($paymentType) => [
'id' => $paymentType->id,
'name' => $paymentType->name,

View File

@@ -6,6 +6,7 @@ use App\Http\Controllers\Controller;
use App\Http\Requests\CheckoutOrderRequest;
use App\Http\Resources\Api\V1\Order\OrderIndexResource;
use App\Models\Ecommerce\Product\Order\Order;
use App\Models\Ecommerce\Product\Order\Shipping\OrderShipping;
use App\Repositories\Ecommerce\Order\OrderRepository;
use App\Services\Order\CreateOrderService;
use Illuminate\Http\JsonResponse;
@@ -33,6 +34,27 @@ class OrderController extends Controller
return response()->rest(OrderRepository::availableTimes());
}
/**
* Order deliveries
*/
public function deliveries(): JsonResponse
{
return response()->rest([
[
'name' => OrderShipping::STANDART,
'price' => 20,
],
[
'name' => OrderShipping::SELF_PICKUP,
'price' => 0
],
[
'name' => OrderShipping::REGION,
'price' => 40,
],
]);
}
/**
* (*) Store order
*/

View File

@@ -16,18 +16,17 @@ class ProductRelatedController extends Controller
*/
public function index(Product $product): JsonResponse
{
$products = DB::table('product_has_relations')
->select('product_has_relations.product_id')
->whereIn('product_has_relations.productable_id', (function ($query) use ($product) {
$query->from('product_has_relations')
->select('productable_id')
->distinct('productable_id')
->where('productable_type', '=', 'category')
$products = DB::table('category_product')
->select('category_product.product_id')
->whereIn('category_product.category_id', (function ($query) use ($product) {
$query->from('category_product')
->select('category_id')
->distinct('category_id')
->where('product_id', '=', $product->id);
}))
->limit(12)
->orderByRaw('RANDOM()')
->pluck('product_has_relations.product_id')
->pluck('category_product.product_id')
->unique();
return response()->rest(

View File

@@ -15,9 +15,9 @@ class ProductMediaResource extends JsonResource
public function toArray(Request $request): array
{
return [
'thumbnail' => $this->getUrl('thumb288x431'),
'original' => $this->getUrl(),
'thumbnail' => $this->getUrl('thumb400x400'),
'images_400x400' => $this->getUrl('thumb400x400'),
'images_720x720' => $this->getUrl('thumb720x720'),
'images_800x800' => $this->getUrl('thumb800x800'),
'images_1200x1200' => $this->getUrl('thumb1200x1200'),
];

View File

@@ -40,6 +40,7 @@ class CheckoutOrderRequest extends FormRequest
'customer_address' => ['required', 'string', 'max:255'],
'shipping_method' => ['required', 'string', 'max:255', Rule::in(array_keys(OrderShipping::values()))],
'shipping_price' => ['nullable', 'numeric'],
'payment_type_id' => ['required', Rule::in(array_keys(OrderPayment::values()))],
'notes' => ['nullable', 'string', 'max:255'],
@@ -65,7 +66,7 @@ class CheckoutOrderRequest extends FormRequest
'user_id' => auth()->id(),
'notes' => $this->notes ?: null,
'province_id' => $this->province_id ?: null,
'shipping_price' => OrderShipping::priceFor($this->shipping_method),
'shipping_price' => $this->shipping_price ?: OrderShipping::priceFor($this->shipping_method),
'delivery_time' => $this->delivery_time ?: OrderShipping::MORNING,
'delivery_at' => $this->delivery_at ?: date('Y-m-d'),
'source' => $this->source ?: OS::MOBILE_APP,

View File

@@ -17,7 +17,6 @@ class ChannelMediaResource extends JsonResource
return [
'thumbnail' => $this->getUrl(),
'images_400x400' => $this->getUrl('thumb400x400'),
'images_720x720' => $this->getUrl('thumb720x720'),
'images_800x800' => $this->getUrl('thumb800x800'),
'images_1200x1200' => $this->getUrl('thumb1200x1200'),
];

View File

@@ -15,9 +15,8 @@ class MediaResource extends JsonResource
public function toArray(Request $request): array
{
return [
'thumbnail' => $this->getUrl('thumb720x720'),
'thumbnail' => $this->getUrl('thumb400x400'),
'images_400x400' => $this->getUrl('thumb400x400'),
'images_720x720' => $this->getUrl('thumb720x720'),
'images_800x800' => $this->getUrl('thumb800x800'),
'images_1200x1200' => $this->getUrl('thumb1200x1200'),
];

View File

@@ -59,6 +59,11 @@ class UpdateProductRelations implements ShouldQueue
'product_custom_value' => in_array($attribute->type, ['text', 'number']) ? $this->properties[$attribute->slug] : null,
]);
});
// sync properties json
if (method_exists($this->model, 'syncPropertiesJson')) {
$this->model->syncPropertiesJson();
}
});
}
}

View File

@@ -106,24 +106,15 @@ class Banner extends Model implements HasMedia
*/
public function registerMediaConversions(?Media $media = null): void
{
$this->addMediaConversion('thumb200x200')
->fit(Manipulations::FIT_CONTAIN, 200, 200);
$this->addMediaConversion('thumb400x400')
->fit(Manipulations::FIT_CONTAIN, 400, 400);
$this->addMediaConversion('thumb350x350')
->fit(Manipulations::FIT_CONTAIN, 350, 350);
$this->addMediaConversion('thumb720x720')
->fit(Manipulations::FIT_CONTAIN, 720, 720);
$this->addMediaConversion('thumb800x800')
->fit(Manipulations::FIT_CONTAIN, 800, 800);
$this->addMediaConversion('thumb1200x1200')
->fit(Manipulations::FIT_CONTAIN, 1200, 1200);
$this->addMediaConversion('thumb288x431')
->fit(Manipulations::FIT_CONTAIN, 288, 431);

View File

@@ -11,9 +11,9 @@ use App\Repositories\System\Cache\CacheRepository;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\MorphTo;
use Illuminate\Database\Eloquent\Relations\MorphToMany;
use Laravel\Nova\Nova;
use Spatie\EloquentSortable\Sortable;
use Spatie\EloquentSortable\SortableTrait;
@@ -109,29 +109,14 @@ class Channel extends Model implements HasMedia, Sortable
*/
public function registerMediaConversions(?Media $media = null): void
{
$this->addMediaConversion('thumb150x150')
->fit(Manipulations::FIT_CONTAIN, 150, 150);
$this->addMediaConversion('thumb200x200')
->fit(Manipulations::FIT_CONTAIN, 200, 200);
$this->addMediaConversion('thumb400x400')
->fit(Manipulations::FIT_CONTAIN, 400, 400);
$this->addMediaConversion('thumb720x720')
->fit(Manipulations::FIT_CONTAIN, 720, 720);
$this->addMediaConversion('thumb800x800')
->fit(Manipulations::FIT_CONTAIN, 800, 800);
$this->addMediaConversion('thumb1200x1200')
->fit(Manipulations::FIT_CONTAIN, 1200, 1200);
$this->addMediaConversion('thumb288x431')
->fit(Manipulations::FIT_CONTAIN, 288, 431);
$this->addMediaConversion('thumb270x350')
->fit(Manipulations::FIT_CONTAIN, 270, 350);
}
/**
@@ -179,9 +164,9 @@ class Channel extends Model implements HasMedia, Sortable
/**
* Products
*/
public function products(): MorphToMany
public function products(): BelongsToMany
{
return $this->morphToMany(Product::class, 'productable', 'product_has_relations');
return $this->belongsToMany(Product::class, 'channel_product');
}
/**

View File

@@ -85,26 +85,14 @@ class Brand extends Model implements HasMedia, Sortable
*/
public function registerMediaConversions(?Media $media = null): void
{
$this->addMediaConversion('thumb200x200')
->fit(Manipulations::FIT_CONTAIN, 200, 200);
$this->addMediaConversion('thumb400x400')
->fit(Manipulations::FIT_CONTAIN, 400, 400);
$this->addMediaConversion('thumb720x720')
->fit(Manipulations::FIT_CONTAIN, 720, 720);
$this->addMediaConversion('thumb800x800')
->fit(Manipulations::FIT_CONTAIN, 800, 800);
$this->addMediaConversion('thumb1200x1200')
->fit(Manipulations::FIT_CONTAIN, 1200, 1200);
$this->addMediaConversion('thumb288x431')
->fit(Manipulations::FIT_CONTAIN, 288, 431);
$this->addMediaConversion('thumb270x350')
->fit(Manipulations::FIT_CONTAIN, 270, 350);
}
/**

View File

@@ -10,7 +10,6 @@ use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\MorphToMany;
use Spatie\EloquentSortable\Sortable;
use Spatie\EloquentSortable\SortableTrait;
use Spatie\Image\Manipulations;
@@ -83,7 +82,6 @@ class Category extends Model implements HasMedia, Sortable
{
$this->addMediaCollection('uploads')
->singleFile()
->acceptsMimeTypes(['image/jpg', 'image/jpeg', 'image/png'])
->useFallbackUrl(
sprintf('%s/logo-space.png', config('app.url'))
);
@@ -94,29 +92,14 @@ class Category extends Model implements HasMedia, Sortable
*/
public function registerMediaConversions(?Media $media = null): void
{
$this->addMediaConversion('thumb200x200')
->fit(Manipulations::FIT_CONTAIN, 200, 200);
$this->addMediaConversion('thumb400x400')
->fit(Manipulations::FIT_CONTAIN, 400, 400);
$this->addMediaConversion('thumb720x720')
->fit(Manipulations::FIT_CONTAIN, 720, 720);
$this->addMediaConversion('thumb800x800')
->fit(Manipulations::FIT_CONTAIN, 800, 800);
$this->addMediaConversion('thumb1200x1200')
->fit(Manipulations::FIT_CONTAIN, 1200, 1200);
$this->addMediaConversion('thumb288x431')
->fit(Manipulations::FIT_CONTAIN, 288, 431);
$this->addMediaConversion('thumb270x350')
->fit(Manipulations::FIT_CONTAIN, 270, 350);
$this->addMediaConversion('thumb657x230')
->fit(Manipulations::FIT_CROP, 657, 230);
}
/**
@@ -152,9 +135,9 @@ class Category extends Model implements HasMedia, Sortable
/**
* Category Products
*/
public function products(): MorphToMany
public function products(): BelongsToMany
{
return $this->morphToMany(Product::class, 'productable', 'product_has_relations');
return $this->belongsToMany(Product::class, 'category_product');
}
/**

View File

@@ -0,0 +1,31 @@
<?php
namespace App\Models\Ecommerce\Product\Category;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Spatie\Translatable\HasTranslations;
class SelectedCategory extends Model
{
use HasFactory;
use HasTranslations;
protected $fillable = [
'name',
'description',
'is_visible',
];
public $translatable = ['name', 'description'];
protected $casts = [
'is_visible' => 'boolean',
];
public function categories(): BelongsToMany
{
return $this->belongsToMany(Category::class, 'category_selected_category');
}
}

View File

@@ -5,7 +5,7 @@ namespace App\Models\Ecommerce\Product\Collection;
use App\Models\Ecommerce\Product\Product\Product;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphToMany;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Spatie\EloquentSortable\Sortable;
use Spatie\EloquentSortable\SortableTrait;
use Spatie\Image\Manipulations;
@@ -84,34 +84,22 @@ class Collection extends Model implements HasMedia, Sortable
*/
public function registerMediaConversions(?Media $media = null): void
{
$this->addMediaConversion('thumb200x200')
->fit(Manipulations::FIT_CONTAIN, 200, 200);
$this->addMediaConversion('thumb400x400')
->fit(Manipulations::FIT_CONTAIN, 400, 400);
$this->addMediaConversion('thumb720x720')
->fit(Manipulations::FIT_CONTAIN, 720, 720);
$this->addMediaConversion('thumb800x800')
->fit(Manipulations::FIT_CONTAIN, 800, 800);
$this->addMediaConversion('thumb1200x1200')
->fit(Manipulations::FIT_CONTAIN, 1200, 1200);
$this->addMediaConversion('thumb288x431')
->fit(Manipulations::FIT_CONTAIN, 288, 431);
$this->addMediaConversion('thumb270x350')
->fit(Manipulations::FIT_CONTAIN, 270, 350);
}
/**
* Products
*/
public function products(): MorphToMany
public function products(): BelongsToMany
{
return $this->morphToMany(Product::class, 'productable', 'product_has_relations');
return $this->belongsToMany(Product::class, 'collection_product');
}
/**

View File

@@ -2,6 +2,7 @@
namespace App\Models\Ecommerce\Product\Order;
use App\Models\Concerns\HasSchemalessAttributes;
use App\Models\Ecommerce\Product\Order\Concerns\HasPayments;
use App\Models\Ecommerce\Product\Order\Concerns\HasShipping;
use App\Models\Ecommerce\Product\Order\Concerns\HasStatus;
@@ -19,6 +20,7 @@ class Order extends Model
{
use HasFactory;
use HasPayments;
use HasSchemalessAttributes;
use HasShipping;
use HasStatus;
use SoftDeletes;

View File

@@ -21,32 +21,20 @@ trait ProductMedia
*/
public function registerMediaConversions(?Media $media = null): void
{
$this->addMediaConversion('thumb200x200')
->fit(Manipulations::FIT_CONTAIN, 200, 200);
$this->addMediaConversion('thumb400x400')
->fit(Manipulations::FIT_CONTAIN, 400, 400);
$this->addMediaConversion('thumb720x720')
->fit(Manipulations::FIT_CONTAIN, 720, 720);
$this->addMediaConversion('thumb800x800')
->fit(Manipulations::FIT_CONTAIN, 800, 800);
$this->addMediaConversion('thumb1200x1200')
->fit(Manipulations::FIT_CONTAIN, 1200, 1200);
$this->addMediaConversion('thumb288x431')
->fit(Manipulations::FIT_CONTAIN, 288, 431);
$this->addMediaConversion('thumb270x350')
->fit(Manipulations::FIT_CONTAIN, 270, 350);
}
/**
* Thumbnail
*/
public function thumbnail(string $size = '200x200'): string
public function thumbnail(string $size = '400x400'): string
{
return $this->getFirstMediaUrl('uploads', 'thumb'.$size);
}
@@ -54,7 +42,7 @@ trait ProductMedia
/**
* Get image when hovered (returns second image)
*/
public function getHoverImage(string $size = '270x350'): string
public function getHoverImage(string $size = '400x400'): string
{
$media = $this->getMedia('uploads');
$image_count = $media->count();

View File

@@ -15,7 +15,6 @@ use App\Models\Ecommerce\Product\Review\Review;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\MorphToMany;
trait ProductRelationships
{
@@ -38,9 +37,9 @@ trait ProductRelationships
/**
* Related Channels
*/
public function channels(): MorphToMany
public function channels(): BelongsToMany
{
return $this->morphedByMany(Channel::class, 'productable', 'product_has_relations');
return $this->belongsToMany(Channel::class, 'channel_product');
}
/**
@@ -56,25 +55,25 @@ trait ProductRelationships
/**
* Related products (similar)
*/
public function relatedProducts(): MorphToMany
public function relatedProducts(): BelongsToMany
{
return $this->morphedByMany(Product::class, 'productable', 'product_has_relations');
return $this->belongsToMany(Product::class, 'product_related', 'product_id', 'related_product_id');
}
/**
* Related categories
*/
public function categories(): MorphToMany
public function categories(): BelongsToMany
{
return $this->morphedByMany(Category::class, 'productable', 'product_has_relations');
return $this->belongsToMany(Category::class, 'category_product');
}
/**
* Related Collections
*/
public function collections(): MorphToMany
public function collections(): BelongsToMany
{
return $this->morphedByMany(Collection::class, 'productable', 'product_has_relations');
return $this->belongsToMany(Collection::class, 'collection_product');
}
/**

View File

@@ -62,7 +62,7 @@ class MostSoldProducts extends Lens
{
return [
ID::make(__('ID'), 'id')->sortable(),
Images::make(__('Image'), 'uploads')->conversionOnIndexView('thumb200x200'),
Images::make(__('Image'), 'uploads')->conversionOnIndexView('thumb400x400'),
Text::make(__('Name'), 'name')->sortable(),
Number::make(__('Price'), 'price_amount')->sortable(),

View File

@@ -81,7 +81,7 @@ class Banner extends Resource
ID::make()->sortable(),
Images::make(__('Image'), 'main')
->conversionOnIndexView('thumb200x200')
->conversionOnIndexView('thumb400x400')
->rules('required')
->required(),

View File

@@ -90,7 +90,7 @@ class Channel extends Resource
ID::make()->sortable(),
Images::make(__('Image'), 'uploads')
->conversionOnIndexView('thumb200x200')
->conversionOnIndexView('thumb400x400')
->rules('required')
->required(),

View File

@@ -18,7 +18,7 @@ class ChannelFieldsForIndex
ID::make()->hidden(),
Images::make(__('Image'), 'uploads')
->conversionOnIndexView('thumb200x200'),
->conversionOnIndexView('thumb400x400'),
Text::make(__('Name'), 'name')
->sortable(),

View File

@@ -96,7 +96,7 @@ class Brand extends Resource
ID::make()->sortable(),
Images::make(__('Image'), 'uploads')
->conversionOnIndexView('thumb200x200')
->conversionOnIndexView('thumb400x400')
->rules('required')
->required(),

View File

@@ -83,7 +83,7 @@ class Category extends Resource
ID::make()->sortable(),
Images::make(__('Image'), 'uploads')
->conversionOnIndexView('thumb200x200')
->conversionOnIndexView('thumb400x400')
->rules('required')
->required(),

View File

@@ -0,0 +1,120 @@
<?php
namespace App\Nova\Resources\Ecommerce\Product\Category;
use App\Models\Ecommerce\Product\Category\SelectedCategory as SelectedCategoryModel;
use App\Nova\Resource;
use Laravel\Nova\Fields\BelongsToMany;
use Laravel\Nova\Fields\ID;
use Laravel\Nova\Fields\Text;
use Laravel\Nova\Fields\Textarea;
use Laravel\Nova\Http\Requests\NovaRequest;
use Trin4ik\NovaSwitcher\NovaSwitcher;
class SelectedCategory extends Resource
{
/**
* The model the resource corresponds to.
*
* @var class-string<SelectedCategoryModel>
*/
public static $model = SelectedCategoryModel::class;
/**
* The single value that should be used to represent the resource when being displayed.
*
* @var string
*/
public static $title = 'name';
/**
* The columns that should be searched.
*
* @var array
*/
public static $search = [
'id', 'name',
];
/**
* Get the displayable label of the resource.
*/
public static function label(): string
{
return __('Sections');
}
/**
* Get the displayable singular label of the resource.
*/
public static function singularLabel(): string
{
return __('Section');
}
/**
* Get the fields displayed by the resource.
*
* @return array
*/
public function fields(NovaRequest $request)
{
return [
ID::make()->sortable(),
Text::make(__('Name'), 'name')
->sortable()
->translatable()
->rules('required'),
Textarea::make(__('Description'), 'description')
->translatable()
->nullable(),
NovaSwitcher::make(__('Is Visible'), 'is_visible')
->default(true),
BelongsToMany::make(__('Categories'), 'categories', Category::class),
];
}
/**
* Get the cards available for the request.
*
* @return array
*/
public function cards(NovaRequest $request)
{
return [];
}
/**
* Get the filters available for the resource.
*
* @return array
*/
public function filters(NovaRequest $request)
{
return [];
}
/**
* Get the lenses available for the resource.
*
* @return array
*/
public function lenses(NovaRequest $request)
{
return [];
}
/**
* Get the actions available for the resource.
*
* @return array
*/
public function actions(NovaRequest $request)
{
return [];
}
}

View File

@@ -76,7 +76,7 @@ class Collection extends Resource
ID::make()->sortable(),
Images::make(__('Image'), 'uploads')
->conversionOnIndexView('thumb200x200')
->conversionOnIndexView('thumb400x400')
->rules('required')
->required(),

View File

@@ -81,7 +81,7 @@ class ProductResource extends Resource
{
return [
ID::make()->sortable(),
Images::make(__('Image'), 'uploads')->conversionOnIndexView('thumb200x200'),
Images::make(__('Image'), 'uploads')->conversionOnIndexView('thumb400x400'),
Text::make(__('Name'), fn () => $this->novaDetailPage())
->displayUsing(FieldHelpers::asLink(
link: $this->novaDetailPage(),

View File

@@ -27,7 +27,7 @@ class OrderFieldsForCreate
return [
Hidden::make('number')->default(Str::random(30)),
Hidden::make('user_id')->default($request->user()->id),
Hidden::make('source_app')->default(OS::ADMIN),
Hidden::make('source')->default(OS::ADMIN),
ID::make(),

View File

@@ -67,7 +67,7 @@ class OrderFieldsForDetail
)
->asHtml(),
Select::make(__('App'), 'source_app')
Select::make(__('App'), 'source')
->displayUsingLabels()
->options(OS::apps())
->sortable(),

View File

@@ -55,7 +55,7 @@ class OrderFieldsForIndex
->default(OrderShipping::default())
->sortable(),
Select::make(__('Source'), 'source_app')
Select::make(__('Source'), 'source')
->displayUsingLabels()
->options(OS::apps())
->sortable(),

View File

@@ -93,7 +93,7 @@ class ProductFieldsForCreate
->rules('nullable', 'string'),
Images::make(__('Image'), 'uploads')
->conversionOnIndexView('thumb200x200')
->conversionOnIndexView('thumb400x400')
->rules('required')
->setFileName(NovaForm::fillMediaFileName())
->required(),

View File

@@ -53,7 +53,7 @@ class ProductFieldsForDetail
Text::make(__('Name'), 'name'),
Images::make(__('Image'), 'uploads')
->conversionOnIndexView('thumb200x200'),
->conversionOnIndexView('thumb400x400'),
Trix::make(__('Description'), 'description')->withFiles('public')->alwaysShow(),
Text::make(__('Price'), 'cost_amount'),
@@ -134,8 +134,8 @@ class ProductFieldsForDetail
'name' => $property->attribute->slug,
'type' => $property->attribute->type,
'default' => $property->attribute->type === 'select'
? $property->values->first()->value?->id
: $property->values->first()->product_custom_value,
? $property->values->first()->value?->id ?? '-'
: $property->values->first()->product_custom_value ?? '-',
'options' => $property->attribute->values->map(fn ($value) => [
'label' => $value->value,
'value' => $value->id,

View File

@@ -26,7 +26,7 @@ class ProductFieldsForIndex
return [
ID::make()->sortable(),
Images::make(__('Image'), 'uploads')->conversionOnIndexView('thumb200x200'),
Images::make(__('Image'), 'uploads')->conversionOnIndexView('thumb400x400'),
Text::make(__('Name'), 'name')->sortable(),
BelongsTo::make(__('Brand'), 'brand', Brand::class)

View File

@@ -32,7 +32,7 @@ class VariantFieldsForDetail
BelongsTo::make(__('Parent'), 'parent', Product::class),
Images::make(__('Image'), 'uploads')
->conversionOnIndexView('thumb200x200'),
->conversionOnIndexView('thumb400x400'),
Text::make(__('Price'), 'cost_amount')
->rules('required', 'numeric'),

View File

@@ -20,7 +20,7 @@ class VariantFieldsForIndex
ID::make()->sortable(),
Images::make(__('Image'), 'uploads')
->conversionOnIndexView('thumb200x200'),
->conversionOnIndexView('thumb400x400'),
Text::make(__('Price'), 'cost_amount')
->rules('required', 'numeric'),

View File

@@ -33,9 +33,8 @@ class ProductEntrepreneurFilter extends Filter
*/
public function apply(NovaRequest $request, $query, $value)
{
$vendorProducts = DB::table('product_has_relations')
->where('productable_type', 'channel')
->where('productable_id', $value)
$vendorProducts = DB::table('channel_product')
->where('channel_id', $value)
->pluck('product_id');
$query->whereIntegerInRaw('id', $vendorProducts);

View File

@@ -123,9 +123,8 @@ class Product extends Resource
$user = $request->user();
if ($user->hasRole('vendor')) {
$vendorProducts = DB::table('product_has_relations')
->where('productable_type', 'channel')
->where('productable_id', $user->channel()->id)
$vendorProducts = DB::table('channel_product')
->where('channel_id', $user->channel()->id)
->pluck('product_id');
$query->whereIntegerInRaw('id', $vendorProducts);

View File

@@ -64,8 +64,8 @@ class ChannelPolicy
*/
public function update(User $user, Channel $channel): Response
{
if ($user->hasRole(['admin'])) {
return $this->allow();
if (tmpostChannel()->slug === $channel->slug) {
return $this->deny();
}
return $this->deny();

View File

@@ -15,6 +15,7 @@ use App\Nova\Resources\Ecommerce\Payout\PayoutResource;
use App\Nova\Resources\Ecommerce\Product\Attribute\Attribute;
use App\Nova\Resources\Ecommerce\Product\Brand\Brand;
use App\Nova\Resources\Ecommerce\Product\Category\Category;
use App\Nova\Resources\Ecommerce\Product\Category\SelectedCategory;
use App\Nova\Resources\Ecommerce\Product\Collection\Collection;
use App\Nova\Resources\Ecommerce\Product\Coupon\Coupon;
use App\Nova\Resources\Ecommerce\Product\Inventory\Inventory;
@@ -158,6 +159,7 @@ class NovaServiceProvider extends NovaApplicationServiceProvider
MenuItem::resource(Category::class),
MenuItem::resource(Brand::class),
MenuItem::resource(Attribute::class),
MenuItem::resource(SelectedCategory::class),
])->icon('color-swatch'),
])->icon('shopping-bag')->collapsedByDefault(),

View File

@@ -3,7 +3,6 @@
namespace App\Repositories\Ecommerce\Product\Category;
use App\Models\Ecommerce\Product\Category\Category;
use App\Repositories\System\Cache\CacheRepository;
use Illuminate\Http\Request;
class CategoryRepository
@@ -124,16 +123,13 @@ class CategoryRepository
/**
* Names with taxes (usefull for nova)
*/
public static function namesWithTaxes(): array
public static function namesWithTaxes(): mixed
{
return CacheRepository::make(
name: 'cs-nova-models-categories',
value: fn () => static::maskParentName(
Category::tree()
->where('is_visible', true)
->get(['id', 'slug', 'name', 'tax_percentage', 'parent_id', 'is_visible'])
->toTree()
)
return static::maskParentName(
Category::tree()
->where('is_visible', true)
->get(['id', 'slug', 'name', 'tax_percentage', 'parent_id', 'is_visible'])
->toTree()
);
}
@@ -181,9 +177,8 @@ class CategoryRepository
])) {
$products = $resource->products()->distinct('products.id')->pluck('products.id');
return $this->queryBuilder->join('product_has_relations', 'categories.id', '=', 'product_has_relations.productable_id')
->where('product_has_relations.productable_type', '=', 'category')
->whereIntegerInRaw('product_has_relations.product_id', $products)
return $this->queryBuilder->join('category_product', 'categories.id', '=', 'category_product.category_id')
->whereIntegerInRaw('category_product.product_id', $products)
->distinct('categories.id')
->get(['id', 'slug', 'name']);
}

View File

@@ -18,7 +18,6 @@ class CreateOrderService
{
return DB::transaction(function () use ($user, $data) {
// 1. Create the order
info(['service' => $data]);
$order = Order::create($data);
// 2. Process Cart Items
@@ -41,10 +40,24 @@ class CreateOrderService
'updated_at' => now(),
]);
// Update Stock
// Update Stock directly in products table, also relationship
$stock = $cart->product->stock - $cart->product_quantity;
$cart->product->update([
'stock' => $cart->product->stock - $cart->product_quantity,
'stock' => $stock,
]);
$data = DB::table('inventory_product')->where('product_id', $cart->product_id)->first();
if ($data) {
DB::table('inventory_product')->where('id', $data->id)->update([
'stock' => $stock,
]);
} else {
warn('Product has no inventory record', json_encode([
'product_id' => $cart->product_id,
'order_id' => $order->id,
]));
}
});
// 3. Clear User Cart

View File

@@ -0,0 +1,28 @@
<?php
namespace App\Support;
use Illuminate\Support\Str;
use Spatie\MediaLibrary\Conversions\Conversion;
use Spatie\MediaLibrary\Support\FileNamer\FileNamer;
class ShortFileNamer extends FileNamer
{
/**
* Generate a short random name for the original file.
*/
public function originalFileName(string $fileName): string
{
return Str::random(10);
}
public function conversionFileName(string $fileName, Conversion $conversion): string
{
return $conversion->getName();
}
public function responsiveFileName(string $fileName): string
{
return 'res';
}
}

View File

@@ -58,7 +58,7 @@ return [
/*
* This is the class that is responsible for naming generated files.
*/
'file_namer' => Spatie\MediaLibrary\Support\FileNamer\DefaultFileNamer::class,
'file_namer' => App\Support\ShortFileNamer::class,
/*
* The class that contains the strategy for determining a media file's path.

3
database/data/.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
*
!provinces.json
!.gitignore

View File

@@ -0,0 +1,716 @@
[
{
"id": 1,
"region": "ah",
"name": "{\"en\": \"Ak bugday\", \"ru\": \"Ак бугдай\", \"tk\": \"Ak bugdaý\"}",
"created_at": "2022-11-15 14:24:55",
"updated_at": "2022-11-15 14:24:55"
},
{
"id": 2,
"region": "ah",
"name": "{\"en\": \"Tejen\", \"ru\": \"Теджен\", \"tk\": \"Tejen\"}",
"created_at": "2022-11-15 14:26:35",
"updated_at": "2022-11-15 14:26:35"
},
{
"id": 3,
"region": "ah",
"name": "{\"en\": \"Kaka\", \"ru\": \"Кака\", \"tk\": \"Kaka\"}",
"created_at": "2022-11-15 14:28:01",
"updated_at": "2022-11-15 14:28:01"
},
{
"id": 4,
"region": "ag",
"name": "{\"en\": \"Bagtyyarlyk\", \"ru\": \"Багтыярлык\", \"tk\": \"Bagtyýarlyk\"}",
"created_at": "2022-11-15 15:08:49",
"updated_at": "2022-11-15 15:08:49"
},
{
"id": 5,
"region": "ag",
"name": "{\"en\": \"Kopetdag\", \"ru\": \"Копетдаг\", \"tk\": \"Köpetdag\"}",
"created_at": "2022-11-15 15:10:33",
"updated_at": "2022-11-15 15:10:33"
},
{
"id": 6,
"region": "ag",
"name": "{\"en\": \"Berkararlyk\", \"ru\": \"Беркарарлык\", \"tk\": \"Berkararlyk\"}",
"created_at": "2022-11-15 15:11:56",
"updated_at": "2022-11-15 15:11:56"
},
{
"id": 7,
"region": "ag",
"name": "{\"en\": \"Buzmeyin\", \"ru\": \"Бузмеин\", \"tk\": \"Büzmeýin\"}",
"created_at": "2022-11-15 15:13:11",
"updated_at": "2022-11-15 15:13:11"
},
{
"id": 8,
"region": "ah",
"name": "{\"en\": \"Babadayhan\", \"ru\": \"Бабадайхан\", \"tk\": \"Babadaýhan\"}",
"created_at": "2022-11-15 15:18:43",
"updated_at": "2022-11-15 15:18:43"
},
{
"id": 9,
"region": "ah",
"name": "{\"en\": \"Gokdepe\", \"ru\": \"Геёкдепе\", \"tk\": \"Gökdepe\"}",
"created_at": "2022-11-15 15:20:02",
"updated_at": "2022-11-15 15:20:02"
},
{
"id": 10,
"region": "ah",
"name": "{\"en\": \"Baherden\", \"ru\": \"Бахерден\", \"tk\": \"Bäherden\"}",
"created_at": "2022-11-15 15:21:20",
"updated_at": "2022-11-15 15:21:20"
},
{
"id": 11,
"region": "ah",
"name": "{\"en\": \"Sarahs\", \"ru\": \"Сарахс\", \"tk\": \"Sarahs\"}",
"created_at": "2022-11-15 15:23:04",
"updated_at": "2022-11-15 15:23:04"
},
{
"id": 12,
"region": "mr",
"name": "{\"en\": \"Mary\", \"ru\": \"Мары\", \"tk\": \"Mary\"}",
"created_at": "2022-11-15 15:24:10",
"updated_at": "2022-11-15 15:24:10"
},
{
"id": 13,
"region": "mr",
"name": "{\"en\": \"Garagum\", \"ru\": \"Гарагум\", \"tk\": \"Garagum\"}",
"created_at": "2022-11-15 15:25:22",
"updated_at": "2022-11-15 15:25:22"
},
{
"id": 14,
"region": "mr",
"name": "{\"en\": \"Vekilbazar\", \"ru\": \"Векилбазар\", \"tk\": \"Wekilbazar\"}",
"created_at": "2022-11-15 15:26:42",
"updated_at": "2022-11-15 15:26:42"
},
{
"id": 15,
"region": "mr",
"name": "{\"en\": \"Yoloten\", \"ru\": \"Ёлотен\", \"tk\": \"Yolöten\"}",
"created_at": "2022-11-15 15:27:45",
"updated_at": "2022-11-15 15:27:45"
},
{
"id": 16,
"region": "mr",
"name": "{\"en\": \"Bayramaly\", \"ru\": \"Байрамалы\", \"tk\": \"Baýramaly\"}",
"created_at": "2022-11-15 15:28:49",
"updated_at": "2022-11-15 15:28:49"
},
{
"id": 17,
"region": "mr",
"name": "{\"en\": \"Murgap\", \"ru\": \"Мургап\", \"tk\": \"Murgap\"}",
"created_at": "2022-11-15 15:30:27",
"updated_at": "2022-11-15 15:30:27"
},
{
"id": 18,
"region": "mr",
"name": "{\"en\": \"Sakarchage\", \"ru\": \"Сакарчаге\", \"tk\": \"Sakarçäge\"}",
"created_at": "2022-11-15 15:31:42",
"updated_at": "2022-11-15 15:31:42"
},
{
"id": 19,
"region": "mr",
"name": "{\"en\": \"Tagtabazar\", \"ru\": \"Тагтабазар\", \"tk\": \"Tagtabazar\"}",
"created_at": "2022-11-15 15:32:53",
"updated_at": "2022-11-15 15:32:53"
},
{
"id": 20,
"region": "mr",
"name": "{\"en\": \"Turkmengala\", \"ru\": \"Туркменгала\", \"tk\": \"Türkmengala\"}",
"created_at": "2022-11-15 15:33:59",
"updated_at": "2022-11-15 15:33:59"
},
{
"id": 21,
"region": "bn",
"name": "{\"en\": \"Etrek\", \"ru\": \"Етрек\", \"tk\": \"Etrek\"}",
"created_at": "2022-11-15 15:35:37",
"updated_at": "2022-11-15 15:35:37"
},
{
"id": 22,
"region": "bn",
"name": "{\"en\": \"Turkmenbashy\", \"ru\": \"Туркменбащы\", \"tk\": \"Türkmenbaşy\"}",
"created_at": "2022-11-15 15:38:29",
"updated_at": "2022-11-15 15:38:29"
},
{
"id": 23,
"region": "bn",
"name": "{\"en\": \"Esenguly\", \"ru\": \"Эсенгулы\", \"tk\": \"Esenguly\"}",
"created_at": "2022-11-15 15:39:37",
"updated_at": "2022-11-15 15:39:37"
},
{
"id": 24,
"region": "bn",
"name": "{\"en\": \"Gyzylarbat\", \"ru\": \"Гызыларбат\", \"tk\": \"Gyzylarbat\"}",
"created_at": "2022-11-15 15:40:50",
"updated_at": "2022-11-15 15:40:50"
},
{
"id": 25,
"region": "bn",
"name": "{\"en\": \"Bereket\", \"ru\": \"Берекет\", \"tk\": \"Bereket\"}",
"created_at": "2022-11-15 15:41:39",
"updated_at": "2022-11-15 15:41:39"
},
{
"id": 26,
"region": "bn",
"name": "{\"en\": \"Magtymguly\", \"ru\": \"Магтымгулы\", \"tk\": \"Magtymguly\"}",
"created_at": "2022-11-15 15:42:46",
"updated_at": "2022-11-15 15:42:46"
},
{
"id": 27,
"region": "dz",
"name": "{\"en\": \"Gorogly\", \"ru\": \"Гёроглы\", \"tk\": \"Görogly\"}",
"created_at": "2022-11-15 15:44:05",
"updated_at": "2022-11-15 15:44:05"
},
{
"id": 28,
"region": "dz",
"name": "{\"en\": \"Akdepe\", \"ru\": \"Aкдепе\", \"tk\": \"Akdepe\"}",
"created_at": "2022-11-15 15:44:53",
"updated_at": "2022-11-15 15:44:53"
},
{
"id": 29,
"region": "dz",
"name": "{\"en\": \"Ruhybelent\", \"ru\": \"Рухыбелент\", \"tk\": \"Ruhybelent\"}",
"created_at": "2022-11-15 15:45:43",
"updated_at": "2022-11-15 15:45:43"
},
{
"id": 30,
"region": "dz",
"name": "{\"en\": \"Boldumsaz\", \"ru\": \"Болдумсаз\", \"tk\": \"Boldumsaz\"}",
"created_at": "2022-11-15 15:46:38",
"updated_at": "2022-11-15 15:46:38"
},
{
"id": 31,
"region": "dz",
"name": "{\"en\": \"Koneurgench\", \"ru\": \"Конеугенч\", \"tk\": \"Köneürgenç\"}",
"created_at": "2022-11-15 15:48:00",
"updated_at": "2022-11-15 15:48:00"
},
{
"id": 32,
"region": "dz",
"name": "{\"en\": \"Shabat\", \"ru\": \"Шабать\", \"tk\": \"Gubadag\"}",
"created_at": "2022-11-15 15:48:46",
"updated_at": "2022-12-03 10:08:32"
},
{
"id": 33,
"region": "dz",
"name": "{\"en\": \"S.Turkmenbashy\", \"ru\": \"С.Туркменбащы\", \"tk\": \"S.Türkmenbaşy\"}",
"created_at": "2022-11-15 15:50:34",
"updated_at": "2022-12-03 10:13:38"
},
{
"id": 34,
"region": "lb",
"name": "{\"en\": \"Charjev\", \"ru\": \"Чарджев\", \"tk\": \"Çärjew\"}",
"created_at": "2022-11-15 15:52:33",
"updated_at": "2022-11-15 15:52:33"
},
{
"id": 35,
"region": "lb",
"name": "{\"en\": \"Halach\", \"ru\": \"Халач\", \"tk\": \"Halaç\"}",
"created_at": "2022-11-15 15:53:16",
"updated_at": "2022-11-15 15:53:16"
},
{
"id": 36,
"region": "lb",
"name": "{\"en\": \"Hojambaz\", \"ru\": \"Ходжамбаз\", \"tk\": \"Hojambaz\"}",
"created_at": "2022-11-15 15:54:20",
"updated_at": "2022-11-15 15:54:20"
},
{
"id": 37,
"region": "lb",
"name": "{\"en\": \"Kerki\", \"ru\": \"Керки\", \"tk\": \"Kerki\"}",
"created_at": "2022-11-15 15:55:09",
"updated_at": "2022-11-15 15:55:09"
},
{
"id": 38,
"region": "lb",
"name": "{\"en\": \"Darganata\", \"ru\": \"Дарганата\", \"tk\": \"Darganata\"}",
"created_at": "2022-11-15 15:56:01",
"updated_at": "2022-11-15 15:56:01"
},
{
"id": 39,
"region": "lb",
"name": "{\"en\": \"Danev\", \"ru\": \"Данев\", \"tk\": \"Dänew\"}",
"created_at": "2022-11-15 15:57:20",
"updated_at": "2022-11-15 15:57:20"
},
{
"id": 40,
"region": "lb",
"name": "{\"en\": \"Sayat\", \"ru\": \"Саят\", \"tk\": \"Saýat\"}",
"created_at": "2022-11-15 15:58:08",
"updated_at": "2022-11-15 15:58:08"
},
{
"id": 41,
"region": "lb",
"name": "{\"en\": \"Koytendag\", \"ru\": \"Койтендаг\", \"tk\": \"Köýtendag\"}",
"created_at": "2022-11-15 15:58:58",
"updated_at": "2022-11-15 15:58:58"
},
{
"id": 42,
"region": "ah",
"name": "{\"en\": \"Altyn asyr c.\", \"ru\": \"Aлтын асыр г.\", \"tk\": \"Altyn asyr ş.\"}",
"created_at": "2022-11-15 16:44:54",
"updated_at": "2022-11-15 16:47:40"
},
{
"id": 43,
"region": "ah",
"name": "{\"en\": \"Anev c.\", \"ru\": \"Аннау г.\", \"tk\": \"Änew ş.\"}",
"created_at": "2022-11-15 16:46:43",
"updated_at": "2022-11-15 16:46:43"
},
{
"id": 44,
"region": "ah",
"name": "{\"en\": \"Dushak c.\", \"ru\": \"Душак г.\", \"tk\": \"Duşak ş.\"}",
"created_at": "2022-11-15 16:49:47",
"updated_at": "2022-11-15 16:50:26"
},
{
"id": 45,
"region": "bn",
"name": "{\"en\": \"Balkanaabat c.\", \"ru\": \"Балканабат г.\", \"tk\": \"Balkanabat ş.\"}",
"created_at": "2022-11-15 16:51:48",
"updated_at": "2022-11-15 16:51:48"
},
{
"id": 46,
"region": "bn",
"name": "{\"en\": \"Gumdag c.\", \"ru\": \"Гумдаг г.\", \"tk\": \"Gumdag ş.\"}",
"created_at": "2022-11-15 16:53:07",
"updated_at": "2022-11-15 16:53:07"
},
{
"id": 47,
"region": "bn",
"name": "{\"en\": \"Garabogaz c.\", \"ru\": \"Гарабогаз г.\", \"tk\": \"Garabogaz ş.\"}",
"created_at": "2022-11-15 16:54:31",
"updated_at": "2022-11-15 16:54:31"
},
{
"id": 48,
"region": "bn",
"name": "{\"en\": \"Hazar c.\", \"ru\": \"Хазар г.\", \"tk\": \"Hazar ş.\"}",
"created_at": "2022-11-15 16:55:40",
"updated_at": "2022-11-15 16:55:40"
},
{
"id": 49,
"region": "lb",
"name": "{\"en\": \"Sakar c.\", \"ru\": \"Cакар г.\", \"tk\": \"Sakar ş\"}",
"created_at": "2022-11-15 17:06:13",
"updated_at": "2022-11-15 17:06:13"
},
{
"id": 50,
"region": "lb",
"name": "{\"en\": \"Seydi c.\", \"ru\": \"Сейди г.\", \"tk\": \"Seýdi ş.\"}",
"created_at": "2022-11-15 17:07:17",
"updated_at": "2022-11-15 17:07:17"
},
{
"id": 51,
"region": "lb",
"name": "{\"en\": \"Dostlyk c.\", \"ru\": \"Достлык г.\", \"tk\": \"Dostluk ş.\"}",
"created_at": "2022-11-15 17:08:27",
"updated_at": "2022-11-15 17:08:27"
},
{
"id": 52,
"region": "mr",
"name": "{\"en\": \"Murgap\", \"ru\": \"Мургап\", \"tk\": \"Murgap\"}",
"created_at": "2022-11-15 17:09:29",
"updated_at": "2022-11-15 17:09:29"
},
{
"id": 53,
"region": "lb",
"name": "{\"en\": \"Garabekrevul c.\", \"ru\": \"Гарабекевул г.\", \"tk\": \"Garabekewül ş.\"}",
"created_at": "2022-11-15 17:11:41",
"updated_at": "2022-11-16 09:02:15"
},
{
"id": 54,
"region": "dz",
"name": "{\"en\": \"Gurbansoltan Eje\", \"ru\": \"Гурбансолтан эдже\", \"tk\": \"Gurbansoltan Eje\"}",
"created_at": "2022-12-03 10:10:57",
"updated_at": "2022-12-03 10:10:57"
},
{
"id": 55,
"region": "dz",
"name": "{\"en\": \"S.Nyýazow\", \"ru\": \"С,Ныязов\", \"tk\": \"S.Nyýazow\"}",
"created_at": "2022-12-03 10:12:31",
"updated_at": "2022-12-03 10:12:31"
},
{
"id": 56,
"region": "dz",
"name": "{\"en\": \"Dashoguz s.\", \"ru\": \"Дашогуз г.\", \"tk\": \"Daşoguz ş.\"}",
"created_at": "2023-01-18 11:00:24",
"updated_at": "2023-01-18 11:00:24"
},
{
"id": 58,
"region": "ah",
"name": "{\"en\": \"Watan f/u\", \"ru\": \"Ватан с/ф\", \"tk\": \"Watan d/b\"}",
"created_at": "2023-05-23 14:31:44",
"updated_at": "2023-05-23 14:45:56"
},
{
"id": 59,
"region": "ah",
"name": "{\"en\": \"A blessed town\", \"ru\": \"Берекет г.\", \"tk\": \"Bereket ş.\"}",
"created_at": "2023-05-23 15:47:26",
"updated_at": "2023-05-23 15:47:26"
},
{
"id": 60,
"region": "ah",
"name": "{\"en\": \"Youth town\", \"ru\": \"Яшлик г.\", \"tk\": \"Ýaşlyk ş.\"}",
"created_at": "2023-05-23 15:59:14",
"updated_at": "2023-05-23 15:59:57"
},
{
"id": 61,
"region": "ah",
"name": "{\"en\": \"Altyn asyr ş.\", \"ru\": \"Altyn asyr ş.\", \"tk\": \"Altyn asyr ş.\"}",
"created_at": "2023-05-23 16:09:54",
"updated_at": "2023-05-23 16:09:54"
},
{
"id": 62,
"region": "ah",
"name": "{\"en\": \"Small brother village\", \"ru\": \"Деревня младшего брата\", \"tk\": \"Kiçi aga obasy\"}",
"created_at": "2023-05-23 16:22:25",
"updated_at": "2023-05-23 16:22:25"
},
{
"id": 63,
"region": "ah",
"name": "{\"en\": \"Truth Farmers Union\", \"ru\": \"Правда с/ф\", \"tk\": \"Hakykat d/b\"}",
"created_at": "2023-05-23 16:45:22",
"updated_at": "2023-05-23 16:45:22"
},
{
"id": 64,
"region": "ah",
"name": "{\"en\": \"Bed City\", \"ru\": \"Бед Сити\", \"tk\": \"Duşak ş.\"}",
"created_at": "2023-05-23 16:49:30",
"updated_at": "2023-05-23 16:49:30"
},
{
"id": 65,
"region": "ah",
"name": "{\"en\": \"White Gold Consulting\", \"ru\": \"Консалтинг Белого Золота\", \"tk\": \"Ak Altyn g.\"}",
"created_at": "2023-05-23 17:02:49",
"updated_at": "2023-05-23 17:02:49"
},
{
"id": 66,
"region": "ah",
"name": "{\"en\": \"\\\"Hasyl\\\" f/a\", \"ru\": \"«Хасыл» a/ф\", \"tk\": \"Hasyl d/b\"}",
"created_at": "2023-05-23 17:11:00",
"updated_at": "2023-05-23 17:11:00"
},
{
"id": 67,
"region": "ah",
"name": "{\"en\": \"Zahmet Farmers Union\", \"ru\": \"Захмет c/ф\", \"tk\": \"Zähmet d/b\"}",
"created_at": "2023-05-23 17:16:37",
"updated_at": "2023-05-23 17:16:37"
},
{
"id": 68,
"region": "bn",
"name": "{\"en\": \"Jebel t.\", \"ru\": \"Джебель ш.\", \"tk\": \"Jebel ş.\"}",
"created_at": "2023-05-24 16:33:15",
"updated_at": "2023-05-24 16:39:39"
},
{
"id": 69,
"region": "bn",
"name": "{\"en\": \"Edge\", \"ru\": \"Кенар\", \"tk\": \"Kenar\"}",
"created_at": "2023-05-24 17:23:40",
"updated_at": "2023-05-24 17:23:40"
},
{
"id": 70,
"region": "bn",
"name": "{\"en\": \"Serdar\", \"ru\": \"Сердар\", \"tk\": \"Serdar\"}",
"created_at": "2023-05-25 16:36:42",
"updated_at": "2023-05-25 16:36:42"
},
{
"id": 71,
"region": "bn",
"name": "{\"en\": \"Ekerem şäherçe\", \"ru\": \"Экерем ш.\", \"tk\": \"Ekerem şäherçe\"}",
"created_at": "2023-05-25 16:53:18",
"updated_at": "2023-05-25 16:53:18"
},
{
"id": 72,
"region": "bn",
"name": "{\"en\": \"Garadepe ş.\", \"ru\": \"Гарадепе ш.\", \"tk\": \"Garadepe ş.\"}",
"created_at": "2023-05-25 17:09:27",
"updated_at": "2023-05-25 17:09:27"
},
{
"id": 73,
"region": "dz",
"name": "{\"en\": \"Bagtyýar zaman oba\", \"ru\": \"Багтыйар заман село\", \"tk\": \"Bagtyýar zaman oba\"}",
"created_at": "2023-05-26 15:15:20",
"updated_at": "2023-05-26 15:15:20"
},
{
"id": 74,
"region": "dz",
"name": "{\"en\": \"Al Horezmi\", \"ru\": \"Ал Хорезми\", \"tk\": \"Al Horezmi\"}",
"created_at": "2023-05-26 15:37:43",
"updated_at": "2023-05-26 15:37:43"
},
{
"id": 75,
"region": "dz",
"name": "{\"en\": \"Jeýhun\", \"ru\": \"Джейхун\", \"tk\": \"Jeýhun\"}",
"created_at": "2023-05-26 15:58:35",
"updated_at": "2023-05-26 15:58:35"
},
{
"id": 76,
"region": "dz",
"name": "{\"en\": \"Azady\", \"ru\": \"Азады\", \"tk\": \"Azady\"}",
"created_at": "2023-05-26 16:12:52",
"updated_at": "2023-05-26 16:12:52"
},
{
"id": 77,
"region": "dz",
"name": "{\"en\": \"Baýramhan\", \"ru\": \"Байрамхан\", \"tk\": \"Baýramhan\"}",
"created_at": "2023-05-26 16:21:31",
"updated_at": "2023-05-26 16:21:31"
},
{
"id": 78,
"region": "dz",
"name": "{\"en\": \"Ruhnama\", \"ru\": \"Рухнама\", \"tk\": \"Ruhnama\"}",
"created_at": "2023-05-29 14:53:11",
"updated_at": "2023-05-29 14:53:11"
},
{
"id": 79,
"region": "dz",
"name": "{\"en\": \"Magtymguly\", \"ru\": \"Махтумкули\", \"tk\": \"Magtymguly\"}",
"created_at": "2023-05-29 14:58:36",
"updated_at": "2023-05-29 14:58:36"
},
{
"id": 80,
"region": "dz",
"name": "{\"en\": \"Ýalkym\", \"ru\": \"Ялкым\", \"tk\": \"Ýalkym\"}",
"created_at": "2023-05-29 15:03:57",
"updated_at": "2023-05-29 15:03:57"
},
{
"id": 81,
"region": "dz",
"name": "{\"en\": \"Türkmenbaşy şaýoly\", \"ru\": \"Туркменбаши шайолы\", \"tk\": \"Türkmenbaşy şaýoly\"}",
"created_at": "2023-05-29 15:26:24",
"updated_at": "2023-05-29 15:28:05"
},
{
"id": 82,
"region": "dz",
"name": "{\"en\": \"Oguzhan\", \"ru\": \"Огузхан\", \"tk\": \"Oguzhan\"}",
"created_at": "2023-05-29 15:36:50",
"updated_at": "2023-05-29 15:36:50"
},
{
"id": 83,
"region": "lb",
"name": "{\"en\": \"Türkmenabat ş\", \"ru\": \"Туркменабат ш\", \"tk\": \"Türkmenabat ş\"}",
"created_at": "2023-05-29 15:55:51",
"updated_at": "2023-05-29 15:55:51"
},
{
"id": 84,
"region": "lb",
"name": "{\"en\": \"Farap\", \"ru\": \"Фарап\", \"tk\": \"Farap\"}",
"created_at": "2023-05-30 15:09:46",
"updated_at": "2023-05-30 15:09:46"
},
{
"id": 85,
"region": "lb",
"name": "{\"en\": \"Döwletli\", \"ru\": \"Довлетли\", \"tk\": \"Döwletli\"}",
"created_at": "2023-05-30 17:34:41",
"updated_at": "2023-05-30 17:34:41"
},
{
"id": 86,
"region": "mr",
"name": "{\"en\": \"Mollanepes k.\", \"ru\": \"Молланепес к.\", \"tk\": \"Mollanepes k.\"}",
"created_at": "2023-06-01 14:46:03",
"updated_at": "2023-06-01 14:46:03"
},
{
"id": 87,
"region": "mr",
"name": "{\"en\": \"Parahatlyk k.\", \"ru\": \"Парахатлык к.\", \"tk\": \"Parahatlyk k.\"}",
"created_at": "2023-06-01 14:49:45",
"updated_at": "2023-06-01 14:49:45"
},
{
"id": 88,
"region": "mr",
"name": "{\"en\": \"Kemine k.\", \"ru\": \"Кемине к.\", \"tk\": \"Kemine k.\"}",
"created_at": "2023-06-01 14:56:09",
"updated_at": "2023-06-01 14:56:09"
},
{
"id": 89,
"region": "mr",
"name": "{\"en\": \"Bagtyýarlyk k.\", \"ru\": \"Багтыярлык к.\", \"tk\": \"Bagtyýarlyk k.\"}",
"created_at": "2023-06-01 15:03:22",
"updated_at": "2023-06-01 15:03:22"
},
{
"id": 90,
"region": "mr",
"name": "{\"en\": \"Güneş ş.\", \"ru\": \"Гюнеш ш.\", \"tk\": \"Güneş ş.\"}",
"created_at": "2023-06-01 15:14:30",
"updated_at": "2023-06-01 15:14:30"
},
{
"id": 91,
"region": "mr",
"name": "{\"en\": \"Türkmenbaşy ş.\", \"ru\": \"Туркменбаши ш.\", \"tk\": \"Türkmenbaşy ş.\"}",
"created_at": "2023-06-01 15:24:42",
"updated_at": "2023-06-01 15:24:42"
},
{
"id": 92,
"region": "mr",
"name": "{\"en\": \"Şatlyk ş\", \"ru\": \"Шатлык ш\", \"tk\": \"Şatlyk ş\"}",
"created_at": "2023-06-01 15:40:12",
"updated_at": "2023-06-01 15:40:12"
},
{
"id": 93,
"region": "mr",
"name": "{\"en\": \"Oguzhan ş.\", \"ru\": \"Огузхан ш.\", \"tk\": \"Oguzhan ş.\"}",
"created_at": "2023-06-01 16:25:28",
"updated_at": "2023-06-01 16:25:28"
},
{
"id": 94,
"region": "mr",
"name": "{\"en\": \"Oguzhan\", \"ru\": \"Огузхан\", \"tk\": \"Oguzhan\"}",
"created_at": "2023-06-01 16:31:14",
"updated_at": "2023-06-01 16:31:14"
},
{
"id": 95,
"region": "mr",
"name": "{\"en\": \"Hakykat d/b\", \"ru\": \"Хакукат д/б\", \"tk\": \"Hakykat d/b\"}",
"created_at": "2023-06-01 16:37:04",
"updated_at": "2023-06-01 16:37:04"
},
{
"id": 96,
"region": "mr",
"name": "{\"en\": \"Peşanaly ş.\", \"ru\": \"Пешаналы ш.\", \"tk\": \"Peşanaly ş.\"}",
"created_at": "2023-06-01 16:48:10",
"updated_at": "2023-06-01 16:48:10"
},
{
"id": 97,
"region": "mr",
"name": "{\"en\": \"Türkmengala ş\", \"ru\": \"Туркменгала ш.\", \"tk\": \"Türkmengala ş\"}",
"created_at": "2023-06-02 15:09:50",
"updated_at": "2023-06-02 15:09:50"
},
{
"id": 98,
"region": "mr",
"name": "{\"en\": \"Nurana Gurbanmyradow d/b\", \"ru\": \"Нурана Гурбанмурадов д/б\", \"tk\": \"Nurana Gurbanmyradow d/b\"}",
"created_at": "2023-06-02 15:34:15",
"updated_at": "2023-06-02 15:34:15"
},
{
"id": 99,
"region": "mr",
"name": "{\"en\": \"746470 +993 632 8-41-68\", \"ru\": \"Сариязи д/б\", \"tk\": \"Saryýazy d/b\"}",
"created_at": "2023-06-02 16:33:22",
"updated_at": "2023-06-02 16:33:22"
},
{
"id": 100,
"region": "mr",
"name": "{\"en\": \"746500 +993 624 5-43-97\", \"ru\": \"Сариджа\", \"tk\": \"Saryja\"}",
"created_at": "2023-06-02 16:36:09",
"updated_at": "2023-06-02 16:36:09"
},
{
"id": 101,
"region": "mr",
"name": "{\"en\": \"Serhatabat ş.\", \"ru\": \"Серхетабат ш.\", \"tk\": \"Serhatabat ş.\"}",
"created_at": "2023-06-02 16:48:42",
"updated_at": "2023-06-02 16:48:42"
},
{
"id": 102,
"region": "mr",
"name": "{\"en\": \"Ýeňiş d/b\", \"ru\": \"Йениш д/б\", \"tk\": \"Ýeňiş d/b\"}",
"created_at": "2023-06-02 16:52:04",
"updated_at": "2023-06-02 16:52:04"
},
{
"id": 103,
"region": "mr",
"name": "{\"en\": \"Serhetabat etr\", \"ru\": \"Серхетабат\", \"tk\": \"Serhetabat etr\"}",
"created_at": "2023-06-02 16:56:52",
"updated_at": "2023-06-02 16:56:52"
}
]

View File

@@ -0,0 +1,61 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
// 1. Create new pivot tables
Schema::create('category_product', function (Blueprint $table) {
$table->foreignId('product_id')->constrained()->cascadeOnDelete();
$table->foreignId('category_id')->constrained()->cascadeOnDelete();
// No primary key on pivot usually, but maybe composite unique?
// product_has_relations didn't have one explicitly shown but usually pivots do.
// Let's add an index for performance.
$table->unique(['product_id', 'category_id']);
});
Schema::create('collection_product', function (Blueprint $table) {
$table->foreignId('product_id')->constrained()->cascadeOnDelete();
$table->foreignId('collection_id')->constrained()->cascadeOnDelete();
$table->unique(['product_id', 'collection_id']);
});
Schema::create('channel_product', function (Blueprint $table) {
$table->foreignId('product_id')->constrained()->cascadeOnDelete();
$table->foreignId('channel_id')->constrained()->cascadeOnDelete();
$table->unique(['product_id', 'channel_id']);
});
Schema::create('product_related', function (Blueprint $table) {
$table->foreignId('product_id')->constrained()->cascadeOnDelete();
$table->foreignId('related_product_id')->constrained('products')->cascadeOnDelete();
$table->unique(['product_id', 'related_product_id']);
});
// 2. Migrate data
// Using raw SQL for efficiency and simplicity
DB::statement("INSERT INTO category_product (product_id, category_id) SELECT product_id, productable_id FROM product_has_relations WHERE productable_type = 'category'");
DB::statement("INSERT INTO collection_product (product_id, collection_id) SELECT product_id, productable_id FROM product_has_relations WHERE productable_type = 'collection'");
DB::statement("INSERT INTO channel_product (product_id, channel_id) SELECT product_id, productable_id FROM product_has_relations WHERE productable_type = 'channel'");
DB::statement("INSERT INTO product_related (product_id, related_product_id) SELECT product_id, productable_id FROM product_has_relations WHERE productable_type = 'product'");
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('category_product');
Schema::dropIfExists('collection_product');
Schema::dropIfExists('channel_product');
Schema::dropIfExists('product_related');
}
};

View File

@@ -0,0 +1,41 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('user_addresses', function (Blueprint $table) {
$table->string('title')->nullable();
$table->string('building')->nullable();
$table->string('floor')->nullable();
$table->string('door')->nullable();
$table->string('location')->nullable();
$table->dropColumn('first_name');
$table->dropColumn('last_name');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('user_addresses', function (Blueprint $table) {
$table->dropColumn('title');
$table->dropColumn('building');
$table->dropColumn('floor');
$table->dropColumn('door');
$table->dropColumn('location');
$table->string('first_name')->nullable();
$table->string('last_name')->nullable();
});
}
};

View File

@@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('orders', function (Blueprint $table) {
$table->jsonb('options')->nullable();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('orders', function (Blueprint $table) {
$table->dropColumn('options');
});
}
};

View File

@@ -0,0 +1,24 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('selected_categories', function (Blueprint $table) {
$table->id();
$table->jsonb('name');
$table->jsonb('description')->nullable();
$table->boolean('is_visible')->default(true);
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('selected_categories');
}
};

View File

@@ -0,0 +1,23 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('category_selected_category', function (Blueprint $table) {
$table->id();
$table->foreignId('selected_category_id')->constrained()->cascadeOnDelete();
$table->foreignId('category_id')->constrained()->cascadeOnDelete();
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('category_selected_category');
}
};

View File

@@ -2,8 +2,23 @@
namespace Database\Seeders;
use Database\Seeders\New\AddressSeeder;
use Database\Seeders\New\BrandsSeeder;
use Database\Seeders\New\CategoriesTableSeeder;
use Database\Seeders\New\CustomersTableSeeder;
use Database\Seeders\New\FavoritesSeeder;
use Database\Seeders\New\MediaSeeder;
use Database\Seeders\New\OrderAddressSeeder;
use Database\Seeders\New\OrderSeeder;
use Database\Seeders\New\ProductBarcodesSeeder;
use Database\Seeders\New\ProductCategoryRelationshipsSeeder;
use Database\Seeders\New\ProductPricesSeeder;
use Database\Seeders\New\ProductPropertiesSeeder;
use Database\Seeders\New\ProductPropertyValuesSeeder;
use Database\Seeders\New\ProductsTableSeeder;
use Database\Seeders\New\ProductStocksSeeder;
use Database\Seeders\New\PropertiesTableSeeder;
use Database\Seeders\New\SectionsSeeder;
use Database\Seeders\New\SellersTableSeeder;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
@@ -18,11 +33,27 @@ class DatabaseSeeder extends Seeder
public function run(): void
{
$this->call([
// ProvincesTableSeeder::class,
// PaymentTypeSeeder::class,
// UsersTableSeeder::class,
// BrandsSeeder::class,
// CustomersTableSeeder::class,
SellersTableSeeder::class,
// InventoriesTableSeeder::class,
// BrandsSeeder::class,
// CustomersTableSeeder::class,
// SellersTableSeeder::class,
// ProductsTableSeeder::class,
// ProductPricesSeeder::class,
// CategoriesTableSeeder::class,
// AddressSeeder::class,
// PropertiesTableSeeder::class,
// FavoritesSeeder::class,
// SectionsSeeder::class,
// ProductCategoryRelationshipsSeeder::class,
// ProductBarcodesSeeder::class,
// ProductPropertiesSeeder::class,
// ProductPropertyValuesSeeder::class,
// ProductStocksSeeder::class,
// MediaSeeder::class,
// OrderSeeder::class,
// OrderAddressSeeder::class,
]);
}
}

View File

@@ -0,0 +1,42 @@
<?php
namespace Database\Seeders;
use App\Models\System\Settings\Payments\PaymentType;
use Illuminate\Database\Seeder;
class PaymentTypeSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
collect([
[
'id' => 1,
'code' => 'cash',
'name' => ['en' => 'Cash', 'tk' => 'Nagt', 'ru' => 'Näliçni'],
'tax' => 0,
'is_enabled' => true,
'options' => null,
],
[
'id' => 2,
'code' => 'atm',
'name' => ['en' => 'ATM', 'tk' => 'Terminal', 'ru' => 'ATM'],
'tax' => 0,
'is_enabled' => true,
'options' => null,
],
[
'id' => 3,
'code' => 'online_halkbank',
'name' => ['en' => 'Online (halkbank)', 'tk' => 'Onlaýn (halkbank)', 'ru' => 'Onlaýn (halkbank)'],
'tax' => 0,
'is_enabled' => true,
'options' => null,
],
])->each(fn ($data) => PaymentType::create($data));
}
}

View File

@@ -0,0 +1,38 @@
<?php
namespace Database\Seeders;
use Exception;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\File;
class ProvincesTableSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
$datas = json_decode(File::get('database/data/provinces.json'));
$table = 'provinces';
foreach ($datas as $data) {
try {
DB::table($table)->insert([
'id' => $data->id,
'region' => $data->region,
'name' => $data->name,
'created_at' => $data->created_at,
'updated_at' => $data->updated_at,
]);
} catch (Exception $e) {
info(['province db erorr: ' => $e->getMessage()]);
}
}
DB::statement("
SELECT setval('{$table}_id_seq', (SELECT MAX(id) from {$table}))
");
}
}

View File

@@ -2,6 +2,8 @@
namespace Database\Seeders;
use App\Models\Ecommerce\Channel\Channel;
use App\Models\Ecommerce\Product\Inventory\Inventory;
use App\Models\System\Roles\Role;
use App\Models\User;
use Illuminate\Database\Seeder;
@@ -22,7 +24,7 @@ class UsersTableSeeder extends Seeder
*/
public function seedStarterKit(): void
{
$this->seedRoles();
// $this->seedRoles();
collect([
[
@@ -36,6 +38,8 @@ class UsersTableSeeder extends Seeder
$user->assignRole('admin');
});
// $this->createChannels();
}
public function seedRoles(): void
@@ -76,4 +80,31 @@ class UsersTableSeeder extends Seeder
SELECT setval('roles_id_seq', (SELECT MAX(id) from roles))
");
}
public function createChannels(): void
{
collect([
[
'name' => 'Tmpost',
'slug' => 'tmpost',
'description' => 'Tmpost default',
'timezone' => 'Asia/Ashgabat',
'url' => 'http://shop.post.tm',
'is_default' => true,
],
])->each(fn ($data) => Channel::create($data));
DB::statement("
SELECT setval('channels_id_seq', (SELECT MAX(id) from channels))
");
Inventory::create([
'id' => 14,
'code' => 'tmpost',
'name' => 'Tmpost inventory',
'region' => 'ag',
'phone_number' => '99361099310',
'channel_id' => Channel::where('slug', 'tmpost')->first()->id,
]);
}
}

View File

@@ -0,0 +1,56 @@
<?php
namespace Database\Seeders\New;
use App\Models\User;
use Exception;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
use JsonMachine\Items;
use JsonMachine\JsonDecoder\ExtJsonDecoder;
class AddressSeeder extends Seeder
{
public function run()
{
DB::transaction(function () {
$table = 'user_addresses';
$items = Items::fromFile(
database_path('data/addresses.json'),
['decoder' => new ExtJsonDecoder(true)]
);
// First we need to fill user_addresses table with json data, then find user id matching addressable_id and fill user options with {"address": "$data"}
foreach ($items as $data) {
try {
DB::table($table)->insert([
'id' => $data['id'],
'user_id' => $data['addressable_id'],
'street_address' => $data['address'],
'title' => $data['title'],
'building' => $data['building'],
'floor' => $data['floor'],
'door' => $data['door'],
'location' => $data['location'],
'type' => $data['type'],
'created_at' => $data['created_at'],
'updated_at' => $data['updated_at'],
'is_default' => false,
]);
} catch (Exception $e) {
continue;
}
$user = User::where('id', $data['addressable_id'])->first();
if ($user) {
info($user);
$user->options->set('address', $data['address']);
$user->save();
}
}
});
}
}

View File

@@ -17,10 +17,12 @@ class BrandsSeeder extends Seeder
DB::table($table)->truncate();
foreach ($datas as $data) {
DB::table($table)->insertOrIgnore([
$name = json_decode($data->name);
DB::table($table)->insert([
'id' => $data->id,
'slug' => Str::slug(json_decode($data->name)->en ?? $data->code),
'name' => $data->name,
'slug' => Str::slug($name->en ?? $data->code).'_'.$data->id,
'name' => $name->en,
'is_visible' => $data->is_blocked,
'sort_order' => $data->priority,
'created_at' => $data->created_at,

View File

@@ -0,0 +1,41 @@
<?php
namespace Database\Seeders\New;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
use JsonMachine\Items;
use JsonMachine\JsonDecoder\ExtJsonDecoder;
class CategoriesTableSeeder extends Seeder
{
public function run()
{
DB::transaction(function () {
$table = 'categories';
$items = Items::fromFile(
database_path('data/categories.json'),
['decoder' => new ExtJsonDecoder(true)]
);
foreach ($items as $data) {
DB::table($table)->insert([
'id' => $data['id'],
'parent_id' => $data['parent_id'],
'slug' => $data['slug'],
'name' => str_replace('"tm"', '"tk"', $data['name']),
'is_visible' => $data['is_blocked'],
'sort_order' => $data['priority'],
'tax_percentage' => $data['percentage'],
'created_at' => $data['created_at'],
'updated_at' => $data['updated_at'],
]);
}
DB::statement("
SELECT setval('{$table}_id_seq', (SELECT MAX(id) from {$table}))
");
});
}
}

View File

@@ -0,0 +1,29 @@
<?php
namespace Database\Seeders\New;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\File;
class FavoritesSeeder extends Seeder
{
public function run()
{
$datas = json_decode(File::get('database/data/favorite_products.json'));
$table = 'favorites';
DB::table($table)->truncate();
foreach ($datas as $data) {
DB::table($table)->insert([
'user_id' => $data->customer_id,
'product_id' => $data->item_id,
]);
}
DB::statement("
SELECT setval('{$table}_id_seq', (SELECT MAX(id) from {$table}))
");
}
}

View File

@@ -0,0 +1,64 @@
<?php
namespace Database\Seeders\New;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
use JsonMachine\Items;
use JsonMachine\JsonDecoder\ExtJsonDecoder;
class MediaSeeder extends Seeder
{
public function run()
{
DB::transaction(function () {
$table = 'media';
DB::table($table)->truncate();
$items = Items::fromFile(
database_path('data/media.json'),
['decoder' => new ExtJsonDecoder(true)]
);
foreach ($items as $data) {
$modelType = match ($data['model_type']) {
'Domain\Brand\Models\Brand' => 'brand',
'Domain\Category\Models\Category' => 'category',
'Domain\Item\Models\Item' => 'product',
'Domain\Promo\Models\Banner' => 'banner',
'Domain\Promo\Models\Carousel' => 'channel',
};
if ($modelType === 'banner') {
continue;
}
DB::table($table)->insert([
'id' => $data['id'],
'model_type' => $modelType,
'model_id' => $data['model_id'],
'uuid' => $data['uuid'],
'collection_name' => 'uploads',
'name' => $data['name'],
'file_name' => $data['file_name'],
'mime_type' => $data['mime_type'],
'disk' => $data['disk'],
'conversions_disk' => $data['conversions_disk'],
'size' => $data['size'],
'manipulations' => $data['manipulations'],
'custom_properties' => $data['custom_properties'],
'generated_conversions' => $data['generated_conversions'],
'responsive_images' => $data['responsive_images'],
'order_column' => $data['order_column'],
'created_at' => $data['created_at'],
'updated_at' => $data['updated_at'],
]);
}
DB::statement("
SELECT setval('{$table}_id_seq', (SELECT MAX(id) from {$table}))
");
});
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace Database\Seeders\New;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
use JsonMachine\Items;
use JsonMachine\JsonDecoder\ExtJsonDecoder;
class OrderAddressSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
DB::transaction(function () {
$items = Items::fromFile(
database_path('data/order_address.json'),
['decoder' => new ExtJsonDecoder(true)]
);
foreach ($items as $data) {
DB::table('orders')->where('id', $data['order_id'])->update([
'customer_address' => $data['address'],
]);
}
});
}
}

View File

@@ -0,0 +1,119 @@
<?php
namespace Database\Seeders\New;
use App\Models\Ecommerce\Product\Order\Shipping\OrderShipping;
use App\Models\Ecommerce\Product\Order\Status\OrderStatus;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use JsonMachine\Items;
use JsonMachine\JsonDecoder\ExtJsonDecoder;
class OrderSeeder extends Seeder
{
public function run()
{
$this->fillOrders();
$this->fillOrderItems();
}
private function fillOrderItems()
{
DB::transaction(function () {
$table = 'order_items';
DB::table($table)->truncate();
$items = Items::fromFile(
database_path('data/order_items.json'),
['decoder' => new ExtJsonDecoder(true)]
);
foreach ($items as $data) {
$channel_id = DB::table('channel_product')->where('product_id', $data['item_id'])->first()->channel_id ?? tmpostChannel()->id;
DB::table($table)->insert([
'id' => $data['id'],
'order_id' => $data['order_id'],
'product_id' => $data['item_id'],
'quantity' => $data['quantity'],
'unit_price_amount' => $data['price'],
'channel_id' => $channel_id,
'created_at' => $data['created_at'],
'updated_at' => $data['updated_at'],
]);
}
DB::statement("
SELECT setval('{$table}_id_seq', (SELECT MAX(id) from {$table}))
");
});
}
private function fillOrders()
{
DB::transaction(function () {
$table = 'orders';
DB::table($table)->truncate();
$items = Items::fromFile(
database_path('data/orders.json'),
['decoder' => new ExtJsonDecoder(true)]
);
foreach ($items as $data) {
DB::table($table)->insert([
'id' => $data['id'],
'number' => Str::random(30),
'user_id' => $data['customer_id'],
'options' => sprintf('{"seller_id":"%s","warehouse_id":"%s"}', $data['seller_id'], $data['warehouse_id']),
'status' => $this->formatStatus($data['status']),
'shipping_method' => $this->formatShippingMethod($data['delivery_type']),
'payment_type_id' => $this->formatPaymentType($data['payment_type']),
'notes' => $data['note'],
'created_at' => $data['created_at'],
'updated_at' => $data['updated_at'],
'deleted_at' => $data['deleted_at'],
]);
}
DB::statement("
SELECT setval('{$table}_id_seq', (SELECT MAX(id) from {$table}))
");
});
}
private function formatStatus(string $status): string
{
return match ($status) {
'DRAFT' => OrderStatus::PENDING,
'COMPLETED' => OrderStatus::COMPLETED,
'DELIVERED' => OrderStatus::COMPLETED,
'CANCELLED' => OrderStatus::CANCELLED,
default => OrderStatus::default(),
};
}
private function formatShippingMethod($shippingMethod): string
{
return match ($shippingMethod) {
'PICK_UP' => OrderShipping::SELF_PICKUP,
'EXPRESS_DELIVERY' => OrderShipping::EXPRESS,
'SELECTED_DELIVERY' => OrderShipping::STANDART,
default => OrderShipping::default(),
};
}
private function formatPaymentType($paymentType): int
{
return match ($paymentType) {
'CASH' => 1,
'CARD' => 2,
default => 1,
};
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace Database\Seeders\New;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
use JsonMachine\Items;
use JsonMachine\JsonDecoder\ExtJsonDecoder;
class ProductBarcodesSeeder extends Seeder
{
public function run()
{
DB::transaction(function () {
$table = 'products';
$items = Items::fromFile(
database_path('data/item_barcodes.json'),
['decoder' => new ExtJsonDecoder(true)]
);
foreach ($items as $data) {
// item_id is product id, I need to find product and update price_amount to $data['value'] and cost_amount to $data['cost_value']
DB::table($table)->where('id', $data['item_id'])->update([
'barcode' => $data['barcode'],
]);
}
});
}
}

View File

@@ -0,0 +1,35 @@
<?php
namespace Database\Seeders\New;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
use JsonMachine\Items;
use JsonMachine\JsonDecoder\ExtJsonDecoder;
class ProductCategoryRelationshipsSeeder extends Seeder
{
public function run()
{
DB::transaction(function () {
$table = 'category_product';
DB::table($table)->truncate();
$items = Items::fromFile(
database_path('data/categorizables.json'),
['decoder' => new ExtJsonDecoder(true)]
);
foreach ($items as $data) {
if ($data['categorizable_type'] == 'Domain\\Item\\Models\\Item') {
DB::table($table)->insert([
'product_id' => $data['categorizable_id'],
'category_id' => $data['category_id'],
]);
}
}
});
}
}

View File

@@ -0,0 +1,31 @@
<?php
namespace Database\Seeders\New;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
use JsonMachine\Items;
use JsonMachine\JsonDecoder\ExtJsonDecoder;
class ProductPricesSeeder extends Seeder
{
public function run()
{
DB::transaction(function () {
$table = 'products';
$items = Items::fromFile(
database_path('data/prices.json'),
['decoder' => new ExtJsonDecoder(true)]
);
foreach ($items as $data) {
// item_id is product id, I need to find product and update price_amount to $data['value'] and cost_amount to $data['cost_value']
DB::table($table)->where('id', $data['item_id'])->update([
'price_amount' => $data['value'],
'cost_amount' => $data['cost_value'],
]);
}
});
}
}

View File

@@ -0,0 +1,32 @@
<?php
namespace Database\Seeders\New;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
use JsonMachine\Items;
use JsonMachine\JsonDecoder\ExtJsonDecoder;
class ProductPropertiesSeeder extends Seeder
{
public function run()
{
DB::transaction(function () {
$table = 'product_attributes';
DB::table($table)->truncate();
$items = Items::fromFile(
database_path('data/item_properties.json'),
['decoder' => new ExtJsonDecoder(true)]
);
foreach ($items as $data) {
DB::table($table)->insert([
'product_id' => $data['item_id'],
'attribute_id' => $data['property_id'],
]);
}
});
}
}

View File

@@ -0,0 +1,80 @@
<?php
namespace Database\Seeders\New;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
use JsonMachine\Items;
use JsonMachine\JsonDecoder\ExtJsonDecoder;
class ProductPropertyValuesSeeder extends Seeder
{
public function run()
{
$propertyWithValues = $this->fillPropertyValues();
$this->fillProductPropertyValues($propertyWithValues);
}
private function fillPropertyValues()
{
$propertyWithValues = collect();
DB::transaction(function () use (&$propertyWithValues) {
$table = 'attribute_values';
DB::table($table)->truncate();
$items = Items::fromFile(
database_path('data/property_values.json'),
['decoder' => new ExtJsonDecoder(true)]
);
foreach ($items as $data) {
DB::table($table)->insert([
'id' => $data['id'],
'key' => $data['id'],
'attribute_id' => $data['property_id'],
'value' => str_replace('"tm"', '"tk"', $data['name']),
'created_at' => $data['created_at'],
'updated_at' => $data['updated_at'],
]);
$propertyWithValues->push([
'attribute_id' => $data['property_id'],
'value_id' => $data['id'],
]);
}
DB::statement("
SELECT setval('{$table}_id_seq', (SELECT MAX(id) from {$table}))
");
});
return $propertyWithValues;
}
private function fillProductPropertyValues($propertyWithValues)
{
DB::transaction(function () use ($propertyWithValues) {
$table = 'attribute_value_product_attribute';
DB::table($table)->truncate();
$items = Items::fromFile(
database_path('data/item_property_values.json'),
['decoder' => new ExtJsonDecoder(true)]
);
foreach ($items as $data) {
DB::table($table)->insert([
'product_attribute_id' => $propertyWithValues->firstWhere('value_id', $data['property_value_id'])['attribute_id'],
'attribute_value_id' => $data['property_value_id'],
]);
}
DB::statement("
SELECT setval('{$table}_id_seq', (SELECT MAX(id) from {$table}))
");
});
}
}

View File

@@ -0,0 +1,39 @@
<?php
namespace Database\Seeders\New;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
use JsonMachine\Items;
use JsonMachine\JsonDecoder\ExtJsonDecoder;
class ProductStocksSeeder extends Seeder
{
public function run()
{
DB::transaction(function () {
$items = Items::fromFile(
database_path('data/stocks.json'),
['decoder' => new ExtJsonDecoder(true)]
);
// First we update stock in products table, then in inventory_product table
foreach ($items as $data) {
DB::table('products')->where('id', $data['item_id'])->update([
'stock' => $data['quantity'],
]);
DB::table('inventory_product')
->insert([
'product_id' => $data['item_id'],
'inventory_id' => $data['warehouse_id'],
'stock' => $data['quantity'],
]);
}
DB::statement("
SELECT setval('inventory_product_id_seq', (SELECT MAX(id) from inventory_product))
");
});
}
}

View File

@@ -0,0 +1,59 @@
<?php
namespace Database\Seeders\New;
use App\Models\User;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
use JsonMachine\Items;
use JsonMachine\JsonDecoder\ExtJsonDecoder;
class ProductsTableSeeder extends Seeder
{
public function run()
{
DB::transaction(function () {
$table = 'products';
$users = User::whereNotNull('options->old_customer_id')->get();
DB::table($table)->truncate();
$items = Items::fromFile(
database_path('data/items.json'),
['decoder' => new ExtJsonDecoder(true)]
);
foreach ($items as $data) {
$name = json_decode($data['name']);
$description = json_decode($data['description']);
DB::table($table)->insert([
'id' => $data['id'],
'sku' => $data['sku'],
'name' => $name->tm ?? $name->en ?? '',
'slug' => $data['slug'],
'description' => $description->tm ?? $description->en ?? '',
'brand_id' => $data['brand_id'],
'is_visible' => $data['is_blocked'],
'created_at' => $data['created_at'],
'updated_at' => $data['updated_at'],
]);
// We find user with $data->seller_id matching old_customer_id in options, then we get users channel and attach product to it
$user = $users->firstWhere(function ($user) use ($data) {
return $user->options['old_customer_id'] == $data['seller_id'];
});
$channel = $user ? $user->channel() : tmpostChannel();
DB::table('channel_product')->insert([
'product_id' => $data['id'],
'channel_id' => $channel->id,
]);
}
DB::statement("
SELECT setval('{$table}_id_seq', (SELECT MAX(id) from {$table}))
");
});
}
}

View File

@@ -0,0 +1,34 @@
<?php
namespace Database\Seeders\New;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\File;
class PropertiesTableSeeder extends Seeder
{
public function run()
{
$datas = json_decode(File::get('database/data/properties.json'));
$table = 'attributes';
DB::table($table)->truncate();
foreach ($datas as $data) {
DB::table($table)->insertOrIgnore([
'id' => $data->id,
'slug' => $data->code,
'name' => str_replace('"tm"', '"tk"', $data->name),
'description' => $data->description,
'type' => 'text',
'created_at' => $data->created_at,
'updated_at' => $data->updated_at,
]);
}
DB::statement("
SELECT setval('{$table}_id_seq', (SELECT MAX(id) from {$table}))
");
}
}

View File

@@ -0,0 +1,37 @@
<?php
namespace Database\Seeders\New;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Str;
class SectionsSeeder extends Seeder
{
public function run()
{
DB::transaction(function () {
$datas = json_decode(File::get('database/data/sections.json'));
$table = 'collections';
DB::table($table)->truncate();
foreach ($datas as $data) {
DB::table($table)->insert([
'id' => $data->id,
'name' => str_replace('"tm"', '"tk"', $data->title),
'slug' => Str::slug($data->title).'_'.$data->id,
'is_visible' => ! $data->is_blocked,
'sort_order' => $data->priority,
'created_at' => $data->created_at,
'updated_at' => $data->updated_at,
]);
}
DB::statement("
SELECT setval('{$table}_id_seq', (SELECT MAX(id) from {$table}))
");
});
}
}

View File

@@ -26,6 +26,7 @@ class SellersTableSeeder extends Seeder
'email' => $data->email,
'phone_number' => Str::after($data->phone, '993'),
'password' => Hash::make('12345678'),
'options' => sprintf('{"old_customer_id":"%s"}', $data->id),
]);
$user->assignRole('vendor');

View File

@@ -363,6 +363,7 @@
"Seller": "Satyjy",
"View products": "Harytlary gör",
"Sections": "Bölümler",
"Section": "Bölüm",
"Product count": "Haryt sany",
"Count": "Sany",
"Shipping rates": "Eltip bermek nyrhnamasy",
@@ -397,5 +398,6 @@
"Inventory exit": "Çykyş",
"Total count": "Jemi sany",
"Verification code is incorrect": "Tassyklaýyş belgi ýalňyş",
"User with this phone number does not exist": "GIrizilen telefon belgili ulanyjy tapylmady"
"User with this phone number does not exist": "GIrizilen telefon belgili ulanyjy tapylmady",
"Page not found": "Sahypa tapylmady"
}

View File

@@ -0,0 +1,219 @@
<!DOCTYPE html>
<html>
<head>
<title>404 - Page Not Found</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
body {
margin: 0;
padding: 0;
background-color: #0a1821;
height: 100vh;
width: 100vw;
font-family: Roboto, Arial, sans-serif;
color: #fff;
text-align: center;
}
.not-found {
width: 560px;
height: 225px;
margin-right: -10px;
}
.starry-sky {
display: block;
width: 100vw;
height: 100vh;
object-fit: cover;
}
.search-icon {
display: inline-block;
}
.notfound-copy {
color: #fff;
position: fixed;
top: 25px;
right: 10%;
text-align: right;
}
h1 {
font-weight: 700;
font-size: 40px;
}
a {
font-weight: 300;
color: #fff;
border-bottom: 1.5px solid #5581d4;
text-decoration: none;
}
a:hover {
font-weight: 300;
color: #fff;
border-bottom: 2px solid #fff;
text-decoration: none;
}
/* change to alternating star opacities */
.all-stars {
animation: blinkblink 4s linear infinite;
}
@keyframes blinkblink {
50% {
opacity: 0.1;
}
}
@keyframes float {
0% {
transform: translateY(0px);
}
50% {
transform: translateY(-25px);
}
100% {
transform: translateY(0px);
}
}
.floating-object {
animation: float 3s ease-in-out infinite;
transform-box: fill-box;
}
.moon {}
input[type=text] {
color: #fff;
background-color: #0a1821;
padding: 5px;
border: none;
border-bottom: 2px solid #ccc;
font-size: 18px;
}
input[type=text]:focus {
border-color: none;
border-bottom: 2px solid #ccc;
}
@media (max-width: 647px) {
.moon {
padding-top: -500px;
}
}
</style>
</head>
<body>
<div class="notfound-copy">
<svg aria-labelledby="404" alt="404 Page not found" class="not-found">
<title id="svgtitle1">404 Page not found</title>
<g class="404-text floating-object">
<g opacity=".5" fill="#3B3D3D">
<path
d="M320.1 209.5c0 7.2-6 12.5-13.7 12.5s-13.7-5.4-13.7-12.5v-16.3h-43.8c-8.4 0-14.1-6.4-14.1-13.5 0-.8.8-4.4 2-7L275 84.5c2-4.8 7.2-7.8 12.3-7.8 6.8 0 13.7 6 13.7 12.9 0 1.2 0 2.6-.8 4.4-11.5 26.7-20.9 47.6-32.4 74.2h24.9v-30.4c0-7.2 6-12.5 13.7-12.5 7.4 0 13.7 5.4 13.7 12.5v30.4h2.6c7.6 0 13.3 5.8 13.3 12.3 0 7-5.8 12.5-13.3 12.5h-2.6v16.5zM436.9 123.9v54.7c0 24.3-16.3 43.6-46 43.6s-46-19.3-46-43.6v-54.7c0-26.3 13.9-47.4 46-47.4 32.1.1 46 21.2 46 47.4zm-64.3-1.4v56.1c0 11.3 6.4 19.1 18.3 19.1s18.3-7.8 18.3-19.1v-56.1c0-12.7-5.2-21.7-18.3-21.7s-18.3 9-18.3 21.7zM535 209.5c0 7.2-6 12.5-13.7 12.5s-13.7-5.4-13.7-12.5v-16.3h-43.8c-8.4 0-14.1-6.4-14.1-13.5 0-.8.8-4.4 2-7l38.2-88.2c2-4.8 7.2-7.8 12.3-7.8 6.8 0 13.7 6 13.7 12.9 0 1.2 0 2.6-.8 4.4-11.5 26.7-20.9 47.6-32.4 74.2h24.9v-30.4c0-7.2 6-12.5 13.7-12.5 7.4 0 13.7 5.4 13.7 12.5v30.4h2.6c7.6 0 13.3 5.8 13.3 12.3 0 7-5.8 12.5-13.3 12.5H535v16.5z" />
</g>
<g fill="#FFF">
<path
d="M326.4 197.5c0 7.2-6 12.5-13.7 12.5s-13.7-5.4-13.7-12.5v-16.3h-43.8c-8.4 0-14.1-6.4-14.1-13.5 0-.8.8-4.4 2-7l38.2-88.2c2-4.8 7.2-7.8 12.3-7.8 6.8 0 13.7 6 13.7 12.9 0 1.2 0 2.6-.8 4.4-11.5 26.7-20.9 47.6-32.4 74.2H299v-30.4c0-7.2 6-12.5 13.7-12.5 7.4 0 13.7 5.4 13.7 12.5v30.4h2.6c7.6 0 13.3 5.8 13.3 12.3 0 7-5.8 12.5-13.3 12.5h-2.6v16.5zM443.2 111.9v54.7c0 24.3-16.3 43.6-46 43.6s-46-19.3-46-43.6v-54.7c0-26.3 13.9-47.4 46-47.4 32.1.1 46 21.2 46 47.4zm-64.2-1.4v56.1c0 11.3 6.4 19.1 18.3 19.1s18.3-7.8 18.3-19.1v-56.1c0-12.7-5.2-21.7-18.3-21.7s-18.3 9-18.3 21.7zM541.4 197.5c0 7.2-6 12.5-13.7 12.5-7.8 0-13.7-5.4-13.7-12.5v-16.3h-43.8c-8.4 0-14.1-6.4-14.1-13.5 0-.8.8-4.4 2-7l38.2-88.2c2-4.8 7.2-7.8 12.3-7.8 6.8 0 13.7 6 13.7 12.9 0 1.2 0 2.6-.8 4.4-11.5 26.7-20.9 47.6-32.4 74.2H514v-30.4c0-7.2 6-12.5 13.7-12.5 7.4 0 13.7 5.4 13.7 12.5v30.4h2.6c7.6 0 13.3 5.8 13.3 12.3 0 7-5.8 12.5-13.3 12.5h-2.6v16.5z" />
</g>
</g>
</svg>
<h1>{{ __('Page not found') }}</h1>
<p><a href="/">{{ __('Home') }}</a></p>
</div>
<svg aria-labelledby="Starry sky" alt="Starry sky" class="starry-sky">
<title id="svgtitle2">Starry sky</title>
<!-- STARS START -->
<g class="all-stars" fill="#F6F5BC">
<path class="stars-one"
d="M148.9 151.5c-.3-.3-.6-.4-1-.4s-.7.1-1 .4c-1.6 4.9-6.2 6.2-6.2 6.2-.3.3-.4.6-.4 1s.1.7.4 1c4.9 1.6 6.2 6.2 6.2 6.2.3.3.6.4 1 .4s.7-.1 1-.4c1.6-4.9 6.2-6.2 6.2-6.2.3-.3.4-.6.4-1s-.1-.7-.4-1c-5-1.7-6.2-6.2-6.2-6.2zM93.6 " />
<path class="stars-two"
d="M148.9 540.6c-4.9-1.6-6.2-6.2-6.2-6.2-.3-.3-.6-.4-1-.4s-.7.1-1 .4c-1.6 4.9-6.2 6.2-6.2 6.2-.3.3-.4.6-.4 1s.1.7.4 1c4.9 1.6 6.2 6.2 6.2 6.2.3.3.6.4 1 .4s.7-.1 1-.4c1.6-4.9 6.2-6.2 6.2-6.2.3-.3.4-.6.4-1s-.1-.8-.4-1zM526.3 551.7c-.3-.3-.6-.4-1-.4s-.7.1-1 .4c-1.6 4.9-6.2 6.2-6.2 6.2-.3.3-.4.6-.4 1s.1.7.4 1c4.9 1.6 6.2 6.2 6.2 6.2.3.3.6.4 1 .4s.7-.1 1-.4c1.6-4.9 6.2-6.2 6.2-6.2.3-.3.4-.6.4-1s-.1-.7-.4-1c-5-1.7-6.2-6.2-6.2-6.2zM617.4 291.9c-.3-.3-.6-.4-1-.4s-.7.1-1 .4c-1.6 4.9-6.2 6.2-6.2 6.2-.3.3-.4.6-.4 1s.1.7.4 1c4.9 1.6 6.2 6.2 6.2 6.2.3.3.6.4 1 .4s.7-.1 1-.4c1.6-4.9 6.2-6.2 6.2-6.2.3-.3.4-.6.4-1s-.1-.7-.4-1c-5-1.6-6.2-6.2-6.2-6.2zM681.9 42.7c-.3-.3-.6-.4-1-.4s-.7.1-1 .4c-1.6 4.9-6.2 6.2-6.2 6.2-.3.3-.4.6-.4 1s.1.7.4 1c4.9 1.6 6.2 6.2 6.2 6.2.3.3.6.4 1 .4s.7-.1 1-.4c1.6-4.9 6.2-6.2 6.2-6.2.3-.3.4-.6.4-1s-.1-.7-.4-1c-5-1.7-6.2-6.2-6.2-6.2zM51.1 83.9c-.3-.3-.6-.4-1-.4s-.7.1-1 .4C47.5 88.8 43 90 43 90c-.3.3-.4.6-.4 1s.1.7.4 1c4.9 1.6 6.2 6.2 6.2 6.2.3.3.6.4 1 .4s.7-.1 1-.4c1.6-4.9 6.2-6.2 6.2-6.2.3-.3.4-.6.4-1s-.1-.7-.4-1c-5.1-1.6-6.3-6.1-6.3-6.1z" />
<path class="all-stars"
d="M148.9 151.5c-.3-.3-.6-.4-1-.4s-.7.1-1 .4c-1.6 4.9-6.2 6.2-6.2 6.2-.3.3-.4.6-.4 1s.1.7.4 1c4.9 1.6 6.2 6.2 6.2 6.2.3.3.6.4 1 .4s.7-.1 1-.4c1.6-4.9 6.2-6.2 6.2-6.2.3-.3.4-.6.4-1s-.1-.7-.4-1c-5-1.7-6.2-6.2-6.2-6.2zM93.6 318.5c-.3-.3-.6-.4-1-.4s-.7.1-1 .4c-1.6 4.9-6.2 6.2-6.2 6.2-.3.3-.4.6-.4 1s.1.7.4 1c4.9 1.6 6.2 6.2 6.2 6.2.3.3.6.4 1 .4s.7-.1 1-.4c1.6-4.9 6.2-6.2 6.2-6.2.3-.3.4-.6.4-1s-.1-.7-.4-1c-5-1.7-6.2-6.2-6.2-6.2zM132.3 169c-.2-.2-.4-.3-.6-.3s-.4.1-.6.3c-1 3.1-3.8 3.8-3.8 3.8-.2.2-.3.4-.3.6 0 .2.1.4.3.6 3.1 1 3.8 3.8 3.8 3.8.2.2.4.3.6.3s.4-.1.6-.3c1-3.1 3.8-3.8 3.8-3.8.2-.2.3-.4.3-.6 0-.2-.1-.4-.3-.6-3-.9-3.8-3.8-3.8-3.8zM585.9 269.5c-.3-.3-.6-.4-1-.4s-.7.1-1 .4c-1.6 4.9-6.2 6.2-6.2 6.2-.3.3-.4.6-.4 1s.1.7.4 1c4.9 1.6 6.2 6.2 6.2 6.2.3.3.6.4 1 .4s.7-.1 1-.4c1.6-4.9 6.2-6.2 6.2-6.2.3-.3.4-.6.4-1s-.1-.7-.4-1c-5-1.7-6.2-6.2-6.2-6.2zM723.4 540.6c-4.9-1.6-6.2-6.2-6.2-6.2-.3-.3-.6-.4-1-.4s-.7.1-1 .4c-1.6 4.9-6.2 6.2-6.2 6.2-.3.3-.4.6-.4 1s.1.7.4 1c4.9 1.6 6.2 6.2 6.2 6.2.3.3.6.4 1 .4s.7-.1 1-.4c1.6-4.9 6.2-6.2 6.2-6.2.3-.3.4-.6.4-1s-.1-.8-.4-1zM526.3 551.7c-.3-.3-.6-.4-1-.4s-.7.1-1 .4c-1.6 4.9-6.2 6.2-6.2 6.2-.3.3-.4.6-.4 1s.1.7.4 1c4.9 1.6 6.2 6.2 6.2 6.2.3.3.6.4 1 .4s.7-.1 1-.4c1.6-4.9 6.2-6.2 6.2-6.2.3-.3.4-.6.4-1s-.1-.7-.4-1c-5-1.7-6.2-6.2-6.2-6.2zM617.4 291.9c-.3-.3-.6-.4-1-.4s-.7.1-1 .4c-1.6 4.9-6.2 6.2-6.2 6.2-.3.3-.4.6-.4 1s.1.7.4 1c4.9 1.6 6.2 6.2 6.2 6.2.3.3.6.4 1 .4s.7-.1 1-.4c1.6-4.9 6.2-6.2 6.2-6.2.3-.3.4-.6.4-1s-.1-.7-.4-1c-5-1.6-6.2-6.2-6.2-6.2zM681.9 42.7c-.3-.3-.6-.4-1-.4s-.7.1-1 .4c-1.6 4.9-6.2 6.2-6.2 6.2-.3.3-.4.6-.4 1s.1.7.4 1c4.9 1.6 6.2 6.2 6.2 6.2.3.3.6.4 1 .4s.7-.1 1-.4c1.6-4.9 6.2-6.2 6.2-6.2.3-.3.4-.6.4-1s-.1-.7-.4-1c-5-1.7-6.2-6.2-6.2-6.2zM51.1 83.9c-.3-.3-.6-.4-1-.4s-.7.1-1 .4C47.5 88.8 43 90 43 90c-.3.3-.4.6-.4 1s.1.7.4 1c4.9 1.6 6.2 6.2 6.2 6.2.3.3.6.4 1 .4s.7-.1 1-.4c1.6-4.9 6.2-6.2 6.2-6.2.3-.3.4-.6.4-1s-.1-.7-.4-1c-5.1-1.6-6.3-6.1-6.3-6.1z" />
</g>
<!-- Moon shadow -->
<g class="moon">
<g class="floating-object">
<path opacity=".5" fill="#3B3D3D" d="M122.7 373.9L-237.1 744l283.8 269.4 279.9-492.8z" />
<!-- Moon color base -->
<g fill="#D1D5D6">
<path
d="M209 340.6c-70.9 0-128.4 57.5-128.4 128.4S138.1 597.4 209 597.4 337.4 539.9 337.4 469 279.9 340.6 209 340.6zm-41.9 176.1c0 10.9-8.9 19.8-19.8 19.8s-19.8-8.9-19.8-19.8c0-1.4.1-2.7.4-4-18.3-1.7-32.6-17-32.6-35.7 0-19.8 16.1-35.9 35.9-35.9 19.8 0 35.9 16.1 35.9 35.9 0 8.9-3.3 17.1-8.6 23.3 5.1 3.7 8.6 9.7 8.6 16.4zm20.2 3.9c0-8.8 7.1-16 16-16 8.8 0 16 7.1 16 16 0 8.8-7.1 16-16 16-8.9-.1-16-7.2-16-16zm5.8-43.5c0-13.3 10.7-24 24-24s24 10.7 24 24-10.7 24-24 24-24-10.8-24-24zm72.7 86.4c-4.2 0-8.2-.7-11.9-2-.5 12.8-11 23.1-24 23.1-13.3 0-24-10.7-24-24s10.7-24 24-24h1.1c-.7-2.9-1.1-5.8-1.1-8.9 0-19.8 16.1-35.9 35.9-35.9 19.8 0 35.9 16.1 35.9 35.9s-16.1 35.8-35.9 35.8zm10.9-91.1c0 2.6-2.1 4.6-4.6 4.6-2.6 0-4.6-2.1-4.6-4.6 0-2.6 2.1-4.6 4.6-4.6 2.5 0 4.6 2.1 4.6 4.6zm27.7 13.1c-2.1 2-4.9 3.2-8.1 3.2-6.5 0-11.7-5.2-11.7-11.7 0-6.5 5.2-11.7 11.7-11.7.5 0 1.1 0 1.6.1-1.5-3.6-3.2-7.1-5.1-10.5 2.5-4 3.9-8.7 3.9-13.8 0-14.6-11.8-26.4-26.4-26.4-3.6 0-7 .7-10.2 2-4.9-3.8-10.2-7.2-15.7-10.2.1 1 .2 2 .2 3.1 0 17.4-14.1 31.5-31.5 31.5-16.7 0-30.4-13-31.4-29.5 0-.7-.1-1.3-.1-2 0-3.4.5-6.6 1.5-9.6.9-2.8 2.2-5.5 3.8-7.9-2.2 0-4.3.1-6.5.2-3.8-6.7-11-11.2-19.2-11.2-11.8 0-21.5 9.3-22.1 21-17.1 7.5-32.1 18.8-43.9 32.8 14.3-50.3 60.6-87.2 115.6-87.2 66.3 0 120.1 53.8 120.1 120.1 0 29.5-10.6 56.4-28.2 77.3 3-10.5 4.6-21.5 4.6-32.9.1-9.1-.9-18.1-2.9-26.7z" />
<circle cx="256.3" cy="384.3" r="14.1" />
</g>
<g fill="#FFF">
<circle cx="217.1" cy="477.1" r="24" />
<path
d="M265.8 491.7c-19.8 0-35.9 16.1-35.9 35.9 0 3.1.4 6.1 1.1 8.9h-1.1c-13.3 0-24 10.7-24 24s10.7 24 24 24c12.9 0 23.5-10.2 24-23.1 3.7 1.3 7.7 2 11.9 2 19.8 0 35.9-16.1 35.9-35.9s-16.1-35.8-35.9-35.8zM131.2 441.2c-19.8 0-35.9 16.1-35.9 35.9 0 18.7 14.3 34.1 32.6 35.7-.3 1.3-.4 2.6-.4 4 0 10.9 8.9 19.8 19.8 19.8s19.8-8.9 19.8-19.8c0-6.8-3.4-12.8-8.6-16.3 5.4-6.3 8.6-14.4 8.6-23.3 0-20-16.1-36-35.9-36z" />
<path
d="M331 467.9c0-66.3-53.8-120.1-120.1-120.1-54.9 0-101.3 36.9-115.6 87.2 11.8-14.1 26.9-25.4 43.9-32.8.6-11.7 10.2-21 22.1-21 8.2 0 15.4 4.5 19.2 11.2 2.2-.1 4.3-.2 6.5-.2-1.6 2.4-2.9 5.1-3.8 7.9-1 3-1.5 6.3-1.5 9.6 0 .7 0 1.3.1 2 1 16.5 14.7 29.5 31.4 29.5 17.4 0 31.5-14.1 31.5-31.5 0-1-.1-2.1-.2-3.1 5.5 3 10.7 6.4 15.7 10.2 3.1-1.3 6.6-2 10.2-2 14.6 0 26.4 11.8 26.4 26.4 0 5-1.4 9.8-3.9 13.8 1.9 3.4 3.6 6.9 5.1 10.5-.5-.1-1.1-.1-1.6-.1-6.5 0-11.7 5.2-11.7 11.7 0 6.5 5.2 11.7 11.7 11.7 3.1 0 6-1.2 8.1-3.2 2 8.6 3 17.6 3 26.8 0 11.4-1.6 22.5-4.6 32.9 17.5-21 28.1-48 28.1-77.4z" />
<circle cx="203.2" cy="520.6" r="16" />
<circle cx="272.1" cy="472.4" r="4.6" />
</g>
<path opacity=".1" fill-rule="evenodd" clip-rule="evenodd" fill="#3B3D3D"
d="M201.2 368.2l-.4-.8c-.4-.7-1.4-1-2.1-.6l-80.5 46.8c-.7.4-1 1.4-.6 2.1l.4.8c.3.5.8.8 1.4.8.3 0 .5-.1.8-.2l1-.6c0 .1.1.3.2.4l24.2 41.7c.2.4.7.7 1.2.7.2 0 .5-.1.7-.2l34.1-19.8c.3-.2.5-.5.6-.8.1-.4 0-.7-.1-1l-24.2-41.7-.3-.3 43.2-25.1c.4-.2.6-.5.7-1-.1-.4-.1-.8-.3-1.2z" />
<!-- Flag stick -->
<g>
<path fill="#acacac" stroke="#B5B5B6" stroke-miterlimit="10"
d="M200.8 369.5c-.2.6-.8 1-1.4.8l-.9-.2c-.6-.2-1-.8-.8-1.4l25.5-89.5c.2-.6.8-1 1.4-.8l.9.2c.6.2 1 .8.8 1.4l-25.5 89.5z" />
<path fill-rule="evenodd" clip-rule="evenodd" fill="#263563" stroke="#B5B5B6" stroke-miterlimit="10"
d="M262.9 334c-.1.5-.7.8-1.2.7l-46.4-13.2c-.5-.1-.8-.7-.7-1.2l10.8-38c.1-.5.7-.8 1.2-.7l46.4 13.2c.5.1.8.7.7 1.2l-10.8 38z" />
<g fill-rule="evenodd" clip-rule="evenodd" fill="#263563"> <!-- #F9DA1E -->
<path
d="M249 311.1c.4.6.8 1.1 1.2 1.6.4.5.9.9.3 1.6.8.2 1.6.3 2.3.5-.1.4-.2.6-.2.9 0 .1.1.2.1.3.1-.1.2-.1.2-.2l.3-.9c.7.3 1.4.5 2.1.7.1-.8.1-.8 1-1.2.7-.3 1.3-.5 2-.8-.5-.7-.5-.7 0-1.3.1-.2.2-.4.4-.5-.5-.5-1.6.1-1.6-1.1-.4.3-.7.5-1 .7-.2.1-.4.1-.6.2 0-.2 0-.4.1-.6l.9-1.8c-.3.1-.6.1-.8.2-.2-.7-.3-1.3-.5-2-.5.5-1 .9-1.5 1.4l-.6-.6c-.1.8-.1 1.5-.2 2.3 0 .1-.1.3-.2.4-.1-.1-.3-.2-.3-.3-.2-.4-.3-.8-.5-1.2-.6.5-.6.5-1.3.2-.2-.1-.4-.1-.6-.2-.4.5.4 1.5-1 1.7z" />
<path
d="M260.2 298.6c-.6 1.1-1.2 2.2-1.8 3.4-.6 1.3-1.3 2.5-1.9 3.8-.4.8-.5.9-1.4.4.2-.8.4-1.6.5-2.4.1-.5.1-1 .2-1.5.1-1.8-1.4-3.7-3.2-3.9-.1 0-.4.2-.5.3-.9 1.6-1.7 3.3-2.5 5 0 .1-.1.2-.1.2-.2.2-.1.6-.6.4-.4-.2-.1-.4-.1-.7.2-.9.4-1.8.5-2.8 0-.7-.1-1.4-.3-2.1-.1-.4-.3-.5-.8-.4-1.8.7-3.3 1.7-4.5 3.1-2.5 2.6-4.3 5.6-5.2 9.1-.5 1.9-.5 3.8.3 5.6.8 1.7 1.2 1.8 2.8.8.7-.4 1.3-.9 2-1.4.3-.2.6-.3 1 0-1.2 1-2.4 2-3.7 2.9.7.6 1.6.8 2.6 1 2.5.4 4.6-.7 6.4-2.3.7-.6 1.2-.5 1.9-.2v.1c-2 2.2-4.2 4.1-7.3 4.3-2.3.2-4.4-.4-6.4-1.8-.2-.2-.5-.3-.8-.3-5.5.1-9.3-3.9-9-9.4 0-.7.1-1.4.2-2 .2-.6.4-1.3.5-1.9.1-.2.2-.3.3-.5.6-1.2 1.1-2.5 1.9-3.6 2.5-3.8 5.9-6.4 10.5-7.2 1.9-.3 3.9-.3 5.6.6 1.1.6 2.1.7 3.3.7.6 0 1.2.1 1.8.1.5.1.9.3 1.4.4.2.1.5.3.7.4.8.6 1.6 1.1 2.4 1.7.4.3.8.4 1.2.1.4-.2.7-.5 1.1-.7.3.5.7.6 1 .7zm-13.4-2c-.7-.5-2.1-.7-3.4-.6-2.1.2-3.8 1.2-5.3 2.4-2.4 2-4.2 4.6-5.5 7.5-.8 1.7-1.3 3.5-1.3 5.4 0 2.5.8 4.6 3.1 5.9.6.3 1.2.5 1.9.8 0-.1.1-.1.1-.2s-.1-.2-.1-.2c-1-1.5-1.4-3.1-1.4-4.9-.1-2.3.5-4.5 1.5-6.6 1.6-3.3 3.9-6.1 7.1-8 .9-.5 2.1-.9 3.3-1.5z" />
<path
d="M249 311.1c1.4-.1.5-1.1.8-1.8.2.1.4.1.6.2.7.3.7.3 1.3-.2.2.4.3.8.5 1.2l.3.3c.1-.1.2-.3.2-.4.1-.7.1-1.5.2-2.3l.6.6c.5-.4 1-.9 1.5-1.4.2.7.4 1.3.5 2 .3-.1.5-.1.8-.2-.3.7-.6 1.2-.9 1.8-.1.2-.1.4-.1.6.2-.1.4-.1.6-.2.3-.2.6-.4 1-.7 0 1.2 1.1.7 1.6 1.1-.1.2-.2.3-.4.5-.5.7-.5.7 0 1.3-.7.3-1.3.5-2 .8-.8.3-.8.3-1 1.2-.7-.2-1.3-.5-2.1-.7l-.3.9c0 .1-.2.2-.2.2 0-.1-.1-.2-.1-.3 0-.3.1-.5.2-.9-.7-.2-1.5-.3-2.3-.5.6-.7.1-1.2-.3-1.6-.1-.5-.5-1-1-1.5z" />
</g>
</g>
</g>
</g>
</svg>
<script>
// Use requestAnimationFrame for smooth animation performance
let rafId = null;
document.addEventListener('mousemove', (e) => {
if (rafId) return;
rafId = requestAnimationFrame(() => {
const width = window.innerWidth;
const height = window.innerHeight;
const mouseX = e.clientX;
const mouseY = e.clientY;
const xVal = (mouseX - width / 2) / width;
const yVal = (mouseY - height / 2) / height;
const moon = document.querySelector('.moon');
const stars = document.querySelector('.all-stars');
const notFound = document.querySelector('.notfound-copy');
// Parallax factors - adjust multipliers to control speed/depth
if (moon) moon.style.transform = `translate(${xVal * 40}px, ${yVal * 40}px)`;
if (stars) stars.style.transform = `translate(${xVal * 20}px, ${yVal * 20}px)`;
// Inverse movement for foreground to create depth
if (notFound) notFound.style.transform = `translate(${xVal * -30}px, ${yVal * -30}px)`;
rafId = null;
});
});
</script>
</body>
</html>

View File

@@ -6,6 +6,7 @@ use App\Http\Controllers\Api\V1\Brand\BrandController;
use App\Http\Controllers\Api\V1\Carousel\CarouselController;
use App\Http\Controllers\Api\V1\CartController;
use App\Http\Controllers\Api\V1\Category\CategoryController;
use App\Http\Controllers\Api\V1\Category\SelectedCategoryController;
use App\Http\Controllers\Api\V1\Channel\ChannelController;
use App\Http\Controllers\Api\V1\Collection\CollectionController;
use App\Http\Controllers\Api\V1\ContactMessageController;
@@ -64,6 +65,10 @@ Route::get('categories', [CategoryController::class, 'index']);
Route::get('categories/{category}', [CategoryController::class, 'show'])->where(['category' => '[0-9]+']);
Route::get('categories/{category}/products', [CategoryController::class, 'products'])->where(['category' => '[0-9]+']);
// Selected Categories...
Route::get('selected-categories', [SelectedCategoryController::class, 'index']);
Route::get('selected-categories/{selectedCategory}', [SelectedCategoryController::class, 'show'])->where(['selectedCategory' => '[0-9]+']);
// Collections...
Route::get('collections', [CollectionController::class, 'index']);
Route::get('collections-paginated', [CollectionController::class, 'paginated']);
@@ -87,6 +92,7 @@ Route::middleware('auth:sanctum')
// Order settings...
Route::get('order-time', [OrderController::class, 'time']);
Route::get('order-payments', [OrderPaymentController::class, 'index']);
Route::get('order-deliveries', [OrderController::class, 'deliveries']);
// Provinces...
Route::get('provinces', [ProvinceController::class, 'index']);

View File

@@ -217,7 +217,7 @@ test('can list related products', function () {
// Then it selects product_ids from those categories.
// It doesn't explicitly exclude the main product ID in the query shown in tool output?
// Let's check the controller code again.
// It selects `product_has_relations.product_id` where `productable_id` IN (categories of main product).
// It selects `category_product.product_id` where `category_id` IN (categories of main product).
// It does NOT seem to exclude the main product ID in the query.
// However, `unique()` is used.
// If the main product is returned, count would be 3. If excluded, 2.