Two gaps found while auditing the order/stock flow: - Receiving stock via an approved stock request only updated products.productstatus (a global per-product flag). A location flagged outofstock by CreateOrder never got reactivated, since nothing touched productlocations.status on the way back in. CreateProductStock now reactivates the specific (tenant, location, product) row to "available" for every "in" entry, the counterpart to how it gets flagged out. - getproductbyvariant returned no stock info at all, so the app could only find out a product was unavailable from the 409 at order time. It now accepts an optional locationid and, when passed, returns live productstock (same SUM(in)-SUM(out) formula the order check uses) and locationstatus per product. Omitting locationid keeps the old response shape. Added MOBILE_ORDER_VERIFICATION.md as a handoff doc for the mobile team covering the expected request/response shapes and how to verify their integration. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
658 lines
17 KiB
Go
658 lines
17 KiB
Go
package controllers
|
|
|
|
import (
|
|
"net/http"
|
|
"strconv"
|
|
|
|
"nearle/models"
|
|
"nearle/services"
|
|
|
|
"github.com/gofiber/fiber/v2"
|
|
)
|
|
|
|
type ProductController struct {
|
|
productService services.ProductService
|
|
}
|
|
|
|
func NewProductController(productService services.ProductService) *ProductController {
|
|
return &ProductController{productService: productService}
|
|
}
|
|
|
|
func (ctl *ProductController) GetProductSubCategory(c *fiber.Ctx) error {
|
|
categoryID, _ := strconv.Atoi(c.Query("categoryid", "0"))
|
|
tenantID, _ := strconv.Atoi(c.Query("tenantid", "0"))
|
|
|
|
data, err := ctl.productService.GetProductSubCategory(categoryID, tenantID)
|
|
if err != nil {
|
|
return c.JSON(fiber.Map{
|
|
"code": http.StatusInternalServerError,
|
|
"message": err.Error(),
|
|
"status": false,
|
|
})
|
|
}
|
|
|
|
return c.JSON(fiber.Map{
|
|
"code": 200,
|
|
"message": "Success",
|
|
"status": true,
|
|
"details": data,
|
|
})
|
|
}
|
|
|
|
func (ctl *ProductController) GetProductCount(c *fiber.Ctx) error {
|
|
tenantID, _ := strconv.Atoi(c.Query("tenantid"))
|
|
categoryID, _ := strconv.Atoi(c.Query("categoryid"))
|
|
subcategoryID, _ := strconv.Atoi(c.Query("subcategoryid"))
|
|
// locationID, _ := strconv.Atoi(c.Query("locationid"))
|
|
approve := c.Query("approve", "")
|
|
|
|
data, err := ctl.productService.GetProductCount(tenantID, categoryID, subcategoryID, approve)
|
|
if err != nil {
|
|
return c.JSON(fiber.Map{
|
|
"code": http.StatusConflict,
|
|
"message": "failed",
|
|
"status": false,
|
|
})
|
|
}
|
|
|
|
return c.JSON(fiber.Map{
|
|
"code": http.StatusOK,
|
|
"message": "Success",
|
|
"status": true,
|
|
"details": data,
|
|
})
|
|
}
|
|
|
|
func (ctl *ProductController) GetProductCategory(c *fiber.Ctx) error {
|
|
data, err := ctl.productService.GetProductCategory()
|
|
if err != nil {
|
|
return c.JSON(fiber.Map{
|
|
"code": 500,
|
|
"message": "Failed to fetch categories",
|
|
"status": false,
|
|
})
|
|
}
|
|
|
|
return c.JSON(fiber.Map{
|
|
"code": http.StatusOK,
|
|
"message": "Success",
|
|
"status": true,
|
|
"details": data,
|
|
})
|
|
}
|
|
|
|
func (ctl *ProductController) GetProductVariants(c *fiber.Ctx) error {
|
|
|
|
tenantID, _ := strconv.Atoi(c.Query("tenantid"))
|
|
subcategoryID, _ := strconv.Atoi(c.Query("subcategoryid"))
|
|
|
|
data, err := ctl.productService.GetProductVariants(tenantID, subcategoryID)
|
|
if err != nil {
|
|
return c.JSON(fiber.Map{
|
|
"code": 500,
|
|
"message": "Failed to fetch Product varients",
|
|
"status": false,
|
|
})
|
|
}
|
|
|
|
return c.JSON(fiber.Map{
|
|
"code": http.StatusOK,
|
|
"message": "Success",
|
|
"status": true,
|
|
"details": data,
|
|
})
|
|
}
|
|
|
|
func (ctl *ProductController) GetCatalougeProducts(c *fiber.Ctx) error {
|
|
|
|
tenantID, _ := strconv.Atoi(c.Query("tenantid"))
|
|
locationID, _ := strconv.Atoi(c.Query("locationid"))
|
|
subcategoryID, _ := strconv.Atoi(c.Query("subcategoryid"))
|
|
keyword := c.Query("keyword")
|
|
pageno, _ := strconv.Atoi(c.Query("pageno"))
|
|
pagesize, _ := strconv.Atoi(c.Query("pagesize"))
|
|
|
|
data, err := ctl.productService.GetCatalougeProducts(tenantID, locationID, subcategoryID, pageno, pagesize, keyword)
|
|
if err != nil {
|
|
return c.JSON(fiber.Map{
|
|
"code": 500,
|
|
"message": "Failed to fetch Catalouge Product",
|
|
"status": false,
|
|
})
|
|
}
|
|
|
|
return c.JSON(fiber.Map{
|
|
"code": http.StatusOK,
|
|
"message": "Success",
|
|
"status": true,
|
|
"details": data,
|
|
})
|
|
|
|
}
|
|
|
|
func (ctl *ProductController) GetProductStocks(c *fiber.Ctx) error {
|
|
tenantID := c.Query("tenantid")
|
|
locationID := c.Query("locationid")
|
|
|
|
stocks, err := ctl.productService.GetProductStocks(tenantID, locationID)
|
|
if err != nil {
|
|
return c.JSON(fiber.Map{
|
|
"code": 500,
|
|
"message": "Failed to fetch product stocks",
|
|
"status": false,
|
|
})
|
|
}
|
|
|
|
return c.JSON(fiber.Map{
|
|
"code": 200,
|
|
"message": "Product stocks fetched successfully",
|
|
"status": true,
|
|
"data": stocks,
|
|
})
|
|
}
|
|
|
|
func (ctl *ProductController) CreateProductStock(c *fiber.Ctx) error {
|
|
var stocks []models.Productstock
|
|
|
|
if err := c.BodyParser(&stocks); err != nil {
|
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
|
"code": 400,
|
|
"message": "Invalid request body",
|
|
"status": false,
|
|
})
|
|
}
|
|
|
|
if err := ctl.productService.CreateProductStock(stocks); err != nil {
|
|
return c.JSON(fiber.Map{
|
|
"code": 500,
|
|
"message": "Failed to create product stocks",
|
|
"status": false,
|
|
})
|
|
}
|
|
|
|
return c.JSON(fiber.Map{
|
|
"code": 201,
|
|
"message": "Product stocks created successfully",
|
|
"status": true,
|
|
"details": stocks,
|
|
})
|
|
}
|
|
|
|
func (ctl *ProductController) CreateProduct(c *fiber.Ctx) error {
|
|
var product models.Products
|
|
|
|
if err := c.BodyParser(&product); err != nil {
|
|
return c.JSON(fiber.Map{
|
|
"code": http.StatusBadRequest,
|
|
"message": "Invalid request body",
|
|
"status": false,
|
|
})
|
|
}
|
|
|
|
if err := ctl.productService.CreateProduct(product); err != nil {
|
|
return c.JSON(fiber.Map{
|
|
"code": http.StatusInternalServerError,
|
|
"message": "Failed to create product",
|
|
"status": false,
|
|
})
|
|
}
|
|
|
|
return c.JSON(fiber.Map{
|
|
"code": http.StatusCreated,
|
|
"message": "Product created successfully",
|
|
"status": true,
|
|
"data": product,
|
|
})
|
|
}
|
|
|
|
func (ctl *ProductController) UpdateProduct(c *fiber.Ctx) error {
|
|
var product models.Products
|
|
|
|
if err := c.BodyParser(&product); err != nil {
|
|
return c.JSON(fiber.Map{
|
|
"code": http.StatusBadRequest,
|
|
"message": "Invalid request body",
|
|
"status": false,
|
|
})
|
|
}
|
|
|
|
if err := ctl.productService.UpdateProduct(product); err != nil {
|
|
return c.JSON(fiber.Map{
|
|
"code": http.StatusConflict,
|
|
"message": err.Error(),
|
|
"status": false,
|
|
})
|
|
}
|
|
|
|
return c.JSON(fiber.Map{
|
|
"code": http.StatusAccepted,
|
|
"message": "Product updated successfully",
|
|
"status": true,
|
|
"data": product,
|
|
})
|
|
}
|
|
|
|
func (ctl *ProductController) DeleteProduct(c *fiber.Ctx) error {
|
|
|
|
pidStr := c.Query("productid")
|
|
pid, err := strconv.Atoi(pidStr)
|
|
if err != nil || pid <= 0 {
|
|
return c.JSON(fiber.Map{
|
|
"code": http.StatusBadRequest,
|
|
"message": "Invalid product ID",
|
|
"status": false,
|
|
})
|
|
}
|
|
|
|
if err := ctl.productService.DeleteProduct(pid); err != nil {
|
|
return c.JSON(fiber.Map{
|
|
"code": http.StatusInternalServerError,
|
|
"message": err.Error(),
|
|
"status": false,
|
|
})
|
|
}
|
|
|
|
return c.JSON(fiber.Map{
|
|
"code": http.StatusOK,
|
|
"message": "Product deleted successfully",
|
|
"status": true,
|
|
})
|
|
}
|
|
|
|
func (ctl *ProductController) GetStockStatement(c *fiber.Ctx) error {
|
|
tenantID, _ := strconv.Atoi(c.Query("tenantid"))
|
|
locationID, _ := strconv.Atoi(c.Query("locationid"))
|
|
subcategoryID, _ := strconv.Atoi(c.Query("subcategoryid"))
|
|
pageno, _ := strconv.Atoi(c.Query("pageno"))
|
|
pagesize, _ := strconv.Atoi(c.Query("pagesize"))
|
|
keyword := c.Query("keyword")
|
|
|
|
data, err := ctl.productService.GetStockStatement(tenantID, locationID, subcategoryID, pageno, pagesize, keyword)
|
|
if err != nil {
|
|
return c.JSON(fiber.Map{
|
|
"code": http.StatusInternalServerError,
|
|
"message": err.Error(),
|
|
"status": false,
|
|
})
|
|
}
|
|
|
|
return c.JSON(fiber.Map{
|
|
"code": http.StatusOK,
|
|
"message": "Success",
|
|
"status": true,
|
|
"details": data,
|
|
})
|
|
}
|
|
|
|
func (ctl *ProductController) GetLocationProducts(c *fiber.Ctx) error {
|
|
tenantID, _ := strconv.Atoi(c.Query("tenantid"))
|
|
locationID, _ := strconv.Atoi(c.Query("locationid"))
|
|
subcategoryID, _ := strconv.Atoi(c.Query("subcategoryid"))
|
|
keyword := c.Query("keyword")
|
|
pageno, _ := strconv.Atoi(c.Query("pageno"))
|
|
pagesize, _ := strconv.Atoi(c.Query("pagesize"))
|
|
|
|
result, err := ctl.productService.GetLocationProducts(tenantID, locationID, subcategoryID, pageno, pagesize, keyword)
|
|
if err != nil {
|
|
return c.JSON(fiber.Map{
|
|
"status": false,
|
|
"code": http.StatusInternalServerError,
|
|
"message": err.Error(),
|
|
})
|
|
}
|
|
|
|
return c.JSON(fiber.Map{
|
|
"status": true,
|
|
"code": http.StatusOK,
|
|
"message": "Success",
|
|
"details": result,
|
|
})
|
|
}
|
|
|
|
func (ctl *ProductController) GetLocationProductSummary(c *fiber.Ctx) error {
|
|
tenantID, _ := strconv.Atoi(c.Query("tenantid"))
|
|
locationID, _ := strconv.Atoi(c.Query("locationid"))
|
|
|
|
result, err := ctl.productService.GetLocationProductSummary(tenantID, locationID)
|
|
if err != nil {
|
|
return c.JSON(fiber.Map{
|
|
"status": false,
|
|
"code": http.StatusInternalServerError,
|
|
"message": err.Error(),
|
|
})
|
|
}
|
|
|
|
return c.JSON(fiber.Map{
|
|
"status": true,
|
|
"code": http.StatusOK,
|
|
"message": "Success",
|
|
"details": result,
|
|
})
|
|
}
|
|
|
|
func (ctl *ProductController) GetAllProducts(c *fiber.Ctx) error {
|
|
categoryID, _ := strconv.Atoi(c.Query("categoryid"))
|
|
subcategoryID, _ := strconv.Atoi(c.Query("subcategoryid"))
|
|
productID, _ := strconv.Atoi(c.Query("productid"))
|
|
applocationID, _ := strconv.Atoi(c.Query("applocationid"))
|
|
tenantID, _ := strconv.Atoi(c.Query("tenantid"))
|
|
locationID, _ := strconv.Atoi(c.Query("locationid"))
|
|
keyword := c.Query("keyword", "")
|
|
productStatus := c.Query("productstatus", "")
|
|
approve := c.Query("approve", "")
|
|
pageno, _ := strconv.Atoi(c.Query("pageno"))
|
|
pagesize, _ := strconv.Atoi(c.Query("pagesize"))
|
|
|
|
details, err := ctl.productService.FetchFilteredProducts(
|
|
categoryID, subcategoryID, productID, applocationID, tenantID,
|
|
locationID, keyword, productStatus, approve, pageno, pagesize,
|
|
)
|
|
if err != nil {
|
|
return c.JSON(fiber.Map{
|
|
"status": false,
|
|
"code": http.StatusConflict,
|
|
"message": err.Error(),
|
|
})
|
|
}
|
|
|
|
return c.JSON(fiber.Map{
|
|
"status": true,
|
|
"code": http.StatusOK,
|
|
"message": "Success",
|
|
"data": details,
|
|
})
|
|
}
|
|
|
|
func (ctl *ProductController) GetProductByVariant(c *fiber.Ctx) error {
|
|
|
|
tenantID, _ := strconv.Atoi(c.Query("tenantid"))
|
|
variantid, _ := strconv.Atoi(c.Query("variantid"))
|
|
locationID, _ := strconv.Atoi(c.Query("locationid"))
|
|
|
|
result, err := ctl.productService.GetProductByVariant(tenantID, variantid, locationID)
|
|
|
|
if err != nil {
|
|
|
|
return c.JSON(fiber.Map{
|
|
"code": http.StatusConflict,
|
|
"message": "failed",
|
|
"status": false,
|
|
})
|
|
|
|
}
|
|
|
|
return c.JSON(fiber.Map{
|
|
"code": http.StatusOK,
|
|
"message": "Success",
|
|
"status": true,
|
|
"details": result,
|
|
})
|
|
|
|
}
|
|
|
|
func (ctl *ProductController) GetProductsBySubcategory(c *fiber.Ctx) error {
|
|
categoryID, err := strconv.Atoi(c.Query("categoryid"))
|
|
if err != nil || categoryID == 0 {
|
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
|
"code": 400,
|
|
"status": false,
|
|
"message": "Valid categoryid is required",
|
|
"data": fiber.Map{},
|
|
})
|
|
}
|
|
|
|
params := models.ProductFilter{
|
|
CategoryID: categoryID,
|
|
TenantID: parseInt(c.Query("tenantid")),
|
|
AppLocationID: parseInt(c.Query("applocationid")),
|
|
ProductID: parseInt(c.Query("productid")),
|
|
Keyword: c.Query("keyword"),
|
|
LocationID: parseInt(c.Query("locationid")),
|
|
}
|
|
|
|
result, err := ctl.productService.GetProductsBySubcategory(params)
|
|
if err != nil {
|
|
return c.Status(http.StatusInternalServerError).JSON(fiber.Map{
|
|
"code": 500,
|
|
"status": false,
|
|
"message": err.Error(),
|
|
"data": fiber.Map{},
|
|
})
|
|
}
|
|
|
|
return c.JSON(fiber.Map{
|
|
"code": 200,
|
|
"status": true,
|
|
"message": "Success",
|
|
"data": result,
|
|
})
|
|
}
|
|
|
|
func parseInt(s string) int {
|
|
if s == "" {
|
|
return 0
|
|
}
|
|
val, _ := strconv.Atoi(s)
|
|
return val
|
|
}
|
|
|
|
func (ctl *ProductController) UpdateProductLocation(c *fiber.Ctx) error {
|
|
var data models.Productlocations
|
|
|
|
if err := c.BodyParser(&data); err != nil {
|
|
return c.Status(http.StatusBadRequest).JSON(fiber.Map{
|
|
"status": false,
|
|
"code": http.StatusBadRequest,
|
|
"message": "Invalid request body",
|
|
})
|
|
}
|
|
|
|
if err := ctl.productService.UpdateProductLocation(data); err != nil {
|
|
return c.Status(http.StatusConflict).JSON(fiber.Map{
|
|
"status": false,
|
|
"code": http.StatusConflict,
|
|
"message": err.Error(),
|
|
})
|
|
}
|
|
|
|
return c.Status(http.StatusAccepted).JSON(fiber.Map{
|
|
"status": true,
|
|
"code": http.StatusAccepted,
|
|
"message": "Product update successful",
|
|
})
|
|
}
|
|
|
|
func (ctl *ProductController) CreateProductLocation(c *fiber.Ctx) error {
|
|
var data []models.Productlocations
|
|
|
|
if err := c.BodyParser(&data); err != nil {
|
|
return c.Status(http.StatusBadRequest).JSON(fiber.Map{
|
|
"code": http.StatusBadRequest,
|
|
"message": "Invalid request body",
|
|
"status": false,
|
|
})
|
|
}
|
|
|
|
if err := ctl.productService.CreateProductLocation(data); err != nil {
|
|
return c.Status(http.StatusConflict).JSON(fiber.Map{
|
|
"status": false,
|
|
"code": http.StatusConflict,
|
|
"message": err.Error(),
|
|
})
|
|
}
|
|
|
|
return c.Status(http.StatusCreated).JSON(fiber.Map{
|
|
"status": true,
|
|
"code": http.StatusCreated,
|
|
"message": "Success",
|
|
})
|
|
}
|
|
|
|
func (ctl *ProductController) CreateProductVariant(c *fiber.Ctx) error {
|
|
var input models.Productvariant
|
|
|
|
// Parse request body
|
|
if err := c.BodyParser(&input); err != nil {
|
|
return c.Status(http.StatusBadRequest).JSON(fiber.Map{
|
|
"code": http.StatusBadRequest,
|
|
"message": "Invalid request body",
|
|
"status": false,
|
|
})
|
|
}
|
|
|
|
// Call service layer
|
|
if err := ctl.productService.CreateProductVariant(input); err != nil {
|
|
return c.Status(http.StatusConflict).JSON(fiber.Map{
|
|
"code": http.StatusConflict,
|
|
"message": err.Error(),
|
|
"status": false,
|
|
})
|
|
}
|
|
|
|
return c.Status(http.StatusOK).JSON(fiber.Map{
|
|
"code": http.StatusOK,
|
|
"message": "Success",
|
|
"status": true,
|
|
})
|
|
}
|
|
|
|
func (ctl *ProductController) ImportCatalogueProduct(c *fiber.Ctx) error {
|
|
var data []models.ImportCatalogueProductRequest
|
|
|
|
if err := c.BodyParser(&data); err != nil {
|
|
return c.Status(http.StatusBadRequest).JSON(fiber.Map{
|
|
"code": http.StatusBadRequest,
|
|
"message": "Invalid request body",
|
|
"status": false,
|
|
})
|
|
}
|
|
|
|
if len(data) == 0 {
|
|
return c.Status(http.StatusBadRequest).JSON(fiber.Map{
|
|
"code": http.StatusBadRequest,
|
|
"message": "Request body must contain at least one product",
|
|
"status": false,
|
|
})
|
|
}
|
|
|
|
for _, req := range data {
|
|
if req.Tenantid == 0 || req.Locationid == 0 || req.Brand == "" || req.Catalogueid == 0 || req.Categoryid == 0 {
|
|
return c.Status(http.StatusBadRequest).JSON(fiber.Map{
|
|
"code": http.StatusBadRequest,
|
|
"message": "tenantid, locationid, brand, catalogueid, and categoryid are required",
|
|
"status": false,
|
|
})
|
|
}
|
|
}
|
|
|
|
if err := ctl.productService.ImportCatalogueProduct(data); err != nil {
|
|
return c.Status(http.StatusConflict).JSON(fiber.Map{
|
|
"status": false,
|
|
"code": http.StatusConflict,
|
|
"message": err.Error(),
|
|
})
|
|
}
|
|
|
|
return c.Status(http.StatusCreated).JSON(fiber.Map{
|
|
"status": true,
|
|
"code": http.StatusCreated,
|
|
"message": "Success",
|
|
})
|
|
}
|
|
|
|
func (ctl *ProductController) GetImportedCatalogueProducts(c *fiber.Ctx) error {
|
|
tenantID, _ := strconv.Atoi(c.Query("tenantid"))
|
|
// brand is optional: omit it to check imported status across every
|
|
// brand at once, matching the all-brands catalogue browse view.
|
|
brand := c.Query("brand")
|
|
|
|
if tenantID == 0 {
|
|
return c.Status(http.StatusBadRequest).JSON(fiber.Map{
|
|
"code": http.StatusBadRequest,
|
|
"message": "tenantid is required",
|
|
"status": false,
|
|
})
|
|
}
|
|
|
|
refs, err := ctl.productService.GetImportedCatalogueRefs(tenantID, brand)
|
|
if err != nil {
|
|
return c.Status(http.StatusInternalServerError).JSON(fiber.Map{
|
|
"code": http.StatusInternalServerError,
|
|
"message": err.Error(),
|
|
"status": false,
|
|
})
|
|
}
|
|
|
|
return c.JSON(fiber.Map{
|
|
"code": http.StatusOK,
|
|
"message": "Success",
|
|
"status": true,
|
|
"details": refs,
|
|
})
|
|
}
|
|
|
|
func (ctl *ProductController) GetTenantCategories(c *fiber.Ctx) error {
|
|
tenantID, _ := strconv.Atoi(c.Query("tenantid"))
|
|
if tenantID == 0 {
|
|
return c.Status(http.StatusBadRequest).JSON(fiber.Map{
|
|
"code": http.StatusBadRequest,
|
|
"message": "tenantid is required",
|
|
"status": false,
|
|
})
|
|
}
|
|
|
|
categories, err := ctl.productService.GetTenantCategories(tenantID)
|
|
if err != nil {
|
|
return c.Status(http.StatusInternalServerError).JSON(fiber.Map{
|
|
"code": http.StatusInternalServerError,
|
|
"message": err.Error(),
|
|
"status": false,
|
|
})
|
|
}
|
|
|
|
return c.JSON(fiber.Map{
|
|
"code": http.StatusOK,
|
|
"message": "Success",
|
|
"status": true,
|
|
"details": categories,
|
|
})
|
|
}
|
|
|
|
func (ctl *ProductController) DeleteProductLocation(c *fiber.Ctx) error {
|
|
var input struct {
|
|
Tenantid int `json:"tenantid"`
|
|
Locationid int `json:"locationid"`
|
|
Productid int `json:"productid"`
|
|
}
|
|
|
|
if err := c.BodyParser(&input); err != nil {
|
|
return c.Status(http.StatusBadRequest).JSON(fiber.Map{
|
|
"code": http.StatusBadRequest,
|
|
"message": "Invalid request body",
|
|
"status": false,
|
|
})
|
|
}
|
|
|
|
if input.Tenantid == 0 || input.Locationid == 0 || input.Productid == 0 {
|
|
return c.Status(http.StatusBadRequest).JSON(fiber.Map{
|
|
"code": http.StatusBadRequest,
|
|
"message": "tenantid, locationid, and productid are required",
|
|
"status": false,
|
|
})
|
|
}
|
|
|
|
if err := ctl.productService.DeleteProductLocation(input.Tenantid, input.Locationid, input.Productid); err != nil {
|
|
return c.Status(http.StatusConflict).JSON(fiber.Map{
|
|
"code": http.StatusConflict,
|
|
"message": err.Error(),
|
|
"status": false,
|
|
})
|
|
}
|
|
|
|
return c.Status(http.StatusOK).JSON(fiber.Map{
|
|
"code": http.StatusOK,
|
|
"message": "Product location deleted successfully",
|
|
"status": true,
|
|
})
|
|
}
|