Exemplo n.º 1
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
Exemplo n.º 2
0
class Profile(Model):
    _name = "profile"
    _string = "Profile"
    _key = ["name"]
    _fields = {
        "name": fields.Char("Name", required=True, search=True),
        "code": fields.Char("Short Code"),
        "perms": fields.One2Many("profile.access", "profile_id", "Model Permissions"),
        "field_perms": fields.One2Many("field.access", "profile_id", "Field Permissions"),
        "menu_perms": fields.One2Many("menu.access", "profile_id", "Menu Permissions"),
        "other_perms": fields.Many2Many("permission", "Other Permissions"),
        "home_action": fields.Char("Login Action"),
        "login_company_id": fields.Many2One("company", "Login Company"),
        "prevent_login": fields.Boolean("Prevent Login"),
        "comments": fields.One2Many("message", "related_id", "Comments"),
        "default_model_perms": fields.Selection([["full", "Full Access"], ["readonly","Read-only Access"], ["no", "No Access"]], "Default Model Permissions"),
        "default_menu_access": fields.Selection([["visible", "Visible"], ["hidden", "Hidden"]], "Default Menu Access"),
    }
    _order = "name"
    _defaults = {
        "default_model_perms": "full",
    }

    def get_data(self, context={}):
        vals = {}
        perms = []
        for m in get_model("model").search_browse([]):
            perms.append({
                "model_id": [m.id, m.string],
            })
        vals["perms"] = perms
        return vals

    def copy(self, ids, context={}):
        obj = self.browse(ids)[0]
        vals = {
            "name": obj.name + " (Copy)",
            "perms": [],
            "other_perms": [("set", [p.id for p in obj.other_perms])],
            "home_action": obj.home_action,
        }
        for perm in obj.perms:
            vals["perms"].append(("create", {
                "model_id": perm.model_id.id,
                "perm_read": perm.perm_read,
                "perm_create": perm.perm_create,
                "perm_write": perm.perm_write,
                "perm_delete": perm.perm_delete,
                "view_all": perm.view_all,
                "modif_all": perm.modif_all,
            }))
        profile_id = get_model("profile").create(vals)
        return {
            "next": {
                "name": "profile",
                "mode": "form",
                "active_id": profile_id,
            },
            "flash": "New profile created",
        }
Exemplo n.º 3
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 
Exemplo n.º 4
0
class VariantValue(Model):
    _name = "product.variant.values"
    _transient = True
    _fields = {
        "popup_id": fields.Many2One("prod.create.variants", "Popup", required=True, on_delete="cascade"),
        "attribute_id": fields.Many2One("product.attribute", "Attribute", required=True),
        "values": fields.Many2Many("product.attribute.option", "Values", required=True),
    }
Exemplo n.º 5
0
class Group(Model):
    _name = "user.group"
    _string = "Group"
    _fields = {
        "name": fields.Char("Group Name", required=True, search=True),
        "users": fields.Many2Many("base.user", "Users"),
        "comments": fields.One2Many("message", "related_id", "Comments"),
    }
    _order = "name"
Exemplo n.º 6
0
class PayItemProfile(Model):
    _name = "hr.payitem.profile"
    _string = "Pay Item Profile"
    _fields = {
        "name": fields.Char("Name", required=True, search=True),
        #'lines': fields.One2Many("hr.payitem.profile.line","profile_id","Lines"),
        'pay_items': fields.Many2Many("hr.payitem", "Pay Items"),
    }
    _defaults = {}
Exemplo n.º 7
0
class Leave_type(Model):
    _name = "hr.leave.type"
    _string = "Leave Type"
    _key = ["name"]
    _fields = {
        "name": fields.Char("Name", search=True),
        "description": fields.Text("Description"),
        "comments": fields.One2Many("message", "related_id", "Comments"),
        "employees": fields.Many2Many("hr.employee", "Employees"),
    }
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"
Exemplo n.º 9
0
class Event(Model):
    _name = "sale.event"
    _string = "Event"
    _key = ["code"]
    _fields = {
        "name":
        fields.Char("Event Name", required=True, search=True),
        "code":
        fields.Char("Event Code", required=True, search=True),
        "products":
        fields.Many2Many("product",
                         "Products",
                         condition=[["is_published", "=", True]])
    }
Exemplo n.º 10
0
class PricelistAdd(Model):
    _name = "pricelist.add"
    _transient = True
    _fields = {
        "pricelist_id":
        fields.Many2One("price.list",
                        "Price List",
                        required=True,
                        on_delete="cascade"),
        "product_categs":
        fields.Many2Many("product.categ", "Product Categories"),
    }
    _defaults = {
        "pricelist_id": lambda self, ctx: ctx["refer_id"],
    }

    def add_products(self, ids, context={}):
        obj = self.browse(ids)[0]
        pricelist = obj.pricelist_id
        categ_ids = [c.id for c in obj.product_categs]
        for prod in get_model("product").search_browse(
            [["categs.id", "in", categ_ids]]):
            factor = pricelist.factor or 1.0
            if pricelist.base_price == "product":
                base_price = prod.sale_price or 0
            elif pricelist.base_price == "other_pricelist":
                if not pricelist.other_pricelist_id:
                    raise Exception("Missing base price list")
                base_price = get_model("price.list").get_price(
                    pricelist.other_pricelist_id.id, prod.id,
                    1) or 0  # XXX: qty
            elif pricelist.base_price == "volume":
                base_price = prod.volume or 0
            else:
                raise Exception("Invalid base price type")
            price = utils.round_amount(base_price * factor, pricelist.rounding,
                                       pricelist.rounding_method)
            vals = {
                "list_id": pricelist.id,
                "product_id": prod.id,
                "price": price,
            }
            get_model("price.list.item").create(vals)
        return {
            "next": {
                "name": "pricelist_item",
            },
            "flash": "Products added to price list",
        }
Exemplo n.º 11
0
class Bom(Model):
    _name = "bom"
    _string = "Bill of Material"
    _name_field = "number"
    _key = ["number"]
    _fields = {
        "number": fields.Char("Number", required=True, search=True),
        "product_id": fields.Many2One("product", "Product", required=True, search=True),
        "qty": fields.Decimal("Qty", required=True, scale=6),
        "uom_id": fields.Many2One("uom", "UoM", required=True),
        "location_id": fields.Many2One("stock.location", "FG Warehouse"),
        "routing_id": fields.Many2One("routing", "Routing"),
        "lines": fields.One2Many("bom.line", "bom_id", "Lines"),
        "comments": fields.One2Many("message", "related_id", "Comments"),
        "documents": fields.One2Many("document", "related_id", "Documents"),
        "max_qty_loss": fields.Decimal("Max Qty Loss", scale=6),
        "container": fields.Selection([["sale", "From Sales Order"]], "FG Container"),
        "lot": fields.Selection([["production", "From Production Order"]], "FG Lot"),
        "qc_tests": fields.Many2Many("qc.test", "QC Tests"),
    }

    def _get_number(self, context={}):
        while 1:
            num = get_model("sequence").get_number("bom")
            if not num:
                return None
            res = self.search([["number", "=", num]])
            if not res:
                return num
            get_model("sequence").increment("bom")

    _defaults = {
        "number": _get_number,
    }

    def onchange_product(self,context={}):
        data=context['data']
        path=context['path']
        line=get_data_path(data,path,parent=True)
        product_id=line['product_id']
        if product_id:
            product=get_model('product').browse(product_id)
            line['uom_id']=product.uom_id.id
        return data
Exemplo n.º 12
0
class Contact(Model):
    _inherit = "contact"
    _fields = {
        "previous_sale_products":
        fields.Many2Many("product",
                         "Previously Ordered Products",
                         function="get_previous_sale_products"),
    }

    def get_previous_sale_products(self, ids, context={}):
        db = database.get_connection()
        res = db.query(
            "SELECT contact_id,product_id,COUNT(*) FROM sale_order_line l JOIN sale_order o ON o.id=l.order_id and plr_order_type='grocery' WHERE o.contact_id IN %s GROUP BY contact_id,product_id",
            tuple(ids))
        contact_prods = {}
        for r in res:
            contact_prods.setdefault(r.contact_id, []).append(r.product_id)
        vals = {}
        for obj in self.browse(ids):
            vals[obj.id] = contact_prods.get(obj.id, [])[:5]  # XXX
        return vals
Exemplo n.º 13
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()),
    }
Exemplo n.º 14
0
class SaleReturnLine(Model):
    _name = "sale.return.line"
    _name_field = "order_id"
    _fields = {
        "order_id":
        fields.Many2One("sale.return",
                        "Sales Return",
                        required=True,
                        on_delete="cascade",
                        search=True),
        "product_id":
        fields.Many2One("product", "Product", search=True),
        "description":
        fields.Text("Description", required=True, search=True),
        "qty":
        fields.Decimal("Qty"),
        "uom_id":
        fields.Many2One("uom", "UoM"),
        "unit_price":
        fields.Decimal("Unit Price", required=True, search=True, scale=6),
        "tax_id":
        fields.Many2One("account.tax.rate", "Tax Rate"),
        "amount":
        fields.Decimal("Amount",
                       function="get_amount",
                       function_multi=True,
                       store=True,
                       function_order=1,
                       search=True),
        "amount_cur":
        fields.Decimal("Amount",
                       function="get_amount",
                       function_multi=True,
                       store=True,
                       function_order=1,
                       search=True),
        "qty_stock":
        fields.Decimal("Qty (Stock UoM)"),
        "qty_received":
        fields.Decimal("Received Qty", function="get_qty_received"),
        "qty_invoiced":
        fields.Decimal("Invoiced Qty", function="get_qty_invoiced"),
        "contact_id":
        fields.Many2One("contact",
                        "Contact",
                        function="_get_related",
                        function_search="_search_related",
                        function_context={"path": "order_id.contact_id"},
                        search=True),
        "date":
        fields.Date("Date",
                    function="_get_related",
                    function_search="_search_related",
                    function_context={"path": "order_id.date"},
                    search=True),
        "user_id":
        fields.Many2One("base.user",
                        "Owner",
                        function="_get_related",
                        function_search="_search_related",
                        function_context={"path": "order_id.user_id"},
                        search=True),
        "amount_discount":
        fields.Decimal("Discount Amount",
                       function="get_amount",
                       function_multi=True),
        "state":
        fields.Selection([("draft", "Draft"), ("confirmed", "Confirmed"),
                          ("done", "Completed"), ("voided", "Voided")],
                         "Status",
                         function="_get_related",
                         function_search="_search_related",
                         function_context={"path": "order_id.state"},
                         search=True),
        "qty2":
        fields.Decimal("Secondary Qty"),
        "location_id":
        fields.Many2One("stock.location",
                        "Location",
                        condition=[["type", "=", "internal"]]),
        "product_categs":
        fields.Many2Many("product.categ",
                         "Product Categories",
                         function="_get_related",
                         function_context={"path": "product_id.categs"},
                         function_search="_search_related",
                         search=True),
        "discount":
        fields.Decimal("Disc %"),  # XXX: rename to discount_percent later
        "discount_amount":
        fields.Decimal("Disc Amt"),
        "qty_avail":
        fields.Decimal("Qty In Stock", function="get_qty_avail"),
        "agg_amount":
        fields.Decimal("Total Amount", agg_function=["sum", "amount"]),
        "agg_qty":
        fields.Decimal("Total Order Qty", agg_function=["sum", "qty"]),
        "remark":
        fields.Char("Remark"),
        "ship_method_id":
        fields.Many2One("ship.method", "Shipping Method"),
        "sequence":
        fields.Char("Item No."),
        "return_type":
        fields.Selection([["refund", "Refund"], ["exchange", "Exchange"]],
                         "Return Type"),
        "reason_code_id":
        fields.Many2One("reason.code",
                        "Reason Code",
                        condition=[["type", "=", "sale_return"]]),
    }

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

    def write(self, ids, vals, context={}):
        super().write(ids, vals, context)
        self.function_store(ids)

    def get_amount(self, ids, context={}):
        vals = {}
        settings = get_model("settings").browse(1)
        sale_ids = []
        for line in self.browse(ids):
            sale_ids.append(line.order_id.id)
        sale_ids = list(set(sale_ids))
        for sale in get_model("sale.return").browse(sale_ids):
            prod_qtys = {}
            for line in sale.lines:
                prod_qtys.setdefault(line.product_id.id, 0)
                prod_qtys[line.product_id.id] += line.qty
            for line in sale.lines:
                amt = line.qty * line.unit_price
                if line.discount:
                    disc = amt * line.discount / 100
                else:
                    disc = 0
                if line.discount_amount:
                    disc += line.discount_amount
                amt -= disc
                order = line.order_id
                vals[line.id] = {
                    "amount":
                    amt,
                    "amount_cur":
                    get_model("currency").convert(amt, order.currency_id.id,
                                                  settings.currency_id.id),
                    "amount_discount":
                    disc,
                }
        return vals

    def get_qty_received(self, ids, context={}):
        order_ids = []
        for obj in self.browse(ids):
            order_ids.append(obj.order_id.id)
        order_ids = list(set(order_ids))
        vals = {}
        for order in get_model("sale.return").browse(order_ids):
            received_qtys = {}
            for move in order.stock_moves:
                if move.state != "done":
                    continue
                prod_id = move.product_id.id
                k = (prod_id, move.location_to_id.id)
                received_qtys.setdefault(k, 0)
                received_qtys[k] += move.qty  # XXX: uom
                k = (prod_id, move.location_from_id.id)
                received_qtys.setdefault(k, 0)
                received_qtys[k] -= move.qty  # XXX: uom
            for line in order.lines:
                k = (line.product_id.id, line.location_id.id)
                received_qty = received_qtys.get(k, 0)  # XXX: uom
                used_qty = min(line.qty, received_qty)
                vals[line.id] = used_qty
                if k in received_qtys:
                    received_qtys[k] -= used_qty
            for line in reversed(order.lines):
                k = (line.product_id.id, line.location_id.id)
                remain_qty = received_qtys.get(k, 0)  # XXX: uom
                if remain_qty:
                    vals[line.id] += remain_qty
                    received_qtys[k] -= remain_qty
        vals = {x: vals[x] for x in ids}
        return vals

    def get_qty_invoiced(self, ids, context={}):
        vals = {}
        for obj in self.browse(ids):
            order = obj.order_id
            qty = 0
            for inv in order.invoices:
                if inv.state == "voided":
                    continue
                for line in inv.lines:
                    if obj.product_id:
                        if line.product_id.id != obj.product_id.id:
                            continue
                    else:
                        if line.product_id or line.description != obj.description:
                            continue
                    if inv.type == "out":
                        qty += line.qty  # XXX: uom
                    elif inv.type == "in":
                        qty -= line.qty  # XXX: uom
            vals[obj.id] = qty
        return vals

    def get_qty_avail(self, ids, context={}):
        vals = {}
        for obj in self.browse(ids):
            prod_id = obj.product_id.id
            loc_id = obj.location_id.id
            if prod_id and loc_id:
                res = get_model("stock.location").compute_balance([loc_id],
                                                                  prod_id)
                qty = res["bal_qty"]
            else:
                qty = None
            vals[obj.id] = qty
        return vals
Exemplo n.º 15
0
class ShipMethod(Model):
    _name = "ship.method"
    _string = "Shipping Method"
    _key = ["code"]
    _fields = {
        "name":
        fields.Char("Name", required=True, search=True),
        "code":
        fields.Char("Code", search=True),
        "rates":
        fields.One2Many("ship.rate", "method_id", "Shipping Rates"),
        "comments":
        fields.One2Many("message", "related_id", "Comments"),
        "sequence":
        fields.Integer("Sequence", required=True),
        "type":
        fields.Selection([], "Type"),
        "ship_product_id":
        fields.Many2One("product", "Shipping Product"),
        "exclude_ship_methods":
        fields.Many2Many("ship.method",
                         "Exclude Shipping Methods",
                         reltable="m2m_ship_method_exclude",
                         relfield="method1_id",
                         relfield_other="method2_id"),
        "active":
        fields.Boolean("Active"),
        "ship_amount":
        fields.Decimal("Shipping Amount", function="get_ship_amount"),
    }
    _order = "sequence"
    _defaults = {
        "active": True,
    }

    def create_delivery_order(self, ids, context={}):
        pass

    def get_ship_amount(self, ids, context={}):
        vals = {}
        contact_id = context.get("contact_id")
        contact = get_model("contact").browse(
            contact_id) if contact_id else None
        addr_id = context.get("ship_address_id")
        if addr_id:
            addr = get_model("address").browse(addr_id)
        else:
            addr = None
        order_amount = context.get("order_amount")
        order_weight = context.get("order_weight")
        for obj in self.browse(ids):
            if contact and contact.ship_free:
                vals[obj.id] = 0
                continue
            amt = None
            for rate in obj.rates:
                #print("try rate",rate.id)
                if rate.country_id and (
                        not addr or addr.country_id.id != rate.country_id.id):
                    #print("  skip country")
                    continue
                if rate.province_id and (not addr or addr.province_id.id !=
                                         rate.province_id.id):
                    #print("  skip province (%s / %s %s)"%(rate.province_id.id,addr.id,addr.province_id.id if addr else None))
                    continue
                if rate.district_id and (not addr or addr.district_id.id !=
                                         rate.district_id.id):
                    #print("  skip district")
                    continue
                if rate.postal_code and (not addr or
                                         addr.postal_code != rate.postal_code):
                    #print("  skip postal code")
                    continue
                if rate.address_name and (not addr
                                          or addr.name != rate.address_name):
                    #print("  skip address name")
                    continue
                if rate.min_amount and (order_amount is None
                                        or order_amount < rate.min_amount):
                    #print("  skip min amount")
                    continue
                if rate.min_weight and (order_weight is None
                                        or order_weight < rate.min_weight):
                    #print("  skip min weight")
                    continue
                #print("  OK ship_price=%s"%rate.ship_price)
                if amt is None or rate.ship_price < amt:
                    amt = rate.ship_price
            #if amt is not None:
            #print("=> shipping price found: %s"%amt)
            #else:
            #print("=> no shipping price found")
            vals[obj.id] = amt
        return vals
