Example #1
0
class ProductBrand(Model):
    _name = "product.brand"
    _string = "Brand"
    _fields = {
        "name": fields.Char("Name", required=True, search=True),
        "description": fields.Text("Description", search=True),
        "image": fields.File("Image"),
        "code": fields.Char("Code"),
        "parent_id": fields.Many2One("product.brand","Parent Brand"),
        "sub_brands": fields.One2Many("product.brand","parent_id","Sub Brands"),
        "products": fields.One2Many("product","brand_id","Products", operator="child_of"),
        "num_products": fields.Integer("Number of products", function="get_num_products"),
        "groups": fields.Many2Many("product.brand.group","Group"),
    }
    _order = "name"

    def get_num_products(self, ids, context={}):
        vals = {}
        for obj in self.browse(ids):
            nums = 0
            for product in obj.products:
                if not product.parent_id:
                    nums += 1
            vals[obj.id] = nums
        return vals 
Example #2
0
class ProductGroup(Model):
    _name = "product.group"
    _string = "Product Group"
    _key = ["code"]
    _fields = {
        "name":
        fields.Char("Group Name", required=True, search=True),
        "code":
        fields.Char("Group Code", search=True),
        "parent_id":
        fields.Many2One("product.group", "Parent"),
        "products":
        fields.Many2Many("product", "Products"),
        "filter_products":
        fields.Many2Many("product", "Products",
                         function="get_filter_products"),
        "image":
        fields.File("Image"),
        "company_id":
        fields.Many2One("company", "Company"),
    }
    _order = "name"

    def get_filter_products(self, ids, context={}):
        group_id = ids[0]
        cond = [["groups.id", "=", group_id], ["is_published", "=", True]]
        if context.get("product_filter"):
            cond.append(context["product_filter"])
        prod_ids = get_model("product").search(cond)
        vals = {
            group_id: prod_ids,
        }
        return vals
Example #3
0
class EmailAttach(Model):
    _name = "email.attach"
    _string = "Email Attachment"
    _fields = {
        "email_id":
        fields.Many2One("email.message",
                        "Email",
                        required=True,
                        on_delete="cascade"),
        "file":
        fields.File("File", required=True),
    }
Example #4
0
class CartLineImage(Model):
    _name = "ecom.cart.line.image"
    _fields = {
        "line_id":
        fields.Many2One("ecom.cart.line",
                        "Line",
                        required=True,
                        on_delete="cascade"),
        "image":
        fields.File("Image"),
        "name":
        fields.Char("Name"),
    }
class ProductBrandGroup(Model):
    _name = "product.brand.group"
    _string = "Product Brand Group"
    _key = ["code"]
    _fields = {
        "name": fields.Char("Group Name", required=True, search=True),
        "code": fields.Char("Group Code", search=True),
        "parent_id": fields.Many2One("product.brand.group", "Parent"),
        "brands": fields.Many2Many("product.brand", "Product Brands"),
        "image": fields.File("Image"),
        "company_id": fields.Many2One("company","Company"),
    }
    _order = "name"
Example #6
0
class DocumentTmpl(Model):
    _name = "document.tmpl"
    _string = "Document Template"
    _fields = {
        "file": fields.File("File"),
        "categ_id": fields.Many2One("document.categ", "Category", required=True, search=True),
        "description": fields.Text("Description", search=True),
        "date": fields.Date("Date", required=True, search=True),
        "attachments": fields.One2Many("attach", "related_id", "Attachments"),
        "comments": fields.One2Many("message", "related_id", "Comments"),
    }
    _defaults = {
        "date": lambda *a: time.strftime("%Y-%m-%d"),
    }
Example #7
0
class Attach(Model):
    _name = "attach"
    _string = "Attachment"
    _order = "date desc"
    _fields = {
        "date": fields.DateTime("Date", required=True, search=True),
        "user_id": fields.Many2One("base.user", "User", search=True),
        "file": fields.File("File", required=True),
        "related_id": fields.Reference([["document", "Document"]], "Related To"),
        "description": fields.Text("Description", search=True),
        "comments": fields.One2Many("message", "related_id", "Comments"),
    }

    _defaults = {
        "date": lambda *a: time.strftime("%Y-%m-%d %H:%M:%S"),
        "user_id": lambda self, context: int(context.get("user_id")),
    }
Example #8
0
class OptionValue(Model):
    _name = "product.custom.option.value"
    _fields = {
        "cust_opt_id":
        fields.Many2One("product.custom.option",
                        "Custom Option",
                        required=True),
        "name":
        fields.Char("Name", required=True, translate=True),
        "code":
        fields.Char("Code"),
        "description":
        fields.Text("Description"),
        "image":
        fields.File("Image"),
        "price":
        fields.Decimal("Price"),
    }
Example #9
0
class ReportTemplate(Model):
    _name = "report.template"
    _string = "Report Template"
    _multi_company = True
    _fields = {
        "name":
        fields.Char("Template Name", required=True, search=True),
        "type":
        fields.Selection(
            [["cust_invoice", "Customer Invoice"],
             ["cust_credit_note", "Customer Credit Note"],
             ["supp_invoice", "Supplier Invoice"], ["payment", "Payment"],
             ["account_move", "Journal Entry"], ["sale_quot", "Quotation"],
             ["sale_order", "Sales Order"], ["purch_order", "Purchase Order"],
             ["purchase_request", "Purchase Request"],
             ["prod_order", "Production Order"],
             ["goods_receipt", "Goods Receipt"],
             ["goods_transfer", "Goods Transfer"],
             ["goods_issue", "Goods Issue"], ["pay_slip", "Pay Slip"],
             ["tax_detail", "Tax Detail"], ["hr_expense", "HR Expense"],
             ["landed_cost", "Landed Cost"], ["other", "Other"]],
            "Template Type",
            required=True,
            search=True),
        "format":
        fields.Selection(
            [["odt", "ODT (old)"], ["odt2", "ODT"], ["ods", "ODS"],
             ["docx", "DOCX (old)"], ["xlsx", "XLSX"],
             ["jrxml", "JRXML (old)"], ["jrxml2", "JRXML"], ["jsx", "JSX"]],
            "Template Format",
            required=True,
            search=True),
        "file":
        fields.File("Template File"),
        "company_id":
        fields.Many2One("company", "Company"),
        "model_id":
        fields.Many2One("model", "Model"),
        "method":
        fields.Char("Method"),
    }
    _defaults = {
        "file_type": "odt",
    }
Example #10
0
class AttributeOption(Model):
    _name = "product.attribute.option"
    _fields = {
        "attribute_id":
        fields.Many2One("product.attribute", "Attribute", required=True),
        "name":
        fields.Char("Name", required=True, translate=True, size=256),
        "code":
        fields.Char("Code", required=True, size=256),
        "sequence":
        fields.Integer("Sequence", required=True),
        "description":
        fields.Text("Description"),
        "image":
        fields.File("Image"),
        "price":
        fields.Float("Price"),
    }
    _order = "sequence"
    _defaults = {
        "sequence": 0,
    }
Example #11
0
class Import(Model):
    _name = "import.data"
    _transient = True
    _fields = {
        "model": fields.Char("Model", required=True),
        "next": fields.Char("Next"),
        "title": fields.Char("Title"),
        "file": fields.File("File to import", required=True),
    }

    def get_data(self, context={}):
        model = context["import_model"]
        m = get_model(model)
        title = "Import"
        if m._string:
            title += " " + m._string
        return {
            "model": model,
            "title": title,
            "next": context.get("next"),
        }

    def do_import(self, ids, context={}):
        obj = self.browse(ids[0])
        #dbname = get_active_db()
        #data = open(os.path.join("static", "db", dbname, "files", obj.file), "rU", errors="replace").read()
        m = get_model(obj.model)
        #m.import_data(data)
        m.import_csv(obj.file)
        if obj.next:
            return {
                "next": {
                    "name": obj.next,
                },
                "flash": "Data imported successfully",
            }
Example #12
0
class Product(Model):
    _name = "product"
    _key = ["name"]
    _order = "name"
    _name_field = "name"
    _fields = {
        "name":
        fields.Char("Name", required=True),
        "code":
        fields.Char("Code"),
        "description":
        fields.Text("Description"),
        "purchase_price":
        fields.Decimal("Purchase Price"),
        "sale_price":
        fields.Decimal("Sale Price"),
        "tags":
        fields.Many2Many("tag", "Tags"),
        "image":
        fields.File("Image"),
        "cost_method":
        fields.Selection(
            [["standard", "Standard Cost"], ["average", "Weighted Average"],
             ["fifo", "FIFO"], ["lifo", "LIFO"]], "Costing Method"),
        "cost_price":
        fields.Decimal("Cost Price"),
        "stock_in_account_id":
        fields.Many2One("account.account", "Stock Input Account"),
        "stock_out_account_id":
        fields.Many2One("account.account", "Stock Output Account"),
        "uuid":
        fields.Char("UUID"),
    }
    _defaults = {
        "uuid": lambda *a: str(uuid.uuid4()),
    }
Example #13
0
class CartLine(Model):
    _name = "ecom.cart.line"
    _fields = {
        "cart_id":
        fields.Many2One("ecom.cart",
                        "Cart",
                        required=True,
                        on_delete="cascade"),
        "sequence":
        fields.Integer("Item No.", required=True),
        "product_id":
        fields.Many2One("product",
                        "Product",
                        required=True,
                        on_delete="cascade"),
        "description":
        fields.Text("Description"),
        "qty":
        fields.Integer("Qty", required=True),
        "unit_price":
        fields.Decimal("Unit Price", required=True),
        "discount_percent":
        fields.Decimal("Discount Percent"),
        "discount_amount":
        fields.Decimal("Discount Amount"),
        "promotion_amount":
        fields.Decimal("Promotion Amount", function="_get_amount"),
        "amount_before_discount":
        fields.Decimal("Amount Before Discount",
                       function="_get_amount",
                       function_multi=True),
        "amount":
        fields.Decimal("Amount", function="_get_amount", function_multi=True),
        "special_price":
        fields.Decimal("Special Price",
                       function="_get_amount",
                       function_multi=True),
        "has_discount":
        fields.Boolean("Has Discount",
                       function="_get_amount",
                       function_multi=True),
        "image":
        fields.File("Image"),
        "images":
        fields.One2Many("ecom.cart.line.image", "line_id", "Images"),
        "ship_method_id":
        fields.Many2One("ship.method", "Shipping Method"),
    }
    _order = "sequence"
    _defaults = {
        "is_discounted": False,
    }

    def _get_amount(self, ids, context={}):
        vals = {}
        cart_ids = []
        for obj in self.browse(ids):
            cart_ids.append(obj.cart_id.id)
        cart_ids = list(set(cart_ids))
        for cart in get_model("ecom.cart").browse(cart_ids):
            prod_qtys = {}
            prom_amts = {}
            prom_pcts = {}
            for line in cart.lines:
                prod_qtys.setdefault(line.product_id.id, 0)
                prod_qtys[line.product_id.id] += line.qty
            for prom in cart.used_promotions:
                if prom.amount and prom.product_id:
                    prom_amts.setdefault(prom.product_id.id, 0)
                    prom_amts[prom.product_id.id] += prom.amount
                elif prom.percent:
                    prom_pcts.setdefault(prom.product_id.id, 0)
                    prom_pcts[prom.product_id.id] += prom.percent
            for line in cart.lines:
                amt_before_disc = line.qty * line.unit_price
                amt = amt_before_disc
                has_disc = False
                if line.discount_percent:
                    amt -= amt_before_disc * line.discount_percent / 100
                    has_disc = True
                if line.discount_amount:
                    amt -= line.discount_amount
                    has_disc = True
                amt_before_prom = amt
                prom_amt = prom_amts.get(
                    line.product_id.id,
                    Decimal(0)) / prod_qtys[line.product_id.id] * line.qty
                prom_pct = prom_pcts.get(line.product_id.id,
                                         Decimal(0)) + prom_pcts.get(None, 0)
                if prom_pct:
                    prom_amt += math.ceil(
                        amt_before_prom / line.qty * prom_pct / 100) * line.qty
                if prom_amt:
                    amt -= prom_amt
                    has_disc = True
                special_price = amt / line.qty if line.qty else None
                if line.id in ids:
                    vals[line.id] = {
                        "promotion_amount": prom_amt,
                        "amount_before_discount": amt_before_disc,
                        "amount": amt,
                        "special_price": special_price,
                        "has_discount": has_disc,
                    }
        return vals

    def change_qty(self, ids, qty):
        print("CartLine.change_qty", ids, qty)
        obj = self.browse(ids)[0]
        if qty == obj.qty:
            return
        if qty > 0:
            disc_amt = obj.discount_amount * qty / obj.qty
            obj.write({"qty": qty, "discount_amount": disc_amt})
        else:
            obj.delete()
        cart = obj.cart_id
        cart.update_promotions()
Example #14
0
class ProductCateg(Model):
    _name = "product.categ"
    _string = "Product Category"
    _export_name_field = "code"
    _fields = {
        "name":
        fields.Char("Name", required=True, search=True),
        "code":
        fields.Char("Short Code", search=True),
        "parent_id":
        fields.Many2One("product.categ", "Parent Category"),
        "description":
        fields.Text("Description"),
        "comments":
        fields.One2Many("message", "related_id", "Comments"),
        "products":
        fields.One2Many("product",
                        "categ_id",
                        "Products",
                        operator="child_of",
                        condition=[["is_published", "=", True]]),
        "image":
        fields.File("Image"),
        "sub_categories":
        fields.One2Many("product.categ", "parent_id", "Sub Categories"),
        "num_products":
        fields.Integer("Number of products", function="get_num_products"),
        "gross_profit":
        fields.Decimal("Gross Profit (%)"),
        "sale_account_id":
        fields.Many2One("account.account", "Sales Account",
                        multi_company=True),
        "sale_tax_id":
        fields.Many2One("account.tax.rate", "Sales Tax"),
        "purchase_account_id":
        fields.Many2One("account.account",
                        "Purchase Account",
                        multi_company=True),
        "purchase_tax_id":
        fields.Many2One("account.tax.rate", "Purchase Tax"),
        "cost_method":
        fields.Selection(
            [["standard", "Standard Cost"], ["average", "Weighted Average"],
             ["fifo", "FIFO"], ["lifo", "LIFO"]], "Costing Method"),
        "cogs_account_id":
        fields.Many2One("account.account",
                        "Cost Of Goods Sold Account",
                        multi_company=True),
        "stock_account_id":
        fields.Many2One("account.account",
                        "Inventory Account",
                        multi_company=True),
    }
    _order = "name"
    _constraints = ["_check_cycle"]

    def get_full_parent_name(self, obj_id):
        obj = self.browse(obj_id)
        full = [obj.name]
        while obj.parent_id:
            obj = obj.parent_id
            full.append(obj.name)
        name = "/".join(full[::-1])
        return name

    def get_num_products(self, ids, context={}):
        vals = {}
        for obj in self.browse(ids):
            nums = 0
            for product in obj.products:
                if not product.parent_id:
                    nums += 1
            vals[obj.id] = nums
        return vals

    def name_get(self, ids, context={}):
        if not access.check_permission(self._name, "read", ids):
            return [(id, "Permission denied") for id in ids]
        f_name = self._name_field or "name"
        f_image = self._image_field or "image"
        if f_image in self._fields:
            show_image = True
            fields = [f_name, f_image]
        else:
            show_image = False
            fields = [f_name]
        res = self.read(ids, fields)
        for r in res:
            r[f_name] = self.get_full_parent_name(r["id"])
        if show_image:
            return [(r["id"], r[f_name], r[f_image]) for r in res]
        else:
            return [(r["id"], r[f_name]) for r in res]

    def update_sale_prices(self, ids, context={}):
        obj = self.browse(ids[0])
        if not obj.gross_profit:
            raise Exception("Missing gross profit")
        n = 0
        for prod in get_model("product").search_browse(
            [["categ_id", "=", obj.id]]):
            sale_price = round(prod.landed_cost / (1 - obj.gross_profit / 100),
                               2)
            prod.write({
                "gross_profit": obj.gross_profit,
                "sale_price": sale_price
            })
            n += 1
        return {
            "flash": "%d products updated" % n,
        }
Example #15
0
class Expense(Model):
    _name = "account.expense"
    _name_field = "ref"
    _fields = {
        "contact_id":
        fields.Many2One("contact", "Contact", required=True),
        "date":
        fields.Date("Date", required=True),
        "ref":
        fields.Char("Reference", required=True),
        "attach":
        fields.File("Attachment"),
        "tax_type":
        fields.Selection([["tax_ex", "Tax Exclusive"],
                          ["tax_in", "Tax Inclusive"], ["no_tax", "No Tax"]],
                         "Tax Type",
                         required=True),
        "lines":
        fields.One2Many("account.expense.line", "expense_id", "Lines"),
        "amount_subtotal":
        fields.Decimal("Subtotal", function="get_amount", function_multi=True),
        "amount_tax":
        fields.Decimal("Tax Amount",
                       function="get_amount",
                       function_multi=True),
        "amount_total":
        fields.Decimal("Total", function="get_amount", function_multi=True),
        "claim_id":
        fields.Many2One("account.claim", "Claim", on_delete="cascade"),
        "user_id":
        fields.Many2One("base.user", "Receipt Owner", required=True),
        "state":
        fields.Selection(
            [["draft", "Draft"], ["waiting_approval", "Waiting Approval"],
             ["approved", "Approved"], ["declined", "Declined"]],
            "Status",
            required=True),
        "uuid":
        fields.Char("UUID"),
    }
    _order = "date desc,id desc"
    _defaults = {
        "tax_type": "tax_in",
        "uuid": lambda *a: str(uuid.uuid4()),
        "user_id": lambda self, context: int(context["user_id"]),
        "state": "draft",
    }

    def write(self, ids, vals, **kw):
        claim_ids = []
        for obj in self.browse(ids):
            if obj.claim_id:
                claim_ids.append(obj.claim_id.id)
        super().write(ids, vals, **kw)
        claim_id = vals.get("claim_id")
        if claim_id:
            claim_ids.append(claim_id)
        self.function_store(ids)
        if claim_ids:
            get_model("account.claim").function_store(claim_ids)

    def delete(self, ids, **kw):
        claim_ids = []
        for obj in self.browse(ids):
            if obj.claim_id:
                claim_ids.append(obj.claim_id.id)
        super().delete(ids, **kw)
        if claim_ids:
            get_model("account.claim").function_store(claim_ids)

    def get_amount(self, ids, context={}):  # XXX: taxes
        res = {}
        for obj in self.browse(ids):
            vals = {}
            subtotal = 0
            for line in obj.lines:
                subtotal += line.amount
            vals["amount_subtotal"] = subtotal
            vals["amount_tax"] = 0
            vals["amount_total"] = subtotal
            res[obj.id] = vals
        return res

    def update_amounts(self, context):
        data = context["data"]
        data["amount_subtotal"] = 0
        data["amount_tax"] = 0
        tax_type = data["tax_type"]
        for line in data["lines"]:
            if not line:
                continue
            amt = line.get("qty", 0) * line.get("unit_price", 0)
            line["amount"] = amt
            tax_id = line.get("tax_id")
            if tax_id:
                tax = get_model("account.tax.rate").compute_tax(
                    tax_id, amt, tax_type=tax_type)
                data["amount_tax"] += tax
            else:
                tax = 0
            if tax_type == "tax_in":
                data["amount_subtotal"] += amt - tax
            else:
                data["amount_subtotal"] += amt
        data["amount_total"] = data["amount_subtotal"] + data["amount_tax"]
        return data

    def do_submit(self, ids, context={}):
        user_id = None
        for obj in self.browse(ids):
            if user_id is None:
                user_id = obj.user_id.id
            else:
                assert user_id == obj.user_id.id, "Expenses belong to different users"
        vals = {
            "user_id": user_id,
        }
        claim_id = get_model("account.claim").create(vals)
        self.write(ids, {"claim_id": claim_id, "state": "waiting_approval"})
        return {"next": {"name": "claim_waiting_approval"}}

    def do_approve(self, ids, context={}):
        claim_id = None
        for obj in self.browse(ids):
            obj.write({"state": "approved"})
            claim_id = obj.claim_id.id
        return {
            "next": {
                "name": "claim_edit",
                "active_id": claim_id,
            }
        }

    def do_decline(self, ids, context={}):
        claim_id = None
        for obj in self.browse(ids):
            obj.write({"state": "declined"})
            claim_id = obj.claim_id.id
        return {
            "next": {
                "name": "claim_edit",
                "active_id": claim_id,
            }
        }

    def onchange_account(self, context):
        data = context["data"]
        path = context["path"]
        line = get_data_path(data, path, parent=True)
        acc_id = line.get("account_id")
        if not acc_id:
            return {}
        acc = get_model("account.account").browse(acc_id)
        line["tax_id"] = acc.tax_id.id
        data = self.update_amounts(context)
        return data
