mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-20 21:41:55 +00:00
1298 lines
34 KiB
Go
1298 lines
34 KiB
Go
package seed
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
|
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
|
approvalutils "gitlab.com/mbugroup/lti-api.git/internal/utils/approvals"
|
|
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
func Run(db *gorm.DB) error {
|
|
return db.Transaction(func(tx *gorm.DB) error {
|
|
users, err := seedUsers(tx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
adminID := users["admin"]
|
|
|
|
uoms, err := seedUoms(tx, adminID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
areas, err := seedAreas(tx, adminID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
locations, err := seedLocations(tx, adminID, areas)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
productCategories, err := seedProductCategories(tx, adminID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
flocks, err := seedFlocks(tx, adminID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
fcrs, err := seedFcr(tx, adminID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
projectFlocks, err := seedProjectFlocks(tx, adminID, flocks, areas, fcrs, locations)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
kandangs, err := seedKandangs(tx, adminID, locations, users, projectFlocks)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := seedWarehouses(tx, adminID, areas, locations, kandangs); err != nil {
|
|
return err
|
|
}
|
|
|
|
suppliers, err := seedSuppliers(tx, adminID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := seedCustomers(tx, adminID, users); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := seedProducts(tx, adminID, uoms, productCategories, suppliers); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := seedNonstocks(tx, adminID, uoms, suppliers); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := seedBanks(tx, adminID); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := seedProductWarehouse(tx, adminID); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := seedTransferStock(tx, adminID); err != nil {
|
|
return err
|
|
}
|
|
// if err := seedChickin(tx, adminID); err != nil {
|
|
// return err
|
|
// }
|
|
|
|
fmt.Println("✅ Master data seeding completed")
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func seedUsers(tx *gorm.DB) (map[string]uint, error) {
|
|
seeds := []struct {
|
|
Key string
|
|
Data entity.User
|
|
}{
|
|
{
|
|
Key: "admin",
|
|
Data: entity.User{Email: "admin@mbugroup.id", IdUser: 1, Name: "Super Admin"},
|
|
},
|
|
}
|
|
|
|
result := make(map[string]uint, len(seeds))
|
|
|
|
for _, seed := range seeds {
|
|
var user entity.User
|
|
err := tx.Where("email = ?", seed.Data.Email).First(&user).Error
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
user = seed.Data
|
|
if err := tx.Create(&user).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
} else if err != nil {
|
|
return nil, err
|
|
}
|
|
result[seed.Key] = user.Id
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
func seedUoms(tx *gorm.DB, createdBy uint) (map[string]uint, error) {
|
|
names := []string{"Kilogram", "Gram", "Liter", "Unit", "Ekor"}
|
|
result := make(map[string]uint, len(names))
|
|
|
|
for _, name := range names {
|
|
var uom entity.Uom
|
|
err := tx.Where("name = ?", name).First(&uom).Error
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
uom = entity.Uom{Name: name, CreatedBy: createdBy}
|
|
if err := tx.Create(&uom).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
} else if err != nil {
|
|
return nil, err
|
|
}
|
|
result[name] = uom.Id
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
func seedAreas(tx *gorm.DB, createdBy uint) (map[string]uint, error) {
|
|
names := []string{"Priangan", "Banten"}
|
|
result := make(map[string]uint, len(names))
|
|
|
|
for _, name := range names {
|
|
var area entity.Area
|
|
err := tx.Where("name = ?", name).First(&area).Error
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
area = entity.Area{Name: name, CreatedBy: createdBy}
|
|
if err := tx.Create(&area).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
} else if err != nil {
|
|
return nil, err
|
|
}
|
|
result[name] = area.Id
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
func seedLocations(tx *gorm.DB, createdBy uint, areas map[string]uint) (map[string]uint, error) {
|
|
seeds := []struct {
|
|
Name string
|
|
Address string
|
|
Area string
|
|
}{
|
|
{"Singaparna", "Tasik", "Priangan"},
|
|
{"Cikaum", "Cikaum", "Banten"},
|
|
}
|
|
|
|
result := make(map[string]uint, len(seeds))
|
|
|
|
for _, seed := range seeds {
|
|
areaID, ok := areas[seed.Area]
|
|
if !ok {
|
|
return nil, fmt.Errorf("area %s not seeded", seed.Area)
|
|
}
|
|
|
|
var loc entity.Location
|
|
err := tx.Where("name = ?", seed.Name).First(&loc).Error
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
loc = entity.Location{
|
|
Name: seed.Name,
|
|
Address: seed.Address,
|
|
AreaId: areaID,
|
|
CreatedBy: createdBy,
|
|
}
|
|
if err := tx.Create(&loc).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
} else if err != nil {
|
|
return nil, err
|
|
}
|
|
result[seed.Name] = loc.Id
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
func seedFlocks(tx *gorm.DB, createdBy uint) (map[string]uint, error) {
|
|
names := []string{"Flock Priangan", "Flock Banten"}
|
|
result := make(map[string]uint, len(names))
|
|
|
|
for _, name := range names {
|
|
var flock entity.Flock
|
|
err := tx.Where("name = ?", name).First(&flock).Error
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
flock = entity.Flock{
|
|
Name: name,
|
|
CreatedBy: createdBy,
|
|
}
|
|
if err := tx.Create(&flock).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
} else if err != nil {
|
|
return nil, err
|
|
} else {
|
|
if err := tx.Model(&entity.Flock{}).Where("id = ?", flock.Id).Updates(map[string]any{
|
|
"created_by": createdBy,
|
|
}).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
result[name] = flock.Id
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
func seedProjectFlocks(tx *gorm.DB, createdBy uint, flocks, areas, fcrs, locations map[string]uint) (map[string]uint, error) {
|
|
seeds := []struct {
|
|
Key string
|
|
Flock string
|
|
Area string
|
|
Category utils.ProjectFlockCategory
|
|
Fcr string
|
|
Location string
|
|
Period int
|
|
}{
|
|
{
|
|
Key: "Singaparna Period 1",
|
|
Flock: "Flock Priangan",
|
|
Area: "Priangan",
|
|
Category: utils.ProjectFlockCategoryGrowing,
|
|
Fcr: "FCR Layer",
|
|
Location: "Singaparna",
|
|
Period: 1,
|
|
},
|
|
{
|
|
Key: "Cikaum Period 1",
|
|
Flock: "Flock Banten",
|
|
Area: "Banten",
|
|
Category: utils.ProjectFlockCategoryGrowing,
|
|
Fcr: "FCR Layer",
|
|
Location: "Cikaum",
|
|
Period: 1,
|
|
},
|
|
}
|
|
|
|
result := make(map[string]uint, len(seeds))
|
|
|
|
for _, seed := range seeds {
|
|
flockID, ok := flocks[seed.Flock]
|
|
if !ok {
|
|
return nil, fmt.Errorf("floc %s not seeded", seed.Flock)
|
|
}
|
|
areaID, ok := areas[seed.Area]
|
|
if !ok {
|
|
return nil, fmt.Errorf("area %s not seeded", seed.Area)
|
|
}
|
|
fcrID, ok := fcrs[seed.Fcr]
|
|
if !ok {
|
|
return nil, fmt.Errorf("fcr %s not seeded", seed.Fcr)
|
|
}
|
|
locationID, ok := locations[seed.Location]
|
|
if !ok {
|
|
return nil, fmt.Errorf("location %s not seeded", seed.Location)
|
|
}
|
|
|
|
var projectFlock entity.ProjectFlock
|
|
err := tx.Where("flock_id = ? AND area_id = ? AND category = ? AND fcr_id = ? AND location_id = ? AND period = ?",
|
|
flockID, areaID, seed.Category, fcrID, locationID, seed.Period).First(&projectFlock).Error
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
projectFlock = entity.ProjectFlock{
|
|
FlockId: flockID,
|
|
AreaId: areaID,
|
|
Category: string(seed.Category),
|
|
FcrId: fcrID,
|
|
LocationId: locationID,
|
|
Period: seed.Period,
|
|
CreatedBy: createdBy,
|
|
}
|
|
if err := tx.Create(&projectFlock).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
} else if err != nil {
|
|
return nil, err
|
|
} else {
|
|
if err := tx.Model(&entity.ProjectFlock{}).Where("id = ?", projectFlock.Id).Updates(map[string]any{
|
|
"flock_id": flockID,
|
|
"area_id": areaID,
|
|
"category": string(seed.Category),
|
|
"fcr_id": fcrID,
|
|
"location_id": locationID,
|
|
"period": seed.Period,
|
|
}).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
if err := ensureProjectFlockApprovals(tx, projectFlock.Id, createdBy); err != nil {
|
|
return nil, err
|
|
}
|
|
result[seed.Key] = projectFlock.Id
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
func ensureProjectFlockApprovals(tx *gorm.DB, projectFlockID uint, actorID uint) error {
|
|
if projectFlockID == 0 || actorID == 0 {
|
|
return nil
|
|
}
|
|
|
|
workflow := utils.ApprovalWorkflowProjectFlock.String()
|
|
|
|
steps := []struct {
|
|
step approvalutils.ApprovalStep
|
|
action entity.ApprovalAction
|
|
}{
|
|
{step: utils.ProjectFlockStepPengajuan, action: entity.ApprovalActionCreated},
|
|
{step: utils.ProjectFlockStepAktif, action: entity.ApprovalActionApproved},
|
|
}
|
|
|
|
for _, cfg := range steps {
|
|
var count int64
|
|
if err := tx.Model(&entity.Approval{}).
|
|
Where("approvable_type = ? AND approvable_id = ? AND step_number = ?", workflow, projectFlockID, uint16(cfg.step)).
|
|
Count(&count).Error; err != nil {
|
|
return err
|
|
}
|
|
if count > 0 {
|
|
continue
|
|
}
|
|
|
|
stepName, ok := utils.ProjectFlockApprovalSteps[cfg.step]
|
|
if !ok || strings.TrimSpace(stepName) == "" {
|
|
stepName = fmt.Sprintf("Step %d", cfg.step)
|
|
}
|
|
|
|
var actionPtr *entity.ApprovalAction
|
|
action := cfg.action
|
|
actionPtr = &action
|
|
|
|
record := entity.Approval{
|
|
ApprovableType: workflow,
|
|
ApprovableId: projectFlockID,
|
|
StepNumber: uint16(cfg.step),
|
|
StepName: stepName,
|
|
Action: actionPtr,
|
|
ActionBy: uintPtr(actorID),
|
|
}
|
|
|
|
if err := tx.Create(&record).Error; err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func seedKandangs(tx *gorm.DB, createdBy uint, locations map[string]uint, users map[string]uint, projectFlocks map[string]uint) (map[string]uint, error) {
|
|
seeds := []struct {
|
|
Name string
|
|
Status utils.KandangStatus
|
|
Location string
|
|
PicKey string
|
|
ProjectFlockKey *string
|
|
}{
|
|
{Name: "Singaparna 1", Status: utils.KandangStatusActive, Location: "Singaparna", PicKey: "admin", ProjectFlockKey: strPtr("Singaparna Period 1")},
|
|
{Name: "Singaparna 2", Status: utils.KandangStatusNonActive, Location: "Singaparna", PicKey: "admin"},
|
|
{Name: "Cikaum 1", Status: utils.KandangStatusActive, Location: "Cikaum", PicKey: "admin", ProjectFlockKey: strPtr("Cikaum Period 1")},
|
|
{Name: "Cikaum 2", Status: utils.KandangStatusNonActive, Location: "Cikaum", PicKey: "admin"},
|
|
}
|
|
|
|
result := make(map[string]uint, len(seeds))
|
|
|
|
for _, seed := range seeds {
|
|
locID, ok := locations[seed.Location]
|
|
if !ok {
|
|
return nil, fmt.Errorf("location %s not seeded", seed.Location)
|
|
}
|
|
picID, ok := users[seed.PicKey]
|
|
if !ok {
|
|
return nil, fmt.Errorf("user %s not seeded", seed.PicKey)
|
|
}
|
|
|
|
var projectFlockID *uint
|
|
if seed.ProjectFlockKey != nil {
|
|
pfID, ok := projectFlocks[*seed.ProjectFlockKey]
|
|
if !ok {
|
|
return nil, fmt.Errorf("project flock %s not seeded", *seed.ProjectFlockKey)
|
|
}
|
|
projectFlockID = uintPtr(pfID)
|
|
}
|
|
|
|
var kandang entity.Kandang
|
|
err := tx.Where("name = ?", seed.Name).First(&kandang).Error
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
kandang = entity.Kandang{
|
|
Name: seed.Name,
|
|
Status: string(seed.Status),
|
|
LocationId: locID,
|
|
PicId: picID,
|
|
ProjectFlockId: projectFlockID,
|
|
CreatedBy: createdBy,
|
|
}
|
|
if err := tx.Create(&kandang).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
if err := syncPivotRelation(tx, projectFlockID, kandang.Id); err != nil {
|
|
return nil, err
|
|
}
|
|
} else if err != nil {
|
|
return nil, err
|
|
} else {
|
|
updates := map[string]any{
|
|
"location_id": locID,
|
|
"pic_id": picID,
|
|
"status": string(seed.Status),
|
|
}
|
|
if projectFlockID != nil {
|
|
updates["project_flock_id"] = *projectFlockID
|
|
} else {
|
|
updates["project_flock_id"] = nil
|
|
}
|
|
if err := tx.Model(&entity.Kandang{}).Where("id = ?", kandang.Id).Updates(updates).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
if err := syncPivotRelation(tx, projectFlockID, kandang.Id); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
result[seed.Name] = kandang.Id
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
func syncPivotRelation(tx *gorm.DB, projectFlockID *uint, kandangID uint) error {
|
|
if err := detachActivePivot(tx, kandangID); err != nil {
|
|
return err
|
|
}
|
|
if projectFlockID == nil {
|
|
return nil
|
|
}
|
|
return ensureActivePivot(tx, *projectFlockID, kandangID)
|
|
}
|
|
|
|
func detachActivePivot(tx *gorm.DB, kandangID uint) error {
|
|
return tx.Where("kandang_id = ?", kandangID).
|
|
Delete(&entity.ProjectFlockKandang{}).Error
|
|
}
|
|
|
|
func ensureActivePivot(tx *gorm.DB, projectFlockID, kandangID uint) error {
|
|
var pivot entity.ProjectFlockKandang
|
|
err := tx.Where("project_flock_id = ? AND kandang_id = ?", projectFlockID, kandangID).
|
|
First(&pivot).Error
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return err
|
|
}
|
|
newRecord := entity.ProjectFlockKandang{
|
|
ProjectFlockId: projectFlockID,
|
|
KandangId: kandangID,
|
|
}
|
|
return tx.Create(&newRecord).Error
|
|
}
|
|
|
|
func seedWarehouses(tx *gorm.DB, createdBy uint, areas map[string]uint, locations map[string]uint, kandangs map[string]uint) error {
|
|
seeds := []struct {
|
|
Name string
|
|
Type string
|
|
Area string
|
|
Location *string
|
|
Kandang *string
|
|
}{
|
|
{Name: "Gudang Priangan", Type: string(utils.WarehouseTypeArea), Area: "Priangan"},
|
|
{Name: "Gudang Singaparna", Type: string(utils.WarehouseTypeLokasi), Area: "Priangan", Location: strPtr("Singaparna")},
|
|
{Name: "Gudang Singaparna 1", Type: string(utils.WarehouseTypeKandang), Area: "Priangan", Location: strPtr("Singaparna"), Kandang: strPtr("Singaparna 1")},
|
|
{Name: "Gudang Singaparna 2", Type: string(utils.WarehouseTypeKandang), Area: "Priangan", Location: strPtr("Singaparna"), Kandang: strPtr("Singaparna 2")},
|
|
{Name: "Gudang Banten", Type: string(utils.WarehouseTypeArea), Area: "Banten"},
|
|
{Name: "Gudang Cikaum", Type: string(utils.WarehouseTypeLokasi), Area: "Banten", Location: strPtr("Cikaum")},
|
|
{Name: "Gudang Cikaum 1", Type: string(utils.WarehouseTypeKandang), Area: "Banten", Location: strPtr("Cikaum"), Kandang: strPtr("Cikaum 1")},
|
|
{Name: "Gudang Cikaum 2", Type: string(utils.WarehouseTypeKandang), Area: "Banten", Location: strPtr("Cikaum"), Kandang: strPtr("Cikaum 2")},
|
|
}
|
|
|
|
for _, seed := range seeds {
|
|
areaID, ok := areas[seed.Area]
|
|
if !ok {
|
|
return fmt.Errorf("area %s not seeded", seed.Area)
|
|
}
|
|
|
|
var warehouse entity.Warehouse
|
|
err := tx.Where("name = ?", seed.Name).First(&warehouse).Error
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
warehouse = entity.Warehouse{
|
|
Name: seed.Name,
|
|
Type: seed.Type,
|
|
AreaId: areaID,
|
|
CreatedBy: createdBy,
|
|
}
|
|
} else if err != nil {
|
|
return err
|
|
}
|
|
|
|
if seed.Location != nil {
|
|
locID, ok := locations[*seed.Location]
|
|
if !ok {
|
|
return fmt.Errorf("location %s not seeded", *seed.Location)
|
|
}
|
|
warehouse.LocationId = uintPtr(locID)
|
|
}
|
|
if seed.Kandang != nil {
|
|
kandangID, ok := kandangs[*seed.Kandang]
|
|
if !ok {
|
|
return fmt.Errorf("kandang %s not seeded", *seed.Kandang)
|
|
}
|
|
warehouse.KandangId = uintPtr(kandangID)
|
|
}
|
|
|
|
if warehouse.Id == 0 {
|
|
if err := tx.Create(&warehouse).Error; err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
if err := tx.Model(&entity.Warehouse{}).Where("id = ?", warehouse.Id).Updates(map[string]any{
|
|
"type": warehouse.Type,
|
|
"area_id": warehouse.AreaId,
|
|
"location_id": warehouse.LocationId,
|
|
"kandang_id": warehouse.KandangId,
|
|
}).Error; err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func seedProductCategories(tx *gorm.DB, createdBy uint) (map[string]uint, error) {
|
|
seeds := []struct {
|
|
Name string
|
|
Code string
|
|
}{
|
|
{"Bahan Baku", "RAW"},
|
|
{"Day Old Chick", "DOC"},
|
|
}
|
|
|
|
result := make(map[string]uint, len(seeds))
|
|
|
|
for _, seed := range seeds {
|
|
var category entity.ProductCategory
|
|
err := tx.Where("name = ?", seed.Name).First(&category).Error
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
category = entity.ProductCategory{Name: seed.Name, Code: seed.Code, CreatedBy: createdBy}
|
|
if err := tx.Create(&category).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
} else if err != nil {
|
|
return nil, err
|
|
} else {
|
|
if err := tx.Model(&entity.ProductCategory{}).Where("id = ?", category.Id).Updates(map[string]any{
|
|
"code": seed.Code,
|
|
}).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
result[seed.Name] = category.Id
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
func seedSuppliers(tx *gorm.DB, createdBy uint) (map[string]uint, error) {
|
|
seeds := []struct {
|
|
Name string
|
|
Alias string
|
|
Category string
|
|
Email string
|
|
Phone string
|
|
Address string
|
|
}{
|
|
{"PT CHAROEN POKPHAND INDONESIA Tbk", "CPI", string(utils.SupplierCategorySapronak), "cpi@gmail.com", "081200000001", "Jl. Pakan 1, Bekasi"},
|
|
{"BOP Vendor", "BOP", string(utils.SupplierCategoryBOP), "bop@gmail.com", "081200000002", "Jl. Veteriner 3, Bogor"},
|
|
{"Ekspedisi", "EKS", string(utils.SupplierCategoryBOP), "bop@gmail.com", "081200000002", "Jl. Veteriner 3, Bogor"},
|
|
}
|
|
|
|
result := make(map[string]uint, len(seeds))
|
|
|
|
for idx, seed := range seeds {
|
|
var supplier entity.Supplier
|
|
err := tx.Where("name = ?", seed.Name).First(&supplier).Error
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
supplier = entity.Supplier{
|
|
Name: seed.Name,
|
|
Alias: seed.Alias,
|
|
Pic: "John Doe",
|
|
Type: string(utils.CustomerSupplierTypeBisnis),
|
|
Category: seed.Category,
|
|
Phone: seed.Phone,
|
|
Email: seed.Email,
|
|
Address: seed.Address,
|
|
DueDate: 30,
|
|
CreatedBy: createdBy,
|
|
AccountNumber: strPtr(fmt.Sprintf("%03d", idx+1)),
|
|
}
|
|
if err := tx.Create(&supplier).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
} else if err != nil {
|
|
return nil, err
|
|
}
|
|
result[seed.Name] = supplier.Id
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
func seedCustomers(tx *gorm.DB, createdBy uint, users map[string]uint) error {
|
|
seeds := []struct {
|
|
Name string
|
|
PicKey string
|
|
Address string
|
|
Phone string
|
|
Email string
|
|
}{
|
|
{"Abdul Azis", "admin", "Jl. Raya Utama 1, Bekasi", "082100000001", "abdul.azis@gmail.com"},
|
|
}
|
|
|
|
for idx, seed := range seeds {
|
|
picID, ok := users[seed.PicKey]
|
|
if !ok {
|
|
return fmt.Errorf("user %s not seeded", seed.PicKey)
|
|
}
|
|
|
|
var customer entity.Customer
|
|
err := tx.Where("name = ?", seed.Name).First(&customer).Error
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
customer = entity.Customer{
|
|
Name: seed.Name,
|
|
PicId: picID,
|
|
Type: string(utils.CustomerSupplierTypeBisnis),
|
|
Address: seed.Address,
|
|
Phone: seed.Phone,
|
|
Email: seed.Email,
|
|
AccountNumber: *strPtr(fmt.Sprintf("%03d", idx+1)),
|
|
CreatedBy: createdBy,
|
|
}
|
|
if err := tx.Create(&customer).Error; err != nil {
|
|
return err
|
|
}
|
|
} else if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func seedFcr(tx *gorm.DB, createdBy uint) (map[string]uint, error) {
|
|
seeds := []struct {
|
|
Name string
|
|
Standards []struct {
|
|
Weight float64
|
|
FcrNumber float64
|
|
Mortality float64
|
|
}
|
|
}{
|
|
{
|
|
Name: "FCR Layer",
|
|
Standards: []struct {
|
|
Weight float64
|
|
FcrNumber float64
|
|
Mortality float64
|
|
}{
|
|
{Weight: 0.8, FcrNumber: 1.60, Mortality: 2.0},
|
|
{Weight: 1.5, FcrNumber: 1.75, Mortality: 3.5},
|
|
},
|
|
},
|
|
}
|
|
|
|
result := make(map[string]uint, len(seeds))
|
|
|
|
for _, seed := range seeds {
|
|
var fcr entity.Fcr
|
|
err := tx.Where("name = ?", seed.Name).First(&fcr).Error
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
fcr = entity.Fcr{Name: seed.Name, CreatedBy: createdBy}
|
|
if err := tx.Create(&fcr).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
} else if err != nil {
|
|
return nil, err
|
|
}
|
|
result[seed.Name] = fcr.Id
|
|
|
|
for _, std := range seed.Standards {
|
|
var standard entity.FcrStandard
|
|
err := tx.Where("fcr_id = ? AND weight = ?", fcr.Id, std.Weight).First(&standard).Error
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
standard = entity.FcrStandard{
|
|
FcrID: fcr.Id,
|
|
Weight: std.Weight,
|
|
FcrNumber: std.FcrNumber,
|
|
Mortality: std.Mortality,
|
|
}
|
|
if err := tx.Create(&standard).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
} else if err != nil {
|
|
return nil, err
|
|
} else {
|
|
if err := tx.Model(&entity.FcrStandard{}).Where("id = ?", standard.Id).Updates(map[string]any{
|
|
"fcr_number": std.FcrNumber,
|
|
"mortality": std.Mortality,
|
|
}).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
func seedProducts(tx *gorm.DB, createdBy uint, uoms map[string]uint, categories map[string]uint, suppliers map[string]uint) error {
|
|
seeds := []struct {
|
|
Name string
|
|
Brand string
|
|
Sku string
|
|
Uom string
|
|
Category string
|
|
Price float64
|
|
Selling *float64
|
|
Tax *float64
|
|
Expiry *int
|
|
Suppliers []string
|
|
Flags []utils.FlagType
|
|
}{
|
|
{
|
|
Name: "DOC Broiler",
|
|
Brand: "MBU Broiler",
|
|
Sku: "BRO0001",
|
|
Uom: "Ekor",
|
|
Category: "Day Old Chick",
|
|
Price: 7500,
|
|
Suppliers: []string{"PT CHAROEN POKPHAND INDONESIA Tbk"},
|
|
Flags: []utils.FlagType{utils.FlagDOC},
|
|
},
|
|
{
|
|
Name: "281 SPECIAL STARTER",
|
|
Brand: "281 STARTER",
|
|
Sku: "281",
|
|
Uom: "Kilogram",
|
|
Category: "Bahan Baku",
|
|
Price: 7850,
|
|
Expiry: intPtr(60),
|
|
Suppliers: []string{"PT CHAROEN POKPHAND INDONESIA Tbk"},
|
|
Flags: []utils.FlagType{utils.FlagPakan, utils.FlagStarter},
|
|
},
|
|
}
|
|
|
|
for _, seed := range seeds {
|
|
uomID, ok := uoms[seed.Uom]
|
|
if !ok {
|
|
return fmt.Errorf("uom %s not seeded", seed.Uom)
|
|
}
|
|
categoryID, ok := categories[seed.Category]
|
|
if !ok {
|
|
return fmt.Errorf("product category %s not seeded", seed.Category)
|
|
}
|
|
|
|
var product entity.Product
|
|
err := tx.Where("name = ?", seed.Name).First(&product).Error
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
selling := seed.Selling
|
|
tax := seed.Tax
|
|
product = entity.Product{
|
|
Name: seed.Name,
|
|
Brand: seed.Brand,
|
|
Sku: &seed.Sku,
|
|
UomId: uomID,
|
|
ProductCategoryId: categoryID,
|
|
ProductPrice: seed.Price,
|
|
SellingPrice: selling,
|
|
Tax: tax,
|
|
ExpiryPeriod: seed.Expiry,
|
|
CreatedBy: createdBy,
|
|
}
|
|
if err := tx.Create(&product).Error; err != nil {
|
|
return err
|
|
}
|
|
} else if err != nil {
|
|
return err
|
|
} else {
|
|
updates := map[string]any{
|
|
"brand": seed.Brand,
|
|
"uom_id": uomID,
|
|
"product_category_id": categoryID,
|
|
"product_price": seed.Price,
|
|
"selling_price": seed.Selling,
|
|
"tax": seed.Tax,
|
|
"expiry_period": seed.Expiry,
|
|
}
|
|
if seed.Sku != "" {
|
|
updates["sku"] = seed.Sku
|
|
}
|
|
if err := tx.Model(&entity.Product{}).Where("id = ?", product.Id).Updates(updates).Error; err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
for _, supplierName := range seed.Suppliers {
|
|
supplierID, ok := suppliers[supplierName]
|
|
if !ok {
|
|
return fmt.Errorf("supplier %s not seeded", supplierName)
|
|
}
|
|
var existing entity.ProductSupplier
|
|
err := tx.Where("product_id = ? AND supplier_id = ?", product.Id, supplierID).First(&existing).Error
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
link := entity.ProductSupplier{ProductID: product.Id, SupplierID: supplierID}
|
|
if err := tx.Create(&link).Error; err != nil {
|
|
return err
|
|
}
|
|
} else if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if err := seedFlags(tx, product.Id, entity.FlagableTypeProduct, seed.Flags); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func seedNonstocks(tx *gorm.DB, createdBy uint, uoms map[string]uint, suppliers map[string]uint) error {
|
|
seeds := []struct {
|
|
Name string
|
|
Uom string
|
|
Suppliers []string
|
|
Flags []utils.FlagType
|
|
}{
|
|
{
|
|
Name: "Expedisi DOC",
|
|
Uom: "Ekor",
|
|
Suppliers: []string{"Ekspedisi"},
|
|
Flags: []utils.FlagType{utils.FlagEkspedisi},
|
|
},
|
|
{
|
|
Name: "Solar",
|
|
Uom: "Liter",
|
|
Suppliers: []string{"BOP Vendor"},
|
|
Flags: []utils.FlagType{},
|
|
},
|
|
}
|
|
|
|
for _, seed := range seeds {
|
|
uomID, ok := uoms[seed.Uom]
|
|
if !ok {
|
|
return fmt.Errorf("uom %s not seeded", seed.Uom)
|
|
}
|
|
|
|
var nonstock entity.Nonstock
|
|
err := tx.Where("name = ?", seed.Name).First(&nonstock).Error
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
nonstock = entity.Nonstock{
|
|
Name: seed.Name,
|
|
UomId: uomID,
|
|
CreatedBy: createdBy,
|
|
}
|
|
if err := tx.Create(&nonstock).Error; err != nil {
|
|
return err
|
|
}
|
|
} else if err != nil {
|
|
return err
|
|
} else {
|
|
if err := tx.Model(&entity.Nonstock{}).Where("id = ?", nonstock.Id).Updates(map[string]any{
|
|
"uom_id": uomID,
|
|
}).Error; err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
for _, supplierName := range seed.Suppliers {
|
|
supplierID, ok := suppliers[supplierName]
|
|
if !ok {
|
|
return fmt.Errorf("supplier %s not seeded", supplierName)
|
|
}
|
|
var existing entity.NonstockSupplier
|
|
err := tx.Where("nonstock_id = ? AND supplier_id = ?", nonstock.Id, supplierID).First(&existing).Error
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
link := entity.NonstockSupplier{NonstockID: nonstock.Id, SupplierID: supplierID}
|
|
if err := tx.Create(&link).Error; err != nil {
|
|
return err
|
|
}
|
|
} else if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if err := seedFlags(tx, nonstock.Id, entity.FlagableTypeNonstock, seed.Flags); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// nanti saya isi
|
|
|
|
func seedFlags(tx *gorm.DB, flagableID uint, flagableType string, flags []utils.FlagType) error {
|
|
if len(flags) == 0 {
|
|
return nil
|
|
}
|
|
for _, flag := range flags {
|
|
name := strings.ToUpper(string(flag))
|
|
var existing entity.Flag
|
|
err := tx.Where("name = ? AND flagable_id = ? AND flagable_type = ?", name, flagableID, flagableType).First(&existing).Error
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
record := entity.Flag{
|
|
Name: name,
|
|
FlagableID: flagableID,
|
|
FlagableType: flagableType,
|
|
}
|
|
if err := tx.Create(&record).Error; err != nil {
|
|
return err
|
|
}
|
|
} else if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func seedBanks(tx *gorm.DB, createdBy uint) error {
|
|
seeds := []struct {
|
|
Name string
|
|
Alias string
|
|
Owner *string
|
|
AccountNumber string
|
|
}{
|
|
{
|
|
Name: "Bank Central Asia",
|
|
Alias: "BCA",
|
|
AccountNumber: "1234567890",
|
|
Owner: ptr("PT MBU Group"),
|
|
},
|
|
{
|
|
Name: "Bank Rakyat Indonesia",
|
|
Alias: "BRI",
|
|
AccountNumber: "9876543210",
|
|
Owner: ptr("PT MBU Group"),
|
|
},
|
|
{
|
|
Name: "Bank Mandiri",
|
|
Alias: "MAND",
|
|
AccountNumber: "1122334455",
|
|
Owner: ptr("PT MBU Group"),
|
|
},
|
|
}
|
|
|
|
for _, seed := range seeds {
|
|
var bank entity.Bank
|
|
err := tx.Where("name = ?", seed.Name).First(&bank).Error
|
|
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
bank = entity.Bank{
|
|
Name: seed.Name,
|
|
Alias: seed.Alias,
|
|
Owner: seed.Owner,
|
|
AccountNumber: seed.AccountNumber,
|
|
CreatedBy: createdBy,
|
|
CreatedAt: time.Now(),
|
|
UpdatedAt: time.Now(),
|
|
}
|
|
if err := tx.Create(&bank).Error; err != nil {
|
|
return err
|
|
}
|
|
} else if err != nil {
|
|
return err
|
|
} else {
|
|
// update data jika sudah ada
|
|
if err := tx.Model(&entity.Bank{}).Where("id = ?", bank.Id).Updates(map[string]any{
|
|
"alias": seed.Alias,
|
|
"owner": seed.Owner,
|
|
"account_number": seed.AccountNumber,
|
|
"updated_at": time.Now(),
|
|
}).Error; err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func seedProductWarehouse(tx *gorm.DB, createdBy uint) error {
|
|
|
|
seeds := []struct {
|
|
ProductID uint
|
|
WarehouseID uint
|
|
Quantity float64
|
|
}{
|
|
{ProductID: 1, WarehouseID: 1, Quantity: 100},
|
|
{ProductID: 2, WarehouseID: 2, Quantity: 200},
|
|
{ProductID: 2, WarehouseID: 1, Quantity: 300},
|
|
{ProductID: 1, WarehouseID: 3, Quantity: 5000},
|
|
}
|
|
|
|
for _, seed := range seeds {
|
|
var productWarehouse entity.ProductWarehouse
|
|
err := tx.Where("product_id = ? AND warehouse_id = ?", seed.ProductID, seed.WarehouseID).First(&productWarehouse).Error
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
productWarehouse = entity.ProductWarehouse{
|
|
ProductId: seed.ProductID,
|
|
WarehouseId: seed.WarehouseID,
|
|
Quantity: seed.Quantity,
|
|
CreatedBy: createdBy,
|
|
}
|
|
if err := tx.Create(&productWarehouse).Error; err != nil {
|
|
return err
|
|
}
|
|
} else if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func seedTransferStock(tx *gorm.DB, createdBy uint) error {
|
|
|
|
transfer := entity.StockTransfer{
|
|
FromWarehouseId: 1,
|
|
ToWarehouseId: 2,
|
|
Reason: "Seed transfer stock",
|
|
TransferDate: time.Now(),
|
|
MovementNumber: "SEED-TRF-00001",
|
|
CreatedBy: 1,
|
|
}
|
|
if err := tx.Create(&transfer).Error; err != nil {
|
|
return err
|
|
}
|
|
|
|
details := []entity.StockTransferDetail{
|
|
{
|
|
StockTransferId: transfer.Id,
|
|
ProductId: 1,
|
|
Quantity: 10,
|
|
},
|
|
{
|
|
StockTransferId: transfer.Id,
|
|
ProductId: 2,
|
|
Quantity: 5,
|
|
},
|
|
}
|
|
for i := range details {
|
|
if err := tx.Create(&details[i]).Error; err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
deliveries := []entity.StockTransferDelivery{
|
|
{
|
|
StockTransferId: transfer.Id,
|
|
SupplierId: 1,
|
|
VehiclePlate: "B 1234 XYZ",
|
|
DriverName: "Driver Seed",
|
|
DocumentPath: "seed.pdf",
|
|
ShippingCostItem: 1000,
|
|
ShippingCostTotal: 2000,
|
|
},
|
|
}
|
|
for i := range deliveries {
|
|
if err := tx.Create(&deliveries[i]).Error; err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
detailMap := make(map[uint64]uint64)
|
|
for _, d := range details {
|
|
detailMap[d.ProductId] = d.Id
|
|
}
|
|
|
|
deliveryItems := []entity.StockTransferDeliveryItem{
|
|
{
|
|
StockTransferDeliveryId: deliveries[0].Id,
|
|
StockTransferDetailId: detailMap[1],
|
|
Quantity: 50,
|
|
},
|
|
{
|
|
StockTransferDeliveryId: deliveries[0].Id,
|
|
StockTransferDetailId: detailMap[2],
|
|
Quantity: 30,
|
|
},
|
|
}
|
|
for i := range deliveryItems {
|
|
if err := tx.Create(&deliveryItems[i]).Error; err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// func seedChickin(tx *gorm.DB, createdBy uint) error {
|
|
// seeds := []struct {
|
|
// ProjectFlockKandangId uint
|
|
// ChickInDate string
|
|
// Quantity float64
|
|
// Note string
|
|
// }{
|
|
// {ProjectFlockKandangId: 1, ChickInDate: "2025-10-20", Quantity: 100, Note: "Seeder chickin 1"},
|
|
// {ProjectFlockKandangId: 2, ChickInDate: "2025-10-21", Quantity: 200, Note: "Seeder chickin 2"},
|
|
// }
|
|
|
|
// for _, seed := range seeds {
|
|
// chickinDate, err := time.Parse("2006-01-02", seed.ChickInDate)
|
|
// if err != nil {
|
|
// return err
|
|
// }
|
|
|
|
// // Insert ProjectChickin jika belum ada
|
|
// var chickin entity.ProjectChickin
|
|
// err = tx.Where("project_flock_kandang_id = ? AND chick_in_date = ?", seed.ProjectFlockKandangId, chickinDate).
|
|
// First(&chickin).Error
|
|
// if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
// chickin = entity.ProjectChickin{
|
|
// ProjectFlockKandangId: seed.ProjectFlockKandangId,
|
|
// ChickInDate: chickinDate,
|
|
// Quantity: seed.Quantity,
|
|
// Note: seed.Note,
|
|
// CreatedBy: createdBy,
|
|
// }
|
|
// if err := tx.Create(&chickin).Error; err != nil {
|
|
// return err
|
|
// }
|
|
// } else if err != nil {
|
|
// return err
|
|
// }
|
|
|
|
// var population entity.ProjectFlockPopulation
|
|
// err = tx.Where("project_flock_kandang_id = ?", seed.ProjectFlockKandangId).First(&population).Error
|
|
// if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
// population = entity.ProjectFlockPopulation{
|
|
// ProjectFlockKandangId: seed.ProjectFlockKandangId,
|
|
// InitialQuantity: seed.Quantity,
|
|
// CurrentQuantity: seed.Quantity,
|
|
// ReservedQuantity: 0,
|
|
// CreatedBy: createdBy,
|
|
// }
|
|
// if err := tx.Create(&population).Error; err != nil {
|
|
// return err
|
|
// }
|
|
// } else if err != nil {
|
|
// return err
|
|
// } else {
|
|
// // Update population quantities
|
|
// if err := tx.Model(&entity.ProjectFlockPopulation{}).
|
|
// Where("id = ?", population.Id).
|
|
// Updates(map[string]any{
|
|
// "initial_quantity": population.InitialQuantity + seed.Quantity,
|
|
// "current_quantity": population.CurrentQuantity + seed.Quantity,
|
|
// "reserved_quantity": 0,
|
|
// }).Error; err != nil {
|
|
// return err
|
|
// }
|
|
// }
|
|
|
|
// var pfk entity.ProjectFlockKandang
|
|
// if err := tx.Where("id = ?", seed.ProjectFlockKandangId).First(&pfk).Error; err != nil {
|
|
// if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
// // no pivot found; skip creating details
|
|
// continue
|
|
// }
|
|
// return err
|
|
// }
|
|
|
|
// var warehouse entity.Warehouse
|
|
// if err := tx.Where("kandang_id = ?", pfk.KandangId).First(&warehouse).Error; err != nil {
|
|
// // if warehouse not found, cannot create details
|
|
// if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
// continue
|
|
// }
|
|
// return err
|
|
// }
|
|
|
|
// var productWarehouses []entity.ProductWarehouse
|
|
// err = tx.Table("product_warehouses").
|
|
// Select("product_warehouses.*").
|
|
// Joins("JOIN products ON products.id = product_warehouses.product_id").
|
|
// Joins("JOIN product_categories ON product_categories.id = products.product_category_id").
|
|
// Where("product_categories.code = ? AND product_warehouses.warehouse_id = ?", "DOC", warehouse.Id).
|
|
// Order("product_warehouses.created_at DESC").
|
|
// Find(&productWarehouses).Error
|
|
// if err != nil {
|
|
// return err
|
|
// }
|
|
|
|
// // If no product warehouses found, keep existing chickin.Quantity and skip details
|
|
// if len(productWarehouses) == 0 {
|
|
// continue
|
|
// }
|
|
|
|
// // sum all pw quantities and set chickin.Quantity to that total (mimic CreateOne)
|
|
// totalQty := 0.0
|
|
// for _, pw := range productWarehouses {
|
|
// totalQty += pw.Quantity
|
|
// }
|
|
|
|
// if chickin.Quantity != totalQty {
|
|
// if err := tx.Model(&entity.ProjectChickin{}).Where("id = ?", chickin.Id).Update("quantity", totalQty).Error; err != nil {
|
|
// return err
|
|
// }
|
|
// chickin.Quantity = totalQty
|
|
// }
|
|
|
|
// for _, pw := range productWarehouses {
|
|
// // ensure detail exists or create it with full pw.Quantity
|
|
// var detail entity.ProjectChickinDetail
|
|
// err = tx.Where("project_chickin_id = ? AND product_warehouse_id = ?", chickin.Id, pw.Id).First(&detail).Error
|
|
// if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
// detail = entity.ProjectChickinDetail{
|
|
// ProjectChickinId: chickin.Id,
|
|
// ProductWarehouseId: pw.Id,
|
|
// Quantity: pw.Quantity,
|
|
// CreatedBy: createdBy,
|
|
// }
|
|
// if err := tx.Create(&detail).Error; err != nil {
|
|
// return err
|
|
// }
|
|
// } else if err != nil {
|
|
// return err
|
|
// } else {
|
|
// if detail.Quantity != pw.Quantity {
|
|
// if err := tx.Model(&entity.ProjectChickinDetail{}).Where("id = ?", detail.Id).Update("quantity", pw.Quantity).Error; err != nil {
|
|
// return err
|
|
// }
|
|
// }
|
|
// }
|
|
|
|
// // zero out pw quantity
|
|
// if err := tx.Model(&entity.ProductWarehouse{}).Where("id = ?", pw.Id).Update("quantity", 0).Error; err != nil {
|
|
// return err
|
|
// }
|
|
// }
|
|
// }
|
|
|
|
// return nil
|
|
// }
|
|
|
|
func ptr[T any](v T) *T {
|
|
return &v
|
|
}
|
|
|
|
func strPtr(s string) *string {
|
|
return &s
|
|
}
|
|
|
|
func intPtr(v int) *int {
|
|
return &v
|
|
}
|
|
|
|
func uintPtr(v uint) *uint {
|
|
return &v
|
|
}
|