def validate_tax(user, company, tax): # validate_product, validate_discount, validate_contact for more info def err(msg): return {'success': False, 'data': None, 'message': msg} # name: check length if len(tax['name']) > 0: if len(tax['name']) > max_field_length(Tax, 'name'): return err(_("Name too long")) # amount: try to parse number if tax['amount']: r = parse_decimal(user, company, tax['amount']) if r['success']: tax['amount'] = r['number'] # this could else: return err(_("Wrong number format for amount")) else: # amount is mandatory return err(_("No amount entered")) # default: does not matter return {'success': True, 'data': tax, 'message': None}
def validate_product(user, company, data): # data format (*-validation needed): # id # name* # price - numeric value* # unit type # discounts - list of discount ids (checked in create/edit_product) # image # category - id (checked in create/edit_product) # code* # shortcut* # description # private notes # tax* # stock* def r(status, msg): return {'status':status, 'data':data, 'message':msg} # return: # {status:true/false - if cleaning succeeded # data:cleaned_data - empty dict if status = false # message:error_message - empty if status = true try: data['id'] = int(data['id']) except (ValueError, TypeError): # this shouldn't happen return r(False, _("Wrong product id")) if data['id'] != -1: # check if product belongs to this company and if he has the required permissions try: p = Product.objects.get(id=data['id'], company=company) except Product.DoesNotExist: return r(False, _("Cannot edit product: it does not exist")) if p.company != company or not has_permission(user, company, 'product', 'edit'): return r(False, _("You have no permission to edit this product")) # name if not data['name']: return r(False, _("No name entered")) elif len(data['name']) > max_field_length(Product, 'name'): return r(False, _("Name too long")) else: if data['id'] == -1: # when adding new products: # check if a product with that name exists p = Product.objects.filter(company=company,name=data['name']) if p.count() > 0: return r(False, _("There is already a product with that name") + \ " (" + _("code") + ": " + p[0].code + ")") data['name'] = data['name'].strip() # category: must be present and must exist if not data.get('category_id'): return r(False, _("No category assigned")) else: try: data['category_id'] = int(data['category_id']) data['category'] = Category.objects.get(id=data['category_id'], company=company) except Category.DoesNotExist: return r(False, _("Selected category does not exist")) # price if len(data['price']) > g.DECIMAL['currency_digits']+1: return r(False, _("Price too long")) ret = parse_decimal(user, company, data['price'], g.DECIMAL['currency_digits']) if not ret['success']: return r(False, _("Check price notation")) data['price'] = ret['number'] # purchase price if 'purchase_price' in data: if len(data['purchase_price']) > g.DECIMAL['currency_digits']+1: return r(False, _("Purchase price too long")) if len(data['purchase_price']) > 1: # purchase price is not mandatory, so don't whine if it's not entered ret = parse_decimal(user, company, data['purchase_price'], g.DECIMAL['currency_digits']) if not ret['success']: return r(False, _("Check purchase price notation")) data['purchase_price'] = ret['number'] # unit type (probably doesn't need checking if not data['unit_type'] in dict(g.UNITS): return r(False, _("Invalid unit type")) # image: if data['change_image'] == True: if 'image' in data and data['image']: # a new image has been uploaded data['image'] = import_color_image(data['image'], g.IMAGE_DIMENSIONS['product'], 'fill') if not data['image']: # something has gone wrong during conversion return r(False, _("Image upload failed")) else: # image will be deleted in view pass else: # no change regarding images data['image'] = None # code: must exist and must be unique data['code'] = data['code'].strip() if not data['code']: return r(False, _("No code entered")) else: if len(data['code']) > max_field_length(Product, 'code'): return r(False, _("Code too long")) try: p = Product.objects.get(company=company, code=data['code']) if p.id != data['id']: # same ids = product is being edited, so codes can be the same # if ids are not the same, it's either new product or another product's code return r(False, _("A product with this code already exists: ") + p.name) except Product.DoesNotExist: pass # there is no product with this code, everything is ok # shortcut: if exists, must be unique data['shortcut'] = data['shortcut'].strip() if data['shortcut']: if len(data['shortcut']) > max_field_length(Product, 'shortcut'): return r(False, _("Shortcut too long")) try: p = Product.objects.get(company=company, shortcut=data['shortcut']) if p.id != data['id']: return r(False, _("A product with this shortcut already exists: ") + p.name) except Product.DoesNotExist: pass # ok # description, notes - anything can be entered data['description'] = data['description'].strip() data['private_notes'] = data['private_notes'].strip() # tax: id should be among tax rates for this company try: tax = Tax.objects.get(id=int(data['tax_id'])) except: return r(False, _("Invalid tax rate")) del data['tax_id'] data['tax'] = tax # stock if len(data['stock']) > g.DECIMAL['quantity_digits']: return r(False, _("Stock number too big")) ret = parse_decimal(user, company, data['stock'], g.DECIMAL['quantity_digits']-g.DECIMAL['quantity_decimal_places']-1) if not ret['success']: return r(False, _("Check stock notation")) else: data['stock'] = ret['number'] # it cannot be negative if data['stock'] < decimal.Decimal('0'): return r(False, _("Stock cannot be negative")) return {'status': True, 'data': data}
def create_bill_(request, c): def item_error(message, product): return JsonError(message + " " + _("(Item" + ": ") + product.name + ")") # check permissions if not has_permission(request.user, c, "bill", "edit"): return JsonError(_("You have no permission to create bills")) # get data data = JsonParse(request.POST.get("data")) if not data: return JsonError(_("No data received")) # see if we're updating an existing bill existing_bill = None try: existing_bill = Bill.objects.get(company=c, id=int(data.get("id"))) if existing_bill.status == g.PAID: return JsonError(_("This bill has already been paid, editing is not possible")) except (ValueError, TypeError): pass except Bill.DoesNotExist: pass # current company (that will be fixed on this bill forever): # save a FK to BillCompany; the last company is the current one bill_company = BillCompany.objects.filter(company=c).order_by("-datetime_created")[0] # current register: get BillRegister with the same id as current one try: bill_registers = BillRegister.objects.filter(register__id=int(data.get("register_id"))) if len(bill_registers) > 0: bill_register = bill_registers[0] else: raise Register.DoesNotExist except (TypeError, ValueError, Register.DoesNotExist): return JsonError(_("Invalid register specified.")) # current contact: get BillContact with the same id as the requested contact if data.get("contact"): try: bill_contacts = BillContact.objects.get(contact__id=int(data.get("contact").get("id"))).order_by( "datetime_created" ) if len(bill_contacts) > 0: bill_contact = bill_contacts[0] else: raise Contact.DoesNotExist except (Contact.DoesNotExist, ValueError, TypeError): return JsonError(_("Invalid contact")) else: bill_contact = None # save all validated stuff in bill to a dictionary and insert into database at the end # prepare data for insert bill = { "company": c, "issuer": bill_company, "register": bill_register, "contact": bill_contact, "user_id": request.user.id, "user_name": str(request.user), "notes": data.get("notes", "")[: max_field_length(Bill, "notes")], "type": g.CASH, "status": g.WAITING, "items": [], "currency": get_company_value(request.user, c, "pos_currency"), # numbers... "base": Decimal(0), "discount": Decimal(0), "tax": Decimal(0), "total": Decimal(0), "created_by": request.user, } # timestamp try: # timestamp: send in an array of number: # [year, month, day, hour, minute, second] tn = [int(n) for n in data.get("timestamp")] bill["timestamp"] = dtm(year=tn[0], month=tn[1], day=tn[2], hour=tn[3], minute=tn[4], second=tn[5]) except (ValueError, TypeError): return JsonError(_("Invalid timestamp")) r = parse_decimal(request.user, c, data.get("total")) if not r["success"] or r["number"] <= Decimal("0"): return JsonError(_("Invalid grand total value")) else: bill["total"] = r["number"] # validate items for i in data.get("items"): # get product try: product = Product.objects.get(company=c, id=int(i.get("product_id"))) except Product.DoesNotExist: return JsonError(_("Product with this id does not exist") + " (id=" + i.get("product_id") + ")") # parse quantity r = parse_decimal(request.user, c, i.get("quantity"), g.DECIMAL["quantity_digits"]) if not r["success"]: return item_error(_("Invalid quantity value"), product) else: if r["number"] <= Decimal("0"): return item_error(_("Cannot add an item with zero or negative quantity"), product) quantity = r["number"] # remove from stock; TODO: check negative quantities (?) # actually we leave negative quantities as they are or # when stock is empty, we leave it at 0 product.destockify(quantity) product.save() item = { "created_by": request.user, "code": product.code, "shortcut": product.shortcut, "name": product.name, "description": product.description, "private_notes": product.private_notes, "unit_type": product.get_unit_type_display(), # ! display, not the 'code' "stock": product.stock, # 'bill': not now, after bill is saved "product_id": product.id, "bill_notes": i.get("bill_notes"), "discounts": [], # validated discounts (FK in database) # prices: will be calculated when discounts are ready "base": None, "quantity": None, "tax_rate": None, "batch": None, "discount": None, "net": None, "tax": None, "total": None, } bill["items"].append(item) for d in i["discounts"]: # check: # discount id: if it's -1, it's a unique discount on this item; # if it's anything else, the discount must belong to this company # and must be active and enabled d_id = int(d.get("id")) if d_id != -1: try: dbd = Discount.objects.get(id=d_id, company=c) if not dbd.is_active: return item_error(_("The discount is not active"), product) except Discount.DoesNotExist: return item_error(_("Chosen discount does not exist or is not valid"), product) # amount: parse number and check that percentage does not exceed 100% r = parse_decimal(request.user, c, d.get("amount")) if not r["success"]: return item_error(_("Invalid discount amount"), product) else: d_amount = r["number"] if d_amount < Decimal(0) or (d.get("type") == "Relative" and d_amount > Decimal(100)): return item_error(_("Invalid discount amount"), product) # save data to bill discount = { "id": d_id, "code": d.get("code"), "description": d.get("description"), "type": d.get("type"), "amount": d_amount, } item["discounts"].append(discount) # save this item's prices to item's dictionary (will go into database later) try: item["base"] = parse_decimal_exc(request.user, c, i.get("base"), message=_("Invalid base price")) item["quantity"] = parse_decimal_exc(request.user, c, i.get("quantity"), message=_("Invalid quantity")) item["tax_rate"] = parse_decimal_exc(request.user, c, i.get("tax_rate"), message=_("Invalid tax rate")) item["batch"] = parse_decimal_exc(request.user, c, i.get("batch"), message=_("Invalid batch price")) item["discount"] = parse_decimal_exc( request.user, c, i.get("discount"), message=_("Invalid discount amount") ) item["net"] = parse_decimal_exc(request.user, c, i.get("net"), message=_("Invalid net price")) item["tax"] = parse_decimal_exc(request.user, c, i.get("tax"), message=_("Invalid tax amount")) item["total"] = parse_decimal_exc(request.user, c, i.get("total"), message=_("Invalid total")) bill["base"] += item["batch"] bill["discount"] += item["discount"] bill["tax"] += item["tax"] except ValueError as e: return item_error(e.message, product) # at this point, everything is fine, insert into database if existing_bill: existing_bill.delete() bill_payment = Payment( type=g.CASH, total=bill["total"], currency=get_company_value(request.user, c, "pos_currency"), transaction_datetime=datetime.utcnow(), status=g.WAITING, created_by=request.user, ) bill_payment.save() # create a new bill db_bill = Bill( created_by=request.user, company=c, # current company, FK to Company object issuer=bill["issuer"], # fixed company details at this moment, FK to BillCompany object user_id=bill["user_id"], # id of user that created this bill, just an integer, not a FK user_name=bill["user_name"], # copied user name in case that user gets 'fired' register=bill["register"], # current settings of the register this bill was created on contact=bill["contact"], # FK on BillContact, copy of the Contact object notes=bill["notes"], # timestamp=dtm.utcnow().replace(tzinfo=timezone(get_company_value(request.user, c, 'pos_timezone'))), timestamp=bill["timestamp"], payment=bill_payment, base=bill["base"], discount=bill["discount"], tax=bill["tax"], ) db_bill.save() # create new items for item in bill["items"]: db_item = BillItem( created_by=item["created_by"], code=item["code"], shortcut=item["shortcut"], name=item["name"], description=item["description"], private_notes=item["private_notes"], unit_type=item["unit_type"], bill=db_bill, bill_notes=item["bill_notes"], product_id=item["product_id"], quantity=item["quantity"], base=item["base"], tax_rate=item["tax_rate"], batch=item["batch"], discount=item["discount"], net=item["net"], tax=item["tax"], total=item["total"], ) db_item.save() # save discounts for this item for discount in item["discounts"]: db_discount = BillItemDiscount( created_by=request.user, bill_item=db_item, description=discount["description"], code=discount["code"], type=discount["type"], amount=discount["amount"], ) db_discount.save() db_bill.save() d = {"bill": bill_to_dict(request.user, c, db_bill)} return JsonOk(extra=d)