Example #1
0
File: tax.py Project: rokj/sellout
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}
Example #2
0
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}
Example #3
0
File: bill.py Project: rokj/sellout
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)