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>
277 lines
5.4 KiB
GraphQL
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!
|
|
} |