Example #16
0
class ImportAttendance(Model):
    _name = "hr.import.attendance"
    _string = "Import Attendance"

    _fields = {
        "import_type":
        fields.Selection([["manual", "Manual"], ["auto", "Auto"]],
                         "Import From",
                         required=True),
        "file":
        fields.File("CSV File", required=False),
        "machine_id":
        fields.Many2One("hr.attendance.config", "Machine Config"),
        "encoding":
        fields.Selection([["utf-8", "UTF-8"], ["tis-620", "TIS-620"]],
                         "Encoding",
                         required=True),
        "date":
        fields.Date("Date From"),
        "date_fmt":
        fields.Char("Date Format"),
        "comments":
        fields.One2Many("message", "related_id", "Comments"),
    }
    _order = "time desc"
    _defaults = {
        "import_type": "manual",
        "encoding": "utf-8",
        'date_fmt': '%Y-%m-%d %H:%M:%S'
    }

    def get_data(self, ids, context={}):
        machine = 1
        if context:
            if "machine_id" in context:
                machine = context["machine_id"]

        obj = get_model("hr.attendance.config").browse(machine)
        auth_handler = urllib.request.HTTPBasicAuthHandler()
        auth_handler.add_password(realm='Basic Authentication Application',
                                  uri="http://" + obj.ip_address,
                                  user=obj.user,
                                  passwd=obj.password)
        opener = urllib.request.build_opener(auth_handler)
        urllib.request.install_opener(opener)
        urllib.request.urlopen('http://' + obj.ip_address)
        post_data = {
            'uid': 'extlog.dat'
        }  # POST data for download attendance log
        conn = http.client.HTTPConnection(obj.ip_address)
        params = urllib.parse.urlencode(post_data)
        headers = {
            'Content-type': "application/x-www-form-urlencoded",
            'Accept': 'text/plain'
        }
        conn.request('POST', obj.url_download, params, headers)
        response = conn.getresponse()
        log = response.read()
        res = log.decode('tis-620')
        lines = str(res).split('\r\n')
        if conn:
            conn.close()
        return lines

    def import_auto(self, ids, context={}):
        obj = self.browse(ids)[0]
        date = context.get('date', datetime.today())
        lines = obj.get_data(context=context)
        current_date = datetime.today()
        date_in = date.strftime("%Y-%m-%d 00:00:00")
        date_out = current_date.strftime("%Y-%m-%d 23:59:59")
        count = 0
        detail = []
        st = ""
        for line in lines:

            if line:
                st += line.replace("\t", ",")
                st += '\n'

            line = line.split('\t')
            if len(line) > 1:
                if line[1] >= date_in and line[1] <= date_out:
                    attendance_id = get_model("hr.employee").search(
                        [["attendance_id", "=", line[0]]])
                    dt_time = datetime.strptime(str(line[1]),
                                                "%Y-%m-%d %H:%M:%S")
                    dt_in = dt_time.strftime("%Y-%m-%d 00:00:00")
                    dt_out = dt_time.strftime("%Y-%m-%d 23:59:59")
                    if attendance_id:
                        employee = get_model("hr.employee").browse(
                            attendance_id)[0]
                        have = []
                        have = get_model("hr.attendance").search(
                            [["employee_id", "=", employee.id],
                             ["time", "=", line[1]]])
                        check = False
                        if not have:
                            bf_id = []
                            bf_id = get_model("hr.attendance").search(
                                [["employee_id", "=", employee.id],
                                 ["time", ">=", dt_in], ["time", "<=", dt_out],
                                 ["time", "<", line[1]]])
                            if not bf_id:
                                action = "sign_in"
                            elif bf_id:
                                attend = get_model("hr.attendance").browse(
                                    bf_id)[0]
                                date_get = datetime.strptime(
                                    attend.time, "%Y-%m-%d %H:%M:%S")
                                dt = date_get + timedelta(minutes=1)
                                date_check = datetime.strptime(
                                    str(dt), "%Y-%m-%d %H:%M:%S").strftime(
                                        "%Y-%m-%d %H:%M:%S")
                                if line[1] <= date_check:
                                    check = True
                                if attend.action == "sign_out":
                                    action = "sign_in"
                                elif attend.action == "sign_in":
                                    action = "sign_out"
                            if check is False:
                                count += 1
                                detail.append({
                                    "name":
                                    employee.first_name + " " +
                                    employee.last_name,
                                    "date":
                                    line[1],
                                    "action":
                                    action
                                })
                                vals = {
                                    "employee_id": employee.id,
                                    "time": line[1],
                                    "action": action,
                                }
                                get_model("hr.attendance").create(vals)
        audit_log("Add attendance %s record at %s employee: %s " %
                  (count, date, str(detail)))

        open("/tmp/res.csv", "w").write(st)  # XXX
        return count

    def _import_data(self, ids, context={}):
        obj = self.browse(ids[0])
        count = 0
        if obj.import_type == 'auto':
            if not obj.machine_id:
                raise Exception("Select device to import from")

            context = ({"machine_id": obj.machine_id.id, "date": obj.date})
            count = self.import_auto(ids, context=context)
        else:
            if not obj.file:
                raise Exception("Please give csv file for import data")
            dbname = get_active_db()
            data = open(
                os.path.join("static", "db", dbname, "files", obj.file),
                "rb").read().decode(obj.encoding)
            found_delim = False
            for delim in (",", ";", "\t"):
                try:
                    try:
                        rd = csv.reader(StringIO(data), delimiter=delim)
                    except:
                        raise Exception("Invalid CSV file")
                    headers = next(rd)
                    headers = [h.strip() for h in headers]
                    for h in ["id", "date"]:
                        if not h in headers:
                            raise Exception("Missing header: '%s'" % h)
                    found_delim = True
                    break
                except:
                    pass
            if not found_delim:
                raise Exception("Failed to open CSV file")
            rows = [r for r in rd]
            if not rows:
                raise Exception("Statement is empty")
            formats = [
                "%Y-%m-%d  %H:%M:%S", "%d/%m/%Y  %H:%M:%S",
                "%m/%d/%Y  %H:%M:%S", "%d/%m/%y  %H:%M:%S",
                "%m/%d/%y  %H:%M:%S"
            ]
            date_fmt = None
            for fmt in formats:
                fmt_ok = True
                for row in rows:
                    vals = dict(zip(headers, row))
                    date = vals["date"].strip()
                    if not date:
                        continue
                    try:
                        datetime.strptime(date, fmt)
                    except:
                        fmt_ok = False
                        break
                if fmt_ok:
                    date_fmt = fmt
                    break
            if not date_fmt:
                raise Exception("Could not detect date format")
            for i, row in enumerate(rows):
                vals = dict(zip(headers, row))
                try:
                    date = vals["date"].strip()
                    if not date:
                        raise Exception("Missing date")
                    date = datetime.strptime(
                        date, date_fmt).strftime("%Y-%m-%d %H:%M:%S")
                    date_in = datetime.strptime(
                        date, date_fmt).strftime("%Y-%m-%d 00:00:00")
                    date_out = datetime.strptime(
                        date, date_fmt).strftime("%Y-%m-%d 23:59:59")
                    id_employee = vals["id"].strip().replace(",", "")
                    if not id_employee:
                        raise Exception("missing employeeid")
                    attendance_id = get_model("hr.employee").search(
                        [["attendance_id", "=", id_employee]])
                    if attendance_id:
                        employee = get_model("hr.employee").browse(
                            attendance_id)[0]
                        have_id = []
                        have_id = get_model("hr.attendance").search(
                            [["employee_id", "=", employee.id],
                             ["time", "=", date]])
                        check = False
                        if not have_id:
                            bf_id = []
                            bf_id = get_model("hr.attendance").search(
                                [["employee_id", "=", employee.id],
                                 ["time", ">=", date_in],
                                 ["time", "<=", date_out], ["time", "<",
                                                            date]])
                            if not bf_id:
                                action = "sign_in"
                            elif bf_id:
                                attend = get_model("hr.attendance").browse(
                                    bf_id)[0]
                                date_get = datetime.strptime(
                                    attend.time, date_fmt)
                                dt = date_get + timedelta(minutes=1)
                                date_check = datetime.strptime(
                                    str(dt),
                                    date_fmt).strftime("%Y-%m-%d %H:%M:%S")
                                if date <= date_check:
                                    check = True
                                if attend.action == "sign_out":
                                    action = "sign_in"
                                elif attend.action == "sign_in":
                                    action = "sign_out"
                            if check is False:
                                count += 1
                                vals = {
                                    "time": date,
                                    "employee_id": employee.id,
                                    "action": action,
                                }
                                get_model("hr.attendance").create(vals)

                except Exception as e:
                    audit_log("Failed to get attendance orders", details=e)
                    raise Exception("Error on line %d (%s)" % (i + 2, e))

        return {
            "next": {
                "name": "attend",
                "mode": "list",
            },
            "flash": "Import : %s records" % (count)
        }

    def set_att(self, ids, att_ids, context={}):
        index = 0
        for emp in get_model("hr.employee").search_browse(
            [['work_status', '=', 'working']]):
            if index > len(att_ids) - 1:
                att_ids.append(index)  # XXX generate our-self
            emp.write({
                'attendance_id': att_ids[index],
            })
            print("update %s -> %s" % (emp.code, att_ids[index]))
            index += 1
        print("Done!")

    def import_data(self, ids, context={}):
        obj = self.browse(ids)[0]
        if obj.import_type == 'auto':
            obj.import_auto()
        else:
            if not obj.file:
                raise Exception("File not found")
            if obj.file.split(".")[-1] != 'csv':
                raise Exception("Wrong File")
            fpath = get_file_path(obj.file)
            data = open(fpath, "r").read().split("\n")
            att_ids = []
            records = {}
            for row in data:
                lines = row.split(",")
                if not lines:
                    continue
                size = len(lines)
                if size < 2:
                    continue
                if size > 2:
                    raise Exception("Wrong File")
                att_id = lines[0]
                att_date = lines[1]
                if not records.get(att_id):
                    records[att_id] = []
                records[att_id].append(att_date)
                continue
                # TODO Check format date
                if att_id not in att_ids:
                    att_ids.append(att_id)
            # self.set_att(ids,att_ids,context=context)
            emps = {
                emp['attendance_id']: emp['id']
                for emp in get_model("hr.employee").search_read(
                    [], ['attendance_id'])
            }
            att = get_model("hr.attendance")
            at_ids = att.search([])
            # XXX testing
            att.delete(at_ids)
            for att_id, lines in records.items():
                att_id = int(att_id)
                date_list = []
                for line in lines:
                    datetime = line
                    date = datetime.split(" ")[0]
                    action = 'sign_in'
                    if date in date_list:
                        action = 'sign_out'
                        # FIXME find the last record and overwrite time
                    date_list.append(date)
                    att.create({
                        'employee_id': emps[att_id],
                        'action': action,
                        'time': datetime,
                    })
            print("Done!")
