felicity-lims/felicity/api/gql/billing/mutations.py

397 lines
14 KiB
Python
Raw Normal View History

2023-12-05 22:53:43 +08:00
import logging
from datetime import datetime
2023-12-20 02:01:46 +08:00
2023-12-05 22:53:43 +08:00
import strawberry # noqa
from strawberry.types import Info # noqa
2023-12-04 18:04:30 +08:00
2023-12-05 22:53:43 +08:00
from api.gql.auth import auth_from_info, verify_user_auth
from api.gql.billing.types import (
ProfilePriceType, ProfileDiscountType,
AnalysisPriceType, AnalysisDiscountType,
VoucherType, VoucherCodeType,
TestBillTransactionType, TestBillType
)
from api.gql.permissions import IsAuthenticated
2023-12-20 02:01:46 +08:00
from api.gql.types import OperationError
2023-12-05 22:53:43 +08:00
from apps.billing import models, schemas, utils
2023-12-20 02:01:46 +08:00
from apps.billing.config import TransactionKind, DiscountType
from apps.billing.schemas import TestBillTransactionUpdate, TestBillUpdate
2023-12-04 18:04:30 +08:00
2023-12-05 22:53:43 +08:00
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
2023-12-04 18:04:30 +08:00
2023-12-05 22:53:43 +08:00
ProfilePriceResponse = strawberry.union(
"ProfilePriceResponse", (ProfilePriceType, OperationError), description="" # noqa
)
AnalysisPriceResponse = strawberry.union(
2023-12-20 02:01:46 +08:00
"AnalysisPriceResponse", (AnalysisPriceType, OperationError), description="" #
2023-12-05 22:53:43 +08:00
)
ProfileDiscountResponse = strawberry.union(
"ProfileDiscountResponse", (ProfileDiscountType, OperationError), description="" # noqa
)
AnalysisDiscountResponse = strawberry.union(
2023-12-20 02:01:46 +08:00
"AnalysisDiscountResponse", (AnalysisDiscountType, OperationError), description="" #
2023-12-05 22:53:43 +08:00
)
VoucherResponse = strawberry.union(
2023-12-20 02:01:46 +08:00
"VoucherResponse", (VoucherType, OperationError), description="" #
2023-12-05 22:53:43 +08:00
)
VoucherCodeResponse = strawberry.union(
2023-12-20 02:01:46 +08:00
"VoucherCodeResponse", (VoucherCodeType, OperationError), description="" #
2023-12-05 22:53:43 +08:00
)
TestBillTransactionResponse = strawberry.union(
2023-12-20 02:01:46 +08:00
"TestBillTransactionResponse", (TestBillTransactionType, OperationError), description="" #
2023-12-05 22:53:43 +08:00
)
TestBillResponse = strawberry.union(
2023-12-20 02:01:46 +08:00
"TestBillResponse", (TestBillType, OperationError), description="" #
2023-12-05 22:53:43 +08:00
)
@strawberry.input
class PriceInput:
amount: float
is_active: bool | None = True
2023-12-20 02:01:46 +08:00
2023-12-05 22:53:43 +08:00
@strawberry.input
class PriceDiscountInput:
discount_type: str
2023-12-20 02:01:46 +08:00
value_type: str | None = None
2023-12-05 22:53:43 +08:00
start_date: datetime
end_date: datetime
2023-12-20 02:01:46 +08:00
voucher_uid: str | None = None
value_percent: float | None = None
value_amount: float | None = None
2023-12-05 22:53:43 +08:00
is_active: bool
2023-12-20 02:01:46 +08:00
2023-12-05 22:53:43 +08:00
@strawberry.input
class VoucherInput:
name: str
usage_limit: int
start_date: datetime
end_date: datetime
once_per_customer: bool
once_per_order: bool
2023-12-20 02:01:46 +08:00
2023-12-05 22:53:43 +08:00
@strawberry.input
class VoucherCodeInput:
code: str
voucher_uid: str
usage_limit: int
is_active: bool
2023-12-20 02:01:46 +08:00
2023-12-05 22:53:43 +08:00
@strawberry.input
class BillTransactionInput:
test_bill_uid: str
kind: str
amount: float
notes: str | None = ""
2023-12-20 02:01:46 +08:00
2023-12-05 22:53:43 +08:00
@strawberry.input
class ApplyVoucherInput:
voucher_code: str
2023-12-20 02:01:46 +08:00
test_bill_uid: str
2023-12-05 22:53:43 +08:00
customer_uid: str
@strawberry.type
class BillingMutations:
@strawberry.mutation(permission_classes=[IsAuthenticated])
2023-12-20 02:01:46 +08:00
async def update_profile_price(self, info: Info, uid: str, payload: PriceInput) -> ProfilePriceResponse:
2023-12-05 22:53:43 +08:00
_, felicity_user = await auth_from_info(info)
profile_price = await models.ProfilePrice.get(uid=uid)
incoming: dict = {
"amount": payload.amount,
"is_active": payload.is_active,
"created_by_uid": felicity_user.uid,
"updated_by_uid": felicity_user.uid,
}
obj_in = schemas.ProfilePriceUpdate(**incoming)
profile_price = await profile_price.update(obj_in)
return ProfilePriceType(**profile_price.marshal_simple())
2023-12-20 02:01:46 +08:00
2023-12-05 22:53:43 +08:00
@strawberry.mutation(permission_classes=[IsAuthenticated])
2023-12-20 02:01:46 +08:00
async def update_analysis_price(self, info: Info, uid: str, payload: PriceInput) -> AnalysisPriceResponse:
2023-12-05 22:53:43 +08:00
_, felicity_user = await auth_from_info(info)
analysis_price = await models.AnalysisPrice.get(uid=uid)
incoming: dict = {
"amount": payload.amount,
"is_active": payload.is_active,
"created_by_uid": felicity_user.uid,
"updated_by_uid": felicity_user.uid,
}
obj_in = schemas.AnalysisPriceUpdate(**incoming)
analysis_price = await analysis_price.update(obj_in)
return AnalysisPriceType(**analysis_price.marshal_simple())
@strawberry.mutation(permission_classes=[IsAuthenticated])
async def update_profile_discount(
self, info, uid: str, payload: PriceDiscountInput
) -> ProfileDiscountResponse:
_, felicity_user = await auth_from_info(info)
if not uid:
return OperationError(error="No uid provided to identify update obj")
profile_discount = await models.ProfileDiscount.get(uid=uid)
if not profile_discount:
return OperationError(
error=f"ProfileDiscount with uid {uid} not found. Cannot update obj ..."
)
obj_data = profile_discount.to_dict()
2023-12-20 02:01:46 +08:00
payload_data = payload.__dict__
if payload_data["discount_type"] == DiscountType.SALE:
del payload_data["voucher_uid"]
2023-12-05 22:53:43 +08:00
for field in obj_data:
2023-12-20 02:01:46 +08:00
if field in payload_data:
2023-12-05 22:53:43 +08:00
try:
2023-12-20 02:01:46 +08:00
setattr(profile_discount, field, payload_data[field])
2023-12-05 22:53:43 +08:00
except Exception as e:
logger.warning(f"failed to set attribute {field}: {e}")
2023-12-20 02:01:46 +08:00
2023-12-05 22:53:43 +08:00
update_in: dict = {
"created_by_uid": felicity_user.uid,
"updated_by_uid": felicity_user.uid,
}
2023-12-20 02:01:46 +08:00
2023-12-05 22:53:43 +08:00
obj_in = schemas.ProfileDiscountUpdate(**{**profile_discount.to_dict(), **update_in})
profile_discount = await profile_discount.update(obj_in)
return ProfileDiscountType(**profile_discount.marshal_simple(), )
@strawberry.mutation(permission_classes=[IsAuthenticated])
async def update_analysis_discount(
self, info, uid: str, payload: PriceDiscountInput
) -> AnalysisDiscountResponse:
_, felicity_user = await auth_from_info(info)
if not uid:
return OperationError(error="No uid provided to identify update obj")
analysis_discount = await models.AnalysisDiscount.get(uid=uid)
if not analysis_discount:
return OperationError(
error=f"AnalysisDiscount with uid {uid} not found. Cannot update obj ..."
)
obj_data = analysis_discount.to_dict()
for field in obj_data:
if field in payload.__dict__:
try:
setattr(analysis_discount, field, payload.__dict__[field])
except Exception as e:
logger.warning(f"failed to set attribute {field}: {e}")
2023-12-20 02:01:46 +08:00
2023-12-05 22:53:43 +08:00
update_in: dict = {
"created_by_uid": felicity_user.uid,
"updated_by_uid": felicity_user.uid,
}
obj_in = schemas.AnalysisDiscountUpdate(**{**analysis_discount.to_dict(), **update_in})
analysis_discount = await analysis_discount.update(obj_in)
return AnalysisDiscountType(**analysis_discount.marshal_simple())
@strawberry.mutation(permission_classes=[IsAuthenticated])
async def create_voucher(
self, info: Info, payload: VoucherInput
) -> VoucherResponse:
is_authenticated, felicity_user = await auth_from_info(info)
success, auth_err = verify_user_auth(
is_authenticated,
felicity_user,
"Only Authenticated user can create vouchers",
)
if not success:
return auth_err
exists = await models.Voucher.get(name=payload.name)
if exists:
return OperationError(
error=f"Voucher {payload.name} already exists"
)
incoming: dict = {
"created_by_uid": felicity_user.uid,
"updated_by_uid": felicity_user.uid,
}
for k, v in payload.__dict__.items():
incoming[k] = v
obj_in = schemas.VoucherCreate(**incoming)
voucher = await models.Voucher.create(obj_in)
return VoucherType(**voucher.marshal_simple())
@strawberry.mutation(permission_classes=[IsAuthenticated])
async def update_voucher(
self, info, uid: str, payload: VoucherInput
) -> VoucherResponse:
is_authenticated, felicity_user = await auth_from_info(info)
verify_user_auth(
is_authenticated,
felicity_user,
"Only Authenticated user can update vouchers",
)
if not uid:
return OperationError(error="No uid provided to identify update obj")
voucher = await models.Voucher.get(uid=uid)
if not voucher:
return OperationError(
error=f"Voucher with uid {uid} not found. Cannot update obj ..."
)
obj_data = voucher.to_dict()
for field in obj_data:
if field in payload.__dict__:
try:
setattr(voucher, field, payload.__dict__[field])
except Exception as e:
logger.warning(f"failed to set attribute {field}: {e}")
obj_in = schemas.VoucherUpdate(**voucher.to_dict())
voucher = await voucher.update(obj_in)
return VoucherType(**voucher.marshal_simple())
@strawberry.mutation(permission_classes=[IsAuthenticated])
async def create_voucher_code(
self, info: Info, payload: VoucherCodeInput
) -> VoucherCodeResponse:
is_authenticated, felicity_user = await auth_from_info(info)
success, auth_err = verify_user_auth(
is_authenticated,
felicity_user,
"Only Authenticated user can create voucher codes",
)
if not success:
return auth_err
exists = await models.VoucherCode.get(code=payload.code)
if exists:
return OperationError(
error=f"Voucher Code {payload.code} already exists"
)
incoming: dict = {
"created_by_uid": felicity_user.uid,
"updated_by_uid": felicity_user.uid,
}
for k, v in payload.__dict__.items():
incoming[k] = v
obj_in = schemas.VoucherCodeCreate(**incoming)
voucher_code = await models.VoucherCode.create(obj_in)
return VoucherCodeType(**voucher_code.marshal_simple())
@strawberry.mutation(permission_classes=[IsAuthenticated])
async def update_voucher_code(
self, info, uid: str, payload: VoucherCodeInput
2023-12-20 02:01:46 +08:00
) -> VoucherCodeResponse:
2023-12-05 22:53:43 +08:00
is_authenticated, felicity_user = await auth_from_info(info)
verify_user_auth(
is_authenticated,
felicity_user,
"Only Authenticated user can update voucher codes",
)
if not uid:
return OperationError(error="No uid provided to identify update obj")
voucher_code = await models.VoucherCode.get(uid=uid)
if not voucher_code:
return OperationError(
error=f"Voucher with uid {uid} not found. Cannot update obj ..."
)
obj_data = voucher_code.to_dict()
for field in obj_data:
if field in payload.__dict__:
try:
setattr(voucher_code, field, payload.__dict__[field])
except Exception as e:
logger.warning(f"failed to set attribute {field}: {e}")
obj_in = schemas.VoucherCodeUpdate(**voucher_code.to_dict())
voucher_code = await voucher_code.update(obj_in)
2023-12-20 02:01:46 +08:00
return VoucherCodeType(**voucher_code.marshal_simple())
2023-12-05 22:53:43 +08:00
@strawberry.mutation(permission_classes=[IsAuthenticated])
async def create_test_bill_transaction(
self, info, payload: BillTransactionInput
) -> TestBillTransactionResponse:
is_authenticated, felicity_user = await auth_from_info(info)
verify_user_auth(
is_authenticated,
felicity_user,
"Only Authenticated user can add test bill transactions",
)
2023-12-20 02:01:46 +08:00
if payload.amount <= 0:
return OperationError(
error="Invalid transaction Amount.",
suggestion="Transaction amount must be greater than 0"
)
test_bill = await models.TestBill.get(uid=payload.test_bill_uid)
2023-12-05 22:53:43 +08:00
incoming: dict = {
2023-12-20 02:01:46 +08:00
"patient_uid": test_bill.patient_uid,
"client_uid": test_bill.client_uid,
2023-12-05 22:53:43 +08:00
"created_by_uid": felicity_user.uid,
"updated_by_uid": felicity_user.uid,
}
for k, v in payload.__dict__.items():
incoming[k] = v
obj_in = schemas.TestBillTransactionCreate(**incoming)
tbt = await models.TestBillTransaction.create(obj_in)
2023-12-20 02:01:46 +08:00
test_bill_update = {
"total_paid": test_bill.total_paid + tbt.amount,
"partial": True
}
if test_bill_update["total_paid"] >= test_bill.total_charged:
test_bill_update["partial"] = False
test_bill_update["is_active"] = False
await test_bill.update(TestBillUpdate(**test_bill_update))
transaction_update = {"is_success": True}
if payload.kind == TransactionKind.CASH:
transaction_update["action_required"] = False
transaction_update["processed"] = True
else:
incoming["action_required"] = False
incoming["action_message"] = "Confirm funds reception"
tbt = await tbt.update(TestBillTransactionUpdate(**transaction_update))
await utils.generate_invoice(test_bill_update)
return TestBillTransactionType(**tbt.marshal_simple())
2023-12-05 22:53:43 +08:00
@strawberry.mutation(permission_classes=[IsAuthenticated])
async def apply_voucher(
self, info, payload: ApplyVoucherInput
) -> TestBillTransactionResponse:
is_authenticated, felicity_user = await auth_from_info(info)
verify_user_auth(
is_authenticated,
felicity_user,
"Only Authenticated user can add test bill transactions",
)
bill = await utils.apply_voucher(payload.voucher_code, payload.test_bill_uid, payload.customer_uid)
2023-12-20 02:01:46 +08:00
return TestBillType(**bill.marshal_simple(exclude=["orders"]))