diff --git a/gen/finance/v1/uom.pb.go b/gen/finance/v1/uom.pb.go index 68fa67f..b65d065 100644 --- a/gen/finance/v1/uom.pb.go +++ b/gen/finance/v1/uom.pb.go @@ -24,67 +24,6 @@ const ( _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) -// UOMCategory represents the category of a unit of measure. -type UOMCategory int32 - -const ( - // Default unspecified value - used as "no filter" in list/export requests. - UOMCategory_UOM_CATEGORY_UNSPECIFIED UOMCategory = 0 - // Weight-based units (e.g., KG, GR, TON). - UOMCategory_UOM_CATEGORY_WEIGHT UOMCategory = 1 - // Length-based units (e.g., MTR, CM, YARD). - UOMCategory_UOM_CATEGORY_LENGTH UOMCategory = 2 - // Volume-based units (e.g., LTR, ML). - UOMCategory_UOM_CATEGORY_VOLUME UOMCategory = 3 - // Quantity-based units (e.g., PCS, BOX, SET). - UOMCategory_UOM_CATEGORY_QUANTITY UOMCategory = 4 -) - -// Enum value maps for UOMCategory. -var ( - UOMCategory_name = map[int32]string{ - 0: "UOM_CATEGORY_UNSPECIFIED", - 1: "UOM_CATEGORY_WEIGHT", - 2: "UOM_CATEGORY_LENGTH", - 3: "UOM_CATEGORY_VOLUME", - 4: "UOM_CATEGORY_QUANTITY", - } - UOMCategory_value = map[string]int32{ - "UOM_CATEGORY_UNSPECIFIED": 0, - "UOM_CATEGORY_WEIGHT": 1, - "UOM_CATEGORY_LENGTH": 2, - "UOM_CATEGORY_VOLUME": 3, - "UOM_CATEGORY_QUANTITY": 4, - } -) - -func (x UOMCategory) Enum() *UOMCategory { - p := new(UOMCategory) - *p = x - return p -} - -func (x UOMCategory) String() string { - return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) -} - -func (UOMCategory) Descriptor() protoreflect.EnumDescriptor { - return file_finance_v1_uom_proto_enumTypes[0].Descriptor() -} - -func (UOMCategory) Type() protoreflect.EnumType { - return &file_finance_v1_uom_proto_enumTypes[0] -} - -func (x UOMCategory) Number() protoreflect.EnumNumber { - return protoreflect.EnumNumber(x) -} - -// Deprecated: Use UOMCategory.Descriptor instead. -func (UOMCategory) EnumDescriptor() ([]byte, []int) { - return file_finance_v1_uom_proto_rawDescGZIP(), []int{0} -} - // ActiveFilter represents filter options for is_active field. type ActiveFilter int32 @@ -122,11 +61,11 @@ func (x ActiveFilter) String() string { } func (ActiveFilter) Descriptor() protoreflect.EnumDescriptor { - return file_finance_v1_uom_proto_enumTypes[1].Descriptor() + return file_finance_v1_uom_proto_enumTypes[0].Descriptor() } func (ActiveFilter) Type() protoreflect.EnumType { - return &file_finance_v1_uom_proto_enumTypes[1] + return &file_finance_v1_uom_proto_enumTypes[0] } func (x ActiveFilter) Number() protoreflect.EnumNumber { @@ -135,7 +74,7 @@ func (x ActiveFilter) Number() protoreflect.EnumNumber { // Deprecated: Use ActiveFilter.Descriptor instead. func (ActiveFilter) EnumDescriptor() ([]byte, []int) { - return file_finance_v1_uom_proto_rawDescGZIP(), []int{1} + return file_finance_v1_uom_proto_rawDescGZIP(), []int{0} } // UOM represents a Unit of Measure entity. @@ -147,16 +86,20 @@ type UOM struct { UomCode string `protobuf:"bytes,2,opt,name=uom_code,json=uomCode,proto3" json:"uom_code,omitempty"` // Display name (e.g., "Kilogram", "Meter", "Pieces"). UomName string `protobuf:"bytes,3,opt,name=uom_name,json=uomName,proto3" json:"uom_name,omitempty"` - // Category of the UOM. - UomCategory UOMCategory `protobuf:"varint,4,opt,name=uom_category,json=uomCategory,proto3,enum=finance.v1.UOMCategory" json:"uom_category,omitempty"` // Optional description. Description string `protobuf:"bytes,5,opt,name=description,proto3" json:"description,omitempty"` // Whether the UOM is active. IsActive bool `protobuf:"varint,6,opt,name=is_active,json=isActive,proto3" json:"is_active,omitempty"` // Audit information. - Audit *v1.AuditInfo `protobuf:"bytes,7,opt,name=audit,proto3" json:"audit,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Audit *v1.AuditInfo `protobuf:"bytes,7,opt,name=audit,proto3" json:"audit,omitempty"` + // Category ID (FK to mst_uom_category). + UomCategoryId string `protobuf:"bytes,8,opt,name=uom_category_id,json=uomCategoryId,proto3" json:"uom_category_id,omitempty"` + // Category code (denormalized for display, e.g., "WEIGHT"). + UomCategoryCode string `protobuf:"bytes,9,opt,name=uom_category_code,json=uomCategoryCode,proto3" json:"uom_category_code,omitempty"` + // Category name (denormalized for display, e.g., "Weight"). + UomCategoryName string `protobuf:"bytes,10,opt,name=uom_category_name,json=uomCategoryName,proto3" json:"uom_category_name,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *UOM) Reset() { @@ -210,13 +153,6 @@ func (x *UOM) GetUomName() string { return "" } -func (x *UOM) GetUomCategory() UOMCategory { - if x != nil { - return x.UomCategory - } - return UOMCategory_UOM_CATEGORY_UNSPECIFIED -} - func (x *UOM) GetDescription() string { if x != nil { return x.Description @@ -238,6 +174,27 @@ func (x *UOM) GetAudit() *v1.AuditInfo { return nil } +func (x *UOM) GetUomCategoryId() string { + if x != nil { + return x.UomCategoryId + } + return "" +} + +func (x *UOM) GetUomCategoryCode() string { + if x != nil { + return x.UomCategoryCode + } + return "" +} + +func (x *UOM) GetUomCategoryName() string { + if x != nil { + return x.UomCategoryName + } + return "" +} + // CreateUOMRequest is the request for creating a new UOM. type CreateUOMRequest struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -246,10 +203,10 @@ type CreateUOMRequest struct { UomCode string `protobuf:"bytes,1,opt,name=uom_code,json=uomCode,proto3" json:"uom_code,omitempty"` // Display name (1-100 chars). UomName string `protobuf:"bytes,2,opt,name=uom_name,json=uomName,proto3" json:"uom_name,omitempty"` - // Category (required, cannot be UNSPECIFIED). - UomCategory UOMCategory `protobuf:"varint,3,opt,name=uom_category,json=uomCategory,proto3,enum=finance.v1.UOMCategory" json:"uom_category,omitempty"` // Optional description (max 500 chars). - Description string `protobuf:"bytes,4,opt,name=description,proto3" json:"description,omitempty"` + Description string `protobuf:"bytes,4,opt,name=description,proto3" json:"description,omitempty"` + // Category ID (UUID, required - FK to mst_uom_category). + UomCategoryId string `protobuf:"bytes,5,opt,name=uom_category_id,json=uomCategoryId,proto3" json:"uom_category_id,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -298,16 +255,16 @@ func (x *CreateUOMRequest) GetUomName() string { return "" } -func (x *CreateUOMRequest) GetUomCategory() UOMCategory { +func (x *CreateUOMRequest) GetDescription() string { if x != nil { - return x.UomCategory + return x.Description } - return UOMCategory_UOM_CATEGORY_UNSPECIFIED + return "" } -func (x *CreateUOMRequest) GetDescription() string { +func (x *CreateUOMRequest) GetUomCategoryId() string { if x != nil { - return x.Description + return x.UomCategoryId } return "" } @@ -477,13 +434,12 @@ type UpdateUOMRequest struct { // New display name (optional, 1-100 chars if provided). // Empty string means no change. UomName *string `protobuf:"bytes,2,opt,name=uom_name,json=uomName,proto3,oneof" json:"uom_name,omitempty"` - // New category (optional, cannot be UNSPECIFIED if provided). - // Use has_uom_category to check if this field is set. - UomCategory *UOMCategory `protobuf:"varint,3,opt,name=uom_category,json=uomCategory,proto3,enum=finance.v1.UOMCategory,oneof" json:"uom_category,omitempty"` // New description (optional, max 500 chars). Description *string `protobuf:"bytes,4,opt,name=description,proto3,oneof" json:"description,omitempty"` // New active status (optional). - IsActive *bool `protobuf:"varint,5,opt,name=is_active,json=isActive,proto3,oneof" json:"is_active,omitempty"` + IsActive *bool `protobuf:"varint,5,opt,name=is_active,json=isActive,proto3,oneof" json:"is_active,omitempty"` + // New category ID (optional, UUID format). + UomCategoryId *string `protobuf:"bytes,6,opt,name=uom_category_id,json=uomCategoryId,proto3,oneof" json:"uom_category_id,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -532,13 +488,6 @@ func (x *UpdateUOMRequest) GetUomName() string { return "" } -func (x *UpdateUOMRequest) GetUomCategory() UOMCategory { - if x != nil && x.UomCategory != nil { - return *x.UomCategory - } - return UOMCategory_UOM_CATEGORY_UNSPECIFIED -} - func (x *UpdateUOMRequest) GetDescription() string { if x != nil && x.Description != nil { return *x.Description @@ -553,6 +502,13 @@ func (x *UpdateUOMRequest) GetIsActive() bool { return false } +func (x *UpdateUOMRequest) GetUomCategoryId() string { + if x != nil && x.UomCategoryId != nil { + return *x.UomCategoryId + } + return "" +} + // UpdateUOMResponse is the response for updating a UOM. type UpdateUOMResponse struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -709,17 +665,16 @@ type ListUOMsRequest struct { PageSize int32 `protobuf:"varint,2,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"` // Search query (searches in code, name, description). Search string `protobuf:"bytes,3,opt,name=search,proto3" json:"search,omitempty"` - // Filter by category. - // UOM_CATEGORY_UNSPECIFIED (0) means "show all categories" (no filter). - Category UOMCategory `protobuf:"varint,4,opt,name=category,proto3,enum=finance.v1.UOMCategory" json:"category,omitempty"` // Filter by active status. // ACTIVE_FILTER_UNSPECIFIED (0) = show all, ACTIVE_FILTER_ACTIVE (1) = only active, // ACTIVE_FILTER_INACTIVE (2) = only inactive. ActiveFilter ActiveFilter `protobuf:"varint,5,opt,name=active_filter,json=activeFilter,proto3,enum=finance.v1.ActiveFilter" json:"active_filter,omitempty"` - // Sort field: "code", "name", "created_at" (default: "code"). + // Sort field: "code", "name", "category", "created_at" (default: "code"). SortBy string `protobuf:"bytes,6,opt,name=sort_by,json=sortBy,proto3" json:"sort_by,omitempty"` // Sort order: "asc", "desc" (default: "asc"). - SortOrder string `protobuf:"bytes,7,opt,name=sort_order,json=sortOrder,proto3" json:"sort_order,omitempty"` + SortOrder string `protobuf:"bytes,7,opt,name=sort_order,json=sortOrder,proto3" json:"sort_order,omitempty"` + // Filter by category ID (empty = no filter). + UomCategoryId string `protobuf:"bytes,8,opt,name=uom_category_id,json=uomCategoryId,proto3" json:"uom_category_id,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -775,13 +730,6 @@ func (x *ListUOMsRequest) GetSearch() string { return "" } -func (x *ListUOMsRequest) GetCategory() UOMCategory { - if x != nil { - return x.Category - } - return UOMCategory_UOM_CATEGORY_UNSPECIFIED -} - func (x *ListUOMsRequest) GetActiveFilter() ActiveFilter { if x != nil { return x.ActiveFilter @@ -803,6 +751,13 @@ func (x *ListUOMsRequest) GetSortOrder() string { return "" } +func (x *ListUOMsRequest) GetUomCategoryId() string { + if x != nil { + return x.UomCategoryId + } + return "" +} + // ListUOMsResponse is the response for listing UOMs. type ListUOMsResponse struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -870,13 +825,12 @@ func (x *ListUOMsResponse) GetPagination() *v1.PaginationResponse { // ExportUOMsRequest is the request for exporting UOMs to Excel. type ExportUOMsRequest struct { state protoimpl.MessageState `protogen:"open.v1"` - // Filter by category. - // UOM_CATEGORY_UNSPECIFIED (0) means "export all categories". - Category UOMCategory `protobuf:"varint,1,opt,name=category,proto3,enum=finance.v1.UOMCategory" json:"category,omitempty"` // Filter by active status. // ACTIVE_FILTER_UNSPECIFIED (0) = export all, ACTIVE_FILTER_ACTIVE (1) = only active, // ACTIVE_FILTER_INACTIVE (2) = only inactive. - ActiveFilter ActiveFilter `protobuf:"varint,2,opt,name=active_filter,json=activeFilter,proto3,enum=finance.v1.ActiveFilter" json:"active_filter,omitempty"` + ActiveFilter ActiveFilter `protobuf:"varint,2,opt,name=active_filter,json=activeFilter,proto3,enum=finance.v1.ActiveFilter" json:"active_filter,omitempty"` + // Filter by category ID (empty = export all categories). + UomCategoryId string `protobuf:"bytes,3,opt,name=uom_category_id,json=uomCategoryId,proto3" json:"uom_category_id,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -911,18 +865,18 @@ func (*ExportUOMsRequest) Descriptor() ([]byte, []int) { return file_finance_v1_uom_proto_rawDescGZIP(), []int{11} } -func (x *ExportUOMsRequest) GetCategory() UOMCategory { +func (x *ExportUOMsRequest) GetActiveFilter() ActiveFilter { if x != nil { - return x.Category + return x.ActiveFilter } - return UOMCategory_UOM_CATEGORY_UNSPECIFIED + return ActiveFilter_ACTIVE_FILTER_UNSPECIFIED } -func (x *ExportUOMsRequest) GetActiveFilter() ActiveFilter { +func (x *ExportUOMsRequest) GetUomCategoryId() string { if x != nil { - return x.ActiveFilter + return x.UomCategoryId } - return ActiveFilter_ACTIVE_FILTER_UNSPECIFIED + return "" } // ExportUOMsResponse is the response containing the Excel file. @@ -1315,20 +1269,23 @@ var File_finance_v1_uom_proto protoreflect.FileDescriptor const file_finance_v1_uom_proto_rawDesc = "" + "\n" + "\x14finance/v1/uom.proto\x12\n" + - "finance.v1\x1a\x1bbuf/validate/validate.proto\x1a\x16common/v1/common.proto\x1a\x1cgoogle/api/annotations.proto\"\xf9\x01\n" + + "finance.v1\x1a\x1bbuf/validate/validate.proto\x1a\x16common/v1/common.proto\x1a\x1cgoogle/api/annotations.proto\"\xd1\x02\n" + "\x03UOM\x12\x15\n" + "\x06uom_id\x18\x01 \x01(\tR\x05uomId\x12\x19\n" + "\buom_code\x18\x02 \x01(\tR\auomCode\x12\x19\n" + - "\buom_name\x18\x03 \x01(\tR\auomName\x12:\n" + - "\fuom_category\x18\x04 \x01(\x0e2\x17.finance.v1.UOMCategoryR\vuomCategory\x12 \n" + + "\buom_name\x18\x03 \x01(\tR\auomName\x12 \n" + "\vdescription\x18\x05 \x01(\tR\vdescription\x12\x1b\n" + "\tis_active\x18\x06 \x01(\bR\bisActive\x12*\n" + - "\x05audit\x18\a \x01(\v2\x14.common.v1.AuditInfoR\x05audit\"\xe3\x01\n" + + "\x05audit\x18\a \x01(\v2\x14.common.v1.AuditInfoR\x05audit\x12&\n" + + "\x0fuom_category_id\x18\b \x01(\tR\ruomCategoryId\x12*\n" + + "\x11uom_category_code\x18\t \x01(\tR\x0fuomCategoryCode\x12*\n" + + "\x11uom_category_name\x18\n" + + " \x01(\tR\x0fuomCategoryNameJ\x04\b\x04\x10\x05R\fuom_category\"\xe3\x01\n" + "\x10CreateUOMRequest\x127\n" + "\buom_code\x18\x01 \x01(\tB\x1c\xbaH\x19r\x17\x10\x01\x18\x142\x11^[A-Z][A-Z0-9_]*$R\auomCode\x12$\n" + - "\buom_name\x18\x02 \x01(\tB\t\xbaH\x06r\x04\x10\x01\x18dR\auomName\x12D\n" + - "\fuom_category\x18\x03 \x01(\x0e2\x17.finance.v1.UOMCategoryB\b\xbaH\x05\x82\x01\x02 \x00R\vuomCategory\x12*\n" + - "\vdescription\x18\x04 \x01(\tB\b\xbaH\x05r\x03\x18\xf4\x03R\vdescription\"e\n" + + "\buom_name\x18\x02 \x01(\tB\t\xbaH\x06r\x04\x10\x01\x18dR\auomName\x12*\n" + + "\vdescription\x18\x04 \x01(\tB\b\xbaH\x05r\x03\x18\xf4\x03R\vdescription\x120\n" + + "\x0fuom_category_id\x18\x05 \x01(\tB\b\xbaH\x05r\x03\xb0\x01\x01R\ruomCategoryIdJ\x04\b\x03\x10\x04R\fuom_category\"e\n" + "\x11CreateUOMResponse\x12+\n" + "\x04base\x18\x01 \x01(\v2\x17.common.v1.BaseResponseR\x04base\x12#\n" + "\x04data\x18\x02 \x01(\v2\x0f.finance.v1.UOMR\x04data\"0\n" + @@ -1336,44 +1293,44 @@ const file_finance_v1_uom_proto_rawDesc = "" + "\x06uom_id\x18\x01 \x01(\tB\b\xbaH\x05r\x03\xb0\x01\x01R\x05uomId\"b\n" + "\x0eGetUOMResponse\x12+\n" + "\x04base\x18\x01 \x01(\v2\x17.common.v1.BaseResponseR\x04base\x12#\n" + - "\x04data\x18\x02 \x01(\v2\x0f.finance.v1.UOMR\x04data\"\xb6\x02\n" + + "\x04data\x18\x02 \x01(\v2\x0f.finance.v1.UOMR\x04data\"\xaf\x02\n" + "\x10UpdateUOMRequest\x12\x1f\n" + "\x06uom_id\x18\x01 \x01(\tB\b\xbaH\x05r\x03\xb0\x01\x01R\x05uomId\x12'\n" + - "\buom_name\x18\x02 \x01(\tB\a\xbaH\x04r\x02\x18dH\x00R\auomName\x88\x01\x01\x12I\n" + - "\fuom_category\x18\x03 \x01(\x0e2\x17.finance.v1.UOMCategoryB\b\xbaH\x05\x82\x01\x02 \x00H\x01R\vuomCategory\x88\x01\x01\x12/\n" + - "\vdescription\x18\x04 \x01(\tB\b\xbaH\x05r\x03\x18\xf4\x03H\x02R\vdescription\x88\x01\x01\x12 \n" + - "\tis_active\x18\x05 \x01(\bH\x03R\bisActive\x88\x01\x01B\v\n" + - "\t_uom_nameB\x0f\n" + - "\r_uom_categoryB\x0e\n" + + "\buom_name\x18\x02 \x01(\tB\a\xbaH\x04r\x02\x18dH\x00R\auomName\x88\x01\x01\x12/\n" + + "\vdescription\x18\x04 \x01(\tB\b\xbaH\x05r\x03\x18\xf4\x03H\x01R\vdescription\x88\x01\x01\x12 \n" + + "\tis_active\x18\x05 \x01(\bH\x02R\bisActive\x88\x01\x01\x12+\n" + + "\x0fuom_category_id\x18\x06 \x01(\tH\x03R\ruomCategoryId\x88\x01\x01B\v\n" + + "\t_uom_nameB\x0e\n" + "\f_descriptionB\f\n" + "\n" + - "_is_active\"e\n" + + "_is_activeB\x12\n" + + "\x10_uom_category_idJ\x04\b\x03\x10\x04R\fuom_category\"e\n" + "\x11UpdateUOMResponse\x12+\n" + "\x04base\x18\x01 \x01(\v2\x17.common.v1.BaseResponseR\x04base\x12#\n" + "\x04data\x18\x02 \x01(\v2\x0f.finance.v1.UOMR\x04data\"3\n" + "\x10DeleteUOMRequest\x12\x1f\n" + "\x06uom_id\x18\x01 \x01(\tB\b\xbaH\x05r\x03\xb0\x01\x01R\x05uomId\"@\n" + "\x11DeleteUOMResponse\x12+\n" + - "\x04base\x18\x01 \x01(\v2\x17.common.v1.BaseResponseR\x04base\"\xd8\x02\n" + + "\x04base\x18\x01 \x01(\v2\x17.common.v1.BaseResponseR\x04base\"\xe5\x02\n" + "\x0fListUOMsRequest\x12\x1b\n" + "\x04page\x18\x01 \x01(\x05B\a\xbaH\x04\x1a\x02(\x01R\x04page\x12&\n" + "\tpage_size\x18\x02 \x01(\x05B\t\xbaH\x06\x1a\x04\x18d(\x01R\bpageSize\x12\x1f\n" + - "\x06search\x18\x03 \x01(\tB\a\xbaH\x04r\x02\x18dR\x06search\x123\n" + - "\bcategory\x18\x04 \x01(\x0e2\x17.finance.v1.UOMCategoryR\bcategory\x12=\n" + - "\ractive_filter\x18\x05 \x01(\x0e2\x18.finance.v1.ActiveFilterR\factiveFilter\x128\n" + - "\asort_by\x18\x06 \x01(\tB\x1f\xbaH\x1cr\x1aR\x00R\x04codeR\x04nameR\n" + + "\x06search\x18\x03 \x01(\tB\a\xbaH\x04r\x02\x18dR\x06search\x12=\n" + + "\ractive_filter\x18\x05 \x01(\x0e2\x18.finance.v1.ActiveFilterR\factiveFilter\x12B\n" + + "\asort_by\x18\x06 \x01(\tB)\xbaH&r$R\x00R\x04codeR\x04nameR\bcategoryR\n" + "created_atR\x06sortBy\x121\n" + "\n" + - "sort_order\x18\a \x01(\tB\x12\xbaH\x0fr\rR\x00R\x03ascR\x04descR\tsortOrder\"\xa3\x01\n" + + "sort_order\x18\a \x01(\tB\x12\xbaH\x0fr\rR\x00R\x03ascR\x04descR\tsortOrder\x12&\n" + + "\x0fuom_category_id\x18\b \x01(\tR\ruomCategoryIdJ\x04\b\x04\x10\x05R\bcategory\"\xa3\x01\n" + "\x10ListUOMsResponse\x12+\n" + "\x04base\x18\x01 \x01(\v2\x17.common.v1.BaseResponseR\x04base\x12#\n" + "\x04data\x18\x02 \x03(\v2\x0f.finance.v1.UOMR\x04data\x12=\n" + "\n" + "pagination\x18\x03 \x01(\v2\x1d.common.v1.PaginationResponseR\n" + - "pagination\"\x87\x01\n" + - "\x11ExportUOMsRequest\x123\n" + - "\bcategory\x18\x01 \x01(\x0e2\x17.finance.v1.UOMCategoryR\bcategory\x12=\n" + - "\ractive_filter\x18\x02 \x01(\x0e2\x18.finance.v1.ActiveFilterR\factiveFilter\"\x81\x01\n" + + "pagination\"\x8a\x01\n" + + "\x11ExportUOMsRequest\x12=\n" + + "\ractive_filter\x18\x02 \x01(\x0e2\x18.finance.v1.ActiveFilterR\factiveFilter\x12&\n" + + "\x0fuom_category_id\x18\x03 \x01(\tR\ruomCategoryIdJ\x04\b\x01\x10\x02R\bcategory\"\x81\x01\n" + "\x12ExportUOMsResponse\x12+\n" + "\x04base\x18\x01 \x01(\v2\x17.common.v1.BaseResponseR\x04base\x12!\n" + "\ffile_content\x18\x02 \x01(\fR\vfileContent\x12\x1b\n" + @@ -1398,13 +1355,7 @@ const file_finance_v1_uom_proto_rawDesc = "" + "\x18DownloadTemplateResponse\x12+\n" + "\x04base\x18\x01 \x01(\v2\x17.common.v1.BaseResponseR\x04base\x12!\n" + "\ffile_content\x18\x02 \x01(\fR\vfileContent\x12\x1b\n" + - "\tfile_name\x18\x03 \x01(\tR\bfileName*\x91\x01\n" + - "\vUOMCategory\x12\x1c\n" + - "\x18UOM_CATEGORY_UNSPECIFIED\x10\x00\x12\x17\n" + - "\x13UOM_CATEGORY_WEIGHT\x10\x01\x12\x17\n" + - "\x13UOM_CATEGORY_LENGTH\x10\x02\x12\x17\n" + - "\x13UOM_CATEGORY_VOLUME\x10\x03\x12\x19\n" + - "\x15UOM_CATEGORY_QUANTITY\x10\x04*c\n" + + "\tfile_name\x18\x03 \x01(\tR\bfileName*c\n" + "\fActiveFilter\x12\x1d\n" + "\x19ACTIVE_FILTER_UNSPECIFIED\x10\x00\x12\x18\n" + "\x14ACTIVE_FILTER_ACTIVE\x10\x01\x12\x1a\n" + @@ -1437,77 +1388,71 @@ func file_finance_v1_uom_proto_rawDescGZIP() []byte { return file_finance_v1_uom_proto_rawDescData } -var file_finance_v1_uom_proto_enumTypes = make([]protoimpl.EnumInfo, 2) +var file_finance_v1_uom_proto_enumTypes = make([]protoimpl.EnumInfo, 1) var file_finance_v1_uom_proto_msgTypes = make([]protoimpl.MessageInfo, 18) var file_finance_v1_uom_proto_goTypes = []any{ - (UOMCategory)(0), // 0: finance.v1.UOMCategory - (ActiveFilter)(0), // 1: finance.v1.ActiveFilter - (*UOM)(nil), // 2: finance.v1.UOM - (*CreateUOMRequest)(nil), // 3: finance.v1.CreateUOMRequest - (*CreateUOMResponse)(nil), // 4: finance.v1.CreateUOMResponse - (*GetUOMRequest)(nil), // 5: finance.v1.GetUOMRequest - (*GetUOMResponse)(nil), // 6: finance.v1.GetUOMResponse - (*UpdateUOMRequest)(nil), // 7: finance.v1.UpdateUOMRequest - (*UpdateUOMResponse)(nil), // 8: finance.v1.UpdateUOMResponse - (*DeleteUOMRequest)(nil), // 9: finance.v1.DeleteUOMRequest - (*DeleteUOMResponse)(nil), // 10: finance.v1.DeleteUOMResponse - (*ListUOMsRequest)(nil), // 11: finance.v1.ListUOMsRequest - (*ListUOMsResponse)(nil), // 12: finance.v1.ListUOMsResponse - (*ExportUOMsRequest)(nil), // 13: finance.v1.ExportUOMsRequest - (*ExportUOMsResponse)(nil), // 14: finance.v1.ExportUOMsResponse - (*ImportUOMsRequest)(nil), // 15: finance.v1.ImportUOMsRequest - (*ImportUOMsResponse)(nil), // 16: finance.v1.ImportUOMsResponse - (*ImportError)(nil), // 17: finance.v1.ImportError - (*DownloadTemplateRequest)(nil), // 18: finance.v1.DownloadTemplateRequest - (*DownloadTemplateResponse)(nil), // 19: finance.v1.DownloadTemplateResponse - (*v1.AuditInfo)(nil), // 20: common.v1.AuditInfo - (*v1.BaseResponse)(nil), // 21: common.v1.BaseResponse - (*v1.PaginationResponse)(nil), // 22: common.v1.PaginationResponse + (ActiveFilter)(0), // 0: finance.v1.ActiveFilter + (*UOM)(nil), // 1: finance.v1.UOM + (*CreateUOMRequest)(nil), // 2: finance.v1.CreateUOMRequest + (*CreateUOMResponse)(nil), // 3: finance.v1.CreateUOMResponse + (*GetUOMRequest)(nil), // 4: finance.v1.GetUOMRequest + (*GetUOMResponse)(nil), // 5: finance.v1.GetUOMResponse + (*UpdateUOMRequest)(nil), // 6: finance.v1.UpdateUOMRequest + (*UpdateUOMResponse)(nil), // 7: finance.v1.UpdateUOMResponse + (*DeleteUOMRequest)(nil), // 8: finance.v1.DeleteUOMRequest + (*DeleteUOMResponse)(nil), // 9: finance.v1.DeleteUOMResponse + (*ListUOMsRequest)(nil), // 10: finance.v1.ListUOMsRequest + (*ListUOMsResponse)(nil), // 11: finance.v1.ListUOMsResponse + (*ExportUOMsRequest)(nil), // 12: finance.v1.ExportUOMsRequest + (*ExportUOMsResponse)(nil), // 13: finance.v1.ExportUOMsResponse + (*ImportUOMsRequest)(nil), // 14: finance.v1.ImportUOMsRequest + (*ImportUOMsResponse)(nil), // 15: finance.v1.ImportUOMsResponse + (*ImportError)(nil), // 16: finance.v1.ImportError + (*DownloadTemplateRequest)(nil), // 17: finance.v1.DownloadTemplateRequest + (*DownloadTemplateResponse)(nil), // 18: finance.v1.DownloadTemplateResponse + (*v1.AuditInfo)(nil), // 19: common.v1.AuditInfo + (*v1.BaseResponse)(nil), // 20: common.v1.BaseResponse + (*v1.PaginationResponse)(nil), // 21: common.v1.PaginationResponse } var file_finance_v1_uom_proto_depIdxs = []int32{ - 0, // 0: finance.v1.UOM.uom_category:type_name -> finance.v1.UOMCategory - 20, // 1: finance.v1.UOM.audit:type_name -> common.v1.AuditInfo - 0, // 2: finance.v1.CreateUOMRequest.uom_category:type_name -> finance.v1.UOMCategory - 21, // 3: finance.v1.CreateUOMResponse.base:type_name -> common.v1.BaseResponse - 2, // 4: finance.v1.CreateUOMResponse.data:type_name -> finance.v1.UOM - 21, // 5: finance.v1.GetUOMResponse.base:type_name -> common.v1.BaseResponse - 2, // 6: finance.v1.GetUOMResponse.data:type_name -> finance.v1.UOM - 0, // 7: finance.v1.UpdateUOMRequest.uom_category:type_name -> finance.v1.UOMCategory - 21, // 8: finance.v1.UpdateUOMResponse.base:type_name -> common.v1.BaseResponse - 2, // 9: finance.v1.UpdateUOMResponse.data:type_name -> finance.v1.UOM - 21, // 10: finance.v1.DeleteUOMResponse.base:type_name -> common.v1.BaseResponse - 0, // 11: finance.v1.ListUOMsRequest.category:type_name -> finance.v1.UOMCategory - 1, // 12: finance.v1.ListUOMsRequest.active_filter:type_name -> finance.v1.ActiveFilter - 21, // 13: finance.v1.ListUOMsResponse.base:type_name -> common.v1.BaseResponse - 2, // 14: finance.v1.ListUOMsResponse.data:type_name -> finance.v1.UOM - 22, // 15: finance.v1.ListUOMsResponse.pagination:type_name -> common.v1.PaginationResponse - 0, // 16: finance.v1.ExportUOMsRequest.category:type_name -> finance.v1.UOMCategory - 1, // 17: finance.v1.ExportUOMsRequest.active_filter:type_name -> finance.v1.ActiveFilter - 21, // 18: finance.v1.ExportUOMsResponse.base:type_name -> common.v1.BaseResponse - 21, // 19: finance.v1.ImportUOMsResponse.base:type_name -> common.v1.BaseResponse - 17, // 20: finance.v1.ImportUOMsResponse.errors:type_name -> finance.v1.ImportError - 21, // 21: finance.v1.DownloadTemplateResponse.base:type_name -> common.v1.BaseResponse - 3, // 22: finance.v1.UOMService.CreateUOM:input_type -> finance.v1.CreateUOMRequest - 5, // 23: finance.v1.UOMService.GetUOM:input_type -> finance.v1.GetUOMRequest - 7, // 24: finance.v1.UOMService.UpdateUOM:input_type -> finance.v1.UpdateUOMRequest - 9, // 25: finance.v1.UOMService.DeleteUOM:input_type -> finance.v1.DeleteUOMRequest - 11, // 26: finance.v1.UOMService.ListUOMs:input_type -> finance.v1.ListUOMsRequest - 13, // 27: finance.v1.UOMService.ExportUOMs:input_type -> finance.v1.ExportUOMsRequest - 15, // 28: finance.v1.UOMService.ImportUOMs:input_type -> finance.v1.ImportUOMsRequest - 18, // 29: finance.v1.UOMService.DownloadTemplate:input_type -> finance.v1.DownloadTemplateRequest - 4, // 30: finance.v1.UOMService.CreateUOM:output_type -> finance.v1.CreateUOMResponse - 6, // 31: finance.v1.UOMService.GetUOM:output_type -> finance.v1.GetUOMResponse - 8, // 32: finance.v1.UOMService.UpdateUOM:output_type -> finance.v1.UpdateUOMResponse - 10, // 33: finance.v1.UOMService.DeleteUOM:output_type -> finance.v1.DeleteUOMResponse - 12, // 34: finance.v1.UOMService.ListUOMs:output_type -> finance.v1.ListUOMsResponse - 14, // 35: finance.v1.UOMService.ExportUOMs:output_type -> finance.v1.ExportUOMsResponse - 16, // 36: finance.v1.UOMService.ImportUOMs:output_type -> finance.v1.ImportUOMsResponse - 19, // 37: finance.v1.UOMService.DownloadTemplate:output_type -> finance.v1.DownloadTemplateResponse - 30, // [30:38] is the sub-list for method output_type - 22, // [22:30] is the sub-list for method input_type - 22, // [22:22] is the sub-list for extension type_name - 22, // [22:22] is the sub-list for extension extendee - 0, // [0:22] is the sub-list for field type_name + 19, // 0: finance.v1.UOM.audit:type_name -> common.v1.AuditInfo + 20, // 1: finance.v1.CreateUOMResponse.base:type_name -> common.v1.BaseResponse + 1, // 2: finance.v1.CreateUOMResponse.data:type_name -> finance.v1.UOM + 20, // 3: finance.v1.GetUOMResponse.base:type_name -> common.v1.BaseResponse + 1, // 4: finance.v1.GetUOMResponse.data:type_name -> finance.v1.UOM + 20, // 5: finance.v1.UpdateUOMResponse.base:type_name -> common.v1.BaseResponse + 1, // 6: finance.v1.UpdateUOMResponse.data:type_name -> finance.v1.UOM + 20, // 7: finance.v1.DeleteUOMResponse.base:type_name -> common.v1.BaseResponse + 0, // 8: finance.v1.ListUOMsRequest.active_filter:type_name -> finance.v1.ActiveFilter + 20, // 9: finance.v1.ListUOMsResponse.base:type_name -> common.v1.BaseResponse + 1, // 10: finance.v1.ListUOMsResponse.data:type_name -> finance.v1.UOM + 21, // 11: finance.v1.ListUOMsResponse.pagination:type_name -> common.v1.PaginationResponse + 0, // 12: finance.v1.ExportUOMsRequest.active_filter:type_name -> finance.v1.ActiveFilter + 20, // 13: finance.v1.ExportUOMsResponse.base:type_name -> common.v1.BaseResponse + 20, // 14: finance.v1.ImportUOMsResponse.base:type_name -> common.v1.BaseResponse + 16, // 15: finance.v1.ImportUOMsResponse.errors:type_name -> finance.v1.ImportError + 20, // 16: finance.v1.DownloadTemplateResponse.base:type_name -> common.v1.BaseResponse + 2, // 17: finance.v1.UOMService.CreateUOM:input_type -> finance.v1.CreateUOMRequest + 4, // 18: finance.v1.UOMService.GetUOM:input_type -> finance.v1.GetUOMRequest + 6, // 19: finance.v1.UOMService.UpdateUOM:input_type -> finance.v1.UpdateUOMRequest + 8, // 20: finance.v1.UOMService.DeleteUOM:input_type -> finance.v1.DeleteUOMRequest + 10, // 21: finance.v1.UOMService.ListUOMs:input_type -> finance.v1.ListUOMsRequest + 12, // 22: finance.v1.UOMService.ExportUOMs:input_type -> finance.v1.ExportUOMsRequest + 14, // 23: finance.v1.UOMService.ImportUOMs:input_type -> finance.v1.ImportUOMsRequest + 17, // 24: finance.v1.UOMService.DownloadTemplate:input_type -> finance.v1.DownloadTemplateRequest + 3, // 25: finance.v1.UOMService.CreateUOM:output_type -> finance.v1.CreateUOMResponse + 5, // 26: finance.v1.UOMService.GetUOM:output_type -> finance.v1.GetUOMResponse + 7, // 27: finance.v1.UOMService.UpdateUOM:output_type -> finance.v1.UpdateUOMResponse + 9, // 28: finance.v1.UOMService.DeleteUOM:output_type -> finance.v1.DeleteUOMResponse + 11, // 29: finance.v1.UOMService.ListUOMs:output_type -> finance.v1.ListUOMsResponse + 13, // 30: finance.v1.UOMService.ExportUOMs:output_type -> finance.v1.ExportUOMsResponse + 15, // 31: finance.v1.UOMService.ImportUOMs:output_type -> finance.v1.ImportUOMsResponse + 18, // 32: finance.v1.UOMService.DownloadTemplate:output_type -> finance.v1.DownloadTemplateResponse + 25, // [25:33] is the sub-list for method output_type + 17, // [17:25] is the sub-list for method input_type + 17, // [17:17] is the sub-list for extension type_name + 17, // [17:17] is the sub-list for extension extendee + 0, // [0:17] is the sub-list for field type_name } func init() { file_finance_v1_uom_proto_init() } @@ -1521,7 +1466,7 @@ func file_finance_v1_uom_proto_init() { File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_finance_v1_uom_proto_rawDesc), len(file_finance_v1_uom_proto_rawDesc)), - NumEnums: 2, + NumEnums: 1, NumMessages: 18, NumExtensions: 0, NumServices: 1, diff --git a/gen/finance/v1/uom_category.pb.go b/gen/finance/v1/uom_category.pb.go new file mode 100644 index 0000000..6136d93 --- /dev/null +++ b/gen/finance/v1/uom_category.pb.go @@ -0,0 +1,1281 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.11 +// protoc (unknown) +// source: finance/v1/uom_category.proto + +package financev1 + +import ( + _ "buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go/buf/validate" + v1 "github.com/mutugading/goapps-backend/gen/common/v1" + _ "google.golang.org/genproto/googleapis/api/annotations" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// UOMCategory represents a Unit of Measure Category entity. +// Used to classify UOMs (e.g., Weight, Length, Volume, Quantity, Time, Energy). +type UOMCategory struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Unique identifier (UUID). + UomCategoryId string `protobuf:"bytes,1,opt,name=uom_category_id,json=uomCategoryId,proto3" json:"uom_category_id,omitempty"` + // Unique code (e.g., "WEIGHT", "LENGTH", "VOLUME"). Immutable after creation. + CategoryCode string `protobuf:"bytes,2,opt,name=category_code,json=categoryCode,proto3" json:"category_code,omitempty"` + // Display name (e.g., "Weight", "Length", "Volume"). + CategoryName string `protobuf:"bytes,3,opt,name=category_name,json=categoryName,proto3" json:"category_name,omitempty"` + // Optional description. + Description string `protobuf:"bytes,4,opt,name=description,proto3" json:"description,omitempty"` + // Whether the category is active. + IsActive bool `protobuf:"varint,5,opt,name=is_active,json=isActive,proto3" json:"is_active,omitempty"` + // Audit information. + Audit *v1.AuditInfo `protobuf:"bytes,16,opt,name=audit,proto3" json:"audit,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UOMCategory) Reset() { + *x = UOMCategory{} + mi := &file_finance_v1_uom_category_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UOMCategory) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UOMCategory) ProtoMessage() {} + +func (x *UOMCategory) ProtoReflect() protoreflect.Message { + mi := &file_finance_v1_uom_category_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UOMCategory.ProtoReflect.Descriptor instead. +func (*UOMCategory) Descriptor() ([]byte, []int) { + return file_finance_v1_uom_category_proto_rawDescGZIP(), []int{0} +} + +func (x *UOMCategory) GetUomCategoryId() string { + if x != nil { + return x.UomCategoryId + } + return "" +} + +func (x *UOMCategory) GetCategoryCode() string { + if x != nil { + return x.CategoryCode + } + return "" +} + +func (x *UOMCategory) GetCategoryName() string { + if x != nil { + return x.CategoryName + } + return "" +} + +func (x *UOMCategory) GetDescription() string { + if x != nil { + return x.Description + } + return "" +} + +func (x *UOMCategory) GetIsActive() bool { + if x != nil { + return x.IsActive + } + return false +} + +func (x *UOMCategory) GetAudit() *v1.AuditInfo { + if x != nil { + return x.Audit + } + return nil +} + +// CreateUOMCategoryRequest is the request for creating a new UOM category. +type CreateUOMCategoryRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Unique code (uppercase, alphanumeric with underscore, 1-20 chars). + // Must start with an uppercase letter. Immutable after creation. + CategoryCode string `protobuf:"bytes,1,opt,name=category_code,json=categoryCode,proto3" json:"category_code,omitempty"` + // Display name (1-100 chars). + CategoryName string `protobuf:"bytes,2,opt,name=category_name,json=categoryName,proto3" json:"category_name,omitempty"` + // Optional description (max 500 chars). + Description string `protobuf:"bytes,3,opt,name=description,proto3" json:"description,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CreateUOMCategoryRequest) Reset() { + *x = CreateUOMCategoryRequest{} + mi := &file_finance_v1_uom_category_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CreateUOMCategoryRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateUOMCategoryRequest) ProtoMessage() {} + +func (x *CreateUOMCategoryRequest) ProtoReflect() protoreflect.Message { + mi := &file_finance_v1_uom_category_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateUOMCategoryRequest.ProtoReflect.Descriptor instead. +func (*CreateUOMCategoryRequest) Descriptor() ([]byte, []int) { + return file_finance_v1_uom_category_proto_rawDescGZIP(), []int{1} +} + +func (x *CreateUOMCategoryRequest) GetCategoryCode() string { + if x != nil { + return x.CategoryCode + } + return "" +} + +func (x *CreateUOMCategoryRequest) GetCategoryName() string { + if x != nil { + return x.CategoryName + } + return "" +} + +func (x *CreateUOMCategoryRequest) GetDescription() string { + if x != nil { + return x.Description + } + return "" +} + +// CreateUOMCategoryResponse is the response for creating a UOM category. +type CreateUOMCategoryResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Standard response metadata. + Base *v1.BaseResponse `protobuf:"bytes,1,opt,name=base,proto3" json:"base,omitempty"` + // Created UOM category data. + Data *UOMCategory `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CreateUOMCategoryResponse) Reset() { + *x = CreateUOMCategoryResponse{} + mi := &file_finance_v1_uom_category_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CreateUOMCategoryResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateUOMCategoryResponse) ProtoMessage() {} + +func (x *CreateUOMCategoryResponse) ProtoReflect() protoreflect.Message { + mi := &file_finance_v1_uom_category_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateUOMCategoryResponse.ProtoReflect.Descriptor instead. +func (*CreateUOMCategoryResponse) Descriptor() ([]byte, []int) { + return file_finance_v1_uom_category_proto_rawDescGZIP(), []int{2} +} + +func (x *CreateUOMCategoryResponse) GetBase() *v1.BaseResponse { + if x != nil { + return x.Base + } + return nil +} + +func (x *CreateUOMCategoryResponse) GetData() *UOMCategory { + if x != nil { + return x.Data + } + return nil +} + +// GetUOMCategoryRequest is the request for getting a UOM category by ID. +type GetUOMCategoryRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // UOM category ID (UUID format). + UomCategoryId string `protobuf:"bytes,1,opt,name=uom_category_id,json=uomCategoryId,proto3" json:"uom_category_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetUOMCategoryRequest) Reset() { + *x = GetUOMCategoryRequest{} + mi := &file_finance_v1_uom_category_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetUOMCategoryRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetUOMCategoryRequest) ProtoMessage() {} + +func (x *GetUOMCategoryRequest) ProtoReflect() protoreflect.Message { + mi := &file_finance_v1_uom_category_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetUOMCategoryRequest.ProtoReflect.Descriptor instead. +func (*GetUOMCategoryRequest) Descriptor() ([]byte, []int) { + return file_finance_v1_uom_category_proto_rawDescGZIP(), []int{3} +} + +func (x *GetUOMCategoryRequest) GetUomCategoryId() string { + if x != nil { + return x.UomCategoryId + } + return "" +} + +// GetUOMCategoryResponse is the response for getting a UOM category. +type GetUOMCategoryResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Standard response metadata. + Base *v1.BaseResponse `protobuf:"bytes,1,opt,name=base,proto3" json:"base,omitempty"` + // UOM category data. + Data *UOMCategory `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetUOMCategoryResponse) Reset() { + *x = GetUOMCategoryResponse{} + mi := &file_finance_v1_uom_category_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetUOMCategoryResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetUOMCategoryResponse) ProtoMessage() {} + +func (x *GetUOMCategoryResponse) ProtoReflect() protoreflect.Message { + mi := &file_finance_v1_uom_category_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetUOMCategoryResponse.ProtoReflect.Descriptor instead. +func (*GetUOMCategoryResponse) Descriptor() ([]byte, []int) { + return file_finance_v1_uom_category_proto_rawDescGZIP(), []int{4} +} + +func (x *GetUOMCategoryResponse) GetBase() *v1.BaseResponse { + if x != nil { + return x.Base + } + return nil +} + +func (x *GetUOMCategoryResponse) GetData() *UOMCategory { + if x != nil { + return x.Data + } + return nil +} + +// UpdateUOMCategoryRequest is the request for updating a UOM category. +// Note: category_code is immutable and cannot be changed after creation. +type UpdateUOMCategoryRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // UOM category ID to update (UUID format). + UomCategoryId string `protobuf:"bytes,1,opt,name=uom_category_id,json=uomCategoryId,proto3" json:"uom_category_id,omitempty"` + // New display name (optional, 1-100 chars if provided). + CategoryName *string `protobuf:"bytes,2,opt,name=category_name,json=categoryName,proto3,oneof" json:"category_name,omitempty"` + // New description (optional, max 500 chars). + Description *string `protobuf:"bytes,3,opt,name=description,proto3,oneof" json:"description,omitempty"` + // New active status (optional). + IsActive *bool `protobuf:"varint,4,opt,name=is_active,json=isActive,proto3,oneof" json:"is_active,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UpdateUOMCategoryRequest) Reset() { + *x = UpdateUOMCategoryRequest{} + mi := &file_finance_v1_uom_category_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UpdateUOMCategoryRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateUOMCategoryRequest) ProtoMessage() {} + +func (x *UpdateUOMCategoryRequest) ProtoReflect() protoreflect.Message { + mi := &file_finance_v1_uom_category_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpdateUOMCategoryRequest.ProtoReflect.Descriptor instead. +func (*UpdateUOMCategoryRequest) Descriptor() ([]byte, []int) { + return file_finance_v1_uom_category_proto_rawDescGZIP(), []int{5} +} + +func (x *UpdateUOMCategoryRequest) GetUomCategoryId() string { + if x != nil { + return x.UomCategoryId + } + return "" +} + +func (x *UpdateUOMCategoryRequest) GetCategoryName() string { + if x != nil && x.CategoryName != nil { + return *x.CategoryName + } + return "" +} + +func (x *UpdateUOMCategoryRequest) GetDescription() string { + if x != nil && x.Description != nil { + return *x.Description + } + return "" +} + +func (x *UpdateUOMCategoryRequest) GetIsActive() bool { + if x != nil && x.IsActive != nil { + return *x.IsActive + } + return false +} + +// UpdateUOMCategoryResponse is the response for updating a UOM category. +type UpdateUOMCategoryResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Standard response metadata. + Base *v1.BaseResponse `protobuf:"bytes,1,opt,name=base,proto3" json:"base,omitempty"` + // Updated UOM category data. + Data *UOMCategory `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UpdateUOMCategoryResponse) Reset() { + *x = UpdateUOMCategoryResponse{} + mi := &file_finance_v1_uom_category_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UpdateUOMCategoryResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateUOMCategoryResponse) ProtoMessage() {} + +func (x *UpdateUOMCategoryResponse) ProtoReflect() protoreflect.Message { + mi := &file_finance_v1_uom_category_proto_msgTypes[6] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpdateUOMCategoryResponse.ProtoReflect.Descriptor instead. +func (*UpdateUOMCategoryResponse) Descriptor() ([]byte, []int) { + return file_finance_v1_uom_category_proto_rawDescGZIP(), []int{6} +} + +func (x *UpdateUOMCategoryResponse) GetBase() *v1.BaseResponse { + if x != nil { + return x.Base + } + return nil +} + +func (x *UpdateUOMCategoryResponse) GetData() *UOMCategory { + if x != nil { + return x.Data + } + return nil +} + +// DeleteUOMCategoryRequest is the request for deleting (soft delete) a UOM category. +type DeleteUOMCategoryRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // UOM category ID to delete (UUID format). + UomCategoryId string `protobuf:"bytes,1,opt,name=uom_category_id,json=uomCategoryId,proto3" json:"uom_category_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DeleteUOMCategoryRequest) Reset() { + *x = DeleteUOMCategoryRequest{} + mi := &file_finance_v1_uom_category_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeleteUOMCategoryRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteUOMCategoryRequest) ProtoMessage() {} + +func (x *DeleteUOMCategoryRequest) ProtoReflect() protoreflect.Message { + mi := &file_finance_v1_uom_category_proto_msgTypes[7] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteUOMCategoryRequest.ProtoReflect.Descriptor instead. +func (*DeleteUOMCategoryRequest) Descriptor() ([]byte, []int) { + return file_finance_v1_uom_category_proto_rawDescGZIP(), []int{7} +} + +func (x *DeleteUOMCategoryRequest) GetUomCategoryId() string { + if x != nil { + return x.UomCategoryId + } + return "" +} + +// DeleteUOMCategoryResponse is the response for deleting a UOM category. +type DeleteUOMCategoryResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Standard response metadata. + Base *v1.BaseResponse `protobuf:"bytes,1,opt,name=base,proto3" json:"base,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DeleteUOMCategoryResponse) Reset() { + *x = DeleteUOMCategoryResponse{} + mi := &file_finance_v1_uom_category_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeleteUOMCategoryResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteUOMCategoryResponse) ProtoMessage() {} + +func (x *DeleteUOMCategoryResponse) ProtoReflect() protoreflect.Message { + mi := &file_finance_v1_uom_category_proto_msgTypes[8] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteUOMCategoryResponse.ProtoReflect.Descriptor instead. +func (*DeleteUOMCategoryResponse) Descriptor() ([]byte, []int) { + return file_finance_v1_uom_category_proto_rawDescGZIP(), []int{8} +} + +func (x *DeleteUOMCategoryResponse) GetBase() *v1.BaseResponse { + if x != nil { + return x.Base + } + return nil +} + +// ListUOMCategoriesRequest is the request for listing UOM categories +// with search, filter, and pagination. +type ListUOMCategoriesRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Page number (1-indexed, default 1, min 1). + Page int32 `protobuf:"varint,1,opt,name=page,proto3" json:"page,omitempty"` + // Items per page (1-100, default 10). + PageSize int32 `protobuf:"varint,2,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"` + // Search query (searches in code, name, description). + Search string `protobuf:"bytes,3,opt,name=search,proto3" json:"search,omitempty"` + // Filter by active status. + // ACTIVE_FILTER_UNSPECIFIED (0) = show all, ACTIVE_FILTER_ACTIVE (1) = only active, + // ACTIVE_FILTER_INACTIVE (2) = only inactive. + ActiveFilter ActiveFilter `protobuf:"varint,4,opt,name=active_filter,json=activeFilter,proto3,enum=finance.v1.ActiveFilter" json:"active_filter,omitempty"` + // Sort field: "code", "name", "created_at" (default: "code"). + SortBy string `protobuf:"bytes,5,opt,name=sort_by,json=sortBy,proto3" json:"sort_by,omitempty"` + // Sort order: "asc", "desc" (default: "asc"). + SortOrder string `protobuf:"bytes,6,opt,name=sort_order,json=sortOrder,proto3" json:"sort_order,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListUOMCategoriesRequest) Reset() { + *x = ListUOMCategoriesRequest{} + mi := &file_finance_v1_uom_category_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListUOMCategoriesRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListUOMCategoriesRequest) ProtoMessage() {} + +func (x *ListUOMCategoriesRequest) ProtoReflect() protoreflect.Message { + mi := &file_finance_v1_uom_category_proto_msgTypes[9] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListUOMCategoriesRequest.ProtoReflect.Descriptor instead. +func (*ListUOMCategoriesRequest) Descriptor() ([]byte, []int) { + return file_finance_v1_uom_category_proto_rawDescGZIP(), []int{9} +} + +func (x *ListUOMCategoriesRequest) GetPage() int32 { + if x != nil { + return x.Page + } + return 0 +} + +func (x *ListUOMCategoriesRequest) GetPageSize() int32 { + if x != nil { + return x.PageSize + } + return 0 +} + +func (x *ListUOMCategoriesRequest) GetSearch() string { + if x != nil { + return x.Search + } + return "" +} + +func (x *ListUOMCategoriesRequest) GetActiveFilter() ActiveFilter { + if x != nil { + return x.ActiveFilter + } + return ActiveFilter_ACTIVE_FILTER_UNSPECIFIED +} + +func (x *ListUOMCategoriesRequest) GetSortBy() string { + if x != nil { + return x.SortBy + } + return "" +} + +func (x *ListUOMCategoriesRequest) GetSortOrder() string { + if x != nil { + return x.SortOrder + } + return "" +} + +// ListUOMCategoriesResponse is the response for listing UOM categories. +type ListUOMCategoriesResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Standard response metadata. + Base *v1.BaseResponse `protobuf:"bytes,1,opt,name=base,proto3" json:"base,omitempty"` + // List of UOM categories. + Data []*UOMCategory `protobuf:"bytes,2,rep,name=data,proto3" json:"data,omitempty"` + // Pagination metadata. + Pagination *v1.PaginationResponse `protobuf:"bytes,3,opt,name=pagination,proto3" json:"pagination,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListUOMCategoriesResponse) Reset() { + *x = ListUOMCategoriesResponse{} + mi := &file_finance_v1_uom_category_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListUOMCategoriesResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListUOMCategoriesResponse) ProtoMessage() {} + +func (x *ListUOMCategoriesResponse) ProtoReflect() protoreflect.Message { + mi := &file_finance_v1_uom_category_proto_msgTypes[10] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListUOMCategoriesResponse.ProtoReflect.Descriptor instead. +func (*ListUOMCategoriesResponse) Descriptor() ([]byte, []int) { + return file_finance_v1_uom_category_proto_rawDescGZIP(), []int{10} +} + +func (x *ListUOMCategoriesResponse) GetBase() *v1.BaseResponse { + if x != nil { + return x.Base + } + return nil +} + +func (x *ListUOMCategoriesResponse) GetData() []*UOMCategory { + if x != nil { + return x.Data + } + return nil +} + +func (x *ListUOMCategoriesResponse) GetPagination() *v1.PaginationResponse { + if x != nil { + return x.Pagination + } + return nil +} + +// ExportUOMCategoriesRequest is the request for exporting UOM categories to Excel. +type ExportUOMCategoriesRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Filter by active status. + // ACTIVE_FILTER_UNSPECIFIED (0) = export all, ACTIVE_FILTER_ACTIVE (1) = only active, + // ACTIVE_FILTER_INACTIVE (2) = only inactive. + ActiveFilter ActiveFilter `protobuf:"varint,1,opt,name=active_filter,json=activeFilter,proto3,enum=finance.v1.ActiveFilter" json:"active_filter,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ExportUOMCategoriesRequest) Reset() { + *x = ExportUOMCategoriesRequest{} + mi := &file_finance_v1_uom_category_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ExportUOMCategoriesRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ExportUOMCategoriesRequest) ProtoMessage() {} + +func (x *ExportUOMCategoriesRequest) ProtoReflect() protoreflect.Message { + mi := &file_finance_v1_uom_category_proto_msgTypes[11] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ExportUOMCategoriesRequest.ProtoReflect.Descriptor instead. +func (*ExportUOMCategoriesRequest) Descriptor() ([]byte, []int) { + return file_finance_v1_uom_category_proto_rawDescGZIP(), []int{11} +} + +func (x *ExportUOMCategoriesRequest) GetActiveFilter() ActiveFilter { + if x != nil { + return x.ActiveFilter + } + return ActiveFilter_ACTIVE_FILTER_UNSPECIFIED +} + +// ExportUOMCategoriesResponse is the response containing the Excel file. +type ExportUOMCategoriesResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Standard response metadata. + Base *v1.BaseResponse `protobuf:"bytes,1,opt,name=base,proto3" json:"base,omitempty"` + // Excel file content as bytes (.xlsx format). + FileContent []byte `protobuf:"bytes,2,opt,name=file_content,json=fileContent,proto3" json:"file_content,omitempty"` + // Suggested filename. + FileName string `protobuf:"bytes,3,opt,name=file_name,json=fileName,proto3" json:"file_name,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ExportUOMCategoriesResponse) Reset() { + *x = ExportUOMCategoriesResponse{} + mi := &file_finance_v1_uom_category_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ExportUOMCategoriesResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ExportUOMCategoriesResponse) ProtoMessage() {} + +func (x *ExportUOMCategoriesResponse) ProtoReflect() protoreflect.Message { + mi := &file_finance_v1_uom_category_proto_msgTypes[12] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ExportUOMCategoriesResponse.ProtoReflect.Descriptor instead. +func (*ExportUOMCategoriesResponse) Descriptor() ([]byte, []int) { + return file_finance_v1_uom_category_proto_rawDescGZIP(), []int{12} +} + +func (x *ExportUOMCategoriesResponse) GetBase() *v1.BaseResponse { + if x != nil { + return x.Base + } + return nil +} + +func (x *ExportUOMCategoriesResponse) GetFileContent() []byte { + if x != nil { + return x.FileContent + } + return nil +} + +func (x *ExportUOMCategoriesResponse) GetFileName() string { + if x != nil { + return x.FileName + } + return "" +} + +// ImportUOMCategoriesRequest is the request for importing UOM categories from Excel. +type ImportUOMCategoriesRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Excel file content as bytes (.xlsx or .xls format). + FileContent []byte `protobuf:"bytes,1,opt,name=file_content,json=fileContent,proto3" json:"file_content,omitempty"` + // Original filename (for format detection). + // Must be a simple filename without path separators. + FileName string `protobuf:"bytes,2,opt,name=file_name,json=fileName,proto3" json:"file_name,omitempty"` + // Required. How to handle duplicates: "skip", "update", "error". + DuplicateAction string `protobuf:"bytes,3,opt,name=duplicate_action,json=duplicateAction,proto3" json:"duplicate_action,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ImportUOMCategoriesRequest) Reset() { + *x = ImportUOMCategoriesRequest{} + mi := &file_finance_v1_uom_category_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ImportUOMCategoriesRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ImportUOMCategoriesRequest) ProtoMessage() {} + +func (x *ImportUOMCategoriesRequest) ProtoReflect() protoreflect.Message { + mi := &file_finance_v1_uom_category_proto_msgTypes[13] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ImportUOMCategoriesRequest.ProtoReflect.Descriptor instead. +func (*ImportUOMCategoriesRequest) Descriptor() ([]byte, []int) { + return file_finance_v1_uom_category_proto_rawDescGZIP(), []int{13} +} + +func (x *ImportUOMCategoriesRequest) GetFileContent() []byte { + if x != nil { + return x.FileContent + } + return nil +} + +func (x *ImportUOMCategoriesRequest) GetFileName() string { + if x != nil { + return x.FileName + } + return "" +} + +func (x *ImportUOMCategoriesRequest) GetDuplicateAction() string { + if x != nil { + return x.DuplicateAction + } + return "" +} + +// ImportUOMCategoriesResponse is the response for importing UOM categories. +type ImportUOMCategoriesResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Standard response metadata. + Base *v1.BaseResponse `protobuf:"bytes,1,opt,name=base,proto3" json:"base,omitempty"` + // Number of successfully imported records. + SuccessCount int32 `protobuf:"varint,2,opt,name=success_count,json=successCount,proto3" json:"success_count,omitempty"` + // Number of skipped records (duplicates with skip action). + SkippedCount int32 `protobuf:"varint,3,opt,name=skipped_count,json=skippedCount,proto3" json:"skipped_count,omitempty"` + // Number of updated records (duplicates with update action). + UpdatedCount int32 `protobuf:"varint,4,opt,name=updated_count,json=updatedCount,proto3" json:"updated_count,omitempty"` + // Number of failed records. + FailedCount int32 `protobuf:"varint,5,opt,name=failed_count,json=failedCount,proto3" json:"failed_count,omitempty"` + // Details of failed records. + Errors []*ImportError `protobuf:"bytes,6,rep,name=errors,proto3" json:"errors,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ImportUOMCategoriesResponse) Reset() { + *x = ImportUOMCategoriesResponse{} + mi := &file_finance_v1_uom_category_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ImportUOMCategoriesResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ImportUOMCategoriesResponse) ProtoMessage() {} + +func (x *ImportUOMCategoriesResponse) ProtoReflect() protoreflect.Message { + mi := &file_finance_v1_uom_category_proto_msgTypes[14] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ImportUOMCategoriesResponse.ProtoReflect.Descriptor instead. +func (*ImportUOMCategoriesResponse) Descriptor() ([]byte, []int) { + return file_finance_v1_uom_category_proto_rawDescGZIP(), []int{14} +} + +func (x *ImportUOMCategoriesResponse) GetBase() *v1.BaseResponse { + if x != nil { + return x.Base + } + return nil +} + +func (x *ImportUOMCategoriesResponse) GetSuccessCount() int32 { + if x != nil { + return x.SuccessCount + } + return 0 +} + +func (x *ImportUOMCategoriesResponse) GetSkippedCount() int32 { + if x != nil { + return x.SkippedCount + } + return 0 +} + +func (x *ImportUOMCategoriesResponse) GetUpdatedCount() int32 { + if x != nil { + return x.UpdatedCount + } + return 0 +} + +func (x *ImportUOMCategoriesResponse) GetFailedCount() int32 { + if x != nil { + return x.FailedCount + } + return 0 +} + +func (x *ImportUOMCategoriesResponse) GetErrors() []*ImportError { + if x != nil { + return x.Errors + } + return nil +} + +// DownloadUOMCategoryTemplateRequest is the request for downloading the import template. +type DownloadUOMCategoryTemplateRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DownloadUOMCategoryTemplateRequest) Reset() { + *x = DownloadUOMCategoryTemplateRequest{} + mi := &file_finance_v1_uom_category_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DownloadUOMCategoryTemplateRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DownloadUOMCategoryTemplateRequest) ProtoMessage() {} + +func (x *DownloadUOMCategoryTemplateRequest) ProtoReflect() protoreflect.Message { + mi := &file_finance_v1_uom_category_proto_msgTypes[15] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DownloadUOMCategoryTemplateRequest.ProtoReflect.Descriptor instead. +func (*DownloadUOMCategoryTemplateRequest) Descriptor() ([]byte, []int) { + return file_finance_v1_uom_category_proto_rawDescGZIP(), []int{15} +} + +// DownloadUOMCategoryTemplateResponse is the response containing the template file. +type DownloadUOMCategoryTemplateResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Standard response metadata. + Base *v1.BaseResponse `protobuf:"bytes,1,opt,name=base,proto3" json:"base,omitempty"` + // Excel template file content as bytes (.xlsx format). + FileContent []byte `protobuf:"bytes,2,opt,name=file_content,json=fileContent,proto3" json:"file_content,omitempty"` + // Suggested filename. + FileName string `protobuf:"bytes,3,opt,name=file_name,json=fileName,proto3" json:"file_name,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DownloadUOMCategoryTemplateResponse) Reset() { + *x = DownloadUOMCategoryTemplateResponse{} + mi := &file_finance_v1_uom_category_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DownloadUOMCategoryTemplateResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DownloadUOMCategoryTemplateResponse) ProtoMessage() {} + +func (x *DownloadUOMCategoryTemplateResponse) ProtoReflect() protoreflect.Message { + mi := &file_finance_v1_uom_category_proto_msgTypes[16] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DownloadUOMCategoryTemplateResponse.ProtoReflect.Descriptor instead. +func (*DownloadUOMCategoryTemplateResponse) Descriptor() ([]byte, []int) { + return file_finance_v1_uom_category_proto_rawDescGZIP(), []int{16} +} + +func (x *DownloadUOMCategoryTemplateResponse) GetBase() *v1.BaseResponse { + if x != nil { + return x.Base + } + return nil +} + +func (x *DownloadUOMCategoryTemplateResponse) GetFileContent() []byte { + if x != nil { + return x.FileContent + } + return nil +} + +func (x *DownloadUOMCategoryTemplateResponse) GetFileName() string { + if x != nil { + return x.FileName + } + return "" +} + +var File_finance_v1_uom_category_proto protoreflect.FileDescriptor + +const file_finance_v1_uom_category_proto_rawDesc = "" + + "\n" + + "\x1dfinance/v1/uom_category.proto\x12\n" + + "finance.v1\x1a\x1bbuf/validate/validate.proto\x1a\x16common/v1/common.proto\x1a\x14finance/v1/uom.proto\x1a\x1cgoogle/api/annotations.proto\"\xea\x01\n" + + "\vUOMCategory\x12&\n" + + "\x0fuom_category_id\x18\x01 \x01(\tR\ruomCategoryId\x12#\n" + + "\rcategory_code\x18\x02 \x01(\tR\fcategoryCode\x12#\n" + + "\rcategory_name\x18\x03 \x01(\tR\fcategoryName\x12 \n" + + "\vdescription\x18\x04 \x01(\tR\vdescription\x12\x1b\n" + + "\tis_active\x18\x05 \x01(\bR\bisActive\x12*\n" + + "\x05audit\x18\x10 \x01(\v2\x14.common.v1.AuditInfoR\x05audit\"\xb9\x01\n" + + "\x18CreateUOMCategoryRequest\x12A\n" + + "\rcategory_code\x18\x01 \x01(\tB\x1c\xbaH\x19r\x17\x10\x01\x18\x142\x11^[A-Z][A-Z0-9_]*$R\fcategoryCode\x12.\n" + + "\rcategory_name\x18\x02 \x01(\tB\t\xbaH\x06r\x04\x10\x01\x18dR\fcategoryName\x12*\n" + + "\vdescription\x18\x03 \x01(\tB\b\xbaH\x05r\x03\x18\xf4\x03R\vdescription\"u\n" + + "\x19CreateUOMCategoryResponse\x12+\n" + + "\x04base\x18\x01 \x01(\v2\x17.common.v1.BaseResponseR\x04base\x12+\n" + + "\x04data\x18\x02 \x01(\v2\x17.finance.v1.UOMCategoryR\x04data\"I\n" + + "\x15GetUOMCategoryRequest\x120\n" + + "\x0fuom_category_id\x18\x01 \x01(\tB\b\xbaH\x05r\x03\xb0\x01\x01R\ruomCategoryId\"r\n" + + "\x16GetUOMCategoryResponse\x12+\n" + + "\x04base\x18\x01 \x01(\v2\x17.common.v1.BaseResponseR\x04base\x12+\n" + + "\x04data\x18\x02 \x01(\v2\x17.finance.v1.UOMCategoryR\x04data\"\x82\x02\n" + + "\x18UpdateUOMCategoryRequest\x120\n" + + "\x0fuom_category_id\x18\x01 \x01(\tB\b\xbaH\x05r\x03\xb0\x01\x01R\ruomCategoryId\x121\n" + + "\rcategory_name\x18\x02 \x01(\tB\a\xbaH\x04r\x02\x18dH\x00R\fcategoryName\x88\x01\x01\x12/\n" + + "\vdescription\x18\x03 \x01(\tB\b\xbaH\x05r\x03\x18\xf4\x03H\x01R\vdescription\x88\x01\x01\x12 \n" + + "\tis_active\x18\x04 \x01(\bH\x02R\bisActive\x88\x01\x01B\x10\n" + + "\x0e_category_nameB\x0e\n" + + "\f_descriptionB\f\n" + + "\n" + + "_is_active\"u\n" + + "\x19UpdateUOMCategoryResponse\x12+\n" + + "\x04base\x18\x01 \x01(\v2\x17.common.v1.BaseResponseR\x04base\x12+\n" + + "\x04data\x18\x02 \x01(\v2\x17.finance.v1.UOMCategoryR\x04data\"L\n" + + "\x18DeleteUOMCategoryRequest\x120\n" + + "\x0fuom_category_id\x18\x01 \x01(\tB\b\xbaH\x05r\x03\xb0\x01\x01R\ruomCategoryId\"H\n" + + "\x19DeleteUOMCategoryResponse\x12+\n" + + "\x04base\x18\x01 \x01(\v2\x17.common.v1.BaseResponseR\x04base\"\xac\x02\n" + + "\x18ListUOMCategoriesRequest\x12\x1b\n" + + "\x04page\x18\x01 \x01(\x05B\a\xbaH\x04\x1a\x02(\x01R\x04page\x12&\n" + + "\tpage_size\x18\x02 \x01(\x05B\t\xbaH\x06\x1a\x04\x18d(\x01R\bpageSize\x12\x1f\n" + + "\x06search\x18\x03 \x01(\tB\a\xbaH\x04r\x02\x18dR\x06search\x12=\n" + + "\ractive_filter\x18\x04 \x01(\x0e2\x18.finance.v1.ActiveFilterR\factiveFilter\x128\n" + + "\asort_by\x18\x05 \x01(\tB\x1f\xbaH\x1cr\x1aR\x00R\x04codeR\x04nameR\n" + + "created_atR\x06sortBy\x121\n" + + "\n" + + "sort_order\x18\x06 \x01(\tB\x12\xbaH\x0fr\rR\x00R\x03ascR\x04descR\tsortOrder\"\xb4\x01\n" + + "\x19ListUOMCategoriesResponse\x12+\n" + + "\x04base\x18\x01 \x01(\v2\x17.common.v1.BaseResponseR\x04base\x12+\n" + + "\x04data\x18\x02 \x03(\v2\x17.finance.v1.UOMCategoryR\x04data\x12=\n" + + "\n" + + "pagination\x18\x03 \x01(\v2\x1d.common.v1.PaginationResponseR\n" + + "pagination\"[\n" + + "\x1aExportUOMCategoriesRequest\x12=\n" + + "\ractive_filter\x18\x01 \x01(\x0e2\x18.finance.v1.ActiveFilterR\factiveFilter\"\x8a\x01\n" + + "\x1bExportUOMCategoriesResponse\x12+\n" + + "\x04base\x18\x01 \x01(\v2\x17.common.v1.BaseResponseR\x04base\x12!\n" + + "\ffile_content\x18\x02 \x01(\fR\vfileContent\x12\x1b\n" + + "\tfile_name\x18\x03 \x01(\tR\bfileName\"\xd6\x01\n" + + "\x1aImportUOMCategoriesRequest\x12/\n" + + "\ffile_content\x18\x01 \x01(\fB\f\xbaH\tz\a\x10\x01\x18\x80\x80\x80\x05R\vfileContent\x12>\n" + + "\tfile_name\x18\x02 \x01(\tB!\xbaH\x1er\x1c\x10\x01\x18\xff\x012\x15^[^/\\\\]+\\.(xlsx|xls)$R\bfileName\x12G\n" + + "\x10duplicate_action\x18\x03 \x01(\tB\x1c\xbaH\x19r\x17\x10\x01R\x04skipR\x06updateR\x05errorR\x0fduplicateAction\"\x8d\x02\n" + + "\x1bImportUOMCategoriesResponse\x12+\n" + + "\x04base\x18\x01 \x01(\v2\x17.common.v1.BaseResponseR\x04base\x12#\n" + + "\rsuccess_count\x18\x02 \x01(\x05R\fsuccessCount\x12#\n" + + "\rskipped_count\x18\x03 \x01(\x05R\fskippedCount\x12#\n" + + "\rupdated_count\x18\x04 \x01(\x05R\fupdatedCount\x12!\n" + + "\ffailed_count\x18\x05 \x01(\x05R\vfailedCount\x12/\n" + + "\x06errors\x18\x06 \x03(\v2\x17.finance.v1.ImportErrorR\x06errors\"$\n" + + "\"DownloadUOMCategoryTemplateRequest\"\x92\x01\n" + + "#DownloadUOMCategoryTemplateResponse\x12+\n" + + "\x04base\x18\x01 \x01(\v2\x17.common.v1.BaseResponseR\x04base\x12!\n" + + "\ffile_content\x18\x02 \x01(\fR\vfileContent\x12\x1b\n" + + "\tfile_name\x18\x03 \x01(\tR\bfileName2\xe3\t\n" + + "\x12UOMCategoryService\x12\x8b\x01\n" + + "\x11CreateUOMCategory\x12$.finance.v1.CreateUOMCategoryRequest\x1a%.finance.v1.CreateUOMCategoryResponse\")\x82\xd3\xe4\x93\x02#:\x01*\"\x1e/api/v1/finance/uom-categories\x12\x91\x01\n" + + "\x0eGetUOMCategory\x12!.finance.v1.GetUOMCategoryRequest\x1a\".finance.v1.GetUOMCategoryResponse\"8\x82\xd3\xe4\x93\x022\x120/api/v1/finance/uom-categories/{uom_category_id}\x12\x9d\x01\n" + + "\x11UpdateUOMCategory\x12$.finance.v1.UpdateUOMCategoryRequest\x1a%.finance.v1.UpdateUOMCategoryResponse\";\x82\xd3\xe4\x93\x025:\x01*\x1a0/api/v1/finance/uom-categories/{uom_category_id}\x12\x9a\x01\n" + + "\x11DeleteUOMCategory\x12$.finance.v1.DeleteUOMCategoryRequest\x1a%.finance.v1.DeleteUOMCategoryResponse\"8\x82\xd3\xe4\x93\x022*0/api/v1/finance/uom-categories/{uom_category_id}\x12\x88\x01\n" + + "\x11ListUOMCategories\x12$.finance.v1.ListUOMCategoriesRequest\x1a%.finance.v1.ListUOMCategoriesResponse\"&\x82\xd3\xe4\x93\x02 \x12\x1e/api/v1/finance/uom-categories\x12\x95\x01\n" + + "\x13ExportUOMCategories\x12&.finance.v1.ExportUOMCategoriesRequest\x1a'.finance.v1.ExportUOMCategoriesResponse\"-\x82\xd3\xe4\x93\x02'\x12%/api/v1/finance/uom-categories/export\x12\x98\x01\n" + + "\x13ImportUOMCategories\x12&.finance.v1.ImportUOMCategoriesRequest\x1a'.finance.v1.ImportUOMCategoriesResponse\"0\x82\xd3\xe4\x93\x02*:\x01*\"%/api/v1/finance/uom-categories/import\x12\xaf\x01\n" + + "\x1bDownloadUOMCategoryTemplate\x12..finance.v1.DownloadUOMCategoryTemplateRequest\x1a/.finance.v1.DownloadUOMCategoryTemplateResponse\"/\x82\xd3\xe4\x93\x02)\x12'/api/v1/finance/uom-categories/templateB\xaa\x01\n" + + "\x0ecom.finance.v1B\x10UomCategoryProtoP\x01Z=github.com/mutugading/goapps-backend/gen/finance/v1;financev1\xa2\x02\x03FXX\xaa\x02\n" + + "Finance.V1\xca\x02\n" + + "Finance\\V1\xe2\x02\x16Finance\\V1\\GPBMetadata\xea\x02\vFinance::V1b\x06proto3" + +var ( + file_finance_v1_uom_category_proto_rawDescOnce sync.Once + file_finance_v1_uom_category_proto_rawDescData []byte +) + +func file_finance_v1_uom_category_proto_rawDescGZIP() []byte { + file_finance_v1_uom_category_proto_rawDescOnce.Do(func() { + file_finance_v1_uom_category_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_finance_v1_uom_category_proto_rawDesc), len(file_finance_v1_uom_category_proto_rawDesc))) + }) + return file_finance_v1_uom_category_proto_rawDescData +} + +var file_finance_v1_uom_category_proto_msgTypes = make([]protoimpl.MessageInfo, 17) +var file_finance_v1_uom_category_proto_goTypes = []any{ + (*UOMCategory)(nil), // 0: finance.v1.UOMCategory + (*CreateUOMCategoryRequest)(nil), // 1: finance.v1.CreateUOMCategoryRequest + (*CreateUOMCategoryResponse)(nil), // 2: finance.v1.CreateUOMCategoryResponse + (*GetUOMCategoryRequest)(nil), // 3: finance.v1.GetUOMCategoryRequest + (*GetUOMCategoryResponse)(nil), // 4: finance.v1.GetUOMCategoryResponse + (*UpdateUOMCategoryRequest)(nil), // 5: finance.v1.UpdateUOMCategoryRequest + (*UpdateUOMCategoryResponse)(nil), // 6: finance.v1.UpdateUOMCategoryResponse + (*DeleteUOMCategoryRequest)(nil), // 7: finance.v1.DeleteUOMCategoryRequest + (*DeleteUOMCategoryResponse)(nil), // 8: finance.v1.DeleteUOMCategoryResponse + (*ListUOMCategoriesRequest)(nil), // 9: finance.v1.ListUOMCategoriesRequest + (*ListUOMCategoriesResponse)(nil), // 10: finance.v1.ListUOMCategoriesResponse + (*ExportUOMCategoriesRequest)(nil), // 11: finance.v1.ExportUOMCategoriesRequest + (*ExportUOMCategoriesResponse)(nil), // 12: finance.v1.ExportUOMCategoriesResponse + (*ImportUOMCategoriesRequest)(nil), // 13: finance.v1.ImportUOMCategoriesRequest + (*ImportUOMCategoriesResponse)(nil), // 14: finance.v1.ImportUOMCategoriesResponse + (*DownloadUOMCategoryTemplateRequest)(nil), // 15: finance.v1.DownloadUOMCategoryTemplateRequest + (*DownloadUOMCategoryTemplateResponse)(nil), // 16: finance.v1.DownloadUOMCategoryTemplateResponse + (*v1.AuditInfo)(nil), // 17: common.v1.AuditInfo + (*v1.BaseResponse)(nil), // 18: common.v1.BaseResponse + (ActiveFilter)(0), // 19: finance.v1.ActiveFilter + (*v1.PaginationResponse)(nil), // 20: common.v1.PaginationResponse + (*ImportError)(nil), // 21: finance.v1.ImportError +} +var file_finance_v1_uom_category_proto_depIdxs = []int32{ + 17, // 0: finance.v1.UOMCategory.audit:type_name -> common.v1.AuditInfo + 18, // 1: finance.v1.CreateUOMCategoryResponse.base:type_name -> common.v1.BaseResponse + 0, // 2: finance.v1.CreateUOMCategoryResponse.data:type_name -> finance.v1.UOMCategory + 18, // 3: finance.v1.GetUOMCategoryResponse.base:type_name -> common.v1.BaseResponse + 0, // 4: finance.v1.GetUOMCategoryResponse.data:type_name -> finance.v1.UOMCategory + 18, // 5: finance.v1.UpdateUOMCategoryResponse.base:type_name -> common.v1.BaseResponse + 0, // 6: finance.v1.UpdateUOMCategoryResponse.data:type_name -> finance.v1.UOMCategory + 18, // 7: finance.v1.DeleteUOMCategoryResponse.base:type_name -> common.v1.BaseResponse + 19, // 8: finance.v1.ListUOMCategoriesRequest.active_filter:type_name -> finance.v1.ActiveFilter + 18, // 9: finance.v1.ListUOMCategoriesResponse.base:type_name -> common.v1.BaseResponse + 0, // 10: finance.v1.ListUOMCategoriesResponse.data:type_name -> finance.v1.UOMCategory + 20, // 11: finance.v1.ListUOMCategoriesResponse.pagination:type_name -> common.v1.PaginationResponse + 19, // 12: finance.v1.ExportUOMCategoriesRequest.active_filter:type_name -> finance.v1.ActiveFilter + 18, // 13: finance.v1.ExportUOMCategoriesResponse.base:type_name -> common.v1.BaseResponse + 18, // 14: finance.v1.ImportUOMCategoriesResponse.base:type_name -> common.v1.BaseResponse + 21, // 15: finance.v1.ImportUOMCategoriesResponse.errors:type_name -> finance.v1.ImportError + 18, // 16: finance.v1.DownloadUOMCategoryTemplateResponse.base:type_name -> common.v1.BaseResponse + 1, // 17: finance.v1.UOMCategoryService.CreateUOMCategory:input_type -> finance.v1.CreateUOMCategoryRequest + 3, // 18: finance.v1.UOMCategoryService.GetUOMCategory:input_type -> finance.v1.GetUOMCategoryRequest + 5, // 19: finance.v1.UOMCategoryService.UpdateUOMCategory:input_type -> finance.v1.UpdateUOMCategoryRequest + 7, // 20: finance.v1.UOMCategoryService.DeleteUOMCategory:input_type -> finance.v1.DeleteUOMCategoryRequest + 9, // 21: finance.v1.UOMCategoryService.ListUOMCategories:input_type -> finance.v1.ListUOMCategoriesRequest + 11, // 22: finance.v1.UOMCategoryService.ExportUOMCategories:input_type -> finance.v1.ExportUOMCategoriesRequest + 13, // 23: finance.v1.UOMCategoryService.ImportUOMCategories:input_type -> finance.v1.ImportUOMCategoriesRequest + 15, // 24: finance.v1.UOMCategoryService.DownloadUOMCategoryTemplate:input_type -> finance.v1.DownloadUOMCategoryTemplateRequest + 2, // 25: finance.v1.UOMCategoryService.CreateUOMCategory:output_type -> finance.v1.CreateUOMCategoryResponse + 4, // 26: finance.v1.UOMCategoryService.GetUOMCategory:output_type -> finance.v1.GetUOMCategoryResponse + 6, // 27: finance.v1.UOMCategoryService.UpdateUOMCategory:output_type -> finance.v1.UpdateUOMCategoryResponse + 8, // 28: finance.v1.UOMCategoryService.DeleteUOMCategory:output_type -> finance.v1.DeleteUOMCategoryResponse + 10, // 29: finance.v1.UOMCategoryService.ListUOMCategories:output_type -> finance.v1.ListUOMCategoriesResponse + 12, // 30: finance.v1.UOMCategoryService.ExportUOMCategories:output_type -> finance.v1.ExportUOMCategoriesResponse + 14, // 31: finance.v1.UOMCategoryService.ImportUOMCategories:output_type -> finance.v1.ImportUOMCategoriesResponse + 16, // 32: finance.v1.UOMCategoryService.DownloadUOMCategoryTemplate:output_type -> finance.v1.DownloadUOMCategoryTemplateResponse + 25, // [25:33] is the sub-list for method output_type + 17, // [17:25] is the sub-list for method input_type + 17, // [17:17] is the sub-list for extension type_name + 17, // [17:17] is the sub-list for extension extendee + 0, // [0:17] is the sub-list for field type_name +} + +func init() { file_finance_v1_uom_category_proto_init() } +func file_finance_v1_uom_category_proto_init() { + if File_finance_v1_uom_category_proto != nil { + return + } + file_finance_v1_uom_proto_init() + file_finance_v1_uom_category_proto_msgTypes[5].OneofWrappers = []any{} + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_finance_v1_uom_category_proto_rawDesc), len(file_finance_v1_uom_category_proto_rawDesc)), + NumEnums: 0, + NumMessages: 17, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_finance_v1_uom_category_proto_goTypes, + DependencyIndexes: file_finance_v1_uom_category_proto_depIdxs, + MessageInfos: file_finance_v1_uom_category_proto_msgTypes, + }.Build() + File_finance_v1_uom_category_proto = out.File + file_finance_v1_uom_category_proto_goTypes = nil + file_finance_v1_uom_category_proto_depIdxs = nil +} diff --git a/gen/finance/v1/uom_category.pb.gw.go b/gen/finance/v1/uom_category.pb.gw.go new file mode 100644 index 0000000..e78d5fd --- /dev/null +++ b/gen/finance/v1/uom_category.pb.gw.go @@ -0,0 +1,671 @@ +// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT. +// source: finance/v1/uom_category.proto + +/* +Package financev1 is a reverse proxy. + +It translates gRPC into RESTful JSON APIs. +*/ +package financev1 + +import ( + "context" + "errors" + "io" + "net/http" + + "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" + "github.com/grpc-ecosystem/grpc-gateway/v2/utilities" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/grpclog" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/proto" +) + +// Suppress "imported and not used" errors +var ( + _ codes.Code + _ io.Reader + _ status.Status + _ = errors.New + _ = runtime.String + _ = utilities.NewDoubleArray + _ = metadata.Join +) + +func request_UOMCategoryService_CreateUOMCategory_0(ctx context.Context, marshaler runtime.Marshaler, client UOMCategoryServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq CreateUOMCategoryRequest + metadata runtime.ServerMetadata + ) + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } + msg, err := client.CreateUOMCategory(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err +} + +func local_request_UOMCategoryService_CreateUOMCategory_0(ctx context.Context, marshaler runtime.Marshaler, server UOMCategoryServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq CreateUOMCategoryRequest + metadata runtime.ServerMetadata + ) + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + msg, err := server.CreateUOMCategory(ctx, &protoReq) + return msg, metadata, err +} + +func request_UOMCategoryService_GetUOMCategory_0(ctx context.Context, marshaler runtime.Marshaler, client UOMCategoryServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq GetUOMCategoryRequest + metadata runtime.ServerMetadata + err error + ) + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } + val, ok := pathParams["uom_category_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "uom_category_id") + } + protoReq.UomCategoryId, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "uom_category_id", err) + } + msg, err := client.GetUOMCategory(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err +} + +func local_request_UOMCategoryService_GetUOMCategory_0(ctx context.Context, marshaler runtime.Marshaler, server UOMCategoryServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq GetUOMCategoryRequest + metadata runtime.ServerMetadata + err error + ) + val, ok := pathParams["uom_category_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "uom_category_id") + } + protoReq.UomCategoryId, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "uom_category_id", err) + } + msg, err := server.GetUOMCategory(ctx, &protoReq) + return msg, metadata, err +} + +func request_UOMCategoryService_UpdateUOMCategory_0(ctx context.Context, marshaler runtime.Marshaler, client UOMCategoryServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq UpdateUOMCategoryRequest + metadata runtime.ServerMetadata + err error + ) + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } + val, ok := pathParams["uom_category_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "uom_category_id") + } + protoReq.UomCategoryId, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "uom_category_id", err) + } + msg, err := client.UpdateUOMCategory(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err +} + +func local_request_UOMCategoryService_UpdateUOMCategory_0(ctx context.Context, marshaler runtime.Marshaler, server UOMCategoryServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq UpdateUOMCategoryRequest + metadata runtime.ServerMetadata + err error + ) + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + val, ok := pathParams["uom_category_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "uom_category_id") + } + protoReq.UomCategoryId, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "uom_category_id", err) + } + msg, err := server.UpdateUOMCategory(ctx, &protoReq) + return msg, metadata, err +} + +func request_UOMCategoryService_DeleteUOMCategory_0(ctx context.Context, marshaler runtime.Marshaler, client UOMCategoryServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq DeleteUOMCategoryRequest + metadata runtime.ServerMetadata + err error + ) + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } + val, ok := pathParams["uom_category_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "uom_category_id") + } + protoReq.UomCategoryId, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "uom_category_id", err) + } + msg, err := client.DeleteUOMCategory(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err +} + +func local_request_UOMCategoryService_DeleteUOMCategory_0(ctx context.Context, marshaler runtime.Marshaler, server UOMCategoryServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq DeleteUOMCategoryRequest + metadata runtime.ServerMetadata + err error + ) + val, ok := pathParams["uom_category_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "uom_category_id") + } + protoReq.UomCategoryId, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "uom_category_id", err) + } + msg, err := server.DeleteUOMCategory(ctx, &protoReq) + return msg, metadata, err +} + +var filter_UOMCategoryService_ListUOMCategories_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} + +func request_UOMCategoryService_ListUOMCategories_0(ctx context.Context, marshaler runtime.Marshaler, client UOMCategoryServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq ListUOMCategoriesRequest + metadata runtime.ServerMetadata + ) + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_UOMCategoryService_ListUOMCategories_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + msg, err := client.ListUOMCategories(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err +} + +func local_request_UOMCategoryService_ListUOMCategories_0(ctx context.Context, marshaler runtime.Marshaler, server UOMCategoryServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq ListUOMCategoriesRequest + metadata runtime.ServerMetadata + ) + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_UOMCategoryService_ListUOMCategories_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + msg, err := server.ListUOMCategories(ctx, &protoReq) + return msg, metadata, err +} + +var filter_UOMCategoryService_ExportUOMCategories_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} + +func request_UOMCategoryService_ExportUOMCategories_0(ctx context.Context, marshaler runtime.Marshaler, client UOMCategoryServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq ExportUOMCategoriesRequest + metadata runtime.ServerMetadata + ) + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_UOMCategoryService_ExportUOMCategories_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + msg, err := client.ExportUOMCategories(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err +} + +func local_request_UOMCategoryService_ExportUOMCategories_0(ctx context.Context, marshaler runtime.Marshaler, server UOMCategoryServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq ExportUOMCategoriesRequest + metadata runtime.ServerMetadata + ) + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_UOMCategoryService_ExportUOMCategories_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + msg, err := server.ExportUOMCategories(ctx, &protoReq) + return msg, metadata, err +} + +func request_UOMCategoryService_ImportUOMCategories_0(ctx context.Context, marshaler runtime.Marshaler, client UOMCategoryServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq ImportUOMCategoriesRequest + metadata runtime.ServerMetadata + ) + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } + msg, err := client.ImportUOMCategories(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err +} + +func local_request_UOMCategoryService_ImportUOMCategories_0(ctx context.Context, marshaler runtime.Marshaler, server UOMCategoryServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq ImportUOMCategoriesRequest + metadata runtime.ServerMetadata + ) + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + msg, err := server.ImportUOMCategories(ctx, &protoReq) + return msg, metadata, err +} + +func request_UOMCategoryService_DownloadUOMCategoryTemplate_0(ctx context.Context, marshaler runtime.Marshaler, client UOMCategoryServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq DownloadUOMCategoryTemplateRequest + metadata runtime.ServerMetadata + ) + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } + msg, err := client.DownloadUOMCategoryTemplate(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err +} + +func local_request_UOMCategoryService_DownloadUOMCategoryTemplate_0(ctx context.Context, marshaler runtime.Marshaler, server UOMCategoryServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq DownloadUOMCategoryTemplateRequest + metadata runtime.ServerMetadata + ) + msg, err := server.DownloadUOMCategoryTemplate(ctx, &protoReq) + return msg, metadata, err +} + +// RegisterUOMCategoryServiceHandlerServer registers the http handlers for service UOMCategoryService to "mux". +// UnaryRPC :call UOMCategoryServiceServer directly. +// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. +// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterUOMCategoryServiceHandlerFromEndpoint instead. +// GRPC interceptors will not work for this type of registration. To use interceptors, you must use the "runtime.WithMiddlewares" option in the "runtime.NewServeMux" call. +func RegisterUOMCategoryServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux, server UOMCategoryServiceServer) error { + mux.Handle(http.MethodPost, pattern_UOMCategoryService_CreateUOMCategory_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/finance.v1.UOMCategoryService/CreateUOMCategory", runtime.WithHTTPPathPattern("/api/v1/finance/uom-categories")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_UOMCategoryService_CreateUOMCategory_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_UOMCategoryService_CreateUOMCategory_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodGet, pattern_UOMCategoryService_GetUOMCategory_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/finance.v1.UOMCategoryService/GetUOMCategory", runtime.WithHTTPPathPattern("/api/v1/finance/uom-categories/{uom_category_id}")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_UOMCategoryService_GetUOMCategory_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_UOMCategoryService_GetUOMCategory_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodPut, pattern_UOMCategoryService_UpdateUOMCategory_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/finance.v1.UOMCategoryService/UpdateUOMCategory", runtime.WithHTTPPathPattern("/api/v1/finance/uom-categories/{uom_category_id}")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_UOMCategoryService_UpdateUOMCategory_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_UOMCategoryService_UpdateUOMCategory_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodDelete, pattern_UOMCategoryService_DeleteUOMCategory_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/finance.v1.UOMCategoryService/DeleteUOMCategory", runtime.WithHTTPPathPattern("/api/v1/finance/uom-categories/{uom_category_id}")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_UOMCategoryService_DeleteUOMCategory_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_UOMCategoryService_DeleteUOMCategory_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodGet, pattern_UOMCategoryService_ListUOMCategories_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/finance.v1.UOMCategoryService/ListUOMCategories", runtime.WithHTTPPathPattern("/api/v1/finance/uom-categories")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_UOMCategoryService_ListUOMCategories_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_UOMCategoryService_ListUOMCategories_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodGet, pattern_UOMCategoryService_ExportUOMCategories_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/finance.v1.UOMCategoryService/ExportUOMCategories", runtime.WithHTTPPathPattern("/api/v1/finance/uom-categories/export")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_UOMCategoryService_ExportUOMCategories_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_UOMCategoryService_ExportUOMCategories_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodPost, pattern_UOMCategoryService_ImportUOMCategories_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/finance.v1.UOMCategoryService/ImportUOMCategories", runtime.WithHTTPPathPattern("/api/v1/finance/uom-categories/import")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_UOMCategoryService_ImportUOMCategories_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_UOMCategoryService_ImportUOMCategories_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodGet, pattern_UOMCategoryService_DownloadUOMCategoryTemplate_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/finance.v1.UOMCategoryService/DownloadUOMCategoryTemplate", runtime.WithHTTPPathPattern("/api/v1/finance/uom-categories/template")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_UOMCategoryService_DownloadUOMCategoryTemplate_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_UOMCategoryService_DownloadUOMCategoryTemplate_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + + return nil +} + +// RegisterUOMCategoryServiceHandlerFromEndpoint is same as RegisterUOMCategoryServiceHandler but +// automatically dials to "endpoint" and closes the connection when "ctx" gets done. +func RegisterUOMCategoryServiceHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) { + conn, err := grpc.NewClient(endpoint, opts...) + if err != nil { + return err + } + defer func() { + if err != nil { + if cerr := conn.Close(); cerr != nil { + grpclog.Errorf("Failed to close conn to %s: %v", endpoint, cerr) + } + return + } + go func() { + <-ctx.Done() + if cerr := conn.Close(); cerr != nil { + grpclog.Errorf("Failed to close conn to %s: %v", endpoint, cerr) + } + }() + }() + return RegisterUOMCategoryServiceHandler(ctx, mux, conn) +} + +// RegisterUOMCategoryServiceHandler registers the http handlers for service UOMCategoryService to "mux". +// The handlers forward requests to the grpc endpoint over "conn". +func RegisterUOMCategoryServiceHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error { + return RegisterUOMCategoryServiceHandlerClient(ctx, mux, NewUOMCategoryServiceClient(conn)) +} + +// RegisterUOMCategoryServiceHandlerClient registers the http handlers for service UOMCategoryService +// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "UOMCategoryServiceClient". +// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "UOMCategoryServiceClient" +// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in +// "UOMCategoryServiceClient" to call the correct interceptors. This client ignores the HTTP middlewares. +func RegisterUOMCategoryServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux, client UOMCategoryServiceClient) error { + mux.Handle(http.MethodPost, pattern_UOMCategoryService_CreateUOMCategory_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/finance.v1.UOMCategoryService/CreateUOMCategory", runtime.WithHTTPPathPattern("/api/v1/finance/uom-categories")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_UOMCategoryService_CreateUOMCategory_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_UOMCategoryService_CreateUOMCategory_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodGet, pattern_UOMCategoryService_GetUOMCategory_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/finance.v1.UOMCategoryService/GetUOMCategory", runtime.WithHTTPPathPattern("/api/v1/finance/uom-categories/{uom_category_id}")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_UOMCategoryService_GetUOMCategory_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_UOMCategoryService_GetUOMCategory_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodPut, pattern_UOMCategoryService_UpdateUOMCategory_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/finance.v1.UOMCategoryService/UpdateUOMCategory", runtime.WithHTTPPathPattern("/api/v1/finance/uom-categories/{uom_category_id}")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_UOMCategoryService_UpdateUOMCategory_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_UOMCategoryService_UpdateUOMCategory_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodDelete, pattern_UOMCategoryService_DeleteUOMCategory_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/finance.v1.UOMCategoryService/DeleteUOMCategory", runtime.WithHTTPPathPattern("/api/v1/finance/uom-categories/{uom_category_id}")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_UOMCategoryService_DeleteUOMCategory_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_UOMCategoryService_DeleteUOMCategory_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodGet, pattern_UOMCategoryService_ListUOMCategories_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/finance.v1.UOMCategoryService/ListUOMCategories", runtime.WithHTTPPathPattern("/api/v1/finance/uom-categories")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_UOMCategoryService_ListUOMCategories_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_UOMCategoryService_ListUOMCategories_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodGet, pattern_UOMCategoryService_ExportUOMCategories_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/finance.v1.UOMCategoryService/ExportUOMCategories", runtime.WithHTTPPathPattern("/api/v1/finance/uom-categories/export")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_UOMCategoryService_ExportUOMCategories_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_UOMCategoryService_ExportUOMCategories_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodPost, pattern_UOMCategoryService_ImportUOMCategories_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/finance.v1.UOMCategoryService/ImportUOMCategories", runtime.WithHTTPPathPattern("/api/v1/finance/uom-categories/import")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_UOMCategoryService_ImportUOMCategories_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_UOMCategoryService_ImportUOMCategories_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodGet, pattern_UOMCategoryService_DownloadUOMCategoryTemplate_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/finance.v1.UOMCategoryService/DownloadUOMCategoryTemplate", runtime.WithHTTPPathPattern("/api/v1/finance/uom-categories/template")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_UOMCategoryService_DownloadUOMCategoryTemplate_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_UOMCategoryService_DownloadUOMCategoryTemplate_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + return nil +} + +var ( + pattern_UOMCategoryService_CreateUOMCategory_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"api", "v1", "finance", "uom-categories"}, "")) + pattern_UOMCategoryService_GetUOMCategory_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4}, []string{"api", "v1", "finance", "uom-categories", "uom_category_id"}, "")) + pattern_UOMCategoryService_UpdateUOMCategory_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4}, []string{"api", "v1", "finance", "uom-categories", "uom_category_id"}, "")) + pattern_UOMCategoryService_DeleteUOMCategory_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4}, []string{"api", "v1", "finance", "uom-categories", "uom_category_id"}, "")) + pattern_UOMCategoryService_ListUOMCategories_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"api", "v1", "finance", "uom-categories"}, "")) + pattern_UOMCategoryService_ExportUOMCategories_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4}, []string{"api", "v1", "finance", "uom-categories", "export"}, "")) + pattern_UOMCategoryService_ImportUOMCategories_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4}, []string{"api", "v1", "finance", "uom-categories", "import"}, "")) + pattern_UOMCategoryService_DownloadUOMCategoryTemplate_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4}, []string{"api", "v1", "finance", "uom-categories", "template"}, "")) +) + +var ( + forward_UOMCategoryService_CreateUOMCategory_0 = runtime.ForwardResponseMessage + forward_UOMCategoryService_GetUOMCategory_0 = runtime.ForwardResponseMessage + forward_UOMCategoryService_UpdateUOMCategory_0 = runtime.ForwardResponseMessage + forward_UOMCategoryService_DeleteUOMCategory_0 = runtime.ForwardResponseMessage + forward_UOMCategoryService_ListUOMCategories_0 = runtime.ForwardResponseMessage + forward_UOMCategoryService_ExportUOMCategories_0 = runtime.ForwardResponseMessage + forward_UOMCategoryService_ImportUOMCategories_0 = runtime.ForwardResponseMessage + forward_UOMCategoryService_DownloadUOMCategoryTemplate_0 = runtime.ForwardResponseMessage +) diff --git a/gen/finance/v1/uom_category_grpc.pb.go b/gen/finance/v1/uom_category_grpc.pb.go new file mode 100644 index 0000000..ea953b3 --- /dev/null +++ b/gen/finance/v1/uom_category_grpc.pb.go @@ -0,0 +1,409 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.6.1 +// - protoc (unknown) +// source: finance/v1/uom_category.proto + +package financev1 + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.64.0 or later. +const _ = grpc.SupportPackageIsVersion9 + +const ( + UOMCategoryService_CreateUOMCategory_FullMethodName = "/finance.v1.UOMCategoryService/CreateUOMCategory" + UOMCategoryService_GetUOMCategory_FullMethodName = "/finance.v1.UOMCategoryService/GetUOMCategory" + UOMCategoryService_UpdateUOMCategory_FullMethodName = "/finance.v1.UOMCategoryService/UpdateUOMCategory" + UOMCategoryService_DeleteUOMCategory_FullMethodName = "/finance.v1.UOMCategoryService/DeleteUOMCategory" + UOMCategoryService_ListUOMCategories_FullMethodName = "/finance.v1.UOMCategoryService/ListUOMCategories" + UOMCategoryService_ExportUOMCategories_FullMethodName = "/finance.v1.UOMCategoryService/ExportUOMCategories" + UOMCategoryService_ImportUOMCategories_FullMethodName = "/finance.v1.UOMCategoryService/ImportUOMCategories" + UOMCategoryService_DownloadUOMCategoryTemplate_FullMethodName = "/finance.v1.UOMCategoryService/DownloadUOMCategoryTemplate" +) + +// UOMCategoryServiceClient is the client API for UOMCategoryService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +// +// UOMCategoryService provides CRUD operations for UOM Category master data. +type UOMCategoryServiceClient interface { + // CreateUOMCategory creates a new UOM category. + CreateUOMCategory(ctx context.Context, in *CreateUOMCategoryRequest, opts ...grpc.CallOption) (*CreateUOMCategoryResponse, error) + // GetUOMCategory retrieves a UOM category by ID. + GetUOMCategory(ctx context.Context, in *GetUOMCategoryRequest, opts ...grpc.CallOption) (*GetUOMCategoryResponse, error) + // UpdateUOMCategory updates an existing UOM category. + // Note: category_code is immutable and cannot be changed. + UpdateUOMCategory(ctx context.Context, in *UpdateUOMCategoryRequest, opts ...grpc.CallOption) (*UpdateUOMCategoryResponse, error) + // DeleteUOMCategory soft deletes a UOM category. + DeleteUOMCategory(ctx context.Context, in *DeleteUOMCategoryRequest, opts ...grpc.CallOption) (*DeleteUOMCategoryResponse, error) + // ListUOMCategories lists UOM categories with search, filter, and pagination. + ListUOMCategories(ctx context.Context, in *ListUOMCategoriesRequest, opts ...grpc.CallOption) (*ListUOMCategoriesResponse, error) + // ExportUOMCategories exports UOM categories to Excel file. + ExportUOMCategories(ctx context.Context, in *ExportUOMCategoriesRequest, opts ...grpc.CallOption) (*ExportUOMCategoriesResponse, error) + // ImportUOMCategories imports UOM categories from Excel file. + ImportUOMCategories(ctx context.Context, in *ImportUOMCategoriesRequest, opts ...grpc.CallOption) (*ImportUOMCategoriesResponse, error) + // DownloadUOMCategoryTemplate downloads the Excel import template. + DownloadUOMCategoryTemplate(ctx context.Context, in *DownloadUOMCategoryTemplateRequest, opts ...grpc.CallOption) (*DownloadUOMCategoryTemplateResponse, error) +} + +type uOMCategoryServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewUOMCategoryServiceClient(cc grpc.ClientConnInterface) UOMCategoryServiceClient { + return &uOMCategoryServiceClient{cc} +} + +func (c *uOMCategoryServiceClient) CreateUOMCategory(ctx context.Context, in *CreateUOMCategoryRequest, opts ...grpc.CallOption) (*CreateUOMCategoryResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(CreateUOMCategoryResponse) + err := c.cc.Invoke(ctx, UOMCategoryService_CreateUOMCategory_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *uOMCategoryServiceClient) GetUOMCategory(ctx context.Context, in *GetUOMCategoryRequest, opts ...grpc.CallOption) (*GetUOMCategoryResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetUOMCategoryResponse) + err := c.cc.Invoke(ctx, UOMCategoryService_GetUOMCategory_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *uOMCategoryServiceClient) UpdateUOMCategory(ctx context.Context, in *UpdateUOMCategoryRequest, opts ...grpc.CallOption) (*UpdateUOMCategoryResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(UpdateUOMCategoryResponse) + err := c.cc.Invoke(ctx, UOMCategoryService_UpdateUOMCategory_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *uOMCategoryServiceClient) DeleteUOMCategory(ctx context.Context, in *DeleteUOMCategoryRequest, opts ...grpc.CallOption) (*DeleteUOMCategoryResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(DeleteUOMCategoryResponse) + err := c.cc.Invoke(ctx, UOMCategoryService_DeleteUOMCategory_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *uOMCategoryServiceClient) ListUOMCategories(ctx context.Context, in *ListUOMCategoriesRequest, opts ...grpc.CallOption) (*ListUOMCategoriesResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ListUOMCategoriesResponse) + err := c.cc.Invoke(ctx, UOMCategoryService_ListUOMCategories_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *uOMCategoryServiceClient) ExportUOMCategories(ctx context.Context, in *ExportUOMCategoriesRequest, opts ...grpc.CallOption) (*ExportUOMCategoriesResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ExportUOMCategoriesResponse) + err := c.cc.Invoke(ctx, UOMCategoryService_ExportUOMCategories_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *uOMCategoryServiceClient) ImportUOMCategories(ctx context.Context, in *ImportUOMCategoriesRequest, opts ...grpc.CallOption) (*ImportUOMCategoriesResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ImportUOMCategoriesResponse) + err := c.cc.Invoke(ctx, UOMCategoryService_ImportUOMCategories_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *uOMCategoryServiceClient) DownloadUOMCategoryTemplate(ctx context.Context, in *DownloadUOMCategoryTemplateRequest, opts ...grpc.CallOption) (*DownloadUOMCategoryTemplateResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(DownloadUOMCategoryTemplateResponse) + err := c.cc.Invoke(ctx, UOMCategoryService_DownloadUOMCategoryTemplate_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// UOMCategoryServiceServer is the server API for UOMCategoryService service. +// All implementations must embed UnimplementedUOMCategoryServiceServer +// for forward compatibility. +// +// UOMCategoryService provides CRUD operations for UOM Category master data. +type UOMCategoryServiceServer interface { + // CreateUOMCategory creates a new UOM category. + CreateUOMCategory(context.Context, *CreateUOMCategoryRequest) (*CreateUOMCategoryResponse, error) + // GetUOMCategory retrieves a UOM category by ID. + GetUOMCategory(context.Context, *GetUOMCategoryRequest) (*GetUOMCategoryResponse, error) + // UpdateUOMCategory updates an existing UOM category. + // Note: category_code is immutable and cannot be changed. + UpdateUOMCategory(context.Context, *UpdateUOMCategoryRequest) (*UpdateUOMCategoryResponse, error) + // DeleteUOMCategory soft deletes a UOM category. + DeleteUOMCategory(context.Context, *DeleteUOMCategoryRequest) (*DeleteUOMCategoryResponse, error) + // ListUOMCategories lists UOM categories with search, filter, and pagination. + ListUOMCategories(context.Context, *ListUOMCategoriesRequest) (*ListUOMCategoriesResponse, error) + // ExportUOMCategories exports UOM categories to Excel file. + ExportUOMCategories(context.Context, *ExportUOMCategoriesRequest) (*ExportUOMCategoriesResponse, error) + // ImportUOMCategories imports UOM categories from Excel file. + ImportUOMCategories(context.Context, *ImportUOMCategoriesRequest) (*ImportUOMCategoriesResponse, error) + // DownloadUOMCategoryTemplate downloads the Excel import template. + DownloadUOMCategoryTemplate(context.Context, *DownloadUOMCategoryTemplateRequest) (*DownloadUOMCategoryTemplateResponse, error) + mustEmbedUnimplementedUOMCategoryServiceServer() +} + +// UnimplementedUOMCategoryServiceServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedUOMCategoryServiceServer struct{} + +func (UnimplementedUOMCategoryServiceServer) CreateUOMCategory(context.Context, *CreateUOMCategoryRequest) (*CreateUOMCategoryResponse, error) { + return nil, status.Error(codes.Unimplemented, "method CreateUOMCategory not implemented") +} +func (UnimplementedUOMCategoryServiceServer) GetUOMCategory(context.Context, *GetUOMCategoryRequest) (*GetUOMCategoryResponse, error) { + return nil, status.Error(codes.Unimplemented, "method GetUOMCategory not implemented") +} +func (UnimplementedUOMCategoryServiceServer) UpdateUOMCategory(context.Context, *UpdateUOMCategoryRequest) (*UpdateUOMCategoryResponse, error) { + return nil, status.Error(codes.Unimplemented, "method UpdateUOMCategory not implemented") +} +func (UnimplementedUOMCategoryServiceServer) DeleteUOMCategory(context.Context, *DeleteUOMCategoryRequest) (*DeleteUOMCategoryResponse, error) { + return nil, status.Error(codes.Unimplemented, "method DeleteUOMCategory not implemented") +} +func (UnimplementedUOMCategoryServiceServer) ListUOMCategories(context.Context, *ListUOMCategoriesRequest) (*ListUOMCategoriesResponse, error) { + return nil, status.Error(codes.Unimplemented, "method ListUOMCategories not implemented") +} +func (UnimplementedUOMCategoryServiceServer) ExportUOMCategories(context.Context, *ExportUOMCategoriesRequest) (*ExportUOMCategoriesResponse, error) { + return nil, status.Error(codes.Unimplemented, "method ExportUOMCategories not implemented") +} +func (UnimplementedUOMCategoryServiceServer) ImportUOMCategories(context.Context, *ImportUOMCategoriesRequest) (*ImportUOMCategoriesResponse, error) { + return nil, status.Error(codes.Unimplemented, "method ImportUOMCategories not implemented") +} +func (UnimplementedUOMCategoryServiceServer) DownloadUOMCategoryTemplate(context.Context, *DownloadUOMCategoryTemplateRequest) (*DownloadUOMCategoryTemplateResponse, error) { + return nil, status.Error(codes.Unimplemented, "method DownloadUOMCategoryTemplate not implemented") +} +func (UnimplementedUOMCategoryServiceServer) mustEmbedUnimplementedUOMCategoryServiceServer() {} +func (UnimplementedUOMCategoryServiceServer) testEmbeddedByValue() {} + +// UnsafeUOMCategoryServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to UOMCategoryServiceServer will +// result in compilation errors. +type UnsafeUOMCategoryServiceServer interface { + mustEmbedUnimplementedUOMCategoryServiceServer() +} + +func RegisterUOMCategoryServiceServer(s grpc.ServiceRegistrar, srv UOMCategoryServiceServer) { + // If the following call panics, it indicates UnimplementedUOMCategoryServiceServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&UOMCategoryService_ServiceDesc, srv) +} + +func _UOMCategoryService_CreateUOMCategory_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CreateUOMCategoryRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(UOMCategoryServiceServer).CreateUOMCategory(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: UOMCategoryService_CreateUOMCategory_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(UOMCategoryServiceServer).CreateUOMCategory(ctx, req.(*CreateUOMCategoryRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _UOMCategoryService_GetUOMCategory_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetUOMCategoryRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(UOMCategoryServiceServer).GetUOMCategory(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: UOMCategoryService_GetUOMCategory_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(UOMCategoryServiceServer).GetUOMCategory(ctx, req.(*GetUOMCategoryRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _UOMCategoryService_UpdateUOMCategory_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UpdateUOMCategoryRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(UOMCategoryServiceServer).UpdateUOMCategory(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: UOMCategoryService_UpdateUOMCategory_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(UOMCategoryServiceServer).UpdateUOMCategory(ctx, req.(*UpdateUOMCategoryRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _UOMCategoryService_DeleteUOMCategory_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DeleteUOMCategoryRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(UOMCategoryServiceServer).DeleteUOMCategory(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: UOMCategoryService_DeleteUOMCategory_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(UOMCategoryServiceServer).DeleteUOMCategory(ctx, req.(*DeleteUOMCategoryRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _UOMCategoryService_ListUOMCategories_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListUOMCategoriesRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(UOMCategoryServiceServer).ListUOMCategories(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: UOMCategoryService_ListUOMCategories_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(UOMCategoryServiceServer).ListUOMCategories(ctx, req.(*ListUOMCategoriesRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _UOMCategoryService_ExportUOMCategories_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ExportUOMCategoriesRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(UOMCategoryServiceServer).ExportUOMCategories(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: UOMCategoryService_ExportUOMCategories_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(UOMCategoryServiceServer).ExportUOMCategories(ctx, req.(*ExportUOMCategoriesRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _UOMCategoryService_ImportUOMCategories_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ImportUOMCategoriesRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(UOMCategoryServiceServer).ImportUOMCategories(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: UOMCategoryService_ImportUOMCategories_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(UOMCategoryServiceServer).ImportUOMCategories(ctx, req.(*ImportUOMCategoriesRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _UOMCategoryService_DownloadUOMCategoryTemplate_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DownloadUOMCategoryTemplateRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(UOMCategoryServiceServer).DownloadUOMCategoryTemplate(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: UOMCategoryService_DownloadUOMCategoryTemplate_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(UOMCategoryServiceServer).DownloadUOMCategoryTemplate(ctx, req.(*DownloadUOMCategoryTemplateRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// UOMCategoryService_ServiceDesc is the grpc.ServiceDesc for UOMCategoryService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var UOMCategoryService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "finance.v1.UOMCategoryService", + HandlerType: (*UOMCategoryServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "CreateUOMCategory", + Handler: _UOMCategoryService_CreateUOMCategory_Handler, + }, + { + MethodName: "GetUOMCategory", + Handler: _UOMCategoryService_GetUOMCategory_Handler, + }, + { + MethodName: "UpdateUOMCategory", + Handler: _UOMCategoryService_UpdateUOMCategory_Handler, + }, + { + MethodName: "DeleteUOMCategory", + Handler: _UOMCategoryService_DeleteUOMCategory_Handler, + }, + { + MethodName: "ListUOMCategories", + Handler: _UOMCategoryService_ListUOMCategories_Handler, + }, + { + MethodName: "ExportUOMCategories", + Handler: _UOMCategoryService_ExportUOMCategories_Handler, + }, + { + MethodName: "ImportUOMCategories", + Handler: _UOMCategoryService_ImportUOMCategories_Handler, + }, + { + MethodName: "DownloadUOMCategoryTemplate", + Handler: _UOMCategoryService_DownloadUOMCategoryTemplate_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "finance/v1/uom_category.proto", +} diff --git a/gen/openapi/finance/v1/uom.swagger.json b/gen/openapi/finance/v1/uom.swagger.json index 5b2f955..f101a7e 100644 --- a/gen/openapi/finance/v1/uom.swagger.json +++ b/gen/openapi/finance/v1/uom.swagger.json @@ -58,21 +58,6 @@ "required": false, "type": "string" }, - { - "name": "category", - "description": "Filter by category.\nUOM_CATEGORY_UNSPECIFIED (0) means \"show all categories\" (no filter).\n\n - UOM_CATEGORY_UNSPECIFIED: Default unspecified value - used as \"no filter\" in list/export requests.\n - UOM_CATEGORY_WEIGHT: Weight-based units (e.g., KG, GR, TON).\n - UOM_CATEGORY_LENGTH: Length-based units (e.g., MTR, CM, YARD).\n - UOM_CATEGORY_VOLUME: Volume-based units (e.g., LTR, ML).\n - UOM_CATEGORY_QUANTITY: Quantity-based units (e.g., PCS, BOX, SET).", - "in": "query", - "required": false, - "type": "string", - "enum": [ - "UOM_CATEGORY_UNSPECIFIED", - "UOM_CATEGORY_WEIGHT", - "UOM_CATEGORY_LENGTH", - "UOM_CATEGORY_VOLUME", - "UOM_CATEGORY_QUANTITY" - ], - "default": "UOM_CATEGORY_UNSPECIFIED" - }, { "name": "activeFilter", "description": "Filter by active status.\nACTIVE_FILTER_UNSPECIFIED (0) = show all, ACTIVE_FILTER_ACTIVE (1) = only active,\nACTIVE_FILTER_INACTIVE (2) = only inactive.\n\n - ACTIVE_FILTER_UNSPECIFIED: Show all records regardless of active status (default).\n - ACTIVE_FILTER_ACTIVE: Show only active records.\n - ACTIVE_FILTER_INACTIVE: Show only inactive records.", @@ -88,7 +73,7 @@ }, { "name": "sortBy", - "description": "Sort field: \"code\", \"name\", \"created_at\" (default: \"code\").", + "description": "Sort field: \"code\", \"name\", \"category\", \"created_at\" (default: \"code\").", "in": "query", "required": false, "type": "string" @@ -99,6 +84,13 @@ "in": "query", "required": false, "type": "string" + }, + { + "name": "uomCategoryId", + "description": "Filter by category ID (empty = no filter).", + "in": "query", + "required": false, + "type": "string" } ], "tags": [ @@ -157,21 +149,6 @@ } }, "parameters": [ - { - "name": "category", - "description": "Filter by category.\nUOM_CATEGORY_UNSPECIFIED (0) means \"export all categories\".\n\n - UOM_CATEGORY_UNSPECIFIED: Default unspecified value - used as \"no filter\" in list/export requests.\n - UOM_CATEGORY_WEIGHT: Weight-based units (e.g., KG, GR, TON).\n - UOM_CATEGORY_LENGTH: Length-based units (e.g., MTR, CM, YARD).\n - UOM_CATEGORY_VOLUME: Volume-based units (e.g., LTR, ML).\n - UOM_CATEGORY_QUANTITY: Quantity-based units (e.g., PCS, BOX, SET).", - "in": "query", - "required": false, - "type": "string", - "enum": [ - "UOM_CATEGORY_UNSPECIFIED", - "UOM_CATEGORY_WEIGHT", - "UOM_CATEGORY_LENGTH", - "UOM_CATEGORY_VOLUME", - "UOM_CATEGORY_QUANTITY" - ], - "default": "UOM_CATEGORY_UNSPECIFIED" - }, { "name": "activeFilter", "description": "Filter by active status.\nACTIVE_FILTER_UNSPECIFIED (0) = export all, ACTIVE_FILTER_ACTIVE (1) = only active,\nACTIVE_FILTER_INACTIVE (2) = only inactive.\n\n - ACTIVE_FILTER_UNSPECIFIED: Show all records regardless of active status (default).\n - ACTIVE_FILTER_ACTIVE: Show only active records.\n - ACTIVE_FILTER_INACTIVE: Show only inactive records.", @@ -184,6 +161,13 @@ "ACTIVE_FILTER_INACTIVE" ], "default": "ACTIVE_FILTER_UNSPECIFIED" + }, + { + "name": "uomCategoryId", + "description": "Filter by category ID (empty = export all categories).", + "in": "query", + "required": false, + "type": "string" } ], "tags": [ @@ -357,10 +341,6 @@ "type": "string", "description": "New display name (optional, 1-100 chars if provided).\nEmpty string means no change." }, - "uomCategory": { - "$ref": "#/definitions/v1UOMCategory", - "description": "New category (optional, cannot be UNSPECIFIED if provided).\nUse has_uom_category to check if this field is set." - }, "description": { "type": "string", "description": "New description (optional, max 500 chars)." @@ -368,6 +348,10 @@ "isActive": { "type": "boolean", "description": "New active status (optional)." + }, + "uomCategoryId": { + "type": "string", + "description": "New category ID (optional, UUID format)." } }, "description": "UpdateUOMRequest is the request for updating a UOM.\nNote: uom_code is immutable and cannot be changed after creation." @@ -469,13 +453,13 @@ "type": "string", "description": "Display name (1-100 chars)." }, - "uomCategory": { - "$ref": "#/definitions/v1UOMCategory", - "description": "Category (required, cannot be UNSPECIFIED)." - }, "description": { "type": "string", "description": "Optional description (max 500 chars)." + }, + "uomCategoryId": { + "type": "string", + "description": "Category ID (UUID, required - FK to mst_uom_category)." } }, "description": "CreateUOMRequest is the request for creating a new UOM." @@ -695,10 +679,6 @@ "type": "string", "description": "Display name (e.g., \"Kilogram\", \"Meter\", \"Pieces\")." }, - "uomCategory": { - "$ref": "#/definitions/v1UOMCategory", - "description": "Category of the UOM." - }, "description": { "type": "string", "description": "Optional description." @@ -710,22 +690,22 @@ "audit": { "$ref": "#/definitions/v1AuditInfo", "description": "Audit information." + }, + "uomCategoryId": { + "type": "string", + "description": "Category ID (FK to mst_uom_category)." + }, + "uomCategoryCode": { + "type": "string", + "description": "Category code (denormalized for display, e.g., \"WEIGHT\")." + }, + "uomCategoryName": { + "type": "string", + "description": "Category name (denormalized for display, e.g., \"Weight\")." } }, "description": "UOM represents a Unit of Measure entity." }, - "v1UOMCategory": { - "type": "string", - "enum": [ - "UOM_CATEGORY_UNSPECIFIED", - "UOM_CATEGORY_WEIGHT", - "UOM_CATEGORY_LENGTH", - "UOM_CATEGORY_VOLUME", - "UOM_CATEGORY_QUANTITY" - ], - "default": "UOM_CATEGORY_UNSPECIFIED", - "description": "UOMCategory represents the category of a unit of measure.\n\n - UOM_CATEGORY_UNSPECIFIED: Default unspecified value - used as \"no filter\" in list/export requests.\n - UOM_CATEGORY_WEIGHT: Weight-based units (e.g., KG, GR, TON).\n - UOM_CATEGORY_LENGTH: Length-based units (e.g., MTR, CM, YARD).\n - UOM_CATEGORY_VOLUME: Volume-based units (e.g., LTR, ML).\n - UOM_CATEGORY_QUANTITY: Quantity-based units (e.g., PCS, BOX, SET)." - }, "v1UpdateUOMResponse": { "type": "object", "properties": { diff --git a/gen/openapi/finance/v1/uom_category.swagger.json b/gen/openapi/finance/v1/uom_category.swagger.json new file mode 100644 index 0000000..9ec7fc6 --- /dev/null +++ b/gen/openapi/finance/v1/uom_category.swagger.json @@ -0,0 +1,704 @@ +{ + "swagger": "2.0", + "info": { + "title": "finance/v1/uom_category.proto", + "version": "version not set" + }, + "tags": [ + { + "name": "UOMCategoryService" + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "paths": { + "/api/v1/finance/uom-categories": { + "get": { + "summary": "ListUOMCategories lists UOM categories with search, filter, and pagination.", + "operationId": "UOMCategoryService_ListUOMCategories", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1ListUOMCategoriesResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "page", + "description": "Page number (1-indexed, default 1, min 1).", + "in": "query", + "required": false, + "type": "integer", + "format": "int32" + }, + { + "name": "pageSize", + "description": "Items per page (1-100, default 10).", + "in": "query", + "required": false, + "type": "integer", + "format": "int32" + }, + { + "name": "search", + "description": "Search query (searches in code, name, description).", + "in": "query", + "required": false, + "type": "string" + }, + { + "name": "activeFilter", + "description": "Filter by active status.\nACTIVE_FILTER_UNSPECIFIED (0) = show all, ACTIVE_FILTER_ACTIVE (1) = only active,\nACTIVE_FILTER_INACTIVE (2) = only inactive.\n\n - ACTIVE_FILTER_UNSPECIFIED: Show all records regardless of active status (default).\n - ACTIVE_FILTER_ACTIVE: Show only active records.\n - ACTIVE_FILTER_INACTIVE: Show only inactive records.", + "in": "query", + "required": false, + "type": "string", + "enum": [ + "ACTIVE_FILTER_UNSPECIFIED", + "ACTIVE_FILTER_ACTIVE", + "ACTIVE_FILTER_INACTIVE" + ], + "default": "ACTIVE_FILTER_UNSPECIFIED" + }, + { + "name": "sortBy", + "description": "Sort field: \"code\", \"name\", \"created_at\" (default: \"code\").", + "in": "query", + "required": false, + "type": "string" + }, + { + "name": "sortOrder", + "description": "Sort order: \"asc\", \"desc\" (default: \"asc\").", + "in": "query", + "required": false, + "type": "string" + } + ], + "tags": [ + "UOMCategoryService" + ] + }, + "post": { + "summary": "CreateUOMCategory creates a new UOM category.", + "operationId": "UOMCategoryService_CreateUOMCategory", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1CreateUOMCategoryResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "body", + "description": "CreateUOMCategoryRequest is the request for creating a new UOM category.", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/v1CreateUOMCategoryRequest" + } + } + ], + "tags": [ + "UOMCategoryService" + ] + } + }, + "/api/v1/finance/uom-categories/export": { + "get": { + "summary": "ExportUOMCategories exports UOM categories to Excel file.", + "operationId": "UOMCategoryService_ExportUOMCategories", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1ExportUOMCategoriesResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "activeFilter", + "description": "Filter by active status.\nACTIVE_FILTER_UNSPECIFIED (0) = export all, ACTIVE_FILTER_ACTIVE (1) = only active,\nACTIVE_FILTER_INACTIVE (2) = only inactive.\n\n - ACTIVE_FILTER_UNSPECIFIED: Show all records regardless of active status (default).\n - ACTIVE_FILTER_ACTIVE: Show only active records.\n - ACTIVE_FILTER_INACTIVE: Show only inactive records.", + "in": "query", + "required": false, + "type": "string", + "enum": [ + "ACTIVE_FILTER_UNSPECIFIED", + "ACTIVE_FILTER_ACTIVE", + "ACTIVE_FILTER_INACTIVE" + ], + "default": "ACTIVE_FILTER_UNSPECIFIED" + } + ], + "tags": [ + "UOMCategoryService" + ] + } + }, + "/api/v1/finance/uom-categories/import": { + "post": { + "summary": "ImportUOMCategories imports UOM categories from Excel file.", + "operationId": "UOMCategoryService_ImportUOMCategories", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1ImportUOMCategoriesResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "body", + "description": "ImportUOMCategoriesRequest is the request for importing UOM categories from Excel.", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/v1ImportUOMCategoriesRequest" + } + } + ], + "tags": [ + "UOMCategoryService" + ] + } + }, + "/api/v1/finance/uom-categories/template": { + "get": { + "summary": "DownloadUOMCategoryTemplate downloads the Excel import template.", + "operationId": "UOMCategoryService_DownloadUOMCategoryTemplate", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1DownloadUOMCategoryTemplateResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "tags": [ + "UOMCategoryService" + ] + } + }, + "/api/v1/finance/uom-categories/{uomCategoryId}": { + "get": { + "summary": "GetUOMCategory retrieves a UOM category by ID.", + "operationId": "UOMCategoryService_GetUOMCategory", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1GetUOMCategoryResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "uomCategoryId", + "description": "UOM category ID (UUID format).", + "in": "path", + "required": true, + "type": "string" + } + ], + "tags": [ + "UOMCategoryService" + ] + }, + "delete": { + "summary": "DeleteUOMCategory soft deletes a UOM category.", + "operationId": "UOMCategoryService_DeleteUOMCategory", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1DeleteUOMCategoryResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "uomCategoryId", + "description": "UOM category ID to delete (UUID format).", + "in": "path", + "required": true, + "type": "string" + } + ], + "tags": [ + "UOMCategoryService" + ] + }, + "put": { + "summary": "UpdateUOMCategory updates an existing UOM category.\nNote: category_code is immutable and cannot be changed.", + "operationId": "UOMCategoryService_UpdateUOMCategory", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1UpdateUOMCategoryResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "uomCategoryId", + "description": "UOM category ID to update (UUID format).", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/UOMCategoryServiceUpdateUOMCategoryBody" + } + } + ], + "tags": [ + "UOMCategoryService" + ] + } + } + }, + "definitions": { + "UOMCategoryServiceUpdateUOMCategoryBody": { + "type": "object", + "properties": { + "categoryName": { + "type": "string", + "description": "New display name (optional, 1-100 chars if provided)." + }, + "description": { + "type": "string", + "description": "New description (optional, max 500 chars)." + }, + "isActive": { + "type": "boolean", + "description": "New active status (optional)." + } + }, + "description": "UpdateUOMCategoryRequest is the request for updating a UOM category.\nNote: category_code is immutable and cannot be changed after creation." + }, + "protobufAny": { + "type": "object", + "properties": { + "@type": { + "type": "string" + } + }, + "additionalProperties": {} + }, + "rpcStatus": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + }, + "details": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/protobufAny" + } + } + } + }, + "v1ActiveFilter": { + "type": "string", + "enum": [ + "ACTIVE_FILTER_UNSPECIFIED", + "ACTIVE_FILTER_ACTIVE", + "ACTIVE_FILTER_INACTIVE" + ], + "default": "ACTIVE_FILTER_UNSPECIFIED", + "description": "ActiveFilter represents filter options for is_active field.\n\n - ACTIVE_FILTER_UNSPECIFIED: Show all records regardless of active status (default).\n - ACTIVE_FILTER_ACTIVE: Show only active records.\n - ACTIVE_FILTER_INACTIVE: Show only inactive records." + }, + "v1AuditInfo": { + "type": "object", + "properties": { + "createdAt": { + "type": "string", + "description": "Timestamp when the record was created (ISO 8601)." + }, + "createdBy": { + "type": "string", + "description": "User who created the record." + }, + "updatedAt": { + "type": "string", + "description": "Timestamp when the record was last updated (ISO 8601)." + }, + "updatedBy": { + "type": "string", + "description": "User who last updated the record." + } + }, + "description": "AuditInfo contains audit trail information." + }, + "v1BaseResponse": { + "type": "object", + "properties": { + "validationErrors": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/v1ValidationError" + }, + "description": "List of validation errors if any." + }, + "statusCode": { + "type": "string", + "description": "HTTP-like status code (e.g., \"200\", \"400\", \"404\", \"500\")." + }, + "isSuccess": { + "type": "boolean", + "description": "Indicates if the operation was successful." + }, + "message": { + "type": "string", + "description": "Human-readable message describing the result." + } + }, + "description": "BaseResponse is the standard response wrapper for all API responses." + }, + "v1CreateUOMCategoryRequest": { + "type": "object", + "properties": { + "categoryCode": { + "type": "string", + "description": "Unique code (uppercase, alphanumeric with underscore, 1-20 chars).\nMust start with an uppercase letter. Immutable after creation." + }, + "categoryName": { + "type": "string", + "description": "Display name (1-100 chars)." + }, + "description": { + "type": "string", + "description": "Optional description (max 500 chars)." + } + }, + "description": "CreateUOMCategoryRequest is the request for creating a new UOM category." + }, + "v1CreateUOMCategoryResponse": { + "type": "object", + "properties": { + "base": { + "$ref": "#/definitions/v1BaseResponse", + "description": "Standard response metadata." + }, + "data": { + "$ref": "#/definitions/v1UOMCategory", + "description": "Created UOM category data." + } + }, + "description": "CreateUOMCategoryResponse is the response for creating a UOM category." + }, + "v1DeleteUOMCategoryResponse": { + "type": "object", + "properties": { + "base": { + "$ref": "#/definitions/v1BaseResponse", + "description": "Standard response metadata." + } + }, + "description": "DeleteUOMCategoryResponse is the response for deleting a UOM category." + }, + "v1DownloadUOMCategoryTemplateResponse": { + "type": "object", + "properties": { + "base": { + "$ref": "#/definitions/v1BaseResponse", + "description": "Standard response metadata." + }, + "fileContent": { + "type": "string", + "format": "byte", + "description": "Excel template file content as bytes (.xlsx format)." + }, + "fileName": { + "type": "string", + "description": "Suggested filename." + } + }, + "description": "DownloadUOMCategoryTemplateResponse is the response containing the template file." + }, + "v1ExportUOMCategoriesResponse": { + "type": "object", + "properties": { + "base": { + "$ref": "#/definitions/v1BaseResponse", + "description": "Standard response metadata." + }, + "fileContent": { + "type": "string", + "format": "byte", + "description": "Excel file content as bytes (.xlsx format)." + }, + "fileName": { + "type": "string", + "description": "Suggested filename." + } + }, + "description": "ExportUOMCategoriesResponse is the response containing the Excel file." + }, + "v1GetUOMCategoryResponse": { + "type": "object", + "properties": { + "base": { + "$ref": "#/definitions/v1BaseResponse", + "description": "Standard response metadata." + }, + "data": { + "$ref": "#/definitions/v1UOMCategory", + "description": "UOM category data." + } + }, + "description": "GetUOMCategoryResponse is the response for getting a UOM category." + }, + "v1ImportError": { + "type": "object", + "properties": { + "rowNumber": { + "type": "integer", + "format": "int32", + "description": "Row number in the Excel file." + }, + "field": { + "type": "string", + "description": "Field that caused the error." + }, + "message": { + "type": "string", + "description": "Error message." + } + }, + "description": "ImportError represents a single import error." + }, + "v1ImportUOMCategoriesRequest": { + "type": "object", + "properties": { + "fileContent": { + "type": "string", + "format": "byte", + "description": "Excel file content as bytes (.xlsx or .xls format)." + }, + "fileName": { + "type": "string", + "description": "Original filename (for format detection).\nMust be a simple filename without path separators." + }, + "duplicateAction": { + "type": "string", + "description": "Required. How to handle duplicates: \"skip\", \"update\", \"error\"." + } + }, + "description": "ImportUOMCategoriesRequest is the request for importing UOM categories from Excel." + }, + "v1ImportUOMCategoriesResponse": { + "type": "object", + "properties": { + "base": { + "$ref": "#/definitions/v1BaseResponse", + "description": "Standard response metadata." + }, + "successCount": { + "type": "integer", + "format": "int32", + "description": "Number of successfully imported records." + }, + "skippedCount": { + "type": "integer", + "format": "int32", + "description": "Number of skipped records (duplicates with skip action)." + }, + "updatedCount": { + "type": "integer", + "format": "int32", + "description": "Number of updated records (duplicates with update action)." + }, + "failedCount": { + "type": "integer", + "format": "int32", + "description": "Number of failed records." + }, + "errors": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/v1ImportError" + }, + "description": "Details of failed records." + } + }, + "description": "ImportUOMCategoriesResponse is the response for importing UOM categories." + }, + "v1ListUOMCategoriesResponse": { + "type": "object", + "properties": { + "base": { + "$ref": "#/definitions/v1BaseResponse", + "description": "Standard response metadata." + }, + "data": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/v1UOMCategory" + }, + "description": "List of UOM categories." + }, + "pagination": { + "$ref": "#/definitions/v1PaginationResponse", + "description": "Pagination metadata." + } + }, + "description": "ListUOMCategoriesResponse is the response for listing UOM categories." + }, + "v1PaginationResponse": { + "type": "object", + "properties": { + "currentPage": { + "type": "integer", + "format": "int32", + "description": "Current page number." + }, + "pageSize": { + "type": "integer", + "format": "int32", + "description": "Number of items per page." + }, + "totalItems": { + "type": "string", + "format": "int64", + "description": "Total number of items across all pages." + }, + "totalPages": { + "type": "integer", + "format": "int32", + "description": "Total number of pages." + } + }, + "description": "PaginationResponse contains pagination metadata for list responses." + }, + "v1UOMCategory": { + "type": "object", + "properties": { + "uomCategoryId": { + "type": "string", + "description": "Unique identifier (UUID)." + }, + "categoryCode": { + "type": "string", + "description": "Unique code (e.g., \"WEIGHT\", \"LENGTH\", \"VOLUME\"). Immutable after creation." + }, + "categoryName": { + "type": "string", + "description": "Display name (e.g., \"Weight\", \"Length\", \"Volume\")." + }, + "description": { + "type": "string", + "description": "Optional description." + }, + "isActive": { + "type": "boolean", + "description": "Whether the category is active." + }, + "audit": { + "$ref": "#/definitions/v1AuditInfo", + "description": "Audit information." + } + }, + "description": "UOMCategory represents a Unit of Measure Category entity.\nUsed to classify UOMs (e.g., Weight, Length, Volume, Quantity, Time, Energy)." + }, + "v1UpdateUOMCategoryResponse": { + "type": "object", + "properties": { + "base": { + "$ref": "#/definitions/v1BaseResponse", + "description": "Standard response metadata." + }, + "data": { + "$ref": "#/definitions/v1UOMCategory", + "description": "Updated UOM category data." + } + }, + "description": "UpdateUOMCategoryResponse is the response for updating a UOM category." + }, + "v1ValidationError": { + "type": "object", + "properties": { + "field": { + "type": "string", + "description": "The field name that failed validation." + }, + "message": { + "type": "string", + "description": "The validation error message." + } + }, + "description": "ValidationError represents a single field validation error." + } + } +} diff --git a/services/finance/cmd/server/main.go b/services/finance/cmd/server/main.go index 90c0bc3..4e9e7e2 100644 --- a/services/finance/cmd/server/main.go +++ b/services/finance/cmd/server/main.go @@ -73,9 +73,10 @@ func run() error { rmCategoryRepo := postgres.NewRMCategoryRepository(db) parameterRepo := postgres.NewParameterRepository(db) formulaRepo := postgres.NewFormulaRepository(db) + uomCategoryRepo := postgres.NewUOMCategoryRepository(db) // Setup gRPC handlers - uomHandler, err := grpcdelivery.NewUOMHandler(uomRepo, uomCache) + uomHandler, err := grpcdelivery.NewUOMHandler(uomRepo, uomCategoryRepo, uomCache) if err != nil { return err } @@ -95,8 +96,13 @@ func run() error { return err } + uomCategoryHandler, err := grpcdelivery.NewUOMCategoryHandler(uomCategoryRepo) + if err != nil { + return err + } + // Setup and start servers - return startServers(ctx, cfg, uomHandler, rmCategoryHandler, parameterHandler, formulaHandler, tokenBlacklist) + return startServers(ctx, cfg, uomHandler, rmCategoryHandler, parameterHandler, formulaHandler, uomCategoryHandler, tokenBlacklist) } // setupLogger configures the application logger. @@ -193,7 +199,7 @@ func closeAuthRedis(bl *redisinfra.TokenBlacklist) { } // startServers starts the gRPC and HTTP servers and handles graceful shutdown. -func startServers(ctx context.Context, cfg *config.Config, uomHandler *grpcdelivery.UOMHandler, rmCategoryHandler *grpcdelivery.RMCategoryHandler, parameterHandler *grpcdelivery.ParameterHandler, formulaHandler *grpcdelivery.FormulaHandler, tokenBlacklist *redisinfra.TokenBlacklist) error { +func startServers(ctx context.Context, cfg *config.Config, uomHandler *grpcdelivery.UOMHandler, rmCategoryHandler *grpcdelivery.RMCategoryHandler, parameterHandler *grpcdelivery.ParameterHandler, formulaHandler *grpcdelivery.FormulaHandler, uomCategoryHandler *grpcdelivery.UOMCategoryHandler, tokenBlacklist *redisinfra.TokenBlacklist) error { // Setup gRPC server with JWT auth and token blacklist grpcServer, err := grpcdelivery.NewServer(&cfg.Server, nil, &cfg.JWT, tokenBlacklist) if err != nil { @@ -205,6 +211,7 @@ func startServers(ctx context.Context, cfg *config.Config, uomHandler *grpcdeliv financev1.RegisterRMCategoryServiceServer(grpcServer.GRPCServer(), rmCategoryHandler) financev1.RegisterParameterServiceServer(grpcServer.GRPCServer(), parameterHandler) financev1.RegisterFormulaServiceServer(grpcServer.GRPCServer(), formulaHandler) + financev1.RegisterUOMCategoryServiceServer(grpcServer.GRPCServer(), uomCategoryHandler) // Start gRPC server go func() { diff --git a/services/finance/internal/application/uom/create_handler.go b/services/finance/internal/application/uom/create_handler.go index 9166e1f..f9bbba5 100644 --- a/services/finance/internal/application/uom/create_handler.go +++ b/services/finance/internal/application/uom/create_handler.go @@ -4,16 +4,18 @@ package uom import ( "context" + "github.com/google/uuid" + "github.com/mutugading/goapps-backend/services/finance/internal/domain/uom" ) // CreateCommand represents the create UOM command. type CreateCommand struct { - UOMCode string - UOMName string - UOMCategory string - Description string - CreatedBy string + UOMCode string + UOMName string + UOMCategoryID string + Description string + CreatedBy string } // CreateHandler handles the CreateUOM command. @@ -34,9 +36,9 @@ func (h *CreateHandler) Handle(ctx context.Context, cmd CreateCommand) (*uom.UOM return nil, err } - category, err := uom.NewCategory(cmd.UOMCategory) + categoryID, err := uuid.Parse(cmd.UOMCategoryID) if err != nil { - return nil, err + return nil, uom.ErrInvalidCategory } // 2. Check for duplicates @@ -49,7 +51,7 @@ func (h *CreateHandler) Handle(ctx context.Context, cmd CreateCommand) (*uom.UOM } // 3. Create domain entity - entity, err := uom.NewUOM(code, cmd.UOMName, category, cmd.Description, cmd.CreatedBy) + entity, err := uom.NewUOM(code, cmd.UOMName, categoryID, cmd.Description, cmd.CreatedBy) if err != nil { return nil, err } diff --git a/services/finance/internal/application/uom/export_handler.go b/services/finance/internal/application/uom/export_handler.go index 5477df1..452ad26 100644 --- a/services/finance/internal/application/uom/export_handler.go +++ b/services/finance/internal/application/uom/export_handler.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" + "github.com/google/uuid" "github.com/rs/zerolog/log" "github.com/xuri/excelize/v2" @@ -14,8 +15,8 @@ import ( // ExportQuery represents the export UOMs query. type ExportQuery struct { - Category *string - IsActive *bool + CategoryID *string + IsActive *bool } // ExportResult represents the export UOMs result. @@ -72,12 +73,12 @@ func (ew *excelWriter) error() error { func buildExportFilter(query ExportQuery) (uom.ExportFilter, error) { filter := uom.ExportFilter{} - if query.Category != nil { - cat, err := uom.NewCategory(*query.Category) + if query.CategoryID != nil && *query.CategoryID != "" { + parsed, err := uuid.Parse(*query.CategoryID) if err != nil { - return filter, err + return filter, uom.ErrInvalidCategory } - filter.Category = &cat + filter.CategoryID = &parsed } filter.IsActive = query.IsActive @@ -164,7 +165,7 @@ func (h *ExportHandler) Handle(ctx context.Context, query ExportQuery) (result * writer.setCellValue(fmt.Sprintf("A%d", row), i+1) writer.setCellValue(fmt.Sprintf("B%d", row), u.Code().String()) writer.setCellValue(fmt.Sprintf("C%d", row), u.Name()) - writer.setCellValue(fmt.Sprintf("D%d", row), u.Category().String()) + writer.setCellValue(fmt.Sprintf("D%d", row), u.CategoryInfo().Code()) writer.setCellValue(fmt.Sprintf("E%d", row), u.Description()) writer.setCellValue(fmt.Sprintf("F%d", row), u.IsActive()) writer.setCellValue(fmt.Sprintf("G%d", row), u.CreatedAt().Format("2006-01-02 15:04:05")) diff --git a/services/finance/internal/application/uom/handlers_test.go b/services/finance/internal/application/uom/handlers_test.go index dfda8f0..b0fb289 100644 --- a/services/finance/internal/application/uom/handlers_test.go +++ b/services/finance/internal/application/uom/handlers_test.go @@ -14,7 +14,7 @@ import ( uomdomain "github.com/mutugading/goapps-backend/services/finance/internal/domain/uom" ) -// MockRepository is a mock implementation of uom.Repository +// MockRepository is a mock implementation of uom.Repository. type MockRepository struct { mock.Mock } @@ -71,17 +71,19 @@ func (m *MockRepository) ExistsByID(ctx context.Context, id uuid.UUID) (bool, er } func TestCreateHandler_Handle(t *testing.T) { + categoryID := uuid.New() + t.Run("success - creates new UOM", func(t *testing.T) { mockRepo := new(MockRepository) handler := uom.NewCreateHandler(mockRepo) ctx := context.Background() cmd := uom.CreateCommand{ - UOMCode: "KG", - UOMName: "Kilogram", - UOMCategory: "WEIGHT", - Description: "Weight in kilograms", - CreatedBy: "admin", + UOMCode: "KG", + UOMName: "Kilogram", + UOMCategoryID: categoryID.String(), + Description: "Weight in kilograms", + CreatedBy: "admin", } // Setup expectations @@ -105,10 +107,10 @@ func TestCreateHandler_Handle(t *testing.T) { ctx := context.Background() cmd := uom.CreateCommand{ - UOMCode: "KG", - UOMName: "Kilogram", - UOMCategory: "WEIGHT", - CreatedBy: "admin", + UOMCode: "KG", + UOMName: "Kilogram", + UOMCategoryID: categoryID.String(), + CreatedBy: "admin", } mockRepo.On("ExistsByCode", ctx, mock.AnythingOfType("uom.Code")).Return(true, nil) @@ -126,16 +128,35 @@ func TestCreateHandler_Handle(t *testing.T) { ctx := context.Background() cmd := uom.CreateCommand{ - UOMCode: "invalid", - UOMName: "Test", - UOMCategory: "WEIGHT", - CreatedBy: "admin", + UOMCode: "invalid", + UOMName: "Test", + UOMCategoryID: categoryID.String(), + CreatedBy: "admin", + } + + result, err := handler.Handle(ctx, cmd) + + assert.Nil(t, result) + assert.Error(t, err) + }) + + t.Run("error - invalid category ID", func(t *testing.T) { + mockRepo := new(MockRepository) + handler := uom.NewCreateHandler(mockRepo) + ctx := context.Background() + + cmd := uom.CreateCommand{ + UOMCode: "KG", + UOMName: "Kilogram", + UOMCategoryID: "not-a-uuid", + CreatedBy: "admin", } result, err := handler.Handle(ctx, cmd) assert.Nil(t, result) assert.Error(t, err) + assert.ErrorIs(t, err, uomdomain.ErrInvalidCategory) }) } @@ -147,8 +168,8 @@ func TestGetHandler_Handle(t *testing.T) { id := uuid.New() code, _ := uomdomain.NewCode("KG") - category, _ := uomdomain.NewCategory("WEIGHT") - expected, _ := uomdomain.NewUOM(code, "Kilogram", category, "Weight", "admin") + categoryID := uuid.New() + expected, _ := uomdomain.NewUOM(code, "Kilogram", categoryID, "Weight", "admin") mockRepo.On("GetByID", ctx, id).Return(expected, nil) @@ -232,12 +253,10 @@ func TestListHandler_Handle(t *testing.T) { ctx := context.Background() code1, _ := uomdomain.NewCode("KG") - cat1, _ := uomdomain.NewCategory("WEIGHT") - uom1, _ := uomdomain.NewUOM(code1, "Kilogram", cat1, "", "admin") + uom1, _ := uomdomain.NewUOM(code1, "Kilogram", uuid.New(), "", "admin") code2, _ := uomdomain.NewCode("LTR") - cat2, _ := uomdomain.NewCategory("VOLUME") - uom2, _ := uomdomain.NewUOM(code2, "Liter", cat2, "", "admin") + uom2, _ := uomdomain.NewUOM(code2, "Liter", uuid.New(), "", "admin") mockRepo.On("List", ctx, mock.AnythingOfType("uom.ListFilter")).Return( []*uomdomain.UOM{uom1, uom2}, diff --git a/services/finance/internal/application/uom/import_handler.go b/services/finance/internal/application/uom/import_handler.go index b282f96..5ed45c5 100644 --- a/services/finance/internal/application/uom/import_handler.go +++ b/services/finance/internal/application/uom/import_handler.go @@ -8,6 +8,7 @@ import ( "path/filepath" "strings" + "github.com/google/uuid" "github.com/rs/zerolog/log" "github.com/xuri/excelize/v2" @@ -41,12 +42,13 @@ type ImportError struct { // ImportHandler handles the ImportUOMs command. type ImportHandler struct { - repo uom.Repository + repo uom.Repository + catRepo uom.CategoryRepository } // NewImportHandler creates a new ImportHandler. -func NewImportHandler(repo uom.Repository) *ImportHandler { - return &ImportHandler{repo: repo} +func NewImportHandler(repo uom.Repository, catRepo uom.CategoryRepository) *ImportHandler { + return &ImportHandler{repo: repo, catRepo: catRepo} } // Handle executes the import UOMs command. @@ -111,19 +113,19 @@ func (h *ImportHandler) parseExcelFile(content []byte, fileName string) ([][]str // rowData holds parsed row values. type rowData struct { - code string - name string - category string - description string + code string + name string + categoryCode string + description string } // parseRow extracts cell values from a row. func parseRow(row []string) rowData { return rowData{ - code: getCell(row, 0), - name: getCell(row, 1), - category: getCell(row, 2), - description: getCell(row, 3), + code: getCell(row, 0), + name: getCell(row, 1), + categoryCode: getCell(row, 2), + description: getCell(row, 3), } } @@ -132,7 +134,7 @@ func (h *ImportHandler) processRow(ctx context.Context, row []string, rowNum int data := parseRow(row) // Validate fields - code, category, err := h.validateRowData(data, rowNum, result) + code, categoryID, err := h.validateRowData(ctx, data, rowNum, result) if err != nil { return // Error already recorded in result } @@ -150,16 +152,16 @@ func (h *ImportHandler) processRow(ctx context.Context, row []string, rowNum int } if exists { - h.handleDuplicate(ctx, code, data, rowNum, cmd, result) + h.handleDuplicate(ctx, code, data, categoryID, rowNum, cmd, result) return } // Create new UOM - h.createUOM(ctx, code, category, data, rowNum, cmd.CreatedBy, result) + h.createUOM(ctx, code, categoryID, data, rowNum, cmd.CreatedBy, result) } // validateRowData validates the row data and returns domain objects. -func (h *ImportHandler) validateRowData(data rowData, rowNum int32, result *ImportResult) (uom.Code, uom.Category, error) { +func (h *ImportHandler) validateRowData(ctx context.Context, data rowData, rowNum int32, result *ImportResult) (uom.Code, uuid.UUID, error) { // Validate code code, err := uom.NewCode(data.code) if err != nil { @@ -169,19 +171,29 @@ func (h *ImportHandler) validateRowData(data rowData, rowNum int32, result *Impo Field: "uom_code", Message: err.Error(), }) - return uom.Code{}, "", err + return uom.Code{}, uuid.Nil, err } - // Validate category - category, err := uom.NewCategory(data.category) + // Resolve category code to ID + if data.categoryCode == "" { + result.FailedCount++ + result.Errors = append(result.Errors, ImportError{ + RowNumber: rowNum, + Field: "uom_category", + Message: "category code cannot be empty", + }) + return uom.Code{}, uuid.Nil, fmt.Errorf("category code cannot be empty") + } + + categoryID, err := h.catRepo.GetCategoryIDByCode(ctx, strings.ToUpper(data.categoryCode)) if err != nil { result.FailedCount++ result.Errors = append(result.Errors, ImportError{ RowNumber: rowNum, Field: "uom_category", - Message: err.Error(), + Message: fmt.Sprintf("invalid category code %q: %v", data.categoryCode, err), }) - return uom.Code{}, "", err + return uom.Code{}, uuid.Nil, err } // Validate name @@ -192,19 +204,19 @@ func (h *ImportHandler) validateRowData(data rowData, rowNum int32, result *Impo Field: "uom_name", Message: "name cannot be empty", }) - return uom.Code{}, "", fmt.Errorf("name cannot be empty") + return uom.Code{}, uuid.Nil, fmt.Errorf("name cannot be empty") } - return code, category, nil + return code, categoryID, nil } // handleDuplicate handles a duplicate code based on the specified action. -func (h *ImportHandler) handleDuplicate(ctx context.Context, code uom.Code, data rowData, rowNum int32, cmd ImportCommand, result *ImportResult) { +func (h *ImportHandler) handleDuplicate(ctx context.Context, code uom.Code, data rowData, categoryID uuid.UUID, rowNum int32, cmd ImportCommand, result *ImportResult) { switch cmd.DuplicateAction { case "skip": result.SkippedCount++ case "update": - h.updateExisting(ctx, code, data, rowNum, cmd.CreatedBy, result) + h.updateExisting(ctx, code, data, categoryID, rowNum, cmd.CreatedBy, result) case "error": result.FailedCount++ result.Errors = append(result.Errors, ImportError{ @@ -219,7 +231,7 @@ func (h *ImportHandler) handleDuplicate(ctx context.Context, code uom.Code, data } // updateExisting updates an existing UOM. -func (h *ImportHandler) updateExisting(ctx context.Context, code uom.Code, data rowData, rowNum int32, updatedBy string, result *ImportResult) { +func (h *ImportHandler) updateExisting(ctx context.Context, code uom.Code, data rowData, categoryID uuid.UUID, rowNum int32, updatedBy string, result *ImportResult) { existing, err := h.repo.GetByCode(ctx, code) if err != nil { result.FailedCount++ @@ -231,18 +243,7 @@ func (h *ImportHandler) updateExisting(ctx context.Context, code uom.Code, data return } - category, err := uom.NewCategory(data.category) - if err != nil { - result.FailedCount++ - result.Errors = append(result.Errors, ImportError{ - RowNumber: rowNum, - Field: "uom_category", - Message: err.Error(), - }) - return - } - - if err := existing.Update(&data.name, &category, &data.description, nil, updatedBy); err != nil { + if err := existing.Update(&data.name, &categoryID, &data.description, nil, updatedBy); err != nil { result.FailedCount++ result.Errors = append(result.Errors, ImportError{ RowNumber: rowNum, @@ -266,8 +267,8 @@ func (h *ImportHandler) updateExisting(ctx context.Context, code uom.Code, data } // createUOM creates a new UOM. -func (h *ImportHandler) createUOM(ctx context.Context, code uom.Code, category uom.Category, data rowData, rowNum int32, createdBy string, result *ImportResult) { - entity, err := uom.NewUOM(code, data.name, category, data.description, createdBy) +func (h *ImportHandler) createUOM(ctx context.Context, code uom.Code, categoryID uuid.UUID, data rowData, rowNum int32, createdBy string, result *ImportResult) { + entity, err := uom.NewUOM(code, data.name, categoryID, data.description, createdBy) if err != nil { result.FailedCount++ result.Errors = append(result.Errors, ImportError{ diff --git a/services/finance/internal/application/uom/list_handler.go b/services/finance/internal/application/uom/list_handler.go index f231ee7..9f02a63 100644 --- a/services/finance/internal/application/uom/list_handler.go +++ b/services/finance/internal/application/uom/list_handler.go @@ -4,19 +4,21 @@ package uom import ( "context" + "github.com/google/uuid" + "github.com/mutugading/goapps-backend/services/finance/internal/domain/uom" "github.com/mutugading/goapps-backend/services/finance/pkg/safeconv" ) // ListQuery represents the list UOMs query. type ListQuery struct { - Page int - PageSize int - Search string - Category *string - IsActive *bool - SortBy string - SortOrder string + Page int + PageSize int + Search string + CategoryID *string + IsActive *bool + SortBy string + SortOrder string } // ListResult represents the list UOMs result. @@ -50,12 +52,12 @@ func (h *ListHandler) Handle(ctx context.Context, query ListQuery) (*ListResult, } // Category filter - if query.Category != nil { - cat, err := uom.NewCategory(*query.Category) - if err != nil { - return nil, err + if query.CategoryID != nil && *query.CategoryID != "" { + parsed, parseErr := uuid.Parse(*query.CategoryID) + if parseErr != nil { + return nil, uom.ErrInvalidCategory } - filter.Category = &cat + filter.CategoryID = &parsed } // IsActive filter diff --git a/services/finance/internal/application/uom/template_handler.go b/services/finance/internal/application/uom/template_handler.go index 141c304..fc78287 100644 --- a/services/finance/internal/application/uom/template_handler.go +++ b/services/finance/internal/application/uom/template_handler.go @@ -83,7 +83,7 @@ func (h *TemplateHandler) Handle() (result *TemplateResult, err error) { } // Set headers - headers := []string{"Code", "Name", "Category", "Description"} + headers := []string{"Code", "Name", "Category Code", "Description"} for col, header := range headers { cell, err := excelize.CoordinatesToCellName(col+1, 1) if err != nil { @@ -141,7 +141,7 @@ func (h *TemplateHandler) Handle() (result *TemplateResult, err error) { notesWriter.setCellValue("A1", "Import Instructions") notesWriter.setCellValue("A3", "1. Code: Unique, uppercase letters/numbers/underscores (e.g., KG, MTR_SQ)") notesWriter.setCellValue("A4", "2. Name: Display name (required)") - notesWriter.setCellValue("A5", "3. Category: Must be one of: WEIGHT, LENGTH, VOLUME, QUANTITY") + notesWriter.setCellValue("A5", "3. Category Code: Must match an existing UOM Category code (e.g., WEIGHT, LENGTH)") notesWriter.setCellValue("A6", "4. Description: Optional description") notesWriter.setCellValue("A8", "Notes:") notesWriter.setCellValue("A9", "- Delete sample data rows before importing") diff --git a/services/finance/internal/application/uom/update_handler.go b/services/finance/internal/application/uom/update_handler.go index b296d6d..ec66400 100644 --- a/services/finance/internal/application/uom/update_handler.go +++ b/services/finance/internal/application/uom/update_handler.go @@ -11,12 +11,12 @@ import ( // UpdateCommand represents the update UOM command. type UpdateCommand struct { - UOMID string - UOMName *string - UOMCategory *string - Description *string - IsActive *bool - UpdatedBy string + UOMID string + UOMName *string + UOMCategoryID *string + Description *string + IsActive *bool + UpdatedBy string } // UpdateHandler handles the UpdateUOM command. @@ -43,18 +43,18 @@ func (h *UpdateHandler) Handle(ctx context.Context, cmd UpdateCommand) (*uom.UOM return nil, err } - // 3. Prepare category if provided - var category *uom.Category - if cmd.UOMCategory != nil { - cat, err := uom.NewCategory(*cmd.UOMCategory) - if err != nil { - return nil, err + // 3. Prepare category ID if provided + var categoryID *uuid.UUID + if cmd.UOMCategoryID != nil { + parsed, parseErr := uuid.Parse(*cmd.UOMCategoryID) + if parseErr != nil { + return nil, uom.ErrInvalidCategory } - category = &cat + categoryID = &parsed } // 4. Update domain entity - if err := entity.Update(cmd.UOMName, category, cmd.Description, cmd.IsActive, cmd.UpdatedBy); err != nil { + if err := entity.Update(cmd.UOMName, categoryID, cmd.Description, cmd.IsActive, cmd.UpdatedBy); err != nil { return nil, err } diff --git a/services/finance/internal/application/uomcategory/create_handler.go b/services/finance/internal/application/uomcategory/create_handler.go new file mode 100644 index 0000000..6bd71c0 --- /dev/null +++ b/services/finance/internal/application/uomcategory/create_handler.go @@ -0,0 +1,57 @@ +// Package uomcategory provides application layer handlers for UOM Category operations. +package uomcategory + +import ( + "context" + + "github.com/mutugading/goapps-backend/services/finance/internal/domain/uomcategory" +) + +// CreateCommand represents the create UOM Category command. +type CreateCommand struct { + CategoryCode string + CategoryName string + Description string + CreatedBy string +} + +// CreateHandler handles the CreateUOMCategory command. +type CreateHandler struct { + repo uomcategory.Repository +} + +// NewCreateHandler creates a new CreateHandler. +func NewCreateHandler(repo uomcategory.Repository) *CreateHandler { + return &CreateHandler{repo: repo} +} + +// Handle executes the create UOM Category command. +func (h *CreateHandler) Handle(ctx context.Context, cmd CreateCommand) (*uomcategory.Category, error) { + // 1. Validate and create value objects + code, err := uomcategory.NewCode(cmd.CategoryCode) + if err != nil { + return nil, err + } + + // 2. Check for duplicates + exists, err := h.repo.ExistsByCode(ctx, code) + if err != nil { + return nil, err + } + if exists { + return nil, uomcategory.ErrAlreadyExists + } + + // 3. Create domain entity + entity, err := uomcategory.NewCategory(code, cmd.CategoryName, cmd.Description, cmd.CreatedBy) + if err != nil { + return nil, err + } + + // 4. Persist + if err := h.repo.Create(ctx, entity); err != nil { + return nil, err + } + + return entity, nil +} diff --git a/services/finance/internal/application/uomcategory/delete_handler.go b/services/finance/internal/application/uomcategory/delete_handler.go new file mode 100644 index 0000000..7cd9da6 --- /dev/null +++ b/services/finance/internal/application/uomcategory/delete_handler.go @@ -0,0 +1,56 @@ +// Package uomcategory provides application layer handlers for UOM Category operations. +package uomcategory + +import ( + "context" + + "github.com/google/uuid" + + "github.com/mutugading/goapps-backend/services/finance/internal/domain/uomcategory" +) + +// DeleteCommand represents the delete UOM Category command. +type DeleteCommand struct { + UOMCategoryID string + DeletedBy string +} + +// DeleteHandler handles the DeleteUOMCategory command. +type DeleteHandler struct { + repo uomcategory.Repository +} + +// NewDeleteHandler creates a new DeleteHandler. +func NewDeleteHandler(repo uomcategory.Repository) *DeleteHandler { + return &DeleteHandler{repo: repo} +} + +// Handle executes the delete UOM Category command (soft delete). +func (h *DeleteHandler) Handle(ctx context.Context, cmd DeleteCommand) error { + // 1. Parse ID + id, err := uuid.Parse(cmd.UOMCategoryID) + if err != nil { + return uomcategory.ErrNotFound + } + + // 2. Check existence + exists, err := h.repo.ExistsByID(ctx, id) + if err != nil { + return err + } + if !exists { + return uomcategory.ErrNotFound + } + + // 3. Check if in use by any UOM + inUse, err := h.repo.IsInUse(ctx, id) + if err != nil { + return err + } + if inUse { + return uomcategory.ErrInUse + } + + // 4. Soft delete + return h.repo.SoftDelete(ctx, id, cmd.DeletedBy) +} diff --git a/services/finance/internal/application/uomcategory/export_handler.go b/services/finance/internal/application/uomcategory/export_handler.go new file mode 100644 index 0000000..96d9ee0 --- /dev/null +++ b/services/finance/internal/application/uomcategory/export_handler.go @@ -0,0 +1,184 @@ +// Package uomcategory provides application layer handlers for UOM Category operations. +package uomcategory + +import ( + "context" + "errors" + "fmt" + + "github.com/rs/zerolog/log" + "github.com/xuri/excelize/v2" + + "github.com/mutugading/goapps-backend/services/finance/internal/domain/uomcategory" +) + +// ExportQuery represents the export UOM Categories query. +type ExportQuery struct { + IsActive *bool +} + +// ExportResult represents the export UOM Categories result. +type ExportResult struct { + FileContent []byte + FileName string +} + +// ExportHandler handles the ExportUOMCategories query. +type ExportHandler struct { + repo uomcategory.Repository +} + +// NewExportHandler creates a new ExportHandler. +func NewExportHandler(repo uomcategory.Repository) *ExportHandler { + return &ExportHandler{repo: repo} +} + +// exportExcelWriter wraps excelize file with error collection for non-critical operations. +type exportExcelWriter struct { + f *excelize.File + sheetName string + errs []error +} + +// setCellValue sets a cell value and collects any error. +func (ew *exportExcelWriter) setCellValue(cell string, value interface{}) { + if err := ew.f.SetCellValue(ew.sheetName, cell, value); err != nil { + ew.errs = append(ew.errs, fmt.Errorf("cell %s: %w", cell, err)) + } +} + +// setColWidth sets column width and collects any error. +func (ew *exportExcelWriter) setColWidth(startCol, endCol string, width float64) { + if err := ew.f.SetColWidth(ew.sheetName, startCol, endCol, width); err != nil { + ew.errs = append(ew.errs, fmt.Errorf("column %s-%s: %w", startCol, endCol, err)) + } +} + +// hasErrors returns true if any errors were collected. +func (ew *exportExcelWriter) hasErrors() bool { + return len(ew.errs) > 0 +} + +// error returns combined errors or nil. +func (ew *exportExcelWriter) error() error { + if len(ew.errs) == 0 { + return nil + } + return errors.Join(ew.errs...) +} + +// buildExportFilter creates an export filter from the query. +func buildExportFilter(query ExportQuery) uomcategory.ExportFilter { + return uomcategory.ExportFilter{ + IsActive: query.IsActive, + } +} + +// setupExportSheet creates and configures the export sheet. +func setupExportSheet(f *excelize.File, sheetName string) error { + index, err := f.NewSheet(sheetName) + if err != nil { + return fmt.Errorf("failed to create sheet: %w", err) + } + f.SetActiveSheet(index) + + // Delete default sheet (non-critical) + if deleteErr := f.DeleteSheet("Sheet1"); deleteErr != nil { + log.Debug().Err(deleteErr).Msg("Could not delete default Sheet1") + } + + // Set headers + headers := []string{"No", "Code", "Name", "Description", "Active", "Created At", "Created By"} + for col, header := range headers { + cell, cellErr := excelize.CoordinatesToCellName(col+1, 1) + if cellErr != nil { + return fmt.Errorf("failed to get cell name: %w", cellErr) + } + if setErr := f.SetCellValue(sheetName, cell, header); setErr != nil { + return fmt.Errorf("failed to set header %s: %w", header, setErr) + } + } + + // Style headers + headerStyle, err := f.NewStyle(&excelize.Style{ + Font: &excelize.Font{Bold: true, Color: "FFFFFF"}, + Fill: excelize.Fill{Type: "pattern", Color: []string{"4472C4"}, Pattern: 1}, + Alignment: &excelize.Alignment{Horizontal: "center"}, + }) + if err != nil { + return fmt.Errorf("failed to create header style: %w", err) + } + if err := f.SetCellStyle(sheetName, "A1", "G1", headerStyle); err != nil { + return fmt.Errorf("failed to set header style: %w", err) + } + + return nil +} + +// Handle executes the export UOM Categories query. +func (h *ExportHandler) Handle(ctx context.Context, query ExportQuery) (result *ExportResult, err error) { + // Build filter + filter := buildExportFilter(query) + + // Get all categories + categories, err := h.repo.ListAll(ctx, filter) + if err != nil { + return nil, fmt.Errorf("failed to get uom categories for export: %w", err) + } + + // Create Excel file + f := excelize.NewFile() + defer func() { + if closeErr := f.Close(); closeErr != nil { + log.Warn().Err(closeErr).Msg("Failed to close Excel file") + if err == nil { + err = fmt.Errorf("failed to close file: %w", closeErr) + } + } + }() + + sheetName := "UOM Categories" + if err := setupExportSheet(f, sheetName); err != nil { + return nil, err + } + + // Create writer for data rows (non-critical errors are collected) + writer := &exportExcelWriter{f: f, sheetName: sheetName} + + // Write data rows + for i, c := range categories { + row := i + 2 + writer.setCellValue(fmt.Sprintf("A%d", row), i+1) + writer.setCellValue(fmt.Sprintf("B%d", row), c.Code().String()) + writer.setCellValue(fmt.Sprintf("C%d", row), c.Name()) + writer.setCellValue(fmt.Sprintf("D%d", row), c.Description()) + writer.setCellValue(fmt.Sprintf("E%d", row), c.IsActive()) + writer.setCellValue(fmt.Sprintf("F%d", row), c.CreatedAt().Format("2006-01-02 15:04:05")) + writer.setCellValue(fmt.Sprintf("G%d", row), c.CreatedBy()) + } + + // Set column widths + writer.setColWidth("A", "A", 5) + writer.setColWidth("B", "B", 15) + writer.setColWidth("C", "C", 25) + writer.setColWidth("D", "D", 40) + writer.setColWidth("E", "E", 10) + writer.setColWidth("F", "F", 20) + writer.setColWidth("G", "G", 20) + + // Log any non-critical errors but continue + if writer.hasErrors() { + log.Warn().Err(writer.error()).Msg("Some Excel formatting operations failed") + } + + // Write to buffer + buffer, err := f.WriteToBuffer() + if err != nil { + return nil, fmt.Errorf("failed to write excel to buffer: %w", err) + } + + return &ExportResult{ + FileContent: buffer.Bytes(), + FileName: "uom_category_export.xlsx", + }, nil +} diff --git a/services/finance/internal/application/uomcategory/get_handler.go b/services/finance/internal/application/uomcategory/get_handler.go new file mode 100644 index 0000000..0eb0942 --- /dev/null +++ b/services/finance/internal/application/uomcategory/get_handler.go @@ -0,0 +1,35 @@ +// Package uomcategory provides application layer handlers for UOM Category operations. +package uomcategory + +import ( + "context" + + "github.com/google/uuid" + + "github.com/mutugading/goapps-backend/services/finance/internal/domain/uomcategory" +) + +// GetQuery represents the get UOM Category query. +type GetQuery struct { + UOMCategoryID string +} + +// GetHandler handles the GetUOMCategory query. +type GetHandler struct { + repo uomcategory.Repository +} + +// NewGetHandler creates a new GetHandler. +func NewGetHandler(repo uomcategory.Repository) *GetHandler { + return &GetHandler{repo: repo} +} + +// Handle executes the get UOM Category query. +func (h *GetHandler) Handle(ctx context.Context, query GetQuery) (*uomcategory.Category, error) { + id, err := uuid.Parse(query.UOMCategoryID) + if err != nil { + return nil, uomcategory.ErrNotFound + } + + return h.repo.GetByID(ctx, id) +} diff --git a/services/finance/internal/application/uomcategory/import_handler.go b/services/finance/internal/application/uomcategory/import_handler.go new file mode 100644 index 0000000..cdc5a28 --- /dev/null +++ b/services/finance/internal/application/uomcategory/import_handler.go @@ -0,0 +1,275 @@ +// Package uomcategory provides application layer handlers for UOM Category operations. +package uomcategory + +import ( + "bytes" + "context" + "fmt" + "path/filepath" + "strings" + + "github.com/rs/zerolog/log" + "github.com/xuri/excelize/v2" + + "github.com/mutugading/goapps-backend/services/finance/internal/domain/uomcategory" + "github.com/mutugading/goapps-backend/services/finance/pkg/safeconv" +) + +// ImportCommand represents the import UOM Categories command. +type ImportCommand struct { + FileContent []byte + FileName string + DuplicateAction string // "skip", "update", "error" + CreatedBy string +} + +// ImportResult represents the import UOM Categories result. +type ImportResult struct { + SuccessCount int32 + SkippedCount int32 + UpdatedCount int32 + FailedCount int32 + Errors []ImportError +} + +// ImportError represents a single import error. +type ImportError struct { + RowNumber int32 + Field string + Message string +} + +// ImportHandler handles the ImportUOMCategories command. +type ImportHandler struct { + repo uomcategory.Repository +} + +// NewImportHandler creates a new ImportHandler. +func NewImportHandler(repo uomcategory.Repository) *ImportHandler { + return &ImportHandler{repo: repo} +} + +// Handle executes the import UOM Categories command. +func (h *ImportHandler) Handle(ctx context.Context, cmd ImportCommand) (result *ImportResult, err error) { + result = &ImportResult{ + Errors: []ImportError{}, + } + + // Validate file and get rows + rows, err := h.parseExcelFile(cmd.FileContent, cmd.FileName) + if err != nil { + return nil, err + } + + // Skip header row + if len(rows) <= 1 { + return result, nil // No data rows + } + + // Process each row + for i, row := range rows[1:] { + rowNum := safeconv.IntToInt32(i + 2) // 1-indexed, skip header + h.processRow(ctx, row, rowNum, cmd, result) + } + + return result, nil +} + +// parseExcelFile opens and validates the Excel file, returning rows. +func (h *ImportHandler) parseExcelFile(content []byte, fileName string) ([][]string, error) { + // Validate file extension + ext := strings.ToLower(filepath.Ext(fileName)) + if ext != ".xlsx" && ext != ".xls" { + return nil, fmt.Errorf("unsupported file format: %s", ext) + } + + // Open Excel file + f, err := excelize.OpenReader(bytes.NewReader(content)) + if err != nil { + return nil, fmt.Errorf("failed to open excel file: %w", err) + } + defer func() { + if closeErr := f.Close(); closeErr != nil { + log.Warn().Err(closeErr).Msg("Failed to close Excel file") + } + }() + + // Get first sheet + sheets := f.GetSheetList() + if len(sheets) == 0 { + return nil, fmt.Errorf("no sheets found in file") + } + + // Get all rows + rows, err := f.GetRows(sheets[0]) + if err != nil { + return nil, fmt.Errorf("failed to get rows: %w", err) + } + + return rows, nil +} + +// importRowData holds parsed row values. +type importRowData struct { + code string + name string + description string +} + +// parseImportRow extracts cell values from a row. +func parseImportRow(row []string) importRowData { + return importRowData{ + code: getCellValue(row, 0), + name: getCellValue(row, 1), + description: getCellValue(row, 2), + } +} + +// processRow handles a single row import. +func (h *ImportHandler) processRow(ctx context.Context, row []string, rowNum int32, cmd ImportCommand, result *ImportResult) { + data := parseImportRow(row) + + // Validate fields + code, err := h.validateImportRow(data, rowNum, result) + if err != nil { + return // Error already recorded in result + } + + // Check for duplicates + exists, err := h.repo.ExistsByCode(ctx, code) + if err != nil { + result.FailedCount++ + result.Errors = append(result.Errors, ImportError{ + RowNumber: rowNum, + Field: "category_code", + Message: fmt.Sprintf("failed to check duplicate: %v", err), + }) + return + } + + if exists { + h.handleDuplicate(ctx, code, data, rowNum, cmd, result) + return + } + + // Create new category + h.createCategory(ctx, code, data, rowNum, cmd.CreatedBy, result) +} + +// validateImportRow validates the row data and returns domain objects. +func (h *ImportHandler) validateImportRow(data importRowData, rowNum int32, result *ImportResult) (uomcategory.Code, error) { + // Validate code + code, err := uomcategory.NewCode(data.code) + if err != nil { + result.FailedCount++ + result.Errors = append(result.Errors, ImportError{ + RowNumber: rowNum, + Field: "category_code", + Message: err.Error(), + }) + return uomcategory.Code{}, err + } + + // Validate name + if data.name == "" { + result.FailedCount++ + result.Errors = append(result.Errors, ImportError{ + RowNumber: rowNum, + Field: "category_name", + Message: "name cannot be empty", + }) + return uomcategory.Code{}, fmt.Errorf("name cannot be empty") + } + + return code, nil +} + +// handleDuplicate handles a duplicate code based on the specified action. +func (h *ImportHandler) handleDuplicate(ctx context.Context, code uomcategory.Code, data importRowData, rowNum int32, cmd ImportCommand, result *ImportResult) { + switch cmd.DuplicateAction { + case "skip": + result.SkippedCount++ + case "update": + h.updateExisting(ctx, code, data, rowNum, cmd.CreatedBy, result) + case "error": + result.FailedCount++ + result.Errors = append(result.Errors, ImportError{ + RowNumber: rowNum, + Field: "category_code", + Message: "duplicate code already exists", + }) + default: + // Unknown action, treat as skip + result.SkippedCount++ + } +} + +// updateExisting updates an existing UOM Category. +func (h *ImportHandler) updateExisting(ctx context.Context, code uomcategory.Code, data importRowData, rowNum int32, updatedBy string, result *ImportResult) { + existing, err := h.repo.GetByCode(ctx, code) + if err != nil { + result.FailedCount++ + result.Errors = append(result.Errors, ImportError{ + RowNumber: rowNum, + Field: "category_code", + Message: fmt.Sprintf("failed to get existing: %v", err), + }) + return + } + + if err := existing.Update(&data.name, &data.description, nil, updatedBy); err != nil { + result.FailedCount++ + result.Errors = append(result.Errors, ImportError{ + RowNumber: rowNum, + Field: "update", + Message: err.Error(), + }) + return + } + + if err := h.repo.Update(ctx, existing); err != nil { + result.FailedCount++ + result.Errors = append(result.Errors, ImportError{ + RowNumber: rowNum, + Field: "update", + Message: fmt.Sprintf("failed to update: %v", err), + }) + return + } + + result.UpdatedCount++ +} + +// createCategory creates a new UOM Category. +func (h *ImportHandler) createCategory(ctx context.Context, code uomcategory.Code, data importRowData, rowNum int32, createdBy string, result *ImportResult) { + entity, err := uomcategory.NewCategory(code, data.name, data.description, createdBy) + if err != nil { + result.FailedCount++ + result.Errors = append(result.Errors, ImportError{ + RowNumber: rowNum, + Field: "create", + Message: err.Error(), + }) + return + } + + if err := h.repo.Create(ctx, entity); err != nil { + result.FailedCount++ + result.Errors = append(result.Errors, ImportError{ + RowNumber: rowNum, + Field: "create", + Message: fmt.Sprintf("failed to create: %v", err), + }) + return + } + + result.SuccessCount++ +} + +// getCellValue safely gets a cell value from a row. +func getCellValue(row []string, index int) string { + if index < len(row) { + return strings.TrimSpace(row[index]) + } + return "" +} diff --git a/services/finance/internal/application/uomcategory/list_handler.go b/services/finance/internal/application/uomcategory/list_handler.go new file mode 100644 index 0000000..e55ee83 --- /dev/null +++ b/services/finance/internal/application/uomcategory/list_handler.go @@ -0,0 +1,75 @@ +// Package uomcategory provides application layer handlers for UOM Category operations. +package uomcategory + +import ( + "context" + + "github.com/mutugading/goapps-backend/services/finance/internal/domain/uomcategory" + "github.com/mutugading/goapps-backend/services/finance/pkg/safeconv" +) + +// ListQuery represents the list UOM Categories query. +type ListQuery struct { + Page int + PageSize int + Search string + IsActive *bool + SortBy string + SortOrder string +} + +// ListResult represents the list UOM Categories result. +type ListResult struct { + Categories []*uomcategory.Category + TotalItems int64 + TotalPages int32 + CurrentPage int32 + PageSize int32 +} + +// ListHandler handles the ListUOMCategories query. +type ListHandler struct { + repo uomcategory.Repository +} + +// NewListHandler creates a new ListHandler. +func NewListHandler(repo uomcategory.Repository) *ListHandler { + return &ListHandler{repo: repo} +} + +// Handle executes the list UOM Categories query. +func (h *ListHandler) Handle(ctx context.Context, query ListQuery) (*ListResult, error) { + // Build filter + filter := uomcategory.ListFilter{ + Search: query.Search, + Page: query.Page, + PageSize: query.PageSize, + SortBy: query.SortBy, + SortOrder: query.SortOrder, + IsActive: query.IsActive, + } + + // Validate filter + filter.Validate() + + // Execute query + categories, total, err := h.repo.List(ctx, filter) + if err != nil { + return nil, err + } + + // Calculate total pages using safe conversion + var totalPages int32 + if filter.PageSize > 0 && total > 0 { + computed := (total + int64(filter.PageSize) - 1) / int64(filter.PageSize) + totalPages = safeconv.Int64ToInt32(computed) + } + + return &ListResult{ + Categories: categories, + TotalItems: total, + TotalPages: totalPages, + CurrentPage: safeconv.IntToInt32(filter.Page), + PageSize: safeconv.IntToInt32(filter.PageSize), + }, nil +} diff --git a/services/finance/internal/application/uomcategory/template_handler.go b/services/finance/internal/application/uomcategory/template_handler.go new file mode 100644 index 0000000..4a541ea --- /dev/null +++ b/services/finance/internal/application/uomcategory/template_handler.go @@ -0,0 +1,167 @@ +// Package uomcategory provides application layer handlers for UOM Category operations. +package uomcategory + +import ( + "errors" + "fmt" + + "github.com/rs/zerolog/log" + "github.com/xuri/excelize/v2" +) + +// TemplateResult represents the download template result. +type TemplateResult struct { + FileContent []byte + FileName string +} + +// TemplateHandler handles the DownloadUOMCategoryTemplate query. +type TemplateHandler struct{} + +// NewTemplateHandler creates a new TemplateHandler. +func NewTemplateHandler() *TemplateHandler { + return &TemplateHandler{} +} + +// templateWriter wraps excelize file with error collection for template generation. +type templateWriter struct { + f *excelize.File + sheetName string + errs []error +} + +// setCellValue sets a cell value and collects any error. +func (tw *templateWriter) setCellValue(cell string, value interface{}) { + if err := tw.f.SetCellValue(tw.sheetName, cell, value); err != nil { + tw.errs = append(tw.errs, fmt.Errorf("cell %s: %w", cell, err)) + } +} + +// setColWidth sets column width and collects any error. +func (tw *templateWriter) setColWidth(startCol, endCol string, width float64) { + if err := tw.f.SetColWidth(tw.sheetName, startCol, endCol, width); err != nil { + tw.errs = append(tw.errs, fmt.Errorf("column %s-%s: %w", startCol, endCol, err)) + } +} + +// hasErrors returns true if any errors were collected. +func (tw *templateWriter) hasErrors() bool { + return len(tw.errs) > 0 +} + +// error returns combined errors or nil. +func (tw *templateWriter) error() error { + if len(tw.errs) == 0 { + return nil + } + return errors.Join(tw.errs...) +} + +// Handle generates the import template Excel file. +func (h *TemplateHandler) Handle() (result *TemplateResult, err error) { + // Create Excel file + f := excelize.NewFile() + defer func() { + if closeErr := f.Close(); closeErr != nil { + log.Warn().Err(closeErr).Msg("Failed to close Excel template file") + if err == nil { + err = fmt.Errorf("failed to close file: %w", closeErr) + } + } + }() + + sheetName := "UOM Category Import Template" + index, err := f.NewSheet(sheetName) + if err != nil { + return nil, fmt.Errorf("failed to create sheet: %w", err) + } + f.SetActiveSheet(index) + + // Delete default sheet (non-critical) + if deleteErr := f.DeleteSheet("Sheet1"); deleteErr != nil { + log.Debug().Err(deleteErr).Msg("Could not delete default Sheet1") + } + + // Set headers + headers := []string{"Code", "Name", "Description"} + for col, header := range headers { + cell, cellErr := excelize.CoordinatesToCellName(col+1, 1) + if cellErr != nil { + return nil, fmt.Errorf("failed to get cell name: %w", cellErr) + } + if setErr := f.SetCellValue(sheetName, cell, header); setErr != nil { + return nil, fmt.Errorf("failed to set header %s: %w", header, setErr) + } + } + + // Style headers + headerStyle, err := f.NewStyle(&excelize.Style{ + Font: &excelize.Font{Bold: true, Color: "FFFFFF"}, + Fill: excelize.Fill{Type: "pattern", Color: []string{"4472C4"}, Pattern: 1}, + Alignment: &excelize.Alignment{Horizontal: "center"}, + }) + if err != nil { + return nil, fmt.Errorf("failed to create header style: %w", err) + } + if err := f.SetCellStyle(sheetName, "A1", "C1", headerStyle); err != nil { + return nil, fmt.Errorf("failed to set header style: %w", err) + } + + // Create writer for data rows (non-critical errors are collected) + writer := &templateWriter{f: f, sheetName: sheetName} + + // Add sample data + sampleData := [][]string{ + {"WEIGHT", "Weight", "Weight-based units (e.g., KG, GR, TON)"}, + {"LENGTH", "Length", "Length-based units (e.g., MTR, CM, YARD)"}, + {"VOLUME", "Volume", "Volume-based units (e.g., LTR, ML)"}, + {"QUANTITY", "Quantity", "Quantity-based units (e.g., PCS, BOX, SET)"}, + } + + for i, row := range sampleData { + rowNum := i + 2 + writer.setCellValue(fmt.Sprintf("A%d", rowNum), row[0]) + writer.setCellValue(fmt.Sprintf("B%d", rowNum), row[1]) + writer.setCellValue(fmt.Sprintf("C%d", rowNum), row[2]) + } + + // Set column widths + writer.setColWidth("A", "A", 15) + writer.setColWidth("B", "B", 25) + writer.setColWidth("C", "C", 50) + + // Add validation note sheet + notesSheet := "Instructions" + if _, sheetErr := f.NewSheet(notesSheet); sheetErr != nil { + log.Debug().Err(sheetErr).Msg("Could not create Instructions sheet") + } else { + notesWriter := &templateWriter{f: f, sheetName: notesSheet} + notesWriter.setCellValue("A1", "Import Instructions") + notesWriter.setCellValue("A3", "1. Code: Unique, uppercase letters/numbers/underscores (e.g., WEIGHT, TIME_UNIT)") + notesWriter.setCellValue("A4", "2. Name: Display name (required, max 100 chars)") + notesWriter.setCellValue("A5", "3. Description: Optional description") + notesWriter.setCellValue("A7", "Notes:") + notesWriter.setCellValue("A8", "- Delete sample data rows before importing") + notesWriter.setCellValue("A9", "- Save file as .xlsx format") + + if notesWriter.hasErrors() { + log.Warn().Err(notesWriter.error()).Msg("Some Instructions sheet operations failed") + } + } + + // Log any non-critical errors but continue + if writer.hasErrors() { + log.Warn().Err(writer.error()).Msg("Some Excel formatting operations failed") + } + + // Write to buffer + buffer, err := f.WriteToBuffer() + if err != nil { + return nil, fmt.Errorf("failed to write excel to buffer: %w", err) + } + + return &TemplateResult{ + FileContent: buffer.Bytes(), + FileName: "uom_category_import_template.xlsx", + }, nil +} diff --git a/services/finance/internal/application/uomcategory/update_handler.go b/services/finance/internal/application/uomcategory/update_handler.go new file mode 100644 index 0000000..4a0f180 --- /dev/null +++ b/services/finance/internal/application/uomcategory/update_handler.go @@ -0,0 +1,56 @@ +// Package uomcategory provides application layer handlers for UOM Category operations. +package uomcategory + +import ( + "context" + + "github.com/google/uuid" + + "github.com/mutugading/goapps-backend/services/finance/internal/domain/uomcategory" +) + +// UpdateCommand represents the update UOM Category command. +type UpdateCommand struct { + UOMCategoryID string + CategoryName *string + Description *string + IsActive *bool + UpdatedBy string +} + +// UpdateHandler handles the UpdateUOMCategory command. +type UpdateHandler struct { + repo uomcategory.Repository +} + +// NewUpdateHandler creates a new UpdateHandler. +func NewUpdateHandler(repo uomcategory.Repository) *UpdateHandler { + return &UpdateHandler{repo: repo} +} + +// Handle executes the update UOM Category command. +func (h *UpdateHandler) Handle(ctx context.Context, cmd UpdateCommand) (*uomcategory.Category, error) { + // 1. Parse ID + id, err := uuid.Parse(cmd.UOMCategoryID) + if err != nil { + return nil, uomcategory.ErrNotFound + } + + // 2. Get existing entity + entity, err := h.repo.GetByID(ctx, id) + if err != nil { + return nil, err + } + + // 3. Update domain entity + if err := entity.Update(cmd.CategoryName, cmd.Description, cmd.IsActive, cmd.UpdatedBy); err != nil { + return nil, err + } + + // 4. Persist + if err := h.repo.Update(ctx, entity); err != nil { + return nil, err + } + + return entity, nil +} diff --git a/services/finance/internal/delivery/grpc/metrics.go b/services/finance/internal/delivery/grpc/metrics.go index 6553748..90d7a4e 100644 --- a/services/finance/internal/delivery/grpc/metrics.go +++ b/services/finance/internal/delivery/grpc/metrics.go @@ -70,6 +70,14 @@ var ( []string{"operation", "status"}, ) + uomCategoryOperationsTotal = promauto.NewCounterVec( + prometheus.CounterOpts{ + Name: "uom_category_operations_total", + Help: "Total number of UOM Category operations", + }, + []string{"operation", "status"}, + ) + cacheHitsTotal = promauto.NewCounterVec( prometheus.CounterOpts{ Name: "cache_hits_total", diff --git a/services/finance/internal/delivery/grpc/rm_category_handler.go b/services/finance/internal/delivery/grpc/rm_category_handler.go index 6e3e4f7..397b142 100644 --- a/services/finance/internal/delivery/grpc/rm_category_handler.go +++ b/services/finance/internal/delivery/grpc/rm_category_handler.go @@ -1,4 +1,4 @@ -// Package grpc provides gRPC server implementation for finance service. +//nolint:dupl // RMCategoryHandler mirrors UOMCategoryHandler by design — different proto types prevent shared code. package grpc import ( diff --git a/services/finance/internal/delivery/grpc/uom_category_handler.go b/services/finance/internal/delivery/grpc/uom_category_handler.go new file mode 100644 index 0000000..667734e --- /dev/null +++ b/services/finance/internal/delivery/grpc/uom_category_handler.go @@ -0,0 +1,329 @@ +//nolint:dupl // UOMCategoryHandler mirrors RMCategoryHandler by design — different proto types prevent shared code. +package grpc + +import ( + "context" + "time" + + commonv1 "github.com/mutugading/goapps-backend/gen/common/v1" + financev1 "github.com/mutugading/goapps-backend/gen/finance/v1" + "github.com/mutugading/goapps-backend/services/finance/internal/application/uomcategory" + uomcategorydomain "github.com/mutugading/goapps-backend/services/finance/internal/domain/uomcategory" +) + +// UOMCategoryHandler implements the UOMCategoryServiceServer interface. +type UOMCategoryHandler struct { + financev1.UnimplementedUOMCategoryServiceServer + createHandler *uomcategory.CreateHandler + getHandler *uomcategory.GetHandler + updateHandler *uomcategory.UpdateHandler + deleteHandler *uomcategory.DeleteHandler + listHandler *uomcategory.ListHandler + exportHandler *uomcategory.ExportHandler + importHandler *uomcategory.ImportHandler + templateHandler *uomcategory.TemplateHandler + validationHelper *ValidationHelper +} + +// NewUOMCategoryHandler creates a new UOMCategory gRPC handler. +func NewUOMCategoryHandler( + repo uomcategorydomain.Repository, +) (*UOMCategoryHandler, error) { + validationHelper, err := NewValidationHelper() + if err != nil { + return nil, err + } + + return &UOMCategoryHandler{ + createHandler: uomcategory.NewCreateHandler(repo), + getHandler: uomcategory.NewGetHandler(repo), + updateHandler: uomcategory.NewUpdateHandler(repo), + deleteHandler: uomcategory.NewDeleteHandler(repo), + listHandler: uomcategory.NewListHandler(repo), + exportHandler: uomcategory.NewExportHandler(repo), + importHandler: uomcategory.NewImportHandler(repo), + templateHandler: uomcategory.NewTemplateHandler(), + validationHelper: validationHelper, + }, nil +} + +// CreateUOMCategory creates a new UOM category. +func (h *UOMCategoryHandler) CreateUOMCategory(ctx context.Context, req *financev1.CreateUOMCategoryRequest) (*financev1.CreateUOMCategoryResponse, error) { + if baseResp := h.validationHelper.ValidateRequest(req); baseResp != nil { + RecordUOMCategoryOperation("create", false) + return &financev1.CreateUOMCategoryResponse{Base: baseResp}, nil + } + + cmd := uomcategory.CreateCommand{ + CategoryCode: req.CategoryCode, + CategoryName: req.CategoryName, + Description: req.Description, + CreatedBy: getUserFromContext(ctx), + } + + entity, err := h.createHandler.Handle(ctx, cmd) + if err != nil { + RecordUOMCategoryOperation("create", false) + return &financev1.CreateUOMCategoryResponse{Base: domainErrorToBaseResponse(err)}, nil + } + + RecordUOMCategoryOperation("create", true) + + return &financev1.CreateUOMCategoryResponse{ + Base: successResponse("UOM Category created successfully"), + Data: uomCategoryEntityToProto(entity), + }, nil +} + +// GetUOMCategory retrieves a UOM category by ID. +func (h *UOMCategoryHandler) GetUOMCategory(ctx context.Context, req *financev1.GetUOMCategoryRequest) (*financev1.GetUOMCategoryResponse, error) { + if baseResp := h.validationHelper.ValidateRequest(req); baseResp != nil { + RecordUOMCategoryOperation("get", false) + return &financev1.GetUOMCategoryResponse{Base: baseResp}, nil + } + + query := uomcategory.GetQuery{UOMCategoryID: req.UomCategoryId} + entity, err := h.getHandler.Handle(ctx, query) + if err != nil { + RecordUOMCategoryOperation("get", false) + return &financev1.GetUOMCategoryResponse{Base: domainErrorToBaseResponse(err)}, nil + } + + RecordUOMCategoryOperation("get", true) + + return &financev1.GetUOMCategoryResponse{ + Base: successResponse("UOM Category retrieved successfully"), + Data: uomCategoryEntityToProto(entity), + }, nil +} + +// UpdateUOMCategory updates an existing UOM category. +func (h *UOMCategoryHandler) UpdateUOMCategory(ctx context.Context, req *financev1.UpdateUOMCategoryRequest) (*financev1.UpdateUOMCategoryResponse, error) { + if baseResp := h.validationHelper.ValidateRequest(req); baseResp != nil { + RecordUOMCategoryOperation("update", false) + return &financev1.UpdateUOMCategoryResponse{Base: baseResp}, nil + } + + cmd := uomcategory.UpdateCommand{ + UOMCategoryID: req.UomCategoryId, + UpdatedBy: getUserFromContext(ctx), + } + + if req.CategoryName != nil && *req.CategoryName != "" { + cmd.CategoryName = req.CategoryName + } + if req.Description != nil { + cmd.Description = req.Description + } + if req.IsActive != nil { + cmd.IsActive = req.IsActive + } + + entity, err := h.updateHandler.Handle(ctx, cmd) + if err != nil { + RecordUOMCategoryOperation("update", false) + return &financev1.UpdateUOMCategoryResponse{Base: domainErrorToBaseResponse(err)}, nil + } + + RecordUOMCategoryOperation("update", true) + + return &financev1.UpdateUOMCategoryResponse{ + Base: successResponse("UOM Category updated successfully"), + Data: uomCategoryEntityToProto(entity), + }, nil +} + +// DeleteUOMCategory soft deletes a UOM category. +func (h *UOMCategoryHandler) DeleteUOMCategory(ctx context.Context, req *financev1.DeleteUOMCategoryRequest) (*financev1.DeleteUOMCategoryResponse, error) { + if baseResp := h.validationHelper.ValidateRequest(req); baseResp != nil { + RecordUOMCategoryOperation("delete", false) + return &financev1.DeleteUOMCategoryResponse{Base: baseResp}, nil + } + + cmd := uomcategory.DeleteCommand{ + UOMCategoryID: req.UomCategoryId, + DeletedBy: getUserFromContext(ctx), + } + + if err := h.deleteHandler.Handle(ctx, cmd); err != nil { + RecordUOMCategoryOperation("delete", false) + return &financev1.DeleteUOMCategoryResponse{Base: domainErrorToBaseResponse(err)}, nil + } + + RecordUOMCategoryOperation("delete", true) + + return &financev1.DeleteUOMCategoryResponse{ + Base: successResponse("UOM Category deleted successfully"), + }, nil +} + +// ListUOMCategories lists UOM categories with search, filter, and pagination. +func (h *UOMCategoryHandler) ListUOMCategories(ctx context.Context, req *financev1.ListUOMCategoriesRequest) (*financev1.ListUOMCategoriesResponse, error) { + page := int(req.Page) + if page == 0 { + page = 1 + } + pageSize := int(req.PageSize) + if pageSize == 0 { + pageSize = 10 + } + + query := uomcategory.ListQuery{ + Page: page, + PageSize: pageSize, + Search: req.Search, + SortBy: req.SortBy, + SortOrder: req.SortOrder, + } + + // Handle ActiveFilter enum + switch req.ActiveFilter { + case financev1.ActiveFilter_ACTIVE_FILTER_ACTIVE: + active := true + query.IsActive = &active + case financev1.ActiveFilter_ACTIVE_FILTER_INACTIVE: + active := false + query.IsActive = &active + case financev1.ActiveFilter_ACTIVE_FILTER_UNSPECIFIED: + // Show all - no filter + } + + result, err := h.listHandler.Handle(ctx, query) + if err != nil { + RecordUOMCategoryOperation("list", false) + return &financev1.ListUOMCategoriesResponse{Base: domainErrorToBaseResponse(err)}, nil + } + + RecordUOMCategoryOperation("list", true) + + items := make([]*financev1.UOMCategory, len(result.Categories)) + for i, entity := range result.Categories { + items[i] = uomCategoryEntityToProto(entity) + } + + return &financev1.ListUOMCategoriesResponse{ + Base: successResponse("UOM Categories retrieved successfully"), + Data: items, + Pagination: &commonv1.PaginationResponse{ + CurrentPage: result.CurrentPage, + PageSize: result.PageSize, + TotalItems: result.TotalItems, + TotalPages: result.TotalPages, + }, + }, nil +} + +// ExportUOMCategories exports UOM categories to Excel file. +func (h *UOMCategoryHandler) ExportUOMCategories(ctx context.Context, req *financev1.ExportUOMCategoriesRequest) (*financev1.ExportUOMCategoriesResponse, error) { + query := uomcategory.ExportQuery{} + + // Handle ActiveFilter enum + switch req.ActiveFilter { + case financev1.ActiveFilter_ACTIVE_FILTER_ACTIVE: + active := true + query.IsActive = &active + case financev1.ActiveFilter_ACTIVE_FILTER_INACTIVE: + active := false + query.IsActive = &active + case financev1.ActiveFilter_ACTIVE_FILTER_UNSPECIFIED: + // Export all - no filter + } + + result, err := h.exportHandler.Handle(ctx, query) + if err != nil { + RecordUOMCategoryOperation("export", false) + return &financev1.ExportUOMCategoriesResponse{Base: domainErrorToBaseResponse(err)}, nil + } + + RecordUOMCategoryOperation("export", true) + + return &financev1.ExportUOMCategoriesResponse{ + Base: successResponse("UOM Categories exported successfully"), + FileContent: result.FileContent, + FileName: result.FileName, + }, nil +} + +// ImportUOMCategories imports UOM categories from Excel file. +func (h *UOMCategoryHandler) ImportUOMCategories(ctx context.Context, req *financev1.ImportUOMCategoriesRequest) (*financev1.ImportUOMCategoriesResponse, error) { + if baseResp := h.validationHelper.ValidateRequest(req); baseResp != nil { + RecordUOMCategoryOperation("import", false) + return &financev1.ImportUOMCategoriesResponse{Base: baseResp}, nil + } + + cmd := uomcategory.ImportCommand{ + FileContent: req.FileContent, + FileName: req.FileName, + DuplicateAction: req.DuplicateAction, + CreatedBy: getUserFromContext(ctx), + } + + result, err := h.importHandler.Handle(ctx, cmd) + if err != nil { + RecordUOMCategoryOperation("import", false) + return &financev1.ImportUOMCategoriesResponse{Base: domainErrorToBaseResponse(err)}, nil + } + + RecordUOMCategoryOperation("import", true) + + importErrors := make([]*financev1.ImportError, len(result.Errors)) + for i, e := range result.Errors { + importErrors[i] = &financev1.ImportError{ + RowNumber: e.RowNumber, + Field: e.Field, + Message: e.Message, + } + } + + return &financev1.ImportUOMCategoriesResponse{ + Base: successResponse("Import completed"), + SuccessCount: result.SuccessCount, + SkippedCount: result.SkippedCount, + UpdatedCount: result.UpdatedCount, + FailedCount: result.FailedCount, + Errors: importErrors, + }, nil +} + +// DownloadUOMCategoryTemplate downloads the Excel import template. +func (h *UOMCategoryHandler) DownloadUOMCategoryTemplate(_ context.Context, _ *financev1.DownloadUOMCategoryTemplateRequest) (*financev1.DownloadUOMCategoryTemplateResponse, error) { + result, err := h.templateHandler.Handle() + if err != nil { + return &financev1.DownloadUOMCategoryTemplateResponse{Base: InternalErrorResponse(err.Error())}, nil + } + + return &financev1.DownloadUOMCategoryTemplateResponse{ + Base: successResponse("Template generated successfully"), + FileContent: result.FileContent, + FileName: result.FileName, + }, nil +} + +// uomCategoryEntityToProto converts a domain Category entity to proto message. +func uomCategoryEntityToProto(entity *uomcategorydomain.Category) *financev1.UOMCategory { + proto := &financev1.UOMCategory{ + UomCategoryId: entity.ID().String(), + CategoryCode: entity.Code().String(), + CategoryName: entity.Name(), + Description: entity.Description(), + IsActive: entity.IsActive(), + Audit: &commonv1.AuditInfo{ + CreatedAt: entity.CreatedAt().Format(time.RFC3339), + CreatedBy: entity.CreatedBy(), + }, + } + + if entity.UpdatedAt() != nil { + proto.Audit.UpdatedAt = entity.UpdatedAt().Format(time.RFC3339) + } + if entity.UpdatedBy() != nil { + proto.Audit.UpdatedBy = *entity.UpdatedBy() + } + + return proto +} + +// RecordUOMCategoryOperation records a UOM Category operation metric. +func RecordUOMCategoryOperation(operation string, success bool) { + uomCategoryOperationsTotal.WithLabelValues(operation, metricStatus(success)).Inc() +} diff --git a/services/finance/internal/delivery/grpc/uom_handler.go b/services/finance/internal/delivery/grpc/uom_handler.go index c696ada..1baf8ca 100644 --- a/services/finance/internal/delivery/grpc/uom_handler.go +++ b/services/finance/internal/delivery/grpc/uom_handler.go @@ -33,6 +33,7 @@ type UOMHandler struct { // NewUOMHandler creates a new UOM gRPC handler. func NewUOMHandler( repo uomdomain.Repository, + catRepo uomdomain.CategoryRepository, cache *redis.UOMCache, ) (*UOMHandler, error) { validationHelper, err := NewValidationHelper() @@ -47,7 +48,7 @@ func NewUOMHandler( deleteHandler: uom.NewDeleteHandler(repo), listHandler: uom.NewListHandler(repo), exportHandler: uom.NewExportHandler(repo), - importHandler: uom.NewImportHandler(repo), + importHandler: uom.NewImportHandler(repo, catRepo), templateHandler: uom.NewTemplateHandler(), cache: cache, validationHelper: validationHelper, @@ -63,11 +64,11 @@ func (h *UOMHandler) CreateUOM(ctx context.Context, req *financev1.CreateUOMRequ } cmd := uom.CreateCommand{ - UOMCode: req.UomCode, - UOMName: req.UomName, - UOMCategory: protoToCategory(req.UomCategory), - Description: req.Description, - CreatedBy: getUserFromContext(ctx), + UOMCode: req.UomCode, + UOMName: req.UomName, + UOMCategoryID: req.UomCategoryId, + Description: req.Description, + CreatedBy: getUserFromContext(ctx), } entity, err := h.createHandler.Handle(ctx, cmd) @@ -130,9 +131,8 @@ func (h *UOMHandler) UpdateUOM(ctx context.Context, req *financev1.UpdateUOMRequ if req.UomName != nil && *req.UomName != "" { cmd.UOMName = req.UomName } - if req.UomCategory != nil && *req.UomCategory != financev1.UOMCategory_UOM_CATEGORY_UNSPECIFIED { - cat := protoToCategory(*req.UomCategory) - cmd.UOMCategory = &cat + if req.UomCategoryId != nil && *req.UomCategoryId != "" { + cmd.UOMCategoryID = req.UomCategoryId } if req.Description != nil { cmd.Description = req.Description @@ -196,8 +196,6 @@ func (h *UOMHandler) DeleteUOM(ctx context.Context, req *financev1.DeleteUOMRequ // ListUOMs lists UOMs with search, filter, and pagination. func (h *UOMHandler) ListUOMs(ctx context.Context, req *financev1.ListUOMsRequest) (*financev1.ListUOMsResponse, error) { - // Note: ListUOMs typically doesn't have strict validation requirements - page := int(req.Page) if page == 0 { page = 1 @@ -215,9 +213,8 @@ func (h *UOMHandler) ListUOMs(ctx context.Context, req *financev1.ListUOMsReques SortOrder: req.SortOrder, } - if req.Category != financev1.UOMCategory_UOM_CATEGORY_UNSPECIFIED { - cat := protoToCategory(req.Category) - query.Category = &cat + if req.UomCategoryId != "" { + query.CategoryID = &req.UomCategoryId } // Handle ActiveFilter enum @@ -261,9 +258,8 @@ func (h *UOMHandler) ListUOMs(ctx context.Context, req *financev1.ListUOMsReques func (h *UOMHandler) ExportUOMs(ctx context.Context, req *financev1.ExportUOMsRequest) (*financev1.ExportUOMsResponse, error) { query := uom.ExportQuery{} - if req.Category != financev1.UOMCategory_UOM_CATEGORY_UNSPECIFIED { - cat := protoToCategory(req.Category) - query.Category = &cat + if req.UomCategoryId != "" { + query.CategoryID = &req.UomCategoryId } // Handle ActiveFilter enum @@ -323,9 +319,9 @@ func (h *UOMHandler) ImportUOMs(ctx context.Context, req *financev1.ImportUOMsRe } } - errors := make([]*financev1.ImportError, len(result.Errors)) + importErrors := make([]*financev1.ImportError, len(result.Errors)) for i, e := range result.Errors { - errors[i] = &financev1.ImportError{ + importErrors[i] = &financev1.ImportError{ RowNumber: e.RowNumber, Field: e.Field, Message: e.Message, @@ -338,7 +334,7 @@ func (h *UOMHandler) ImportUOMs(ctx context.Context, req *financev1.ImportUOMsRe SkippedCount: result.SkippedCount, UpdatedCount: result.UpdatedCount, FailedCount: result.FailedCount, - Errors: errors, + Errors: importErrors, }, nil } @@ -385,44 +381,16 @@ func domainErrorToBaseResponse(err error) *commonv1.BaseResponse { } } -func protoToCategory(cat financev1.UOMCategory) string { - switch cat { - case financev1.UOMCategory_UOM_CATEGORY_WEIGHT: - return "WEIGHT" - case financev1.UOMCategory_UOM_CATEGORY_LENGTH: - return "LENGTH" - case financev1.UOMCategory_UOM_CATEGORY_VOLUME: - return "VOLUME" - case financev1.UOMCategory_UOM_CATEGORY_QUANTITY: - return "QUANTITY" - default: - return "" - } -} - -func categoryToProto(cat string) financev1.UOMCategory { - switch cat { - case "WEIGHT": - return financev1.UOMCategory_UOM_CATEGORY_WEIGHT - case "LENGTH": - return financev1.UOMCategory_UOM_CATEGORY_LENGTH - case "VOLUME": - return financev1.UOMCategory_UOM_CATEGORY_VOLUME - case "QUANTITY": - return financev1.UOMCategory_UOM_CATEGORY_QUANTITY - default: - return financev1.UOMCategory_UOM_CATEGORY_UNSPECIFIED - } -} - func entityToProto(entity *uomdomain.UOM) *financev1.UOM { proto := &financev1.UOM{ - UomId: entity.ID().String(), - UomCode: entity.Code().String(), - UomName: entity.Name(), - UomCategory: categoryToProto(entity.Category().String()), - Description: entity.Description(), - IsActive: entity.IsActive(), + UomId: entity.ID().String(), + UomCode: entity.Code().String(), + UomName: entity.Name(), + UomCategoryId: entity.CategoryID().String(), + UomCategoryCode: entity.CategoryInfo().Code(), + UomCategoryName: entity.CategoryInfo().Name(), + Description: entity.Description(), + IsActive: entity.IsActive(), Audit: &commonv1.AuditInfo{ CreatedAt: entity.CreatedAt().Format(time.RFC3339), CreatedBy: entity.CreatedBy(), diff --git a/services/finance/internal/delivery/httpdelivery/swagger.json b/services/finance/internal/delivery/httpdelivery/swagger.json index 23cf09d..ce2d6f8 100644 --- a/services/finance/internal/delivery/httpdelivery/swagger.json +++ b/services/finance/internal/delivery/httpdelivery/swagger.json @@ -31,15 +31,15 @@ } ], "paths": { - "/api/v1/finance/parameters": { + "/api/v1/finance/formulas": { "get": { - "summary": "ListParameters lists parameters with search, filter, and pagination.", - "operationId": "ParameterService_ListParameters", + "summary": "ListFormulas lists formulas with search, filter, and pagination.", + "operationId": "FormulaService_ListFormulas", "responses": { "200": { "description": "A successful response.", "schema": { - "$ref": "#/definitions/v1ListParametersResponse" + "$ref": "#/definitions/v1ListFormulasResponse" } }, "default": { @@ -68,42 +68,28 @@ }, { "name": "search", - "description": "Search query (searches in code, name, short_name).", + "description": "Search query (searches in code, name, expression).", "in": "query", "required": false, "type": "string" }, { - "name": "dataType", - "description": "Filter by data type.\nDATA_TYPE_UNSPECIFIED (0) means \"show all data types\" (no filter).\n\n - DATA_TYPE_UNSPECIFIED: Default unspecified value - used as \"no filter\" in list/export requests.\n - DATA_TYPE_NUMBER: Numeric values (integer or decimal).\n - DATA_TYPE_TEXT: Text/string values.\n - DATA_TYPE_BOOLEAN: Boolean (true/false) values.", - "in": "query", - "required": false, - "type": "string", - "enum": [ - "DATA_TYPE_UNSPECIFIED", - "DATA_TYPE_NUMBER", - "DATA_TYPE_TEXT", - "DATA_TYPE_BOOLEAN" - ], - "default": "DATA_TYPE_UNSPECIFIED" - }, - { - "name": "paramCategory", - "description": "Filter by category.\nPARAM_CATEGORY_UNSPECIFIED (0) means \"show all categories\" (no filter).\n\n - PARAM_CATEGORY_UNSPECIFIED: Default unspecified value - used as \"no filter\" in list/export requests.\n - PARAM_CATEGORY_INPUT: Input parameter - manually entered values.\n - PARAM_CATEGORY_RATE: Rate parameter - rate/ratio values.\n - PARAM_CATEGORY_CALCULATED: Calculated parameter - computed from other parameters.", + "name": "formulaType", + "description": "Filter by formula type.\nFORMULA_TYPE_UNSPECIFIED (0) means \"show all types\" (no filter).\n\n - FORMULA_TYPE_UNSPECIFIED: Default unspecified value - used as \"no filter\" in list/export requests.\n - FORMULA_TYPE_CALCULATION: Mathematical calculation using parameter codes (e.g., \"ELEC_CONSUMPTION * ELEC_RATE\").\n - FORMULA_TYPE_SQL_QUERY: SQL query to fetch data from other tables.\n - FORMULA_TYPE_CONSTANT: Constant value (no calculation).", "in": "query", "required": false, "type": "string", "enum": [ - "PARAM_CATEGORY_UNSPECIFIED", - "PARAM_CATEGORY_INPUT", - "PARAM_CATEGORY_RATE", - "PARAM_CATEGORY_CALCULATED" + "FORMULA_TYPE_UNSPECIFIED", + "FORMULA_TYPE_CALCULATION", + "FORMULA_TYPE_SQL_QUERY", + "FORMULA_TYPE_CONSTANT" ], - "default": "PARAM_CATEGORY_UNSPECIFIED" + "default": "FORMULA_TYPE_UNSPECIFIED" }, { "name": "activeFilter", - "description": "Filter by active status.\nACTIVE_FILTER_UNSPECIFIED (0) = show all, ACTIVE_FILTER_ACTIVE (1) = only active,\nACTIVE_FILTER_INACTIVE (2) = only inactive.\n\n - ACTIVE_FILTER_UNSPECIFIED: Show all records regardless of active status (default).\n - ACTIVE_FILTER_ACTIVE: Show only active records.\n - ACTIVE_FILTER_INACTIVE: Show only inactive records.", + "description": "Filter by active status.\n\n - ACTIVE_FILTER_UNSPECIFIED: Show all records regardless of active status (default).\n - ACTIVE_FILTER_ACTIVE: Show only active records.\n - ACTIVE_FILTER_INACTIVE: Show only inactive records.", "in": "query", "required": false, "type": "string", @@ -116,7 +102,7 @@ }, { "name": "sortBy", - "description": "Sort field: \"code\", \"name\", \"category\", \"data_type\", \"created_at\" (default: \"code\").", + "description": "Sort field: \"code\", \"name\", \"type\", \"version\", \"created_at\" (default: \"code\").", "in": "query", "required": false, "type": "string" @@ -130,17 +116,17 @@ } ], "tags": [ - "ParameterService" + "FormulaService" ] }, "post": { - "summary": "CreateParameter creates a new parameter.", - "operationId": "ParameterService_CreateParameter", + "summary": "CreateFormula creates a new formula.", + "operationId": "FormulaService_CreateFormula", "responses": { "200": { "description": "A successful response.", "schema": { - "$ref": "#/definitions/v1CreateParameterResponse" + "$ref": "#/definitions/v1CreateFormulaResponse" } }, "default": { @@ -153,28 +139,28 @@ "parameters": [ { "name": "body", - "description": "CreateParameterRequest is the request for creating a new parameter.", + "description": "CreateFormulaRequest is the request for creating a new formula.", "in": "body", "required": true, "schema": { - "$ref": "#/definitions/v1CreateParameterRequest" + "$ref": "#/definitions/v1CreateFormulaRequest" } } ], "tags": [ - "ParameterService" + "FormulaService" ] } }, - "/api/v1/finance/parameters/export": { + "/api/v1/finance/formulas/export": { "get": { - "summary": "ExportParameters exports parameters to Excel file.", - "operationId": "ParameterService_ExportParameters", + "summary": "ExportFormulas exports formulas to Excel file.", + "operationId": "FormulaService_ExportFormulas", "responses": { "200": { "description": "A successful response.", "schema": { - "$ref": "#/definitions/v1ExportParametersResponse" + "$ref": "#/definitions/v1ExportFormulasResponse" } }, "default": { @@ -186,32 +172,18 @@ }, "parameters": [ { - "name": "dataType", - "description": "Filter by data type.\n\n - DATA_TYPE_UNSPECIFIED: Default unspecified value - used as \"no filter\" in list/export requests.\n - DATA_TYPE_NUMBER: Numeric values (integer or decimal).\n - DATA_TYPE_TEXT: Text/string values.\n - DATA_TYPE_BOOLEAN: Boolean (true/false) values.", - "in": "query", - "required": false, - "type": "string", - "enum": [ - "DATA_TYPE_UNSPECIFIED", - "DATA_TYPE_NUMBER", - "DATA_TYPE_TEXT", - "DATA_TYPE_BOOLEAN" - ], - "default": "DATA_TYPE_UNSPECIFIED" - }, - { - "name": "paramCategory", - "description": "Filter by category.\n\n - PARAM_CATEGORY_UNSPECIFIED: Default unspecified value - used as \"no filter\" in list/export requests.\n - PARAM_CATEGORY_INPUT: Input parameter - manually entered values.\n - PARAM_CATEGORY_RATE: Rate parameter - rate/ratio values.\n - PARAM_CATEGORY_CALCULATED: Calculated parameter - computed from other parameters.", + "name": "formulaType", + "description": "Filter by formula type.\n\n - FORMULA_TYPE_UNSPECIFIED: Default unspecified value - used as \"no filter\" in list/export requests.\n - FORMULA_TYPE_CALCULATION: Mathematical calculation using parameter codes (e.g., \"ELEC_CONSUMPTION * ELEC_RATE\").\n - FORMULA_TYPE_SQL_QUERY: SQL query to fetch data from other tables.\n - FORMULA_TYPE_CONSTANT: Constant value (no calculation).", "in": "query", "required": false, "type": "string", "enum": [ - "PARAM_CATEGORY_UNSPECIFIED", - "PARAM_CATEGORY_INPUT", - "PARAM_CATEGORY_RATE", - "PARAM_CATEGORY_CALCULATED" + "FORMULA_TYPE_UNSPECIFIED", + "FORMULA_TYPE_CALCULATION", + "FORMULA_TYPE_SQL_QUERY", + "FORMULA_TYPE_CONSTANT" ], - "default": "PARAM_CATEGORY_UNSPECIFIED" + "default": "FORMULA_TYPE_UNSPECIFIED" }, { "name": "activeFilter", @@ -228,19 +200,19 @@ } ], "tags": [ - "ParameterService" + "FormulaService" ] } }, - "/api/v1/finance/parameters/import": { + "/api/v1/finance/formulas/import": { "post": { - "summary": "ImportParameters imports parameters from Excel file.", - "operationId": "ParameterService_ImportParameters", + "summary": "ImportFormulas imports formulas from Excel file.", + "operationId": "FormulaService_ImportFormulas", "responses": { "200": { "description": "A successful response.", "schema": { - "$ref": "#/definitions/v1ImportParametersResponse" + "$ref": "#/definitions/v1ImportFormulasResponse" } }, "default": { @@ -253,28 +225,28 @@ "parameters": [ { "name": "body", - "description": "ImportParametersRequest is the request for importing parameters from Excel.", + "description": "ImportFormulasRequest is the request for importing formulas from Excel.", "in": "body", "required": true, "schema": { - "$ref": "#/definitions/v1ImportParametersRequest" + "$ref": "#/definitions/v1ImportFormulasRequest" } } ], "tags": [ - "ParameterService" + "FormulaService" ] } }, - "/api/v1/finance/parameters/template": { + "/api/v1/finance/formulas/template": { "get": { - "summary": "DownloadParameterTemplate downloads the Excel import template.", - "operationId": "ParameterService_DownloadParameterTemplate", + "summary": "DownloadFormulaTemplate downloads the Excel import template.", + "operationId": "FormulaService_DownloadFormulaTemplate", "responses": { "200": { "description": "A successful response.", "schema": { - "$ref": "#/definitions/v1DownloadParameterTemplateResponse" + "$ref": "#/definitions/v1DownloadFormulaTemplateResponse" } }, "default": { @@ -285,19 +257,19 @@ } }, "tags": [ - "ParameterService" + "FormulaService" ] } }, - "/api/v1/finance/parameters/{paramId}": { + "/api/v1/finance/formulas/{formulaId}": { "get": { - "summary": "GetParameter retrieves a parameter by ID.", - "operationId": "ParameterService_GetParameter", + "summary": "GetFormula retrieves a formula by ID.", + "operationId": "FormulaService_GetFormula", "responses": { "200": { "description": "A successful response.", "schema": { - "$ref": "#/definitions/v1GetParameterResponse" + "$ref": "#/definitions/v1GetFormulaResponse" } }, "default": { @@ -309,25 +281,25 @@ }, "parameters": [ { - "name": "paramId", - "description": "Parameter ID (UUID format).", + "name": "formulaId", + "description": "Formula ID (UUID format).", "in": "path", "required": true, "type": "string" } ], "tags": [ - "ParameterService" + "FormulaService" ] }, "delete": { - "summary": "DeleteParameter soft deletes a parameter.", - "operationId": "ParameterService_DeleteParameter", + "summary": "DeleteFormula soft deletes a formula.", + "operationId": "FormulaService_DeleteFormula", "responses": { "200": { "description": "A successful response.", "schema": { - "$ref": "#/definitions/v1DeleteParameterResponse" + "$ref": "#/definitions/v1DeleteFormulaResponse" } }, "default": { @@ -339,25 +311,25 @@ }, "parameters": [ { - "name": "paramId", - "description": "Parameter ID to delete (UUID format).", + "name": "formulaId", + "description": "Formula ID to delete (UUID format).", "in": "path", "required": true, "type": "string" } ], "tags": [ - "ParameterService" + "FormulaService" ] }, "put": { - "summary": "UpdateParameter updates an existing parameter.\nNote: param_code is immutable and cannot be changed.", - "operationId": "ParameterService_UpdateParameter", + "summary": "UpdateFormula updates an existing formula.\nNote: formula_code is immutable and cannot be changed.", + "operationId": "FormulaService_UpdateFormula", "responses": { "200": { "description": "A successful response.", "schema": { - "$ref": "#/definitions/v1UpdateParameterResponse" + "$ref": "#/definitions/v1UpdateFormulaResponse" } }, "default": { @@ -369,8 +341,8 @@ }, "parameters": [ { - "name": "paramId", - "description": "Parameter ID to update (UUID format).", + "name": "formulaId", + "description": "Formula ID to update (UUID format).", "in": "path", "required": true, "type": "string" @@ -380,24 +352,24 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/ParameterServiceUpdateParameterBody" + "$ref": "#/definitions/FormulaServiceUpdateFormulaBody" } } ], "tags": [ - "ParameterService" + "FormulaService" ] } }, - "/api/v1/finance/rm-categories": { + "/api/v1/finance/parameters": { "get": { - "summary": "ListRMCategories lists raw material categories with search, filter, and pagination.", - "operationId": "RMCategoryService_ListRMCategories", + "summary": "ListParameters lists parameters with search, filter, and pagination.", + "operationId": "ParameterService_ListParameters", "responses": { "200": { "description": "A successful response.", "schema": { - "$ref": "#/definitions/v1ListRMCategoriesResponse" + "$ref": "#/definitions/v1ListParametersResponse" } }, "default": { @@ -426,11 +398,39 @@ }, { "name": "search", - "description": "Search query (searches in code, name, description).", + "description": "Search query (searches in code, name, short_name).", "in": "query", "required": false, "type": "string" }, + { + "name": "dataType", + "description": "Filter by data type.\nDATA_TYPE_UNSPECIFIED (0) means \"show all data types\" (no filter).\n\n - DATA_TYPE_UNSPECIFIED: Default unspecified value - used as \"no filter\" in list/export requests.\n - DATA_TYPE_NUMBER: Numeric values (integer or decimal).\n - DATA_TYPE_TEXT: Text/string values.\n - DATA_TYPE_BOOLEAN: Boolean (true/false) values.", + "in": "query", + "required": false, + "type": "string", + "enum": [ + "DATA_TYPE_UNSPECIFIED", + "DATA_TYPE_NUMBER", + "DATA_TYPE_TEXT", + "DATA_TYPE_BOOLEAN" + ], + "default": "DATA_TYPE_UNSPECIFIED" + }, + { + "name": "paramCategory", + "description": "Filter by category.\nPARAM_CATEGORY_UNSPECIFIED (0) means \"show all categories\" (no filter).\n\n - PARAM_CATEGORY_UNSPECIFIED: Default unspecified value - used as \"no filter\" in list/export requests.\n - PARAM_CATEGORY_INPUT: Input parameter - manually entered values.\n - PARAM_CATEGORY_RATE: Rate parameter - rate/ratio values.\n - PARAM_CATEGORY_CALCULATED: Calculated parameter - computed from other parameters.", + "in": "query", + "required": false, + "type": "string", + "enum": [ + "PARAM_CATEGORY_UNSPECIFIED", + "PARAM_CATEGORY_INPUT", + "PARAM_CATEGORY_RATE", + "PARAM_CATEGORY_CALCULATED" + ], + "default": "PARAM_CATEGORY_UNSPECIFIED" + }, { "name": "activeFilter", "description": "Filter by active status.\nACTIVE_FILTER_UNSPECIFIED (0) = show all, ACTIVE_FILTER_ACTIVE (1) = only active,\nACTIVE_FILTER_INACTIVE (2) = only inactive.\n\n - ACTIVE_FILTER_UNSPECIFIED: Show all records regardless of active status (default).\n - ACTIVE_FILTER_ACTIVE: Show only active records.\n - ACTIVE_FILTER_INACTIVE: Show only inactive records.", @@ -446,7 +446,7 @@ }, { "name": "sortBy", - "description": "Sort field: \"code\", \"name\", \"created_at\" (default: \"code\").", + "description": "Sort field: \"code\", \"name\", \"category\", \"data_type\", \"created_at\" (default: \"code\").", "in": "query", "required": false, "type": "string" @@ -460,17 +460,17 @@ } ], "tags": [ - "RMCategoryService" + "ParameterService" ] }, "post": { - "summary": "CreateRMCategory creates a new raw material category.", - "operationId": "RMCategoryService_CreateRMCategory", + "summary": "CreateParameter creates a new parameter.", + "operationId": "ParameterService_CreateParameter", "responses": { "200": { "description": "A successful response.", "schema": { - "$ref": "#/definitions/v1CreateRMCategoryResponse" + "$ref": "#/definitions/v1CreateParameterResponse" } }, "default": { @@ -483,28 +483,28 @@ "parameters": [ { "name": "body", - "description": "CreateRMCategoryRequest is the request for creating a new raw material category.", + "description": "CreateParameterRequest is the request for creating a new parameter.", "in": "body", "required": true, "schema": { - "$ref": "#/definitions/v1CreateRMCategoryRequest" + "$ref": "#/definitions/v1CreateParameterRequest" } } ], "tags": [ - "RMCategoryService" + "ParameterService" ] } }, - "/api/v1/finance/rm-categories/export": { + "/api/v1/finance/parameters/export": { "get": { - "summary": "ExportRMCategories exports raw material categories to Excel file.", - "operationId": "RMCategoryService_ExportRMCategories", + "summary": "ExportParameters exports parameters to Excel file.", + "operationId": "ParameterService_ExportParameters", "responses": { "200": { "description": "A successful response.", "schema": { - "$ref": "#/definitions/v1ExportRMCategoriesResponse" + "$ref": "#/definitions/v1ExportParametersResponse" } }, "default": { @@ -515,9 +515,37 @@ } }, "parameters": [ + { + "name": "dataType", + "description": "Filter by data type.\n\n - DATA_TYPE_UNSPECIFIED: Default unspecified value - used as \"no filter\" in list/export requests.\n - DATA_TYPE_NUMBER: Numeric values (integer or decimal).\n - DATA_TYPE_TEXT: Text/string values.\n - DATA_TYPE_BOOLEAN: Boolean (true/false) values.", + "in": "query", + "required": false, + "type": "string", + "enum": [ + "DATA_TYPE_UNSPECIFIED", + "DATA_TYPE_NUMBER", + "DATA_TYPE_TEXT", + "DATA_TYPE_BOOLEAN" + ], + "default": "DATA_TYPE_UNSPECIFIED" + }, + { + "name": "paramCategory", + "description": "Filter by category.\n\n - PARAM_CATEGORY_UNSPECIFIED: Default unspecified value - used as \"no filter\" in list/export requests.\n - PARAM_CATEGORY_INPUT: Input parameter - manually entered values.\n - PARAM_CATEGORY_RATE: Rate parameter - rate/ratio values.\n - PARAM_CATEGORY_CALCULATED: Calculated parameter - computed from other parameters.", + "in": "query", + "required": false, + "type": "string", + "enum": [ + "PARAM_CATEGORY_UNSPECIFIED", + "PARAM_CATEGORY_INPUT", + "PARAM_CATEGORY_RATE", + "PARAM_CATEGORY_CALCULATED" + ], + "default": "PARAM_CATEGORY_UNSPECIFIED" + }, { "name": "activeFilter", - "description": "Filter by active status.\nACTIVE_FILTER_UNSPECIFIED (0) = export all, ACTIVE_FILTER_ACTIVE (1) = only active,\nACTIVE_FILTER_INACTIVE (2) = only inactive.\n\n - ACTIVE_FILTER_UNSPECIFIED: Show all records regardless of active status (default).\n - ACTIVE_FILTER_ACTIVE: Show only active records.\n - ACTIVE_FILTER_INACTIVE: Show only inactive records.", + "description": "Filter by active status.\n\n - ACTIVE_FILTER_UNSPECIFIED: Show all records regardless of active status (default).\n - ACTIVE_FILTER_ACTIVE: Show only active records.\n - ACTIVE_FILTER_INACTIVE: Show only inactive records.", "in": "query", "required": false, "type": "string", @@ -530,19 +558,19 @@ } ], "tags": [ - "RMCategoryService" + "ParameterService" ] } }, - "/api/v1/finance/rm-categories/import": { + "/api/v1/finance/parameters/import": { "post": { - "summary": "ImportRMCategories imports raw material categories from Excel file.", - "operationId": "RMCategoryService_ImportRMCategories", + "summary": "ImportParameters imports parameters from Excel file.", + "operationId": "ParameterService_ImportParameters", "responses": { "200": { "description": "A successful response.", "schema": { - "$ref": "#/definitions/v1ImportRMCategoriesResponse" + "$ref": "#/definitions/v1ImportParametersResponse" } }, "default": { @@ -555,28 +583,28 @@ "parameters": [ { "name": "body", - "description": "ImportRMCategoriesRequest is the request for importing raw material categories from Excel.", + "description": "ImportParametersRequest is the request for importing parameters from Excel.", "in": "body", "required": true, "schema": { - "$ref": "#/definitions/v1ImportRMCategoriesRequest" + "$ref": "#/definitions/v1ImportParametersRequest" } } ], "tags": [ - "RMCategoryService" + "ParameterService" ] } }, - "/api/v1/finance/rm-categories/template": { + "/api/v1/finance/parameters/template": { "get": { - "summary": "DownloadRMCategoryTemplate downloads the Excel import template.", - "operationId": "RMCategoryService_DownloadRMCategoryTemplate", + "summary": "DownloadParameterTemplate downloads the Excel import template.", + "operationId": "ParameterService_DownloadParameterTemplate", "responses": { "200": { "description": "A successful response.", "schema": { - "$ref": "#/definitions/v1DownloadRMCategoryTemplateResponse" + "$ref": "#/definitions/v1DownloadParameterTemplateResponse" } }, "default": { @@ -587,19 +615,19 @@ } }, "tags": [ - "RMCategoryService" + "ParameterService" ] } }, - "/api/v1/finance/rm-categories/{rmCategoryId}": { + "/api/v1/finance/parameters/{paramId}": { "get": { - "summary": "GetRMCategory retrieves a raw material category by ID.", - "operationId": "RMCategoryService_GetRMCategory", + "summary": "GetParameter retrieves a parameter by ID.", + "operationId": "ParameterService_GetParameter", "responses": { "200": { "description": "A successful response.", "schema": { - "$ref": "#/definitions/v1GetRMCategoryResponse" + "$ref": "#/definitions/v1GetParameterResponse" } }, "default": { @@ -611,25 +639,25 @@ }, "parameters": [ { - "name": "rmCategoryId", - "description": "Raw material category ID (UUID format).", + "name": "paramId", + "description": "Parameter ID (UUID format).", "in": "path", "required": true, "type": "string" } ], "tags": [ - "RMCategoryService" + "ParameterService" ] }, "delete": { - "summary": "DeleteRMCategory soft deletes a raw material category.", - "operationId": "RMCategoryService_DeleteRMCategory", + "summary": "DeleteParameter soft deletes a parameter.", + "operationId": "ParameterService_DeleteParameter", "responses": { "200": { "description": "A successful response.", "schema": { - "$ref": "#/definitions/v1DeleteRMCategoryResponse" + "$ref": "#/definitions/v1DeleteParameterResponse" } }, "default": { @@ -641,25 +669,25 @@ }, "parameters": [ { - "name": "rmCategoryId", - "description": "Raw material category ID to delete (UUID format).", + "name": "paramId", + "description": "Parameter ID to delete (UUID format).", "in": "path", "required": true, "type": "string" } ], "tags": [ - "RMCategoryService" + "ParameterService" ] }, "put": { - "summary": "UpdateRMCategory updates an existing raw material category.\nNote: category_code is immutable and cannot be changed.", - "operationId": "RMCategoryService_UpdateRMCategory", + "summary": "UpdateParameter updates an existing parameter.\nNote: param_code is immutable and cannot be changed.", + "operationId": "ParameterService_UpdateParameter", "responses": { "200": { "description": "A successful response.", "schema": { - "$ref": "#/definitions/v1UpdateRMCategoryResponse" + "$ref": "#/definitions/v1UpdateParameterResponse" } }, "default": { @@ -671,8 +699,8 @@ }, "parameters": [ { - "name": "rmCategoryId", - "description": "Raw material category ID to update (UUID format).", + "name": "paramId", + "description": "Parameter ID to update (UUID format).", "in": "path", "required": true, "type": "string" @@ -682,24 +710,24 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/RMCategoryServiceUpdateRMCategoryBody" + "$ref": "#/definitions/ParameterServiceUpdateParameterBody" } } ], "tags": [ - "RMCategoryService" + "ParameterService" ] } }, - "/api/v1/finance/uoms": { + "/api/v1/finance/rm-categories": { "get": { - "summary": "ListUOMs lists UOMs with search, filter, and pagination.", - "operationId": "UOMService_ListUOMs", + "summary": "ListRMCategories lists raw material categories with search, filter, and pagination.", + "operationId": "RMCategoryService_ListRMCategories", "responses": { "200": { "description": "A successful response.", "schema": { - "$ref": "#/definitions/v1ListUOMsResponse" + "$ref": "#/definitions/v1ListRMCategoriesResponse" } }, "default": { @@ -733,21 +761,6 @@ "required": false, "type": "string" }, - { - "name": "category", - "description": "Filter by category.\nUOM_CATEGORY_UNSPECIFIED (0) means \"show all categories\" (no filter).\n\n - UOM_CATEGORY_UNSPECIFIED: Default unspecified value - used as \"no filter\" in list/export requests.\n - UOM_CATEGORY_WEIGHT: Weight-based units (e.g., KG, GR, TON).\n - UOM_CATEGORY_LENGTH: Length-based units (e.g., MTR, CM, YARD).\n - UOM_CATEGORY_VOLUME: Volume-based units (e.g., LTR, ML).\n - UOM_CATEGORY_QUANTITY: Quantity-based units (e.g., PCS, BOX, SET).", - "in": "query", - "required": false, - "type": "string", - "enum": [ - "UOM_CATEGORY_UNSPECIFIED", - "UOM_CATEGORY_WEIGHT", - "UOM_CATEGORY_LENGTH", - "UOM_CATEGORY_VOLUME", - "UOM_CATEGORY_QUANTITY" - ], - "default": "UOM_CATEGORY_UNSPECIFIED" - }, { "name": "activeFilter", "description": "Filter by active status.\nACTIVE_FILTER_UNSPECIFIED (0) = show all, ACTIVE_FILTER_ACTIVE (1) = only active,\nACTIVE_FILTER_INACTIVE (2) = only inactive.\n\n - ACTIVE_FILTER_UNSPECIFIED: Show all records regardless of active status (default).\n - ACTIVE_FILTER_ACTIVE: Show only active records.\n - ACTIVE_FILTER_INACTIVE: Show only inactive records.", @@ -777,17 +790,17 @@ } ], "tags": [ - "UOMService" + "RMCategoryService" ] }, "post": { - "summary": "CreateUOM creates a new UOM.", - "operationId": "UOMService_CreateUOM", + "summary": "CreateRMCategory creates a new raw material category.", + "operationId": "RMCategoryService_CreateRMCategory", "responses": { "200": { "description": "A successful response.", "schema": { - "$ref": "#/definitions/v1CreateUOMResponse" + "$ref": "#/definitions/v1CreateRMCategoryResponse" } }, "default": { @@ -800,28 +813,28 @@ "parameters": [ { "name": "body", - "description": "CreateUOMRequest is the request for creating a new UOM.", + "description": "CreateRMCategoryRequest is the request for creating a new raw material category.", "in": "body", "required": true, "schema": { - "$ref": "#/definitions/v1CreateUOMRequest" + "$ref": "#/definitions/v1CreateRMCategoryRequest" } } ], "tags": [ - "UOMService" + "RMCategoryService" ] } }, - "/api/v1/finance/uoms/export": { + "/api/v1/finance/rm-categories/export": { "get": { - "summary": "ExportUOMs exports UOMs to Excel file.", - "operationId": "UOMService_ExportUOMs", + "summary": "ExportRMCategories exports raw material categories to Excel file.", + "operationId": "RMCategoryService_ExportRMCategories", "responses": { "200": { "description": "A successful response.", "schema": { - "$ref": "#/definitions/v1ExportUOMsResponse" + "$ref": "#/definitions/v1ExportRMCategoriesResponse" } }, "default": { @@ -832,21 +845,6 @@ } }, "parameters": [ - { - "name": "category", - "description": "Filter by category.\nUOM_CATEGORY_UNSPECIFIED (0) means \"export all categories\".\n\n - UOM_CATEGORY_UNSPECIFIED: Default unspecified value - used as \"no filter\" in list/export requests.\n - UOM_CATEGORY_WEIGHT: Weight-based units (e.g., KG, GR, TON).\n - UOM_CATEGORY_LENGTH: Length-based units (e.g., MTR, CM, YARD).\n - UOM_CATEGORY_VOLUME: Volume-based units (e.g., LTR, ML).\n - UOM_CATEGORY_QUANTITY: Quantity-based units (e.g., PCS, BOX, SET).", - "in": "query", - "required": false, - "type": "string", - "enum": [ - "UOM_CATEGORY_UNSPECIFIED", - "UOM_CATEGORY_WEIGHT", - "UOM_CATEGORY_LENGTH", - "UOM_CATEGORY_VOLUME", - "UOM_CATEGORY_QUANTITY" - ], - "default": "UOM_CATEGORY_UNSPECIFIED" - }, { "name": "activeFilter", "description": "Filter by active status.\nACTIVE_FILTER_UNSPECIFIED (0) = export all, ACTIVE_FILTER_ACTIVE (1) = only active,\nACTIVE_FILTER_INACTIVE (2) = only inactive.\n\n - ACTIVE_FILTER_UNSPECIFIED: Show all records regardless of active status (default).\n - ACTIVE_FILTER_ACTIVE: Show only active records.\n - ACTIVE_FILTER_INACTIVE: Show only inactive records.", @@ -862,19 +860,19 @@ } ], "tags": [ - "UOMService" + "RMCategoryService" ] } }, - "/api/v1/finance/uoms/import": { + "/api/v1/finance/rm-categories/import": { "post": { - "summary": "ImportUOMs imports UOMs from Excel file.", - "operationId": "UOMService_ImportUOMs", + "summary": "ImportRMCategories imports raw material categories from Excel file.", + "operationId": "RMCategoryService_ImportRMCategories", "responses": { "200": { "description": "A successful response.", "schema": { - "$ref": "#/definitions/v1ImportUOMsResponse" + "$ref": "#/definitions/v1ImportRMCategoriesResponse" } }, "default": { @@ -887,28 +885,28 @@ "parameters": [ { "name": "body", - "description": "ImportUOMsRequest is the request for importing UOMs from Excel.", + "description": "ImportRMCategoriesRequest is the request for importing raw material categories from Excel.", "in": "body", "required": true, "schema": { - "$ref": "#/definitions/v1ImportUOMsRequest" + "$ref": "#/definitions/v1ImportRMCategoriesRequest" } } ], "tags": [ - "UOMService" + "RMCategoryService" ] } }, - "/api/v1/finance/uoms/template": { + "/api/v1/finance/rm-categories/template": { "get": { - "summary": "DownloadTemplate downloads the Excel import template.", - "operationId": "UOMService_DownloadTemplate", + "summary": "DownloadRMCategoryTemplate downloads the Excel import template.", + "operationId": "RMCategoryService_DownloadRMCategoryTemplate", "responses": { "200": { "description": "A successful response.", "schema": { - "$ref": "#/definitions/v1DownloadTemplateResponse" + "$ref": "#/definitions/v1DownloadRMCategoryTemplateResponse" } }, "default": { @@ -919,19 +917,19 @@ } }, "tags": [ - "UOMService" + "RMCategoryService" ] } }, - "/api/v1/finance/uoms/{uomId}": { + "/api/v1/finance/rm-categories/{rmCategoryId}": { "get": { - "summary": "GetUOM retrieves a UOM by ID.", - "operationId": "UOMService_GetUOM", + "summary": "GetRMCategory retrieves a raw material category by ID.", + "operationId": "RMCategoryService_GetRMCategory", "responses": { "200": { "description": "A successful response.", "schema": { - "$ref": "#/definitions/v1GetUOMResponse" + "$ref": "#/definitions/v1GetRMCategoryResponse" } }, "default": { @@ -943,25 +941,25 @@ }, "parameters": [ { - "name": "uomId", - "description": "UOM ID (UUID format).", + "name": "rmCategoryId", + "description": "Raw material category ID (UUID format).", "in": "path", "required": true, "type": "string" } ], "tags": [ - "UOMService" + "RMCategoryService" ] }, "delete": { - "summary": "DeleteUOM soft deletes a UOM.", - "operationId": "UOMService_DeleteUOM", + "summary": "DeleteRMCategory soft deletes a raw material category.", + "operationId": "RMCategoryService_DeleteRMCategory", "responses": { "200": { "description": "A successful response.", "schema": { - "$ref": "#/definitions/v1DeleteUOMResponse" + "$ref": "#/definitions/v1DeleteRMCategoryResponse" } }, "default": { @@ -973,25 +971,25 @@ }, "parameters": [ { - "name": "uomId", - "description": "UOM ID to delete (UUID format).", + "name": "rmCategoryId", + "description": "Raw material category ID to delete (UUID format).", "in": "path", "required": true, "type": "string" } ], "tags": [ - "UOMService" + "RMCategoryService" ] }, "put": { - "summary": "UpdateUOM updates an existing UOM.\nNote: uom_code is immutable and cannot be changed.", - "operationId": "UOMService_UpdateUOM", + "summary": "UpdateRMCategory updates an existing raw material category.\nNote: category_code is immutable and cannot be changed.", + "operationId": "RMCategoryService_UpdateRMCategory", "responses": { "200": { "description": "A successful response.", "schema": { - "$ref": "#/definitions/v1UpdateUOMResponse" + "$ref": "#/definitions/v1UpdateRMCategoryResponse" } }, "default": { @@ -1003,8 +1001,8 @@ }, "parameters": [ { - "name": "uomId", - "description": "UOM ID to update (UUID format).", + "name": "rmCategoryId", + "description": "Raw material category ID to update (UUID format).", "in": "path", "required": true, "type": "string" @@ -1014,144 +1012,1165 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/UOMServiceUpdateUOMBody" + "$ref": "#/definitions/RMCategoryServiceUpdateRMCategoryBody" } } ], "tags": [ - "UOMService" + "RMCategoryService" ] } - } - }, - "definitions": { - "ParameterServiceUpdateParameterBody": { - "type": "object", - "properties": { - "paramName": { - "type": "string", - "description": "New display name (optional, 1-200 chars if provided)." - }, - "paramShortName": { - "type": "string", - "description": "New short name (optional, max 50 chars)." - }, - "dataType": { - "$ref": "#/definitions/v1DataType", - "description": "New data type (optional, cannot be UNSPECIFIED if provided)." - }, - "paramCategory": { - "$ref": "#/definitions/v1ParamCategory", - "description": "New category (optional, cannot be UNSPECIFIED if provided)." - }, - "uomId": { - "type": "string", - "description": "New UOM reference (optional, UUID format, empty string to clear)." - }, - "defaultValue": { - "type": "string", - "description": "New default value (optional)." - }, - "minValue": { - "type": "string", - "description": "New minimum value (optional)." - }, - "maxValue": { - "type": "string", - "description": "New maximum value (optional)." - }, - "isActive": { - "type": "boolean", - "description": "New active status (optional)." - } - }, - "description": "UpdateParameterRequest is the request for updating a parameter.\nNote: param_code is immutable and cannot be changed after creation." - }, - "protobufAny": { - "type": "object", - "properties": { - "@type": { - "type": "string" - } - }, - "additionalProperties": {} }, - "rpcStatus": { - "type": "object", - "properties": { - "code": { - "type": "integer", - "format": "int32" - }, - "message": { - "type": "string" - }, - "details": { - "type": "array", - "items": { - "type": "object", - "$ref": "#/definitions/protobufAny" + "/api/v1/finance/uoms": { + "get": { + "summary": "ListUOMs lists UOMs with search, filter, and pagination.", + "operationId": "UOMService_ListUOMs", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1ListUOMsResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } } - } - } - }, - "v1ActiveFilter": { - "type": "string", - "enum": [ - "ACTIVE_FILTER_UNSPECIFIED", - "ACTIVE_FILTER_ACTIVE", - "ACTIVE_FILTER_INACTIVE" - ], - "default": "ACTIVE_FILTER_UNSPECIFIED", - "description": "ActiveFilter represents filter options for is_active field.\n\n - ACTIVE_FILTER_UNSPECIFIED: Show all records regardless of active status (default).\n - ACTIVE_FILTER_ACTIVE: Show only active records.\n - ACTIVE_FILTER_INACTIVE: Show only inactive records." - }, - "v1AuditInfo": { - "type": "object", - "properties": { - "createdAt": { - "type": "string", - "description": "Timestamp when the record was created (ISO 8601)." - }, - "createdBy": { - "type": "string", - "description": "User who created the record." - }, - "updatedAt": { - "type": "string", - "description": "Timestamp when the record was last updated (ISO 8601)." }, - "updatedBy": { - "type": "string", - "description": "User who last updated the record." - } - }, - "description": "AuditInfo contains audit trail information." - }, - "v1BaseResponse": { - "type": "object", - "properties": { - "validationErrors": { - "type": "array", - "items": { - "type": "object", - "$ref": "#/definitions/v1ValidationError" + "parameters": [ + { + "name": "page", + "description": "Page number (1-indexed, default 1, min 1).", + "in": "query", + "required": false, + "type": "integer", + "format": "int32" }, - "description": "List of validation errors if any." - }, - "statusCode": { - "type": "string", - "description": "HTTP-like status code (e.g., \"200\", \"400\", \"404\", \"500\")." - }, - "isSuccess": { - "type": "boolean", - "description": "Indicates if the operation was successful." - }, - "message": { - "type": "string", - "description": "Human-readable message describing the result." - } - }, - "description": "BaseResponse is the standard response wrapper for all API responses." + { + "name": "pageSize", + "description": "Items per page (1-100, default 10).", + "in": "query", + "required": false, + "type": "integer", + "format": "int32" + }, + { + "name": "search", + "description": "Search query (searches in code, name, description).", + "in": "query", + "required": false, + "type": "string" + }, + { + "name": "activeFilter", + "description": "Filter by active status.\nACTIVE_FILTER_UNSPECIFIED (0) = show all, ACTIVE_FILTER_ACTIVE (1) = only active,\nACTIVE_FILTER_INACTIVE (2) = only inactive.\n\n - ACTIVE_FILTER_UNSPECIFIED: Show all records regardless of active status (default).\n - ACTIVE_FILTER_ACTIVE: Show only active records.\n - ACTIVE_FILTER_INACTIVE: Show only inactive records.", + "in": "query", + "required": false, + "type": "string", + "enum": [ + "ACTIVE_FILTER_UNSPECIFIED", + "ACTIVE_FILTER_ACTIVE", + "ACTIVE_FILTER_INACTIVE" + ], + "default": "ACTIVE_FILTER_UNSPECIFIED" + }, + { + "name": "sortBy", + "description": "Sort field: \"code\", \"name\", \"category\", \"created_at\" (default: \"code\").", + "in": "query", + "required": false, + "type": "string" + }, + { + "name": "sortOrder", + "description": "Sort order: \"asc\", \"desc\" (default: \"asc\").", + "in": "query", + "required": false, + "type": "string" + }, + { + "name": "uomCategoryId", + "description": "Filter by category ID (empty = no filter).", + "in": "query", + "required": false, + "type": "string" + } + ], + "tags": [ + "UOMService" + ] + }, + "post": { + "summary": "CreateUOM creates a new UOM.", + "operationId": "UOMService_CreateUOM", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1CreateUOMResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "body", + "description": "CreateUOMRequest is the request for creating a new UOM.", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/v1CreateUOMRequest" + } + } + ], + "tags": [ + "UOMService" + ] + } + }, + "/api/v1/finance/uoms/export": { + "get": { + "summary": "ExportUOMs exports UOMs to Excel file.", + "operationId": "UOMService_ExportUOMs", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1ExportUOMsResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "activeFilter", + "description": "Filter by active status.\nACTIVE_FILTER_UNSPECIFIED (0) = export all, ACTIVE_FILTER_ACTIVE (1) = only active,\nACTIVE_FILTER_INACTIVE (2) = only inactive.\n\n - ACTIVE_FILTER_UNSPECIFIED: Show all records regardless of active status (default).\n - ACTIVE_FILTER_ACTIVE: Show only active records.\n - ACTIVE_FILTER_INACTIVE: Show only inactive records.", + "in": "query", + "required": false, + "type": "string", + "enum": [ + "ACTIVE_FILTER_UNSPECIFIED", + "ACTIVE_FILTER_ACTIVE", + "ACTIVE_FILTER_INACTIVE" + ], + "default": "ACTIVE_FILTER_UNSPECIFIED" + }, + { + "name": "uomCategoryId", + "description": "Filter by category ID (empty = export all categories).", + "in": "query", + "required": false, + "type": "string" + } + ], + "tags": [ + "UOMService" + ] + } + }, + "/api/v1/finance/uoms/import": { + "post": { + "summary": "ImportUOMs imports UOMs from Excel file.", + "operationId": "UOMService_ImportUOMs", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1ImportUOMsResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "body", + "description": "ImportUOMsRequest is the request for importing UOMs from Excel.", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/v1ImportUOMsRequest" + } + } + ], + "tags": [ + "UOMService" + ] + } + }, + "/api/v1/finance/uoms/template": { + "get": { + "summary": "DownloadTemplate downloads the Excel import template.", + "operationId": "UOMService_DownloadTemplate", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1DownloadTemplateResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "tags": [ + "UOMService" + ] + } + }, + "/api/v1/finance/uoms/{uomId}": { + "get": { + "summary": "GetUOM retrieves a UOM by ID.", + "operationId": "UOMService_GetUOM", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1GetUOMResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "uomId", + "description": "UOM ID (UUID format).", + "in": "path", + "required": true, + "type": "string" + } + ], + "tags": [ + "UOMService" + ] + }, + "delete": { + "summary": "DeleteUOM soft deletes a UOM.", + "operationId": "UOMService_DeleteUOM", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1DeleteUOMResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "uomId", + "description": "UOM ID to delete (UUID format).", + "in": "path", + "required": true, + "type": "string" + } + ], + "tags": [ + "UOMService" + ] + }, + "put": { + "summary": "UpdateUOM updates an existing UOM.\nNote: uom_code is immutable and cannot be changed.", + "operationId": "UOMService_UpdateUOM", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1UpdateUOMResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "uomId", + "description": "UOM ID to update (UUID format).", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/UOMServiceUpdateUOMBody" + } + } + ], + "tags": [ + "UOMService" + ] + } + }, + "/api/v1/finance/uom-categories": { + "get": { + "summary": "ListUOMCategories lists UOM categories with search, filter, and pagination.", + "operationId": "UOMCategoryService_ListUOMCategories", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1ListUOMCategoriesResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "page", + "description": "Page number (1-indexed, default 1, min 1).", + "in": "query", + "required": false, + "type": "integer", + "format": "int32" + }, + { + "name": "pageSize", + "description": "Items per page (1-100, default 10).", + "in": "query", + "required": false, + "type": "integer", + "format": "int32" + }, + { + "name": "search", + "description": "Search query (searches in code, name, description).", + "in": "query", + "required": false, + "type": "string" + }, + { + "name": "activeFilter", + "description": "Filter by active status.\nACTIVE_FILTER_UNSPECIFIED (0) = show all, ACTIVE_FILTER_ACTIVE (1) = only active,\nACTIVE_FILTER_INACTIVE (2) = only inactive.\n\n - ACTIVE_FILTER_UNSPECIFIED: Show all records regardless of active status (default).\n - ACTIVE_FILTER_ACTIVE: Show only active records.\n - ACTIVE_FILTER_INACTIVE: Show only inactive records.", + "in": "query", + "required": false, + "type": "string", + "enum": [ + "ACTIVE_FILTER_UNSPECIFIED", + "ACTIVE_FILTER_ACTIVE", + "ACTIVE_FILTER_INACTIVE" + ], + "default": "ACTIVE_FILTER_UNSPECIFIED" + }, + { + "name": "sortBy", + "description": "Sort field: \"code\", \"name\", \"created_at\" (default: \"code\").", + "in": "query", + "required": false, + "type": "string" + }, + { + "name": "sortOrder", + "description": "Sort order: \"asc\", \"desc\" (default: \"asc\").", + "in": "query", + "required": false, + "type": "string" + } + ], + "tags": [ + "UOMCategoryService" + ] + }, + "post": { + "summary": "CreateUOMCategory creates a new UOM category.", + "operationId": "UOMCategoryService_CreateUOMCategory", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1CreateUOMCategoryResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "body", + "description": "CreateUOMCategoryRequest is the request for creating a new UOM category.", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/v1CreateUOMCategoryRequest" + } + } + ], + "tags": [ + "UOMCategoryService" + ] + } + }, + "/api/v1/finance/uom-categories/export": { + "get": { + "summary": "ExportUOMCategories exports UOM categories to Excel file.", + "operationId": "UOMCategoryService_ExportUOMCategories", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1ExportUOMCategoriesResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "activeFilter", + "description": "Filter by active status.\nACTIVE_FILTER_UNSPECIFIED (0) = export all, ACTIVE_FILTER_ACTIVE (1) = only active,\nACTIVE_FILTER_INACTIVE (2) = only inactive.\n\n - ACTIVE_FILTER_UNSPECIFIED: Show all records regardless of active status (default).\n - ACTIVE_FILTER_ACTIVE: Show only active records.\n - ACTIVE_FILTER_INACTIVE: Show only inactive records.", + "in": "query", + "required": false, + "type": "string", + "enum": [ + "ACTIVE_FILTER_UNSPECIFIED", + "ACTIVE_FILTER_ACTIVE", + "ACTIVE_FILTER_INACTIVE" + ], + "default": "ACTIVE_FILTER_UNSPECIFIED" + } + ], + "tags": [ + "UOMCategoryService" + ] + } + }, + "/api/v1/finance/uom-categories/import": { + "post": { + "summary": "ImportUOMCategories imports UOM categories from Excel file.", + "operationId": "UOMCategoryService_ImportUOMCategories", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1ImportUOMCategoriesResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "body", + "description": "ImportUOMCategoriesRequest is the request for importing UOM categories from Excel.", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/v1ImportUOMCategoriesRequest" + } + } + ], + "tags": [ + "UOMCategoryService" + ] + } + }, + "/api/v1/finance/uom-categories/template": { + "get": { + "summary": "DownloadUOMCategoryTemplate downloads the Excel import template.", + "operationId": "UOMCategoryService_DownloadUOMCategoryTemplate", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1DownloadUOMCategoryTemplateResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "tags": [ + "UOMCategoryService" + ] + } + }, + "/api/v1/finance/uom-categories/{uomCategoryId}": { + "get": { + "summary": "GetUOMCategory retrieves a UOM category by ID.", + "operationId": "UOMCategoryService_GetUOMCategory", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1GetUOMCategoryResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "uomCategoryId", + "description": "UOM category ID (UUID format).", + "in": "path", + "required": true, + "type": "string" + } + ], + "tags": [ + "UOMCategoryService" + ] + }, + "delete": { + "summary": "DeleteUOMCategory soft deletes a UOM category.", + "operationId": "UOMCategoryService_DeleteUOMCategory", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1DeleteUOMCategoryResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "uomCategoryId", + "description": "UOM category ID to delete (UUID format).", + "in": "path", + "required": true, + "type": "string" + } + ], + "tags": [ + "UOMCategoryService" + ] + }, + "put": { + "summary": "UpdateUOMCategory updates an existing UOM category.\nNote: category_code is immutable and cannot be changed.", + "operationId": "UOMCategoryService_UpdateUOMCategory", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1UpdateUOMCategoryResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "uomCategoryId", + "description": "UOM category ID to update (UUID format).", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/UOMCategoryServiceUpdateUOMCategoryBody" + } + } + ], + "tags": [ + "UOMCategoryService" + ] + } + } + }, + "definitions": { + "FormulaServiceUpdateFormulaBody": { + "type": "object", + "properties": { + "formulaName": { + "type": "string", + "description": "New display name (optional, 1-200 chars if provided)." + }, + "formulaType": { + "$ref": "#/definitions/v1FormulaType", + "description": "New formula type (optional)." + }, + "expression": { + "type": "string", + "description": "New expression (optional, 1-5000 chars if provided)." + }, + "resultParamId": { + "type": "string", + "description": "New result parameter ID (optional, UUID)." + }, + "inputParamIds": { + "type": "array", + "items": { + "type": "string" + }, + "description": "New input parameter IDs (optional, replaces all existing)." + }, + "description": { + "type": "string", + "description": "New description (optional, max 1000 chars)." + }, + "isActive": { + "type": "boolean", + "description": "New active status (optional)." + } + }, + "description": "UpdateFormulaRequest is the request for updating a formula.\nNote: formula_code is immutable and cannot be changed after creation." + }, + "protobufAny": { + "type": "object", + "properties": { + "@type": { + "type": "string" + } + }, + "additionalProperties": {} + }, + "rpcStatus": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + }, + "details": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/protobufAny" + } + } + } + }, + "v1ActiveFilter": { + "type": "string", + "enum": [ + "ACTIVE_FILTER_UNSPECIFIED", + "ACTIVE_FILTER_ACTIVE", + "ACTIVE_FILTER_INACTIVE" + ], + "default": "ACTIVE_FILTER_UNSPECIFIED", + "description": "ActiveFilter represents filter options for is_active field.\n\n - ACTIVE_FILTER_UNSPECIFIED: Show all records regardless of active status (default).\n - ACTIVE_FILTER_ACTIVE: Show only active records.\n - ACTIVE_FILTER_INACTIVE: Show only inactive records." + }, + "v1AuditInfo": { + "type": "object", + "properties": { + "createdAt": { + "type": "string", + "description": "Timestamp when the record was created (ISO 8601)." + }, + "createdBy": { + "type": "string", + "description": "User who created the record." + }, + "updatedAt": { + "type": "string", + "description": "Timestamp when the record was last updated (ISO 8601)." + }, + "updatedBy": { + "type": "string", + "description": "User who last updated the record." + } + }, + "description": "AuditInfo contains audit trail information." + }, + "v1BaseResponse": { + "type": "object", + "properties": { + "validationErrors": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/v1ValidationError" + }, + "description": "List of validation errors if any." + }, + "statusCode": { + "type": "string", + "description": "HTTP-like status code (e.g., \"200\", \"400\", \"404\", \"500\")." + }, + "isSuccess": { + "type": "boolean", + "description": "Indicates if the operation was successful." + }, + "message": { + "type": "string", + "description": "Human-readable message describing the result." + } + }, + "description": "BaseResponse is the standard response wrapper for all API responses." + }, + "v1CreateFormulaRequest": { + "type": "object", + "properties": { + "formulaCode": { + "type": "string", + "description": "Unique code (uppercase, alphanumeric with underscore, 1-50 chars)." + }, + "formulaName": { + "type": "string", + "description": "Display name (1-200 chars)." + }, + "formulaType": { + "$ref": "#/definitions/v1FormulaType", + "description": "Formula type (required, cannot be UNSPECIFIED)." + }, + "expression": { + "type": "string", + "description": "Expression (required, 1-5000 chars)." + }, + "resultParamId": { + "type": "string", + "description": "Result parameter ID (UUID, required)." + }, + "inputParamIds": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Input parameter IDs (at least 1 for CALCULATION type)." + }, + "description": { + "type": "string", + "description": "Optional description (max 1000 chars)." + } + }, + "description": "CreateFormulaRequest is the request for creating a new formula." + }, + "v1CreateFormulaResponse": { + "type": "object", + "properties": { + "base": { + "$ref": "#/definitions/v1BaseResponse", + "description": "Standard response metadata." + }, + "data": { + "$ref": "#/definitions/v1Formula", + "description": "Created formula data." + } + }, + "description": "CreateFormulaResponse is the response for creating a formula." + }, + "v1DeleteFormulaResponse": { + "type": "object", + "properties": { + "base": { + "$ref": "#/definitions/v1BaseResponse", + "description": "Standard response metadata." + } + }, + "description": "DeleteFormulaResponse is the response for deleting a formula." + }, + "v1DownloadFormulaTemplateResponse": { + "type": "object", + "properties": { + "base": { + "$ref": "#/definitions/v1BaseResponse", + "description": "Standard response metadata." + }, + "fileContent": { + "type": "string", + "format": "byte", + "description": "Excel template file content as bytes (.xlsx format)." + }, + "fileName": { + "type": "string", + "description": "Suggested filename." + } + }, + "description": "DownloadFormulaTemplateResponse is the response containing the template file." + }, + "v1ExportFormulasResponse": { + "type": "object", + "properties": { + "base": { + "$ref": "#/definitions/v1BaseResponse", + "description": "Standard response metadata." + }, + "fileContent": { + "type": "string", + "format": "byte", + "description": "Excel file content as bytes (.xlsx format)." + }, + "fileName": { + "type": "string", + "description": "Suggested filename." + } + }, + "description": "ExportFormulasResponse is the response containing the Excel file." + }, + "v1Formula": { + "type": "object", + "properties": { + "formulaId": { + "type": "string", + "description": "Unique identifier (UUID)." + }, + "formulaCode": { + "type": "string", + "description": "Unique code (e.g., \"COST_ELEC_STD\"). Immutable after creation." + }, + "formulaName": { + "type": "string", + "description": "Display name (e.g., \"Electricity Cost Standard\")." + }, + "formulaType": { + "$ref": "#/definitions/v1FormulaType", + "description": "Type of formula expression." + }, + "expression": { + "type": "string", + "description": "Expression text (e.g., \"ELEC_CONSUMPTION * ELEC_RATE\" or SQL query)." + }, + "resultParamId": { + "type": "string", + "description": "Result/output parameter ID (UUID FK to mst_parameter)." + }, + "resultParamCode": { + "type": "string", + "description": "Resolved result parameter code (read-only, from join)." + }, + "resultParamName": { + "type": "string", + "description": "Resolved result parameter name (read-only, from join)." + }, + "description": { + "type": "string", + "description": "Optional description." + }, + "version": { + "type": "integer", + "format": "int32", + "description": "Formula version number." + }, + "isActive": { + "type": "boolean", + "description": "Whether the formula is active." + }, + "audit": { + "$ref": "#/definitions/v1AuditInfo", + "description": "Audit information." + }, + "inputParams": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/v1FormulaParam" + }, + "description": "Input parameters (from junction table)." + } + }, + "description": "Formula represents a master formula entity for costing calculations." + }, + "v1FormulaParam": { + "type": "object", + "properties": { + "formulaParamId": { + "type": "string", + "description": "Unique identifier (UUID)." + }, + "paramId": { + "type": "string", + "description": "Parameter ID reference (UUID)." + }, + "paramCode": { + "type": "string", + "description": "Resolved parameter code (read-only, from join)." + }, + "paramName": { + "type": "string", + "description": "Resolved parameter name (read-only, from join)." + }, + "sortOrder": { + "type": "integer", + "format": "int32", + "description": "Display order of the parameter in the formula." + } + }, + "description": "FormulaParam represents an input parameter reference within a formula." + }, + "v1FormulaType": { + "type": "string", + "enum": [ + "FORMULA_TYPE_UNSPECIFIED", + "FORMULA_TYPE_CALCULATION", + "FORMULA_TYPE_SQL_QUERY", + "FORMULA_TYPE_CONSTANT" + ], + "default": "FORMULA_TYPE_UNSPECIFIED", + "description": "FormulaType represents the type of a formula expression.\n\n - FORMULA_TYPE_UNSPECIFIED: Default unspecified value - used as \"no filter\" in list/export requests.\n - FORMULA_TYPE_CALCULATION: Mathematical calculation using parameter codes (e.g., \"ELEC_CONSUMPTION * ELEC_RATE\").\n - FORMULA_TYPE_SQL_QUERY: SQL query to fetch data from other tables.\n - FORMULA_TYPE_CONSTANT: Constant value (no calculation)." + }, + "v1GetFormulaResponse": { + "type": "object", + "properties": { + "base": { + "$ref": "#/definitions/v1BaseResponse", + "description": "Standard response metadata." + }, + "data": { + "$ref": "#/definitions/v1Formula", + "description": "Formula data." + } + }, + "description": "GetFormulaResponse is the response for getting a formula." + }, + "v1ImportError": { + "type": "object", + "properties": { + "rowNumber": { + "type": "integer", + "format": "int32", + "description": "Row number in the Excel file." + }, + "field": { + "type": "string", + "description": "Field that caused the error." + }, + "message": { + "type": "string", + "description": "Error message." + } + }, + "description": "ImportError represents a single import error." + }, + "v1ImportFormulasRequest": { + "type": "object", + "properties": { + "fileContent": { + "type": "string", + "format": "byte", + "description": "Excel file content as bytes (.xlsx or .xls format)." + }, + "fileName": { + "type": "string", + "description": "Original filename (for format detection)." + }, + "duplicateAction": { + "type": "string", + "description": "Required. How to handle duplicates: \"skip\", \"update\", \"error\"." + } + }, + "description": "ImportFormulasRequest is the request for importing formulas from Excel." + }, + "v1ImportFormulasResponse": { + "type": "object", + "properties": { + "base": { + "$ref": "#/definitions/v1BaseResponse", + "description": "Standard response metadata." + }, + "successCount": { + "type": "integer", + "format": "int32", + "description": "Number of successfully imported records." + }, + "skippedCount": { + "type": "integer", + "format": "int32", + "description": "Number of skipped records (duplicates with skip action)." + }, + "updatedCount": { + "type": "integer", + "format": "int32", + "description": "Number of updated records (duplicates with update action)." + }, + "failedCount": { + "type": "integer", + "format": "int32", + "description": "Number of failed records." + }, + "errors": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/v1ImportError" + }, + "description": "Details of failed records." + } + }, + "description": "ImportFormulasResponse is the response for importing formulas." + }, + "v1ListFormulasResponse": { + "type": "object", + "properties": { + "base": { + "$ref": "#/definitions/v1BaseResponse", + "description": "Standard response metadata." + }, + "data": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/v1Formula" + }, + "description": "List of formulas." + }, + "pagination": { + "$ref": "#/definitions/v1PaginationResponse", + "description": "Pagination metadata." + } + }, + "description": "ListFormulasResponse is the response for listing formulas." + }, + "v1PaginationResponse": { + "type": "object", + "properties": { + "currentPage": { + "type": "integer", + "format": "int32", + "description": "Current page number." + }, + "pageSize": { + "type": "integer", + "format": "int32", + "description": "Number of items per page." + }, + "totalItems": { + "type": "string", + "format": "int64", + "description": "Total number of items across all pages." + }, + "totalPages": { + "type": "integer", + "format": "int32", + "description": "Total number of pages." + } + }, + "description": "PaginationResponse contains pagination metadata for list responses." + }, + "v1UpdateFormulaResponse": { + "type": "object", + "properties": { + "base": { + "$ref": "#/definitions/v1BaseResponse", + "description": "Standard response metadata." + }, + "data": { + "$ref": "#/definitions/v1Formula", + "description": "Updated formula data." + } + }, + "description": "UpdateFormulaResponse is the response for updating a formula." + }, + "v1ValidationError": { + "type": "object", + "properties": { + "field": { + "type": "string", + "description": "The field name that failed validation." + }, + "message": { + "type": "string", + "description": "The validation error message." + } + }, + "description": "ValidationError represents a single field validation error." + }, + "ParameterServiceUpdateParameterBody": { + "type": "object", + "properties": { + "paramName": { + "type": "string", + "description": "New display name (optional, 1-200 chars if provided)." + }, + "paramShortName": { + "type": "string", + "description": "New short name (optional, max 50 chars)." + }, + "dataType": { + "$ref": "#/definitions/v1DataType", + "description": "New data type (optional, cannot be UNSPECIFIED if provided)." + }, + "paramCategory": { + "$ref": "#/definitions/v1ParamCategory", + "description": "New category (optional, cannot be UNSPECIFIED if provided)." + }, + "uomId": { + "type": "string", + "description": "New UOM reference (optional, UUID format, empty string to clear)." + }, + "defaultValue": { + "type": "string", + "description": "New default value (optional)." + }, + "minValue": { + "type": "string", + "description": "New minimum value (optional)." + }, + "maxValue": { + "type": "string", + "description": "New maximum value (optional)." + }, + "isActive": { + "type": "boolean", + "description": "New active status (optional)." + } + }, + "description": "UpdateParameterRequest is the request for updating a parameter.\nNote: param_code is immutable and cannot be changed after creation." }, "v1CreateParameterRequest": { "type": "object", @@ -1282,25 +2301,6 @@ }, "description": "GetParameterResponse is the response for getting a parameter." }, - "v1ImportError": { - "type": "object", - "properties": { - "rowNumber": { - "type": "integer", - "format": "int32", - "description": "Row number in the Excel file." - }, - "field": { - "type": "string", - "description": "Field that caused the error." - }, - "message": { - "type": "string", - "description": "Error message." - } - }, - "description": "ImportError represents a single import error." - }, "v1ImportParametersRequest": { "type": "object", "properties": { @@ -1369,42 +2369,16 @@ "type": "array", "items": { "type": "object", - "$ref": "#/definitions/v1Parameter" - }, - "description": "List of parameters." - }, - "pagination": { - "$ref": "#/definitions/v1PaginationResponse", - "description": "Pagination metadata." - } - }, - "description": "ListParametersResponse is the response for listing parameters." - }, - "v1PaginationResponse": { - "type": "object", - "properties": { - "currentPage": { - "type": "integer", - "format": "int32", - "description": "Current page number." - }, - "pageSize": { - "type": "integer", - "format": "int32", - "description": "Number of items per page." - }, - "totalItems": { - "type": "string", - "format": "int64", - "description": "Total number of items across all pages." + "$ref": "#/definitions/v1Parameter" + }, + "description": "List of parameters." }, - "totalPages": { - "type": "integer", - "format": "int32", - "description": "Total number of pages." + "pagination": { + "$ref": "#/definitions/v1PaginationResponse", + "description": "Pagination metadata." } }, - "description": "PaginationResponse contains pagination metadata for list responses." + "description": "ListParametersResponse is the response for listing parameters." }, "v1ParamCategory": { "type": "string", @@ -1493,20 +2467,6 @@ }, "description": "UpdateParameterResponse is the response for updating a parameter." }, - "v1ValidationError": { - "type": "object", - "properties": { - "field": { - "type": "string", - "description": "The field name that failed validation." - }, - "message": { - "type": "string", - "description": "The validation error message." - } - }, - "description": "ValidationError represents a single field validation error." - }, "RMCategoryServiceUpdateRMCategoryBody": { "type": "object", "properties": { @@ -1749,10 +2709,6 @@ "type": "string", "description": "New display name (optional, 1-100 chars if provided).\nEmpty string means no change." }, - "uomCategory": { - "$ref": "#/definitions/v1UOMCategory", - "description": "New category (optional, cannot be UNSPECIFIED if provided).\nUse has_uom_category to check if this field is set." - }, "description": { "type": "string", "description": "New description (optional, max 500 chars)." @@ -1760,6 +2716,10 @@ "isActive": { "type": "boolean", "description": "New active status (optional)." + }, + "uomCategoryId": { + "type": "string", + "description": "New category ID (optional, UUID format)." } }, "description": "UpdateUOMRequest is the request for updating a UOM.\nNote: uom_code is immutable and cannot be changed after creation." @@ -1775,13 +2735,13 @@ "type": "string", "description": "Display name (1-100 chars)." }, - "uomCategory": { - "$ref": "#/definitions/v1UOMCategory", - "description": "Category (required, cannot be UNSPECIFIED)." - }, "description": { "type": "string", "description": "Optional description (max 500 chars)." + }, + "uomCategoryId": { + "type": "string", + "description": "Category ID (UUID, required - FK to mst_uom_category)." } }, "description": "CreateUOMRequest is the request for creating a new UOM." @@ -1956,10 +2916,6 @@ "type": "string", "description": "Display name (e.g., \"Kilogram\", \"Meter\", \"Pieces\")." }, - "uomCategory": { - "$ref": "#/definitions/v1UOMCategory", - "description": "Category of the UOM." - }, "description": { "type": "string", "description": "Optional description." @@ -1971,22 +2927,22 @@ "audit": { "$ref": "#/definitions/v1AuditInfo", "description": "Audit information." + }, + "uomCategoryId": { + "type": "string", + "description": "Category ID (FK to mst_uom_category)." + }, + "uomCategoryCode": { + "type": "string", + "description": "Category code (denormalized for display, e.g., \"WEIGHT\")." + }, + "uomCategoryName": { + "type": "string", + "description": "Category name (denormalized for display, e.g., \"Weight\")." } }, "description": "UOM represents a Unit of Measure entity." }, - "v1UOMCategory": { - "type": "string", - "enum": [ - "UOM_CATEGORY_UNSPECIFIED", - "UOM_CATEGORY_WEIGHT", - "UOM_CATEGORY_LENGTH", - "UOM_CATEGORY_VOLUME", - "UOM_CATEGORY_QUANTITY" - ], - "default": "UOM_CATEGORY_UNSPECIFIED", - "description": "UOMCategory represents the category of a unit of measure.\n\n - UOM_CATEGORY_UNSPECIFIED: Default unspecified value - used as \"no filter\" in list/export requests.\n - UOM_CATEGORY_WEIGHT: Weight-based units (e.g., KG, GR, TON).\n - UOM_CATEGORY_LENGTH: Length-based units (e.g., MTR, CM, YARD).\n - UOM_CATEGORY_VOLUME: Volume-based units (e.g., LTR, ML).\n - UOM_CATEGORY_QUANTITY: Quantity-based units (e.g., PCS, BOX, SET)." - }, "v1UpdateUOMResponse": { "type": "object", "properties": { @@ -2000,9 +2956,247 @@ } }, "description": "UpdateUOMResponse is the response for updating a UOM." + }, + "UOMCategoryServiceUpdateUOMCategoryBody": { + "type": "object", + "properties": { + "categoryName": { + "type": "string", + "description": "New display name (optional, 1-100 chars if provided)." + }, + "description": { + "type": "string", + "description": "New description (optional, max 500 chars)." + }, + "isActive": { + "type": "boolean", + "description": "New active status (optional)." + } + }, + "description": "UpdateUOMCategoryRequest is the request for updating a UOM category.\nNote: category_code is immutable and cannot be changed after creation." + }, + "v1CreateUOMCategoryRequest": { + "type": "object", + "properties": { + "categoryCode": { + "type": "string", + "description": "Unique code (uppercase, alphanumeric with underscore, 1-20 chars).\nMust start with an uppercase letter. Immutable after creation." + }, + "categoryName": { + "type": "string", + "description": "Display name (1-100 chars)." + }, + "description": { + "type": "string", + "description": "Optional description (max 500 chars)." + } + }, + "description": "CreateUOMCategoryRequest is the request for creating a new UOM category." + }, + "v1CreateUOMCategoryResponse": { + "type": "object", + "properties": { + "base": { + "$ref": "#/definitions/v1BaseResponse", + "description": "Standard response metadata." + }, + "data": { + "$ref": "#/definitions/v1UOMCategory", + "description": "Created UOM category data." + } + }, + "description": "CreateUOMCategoryResponse is the response for creating a UOM category." + }, + "v1DeleteUOMCategoryResponse": { + "type": "object", + "properties": { + "base": { + "$ref": "#/definitions/v1BaseResponse", + "description": "Standard response metadata." + } + }, + "description": "DeleteUOMCategoryResponse is the response for deleting a UOM category." + }, + "v1DownloadUOMCategoryTemplateResponse": { + "type": "object", + "properties": { + "base": { + "$ref": "#/definitions/v1BaseResponse", + "description": "Standard response metadata." + }, + "fileContent": { + "type": "string", + "format": "byte", + "description": "Excel template file content as bytes (.xlsx format)." + }, + "fileName": { + "type": "string", + "description": "Suggested filename." + } + }, + "description": "DownloadUOMCategoryTemplateResponse is the response containing the template file." + }, + "v1ExportUOMCategoriesResponse": { + "type": "object", + "properties": { + "base": { + "$ref": "#/definitions/v1BaseResponse", + "description": "Standard response metadata." + }, + "fileContent": { + "type": "string", + "format": "byte", + "description": "Excel file content as bytes (.xlsx format)." + }, + "fileName": { + "type": "string", + "description": "Suggested filename." + } + }, + "description": "ExportUOMCategoriesResponse is the response containing the Excel file." + }, + "v1GetUOMCategoryResponse": { + "type": "object", + "properties": { + "base": { + "$ref": "#/definitions/v1BaseResponse", + "description": "Standard response metadata." + }, + "data": { + "$ref": "#/definitions/v1UOMCategory", + "description": "UOM category data." + } + }, + "description": "GetUOMCategoryResponse is the response for getting a UOM category." + }, + "v1ImportUOMCategoriesRequest": { + "type": "object", + "properties": { + "fileContent": { + "type": "string", + "format": "byte", + "description": "Excel file content as bytes (.xlsx or .xls format)." + }, + "fileName": { + "type": "string", + "description": "Original filename (for format detection).\nMust be a simple filename without path separators." + }, + "duplicateAction": { + "type": "string", + "description": "Required. How to handle duplicates: \"skip\", \"update\", \"error\"." + } + }, + "description": "ImportUOMCategoriesRequest is the request for importing UOM categories from Excel." + }, + "v1ImportUOMCategoriesResponse": { + "type": "object", + "properties": { + "base": { + "$ref": "#/definitions/v1BaseResponse", + "description": "Standard response metadata." + }, + "successCount": { + "type": "integer", + "format": "int32", + "description": "Number of successfully imported records." + }, + "skippedCount": { + "type": "integer", + "format": "int32", + "description": "Number of skipped records (duplicates with skip action)." + }, + "updatedCount": { + "type": "integer", + "format": "int32", + "description": "Number of updated records (duplicates with update action)." + }, + "failedCount": { + "type": "integer", + "format": "int32", + "description": "Number of failed records." + }, + "errors": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/v1ImportError" + }, + "description": "Details of failed records." + } + }, + "description": "ImportUOMCategoriesResponse is the response for importing UOM categories." + }, + "v1ListUOMCategoriesResponse": { + "type": "object", + "properties": { + "base": { + "$ref": "#/definitions/v1BaseResponse", + "description": "Standard response metadata." + }, + "data": { + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/v1UOMCategory" + }, + "description": "List of UOM categories." + }, + "pagination": { + "$ref": "#/definitions/v1PaginationResponse", + "description": "Pagination metadata." + } + }, + "description": "ListUOMCategoriesResponse is the response for listing UOM categories." + }, + "v1UOMCategory": { + "type": "object", + "properties": { + "uomCategoryId": { + "type": "string", + "description": "Unique identifier (UUID)." + }, + "categoryCode": { + "type": "string", + "description": "Unique code (e.g., \"WEIGHT\", \"LENGTH\", \"VOLUME\"). Immutable after creation." + }, + "categoryName": { + "type": "string", + "description": "Display name (e.g., \"Weight\", \"Length\", \"Volume\")." + }, + "description": { + "type": "string", + "description": "Optional description." + }, + "isActive": { + "type": "boolean", + "description": "Whether the category is active." + }, + "audit": { + "$ref": "#/definitions/v1AuditInfo", + "description": "Audit information." + } + }, + "description": "UOMCategory represents a Unit of Measure Category entity.\nUsed to classify UOMs (e.g., Weight, Length, Volume, Quantity, Time, Energy)." + }, + "v1UpdateUOMCategoryResponse": { + "type": "object", + "properties": { + "base": { + "$ref": "#/definitions/v1BaseResponse", + "description": "Standard response metadata." + }, + "data": { + "$ref": "#/definitions/v1UOMCategory", + "description": "Updated UOM category data." + } + }, + "description": "UpdateUOMCategoryResponse is the response for updating a UOM category." } }, "tags": [ + { + "name": "FormulaService" + }, { "name": "ParameterService" }, @@ -2011,6 +3205,9 @@ }, { "name": "UOMService" + }, + { + "name": "UOMCategoryService" } ] } \ No newline at end of file diff --git a/services/finance/internal/domain/uom/entity.go b/services/finance/internal/domain/uom/entity.go index fbb485c..685b11b 100644 --- a/services/finance/internal/domain/uom/entity.go +++ b/services/finance/internal/domain/uom/entity.go @@ -9,22 +9,22 @@ import ( // UOM is the aggregate root for Unit of Measure domain. type UOM struct { - id uuid.UUID - code Code - name string - category Category - description string - isActive bool - createdAt time.Time - createdBy string - updatedAt *time.Time - updatedBy *string - deletedAt *time.Time - deletedBy *string + id uuid.UUID + code Code + name string + categoryInfo CategoryInfo + description string + isActive bool + createdAt time.Time + createdBy string + updatedAt *time.Time + updatedBy *string + deletedAt *time.Time + deletedBy *string } // NewUOM creates a new UOM entity with validation. -func NewUOM(code Code, name string, category Category, description string, createdBy string) (*UOM, error) { +func NewUOM(code Code, name string, categoryID uuid.UUID, description string, createdBy string) (*UOM, error) { if name == "" { return nil, ErrEmptyName } @@ -34,19 +34,19 @@ func NewUOM(code Code, name string, category Category, description string, creat if createdBy == "" { return nil, ErrEmptyCreatedBy } - if !category.IsValid() { + if categoryID == uuid.Nil { return nil, ErrInvalidCategory } return &UOM{ - id: uuid.New(), - code: code, - name: name, - category: category, - description: description, - isActive: true, - createdAt: time.Now(), - createdBy: createdBy, + id: uuid.New(), + code: code, + name: name, + categoryInfo: CategoryInfo{id: categoryID}, + description: description, + isActive: true, + createdAt: time.Now(), + createdBy: createdBy, }, nil } @@ -56,7 +56,7 @@ func ReconstructUOM( id uuid.UUID, code Code, name string, - category Category, + categoryInfo CategoryInfo, description string, isActive bool, createdAt time.Time, @@ -67,18 +67,18 @@ func ReconstructUOM( deletedBy *string, ) *UOM { return &UOM{ - id: id, - code: code, - name: name, - category: category, - description: description, - isActive: isActive, - createdAt: createdAt, - createdBy: createdBy, - updatedAt: updatedAt, - updatedBy: updatedBy, - deletedAt: deletedAt, - deletedBy: deletedBy, + id: id, + code: code, + name: name, + categoryInfo: categoryInfo, + description: description, + isActive: isActive, + createdAt: createdAt, + createdBy: createdBy, + updatedAt: updatedAt, + updatedBy: updatedBy, + deletedAt: deletedAt, + deletedBy: deletedBy, } } @@ -95,8 +95,11 @@ func (u *UOM) Code() Code { return u.code } // Name returns the display name. func (u *UOM) Name() string { return u.name } -// Category returns the category. -func (u *UOM) Category() Category { return u.category } +// CategoryInfo returns the category info. +func (u *UOM) CategoryInfo() CategoryInfo { return u.categoryInfo } + +// CategoryID returns the category UUID. +func (u *UOM) CategoryID() uuid.UUID { return u.categoryInfo.id } // Description returns the description. func (u *UOM) Description() string { return u.description } @@ -130,7 +133,7 @@ func (u *UOM) IsDeleted() bool { return u.deletedAt != nil } // ============================================================================= // Update updates the UOM with new values. -func (u *UOM) Update(name *string, category *Category, description *string, isActive *bool, updatedBy string) error { +func (u *UOM) Update(name *string, categoryID *uuid.UUID, description *string, isActive *bool, updatedBy string) error { if u.IsDeleted() { return ErrAlreadyDeleted } @@ -142,11 +145,11 @@ func (u *UOM) Update(name *string, category *Category, description *string, isAc u.name = *name } - if category != nil { - if !category.IsValid() { + if categoryID != nil { + if *categoryID == uuid.Nil { return ErrInvalidCategory } - u.category = *category + u.categoryInfo = CategoryInfo{id: *categoryID} } if description != nil { diff --git a/services/finance/internal/domain/uom/entity_test.go b/services/finance/internal/domain/uom/entity_test.go index f9e5924..379de8d 100644 --- a/services/finance/internal/domain/uom/entity_test.go +++ b/services/finance/internal/domain/uom/entity_test.go @@ -2,7 +2,6 @@ package uom_test import ( - "strings" "testing" "time" @@ -83,47 +82,37 @@ func TestNewCode(t *testing.T) { } } -func TestNewCategory(t *testing.T) { - tests := []struct { - name string - input string - wantErr bool - }{ - {name: "valid - WEIGHT", input: "WEIGHT", wantErr: false}, - {name: "valid - LENGTH", input: "LENGTH", wantErr: false}, - {name: "valid - VOLUME", input: "VOLUME", wantErr: false}, - {name: "valid - QUANTITY", input: "QUANTITY", wantErr: false}, - {name: "valid - lowercase (auto uppercase)", input: "weight", wantErr: false}, - {name: "invalid - empty", input: "", wantErr: true}, - {name: "invalid - unknown", input: "UNKNOWN", wantErr: true}, - } +func TestCategoryInfo(t *testing.T) { + t.Run("create category info", func(t *testing.T) { + id := uuid.New() + info := uom.NewCategoryInfo(id, "WEIGHT", "Weight") - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - cat, err := uom.NewCategory(tt.input) - if tt.wantErr { - assert.Error(t, err) - } else { - assert.NoError(t, err) - // All valid categories should be uppercase - assert.Equal(t, strings.ToUpper(tt.input), cat.String()) - } - }) - } + assert.Equal(t, id, info.ID()) + assert.Equal(t, "WEIGHT", info.Code()) + assert.Equal(t, "Weight", info.Name()) + }) + + t.Run("zero value category info", func(t *testing.T) { + info := uom.CategoryInfo{} + assert.Equal(t, uuid.Nil, info.ID()) + assert.Equal(t, "", info.Code()) + assert.Equal(t, "", info.Name()) + }) } func TestNewUOM(t *testing.T) { + categoryID := uuid.New() + t.Run("valid UOM creation", func(t *testing.T) { code, _ := uom.NewCode("KG") - category, _ := uom.NewCategory("WEIGHT") - entity, err := uom.NewUOM(code, "Kilogram", category, "Unit of weight", "admin") + entity, err := uom.NewUOM(code, "Kilogram", categoryID, "Unit of weight", "admin") require.NoError(t, err) assert.NotEqual(t, uuid.Nil, entity.ID()) assert.Equal(t, "KG", entity.Code().String()) assert.Equal(t, "Kilogram", entity.Name()) - assert.Equal(t, "WEIGHT", entity.Category().String()) + assert.Equal(t, categoryID, entity.CategoryID()) assert.Equal(t, "Unit of weight", entity.Description()) assert.True(t, entity.IsActive()) assert.Equal(t, "admin", entity.CreatedBy()) @@ -134,9 +123,8 @@ func TestNewUOM(t *testing.T) { t.Run("invalid - empty name", func(t *testing.T) { code, _ := uom.NewCode("KG") - category, _ := uom.NewCategory("WEIGHT") - _, err := uom.NewUOM(code, "", category, "Description", "admin") + _, err := uom.NewUOM(code, "", categoryID, "Description", "admin") assert.Error(t, err) assert.ErrorIs(t, err, uom.ErrEmptyName) @@ -144,10 +132,9 @@ func TestNewUOM(t *testing.T) { t.Run("invalid - name too long", func(t *testing.T) { code, _ := uom.NewCode("KG") - category, _ := uom.NewCategory("WEIGHT") longName := string(make([]byte, 101)) // 101 characters - _, err := uom.NewUOM(code, longName, category, "Description", "admin") + _, err := uom.NewUOM(code, longName, categoryID, "Description", "admin") assert.Error(t, err) assert.ErrorIs(t, err, uom.ErrNameTooLong) @@ -155,20 +142,28 @@ func TestNewUOM(t *testing.T) { t.Run("invalid - empty created by", func(t *testing.T) { code, _ := uom.NewCode("KG") - category, _ := uom.NewCategory("WEIGHT") - _, err := uom.NewUOM(code, "Kilogram", category, "Description", "") + _, err := uom.NewUOM(code, "Kilogram", categoryID, "Description", "") assert.Error(t, err) assert.ErrorIs(t, err, uom.ErrEmptyCreatedBy) }) + + t.Run("invalid - nil category ID", func(t *testing.T) { + code, _ := uom.NewCode("KG") + + _, err := uom.NewUOM(code, "Kilogram", uuid.Nil, "Description", "admin") + + assert.Error(t, err) + assert.ErrorIs(t, err, uom.ErrInvalidCategory) + }) } func TestUOM_Update(t *testing.T) { // Create a valid entity first code, _ := uom.NewCode("KG") - category, _ := uom.NewCategory("WEIGHT") - entity, _ := uom.NewUOM(code, "Kilogram", category, "Old description", "admin") + categoryID := uuid.New() + entity, _ := uom.NewUOM(code, "Kilogram", categoryID, "Old description", "admin") t.Run("update name", func(t *testing.T) { newName := "Kilogram Updated" @@ -182,14 +177,22 @@ func TestUOM_Update(t *testing.T) { }) t.Run("update category", func(t *testing.T) { - newCat, _ := uom.NewCategory("LENGTH") - err := entity.Update(nil, &newCat, nil, nil, "editor2") + newCatID := uuid.New() + err := entity.Update(nil, &newCatID, nil, nil, "editor2") require.NoError(t, err) - assert.Equal(t, "LENGTH", entity.Category().String()) + assert.Equal(t, newCatID, entity.CategoryID()) assert.Equal(t, "editor2", *entity.UpdatedBy()) }) + t.Run("update category with nil UUID", func(t *testing.T) { + nilID := uuid.Nil + err := entity.Update(nil, &nilID, nil, nil, "editor") + + assert.Error(t, err) + assert.ErrorIs(t, err, uom.ErrInvalidCategory) + }) + t.Run("update description", func(t *testing.T) { newDesc := "New description" err := entity.Update(nil, nil, &newDesc, nil, "editor3") @@ -210,7 +213,7 @@ func TestUOM_Update(t *testing.T) { func TestReconstructUOM(t *testing.T) { id := uuid.New() code, _ := uom.NewCode("LTR") - category, _ := uom.NewCategory("VOLUME") + categoryInfo := uom.NewCategoryInfo(uuid.New(), "VOLUME", "Volume") createdAt := time.Now().Add(-24 * time.Hour) updatedAt := time.Now() updatedBy := "updater" @@ -219,7 +222,7 @@ func TestReconstructUOM(t *testing.T) { id, code, "Liter", - category, + categoryInfo, "Volume unit", true, createdAt, @@ -233,7 +236,9 @@ func TestReconstructUOM(t *testing.T) { assert.Equal(t, id, entity.ID()) assert.Equal(t, "LTR", entity.Code().String()) assert.Equal(t, "Liter", entity.Name()) - assert.Equal(t, "VOLUME", entity.Category().String()) + assert.Equal(t, categoryInfo.ID(), entity.CategoryID()) + assert.Equal(t, "VOLUME", entity.CategoryInfo().Code()) + assert.Equal(t, "Volume", entity.CategoryInfo().Name()) assert.Equal(t, "Volume unit", entity.Description()) assert.True(t, entity.IsActive()) assert.Equal(t, createdAt, entity.CreatedAt()) @@ -243,13 +248,3 @@ func TestReconstructUOM(t *testing.T) { assert.NotNil(t, entity.UpdatedBy()) assert.Equal(t, "updater", *entity.UpdatedBy()) } - -func TestCategory_IsValid(t *testing.T) { - validCategories := []string{"WEIGHT", "LENGTH", "VOLUME", "QUANTITY"} - - for _, catStr := range validCategories { - cat, err := uom.NewCategory(catStr) - assert.NoError(t, err) - assert.True(t, cat.IsValid()) - } -} diff --git a/services/finance/internal/domain/uom/errors.go b/services/finance/internal/domain/uom/errors.go index 9f1114f..87bb8fe 100644 --- a/services/finance/internal/domain/uom/errors.go +++ b/services/finance/internal/domain/uom/errors.go @@ -23,8 +23,8 @@ var ( // ErrCodeTooLong is returned when the UOM code exceeds max length. ErrCodeTooLong = errors.New("uom code must be at most 20 characters") - // ErrInvalidCategory is returned when the UOM category is invalid. - ErrInvalidCategory = errors.New("invalid uom category: must be WEIGHT, LENGTH, VOLUME, or QUANTITY") + // ErrInvalidCategory is returned when the UOM category ID is invalid. + ErrInvalidCategory = errors.New("invalid uom category: category ID is required") // ErrEmptyName is returned when the UOM name is empty. ErrEmptyName = errors.New("uom name cannot be empty") diff --git a/services/finance/internal/domain/uom/repository.go b/services/finance/internal/domain/uom/repository.go index e7e74d2..8dc28e2 100644 --- a/services/finance/internal/domain/uom/repository.go +++ b/services/finance/internal/domain/uom/repository.go @@ -7,6 +7,13 @@ import ( "github.com/google/uuid" ) +// CategoryRepository defines read-only access to UOM Category data. +// Used by UOM handlers that need to resolve category codes to IDs. +type CategoryRepository interface { + // GetCategoryIDByCode resolves a category code to its UUID. + GetCategoryIDByCode(ctx context.Context, code string) (uuid.UUID, error) +} + // Repository defines the interface for UOM persistence. // This interface is defined in domain layer, implemented in infrastructure layer. type Repository interface { @@ -43,8 +50,8 @@ type ListFilter struct { // Search query (searches in code, name, description). Search string - // Category filter. - Category *Category + // CategoryID filter (FK to mst_uom_category). + CategoryID *uuid.UUID // IsActive filter. IsActive *bool @@ -54,14 +61,14 @@ type ListFilter struct { PageSize int // Sorting. - SortBy string // "code", "name", "created_at" + SortBy string // "code", "name", "category", "created_at" SortOrder string // "asc", "desc" } // ExportFilter contains filtering options for exporting UOMs. type ExportFilter struct { - // Category filter. - Category *Category + // CategoryID filter (FK to mst_uom_category). + CategoryID *uuid.UUID // IsActive filter. IsActive *bool diff --git a/services/finance/internal/domain/uom/uom_test.go b/services/finance/internal/domain/uom/uom_test.go index 72ede9f..62b1ad2 100644 --- a/services/finance/internal/domain/uom/uom_test.go +++ b/services/finance/internal/domain/uom/uom_test.go @@ -3,6 +3,8 @@ package uom_test import ( "testing" + "github.com/google/uuid" + "github.com/mutugading/goapps-backend/services/finance/internal/domain/uom" ) @@ -50,66 +52,15 @@ func TestNewCode_InvalidInput_ReturnsError(t *testing.T) { if err == nil { t.Fatal("expected error, got nil") } - // Error type depends on specific validation failure - }) - } -} - -func TestNewCategory_ValidInput_ReturnsCategory(t *testing.T) { - testCases := []struct { - name string - input string - expected uom.Category - }{ - {"weight", "WEIGHT", uom.CategoryWeight}, - {"length", "LENGTH", uom.CategoryLength}, - {"volume", "VOLUME", uom.CategoryVolume}, - {"quantity", "QUANTITY", uom.CategoryQuantity}, - {"lowercase", "weight", uom.CategoryWeight}, - {"mixed case", "Weight", uom.CategoryWeight}, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - cat, err := uom.NewCategory(tc.input) - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - if cat != tc.expected { - t.Errorf("expected %s, got %s", tc.expected, cat) - } - }) - } -} - -func TestNewCategory_InvalidInput_ReturnsError(t *testing.T) { - testCases := []struct { - name string - input string - }{ - {"empty", ""}, - {"invalid", "UNKNOWN"}, - {"typo", "WEIGTH"}, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - _, err := uom.NewCategory(tc.input) - if err == nil { - t.Fatal("expected error, got nil") - } - if err != uom.ErrInvalidCategory { - t.Errorf("expected ErrInvalidCategory, got %v", err) - } }) } } func TestNewUOM_ValidInput_ReturnsUOM(t *testing.T) { code, _ := uom.NewCode("KG") - category := uom.CategoryWeight + categoryID := uuid.New() - entity, err := uom.NewUOM(code, "Kilogram", category, "Weight in kg", "admin") + entity, err := uom.NewUOM(code, "Kilogram", categoryID, "Weight in kg", "admin") if err != nil { t.Fatalf("expected no error, got %v", err) @@ -120,8 +71,8 @@ func TestNewUOM_ValidInput_ReturnsUOM(t *testing.T) { if entity.Name() != "Kilogram" { t.Errorf("expected name Kilogram, got %s", entity.Name()) } - if entity.Category() != category { - t.Errorf("expected category %s, got %s", category, entity.Category()) + if entity.CategoryID() != categoryID { + t.Errorf("expected category ID %s, got %s", categoryID, entity.CategoryID()) } if !entity.IsActive() { t.Error("expected entity to be active") @@ -133,9 +84,9 @@ func TestNewUOM_ValidInput_ReturnsUOM(t *testing.T) { func TestNewUOM_EmptyName_ReturnsError(t *testing.T) { code, _ := uom.NewCode("KG") - category := uom.CategoryWeight + categoryID := uuid.New() - _, err := uom.NewUOM(code, "", category, "desc", "admin") + _, err := uom.NewUOM(code, "", categoryID, "desc", "admin") if err == nil { t.Fatal("expected error, got nil") @@ -147,9 +98,9 @@ func TestNewUOM_EmptyName_ReturnsError(t *testing.T) { func TestNewUOM_EmptyCreatedBy_ReturnsError(t *testing.T) { code, _ := uom.NewCode("KG") - category := uom.CategoryWeight + categoryID := uuid.New() - _, err := uom.NewUOM(code, "Kilogram", category, "desc", "") + _, err := uom.NewUOM(code, "Kilogram", categoryID, "desc", "") if err == nil { t.Fatal("expected error, got nil") @@ -159,16 +110,30 @@ func TestNewUOM_EmptyCreatedBy_ReturnsError(t *testing.T) { } } +func TestNewUOM_NilCategoryID_ReturnsError(t *testing.T) { + code, _ := uom.NewCode("KG") + + _, err := uom.NewUOM(code, "Kilogram", uuid.Nil, "desc", "admin") + + if err == nil { + t.Fatal("expected error, got nil") + } + if err != uom.ErrInvalidCategory { + t.Errorf("expected ErrInvalidCategory, got %v", err) + } +} + func TestUOM_Update_Success(t *testing.T) { code, _ := uom.NewCode("KG") - entity, _ := uom.NewUOM(code, "Kilogram", uom.CategoryWeight, "desc", "admin") + categoryID := uuid.New() + entity, _ := uom.NewUOM(code, "Kilogram", categoryID, "desc", "admin") newName := "Kilogram Updated" - newCategory := uom.CategoryQuantity + newCategoryID := uuid.New() newDesc := "updated desc" newActive := false - err := entity.Update(&newName, &newCategory, &newDesc, &newActive, "updater") + err := entity.Update(&newName, &newCategoryID, &newDesc, &newActive, "updater") if err != nil { t.Fatalf("expected no error, got %v", err) @@ -176,8 +141,8 @@ func TestUOM_Update_Success(t *testing.T) { if entity.Name() != newName { t.Errorf("expected name %s, got %s", newName, entity.Name()) } - if entity.Category() != newCategory { - t.Errorf("expected category %s, got %s", newCategory, entity.Category()) + if entity.CategoryID() != newCategoryID { + t.Errorf("expected category ID %s, got %s", newCategoryID, entity.CategoryID()) } if entity.Description() != newDesc { t.Errorf("expected description %s, got %s", newDesc, entity.Description()) @@ -195,7 +160,8 @@ func TestUOM_Update_Success(t *testing.T) { func TestUOM_SoftDelete_Success(t *testing.T) { code, _ := uom.NewCode("KG") - entity, _ := uom.NewUOM(code, "Kilogram", uom.CategoryWeight, "desc", "admin") + categoryID := uuid.New() + entity, _ := uom.NewUOM(code, "Kilogram", categoryID, "desc", "admin") err := entity.SoftDelete("deleter") @@ -218,7 +184,8 @@ func TestUOM_SoftDelete_Success(t *testing.T) { func TestUOM_SoftDelete_AlreadyDeleted_ReturnsError(t *testing.T) { code, _ := uom.NewCode("KG") - entity, _ := uom.NewUOM(code, "Kilogram", uom.CategoryWeight, "desc", "admin") + categoryID := uuid.New() + entity, _ := uom.NewUOM(code, "Kilogram", categoryID, "desc", "admin") _ = entity.SoftDelete("deleter") err := entity.SoftDelete("another") diff --git a/services/finance/internal/domain/uom/value_object.go b/services/finance/internal/domain/uom/value_object.go index 21d5051..3504f49 100644 --- a/services/finance/internal/domain/uom/value_object.go +++ b/services/finance/internal/domain/uom/value_object.go @@ -4,6 +4,8 @@ package uom import ( "regexp" "strings" + + "github.com/google/uuid" ) // ============================================================================= @@ -50,48 +52,26 @@ func (c Code) Equals(other Code) bool { } // ============================================================================= -// Category Value Object +// CategoryInfo holds denormalized category data from the FK relationship. // ============================================================================= -// Category represents a validated UOM category value object. -type Category string - -// Category constants. -const ( - CategoryWeight Category = "WEIGHT" - CategoryLength Category = "LENGTH" - CategoryVolume Category = "VOLUME" - CategoryQuantity Category = "QUANTITY" -) - -// validCategories is a set of valid category values. -var validCategories = map[Category]bool{ - CategoryWeight: true, - CategoryLength: true, - CategoryVolume: true, - CategoryQuantity: true, +// CategoryInfo holds the UOM category reference data. +type CategoryInfo struct { + id uuid.UUID + code string + name string } -// NewCategory creates a new validated Category value object. -func NewCategory(category string) (Category, error) { - cat := Category(strings.ToUpper(strings.TrimSpace(category))) - if !validCategories[cat] { - return "", ErrInvalidCategory - } - return cat, nil +// NewCategoryInfo creates a new CategoryInfo. +func NewCategoryInfo(id uuid.UUID, code, name string) CategoryInfo { + return CategoryInfo{id: id, code: code, name: name} } -// String returns the string representation of the category. -func (c Category) String() string { - return string(c) -} +// ID returns the category UUID. +func (c CategoryInfo) ID() uuid.UUID { return c.id } -// IsValid returns true if the category is valid. -func (c Category) IsValid() bool { - return validCategories[c] -} +// Code returns the category code. +func (c CategoryInfo) Code() string { return c.code } -// AllCategories returns a slice of all valid categories. -func AllCategories() []Category { - return []Category{CategoryWeight, CategoryLength, CategoryVolume, CategoryQuantity} -} +// Name returns the category name. +func (c CategoryInfo) Name() string { return c.name } diff --git a/services/finance/internal/domain/uomcategory/entity.go b/services/finance/internal/domain/uomcategory/entity.go new file mode 100644 index 0000000..c5f468c --- /dev/null +++ b/services/finance/internal/domain/uomcategory/entity.go @@ -0,0 +1,192 @@ +// Package uomcategory provides domain logic for UOM Category management. +package uomcategory + +import ( + "time" + + "github.com/google/uuid" +) + +// Category is the aggregate root for UOM Category domain. +type Category struct { + id uuid.UUID + code Code + name string + description string + isActive bool + createdAt time.Time + createdBy string + updatedAt *time.Time + updatedBy *string + deletedAt *time.Time + deletedBy *string +} + +// NewCategory creates a new Category entity with validation. +func NewCategory(code Code, name string, description string, createdBy string) (*Category, error) { + if name == "" { + return nil, ErrEmptyName + } + if len(name) > 100 { + return nil, ErrNameTooLong + } + if createdBy == "" { + return nil, ErrEmptyCreatedBy + } + + return &Category{ + id: uuid.New(), + code: code, + name: name, + description: description, + isActive: true, + createdAt: time.Now(), + createdBy: createdBy, + }, nil +} + +// ReconstructCategory reconstructs a Category entity from persistence data. +func ReconstructCategory( + id uuid.UUID, + code Code, + name string, + description string, + isActive bool, + createdAt time.Time, + createdBy string, + updatedAt *time.Time, + updatedBy *string, + deletedAt *time.Time, + deletedBy *string, +) *Category { + return &Category{ + id: id, + code: code, + name: name, + description: description, + isActive: isActive, + createdAt: createdAt, + createdBy: createdBy, + updatedAt: updatedAt, + updatedBy: updatedBy, + deletedAt: deletedAt, + deletedBy: deletedBy, + } +} + +// ============================================================================= +// Getters - Expose internal state read-only +// ============================================================================= + +// ID returns the unique identifier. +func (c *Category) ID() uuid.UUID { return c.id } + +// Code returns the category code. +func (c *Category) Code() Code { return c.code } + +// Name returns the display name. +func (c *Category) Name() string { return c.name } + +// Description returns the description. +func (c *Category) Description() string { return c.description } + +// IsActive returns whether the category is active. +func (c *Category) IsActive() bool { return c.isActive } + +// CreatedAt returns the creation timestamp. +func (c *Category) CreatedAt() time.Time { return c.createdAt } + +// CreatedBy returns the creator. +func (c *Category) CreatedBy() string { return c.createdBy } + +// UpdatedAt returns the last update timestamp. +func (c *Category) UpdatedAt() *time.Time { return c.updatedAt } + +// UpdatedBy returns the last updater. +func (c *Category) UpdatedBy() *string { return c.updatedBy } + +// DeletedAt returns the soft delete timestamp. +func (c *Category) DeletedAt() *time.Time { return c.deletedAt } + +// DeletedBy returns who deleted the record. +func (c *Category) DeletedBy() *string { return c.deletedBy } + +// IsDeleted returns true if the category is soft deleted. +func (c *Category) IsDeleted() bool { return c.deletedAt != nil } + +// ============================================================================= +// Domain Behavior Methods +// ============================================================================= + +// Update updates the Category with new values. +func (c *Category) Update(name *string, description *string, isActive *bool, updatedBy string) error { + if c.IsDeleted() { + return ErrAlreadyDeleted + } + + if name != nil { + if *name == "" { + return ErrEmptyName + } + if len(*name) > 100 { + return ErrNameTooLong + } + c.name = *name + } + + if description != nil { + c.description = *description + } + + if isActive != nil { + c.isActive = *isActive + } + + now := time.Now() + c.updatedAt = &now + c.updatedBy = &updatedBy + + return nil +} + +// SoftDelete marks the Category as deleted. +func (c *Category) SoftDelete(deletedBy string) error { + if c.IsDeleted() { + return ErrAlreadyDeleted + } + + now := time.Now() + c.deletedAt = &now + c.deletedBy = &deletedBy + c.isActive = false + + return nil +} + +// Activate sets the Category as active. +func (c *Category) Activate(updatedBy string) error { + if c.IsDeleted() { + return ErrAlreadyDeleted + } + + c.isActive = true + now := time.Now() + c.updatedAt = &now + c.updatedBy = &updatedBy + + return nil +} + +// Deactivate sets the Category as inactive. +func (c *Category) Deactivate(updatedBy string) error { + if c.IsDeleted() { + return ErrAlreadyDeleted + } + + c.isActive = false + now := time.Now() + c.updatedAt = &now + c.updatedBy = &updatedBy + + return nil +} diff --git a/services/finance/internal/domain/uomcategory/entity_test.go b/services/finance/internal/domain/uomcategory/entity_test.go new file mode 100644 index 0000000..cd1a317 --- /dev/null +++ b/services/finance/internal/domain/uomcategory/entity_test.go @@ -0,0 +1,201 @@ +// Package uomcategory provides domain layer tests for UOM Category entity. +package uomcategory_test + +import ( + "testing" + "time" + + "github.com/google/uuid" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/mutugading/goapps-backend/services/finance/internal/domain/uomcategory" +) + +func TestNewCode(t *testing.T) { + tests := []struct { + name string + input string + want string + wantErr error + }{ + {name: "valid - simple", input: "WEIGHT", want: "WEIGHT"}, + {name: "valid - with underscore", input: "RAW_MAT", want: "RAW_MAT"}, + {name: "valid - with numbers", input: "TYPE1", want: "TYPE1"}, + {name: "valid - auto uppercase", input: "weight", want: "WEIGHT"}, + {name: "valid - trimmed", input: " WEIGHT ", want: "WEIGHT"}, + {name: "invalid - empty", input: "", wantErr: uomcategory.ErrEmptyCode}, + {name: "invalid - too long", input: "ABCDEFGHIJKLMNOPQRSTU", wantErr: uomcategory.ErrCodeTooLong}, + {name: "invalid - starts with number", input: "1TYPE", wantErr: uomcategory.ErrInvalidCodeFormat}, + {name: "invalid - special chars", input: "TYPE@#", wantErr: uomcategory.ErrInvalidCodeFormat}, + {name: "invalid - spaces", input: "MY TYPE", wantErr: uomcategory.ErrInvalidCodeFormat}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + code, err := uomcategory.NewCode(tt.input) + if tt.wantErr != nil { + assert.ErrorIs(t, err, tt.wantErr) + } else { + require.NoError(t, err) + assert.Equal(t, tt.want, code.String()) + assert.False(t, code.IsEmpty()) + } + }) + } +} + +func TestCode_Equal(t *testing.T) { + code1, _ := uomcategory.NewCode("WEIGHT") + code2, _ := uomcategory.NewCode("WEIGHT") + code3, _ := uomcategory.NewCode("LENGTH") + + assert.True(t, code1.Equal(code2)) + assert.False(t, code1.Equal(code3)) +} + +func TestNewCategory(t *testing.T) { + code, _ := uomcategory.NewCode("WEIGHT") + + t.Run("valid creation", func(t *testing.T) { + entity, err := uomcategory.NewCategory(code, "Weight", "Unit of weight", "admin") + + require.NoError(t, err) + assert.NotEqual(t, uuid.Nil, entity.ID()) + assert.Equal(t, "WEIGHT", entity.Code().String()) + assert.Equal(t, "Weight", entity.Name()) + assert.Equal(t, "Unit of weight", entity.Description()) + assert.True(t, entity.IsActive()) + assert.Equal(t, "admin", entity.CreatedBy()) + assert.False(t, entity.CreatedAt().IsZero()) + assert.Nil(t, entity.UpdatedAt()) + assert.Nil(t, entity.UpdatedBy()) + assert.False(t, entity.IsDeleted()) + }) + + t.Run("invalid - empty name", func(t *testing.T) { + _, err := uomcategory.NewCategory(code, "", "desc", "admin") + assert.ErrorIs(t, err, uomcategory.ErrEmptyName) + }) + + t.Run("invalid - name too long", func(t *testing.T) { + longName := string(make([]byte, 101)) + _, err := uomcategory.NewCategory(code, longName, "desc", "admin") + assert.ErrorIs(t, err, uomcategory.ErrNameTooLong) + }) + + t.Run("invalid - empty created by", func(t *testing.T) { + _, err := uomcategory.NewCategory(code, "Weight", "desc", "") + assert.ErrorIs(t, err, uomcategory.ErrEmptyCreatedBy) + }) +} + +func TestCategory_Update(t *testing.T) { + code, _ := uomcategory.NewCode("WEIGHT") + entity, _ := uomcategory.NewCategory(code, "Weight", "Old desc", "admin") + + t.Run("update name", func(t *testing.T) { + newName := "Weight Updated" + err := entity.Update(&newName, nil, nil, "editor") + + require.NoError(t, err) + assert.Equal(t, "Weight Updated", entity.Name()) + assert.NotNil(t, entity.UpdatedAt()) + assert.Equal(t, "editor", *entity.UpdatedBy()) + }) + + t.Run("update description", func(t *testing.T) { + newDesc := "New description" + err := entity.Update(nil, &newDesc, nil, "editor2") + + require.NoError(t, err) + assert.Equal(t, "New description", entity.Description()) + }) + + t.Run("update is_active", func(t *testing.T) { + inactive := false + err := entity.Update(nil, nil, &inactive, "editor3") + + require.NoError(t, err) + assert.False(t, entity.IsActive()) + }) + + t.Run("update - empty name error", func(t *testing.T) { + empty := "" + err := entity.Update(&empty, nil, nil, "editor") + assert.ErrorIs(t, err, uomcategory.ErrEmptyName) + }) + + t.Run("update - name too long error", func(t *testing.T) { + longName := string(make([]byte, 101)) + err := entity.Update(&longName, nil, nil, "editor") + assert.ErrorIs(t, err, uomcategory.ErrNameTooLong) + }) +} + +func TestCategory_SoftDelete(t *testing.T) { + code, _ := uomcategory.NewCode("WEIGHT") + entity, _ := uomcategory.NewCategory(code, "Weight", "", "admin") + + t.Run("soft delete success", func(t *testing.T) { + err := entity.SoftDelete("deleter") + + require.NoError(t, err) + assert.True(t, entity.IsDeleted()) + assert.False(t, entity.IsActive()) + assert.NotNil(t, entity.DeletedAt()) + assert.Equal(t, "deleter", *entity.DeletedBy()) + }) + + t.Run("already deleted error", func(t *testing.T) { + err := entity.SoftDelete("another") + assert.ErrorIs(t, err, uomcategory.ErrAlreadyDeleted) + }) + + t.Run("update deleted entity error", func(t *testing.T) { + name := "Should Fail" + err := entity.Update(&name, nil, nil, "editor") + assert.ErrorIs(t, err, uomcategory.ErrAlreadyDeleted) + }) +} + +func TestCategory_ActivateDeactivate(t *testing.T) { + code, _ := uomcategory.NewCode("LENGTH") + entity, _ := uomcategory.NewCategory(code, "Length", "", "admin") + + t.Run("deactivate", func(t *testing.T) { + err := entity.Deactivate("editor") + require.NoError(t, err) + assert.False(t, entity.IsActive()) + }) + + t.Run("activate", func(t *testing.T) { + err := entity.Activate("editor") + require.NoError(t, err) + assert.True(t, entity.IsActive()) + }) +} + +func TestReconstructCategory(t *testing.T) { + id := uuid.New() + code, _ := uomcategory.NewCode("VOLUME") + createdAt := time.Now().Add(-24 * time.Hour) + updatedAt := time.Now() + updatedBy := "updater" + + entity := uomcategory.ReconstructCategory( + id, code, "Volume", "Volume units", true, + createdAt, "creator", &updatedAt, &updatedBy, nil, nil, + ) + + assert.Equal(t, id, entity.ID()) + assert.Equal(t, "VOLUME", entity.Code().String()) + assert.Equal(t, "Volume", entity.Name()) + assert.Equal(t, "Volume units", entity.Description()) + assert.True(t, entity.IsActive()) + assert.Equal(t, createdAt, entity.CreatedAt()) + assert.Equal(t, "creator", entity.CreatedBy()) + assert.Equal(t, updatedAt, *entity.UpdatedAt()) + assert.Equal(t, "updater", *entity.UpdatedBy()) + assert.False(t, entity.IsDeleted()) +} diff --git a/services/finance/internal/domain/uomcategory/errors.go b/services/finance/internal/domain/uomcategory/errors.go new file mode 100644 index 0000000..727e247 --- /dev/null +++ b/services/finance/internal/domain/uomcategory/errors.go @@ -0,0 +1,37 @@ +// Package uomcategory provides domain logic for UOM Category management. +package uomcategory + +import "errors" + +// Domain errors for UOM Category operations. +var ( + // ErrNotFound is returned when a UOM category is not found. + ErrNotFound = errors.New("uom category not found") + + // ErrAlreadyExists is returned when attempting to create a category with an existing code. + ErrAlreadyExists = errors.New("uom category already exists") + + // ErrEmptyCode is returned when the category code is empty. + ErrEmptyCode = errors.New("category code cannot be empty") + + // ErrInvalidCodeFormat is returned when the category code format is invalid. + ErrInvalidCodeFormat = errors.New("category code must start with uppercase letter and contain only uppercase letters, numbers, and underscores") + + // ErrCodeTooLong is returned when the category code exceeds max length. + ErrCodeTooLong = errors.New("category code must be at most 20 characters") + + // ErrEmptyName is returned when the category name is empty. + ErrEmptyName = errors.New("category name cannot be empty") + + // ErrNameTooLong is returned when the category name exceeds max length. + ErrNameTooLong = errors.New("category name must be at most 100 characters") + + // ErrEmptyCreatedBy is returned when created_by is empty. + ErrEmptyCreatedBy = errors.New("created_by cannot be empty") + + // ErrAlreadyDeleted is returned when attempting to modify an already deleted category. + ErrAlreadyDeleted = errors.New("uom category is already deleted") + + // ErrInUse is returned when attempting to delete a category that is still referenced by UOMs. + ErrInUse = errors.New("uom category is still in use by one or more UOMs") +) diff --git a/services/finance/internal/domain/uomcategory/repository.go b/services/finance/internal/domain/uomcategory/repository.go new file mode 100644 index 0000000..4fb7dad --- /dev/null +++ b/services/finance/internal/domain/uomcategory/repository.go @@ -0,0 +1,99 @@ +// Package uomcategory provides domain logic for UOM Category management. +package uomcategory + +import ( + "context" + + "github.com/google/uuid" +) + +// Repository defines the interface for UOM Category persistence. +// This interface is defined in domain layer, implemented in infrastructure layer. +type Repository interface { + // Create persists a new UOM Category. + Create(ctx context.Context, entity *Category) error + + // GetByID retrieves a UOM Category by its ID. + GetByID(ctx context.Context, id uuid.UUID) (*Category, error) + + // GetByCode retrieves a UOM Category by its code. + GetByCode(ctx context.Context, code Code) (*Category, error) + + // List retrieves UOM Categories with filtering, searching, and pagination. + List(ctx context.Context, filter ListFilter) ([]*Category, int64, error) + + // Update persists changes to an existing UOM Category. + Update(ctx context.Context, entity *Category) error + + // SoftDelete marks a UOM Category as deleted. + SoftDelete(ctx context.Context, id uuid.UUID, deletedBy string) error + + // ExistsByCode checks if a UOM Category with the given code exists. + ExistsByCode(ctx context.Context, code Code) (bool, error) + + // ExistsByID checks if a UOM Category with the given ID exists. + ExistsByID(ctx context.Context, id uuid.UUID) (bool, error) + + // ListAll retrieves all non-deleted UOM Categories (for export). + ListAll(ctx context.Context, filter ExportFilter) ([]*Category, error) + + // IsInUse checks if a UOM Category is referenced by any UOM. + IsInUse(ctx context.Context, id uuid.UUID) (bool, error) +} + +// ListFilter contains filtering options for listing UOM Categories. +type ListFilter struct { + // Search query (searches in code, name, description). + Search string + + // IsActive filter. + IsActive *bool + + // Pagination. + Page int + PageSize int + + // Sorting. + SortBy string // "code", "name", "created_at" + SortOrder string // "asc", "desc" +} + +// ExportFilter contains filtering options for exporting UOM Categories. +type ExportFilter struct { + // IsActive filter. + IsActive *bool +} + +// NewListFilter creates a ListFilter with default values. +func NewListFilter() ListFilter { + return ListFilter{ + Page: 1, + PageSize: 10, + SortBy: "code", + SortOrder: "asc", + } +} + +// Validate validates and normalizes the filter values. +func (f *ListFilter) Validate() { + if f.Page < 1 { + f.Page = 1 + } + if f.PageSize < 1 { + f.PageSize = 10 + } + if f.PageSize > 100 { + f.PageSize = 100 + } + if f.SortBy == "" { + f.SortBy = "code" + } + if f.SortOrder == "" { + f.SortOrder = "asc" + } +} + +// Offset returns the offset for pagination. +func (f *ListFilter) Offset() int { + return (f.Page - 1) * f.PageSize +} diff --git a/services/finance/internal/domain/uomcategory/value_objects.go b/services/finance/internal/domain/uomcategory/value_objects.go new file mode 100644 index 0000000..bda2a45 --- /dev/null +++ b/services/finance/internal/domain/uomcategory/value_objects.go @@ -0,0 +1,47 @@ +// Package uomcategory provides domain logic for UOM Category management. +package uomcategory + +import ( + "regexp" + "strings" +) + +// codePattern validates the code format: starts with uppercase letter, +// followed by uppercase letters, digits, or underscores. +var codePattern = regexp.MustCompile(`^[A-Z][A-Z0-9_]*$`) + +// Code represents a validated UOM category code. +type Code struct { + value string +} + +// NewCode creates a new Code value object with validation. +func NewCode(code string) (Code, error) { + code = strings.TrimSpace(code) + + if code == "" { + return Code{}, ErrEmptyCode + } + + if len(code) > 20 { + return Code{}, ErrCodeTooLong + } + + // Normalize to uppercase. + code = strings.ToUpper(code) + + if !codePattern.MatchString(code) { + return Code{}, ErrInvalidCodeFormat + } + + return Code{value: code}, nil +} + +// String returns the string representation of the code. +func (c Code) String() string { return c.value } + +// IsEmpty returns true if the code is empty. +func (c Code) IsEmpty() bool { return c.value == "" } + +// Equal returns true if the two codes are equal. +func (c Code) Equal(other Code) bool { return c.value == other.value } diff --git a/services/finance/internal/infrastructure/postgres/rm_category_repository.go b/services/finance/internal/infrastructure/postgres/rm_category_repository.go index 4ceb392..b864942 100644 --- a/services/finance/internal/infrastructure/postgres/rm_category_repository.go +++ b/services/finance/internal/infrastructure/postgres/rm_category_repository.go @@ -84,6 +84,8 @@ func (r *RMCategoryRepository) GetByCode(ctx context.Context, code rmcategory.Co } // List retrieves RMCategories with filtering, searching, and pagination. +// +//nolint:dupl // Mirrors UOMCategoryRepository.List — different table/types prevent shared code. func (r *RMCategoryRepository) List(ctx context.Context, filter rmcategory.ListFilter) ([]*rmcategory.RMCategory, int64, error) { filter.Validate() diff --git a/services/finance/internal/infrastructure/postgres/uom_category_repository.go b/services/finance/internal/infrastructure/postgres/uom_category_repository.go new file mode 100644 index 0000000..8bd0615 --- /dev/null +++ b/services/finance/internal/infrastructure/postgres/uom_category_repository.go @@ -0,0 +1,452 @@ +// Package postgres provides PostgreSQL implementations for domain repositories. +package postgres + +import ( + "context" + "database/sql" + "errors" + "fmt" + "strings" + "time" + + "github.com/google/uuid" + "github.com/lib/pq" + + "github.com/mutugading/goapps-backend/services/finance/internal/domain/uomcategory" +) + +// UOMCategoryRepository implements uomcategory.Repository interface using PostgreSQL. +type UOMCategoryRepository struct { + db *DB +} + +// NewUOMCategoryRepository creates a new UOMCategoryRepository instance. +func NewUOMCategoryRepository(db *DB) *UOMCategoryRepository { + return &UOMCategoryRepository{db: db} +} + +// Verify interface implementation at compile time. +var _ uomcategory.Repository = (*UOMCategoryRepository)(nil) + +// GetCategoryIDByCode resolves a category code to its UUID. +// Implements uom.CategoryRepository interface. +func (r *UOMCategoryRepository) GetCategoryIDByCode(ctx context.Context, code string) (uuid.UUID, error) { + query := `SELECT uom_category_id FROM mst_uom_category WHERE category_code = $1 AND deleted_at IS NULL` + + var id uuid.UUID + if err := r.db.QueryRowContext(ctx, query, code).Scan(&id); err != nil { + if errors.Is(err, sql.ErrNoRows) { + return uuid.Nil, uomcategory.ErrNotFound + } + return uuid.Nil, fmt.Errorf("failed to get category id by code: %w", err) + } + + return id, nil +} + +// Create persists a new UOM Category to the database. +func (r *UOMCategoryRepository) Create(ctx context.Context, entity *uomcategory.Category) error { + query := ` + INSERT INTO mst_uom_category ( + uom_category_id, category_code, category_name, description, + is_active, created_at, created_by + ) VALUES ($1, $2, $3, $4, $5, $6, $7) + ` + + _, err := r.db.ExecContext(ctx, query, + entity.ID(), + entity.Code().String(), + entity.Name(), + entity.Description(), + entity.IsActive(), + entity.CreatedAt(), + entity.CreatedBy(), + ) + + if err != nil { + if isUOMCategoryUniqueViolation(err) { + return uomcategory.ErrAlreadyExists + } + return fmt.Errorf("failed to create uom category: %w", err) + } + + return nil +} + +// GetByID retrieves a UOM Category by its ID. +func (r *UOMCategoryRepository) GetByID(ctx context.Context, id uuid.UUID) (*uomcategory.Category, error) { + query := ` + SELECT uom_category_id, category_code, category_name, description, + is_active, created_at, created_by, updated_at, updated_by, + deleted_at, deleted_by + FROM mst_uom_category + WHERE uom_category_id = $1 AND deleted_at IS NULL + ` + + return r.scanCategory(r.db.QueryRowContext(ctx, query, id)) +} + +// GetByCode retrieves a UOM Category by its code. +func (r *UOMCategoryRepository) GetByCode(ctx context.Context, code uomcategory.Code) (*uomcategory.Category, error) { + query := ` + SELECT uom_category_id, category_code, category_name, description, + is_active, created_at, created_by, updated_at, updated_by, + deleted_at, deleted_by + FROM mst_uom_category + WHERE category_code = $1 AND deleted_at IS NULL + ` + + return r.scanCategory(r.db.QueryRowContext(ctx, query, code.String())) +} + +// List retrieves UOM Categories with filtering, searching, and pagination. +// +//nolint:dupl // Mirrors RMCategoryRepository.List — different table/types prevent shared code. +func (r *UOMCategoryRepository) List(ctx context.Context, filter uomcategory.ListFilter) ([]*uomcategory.Category, int64, error) { + filter.Validate() + + baseQuery := `FROM mst_uom_category WHERE deleted_at IS NULL` + args := []interface{}{} + argIndex := 1 + + // Search filter + if filter.Search != "" { + baseQuery += fmt.Sprintf(` AND ( + category_code ILIKE $%d OR + category_name ILIKE $%d OR + description ILIKE $%d + )`, argIndex, argIndex, argIndex) + args = append(args, "%"+filter.Search+"%") + argIndex++ + } + + // IsActive filter + if filter.IsActive != nil { + baseQuery += fmt.Sprintf(` AND is_active = $%d`, argIndex) + args = append(args, *filter.IsActive) + argIndex++ + } + + // Count total + var total int64 + countQuery := `SELECT COUNT(*) ` + baseQuery + if err := r.db.QueryRowContext(ctx, countQuery, args...).Scan(&total); err != nil { + return nil, 0, fmt.Errorf("failed to count uom categories: %w", err) + } + + // Build order clause with sort column mapping + sortColumnMap := map[string]string{ + "code": "category_code", + "name": "category_name", + "created_at": "created_at", + } + orderColumn := "category_code" + if mapped, ok := sortColumnMap[filter.SortBy]; ok { + orderColumn = mapped + } + orderDir := sortASC + if strings.ToUpper(filter.SortOrder) == sortDESC { + orderDir = sortDESC + } + + selectQuery := ` + SELECT uom_category_id, category_code, category_name, description, + is_active, created_at, created_by, updated_at, updated_by, + deleted_at, deleted_by + ` + baseQuery + fmt.Sprintf( + ` ORDER BY %s %s LIMIT $%d OFFSET $%d`, + orderColumn, orderDir, argIndex, argIndex+1, + ) + + args = append(args, filter.PageSize, filter.Offset()) + + rows, err := r.db.QueryContext(ctx, selectQuery, args...) + if err != nil { + return nil, 0, fmt.Errorf("failed to list uom categories: %w", err) + } + defer func() { + if closeErr := rows.Close(); closeErr != nil { + _ = closeErr + } + }() + + var categories []*uomcategory.Category + for rows.Next() { + entity, err := r.scanCategoryFromRows(rows) + if err != nil { + return nil, 0, err + } + categories = append(categories, entity) + } + + if err := rows.Err(); err != nil { + return nil, 0, fmt.Errorf("error iterating uom category rows: %w", err) + } + + return categories, total, nil +} + +// Update persists changes to an existing UOM Category. +func (r *UOMCategoryRepository) Update(ctx context.Context, entity *uomcategory.Category) error { + query := ` + UPDATE mst_uom_category SET + category_name = $2, + description = $3, + is_active = $4, + updated_at = $5, + updated_by = $6 + WHERE uom_category_id = $1 AND deleted_at IS NULL + ` + + result, err := r.db.ExecContext(ctx, query, + entity.ID(), + entity.Name(), + entity.Description(), + entity.IsActive(), + entity.UpdatedAt(), + entity.UpdatedBy(), + ) + if err != nil { + return fmt.Errorf("failed to update uom category: %w", err) + } + + rowsAffected, err := result.RowsAffected() + if err != nil { + return fmt.Errorf("failed to get rows affected: %w", err) + } + if rowsAffected == 0 { + return uomcategory.ErrNotFound + } + + return nil +} + +// SoftDelete marks a UOM Category as deleted. +func (r *UOMCategoryRepository) SoftDelete(ctx context.Context, id uuid.UUID, deletedBy string) error { + query := ` + UPDATE mst_uom_category SET + deleted_at = $2, + deleted_by = $3, + is_active = false + WHERE uom_category_id = $1 AND deleted_at IS NULL + ` + + result, err := r.db.ExecContext(ctx, query, id, time.Now(), deletedBy) + if err != nil { + return fmt.Errorf("failed to soft delete uom category: %w", err) + } + + rowsAffected, err := result.RowsAffected() + if err != nil { + return fmt.Errorf("failed to get rows affected: %w", err) + } + if rowsAffected == 0 { + return uomcategory.ErrNotFound + } + + return nil +} + +// ExistsByCode checks if a UOM Category with the given code exists. +func (r *UOMCategoryRepository) ExistsByCode(ctx context.Context, code uomcategory.Code) (bool, error) { + query := `SELECT EXISTS(SELECT 1 FROM mst_uom_category WHERE category_code = $1 AND deleted_at IS NULL)` + + var exists bool + if err := r.db.QueryRowContext(ctx, query, code.String()).Scan(&exists); err != nil { + return false, fmt.Errorf("failed to check uom category existence: %w", err) + } + + return exists, nil +} + +// ExistsByID checks if a UOM Category with the given ID exists. +func (r *UOMCategoryRepository) ExistsByID(ctx context.Context, id uuid.UUID) (bool, error) { + query := `SELECT EXISTS(SELECT 1 FROM mst_uom_category WHERE uom_category_id = $1 AND deleted_at IS NULL)` + + var exists bool + if err := r.db.QueryRowContext(ctx, query, id).Scan(&exists); err != nil { + return false, fmt.Errorf("failed to check uom category existence: %w", err) + } + + return exists, nil +} + +// ListAll retrieves all non-deleted UOM Categories (for export). +func (r *UOMCategoryRepository) ListAll(ctx context.Context, filter uomcategory.ExportFilter) ([]*uomcategory.Category, error) { + query := ` + SELECT uom_category_id, category_code, category_name, description, + is_active, created_at, created_by, updated_at, updated_by, + deleted_at, deleted_by + FROM mst_uom_category + WHERE deleted_at IS NULL + ` + args := []interface{}{} + argIndex := 1 + + if filter.IsActive != nil { + query += fmt.Sprintf(` AND is_active = $%d`, argIndex) + args = append(args, *filter.IsActive) + } + + query += ` ORDER BY category_code ASC` + + rows, err := r.db.QueryContext(ctx, query, args...) + if err != nil { + return nil, fmt.Errorf("failed to list all uom categories: %w", err) + } + defer func() { + if closeErr := rows.Close(); closeErr != nil { + _ = closeErr + } + }() + + var categories []*uomcategory.Category + for rows.Next() { + entity, err := r.scanCategoryFromRows(rows) + if err != nil { + return nil, err + } + categories = append(categories, entity) + } + + if err := rows.Err(); err != nil { + return nil, fmt.Errorf("error iterating uom category rows: %w", err) + } + + return categories, nil +} + +// IsInUse checks if a UOM Category is referenced by any UOM. +func (r *UOMCategoryRepository) IsInUse(ctx context.Context, id uuid.UUID) (bool, error) { + query := `SELECT EXISTS(SELECT 1 FROM mst_uom WHERE uom_category_id = $1 AND deleted_at IS NULL)` + + var inUse bool + if err := r.db.QueryRowContext(ctx, query, id).Scan(&inUse); err != nil { + return false, fmt.Errorf("failed to check uom category usage: %w", err) + } + + return inUse, nil +} + +// ============================================================================= +// Helper functions +// ============================================================================= + +func (r *UOMCategoryRepository) scanCategory(row *sql.Row) (*uomcategory.Category, error) { + var dto uomCategoryDTO + err := row.Scan( + &dto.ID, + &dto.Code, + &dto.Name, + &dto.Description, + &dto.IsActive, + &dto.CreatedAt, + &dto.CreatedBy, + &dto.UpdatedAt, + &dto.UpdatedBy, + &dto.DeletedAt, + &dto.DeletedBy, + ) + + if errors.Is(err, sql.ErrNoRows) { + return nil, uomcategory.ErrNotFound + } + if err != nil { + return nil, fmt.Errorf("failed to scan uom category: %w", err) + } + + return dto.ToEntity() +} + +func (r *UOMCategoryRepository) scanCategoryFromRows(rows *sql.Rows) (*uomcategory.Category, error) { + var dto uomCategoryDTO + err := rows.Scan( + &dto.ID, + &dto.Code, + &dto.Name, + &dto.Description, + &dto.IsActive, + &dto.CreatedAt, + &dto.CreatedBy, + &dto.UpdatedAt, + &dto.UpdatedBy, + &dto.DeletedAt, + &dto.DeletedBy, + ) + if err != nil { + return nil, fmt.Errorf("failed to scan uom category row: %w", err) + } + + return dto.ToEntity() +} + +// uomCategoryDTO is a data transfer object for database operations. +type uomCategoryDTO struct { + ID uuid.UUID + Code string + Name string + Description sql.NullString + IsActive bool + CreatedAt time.Time + CreatedBy string + UpdatedAt sql.NullTime + UpdatedBy sql.NullString + DeletedAt sql.NullTime + DeletedBy sql.NullString +} + +// ToEntity converts DTO to domain entity. +func (d *uomCategoryDTO) ToEntity() (*uomcategory.Category, error) { + code, err := uomcategory.NewCode(d.Code) + if err != nil { + return nil, fmt.Errorf("invalid code from db: %w", err) + } + + var description string + if d.Description.Valid { + description = d.Description.String + } + + var updatedAt *time.Time + if d.UpdatedAt.Valid { + updatedAt = &d.UpdatedAt.Time + } + + var updatedBy *string + if d.UpdatedBy.Valid { + updatedBy = &d.UpdatedBy.String + } + + var deletedAt *time.Time + if d.DeletedAt.Valid { + deletedAt = &d.DeletedAt.Time + } + + var deletedBy *string + if d.DeletedBy.Valid { + deletedBy = &d.DeletedBy.String + } + + return uomcategory.ReconstructCategory( + d.ID, + code, + d.Name, + description, + d.IsActive, + d.CreatedAt, + d.CreatedBy, + updatedAt, + updatedBy, + deletedAt, + deletedBy, + ), nil +} + +// isUOMCategoryUniqueViolation checks if the error is a PostgreSQL unique violation. +func isUOMCategoryUniqueViolation(err error) bool { + var pqErr *pq.Error + if errors.As(err, &pqErr) { + return pqErr.Code == "23505" // unique_violation + } + return false +} diff --git a/services/finance/internal/infrastructure/postgres/uom_repository.go b/services/finance/internal/infrastructure/postgres/uom_repository.go index 56dd0e0..f37b77a 100644 --- a/services/finance/internal/infrastructure/postgres/uom_repository.go +++ b/services/finance/internal/infrastructure/postgres/uom_repository.go @@ -32,7 +32,7 @@ var _ uom.Repository = (*UOMRepository)(nil) func (r *UOMRepository) Create(ctx context.Context, entity *uom.UOM) error { query := ` INSERT INTO mst_uom ( - uom_id, uom_code, uom_name, uom_category, description, + uom_id, uom_code, uom_name, uom_category_id, description, is_active, created_at, created_by ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) ` @@ -41,7 +41,7 @@ func (r *UOMRepository) Create(ctx context.Context, entity *uom.UOM) error { entity.ID(), entity.Code().String(), entity.Name(), - entity.Category().String(), + entity.CategoryID(), entity.Description(), entity.IsActive(), entity.CreatedAt(), @@ -61,11 +61,13 @@ func (r *UOMRepository) Create(ctx context.Context, entity *uom.UOM) error { // GetByID retrieves a UOM by its ID. func (r *UOMRepository) GetByID(ctx context.Context, id uuid.UUID) (*uom.UOM, error) { query := ` - SELECT uom_id, uom_code, uom_name, uom_category, description, - is_active, created_at, created_by, updated_at, updated_by, - deleted_at, deleted_by - FROM mst_uom - WHERE uom_id = $1 AND deleted_at IS NULL + SELECT u.uom_id, u.uom_code, u.uom_name, u.uom_category_id, + c.category_code, c.category_name, + u.description, u.is_active, u.created_at, u.created_by, + u.updated_at, u.updated_by, u.deleted_at, u.deleted_by + FROM mst_uom u + JOIN mst_uom_category c ON u.uom_category_id = c.uom_category_id + WHERE u.uom_id = $1 AND u.deleted_at IS NULL ` return r.scanUOM(r.db.QueryRowContext(ctx, query, id)) @@ -74,11 +76,13 @@ func (r *UOMRepository) GetByID(ctx context.Context, id uuid.UUID) (*uom.UOM, er // GetByCode retrieves a UOM by its code. func (r *UOMRepository) GetByCode(ctx context.Context, code uom.Code) (*uom.UOM, error) { query := ` - SELECT uom_id, uom_code, uom_name, uom_category, description, - is_active, created_at, created_by, updated_at, updated_by, - deleted_at, deleted_by - FROM mst_uom - WHERE uom_code = $1 AND deleted_at IS NULL + SELECT u.uom_id, u.uom_code, u.uom_name, u.uom_category_id, + c.category_code, c.category_name, + u.description, u.is_active, u.created_at, u.created_by, + u.updated_at, u.updated_by, u.deleted_at, u.deleted_by + FROM mst_uom u + JOIN mst_uom_category c ON u.uom_category_id = c.uom_category_id + WHERE u.uom_code = $1 AND u.deleted_at IS NULL ` return r.scanUOM(r.db.QueryRowContext(ctx, query, code.String())) @@ -88,32 +92,32 @@ func (r *UOMRepository) GetByCode(ctx context.Context, code uom.Code) (*uom.UOM, func (r *UOMRepository) List(ctx context.Context, filter uom.ListFilter) ([]*uom.UOM, int64, error) { filter.Validate() - // Build dynamic query - baseQuery := `FROM mst_uom WHERE deleted_at IS NULL` + // Build dynamic query with JOIN + baseQuery := `FROM mst_uom u JOIN mst_uom_category c ON u.uom_category_id = c.uom_category_id WHERE u.deleted_at IS NULL` args := []interface{}{} argIndex := 1 // Search filter if filter.Search != "" { baseQuery += fmt.Sprintf(` AND ( - uom_code ILIKE $%d OR - uom_name ILIKE $%d OR - description ILIKE $%d + u.uom_code ILIKE $%d OR + u.uom_name ILIKE $%d OR + u.description ILIKE $%d )`, argIndex, argIndex, argIndex) args = append(args, "%"+filter.Search+"%") argIndex++ } // Category filter - if filter.Category != nil { - baseQuery += fmt.Sprintf(` AND uom_category = $%d`, argIndex) - args = append(args, filter.Category.String()) + if filter.CategoryID != nil { + baseQuery += fmt.Sprintf(` AND u.uom_category_id = $%d`, argIndex) + args = append(args, *filter.CategoryID) argIndex++ } // IsActive filter if filter.IsActive != nil { - baseQuery += fmt.Sprintf(` AND is_active = $%d`, argIndex) + baseQuery += fmt.Sprintf(` AND u.is_active = $%d`, argIndex) args = append(args, *filter.IsActive) argIndex++ } @@ -125,13 +129,16 @@ func (r *UOMRepository) List(ctx context.Context, filter uom.ListFilter) ([]*uom return nil, 0, fmt.Errorf("failed to count uoms: %w", err) } - // Build order clause - orderColumn := "uom_code" - switch filter.SortBy { - case "name": - orderColumn = "uom_name" - case "created_at": - orderColumn = "created_at" + // Build order clause with sort column mapping + sortColumnMap := map[string]string{ + "code": "u.uom_code", + "name": "u.uom_name", + "category": "c.category_code", + "created_at": "u.created_at", + } + orderColumn := "u.uom_code" + if mapped, ok := sortColumnMap[filter.SortBy]; ok { + orderColumn = mapped } orderDir := sortASC if strings.ToUpper(filter.SortOrder) == sortDESC { @@ -140,9 +147,10 @@ func (r *UOMRepository) List(ctx context.Context, filter uom.ListFilter) ([]*uom // Data query with pagination selectQuery := ` - SELECT uom_id, uom_code, uom_name, uom_category, description, - is_active, created_at, created_by, updated_at, updated_by, - deleted_at, deleted_by + SELECT u.uom_id, u.uom_code, u.uom_name, u.uom_category_id, + c.category_code, c.category_name, + u.description, u.is_active, u.created_at, u.created_by, + u.updated_at, u.updated_by, u.deleted_at, u.deleted_by ` + baseQuery + fmt.Sprintf( ` ORDER BY %s %s LIMIT $%d OFFSET $%d`, orderColumn, orderDir, argIndex, argIndex+1, @@ -155,9 +163,8 @@ func (r *UOMRepository) List(ctx context.Context, filter uom.ListFilter) ([]*uom return nil, 0, fmt.Errorf("failed to list uoms: %w", err) } defer func() { - if err := rows.Close(); err != nil { - // Log silently as this is cleanup - _ = err + if closeErr := rows.Close(); closeErr != nil { + _ = closeErr } }() @@ -182,7 +189,7 @@ func (r *UOMRepository) Update(ctx context.Context, entity *uom.UOM) error { query := ` UPDATE mst_uom SET uom_name = $2, - uom_category = $3, + uom_category_id = $3, description = $4, is_active = $5, updated_at = $6, @@ -193,7 +200,7 @@ func (r *UOMRepository) Update(ctx context.Context, entity *uom.UOM) error { result, err := r.db.ExecContext(ctx, query, entity.ID(), entity.Name(), - entity.Category().String(), + entity.CategoryID(), entity.Description(), entity.IsActive(), entity.UpdatedAt(), @@ -267,38 +274,39 @@ func (r *UOMRepository) ExistsByID(ctx context.Context, id uuid.UUID) (bool, err // ListAll retrieves all non-deleted UOMs (for export). func (r *UOMRepository) ListAll(ctx context.Context, filter uom.ExportFilter) ([]*uom.UOM, error) { query := ` - SELECT uom_id, uom_code, uom_name, uom_category, description, - is_active, created_at, created_by, updated_at, updated_by, - deleted_at, deleted_by - FROM mst_uom - WHERE deleted_at IS NULL + SELECT u.uom_id, u.uom_code, u.uom_name, u.uom_category_id, + c.category_code, c.category_name, + u.description, u.is_active, u.created_at, u.created_by, + u.updated_at, u.updated_by, u.deleted_at, u.deleted_by + FROM mst_uom u + JOIN mst_uom_category c ON u.uom_category_id = c.uom_category_id + WHERE u.deleted_at IS NULL ` args := []interface{}{} argIndex := 1 // Category filter - if filter.Category != nil { - query += fmt.Sprintf(` AND uom_category = $%d`, argIndex) - args = append(args, filter.Category.String()) + if filter.CategoryID != nil { + query += fmt.Sprintf(` AND u.uom_category_id = $%d`, argIndex) + args = append(args, *filter.CategoryID) argIndex++ } // IsActive filter if filter.IsActive != nil { - query += fmt.Sprintf(` AND is_active = $%d`, argIndex) + query += fmt.Sprintf(` AND u.is_active = $%d`, argIndex) args = append(args, *filter.IsActive) } - query += ` ORDER BY uom_code ASC` + query += ` ORDER BY u.uom_code ASC` rows, err := r.db.QueryContext(ctx, query, args...) if err != nil { return nil, fmt.Errorf("failed to list all uoms: %w", err) } defer func() { - if err := rows.Close(); err != nil { - // Log silently as this is cleanup - _ = err + if closeErr := rows.Close(); closeErr != nil { + _ = closeErr } }() @@ -328,7 +336,9 @@ func (r *UOMRepository) scanUOM(row *sql.Row) (*uom.UOM, error) { &dto.ID, &dto.Code, &dto.Name, - &dto.Category, + &dto.CategoryID, + &dto.CategoryCode, + &dto.CategoryName, &dto.Description, &dto.IsActive, &dto.CreatedAt, @@ -355,7 +365,9 @@ func (r *UOMRepository) scanUOMFromRows(rows *sql.Rows) (*uom.UOM, error) { &dto.ID, &dto.Code, &dto.Name, - &dto.Category, + &dto.CategoryID, + &dto.CategoryCode, + &dto.CategoryName, &dto.Description, &dto.IsActive, &dto.CreatedAt, @@ -374,18 +386,20 @@ func (r *UOMRepository) scanUOMFromRows(rows *sql.Rows) (*uom.UOM, error) { // uomDTO is a data transfer object for database operations. type uomDTO struct { - ID uuid.UUID - Code string - Name string - Category string - Description sql.NullString - IsActive bool - CreatedAt time.Time - CreatedBy string - UpdatedAt sql.NullTime - UpdatedBy sql.NullString - DeletedAt sql.NullTime - DeletedBy sql.NullString + ID uuid.UUID + Code string + Name string + CategoryID uuid.UUID + CategoryCode string + CategoryName string + Description sql.NullString + IsActive bool + CreatedAt time.Time + CreatedBy string + UpdatedAt sql.NullTime + UpdatedBy sql.NullString + DeletedAt sql.NullTime + DeletedBy sql.NullString } // ToEntity converts DTO to domain entity. @@ -395,10 +409,7 @@ func (d *uomDTO) ToEntity() (*uom.UOM, error) { return nil, fmt.Errorf("invalid code from db: %w", err) } - category, err := uom.NewCategory(d.Category) - if err != nil { - return nil, fmt.Errorf("invalid category from db: %w", err) - } + categoryInfo := uom.NewCategoryInfo(d.CategoryID, d.CategoryCode, d.CategoryName) var description string if d.Description.Valid { @@ -429,7 +440,7 @@ func (d *uomDTO) ToEntity() (*uom.UOM, error) { d.ID, code, d.Name, - category, + categoryInfo, description, d.IsActive, d.CreatedAt, diff --git a/services/finance/internal/infrastructure/postgres/uom_repository_test.go b/services/finance/internal/infrastructure/postgres/uom_repository_test.go index cbfc863..ffd30bb 100644 --- a/services/finance/internal/infrastructure/postgres/uom_repository_test.go +++ b/services/finance/internal/infrastructure/postgres/uom_repository_test.go @@ -19,12 +19,13 @@ import ( "github.com/mutugading/goapps-backend/services/finance/internal/infrastructure/postgres" ) -// UOMRepositorySuite is the test suite for UOM repository +// UOMRepositorySuite is the test suite for UOM repository. type UOMRepositorySuite struct { suite.Suite - db *postgres.DB - repo uom.Repository - ctx context.Context + db *postgres.DB + repo uom.Repository + ctx context.Context + categoryID uuid.UUID // Pre-seeded test category ID } func TestUOMRepositorySuite(t *testing.T) { @@ -61,8 +62,9 @@ func (s *UOMRepositorySuite) SetupSuite() { s.db = &postgres.DB{DB: db} s.repo = postgres.NewUOMRepository(s.db) - // Setup test schema + // Setup test schema and seed category s.setupSchema() + s.categoryID = s.seedTestCategory() } func (s *UOMRepositorySuite) TearDownSuite() { @@ -73,16 +75,29 @@ func (s *UOMRepositorySuite) TearDownSuite() { func (s *UOMRepositorySuite) SetupTest() { // Clean up before each test - _, _ = s.db.ExecContext(s.ctx, "DELETE FROM uoms WHERE uom_code LIKE 'TEST%'") + _, _ = s.db.ExecContext(s.ctx, "DELETE FROM mst_uom WHERE uom_code LIKE 'TEST%'") } func (s *UOMRepositorySuite) setupSchema() { schema := ` - CREATE TABLE IF NOT EXISTS uoms ( - id UUID PRIMARY KEY, + CREATE TABLE IF NOT EXISTS mst_uom_category ( + uom_category_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + category_code VARCHAR(50) NOT NULL UNIQUE, + category_name VARCHAR(100) NOT NULL, + description TEXT, + is_active BOOLEAN DEFAULT true, + created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, + created_by VARCHAR(100) NOT NULL, + updated_at TIMESTAMP WITH TIME ZONE, + updated_by VARCHAR(100), + deleted_at TIMESTAMP WITH TIME ZONE, + deleted_by VARCHAR(100) + ); + CREATE TABLE IF NOT EXISTS mst_uom ( + uom_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), uom_code VARCHAR(20) NOT NULL UNIQUE, uom_name VARCHAR(100) NOT NULL, - uom_category VARCHAR(20) NOT NULL, + uom_category_id UUID NOT NULL REFERENCES mst_uom_category(uom_category_id), description TEXT, is_active BOOLEAN DEFAULT true, created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, @@ -92,18 +107,34 @@ func (s *UOMRepositorySuite) setupSchema() { deleted_at TIMESTAMP WITH TIME ZONE, deleted_by VARCHAR(100) ); - CREATE INDEX IF NOT EXISTS idx_uoms_code ON uoms(uom_code); - CREATE INDEX IF NOT EXISTS idx_uoms_category ON uoms(uom_category); - CREATE INDEX IF NOT EXISTS idx_uoms_active ON uoms(is_active) WHERE deleted_at IS NULL; + CREATE INDEX IF NOT EXISTS idx_mst_uom_code ON mst_uom(uom_code); + CREATE INDEX IF NOT EXISTS idx_mst_uom_category_id ON mst_uom(uom_category_id); + CREATE INDEX IF NOT EXISTS idx_mst_uom_active ON mst_uom(is_active) WHERE deleted_at IS NULL; ` _, err := s.db.ExecContext(s.ctx, schema) require.NoError(s.T(), err) } +func (s *UOMRepositorySuite) seedTestCategory() uuid.UUID { + id := uuid.New() + _, err := s.db.ExecContext(s.ctx, ` + INSERT INTO mst_uom_category (uom_category_id, category_code, category_name, created_by) + VALUES ($1, 'TEST_WEIGHT', 'Test Weight', 'test') + ON CONFLICT (category_code) DO UPDATE SET category_code = EXCLUDED.category_code + RETURNING uom_category_id + `, id) + if err != nil { + // If conflict, fetch existing + _ = s.db.QueryRowContext(s.ctx, + "SELECT uom_category_id FROM mst_uom_category WHERE category_code = 'TEST_WEIGHT'", + ).Scan(&id) + } + return id +} + func (s *UOMRepositorySuite) TestCreate() { code, _ := uom.NewCode("TEST_KG") - category, _ := uom.NewCategory("WEIGHT") - entity, _ := uom.NewUOM(code, "Test Kilogram", category, "Test description", "test_user") + entity, _ := uom.NewUOM(code, "Test Kilogram", s.categoryID, "Test description", "test_user") err := s.repo.Create(s.ctx, entity) assert.NoError(s.T(), err) @@ -117,10 +148,9 @@ func (s *UOMRepositorySuite) TestCreate() { func (s *UOMRepositorySuite) TestCreate_DuplicateCode() { code, _ := uom.NewCode("TEST_DUP") - category, _ := uom.NewCategory("WEIGHT") - entity1, _ := uom.NewUOM(code, "First", category, "", "test") - entity2, _ := uom.NewUOM(code, "Second", category, "", "test") + entity1, _ := uom.NewUOM(code, "First", s.categoryID, "", "test") + entity2, _ := uom.NewUOM(code, "Second", s.categoryID, "", "test") err := s.repo.Create(s.ctx, entity1) assert.NoError(s.T(), err) @@ -138,14 +168,12 @@ func (s *UOMRepositorySuite) TestGetByID_NotFound() { func (s *UOMRepositorySuite) TestUpdate() { // Create first code, _ := uom.NewCode("TEST_UPD") - category, _ := uom.NewCategory("WEIGHT") - entity, _ := uom.NewUOM(code, "Original", category, "Old desc", "creator") + entity, _ := uom.NewUOM(code, "Original", s.categoryID, "Old desc", "creator") _ = s.repo.Create(s.ctx, entity) // Update newName := "Updated Name" - newCat, _ := uom.NewCategory("LENGTH") - _ = entity.Update(&newName, &newCat, nil, nil, "updater") + _ = entity.Update(&newName, nil, nil, nil, "updater") err := s.repo.Update(s.ctx, entity) assert.NoError(s.T(), err) @@ -153,13 +181,11 @@ func (s *UOMRepositorySuite) TestUpdate() { // Verify result, _ := s.repo.GetByID(s.ctx, entity.ID()) assert.Equal(s.T(), "Updated Name", result.Name()) - assert.Equal(s.T(), "LENGTH", result.Category().String()) } func (s *UOMRepositorySuite) TestSoftDelete() { code, _ := uom.NewCode("TEST_DEL") - category, _ := uom.NewCategory("WEIGHT") - entity, _ := uom.NewUOM(code, "To Delete", category, "", "creator") + entity, _ := uom.NewUOM(code, "To Delete", s.categoryID, "", "creator") _ = s.repo.Create(s.ctx, entity) err := s.repo.SoftDelete(s.ctx, entity.ID(), "deleter") @@ -175,8 +201,7 @@ func (s *UOMRepositorySuite) TestList() { // Create test data for i := 1; i <= 5; i++ { code, _ := uom.NewCode(fmt.Sprintf("TEST_LIST%d", i)) - category, _ := uom.NewCategory("WEIGHT") - entity, _ := uom.NewUOM(code, fmt.Sprintf("List Item %d", i), category, "", "tester") + entity, _ := uom.NewUOM(code, fmt.Sprintf("List Item %d", i), s.categoryID, "", "tester") _ = s.repo.Create(s.ctx, entity) } @@ -195,8 +220,7 @@ func (s *UOMRepositorySuite) TestList() { func (s *UOMRepositorySuite) TestExistsByCode() { code, _ := uom.NewCode("TEST_EXISTS") - category, _ := uom.NewCategory("VOLUME") - entity, _ := uom.NewUOM(code, "Exists Test", category, "", "tester") + entity, _ := uom.NewUOM(code, "Exists Test", s.categoryID, "", "tester") _ = s.repo.Create(s.ctx, entity) exists, err := s.repo.ExistsByCode(s.ctx, code) diff --git a/services/finance/internal/infrastructure/redis/uom_cache.go b/services/finance/internal/infrastructure/redis/uom_cache.go index 9ba2e46..e3ee06d 100644 --- a/services/finance/internal/infrastructure/redis/uom_cache.go +++ b/services/finance/internal/infrastructure/redis/uom_cache.go @@ -36,16 +36,18 @@ func NewUOMCache(client *Client) *UOMCache { // uomCacheData is the cached representation of UOM. type uomCacheData struct { - ID string `json:"id"` - Code string `json:"code"` - Name string `json:"name"` - Category string `json:"category"` - Description string `json:"description"` - IsActive bool `json:"is_active"` - CreatedAt string `json:"created_at"` - CreatedBy string `json:"created_by"` - UpdatedAt *string `json:"updated_at,omitempty"` - UpdatedBy *string `json:"updated_by,omitempty"` + ID string `json:"id"` + Code string `json:"code"` + Name string `json:"name"` + CategoryID string `json:"category_id"` + CategoryCode string `json:"category_code"` + CategoryName string `json:"category_name"` + Description string `json:"description"` + IsActive bool `json:"is_active"` + CreatedAt string `json:"created_at"` + CreatedBy string `json:"created_by"` + UpdatedAt *string `json:"updated_at,omitempty"` + UpdatedBy *string `json:"updated_by,omitempty"` } // GetByID retrieves a UOM by ID from cache. @@ -134,14 +136,16 @@ func (c *UOMCache) InvalidateList(ctx context.Context) error { // fromEntity converts domain entity to cache data. func (c *UOMCache) fromEntity(entity *uom.UOM) *uomCacheData { data := &uomCacheData{ - ID: entity.ID().String(), - Code: entity.Code().String(), - Name: entity.Name(), - Category: entity.Category().String(), - Description: entity.Description(), - IsActive: entity.IsActive(), - CreatedAt: entity.CreatedAt().Format(time.RFC3339), - CreatedBy: entity.CreatedBy(), + ID: entity.ID().String(), + Code: entity.Code().String(), + Name: entity.Name(), + CategoryID: entity.CategoryID().String(), + CategoryCode: entity.CategoryInfo().Code(), + CategoryName: entity.CategoryInfo().Name(), + Description: entity.Description(), + IsActive: entity.IsActive(), + CreatedAt: entity.CreatedAt().Format(time.RFC3339), + CreatedBy: entity.CreatedBy(), } if entity.UpdatedAt() != nil { @@ -167,10 +171,11 @@ func (c *UOMCache) toEntity(data *uomCacheData) (*uom.UOM, error) { return nil, err } - category, err := uom.NewCategory(data.Category) + categoryID, err := uuid.Parse(data.CategoryID) if err != nil { return nil, err } + categoryInfo := uom.NewCategoryInfo(categoryID, data.CategoryCode, data.CategoryName) createdAt, err := time.Parse(time.RFC3339, data.CreatedAt) if err != nil { @@ -189,7 +194,7 @@ func (c *UOMCache) toEntity(data *uomCacheData) (*uom.UOM, error) { id, code, data.Name, - category, + categoryInfo, data.Description, data.IsActive, createdAt, diff --git a/services/finance/migrations/postgres/000007_create_mst_uom_category.down.sql b/services/finance/migrations/postgres/000007_create_mst_uom_category.down.sql new file mode 100644 index 0000000..68c03e7 --- /dev/null +++ b/services/finance/migrations/postgres/000007_create_mst_uom_category.down.sql @@ -0,0 +1,36 @@ +-- Reverse migration: Restore UOM category enum approach + +-- Step 1: Drop FK index +DROP INDEX IF EXISTS idx_mst_uom_category_id; + +-- Step 2: Drop FK constraint +ALTER TABLE mst_uom DROP CONSTRAINT IF EXISTS fk_mst_uom_category; + +-- Step 3: Add back the uom_category column +ALTER TABLE mst_uom ADD COLUMN IF NOT EXISTS uom_category VARCHAR(50); + +-- Step 4: Migrate data back from FK to string +UPDATE mst_uom u +SET uom_category = c.category_code +FROM mst_uom_category c +WHERE u.uom_category_id = c.uom_category_id; + +-- Step 5: Make uom_category NOT NULL and add CHECK constraint +ALTER TABLE mst_uom ALTER COLUMN uom_category SET NOT NULL; +ALTER TABLE mst_uom ADD CONSTRAINT chk_mst_uom_category + CHECK (uom_category IN ('WEIGHT', 'LENGTH', 'VOLUME', 'QUANTITY')); + +-- Step 6: Drop uom_category_id column +ALTER TABLE mst_uom DROP COLUMN IF EXISTS uom_category_id; + +-- Step 7: Recreate old index +CREATE INDEX IF NOT EXISTS idx_mst_uom_category ON mst_uom(uom_category) WHERE deleted_at IS NULL; + +-- Step 8: Drop UOM Category table indexes +DROP INDEX IF EXISTS idx_mst_uom_category_search; +DROP INDEX IF EXISTS idx_mst_uom_category_created_at; +DROP INDEX IF EXISTS idx_mst_uom_category_active; +DROP INDEX IF EXISTS idx_mst_uom_category_code; + +-- Step 9: Drop UOM Category table +DROP TABLE IF EXISTS mst_uom_category; diff --git a/services/finance/migrations/postgres/000007_create_mst_uom_category.up.sql b/services/finance/migrations/postgres/000007_create_mst_uom_category.up.sql new file mode 100644 index 0000000..b950fdf --- /dev/null +++ b/services/finance/migrations/postgres/000007_create_mst_uom_category.up.sql @@ -0,0 +1,88 @@ +-- Migration: Create mst_uom_category table and migrate UOM category from enum to FK +-- Description: Master table for UOM Categories, replaces hardcoded UOMCategory enum + +-- Step 1: Create mst_uom_category table +CREATE TABLE IF NOT EXISTS mst_uom_category ( + -- Primary Key + uom_category_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + + -- Core Fields + category_code VARCHAR(20) NOT NULL, + category_name VARCHAR(100) NOT NULL, + description TEXT, + + -- Status + is_active BOOLEAN NOT NULL DEFAULT TRUE, + + -- Audit Fields + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + created_by VARCHAR(100) NOT NULL, + updated_at TIMESTAMPTZ, + updated_by VARCHAR(100), + + -- Soft Delete Fields + deleted_at TIMESTAMPTZ, + deleted_by VARCHAR(100), + + -- Constraints + CONSTRAINT uq_mst_uom_category_code UNIQUE (category_code) +); + +-- Comments +COMMENT ON TABLE mst_uom_category IS 'Master table for UOM Categories'; +COMMENT ON COLUMN mst_uom_category.uom_category_id IS 'Unique identifier (UUID)'; +COMMENT ON COLUMN mst_uom_category.category_code IS 'Unique code (e.g., WEIGHT, LENGTH, VOLUME)'; +COMMENT ON COLUMN mst_uom_category.category_name IS 'Display name (e.g., Weight, Length, Volume)'; +COMMENT ON COLUMN mst_uom_category.is_active IS 'Whether the category is active'; +COMMENT ON COLUMN mst_uom_category.deleted_at IS 'Soft delete timestamp (NULL = not deleted)'; + +-- Indexes for common queries (excluding soft-deleted records) +CREATE INDEX IF NOT EXISTS idx_mst_uom_category_code ON mst_uom_category(category_code) WHERE deleted_at IS NULL; +CREATE INDEX IF NOT EXISTS idx_mst_uom_category_active ON mst_uom_category(is_active) WHERE deleted_at IS NULL; +CREATE INDEX IF NOT EXISTS idx_mst_uom_category_created_at ON mst_uom_category(created_at) WHERE deleted_at IS NULL; + +-- Full-text search index +CREATE INDEX IF NOT EXISTS idx_mst_uom_category_search ON mst_uom_category + USING gin(to_tsvector('english', coalesce(category_code, '') || ' ' || coalesce(category_name, '') || ' ' || coalesce(description, ''))) + WHERE deleted_at IS NULL; + +-- Step 2: Seed initial UOM categories +INSERT INTO mst_uom_category (category_code, category_name, description, created_by) VALUES + ('WEIGHT', 'Weight', 'Weight-based units (e.g., KG, GR, TON)', 'system'), + ('LENGTH', 'Length', 'Length-based units (e.g., MTR, CM, YARD)', 'system'), + ('VOLUME', 'Volume', 'Volume-based units (e.g., LTR, ML)', 'system'), + ('QUANTITY', 'Quantity', 'Quantity-based units (e.g., PCS, BOX, SET)', 'system'), + ('TIME', 'Time', 'Time-based units (e.g., HR, MIN, SEC)', 'system'), + ('AREA', 'Area', 'Area-based units (e.g., M2, HA, ACRE)', 'system'), + ('ENERGY', 'Energy', 'Energy-based units (e.g., KWH, MJ, CAL)', 'system'), + ('TEMPERATURE', 'Temperature', 'Temperature units (e.g., CEL, FAH, KEL)', 'system'), + ('PRESSURE', 'Pressure', 'Pressure-based units (e.g., BAR, PSI, ATM)', 'system') +ON CONFLICT (category_code) DO NOTHING; + +-- Step 3: Add uom_category_id column to mst_uom +ALTER TABLE mst_uom ADD COLUMN IF NOT EXISTS uom_category_id UUID; + +-- Step 4: Migrate existing data from uom_category string to uom_category_id FK +UPDATE mst_uom u +SET uom_category_id = c.uom_category_id +FROM mst_uom_category c +WHERE UPPER(u.uom_category) = c.category_code + AND u.uom_category_id IS NULL; + +-- Step 5: Drop the old CHECK constraint and column +ALTER TABLE mst_uom DROP CONSTRAINT IF EXISTS chk_mst_uom_category; +ALTER TABLE mst_uom DROP COLUMN IF EXISTS uom_category; + +-- Step 6: Make uom_category_id NOT NULL after migration +ALTER TABLE mst_uom ALTER COLUMN uom_category_id SET NOT NULL; + +-- Step 7: Add foreign key constraint +ALTER TABLE mst_uom ADD CONSTRAINT fk_mst_uom_category + FOREIGN KEY (uom_category_id) + REFERENCES mst_uom_category(uom_category_id); + +-- Step 8: Create index on FK column +CREATE INDEX IF NOT EXISTS idx_mst_uom_category_id ON mst_uom(uom_category_id) WHERE deleted_at IS NULL; + +-- Drop old category index (no longer needed) +DROP INDEX IF EXISTS idx_mst_uom_category; diff --git a/services/finance/seeds/main.go b/services/finance/seeds/main.go index 13555d8..753a34d 100644 --- a/services/finance/seeds/main.go +++ b/services/finance/seeds/main.go @@ -15,10 +15,10 @@ import ( ) type uomSeed struct { - code string - name string - category string - description string + code string + name string + categoryCode string + description string } type rmCategorySeed struct { @@ -107,7 +107,16 @@ func main() { return } - // Seed UOMs + // Seed UOMs (uses uom_category_id FK via lookup on mst_uom_category) + seedUOMs(ctx, db) + + // Seed RM Categories + seedRMCategories(ctx, db) +} + +func seedUOMs(ctx context.Context, db *sql.DB) { + log.Info().Msg("Seeding UOMs") + inserted := 0 skipped := 0 @@ -129,11 +138,23 @@ func main() { continue } - // Insert + // Lookup category ID from mst_uom_category + var categoryID string + err = db.QueryRowContext(ctx, + "SELECT uom_category_id FROM mst_uom_category WHERE category_code = $1 AND deleted_at IS NULL", + seed.categoryCode, + ).Scan(&categoryID) + if err != nil { + log.Error().Err(err).Str("code", seed.code).Str("category", seed.categoryCode). + Msg("Failed to find UOM category - run migration 000007 first") + continue + } + + // Insert with uom_category_id FK _, err = db.ExecContext(ctx, - `INSERT INTO mst_uom (uom_code, uom_name, uom_category, description, is_active, created_by) + `INSERT INTO mst_uom (uom_code, uom_name, uom_category_id, description, is_active, created_by) VALUES ($1, $2, $3, $4, true, 'seeder')`, - seed.code, seed.name, seed.category, seed.description, + seed.code, seed.name, categoryID, seed.description, ) if err != nil { log.Error().Err(err).Str("code", seed.code).Msg("Failed to insert UOM") @@ -148,9 +169,6 @@ func main() { fmt.Printf(" Inserted: %d\n", inserted) fmt.Printf(" Skipped: %d\n", skipped) fmt.Printf(" Total: %d\n", len(uomSeeds)) - - // Seed RM Categories - seedRMCategories(ctx, db) } func seedRMCategories(ctx context.Context, db *sql.DB) { diff --git a/services/finance/tests/e2e/uom_test.go b/services/finance/tests/e2e/uom_test.go index 5477a27..64fd914 100644 --- a/services/finance/tests/e2e/uom_test.go +++ b/services/finance/tests/e2e/uom_test.go @@ -21,9 +21,11 @@ import ( // E2ETestSuite is the end-to-end test suite. type E2ETestSuite struct { suite.Suite - conn *grpc.ClientConn - client financev1.UOMServiceClient - ctx context.Context // authenticated context with JWT + conn *grpc.ClientConn + client financev1.UOMServiceClient + categoryClient financev1.UOMCategoryServiceClient + ctx context.Context // authenticated context with JWT + testCategoryID string // cached category ID for tests } func TestE2ESuite(t *testing.T) { @@ -44,6 +46,7 @@ func (s *E2ETestSuite) SetupSuite() { s.conn = conn s.client = financev1.NewUOMServiceClient(conn) + s.categoryClient = financev1.NewUOMCategoryServiceClient(conn) // Generate test JWT and create authenticated context token := s.generateTestToken() @@ -134,10 +137,10 @@ func (s *E2ETestSuite) TestCreateUOM_Success() { code := "E2E_" + time.Now().Format("150405") resp, err := s.client.CreateUOM(ctx, &financev1.CreateUOMRequest{ - UomCode: code, - UomName: "E2E Test Unit", - UomCategory: financev1.UOMCategory_UOM_CATEGORY_QUANTITY, - Description: "Created by E2E test", + UomCode: code, + UomName: "E2E Test Unit", + UomCategoryId: s.getTestCategoryID(), + Description: "Created by E2E test", }) require.NoError(s.T(), err) @@ -154,9 +157,9 @@ func (s *E2ETestSuite) TestCreateUOM_ValidationError() { defer cancel() resp, err := s.client.CreateUOM(ctx, &financev1.CreateUOMRequest{ - UomCode: "invalid_lowercase", // Invalid: lowercase - UomName: "", // Invalid: empty - UomCategory: financev1.UOMCategory_UOM_CATEGORY_UNSPECIFIED, + UomCode: "invalid_lowercase", // Invalid: lowercase + UomName: "", // Invalid: empty + UomCategoryId: "", // Invalid: empty }) // Should NOT return error - validation errors in BaseResponse @@ -196,10 +199,10 @@ func (s *E2ETestSuite) TestCRUDFlow() { // 1. Create createResp, err := s.client.CreateUOM(ctx, &financev1.CreateUOMRequest{ - UomCode: code, - UomName: "CRUD Test", - UomCategory: financev1.UOMCategory_UOM_CATEGORY_WEIGHT, - Description: "CRUD flow test", + UomCode: code, + UomName: "CRUD Test", + UomCategoryId: s.getTestCategoryID(), + Description: "CRUD flow test", }) require.NoError(s.T(), err) require.True(s.T(), createResp.Base.IsSuccess, "Create failed: %s", createResp.Base.Message) @@ -239,19 +242,20 @@ func (s *E2ETestSuite) TestListWithFilters() { ctx, cancel := context.WithTimeout(s.ctx, 10*time.Second) defer cancel() - // List with category filter + // List with category filter (requires a known category ID from seed data) + categoryID := s.getTestCategoryID() resp, err := s.client.ListUOMs(ctx, &financev1.ListUOMsRequest{ - Page: 1, - PageSize: 50, - Category: financev1.UOMCategory_UOM_CATEGORY_WEIGHT, + Page: 1, + PageSize: 50, + UomCategoryId: categoryID, }) require.NoError(s.T(), err) assert.True(s.T(), resp.Base.IsSuccess) - // All returned items should be WEIGHT category + // All returned items should have the same category for _, item := range resp.Data { - assert.Equal(s.T(), financev1.UOMCategory_UOM_CATEGORY_WEIGHT, item.UomCategory) + assert.Equal(s.T(), categoryID, item.UomCategoryId) } } @@ -293,6 +297,39 @@ func (s *E2ETestSuite) TestUnauthenticated() { assert.Equal(s.T(), "401", resp.Base.StatusCode) } +// getTestCategoryID returns a valid UOM category ID for use in tests. +// It fetches the first active category from the database via gRPC, or creates one if none exist. +func (s *E2ETestSuite) getTestCategoryID() string { + if s.testCategoryID != "" { + return s.testCategoryID + } + + ctx, cancel := context.WithTimeout(s.ctx, 10*time.Second) + defer cancel() + + // Try to list existing categories + listResp, err := s.categoryClient.ListUOMCategories(ctx, &financev1.ListUOMCategoriesRequest{ + Page: 1, + PageSize: 1, + }) + if err == nil && listResp.Base.IsSuccess && len(listResp.Data) > 0 { + s.testCategoryID = listResp.Data[0].UomCategoryId + return s.testCategoryID + } + + // Create a test category if none exist + createResp, err := s.categoryClient.CreateUOMCategory(ctx, &financev1.CreateUOMCategoryRequest{ + CategoryCode: "E2E_TEST", + CategoryName: "E2E Test Category", + Description: "Category for E2E tests", + }) + require.NoError(s.T(), err) + require.True(s.T(), createResp.Base.IsSuccess, "Failed to create test category: %s", createResp.Base.Message) + + s.testCategoryID = createResp.Data.UomCategoryId + return s.testCategoryID +} + // Helper function. func getEnv(key, defaultVal string) string { if val := os.Getenv(key); val != "" { diff --git a/services/iam/migrations/postgres/000016_seed_uom_category_menu.down.sql b/services/iam/migrations/postgres/000016_seed_uom_category_menu.down.sql new file mode 100644 index 0000000..469183b --- /dev/null +++ b/services/iam/migrations/postgres/000016_seed_uom_category_menu.down.sql @@ -0,0 +1,21 @@ +-- IAM Service Database Migrations +-- 000016: Rollback UOM Category menu and permissions seed + +-- Remove role_permissions for UOM Category +DELETE FROM role_permissions +WHERE permission_id IN ( + SELECT permission_id FROM mst_permission + WHERE permission_code LIKE 'finance.master.uomcategory.%' +); + +-- Remove menu_permissions for UOM Category +DELETE FROM menu_permissions +WHERE menu_id = '00000000-0000-0000-0003-000000000007'; + +-- Remove menu entry +DELETE FROM mst_menu +WHERE menu_code = 'FINANCE_UOM_CATEGORY'; + +-- Remove permissions +DELETE FROM mst_permission +WHERE permission_code LIKE 'finance.master.uomcategory.%'; diff --git a/services/iam/migrations/postgres/000016_seed_uom_category_menu.up.sql b/services/iam/migrations/postgres/000016_seed_uom_category_menu.up.sql new file mode 100644 index 0000000..216f7ba --- /dev/null +++ b/services/iam/migrations/postgres/000016_seed_uom_category_menu.up.sql @@ -0,0 +1,54 @@ +-- IAM Service Database Migrations +-- 000016: Seed UOM Category menu and permissions +-- +-- Adds UOM Category as a child of Finance > Master in the sidebar navigation. +-- Also seeds the finance.master.uomcategory.* permissions for RBAC. +-- All inserts use ON CONFLICT DO NOTHING for idempotency. + +-- ============================================================================= +-- PERMISSIONS — finance.master.uomcategory.* +-- ============================================================================= + +INSERT INTO mst_permission (permission_code, permission_name, description, service_name, module_name, action_type, is_active, created_by) +VALUES + ('finance.master.uomcategory.view', 'View UOM Categories', 'View UOM categories list and details', 'finance', 'master', 'view', true, 'seed'), + ('finance.master.uomcategory.create', 'Create UOM Category', 'Create new UOM categories', 'finance', 'master', 'create', true, 'seed'), + ('finance.master.uomcategory.update', 'Update UOM Category', 'Update existing UOM categories', 'finance', 'master', 'update', true, 'seed'), + ('finance.master.uomcategory.delete', 'Delete UOM Category', 'Delete UOM categories', 'finance', 'master', 'delete', true, 'seed'), + ('finance.master.uomcategory.export', 'Export UOM Categories', 'Export UOM categories to Excel', 'finance', 'master', 'export', true, 'seed'), + ('finance.master.uomcategory.import', 'Import UOM Categories', 'Import UOM categories from Excel', 'finance', 'master', 'import', true, 'seed') +ON CONFLICT (permission_code) DO NOTHING; + +-- ============================================================================= +-- MENU ENTRY — Finance > Master > UOM Category +-- ============================================================================= + +INSERT INTO mst_menu (menu_id, parent_id, menu_code, menu_title, menu_url, icon_name, service_name, menu_level, sort_order, is_visible, is_active, created_by) +VALUES + ('00000000-0000-0000-0003-000000000007', '00000000-0000-0000-0002-000000000002', 'FINANCE_UOM_CATEGORY', 'UOM Category', '/finance/master/uom-category', 'FolderTree', 'finance', 3, 12, true, true, 'seed') +ON CONFLICT (menu_code) DO NOTHING; + +-- ============================================================================= +-- MENU PERMISSIONS — Link UOM Category menu to view permission +-- ============================================================================= + +INSERT INTO menu_permissions (menu_id, permission_id, assigned_by) +SELECT '00000000-0000-0000-0003-000000000007', permission_id, 'seed' +FROM mst_permission +WHERE permission_code = 'finance.master.uomcategory.view' AND is_active = true +ON CONFLICT (menu_id, permission_id) DO NOTHING; + +-- ============================================================================= +-- ASSIGN UOM CATEGORY PERMISSIONS TO SUPER ADMIN ROLE +-- ============================================================================= +-- Ensures the super admin role (if it exists) gets all UOM Category permissions. + +INSERT INTO role_permissions (role_id, permission_id, assigned_by) +SELECT r.role_id, p.permission_id, 'seed' +FROM mst_role r +CROSS JOIN mst_permission p +WHERE r.role_code = 'SUPER_ADMIN' + AND p.permission_code LIKE 'finance.master.uomcategory.%' + AND r.is_active = true + AND p.is_active = true +ON CONFLICT (role_id, permission_id) DO NOTHING;