Example #17
0
class Invoice(Model):
    _name = "account.invoice"
    _string = "Invoice"
    _audit_log = True
    _key = ["company_id", "number"]
    _name_field = "number"
    _multi_company = True
    _fields = {
        "type": fields.Selection([["out", "Receivable"], ["in", "Payable"]], "Type", required=True),
        "inv_type": fields.Selection([["invoice", "Invoice"], ["credit", "Credit Note"], ["debit", "Debit Note"]], "Subtype", required=True, search=True),
        "number": fields.Char("Number", search=True),
        "ref": fields.Char("Ref", size=256, search=True),
        "memo": fields.Char("Memo", size=1024, search=True),
        "contact_id": fields.Many2One("contact", "Contact", required=True, search=True),
        "contact_credit": fields.Decimal("Outstanding Credit", function="get_contact_credit"),
        "account_id": fields.Many2One("account.account", "Account"),
        "date": fields.Date("Date", required=True, search=True),
        "due_date": fields.Date("Due Date", search=True),
        "currency_id": fields.Many2One("currency", "Currency", required=True, search=True),
        "tax_type": fields.Selection([["tax_ex", "Tax Exclusive"], ["tax_in", "Tax Inclusive"], ["no_tax", "No Tax"]], "Tax Type", required=True),
        "state": fields.Selection([("draft", "Draft"), ("waiting_approval", "Waiting Approval"), ("waiting_payment", "Waiting Payment"), ("paid", "Paid"), ("voided", "Voided")], "Status", function="get_state", store=True, function_order=20, search=True),
        "lines": fields.One2Many("account.invoice.line", "invoice_id", "Lines"),
        "amount_subtotal": fields.Decimal("Subtotal", function="get_amount", function_multi=True, store=True),
        "amount_tax": fields.Decimal("Tax Amount", function="get_amount", function_multi=True, store=True),
        "amount_total": fields.Decimal("Total", function="get_amount", function_multi=True, store=True),
        "amount_paid": fields.Decimal("Paid Amount", function="get_amount", function_multi=True, store=True),
        "amount_due": fields.Decimal("Due Amount", function="get_amount", function_multi=True, store=True),
        "amount_credit_total": fields.Decimal("Total Credit", function="get_amount", function_multi=True, store=True),
        "amount_credit_remain": fields.Decimal("Remaining Credit", function="get_amount", function_multi=True, store=True),
        "amount_total_cur": fields.Decimal("Total Amount", function="get_amount", function_multi=True, store=True),
        "amount_due_cur": fields.Decimal("Due Amount", function="get_amount", function_multi=True, store=True),
        "amount_paid_cur": fields.Decimal("Paid Amount", function="get_amount", function_multi=True, store=True),
        "amount_credit_remain_cur": fields.Decimal("Remaining Credit", function="get_amount", function_multi=True, store=True),
        "amount_rounding": fields.Decimal("Rounding", function="get_amount", function_multi=True, store=True),
        "qty_total": fields.Decimal("Total Quantity", function="get_qty_total"),
        "attachment": fields.File("Attachment"),
        "payments": fields.One2Many("account.payment.line", "invoice_id", "Payments", condition=[["payment_id.state", "=", "posted"]]), # XXX: deprecated
        "move_id": fields.Many2One("account.move", "Journal Entry"),
        "reconcile_move_line_id": fields.Many2One("account.move.line", "Reconcile Item"), # XXX: deprecated
        "credit_alloc": fields.One2Many("account.credit.alloc", "credit_id", "Credit Allocation"), # XXX: deprecated
        "credit_notes": fields.One2Many("account.credit.alloc", "invoice_id", "Credit Notes"), # XXX: deprecated
        "currency_rate": fields.Decimal("Currency Rate", scale=6),
        "payment_id": fields.Many2One("account.payment", "Payment"),
        "related_id": fields.Reference([["sale.order", "Sales Order"], ["purchase.order", "Purchase Order"], ["production.order","Production Order"], ["project", "Project"], ["job", "Service Order"], ["service.contract", "Service Contract"]], "Related To"),
        "company_id": fields.Many2One("company", "Company"),
        "amount_discount": fields.Decimal("Discount", function="get_discount"),
        "bill_address_id": fields.Many2One("address", "Billing Address"),
        "comments": fields.One2Many("message", "related_id", "Comments"),
        "documents": fields.One2Many("document", "related_id", "Documents"),
        "fixed_assets": fields.One2Many("account.fixed.asset", "invoice_id", "Fixed Assets"),
        "tax_no": fields.Char("Tax No."),
        "tax_branch_no": fields.Char("Tax Branch No."),
        "pay_method_id": fields.Many2One("payment.method", "Payment Method"),
        "journal_id": fields.Many2One("account.journal", "Journal"),
        "sequence_id": fields.Many2One("sequence", "Sequence"),
        "original_invoice_id": fields.Many2One("account.invoice", "Original Invoice"),
        "product_id": fields.Many2One("product","Product",store=False,function_search="search_product",search=True),
        "taxes": fields.One2Many("account.invoice.tax","invoice_id","Taxes"),
        "agg_amount_total": fields.Decimal("Total Amount", agg_function=["sum", "amount_total"]),
        "agg_amount_subtotal": fields.Decimal("Total Amount w/o Tax", agg_function=["sum", "amount_subtotal"]),
        "year": fields.Char("Year", sql_function=["year", "date"]),
        "quarter": fields.Char("Quarter", sql_function=["quarter", "date"]),
        "month": fields.Char("Month", sql_function=["month", "date"]),
        "week": fields.Char("Week", sql_function=["week", "date"]),
        "transaction_no": fields.Char("Transaction ID",search=True),
        "payment_entries": fields.One2Many("account.move.line",None,"Payment Entries",function="get_payment_entries"),
        "journal_date": fields.Date("Journal Date"),
    }
    _order = "date desc,number desc"

    def _get_currency(self, context={}):
        settings = get_model("settings").browse(1)
        return settings.currency_id.id

    def _get_number(self, context={}):
        defaults = context.get("defaults")
        if defaults:  # XXX
            type = defaults.get("type")
            inv_type = defaults.get("inv_type")
        else:
            type = context.get("type")
            inv_type = context.get("inv_type")
        seq_id = context.get("sequence_id")
        if not seq_id:
            seq_type = None
            if type == "out":
                if inv_type in ("invoice", "prepay"):
                    seq_type = "cust_invoice"
                elif inv_type == "credit":
                    seq_type = "cust_credit"
                elif inv_type == "debit":
                    seq_type = "cust_debit"
            elif type == "in":
                if inv_type in ("invoice", "prepay"):
                    seq_type = "supp_invoice"
                elif inv_type == "credit":
                    seq_type = "supp_credit"
                elif inv_type == "debit":
                    seq_type = "supp_debit"
            if not seq_type:
                return
            seq_id = get_model("sequence").find_sequence(type=seq_type)
            if not seq_id:
                return None
        while 1:
            num = get_model("sequence").get_next_number(seq_id, context=context)
            res = self.search([["number", "=", num]])
            if not res:
                return num
            get_model("sequence").increment_number(seq_id, context=context)

    _defaults = {
        "state": "draft",
        "currency_id": _get_currency,
        "tax_type": "tax_ex",
        "number": _get_number,
        "date": lambda *a: time.strftime("%Y-%m-%d"),
        "company_id": lambda *a: get_active_company(),
    }
    _constraints = ["check_fields"]

    def search_product(self, clause, context={}):
        product_id = clause[2]
        product = get_model("product").browse(product_id)
        product_ids = [product_id]
        for var in product.variants:
            product_ids.append(var.id)
        for comp in product.components:
            product_ids.append(comp.component_id.id)
        invoice_ids = []
        for line in get_model("account.invoice.line").search_browse([["product_id","in",product_ids]]):
            invoice_ids.append(line.invoice_id.id)
        cond = [["id","in",invoice_ids]]
        return cond

    def check_fields(self, ids, context={}):
        for obj in self.browse(ids):
            if obj.state in ("waiting_approval", "waiting_payment"):
                if obj.inv_type == "invoice":
                    if not obj.due_date:
                        raise Exception("Missing due date")
                # if not obj.lines: # XXX: in myob, lines can be empty...
                #    raise Exception("Lines are empty")

    def name_get(self, ids, context={}):
        vals = []
        for obj in self.browse(ids):
            name = obj.number
            if not name:
                if obj.inv_type == "invoice":
                    name = "Invoice"
                elif obj.inv_type == "credit":
                    name = "Credit Note"
                elif obj.inv_type == "prepay":
                    name = "Prepayment"
                elif obj.inv_type == "overpay":
                    name = "Overpayment"
            if obj.ref:
                name += " [%s]" % obj.ref
            vals.append((obj.id, name))
        return vals

    def create(self, vals, context={}):
        id = super(Invoice, self).create(vals, context=context)
        self.function_store([id])
        return id

    def write(self, ids, vals, **kw):
        super(Invoice, self).write(ids, vals, **kw)
        self.function_store(ids)
        sale_ids = []
        purch_ids = []
        for inv in self.browse(ids):
            for line in inv.lines:
                if line.sale_id:
                    sale_ids.append(line.sale_id.id)
                if line.purch_id:
                    purch_ids.append(line.purch_id.id)
        if sale_ids:
            get_model("sale.order").function_store(sale_ids)
        if purch_ids:
            get_model("purchase.order").function_store(purch_ids)

    def delete(self, ids, context={}):
        sale_ids = []
        purch_ids = []
        for inv in self.browse(ids):
            if inv.inv_type not in ("prepay", "overpay"):
                if inv.state not in ("draft", "waiting_approval", "voided"):
                    raise Exception("Can't delete invoice with this status")
            for line in inv.lines:
                if line.sale_id:
                    sale_ids.append(line.sale_id.id)
                if line.purch_id:
                    purch_ids.append(line.purch_id.id)
        super(Invoice, self).delete(ids, context=context)
        if sale_ids:
            get_model("sale.order").function_store(sale_ids)
        if purch_ids:
            get_model("purchase.order").function_store(purch_ids)

    def function_store(self, ids, field_names=None, context={}):
        super().function_store(ids, field_names, context)
        sale_ids = []
        purch_ids = []
        for obj in self.browse(ids):
            for line in obj.lines:
                if line.sale_id:
                    sale_ids.append(line.sale_id.id)
                if line.purch_id:
                    purch_ids.append(line.purch_id.id)
        if sale_ids:
            get_model("sale.order").function_store(sale_ids)
        if purch_ids:
            get_model("purchase.order").function_store(purch_ids)

    def submit_for_approval(self, ids, context={}):
        for obj in self.browse(ids):
            if obj.state != "draft":
                raise Exception("Invalid state")
            obj.write({"state": "waiting_approval"})
        self.trigger(ids, "submit_for_approval")
        return {
            "flash": "Invoice submitted for approval."
        }

    def approve(self, ids, context={}):
        obj = self.browse(ids)[0]
        if obj.state not in ("draft", "waiting_approval"):
            raise Exception("Invalid state")
        obj.post()
        if obj.inv_type == "invoice":
            msg = "Invoice approved."
            if obj.type == "in":
                obj.create_fixed_assets()
        elif obj.inv_type == "credit":
            msg = "Credit note approved."
        elif obj.inv_type == "debit":
            msg = "Debit note approved."
        return {
            "flash": msg,
        }

    def calc_taxes(self,ids,context={}):
        obj=self.browse(ids[0])
        obj.taxes.delete()
        settings = get_model("settings").browse(1)
        if obj.currency_rate:
            currency_rate = obj.currency_rate
        else:
            if obj.currency_id.id == settings.currency_id.id:
                currency_rate = 1
            else:
                rate_from = obj.currency_id.get_rate(date=obj.date)
                if not rate_from:
                    raise Exception("Missing currency rate for %s" % obj.currency_id.code)
                rate_to = settings.currency_id.get_rate(date=obj.date)
                if not rate_to:
                    raise Exception("Missing currency rate for %s" % settings.currency_id.code)
                currency_rate = rate_from / rate_to
            obj.write({"currency_rate": currency_rate})
        taxes = {}
        tax_nos = []
        total_amt = 0
        total_base = 0
        total_tax = 0
        for line in obj.lines:
            cur_amt = get_model("currency").convert(
                line.amount, obj.currency_id.id, settings.currency_id.id, rate=currency_rate)
            tax_id = line.tax_id
            if tax_id and obj.tax_type != "no_tax":
                base_amt = get_model("account.tax.rate").compute_base(tax_id, cur_amt, tax_type=obj.tax_type)
                if settings.rounding_account_id:
                    base_amt=get_model("currency").round(obj.currency_id.id,base_amt)
                tax_comps = get_model("account.tax.rate").compute_taxes(tax_id, base_amt, when="invoice")
                for comp_id, tax_amt in tax_comps.items():
                    tax_vals = taxes.setdefault(comp_id, {"tax_amt": 0, "base_amt": 0})
                    tax_vals["tax_amt"] += tax_amt
                    tax_vals["base_amt"] += base_amt
            else:
                base_amt = cur_amt
        for comp_id, tax_vals in taxes.items():
            comp = get_model("account.tax.component").browse(comp_id)
            acc_id = comp.account_id.id
            if not acc_id:
                raise Exception("Missing account for tax component %s" % comp.name)
            vals = {
                "invoice_id": obj.id,
                "tax_comp_id": comp_id,
                "base_amount": get_model("currency").round(obj.currency_id.id,tax_vals["base_amt"]),
                "tax_amount": get_model("currency").round(obj.currency_id.id,tax_vals["tax_amt"]),
            }
            if comp.type in ("vat", "vat_exempt"):
                if obj.type == "out":
                    if obj.tax_no:
                        tax_no = obj.tax_no
                    else:
                        tax_no = self.gen_tax_no(exclude=tax_nos, context={"date": obj.date})
                        tax_nos.append(tax_no)
                        obj.write({"tax_no": tax_no})
                    vals["tax_no"] = tax_no
                elif obj.type == "in":
                    vals["tax_no"] = obj.tax_no
            get_model("account.invoice.tax").create(vals)

    def post(self, ids, context={}):
        t0 = time.time()
        settings = get_model("settings").browse(1)
        for obj in self.browse(ids):
            if obj.related_id:
                for line in obj.lines:
                    if not line.related_id:
                        line.write({"related_id":"%s,%d"%(obj.related_id._model,obj.related_id.id)})
            obj.check_related()
            #if obj.amount_total == 0:
            #    raise Exception("Invoice total is zero") # need in some cases
            if obj.amount_total < 0:
                raise Exception("Invoice total is negative")
            if not obj.taxes:
                obj.calc_taxes()
                obj=obj.browse()[0]
            contact = obj.contact_id
            if obj.type == "out":
                account_id = contact.account_receivable_id.id or settings.account_receivable_id.id
                if not account_id:
                    raise Exception("Account receivable not found")
            elif obj.type == "in":
                account_id = contact.account_payable_id.id or settings.account_payable_id.id
                if not account_id:
                    raise Exception("Account payable not found")
            account=get_model("account.account").browse(account_id)
            if account.currency_id.id!=obj.currency_id.id:
                raise Exception("Currency of accounts %s is different than invoice (%s / %s)"%(account.code,account.currency_id.code,obj.currency_id.code))
            sign = obj.type == "out" and 1 or -1
            if obj.inv_type == "credit":
                sign *= -1
            obj.write({"account_id": account_id})
            if obj.type == "out":
                desc = "Sale; " + contact.name
            elif obj.type == "in":
                desc = "Purchase; " + contact.name
            if obj.type == "out":
                journal_id = obj.journal_id.id or settings.sale_journal_id.id
                if not journal_id:
                    raise Exception("Sales journal not found")
            elif obj.type == "in":
                journal_id = obj.journal_id.id or settings.purchase_journal_id.id
                if not journal_id:
                    raise Exception("Purchases journal not found")
            if obj.currency_rate:
                currency_rate = obj.currency_rate
            else:
                if obj.currency_id.id == settings.currency_id.id:
                    currency_rate = 1
                else:
                    rate_from = obj.currency_id.get_rate(date=obj.date)
                    if not rate_from:
                        raise Exception("Missing currency rate for %s" % obj.currency_id.code)
                    rate_to = settings.currency_id.get_rate(date=obj.date)
                    if not rate_to:
                        raise Exception("Missing currency rate for %s" % settings.currency_id.code)
                    currency_rate = rate_from / rate_to
                obj.write({"currency_rate": currency_rate})
            move_vals = {
                "journal_id": journal_id,
                "number": obj.number,
                "date": obj.journal_date or obj.date,
                "ref": obj.ref,
                "narration": desc,
                "related_id": "account.invoice,%s" % obj.id,
                "company_id": obj.company_id.id,
            }
            lines = []
            t01 = time.time()
            for line in obj.lines:
                cur_amt = get_model("currency").convert(
                    line.amount, obj.currency_id.id, settings.currency_id.id, rate=currency_rate)
                tax_id = line.tax_id
                if tax_id and obj.tax_type != "no_tax":
                    base_amt = get_model("account.tax.rate").compute_base(tax_id, cur_amt, tax_type=obj.tax_type)
                else:
                    base_amt = cur_amt
                acc_id = line.account_id.id
                if not acc_id:
                    raise Exception("Missing line account for invoice line '%s'" % line.description)
                amt = base_amt * sign
                line_vals = {
                    "description": line.description,
                    "account_id": acc_id,
                    "credit": amt > 0 and amt or 0,
                    "debit": amt < 0 and -amt or 0,
                    "track_id": line.track_id.id,
                    "track2_id": line.track2_id.id,
                    "contact_id": contact.id,
                }
                lines.append(line_vals)
            for tax in obj.taxes:
                comp = tax.tax_comp_id
                acc_id = comp.account_id.id
                if not acc_id:
                    raise Exception("Missing account for tax component %s" % comp.name)
                amt = sign * tax.tax_amount
                line_vals = {
                    "description": desc,
                    "account_id": acc_id,
                    "credit": amt > 0 and amt or 0,
                    "debit": amt < 0 and -amt or 0,
                    "tax_comp_id": comp.id,
                    "tax_base": tax.base_amount,
                    "contact_id": contact.id,
                    "invoice_id": obj.id,
                    "tax_no": tax.tax_no,
                    "tax_date": obj.date,
                }
                lines.append(line_vals)
            t02 = time.time()
            dt01 = (t02 - t01) * 1000
            print("post dt01", dt01)
            groups = {}
            keys = ["description", "account_id", "track_id", "tax_comp_id", "contact_id", "invoice_id", "reconcile_id"]
            for line in lines:
                key_val = tuple(line.get(k) for k in keys)
                if key_val in groups:
                    group = groups[key_val]
                    group["debit"] += line["debit"]
                    group["credit"] += line["credit"]
                    if line.get("tax_base"):
                        if "tax_base" not in group:
                            group["tax_base"] = 0
                        group["tax_base"] += line["tax_base"]
                else:
                    groups[key_val] = line.copy()
            group_lines = sorted(groups.values(), key=lambda l: (l["debit"], l["credit"]))
            for line in group_lines:
                amt = line["debit"] - line["credit"]
                amt = get_model("currency").round(obj.currency_id.id,amt)
                if amt >= 0:
                    line["debit"] = amt
                    line["credit"] = 0
                else:
                    line["debit"] = 0
                    line["credit"] = -amt
            amt = 0
            for line in group_lines:
                amt -= line["debit"] - line["credit"]
            line_vals = {
                "description": desc,
                "account_id": account_id,
                "debit": amt > 0 and amt or 0,
                "credit": amt < 0 and -amt or 0,
                "due_date": obj.due_date,
                "contact_id": contact.id,
            }
            acc = get_model("account.account").browse(account_id)
            if acc.currency_id.id != settings.currency_id.id:
                if acc.currency_id.id != obj.currency_id.id:
                    raise Exception("Invalid account currency for this invoice: %s" % acc.code)
                line_vals["amount_cur"] = obj.amount_total
            move_vals["lines"] = [("create", line_vals)]
            move_vals["lines"] += [("create", vals) for vals in group_lines]
            t03 = time.time()
            dt02 = (t03 - t02) * 1000
            print("post dt02", dt02)
            move_id = get_model("account.move").create(move_vals)
            t04 = time.time()
            dt03 = (t04 - t03) * 1000
            print("post dt03", dt03)
            get_model("account.move").post([move_id])
            t05 = time.time()
            dt04 = (t05 - t04) * 1000
            print("post dt04", dt04)
            obj.write({"move_id": move_id, "state": "waiting_payment"})
            t06 = time.time()
            dt05 = (t06 - t05) * 1000
            print("post dt05", dt05)
        t1 = time.time()
        dt = (t1 - t0) * 1000
        print("invoice.post <<< %d ms" % dt)

    def repost_invoices(self, context={}):  # XXX
        ids = self.search([["state", "in", ("waiting_payment", "paid")]], order="date")
        for obj in self.browse(ids):
            print("invoice %d..." % obj.id)
            if not obj.move_id:
                raise Exception("No journal entry for invoice #%d" % obj.id)
            obj.move_id.delete()
            obj.post()

    def void(self, ids, context={}):
        print("invoice.void", ids)
        obj = self.browse(ids)[0]
        if obj.state not in ("draft", "waiting_payment"):
            raise Exception("Invalid invoice state")
        if obj.payment_entries:
            raise Exception("Can't void invoice because there are still related payment entries")
        if obj.move_id:
            obj.move_id.void()
            obj.move_id.delete()
        obj.write({"state": "voided"})

    def to_draft(self, ids, context={}):
        obj = self.browse(ids)[0]
        if obj.state not in ("waiting_payment","voided"):
            raise Exception("Invalid status")
        if obj.payment_entries:
            raise Exception("There are still payment entries for this invoice")
        if obj.move_id:
            obj.move_id.void()
            obj.move_id.delete()
        if obj.reconcile_move_line_id: # XXX: deprecated
            obj.write({"reconcile_move_line_id":None})
        obj.taxes.delete()
        obj.write({"state": "draft"})

    def get_amount(self, ids, context={}):
        t0 = time.time()
        settings = get_model("settings").browse(1)
        res = {}
        for inv in self.browse(ids):
            vals = {}
            subtotal = 0
            tax = 0
            for line in inv.lines:
                tax_id = line.tax_id
                if tax_id and inv.tax_type != "no_tax":
                    base_amt = get_model("account.tax.rate").compute_base(tax_id, line.amount, tax_type=inv.tax_type)
                    tax_comps = get_model("account.tax.rate").compute_taxes(tax_id, base_amt, when="invoice")
                    for comp_id, tax_amt in tax_comps.items():
                        tax += tax_amt
                else:
                    base_amt = line.amount
                subtotal += base_amt
            subtotal=get_model("currency").round(inv.currency_id.id,subtotal)
            tax=get_model("currency").round(inv.currency_id.id,tax)
            vals["amount_subtotal"] = subtotal
            if inv.taxes:
                tax=sum(t.tax_amount for t in inv.taxes)
            vals["amount_tax"] = tax
            if inv.tax_type == "tax_in":
                vals["amount_rounding"] = sum(l.amount for l in inv.lines) - (subtotal + tax)
            else:
                vals["amount_rounding"] = 0
            vals["amount_total"] = subtotal + tax + vals["amount_rounding"]
            vals["amount_total_cur"] = get_model("currency").convert(
                vals["amount_total"], inv.currency_id.id, settings.currency_id.id, round=True, rate=inv.currency_rate)
            vals["amount_credit_total"] = vals["amount_total"]
            paid_amt = 0
            for pmt in inv.payment_entries:
                if pmt.amount_cur is not None:
                    pmt_amt=abs(pmt.amount_cur) # XXX: no need for abs, amount_cur always>=0
                else:
                    if inv.type == "in":
                        pmt_amt=pmt.debit
                    else:
                        pmt_amt=pmt.credit
                paid_amt+=pmt_amt
            vals["amount_paid"] = paid_amt
            if inv.inv_type in ("invoice", "debit"):
                vals["amount_due"] = vals["amount_total"] - paid_amt
            elif inv.inv_type in ("credit", "prepay", "overpay"):
                cred_amt = 0
                for pmt in inv.payment_entries:
                    if pmt.amount_cur is not None:
                        pmt_amt=abs(pmt.amount_cur)
                    else:
                        if inv.type == "in":
                            pmt_amt=pmt.credit
                        else:
                            pmt_amt=pmt.debit
                    cred_amt += pmt_amt
                vals["amount_credit_remain"] = vals["amount_total"] - cred_amt
                vals["amount_due"] = -vals["amount_credit_remain"]
            vals["amount_due_cur"] = get_model("currency").convert(
                vals["amount_due"], inv.currency_id.id, settings.currency_id.id, round=True, rate=inv.currency_rate)
            vals["amount_paid_cur"] = get_model("currency").convert(
                vals["amount_paid"], inv.currency_id.id, settings.currency_id.id, round=True, rate=inv.currency_rate)
            vals["amount_credit_remain_cur"] = get_model("currency").convert(
                vals.get("amount_credit_remain", 0), inv.currency_id.id, settings.currency_id.id, round=True, rate=inv.currency_rate)
            res[inv.id] = vals
        t1 = time.time()
        dt = (t1 - t0) * 1000
        print("invoice.get_amount <<< %d ms" % dt)
        return res

    def get_qty_total(self, ids, context={}):
        res = {}
        for obj in self.browse(ids):
            qty = sum([line.qty or 0 for line in obj.lines])
            res[obj.id] = qty
        return res

    def update_amounts(self, context):
        data = context["data"]
        settings=get_model("settings").browse(1)
        currency_id = data["currency_id"]
        data["amount_subtotal"] = 0
        data["amount_tax"] = 0
        tax_type = data["tax_type"]
        tax_in_total = 0
        for line in data["lines"]:
            if not line:
                continue
            if line.get("unit_price") is not None:
                amt = (line.get("qty") or 0) * (line.get("unit_price") or 0)
                if line.get("discount"):
                    disc = amt * line["discount"] / 100
                    amt -= disc
                if line.get("discount_amount"):
                    amt -= line["discount_amount"]
                line["amount"] = amt
            else:
                amt = line.get("amount") or 0
            tax_id = line.get("tax_id")
            if tax_id and tax_type != "no_tax":
                base_amt = get_model("account.tax.rate").compute_base(tax_id, amt, tax_type=tax_type)
                tax_comps = get_model("account.tax.rate").compute_taxes(tax_id, base_amt, when="invoice")
                for comp_id, tax_amt in tax_comps.items():
                    data["amount_tax"] += tax_amt
            else:
                base_amt = amt
            data["amount_subtotal"] += base_amt
        if tax_type == "tax_in":
            data["amount_rounding"] = sum(
                l.get("amount") or 0 for l in data["lines"] if l) - (data["amount_subtotal"] + data["amount_tax"])
        else:
            data["amount_rounding"] = 0
        data["amount_total"] = data["amount_subtotal"] + data["amount_tax"] + data["amount_rounding"]
        return data

    def onchange_product(self, context):
        data = context["data"]
        type = data["type"]
        path = context["path"]
        contact_id = data["contact_id"]
        contact = get_model("contact").browse(contact_id)
        line = get_data_path(data, path, parent=True)
        prod_id = line.get("product_id")
        if not prod_id:
            return {}
        prod = get_model("product").browse(prod_id)
        line["description"] = prod.description
        line["qty"] = 1
        if prod.uom_id is not None:
            line["uom_id"] = prod.uom_id.id
        if type == "out":
            if prod.sale_price is not None:
                line["unit_price"] = prod.sale_price
            if prod.sale_account_id is not None:
                line["account_id"] = prod.sale_account_id.id or prod.categ_id.sale_account_id.id
            if prod.sale_tax_id is not None:
                line["tax_id"] = contact.tax_receivable_id.id or prod.sale_tax_id.id or prod.categ_id.sale_tax_id.id
        elif type == "in":
            if prod.purchase_price is not None:
                line["unit_price"] = prod.purchase_price
            if prod.purchase_account_id is not None:
                line["account_id"] = prod.purchase_account_id.id or prod.categ_id.purchase_account_id.id
            if prod.purchase_tax_id is not None:
                line["tax_id"] = contact.tax_payable_id.id or prod.purchase_tax_id.id or prod.categ_id.purchase_tax_id.id
        data = self.update_amounts(context)
        return data

    def onchange_account(self, context):
        data = context["data"]
        path = context["path"]
        line = get_data_path(data, path, parent=True)
        acc_id = line.get("account_id")
        if not acc_id:
            return {}
        acc = get_model("account.account").browse(acc_id)
        line["tax_id"] = acc.tax_id.id
        data = self.update_amounts(context)
        return data

    def onchange_contact(self, context):
        data = context["data"]
        contact_id = data.get("contact_id")
        if not contact_id:
            return {}
        contact = get_model("contact").browse(contact_id)
        data["bill_address_id"] = contact.get_address(pref_type="billing")
        if data["type"] == "out":
            data["journal_id"] = contact.sale_journal_id.id
        elif data["type"] == "in":
            data["journal_id"] = contact.purchase_journal_id.id
        self.onchange_journal(context=context)
        if contact.currency_id:
            data["currency_id"] = contact.currency_id.id
        else:
            settings = get_model("settings").browse(1)
            data["currency_id"] = settings.currency_id.id
        return data

    def view_invoice(self, ids, context={}):
        obj = self.browse(ids[0])
        if obj.type == "out":
            action = "cust_invoice"
            if obj.inv_type == "invoice":
                layout = "cust_invoice_form"
            elif obj.inv_type == "credit":
                layout = "cust_credit_form"
            elif obj.inv_type == "debit":
                layout = "cust_debit_form"
            elif obj.inv_type == "prepay":
                layout = "cust_prepay_form"
            elif obj.inv_type == "overpay":
                layout = "cust_overpay_form"
        elif obj.type == "in":
            action = "supp_invoice"
            if obj.inv_type == "invoice":
                layout = "supp_invoice_form"
            elif obj.inv_type == "credit":
                layout = "supp_credit_form"
            elif obj.inv_type == "debit":
                layout = "supp_debit_form"
            elif obj.inv_type == "prepay":
                layout = "supp_prepay_form"
            elif obj.inv_type == "overpay":
                layout = "supp_overpay_form"
        next_action={
            "name": action,
            "mode": "form",
            "form_view_xml": layout,
            "active_id": obj.id,
        }
        call_action=context.get("action",{})
        if call_action.get("tab_no"):
            next_action["tab_no"]=call_action["tab_no"]
        if call_action.get("offset"):
            next_action["offset"]=call_action["offset"]
        if call_action.get("search_condition"):
            next_action["search_condition"]=call_action["search_condition"]
        return {
            "next": next_action,
        }

    def get_contact_credit(self, ids, context={}):
        obj = self.browse(ids[0])
        contact = get_model("contact").browse(obj.contact_id.id, context={"currency_id": obj.currency_id.id})
        vals = {}
        if obj.type == "out":
            amt = contact.receivable_credit
        elif obj.type == "in":
            amt = contact.payable_credit
        vals[obj.id] = amt
        return vals

    def get_state(self, ids, context={}):
        vals = {}
        for obj in self.browse(ids):
            state = obj.state
            if state == "waiting_payment":
                if obj.inv_type in ("invoice", "debit"):
                    if obj.amount_due == 0:
                        state = "paid"
                elif obj.inv_type in ("credit", "prepay", "overpay"):
                    if obj.amount_credit_remain == 0:
                        state = "paid"
            elif state == "paid":
                if obj.inv_type in ("invoice", "debit"):
                    if obj.amount_due > 0:
                        state = "waiting_payment"
                elif obj.inv_type in ("credit", "prepay", "overpay"):
                    if obj.amount_credit_remain > 0:
                        state = "waiting_payment"
            vals[obj.id] = state
        return vals

    def copy(self, ids, context):
        obj = self.browse(ids)[0]
        vals = {
            "type": obj.type,
            "inv_type": obj.inv_type,
            "ref": obj.ref,
            "contact_id": obj.contact_id.id,
            "currency_id": obj.currency_id.id,
            "tax_type": obj.tax_type,
            "memo": obj.memo,
            "lines": [],
        }
        if obj.related_id:
            vals["related_id"] = "%s,%s" % (obj.related_id._model, obj.related_id.id)
        for line in obj.lines:
            line_vals = {
                "product_id": line.product_id.id,
                "description": line.description,
                "qty": line.qty,
                "uom_id": line.uom_id.id,
                "unit_price": line.unit_price,
                "tax_id": line.tax_id.id,
                "account_id": line.account_id.id,
                "sale_id": line.sale_id.id,
                "purch_id": line.purch_id.id,
                "amount": line.amount,
            }
            vals["lines"].append(("create", line_vals))
        new_id = self.create(vals, context={"type": obj.type, "inv_type": obj.inv_type})
        new_obj = self.browse(new_id)
        if obj.type == "out":
            msg = "Invoice %s copied to %s" % (obj.number, new_obj.number)
        else:
            msg = "Invoice copied"
        return {
            "next": {
                "name": "view_invoice",
                "active_id": new_id,
            },
            "flash": msg,
        }

    def copy_to_credit_note(self, ids, context):
        obj = self.browse(ids)[0]
        vals = {
            "type": obj.type,
            "inv_type": "credit",
            "ref": obj.number,
            "contact_id": obj.contact_id.id,
            "bill_address_id": obj.bill_address_id.id,
            "currency_id": obj.currency_id.id,
            "currency_rate": obj.currency_rate,
            "tax_type": obj.tax_type,
            "memo": obj.memo,
            "tax_no": obj.tax_no,
            "pay_method_id": obj.pay_method_id.id,
            "original_invoice_id": obj.id,
            "lines": [],
        }
        if obj.related_id:
            vals["related_id"] = "%s,%s" % (obj.related_id._model, obj.related_id.id)
        for line in obj.lines:
            line_vals = {
                "product_id": line.product_id.id,
                "description": line.description,
                "qty": line.qty,
                "uom_id": line.uom_id.id,
                "unit_price": line.unit_price,
                "tax_id": line.tax_id.id,
                "account_id": line.account_id.id,
                "sale_id": line.sale_id.id,
                "purch_id": line.purch_id.id,
                "amount": line.amount,
            }
            vals["lines"].append(("create", line_vals))
        new_id = self.create(vals, context={"type": vals["type"], "inv_type": vals["inv_type"]})
        new_obj = self.browse(new_id)
        msg = "Credit note %s created from invoice %s" % (new_obj.number, obj.number)
        return {
            "next": {
                "name": "view_invoice",
                "active_id": new_id,
            },
            "flash": msg,
            "invoice_id": new_id,
        }

    def view_journal_entry(self, ids, context={}):
        obj = self.browse(ids)[0]
        return {
            "next": {
                "name": "journal_entry",
                "mode": "form",
                "active_id": obj.move_id.id,
            }
        }

    def gen_tax_no(self, exclude=None, context={}):
        company_id = get_active_company()  # XXX: improve this?
        seq_id = get_model("sequence").find_sequence(type="tax_no")
        if not seq_id:
            return None
        while 1:
            num = get_model("sequence").get_next_number(seq_id, context=context)
            if exclude and num in exclude:
                get_model("sequence").increment_number(seq_id, context=context)
                continue
            res = get_model("account.move.line").search([["tax_no", "=", num], ["move_id.company_id", "=", company_id]])
            if not res:
                return num
            get_model("sequence").increment_number(seq_id, context=context)

    def get_discount(self, ids, context={}):
        vals = {}
        for obj in self.browse(ids):
            amt = 0
            for line in obj.lines:
                amt += line.amount_discount
            vals[obj.id] = amt
        return vals

    def create_fixed_assets(self, ids, context={}):
        for obj in self.browse(ids):
            if obj.fixed_assets:
                raise Exception("Fixed assets already created for invoice %s" % obj.number)
            for line in obj.lines:
                acc = line.account_id
                if acc.type != "fixed_asset":
                    continue
                ass_type = acc.fixed_asset_type_id
                if not ass_type:
                    continue
                vals = {
                    "name": line.description,
                    "type_id": ass_type.id,
                    "date_purchase": obj.date,
                    "price_purchase": line.amount,  # XXX: should be tax-ex
                    "fixed_asset_account_id": acc.id,
                    "dep_rate": ass_type.dep_rate,
                    "dep_method": ass_type.dep_method,
                    "accum_dep_account_id": ass_type.accum_dep_account_id.id,
                    "dep_exp_account_id": ass_type.dep_exp_account_id.id,
                    "invoice_id": obj.id,
                }
                get_model("account.fixed.asset").create(vals)

    def delete_alloc(self, context={}):
        alloc_id = context["alloc_id"]
        get_model("account.credit.alloc").delete([alloc_id])

    def onchange_date(self, context={}):
        data = context["data"]
        ctx = {
            "type": data["type"],
            "inv_type": data["inv_type"],
            "date": data["date"],
        }
        number = self._get_number(context=ctx)
        data["number"] = number
        return data

    def check_related(self, ids, context={}):
        obj = self.browse(ids)[0]
        rel = obj.related_id
        if not rel:
            return
        # if rel._model=="job": # XXX: doesn't work for bkkbase modules
        #    if not rel.done_approved_by_id:
        #        raise Exception("Service order has to be approved before it is invoiced")

    def get_template_invoice_form(self, ids=None, context={}):
        if ids is None:  # XXX: for backward compat with old templates
            ids = context["ids"]
        obj = get_model("account.invoice").browse(ids)[0]
        if obj.type == "out":
            if obj.amount_discount:
                return "cust_invoice_form_disc"
            else:
                return "cust_invoice_form"
        elif obj.type == "in":
            return "supp_invoice_form"

    def onchange_journal(self, context={}):
        data = context["data"]
        journal_id = data["journal_id"]
        if journal_id:
            journal = get_model("account.journal").browse(journal_id)
            data["sequence_id"] = journal.sequence_id.id
        else:
            data["sequence_id"] = None
        self.onchange_sequence(context=context)
        return data

    def onchange_sequence(self, context={}):
        data = context["data"]
        seq_id = data["sequence_id"]
        num = self._get_number(context={"type": data["type"], "inv_type": data["inv_type"], "date": data["date"], "sequence_id": seq_id})
        data["number"] = num
        return data

    def pay_online(self,ids,context={}):
        obj=self.browse(ids[0])
        method=obj.pay_method_id
        if not method:
            raise Exception("Missing payment method for invoice %s"%obj.number)
        ctx={
            "amount": obj.amount_total,
            "currency_id": obj.currency_id.id,
            "details": "Invoice %s"%obj.number,
        }
        res=method.start_payment(context=ctx)
        if not res:
            raise Exception("Failed to start online payment for payment method %s"%method.name)
        transaction_no=res["transaction_no"]
        obj.write({"transaction_no":transaction_no})
        return {
            "next": res["payment_action"],
        }

    def get_payment_entries(self,ids,context={}):
        vals={}
        for obj in self.browse(ids):
            line_ids=[]
            if obj.move_id:
                inv_line=obj.move_id.lines[0]
                rec=inv_line.reconcile_id
                if rec:
                    for line in rec.lines:
                        if line.id!=inv_line.id:
                            line_ids.append(line.id)
            vals[obj.id]=line_ids
        return vals