Exemplo n.º 16
0
class SaleOrderLine(Model):
    _name = "sale.order.line"
    _name_field = "order_id"
    _fields = {
        "order_id": fields.Many2One("sale.order", "Sales Order", required=True, on_delete="cascade", search=True),
        "product_id": fields.Many2One("product", "Product", search=True),
        "description": fields.Text("Description", required=True, search=True),
        "qty": fields.Decimal("Qty"),
        "uom_id": fields.Many2One("uom", "UoM"),
        "unit_price": fields.Decimal("Unit Price", search=True, required=True, scale=6),
        "tax_id": fields.Many2One("account.tax.rate", "Tax Rate"),
        "amount": fields.Decimal("Amount", function="get_amount", function_multi=True, store=True, function_order=1, search=True),
        "amount_cur": fields.Decimal("Amount", function="get_amount", function_multi=True, store=True, function_order=1, search=True),
        "qty_stock": fields.Decimal("Qty (Stock UoM)"),
        "qty_delivered": fields.Decimal("Delivered Qty", function="get_qty_delivered"),
        "qty_invoiced": fields.Decimal("Invoiced Qty", function="get_qty_invoiced"),
        "contact_id": fields.Many2One("contact", "Contact", function="_get_related", function_search="_search_related", function_context={"path": "order_id.contact_id"}, search=True),
        "date": fields.Date("Date", function="_get_related", function_search="_search_related", function_context={"path": "order_id.date"}, search=True),
        "user_id": fields.Many2One("base.user", "Owner", function="_get_related", function_search="_search_related", function_context={"path": "order_id.user_id"}, search=True),
        "amount_discount": fields.Decimal("Discount Amount", function="get_amount", function_multi=True),
        "state": fields.Selection([("draft", "Draft"), ("confirmed", "Confirmed"), ("done", "Completed"), ("voided", "Voided")], "Status", function="_get_related", function_search="_search_related", function_context={"path": "order_id.state"}, search=True),
        "qty2": fields.Decimal("Secondary Qty"),
        "location_id": fields.Many2One("stock.location", "Location", condition=[["type", "=", "internal"]]),
        "product_categs": fields.Many2Many("product.categ", "Product Categories", function="_get_related", function_context={"path": "product_id.categs"}, function_search="_search_related", search=True),
        "product_categ_id": fields.Many2Many("product.categ", "Product Category", function="_get_related", function_context={"path": "product_id.categ_id"}, function_search="_search_related", search=True),
        "discount": fields.Decimal("Disc %"),  # XXX: rename to discount_percent later
        "discount_amount": fields.Decimal("Disc Amt"),
        "qty_avail": fields.Decimal("Qty In Stock", function="get_qty_avail"),
        "agg_amount": fields.Decimal("Total Amount", agg_function=["sum", "amount"]),
        "agg_qty": fields.Decimal("Total Order Qty", agg_function=["sum", "qty"]),
        "remark": fields.Char("Remark"),
        "ship_method_id": fields.Many2One("ship.method", "Shipping Method"),
        "sequence": fields.Char("Item No."),
        "due_date": fields.Date("Due Date"),
        "est_cost_amount": fields.Float("Est. Cost Amount",function="get_est_profit",function_multi=True),
        "est_profit_amount": fields.Float("Est. Profit Amount",function="get_est_profit",function_multi=True),
        "est_margin_percent": fields.Float("Est. Margin %",function="get_est_profit",function_multi=True),
        "act_cost_amount": fields.Float("Act. Cost Amount",function="get_act_profit",function_multi=True),
        "act_profit_amount": fields.Float("Act. Profit Amount",function="get_act_profit",function_multi=True,store=True),
        "act_margin_percent": fields.Float("Act. Margin %",function="get_act_profit",function_multi=True),
        "promotion_amount": fields.Decimal("Prom Amt",function="get_amount",function_multi=True),
        "agg_act_profit": fields.Decimal("Total Actual Profit", agg_function=["sum", "act_profit_amount"]),
        "production_id": fields.Many2One("production.order","Production Order"),
        "lot_id": fields.Many2One("stock.lot","Lot / Serial Number"),
        "ship_address_id": fields.Many2One("address", "Shipping Address"),
        "packaging_id": fields.Many2One("stock.packaging", "Packaging"),
        "delivery_slot_id": fields.Many2One("delivery.slot","Delivery Slot"),
        "ship_tracking": fields.Char("Tracking Numbers", function="get_ship_tracking"),
    }

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

    def write(self, ids, vals, context={}):
        super(SaleOrderLine, self).write(ids, vals, context)
        self.function_store(ids)

    def get_amount(self, ids, context={}):
        vals = {}
        settings = get_model("settings").browse(1)
        sale_ids=[]
        for line in self.browse(ids):
            sale_ids.append(line.order_id.id)
        sale_ids=list(set(sale_ids))
        for sale in get_model("sale.order").browse(sale_ids):
            prod_qtys={}
            prom_amts={}
            prom_pcts={}
            for line in sale.lines:
                prod_qtys.setdefault(line.product_id.id,0)
                prod_qtys[line.product_id.id]+=line.qty
            for line in sale.used_promotions:
                if line.amount and line.product_id:
                    prom_amts.setdefault(line.product_id.id,0)
                    prom_amts[line.product_id.id]+=line.amount
                elif line.percent:
                    prom_pcts.setdefault(line.product_id.id,0)
                    prom_pcts[line.product_id.id]+=line.percent
            for line in sale.lines:
                amt = line.qty * line.unit_price
                if line.discount:
                    disc = amt * line.discount / 100
                else:
                    disc = 0
                if line.discount_amount:
                    disc += line.discount_amount
                amt-=disc
                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
                order = line.order_id
                vals[line.id] = {
                    "amount": amt,
                    "amount_cur": get_model("currency").convert(amt, order.currency_id.id, settings.currency_id.id),
                    "amount_discount": disc,
                    "promotion_amount": prom_amt,
                }
        return vals

    def get_qty_delivered(self, ids, context={}):
        order_ids = []
        for obj in self.browse(ids):
            order_ids.append(obj.order_id.id)
        order_ids = list(set(order_ids))
        vals = {}
        for order in get_model("sale.order").browse(order_ids):
            delivered_qtys = {}
            for move in order.stock_moves:
                if move.state != "done":
                    continue
                prod_id = move.product_id.id
                k = (prod_id, move.location_from_id.id)
                delivered_qtys.setdefault(k, 0)
                delivered_qtys[k] += move.qty  # XXX: uom
                k = (prod_id, move.location_to_id.id)
                delivered_qtys.setdefault(k, 0)
                delivered_qtys[k] -= move.qty  # XXX: uom
            for line in order.lines:
                k = (line.product_id.id, line.location_id.id)
                delivered_qty = delivered_qtys.get(k, 0)  # XXX: uom
                used_qty = min(line.qty, delivered_qty)
                vals[line.id] = used_qty
                if k in delivered_qtys:
                    delivered_qtys[k] -= used_qty
            for line in reversed(order.lines):
                k = (line.product_id.id, line.location_id.id)
                remain_qty = delivered_qtys.get(k, 0)  # XXX: uom
                if remain_qty:
                    vals[line.id] += remain_qty
                    delivered_qtys[k] -= remain_qty
        vals = {x: vals[x] for x in ids}
        return vals

    def get_qty_invoiced(self, ids, context={}):
        order_ids = []
        for obj in self.browse(ids):
            order_ids.append(obj.order_id.id)
        order_ids = list(set(order_ids))
        vals = {}
        for order in get_model("sale.order").browse(order_ids):
            inv_qtys = {}
            for inv in order.invoices:
                if inv.state not in ("draft","waiting_payment","paid"):
                    continue
                for line in inv.lines:
                    prod_id = line.product_id.id
                    inv_qtys.setdefault(prod_id, 0)
                    inv_qtys[prod_id] += line.qty
            for line in order.lines:
                if line.id not in ids:
                    continue
                prod_id = line.product_id.id
                inv_qty = inv_qtys.get(prod_id, 0)  # XXX: uom
                used_qty = min(line.qty, inv_qty)
                vals[line.id] = used_qty
                if prod_id in inv_qtys:
                    inv_qtys[prod_id] -= used_qty
            for line in reversed(order.lines):
                prod_id = line.product_id.id
                remain_qty = inv_qtys.get(prod_id, 0)  # XXX: uom
                if remain_qty:
                    vals[line.id] += remain_qty
                    inv_qtys[prod_id] -= remain_qty
        vals = {x: vals[x] for x in ids}
        return vals

    def get_qty_avail(self, ids, context={}):
        vals = {}
        for obj in self.browse(ids):
            prod_id = obj.product_id.id
            loc_id = obj.location_id.id
            if prod_id and loc_id:
                res = get_model("stock.location").compute_balance([loc_id], prod_id)
                qty = res["bal_qty"]
            else:
                qty = None
            vals[obj.id] = qty
        return vals

    def get_est_profit(self,ids,context={}):
        sale_ids=[]
        for line in self.browse(ids):
            sale_ids.append(line.order_id.id)
        sale_ids=list(set(sale_ids))
        item_costs={}
        for sale in get_model("sale.order").browse(sale_ids):
            for cost in sale.est_costs:
                k=(sale.id,cost.sequence)
                if k not in item_costs:
                    item_costs[k]=0
                amt=cost.amount or 0
                if cost.currency_id:
                    rate=sale.get_relative_currency_rate(cost.currency_id.id)
                    amt=amt*rate
                item_costs[k]+=amt
        vals={}
        for line in self.browse(ids):
            k=(line.order_id.id,line.sequence)
            cost=item_costs.get(k,0)
            profit=line.amount-cost
            margin=profit*100/line.amount if line.amount else None
            vals[line.id]={
                "est_cost_amount": cost,
                "est_profit_amount": profit,
                "est_margin_percent": margin,
            }
        return vals

    def get_act_profit(self,ids,context={}):
        sale_ids=[]
        for line in self.browse(ids):
            sale_ids.append(line.order_id.id)
        sale_ids=list(set(sale_ids))
        item_costs={}
        for sale in get_model("sale.order").browse(sale_ids):
            for line in sale.track_entries:
                k=(sale.id,line.track_id.code)
                if k not in item_costs:
                    item_costs[k]=0
                # TODO: convert currency
                item_costs[k]-=line.amount
        vals={}
        for line in self.browse(ids):
            track_code="%s / %s"%(line.order_id.number,line.sequence)
            k=(line.order_id.id,track_code)
            cost=item_costs.get(k,0)
            profit=line.amount-cost
            margin=profit*100/line.amount if line.amount else None
            vals[line.id]={
                "act_cost_amount": cost,
                "act_profit_amount": profit,
                "act_margin_percent": margin,
            }
        return vals

    def get_ship_tracking(self, ids, context={}):
        vals = {}
        for obj in self.browse(ids):
            track_nos = []
            if obj.due_date:
                for pick in obj.order_id.pickings:
                    if pick.date[:10]==obj.due_date and pick.ship_tracking:
                        track_nos.append(pick.ship_tracking)
            vals[obj.id] = ", ".join(track_nos)
        return vals
