{
  "info": {
    "_postman_id": "4e479a2c-7d26-4d47-b47d-1638b938971a",
    "name": "Booking Window Hotel Markup (admin) v3",
    "description": "Hotel markup admin APIs.\n1. Markup group CRUD - table hotel_markup_group\n2. Markup CRUD - table hotel_markup (create takes hotel_markup_group_id)\n3. Agent assignment - website_users.hotel_markup_group_id\n\nAuth: Bearer token of a staff / admin user (agents get 403). Set base_url, staff_token, website_user_id. Run folder 2 before deleting the group in folder 1.\nChanges reach every server process within 60 seconds.",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "auth": {
    "type": "bearer",
    "bearer": [
      {
        "key": "token",
        "value": "{{staff_token}}",
        "type": "string"
      }
    ]
  },
  "variable": [
    {
      "key": "base_url",
      "value": "https://<booking-window-hotel-api-host>"
    },
    {
      "key": "staff_token",
      "value": "<staff JWT>"
    },
    {
      "key": "website_user_id",
      "value": "123"
    },
    {
      "key": "hotel_markup_group_id",
      "value": ""
    },
    {
      "key": "markup_id",
      "value": ""
    }
  ],
  "item": [
    {
      "name": "1. Markup group (hotel_markup_group) CRUD",
      "item": [
        {
          "name": "Create group",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"group_title\": \"B2B Silver\",\n  \"group_description\": \"Standard agents\",\n  \"is_default\": 0,\n  \"status\": 1\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{base_url}}/create_hotel_markup_group",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "create_hotel_markup_group"
              ]
            },
            "description": "Table: hotel_markup_group.\nRequired: group_title.\nis_default: 0/1 - 1 makes it the fallback group for agents with no group (clears the flag on every other group).\nstatus: 0 inactive, 1 active.\nSaves hotel_markup_group_id for the next requests."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                  "const j = pm.response.json();",
                  "pm.test('status true', () => pm.expect(j.status).to.eql(true));",
                  "if (j.data) pm.collectionVariables.set('hotel_markup_group_id', j.data.id);"
                ]
              }
            }
          ]
        },
        {
          "name": "List groups",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"page\": 1,\n  \"limit\": 20,\n  \"status\": \"\",\n  \"search\": \"\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{base_url}}/list_hotel_markup_groups",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "list_hotel_markup_groups"
              ]
            },
            "description": "Optional filters: status (0/1), search (group_title). Each row has markup_count and agent_count."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                  "const j = pm.response.json();",
                  "pm.test('status true', () => pm.expect(j.status).to.eql(true));"
                ]
              }
            }
          ]
        },
        {
          "name": "Get group",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"id\": \"{{hotel_markup_group_id}}\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{base_url}}/get_hotel_markup_group",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "get_hotel_markup_group"
              ]
            },
            "description": "One group with its markups."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                  "const j = pm.response.json();",
                  "pm.test('status true', () => pm.expect(j.status).to.eql(true));"
                ]
              }
            }
          ]
        },
        {
          "name": "Update group",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"id\": \"{{hotel_markup_group_id}}\",\n  \"group_title\": \"B2B Silver (updated)\",\n  \"group_description\": \"Standard agents\",\n  \"is_default\": 0,\n  \"status\": 1\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{base_url}}/update_hotel_markup_group",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "update_hotel_markup_group"
              ]
            },
            "description": "Required: id. Send only the fields to change."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                  "const j = pm.response.json();",
                  "pm.test('status true', () => pm.expect(j.status).to.eql(true));"
                ]
              }
            }
          ]
        },
        {
          "name": "Delete group",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"id\": \"{{hotel_markup_group_id}}\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{base_url}}/delete_hotel_markup_group",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "delete_hotel_markup_group"
              ]
            },
            "description": "Soft delete (status = 2). Agents in this group fall back to the default group. Run this last - later requests use the group."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                  "const j = pm.response.json();",
                  "pm.test('status true', () => pm.expect(j.status).to.eql(true));"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "2. Markup (hotel_markup) CRUD",
      "item": [
        {
          "name": "Create markup",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"hotel_markup_group_id\": \"{{hotel_markup_group_id}}\",\n  \"title\": \"Silver - Bangkok\",\n  \"hotel_ids\": \"101,205\",\n  \"user_ids\": \"\",\n  \"cities\": \"Bangkok,Pattaya\",\n  \"country\": \"Thailand\",\n  \"price_from\": 0,\n  \"price_to\": 99999999,\n  \"score\": 10,\n  \"booking_date_from\": \"2026-10-01\",\n  \"booking_date_to\": \"2026-12-31\",\n  \"travel_date_from\": \"2026-12-15\",\n  \"travel_date_to\": \"2027-01-05\",\n  \"markup_amount_percentage\": 12,\n  \"discount_amount_percentage\": 2,\n  \"status\": 1\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{base_url}}/create_hotel_markup",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "create_hotel_markup"
              ]
            },
            "description": "Table: hotel_markup. One call creates one markup in the group given by hotel_markup_group_id.\n\nRequired: hotel_markup_group_id, markup_amount_percentage.\nOptional (empty / null = any): title, hotel_ids, user_ids (website_users.id), cities (comma string or array), country (hotel's country, name or code), booking_date_from/to (booking date = today), travel_date_from/to (check-in), YYYY-MM-DD.\nprice_from / price_to: stay total in INR (default price_to 99999 is low for a stay - set it).\nscore: highest wins when several markups match.\nPrice = supplier price + markup% - discount%.\nSaves markup_id."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                  "const j = pm.response.json();",
                  "pm.test('status true', () => pm.expect(j.status).to.eql(true));",
                  "if (j.data) pm.collectionVariables.set('markup_id', j.data.id);"
                ]
              }
            }
          ]
        },
        {
          "name": "List markups",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"hotel_markup_group_id\": \"{{hotel_markup_group_id}}\",\n  \"page\": 1,\n  \"limit\": 20,\n  \"status\": \"\",\n  \"search\": \"\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{base_url}}/list_hotel_markups",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "list_hotel_markups"
              ]
            },
            "description": "Optional filters: hotel_markup_group_id, status (0/1), search (title)."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                  "const j = pm.response.json();",
                  "pm.test('status true', () => pm.expect(j.status).to.eql(true));"
                ]
              }
            }
          ]
        },
        {
          "name": "Get markup",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"id\": \"{{markup_id}}\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{base_url}}/get_hotel_markup",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "get_hotel_markup"
              ]
            },
            "description": "One markup."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                  "const j = pm.response.json();",
                  "pm.test('status true', () => pm.expect(j.status).to.eql(true));"
                ]
              }
            }
          ]
        },
        {
          "name": "Update markup",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"id\": \"{{markup_id}}\",\n  \"markup_amount_percentage\": 10,\n  \"discount_amount_percentage\": 1,\n  \"cities\": \"Bangkok\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{base_url}}/update_hotel_markup",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "update_hotel_markup"
              ]
            },
            "description": "Required: id. Send only the fields to change (any Create markup field, including hotel_markup_group_id to move it). null or \"\" clears a list / date (= any)."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                  "const j = pm.response.json();",
                  "pm.test('status true', () => pm.expect(j.status).to.eql(true));"
                ]
              }
            }
          ]
        },
        {
          "name": "Delete markup",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"id\": \"{{markup_id}}\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{base_url}}/delete_hotel_markup",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "delete_hotel_markup"
              ]
            },
            "description": "Soft delete (status = 2)."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                  "const j = pm.response.json();",
                  "pm.test('status true', () => pm.expect(j.status).to.eql(true));"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "3. Agent assignment",
      "item": [
        {
          "name": "Assign group to agent",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"website_user_id\": \"{{website_user_id}}\",\n  \"hotel_markup_group_id\": \"{{hotel_markup_group_id}}\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{base_url}}/assign_hotel_markup_group",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "assign_hotel_markup_group"
              ]
            },
            "description": "One agent, one group (website_users.hotel_markup_group_id). Assigning again replaces the agent's current group; response shows previous_hotel_markup_group_id.\nRequired: website_user_id (single id), hotel_markup_group_id (an active group, or null to unassign - the agent then uses the default group)."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                  "const j = pm.response.json();",
                  "pm.test('status true', () => pm.expect(j.status).to.eql(true));"
                ]
              }
            }
          ]
        },
        {
          "name": "Get agent's group",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"website_user_id\": \"{{website_user_id}}\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{base_url}}/get_agent_hotel_markup_group",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "get_agent_hotel_markup_group"
              ]
            },
            "description": "effective_group_id = the group pricing uses (assigned if active, else default)."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                  "const j = pm.response.json();",
                  "pm.test('status true', () => pm.expect(j.status).to.eql(true));"
                ]
              }
            }
          ]
        },
        {
          "name": "Preview markup",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"agent_id\": \"{{website_user_id}}\",\n  \"hotel_id\": \"101\",\n  \"city\": \"Bangkok\",\n  \"country\": \"Thailand\",\n  \"checkin\": \"2026-12-20\",\n  \"amount\": 25000,\n  \"currency\": \"THB\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{base_url}}/preview_hotel_markup",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "preview_hotel_markup"
              ]
            },
            "description": "Dry run: which markup applies to this agent / hotel / price, and the amounts. Nothing is saved."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                  "const j = pm.response.json();",
                  "pm.test('status true', () => pm.expect(j.status).to.eql(true));"
                ]
              }
            }
          ]
        }
      ]
    }
  ]
}