Example #18
0
class BatchImport(Model):
    _name = "batch.import"
    _string = "Batch Import"
    _fields = {
        "from_date":
        fields.Date("From Date", required=True, search=True),
        "to_date":
        fields.Date("To Date", required=True),
        "cash_account_id":
        fields.Many2One("account.account", "Cash Account"),
        "bank_account_id":
        fields.Many2One("account.account", "Bank Account"),
        "cash_payments":
        fields.One2Many("batch.import.payment",
                        "import_id",
                        "Cash Payments",
                        condition=[["type", "=", "cash"]]),
        "bank_payments":
        fields.One2Many("batch.import.payment",
                        "import_id",
                        "Bank Payments",
                        condition=[["type", "=", "bank"]]),
        "sale_invoices":
        fields.One2Many("batch.import.sale.invoice", "import_id",
                        "Sales Invoices"),
        "purchase_invoices":
        fields.One2Many("batch.import.purchase.invoice", "import_id",
                        "Purchase Invoices"),
        "cash_file":
        fields.File("Cash File"),
        "bank_file":
        fields.File("Bank File"),
        "sale_file":
        fields.File("Sales File"),
        "purchase_file":
        fields.File("Purchases File"),
        "date_fmt":
        fields.Char("Date Format"),
        "state":
        fields.Selection([["draft", "Draft"], ["posted", "Posted"]],
                         "Status",
                         required=True),
    }
    _order = "from_date,id"

    def _get_cash_account(self, context={}):
        res = get_model("account.account").search([["type", "=", "cash"]],
                                                  order="code")
        if not res:
            return None
        return res[0]

    def _get_bank_account(self, context={}):
        res = get_model("account.account").search([["type", "=", "bank"]],
                                                  order="code")
        if not res:
            return None
        return res[0]

    _defaults = {
        "state":
        "draft",
        "date_fmt":
        "%m/%d/%Y",
        "from_date":
        lambda *a: time.strftime("%Y-%m-01"),
        "to_date":
        lambda *a: (date.today() + relativedelta(day=31)).strftime("%Y-%m-%d"),
        "cash_account_id":
        _get_cash_account,
        "bank_account_id":
        _get_bank_account,
    }

    def clear_data(self, ids, context={}):
        obj = self.browse(ids)[0]
        obj.cash_payments.delete()
        obj.bank_payments.delete()
        obj.sale_invoices.delete()
        obj.purchase_invoices.delete()

    def import_files(self, ids, context={}):
        obj = self.browse(ids)[0]
        obj.clear_data()
        if obj.cash_file:
            obj.import_cash_file()
        if obj.bank_file:
            obj.import_bank_file()
        if obj.sale_file:
            obj.import_sale_file()
        if obj.purchase_file:
            obj.import_purchase_file()

    def import_cash_file(self, ids, context={}):
        obj = self.browse(ids)[0]
        path = get_file_path(obj.cash_file)
        data = open(path).read()
        rd = csv.reader(StringIO(data))
        headers = next(rd)
        headers = [h.strip() for h in headers]
        for row in rd:
            print("row", row)
            line = dict(zip(headers, row))
            print("line", line)
            vals = {
                "import_id": obj.id,
                "type": "cash",
                "date": parse_date(line["Date"], obj.date_fmt),
                "description": line["Description"],
                "received": parse_float(line["Received"]),
                "spent": parse_float(line["Spent"]),
                "invoice_no": line["Invoice No."],
                "other_account_id": get_account(line["Other Account"]),
            }
            get_model("batch.import.payment").create(vals)

    def import_bank_file(self, ids, context={}):
        obj = self.browse(ids)[0]
        path = get_file_path(obj.bank_file)
        data = open(path).read()
        rd = csv.reader(StringIO(data))
        headers = next(rd)
        headers = [h.strip() for h in headers]
        for row in rd:
            print("row", row)
            line = dict(zip(headers, row))
            print("line", line)
            vals = {
                "import_id": obj.id,
                "type": "bank",
                "date": parse_date(line["Date"], obj.date_fmt),
                "description": line["Description"],
                "received": parse_float(line["Received"]),
                "spent": parse_float(line["Spent"]),
                "invoice_no": line["Invoice No."],
                "other_account_id": get_account(line["Other Account"]),
            }
            get_model("batch.import.payment").create(vals)

    def import_sale_file(self, ids, context={}):
        obj = self.browse(ids)[0]
        path = get_file_path(obj.sale_file)
        data = open(path).read()
        rd = csv.reader(StringIO(data))
        headers = next(rd)
        headers = [h.strip() for h in headers]
        for row in rd:
            print("row", row)
            line = dict(zip(headers, row))
            print("line", line)
            vals = {
                "import_id": obj.id,
                "date": parse_date(line["Date"], obj.date_fmt),
                "number": line["Invoice No."],
                "contact": line["Customer Name"],
                "description": line["Description"],
                "amount": parse_float(line["Amount"]),
                "account_id": get_account(line["Income Account"]),
                "tax_id": get_tax_rate(line["Tax Rate"]),
            }
            get_model("batch.import.sale.invoice").create(vals)

    def import_purchase_file(self, ids, context={}):
        obj = self.browse(ids)[0]
        path = get_file_path(obj.purchase_file)
        data = open(path).read()
        rd = csv.reader(StringIO(data))
        headers = next(rd)
        headers = [h.strip() for h in headers]
        for row in rd:
            print("row", row)
            line = dict(zip(headers, row))
            print("line", line)
            vals = {
                "import_id": obj.id,
                "date": parse_date(line["Date"], obj.date_fmt),
                "number": line["Invoice No."],
                "contact": line["Supplier Name"],
                "description": line["Description"],
                "amount": parse_float(line["Amount"]),
                "account_id": get_account(line["Expense Account"]),
                "tax_id": get_tax_rate(line["Tax Rate"]),
            }
            get_model("batch.import.purchase.invoice").create(vals)

    def post(self, ids, context={}):
        obj = self.browse(ids)[0]
        for line in obj.sale_invoices:
            if line.invoice_id:
                continue
            vals = {
                "date": line.date,
                "due_date": line.date,  # XXX
                "number": line.number,
                "contact_id": merge_contact(line.contact),
                "type": "out",
                "inv_type": "invoice",
                "lines": [],
            }
            line_vals = {
                "description": line.description,
                "amount": line.amount,
                "account_id": line.account_id.id,
                "tax_id": line.tax_id.id,
            }
            vals["lines"].append(("create", line_vals))
            inv_id = get_model("account.invoice").create(vals,
                                                         context={
                                                             "type":
                                                             vals["type"],
                                                             "inv_type":
                                                             vals["inv_type"]
                                                         })
            get_model("account.invoice").post([inv_id])
            line.write({"invoice_id": inv_id})
        for line in obj.purchase_invoices:
            if line.invoice_id:
                continue
            vals = {
                "date": line.date,
                "due_date": line.date,  # XXX
                "number": line.number,
                "contact_id": merge_contact(line.contact),
                "type": "in",
                "inv_type": "invoice",
                "lines": [],
            }
            line_vals = {
                "description": line.description,
                "amount": line.amount,
                "account_id": line.account_id.id,
                "tax_id": line.tax_id.id,
            }
            vals["lines"].append(("create", line_vals))
            inv_id = get_model("account.invoice").create(vals,
                                                         context={
                                                             "type":
                                                             vals["type"],
                                                             "inv_type":
                                                             vals["inv_type"]
                                                         })
            get_model("account.invoice").post([inv_id])
            line.write({"invoice_id": inv_id})
        for line in obj.cash_payments:
            if line.payment_id:
                continue
            vals = {
                "date": line.date,
                "account_id": obj.cash_account_id.id,
                "lines": [],
            }
            if line.received:
                vals["type"] = "in"
            elif line.spent:
                vals["type"] = "out"
            else:
                raise Exception("Missing amount in cash payment")
            amt = line.received or line.spent
            if line.invoice_no:
                vals["pay_type"] = "invoice"
                line_vals = {
                    "type": "invoice",
                    "description": line.description,
                    "invoice_id": get_invoice(line.invoice_no),
                    "amount": amt,
                }
            else:
                vals["pay_type"] = "direct"
                line_vals = {
                    "type": "direct",
                    "description": line.description,
                    "amount": amt,
                    "account_id": line.other_account_id.id,
                }
            vals["lines"].append(("create", line_vals))
            pmt_id = get_model("account.payment").create(
                vals, context={"type": vals["type"]})
            get_model("account.payment").post([pmt_id])
            line.write({"payment_id": pmt_id})
        for line in obj.bank_payments:
            if line.payment_id:
                continue
            vals = {
                "date": line.date,
                "account_id": obj.bank_account_id.id,
                "lines": [],
            }
            if line.received:
                vals["type"] = "in"
            elif line.spent:
                vals["type"] = "out"
            else:
                raise Exception("Missing amount in bank payment")
            amt = line.received or line.spent
            if line.invoice_no:
                vals["pay_type"] = "invoice"
                line_vals = {
                    "type": "invoice",
                    "description": line.description,
                    "invoice_id": get_invoice(line.invoice_no),
                    "amount": amt,
                }
            else:
                vals["pay_type"] = "direct"
                line_vals = {
                    "type": "direct",
                    "description": line.description,
                    "amount": amt,
                    "account_id": line.other_account_id.id,
                }
            vals["lines"].append(("create", line_vals))
            pmt_id = get_model("account.payment").create(
                vals, context={"type": vals["type"]})
            get_model("account.payment").post([pmt_id])
            line.write({"payment_id": pmt_id})
        obj.write({"state": "posted"})
Example #19
0
class Contact(Model):
    _name = "contact"
    _string = "Contact"
    _audit_log = True
    _export_field = "name"
    _key = ["code"]
    _fields = {
        "user_id":
        fields.Many2One("base.user", "User"),
        "type":
        fields.Selection([["person", "Individual"], ["org", "Organization"]],
                         "Contact Type",
                         required=True,
                         search=True),
        "customer":
        fields.Boolean("Customer", search=True),
        "supplier":
        fields.Boolean("Supplier", search=True),
        "name":
        fields.Char("Name",
                    required=True,
                    search=True,
                    translate=True,
                    size=256),
        "code":
        fields.Char("Code", search=True, required=True),
        "phone":
        fields.Char("Phone", search=True),
        "fax":
        fields.Char("Fax"),
        "website":
        fields.Char("Website"),
        "industry":
        fields.Char("Industry"),  # XXX: deprecated
        "employees":
        fields.Char("Employees"),
        "revenue":
        fields.Char("Annual Revenue"),
        "description":
        fields.Text("Description"),
        "tax_no":
        fields.Char("Tax ID Number"),
        "tax_branch_no":
        fields.Char("Tax Branch Id"),
        "bank_account_no":
        fields.Char("Bank Account Number"),
        "bank_account_name":
        fields.Char("Bank Account Name"),
        "bank_account_details":
        fields.Char("Bank Account Details"),
        "active":
        fields.Boolean("Active"),
        "account_receivable_id":
        fields.Many2One("account.account",
                        "Account Receivable",
                        multi_company=True),
        "tax_receivable_id":
        fields.Many2One("account.tax.rate", "Account Receivable Tax"),
        "account_payable_id":
        fields.Many2One("account.account",
                        "Account Payable",
                        multi_company=True),
        "tax_payable_id":
        fields.Many2One("account.tax.rate", "Account Payable Tax"),
        "currency_id":
        fields.Many2One("currency", "Default Currency"),
        "payables_due":
        fields.Decimal("Payables Due"),
        "payables_overdue":
        fields.Decimal("Payables Overdue"),
        "receivables_due":
        fields.Decimal("Receivables Due"),
        "receivables_overdue":
        fields.Decimal("Receivables Overdue"),
        "payable_credit":
        fields.Decimal("Payable Credit",
                       function="get_credit",
                       function_multi=True),
        "receivable_credit":
        fields.Decimal("Receivable Credit",
                       function="get_credit",
                       function_multi=True),
        "invoices":
        fields.One2Many("account.invoice", "contact_id", "Invoices"),
        "sale_price_list_id":
        fields.Many2One("price.list",
                        "Sales Price List",
                        condition=[["type", "=", "sale"]]),
        "purchase_price_list_id":
        fields.Many2One("price.list",
                        "Purchasing Price List",
                        condition=[["type", "=", "purchase"]]),
        "categ_id":
        fields.Many2One("contact.categ", "Contact Category"),
        "payment_terms":
        fields.Char("Payment Terms"),
        "opports":
        fields.One2Many("sale.opportunity",
                        "contact_id",
                        "Open Opportunities",
                        condition=[["state", "=", "open"]]),
        "addresses":
        fields.One2Many("address", "contact_id", "Addresses"),
        "comments":
        fields.One2Many("message", "related_id", "Comments"),
        "bank_accounts":
        fields.One2Many("bank.account", "contact_id", "Bank Accounts"),
        "last_name":
        fields.Char("Last Name"),
        "first_name":
        fields.Char("First Name"),
        "first_name2":
        fields.Char("First Name (2)"),
        "first_name3":
        fields.Char("First Name (3)"),
        "title":
        fields.Char("Title"),
        "position":
        fields.Char("Position"),
        "report_to_id":
        fields.Many2One("contact", "Reports To"),
        "mobile":
        fields.Char("Mobile"),
        "email":
        fields.Char("Email", search=True),
        "home_phone":
        fields.Char("Home Phone"),
        "other_phone":
        fields.Char("Other Phone"),
        "assistant":
        fields.Char("Assistant"),
        "assistant_phone":
        fields.Char("Assistant Phone"),
        "birth_date":
        fields.Date("Birth Date"),
        "department":
        fields.Char("Department"),
        "job_templates":
        fields.Many2Many("job.template", "Job Template"),
        "projects":
        fields.One2Many("project", "contact_id", "Projects"),
        "documents":
        fields.One2Many("document", "contact_id", "Documents"),
        "assigned_to_id":
        fields.Many2One("base.user", "Assigned To"),
        "lead_source":
        fields.Char("Lead source"),
        "inquiry_type":
        fields.Char("Type of inquiry"),
        "relations":
        fields.One2Many("contact.relation",
                        "from_contact_id",
                        "Relations",
                        function="_get_relations"),
        "contact_id":
        fields.Many2One(
            "contact",
            "Parent"),  # XXX: not used any more, just there for migration
        "emails":
        fields.One2Many("email.message", "name_id", "Emails"),
        "default_address_id":
        fields.Many2One("address",
                        "Default Address",
                        function="get_default_address"),
        "sale_orders":
        fields.One2Many("sale.order", "contact_id", "Sales Orders"),
        "country_id":
        fields.Many2One("country", "Country", search=True),
        "region":
        fields.Char("Region"),  # XXX: deprecated
        "service_items":
        fields.One2Many("service.item",
                        "contact_id",
                        "Service Items",
                        condition=[["parent_id", "=", None]]),
        "contracts":
        fields.One2Many("service.contract", "contact_id", "Contracts"),
        "branch":
        fields.Char("Branch"),  # XXX: add by Cash
        "industry_id":
        fields.Many2One("industry", "Industry", search=True),
        "region_id":
        fields.Many2One("region", "Region", search=True),
        "commission_po_percent":
        fields.Decimal("Commission Purchase Percentage"),
        "business_area_id":
        fields.Many2One("business.area", "Business Area", search=True),
        "fleet_size_id":
        fields.Many2One("fleet.size", "Fleet Size", search=True),
        "groups":
        fields.Many2Many("contact.group", "Groups", search=True),
        "sale_journal_id":
        fields.Many2One("account.journal", "Sales Journal"),
        "purchase_journal_id":
        fields.Many2One("account.journal", "Purchase Journal"),
        "pay_in_journal_id":
        fields.Many2One("account.journal", "Receipts Journal"),
        "pay_out_journal_id":
        fields.Many2One("account.journal", "Disbursements Journal"),
        "pick_in_journal_id":
        fields.Many2One("stock.journal", "Goods Receipt Journal"),
        "pick_out_journal_id":
        fields.Many2One("stock.journal", "Goods Issue Journal"),
        "coupons":
        fields.One2Many("sale.coupon", "contact_id", "Coupons"),
        "companies":
        fields.Many2Many("company", "Companies"),
        "request_product_groups":
        fields.Many2Many("product.group",
                         "Request Product Groups",
                         reltable="m2m_contact_request_product_groups",
                         relfield="contact_id",
                         relfield_other="group_id"),
        "exclude_product_groups":
        fields.Many2Many("product.group",
                         "Exclude Product Groups",
                         reltable="m2m_contact_exclude_product_groups",
                         relfield="contact_id",
                         relfield_other="group_id"),
        "picture":
        fields.File("Picture"),
        "users":
        fields.One2Many("base.user", "contact_id", "Users"),
        "ship_free":
        fields.Boolean("Free Shipping"),
    }

    def _get_number(self, context={}):
        seq_id = get_model("sequence").find_sequence(type="contact")
        if not seq_id:
            return None
        while 1:
            num = get_model("sequence").get_next_number(seq_id,
                                                        context=context)
            res = self.search([["code", "=", num]])
            if not res:
                return num
            get_model("sequence").increment_number(seq_id, context=context)

    _defaults = {
        "active": True,
        "type": "person",
        "code": _get_number,
    }
    _order = "name"
    _constraints = ["check_email"]

    def create(self, vals, **kw):
        if not vals.get("type"):
            if vals.get("name"):
                vals["type"] = "org"
            elif vals.get("last_name"):
                vals["type"] = "person"
        if vals.get("type") == "person":
            if vals.get("first_name"):
                vals["name"] = vals["first_name"] + " " + vals["last_name"]
            else:
                vals["name"] = vals["last_name"]
        new_id = super().create(vals, **kw)
        return new_id

    def write(self, ids, vals, set_name=True, **kw):
        super().write(ids, vals, **kw)
        if set_name:
            for obj in self.browse(ids):
                if obj.type == "person":
                    if obj.first_name:
                        name = obj.first_name + " " + obj.last_name
                    else:
                        name = obj.last_name
                    obj.write({"name": name}, set_name=False)

    def get_credit(self, ids, context={}):
        print("contact.get_credit", ids)
        currency_id = context.get("currency_id")
        print("currency_id", currency_id)
        vals = {}
        for obj in self.browse(ids):
            ctx = {
                "contact_id": obj.id,
            }
            r_credit = 0
            p_credit = 0
            for acc in get_model("account.account").search_browse(
                [["type", "=", "cust_deposit"]], context=ctx):
                r_credit -= acc.balance
            for acc in get_model("account.account").search_browse(
                [["type", "=", "sup_deposit"]], context=ctx):
                p_credit += acc.balance
            vals[obj.id] = {
                "receivable_credit": r_credit,
                "payable_credit": p_credit,  # TODO
            }
        return vals

    def get_address_str(self, ids, context={}):
        obj = self.browse(ids[0])
        if not obj.addresses:
            return ""
        addr = obj.addresses[0]
        return addr.name_get()[0][1]

    def _get_relations(self, ids, context={}):
        cond = [
            "or", ["from_contact_id", "in", ids], ["to_contact_id", "in", ids]
        ]
        rels = get_model("contact.relation").search_read(
            cond, ["from_contact_id", "to_contact_id"])
        vals = {}
        for rel in rels:
            from_id = rel["from_contact_id"][0]
            to_id = rel["to_contact_id"][0]
            vals.setdefault(from_id, []).append(rel["id"])
            vals.setdefault(to_id, []).append(rel["id"])
        return vals

    def get_address(self, ids, pref_type=None, context={}):
        obj = self.browse(ids)[0]
        for addr in obj.addresses:
            if pref_type and addr.type != pref_type:
                continue
            return addr.id
        if obj.addresses:
            return obj.addresses[0].id
        return None

    def get_default_address(self, ids, context={}):
        vals = {}
        for obj in self.browse(ids):
            addr_id = None
            for addr in obj.addresses:
                if addr.type == "billing":
                    addr_id = addr.id
                    break
            if not addr_id and obj.addresses:
                addr_id = obj.addresses[0].id
            vals[obj.id] = addr_id
        print("XXX", vals)
        return vals

    def check_email(self, ids, context={}):
        for obj in self.browse(ids):
            if not obj.email:
                continue
            if not utils.check_email_syntax(obj.email):
                raise Exception("Invalid email for contact '%s'" % obj.name)

    def find_address(self, ids, addr_vals, context={}):
        obj = self.browse(ids[0])
        addr_id = None
        for addr in obj.addresses:
            if "address" in addr_vals and addr_vals["address"] != addr.address:
                continue
            if "address2" in addr_vals and addr_vals[
                    "address2"] != addr.address2:
                continue
            if "city" in addr_vals and addr_vals["city"] != addr.city:
                continue
            if "postal_code" in addr_vals and addr_vals[
                    "postal_code"] != addr.postal_code:
                continue
            if "country_id" in addr_vals and addr_vals[
                    "country_id"] != addr.country_id.id:
                continue
            if "province_id" in addr_vals and addr_vals[
                    "province_id"] != addr.province_id.id:
                continue
            if "district_id" in addr_vals and addr_vals[
                    "district_id"] != addr.district_id.id:
                continue
            if "subdistrict_id" in addr_vals and addr_vals[
                    "subdistrict_id"] != addr.subdistrict_id.id:
                continue
            if "phone" in addr_vals and addr_vals["phone"] != addr.phone:
                continue
            if "first_name" in addr_vals and addr_vals[
                    "phone"] != addr.first_name:
                continue
            if "last_name" in addr_vals and addr_vals[
                    "last_name"] != addr.last_name:
                continue
            addr_id = addr.id
            break
        return addr_id

    def add_address(self, ids, addr_vals, context={}):
        addr_id = self.find_address(ids)
        if not addr_id:
            vals = addr_vals.copy()
            vals["contact_id"] = ids[0]
            addr_id = get_model("address").create(vals)
        return addr_id
Example #20
0
class ProductImage(Model):
    _name = "product.image"
    _fields = {
        "product_id":
        fields.Many2One("product",
                        "Product",
                        required=True,
                        on_delete="cascade"),
        "image":
        fields.File("Image", required=True),
        "title":
        fields.Char("Title"),
        "description":
        fields.Text("Description"),
        "rotate_cw":
        fields.Boolean("Rotate Clockwise",
                       function="_get_related",
                       function_context={"path": "product_id.rotate_cw"}),
        "rotate_footage":
        fields.Char("Define Column",
                    function="_get_related",
                    function_context={"path": "product_id.rotate_footage"}),
        "rotate_frame":
        fields.Char("Define Amount of image",
                    function="_get_related",
                    function_context={"path": "product_id.rotate_frame"}),
        "rotate_speed":
        fields.Char("Define Rotating Speed",
                    function="_get_related",
                    function_context={"path": "product_id.rotate_speed"}),
        "rotate_height":
        fields.Char("Image Height",
                    function="cal_dimension",
                    function_multi=True),
        "rotate_width":
        fields.Char("Image Width",
                    function="cal_dimension",
                    function_multi=True),
        "master_image":
        fields.Char("Master image",
                    function="cal_dimension",
                    function_multi=True),
    }

    def cal_dimension(self, ids, context={}):
        all_vals = {}
        dbname = database.get_active_db()
        for obj in self.browse(ids):
            master_img = obj.product_id.image
            master_path = os.path.join("static/db/", dbname, "files",
                                       master_img)
            frame = int(obj.get("rotate_frame"))
            column = int(obj.get("rotate_footage"))
            row = 1
            if frame and column:
                row = frame / column
            vals = {}
            im_path = obj.image
            if im_path and frame and column:
                filename = os.path.join("static/db/", dbname, "files", im_path)
                img = Image.open(filename)
                (width, height) = img.size
                swidth = math.floor(width / column)
                sheight = math.floor(height / row)
                vals["rotate_width"] = swidth
                vals["rotate_height"] = sheight
                vals["master_image"] = master_path
                all_vals[obj.id] = vals
            else:
                print("Not enough arguments given")
        return all_vals