Exemplo n.º 17
0
class SaleOrderLine(Model):
    _name = "sale.order.line"
    _name_field = "order_id"
    _fields = {
        "order_id": fields.Many2One("sale.order", "Sales Order", required=True, on_delete="cascade", search=True),
        "product_id": fields.Many2One("product", "Product", search=True),
        "description": fields.Text("Description", required=True, search=True),
        "qty": fields.Decimal("Qty"),
        "uom_id": fields.Many2One("uom", "UoM"),
        "unit_price": fields.Decimal("Unit Price", search=True, required=True, scale=6),
        "tax_id": fields.Many2One("account.tax.rate", "Tax Rate"),
        "amount": fields.Decimal("Amount", function="get_amount", function_multi=True, store=True, function_order=1, search=True),
        "amount_cur": fields.Decimal("Amount (Cur)", function="get_amount", function_multi=True, store=True, function_order=1, search=True),
        "qty_stock": fields.Decimal("Qty (Stock UoM)"),
        "qty_delivered": fields.Decimal("Delivered Qty", function="get_qty_delivered"),
        "qty_invoiced": fields.Decimal("Invoiced Qty", function="get_qty_invoiced"),
        "unit_amount_invoiced": fields.Decimal("UNIT amount invoiced", function="get_unit_amount_invoiced"),
        "qty_service_invoiced": fields.Decimal("Invoiced Qty", function="get_qty_service_invoiced"),
        "unit_service_amount_invoiced": fields.Decimal("service unit amount invoiced", function="get_service_unit_amount_invoiced"),
        "contact_id": fields.Many2One("contact", "Contact", function="_get_related", function_search="_search_related", function_context={"path": "order_id.contact_id"}, search=True),
        "date": fields.Date("Date", function="_get_related", function_search="_search_related", function_context={"path": "order_id.date"}, search=True),
        "user_id": fields.Many2One("base.user", "Owner", function="_get_related", function_search="_search_related", function_context={"path": "order_id.user_id"}, search=True),
        "amount_discount": fields.Decimal("Discount Amount", function="get_amount", function_multi=True),
        "state": fields.Selection([("draft", "Draft"), ("confirmed", "Confirmed"), ("done", "Completed"), ("voided", "Voided")], "Status", function="_get_related", function_search="_search_related", function_context={"path": "order_id.state"}, search=True),
        "qty2": fields.Decimal("Secondary Qty"),
        "location_id": fields.Many2One("stock.location", "Location", condition=[["type", "=", "internal"]]),
        "product_categ_id": fields.Many2Many("product.categ", "Product Category", function="_get_related", function_context={"path": "product_id.categ_id"}, function_search="_search_related", search=True),
        "discount": fields.Decimal("Disc %"),  # XXX: rename to discount_percent later
        "discount_amount": fields.Decimal("Disc Amt"),
        "qty_avail": fields.Decimal("Qty In Stock", function="get_qty_avail"),
        "agg_amount": fields.Decimal("Total Amount", agg_function=["sum", "amount"]),
        "agg_amount_cur": fields.Decimal("Total Amount Cur", agg_function=["sum", "amount_cur"]),
        "agg_qty": fields.Decimal("Total Order Qty", agg_function=["sum", "qty"]),
        "remark": fields.Char("Remark"),
        "ship_method_id": fields.Many2One("ship.method", "Shipping Method"),
        "sequence": fields.Char("Item No."),
        "est_cost_amount": fields.Float("Est. Cost Amount",function="get_est_profit",function_multi=True),
        "est_profit_amount": fields.Float("Est. Profit Amount",function="get_est_profit",function_multi=True),
        "est_margin_percent": fields.Float("Est. Margin %",function="get_est_profit",function_multi=True),
        "act_cost_amount": fields.Float("Act. Cost Amount",function="get_act_profit",function_multi=True),
        "act_profit_amount": fields.Float("Act. Profit Amount",function="get_act_profit",function_multi=True),
        "act_margin_percent": fields.Float("Act. Margin %",function="get_act_profit",function_multi=True),
        "promotion_amount": fields.Decimal("Prom Amt",function="get_amount",function_multi=True),
        "agg_act_profit": fields.Decimal("Total Actual Profit", agg_function=["sum", "act_profit_amount"]),
        "production_id": fields.Many2One("production.order","Production Order"),
    }

    _order_expression="case when tbl0.sequence is not null then (substring(tbl0.sequence, '^[0-9]+'))::int else tbl0.id end, tbl0.sequence"

    def get_service_unit_amount_invoiced(self, ids, context={}): #this will get qty based on the description
        order_ids = []
        for obj in self.browse(ids):
            order_ids.append(obj.order_id.id)
        order_ids = list(set(order_ids))
        vals = {}
        for order in get_model("sale.order").browse(order_ids):
            inv_amounts = {}
            for inv in order.invoices:
                if inv.state not in ("draft","waiting_payment","paid"):
                    continue
                for line in inv.lines:
                    prod_id = line.description # assume description as the product and fitler
                    inv_amounts.setdefault(prod_id, 0)
                    inv_amounts[prod_id] += line.amount or 0
            for line in order.lines:
                if line.id not in ids:
                    continue
                prod_id = line.description
                inv_amount = inv_amounts.get(prod_id, 0)  # XXX: uom
                used_amount = min(line.amount, inv_amount)
                vals[line.id] = used_amount
                if prod_id in inv_amounts:
                    inv_amounts[prod_id] -= used_amount
            for line in reversed(order.lines):
                prod_id = line.description
                remain_amount = inv_amounts.get(prod_id, 0)  # XXX: uom
                if remain_amount:
                    vals[line.id] += remain_amount
                    inv_amounts[prod_id] -= remain_amount
        vals = {x: vals[x] for x in ids}
        return vals

    def get_unit_amount_invoiced(self, ids, context={}):
        order_ids = []
        for obj in self.browse(ids):
            order_ids.append(obj.order_id.id)
        order_ids = list(set(order_ids))
        vals = {}
        for order in get_model("sale.order").browse(order_ids):
            inv_amounts = {}
            for inv in order.invoices:
                if inv.state not in ("draft","waiting_payment","paid"):
                    continue
                for line in inv.lines:
                    prod_id = line.product_id.id
                    inv_amounts.setdefault(prod_id, 0)
                    inv_amounts[prod_id] += line.amount or 0
            for line in order.lines:
                if line.id not in ids:
                    continue
                prod_id = line.product_id.id
                inv_amount = inv_amounts.get(prod_id, 0)  # XXX: uom
                used_amount = min(line.amount, inv_amount)
                vals[line.id] = used_amount
                if prod_id in inv_amounts:
                    inv_amounts[prod_id] -= used_amount
            for line in reversed(order.lines):
                prod_id = line.product_id.id
                remain_amount = inv_amounts.get(prod_id, 0)  # XXX: uom
                if remain_amount:
                    vals[line.id] += remain_amount
                    inv_amounts[prod_id] -= remain_amount
        vals = {x: vals[x] for x in ids}
        return vals

    def get_qty_service_invoiced(self, ids, context={}): #this will get qty based on the description
        order_ids = []
        for obj in self.browse(ids):
            order_ids.append(obj.order_id.id)
        order_ids = list(set(order_ids))
        vals = {}
        for order in get_model("sale.order").browse(order_ids):
            inv_qtys = {}
            for inv in order.invoices:
                if inv.state not in ("draft","waiting_payment","paid"):
                    continue
                for line in inv.lines:
                    prod_id = line.description # assume description as the product and fitler
                    inv_qtys.setdefault(prod_id, 0)
                    inv_qtys[prod_id] += line.qty or 0
            for line in order.lines:
                if line.id not in ids:
                    continue
                prod_id = line.description
                inv_qty = inv_qtys.get(prod_id, 0)  # XXX: uom
                used_qty = min(line.qty, inv_qty)
                vals[line.id] = used_qty
                if prod_id in inv_qtys:
                    inv_qtys[prod_id] -= used_qty
            for line in reversed(order.lines):
                prod_id = line.description
                remain_qty = inv_qtys.get(prod_id, 0)  # XXX: uom
                if remain_qty:
                    vals[line.id] += remain_qty
                    inv_qtys[prod_id] -= remain_qty
        vals = {x: vals[x] for x in ids}
        return vals

    def create(self, vals, context={}):
        if not vals.get('qty_stock'):
            product_id=vals.get('product_id')
            location_id=vals.get('location_id')
            if product_id and location_id:
                vals['qty_stock']=get_model('stock.balance').get_qty_stock(product_id, location_id)
        id = super(SaleOrderLine, self).create(vals, context)
        self.function_store([id])
        return id

    def write(self, ids, vals, context={}):
        super(SaleOrderLine, self).write(ids, vals, context)
        self.function_store(ids)

    def get_amount(self, ids, context={}):
        vals = {}
        settings = get_model("settings").browse(1)
        sale_ids=[]
        for line in self.browse(ids):
            sale_ids.append(line.order_id.id)
        sale_ids=list(set(sale_ids))
        for sale in get_model("sale.order").browse(sale_ids):
            prod_qtys={}
            prom_amts={}
            prom_pcts={}
            for line in sale.lines:
                prod_qtys.setdefault(line.product_id.id,0)
                prod_qtys[line.product_id.id]+=line.qty
            for line in sale.used_promotions:
                if line.amount and line.product_id:
                    prom_amts.setdefault(line.product_id.id,0)
                    prom_amts[line.product_id.id]+=line.amount
                elif line.percent:
                    prom_pcts.setdefault(line.product_id.id,0)
                    prom_pcts[line.product_id.id]+=line.percent
            for line in sale.lines:
                amt = line.qty * line.unit_price
                amt = roundup(amt)
                if line.discount:
                    disc = amt * line.discount / 100
                else:
                    disc = 0
                if line.discount_amount:
                    disc += line.discount_amount
                amt-=disc
                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
                order = line.order_id
                new_cur=get_model("currency").convert(amt, order.currency_id.id, settings.currency_id.id, rate_type="sell", date=sale.date)
                vals[line.id] = {
                    "amount": roundup(amt),
                    "amount_discount": disc,
                    "promotion_amount": prom_amt,
                    "amount_cur": new_cur and new_cur or None,
                }
        return vals

    def get_qty_delivered(self, ids, context={}):
        order_ids = []
        for obj in self.browse(ids):
            order_ids.append(obj.order_id.id)
        order_ids = list(set(order_ids))
        vals = {}
        for order in get_model("sale.order").browse(order_ids):
            delivered_qtys = {}
            for move in order.stock_moves:
                if move.state != "done":
                    continue
                prod_id = move.product_id.id
                k = (prod_id, move.location_from_id.id)
                delivered_qtys.setdefault(k, 0)
                delivered_qtys[k] += move.qty  # XXX: uom
                k = (prod_id, move.location_to_id.id)
                delivered_qtys.setdefault(k, 0)
                delivered_qtys[k] -= move.qty  # XXX: uom
            for job in order.jobs:
                for move in job.stock_moves:
                    if move.state != "done":
                        continue
                    prod_id = move.product_id.id
                    k = (prod_id, move.location_from_id.id)
                    delivered_qtys.setdefault(k, 0)
                    delivered_qtys[k] += move.qty  # XXX: uom
                    k = (prod_id, move.location_to_id.id)
                    delivered_qtys.setdefault(k, 0)
                    delivered_qtys[k] -= move.qty  # XXX: uom
            for line in order.lines:
                k = (line.product_id.id, line.location_id.id)
                delivered_qty = delivered_qtys.get(k, 0)  # XXX: uom
                used_qty = min(line.qty, delivered_qty)
                vals[line.id] = used_qty
                if k in delivered_qtys:
                    delivered_qtys[k] -= used_qty
            for line in reversed(order.lines):
                k = (line.product_id.id, line.location_id.id)
                remain_qty = delivered_qtys.get(k, 0)  # XXX: uom
                if remain_qty:
                    vals[line.id] += remain_qty
                    delivered_qtys[k] -= remain_qty
        vals = {x: vals[x] for x in ids}
        return vals

    def get_qty_invoiced(self, ids, context={}):
        order_ids = []
        for obj in self.browse(ids):
            order_ids.append(obj.order_id.id)
        order_ids = list(set(order_ids))
        vals = {}
        for order in get_model("sale.order").browse(order_ids):
            inv_qtys = {}
            for inv in order.invoices:
                if inv.state not in ("draft","waiting_payment","paid"):
                    continue
                for line in inv.lines:
                    prod_id = line.product_id.id
                    inv_qtys.setdefault(prod_id, 0)
                    inv_qtys[prod_id] += line.qty or 0
            for job in order.jobs:
                for inv in job.invoices:
                    if inv.state not in ("draft","waiting_payment","paid"):
                        continue
                    for line in inv.lines:
                        prod_id = line.product_id.id
                        inv_qtys.setdefault(prod_id, 0)
                        inv_qtys[prod_id] += line.qty or 0
            for line in order.lines:
                if line.id not in ids:
                    continue
                prod_id = line.product_id.id
                inv_qty = inv_qtys.get(prod_id, 0)  # XXX: uom
                used_qty = min(line.qty, inv_qty)
                vals[line.id] = used_qty
                if prod_id in inv_qtys:
                    inv_qtys[prod_id] -= used_qty
            for line in reversed(order.lines):
                prod_id = line.product_id.id
                remain_qty = inv_qtys.get(prod_id, 0)  # XXX: uom
                if remain_qty:
                    vals[line.id] += remain_qty
                    inv_qtys[prod_id] -= remain_qty
        vals = {x: vals[x] for x in ids}
        return vals

    def get_qty_avail(self, ids, context={}):
        vals = {}
        for obj in self.browse(ids):
            prod_id = obj.product_id.id
            loc_id = obj.location_id.id
            if prod_id and loc_id:
                res = get_model("stock.location").compute_balance([loc_id], prod_id)
                qty = res["bal_qty"]
            else:
                qty = None
            vals[obj.id] = qty
        return vals

    def get_est_profit(self,ids,context={}):
        sale_ids=[]
        settings = get_model('settings').browse(1)
        currency_id = settings.currency_id.id
        for line in self.browse(ids):
            sale_ids.append(line.order_id.id)
        sale_ids=list(set(sale_ids))
        item_costs={}
        for sale in get_model("sale.order").browse(sale_ids):
            for cost in sale.est_costs:
                k=(sale.id,cost.sequence)
                if k not in item_costs:
                    item_costs[k]=0
                if cost.currency_id == currency_id:
                    item_costs[k]+=cost.amount or 0
                else:
                    item_costs[k]+=cost.amount_conv or 0
        vals={}
        for line in self.browse(ids):
            profit=0
            k=(line.order_id.id,line.sequence)
            cost=item_costs.get(k,0)
            profit=line.amount-cost
            margin=(profit/line.amount)*100 if line.amount else 0
            vals[line.id]={
                "est_cost_amount": cost,
                "est_profit_amount": profit,
                "est_margin_percent": margin,
            }
        return vals

    def get_act_profit(self,ids,context={}):
        sale_ids=[]
        for line in self.browse(ids):
            sale_ids.append(line.order_id.id)
        sale_ids=list(set(sale_ids))
        item_costs={}
        for sale in get_model("sale.order").browse(sale_ids):
            for line in sale.track_entries:
                k=(sale.id,line.track_id.code)
                if k not in item_costs:
                    item_costs[k]=0
                # TODO: convert currency
                item_costs[k]-=line.amount
        vals={}
        for line in self.browse(ids):
            track_code="%s / %s"%(line.order_id.number,line.sequence)
            k=(line.order_id.id,track_code)
            cost=item_costs.get(k,0)
            profit=line.amount-cost
            margin=profit*100/line.amount if line.amount else None
            vals[line.id]={
                "act_cost_amount": cost,
                "act_profit_amount": profit,
                "act_margin_percent": margin,
            }
        return vals
Exemplo n.º 18
0
class MoveLine(Model):
    _name = "account.move.line"
    _name_field = "move_id"
    #_key = ["move_id", "sequence"]
    _fields = {
        "move_id":
        fields.Many2One("account.move",
                        "Journal Entry",
                        required=True,
                        on_delete="cascade"),
        "description":
        fields.Text("Description", required=True),
        "account_id":
        fields.Many2One("account.account",
                        "Account",
                        required=True,
                        search=True),
        "debit":
        fields.Decimal("Debit", required=True),
        "credit":
        fields.Decimal("Credit", required=True),
        "statement_line_id":
        fields.Many2One("account.statement.line",
                        "Statement Line"),  # XXX: not used any more
        # XXX: simplify this...
        "statement_line_search":
        fields.Many2One("account.statement.line",
                        "Statement Line",
                        store=False,
                        function_search="_search_statement_line"),
        "state":
        fields.Selection([["not_reconciled", "Not Reconciled"],
                          ["reconciled", "Reconciled"]], "Status"),
        # XXX: remove store?
        "move_date":
        fields.Date("Date",
                    function="_get_related",
                    function_context={"path": "move_id.date"},
                    store=True,
                    search=True),
        "move_state":
        fields.Selection(
            [["draft", "Draft"], ["posted", "Posted"], ["voided", "Voided"]],
            "Status",
            function="_get_related",
            function_context={"path": "move_id.state"},
            store=True),
        "move_narration":
        fields.Char("Narration",
                    function="_get_related",
                    function_context={"path": "move_id.narration"}),
        "move_type":
        fields.Selection([["invoice", "Invoice"], ["payment", "Payment"],
                          ["transfer", "Transfer"], ["picking", "Picking"],
                          ["stock_count", "Stock Count"], ["claim", "Claim"],
                          ["manual", "Manual"]],
                         "Type",
                         function="_get_related",
                         function_context={"path": "move_id.type"}),
        "move_ref":
        fields.Char("Reference",
                    function="_get_related",
                    function_context={"path": "move_id.ref"}),
        "move_number":
        fields.Char("Number",
                    function="_get_related",
                    function_context={"path": "move_id.number"},
                    function_search="_search_related",
                    search=True),
        "account_name":
        fields.Char("Account Name",
                    function="_get_related",
                    function_context={"path": "account_id.name"}),
        "account_code":
        fields.Char("Account Code",
                    function="_get_related",
                    function_context={"path": "account_id.code"}),
        "stock_move_id":
        fields.Many2One("stock.move", "Stock Move"),
        "product_id":
        fields.Many2One("product", "Product"),
        "tax_comp_id":
        fields.Many2One("account.tax.component",
                        "Tax Comp.",
                        on_delete="restrict"),
        "tax_base":
        fields.Decimal("Tax Base"),
        "contact_id":
        fields.Many2One("contact", "Contact", search=True),
        "due_date":
        fields.Date("Due Date"),
        "track_id":
        fields.Many2One("account.track.categ",
                        "Track-1",
                        condition=[["type", "=", "1"]]),
        "track2_id":
        fields.Many2One("account.track.categ",
                        "Track-2",
                        condition=[["type", "=", "2"]]),
        "invoice_id":
        fields.Many2One("account.invoice", "Invoice"),
        "qty":
        fields.Decimal("Qty"),
        "reconcile_id":
        fields.Many2One("account.reconcile", "Reconciliation"),
        "bank_reconcile_id":
        fields.Many2One("account.bank.reconcile", "Bank Reconciliation"),
        "statement_lines":
        fields.Many2Many("account.statement.line", "Statement Lines"),
        "is_account_reconciled":
        fields.Boolean("Is Reconciled?", function="_is_account_reconciled"),
        "empty_contact":
        fields.Boolean("Empty Contact",
                       store=False,
                       function_search="_search_empty_contact",
                       search=True),
        "tax_no":
        fields.Char("Tax No."),
        "tax_date":
        fields.Date("Tax Invoice Date"),
        "sequence":
        fields.Integer("Sequence"),
        "amount_cur":
        fields.Decimal("Currency Amt"),
    }

    _defaults = {
        "state": "not_reconciled",
        "debit": 0,
        "credit": 0,
    }
    _indexes = [
        ("account_id", "move_date"),
    ]
    _order = "sequence,id"

    def view_transaction(self, ids, context={}):
        obj = self.browse(ids)[0]
        res = obj.move_id.view_journal()
        return res

    def unreconcile(self, ids, context={}):
        st_line_ids = []
        for obj in self.browse(ids):
            for st_line in obj.statement_lines:
                st_line_ids.append(st_line.id)
        st_line_ids = list(set(st_line_ids))
        get_model("account.statement.line").unreconcile(st_line_ids)
        self.write(ids, {"state": "not_reconciled"})

    def reconcile(self, ids, context={}):
        print("MoveLine.reconcile", ids)
        rec_id = get_model("account.reconcile").create({})
        all_ids = ids[:]
        for line in self.browse(ids):
            rec = line.reconcile_id
            if not rec:
                continue
            for rline in rec.lines:
                all_ids.append(rline.id)
        all_ids = list(set(all_ids))
        acc_id = None
        for obj in self.browse(all_ids):
            if not acc_id:
                acc_id = obj.account_id.id
            else:
                if obj.account_id.id != acc_id:
                    acc = get_model("account.account").browse(acc_id)
                    raise Exception(
                        "Can only reconcile transactions of same account (%s / %s)"
                        % (obj.account_id.code, acc.code))
        self.write(all_ids, {"reconcile_id": rec_id})
        inv_ids = []
        for obj in self.browse(all_ids):
            move = obj.move_id
            rel = move.related_id
            if rel._model == "account.invoice":
                inv_ids.append(rel.id)
        if inv_ids:
            get_model("account.invoice").function_store(inv_ids)

    def unreconcile_manual(self, ids, context={}):
        all_ids = ids[:]
        for line in self.browse(ids):
            rec = line.reconcile_id
            if not rec:
                continue
            for rline in rec.lines:
                all_ids.append(rline.id)
        all_ids = list(set(all_ids))
        self.write(all_ids, {"reconcile_id": None})

    def write(self, ids, vals, **kw):
        rec_ids = []
        for obj in self.browse(ids):
            if obj.reconcile_id:
                rec_ids.append(obj.reconcile_id.id)
        super().write(ids, vals, **kw)
        for obj in self.browse(ids):
            if obj.reconcile_id:
                rec_ids.append(obj.reconcile_id.id)
        if rec_ids:
            rec_ids = list(set(rec_ids))
            get_model("account.reconcile").function_store(rec_ids)

    def delete(self, ids, **kw):
        rec_ids = []
        for obj in self.browse(ids):
            if obj.reconcile_id:
                rec_ids.append(obj.reconcile_id.id)
        super().delete(ids, **kw)
        if rec_ids:
            rec_ids = list(set(rec_ids))
            get_model("account.reconcile").function_store(rec_ids)

    def _is_account_reconciled(self, ids, context={}):
        vals = {}
        for obj in self.browse(ids):
            if obj.reconcile_id:
                vals[obj.id] = abs(obj.reconcile_id.balance) == 0
            else:
                vals[obj.id] = False
        return vals

    def _search_empty_contact(self, clause, context={}):
        if clause[2]:
            return [["contact_id", "=", None]]
        else:
            return [["contact_id", "!=", None]]

    def reconcile_remove_from_all(self, ids, context={}):
        print("reconcile_remove_from_all", ids)
        self.write(ids, {"statement_lines": [("set", [])]})
