From c02f72c5e572d1db4bc92bc84a4c7be1bb57e19d Mon Sep 17 00:00:00 2001 From: ragilap Date: Tue, 25 Nov 2025 10:32:15 +0700 Subject: [PATCH] fix: next period,purchase before bop, integration auth module,fix validation-master data --- ...20250925040409_create_master_tables.up.sql | 134 ++-- internal/entities/area.go | 2 +- internal/entities/bank.go | 4 +- internal/entities/customer.go | 4 +- internal/entities/fcr.go | 2 +- internal/entities/flag.go | 2 +- internal/entities/kandang.go | 2 +- internal/entities/location.go | 2 +- internal/entities/nonstock.go | 2 +- internal/entities/product-category.go | 2 +- internal/entities/product.go | 4 +- internal/entities/purchase.go | 6 +- internal/entities/purchase_item.go | 10 +- internal/entities/supplier.go | 8 +- internal/entities/uom.go | 2 +- internal/entities/user.go | 4 +- internal/entities/warehouse.go | 2 +- internal/middleware/auth.go | 8 + internal/modules/approvals/route.go | 4 +- internal/modules/expenses/route.go | 4 +- .../services/adjustment.service.go | 11 +- .../product_warehouse.repository.go | 4 +- .../transfers/services/transfer.service.go | 16 +- .../services/delivery-orders.service.go | 8 +- .../services/sales-orders.service.go | 23 +- .../master/areas/services/area.service.go | 9 +- .../areas/validations/area.validation.go | 4 +- .../banks/validations/bank.validation.go | 12 +- .../customers/services/customer.service.go | 11 +- .../validations/customer.validation.go | 18 +- internal/modules/master/kandangs/route.go | 4 +- .../kandangs/services/kandang.service.go | 12 +- .../validations/kandang.validation.go | 8 +- .../locations/services/location.service.go | 20 +- .../validations/location.validation.go | 4 +- .../validations/nonstock.validation.go | 4 +- .../product-category.validation.go | 4 +- .../master/products/dto/product.dto.go | 32 +- .../validations/product.validation.go | 6 +- internal/modules/master/suppliers/route.go | 4 +- .../suppliers/services/supplier.service.go | 18 +- .../validations/supplier.validation.go | 24 +- .../master/uoms/services/uom.service.go | 17 +- .../master/uoms/validations/uom.validation.go | 4 +- .../repositories/warehouse.repository.go | 13 - .../warehouses/services/warehouse.service.go | 12 +- .../validations/warehouse.validation.go | 8 +- .../chickins/services/chickin.service.go | 14 +- .../project-flock-kandangs/route.go | 4 +- .../repositories/projectflock.repository.go | 18 +- .../production/project_flocks/route.go | 4 +- .../services/projectflock.service.go | 27 +- .../recordings/services/recording.service.go | 24 +- .../production/transfer_layings/route.go | 4 +- .../services/transfer_laying.service.go | 16 +- .../controllers/purchase.controller.go | 61 +- .../modules/purchases/dto/purchase.dto.go | 195 +++--- internal/modules/purchases/module.go | 1 - .../repositories/purchase.repository.go | 144 +---- internal/modules/purchases/route.go | 4 +- .../purchases/services/expense_bridge.go | 18 +- .../purchases/services/purchase.service.go | 608 +++++++++--------- .../validations/purchase.validation.go | 42 +- 63 files changed, 838 insertions(+), 864 deletions(-) diff --git a/internal/database/migrations/20250925040409_create_master_tables.up.sql b/internal/database/migrations/20250925040409_create_master_tables.up.sql index eabc78b5..7a1a6bf1 100644 --- a/internal/database/migrations/20250925040409_create_master_tables.up.sql +++ b/internal/database/migrations/20250925040409_create_master_tables.up.sql @@ -2,42 +2,42 @@ CREATE TABLE users ( id BIGSERIAL PRIMARY KEY, id_user BIGINT NOT NULL, - name VARCHAR NOT NULL, - email VARCHAR NOT NULL, - created_at TIMESTAMPTZ DEFAULT NOW(), - updated_at TIMESTAMPTZ DEFAULT NOW(), + name VARCHAR(50) NOT NULL, + email VARCHAR(50) NOT NULL, + created_at TIMESTAMPTZ DEFAULT NOW (), + updated_at TIMESTAMPTZ DEFAULT NOW (), deleted_at TIMESTAMPTZ ); -CREATE UNIQUE INDEX users_id_user_unique ON users (id_user) WHERE deleted_at IS NULL; +CREATE UNIQUE INDEX users_id_user_unique ON users (id_user) +WHERE + deleted_at IS NULL; -CREATE UNIQUE INDEX users_email_unique ON users (email) WHERE deleted_at IS NULL; +CREATE UNIQUE INDEX users_email_unique ON users (email) +WHERE + deleted_at IS NULL; -- FLAGS CREATE TABLE flags ( id BIGSERIAL PRIMARY KEY, - name VARCHAR NOT NULL, + name VARCHAR(50) NOT NULL, flagable_id BIGINT NOT NULL, flagable_type VARCHAR(50) NOT NULL, - created_at TIMESTAMPTZ DEFAULT NOW(), - updated_at TIMESTAMPTZ DEFAULT NOW() + created_at TIMESTAMPTZ DEFAULT NOW (), + updated_at TIMESTAMPTZ DEFAULT NOW () ); -CREATE UNIQUE INDEX flags_unique_flagable ON flags ( - name, - flagable_id, - flagable_type -); +CREATE UNIQUE INDEX flags_unique_flagable ON flags (name, flagable_id, flagable_type); CREATE INDEX flags_flagable_lookup ON flags (flagable_type, flagable_id); -- PRODUCT CATEGORIES CREATE TABLE product_categories ( id BIGSERIAL PRIMARY KEY, - name VARCHAR NOT NULL, + name VARCHAR(50) NOT NULL, code VARCHAR(10) NOT NULL, - created_at TIMESTAMPTZ DEFAULT NOW(), - updated_at TIMESTAMPTZ DEFAULT NOW(), + created_at TIMESTAMPTZ DEFAULT NOW (), + updated_at TIMESTAMPTZ DEFAULT NOW (), deleted_at TIMESTAMPTZ, created_by BIGINT REFERENCES users (id) ON DELETE SET NULL ON UPDATE CASCADE ); @@ -53,9 +53,9 @@ WHERE -- UOM CREATE TABLE uoms ( id BIGSERIAL PRIMARY KEY, - name VARCHAR NOT NULL, - created_at TIMESTAMPTZ DEFAULT NOW(), - updated_at TIMESTAMPTZ DEFAULT NOW(), + name VARCHAR(50) NOT NULL, + created_at TIMESTAMPTZ DEFAULT NOW (), + updated_at TIMESTAMPTZ DEFAULT NOW (), deleted_at TIMESTAMPTZ, created_by BIGINT REFERENCES users (id) ON DELETE SET NULL ON UPDATE CASCADE ); @@ -67,12 +67,12 @@ WHERE -- BANKS CREATE TABLE banks ( id BIGSERIAL PRIMARY KEY, - name VARCHAR NOT NULL, + name VARCHAR(50) NOT NULL, alias VARCHAR(5) NOT NULL, - owner VARCHAR, + owner VARCHAR(50), account_number VARCHAR(50) NOT NULL, - created_at TIMESTAMPTZ DEFAULT NOW(), - updated_at TIMESTAMPTZ DEFAULT NOW(), + created_at TIMESTAMPTZ DEFAULT NOW (), + updated_at TIMESTAMPTZ DEFAULT NOW (), deleted_at TIMESTAMPTZ, created_by BIGINT REFERENCES users (id) ON DELETE SET NULL ON UPDATE CASCADE ); @@ -84,9 +84,9 @@ WHERE -- AREAS CREATE TABLE areas ( id BIGSERIAL PRIMARY KEY, - name VARCHAR NOT NULL, - created_at TIMESTAMPTZ DEFAULT NOW(), - updated_at TIMESTAMPTZ DEFAULT NOW(), + name VARCHAR(50) NOT NULL, + created_at TIMESTAMPTZ DEFAULT NOW (), + updated_at TIMESTAMPTZ DEFAULT NOW (), deleted_at TIMESTAMPTZ, created_by BIGINT REFERENCES users (id) ON DELETE SET NULL ON UPDATE CASCADE ); @@ -98,11 +98,11 @@ WHERE -- LOCATIONS CREATE TABLE locations ( id BIGSERIAL PRIMARY KEY, - name VARCHAR NOT NULL, + name VARCHAR(50) NOT NULL, address TEXT NOT NULL, area_id BIGINT NOT NULL REFERENCES areas (id) ON DELETE RESTRICT ON UPDATE CASCADE, - created_at TIMESTAMPTZ DEFAULT NOW(), - updated_at TIMESTAMPTZ DEFAULT NOW(), + created_at TIMESTAMPTZ DEFAULT NOW (), + updated_at TIMESTAMPTZ DEFAULT NOW (), deleted_at TIMESTAMPTZ, created_by BIGINT REFERENCES users (id) ON DELETE SET NULL ON UPDATE CASCADE ); @@ -114,11 +114,11 @@ WHERE -- KANDANG CREATE TABLE kandangs ( id BIGSERIAL PRIMARY KEY, - name VARCHAR NOT NULL, + name VARCHAR(50) NOT NULL, location_id BIGINT NOT NULL REFERENCES locations (id) ON DELETE RESTRICT ON UPDATE CASCADE, pic_id BIGINT REFERENCES users (id) ON DELETE SET NULL ON UPDATE CASCADE, - created_at TIMESTAMPTZ DEFAULT NOW(), - updated_at TIMESTAMPTZ DEFAULT NOW(), + created_at TIMESTAMPTZ DEFAULT NOW (), + updated_at TIMESTAMPTZ DEFAULT NOW (), deleted_at TIMESTAMPTZ, created_by BIGINT REFERENCES users (id) ON DELETE SET NULL ON UPDATE CASCADE ); @@ -130,13 +130,13 @@ WHERE -- WAREHOUSES CREATE TABLE warehouses ( id BIGSERIAL PRIMARY KEY, - name VARCHAR NOT NULL, + name VARCHAR(50) NOT NULL, type VARCHAR(50) NOT NULL, area_id BIGINT NOT NULL REFERENCES areas (id) ON DELETE RESTRICT ON UPDATE CASCADE, location_id BIGINT REFERENCES locations (id) ON DELETE SET NULL ON UPDATE CASCADE, kandang_id BIGINT REFERENCES kandangs (id) ON DELETE SET NULL ON UPDATE CASCADE, - created_at TIMESTAMPTZ DEFAULT NOW(), - updated_at TIMESTAMPTZ DEFAULT NOW(), + created_at TIMESTAMPTZ DEFAULT NOW (), + updated_at TIMESTAMPTZ DEFAULT NOW (), deleted_at TIMESTAMPTZ, created_by BIGINT REFERENCES users (id) ON DELETE SET NULL ON UPDATE CASCADE ); @@ -148,16 +148,16 @@ WHERE -- CUSTOMERS CREATE TABLE customers ( id BIGSERIAL PRIMARY KEY, - name VARCHAR NOT NULL, + name VARCHAR(50) NOT NULL, pic_id BIGINT REFERENCES users (id) ON DELETE SET NULL ON UPDATE CASCADE, type VARCHAR(50) NOT NULL, address TEXT NOT NULL, phone VARCHAR(20) NOT NULL, - email VARCHAR NOT NULL, + email VARCHAR(50) NOT NULL, account_number VARCHAR(50) NOT NULL, balance NUMERIC(15, 3) DEFAULT 0, - created_at TIMESTAMPTZ DEFAULT NOW(), - updated_at TIMESTAMPTZ DEFAULT NOW(), + created_at TIMESTAMPTZ DEFAULT NOW (), + updated_at TIMESTAMPTZ DEFAULT NOW (), deleted_at TIMESTAMPTZ, created_by BIGINT REFERENCES users (id) ON DELETE SET NULL ON UPDATE CASCADE ); @@ -169,10 +169,10 @@ WHERE -- NONSTOCK CREATE TABLE nonstocks ( id BIGSERIAL PRIMARY KEY, - name VARCHAR NOT NULL, + name VARCHAR(50) NOT NULL, uom_id BIGINT NOT NULL REFERENCES uoms (id) ON DELETE RESTRICT ON UPDATE CASCADE, - created_at TIMESTAMPTZ DEFAULT NOW(), - updated_at TIMESTAMPTZ DEFAULT NOW(), + created_at TIMESTAMPTZ DEFAULT NOW (), + updated_at TIMESTAMPTZ DEFAULT NOW (), deleted_at TIMESTAMPTZ, created_by BIGINT REFERENCES users (id) ON DELETE SET NULL ON UPDATE CASCADE ); @@ -184,9 +184,9 @@ WHERE -- FCR CREATE TABLE fcrs ( id BIGSERIAL PRIMARY KEY, - name VARCHAR NOT NULL, - created_at TIMESTAMPTZ DEFAULT NOW(), - updated_at TIMESTAMPTZ DEFAULT NOW(), + name VARCHAR(50) NOT NULL, + created_at TIMESTAMPTZ DEFAULT NOW (), + updated_at TIMESTAMPTZ DEFAULT NOW (), deleted_at TIMESTAMPTZ, created_by BIGINT REFERENCES users (id) ON DELETE SET NULL ON UPDATE CASCADE ); @@ -201,29 +201,29 @@ CREATE TABLE fcr_standards ( weight NUMERIC(15, 3) NOT NULL, fcr_number NUMERIC(15, 3) NOT NULL, mortality NUMERIC(15, 3) NOT NULL, - created_at TIMESTAMPTZ DEFAULT NOW(), - updated_at TIMESTAMPTZ DEFAULT NOW(), + created_at TIMESTAMPTZ DEFAULT NOW (), + updated_at TIMESTAMPTZ DEFAULT NOW (), deleted_at TIMESTAMPTZ ); -- SUPPLIERS CREATE TABLE suppliers ( id BIGSERIAL PRIMARY KEY, - name VARCHAR NOT NULL, + name VARCHAR(50) NOT NULL, alias VARCHAR(5) NOT NULL, - pic VARCHAR NOT NULL, + pic VARCHAR(50) NOT NULL, type VARCHAR(50) NOT NULL, category VARCHAR(20) NOT NULL, - hatchery VARCHAR, + hatchery VARCHAR(50), phone VARCHAR(20) NOT NULL, - email VARCHAR NOT NULL, + email VARCHAR(50) NOT NULL, address TEXT NOT NULL, npwp VARCHAR(50), account_number VARCHAR(50), balance NUMERIC(15, 3) DEFAULT 0, due_date INT NOT NULL, - created_at TIMESTAMPTZ DEFAULT NOW(), - updated_at TIMESTAMPTZ DEFAULT NOW(), + created_at TIMESTAMPTZ DEFAULT NOW (), + updated_at TIMESTAMPTZ DEFAULT NOW (), deleted_at TIMESTAMPTZ, created_by BIGINT REFERENCES users (id) ON DELETE SET NULL ON UPDATE CASCADE ); @@ -235,15 +235,15 @@ WHERE CREATE TABLE nonstock_suppliers ( nonstock_id BIGINT NOT NULL REFERENCES nonstocks (id) ON DELETE CASCADE ON UPDATE CASCADE, supplier_id BIGINT NOT NULL REFERENCES suppliers (id) ON DELETE CASCADE ON UPDATE CASCADE, - created_at TIMESTAMPTZ DEFAULT NOW(), + created_at TIMESTAMPTZ DEFAULT NOW (), PRIMARY KEY (nonstock_id, supplier_id) ); -- PRODUCTS CREATE TABLE products ( id BIGSERIAL PRIMARY KEY, - name VARCHAR NOT NULL, - brand VARCHAR NOT NULL, + name VARCHAR(50) NOT NULL, + brand VARCHAR(50) NOT NULL, sku VARCHAR(100), uom_id BIGINT NOT NULL REFERENCES uoms (id) ON DELETE RESTRICT ON UPDATE CASCADE, product_category_id BIGINT NOT NULL REFERENCES product_categories (id) ON DELETE RESTRICT ON UPDATE CASCADE, @@ -251,8 +251,8 @@ CREATE TABLE products ( selling_price NUMERIC(15, 3), tax NUMERIC(15, 3), expiry_period INT, - created_at TIMESTAMPTZ DEFAULT NOW(), - updated_at TIMESTAMPTZ DEFAULT NOW(), + created_at TIMESTAMPTZ DEFAULT NOW (), + updated_at TIMESTAMPTZ DEFAULT NOW (), deleted_at TIMESTAMPTZ, created_by BIGINT REFERENCES users (id) ON DELETE SET NULL ON UPDATE CASCADE ); @@ -268,15 +268,15 @@ WHERE CREATE TABLE product_suppliers ( product_id BIGINT NOT NULL REFERENCES products (id) ON DELETE CASCADE ON UPDATE CASCADE, supplier_id BIGINT NOT NULL REFERENCES suppliers (id) ON DELETE CASCADE ON UPDATE CASCADE, - created_at TIMESTAMPTZ DEFAULT NOW(), + created_at TIMESTAMPTZ DEFAULT NOW (), PRIMARY KEY (product_id, supplier_id) ); -- PROJECTS CREATE TABLE projects ( id BIGSERIAL PRIMARY KEY, - created_at TIMESTAMPTZ DEFAULT NOW(), - updated_at TIMESTAMPTZ DEFAULT NOW(), + created_at TIMESTAMPTZ DEFAULT NOW (), + updated_at TIMESTAMPTZ DEFAULT NOW (), deleted_at TIMESTAMPTZ, created_by BIGINT REFERENCES users (id) ON DELETE SET NULL ON UPDATE CASCADE ); @@ -288,8 +288,8 @@ CREATE TABLE product_warehouses ( warehouse_id BIGINT NOT NULL REFERENCES warehouses (id), quantity INTEGER NOT NULL DEFAULT 0, created_by BIGINT NOT NULL REFERENCES users (id), - created_at TIMESTAMPTZ DEFAULT NOW(), - updated_at TIMESTAMPTZ DEFAULT NOW(), + created_at TIMESTAMPTZ DEFAULT NOW (), + updated_at TIMESTAMPTZ DEFAULT NOW (), deleted_at TIMESTAMPTZ ); @@ -316,8 +316,8 @@ CREATE TABLE stock_logs ( note TEXT, product_warehouse_id BIGINT NOT NULL REFERENCES product_warehouses (id) ON DELETE CASCADE ON UPDATE CASCADE, created_by BIGINT NOT NULL REFERENCES users (id) ON DELETE SET NULL ON UPDATE CASCADE, - created_at TIMESTAMPTZ DEFAULT NOW(), - updated_at TIMESTAMPTZ DEFAULT NOW(), + created_at TIMESTAMPTZ DEFAULT NOW (), + updated_at TIMESTAMPTZ DEFAULT NOW (), deleted_at TIMESTAMPTZ ); @@ -330,4 +330,4 @@ CREATE INDEX stock_logs_created_by_idx ON stock_logs (created_by); CREATE INDEX stock_logs_created_at_idx ON stock_logs (created_at); -CREATE INDEX stock_logs_deleted_at_idx ON stock_logs (deleted_at); +CREATE INDEX stock_logs_deleted_at_idx ON stock_logs (deleted_at); \ No newline at end of file diff --git a/internal/entities/area.go b/internal/entities/area.go index 0af4d1f0..cda0a8ed 100644 --- a/internal/entities/area.go +++ b/internal/entities/area.go @@ -8,7 +8,7 @@ import ( type Area struct { Id uint `gorm:"primaryKey"` - Name string `gorm:"not null;uniqueIndex:areas_name_unique,where:deleted_at IS NULL"` + Name string `gorm:"type:varchar(50);not null;uniqueIndex:areas_name_unique,where:deleted_at IS NULL"` CreatedBy uint `gorm:"not null"` CreatedAt time.Time `gorm:"autoCreateTime"` UpdatedAt time.Time `gorm:"autoUpdateTime"` diff --git a/internal/entities/bank.go b/internal/entities/bank.go index 3c2a93da..0275a517 100644 --- a/internal/entities/bank.go +++ b/internal/entities/bank.go @@ -8,9 +8,9 @@ import ( type Bank struct { Id uint `gorm:"primaryKey"` - Name string `gorm:"not null;uniqueIndex:banks_name_unique,where:deleted_at IS NULL"` + Name string `gorm:"type:varchar(50);not null;uniqueIndex:banks_name_unique,where:deleted_at IS NULL"` Alias string `gorm:"not null;size:5"` - Owner *string `gorm:""` + Owner *string `gorm:"type:varchar(50)"` AccountNumber string `gorm:"not null;size:50"` CreatedBy uint `gorm:"not null"` CreatedAt time.Time `gorm:"autoCreateTime"` diff --git a/internal/entities/customer.go b/internal/entities/customer.go index 98d0c861..f171f0ff 100644 --- a/internal/entities/customer.go +++ b/internal/entities/customer.go @@ -8,12 +8,12 @@ import ( type Customer struct { Id uint `gorm:"primaryKey"` - Name string `gorm:"not null;uniqueIndex:customers_name_unique,where:deleted_at IS NULL"` + Name string `gorm:"type:varchar(50);not null;uniqueIndex:customers_name_unique,where:deleted_at IS NULL"` PicId uint `gorm:"not null"` Type string `gorm:"not null;size:50"` Address string `gorm:"not null"` Phone string `gorm:"not null;size:20"` - Email string `gorm:"not null"` + Email string `gorm:"type:varchar(50);not null"` AccountNumber string `gorm:"not null;size:50"` Balance float64 `gorm:"default:0"` CreatedBy uint `gorm:"not null"` diff --git a/internal/entities/fcr.go b/internal/entities/fcr.go index 4bf96eaf..776c314e 100644 --- a/internal/entities/fcr.go +++ b/internal/entities/fcr.go @@ -8,7 +8,7 @@ import ( type Fcr struct { Id uint `gorm:"primaryKey"` - Name string `gorm:"not null;uniqueIndex:idx_suppliers_name,where:deleted_at IS NULL"` + Name string `gorm:"type:varchar(50);not null;uniqueIndex:idx_suppliers_name,where:deleted_at IS NULL"` CreatedBy uint `gorm:"not null"` CreatedAt time.Time `gorm:"autoCreateTime"` UpdatedAt time.Time `gorm:"autoUpdateTime"` diff --git a/internal/entities/flag.go b/internal/entities/flag.go index aba2c8d5..e86f81ee 100644 --- a/internal/entities/flag.go +++ b/internal/entities/flag.go @@ -9,7 +9,7 @@ const ( type Flag struct { Id uint `gorm:"primaryKey"` - Name string `gorm:"not null;uniqueIndex:flags_unique_flagable"` + Name string `gorm:"type:varchar(50);size:50;not null;uniqueIndex:flags_unique_flagable"` FlagableID uint `gorm:"not null;uniqueIndex:flags_unique_flagable;index:flags_flagable_lookup,priority:2"` FlagableType string `gorm:"size:50;not null;uniqueIndex:flags_unique_flagable;index:flags_flagable_lookup,priority:1"` CreatedAt time.Time `gorm:"autoCreateTime"` diff --git a/internal/entities/kandang.go b/internal/entities/kandang.go index 882184b3..7c083d95 100644 --- a/internal/entities/kandang.go +++ b/internal/entities/kandang.go @@ -8,7 +8,7 @@ import ( type Kandang struct { Id uint `gorm:"primaryKey"` - Name string `gorm:"not null;uniqueIndex:kandangs_name_unique,where:deleted_at IS NULL"` + Name string `gorm:"type:varchar(50);not null;uniqueIndex:kandangs_name_unique,where:deleted_at IS NULL"` Status string `gorm:"type:varchar(50);not null"` LocationId uint `gorm:"not null"` Capacity float64 `gorm:"not null"` diff --git a/internal/entities/location.go b/internal/entities/location.go index 1dba8f82..58b90c6e 100644 --- a/internal/entities/location.go +++ b/internal/entities/location.go @@ -8,7 +8,7 @@ import ( type Location struct { Id uint `gorm:"primaryKey"` - Name string `gorm:"not null;uniqueIndex:locations_name_unique,where:deleted_at IS NULL"` + Name string `gorm:"type:varchar(50);not null;uniqueIndex:locations_name_unique,where:deleted_at IS NULL"` Address string `gorm:"not null"` AreaId uint `gorm:"not null"` CreatedBy uint `gorm:"not null"` diff --git a/internal/entities/nonstock.go b/internal/entities/nonstock.go index 0e78ca8b..ca6f57b7 100644 --- a/internal/entities/nonstock.go +++ b/internal/entities/nonstock.go @@ -8,7 +8,7 @@ import ( type Nonstock struct { Id uint `gorm:"primaryKey"` - Name string `gorm:"not null;uniqueIndex:nonstocks_name_unique,where:deleted_at IS NULL"` + Name string `gorm:"type:varchar(50);not null;uniqueIndex:nonstocks_name_unique,where:deleted_at IS NULL"` UomId uint `gorm:"not null"` CreatedBy uint `gorm:"not null"` CreatedAt time.Time `gorm:"autoCreateTime"` diff --git a/internal/entities/product-category.go b/internal/entities/product-category.go index 45acc299..c59c9c6f 100644 --- a/internal/entities/product-category.go +++ b/internal/entities/product-category.go @@ -8,7 +8,7 @@ import ( type ProductCategory struct { Id uint `gorm:"primaryKey"` - Name string `gorm:"not null;uniqueIndex:product_categories_name_unique,where:deleted_at IS NULL"` + Name string `gorm:"type:varchar(50);not null;uniqueIndex:product_categories_name_unique,where:deleted_at IS NULL"` Code string `gorm:"not null;size:10;uniqueIndex:product_categories_code_unique,where:deleted_at IS NULL"` CreatedBy uint `gorm:"not null"` CreatedAt time.Time `gorm:"autoCreateTime"` diff --git a/internal/entities/product.go b/internal/entities/product.go index 52b04627..8f025fff 100644 --- a/internal/entities/product.go +++ b/internal/entities/product.go @@ -8,8 +8,8 @@ import ( type Product struct { Id uint `gorm:"primaryKey"` - Name string `gorm:"not null;uniqueIndex:products_name_unique,where:deleted_at IS NULL"` - Brand string `gorm:"not null"` + Name string `gorm:"type:varchar(50);not null;uniqueIndex:products_name_unique,where:deleted_at IS NULL"` + Brand string `gorm:"type:varchar(50);not null"` Sku *string `gorm:"size:100;uniqueIndex:products_sku_unique,where:deleted_at IS NULL"` UomId uint `gorm:"not null"` ProductCategoryId uint `gorm:"not null"` diff --git a/internal/entities/purchase.go b/internal/entities/purchase.go index 36b698b2..47ac15c8 100644 --- a/internal/entities/purchase.go +++ b/internal/entities/purchase.go @@ -5,11 +5,11 @@ import ( ) type Purchase struct { - Id uint64 `gorm:"primaryKey;autoIncrement"` + Id uint `gorm:"primaryKey;autoIncrement"` PrNumber string `gorm:"not null"` PoNumber *string PoDate *time.Time - SupplierId uint64 `gorm:"not null"` + SupplierId uint `gorm:"not null"` CreditTerm *int DueDate *time.Time GrandTotal float64 `gorm:"type:numeric(15,3);default:0"` @@ -17,7 +17,7 @@ type Purchase struct { CreatedAt time.Time `gorm:"autoCreateTime"` UpdatedAt time.Time `gorm:"autoUpdateTime"` DeletedAt *time.Time `gorm:"index"` - CreatedBy uint64 `gorm:"not null"` + CreatedBy uint `gorm:"not null"` // Relations Supplier Supplier `gorm:"foreignKey:SupplierId;references:Id"` diff --git a/internal/entities/purchase_item.go b/internal/entities/purchase_item.go index 59f1a030..e5b45bad 100644 --- a/internal/entities/purchase_item.go +++ b/internal/entities/purchase_item.go @@ -5,11 +5,11 @@ import ( ) type PurchaseItem struct { - Id uint64 `gorm:"primaryKey;autoIncrement"` - PurchaseId uint64 `gorm:"not null"` - ProductId uint64 `gorm:"not null"` - WarehouseId uint64 `gorm:"not null"` - ProductWarehouseId *uint64 + Id uint `gorm:"primaryKey;autoIncrement"` + PurchaseId uint `gorm:"not null"` + ProductId uint `gorm:"not null"` + WarehouseId uint `gorm:"not null"` + ProductWarehouseId *uint ReceivedDate *time.Time TravelNumber *string TravelNumberDocs *string diff --git a/internal/entities/supplier.go b/internal/entities/supplier.go index 7d801896..bdbb4dfe 100644 --- a/internal/entities/supplier.go +++ b/internal/entities/supplier.go @@ -8,14 +8,14 @@ import ( type Supplier struct { Id uint `gorm:"primaryKey"` - Name string `gorm:"not null;uniqueIndex:suppliers_name_unique,where:deleted_at IS NULL"` + Name string `gorm:"type:varchar(50);not null;uniqueIndex:suppliers_name_unique,where:deleted_at IS NULL"` Alias string `gorm:"not null;size:5"` - Pic string `gorm:"not null"` + Pic string `gorm:"type:varchar(50);not null"` Type string `gorm:"not null;size:50"` Category string `gorm:"not null;size:20"` - Hatchery *string `gorm:"size:255"` + Hatchery *string `gorm:"type:varchar(50)"` Phone string `gorm:"not null;size:20"` - Email string `gorm:"not null"` + Email string `gorm:"type:varchar(50);not null"` Address string `gorm:"not null"` Npwp *string `gorm:"size:50"` AccountNumber *string `gorm:"size:50"` diff --git a/internal/entities/uom.go b/internal/entities/uom.go index a3335428..8f3e3f91 100644 --- a/internal/entities/uom.go +++ b/internal/entities/uom.go @@ -8,7 +8,7 @@ import ( type Uom struct { Id uint `gorm:"primaryKey"` - Name string `gorm:"not null;uniqueIndex:uoms_name_unique,where:deleted_at IS NULL"` + Name string `gorm:"type:varchar(50);not null;uniqueIndex:uoms_name_unique,where:deleted_at IS NULL"` CreatedBy uint `gorm:"not null"` CreatedAt time.Time `gorm:"autoCreateTime"` UpdatedAt time.Time `gorm:"autoUpdateTime"` diff --git a/internal/entities/user.go b/internal/entities/user.go index dcef91d0..d8f4d9c8 100644 --- a/internal/entities/user.go +++ b/internal/entities/user.go @@ -9,8 +9,8 @@ import ( type User struct { Id uint `gorm:"primaryKey"` IdUser int64 `gorm:"uniqueIndex"` - Email string `gorm:"uniqueIndex"` - Name string `gorm:"not null"` + Email string `gorm:"type:varchar(50);uniqueIndex"` + Name string `gorm:"type:varchar(50);not null"` CreatedAt time.Time `gorm:"autoCreateTime"` UpdatedAt time.Time `gorm:"autoUpdateTime"` DeletedAt gorm.DeletedAt `gorm:"index" json:"-"` diff --git a/internal/entities/warehouse.go b/internal/entities/warehouse.go index 31a0476e..fe2d96aa 100644 --- a/internal/entities/warehouse.go +++ b/internal/entities/warehouse.go @@ -8,7 +8,7 @@ import ( type Warehouse struct { Id uint `gorm:"primaryKey"` - Name string `gorm:"not null"` + Name string `gorm:"type:varchar(50);not null"` Type string `gorm:"not null"` AreaId uint `gorm:"not null"` LocationId *uint diff --git a/internal/middleware/auth.go b/internal/middleware/auth.go index 10f9a3f8..881c3a67 100644 --- a/internal/middleware/auth.go +++ b/internal/middleware/auth.go @@ -105,6 +105,14 @@ func AuthenticatedUser(c *fiber.Ctx) (*entity.User, bool) { return nil, false } +func ActorIDFromContext(c *fiber.Ctx) (uint, error) { + user, ok := AuthenticatedUser(c) + if !ok || user == nil || user.Id == 0 { + return 0, fiber.NewError(fiber.StatusUnauthorized, "Please authenticate") + } + return user.Id, nil +} + // AuthDetails returns the full authentication context (token, claims, user). func AuthDetails(c *fiber.Ctx) (*AuthContext, bool) { value := c.Locals(authContextLocalsKey) diff --git a/internal/modules/approvals/route.go b/internal/modules/approvals/route.go index b7d66abd..5dd39616 100644 --- a/internal/modules/approvals/route.go +++ b/internal/modules/approvals/route.go @@ -3,7 +3,7 @@ package approvals import ( // m "gitlab.com/mbugroup/lti-api.git/internal/middleware" "github.com/gofiber/fiber/v2" - + m "gitlab.com/mbugroup/lti-api.git/internal/middleware" common "gitlab.com/mbugroup/lti-api.git/internal/common/service" controller "gitlab.com/mbugroup/lti-api.git/internal/modules/approvals/controllers" user "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services" @@ -12,8 +12,8 @@ import ( func ApprovalRoutes(v1 fiber.Router, u user.UserService, s common.ApprovalService) { _ = u ctrl := controller.NewApprovalController(s) - route := v1.Group("/approvals") + route.Use(m.Auth(u)) route.Get("/", ctrl.GetAll) } diff --git a/internal/modules/expenses/route.go b/internal/modules/expenses/route.go index 49a4e7c5..b102cfb3 100644 --- a/internal/modules/expenses/route.go +++ b/internal/modules/expenses/route.go @@ -1,7 +1,7 @@ package expenses import ( - // m "gitlab.com/mbugroup/lti-api.git/internal/middleware" + m "gitlab.com/mbugroup/lti-api.git/internal/middleware" controller "gitlab.com/mbugroup/lti-api.git/internal/modules/expenses/controllers" expense "gitlab.com/mbugroup/lti-api.git/internal/modules/expenses/services" user "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services" @@ -13,7 +13,7 @@ func ExpenseRoutes(v1 fiber.Router, u user.UserService, s expense.ExpenseService ctrl := controller.NewExpenseController(s) route := v1.Group("/expenses") - + route.Use(m.Auth(u)) // route.Get("/", m.Auth(u), ctrl.GetAll) // route.Post("/", m.Auth(u), ctrl.CreateOne) // route.Get("/:id", m.Auth(u), ctrl.GetOne) diff --git a/internal/modules/inventory/adjustments/services/adjustment.service.go b/internal/modules/inventory/adjustments/services/adjustment.service.go index e1c4166d..1a7dcfc1 100644 --- a/internal/modules/inventory/adjustments/services/adjustment.service.go +++ b/internal/modules/inventory/adjustments/services/adjustment.service.go @@ -5,8 +5,8 @@ import ( "strings" common "gitlab.com/mbugroup/lti-api.git/internal/common/service" - entity "gitlab.com/mbugroup/lti-api.git/internal/entities" + m "gitlab.com/mbugroup/lti-api.git/internal/middleware" validation "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/adjustments/validations" ProductWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/repositories" productRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/master/products/repositories" @@ -78,7 +78,10 @@ func (s *adjustmentService) Adjustment(c *fiber.Ctx, req *validation.Create) (*e return nil, err } ctx := c.Context() - + actorID, err := m.ActorIDFromContext(c) + if err != nil { + return nil, err + } if err := common.EnsureRelations(c.Context(), common.RelationCheck{Name: "Product", ID: &req.ProductID, Exists: s.ProductRepo.IdExists}, common.RelationCheck{Name: "Warehouse", ID: &req.WarehouseID, Exists: s.WarehouseRepo.IdExists}, @@ -107,7 +110,7 @@ func (s *adjustmentService) Adjustment(c *fiber.Ctx, req *validation.Create) (*e ProductId: uint(req.ProductID), WarehouseId: uint(req.WarehouseID), Quantity: 0, - CreatedBy: 1, // TODO: should Get from auth middleware + CreatedBy: actorID, } if err := s.ProductWarehouseRepo.CreateOne(ctx, newPW, nil); err != nil { @@ -143,7 +146,7 @@ func (s *adjustmentService) Adjustment(c *fiber.Ctx, req *validation.Create) (*e LogId: 0, Note: req.Note, ProductWarehouseId: productWarehouse.Id, - CreatedBy: 1, // TODO: should Get from auth middleware + CreatedBy: actorID, } if err := s.StockLogsRepository.WithTx(tx).CreateOne(ctx, newLog, nil); err != nil { diff --git a/internal/modules/inventory/product-warehouses/repositories/product_warehouse.repository.go b/internal/modules/inventory/product-warehouses/repositories/product_warehouse.repository.go index b5685faa..b285bbc6 100644 --- a/internal/modules/inventory/product-warehouses/repositories/product_warehouse.repository.go +++ b/internal/modules/inventory/product-warehouses/repositories/product_warehouse.repository.go @@ -27,7 +27,7 @@ type ProductWarehouseRepository interface { GetDetailByID(ctx context.Context, id uint) (*entity.ProductWarehouse, error) IdExists(ctx context.Context, id uint) (bool, error) CleanupEmpty(ctx context.Context, affected map[uint]struct{}) error - EnsureProductWarehouse(ctx context.Context, productID, warehouseID uint, createdBy uint64) (uint, error) + EnsureProductWarehouse(ctx context.Context, productID, warehouseID uint, createdBy uint) (uint, error) } type ProductWarehouseRepositoryImpl struct { @@ -199,7 +199,7 @@ func (r *ProductWarehouseRepositoryImpl) EnsureProductWarehouse( ctx context.Context, productID uint, warehouseID uint, - createdBy uint64, + createdBy uint, ) (uint, error) { record, err := r.GetProductWarehouseByProductAndWarehouseID(ctx, productID, warehouseID) if err == nil { diff --git a/internal/modules/inventory/transfers/services/transfer.service.go b/internal/modules/inventory/transfers/services/transfer.service.go index dd6c0068..a21126a6 100644 --- a/internal/modules/inventory/transfers/services/transfer.service.go +++ b/internal/modules/inventory/transfers/services/transfer.service.go @@ -3,15 +3,15 @@ package service import ( "errors" "fmt" - "strings" - entity "gitlab.com/mbugroup/lti-api.git/internal/entities" + m "gitlab.com/mbugroup/lti-api.git/internal/middleware" rProductWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/repositories" rStockTransfer "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/transfers/repositories" validation "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/transfers/validations" rSupplier "gitlab.com/mbugroup/lti-api.git/internal/modules/master/suppliers/repositories" rStockLogs "gitlab.com/mbugroup/lti-api.git/internal/modules/shared/repositories" "gitlab.com/mbugroup/lti-api.git/internal/utils" + "strings" "github.com/go-playground/validator/v10" "github.com/gofiber/fiber/v2" @@ -127,6 +127,10 @@ func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferReques return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Stok produk %d di gudang asal tidak cukup", product.ProductID)) } } + actorID, err := m.ActorIDFromContext(c) + if err != nil { + return nil, err + } // validasi total qty harus lebih besar dari atau sama dengan total qty di delivery compare berdasarkan productid deliveryQtyMap := make(map[uint]float64) @@ -174,7 +178,7 @@ func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferReques Reason: req.TransferReason, TransferDate: transferDate, MovementNumber: movementNumber, - CreatedBy: 1, //todo: get from token + CreatedBy: uint64(actorID), } // Save the transfer entity to the database @@ -277,7 +281,7 @@ func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferReques LogId: uint(entityTransfer.Id), Note: "", ProductWarehouseId: sourcePW.Id, - CreatedBy: 1, + CreatedBy: actorID, } if err := s.StockLogsRepository.WithTx(tx).CreateOne(c.Context(), decreaseLog, nil); err != nil { s.Log.Errorf("Failed to create stock log decrease: %+v", err) @@ -298,7 +302,7 @@ func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferReques ProductId: uint(product.ProductID), WarehouseId: uint(req.DestinationWarehouseID), Quantity: 0, - CreatedBy: 1, // TODO: should Get from auth middleware + CreatedBy: actorID, } if err := s.ProductWarehouseRepo.WithTx(tx).CreateOne(c.Context(), destPW, nil); err != nil { s.Log.Errorf("Failed to create destination product warehouse: %+v", err) @@ -325,7 +329,7 @@ func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferReques LogId: uint(entityTransfer.Id), Note: "", ProductWarehouseId: destPW.Id, - CreatedBy: 1, + CreatedBy: actorID, } if err := s.StockLogsRepository.WithTx(tx).CreateOne(c.Context(), increaseLog, nil); err != nil { s.Log.Errorf("Failed to create stock log increase: %+v", err) diff --git a/internal/modules/marketing/delivery-orderss/services/delivery-orders.service.go b/internal/modules/marketing/delivery-orderss/services/delivery-orders.service.go index 712c6ace..24c08eaa 100644 --- a/internal/modules/marketing/delivery-orderss/services/delivery-orders.service.go +++ b/internal/modules/marketing/delivery-orderss/services/delivery-orders.service.go @@ -8,6 +8,7 @@ import ( commonRepo "gitlab.com/mbugroup/lti-api.git/internal/common/repository" commonSvc "gitlab.com/mbugroup/lti-api.git/internal/common/service" + m "gitlab.com/mbugroup/lti-api.git/internal/middleware" entity "gitlab.com/mbugroup/lti-api.git/internal/entities" marketingDeliveryProductRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/marketing-delivery-products/repositories" productWarehouseRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/repositories" @@ -175,6 +176,11 @@ func (s *deliveryOrdersService) CreateOne(c *fiber.Ctx, req *validation.Create) return nil, err } + actorID, err := m.ActorIDFromContext(c) + if err != nil { + return nil, err + } + approvalSvc := commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(s.MarketingRepo.DB())) latestApproval, err := approvalSvc.LatestByTarget(c.Context(), utils.ApprovalWorkflowMarketing, req.MarketingId, nil) @@ -256,7 +262,7 @@ func (s *deliveryOrdersService) CreateOne(c *fiber.Ctx, req *validation.Create) } - actorID := uint(1) // TODO: ambil dari auth context + approvalAction := entity.ApprovalActionApproved if _, err := approvalSvcTx.CreateApproval( c.Context(), diff --git a/internal/modules/marketing/sales-orders/services/sales-orders.service.go b/internal/modules/marketing/sales-orders/services/sales-orders.service.go index d750c4a4..d867059e 100644 --- a/internal/modules/marketing/sales-orders/services/sales-orders.service.go +++ b/internal/modules/marketing/sales-orders/services/sales-orders.service.go @@ -9,6 +9,7 @@ import ( commonRepo "gitlab.com/mbugroup/lti-api.git/internal/common/repository" commonSvc "gitlab.com/mbugroup/lti-api.git/internal/common/service" entity "gitlab.com/mbugroup/lti-api.git/internal/entities" + m "gitlab.com/mbugroup/lti-api.git/internal/middleware" rInvMarketingDeliveryProduct "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/marketing-delivery-products/repositories" productWarehouseRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/repositories" repository "gitlab.com/mbugroup/lti-api.git/internal/modules/marketing/sales-orders/repositories" @@ -90,6 +91,11 @@ func (s *salesOrdersService) CreateOne(c *fiber.Ctx, req *validation.Create) (*e return nil, err } + actorID, err := m.ActorIDFromContext(c) + if err != nil { + return nil, err + } + if err := commonSvc.EnsureRelations(c.Context(), commonSvc.RelationCheck{Name: "Customer", ID: &req.CustomerId, Exists: s.CustomerRepo.IdExists}, ); err != nil { @@ -129,7 +135,7 @@ func (s *salesOrdersService) CreateOne(c *fiber.Ctx, req *validation.Create) (*e SoDate: soDate, SalesPersonId: req.SalesPersonId, Notes: req.Notes, - CreatedBy: 1, + CreatedBy: actorID, } if err := marketingRepoTx.CreateOne(c.Context(), marketing, nil); err != nil { return fiber.NewError(fiber.StatusInternalServerError, "Failed to create salesOrders") @@ -143,7 +149,6 @@ func (s *salesOrdersService) CreateOne(c *fiber.Ctx, req *validation.Create) (*e } } - actorID := uint(1) // TODO: ambil dari auth context approvalAction := entity.ApprovalActionCreated if _, err := approvalSvcTx.CreateApproval( c.Context(), @@ -180,6 +185,11 @@ func (s salesOrdersService) UpdateOne(c *fiber.Ctx, req *validation.Update, id u return nil, err } + actorID, err := m.ActorIDFromContext(c) + if err != nil { + return nil, err + } + if err := commonSvc.EnsureRelations(c.Context(), commonSvc.RelationCheck{Name: "Marketing", ID: &id, Exists: s.MarketingRepo.IdExists}, commonSvc.RelationCheck{Name: "Customer", ID: &req.CustomerId, Exists: s.CustomerRepo.IdExists}, @@ -321,7 +331,6 @@ func (s salesOrdersService) UpdateOne(c *fiber.Ctx, req *validation.Update, id u } } if latestApproval != nil { - actorID := uint(1) // todo: ambil dari auth context action := entity.ApprovalActionUpdated _, err := approvalSvcTx.CreateApproval( c.Context(), @@ -405,6 +414,11 @@ func (s salesOrdersService) Approval(c *fiber.Ctx, req *validation.Approve) ([]e return nil, err } + actorID, err := m.ActorIDFromContext(c) + if err != nil { + return nil, err + } + approvalSvc := commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(s.MarketingRepo.DB())) var action entity.ApprovalAction @@ -448,7 +462,7 @@ func (s salesOrdersService) Approval(c *fiber.Ctx, req *validation.Approve) ([]e } } - err := s.MarketingRepo.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error { + err = s.MarketingRepo.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error { approvalSvc := commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(dbTransaction)) @@ -479,7 +493,6 @@ func (s salesOrdersService) Approval(c *fiber.Ctx, req *validation.Approve) ([]e nextStep = approvalutils.ApprovalStep(currentStep) } - actorID := uint(1) // todo ambil dari auth context if _, err := approvalSvc.CreateApproval( c.Context(), utils.ApprovalWorkflowMarketing, diff --git a/internal/modules/master/areas/services/area.service.go b/internal/modules/master/areas/services/area.service.go index 1925a592..0a976567 100644 --- a/internal/modules/master/areas/services/area.service.go +++ b/internal/modules/master/areas/services/area.service.go @@ -5,6 +5,7 @@ import ( "fmt" entity "gitlab.com/mbugroup/lti-api.git/internal/entities" + m "gitlab.com/mbugroup/lti-api.git/internal/middleware" repository "gitlab.com/mbugroup/lti-api.git/internal/modules/master/areas/repositories" validation "gitlab.com/mbugroup/lti-api.git/internal/modules/master/areas/validations" "gitlab.com/mbugroup/lti-api.git/internal/utils" @@ -87,10 +88,14 @@ func (s *areaService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entity.A return nil, fiber.NewError(fiber.StatusConflict, fmt.Sprintf("Area with name %s already exists", req.Name)) } - //TODO: created by dummy + actorID, err := m.ActorIDFromContext(c) + if err != nil { + return nil, err + } + createBody := &entity.Area{ Name: req.Name, - CreatedBy: 1, + CreatedBy: actorID, } if err := s.Repository.CreateOne(c.Context(), createBody, nil); err != nil { diff --git a/internal/modules/master/areas/validations/area.validation.go b/internal/modules/master/areas/validations/area.validation.go index 56bbd601..a7004c26 100644 --- a/internal/modules/master/areas/validations/area.validation.go +++ b/internal/modules/master/areas/validations/area.validation.go @@ -1,11 +1,11 @@ package validation type Create struct { - Name string `json:"name" validate:"required_strict,min=3"` + Name string `json:"name" validate:"required_strict,min=3,max=50"` } type Update struct { - Name *string `json:"name,omitempty" validate:"omitempty"` + Name *string `json:"name,omitempty" validate:"omitempty,max=50"` } type Query struct { diff --git a/internal/modules/master/banks/validations/bank.validation.go b/internal/modules/master/banks/validations/bank.validation.go index 9d2bd897..34f1db27 100644 --- a/internal/modules/master/banks/validations/bank.validation.go +++ b/internal/modules/master/banks/validations/bank.validation.go @@ -1,16 +1,16 @@ package validation type Create struct { - Name string `json:"name" validate:"required_strict,min=3"` - Alias string `json:"alias" validate:"required_strict"` - Owner *string `json:"owner,omitempty" validate:"omitempty"` + Name string `json:"name" validate:"required_strict,min=3,max=50"` + Alias string `json:"alias" validate:"required_strict,max=5"` + Owner *string `json:"owner,omitempty" validate:"omitempty,max=50"` AccountNumber string `json:"account_number" validate:"required_strict,max=50"` } type Update struct { - Name *string `json:"name,omitempty" validate:"omitempty"` - Alias *string `json:"alias,omitempty" validate:"omitempty"` - Owner *string `json:"owner,omitempty" validate:"omitempty"` + Name *string `json:"name,omitempty" validate:"omitempty,max=50"` + Alias *string `json:"alias,omitempty" validate:"omitempty,max=5"` + Owner *string `json:"owner,omitempty" validate:"omitempty,max=50"` AccountNumber *string `json:"account_number,omitempty" validate:"omitempty,max=50"` } diff --git a/internal/modules/master/customers/services/customer.service.go b/internal/modules/master/customers/services/customer.service.go index b2cc1e85..12a31441 100644 --- a/internal/modules/master/customers/services/customer.service.go +++ b/internal/modules/master/customers/services/customer.service.go @@ -3,13 +3,13 @@ package service import ( "errors" "fmt" - "strings" - common "gitlab.com/mbugroup/lti-api.git/internal/common/service" entity "gitlab.com/mbugroup/lti-api.git/internal/entities" + m "gitlab.com/mbugroup/lti-api.git/internal/middleware" repository "gitlab.com/mbugroup/lti-api.git/internal/modules/master/customers/repositories" validation "gitlab.com/mbugroup/lti-api.git/internal/modules/master/customers/validations" "gitlab.com/mbugroup/lti-api.git/internal/utils" + "strings" "github.com/go-playground/validator/v10" "github.com/gofiber/fiber/v2" @@ -81,6 +81,10 @@ func (s *customerService) CreateOne(c *fiber.Ctx, req *validation.Create) (*enti if err := s.Validate.Struct(req); err != nil { return nil, err } + actorID, err := m.ActorIDFromContext(c) + if err != nil { + return nil, err + } if exists, err := s.Repository.NameExists(c.Context(), req.Name, nil); err != nil { s.Log.Errorf("Failed to check customer name: %+v", err) @@ -100,7 +104,6 @@ func (s *customerService) CreateOne(c *fiber.Ctx, req *validation.Create) (*enti return nil, err } - //TODO: created by dummy createBody := &entity.Customer{ Name: req.Name, PicId: req.PicId, @@ -109,7 +112,7 @@ func (s *customerService) CreateOne(c *fiber.Ctx, req *validation.Create) (*enti Phone: req.Phone, Email: req.Email, AccountNumber: req.AccountNumber, - CreatedBy: 1, + CreatedBy: actorID, } if err := s.Repository.CreateOne(c.Context(), createBody, nil); err != nil { diff --git a/internal/modules/master/customers/validations/customer.validation.go b/internal/modules/master/customers/validations/customer.validation.go index a7a666ec..457bbf9a 100644 --- a/internal/modules/master/customers/validations/customer.validation.go +++ b/internal/modules/master/customers/validations/customer.validation.go @@ -1,23 +1,23 @@ package validation type Create struct { - Name string `json:"name" validate:"required_strict,min=3"` + Name string `json:"name" validate:"required_strict,min=3,max=50"` PicId uint `json:"pic_id" validate:"required_strict,number,gt=0"` - Type string `json:"type" validate:"required_strict"` + Type string `json:"type" validate:"required_strict,max=50"` Address string `json:"address" validate:"required_strict"` Phone string `json:"phone" validate:"required_strict,max=20"` - Email string `json:"email" validate:"required_strict,email"` - AccountNumber string `json:"account_number" validate:"required_strict"` + Email string `json:"email" validate:"required_strict,email,max=50"` + AccountNumber string `json:"account_number" validate:"required_strict,max=50"` } type Update struct { - Name *string `json:"name,omitempty" validate:"omitempty"` + Name *string `json:"name,omitempty" validate:"omitempty,max=50"` PicId *uint `json:"pic_id,omitempty" validate:"omitempty,number,gt=0"` - Type *string `json:"type,omitempty" validate:"omitempty"` + Type *string `json:"type,omitempty" validate:"omitempty,max=50"` Address *string `json:"address,omitempty" validate:"omitempty"` - Phone *string `json:"phone,omitempty" validate:"omitempty"` - Email *string `json:"email,omitempty" validate:"omitempty"` - AccountNumber *string `json:"account_number,omitempty" validate:"omitempty"` + Phone *string `json:"phone,omitempty" validate:"omitempty,max=20"` + Email *string `json:"email,omitempty" validate:"omitempty,max=50"` + AccountNumber *string `json:"account_number,omitempty" validate:"omitempty,max=50"` } type Query struct { diff --git a/internal/modules/master/kandangs/route.go b/internal/modules/master/kandangs/route.go index 1e384b1f..6a425b64 100644 --- a/internal/modules/master/kandangs/route.go +++ b/internal/modules/master/kandangs/route.go @@ -1,7 +1,7 @@ package kandangs import ( - // m "gitlab.com/mbugroup/lti-api.git/internal/middleware" + m "gitlab.com/mbugroup/lti-api.git/internal/middleware" controller "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs/controllers" kandang "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs/services" user "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services" @@ -13,7 +13,7 @@ func KandangRoutes(v1 fiber.Router, u user.UserService, s kandang.KandangService ctrl := controller.NewKandangController(s) route := v1.Group("/kandangs") - // route.Use(m.Auth(u)) + route.Use(m.Auth(u)) route.Get("/", ctrl.GetAll) route.Post("/", ctrl.CreateOne) diff --git a/internal/modules/master/kandangs/services/kandang.service.go b/internal/modules/master/kandangs/services/kandang.service.go index e65348fc..35fe2c30 100644 --- a/internal/modules/master/kandangs/services/kandang.service.go +++ b/internal/modules/master/kandangs/services/kandang.service.go @@ -3,13 +3,13 @@ package service import ( "errors" "fmt" - "strings" - common "gitlab.com/mbugroup/lti-api.git/internal/common/service" entity "gitlab.com/mbugroup/lti-api.git/internal/entities" + m "gitlab.com/mbugroup/lti-api.git/internal/middleware" repository "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs/repositories" validation "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs/validations" "gitlab.com/mbugroup/lti-api.git/internal/utils" + "strings" "github.com/go-playground/validator/v10" "github.com/gofiber/fiber/v2" @@ -130,14 +130,18 @@ func (s *kandangService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entit } - //TODO: created by dummy + actorID, err := m.ActorIDFromContext(c) + if err != nil { + return nil, err + } + createBody := &entity.Kandang{ Name: req.Name, LocationId: req.LocationId, Capacity: req.Capacity, Status: status, PicId: req.PicId, - CreatedBy: 1, + CreatedBy: actorID, } if err := s.Repository.CreateOne(c.Context(), createBody, nil); err != nil { diff --git a/internal/modules/master/kandangs/validations/kandang.validation.go b/internal/modules/master/kandangs/validations/kandang.validation.go index 6d7c090b..f4adc55e 100644 --- a/internal/modules/master/kandangs/validations/kandang.validation.go +++ b/internal/modules/master/kandangs/validations/kandang.validation.go @@ -1,8 +1,8 @@ package validation type Create struct { - Name string `json:"name" validate:"required_strict,min=3"` - Status string `json:"status,omitempty" validate:"omitempty,min=3"` + Name string `json:"name" validate:"required_strict,min=3,max=50"` + Status string `json:"status,omitempty" validate:"omitempty,min=3,max=50"` Capacity float64 `json:"capacity" validate:"required_strict,gt=0"` LocationId uint `json:"location_id" validate:"required_strict,number,gt=0"` PicId uint `json:"pic_id" validate:"required_strict,number,gt=0"` @@ -10,8 +10,8 @@ type Create struct { } type Update struct { - Name *string `json:"name,omitempty" validate:"omitempty"` - Status *string `json:"status,omitempty" validate:"omitempty,min=3"` + Name *string `json:"name,omitempty" validate:"omitempty,max=50"` + Status *string `json:"status,omitempty" validate:"omitempty,min=3,max=50"` Capacity *float64 `json:"capacity" validate:"omitempty,gt=0"` LocationId *uint `json:"location_id,omitempty" validate:"omitempty,number,gt=0"` PicId *uint `json:"pic_id,omitempty" validate:"omitempty,number,gt=0"` diff --git a/internal/modules/master/locations/services/location.service.go b/internal/modules/master/locations/services/location.service.go index 7b7599ea..19894d10 100644 --- a/internal/modules/master/locations/services/location.service.go +++ b/internal/modules/master/locations/services/location.service.go @@ -4,15 +4,15 @@ import ( "errors" "fmt" - common "gitlab.com/mbugroup/lti-api.git/internal/common/service" - entity "gitlab.com/mbugroup/lti-api.git/internal/entities" - repository "gitlab.com/mbugroup/lti-api.git/internal/modules/master/locations/repositories" - validation "gitlab.com/mbugroup/lti-api.git/internal/modules/master/locations/validations" - "gitlab.com/mbugroup/lti-api.git/internal/utils" - "github.com/go-playground/validator/v10" "github.com/gofiber/fiber/v2" "github.com/sirupsen/logrus" + common "gitlab.com/mbugroup/lti-api.git/internal/common/service" + entity "gitlab.com/mbugroup/lti-api.git/internal/entities" + m "gitlab.com/mbugroup/lti-api.git/internal/middleware" + repository "gitlab.com/mbugroup/lti-api.git/internal/modules/master/locations/repositories" + validation "gitlab.com/mbugroup/lti-api.git/internal/modules/master/locations/validations" + "gitlab.com/mbugroup/lti-api.git/internal/utils" "gorm.io/gorm" ) @@ -97,12 +97,16 @@ func (s *locationService) CreateOne(c *fiber.Ctx, req *validation.Create) (*enti return nil, err } - //TODO: created by dummy + actorID, err := m.ActorIDFromContext(c) + if err != nil { + return nil, err + } + createBody := &entity.Location{ Name: req.Name, Address: req.Address, AreaId: req.AreaId, - CreatedBy: 1, + CreatedBy: actorID, } if err := s.Repository.CreateOne(c.Context(), createBody, nil); err != nil { diff --git a/internal/modules/master/locations/validations/location.validation.go b/internal/modules/master/locations/validations/location.validation.go index 029953c0..61ab4125 100644 --- a/internal/modules/master/locations/validations/location.validation.go +++ b/internal/modules/master/locations/validations/location.validation.go @@ -1,13 +1,13 @@ package validation type Create struct { - Name string `json:"name" validate:"required_strict,min=3"` + Name string `json:"name" validate:"required_strict,min=3,max=50"` Address string `json:"address" validate:"required_strict"` AreaId uint `json:"area_id" validate:"required_strict,number,gt=0"` } type Update struct { - Name *string `json:"name,omitempty" validate:"omitempty"` + Name *string `json:"name,omitempty" validate:"omitempty,max=50"` Address *string `json:"address,omitempty" validate:"omitempty"` AreaId *uint `json:"area_id,omitempty" validate:"omitempty,number,gt=0"` } diff --git a/internal/modules/master/nonstocks/validations/nonstock.validation.go b/internal/modules/master/nonstocks/validations/nonstock.validation.go index 9d93ce3d..c421b7ec 100644 --- a/internal/modules/master/nonstocks/validations/nonstock.validation.go +++ b/internal/modules/master/nonstocks/validations/nonstock.validation.go @@ -1,14 +1,14 @@ package validation type Create struct { - Name string `json:"name" validate:"required_strict,min=3"` + Name string `json:"name" validate:"required_strict,min=3,max=50"` UomID uint `json:"uom_id" validate:"required,gt=0"` SupplierIDs []uint `json:"supplier_ids,omitempty" validate:"omitempty,dive,gt=0"` Flags []string `json:"flags,omitempty" validate:"omitempty,dive,max=50"` } type Update struct { - Name *string `json:"name,omitempty" validate:"omitempty,min=3"` + Name *string `json:"name,omitempty" validate:"omitempty,min=3,max=50"` UomID *uint `json:"uom_id,omitempty" validate:"omitempty,gt=0"` SupplierIDs *[]uint `json:"supplier_ids,omitempty" validate:"omitempty,dive,gt=0"` Flags *[]string `json:"flags,omitempty" validate:"omitempty,dive,max=50"` diff --git a/internal/modules/master/product-categories/validations/product-category.validation.go b/internal/modules/master/product-categories/validations/product-category.validation.go index 7a7d6e40..46cfaedb 100644 --- a/internal/modules/master/product-categories/validations/product-category.validation.go +++ b/internal/modules/master/product-categories/validations/product-category.validation.go @@ -1,12 +1,12 @@ package validation type Create struct { - Name string `json:"name" validate:"required_strict,min=3"` + Name string `json:"name" validate:"required_strict,min=3,max=50"` Code string `json:"code" validate:"required_strict,max=10"` } type Update struct { - Name *string `json:"name,omitempty" validate:"omitempty"` + Name *string `json:"name,omitempty" validate:"omitempty,max=50"` Code *string `json:"code,omitempty" validate:"omitempty,max=10"` } diff --git a/internal/modules/master/products/dto/product.dto.go b/internal/modules/master/products/dto/product.dto.go index 3b2370b2..dfd4c86f 100644 --- a/internal/modules/master/products/dto/product.dto.go +++ b/internal/modules/master/products/dto/product.dto.go @@ -12,12 +12,13 @@ import ( // === DTO Structs === type ProductRelationDTO struct { - Id uint `json:"id"` - Name string `json:"name"` - ProductPrice float64 `gorm:"type:numeric(15,3);not null"` - SellingPrice *float64 `gorm:"type:numeric(15,3)"` - Uom *uomDTO.UomRelationDTO `json:"uom,omitempty"` - Flags *[]string `json:"flags,omitempty"` + Id uint `json:"id"` + Name string `json:"name"` + ProductPrice float64 `gorm:"type:numeric(15,3);not null"` + SellingPrice *float64 `gorm:"type:numeric(15,3)"` + Uom *uomDTO.UomRelationDTO `json:"uom,omitempty"` + Flags *[]string `json:"flags,omitempty"` + ProductCategory *productCategoryDTO.ProductCategoryRelationDTO `json:"product_category,omitempty"` } type ProductListDTO struct { @@ -55,13 +56,20 @@ func ToProductRelationDTO(e entity.Product) ProductRelationDTO { uomRef = &mapped } + var categoryRef *productCategoryDTO.ProductCategoryRelationDTO + if e.ProductCategory.Id != 0 { + mapped := productCategoryDTO.ToProductCategoryRelationDTO(e.ProductCategory) + categoryRef = &mapped + } + return ProductRelationDTO{ - Id: e.Id, - Name: e.Name, - ProductPrice: e.ProductPrice, - SellingPrice: e.SellingPrice, - Flags: &flags, - Uom: uomRef, + Id: e.Id, + Name: e.Name, + ProductPrice: e.ProductPrice, + SellingPrice: e.SellingPrice, + Flags: &flags, + Uom: uomRef, + ProductCategory: categoryRef, } } diff --git a/internal/modules/master/products/validations/product.validation.go b/internal/modules/master/products/validations/product.validation.go index 70e23a74..e732d054 100644 --- a/internal/modules/master/products/validations/product.validation.go +++ b/internal/modules/master/products/validations/product.validation.go @@ -1,9 +1,9 @@ package validation type Create struct { - Name string `json:"name" validate:"required_strict,min=3"` - Brand string `json:"brand" validate:"required_strict,min=2"` - Sku *string `json:"sku,omitempty" validate:"omitempty"` + Name string `json:"name" validate:"required_strict,min=3,max=50"` + Brand string `json:"brand" validate:"required_strict,min=2,max=50"` + Sku *string `json:"sku,omitempty" validate:"omitempty,max=100"` UomID uint `json:"uom_id" validate:"required,gt=0"` ProductCategoryID uint `json:"product_category_id" validate:"required,gt=0"` ProductPrice float64 `json:"product_price" validate:"required"` diff --git a/internal/modules/master/suppliers/route.go b/internal/modules/master/suppliers/route.go index 3a57f645..17271d4a 100644 --- a/internal/modules/master/suppliers/route.go +++ b/internal/modules/master/suppliers/route.go @@ -1,7 +1,7 @@ package suppliers import ( - // m "gitlab.com/mbugroup/lti-api.git/internal/middleware" + m "gitlab.com/mbugroup/lti-api.git/internal/middleware" controller "gitlab.com/mbugroup/lti-api.git/internal/modules/master/suppliers/controllers" supplier "gitlab.com/mbugroup/lti-api.git/internal/modules/master/suppliers/services" user "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services" @@ -13,7 +13,7 @@ func SupplierRoutes(v1 fiber.Router, u user.UserService, s supplier.SupplierServ ctrl := controller.NewSupplierController(s) route := v1.Group("/suppliers") - // route.Use(m.Auth(u)) + route.Use(m.Auth(u)) route.Get("/", ctrl.GetAll) route.Post("/", ctrl.CreateOne) diff --git a/internal/modules/master/suppliers/services/supplier.service.go b/internal/modules/master/suppliers/services/supplier.service.go index 30ff4b9b..75d8fa04 100644 --- a/internal/modules/master/suppliers/services/supplier.service.go +++ b/internal/modules/master/suppliers/services/supplier.service.go @@ -5,14 +5,14 @@ import ( "fmt" "strings" - entity "gitlab.com/mbugroup/lti-api.git/internal/entities" - repository "gitlab.com/mbugroup/lti-api.git/internal/modules/master/suppliers/repositories" - validation "gitlab.com/mbugroup/lti-api.git/internal/modules/master/suppliers/validations" - "gitlab.com/mbugroup/lti-api.git/internal/utils" - "github.com/go-playground/validator/v10" "github.com/gofiber/fiber/v2" "github.com/sirupsen/logrus" + entity "gitlab.com/mbugroup/lti-api.git/internal/entities" + m "gitlab.com/mbugroup/lti-api.git/internal/middleware" + repository "gitlab.com/mbugroup/lti-api.git/internal/modules/master/suppliers/repositories" + validation "gitlab.com/mbugroup/lti-api.git/internal/modules/master/suppliers/validations" + "gitlab.com/mbugroup/lti-api.git/internal/utils" "gorm.io/gorm" ) @@ -124,8 +124,10 @@ func (s *supplierService) CreateOne(c *fiber.Ctx, req *validation.Create) (*enti } alias := strings.TrimSpace(strings.ToUpper(req.Alias)) - - //TODO: created by dummy + actorID, err := m.ActorIDFromContext(c) + if err != nil { + return nil, err + } createBody := &entity.Supplier{ Name: req.Name, Alias: alias, @@ -139,7 +141,7 @@ func (s *supplierService) CreateOne(c *fiber.Ctx, req *validation.Create) (*enti Npwp: req.Npwp, AccountNumber: req.AccountNumber, DueDate: req.DueDate, - CreatedBy: 1, + CreatedBy: actorID, } if err := s.Repository.CreateOne(c.Context(), createBody, nil); err != nil { diff --git a/internal/modules/master/suppliers/validations/supplier.validation.go b/internal/modules/master/suppliers/validations/supplier.validation.go index fa1d135d..ec02cd8e 100644 --- a/internal/modules/master/suppliers/validations/supplier.validation.go +++ b/internal/modules/master/suppliers/validations/supplier.validation.go @@ -1,14 +1,14 @@ package validation type Create struct { - Name string `json:"name" validate:"required_strict,min=3"` + Name string `json:"name" validate:"required_strict,min=3,max=50"` Alias string `json:"alias" validate:"required_strict,max=5"` - Pic string `json:"pic" validate:"required_strict"` - Type string `json:"type" validate:"required_strict"` - Category string `json:"category" validate:"required_strict"` - Hatchery *string `json:"hatchery,omitempty" validate:"omitempty"` + Pic string `json:"pic" validate:"required_strict,max=50"` + Type string `json:"type" validate:"required_strict,max=50"` + Category string `json:"category" validate:"required_strict,max=20"` + Hatchery *string `json:"hatchery,omitempty" validate:"omitempty,max=50"` Phone string `json:"phone" validate:"required_strict,max=20"` - Email string `json:"email" validate:"required_strict,email"` + Email string `json:"email" validate:"required_strict,email,max=50"` Address string `json:"address" validate:"required_strict"` Npwp *string `json:"npwp,omitempty" validate:"omitempty,max=50"` AccountNumber *string `json:"account_number,omitempty" validate:"omitempty,max=50"` @@ -16,14 +16,14 @@ type Create struct { } type Update struct { - Name *string `json:"name,omitempty" validate:"omitempty,min=3"` + Name *string `json:"name,omitempty" validate:"omitempty,min=3,max=50"` Alias *string `json:"alias,omitempty" validate:"omitempty,max=5"` - Pic *string `json:"pic,omitempty" validate:"omitempty"` - Type *string `json:"type,omitempty" validate:"omitempty"` - Category *string `json:"category,omitempty" validate:"omitempty"` - Hatchery *string `json:"hatchery,omitempty" validate:"omitempty"` + Pic *string `json:"pic,omitempty" validate:"omitempty,max=50"` + Type *string `json:"type,omitempty" validate:"omitempty,max=50"` + Category *string `json:"category,omitempty" validate:"omitempty,max=20"` + Hatchery *string `json:"hatchery,omitempty" validate:"omitempty,max=50"` Phone *string `json:"phone,omitempty" validate:"omitempty,max=20"` - Email *string `json:"email,omitempty" validate:"omitempty,email"` + Email *string `json:"email,omitempty" validate:"omitempty,email,max=50"` Address *string `json:"address,omitempty" validate:"omitempty"` Npwp *string `json:"npwp,omitempty" validate:"omitempty,max=50"` AccountNumber *string `json:"account_number,omitempty" validate:"omitempty,max=50"` diff --git a/internal/modules/master/uoms/services/uom.service.go b/internal/modules/master/uoms/services/uom.service.go index b0888751..5396849b 100644 --- a/internal/modules/master/uoms/services/uom.service.go +++ b/internal/modules/master/uoms/services/uom.service.go @@ -4,14 +4,14 @@ import ( "errors" "fmt" - entity "gitlab.com/mbugroup/lti-api.git/internal/entities" - repository "gitlab.com/mbugroup/lti-api.git/internal/modules/master/uoms/repositories" - validation "gitlab.com/mbugroup/lti-api.git/internal/modules/master/uoms/validations" - "gitlab.com/mbugroup/lti-api.git/internal/utils" - "github.com/go-playground/validator/v10" "github.com/gofiber/fiber/v2" "github.com/sirupsen/logrus" + entity "gitlab.com/mbugroup/lti-api.git/internal/entities" + m "gitlab.com/mbugroup/lti-api.git/internal/middleware" + repository "gitlab.com/mbugroup/lti-api.git/internal/modules/master/uoms/repositories" + validation "gitlab.com/mbugroup/lti-api.git/internal/modules/master/uoms/validations" + "gitlab.com/mbugroup/lti-api.git/internal/utils" "gorm.io/gorm" ) @@ -87,10 +87,13 @@ func (s *uomService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entity.Uo return nil, fiber.NewError(fiber.StatusConflict, fmt.Sprintf("Uom with name %s already exists", req.Name)) } - //TODO: created by dummy + actorID, err := m.ActorIDFromContext(c) + if err != nil { + return nil, err + } createBody := &entity.Uom{ Name: req.Name, - CreatedBy: 1, + CreatedBy: actorID, } if err := s.Repository.CreateOne(c.Context(), createBody, nil); err != nil { diff --git a/internal/modules/master/uoms/validations/uom.validation.go b/internal/modules/master/uoms/validations/uom.validation.go index 56bbd601..a7004c26 100644 --- a/internal/modules/master/uoms/validations/uom.validation.go +++ b/internal/modules/master/uoms/validations/uom.validation.go @@ -1,11 +1,11 @@ package validation type Create struct { - Name string `json:"name" validate:"required_strict,min=3"` + Name string `json:"name" validate:"required_strict,min=3,max=50"` } type Update struct { - Name *string `json:"name,omitempty" validate:"omitempty"` + Name *string `json:"name,omitempty" validate:"omitempty,max=50"` } type Query struct { diff --git a/internal/modules/master/warehouses/repositories/warehouse.repository.go b/internal/modules/master/warehouses/repositories/warehouse.repository.go index ff05b3a1..e879e01a 100644 --- a/internal/modules/master/warehouses/repositories/warehouse.repository.go +++ b/internal/modules/master/warehouses/repositories/warehouse.repository.go @@ -17,7 +17,6 @@ type WarehouseRepository interface { IdExists(ctx context.Context, id uint) (bool, error) GetByKandangID(ctx context.Context, kandangId uint) (*entity.Warehouse, error) GetLatestByKandangID(ctx context.Context, kandangId uint) (*entity.Warehouse, error) - GetDetailByID(ctx context.Context, id uint) (*entity.Warehouse, error) } type WarehouseRepositoryImpl struct { @@ -63,18 +62,6 @@ func (r *WarehouseRepositoryImpl) GetByKandangID(ctx context.Context, kandangId return &warehouse, nil } -func (r *WarehouseRepositoryImpl) GetDetailByID(ctx context.Context, id uint) (*entity.Warehouse, error) { - var warehouse entity.Warehouse - err := r.db.WithContext(ctx). - Preload("Area"). - Preload("Location"). - First(&warehouse, id).Error - if err != nil { - return nil, err - } - return &warehouse, nil -} - func (r *WarehouseRepositoryImpl) GetLatestByKandangID(ctx context.Context, kandangId uint) (*entity.Warehouse, error) { var warehouse entity.Warehouse err := r.db.WithContext(ctx). diff --git a/internal/modules/master/warehouses/services/warehouse.service.go b/internal/modules/master/warehouses/services/warehouse.service.go index 6cf45e0a..4c15b94c 100644 --- a/internal/modules/master/warehouses/services/warehouse.service.go +++ b/internal/modules/master/warehouses/services/warehouse.service.go @@ -3,13 +3,13 @@ package service import ( "errors" "fmt" - "strings" - common "gitlab.com/mbugroup/lti-api.git/internal/common/service" entity "gitlab.com/mbugroup/lti-api.git/internal/entities" + m "gitlab.com/mbugroup/lti-api.git/internal/middleware" repository "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/repositories" validation "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/validations" "gitlab.com/mbugroup/lti-api.git/internal/utils" + "strings" "github.com/go-playground/validator/v10" "github.com/gofiber/fiber/v2" @@ -105,13 +105,15 @@ func (s *warehouseService) CreateOne(c *fiber.Ctx, req *validation.Create) (*ent ); err != nil { return nil, err } - - //TODO: created by dummy + actorID, err := m.ActorIDFromContext(c) + if err != nil { + return nil, err + } createBody := &entity.Warehouse{ Name: req.Name, Type: typ, AreaId: req.AreaId, - CreatedBy: 1, + CreatedBy: actorID, } if req.LocationId != nil { createBody.LocationId = req.LocationId diff --git a/internal/modules/master/warehouses/validations/warehouse.validation.go b/internal/modules/master/warehouses/validations/warehouse.validation.go index 809ef0c4..6046defe 100644 --- a/internal/modules/master/warehouses/validations/warehouse.validation.go +++ b/internal/modules/master/warehouses/validations/warehouse.validation.go @@ -1,16 +1,16 @@ package validation type Create struct { - Name string `json:"name" validate:"required_strict,min=3"` - Type string `json:"type" validate:"required_strict"` + Name string `json:"name" validate:"required_strict,min=3,max=50"` + Type string `json:"type" validate:"required_strict,max=50"` AreaId uint `json:"area_id" validate:"required_strict,number,gt=0"` LocationId *uint `json:"location_id,omitempty" validate:"omitempty,number,gt=0"` KandangId *uint `json:"kandang_id,omitempty" validate:"omitempty,number,gt=0"` } type Update struct { - Name *string `json:"name,omitempty" validate:"omitempty"` - Type *string `json:"type,omitempty" validate:"omitempty"` + Name *string `json:"name,omitempty" validate:"omitempty,max=50"` + Type *string `json:"type,omitempty" validate:"omitempty,max=50"` AreaId *uint `json:"area_id,omitempty" validate:"omitempty,number,gt=0"` LocationId *uint `json:"location_id,omitempty" validate:"omitempty,number,gt=0"` KandangId *uint `json:"kandang_id,omitempty" validate:"omitempty,number,gt=0"` diff --git a/internal/modules/production/chickins/services/chickin.service.go b/internal/modules/production/chickins/services/chickin.service.go index a130740a..4d06aef7 100644 --- a/internal/modules/production/chickins/services/chickin.service.go +++ b/internal/modules/production/chickins/services/chickin.service.go @@ -8,6 +8,7 @@ import ( commonRepo "gitlab.com/mbugroup/lti-api.git/internal/common/repository" commonSvc "gitlab.com/mbugroup/lti-api.git/internal/common/service" entity "gitlab.com/mbugroup/lti-api.git/internal/entities" + m "gitlab.com/mbugroup/lti-api.git/internal/middleware" rProductWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/repositories" KandangRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/master/kandangs/repositories" rWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/repositories" @@ -125,7 +126,10 @@ func (s *chickinService) CreateOne(c *fiber.Ctx, req *validation.Create) ([]enti category := strings.ToUpper(strings.TrimSpace(projectFlockKandang.ProjectFlock.Category)) - actorID := uint(1) // todo nanti ambil dari auth context + actorID, err := m.ActorIDFromContext(c) + if err != nil { + return nil, err + } newChikins := make([]*entity.ProjectChickin, 0) for _, chickinReq := range req.ChickinRequests { @@ -356,6 +360,11 @@ func (s chickinService) Approval(c *fiber.Ctx, req *validation.Approve) ([]entit return nil, err } + actorID, err := m.ActorIDFromContext(c) + if err != nil { + return nil, err + } + approvalSvc := commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(s.Repository.DB())) var action entity.ApprovalAction @@ -397,14 +406,13 @@ func (s chickinService) Approval(c *fiber.Ctx, req *validation.Approve) ([]entit step = utils.ProjectFlockKandangStepDisetujui } - err := s.Repository.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error { + err = s.Repository.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error { approvalSvc := commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(dbTransaction)) chickinRepoTx := repository.NewChickinRepository(dbTransaction) productWarehouseTx := s.ProductWarehouseRepo.WithTx(dbTransaction) for _, approvableID := range approvableIDs { - actorID := uint(1) // todo nanti ambil dari auth context if _, err := approvalSvc.CreateApproval( c.Context(), utils.ApprovalWorkflowProjectFlockKandang, diff --git a/internal/modules/production/project-flock-kandangs/route.go b/internal/modules/production/project-flock-kandangs/route.go index 8057e847..7bab770e 100644 --- a/internal/modules/production/project-flock-kandangs/route.go +++ b/internal/modules/production/project-flock-kandangs/route.go @@ -1,7 +1,7 @@ package project_flock_kandangs import ( - // m "gitlab.com/mbugroup/lti-api.git/internal/middleware" + m "gitlab.com/mbugroup/lti-api.git/internal/middleware" controller "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project-flock-kandangs/controllers" projectFlockKandang "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project-flock-kandangs/services" user "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services" @@ -13,7 +13,7 @@ func ProjectFlockKandangRoutes(v1 fiber.Router, u user.UserService, s projectFlo ctrl := controller.NewProjectFlockKandangController(s) route := v1.Group("/project-flock-kandangs") - + route.Use(m.Auth(u)) // route.Get("/", m.Auth(u), ctrl.GetAll) // route.Post("/", m.Auth(u), ctrl.CreateOne) // route.Get("/:id", m.Auth(u), ctrl.GetOne) diff --git a/internal/modules/production/project_flocks/repositories/projectflock.repository.go b/internal/modules/production/project_flocks/repositories/projectflock.repository.go index de4df25d..eede3638 100644 --- a/internal/modules/production/project_flocks/repositories/projectflock.repository.go +++ b/internal/modules/production/project_flocks/repositories/projectflock.repository.go @@ -11,7 +11,6 @@ import ( "gorm.io/gorm" ) - type ProjectflockRepository interface { repository.BaseRepository[entity.ProjectFlock] GetAllWithFilters(ctx context.Context, offset, limit int, params *validation.Query) ([]entity.ProjectFlock, int64, error) @@ -42,24 +41,23 @@ func NewProjectflockRepository(db *gorm.DB) ProjectflockRepository { func (r *ProjectflockRepositoryImpl) GetAllWithFilters(ctx context.Context, offset, limit int, params *validation.Query) ([]entity.ProjectFlock, int64, error) { return r.GetAll(ctx, offset, limit, func(db *gorm.DB) *gorm.DB { - return r.applyQueryFilters(db, params) + return r.applyQueryFilters(r.WithDefaultRelations()(db), params) }) } func (r *ProjectflockRepositoryImpl) WithDefaultRelations() func(*gorm.DB) *gorm.DB { return func(db *gorm.DB) *gorm.DB { return db. - Preload("CreatedUser"). - Preload("Area"). - Preload("Fcr"). - Preload("Location"). - Preload("Kandangs"). - Preload("KandangHistory"). - Preload("KandangHistory.Kandang") + Preload("CreatedUser"). + Preload("Area"). + Preload("Fcr"). + Preload("Location"). + Preload("Kandangs"). + Preload("KandangHistory"). + Preload("KandangHistory.Kandang") } } - func (r *ProjectflockRepositoryImpl) applyQueryFilters(db *gorm.DB, params *validation.Query) *gorm.DB { if params == nil { return db diff --git a/internal/modules/production/project_flocks/route.go b/internal/modules/production/project_flocks/route.go index eb806129..c1e37cd5 100644 --- a/internal/modules/production/project_flocks/route.go +++ b/internal/modules/production/project_flocks/route.go @@ -1,7 +1,7 @@ package project_flocks import ( - // m "gitlab.com/mbugroup/lti-api.git/internal/middleware" + m "gitlab.com/mbugroup/lti-api.git/internal/middleware" controller "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/controllers" projectflock "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/services" user "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services" @@ -13,7 +13,7 @@ func ProjectflockRoutes(v1 fiber.Router, u user.UserService, s projectflock.Proj ctrl := controller.NewProjectflockController(s) route := v1.Group("/project-flocks") - // route.Use(m.Auth(u)) + route.Use(m.Auth(u)) route.Get("/", ctrl.GetAll) route.Post("/", ctrl.CreateOne) diff --git a/internal/modules/production/project_flocks/services/projectflock.service.go b/internal/modules/production/project_flocks/services/projectflock.service.go index 19b07447..df1986a8 100644 --- a/internal/modules/production/project_flocks/services/projectflock.service.go +++ b/internal/modules/production/project_flocks/services/projectflock.service.go @@ -10,8 +10,7 @@ import ( commonRepo "gitlab.com/mbugroup/lti-api.git/internal/common/repository" commonSvc "gitlab.com/mbugroup/lti-api.git/internal/common/service" entity "gitlab.com/mbugroup/lti-api.git/internal/entities" - - // authmiddleware "gitlab.com/mbugroup/lti-api.git/internal/middleware" + m "gitlab.com/mbugroup/lti-api.git/internal/middleware" productWarehouseRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/repositories" flockDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/flocks/dto" flockRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/master/flocks/repositories" @@ -90,13 +89,6 @@ func (s projectflockService) GetAll(c *fiber.Ctx, params *validation.Query) ([]e return nil, 0, nil, err } - if params.Page <= 0 { - params.Page = 1 - } - if params.Limit <= 0 { - params.Limit = 10 - } - offset := (params.Page - 1) * params.Limit projectflocks, total, err := s.Repository.GetAllWithFilters(c.Context(), offset, params.Limit, params) @@ -221,7 +213,7 @@ func (s *projectflockService) CreateOne(c *fiber.Ctx, req *validation.Create) (* return nil, err } - actorID, err := actorIDFromContext(c) + actorID, err := m.ActorIDFromContext(c) if err != nil { return nil, err } @@ -344,7 +336,7 @@ func (s projectflockService) UpdateOne(c *fiber.Ctx, req *validation.Update, id return nil, err } - actorID, err := actorIDFromContext(c) + actorID, err := m.ActorIDFromContext(c) if err != nil { return nil, err } @@ -602,7 +594,7 @@ func (s projectflockService) Approval(c *fiber.Ctx, req *validation.Approve) ([] return nil, err } - actorID, err := actorIDFromContext(c) + actorID, err := m.ActorIDFromContext(c) if err != nil { return nil, err } @@ -847,7 +839,7 @@ func (s projectflockService) GetPeriodSummary(c *fiber.Ctx, locationID uint) ([] summaries := make([]KandangPeriodSummary, 0, len(rows)) for _, row := range rows { - nextPeriod := 0 + nextPeriod := 1 if row.LatestPeriod > 0 { nextPeriod = row.LatestPeriod + 1 } @@ -1046,12 +1038,3 @@ func (s projectflockService) kandangRepoWithTx(tx *gorm.DB) kandangRepository.Ka } return kandangRepository.NewKandangRepository(s.Repository.DB()) } - -func actorIDFromContext(_ *fiber.Ctx) (uint, error) { - // user, ok := authmiddleware.AuthenticatedUser(c) - // if !ok || user == nil || user.Id == 0 { - // return 0, fiber.NewError(fiber.StatusUnauthorized, "Please authenticate") - // } - // return user.Id, nil - return 1, nil -} diff --git a/internal/modules/production/recordings/services/recording.service.go b/internal/modules/production/recordings/services/recording.service.go index b31a90c0..4ed99685 100644 --- a/internal/modules/production/recordings/services/recording.service.go +++ b/internal/modules/production/recordings/services/recording.service.go @@ -4,13 +4,10 @@ import ( "context" "errors" "fmt" - "math" - "strings" - "time" - commonRepo "gitlab.com/mbugroup/lti-api.git/internal/common/repository" commonSvc "gitlab.com/mbugroup/lti-api.git/internal/common/service" entity "gitlab.com/mbugroup/lti-api.git/internal/entities" + m "gitlab.com/mbugroup/lti-api.git/internal/middleware" rProductWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/repositories" rProjectFlock "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/repositories" repository "gitlab.com/mbugroup/lti-api.git/internal/modules/production/recordings/repositories" @@ -18,6 +15,9 @@ import ( "gitlab.com/mbugroup/lti-api.git/internal/utils" approvalutils "gitlab.com/mbugroup/lti-api.git/internal/utils/approvals" recordingutil "gitlab.com/mbugroup/lti-api.git/internal/utils/recording" + "math" + "strings" + "time" "github.com/go-playground/validator/v10" "github.com/gofiber/fiber/v2" @@ -169,7 +169,10 @@ func (s *recordingService) CreateOne(c *fiber.Ctx, req *validation.Create) (*ent if err := s.ensureProductWarehousesExist(c, req.Stocks, req.Depletions, req.Eggs); err != nil { return nil, err } - + actorID, err := m.ActorIDFromContext(c) + if err != nil { + return nil, err + } var createdRecording entity.Recording transactionErr := s.Repository.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error { nextDay, err := s.Repository.GenerateNextDay(tx, req.ProjectFlockKandangId) @@ -193,7 +196,7 @@ func (s *recordingService) CreateOne(c *fiber.Ctx, req *validation.Create) (*ent ProjectFlockKandangId: req.ProjectFlockKandangId, RecordDatetime: recordTime, Day: &day, - CreatedBy: 1, // TODO: replace with authenticated user + CreatedBy: actorID, } if err := s.Repository.CreateOne(ctx, &createdRecording, func(*gorm.DB) *gorm.DB { return tx }); err != nil { @@ -422,7 +425,7 @@ func (s recordingService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uin action := entity.ApprovalActionUpdated actorID := recordingEntity.CreatedBy if actorID == 0 { - actorID = 1 + return fiber.NewError(fiber.StatusBadRequest, "Actor Id tidak valid untuk approval") } var step approvalutils.ApprovalStep @@ -613,7 +616,10 @@ func (s recordingService) Approval(c *fiber.Ctx, req *validation.Approve) ([]ent } ctx := c.Context() - actorID := uint(1) // TODO: replace with authenticated user once auth is integrated + actorID, err := m.ActorIDFromContext(c) + if err != nil { + return nil, err + } transactionErr := s.Repository.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error { repoTx := s.Repository.WithTx(tx) @@ -951,7 +957,7 @@ func (s *recordingService) createRecordingApproval( return fiber.NewError(fiber.StatusBadRequest, "Recording tidak valid untuk approval") } if actorID == 0 { - actorID = 1 + return fiber.NewError(fiber.StatusBadRequest, "Actor Id tidak valid untuk approval") } var svc commonSvc.ApprovalService diff --git a/internal/modules/production/transfer_layings/route.go b/internal/modules/production/transfer_layings/route.go index ad0cb9e1..868454c5 100644 --- a/internal/modules/production/transfer_layings/route.go +++ b/internal/modules/production/transfer_layings/route.go @@ -1,7 +1,7 @@ package transfer_layings import ( - // m "gitlab.com/mbugroup/lti-api.git/internal/middleware" + m "gitlab.com/mbugroup/lti-api.git/internal/middleware" controller "gitlab.com/mbugroup/lti-api.git/internal/modules/production/transfer_layings/controllers" transferLaying "gitlab.com/mbugroup/lti-api.git/internal/modules/production/transfer_layings/services" user "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services" @@ -13,7 +13,7 @@ func TransferLayingRoutes(v1 fiber.Router, u user.UserService, s transferLaying. ctrl := controller.NewTransferLayingController(s) route := v1.Group("/transfer_layings") - + route.Use(m.Auth(u)) // route.Get("/", m.Auth(u), ctrl.GetAll) // route.Post("/", m.Auth(u), ctrl.CreateOne) // route.Get("/:id", m.Auth(u), ctrl.GetOne) diff --git a/internal/modules/production/transfer_layings/services/transfer_laying.service.go b/internal/modules/production/transfer_layings/services/transfer_laying.service.go index 2aa7129c..bb6d44b1 100644 --- a/internal/modules/production/transfer_layings/services/transfer_laying.service.go +++ b/internal/modules/production/transfer_layings/services/transfer_laying.service.go @@ -10,6 +10,7 @@ import ( commonRepo "gitlab.com/mbugroup/lti-api.git/internal/common/repository" commonSvc "gitlab.com/mbugroup/lti-api.git/internal/common/service" entity "gitlab.com/mbugroup/lti-api.git/internal/entities" + m "gitlab.com/mbugroup/lti-api.git/internal/middleware" rInventory "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/repositories" rWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/repositories" ProjectFlockRepository "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/repositories" @@ -154,6 +155,11 @@ func (s *transferLayingService) CreateOne(c *fiber.Ctx, req *validation.Create) return nil, err } + actorID, err := m.ActorIDFromContext(c) + if err != nil { + return nil, err + } + if _, err := s.ProjectFlockRepo.GetByID(c.Context(), req.SourceProjectFlockId, nil); err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, fiber.NewError(fiber.StatusNotFound, "Source Project Flock not found") @@ -259,7 +265,7 @@ func (s *transferLayingService) CreateOne(c *fiber.Ctx, req *validation.Create) ToProjectFlockId: req.TargetProjectFlockId, TransferDate: transferDate, PendingUsageQty: &totalSourceQty, - CreatedBy: 1, //todo : harus diambil dari auth + CreatedBy: actorID, } err = s.Repository.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error { @@ -592,7 +598,11 @@ func (s transferLayingService) Approval(c *fiber.Ctx, req *validation.Approve) ( return nil, err } - actorID := uint(1) // TODO: change from auth context + actorID, err := m.ActorIDFromContext(c) + if err != nil { + return nil, err + } + var action entity.ApprovalAction switch strings.ToUpper(strings.TrimSpace(req.Action)) { case string(entity.ApprovalActionRejected): @@ -613,7 +623,7 @@ func (s transferLayingService) Approval(c *fiber.Ctx, req *validation.Approve) ( step = utils.TransferToLayingStepDisetujui } - err := s.Repository.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error { + err = s.Repository.DB().WithContext(c.Context()).Transaction(func(dbTransaction *gorm.DB) error { approvalSvcTx := commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(dbTransaction)) sourceRepoTx := repository.NewLayingTransferSourceRepository(dbTransaction) diff --git a/internal/modules/purchases/controllers/purchase.controller.go b/internal/modules/purchases/controllers/purchase.controller.go index d10f42af..b4cf5660 100644 --- a/internal/modules/purchases/controllers/purchase.controller.go +++ b/internal/modules/purchases/controllers/purchase.controller.go @@ -23,21 +23,19 @@ func NewPurchaseController(s service.PurchaseService) *PurchaseController { } func (ctrl *PurchaseController) GetAll(c *fiber.Ctx) error { - query := &validation.PurchaseQuery{ + query := &validation.Query{ Page: c.QueryInt("page", 1), Limit: c.QueryInt("limit", 10), - Search: strings.TrimSpace(c.Query("search")), - PrNumber: strings.TrimSpace(c.Query("pr_number")), CreatedFrom: strings.TrimSpace(c.Query("created_from")), CreatedTo: strings.TrimSpace(c.Query("created_to")), + SupplierID: uint(c.QueryInt("supplier_id", 0)), + AreaID: uint(c.QueryInt("area_id", 0)), + LocationID: uint(c.QueryInt("location_id", 0)), + ProductCategoryID: uint(c.QueryInt("product_category_id", 0)), } - if supplierID := c.QueryInt("supplier_id", 0); supplierID > 0 { - query.SupplierID = uint(supplierID) - } - - if status := strings.TrimSpace(c.Query("status")); status != "" { - query.Status = strings.ToUpper(status) + if query.Page < 1 || query.Limit < 1 { + return fiber.NewError(fiber.StatusBadRequest, "page and limit must be greater than 0") } results, total, err := ctrl.service.GetAll(c, query) @@ -45,24 +43,15 @@ func (ctrl *PurchaseController) GetAll(c *fiber.Ctx) error { return err } - limit := query.Limit - if limit <= 0 { - limit = 10 - } - page := query.Page - if page <= 0 { - page = 1 - } - return c.Status(fiber.StatusOK). - JSON(response.SuccessWithPaginate[dto.PurchaseListItemDTO]{ + JSON(response.SuccessWithPaginate[dto.PurchaseListDTO]{ Code: fiber.StatusOK, Status: "success", Message: "Purchase fetched successfully", Meta: response.Meta{ - Page: page, - Limit: limit, - TotalPages: int64(math.Ceil(float64(total) / float64(limit))), + Page: query.Page, + Limit: query.Limit, + TotalPages: int64(math.Ceil(float64(total) / float64(query.Limit))), TotalResults: total, }, Data: dto.ToPurchaseListDTOs(results), @@ -71,12 +60,13 @@ func (ctrl *PurchaseController) GetAll(c *fiber.Ctx) error { func (ctrl *PurchaseController) GetOne(c *fiber.Ctx) error { param := c.Params("id") - id, err := strconv.ParseUint(param, 10, 64) + + id, err := strconv.Atoi(param) if err != nil || id == 0 { return fiber.NewError(fiber.StatusBadRequest, "Invalid purchase id") } - result, err := ctrl.service.GetOne(c, id) + result, err := ctrl.service.GetOne(c, uint(id)) if err != nil { return err } @@ -96,7 +86,7 @@ func (ctrl *PurchaseController) CreateOne(c *fiber.Ctx) error { if err := c.BodyParser(req); err != nil { return fiber.NewError(fiber.StatusBadRequest, "Invalid request body") } - + result, err := ctrl.service.CreateOne(c, req) if err != nil { return err @@ -113,7 +103,7 @@ func (ctrl *PurchaseController) CreateOne(c *fiber.Ctx) error { func (ctrl *PurchaseController) ApproveStaffPurchase(c *fiber.Ctx) error { param := c.Params("id") - id, err := strconv.ParseUint(param, 10, 64) + id, err := strconv.Atoi(param) if err != nil || id == 0 { return fiber.NewError(fiber.StatusBadRequest, "Invalid purchase id") } @@ -123,7 +113,7 @@ func (ctrl *PurchaseController) ApproveStaffPurchase(c *fiber.Ctx) error { return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Invalid request body: %v", err)) } - result, err := ctrl.service.ApproveStaffPurchase(c, id, req) + result, err := ctrl.service.ApproveStaffPurchase(c, uint(id), req) if err != nil { return err } @@ -137,10 +127,9 @@ func (ctrl *PurchaseController) ApproveStaffPurchase(c *fiber.Ctx) error { }) } - func (ctrl *PurchaseController) ApproveManagerPurchase(c *fiber.Ctx) error { param := c.Params("id") - id, err := strconv.ParseUint(param, 10, 64) + id, err := strconv.Atoi(param) if err != nil || id == 0 { return fiber.NewError(fiber.StatusBadRequest, "Invalid purchase id") } @@ -150,7 +139,7 @@ func (ctrl *PurchaseController) ApproveManagerPurchase(c *fiber.Ctx) error { return fiber.NewError(fiber.StatusBadRequest, "Invalid request body") } - result, err := ctrl.service.ApproveManagerPurchase(c, id, req) + result, err := ctrl.service.ApproveManagerPurchase(c, uint(id), req) if err != nil { return err } @@ -166,7 +155,7 @@ func (ctrl *PurchaseController) ApproveManagerPurchase(c *fiber.Ctx) error { func (ctrl *PurchaseController) ReceiveProducts(c *fiber.Ctx) error { param := c.Params("id") - id, err := strconv.ParseUint(param, 10, 64) + id, err := strconv.Atoi(param) if err != nil || id == 0 { return fiber.NewError(fiber.StatusBadRequest, "Invalid purchase id") } @@ -176,7 +165,7 @@ func (ctrl *PurchaseController) ReceiveProducts(c *fiber.Ctx) error { return fiber.NewError(fiber.StatusBadRequest, "Invalid request body") } - result, err := ctrl.service.ReceiveProducts(c, id, req) + result, err := ctrl.service.ReceiveProducts(c, uint(id), req) if err != nil { return err } @@ -192,7 +181,7 @@ func (ctrl *PurchaseController) ReceiveProducts(c *fiber.Ctx) error { func (ctrl *PurchaseController) DeleteItems(c *fiber.Ctx) error { param := c.Params("id") - id, err := strconv.ParseUint(param, 10, 64) + id, err := strconv.Atoi(param) if err != nil || id == 0 { return fiber.NewError(fiber.StatusBadRequest, "Invalid purchase id") } @@ -202,7 +191,7 @@ func (ctrl *PurchaseController) DeleteItems(c *fiber.Ctx) error { return fiber.NewError(fiber.StatusBadRequest, "Invalid request body") } - result, err := ctrl.service.DeleteItems(c, id, req) + result, err := ctrl.service.DeleteItems(c, uint(id), req) if err != nil { return err } @@ -218,12 +207,12 @@ func (ctrl *PurchaseController) DeleteItems(c *fiber.Ctx) error { func (ctrl *PurchaseController) DeletePurchase(c *fiber.Ctx) error { param := c.Params("id") - id, err := strconv.ParseUint(param, 10, 64) + id, err := strconv.Atoi(param) if err != nil || id == 0 { return fiber.NewError(fiber.StatusBadRequest, "Invalid purchase id") } - if err := ctrl.service.DeletePurchase(c, id); err != nil { + if err := ctrl.service.DeletePurchase(c, uint(id)); err != nil { return err } diff --git a/internal/modules/purchases/dto/purchase.dto.go b/internal/modules/purchases/dto/purchase.dto.go index bbd59fdd..4a29d860 100644 --- a/internal/modules/purchases/dto/purchase.dto.go +++ b/internal/modules/purchases/dto/purchase.dto.go @@ -10,46 +10,51 @@ import ( productDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/products/dto" supplierDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/suppliers/dto" warehouseDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/master/warehouses/dto" + userDTO "gitlab.com/mbugroup/lti-api.git/internal/modules/users/dto" ) -type PurchaseListItemDTO struct { - Id uint64 `json:"id"` - PrNumber string `json:"pr_number"` - PoNumber *string `json:"po_number"` - Supplier *supplierDTO.SupplierRelationDTO `json:"supplier"` - CreditTerm *int `json:"credit_term"` - DueDate *time.Time `json:"due_date"` - PoDate *time.Time `json:"po_date"` - GrandTotal float64 `json:"grand_total"` - Notes *string `json:"notes"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` - Approval *approvalDTO.ApprovalRelationDTO `json:"approval"` +type PurchaseRelationDTO struct { + Id uint `json:"id"` + PrNumber string `json:"pr_number"` + PoNumber *string `json:"po_number"` + PoDate *time.Time `json:"po_date"` + Notes *string `json:"notes"` +} + + +type PurchaseListDTO struct { + PurchaseRelationDTO + Supplier *supplierDTO.SupplierRelationDTO `json:"supplier"` + CreditTerm *int `json:"credit_term"` + DueDate *time.Time `json:"due_date"` + GrandTotal float64 `json:"grand_total"` + CreatedUser *userDTO.UserRelationDTO `json:"created_user"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + LatestApproval *approvalDTO.ApprovalRelationDTO `json:"latest_approval"` } type PurchaseDetailDTO struct { - Id uint64 `json:"id"` - PrNumber string `json:"pr_number"` - PoNumber *string `json:"po_number"` - Supplier *supplierDTO.SupplierRelationDTO `json:"supplier"` - CreditTerm *int `json:"credit_term"` - DueDate *time.Time `json:"due_date"` - PoDate *time.Time `json:"po_date"` - GrandTotal float64 `json:"grand_total"` - Notes *string `json:"notes"` - Items []PurchaseItemDTO `json:"items"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` - Approval *approvalDTO.ApprovalRelationDTO `json:"approval"` + PurchaseRelationDTO + Supplier *supplierDTO.SupplierRelationDTO `json:"supplier"` + CreditTerm *int `json:"credit_term"` + DueDate *time.Time `json:"due_date"` + GrandTotal float64 `json:"grand_total"` + Items []PurchaseItemDTO `json:"items"` + CreatedUser *userDTO.UserRelationDTO `json:"created_user"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + LatestApproval *approvalDTO.ApprovalRelationDTO `json:"latest_approval"` } + type PurchaseItemDTO struct { - Id uint64 `json:"id"` - ProductID uint64 `json:"product_id"` + Id uint `json:"id"` + ProductID uint `json:"product_id"` Product *productDTO.ProductRelationDTO `json:"product"` - WarehouseID uint64 `json:"warehouse_id"` + WarehouseID uint `json:"warehouse_id"` Warehouse *warehouseDTO.WarehouseRelationDTO `json:"warehouse"` - ProductWarehouseID *uint64 `json:"product_warehouse_id"` + ProductWarehouseID *uint `json:"product_warehouse_id"` SubQty float64 `json:"sub_qty"` TotalQty float64 `json:"total_qty"` TotalUsed float64 `json:"total_used"` @@ -61,6 +66,17 @@ type PurchaseItemDTO struct { VehicleNumber *string `json:"vehicle_number"` } + +func ToPurchaseRelationDTO(p *entity.Purchase) PurchaseRelationDTO { + return PurchaseRelationDTO{ + Id: p.Id, + PrNumber: p.PrNumber, + PoNumber: p.PoNumber, + PoDate: p.PoDate, + Notes: p.Notes, + } +} + func ToPurchaseItemDTO(item entity.PurchaseItem) PurchaseItemDTO { dto := PurchaseItemDTO{ Id: item.Id, @@ -77,10 +93,12 @@ func ToPurchaseItemDTO(item entity.PurchaseItem) PurchaseItemDTO { TravelDocumentPath: item.TravelNumberDocs, VehicleNumber: item.VehicleNumber, } + if item.Product != nil && item.Product.Id != 0 { summary := productDTO.ToProductRelationDTO(*item.Product) dto.Product = &summary } + if item.Warehouse != nil && item.Warehouse.Id != 0 { summary := warehouseDTO.ToWarehouseRelationDTO(*item.Warehouse) if item.Warehouse.Area.Id != 0 { @@ -93,6 +111,7 @@ func ToPurchaseItemDTO(item entity.PurchaseItem) PurchaseItemDTO { } dto.Warehouse = &summary } + return dto } @@ -104,70 +123,78 @@ func ToPurchaseItemDTOs(items []entity.PurchaseItem) []PurchaseItemDTO { return result } -func ToPurchaseDetailDTO(p entity.Purchase) PurchaseDetailDTO { - dto := PurchaseDetailDTO{ - Id: p.Id, - PrNumber: p.PrNumber, - PoNumber: p.PoNumber, - Supplier: mapSupplier(p.Supplier), - CreditTerm: p.CreditTerm, - DueDate: p.DueDate, - PoDate: p.PoDate, - GrandTotal: p.GrandTotal, - Notes: p.Notes, - Items: ToPurchaseItemDTOs(p.Items), - CreatedAt: p.CreatedAt, - UpdatedAt: p.UpdatedAt, +func ToPurchaseListDTO(p entity.Purchase) PurchaseListDTO { + var supplier *supplierDTO.SupplierRelationDTO + if p.Supplier.Id != 0 { + mapped := supplierDTO.ToSupplierRelationDTO(p.Supplier) + supplier = &mapped } - if approval := toPurchaseApprovalDTO(p); approval != nil { - dto.Approval = approval + + var createdUser *userDTO.UserRelationDTO + if p.CreatedUser.Id != 0 { + mapped := userDTO.ToUserRelationDTO(p.CreatedUser) + createdUser = &mapped + } + + var latestApproval *approvalDTO.ApprovalRelationDTO + if p.LatestApproval != nil && p.LatestApproval.Id != 0 { + mapped := approvalDTO.ToApprovalDTO(*p.LatestApproval) + latestApproval = &mapped + } + + return PurchaseListDTO{ + PurchaseRelationDTO: ToPurchaseRelationDTO(&p), + Supplier: supplier, + CreditTerm: p.CreditTerm, + DueDate: p.DueDate, + GrandTotal: p.GrandTotal, + CreatedUser: createdUser, + CreatedAt: p.CreatedAt, + UpdatedAt: p.UpdatedAt, + LatestApproval: latestApproval, } - return dto } -func ToPurchaseListDTO(p entity.Purchase) PurchaseListItemDTO { - dto := PurchaseListItemDTO{ - Id: p.Id, - PrNumber: p.PrNumber, - PoNumber: p.PoNumber, - Supplier: mapSupplier(p.Supplier), - CreditTerm: p.CreditTerm, - DueDate: p.DueDate, - PoDate: p.PoDate, - GrandTotal: p.GrandTotal, - Notes: p.Notes, - CreatedAt: p.CreatedAt, - UpdatedAt: p.UpdatedAt, - } - if approval := toPurchaseApprovalDTO(p); approval != nil { - dto.Approval = approval - } - return dto -} - -func mapSupplier(s entity.Supplier) *supplierDTO.SupplierRelationDTO { - if s.Id == 0 { - return nil - } - summary := supplierDTO.ToSupplierRelationDTO(s) - return &summary -} - -func ToPurchaseListDTOs(items []entity.Purchase) []PurchaseListItemDTO { +func ToPurchaseListDTOs(items []entity.Purchase) []PurchaseListDTO { if len(items) == 0 { - return nil + return make([]PurchaseListDTO, 0) } - result := make([]PurchaseListItemDTO, len(items)) + result := make([]PurchaseListDTO, len(items)) for i, item := range items { result[i] = ToPurchaseListDTO(item) } return result } -func toPurchaseApprovalDTO(p entity.Purchase) *approvalDTO.ApprovalRelationDTO { - if p.LatestApproval == nil || p.LatestApproval.Id == 0 { - return nil +func ToPurchaseDetailDTO(p entity.Purchase) PurchaseDetailDTO { + var supplier *supplierDTO.SupplierRelationDTO + if p.Supplier.Id != 0 { + mapped := supplierDTO.ToSupplierRelationDTO(p.Supplier) + supplier = &mapped } - mapped := approvalDTO.ToApprovalDTO(*p.LatestApproval) - return &mapped -} + + var createdUser *userDTO.UserRelationDTO + if p.CreatedUser.Id != 0 { + mapped := userDTO.ToUserRelationDTO(p.CreatedUser) + createdUser = &mapped + } + + var latestApproval *approvalDTO.ApprovalRelationDTO + if p.LatestApproval != nil && p.LatestApproval.Id != 0 { + mapped := approvalDTO.ToApprovalDTO(*p.LatestApproval) + latestApproval = &mapped + } + + return PurchaseDetailDTO{ + PurchaseRelationDTO: ToPurchaseRelationDTO(&p), + Supplier: supplier, + CreditTerm: p.CreditTerm, + DueDate: p.DueDate, + GrandTotal: p.GrandTotal, + Items: ToPurchaseItemDTOs(p.Items), + CreatedUser: createdUser, + CreatedAt: p.CreatedAt, + UpdatedAt: p.UpdatedAt, + LatestApproval: latestApproval, + } +} \ No newline at end of file diff --git a/internal/modules/purchases/module.go b/internal/modules/purchases/module.go index 1911e364..56dd5932 100644 --- a/internal/modules/purchases/module.go +++ b/internal/modules/purchases/module.go @@ -43,7 +43,6 @@ func (PurchaseModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate warehouseRepo, supplierRepo, productWarehouseRepo, - approvalRepo, approvalService, expenseBridge, ) diff --git a/internal/modules/purchases/repositories/purchase.repository.go b/internal/modules/purchases/repositories/purchase.repository.go index bc1c038a..49bb07e9 100644 --- a/internal/modules/purchases/repositories/purchase.repository.go +++ b/internal/modules/purchases/repositories/purchase.repository.go @@ -18,14 +18,11 @@ import ( type PurchaseRepository interface { repository.BaseRepository[entity.Purchase] CreateWithItems(ctx context.Context, purchase *entity.Purchase, items []*entity.PurchaseItem) error - CreateItems(ctx context.Context, purchaseID uint64, items []*entity.PurchaseItem) error - GetByIDWithRelations(ctx context.Context, id uint64) (*entity.Purchase, error) - GetAllWithFilters(ctx context.Context, offset, limit int, filter *PurchaseListFilter) ([]entity.Purchase, int64, error) - UpdatePricing(ctx context.Context, purchaseID uint64, updates []PurchasePricingUpdate, grandTotal float64) error - UpdateReceivingDetails(ctx context.Context, purchaseID uint64, updates []PurchaseReceivingUpdate) error - DeleteItems(ctx context.Context, purchaseID uint64, itemIDs []uint64) error - WithListRelations() func(*gorm.DB) *gorm.DB - UpdateGrandTotal(ctx context.Context, purchaseID uint64, grandTotal float64) error + CreateItems(ctx context.Context, purchaseID uint, items []*entity.PurchaseItem) error + UpdatePricing(ctx context.Context, purchaseID uint, updates []PurchasePricingUpdate, grandTotal float64) error + UpdateReceivingDetails(ctx context.Context, purchaseID uint, updates []PurchaseReceivingUpdate) error + DeleteItems(ctx context.Context, purchaseID uint, itemIDs []uint) error + UpdateGrandTotal(ctx context.Context, purchaseID uint, grandTotal float64) error NextPrNumber(ctx context.Context, tx *gorm.DB) (string, error) NextPoNumber(ctx context.Context, tx *gorm.DB) (string, error) } @@ -40,19 +37,10 @@ func NewPurchaseRepository(db *gorm.DB) PurchaseRepository { } } -type PurchaseListFilter struct { - SupplierID uint - Search string - PrNumber string - CreatedFrom *time.Time - CreatedTo *time.Time - Status *entity.ApprovalAction - CompletedOnly bool -} - func (r *PurchaseRepositoryImpl) CreateWithItems(ctx context.Context, purchase *entity.Purchase, items []*entity.PurchaseItem) error { db := r.DB().WithContext(ctx) + //ambil dari base repository if err := db.Create(purchase).Error; err != nil { return err } @@ -71,7 +59,7 @@ func (r *PurchaseRepositoryImpl) CreateWithItems(ctx context.Context, purchase * return nil } -func (r *PurchaseRepositoryImpl) CreateItems(ctx context.Context, purchaseID uint64, items []*entity.PurchaseItem) error { +func (r *PurchaseRepositoryImpl) CreateItems(ctx context.Context, purchaseID uint, items []*entity.PurchaseItem) error { if len(items) == 0 { return nil } @@ -86,52 +74,9 @@ func (r *PurchaseRepositoryImpl) CreateItems(ctx context.Context, purchaseID uin return r.DB().WithContext(ctx).Create(&items).Error } -func (r *PurchaseRepositoryImpl) GetByIDWithRelations(ctx context.Context, id uint64) (*entity.Purchase, error) { - var purchase entity.Purchase - err := r.DB().WithContext(ctx). - Scopes(r.withDetailRelations). - First(&purchase, id).Error - if err != nil { - return nil, err - } - return &purchase, nil -} - -func (r *PurchaseRepositoryImpl) GetAllWithFilters(ctx context.Context, offset, limit int, filter *PurchaseListFilter) ([]entity.Purchase, int64, error) { - return r.GetAll(ctx, offset, limit, func(db *gorm.DB) *gorm.DB { - db = r.withListRelations(db) - return r.applyListFilters(db, filter) - }) -} - -func (r *PurchaseRepositoryImpl) WithListRelations() func(*gorm.DB) *gorm.DB { - return func(db *gorm.DB) *gorm.DB { - return r.withListRelations(db) - } -} - -func (r *PurchaseRepositoryImpl) withDetailRelations(db *gorm.DB) *gorm.DB { - return db. - Preload("Supplier"). - Preload("Items", func(db *gorm.DB) *gorm.DB { - return db.Order("id ASC") - }). - Preload("Items.Product"). - Preload("Items.Warehouse"). - Preload("Items.Warehouse.Area"). - Preload("Items.Warehouse.Location"). - Preload("Items.ProductWarehouse") -} - -func (r *PurchaseRepositoryImpl) WithDetailRelations() func(*gorm.DB) *gorm.DB { - return func(db *gorm.DB) *gorm.DB { - return r.withDetailRelations(db) - } -} - type PurchasePricingUpdate struct { - ItemID uint64 - ProductID *uint64 + ItemID uint + ProductID *uint Price float64 TotalPrice float64 Quantity *float64 @@ -139,7 +84,7 @@ type PurchasePricingUpdate struct { } type PurchaseReceivingUpdate struct { - ItemID uint64 + ItemID uint ReceivedDate *time.Time TravelNumber *string TravelDocumentPath *string @@ -152,7 +97,7 @@ type PurchaseReceivingUpdate struct { func (r *PurchaseRepositoryImpl) UpdatePricing( ctx context.Context, - purchaseID uint64, + purchaseID uint, updates []PurchasePricingUpdate, grandTotal float64, ) error { @@ -192,7 +137,6 @@ func (r *PurchaseRepositoryImpl) UpdatePricing( Where("id = ?", purchaseID). Updates(map[string]interface{}{ "grand_total": grandTotal, - "updated_at": gorm.Expr("NOW()"), }).Error; err != nil { return err } @@ -202,7 +146,7 @@ func (r *PurchaseRepositoryImpl) UpdatePricing( func (r *PurchaseRepositoryImpl) UpdateReceivingDetails( ctx context.Context, - purchaseID uint64, + purchaseID uint, updates []PurchaseReceivingUpdate, ) error { if len(updates) == 0 { @@ -259,7 +203,7 @@ func (r *PurchaseRepositoryImpl) UpdateReceivingDetails( func (r *PurchaseRepositoryImpl) UpdateGrandTotal( ctx context.Context, - purchaseID uint64, + purchaseID uint, grandTotal float64, ) error { return r.DB().WithContext(ctx). @@ -271,7 +215,7 @@ func (r *PurchaseRepositoryImpl) UpdateGrandTotal( }).Error } -func (r *PurchaseRepositoryImpl) DeleteItems(ctx context.Context, purchaseID uint64, itemIDs []uint64) error { +func (r *PurchaseRepositoryImpl) DeleteItems(ctx context.Context, purchaseID uint, itemIDs []uint) error { if len(itemIDs) == 0 { return errors.New("itemIDs cannot be empty") } @@ -361,63 +305,3 @@ func parseNumericSuffix(value, prefix string) (int, bool) { } return number, true } - -func (r *PurchaseRepositoryImpl) withListRelations(db *gorm.DB) *gorm.DB { - return db.Preload("Supplier") -} - -func (r *PurchaseRepositoryImpl) applyListFilters(db *gorm.DB, filter *PurchaseListFilter) *gorm.DB { - if filter == nil { - return db - } - - if filter.SupplierID > 0 { - db = db.Where("purchases.supplier_id = ?", filter.SupplierID) - } - - if search := strings.ToLower(strings.TrimSpace(filter.Search)); search != "" { - like := "%" + search + "%" - db = db.Where("(LOWER(purchases.pr_number) LIKE ? OR LOWER(COALESCE(purchases.notes, '')) LIKE ?)", like, like) - } - - if pr := strings.TrimSpace(filter.PrNumber); pr != "" { - db = db.Where("purchases.pr_number ILIKE ?", "%"+pr+"%") - } - - if filter.CreatedFrom != nil { - db = db.Where("purchases.created_at >= ?", *filter.CreatedFrom) - } - - if filter.CreatedTo != nil { - db = db.Where("purchases.created_at < ?", *filter.CreatedTo) - } - - if filter.CompletedOnly { - step := uint16(utils.PurchaseStepCompleted) - db = r.applyLatestApprovalFilter(db, entity.ApprovalActionApproved, &step) - } else if filter.Status != nil { - db = r.applyLatestApprovalFilter(db, *filter.Status, nil) - } - - return db.Order("purchases.created_at DESC").Order("purchases.id DESC") -} - -func (r *PurchaseRepositoryImpl) applyLatestApprovalFilter(db *gorm.DB, action entity.ApprovalAction, minStep *uint16) *gorm.DB { - latestSub := r.DB(). - Model(&entity.Approval{}). - Select("approvable_id, MAX(action_at) AS latest_action_at"). - Where("approvable_type = ?", utils.ApprovalWorkflowPurchase.String()). - Group("approvable_id") - - db = db. - Joins("LEFT JOIN (?) AS latest_purchase_approvals ON latest_purchase_approvals.approvable_id = purchases.id", latestSub). - Joins( - "LEFT JOIN approvals ON approvals.approvable_id = purchases.id AND approvals.approvable_type = ? AND approvals.action_at = latest_purchase_approvals.latest_action_at", - utils.ApprovalWorkflowPurchase.String(), - ). - Where("approvals.action = ?", string(action)) - if minStep != nil { - db = db.Where("approvals.step_number >= ?", *minStep) - } - return db -} diff --git a/internal/modules/purchases/route.go b/internal/modules/purchases/route.go index aedc3ee8..5145bc94 100644 --- a/internal/modules/purchases/route.go +++ b/internal/modules/purchases/route.go @@ -3,7 +3,7 @@ package purchases import ( "github.com/gofiber/fiber/v2" - middleware "gitlab.com/mbugroup/lti-api.git/internal/middleware" + m "gitlab.com/mbugroup/lti-api.git/internal/middleware" controller "gitlab.com/mbugroup/lti-api.git/internal/modules/purchases/controllers" service "gitlab.com/mbugroup/lti-api.git/internal/modules/purchases/services" user "gitlab.com/mbugroup/lti-api.git/internal/modules/users/services" @@ -13,7 +13,7 @@ func Routes(router fiber.Router, purchaseService service.PurchaseService, userSe ctrl := controller.NewPurchaseController(purchaseService) route := router.Group("/purchases") - route.Use(middleware.Auth(userService)) + route.Use(m.Auth(userService)) route.Get("/", ctrl.GetAll) route.Get("/:id", ctrl.GetOne) diff --git a/internal/modules/purchases/services/expense_bridge.go b/internal/modules/purchases/services/expense_bridge.go index b7c96d03..3e857d35 100644 --- a/internal/modules/purchases/services/expense_bridge.go +++ b/internal/modules/purchases/services/expense_bridge.go @@ -9,16 +9,16 @@ import ( // PurchaseExpenseBridge defines hooks that allow purchase flows to stay in sync with expense data once it exists. type PurchaseExpenseBridge interface { - OnItemsCreated(ctx context.Context, purchaseID uint64, items []entity.PurchaseItem) error - OnItemsDeleted(ctx context.Context, purchaseID uint64, itemIDs []uint64) error - OnItemsReceived(ctx context.Context, purchaseID uint64, updates []ExpenseReceivingPayload) error + OnItemsCreated(ctx context.Context, purchaseID uint, items []entity.PurchaseItem) error + OnItemsDeleted(ctx context.Context, purchaseID uint, itemIDs []uint) error + OnItemsReceived(ctx context.Context, purchaseID uint, updates []ExpenseReceivingPayload) error } // ExpenseReceivingPayload captures the minimum data expense integration will need once available. type ExpenseReceivingPayload struct { - PurchaseItemID uint64 - ProductID uint64 - WarehouseID uint64 + PurchaseItemID uint + ProductID uint + WarehouseID uint ReceivedQty float64 ReceivedDate *time.Time } @@ -30,14 +30,14 @@ func NewNoopPurchaseExpenseBridge() PurchaseExpenseBridge { return &noopPurchaseExpenseBridge{} } -func (n *noopPurchaseExpenseBridge) OnItemsCreated(_ context.Context, _ uint64, _ []entity.PurchaseItem) error { +func (n *noopPurchaseExpenseBridge) OnItemsCreated(_ context.Context, _ uint, _ []entity.PurchaseItem) error { return nil } -func (n *noopPurchaseExpenseBridge) OnItemsDeleted(_ context.Context, _ uint64, _ []uint64) error { +func (n *noopPurchaseExpenseBridge) OnItemsDeleted(_ context.Context, _ uint, _ []uint) error { return nil } -func (n *noopPurchaseExpenseBridge) OnItemsReceived(_ context.Context, _ uint64, _ []ExpenseReceivingPayload) error { +func (n *noopPurchaseExpenseBridge) OnItemsReceived(_ context.Context, _ uint, _ []ExpenseReceivingPayload) error { return nil } diff --git a/internal/modules/purchases/services/purchase.service.go b/internal/modules/purchases/services/purchase.service.go index b0d5311d..60a65960 100644 --- a/internal/modules/purchases/services/purchase.service.go +++ b/internal/modules/purchases/services/purchase.service.go @@ -11,7 +11,7 @@ import ( commonRepo "gitlab.com/mbugroup/lti-api.git/internal/common/repository" commonSvc "gitlab.com/mbugroup/lti-api.git/internal/common/service" entity "gitlab.com/mbugroup/lti-api.git/internal/entities" - authmiddleware "gitlab.com/mbugroup/lti-api.git/internal/middleware" + m "gitlab.com/mbugroup/lti-api.git/internal/middleware" rProductWarehouse "gitlab.com/mbugroup/lti-api.git/internal/modules/inventory/product-warehouses/repositories" rProduct "gitlab.com/mbugroup/lti-api.git/internal/modules/master/products/repositories" rSupplier "gitlab.com/mbugroup/lti-api.git/internal/modules/master/suppliers/repositories" @@ -28,19 +28,18 @@ import ( ) type PurchaseService interface { - GetAll(ctx *fiber.Ctx, params *validation.PurchaseQuery) ([]entity.Purchase, int64, error) - GetOne(ctx *fiber.Ctx, id uint64) (*entity.Purchase, error) + GetAll(ctx *fiber.Ctx, params *validation.Query) ([]entity.Purchase, int64, error) + GetOne(ctx *fiber.Ctx, id uint) (*entity.Purchase, error) CreateOne(ctx *fiber.Ctx, req *validation.CreatePurchaseRequest) (*entity.Purchase, error) - ApproveStaffPurchase(ctx *fiber.Ctx, id uint64, req *validation.ApproveStaffPurchaseRequest) (*entity.Purchase, error) - ApproveManagerPurchase(ctx *fiber.Ctx, id uint64, req *validation.ApproveManagerPurchaseRequest) (*entity.Purchase, error) - ReceiveProducts(ctx *fiber.Ctx, id uint64, req *validation.ReceivePurchaseRequest) (*entity.Purchase, error) - DeleteItems(ctx *fiber.Ctx, id uint64, req *validation.DeletePurchaseItemsRequest) (*entity.Purchase, error) - DeletePurchase(ctx *fiber.Ctx, id uint64) error + ApproveStaffPurchase(ctx *fiber.Ctx, id uint, req *validation.ApproveStaffPurchaseRequest) (*entity.Purchase, error) + ApproveManagerPurchase(ctx *fiber.Ctx, id uint, req *validation.ApproveManagerPurchaseRequest) (*entity.Purchase, error) + ReceiveProducts(ctx *fiber.Ctx, id uint, req *validation.ReceivePurchaseRequest) (*entity.Purchase, error) + DeleteItems(ctx *fiber.Ctx, id uint, req *validation.DeletePurchaseItemsRequest) (*entity.Purchase, error) + DeletePurchase(ctx *fiber.Ctx, id uint) error } const ( - priceTolerance = 0.0001 - queryDateLayout = "2006-01-02" + priceTolerance = 0.0001 ) type purchaseService struct { @@ -51,9 +50,9 @@ type purchaseService struct { WarehouseRepo rWarehouse.WarehouseRepository SupplierRepo rSupplier.SupplierRepository ProductWarehouseRepo rProductWarehouse.ProductWarehouseRepository - ApprovalRepo commonRepo.ApprovalRepository ApprovalSvc commonSvc.ApprovalService ExpenseBridge PurchaseExpenseBridge + approvalWorkflow approvalutils.ApprovalWorkflowKey } type staffAdjustmentPayload struct { @@ -69,7 +68,6 @@ func NewPurchaseService( warehouseRepo rWarehouse.WarehouseRepository, supplierRepo rSupplier.SupplierRepository, productWarehouseRepo rProductWarehouse.ProductWarehouseRepository, - approvalRepo commonRepo.ApprovalRepository, approvalSvc commonSvc.ApprovalService, expenseBridge PurchaseExpenseBridge, ) PurchaseService { @@ -84,70 +82,113 @@ func NewPurchaseService( WarehouseRepo: warehouseRepo, SupplierRepo: supplierRepo, ProductWarehouseRepo: productWarehouseRepo, - ApprovalRepo: approvalRepo, ApprovalSvc: approvalSvc, ExpenseBridge: expenseBridge, + approvalWorkflow: utils.ApprovalWorkflowPurchase, } } +func (s *purchaseService) withRelations(db *gorm.DB) *gorm.DB { + if db == nil { + return db + } -func (s *purchaseService) GetAll(c *fiber.Ctx, params *validation.PurchaseQuery) ([]entity.Purchase, int64, error) { + return db. + Preload("Supplier"). + Preload("Items", func(db *gorm.DB) *gorm.DB { + return db.Order("id ASC") + }). + Preload("Items.Product"). + Preload("Items.Product.Uom"). + Preload("Items.Product.ProductCategory"). + Preload("Items.Warehouse"). + Preload("Items.Product.Flags"). + Preload("Items.Warehouse.Area"). + Preload("Items.Warehouse.Location"). + Preload("Items.ProductWarehouse") +} + +func (s *purchaseService) GetAll(c *fiber.Ctx, params *validation.Query) ([]entity.Purchase, int64, error) { if err := s.Validate.Struct(params); err != nil { return nil, 0, err } - limit := params.Limit - if limit <= 0 { - limit = 10 - } - page := params.Page - if page <= 0 { - page = 1 - } - offset := (page - 1) * limit - - ctx := c.Context() + offset := (params.Page - 1) * params.Limit createdFrom, createdTo, err := parseQueryDates(params.CreatedFrom, params.CreatedTo) if err != nil { return nil, 0, err } - statusAction, completedOnly, err := parseApprovalAction(params.Status) - if err != nil { - return nil, 0, err - } + purchases, total, err := s.PurchaseRepo.GetAll(c.Context(), offset, params.Limit, func(db *gorm.DB) *gorm.DB { + db = s.withRelations(db) - filter := &rPurchase.PurchaseListFilter{ - SupplierID: params.SupplierID, - Search: params.Search, - PrNumber: params.PrNumber, - CreatedFrom: createdFrom, - CreatedTo: createdTo, - Status: statusAction, - CompletedOnly: completedOnly, - } + if params.SupplierID > 0 { + db = db.Where("supplier_id = ?", params.SupplierID) + } + + if createdFrom != nil { + db = db.Where("created_at >= ?", *createdFrom) + } + + if createdTo != nil { + db = db.Where("created_at < ?", *createdTo) + } + + if params.AreaID > 0 { + db = db.Where( + `EXISTS ( + SELECT 1 + FROM purchase_items pi + JOIN warehouses w ON w.id = pi.warehouse_id + WHERE pi.purchase_id = purchases.id AND w.area_id = ? + )`, + params.AreaID, + ) + } + + if params.LocationID > 0 { + db = db.Where( + `EXISTS ( + SELECT 1 + FROM purchase_items pi + JOIN warehouses w ON w.id = pi.warehouse_id + WHERE pi.purchase_id = purchases.id AND w.location_id = ? + )`, + params.LocationID, + ) + } + + if params.ProductCategoryID > 0 { + db = db.Where( + `EXISTS ( + SELECT 1 + FROM purchase_items pi + JOIN products p ON p.id = pi.product_id + WHERE pi.purchase_id = purchases.id AND p.product_category_id = ? + )`, + params.ProductCategoryID, + ) + } + + return db.Order("created_at DESC").Order("purchases.id DESC") + }) - purchases, total, err := s.PurchaseRepo.GetAllWithFilters(ctx, offset, limit, filter) if err != nil { s.Log.Errorf("Failed to get purchases: %+v", err) return nil, 0, fiber.NewError(fiber.StatusInternalServerError, "Failed to get purchases") } - if err := s.attachLatestApprovals(ctx, purchases); err != nil { - s.Log.Warnf("Unable to attach latest approvals to purchases: %+v", err) + for i := range purchases { + if err := s.attachLatestApproval(c.Context(), &purchases[i]); err != nil { + s.Log.Warnf("Unable to attach latest approval for purchase %d: %+v", purchases[i].Id, err) + } } return purchases, total, nil } -func (s *purchaseService) GetOne(c *fiber.Ctx, id uint64) (*entity.Purchase, error) { - if id == 0 { - return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid purchase id") - } - - ctx := c.Context() - - purchase, err := s.PurchaseRepo.GetByIDWithRelations(ctx, id) +func (s *purchaseService) GetOne(c *fiber.Ctx, id uint) (*entity.Purchase, error) { + purchase, err := s.PurchaseRepo.GetByID(c.Context(), id, s.withRelations) if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, fiber.NewError(fiber.StatusNotFound, "Purchase not found") @@ -156,10 +197,9 @@ func (s *purchaseService) GetOne(c *fiber.Ctx, id uint64) (*entity.Purchase, err return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to get purchase") } - if err := s.attachLatestApproval(ctx, purchase); err != nil { + if err := s.attachLatestApproval(c.Context(), purchase); err != nil { s.Log.Warnf("Unable to attach latest approval for purchase %d: %+v", id, err) } - return purchase, nil } @@ -168,14 +208,12 @@ func (s *purchaseService) CreateOne(c *fiber.Ctx, req *validation.CreatePurchase return nil, err } - actorID, err := actorIDFromContext(c) + actorID, err := m.ActorIDFromContext(c) if err != nil { return nil, err } - ctx := c.Context() - - if _, err := s.SupplierRepo.GetByID(ctx, req.SupplierID, nil); err != nil { + if _, err := s.SupplierRepo.GetByID(c.Context(), req.SupplierID, nil); err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, fiber.NewError(fiber.StatusNotFound, "Supplier not found") } @@ -184,8 +222,8 @@ func (s *purchaseService) CreateOne(c *fiber.Ctx, req *validation.CreatePurchase } type aggregatedItem struct { - productId uint64 - warehouseId uint64 + productId uint + warehouseId uint subQty float64 } @@ -195,13 +233,14 @@ func (s *purchaseService) CreateOne(c *fiber.Ctx, req *validation.CreatePurchase warehouseCache := make(map[uint]*entity.Warehouse) productSupplierCache := make(map[uint]bool) - getWarehouse := func(id uint) (*entity.Warehouse, error) { if warehouse, ok := warehouseCache[id]; ok { return warehouse, nil } + warehouse, err := s.WarehouseRepo.GetByID(c.Context(), id, func(db *gorm.DB) *gorm.DB { + return db.Preload("Area").Preload("location") + }) - warehouse, err := s.WarehouseRepo.GetDetailByID(ctx, id) if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, fiber.NewError(fiber.StatusNotFound, fmt.Sprintf("Warehouse %d not found", id)) @@ -223,7 +262,7 @@ func (s *purchaseService) CreateOne(c *fiber.Ctx, req *validation.CreatePurchase } if _, checked := productSupplierCache[item.ProductID]; !checked { - linked, err := s.ProductRepo.IsLinkedToSupplier(ctx, item.ProductID, req.SupplierID) + linked, err := s.ProductRepo.IsLinkedToSupplier(c.Context(), item.ProductID, req.SupplierID) if err != nil { s.Log.Errorf("Failed to validate product %d for supplier %d: %+v", item.ProductID, req.SupplierID, err) return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to validate product for supplier") @@ -234,8 +273,8 @@ func (s *purchaseService) CreateOne(c *fiber.Ctx, req *validation.CreatePurchase productSupplierCache[item.ProductID] = true } - productId := uint64(item.ProductID) - warehouseId := uint64(item.WarehouseID) + productId := uint(item.ProductID) + warehouseId := uint(item.WarehouseID) key := fmt.Sprintf("%d:%d", productId, warehouseId) if idx, ok := indexMap[key]; ok { @@ -258,12 +297,12 @@ func (s *purchaseService) CreateOne(c *fiber.Ctx, req *validation.CreatePurchase dueDate := &dueDateValue purchase := &entity.Purchase{ - SupplierId: uint64(req.SupplierID), + SupplierId: uint(req.SupplierID), CreditTerm: creditTerm, DueDate: dueDate, GrandTotal: 0, Notes: req.Notes, - CreatedBy: uint64(actorID), + CreatedBy: uint(actorID), } items := make([]*entity.PurchaseItem, 0, len(aggregated)) @@ -279,25 +318,21 @@ func (s *purchaseService) CreateOne(c *fiber.Ctx, req *validation.CreatePurchase }) } - transactionErr := s.PurchaseRepo.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error { + transactionErr := s.PurchaseRepo.DB().WithContext(c.Context()).Transaction(func(tx *gorm.DB) error { purchaseRepoTx := rPurchase.NewPurchaseRepository(tx) - code, err := purchaseRepoTx.NextPrNumber(ctx, tx) + code, err := purchaseRepoTx.NextPrNumber(c.Context(), tx) if err != nil { return err } purchase.PrNumber = code - if err := purchaseRepoTx.CreateWithItems(ctx, purchase, items); err != nil { + if err := purchaseRepoTx.CreateWithItems(c.Context(), purchase, items); err != nil { return err } actorID := uint(purchase.CreatedBy) - if actorID == 0 { - actorID = 1 - } - action := entity.ApprovalActionCreated - if err := s.createPurchaseApproval(ctx, tx, purchase.Id, utils.PurchaseStepPengajuan, action, actorID, nil, false); err != nil { + if err := s.createPurchaseApproval(c.Context(), tx, purchase.Id, utils.PurchaseStepPengajuan, entity.ApprovalActionCreated, actorID, nil, false); err != nil { return err } @@ -308,37 +343,35 @@ func (s *purchaseService) CreateOne(c *fiber.Ctx, req *validation.CreatePurchase return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to create purchase") } - created, err := s.PurchaseRepo.GetByIDWithRelations(ctx, purchase.Id) + created, err := s.PurchaseRepo.GetByID(c.Context(), purchase.Id, s.withRelations) if err != nil { s.Log.Errorf("Failed to load created purchase: %+v", err) return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to load purchase") } - if err := s.attachLatestApproval(ctx, created); err != nil { + if err := s.attachLatestApproval(c.Context(), created); err != nil { s.Log.Warnf("Unable to attach latest approval for purchase %d: %+v", created.Id, err) } - s.notifyExpenseItemsCreated(ctx, created.Id, created.Items) - return created, nil } -func (s *purchaseService) ApproveStaffPurchase(c *fiber.Ctx, id uint64, req *validation.ApproveStaffPurchaseRequest) (*entity.Purchase, error) { - return s.processStaffPurchaseApproval(c, id, req, false) -} - -func (s *purchaseService) processStaffPurchaseApproval(c *fiber.Ctx, id uint64, req *validation.ApproveStaffPurchaseRequest, requireStaffApproval bool) (*entity.Purchase, error) { +func (s *purchaseService) ApproveStaffPurchase(c *fiber.Ctx, id uint, req *validation.ApproveStaffPurchaseRequest) (*entity.Purchase, error) { if err := s.Validate.Struct(req); err != nil { return nil, err } - actorID, err := actorIDFromContext(c) + action, err := parseApprovalActionInput(req.Action) if err != nil { return nil, err } - ctx := c.Context() - purchase, err := s.PurchaseRepo.GetByIDWithRelations(ctx, id) + actorID, err := m.ActorIDFromContext(c) + if err != nil { + return nil, err + } + + purchase, err := s.PurchaseRepo.GetByID(c.Context(), id, s.withRelations) if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, fiber.NewError(fiber.StatusNotFound, "Purchase not found") @@ -346,7 +379,7 @@ func (s *purchaseService) processStaffPurchaseApproval(c *fiber.Ctx, id uint64, return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to get purchase") } - if err := s.attachLatestApproval(ctx, purchase); err != nil { + if err := s.attachLatestApproval(c.Context(), purchase); err != nil { s.Log.Warnf("Unable to load latest approval for purchase %d: %+v", id, err) } @@ -355,8 +388,8 @@ func (s *purchaseService) processStaffPurchaseApproval(c *fiber.Ctx, id uint64, latestStep = purchase.LatestApproval.StepNumber } - if requireStaffApproval && latestStep < uint16(utils.PurchaseStepStaffPurchase) { - return nil, fiber.NewError(fiber.StatusBadRequest, "Purchase cannot be edited before staff approval") + if action == entity.ApprovalActionRejected { + return s.rejectAndReload(c, utils.PurchaseStepStaffPurchase, purchase.Id, actorID, req.Notes) } isInitialApproval := latestStep < uint16(utils.PurchaseStepStaffPurchase) @@ -374,68 +407,56 @@ func (s *purchaseService) processStaffPurchaseApproval(c *fiber.Ctx, id uint64, syncReceiving := !isInitialApproval && hasReceivingData - payload, err := s.buildStaffAdjustmentPayload(ctx, purchase, req, syncReceiving) + if len(req.Items) == 0 { + return nil, fiber.NewError(fiber.StatusBadRequest, "Items must not be empty for staff approval") + } + + payload, err := s.buildStaffAdjustmentPayload(c.Context(), purchase, req, syncReceiving) if err != nil { return nil, err } - transactionErr := s.PurchaseRepo.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error { + transactionErr := s.PurchaseRepo.DB().WithContext(c.Context()).Transaction(func(tx *gorm.DB) error { purchaseRepoTx := rPurchase.NewPurchaseRepository(tx) grandTotalUpdated := false if len(payload.PricingUpdates) > 0 { - if err := purchaseRepoTx.UpdatePricing(ctx, purchase.Id, payload.PricingUpdates, payload.GrandTotal); err != nil { + if err := purchaseRepoTx.UpdatePricing(c.Context(), purchase.Id, payload.PricingUpdates, payload.GrandTotal); err != nil { return err } grandTotalUpdated = true } if len(payload.NewItems) > 0 { - if err := purchaseRepoTx.CreateItems(ctx, purchase.Id, payload.NewItems); err != nil { + if err := purchaseRepoTx.CreateItems(c.Context(), purchase.Id, payload.NewItems); err != nil { return err } } if !grandTotalUpdated { - if err := purchaseRepoTx.UpdateGrandTotal(ctx, purchase.Id, payload.GrandTotal); err != nil { + if err := purchaseRepoTx.UpdateGrandTotal(c.Context(), purchase.Id, payload.GrandTotal); err != nil { return err } } if isInitialApproval { - action := entity.ApprovalActionApproved - if err := s.createPurchaseApproval(ctx, tx, purchase.Id, utils.PurchaseStepStaffPurchase, action, actorID, req.Notes, false); err != nil { + if err := s.createPurchaseApproval(c.Context(), tx, purchase.Id, utils.PurchaseStepStaffPurchase, action, actorID, req.Notes, false); err != nil { return err } return nil } if len(payload.PricingUpdates) > 0 || len(payload.NewItems) > 0 { - approvalSvc := commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(tx)) - if approvalSvc != nil { - latest, err := approvalSvc.LatestByTarget(ctx, utils.ApprovalWorkflowPurchase, uint(purchase.Id), nil) - if err != nil { - return err - } - - shouldRecordStaffUpdate := latest == nil || - latest.StepNumber != uint16(utils.PurchaseStepStaffPurchase) || - latest.Action == nil || - (latest.Action != nil && *latest.Action != entity.ApprovalActionUpdated) - - if shouldRecordStaffUpdate { - action := entity.ApprovalActionUpdated - if _, err := approvalSvc.CreateApproval( - ctx, - utils.ApprovalWorkflowPurchase, - uint(purchase.Id), - utils.PurchaseStepStaffPurchase, - &action, - actorID, - req.Notes, - ); err != nil { - return err - } - } + if err := s.createPurchaseApproval( + c.Context(), + tx, + purchase.Id, + utils.PurchaseStepStaffPurchase, + entity.ApprovalActionUpdated, + actorID, + req.Notes, + true, // allowDuplicate = true supaya boleh UPDATED berkali2 + ); err != nil { + return err } } @@ -452,11 +473,11 @@ func (s *purchaseService) processStaffPurchaseApproval(c *fiber.Ctx, id uint64, return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to update purchase pricing") } - updated, err := s.PurchaseRepo.GetByIDWithRelations(ctx, purchase.Id) + updated, err := s.PurchaseRepo.GetByID(c.Context(), purchase.Id, s.withRelations) if err != nil { return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to load purchase") } - if err := s.attachLatestApproval(ctx, updated); err != nil { + if err := s.attachLatestApproval(c.Context(), updated); err != nil { s.Log.Warnf("Unable to attach latest approval for purchase %d: %+v", updated.Id, err) } @@ -468,25 +489,28 @@ func (s *purchaseService) processStaffPurchaseApproval(c *fiber.Ctx, id uint64, } newItems[i] = *item } - s.notifyExpenseItemsCreated(ctx, purchase.Id, newItems) + s.notifyExpenseItemsCreated(c.Context(), purchase.Id, newItems) } return updated, nil } -func (s *purchaseService) ApproveManagerPurchase(c *fiber.Ctx, id uint64, req *validation.ApproveManagerPurchaseRequest) (*entity.Purchase, error) { +func (s *purchaseService) ApproveManagerPurchase(c *fiber.Ctx, id uint, req *validation.ApproveManagerPurchaseRequest) (*entity.Purchase, error) { if err := s.Validate.Struct(req); err != nil { return nil, err } - actorID, err := actorIDFromContext(c) + action, err := parseApprovalActionInput(req.Action) if err != nil { return nil, err } - ctx := c.Context() + actorID, err := m.ActorIDFromContext(c) + if err != nil { + return nil, err + } - purchase, err := s.PurchaseRepo.GetByIDWithRelations(ctx, id) + purchase, err := s.PurchaseRepo.GetByID(c.Context(), id, s.withRelations) if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, fiber.NewError(fiber.StatusNotFound, "Purchase not found") @@ -495,7 +519,7 @@ func (s *purchaseService) ApproveManagerPurchase(c *fiber.Ctx, id uint64, req *v return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to get purchase") } - if err := s.attachLatestApproval(ctx, purchase); err != nil { + if err := s.attachLatestApproval(c.Context(), purchase); err != nil { s.Log.Warnf("Unable to load latest approval for purchase %d: %+v", id, err) } @@ -504,16 +528,19 @@ func (s *purchaseService) ApproveManagerPurchase(c *fiber.Ctx, id uint64, req *v return nil, fiber.NewError(fiber.StatusBadRequest, "Purchase must reach staff purchase step before manager approval") } - action := entity.ApprovalActionApproved + if action == entity.ApprovalActionRejected { + return s.rejectAndReload(c, utils.PurchaseStepManager, purchase.Id, actorID, req.Notes) + } + now := time.Now().UTC() hasExistingPO := purchase.PoNumber != nil && strings.TrimSpace(*purchase.PoNumber) != "" var generatedNumber string - transactionErr := s.PurchaseRepo.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error { + transactionErr := s.PurchaseRepo.DB().WithContext(c.Context()).Transaction(func(tx *gorm.DB) error { updateData := map[string]any{} if !hasExistingPO { repoTx := rPurchase.NewPurchaseRepository(tx) - code, err := repoTx.NextPoNumber(ctx, tx) + code, err := repoTx.NextPoNumber(c.Context(), tx) if err != nil { return err } @@ -524,7 +551,7 @@ func (s *purchaseService) ApproveManagerPurchase(c *fiber.Ctx, id uint64, req *v if len(updateData) > 0 { repoTx := rPurchase.NewPurchaseRepository(tx) - if err := repoTx.PatchOne(ctx, uint(purchase.Id), updateData, nil); err != nil { + if err := repoTx.PatchOne(c.Context(), uint(purchase.Id), updateData, nil); err != nil { return err } } @@ -537,11 +564,11 @@ func (s *purchaseService) ApproveManagerPurchase(c *fiber.Ctx, id uint64, req *v return db.Where("step_number = ?", uint16(step)) } } - latestStaff, err := approvalSvcTx.LatestByTarget(ctx, utils.ApprovalWorkflowPurchase, uint(purchase.Id), filterByStep(utils.PurchaseStepStaffPurchase)) + latestStaff, err := approvalSvcTx.LatestByTarget(c.Context(), utils.ApprovalWorkflowPurchase, uint(purchase.Id), filterByStep(utils.PurchaseStepStaffPurchase)) if err != nil { return err } - latestManager, err := approvalSvcTx.LatestByTarget(ctx, utils.ApprovalWorkflowPurchase, uint(purchase.Id), filterByStep(utils.PurchaseStepManager)) + latestManager, err := approvalSvcTx.LatestByTarget(c.Context(), utils.ApprovalWorkflowPurchase, uint(purchase.Id), filterByStep(utils.PurchaseStepManager)) if err != nil { return err } @@ -550,7 +577,7 @@ func (s *purchaseService) ApproveManagerPurchase(c *fiber.Ctx, id uint64, req *v } } - if err := s.createPurchaseApproval(ctx, tx, purchase.Id, utils.PurchaseStepManager, action, actorID, req.Notes, forceManagerApproval); err != nil { + if err := s.createPurchaseApproval(c.Context(), tx, purchase.Id, utils.PurchaseStepManager, action, actorID, req.Notes, forceManagerApproval); err != nil { return err } @@ -566,32 +593,35 @@ func (s *purchaseService) ApproveManagerPurchase(c *fiber.Ctx, id uint64, req *v purchase.PoDate = &now } - updated, err := s.PurchaseRepo.GetByIDWithRelations(ctx, purchase.Id) + updated, err := s.PurchaseRepo.GetByID(c.Context(), purchase.Id, s.withRelations) if err != nil { s.Log.Errorf("Failed to load purchase after manager approval: %+v", err) return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to load purchase") } - if err := s.attachLatestApproval(ctx, updated); err != nil { + if err := s.attachLatestApproval(c.Context(), updated); err != nil { s.Log.Warnf("Unable to attach latest approval for purchase %d: %+v", updated.Id, err) } return updated, nil } -func (s *purchaseService) ReceiveProducts(c *fiber.Ctx, id uint64, req *validation.ReceivePurchaseRequest) (*entity.Purchase, error) { +func (s *purchaseService) ReceiveProducts(c *fiber.Ctx, id uint, req *validation.ReceivePurchaseRequest) (*entity.Purchase, error) { if err := s.Validate.Struct(req); err != nil { return nil, err } - actorID, err := actorIDFromContext(c) + action, err := parseApprovalActionInput(req.Action) if err != nil { return nil, err } - ctx := c.Context() + actorID, err := m.ActorIDFromContext(c) + if err != nil { + return nil, err + } - purchase, err := s.PurchaseRepo.GetByIDWithRelations(ctx, id) + purchase, err := s.PurchaseRepo.GetByID(c.Context(), id, s.withRelations) if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, fiber.NewError(fiber.StatusNotFound, "Purchase not found") @@ -603,7 +633,7 @@ func (s *purchaseService) ReceiveProducts(c *fiber.Ctx, id uint64, req *validati return nil, fiber.NewError(fiber.StatusBadRequest, "Purchase order has not been generated") } - if err := s.attachLatestApproval(ctx, purchase); err != nil { + if err := s.attachLatestApproval(c.Context(), purchase); err != nil { s.Log.Warnf("Unable to load latest approval for purchase %d: %+v", id, err) } @@ -612,7 +642,25 @@ func (s *purchaseService) ReceiveProducts(c *fiber.Ctx, id uint64, req *validati return nil, fiber.NewError(fiber.StatusBadRequest, "Purchase must be approved by manager before receiving products") } - itemMap := make(map[uint64]*entity.PurchaseItem, len(purchase.Items)) + if action == entity.ApprovalActionApproved && len(req.Items) == 0 { + return nil, fiber.NewError(fiber.StatusBadRequest, "Receiving data must not be empty") + } + + if action == entity.ApprovalActionRejected { + if err := s.createPurchaseApproval(c.Context(), nil, purchase.Id, utils.PurchaseStepReceiving, action, actorID, req.Notes, true); err != nil { + return nil, err + } + updated, err := s.PurchaseRepo.GetByID(c.Context(), purchase.Id, s.withRelations) + if err != nil { + return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to load purchase") + } + if err := s.attachLatestApproval(c.Context(), updated); err != nil { + s.Log.Warnf("Unable to load latest approval for purchase %d: %+v", id, err) + } + return updated, nil + } + + itemMap := make(map[uint]*entity.PurchaseItem, len(purchase.Items)) for i := range purchase.Items { itemMap[purchase.Items[i].Id] = &purchase.Items[i] } @@ -626,7 +674,7 @@ func (s *purchaseService) ReceiveProducts(c *fiber.Ctx, id uint64, req *validati receivedQty float64 } - visitedItems := make(map[uint64]struct{}, len(req.Items)) + visitedItems := make(map[uint]struct{}, len(req.Items)) prepared := make([]preparedReceiving, 0, len(req.Items)) for _, payload := range req.Items { item, exists := itemMap[payload.PurchaseItemID] @@ -684,10 +732,12 @@ func (s *purchaseService) ReceiveProducts(c *fiber.Ctx, id uint64, req *validati return nil, fiber.NewError(fiber.StatusBadRequest, "Receiving data must be provided for all purchase items") } - receivingAction := entity.ApprovalActionApproved + receivingAction := action completedAction := entity.ApprovalActionApproved - - approvalSvc := s.approvalServiceForDB(nil) + approvalSvc := commonSvc.NewApprovalService( + commonRepo.NewApprovalRepository(s.PurchaseRepo.DB()), + ) + if approvalSvc != nil { filterStep := func(step approvalutils.ApprovalStep) func(*gorm.DB) *gorm.DB { return func(db *gorm.DB) *gorm.DB { @@ -695,7 +745,12 @@ func (s *purchaseService) ReceiveProducts(c *fiber.Ctx, id uint64, req *validati } } - latestReceiving, err := approvalSvc.LatestByTarget(ctx, utils.ApprovalWorkflowPurchase, uint(purchase.Id), filterStep(utils.PurchaseStepReceiving)) + latestReceiving, err := approvalSvc.LatestByTarget( + c.Context(), + utils.ApprovalWorkflowPurchase, + uint(purchase.Id), + filterStep(utils.PurchaseStepReceiving), + ) if err != nil { s.Log.Errorf("Failed to inspect receiving approval for purchase %d: %+v", purchase.Id, err) return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to record purchase receiving") @@ -705,8 +760,7 @@ func (s *purchaseService) ReceiveProducts(c *fiber.Ctx, id uint64, req *validati receivingAction = entity.ApprovalActionUpdated } } - - transactionErr := s.PurchaseRepo.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error { + transactionErr := s.PurchaseRepo.DB().WithContext(c.Context()).Transaction(func(tx *gorm.DB) error { repoTx := rPurchase.NewPurchaseRepository(tx) pwRepoTx := rProductWarehouse.NewProductWarehouseRepository(tx) @@ -727,7 +781,7 @@ func (s *purchaseService) ReceiveProducts(c *fiber.Ctx, id uint64, req *validati clearPW := false if prep.receivedQty > 0 { - pwID, err := pwRepoTx.EnsureProductWarehouse(ctx, uint(item.ProductId), prep.warehouseID, purchase.CreatedBy) + pwID, err := pwRepoTx.EnsureProductWarehouse(c.Context(), uint(item.ProductId), prep.warehouseID, purchase.CreatedBy) if err != nil { return err } @@ -764,23 +818,23 @@ func (s *purchaseService) ReceiveProducts(c *fiber.Ctx, id uint64, req *validati updates = append(updates, update) } - if err := repoTx.UpdateReceivingDetails(ctx, purchase.Id, updates); err != nil { + if err := repoTx.UpdateReceivingDetails(c.Context(), purchase.Id, updates); err != nil { return err } - if err := pwRepoTx.AdjustQuantities(ctx, deltas, nil); err != nil { + if err := pwRepoTx.AdjustQuantities(c.Context(), deltas, nil); err != nil { return err } - if err := pwRepoTx.CleanupEmpty(ctx, affected); err != nil { + if err := pwRepoTx.CleanupEmpty(c.Context(), affected); err != nil { return err } - if err := s.createPurchaseApproval(ctx, tx, purchase.Id, utils.PurchaseStepReceiving, receivingAction, actorID, req.Notes, true); err != nil { + if err := s.createPurchaseApproval(c.Context(), tx, purchase.Id, utils.PurchaseStepReceiving, receivingAction, actorID, req.Notes, true); err != nil { return err } - if err := s.createPurchaseApproval(ctx, tx, purchase.Id, utils.PurchaseStepCompleted, completedAction, actorID, req.Notes, true); err != nil { + if err := s.createPurchaseApproval(c.Context(), tx, purchase.Id, utils.PurchaseStepCompleted, completedAction, actorID, req.Notes, true); err != nil { return err } @@ -794,11 +848,11 @@ func (s *purchaseService) ReceiveProducts(c *fiber.Ctx, id uint64, req *validati return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to record purchase receiving") } - updated, err := s.PurchaseRepo.GetByIDWithRelations(ctx, purchase.Id) + updated, err := s.PurchaseRepo.GetByID(c.Context(), purchase.Id, s.withRelations) if err != nil { return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to load purchase ") } - if err := s.attachLatestApproval(ctx, updated); err != nil { + if err := s.attachLatestApproval(c.Context(), updated); err != nil { s.Log.Warnf("Unable to attach latest approval for purchase %d: %+v", updated.Id, err) } @@ -808,24 +862,24 @@ func (s *purchaseService) ReceiveProducts(c *fiber.Ctx, id uint64, req *validati payload := ExpenseReceivingPayload{ PurchaseItemID: prep.item.Id, ProductID: prep.item.ProductId, - WarehouseID: uint64(prep.warehouseID), + WarehouseID: uint(prep.warehouseID), ReceivedQty: prep.receivedQty, ReceivedDate: &date, } receivingPayloads = append(receivingPayloads, payload) } - s.notifyExpenseItemsReceived(ctx, purchase.Id, receivingPayloads) + s.notifyExpenseItemsReceived(c.Context(), purchase.Id, receivingPayloads) return updated, nil } -func (s *purchaseService) DeleteItems(c *fiber.Ctx, id uint64, req *validation.DeletePurchaseItemsRequest) (*entity.Purchase, error) { +func (s *purchaseService) DeleteItems(c *fiber.Ctx, id uint, req *validation.DeletePurchaseItemsRequest) (*entity.Purchase, error) { if err := s.Validate.Struct(req); err != nil { return nil, err } ctx := c.Context() - purchase, err := s.PurchaseRepo.GetByIDWithRelations(ctx, id) + purchase, err := s.PurchaseRepo.GetByID(ctx, id, s.withRelations) if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, fiber.NewError(fiber.StatusNotFound, "Purchase not found") @@ -844,19 +898,16 @@ func (s *purchaseService) DeleteItems(c *fiber.Ctx, id uint64, req *validation.D return nil, fiber.NewError(fiber.StatusBadRequest, "Purchase has no items to delete") } - requested := make(map[uint64]struct{}, len(req.ItemIDs)) + requested := make(map[uint]struct{}, len(req.ItemIDs)) for _, id := range req.ItemIDs { requested[id] = struct{}{} } - toDelete := make([]uint64, 0, len(req.ItemIDs)) + toDelete := make([]uint, 0, len(req.ItemIDs)) var remainingTotal float64 for _, item := range purchase.Items { if _, ok := requested[item.Id]; ok { - if item.TotalQty > 0 || item.TotalUsed > 0 { - return nil, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Cannot delete item %d because it already has receiving data", item.Id)) - } toDelete = append(toDelete, item.Id) } else { remainingTotal += item.TotalPrice @@ -895,7 +946,7 @@ func (s *purchaseService) DeleteItems(c *fiber.Ctx, id uint64, req *validation.D s.notifyExpenseItemsDeleted(ctx, purchase.Id, toDelete) } - updated, err := s.PurchaseRepo.GetByIDWithRelations(ctx, purchase.Id) + updated, err := s.PurchaseRepo.GetByID(ctx, purchase.Id, s.withRelations) if err != nil { return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to load purchase") } @@ -906,13 +957,13 @@ func (s *purchaseService) DeleteItems(c *fiber.Ctx, id uint64, req *validation.D return updated, nil } -func (s *purchaseService) DeletePurchase(c *fiber.Ctx, id uint64) error { +func (s *purchaseService) DeletePurchase(c *fiber.Ctx, id uint) error { if id == 0 { return fiber.NewError(fiber.StatusBadRequest, "Invalid purchase id") } ctx := c.Context() - purchase, err := s.PurchaseRepo.GetByIDWithRelations(ctx, id) + purchase, err := s.PurchaseRepo.GetByID(ctx, id, s.withRelations) if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return fiber.NewError(fiber.StatusNotFound, "Purchase not found") @@ -920,7 +971,7 @@ func (s *purchaseService) DeletePurchase(c *fiber.Ctx, id uint64) error { return fiber.NewError(fiber.StatusInternalServerError, "Failed to get purchase") } - itemIDs := make([]uint64, 0, len(purchase.Items)) + itemIDs := make([]uint, 0, len(purchase.Items)) for _, item := range purchase.Items { itemIDs = append(itemIDs, item.Id) } @@ -945,105 +996,13 @@ func (s *purchaseService) DeletePurchase(c *fiber.Ctx, id uint64) error { } if len(itemIDs) > 0 { - s.notifyExpenseItemsDeleted(ctx, uint64(id), itemIDs) + s.notifyExpenseItemsDeleted(ctx, uint(id), itemIDs) } return nil } -func (s *purchaseService) createPurchaseApproval( - ctx context.Context, - db *gorm.DB, - purchaseID uint64, - step approvalutils.ApprovalStep, - action entity.ApprovalAction, - actorID uint, - notes *string, - allowDuplicate bool, -) error { - if purchaseID == 0 { - return fiber.NewError(fiber.StatusBadRequest, "Purchase is invalid for approval") - } - if actorID == 0 { - actorID = 1 - } - - svc := s.approvalServiceForDB(db) - - modifier := func(db *gorm.DB) *gorm.DB { - return db.Where("step_number = ?", uint16(step)) - } - - latest, err := svc.LatestByTarget(ctx, utils.ApprovalWorkflowPurchase, uint(purchaseID), modifier) - if err != nil { - return err - } - - if !allowDuplicate && latest != nil && - latest.Action != nil && - *latest.Action == action { - return nil - } - - actionCopy := action - _, err = svc.CreateApproval(ctx, utils.ApprovalWorkflowPurchase, uint(purchaseID), step, &actionCopy, actorID, notes) - return err -} - -func (s *purchaseService) approvalServiceForDB(db *gorm.DB) commonSvc.ApprovalService { - if db != nil { - return commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(db)) - } - if s.ApprovalSvc != nil { - return s.ApprovalSvc - } - return commonSvc.NewApprovalService(s.ApprovalRepo) -} - -func (s *purchaseService) attachLatestApprovals(ctx context.Context, items []entity.Purchase) error { - if len(items) == 0 || s.ApprovalSvc == nil { - return nil - } - - ids := make([]uint, 0, len(items)) - visited := make(map[uint64]struct{}, len(items)) - for _, item := range items { - if item.Id == 0 { - continue - } - if _, ok := visited[item.Id]; ok { - continue - } - visited[item.Id] = struct{}{} - ids = append(ids, uint(item.Id)) - } - - if len(ids) == 0 { - return nil - } - - latestMap, err := s.ApprovalSvc.LatestByTargets(ctx, utils.ApprovalWorkflowPurchase, ids, func(db *gorm.DB) *gorm.DB { - return db.Preload("ActionUser") - }) - if err != nil { - return err - } - - for i := range items { - if items[i].Id == 0 { - continue - } - if approval, ok := latestMap[uint(items[i].Id)]; ok { - items[i].LatestApproval = approval - } else { - items[i].LatestApproval = nil - } - } - - return nil -} - -func (s *purchaseService) notifyExpenseItemsCreated(ctx context.Context, purchaseID uint64, items []entity.PurchaseItem) { +func (s *purchaseService) notifyExpenseItemsCreated(ctx context.Context, purchaseID uint, items []entity.PurchaseItem) { if s.ExpenseBridge == nil || purchaseID == 0 || len(items) == 0 { return } @@ -1052,7 +1011,7 @@ func (s *purchaseService) notifyExpenseItemsCreated(ctx context.Context, purchas } } -func (s *purchaseService) notifyExpenseItemsReceived(ctx context.Context, purchaseID uint64, payloads []ExpenseReceivingPayload) { +func (s *purchaseService) notifyExpenseItemsReceived(ctx context.Context, purchaseID uint, payloads []ExpenseReceivingPayload) { if s.ExpenseBridge == nil || purchaseID == 0 || len(payloads) == 0 { return } @@ -1061,7 +1020,7 @@ func (s *purchaseService) notifyExpenseItemsReceived(ctx context.Context, purcha } } -func (s *purchaseService) notifyExpenseItemsDeleted(ctx context.Context, purchaseID uint64, itemIDs []uint64) { +func (s *purchaseService) notifyExpenseItemsDeleted(ctx context.Context, purchaseID uint, itemIDs []uint) { if s.ExpenseBridge == nil || purchaseID == 0 || len(itemIDs) == 0 { return } @@ -1070,14 +1029,6 @@ func (s *purchaseService) notifyExpenseItemsDeleted(ctx context.Context, purchas } } -func actorIDFromContext(c *fiber.Ctx) (uint, error) { - user, ok := authmiddleware.AuthenticatedUser(c) - if !ok || user == nil || user.Id == 0 { - return 0, fiber.NewError(fiber.StatusUnauthorized, "Please authenticate") - } - return user.Id, nil -} - func (s *purchaseService) buildStaffAdjustmentPayload( ctx context.Context, purchase *entity.Purchase, @@ -1088,7 +1039,7 @@ func (s *purchaseService) buildStaffAdjustmentPayload( return nil, fiber.NewError(fiber.StatusBadRequest, "Items must not be empty") } - requestItems := make(map[uint64]validation.StaffPurchaseApprovalItem, len(req.Items)) + requestItems := make(map[uint]validation.StaffPurchaseApprovalItem, len(req.Items)) newPayloads := make([]validation.StaffPurchaseApprovalItem, 0) for _, item := range req.Items { @@ -1111,7 +1062,7 @@ func (s *purchaseService) buildStaffAdjustmentPayload( existingCombos[key] = struct{}{} } - allowedWarehouses := make(map[uint64]struct{}, len(purchase.Items)) + allowedWarehouses := make(map[uint]struct{}, len(purchase.Items)) for _, item := range purchase.Items { allowedWarehouses[item.WarehouseId] = struct{}{} } @@ -1176,7 +1127,7 @@ func (s *purchaseService) buildStaffAdjustmentPayload( return nil, fiber.NewError(fiber.StatusBadRequest, "Found pricing data for items that do not belong to this purchase") } - productSupplierCache := make(map[uint64]bool) + productSupplierCache := make(map[uint]bool) newItems := make([]*entity.PurchaseItem, 0, len(newPayloads)) for _, payload := range newPayloads { @@ -1249,6 +1200,7 @@ func (s *purchaseService) buildStaffAdjustmentPayload( }, nil } +// ? helper func calculateTotalPrice(quantity float64, price float64, provided *float64, ref string) (float64, error) { if quantity <= 0 { return 0, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Quantity for %s must be greater than 0", ref)) @@ -1257,7 +1209,6 @@ func calculateTotalPrice(quantity float64, price float64, provided *float64, ref return 0, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Price for %s must be greater than 0", ref)) } - fmt.Println(price, quantity) expectedTotal := price * quantity if provided == nil { @@ -1291,6 +1242,7 @@ func (s *purchaseService) attachLatestApproval(ctx context.Context, item *entity func parseQueryDates(fromStr, toStr string) (*time.Time, *time.Time, error) { var fromPtr *time.Time var toPtr *time.Time + const queryDateLayout = "2006-01-02" if strings.TrimSpace(fromStr) != "" { parsed, err := time.Parse(queryDateLayout, fromStr) @@ -1317,24 +1269,86 @@ func parseQueryDates(fromStr, toStr string) (*time.Time, *time.Time, error) { return fromPtr, toPtr, nil } -func parseApprovalAction(status string) (*entity.ApprovalAction, bool, error) { - value := strings.TrimSpace(strings.ToUpper(status)) - if value == "" { - return nil, false, nil - } - - if value == "COMPLETED" { - return nil, true, nil - } - - action := entity.ApprovalAction(value) - switch action { - case entity.ApprovalActionApproved, - entity.ApprovalActionRejected, - entity.ApprovalActionCreated, - entity.ApprovalActionUpdated: - return &action, false, nil +func parseApprovalActionInput(raw string) (entity.ApprovalAction, error) { + value := strings.ToUpper(strings.TrimSpace(raw)) + switch value { + case string(entity.ApprovalActionApproved): + return entity.ApprovalActionApproved, nil + case string(entity.ApprovalActionRejected): + return entity.ApprovalActionRejected, nil default: - return nil, false, fiber.NewError(fiber.StatusBadRequest, "Invalid status filter") + return "", fiber.NewError(fiber.StatusBadRequest, "action must be APPROVED or REJECTED") } } + +func (s *purchaseService) rejectAndReload( + c *fiber.Ctx, + step approvalutils.ApprovalStep, + purchaseID uint, + actorID uint, + notes *string, +) (*entity.Purchase, error) { + + if err := s.createPurchaseApproval(c.Context(), nil, purchaseID, step, entity.ApprovalActionRejected, actorID, notes, false); err != nil { + return nil, err + } + + updated, err := s.PurchaseRepo.GetByID(c.Context(), purchaseID, s.withRelations) + if err != nil { + return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to load purchase") + } + if err := s.attachLatestApproval(c.Context(), updated); err != nil { + s.Log.Warnf("Unable to attach latest approval for purchase %d: %+v", updated.Id, err) + } + return updated, nil +} + +func (s *purchaseService) createPurchaseApproval( + ctx context.Context, + db *gorm.DB, + purchaseID uint, + step approvalutils.ApprovalStep, + action entity.ApprovalAction, + actorID uint, + notes *string, + allowDuplicate bool, +) error { + if purchaseID == 0 { + return fiber.NewError(fiber.StatusBadRequest, "Purchase is invalid for approval") + } + if actorID == 0 { + return fiber.NewError(fiber.StatusBadRequest, "ActorId is invalid for approval") + } + + var svc commonSvc.ApprovalService + switch { + case db != nil: + svc = commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(db)) + case s.ApprovalSvc != nil: + svc = s.ApprovalSvc + case s.PurchaseRepo != nil && s.PurchaseRepo.DB() != nil: + svc = commonSvc.NewApprovalService(commonRepo.NewApprovalRepository(s.PurchaseRepo.DB())) + } + if svc == nil { + return fiber.NewError(fiber.StatusInternalServerError, "Approval service not available") + } + + modifier := func(db *gorm.DB) *gorm.DB { + return db.Where("step_number = ?", uint16(step)) + } + + latest, err := svc.LatestByTarget(ctx, utils.ApprovalWorkflowPurchase, uint(purchaseID), modifier) + if err != nil { + return err + } + + if !allowDuplicate && latest != nil && + latest.Action != nil && + *latest.Action == action { + return nil + } + + actionCopy := action + _, err = svc.CreateApproval(ctx, utils.ApprovalWorkflowPurchase, uint(purchaseID), step, &actionCopy, actorID, notes) + return err +} diff --git a/internal/modules/purchases/validations/purchase.validation.go b/internal/modules/purchases/validations/purchase.validation.go index 4994a927..420b6c63 100644 --- a/internal/modules/purchases/validations/purchase.validation.go +++ b/internal/modules/purchases/validations/purchase.validation.go @@ -14,26 +14,28 @@ type CreatePurchaseRequest struct { } type StaffPurchaseApprovalItem struct { - PurchaseItemID uint64 `json:"purchase_item_id,omitempty" validate:"omitempty,gt=0"` + PurchaseItemID uint `json:"purchase_item_id,omitempty" validate:"omitempty,gt=0"` // For new items (no purchase_item_id), product_id is required. - ProductID uint64 `json:"product_id,omitempty" validate:"required_without=PurchaseItemID,omitempty,gt=0"` - WarehouseID uint64 `json:"warehouse_id,omitempty" validate:"required_without=PurchaseItemID,omitempty,gt=0"` + ProductID uint `json:"product_id,omitempty" validate:"required_without=PurchaseItemID,omitempty,gt=0"` + WarehouseID uint `json:"warehouse_id,omitempty" validate:"required_without=PurchaseItemID,omitempty,gt=0"` Qty *float64 `json:"qty,omitempty" validate:"required_without=PurchaseItemID,omitempty,gt=0"` Price float64 `json:"price" validate:"required,gt=0"` TotalPrice float64 `json:"total_price" validate:"required,gt=0"` } type ApproveStaffPurchaseRequest struct { - Items []StaffPurchaseApprovalItem `json:"items" validate:"required,min=1,dive"` - Notes *string `json:"notes,omitempty" validate:"omitempty,max=500"` + Action string `json:"action" validate:"required,oneof=APPROVED REJECTED"` + Items []StaffPurchaseApprovalItem `json:"items,omitempty" validate:"omitempty,min=1,dive"` + Notes *string `json:"notes,omitempty" validate:"omitempty,max=500"` } type ApproveManagerPurchaseRequest struct { - Notes *string `json:"notes,omitempty" validate:"omitempty,max=500"` + Action string `json:"action" validate:"required,oneof=APPROVED REJECTED"` + Notes *string `json:"notes,omitempty" validate:"omitempty,max=500"` } type ReceivePurchaseItemRequest struct { - PurchaseItemID uint64 `json:"purchase_item_id" validate:"required,gt=0"` + PurchaseItemID uint `json:"purchase_item_id" validate:"required,gt=0"` WarehouseID *uint `json:"warehouse_id" validate:"omitempty,gt=0"` ReceivedDate string `json:"received_date" validate:"required,datetime=2006-01-02"` TravelNumber *string `json:"travel_number" validate:"omitempty,max=100"` @@ -43,21 +45,23 @@ type ReceivePurchaseItemRequest struct { } type ReceivePurchaseRequest struct { - Items []ReceivePurchaseItemRequest `json:"items" validate:"required,min=1,dive"` - Notes *string `json:"notes,omitempty" validate:"omitempty,max=500"` + Action string `json:"action" validate:"required,oneof=APPROVED REJECTED"` + Items []ReceivePurchaseItemRequest `json:"items,omitempty" validate:"omitempty,min=1,dive"` + Notes *string `json:"notes,omitempty" validate:"omitempty,max=500"` } type DeletePurchaseItemsRequest struct { - ItemIDs []uint64 `json:"item_ids" validate:"required,min=1,dive,gt=0"` + ItemIDs []uint `json:"item_ids" validate:"required,min=1,dive,gt=0"` } -type PurchaseQuery struct { - Page int `query:"page" validate:"omitempty,number,min=1"` - Limit int `query:"limit" validate:"omitempty,number,min=1,max=100"` - SupplierID uint `query:"supplier_id" validate:"omitempty,gt=0"` - Search string `query:"search" validate:"omitempty,max=100"` - PrNumber string `query:"pr_number" validate:"omitempty,max=50"` - CreatedFrom string `query:"created_from" validate:"omitempty,datetime=2006-01-02"` - CreatedTo string `query:"created_to" validate:"omitempty,datetime=2006-01-02"` - Status string `query:"status" validate:"omitempty,oneof=CREATED UPDATED APPROVED REJECTED COMPLETED"` +type Query struct { + Page int `query:"page" validate:"omitempty,number,min=1"` + Limit int `query:"limit" validate:"omitempty,number,min=1,max=100"` + SupplierID uint `query:"supplier_id" validate:"omitempty,gt=0"` + AreaID uint `query:"area_id" validate:"omitempty,gt=0"` + LocationID uint `query:"location_id" validate:"omitempty,gt=0"` + ProductCategoryID uint `query:"product_category_id" validate:"omitempty,gt=0"` + Search string `query:"search" validate:"omitempty,max=100"` + CreatedFrom string `query:"created_from" validate:"omitempty,datetime=2006-01-02"` + CreatedTo string `query:"created_to" validate:"omitempty,datetime=2006-01-02"` }