Example #21
0
class Product(Model):
    _name = "product"
    _string = "Product"
    _audit_log = True
    _key = ["code", "state", "company_id"]
    _order = "code,name"
    _export_name_field = "code"
    _history = True
    _fields = {
        "name":
        fields.Char("Name",
                    required=True,
                    search=True,
                    translate=True,
                    size=256),
        "code":
        fields.Char("Code", required=True, search=True),
        "type":
        fields.Selection([["stock", "Stockable"], ["consumable", "Consumable"],
                          ["service", "Service"], ["master", "Master"],
                          ["bundle", "Bundle"]],
                         "Product Type",
                         required=True,
                         search=True),
        "uom_id":
        fields.Many2One("uom", "Default UoM", required=True, search=True),
        "parent_id":
        fields.Many2One("product", "Master Product"),
        "categ_id":
        fields.Many2One("product.categ", "Product Category", search=True),
        "description":
        fields.Text("Description", translate=True),
        "purchase_price":
        fields.Decimal("Purchase Price", scale=6),
        "sale_price":
        fields.Decimal("List Price (Sales Invoice UoM)", scale=6),
        "sale_price_order_uom":
        fields.Decimal("List Price (Sales Order UoM)",
                       scale=6,
                       function="get_sale_price_order_uom"),
        "tags":
        fields.Many2Many("tag", "Tags"),
        "image":
        fields.File("Image"),
        "cost_method":
        fields.Selection(
            [["standard", "Standard Cost"], ["average", "Weighted Average"],
             ["fifo", "FIFO"], ["lifo", "LIFO"]], "Costing Method"),
        "cost_price":
        fields.Decimal("Cost Price", scale=6),
        "stock_in_account_id":
        fields.Many2One("account.account",
                        "Stock Input Account",
                        multi_company=True),  # XXX: deprecated
        "stock_out_account_id":
        fields.Many2One("account.account",
                        "Stock Output Account",
                        multi_company=True),  # XXX: deprecated
        "cogs_account_id":
        fields.Many2One("account.account",
                        "Cost Of Goods Sold Account",
                        multi_company=True),
        "stock_account_id":
        fields.Many2One("account.account",
                        "Inventory Account",
                        multi_company=True),
        "purchase_account_id":
        fields.Many2One("account.account",
                        "Purchase Account",
                        multi_company=True),
        "purchase_tax_id":
        fields.Many2One("account.tax.rate", "Purchase Tax"),
        "supplier_id":
        fields.Many2One("contact", "Default Supplier"),  # XXX: deprecated
        "sale_account_id":
        fields.Many2One("account.account", "Sales Account",
                        multi_company=True),
        "sale_return_account_id":
        fields.Many2One("account.account",
                        "Sales Returns Account",
                        multi_company=True),
        "sale_promotion_account_id":
        fields.Many2One("account.account",
                        "Sales Promotion Account",
                        multi_company=True),
        "sale_tax_id":
        fields.Many2One("account.tax.rate", "Sales Tax"),
        "sale_promotion_tax_id":
        fields.Many2One("account.tax.rate", "Promotion Tax"),
        "location_id":
        fields.Many2One("stock.location", "Warehouse"),  # XXX: deprecated
        "bin_location":
        fields.Char("Bin Location"),
        "update_balance":
        fields.Boolean("Update Balance"),
        "active":
        fields.Boolean("Active"),
        "comments":
        fields.One2Many("message", "related_id", "Comments"),
        "categs":
        fields.Many2Many("product.categ",
                         "Other Categories"),  # XXX: deprecated
        "attributes":
        fields.One2Many("product.attribute.value", "product_id", "Attributes"),
        "variants":
        fields.One2Many("product", "parent_id", "Variants"),
        #"variant_values": fields.One2Many("product.custom.option.variant.value","product_id","Variant Values"),
        "custom_options":
        fields.Many2Many("product.custom.option", "Custom Options"),
        "images":
        fields.One2Many("product.image", "product_id", "Images"),
        "store_type_id":
        fields.Many2One("store.type", "Storage Type"),
        "brand_id":
        fields.Many2One("product.brand", "Brand", search=True),
        "related_products":
        fields.Many2Many("product",
                         "Related Products",
                         relfield="product1_id",
                         relfield_other="product2_id"),
        "min_sale_qty":
        fields.Decimal("Min Sale Qty"),
        "sale_unit_qty":
        fields.Decimal("Sale Unit Qty"),
        "shelf_life":
        fields.Decimal("Shelf Life (Days)"),
        "weight":
        fields.Decimal("Weight (Kg)"),
        "volume":
        fields.Decimal("Volume (M^3)"),
        "width":
        fields.Decimal("Width"),
        "height":
        fields.Decimal("Height"),
        "length":
        fields.Decimal("Length"),
        "packing_size":
        fields.Char("Packing Size"),
        "details":
        fields.Text("Product Details", translate=True),
        "details2":
        fields.Text("Product Details (2)", translate=True),
        "details2_label":
        fields.Char("Product Details Label (2)"),
        "details3":
        fields.Text("Product Details (3)", translate=True),
        "details3_label":
        fields.Char("Product Details Label (3)"),
        "other_url":
        fields.Char("Product URL", size=256),
        "purchase_currency_id":
        fields.Many2One("currency", "Purchase Currency"),
        "purchase_currency_rate":
        fields.Decimal("Purchase Currency Rate", scale=6),
        "purchase_duty_percent":
        fields.Decimal("Import Duty (%)"),
        "purchase_ship_percent":
        fields.Decimal("Shipping Charge (%)"),
        "landed_cost":
        fields.Decimal("Landed Cost",
                       function="get_landed_cost",
                       function_multi=True),
        "landed_cost_conv":
        fields.Decimal("Landed Cost (Conv)",
                       function="get_landed_cost",
                       function_multi=True),
        "gross_profit":
        fields.Decimal("Gross Profit (%)"),
        "auto_list_price":
        fields.Decimal("Auto List Price", function="get_auto_list_price"),
        "max_discount":
        fields.Decimal("Max Discount (%)", function="get_max_discount"),
        "price_index":
        fields.Decimal("Price Index", function="get_price_index"),
        "price_notes":
        fields.Text("Notes"),
        "price_date":
        fields.Date("Price Date"),
        "pricelist_items":
        fields.One2Many("price.list.item", "product_id", "Pricelist Items"),
        "procure_method":
        fields.Selection([["mto", "Make To Order"], ["mts", "Make To Stock"]],
                         "Procurement Method"),
        "supply_method":
        fields.Selection(
            [["purchase", "Purchase"], ["production", "Production"]],
            "Supply Method"),
        "can_sell":
        fields.Boolean("Can Sell"),
        "can_purchase":
        fields.Boolean("Can Purchase"),
        "id":
        fields.Integer("Database ID", readonly=True),  # MTS
        "create_time":
        fields.DateTime("Create Time", readonly=True),  # MTS
        "supplier_product_code":
        fields.Char("Supplier Product Code"),  # XXX: deprecated
        "require_qty2":
        fields.Boolean("Require Secondary Qty"),
        "qty2_factor":
        fields.Decimal("UoM -> Secondary Qty Factor", scale=6),
        "replacements":
        fields.Many2Many("product",
                         "Replacement Products",
                         reltable="m2m_product_replacement",
                         relfield="product1_id",
                         relfield_other="product2_id"),
        "suppliers":
        fields.One2Many("product.supplier", "product_id", "Suppliers"),
        "max_qty_loss":
        fields.Decimal("Max Qty Loss"),
        "documents":
        fields.One2Many("document", "related_id", "Documents"),
        "ship_methods":
        fields.Many2Many("ship.method", "Shipping Methods"),
        "ecom_discount_percent":
        fields.Decimal("Ecom. Discount Percent"),  # XXX: deprecated
        "ecom_special_price":
        fields.Decimal("Ecom. Special Price"),  # XXX: deprecated
        #"ecom_sale_price": fields.Decimal("Ecom. Sale Price", function="get_ecom_sale_price", function_multi=True), # XXX: deprecated
        #"ecom_has_discount": fields.Decimal("Ecom. Has Discount", function="get_ecom_sale_price", function_multi=True), # XXX: deprecated
        "variant_attributes":
        fields.Json("Variant Attributes", function="get_variant_attributes"),
        "company_id":
        fields.Many2One("company", "Company"),
        "sale_channels":
        fields.Many2Many("sale.channel", "Sales Channels"),
        "customer_price":
        fields.Decimal("Customer Price",
                       function="get_customer_price",
                       function_multi=True),
        "customer_has_discount":
        fields.Decimal("Customer Has Discount",
                       function="get_customer_price",
                       function_multi=True),
        "customer_discount_text":
        fields.Decimal("Customer Discount Text",
                       function="get_customer_price",
                       function_multi=True),
        "customer_discount_percent":
        fields.Decimal("Customer Discount Percent",
                       function="get_customer_price",
                       function_multi=True),
        "sale_company_id":
        fields.Many2One("company", "Sold By"),
        "groups":
        fields.Many2Many("product.group", "Groups"),
        "payment_methods":
        fields.Many2Many("payment.method", "Payment Methods"),
        "locations":
        fields.One2Many("product.location", "product_id", "Warehouses"),
        "stock_qty":
        fields.Decimal("Total Stock Qty", function="get_stock_qty"),
        "stock_lots":
        fields.Many2Many("stock.lot",
                         "Lots In Stock",
                         function="get_stock_lots"),
        "state":
        fields.Selection([["draft", "Draft"], ["approved", "Approved"]],
                         "Status"),
        "sale_uom_id":
        fields.Many2One("uom", "Sales Order UoM"),
        "sale_invoice_uom_id":
        fields.Many2One("uom", "Sales Invoice UoM"),
        "sale_to_stock_uom_factor":
        fields.Decimal("Sales Order -> Stock Uom Conversion Factor", scale=6),
        "sale_to_invoice_uom_factor":
        fields.Decimal("Sales Order -> Sales Invoice Uom Conversion Factor",
                       scale=6),
        "purchase_uom_id":
        fields.Many2One("uom", "Purchase Order UoM"),
        "purchase_invoice_uom_id":
        fields.Many2One("uom", "Purchase Invoice UoM"),
        "purchase_to_stock_uom_factor":
        fields.Decimal("Purchase Order -> Stock Uom Conversion Factor",
                       scale=6),
        "purchase_to_invoice_uom_factor":
        fields.Decimal(
            "Purchase Order -> Purchase Invoice Uom Conversion Factor",
            scale=6),
        "purchase_lead_time":
        fields.Integer("Purchasing Lead Time (Days)"),
        "purchase_min_qty":
        fields.Decimal("Purchase Minimum Qty"),
        "purchase_qty_multiple":
        fields.Decimal("Purchase Qty Multiple"),
        "mfg_lead_time":
        fields.Integer("Manufacturing Lead Time (Days)"),
        "mfg_min_qty":
        fields.Decimal("Manufacturing Minimum Qty"),
        "mfg_qty_multiple":
        fields.Decimal("Manufacturing Qty Multiple"),
        #"purchase_price_uom_id": fields.Many2One("uom", "Purchase Price UoM"), # not needed?
        #"sale_price_uom_id": fields.Many2One("uom", "List Price UoM"), # not needed?
        "events":
        fields.Many2Many("sale.event", "Events"),
        "is_published":
        fields.Boolean("Publish Product"),
        "require_lot":
        fields.Boolean("Require Lot"),
        "lot_select":
        fields.Selection([["fifo", "FIFO"], ["fefo", "FEFO"]],
                         "Lot Selection"),
        "components":
        fields.One2Many("product.component", "product_id",
                        "Bundle Components"),
        "approve_date":
        fields.DateTime("Approve Date"),
        "service_items":
        fields.One2Many("service.item", "product_id", "Service Items"),
        "lots":
        fields.One2Many("stock.lot", "product_id", "Lots"),
        "stock_plan_horizon":
        fields.Integer("Inventory Planning Horizon (days)"),  # XXX: deprecated
        "ecom_hide_qty":
        fields.Boolean("Hide Stock Qty From Website"),
        "ecom_hide_unavail":
        fields.Boolean("Hide From Website When Out Of Stock"),
        "ecom_no_order_unavail":
        fields.Boolean("Prevent Orders When Out Of Stock"),
        "ecom_select_lot":
        fields.Boolean("Customers Can Select Lot When Ordering"),
        "product_origin":
        fields.Char("Product Origin"),
        "stock_balances":
        fields.One2Many("stock.balance", "product_id", "Stock Balances"),
        "check_lot_neg_stock":
        fields.Boolean("Check Lot Negative Stock"),
        "sale_lead_time_nostock":
        fields.Integer("Sale Lead Time When Out Of Stock (Days)"),
        "ship_methods":
        fields.Many2Many("ship.method", "Shipping Methods"),
        "delivery_weekdays":
        fields.Char("Delivery Weekday Constraints"),
    }

    _defaults = {
        "update_balance": True,
        "active": True,
        "can_sell": False,
        "can_purchase": False,
        "company_id": lambda *a: access.get_active_company(),
        "state": "draft",
    }

    def name_get(self, ids, context={}):
        vals = []
        for obj in self.browse(ids):
            if obj.code:
                name = "[%s] %s" % (obj.code, obj.name)
            else:
                name = obj.name
            vals.append((obj.id, name, obj.image))
        return vals

    def name_search(self, name, condition=None, context={}, limit=None, **kw):
        print("condition", condition)
        search_mode = context.get("search_mode")
        print("##############################")
        print("search_mode", search_mode)
        if search_mode == "suffix":
            cond = [["code", "=ilike", "%" + name]]
        elif search_mode == "prefix":
            cond = [["code", "=ilike", name + "%"]]
        else:
            cond = [["code", "ilike", name]]
        if condition:
            cond = [cond, condition]
        ids1 = self.search(cond, limit=limit)
        if search_mode == "suffix":
            cond = [["name", "=ilike", "%" + name]]
        elif search_mode == "prefix":
            cond = [["name", "=ilike", name + "%"]]
        else:
            cond = [["name", "ilike", name]]
        if condition:
            cond = [cond, condition]
        ids2 = self.search(cond, limit=limit)
        ids = list(set(ids1 + ids2))
        return self.name_get(ids, context=context)

    def copy(self, ids, context={}):
        replace_id = context.get("replace_id")
        obj = self.browse(ids)[0]
        code = obj.code
        if not replace_id:
            for i in range(1, 10):
                code = obj.code + " (%s)" % i
                res = self.search([["code", "=", code]])
                if not res:
                    break
        vals = {
            "name": obj.name,
            "code": code,
            "type": obj.type,
            "uom_id": obj.uom_id.id,
            #"parent_id": obj.parent_id.id, XXX
            "description": obj.description,
            "image": obj.image,
            "categ_id": obj.categ_id.id,
            "categs": [("set", [c.id for c in obj.categs])],
            "supply_method": obj.supply_method,
            "procure_method": obj.procure_method,
            "can_sell": obj.can_sell,
            "can_purchase": obj.can_purchase,
            "sale_uom_id": obj.sale_uom_id.id,
            "sale_invoice_uom_id": obj.sale_invoice_uom_id.id,
            "sale_to_stock_uom_factor": obj.sale_to_stock_uom_factor,
            "sale_to_invoice_uom_factor": obj.sale_to_invoice_uom_factor,
            "purchase_uom_id": obj.purchase_uom_id.id,
            "purchase_invoice_uom_id": obj.purchase_invoice_uom_id.id,
            "purchase_to_stock_uom_factor": obj.purchase_to_stock_uom_factor,
            "purchase_to_invoice_uom_factor":
            obj.purchase_to_invoice_uom_factor,
            "purchase_price": obj.purchase_price,
            "purchase_account_id": obj.purchase_account_id.id,
            "purchase_tax_id": obj.purchase_tax_id.id,
            "supplier_id": obj.supplier_id.id,
            "sale_price": obj.sale_price,
            "sale_account_id": obj.sale_account_id.id,
            "sale_tax_id": obj.sale_tax_id.id,
            "sale_return_account_id": obj.sale_return_account_id.id,
            "sale_promotion_account_id": obj.sale_promotion_account_id.id,
            "sale_promotion_tax_id": obj.sale_promotion_tax_id.id,
            "cost_method": obj.cost_method,
            "stock_in_account_id": obj.stock_in_account_id.id,
            "stock_out_account_id": obj.stock_out_account_id.id,
            "bin_location": obj.bin_location,
            "sale_company_id": obj.sale_company_id.id,
            "attributes": [],
        }
        vals["attributes"] = [("delete_all", )]
        for attr in obj.attributes:
            vals["attributes"].append(("create", {
                "attribute_id": attr.attribute_id.id,
                "option_id": attr.option_id.id
            }))
        vals["pricelist_items"] = [("delete_all", )]
        for item in obj.pricelist_items:
            vals["pricelist_items"].append(("create", {
                "list_id": item.list_id.id,
                "price": item.price
            }))
        vals["images"] = [("delete_all", )]
        for image in obj.images:
            vals["images"].append(("create", {
                "image": image.image,
                "title": image.title
            }))
        print("vals", vals)
        if replace_id:
            self.write([replace_id], vals)
            new_id = replace_id
        else:
            new_id = self.create(vals)
        return {
            "next": {
                "name": "product",
                "mode": "form",
                "active_id": new_id,
            },
            "flash": "New product copied from %s" % obj.name,
        }

    def get_landed_cost(self, ids, context={}):
        vals = {}
        for obj in self.browse(ids):
            amt = Decimal(obj.purchase_price or 0) * Decimal(
                1 + (obj.purchase_duty_percent or 0) /
                100) * Decimal(1 + (obj.purchase_ship_percent or 0) / 100)
            amt_cur = amt * Decimal(obj.purchase_currency_rate or 1)
            vals[obj.id] = {
                "landed_cost": amt,
                "landed_cost_conv": amt_cur,
            }
        return vals

    def get_auto_list_price(self, ids, context={}):
        vals = {}
        for obj in self.browse(ids):
            if obj.landed_cost_conv and obj.gross_profit and obj.gross_profit != 100:
                vals[obj.id] = obj.landed_cost_conv / (1 -
                                                       obj.gross_profit / 100)
            else:
                vals[obj.id] = None
        return vals

    def get_max_discount(self, ids, context={}):
        vals = {}
        for obj in self.browse(ids):
            if obj.sale_price and obj.landed_cost_conv:
                vals[obj.id] = max(0, (obj.sale_price - obj.landed_cost_conv) /
                                   obj.sale_price * 100)
            else:
                vals[obj.id] = None
        return vals

    def get_price_index(self, ids, context={}):
        vals = {}
        for obj in self.browse(ids):
            if obj.sale_price and obj.landed_cost_conv:
                vals[obj.id] = obj.sale_price / obj.landed_cost_conv
            else:
                vals[obj.id] = None
        return vals

    def update_prices(self, context={}):
        print("update_prices")
        data = context["data"]
        purchase_price = data.get("purchase_price")
        purchase_currency_rate = data.get("purchase_currency_rate")
        purchase_duty_percent = data.get("purchase_duty_percent")
        purchase_ship_percent = data.get("purchase_ship_percent")
        if purchase_price:
            landed_cost = purchase_price * \
                (1 + (purchase_duty_percent or 0) / 100) * (1 + (purchase_ship_percent or 0) / 100)
            landed_cost_conv = landed_cost * (purchase_currency_rate or 1)
        else:
            landed_cost = None
            landed_cost_conv = None
        gross_profit = data.get("gross_profit")
        if landed_cost_conv and gross_profit and gross_profit != 100:
            auto_list_price = landed_cost_conv / (1 - gross_profit / 100)
        else:
            auto_list_price = None
        sale_price = data.get("sale_price")
        if sale_price and landed_cost_conv:
            max_discount = max(0, (sale_price - landed_cost_conv) /
                               sale_price * 100)
            price_index = sale_price / landed_cost
        else:
            max_discount = None
            price_index = None
        data.update({
            "landed_cost": landed_cost,
            "landed_cost_conv": landed_cost_conv,
            "auto_list_price": auto_list_price,
            "max_discount": max_discount,
            "price_index": price_index,
        })
        return data

    def get_ecom_sale_price(self, ids, context={}):
        raise Exception("Deprecated!")
        vals = {}
        for obj in self.browse(ids):
            if obj.ecom_discount_percent:
                p = (obj.sale_price
                     or 0) * (1 - obj.ecom_discount_percent / 100)
                has_disc = True
            elif obj.ecom_special_price:
                p = obj.ecom_special_price
                has_disc = True
            else:
                p = obj.sale_price or 0
                has_disc = False
            vals[obj.id] = {
                "ecom_sale_price": p,
                "ecom_has_discount": has_disc,
            }
        return vals

    def create_variants(self, ids, context={}):  # deprecated
        print("##################################")
        print("product.create_variants", ids)
        obj = self.browse(ids[0])
        if obj.type != "master":
            raise Exception("Not a master product")
        if not obj.custom_options:
            raise Exception("No custom options for this product")
        variants = [{}]
        for opt in obj.custom_options:
            new_variants = []
            for variant in variants:
                for opt_val in opt.values:
                    new_variant = variant.copy()
                    new_variant[opt.code] = opt_val.code
                    new_variants.append(new_variant)
            variants = new_variants
        print("variants", len(variants), variants)
        count = 1
        for variant in variants:
            vals = {
                "code": "%s_VARIANT_%.2d" % (obj.code, count),
                "name": obj.name,
                "type": "stock",
                "uom_id": obj.uom_id.id,
                "parent_id": obj.id,
                "location_id": obj.location_id.id,
                "attributes": [],
            }
            for k, v in variant.items():
                name = "_CUST_OPT_" + k
                res = get_model("product.attribute").search(
                    [["name", "=", name]])
                if res:
                    attr_id = res[0]
                else:
                    attr_id = get_model("product.attribute").create(
                        {"name": name})
                vals["attributes"].append(("create", {
                    "attribute_id": attr_id,
                    "value": v,
                }))
            get_model("product").create(vals)
            count += 1
        return {
            "flash": "%d variants created" % len(variants),
        }

    def get_variant_attributes(self, ids, context={}):
        print("get_variant_attributes", ids)
        vals = {}
        for obj in self.browse(ids):
            attrs = []
            attr_options = {}
            for variant in obj.variants:
                for attr in variant.attributes:
                    if not attr.attribute_id or not attr.option_id:
                        continue
                    attr_code = attr.attribute_id.code
                    attr_name = attr.attribute_id.name
                    attrs.append((attr_name, attr_code))
                    opt_code = attr.option_id.code
                    opt_name = attr.option_id.name
                    opt_sequence = attr.option_id.sequence
                    if attr_code == "color":
                        opt_image = variant.image or attr.option_id.image
                    else:
                        opt_image = attr.option_id.image
                    #attr_options.setdefault(attr_code,[]).append((opt_sequence,opt_name,opt_code,opt_image))
                    attr_options.setdefault(attr_code, []).append(
                        (opt_sequence, opt_name, opt_code))
            attrs = list(set(attrs))
            res = []
            for attr_name, attr_code in sorted(attrs):
                attr_vals = {
                    "code": attr_code,
                    "name": attr_name,
                    "values": [],
                }
                attr_options[attr_code] = list(set(attr_options[attr_code]))
                #for opt_sequence,opt_name,opt_code,opt_image in sorted(attr_options[attr_code]):
                for opt_sequence, opt_name, opt_code in sorted(
                        attr_options[attr_code]):
                    attr_vals["values"].append({
                        "sequence": opt_sequence,
                        "code": opt_code,
                        "name": opt_name,
                        #"image": opt_image,
                    })
                res.append(attr_vals)
            vals[obj.id] = res
        print("vals", vals)
        return vals

    def get_customer_price(self, ids, context={}):  # XXX: make it faster
        pricelist_id = context.get("pricelist_id")
        pricelist_ids = context.get("pricelist_ids")
        if pricelist_ids is None and pricelist_id:
            pricelist_ids = [pricelist_id]
        vals = {}
        for obj in self.browse(ids):
            sale_price = None
            discount_text = None
            discount_percent = None
            if pricelist_ids:
                min_sale_price = None
                for item in obj.pricelist_items:
                    if item.list_id.id in pricelist_ids:
                        sale_price = (item.price or 0)
                        if min_sale_price is None or sale_price < min_sale_price:
                            min_sale_price = sale_price
                            discount_text = item.discount_text
                            discount_percent = item.discount_percent
                sale_price = min_sale_price
            if sale_price is None:
                sale_price = (obj.sale_price or 0)
            has_discount = sale_price < (obj.sale_price or 0)
            vals[obj.id] = {
                "customer_price": sale_price,
                "customer_has_discount": has_discount,
                "customer_discount_text": discount_text,
                "customer_discount_percent": discount_percent,
            }
        return vals

    def get_stock_qty(self, ids, context={}):
        vals = {}
        for obj in self.browse(ids):
            qty = 0
            for loc in obj.locations:
                qty += loc.stock_qty
            vals[obj.id] = qty
        return vals

    def get_stock_lots(self, ids, context={}):
        db = database.get_connection()
        res = db.query(
            "SELECT b.product_id,b.location_id,p.sale_price,l.id AS lot_id,l.number,l.weight,SUM(b.qty_virt) AS qty FROM stock_balance b JOIN stock_lot l ON l.id=b.lot_id JOIN product p ON p.id=b.product_id WHERE b.product_id IN %s GROUP BY b.product_id,b.location_id,p.sale_price,l.id,l.number,l.weight ORDER BY l.expiry_date,l.received_date",
            tuple(ids))
        lots = {}
        prod_lots = {}
        for r in res:
            k = (r.product_id, r.lot_id)
            if k not in lots:
                lot_vals = {
                    "id": r.lot_id,
                    "qty": 0,
                }
                lots[k] = lot_vals
                prod_lots.setdefault(r.product_id, []).append(lot_vals)
            lots[k]["qty"] += r.qty
        vals = {}
        for obj in self.browse(ids):
            vals[obj.id] = [
                l["id"] for l in prod_lots.get(obj.id, []) if l["qty"] > 0
            ]
        return vals

    def to_draft(self, ids, context={}):
        for obj in self.browse(ids):
            obj.write({"state": "draft", "is_published": False})  #XXX

    def approve(self, ids, context={}):
        for obj in self.browse(ids):
            res = self.search([["code", "=", obj.code],
                               ["state", "=", "approved"],
                               ["company_id", "=", obj.company_id.id]])
            if res:
                repl_id = res[0]
                obj.copy(context={"replace_id": repl_id})
                obj.write({"active": False})
            else:
                vals = {
                    "state": "approved",
                }
                if not obj.parent_id:
                    group_ids = []  #XXX
                    for group in obj.groups:
                        group_ids.append(group.id)
                    res = get_model("product.group").search(
                        [["code", "=", "new"]])
                    if res and res[0] not in group_ids:
                        group_ids.append(res[0])
                    vals.update({
                        "is_published": True,
                        "groups": [("set", group_ids)]
                    })
                if not obj.approve_date:
                    t = time.strftime("%Y-%m-%d %H:%M:%S")
                    vals.update({"approve_date": t})
                obj.write(vals)
        return {
            "flash": "Products approved",
        }

    def ecom_preview(self, ids, context={}):
        prod_id = ids[0]
        return {
            "next": {
                "type": "url",
                "url": "/ecom_product?product_id=%s" % prod_id,
            }
        }

    def get_sale_price_order_uom(self, ids, context={}):
        vals = {}
        for obj in self.browse(ids):
            factor = obj.sale_to_invoice_uom_factor or 1
            vals[obj.id] = math.ceil((obj.sale_price or 0) * factor)
        return vals

    def create_thumbnails(self, ids, context={}):
        print("Product.create_thumbnails", ids)
        for obj in self.browse(ids):
            if not obj.image:
                continue
            dbname = database.get_active_db()
            if not dbname:
                return None
            fdir = os.path.join(os.getcwd(), "static", "db", dbname, "files")
            path = os.path.join(fdir, obj.image)
            basename, ext = os.path.splitext(obj.image)
            res = "," in basename
            if not res:
                rand = base64.urlsafe_b64encode(os.urandom(8)).decode()
                res = os.path.splitext(obj.image)
                basename, ext = res
                fname2 = basename + "," + rand + ext
                #rename image
                dest_path = fdir + "/" + fname2
                print("destination path and file name ", dest_path)
                cmd = "cp %s %s" % (path, dest_path)
                os.system(cmd)
                obj.write({
                    'image': fname2,
                })
                utils.create_thumbnails(fname2)
            else:
                print("called", obj.image)
                utils.create_thumbnails(obj.image)