Exemplo n.º 19
0
class PurchaseOrder(Model):
    _name = "purchase.order"
    _string = "Purchase Order"
    _audit_log = True
    _name_field = "number"
    _multi_company = True
    _key = ["company_id", "number"]
    _fields = {
        "number": fields.Char("Number", required=True, search=True),
        "ref": fields.Char("Ref", search=True),
        "contact_id": fields.Many2One("contact", "Supplier", required=True, search=True),
        "customer_id": fields.Many2One("contact", "Customer", search=True),
        "date": fields.Date("Date", required=True, search=True),
        "date_required": fields.Date("Required Date"),
        "state": fields.Selection([("draft", "Draft"), ("confirmed", "Confirmed"), ("done", "Completed"), ("voided", "Voided")], "Status", required=True, search=True),
        "lines": fields.One2Many("purchase.order.line", "order_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_total_cur": fields.Decimal("Total", function="get_amount", function_multi=True, store=True),
        "amount_total_words": fields.Char("Total Words", function="get_amount_total_words"),
        "qty_total": fields.Decimal("Total Quantity", function="get_qty_total"),
        "currency_id": fields.Many2One("currency", "Currency", required=True),
        "tax_type": fields.Selection([["tax_ex", "Tax Exclusive"], ["tax_in", "Tax Inclusive"], ["no_tax", "No Tax"]], "Tax Type", required=True),
        "invoice_lines": fields.One2Many("account.invoice.line", "purch_id", "Invoice Lines"),
        #"stock_moves": fields.One2Many("stock.move","purch_id","Stock Moves"),
        "invoices": fields.One2Many("account.invoice", "related_id", "Invoices"),
        "pickings": fields.Many2Many("stock.picking", "Stock Pickings", function="get_pickings"),
        "is_delivered": fields.Boolean("Delivered", function="get_delivered"),
        "is_paid": fields.Boolean("Paid", function="get_paid"),
        "invoice_status": fields.Char("Invoice", function="get_invoice_status"),
        "comments": fields.One2Many("message", "related_id", "Comments"),
        "delivery_date": fields.Date("Delivery Date"),
        "ship_method_id": fields.Many2One("ship.method", "Shipping Method"),  # XXX: deprecated
        "payment_terms": fields.Text("Payment Terms"),
        "ship_term_id": fields.Many2One("ship.term", "Shipping Terms"),
        "price_list_id": fields.Many2One("price.list", "Price List", condition=[["type", "=", "purchase"]]),
        "documents": fields.One2Many("document", "related_id", "Documents"),
        "company_id": fields.Many2One("company", "Company"),
        "purchase_type_id": fields.Many2One("purchase.type", "Purchase Type"),
        "other_info": fields.Text("Other Info"),
        "bill_address_id": fields.Many2One("address", "Billing Address"),
        "ship_address_id": fields.Many2One("address", "Shipping Address"),
        "sequence_id": fields.Many2One("sequence", "Number Sequence"),
        "stock_moves": fields.One2Many("stock.move", "related_id", "Stock Movements"),
        "agg_qty": fields.Decimal("Total Order Qty", agg_function=["sum_line", "qty", "order_id", "purchase_order_line"]),
        "agg_amount_total": fields.Decimal("Total Amount", agg_function=["sum", "amount_total"]),
        "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"]),
        "agg_amount_subtotal": fields.Decimal("Total Amount w/o Tax", agg_function=["sum", "amount_subtotal"]),
        "user_id": fields.Many2One("base.user", "Owner", search=True),
        "emails": fields.One2Many("email.message", "related_id", "Emails"),
        "product_id": fields.Many2One("product", "Product", store=False, function_search="search_product", search=True),
        "currency_rates": fields.One2Many("custom.currency.rate","related_id","Currency Rates"),
        "related_id": fields.Reference([],"Related To"),
        "location_id": fields.Many2One("stock.location", "Location", function_search="search_location", search=True, store=False),
    }
    _order = "date desc,number desc"
    _constraints = ["check_fields"]
    _sql_constraints = [
        ("key_uniq", "unique (company_id, number)", "The number of each company must be unique!")
    ]

    def check_fields(self, ids, context={}):
        for obj in self.browse(ids):
            if context.get('is_draft'):
                continue
            dup=None
            sequence_item = []
            for line in obj.lines:
                if line.sequence:
                    sequence_item.append(line.sequence)
            dup_sequence = set()
            for i in sequence_item:
                if sequence_item.count(i)>=2:
                    dup_sequence.add(i)
                    dup=True
            if dup:
                raise Exception("Lines: Fields Item No. Duplicates : %s" % (str(list(dup_sequence))))

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

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

    def _get_currency_rates(self,context={}):
        settings = get_model("settings").browse(1)
        lines=[]
        date = time.strftime("%Y-%m-%d")
        line_vals={
            "currency_id": settings.currency_id.id,
            "rate": settings.currency_id and settings.currency_id.get_rate(date,"buy") or 1
        }
        if context.get("is_create"):
            lines.append(('create',line_vals))
        else:
            lines.append(line_vals)
        return lines

    _defaults = {
        "state": "draft",
        "date": lambda *a: time.strftime("%Y-%m-%d"),
        "number": _get_number,
        "currency_id": _get_currency,
        "tax_type": "tax_ex",
        "company_id": lambda *a: get_active_company(),
        "user_id": lambda *a: get_active_user(),
        "currency_rates": _get_currency_rates,
    }

    def get_currency_rate(self,context={}):
        data=context['data']
        currency_id = data["currency_id"]
        currency_rate=0
        for cr_rate in data['currency_rates']:
            if cr_rate['currency_id']==currency_id:
                currency_rate=cr_rate['rate'] or 0
                break
        if not currency_rate:
            currency=get_model("currency").browse(currency_id)
            currency_rate=currency.get_rate(date=data['date'],rate_type="buy") or 1
        return currency_rate

    def onchange_currency(self, context):
        data=context['data']
        currency_id = data["currency_id"]
        currency=get_model("currency").browse(currency_id)
        rate=currency.get_rate(date=data['date'],rate_type="buy") or 1
        for crr in data['currency_rates']:
            crr.update({
                'currency_id': currency_id,
                'rate': rate,
            })
            break
        data = self.update_line_currency(context)
        return data

    def onchange_pricelist(self, context):
        data=context['data']
        data = self.update_line_currency(context)
        return data

    def update_line_currency(self, context):
        settings=get_model("settings").browse(1)
        data=context['data']
        currency_id = data["currency_id"]
        pricelist_id = data["price_list_id"]
        for line in data['lines']:
            prod_cur_id = None
            price_list = 0
            prod_id=line.get('product_id')
            if not prod_id:
                continue
            prod = get_model("product").browse(prod_id)
            qty = line.get('qty') or 0
            if pricelist_id:
                price_list = get_model("price.list").get_price(pricelist_id, prod.id, qty)
                prod_cur_id = get_model("price.list").browse(pricelist_id).currency_id.id or None
                    #continue
            #price = prod.purchase_price or 0
            if price_list == 0:
                price = prod.purchase_price or 0
            else:
               price = price_list
            currency_id = data["currency_id"]
            currency_rate = self.get_currency_rate(context)
            if not prod_cur_id and prod.purchase_currency_id:
                prod_cur_id = prod.purchase_currency_id.id
            if prod_cur_id:
                if prod_cur_id != currency_id:
                    cur_id=get_model("currency").browse(prod_cur_id)
                    currency_from_rate=cur_id.get_rate(date=data['date'],rate_type="buy") or 1
                    price = get_model("currency").convert(price, prod_cur_id, currency_id, from_rate=currency_from_rate, to_rate=currency_rate)
                else:
                    price = get_model("currency").convert(price, prod_cur_id, currency_id, to_rate=currency_rate)
            else:
                price = get_model("currency").convert(price, settings.currency_id.id, currency_id, to_rate=currency_rate)
            line["unit_price"] = price
        data = self.update_amounts(context)
        return data

    def onchange_currency_rate(self, context={}):
        data=context['data']
        path=context['path']
        line=get_data_path(data, path, parent=True)
        currency=get_model("currency").browse(line['currency_id'])
        line['rate']=currency.get_rate(date=data['date'],rate_type="buy") or 1
        data = self.update_line_currency(context)
        return data

    def create(self, vals, **kw):
        context=kw.get('context',{})
        context['is_create']=True
        kw['context']=context
        id = super(PurchaseOrder, self).create(vals, **kw)
        self.function_store([id])
        return id

    def write(self, ids, vals, **kw):
        super(PurchaseOrder, self).write(ids, vals, **kw)
        self.function_store(ids)
        line_ids=get_model('purchase.order.line').search([['order_id','in', ids]])
        get_model("purchase.order.line").function_store(line_ids)

    def confirm(self, ids, context={}):
        settings = get_model("settings").browse(1)
        for obj in self.browse(ids):
            if obj.state != "draft":
                raise Exception("Invalid state")
            if not obj.amount_total:
                raise Exception("Cannot confirm PO if total amount is zero")
            service_count=0
            non_pro_count=0
            for line in obj.lines:
                prod = line.product_id
                if not prod:
                    non_pro_count +=1
                if prod and prod.type in ("stock", "consumable", "bundle", "master") and not line.location_id:
                    raise Exception("Missing location for product %s" % prod.code)
                if prod.purchase_min_qty and line.qty < prod.purchase_min_qty:
                    raise Exception("Minimum Purchases Qty for [%s] %s is %s"%(prod.code,prod.name,prod.purchase_min_qty))
                if prod.type=='service':
                    service_count+=1
            obj.write({"state": "confirmed"})
            if settings.purchase_copy_picking and service_count!=len(obj.lines) and non_pro_count!=len(obj.lines):
                if obj.pickings:
                    for pick in obj.pickings:
                        pick.delete()
                res=obj.copy_to_picking()
                if res:
                    picking_id=res["picking_id"]
                    get_model("stock.picking").pending([picking_id])
            if settings.purchase_copy_invoice:
                if obj.invoices:
                    for inv in obj.invoices:
                        inv.delete()
                obj.copy_to_invoice()
            obj.trigger("confirm")

    def done(self, ids, context={}):
        for obj in self.browse(ids):
            if obj.state != "confirmed":
                raise Exception("Invalid state")
            obj.write({"state": "done"})

    def reopen(self, ids, context={}):
        for obj in self.browse(ids):
            if obj.state != "done":
                raise Exception("Invalid state")
            obj.write({"state": "confirmed"})

    def to_draft(self, ids, context={}):
        for obj in self.browse(ids):
            non_pro = 0
            for line in obj.lines:
                if not line.product_id:
                    non_pro += 1
            if non_pro == len(obj.lines):
                obj.is_delivered = False
            cannot_draft=(obj.is_delivered and obj.pickings) or obj.is_paid
            if cannot_draft:
                raise Exception("Cannot to draft if order is delivered or paid!")
            for inv in obj.invoices:
                assert inv.state=="draft" or inv.state=="voided" or inv.state=="waiting_approval","Can not To Draft purchase order.The Invoice must be Draft."
            for pick in obj.pickings:
                assert pick.state == "draft" or pick.state=="voided","Can not To Draft purchase order.The Stock Picking must be Draft."
            obj.write({"state": "draft"})

    def get_amount(self, ids, context={}):
        settings = get_model("settings").browse(1)
        res = {}
        for obj in self.browse(ids):
            vals = {}
            subtotal = 0
            tax = 0
            for line in obj.lines:
                if line.tax_id:
                    line_tax = get_model("account.tax.rate").compute_tax(
                        line.tax_id.id, line.amount, tax_type=obj.tax_type)
                else:
                    line_tax = 0
                tax += line_tax
                if obj.tax_type == "tax_in":
                    subtotal += line.amount - line_tax
                else:
                    subtotal += line.amount
            vals["amount_subtotal"] = subtotal
            vals["amount_tax"] = tax
            vals["amount_total"] = subtotal + tax
            vals["amount_total_cur"] = get_model("currency").convert(
                vals["amount_total"], obj.currency_id.id, settings.currency_id.id)
            res[obj.id] = vals
        return res

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

    def update_amounts(self, context):
        settings=get_model("settings").browse(1)
        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 = Decimal((line.get("qty") or 0) * (line.get("unit_price") or 0))
            if line.get("discount_percent"):
                disc = amt * line["discount_percent"] / Decimal(100)
                amt -= disc
            amt -= (line.get("discount_amount") or 0)
            line["amount"] = amt
            currency_rate=self.get_currency_rate(context)
            line['amount_cur']=get_model("currency").convert(amt, data['currency_id'], settings.currency_id.id, rate=currency_rate)
            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 onchange_product(self, context):
        data = context["data"]
        path = context["path"]
        settings=get_model("settings").browse(1)
        line = get_data_path(data, path, parent=True)
        prod_id = line.get("product_id")
        if not prod_id:
            return {}
        prod_cur_id = None
        prod = get_model("product").browse(prod_id)
        line["description"] = prod.description
        line["qty"] = prod.purchase_min_qty or 1
        line["uom_id"] = prod.purchase_uom_id.id or prod.uom_id.id
        pricelist_id = data["price_list_id"]
        price = 0
        if pricelist_id:
            price = get_model("price.list").get_price(pricelist_id, prod.id, 1) or 0
            prod_cur_id = get_model("price.list").browse(pricelist_id).currency_id.id or None
        if not price or price == 0:
            price = prod.purchase_price or 0
            if not prod_cur_id:
                prod_cur_id = prod.purchase_currency_id.id or None
        currency_id = data["currency_id"]
        currency_rate = self.get_currency_rate(context)
        if price:
            if prod_cur_id:
                if prod_cur_id != currency_id:
                    cur_id=get_model("currency").browse(prod_cur_id)
                    currency_from_rate=cur_id.get_rate(date=data['date'],rate_type="buy") or 1
                    price = get_model("currency").convert(price, prod_cur_id, currency_id, from_rate=currency_from_rate, to_rate=currency_rate)
                else:
                    price = get_model("currency").convert(price, prod_cur_id, currency_id, to_rate=currency_rate)
            else:
                price = get_model("currency").convert(price, settings.currency_id.id, currency_id, to_rate=currency_rate)

        line["unit_price"] = price
        ratio = get_model("uom").browse(int(line["uom_id"])).ratio
        line["unit_price"] = price * ratio
        if prod.purchase_uom_id:
            line["unit_price"] = price * ratio / prod.purchase_uom_id.ratio or 1
        if prod.categ_id and prod.categ_id.purchase_tax_id:
            line["tax_id"] = prod.categ_id.purchase_tax_id.id
        if prod.purchase_tax_id is not None:
            line["tax_id"] = prod.purchase_tax_id.id
        contact_id=data.get('contact_id')
        if contact_id:
            contact=get_model("contact").browse(contact_id)
            if contact.tax_payable_id:
                line["tax_id"] = contact.tax_payable_id.id
        if data.get("tax_type","")=="no_tax":
            line["tax_id"]=None

        if prod.location_id:
            line["location_id"] = prod.location_id.id
        elif prod.locations:
            line["location_id"] = prod.locations[0].location_id.id
            #TODO
        #amount_cur
        self.onchange_location(context)
        data = self.update_amounts(context)
        return data

    def onchange_location(self, context):
        data = context["data"]
        path = context["path"]
        line = get_data_path(data, path, parent=True)
        line["qty_stock"] = 0
        if "product_id" in line and 'location_id' in line:
            product_id=line['product_id']
            location_id=line['location_id']
            qty_stock=get_model("stock.balance").get_qty_stock(product_id, location_id)
            line["qty_stock"] = qty_stock
        return data

    def onchange_qty(self, context):
        data = context["data"]
        path = context["path"]
        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)
        qty = line["qty"]
        price = line["unit_price"]
        if not price:
            settings = get_model("settings").browse(1)
            currency_id = data["currency_id"]
            currency_rate=self.get_currency_rate(context)
            price_cur = get_model("currency").convert(price, settings.currency_id.id, currency_id, from_rate=1, to_rate=currency_rate)
            line["unit_price"] = price_cur
            line['amount_cur']= price_cur*qty
        if prod.purchase_min_qty and qty < prod.purchase_min_qty:
            raise Exception("Minimum Sales Qty for [%s] %s is %s"%(prod.code,prod.name,prod.purchase_min_qty))
        data = self.update_amounts(context)
        return data

    def onchange_uom(self, context):
        data = context["data"]
        path = context["path"]
        line = get_data_path(data, path, parent=True)
        prod_id = line.get("product_id")
        uom_id = line.get("uom_id")
        pricelist_id = data["price_list_id"]
        price=None
        if not prod_id:return data
        prod = get_model("product").browse(prod_id)
        if not uom_id: return data
        uom = get_model("uom").browse(uom_id)
        if pricelist_id:
            price = get_model("price.list").get_price(pricelist_id, prod.id, 1)
        if price is None:
            price = prod.purchase_price
        if price is not None:
            if prod.purchase_currency_id and prod.purchase_currency_id.id != data['currency_id']:
                price=get_model("currency").convert(price, prod.purchase_currency_id.id, data['currency_id'])
        line["unit_price"] = price * uom.ratio / prod.uom_id.ratio
        if prod.purchase_uom_id:
            line["unit_price"] = price * uom.ratio / prod.purchase_uom_id.ratio or 1
        data = self.update_amounts(context)
        return data

    def copy_to_picking(self, ids, context={}):
        settings=get_model("settings").browse(1)
        obj = self.browse(ids[0])
        contact = obj.contact_id
        pick_vals = {
            "type": "in",
            "ref": obj.number,
            "related_id": "purchase.order,%s" % obj.id,
            "contact_id": contact.id,
            "currency_id": obj.currency_id.id,
            "ship_method_id": obj.ship_method_id.id,
            "lines": [],
        }
        if obj.delivery_date:
            pick_vals["date"]=obj.delivery_date+datetime.strftime(datetime.now()," %H:%M:%S")
        if contact and contact.pick_in_journal_id:
            pick_vals["journal_id"] = contact.pick_in_journal_id.id
        res = get_model("stock.location").search([["type", "=", "supplier"]],order="id")
        if not res:
            raise Exception("Supplier location not found")
        supp_loc_id = res[0]
        res = get_model("stock.location").search([["type", "=", "internal"]])
        if not res:
            raise Exception("Warehouse not found")
        wh_loc_id = res[0]
        if not settings.currency_id:
            raise Exception("Missing currency in financial settings")
        for line in obj.lines:
            prod = line.product_id
            if prod.type not in ("stock", "consumable", "bundle", "master"):
                continue
            remain_qty = (line.qty or 0) - line.qty_received
            #remain_qty = (line.qty_stock or line.qty) - line.qty_received
            if remain_qty <= 0:
                continue
            if not prod.unique_lot:
                unit_price=line.amount/line.qty if line.qty else 0
                if obj.tax_type=="tax_in":
                    if line.tax_id:
                        tax_amt = get_model("account.tax.rate").compute_tax(
                            line.tax_id.id, unit_price, tax_type=obj.tax_type)
                    else:
                        tax_amt = 0
                    cost_price_cur=unit_price-tax_amt
                else:
                    cost_price_cur=unit_price
                #if line.qty_stock:
                    #purch_uom=prod.uom_id
                    #if not prod.purchase_to_stock_uom_factor:
                        #raise Exception("Missing purchase order to stock UoM factor for product %s"%prod.code)
                    #cost_price_cur/=prod.purchase_to_stock_uom_factor
                #else:
                    #purch_uom=line.uom_id
                purch_uom=line.uom_id
                cost_price=get_model("currency").convert(cost_price_cur,obj.currency_id.id,settings.currency_id.id,date=pick_vals.get("date"))
                cost_amount=cost_price*remain_qty
                line_vals = {
                    "product_id": prod.id,
                    "qty": remain_qty,
                    "uom_id": purch_uom.id,
                    "cost_price_cur": 0 if prod.type == "bundle" else cost_price_cur,
                    "cost_price": 0 if prod.type == "bundle" else cost_price,
                    "cost_amount": 0 if prod.type == "bundle" else cost_amount,
                    "location_from_id": supp_loc_id,
                    "location_to_id": line.location_id.id or wh_loc_id,
                    "related_id": "purchase.order,%s" % obj.id,
                }
                pick_vals["lines"].append(("create", line_vals))
            else:
                for i in range(int(line.qty)):
                    if obj.tax_type=="tax_in":
                        if line.tax_id:
                            tax_amt = get_model("account.tax.rate").compute_tax(
                                line.tax_id.id, line.unit_price, tax_type=obj.tax_type)
                        else:
                            tax_amt = 0
                        cost_price_cur=line.unit_price-tax_amt
                    else:
                        cost_price_cur=line.unit_price
                    purch_uom=line.uom_id
                    cost_price=get_model("currency").convert(cost_price_cur,obj.currency_id.id,settings.currency_id.id,date=pick_vals.get("date"))
                    cost_amount=cost_price
                    line_spilt = {
                        "product_id": prod.id,
                        "qty": 1,
                        "uom_id": purch_uom.id,
                        "cost_price_cur": cost_price_cur,
                        "cost_price": cost_price,
                        "cost_amount": cost_amount,
                        "location_from_id": supp_loc_id,
                        "location_to_id": line.location_id.id or wh_loc_id,
                        "related_id": "purchase.order,%s" % obj.id,
                    }
                    pick_vals["lines"].append(("create", line_spilt))
        if not pick_vals["lines"]:
            return
        pick_id = get_model("stock.picking").create(pick_vals, {"pick_type": "in"})
        pick = get_model("stock.picking").browse(pick_id)
        pick.set_currency_rate()
        return {
            "next": {
                "name": "pick_in",
                "mode": "form",
                "active_id": pick_id,
            },
            "flash": "Goods receipt %s created from purchase order %s" % (pick.number, obj.number),
            "picking_id": pick_id,
        }

    def copy_to_invoice(self, ids, context={}):
        id = ids[0]
        obj = self.browse(id)
        contact = obj.contact_id
        inv_vals = {
            "type": "in",
            "inv_type": "invoice",
            "ref": obj.number,
            "related_id": "purchase.order,%s" % obj.id,
            "contact_id": obj.contact_id.id,
            "currency_id": obj.currency_id.id,
            "lines": [],
            "tax_type": obj.tax_type,
        }
        if contact.purchase_journal_id:
            inv_vals["journal_id"] = contact.purchase_journal_id.id
            if contact.purchase_journal_id.sequence_id:
                inv_vals["sequence_id"] = contact.purchase_journal_id.sequence_id.id
        if contact.purchase_payment_terms_id:
            inv_vals["payment_terms_id"] = contact.purchase_payment_terms_id.id,
            inv_vals["due_date"] = get_model("account.invoice").calc_date(time.strftime("%Y-%m-%d"),contact.purchase_payment_terms_id.days)
        ## get curruncy rate
        if obj.currency_id:
            inv_vals["currency_rate"] = obj.currency_id.get_rate(date=time.strftime("%Y-%m-%d"),rate_type="buy")
        for line in obj.lines:
            prod = line.product_id
            remain_qty = line.qty - line.qty_invoiced
            if remain_qty <= 0:
                continue
            # get account for purchase invoice
            purch_acc_id=None
            if prod:
                # 1. get from product
                purch_acc_id=prod.purchase_account_id and prod.purchase_account_id.id or None
                # 2. if not get from master / parent product
                if not purch_acc_id and prod.parent_id:
                    purch_acc_id=prod.parent_id.purchase_account_id.id
                # 3. if not get from product category
                categ=prod.categ_id
                if categ and not purch_acc_id:
                    purch_acc_id= categ.purchase_account_id and categ.purchase_account_id.id or None

            #if not purch_acc_id:
                #raise Exception("Missing purchase account configure for product [%s]" % prod.name)

            amt = Decimal((remain_qty * line.unit_price))
            disc = amt * ((line.discount_percent or 0) / Decimal(100))
            amt -= disc
            amt -= line.discount_amount or 0
            line_vals = {
                "product_id": prod.id,
                "description": line.description,
                "qty": remain_qty,
                "uom_id": line.uom_id.id,
                "unit_price": line.unit_price,
                "account_id": purch_acc_id,
                "tax_id": line.tax_id.id,
                "discount": line.discount_percent,
                "discount_amount": line.discount_amount,
                "amount": amt,
            }
            inv_vals["lines"].append(("create", line_vals))
        if not inv_vals["lines"]:
            raise Exception("Nothing left to invoice")
        inv_id = get_model("account.invoice").create(inv_vals, {"type": "in", "inv_type": "invoice"})
        inv = get_model("account.invoice").browse(inv_id)
        return {
            "next": {
                "name": "view_invoice",
                "active_id": inv_id,
            },
            "flash": "Invoice %s created from purchase order %s" % (inv.number, obj.number),
        }

    def get_delivered(self, ids, context={}):
        vals = {}
        for obj in self.browse(ids):
            if obj.state == "draft":
                vals[obj.id] = False
                return vals
            is_delivered = True
            count_line = 0
            for line in obj.lines:
                prod = line.product_id
                count_line += 1
                if not prod:
                    continue
                if prod.type not in ("stock", "consumable", "bundle", "master"):
                    continue
                remain_qty = line.qty - line.qty_received
                if remain_qty > 0:
                    is_delivered = False
                    break
            if not count_line:
                is_delivered = False
            vals[obj.id] = is_delivered
        return vals

    def get_paid(self, ids, context={}):
        vals = {}
        for obj in self.browse(ids):
            amt_paid = 0
            count_line = 0
            for inv in obj.invoices:
                if inv.state != "paid":
                    continue
                count_line += 1
                amt_paid += inv.amount_total
                if inv.deposit_notes:
                    amt_paid += inv.amount_deposit
            is_paid = amt_paid >= obj.amount_total and count_line
            vals[obj.id] = is_paid
            if obj.state=='confirmed':
                if is_paid==True and obj.is_delivered==True:
                    obj.done()
        return vals

    def get_invoice_status(self,ids,context={}):
        vals = {}
        for obj in self.browse(ids):
            inv_status = "No"
            amt_inv = 0
            for inv in obj.invoices:
                if inv.state == "voided" or inv.inv_type != 'invoice': continue
                amt_inv += inv.amount_total
                if inv.deposit_notes:
                    amt_inv += inv.amount_deposit
            if amt_inv >= obj.amount_total:
                inv_status = "Yes"
            elif amt_inv != 0:
                percent = round(Decimal(Decimal(amt_inv / obj.amount_total)*100))
                inv_status = "%s"%percent+"%"
            vals[obj.id] = inv_status
        return vals

    def void(self, ids, context={}):
        obj = self.browse(ids)[0]
        for pick in obj.pickings:
            if pick.state != "voided":
                raise Exception("There are still goods receipts for this purchase order")
        for inv in obj.invoices:
            if inv.state != "voided":
                raise Exception("There are still invoices for this purchase order")
        obj.write({"state": "voided"})

    def copy(self, ids, context):
        obj = self.browse(ids)[0]
        vals = {
            "contact_id": obj.contact_id.id,
            "date": obj.date,
            "ref": obj.ref,
            "currency_id": obj.currency_id.id,
            "tax_type": obj.tax_type,
            "lines": [],
        }
        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,
                "location_id": line.location_id.id,
            }
            vals["lines"].append(("create", line_vals))
        new_id = self.create(vals)
        new_obj = self.browse(new_id)
        return {
            "next": {
                "name": "purchase",
                "mode": "form",
                "active_id": new_id,
            },
            "flash": "Purchase order %s copied to %s" % (obj.number, new_obj.number),
        }

    def get_invoices(self, ids, context={}):
        vals = {}
        for obj in self.browse(ids):
            inv_ids = []
            for inv_line in obj.invoice_lines:
                inv_id = inv_line.invoice_id.id
                if inv_id not in inv_ids:
                    inv_ids.append(inv_id)
            vals[obj.id] = inv_ids
        return vals

    def get_pickings(self, ids, context={}):
        vals = {}
        for obj in self.browse(ids):
            pick_ids = []
            for move in obj.stock_moves:
                pick_id = move.picking_id.id
                if pick_id not in pick_ids:
                    pick_ids.append(pick_id)
            vals[obj.id] = pick_ids
        return vals

    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["payment_terms"] = contact.payment_terms
        data["price_list_id"] = contact.purchase_price_list_id.id
        
        data["bill_address_id"] = get_model("address").get_billing_address_company()
        data["ship_address_id"] = get_model("address").get_shipping_address_company()

        if contact.currency_id:
            data["currency_id"] = contact.currency_id.id
            data=self.onchange_currency(context)
        else:
            settings = get_model("settings").browse(1)
            data["currency_id"] = settings.currency_id.id
        data=self.onchange_currency(context)
        return data

    def check_received_qtys(self, ids, context={}):
        obj = self.browse(ids)[0]
        for line in obj.lines:
            if line.qty_received > (line.qty or line.qty_stock):
                raise Exception("Can not receive excess quantity for purchase order %s and product %s (order qty: %s, received qty: %s)" % (
                    obj.number, line.product_id.code, line.qty or line.qty_stock, line.qty_received))

    def get_purchase_form_template(self, ids, context={}):
        obj = self.browse(ids)[0]
        if obj.state == "draft":
            return "rfq_form"
        else:
            return "purchase_form"

    def get_amount_total_words(self, ids, context={}):
        vals = {}
        for obj in self.browse(ids):
            amount_total_words = utils.num2word(obj.amount_total)
            vals[obj.id] = amount_total_words
            return vals

    def onchange_sequence(self, context={}):
        data = context["data"]
        context['date'] = data['date']
        seq_id = data["sequence_id"]
        if not seq_id:
            seq_id = get_model("sequence").find_sequence(type="purchase_order")
        while 1:
            num = get_model("sequence").get_next_number(seq_id, context=context)
            user_id = get_active_user()
            set_active_user(1)
            res = self.search([["number", "=", num]])
            set_active_user(user_id)
            if not res:
                break
            get_model("sequence").increment_number(seq_id, context=context)
        data["number"] = num
        return data

    def delete(self, ids, **kw):
        for obj in self.browse(ids):
            if obj.state in ("confirmed", "done"):
                raise Exception("Can not delete purchase order in this status")
        super().delete(ids, **kw)

    def view_purchase(self, ids, context={}):
        obj=get_model("purchase.order.line").browse(ids)[0]
        return {
            'next': {
                'name': 'purchase',
                'active_id': obj.order_id.id,
                'mode': 'form',
            },
        }

    def copy_to_purchase_return(self,ids,context={}):
        seq_id = get_model("sequence").find_sequence(type="purchase_return")
        if not seq_id:
            raise Exception("Missing Sequence purchase return")
        for obj in self.browse(ids):
            order_vals = {}
            order_vals = {
                "contact_id":obj.contact_id.id,
                "date":obj.date,
                "ref":obj.number,
                "currency_id":obj.currency_id.id,
                "tax_type":obj.tax_type,
                "bill_address_id":obj.bill_address_id.id,
                "ship_address_id":obj.ship_address_id.id,
                "price_list_id": obj.price_list_id.id,
                "payment_terms": obj.payment_terms or obj.contact_id.payment_terms,
                "lines":[],
            }
            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,
                    "amount":line.amount,
                    "location_id":line.location_id.id,
                }
                order_vals["lines"].append(("create", line_vals))
            purchase_id = get_model("purchase.return").create(order_vals)
            purchase = get_model("purchase.return").browse(purchase_id)
        return {
            "next": {
                "name": "purchase_return",
                "mode": "form",
                "active_id": purchase_id,
            },
            "flash": "Purchase Return %s created from purchases order %s" % (purchase.number, obj.number),
        }

    def onchange_tax_type(self, context={}):
        data=context['data']
        if data['tax_type']=='no_tax':
            for line in data['lines']:
                line['tax_id']=None
        else:
            for line in data['lines']:
                product_id=line.get('product_id')
                if not product_id:
                    continue
                if line['tax_id']:
                    continue

                contact_id=data.get('contact_id')
                if contact_id:
                    contact=get_model("contact").browse(contact_id)
                    if contact.tax_payable_id:
                        line["tax_id"] = contact.tax_payable_id.id
                product=get_model("product").browse(product_id)
                if product.purchase_tax_id and not line.get('tax_id'):
                    line["tax_id"] = product.purchase_tax_id.id
                if product.categ_id and product.categ_id.purchase_tax_id and not line.get('tax_id'):
                    line["tax_id"] = product.categ_id.purchase_tax_id.id
        data=self.update_amounts(context)
        return data

    def search_product(self, clause, context={}):
        op = clause[1]
        val = clause[2]
        if isinstance(val, int):
            return ["lines.product_id.id", op, val]
        return ["lines.product_id.name", op, val]

    def onchange_date(self, context={}):
        data=self.onchange_sequence(context)
        data=self.onchange_currency(context)
        return data

    def search_location(self, clause, context={}):
        op = clause[1]
        val = clause[2]
        if isinstance(val, int):
            return ["lines.location_id.id", op, val]
        return ["lines.location_id.name", op, val]

    def find_po_line(self, ids, product_id, context={}):
        obj = self.browse(ids)[0]
        for line in obj.lines:
            if line.product_id.id == product_id:
                return line.id
        return None
