Files
bbs/schema.graphql
T
leanderandClaude Opus 5.5 7ab2a0f1f2 feat(inventory): add and edit products without the admin
The inventory page gets a "Ny vare" link and a "Rediger" link per product,
leading to a form for name, prices, volume, alcohol, description and whether
the product is for sale. Stock still changes only through inventory
transactions and sales, and the image through the admin.

Saving needs the same access as the Django admin: staff, plus the add or
change product permission (superusers have both). The server checks this on
every save and validates with the model's rules. Logged out visitors are
sent to log in and back to the form.

New in the API: createProduct and updateProduct mutations, a product query,
and a me query so the frontend knows who is logged in.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 03:44:12 +01:00

277 lines
5.4 KiB
GraphQL

directive @oneOf on INPUT_OBJECT
type AboveMaxSaldo implements Error {
message: String!
error: Boolean!
maxSaldo: Float!
above: Float!
}
union CreateDepositResponse = CreateDepositSuccess | AboveMaxSaldo | FormErrors
type CreateDepositSuccess {
transaction: Transaction!
}
union CreateInventoryTransactionResponse = CreateInventoryTransactionSuccess | FormErrors
type CreateInventoryTransactionSuccess {
inventoryTransaction: InventoryTransaction!
}
union CreateOrderResponse = CreateOrderSuccess | InsufficientFunds | FormErrors
type CreateOrderSuccess {
order: Order!
}
"""Date with time (isoformat)"""
scalar DateTime
"""Decimal (fixed-point)"""
scalar Decimal
interface Error {
message: String!
error: Boolean!
}
type FieldError implements Error {
message: String!
error: Boolean!
field: String!
}
type FormErrors {
fields: [FieldError!]!
message: String!
error: Boolean!
}
type InsufficientFunds implements Error {
message: String!
error: Boolean!
amountLacking: Float!
}
type InventoryReport {
products: [InventoryReportProduct!]!
inventoryPurchases: Decimal!
productSales: Decimal!
deposits: Decimal!
orderPurchases: Decimal!
balanceAtStart: Decimal!
balanceAtEnd: Decimal!
}
type InventoryReportProduct {
productId: ID!
productName: String!
purchaseUnits: Int!
purchaseValue: Decimal!
salesUnits: Int!
salesValue: Decimal!
}
type InventoryTransaction {
id: ID!
user: User!
product: Product!
amount: Int!
unitPrice: Decimal!
comment: String!
created: DateTime!
}
input InventoryTransactionInput {
productId: ID!
amount: Int!
unitPrice: Decimal!
comment: String! = ""
}
input InventoryTransactionOrdering @oneOf {
created: Ordering
}
"""
The `JSON` scalar type represents JSON values as specified by [ECMA-404](https://ecma-international.org/wp-content/uploads/ECMA-404_2nd_edition_december_2017.pdf).
"""
scalar JSON @specifiedBy(url: "https://ecma-international.org/wp-content/uploads/ECMA-404_2nd_edition_december_2017.pdf")
type Mutation {
createInventoryTransaction(data: InventoryTransactionInput!): CreateInventoryTransactionResponse!
createProduct(data: ProductInput!): SaveProductResponse!
updateProduct(productId: ID!, data: ProductInput!): SaveProductResponse!
createOrder(customerId: ID!, orderLines: [OrderLineInput!]!, isExternal: Boolean! = false): CreateOrderResponse!
createDeposit(userId: ID!, amount: Int!): CreateDepositResponse!
createUser(input: UserInput!): User!
}
input OffsetPaginationInput {
offset: Int! = 0
limit: Int
}
type Order {
id: ID!
customer: User
orderSum: Decimal!
created: DateTime!
orderlines: [OrderLine!]!
isExternal: Boolean!
}
type OrderLine {
id: ID!
order: Order!
product: Product!
amount: Int!
unitPrice: Decimal!
}
input OrderLineInput {
productId: ID!
amount: Int!
}
input OrderOrdering @oneOf {
created: Ordering
}
type OrderStats {
yearly: [OrderStatsByTime!]!
monthly: [OrderStatsByTime!]!
daily: [OrderStatsByTime!]!
hourly: [OrderStatsByTime!]!
}
type OrderStatsByTime {
period: Int!
count: String!
}
enum Ordering {
ASC
ASC_NULLS_FIRST
ASC_NULLS_LAST
DESC
DESC_NULLS_FIRST
DESC_NULLS_LAST
}
type Point {
x: String!
y: Int!
}
type Product {
id: ID!
name: String!
description: String!
volumeLiter: Decimal
alcoholPercent: Decimal
imageUrl: String!
salePriceInt: Decimal!
salePriceExt: Decimal!
active: Boolean!
inventoryAmount: Int!
updated: DateTime!
wholesaleUnitPrice: Decimal!
wholesaleValue: Decimal!
userCounts: JSON!
}
input ProductFilter {
id: ID
active: Boolean
AND: ProductFilter
OR: ProductFilter
NOT: ProductFilter
DISTINCT: Boolean
}
input ProductInput {
name: String!
salePriceInt: Decimal!
salePriceExt: Decimal!
volumeLiter: Decimal = null
alcoholPercent: Decimal = null
description: String! = ""
active: Boolean! = true
}
type ProductOrderStat {
productName: String!
data: [Point!]!
}
type Query {
transactionList(ordering: [TransactionOrdering!]! = [], pagination: OffsetPaginationInput): [Transaction!]!
inventoryTransactionList(ordering: [InventoryTransactionOrdering!]! = [], pagination: OffsetPaginationInput): [InventoryTransaction!]!
inventoryReport(startTime: DateTime!, endTime: DateTime!): InventoryReport!
allProducts(filters: ProductFilter): [Product!]!
allOrders: [Order!]!
orderList(ordering: [OrderOrdering!]! = [], pagination: OffsetPaginationInput): [Order!]!
product(productId: ID!): Product!
orderStats: OrderStats!
productStats: [ProductOrderStat!]!
allUsers: [User!]!
user(userId: ID!): User!
"""The logged in user, or null when nobody is logged in"""
me: Viewer
}
union SaveProductResponse = SaveProductSuccess | FormErrors
type SaveProductSuccess {
product: Product!
}
type TopMonth {
period: String!
count: Int!
sum: Float!
}
type Transaction {
id: ID!
user: User!
amount: Decimal!
created: DateTime!
}
input TransactionOrdering @oneOf {
created: Ordering
}
type User {
id: ID!
firstName: String!
lastName: String!
profile: UserProfile
}
input UserInput {
email: String
firstName: String
lastName: String
}
type UserProfile {
id: ID!
balance: Decimal!
image: String!
lastPurchaseDate: DateTime!
orderSumTotal: Decimal!
topMonths: [TopMonth!]!
productOrderStats: [ProductOrderStat!]!
}
type Viewer {
id: ID!
firstName: String!
canAddProducts: Boolean!
canChangeProducts: Boolean!
}