Example #22
0
class ImportStatement(Model):
    _name = "import.statement"
    _transient = True
    _fields = {
        "account_id":
        fields.Many2One("account.account",
                        "Account",
                        required=True,
                        on_delete="cascade"),
        "date_start":
        fields.Date("From Date", required=True),
        "date_end":
        fields.Date("To Date", required=True),
        "file":
        fields.File("CSV File", required=True),
        "encoding":
        fields.Selection([["utf-8", "UTF-8"], ["tis-620", "TIS-620"]],
                         "Encoding",
                         required=True),
    }
    _defaults = {
        "encoding": "utf-8",
    }

    def import_data(self, ids, context={}):
        obj = self.browse(ids[0])
        acc_id = obj.account_id.id
        dbname = get_active_db()
        data = open(os.path.join("static", "db", dbname, "files", obj.file),
                    "rb").read().decode(obj.encoding)
        found_delim = False
        for delim in (",", ";", "\t"):
            try:
                try:
                    rd = csv.reader(StringIO(data), delimiter=delim)
                except:
                    raise Exception("Invalid CSV file")
                headers = next(rd)
                headers = [h.strip() for h in headers]
                for h in [
                        "Date", "Description", "Spent", "Received", "Balance"
                ]:
                    if not h in headers:
                        raise Exception("Missing header: '%s'" % h)
                found_delim = True
                break
            except:
                pass
        if not found_delim:
            raise Exception("Failed to open CSV file")
        rows = [r for r in rd]
        if not rows:
            raise Exception("Statement is empty")
        formats = [
            "%Y-%m-%d", "%d/%m/%Y", "%m/%d/%Y", "%d/%m/%y", "%m/%d/%y",
            "%d-%m-%y", "%m-%d-%y"
        ]
        date_fmt = None
        for fmt in formats:
            fmt_ok = True
            for row in rows:
                vals = dict(zip(headers, row))
                date = vals["Date"].strip()
                if not date:
                    continue
                try:
                    datetime.strptime(date, fmt)
                except:
                    fmt_ok = False
                    break
            if fmt_ok:
                date_fmt = fmt
                break
        if not date_fmt:
            raise Exception("Could not detect date format")
        lines = []
        for i, row in enumerate(rows):
            vals = dict(zip(headers, row))
            try:
                date = vals["Date"].strip()
                if not date:
                    raise Exception("Missing date")
                date = datetime.strptime(date, date_fmt).strftime("%Y-%m-%d")
                if date < obj.date_start:
                    raise Exception(
                        "Transaction date is before start date: %s" % date)
                if date > obj.date_end:
                    raise Exception("Transaction date is after end date: %s" %
                                    date)
                balance = vals["Balance"].strip().replace(",", "")
                if not balance:
                    raise Exception("missing balance")
                try:
                    balance = float(balance)
                except:
                    raise Exception("Failed to read Balance amount")
                description = vals.get("Description").strip()
                try:
                    spent = vals["Spent"].strip().replace(",", "")
                    spent = float(spent) if spent else 0
                except:
                    raise Exception("Failed to read Spent amount")
                try:
                    received = vals["Received"].strip().replace(",", "")
                    received = float(received) if received else 0
                except:
                    raise Exception("Failed to read Received amount")
                if not spent and not received:
                    raise Exception("No spent or received amount")
                if spent and received:
                    raise Exception(
                        "Can not have both Spent and Received amounts on the same line"
                    )
                line_vals = {
                    "date": date,
                    "balance": balance,
                    "description": description,
                    "spent": spent,
                    "received": received,
                }
                lines.append(line_vals)
            except Exception as e:
                raise Exception("Error on line %d (%s)" % (i + 2, e))
        if not lines:
            raise Exception("Empty statement")
        first_bal = lines[0]["balance"] + lines[0]["spent"] - lines[0][
            "received"]
        first_date = lines[0]["date"]
        res = get_model("account.statement.line").search(
            [["statement_id.account_id", "=", acc_id],
             ["date", "<", first_date]],
            order="date desc,id desc",
            limit=1)
        if res:
            prev_line = get_model("account.statement.line").browse(res[0])
            prev_bal = prev_line.balance
            if abs(first_bal - prev_bal) > 0.001:
                raise Exception("Invalid balance: previous balance is %.2f" %
                                prev_bal)
        st_vals = {
            "account_id": acc_id,
            "date_start": obj.date_start,
            "date_end": obj.date_end,
            "balance_start": first_bal,
            "lines": [("create", v) for v in lines],
        }
        stmt_id = get_model("account.statement").create(st_vals)
        return {
            "next": {
                "name": "statement",
                "mode": "page",
                "active_id": stmt_id,
            }
        }

    def onchange_account(self, context={}):
        data = context["data"]
        account_id = data["account_id"]
        acc = get_model("account.account").browse(account_id)
        if acc.statements:
            st = acc.statements[0]
            d = datetime.strptime(st.date_end, "%Y-%m-%d") + timedelta(days=1)
            data["date_start"] = d.strftime("%Y-%m-%d")
            data["date_end"] = (d + relativedelta(day=31)).strftime("%Y-%m-%d")
        else:
            data["date_start"] = (datetime.today() -
                                  relativedelta(day=1)).strftime("%Y-%m-%d")
            data["date_end"] = (datetime.today() +
                                relativedelta(day=31)).strftime("%Y-%m-%d")
        return data
Example #23
0
class Employee(Model):
    _name = "hr.employee"
    _string = "Employee"
    _name_field = "first_name"  # XXX
    _multi_company = True
    _key = ["code", "company_id"]
    _export_field = "code"

    _fields = {
        "code":
        fields.Char("Employee Code", search=True),
        "department_id":
        fields.Many2One("hr.department", "Department", search=True),
        "title":
        fields.Selection(
            [["mr", "Mr."], ["mrs", "Mrs."], ["miss", "Miss"], ["ms", "Ms."]],
            "Title"),
        "first_name":
        fields.Char("First Name", search=True, translate=True),
        "last_name":
        fields.Char("Last Name", required=True, search=True, translate=True),
        "hire_date":
        fields.Date("Hire Date"),
        "work_status":
        fields.Selection([["working", "Working"], ["dismissed", "Dismissed"],
                          ["resigned", "Resigned"], ["died", "Died"]],
                         "Work Status"),
        "work_type":
        fields.Selection(
            [["monthly", "Monthly"], ["daily", "Daily"], ["hourly", "Job"]],
            "Work Type"),
        "resign_date":
        fields.Date("Resign Date"),
        "position":
        fields.Char("Position", search=True),
        "birth_date":
        fields.Date("Birth Date"),
        "age":
        fields.Integer("Age", function="get_age"),
        "gender":
        fields.Selection([["male", "Male"], ["female", "Female"]], "Gender"),
        "marital_status":
        fields.Selection([["single", "Single"], ["married", "Married"],
                          ["divorced", "Divorced"], ["widowed", "Widowed"]],
                         "Marital Status"),
        "addresses":
        fields.One2Many("address", "employee_id", "Address"),
        "id_no":
        fields.Char("ID No."),
        "drive_license_type":
        fields.Selection([["car", "Car"], ['motorcycle', 'Motorcycle']],
                         "Driving License"),
        "drive_license_no":
        fields.Char("Driving License No."),
        "country_id":
        fields.Many2One("country", "Country"),
        "bank_account":
        fields.Char("Bank Account"),
        "salary":
        fields.Decimal("Salary"),
        "picture":
        fields.File("Picture"),
        "tax_no":
        fields.Char("Taxpayer ID No."),
        "spouse_first_name":
        fields.Char("Spouse First Name"),
        "spouse_last_name":
        fields.Char("Spouse Last Name"),
        "spouse_title":
        fields.Selection([["mr", "Mr."], ["ms", "Ms."]], "Spouse Title"),
        "spouse_birth_date":
        fields.Date("Spouse Birth Date"),
        "spouse_tax_no":
        fields.Char("Spouse Tax ID No"),
        "spouse_status":
        fields.Selection(
            [["married", "Married existed throughout this tax year"],
             ["married_new", "Married during this tax year"],
             ["divorced", "Divorced during tax year"],
             ["deceased", "Deceased during tax year"]], "Spouse Status"),
        "spouse_filing_status":
        fields.Selection(
            [["joint", "Has income and file joint return"],
             ["separate", "Has income and file separate tax return"],
             ["no_income", "Has no income"]], "Spouse Filing Status"),
        "num_child1":
        fields.Integer("No. of Children #1 (C3)"),
        "num_child2":
        fields.Integer("No. of Children #2 (C3)"),
        "social_no":
        fields.Char("Social No."),
        "social_register":
        fields.Boolean("Register Soc. Secur."),
        "social_calc_method":
        fields.Selection(
            [["regular", "Regular Rate"], ["none", "Not Participate"],
             ["special", "Special Rate"]], "Calc. Method"),
        "prov_fund_no":
        fields.Char("Prov. Fund No."),
        "prov_open_date":
        fields.Char("Opened Prov. Fund A/C Date"),
        "prov_rate_employer":
        fields.Decimal("Employer Contribution (%)"),
        "prov_rate_employee":
        fields.Decimal("Employee Contribution (%)"),
        "gov_pension_fund":
        fields.Decimal("Gov. Pension Fund Amount (B2)"),
        "teacher_fund":
        fields.Decimal("Teacher Aid Fund Amount (B3)"),
        "old_disabled":
        fields.Decimal("Older than 65 or disabled (personal, B4)"),
        "old_disabled_spouse":
        fields.Decimal("Older than 65 or disabled (spouse, B5)"),
        "severance_pay":
        fields.Decimal("Severance Pay (B6)"),
        "education_donation":
        fields.Decimal("Education Donations (A8)"),
        "other_donation":
        fields.Decimal("Other Donations (A10)"),
        "house_deduct":
        fields.Decimal("Exemption for home buyer (A13)"),
        "wht_amount":
        fields.Decimal("Withholding Tax Amount (A15)"),
        "father_id_no":
        fields.Char("Father ID No. (C4)"),
        "mother_id_no":
        fields.Char("Mother ID No. (C4)"),
        "spouse_father_id_no":
        fields.Char("Father of spouse ID No. (C4)"),
        "spouse_mother_id_no":
        fields.Char("Mother of spouse ID No. (C4)"),
        "disabled_support":
        fields.Decimal("Disabled person support (C5)"),
        "parent_health_insurance":
        fields.Decimal("Parent Health Insurance (C6)"),
        "life_insurance":
        fields.Decimal("Life Insurance (C7)"),
        "retirement_mutual_fund":
        fields.Decimal("Retirement Mutual Fund (C9)"),
        "long_term_equity_fund":
        fields.Decimal("Long Term Equity Fund (C10)"),
        "interest_residence":
        fields.Decimal("Interest paid for residence (C11)"),
        "other_deduct":
        fields.Decimal("Other Deductions (C12)"),
        "comments":
        fields.One2Many("message", "related_id", "Comments"),
        "active":
        fields.Boolean("Active"),
        "time_in":
        fields.DateTime("Last Sign In",
                        function="get_attend",
                        function_multi=True),
        "time_out":
        fields.DateTime("Last Sign Out",
                        function="get_attend",
                        function_multi=True),
        "attend_state":
        fields.Selection([["absent", "Absent"], ["present", "Present"]],
                         "Status",
                         function="get_attend",
                         function_multi=True),
        "user_id":
        fields.Many2One("base.user", "User", search=True),
        "payslips":
        fields.One2Many("hr.payslip", "employee_id", "Payslips"),
        "documents":
        fields.One2Many("document", "related_id", "Documents"),
        "phone":
        fields.Char("Phone", search=True),
        "approver_id":
        fields.Many2One("base.user", "Approver"),
        "company_id":
        fields.Many2One("company", "Company"),
        "leave_types":
        fields.Many2Many("hr.leave.type", "Leave Types"),
        "attendance_id":
        fields.Integer("Attendance ID"),
        "email":
        fields.Char("Email", search=True),
        'profile_id':
        fields.Many2One("hr.payitem.profile", "Pay Item Profile"),
        'schedule_id':
        fields.Many2One("hr.schedule", "Working Schedule"),
        'leaves':
        fields.One2Many('hr.leave', 'employee_id', 'Leaves'),
    }

    def _get_code(self, context={}):
        while 1:
            code = get_model("sequence").get_number("employee")
            if not code:
                return None
            res = self.search([["code", "=", code]])
            if not res:
                return code
            get_model("sequence").increment("employee")

    _defaults = {
        "active": True,
        "work_status": "working",
        "code": _get_code,
        "company_id": lambda *a: get_active_company(),
    }
    _order = "code,first_name,last_name"

    def name_get(self, ids, context={}):
        vals = []
        for obj in self.browse(ids):
            if obj.first_name:
                name = obj.first_name + " " + obj.last_name
            else:
                name = obj.last_name
            if obj.code:
                name += " [%s]" % obj.code
            vals.append((obj.id, name))
        return vals

    def name_search(self, name, condition=[], limit=None, context={}):
        cond = [[
            "or", ["first_name", "ilike", "%" + name + "%"],
            ["last_name", "ilike", "%" + name + "%"],
            ["code", "ilike", "%" + name + "%"]
        ], condition]
        ids = self.search(cond, limit=limit)
        return self.name_get(ids, context)

    def get_age(self, ids, context={}):
        vals = {}
        cr_year = int(time.strftime('%Y'))
        for obj in self.browse(ids):
            if obj.birth_date:
                age = cr_year - int(obj.birth_date[0:4])
            else:
                age = 0
            vals[obj.id] = age
        return vals

    def get_attend(self, ids, context={}):
        vals = {}
        for obj in self.browse(ids):
            # user_id=obj.user_id.id
            # if user_id:
            # db=database.get_connection()
            #res=db.get("SELECT MAX(time) AS time_in FROM hr_attendance WHERE user_id=%s AND action='sign_in'",user_id)
            # time_in=res.time_in
            #res=db.get("SELECT MAX(time) AS time_out FROM hr_attendance WHERE user_id=%s AND action='sign_out'",user_id)
            # time_out=res.time_out
            # else:
            # time_in=None
            # time_out=None
            db = database.get_connection()
            res = db.get(
                "SELECT MAX(time) AS time_in FROM hr_attendance WHERE employee_id=%s AND action='sign_in'",
                obj.id)
            time_in = res.time_in
            res = db.get(
                "SELECT MAX(time) AS time_out FROM hr_attendance WHERE employee_id=%s AND action='sign_out'",
                obj.id)
            time_out = res.time_out
            if time_in:
                if time_out and time_out > time_in:
                    state = "absent"
                else:
                    today = time.strftime("%Y-%m-%d")
                    if time_in.startswith(today):
                        state = "present"
                    else:
                        state = "absent"
                    # should not show timeout of anotherday
                    # if not time_out.startswith(today):
                    # time_out=None
            else:
                state = "absent"
            vals[obj.id] = {
                "time_in": time_in,
                "time_out": time_out,
                "attend_state": state,
            }
        return vals

    def get_address(self, ids, context={}):
        obj = self.browse(ids)[0]
        if not obj.addresses:
            return ""
        addr = obj.addresses[0]
        res = addr.get_address_text()
        return res[addr.id]

    def onchange_num_child(self, context={}):
        data = context["data"]
        setting = get_model("hr.payroll.settings").browse(1)
        child_alw_limit = setting.child_alw_limit or 0
        child_total = (data['num_child1'] or 0) + (data['num_child2'] or 0)
        if child_alw_limit and child_total > child_alw_limit:
            data['num_child1'] = 0
            data['num_child2'] = 0
        return data