Exemplo n.º 20
0
class Campaign(Model):
    _name = "mkt.campaign"
    _string = "Campaign"
    _fields = {
        "name":
        fields.Char("Campaign Name", required=True, search=True),
        "date":
        fields.Date("Date", required=True, search=True),
        "target_lists":
        fields.Many2Many("mkt.target.list", "Target Lists"),
        "email_tmpl_id":
        fields.Many2One("email.template", "Email Template"),
        "mailbox_id":
        fields.Many2One("email.mailbox", "Email Mailbox"),
        "comments":
        fields.One2Many("message", "related_id", "Comments"),
        "state":
        fields.Selection([["active", "Active"], ["inactive", "Inactive"]],
                         "Status",
                         required=True),
        "limit_day":
        fields.Integer("Daily Limit"),
        "limit_hour":
        fields.Integer("Hourly Limit"),
        "num_targets":
        fields.Integer("Number targets",
                       function="get_stats",
                       function_multi=True),
        "num_create":
        fields.Integer("Number emails created",
                       function="get_stats",
                       function_multi=True),
        "percent_create":
        fields.Float("% created", function="get_stats", function_multi=True),
        "num_sent":
        fields.Integer("Number emails sent",
                       function="get_stats",
                       function_multi=True),
        "percent_sent":
        fields.Float("% sent", function="get_stats", function_multi=True),
        "num_delivered":
        fields.Integer("Number emails delivered",
                       function="get_stats",
                       function_multi=True),
        "percent_delivered":
        fields.Float("% delivered", function="get_stats", function_multi=True),
        "num_bounced":
        fields.Integer("Number emails bounced",
                       function="get_stats",
                       function_multi=True),
        "percent_bounced":
        fields.Float("% bounced", function="get_stats", function_multi=True),
        "num_rejected":
        fields.Integer("Number emails rejected",
                       function="get_stats",
                       function_multi=True),
        "percent_rejected":
        fields.Float("% rejected", function="get_stats", function_multi=True),
        "num_opened":
        fields.Integer("Number emails opened",
                       function="get_stats",
                       function_multi=True),
        "percent_opened":
        fields.Float("% opened", function="get_stats", function_multi=True),
        "num_clicked":
        fields.Integer("Number emails clicked",
                       function="get_stats",
                       function_multi=True),
        "percent_clicked":
        fields.Float("% clicked", function="get_stats", function_multi=True),
        "num_create_day":
        fields.Integer("Emails created within day",
                       function="get_stats",
                       function_multi=True),
        "num_create_hour":
        fields.Integer("Emails created within hour",
                       function="get_stats",
                       function_multi=True),
        "emails":
        fields.One2Many("email.message", "related_id", "Emails"),
        "min_target_life":
        fields.Integer("Minimum Target Life (days)"),
    }
    _defaults = {
        "date": lambda *a: time.strftime("%Y-%m-%d"),
        "state": "active",
    }

    def create_emails_all(self, context={}):
        for obj in self.search_browse([["state", "=", "active"]]):
            obj.create_emails()

    def create_emails(self, ids, context={}):
        obj = self.browse(ids)[0]
        if obj.state != "active":
            raise Exception("Invalid state")
        if not obj.email_tmpl_id:
            raise Exception("Missing email template")
        limit = None
        if obj.limit_day:
            limit = obj.limit_day - obj.num_create_day
        if obj.limit_hour:
            l = obj.limit_hour - obj.num_create_hour
            if limit is None or l < limit:
                limit = l
        sent_emails = set()
        for email in obj.emails:
            if not email.name_id:
                continue
            if email.name_id._model != "mkt.target":
                continue
            target_id = email.name_id.id
            res = get_model("mkt.target").search(
                [["id", "=", email.name_id.id]])  # XXX
            if not res:
                continue
            target = get_model("mkt.target").browse(target_id)
            sent_emails.add(target.email)
        count = 0
        for tl in obj.target_lists:
            for target in tl.targets:
                if target.email in sent_emails:
                    continue
                if obj.min_target_life and target.target_life < obj.min_target_life:
                    continue
                if limit is not None and count >= limit:
                    break
                settings = get_model("settings").browse(1)
                data = {
                    "settings": settings,
                    "obj": target,
                }
                obj.email_tmpl_id.create_email(
                    data,
                    name_id="mkt.target,%d" % target.id,
                    related_id="mkt.campaign,%d" % obj.id,
                    mailbox_id=obj.mailbox_id)
                count += 1
                db = get_connection()
                db.commit()
        return {
            "next": {
                "name": "campaign",
                "mode": "form",
                "active_id": obj.id,
            },
            "flash": "%d emails created" % count,
        }

    def get_stats(self, ids, context={}):
        vals = {}
        for obj_id in ids:
            vals[obj_id] = {
                "num_targets": 0,
                "num_create": 0,
                "num_sent": 0,
                "num_delivered": 0,
                "num_bounced": 0,
                "num_rejected": 0,
                "num_opened": 0,
                "num_clicked": 0,
                "num_create_day": 0,
                "num_create_hour": 0,
            }
        db = get_connection()
        res = db.query(
            "SELECT c.id,COUNT(DISTINCT t.email) FROM mkt_campaign c JOIN m2m_mkt_campaign_mkt_target_list r ON r.mkt_campaign_id=c.id JOIN mkt_target t ON t.list_id=r.mkt_target_list_id WHERE c.id IN %s GROUP BY c.id",
            tuple(ids))
        for r in res:
            vals[r.id]["num_targets"] = r.count
        res = db.query(
            "SELECT related_id,COUNT(*) FROM email_message WHERE related_id IN %s GROUP BY related_id",
            tuple(["mkt.campaign,%d" % x for x in ids]))
        for r in res:
            obj_id = int(r.related_id.split(",")[1])
            v = vals[obj_id]
            v["num_create"] = r.count
        d = (datetime.now() -
             timedelta(hours=24)).strftime("%Y-%m-%d %H:%M:%S")
        res = db.query(
            "SELECT related_id,COUNT(*) FROM email_message WHERE related_id IN %s AND date>%s GROUP BY related_id",
            tuple(["mkt.campaign,%d" % x for x in ids]), d)
        for r in res:
            obj_id = int(r.related_id.split(",")[1])
            v = vals[obj_id]
            v["num_create_day"] = r.count
        d = (datetime.now() - timedelta(hours=1)).strftime("%Y-%m-%d %H:%M:%S")
        res = db.query(
            "SELECT related_id,COUNT(*) FROM email_message WHERE related_id IN %s AND date>%s GROUP BY related_id",
            tuple(["mkt.campaign,%d" % x for x in ids]), d)
        for r in res:
            obj_id = int(r.related_id.split(",")[1])
            v = vals[obj_id]
            v["num_create_hour"] = r.count
        res = db.query(
            "SELECT related_id,COUNT(*) FROM email_message WHERE related_id IN %s AND state='sent' GROUP BY related_id",
            tuple(["mkt.campaign,%d" % x for x in ids]))
        for r in res:
            obj_id = int(r.related_id.split(",")[1])
            v = vals[obj_id]
            v["num_sent"] = r.count
        res = db.query(
            "SELECT related_id,COUNT(*) FROM email_message WHERE related_id IN %s AND state='delivered' GROUP BY related_id",
            tuple(["mkt.campaign,%d" % x for x in ids]))
        for r in res:
            obj_id = int(r.related_id.split(",")[1])
            v = vals[obj_id]
            v["num_delivered"] = r.count
        res = db.query(
            "SELECT related_id,COUNT(*) FROM email_message WHERE related_id IN %s AND state='bounced' GROUP BY related_id",
            tuple(["mkt.campaign,%d" % x for x in ids]))
        for r in res:
            obj_id = int(r.related_id.split(",")[1])
            v = vals[obj_id]
            v["num_bounced"] = r.count
        res = db.query(
            "SELECT related_id,COUNT(*) FROM email_message WHERE related_id IN %s AND state='rejected' GROUP BY related_id",
            tuple(["mkt.campaign,%d" % x for x in ids]))
        for r in res:
            obj_id = int(r.related_id.split(",")[1])
            v = vals[obj_id]
            v["num_rejected"] = r.count
        res = db.query(
            "SELECT related_id,COUNT(*) FROM email_message WHERE related_id IN %s AND opened GROUP BY related_id",
            tuple(["mkt.campaign,%d" % x for x in ids]))
        for r in res:
            obj_id = int(r.related_id.split(",")[1])
            v = vals[obj_id]
            v["num_opened"] = r.count
        res = db.query(
            "SELECT related_id,COUNT(*) FROM email_message WHERE related_id IN %s AND clicked GROUP BY related_id",
            tuple(["mkt.campaign,%d" % x for x in ids]))
        for r in res:
            obj_id = int(r.related_id.split(",")[1])
            v = vals[obj_id]
            v["num_clicked"] = r.count
        for obj in self.browse(ids):
            v = vals[obj.id]
            v["percent_create"] = v["num_create"] * 100.0 / v[
                "num_targets"] if v["num_targets"] else None
            v["percent_sent"] = v["num_sent"] * 100.0 / v["num_create"] if v[
                "num_create"] else None
            v["percent_delivered"] = v["num_delivered"] * 100.0 / v[
                "num_create"] if v["num_create"] else None
            v["percent_bounced"] = v["num_bounced"] * 100.0 / v[
                "num_create"] if v["num_create"] else None
            v["percent_rejected"] = v["num_rejected"] * 100.0 / v[
                "num_create"] if v["num_create"] else None
            v["percent_opened"] = v["num_opened"] * 100.0 / v[
                "num_create"] if v["num_create"] else None
            v["percent_clicked"] = v["num_clicked"] * 100.0 / v[
                "num_create"] if v["num_create"] else None
        return vals
