diff --git a/api/api.gen.go b/api/api.gen.go index a6bbd97c6..3e0510dfd 100644 --- a/api/api.gen.go +++ b/api/api.gen.go @@ -243,16 +243,19 @@ type EntitlementGrant struct { // Id Readonly unique ULID identifier. Id *string `json:"id,omitempty"` - // MaxRolloverAmount The maximum amount of the grant that can be rolled over. Defaults to 0. + // MaxRolloverAmount Grants are rolled over at reset, after which they can have a different balance compared to what they had before the reset. // - // - maxAmount = {original_amount} -> rollover original amount - // - maxAmount = 0 -> no rollover - // - maxAmount = 90 -> rollover 90 max - // - // If it's larger than 0 then the grant's balance will be the MAX(maxRollover, balance) + amount. + // Balance after the reset is calculated as: + // Balance_After_Reset = MIN(MaxRolloverAmount, MAX(Balance_Before_Reset, MinRolloverAmount)) MaxRolloverAmount *float64 `json:"maxRolloverAmount,omitempty"` Metadata *map[string]string `json:"metadata,omitempty"` + // MinRolloverAmount Grants are rolled over at reset, after which they can have a different balance compared to what they had before the reset. + // + // Balance after the reset is calculated as: + // Balance_After_Reset = MIN(MaxRolloverAmount, MAX(Balance_Before_Reset, MinRolloverAmount)) + MinRolloverAmount *float64 `json:"minRolloverAmount,omitempty"` + // NextRecurrence The next time the grant will recurr. NextRecurrence *time.Time `json:"nextRecurrence,omitempty"` @@ -283,16 +286,19 @@ type EntitlementGrantCreateInput struct { EffectiveAt time.Time `json:"effectiveAt"` Expiration ExpirationPeriod `json:"expiration"` - // MaxRolloverAmount The maximum amount of the grant that can be rolled over. Defaults to 0. + // MaxRolloverAmount Grants are rolled over at reset, after which they can have a different balance compared to what they had before the reset. // - // - maxAmount = {original_amount} -> rollover original amount - // - maxAmount = 0 -> no rollover - // - maxAmount = 90 -> rollover 90 max - // - // If it's larger than 0 then the grant's balance will be the MAX(maxRollover, balance) + amount. + // Balance after the reset is calculated as: + // Balance_After_Reset = MIN(MaxRolloverAmount, MAX(Balance_Before_Reset, MinRolloverAmount)) MaxRolloverAmount *float64 `json:"maxRolloverAmount,omitempty"` Metadata *map[string]string `json:"metadata,omitempty"` + // MinRolloverAmount Grants are rolled over at reset, after which they can have a different balance compared to what they had before the reset. + // + // Balance after the reset is calculated as: + // Balance_After_Reset = MIN(MaxRolloverAmount, MAX(Balance_Before_Reset, MinRolloverAmount)) + MinRolloverAmount *float64 `json:"minRolloverAmount,omitempty"` + // Priority The priority of the grant. Grants with higher priority are applied first. // Priority is a positive decimal numbers. With lower numbers indicating higher importance. // For example, a priority of 1 is more urgent than a priority of 2. @@ -3146,174 +3152,174 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "H4sIAAAAAAAC/+x9+3LbttL4q2D4OzNNWkmWZTuNPdM5o/qSuomd1JckbexfDkRCEmsKUADSsuLxH99b", - "fM93nuQbLAASJEGJkuXEbdPpnFOLuCwWi8XuYi+3ns9GY0YJjYW3c+uNMccjEhMOfxEah3FERoTGh4H8", - "ISDC5+E4Dhn1drwuSmj4KSHo/NXhHuozjjBFVp+W1/BC2XCM46HX8CgeEW+nMGrD4+RTEnISeDsxT0jD", - "E/6QjDDMf4NH40j2aa93T/7YON7bf3l2+nbz5OTg4Ldn2y+2DrpvvYYXT8eyjYh5SAfe3V0jP8VrfkBw", - "nHDykkzLizgbEhQGiPVRPCQ29Ihx+OmKTM3Xvhqnzrpyk953jRKUwTje/BizK0KFc8katvn7FAYS0H5I", - "uNqyOavKxn2QnRpwPIe4SvBClwpozXAPAmtI/SgJyB6JSEwcIB+q7yhQDSQx8ZCIFNRPCeHTDNbCcDaI", - "AenjJIq9nT6OBGlkIKu1aNB6jEUEU4ANDq0ku9MoGdTHpqRq6FqBz/yws7D6L0763o73/9YyhrKmvoq1", - "dAAJKWDhIIxiwl9wlox/hkPpQlA/18ieDAdBKFeGozecjQmPQwIMq7BnjQIWTkOJRaTGhfUP5OCoNxVo", - "EsZDRG6wH6MRjv1h64Je0HOBB2QH/effOVA+yGkufwrpOIkvkna78yz/ecQCEl3+NBjHzc3/XFDP2r9b", - "Dz5KSpVfMzKDwSTE+m/W+5P48IOIp0CsASHj1+mvFhZPE/VradPV55AOUG+KRkkUh3L1QjUX9gL/rX/8", - "yU9EzEaEN9fVwkq/d2A9rt3STXP7FMZk5N4Y/QPmHE8tsuBsVF7HaYx5jAIck2YcjggKKTo52EUbGxvb", - "chNHOG5dUDh8IrwmrUoI+3J0NyfotDsbzfZ6s71+1m7vwL9/eA1PjS6RbyZ3MgaYxyLmAlvoI8piJMbE", - "lwcvQBiJkA4igvBgwMkAxwRNwihCPYI4iRNOSQDESbA/NNuFMA0QrH4S0oBNWhf0P/rTf1AoEEacCMKv", - "SZDSNLrGUTIDHQPHwUox8kETql7uZWPhvXwVjkIHUR4nox7h8kbV7BHFTC+7As4IBnKyx/V2u22drnX5", - "1wjfhKNkZD6OQqr/tM5aTAaEZ6C+7vcFqQuruArHFZAyNY4TVBtOG6x2NVhnrAzSPg1WcBJiNu8cdJY+", - "B++APk/Dz2T+UWhkZyGRnGjeiTAiGidxKpRl52pMeMgqjg4QezVCJhnQdS81a52FtZ+FI/IHo8QtbMKp", - "lEdWAi+nNwuBHf3MKEFYoID0Q7nqkMK3w+5xF8lxkRwY7eEY97Ag6Mkwjsc7a2uTyaQVYopbjA/W5EBN", - "OZB4KsmhhHM54PnZLkwI8xlcJ4IE83CULs5J5d752a5933ndEeGhj9eOyeTj74xfOelGb5QUE5wyerUs", - "qHtWSC+FcesJhelVFwYOYO/kIGLMqFASx884OCGfEiLiN5z1IjI60V/lR5/RmFBgLHg8jkIfywWtjVXL", - "H/4UcnW3tnQQkBiHUjwYEhwQjnbVCM2z6ZigIRYooeRmTHwpXipCusgNfTOKLjy5NTGOE+HtbEoWCGqJ", - "tyNhRRrYbGUJpzsaoKb8aaeHgybXre7qHga9eIWg/ObZs941vF1G+1HorwZdvh4spIN9GnMlSQYg2f/y", - "/u1R+7S9e/THr6e/dTZebB+9fH/y25sfPZCVcYBjWJTc2DF5g6dGyfXG4cfN17x7NXx1PQ2HIdseb60P", - "t8PwgP7sZbSaUVdzXQmaeuMiEgwIR+QmFLHI7cR2thO6EY44wcE0a1y1J7pB7e0oINm1La8UCPvpyMcs", - "PmAJDVZLx6BAAAPqy8FzCNnMEHLMYnSgG1QhgbK4qQZZBVlmM6q1H0rQJRGQFWNAK9CAgzCbxMLEVns9", - "j4nDXLNZ+LAHXBVWDvNjnlOcxEPGw8+rxswoFPLyR4yjkF7jKAwQGDhyRGKhxoZkBl4Su9kqkHJeGPA8", - "5cGrxYfF2wnnjOdIpG3jIW23r9tV48I0XREmChDepaPq2zDC1Ce/hCJmRhpyXedKlJCCT0/1QEPVRUkf", - "45xSr5t0Y9AD3UKVbbczQ+JYyYegPWoZS4mIrbxSDmpDJuKypBcREBZw8JpG04LVhYJSINGpBpuLTtXq", - "ruGBiOuGP2YxjrQMnDc2GglwhZC7rAzFG6PSyKJGzK/AdM5bOvKXcxhPD9Ob+Xjv15Otjc7+8xdnP789", - "3e28f7m1t+mVb9EnWrBsVfd6at+isYhBMDSSf3ZFS/lQxJIu9DHRIs5OxHwcrf169DryY/Hy7fNmW/6z", - "XnVzW0s1am+PJfFOL8L0CogiR7tmQeUjMExGmDYl5LgXEURuxhGmwCuMqO5LbTMehgIx3084J5KoDR2r", - "bWrlZO0eC6ZolIhYyvIY/Xr6+hgxY5Qpae3kJiZUhIyK6s3W5sf8mpz7OtusbnVRR3Wah3xBQ2wmxn9w", - "QnPpIPBs+8t7cX5yiDjpE4XieIjjTNMQtpLp192KehSWntyEh64tMhToQu4vZ2dvkGqAfBYQNCCUcCxZ", - "c28KgDEeDkKKwCpkrL21iWkzx1lCGm90PMu2srW9bdkwlJZRsGKkZ6aMb4zEkPG4UTwEIhmNMJ8W4EJy", - "5Dx6nZpM0SBVwprUoeT1jEMqEIZdd+119bQzdaV521mgW/hqcJRudcoCXSS8n11ysLpQrm4UUhwzDqZl", - "PB7LqeSdqZ8GKu4ka6SfdUv9hCCBm9vnSLdUYId+jS6nqmHGIKfH2hQlV3nX8Bglr/vezofZl6kDiLtG", - "7S4pELV7GOzcXebxb36XnDOKFgNc993lBMfkkI4T0L/qL2KIOQkOQhIFwru7bJQMhNBSZMYReY0kglgv", - "fSBEzCIvF4jLLFUNkIe4cbssxGPHW8+tR6hkQR9Skr/MX4mGumucxvKZK+x6ER8rPYG5wZc4jYX+C57M", - "AjWu+pQuTewu4O530px7miPRndsCqc14Wz+zJHUQFDQFh8LoJIGm5oLovpDMk7dZLfn62U27ITNaalSf", - "zRlAL3lTS9s5IVK6COlANbdQXzpzGVbnXHYvONYK9aL8B3rmYJhDPIoOjkiMLXaVJ4Y5TjGSILS12lZK", - "wfUCpA2JbnBTgGc7IZgfgtQ2CePhQjRSoeXZEv445ER0q3Rm+VlpHAGOU2lQuVcUAFnkfXQuXJTcxIpO", - "iFMgl8DJNupFJMMXPFNw6Hgf+Erw8BwsC1C37HzNwoAEVTgGxKZPt9ZSsECqJ3riY0pZbN5fEO7HhAOp", - "PG0tieLCQctT7NxbrnRoSiuDFgJhrkGOGQqpzwkWJLW+sH6mNFkuB6VrHI9YQiuwp77J4RVRotMhS6JA", - "qbdjJsI4vCZImTXqWUZKNhzS7xNfDlN5SkwD2MIWesPZNeyben4xT2c+CTWrh4tbKrrZmyJ6MgppEpOn", - "KyXb7PwuwBvTPoaC5eaP8M0JiyJ2TXjX2o7s5bqMFq0Jmi2yeYdicz6m8HzLIokXOXQL7akh4Rm9Df4n", - "TTmQmhP9hG6Vyoqjj2rYO9S8SNrtDTWMHAOZFnri4gjttAdlaadio+12edzttmwiQTrsozD+TqAI84E6", - "iBS15epotsTvRErnhgDkt6Pu+ycWLhum0VP0g4Z3SSpd6va3THELv3KVruMxDxkP42ne/cJFGaZl/j5B", - "mmeAm9UwHAwJz1pKRgLGchKgfsiFRNMb8xGcW9LDHhA/HOFIH3rRQu/kgBGbEG5+QyENwOxOB2amcDRm", - "HMw/rQt6wDjSuGnIoS141+VsIyb5Gh8QRcq00KbTuqDvNDVIuDlBglwTjiO1VIHwNQ4jMGgY8UrgUSoa", - "qvd3MRUxGSFBIskYc5dcPIQ/AXQRp3ODZwfysSACTWBqPZ2QMks2TQprRK5J1LCG9iMm5IiSXcfCvv9t", - "B4B0Bw4VwcsZYS8nzMw4xNfmLd7HkZkxlCocDYpyhcgtGGaSul2BXcgL0Yc7JwMgd1Is41Nna2u2X889", - "rvRZAqu+qvKXRo4PzxFkjcliGVH2noqU6Y4jP4kkli359hEZHqrALF3NWQvUhybaIUSrzUU39IIFG2gj", - "Pq+v1mTyXoRFfEKc7mKS+8nPmbCnXr8lbXPZZYXXf4E0M6gartXVo8svY+45Ku+Q5mfY94kQkgUHss0I", - "HKB6WPIkRlMtG5RRlyQZilPWjy23w7wPdckTTZjWP0khOqe9S+HFMCkzL7kmFIXlMAGp6d8McSJi4zxV", - "dM9ueKE4p+DGaNzG54CWmNb1QJM/mR45QcMFiUhIV6oYFUT8O0tgDmhovPKSmI0wMPtoqhUURdDobBgK", - "3SoU4ONgS3wARA35pmjLM8avnC1vlFqdS6LOwxgotIk+mXmQCspT0ZBU7ww5zQ73O3Sr43ia8mZEz6RO", - "eVdkaowf8HaZkm2O7WVO5qvdy9IGWpDP3zhtL12C7d3PSPklrlUHhA/G39VcORY5YNotl5Ox5BsUZHPD", - "vXxG++EgUSKUi7GrBm7ig0fuMeYC5G3V0hHMVbx5by+8KzJdv/B20IUHmvz6hXc36y3RsCZtV89xJv1b", - "zSc/vZy5FPlWglU2Rmud0o0Oy/pSKQvd2++l1n4Qa0P03mqTCcAmSlTSqrdBc62LQyy6IEiUAXw3JFJp", - "y92nQyyM4KFZlhUPV4i7qpjbul7lrVfp5KM/1tuc5bamysWornsR0jFswgArckBtLeluZJ+CbH+cUum1", - "cZ6b5ZBS0AUilgTQUaBTbXNUBAfUeKq83nIGEWNMyUVjNeOE95g8xirMcsdb72y4YrTAxXjLX2/3cUCa", - "6/42aW4Gz/zm886PW01/q+NvPPtxYz3YkCxBsISD05Eg/Dr0SRN80xueGBP/mnChlrDeanu2b3HBCT0c", - "FdWF9R34t9Vur/+RQTjmbDSOHW5IM21HLpcuMH1KlKIxnkYMB60ZYWwViHPZkSQk2kXS7aWhPd7B+8LQ", - "KjxVqWgmdJSIGOEAFIaYQfhLp735zIS/SCg1p7bdL8HtMsezS1/BpPCK0EE8BKMCTSIw4lQyGgmVHQKR", - "k+EL7kPQTFk6YDFqAZLltMqOIwvCEQZz54edzDPYmuSbh6XsoaSpe878sOM3sWQ2k2HoDyFQHKhriMdj", - "QkmevIpnxcZPM3XUqgGdfcacsTjqo6Ezm5GIHCNRUKeolPJXnjPqEzwPoKpQzT34q2fIRd9NRo6BKTWj", - "NqjMfRtzFiQ+4ehJKoyDG5janqctt/gNvGUOxIr1lHAXjoiI8WgswZgY07jlWZZuq+u8bmxsbLcqDSEF", - "zuY0hix4QtycJo9zw28UQjlRtiUtEuhXBxBbs1Xm16B57zwhEJCuj02eQhtGQDRUUrofG95Nc8Ca+kd1", - "qNWFaX1pKmO3yuYgMeQNwniY9Fo+G635ksKho1gTwVVzwNauO2vwA0Baeh8qByBm1l0dbwfijPVqXBTe", - "K5/3SGkoaIyi8Iqg9Q4aMRoPRdEQ3HHZe4MkewmrM5Fpr+aCifQ8+vL45fX5idfw9rq/ew3v3f7+S6/h", - "Hb0+PvvFa3i/73dPrM2p2OgUpIbGgUvc0Uki8tpY4YGU+8Pw2v3EfFiQ5QQyrRso1A/m9tuyCnkHGz7j", - "tiXtQV76Hff/bFVTY2MhTbpsOimp0N0cgtI/GEf6nkGs3wdhPGYII8Mf809FO2j3zXnzF5Zw0UBnIOc0", - "UPfNIdrFUSQaiMR+hR7uWpXDKfZLwpinsKsqy4425ZQzoKiYbeMwHcaoz9kITVnCkSVXFUlrZgqTr+zi", - "BOqYDuFXeRPEPeB4Pc6gIFmWCZ15QrTQuSD9JDLmZNVK+GwM29/jDII/4fFxImXGFO0c+1dFjljMKXFX", - "tTx3apCzFIDUPckmxbyDEjzwBUT5egjDS3rTlJcc9pE28ahnP6fOqX0DtApcJBNFIR8hJMZFJyq41+k0", - "ZEVT57bbikc+1Adj7kV9RdLYZxfnhvfsnxNO99iE6oCnUzJInbMLp1uoT3lHiV7CKQrYhFrhTxe0iwbh", - "NaFpl9RoJvIYxcaNjCKc+drYcUIVcVT7NFg4iorQ4EFiqB5tcBfgVseziRRnVfygxhtHtbGuxwm+AjKQ", - "8ieW6pHyOFL7W73UHeVVcbi3gzSkRc4ww4FQYuauEhEZoRcQke7TPxUV8IbiNC5KFVFzxIl6z4wTeDGT", - "5zxWBz1nbgNu2psqHm+/26W5VmZJPi9SaE6Iz3jgVS9Bp2aZYZ98fU24elZUzhj0uxj5srVSJNWyCker", - "s/W1wyZzt0vKDb9AuGQJ9yUzfWVis3xknNPbdjHX8IXws5QDmgsDC+Uaa8264LOYgkKw8avDPfTknIZS", - "QYZzdK7GfUVuQp8NOB4P9ZP0KeMxvDekdif+tIDRF8+2/vhxa6t78K778pf99c7x7+3d37YPpD43xnFM", - "uJzy/39oN7e7P+/u7R+8+OXXl0fHb347OT17++79739c3nae3f3Lgf/b6pWN8I0xUzzbKFot7Flx83O7", - "uX35w5N/73xM/3j6vWO6SwcBHNIBETEJlrGddyVLUt216QPEPGYEWAjLV6oyRHwXzMDETLmIQX0BC3rw", - "9Szo2cpVkH0pl4VKNKRMJ0VZK8XLzJdV07c0lU1TJjUC0YaeBX2/oZdLgAU/HNfpVcsENTT/fKfM15CY", - "SKAhm4C3M459pRVkqZuUfalAKeazfiQ+P/JKpHio0j8oriV7y3HOcvvSSLOWWbT2r1YuV5n8wcQrCeBQ", - "xXMJTwn6aExVe6MTWImgdryjw+Pzs/3y7ubWMnuPActdq31R/Cnj3/rbsO40RWNZkUHnGltzraAWOm8r", - "X3vADBqzbDfrGTlz+1IlGWbDlHasSpE+xiMSwDPeGwz5GaUqBEHrEkhyIxViE1JjZ70TyiKRWcglf2qh", - "l2Qq0gh5fUtJ2vUZFaGIEaPRFOFoPMQ0gcRV8DWhAeHCZ1JzHWI5o9Th3Sr4DFosh6QHSybqrHmvzX0q", - "FzPv71J4diVIysh/XyR+gduzdPSLa3dQWpHQlBNDSl752/M7od73TPhk64Ke2Y10Z8bR6flRA3Xfvmig", - "o8PjBqDoqPseWaxFKB5MdRJCSHcG68gFWICvhbYKppEwB4yj8+PD3873P+6+Pj8+s4dt5MFWEGVZI9QU", - "LSSHKPXNEGBQKGEMB5Q57DgWVy1twySXJHCBNHs59y6Vk9bmxjaXy80y5y0FDq1oqTux7mMKGxOqon1Y", - "9t9r46vBmhoOAC5dAO5IJxvHmgVLdax89PX7hLpAYYO8hmfvtdfwum9feA15e8n/7b7PP3yrnvkdqUZG", - "N4fcVePlt4Tw6QkR4AzrwguHb5bHDIKkhC2XN8eHW5dwUBBEi1JelcB4rZyv1juGisAAVJUfUxOaMo24", - "n1Ck5A4JaCufWGI2d4LZYomRwbMkro8eI7UMHRapsEk54azB6+2qAutitrKkqCvidLCzlUJ8ihrHVc7Z", - "xJhE6p2lx0wwjZKNZa7MOSsqrub6KgRrK0N3lVfHWdmXQyFe+TLN8MqYK8xfG+dQ18N4ZmuzEL9qijZ2", - "4IcIZ1Gry09lL2aB2zw9HCu9vaq8I7oq9kdZHx1vMH1ngnM1mjZrqyTfyl8mi4GeYGFCJFvosG+eWXsR", - "WWlMccwqoSNpAvKvAlsxeYTK5R4zJ1t8w3iMI9CPXXskNRWpmiCICo2U2aFor4giNgHHAvVwKsCD29ZQ", - "LhuejljsxtVrU0kYAquUQ5qUoeoY6gy2MzS7SjOXWrHHRh/V0j4edunexpvxu3edbucdfz7a/rP/mfwS", - "vXj//Ga0+37yojXd+rR52uy++3SQPPv0Zx8ffG5//u3T5v7nzvMTQadvJ7/2+++3Pt0cXTOHIaSMpNuK", - "p/AGCvtpVmtQEfPJu1XVmTQSVY9s70kZ/dUp6UchPVQf1wviQsNT2q3+LNnrXW4jbx8q7UVKCbc1HMlz", - "uTuWY9xzAQqDYjmUexkTsluwZjLtlF7dGTGvCJXaJdBKmgVeKr5yr0KWP7APRfKL2lsrffckV1omt2YX", - "6W5oDzLDCZ3SET05OdhFPz5v//i0dUEtx5fshBYSSeYz26ERnoL9QPkWFDUqk4hzZk7J1WUZ/8tkzfyW", - "RvJbGsm/chpJp6B8Cr0Me1qpoFwM0SyhM21guxPTcuxeQfCg/pC5Xq8owrwXxlzuv2ok+QHUylA6cHE2", - "WgxSniUeS6rk1zhaMDB1nyajsiO4GathVuO6M2aEKz9KVOpqI2OTJQny5gRW3h/KJijmCfVt//ohS/hX", - "34ca6IcBSph6Ba8e4PjYNBKuwonImepTF+wMdarKFsQHBUkU0gF4AO51f99B8M/+NeFTFODpBX23v/9y", - "x/pxQsjVBQWf9J3sV3Blv6C/73dP7MZTgnnOv30Rx/aGV3Ky3nFkddDutnY2Dk7U4716BhHEfIWEP7mk", - "AmM21uk89LWj88XYVX9ApplIyYVWpIDIifF1srEZAO38M62Hy3ynKwMuCZ/u/YDwhU4GjQOQwd1FJFeb", - "sjAZB/fYQEi+oodoPWBUQymeJ6M7ewn2fruYi1W4bxGPHTvHKK6sjJQI9WjoSLeepoMwTC0zy3aKGMq1", - "nGmdbXhBKMYRNiljd7Xqh46Vu858qwbEIRTD06yggGHSE2Om8petdzY2t54p6ZaHY2Jmg49+Ij5mgnJZ", - "vygvv6xjd2rRylw7rQt/y1oY5kek2htgz1Lci5oxpasyDegAk9pmgfmRIGoii6LdtDHfdF8ingKYNh3N", - "xZsjgsB17mdVyLPeW02VFBF+JvbTr37+a9gxarkX3rRBjUfed3bhuxXK/WpcEuQrwri5+kS3rVUMJuGy", - "MZ05YEV0hcs3uL6ndUWsh+M50qxn2UW/zixeAnE2UdKU6iAy8ztXl54VxVdrMc4aPa4qngW6ladFyq1h", - "PIWMCmo/IGR6l7GrkHQTSSm3ruQMEIQ2IT2Ex2PkQ2tTsS/9S9fs+/hRKL8bq8LKOHxJALMwmGXcN1P2", - "COaEHximycb4E7wfuUBxGv9NNUMwCMFg2fTDOB6nky89rcRA7anmL/HPSew5yvCUVyaJXp7SppaTsmjj", - "OVDc6RoxnOJoj/kOmX+P+Yk8A8Y7JOGR7i121jLW0ArZWiAHAMWtz1xvBIQeWV6ngDCKIfBDubCnSXW1", - "D7D2q8o6SvTCm4FAU5aojMQDImLtjNpQb936+RXGVD5XSpdIxUjRuqDNZvOCfv96TLh2w0pTcv73f/8H", - "PQHonkotV3Fpqcsol7c07WdILchg+1vfwwGNQp/oOmGa3Ltj7A8J6oD3coZAXdITw1co6qm7irVXh7v7", - "x6f7zU6r3RrGo8gykXk5fHgNL+ce3WpDBMiYUDwOvR1vo9VubSj39yHs7hoeh2vX62uWwUA9s7vyxIHS", - "a7eUVxQzSIN7WDbZt8eSc3GsXnoq805lTdasOsJVYcDF1rqUbzkt1WseEK5iMEkUVBXwlY0KJZGz2qa2", - "lG8uY7fk7wgOvywUDu202zPKxZkycY5S2jVzdTlYerkGpDZc5LfxruFttterpkoXsTarMCBMpfE2f5yq", - "cnqqNiwYc10UJzkWVg+zNpmhJ/s38ooH1hQ99S7lMCltX8+n6rxLpzr/EGQJWifH1Ny3DnK/Xp7Qofh5", - "XTo/Yw4at+pVK8jvV1p7VmXtOYW170vttgvbUjEmf8GkTRBKtOBapaZjhbMnsb3Y7fVOsB48/7HZ3sZB", - "c7Pn+0289WPQ3OptbG11Nrc3SNB56MV2qhZb1/kvH9y0CFNTR0CKzgHpJYNBSAeaubXnM6Xqus6Pmj0W", - "WJfNIdUPl3cNb8yEK5cIdBVZaibGUQ+ie2xkShFLuaDPSJfk4o5q+JQ/6ueun1kwncEYrHQ1P5SZRI3Y", - "qrtG1XhNWNsPs1nP3z1N3N+fz9RlM7XZSxYq5Tgfqdqh9RNN5ajgY2AfHMvfAGI8pELBMTxeVzQTevCq", - "UUb5jFvFk1qZIVGd2nyJ/LvSPb5Z5hynCeRz7CdRNE050N+Z1R7aqqWLx1oCp85DMkfkNK2q5MoDM8pf", - "XIWCQ2x0pxlPJ67sWuULC1KVpimvMiRWgBaqDl3d3g2ifnMpeuR9Gf3NpANbQMzJFv2oBZN+RsD1dLYq", - "MUV5QIhi9uv8kVGNDDbrShuLSRjOZGXu+8LKmqXIvVWD0a6vGlIXdAcmI5N5+n40THurzhjHLD6Uwpok", - "my9DzGq7zY4uY4EwJ2HtNi2AeKfIPCKxM02l/N3KCdeboisydZG9apqR/WJXRVaP0cHsHNe+oZ3ULWFF", - "O7+pJpu78wcsoV9kz/UOLLrnDfd9/4LE9laGgWsnX5D4Qbax/SWZCmTJ+PuShbWRy/ABlVJptliIoyiX", - "eqksGKqyan91sdD2mVlaOpyzCC39qcO8gsOxqEFeVbFdQLLTG/+45bqBob8l6X/tVifKmnkHvmVhgJ6o", - "r0/TbMOoawrbYaqe4HrpbYTCPgqhugP9TmrdkFaGOHmtHFxtzqKnyOT4qndfwhxQbwLAUeVfH5HEdV8G", - "u9nent9/l9F+FPrxF7y3NbksQ6OGFGbxaNXG/fp5pPqv8DHk66RRqhGRuJJESwtlBFiEl5pNetS8dGSo", - "xdCpJp9qZfj77hyfg+91EU+HB0jDhFziTP+zigxYeb/6YUTkYBe6kr7JH1CteZs8Kg+hd+u9d2vaOpHx", - "qvXsr57H7EsdwMZ9N2H3myGh3on/7//+D9LHaaRPS+nYl26itVv4f5N7c6bMNJc3XND0glTHpjdFh3uQ", - "cj5KBtX2BXO4F5OVcoDXlJgUwHn7wv1klEdMDHozKolhhjnBsX0ui8IDbVz7GwNdIQNVNG+bTh6h2WN5", - "jqVsCZUS9W+Wl2Y/LVFSImZothJybjycb1adpnbOp4W6nIUj8gej9bupohYm5GixXrouRu1eaft7M4t/", - "bGa3BbiInUQP/PbJTbzmi+sKJVLP+BGyDTX0H4QGDY2wBuC3IfHZAFxdUNeyGoUf1+FHg+qP6w1rexrg", - "EtNY75SGWs8PpVDTmT9Up10aquMaaiM/VCc3lHJjaWw6guxLbBmy7Kossn9jxwaL/S7H3TUNzTGZmFZ2", - "zf4ZBpRTM+hXEV1cdpgCS8noa3ZWorrWCoOfFZHaA1orUlDnkIuKSMlTzTLyQCF4R9efJzQYs1DV4h8n", - "vSj0oykiN2OmqkLHLO0nKmQJFXBTIVEskaEZnkAgdC59AclKMBVtEg0nm55dBumfJrh8YRHk2/X77fr9", - "YtevjqoEXlOK/PtwKUneHfj44VKSd/H61tF/xVtc9XayZePmW8GEa1iS4EZwRlZWvlxbqxHzeG4W2JIb", - "OotvaaE9tRnyCuhstRaPd+lsFSobfcFol1pvHnZOywVkifxefDPL1rDEAepyeHMdo9kOiqp4ijvLqOvF", - "xN7dRd9NFs1a6taMayudOUJ0P8PkApAXeo1Z3eU8B8zH91DxUL6KhTj32vfBmq48hNXrwpIvkBf0MB1G", - "VFwRlulakaY7gMgMU7g5ljosQG7ZkAhHkSskyv5eZKVZEu47KMKVtoXVzB4tf0Kggz3gXHN1ecbKJOSz", - "FqEVcIN1G4IKJnGXYxP53Byu7FE1Fl2/Sv5iS5lbjbrMFCB7HVTn43aWpTRLcQPm1ROaVMXZMQlaywXQ", - "vMktJDfet/t6/n1tkcXcW9tidnNtVXXl3tRcUyHpWvarhxcTMwv/suamR3iTpVKZw9yUYvcebjIX9Hws", - "CI+FxUGQiTRhXOeUE9b1pCvvGyYRMAKOhuQmFHFWe7+XRVaUukBTkWs7xjwOIR2i9nXNUo+kCTVVBkUX", - "qaklmP1f3vlm1WRmV8GIGUoAzNXLgqsGWzfV8H7jxfVOqSLCVBpxHlMHD1671f91GLzmL8l0ZZ4thvBS", - "CXNm6Ex2eBZ7aMgDX9O7xRBY3r/l7+AJ/Pi9bGbSZ2NpceAFiWtT3AsSPxy5rU6NTjlmNYf8m8f5SLKx", - "NnZ5nrZAYi2pZtitC9rNbEHzXjm3irT1+ANrvmW6qhs1XcgGP5+oVDcb1ythVat3CrdArBOQbSPhKwVl", - "5wi4DKX1+REGZ9831OdxB3eTHL0vHDRUj/+v3Vp/yRY6ahYapla4SpdfqROazLFQ0TEtLVMq91ASO6yV", - "vNWF9h76jqhcqyOe9A24crC+TvLOkD8k/pWqmbiD0oTYUgg7OdhFGxsb20jly05fAU2lCZ0W1/ESqFNq", - "ux7o71Ok7iElsdK21WBuutDk30azeBBv5npn6YuxglrZIPIXeWUeAdV8lRf4gme9pi5sX3f/lHwSy1wz", - "M4JAatFDnvs/BmJYjD/iKHrdr4TUrRwUn6kiLOITIkhFjREoIgIXjEo9PcECcdn8ISuKFF6mLudIhP+A", - "zBpfQwi7WzAph1Kg6DyRq5AHe8mUHfc+e4tr8o8z1ce3xB2rS9zhoOCV2Rp0co56Z0R1Wi4Nx2pupQc1", - "SsDC7OJ9FbK7QtnXN0lomi8DqZKYfEsXt5hFYekEJEteY8Os8kwtC4IpkDPLkqCSzpoqNJhLBdyPEhFe", - "E4RhuJDrEvaYBojclD4SGujUtSBj6fJGug6xGOEoIhzFQ6xyUFjVj2DAUOQn7LF4KIcU8+VcU4jna1y5", - "hScTwI+xbUApAXnaVXESq/McOwe8NIlyHU1JZ0mEeRin1W8TGhAeTUM6sJN2uO5tXVK/TmzKver4l3W/", - "4P4YcVh+dClSKDg6JBQNVo+ymHlfAEGn8gho2GD9+mjEDEFYrCqhqo9wb9pCu5jKlctTNWQ8NqdKhVFZ", - "C65e2MQuCzaDImaXJFsyZqkYKPSQRrWKOmVz9K9izS502DecUe6V7TKoaM/eoVCobctqndqF3i7oN2td", - "bWvd/HvrC1663NgU3GIxmByyWraakpSNQd5v+upElExMhTqkVMVcEWF58XIWRSRA7NqdgwlmslZ7rtNo", - "/HWk6rzBhvT7xI/Da1JVFhaYIo7RZBj6Q1MWViIbXxGBVPdGsQp1mqJftvRTjqkzYPUTnVv6zHa+A3O6", - "PMDLXyMruSMkZmMc0m5F7e7TIUuiwMKEaq7AAorTNbtVXW/tnCiZe1UTuWg1iPFlVInSZ7eHTVAIsjYR", - "9ktho5Rm3emkvYRLtc2ttfXuG1utYquKNS1lcSvEL6axiu4IRrtQpA5eJPzaHfMXMR8Cn3NF8dY7P7ba", - "rXZrfef58+fPHQI2VHiZUYtQfZcz6wU6pFGIJZfkG5kTropayJMsOXVaTkgTvqoD0bqgH14RzCkaMU4u", - "n1TWQVwbkFiO1QSOT4I1GGVNcvPrkEyewsHQcpiuKOEUmstggloe0oEqbQiyVMp97gGfZu9OAHWwf00A", - "dW7BXAh/bbBGjJI4/EzWAiyGPYZ5oOOVmgG5JpG8MZqDJAxIDkDt+V8TQMuVf0lkmRFyQKSOYjXByLkp", - "LY6gnJjipqsZp/ru8u7/AgAA//+QnyapF/IAAA==", + "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/api/client/go/client.gen.go b/api/client/go/client.gen.go index 0aa16c853..9df6673ee 100644 --- a/api/client/go/client.gen.go +++ b/api/client/go/client.gen.go @@ -243,16 +243,19 @@ type EntitlementGrant struct { // Id Readonly unique ULID identifier. Id *string `json:"id,omitempty"` - // MaxRolloverAmount The maximum amount of the grant that can be rolled over. Defaults to 0. + // MaxRolloverAmount Grants are rolled over at reset, after which they can have a different balance compared to what they had before the reset. // - // - maxAmount = {original_amount} -> rollover original amount - // - maxAmount = 0 -> no rollover - // - maxAmount = 90 -> rollover 90 max - // - // If it's larger than 0 then the grant's balance will be the MAX(maxRollover, balance) + amount. + // Balance after the reset is calculated as: + // Balance_After_Reset = MIN(MaxRolloverAmount, MAX(Balance_Before_Reset, MinRolloverAmount)) MaxRolloverAmount *float64 `json:"maxRolloverAmount,omitempty"` Metadata *map[string]string `json:"metadata,omitempty"` + // MinRolloverAmount Grants are rolled over at reset, after which they can have a different balance compared to what they had before the reset. + // + // Balance after the reset is calculated as: + // Balance_After_Reset = MIN(MaxRolloverAmount, MAX(Balance_Before_Reset, MinRolloverAmount)) + MinRolloverAmount *float64 `json:"minRolloverAmount,omitempty"` + // NextRecurrence The next time the grant will recurr. NextRecurrence *time.Time `json:"nextRecurrence,omitempty"` @@ -283,16 +286,19 @@ type EntitlementGrantCreateInput struct { EffectiveAt time.Time `json:"effectiveAt"` Expiration ExpirationPeriod `json:"expiration"` - // MaxRolloverAmount The maximum amount of the grant that can be rolled over. Defaults to 0. + // MaxRolloverAmount Grants are rolled over at reset, after which they can have a different balance compared to what they had before the reset. // - // - maxAmount = {original_amount} -> rollover original amount - // - maxAmount = 0 -> no rollover - // - maxAmount = 90 -> rollover 90 max - // - // If it's larger than 0 then the grant's balance will be the MAX(maxRollover, balance) + amount. + // Balance after the reset is calculated as: + // Balance_After_Reset = MIN(MaxRolloverAmount, MAX(Balance_Before_Reset, MinRolloverAmount)) MaxRolloverAmount *float64 `json:"maxRolloverAmount,omitempty"` Metadata *map[string]string `json:"metadata,omitempty"` + // MinRolloverAmount Grants are rolled over at reset, after which they can have a different balance compared to what they had before the reset. + // + // Balance after the reset is calculated as: + // Balance_After_Reset = MIN(MaxRolloverAmount, MAX(Balance_Before_Reset, MinRolloverAmount)) + MinRolloverAmount *float64 `json:"minRolloverAmount,omitempty"` + // Priority The priority of the grant. Grants with higher priority are applied first. // Priority is a positive decimal numbers. With lower numbers indicating higher importance. // For example, a priority of 1 is more urgent than a priority of 2. @@ -6735,174 +6741,174 @@ func ParseResetEntitlementUsageResponse(rsp *http.Response) (*ResetEntitlementUs // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "LY6gnJjipqsZp/ru8u7/AgAA//+QnyapF/IAAA==", + "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/api/openapi.yaml b/api/openapi.yaml index 718a94236..96a07b1a0 100644 --- a/api/openapi.yaml +++ b/api/openapi.yaml @@ -1837,13 +1837,20 @@ components: - $ref: "#/components/schemas/ExpirationPeriod" maxRolloverAmount: description: | - The maximum amount of the grant that can be rolled over. Defaults to 0. + Grants are rolled over at reset, after which they can have a different balance compared to what they had before the reset. - - maxAmount = {original_amount} -> rollover original amount - - maxAmount = 0 -> no rollover - - maxAmount = 90 -> rollover 90 max + Balance after the reset is calculated as: + Balance_After_Reset = MIN(MaxRolloverAmount, MAX(Balance_Before_Reset, MinRolloverAmount)) + type: number + format: double + default: 0 + example: 100 + minRolloverAmount: + description: | + Grants are rolled over at reset, after which they can have a different balance compared to what they had before the reset. - If it's larger than 0 then the grant's balance will be the MAX(maxRollover, balance) + amount. + Balance after the reset is calculated as: + Balance_After_Reset = MIN(MaxRolloverAmount, MAX(Balance_Before_Reset, MinRolloverAmount)) type: number format: double default: 0 diff --git a/e2e/e2e_test.go b/e2e/e2e_test.go index 497fa3ac6..8bf92fb39 100644 --- a/e2e/e2e_test.go +++ b/e2e/e2e_test.go @@ -560,6 +560,7 @@ func TestCredit(t *testing.T) { priority := 1 maxRolloverAmount := 100.0 + minRolloverAmount := 0.0 // Create grant resp, err := client.CreateGrantWithResponse(context.Background(), subject, *entitlementId, api.EntitlementGrantCreateInput{ @@ -571,6 +572,7 @@ func TestCredit(t *testing.T) { }, Priority: &priority, MaxRolloverAmount: &maxRolloverAmount, + MinRolloverAmount: &minRolloverAmount, Recurrence: &api.RecurringPeriodCreateInput{ Anchor: convert.ToPointer(time.Date(2024, time.January, 1, 0, 0, 0, 0, time.UTC)), Interval: "YEAR", @@ -591,6 +593,7 @@ func TestCredit(t *testing.T) { Priority: &priority, EffectiveAt: effectiveAt.UTC(), MaxRolloverAmount: &maxRolloverAmount, + MinRolloverAmount: &minRolloverAmount, Expiration: api.ExpirationPeriod{ Duration: "MONTH", Count: 1, diff --git a/internal/credit/grant.go b/internal/credit/grant.go index 748ce61bb..abebed20c 100644 --- a/internal/credit/grant.go +++ b/internal/credit/grant.go @@ -22,6 +22,10 @@ func (n NamespacedGrantOwner) NamespacedID() models.NamespacedID { } } +const ( + GrantPriorityDefault uint8 = 1 +) + // Grant is an immutable definition used to increase balance. type Grant struct { models.ManagedModel @@ -57,8 +61,13 @@ type Grant struct { VoidedAt *time.Time `json:"voidedAt,omitempty"` // How much of the grant can be rolled over after a reset operation. + // Balance after a reset will be between ResetMinRollover and ResetMaxRollover. ResetMaxRollover float64 `json:"resetMaxRollover"` + // How much balance the grant must have after a reset. + // Balance after a reset will be between ResetMinRollover and ResetMaxRollover. + ResetMinRollover float64 `json:"resetMinRollover"` + // Recurrence config for the grant. If nil the grant doesn't recur. Recurrence *recurrence.Recurrence `json:"recurrence,omitempty"` } @@ -95,8 +104,9 @@ func (g Grant) RecurrenceBalance(currentBalance float64) float64 { // Calculates the new balance after a rollover from the current balance func (g Grant) RolloverBalance(currentBalance float64) float64 { - // At a rollover the maximum balance that can remain is the ResetMaxRollover - return math.Min(g.ResetMaxRollover, currentBalance) + // At a rollover the maximum balance that can remain is the ResetMaxRollover, + // while the minimum that has to be granted is ResetMinRollover. + return math.Min(g.ResetMaxRollover, math.Max(g.ResetMinRollover, currentBalance)) } func (g Grant) GetNamespacedID() models.NamespacedID { diff --git a/internal/credit/grant_connector.go b/internal/credit/grant_connector.go index a11df836f..872eccecb 100644 --- a/internal/credit/grant_connector.go +++ b/internal/credit/grant_connector.go @@ -17,6 +17,7 @@ type CreateGrantInput struct { Expiration ExpirationPeriod Metadata map[string]string ResetMaxRollover float64 + ResetMinRollover float64 Recurrence *recurrence.Recurrence } @@ -57,6 +58,7 @@ type GrantRepoCreateGrantInput struct { ExpiresAt time.Time Metadata map[string]string ResetMaxRollover float64 + ResetMinRollover float64 Recurrence *recurrence.Recurrence } @@ -133,6 +135,7 @@ func (m *grantConnector) CreateGrant(ctx context.Context, owner NamespacedGrantO ExpiresAt: input.Expiration.GetExpiration(input.EffectiveAt), Metadata: input.Metadata, ResetMaxRollover: input.ResetMaxRollover, + ResetMinRollover: input.ResetMinRollover, Recurrence: input.Recurrence, }) diff --git a/internal/credit/postgresadapter/ent/db/grant.go b/internal/credit/postgresadapter/ent/db/grant.go index 8b41f1a4b..39c186bb9 100644 --- a/internal/credit/postgresadapter/ent/db/grant.go +++ b/internal/credit/postgresadapter/ent/db/grant.go @@ -46,6 +46,8 @@ type Grant struct { VoidedAt *time.Time `json:"voided_at,omitempty"` // ResetMaxRollover holds the value of the "reset_max_rollover" field. ResetMaxRollover float64 `json:"reset_max_rollover,omitempty"` + // ResetMinRollover holds the value of the "reset_min_rollover" field. + ResetMinRollover float64 `json:"reset_min_rollover,omitempty"` // RecurrencePeriod holds the value of the "recurrence_period" field. RecurrencePeriod *recurrence.RecurrenceInterval `json:"recurrence_period,omitempty"` // RecurrenceAnchor holds the value of the "recurrence_anchor" field. @@ -60,7 +62,7 @@ func (*Grant) scanValues(columns []string) ([]any, error) { switch columns[i] { case grant.FieldMetadata, grant.FieldExpiration: values[i] = new([]byte) - case grant.FieldAmount, grant.FieldResetMaxRollover: + case grant.FieldAmount, grant.FieldResetMaxRollover, grant.FieldResetMinRollover: values[i] = new(sql.NullFloat64) case grant.FieldPriority: values[i] = new(sql.NullInt64) @@ -173,6 +175,12 @@ func (gr *Grant) assignValues(columns []string, values []any) error { } else if value.Valid { gr.ResetMaxRollover = value.Float64 } + case grant.FieldResetMinRollover: + if value, ok := values[i].(*sql.NullFloat64); !ok { + return fmt.Errorf("unexpected type %T for field reset_min_rollover", values[i]) + } else if value.Valid { + gr.ResetMinRollover = value.Float64 + } case grant.FieldRecurrencePeriod: if value, ok := values[i].(*sql.NullString); !ok { return fmt.Errorf("unexpected type %T for field recurrence_period", values[i]) @@ -266,6 +274,9 @@ func (gr *Grant) String() string { builder.WriteString("reset_max_rollover=") builder.WriteString(fmt.Sprintf("%v", gr.ResetMaxRollover)) builder.WriteString(", ") + builder.WriteString("reset_min_rollover=") + builder.WriteString(fmt.Sprintf("%v", gr.ResetMinRollover)) + builder.WriteString(", ") if v := gr.RecurrencePeriod; v != nil { builder.WriteString("recurrence_period=") builder.WriteString(fmt.Sprintf("%v", *v)) diff --git a/internal/credit/postgresadapter/ent/db/grant/grant.go b/internal/credit/postgresadapter/ent/db/grant/grant.go index 3f7dd939a..b63996cbc 100644 --- a/internal/credit/postgresadapter/ent/db/grant/grant.go +++ b/internal/credit/postgresadapter/ent/db/grant/grant.go @@ -41,6 +41,8 @@ const ( FieldVoidedAt = "voided_at" // FieldResetMaxRollover holds the string denoting the reset_max_rollover field in the database. FieldResetMaxRollover = "reset_max_rollover" + // FieldResetMinRollover holds the string denoting the reset_min_rollover field in the database. + FieldResetMinRollover = "reset_min_rollover" // FieldRecurrencePeriod holds the string denoting the recurrence_period field in the database. FieldRecurrencePeriod = "recurrence_period" // FieldRecurrenceAnchor holds the string denoting the recurrence_anchor field in the database. @@ -65,6 +67,7 @@ var Columns = []string{ FieldExpiresAt, FieldVoidedAt, FieldResetMaxRollover, + FieldResetMinRollover, FieldRecurrencePeriod, FieldRecurrenceAnchor, } @@ -167,6 +170,11 @@ func ByResetMaxRollover(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldResetMaxRollover, opts...).ToFunc() } +// ByResetMinRollover orders the results by the reset_min_rollover field. +func ByResetMinRollover(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldResetMinRollover, opts...).ToFunc() +} + // ByRecurrencePeriod orders the results by the recurrence_period field. func ByRecurrencePeriod(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldRecurrencePeriod, opts...).ToFunc() diff --git a/internal/credit/postgresadapter/ent/db/grant/where.go b/internal/credit/postgresadapter/ent/db/grant/where.go index 4ce4924cf..a0fb8e0f6 100644 --- a/internal/credit/postgresadapter/ent/db/grant/where.go +++ b/internal/credit/postgresadapter/ent/db/grant/where.go @@ -122,6 +122,11 @@ func ResetMaxRollover(v float64) predicate.Grant { return predicate.Grant(sql.FieldEQ(FieldResetMaxRollover, v)) } +// ResetMinRollover applies equality check predicate on the "reset_min_rollover" field. It's identical to ResetMinRolloverEQ. +func ResetMinRollover(v float64) predicate.Grant { + return predicate.Grant(sql.FieldEQ(FieldResetMinRollover, v)) +} + // RecurrenceAnchor applies equality check predicate on the "recurrence_anchor" field. It's identical to RecurrenceAnchorEQ. func RecurrenceAnchor(v time.Time) predicate.Grant { return predicate.Grant(sql.FieldEQ(FieldRecurrenceAnchor, v)) @@ -666,6 +671,46 @@ func ResetMaxRolloverLTE(v float64) predicate.Grant { return predicate.Grant(sql.FieldLTE(FieldResetMaxRollover, v)) } +// ResetMinRolloverEQ applies the EQ predicate on the "reset_min_rollover" field. +func ResetMinRolloverEQ(v float64) predicate.Grant { + return predicate.Grant(sql.FieldEQ(FieldResetMinRollover, v)) +} + +// ResetMinRolloverNEQ applies the NEQ predicate on the "reset_min_rollover" field. +func ResetMinRolloverNEQ(v float64) predicate.Grant { + return predicate.Grant(sql.FieldNEQ(FieldResetMinRollover, v)) +} + +// ResetMinRolloverIn applies the In predicate on the "reset_min_rollover" field. +func ResetMinRolloverIn(vs ...float64) predicate.Grant { + return predicate.Grant(sql.FieldIn(FieldResetMinRollover, vs...)) +} + +// ResetMinRolloverNotIn applies the NotIn predicate on the "reset_min_rollover" field. +func ResetMinRolloverNotIn(vs ...float64) predicate.Grant { + return predicate.Grant(sql.FieldNotIn(FieldResetMinRollover, vs...)) +} + +// ResetMinRolloverGT applies the GT predicate on the "reset_min_rollover" field. +func ResetMinRolloverGT(v float64) predicate.Grant { + return predicate.Grant(sql.FieldGT(FieldResetMinRollover, v)) +} + +// ResetMinRolloverGTE applies the GTE predicate on the "reset_min_rollover" field. +func ResetMinRolloverGTE(v float64) predicate.Grant { + return predicate.Grant(sql.FieldGTE(FieldResetMinRollover, v)) +} + +// ResetMinRolloverLT applies the LT predicate on the "reset_min_rollover" field. +func ResetMinRolloverLT(v float64) predicate.Grant { + return predicate.Grant(sql.FieldLT(FieldResetMinRollover, v)) +} + +// ResetMinRolloverLTE applies the LTE predicate on the "reset_min_rollover" field. +func ResetMinRolloverLTE(v float64) predicate.Grant { + return predicate.Grant(sql.FieldLTE(FieldResetMinRollover, v)) +} + // RecurrencePeriodEQ applies the EQ predicate on the "recurrence_period" field. func RecurrencePeriodEQ(v recurrence.RecurrenceInterval) predicate.Grant { vc := v diff --git a/internal/credit/postgresadapter/ent/db/grant_create.go b/internal/credit/postgresadapter/ent/db/grant_create.go index 6f77e721b..9704e7b31 100644 --- a/internal/credit/postgresadapter/ent/db/grant_create.go +++ b/internal/credit/postgresadapter/ent/db/grant_create.go @@ -143,6 +143,12 @@ func (gc *GrantCreate) SetResetMaxRollover(f float64) *GrantCreate { return gc } +// SetResetMinRollover sets the "reset_min_rollover" field. +func (gc *GrantCreate) SetResetMinRollover(f float64) *GrantCreate { + gc.mutation.SetResetMinRollover(f) + return gc +} + // SetRecurrencePeriod sets the "recurrence_period" field. func (gc *GrantCreate) SetRecurrencePeriod(ri recurrence.RecurrenceInterval) *GrantCreate { gc.mutation.SetRecurrencePeriod(ri) @@ -275,6 +281,9 @@ func (gc *GrantCreate) check() error { if _, ok := gc.mutation.ResetMaxRollover(); !ok { return &ValidationError{Name: "reset_max_rollover", err: errors.New(`db: missing required field "Grant.reset_max_rollover"`)} } + if _, ok := gc.mutation.ResetMinRollover(); !ok { + return &ValidationError{Name: "reset_min_rollover", err: errors.New(`db: missing required field "Grant.reset_min_rollover"`)} + } if v, ok := gc.mutation.RecurrencePeriod(); ok { if err := grant.RecurrencePeriodValidator(v); err != nil { return &ValidationError{Name: "recurrence_period", err: fmt.Errorf(`db: validator failed for field "Grant.recurrence_period": %w`, err)} @@ -368,6 +377,10 @@ func (gc *GrantCreate) createSpec() (*Grant, *sqlgraph.CreateSpec) { _spec.SetField(grant.FieldResetMaxRollover, field.TypeFloat64, value) _node.ResetMaxRollover = value } + if value, ok := gc.mutation.ResetMinRollover(); ok { + _spec.SetField(grant.FieldResetMinRollover, field.TypeFloat64, value) + _node.ResetMinRollover = value + } if value, ok := gc.mutation.RecurrencePeriod(); ok { _spec.SetField(grant.FieldRecurrencePeriod, field.TypeEnum, value) _node.RecurrencePeriod = &value @@ -538,6 +551,9 @@ func (u *GrantUpsertOne) UpdateNewValues() *GrantUpsertOne { if _, exists := u.create.mutation.ResetMaxRollover(); exists { s.SetIgnore(grant.FieldResetMaxRollover) } + if _, exists := u.create.mutation.ResetMinRollover(); exists { + s.SetIgnore(grant.FieldResetMinRollover) + } if _, exists := u.create.mutation.RecurrencePeriod(); exists { s.SetIgnore(grant.FieldRecurrencePeriod) } @@ -862,6 +878,9 @@ func (u *GrantUpsertBulk) UpdateNewValues() *GrantUpsertBulk { if _, exists := b.mutation.ResetMaxRollover(); exists { s.SetIgnore(grant.FieldResetMaxRollover) } + if _, exists := b.mutation.ResetMinRollover(); exists { + s.SetIgnore(grant.FieldResetMinRollover) + } if _, exists := b.mutation.RecurrencePeriod(); exists { s.SetIgnore(grant.FieldRecurrencePeriod) } diff --git a/internal/credit/postgresadapter/ent/db/migrate/schema.go b/internal/credit/postgresadapter/ent/db/migrate/schema.go index e4625448e..06748dfb5 100644 --- a/internal/credit/postgresadapter/ent/db/migrate/schema.go +++ b/internal/credit/postgresadapter/ent/db/migrate/schema.go @@ -60,6 +60,7 @@ var ( {Name: "expires_at", Type: field.TypeTime}, {Name: "voided_at", Type: field.TypeTime, Nullable: true}, {Name: "reset_max_rollover", Type: field.TypeFloat64, SchemaType: map[string]string{"postgres": "numeric"}}, + {Name: "reset_min_rollover", Type: field.TypeFloat64, SchemaType: map[string]string{"postgres": "numeric"}}, {Name: "recurrence_period", Type: field.TypeEnum, Nullable: true, Enums: []string{"DAY", "WEEK", "MONTH", "YEAR"}}, {Name: "recurrence_anchor", Type: field.TypeTime, Nullable: true}, } diff --git a/internal/credit/postgresadapter/ent/db/mutation.go b/internal/credit/postgresadapter/ent/db/mutation.go index 625d1e3d1..bdceca731 100644 --- a/internal/credit/postgresadapter/ent/db/mutation.go +++ b/internal/credit/postgresadapter/ent/db/mutation.go @@ -902,6 +902,8 @@ type GrantMutation struct { voided_at *time.Time reset_max_rollover *float64 addreset_max_rollover *float64 + reset_min_rollover *float64 + addreset_min_rollover *float64 recurrence_period *recurrence.RecurrenceInterval recurrence_anchor *time.Time clearedFields map[string]struct{} @@ -1581,6 +1583,62 @@ func (m *GrantMutation) ResetResetMaxRollover() { m.addreset_max_rollover = nil } +// SetResetMinRollover sets the "reset_min_rollover" field. +func (m *GrantMutation) SetResetMinRollover(f float64) { + m.reset_min_rollover = &f + m.addreset_min_rollover = nil +} + +// ResetMinRollover returns the value of the "reset_min_rollover" field in the mutation. +func (m *GrantMutation) ResetMinRollover() (r float64, exists bool) { + v := m.reset_min_rollover + if v == nil { + return + } + return *v, true +} + +// OldResetMinRollover returns the old "reset_min_rollover" field's value of the Grant entity. +// If the Grant object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *GrantMutation) OldResetMinRollover(ctx context.Context) (v float64, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldResetMinRollover is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldResetMinRollover requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldResetMinRollover: %w", err) + } + return oldValue.ResetMinRollover, nil +} + +// AddResetMinRollover adds f to the "reset_min_rollover" field. +func (m *GrantMutation) AddResetMinRollover(f float64) { + if m.addreset_min_rollover != nil { + *m.addreset_min_rollover += f + } else { + m.addreset_min_rollover = &f + } +} + +// AddedResetMinRollover returns the value that was added to the "reset_min_rollover" field in this mutation. +func (m *GrantMutation) AddedResetMinRollover() (r float64, exists bool) { + v := m.addreset_min_rollover + if v == nil { + return + } + return *v, true +} + +// ResetResetMinRollover resets all changes to the "reset_min_rollover" field. +func (m *GrantMutation) ResetResetMinRollover() { + m.reset_min_rollover = nil + m.addreset_min_rollover = nil +} + // SetRecurrencePeriod sets the "recurrence_period" field. func (m *GrantMutation) SetRecurrencePeriod(ri recurrence.RecurrenceInterval) { m.recurrence_period = &ri @@ -1713,7 +1771,7 @@ func (m *GrantMutation) Type() string { // order to get all numeric fields that were incremented/decremented, call // AddedFields(). func (m *GrantMutation) Fields() []string { - fields := make([]string, 0, 15) + fields := make([]string, 0, 16) if m.namespace != nil { fields = append(fields, grant.FieldNamespace) } @@ -1753,6 +1811,9 @@ func (m *GrantMutation) Fields() []string { if m.reset_max_rollover != nil { fields = append(fields, grant.FieldResetMaxRollover) } + if m.reset_min_rollover != nil { + fields = append(fields, grant.FieldResetMinRollover) + } if m.recurrence_period != nil { fields = append(fields, grant.FieldRecurrencePeriod) } @@ -1793,6 +1854,8 @@ func (m *GrantMutation) Field(name string) (ent.Value, bool) { return m.VoidedAt() case grant.FieldResetMaxRollover: return m.ResetMaxRollover() + case grant.FieldResetMinRollover: + return m.ResetMinRollover() case grant.FieldRecurrencePeriod: return m.RecurrencePeriod() case grant.FieldRecurrenceAnchor: @@ -1832,6 +1895,8 @@ func (m *GrantMutation) OldField(ctx context.Context, name string) (ent.Value, e return m.OldVoidedAt(ctx) case grant.FieldResetMaxRollover: return m.OldResetMaxRollover(ctx) + case grant.FieldResetMinRollover: + return m.OldResetMinRollover(ctx) case grant.FieldRecurrencePeriod: return m.OldRecurrencePeriod(ctx) case grant.FieldRecurrenceAnchor: @@ -1936,6 +2001,13 @@ func (m *GrantMutation) SetField(name string, value ent.Value) error { } m.SetResetMaxRollover(v) return nil + case grant.FieldResetMinRollover: + v, ok := value.(float64) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetResetMinRollover(v) + return nil case grant.FieldRecurrencePeriod: v, ok := value.(recurrence.RecurrenceInterval) if !ok { @@ -1967,6 +2039,9 @@ func (m *GrantMutation) AddedFields() []string { if m.addreset_max_rollover != nil { fields = append(fields, grant.FieldResetMaxRollover) } + if m.addreset_min_rollover != nil { + fields = append(fields, grant.FieldResetMinRollover) + } return fields } @@ -1981,6 +2056,8 @@ func (m *GrantMutation) AddedField(name string) (ent.Value, bool) { return m.AddedPriority() case grant.FieldResetMaxRollover: return m.AddedResetMaxRollover() + case grant.FieldResetMinRollover: + return m.AddedResetMinRollover() } return nil, false } @@ -2011,6 +2088,13 @@ func (m *GrantMutation) AddField(name string, value ent.Value) error { } m.AddResetMaxRollover(v) return nil + case grant.FieldResetMinRollover: + v, ok := value.(float64) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.AddResetMinRollover(v) + return nil } return fmt.Errorf("unknown Grant numeric field %s", name) } @@ -2110,6 +2194,9 @@ func (m *GrantMutation) ResetField(name string) error { case grant.FieldResetMaxRollover: m.ResetResetMaxRollover() return nil + case grant.FieldResetMinRollover: + m.ResetResetMinRollover() + return nil case grant.FieldRecurrencePeriod: m.ResetRecurrencePeriod() return nil diff --git a/internal/credit/postgresadapter/ent/schema/grant.go b/internal/credit/postgresadapter/ent/schema/grant.go index 2c5ee5df2..837a91cc9 100644 --- a/internal/credit/postgresadapter/ent/schema/grant.go +++ b/internal/credit/postgresadapter/ent/schema/grant.go @@ -42,6 +42,9 @@ func (Grant) Fields() []ent.Field { field.Float("reset_max_rollover").Immutable().SchemaType(map[string]string{ dialect.Postgres: "numeric", }), + field.Float("reset_min_rollover").Immutable().SchemaType(map[string]string{ + dialect.Postgres: "numeric", + }), field.Enum("recurrence_period").Optional().Nillable().GoType(recurrence.RecurrenceInterval("")).Immutable(), field.Time("recurrence_anchor").Optional().Nillable().Immutable(), } diff --git a/internal/credit/postgresadapter/grant.go b/internal/credit/postgresadapter/grant.go index 0606ae687..e490f1924 100644 --- a/internal/credit/postgresadapter/grant.go +++ b/internal/credit/postgresadapter/grant.go @@ -33,7 +33,8 @@ func (g *grantDBADapter) CreateGrant(ctx context.Context, grant credit.GrantRepo SetExpiration(grant.Expiration). SetExpiresAt(grant.ExpiresAt). SetMetadata(grant.Metadata). - SetResetMaxRollover(grant.ResetMaxRollover) + SetResetMaxRollover(grant.ResetMaxRollover). + SetResetMinRollover(grant.ResetMinRollover) if grant.Recurrence != nil { command = command. @@ -166,6 +167,7 @@ func mapGrantEntity(entity *db.Grant) credit.Grant { ExpiresAt: entity.ExpiresAt, Metadata: entity.Metadata, ResetMaxRollover: entity.ResetMaxRollover, + ResetMinRollover: entity.ResetMinRollover, } if entity.RecurrencePeriod != nil && entity.RecurrenceAnchor != nil { diff --git a/internal/entitlement/boolean/connector.go b/internal/entitlement/boolean/connector.go index 40a97807e..0a7eb90f7 100644 --- a/internal/entitlement/boolean/connector.go +++ b/internal/entitlement/boolean/connector.go @@ -26,7 +26,7 @@ func (c *connector) GetValue(entitlement *entitlement.Entitlement, at time.Time) return &BooleanEntitlementValue{}, nil } -func (c *connector) SetDefaultsAndValidate(model *entitlement.CreateEntitlementInputs) error { +func (c *connector) BeforeCreate(model *entitlement.CreateEntitlementInputs, feature *productcatalog.Feature) error { model.EntitlementType = entitlement.EntitlementTypeBoolean if model.MeasureUsageFrom != nil || model.IssueAfterReset != nil || @@ -38,7 +38,7 @@ func (c *connector) SetDefaultsAndValidate(model *entitlement.CreateEntitlementI return nil } -func (c *connector) ValidateForFeature(entitlement *entitlement.CreateEntitlementInputs, feature productcatalog.Feature) error { +func (c *connector) AfterCreate(entitlement *entitlement.Entitlement) error { return nil } diff --git a/internal/entitlement/connector.go b/internal/entitlement/connector.go index ed8426b2d..7af992143 100644 --- a/internal/entitlement/connector.go +++ b/internal/entitlement/connector.go @@ -83,11 +83,7 @@ func (c *entitlementConnector) CreateEntitlement(ctx context.Context, input Crea if err != nil { return nil, err } - err = connector.SetDefaultsAndValidate(&input) - if err != nil { - return nil, err - } - err = connector.ValidateForFeature(&input, *feature) + err = connector.BeforeCreate(&input, feature) if err != nil { return nil, err } @@ -122,6 +118,11 @@ func (c *entitlementConnector) CreateEntitlement(ctx context.Context, input Crea if err != nil || ent == nil { return nil, err } + + err = connector.AfterCreate(ent) + if err != nil { + return nil, err + } return ent, nil } diff --git a/internal/entitlement/entitlement_types.go b/internal/entitlement/entitlement_types.go index 600be772b..d691b880e 100644 --- a/internal/entitlement/entitlement_types.go +++ b/internal/entitlement/entitlement_types.go @@ -12,8 +12,12 @@ type EntitlementValue interface { type SubTypeConnector interface { GetValue(entitlement *Entitlement, at time.Time) (EntitlementValue, error) - SetDefaultsAndValidate(entitlement *CreateEntitlementInputs) error - // ValidateForFeature validates the entitlement against the feature. - ValidateForFeature(entitlement *CreateEntitlementInputs, feature productcatalog.Feature) error + // Runs before creating the entitlement. Might manipulate the inputs. + // If it returns an error the operation has to fail. + BeforeCreate(entitlement *CreateEntitlementInputs, feature *productcatalog.Feature) error + + // Runs after entitlement creation. + // If it returns an error the operation has to fail. + AfterCreate(entitlement *Entitlement) error } diff --git a/internal/entitlement/httpdriver/metered.go b/internal/entitlement/httpdriver/metered.go index f9be7b2c8..b18ffc268 100644 --- a/internal/entitlement/httpdriver/metered.go +++ b/internal/entitlement/httpdriver/metered.go @@ -98,6 +98,7 @@ func (h *meteredEntitlementHandler) CreateGrant() CreateGrantHandler { Duration: credit.ExpirationPeriodDuration(apiGrant.Expiration.Duration), }, ResetMaxRollover: defaultx.WithDefault(apiGrant.MaxRolloverAmount, 0), + ResetMinRollover: defaultx.WithDefault(apiGrant.MinRolloverAmount, 0), }, } @@ -361,6 +362,7 @@ func MapEntitlementGrantToAPI(subjectKey *string, grant *meteredentitlement.Enti EntitlementId: &grant.EntitlementID, ExpiresAt: &grant.ExpiresAt, MaxRolloverAmount: &grant.MaxRolloverAmount, + MinRolloverAmount: &grant.MinRolloverAmount, NextRecurrence: grant.NextRecurrence, VoidedAt: grant.VoidedAt, } diff --git a/internal/entitlement/metered/balance.go b/internal/entitlement/metered/balance.go index c455530ff..2a6e201b6 100644 --- a/internal/entitlement/metered/balance.go +++ b/internal/entitlement/metered/balance.go @@ -224,30 +224,3 @@ func (e *connector) GetEntitlementBalanceHistory(ctx context.Context, entitlemen return windows, burndownHistory, nil } - -// This is just a wrapper around credot.BalanceConnector.ResetUsageForOwner -func (e *connector) ResetEntitlementUsage(ctx context.Context, entitlementID models.NamespacedID, params ResetEntitlementUsageParams) (*EntitlementBalance, error) { - owner := credit.NamespacedGrantOwner{ - Namespace: entitlementID.Namespace, - ID: credit.GrantOwner(entitlementID.ID), - } - - balanceAfterReset, err := e.balanceConnector.ResetUsageForOwner(ctx, owner, credit.ResetUsageForOwnerParams{ - At: params.At, - RetainAnchor: params.RetainAnchor, - }) - if err != nil { - if _, ok := err.(*credit.OwnerNotFoundError); ok { - return nil, &entitlement.NotFoundError{EntitlementID: entitlementID} - } - return nil, err - } - - return &EntitlementBalance{ - EntitlementID: entitlementID.ID, - Balance: balanceAfterReset.Balance(), - UsageInPeriod: 0.0, // you cannot have usage right after a reset - Overage: balanceAfterReset.Overage, - StartOfPeriod: params.At, - }, nil -} diff --git a/internal/entitlement/metered/connector.go b/internal/entitlement/metered/connector.go index 36e1824bd..f8e5b6c71 100644 --- a/internal/entitlement/metered/connector.go +++ b/internal/entitlement/metered/connector.go @@ -2,8 +2,6 @@ package meteredentitlement import ( "context" - "errors" - "fmt" "time" "github.com/openmeterio/openmeter/internal/credit" @@ -103,7 +101,7 @@ func (e *connector) GetValue(entitlement *entitlement.Entitlement, at time.Time) }, nil } -func (c *connector) SetDefaultsAndValidate(model *entitlement.CreateEntitlementInputs) error { +func (c *connector) BeforeCreate(model *entitlement.CreateEntitlementInputs, feature *productcatalog.Feature) error { model.EntitlementType = entitlement.EntitlementTypeMetered model.MeasureUsageFrom = convert.ToPointer(defaultx.WithDefault(model.MeasureUsageFrom, time.Now().Truncate(c.granularity))) model.IsSoftLimit = convert.ToPointer(defaultx.WithDefault(model.IsSoftLimit, false)) @@ -117,39 +115,43 @@ func (c *connector) SetDefaultsAndValidate(model *entitlement.CreateEntitlementI return &entitlement.InvalidValueError{Message: "UsagePeriod is required for metered entitlements", Type: entitlement.EntitlementTypeMetered} } - return nil -} - -func (c *connector) ValidateForFeature(model *entitlement.CreateEntitlementInputs, feature productcatalog.Feature) error { if feature.MeterSlug == nil { return &entitlement.InvalidFeatureError{FeatureID: feature.ID, Message: "Feature has no meter"} } return nil } -func (c *connector) ResetEntitlementsWithExpiredUsagePeriod(ctx context.Context, namespace string, highwatermark time.Time) ([]models.NamespacedID, error) { - entitlements, err := c.entitlementRepo.ListEntitlementsWithExpiredUsagePeriod(ctx, namespace, highwatermark) +func (c *connector) AfterCreate(entitlement *entitlement.Entitlement) error { + metered, err := ParseFromGenericEntitlement(entitlement) if err != nil { - return nil, fmt.Errorf("failed to list entitlements with due reset: %w", err) + return err } - result := make([]models.NamespacedID, 0, len(entitlements)) - - var finalError error - for _, ent := range entitlements { - namespacedID := models.NamespacedID{Namespace: namespace, ID: ent.ID} - - _, err := c.ResetEntitlementUsage(ctx, - namespacedID, - ResetEntitlementUsageParams{ - At: ent.CurrentUsagePeriod.To, - RetainAnchor: true, - }) + // issue default grants + if metered.HasDefaultGrant() { + amountToIssue := *metered.IssuesAfterReset + effectiveAt := metered.UsagePeriod.Anchor + // issue single recurring grant that can't be rolled over + _, err := c.CreateGrant(context.Background(), models.NamespacedID{ + ID: entitlement.ID, + Namespace: entitlement.Namespace, + }, CreateEntitlementGrantInputs{ + CreateGrantInput: credit.CreateGrantInput{ + Amount: amountToIssue, + Priority: credit.GrantPriorityDefault, + EffectiveAt: effectiveAt, + Expiration: credit.ExpirationPeriod{ + Count: 100, // This is a bit of an issue... It would make sense for recurring tags to not have an expiration + Duration: credit.ExpirationPeriodDurationYear, + }, + // These two in conjunction make the grant always have `amountToIssue` balance after a reset + ResetMaxRollover: amountToIssue, + ResetMinRollover: amountToIssue, + }, + }) if err != nil { - finalError = errors.Join(finalError, fmt.Errorf("failed to reset entitlement usage ns=%s id=%s: %w", namespace, ent.ID, err)) + return err } - - result = append(result, namespacedID) } - return result, finalError + return nil } diff --git a/internal/entitlement/metered/entitlement.go b/internal/entitlement/metered/entitlement.go index 00786abc4..21fbd8459 100644 --- a/internal/entitlement/metered/entitlement.go +++ b/internal/entitlement/metered/entitlement.go @@ -32,6 +32,12 @@ type Entitlement struct { LastReset time.Time `json:"lastReset"` } +// HasDefaultGrant returns true if the entitlement has a default grant. +// This is the case when `IssuesAfterReset` is set and greater than 0. +func (e *Entitlement) HasDefaultGrant() bool { + return e.IssuesAfterReset != nil && *e.IssuesAfterReset > 0 +} + func ParseFromGenericEntitlement(model *entitlement.Entitlement) (*Entitlement, error) { if model.EntitlementType != entitlement.EntitlementTypeMetered { return nil, &entitlement.WrongTypeError{Expected: entitlement.EntitlementTypeMetered, Actual: model.EntitlementType} diff --git a/internal/entitlement/metered/entitlement_grant.go b/internal/entitlement/metered/entitlement_grant.go index 221b2ccfb..a10672c45 100644 --- a/internal/entitlement/metered/entitlement_grant.go +++ b/internal/entitlement/metered/entitlement_grant.go @@ -20,6 +20,7 @@ func (e *connector) CreateGrant(ctx context.Context, ent models.NamespacedID, in EffectiveAt: inputGrant.EffectiveAt, Expiration: inputGrant.Expiration, ResetMaxRollover: inputGrant.ResetMaxRollover, + ResetMinRollover: inputGrant.ResetMinRollover, Recurrence: inputGrant.Recurrence, Metadata: inputGrant.Metadata, }) @@ -65,11 +66,13 @@ type EntitlementGrant struct { // "removing" fields OwnerID string `json:"-"` ResetMaxRollover float64 `json:"-"` + ResetMinRollover float64 `json:"-"` // "adding" fields EntitlementID string `json:"entitlementId"` NextRecurrence *time.Time `json:"nextRecurrence,omitempty"` MaxRolloverAmount float64 `json:"maxRolloverAmount"` + MinRolloverAmount float64 `json:"minRolloverAmount"` } func GrantFromCreditGrant(grant credit.Grant) (*EntitlementGrant, error) { @@ -84,6 +87,7 @@ func GrantFromCreditGrant(grant credit.Grant) (*EntitlementGrant, error) { g.Grant = grant g.EntitlementID = string(grant.OwnerID) g.MaxRolloverAmount = grant.ResetMaxRollover + g.MinRolloverAmount = grant.ResetMinRollover return g, nil } diff --git a/internal/entitlement/metered/reset.go b/internal/entitlement/metered/reset.go new file mode 100644 index 000000000..eb5e4c125 --- /dev/null +++ b/internal/entitlement/metered/reset.go @@ -0,0 +1,75 @@ +package meteredentitlement + +import ( + "context" + "errors" + "fmt" + "time" + + "github.com/openmeterio/openmeter/internal/credit" + "github.com/openmeterio/openmeter/internal/entitlement" + "github.com/openmeterio/openmeter/pkg/models" +) + +func (e *connector) ResetEntitlementUsage(ctx context.Context, entitlementID models.NamespacedID, params ResetEntitlementUsageParams) (*EntitlementBalance, error) { + owner := credit.NamespacedGrantOwner{ + Namespace: entitlementID.Namespace, + ID: credit.GrantOwner(entitlementID.ID), + } + + ent, err := e.entitlementRepo.GetEntitlement(ctx, entitlementID) + if err != nil { + return nil, fmt.Errorf("failed to get entitlement: %w", err) + } + + _, err = ParseFromGenericEntitlement(ent) + if err != nil { + return nil, fmt.Errorf("failed to parse entitlement: %w", err) + } + + balanceAfterReset, err := e.balanceConnector.ResetUsageForOwner(ctx, owner, credit.ResetUsageForOwnerParams{ + At: params.At, + RetainAnchor: params.RetainAnchor, + }) + if err != nil { + if _, ok := err.(*credit.OwnerNotFoundError); ok { + return nil, &entitlement.NotFoundError{EntitlementID: entitlementID} + } + return nil, err + } + + return &EntitlementBalance{ + EntitlementID: entitlementID.ID, + Balance: balanceAfterReset.Balance(), + UsageInPeriod: 0.0, // you cannot have usage right after a reset + Overage: balanceAfterReset.Overage, + StartOfPeriod: params.At, + }, nil +} + +func (c *connector) ResetEntitlementsWithExpiredUsagePeriod(ctx context.Context, namespace string, highwatermark time.Time) ([]models.NamespacedID, error) { + entitlements, err := c.entitlementRepo.ListEntitlementsWithExpiredUsagePeriod(ctx, namespace, highwatermark) + if err != nil { + return nil, fmt.Errorf("failed to list entitlements with due reset: %w", err) + } + + result := make([]models.NamespacedID, 0, len(entitlements)) + + var finalError error + for _, ent := range entitlements { + namespacedID := models.NamespacedID{Namespace: namespace, ID: ent.ID} + + _, err := c.ResetEntitlementUsage(ctx, + namespacedID, + ResetEntitlementUsageParams{ + At: ent.CurrentUsagePeriod.To, + RetainAnchor: true, + }) + if err != nil { + finalError = errors.Join(finalError, fmt.Errorf("failed to reset entitlement usage ns=%s id=%s: %w", namespace, ent.ID, err)) + } + + result = append(result, namespacedID) + } + return result, finalError +} diff --git a/internal/entitlement/static/connector.go b/internal/entitlement/static/connector.go index f09dfd381..e86c7cec7 100644 --- a/internal/entitlement/static/connector.go +++ b/internal/entitlement/static/connector.go @@ -29,7 +29,7 @@ func (c *connector) GetValue(entitlement *entitlement.Entitlement, at time.Time) }, nil } -func (c *connector) SetDefaultsAndValidate(model *entitlement.CreateEntitlementInputs) error { +func (c *connector) BeforeCreate(model *entitlement.CreateEntitlementInputs, feature *productcatalog.Feature) error { model.EntitlementType = entitlement.EntitlementTypeStatic if model.MeasureUsageFrom != nil || @@ -54,7 +54,7 @@ func (c *connector) SetDefaultsAndValidate(model *entitlement.CreateEntitlementI return nil } -func (c *connector) ValidateForFeature(entitlement *entitlement.CreateEntitlementInputs, feature productcatalog.Feature) error { +func (c *connector) AfterCreate(entitlement *entitlement.Entitlement) error { return nil }