Example #24
0
class Document(Model):
    _name = "document"
    _string = "Document"
    _audit_log = True
    _fields = {
        "file":
        fields.File("File"),
        "categ_id":
        fields.Many2One("document.categ", "Category", search=True),
        "description":
        fields.Text("Description", search=True),
        "contact_id":
        fields.Many2One("contact", "Contact", search=True),
        "related_id":
        fields.Reference(
            [["sale.quot", "Quotation"], ["sale.order", "Sales Order"],
             ["purchase.order", "Purchase Order"], ["job", "Service Order"],
             ["project", "Project"], ["hr.employee", "Employee"],
             ["account.invoice", "Invoice"], ["account.payment", "Payment"],
             ["account.track.categ", "Tracking Category"]], "Related To"),
        "date":
        fields.Date("Date Created", required=True, search=True),
        "attachments":
        fields.One2Many("attach", "related_id", "Attachments"),
        "comments":
        fields.One2Many("message", "related_id", "Comments"),
        "expiry_date":
        fields.Date("Expiry Date", search=True),
        "expiring_soon":
        fields.Boolean("Expiring Soon",
                       store=False,
                       function_search="search_expiring"),
        "expired":
        fields.Boolean("Expired",
                       function="get_expired",
                       function_search="search_expired"),
        "create_job":
        fields.Boolean("Automatically Create Job To Renew"),  # XXX: deprecated
        "active":
        fields.Boolean("Active"),
        "days_remaining":
        fields.Integer("Days Remaining", function="get_days_remaining"),
        "reminders":
        fields.One2Many("reminder", "doc_id", "Reminders"),
        "state":
        fields.Selection([["draft", "Draft"], ["verified", "Verified"]],
                         "Status"),
        "share":
        fields.Boolean("Share With Contact"),
    }
    _order = "date desc"

    def _get_contact(self, context={}):
        defaults = context.get("defaults")
        if not defaults:
            return
        related_id = defaults.get("related_id")
        if not related_id:
            return
        model, model_id = related_id.split(",")
        model_id = int(model_id)
        if model == "job":
            job = get_model("job").browse(model_id)
            return job.contact_id.id
        elif model == "sale.quot":
            quot = get_model("sale.quot").browse(model_id)
            return quot.contact_id.id

    _defaults = {
        "date": lambda *a: time.strftime("%Y-%m-%d"),
        "contact_id": _get_contact,
        "active": True,
        "state": "draft",
    }
    _constraints = ["_check_date"]

    def _check_date(self, ids, context={}):
        for obj in self.browse(ids):
            if obj.expiry_date:
                if obj.expiry_date and obj.expiry_date < obj.date:
                    raise Exception("Expiry date is before creation date")

    def name_get(self, ids, context={}):
        vals = []
        for obj in self.browse(ids):
            if obj.file:
                s, ext = os.path.splitext(obj.file)
                name = s.rsplit(",")[0] + ext
            else:
                name = "#%d" % obj.id
            vals.append((obj.id, name))
        return vals

    def search_expiring(self, clause, context={}):
        d = datetime.date.today() + datetime.timedelta(days=35)
        return [["expiry_date", "<=", d.strftime("%Y-%m-%d")]]

    def onchange_categ(self, context={}):
        data = context["data"]
        categ_id = data.get("categ_id")
        if not categ_id:
            return
        categ = get_model("document.categ").browse(categ_id)
        expire_after = categ.expire_after
        if expire_after:
            expire_after = expire_after.strip()
            t0 = datetime.datetime.strptime(data.get("date"), "%Y-%m-%d")
            p = expire_after[-1]
            n = int(expire_after[:-1])
            if p == "y":
                dt = relativedelta(years=n)
            elif p == "m":
                dt = relativedelta(months=n)
            elif p == "w":
                dt = relativedelta(weeks=n)
            elif p == "d":
                dt = relativedelta(days=n)
            exp_date = (t0 + dt).strftime("%Y-%m-%d")
        else:
            exp_date = None
        return {
            "expiry_date": exp_date,
            "create_job": categ.create_job,
        }

    def onchange_file(self, context={}):
        print("onchange_file")
        data = context["data"]
        filename = data["file"]
        if not filename:
            return
        categ_id = data["categ_id"]
        if not categ_id:
            return
        categ = get_model("document.categ").browse(categ_id)
        fmt = categ.file_name
        if not fmt:
            return
        contact_id = data.get("contact_id")
        if contact_id:
            contact = get_model("contact").browse(contact_id)
        else:
            contact = None
        date = data["date"]
        vals = {
            "contact_code": contact and contact.code or "",
            "doc_code": categ.code or "",
            "Y": date[0:4],
            "y": date[2:4],
            "m": date[5:7],
            "d": date[8:10],
        }
        filename2 = fmt % vals
        res = os.path.splitext(filename)
        rand = base64.urlsafe_b64encode(os.urandom(8)).decode()
        filename2 += "," + rand + res[1]
        if filename2 != filename:
            path = utils.get_file_path(filename)
            path2 = utils.get_file_path(filename2)
            os.rename(path, path2)
        return {
            "vals": {
                "file": filename2,
            }
        }

    def get_expired(self, ids, context={}):
        vals = {}
        for obj in self.browse(ids):
            if obj.expiry_date:
                vals[obj.id] = obj.expiry_date < time.strftime("%Y-%m-%d")
            else:
                vals[obj.id] = False
        return vals

    def search_expired(self, clause, context={}):
        return [["expiry_date", "<", time.strftime("%Y-%m-%d")]]

    def do_create_job(self, ids, context={}):
        for obj in self.browse(ids):
            categ = obj.categ_id
            tmpl = categ.job_template_id
            if not tmpl:
                continue
            job_id = tmpl.create_job(context={"contact_id": obj.contact_id.id})
            obj.write({"create_job": False, "related_id": "job,%d" % job_id})

    def create_jobs(self, context={}):
        try:
            for categ in get_model("document.categ").search_browse(
                [["create_job", "=", True]]):
                days = categ.create_days and int(categ.create_days) or 0
                d = (datetime.date.today() +
                     datetime.timedelta(days=days)).strftime("%Y-%m-%d")
                for doc in self.search_browse([["expiry_date", "<=", d],
                                               ["create_job", "=", True]]):
                    doc.do_create_job()
        except Exception as e:
            print("WARNING: Failed to create jobs")
            import traceback
            traceback.print_exc()

    def check_days_before_expiry(self,
                                 ids,
                                 days=None,
                                 days_from=None,
                                 days_to=None,
                                 categs=None,
                                 context={}):
        print("Document.check_days_before_expiry", ids, days)
        cond = []
        if days != None:
            d = (datetime.date.today() +
                 datetime.timedelta(days=days)).strftime("%Y-%m-%d")
            cond.append(["expiry_date", "=", d])
        if days_from != None:
            d = (datetime.date.today() +
                 datetime.timedelta(days=days_from)).strftime("%Y-%m-%d")
            cond.append(["expiry_date", "<=", d])
        if days_to != None:
            d = (datetime.date.today() +
                 datetime.timedelta(days=days_to)).strftime("%Y-%m-%d")
            cond.append(["expiry_date", ">=", d])
        if categs:
            cond.append(["categ_id.code", "in", categs])
        if ids:
            cond.append(["ids", "in", ids])
        ids = self.search(cond)
        return ids

    def get_days_remaining(self, ids, context={}):
        vals = {}
        d = datetime.datetime.now()
        for obj in self.browse(ids):
            if obj.expiry_date:
                vals[obj.id] = (
                    datetime.datetime.strptime(obj.expiry_date, "%Y-%m-%d") -
                    d).days
            else:
                vals[obj.id] = None
        return vals

    def create_reminders(self, ids, context={}):
        for obj in self.browse(ids):
            categ = obj.categ_id
            if not categ:
                continue
            obj.write({"reminders": [("delete_all", )]})
            for tmpl in categ.reminder_templates:
                s = tmpl.scheduled_date.strip()
                days = int(s)
                d = datetime.datetime.strptime(
                    obj.expiry_date,
                    "%Y-%m-%d") + datetime.timedelta(days=days)
                ctx = {"doc": obj}
                subject = render_template(tmpl.subject, ctx)
                body = render_template(tmpl.body or "", ctx)
                vals = {
                    "scheduled_date": d.strftime("%Y-%m-%d"),
                    "doc_id": obj.id,
                    "user_id": tmpl.user_id.id,
                    "subject": subject,
                    "body": body,
                }
                get_model("reminder").create(vals)

    def delete_pending_reminders(self, ids, context={}):
        for obj in self.browse(ids):
            for reminder in obj.reminders:
                if reminder.state == "pending":
                    reminder.delete()

    def create(self, vals, **kw):
        new_id = super().create(vals, **kw)
        obj = self.browse(new_id)
        obj.create_reminders()
        return new_id

    def write(self, ids, vals, **kw):
        old_categs = {}
        old_dates = {}
        for obj in self.browse(ids):
            old_categs[obj.id] = obj.categ_id.id
            old_dates[obj.id] = obj.expiry_date
        super().write(ids, vals, **kw)
        for obj in self.browse(ids):
            if obj.categ_id.id != old_categs[
                    obj.id] or obj.expiry_date != old_dates[obj.id]:
                obj.create_reminders()

    def to_draft(self, ids, context={}):
        for obj in self.browse(ids):
            obj.write({"state": "draft"})

    def to_verified(self, ids, context={}):
        for obj in self.browse(ids):
            obj.write({"state": "verified"})

    def delete(self, ids, **kw):
        files = []
        for obj in self.browse(ids):
            if obj.file:
                files.append(obj.file)
        super().delete(ids, **kw)
        for f in files:
            path = utils.get_file_path(f)
            os.remove(path)
Example #25
0
class Theme(Model):
    _name = "theme"
    _string = "Theme"
    _fields = {
        "name": fields.Char("Name", required=True),
        "description": fields.Text("Description"),
        "file": fields.File("ZIP File"),
        "templates": fields.One2Many("template", "theme_id", "Templates"),
    }
    _defaults = {
        "state": "inactive",
    }

    def activate(self, ids, context={}):
        obj = self.browse(ids)[0]
        all_ids = self.search([])
        self.write(all_ids, {"state": "inactive"})
        obj.write({"state": "active"})
        obj.update()

    def update(self, ids, context={}):
        obj = self.browse(ids)[0]
        obj.export_static_files()
        obj.load_templates()

    def export_static_files(self, ids, context={}):
        obj = self.browse(ids)[0]
        theme = obj.name
        dbname = get_active_db()
        if obj.file:
            zip_path = utils.get_file_path(obj.file)
            zf = zipfile.ZipFile(zip_path)
            for n in zf.namelist():
                if not n.startswith("static/"):
                    continue
                if n[-1] == "/":
                    continue
                n2 = n[7:]
                if n2.find("..") != -1:
                    continue
                data = zf.read(n)
                f_path = "static/db/" + dbname + "/themes/" + theme + "/" + n2
                dir_path = os.path.dirname(f_path)
                if not os.path.exists(dir_path):
                    os.makedirs(dir_path)
                print("export file", f_path)
                open(f_path, "wb").write(data)
        else:
            export_module_file_all("themes/" + theme + "/static",
                                   "static/db/" + dbname + "/themes/" + theme)

    def load_templates(self, ids, context={}):
        obj = self.browse(ids[0])
        if obj.file:
            zip_path = utils.get_file_path(obj.file)
            zf = zipfile.ZipFile(zip_path)
            for n in zf.namelist():
                if not n.startswith("templates/"):
                    continue
                if not n.endswith(".hbs"):
                    continue
                n2 = n[10:-4]
                if n2.find("..") != -1:
                    continue
                print("load template", n2)
                data = zf.read(n)
                vals = {
                    "name": n2,
                    "template": data.decode(),
                    "theme_id": obj.id,
                }
                get_model("template").merge(vals)
        else:
            theme = obj.name
            loaded_modules = module.get_loaded_modules()
            for m in reversed(loaded_modules):
                if not pkg_resources.resource_isdir(
                        m, "themes/" + theme + "/templates"):
                    continue
                for f in pkg_resources.resource_listdir(
                        m, "themes/" + theme + "/templates"):
                    if not f.endswith(".hbs"):
                        continue
                    f2 = f[:-4]
                    print("load template", f2)
                    data = pkg_resources.resource_string(
                        m, "themes/" + theme + "/templates/" + f)
                    vals = {
                        "name": f2,
                        "template": data.decode(),
                        "theme_id": obj.id,
                    }
                    get_model("template").merge(vals)
Example #26
0
class Settings(Model):
    _name = "settings"
    _key = ["name"]
    _audit_log = True
    _fields = {
        "name":
        fields.Char("Display Name"),  # not used any more...
        "legal_name":
        fields.Char("Legal Name"),  # not used any more...
        "company_type_id":
        fields.Many2One("company.type", "Organization Type"),
        "currency_id":
        fields.Many2One("currency", "Default Currency", multi_company=True),
        "account_receivable_id":
        fields.Many2One("account.account",
                        "Account Receivable",
                        multi_company=True),
        "tax_receivable_id":
        fields.Many2One("account.tax.rate", "Account Receivable Tax"),
        "account_payable_id":
        fields.Many2One("account.account",
                        "Account Payable",
                        multi_company=True),
        "tax_payable_id":
        fields.Many2One("account.tax.rate", "Account Payable Tax"),
        "year_end_day":
        fields.Selection(_days, "Financial Year End (Day)"),
        "year_end_month":
        fields.Selection(_months, "Financial Year End (Month)"),
        "lock_date":
        fields.Date("Lock Date"),
        "nf_email":
        fields.Char("Email to Netforce"),  # XXX: deprecated
        "share_settings":
        fields.One2Many("share.access", "settings_id", "Sharing Settings"),
        "currency_gain_id":
        fields.Many2One("account.account",
                        "Currency Gain Account",
                        multi_company=True),
        "currency_loss_id":
        fields.Many2One("account.account",
                        "Currency Loss Account",
                        multi_company=True),
        "unpaid_claim_id":
        fields.Many2One("account.account",
                        "Unpaid Expense Claims Account",
                        multi_company=True),
        "retained_earnings_account_id":
        fields.Many2One("account.account",
                        "Retained Earnings Account",
                        multi_company=True),
        "logo":
        fields.File("Company Logo", multi_company=True),
        "package":
        fields.Char("Package", readonly=True),
        "version":
        fields.Char("Version"),
        "tax_no":
        fields.Char("Tax ID Number", multi_company=True),
        "branch_no":
        fields.Char("Branch Number", multi_company=True),
        "addresses":
        fields.One2Many("address",
                        "settings_id",
                        "Addresses",
                        function="get_addresses"),
        "date_format":
        fields.Char("Date Format"),
        "use_buddhist_date":
        fields.Boolean("Use Buddhist Date"),
        "phone":
        fields.Char("Phone", multi_company=True),
        "fax":
        fields.Char("Fax", multi_company=True),
        "website":
        fields.Char("Website", multi_company=True),
        "root_url":
        fields.Char("Root URL"),
        "sale_journal_id":
        fields.Many2One("account.journal", "Sales Journal"),
        "purchase_journal_id":
        fields.Many2One("account.journal", "Purchase Journal"),
        "pay_in_journal_id":
        fields.Many2One("account.journal", "Receipts Journal"),
        "pay_out_journal_id":
        fields.Many2One("account.journal", "Disbursements Journal"),
        "general_journal_id":
        fields.Many2One("account.journal", "General Journal"),
        "default_address_id":
        fields.Many2One("address",
                        "Default Address",
                        function="get_default_address"),
        "ar_revenue_id":
        fields.Many2One("account.account",
                        "Revenue Account",
                        multi_company=True),
        # XXX: rename for report
        "input_report_id":
        fields.Many2One("account.account",
                        "Input Vat Account",
                        multi_company=True),
        # XXX: rename for report
        "output_report_id":
        fields.Many2One("account.account",
                        "Output Vat Account",
                        multi_company=True),
        # XXX: rename for report
        "wht3_report_id":
        fields.Many2One("account.account", "WHT3 Account", multi_company=True),
        # XXX: rename for report
        "wht53_report_id":
        fields.Many2One("account.account", "WHT53 Account",
                        multi_company=True),
        "sale_copy_picking":
        fields.Boolean("Auto-copy sales orders to goods issue"),
        "sale_copy_invoice":
        fields.Boolean("Auto-copy sales orders to customer invoice"),
        "sale_copy_production":
        fields.Boolean("Auto-copy sales orders to production"),
        "rounding_account_id":
        fields.Many2One("account.account",
                        "Rounding Account",
                        multi_company=True),
        "production_waiting_suborder":
        fields.Boolean("Wait Sub-Order"),  # XXX: check this
        "anon_profile_id":
        fields.Many2One("profile", "Anonymous User Profile"),
        "pick_in_journal_id":
        fields.Many2One("stock.journal", "Goods Receipt Journal"),
        "pick_out_journal_id":
        fields.Many2One("stock.journal", "Goods Issue Journal"),
        "pick_internal_journal_id":
        fields.Many2One("stock.journal", "Goods Transfer Journal"),
        "stock_count_journal_id":
        fields.Many2One("stock.journal", "Stock Count Journal"),
        "landed_cost_journal_id":
        fields.Many2One("stock.journal", "Landed Cost Journal"),
        "transform_journal_id":
        fields.Many2One("stock.journal", "Transform Journal"),
        "production_journal_id":
        fields.Many2One("stock.journal", "Production Journal"),
        "product_borrow_journal_id":
        fields.Many2One("stock.journal", "Borrow Request Journal"),
        "stock_cost_mode":
        fields.Selection(
            [["periodic", "Periodic"], ["perpetual", "Perpetual"]],
            "Inventory Costing Mode"),
        "landed_cost_variance_account_id":
        fields.Many2One("account.account",
                        "Landed Cost Variance Account",
                        multi_company=True),
        "est_ship_account_id":
        fields.Many2One("account.account",
                        "Estimate Shipping Account",
                        multi_company=True),
        "est_duty_account_id":
        fields.Many2One("account.account",
                        "Estimate Duty Account",
                        multi_company=True),
        "act_ship_account_id":
        fields.Many2One("account.account",
                        "Actual Shipping Account",
                        multi_company=True),
        "act_duty_account_id":
        fields.Many2One("account.account",
                        "Actual Duty Account",
                        multi_company=True),
        "menu_icon":
        fields.File("Menu Icon"),
        "stock_cost_auto_compute":
        fields.Boolean("Auto Compute Cost"),
        "purchase_copy_picking":
        fields.Boolean("Auto-copy purchase orders to goods receipt"),
        "purchase_copy_invoice":
        fields.Boolean("Auto-copy purchase orders to supplier invoice"),
        "lot_expiry_journal_id":
        fields.Many2One("stock.journal", "Lot Expiry Journal"),
        "auto_create_delivery_order":
        fields.Boolean("Auto-create delivery orders"),
    }
    _defaults = {
        "package": "free",
    }

    def get_address_str(self, ids, context={}):
        obj = self.browse(ids[0])
        if not obj.addresses:
            return ""
        addr = obj.addresses[0]
        return addr.name_get()[0][1]

    def write(self, ids, vals, **kw):
        res = super().write(ids, vals, **kw)
        if "date_format" in vals or "use_buddhist_date" in vals:
            static.clear_translations()  # XXX: rename this

    def get_fiscal_year_end(self, date=None):
        if date:
            d0 = datetime.datetime.strptime(date, "%Y-%m-%d").date()
        else:
            d0 = datetime.date.today()
        settings = self.browse(1)
        if not settings.year_end_month or not settings.year_end_day:
            raise Exception("Missing fiscal year end")
        month = int(settings.year_end_month)
        day = int(settings.year_end_day)
        d = datetime.date(d0.year, month, day)
        if d < d0:
            d += relativedelta(years=1)
        return d.strftime("%Y-%m-%d")

    def get_fiscal_year_start(self, date=None):
        d1 = self.get_fiscal_year_end(date)
        d = datetime.datetime.strptime(d1, "%Y-%m-%d") - relativedelta(
            years=1) + datetime.timedelta(days=1)
        return d.strftime("%Y-%m-%d")

    def get_default_address(self, ids, context={}):
        vals = {}
        for obj in self.browse(ids):
            vals[obj.id] = obj.addresses and obj.addresses[0].id or None
        return vals

    def get_addresses(self, ids, context={}):
        vals = {}
        comp_id = access.get_active_company()
        for obj in self.browse(ids):
            res = get_model("address").search(
                [["settings_id", "=", obj.id],
                 [
                     "or", ["company_id", "=", None],
                     ["company_id", "child_of", comp_id]
                 ]])
            vals[obj.id] = res
        return vals