Exemplo n.º 21
0
class StockConsign(Model):
    _name = "stock.consign"
    _string = "Consignment Stock"
    _name_field = "location_id"
    _multi_company = True
    _fields = {
        "location_id":
        fields.Many2One("stock.location",
                        "Stock Location",
                        required=True,
                        search=True),
        "contact_id":
        fields.Many2One("contact", "Contact", required=True, search=True),
        "type":
        fields.Selection([["sale", "Sell"], ["purchase", "Purchase"]],
                         "Consignment Type",
                         required=True,
                         search=True),
        "company_id":
        fields.Many2One("company", "Company", search=True),
        "periods":
        fields.One2Many("stock.consign.period", "consign_id",
                        "Consignment Periods"),
        "order_type":
        fields.Selection(
            [["stock", "From Stock"], ["sale", "From Sales Orders"],
             ["invoice", "From Customer Invoices"]], "Create Order"),
        "new_invoice_lines":
        fields.Many2Many("account.invoice.line",
                         "New Invoice Lines",
                         function="get_new_invoice_lines"),
        "purchase_orders":
        fields.One2Many("purchase.order", "related_id", "Purchase Orders"),
    }
    _defaults = {
        "company_id": lambda *a: access.get_active_company(),
        "order_type": "stock",
    }

    def create_purchase(self, ids, context={}):
        for obj in self.browse(ids):
            if obj.type != "purchase":
                raise Exception("Wrong consignment type")
            if obj.order_type in ("stock", "sale"):  # XXX
                for period in obj.periods:
                    if period.purchase_id:
                        continue
                    period.create_purchase()
            elif obj.order_type == "invoice":
                obj.create_purchase_from_invoice()

    def create_sale(self, ids, context={}):
        for obj in self.browse(ids):
            if obj.type != "sale":
                raise Exception("Wrong consignment type")
            for period in obj.periods:
                if period.sale_id:
                    continue
                period.create_sale()

    def create_periods(self, ids, context={}):
        for obj in self.browse(ids):
            res = get_model("stock.consign.period").search(
                [["consign_id", "=", obj.id]], order="date_to desc")
            if res:
                period_id = res[0]
                period = get_model("stock.consign.period").browse(period_id)
                date_from = datetime.strptime(
                    period.date_to, "%Y-%m-%d").date() + timedelta(days=1)
            else:
                date_from = date.today()
            last_d = date.today() + timedelta(days=1)
            while date_from <= last_d:
                next_d = date_from + timedelta(days=1)
                while not is_business_day(next_d.strftime("%Y-%m-%d")):
                    next_d += timedelta(days=1)
                date_to = next_d - timedelta(days=1)
                date_from_s = date_from.strftime("%Y-%m-%d")
                date_to_s = date_to.strftime("%Y-%m-%d")
                res = get_model("stock.consign.period").search(
                    [["consign_id", "=", obj.id],
                     ["date_from", "<=", date_to_s],
                     ["date_to", ">=", date_from_s]])
                if res:
                    print("overlapping consign period already exists", obj.id,
                          date_from_s, date_to_s)
                    continue
                vals = {
                    "consign_id": obj.id,
                    "date_from": date_from_s,
                    "date_to": date_to_s,
                }
                print("create consign period", obj.id, date_from_s, date_to_s)
                get_model("stock.consign.period").create(vals)
                date_from = date_to + timedelta(days=1)

    def get_new_invoice_lines(self, ids, context={}):
        vals = {}
        for obj in self.browse(ids):
            res = get_model("company").search(
                [["contact_id", "=", obj.contact_id.id]])
            if res:
                sup_company_id = res[0]
                inv_line_ids = get_model("account.invoice.line").search(
                    [["product_id.company_id", "child_of", sup_company_id],
                     ["purchase_id", "=", None],
                     ["invoice_id.state", "=", "paid"]])
            else:
                inv_line_ids = []
            vals[obj.id] = inv_line_ids
        return vals

    def create_purchase_from_invoice(self, ids, context={}):
        obj = self.browse(ids[0])
        day_inv_lines = {}
        for inv_line in obj.new_invoice_lines:
            inv = inv_line.invoice_id
            day_inv_lines.setdefault(inv.date, []).append(inv_line.id)
        for d, inv_line_ids in day_inv_lines.items():
            purch_vals = {
                "date": d,
                "company_id": obj.company_id.id,
                "contact_id": obj.contact_id.id,
                "tax_type": "tax_ex",
                "lines": [],
                "related_id": "stock.consign,%d" % obj.id,
            }
            prods = {}
            for inv_line in get_model("account.invoice.line").browse(
                    inv_line_ids):
                prod = inv_line.product_id
                prods.setdefault(prod.id, {
                    "qty": 0,
                    "amt": 0,
                })
                qty = inv_line.qty or 0
                amt = (prod.purchase_price or 0) * qty
                prods[prod.id]["qty"] += qty
                prods[prod.id]["amt"] += amt
            for prod_id, prod_vals in prods.items():
                qty = prod_vals["qty"]
                amt = prod_vals["amt"]
                price = amt / qty if qty else 0
                line_vals = {
                    "product_id": prod.id,
                    "description": prod.description or "/",
                    "qty": qty,
                    "uom_id": prod.uom_id.id,
                    "unit_price": price,
                }
                purch_vals["lines"].append(("create", line_vals))
            purch_id = get_model("purchase.order").create(purch_vals)
            get_model("account.invoice.line").write(inv_line_ids,
                                                    {"purchase_id": purch_id})
Exemplo n.º 22
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)
Exemplo n.º 23
0
class StatementLine(Model):
    _name = "account.statement.line"
    _order = "date,id"
    _name_field = "description"
    _fields = {
        "statement_id":
        fields.Many2One("account.statement",
                        "Statement",
                        required=True,
                        on_delete="cascade"),
        "state":
        fields.Selection([["not_reconciled", "Not Reconciled"],
                          ["reconciled", "Reconciled"]],
                         "Status",
                         required=True),
        "date":
        fields.Date("Date", required=True),
        "description":
        fields.Char("Description", size=256),
        "spent":
        fields.Decimal("Spent", required=True),
        "received":
        fields.Decimal("Received", required=True),
        "balance":
        fields.Decimal("Balance", required=True, readonly=True),
        "bank_reconcile_id":
        fields.Many2One("account.bank.reconcile", "Bank Reconciliation"),
        "move_lines":
        fields.Many2Many("account.move.line", "Account Entries"),
        "account_id":
        fields.Many2One("account.account",
                        "Account",
                        function="_get_related",
                        function_context={"path": "statement_id.account_id"}),
        "account_balance":
        fields.Decimal("Accounting Balance", function="get_account_balance"),
    }
    _defaults = {
        "state": "not_reconciled",
        "spent": 0,
        "received": 0,
    }

    def get_reconcile_lines(self, ids):
        st_line_ids = set(ids)
        new_st_line_ids = set(ids)
        acc_line_ids = set()
        while 1:
            new_acc_line_ids = set()
            for st_line in get_model("account.statement.line").browse(
                    list(new_st_line_ids)):
                for acc_line in st_line.move_lines:
                    if acc_line.id not in acc_line_ids and acc_line.id not in new_acc_line_ids:
                        new_acc_line_ids.add(acc_line.id)
            if not new_acc_line_ids:
                break
            acc_line_ids |= new_acc_line_ids
            new_st_line_ids = set()
            for acc_line in get_model("account.move.line").browse(
                    list(new_acc_line_ids)):
                for st_line in acc_line.statement_lines:
                    if st_line.id not in st_line_ids and st_line.id not in new_st_line_ids:
                        new_st_line_ids.add(st_line.id)
            if not new_st_line_ids:
                break
            st_line_ids |= new_st_line_ids
        return list(st_line_ids), list(acc_line_ids)

    def reconcile(self, ids, context={}):
        st_line_ids, acc_line_ids = self.get_reconcile_lines(ids)
        total_st = 0
        for st_line in get_model("account.statement.line").browse(st_line_ids):
            total_st += st_line.received - st_line.spent
        total_acc = 0
        for acc_line in get_model("account.move.line").browse(acc_line_ids):
            total_acc += acc_line.debit - acc_line.credit
        if total_st - total_acc != 0:
            return {
                "next": {
                    "name": "reconcile_adjust",
                    "line_id": ids[0],
                }
            }
        get_model("account.statement.line").write(st_line_ids,
                                                  {"state": "reconciled"})
        get_model("account.move.line").write(acc_line_ids,
                                             {"state": "reconciled"})

    def unreconcile(self, ids, context={}):
        st_line_ids, acc_line_ids = self.get_reconcile_lines(ids)
        get_model("account.statement.line").write(st_line_ids,
                                                  {"state": "not_reconciled"})
        get_model("account.move.line").write(acc_line_ids,
                                             {"state": "not_reconciled"})

    def get_account_balance(self, ids, context={}):
        vals = {}
        for obj in self.browse(ids):
            bal = 0
            line_ids = set()
            for line in obj.move_lines:
                if line.id in line_ids:
                    continue
                bal += line.debit - line.credit
                line_ids.add(line.id)
            vals[obj.id] = bal
        return vals

    def onchange_move_lines(self, context={}):
        data = context["data"]
        move_line_ids = data["move_lines"]
        move_line_ids = list(set(move_line_ids))
        bal = 0
        for line in get_model("account.move.line").browse(move_line_ids):
            bal += line.debit - line.credit
        data["account_balance"] = bal
        return data
Exemplo n.º 24
0
class PurchaseOrderLine(Model):
    _name = "purchase.order.line"
    _name_field = "order_id"
    _fields = {
        "order_id": fields.Many2One("purchase.order", "Purchase Order", required=True, on_delete="cascade"),
        "product_id": fields.Many2One("product", "Product"),
        "description": fields.Text("Description", required=True),
        "qty": fields.Decimal("Qty", required=True, scale=6),
        "uom_id": fields.Many2One("uom", "UoM", required=True),
        "unit_price": fields.Decimal("Unit Price", required=True, scale=6),
        "tax_id": fields.Many2One("account.tax.rate", "Tax Rate"),
        "amount": fields.Decimal("Amount", function="get_amount", function_multi=True, store=True, function_order=1),
        "amount_cur": fields.Decimal("Amount", function="get_amount", function_multi=True, store=True, function_order=1),
        "qty_received": fields.Decimal("Received Qty", function="get_qty_received"),
        "qty_invoiced": fields.Decimal("Invoiced Qty", function="get_qty_invoiced"),
        "contact_id": fields.Many2One("contact", "Contact", function="_get_related", function_search="_search_related", function_context={"path": "order_id.contact_id"}),
        "date": fields.Date("Date", function="_get_related", function_search="_search_related", function_context={"path": "order_id.date"}),
        "state": fields.Selection([("draft", "Draft"), ("confirmed", "Confirmed"), ("done", "Completed"), ("voided", "Voided")], "Status", function="_get_related", function_search="_search_related", function_context={"path": "order_id.state"}),
        "sale_id": fields.Many2One("sale.order", "Sales Order"),
        "location_id": fields.Many2One("stock.location", "Location", condition=[["type", "=", "internal"]]),
        "product_categs": fields.Many2Many("product.categ", "Product Categories", function="_get_related", function_context={"path": "product_id.categs"}, function_search="_search_related", search=True),
        "product_categ_id": fields.Many2Many("product.categ", "Product Category", function="_get_related", function_context={"path": "product_id.categ_id"}, function_search="_search_related", search=True),
        "agg_amount": fields.Decimal("Total Amount", agg_function=["sum", "amount"]),
        "agg_qty": fields.Decimal("Total Order Qty", agg_function=["sum", "qty"]),
        "ship_method_id": fields.Many2One("ship.method", "Shipping Method"),
        "discount_amount": fields.Decimal("Disc Amt"),
        "qty_stock": fields.Decimal("Qty (Stock UoM)"),
    }
    _order = "order_id desc,id"

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

    def write(self, ids, vals, context={}):
        super(PurchaseOrderLine, self).write(ids, vals, context)
        self.function_store(ids)

    def get_amount(self, ids, context={}):
        settings = get_model("settings").browse(1)
        vals = {}
        for line in self.browse(ids):
            uom_factor=line.product_id.purchase_to_stock_uom_factor
            amt=line.qty * line.unit_price
            if uom_factor:
                amt=amt*uom_factor
            amt = amt-(line.discount_amount or 0)
            order = line.order_id
            vals[line.id] = {
                "amount": amt,
                "amount_cur": get_model("currency").convert(amt, order.currency_id.id, settings.currency_id.id),
            }
        return vals

    def get_qty_received(self, ids, context={}):
        order_ids = []
        for obj in self.browse(ids):
            order_ids.append(obj.order_id.id)
        order_ids = list(set(order_ids))
        vals = {}
        for order in get_model("purchase.order").browse(order_ids):
            received_qtys = {}
            for move in order.stock_moves:
                if move.state != "done":
                    continue
                prod_id = move.product_id.id
                k = (prod_id, move.location_to_id.id)
                received_qtys.setdefault(k, 0)
                received_qtys[k] += move.qty  # XXX: uom
                k = (prod_id, move.location_from_id.id)
                received_qtys.setdefault(k, 0)
                received_qtys[k] -= move.qty  # XXX: uom
            for line in order.lines:
                if line.id not in ids:
                    continue
                k = (line.product_id.id, line.location_id.id)
                received_qty = received_qtys.get(k, 0)  # XXX: uom
                used_qty = min(line.qty, received_qty)
                vals[line.id] = used_qty
                if k in received_qtys:
                    received_qtys[k] -= used_qty
            for line in reversed(order.lines):
                k = (line.product_id.id, line.location_id.id)
                remain_qty = received_qtys.get(k, 0)  # XXX: uom
                if remain_qty:
                    vals[line.id] += remain_qty
                    received_qtys[k] -= remain_qty
        vals = {x: vals[x] for x in ids}
        return vals

    def get_qty_invoiced(self, ids, context={}):
        order_ids = []
        for obj in self.browse(ids):
            order_ids.append(obj.order_id.id)
        order_ids = list(set(order_ids))
        vals = {}
        for order in get_model("purchase.order").browse(order_ids):
            inv_qtys = {}
            for inv in order.invoices:
                if inv.state not in ("draft","waiting_payment","paid"):
                    continue
                for line in inv.lines:
                    prod_id = line.product_id.id
                    inv_qtys.setdefault(prod_id, 0)
                    inv_qtys[prod_id] += line.qty 
            for line in order.lines:
                if line.id not in ids:
                    continue
                prod_id = line.product_id.id
                inv_qty = inv_qtys.get(prod_id, 0)  # XXX: uom
                used_qty = min(line.qty, inv_qty)
                vals[line.id] = used_qty
                if prod_id in inv_qtys:
                    inv_qtys[prod_id] -= used_qty
            for line in reversed(order.lines):
                prod_id = line.product_id.id
                remain_qty = inv_qtys.get(prod_id, 0)  # XXX: uom
                if remain_qty:
                    vals[line.id] += remain_qty
                    inv_qtys[prod_id] -= remain_qty
        vals = {x: vals[x] for x in ids}
        return vals
Exemplo n.º 25
0
class ProductCateg(Model):
    _name = "product.categ"
    _string = "Product Category"
    _export_field = "code"
    _multi_company = True
    _key = ["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",
                        condition=[["type", "!=", "view"]],
                        multi_company=True),
        "sale_tax_id":
        fields.Many2One("account.tax.rate", "Sales Tax"),
        "purchase_account_id":
        fields.Many2One("account.account",
                        "Purchase Account",
                        condition=[["type", "!=", "view"]],
                        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",
                        condition=[["type", "!=", "view"]],
                        multi_company=True),
        "stock_account_id":
        fields.Many2One("account.account",
                        "Inventory Account",
                        condition=[["type", "!=", "view"]],
                        multi_company=True),
        "companies":
        fields.Many2Many("company", "Companies"),
    }
    _order = "name"

    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,
        }
Exemplo n.º 26
0
class Project(Model):
    _name = "project"
    _string = "Project"
    _audit_log = True
    _fields = {
        "name":
        fields.Char("Project Name", required=True, search=True),
        "number":
        fields.Char("Project Number", search=True),
        "contact_id":
        fields.Many2One("contact", "Customer", search=True),
        "start_date":
        fields.Date("Start Date", required=True),
        "end_date":
        fields.Date("End Date"),
        "product_id":
        fields.Many2One("product", "Product"),  # XXX: deprecated
        "comments":
        fields.One2Many("message", "related_id", "Comments"),
        "documents":
        fields.One2Many("document", "related_id", "Documents"),
        "state":
        fields.Selection([["in_progress", "In Progress"],
                          ["done", "Completed"], ["canceled", "Canceled"]],
                         "Status",
                         required=True),
        "jobs":
        fields.One2Many("job", "project_id", "Jobs"),
        "tasks":
        fields.One2Many("task", "project_id", "Tasks"),
        "work_time":
        fields.One2Many("work.time", "job_id", "Work Time"),
        "claims":
        fields.One2Many("product.claim", "project_id", "Claim Bills"),
        "borrows":
        fields.One2Many("product.borrow", "project_id", "Borrow Requests"),
        "description":
        fields.Text("Description"),
        "track_id":
        fields.Many2One("account.track.categ", "Actual Cost Tracking Code"),
        "track_balance":
        fields.Decimal("Tracking Balance",
                       function="_get_related",
                       function_context={"path": "track_id.balance"}),
        "sub_tracks":
        fields.One2Many("account.track.categ",
                        None,
                        "Actual Cost Sub-Tracking Codes",
                        function="_get_related",
                        function_context={"path": "track_id.sub_tracks"}),
        "est_track_id":
        fields.Many2One("account.track.categ", "Estimate Cost Tracking Code"),
        "est_track_balance":
        fields.Decimal("Tracking Balance",
                       function="_get_related",
                       function_context={"path": "est_track_id.balance"}),
        "est_sub_tracks":
        fields.One2Many("account.track.categ",
                        None,
                        "Est. Cost Sub-Tracking Codes",
                        function="_get_related",
                        function_context={"path": "est_track_id.sub_tracks"}),
        "issues":
        fields.One2Many("issue", "project_id", "Issues"),
        "resources":
        fields.Many2Many("service.resource", "Resources"),
        "milestones":
        fields.One2Many("project.milestone", "project_id", "Milestones"),
    }
    _order = "start_date"

    _defaults = {
        "start_date": lambda *a: time.strftime("%Y-%m-%d"),
        "state": "in_progress",
    }

    def copy(self, ids, context={}):
        obj = self.browse(ids[0])
        vals = {
            "name": obj.name,
            "number": obj.number,
            "contact_id": obj.contact_id.id,
            "start_date": obj.start_date,
            "end_date": obj.end_date,
            "description": description,
            "resources": [("set", [r.id for r in obj.resources])],
        }
        new_proj_id = self.create(vals, context=context)
        new_proj = self.browse(new_proj_id)
        track = obj.track_id
        if track:
            vals = {
                "name": track.name,  # XXX
                "type": track.type,
                "code": track.code,  # XXX
            }
            new_track_id = get_model("account.track.categ").create(vals)
            new_proj.write({"track_id": new_track_id})
            for subtrack in track.sub_tracks:
                vals = {
                    "parent_id": new_track_id,
                    "name": subtrack.name,
                    "type": subtrack.type,
                    "code": subtrack.code,
                }
                get_model("account.track.categ").create(vals)
        return {
            "next": {
                "name": "project",
                "mode": "form",
                "active_id": new_proj_id,
            },
            "flash": "New project copied from %s" % obj.name,
        }
Exemplo n.º 27
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
Exemplo n.º 28
0
class Resource(Model):
    _name = "service.resource"
    _string = "Resource"
    _fields = {
        "name":
        fields.Char("Name", required=True, search=True),
        "employee_id":
        fields.Many2One("hr.employee", "Employee", search=True),
        "product_categs":
        fields.Many2Many("product.categ", "Product Categories"),
        "regions":
        fields.Many2Many("region", "Regions"),
        "skill_level_id":
        fields.Many2One("skill.level", "Skill Level"),
        "allocs":
        fields.One2Many("service.resource.alloc", "resource_id",
                        "Resource Allocations"),
        "comments":
        fields.One2Many("message", "related_id", "Comments"),
        "can_alloc":
        fields.Boolean("Can Allocate", function="get_can_alloc"),
        "is_avail":
        fields.Boolean("Is Available",
                       store=False,
                       function_search="is_avail_search"),
        "documents":
        fields.One2Many("document", "related_id", "Documents"),
        "time_sheets":
        fields.One2Many("time.sheet", "resource_id", "Time Sheets"),
        "user_id":
        fields.Many2One("base.user", "User"),
        "type":
        fields.Selection([["person", "Person"], ["machine", "Machine"]],
                         "Resource Type"),
        "product_id":
        fields.Many2One("product", "Product"),
    }
    _order = "name"

    def name_get(self, ids, context={}):
        vals = []
        for obj in self.browse(ids, context=context):
            name = obj.name
            can_alloc = obj.can_alloc
            if can_alloc == False:
                name += " [WARNING]"
            vals.append((obj.id, name))
        return vals

    def get_can_alloc(self, ids, context={}):
        print("get_can_alloc", ids, context)
        job_id = context.get("job_id")
        if not job_id:
            return {id: None for id in ids}
        job = get_model("job").browse(job_id)
        prod_ids = []
        for item in job.items:
            sitem = item.service_item_id
            if sitem.product_id:
                prod_ids.append(sitem.product_id.id)
        print("prod_ids", prod_ids)
        vals = {}
        for obj in self.browse(ids):
            if job.skill_level_id:
                if obj.skill_level_id and obj.skill_level_id.level < job.skill_level_id.level:
                    vals[obj.id] = False
                    continue
            if obj.product_categs and prod_ids:
                categ_ids = [c.id for c in obj.product_categs]
                res = get_model("product").search(
                    [["id", "in", prod_ids],
                     ["categs.id", "child_of", categ_ids]])
                if not res:
                    vals[obj.id] = False
                    continue
            region = job.contact_id.region_id
            if obj.regions and region:
                region_ids = [r.id for r in obj.regions]
                if region.id not in region_ids:
                    vals[obj.id] = False
                    continue
            vals[obj.id] = True
        return vals

    def is_avail_search(self, clause, context={}):
        print("is_avail_search", clause, context)
        time_start = context.get("time_start")
        time_stop = context.get("time_stop")
        job_id = context.get("job_id")
        if job_id:
            job = get_model("job").browse(job_id)
            if job.state == "planned":
                job.write({"state": "allocated"})
        if not time_start or not time_stop:
            return []
        ids = []
        for alloc in get_model("service.resource.alloc").search_browse(
            [["time_stop", ">", time_start], ["time_start", "<", time_stop]]):
            ids.append(alloc.resource_id.id)
        ids = list(set(ids))
        return [["id", "not in", ids]]