Example #27
0
class ConvBal(Model):
    _name = "conv.bal"
    _transient = True
    _fields = {
        "date": fields.Date("Conversion Date", required=True),
        "accounts": fields.One2Many("conv.account", "conv_id", "Account Balances"),
        "sale_invoices": fields.One2Many("conv.sale.invoice", "conv_id", "Sales Invoices"),
        "purch_invoices": fields.One2Many("conv.purch.invoice", "conv_id", "Purchase Invoices"),
        "total_debit": fields.Decimal("Total Debit", function="get_total", function_multi=True),
        "total_credit": fields.Decimal("Total Credit", function="get_total", function_multi=True),
        "total_sale": fields.Decimal("Total Amount Due", function="get_total", function_multi=True),
        "total_purch": fields.Decimal("Total Amount Due", function="get_total", function_multi=True),
        "total_ar": fields.Decimal("Account Receivable Balance", function="get_total", function_multi=True),
        "total_ap": fields.Decimal("Account Payable Balance", function="get_total", function_multi=True),
        "move_id": fields.Many2One("account.move", "Opening Entry"),
        "file": fields.File("CSV File"),
        "date_fmt": fields.Char("Date Format"),
    }
    _defaults = {
        "date": lambda *a: time.strftime("%Y-%m-01"),
        "date_fmt": "%m/%d/%Y",
    }

    def get_total(self, ids, context={}):
        vals = {}
        for obj in self.browse(ids):
            total_debit = 0
            total_credit = 0
            total_sale = 0
            total_purch = 0
            total_ar = 0
            total_ap = 0
            for acc in obj.accounts:
                total_debit += acc.debit or 0
                total_credit += acc.credit or 0
                if acc.account_id.type == "receivable":
                    total_ar += (acc.debit or 0) - (acc.credit or 0)
                if acc.account_id.type == "payable":
                    total_ap += (acc.credit or 0) - (acc.debit or 0)
            for inv in obj.sale_invoices:
                total_sale += inv.amount_due
            for inv in obj.purch_invoices:
                total_purch += inv.amount_due
            vals[obj.id] = {
                "total_debit": total_debit,
                "total_credit": total_credit,
                "total_sale": total_sale,
                "total_purch": total_purch,
                "total_ar": total_ar,
                "total_ap": total_ap,
            }
        return vals

    def next1(self, ids, context={}):
        obj = self.browse(ids)[0]
        if obj.total_debit - obj.total_credit != 0:
            raise Exception("Conversion balance is not balanced")
        return {
            "next": {
                "name": "conv_bal",
                "active_id": obj.id,
                "view_xml": "conv_bal2",
            }
        }

    def next2(self, ids, context={}):
        obj = self.browse(ids)[0]
        check_dupplicate_number(lines=obj.sale_invoices)
        return {
            "next": {
                "name": "conv_bal",
                "active_id": obj.id,
                "view_xml": "conv_bal3",
            }
        }

    def back2(self, ids, context={}):
        obj = self.browse(ids)[0]
        return {
            "next": {
                "name": "conv_bal",
                "active_id": obj.id,
                "view_xml": "conv_bal1",
            }
        }

    def next3(self, ids, context={}):
        obj = self.browse(ids)[0]
        check_dupplicate_number(lines=obj.purch_invoices)
        obj.create_open_entry()
        obj.create_sale_invoices()
        obj.create_purch_invoices()
        return {
            "next": {
                "name": "journal_entry",
                "mode": "form",
                "active_id": obj.move_id.id,
            },
            "flash": "Conversion balance created successfully",
        }

    def back3(self, ids, context={}):
        obj = self.browse(ids)[0]
        return {
            "next": {
                "name": "conv_bal",
                "active_id": obj.id,
                "view_xml": "conv_bal2",
            }
        }

    def create_open_entry(self, ids, context={}):
        obj = self.browse(ids)[0]
        settings = get_model("settings").browse(1)
        desc = "Conversion balance %s" % obj.date
        if not settings.general_journal_id:
            raise Exception("General journal not found")
        journal_id = settings.general_journal_id.id
        vals = {
            "journal_id": journal_id,
            "number": "OPENING ENTRY",
            "date": obj.date,
            "narration": desc,
        }
        move_id = get_model("account.move").create(vals)
        for acc in obj.accounts:
            if acc.account_id.type in ("receivable", "payable"):
                continue
            line_vals = {
                "move_id": move_id,
                "description": desc,
                "account_id": acc.account_id.id,
                "debit": acc.debit or 0,
                "credit": acc.credit or 0,
                "amount_cur": acc.amount_cur,
                "track_id": acc.track_id and acc.track_id.id or None,
                "track2_id": acc.track2_id and acc.track2_id.id or None,
            }
            line_id = get_model("account.move.line").create(line_vals)
        for inv in obj.sale_invoices:
            amt = inv.amount_due
            line_vals = {
                "move_id": move_id,
                "description": desc + ", %s" % inv.number,
                "account_id": inv.account_id.id,
                "debit": amt > 0 and amt or 0,
                "credit": amt < 0 and -amt or 0,
                "contact_id": inv.contact_id.id,
                "amount_cur": inv.amount_cur,
                "track_id": inv.track_id and inv.track_id.id or None,
                "track2_id": inv.track2_id and inv.track2_id.id or None,
            }
            line_id = get_model("account.move.line").create(line_vals)
            inv.write({"move_line_id": line_id})
        for inv in obj.purch_invoices:
            amt = -inv.amount_due
            line_vals = {
                "move_id": move_id,
                "description": desc + ", %s" % inv.number,
                "account_id": inv.account_id.id,
                "debit": amt > 0 and amt or 0,
                "credit": amt < 0 and -amt or 0,
                "contact_id": inv.contact_id.id,
                "amount_cur": -inv.amount_cur if inv.amount_cur is not None else None,
                "track_id": inv.track_id and inv.track_id.id or None,
                "track2_id": inv.track2_id and inv.track2_id.id or None,
            }
            line_id = get_model("account.move.line").create(line_vals)
            inv.write({"move_line_id": line_id})
        get_model("account.move").post([move_id])
        obj.write({"move_id": move_id})

    def create_sale_invoices(self, ids, context={}):
        obj = self.browse(ids)[0]
        settings = get_model("settings").browse(1)
        desc = "Conversion balance %s" % obj.date
        for inv in obj.sale_invoices:
            vals = {
                "type": "out",
                "inv_type": inv.amount_due >= 0 and "invoice" or "credit",
                "contact_id": inv.contact_id.id,
                "date": inv.date,
                "due_date": inv.due_date,
                "number": inv.number,
                "ref": inv.ref,
                "memo": desc,
                "lines": [],
                "state": "waiting_payment",
                "account_id": inv.account_id.id,
                "reconcile_move_line_id": inv.move_line_id.id,
                "currency_id": inv.account_id.currency_id.id,
                "currency_rate": inv.amount_due / inv.amount_cur if inv.amount_cur else None,
            }
            line_vals = {
                "description": desc,
                "amount": abs(inv.amount_cur or inv.amount_due),
                "track_id": inv.track_id and inv.track_id.id or None,
                "track2_id": inv.track2_id and inv.track2_id.id or None,
            }
            vals["lines"].append(("create", line_vals))
            res = get_model("account.invoice").search([["number", "=", inv.number], ["type", "=", "out"]])
            if res:
                inv2_id = res[0]
                inv2 = get_model("account.invoice").browse(inv2_id)
                if abs(inv2.amount_total) - abs(inv.amount_due) != 0:  # XXX
                    raise Exception("Failed to update invoice %s: different amount" % inv.number)
                if inv2.state == "draft":
                    raise Exception("Failed to update invoice %s: invalid state" % inv.number)
                inv2.write({
                    "move_id": obj.move_id.id,
                    "reconcile_move_line_id": inv.move_line_id.id,
                })
            else:
                get_model("account.invoice").create(vals)

    def create_purch_invoices(self, ids, context={}):
        obj = self.browse(ids)[0]
        settings = get_model("settings").browse(1)
        desc = "Conversion balance %s" % obj.date
        for inv in obj.purch_invoices:
            vals = {
                "type": "in",
                "inv_type": inv.amount_due >= 0 and "invoice" or "credit",
                "contact_id": inv.contact_id.id,
                "date": inv.date,
                "due_date": inv.due_date,
                "number": inv.number,
                "ref": inv.ref,
                "memo": desc,
                "lines": [],
                "state": "waiting_payment",
                "account_id": inv.account_id.id,
                "reconcile_move_line_id": inv.move_line_id.id,
                "currency_id": inv.account_id.currency_id.id,
                "currency_rate": inv.amount_due / inv.amount_cur if inv.amount_cur else None,
            }
            line_vals = {
                "description": desc,
                "amount": abs(inv.amount_cur or inv.amount_due),
                "track_id": inv.track_id and inv.track_id.id or None,
                "track2_id": inv.track2_id and inv.track2_id.id or None,
            }
            vals["lines"].append(("create", line_vals))
            res = get_model("account.invoice").search([["number", "=", inv.number], ["type", "=", "in"]])
            if res:
                inv2_id = res[0]
                inv2 = get_model("account.invoice").browse(inv2_id)
                if abs(inv2.amount_total) - abs(inv.amount_due) != 0:  # XXX
                    raise Exception("Failed to update invoice %s" % inv.number)
                if inv2.state == "draft":
                    raise Exception("Failed to update invoice %s: invalid state" % inv.number)
                inv2.write({
                    "move_id": obj.move_id.id,
                    "reconcile_move_line_id": inv.move_line_id.id,
                })
            else:
                get_model("account.invoice").create(vals)

    def update_total(self, context):
        data = context["data"]
        data["total_debit"] = 0
        data["total_credit"] = 0
        for line in data["accounts"]:
            if not line:
                continue
            debit = line.get("debit") or 0
            credit = line.get("credit") or 0
            data["total_debit"] += debit
            data["total_credit"] += credit
        return data

    def import_acc(self, ids, context={}):
        obj = self.browse(ids)[0]
        obj.write({"file": None})
        return {
            "next": {
                "view_cls": "form_popup",
                "model": "conv.bal",
                "active_id": obj.id,
                "target": "_popup",
                "view_xml": "conv_import1",
            }
        }

    def import_sale(self, ids, context={}):
        obj = self.browse(ids)[0]
        obj.write({"file": None})
        return {
            "next": {
                "view_cls": "form_popup",
                "model": "conv.bal",
                "active_id": obj.id,
                "target": "_popup",
                "view_xml": "conv_import2",
            }
        }

    def import_purch(self, ids, context={}):
        obj = self.browse(ids)[0]
        obj.write({"file": None})
        return {
            "next": {
                "view_cls": "form_popup",
                "model": "conv.bal",
                "active_id": obj.id,
                "target": "_popup",
                "view_xml": "conv_import3",
            }
        }

    def get_track_categ(self, val):
        val=val.strip()
        mr = get_model("account.track.categ")
        ctx={
            'parent_vals': val,
        }
        res = mr.import_get(val, context=ctx)
        if not res:
            raise Exception("Invalid value for field %s"%val)
        val = res
        return val

    def import_acc_file(self, ids, context):
        obj = self.browse(ids)[0]
        path = get_file_path(obj.file)
        data = open(path).read()
        rd = csv.reader(StringIO(data))
        headers = next(rd)
        headers = [h.strip() for h in headers]
        del_ids = get_model("conv.account").search([["conv_id", "=", obj.id]])
        get_model("conv.account").delete(del_ids)
        for row in rd:
            print("row", row)
            line = dict(zip(headers, row))
            print("line", line)
            if not line.get("Account"):
                continue
            acc_code = line["Account"].strip()
            if not acc_code:
                continue
            res = get_model("account.account").search([["code", "=", acc_code]])
            if not res:
                raise Exception("Account code not found: %s" % acc_code)
            acc_id = res[0]
            debit = float(line["Debit"].strip().replace(",", "") or 0)
            credit = float(line["Credit"].strip().replace(",", "") or 0)
            amount_cur = line["Currency Amt"].strip().replace(",", "")
            if amount_cur:
                amount_cur = float(amount_cur)
            else:
                amount_cur = None
            track_id=line.get('Track-1')
            if track_id:
                track_id = self.get_track_categ(track_id)
            track2_id=line.get('Track-2')
            if track2_id:
                track2_id = self.get_track_categ(track2_id)
            vals = {
                "conv_id": obj.id,
                "account_id": acc_id,
                "debit": debit,
                "credit": credit,
                "amount_cur": amount_cur,
                "track_id": track_id,
                "track2_id": track2_id,
            }
            get_model("conv.account").create(vals)
        return {
            "next": {
                "name": "conv_bal",
                "active_id": obj.id,
                "view_xml": "conv_bal1",
            }
        }

    def import_sale_file(self, ids, context):
        obj = self.browse(ids)[0]
        path = get_file_path(obj.file)
        data = open(path).read()
        rd = csv.reader(StringIO(data))
        headers = next(rd)
        headers = [h.strip() for h in headers]
        del_ids = get_model("conv.sale.invoice").search([["conv_id", "=", obj.id]])
        get_model("conv.sale.invoice").delete(del_ids)
        i = 1
        for row in rd:
            i += 1
            try:
                print("row", row)
                line = dict(zip(headers, row))
                print("line", line)
                if not line.get("Number"):
                    continue
                number = line["Number"].strip()
                if not number:
                    continue
                ref = line["Reference"].strip()
                contact_name = line["Contact"].strip()
                res = get_model("contact").search([["name", "=", contact_name]])
                if not res:
                    raise Exception("Contact not found: '%s'" % contact_name)
                contact_id = res[0]
                date = datetime.datetime.strptime(line["Date"].strip(), obj.date_fmt).strftime("%Y-%m-%d")
                due_date = datetime.datetime.strptime(line["Due Date"].strip(), obj.date_fmt).strftime("%Y-%m-%d")
                amount_due = float(line["Amount Due"].strip().replace(",", "") or 0)
                acc_code = line["Account"].strip()
                if not acc_code:
                    raise Exception("Account is missing")
                res = get_model("account.account").search([["code", "=", acc_code]])
                if not res:
                    raise Exception("Account code not found: %s" % acc_code)
                acc_id = res[0]
                amount_cur = None
                if line.get("Currency Amt"):
                    amount_cur = line["Currency Amt"].strip().replace(",", "")
                    amount_cur = float(amount_cur)
                track_id=line.get('Track-1')
                if track_id:
                    track_id = self.get_track_categ(track_id)
                track2_id=line.get('Track-2')
                if track2_id:
                    track2_id = self.get_track_categ(track2_id)
                vals = {
                    "conv_id": obj.id,
                    "number": number,
                    "ref": ref,
                    "contact_id": contact_id,
                    "date": date,
                    "due_date": due_date,
                    "amount_due": amount_due,
                    "account_id": acc_id,
                    "amount_cur": amount_cur,
                    "track_id": track_id,
                    "track2_id": track2_id,
                }
                get_model("conv.sale.invoice").create(vals)
            except Exception as e:
                raise Exception("Error line %d: %s" % (i, e))
        return {
            "next": {
                "name": "conv_bal",
                "active_id": obj.id,
                "view_xml": "conv_bal2",
            }
        }

    def import_purch_file(self, ids, context):
        obj = self.browse(ids)[0]
        path = get_file_path(obj.file)
        data = open(path).read()
        rd = csv.reader(StringIO(data))
        headers = next(rd)
        headers = [h.strip() for h in headers]
        del_ids = get_model("conv.purch.invoice").search([["conv_id", "=", obj.id]])
        get_model("conv.purch.invoice").delete(del_ids)
        i = 1
        for row in rd:
            i += 1
            try:
                print("row", row)
                line = dict(zip(headers, row))
                print("line", line)
                if not line.get("Number"):
                    continue
                number = line["Number"].strip()
                if not number:
                    continue
                ref = line["Reference"].strip()
                contact_name = line["Contact"].strip()
                res = get_model("contact").search([["name", "=", contact_name]])
                if not res:
                    raise Exception("Contact not found: '%s'" % contact_name)
                contact_id = res[0]
                date = datetime.datetime.strptime(line["Date"].strip(), obj.date_fmt).strftime("%Y-%m-%d")
                due_date = datetime.datetime.strptime(line["Due Date"].strip(), obj.date_fmt).strftime("%Y-%m-%d")
                amount_due = float(line["Amount Due"].strip().replace(",", "") or 0)
                acc_code = line["Account"].strip()
                if not acc_code:
                    raise Exception("Account is missing")
                res = get_model("account.account").search([["code", "=", acc_code]])
                if not res:
                    raise Exception("Account code not found: %s" % acc_code)
                acc_id = res[0]
                amount_cur=None
                if line.get("Currency Amt"):
                    amount_cur = line["Currency Amt"].strip().replace(",", "")
                    amount_cur = float(amount_cur)
                track_id=line.get('Track-1')
                if track_id:
                    track_id = self.get_track_categ(track_id)
                track2_id=line.get('Track-2')
                if track2_id:
                    track2_id = self.get_track_categ(track2_id)
                vals = {
                    "conv_id": obj.id,
                    "number": number,
                    "ref": ref,
                    "contact_id": contact_id,
                    "date": date,
                    "due_date": due_date,
                    "amount_due": amount_due,
                    "account_id": acc_id,
                    "amount_cur": amount_cur,
                    "track_id": track_id,
                    "track2_id": track2_id,
                }
                get_model("conv.purch.invoice").create(vals)
            except Exception as e:
                raise Exception("Error line %d: %s" % (i, e))
        return {
            "next": {
                "name": "conv_bal",
                "active_id": obj.id,
                "view_xml": "conv_bal3",
            }
        }
Example #28
0
class ImportModule(Model):
    _name = "import.module"
    _transient = True
    _fields = {
        "file": fields.File("Zip File"),
    }

    def do_import(self, ids, context={}):
        print("import modules")
        obj = self.browse(ids)[0]
        path = utils.get_file_path(obj.file)
        zf = zipfile.ZipFile(path, "r")
        modules = {}
        for n in zf.namelist():
            if not n.endswith("/module.json"):
                continue
            path = n[:-len("/module.json")]
            data = zf.read(n).decode()
            vals = json.loads(data)
            name = vals["name"]
            module = {
                "name": name,
                "path": path,
                "info": vals,
                "model_files": [],
                "layout_files": [],
                "action_files": [],
                "template_files": [],
                "script_files": [],
            }
            modules[name] = module
        if not modules:
            raise Exception("No modules found")
        print("modules", modules.keys())

        ids = get_model("module").search([["name", "in", modules.keys()]])
        get_model("module").delete_modules(ids)

        for name, module in modules.items():
            info = module["info"]
            vals = {
                "name": name,
                "description": info.get("description"),
                "version": info.get("version"),
                "author": info.get("author"),
            }
            get_model("module").create(vals)

        for path in zf.namelist():
            found = False
            for name, module in modules.items():
                if path.startswith(module["path"] + "/"):
                    path2 = path[len(module["path"]) + 1:]
                    module = modules[name]
                    found = True
                    break
            if not found:
                continue
            if path2.startswith("models/") and path2.endswith(".xml"):
                module["model_files"].append(path)
            elif path2.startswith("layouts/") and path2.endswith(".xml"):
                module["layout_files"].append(path)
            elif path2.startswith("actions/") and path2.endswith(".json"):
                module["action_files"].append(path)
            elif path2.startswith("templates/") and path2.endswith(".hbs"):
                module["template_files"].append(path)
            elif path2.startswith("scripts/") and path2.endswith(".js"):
                module["script_files"].append(path)

        for mod_name, module in modules.items():
            for path in module["model_files"]:
                print("import model", path)
                n = os.path.splitext(os.path.basename(path))[0]
                data = zf.read(path).decode()
                root = etree.fromstring(data)
                vals = {
                    "name": root.attrib["name"],
                    "module_id": get_model("module").get(mod_name,
                                                         require=True),
                }
                if root.attrib.get("string"):
                    vals["string"] = root.attrib["string"]
                if root.attrib.get("description"):  # XXX
                    vals["description"] = root.attrib["description"]
                res = get_model("model").search([["name", "=", vals["name"]]])
                if res:
                    model_id = res[0]
                    get_model("model").write([model_id], vals)
                else:
                    model_id = get_model("model").create(vals)

        def _import_field(el, model, mod_name):
            vals = {
                "model_id": get_model("model").get(model, require=True),
                "module_id": get_model("module").get(mod_name, require=True),
                "name": el.attrib["name"],
                "string": el.attrib["string"],
                "type": el.attrib["type"],
            }
            if el.attrib.get("relation"):
                vals["relation_id"] = get_model("model").get(
                    el.attrib["relation"], require=True)
            if el.attrib.get("relfield"):
                vals["relfield_id"] = get_model("field").find_field(
                    el.attrib["relation"], el.attrib["relfield"])
            if el.attrib.get("selection"):
                vals["selection"] = el.attrib["selection"]
            if el.attrib.get("required"):
                vals["required"] = True
            if el.attrib.get("readonly"):
                vals["readonly"] = True
            if el.attrib.get("function"):
                vals["function"] = el.attrib["function"]
            if el.attrib.get("default"):
                vals["default"] = el.attrib["default"]
            if el.attrib.get("search"):
                vals["search"] = True
            if el.attrib.get("condition"):
                vals["condition"] = el.attrib["condition"]
            if el.attrib.get("description"):
                vals["description"] = el.attrib["description"]
            get_model("field").create(vals)

        for mod_name, module in modules.items():
            for path in module["model_files"]:
                print("import fields #1", path)
                n = os.path.splitext(os.path.basename(path))[0]
                data = zf.read(path).decode()
                root = etree.fromstring(data)
                for el in root:
                    if el.tag != "field":
                        continue
                    if el.attrib.get("relfield"):
                        continue
                    _import_field(el, root.attrib["name"], mod_name)

        for mod_name, module in modules.items():
            for path in module["model_files"]:
                print("import fields #2", path)
                n = os.path.splitext(os.path.basename(path))[0]
                data = zf.read(path).decode()
                root = etree.fromstring(data)
                for el in root:
                    if el.tag != "field":
                        continue
                    if not el.attrib.get("relfield"):
                        continue
                    _import_field(el, root.attrib["name"], mod_name)

        for mod_name, module in modules.items():
            for path in module["layout_files"]:
                print("import layout", path)
                n = os.path.splitext(os.path.basename(path))[0]
                data = zf.read(path).decode()
                root = etree.fromstring(data)
                vals = {
                    "name": n,
                    "module_id": get_model("module").get(mod_name,
                                                         require=True),
                    "type": root.tag.lower(),
                }
                if root.attrib.get("model"):
                    vals["model_id"] = get_model("model").get(
                        root.attrib["model"], require=True)
                    del root.attrib["model"]
                if root.attrib.get("inherit"):
                    vals["inherit"] = root.attrib["inherit"]
                    del root.attrib["inherit"]
                vals["layout"] = etree.tostring(root,
                                                pretty_print=True).decode()
                get_model("view.layout").create(vals)

        for mod_name, module in modules.items():
            for path in module["action_files"]:
                print("import action", path)
                n = os.path.splitext(os.path.basename(path))[0]
                data = zf.read(path).decode()
                vals = json.loads(data)
                vals2 = {
                    "name": n,
                    "module_id": get_model("module").get(mod_name,
                                                         require=True),
                }
                if vals.get("string"):
                    vals2["string"] = vals["string"]
                if vals.get("view"):
                    vals2["view"] = vals["view"]
                if vals.get("model"):
                    vals2["model_id"] = get_model("model").get(vals["model"],
                                                               require=True)
                if vals.get("view_layout"):
                    vals2["view_layout_id"] = get_model("view.layout").get(
                        vals["view_layout"], require=True)
                if vals.get("menu"):
                    vals2["menu_id"] = get_model("view.layout").get(
                        vals["menu"], require=True)
                if vals.get("options"):
                    vals2["options"] = json.dumps(vals["options"])
                get_model("action").create(vals2)

        for mod_name, module in modules.items():
            for path in module["template_files"]:
                print("import template", path)
                n = os.path.splitext(os.path.basename(path))[0]
                data = zf.read(path).decode()
                vals = {
                    "name": n,
                    "module_id": get_model("module").get(mod_name,
                                                         require=True),
                    "template": data,
                }
                get_model("template").create(vals)

        for mod_name, module in modules.items():
            for path in module["script_files"]:
                print("import script", path)
                n = os.path.splitext(os.path.basename(path))[0]
                data = zf.read(path).decode()
                vals = {
                    "name": n,
                    "module_id": get_model("module").get(mod_name,
                                                         require=True),
                    "code": data,
                }
                get_model("script").create(vals)

        return {
            "next": {
                "name": "module",
            },
            "flash": "Modules imported successfully",
        }
Example #29
0
class Message(Model):
    _name = "message"
    _string = "Message"
    _fields = {
        "date":
        fields.DateTime("Date", required=True, search=True),
        "from_id":
        fields.Many2One("base.user", "From User", required=True, search=True),
        "to_id":
        fields.Many2One("base.user", "To User", search=True),
        "subject":
        fields.Char("Subject", search=True),
        "body":
        fields.Text("Message Body", required=True, search=True),
        "attach":
        fields.File("Attachment"),
        "ref_uuid":
        fields.Char("Reference UUID"),
        "related_id":
        fields.Reference([], "Related To"),
        "state":
        fields.Selection([["new", "New"], ["opened", "Opened"]],
                         "Status",
                         required=True,
                         search=True),
        "open_dummy":
        fields.Boolean("Open Dummy", function="get_open_dummy"),
    }
    _order = "date desc"
    _defaults = {
        "date": lambda *a: time.strftime("%Y-%m-%d %H:%M:%S"),
        "from_id": lambda *a: get_active_user(),
        "state": "new",
    }

    def create(self, vals, **kw):
        new_id = super().create(vals, **kw)
        to_id = vals.get("to_id")
        if to_id:
            db = get_connection()
            get_model("ws.event").new_event("new_message", to_id)
        self.trigger([new_id], "created")
        return new_id

    def get_open_dummy(self, ids, context={}):
        vals = {}
        user_id = get_active_user()
        for obj in self.browse(ids):
            if user_id == obj.to_id.id:
                obj.write({"state": "opened"})
            vals[obj.id] = True
        return vals

    def send_from_template(self,
                           template=None,
                           from_user=None,
                           to_user=None,
                           context={}):
        print("####################################################")
        print("Message.send_from_template", template, from_user, to_user)
        res = get_model("message.tmpl").search([["name", "=", template]])
        if not res:
            raise Exception("Template not found: %s" % template)
        tmpl_id = res[0]
        tmpl = get_model("message.tmpl").browse(tmpl_id)
        try:
            trigger_model = context.get("trigger_model")
            if not trigger_model:
                raise Exception("Missing trigger model")
            print("trigger_model", trigger_model)
            tm = get_model(trigger_model)
            trigger_ids = context.get("trigger_ids")
            if trigger_ids is None:
                raise Exception("Missing trigger ids")
            print("trigger_ids", trigger_ids)
            user_id = get_active_user()
            user = get_model("base.user").browse(user_id)
            for obj in tm.browse(trigger_ids):
                tmpl_ctx = {"obj": obj, "user": user}
                from_user_ = from_user
                if not from_user_:
                    try:
                        from_user_ = render_template(tmpl.from_user or "",
                                                     tmpl_ctx)
                    except:
                        raise Exception("Error in 'From User': %s" %
                                        tmpl.from_user)
                if not from_user_:
                    raise Exception("Missing 'From User'")
                res = get_model("base.user").search(
                    [["login", "=", from_user_]])
                if not res:
                    raise Exception("'From User' not found: %s" % from_user_)
                from_id = res[0]
                to_user_ = to_user
                if not to_user_:
                    try:
                        to_user_ = render_template(tmpl.to_user or "",
                                                   tmpl_ctx)
                    except:
                        raise Exception("Error in 'To User': %s" %
                                        tmpl.to_user)
                if not to_user_:
                    raise Exception("Missing 'To User'")
                to_ids = []
                for login in [x.strip() for x in to_user_.split(",")]:
                    res = get_model("base.user").search([["login", "=",
                                                          login]])
                    if not res:
                        raise Exception("'To User' not found: %s" % login)
                    to_id = res[0]
                    to_ids.append(to_id)
                try:
                    subject = render_template(tmpl.subject, tmpl_ctx)
                except:
                    raise Exception("Error in  'Subject': %s" % tmpl.subject)
                try:
                    body = render_template(tmpl.body, tmpl_ctx)
                except:
                    raise Exception("Error in 'Body': %s" % tmpl.body)
                for to_id in to_ids:
                    vals = {
                        "from_id": from_id,
                        "to_id": to_id,
                        "subject": subject,
                        "body": body,
                    }
                    self.create(vals)
        except Exception as e:
            import traceback
            traceback.print_exc()
            raise Exception("Error in template %s: %s" % (template, e))