Exemplo n.º 29
0
class PurchaseOrder(Model):
    _name = "purchase.order"
    _string = "Purchase Order"
    _audit_log = True
    _name_field = "number"
    _multi_company = True
    _key = ["company_id", "number"]
    _fields = {
        "number":
        fields.Char("Number", required=True, search=True),
        "ref":
        fields.Char("Ref", search=True),
        "contact_id":
        fields.Many2One("contact", "Supplier", required=True, search=True),
        "customer_id":
        fields.Many2One("contact", "Customer", search=True),
        "date":
        fields.Date("Date", required=True, search=True),
        "date_required":
        fields.Date("Required Date"),
        "state":
        fields.Selection([("draft", "Draft"), ("confirmed", "Confirmed"),
                          ("done", "Completed"), ("voided", "Voided")],
                         "Status",
                         required=True),
        "lines":
        fields.One2Many("purchase.order.line", "order_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_total_cur":
        fields.Decimal("Total",
                       function="get_amount",
                       function_multi=True,
                       store=True),
        "amount_total_words":
        fields.Char("Total Words", function="get_amount_total_words"),
        "qty_total":
        fields.Decimal("Total Quantity", function="get_qty_total"),
        "currency_id":
        fields.Many2One("currency", "Currency", required=True),
        "tax_type":
        fields.Selection([["tax_ex", "Tax Exclusive"],
                          ["tax_in", "Tax Inclusive"], ["no_tax", "No Tax"]],
                         "Tax Type",
                         required=True),
        "invoice_lines":
        fields.One2Many("account.invoice.line", "related_id", "Invoice Lines"),
        #"stock_moves": fields.One2Many("stock.move","purch_id","Stock Moves"),
        "invoices":
        fields.Many2Many("account.invoice",
                         "Invoices",
                         function="get_invoices"),
        "pickings":
        fields.Many2Many("stock.picking",
                         "Stock Pickings",
                         function="get_pickings"),
        "is_delivered":
        fields.Boolean("Delivered", function="get_delivered"),
        "is_paid":
        fields.Boolean("Paid", function="get_paid"),
        "comments":
        fields.One2Many("message", "related_id", "Comments"),
        "location_id":
        fields.Many2One("stock.location", "Warehouse"),  # XXX: deprecated
        "delivery_date":
        fields.Date("Delivery Date"),
        "ship_method_id":
        fields.Many2One("ship.method", "Shipping Method"),  # XXX: deprecated
        "payment_terms":
        fields.Text("Payment Terms"),
        "ship_term_id":
        fields.Many2One("ship.term", "Shipping Terms"),
        "price_list_id":
        fields.Many2One("price.list",
                        "Price List",
                        condition=[["type", "=", "purchase"]]),
        "documents":
        fields.One2Many("document", "related_id", "Documents"),
        "company_id":
        fields.Many2One("company", "Company"),
        "purchase_type_id":
        fields.Many2One("purchase.type", "Purchase Type"),
        "other_info":
        fields.Text("Other Info"),
        "bill_address_id":
        fields.Many2One("address", "Billing Address"),
        "ship_address_id":
        fields.Many2One("address", "Shipping Address"),
        "sequence_id":
        fields.Many2One("sequence", "Number Sequence"),
        "stock_moves":
        fields.One2Many("stock.move", "related_id", "Stock Movements"),
        "agg_amount_total":
        fields.Decimal("Total Amount", agg_function=["sum", "amount_total"]),
        "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"]),
        "agg_amount_subtotal":
        fields.Decimal("Total Amount w/o Tax",
                       agg_function=["sum", "amount_subtotal"]),
        "user_id":
        fields.Many2One("base.user", "Owner", search=True),
        "emails":
        fields.One2Many("email.message", "related_id", "Emails"),
        "related_id":
        fields.Reference([["sale.order", "Sales Order"],
                          ["stock.consign", "Consignment Stock"]],
                         "Related To"),
    }
    _order = "date desc,number desc"

    _sql_constraints = [("key_uniq", "unique (company_id, number)",
                         "The number of each company must be unique!")]

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

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

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

    def create(self, vals, **kw):
        id = super(PurchaseOrder, self).create(vals, **kw)
        self.function_store([id])
        return id

    def write(self, ids, vals, **kw):
        super(PurchaseOrder, self).write(ids, vals, **kw)
        self.function_store(ids)

    def confirm(self, ids, context={}):
        settings = get_model("settings").browse(1)
        for obj in self.browse(ids):
            if obj.state != "draft":
                raise Exception("Invalid state")
            for line in obj.lines:
                prod = line.product_id
                if prod and prod.type in ("stock", "consumable",
                                          "bundle") and not line.location_id:
                    raise Exception("Missing location for product %s" %
                                    prod.code)
            obj.write({"state": "confirmed"})
            if settings.purchase_copy_picking:
                res = obj.copy_to_picking()
                picking_id = res["picking_id"]
                get_model("stock.picking").pending([picking_id])
            if settings.purchase_copy_invoice:
                obj.copy_to_invoice()
            obj.trigger("confirm")

    def done(self, ids, context={}):
        for obj in self.browse(ids):
            if obj.state != "confirmed":
                raise Exception("Invalid state")
            obj.write({"state": "done"})

    def reopen(self, ids, context={}):
        for obj in self.browse(ids):
            if obj.state != "done":
                raise Exception("Invalid state")
            obj.write({"state": "confirmed"})

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

    def get_amount(self, ids, context={}):
        settings = get_model("settings").browse(1)
        res = {}
        for obj in self.browse(ids):
            vals = {}
            subtotal = 0
            tax = 0
            for line in obj.lines:
                if line.tax_id:
                    line_tax = get_model("account.tax.rate").compute_tax(
                        line.tax_id.id, line.amount, tax_type=obj.tax_type)
                else:
                    line_tax = 0
                tax += line_tax
                if obj.tax_type == "tax_in":
                    subtotal += line.amount - line_tax
                else:
                    subtotal += line.amount
            vals["amount_subtotal"] = subtotal
            vals["amount_tax"] = tax
            vals["amount_total"] = subtotal + tax
            vals["amount_total_cur"] = get_model("currency").convert(
                vals["amount_total"], obj.currency_id.id,
                settings.currency_id.id)
            res[obj.id] = vals
        return res

    def get_qty_total(self, ids, context={}):
        res = {}
        for obj in self.browse(ids):
            qty = sum([line.qty for line in obj.lines])
            res[obj.id] = qty or 0
        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 = Decimal(((line.get("qty") or 0) *
                           (line.get("unit_price") or 0)) -
                          (line.get("discount_amount") or 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 onchange_product(self, context):
        data = context["data"]
        path = context["path"]
        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
        pricelist_id = data["price_list_id"]
        price = None
        if pricelist_id:
            price = get_model("price.list").get_price(pricelist_id, prod.id, 1)
            price_list = get_model("price.list").browse(pricelist_id)
            price_currency_id = price_list.currency_id.id
        if price is None:
            price = prod.purchase_price
            settings = get_model("settings").browse(1)
            price_currency_id = settings.currency_id.id
        if price is not None:
            currency_id = data["currency_id"]
            price_cur = get_model("currency").convert(price, price_currency_id,
                                                      currency_id)
            line["unit_price"] = price_cur
        if prod.purchase_tax_id is not None:
            line["tax_id"] = prod.purchase_tax_id.id
        if prod.location_id:
            line["location_id"] = prod.location_id.id
        data = self.update_amounts(context)
        return data

    def onchange_qty(self, context):
        data = context["data"]
        path = context["path"]
        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)
        pricelist_id = data["price_list_id"]
        qty = line["qty"]
        price = None
        if pricelist_id:
            price = get_model("price.list").get_price(pricelist_id, prod.id,
                                                      qty)
            price_list = get_model("price.list").browse(pricelist_id)
            price_currency_id = price_list.currency_id.id
        if price is None:
            price = prod.purchase_price
            settings = get_model("settings").browse(1)
            price_currency_id = settings.currency_id.id
        if price is not None:
            currency_id = data["currency_id"]
            price_cur = get_model("currency").convert(price, price_currency_id,
                                                      currency_id)
            line["unit_price"] = price_cur
        data = self.update_amounts(context)
        return data

    def copy_to_picking(self, ids, context={}):
        settings = get_model("settings").browse(1)
        obj = self.browse(ids[0])
        contact = obj.contact_id
        pick_vals = {
            "type": "in",
            "ref": obj.number,
            "related_id": "purchase.order,%s" % obj.id,
            "contact_id": contact.id,
            "currency_id": obj.currency_id.id,
            "lines": [],
        }
        if obj.delivery_date:
            pick_vals["date"] = obj.delivery_date
        if contact and contact.pick_in_journal_id:
            pick_vals["journal_id"] = contact.pick_in_journal_id.id
        res = get_model("stock.location").search([["type", "=", "supplier"]],
                                                 order="id")
        if not res:
            raise Exception("Supplier location not found")
        supp_loc_id = res[0]
        res = get_model("stock.location").search([["type", "=", "internal"]])
        if not res:
            raise Exception("Warehouse not found")
        wh_loc_id = res[0]
        if not settings.currency_id:
            raise Exception("Missing currency in financial settings")
        for line in obj.lines:
            prod = line.product_id
            if prod.type not in ("stock", "consumable"):
                continue
            remain_qty = (line.qty_stock or line.qty) - line.qty_received
            if remain_qty <= 0:
                continue
            unit_price = line.amount / line.qty if line.qty else 0
            if obj.tax_type == "tax_in":
                if line.tax_id:
                    tax_amt = get_model("account.tax.rate").compute_tax(
                        line.tax_id.id, unit_price, tax_type=obj.tax_type)
                else:
                    tax_amt = 0
                cost_price_cur = round(unit_price - tax_amt, 2)
            else:
                cost_price_cur = unit_price
            if line.qty_stock:
                purch_uom = prod.uom_id
                if not prod.purchase_to_stock_uom_factor:
                    raise Exception(
                        "Missing purchase order to stock UoM factor for product %s"
                        % prod.code)
                cost_price_cur /= prod.purchase_to_stock_uom_factor
            else:
                purch_uom = line.uom_id
            cost_price = get_model("currency").convert(cost_price_cur,
                                                       obj.currency_id.id,
                                                       settings.currency_id.id)
            cost_amount = cost_price * remain_qty
            line_vals = {
                "product_id": prod.id,
                "qty": remain_qty,
                "uom_id": purch_uom.id,
                "cost_price_cur": cost_price_cur,
                "cost_price": cost_price,
                "cost_amount": cost_amount,
                "location_from_id": supp_loc_id,
                "location_to_id": line.location_id.id or wh_loc_id,
                "related_id": "purchase.order,%s" % obj.id,
            }
            pick_vals["lines"].append(("create", line_vals))
        if not pick_vals["lines"]:
            raise Exception("Nothing left to receive")
        pick_id = get_model("stock.picking").create(pick_vals,
                                                    {"pick_type": "in"})
        pick = get_model("stock.picking").browse(pick_id)
        return {
            "next": {
                "name": "pick_in",
                "mode": "form",
                "active_id": pick_id,
            },
            "flash":
            "Goods receipt %s created from purchase order %s" %
            (pick.number, obj.number),
            "picking_id":
            pick_id,
        }

    def copy_to_invoice(self, ids, context={}):
        id = ids[0]
        obj = self.browse(id)
        contact = obj.contact_id
        inv_vals = {
            "type": "in",
            "inv_type": "invoice",
            "ref": obj.number,
            "related_id": "purchase.order,%s" % obj.id,
            "contact_id": obj.contact_id.id,
            "currency_id": obj.currency_id.id,
            "lines": [],
            "tax_type": obj.tax_type,
        }
        if contact.purchase_journal_id:
            inv_vals["journal_id"] = contact.purchase_journal_id.id
            if contact.purchase_journal_id.sequence_id:
                inv_vals[
                    "sequence_id"] = contact.purchase_journal_id.sequence_id.id
        for line in obj.lines:
            prod = line.product_id
            remain_qty = line.qty - line.qty_invoiced
            if remain_qty <= 0:
                continue
            purch_acc_id = None
            if prod:
                purch_acc_id = prod.purchase_account_id.id
                if not purch_acc_id and prod.parent_id:
                    purch_acc_id = prod.parent_id.purchase_account_id.id
                if not purch_acc_id and prod.categ_id and prod.categ_id.purchase_account_id:
                    purch_acc_id = prod.categ_id.purchase_account_id.id
            line_vals = {
                "product_id": prod.id,
                "description": line.description,
                "qty": remain_qty,
                "uom_id": line.uom_id.id,
                "unit_price": line.unit_price,
                "account_id": prod and prod.purchase_account_id.id or None,
                "tax_id": line.tax_id.id,
                "amount": line.amount,
            }
            inv_vals["lines"].append(("create", line_vals))
        if not inv_vals["lines"]:
            raise Exception("Nothing left to invoice")
        inv_id = get_model("account.invoice").create(inv_vals, {
            "type": "in",
            "inv_type": "invoice"
        })
        inv = get_model("account.invoice").browse(inv_id)
        return {
            "next": {
                "name": "view_invoice",
                "active_id": inv_id,
            },
            "flash":
            "Invoice %s created from purchase order %s" %
            (inv.number, obj.number),
        }

    def get_delivered(self, ids, context={}):
        vals = {}
        #import pdb; pdb.set_trace()
        for obj in self.browse(ids):
            is_delivered = True
            for line in obj.lines:
                prod = line.product_id
                if prod.type not in ("stock", "consumable"):
                    continue
                remain_qty = line.qty - line.qty_received
                if remain_qty > 0:
                    is_delivered = False
                    break
            vals[obj.id] = is_delivered
        return vals

    def get_paid(self, ids, context={}):
        vals = {}
        for obj in self.browse(ids):
            amt_paid = 0
            for inv_line in obj.invoice_lines:
                inv = inv_line.invoice_id
                if inv.state != "paid":
                    continue
                amt_paid += inv_line.amount
            is_paid = amt_paid >= obj.amount_subtotal
            vals[obj.id] = is_paid
        return vals

    def void(self, ids, context={}):
        obj = self.browse(ids)[0]
        for pick in obj.pickings:
            if pick.state != "voided":
                raise Exception(
                    "There are still goods receipts for this purchase order")
        for inv in obj.invoices:
            if inv.state != "voided":
                raise Exception(
                    "There are still invoices for this purchase order")
        obj.write({"state": "voided"})

    def copy(self, ids, context):
        obj = self.browse(ids)[0]
        vals = {
            "contact_id": obj.contact_id.id,
            "date": obj.date,
            "ref": obj.ref,
            "currency_id": obj.currency_id.id,
            "tax_type": obj.tax_type,
            "lines": [],
        }
        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,
            }
            vals["lines"].append(("create", line_vals))
        new_id = self.create(vals)
        new_obj = self.browse(new_id)
        return {
            "next": {
                "name": "purchase",
                "mode": "form",
                "active_id": new_id,
            },
            "flash":
            "Purchase order %s copied to %s" % (obj.number, new_obj.number),
        }

    def get_invoices(self, ids, context={}):
        vals = {}
        for obj in self.browse(ids):
            inv_ids = []
            for inv_line in obj.invoice_lines:
                inv_id = inv_line.invoice_id.id
                if inv_id not in inv_ids:
                    inv_ids.append(inv_id)
            vals[obj.id] = inv_ids
        return vals

    def get_pickings(self, ids, context={}):
        vals = {}
        for obj in self.browse(ids):
            pick_ids = []
            for move in obj.stock_moves:
                pick_id = move.picking_id.id
                if pick_id not in pick_ids:
                    pick_ids.append(pick_id)
            vals[obj.id] = pick_ids
        return vals

    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["payment_terms"] = contact.payment_terms
        data["price_list_id"] = contact.purchase_price_list_id.id
        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 check_received_qtys(self, ids, context={}):
        obj = self.browse(ids)[0]
        for line in obj.lines:
            if line.qty_received > (line.qty_stock or line.qty):
                raise Exception(
                    "Can not receive excess quantity for purchase order %s and product %s (order qty: %s, received qty: %s)"
                    % (obj.number, line.product_id.code, line.qty_stock
                       or line.qty, line.qty_received))

    def get_purchase_form_template(self, ids, context={}):
        obj = self.browse(ids)[0]
        if obj.state == "draft":
            return "rfq_form"
        else:
            return "purchase_form"

    def get_amount_total_words(self, ids, context={}):
        vals = {}
        for obj in self.browse(ids):
            amount_total_words = utils.num2word(obj.amount_total)
            vals[obj.id] = amount_total_words
            return vals

    def onchange_sequence(self, context={}):
        data = context["data"]
        seq_id = data["sequence_id"]
        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:
                break
            get_model("sequence").increment_number(seq_id, context=context)
        data["number"] = num
        return data

    def delete(self, ids, **kw):
        for obj in self.browse(ids):
            if obj.state in ("confirmed", "done"):
                raise Exception("Can not delete purchase order in this status")
        super().delete(ids, **kw)
Exemplo n.º 30
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